Ai_Assistant/client/_archive/movementManager.js

230 lines
6.5 KiB
JavaScript
Raw Permalink Normal View History

2026-05-24 13:31:30 +02:00
// movementManager.js
import * as THREE from 'three';
export class MovementManager {
constructor(vrm, animationMgr, scene) {
this.vrm = vrm;
this.animationMgr = animationMgr;
this.scene = scene;
// Movement state
this.isMoving = false;
this.targetPosition = null;
this.moveSpeed = 1.5; // units per second
this.rotationSpeed = 5; // radians per second
this.stopDistance = 0.1; // how close to get before stopping
// Animation states
this.idleAction = null;
this.walkAction = null;
this.runAction = null;
this.currentAction = null;
// Movement mixer (separate from VRMA mixer)
this.movementMixer = new THREE.AnimationMixer(vrm.scene);
// Debug helper (optional)
this.targetMarker = null;
this.showDebugMarker = false;
}
// Load locomotion animations (idle, walk, run)
async loadLocomotionAnimations(idleUrl, walkUrl, runUrl = null) {
const loader = new THREE.GLTFLoader();
try {
// Load idle animation
if (idleUrl) {
const idleGltf = await loader.loadAsync(idleUrl);
const idleClip = idleGltf.animations[0];
this.idleAction = this.movementMixer.clipAction(idleClip);
this.idleAction.setLoop(THREE.LoopRepeat);
}
// Load walk animation
if (walkUrl) {
const walkGltf = await loader.loadAsync(walkUrl);
const walkClip = walkGltf.animations[0];
this.walkAction = this.movementMixer.clipAction(walkClip);
this.walkAction.setLoop(THREE.LoopRepeat);
}
// Load run animation (optional)
if (runUrl) {
const runGltf = await loader.loadAsync(runUrl);
const runClip = runGltf.animations[0];
this.runAction = this.movementMixer.clipAction(runClip);
this.runAction.setLoop(THREE.LoopRepeat);
}
// Start with idle
if (this.idleAction) {
this.currentAction = this.idleAction;
this.idleAction.play();
}
console.log("✅ Locomotion animations loaded");
} catch (error) {
console.error("Failed to load locomotion animations:", error);
}
}
// Transition between animations with crossfade
crossFadeTo(targetAction, duration = 0.3) {
if (!targetAction || targetAction === this.currentAction) return;
targetAction.reset();
targetAction.setEffectiveTimeScale(1);
targetAction.setEffectiveWeight(1);
targetAction.play();
if (this.currentAction) {
this.currentAction.crossFadeTo(targetAction, duration, false);
}
this.currentAction = targetAction;
}
// Move to a specific position
moveTo(x, y, z, speed = null) {
this.targetPosition = new THREE.Vector3(x, y, z);
this.isMoving = true;
if (speed) {
this.moveSpeed = speed;
}
// Switch to walk animation
if (this.walkAction) {
this.crossFadeTo(this.walkAction, 0.2);
}
// Show debug marker
if (this.showDebugMarker) {
this.createTargetMarker(this.targetPosition);
}
console.log(`🚶 Moving to position: (${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)})`);
}
// Stop movement
stop() {
this.isMoving = false;
this.targetPosition = null;
// Switch back to idle
if (this.idleAction) {
this.crossFadeTo(this.idleAction, 0.3);
}
// Remove debug marker
if (this.targetMarker) {
this.scene.remove(this.targetMarker);
this.targetMarker = null;
}
}
// Update loop (call this in your main animation loop)
update(deltaTime) {
// Update movement mixer
if (this.movementMixer) {
this.movementMixer.update(deltaTime);
}
// Handle movement
if (this.isMoving && this.targetPosition) {
const currentPos = this.vrm.scene.position;
const direction = new THREE.Vector3()
.subVectors(this.targetPosition, currentPos);
const distance = direction.length();
// Check if we've arrived
if (distance < this.stopDistance) {
this.stop();
console.log("✅ Arrived at destination");
return;
}
// Normalize direction
direction.normalize();
// Move towards target
const moveDistance = this.moveSpeed * deltaTime;
const actualMove = Math.min(moveDistance, distance);
currentPos.x += direction.x * actualMove;
currentPos.z += direction.z * actualMove;
// Keep Y position (don't move vertically unless specified)
if (this.targetPosition.y !== currentPos.y) {
currentPos.y = this.targetPosition.y;
}
// Rotate to face direction of movement
const targetRotation = Math.atan2(direction.x, direction.z);
const currentRotation = this.vrm.scene.rotation.y;
// Smooth rotation interpolation
let rotationDiff = targetRotation - currentRotation;
// Normalize rotation difference to [-PI, PI]
while (rotationDiff > Math.PI) rotationDiff -= Math.PI * 2;
while (rotationDiff < -Math.PI) rotationDiff += Math.PI * 2;
const maxRotation = this.rotationSpeed * deltaTime;
const actualRotation = Math.max(-maxRotation, Math.min(maxRotation, rotationDiff));
this.vrm.scene.rotation.y += actualRotation;
}
}
// Create a visual marker at target position (for debugging)
createTargetMarker(position) {
// Remove old marker
if (this.targetMarker) {
this.scene.remove(this.targetMarker);
}
// Create new marker
const geometry = new THREE.SphereGeometry(0.1, 16, 16);
const material = new THREE.MeshBasicMaterial({
color: 0xff0000,
transparent: true,
opacity: 0.7
});
this.targetMarker = new THREE.Mesh(geometry, material);
this.targetMarker.position.copy(position);
this.scene.add(this.targetMarker);
}
// Enable/disable debug marker
setDebugMarker(enabled) {
this.showDebugMarker = enabled;
if (!enabled && this.targetMarker) {
this.scene.remove(this.targetMarker);
this.targetMarker = null;
}
}
// Set movement speed
setSpeed(speed) {
this.moveSpeed = speed;
// Optionally switch to run animation for higher speeds
if (this.runAction && speed > 2.5 && this.isMoving) {
this.crossFadeTo(this.runAction, 0.2);
} else if (this.walkAction && speed <= 2.5 && this.isMoving) {
this.crossFadeTo(this.walkAction, 0.2);
}
}
// Get current position
getPosition() {
return this.vrm.scene.position.clone();
}
// Check if currently moving
isCurrentlyMoving() {
return this.isMoving;
}
}