math3d.mjs

/**
 * Pure 3D Math Engine for Arcball/Trackball raw matrix transformations
 */

export const crossProduct = (u, v) => [
  u[1]*v[2] - u[2]*v[1],
  u[2]*v[0] - u[0]*v[2],
  u[0]*v[1] - u[1]*v[0]
];

export const dotProduct = (u, v) => u[0]*v[0] + u[1]*v[1] + u[2]*v[2];

export const normalize = (v) => {
  const len = Math.hypot(v[0], v[1], v[2]);
  if (len === 0) return [0, 0, 0];
  return [v[0]/len, v[1]/len, v[2]/len];
};

export const projectToTrackball = (x, y, radius = 1) => {
  const d2 = x*x + y*y;
  const r2 = radius * radius;
  if (d2 <= r2 * 0.5) {
     return normalize([x, y, Math.sqrt(r2 - d2)]);
  } else {
     return normalize([x, y, (r2 * 0.5) / Math.sqrt(d2)]);
  }
};

export const axisAngleToMatrix4 = (axis, angle) => {
  const [x, y, z] = normalize(axis);
  const c = Math.cos(angle);
  const s = Math.sin(angle);
  const t = 1 - c;

  // Column-major array mapping (standard for WebGL and Three.js matrices)
  return [
    c + x*x*t,     y*x*t + z*s,   z*x*t - y*s,   0,
    x*y*t - z*s,   c + y*y*t,     z*y*t + x*s,   0,
    x*z*t + y*s,   y*z*t - x*s,   c + z*z*t,     0,
    0,             0,             0,             1
  ];
};

export const multiplyMatrix4 = (a, b) => {
  const out = new Array(16);
  for (let c = 0; c < 4; c++) {
    for (let r = 0; r < 4; r++) {
      let sum = 0;
      for (let i = 0; i < 4; i++) {
         sum += a[i*4 + r] * b[c*4 + i]; // Note: column-major alignment
      }
      out[c*4 + r] = sum;
    }
  }
  return out;
};

export const getRotationMatrixFromVectors = (u, v) => {
  const axis = crossProduct(u, v);
  if (axis[0] === 0 && axis[1] === 0 && axis[2] === 0) {
      return [1,0,0,0,  0,1,0,0,  0,0,1,0,  0,0,0,1]; // Identity
  }
  const dot = dotProduct(u, v);
  const angle = Math.acos(Math.max(-1, Math.min(1, dot)));
  return axisAngleToMatrix4(axis, angle);
};