From aa6217167c788cbede8db14bab1c64fa1153d914 Mon Sep 17 00:00:00 2001 From: KaseToatz1337 <112391293+Raeven69@users.noreply.github.com> Date: Fri, 19 Dec 2025 16:45:33 +0100 Subject: [PATCH] Refactor terrain collision to use terrainRef group Updated Car and Character components to use a shared terrainRef group for ground and ramp collision detection, improving accuracy and maintainability. The ground and ramps are now grouped in App and passed as a ref to both Car and Character, ensuring consistent collision logic. --- src/App.tsx | 27 ++++++++++++++---------- src/Car.tsx | 53 ++++++++++++++++++++++++----------------------- src/Character.tsx | 9 +++++--- 3 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f1c2d01..f2cb676 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,7 @@ import { Sky, Grid } from '@react-three/drei' import { Character } from './Character' import { Car } from './Car' import { WorldBorder } from './WorldBorder' -import { Mesh } from 'three' +import { Mesh, Group } from 'three' import { CheatMenu } from './CheatMenu' import { Ramp } from './Ramp' @@ -15,6 +15,7 @@ export default function App() { const [showHitboxes, setShowHitboxes] = useState(false) const [ramps, setRamps] = useState<{ id: number; position: [number, number, number]; rotation: [number, number, number] }[]>([]) const carRef = useRef(null) + const terrainRef = useRef(null) const spawnRamp = () => { if (carRef.current) { @@ -33,7 +34,7 @@ export default function App() { setRamps(prev => [...prev, { id: Date.now(), - position: [spawnPos.x, 0.5, spawnPos.z], // Ensure it's on ground + position: [spawnPos.x, 0, spawnPos.z], // Ensure it's on ground (y=0 for center of 1-unit high box on -0.5 ground? No, ground is -0.5. Box height 1. Center at 0 puts bottom at -0.5. Perfect.) rotation: spawnRot }]) } @@ -46,11 +47,17 @@ export default function App() { - {/* Ground */} - - - - + + {/* Ground */} + + + + + + {ramps.map(ramp => ( + + ))} + - {ramps.map(ramp => ( - - ))} - setIsDriving(true)} carRef={carRef} showHitboxes={showHitboxes} + terrainRef={terrainRef} /> setIsDriving(false)} showHitboxes={showHitboxes} + terrainRef={terrainRef} /> void position?: [number, number, number] showHitboxes?: boolean + terrainRef: React.RefObject } -export const Car = forwardRef(({ isDriving, onExit, position = [5, 0.5, 5], showHitboxes = false }, ref) => { +export const Car = forwardRef(({ isDriving, onExit, position = [5, 0, 5], showHitboxes = false, terrainRef }, ref) => { const internalRef = useRef(null) useImperativeHandle(ref, () => internalRef.current!) @@ -20,6 +20,7 @@ export const Car = forwardRef(({ isDriving, onExit, position = [ const { camera } = useThree() const keys = useRef({ w: false, a: false, s: false, d: false, f: false }) const speed = useRef(0) + const verticalVelocity = useRef(0) // Camera state for car const cameraState = useRef({ @@ -129,22 +130,12 @@ export const Car = forwardRef(({ isDriving, onExit, position = [ const absSin = Math.abs(Math.sin(yaw)) const absCos = Math.abs(Math.cos(yaw)) - const extentX = absSin * halfLength + absCos * halfWidth - const extentZ = absCos * halfLength + absSin * halfWidth - - const boundaryX = halfMapSize - extentX - const boundaryZ = halfMapSize - extentZ - - internalRef.current.position.x = Math.max(-boundaryX, Math.min(boundaryX, internalRef.current.position.x)) - internalRef.current.position.z = Math.max(-boundaryZ, Math.min(boundaryZ, internalRef.current.position.z)) - // Ground Collision & Pitch - // Cast rays from front and back to determine pitch and height const carPos = internalRef.current.position const carRot = internalRef.current.rotation const forwardDir = new Vector3(0, 0, 1).applyEuler(carRot) - const frontOffset = forwardDir.clone().multiplyScalar(2.5) // Half length approx + const frontOffset = forwardDir.clone().multiplyScalar(2.5) const backOffset = forwardDir.clone().multiplyScalar(-2.5) const rayOriginFront = carPos.clone().add(frontOffset).add(new Vector3(0, 5, 0)) @@ -154,29 +145,39 @@ export const Car = forwardRef(({ isDriving, onExit, position = [ const raycasterFront = new THREE.Raycaster(rayOriginFront, rayDir, 0, 10) const raycasterBack = new THREE.Raycaster(rayOriginBack, rayDir, 0, 10) - const intersectsFront = raycasterFront.intersectObjects(_state.scene.children, true) - const intersectsBack = raycasterBack.intersectObjects(_state.scene.children, true) + const objectsToTest = terrainRef?.current ? terrainRef.current.children : [] + + const intersectsFront = raycasterFront.intersectObjects(objectsToTest, true) + const intersectsBack = raycasterBack.intersectObjects(objectsToTest, true) const groundHitFront = intersectsFront.find(hit => hit.object.userData.isGround) const groundHitBack = intersectsBack.find(hit => hit.object.userData.isGround) - let frontY = 0.5 - let backY = 0.5 + let frontY = -100 + let backY = -100 - if (groundHitFront) frontY = groundHitFront.point.y + 0.5 // Car half height - if (groundHitBack) backY = groundHitBack.point.y + 0.5 + if (groundHitFront) frontY = groundHitFront.point.y - 1.0 + if (groundHitBack) backY = groundHitBack.point.y - 1.0 - // Average height - const targetY = (frontY + backY) / 2 + let targetY = -100 + if (groundHitFront && groundHitBack) { + targetY = (frontY + backY) / 2 + } else if (groundHitFront) { + targetY = frontY + } else if (groundHitBack) { + targetY = backY + } - // Apply gravity if in air (simple approach: just lerp to targetY) - // For ramps, we want to snap to it - internalRef.current.position.y = THREE.MathUtils.lerp(internalRef.current.position.y, targetY, 0.2) + if (targetY > -90) { + if (internalRef.current.position.y <= targetY) { + internalRef.current.position.y = targetY + verticalVelocity.current = 0 + } + } // Calculate pitch - // atan2(deltaY, length) const deltaY = frontY - backY - const dist = 5 // Distance between ray points + const dist = 5 const targetPitch = Math.atan2(deltaY, dist) // Smoothly interpolate pitch diff --git a/src/Character.tsx b/src/Character.tsx index d58f3f7..c2644dc 100644 --- a/src/Character.tsx +++ b/src/Character.tsx @@ -9,9 +9,10 @@ interface CharacterProps { onEnter: () => void carRef: React.RefObject showHitboxes?: boolean + terrainRef: React.RefObject } -export function Character({ isDriving, onEnter, carRef, showHitboxes = false }: CharacterProps) { +export function Character({ isDriving, onEnter, carRef, showHitboxes = false, terrainRef }: CharacterProps) { const ref = useRef(null) const gltf = useGLTF('/character.glb') const { camera } = useThree() @@ -182,8 +183,10 @@ export function Character({ isDriving, onEnter, carRef, showHitboxes = false }: const rayDir = new Vector3(0, -1, 0) const raycaster = new THREE.Raycaster(rayOrigin, rayDir, 0, 10) - // Filter objects with userData.isGround - const intersects = raycaster.intersectObjects(_state.scene.children, true) + // Use terrainRef for intersection if available + const objectsToTest = terrainRef?.current ? terrainRef.current.children : [] + + const intersects = raycaster.intersectObjects(objectsToTest, true) const groundHit = intersects.find(hit => hit.object.userData.isGround) let groundY = 0.5 // Default ground