Grid Updates

This commit is contained in:
Brian Fertig 2026-04-09 21:23:31 -06:00
parent f34ff9b8fb
commit be25e35230
1 changed files with 226 additions and 6 deletions

View File

@ -118,6 +118,208 @@ if (canvas) {
return geo; return geo;
} }
// ---------- Palm trees ----------
const palmMat = new THREE.LineBasicMaterial({ color: 0x00e5ff, transparent: true, opacity: 0.7 });
function makePalmTree() {
const group = new THREE.Group();
// Trunk
const trunkGeo = new THREE.CylinderGeometry(0.08, 0.15, 3.2, 5, 1);
const trunk = new THREE.LineSegments(new THREE.EdgesGeometry(trunkGeo), palmMat);
trunk.rotation.z = 0.12;
group.add(trunk);
// Fronds — 5 cone leaves spread around trunk top, long and drooping
for (let i = 0; i < 5; i++) {
const frondGeo = new THREE.ConeGeometry(0.75, 2.8, 4, 1);
const frond = new THREE.LineSegments(new THREE.EdgesGeometry(frondGeo), palmMat);
frond.position.y = 1.4; // half of cone height, so base sits at pivot
const pivot = new THREE.Object3D();
pivot.position.set(0, 3.2, 0); // trunk top
pivot.rotation.z = -1.15; // droop outward ~66°
pivot.rotation.y = (Math.PI * 2 / 5) * i;
pivot.add(frond);
group.add(pivot);
}
return group;
}
// 4 columns × 4 rows = 16 trees; adjacent columns staggered by half-step
const PALM_COLS = [
{ x: +10, zOffset: 0 },
{ x: +14, zOffset: -7 },
{ x: -10, zOffset: 0 },
{ x: -14, zOffset: -7 },
];
const PALM_Z_ROWS = [-5, -19, -33, -47];
const palmTrees = [];
for (const col of PALM_COLS) {
for (const zRow of PALM_Z_ROWS) {
const tree = makePalmTree();
tree.position.set(col.x, 0, zRow + col.zOffset);
tree.userData.baseX = col.x; // restore X to this lane on recycle
scene.add(tree);
palmTrees.push(tree);
}
}
// ---------- Flyby wireframe vehicles ----------
function rnd(a, b) { return a + Math.random() * (b - a); }
function makeVehicleMat() {
// Each vehicle gets its own material so color can be changed at spawn time.
return new THREE.LineBasicMaterial({ color: 0x00e5ff, transparent: true, opacity: 0.9 });
}
function addEdges(group, geo, mat) {
group.add(new THREE.LineSegments(new THREE.EdgesGeometry(geo), mat));
}
function makeAirplane() {
const mat = makeVehicleMat();
const g = new THREE.Group();
addEdges(g, new THREE.BoxGeometry(4.0, 0.4, 0.6), mat); // fuselage
const lw = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(2.2, 0.12, 0.9)), mat);
lw.position.set(-1.8, 0, 0); g.add(lw); // left wing
const rw = lw.clone(); rw.position.set(1.8, 0, 0); g.add(rw); // right wing
const vf = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(0.1, 0.7, 0.45)), mat);
vf.position.set(-1.9, 0.3, 0); g.add(vf); // vertical tail fin
const hs = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.0, 0.1, 0.35)), mat);
hs.position.set(-1.9, 0.1, 0); g.add(hs); // horizontal stabiliser
return { group: g, mat };
}
function makeUFO() {
const mat = makeVehicleMat();
const g = new THREE.Group();
addEdges(g, new THREE.CylinderGeometry(1.8, 1.8, 0.35, 8, 1), mat); // main disc
const dome = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.SphereGeometry(0.8, 6, 4)), mat);
dome.position.set(0, 0.35, 0); g.add(dome); // top dome
const rim = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.CylinderGeometry(2.1, 2.1, 0.1, 8, 1)), mat);
rim.position.set(0, -0.1, 0); g.add(rim); // outer rim
return { group: g, mat };
}
function makeCar() {
const mat = makeVehicleMat();
const g = new THREE.Group();
addEdges(g, new THREE.BoxGeometry(2.8, 0.55, 1.2), mat); // body
const roof = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.4, 0.5, 1.0)), mat);
roof.position.set(-0.2, 0.52, 0); g.add(roof);
const wheelPositions = [[0.9, -0.2, 0.65], [0.9, -0.2, -0.65], [-0.9, -0.2, 0.65], [-0.9, -0.2, -0.65]];
for (const [x, y, z] of wheelPositions) {
const w = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.CylinderGeometry(0.3, 0.3, 0.2, 6, 1)), mat);
w.rotation.z = Math.PI / 2;
w.position.set(x, y, z);
g.add(w);
}
return { group: g, mat };
}
function makeSpaceship() {
const mat = makeVehicleMat();
const g = new THREE.Group();
const front = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(2.0, 0.4, 0.8)), mat);
front.position.set(0.75, 0, 0); g.add(front);
const rear = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.5, 0.7, 1.4)), mat);
rear.position.set(-0.8, 0, 0); g.add(rear);
const cockpit = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(0.5, 0.3, 0.5)), mat);
cockpit.position.set(0.9, 0.35, 0); g.add(cockpit);
// swept wings
for (const side of [1, -1]) {
const wing = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.6, 0.1, 0.65)), mat);
wing.rotation.y = side * 0.35;
wing.position.set(-0.5, -0.1, side * 0.9); g.add(wing);
const nacelle = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(0.4, 0.3, 0.3)), mat);
nacelle.position.set(-1.6, 0, side * 0.55); g.add(nacelle);
}
return { group: g, mat };
}
function makeHelicopter() {
const mat = makeVehicleMat();
const g = new THREE.Group();
addEdges(g, new THREE.BoxGeometry(2.0, 0.8, 0.9), mat); // cabin
const tail = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.8, 0.2, 0.25)), mat);
tail.position.set(-1.8, 0.2, 0); g.add(tail);
const mainRotor = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.CylinderGeometry(2.0, 2.0, 0.05, 8, 1)), mat);
mainRotor.position.set(0, 0.7, 0); g.add(mainRotor);
const tailRotor = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.CylinderGeometry(0.45, 0.45, 0.06, 6, 1)), mat);
tailRotor.rotation.z = Math.PI / 2;
tailRotor.position.set(-2.7, 0.35, 0.15); g.add(tailRotor);
for (const side of [1, -1]) {
const skid = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.6, 0.1, 0.12)), mat);
skid.position.set(0, -0.5, side * 0.5); g.add(skid);
}
return { group: g, mat };
}
function makeRocket() {
const mat = makeVehicleMat();
const g = new THREE.Group();
// Build pointing along local Y, then rotate group so it flies along X
addEdges(g, new THREE.CylinderGeometry(0.22, 0.28, 3.0, 6, 1), mat);
const nose = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.ConeGeometry(0.25, 0.85, 6, 1)), mat);
nose.position.set(0, 1.93, 0); g.add(nose);
for (let i = 0; i < 3; i++) {
const fin = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(0.08, 0.7, 0.5)), mat);
fin.rotation.y = (Math.PI * 2 / 3) * i;
fin.position.set(Math.sin((Math.PI * 2 / 3) * i) * 0.28, -1.1, Math.cos((Math.PI * 2 / 3) * i) * 0.28);
g.add(fin);
}
g.rotation.z = Math.PI / 2; // point along X so xVelocity moves it naturally
return { group: g, mat };
}
function makeMotorcycle() {
const mat = makeVehicleMat();
const g = new THREE.Group();
for (const x of [0.85, -0.75]) {
const wheel = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.CylinderGeometry(0.5, 0.5, 0.2, 8, 1)), mat);
wheel.rotation.z = Math.PI / 2;
wheel.position.set(x, 0, 0); g.add(wheel);
}
const frame = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.5, 0.6, 0.25)), mat);
frame.position.set(0, 0.5, 0); g.add(frame);
const seat = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(0.7, 0.15, 0.3)), mat);
seat.position.set(-0.1, 0.9, 0); g.add(seat);
const bar = new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(0.3, 0.08, 0.7)), mat);
bar.position.set(0.7, 0.85, 0); g.add(bar);
g.scale.setScalar(1.5);
return { group: g, mat };
}
const FLYBY_COLORS = [0x00e5ff, 0xff2bd6, 0x8f5bff];
const flybyPool = [makeAirplane, makeUFO, makeCar, makeSpaceship, makeHelicopter, makeRocket, makeMotorcycle]
.map(fn => {
const r = fn();
r.group.visible = false;
scene.add(r.group);
return r;
});
const flyby = {
active: null,
xVel: 0,
nextAt: performance.now() + rnd(15000, 30000),
colorIdx: 0,
};
function spawnFlyby() {
const entry = flybyPool[Math.floor(Math.random() * flybyPool.length)];
const goLeft = Math.random() < 0.5;
entry.mat.color.setHex(FLYBY_COLORS[flyby.colorIdx++ % FLYBY_COLORS.length]);
entry.group.position.set(goLeft ? 65 : -65, rnd(7, 12), rnd(-30, -45));
entry.group.rotation.set(0, 0, 0);
entry.group.visible = true;
flyby.active = entry;
flyby.xVel = goLeft ? -20 : 20;
}
// ---------- Beat / bass reactivity surface ---------- // ---------- Beat / bass reactivity surface ----------
const state = { const state = {
beat: 0, // smoothed bass energy 0..1 beat: 0, // smoothed bass energy 0..1
@ -131,16 +333,13 @@ if (canvas) {
}; };
function pickNewDirection() { function pickNewDirection() {
// Pick a random heading biased toward the forward hemisphere so the const spread = Math.PI * 0.9;
// horizon remains in frame, then ease toward it over the next second.
const spread = Math.PI * 0.9; // ±~80° around forward
state.targetAngle = (Math.random() - 0.5) * spread; state.targetAngle = (Math.random() - 0.5) * spread;
state.nextTurnAt = performance.now() + 10000; state.nextTurnAt = performance.now() + 10000;
} }
window.briTunesBg = { window.briTunesBg = {
setBeat(v) { setBeat(v) {
// called from player-viz.js
state.beat = Math.max(state.beat * 0.6, Math.min(1, v)); state.beat = Math.max(state.beat * 0.6, Math.min(1, v));
}, },
}; };
@ -174,10 +373,32 @@ if (canvas) {
state.scrollX += dt * speed * Math.sin(state.dirAngle); state.scrollX += dt * speed * Math.sin(state.dirAngle);
state.scrollZ += dt * speed * Math.cos(state.dirAngle); state.scrollZ += dt * speed * Math.cos(state.dirAngle);
const step = (GRID_SIZE / GRID_DIVS); const step = (GRID_SIZE / GRID_DIVS);
// Keep scroll in [0, step) so the grid wraps seamlessly on both axes.
grid.position.x = ((state.scrollX % step) + step) % step; grid.position.x = ((state.scrollX % step) + step) % step;
grid.position.z = ((state.scrollZ % step) + step) % step; grid.position.z = ((state.scrollZ % step) + step) % step;
// Scroll palm trees with the grid (same heading); recycle when behind camera
for (const t of palmTrees) {
t.position.x += dt * speed * Math.sin(state.dirAngle);
t.position.z += dt * speed * Math.cos(state.dirAngle);
if (t.position.z > 15) {
t.position.z = -54;
t.position.x = t.userData.baseX; // snap back to column lane on recycle
}
}
// Flyby objects
if (!flyby.active && now >= flyby.nextAt) spawnFlyby();
if (flyby.active) {
flyby.active.group.position.x += flyby.xVel * dt;
flyby.active.group.rotation.y += 0.3 * dt;
flyby.active.group.rotation.z += 0.12 * dt;
if (Math.abs(flyby.active.group.position.x) > 72) {
flyby.active.group.visible = false;
flyby.active = null;
flyby.nextAt = now + rnd(15000, 30000);
}
}
// Pulse color on beat (violet -> magenta) // Pulse color on beat (violet -> magenta)
const mix = Math.min(1, state.beat); const mix = Math.min(1, state.beat);
gridMat.color.setRGB(0.56 + mix * 0.44, 0.35 - mix * 0.2, 0.94); gridMat.color.setRGB(0.56 + mix * 0.44, 0.35 - mix * 0.2, 0.94);
@ -191,7 +412,6 @@ if (canvas) {
} }
if (reduced) { if (reduced) {
// Render a single still frame and stop.
renderer.render(scene, camera); renderer.render(scene, camera);
} else { } else {
requestAnimationFrame(frame); requestAnimationFrame(frame);