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

Posted

技术标签:

【中文标题】如何使用 HTML5 Javascript Canvas 获得三个碰撞形状的交集并删除未碰撞的部分?【英文标题】:How can I get the intersection of three shapes colliding and delete the parts that are not colliding using HMTL5 Javascript Canvas? 【发布时间】:2021-02-17 19:46:36 【问题描述】:

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

如果给我 3 个圆圈并将它们定位为原色圆圈(3 个相互交叉的圆圈),是否有一个功能可以帮助我删除不与任何东西碰撞的部分并只保留相交的部分?

另一个例子是绘制三条线,使其形成一个三角形。通过删除不碰撞的部分,我们最终会得到 3 个点(至少在视觉上,不确定我最终是总共得到 3 个还是堆叠起来得到 6 个),它们是前一个三角形的边缘。

根据我搜索的内容,我可以通过使用hit region 检测碰撞区域,然后以某种方式将clip function 应用于碰撞的部分以仅获得所需的结果。我在这个解决方案中看到的问题是,如果每个形状都是可拖动的,那么这些形状可能会被剪裁但不会跟随拖动的形状。

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

我不确定上述任何想法是否是解决问题的正确/最佳方法...

【问题讨论】:

【参考方案1】:

Compositing 可以做到这一点。

诀窍是在第二个屏幕外画布上处理每个合成,然后使用默认的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>

【讨论】:

只是想知道在玩完 globalCompositeOperation 之后,我最终会得到 3 个不同的数字还是一个完整的数字? @BestFaucetListXYZSupport 在复制画布上一次只显示三个部分之一。然后我们将这三个部分“合并”到可见的画布上。【参考方案2】:

我认为主要思想是您不使用画布方法进行剪辑,而是自己保留形状数据并自己执行所需的计算,在画布上绘制结果或将其用作一种像素数据获取器。


如果您对自己的形状变成 raster data 感到满意,即只有新“相交”形状的像素表示,一般的想法是:

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

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

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

我可能会这样做:

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

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

【讨论】:

以上是关于如何使用 HTML5 Javascript Canvas 获得三个碰撞形状的交集并删除未碰撞的部分?的主要内容,如果未能解决你的问题,请参考以下文章

无法停止 requestAnimationFrame (Javascript/html5: canvas)

如何使用图像创建 JavaScript/HTML5 微调器列表?

如何在 iPad 上使用 HTML5/Javascript 合成音频

如何使用 javascript 控制 HTML5 视频?

我应该如何使用 JavaScript/HTML5 处理繁重的音频负载?

如何使用 JavaScript 检测文档中是不是存在 HTML5 有效的文档类型?