2

I am trying to make a rotating zooming recursive golden triangle. It draws a golden triangle, then it draws another one inside it and so on. This was easy, but the challenge is making it zoom in and rotate around the point that the triangles are approaching.

To make it zoom in on that point infinitely I need to come up with the formula to calculate which point the triangles are approaching.

enter image description here

Running demo at this point: https://waltari10.github.io/recursive-golden-triangle/index.html

Repository: https://github.com/Waltari10/recursive-golden-triangle

/**
 * 
 * @param {float[]} pivot
 * @param {float} angle 
 * @param {float[]} point 
 * @returns {float[]} point 
 */
function rotatePoint(pivot, angle, point)
{
  const s = Math.sin(angle);
  const c = Math.cos(angle);


  const pointOriginX = point[0] - pivot[0];
  const pointOriginY = point[1] - pivot[1];

  // rotate point
  const xNew = (pointOriginX * c) - (pointOriginY * s);
  const yNew = (pointOriginX * s) + (pointOriginY * c);

  const newPoint = [
    pivot[0] + xNew,
    pivot[1] + yNew,
  ]

  return newPoint;
}

// https://www.onlinemath4all.com/90-degree-clockwise-rotation.html
// https://stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-2d
// Position is half way between points B and C 72 and 72, because AB/BC is golden ratio
function drawGoldenTriangle(pos, height, rotation, color = [0,255,0,255], pivot) {

// golden triangle degrees 72, 72, 36
// golden gnomon 36, 36, 108
// AB/BC is the golden ratio number
// https://www.mathsisfun.com/algebra/sohcahtoa.html

  const baseLength = (Math.tan(degToRad(18)) * height) * 2;

  const pointA = rotatePoint(pos, rotation, [pos[0], pos[1] - height]); // sharpest angle
  const pointB = rotatePoint(pos, rotation, [pos[0] - (baseLength / 2), pos[1]]); 
  const pointC = rotatePoint(pos, rotation, [pos[0] + (baseLength / 2), pos[1]]);


  drawTriangle(pointA, pointB, pointC, [0,255,0,255]);

}

let i = 0;

function drawRecursiveGoldenTriangle(pos, height, rotation, pivot) {

  
  drawGoldenTriangle(pos, height, rotation, [0,255,0,255], pivot);
  i++;

  if (i > 10) {
    return;
  }


  const hypotenuseLength = height / Math.cos(degToRad(18));
  const baseLength = (Math.tan(degToRad(18)) * height) * 2;
  const goldenRatio = hypotenuseLength / baseLength;

  const newHeight = height / goldenRatio;

  const newRotation = rotation - 108 * Math.PI/180

  const newPointC = rotatePoint(pos, rotation, [pos[0] + (baseLength / 2), pos[1]]);

  // Go half baselength up CA direction from pointC to get new position
  const newHypotenuseLength = baseLength;
  const newBaseLength = newHypotenuseLength / goldenRatio;

  let newPosXRelative = Math.cos(newRotation) * (newBaseLength / 2)
  let newPosYRelative = Math.sin(newRotation) * (newBaseLength / 2)
  
  const newPos = [newPointC[0] + newPosXRelative, newPointC[1] + newPosYRelative];

  drawRecursiveGoldenTriangle(newPos, newHeight, newRotation, [0,255,0,255], pivot); 

  
}

let triangleHeight = height - 50;

let pivotPoint = [(width/2),(height/2) -50];
let triangleLocation = [width/2, height/2 + 300];


let triangleRotation = 0;

function loop() {
  i = 0;

  const startTime = Date.now()
  wipeCanvasData();

  // triangleHeight++; 
  // triangleRotation = triangleRotation + 0.005;


  // drawX(pivotPoint)
  // drawX(triangleLocation)

  
  // Pivot point determines the point which the recursive golden 
  // triangle rotates around. Should be the point that triangles 
  // approach.
  drawRecursiveGoldenTriangle(triangleLocation, triangleHeight, triangleRotation, pivotPoint); 

  updateCanvas()
  const renderTime = Date.now() - startTime
  timeDelta = renderTime < targetFrameDuration ? targetFrameDuration : renderTime
  this.setTimeout(() => {
    loop()
  }, targetFrameDuration - renderTime)
}

loop()

What would be the formula to calculate the point that recursive golden triangle is approaching? Or is there some clever hack I could do in this situation?

Waltari
  • 1,052
  • 1
  • 30
  • 64
  • I was wondering where I should put it... I'd imagine there could be some clever hack you could do where you don't need to come up with the formula and instead just see where the points end up being. In my experience it wasn't accurate enough and caused too big discrepancy in the long run, but maybe I just did it wrong. Could ask the same question in math.stackexchange without the Javascript... That would belp solve this as well. – Waltari Apr 24 '21 at 09:13

1 Answers1

2

The starting point of the logarithmic spiral is calculated by startingPoint(a,b,c) where a,b,c are the points of your triangle:

enter image description here

The triangle in the snippet is not a proper 'golden triangle' but the calculations should be correct...

const distance = (p1, p2) => Math.hypot(p2.x - p1.x, p2.y - p1.y);

const intersection = (p1, p2, p3, p4) => {
  const l1A = (p2.y - p1.y) / (p2.x - p1.x);
  const l1B = p1.y - l1A * p1.x;

  const l2A = (p4.y - p3.y) / (p4.x - p3.x);
  const l2B = p3.y - l2A * p3.x;
  
  const x = (l2B - l1B) / (l1A - l2A);
  const y = x * l1A + l1B;
  
  return {x, y};
}

const startingPoint = (a, b, c) => {
  const ac = distance(a, c);
  const ab = distance(a, b);
  const bc = distance(b, c);
  // Law of cosines
  const alpha = Math.acos((ab * ab + ac * ac - bc * bc) / (2 * ab * ac));
  const gamma = Math.acos((ac * ac + bc * bc - ab * ab) / (2 * ac * bc));
  const delta = Math.PI - alpha / 2 - gamma;
  // Law of sines
  const cd = ac * Math.sin(alpha / 2) / Math.sin(delta);
  const d = {
    x: cd * (b.x - c.x) / bc + c.x,
    y: cd * (b.y - c.y) / bc + c.y
  };
  const e = {
    x: (a.x + c.x) / 2,
    y: (a.y + c.y) / 2
  };
  const f = {
    x: (a.x + b.x) / 2,
    y: (a.y + b.y) / 2,
  };
  return intersection(c, f, d, e);
};

d3.select('svg').append('path')
  .attr('d', 'M 100,50 L150,200 H 50 Z')
  .style('fill', 'none')
  .style('stroke', 'blue')
  
const point = startingPoint({x: 50, y: 200},{x: 100, y: 50},{x: 150, y: 200});  
console.log(point);

d3.select('svg').append('circle')
  .attr('cx', point.x)  
  .attr('cy', point.y)
  .attr('r', 5)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="200" height="400"></svg>
Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
  • Can you provide source for this snippet? I tried it in my repo, but the end point is a bit off... Now I'm wondering is it because of Javascript imperfections in doing math or where the problem lies. I'm willing to accept this answer if source is provided. – Waltari Apr 25 '21 at 06:49
  • The snippet uses D3 library just to draw the triange and the point. You can ignore the code starting from `d3.select('svg')` and use the computational routines only: `startingPoint`, `intersection`, and `distance` – Michael Rovinsky Apr 25 '21 at 06:56
  • I mean did you write this code, or did you copy it from somewhere? – Waltari Apr 25 '21 at 06:58
  • 2
    Wrote it after learning the issue from Wikipedia :) The computation is not so difficult, it's a basic trigonometry – Michael Rovinsky Apr 25 '21 at 07:00
  • 1
    Okay, thank you! I will write some tests and see why it's a bit off in my code. Probably Javascript rounding things up. – Waltari Apr 25 '21 at 07:17