Grid Updates
This commit is contained in:
parent
f34ff9b8fb
commit
be25e35230
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue