Phase 3 update: orbit ghosts, parallax, FX, shader fix [build v8]
This commit is contained in:
+80
-11
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user