docs: expose map performance notes

This commit is contained in:
Tom Boullay
2026-05-25 00:08:29 +02:00
parent 44f9d68ef1
commit 50fa94b3ad
8 changed files with 87 additions and 13 deletions
+38
View File
@@ -137,6 +137,44 @@ Once the expensive model families are isolated, the real triangle fixes are:
Chunked instancing is especially important. A single `InstancedMesh` containing every bush has one global bounding sphere. If that bounding sphere is visible, Three.js may keep the whole batch visible. Splitting instances into grid chunks allows entire offscreen chunks to be skipped.
## Player-Only Vegetation Streaming
The first distance-streaming pass is intentionally limited to vegetation and crop instances:
- `arbre`
- `sapin`
- `buisson`
- `champdeble`
- `champdesoja`
- `champsdetournesol`
The behavior is configured in:
```txt
src/data/world/fogConfig.ts
```
Current runtime values:
```txt
chunkSize: 35
loadRadius: 45
unloadRadius: 58
updateInterval: 350ms
fog near: 34
fog far: 58
```
The streaming and fog are scoped to the production game scene with the player camera only:
```txt
sceneMode === "game" && cameraMode === "player"
```
This matters for debugging. In debug camera mode there is no fog and no distance streaming, so the developer can inspect the full map freely. In player mode, chunks mount and unmount around the camera to reduce visible triangle count while fog hides vegetation pop-in.
Chunk cleanup is handled through React unmounting. `VegetationSystem` removes chunks from the tree, and `InstancedVegetation` removes its `THREE.InstancedMesh` objects from the group while disposing the locally created merged geometries/material clones in its own cleanup path.
## Current Code-Side Optimization
Repeated static assets are configured in:
+11 -5
View File
@@ -80,6 +80,12 @@ export const docGroups: DocGroup[] = [
subtitle: "Step into Three.js internals",
meta: "11",
},
{
path: "/docs/map-performance",
title: "Map Performance",
subtitle: "Draw calls, triangles, and streaming",
meta: "12",
},
],
},
{
@@ -89,25 +95,25 @@ export const docGroups: DocGroup[] = [
path: "/docs/features",
title: "Features",
subtitle: "Implemented scope",
meta: "12",
meta: "13",
},
{
path: "/docs/main-feature",
title: "Main Feature",
subtitle: "Repair-game prototype",
meta: "13",
meta: "14",
},
{
path: "/docs/editor",
title: "Editor User Guide",
subtitle: "Editing workflow",
meta: "14",
meta: "15",
},
{
path: "/docs/animation",
title: "Animation & 3D Model System",
subtitle: "Components and usage",
meta: "15",
meta: "16",
},
],
},
@@ -118,7 +124,7 @@ export const docGroups: DocGroup[] = [
path: "/docs/code-review",
title: "Code Review Prep",
subtitle: "Presentation support",
meta: "16",
meta: "17",
},
],
},
+5 -5
View File
@@ -3,15 +3,15 @@ import { TERRAIN_COLORS } from "@/data/world/terrainConfig";
export const FOG_CONFIG = {
enabled: true,
color: "#c8dbbe",
near: 48,
far: 78,
near: 34,
far: 58,
};
export const CHUNK_CONFIG = {
enabled: true,
chunkSize: 45,
loadRadius: 60,
unloadRadius: 75,
chunkSize: 35,
loadRadius: 45,
unloadRadius: 58,
updateInterval: 350,
};
+13
View File
@@ -0,0 +1,13 @@
import mapPerformance from "../../../../docs/technical/map-performance.md?raw";
import { DocsDocument } from "@/components/docs/DocsDocument";
export function DocsMapPerformancePage(): React.JSX.Element {
return (
<DocsDocument
content={mapPerformance}
frContent={mapPerformance}
meta="12"
title="Map Performance"
/>
);
}
+2
View File
@@ -17,6 +17,7 @@ import {
DocsInteractionRoute,
DocsLayoutRoute,
DocsMainFeatureRoute,
DocsMapPerformanceRoute,
DocsMissionFlowRoute,
DocsReadmeRoute,
DocsRepairGameRoute,
@@ -62,6 +63,7 @@ const docsChildRoutes = [
{ path: "hand-tracking", component: DocsHandTrackingRoute },
{ path: "zustand", component: DocsZustandRoute },
{ path: "three-debugging", component: DocsThreeDebuggingRoute },
{ path: "map-performance", component: DocsMapPerformanceRoute },
{ path: "features", component: DocsFeaturesRoute },
{ path: "main-feature", component: DocsMainFeatureRoute },
{ path: "editor", component: DocsEditorRoute },
+7
View File
@@ -99,6 +99,10 @@ const LazyDocsThreeDebuggingPage = lazyNamed(
() => import("@/pages/docs/three-debugging/page"),
"DocsThreeDebuggingPage",
);
const LazyDocsMapPerformancePage = lazyNamed(
() => import("@/pages/docs/map-performance/page"),
"DocsMapPerformancePage",
);
export const DocsLayoutRoute = createDocsRoute(LazyDocsLayout);
export const DocsReadmeRoute = createDocsRoute(LazyDocsReadmePage);
@@ -124,3 +128,6 @@ export const DocsMissionFlowRoute = createDocsRoute(LazyDocsMissionFlowPage);
export const DocsThreeDebuggingRoute = createDocsRoute(
LazyDocsThreeDebuggingPage,
);
export const DocsMapPerformanceRoute = createDocsRoute(
LazyDocsMapPerformancePage,
);
+3 -1
View File
@@ -7,6 +7,7 @@ import {
PHYSICS_SCENE_BACKGROUND_COLOR,
} from "@/data/world/environmentConfig";
import { FOG_CONFIG } from "@/data/world/fogConfig";
import { useCameraMode } from "@/hooks/debug/useCameraMode";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
import {
isMapModelVisible,
@@ -15,6 +16,7 @@ import {
import { SkyModel } from "@/components/three/world/SkyModel";
export function Environment(): React.JSX.Element {
const cameraMode = useCameraMode();
const sceneMode = useSceneMode();
const groups = useMapPerformanceStore((state) => state.groups);
const models = useMapPerformanceStore((state) => state.models);
@@ -28,7 +30,7 @@ export function Environment(): React.JSX.Element {
return (
<>
{FOG_CONFIG.enabled ? (
{FOG_CONFIG.enabled && sceneMode === "game" && cameraMode === "player" ? (
<fog
attach="fog"
args={[FOG_CONFIG.color, FOG_CONFIG.near, FOG_CONFIG.far]}
+8 -2
View File
@@ -1,6 +1,8 @@
import { Suspense, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { CHUNK_CONFIG } from "@/data/world/fogConfig";
import { useCameraMode } from "@/hooks/debug/useCameraMode";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
import {
isMapModelVisible,
useMapPerformanceStore,
@@ -75,6 +77,8 @@ function createVegetationChunks(
export function VegetationSystem(): React.JSX.Element | null {
const camera = useThree((state) => state.camera);
const cameraMode = useCameraMode();
const sceneMode = useSceneMode();
const groups = useMapPerformanceStore((state) => state.groups);
const models = useMapPerformanceStore((state) => state.models);
const { data, isLoading } = useVegetationData();
@@ -82,6 +86,8 @@ export function VegetationSystem(): React.JSX.Element | null {
const [activeChunkKeys, setActiveChunkKeys] = useState<Set<string>>(
() => new Set(),
);
const streamingEnabled =
CHUNK_CONFIG.enabled && sceneMode === "game" && cameraMode === "player";
const chunks = useMemo(() => {
if (!data) return [];
@@ -98,7 +104,7 @@ export function VegetationSystem(): React.JSX.Element | null {
}, [data, groups, models]);
useFrame(({ clock }) => {
if (!CHUNK_CONFIG.enabled) return;
if (!streamingEnabled) return;
const now = clock.elapsedTime * 1000;
if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return;
@@ -137,7 +143,7 @@ export function VegetationSystem(): React.JSX.Element | null {
return null;
}
const visibleChunks = CHUNK_CONFIG.enabled
const visibleChunks = streamingEnabled
? chunks.filter((chunk) => activeChunkKeys.has(chunk.key))
: chunks;