94 lines
2.6 KiB
JavaScript
94 lines
2.6 KiB
JavaScript
// Shared preview render-facing primitives.
|
|
// Kept intentionally small in this refactor phase; runtime still hosts the
|
|
// full draw pipeline and can be incrementally migrated here.
|
|
import { getCutoutShotParams } from "./pano_cutout_projection.js";
|
|
import { clamp, wrapYaw, shortestYawDelta } from "./pano_math.js";
|
|
|
|
export const DEG2RAD = Math.PI / 180;
|
|
export const RAD2DEG = 180 / Math.PI;
|
|
|
|
export { clamp, wrapYaw, shortestYawDelta };
|
|
|
|
export function vec3(x, y, z) {
|
|
return { x, y, z };
|
|
}
|
|
|
|
export function add(a, b) {
|
|
return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
|
|
}
|
|
|
|
export function mul(a, s) {
|
|
return vec3(a.x * s, a.y * s, a.z * s);
|
|
}
|
|
|
|
export function dot(a, b) {
|
|
return a.x * b.x + a.y * b.y + a.z * b.z;
|
|
}
|
|
|
|
export function cross(a, b) {
|
|
return vec3(
|
|
a.y * b.z - a.z * b.y,
|
|
a.z * b.x - a.x * b.z,
|
|
a.x * b.y - a.y * b.x,
|
|
);
|
|
}
|
|
|
|
export function norm(a) {
|
|
const l = Math.hypot(a.x, a.y, a.z) || 1e-8;
|
|
return vec3(a.x / l, a.y / l, a.z / l);
|
|
}
|
|
|
|
export function yawPitchToDir(yawDeg, pitchDeg) {
|
|
const yaw = yawDeg * DEG2RAD;
|
|
const pitch = pitchDeg * DEG2RAD;
|
|
const cp = Math.cos(pitch);
|
|
return vec3(cp * Math.sin(yaw), Math.sin(pitch), cp * Math.cos(yaw));
|
|
}
|
|
|
|
export function cameraBasis(yawDeg, pitchDeg, rollDeg = 0) {
|
|
const fwd = yawPitchToDir(yawDeg, pitchDeg);
|
|
const worldUp = vec3(0, 1, 0);
|
|
let right = cross(worldUp, fwd);
|
|
if (Math.hypot(right.x, right.y, right.z) < 1e-6) right = vec3(1, 0, 0);
|
|
right = norm(right);
|
|
const up0 = norm(cross(fwd, right));
|
|
const rr = rollDeg * DEG2RAD;
|
|
const cr = Math.cos(rr);
|
|
const sr = Math.sin(rr);
|
|
const r2 = add(mul(right, cr), mul(up0, sr));
|
|
const u2 = add(mul(right, -sr), mul(up0, cr));
|
|
return { fwd, right: norm(r2), up: norm(u2) };
|
|
}
|
|
|
|
export function parseState(text, bg = "#00ff00") {
|
|
const base = {
|
|
version: 1,
|
|
projection_model: "pinhole_rectilinear",
|
|
alpha_mode: "straight",
|
|
bg_color: bg,
|
|
output_preset: 2048,
|
|
assets: {},
|
|
stickers: [],
|
|
shots: [],
|
|
active: { selected_sticker_id: null },
|
|
};
|
|
const trimmed = String(text || "").trim();
|
|
if (!trimmed) return base;
|
|
try {
|
|
const p = JSON.parse(trimmed);
|
|
if (!p || typeof p !== "object") return base;
|
|
return {
|
|
...base,
|
|
...p,
|
|
assets: p.assets && typeof p.assets === "object" && !Array.isArray(p.assets) ? p.assets : {},
|
|
stickers: Array.isArray(p.stickers) ? p.stickers : [],
|
|
shots: Array.isArray(p.shots) ? p.shots : [],
|
|
active: p.active && typeof p.active === "object" && !Array.isArray(p.active) ? p.active : base.active,
|
|
};
|
|
} catch {
|
|
return base;
|
|
}
|
|
}
|
|
|
|
export { getCutoutShotParams };
|