简体   繁体   English

如何使用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?

I've recently posted a similar question specifically for KonvaJs here , however, I have not gotten any answer and wanted to go directly to the root of what KonvaJs uses.我最近在这里专门针对 KonvaJs 发布了一个类似的问题,但是,我没有得到任何答案,想直接了解 KonvaJs 使用的根源。 I would like to know if there's a standard way of solving the following problem using HMTL5 Javascript Canvas.我想知道是否有使用 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?如果给我 3 个圆圈并将它们定位为原色圆圈(3 个圆圈彼此相交),是否有一个功能可以帮助我删除不与任何东西碰撞的部分并只保留相交的部分?

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.通过删除没有碰撞的部分,我们最终会得到 3 个点(至少在视觉上,不确定我是总共得到 3 个还是堆叠起来得到 6 个),它们是前一个三角形的边缘。

Based on what I've searched, I could detect the collided area by using the hit region and then somehow applying the clip function over the parts that are colliding to only get the desired result.根据我搜索过的内容,我可以通过使用 命中区域来检测碰撞 区域,然后以某种方式在碰撞的部分上应用剪辑功能,以获得所需的结果。 The problem I see with this solution is that if every shape is draggable, the shapes might be clipped but not follow the dragged shape.我在这个解决方案中看到的问题是,如果每个形状都是可拖动的,则形状可能会被剪裁,但不会跟随拖动的形状。

Another idea I've had is to detect and cut the collided areas and then delete the areas with no collision and then group them together.我的另一个想法是检测并切割碰撞区域,然后删除没有碰撞的区域,然后将它们组合在一起。 (I do not know how to cut them into small pieces...) (我不知道怎么把它们切成小块……)

I'm not sure if any of the mentioned ideas are the correct/best way to solve it...我不确定上面提到的任何想法是否是解决它的正确/最佳方法......

Compositing can do this. 合成可以做到这一点。

The trick is to process each compositing on a second off-screen canvas, and then merge them using the default source-over mode.诀窍是在第二个离屏画布上处理每个合成,然后使用默认的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>

Using lines instead:改用线条:

 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>

I think the main idea is you don't use canvas methods to do clip, but instead keep the shape data yourself and perform the required calculations yourself, drawing the result on canvas or using it as a sort of pixel data getter.我认为主要思想是您不使用画布方法进行剪辑,而是自己保留形状数据并自己执行所需的计算,在画布上绘制结果或将其用作一种像素数据获取器。


If you are OK with your shapes becoming raster data , that is having just a pixel representation of your new "intersected" shape, the general idea would be to:如果您同意您的形状成为栅格数据,即只有新的“相交”形状的像素表示,一般的想法是:

  1. convert each of the shape to pixel data including all empty pixels of canvas and store the pixels into an array;将每个形状转换为像素数据,包括画布的所有空像素并将像素存储到数组中;
  2. calculate the bounding box of all three shapes;计算所有三个形状的边界框;
  3. loop over all pixels of the bounding box area, save pixels where all three shapes are present into new array;循环边界框区域的所有像素,将所有三个形状都存在的像素保存到新数组中;
  4. draw the new array of pixels into canvas.将新的像素数组绘制到画布中。

If you instead need vector data , eg be able to scale your shapes later, it get's a bit more complex.如果您需要矢量数据,例如能够稍后缩放您的形状,它会变得有点复杂。

The problem is, it's a hard task to express the possibly very complex shapes resulting from intersections using the canvas functions instead of pixel data.问题是,使用画布函数而不是像素数据来表达由交叉点产生的可能非常复杂的形状是一项艰巨的任务。

I would probably do it like this:我可能会这样做:

  1. Keep my shapes as SVG image data将我的形状保留为 SVG 图像数据
  2. Draw the svg paths on canvas so they are visible.在画布上绘制 svg 路径,使其可见。 New technology makes it kind of easy: https://developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D新技术使它变得简单: https : //developer.mozilla.org/en-US/docs/Web/API/Path2D/Path2D
  3. Use a third party library to get new path between the intersecting svg shapes.使用第三方库获取相交 svg 形状之间的新路径。 I didn't investigate deeply, but I'm sure libraries such as Raphael or Snap.js will have methods for getting intersection data between two svgs.我没有深入研究,但我确信诸如 Raphael 或 Snap.js 之类的库将具有获取两个 svg 之间的交集数据的方法。
  4. Get that new intersection data and store it as new SVG image data, draw it on canvas.获取新的交叉点数据并将其存储为新的 SVG 图像数据,然后将其绘制在画布上。

It is very possible the mentioned libraries, like Raphael might have stuff allowing one to do this in an easier way.很可能提到的库,比如 Raphael 可能有一些东西可以让人们以更简单的方式做到这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM