From 07ff0c6fc06910512d874ababf40b202948c0783 Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 14 Jun 2024 00:35:22 +0200 Subject: [PATCH 1/4] implement basic bounding boxes with some slopes --- app/components/FirstPersonControls.tsx | 99 ++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index 4a62ee9..a8d6328 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -3,7 +3,7 @@ import { useThree, useFrame } from '@react-three/fiber'; import * as THREE from 'three'; export const FirstPersonControls = (speed) => { - const { camera } = useThree(); + const { camera, scene } = useThree(); const moveForward = useRef(false); const moveBackward = useRef(false); const moveLeft = useRef(false); @@ -15,6 +15,16 @@ export const FirstPersonControls = (speed) => { const left = new THREE.Vector3(); const forward = new THREE.Vector3(); + // Desired height above the ground + const cameraHeight = 1.8; + + // temporary location for rooms, TODO: move this outside of the controller + const rooms = [ + { minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, slope: { angle: Math.PI / 6, position: { x: 0, y: 0, z: 0 } } }, + { minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, slope: { angle: 0, position: { x: 0, y: 0, z: 0 } } }, + { minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, slope: { angle: Math.PI / 4, position: { x: 110, y: 0, z: 0 } } }, + ]; + useEffect(() => { const onKeyDown = (event) => { switch (event.code) { @@ -79,15 +89,50 @@ export const FirstPersonControls = (speed) => { window.addEventListener('keydown', onKeyDown); window.addEventListener('keyup', onKeyUp); + // Add transparent boxes to visualize rooms and slopes + rooms.forEach(room => { + const roomGeometry = new THREE.BoxGeometry( + room.maxX - room.minX, + room.maxY - room.minY, + room.maxZ - room.minZ + ); + const roomMaterial = new THREE.MeshBasicMaterial({ + color: 0x00ff00, + transparent: true, + opacity: 0.25, + wireframe: true + }); + const roomBox = new THREE.Mesh(roomGeometry, roomMaterial); + roomBox.position.set( + (room.minX + room.maxX) / 2, + (room.minY + room.maxY) / 2, + (room.minZ + room.maxZ) / 2 + ); + scene.add(roomBox); + + // Add slope to the scene + const slopeGeometry = new THREE.PlaneGeometry(100, 100); + const slopeMaterial = new THREE.MeshBasicMaterial({ + color: 0xcccccc, + side: THREE.DoubleSide, + wireframe: true + }); + const slope = new THREE.Mesh(slopeGeometry, slopeMaterial); + slope.rotation.x = -room.slope.angle; + slope.position.set(room.slope.position.x, room.slope.position.y, room.slope.position.z); + slope.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; // Naming slopes to easily find them later + scene.add(slope); + }); + return () => { window.removeEventListener('keydown', onKeyDown); window.removeEventListener('keyup', onKeyUp); }; - }, []); + }, [rooms, scene]); useFrame((_, delta) => { - // change this if you want to move faster / slower const movementSpeed = speed.speed ?? 300; + // Get the camera's forward and left direction camera.getWorldDirection(forward); forward.y = 0; @@ -109,10 +154,54 @@ export const FirstPersonControls = (speed) => { velocity.addScaledVector(direction, movementSpeed * delta); } - camera.position.addScaledVector(velocity, delta); + const newPosition = camera.position.clone().addScaledVector(velocity, delta); + + // find the room the user is currently in + let currentRoom = rooms.find(room => + camera.position.x >= room.minX && camera.position.x <= room.maxX && + camera.position.y >= room.minY && camera.position.y <= room.maxY && + camera.position.z >= room.minZ && camera.position.z <= room.maxZ + ); + + // find the room the user might be going into + const nextRoom = rooms.find(room => + newPosition.x >= room.minX && newPosition.x <= room.maxX && + newPosition.y >= room.minY && newPosition.y <= room.maxY && + newPosition.z >= room.minZ && newPosition.z <= room.maxZ + ); + + // detect if user is going into another room + if(currentRoom && nextRoom && currentRoom !== nextRoom) { + currentRoom = nextRoom; + } + + if (currentRoom) { + // Boundary checks for the current room + newPosition.x = Math.max(currentRoom.minX, Math.min(currentRoom.maxX, newPosition.x)); + newPosition.z = Math.max(currentRoom.minZ, Math.min(currentRoom.maxZ, newPosition.z)); + + // Calculate ground height using a raycaster and specific slope + const slopeObject = scene.getObjectByName(`slope-${currentRoom.minX}-${currentRoom.maxX}-${currentRoom.minZ}-${currentRoom.maxZ}`); + if (slopeObject) { + const raycaster = new THREE.Raycaster(newPosition.clone().setY(100), new THREE.Vector3(0, -1, 0)); + const intersects = raycaster.intersectObject(slopeObject, true); + if (intersects.length > 0) { + const groundHeight = intersects[0].point.y; + newPosition.y = groundHeight + cameraHeight; + } else { + // Default to room min height if no ground intersection found + newPosition.y = currentRoom.minY + cameraHeight; + } + } + + // Clamp Y position to the room boundaries + newPosition.y = Math.max(currentRoom.minY + cameraHeight, Math.min(currentRoom.maxY, newPosition.y)); + } + + camera.position.copy(newPosition); + velocity.multiplyScalar(1 - 10.0 * delta); }); return null; }; - From d5fa573412558707d9a608c077d6ee557f70b769 Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 14 Jun 2024 01:07:02 +0200 Subject: [PATCH 2/4] ability to add multiple slopes to a room --- app/components/FirstPersonControls.tsx | 80 +++++++++++++++++--------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index a8d6328..fb71f3b 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -20,9 +20,21 @@ export const FirstPersonControls = (speed) => { // temporary location for rooms, TODO: move this outside of the controller const rooms = [ - { minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, slope: { angle: Math.PI / 6, position: { x: 0, y: 0, z: 0 } } }, - { minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, slope: { angle: 0, position: { x: 0, y: 0, z: 0 } } }, - { minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, slope: { angle: Math.PI / 4, position: { x: 110, y: 0, z: 0 } } }, + { + minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, + slopes: [ + { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10 }, + { angle: Math.PI / 3, position: { x: 10, y: 0, z: 0 }, width: 10 } + ] + }, + { + minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, + slopes: [] + }, + { + minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, + slopes: [] + }, ]; useEffect(() => { @@ -110,18 +122,24 @@ export const FirstPersonControls = (speed) => { ); scene.add(roomBox); - // Add slope to the scene - const slopeGeometry = new THREE.PlaneGeometry(100, 100); - const slopeMaterial = new THREE.MeshBasicMaterial({ - color: 0xcccccc, - side: THREE.DoubleSide, - wireframe: true + // Add slopes to the scene + room.slopes.forEach((slope, index) => { + const slopeGeometry = new THREE.PlaneGeometry(slope.width, 100); + const slopeMaterial = new THREE.MeshBasicMaterial({ + color: 0xcccccc, + side: THREE.DoubleSide, + wireframe: true + }); + const slopeMesh = new THREE.Mesh(slopeGeometry, slopeMaterial); + slopeMesh.rotation.x = -slope.angle; + slopeMesh.position.set( + room.minX + (room.maxX - room.minX) / 2 + slope.position.x, + (room.minY + room.maxY) / 2 + slope.position.y, + room.minZ + (room.maxZ - room.minZ) / 2 + slope.position.z + ); + slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; // Naming slopes to easily find them later + scene.add(slopeMesh); }); - const slope = new THREE.Mesh(slopeGeometry, slopeMaterial); - slope.rotation.x = -room.slope.angle; - slope.position.set(room.slope.position.x, room.slope.position.y, room.slope.position.z); - slope.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; // Naming slopes to easily find them later - scene.add(slope); }); return () => { @@ -171,8 +189,8 @@ export const FirstPersonControls = (speed) => { ); // detect if user is going into another room - if(currentRoom && nextRoom && currentRoom !== nextRoom) { - currentRoom = nextRoom; + if (currentRoom && nextRoom && currentRoom !== nextRoom) { + currentRoom = nextRoom; } if (currentRoom) { @@ -180,20 +198,28 @@ export const FirstPersonControls = (speed) => { newPosition.x = Math.max(currentRoom.minX, Math.min(currentRoom.maxX, newPosition.x)); newPosition.z = Math.max(currentRoom.minZ, Math.min(currentRoom.maxZ, newPosition.z)); - // Calculate ground height using a raycaster and specific slope - const slopeObject = scene.getObjectByName(`slope-${currentRoom.minX}-${currentRoom.maxX}-${currentRoom.minZ}-${currentRoom.maxZ}`); - if (slopeObject) { - const raycaster = new THREE.Raycaster(newPosition.clone().setY(100), new THREE.Vector3(0, -1, 0)); - const intersects = raycaster.intersectObject(slopeObject, true); - if (intersects.length > 0) { - const groundHeight = intersects[0].point.y; - newPosition.y = groundHeight + cameraHeight; - } else { - // Default to room min height if no ground intersection found - newPosition.y = currentRoom.minY + cameraHeight; + // Calculate ground height using a raycaster and specific slopes + const raycaster = new THREE.Raycaster(newPosition.clone().setY(100), new THREE.Vector3(0, -1, 0)); + let groundHeight = currentRoom.minY; + let foundGround = false; + + for (let i = 0; i < currentRoom.slopes.length; i++) { + const slopeObject = scene.getObjectByName(`slope-${currentRoom.minX}-${currentRoom.maxX}-${currentRoom.minZ}-${currentRoom.maxZ}-${i}`); + if (slopeObject) { + const intersects = raycaster.intersectObject(slopeObject, true); + if (intersects.length > 0) { + groundHeight = Math.max(groundHeight, intersects[0].point.y); + foundGround = true; + } } } + if (!foundGround) { + groundHeight = currentRoom.minY; + } + + newPosition.y = groundHeight + cameraHeight; + // Clamp Y position to the room boundaries newPosition.y = Math.max(currentRoom.minY + cameraHeight, Math.min(currentRoom.maxY, newPosition.y)); } From 398c527d2116321c7f48d867b259dac7149d6931 Mon Sep 17 00:00:00 2001 From: Pascal Date: Thu, 20 Jun 2024 00:52:22 +0200 Subject: [PATCH 3/4] some changes for slopes & add objects to rooms --- app/components/FirstPersonControls.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index fb71f3b..3b7059a 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -23,17 +23,24 @@ export const FirstPersonControls = (speed) => { { minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, slopes: [ - { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10 }, - { angle: Math.PI / 3, position: { x: 10, y: 0, z: 0 }, width: 10 } + { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10, length: 50 }, + { angle: Math.PI / 3, position: { x: 10, y: 5, z: 5 }, width: 5, length: 50 } + ], + objects: [ + { minX: 5, maxX: 10, minY: 5, maxY: 10, minZ: -50, maxZ: 10 } ] }, { minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, - slopes: [] + slopes: [ + { angle: Math.PI / 2, position: { x: 10, y: 5, z: 5 }, width: 5, length: 10 } + ], + objects: [] }, { minX: 60, maxX: 160, minY: 0, maxY: 20, minZ: -50, maxZ: 50, - slopes: [] + slopes: [], + objects: [] }, ]; @@ -124,7 +131,7 @@ export const FirstPersonControls = (speed) => { // Add slopes to the scene room.slopes.forEach((slope, index) => { - const slopeGeometry = new THREE.PlaneGeometry(slope.width, 100); + const slopeGeometry = new THREE.PlaneGeometry(slope.width, slope.length); const slopeMaterial = new THREE.MeshBasicMaterial({ color: 0xcccccc, side: THREE.DoubleSide, From c6379ab9cb892c4e0a9d30c08f915dbaaa538213 Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 21 Jun 2024 00:15:11 +0200 Subject: [PATCH 4/4] add collision detection for objects --- app/components/FirstPersonControls.tsx | 53 +++++++++++++++++++++----- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/app/components/FirstPersonControls.tsx b/app/components/FirstPersonControls.tsx index 3b7059a..be190f2 100644 --- a/app/components/FirstPersonControls.tsx +++ b/app/components/FirstPersonControls.tsx @@ -22,18 +22,18 @@ export const FirstPersonControls = (speed) => { const rooms = [ { minX: -50, maxX: 50, minY: 0, maxY: 20, minZ: -50, maxZ: 50, - slopes: [ - { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10, length: 50 }, - { angle: Math.PI / 3, position: { x: 10, y: 5, z: 5 }, width: 5, length: 50 } - ], + slopes: [], objects: [ - { minX: 5, maxX: 10, minY: 5, maxY: 10, minZ: -50, maxZ: 10 } + { minX: 10, maxX: 15, minY: 0, maxY: 15, minZ: 10, maxZ: 15 } ] }, { minX: 50, maxX: 60, minY: 0, maxY: 10, minZ: 0, maxZ: 10, slopes: [ - { angle: Math.PI / 2, position: { x: 10, y: 5, z: 5 }, width: 5, length: 10 } + { angle: Math.PI / 2, position: { x: 10, y: 5, z: 5 }, width: 5, length: 10 }, + { angle: Math.PI / 3, position: { x: 0, y: 0, z: 0 }, width: 10, length: 50 }, + { angle: Math.PI / 3, position: { x: 10, y: 5, z: 5 }, width: 5, length: 50 } + ], objects: [] }, @@ -129,6 +129,29 @@ export const FirstPersonControls = (speed) => { ); scene.add(roomBox); + room.objects.forEach(object => { + + const objectGeometry = new THREE.BoxGeometry( + object.maxX - object.minX, + object.maxY - object.minY, + object.maxZ - object.minZ + ); + const objectMaterial = new THREE.MeshBasicMaterial({ + color: 0xff0000, + transparent: true, + opacity: 0.25, + wireframe: true + }); + const objectBox = new THREE.Mesh(objectGeometry, objectMaterial); + objectBox.position.set( + (room.minX + object.minX - room.maxX + object.maxX) / 2, + (room.minY + object.minY - room.maxY + object.maxY) / 2, + (room.minZ + object.minZ - room.maxZ + object.maxZ) / 2 + ); + objectBox.name = `object-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}`; + scene.add(objectBox); + }); + // Add slopes to the scene room.slopes.forEach((slope, index) => { const slopeGeometry = new THREE.PlaneGeometry(slope.width, slope.length); @@ -144,9 +167,10 @@ export const FirstPersonControls = (speed) => { (room.minY + room.maxY) / 2 + slope.position.y, room.minZ + (room.maxZ - room.minZ) / 2 + slope.position.z ); - slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; // Naming slopes to easily find them later + slopeMesh.name = `slope-${room.minX}-${room.maxX}-${room.minZ}-${room.maxZ}-${index}`; scene.add(slopeMesh); }); + }); return () => { @@ -179,7 +203,7 @@ export const FirstPersonControls = (speed) => { velocity.addScaledVector(direction, movementSpeed * delta); } - const newPosition = camera.position.clone().addScaledVector(velocity, delta); + let newPosition = camera.position.clone().addScaledVector(velocity, delta); // find the room the user is currently in let currentRoom = rooms.find(room => @@ -195,12 +219,23 @@ export const FirstPersonControls = (speed) => { newPosition.z >= room.minZ && newPosition.z <= room.maxZ ); + // Boundary checks for objects in the room + const object = currentRoom.objects.find(object => + newPosition.x >= currentRoom.minX + object.minX && newPosition.x <= currentRoom.minX + object.minX + (object.maxX - object.minX) && + newPosition.y >= currentRoom.minY + object.minY - 10 && newPosition.y <= currentRoom.minY + object.minY - 10 + (object.maxY - object.minY) && + newPosition.z >= currentRoom.minZ + object.minZ && newPosition.z <= currentRoom.minZ + object.minZ + (object.maxZ - object.minZ) + ); + + if (object) { + newPosition = camera.position; + } + // detect if user is going into another room if (currentRoom && nextRoom && currentRoom !== nextRoom) { currentRoom = nextRoom; } - if (currentRoom) { + if (currentRoom && !object) { // Boundary checks for the current room newPosition.x = Math.max(currentRoom.minX, Math.min(currentRoom.maxX, newPosition.x)); newPosition.z = Math.max(currentRoom.minZ, Math.min(currentRoom.maxZ, newPosition.z));