[claude] Gravity anomaly zones — particles floating upward (#244) #347
97
app.js
97
app.js
@@ -1394,6 +1394,27 @@ function animate() {
|
||||
snowGeo.attributes.position.needsUpdate = true;
|
||||
}
|
||||
|
||||
// === GRAVITY ANOMALY ANIMATION ===
|
||||
for (const gz of gravityZoneObjects) {
|
||||
const pos = gz.geo.attributes.position.array;
|
||||
const count = gz.zone.particleCount;
|
||||
for (let i = 0; i < count; i++) {
|
||||
pos[i * 3 + 1] += gz.velocities[i];
|
||||
pos[i * 3] += Math.sin(elapsed * 0.5 + gz.driftPhases[i]) * 0.003;
|
||||
pos[i * 3 + 2] += Math.cos(elapsed * 0.5 + gz.driftPhases[i]) * 0.003;
|
||||
if (pos[i * 3 + 1] > GRAVITY_ANOMALY_CEIL) {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const r = Math.sqrt(Math.random()) * gz.zone.radius;
|
||||
pos[i * 3] = gz.zone.x + Math.cos(angle) * r;
|
||||
pos[i * 3 + 1] = GRAVITY_ANOMALY_FLOOR + Math.random() * 2.0;
|
||||
pos[i * 3 + 2] = gz.zone.z + Math.sin(angle) * r;
|
||||
}
|
||||
}
|
||||
gz.geo.attributes.position.needsUpdate = true;
|
||||
gz.ringMat.opacity = 0.3 + Math.sin(elapsed * 1.5 + gz.zone.x) * 0.15;
|
||||
gz.discMat.opacity = 0.02 + Math.sin(elapsed * 1.5 + gz.zone.x) * 0.02;
|
||||
}
|
||||
|
||||
// Portal collision detection
|
||||
forwardVector.set(0, 0, -1).applyQuaternion(camera.quaternion);
|
||||
raycaster.set(camera.position, forwardVector);
|
||||
@@ -3073,6 +3094,82 @@ async function fetchWeather() {
|
||||
fetchWeather();
|
||||
setInterval(fetchWeather, WEATHER_REFRESH_MS);
|
||||
|
||||
// === GRAVITY ANOMALY ZONES ===
|
||||
// Areas where particles defy gravity and float upward.
|
||||
// Each zone has a glowing floor ring and a rising particle stream.
|
||||
|
||||
const GRAVITY_ANOMALY_FLOOR = 0.2; // Y where particles respawn (ground level)
|
||||
const GRAVITY_ANOMALY_CEIL = 16.0; // Y where particles wrap back to floor
|
||||
|
||||
const GRAVITY_ZONES = [
|
||||
{ x: -8, z: -6, radius: 3.5, color: 0x00ffcc, particleCount: 180 },
|
||||
{ x: 10, z: 4, radius: 3.0, color: 0xaa44ff, particleCount: 160 },
|
||||
{ x: -3, z: 9, radius: 2.5, color: 0xff8844, particleCount: 140 },
|
||||
];
|
||||
|
||||
const gravityZoneObjects = GRAVITY_ZONES.map((zone) => {
|
||||
// Glowing floor ring
|
||||
const ringGeo = new THREE.RingGeometry(zone.radius - 0.15, zone.radius + 0.15, 64);
|
||||
const ringMat = new THREE.MeshBasicMaterial({
|
||||
color: zone.color,
|
||||
transparent: true,
|
||||
opacity: 0.4,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
});
|
||||
const ring = new THREE.Mesh(ringGeo, ringMat);
|
||||
ring.rotation.x = -Math.PI / 2;
|
||||
ring.position.set(zone.x, GRAVITY_ANOMALY_FLOOR + 0.05, zone.z);
|
||||
scene.add(ring);
|
||||
|
||||
// Faint inner disc
|
||||
const discGeo = new THREE.CircleGeometry(zone.radius - 0.15, 64);
|
||||
const discMat = new THREE.MeshBasicMaterial({
|
||||
color: zone.color,
|
||||
transparent: true,
|
||||
opacity: 0.04,
|
||||
side: THREE.DoubleSide,
|
||||
depthWrite: false,
|
||||
});
|
||||
const disc = new THREE.Mesh(discGeo, discMat);
|
||||
disc.rotation.x = -Math.PI / 2;
|
||||
disc.position.set(zone.x, GRAVITY_ANOMALY_FLOOR + 0.04, zone.z);
|
||||
scene.add(disc);
|
||||
|
||||
// Rising particle stream
|
||||
const count = zone.particleCount;
|
||||
const positions = new Float32Array(count * 3);
|
||||
const driftPhases = new Float32Array(count);
|
||||
const velocities = new Float32Array(count);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const angle = Math.random() * Math.PI * 2;
|
||||
const r = Math.sqrt(Math.random()) * zone.radius;
|
||||
positions[i * 3] = zone.x + Math.cos(angle) * r;
|
||||
positions[i * 3 + 1] = GRAVITY_ANOMALY_FLOOR + Math.random() * (GRAVITY_ANOMALY_CEIL - GRAVITY_ANOMALY_FLOOR);
|
||||
positions[i * 3 + 2] = zone.z + Math.sin(angle) * r;
|
||||
driftPhases[i] = Math.random() * Math.PI * 2;
|
||||
velocities[i] = 0.03 + Math.random() * 0.04;
|
||||
}
|
||||
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
||||
|
||||
const mat = new THREE.PointsMaterial({
|
||||
color: zone.color,
|
||||
size: 0.10,
|
||||
sizeAttenuation: true,
|
||||
transparent: true,
|
||||
opacity: 0.7,
|
||||
depthWrite: false,
|
||||
});
|
||||
|
||||
const points = new THREE.Points(geo, mat);
|
||||
scene.add(points);
|
||||
|
||||
return { zone, ring, ringMat, disc, discMat, points, geo, driftPhases, velocities };
|
||||
});
|
||||
|
||||
// === TIMMY SPEECH BUBBLE ===
|
||||
// When Timmy sends a chat message, a glowing floating text sprite appears near
|
||||
// his avatar position above the platform. Fades in quickly, holds for 5 s total,
|
||||
|
||||
Reference in New Issue
Block a user