refactor: clean map gameplay architecture
This commit is contained in:
@@ -24,7 +24,7 @@ import {
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { GameMapCollision } from "@/world/GameMapCollision";
|
||||
import { GeneratedMapNodeInstance } from "@/world/map-generated/GeneratedMapNodeInstance";
|
||||
import { isGeneratedMapModelName } from "@/world/map-generated/generatedMapModelConfig";
|
||||
import { isGeneratedMapModelName } from "@/data/world/generatedMapModelConfig";
|
||||
import { MapInstancingSystem } from "@/world/map-instancing/MapInstancingSystem";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { InteractableObject } from "@/components/three/interaction/InteractableObject";
|
||||
import { RepairGame } from "@/components/three/gameplay/RepairGame";
|
||||
import {
|
||||
EBIKE_REPAIR_POSITION,
|
||||
REPAIR_MISSION_POSITION_ENTRIES,
|
||||
REPAIR_MISSION_TRIGGERS,
|
||||
type RepairMissionTriggerConfig,
|
||||
} from "@/data/gameplay/repairMissionAnchors";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
@@ -32,21 +33,31 @@ function StageAnchor({
|
||||
);
|
||||
}
|
||||
|
||||
function EbikeMissionTrigger(): React.JSX.Element | null {
|
||||
function RepairMissionTrigger({
|
||||
config,
|
||||
}: {
|
||||
config: RepairMissionTriggerConfig;
|
||||
}): React.JSX.Element | null {
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const ebikeStep = useGameStore((state) => state.ebike.currentStep);
|
||||
const missionStep = useGameStore(
|
||||
(state) => state[config.mission].currentStep,
|
||||
);
|
||||
const setMissionStep = useGameStore((state) => state.setMissionStep);
|
||||
const position = REPAIR_MISSION_POSITION_ENTRIES.find(
|
||||
(entry) => entry.mission === config.mission,
|
||||
)?.position;
|
||||
|
||||
if (mainState !== "ebike" || ebikeStep !== "locked") return null;
|
||||
if (!position) return null;
|
||||
if (mainState !== config.mission || missionStep !== "locked") return null;
|
||||
|
||||
return (
|
||||
<group position={EBIKE_REPAIR_POSITION}>
|
||||
<group position={position}>
|
||||
<InteractableObject
|
||||
kind="trigger"
|
||||
label="Réparer l'e-bike"
|
||||
position={EBIKE_REPAIR_POSITION}
|
||||
radius={4}
|
||||
onPress={() => setMissionStep("ebike", "waiting")}
|
||||
label={config.label}
|
||||
position={position}
|
||||
radius={config.radius}
|
||||
onPress={() => setMissionStep(config.mission, "waiting")}
|
||||
>
|
||||
<mesh>
|
||||
<sphereGeometry args={[1.3, 16, 16]} />
|
||||
@@ -68,7 +79,9 @@ export function GameStageContent(): React.JSX.Element {
|
||||
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission, position }) => (
|
||||
<RepairGame key={mission} mission={mission} position={position} />
|
||||
))}
|
||||
<EbikeMissionTrigger />
|
||||
{REPAIR_MISSION_TRIGGERS.map((config) => (
|
||||
<RepairMissionTrigger key={config.mission} config={config} />
|
||||
))}
|
||||
{mainState === "outro" ? (
|
||||
<StageAnchor color="#fb7185" position={[0, 6, 10]} scale={1.25} />
|
||||
) : null}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Suspense, useMemo, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { CLOUD_CONFIG } from "@/data/world/cloudConfig";
|
||||
import { getWindVector } from "@/data/world/windConfig";
|
||||
import { getWindVector } from "@/utils/world/windVector";
|
||||
import { useDynamicClouds } from "@/hooks/world/useGraphicsSettings";
|
||||
import { useCloudSettings } from "@/hooks/world/useCloudSettings";
|
||||
import { useWind } from "@/hooks/world/useWind";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
GRASS_BASE_COLOR,
|
||||
GRASS_COLORS,
|
||||
GRASS_CONFIG,
|
||||
} from "@/world/grass/grassConfig";
|
||||
} from "@/data/world/grassConfig";
|
||||
import {
|
||||
grassFragmentShader,
|
||||
grassVertexShader,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
useDynamicGrass,
|
||||
useGrassDensity,
|
||||
} from "@/hooks/world/useGraphicsSettings";
|
||||
import { GRASS_CONFIG } from "@/world/grass/grassConfig";
|
||||
import { GRASS_CONFIG } from "@/data/world/grassConfig";
|
||||
import { GrassPatch } from "@/world/grass/GrassPatch";
|
||||
import { useTerrainGrassSampler } from "@/world/grass/useTerrainGrassSampler";
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
export const GRASS_CONFIG = {
|
||||
enabled: true,
|
||||
patchSize: 30,
|
||||
bladeCount: 32000,
|
||||
bladeWidth: 0.08,
|
||||
maxBladeHeight: 0.56,
|
||||
randomHeightAmount: 0.25,
|
||||
surfaceOffset: 0.025,
|
||||
heightTextureSize: 128,
|
||||
windNoiseScale: 0.9,
|
||||
windStrength: 0.35,
|
||||
baldPatchModifier: 1.1,
|
||||
falloffSharpness: 0.35,
|
||||
heightNoiseFrequency: 9,
|
||||
heightNoiseAmplitude: 1,
|
||||
clumpFrequency: 2.6,
|
||||
clumpThreshold: 0.18,
|
||||
clumpSoftness: 0.45,
|
||||
zoneFrequency: 0.035,
|
||||
noGrassZoneThreshold: 0.2,
|
||||
sparseZoneThreshold: 0.4,
|
||||
mediumZoneThreshold: 0.65,
|
||||
zoneSoftness: 0.08,
|
||||
noGrassZoneHeight: 0,
|
||||
sparseZoneHeight: 0.08,
|
||||
mediumZoneHeight: 0.45,
|
||||
tallZoneHeight: 1,
|
||||
noGrassZoneDensity: 0,
|
||||
sparseZoneDensity: 0.08,
|
||||
mediumZoneDensity: 0.72,
|
||||
tallZoneDensity: 1,
|
||||
maxBendAngle: 14,
|
||||
} as const;
|
||||
|
||||
export const GRASS_COLORS = ["#84C66B", "#67B058", "#A3CA5B"] as const;
|
||||
export const GRASS_BASE_COLOR = "#1A3A1A" as const;
|
||||
@@ -6,7 +6,7 @@ import type { TerrainSurfaceBounds } from "@/types/world/terrainSurface";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
import { logger } from "@/utils/core/Logger";
|
||||
import { getMapNodesByName } from "@/utils/map/loadMapSceneData";
|
||||
import { GRASS_CONFIG } from "@/world/grass/grassConfig";
|
||||
import { GRASS_CONFIG } from "@/data/world/grassConfig";
|
||||
|
||||
const RAYCAST_Y = 500;
|
||||
const RAYCAST_FAR = 1000;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
const GENERATED_MAP_MODEL_NAMES = new Set([
|
||||
"ecole",
|
||||
"fermeverticale",
|
||||
"generateur",
|
||||
"lafabrik",
|
||||
]);
|
||||
|
||||
export function isGeneratedMapModelName(name: string): boolean {
|
||||
return GENERATED_MAP_MODEL_NAMES.has(name);
|
||||
}
|
||||
@@ -27,6 +27,8 @@ interface MeshMergeGroup {
|
||||
material: THREE.Material | THREE.Material[];
|
||||
}
|
||||
|
||||
const meshDataCache = new Map<string, MeshData[]>();
|
||||
|
||||
function cloneMaterial(
|
||||
material: THREE.Material | THREE.Material[],
|
||||
): THREE.Material | THREE.Material[] {
|
||||
@@ -49,8 +51,6 @@ function disposeMaterialOnly(
|
||||
}
|
||||
|
||||
function disposeInstancedMapMesh(mesh: THREE.InstancedMesh): void {
|
||||
mesh.geometry.dispose();
|
||||
disposeMaterialOnly(mesh.material);
|
||||
mesh.dispose();
|
||||
}
|
||||
|
||||
@@ -183,6 +183,15 @@ export function InstancedMapAsset({
|
||||
state.gl.capabilities.getMaxAnisotropy(),
|
||||
);
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const meshDataList = useMemo(() => {
|
||||
const cached = meshDataCache.get(modelPath);
|
||||
if (cached) return cached;
|
||||
|
||||
optimizeGLTFSceneTextures(scene, maxAnisotropy);
|
||||
const extracted = extractMeshes(scene);
|
||||
meshDataCache.set(modelPath, extracted);
|
||||
return extracted;
|
||||
}, [maxAnisotropy, modelPath, scene]);
|
||||
const groundedInstances = useMemo(
|
||||
() =>
|
||||
instances.map((instance) => {
|
||||
@@ -202,8 +211,6 @@ export function InstancedMapAsset({
|
||||
const group = groupRef.current;
|
||||
if (!group || groundedInstances.length === 0) return;
|
||||
|
||||
optimizeGLTFSceneTextures(scene, maxAnisotropy);
|
||||
const meshDataList = extractMeshes(scene);
|
||||
const geometryBottomY = getMeshBottomY(meshDataList);
|
||||
const instancedMeshes = meshDataList.map((meshData, index) => {
|
||||
const instancedMesh = new THREE.InstancedMesh(
|
||||
@@ -232,7 +239,7 @@ export function InstancedMapAsset({
|
||||
disposeInstancedMapMesh(mesh);
|
||||
}
|
||||
};
|
||||
}, [castShadow, groundedInstances, maxAnisotropy, receiveShadow, scene]);
|
||||
}, [castShadow, groundedInstances, meshDataList, receiveShadow]);
|
||||
|
||||
if (instances.length === 0) {
|
||||
return null;
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
export const MAP_INSTANCING_ASSETS = {
|
||||
boiteauxlettres: {
|
||||
mapName: "boiteauxlettres",
|
||||
modelPath: "/models/boiteauxlettres/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
pylone: {
|
||||
mapName: "pylone",
|
||||
modelPath: "/models/pylone/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
immeuble1: {
|
||||
mapName: "immeuble1",
|
||||
modelPath: "/models/immeuble1/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
maison1: {
|
||||
mapName: "maison1",
|
||||
modelPath: "/models/maison1/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
eolienne: {
|
||||
mapName: "eolienne",
|
||||
modelPath: "/models/eolienne/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
parcebike: {
|
||||
mapName: "parcebike",
|
||||
modelPath: "/models/parcebike/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
panneauaffichage: {
|
||||
mapName: "panneauaffichage",
|
||||
modelPath: "/models/panneauaffichage/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
panneauclassique: {
|
||||
mapName: "panneauclassique",
|
||||
modelPath: "/models/panneauclassique/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
panneaufleche: {
|
||||
mapName: "panneaufleche",
|
||||
modelPath: "/models/panneaufleche/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
panneausolaire: {
|
||||
mapName: "panneausolaire",
|
||||
modelPath: "/models/panneausolaire/model.gltf",
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
enabled: true,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type MapInstancingAssetType = keyof typeof MAP_INSTANCING_ASSETS;
|
||||
|
||||
export type MapInstancingAssetConfig =
|
||||
(typeof MAP_INSTANCING_ASSETS)[MapInstancingAssetType];
|
||||
|
||||
export const MAP_INSTANCED_NODE_NAMES: ReadonlySet<string> = new Set(
|
||||
Object.values(MAP_INSTANCING_ASSETS)
|
||||
.filter((config) => config.enabled)
|
||||
.map((config) => config.mapName),
|
||||
);
|
||||
|
||||
export function isInstancedMapNodeName(name: string): boolean {
|
||||
return MAP_INSTANCED_NODE_NAMES.has(name);
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
import { getMapNodes, loadMapSceneData } from "@/utils/map/loadMapSceneData";
|
||||
import {
|
||||
MAP_INSTANCING_ASSETS,
|
||||
type MapInstancingAssetType,
|
||||
} from "@/world/map-instancing/mapInstancingConfig";
|
||||
|
||||
export interface MapAssetInstance {
|
||||
position: Vector3Tuple;
|
||||
rotation: Vector3Tuple;
|
||||
scale: Vector3Tuple;
|
||||
}
|
||||
|
||||
export type MapInstancingData = Map<MapInstancingAssetType, MapAssetInstance[]>;
|
||||
|
||||
function mapNodeToInstance(node: MapNode): MapAssetInstance {
|
||||
return {
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
};
|
||||
}
|
||||
|
||||
function extractMapInstancingData(mapNodes: MapNode[]): MapInstancingData {
|
||||
const data: MapInstancingData = new Map();
|
||||
|
||||
for (const [type, config] of Object.entries(MAP_INSTANCING_ASSETS)) {
|
||||
if (!config.enabled) continue;
|
||||
|
||||
const instances = mapNodes
|
||||
.filter(
|
||||
(node) => node.name === config.mapName && node.type === "Object3D",
|
||||
)
|
||||
.map(mapNodeToInstance);
|
||||
|
||||
if (instances.length > 0) {
|
||||
data.set(type as MapInstancingAssetType, instances);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function useMapInstancingData(): {
|
||||
data: MapInstancingData | null;
|
||||
isLoading: boolean;
|
||||
} {
|
||||
const [data, setData] = useState<MapInstancingData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function load() {
|
||||
const cachedNodes = getMapNodes();
|
||||
|
||||
if (cachedNodes) {
|
||||
if (!cancelled) {
|
||||
setData(extractMapInstancingData(cachedNodes));
|
||||
setIsLoading(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await loadMapSceneData();
|
||||
const nodes = getMapNodes();
|
||||
|
||||
if (!cancelled && nodes) {
|
||||
setData(extractMapInstancingData(nodes));
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { data, isLoading };
|
||||
}
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
PATH_DEBUG_PREVIEW_ENABLED,
|
||||
PATH_TILE_RENDER_ENABLED,
|
||||
PATH_TILE_MODEL_PATH,
|
||||
} from "@/world/paths/pathConfig";
|
||||
} from "@/data/world/pathConfig";
|
||||
import { usePathTileData } from "@/world/paths/usePathTileData";
|
||||
import type { MapAssetInstance } from "@/world/map-instancing/useMapInstancingData";
|
||||
import type { MapAssetInstance } from "@/hooks/world/useMapInstancingData";
|
||||
|
||||
export function PathSystem(): React.JSX.Element | null {
|
||||
if (!PATH_DEBUG_PREVIEW_ENABLED && !PATH_TILE_RENDER_ENABLED) {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { TERRAIN_COLORS, TERRAIN_TILE_SIZE } from "@/data/world/terrainConfig";
|
||||
|
||||
export const PATH_SURFACE_KEY = "chemin";
|
||||
export const PATH_DEBUG_PREVIEW_ENABLED = false;
|
||||
export const PATH_TILE_RENDER_ENABLED = false;
|
||||
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;
|
||||
@@ -4,14 +4,14 @@ import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight";
|
||||
import { useTerrainSurfaceData } from "@/hooks/world/useTerrainSurfaceData";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
import { sampleTerrainSurfaceAtXZ } from "@/utils/world/terrainSurfaceSampler";
|
||||
import type { MapAssetInstance } from "@/world/map-instancing/useMapInstancingData";
|
||||
import type { MapAssetInstance } from "@/hooks/world/useMapInstancingData";
|
||||
import {
|
||||
PATH_TILE_MAX_COUNT,
|
||||
PATH_SURFACE_KEY,
|
||||
PATH_TILE_ROTATION,
|
||||
PATH_TILE_SAMPLE_STEP,
|
||||
PATH_TILE_SCALE,
|
||||
} from "@/world/paths/pathConfig";
|
||||
} from "@/data/world/pathConfig";
|
||||
|
||||
function createSampleCenters(min: number, max: number, step: number): number[] {
|
||||
const start = Math.ceil(min / step) * step + step * 0.5;
|
||||
|
||||
@@ -36,6 +36,8 @@ interface VegetationWindUniforms {
|
||||
noiseScale: { value: number };
|
||||
}
|
||||
|
||||
const meshDataCache = new Map<string, MeshData[]>();
|
||||
|
||||
function updateVegetationWindUniforms(
|
||||
uniforms: VegetationWindUniforms,
|
||||
elapsedTime: number,
|
||||
@@ -242,9 +244,14 @@ export function InstancedVegetation({
|
||||
const windUniformsRef = useRef<VegetationWindUniforms[]>([]);
|
||||
|
||||
const meshDataList = useMemo(() => {
|
||||
const cached = meshDataCache.get(modelPath);
|
||||
if (cached) return cached;
|
||||
|
||||
optimizeGLTFSceneTextures(scene, maxAnisotropy);
|
||||
return extractMeshes(scene);
|
||||
}, [maxAnisotropy, scene]);
|
||||
const extracted = extractMeshes(scene);
|
||||
meshDataCache.set(modelPath, extracted);
|
||||
return extracted;
|
||||
}, [maxAnisotropy, modelPath, scene]);
|
||||
const groundedInstances = useMemo(
|
||||
() =>
|
||||
instances.map((instance) => {
|
||||
@@ -325,20 +332,8 @@ export function InstancedVegetation({
|
||||
(uniforms): uniforms is VegetationWindUniforms =>
|
||||
uniforms !== undefined,
|
||||
);
|
||||
|
||||
return () => {
|
||||
windUniformsRef.current = [];
|
||||
|
||||
for (const meshData of meshDataList) {
|
||||
meshData.geometry.dispose();
|
||||
if (Array.isArray(meshData.material)) {
|
||||
for (const mat of meshData.material) {
|
||||
mat.dispose();
|
||||
}
|
||||
} else {
|
||||
meshData.material.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [meshDataList]);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
VEGETATION_TYPE_KEYS,
|
||||
VEGETATION_TYPES,
|
||||
type VegetationType,
|
||||
} from "@/world/vegetation/vegetationConfig";
|
||||
} from "@/data/world/vegetationConfig";
|
||||
|
||||
interface VegetationChunk {
|
||||
key: string;
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { MapNode } from "@/types/editor/editor";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
import { loadMapSceneData } from "@/utils/map/loadMapSceneData";
|
||||
import { INSTANCED_MAP_EXCEPTIONS } from "@/world/vegetation/vegetationConfig";
|
||||
|
||||
export interface VegetationInstance {
|
||||
position: Vector3Tuple;
|
||||
rotation: Vector3Tuple;
|
||||
scale: Vector3Tuple;
|
||||
}
|
||||
|
||||
export interface InstancedMapEntry {
|
||||
modelPath: string;
|
||||
instances: VegetationInstance[];
|
||||
}
|
||||
|
||||
export type VegetationData = Map<string, InstancedMapEntry>;
|
||||
|
||||
function mapNodeToInstance(node: MapNode): VegetationInstance {
|
||||
return {
|
||||
position: node.position,
|
||||
rotation: node.rotation,
|
||||
scale: node.scale,
|
||||
};
|
||||
}
|
||||
|
||||
function extractVegetationData(
|
||||
mapNodes: MapNode[],
|
||||
models: Map<string, string>,
|
||||
): VegetationData {
|
||||
const data: VegetationData = new Map();
|
||||
|
||||
for (const node of mapNodes) {
|
||||
if (node.type !== "Object3D") continue;
|
||||
if (INSTANCED_MAP_EXCEPTIONS.has(node.name)) continue;
|
||||
|
||||
const modelPath = models.get(node.name);
|
||||
if (!modelPath) continue;
|
||||
|
||||
const entry = data.get(node.name);
|
||||
|
||||
if (entry) {
|
||||
entry.instances.push(mapNodeToInstance(node));
|
||||
} else {
|
||||
data.set(node.name, {
|
||||
modelPath,
|
||||
instances: [mapNodeToInstance(node)],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function useVegetationData(): {
|
||||
data: VegetationData | null;
|
||||
isLoading: boolean;
|
||||
} {
|
||||
const [data, setData] = useState<VegetationData | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
async function load() {
|
||||
const sceneData = await loadMapSceneData();
|
||||
|
||||
if (!cancelled && sceneData) {
|
||||
setData(extractVegetationData(sceneData.mapNodes, sceneData.models));
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { data, isLoading };
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
export const VEGETATION_TYPES = {
|
||||
buissons: {
|
||||
mapName: "buisson",
|
||||
modelPath: "/models/buisson/model.gltf",
|
||||
scaleMultiplier: 2,
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
windStrength: 0.08,
|
||||
enabled: true,
|
||||
},
|
||||
sapin: {
|
||||
mapName: "sapin",
|
||||
modelPath: "/models/sapin/model.gltf",
|
||||
scaleMultiplier: 5,
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
windStrength: 0.04,
|
||||
enabled: true,
|
||||
},
|
||||
arbre: {
|
||||
mapName: "arbre",
|
||||
modelPath: "/models/arbre/model.gltf",
|
||||
scaleMultiplier: 1,
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
windStrength: 0.06,
|
||||
enabled: true,
|
||||
},
|
||||
champdeble: {
|
||||
mapName: "champdeble",
|
||||
modelPath: "/models/champdeble/model.gltf",
|
||||
scaleMultiplier: 1,
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
windStrength: 0.18,
|
||||
enabled: true,
|
||||
},
|
||||
champdesoja: {
|
||||
mapName: "champdesoja",
|
||||
modelPath: "/models/champdesoja/model.gltf",
|
||||
scaleMultiplier: 1,
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
windStrength: 0.16,
|
||||
enabled: true,
|
||||
},
|
||||
champsdetournesol: {
|
||||
mapName: "champsdetournesol",
|
||||
modelPath: "/models/champsdetournesol/model.gltf",
|
||||
scaleMultiplier: 1,
|
||||
castShadow: true,
|
||||
receiveShadow: true,
|
||||
windStrength: 0.14,
|
||||
enabled: true,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const VEGETATION_TYPE_KEYS = [
|
||||
"buissons",
|
||||
"sapin",
|
||||
"arbre",
|
||||
"champdeble",
|
||||
"champdesoja",
|
||||
"champsdetournesol",
|
||||
] as const satisfies readonly (keyof typeof VEGETATION_TYPES)[];
|
||||
|
||||
export type VegetationType = (typeof VEGETATION_TYPE_KEYS)[number];
|
||||
|
||||
export const INSTANCED_MAP_EXCEPTIONS = new Set([
|
||||
"Scene",
|
||||
"blocking",
|
||||
"terrain",
|
||||
]);
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { FOG_CONFIG } from "@/data/world/fogConfig";
|
||||
import { getWindVector } from "@/data/world/windConfig";
|
||||
import { getWindVector } from "@/utils/world/windVector";
|
||||
import { WATER_SHADER_CONFIG } from "@/data/world/waterConfig";
|
||||
import type { WaterSurfaceConfig } from "@/data/world/waterConfig";
|
||||
import { useWind } from "@/hooks/world/useWind";
|
||||
|
||||
Reference in New Issue
Block a user