Search code examples
javascriptkonvajskonva

KonvaJS - how to check if Line is inside other Node


Looking for help. I need to check if one node (in my case it's Line) is inside another node (can be different shapes). In my case, I need to check if the Red Line is inside Blue Node.

My attempt: https://codesandbox.io/s/infallible-wing-xmy2pi

Edit:

My goal is to check whether a separate node (in my case -> the Red Line) is COMPLETELY inside (over) the Blue Node. They are both separate nodes (Red Line is not a child of the Blue Box and vice versa).

The Blue Node can be different shapes. Note that I can't check if only the first and last point of the Red Line is inside the Blue Node (see the image -> it should fail in such case because Red Line is NOT completely over the Blue Node).

enter image description here


Solution

  • TLDR: Solution in snippet below and at https://codepen.io/JEE42/pen/QWZEKYw. Dear the blue line around so it overlaps / is inside / is outside the polygon and click the 'test button.

    Assuming that you know the x,y points of the polygon vertices:

    Test #1 - does the line cross any edge?

    If you think of the polygon edges as being a series of individual lines then you can tackle the first question via the line-line intersection test. There are plenty of solutions on the web, for example see solution for that at Jeffery Thompson.

    So what you do is test each edge for collision with the line. Any collision means the line is not fully contained.

    Test #2 - is the line inside or outside the shape?

    We can use Konva's Shape.intersects(point) for this. Since we confirmed in test #1 that the line is either entirely inside or entirely outside the polygon, we can pick any point on the line and call shape.intersects(point) once - if the answer is false then the entire line is outside the polygon, if the answer is true then the line is entirely inside.

    Example of using edges as lines for collision detection of line on polygon

    And here is the code from the solution

    const stage = new Konva.Stage({
        container: "container",
        width: 1000,
        height: 800,
        draggable: true
      }),
          
      layer = new Konva.Layer({
        draggable: false
      }),
               
      poly = new Konva.Line({
            x: 100,
            y: 100,
            points: [23, 20, 23, 160, 70, 93, 150, 109, 290, 139, 270, 93],
            stroke: 'black',
            strokeWidth: 5,
            closed: true,
            draggable: true
          }),
          
       line = new Konva.Line({
         x: 130, y: 140,
         points: [0, 0, 100, 50],
         stroke: 'blue',
         strokeWidth: 8,
         draggable: true
       })
    
    layer.add(poly, line);
    
    stage.add(layer);
     
    function test(line, polygon){
      
      let hit = false;
      
      // clear collsion marker
        const circles = layer.find('.demoPt');
        if (circles.length > 0){
          for (const circle of circles){
            circle.destroy()
          }
        }  
    
      // get the end-of-line points
      const e1 = {
        x: line.x() + line.points()[0],
        y: line.y() + line.points()[1],
      }
    
      const e2 = {
        x: line.x() + line.points()[2],
        y: line.y() + line.points()[3],
      }
        
      
      // Process each edge of the polygon as a line
      for (let i = 0; i < polygon.points().length; i = i + 2){
    
        // get the line point
        const v1 = {
          x: poly.x() + polygon.points()[i],
          y: poly.y() + polygon.points()[i + 1],
        }
    
        // for the other end we need the first point if we are on the last point
        let v2
        if (i  === polygon.points().length - 2){
          v2 = {
            x: poly.x() + polygon.points()[0],
            y: poly.y() + polygon.points()[1],
          }  
        }
        else {
          v2 = {
            x: poly.x() + polygon.points()[i+2],
            y: poly.y() + polygon.points()[i + 3],
          }
        }
         
        // Now run the line intersection check    
        const result = lineIntersection(v1.x, v1.y, v2.x,  v2.y, e1.x, e1.y, e2.x, e2.y)
        
        if (result){
          
          // If we have a result then then lines DO interset -- add a circle to indicate
          
          const c1 = new Konva.Circle({
            name: 'demoPt',
            x: result.x,
            y: result.y,
            fill: 'cyan',
            radius: 10
          })
          layer.add(c1)     
          
        }
        else {
        // if we get to here then there are no line collisions.        
        // So check if line is outside of polygon
        
        // we already know one end point of line in e1.
        hit = polygon.intersects(e1)
        
        // show the result
          
        }
        
        $('#result').html('line inside shape = ' + hit)
        
      }
    }
    
    $('#test').on('click', function(){
     
      test(line, poly)
    })
     
    
    function lineIntersection(x1, y1, x2, y2, x3, y3, x4, y4) {
    
      // calculate the distance to intersection point
      let denom = ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
      let uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / denom;
      let uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / denom;
    
      // if uA and uB are between 0-1, lines are colliding
      if (uA < 0 || uA > 1 || uB < 0 || uB > 1){
        return null;    
      }
      return {
        x: x1 + (uA * (x2-x1)), 
        y: y1 + (uA * (y2-y1))};
    
    }
    body {
      margin: 10px;
      overflow: hidden;
      background-color: #f0f0f0;
    } 
    #container {
      width: 1000px;
      height: 1000px; 
    }
    span {
      margin-left: 10px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://unpkg.com/konva@8/konva.min.js"></script>
    <p id='info'>  
      <button id='test'>Test</button> <span id='result'>Is the line inside the shape?</span>
    </p> 
    <div id="container" class='container'>