import { Suspense, useMemo } from "react"; import { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; import { useVisibleWorldChunks } from "@/hooks/world/useVisibleWorldChunks"; import { isMapModelVisible, useMapPerformanceStore, } from "@/managers/stores/useMapPerformanceStore"; import { InstancedVegetation } from "@/world/vegetation/InstancedVegetation"; import { useVegetationData } from "@/hooks/world/useVegetationData"; import type { VegetationInstance } from "@/types/map/mapScene"; import { VEGETATION_TYPE_KEYS, VEGETATION_TYPES, type VegetationType, } from "@/data/world/vegetationConfig"; interface VegetationSystemProps { onlyModelName?: string | null; streaming?: boolean; } interface VegetationChunk { key: string; type: VegetationType; modelPath: string; scaleMultiplier: number; castShadow: boolean; receiveShadow: boolean; windStrength: number; rotationOffset: readonly [number, number, number]; centerX: number; centerZ: number; instances: VegetationInstance[]; } function getChunkKey(instance: VegetationInstance): string { const [x, , z] = instance.position; const chunkX = Math.floor(x / CHUNK_CONFIG.chunkSize); const chunkZ = Math.floor(z / CHUNK_CONFIG.chunkSize); return `${chunkX}:${chunkZ}`; } function createVegetationChunks( type: VegetationType, instances: VegetationInstance[], ): VegetationChunk[] { const config = VEGETATION_TYPES[type]; const chunks = new Map(); for (const instance of instances) { const key = getChunkKey(instance); const chunk = chunks.get(key); if (chunk) { chunk.push(instance); } else { chunks.set(key, [instance]); } } return [...chunks.entries()].map(([chunkKey, chunkInstances]) => { const center = chunkInstances.reduce( (sum, instance) => { sum.x += instance.position[0]; sum.z += instance.position[2]; return sum; }, { x: 0, z: 0 }, ); return { key: `${type}:${chunkKey}`, type, modelPath: config.modelPath, scaleMultiplier: config.scaleMultiplier, castShadow: config.castShadow, receiveShadow: config.receiveShadow, windStrength: config.windStrength, rotationOffset: config.rotationOffset, centerX: center.x / chunkInstances.length, centerZ: center.z / chunkInstances.length, instances: chunkInstances, }; }); } export function VegetationSystem({ onlyModelName = null, streaming = true, }: VegetationSystemProps): React.JSX.Element | null { const cameraMode = useCameraMode(); const sceneMode = useSceneMode(); const groups = useMapPerformanceStore((state) => state.groups); const models = useMapPerformanceStore((state) => state.models); const { data, isLoading } = useVegetationData(); const streamingEnabled = streaming && CHUNK_CONFIG.enabled && sceneMode === "game" && cameraMode === "player"; const chunks = useMemo(() => { if (!data) return []; return VEGETATION_TYPE_KEYS.flatMap((type) => { const config = VEGETATION_TYPES[type]; if (onlyModelName && config.mapName !== onlyModelName) return []; if (!config.enabled) return []; if (!isMapModelVisible(config.mapName, { groups, models })) return []; const entry = data.get(config.mapName); if (!entry || entry.instances.length === 0) return []; return createVegetationChunks(type, entry.instances); }); }, [data, groups, models, onlyModelName]); const visibleChunks = useVisibleWorldChunks(chunks, streamingEnabled); if (isLoading || !data) { return null; } return ( {visibleChunks.map((chunk) => ( ))} ); }