update Tue 07/14/2026 11:38:16.92
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
/* behavior.js — deterministic ghost motion so all viewers see the same movement.
|
||||
* Motion is a pure function of (server spawn record, wall-clock time): no per-client
|
||||
* randomness, so phones stay in sync without streaming positions.
|
||||
*/
|
||||
import * as THREE from 'three';
|
||||
|
||||
function hashNoise(seed, k) {
|
||||
// cheap deterministic pseudo-noise in [-1, 1]
|
||||
const x = Math.sin(seed * 127.1 + k * 311.7) * 43758.5453;
|
||||
return (x - Math.floor(x)) * 2 - 1;
|
||||
}
|
||||
|
||||
export function ghostTransform(rec, nowMs, out) {
|
||||
const t = (nowMs - rec.spawnedAt) / 1000;
|
||||
const base = new THREE.Vector3(...rec.pos);
|
||||
const b = rec.behavior || { type: 'static' };
|
||||
|
||||
if (b.type === 'wander') {
|
||||
const s = b.seed || 1;
|
||||
const R = b.radius ?? 0.6;
|
||||
const v = b.speed ?? 0.15;
|
||||
// smooth pseudo-random orbit-drift: two incommensurate sines per axis
|
||||
const ph = t * v;
|
||||
out.position.set(
|
||||
base.x + R * 0.9 * Math.sin(ph * 1.0 + hashNoise(s, 1) * 6.28) * 0.7
|
||||
+ R * 0.4 * Math.sin(ph * 2.3 + hashNoise(s, 2) * 6.28) * 0.3,
|
||||
base.y + 0.06 * Math.sin(t * 1.7 + hashNoise(s, 3) * 6.28),
|
||||
base.z + R * 0.9 * Math.cos(ph * 0.8 + hashNoise(s, 4) * 6.28) * 0.7
|
||||
+ R * 0.4 * Math.cos(ph * 1.9 + hashNoise(s, 5) * 6.28) * 0.3
|
||||
);
|
||||
// face travel direction (finite difference)
|
||||
const eps = 0.05;
|
||||
const ahead = (t2) => new THREE.Vector3(
|
||||
base.x + R * 0.9 * Math.sin(t2 * v + hashNoise(s, 1) * 6.28) * 0.7,
|
||||
0,
|
||||
base.z + R * 0.9 * Math.cos(t2 * v * 0.8 + hashNoise(s, 4) * 6.28) * 0.7);
|
||||
const dir = ahead(t + eps).sub(ahead(t));
|
||||
out.rotationY = Math.atan2(dir.x, dir.z);
|
||||
} else {
|
||||
const amp = b.bobAmp ?? 0.06;
|
||||
const hz = b.bobHz ?? 0.4;
|
||||
out.position.set(base.x, base.y + amp * Math.sin(t * hz * Math.PI * 2), base.z);
|
||||
out.rotationY = 0.25 * Math.sin(t * 0.3); // slow idle sway
|
||||
}
|
||||
|
||||
// crossfade opacity: fade in on spawn, fade out approaching `until`
|
||||
const fade = (rec.crossfade || 3) * 1000;
|
||||
const inA = Math.min(1, (nowMs - rec.spawnedAt) / fade);
|
||||
const outA = Math.min(1, Math.max(0, (rec.until - nowMs) / fade));
|
||||
out.opacity = Math.min(inA, outA);
|
||||
return out;
|
||||
}
|
||||
Reference in New Issue
Block a user