From 386d094d44b8329f50a174b390dc63bdaf04fb2b Mon Sep 17 00:00:00 2001 From: jessikitty Date: Fri, 24 Jul 2026 11:34:29 +1000 Subject: [PATCH] Normalize ghost models to exact cm height (setHeight API, feet at origin, manifest scale multiplier) --- public/js/ghosts/loader.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/public/js/ghosts/loader.js b/public/js/ghosts/loader.js index b9be31f..4fdca52 100644 --- a/public/js/ghosts/loader.js +++ b/public/js/ghosts/loader.js @@ -83,7 +83,6 @@ export async function buildGhost(ghost, gradients, manifest) { if (!url) continue; group.add(await loadOBJ(url)); } - if (model.scale) group.scale.setScalar(model.scale); if (!group.children.length) group = proceduralWisp(); } catch (e) { console.warn('ghost model load failed, using wisp fallback', e); @@ -103,8 +102,20 @@ export async function buildGhost(ghost, gradients, manifest) { } }); - group.userData.material = mat; - group.userData.setOpacity = (v) => { mat.uniforms.opacity.value = v; }; - group.userData.tick = (t) => { mat.uniforms.uTime.value = t; }; - return group; + // Normalize: wrap so feet sit at the group origin, then expose setHeight(cm) that + // scales the whole ghost (any source units) to an exact world height. Minifigure ≈ 4 cm. + const outer = new THREE.Group(); + const inner = new THREE.Group(); + inner.position.y = -box.min.y; // feet -> origin + inner.add(group); + outer.add(inner); + const rawH = Math.max(box.max.y - box.min.y, 1e-6); + const modelScale = model?.scale || 1; // per-model fine-tune multiplier from the manifest + outer.userData.setHeight = (cm) => outer.scale.setScalar((cm / rawH) * modelScale); + outer.userData.setHeight(4); + + outer.userData.material = mat; + outer.userData.setOpacity = (v) => { mat.uniforms.opacity.value = v; }; + outer.userData.tick = (t) => { mat.uniforms.uTime.value = t; }; + return outer; }