fix(ebike): snap parked model to terrain

This commit is contained in:
Tom Boullay
2026-06-01 10:52:17 +02:00
parent 8aa755da7a
commit bce7d11b66
4 changed files with 41 additions and 21 deletions
+35 -15
View File
@@ -8,6 +8,10 @@ import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF";
import { useClonedObject } from "@/hooks/three/useClonedObject";
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
import { useEbikeSounds } from "@/hooks/ebike/useEbikeSounds";
import {
getObjectBottomOffset,
useTerrainHeightSampler,
} from "@/hooks/three/useTerrainHeight";
import { animateCameraTransformTransition } from "@/world/GameCinematics";
import { useGameStore } from "@/managers/stores/useGameStore";
import {
@@ -32,6 +36,18 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
position: position,
});
const model = useClonedObject(scene);
const terrainHeight = useTerrainHeightSampler();
const parkedPosition = useMemo<Vector3Tuple>(() => {
const [x, y, z] = position;
const height = terrainHeight.getHeight(x, z) ?? y;
const bottomOffset = getObjectBottomOffset(model, [
EBIKE_WORLD_SCALE,
EBIKE_WORLD_SCALE,
EBIKE_WORLD_SCALE,
]);
return [x, height + bottomOffset, z];
}, [model, position, terrainHeight]);
const movementMode = useGameStore((state) => state.player.movementMode);
const mainState = useGameStore((state) => state.mainState);
const ebikeStep = useGameStore((state) => state.ebike.currentStep);
@@ -64,19 +80,19 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
y: number;
z: number;
}>({
x: position[0],
y: position[1],
z: position[2],
x: parkedPosition[0],
y: parkedPosition[1],
z: parkedPosition[2],
});
const lastGpsUpdatePos = useRef<THREE.Vector3>(
new THREE.Vector3(...position),
new THREE.Vector3(...parkedPosition),
);
// Use ref for internal state, and state for debug visualization (to avoid ref access during render)
const restingPositionRef = useRef<Vector3Tuple>([
position[0],
position[1],
position[2],
parkedPosition[0],
parkedPosition[1],
parkedPosition[2],
]);
const restingRotationRef = useRef<number>(EBIKE_WORLD_ROTATION_Y);
const forkRef = useRef<THREE.Object3D | null>(null);
@@ -84,23 +100,27 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
// State for debug visualization (synced from refs during useFrame)
const [showCameraPoints, setShowCameraPoints] = useState(true);
const [debugRestingPosition, setDebugRestingPosition] =
useState<Vector3Tuple>([position[0], position[1], position[2]]);
useState<Vector3Tuple>([
parkedPosition[0],
parkedPosition[1],
parkedPosition[2],
]);
useEffect(() => {
if (movementMode === "ebike") return;
restingPositionRef.current = position;
restingPositionRef.current = parkedPosition;
restingRotationRef.current = EBIKE_WORLD_ROTATION_Y;
lastGpsUpdatePos.current.set(...position);
lastGpsUpdatePos.current.set(...parkedPosition);
if (groupRef.current) {
groupRef.current.position.set(...position);
groupRef.current.position.set(...parkedPosition);
groupRef.current.rotation.set(0, EBIKE_WORLD_ROTATION_Y, 0);
}
window.ebikeParkedPosition = position;
window.ebikeParkedPosition = parkedPosition;
window.ebikeParkedRotation = EBIKE_WORLD_ROTATION_Y;
}, [movementMode, position]);
}, [movementMode, parkedPosition]);
useEffect(() => {
if (model) {
@@ -293,7 +313,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
{!repairGameOwnsEbikeModel ? (
<group
ref={groupRef}
position={position}
position={parkedPosition}
rotation={[0, EBIKE_WORLD_ROTATION_Y, 0]}
scale={EBIKE_WORLD_SCALE}
>
@@ -301,7 +321,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
<InteractableObject
kind="trigger"
label={interactionLabel}
position={position}
position={parkedPosition}
radius={5}
onPress={handleInteract}
>
+1 -1
View File
@@ -15,7 +15,7 @@ export const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = {
rotation: [0, 0, 0],
};
export const EBIKE_WORLD_POSITION: Vector3Tuple = [57.9, 6.3, 58.35];
export const EBIKE_WORLD_POSITION: Vector3Tuple = [65, 0.8, 72];
export const EBIKE_WORLD_ROTATION_Y = -2.5;
export const EBIKE_WORLD_SCALE = 0.35;
+3 -3
View File
@@ -5,7 +5,7 @@ export const PLAYER_EYE_HEIGHT = 1.75;
export const PLAYER_CAPSULE_RADIUS = 0.35;
export const PLAYER_WALK_SPEED = 5;
export const PLAYER_EBIKE_SPEED = 20;
export const PLAYER_EBIKE_SPEED = 30;
export const PLAYER_AIR_CONTROL_FACTOR = 0.35;
export const PLAYER_JUMP_SPEED = 9;
export const PLAYER_GRAVITY = 30;
@@ -16,8 +16,8 @@ export const PLAYER_FALL_RESPAWN_Y = -20;
export const PLAYER_FALL_RESPAWN_DELAY = 3;
export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [
LA_FABRIK_PLAYER_SPAWN[0] + 5,
LA_FABRIK_PLAYER_SPAWN[0] + 1,
LA_FABRIK_PLAYER_SPAWN[1],
LA_FABRIK_PLAYER_SPAWN[2] + 5,
LA_FABRIK_PLAYER_SPAWN[2] - 1,
];
export const PLAYER_SPAWN_POSITION_PHYSICS: Vector3Tuple = [0, 3, 0];
+2 -2
View File
@@ -6,8 +6,8 @@ export const LA_FABRIK_HALF_EXTENTS = {
x: 8.5,
z: 7.5,
} as const;
export const LA_FABRIK_PLAYER_SPAWN: Vector3Tuple = [59.5, 7.8, 64.64];
export const LA_FABRIK_INITIAL_LOOK_AT: Vector3Tuple = [58, 7.8, 62.5];
export const LA_FABRIK_PLAYER_SPAWN: Vector3Tuple = [59.5, 6.3, 64.64];
export const LA_FABRIK_INITIAL_LOOK_AT: Vector3Tuple = [58, 7.3, 62.5];
export const LA_FABRIK_INTERIOR_LIGHT_POSITION: Vector3Tuple = [59.5, 9, 64.64];
export function isInsideLaFabrikFootprint(