Feat/map-environment #6
@@ -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
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user