Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 725
Promo Otter Avatar asked Mar 02 '23 22:03

Promo Otter


1 Answers

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>
like image 186
Kaiido Avatar answered Apr 09 '23 17:04

Kaiido