Files
La-Fabrik/src/world/vegetation/VegetationSystem.tsx
T
2026-05-28 15:48:33 +02:00

145 lines
4.2 KiB
TypeScript

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<string, VegetationInstance[]>();
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 (
<group name="vegetation-system">
{visibleChunks.map((chunk) => (
<Suspense key={chunk.key} fallback={null}>
<InstancedVegetation
modelPath={chunk.modelPath}
instances={chunk.instances}
scaleMultiplier={chunk.scaleMultiplier}
castShadow={chunk.castShadow}
receiveShadow={chunk.receiveShadow}
windStrength={chunk.windStrength}
rotationOffset={chunk.rotationOffset}
/>
</Suspense>
))}
</group>
);
}