Compare commits
5 Commits
2c194cdd2e
...
4f1b3b4ff3
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f1b3b4ff3 | |||
| 627c8d4eb9 | |||
| 0b801888f0 | |||
| a180b89ee6 | |||
| 3e66e31117 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -105,6 +105,9 @@ function GraphicsPresetButton({
|
||||
const lodLabel = config.forceLodModels
|
||||
? "LOD forcé"
|
||||
: `HD ${config.lodHighDetailDistance}m`;
|
||||
const chunkLabel = config.chunkStreamingEnabled
|
||||
? formatChunkDistance(config.chunkLoadRadius)
|
||||
: "All";
|
||||
|
||||
return (
|
||||
<button
|
||||
@@ -115,8 +118,7 @@ function GraphicsPresetButton({
|
||||
>
|
||||
<span>{config.label}</span>
|
||||
<small>
|
||||
{formatChunkDistance(config.chunkLoadRadius)} · {lodLabel} ·{" "}
|
||||
{config.fogEnabled ? "Fog" : "Clear"}
|
||||
{chunkLabel} · {lodLabel} · {config.fogEnabled ? "Fog" : "Clear"}
|
||||
</small>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
export const GRAPHICS_PRESET_KEYS = ["low", "medium", "high", "ultra"] as const;
|
||||
export const GRAPHICS_PRESET_KEYS = [
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"ultra",
|
||||
"max",
|
||||
] as const;
|
||||
|
||||
export type GraphicsPreset = (typeof GRAPHICS_PRESET_KEYS)[number];
|
||||
|
||||
export interface GraphicsPresetConfig {
|
||||
chunkLoadRadius: number;
|
||||
chunkStreamingEnabled: boolean;
|
||||
chunkUnloadRadius: number;
|
||||
fogEnabled: boolean;
|
||||
forceLodModels: boolean;
|
||||
@@ -16,6 +23,7 @@ export const GRAPHICS_PRESETS = {
|
||||
label: "Basse",
|
||||
chunkLoadRadius: 10,
|
||||
chunkUnloadRadius: 18,
|
||||
chunkStreamingEnabled: true,
|
||||
fogEnabled: true,
|
||||
forceLodModels: true,
|
||||
lodHighDetailDistance: 0,
|
||||
@@ -24,25 +32,37 @@ export const GRAPHICS_PRESETS = {
|
||||
label: "Moyenne",
|
||||
chunkLoadRadius: 20,
|
||||
chunkUnloadRadius: 30,
|
||||
chunkStreamingEnabled: true,
|
||||
fogEnabled: true,
|
||||
forceLodModels: true,
|
||||
lodHighDetailDistance: 0,
|
||||
},
|
||||
high: {
|
||||
label: "High",
|
||||
chunkLoadRadius: 35,
|
||||
chunkUnloadRadius: 45,
|
||||
chunkLoadRadius: 30,
|
||||
chunkUnloadRadius: 40,
|
||||
chunkStreamingEnabled: true,
|
||||
fogEnabled: false,
|
||||
forceLodModels: false,
|
||||
lodHighDetailDistance: 10,
|
||||
lodHighDetailDistance: 20,
|
||||
},
|
||||
ultra: {
|
||||
label: "Ultra",
|
||||
chunkLoadRadius: 50,
|
||||
chunkUnloadRadius: 65,
|
||||
chunkStreamingEnabled: true,
|
||||
fogEnabled: false,
|
||||
forceLodModels: false,
|
||||
lodHighDetailDistance: 20,
|
||||
lodHighDetailDistance: 30,
|
||||
},
|
||||
max: {
|
||||
label: "Max",
|
||||
chunkLoadRadius: 50,
|
||||
chunkUnloadRadius: 65,
|
||||
chunkStreamingEnabled: false,
|
||||
fogEnabled: false,
|
||||
forceLodModels: false,
|
||||
lodHighDetailDistance: 50,
|
||||
},
|
||||
} as const satisfies Record<GraphicsPreset, GraphicsPresetConfig>;
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ export const MAP_LOD_MODEL_PATHS = {
|
||||
maison1: "/models/maison1-LOD/model.gltf",
|
||||
panneauaffichage: "/models/panneauaffichage-LOD/model.gltf",
|
||||
talkie: "/models/talkie-LOD/model.gltf",
|
||||
arbre: "/models/arbre-LOD/model.glb",
|
||||
buisson: "/models/buisson-LOD/model.glb",
|
||||
sapin: "/models/sapin-LOD/model.glb",
|
||||
} as const satisfies Record<string, string>;
|
||||
|
||||
export function getMapLodModelPath(modelName: string): string | null {
|
||||
|
||||
+1
-1
@@ -1544,7 +1544,7 @@ canvas {
|
||||
}
|
||||
|
||||
.game-settings-menu__choice-group--presets {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.game-settings-menu__choice-group button,
|
||||
|
||||
@@ -149,6 +149,7 @@ export function MapInstancingSystem({
|
||||
const streamingEnabled =
|
||||
streaming &&
|
||||
CHUNK_CONFIG.enabled &&
|
||||
graphicsPresetConfig.chunkStreamingEnabled &&
|
||||
sceneMode === "game" &&
|
||||
cameraMode === "player";
|
||||
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { Suspense, useMemo } from "react";
|
||||
import {
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig";
|
||||
import { selectMapModelPathByDistance } from "@/data/world/mapLodConfig";
|
||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import { useGraphicsPresetConfig } from "@/hooks/world/useGraphicsSettings";
|
||||
import {
|
||||
useGraphicsPreset,
|
||||
useGraphicsPresetConfig,
|
||||
} from "@/hooks/world/useGraphicsSettings";
|
||||
import { useVisibleWorldChunks } from "@/hooks/world/useVisibleWorldChunks";
|
||||
import {
|
||||
isMapModelVisible,
|
||||
@@ -18,6 +30,7 @@ import {
|
||||
} from "@/data/world/vegetationConfig";
|
||||
import { isInsideLaFabrikFootprint } from "@/data/world/laFabrikConfig";
|
||||
import { createWorldInstanceChunks } from "@/utils/world/chunkInstances";
|
||||
import type { GraphicsPreset } from "@/data/world/graphicsConfig";
|
||||
|
||||
interface VegetationSystemProps {
|
||||
onlyMapName?: string | null;
|
||||
@@ -70,12 +83,78 @@ function removeLaFabrikVegetation(
|
||||
});
|
||||
}
|
||||
|
||||
function areChunkModelPathsEqual(
|
||||
a: ReadonlyMap<string, string>,
|
||||
b: ReadonlyMap<string, string>,
|
||||
): boolean {
|
||||
return (
|
||||
a.size === b.size && [...a].every(([key, value]) => b.get(key) === value)
|
||||
);
|
||||
}
|
||||
|
||||
function useVegetationChunkModelPaths(
|
||||
chunks: readonly VegetationChunk[],
|
||||
preset: GraphicsPreset,
|
||||
): ReadonlyMap<string, string> {
|
||||
const camera = useThree((state) => state.camera);
|
||||
const lastUpdateRef = useRef(-CHUNK_CONFIG.updateInterval);
|
||||
const modelPathsRef = useRef<Map<string, string>>(new Map());
|
||||
const [modelPaths, setModelPaths] = useState<ReadonlyMap<string, string>>(
|
||||
() => new Map(),
|
||||
);
|
||||
|
||||
const updateModelPaths = useCallback(() => {
|
||||
const cameraX = camera.position.x;
|
||||
const cameraZ = camera.position.z;
|
||||
const next = new Map<string, string>();
|
||||
|
||||
for (const chunk of chunks) {
|
||||
let nearestDistance = Number.POSITIVE_INFINITY;
|
||||
for (const instance of chunk.instances) {
|
||||
const distance = Math.hypot(
|
||||
instance.position[0] - cameraX,
|
||||
instance.position[2] - cameraZ,
|
||||
);
|
||||
if (distance < nearestDistance) nearestDistance = distance;
|
||||
}
|
||||
|
||||
const modelPath = selectMapModelPathByDistance({
|
||||
distance: nearestDistance,
|
||||
modelName: VEGETATION_TYPES[chunk.type].mapName,
|
||||
modelPath: chunk.modelPath,
|
||||
preset,
|
||||
});
|
||||
next.set(chunk.key, modelPath);
|
||||
}
|
||||
|
||||
if (areChunkModelPathsEqual(next, modelPathsRef.current)) return;
|
||||
|
||||
modelPathsRef.current = next;
|
||||
setModelPaths(next);
|
||||
}, [camera, chunks, preset]);
|
||||
|
||||
useEffect(() => {
|
||||
updateModelPaths();
|
||||
}, [updateModelPaths]);
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
const now = clock.elapsedTime * 1000;
|
||||
if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return;
|
||||
lastUpdateRef.current = now;
|
||||
|
||||
updateModelPaths();
|
||||
});
|
||||
|
||||
return modelPaths;
|
||||
}
|
||||
|
||||
export function VegetationSystem({
|
||||
onlyMapName = null,
|
||||
streaming = true,
|
||||
}: VegetationSystemProps): React.JSX.Element | null {
|
||||
const cameraMode = useCameraMode();
|
||||
const sceneMode = useSceneMode();
|
||||
const graphicsPresetKey = useGraphicsPreset();
|
||||
const graphicsPreset = useGraphicsPresetConfig();
|
||||
const groups = useMapPerformanceStore((state) => state.groups);
|
||||
const models = useMapPerformanceStore((state) => state.models);
|
||||
@@ -83,6 +162,7 @@ export function VegetationSystem({
|
||||
const streamingEnabled =
|
||||
streaming &&
|
||||
CHUNK_CONFIG.enabled &&
|
||||
graphicsPreset.chunkStreamingEnabled &&
|
||||
sceneMode === "game" &&
|
||||
cameraMode === "player";
|
||||
|
||||
@@ -112,16 +192,23 @@ export function VegetationSystem({
|
||||
unloadRadius: graphicsPreset.chunkUnloadRadius,
|
||||
});
|
||||
|
||||
const chunkModelPaths = useVegetationChunkModelPaths(
|
||||
visibleChunks,
|
||||
graphicsPresetKey,
|
||||
);
|
||||
|
||||
if (isLoading || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<group name="vegetation-system">
|
||||
{visibleChunks.map((chunk) => (
|
||||
<Suspense key={chunk.key} fallback={null}>
|
||||
{visibleChunks.map((chunk) => {
|
||||
const modelPath = chunkModelPaths.get(chunk.key) ?? chunk.modelPath;
|
||||
return (
|
||||
<Suspense key={`${chunk.key}:${modelPath}`} fallback={null}>
|
||||
<InstancedVegetation
|
||||
modelPath={chunk.modelPath}
|
||||
modelPath={modelPath}
|
||||
instances={chunk.instances}
|
||||
scaleMultiplier={chunk.scaleMultiplier}
|
||||
castShadow={chunk.castShadow}
|
||||
@@ -130,7 +217,8 @@ export function VegetationSystem({
|
||||
rotationOffset={chunk.rotationOffset}
|
||||
/>
|
||||
</Suspense>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user