145 lines
4.2 KiB
TypeScript
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>
|
|
);
|
|
}
|