Phase 3 update: orbit ghosts, parallax, FX, shader fix [build v8]
This commit is contained in:
+31
-6
@@ -65,9 +65,18 @@ function hexToRGB(THREE, hex) { return new THREE.Color(hex); }
|
||||
* Kept cheap: a subdivided cone/teardrop so the gradient + billow read well.
|
||||
*/
|
||||
function buildWispGeometry(THREE) {
|
||||
// teardrop: radius shrinks toward the tail (top in UV space = head)
|
||||
const geo = new THREE.CylinderGeometry(0.0, 0.5, 1.2, 18, 8, true);
|
||||
geo.translate(0, 0.1, 0);
|
||||
// teardrop-ish body: rounded head (sphere-like top) tapering to a wispy tail.
|
||||
// Lathe a profile curve so it reads as a ghost rather than a hard cone.
|
||||
const pts = [];
|
||||
const N = 12;
|
||||
for (let i = 0; i <= N; i++) {
|
||||
const t = i / N; // 0 = tail bottom, 1 = head top
|
||||
const y = -0.6 + t * 1.2; // height -0.6 .. 0.6
|
||||
// radius: thin pointed tail, bulging rounded head
|
||||
const r = Math.sin(t * Math.PI * 0.92) * 0.42 + t * 0.06;
|
||||
pts.push(new THREE.Vector2(Math.max(0.001, r), y));
|
||||
}
|
||||
const geo = new THREE.LatheGeometry(pts, 20);
|
||||
return geo;
|
||||
}
|
||||
|
||||
@@ -98,6 +107,18 @@ export function createGhostVisual(THREE, opts = {}) {
|
||||
const body = new THREE.Mesh(buildWispGeometry(THREE), mat);
|
||||
group.add(body);
|
||||
|
||||
// --- always-on soft glow halo (procedural, no texture needed) so the ghost
|
||||
// never looks like a bare cone even before FX textures attach ---
|
||||
const haloMat = new THREE.SpriteMaterial({
|
||||
color: hexToRGB(THREE, grad.bottom),
|
||||
transparent: true, opacity: 0.35, depthWrite: false,
|
||||
blending: THREE.AdditiveBlending,
|
||||
});
|
||||
const halo = new THREE.Sprite(haloMat);
|
||||
halo.scale.setScalar(1.5);
|
||||
halo.position.y = 0.1;
|
||||
group.add(halo);
|
||||
|
||||
// --- Tier 1: FX billboards (progressive enhancement) ---
|
||||
const fxSprites = [];
|
||||
function addFxBillboard(texture, scale, yOffset, opacity, blend) {
|
||||
@@ -121,8 +142,10 @@ export function createGhostVisual(THREE, opts = {}) {
|
||||
let fxAttached = false;
|
||||
function attachFx(textures) {
|
||||
if (!wantFx || fxAttached || !textures) return;
|
||||
if (textures.glow) addFxBillboard(textures.glow, 1.6, 0.1, 0.5);
|
||||
if (textures.trail) addFxBillboard(textures.trail, 1.2, -0.4, 0.35);
|
||||
// give the procedural halo a real soft-glow texture if provided
|
||||
if (textures.glow) { haloMat.map = textures.glow; haloMat.opacity = 0.55; haloMat.needsUpdate = true; }
|
||||
if (textures.trail) addFxBillboard(textures.trail, 1.0, -0.45, 0.4);
|
||||
if (textures.smoke) addFxBillboard(textures.smoke, 0.8, 0.3, 0.25);
|
||||
fxAttached = true;
|
||||
}
|
||||
|
||||
@@ -150,6 +173,7 @@ export function createGhostVisual(THREE, opts = {}) {
|
||||
// stays fixed (previously this accumulated onto group.y and the ghost
|
||||
// drifted up out of view every frame).
|
||||
body.position.y = Math.sin(elapsed * 1.8) * 0.08;
|
||||
halo.position.y = 0.1 + Math.sin(elapsed * 1.8) * 0.08; // halo bobs with body
|
||||
// billboard FX face camera handled by Sprite automatically
|
||||
if (state === 'spawn') {
|
||||
const k = Math.min(stateT / 0.6, 1);
|
||||
@@ -178,10 +202,11 @@ export function createGhostVisual(THREE, opts = {}) {
|
||||
const g2 = GHOST_GRADIENTS[next]; if (!g2) return;
|
||||
uniforms.uTop.value.set(g2.top);
|
||||
uniforms.uBottom.value.set(g2.bottom);
|
||||
haloMat.color.set(g2.bottom);
|
||||
fxSprites.forEach(s => s.material.color.set(g2.top));
|
||||
},
|
||||
dispose() {
|
||||
body.geometry?.dispose?.(); mat.dispose?.();
|
||||
body.geometry?.dispose?.(); mat.dispose?.(); haloMat.dispose?.();
|
||||
fxSprites.forEach(s => s.material?.map?.dispose?.());
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user