diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index c787ccb..6759cea 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -74,12 +74,12 @@ jobs: - name: 📏 Check bundle size run: | - # Get bundle size in KB - SIZE=$(du -k dist | cut -f1) + # Check generated app assets only; public/ model files are runtime assets copied to dist. + SIZE=$(du -k dist/assets | cut -f1) echo "Bundle size: ${SIZE}KB" - # Threshold: 1000KB (configurable) - THRESHOLD=1000 + # Threshold: 5000KB (configurable) + THRESHOLD=5000 if [ "$SIZE" -gt "$THRESHOLD" ]; then echo "❌ Bundle size ${SIZE}KB exceeds threshold ${THRESHOLD}KB" diff --git a/.gitignore b/.gitignore index d6b00ff..9b04b43 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,8 @@ Thumbs.db # 3D Assets Cache (drei, GLTFJSX) .drei/ -.glitchdrei-cache/ \ No newline at end of file +.glitchdrei-cache/ + +# Temporaire +.backend/ +backend/ diff --git a/docs/technical/architecture.md b/docs/technical/architecture.md index 769575d..b10ff54 100644 --- a/docs/technical/architecture.md +++ b/docs/technical/architecture.md @@ -4,13 +4,16 @@ This document describes the code that exists today in the repository. ## Runtime Structure -- `src/App.tsx` mounts the `Canvas`, the 3D `World`, the debug perf overlay, and the HTML overlays. +- `src/main.tsx` mounts React and wraps the app in `BrowserRouter`. +- `src/App.tsx` declares the top-level routes: + - `/` mounts the playable 3D scene, debug perf overlay, and HTML overlays. + - `/editor` mounts the map editor page. - `src/world/World.tsx` composes the active scene, including: - environment and lighting - debug helpers and debug camera mode - either the map scene or the debug physics test scene - the player rig when the active camera mode is `player` -- `src/world/Map.tsx` loads the main map model and builds the collision octree. +- `src/world/GameMap.tsx` loads map nodes from `public/map.json`, resolves available models, and builds the collision octree. - `src/world/debug/TestScene.tsx` provides a debug-oriented interaction and physics scene. - `src/world/player/PlayerComponent.tsx` mounts the camera and controller. - `src/world/player/PlayerController.tsx` owns pointer lock movement, jump handling, and interaction input. @@ -38,6 +41,26 @@ This document describes the code that exists today in the repository. - `src/utils/debug/scene/DebugHelpers.tsx` mounts debug helpers. - `src/utils/debug/scene/DebugCameraControls.tsx` mounts the free debug camera. +## Editor System + +- `src/pages/editor/EditorPage.tsx` is the route-level editor page for `/editor`. +- `src/features/editor/components/EditorControls.tsx` renders the HTML editor control panel. +- `src/features/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, shortcuts, and map rendering. +- `src/features/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. +- `src/features/editor/controls/FlyController.tsx` provides player-style editor navigation. +- `src/features/editor/hooks/useEditorSceneData.ts` loads scene data and handles folder upload fallback. +- `src/features/editor/hooks/useEditorHistory.ts` owns editor undo and redo state. +- `src/utils/editor/loadEditorScene.ts` handles editor-only folder upload parsing. +- `src/utils/loadMapSceneData.ts` is shared by the game scene and editor to load `public/map.json` and resolve model URLs. +- `src/types/editor.ts` contains the shared `MapNode`, `SceneData`, and `TransformMode` types. + +## Map Data + +- `public/map.json` is expected to be a `MapNode[]`. +- Each map node `name` maps to `public/models/{name}/model.gltf`. +- The editor renders a fallback cube for missing models. +- The game scene filters out nodes whose model cannot be resolved. + ## Current Limitations - The repository is still a prototype, not the full intended game runtime. @@ -45,3 +68,4 @@ This document describes the code that exists today in the repository. - There is no central gameplay orchestrator such as `GameManager` yet. - Missions, zones, cinematics, and dialogue systems are not implemented. - The player uses octree collision and simple movement rules, not a complete gameplay physics stack. +- Editor save-to-server is implemented as a Vite dev-server plugin, not a production backend API. diff --git a/docs/technical/editor.md b/docs/technical/editor.md new file mode 100644 index 0000000..35e9761 --- /dev/null +++ b/docs/technical/editor.md @@ -0,0 +1,143 @@ +# Editor Technical Notes + +This document describes the map editor that exists in the current codebase. + +## 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. + +## Routing + +- `/` renders the playable La-Fabrik scene. +- `/editor` renders the map editor. +- `src/main.tsx` wraps the app with `BrowserRouter`. +- `src/App.tsx` defines the route and imports `EditorPage` from `src/pages/editor/EditorPage.tsx`. + +## File Structure + +```txt +src/ +├── pages/ +│ └── editor/ +│ └── EditorPage.tsx +├── features/ +│ └── editor/ +│ ├── components/ +│ │ └── EditorControls.tsx +│ ├── controls/ +│ │ └── FlyController.tsx +│ ├── hooks/ +│ │ ├── useEditorHistory.ts +│ │ └── useEditorSceneData.ts +│ ├── scene/ +│ │ ├── EditorMap.tsx +│ │ └── EditorScene.tsx +├── types/ +│ └── editor.ts +└── utils/ + ├── editor/ + │ └── loadEditorScene.ts + └── loadMapSceneData.ts +``` + +## Responsibilities + +`src/pages/editor/EditorPage.tsx` is the route-level composition component. It owns route-specific state such as selected object, hovered object, transform mode, and player-mode toggle. + +`src/features/editor/hooks/useEditorSceneData.ts` loads the default map data and handles folder uploads. + +`src/features/editor/hooks/useEditorHistory.ts` owns editor undo and redo history. + +`src/features/editor/scene/EditorScene.tsx` composes the editor canvas scene, camera controls, lights, keyboard shortcuts, and `EditorMap`. + +`src/features/editor/scene/EditorMap.tsx` renders map nodes, fallback cubes, selection highlighting, and transform controls. + +`src/features/editor/components/EditorControls.tsx` renders the HTML control panel outside the canvas. + +`src/features/editor/controls/FlyController.tsx` provides editor movement controls for player-style navigation. + +`src/utils/loadMapSceneData.ts` is shared by the game map and editor. It loads `/map.json` and resolves available `public/models/{name}/model.gltf` files. + +`src/utils/editor/loadEditorScene.ts` contains editor-only upload handling for user-selected folders. + +## Data Format + +The shared editor type lives in `src/types/editor.ts`. + +```ts +interface MapNode { + name: string; + type: string; + position: [number, number, number]; + rotation: [number, number, number]; + scale: [number, number, number]; +} +``` + +`public/map.json` is expected to be a `MapNode[]`. + +```json +[ + { + "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: + +```txt +public/ +├── map.json +└── models/ + └── pylone/ + └── model.gltf +``` + +If a model is missing, the editor renders a fallback cube so the node can still be selected and transformed. + +## Editor Flow + +1. `EditorPage` mounts on `/editor`. +2. `useEditorSceneData` calls `loadMapSceneData()`. +3. `loadMapSceneData()` loads `/map.json` and available model URLs. +4. If `/map.json` is missing, the page displays a folder-upload flow. +5. `EditorScene` renders the grid, lights, camera controls, and map nodes. +6. `EditorControls` exposes transform mode, history actions, export, save, and selection info. + +## Controls + +- Click: select a node. +- `Esc`: clear selection. +- `T`: translate mode. +- `R`: rotate mode. +- `S`: scale mode. +- `Ctrl+Z` or `Cmd+Z`: undo. +- `Ctrl+Y` or `Cmd+Y`: redo. +- `WASD`, `ZQSD`, or arrow keys: move in player-controller mode. +- `Space`: move upward in player-controller mode. +- `Shift`: move downward in player-controller mode. + +## Saving And Exporting + +The editor supports two output paths: + +- Export JSON downloads the current `MapNode[]` as `map.json`. +- Save to Server posts the current `MapNode[]` 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. + +## Styling + +Editor styles are in `src/index.css` under the `/* Editor page */` section. Classes are prefixed with `editor-` to avoid collisions with the game UI. + +## Known Limitations + +- Uploaded model object URLs are not currently revoked after replacement or unmount. +- Large `map.json` files may need virtualization, culling, or LOD support later. +- There is no snap-to-grid, duplication, material editing, or object creation workflow yet. +- Save to Server is a Vite dev-server helper, not a production backend API. diff --git a/docs/user/features.md b/docs/user/features.md index 133c126..a04fb80 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -5,7 +5,7 @@ This document lists features that are implemented in the current codebase. ## Scene - Fullscreen React Three Fiber scene -- Main map scene loaded from `public/models/map/model.gltf` +- Main map scene loaded from `public/map.json` and matching `public/models/{name}/model.gltf` assets - Debug physics test scene selectable from the debug panel - Ambient and directional lighting - Environment background setup @@ -38,6 +38,20 @@ This document lists features that are implemented in the current codebase. - Free debug camera - `r3f-perf` overlay +## Map Editor + +- `/editor` route for inspecting and editing `public/map.json` +- Automatic loading of `public/map.json` when available +- Folder upload fallback when `map.json` is missing +- Rendering of available `public/models/{name}/model.gltf` assets +- Fallback cubes for nodes whose model is missing +- Object selection by click +- Transform modes for translate, rotate, and scale +- Keyboard shortcuts for `T`, `R`, `S`, `Esc`, undo, and redo +- Player-style navigation mode with `WASD`, `ZQSD`, arrow keys, `Space`, and `Shift` +- JSON export for downloading the edited map +- Dev-server save endpoint for writing changes back to `public/map.json` + ## Not Implemented Yet - mission system @@ -47,3 +61,4 @@ This document lists features that are implemented in the current codebase. - loading flow - minimap and mission HUD - full production separation between gameplay and debug scenes +- production backend persistence for editor saves diff --git a/package-lock.json b/package-lock.json index 4fbc348..1481516 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "@react-three/rapier": "^2.2.0", "gsap": "^3.15.0", "lil-gui": "^0.21.0", + "lucide-react": "^1.11.0", "r3f-perf": "^7.2.3", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-router-dom": "^7.5.0", "three": "^0.183.2" }, "devDependencies": { @@ -35,6 +37,9 @@ "typescript": "~6.0.2", "typescript-eslint": "^8.58.0", "vite": "^8.0.4" + }, + "engines": { + "node": ">=20.19.0 || >=22.12.0" } }, "node_modules/@babel/code-frame": { @@ -902,9 +907,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -922,9 +924,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -942,9 +941,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -962,9 +958,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -982,9 +975,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1002,9 +992,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1845,6 +1832,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -2751,9 +2751,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2775,9 +2772,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2799,9 +2793,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2823,9 +2814,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2932,6 +2920,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz", + "integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/maath": { "version": "0.10.8", "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", @@ -3492,6 +3489,44 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-router": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz", + "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz", + "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.14.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-use-measure": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", @@ -3583,6 +3618,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index 3a737f0..9e1cef4 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,11 @@ "@react-three/rapier": "^2.2.0", "gsap": "^3.15.0", "lil-gui": "^0.21.0", + "lucide-react": "^1.11.0", "r3f-perf": "^7.2.3", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-router-dom": "^7.5.0", "three": "^0.183.2" }, "devDependencies": { @@ -41,5 +43,8 @@ "typescript": "~6.0.2", "typescript-eslint": "^8.58.0", "vite": "^8.0.4" + }, + "engines": { + "node": ">=20.19.0 || >=22.12.0" } } diff --git a/public/map.json b/public/map.json new file mode 100644 index 0000000..25619f0 --- /dev/null +++ b/public/map.json @@ -0,0 +1,4587 @@ +[ + { + "name": "Neutre", + "type": "Object3D", + "position": [-0.6455, 0, 5.6812], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "lac", + "type": "Mesh", + "position": [-0.6455, 0, 5.6812], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "arbres_quartier", + "type": "Object3D", + "position": [-0.699, 8.2885, 12.0707], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "arbres_quartier", + "type": "Object3D", + "position": [-0.8061, 24.8656, 24.8498], + "rotation": [0, -0.7007, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [64.2046, 1.3963, -66.2457], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-77.4301, 1.3912, -56.435], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [94.0948, 1.396, -13.7286], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [9.1941, 10.2941, 72.1876], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [85.6336, 2.3645, 16.4202], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-72.396, 5.1429, -14.0848], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [27.9832, 6.8786, -54.0086], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-8.7053, 20.947, 57.5741], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-89.8978, 1.4012, -25.8285], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [51.8989, 4.3513, -50.0921], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [33.235, 12.6737, 58.6656], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-17.6706, 6.3797, 81.7969], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [34.9999, 1.4037, 93.9609], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-61.8401, 11.0729, 13.0361], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [38.7701, 19.0344, 0.8338], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [59.3076, 6.5206, 49.1125], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-89.2687, 2.6346, 16.7745], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [17.8449, 19.0217, -19.9902], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [69.5646, 5.8304, -13.2384], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-39.7419, 20.8513, 23.7226], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [70.7249, 3.0111, -37.8449], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [38.1303, 19.7136, 34.2969], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-31.6587, 20.5164, -4.8425], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [65.6865, 1.3944, 77.4458], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [54.0114, 13.5402, 21.5344], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-49.1702, 11.9546, -18.5196], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [41.9237, 12.5818, -23.4932], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-1.7275, 1.4007, 102.2273], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [88.0982, 1.3938, 45.9656], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "mc", + "type": "Mesh", + "position": [46.9851, 3.9982, 69.8056], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeubles", + "type": "Object3D", + "position": [-62.3322, 8.2405, -10.6914], + "rotation": [-3.1416, -1.1003, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-42.2765, 22.5511, 18.1635], + "rotation": [0, 1.2167, 0], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [-74.1764, 4.7725, -44.3093], + "rotation": [0, 1.0979, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble", + "type": "Mesh", + "position": [-75.8161, 3.7884, -41.1777], + "rotation": [0, 1.0979, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble", + "type": "Mesh", + "position": [-72.5368, 5.7567, -47.441], + "rotation": [0, 1.0979, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-62.3173, 11.6774, -9.3092], + "rotation": [0, 1.0595, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-85.6865, 4.7725, -20.4085], + "rotation": [0, 1.5621, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-65.7762, 12.2088, 9.2812], + "rotation": [0, 0.8527, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_22", + "type": "Mesh", + "position": [-53.6087, 11.6774, -26.0884], + "rotation": [0, 0.7698, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_22", + "type": "Mesh", + "position": [-40.1599, 22.5511, 6.2908], + "rotation": [0, 1.2682, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-34.6253, 22.5511, -4.4192], + "rotation": [0, 1.423, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-89.7126, 5.0587, 5.8019], + "rotation": [0, 1.0307, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeubles", + "type": "Object3D", + "position": [-3.8031, 9.4131, 71.852], + "rotation": [3.1416, 0.1131, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [-3.8544, 22.5511, 59.6426], + "rotation": [0, 0.0613, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [-7.3845, 21.5669, 59.8259], + "rotation": [0, 0.0613, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-0.3242, 23.5352, 59.4593], + "rotation": [0, 0.0613, 0], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2_1", + "type": "Object3D", + "position": [-7.4612, 5.0001, 95.492], + "rotation": [0, 0.0402, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [-10.9945, 4.0159, 95.6007], + "rotation": [0, 0.0402, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [-3.928, 5.9843, 95.3834], + "rotation": [0, 0.0402, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_22", + "type": "Mesh", + "position": [-5.7841, 11.9398, 75.0631], + "rotation": [0, -0.0411, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeubles", + "type": "Object3D", + "position": [58.3738, -0.4334, -15.1953], + "rotation": [-3.1416, 1.1456, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2_0", + "type": "Object3D", + "position": [93.8738, 4.1985, -8.5653], + "rotation": [-3.1416, 1.2256, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [95.0387, 3.2144, -5.2279], + "rotation": [3.1416, 1.2255, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [92.709, 5.1827, -11.9028], + "rotation": [-3.1416, 1.2256, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2_1", + "type": "Object3D", + "position": [34.1654, 9.4199, -51.5725], + "rotation": [0, 1.4871, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [33.8368, 8.4357, -48.0529], + "rotation": [0, 1.4871, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [34.4941, 10.404, -55.0921], + "rotation": [0, 1.4871, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_22_2", + "type": "Mesh", + "position": [69.7762, 9.4199, 18.3362], + "rotation": [-3.1416, 1.1906, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [41.9036, 19.8047, -4.3543], + "rotation": [0, 1.4051, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [41.2876, 18.8206, -0.8735], + "rotation": [0, 1.4051, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [42.5195, 20.7889, -7.8352], + "rotation": [0, 1.4051, 0], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1_4", + "type": "Mesh", + "position": [69.1598, 9.4199, -2.3765], + "rotation": [-3.1416, 1.4482, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [93.8789, 4.1985, 19.927], + "rotation": [3.1416, 0.3786, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [69.4247, 4.1985, -59.3345], + "rotation": [-3.1416, 1.5089, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [50.5352, 9.4199, -38.8631], + "rotation": [3.1416, 0.8956, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [52.7187, 8.4357, -36.0832], + "rotation": [3.1416, 0.8956, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [48.3518, 10.404, -41.6431], + "rotation": [3.1416, 0.8956, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1_8", + "type": "Mesh", + "position": [46.4293, 19.8047, 8.6568], + "rotation": [-3.1416, 1.3224, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [33.7611, 19.8047, -15.4911], + "rotation": [3.1416, 0.809, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [36.1767, 18.8206, -12.9102], + "rotation": [3.1416, 0.809, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [31.3456, 20.7889, -18.0719], + "rotation": [3.1416, 0.809, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [62.5434, 9.4199, -21.9889], + "rotation": [3.1416, 0.9129, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [64.6785, 8.4357, -19.1716], + "rotation": [3.1416, 0.9128, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [60.4084, 10.404, -24.8062], + "rotation": [3.1416, 0.9129, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [46.879, 19.8047, 22.4645], + "rotation": [3.1416, 0.9771, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [48.8288, 18.8206, 25.4131], + "rotation": [3.1416, 0.9771, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [44.9291, 20.7889, 19.516], + "rotation": [3.1416, 0.9771, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe2", + "type": "Object3D", + "position": [22.7244, 19.8047, -23.8431], + "rotation": [3.1416, 0.6712, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [25.4717, 18.8206, -21.6186], + "rotation": [3.1416, 0.6712, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [19.9772, 20.7889, -26.0676], + "rotation": [3.1416, 0.6712, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [85.4754, 4.1985, -35.7925], + "rotation": [-3.1416, 1.3009, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "zone_domaine", + "type": "Object3D", + "position": [-16.0844, 5.1349, -46.2022], + "rotation": [0, -1.3459, 0], + "scale": [1, 1, 1] + }, + { + "name": "champ2", + "type": "Object3D", + "position": [3.4286, 16.0553, -30.2701], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [11.4121, 3.9653, -66.3569], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-70.8139, 0.3267, -62.2507], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-14.3249, 3.5365, -68.212], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [24.0599, 4.8852, -59.538], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-52.9323, 4.7206, -42.7644], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-31.704, 4.6976, -57.309], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-25.9223, 3.5026, -65.0315], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-5.4992, 4.7648, -63.6341], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-40.2304, 0.7972, -75.3914], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-38.3843, 1.4111, -71.7936], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-37.0816, 0.7509, -76.9614], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-34.1436, 0.7514, -78.1219], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-29.7402, 1.313, -75.8371], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-30.9037, 0.7698, -79.306], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-16.8479, 0.7778, -83.3324], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-16.1993, 1.345, -79.5906], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-13.4224, 0.7561, -83.8093], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-10.2307, 0.754, -84.0774], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-6.5395, 1.2827, -80.7849], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-6.5723, 0.7644, -84.3157], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-2.4983, 0.4448, -88.0723], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-2.4407, 0.7758, -84.5676], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-6.5447, 0.4457, -87.7718], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-6.6798, 0.3279, -91.2671], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-10.2825, 0.4466, -87.4858], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-10.4639, 0.328, -90.9794], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-14.1131, 0.3277, -90.6931], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-17.854, 0.3273, -90.3823], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-21.9187, 0.3273, -89.8092], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-26.0047, 0.3278, -88.6636], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-29.7266, 0.3282, -87.23], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-33.1971, 0.3284, -85.8711], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-36.5348, 0.3283, -84.5591], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-39.8565, 0.3279, -83.2413], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-43.3585, 0.3276, -81.8413], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-47.1477, 0.3277, -80.111], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-50.9789, 0.3277, -77.9043], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-54.6075, 0.3281, -75.352], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-58.015, 0.3286, -72.6911], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-61.2668, 0.3291, -70.0898], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-64.4716, 0.3293, -67.5244], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-67.6622, 0.3286, -64.9362], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-62.1519, 0.4755, -64.6412], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-65.1778, 0.4774, -62.0524], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-62.5731, 0.8884, -59.2255], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-65.2583, 0.9221, -56.6381], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-68.0021, 0.4844, -59.3904], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-62.6398, 1.5402, -53.9245], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-60.1295, 2.2225, -51.1926], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-57.6942, 2.9614, -48.4318], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-55.3051, 3.7904, -45.6226], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-55.0285, 2.9288, -50.7766], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-52.6408, 3.7656, -47.8775], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-49.9983, 3.7437, -50.0915], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-47.6432, 4.6824, -47.0805], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-50.2744, 4.6994, -44.9432], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-45.0479, 4.6733, -49.1549], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-42.5034, 4.6771, -51.1238], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-40.0021, 4.6962, -52.9499], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-37.4182, 4.7118, -54.5714], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-34.6659, 4.7103, -55.9908], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-36.3713, 3.801, -59.4048], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-39.3483, 3.7876, -57.8793], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-41.2833, 2.9441, -61.2257], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-42.1287, 3.7586, -56.1669], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-44.2741, 2.8999, -59.453], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-46.3307, 2.1209, -62.8234], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-47.0518, 2.8696, -57.4896], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-49.3098, 2.0824, -60.781], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-51.471, 1.3905, -63.9422], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-52.1045, 2.0956, -58.4714], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-54.4709, 1.4066, -61.4374], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-56.7543, 0.8449, -64.3148], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-57.2765, 1.4443, -58.9232], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-59.7579, 0.8624, -61.7795], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-54.7976, 2.1355, -56.0498], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-57.4656, 2.1801, -53.6207], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-59.9754, 1.4918, -56.428], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-55.7722, 0.4691, -69.7848], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-53.5905, 0.8371, -66.8875], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-58.9988, 0.4715, -67.199], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-52.4153, 0.467, -72.3871], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-50.2966, 0.8397, -69.3921], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-46.9317, 0.8543, -71.5878], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-45.0136, 1.4625, -68.1254], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-48.2887, 1.4158, -66.2071], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-41.7242, 1.4734, -69.9029], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-39.8703, 2.205, -66.2953], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-43.1555, 2.1795, -64.6226], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-36.5275, 2.1843, -67.9115], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-34.8539, 2.9593, -64.334], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-38.1084, 2.9689, -62.8268], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-33.248, 3.7921, -60.8149], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-43.5975, 0.8525, -73.4912], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-45.4034, 0.4645, -76.8627], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-48.9279, 0.4658, -74.8171], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-49.7218, 2.8713, -55.3626], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-52.3728, 2.8954, -53.1058], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-44.7745, 3.7336, -54.2644], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-47.382, 3.7303, -52.2273], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-41.8617, 0.4583, -78.548], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-38.4801, 0.4512, -79.9867], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-35.2796, 0.449, -81.2835], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-31.9888, 0.4499, -82.5702], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-28.5374, 0.4485, -83.9109], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-24.8823, 0.4458, -85.2594], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-23.9168, 0.7906, -81.7579], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-27.4483, 0.7856, -80.5556], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-23.0257, 1.347, -78.1146], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-26.3784, 1.3319, -77.0358], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-22.0995, 2.0236, -74.3895], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-25.2687, 2.0056, -73.3753], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-24.1384, 2.7357, -69.717], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-27.1911, 2.7231, -68.6173], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-28.4527, 1.999, -72.195], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-21.1282, 2.7564, -70.6683], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-20.1171, 3.5359, -67.0021], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-22.9884, 3.5156, -66.085], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-17.281, 3.5474, -67.7013], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-18.0783, 2.7729, -71.3947], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-15.5468, 2.0461, -75.6822], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-14.9242, 2.7669, -71.94], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-18.8661, 2.0465, -75.1275], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-19.6373, 1.3657, -78.91], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-20.3814, 0.7975, -82.654], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-21.1303, 0.4462, -86.2784], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-17.4084, 0.4483, -86.8292], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-13.7939, 0.4485, -87.1824], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [17.1237, 1.8611, -75.7969], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [23.493, 2.606, -70.255], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [19.8781, 2.545, -71.3996], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [27.0851, 2.6732, -69.0737], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [26.0441, 3.3494, -65.8794], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [22.5075, 3.284, -67.0215], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [25.0415, 4.0826, -62.7099], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [5.6979, 1.2772, -81.2456], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [1.6222, 1.2936, -81.239], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [1.597, 1.9083, -77.7099], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-2.3509, 1.9098, -77.5042], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-2.4034, 1.2957, -81.0285], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-2.2823, 2.5523, -74.0563], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-6.1525, 2.5503, -73.7771], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-6.3502, 1.9081, -77.2279], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-5.9449, 3.2322, -70.373], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-5.7289, 3.9722, -66.9965], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-2.2046, 3.2338, -70.6485], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-2.1182, 3.9748, -67.2496], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [1.3889, 3.9849, -67.383], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [1.3115, 4.7827, -63.9926], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-2.011, 4.7703, -63.8656], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [4.5112, 4.7923, -63.9186], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [7.6364, 4.787, -63.5674], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [10.7761, 4.7752, -62.9861], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [14.0162, 4.774, -62.2665], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [17.3227, 4.7909, -61.4448], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [20.6662, 4.8285, -60.5336], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [21.572, 4.0223, -63.7816], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [18.1432, 3.9811, -64.7571], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [18.9813, 3.231, -68.0901], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [14.7627, 3.9635, -65.6126], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [15.4971, 3.2048, -69.012], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [16.2679, 2.507, -72.4232], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [12.031, 3.2046, -69.7865], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [12.6499, 2.5008, -73.2628], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [13.2941, 1.85, -76.7185], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [9.003, 2.5182, -73.8935], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [9.4167, 1.8628, -77.4042], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [5.3267, 2.5415, -74.2282], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [5.5209, 1.8889, -77.7351], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [5.063, 3.2371, -70.7487], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [1.4814, 3.2376, -70.8132], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [1.5601, 2.5524, -74.247], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [8.557, 3.2215, -70.3955], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [8.0938, 3.9809, -66.947], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [4.7769, 3.9917, -67.2987], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [9.8319, 1.2611, -80.8477], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "champ1", + "type": "Object3D", + "position": [3.4286, 16.0553, -30.2701], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_0", + "type": "Mesh", + "position": [21.9129, 7.0614, -52.1148], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_1", + "type": "Mesh", + "position": [14.67, 16.8504, -24.9758], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_2", + "type": "Mesh", + "position": [-4.1759, 16.8338, -27.1377], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_3", + "type": "Mesh", + "position": [-5.6179, 7.0424, -55.3704], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_4", + "type": "Mesh", + "position": [-5.4166, 7.9551, -52.5529], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_5", + "type": "Mesh", + "position": [-5.233, 8.8832, -49.7254], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_6", + "type": "Mesh", + "position": [-5.0653, 9.8424, -46.9005], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_7", + "type": "Mesh", + "position": [-4.9172, 10.8242, -44.0908], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_8", + "type": "Mesh", + "position": [-4.7873, 11.8151, -41.2835], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_9", + "type": "Mesh", + "position": [-4.6704, 12.8175, -38.4752], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_10", + "type": "Mesh", + "position": [-4.5659, 13.8223, -35.671], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_11", + "type": "Mesh", + "position": [-4.4628, 14.8249, -32.8511], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_12", + "type": "Mesh", + "position": [-4.3386, 15.8289, -30.0009], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_13", + "type": "Mesh", + "position": [-1.8656, 14.8461, -32.9701], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_14", + "type": "Mesh", + "position": [-1.8353, 15.84, -30.1425], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_15", + "type": "Mesh", + "position": [0.652, 15.8554, -30.2211], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_16", + "type": "Mesh", + "position": [0.61, 16.8412, -27.4298], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_17", + "type": "Mesh", + "position": [-1.7309, 16.8364, -27.3029], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_18", + "type": "Mesh", + "position": [2.8703, 16.8403, -27.3634], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_19", + "type": "Mesh", + "position": [5.1088, 16.8341, -27.063], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_20", + "type": "Mesh", + "position": [7.3875, 16.8324, -26.6201], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_21", + "type": "Mesh", + "position": [9.7701, 16.8341, -26.0953], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_22", + "type": "Mesh", + "position": [12.2114, 16.839, -25.5469], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_23", + "type": "Mesh", + "position": [12.8382, 15.8459, -28.2976], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_24", + "type": "Mesh", + "position": [10.3482, 15.8312, -28.8839], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_25", + "type": "Mesh", + "position": [10.8975, 14.8278, -31.665], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_26", + "type": "Mesh", + "position": [7.9139, 15.826, -29.4276], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_27", + "type": "Mesh", + "position": [8.37, 14.8169, -32.2443], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_28", + "type": "Mesh", + "position": [8.7977, 13.7982, -35.0653], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_29", + "type": "Mesh", + "position": [5.8653, 14.8316, -32.6886], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_30", + "type": "Mesh", + "position": [6.1799, 13.8139, -35.5349], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_31", + "type": "Mesh", + "position": [6.4862, 12.7916, -38.3722], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_32", + "type": "Mesh", + "position": [3.5377, 13.864, -35.7712], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_33", + "type": "Mesh", + "position": [3.7249, 12.8501, -38.6076], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_34", + "type": "Mesh", + "position": [3.909, 11.8411, -41.4432], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_35", + "type": "Mesh", + "position": [0.9278, 12.8715, -38.657], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_36", + "type": "Mesh", + "position": [1.0171, 11.8654, -41.4879], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_37", + "type": "Mesh", + "position": [1.1031, 10.8645, -44.3329], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_38", + "type": "Mesh", + "position": [-1.8864, 11.8368, -41.4212], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_39", + "type": "Mesh", + "position": [-1.9082, 10.8411, -44.2483], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_40", + "type": "Mesh", + "position": [4.2939, 9.8522, -47.1238], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_41", + "type": "Mesh", + "position": [4.0966, 10.838, -44.2907], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_42", + "type": "Mesh", + "position": [1.1699, 9.8746, -47.185], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_43", + "type": "Mesh", + "position": [1.2294, 8.9124, -50.0335], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_44", + "type": "Mesh", + "position": [-1.9519, 9.8553, -47.0793], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_45", + "type": "Mesh", + "position": [-2.0139, 8.8935, -49.9197], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_46", + "type": "Mesh", + "position": [4.7517, 7.97, -52.7667], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_47", + "type": "Mesh", + "position": [4.5126, 8.8984, -49.938], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_48", + "type": "Mesh", + "position": [1.2983, 7.9774, -52.8974], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_49", + "type": "Mesh", + "position": [1.3272, 7.051, -55.8159], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_50", + "type": "Mesh", + "position": [-2.0914, 7.9602, -52.7724], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_51", + "type": "Mesh", + "position": [-2.2051, 7.0431, -55.616], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_52", + "type": "Mesh", + "position": [4.9794, 7.0489, -55.6562], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_53", + "type": "Mesh", + "position": [8.6356, 7.0384, -55.0814], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_54", + "type": "Mesh", + "position": [12.1483, 7.037, -54.3652], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_55", + "type": "Mesh", + "position": [11.625, 7.9392, -51.6356], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_56", + "type": "Mesh", + "position": [8.2227, 7.9454, -52.3055], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_57", + "type": "Mesh", + "position": [11.1259, 8.8565, -48.8917], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_58", + "type": "Mesh", + "position": [7.8345, 8.866, -49.5271], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_59", + "type": "Mesh", + "position": [10.6221, 9.8029, -46.154], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_60", + "type": "Mesh", + "position": [7.4572, 9.8088, -46.7716], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_61", + "type": "Mesh", + "position": [10.1298, 10.778, -43.4129], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_62", + "type": "Mesh", + "position": [7.1095, 10.7832, -44], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_63", + "type": "Mesh", + "position": [9.6719, 11.7716, -40.6454], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_64", + "type": "Mesh", + "position": [6.7926, 11.7815, -41.1878], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_65", + "type": "Mesh", + "position": [12.5261, 11.8016, -39.937], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_66", + "type": "Mesh", + "position": [11.9779, 12.8073, -37.1885], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_67", + "type": "Mesh", + "position": [9.2319, 12.7794, -37.865], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_68", + "type": "Mesh", + "position": [14.7089, 12.8482, -36.4744], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_69", + "type": "Mesh", + "position": [14.0837, 13.8538, -33.7567], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_70", + "type": "Mesh", + "position": [11.4354, 13.8193, -34.4327], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_71", + "type": "Mesh", + "position": [16.7472, 13.8935, -33.0643], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_72", + "type": "Mesh", + "position": [16.0544, 14.8874, -30.3714], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_73", + "type": "Mesh", + "position": [13.4646, 14.853, -31.0333], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_74", + "type": "Mesh", + "position": [15.3574, 15.8734, -27.6679], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_75", + "type": "Mesh", + "position": [17.4423, 12.8936, -35.747], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_76", + "type": "Mesh", + "position": [18.1474, 11.8961, -38.4316], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_77", + "type": "Mesh", + "position": [18.8646, 10.9073, -41.1207], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_78", + "type": "Mesh", + "position": [19.595, 9.9238, -43.818], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_79", + "type": "Mesh", + "position": [20.3535, 8.9572, -46.5394], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_80", + "type": "Mesh", + "position": [21.1388, 8.0082, -49.3067], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_81", + "type": "Mesh", + "position": [17.3672, 8.9059, -47.3638], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_82", + "type": "Mesh", + "position": [18.0703, 7.9654, -50.1244], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_83", + "type": "Mesh", + "position": [18.7235, 7.0449, -52.8776], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_84", + "type": "Mesh", + "position": [14.906, 7.9444, -50.8933], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_85", + "type": "Mesh", + "position": [15.488, 7.0391, -53.6161], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_86", + "type": "Mesh", + "position": [14.308, 8.8686, -48.1541], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_87", + "type": "Mesh", + "position": [16.6617, 9.8732, -44.6197], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_88", + "type": "Mesh", + "position": [13.6882, 9.828, -45.4074], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_89", + "type": "Mesh", + "position": [15.9923, 10.8561, -41.9015], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_90", + "type": "Mesh", + "position": [13.0896, 10.8098, -42.6729], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_91", + "type": "Mesh", + "position": [15.3455, 11.8465, -39.1897], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_92", + "type": "Mesh", + "position": [-1.8737, 12.8415, -38.6004], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_93", + "type": "Mesh", + "position": [0.8385, 13.8775, -35.8359], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_94", + "type": "Mesh", + "position": [-1.867, 13.8471, -35.7865], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_95", + "type": "Mesh", + "position": [3.3283, 14.8668, -32.9433], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_96", + "type": "Mesh", + "position": [0.736, 14.8722, -33.0245], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_97", + "type": "Mesh", + "position": [5.5185, 15.833, -29.8703], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_98", + "type": "Mesh", + "position": [3.1096, 15.8529, -30.1424], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_99", + "type": "Mesh", + "position": [-48.1453, 7.0266, -36.6113], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_100", + "type": "Mesh", + "position": [-33.0347, 16.8004, -14.704], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_101", + "type": "Mesh", + "position": [-16.4865, 16.8879, -24.3421], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_102", + "type": "Mesh", + "position": [-24.6472, 7.0777, -50.9985], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_103", + "type": "Mesh", + "position": [-27.9084, 7.0688, -49.7732], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_104", + "type": "Mesh", + "position": [-31.2571, 7.0698, -48.4518], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_105", + "type": "Mesh", + "position": [-34.5056, 7.0627, -46.7514], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_106", + "type": "Mesh", + "position": [-37.5152, 7.0447, -44.6891], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_107", + "type": "Mesh", + "position": [-40.3646, 7.0348, -42.5789], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_108", + "type": "Mesh", + "position": [-43.0557, 7.0312, -40.52], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_109", + "type": "Mesh", + "position": [-45.6294, 7.029, -38.5433], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_110", + "type": "Mesh", + "position": [-41.5311, 7.9215, -38.4189], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_111", + "type": "Mesh", + "position": [-44.0943, 7.9131, -36.4774], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_112", + "type": "Mesh", + "position": [-46.6055, 7.9043, -34.5707], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_113", + "type": "Mesh", + "position": [-42.5958, 8.8101, -34.3776], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_114", + "type": "Mesh", + "position": [-45.0878, 8.7914, -32.5179], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_115", + "type": "Mesh", + "position": [-43.6043, 9.7125, -30.4406], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_116", + "type": "Mesh", + "position": [-42.1392, 10.6664, -28.3315], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_117", + "type": "Mesh", + "position": [-40.682, 11.6354, -26.1926], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_118", + "type": "Mesh", + "position": [-39.2229, 12.631, -24.0008], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_119", + "type": "Mesh", + "position": [-37.7376, 13.656, -21.7488], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_120", + "type": "Mesh", + "position": [-36.2206, 14.6921, -19.4472], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_121", + "type": "Mesh", + "position": [-34.6597, 15.7371, -17.0928], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_122", + "type": "Mesh", + "position": [-34.0276, 14.7253, -20.9862], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_123", + "type": "Mesh", + "position": [-32.4957, 15.766, -18.6283], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_124", + "type": "Mesh", + "position": [-30.3881, 15.7807, -20.1706], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_125", + "type": "Mesh", + "position": [-28.805, 16.8165, -17.8519], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_126", + "type": "Mesh", + "position": [-30.8774, 16.8124, -16.2965], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_127", + "type": "Mesh", + "position": [-26.8283, 16.8209, -19.3325], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_128", + "type": "Mesh", + "position": [-24.945, 16.8329, -20.6418], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_129", + "type": "Mesh", + "position": [-23.0352, 16.8559, -21.7511], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_130", + "type": "Mesh", + "position": [-20.9621, 16.8724, -22.687], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_131", + "type": "Mesh", + "position": [-18.753, 16.8786, -23.5178], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_132", + "type": "Mesh", + "position": [-21.9378, 15.9357, -25.1001], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_133", + "type": "Mesh", + "position": [-19.6334, 15.959, -25.9432], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_134", + "type": "Mesh", + "position": [-20.4666, 15.0268, -28.4174], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_135", + "type": "Mesh", + "position": [-18.016, 15.0846, -29.1732], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_136", + "type": "Mesh", + "position": [-17.2806, 15.9942, -26.7251], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_137", + "type": "Mesh", + "position": [-18.7262, 14.134, -31.7247], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_138", + "type": "Mesh", + "position": [-19.4452, 13.1457, -34.354], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_139", + "type": "Mesh", + "position": [-20.1963, 12.1317, -37.0465], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_140", + "type": "Mesh", + "position": [-20.9938, 11.106, -39.7867], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_141", + "type": "Mesh", + "position": [-21.8426, 10.0757, -42.5596], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_142", + "type": "Mesh", + "position": [-22.7282, 9.0569, -45.3578], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_143", + "type": "Mesh", + "position": [-23.6575, 8.0608, -48.1799], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_144", + "type": "Mesh", + "position": [-26.8236, 8.0304, -47.0553], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_145", + "type": "Mesh", + "position": [-25.7939, 9.0113, -44.3233], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_146", + "type": "Mesh", + "position": [-28.8249, 8.9852, -43.1821], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_147", + "type": "Mesh", + "position": [-24.8051, 10.0266, -41.5784], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_148", + "type": "Mesh", + "position": [-27.7093, 9.9936, -40.5203], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_149", + "type": "Mesh", + "position": [-30.5427, 9.9297, -39.2823], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_150", + "type": "Mesh", + "position": [-26.6726, 11.0161, -37.8459], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_151", + "type": "Mesh", + "position": [-29.4226, 10.9336, -36.713], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_152", + "type": "Mesh", + "position": [-32.1298, 10.8228, -35.3214], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_153", + "type": "Mesh", + "position": [-28.3773, 11.9379, -34.1323], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_154", + "type": "Mesh", + "position": [-30.9822, 11.8199, -32.8411], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_155", + "type": "Mesh", + "position": [-33.4911, 11.7443, -31.2829], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_156", + "type": "Mesh", + "position": [-29.8479, 12.825, -30.3637], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_157", + "type": "Mesh", + "position": [-32.2576, 12.7464, -28.8854], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_158", + "type": "Mesh", + "position": [-34.6048, 12.701, -27.2649], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_159", + "type": "Mesh", + "position": [-31.0163, 13.7596, -26.4742], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_160", + "type": "Mesh", + "position": [-33.2633, 13.7202, -24.9117], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_161", + "type": "Mesh", + "position": [-29.7227, 14.778, -24.0562], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_162", + "type": "Mesh", + "position": [-31.8647, 14.7496, -22.5374], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_163", + "type": "Mesh", + "position": [-27.5462, 14.8319, -25.4367], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_164", + "type": "Mesh", + "position": [-26.305, 15.8294, -23.0074], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_165", + "type": "Mesh", + "position": [-28.3423, 15.7959, -21.6622], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_166", + "type": "Mesh", + "position": [-25.264, 14.919, -26.5971], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_167", + "type": "Mesh", + "position": [-24.174, 15.89, -24.1421], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_168", + "type": "Mesh", + "position": [-26.3048, 13.9381, -29.0867], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_169", + "type": "Mesh", + "position": [-23.8108, 14.0185, -30.0877], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_170", + "type": "Mesh", + "position": [-22.8914, 14.9865, -27.5703], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_171", + "type": "Mesh", + "position": [-24.7415, 13.0277, -32.638], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_172", + "type": "Mesh", + "position": [-22.1047, 13.0775, -33.5466], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé_173", + "type": "Mesh", + "position": [-21.2769, 14.0671, -30.9553], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol_174", + "type": "Mesh", + "position": [-25.6933, 12.0254, -35.2193], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja_175", + "type": "Mesh", + "position": [-22.9608, 12.0702, -36.1809], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-27.3388, 12.9411, -31.596], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-28.7111, 13.8315, -27.896], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-38.3133, 11.6684, -27.8836], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-35.9252, 11.7011, -29.5866], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-36.9157, 12.6672, -25.6275], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-39.7159, 10.6948, -30.0846], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-37.2643, 10.7232, -31.8548], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-38.6358, 9.763, -34.0729], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-36.0583, 9.7975, -35.9129], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-34.7547, 10.7587, -33.6372], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-37.4266, 8.8588, -38.1564], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-34.6681, 8.9017, -40.0286], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-33.3446, 9.8492, -37.7142], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-36.0646, 7.9667, -42.375], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-33.1048, 8.004, -44.2353], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-31.789, 8.9496, -41.7689], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-38.8688, 7.938, -40.3989], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-40.0542, 8.8274, -36.2638], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "tournesol", + "type": "Mesh", + "position": [-41.1398, 9.7386, -32.2455], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-35.494, 13.6905, -23.3247], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "blé", + "type": "Mesh", + "position": [-23.8565, 11.0527, -38.8595], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "soja", + "type": "Mesh", + "position": [-29.9994, 8.0221, -45.7926], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "buissons", + "type": "Object3D", + "position": [-20.2246, 3.1095, -70.1386], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-14.1379, 5.834, -59.1492], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-13.2719, 5.7979, -59.6663], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-12.4487, 5.7453, -60.2479], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-11.6734, 5.6764, -60.8906], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-10.9509, 5.5916, -61.5902], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-10.2855, 5.4916, -62.3426], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-9.6814, 5.3769, -63.143], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-9.1424, 5.2481, -63.9865], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-8.6716, 5.1063, -64.868], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-8.2721, 4.9521, -65.782], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.9463, 4.7865, -66.7228], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.6961, 4.6106, -67.6847], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.5232, 4.4254, -68.6617], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.4286, 4.2322, -69.6478], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.413, 4.032, -70.637], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.4762, 3.8261, -71.6231], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.6181, 3.6158, -72.6], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-7.8378, 3.4024, -73.5617], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-8.1337, 3.1872, -74.5024], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-8.5043, 2.9715, -75.4161], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-8.9471, 2.7566, -76.2973], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-9.4594, 2.5439, -77.1406], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-10.0381, 2.3347, -77.9406], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-10.6796, 2.1303, -78.6926], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-11.3799, 1.932, -79.3918], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-12.1348, 1.7408, -80.034], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-12.9395, 1.5582, -80.6151], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-13.7892, 1.385, -81.1317], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-14.6785, 1.2226, -81.5805], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-15.6021, 1.0717, -81.9588], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-16.5541, 0.9334, -82.2641], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-17.5287, 0.8086, -82.4948], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-18.52, 0.6979, -82.6492], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-19.5218, 0.6021, -82.7265], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-20.5279, 0.5217, -82.7262], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-21.5322, 0.4573, -82.6483], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-22.5284, 0.4093, -82.4933], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-23.5104, 0.3779, -82.262], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-24.4721, 0.3633, -81.9561], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-25.4076, 0.3657, -81.5773], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-26.3112, 0.3849, -81.128], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-27.1773, 0.421, -80.6109], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-28.0005, 0.4737, -80.0292], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-28.7757, 0.5426, -79.3866], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-29.4983, 0.6273, -78.6869], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-30.1636, 0.7274, -77.9346], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-30.7677, 0.8421, -77.1342], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-31.3068, 0.9708, -76.2906], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-31.7775, 1.1127, -75.4091], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.1771, 1.2669, -74.4952], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.5029, 1.4325, -73.5543], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.753, 1.6084, -72.5924], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.9259, 1.7935, -71.6154], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-33.0205, 1.9868, -70.6293], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-33.0362, 2.187, -69.6402], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.9729, 2.3928, -68.6541], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.831, 2.6031, -67.6771], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.6114, 2.8166, -66.7154], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-32.3154, 3.0318, -65.7748], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-31.9449, 3.2475, -64.861], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-31.5021, 3.4623, -63.9798], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-30.9898, 3.675, -63.1366], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-30.4111, 3.8842, -62.3365], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-29.7696, 4.0886, -61.5846], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-29.0692, 4.287, -60.8853], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-28.3144, 4.4781, -60.2432], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-27.5096, 4.6608, -59.662], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-26.66, 4.8339, -59.1454], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-25.7706, 4.9964, -58.6966], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-24.8471, 5.1472, -58.3184], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-23.8951, 5.2855, -58.013], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-22.9204, 5.4104, -57.7824], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-21.9291, 5.5211, -57.6279], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-20.9273, 5.6169, -57.5506], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-19.9212, 5.6972, -57.5509], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "buisson", + "type": "Mesh", + "position": [-15.0415, 5.8533, -58.6998], + "rotation": [0.1394, -1.3403, 0.3489], + "scale": [1, 1, 1] + }, + { + "name": "arbres", + "type": "Object3D", + "position": [4.6185, 12.2303, -52.5395], + "rotation": [0, -1.3459, 0], + "scale": [1, 1, 1] + }, + { + "name": "arbres", + "type": "Object3D", + "position": [12.6294, 12.2056, -51.0727], + "rotation": [3.1416, -1.2064, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [13.5023, 8.8974, -51.6228], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [12.2232, 11.4636, -44.1262], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [10.9763, 14.2168, -36.5025], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [9.7295, 17.0247, -28.7199], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-5.3958, 8.8974, -53.0006], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-4.9669, 11.4636, -45.3693], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-4.5494, 14.2168, -37.6189], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-4.1326, 17.0247, -29.7158], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-23.8928, 8.8974, -48.8971], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-21.7962, 11.4636, -41.6663], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-19.7509, 14.2168, -34.2931], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-17.7039, 17.0247, -26.7489], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-40.4273, 8.8974, -39.6637], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-36.858, 11.4636, -33.3286], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-33.3621, 14.2168, -26.8015], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre", + "type": "Mesh", + "position": [-29.8487, 17.0247, -20.0648], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "ferme_verti", + "type": "Mesh", + "position": [-20.5518, 8.1225, -70.6352], + "rotation": [3.1416, -1.1848, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "zone_energie", + "type": "Object3D", + "position": [-38.5694, 21.397, 50.3786], + "rotation": [3.1416, -1.1286, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "panneaux_solaires_", + "type": "Object3D", + "position": [-48.6802, 22.897, 32.2311], + "rotation": [-3.1416, -0.0649, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_0", + "type": "Mesh", + "position": [-70.4558, 20.086, 34.4879], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_1", + "type": "Mesh", + "position": [-71.9487, 16.01, 24.3689], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_2", + "type": "Mesh", + "position": [-60.0322, 21.2231, 32.7426], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_3", + "type": "Mesh", + "position": [-60.2651, 18.9485, 24.041], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_4", + "type": "Mesh", + "position": [-50.148, 22.724, 32.715], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_5", + "type": "Mesh", + "position": [-50.2549, 22.2507, 25.653], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_6", + "type": "Mesh", + "position": [-42.6015, 24.6195, 33.6543], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_7", + "type": "Mesh", + "position": [-43.5134, 25.004, 26.3826], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "panneaux_solaires", + "type": "Object3D", + "position": [-28.1161, 22.0484, 66.7736], + "rotation": [-3.1416, -0.9294, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_0", + "type": "Mesh", + "position": [-34.345, 19.9921, 75.3957], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_1", + "type": "Mesh", + "position": [-20.2017, 25.2104, 56.4639], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_2", + "type": "Mesh", + "position": [-23.5978, 14.8881, 79.7071], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_3", + "type": "Mesh", + "position": [-14.252, 24.6604, 59.324], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_4", + "type": "Mesh", + "position": [-17.5336, 20.6605, 65.6909], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_5", + "type": "Mesh", + "position": [-20.6537, 17.5371, 72.1844], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_6", + "type": "Mesh", + "position": [-29.8252, 21.3349, 68.2666], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_7", + "type": "Mesh", + "position": [-24.9035, 22.779, 61.9872], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "éoliennes", + "type": "Object3D", + "position": [-67.1528, 25.397, 68.0941], + "rotation": [-3.1416, -0.5658, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_0", + "type": "Mesh", + "position": [-92.0412, 16.0117, 33.8493], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_1", + "type": "Mesh", + "position": [-86.0049, 16.7563, 32.0059], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_2", + "type": "Mesh", + "position": [-79.8545, 22.1436, 59.5407], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_3", + "type": "Mesh", + "position": [-74.8473, 22.7176, 55.9935], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_4", + "type": "Mesh", + "position": [-60.6625, 22.1436, 80.5695], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_5", + "type": "Mesh", + "position": [-57.0062, 22.7176, 75.7209], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_6", + "type": "Mesh", + "position": [-35.9614, 15.9655, 95.0568], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "Cylindre_7", + "type": "Mesh", + "position": [-33.8614, 16.6976, 89.3578], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "générateur", + "type": "Mesh", + "position": [-48.5737, 21.4281, 54.5311], + "rotation": [3.1416, -1.1286, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "zone_fabrik", + "type": "Object3D", + "position": [35.6417, 8.9438, 51.2707], + "rotation": [0, -1.5519, 0], + "scale": [1, 1, 1] + }, + { + "name": "bati-fabrik", + "type": "Mesh", + "position": [47.4907, 11.1375, 63.8349], + "rotation": [-3.1416, -0.6297, -3.1416], + "scale": [1, 2, 1] + }, + { + "name": "Groupe1", + "type": "Object3D", + "position": [30.2688, 4.9268, 89.1765], + "rotation": [-3.1416, -0.7879, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [32.7856, 3.9426, 86.6943], + "rotation": [3.1416, -0.7879, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [27.7519, 5.9109, 91.6587], + "rotation": [-3.1416, -0.7879, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble", + "type": "Mesh", + "position": [72.2157, 4.1055, 54.7221], + "rotation": [-3.1416, -0.35, -3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [18.9151, 13.8995, 66.8272], + "rotation": [3.1416, -1.0563, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "Groupe", + "type": "Object3D", + "position": [50.8106, 15.3193, 42.1115], + "rotation": [3.1416, -0.5678, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_2", + "type": "Mesh", + "position": [53.8087, 14.3351, 40.2387], + "rotation": [3.1416, -0.5678, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "immeuble_1", + "type": "Mesh", + "position": [47.8126, 16.3034, 43.9843], + "rotation": [3.1416, -0.5678, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "zone_ecole", + "type": "Object3D", + "position": [3.7017, 28.0335, 28.3142], + "rotation": [0.1129, -1.4525, 0.1096], + "scale": [1, 1, 1] + }, + { + "name": "terrainarbresecole", + "type": "Object3D", + "position": [-0.6455, 0, 5.6812], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "panneau", + "type": "Mesh", + "position": [1.3128, 26.9637, 34.1952], + "rotation": [0.0126, -0.5805, 0.0154], + "scale": [1, 1, 1] + }, + { + "name": "arbres", + "type": "Object3D", + "position": [-0.8061, 24.8656, 24.8498], + "rotation": [0, -0.7007, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [13.7329, 23.9356, 47.3633], + "rotation": [3.1416, -0.5734, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [6.2307, 23.9356, 50.7095], + "rotation": [3.1416, -0.2657, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-1.9326, 23.9356, 51.6261], + "rotation": [3.1416, 0.042, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-9.9901, 23.9356, 50.0271], + "rotation": [3.1416, 0.3498, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-17.1848, 23.9356, 46.0625], + "rotation": [3.1416, 0.6575, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-22.8406, 23.9356, 40.105], + "rotation": [-3.1416, 0.9652, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-26.4262, 23.9356, 32.7143], + "rotation": [-3.1416, 1.273, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-27.6048, 23.9356, 24.5846], + "rotation": [0, 1.5609, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-26.2655, 23.9356, 16.4799], + "rotation": [0, 1.2532, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-22.5343, 23.9356, 9.1615], + "rotation": [0, 0.9454, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-16.7617, 23.9356, 3.3171], + "rotation": [0, 0.6377, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-9.49, 23.9356, -0.5042], + "rotation": [0, 0.33, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-1.4024, 23.9356, -1.9435], + "rotation": [0, 0.0223, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [6.7412, 23.9356, -0.8655], + "rotation": [0, -0.2855, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [14.1757, 23.9356, 2.6285], + "rotation": [0, -0.5932, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [20.2026, 23.9356, 8.2103], + "rotation": [0, -0.9009, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [24.2557, 23.9356, 15.3554], + "rotation": [0, -1.2087, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [25.9543, 23.9356, 23.3925], + "rotation": [0, -1.5164, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [25.1386, 23.9356, 31.5665], + "rotation": [3.1416, -1.3175, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "arbres", + "type": "Object3D", + "position": [-0.8061, 24.8656, 24.8498], + "rotation": [0, -0.7007, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [9.5123, 26.3756, 40.8277], + "rotation": [-3.1416, -0.5734, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [4.1879, 26.3756, 43.2025], + "rotation": [-3.1416, -0.2657, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-1.6056, 26.3756, 43.853], + "rotation": [-3.1416, 0.042, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-7.324, 26.3756, 42.7182], + "rotation": [-3.1416, 0.3498, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-12.4301, 26.3756, 39.9045], + "rotation": [-3.1416, 0.6575, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-16.444, 26.3756, 35.6765], + "rotation": [-3.1416, 0.9652, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-18.9887, 26.3756, 30.4312], + "rotation": [-3.1416, 1.273, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-19.8252, 26.3756, 24.6616], + "rotation": [0, 1.5609, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-18.8747, 26.3756, 18.9097], + "rotation": [0, 1.2532, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-16.2267, 26.3756, 13.7158], + "rotation": [0, 0.9454, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-12.1298, 26.3756, 9.568], + "rotation": [0, 0.6377, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [-6.9691, 26.3756, 6.856], + "rotation": [0, 0.33, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [-1.2293, 26.3756, 5.8345], + "rotation": [0, 0.0223, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [4.5502, 26.3756, 6.5996], + "rotation": [0, -0.2855, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [9.8265, 26.3756, 9.0793], + "rotation": [0, -0.5932, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin", + "type": "Mesh", + "position": [14.1038, 26.3756, 13.0407], + "rotation": [0, -0.9009, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [16.9803, 26.3756, 18.1116], + "rotation": [0, -1.2087, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [18.1858, 26.3756, 23.8156], + "rotation": [0, -1.5164, 0], + "scale": [1, 1, 1] + }, + { + "name": "pylone", + "type": "Mesh", + "position": [17.6069, 26.3756, 29.6167], + "rotation": [3.1416, -1.3175, 3.1416], + "scale": [1, 1, 1] + }, + { + "name": "arbres-école", + "type": "Object3D", + "position": [17.2143, 27.7146, 29.0483], + "rotation": [0.1129, -1.4525, 0.1096], + "scale": [1, 1, 1] + }, + { + "name": "arbres_1", + "type": "Object3D", + "position": [4.0735, 27.5587, 31.7711], + "rotation": [0.0133, -0.6214, 0.0159], + "scale": [1, 1, 1] + }, + { + "name": "sapin_0", + "type": "Mesh", + "position": [20.1553, 24.2993, 42.4409], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin_1", + "type": "Mesh", + "position": [7.5375, 27.2128, 33.2893], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin_2", + "type": "Mesh", + "position": [24.758, 24.3496, 35.4002], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "arbre_3", + "type": "Mesh", + "position": [9.41, 27.2123, 30.5578], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin_4", + "type": "Mesh", + "position": [14.607, 26.6674, 32.4709], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "sapin_5", + "type": "Mesh", + "position": [19.5845, 25.7627, 34.0445], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "arbre_6", + "type": "Mesh", + "position": [16.0263, 25.6963, 39.6732], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "arbre_7", + "type": "Mesh", + "position": [11.8387, 26.6517, 36.6584], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + }, + { + "name": "lecole", + "type": "Object3D", + "position": [-0.3871, 30.9693, 26.172], + "rotation": [0.0126, -0.5805, 0.0154], + "scale": [1, 2, 1] + }, + { + "name": "bati-ecole", + "type": "Mesh", + "position": [-7.1777, 30.3365, 36.5318], + "rotation": [0.0126, -0.5805, 0.0154], + "scale": [1, 2, 1] + }, + { + "name": "bati-ecole", + "type": "Mesh", + "position": [6.4132, 30.5965, 15.813], + "rotation": [0.0126, -0.5805, 0.0154], + "scale": [1, 2, 1] + }, + { + "name": "bati-ecole", + "type": "Mesh", + "position": [-0.3968, 31.9749, 26.1711], + "rotation": [0.0126, -0.5805, 0.0154], + "scale": [1, 2, 1] + }, + { + "name": "terrain", + "type": "Mesh", + "position": [-0.6455, 0, 5.6812], + "rotation": [0, 0, 0], + "scale": [1, 1, 1] + } +] diff --git a/public/models/pylone/cable1_base_color.png b/public/models/pylone/cable1_base_color.png new file mode 100644 index 0000000..f1c5101 --- /dev/null +++ b/public/models/pylone/cable1_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e855a105690f232494d5f3959d8fd6e8512de53de6d1b8a7e13e79d44b455635 +size 170284 diff --git a/public/models/pylone/cable1_height.png b/public/models/pylone/cable1_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/cable1_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/cable1_metallic.png b/public/models/pylone/cable1_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/pylone/cable1_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/pylone/cable1_mixed_ao.png b/public/models/pylone/cable1_mixed_ao.png new file mode 100644 index 0000000..b21d620 --- /dev/null +++ b/public/models/pylone/cable1_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d3210319fea926e024d9d64c9440319550e548a7147f0998610293d05351411 +size 106768 diff --git a/public/models/pylone/cable1_normal.png b/public/models/pylone/cable1_normal.png new file mode 100644 index 0000000..707fa61 --- /dev/null +++ b/public/models/pylone/cable1_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2d01588d749aa5fc4ca2598db56250d22381ca2cb5d4acf22f7e2075d9ee69f +size 145554 diff --git a/public/models/pylone/cable1_normal_opengl.png b/public/models/pylone/cable1_normal_opengl.png new file mode 100644 index 0000000..7fdc9e2 --- /dev/null +++ b/public/models/pylone/cable1_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:700e5261c9513fe08bf918e8e73c82b390bcd5b220427ae8a061150ace2bb39d +size 146495 diff --git a/public/models/pylone/cable1_roughness.png b/public/models/pylone/cable1_roughness.png new file mode 100644 index 0000000..59ffd1c --- /dev/null +++ b/public/models/pylone/cable1_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fee20587280c1bee1ed7a251c9584a48b4e159d45f674b6aabe58783c17049a +size 53738 diff --git a/public/models/pylone/cable2_base_color.png b/public/models/pylone/cable2_base_color.png new file mode 100644 index 0000000..67aeedd --- /dev/null +++ b/public/models/pylone/cable2_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fef27c0cae8e434723a7a200f6a490b96349a002af0e2ba583ab689a7501bb58 +size 152980 diff --git a/public/models/pylone/cable2_height.png b/public/models/pylone/cable2_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/cable2_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/cable2_metallic.png b/public/models/pylone/cable2_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/pylone/cable2_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/pylone/cable2_mixed_ao.png b/public/models/pylone/cable2_mixed_ao.png new file mode 100644 index 0000000..d0c5f78 --- /dev/null +++ b/public/models/pylone/cable2_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00fcdce71383ed2081696921fa6ef9592e653560bb7c3058f7d0ac4da6427a8c +size 100588 diff --git a/public/models/pylone/cable2_normal.png b/public/models/pylone/cable2_normal.png new file mode 100644 index 0000000..0aeb96a --- /dev/null +++ b/public/models/pylone/cable2_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6187d87bcb23afd869017739161862832c6520754a0670c460cd326d85357c0f +size 145219 diff --git a/public/models/pylone/cable2_normal_opengl.png b/public/models/pylone/cable2_normal_opengl.png new file mode 100644 index 0000000..0b68af5 --- /dev/null +++ b/public/models/pylone/cable2_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22660de1a0682b1abd1684f7e3fb5e2589b483eabe301f72f3e9d821fe82226e +size 146306 diff --git a/public/models/pylone/cable2_roughness.png b/public/models/pylone/cable2_roughness.png new file mode 100644 index 0000000..bdad05d --- /dev/null +++ b/public/models/pylone/cable2_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:603aa4f5dc13de2d56a150a3ebaf555b50d87e6928cb8f9f97d372035caf0123 +size 52660 diff --git a/public/models/pylone/chap_base_color.png b/public/models/pylone/chap_base_color.png new file mode 100644 index 0000000..747833f --- /dev/null +++ b/public/models/pylone/chap_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92723a60f4f0fe39a6309fb3faec588c0178a67cf765c52646a776134d5086b6 +size 86318 diff --git a/public/models/pylone/chap_height.png b/public/models/pylone/chap_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/chap_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/chap_metallic.png b/public/models/pylone/chap_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/pylone/chap_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/pylone/chap_mixed_ao.png b/public/models/pylone/chap_mixed_ao.png new file mode 100644 index 0000000..ae16438 --- /dev/null +++ b/public/models/pylone/chap_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08db27428a9262caa0f45d814fe4e72e813cebe3ea2139417c34c749782b5071 +size 200657 diff --git a/public/models/pylone/chap_normal.png b/public/models/pylone/chap_normal.png new file mode 100644 index 0000000..a7fdf27 --- /dev/null +++ b/public/models/pylone/chap_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:81c90f031b04c4376d8d715f4b7087fba8174539e79270155c7ff56d3b5be726 +size 187794 diff --git a/public/models/pylone/chap_normal_opengl.png b/public/models/pylone/chap_normal_opengl.png new file mode 100644 index 0000000..a31b821 --- /dev/null +++ b/public/models/pylone/chap_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d9a52a596c1cafc8e299efcaf215b8d4501ae9ab09ed84ca36729451797ede0 +size 188092 diff --git a/public/models/pylone/chap_roughness.png b/public/models/pylone/chap_roughness.png new file mode 100644 index 0000000..1588b9f --- /dev/null +++ b/public/models/pylone/chap_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbccc4f4042014922faab436e859024a6e66656eb1b3a7aa2f80c4c48f112e76 +size 78815 diff --git a/public/models/pylone/lampe_base_color.png b/public/models/pylone/lampe_base_color.png new file mode 100644 index 0000000..de93f3e --- /dev/null +++ b/public/models/pylone/lampe_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b1df0d30023f3f9be05c310f1d2c3fe2b80c9cc79b631f8ceee7df17bb0d7d6 +size 435213 diff --git a/public/models/pylone/lampe_height.png b/public/models/pylone/lampe_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/lampe_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/lampe_metallic.png b/public/models/pylone/lampe_metallic.png new file mode 100644 index 0000000..a2a6ea5 --- /dev/null +++ b/public/models/pylone/lampe_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73f0f1f9a2449135c3786b1e2a8b0308d91ce0b17e8ab419d42dbe12f6cd49ad +size 119101 diff --git a/public/models/pylone/lampe_mixed_ao.png b/public/models/pylone/lampe_mixed_ao.png new file mode 100644 index 0000000..129ce8e --- /dev/null +++ b/public/models/pylone/lampe_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79a620d8bf89c36ac579e361a0383f2e00cc0a46bd20af3866a272fbdb66cf44 +size 333234 diff --git a/public/models/pylone/lampe_normal.png b/public/models/pylone/lampe_normal.png new file mode 100644 index 0000000..3fbc93b --- /dev/null +++ b/public/models/pylone/lampe_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:670b8083516218cfa7fee2abfc4b5647bc7c97c513b6ffda6c81544078b190d1 +size 416583 diff --git a/public/models/pylone/lampe_normal_opengl.png b/public/models/pylone/lampe_normal_opengl.png new file mode 100644 index 0000000..ce6b274 --- /dev/null +++ b/public/models/pylone/lampe_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16f4756e77ffb2b96b4964dbf3ba1a455cc551ab04eedbad5f9b25476652e064 +size 420113 diff --git a/public/models/pylone/lampe_opacity.png b/public/models/pylone/lampe_opacity.png new file mode 100644 index 0000000..377c715 --- /dev/null +++ b/public/models/pylone/lampe_opacity.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3bdaa1be72aa14047f2a4c50e112e9e428a088bbbaa5153ad40ae2897decf2b +size 3179 diff --git a/public/models/pylone/lampe_roughness.png b/public/models/pylone/lampe_roughness.png new file mode 100644 index 0000000..f25e00b --- /dev/null +++ b/public/models/pylone/lampe_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23a8670ac6eb007f98005bf66c49168f20dbb89957478da0254a568e93b71b35 +size 176715 diff --git a/public/models/pylone/model.gltf b/public/models/pylone/model.gltf new file mode 100644 index 0000000..05d147d --- /dev/null +++ b/public/models/pylone/model.gltf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a4beb235d9ce60f1a59a8e73b9e1bcdb44f45c6a2017fc6e912a29cee5f249ec +size 3588015 diff --git a/public/models/pylone/panneaux_base_color.png b/public/models/pylone/panneaux_base_color.png new file mode 100644 index 0000000..4b9de68 --- /dev/null +++ b/public/models/pylone/panneaux_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86cd37f97c5a3f6d381c8aefee406e784e7827cd895d870354624384fe1b79e7 +size 156525 diff --git a/public/models/pylone/panneaux_height.png b/public/models/pylone/panneaux_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/panneaux_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/panneaux_metallic.png b/public/models/pylone/panneaux_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/pylone/panneaux_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/pylone/panneaux_mixed_ao.png b/public/models/pylone/panneaux_mixed_ao.png new file mode 100644 index 0000000..e2ac5c4 --- /dev/null +++ b/public/models/pylone/panneaux_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a73fc0135fbacb8be5a64251dabe071e402d48c9d0f4c2bd25fc033c92eafbc6 +size 237957 diff --git a/public/models/pylone/panneaux_normal.png b/public/models/pylone/panneaux_normal.png new file mode 100644 index 0000000..d910f65 --- /dev/null +++ b/public/models/pylone/panneaux_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54f044e90f7a96a9d9218733b9a4136c164f0c60f4389baa4d0f14b2cbef8270 +size 83209 diff --git a/public/models/pylone/panneaux_normal_opengl.png b/public/models/pylone/panneaux_normal_opengl.png new file mode 100644 index 0000000..2956cf0 --- /dev/null +++ b/public/models/pylone/panneaux_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:537b66ad9e53107bc4b577877ce2282d612de6e38241f5911a2f330ef9ed4c22 +size 86378 diff --git a/public/models/pylone/panneaux_roughness.png b/public/models/pylone/panneaux_roughness.png new file mode 100644 index 0000000..3848015 --- /dev/null +++ b/public/models/pylone/panneaux_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a4c3430448c893a4c9b13c24b6ba450d2a54ecc19fe73f8a6e07244499f4f13 +size 47993 diff --git a/public/models/pylone/pied_base_color.png b/public/models/pylone/pied_base_color.png new file mode 100644 index 0000000..bf377ef --- /dev/null +++ b/public/models/pylone/pied_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0175ecfec561cc3614884bfd2a06095715c799f148d20e86c136ef6a3b0d022 +size 220660 diff --git a/public/models/pylone/pied_height.png b/public/models/pylone/pied_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/pied_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/pied_metallic.png b/public/models/pylone/pied_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/pylone/pied_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/pylone/pied_mixed_ao.png b/public/models/pylone/pied_mixed_ao.png new file mode 100644 index 0000000..e0b72f3 --- /dev/null +++ b/public/models/pylone/pied_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cf789a1fdd11527c58d70fe288ad149f5f032e677805e914e8142a83895b6b63 +size 268116 diff --git a/public/models/pylone/pied_normal.png b/public/models/pylone/pied_normal.png new file mode 100644 index 0000000..8b3b7bd --- /dev/null +++ b/public/models/pylone/pied_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1070e5d8ae0b38b979bf34f80c03e00a628b21b53bfe64e2a801fccfb976847f +size 259296 diff --git a/public/models/pylone/pied_normal_opengl.png b/public/models/pylone/pied_normal_opengl.png new file mode 100644 index 0000000..648434d --- /dev/null +++ b/public/models/pylone/pied_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77678fa53d89918eefc61f1a6942075cb73e38ff30f9ace95d7916e250d20af3 +size 261233 diff --git a/public/models/pylone/pied_roughness.png b/public/models/pylone/pied_roughness.png new file mode 100644 index 0000000..2d705d1 --- /dev/null +++ b/public/models/pylone/pied_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:919f981e76f106cf597b90ff0f0d175a3bb61beee039a92b370445f463d52516 +size 104005 diff --git a/public/models/pylone/puces_base_color.png b/public/models/pylone/puces_base_color.png new file mode 100644 index 0000000..566ec43 --- /dev/null +++ b/public/models/pylone/puces_base_color.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e1b5e8e6c40fd84b7d1220990653359933880e478f9dd5122d2f8359d12fb97 +size 343406 diff --git a/public/models/pylone/puces_height.png b/public/models/pylone/puces_height.png new file mode 100644 index 0000000..713980d --- /dev/null +++ b/public/models/pylone/puces_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed +size 3178 diff --git a/public/models/pylone/puces_metallic.png b/public/models/pylone/puces_metallic.png new file mode 100644 index 0000000..b46e54a --- /dev/null +++ b/public/models/pylone/puces_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 +size 1118 diff --git a/public/models/pylone/puces_mixed_ao.png b/public/models/pylone/puces_mixed_ao.png new file mode 100644 index 0000000..7c4ea3c --- /dev/null +++ b/public/models/pylone/puces_mixed_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c217f278d763ba946e5133c85d45295090cd9a790650af6f084bdd8d169bb5ab +size 262622 diff --git a/public/models/pylone/puces_normal.png b/public/models/pylone/puces_normal.png new file mode 100644 index 0000000..addb846 --- /dev/null +++ b/public/models/pylone/puces_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4f066202057a3edcdf154c9174f1520ec44bf4ce9bab75a24c515b4c4cd3b52 +size 129597 diff --git a/public/models/pylone/puces_normal_opengl.png b/public/models/pylone/puces_normal_opengl.png new file mode 100644 index 0000000..fb4e86d --- /dev/null +++ b/public/models/pylone/puces_normal_opengl.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4f4e1e08446160b90a55c8f3f7b478cbcf7ea2084add0ad406fe25c1f3b92175 +size 133187 diff --git a/public/models/pylone/puces_roughness.png b/public/models/pylone/puces_roughness.png new file mode 100644 index 0000000..50a2a9b --- /dev/null +++ b/public/models/pylone/puces_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:814dccc984084f46d5e09a62f5001441d96aa6aa928f3dcbf2b14e73ad975cb6 +size 63405 diff --git a/src/App.tsx b/src/App.tsx index 5879bcd..9cc5acc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,22 +1,32 @@ +import { Routes, Route } from "react-router-dom"; import { Suspense } from "react"; import { Canvas } from "@react-three/fiber"; import { Crosshair } from "@/components/ui/Crosshair"; import { InteractPrompt } from "@/components/ui/InteractPrompt"; import { DebugPerf } from "@/utils/debug/DebugPerf"; import { World } from "@/world/World"; +import { EditorPage } from "@/pages/editor/EditorPage"; function App(): React.JSX.Element { return ( - <> - - - - - - - - - > + + + + + + + + + + + > + } + /> + } /> + ); } diff --git a/src/features/editor/components/EditorControls.tsx b/src/features/editor/components/EditorControls.tsx new file mode 100644 index 0000000..f3db353 --- /dev/null +++ b/src/features/editor/components/EditorControls.tsx @@ -0,0 +1,324 @@ +import { + Box, + Braces, + Download, + Expand, + Keyboard, + Lock, + MousePointer2, + Move3D, + Redo2, + RotateCw, + Save, + Undo2, +} from "lucide-react"; +import type { MapNode, TransformMode } from "@/types/editor"; + +interface EditorControlsProps { + transformMode: TransformMode; + onTransformModeChange: (mode: TransformMode) => void; + selectedNodeIndex: number | null; + mapNodes: MapNode[]; + nodesCount: number; + selectedNodeName: string | null; + undoCount: number; + redoCount: number; + onUndo: () => void; + onRedo: () => void; + onExportJson: () => void; + onSaveToServer?: (() => void | Promise) | undefined; + onPlayerMode?: (() => void) | undefined; + isPlayerMode?: boolean; +} + +export function EditorControls({ + transformMode, + onTransformModeChange, + selectedNodeIndex, + mapNodes, + nodesCount, + selectedNodeName, + undoCount, + redoCount, + onUndo, + onRedo, + onExportJson, + onSaveToServer, + onPlayerMode, + isPlayerMode, +}: EditorControlsProps): React.JSX.Element { + const viewModeLabel = isPlayerMode ? "View locked" : "Lock view"; + const jsonPreview = getJsonPreview(mapNodes, selectedNodeIndex); + + return ( + <> + + > + ); +} + +interface JsonPreviewLine { + number: number; + content: string; + isSelected: boolean; +} + +interface JsonPreview { + label: string; + lines: JsonPreviewLine[]; +} + +function getJsonPreview( + mapNodes: MapNode[], + selectedNodeIndex: number | null, +): JsonPreview { + const { lines, ranges } = formatMapNodesWithRanges(mapNodes); + + if (selectedNodeIndex === null || !ranges[selectedNodeIndex]) { + return { + label: `${lines.length} raw lines`, + lines: lines.map((content, index) => ({ + number: index + 1, + content, + isSelected: false, + })), + }; + } + + const range = ranges[selectedNodeIndex]; + const selectedLines = lines.slice(range.start - 1, range.end); + + return { + label: `Lines ${range.start}-${range.end}`, + lines: selectedLines.map((content, index) => ({ + number: range.start + index, + content, + isSelected: true, + })), + }; +} + +function formatMapNodesWithRanges(mapNodes: MapNode[]): { + lines: string[]; + ranges: Array<{ start: number; end: number }>; +} { + const lines = ["["]; + const ranges: Array<{ start: number; end: number }> = []; + + mapNodes.forEach((node, index) => { + const objectLines = JSON.stringify(node, null, 2) + .split("\n") + .map((line) => ` ${line}`); + + if (index < mapNodes.length - 1) { + objectLines[objectLines.length - 1] += ","; + } + + const start = lines.length + 1; + lines.push(...objectLines); + ranges.push({ start, end: lines.length }); + }); + + lines.push("]"); + + return { lines, ranges }; +} diff --git a/src/features/editor/controls/FlyController.tsx b/src/features/editor/controls/FlyController.tsx new file mode 100644 index 0000000..329b415 --- /dev/null +++ b/src/features/editor/controls/FlyController.tsx @@ -0,0 +1,126 @@ +import { + useRef, + useEffect, + useCallback, + forwardRef, + useImperativeHandle, + type ElementRef, +} from "react"; +import { useFrame, useThree } from "@react-three/fiber"; +import { OrbitControls } from "@react-three/drei"; +import * as THREE from "three"; + +type OrbitControlsRef = ElementRef; + +interface FlyControllerProps { + speed?: number; + verticalSpeed?: number; + onPositionChange?: (position: THREE.Vector3) => void; + disabled?: boolean; +} + +interface FlyControllerRef { + controls: OrbitControlsRef | null; +} + +export const FlyController = forwardRef( + ( + { speed = 10, verticalSpeed = 5, onPositionChange, disabled = false }, + ref, + ) => { + const { camera: rawCamera } = useThree(); + const cameraRef = useRef(rawCamera); + const keys = useRef<{ [key: string]: boolean }>({}); + const controlsRef = useRef(null); + const lastPosition = useRef(new THREE.Vector3()); + + useImperativeHandle(ref, () => ({ + controls: controlsRef.current, + })); + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + keys.current[e.code] = true; + }, []); + + const handleKeyUp = useCallback((e: KeyboardEvent) => { + keys.current[e.code] = false; + }, []); + + useEffect(() => { + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }, [handleKeyDown, handleKeyUp]); + + useFrame((_, delta) => { + // Disabled mode keeps OrbitControls active without keyboard movement. + if (disabled) { + return; + } + + // Supports AZERTY, QWERTY, and arrow-key movement. + const isForward = + keys.current["KeyW"] || keys.current["KeyZ"] || keys.current["ArrowUp"]; + const isBackward = keys.current["KeyS"] || keys.current["ArrowDown"]; + const isLeft = + keys.current["KeyQ"] || + keys.current["KeyA"] || + keys.current["ArrowLeft"]; + const isRight = keys.current["KeyD"] || keys.current["ArrowRight"]; + + const direction = new THREE.Vector3(); + const frontVector = new THREE.Vector3( + 0, + 0, + Number(isBackward) - Number(isForward), + ); + const sideVector = new THREE.Vector3( + Number(isRight) - Number(isLeft), + 0, + 0, + ); + + direction.subVectors(frontVector, sideVector); + if (direction.lengthSq() > 0) { + direction.normalize().multiplyScalar(speed * delta); + direction.applyQuaternion(cameraRef.current.quaternion); + cameraRef.current.position.add(direction); + } + + // Space moves up; Shift moves down. + if (keys.current["Space"]) { + cameraRef.current.position.y += verticalSpeed * delta; + } + if (keys.current["ShiftLeft"] || keys.current["ShiftRight"]) { + cameraRef.current.position.y -= verticalSpeed * delta; + } + + if ( + onPositionChange && + !cameraRef.current.position.equals(lastPosition.current) + ) { + lastPosition.current.copy(cameraRef.current.position); + onPositionChange(cameraRef.current.position); + } + }); + + return ( + + ); + }, +); + +FlyController.displayName = "FlyController"; diff --git a/src/features/editor/hooks/useEditorHistory.ts b/src/features/editor/hooks/useEditorHistory.ts new file mode 100644 index 0000000..275bcad --- /dev/null +++ b/src/features/editor/hooks/useEditorHistory.ts @@ -0,0 +1,164 @@ +import { useCallback, useRef, useState } from "react"; +import type { MapNode, SceneData } from "@/types/editor"; + +interface ObjectTransform { + uuid: string; + position: { x: number; y: number; z: number }; + rotation: { x: number; y: number; z: number }; + scale: { x: number; y: number; z: number }; +} + +class HistoryManager { + private history: ObjectTransform[][] = []; + private currentIndex = -1; + private maxSize: number; + + constructor(maxSize = 50) { + this.maxSize = maxSize; + } + + saveSnapshot(objects: ObjectTransform[]): void { + if (this.currentIndex < this.history.length - 1) { + this.history = this.history.slice(0, this.currentIndex + 1); + } + + this.history.push(objects.map((object) => ({ ...object }))); + this.currentIndex = this.history.length - 1; + + if (this.history.length > this.maxSize) { + this.history.shift(); + this.currentIndex--; + } + } + + undo(): ObjectTransform[] | undefined { + if (this.currentIndex <= 0) return undefined; + + this.currentIndex--; + return this.history[this.currentIndex]; + } + + redo(): ObjectTransform[] | undefined { + if (this.currentIndex >= this.history.length - 1) return undefined; + + this.currentIndex++; + return this.history[this.currentIndex]; + } + + getUndoCount(): number { + return this.currentIndex; + } + + getRedoCount(): number { + return this.history.length - 1 - this.currentIndex; + } +} + +interface UseEditorHistoryResult { + undoCount: number; + redoCount: number; + handleUndo: () => void; + handleRedo: () => void; + handleTransformStart: () => void; + handleTransformEnd: () => void; +} + +export function useEditorHistory( + sceneData: SceneData | null, + setSceneData: React.Dispatch>, +): UseEditorHistoryResult { + const [undoCount, setUndoCount] = useState(0); + const [redoCount, setRedoCount] = useState(0); + const historyManager = useRef(new HistoryManager(50)); + + const updateHistoryCounts = useCallback(() => { + setUndoCount(historyManager.current.getUndoCount()); + setRedoCount(historyManager.current.getRedoCount()); + }, []); + + const applySnapshot = useCallback( + (snapshot: ObjectTransform[]): void => { + setSceneData((prev) => { + if (!prev) return null; + + const mapNodes = prev.mapNodes.map((node, index) => { + const transform = snapshot.find( + (item) => item.uuid === `node-${index}`, + ); + if (!transform) return node; + + return { + ...node, + position: [ + transform.position.x, + transform.position.y, + transform.position.z, + ], + rotation: [ + transform.rotation.x, + transform.rotation.y, + transform.rotation.z, + ], + scale: [transform.scale.x, transform.scale.y, transform.scale.z], + } satisfies MapNode; + }); + + return { ...prev, mapNodes }; + }); + }, + [setSceneData], + ); + + const handleUndo = useCallback(() => { + const snapshot = historyManager.current.undo(); + if (!snapshot) return; + + applySnapshot(snapshot); + updateHistoryCounts(); + }, [applySnapshot, updateHistoryCounts]); + + const handleRedo = useCallback(() => { + const snapshot = historyManager.current.redo(); + if (!snapshot) return; + + applySnapshot(snapshot); + updateHistoryCounts(); + }, [applySnapshot, updateHistoryCounts]); + + const handleTransformStart = useCallback(() => { + if (!sceneData) return; + historyManager.current.saveSnapshot(createSnapshot(sceneData)); + }, [sceneData]); + + const handleTransformEnd = useCallback(() => { + if (!sceneData) return; + historyManager.current.saveSnapshot(createSnapshot(sceneData)); + updateHistoryCounts(); + }, [sceneData, updateHistoryCounts]); + + return { + undoCount, + redoCount, + handleUndo, + handleRedo, + handleTransformStart, + handleTransformEnd, + }; +} + +function createSnapshot(sceneData: SceneData): ObjectTransform[] { + return sceneData.mapNodes.map((node, index) => ({ + uuid: `node-${index}`, + position: { + x: node.position[0], + y: node.position[1], + z: node.position[2], + }, + rotation: { + x: node.rotation[0], + y: node.rotation[1], + z: node.rotation[2], + }, + scale: { x: node.scale[0], y: node.scale[1], z: node.scale[2] }, + })); +} diff --git a/src/features/editor/hooks/useEditorSceneData.ts b/src/features/editor/hooks/useEditorSceneData.ts new file mode 100644 index 0000000..ffdab7b --- /dev/null +++ b/src/features/editor/hooks/useEditorSceneData.ts @@ -0,0 +1,65 @@ +import { useCallback, useEffect, useState } from "react"; +import { createSceneDataFromFiles } from "@/utils/editor/loadEditorScene"; +import { loadMapSceneData } from "@/utils/loadMapSceneData"; +import type { SceneData } from "@/types/editor"; + +interface UseEditorSceneDataResult { + hasMapJson: boolean; + isMapLoading: boolean; + sceneData: SceneData | null; + setSceneData: React.Dispatch>; + handleFolderUpload: ( + event: React.ChangeEvent, + ) => Promise; +} + +export function useEditorSceneData(): UseEditorSceneDataResult { + const [hasMapJson, setHasMapJson] = useState(false); + const [isMapLoading, setIsMapLoading] = useState(true); + const [sceneData, setSceneData] = useState(null); + + useEffect(() => { + const loadScene = async (): Promise => { + setIsMapLoading(true); + + try { + const loadedSceneData = await loadMapSceneData(); + setSceneData(loadedSceneData); + setHasMapJson(Boolean(loadedSceneData)); + } catch (error) { + console.error("Error loading map data:", error); + setHasMapJson(false); + } finally { + setIsMapLoading(false); + } + }; + + loadScene(); + }, []); + + const handleFolderUpload = useCallback( + async (event: React.ChangeEvent): Promise => { + const files = event.target.files; + if (!files) return; + + try { + const uploadedSceneData = await createSceneDataFromFiles(files); + setSceneData(uploadedSceneData); + setHasMapJson(true); + } catch (error) { + const message = error instanceof Error ? error.message : "Erreur"; + console.error("Error processing upload:", error); + alert(message); + } + }, + [], + ); + + return { + hasMapJson, + isMapLoading, + sceneData, + setSceneData, + handleFolderUpload, + }; +} diff --git a/src/features/editor/scene/EditorMap.tsx b/src/features/editor/scene/EditorMap.tsx new file mode 100644 index 0000000..53cb125 --- /dev/null +++ b/src/features/editor/scene/EditorMap.tsx @@ -0,0 +1,344 @@ +import { useMemo, useRef, useEffect, useState } from "react"; +import { Grid, TransformControls, useGLTF } from "@react-three/drei"; +import type { ThreeEvent } from "@react-three/fiber"; +import * as THREE from "three"; + +import type { SceneData, MapNode, TransformMode } from "@/types/editor"; + +interface EditorMapProps { + sceneData: SceneData; + selectedNodeIndex: number | null; + onSelectNode: (index: number | null) => void; + hoveredNodeIndex: number | null; + onHoverNode: (index: number | null) => void; + transformMode: TransformMode; + onTransformStart: () => void; + onTransformEnd: () => void; + onNodeTransform: (nodeIndex: number, transform: MapNode) => void; +} + +type EditorNodeObjectRef = React.RefObject>; + +interface EditorNodeCommonProps { + index: number; + node: MapNode; + isSelected: boolean; + isHovered: boolean; + objectsMapRef: EditorNodeObjectRef; + onSelectNode: (index: number | null) => void; + onHoverNode: (index: number | null) => void; +} + +function applyNodeTransform(object: THREE.Object3D, node: MapNode): void { + object.position.set(...node.position); + object.rotation.set(...node.rotation); + object.scale.set(...node.scale); +} + +function useRegisteredEditorNode( + objectRef: React.RefObject, + index: number, + node: MapNode, + objectsMapRef: EditorNodeObjectRef, +): void { + useEffect(() => { + const object = objectRef.current; + if (object) { + applyNodeTransform(object, node); + object.userData = { nodeIndex: index, nodeName: node.name }; + objectsMapRef.current.set(index, object); + } + + const currentMap = objectsMapRef.current; + const currentIndex = index; + return () => { + currentMap.delete(currentIndex); + }; + }, [index, node, objectRef, objectsMapRef]); + + useEffect(() => { + const object = objectRef.current; + if (object) { + applyNodeTransform(object, node); + } + }, [node, objectRef]); +} + +function disposeMaterial(material: THREE.Material | THREE.Material[]): void { + if (Array.isArray(material)) { + material.forEach((item) => item.dispose()); + return; + } + + material.dispose(); +} + +function cloneHighlightedMaterial( + material: THREE.Material | THREE.Material[], + color: string, +): THREE.Material | THREE.Material[] { + if (Array.isArray(material)) { + return material.map((item) => cloneHighlightedMaterial(item, color)).flat(); + } + + const clone = material.clone(); + if (clone instanceof THREE.MeshStandardMaterial) { + clone.color.set(color); + } + return clone; +} + +export function EditorMap({ + sceneData, + selectedNodeIndex, + onSelectNode, + hoveredNodeIndex, + onHoverNode, + transformMode, + onTransformStart, + onTransformEnd, + onNodeTransform, +}: EditorMapProps): React.JSX.Element { + const objectsMapRef = useRef>(new Map()); + + const handleTransformMouseDown = () => { + onTransformStart?.(); + }; + + const handleTransformMouseUp = () => { + if (selectedNodeIndex !== null) { + const obj = objectsMapRef.current.get(selectedNodeIndex); + if (!obj) return; + const node = sceneData.mapNodes[selectedNodeIndex]; + if (node) { + const updatedNode: MapNode = { + ...node, + position: [obj.position.x, obj.position.y, obj.position.z], + rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], + scale: [obj.scale.x, obj.scale.y, obj.scale.z], + }; + onNodeTransform?.(selectedNodeIndex, updatedNode); + } + } + onTransformEnd?.(); + }; + + const [selectedObject, setSelectedObject] = useState( + null, + ); + + useEffect(() => { + if (selectedNodeIndex !== null) { + const obj = objectsMapRef.current.get(selectedNodeIndex); + setSelectedObject(obj || null); + } else { + setSelectedObject(null); + } + }, [selectedNodeIndex]); + + return ( + <> + + + + ) => { + e.stopPropagation(); + onSelectNode(null); + }} + > + {sceneData.mapNodes.map((node, index) => { + const modelUrl = sceneData.models.get(node.name); + + if (modelUrl) { + return ( + + ); + } else { + return ( + + ); + } + })} + + + {selectedObject && ( + + )} + > + ); +} + +function EditorModelNode({ + index, + node, + modelUrl, + isSelected, + isHovered, + objectsMapRef, + onSelectNode, + onHoverNode, +}: EditorNodeCommonProps & { + modelUrl: string; +}) { + const groupRef = useRef(null); + const originalMaterialsRef = useRef( + new Map(), + ); + const { scene } = useGLTF(modelUrl); + + const sceneInstance = useMemo(() => scene.clone(true), [scene]); + useRegisteredEditorNode(groupRef, index, node, objectsMapRef); + + useEffect(() => { + if (!groupRef.current) return; + const highlightColor = isSelected + ? "#ffffff" + : isHovered + ? "#b8b8b8" + : null; + + groupRef.current.traverse((child) => { + if (!(child instanceof THREE.Mesh)) { + return; + } + + const originalMaterial = originalMaterialsRef.current.get(child); + + if (!originalMaterial) { + originalMaterialsRef.current.set(child, child.material); + } + + if (child.material !== originalMaterial && originalMaterial) { + disposeMaterial(child.material); + } + + if (highlightColor) { + child.material = cloneHighlightedMaterial( + originalMaterial ?? child.material, + highlightColor, + ); + } else if (originalMaterial) { + child.material = originalMaterial; + } + }); + }, [isSelected, isHovered]); + + useEffect(() => { + const group = groupRef.current; + const originalMaterials = originalMaterialsRef.current; + + return () => { + if (!group) return; + + group.traverse((child) => { + if (!(child instanceof THREE.Mesh)) { + return; + } + + const originalMaterial = originalMaterials.get(child); + if (originalMaterial && child.material !== originalMaterial) { + disposeMaterial(child.material); + child.material = originalMaterial; + } + }); + }; + }, []); + + return ( + ) => { + e.stopPropagation(); + onSelectNode(index); + }} + onPointerEnter={(e: ThreeEvent) => { + e.stopPropagation(); + onHoverNode(index); + }} + onPointerLeave={(e: ThreeEvent) => { + e.stopPropagation(); + onHoverNode(null); + }} + /> + ); +} + +function EditorFallbackNode({ + index, + node, + isSelected, + isHovered, + objectsMapRef, + onSelectNode, + onHoverNode, +}: EditorNodeCommonProps) { + const meshRef = useRef(null); + useRegisteredEditorNode(meshRef, index, node, objectsMapRef); + + const color = isSelected ? "#ffffff" : isHovered ? "#b8b8b8" : "#6f6f6f"; + + return ( + ) => { + e.stopPropagation(); + onSelectNode(index); + }} + onPointerEnter={(e: ThreeEvent) => { + e.stopPropagation(); + onHoverNode(index); + }} + onPointerLeave={(e: ThreeEvent) => { + e.stopPropagation(); + onHoverNode(null); + }} + > + + + + ); +} diff --git a/src/features/editor/scene/EditorScene.tsx b/src/features/editor/scene/EditorScene.tsx new file mode 100644 index 0000000..7497b5e --- /dev/null +++ b/src/features/editor/scene/EditorScene.tsx @@ -0,0 +1,108 @@ +import { useEffect } from "react"; +import { OrbitControls } from "@react-three/drei"; +import { FlyController } from "@/features/editor/controls/FlyController"; +import { EditorMap } from "@/features/editor/scene/EditorMap"; +import type { MapNode, TransformMode, SceneData } from "@/types/editor"; + +interface EditorSceneProps { + sceneData: SceneData; + selectedNodeIndex: number | null; + onSelectNode: (index: number | null) => void; + hoveredNodeIndex: number | null; + onHoverNode: (index: number | null) => void; + transformMode: TransformMode; + onTransformModeChange: (mode: TransformMode) => void; + onTransformStart: () => void; + onTransformEnd: () => void; + onNodeTransform: (nodeIndex: number, transform: MapNode) => void; + onUndo: () => void; + onRedo: () => void; + isPlayerMode?: boolean; +} + +export function EditorScene({ + sceneData, + selectedNodeIndex, + onSelectNode, + hoveredNodeIndex, + onHoverNode, + transformMode, + onTransformModeChange, + onTransformStart, + onTransformEnd, + onNodeTransform, + onUndo, + onRedo, + isPlayerMode = false, +}: EditorSceneProps): React.JSX.Element { + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.ctrlKey || e.metaKey) { + if (e.key === "z" || e.key === "Z") { + e.preventDefault(); + onUndo(); + return; + } + if (e.key === "y" || e.key === "Y") { + e.preventDefault(); + onRedo(); + return; + } + } + + if (selectedNodeIndex !== null) { + switch (e.key.toLowerCase()) { + case "escape": + onSelectNode(null); + break; + case "t": + onTransformModeChange("translate"); + break; + case "r": + onTransformModeChange("rotate"); + break; + case "s": + onTransformModeChange("scale"); + break; + } + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedNodeIndex, onSelectNode, onTransformModeChange, onUndo, onRedo]); + + return ( + <> + {isPlayerMode ? ( + + ) : ( + + )} + + + + + + + > + ); +} diff --git a/src/index.css b/src/index.css index 8e0c2e2..d59a38b 100644 --- a/src/index.css +++ b/src/index.css @@ -79,3 +79,573 @@ canvas { color: rgba(255, 255, 255, 0.85); letter-spacing: 0.03em; } + +/* Editor page */ +.editor-container { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: #050505; + color: #f8f8f8; + font-family: + Inter, + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + sans-serif; + overflow: hidden; +} + +.editor-loading, +.editor-error { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100%; + color: #f8f8f8; + text-align: center; + padding: 2rem; +} + +.editor-loading h2 { + font-size: clamp(1.8rem, 4vw, 3rem); + color: #ffffff; + margin: 0 0 0.75rem; + letter-spacing: -0.05em; +} + +.editor-loading p { + font-size: 1rem; + color: #9b9b9b; +} + +.editor-error h2 { + font-size: clamp(1.8rem, 4vw, 3rem); + color: #ffffff; + margin: 0 0 0.75rem; + letter-spacing: -0.05em; +} + +.editor-error p { + font-size: 1.1rem; + color: #b7b7b7; + margin: 0 0 2rem; + max-width: 600px; +} + +.editor-container code { + background: #171717; + padding: 0.2rem 0.4rem; + border-radius: 4px; + color: #ffffff; + font-family: "SFMono-Regular", "Courier New", monospace; +} + +.editor-upload-section { + width: min(520px, calc(100vw - 2rem)); + background: #0d0d0d; + border-radius: 24px; + padding: 1.25rem; + border: 1px solid #2a2a2a; + box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45); +} + +.editor-upload-section h3 { + color: #ffffff; + margin: 0 0 1rem; + font-size: 0.9rem; + font-weight: 650; + letter-spacing: -0.02em; +} + +.editor-drop-zone { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + min-height: 116px; + padding: 1.25rem; + border: 1px dashed #5b5b5b; + border-radius: 18px; + background: #111111; + color: #f8f8f8; + font-weight: 650; + text-align: center; + cursor: pointer; + transition: + background 160ms ease, + border-color 160ms ease, + transform 160ms ease; + font-size: 0.95rem; + margin-bottom: 1rem; +} + +.editor-drop-zone:hover { + background: #181818; + border-color: #ffffff; + transform: translateY(-1px); +} + +.editor-folder-input { + display: none; +} + +.editor-folder-structure { + background: #080808; + border: 1px solid #202020; + border-radius: 16px; + padding: 1rem; +} + +.editor-folder-structure h4 { + color: #ffffff; + margin: 0 0 0.5rem; + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.editor-folder-structure pre { + margin: 0; + background: transparent; + color: #a7a7a7; + font-family: "SFMono-Regular", "Courier New", monospace; + font-size: 0.78rem; + line-height: 1.55; + overflow-x: auto; + white-space: pre-wrap; +} + +.editor-camera-info { + position: absolute; + top: 16px; + left: 16px; + display: flex; + align-items: center; + gap: 10px; + z-index: 2; + background: rgba(5, 5, 5, 0.78); + color: #f8f8f8; + padding: 8px 10px; + border-radius: 999px; + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: 0 16px 50px rgba(0, 0, 0, 0.35); + backdrop-filter: blur(18px); + font-size: 11px; + line-height: 1; +} + +.editor-camera-info span { + color: #9b9b9b; +} + +.editor-camera-info strong { + color: #ffffff; + font-weight: 600; +} + +.editor-controls-panel { + position: absolute; + right: 16px; + top: 16px; + bottom: 16px; + width: min(340px, calc(100vw - 32px)); + background: rgba(8, 8, 8, 0.88); + padding: 14px; + color: #f8f8f8; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 28px; + box-shadow: 0 24px 90px rgba(0, 0, 0, 0.45); + overflow-y: auto; + display: flex; + flex-direction: column; + backdrop-filter: blur(22px); + scrollbar-width: thin; + scrollbar-color: #3a3a3a transparent; +} + +.editor-controls-panel::-webkit-scrollbar { + width: 6px; +} + +.editor-controls-panel::-webkit-scrollbar-thumb { + background: #3a3a3a; + border-radius: 999px; +} + +.editor-panel-header { + padding: 12px 12px 16px; +} + +.editor-panel-kicker { + color: #8f8f8f; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +.editor-panel-header h2 { + margin: 0.35rem 0 0.45rem; + color: #ffffff; + font-size: 1.55rem; + font-weight: 720; + letter-spacing: -0.06em; +} + +.editor-panel-header p { + margin: 0; + color: #a3a3a3; + font-size: 0.84rem; + line-height: 1.45; +} + +.editor-control-section { + padding: 14px 12px; + border-top: 1px solid rgba(255, 255, 255, 0.09); +} + +.editor-section-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 10px; +} + +.editor-section-heading h3 { + margin: 0; + color: #ffffff; + font-size: 0.78rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.editor-section-heading span, +.editor-section-heading svg { + color: #777777; + font-size: 0.74rem; +} + +.editor-transform-buttons { + display: grid; + grid-template-columns: 1fr; + gap: 6px; +} + +.editor-transform-button { + display: grid; + grid-template-columns: 18px 1fr auto; + align-items: center; + gap: 10px; + width: 100%; + padding: 10px 11px; + background: #101010; + color: #d9d9d9; + border: 1px solid #242424; + border-radius: 14px; + cursor: pointer; + font-size: 0.88rem; + font-weight: 620; + text-align: left; + transition: + background 160ms ease, + border-color 160ms ease, + color 160ms ease, + transform 160ms ease; +} + +.editor-transform-button.active { + background: #ffffff; + color: #050505; + border-color: #ffffff; +} + +.editor-transform-button:hover { + background: #191919; + border-color: #5c5c5c; + color: #ffffff; + transform: translateY(-1px); +} + +.editor-transform-button.active:hover { + background: #ffffff; + color: #050505; +} + +.editor-transform-button kbd { + min-width: 22px; + padding: 3px 6px; + border-radius: 7px; + background: rgba(0, 0, 0, 0.08); + color: currentColor; + font-family: inherit; + font-size: 0.7rem; + font-weight: 720; + text-align: center; +} + +.editor-history-buttons { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-top: 8px; +} + +.editor-history-button { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + padding: 9px; + background: #101010; + color: #f2f2f2; + border: 1px solid #242424; + border-radius: 13px; + cursor: pointer; + font-size: 0.78rem; + font-weight: 650; +} + +.editor-history-button span { + color: #8e8e8e; +} + +.editor-history-button:disabled { + cursor: not-allowed; + opacity: 0.38; +} + +.editor-action-button, +.editor-player-button { + display: flex; + align-items: center; + justify-content: center; + gap: 9px; + width: 100%; + padding: 11px 12px; + background: #101010; + color: #f2f2f2; + border: 1px solid #242424; + border-radius: 14px; + cursor: pointer; + font-size: 0.88rem; + font-weight: 680; + transition: + background 160ms ease, + border-color 160ms ease, + color 160ms ease, + transform 160ms ease; +} + +.editor-action-button + .editor-action-button { + margin-top: 8px; +} + +.editor-action-button:hover, +.editor-player-button:hover { + background: #191919; + border-color: #5c5c5c; + color: #ffffff; + transform: translateY(-1px); +} + +.editor-action-button-primary, +.editor-player-button.active { + background: #ffffff; + color: #050505; + border-color: #ffffff; +} + +.editor-action-button-primary:hover, +.editor-player-button.active:hover { + background: #ffffff; + color: #050505; +} + +.editor-selected-info { + display: flex; + align-items: center; + gap: 11px; + background: #ffffff; + border: 1px solid #ffffff; + border-radius: 16px; + padding: 12px; + color: #050505; +} + +.editor-selected-info strong, +.editor-selected-info span { + display: block; +} + +.editor-selected-info strong { + font-size: 0.92rem; + line-height: 1.2; +} + +.editor-selected-info span { + color: #555555; + font-size: 0.75rem; + margin-top: 2px; +} + +.editor-no-selection { + display: flex; + align-items: center; + gap: 10px; + background: #101010; + border: 1px dashed #363636; + border-radius: 16px; + padding: 12px; + color: #8f8f8f; + font-size: 0.86rem; +} + +.editor-shortcuts-list { + display: grid; + gap: 7px; + margin: 0; +} + +.editor-shortcuts-list div { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 7px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.editor-shortcuts-list div:last-child { + border-bottom: 0; +} + +.editor-shortcuts-list dt, +.editor-shortcuts-list dd { + margin: 0; + font-size: 0.76rem; +} + +.editor-shortcuts-list dt { + color: #ffffff; + font-weight: 700; +} + +.editor-shortcuts-list dd { + color: #8d8d8d; + text-align: right; +} + +.editor-json-section { + display: flex; + flex-direction: column; + min-height: 240px; + padding: 14px 12px 12px; + border-top: 1px solid rgba(255, 255, 255, 0.09); +} + +.editor-json-view { + flex: 1; + max-height: 320px; + margin: 0; + padding: 8px 0; + overflow: auto; + background: #050505; + border: 1px solid #1f1f1f; + border-radius: 16px; + color: #d7d7d7; + font-family: "SFMono-Regular", "Courier New", monospace; + font-size: 0.72rem; + line-height: 1.55; + scrollbar-width: thin; + scrollbar-color: #3a3a3a transparent; +} + +.editor-json-view::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +.editor-json-view::-webkit-scrollbar-thumb { + background: #3a3a3a; + border-radius: 999px; +} + +.editor-json-view code { + display: grid; + grid-template-columns: 34px max-content; + gap: 10px; + min-width: 100%; + padding: 0 12px; + background: transparent; + color: inherit; + font-family: inherit; + white-space: pre; +} + +.editor-json-view code span { + color: #5f5f5f; + text-align: right; + user-select: none; +} + +.editor-json-view code.is-selected { + background: #111111; + color: #f2f2f2; +} + +.editor-json-view code.is-selected * { + color: #f2f2f2; +} + +.editor-json-view code.is-selected span { + color: #8a8a8a; +} + +.editor-json-hint { + display: flex; + align-items: center; + gap: 7px; + margin-top: 8px; + color: #8d8d8d; + font-size: 0.74rem; +} + +@media (max-width: 768px) { + .editor-error h2 { + font-size: 1.5rem; + } + + .editor-upload-section { + padding: 1.5rem; + } + + .editor-drop-zone { + padding: 1.5rem 1rem; + } + + .editor-camera-info { + display: none; + } + + .editor-controls-panel { + top: auto; + right: 10px; + bottom: 10px; + left: 10px; + width: auto; + max-height: 46vh; + border-radius: 22px; + } + + .editor-json-section { + min-height: 180px; + } +} diff --git a/src/main.tsx b/src/main.tsx index ef474bf..b3c2e37 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,13 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; +import { BrowserRouter } from "react-router-dom"; +import App from "./App"; import "./index.css"; createRoot(document.getElementById("root")!).render( - + + + , ); diff --git a/src/pages/editor/EditorPage.tsx b/src/pages/editor/EditorPage.tsx new file mode 100644 index 0000000..2e3cfe1 --- /dev/null +++ b/src/pages/editor/EditorPage.tsx @@ -0,0 +1,195 @@ +import { useCallback, useState } from "react"; +import { Canvas } from "@react-three/fiber"; +import { EditorControls } from "@/features/editor/components/EditorControls"; +import { useEditorHistory } from "@/features/editor/hooks/useEditorHistory"; +import { useEditorSceneData } from "@/features/editor/hooks/useEditorSceneData"; +import { EditorScene } from "@/features/editor/scene/EditorScene"; +import type { MapNode, TransformMode } from "@/types/editor"; + +export function EditorPage(): React.JSX.Element { + const { + hasMapJson, + isMapLoading, + sceneData, + setSceneData, + handleFolderUpload, + } = useEditorSceneData(); + + const [selectedNodeIndex, setSelectedNodeIndex] = useState( + null, + ); + const [hoveredNodeIndex, setHoveredNodeIndex] = useState(null); + const [transformMode, setTransformMode] = + useState("translate"); + const [isPlayerMode, setIsPlayerMode] = useState(false); + + const { + undoCount, + redoCount, + handleUndo, + handleRedo, + handleTransformStart, + handleTransformEnd, + } = useEditorHistory(sceneData, setSceneData); + + const handleSelectNode = useCallback((index: number | null) => { + setSelectedNodeIndex(index); + }, []); + + const handleHoverNode = useCallback((index: number | null) => { + setHoveredNodeIndex(index); + }, []); + + const handleTransformModeChange = useCallback((mode: TransformMode) => { + setTransformMode(mode); + }, []); + + const handleSaveToServer = useCallback(async () => { + if (!sceneData) return; + const json = JSON.stringify(sceneData.mapNodes, null, 2); + + try { + const response = await fetch("/api/save-map", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: json, + }); + + if (response.ok) { + alert("Map enregistrée avec succès!"); + } else { + alert("Erreur lors de l'enregistrement"); + } + } catch (err) { + console.error("Error saving map:", err); + alert("Erreur lors de l'enregistrement"); + } + }, [sceneData]); + + const handleExportJson = useCallback(() => { + if (!sceneData) return; + const json = JSON.stringify(sceneData.mapNodes, null, 2); + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "map.json"; + a.click(); + window.setTimeout(() => URL.revokeObjectURL(url), 0); + }, [sceneData]); + + const handlePlayerMode = useCallback(() => { + setIsPlayerMode((prev) => !prev); + }, []); + + const handleNodeTransform = useCallback( + (nodeIndex: number, updatedNode: MapNode) => { + setSceneData((prev) => { + if (!prev) return null; + const newMapNodes = [...prev.mapNodes]; + newMapNodes[nodeIndex] = updatedNode; + return { ...prev, mapNodes: newMapNodes }; + }); + }, + [setSceneData], + ); + + if (isMapLoading) { + return ( + + + Chargement de l'éditeur... + Vérification de map.json dans public/ + + + ); + } + + if (!hasMapJson) { + return ( + + + Erreur : map.json introuvable + + Le fichier map.json est requis dans le dossier public/. + + + + Télécharger un dossier contenant map.json + + + + Choisir un dossier contenant map.json + + + + Structure requise : + + public/ ├── map.json (à la racine) └── models/ + ├── arbre/ │ └── model.gltf ├── building/ │ └── model.gltf └── + ... + + + + + + ); + } + + return ( + + { + gl.setClearColor("#050505"); + }} + > + + + + {sceneData && ( + + )} + + ); +} diff --git a/src/types/editor.ts b/src/types/editor.ts new file mode 100644 index 0000000..89b601f --- /dev/null +++ b/src/types/editor.ts @@ -0,0 +1,16 @@ +import type { Vector3Tuple } from "@/types/3d"; + +export interface MapNode { + name: string; + type: string; + position: Vector3Tuple; + rotation: Vector3Tuple; + scale: Vector3Tuple; +} + +export interface SceneData { + mapNodes: MapNode[]; + models: Map; +} + +export type TransformMode = "translate" | "rotate" | "scale"; diff --git a/src/types/interaction.ts b/src/types/interaction.ts index 986bed9..5e65bf6 100644 --- a/src/types/interaction.ts +++ b/src/types/interaction.ts @@ -1,6 +1,6 @@ export type InteractableKind = "grab" | "trigger"; -export interface TriggerInteractableHandle { +interface TriggerInteractableHandle { kind: "trigger"; label: string; onPress: () => void; diff --git a/src/types/logger.ts b/src/types/logger.ts index 3eb022a..7384c4c 100644 --- a/src/types/logger.ts +++ b/src/types/logger.ts @@ -1,6 +1,6 @@ export type LogLevel = "debug" | "info" | "warn" | "error"; -export type LogValue = +type LogValue = | string | number | boolean diff --git a/src/utils/editor/loadEditorScene.ts b/src/utils/editor/loadEditorScene.ts new file mode 100644 index 0000000..1ca8896 --- /dev/null +++ b/src/utils/editor/loadEditorScene.ts @@ -0,0 +1,41 @@ +import type { MapNode, SceneData } from "@/types/editor"; + +const MAP_JSON_PATH = "/map.json"; + +export async function createSceneDataFromFiles( + files: FileList, +): Promise { + const fileMap = new Map(); + + for (const file of Array.from(files)) { + fileMap.set(getProjectRelativePath(file), file); + } + + const mapFile = fileMap.get(MAP_JSON_PATH); + if (!mapFile) { + throw new Error("Fichier map.json manquant à la racine du dossier"); + } + + const mapNodes: MapNode[] = JSON.parse(await mapFile.text()); + const models = new Map(); + + for (const [path, file] of fileMap.entries()) { + const modelMatch = path.match(/^\/models\/(.+)\/model\.gltf$/); + if (modelMatch?.[1]) { + models.set(modelMatch[1], URL.createObjectURL(file)); + } + } + + return { mapNodes, models }; +} + +function getProjectRelativePath(file: File): string { + const relativePath = file.webkitRelativePath || file.name; + + if (!relativePath.includes("/")) { + return `/${relativePath}`; + } + + const [, ...pathParts] = relativePath.split("/"); + return `/${pathParts.join("/")}`; +} diff --git a/src/utils/loadMapSceneData.ts b/src/utils/loadMapSceneData.ts new file mode 100644 index 0000000..9ba2f8f --- /dev/null +++ b/src/utils/loadMapSceneData.ts @@ -0,0 +1,40 @@ +import type { MapNode, SceneData } from "@/types/editor"; + +const MAP_JSON_PATH = "/map.json"; +const MODEL_FILE_NAME = "model.gltf"; + +export async function loadMapSceneData(): Promise { + const response = await fetch(MAP_JSON_PATH); + + if (!response.ok) { + return null; + } + + const mapNodes: MapNode[] = await response.json(); + return createSceneData(mapNodes); +} + +async function createSceneData(mapNodes: MapNode[]): Promise { + const models = await loadMapModelUrls(mapNodes); + return { mapNodes, models }; +} + +async function loadMapModelUrls( + mapNodes: MapNode[], +): Promise> { + const uniqueModelNames = [...new Set(mapNodes.map((node) => node.name))]; + const modelEntries = await Promise.all( + uniqueModelNames.map(async (modelName) => { + const modelUrl = `/models/${modelName}/${MODEL_FILE_NAME}`; + + try { + const response = await fetch(modelUrl, { method: "HEAD" }); + return response.ok ? ([modelName, modelUrl] as const) : null; + } catch { + return null; + } + }), + ); + + return new Map(modelEntries.filter((entry) => entry !== null)); +} diff --git a/src/world/GameMap.tsx b/src/world/GameMap.tsx new file mode 100644 index 0000000..967c0a8 --- /dev/null +++ b/src/world/GameMap.tsx @@ -0,0 +1,80 @@ +import { useEffect, useMemo, useState, useRef } from "react"; +import { useGLTF } from "@react-three/drei"; +import * as THREE from "three"; +import { useOctreeGraphNode } from "@/hooks/useOctreeGraphNode"; +import { loadMapSceneData } from "@/utils/loadMapSceneData"; +import type { OctreeReadyHandler } from "@/types/3d"; +import type { MapNode } from "@/types/editor"; + +interface GameMapProps { + onOctreeReady: OctreeReadyHandler; +} + +export function GameMap({ onOctreeReady }: GameMapProps): React.JSX.Element { + const [mapNodes, setMapNodes] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const groupRef = useRef(null); + + useOctreeGraphNode(groupRef, onOctreeReady); + + useEffect(() => { + const loadMap = async () => { + try { + const sceneData = await loadMapSceneData(); + if (!sceneData) { + console.warn("map.json not found"); + setIsLoading(false); + return; + } + + setMapNodes( + sceneData.mapNodes.filter((node) => sceneData.models.has(node.name)), + ); + } catch (error) { + console.error("Error loading map:", error); + } finally { + setIsLoading(false); + } + }; + + loadMap(); + }, []); + + if (isLoading) { + return <>>; + } + + return ( + + {mapNodes.map((node, index) => ( + + ))} + + ); +} + +function ModelInstance({ node }: { node: MapNode }): React.JSX.Element { + const modelPath = `/models/${node.name}/model.gltf`; + const groupRef = useRef(null); + const { scene } = useGLTF(modelPath); + const sceneInstance = useMemo(() => scene.clone(true), [scene]); + const { position, rotation, scale } = node; + + useEffect(() => { + if (groupRef.current) { + groupRef.current.position.set(...position); + groupRef.current.rotation.set(...rotation); + groupRef.current.scale.set(...scale); + } + }, [position, rotation, scale]); + + return ( + + ); +} diff --git a/src/world/World.tsx b/src/world/World.tsx index af7c39f..3798050 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -10,7 +10,7 @@ import { DebugCameraControls } from "@/utils/debug/scene/DebugCameraControls"; import { DebugHelpers } from "@/utils/debug/scene/DebugHelpers"; import { Environment } from "@/world/Environment"; import { Lighting } from "@/world/Lighting"; -import { Map } from "@/world/Map"; +import { GameMap } from "@/world/GameMap"; import { PlayerComponent } from "@/world/player/PlayerComponent"; import { TestScene } from "@/world/debug/TestScene"; @@ -31,7 +31,7 @@ export function World(): React.JSX.Element { {cameraMode === "debug" ? : null} {sceneMode === "game" ? ( - + ) : ( )} diff --git a/vite.config.ts b/vite.config.ts index a8b3919..e91d93a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,9 +1,103 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import path from "node:path"; +import fs from "node:fs"; import { fileURLToPath } from "node:url"; +import type { ServerResponse } from "node:http"; +import type { Plugin } from "vite"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +const MAX_MAP_PAYLOAD_BYTES = 1024 * 1024; +const JSON_HEADERS = { "Content-Type": "application/json" }; + +function sendJson( + res: ServerResponse, + status: number, + body: unknown, + headers: Record = {}, +): void { + res + .writeHead(status, { ...JSON_HEADERS, ...headers }) + .end(JSON.stringify(body)); +} + +function isVector3(value: unknown): value is [number, number, number] { + return ( + Array.isArray(value) && + value.length === 3 && + value.every((item) => typeof item === "number" && Number.isFinite(item)) + ); +} + +function isMapNode(value: unknown): value is Record { + if (typeof value !== "object" || value === null) { + return false; + } + + const node = value as Record; + + return ( + typeof node.name === "string" && + typeof node.type === "string" && + isVector3(node.position) && + isVector3(node.rotation) && + isVector3(node.scale) + ); +} + +function isMapPayload(value: unknown): boolean { + return Array.isArray(value) && value.every(isMapNode); +} + +const saveMapPlugin = (): Plugin => ({ + name: "save-map-api", + configureServer(server) { + server.middlewares.use("/api/save-map", async (req, res) => { + if (req.method !== "POST") { + sendJson(res, 405, { error: "Method not allowed" }, { Allow: "POST" }); + return; + } + + const chunks: Buffer[] = []; + let size = 0; + + for await (const chunk of req) { + const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); + size += buffer.length; + if (size > MAX_MAP_PAYLOAD_BYTES) { + sendJson(res, 413, { error: "Payload too large" }); + req.destroy(); + return; + } + chunks.push(buffer); + } + + try { + const data = JSON.parse(Buffer.concat(chunks).toString()); + if (!isMapPayload(data)) { + sendJson(res, 400, { error: "Invalid map payload" }); + return; + } + + const mapPath = path.resolve(__dirname, "public/map.json"); + await fs.promises.writeFile( + mapPath, + JSON.stringify(data, null, 2), + "utf8", + ); + sendJson(res, 200, { success: true }); + } catch (err) { + const status = err instanceof SyntaxError ? 400 : 500; + const message = err instanceof Error ? err.message : "Unknown error"; + sendJson(res, status, { error: message }); + } + }); + }, +}); export default defineConfig({ - plugins: [react()], + plugins: [react(), saveMapPlugin()], resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)),
Vérification de map.json dans public/
+ Le fichier map.json est requis dans le dossier public/. +
public/
+ public/ ├── map.json (à la racine) └── models/ + ├── arbre/ │ └── model.gltf ├── building/ │ └── model.gltf └── + ... +