Pomax / custom-graphics-element

Because what if you could just... write graphics sketches? On the web? Like, directly?
MIT License
17 stars 1 forks source link

trig example: finding ellipse radii. intrinsic angle, and AABB #105

Open Pomax opened 3 months ago

Pomax commented 3 months ago
function draw() {
  clear(`white`);
  const { P, Q } = drawInitialSetup();

  // Let's get to work. Step 1: rotate P to P'
  setColor(`#933`);
  const Pprime = new Point(-P.y, P.x);
  showPointFromCenter(Pprime, `P'`);

  // Step 2: get the midpoint D = Q--P'
  setColor(`#333`);
  const D = new Point((Q.x + Pprime.x) / 2, (Q.y + Pprime.y) / 2);
  showPoint(D, `D`);
  line(Pprime, Q);

  // Then in a "new panel", step 3: get A and B
  translate(width / 3, 0);
  setColor(`red`);
  showPoint(Q, `Q`);
  setColor(`#333`);
  showPointFromCenter(D, `D`);
  noFill();

  setStroke(`#0909`);
  const Dr = dist(C, D);
  circle(D, Dr);

  setColor(`#060`);
  let T = atan2(Q.y - D.y, Q.x - D.x);
  const A = new Point(D.x + Dr * cos(T), D.y + Dr * sin(T));
  const B = new Point(D.x - Dr * cos(T), D.y - Dr * sin(T));
  showPoint(A, `A`);
  showPoint(B, `B`);

  // step 4: get lengths a and b
  const a = dist(Q, A);
  const b = dist(Q, B);

  // and let's highlight those lengths:
  setStroke(`#2DF`);
  line(B, Q);
  setStroke(`#22D`);
  line(A, Q);

  // step 5: get our ellipse radii!
  const Alen = dist(C, A);
  const Blen = dist(C, B);
  const v1 = new Point((B.x / Blen) * a, (B.y / Blen) * a);
  const v2 = new Point((A.x / Alen) * b, (A.y / Alen) * b);

  // And let's highlight those same identities
  setColor(`#22D`);
  showPointFromCenter(v1, ``);
  setColor(`#2DF`);
  showPointFromCenter(v2, ``);

  setStroke(`#AAA`);
  line(v1, B);
  line(v2, A);

  // Then to finish up, let's show the things we set out to find.
  translate(width / 3, 0);

  // Show the major/minor radius:
  setColor(`black`);
  showPointFromCenter(v1, `v1`);
  showPointFromCenter(v2, `v2`);

  // Show the ellipse's intrinsic rotation:
  setColor(`#9095`);
  const phi = atan2(v2.y, v2.x);
  line(polarPoint(phi, -1000), polarPoint(phi, 1000));

  // and show the AABB (see https://stackoverflow.com/questions/87734)
  let ux = b * cos(phi);
  let uy = b * sin(phi);
  let vx = a * cos(phi + PI / 2);
  let vy = a * sin(phi + PI / 2);
  let w = sqrt(ux * ux + vx * vx);
  let h = sqrt(uy * uy + vy * vy);

  setColor(`#111`);
  line(-w, -h, w, -h);
  line(-w, h, w, h);
  line(-w, -h, -w, h);
  line(w, -h, w, h);
}

// ----------helper functions past this point----------

const C = new Point(0, 0);
const radius = 100;
let shearMatrix, scaleMatrix, rotateMatrix;

function setup() {
  setSize(900, 400);
  setBorder(1, `black`);
  setGrid(20, `grey`);
  addSlider(`sx`, { min: 0, max: 4, value: 1, step: 0.01 });
  addSlider(`sy`, { min: 0, max: 4, value: 1, step: 0.01 });
  addSlider(`shx`, { min: -3, max: 3, value: 1, step: 0.01 });
  addSlider(`shy`, { min: -3, max: 3, value: 0, step: 0.01 });
  addSlider(`angle`, { min: 0, max: TAU, value: 0.3, step: 0.01 });
}

function drawInitialSetup() {
  setColor(`black`);
  text(`Panel 1`, 11, 23);
  line(300, 0, 300, height);
  text(`Panel 2`, 311, 23);
  line(600, 0, 600, height);
  text(`Panel 3`, 611, 23);

  // Set up our transforms and draw both our original circle,
  // and the ellipse we get from transforming that circle:
  shearMatrix = [1, shx, shy, 1];
  scaleMatrix = [sx, 0, 0, sy];
  rotateMatrix = [cos(angle), -sin(angle), sin(angle), cos(angle)];
  drawBaseShapes();

  // Show the original "axes" and the corresponding
  // post-transform conjugated half-diameters:
  const P = new Point(...transformCoords(radius, 0));
  const Q = new Point(...transformCoords(0, radius));

  setStroke(`#00F3`);
  line(0, 0, 0, radius);
  line(0, 0, radius, 0);

  setColor(`red`);
  showPointFromCenter(P, `P`);
  showPointFromCenter(Q, `Q`);
  return { P, Q };
}

function showPoint(p, label, offset) {
  point(p);
  offsetText(label, p, offset);
}

function showPointFromCenter(p, label) {
  line(C, p);
  showPoint(p, label);
}

function drawBaseShapes() {
  for (let t = 0, x, y; t <= TAU; t += 0.01) {
    resetTransform();
    translate(width / 6, height / 2);
    x = radius * cos(t);
    y = radius * sin(t);

    setStroke(`#00F3`);

    circle(x, y, 0.05);
    translate(width / 3, 0);
    circle(x, y, 0.05);
    translate(width / 3, 0);
    circle(x, y, 0.05);

    resetTransform();
    translate(width / 6, height / 2);
    let [nx, ny] = transformCoords(x, y);
    setStroke(`red`);
    circle(nx, ny, 0.05);
    translate(width / 3, 0);
    circle(nx, ny, 0.05);
    translate(width / 3, 0);
    circle(nx, ny, 0.05);
  }

  setColor(`red`);
  resetTransform();
  translate(width / 6, height / 2);
  showPoint(C, `C`, -10);
  translate(width / 3, 0);
  showPoint(C, `C`, -10);
  translate(width / 3, 0);
  showPoint(C, `C`, -10);

  resetTransform();
  translate(width / 6, height / 2);
}

function polarPoint(a, r) {
  return new Point(r * cos(a), r * sin(a));
}

function transformCoords(x, y) {
  return mul(...mul(...mul(x, y, shearMatrix), scaleMatrix), rotateMatrix);
}

function mul(x, y, m) {
  return [m[0] * x + m[1] * y, m[2] * x + m[3] * y];
}

function offsetText(label, p, offset = 20) {
  const a = atan2(p.y, p.x);
  const r = dist(0, 0, p.x, p.y) + offset;
  text(label, r * cos(a) - 5, 5 + r * sin(a));
}