Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2cecfbbc9 | |||
| 1b9ac5c996 | |||
| e45fdbf97d | |||
| 08b01715c0 | |||
| a2fc417be6 | |||
| 57498b9bb1 | |||
| b87a7e929c | |||
| ea23b4bb46 | |||
| 3881e38a6d | |||
| 7a72743e5c | |||
| 65651405b6 | |||
| 81cd935bba | |||
| fe989c9550 | |||
| e15fb18d6b | |||
| 0230795f55 | |||
| b1fca3a25b | |||
| 3eba38a80b | |||
| 0992aacec6 | |||
| bfe184dea4 | |||
| 4ee13b0336 | |||
| fdd530a3e7 | |||
| 2b676d985d | |||
| b89eedd5be | |||
| d38ad242d6 | |||
| c2b16434fb | |||
| ab100c683f | |||
| b8cff43545 | |||
| 25e0f7e062 | |||
| ab3943eef3 | |||
| 4ebb5b8c25 | |||
| a6787a7ecb | |||
| d816e4b07e | |||
| 665d9f9702 | |||
| 0696ca2ae3 | |||
| d6d3d5b685 | |||
| 1c27d55e5a | |||
| fd558db034 | |||
| fbe8c0c854 | |||
| 88b6db6166 | |||
| 2b08665508 | |||
| 4f8355e934 | |||
| 417afdc1d5 | |||
| 235a38f67b | |||
| f54e71fc03 | |||
| 6d178dc59e | |||
| b4a3545460 | |||
| 1f6d9659ed | |||
| a52d57ae6c | |||
| d0497ec42c | |||
| 4c42e11268 | |||
| f175ad6240 | |||
| a4383a7cec | |||
| d07ffc4662 | |||
| d17738eaf1 | |||
| 50fa94b3ad | |||
| 44f9d68ef1 | |||
| e4857135b1 | |||
| 1f6335092a | |||
| f035195b56 | |||
| b8e5c4d1a9 | |||
| 4e6582b543 | |||
| e4ee2d768b | |||
| 5e594c51f7 | |||
| 26ddbebe14 | |||
| 48c2b4f0cd | |||
| cf08062def | |||
| 4f25b33d3b | |||
| 072dec03b4 | |||
| 529c60adae | |||
| 6957b9e4f0 | |||
| 9dff245aab | |||
| 27951d13fd | |||
| cdd919c010 | |||
| cea2856fd0 | |||
| d245d6b460 | |||
| dba7aec6fa | |||
| d376d0ba6b | |||
| 242a3dcd37 | |||
| 225ac828df | |||
| 28f7db172c | |||
| 2063656f29 | |||
| 592cfa405f | |||
| 0a32cd1d21 | |||
| 4516cf4ec6 | |||
| c6f60d1ca7 | |||
| aa35e97cbb | |||
| 57c142c8ef | |||
| 4843bf1d75 | |||
| d02cf29a1d | |||
| 3b4c9c2529 | |||
| fdf03349cf | |||
| 439f9c1dad | |||
| 260bfea716 | |||
| b3a3f3557c | |||
| 621556b38c | |||
| 5c55f2c7f4 | |||
| bb4bccb175 | |||
| ae835e5008 | |||
| 1d3aa1c9f2 | |||
| 23cab2da5e | |||
| 44216f9395 |
@@ -1,59 +1,58 @@
|
|||||||
name: 🔁 Weekly Branch Promotions
|
name: 🔁 Branch Promotions
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 6 * * 1"
|
- cron: "0 6 * * 1,4" # Lundi et Jeudi à 6h UTC (design → develop)
|
||||||
|
- cron: "0 6 * * 1" # Lundi à 6h UTC (develop → main)
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
promotion:
|
||||||
|
description: "Which promotion to run"
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- design-to-develop
|
||||||
|
- develop-to-main
|
||||||
|
- both
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: weekly-branch-promotions
|
group: branch-promotions
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
open-promotion-pr:
|
design-to-develop:
|
||||||
name: Open ${{ matrix.head }} → ${{ matrix.base }}
|
name: Open design → develop
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
if: |
|
||||||
fail-fast: false
|
(github.event_name == 'schedule') ||
|
||||||
matrix:
|
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'design-to-develop' || github.event.inputs.promotion == 'both'))
|
||||||
include:
|
|
||||||
- head: develop
|
|
||||||
base: design
|
|
||||||
title: "chore: merge develop into design"
|
|
||||||
- head: design
|
|
||||||
base: main
|
|
||||||
title: "chore: merge design into main"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: ⬇️ Checkout
|
- name: ⬇️ Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 🔁 Open promotion PR
|
- name: 🔁 Open promotion PR
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
BASE_BRANCH: ${{ matrix.base }}
|
|
||||||
HEAD_BRANCH: ${{ matrix.head }}
|
|
||||||
PR_TITLE: ${{ matrix.title }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
git fetch origin "$BASE_BRANCH" "$HEAD_BRANCH"
|
git fetch origin develop design
|
||||||
|
|
||||||
if git merge-base --is-ancestor "origin/$HEAD_BRANCH" "origin/$BASE_BRANCH"; then
|
if git merge-base --is-ancestor origin/design origin/develop; then
|
||||||
echo "No promotion needed: $BASE_BRANCH already contains $HEAD_BRANCH."
|
echo "No promotion needed: develop already contains design."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
existing_pr="$(gh pr list \
|
existing_pr="$(gh pr list \
|
||||||
--state open \
|
--state open \
|
||||||
--base "$BASE_BRANCH" \
|
--base develop \
|
||||||
--head "$GITHUB_REPOSITORY_OWNER:$HEAD_BRANCH" \
|
--head "$GITHUB_REPOSITORY_OWNER:design" \
|
||||||
--json number \
|
--json number \
|
||||||
--jq '.[0].number // empty')"
|
--jq '.[0].number // empty')"
|
||||||
|
|
||||||
@@ -63,7 +62,50 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
gh pr create \
|
gh pr create \
|
||||||
--base "$BASE_BRANCH" \
|
--base develop \
|
||||||
--head "$HEAD_BRANCH" \
|
--head design \
|
||||||
--title "$PR_TITLE" \
|
--title "chore: merge design into develop" \
|
||||||
--body "Automated weekly promotion PR from \`$HEAD_BRANCH\` to \`$BASE_BRANCH\`."
|
--body "Automated promotion PR from \`design\` to \`develop\`."
|
||||||
|
|
||||||
|
develop-to-main:
|
||||||
|
name: Open develop → main
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: |
|
||||||
|
(github.event_name == 'schedule' && github.event.schedule == '0 6 * * 1') ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && (github.event.inputs.promotion == 'develop-to-main' || github.event.inputs.promotion == 'both'))
|
||||||
|
steps:
|
||||||
|
- name: ⬇️ Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 🔁 Open promotion PR
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
git fetch origin main develop
|
||||||
|
|
||||||
|
if git merge-base --is-ancestor origin/develop origin/main; then
|
||||||
|
echo "No promotion needed: main already contains develop."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
existing_pr="$(gh pr list \
|
||||||
|
--state open \
|
||||||
|
--base main \
|
||||||
|
--head "$GITHUB_REPOSITORY_OWNER:develop" \
|
||||||
|
--json number \
|
||||||
|
--jq '.[0].number // empty')"
|
||||||
|
|
||||||
|
if [ -n "$existing_pr" ]; then
|
||||||
|
echo "Promotion PR already open: #$existing_pr."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
gh pr create \
|
||||||
|
--base main \
|
||||||
|
--head develop \
|
||||||
|
--title "chore: merge develop into main" \
|
||||||
|
--body "Automated weekly promotion PR from \`develop\` to \`main\`."
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ WS ws://localhost:8000/ws
|
|||||||
| `docs/technical/hand-tracking.md` | Webcam, backend/browser MediaPipe, glove, and gesture flow |
|
| `docs/technical/hand-tracking.md` | Webcam, backend/browser MediaPipe, glove, and gesture flow |
|
||||||
| `docs/technical/zustand.md` | Game, settings, and subtitle stores |
|
| `docs/technical/zustand.md` | Game, settings, and subtitle stores |
|
||||||
| `docs/technical/three-debugging.md` | DevTools workflow for stepping into Three.js internals |
|
| `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/editor.md` | Editor implementation details |
|
| `docs/technical/editor.md` | Editor implementation details |
|
||||||
| `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components |
|
| `docs/technical/animation.md` | Animated, explodable, and reusable 3D model components |
|
||||||
| `docs/user/features.md` | Implemented feature inventory |
|
| `docs/user/features.md` | Implemented feature inventory |
|
||||||
|
|||||||
+18
-20
@@ -4,7 +4,7 @@ This document describes the map editor that exists in the current codebase.
|
|||||||
|
|
||||||
## Purpose
|
## Purpose
|
||||||
|
|
||||||
The editor is a React route used to inspect and adjust the `public/map.json` scene data from inside the La-Fabrik app. It shares the same `MapNode` data format as the game scene and uses React Three Fiber for rendering.
|
The editor is a React route used to inspect and adjust the current hierarchical `public/map.json` scene data from inside the La-Fabrik app. It exposes editable object nodes as a flat list for UI selection, while preserving and saving the full map tree.
|
||||||
|
|
||||||
## Routing
|
## Routing
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ src/
|
|||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
|
|
||||||
`src/pages/editor/page.tsx` is the route-level composition component. It owns route-specific state such as selected object, hovered object, transform mode, selection lock, player-mode toggle, cinematic preview requests, and editor scene loading state.
|
`src/pages/editor/page.tsx` is the route-level composition component. It owns route-specific state such as primary selected object, selected object indexes, hovered object, transform mode, selection lock, player-mode toggle, cinematic preview requests, and editor scene loading state.
|
||||||
|
|
||||||
`src/hooks/editor/useEditorSceneData.ts` loads the default map data and handles folder uploads.
|
`src/hooks/editor/useEditorSceneData.ts` loads the default map data and handles folder uploads.
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ src/
|
|||||||
|
|
||||||
`src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, keyboard shortcuts, and `EditorMap`.
|
`src/components/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, keyboard shortcuts, and `EditorMap`.
|
||||||
|
|
||||||
`src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls.
|
`src/components/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. For multi-selection, it attaches `TransformControls` to a temporary group centered on the selected nodes, then decomposes the group delta back into each selected node transform.
|
||||||
|
|
||||||
`src/components/editor/EditorControls.tsx` renders the HTML control panel outside the canvas. The panel is organized into top-level `details` groups: `Editor`, `Cinematics`, `Dialogues`, and `SRT`.
|
`src/components/editor/EditorControls.tsx` renders the HTML control panel outside the canvas. The panel is organized into top-level `details` groups: `Editor`, `Cinematics`, `Dialogues`, and `SRT`.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ src/
|
|||||||
|
|
||||||
`src/controls/editor/FlyController.tsx` provides editor movement controls for player-style navigation.
|
`src/controls/editor/FlyController.tsx` provides editor movement controls for player-style navigation.
|
||||||
|
|
||||||
`src/utils/map/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json` and resolves available `public/models/{name}/model.glb` files first, then falls back to `public/models/{name}/model.gltf`.
|
`src/utils/map/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json`, validates the hierarchical payload, exposes editable nodes with their `sourcePath` back to the tree, and resolves available `public/models/{name}/model.glb` files first, then falls back to `public/models/{name}/model.gltf`.
|
||||||
|
|
||||||
`src/utils/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders.
|
`src/utils/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders.
|
||||||
|
|
||||||
@@ -87,22 +87,13 @@ interface MapNode {
|
|||||||
position: [number, number, number];
|
position: [number, number, number];
|
||||||
rotation: [number, number, number];
|
rotation: [number, number, number];
|
||||||
scale: [number, number, number];
|
scale: [number, number, number];
|
||||||
|
sourcePath?: number[];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`public/map.json` is expected to be a `MapNode[]`.
|
`public/map.json` may be hierarchical. The editor keeps the hierarchy in `SceneData.mapTree` and stores editable entries in `SceneData.mapNodes` with a `sourcePath` back to the real tree node.
|
||||||
|
|
||||||
```json
|
Group nodes use `role: "group"`; editable nodes keep `name`, `type`, `position`, `rotation`, and `scale`.
|
||||||
[
|
|
||||||
{
|
|
||||||
"name": "pylone",
|
|
||||||
"type": "Mesh",
|
|
||||||
"position": [0, 5, 0],
|
|
||||||
"rotation": [0, 1.57, 0],
|
|
||||||
"scale": [1, 1, 1]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
Each node `name` maps to a model folder:
|
Each node `name` maps to a model folder:
|
||||||
|
|
||||||
@@ -124,11 +115,12 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
|||||||
4. If `/map.json` is missing, the page displays a folder-upload flow.
|
4. If `/map.json` is missing, the page displays a folder-upload flow.
|
||||||
5. `EditorSceneLoadingTracker` uses drei `useProgress()` to update the fullscreen editor loading overlay while models load.
|
5. `EditorSceneLoadingTracker` uses drei `useProgress()` to update the fullscreen editor loading overlay while models load.
|
||||||
6. `EditorScene` renders the grid, lights, camera controls, and map nodes inside `Suspense`.
|
6. `EditorScene` renders the grid, lights, camera controls, and map nodes inside `Suspense`.
|
||||||
7. `EditorControls` exposes transform mode, history actions, export, save, JSON preview, selection lock, and the cinematic/dialogue/SRT editors.
|
7. `EditorControls` exposes transform mode, terrain snap, terrain-selection lock, add/delete node, precise scale inputs, history actions, camera focus/reset, export, save, JSON preview, selection lock, multi-selection status, and the cinematic/dialogue/SRT editors.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
- Click: select a node.
|
- Click: select a node.
|
||||||
|
- `Shift` + right click: add or remove a node from the multi-selection.
|
||||||
- `Esc`: clear selection.
|
- `Esc`: clear selection.
|
||||||
- Click empty space: clear selection.
|
- Click empty space: clear selection.
|
||||||
- Selection lock button: prevent object clicks, empty-space clicks, and `Esc` from changing the current selection.
|
- Selection lock button: prevent object clicks, empty-space clicks, and `Esc` from changing the current selection.
|
||||||
@@ -136,6 +128,12 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
|||||||
- `T`: translate mode.
|
- `T`: translate mode.
|
||||||
- `R`: rotate mode.
|
- `R`: rotate mode.
|
||||||
- `S`: scale mode.
|
- `S`: scale mode.
|
||||||
|
- Snap terrain on move: enabled by default and applied while translating an object.
|
||||||
|
- Multi-selection transforms use a temporary centered group and write the resulting position, rotation, and scale back to every selected map node.
|
||||||
|
- Lock terrain: enabled by default so terrain remains visible but ignores selection clicks.
|
||||||
|
- Camera action: centers on the selected object or resets to the editor home view.
|
||||||
|
- Add node: creates a fallback cube under `blocking` using the requested model folder name.
|
||||||
|
- Delete selected node: removes the editable node from the preserved map tree.
|
||||||
- `Ctrl+Z` or `Cmd+Z`: undo.
|
- `Ctrl+Z` or `Cmd+Z`: undo.
|
||||||
- `Ctrl+Y` or `Cmd+Y`: redo.
|
- `Ctrl+Y` or `Cmd+Y`: redo.
|
||||||
- `WASD`, `ZQSD`, or arrow keys: move in player-controller mode.
|
- `WASD`, `ZQSD`, or arrow keys: move in player-controller mode.
|
||||||
@@ -146,10 +144,10 @@ If `model.glb` and `model.gltf` are both missing, the editor renders a fallback
|
|||||||
|
|
||||||
The editor supports two output paths:
|
The editor supports two output paths:
|
||||||
|
|
||||||
- Export JSON downloads the current `MapNode[]` as `map.json`.
|
- Export JSON downloads the current hierarchical map tree as `map.json`.
|
||||||
- Save to Server posts the current `MapNode[]` to `/api/save-map`.
|
- Save to Server posts the current hierarchical map tree to `/api/save-map`.
|
||||||
|
|
||||||
The dev-only `/api/save-map` endpoint is implemented by the Vite plugin in `vite.config.ts`. It writes to `public/map.json` and enforces a maximum payload size.
|
The dev-only `/api/save-map` endpoint is implemented by the Vite plugin in `vite.config.ts`. It validates the payload through the shared map parser, writes to `public/map.json`, and enforces a maximum payload size.
|
||||||
|
|
||||||
## Editor Loading Overlay
|
## Editor Loading Overlay
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
# Map Performance Notes
|
||||||
|
|
||||||
|
This document tracks the current map-rendering performance pass.
|
||||||
|
|
||||||
|
## Current Runtime Path
|
||||||
|
|
||||||
|
- `public/map.json` is the source of map transforms.
|
||||||
|
- `src/world/GameMap.tsx` renders regular visual map nodes.
|
||||||
|
- `src/world/vegetation/VegetationSystem.tsx` already instances dense vegetation.
|
||||||
|
- `src/world/map-instancing/MapInstancingSystem.tsx` instances selected repeated static map assets.
|
||||||
|
- `src/world/GameMapCollision.tsx` keeps terrain collision separate for the player octree.
|
||||||
|
|
||||||
|
## Draw-Call Bottlenecks Found
|
||||||
|
|
||||||
|
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. |
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
|
## Runtime Merge Pass
|
||||||
|
|
||||||
|
`InstancedMapAsset` now groups source meshes by material and compatible geometry attributes before creating `THREE.InstancedMesh` objects. This reduces the runtime draw groups even when the source GLTF is exported as many small meshes.
|
||||||
|
|
||||||
|
Estimated source primitive count versus runtime merged groups:
|
||||||
|
|
||||||
|
| Model | Source primitives | Runtime merged groups |
|
||||||
|
| ------------ | ----------------: | --------------------: |
|
||||||
|
| `generateur` | 3152 | 8 |
|
||||||
|
| `ecole` | 107 | 2 |
|
||||||
|
| `eolienne` | 118 | 8 |
|
||||||
|
| `lafabrik` | 56 | 14 |
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Current Triangle Bottleneck
|
||||||
|
|
||||||
|
After the runtime merge pass, draw calls can drop dramatically, but FPS can still stay low because the scene now remains triangle-bound. A debug capture after the merge showed roughly:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
138 draw calls
|
||||||
|
~69.6M triangles
|
||||||
|
~10 FPS
|
||||||
|
```
|
||||||
|
|
||||||
|
That means the renderer is no longer mostly blocked by draw-call submission. It is mostly drawing too many visible triangles.
|
||||||
|
|
||||||
|
Estimated triangle contribution from `map.json` instance counts:
|
||||||
|
|
||||||
|
| Model | Instances | Triangles each | Estimated total triangles |
|
||||||
|
| ------------------- | --------: | -------------: | ------------------------: |
|
||||||
|
| `buisson` | 646 | 37 500 | ~24.2M |
|
||||||
|
| `champdesoja` | 1181 | 16 268 | ~19.2M |
|
||||||
|
| `arbre` | 291 | 38 906 | ~11.3M |
|
||||||
|
| `champdeble` | 1307 | 6 260 | ~8.2M |
|
||||||
|
| `champsdetournesol` | 1163 | 3 264 | ~3.8M |
|
||||||
|
| `sapin` | 93 | 23 972 | ~2.2M |
|
||||||
|
|
||||||
|
These vegetation and crop assets account for almost all of the current `~69M` triangle count. By comparison, the previously suspicious static buildings are much smaller in triangle cost:
|
||||||
|
|
||||||
|
| Model | Estimated total triangles |
|
||||||
|
| ---------------- | ------------------------: |
|
||||||
|
| `generateur` | ~123k |
|
||||||
|
| `lafabrik` | ~124k |
|
||||||
|
| `ecole` | ~5k |
|
||||||
|
| `fermeverticale` | ~1k |
|
||||||
|
|
||||||
|
`InstancedMesh` reduces draw calls, but it does not reduce triangle count. If 646 bushes each contain 37 500 triangles, the GPU still has to draw about 24 million bush triangles when those instances are visible.
|
||||||
|
|
||||||
|
## Debug Performance Controls
|
||||||
|
|
||||||
|
The next useful runtime tool is a debug-only performance folder that can isolate model families. This should be mounted only when `?debug` is enabled.
|
||||||
|
|
||||||
|
Proposed controls:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
Performance / Map
|
||||||
|
- vegetation
|
||||||
|
- crops
|
||||||
|
- trees
|
||||||
|
- buildings
|
||||||
|
- landmarks
|
||||||
|
- props
|
||||||
|
- terrain
|
||||||
|
- sky
|
||||||
|
```
|
||||||
|
|
||||||
|
Useful per-model toggles:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
buisson
|
||||||
|
arbre
|
||||||
|
sapin
|
||||||
|
champdeble
|
||||||
|
champdesoja
|
||||||
|
champsdetournesol
|
||||||
|
fermeverticale
|
||||||
|
lafabrik
|
||||||
|
immeuble1
|
||||||
|
eolienne
|
||||||
|
pylone
|
||||||
|
```
|
||||||
|
|
||||||
|
The purpose is diagnostic, not final gameplay behavior. The expected workflow is:
|
||||||
|
|
||||||
|
1. Open `/?debug` with R3F perf enabled.
|
||||||
|
2. Disable one family or model type.
|
||||||
|
3. Watch `triangles`, `calls`, and FPS.
|
||||||
|
4. Identify which model groups need LOD, density reduction, or asset re-export.
|
||||||
|
|
||||||
|
Recommended implementation files:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/managers/stores/useMapPerformanceStore.ts
|
||||||
|
src/hooks/debug/useMapPerformanceDebug.ts
|
||||||
|
src/world/vegetation/VegetationSystem.tsx
|
||||||
|
src/world/map-instancing/MapInstancingSystem.tsx
|
||||||
|
src/world/GameMap.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
The store should stay runtime/debug-only. It should not change persisted production map data.
|
||||||
|
|
||||||
|
## Triangle-Reduction Follow-Up
|
||||||
|
|
||||||
|
Once the expensive model families are isolated, the real triangle fixes are:
|
||||||
|
|
||||||
|
1. Lower-poly vegetation and crop exports.
|
||||||
|
2. LOD variants for trees, bushes, and crop fields.
|
||||||
|
3. Distance-based culling for vegetation/crop instances.
|
||||||
|
4. Chunked instancing so Three.js can frustum-cull groups instead of one huge global `InstancedMesh`.
|
||||||
|
5. Billboard/impostor versions for far vegetation.
|
||||||
|
|
||||||
|
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: 45
|
||||||
|
updateInterval: 350ms
|
||||||
|
fog near: 30
|
||||||
|
fog far: 45
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Runtime Texture Filtering
|
||||||
|
|
||||||
|
Loaded GLTF textures are normalized in code through:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/utils/three/optimizeGLTFScene.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
The runtime pass applies conservative texture filtering:
|
||||||
|
|
||||||
|
1. Cap anisotropy to a small value.
|
||||||
|
2. Enable mipmap generation for regular PNG/JPG/WebP textures.
|
||||||
|
3. Use trilinear mipmap filtering for minification.
|
||||||
|
4. Keep existing opacity/alpha material mapping intact.
|
||||||
|
|
||||||
|
This mirrors the intent of the designer upload pipeline without rewriting model files at runtime. The sibling `upload-GLTF` project already has the stronger asset-side path: Blender GLB export with Draco, texture resizing, KTX2 generation with mipmaps, WebP fallback, and GLTF JSON URI/extension rewriting for `KHR_texture_basisu`.
|
||||||
|
|
||||||
|
Runtime texture filtering improves distant texture stability and GPU sampling behavior, but it does not reduce mesh triangle count. Triangle reduction still comes from streaming, distance unloading, or optimized source assets.
|
||||||
|
|
||||||
|
## Terrain-Snapped Map Placement
|
||||||
|
|
||||||
|
Map object heights are corrected at runtime through:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/hooks/three/useTerrainHeight.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
The terrain raycast is not done every frame. The terrain mesh list is built from the cached terrain GLTF, then each model or instance computes its snapped `y` when it is mounted or when its instance data changes.
|
||||||
|
|
||||||
|
Applied paths:
|
||||||
|
|
||||||
|
1. Regular `GameMap` model instances.
|
||||||
|
2. Generated static map models.
|
||||||
|
3. Instanced static map assets.
|
||||||
|
4. Vegetation and crop chunks.
|
||||||
|
|
||||||
|
Only the `y` coordinate is replaced. `x`, `z`, and rotation stay from `map.json`. Runtime scale is also normalized when a static map node has a non-uniform scale, which prevents exported values like `[1, 2, 1]` from stretching or shrinking a map model unexpectedly.
|
||||||
|
|
||||||
|
## Current Code-Side Optimization
|
||||||
|
|
||||||
|
Repeated static assets are configured in:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/world/map-instancing/mapInstancingConfig.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Those names are excluded from the regular `GameMap` clone path, then rendered by `MapInstancingSystem` with merged `THREE.InstancedMesh` batches.
|
||||||
|
|
||||||
|
This keeps the existing map authoring format while reducing repeated draw calls for selected assets.
|
||||||
|
|
||||||
|
## Generated R3F Model Path
|
||||||
|
|
||||||
|
Unique static map assets can use explicit R3F components instead of the generic cloned GLTF path. This follows the same intent as `gltfjsx`: expose the model as a React component, then keep control over mesh/material setup in code.
|
||||||
|
|
||||||
|
Current generated map-model entry point:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/world/map-generated/GeneratedMapNodeInstance.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
Current generated model component:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
src/components/three/world/EcoleModel.tsx
|
||||||
|
src/components/three/world/LafabrikModel.tsx
|
||||||
|
src/components/three/world/FermeVerticaleModel.tsx
|
||||||
|
src/components/three/world/GenerateurModel.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
`ecole`, `lafabrik`, `fermeverticale`, and `generateur` use this path. Their components share the same merged static model renderer, which groups compatible geometry by material before mounting meshes.
|
||||||
|
|
||||||
|
This path should be used selectively. It improves control and can remove clone overhead, but it does not reduce source triangle count by itself.
|
||||||
|
|
||||||
|
## Asset-Side Follow-Up
|
||||||
|
|
||||||
|
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.
|
||||||
|
5. Merge `ecole` primitives because it uses a single material.
|
||||||
|
6. Prefer runtime `.glb` or compressed runtime textures when the pipeline supports it.
|
||||||
|
|
||||||
|
## Safety Rules
|
||||||
|
|
||||||
|
- Do not instance `terrain` for player collision without validating `Octree.fromGraphNode` support.
|
||||||
|
- Do not replace repair-game models with optimized map models unless repair node names are preserved.
|
||||||
|
- Dispose only GPU resources created locally. Do not dispose textures or geometries owned by `useGLTF`'s cache.
|
||||||
+41
-11
@@ -6,7 +6,7 @@ The map editor is available at `/editor`. It is a browser-based tool for editing
|
|||||||
|
|
||||||
Use the editor when you need to:
|
Use the editor when you need to:
|
||||||
|
|
||||||
- move, rotate, or scale objects from `public/map.json`
|
- move, rotate, scale, add, or delete objects from `public/map.json`
|
||||||
- inspect the raw JSON generated by the editor
|
- inspect the raw JSON generated by the editor
|
||||||
- preview and edit cinematics from `public/cinematics.json`
|
- preview and edit cinematics from `public/cinematics.json`
|
||||||
- create, preview, and validate dialogue entries from `public/sounds/dialogue/dialogues.json`
|
- create, preview, and validate dialogue entries from `public/sounds/dialogue/dialogues.json`
|
||||||
@@ -14,13 +14,13 @@ Use the editor when you need to:
|
|||||||
|
|
||||||
The map editor reads the same map data as the runtime scene:
|
The map editor reads the same map data as the runtime scene:
|
||||||
|
|
||||||
- `public/map.json` contains the object list.
|
- `public/map.json` contains the current hierarchical runtime map.
|
||||||
- `public/models/{name}/model.glb` contains the matching 3D model for each object name. `model.gltf` is still supported as a fallback during migration.
|
- `public/models/{name}/model.glb` contains the matching 3D model for each object name. `model.gltf` is still supported as a fallback during migration.
|
||||||
- Missing models are displayed as gray fallback cubes, so incomplete maps remain editable.
|
- Missing models are displayed as gray fallback cubes, so incomplete maps remain editable.
|
||||||
|
|
||||||
## Map Node Format
|
## Map Node Format
|
||||||
|
|
||||||
Each entry in `public/map.json` represents one object:
|
`public/map.json` is hierarchical. Group nodes such as `Scene`, `blocking`, `vegetation`, or `agriculture` organize the map. Editable object nodes still use the same transform fields:
|
||||||
|
|
||||||
| Field | Description |
|
| Field | Description |
|
||||||
| ---------- | ------------------------------------------------- |
|
| ---------- | ------------------------------------------------- |
|
||||||
@@ -45,17 +45,33 @@ Only the `Editor` group is open by default. Open the other groups when you need
|
|||||||
|
|
||||||
1. Open `/editor` in the local app.
|
1. Open `/editor` in the local app.
|
||||||
2. Click an object in the scene to select it.
|
2. Click an object in the scene to select it.
|
||||||
3. Choose a transform mode: translate, rotate, or scale.
|
3. Use `Shift + right click` on other objects to add or remove them from the current multi-selection.
|
||||||
4. Drag the transform gizmo in the 3D view.
|
4. Choose a transform mode: translate, rotate, or scale.
|
||||||
5. Check the JSON inspector if you need exact values.
|
5. Drag the transform gizmo in the 3D view. With multiple objects selected, the gizmo transforms the selected group and writes each object transform back to `map.json`.
|
||||||
6. Use undo or redo if the transform is not correct.
|
6. Keep `Snap terrain on move` enabled when placing objects on the terrain.
|
||||||
7. Export the JSON or save it to the dev server.
|
7. Use `Center on object` or `Reset camera` from the `View` section when navigating large maps.
|
||||||
|
8. Adjust scale numerically from the `Selection` section if the gizmo is not precise enough.
|
||||||
|
9. Check the JSON inspector if you need exact values.
|
||||||
|
10. Use undo or redo if the transform is not correct.
|
||||||
|
11. Export the JSON or save it to the dev server.
|
||||||
|
|
||||||
|
## Adding And Deleting Nodes
|
||||||
|
|
||||||
|
Use `Add Node` to create a new editable object under the `blocking` group.
|
||||||
|
|
||||||
|
- The new object starts as a fallback cube at `[0, 0, 0]`.
|
||||||
|
- Name it with the model folder name you want, for example `maison1`.
|
||||||
|
- If `public/models/{name}/model.glb` or `model.gltf` exists, saving and reloading will display the matching model.
|
||||||
|
- If no matching model exists, the node stays editable as a gray cube.
|
||||||
|
|
||||||
|
Use the trash button in `Selection` to delete the selected node from the map tree.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
| Action | Input |
|
| Action | Input |
|
||||||
| -------------------- | -------------------------- |
|
| -------------------- | -------------------------- |
|
||||||
| Select object | Click object |
|
| Select object | Click object |
|
||||||
|
| Toggle multi-select | `Shift` + right click |
|
||||||
| Deselect | `Esc` or click empty space |
|
| Deselect | `Esc` or click empty space |
|
||||||
| Lock selection | `Lock` button in Selection |
|
| Lock selection | `Lock` button in Selection |
|
||||||
| Clear selection | `X` button in Selection |
|
| Clear selection | `X` button in Selection |
|
||||||
@@ -73,9 +89,21 @@ Only the `Editor` group is open by default. Open the other groups when you need
|
|||||||
The `Selection` section shows the selected object name and its index in `public/map.json`.
|
The `Selection` section shows the selected object name and its index in `public/map.json`.
|
||||||
|
|
||||||
- Click an object to select it.
|
- Click an object to select it.
|
||||||
|
- Use `Shift + right click` on objects to add or remove them from a multi-selection.
|
||||||
|
- When several objects are selected, the gizmo appears on the selection group and applies translate, rotate, or scale to each selected node.
|
||||||
- Click empty space or press `Esc` to clear the selection.
|
- Click empty space or press `Esc` to clear the selection.
|
||||||
- Use the `X` button to clear the selection explicitly.
|
- Use the `X` button to clear the selection explicitly.
|
||||||
- Use the `Lock` button to protect the current selection while editing.
|
- Use the `Lock` button to protect the current selection while editing.
|
||||||
|
- Use the scale fields to edit X/Y/Z scale precisely.
|
||||||
|
- Use the trash button to remove the selected object.
|
||||||
|
|
||||||
|
## Terrain Snapping
|
||||||
|
|
||||||
|
`Snap terrain on move` is enabled by default. When you move an object, the editor samples the terrain height at the object's X/Z position and updates its Y position.
|
||||||
|
|
||||||
|
This is intended for map objects that should sit on the ground. Disable it when you intentionally need a floating object.
|
||||||
|
|
||||||
|
`Lock terrain` is also enabled by default. The terrain stays visible, but terrain clicks are ignored so normal objects remain easier to select. Disable it only when you need to select or transform the terrain node itself.
|
||||||
|
|
||||||
When selection is locked:
|
When selection is locked:
|
||||||
|
|
||||||
@@ -88,9 +116,11 @@ When selection is locked:
|
|||||||
|
|
||||||
The `Lock view` action switches the editor into a movement mode closer to the runtime player camera. Use it to navigate larger scenes while keeping the transform tools available.
|
The `Lock view` action switches the editor into a movement mode closer to the runtime player camera. Use it to navigate larger scenes while keeping the transform tools available.
|
||||||
|
|
||||||
|
The camera action switches between `Center on object` and `Reset camera`. Selecting an object also focuses the camera on that object automatically.
|
||||||
|
|
||||||
## JSON Inspector
|
## JSON Inspector
|
||||||
|
|
||||||
The `JSON` section shows the raw map data that will be exported or saved:
|
The `JSON` section shows the editable node data:
|
||||||
|
|
||||||
- When no object is selected, it shows the full map node list.
|
- When no object is selected, it shows the full map node list.
|
||||||
- When an object is selected, it highlights the JSON lines for that object.
|
- When an object is selected, it highlights the JSON lines for that object.
|
||||||
@@ -101,11 +131,11 @@ Use it to verify exact numeric transform values before saving or exporting. The
|
|||||||
|
|
||||||
### Export JSON
|
### Export JSON
|
||||||
|
|
||||||
`Export JSON` downloads the current map node list as `map.json`. Use this when you want to manually replace `public/map.json`.
|
`Export JSON` downloads the current hierarchical map tree as `map.json`. Use this when you want to manually replace `public/map.json`.
|
||||||
|
|
||||||
### Save To Server
|
### Save To Server
|
||||||
|
|
||||||
`Save to server` is available only during local development. It writes the edited map back to `public/map.json` through the Vite dev-server endpoint.
|
`Save to server` is available only during local development. It writes the edited hierarchical map back to `public/map.json` through the Vite dev-server endpoint.
|
||||||
|
|
||||||
The button is hidden in production builds because production persistence is not implemented.
|
The button is hidden in production builds because production persistence is not implemented.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+38157
-32639
File diff suppressed because it is too large
Load Diff
+35051
File diff suppressed because it is too large
Load Diff
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.
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.
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.
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.
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.
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.
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.
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.
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.
Binary file not shown.
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