Trigonometric confusion over simple diagonals

Hello,

I am coding a sketch to generate diagonals. The diagonals all start from the left side of the canvas from a random vertical position. Then the diagonal should be traced from a random angle. The range of possible angles is such that pure vertical lines are not possible. The diagonals should reach the border of the canvas, so they either reach its top, right or bottom sides. The question is : how do I calculate the coordinates of the end points?

Basically, there are four different situations:

  • diagonal goes upwards and reaches the top side ;
  • diagonal goes upwards and reaches the right side ;
  • diagonal goes downwards and reaches the right side ;
  • diagonal goes downwards and reaches the bottom side ;

A first separation is between the angles in the first quadrant (between 0 and 90 degrees) and the fourth quadrant (between 270 and 360 degrees).

Then I am trying to get the angles that mark the boundaries between the situations where the diagonals reach either, the top, right or bottom side (red color in the picture) using atan2(). However something is wrong because the values I get never match the quadrant they belong to. So currently I am not using these boundary angles. My current code seems to function in some cases but certainly not in all of them.

Note that a second situation where the diagonal starts from the top and goes downwards is a second possible case in the code.

Any help to sort this out would be highly appreciated!

// ATTENTION : P5JS USES CLOCKWISE ANGLES!

var lineCoords = [];


function setup() {
  createCanvas(500, 500);
  background(0);
  stroke(255,0,0);
  strokeWeight(2);
  fill(255,0,0);  
  angleMode(DEGREES);
  mousePressed();
  
}

function draw() {
  background(0);
  // draw line
  stroke(255,0,0);
  line(lineCoords[0], lineCoords[1], lineCoords[2], lineCoords[3]);
  // green circle on start pos
  stroke(0,255,0);
  circle(lineCoords[0], lineCoords[1], 20);
  // orange circle on end pos
  stroke(255,127,0);
  circle(lineCoords[2], lineCoords[3], 20);
  
}

function mousePressed() {
  createDiagonal();
}

function createDiagonal() {
  var startX, startY, endX, endY, angle, boundaryAngle;
  var startAxis = floor(random(2));
  // 1-STARTS FROM LEFT
  if (startAxis == 0) {
    startX = 0;
    startY = random(3*height/4)+height/8;
    angle = random(180)+270;
    // depassement 360°
    if (angle > 360) {
      angle = angle - 360;
    }
    print("STARTS FROM LEFT - angle : "+angle);
    // 1.1 - goes downwards
    if (angle  < 90) {
      boundaryAngle = atan2((height-startY)/width);
      print("angle-limite: "+boundaryAngle);
      endY = tan(angle) * (width) + startY;
      // 1.1.1 - reaches bottom
      if (endY > height) {
        print("case 1.1.1");
        endY = height;
        endX = tan(angle)*(height-startY); 
      // 1.1.2 - otherwise reaches right side
      } else {
        print("case 1.1.2");
        endX = width;
      }
    }
    // 1.2 - otherwise goes upwards
    else {
      // calcul angle-limite
      boundaryAngle = atan2(startY, width);
      print("boundary angle: "+boundaryAngle);
      endY = startY - tan(angle) * width;
      // 1.2.1 - reaches top
      if (endY < 0) {
        print("case 1.2.1");
        endY = 0;      
        endX = tan(90+angle)*startY; //cotangent
      // 1.2.2 - otherwise reaches right side
      } else {
        print("case 1.2.2");
        endX = width;
      }
    }   
  }
  //2 - STARTS FROM TOP
  else {
    startX = random(3*width/4)+width/8;
    startY = 0;  
    angle = random(30, 150);
    print("STARTS FROM TOP - angle: "+angle);
    //2.1 - goes leftwards
    if (angle > 90) {
      print("goes leftwards");
      boundaryAngle = atan2(startX, height)+180;
      print("boundary angle: "+boundaryAngle);
      endY = tan(angle) * startX;
      print("case 2.1 / endY = "+endY);
      // 2.1.1 - reaches bottom
      if (endY >= height) {
        print("case 2.1.1");
        endY = height;      
        endX = sqrt(startX*startX + endY*endY);
      // 2.1.2 otherwise reaches left side
      } else {
        print("case 2.1.2");
        endX = 0;
      }
    }
    // 2.2 - otherwise goes rightwards
    else {
      boundaryAngle = atan2(height, width-startX)+180;
      print("boundary angle: "+boundaryAngle);
      endY = tan(angle) * (width-startX);
      // 2.2.1 - reaches bottom
      if (endY > height) {
        print("case 2.2.1");
        endY = height;
        endX = sqrt(startX*startX + endY*endY);
      // 2.2.2 - otherwise reaches right side
      } else {
        print("case 2.2.2");
        endX = width;
      }
    }
  }  
  lineCoords = [startX, startY, endX, endY];
  print("lineCoords: ", lineCoords);
}


Actually there is just one situation and you can state it as

Given the start coordinates [x,y] and the inclination angle of a line calculate the intersection(s) between the line and the borders of a rectangle.

So you need a function that calculates the intersection of two lines and then test for intersections between the diagonal and each of the 4 sides.

1 Like

Here is an example sketch you might try and adapt for your needs.

let boxX0 = 111, boxX1 = 399, boxY0 = 99, boxY1 = 222;
let sx, sy;

function setup() {
    let p5canvas = createCanvas(500, 500);
    cursor(CROSS);
    sx = width / 3; sy = height / 3;
    textSize(16);
}

function draw() {
    background(250, 250, 200);
    // Draw box
    stroke(0); strokeWeight(1.5);
    line(boxX0, boxY0, boxX1, boxY0); // top
    line(boxX0, boxY1, boxX1, boxY1); // bottom
    line(boxX0, boxY0, boxX0, boxY1); // left
    line(boxX1, boxY0, boxX1, boxY1); // right
    // Very long line from start through mouse position
    let ang = atan2(mouseY - sy, mouseX - sx);
    let ex = 10000 * cos(ang), ey = 10000 * sin(ang);
    stroke(0, 32); strokeWeight(1);
    line(sx, sy, ex, ey);
    // Draw diagonals
    stroke(0, 0, 160); strokeWeight(1.5);
    let intersect = line_line_p(sx, sy, ex, ey, boxX0, boxY0, boxX1, boxY0);
    if (intersect) {  // Top
        line(sx, sy, intersect.x, intersect.y);
    }
    intersect = line_line_p(sx, sy, ex, ey, boxX0, boxY1, boxX1, boxY1);
    if (intersect) {  // Bottom
        line(sx, sy, intersect.x, intersect.y);
    }
    intersect = line_line_p(sx, sy, ex, ey, boxX0, boxY0, boxX0, boxY1);
    if (intersect) {  // Left
        line(sx, sy, intersect.x, intersect.y);
    }
    intersect = line_line_p(sx, sy, ex, ey, boxX1, boxY0, boxX1, boxY1);
    if (intersect) { // Right
        line(sx, sy, intersect.x, intersect.y);
    }
}

/**
 * Find the point of intersection between two lines. <br>
 * An array is returned that contains the intersection pos in x, y order.
 * If the array is of length: <br>
 * 0 then there is no intersection <br>
 * 2 these are the x/y coordinates of the intersection po. <br>
 * @param x0 start of line 1
 * @param y0 start of line 1
 * @param x1 end of line 1
 * @param y1 end of line 1
 * @param x2 start of line 2
 * @param y2 start of line 2
 * @param x3 end of line 2
 * @param y3 end of line 2
 * @return an array of coordinates for the intersection if any
 */
function line_line_p(x0, y0, x1, y1, x2, y2, x3, y3) {
    let result;
    let f1 = (x1 - x0);
    let g1 = (y1 - y0);
    let f2 = (x3 - x2);
    let g2 = (y3 - y2);
    let f1g2 = f1 * g2;
    let f2g1 = f2 * g1;
    let det = f2g1 - f1g2;
    if (Math.abs(det) > 1E-30) {
        let s = (f2 * (y2 - y0) - g2 * (x2 - x0)) / det;
        let t = (f1 * (y2 - y0) - g1 * (x2 - x0)) / det;
        if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
            result = { x: x0 + f1 * s, y: y0 + g1 * s };
    }
    return result;
}
1 Like

Quite clever. Thank you, @quark !

I’ve tried to adapt @quark’s code but I don’t get anything suitable from the line_line_p function. What’s wrong?

let boxX0, boxX1, boxY0, boxY1;
let sx, sy, ex, ey;
let finalLine = [];

function setup() {
  let p5canvas = createCanvas(500, 500);
  let boxX0 = 0, boxX1 = width-1, boxY0 = 0, boxY1 = height-1; //reference frame for intersections
  print("boxX0: "+boxX0 + " / boxY0: "+boxY0+ "/ boxX1: "+boxX1+" / boxY1: "+boxY1);
  background(0);
  stroke(255,0,0);
  strokeWeight(2);
  fill(255,0,0);  
  angleMode(DEGREES);
  mousePressed();
}

function draw() {
  background(0);
  // draw line
  stroke(255,0,0);
  line(finalLine[0], finalLine[1], finalLine[2], finalLine[3]);
  // green circle on start pos
  stroke(0,255,0);
  circle(finalLine[0], finalLine[1], 20);
  // orange circle on end pos
  stroke(255,127,0);
  circle(finalLine[2], finalLine[3], 20); 
}

function mousePressed() {
  let startAxis = floor(random(2));
  // 1-STARTS FROM LEFT
  if (startAxis == 0) {
    sx = 0;
    sy = random(3*height/4)+height/8;
    angle = random(120)+300;
    print("STARTS FROM LEFT - angle = "+angle);
  }
  //2 - STARTS FROM TOP
  else {
    sx = random(3*width/4)+width/8;
    sy = 0;  
    angle = random(30, 150);
    print("STARTS FROM TOP - angle = "+angle);
  }
  ex = sx+cos(angle) * 10000;
  ey = sy+sin(angle) * 10000;
  print("line before intersection: "+sx+ " , "+sy+" , "+ex+", "+ey);
  // test intersections
  let intersect = line_line_p(sx, sy, ex, ey, boxX0, boxY0, boxX1, boxY0);
  if (intersect) {  // Top
      finalLine = [sx, sy, intersect.x, intersect.y];
      print ("Intersection with top border.");
  }
  intersect = line_line_p(sx, sy, ex, ey, boxX0, boxY1, boxX1, boxY1);
  if (intersect) {  // Bottom
      finalLine = [sx, sy, intersect.x, intersect.y];
      print ("Intersection with bottom border.");
  }
  intersect = line_line_p(sx, sy, ex, ey, boxX0, boxY0, boxX0, boxY1);
  if (intersect) {  // Left
      finalLine = [sx, sy, intersect.x, intersect.y];
      print ("Intersection with left border.");
  }
  intersect = line_line_p(sx, sy, ex, ey, boxX1, boxY0, boxX1, boxY1);
  if (intersect) { // Right
      finalLine = [sx, sy, intersect.x, intersect.y];
      print ("Intersection with right border.");
  }
  print("line after intersection: "+finalLine[0]+ " , "+finalLine[1]+" , "+finalLine[2]+", "+finalLine[3]);
}

/**
 * Find the point of intersection between two lines. <br>
 * An array is returned that contains the intersection pos in x, y order.
 * If the array is of length: <br>
 * 0 then there is no intersection <br>
 * 2 these are the x/y coordinates of the intersection po. <br>
 * @param x0 start of line 1
 * @param y0 start of line 1
 * @param x1 end of line 1
 * @param y1 end of line 1
 * @param x2 start of line 2
 * @param y2 start of line 2
 * @param x3 end of line 2
 * @param y3 end of line 2
 * @return an array of coordinates for the intersection if any
 */
function line_line_p(x0, y0, x1, y1, x2, y2, x3, y3) {
    let result;
    let f1 = (x1 - x0);
    let g1 = (y1 - y0);
    let f2 = (x3 - x2);
    let g2 = (y3 - y2);
    let f1g2 = f1 * g2;
    let f2g1 = f2 * g1;
    let det = f2g1 - f1g2;
    if (Math.abs(det) > 1E-30) {
        let s = (f2 * (y2 - y0) - g2 * (x2 - x0)) / det;
        let t = (f1 * (y2 - y0) - g1 * (x2 - x0)) / det;
        if (s >= 0 && s <= 1 && t >= 0 && t <= 1)
            result = { x: x0 + f1 * s, y: y0 + g1 * s };
    }
    return result;
}

The second line in setup is creating local variables for the box so are undefined in mousePressed. Change the line to
boxX0 = 0; boxX1 = width-1; boxY0 = 0; boxY1 = height-1; //reference frame for intersections

2 Likes