// math3d.jsx — minimal 3D math for the mindmap.
// Right-handed coords. Camera is implicit; we apply rotations then orthographic
// projection with an isometric-like default view.

const M3 = {
  // Build a rotation matrix (3x3) for yaw (around Y) then pitch (around X).
  // pitch is applied AFTER yaw (object-space yaw, then camera-style pitch).
  rot(yaw, pitch) {
    const cy = Math.cos(yaw),  sy = Math.sin(yaw);
    const cp = Math.cos(pitch), sp = Math.sin(pitch);
    // R = Rx(pitch) * Ry(yaw)
    return [
      [ cy,        0,    sy       ],
      [ sp * sy,   cp,  -sp * cy  ],
      [-cp * sy,   sp,   cp * cy  ],
    ];
  },

  apply(m, [x, y, z]) {
    return [
      m[0][0]*x + m[0][1]*y + m[0][2]*z,
      m[1][0]*x + m[1][1]*y + m[1][2]*z,
      m[2][0]*x + m[2][1]*y + m[2][2]*z,
    ];
  },

  // Orthographic projection: just take x,y after rotation. z is depth.
  project(p, scale, cx, cy) {
    return { x: cx + p[0] * scale, y: cy - p[1] * scale, z: p[2] };
  },

  // Linear interpolation
  lerp(a, b, t) { return a + (b - a) * t; },
  lerp3([ax,ay,az],[bx,by,bz],t) {
    return [ax+(bx-ax)*t, ay+(by-ay)*t, az+(bz-az)*t];
  },

  // Build cuboid 8 vertices around center [cx,cy,cz] with half-extents [hx,hy,hz]
  cuboidVerts([cx, cy, cz], [hx, hy, hz]) {
    const v = [];
    for (let ix = -1; ix <= 1; ix += 2) {
      for (let iy = -1; iy <= 1; iy += 2) {
        for (let iz = -1; iz <= 1; iz += 2) {
          v.push([cx + ix*hx, cy + iy*hy, cz + iz*hz]);
        }
      }
    }
    return v; // 8 verts, indexed by (ix,iy,iz) bits: idx = ((ix+1)/2)*4 + ((iy+1)/2)*2 + ((iz+1)/2)
  },

  // 6 faces of a cuboid as quads of vert indices, with outward normals
  cuboidFaces() {
    return [
      // -X
      { verts: [0,1,3,2], normal: [-1,0,0] },
      // +X
      { verts: [4,6,7,5], normal: [ 1,0,0] },
      // -Y (bottom)
      { verts: [0,4,5,1], normal: [0,-1,0] },
      // +Y (top)
      { verts: [2,3,7,6], normal: [0, 1,0] },
      // -Z
      { verts: [0,2,6,4], normal: [0,0,-1] },
      // +Z
      { verts: [1,5,7,3], normal: [0,0, 1] },
    ];
  },

  // Cubic bezier evaluator
  bezier(t, p0, p1, p2, p3) {
    const u = 1 - t;
    const b0 = u*u*u, b1 = 3*u*u*t, b2 = 3*u*t*t, b3 = t*t*t;
    return [
      b0*p0[0] + b1*p1[0] + b2*p2[0] + b3*p3[0],
      b0*p0[1] + b1*p1[1] + b2*p2[1] + b3*p3[1],
      b0*p0[2] + b1*p1[2] + b2*p2[2] + b3*p3[2],
    ];
  },
};

// Andrew's monotone chain convex hull (2D).
M3.convexHull2D = function(points) {
  const pts = points.slice().sort((a, b) => a.x - b.x || a.y - b.y);
  if (pts.length < 3) return pts;
  const cross = (O, A, B) => (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x);
  const lower = [];
  for (const p of pts) {
    while (lower.length >= 2 && cross(lower[lower.length-2], lower[lower.length-1], p) <= 0) lower.pop();
    lower.push(p);
  }
  const upper = [];
  for (let i = pts.length - 1; i >= 0; i--) {
    const p = pts[i];
    while (upper.length >= 2 && cross(upper[upper.length-2], upper[upper.length-1], p) <= 0) upper.pop();
    upper.push(p);
  }
  lower.pop(); upper.pop();
  return lower.concat(upper);
};

window.M3 = M3;
window.convexHull2D = M3.convexHull2D;
