diff --git a/src/components/three/world/TerrainModel.tsx b/src/components/three/world/TerrainModel.tsx index 5ba842a..4525ee2 100644 --- a/src/components/three/world/TerrainModel.tsx +++ b/src/components/three/world/TerrainModel.tsx @@ -3,7 +3,6 @@ import * as THREE from "three"; import { useGLTF } from "@react-three/drei"; import { useThree } from "@react-three/fiber"; import { TERRAIN_MODEL_PATH } from "@/data/world/terrainConfig"; -import { flattenLaFabrikTerrainFootprint } from "@/data/world/laFabrikConfig"; import type { Vector3Tuple } from "@/types/three/three"; import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene"; @@ -66,10 +65,9 @@ export function TerrainModel({ const terrainModel = useMemo(() => { optimizeGLTFSceneTextures(scene, maxAnisotropy); const model = scene.clone(true); - flattenLaFabrikTerrainFootprint(model, position, rotation, scale); applyTerrainMaterialSettings(model, receiveShadow); return model; - }, [maxAnisotropy, position, receiveShadow, rotation, scale, scene]); + }, [maxAnisotropy, scene, receiveShadow]); useEffect(() => { onLoaded?.(); diff --git a/src/data/player/playerConfig.ts b/src/data/player/playerConfig.ts index a1aa8d3..b9a99ce 100644 --- a/src/data/player/playerConfig.ts +++ b/src/data/player/playerConfig.ts @@ -15,5 +15,9 @@ export const PLAYER_XZ_DAMPING_FACTOR = 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; +export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [ + LA_FABRIK_PLAYER_SPAWN[0] + 5, + LA_FABRIK_PLAYER_SPAWN[1], + LA_FABRIK_PLAYER_SPAWN[2] + 5, +]; export const PLAYER_SPAWN_POSITION_PHYSICS: Vector3Tuple = [0, 3, 0]; diff --git a/src/data/world/characters/characterConfig.ts b/src/data/world/characters/characterConfig.ts index 26776cb..579cb55 100644 --- a/src/data/world/characters/characterConfig.ts +++ b/src/data/world/characters/characterConfig.ts @@ -20,7 +20,6 @@ export interface CharacterConfig { scale: Vector3Tuple; animations: readonly string[]; defaultAnimation: string; - snapToTerrain?: boolean; } export const CHARACTER_CONFIGS = { diff --git a/src/data/world/laFabrikConfig.ts b/src/data/world/laFabrikConfig.ts index 1eeb7b0..795eb08 100644 --- a/src/data/world/laFabrikConfig.ts +++ b/src/data/world/laFabrikConfig.ts @@ -1,4 +1,3 @@ -import * as THREE from "three"; import type { Vector3Tuple } from "@/types/three/three"; export const LA_FABRIK_CENTER: Vector3Tuple = [59.4973, 6.2746, 64.6354]; @@ -7,15 +6,10 @@ export const LA_FABRIK_HALF_EXTENTS = { x: 8.5, z: 7.5, } as const; -export const LA_FABRIK_FLOOR_Y = 6.3; -export const LA_FABRIK_PLAYER_SPAWN: Vector3Tuple = [59.5, 8.05, 64.64]; +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_INTERIOR_LIGHT_POSITION: Vector3Tuple = [59.5, 9, 64.64]; -const _terrainMatrix = new THREE.Matrix4(); -const _meshWorldMatrix = new THREE.Matrix4(); -const _inverseMeshWorldMatrix = new THREE.Matrix4(); -const _worldPosition = new THREE.Vector3(); - export function isInsideLaFabrikFootprint( x: number, z: number, @@ -33,51 +27,3 @@ export function isInsideLaFabrikFootprint( Math.abs(localZ) <= LA_FABRIK_HALF_EXTENTS.z + padding ); } - -export function flattenLaFabrikTerrainFootprint( - object: THREE.Object3D, - position: Vector3Tuple, - rotation: Vector3Tuple, - scale: Vector3Tuple, -): void { - _terrainMatrix.compose( - new THREE.Vector3(...position), - new THREE.Quaternion().setFromEuler(new THREE.Euler(...rotation)), - new THREE.Vector3(...scale), - ); - object.updateMatrixWorld(true); - - object.traverse((child) => { - if (!(child instanceof THREE.Mesh)) return; - const geometry = child.geometry; - const positions = geometry.getAttribute("position"); - if (!positions) return; - - _meshWorldMatrix.multiplyMatrices(_terrainMatrix, child.matrixWorld); - _inverseMeshWorldMatrix.copy(_meshWorldMatrix).invert(); - - for (let index = 0; index < positions.count; index++) { - _worldPosition - .fromBufferAttribute(positions, index) - .applyMatrix4(_meshWorldMatrix); - - if (!isInsideLaFabrikFootprint(_worldPosition.x, _worldPosition.z, 0.8)) { - continue; - } - - _worldPosition.y = Math.min(_worldPosition.y, LA_FABRIK_FLOOR_Y - 0.35); - _worldPosition.applyMatrix4(_inverseMeshWorldMatrix); - positions.setXYZ( - index, - _worldPosition.x, - _worldPosition.y, - _worldPosition.z, - ); - } - - positions.needsUpdate = true; - geometry.computeVertexNormals(); - geometry.computeBoundingBox(); - geometry.computeBoundingSphere(); - }); -} diff --git a/src/hooks/three/useTerrainHeight.ts b/src/hooks/three/useTerrainHeight.ts index 7e0a9d3..b4405f2 100644 --- a/src/hooks/three/useTerrainHeight.ts +++ b/src/hooks/three/useTerrainHeight.ts @@ -2,10 +2,6 @@ import { useMemo } from "react"; import { useGLTF } from "@react-three/drei"; import * as THREE from "three"; import { TERRAIN_MODEL_PATH } from "@/data/world/terrainConfig"; -import { - isInsideLaFabrikFootprint, - LA_FABRIK_FLOOR_Y, -} from "@/data/world/laFabrikConfig"; import type { Vector3Tuple } from "@/types/three/three"; import { getMapNodesByName } from "@/utils/map/loadMapSceneData"; @@ -70,10 +66,6 @@ function createTerrainHeightSampler( return { getHeight: (x, z) => { - if (isInsideLaFabrikFootprint(x, z, 0.6)) { - return LA_FABRIK_FLOOR_Y; - } - localOrigin.set(x, RAYCAST_Y, z).applyMatrix4(inverseTerrainMatrix); raycaster.set(localOrigin, localDirection); hits.length = 0; diff --git a/src/world/GameMapCollision.tsx b/src/world/GameMapCollision.tsx index f27955b..9d038d7 100644 --- a/src/world/GameMapCollision.tsx +++ b/src/world/GameMapCollision.tsx @@ -18,7 +18,6 @@ import { useTerrainHeightSampler, } from "@/hooks/three/useTerrainHeight"; import { WorldBoundsCollision } from "@/world/collision/WorldBoundsCollision"; -import { flattenLaFabrikTerrainFootprint } from "@/data/world/laFabrikConfig"; import type { MapNode } from "@/types/map/mapScene"; import type { OctreeReadyHandler } from "@/types/three/three"; import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading"; @@ -214,7 +213,7 @@ function CollisionModelInstance({ modelUrl: string; onLoaded: () => void; terrainHeight: TerrainHeightSampler; -}): React.JSX.Element | null { +}): React.JSX.Element { const { position, rotation, scale } = node; const normalizedScale = normalizeMapScale(scale); const { scene } = useLoggedGLTF(modelUrl, { @@ -224,46 +223,22 @@ function CollisionModelInstance({ scale: normalizedScale, }); const sceneInstance = useClonedObject(scene); - const collisionSceneInstance = useMemo(() => { - if (node.name === "terrain") { - flattenLaFabrikTerrainFootprint( - sceneInstance, - position, - rotation, - normalizedScale, - ); - } - return sceneInstance; - }, [node.name, normalizedScale, position, rotation, sceneInstance]); const collisionPosition = useMemo(() => { if (node.name === "terrain") return position; const [x, y, z] = position; const height = terrainHeight.getHeight(x, z); - const bottomOffset = getObjectBottomOffset( - collisionSceneInstance, - normalizedScale, - ); + const bottomOffset = getObjectBottomOffset(sceneInstance, normalizedScale); return [x, height !== null ? height + bottomOffset : y, z] as const; - }, [ - node.name, - normalizedScale, - position, - collisionSceneInstance, - terrainHeight, - ]); + }, [node.name, normalizedScale, position, sceneInstance, terrainHeight]); useEffect(() => { onLoaded(); }, [onLoaded]); - if (node.name === "lafabrik") { - return null; - } - return ( {mainState === "outro" ? : null} {mainState !== "intro" ? : null} - + ) : null} diff --git a/src/world/characters/CharacterSystem.tsx b/src/world/characters/CharacterSystem.tsx index d2f6672..3de23a4 100644 --- a/src/world/characters/CharacterSystem.tsx +++ b/src/world/characters/CharacterSystem.tsx @@ -3,18 +3,15 @@ import { AnimatedModel } from "@/components/three/models/AnimatedModel"; import { CHARACTER_CONFIGS, CHARACTER_IDS, - type CharacterConfig, type CharacterId, } from "@/data/world/characters/characterConfig"; import { useTerrainSnappedPosition } from "@/hooks/three/useTerrainHeight"; import { useCharacterDebugStore } from "@/managers/stores/useCharacterDebugStore"; function CharacterModel({ id }: { id: CharacterId }): React.JSX.Element { - const config: CharacterConfig = CHARACTER_CONFIGS[id]; + const config = CHARACTER_CONFIGS[id]; const state = useCharacterDebugStore((store) => store.characters[id]); - const snappedPosition = useTerrainSnappedPosition(state.position); - const position = - config.snapToTerrain === false ? state.position : snappedPosition; + const position = useTerrainSnappedPosition(state.position); return ( { camera.position.set(...spawnPosition); - }, [camera, spawnPosition]); + if (initialLookAt) camera.lookAt(...initialLookAt); + }, [camera, initialLookAt, spawnPosition]); return ( <> - + ); } diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 4c74d52..62fbe14 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -75,6 +75,7 @@ const PLAYER_FLOOR_NORMAL_MIN = 0.15; const PLAYER_GROUND_SNAP_DISTANCE = 0.22; interface PlayerControllerProps { + initialLookAt?: Vector3Tuple | undefined; octree: Octree | null; spawnPosition: Vector3Tuple; } @@ -89,6 +90,7 @@ const _collisionCorrection = new THREE.Vector3(); function resetPlayerCapsule( capsule: Capsule, spawnPosition: Vector3Tuple, + initialLookAt: Vector3Tuple | undefined, camera: THREE.Camera, velocity: THREE.Vector3, ): void { @@ -100,6 +102,7 @@ function resetPlayerCapsule( capsule.end.set(...spawnPosition); velocity.set(0, 0, 0); camera.position.copy(capsule.end); + if (initialLookAt) camera.lookAt(...initialLookAt); } function createSpawnCapsule(spawnPosition: Vector3Tuple): Capsule { @@ -145,6 +148,7 @@ function getCapsuleFootY(capsule: Capsule): number { } export function PlayerController({ + initialLookAt, octree, spawnPosition, }: PlayerControllerProps): null { @@ -234,6 +238,7 @@ export function PlayerController({ resetPlayerCapsule( capsule.current, spawnPosition, + initialLookAt, camera, velocity.current, ); @@ -241,7 +246,7 @@ export function PlayerController({ onFloor.current = false; wantsJump.current = false; initializedRef.current = true; - }, [camera, spawnPosition]); + }, [camera, initialLookAt, spawnPosition]); useEffect(() => { movementLockedRef.current = movementLocked; @@ -339,6 +344,7 @@ export function PlayerController({ resetPlayerCapsule( capsule.current, spawnPosition, + initialLookAt, camera, velocity.current, ); diff --git a/src/world/vegetation/VegetationSystem.tsx b/src/world/vegetation/VegetationSystem.tsx index c340633..ef07b55 100644 --- a/src/world/vegetation/VegetationSystem.tsx +++ b/src/world/vegetation/VegetationSystem.tsx @@ -16,6 +16,7 @@ import { VEGETATION_TYPES, type VegetationType, } from "@/data/world/vegetationConfig"; +import { isInsideLaFabrikFootprint } from "@/data/world/laFabrikConfig"; import { createWorldInstanceChunks } from "@/utils/world/chunkInstances"; interface VegetationSystemProps { @@ -60,6 +61,15 @@ function createVegetationChunks( }); } +function removeLaFabrikVegetation( + instances: VegetationInstance[], +): VegetationInstance[] { + return instances.filter((instance) => { + const [x, , z] = instance.position; + return !isInsideLaFabrikFootprint(x, z, 1.2); + }); +} + export function VegetationSystem({ onlyMapName = null, streaming = true, @@ -90,7 +100,10 @@ export function VegetationSystem({ const entry = data.get(config.mapName); if (!entry || entry.instances.length === 0) return []; - return createVegetationChunks(type, entry.instances); + const instances = removeLaFabrikVegetation(entry.instances); + if (instances.length === 0) return []; + + return createVegetationChunks(type, instances); }); }, [data, groups, models, onlyMapName]);