Search code examples
javascriptgeometryhtml5-canvas

Calculate offset point coordinate between two points


I'm having a hard time figuring out how to word this, so picture time.

I have points arranged in a circle, I know the x,y coordinates for all of them.

enter image description here

I can iterate each point and draw a line between them, easy

//in a for each dot loop..
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();

enter image description here

This is where I'm up to now. The rest I can't figure out

While iterating a point, I'd like to create a "diversion" that alternates between left and right (as though you're looking from the one point towards the next point in the sequence).

I want to take the midpoint of that straight line between two points, shoot 90 degrees off either left or right (let's say i % 2 or something), travel set distance d and place a point at the end location. The distance d will be different for each point and I have determined values for each of them.

enter image description here

Then continue iterating each point while creating offset points at set distances alternating left or right from the reference of the midway line between two points.

enter image description here

When iterating a point, instead of drawing a straight line from startX, startY to endX, endY, I will draw 2 lines - one from startX, startY to diversionX, diversionY and one from that to endX, endY. The final rendered result would be this.

enter image description here

What I'm really asking is for help with the offset dot placing, I didn't have a great relationship with geometry at school, forever this has haunted me.. Drawing paths is trivial, I just need to know the point coordinates where to draw to/from..

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var pointSize = 5;

var points = [
  [300, 50, 10], // x, y, offset point distance
  [175, 80, 15],
  [100, 185, 9],
  [100, 315, 21],
  [175, 420, 12],
  [300, 460, 15],
  [420, 420, 8],
  [500, 315, 16],
  [500, 185, 25],
  [420, 80, 17]
];

for(var i = 0; i < points.length; i++) {
  x = points[i][0];
  y = points[i][1];
  d = points[i][2]; // diversion length
  
  // draw point
  ctx.fillStyle = '#000000';
  ctx.fillRect(x-(pointSize/2), y-(pointSize/2), pointSize, pointSize);  
  
  // draw temporary connecting line to next point
  ctx.strokeStyle = '#d4d4d4';
  nextPoint = points[(i + 1) % points.length];
  
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineWidth = 3;
  ctx.lineTo(nextPoint[0], nextPoint[1]);
  ctx.stroke();    
  
  // aaaaand I'm lost
  // get midpoint of drawn line
  // shoot off 90 degress to a side (i %2)
  // travel distance of "d"
  // get "d" x, y position
  
  // draw lines between 
  // x,y and dx,dy
  // dx,dy and nextX, nextY
 
}
<canvas id="canvas" width="600" height="600"></canvas>


Solution

  • Getting the center point of a line

    mid.X = (start.X + end.X) / 2;
    mid.Y = (start.Y + end.Y) / 2;
    

    Getting a vector from two points

    vec.X = end.X - start.X;
    vec.Y = end.Y - start.Y;
    

    Getting a vector form point => position

    This is just like having a vector from 0, 0 to the point => your position so:

    vec.X = mid.X;
    vec.Y = mid.Y;
    

    Turning by 90 degrees

    // Depending on the direction you'd either have to negate the x or y component
    const prevY = vec.Y;
    vec.Y = vec.X;
    vec.X = -prevY;
    
    const prevY = vec.Y;
    vec.Y = -vec.X;
    vec.X = prevY;
    

    Going a certain distance

    // You have 'point' in your case the center point and the rotated vector 'vec'
    // Here you first calculate the length of 'vec'
    // then get dX and dY that you move per unit in the direction of 'vec'
    // then you multiply that with the distance and get your offset
    const vecLength = Math.sqrt(vec.X * vec.X + vec.Y * vec.Y);
    const xOffset = vec.X / vecLength * distance;
    const yOffset = vec.Y / vecLength * distance;
    

    Add an offset (but I guess that's not the difficult part ;) )

    point.X += xOffset;
    point.Y += yOffset;
    

    This is the implemented solution: There are a few temporary drawings just to make it a bit clearer what is happening at every step.

    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const pointSize = 5;
    
    const points = [
      [300, 50, 50], // x, y, offset point distance
      [175, 80, 15],
      [100, 185, 9],
      [100, 315, 21],
      [175, 420, 12],
      [300, 460, 15],
      [420, 420, 8],
      [500, 315, 16],
      [500, 185, 25],
      [420, 80, 17]
    ];
    
    for(let i = 0; i < points.length; i++) {
      x = points[i][0];
      y = points[i][1];
      d = points[i][2]; // diversion length
      
      // draw point
      ctx.fillStyle = '#000000';
      ctx.fillRect(x-(pointSize/2), y-(pointSize/2), pointSize, pointSize);  
      
      // draw temporary connecting line to next point
      ctx.strokeStyle = '#d4d4d4';
      nextPoint = points[(i + 1) % points.length];
      
      ctx.beginPath();
      ctx.moveTo(x, y);
      ctx.lineWidth = 3;
      ctx.lineTo(nextPoint[0], nextPoint[1]);
      ctx.stroke();
      
      const midX = (x + nextPoint[0]) / 2;
      const midY = (y + nextPoint[1]) / 2;
      
      // draw center point
      ctx.fillStyle = '#00ff00';
      ctx.fillRect(midX-(pointSize/2), midY-(pointSize/2), pointSize, pointSize); 
      
      const midXVec = nextPoint[0] - midX;
      const midYVec = nextPoint[1] - midY;
      
      const nMidXVec = i % 2 === 0 ? -midYVec : midYVec;
      const nMidYVec = i % 2 === 0 ? midXVec : -midXVec;
      
      // draw rotated vector from midpoint
      ctx.strokeStyle = '#f1f1f1';
      ctx.beginPath();
      ctx.moveTo(midX, midY);
      ctx.lineWidth = 3;
      ctx.lineTo(midX + nMidXVec, midY + nMidYVec);
      ctx.stroke();
      
      const nMidLength = Math.sqrt(nMidXVec * nMidXVec + nMidYVec * nMidYVec);
      const dXVec = nMidXVec / nMidLength * d;
      const dYvec = nMidYVec / nMidLength * d;
      
      const dX = midX + dXVec;
      const dY = midY + dYvec;
      
      // draw point dX, dY
      ctx.fillStyle = '#ff0000';
      ctx.fillRect(dX-(pointSize/2), dY-(pointSize/2), pointSize, pointSize); 
      
      
      // draw final lines
      ctx.strokeStyle = '#000000';
      ctx.beginPath();
      ctx.moveTo(x, y);
      ctx.lineWidth = 3;
      ctx.lineTo(dX, dY);
      ctx.lineTo(nextPoint[0], nextPoint[1]);
      ctx.stroke();
    }
    <canvas id="canvas" width="600" height="600"></canvas>