Feature: musicians dancing and jumping from stage to floor
This commit is contained in:
parent
47e11a497c
commit
4726b419f4
@ -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();
|
||||||
|
|||||||
@ -4,6 +4,15 @@ 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';
|
||||||
|
|
||||||
|
// --- Stage dimensions for positioning ---
|
||||||
|
const stageHeight = 1.5;
|
||||||
|
const stageDepth = 5;
|
||||||
|
const length = 40;
|
||||||
|
|
||||||
|
// --- Billboard Properties ---
|
||||||
|
const musicianHeight = 2.5;
|
||||||
|
const musicianWidth = 2.5;
|
||||||
|
|
||||||
export class MedievalMusicians extends SceneFeature {
|
export class MedievalMusicians extends SceneFeature {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@ -12,15 +21,6 @@ export class MedievalMusicians extends SceneFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// --- Stage dimensions for positioning ---
|
|
||||||
const stageHeight = 1.5;
|
|
||||||
const stageDepth = 5;
|
|
||||||
const length = 40;
|
|
||||||
|
|
||||||
// --- Billboard Properties ---
|
|
||||||
const musicianHeight = 2.5;
|
|
||||||
const musicianWidth = 2.5;
|
|
||||||
|
|
||||||
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user