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

喜你入骨 提交于 2021-02-07 19:17:54

问题


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. I would like to know if there's a standard way of solving the following problem using 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.

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...


回答1:


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.

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>



回答2:


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, e.g. 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
  2. Draw the svg paths on canvas so they are visible. New technology makes it kind of easy: 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. 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.
  4. Get that new intersection data and store it as new SVG image data, draw it on canvas.

It is very possible the mentioned libraries, like Raphael might have stuff allowing one to do this in an easier way.



来源:https://stackoverflow.com/questions/64691527/how-can-i-get-the-intersection-of-three-shapes-colliding-and-delete-the-parts-th

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!