Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 68253fae41 | |||
| 2dabb73d3d | |||
| 4f1b3b4ff3 | |||
| 627c8d4eb9 | |||
| 0b801888f0 | |||
| a180b89ee6 | |||
| 3e66e31117 | |||
| 2c194cdd2e | |||
| feaf502e44 |
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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export function MissionNotification({
|
|||||||
}: MissionNotificationProps): React.JSX.Element {
|
}: MissionNotificationProps): React.JSX.Element {
|
||||||
const src =
|
const src =
|
||||||
imagePath ?? (mission ? MISSION_NOTIFICATION_IMAGE_PATHS[mission] : "");
|
imagePath ?? (mission ? MISSION_NOTIFICATION_IMAGE_PATHS[mission] : "");
|
||||||
|
const isVideo = src.toLowerCase().endsWith(".webm");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -22,11 +23,24 @@ export function MissionNotification({
|
|||||||
>
|
>
|
||||||
<div className="mission-notification__glow" />
|
<div className="mission-notification__glow" />
|
||||||
<span className="mission-notification__image-wrap">
|
<span className="mission-notification__image-wrap">
|
||||||
<img
|
{isVideo ? (
|
||||||
className="mission-notification__image"
|
<video
|
||||||
src={src}
|
className="mission-notification__image"
|
||||||
alt="Nouvel objectif de mission"
|
src={src}
|
||||||
/>
|
aria-label="Nouvel objectif de mission"
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
preload="auto"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
className="mission-notification__image"
|
||||||
|
src={src}
|
||||||
|
alt="Nouvel objectif de mission"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ export const EBIKE_WORLD_SCALE = 0.35;
|
|||||||
export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 15;
|
export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 15;
|
||||||
export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250;
|
export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250;
|
||||||
|
|
||||||
export const EBIKE_MAX_SPEED = 3;
|
|
||||||
export const EBIKE_ACCELERATION_DURATION_MS = 2000;
|
export const EBIKE_ACCELERATION_DURATION_MS = 2000;
|
||||||
export const EBIKE_DECELERATION_DURATION_MS = 2000;
|
export const EBIKE_DECELERATION_DURATION_MS = 2000;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const INTRO_MISSION_NOTIFICATION_IMAGE_PATH =
|
|||||||
|
|
||||||
export const MISSION_NOTIFICATION_IMAGE_PATHS: Record<RepairMissionId, string> =
|
export const MISSION_NOTIFICATION_IMAGE_PATHS: Record<RepairMissionId, string> =
|
||||||
{
|
{
|
||||||
ebike: "/assets/world/UI/ebike-mission-notification.png",
|
ebike: "/assets/world/UI/ebike-mission-notification.webm",
|
||||||
pylon: "/assets/world/UI/pylon-mission-notification.png",
|
pylon: "/assets/world/UI/pylon-mission-notification.webm",
|
||||||
farm: "/assets/world/UI/farm-mission-notification.png",
|
farm: "/assets/world/UI/farm-mission-notification.webm",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
|||||||
"Repair the damaged cooling module before relaunching the bike",
|
"Repair the damaged cooling module before relaunching the bike",
|
||||||
modelPath: "/models/ebike/model.gltf",
|
modelPath: "/models/ebike/model.gltf",
|
||||||
modelScale: 0.3,
|
modelScale: 0.3,
|
||||||
stageUiPath: "/assets/world/UI/ebike.webm",
|
stageUiPath: "/assets/world/UI/ebike-mission-notification.webm",
|
||||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||||
case: DEFAULT_REPAIR_CASE,
|
case: DEFAULT_REPAIR_CASE,
|
||||||
@@ -59,7 +59,7 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
|||||||
description:
|
description:
|
||||||
"Restore the pylon lamp relay and damaged panel before reconnecting the grid",
|
"Restore the pylon lamp relay and damaged panel before reconnecting the grid",
|
||||||
modelPath: "/models/pylone/model.gltf",
|
modelPath: "/models/pylone/model.gltf",
|
||||||
stageUiPath: "/assets/world/UI/centrale.webm",
|
stageUiPath: "/assets/world/UI/pylon-mission-notification.webm",
|
||||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||||
case: DEFAULT_REPAIR_CASE,
|
case: DEFAULT_REPAIR_CASE,
|
||||||
@@ -104,7 +104,7 @@ export const REPAIR_MISSIONS: Record<RepairMissionId, RepairMissionConfig> = {
|
|||||||
description:
|
description:
|
||||||
"Stabilize the irrigation loop and humidity sensor before restarting the farm",
|
"Stabilize the irrigation loop and humidity sensor before restarting the farm",
|
||||||
modelPath: "/models/fermeverticale/model.gltf",
|
modelPath: "/models/fermeverticale/model.gltf",
|
||||||
stageUiPath: "/assets/world/UI/laferme.webm",
|
stageUiPath: "/assets/world/UI/farm-mission-notification.webm",
|
||||||
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
interactUiPath: REPAIR_INTERACT_UI_PATH,
|
||||||
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
brokenUiPath: REPAIR_BROKEN_UI_PATH,
|
||||||
case: DEFAULT_REPAIR_CASE,
|
case: DEFAULT_REPAIR_CASE,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const PLAYER_EYE_HEIGHT = 1.75;
|
|||||||
export const PLAYER_CAPSULE_RADIUS = 0.35;
|
export const PLAYER_CAPSULE_RADIUS = 0.35;
|
||||||
|
|
||||||
export const PLAYER_WALK_SPEED = 5;
|
export const PLAYER_WALK_SPEED = 5;
|
||||||
export const PLAYER_EBIKE_SPEED = 30;
|
export const PLAYER_EBIKE_SPEED = 20;
|
||||||
export const PLAYER_AIR_CONTROL_FACTOR = 0.35;
|
export const PLAYER_AIR_CONTROL_FACTOR = 0.35;
|
||||||
export const PLAYER_JUMP_SPEED = 9;
|
export const PLAYER_JUMP_SPEED = 9;
|
||||||
export const PLAYER_GRAVITY = 30;
|
export const PLAYER_GRAVITY = 30;
|
||||||
|
|||||||
@@ -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>;
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ const SUN_LIGHT_COLOR = "#ffe2bf";
|
|||||||
|
|
||||||
export const LIGHTING_DEFAULTS = {
|
export const LIGHTING_DEFAULTS = {
|
||||||
ambientColor: AMBIENT_LIGHT_COLOR,
|
ambientColor: AMBIENT_LIGHT_COLOR,
|
||||||
ambientIntensity: 0.9,
|
ambientIntensity: 0.7,
|
||||||
sunColor: SUN_LIGHT_COLOR,
|
sunColor: SUN_LIGHT_COLOR,
|
||||||
sunIntensity: 2.2,
|
sunIntensity: 1.9,
|
||||||
sunX: 70,
|
sunX: 70,
|
||||||
sunY: 45,
|
sunY: 45,
|
||||||
sunZ: 35,
|
sunZ: 35,
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -22,6 +25,20 @@ export function getMapLodModelPath(modelName: string): string | null {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const MAP_LOD_SCALE_MULTIPLIERS = {
|
||||||
|
sapin: 0.35,
|
||||||
|
buisson: 0.7,
|
||||||
|
} as const satisfies Partial<Record<keyof typeof MAP_LOD_MODEL_PATHS, number>>;
|
||||||
|
|
||||||
|
export function getMapLodScaleMultiplier(modelName: string): number {
|
||||||
|
return (
|
||||||
|
MAP_LOD_SCALE_MULTIPLIERS[
|
||||||
|
modelName as keyof typeof MAP_LOD_SCALE_MULTIPLIERS
|
||||||
|
] ?? 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function selectMapModelPathByDistance({
|
export function selectMapModelPathByDistance({
|
||||||
distance,
|
distance,
|
||||||
modelName,
|
modelName,
|
||||||
|
|||||||
+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";
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
EBIKE_ACCELERATION_DURATION_MS,
|
EBIKE_ACCELERATION_DURATION_MS,
|
||||||
EBIKE_CAMERA_TRANSFORM,
|
EBIKE_CAMERA_TRANSFORM,
|
||||||
EBIKE_DECELERATION_DURATION_MS,
|
EBIKE_DECELERATION_DURATION_MS,
|
||||||
EBIKE_MAX_SPEED,
|
|
||||||
} from "@/data/ebike/ebikeConfig";
|
} from "@/data/ebike/ebikeConfig";
|
||||||
|
|
||||||
/** Global window properties used for ebike communication */
|
/** Global window properties used for ebike communication */
|
||||||
@@ -415,7 +414,7 @@ export function PlayerController({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const movementSpeed = isEbikeMounted
|
const movementSpeed = isEbikeMounted
|
||||||
? EBIKE_MAX_SPEED * ebikeSpeedFactor.current
|
? currentSpeed * ebikeSpeedFactor.current
|
||||||
: currentSpeed;
|
: currentSpeed;
|
||||||
const accel = onFloor.current
|
const accel = onFloor.current
|
||||||
? movementSpeed
|
? movementSpeed
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
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 {
|
||||||
|
getMapLodModelPath,
|
||||||
|
getMapLodScaleMultiplier,
|
||||||
|
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 +34,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 +87,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 +166,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,25 +196,38 @@ 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;
|
||||||
<InstancedVegetation
|
const mapName = VEGETATION_TYPES[chunk.type].mapName;
|
||||||
modelPath={chunk.modelPath}
|
const isLod = modelPath === getMapLodModelPath(mapName);
|
||||||
instances={chunk.instances}
|
const scaleMultiplier =
|
||||||
scaleMultiplier={chunk.scaleMultiplier}
|
chunk.scaleMultiplier *
|
||||||
castShadow={chunk.castShadow}
|
(isLod ? getMapLodScaleMultiplier(mapName) : 1);
|
||||||
receiveShadow={chunk.receiveShadow}
|
return (
|
||||||
windStrength={chunk.windStrength}
|
<Suspense key={`${chunk.key}:${modelPath}`} fallback={null}>
|
||||||
rotationOffset={chunk.rotationOffset}
|
<InstancedVegetation
|
||||||
/>
|
modelPath={modelPath}
|
||||||
</Suspense>
|
instances={chunk.instances}
|
||||||
))}
|
scaleMultiplier={scaleMultiplier}
|
||||||
|
castShadow={chunk.castShadow}
|
||||||
|
receiveShadow={chunk.receiveShadow}
|
||||||
|
windStrength={chunk.windStrength}
|
||||||
|
rotationOffset={chunk.rotationOffset}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user