diff options
Diffstat (limited to 'public/bluebey-studio/src/main.js')
| -rw-r--r-- | public/bluebey-studio/src/main.js | 32 |
1 files changed, 27 insertions, 5 deletions
diff --git a/public/bluebey-studio/src/main.js b/public/bluebey-studio/src/main.js index 7fdc97f..849d3d1 100644 --- a/public/bluebey-studio/src/main.js +++ b/public/bluebey-studio/src/main.js @@ -382,14 +382,18 @@ async function init() { let hatObject = null; const hatMeshes = []; /** Last transform applied, so the render loop only wakes when it changes. */ - const hatXform = { height: null, tiltX: null, tiltZ: null }; + const hatXform = { x: null, z: null, height: null, tiltX: null, tiltZ: null }; /** Set once the outline pass exists, so a hat change can refresh the exclude set. */ let refreshOutlineExclusion = null; + /** Set once the style system exists, so a hat can register for styles + outline. */ + let hatStyleRegister = null; + let hatStyleUnregister = null; function applyHat(kind) { if (!hatMount) return; if (kind !== hatKindCurrent) { hatKindCurrent = kind; if (hatObject) { + hatObject.traverse((o) => { if (o.isMesh) hatStyleUnregister?.(o); }); hatMount.remove(hatObject); hatObject.traverse((o) => { o.geometry?.dispose(); @@ -403,7 +407,11 @@ async function init() { if (built) { hatMount.add(built); hatObject = built; - built.traverse((o) => { if (o.isMesh) hatMeshes.push(o); }); + built.traverse((o) => { + if (!o.isMesh) return; + hatMeshes.push(o); + hatStyleRegister?.(o); + }); } refreshOutlineExclusion?.(); } @@ -412,14 +420,18 @@ async function init() { // character's front and +x its right: a plain translation and rotation read // the way the sliders say. const hat = state.face.hat ?? {}; + const x = hat.x ?? 0; + const z = hat.z ?? 0; const height = hat.height ?? 0; const tiltX = hat.tiltX ?? 0; const tiltZ = hat.tiltZ ?? 0; if (hatObject) { - hatObject.position.y = height; + hatObject.position.set(x, height, z); hatObject.rotation.set((tiltX * Math.PI) / 180, 0, (tiltZ * Math.PI) / 180); } - if (height !== hatXform.height || tiltX !== hatXform.tiltX || tiltZ !== hatXform.tiltZ) { + if (x !== hatXform.x || z !== hatXform.z || height !== hatXform.height || tiltX !== hatXform.tiltX || tiltZ !== hatXform.tiltZ) { + hatXform.x = x; + hatXform.z = z; hatXform.height = height; hatXform.tiltX = tiltX; hatXform.tiltZ = tiltZ; @@ -454,6 +466,12 @@ async function init() { // ------------------------------------------------------------- controllers const styles = new Styles({ meshes: model.parts.body }); + // Hats are built at runtime, so they are not in the GLB's mesh list. Register + // them with the style system (and the current hat) so they follow flat/line art + // and get an outline hull like every other part. + hatStyleRegister = (mesh) => styles.addMesh(mesh); + hatStyleUnregister = (mesh) => styles.removeMesh(mesh); + for (const mesh of hatMeshes) styles.addMesh(mesh); const face = new Face({ eyeMesh: model.parts.eyeMesh, mouthMesh: model.parts.mouthMesh, @@ -1626,11 +1644,15 @@ async function init() { const eyeFor = (key) => { const own = eyes[key]; + // A shut artwork (1〜3線 / 3の目 / わらう) does not blink: its lid stays where + // the user put it, so the blink animation must not scale its `open`. + const shutArt = own.shape === 'three' || own.shape === 'arch' + || (typeof own.shape === 'string' && own.shape.startsWith('line')); return { // Which artwork this eye shows (ふつう / 1〜3線 / 3の目 / わらう / ハート). // The renderer chooses the shut artwork from this, so it must travel. shape: own.shape, - open: clamp01(own.open * openScale), + open: clamp01(own.open * (shutArt ? 1 : openScale)), lookX: clamp(own.lookX + drift.x + aimOffset.x, -1, 1), lookY: clamp(own.lookY + drift.y + aimOffset.y, -1, 1), closed: own.closed, |
