如何使用 HMTL5 Javascript Canvas 获取碰撞的三个形状的交集并删除未碰撞的部分?

How can I get the intersection of three shapes colliding and delete the parts that are not colliding using HMTL5 Javascript Canvas?

我最近发布了一个专门针对 KonvaJs 的类似问题 here,但是,我没有得到任何答案,我想直接了解 KonvaJs 使用的根源。我想知道是否有使用 HMTL5 Javascript Canvas.

解决以下问题的标准方法

If I am given 3 circles and they are positioned as the primary color circles (3 circles intersecting each other), is there a function that might help me to delete the parts that are not colliding with anything and just keep the intersecting parts?

Another example could be drawing three lines in such a way that it forms a triangle. By deleting the parts that are not colliding, we would end up with 3 points (at least visually, not sure if I end up with 3 in total or 6 from stacking up), which are the edges of the previous triangle.

根据我搜索到的内容,我可以通过在发生碰撞的部分上使用 hit region and then somehow applying the clip function 来检测碰撞区域,从而只获得所需的结果。我在这个解决方案中看到的问题是,如果每个形状都是可拖动的,则形状可能会被剪裁但不会跟随拖动的形状。

我的另一个想法是检测并切割碰撞区域,然后删除没有碰撞的区域,然后将它们组合在一起。 (不知道怎么切小块。。。)

我不确定提到的任何想法是否是 correct/best 解决问题的方法...

我认为主要思想是你不使用canvas方法来做剪辑,而是自己保留形状数据并自己执行所需的计算,将结果绘制在canvas或将其用作一种像素数据 getter.


如果您同意您的形状变成 raster data,即只有一个像素表示您的新“相交”形状,一般的想法是:

  1. 将每个形状转换为像素数据,包括 canvas 的所有空像素,并将像素存储到数组中;
  2. 计算所有三个形状的边界框;
  3. 遍历边界框区域的所有像素,将所有三种形状都存在的像素保存到新数组中;
  4. 将新的像素数组绘制到 canvas。

如果您需要 vector data,例如稍后能够缩放你的形状,它变得有点复杂。

问题是,使用 canvas 函数而不是像素数据来表达可能非常复杂的形状是一项艰巨的任务。

我可能会这样做:

  1. 将我的形状保留为 SVG 图像数据
  2. 在 canvas 上绘制 svg 路径,使它们可见。新技术让它变得简单:https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D
  3. 使用第三方库在相交的 svg 形状之间获取新路径。我没有深入调查,但我确信诸如 Raphael 或 Snap.js 之类的库将具有获取两个 svg 之间的交集数据的方法。
  4. 获取新的交叉点数据并将其存储为新的 SVG 图像数据,在 canvas 上绘制。

很有可能提到的库,比如 Raphael 可能有一些东西可以让人们以更简单的方式做到这一点。

Compositing可以做到。

诀窍是在第二个 off-screen canvas 上处理每个合成,然后使用默认 source-over 模式合并它们。

const canvas = document.getElementById( "canvas" );
const ctx = canvas.getContext( "2d" );
// we create an off-screen copy of the canvas to perform our clippings
const copy = canvas.cloneNode();
const off = copy.getContext( "2d" );

// declares our shapes
const circle = new Path2D();
circle.arc( 0, 0, 145, 0, Math.PI * 2 );
const blue_circle = new Path2D();
blue_circle.addPath( circle, { e: 260, f: 160 } );
const red_circle = new Path2D();
red_circle.addPath( circle, { e: 160, f: 310 } );
const yellow_circle = new Path2D();
yellow_circle.addPath( circle, { e: 340, f: 310 } );

// get common area of blue & red
off.fill( blue_circle );
off.globalCompositeOperation = "source-in";
off.fill( red_circle );
// save to visible canvas
ctx.drawImage( copy, 0, 0 )

// clear
off.globalCompositeOperation = "source-over";
off.clearRect( 0, 0, 500, 500 );
// get common area of blue & yellow
off.fill( blue_circle );
off.globalCompositeOperation = "source-in";
off.fill( yellow_circle );
// save to visible canvas
ctx.drawImage( copy, 0, 0 )

// clear
off.globalCompositeOperation = "source-over";
off.clearRect( 0, 0, 500, 500 );
// get common area of red & yellow
off.fill( red_circle );
off.globalCompositeOperation = "source-in";
off.fill( yellow_circle );
// save to visible canvas
ctx.drawImage( copy, 0, 0 );

// last pass to blend the colors
off.globalCompositeOperation = "source-over";
off.clearRect( 0, 0, 500, 500 );
off.globalAlpha = 0.6;
off.fillStyle = "blue";
off.fill( blue_circle );
off.fillStyle = "red";
off.fill( red_circle );
off.fillStyle = "yellow";
off.fill( yellow_circle );

// draw only where we did draw previously
ctx.globalCompositeOperation = "source-in";
ctx.drawImage( copy, 0, 0 );
canvas { background: white }
<canvas id="canvas" width="500" height="500"></canvas>

改用线条:

const canvas = document.getElementById( "canvas" );
const ctx = canvas.getContext( "2d" );
// we create an off-screen copy of the canvas to perform our clippings
const copy = canvas.cloneNode();
const off = copy.getContext( "2d" );
off.lineWidth = 30;

const bottom_left_top_center = new Path2D("M0,300L300,0");
const top_left_bottom_right = new Path2D("M0,0L300,300");
const bottom_left_bottom_right = new Path2D("M0,200L300,200");

off.stroke( bottom_left_top_center );
off.globalCompositeOperation = "source-in";
off.stroke( top_left_bottom_right );

// save to visible canvas
ctx.drawImage( copy, 0, 0 )

// clear
off.globalCompositeOperation = "source-over";
off.clearRect( 0, 0, 500, 500 );

off.stroke( bottom_left_top_center );
off.globalCompositeOperation = "source-in";
off.stroke( bottom_left_bottom_right );
// save to visible canvas
ctx.drawImage( copy, 0, 0 )

// clear
off.globalCompositeOperation = "source-over";
off.clearRect( 0, 0, 500, 500 );

off.stroke( top_left_bottom_right );
off.globalCompositeOperation = "source-in";
off.stroke( bottom_left_bottom_right );
// save to visible canvas
ctx.drawImage( copy, 0, 0 )
canvas { background: white }
<canvas id="canvas" width="300" height="300"></canvas>