Feature: musicians dancing and jumping from stage to floor

This commit is contained in:
Dejvino 2025-11-21 21:35:10 +01:00
parent 47e11a497c
commit 4726b419f4
2 changed files with 143 additions and 15 deletions

View File

@ -67,10 +67,19 @@ function updateVideo() {
// --- Animation Loop --- // --- Animation Loop ---
let lastTime = -1;
export function animate() { export function animate() {
requestAnimationFrame(animate); requestAnimationFrame(animate);
const deltaTime = 1; let deltaTime = 0;
if (lastTime !== -1) {
const newTime = state.clock.getElapsedTime();
deltaTime = newTime - lastTime;
lastTime = newTime;
} else {
lastTime = state.clock.getElapsedTime();
}
sceneFeatureManager.update(deltaTime); sceneFeatureManager.update(deltaTime);
state.effectsManager.update(); state.effectsManager.update();
updateCamera(); updateCamera();

View File

@ -4,14 +4,6 @@ import { SceneFeature } from './SceneFeature.js';
import sceneFeatureManager from './SceneFeatureManager.js'; import sceneFeatureManager from './SceneFeatureManager.js';
import musiciansTextureUrl from '/textures/musician1.png'; import musiciansTextureUrl from '/textures/musician1.png';
export class MedievalMusicians extends SceneFeature {
constructor() {
super();
this.musicians = [];
sceneFeatureManager.register(this);
}
init() {
// --- Stage dimensions for positioning --- // --- Stage dimensions for positioning ---
const stageHeight = 1.5; const stageHeight = 1.5;
const stageDepth = 5; const stageDepth = 5;
@ -21,6 +13,14 @@ export class MedievalMusicians extends SceneFeature {
const musicianHeight = 2.5; const musicianHeight = 2.5;
const musicianWidth = 2.5; const musicianWidth = 2.5;
export class MedievalMusicians extends SceneFeature {
constructor() {
super();
this.musicians = [];
sceneFeatureManager.register(this);
}
init() {
// Load the texture and create the material inside the callback // Load the texture and create the material inside the callback
state.loader.load(musiciansTextureUrl, (texture) => { state.loader.load(musiciansTextureUrl, (texture) => {
// 1. Draw texture to canvas to process it // 1. Draw texture to canvas to process it
@ -60,7 +60,7 @@ export class MedievalMusicians extends SceneFeature {
const material = new THREE.MeshStandardMaterial({ const material = new THREE.MeshStandardMaterial({
map: processedTexture, map: processedTexture,
side: THREE.DoubleSide, side: THREE.DoubleSide,
transparent: true, alphaTest: 0.5, // Treat pixels with alpha < 0.5 as fully transparent
roughness: 0.7, roughness: 0.7,
metalness: 0.1, metalness: 0.1,
}); });
@ -77,9 +77,25 @@ export class MedievalMusicians extends SceneFeature {
musicianPositions.forEach(pos => { musicianPositions.forEach(pos => {
const musician = new THREE.Mesh(geometry, material); const musician = new THREE.Mesh(geometry, material);
musician.position.copy(pos); musician.position.copy(pos);
state.scene.add(musician); state.scene.add(musician);
this.musicians.push(musician);
// Store musician object with state for animation
this.musicians.push({
mesh: musician,
// --- State for complex movement ---
currentPlane: 'stage', // 'stage' or 'floor'
state: 'WAITING',
targetPosition: pos.clone(),
waitStartTime: 0,
waitTime: 1 + Math.random() * 2, // Wait 1-3 seconds
jumpStartPos: null,
jumpEndPos: null,
jumpProgress: 0,
// --- State for jumping in place ---
isJumping: false,
jumpStartTime: 0,
});
}); });
}); });
} }
@ -90,9 +106,112 @@ export class MedievalMusicians extends SceneFeature {
const cameraPosition = new THREE.Vector3(); const cameraPosition = new THREE.Vector3();
state.camera.getWorldPosition(cameraPosition); state.camera.getWorldPosition(cameraPosition);
this.musicians.forEach(musician => { const time = state.clock.getElapsedTime();
const moveSpeed = 2.0;
const stageArea = { x: 10, z: 4, y: stageHeight, centerZ: -length / 2 + stageDepth / 2 };
const floorArea = { x: 10, z: 4, y: 0, centerZ: -length / 2 + stageDepth + 2 };
const planeEdgeZ = -length / 2 + stageDepth;
const planeJumpChance = 0.1;
const jumpChance = 0.005;
const jumpDuration = 0.5;
const jumpHeight = 2.0;
this.musicians.forEach(musicianObj => {
const { mesh } = musicianObj;
// We only want to rotate on the Y axis to keep them upright // We only want to rotate on the Y axis to keep them upright
musician.lookAt(cameraPosition.x, musician.position.y, cameraPosition.z); mesh.lookAt(cameraPosition.x, mesh.position.y, cameraPosition.z);
// --- Main State Machine ---
const area = musicianObj.currentPlane === 'stage' ? stageArea : floorArea;
const otherArea = musicianObj.currentPlane === 'stage' ? floorArea : stageArea;
if (musicianObj.state === 'WAITING') {
if (time > musicianObj.waitStartTime + musicianObj.waitTime) {
if (Math.random() < planeJumpChance) {
// --- Decide to jump to the other plane ---
musicianObj.state = 'PREPARING_JUMP';
const targetX = (Math.random() - 0.5) * area.x;
musicianObj.targetPosition = new THREE.Vector3(targetX, mesh.position.y, planeEdgeZ);
} else {
// --- Decide to move to a new spot on the current plane ---
const newTarget = new THREE.Vector3(
(Math.random() - 0.5) * area.x,
area.y + musicianHeight/2,
area.centerZ + (Math.random() - 0.5) * area.z
);
musicianObj.targetPosition = newTarget;
musicianObj.state = 'MOVING';
}
}
} else if (musicianObj.state === 'MOVING') {
const distance = mesh.position.distanceTo(musicianObj.targetPosition);
if (distance > 0.1) {
const direction = musicianObj.targetPosition.clone().sub(mesh.position).normalize();
mesh.position.add(direction.multiplyScalar(moveSpeed * deltaTime));
} else {
musicianObj.state = 'WAITING';
musicianObj.waitStartTime = time;
musicianObj.waitTime = 1 + Math.random() * 2;
}
} else if (musicianObj.state === 'PREPARING_JUMP') {
const distance = mesh.position.distanceTo(musicianObj.targetPosition);
if (distance > 0.1) {
const direction = musicianObj.targetPosition.clone().sub(mesh.position).normalize();
mesh.position.add(direction.multiplyScalar(moveSpeed * deltaTime));
} else {
// --- Arrived at edge, start the plane jump ---
musicianObj.state = 'JUMPING_PLANE';
musicianObj.jumpStartPos = mesh.position.clone();
const targetPlane = musicianObj.currentPlane === 'stage' ? 'floor' : 'stage';
const targetArea = targetPlane === 'stage' ? stageArea : floorArea;
musicianObj.jumpEndPos = new THREE.Vector3(
mesh.position.x,
targetArea.y + musicianHeight/2,
planeEdgeZ + (targetPlane === 'stage' ? -1 : 1)
);
musicianObj.targetPosition = musicianObj.jumpEndPos.clone();
musicianObj.currentPlane = targetPlane;
musicianObj.jumpProgress = 0;
}
} else if (musicianObj.state === 'JUMPING_PLANE') {
musicianObj.jumpProgress += deltaTime / jumpDuration;
if (musicianObj.jumpProgress < 1) {
// Determine base height based on which half of the jump we're in
const baseHeight = musicianObj.jumpProgress < 0.5 ? musicianObj.jumpStartPos.y : musicianObj.jumpEndPos.y;
const arcHeight = Math.sin(musicianObj.jumpProgress * Math.PI) * jumpHeight;
// Interpolate horizontal position
const horizontalProgress = musicianObj.jumpProgress;
mesh.position.x = THREE.MathUtils.lerp(musicianObj.jumpStartPos.x, musicianObj.jumpEndPos.x, horizontalProgress);
mesh.position.z = THREE.MathUtils.lerp(musicianObj.jumpStartPos.z, musicianObj.jumpEndPos.z, horizontalProgress);
// Apply vertical arc
mesh.position.y = baseHeight + arcHeight;
} else {
// Landed
mesh.position.copy(musicianObj.jumpEndPos);
musicianObj.state = 'WAITING';
musicianObj.waitStartTime = time;
musicianObj.waitTime = 1 + Math.random() * 2;
}
}
// --- Jumping in place (can happen in any state except during a plane jump) ---
if (musicianObj.isJumping) {
const jumpProgress = (time - musicianObj.jumpStartTime) / jumpDuration;
if (jumpProgress < 1) {
const baseHeight = area.y + musicianHeight/2;
mesh.position.y = baseHeight + Math.sin(jumpProgress * Math.PI) * jumpHeight;
} else {
musicianObj.isJumping = false;
mesh.position.y = area.y + musicianHeight / 2;
}
} else {
if (Math.random() < jumpChance && musicianObj.state !== 'JUMPING_PLANE' && musicianObj.state !== 'PREPARING_JUMP') {
musicianObj.isJumping = true;
musicianObj.jumpStartTime = time;
}
}
}); });
} }
} }