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]);