docs: expose map performance notes
This commit is contained in:
@@ -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.
|
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
|
## Current Code-Side Optimization
|
||||||
|
|
||||||
Repeated static assets are configured in:
|
Repeated static assets are configured in:
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ export const docGroups: DocGroup[] = [
|
|||||||
subtitle: "Step into Three.js internals",
|
subtitle: "Step into Three.js internals",
|
||||||
meta: "11",
|
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",
|
path: "/docs/features",
|
||||||
title: "Features",
|
title: "Features",
|
||||||
subtitle: "Implemented scope",
|
subtitle: "Implemented scope",
|
||||||
meta: "12",
|
meta: "13",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/docs/main-feature",
|
path: "/docs/main-feature",
|
||||||
title: "Main Feature",
|
title: "Main Feature",
|
||||||
subtitle: "Repair-game prototype",
|
subtitle: "Repair-game prototype",
|
||||||
meta: "13",
|
meta: "14",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/docs/editor",
|
path: "/docs/editor",
|
||||||
title: "Editor User Guide",
|
title: "Editor User Guide",
|
||||||
subtitle: "Editing workflow",
|
subtitle: "Editing workflow",
|
||||||
meta: "14",
|
meta: "15",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/docs/animation",
|
path: "/docs/animation",
|
||||||
title: "Animation & 3D Model System",
|
title: "Animation & 3D Model System",
|
||||||
subtitle: "Components and usage",
|
subtitle: "Components and usage",
|
||||||
meta: "15",
|
meta: "16",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -118,7 +124,7 @@ export const docGroups: DocGroup[] = [
|
|||||||
path: "/docs/code-review",
|
path: "/docs/code-review",
|
||||||
title: "Code Review Prep",
|
title: "Code Review Prep",
|
||||||
subtitle: "Presentation support",
|
subtitle: "Presentation support",
|
||||||
meta: "16",
|
meta: "17",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import { TERRAIN_COLORS } from "@/data/world/terrainConfig";
|
|||||||
export const FOG_CONFIG = {
|
export const FOG_CONFIG = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
color: "#c8dbbe",
|
color: "#c8dbbe",
|
||||||
near: 48,
|
near: 34,
|
||||||
far: 78,
|
far: 58,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CHUNK_CONFIG = {
|
export const CHUNK_CONFIG = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
chunkSize: 45,
|
chunkSize: 35,
|
||||||
loadRadius: 60,
|
loadRadius: 45,
|
||||||
unloadRadius: 75,
|
unloadRadius: 58,
|
||||||
updateInterval: 350,
|
updateInterval: 350,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
DocsInteractionRoute,
|
DocsInteractionRoute,
|
||||||
DocsLayoutRoute,
|
DocsLayoutRoute,
|
||||||
DocsMainFeatureRoute,
|
DocsMainFeatureRoute,
|
||||||
|
DocsMapPerformanceRoute,
|
||||||
DocsMissionFlowRoute,
|
DocsMissionFlowRoute,
|
||||||
DocsReadmeRoute,
|
DocsReadmeRoute,
|
||||||
DocsRepairGameRoute,
|
DocsRepairGameRoute,
|
||||||
@@ -62,6 +63,7 @@ const docsChildRoutes = [
|
|||||||
{ path: "hand-tracking", component: DocsHandTrackingRoute },
|
{ path: "hand-tracking", component: DocsHandTrackingRoute },
|
||||||
{ path: "zustand", component: DocsZustandRoute },
|
{ path: "zustand", component: DocsZustandRoute },
|
||||||
{ path: "three-debugging", component: DocsThreeDebuggingRoute },
|
{ path: "three-debugging", component: DocsThreeDebuggingRoute },
|
||||||
|
{ path: "map-performance", component: DocsMapPerformanceRoute },
|
||||||
{ path: "features", component: DocsFeaturesRoute },
|
{ path: "features", component: DocsFeaturesRoute },
|
||||||
{ path: "main-feature", component: DocsMainFeatureRoute },
|
{ path: "main-feature", component: DocsMainFeatureRoute },
|
||||||
{ path: "editor", component: DocsEditorRoute },
|
{ path: "editor", component: DocsEditorRoute },
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ const LazyDocsThreeDebuggingPage = lazyNamed(
|
|||||||
() => import("@/pages/docs/three-debugging/page"),
|
() => import("@/pages/docs/three-debugging/page"),
|
||||||
"DocsThreeDebuggingPage",
|
"DocsThreeDebuggingPage",
|
||||||
);
|
);
|
||||||
|
const LazyDocsMapPerformancePage = lazyNamed(
|
||||||
|
() => import("@/pages/docs/map-performance/page"),
|
||||||
|
"DocsMapPerformancePage",
|
||||||
|
);
|
||||||
|
|
||||||
export const DocsLayoutRoute = createDocsRoute(LazyDocsLayout);
|
export const DocsLayoutRoute = createDocsRoute(LazyDocsLayout);
|
||||||
export const DocsReadmeRoute = createDocsRoute(LazyDocsReadmePage);
|
export const DocsReadmeRoute = createDocsRoute(LazyDocsReadmePage);
|
||||||
@@ -124,3 +128,6 @@ export const DocsMissionFlowRoute = createDocsRoute(LazyDocsMissionFlowPage);
|
|||||||
export const DocsThreeDebuggingRoute = createDocsRoute(
|
export const DocsThreeDebuggingRoute = createDocsRoute(
|
||||||
LazyDocsThreeDebuggingPage,
|
LazyDocsThreeDebuggingPage,
|
||||||
);
|
);
|
||||||
|
export const DocsMapPerformanceRoute = createDocsRoute(
|
||||||
|
LazyDocsMapPerformancePage,
|
||||||
|
);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
PHYSICS_SCENE_BACKGROUND_COLOR,
|
PHYSICS_SCENE_BACKGROUND_COLOR,
|
||||||
} from "@/data/world/environmentConfig";
|
} from "@/data/world/environmentConfig";
|
||||||
import { FOG_CONFIG } from "@/data/world/fogConfig";
|
import { FOG_CONFIG } from "@/data/world/fogConfig";
|
||||||
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||||
import {
|
import {
|
||||||
isMapModelVisible,
|
isMapModelVisible,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
import { SkyModel } from "@/components/three/world/SkyModel";
|
import { SkyModel } from "@/components/three/world/SkyModel";
|
||||||
|
|
||||||
export function Environment(): React.JSX.Element {
|
export function Environment(): React.JSX.Element {
|
||||||
|
const cameraMode = useCameraMode();
|
||||||
const sceneMode = useSceneMode();
|
const sceneMode = useSceneMode();
|
||||||
const groups = useMapPerformanceStore((state) => state.groups);
|
const groups = useMapPerformanceStore((state) => state.groups);
|
||||||
const models = useMapPerformanceStore((state) => state.models);
|
const models = useMapPerformanceStore((state) => state.models);
|
||||||
@@ -28,7 +30,7 @@ export function Environment(): React.JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{FOG_CONFIG.enabled ? (
|
{FOG_CONFIG.enabled && sceneMode === "game" && cameraMode === "player" ? (
|
||||||
<fog
|
<fog
|
||||||
attach="fog"
|
attach="fog"
|
||||||
args={[FOG_CONFIG.color, FOG_CONFIG.near, FOG_CONFIG.far]}
|
args={[FOG_CONFIG.color, FOG_CONFIG.near, FOG_CONFIG.far]}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Suspense, useMemo, useRef, useState } from "react";
|
import { Suspense, useMemo, useRef, useState } from "react";
|
||||||
import { useFrame, useThree } from "@react-three/fiber";
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { CHUNK_CONFIG } from "@/data/world/fogConfig";
|
import { CHUNK_CONFIG } from "@/data/world/fogConfig";
|
||||||
|
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||||
|
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||||
import {
|
import {
|
||||||
isMapModelVisible,
|
isMapModelVisible,
|
||||||
useMapPerformanceStore,
|
useMapPerformanceStore,
|
||||||
@@ -75,6 +77,8 @@ function createVegetationChunks(
|
|||||||
|
|
||||||
export function VegetationSystem(): React.JSX.Element | null {
|
export function VegetationSystem(): React.JSX.Element | null {
|
||||||
const camera = useThree((state) => state.camera);
|
const camera = useThree((state) => state.camera);
|
||||||
|
const cameraMode = useCameraMode();
|
||||||
|
const sceneMode = useSceneMode();
|
||||||
const groups = useMapPerformanceStore((state) => state.groups);
|
const groups = useMapPerformanceStore((state) => state.groups);
|
||||||
const models = useMapPerformanceStore((state) => state.models);
|
const models = useMapPerformanceStore((state) => state.models);
|
||||||
const { data, isLoading } = useVegetationData();
|
const { data, isLoading } = useVegetationData();
|
||||||
@@ -82,6 +86,8 @@ export function VegetationSystem(): React.JSX.Element | null {
|
|||||||
const [activeChunkKeys, setActiveChunkKeys] = useState<Set<string>>(
|
const [activeChunkKeys, setActiveChunkKeys] = useState<Set<string>>(
|
||||||
() => new Set(),
|
() => new Set(),
|
||||||
);
|
);
|
||||||
|
const streamingEnabled =
|
||||||
|
CHUNK_CONFIG.enabled && sceneMode === "game" && cameraMode === "player";
|
||||||
|
|
||||||
const chunks = useMemo(() => {
|
const chunks = useMemo(() => {
|
||||||
if (!data) return [];
|
if (!data) return [];
|
||||||
@@ -98,7 +104,7 @@ export function VegetationSystem(): React.JSX.Element | null {
|
|||||||
}, [data, groups, models]);
|
}, [data, groups, models]);
|
||||||
|
|
||||||
useFrame(({ clock }) => {
|
useFrame(({ clock }) => {
|
||||||
if (!CHUNK_CONFIG.enabled) return;
|
if (!streamingEnabled) return;
|
||||||
|
|
||||||
const now = clock.elapsedTime * 1000;
|
const now = clock.elapsedTime * 1000;
|
||||||
if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return;
|
if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return;
|
||||||
@@ -137,7 +143,7 @@ export function VegetationSystem(): React.JSX.Element | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleChunks = CHUNK_CONFIG.enabled
|
const visibleChunks = streamingEnabled
|
||||||
? chunks.filter((chunk) => activeChunkKeys.has(chunk.key))
|
? chunks.filter((chunk) => activeChunkKeys.has(chunk.key))
|
||||||
: chunks;
|
: chunks;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user