0

I'm trying to do a function who take a point position and an axis (a line) and return the projection of the point on the axis.

  • Point are simply represented by {x, y} coords
  • Axis are represented by an f(x) = ax + b linear function (or f(x) = C for vertical line) and the rad value for the a (where 0rad is right, and turn clockwise). So axis = {a, b, rad} or axis = {c, rad}.

It should be a simple mathematical problem using Pythagorean but i cannot get the right solution.. Projections are always looking on the other directions (but look at the good distance).

Code demo:

I create a dark point on 25;100 and multiples coloured axis. Every axis have a related point (same color) who represent the projection of the dark point on it. Both grey are vertical and horizontal examples and work well but 3 others axis (Red, Green and Blue) are wrong.

I tried to redo getProjectionOnAxis function multiple times, and even try to get the good results by testing all possibility (by inverse vars, using another cos/sin/tan function) and try to catch my issue like that but nothing to do I never get the good result.

const point = {x:25, y:100};

const axis = [
  {a: 1, b: 25, rad:Math.PI/4, rgb: '255,0,0'}, // 45deg
  {a: -.58, b: 220, rad:Math.PI/6, rgb: '0,255,0'}, // -30deg
  {a: 3.73, b: -50, rad:5*Math.PI/12, rgb: '0,0,255'}, // 75deg
  
  // Gray 
  {c: 150, rad:Math.PI/2, rgb: '100,100,100'},
  {b: 150, rad:0},
];

const execute = () => {
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  
  drawPoint({...point, rgb:'0,0,0', ctx});
   
  axis.forEach(_axis => {
    drawAxis({axis:_axis, rgb: _axis.rgb, ctx, canvas});
    const projected = getProjectionOnAxis({...point, axis: _axis});
    drawPoint({...projected, rgb:_axis.rgb, ctx});
  });
}




// --- Issue come from here ---

const getProjectionOnAxis = ({x,y,axis}) => {
  // On vertical axis |
  if (axis.c) {
    return { x: axis.c, y };
  }
  // On horizontal axis _
  if (!axis.a) {
    return { x, y: axis.b };
  }

  // Projected on axis but perpendicular to X axis
  const projectedOnX = {
    x,
    y: axis.a * x + axis.b,
  }
  
// The distance between both points is the hypothenus of the triangle:
// point, projectedOnAxis, projectedOnX
// where point <-> projectedOnX is the hypothenus
// and point angle is same that axis

  const distCornerToProjectedOnX = Math.abs(y - projectedOnX.y);
  const distCornerToProjectedOnAxis = distCornerToProjectedOnX * Math.cos(axis.rad);

  const projectedVector = {
    x: distCornerToProjectedOnAxis * Math.cos(axis.rad),
    y: distCornerToProjectedOnAxis * Math.sin(axis.rad),
  };

  return {
    x: x + projectedVector.x,
    y: y + projectedVector.y,
  };
}




// --- Draw Functions ---
// Not really important for the issue
const drawPoint = ({x,y,rgb, ctx}) => {

    ctx.save();
    ctx.translate(x, y);

    ctx.beginPath();
    ctx.fillStyle = `rgba(${rgb},1)`;
    ctx.arc(0, 0, 2, 0, Math.PI*2, true);
    
    ctx.closePath();
    ctx.fill();

    ctx.restore();
};

const drawAxis = ({axis, rgb, ctx, canvas}) => {
  if (axis.c) {
    // Vertical axis
    drawLine({
      from: {x: axis.c, y:0},
      to: {x:axis.c, y:canvas.height},
      rgb, ctx
    });
  }
  else if (!axis.a) {
    // Horizontal axis
    drawLine({
      from: {x:0, y:axis.b},
      to: {x:canvas.width, y:axis.b},
      rgb, ctx
    });
  }
  else {
    // ax + b (here a != 0)
    let to = {
      x: canvas.width,
      y: axis.a * canvas.width + axis.b,
    };
    if (to.y < 0) {
      to = {
        x: axis.b / - axis.a,
        y: 0,
      }
    }
    drawLine({
      from: {x:0, y:axis.b},
      to,
      rgb, ctx
    });
  }
}

const drawLine = ({
    from, to, rgb=null, ctx
  }) => {
    ctx.save();
    ctx.translate(from.x, from.y);
    ctx.beginPath();
    ctx.strokeStyle = `rgba(${rgb},1)`;
    ctx.moveTo(0, 0);
    ctx.lineTo(to.x - from.x, to.y - from.y);
    ctx.stroke();
    ctx.restore();
};

execute();
html, body, canvas { margin: 0; padding: 0;}
<canvas id="canvas" width="500" height="500"></canvas>

This is the positions I should get:

enter image description here

PS: I don't really like the way I manage my axis, maybe they are another (simple) way to do it ?

Arthur
  • 4,870
  • 3
  • 32
  • 57

1 Answers1

2

Represent line (your axis) in parametric form as base point A and unit direction vector d = (dx, dy). It is universal representation suitable for all slopes. If you have slope angle fi relative to OX axis, then dx=cos(fi), dy=sin(fi)

L = P0 + d * t

Then projection of point C onto line is (using scalar product)

AC = C - A
P = A + d * (d.dot.AC)

enter image description here

In coordinates

dotvalue = dx * (C.x - A.x) + dy * (C.y - A.y)
P.x = A.x + d.x * dotvalue
P.y = A.y + d.y * dotvalue
MBo
  • 77,366
  • 5
  • 53
  • 86
  • Oh.. Look way more simple to handle it like that. I'll look a this (and first, redo my line as parametric form). Non related question: What did you use to draw the example ? (or do you copy/past an existing image?) – Arthur May 22 '20 at 14:18
  • 1
    Geogebra application (web version exists) – MBo May 22 '20 at 14:19
  • Okay good. I got all my code refactored and I can say now, it works like a charm ! 10 times easier than my previous code for a better result ! Just have a question about `d.dot.AC` meaning ? (I had to use the coordinate version to make it works) – Arthur May 25 '20 at 21:39
  • This is scalar (dot) product of vectors, meaning is under "In coordinates". Some 2d libraries have ready-to-use functions for dot product and another vector operations – MBo May 26 '20 at 02:26