Merge e_bike + gps into develop #7

Merged
math-pixel merged 23 commits from feat/gps into develop 2026-05-28 05:55:19 +00:00
3 changed files with 91 additions and 24 deletions
Showing only changes of commit ce0eb90321 - Show all commits
+6 -12
View File
@@ -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 {
+59
View File
@@ -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,
);
}
+26 -12
View File
@@ -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,18 +271,23 @@ 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;
} }
} }
camera.getWorldDirection(_forward); if (movementModeRef.current === "ebike") {
_forward.setY(0); _forward.set(Math.sin(ebikeAngle.current), 0, Math.cos(ebikeAngle.current)).normalize();
if (_forward.lengthSq() > 0) {
_forward.normalize();
_right.crossVectors(_forward, _up).normalize(); _right.crossVectors(_forward, _up).normalize();
} else {
camera.getWorldDirection(_forward);
_forward.setY(0);
if (_forward.lengthSq() > 0) {
_forward.normalize();
_right.crossVectors(_forward, _up).normalize();
}
} }
_wishDir.set(0, 0, 0); _wishDir.set(0, 0, 0);
@@ -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;