diff --git a/README.md b/README.md index 948728e..8a88249 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ WS ws://localhost:8000/ws | `docs/technical/zustand.md` | Game, settings, and subtitle stores | | `docs/technical/three-debugging.md` | DevTools workflow for stepping into Three.js internals | | `docs/technical/map-performance.md` | Map draw-call bottlenecks and optimization notes | +| `docs/technical/map-lod.md` | Runtime map LOD presets, paths, and workflow | | `docs/technical/editor.md` | Editor implementation details | | `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components | | `docs/user/features.md` | Implemented feature inventory | diff --git a/docs/technical/map-lod.md b/docs/technical/map-lod.md new file mode 100644 index 0000000..fda0f5d --- /dev/null +++ b/docs/technical/map-lod.md @@ -0,0 +1,119 @@ +# Map LOD System + +This document describes the runtime LOD system used by the production map. + +## Goal + +The map now supports two visual versions for selected models: + +- the regular model in `public/models//` +- the lighter model in `public/models/-LOD/` + +The runtime chooses between those paths from the active graphics preset. This keeps nearby objects visually richer while reducing the cost of distant objects. + +## Graphics Presets + +Presets are configured in: + +```txt +src/data/world/graphicsConfig.ts +``` + +Current behavior: + +| Preset | Chunk load distance | Fog | LOD behavior | +| -------- | ------------------: | --- | ------------------------------------- | +| `low` | 10m | On | Always use `*-LOD` models | +| `medium` | 20m | On | Always use `*-LOD` models | +| `high` | Current default 50m | Off | Regular model up to 10m, then `*-LOD` | +| `ultra` | 50m | Off | Regular model up to 20m, then `*-LOD` | + +The unload distance stays slightly larger than the load distance to avoid rapid mount/unmount flickering when the player stands near a boundary. + +## Runtime Selection + +LOD path mapping lives in: + +```txt +src/data/world/mapLodConfig.ts +``` + +The main selector is `selectMapModelPathByDistance()`. It receives: + +- the current camera distance +- the map model name +- the regular model path +- the active graphics preset + +It returns either the regular path or the `*-LOD` path. + +## Chunked Instanced Models + +Repeated static assets are rendered through: + +```txt +src/world/map-instancing/MapInstancingSystem.tsx +``` + +For each visible chunk, the system checks the nearest instance in that chunk. If the nearest instance is inside the high-detail threshold, the whole chunk uses the regular model. Otherwise, it uses the `*-LOD` model. + +This is intentionally chunk-level LOD instead of per-instance LOD. It matches the existing chunk streaming architecture and avoids splitting every object into many tiny batches. + +## Single And Generated Models + +Single map nodes use: + +```txt +src/hooks/world/useMapLodModelPath.ts +src/world/GameMap.tsx +``` + +Some named map objects are rendered through dedicated generated components instead of the generic `GameMap` path. Those components must call `useMapLodModelPath()` directly. + +Current dedicated generated components with LOD support: + +```txt +src/components/three/world/EcoleModel.tsx +src/components/three/world/LaFabrikMapModel.tsx +``` + +This matters for `lafabrik`: adding `public/models/lafabrik-LOD/` is not enough by itself. The component must also be connected to `useMapLodModelPath()`. + +## Adding A New LOD Model + +To add LOD support for a model: + +1. Add the light model in `public/models/-LOD/model.gltf`. +2. Keep the regular model in `public/models//model.gltf`. +3. Add the mapping in `src/data/world/mapLodConfig.ts`. +4. If the model uses a dedicated component, call `useMapLodModelPath()` in that component. +5. Preload both paths when the component is dedicated and uses `useGLTF.preload()`. +6. Verify the GLTF references: buffers, textures, opacity maps, and relative paths. + +## Current LOD Models + +The current explicit LOD mappings are: + +```txt +ebike +eolienne +pylone +boiteimmeuble +ecole +immeuble1 +lafabrik +maison1 +panneauaffichage +talkie +``` + +## Regression Risks + +The most common failure modes are: + +- the `*-LOD` folder exists but is missing from `mapLodConfig.ts` +- a dedicated generated component keeps a hardcoded model path +- GLTF references point to textures that were renamed during export +- a model is added to LOD config but does not spawn through `GameMap` or `MapInstancingSystem` + +Before committing model changes, validate both the regular and LOD folders for missing GLTF refs. diff --git a/src/data/docs/docsSections.ts b/src/data/docs/docsSections.ts index 9b23d60..c01ac0b 100644 --- a/src/data/docs/docsSections.ts +++ b/src/data/docs/docsSections.ts @@ -92,6 +92,12 @@ export const docGroups: DocGroup[] = [ subtitle: "Draw calls, triangles, and streaming", meta: "13", }, + { + path: "/docs/map-lod", + title: "Map LOD System", + subtitle: "Presets, paths, and model workflow", + meta: "14", + }, ], }, { @@ -101,31 +107,31 @@ export const docGroups: DocGroup[] = [ path: "/docs/features", title: "Features", subtitle: "Implemented scope", - meta: "14", + meta: "15", }, { path: "/docs/main-feature", title: "Main Feature", subtitle: "Repair-game prototype", - meta: "15", + meta: "16", }, { path: "/docs/editor", title: "Editor User Guide", subtitle: "Editing workflow", - meta: "16", + meta: "17", }, { path: "/docs/animation", title: "Animation & 3D Model System", subtitle: "Components and usage", - meta: "17", + meta: "18", }, { path: "/docs/gallery", title: "Model Gallery", subtitle: "Browsing 3D assets", - meta: "18", + meta: "19", }, ], }, @@ -136,7 +142,7 @@ export const docGroups: DocGroup[] = [ path: "/docs/code-review", title: "Code Review Prep", subtitle: "Presentation support", - meta: "19", + meta: "20", }, ], }, diff --git a/src/pages/docs/map-lod/page.tsx b/src/pages/docs/map-lod/page.tsx new file mode 100644 index 0000000..117e9a1 --- /dev/null +++ b/src/pages/docs/map-lod/page.tsx @@ -0,0 +1,6 @@ +import mapLod from "../../../../docs/technical/map-lod.md?raw"; +import { DocsDocument } from "@/components/docs/DocsDocument"; + +export function DocsMapLodPage(): React.JSX.Element { + return ; +} diff --git a/src/router.tsx b/src/router.tsx index f9c481e..12f2bc2 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -22,6 +22,7 @@ import { DocsInteractionRoute, DocsLayoutRoute, DocsMainFeatureRoute, + DocsMapLodRoute, DocsMapPerformanceRoute, DocsMissionFlowRoute, DocsReadmeRoute, @@ -93,6 +94,7 @@ const docsChildRoutes = [ { path: "zustand", component: DocsZustandRoute }, { path: "three-debugging", component: DocsThreeDebuggingRoute }, { path: "map-performance", component: DocsMapPerformanceRoute }, + { path: "map-lod", component: DocsMapLodRoute }, { path: "features", component: DocsFeaturesRoute }, { path: "main-feature", component: DocsMainFeatureRoute }, { path: "editor", component: DocsEditorRoute }, diff --git a/src/routes/DocsRoute.tsx b/src/routes/DocsRoute.tsx index 53cd8c4..24da6c3 100644 --- a/src/routes/DocsRoute.tsx +++ b/src/routes/DocsRoute.tsx @@ -107,6 +107,10 @@ const LazyDocsMapPerformancePage = lazyNamed( () => import("@/pages/docs/map-performance/page"), "DocsMapPerformancePage", ); +const LazyDocsMapLodPage = lazyNamed( + () => import("@/pages/docs/map-lod/page"), + "DocsMapLodPage", +); export const DocsLayoutRoute = createDocsRoute(LazyDocsLayout); export const DocsReadmeRoute = createDocsRoute(LazyDocsReadmePage); @@ -136,3 +140,4 @@ export const DocsThreeDebuggingRoute = createDocsRoute( export const DocsMapPerformanceRoute = createDocsRoute( LazyDocsMapPerformancePage, ); +export const DocsMapLodRoute = createDocsRoute(LazyDocsMapLodPage);