diff --git a/public/ghost-visual.js b/public/ghost-visual.js index bc49643..c718a9e 100644 --- a/public/ghost-visual.js +++ b/public/ghost-visual.js @@ -27,6 +27,15 @@ export const GHOST_GRADIENTS = { Blue: { top: '#529EFF', bottom: '#51EAF1' }, // GhostType_Sad }; +// Shared ghost mesh (Ghost_Bat) loaded once and cloned per-ghost. Set via +// setSharedMesh() before spawning; if null, ghosts fall back to the lathe wisp. +let SHARED_MESH = null; +export function setSharedMesh(mesh) { SHARED_MESH = mesh || null; } + +// Y-range of the shared mesh, for mapping the vertical gradient onto it. +let SHARED_MESH_Y = { min: -1.83, max: 1.83 }; +export function setSharedMeshYRange(min, max) { SHARED_MESH_Y = { min, max }; } + const WISP_VERT = ` varying float vY; varying vec3 vPos; @@ -58,6 +67,34 @@ const WISP_FRAG = ` } `; +// Gradient shader for the real mesh: maps the vertical gradient onto the mesh's +// object-space Y range (passed as uYMin/uYMax) and keeps it solid + glowy. +const MESH_VERT = ` + varying float vT; + uniform float uYMin; + uniform float uYMax; + uniform float uTime; + void main() { + vT = clamp((position.y - uYMin) / max(0.001, (uYMax - uYMin)), 0.0, 1.0); + vec3 p = position; + // subtle billow so the ghost feels alive + p.x += sin(uTime * 1.5 + position.y * 2.0) * 0.03; + gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0); + } +`; +const MESH_FRAG = ` + varying float vT; + uniform vec3 uTop; + uniform vec3 uBottom; + uniform float uAlpha; + uniform float uTime; + void main() { + vec3 col = mix(uBottom, uTop, vT); + float shimmer = 0.85 + 0.15 * sin(uTime * 2.0 + vT * 6.2831); + gl_FragColor = vec4(col * shimmer, uAlpha); + } +`; + function hexToRGB(THREE, hex) { return new THREE.Color(hex); } /** @@ -88,23 +125,55 @@ export function createGhostVisual(THREE, opts = {}) { const group = new THREE.Group(); group.name = `ghost-${color}`; - // --- Tier 0: wisp body + gradient material --- + // --- Tier 0: ghost body. Prefer the real shared mesh (Ghost_Bat), recoloured + // by the gradient; fall back to the procedural lathe wisp if no mesh. --- const uniforms = { uTime: { value: 0 }, uTop: { value: hexToRGB(THREE, grad.top) }, uBottom: { value: hexToRGB(THREE, grad.bottom) }, uAlpha: { value: 1.0 }, }; - const mat = new THREE.ShaderMaterial({ - vertexShader: WISP_VERT, - fragmentShader: WISP_FRAG, - uniforms, - transparent: true, - depthWrite: false, - side: THREE.DoubleSide, - blending: THREE.NormalBlending, - }); - const body = new THREE.Mesh(buildWispGeometry(THREE), mat); + + let body, mat; + if (SHARED_MESH) { + // clone the shared mesh geometry and give it the mesh gradient material + mat = new THREE.ShaderMaterial({ + vertexShader: MESH_VERT, + fragmentShader: MESH_FRAG, + uniforms: { + ...uniforms, + uYMin: { value: SHARED_MESH_Y.min }, + uYMax: { value: SHARED_MESH_Y.max }, + }, + transparent: true, + depthWrite: true, + side: THREE.DoubleSide, + blending: THREE.NormalBlending, + }); + // SHARED_MESH may be a Group (from OBJLoader) or a Mesh; find the first mesh geometry + let srcGeo = null; + SHARED_MESH.traverse?.(o => { if (!srcGeo && o.isMesh) srcGeo = o.geometry; }); + if (!srcGeo && SHARED_MESH.isMesh) srcGeo = SHARED_MESH.geometry; + body = new THREE.Mesh(srcGeo, mat); + // normalise scale: mesh is ~3.6 units tall; scale to ~1.2 like the wisp + const targetH = 1.4; + const meshH = (SHARED_MESH_Y.max - SHARED_MESH_Y.min) || 3.66; + body.scale.setScalar(targetH / meshH); + // keep the alpha-driven uniforms reachable for animation + uniforms.uTime = mat.uniforms.uTime; + uniforms.uAlpha = mat.uniforms.uAlpha; + } else { + mat = new THREE.ShaderMaterial({ + vertexShader: WISP_VERT, + fragmentShader: WISP_FRAG, + uniforms, + transparent: true, + depthWrite: false, + side: THREE.DoubleSide, + blending: THREE.NormalBlending, + }); + body = new THREE.Mesh(buildWispGeometry(THREE), mat); + } group.add(body); // --- always-on soft glow halo (procedural, no texture needed) so the ghost diff --git a/public/hunt.html b/public/hunt.html index a42038f..c0b070e 100644 --- a/public/hunt.html +++ b/public/hunt.html @@ -203,7 +203,7 @@