[claude] Gravity anomaly zones — particles floating upward (#244) #347

Merged
Timmy merged 1 commits from claude/issue-244 into main 2026-03-24 05:33:25 +00:00

97
app.js
View File

@@ -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,