Feat/map-environment #6

Merged
math-pixel merged 116 commits from feat/map-environment into develop 2026-05-29 00:00:51 +00:00
2 changed files with 42 additions and 5 deletions
Showing only changes of commit b89eedd5be - Show all commits
+29 -4
View File
@@ -1,12 +1,16 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { useGLTF } from "@react-three/drei"; import { useGLTF } from "@react-three/drei";
import * as THREE from "three"; import * as THREE from "three";
import { TERRAIN_MODEL_PATH } from "@/data/world/terrainConfig";
import type { Vector3Tuple } from "@/types/three/three"; import type { Vector3Tuple } from "@/types/three/three";
import { getMapNodesByName } from "@/utils/map/loadMapSceneData";
const TERRAIN_MODEL_PATH = "/models/terrain/model.gltf";
const RAYCAST_Y = 500; const RAYCAST_Y = 500;
const RAYCAST_FAR = 1000; const RAYCAST_FAR = 1000;
const DOWN = new THREE.Vector3(0, -1, 0); const DOWN = new THREE.Vector3(0, -1, 0);
const DEFAULT_TERRAIN_POSITION: Vector3Tuple = [0, 0, 0];
const DEFAULT_TERRAIN_ROTATION: Vector3Tuple = [0, 0, 0];
const DEFAULT_TERRAIN_SCALE: Vector3Tuple = [1, 1, 1];
interface TerrainHeightSampler { interface TerrainHeightSampler {
getHeight: (x: number, z: number) => number | null; getHeight: (x: number, z: number) => number | null;
@@ -14,8 +18,17 @@ interface TerrainHeightSampler {
function createTerrainHeightSampler( function createTerrainHeightSampler(
scene: THREE.Object3D, scene: THREE.Object3D,
position: Vector3Tuple,
rotation: Vector3Tuple,
scale: Vector3Tuple,
): TerrainHeightSampler { ): TerrainHeightSampler {
const meshes: THREE.Mesh[] = []; const meshes: THREE.Mesh[] = [];
const terrainMatrix = new THREE.Matrix4().compose(
new THREE.Vector3(...position),
new THREE.Quaternion().setFromEuler(new THREE.Euler(...rotation)),
new THREE.Vector3(...scale),
);
const inverseTerrainMatrix = terrainMatrix.clone().invert();
const raycaster = new THREE.Raycaster( const raycaster = new THREE.Raycaster(
new THREE.Vector3(), new THREE.Vector3(),
DOWN, DOWN,
@@ -32,17 +45,29 @@ function createTerrainHeightSampler(
return { return {
getHeight: (x, z) => { getHeight: (x, z) => {
raycaster.set(new THREE.Vector3(x, RAYCAST_Y, z), DOWN); const localOrigin = new THREE.Vector3(x, RAYCAST_Y, z).applyMatrix4(
inverseTerrainMatrix,
);
const localDirection =
DOWN.clone().transformDirection(inverseTerrainMatrix);
raycaster.set(localOrigin, localDirection);
const hit = raycaster.intersectObjects(meshes, false)[0]; const hit = raycaster.intersectObjects(meshes, false)[0];
return hit?.point.y ?? null; return hit?.point.applyMatrix4(terrainMatrix).y ?? null;
}, },
}; };
} }
export function useTerrainHeightSampler(): TerrainHeightSampler { export function useTerrainHeightSampler(): TerrainHeightSampler {
const { scene } = useGLTF(TERRAIN_MODEL_PATH); const { scene } = useGLTF(TERRAIN_MODEL_PATH);
const terrainNode = getMapNodesByName("terrain")[0];
const position = terrainNode?.position ?? DEFAULT_TERRAIN_POSITION;
const rotation = terrainNode?.rotation ?? DEFAULT_TERRAIN_ROTATION;
const scale = terrainNode?.scale ?? DEFAULT_TERRAIN_SCALE;
return useMemo(() => createTerrainHeightSampler(scene), [scene]); return useMemo(
() => createTerrainHeightSampler(scene, position, rotation, scale),
[position, rotation, scale, scene],
);
} }
export function useTerrainSnappedPosition( export function useTerrainSnappedPosition(
+13 -1
View File
@@ -118,6 +118,7 @@ export function GameMap({
const [collisionMapNodes, setCollisionMapNodes] = useState<LoadedMapNode[]>( const [collisionMapNodes, setCollisionMapNodes] = useState<LoadedMapNode[]>(
[], [],
); );
const [terrainNode, setTerrainNode] = useState<MapNode | null>(null);
const [mapLoaded, setMapLoaded] = useState(false); const [mapLoaded, setMapLoaded] = useState(false);
const [settledMapNodeCount, setSettledMapNodeCount] = useState(0); const [settledMapNodeCount, setSettledMapNodeCount] = useState(0);
const mapReady = mapLoaded && settledMapNodeCount >= renderMapNodes.length; const mapReady = mapLoaded && settledMapNodeCount >= renderMapNodes.length;
@@ -133,6 +134,7 @@ export function GameMap({
(currentStep: string) => { (currentStep: string) => {
setRenderMapNodes([]); setRenderMapNodes([]);
setCollisionMapNodes([]); setCollisionMapNodes([]);
setTerrainNode(null);
setMapLoaded(true); setMapLoaded(true);
settledMapNodesRef.current.clear(); settledMapNodesRef.current.clear();
setSettledMapNodeCount(0); setSettledMapNodeCount(0);
@@ -187,6 +189,7 @@ export function GameMap({
const modelUrl = sceneData.models.get(node.name); const modelUrl = sceneData.models.get(node.name);
return { node, modelUrl: modelUrl ?? null }; return { node, modelUrl: modelUrl ?? null };
}); });
const loadedTerrainNode = loadedCollisionNodes[0]?.node ?? null;
const missingModelCount = loadedMapNodes.filter( const missingModelCount = loadedMapNodes.filter(
(mapNode) => mapNode.modelUrl === null, (mapNode) => mapNode.modelUrl === null,
).length; ).length;
@@ -203,6 +206,7 @@ export function GameMap({
setRenderMapNodes(loadedMapNodes); setRenderMapNodes(loadedMapNodes);
setCollisionMapNodes(loadedCollisionNodes); setCollisionMapNodes(loadedCollisionNodes);
setTerrainNode(loadedTerrainNode);
setMapLoaded(true); setMapLoaded(true);
settledMapNodesRef.current.clear(); settledMapNodesRef.current.clear();
setSettledMapNodeCount(0); setSettledMapNodeCount(0);
@@ -265,7 +269,15 @@ export function GameMap({
<CloudSystem /> <CloudSystem />
<VegetationSystem /> <VegetationSystem />
{isMapModelVisible("terrain", { groups, models }) ? ( {isMapModelVisible("terrain", { groups, models }) ? (
<TerrainModel /> terrainNode ? (
<TerrainModel
position={terrainNode.position}
rotation={terrainNode.rotation}
scale={terrainNode.scale}
/>
) : (
<TerrainModel />
)
) : null} ) : null}
<GameMapCollision <GameMapCollision
buildOctree={buildOctree} buildOctree={buildOctree}