Projection onto the picture
32-bit, display onlyA model point's place in the picture and its depth.
orthographic: u = aspect ÷ 2 + q·right ÷ 2h; perspective: u = aspect ÷ 2 + q·right ÷ (2 tan(fov ÷ 2) × depth)
The view axes are unit vectors from two cross products. In perspective the depth is floored at 1e-9 before dividing. The result places lines in a picture.
export function projector(cam, aspect = 1) {
const dir = norm(sub(cam.target, cam.pos));
let right = norm(cross(dir, cam.up));
if (len(right) < 1e-9) right = norm(cross(dir, Math.abs(dir[1]) < 0.9 ? [0, 1, 0] : [1, 0, 0]));
const up = cross(right, dir);
const dist = len(sub(cam.target, cam.pos));
if (cam.type === 'persp') {
const tanH = Math.tan((cam.fov || 35) * Math.PI / 360); // half the vertical field
return {
type: 'persp', aspect, right, up, dir, mmPerUnit: 2 * dist * tanH,
project(p) {
const q = sub(p, cam.pos), d = dot(q, dir);
const k = 1 / (2 * tanH * Math.max(d, 1e-9));
return [aspect / 2 + dot(q, right) * k, 0.5 + dot(q, up) * k, d];
},
ray(u, v) {
const x = (u - aspect / 2) * 2 * tanH, y = (v - 0.5) * 2 * tanH;
return { o: [...cam.pos], d: norm(add(dir, add(mul(right, x), mul(up, y)))) };
},
};
}
const h = cam.height || 100;
return {
type: 'ortho', aspect, right, up, dir, mmPerUnit: 2 * h,
project(p) {
const q = sub(p, cam.target);
return [aspect / 2 + dot(q, right) / (2 * h), 0.5 + dot(q, up) / (2 * h), dot(sub(p, cam.pos), dir)];
},
ray(u, v) {
const o = add(cam.target, add(mul(right, (u - aspect / 2) * 2 * h), mul(up, (v - 0.5) * 2 * h)));
return { o: sub(o, mul(dir, dist + 1e6)), d: dir };
},
};
}