music-video-gen/party-cathedral/src/scene/party-guests.js
2025-11-22 00:04:01 +01:00

157 lines
6.1 KiB
JavaScript

import * as THREE from 'three';
import { state } from '../state.js';
import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js';
const guestTextureUrls = [
'/textures/guest1.png',
'/textures/guest2.png',
'/textures/guest3.png',
'/textures/guest4.png',
];
// --- Scene dimensions for positioning ---
const stageHeight = 1.5;
const stageDepth = 5;
const length = 44;
// --- Billboard Properties ---
const guestHeight = 2.5;
const guestWidth = 2.5;
export class PartyGuests extends SceneFeature {
constructor() {
super();
this.guests = [];
sceneFeatureManager.register(this);
}
async init() {
const processTexture = (texture) => {
const image = texture.image;
const canvas = document.createElement('canvas');
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext('2d');
context.drawImage(image, 0, 0);
const keyPixelData = context.getImageData(0, 0, 1, 1).data;
const keyColor = { r: keyPixelData[0], g: keyPixelData[1], b: keyPixelData[2] };
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const threshold = 20;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
const distance = Math.sqrt(Math.pow(r - keyColor.r, 2) + Math.pow(g - keyColor.g, 2) + Math.pow(b - keyColor.b, 2));
if (distance < threshold) data[i + 3] = 0;
}
context.putImageData(imageData, 0, 0);
return new THREE.CanvasTexture(canvas);
};
const materials = await Promise.all(guestTextureUrls.map(async (url) => {
const texture = await state.loader.loadAsync(url);
const processedTexture = processTexture(texture);
return new THREE.MeshStandardMaterial({
map: processedTexture,
side: THREE.DoubleSide,
alphaTest: 0.5,
roughness: 0.7,
metalness: 0.1,
});
}));
const createGuests = () => {
const geometry = new THREE.PlaneGeometry(guestWidth, guestHeight);
const numGuests = 80;
for (let i = 0; i < numGuests; i++) {
const material = materials[i % materials.length];
const guest = new THREE.Mesh(geometry, material);
const pos = new THREE.Vector3(
(Math.random() - 0.5) * 10,
guestHeight / 2,
(Math.random() * 20) - 6 // Position them in the main hall
);
guest.position.copy(pos);
state.scene.add(guest);
this.guests.push({
mesh: guest,
state: 'WAITING',
targetPosition: pos.clone(),
waitStartTime: 0,
waitTime: 3 + Math.random() * 4, // Wait longer: 3-7 seconds
isJumping: false,
jumpStartTime: 0,
});
}
};
createGuests();
}
update(deltaTime) {
if (this.guests.length === 0) return;
const cameraPosition = new THREE.Vector3();
state.camera.getWorldPosition(cameraPosition);
const time = state.clock.getElapsedTime();
const moveSpeed = 1.0; // Move slower
const movementArea = { x: 10, z: 30, y: 0, centerZ: 5 };
const jumpChance = 0.05; // Jump way more
const jumpDuration = 0.5;
const jumpHeight = 0.1;
const jumpVariance = 0.5;
this.guests.forEach(guestObj => {
const { mesh } = guestObj;
mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z);
if (guestObj.state === 'WAITING') {
if (time > guestObj.waitStartTime + guestObj.waitTime) {
const newTarget = new THREE.Vector3(
(Math.random() - 0.5) * movementArea.x,
movementArea.y + guestHeight / 2,
movementArea.centerZ + (Math.random() - 0.5) * movementArea.z
);
guestObj.targetPosition = newTarget;
guestObj.state = 'MOVING';
}
} else if (guestObj.state === 'MOVING') {
const distance = mesh.position.distanceTo(guestObj.targetPosition);
if (distance > 0.1) {
const direction = guestObj.targetPosition.clone().sub(mesh.position).normalize();
mesh.position.add(direction.multiplyScalar(moveSpeed * deltaTime));
} else {
guestObj.state = 'WAITING';
guestObj.waitStartTime = time;
guestObj.waitTime = 3 + Math.random() * 4;
}
}
if (guestObj.isJumping) {
const jumpProgress = (time - guestObj.jumpStartTime) / jumpDuration;
if (jumpProgress < 1) {
const baseHeight = movementArea.y + guestHeight / 2;
mesh.position.y = baseHeight + Math.sin(jumpProgress * Math.PI) * guestObj.jumpHeight;
} else {
guestObj.isJumping = false;
mesh.position.y = movementArea.y + guestHeight / 2;
}
} else {
let currentJumpChance = jumpChance * deltaTime; // Base chance over time
if (state.music && state.music.beatIntensity > 0.8) {
currentJumpChance = 0.1; // High, fixed chance on the beat
}
if (Math.random() < currentJumpChance) {
guestObj.isJumping = true;
guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
guestObj.jumpStartTime = time;
}
}
});
}
}
new PartyGuests();