From bce7d11b660474670f6a165a64b5c238719fe811 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 1 Jun 2026 10:52:17 +0200 Subject: [PATCH] fix(ebike): snap parked model to terrain --- src/components/ebike/Ebike.tsx | 50 ++++++++++++++++++++++---------- src/data/ebike/ebikeConfig.ts | 2 +- src/data/player/playerConfig.ts | 6 ++-- src/data/world/laFabrikConfig.ts | 4 +-- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index be6488c..d5a32ac 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -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(() => { + 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( - 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([ - position[0], - position[1], - position[2], + parkedPosition[0], + parkedPosition[1], + parkedPosition[2], ]); const restingRotationRef = useRef(EBIKE_WORLD_ROTATION_Y); const forkRef = useRef(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([position[0], position[1], position[2]]); + useState([ + 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 ? ( @@ -301,7 +321,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { diff --git a/src/data/ebike/ebikeConfig.ts b/src/data/ebike/ebikeConfig.ts index dbefe5e..9c235e1 100644 --- a/src/data/ebike/ebikeConfig.ts +++ b/src/data/ebike/ebikeConfig.ts @@ -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; diff --git a/src/data/player/playerConfig.ts b/src/data/player/playerConfig.ts index b9a99ce..e573afd 100644 --- a/src/data/player/playerConfig.ts +++ b/src/data/player/playerConfig.ts @@ -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]; diff --git a/src/data/world/laFabrikConfig.ts b/src/data/world/laFabrikConfig.ts index 795eb08..356116d 100644 --- a/src/data/world/laFabrikConfig.ts +++ b/src/data/world/laFabrikConfig.ts @@ -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(