From dd6696650706ffaae410b7d85ba5239e633eab55 Mon Sep 17 00:00:00 2001 From: math-pixel <59537610+math-pixel@users.noreply.github.com> Date: Tue, 19 May 2026 17:04:01 +0200 Subject: [PATCH] working move kikle --- src/components/ebike/Ebike.tsx | 31 +++++++++++++++-- src/world/player/PlayerController.tsx | 50 +++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index fce55d7..bbbca53 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -23,7 +23,7 @@ export const EBIKE_CAMERA_TRANSFORM: CameraTransform = { }; const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = { - position: [0, 1.5, 3], + position: [0, 1.5, -3], rotation: [0, 0, 0], }; @@ -47,6 +47,16 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { position[2], ]); const restingRotation = useRef(0); + const forkRef = useRef(null); + + useEffect(() => { + if (model) { + const fork = model.getObjectByName("fourche"); + if (fork) { + forkRef.current = fork; + } + } + }, [model]); useEffect(() => { (window as any).ebikeVisualGroup = groupRef; @@ -59,7 +69,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { }; }, []); - useFrame(() => { + useFrame((_, delta) => { if (groupRef.current) { if (movementMode === "ebike") { restingPosition.current = [ @@ -68,9 +78,26 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { groupRef.current.position.z, ]; restingRotation.current = groupRef.current.rotation.y; + + // Smoothly rotate the front fork ("fourche") up to 15 degrees in its own Z axis + const steerFactor = (window as any).ebikeSteerFactor || 0; + if (forkRef.current) { + // 15 degrees is 0.26 radians + const targetForkRotation = steerFactor * 0.26; + forkRef.current.rotation.z = THREE.MathUtils.lerp( + forkRef.current.rotation.z, + targetForkRotation, + 12 * delta + ); + } } else { groupRef.current.position.set(...restingPosition.current); groupRef.current.rotation.set(0, restingRotation.current, 0); + + // Reset fork rotation when parked + if (forkRef.current) { + forkRef.current.rotation.z = 0; + } } (window as any).ebikeParkedPosition = restingPosition.current; (window as any).ebikeParkedRotation = restingRotation.current; diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 2e85615..b5c25fe 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -158,6 +158,11 @@ export function PlayerController({ const rollRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[2]); camera.rotation.set(pitchRad, yawRad, rollRad, "YXZ"); } else if (movementMode === "walk" && prevMovementModeRef.current === "ebike") { + // Restore default walk FOV + const perspectiveCam = camera as THREE.PerspectiveCamera; + perspectiveCam.fov = 60; + perspectiveCam.updateProjectionMatrix(); + // Dismount! Teleport player capsule 3 units to the right const rightDir = new THREE.Vector3(); camera.getWorldDirection(_forward); @@ -358,22 +363,51 @@ export function PlayerController({ } if (movementModeRef.current === "ebike") { - // Offset of position rotated by e-bike angle + // Calculate dynamic steering factor + let targetSteer = 0; + if (keys.current.left) targetSteer = 1; + else if (keys.current.right) targetSteer = -1; + + const currentSteer = (window as any).ebikeSteerFactor || 0; + const steerFactor = THREE.MathUtils.lerp(currentSteer, targetSteer, 8 * dt); + (window as any).ebikeSteerFactor = steerFactor; + + // 1. Dynamic FOV stretch based on speed! + const speed = velocity.current.length(); + const targetFov = 60 + Math.min(speed * 0.35, 9); // stretch FOV up to 9 degrees at high speed (halved by two)! + const perspectiveCam = camera as THREE.PerspectiveCamera; + perspectiveCam.fov = THREE.MathUtils.lerp(perspectiveCam.fov, targetFov, 6 * dt); + perspectiveCam.updateProjectionMatrix(); + + // 2. Camera lag & dynamic swing trailing const cameraOffset = new THREE.Vector3(...EBIKE_CAMERA_TRANSFORM.position); cameraOffset.applyAxisAngle(_up, ebikeAngle.current); - const camPos = new THREE.Vector3() + // Swing camera to optimize the view for both left and right turns: + // Since the camera is on the left (X = -3.5), it naturally trails beautifully in right turns, + // but cuts forward in left turns. We compensate by pushing the camera backward (+Z) during left turns! + const swingX = -Math.abs(steerFactor) * 1.5; + const swingZ = steerFactor > 0 ? steerFactor * 2.5 : steerFactor * 1.0; + + const cameraSwing = new THREE.Vector3(swingX, 0, swingZ); + cameraSwing.applyAxisAngle(_up, ebikeAngle.current); + cameraOffset.add(cameraSwing); + + const targetCamPos = new THREE.Vector3() .copy(capsule.current.end) .add(cameraOffset); - camera.position.copy(camPos); - // Set camera rotation strictly to EBIKE_CAMERA_TRANSFORM.rotation + ebikeAngle.current + // Smoothly lerp camera position to eliminate rigidity + camera.position.lerp(targetCamPos, 12 * dt); + + // 3. Dynamic camera roll based on steering! 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]); + // Tilt camera slightly opposite to the turn direction (-5 degrees maximum roll) + const rollRad = THREE.MathUtils.degToRad(EBIKE_CAMERA_TRANSFORM.rotation[2]) - steerFactor * 0.08; camera.rotation.set(pitchRad, yawRad, rollRad, "YXZ"); - // Synchronize visual e-bike mesh position and rotation instantly to eliminate 1-frame follow latency jitter! + // 4. Synchronize visual e-bike position and apply leaning! const ebikeVisual = (window as any).ebikeVisualGroup?.current; if (ebikeVisual) { ebikeVisual.position.set( @@ -381,7 +415,9 @@ export function PlayerController({ capsule.current.end.y - PLAYER_EYE_HEIGHT, capsule.current.end.z ); - ebikeVisual.rotation.set(0, ebikeAngle.current, 0); + // Lean (roll) the bike sideways in turns (up to 15 degrees) + const leanAngle = steerFactor * 0.26; // rotate in direction of turn! + ebikeVisual.rotation.set(0, ebikeAngle.current, leanAngle, "YXZ"); } } else { camera.position.copy(capsule.current.end);