Merge e_bike + gps into develop #7
@@ -5,26 +5,26 @@ import { InteractableObject } from "@/components/three/interaction/InteractableO
|
|||||||
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
|
||||||
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
import { useClonedObject } from "@/hooks/three/useClonedObject";
|
||||||
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
|
||||||
import { animateCameraTransition } from "@/world/GameCinematics";
|
import { animateCameraTransition, animateCameraTransformTransition } from "@/world/GameCinematics";
|
||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig";
|
import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
|
||||||
const EBIKE_MODEL_PATH = "/models/ebike/model.gltf";
|
const EBIKE_MODEL_PATH = "/models/ebike/model.gltf";
|
||||||
|
|
||||||
interface CameraTransform {
|
export interface CameraTransform {
|
||||||
position: Vector3Tuple;
|
position: Vector3Tuple;
|
||||||
rotation: Vector3Tuple;
|
rotation: Vector3Tuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EBIKE_CAMERA_TRANSFORM: CameraTransform = {
|
export const EBIKE_CAMERA_TRANSFORM: CameraTransform = {
|
||||||
position: [-3, 8, 0],
|
position: [-3, 8, 0],
|
||||||
rotation: [90, 90, 90],
|
rotation: [0, 0, 0],
|
||||||
};
|
};
|
||||||
|
|
||||||
const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = {
|
const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = {
|
||||||
position: [3, 1.5, 0],
|
position: [3, 1.5, 0],
|
||||||
rotation: [0, 0, 0],
|
rotation: [90, 90, 0],
|
||||||
};
|
};
|
||||||
|
|
||||||
interface EbikeProps {
|
interface EbikeProps {
|
||||||
@@ -86,13 +86,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
|||||||
position[1] + EBIKE_CAMERA_TRANSFORM.position[1],
|
position[1] + EBIKE_CAMERA_TRANSFORM.position[1],
|
||||||
position[2] + EBIKE_CAMERA_TRANSFORM.position[2],
|
position[2] + EBIKE_CAMERA_TRANSFORM.position[2],
|
||||||
];
|
];
|
||||||
const targetLookAt: Vector3Tuple = [
|
animateCameraTransformTransition(targetCamPos, EBIKE_CAMERA_TRANSFORM.rotation, 1, () => {
|
||||||
position[0],
|
|
||||||
position[1] + 1,
|
|
||||||
position[2],
|
|
||||||
];
|
|
||||||
|
|
||||||
animateCameraTransition(targetCamPos, targetLookAt, 1, () => {
|
|
||||||
useGameStore.getState().setPlayerMovementMode("ebike");
|
useGameStore.getState().setPlayerMovementMode("ebike");
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -233,3 +233,62 @@ export function animateCameraTransition(
|
|||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function animateCameraTransformTransition(
|
||||||
|
targetPosition: Vector3Tuple,
|
||||||
|
targetRotation: Vector3Tuple,
|
||||||
|
duration: number = 1,
|
||||||
|
onComplete?: () => void,
|
||||||
|
): void {
|
||||||
|
if (!globalCamera) {
|
||||||
|
logger.warn("GameCinematics", "Camera not found for transition");
|
||||||
|
onComplete?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const camera = globalCamera;
|
||||||
|
|
||||||
|
cameraTransitionTimeline?.kill();
|
||||||
|
useGameStore.getState().setCinematicPlaying(true);
|
||||||
|
|
||||||
|
// Convert target rotation in degrees to quaternion
|
||||||
|
const targetEuler = new THREE.Euler(
|
||||||
|
THREE.MathUtils.degToRad(targetRotation[0]),
|
||||||
|
THREE.MathUtils.degToRad(targetRotation[1]),
|
||||||
|
THREE.MathUtils.degToRad(targetRotation[2]),
|
||||||
|
"YXZ"
|
||||||
|
);
|
||||||
|
const startQuaternion = camera.quaternion.clone();
|
||||||
|
const endQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
|
||||||
|
|
||||||
|
const transitionObj = { progress: 0 };
|
||||||
|
|
||||||
|
cameraTransitionTimeline = gsap.timeline({
|
||||||
|
onUpdate: () => {
|
||||||
|
camera.quaternion.copy(startQuaternion).slerp(endQuaternion, transitionObj.progress);
|
||||||
|
},
|
||||||
|
onComplete: () => {
|
||||||
|
cameraTransitionTimeline = null;
|
||||||
|
useGameStore.getState().setCinematicPlaying(false);
|
||||||
|
onComplete?.();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cameraTransitionTimeline.to(camera.position, {
|
||||||
|
x: targetPosition[0],
|
||||||
|
y: targetPosition[1],
|
||||||
|
z: targetPosition[2],
|
||||||
|
duration,
|
||||||
|
ease: "power2.inOut",
|
||||||
|
});
|
||||||
|
|
||||||
|
cameraTransitionTimeline.to(
|
||||||
|
transitionObj,
|
||||||
|
{
|
||||||
|
progress: 1,
|
||||||
|
duration,
|
||||||
|
ease: "power2.inOut",
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { InteractionManager } from "@/managers/InteractionManager";
|
|||||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||||
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
import { useSettingsStore } from "@/managers/stores/useSettingsStore";
|
||||||
import type { Vector3Tuple } from "@/types/three/three";
|
import type { Vector3Tuple } from "@/types/three/three";
|
||||||
|
import { EBIKE_CAMERA_TRANSFORM } from "@/components/ebike/Ebike";
|
||||||
|
|
||||||
type Keys = {
|
type Keys = {
|
||||||
forward: boolean;
|
forward: boolean;
|
||||||
@@ -111,6 +112,7 @@ export function PlayerController({
|
|||||||
const movementMode = useGameStore((state) => state.player.movementMode);
|
const movementMode = useGameStore((state) => state.player.movementMode);
|
||||||
const movementModeRef = useRef(movementMode);
|
const movementModeRef = useRef(movementMode);
|
||||||
const prevMovementModeRef = useRef(movementMode);
|
const prevMovementModeRef = useRef(movementMode);
|
||||||
|
const ebikeAngle = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
movementModeRef.current = movementMode;
|
movementModeRef.current = movementMode;
|
||||||
@@ -130,6 +132,9 @@ export function PlayerController({
|
|||||||
onFloor.current = false;
|
onFloor.current = false;
|
||||||
wantsJump.current = false;
|
wantsJump.current = false;
|
||||||
|
|
||||||
|
// Initialize ebikeAngle to the bike's visual orientation (0 by default)
|
||||||
|
ebikeAngle.current = 0;
|
||||||
|
|
||||||
// Position the camera exactly at the EBIKE_CAMERA_TRANSFORM offset [-3, 8, 0]
|
// Position the camera exactly at the EBIKE_CAMERA_TRANSFORM offset [-3, 8, 0]
|
||||||
const cameraOffset = new THREE.Vector3(-3, 8, 0);
|
const cameraOffset = new THREE.Vector3(-3, 8, 0);
|
||||||
const camPos = new THREE.Vector3()
|
const camPos = new THREE.Vector3()
|
||||||
@@ -266,19 +271,24 @@ export function PlayerController({
|
|||||||
if (movementModeRef.current === "ebike") {
|
if (movementModeRef.current === "ebike") {
|
||||||
const turnSpeed = 1.8; // radians per second
|
const turnSpeed = 1.8; // radians per second
|
||||||
if (keys.current.left) {
|
if (keys.current.left) {
|
||||||
camera.rotateOnWorldAxis(_up, turnSpeed * dt);
|
ebikeAngle.current += turnSpeed * dt;
|
||||||
}
|
}
|
||||||
if (keys.current.right) {
|
if (keys.current.right) {
|
||||||
camera.rotateOnWorldAxis(_up, -turnSpeed * dt);
|
ebikeAngle.current -= turnSpeed * dt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (movementModeRef.current === "ebike") {
|
||||||
|
_forward.set(Math.sin(ebikeAngle.current), 0, Math.cos(ebikeAngle.current)).normalize();
|
||||||
|
_right.crossVectors(_forward, _up).normalize();
|
||||||
|
} else {
|
||||||
camera.getWorldDirection(_forward);
|
camera.getWorldDirection(_forward);
|
||||||
_forward.setY(0);
|
_forward.setY(0);
|
||||||
if (_forward.lengthSq() > 0) {
|
if (_forward.lengthSq() > 0) {
|
||||||
_forward.normalize();
|
_forward.normalize();
|
||||||
_right.crossVectors(_forward, _up).normalize();
|
_right.crossVectors(_forward, _up).normalize();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_wishDir.set(0, 0, 0);
|
_wishDir.set(0, 0, 0);
|
||||||
if (!movementLocked) {
|
if (!movementLocked) {
|
||||||
@@ -337,24 +347,28 @@ export function PlayerController({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const euler = new THREE.Euler().setFromQuaternion(camera.quaternion, "YXZ");
|
|
||||||
|
|
||||||
if (movementModeRef.current === "ebike") {
|
if (movementModeRef.current === "ebike") {
|
||||||
// Offset of [-3, 8, 0] rotated by the camera's actual yaw (euler.y)
|
// Offset of position rotated by e-bike angle
|
||||||
const cameraOffset = new THREE.Vector3(-3, 8, 0);
|
const cameraOffset = new THREE.Vector3(...EBIKE_CAMERA_TRANSFORM.position);
|
||||||
cameraOffset.applyAxisAngle(_up, euler.y);
|
cameraOffset.applyAxisAngle(_up, ebikeAngle.current);
|
||||||
|
|
||||||
const camPos = new THREE.Vector3()
|
const camPos = new THREE.Vector3()
|
||||||
.copy(capsule.current.end)
|
.copy(capsule.current.end)
|
||||||
.add(cameraOffset);
|
.add(cameraOffset);
|
||||||
camera.position.copy(camPos);
|
camera.position.copy(camPos);
|
||||||
|
|
||||||
|
// Set camera rotation strictly to EBIKE_CAMERA_TRANSFORM.rotation + ebikeAngle.current
|
||||||
|
const pitchRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[0]);
|
||||||
|
const yawRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[1]) + ebikeAngle.current;
|
||||||
|
const rollRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[2]);
|
||||||
|
camera.rotation.set(pitchRad, yawRad, rollRad, "YXZ");
|
||||||
} else {
|
} else {
|
||||||
camera.position.copy(capsule.current.end);
|
camera.position.copy(capsule.current.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save player capsule end position and camera yaw globally so other components (like Ebike) can access it
|
// Save player capsule end position and camera yaw globally so other components (like Ebike) can access it
|
||||||
(window as any).playerPos = [capsule.current.end.x, capsule.current.end.y, capsule.current.end.z];
|
(window as any).playerPos = [capsule.current.end.x, capsule.current.end.y, capsule.current.end.z];
|
||||||
(window as any).ebikeAngle = euler.y;
|
(window as any).ebikeAngle = ebikeAngle.current;
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user