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
|
const lodLabel = config.forceLodModels
|
||||||
? "LOD forcé"
|
? "LOD forcé"
|
||||||
: `HD ${config.lodHighDetailDistance}m`;
|
: `HD ${config.lodHighDetailDistance}m`;
|
||||||
|
const chunkLabel = config.chunkStreamingEnabled
|
||||||
|
? formatChunkDistance(config.chunkLoadRadius)
|
||||||
|
: "All";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -115,8 +118,7 @@ function GraphicsPresetButton({
|
|||||||
>
|
>
|
||||||
<span>{config.label}</span>
|
<span>{config.label}</span>
|
||||||
<small>
|
<small>
|
||||||
{formatChunkDistance(config.chunkLoadRadius)} · {lodLabel} ·{" "}
|
{chunkLabel} · {lodLabel} · {config.fogEnabled ? "Fog" : "Clear"}
|
||||||
{config.fogEnabled ? "Fog" : "Clear"}
|
|
||||||
</small>
|
</small>
|
||||||
</button>
|
</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 type GraphicsPreset = (typeof GRAPHICS_PRESET_KEYS)[number];
|
||||||
|
|
||||||
export interface GraphicsPresetConfig {
|
export interface GraphicsPresetConfig {
|
||||||
chunkLoadRadius: number;
|
chunkLoadRadius: number;
|
||||||
|
chunkStreamingEnabled: boolean;
|
||||||
chunkUnloadRadius: number;
|
chunkUnloadRadius: number;
|
||||||
fogEnabled: boolean;
|
fogEnabled: boolean;
|
||||||
forceLodModels: boolean;
|
forceLodModels: boolean;
|
||||||
@@ -16,6 +23,7 @@ export const GRAPHICS_PRESETS = {
|
|||||||
label: "Basse",
|
label: "Basse",
|
||||||
chunkLoadRadius: 10,
|
chunkLoadRadius: 10,
|
||||||
chunkUnloadRadius: 18,
|
chunkUnloadRadius: 18,
|
||||||
|
chunkStreamingEnabled: true,
|
||||||
fogEnabled: true,
|
fogEnabled: true,
|
||||||
forceLodModels: true,
|
forceLodModels: true,
|
||||||
lodHighDetailDistance: 0,
|
lodHighDetailDistance: 0,
|
||||||
@@ -24,25 +32,37 @@ export const GRAPHICS_PRESETS = {
|
|||||||
label: "Moyenne",
|
label: "Moyenne",
|
||||||
chunkLoadRadius: 20,
|
chunkLoadRadius: 20,
|
||||||
chunkUnloadRadius: 30,
|
chunkUnloadRadius: 30,
|
||||||
|
chunkStreamingEnabled: true,
|
||||||
fogEnabled: true,
|
fogEnabled: true,
|
||||||
forceLodModels: true,
|
forceLodModels: true,
|
||||||
lodHighDetailDistance: 0,
|
lodHighDetailDistance: 0,
|
||||||
},
|
},
|
||||||
high: {
|
high: {
|
||||||
label: "High",
|
label: "High",
|
||||||
chunkLoadRadius: 35,
|
chunkLoadRadius: 30,
|
||||||
chunkUnloadRadius: 45,
|
chunkUnloadRadius: 40,
|
||||||
|
chunkStreamingEnabled: true,
|
||||||
fogEnabled: false,
|
fogEnabled: false,
|
||||||
forceLodModels: false,
|
forceLodModels: false,
|
||||||
lodHighDetailDistance: 10,
|
lodHighDetailDistance: 20,
|
||||||
},
|
},
|
||||||
ultra: {
|
ultra: {
|
||||||
label: "Ultra",
|
label: "Ultra",
|
||||||
chunkLoadRadius: 50,
|
chunkLoadRadius: 50,
|
||||||
chunkUnloadRadius: 65,
|
chunkUnloadRadius: 65,
|
||||||
|
chunkStreamingEnabled: true,
|
||||||
fogEnabled: false,
|
fogEnabled: false,
|
||||||
forceLodModels: 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>;
|
} as const satisfies Record<GraphicsPreset, GraphicsPresetConfig>;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ export const MAP_LOD_MODEL_PATHS = {
|
|||||||
maison1: "/models/maison1-LOD/model.gltf",
|
maison1: "/models/maison1-LOD/model.gltf",
|
||||||
panneauaffichage: "/models/panneauaffichage-LOD/model.gltf",
|
panneauaffichage: "/models/panneauaffichage-LOD/model.gltf",
|
||||||
talkie: "/models/talkie-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>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
export function getMapLodModelPath(modelName: string): string | null {
|
export function getMapLodModelPath(modelName: string): string | null {
|
||||||
|
|||||||
+1
-1
@@ -1544,7 +1544,7 @@ canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.game-settings-menu__choice-group--presets {
|
.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,
|
.game-settings-menu__choice-group button,
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export function MapInstancingSystem({
|
|||||||
const streamingEnabled =
|
const streamingEnabled =
|
||||||
streaming &&
|
streaming &&
|
||||||
CHUNK_CONFIG.enabled &&
|
CHUNK_CONFIG.enabled &&
|
||||||
|
graphicsPresetConfig.chunkStreamingEnabled &&
|
||||||
sceneMode === "game" &&
|
sceneMode === "game" &&
|
||||||
cameraMode === "player";
|
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 { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig";
|
||||||
|
import { selectMapModelPathByDistance } from "@/data/world/mapLodConfig";
|
||||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
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 { useVisibleWorldChunks } from "@/hooks/world/useVisibleWorldChunks";
|
||||||
import {
|
import {
|
||||||
isMapModelVisible,
|
isMapModelVisible,
|
||||||
@@ -18,6 +30,7 @@ import {
|
|||||||
} from "@/data/world/vegetationConfig";
|
} from "@/data/world/vegetationConfig";
|
||||||
import { isInsideLaFabrikFootprint } from "@/data/world/laFabrikConfig";
|
import { isInsideLaFabrikFootprint } from "@/data/world/laFabrikConfig";
|
||||||
import { createWorldInstanceChunks } from "@/utils/world/chunkInstances";
|
import { createWorldInstanceChunks } from "@/utils/world/chunkInstances";
|
||||||
|
import type { GraphicsPreset } from "@/data/world/graphicsConfig";
|
||||||
|
|
||||||
interface VegetationSystemProps {
|
interface VegetationSystemProps {
|
||||||
onlyMapName?: string | null;
|
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({
|
export function VegetationSystem({
|
||||||
onlyMapName = null,
|
onlyMapName = null,
|
||||||
streaming = true,
|
streaming = true,
|
||||||
}: VegetationSystemProps): React.JSX.Element | null {
|
}: VegetationSystemProps): React.JSX.Element | null {
|
||||||
const cameraMode = useCameraMode();
|
const cameraMode = useCameraMode();
|
||||||
const sceneMode = useSceneMode();
|
const sceneMode = useSceneMode();
|
||||||
|
const graphicsPresetKey = useGraphicsPreset();
|
||||||
const graphicsPreset = useGraphicsPresetConfig();
|
const graphicsPreset = useGraphicsPresetConfig();
|
||||||
const groups = useMapPerformanceStore((state) => state.groups);
|
const groups = useMapPerformanceStore((state) => state.groups);
|
||||||
const models = useMapPerformanceStore((state) => state.models);
|
const models = useMapPerformanceStore((state) => state.models);
|
||||||
@@ -83,6 +162,7 @@ export function VegetationSystem({
|
|||||||
const streamingEnabled =
|
const streamingEnabled =
|
||||||
streaming &&
|
streaming &&
|
||||||
CHUNK_CONFIG.enabled &&
|
CHUNK_CONFIG.enabled &&
|
||||||
|
graphicsPreset.chunkStreamingEnabled &&
|
||||||
sceneMode === "game" &&
|
sceneMode === "game" &&
|
||||||
cameraMode === "player";
|
cameraMode === "player";
|
||||||
|
|
||||||
@@ -112,16 +192,23 @@ export function VegetationSystem({
|
|||||||
unloadRadius: graphicsPreset.chunkUnloadRadius,
|
unloadRadius: graphicsPreset.chunkUnloadRadius,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const chunkModelPaths = useVegetationChunkModelPaths(
|
||||||
|
visibleChunks,
|
||||||
|
graphicsPresetKey,
|
||||||
|
);
|
||||||
|
|
||||||
if (isLoading || !data) {
|
if (isLoading || !data) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group name="vegetation-system">
|
<group name="vegetation-system">
|
||||||
{visibleChunks.map((chunk) => (
|
{visibleChunks.map((chunk) => {
|
||||||
<Suspense key={chunk.key} fallback={null}>
|
const modelPath = chunkModelPaths.get(chunk.key) ?? chunk.modelPath;
|
||||||
|
return (
|
||||||
|
<Suspense key={`${chunk.key}:${modelPath}`} fallback={null}>
|
||||||
<InstancedVegetation
|
<InstancedVegetation
|
||||||
modelPath={chunk.modelPath}
|
modelPath={modelPath}
|
||||||
instances={chunk.instances}
|
instances={chunk.instances}
|
||||||
scaleMultiplier={chunk.scaleMultiplier}
|
scaleMultiplier={chunk.scaleMultiplier}
|
||||||
castShadow={chunk.castShadow}
|
castShadow={chunk.castShadow}
|
||||||
@@ -130,7 +217,8 @@ export function VegetationSystem({
|
|||||||
rotationOffset={chunk.rotationOffset}
|
rotationOffset={chunk.rotationOffset}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user