Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7dff4a1238 | |||
| a8cd66dcaa | |||
| 116746f838 | |||
| a388c02ab3 | |||
| 4b4162b7d2 | |||
| 4415faa1f1 | |||
| 4c5f08d772 | |||
| 51569af7b8 | |||
| d26c676edf | |||
| d3b4a55e71 | |||
| e212e4bbd5 | |||
| 39ec9feb0e | |||
| 4a43083178 | |||
| efcbf9e972 | |||
| f11ed67452 | |||
| 3e7edcb1b7 | |||
| b9c5d0c563 | |||
| ebdb72ce0d | |||
| 34c198ebfd | |||
| 564a455520 | |||
| 2c2a90264d | |||
| e02d06b8a5 | |||
| 1901075e3a | |||
| e073fc375b | |||
| bff8a16290 | |||
| a3f611e227 | |||
| b578e68c2e | |||
| 7c691a8044 | |||
| f24704091a | |||
| e6bfcbe960 | |||
| 0fa7a82175 | |||
| 82dc47a296 | |||
| 970adf4853 | |||
| 07b09c22af | |||
| 0f6860f1ae | |||
| 6ae21a2427 | |||
| 29342d796c | |||
| 60e3c92511 | |||
| 02c1fb33d0 | |||
| ce5dc8ada0 | |||
| a2cff0567e | |||
| 8cfee1ac93 | |||
| 4c5e2ed945 | |||
| 345d49f485 |
@@ -112,7 +112,7 @@ npm run format:check
|
||||
npm run build
|
||||
```
|
||||
|
||||
Regenerate runtime map data after editing `public/map_raw.json`:
|
||||
Regenerate runtime map data after editing `public/map_raw.json` that came from the hierachy node of the model Blocking.gltf:
|
||||
|
||||
```bash
|
||||
npm run map:transform
|
||||
@@ -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 |
|
||||
|
||||
Binary file not shown.
@@ -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/<name>/`
|
||||
- the lighter model in `public/models/<name>-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/<name>-LOD/model.gltf`.
|
||||
2. Keep the regular model in `public/models/<name>/model.glb` or `public/models/<name>/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/GLB 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.
|
||||
@@ -14,12 +14,12 @@ This document tracks the current map-rendering performance pass.
|
||||
|
||||
The first performance bottleneck was draw calls. Some assets were exported as many small GLTF primitives even when they used only a few materials.
|
||||
|
||||
| Model | Instances | Meshes / primitives | Notes |
|
||||
| ---------------- | --------: | ------------------: | ---------------------------------------------------------------- |
|
||||
| `generateur` | 3 | 3152 | Worst draw-call offender. Needs asset-side mesh merging. |
|
||||
| `lafabrik` | 4 | 56 | Moderate draw calls, heavy 2048 texture set. |
|
||||
| `ecole` | 1 | 107 | One material but many primitives; should be merged. |
|
||||
| `fermeverticale` | 3 | 1 | Geometry is fine; textures are large for the visible complexity. |
|
||||
| Model | Instances | Meshes / primitives | Notes |
|
||||
| ---------------- | --------: | ------------------: | ------------------------------------------------------------------------------------ |
|
||||
| `generateur` | 3 | 3152 | Worst draw-call offender. Needs asset-side mesh merging. |
|
||||
| `lafabrik` | 4 | 474 | High primitive count; current HD GLB has embedded geometry and no external textures. |
|
||||
| `ecole` | 1 | 107 | One material but many primitives; should be merged. |
|
||||
| `fermeverticale` | 3 | 1 | Geometry is fine; textures are large for the visible complexity. |
|
||||
|
||||
`generateur` was especially expensive because three visible instances could multiply thousands of primitives into thousands of draw calls. Instancing reduces repeated instance cost, but the source asset still needs a cleaner export.
|
||||
|
||||
@@ -34,7 +34,7 @@ Estimated source primitive count versus runtime merged groups:
|
||||
| `generateur` | 3152 | 8 |
|
||||
| `ecole` | 107 | 2 |
|
||||
| `eolienne` | 118 | 8 |
|
||||
| `lafabrik` | 56 | 14 |
|
||||
| `lafabrik` | 474 | ~77 |
|
||||
|
||||
This is a code-side safety net, not a replacement for clean asset exports. Clean GLB exports with merged meshes and fewer textures remain the preferred long-term path.
|
||||
|
||||
@@ -255,7 +255,7 @@ Design/export should prioritize:
|
||||
1. Produce lower-poly `buisson`, `arbre`, `sapin`, and crop assets.
|
||||
2. Add LOD or billboard variants for far vegetation.
|
||||
3. Merge `generateur` meshes from 3152 primitives to a small number of material groups.
|
||||
4. Reduce `lafabrik` texture count and downscale flat/low-detail maps.
|
||||
4. Keep `lafabrik` exports texture-light, and merge repeated material primitives where possible.
|
||||
5. Merge `ecole` primitives because it uses a single material.
|
||||
6. Prefer runtime `.glb` or compressed runtime textures when the pipeline supports it.
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ The loading progress in `HomePage` is monotonic:
|
||||
|
||||
This prevents the overlay from jumping backward when nested loaders finish in a slightly different order.
|
||||
|
||||
After the initial map boot is complete, late loading signals no longer reopen the full-screen loading overlay. Instead, `HomePage` shows the compact `AppLoadingIndicator` while the game remains visible. This is reserved for explicit runtime reload signals such as graphics preset changes, repair-state transitions, or late world loading events; chunk streaming intentionally does not drive this indicator.
|
||||
|
||||
## World Composition
|
||||
|
||||
`src/world/World.tsx` is the main scene composer.
|
||||
@@ -72,14 +74,23 @@ It tracks:
|
||||
- `gameMapLoaded`: map data and visible map nodes settled
|
||||
- `gameStageLoaded`: Rapier gameplay stage mounted
|
||||
- `showGameStage`: true when the map is ready enough to mount gameplay content
|
||||
- `gameplayReady`: true when map, stage, and octree are all ready
|
||||
- `shadowsReady`: renderer, shadow lights, and scene matrices have been forced once after the scene is mounted
|
||||
- `gameplayReady`: true when map, stage, octree, and the shadow warmup are all ready
|
||||
|
||||
The final game-scene readiness condition is:
|
||||
The base game-scene readiness condition before the shadow warmup is:
|
||||
|
||||
```ts
|
||||
showGameStage && gameStageLoaded && octree !== null;
|
||||
```
|
||||
|
||||
After that condition is met, `SceneShadowWarmup` runs one final loading step:
|
||||
|
||||
```txt
|
||||
Activation des ombres -> Ombres prêtes -> Gameplay prêt
|
||||
```
|
||||
|
||||
This keeps the loading overlay visible until the renderer shadow map, shadow-casting light, and mounted scene graph have all been explicitly refreshed.
|
||||
|
||||
The debug physics scene is ready when:
|
||||
|
||||
```ts
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2
-227
@@ -584,22 +584,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Object3D",
|
||||
"position": [50.072, 2.2583, 78.7082],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Mesh",
|
||||
"position": [50.072, 2.2583, 78.7082],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Object3D",
|
||||
@@ -888,22 +872,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Object3D",
|
||||
"position": [59.1794, 2.2557, 73.349],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Mesh",
|
||||
"position": [59.1794, 2.2557, 73.349],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Object3D",
|
||||
@@ -1112,22 +1080,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Object3D",
|
||||
"position": [74.0452, 2.309, 59.2374],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Mesh",
|
||||
"position": [74.0452, 2.309, 59.2374],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "arbre",
|
||||
"type": "Object3D",
|
||||
@@ -2754,22 +2706,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [73.7334, 1.1132, 54.1382],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [73.7334, 1.1132, 54.1382],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -3330,22 +3266,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [67.9046, 0.5562, 74.8395],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [67.9046, 0.5562, 74.8395],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -3714,22 +3634,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [73.5205, 0.3748, 75.9136],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [73.5205, 0.3748, 75.9136],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -3858,22 +3762,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [66.999, 1.7223, 48.3983],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [66.999, 1.7223, 48.3983],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -4914,22 +4802,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [61.3924, 0.4621, 82.2195],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [61.3924, 0.4621, 82.2195],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -5122,22 +4994,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [61.1082, 0.6236, 77.7642],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [61.1082, 0.6236, 77.7642],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -5170,22 +5026,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [53.1033, 1.6054, 63.3842],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [53.1033, 1.6054, 63.3842],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -5266,22 +5106,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [59.647, 1.5484, 59.429],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [59.647, 1.5484, 59.429],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -5410,22 +5234,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [69.2496, 0.6286, 71.5478],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [69.2496, 0.6286, 71.5478],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -6226,22 +6034,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
"position": [58.3126, 0.686, 77.9828],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Mesh",
|
||||
"position": [58.3126, 0.686, 77.9828],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "buisson",
|
||||
"type": "Object3D",
|
||||
@@ -37602,23 +37394,6 @@
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "ebike",
|
||||
"type": "Object3D",
|
||||
"role": "group",
|
||||
"position": [0, 0, 0],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "ebike",
|
||||
"type": "Object3D",
|
||||
"position": [42.2399, 4.5484, 34.6468],
|
||||
"rotation": [0, 0, 0],
|
||||
"scale": [1, 1, 1]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "zone1_residence",
|
||||
"type": "Object3D",
|
||||
@@ -40477,14 +40252,14 @@
|
||||
"name": "lafabrik",
|
||||
"type": "Object3D",
|
||||
"position": [59.4973, 6.2746, 64.6354],
|
||||
"rotation": [-3.1416, -0.7309, -3.1416],
|
||||
"rotation": [-3.1416, 2.4107, -3.1416],
|
||||
"scale": [1, 2, 1],
|
||||
"children": [
|
||||
{
|
||||
"name": "lafabrik",
|
||||
"type": "Mesh",
|
||||
"position": [59.4973, 6.2746, 64.6354],
|
||||
"rotation": [-3.1416, -0.7309, -3.1416],
|
||||
"rotation": [-3.1416, 2.4107, -3.1416],
|
||||
"scale": [1, 2, 1]
|
||||
}
|
||||
]
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user