Can someone good with trigonometry help me out with a small sketch?


#1

It’s been a few too many years since I took geometry, so I’ve been beating my head against what should be a simple problem :grimacing:.

The goal, illustrated by the test sketch, is to calculate the 3rd point of a triangle, given the coordinates of the first two points and the side lengths. It’s quite close, but the 2 side lengths that are supposed to be constant are not staying constant at certain angles when using the calculated 3rd point.

I went ahead and created a horrible MS paint image to give an illustration of the example: triangle

Thanks!

void setup () {
  size(400, 400);
}

void draw () {
  background(255);
  PVector A = new PVector(mouseX, mouseY);
  float AC = 100;
  PVector B = new PVector(250, 100);
  float BC = 100;
  float AB = A.dist(B);
  PVector C = new PVector();
  float theta = acos( (AB*AB + AC*AC - BC*BC) / (2*AB*AC) );
  C.x = AC * cos(theta) + A.x;
  C.y = AC * sin(theta) + A.y;
    
  line(A.x, A.y, B.x, B.y);
  line(A.x, A.y, C.x, C.y);
  line(B.x, B.y, C.x, C.y);
}

#2

Look at everything related to inverse cinematics, that’s your subject.


#3

// triangle from two points

// Demonstrator for getting a point C orthogonal 
// to the center of a line between points A and B. 
// Thanks to amnon. 

final color BLACK = color(0);
final color WHITE = color(255);

final color RED   = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE  = color(0, 0, 255);


void setup() {
  size(600, 600);
  background(WHITE);
}

void draw() {
  background(WHITE);

  // points A and B
  PVector A = new PVector(width/2, height/2);
  PVector B = new PVector(mouseX, mouseY);

  // connectedPoints(A, B, BLACK);

  // the core: get side point C  
  PVector C = getSidePoint(A, B, 100);   // -100 would make the triangle to the other side 

  // connectedPoints(C, center, RED);

  // draw triangle from ABC
  trianglePV(A, B, C);

  // highlight its corners 
  ellipsePV(A, RED);
  ellipsePV(B, GREEN);
  ellipsePV(C, BLUE);
  //
}

// ----------------------------------------------------------------

void trianglePV(PVector pv1, PVector pv2, PVector pv3) {
  fill(144); // gray  
  stroke(BLACK);
  triangle( pv1.x, pv1.y, 
    pv2.x, pv2.y, 
    pv3.x, pv3.y  );
}

void ellipsePV(PVector pv, color col) {
  fill(col); 
  ellipse(pv.x, pv.y, 10, 10);
}

PVector getSidePoint(PVector A, PVector B, 
  int distFromLine) {
  // the core function of the demonstrator
  // thanks to amnon 

  // difference between A & B
  PVector difference = PVector.sub(B, A);
  difference.normalize();
  difference.mult(distFromLine);
  PVector sidePoint = new PVector(-difference.y, difference.x);

  // center between A & B 
  PVector center = getCenter(A, B); 
  // from relative pos to absolute pos 
  sidePoint.add(center);

  return sidePoint;
}

// ---------------------------------------------------
// help functions 

void connectedPoints(PVector A, PVector B, color myColorStroke) {
  // show 2 points A and B and connect them   
  stroke(myColorStroke);
  line(A.x, A.y, B.x, B.y);
  ellipse(A.x, A.y, 10, 10);
  ellipse(B.x, B.y, 10, 10);
}

PVector getCenter(PVector A, PVector B) {
  // returns center point 
  return PVector.lerp(A, B, 0.5);  // 0.5 defines center
}
//

#4

Did you mean Inverse kinematics
?


#5

Given a triangle:

PVector[] tri = new PVector[3];

Then you can use the PVector reference:

https://processing.org/reference/PVector.html

to figure out how to solve each part of the problem. Your constraint set, however, is suspicious – you said:

but not all mouse input will satisfy your constraints – for example, a mouse at 0,0 will be more than 200 away from 250, 100, so the two arms cannot be 100 each and reach it. What should happen when you mouse point is invalid, and no 100,00,x triangle exists that can connect to it?

Chrisir solved this problem by making the height of your triangle constant (with the hypotenuse as the base), so that the short arms aren’t always 100, but they are equal.


#6

Thanks, @Chrisir this is a much cleaner setup than my example. But, @jeremydouglass is right, this doesn’t exactly solve the problem that I was defining - I’m trying to keep the two known points have a constant side length. I was aware that you can have invalid inputs in my code, but it was just supposed to be an example.

For context, I’m trying to model something like this:


#7

Ah. You may need to update your model before coding it…

In your image, I can see that the two small wheels are bolted on, and it looks like the second wheel slides on an arc slot – while the pen moves freely on the arm join. What are your inputs? Is it a handle that you crank that turns the center wheel?


#8

Ah sorry, this is just an image I pulled from the web. What I meant was I’m trying to simulate something like this in Processing, starting with (what should be) the simple vector math that defines the position of the marker based on the position of the two axles on the left.


#9

Right. But where will your degrees of freedom be? Analysis of motion and what you solve for all depends on some part of the system being able to move…

If all you want is two fixed segments pinned to a point, try the Arm example sketch –

https://processing.org/examples/arm.html


#10

Thanks, that is a useful example. I haven’t thought out possible parameters, but theoretically the math should be the same regardless. You have two segments of fixed length with known starting points attached at their endpoints.


#11

Yes, but that’s just a definition of a triangle – if both endpoints are fixed, there are only two possible places where the fixed-length segments could meet, and in either configuration no motion is possible.

The parameters come in when you decide which of those “known starting points” can move, and how. That introduces equations with constants and variables, which then allows you to solve for the joint – then the math is different depending on the kind of motion.


#12

I’m trying to keep the two known points have a constant side length.

Do you mean the line between the fixed point centerPV and the mouse?

Then this sketch below is for you:

a static triangle rotating towards mouse

(Not what you want I guess)


// triangle from two points - NEW 

// https : // discourse.processing.org/t/can-someone-good-with-trigonometry-help-me-out-with-a-small-sketch/9127

// Demonstrator for getting a point C orthogonal 
// to the center of a line between points A and B. 
// Thanks to amnon. 

final color BLACK = color(0);
final color WHITE = color(255);

final color RED   = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE  = color(0, 0, 255);

final float radiusTriangle1 = 220;   // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
float angle= 1.3; 

// for the triangle 
PVector centerPV; // center RED
PVector pointingTowardsMousePV;  //  pointing towards mouse - Green 
PVector thirdCornerPV;   // BLUE 

// for the mouse
PVector mousePV; 

void setup() {
  size(600, 600);
  background(WHITE);

  // point centerPV 
  centerPV = new PVector(width/2, height/2);
  mousePV = new PVector();
  pointingTowardsMousePV = new PVector();
}// setup()

void draw() {
  background(WHITE);

  // get angle from centerPV to mouse
  mousePV.set (mouseX, mouseY); 
  angle=angleBetweenPV_PV( centerPV, mousePV );

  // calc 2nd corner 
  pointingTowardsMousePV.set(cos(angle)*radiusTriangle1+centerPV.x, 
    sin(angle)*radiusTriangle1+centerPV.y);

  // calc 3rd corner
  // the core: get side point C  
  thirdCornerPV = getSidePoint(centerPV, pointingTowardsMousePV, 100);   // -100 would make the triangle to the other side 

  // draw triangle 
  trianglePV(centerPV, pointingTowardsMousePV, thirdCornerPV);

  // highlight its corners 
  ellipsePV(centerPV, RED);
  ellipsePV(pointingTowardsMousePV, GREEN);
  ellipsePV(thirdCornerPV, BLUE);
  //
} // draw() 

// ----------------------------------------------------------------

float angleBetweenPV_PV(PVector a, PVector mousePV) {

  // calc angle 

  // https : // forum.processing.org/two/discussion/10474/find-angle-between-2-points

  PVector d = new PVector();

  // calc angle
  pushMatrix();
  translate(a.x, a.y);
  // delta 
  d.x = mousePV.x - a.x;
  d.y = mousePV.y - a.y;
  // angle 
  float angle1 = atan2(d.y, d.x);
  popMatrix();

  return angle1;
} 

void trianglePV(PVector pv1, PVector pv2, PVector pv3) {
  fill(144); // gray  
  stroke(BLACK);
  triangle( pv1.x, pv1.y, 
    pv2.x, pv2.y, 
    pv3.x, pv3.y  );
}

void ellipsePV(PVector pv, color col) {
  fill(col); 
  ellipse(pv.x, pv.y, 10, 10);
}

PVector getSidePoint(PVector A, PVector B, 
  int distFromLine) {
  // the core function of the demonstrator
  // thanks to amnon 

  // difference between A & B
  PVector difference = PVector.sub(B, A);
  difference.normalize();
  difference.mult(distFromLine);
  PVector sidePoint = new PVector(-difference.y, difference.x);

  // center between A & B 
  PVector center = getCenter(A, B); 
  // from relative pos to absolute pos 
  sidePoint.add(center);

  return sidePoint;
}

// ---------------------------------------------------
// help functions 

void connectedPoints(PVector A, PVector B, color myColorStroke) {
  // show 2 points A and B and connect them   
  stroke(myColorStroke);
  line(A.x, A.y, B.x, B.y);
  ellipse(A.x, A.y, 10, 10);
  ellipse(B.x, B.y, 10, 10);
}

PVector getCenter(PVector A, PVector B) {
  // returns center point 
  return PVector.lerp(A, B, 0.5);  // 0.5 defines center
}

#13

in this new version we have indeed a variable distance between fixed point A and mouse and C is found in a way that indeed both sides stay at fixed length 100 (so the height of C varies proportional to the distance between A and mouse)

When the mouse is too far away the triangle becomes a line of course

final color BLACK = color(0);
final color WHITE = color(255);

final color RED   = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE  = color(0, 0, 255);


void setup() {
  size(600, 600);
  background(WHITE);
}

void draw() {
  background(WHITE);

  // points A and B
  PVector A = new PVector(width/2, height/2);
  PVector B = new PVector(mouseX, mouseY);

  // https : // en.wikipedia.org/wiki/Isosceles_triangle#Height
  float lineC = A.dist(B); 
  float distFromBase= (100*100) - (  lineC*lineC / 4  ) ; 
  distFromBase=sqrt(distFromBase); 

  // the core: get side point C  
  PVector C = getSidePoint(A, B, distFromBase);   // -100 would make the triangle to the other side 

  // connectedPoints(C, center, RED);

  // draw triangle from ABC
  trianglePV(A, B, C);

  // highlight its corners 
  ellipsePV(A, RED);
  ellipsePV(B, GREEN);
  ellipsePV(C, BLUE);

  fill(0); 
  text( A.dist(C), 
    22, 22);  
  //
}

// ----------------------------------------------------------------

void trianglePV(PVector pv1, PVector pv2, PVector pv3) {
  fill(144); // gray  
  stroke(BLACK);
  triangle( pv1.x, pv1.y, 
    pv2.x, pv2.y, 
    pv3.x, pv3.y  );
}

void ellipsePV(PVector pv, color col) {
  fill(col); 
  ellipse(pv.x, pv.y, 10, 10);
}

PVector getSidePoint(PVector A, PVector B, 
  float distFromLine) {
  // the core function of the demonstrator
  // thanks to amnon 

  // difference between A & B
  PVector difference = PVector.sub(B, A);
  difference.normalize();
  difference.mult(distFromLine);
  PVector sidePoint = new PVector(-difference.y, difference.x);

  // center between A & B 
  PVector center = getCenter(A, B); 
  // from relative pos to absolute pos 
  sidePoint.add(center);

  return sidePoint;
}

// ---------------------------------------------------
// help functions 

void connectedPoints(PVector A, PVector B, color myColorStroke) {
  // show 2 points A and B and connect them   
  stroke(myColorStroke);
  line(A.x, A.y, B.x, B.y);
  ellipse(A.x, A.y, 10, 10);
  ellipse(B.x, B.y, 10, 10);
}

PVector getCenter(PVector A, PVector B) {
  // returns center point 
  return PVector.lerp(A, B, 0.5);  // 0.5 defines center
}
//

#14

new version following your 2nd image with the web image of a drawing machine with 2 rotating wheels connected by 2 arms and a blue pen

This calls for a Wheel class…


final color BLACK = color(0);
final color WHITE = color(255);

final color RED   = color(255, 0, 0);
final color GREEN = color(0, 255, 0);
final color BLUE  = color(0, 0, 255);


// WHEEL I
PVector centerPV1=new PVector (200, 300); 
float angle1=1.3; 
float angleSpeed1=.1; 
float radiusWheel1= 44; 

// WHEEL II
PVector centerPV2=new PVector (300, 320);
float angle2=0;
float angleSpeed2=.021;
float radiusWheel2= 22; 

// resulting drawing  
ArrayList<PVector> list = new ArrayList(); 

void setup() {
  size(600, 600);
  background(WHITE);
}

void draw() {
  background(WHITE);

  // points A and B from WHEELS 
  PVector A = new PVector(
    cos(angle1)*radiusWheel1+centerPV1.x, 
    sin(angle1)*radiusWheel1+centerPV1.y);

  PVector B = new PVector(
    cos(angle2)*radiusWheel2+centerPV2.x, 
    sin(angle2)*radiusWheel2+centerPV2.y);

  // move WHEELS 
  angle1+=angleSpeed1;
  angle2+=angleSpeed2;

  // show WHEELS 
  noFill(); 
  stroke(BLACK); 
  ellipse(centerPV1.x, centerPV1.y, 
    radiusWheel1*2, radiusWheel1*2); 
  ellipse(centerPV2.x, centerPV2.y, 
    radiusWheel2*2, radiusWheel2*2); 

  // CALC 
  // https : // en.wikipedia.org/wiki/Isosceles_triangle#Height
  float lineC = A.dist(B); 
  float distFromBase= (100*100) - (  lineC*lineC / 4  ) ; 
  distFromBase=sqrt(distFromBase); 

  // the core: get side point C  
  PVector C = getSidePoint(A, B, distFromBase);   // -100 would make the triangle to the other side 

  // add to drawing 
  list.add(C); 

  // connectedPoints(C, center, RED);

  // draw triangle from ABC
  trianglePV(A, B, C);

  // highlight its corners 
  ellipsePV(A, RED);
  ellipsePV(B, GREEN);
  ellipsePV(C, BLUE);

  fill(0); 
  text( A.dist(C), 
    22, 22);  

  // show  drawing 
  for (PVector pv : list) {
    stroke(BLUE); 
    point(pv.x, pv.y);
  }
  //
}

// ----------------------------------------------------------------

void trianglePV(PVector pv1, PVector pv2, PVector pv3) {
  fill(144); // gray  
  stroke(BLACK);
  triangle( pv1.x, pv1.y, 
    pv2.x, pv2.y, 
    pv3.x, pv3.y  );
}

void ellipsePV(PVector pv, color col) {
  fill(col); 
  ellipse(pv.x, pv.y, 10, 10);
}

PVector getSidePoint(PVector A, PVector B, 
  float distFromLine) {
  // the core function of the demonstrator
  // thanks to amnon 

  // difference between A & B
  PVector difference = PVector.sub(B, A);
  difference.normalize();
  difference.mult(distFromLine);
  PVector sidePoint = new PVector(-difference.y, difference.x);

  // center between A & B 
  PVector center = getCenter(A, B); 
  // from relative pos to absolute pos 
  sidePoint.add(center);

  return sidePoint;
}

// ---------------------------------------------------
// help functions 

void connectedPoints(PVector A, PVector B, color myColorStroke) {
  // show 2 points A and B and connect them   
  stroke(myColorStroke);
  line(A.x, A.y, B.x, B.y);
  ellipse(A.x, A.y, 10, 10);
  ellipse(B.x, B.y, 10, 10);
}

PVector getCenter(PVector A, PVector B) {
  // returns center point 
  return PVector.lerp(A, B, 0.5);  // 0.5 defines center
}
//

#15

Thank you @Chrisir this is awesome! I need to take some time to make sure I understand the math, but this is exactly what I was looking for.
I’m not sure I understand this line:
PVector sidePoint = new PVector(-difference.y, difference.x);

Why is the basis for the third point the difference between A and B with the x and y reversed?


#16

I have no idea, as stated this function is made by amnon

Thoughts

you could experiment by leaving out the sign ("-") or swapping the x and y.

Since it’s the difference-Vector that we take to get sidePoint (which is relative, not absolute) I assume it’s just orthogonal to the difference between vectors A and B (hence x and y swapped). Gut feeling.

Remark I

Remember that sidePoint is based on Vector difference and that we did with it first:

   difference.normalize();
   difference.mult(distFromLine);

so before deriving sidePoint from difference we bring difference to the length “distFromLine” we want sidePoint to have…

Remark II

Even more interesting might these lines in draw() be where I calculate the length of the height towards C (the height upon triangles base) and pass this as distFromBase to the function getSidePoint.

I calculate it based on 100 for both sides. To calculate distFromBase in the first place was one of my core ideas. Because distFromBase is different (based on 100 and 100) depending on where the mouse is, everything works.

  // CALC 
  // https : // en.wikipedia.org/wiki/Isosceles_triangle#Height // see also German article
  float lineC = A.dist(B); 
  float distFromBase= (100*100) - (  lineC*lineC / 4  ) ; 
  distFromBase=sqrt(distFromBase); 

You could / should make these lines into their own function that you would call like this from draw():

  float distFromBase = getHeightUponBaseInIsoscelesTriangle ( A, B, 100 );

Chrisir


#17

orthogonal? yes!

(x, y) → (-y, x) is a rotation of 90° counterclockwise around 0,0.

So the code draws a relative vector from A->B, change its length to distFromLine, rotates it by 90 degrees (it is relative, so already logically starts at 0,0), and then add the result to the A-B midpoint – so, from the AB midpoint, to proceeds perpendicularly to distFromLine.