feat(map): generate path tiles from terrain colors

This commit is contained in:
Tom Boullay
2026-05-25 17:08:07 +02:00
parent 6d178dc59e
commit f54e71fc03
7 changed files with 132 additions and 1 deletions
+2
View File
@@ -24,6 +24,7 @@ import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNode
import { isGeneratedMapModelName } from "@/world/map-generated/generatedMapModelConfig";
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
import { isInstancedMapNodeName } from "@/world/map-instancing/mapInstancingConfig";
import { PathSystem } from "@/world/paths/PathSystem";
import { VegetationSystem } from "@/world/vegetation/VegetationSystem";
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
import { logger } from "@/utils/core/Logger";
@@ -257,6 +258,7 @@ export function GameMap({
))}
</group>
<MapInstancingSystem />
<PathSystem />
<VegetationSystem />
{isMapModelVisible("terrain", { groups, models }) ? (
<TerrainModel />
+20
View File
@@ -0,0 +1,20 @@
import { InstancedMapAsset } from "@/world/map-instancing/InstancedMapAsset";
import { PATH_TILE_MODEL_PATH } from "@/world/paths/pathConfig";
import { usePathTileData } from "@/world/paths/usePathTileData";
export function PathSystem(): React.JSX.Element | null {
const pathTiles = usePathTileData();
if (pathTiles.length === 0) {
return null;
}
return (
<InstancedMapAsset
castShadow={false}
instances={pathTiles}
modelPath={PATH_TILE_MODEL_PATH}
receiveShadow
/>
);
}
+10
View File
@@ -0,0 +1,10 @@
import { TERRAIN_COLORS, TERRAIN_TILE_SIZE } from "@/data/world/terrainConfig";
export const PATH_SURFACE_KEY = "chemin";
export const PATH_TILE_MODEL_PATH = TERRAIN_COLORS.chemin.modelPath;
export const PATH_TILE_SIZE =
TERRAIN_COLORS.chemin.tileSize ?? TERRAIN_TILE_SIZE;
export const PATH_TILE_SAMPLE_STEP = 2;
export const PATH_TILE_MAX_COUNT = 1500;
export const PATH_TILE_ROTATION = [0, 0, 0] as const;
export const PATH_TILE_SCALE = [1, 1, 1] as const;
+68
View File
@@ -0,0 +1,68 @@
import { useMemo } from "react";
import { useTerrainSurfaceData } from "@/hooks/world/useTerrainSurfaceData";
import type { Vector3Tuple } from "@/types/three/three";
import { sampleTerrainSurfaceAtXZFromRaycast } from "@/utils/world/terrainSurfaceSampler";
import type { MapAssetInstance } from "@/world/map-instancing/useMapInstancingData";
import {
PATH_TILE_MAX_COUNT,
PATH_SURFACE_KEY,
PATH_TILE_ROTATION,
PATH_TILE_SAMPLE_STEP,
PATH_TILE_SCALE,
} from "@/world/paths/pathConfig";
function createSampleCenters(min: number, max: number, step: number): number[] {
const start = Math.ceil(min / step) * step + step * 0.5;
const centers: number[] = [];
for (let value = start; value <= max; value += step) {
centers.push(value);
}
return centers;
}
export function usePathTileData(): MapAssetInstance[] {
const terrainSurfaceData = useTerrainSurfaceData();
return useMemo(() => {
if (!terrainSurfaceData) return [];
const instances: MapAssetInstance[] = [];
const xCenters = createSampleCenters(
terrainSurfaceData.bounds.minX,
terrainSurfaceData.bounds.maxX,
PATH_TILE_SAMPLE_STEP,
);
const zCenters = createSampleCenters(
terrainSurfaceData.bounds.minZ,
terrainSurfaceData.bounds.maxZ,
PATH_TILE_SAMPLE_STEP,
);
const raycastY = terrainSurfaceData.bounds.maxY + 10;
for (const x of xCenters) {
for (const z of zCenters) {
if (instances.length >= PATH_TILE_MAX_COUNT) return instances;
const sample = sampleTerrainSurfaceAtXZFromRaycast(
terrainSurfaceData.imageData,
terrainSurfaceData.raycastTarget,
x,
z,
raycastY,
);
if (sample?.key !== PATH_SURFACE_KEY) continue;
instances.push({
position: [x, 0, z],
rotation: [...PATH_TILE_ROTATION] as Vector3Tuple,
scale: [...PATH_TILE_SCALE] as Vector3Tuple,
});
}
}
return instances;
}, [terrainSurfaceData]);
}