Feature: party guests
This commit is contained in:
parent
56ec41a802
commit
a4e48d1d5a
@ -7,13 +7,13 @@ function updateCamera() {
|
|||||||
const globalTime = Date.now() * 0.0001;
|
const globalTime = Date.now() * 0.0001;
|
||||||
const lookAtTime = Date.now() * 0.0002;
|
const lookAtTime = Date.now() * 0.0002;
|
||||||
|
|
||||||
const camAmplitude = 1.0;
|
const camAmplitude = new THREE.Vector3(1.0, 0.1, 10.0);
|
||||||
const lookAmplitude = 8.0;
|
const lookAmplitude = 8.0;
|
||||||
|
|
||||||
// Base Camera Position in front of the TV
|
// Base Camera Position in front of the TV
|
||||||
const baseX = 0;
|
const baseX = 0;
|
||||||
const baseY = 1.6;
|
const baseY = 2.6;
|
||||||
const baseZ = -10.0;
|
const baseZ = 0.0;
|
||||||
|
|
||||||
// Base LookAt target (Center of the screen)
|
// Base LookAt target (Center of the screen)
|
||||||
const baseTargetX = 0;
|
const baseTargetX = 0;
|
||||||
@ -21,9 +21,9 @@ function updateCamera() {
|
|||||||
const baseTargetZ = -30.0;
|
const baseTargetZ = -30.0;
|
||||||
|
|
||||||
// Camera Position Offsets (Drift)
|
// Camera Position Offsets (Drift)
|
||||||
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude;
|
const camOffsetX = Math.sin(globalTime * 3.1) * camAmplitude.x;
|
||||||
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude * 0.1;
|
const camOffsetY = Math.cos(globalTime * 2.5) * camAmplitude.y;
|
||||||
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude;
|
const camOffsetZ = Math.cos(globalTime * 3.2) * camAmplitude.z;
|
||||||
|
|
||||||
state.camera.position.x = baseX + camOffsetX;
|
state.camera.position.x = baseX + camOffsetX;
|
||||||
state.camera.position.y = baseY + camOffsetY;
|
state.camera.position.y = baseY + camOffsetY;
|
||||||
|
|||||||
152
party-cathedral/src/scene/party-guests.js
Normal file
152
party-cathedral/src/scene/party-guests.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
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) - 12 // 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: 0 };
|
||||||
|
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 {
|
||||||
|
if (Math.random() < jumpChance) {
|
||||||
|
guestObj.isJumping = true;
|
||||||
|
guestObj.jumpHeight = jumpHeight + Math.random() * jumpVariance;
|
||||||
|
guestObj.jumpStartTime = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new PartyGuests();
|
||||||
@ -8,6 +8,7 @@ import { LightBall } from './light-ball.js';
|
|||||||
import { Pews } from './pews.js';
|
import { Pews } from './pews.js';
|
||||||
import { Stage } from './stage.js';
|
import { Stage } from './stage.js';
|
||||||
import { MedievalMusicians } from './medieval-musicians.js';
|
import { MedievalMusicians } from './medieval-musicians.js';
|
||||||
|
import { PartyGuests } from './party-guests.js';
|
||||||
// Scene Features ^^^
|
// Scene Features ^^^
|
||||||
|
|
||||||
// --- Scene Modeling Function ---
|
// --- Scene Modeling Function ---
|
||||||
|
|||||||
BIN
party-cathedral/textures/guest1.png
Normal file
BIN
party-cathedral/textures/guest1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 972 KiB |
BIN
party-cathedral/textures/guest2.png
Normal file
BIN
party-cathedral/textures/guest2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 951 KiB |
BIN
party-cathedral/textures/guest3.png
Normal file
BIN
party-cathedral/textures/guest3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 981 KiB |
BIN
party-cathedral/textures/guest4.png
Normal file
BIN
party-cathedral/textures/guest4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Loading…
Reference in New Issue
Block a user