diff --git a/docs/technical/map-lod.md b/docs/technical/map-lod.md index 952a09c..b43e6f5 100644 --- a/docs/technical/map-lod.md +++ b/docs/technical/map-lod.md @@ -25,7 +25,7 @@ Current behavior: | -------- | ------------------: | --- | ------------------------------------- | | `low` | 10m | On | Always use `*-LOD` models | | `medium` | 20m | On | Always use `*-LOD` models | -| `high` | Current default 50m | Off | Regular model up to 10m, then `*-LOD` | +| `high` | 35m | Off | Regular model up to 10m, then `*-LOD` | | `ultra` | 50m | Off | Regular model up to 20m, then `*-LOD` | The unload distance stays slightly larger than the load distance to avoid rapid mount/unmount flickering when the player stands near a boundary. diff --git a/docs/technical/map-performance.md b/docs/technical/map-performance.md index 853b890..5b509b4 100644 --- a/docs/technical/map-performance.md +++ b/docs/technical/map-performance.md @@ -158,9 +158,11 @@ Current runtime values: ```txt chunkSize: 35 -loadRadius: 45 -unloadRadius: 45 -updateInterval: 350ms +low load/unload radius: 10m / 18m +medium load/unload radius: 20m / 30m +high load/unload radius: 35m / 45m +ultra load/unload radius: 50m / 65m +updateInterval: 250ms fog near: 30 fog far: 45 ``` diff --git a/docs/technical/scene-runtime.md b/docs/technical/scene-runtime.md index 08f38e2..20848ab 100644 --- a/docs/technical/scene-runtime.md +++ b/docs/technical/scene-runtime.md @@ -74,22 +74,32 @@ It tracks: - `gameMapLoaded`: map data and visible map nodes settled - `gameStageLoaded`: Rapier gameplay stage mounted - `showGameStage`: true when the map is ready enough to mount gameplay content -- `shadowsReady`: renderer, shadow lights, and scene matrices have been forced once after the scene is mounted -- `gameplayReady`: true when map, stage, octree, and the shadow warmup are all ready +- `gameplayReady`: true when map, stage, and octree are all ready -The base game-scene readiness condition before the shadow warmup is: +The game-scene readiness condition is: ```ts showGameStage && gameStageLoaded && octree !== null; ``` -After that condition is met, `SceneShadowWarmup` runs one final loading step: +Shadows are configured once when `Lighting` mounts (renderer `shadowMap.enabled`, sun +`shadow.autoUpdate = true`, bias and frustum from `SHADOW_CONFIG` in +`src/data/world/lightingConfig.ts`). The shadow map then refreshes every frame and +follows the player camera through the sun's `target`. The earlier `SceneShadowWarmup` +step has been removed — the visible loading overlay no longer waits for a forced +shadow refresh because `autoUpdate` covers steady-state rendering. -```txt -Activation des ombres -> Ombres prêtes -> Gameplay prêt -``` +### Avoiding global scene remounts -This keeps the loading overlay visible until the renderer shadow map, shadow-casting light, and mounted scene graph have all been explicitly refreshed. +Heavy stage components (`GameStageContent`, `Player`, dialogues) load assets via +`useGLTF`/`useTexture` without preload (e.g. `EbikeSpeedometer` calls `useTexture` +when the bike mounts). To prevent any late suspension from bubbling up to the +root `` boundary in `src/pages/page.tsx` and unmounting the entire +world (which would trigger a redundant octree rebuild and shadow re-config), the +game stage block and the spawn-player block are wrapped in their own +`` boundaries inside `src/world/World.tsx`. Any new +sibling that suspends late should be added inside one of these boundaries or get +its own. The debug physics scene is ready when: diff --git a/docs/technical/three-debugging.md b/docs/technical/three-debugging.md index 1e7c8aa..467239c 100644 --- a/docs/technical/three-debugging.md +++ b/docs/technical/three-debugging.md @@ -20,3 +20,63 @@ If DevTools still opens a bundled file, stop the dev server, clear Vite's cached rm -rf node_modules/.vite npm run dev:three-debug ``` + +## Visual debug toggles + +The `Debug` folder of the runtime debug GUI exposes inspection toggles backed by +`src/managers/stores/useDebugVisualsStore.ts`: + +- **Show Player Model** — renders the main character GLTF in front of the + current camera (`src/components/debug/DebugPlayerModel.tsx`). The model is + positioned in camera-local space so it stays visible regardless of pitch. +- **Show Octree** — overlays the collision octree as colored line segments, + one wireframe per spatial cell (`src/components/debug/DebugOctreeVisualization.tsx`). + Cells are colored by depth. Use it to inspect collision precision around + doorways or passages. +- **Octree Max Depth** — caps how deep the octree visualization recurses + (default 6). Increase to see leaf-level subdivisions; decrease to keep the + scene readable when the tree is large. + +The octree visualization reads the live `Octree` instance from `World`. The +mesh uses `depthTest: false` and a high `renderOrder`, so cells stay visible +through opaque geometry. + +## Shadow rendering intermittence + +Shadows occasionally failed to render on initial load and could disappear +mid-session even though the `Lighting` configuration ran to completion. The +fix has two layers: + +### Per-frame refresh (steady state) + +The sun follows the camera, so its world matrix is dirty every frame. With +`shadow.autoUpdate` alone, three.js can skip the shadow map re-render on a +frame where the matrix update has happened but the renderer's internal dirty +tracking does not pick it up. To prevent that, `Lighting.useFrame` sets +`sun.shadow.needsUpdate = true` after the per-frame matrix updates. Shadow +config is centralized in `src/data/world/lightingConfig.ts` (`bias=0`, +`normalBias=0`, `cameraSize=95`). + +### Mount-time shadow map reallocation (`useShadowMapWarmup`) + +The merged static map and other GLTFs mount imperatively after `Lighting`, +so the shadow render target ends up linked to a renderer state that pre-dates +the final scene. Materials compiled at that point bake a "no shadow map" +permutation into their shader program and silently fail to render shadows +until a WebGL context-restore cycle (the kind triggered by Chrome DevTools +in `?debug` runs) reallocates everything. + +`src/hooks/three/useShadowMapWarmup.ts` replays that cycle programmatically +without the cost of a full context loss. It runs a `useFrame` watchdog that +samples the scene mesh count every 6 frames; once the count has been stable +for ~1 s (or after a 5 s safety cap), it: + +1. Disposes the directional light shadow map and nulls it. three.js + reallocates the render target on the next render at the configured + `mapSize`. +2. Marks every material's `needsUpdate = true`, forcing a shader recompile + that rebinds every program to the freshly created shadow sampler. +3. Forces a single shadow pass and invalidates the renderer. + +The watchdog runs once per mount and adds a single traversal every 6 frames +during the warmup window, after which it self-terminates. diff --git a/docs/technical/webgl-context-lost-investigation.md b/docs/technical/webgl-context-lost-investigation.md new file mode 100644 index 0000000..f6a2dd8 --- /dev/null +++ b/docs/technical/webgl-context-lost-investigation.md @@ -0,0 +1,367 @@ +# WebGL Context Lost - Investigation + +## Résumé court + +Le projet subit des pertes de contexte WebGL pendant les phases où le jeu active +ou prépare le hand tracking, les interactions physiques ou le repair game. + +Le symptôme visible côté console est : + +```txt +THREE.WebGLRenderer: Context Lost. +[ERROR] [WebGL] Context lost - attempting auto-restore +THREE.WebGLRenderer: Context Restored. +``` + +Le problème est bloquant parce que le hand tracking et le repair game sont au +coeur de l'expérience. Quand le contexte WebGL saute, la scène Three.js peut se +remonter, le joueur peut revenir au spawn, le pointer lock peut être perdu, et +les tests de gameplay deviennent instables. + +## Ce qui fonctionne aujourd'hui + +La page principale monte un `` React Three Fiber dans +`src/pages/page.tsx`. + +`src/world/World.tsx` compose ensuite : + +- la scène de jeu ou la scène de test physique ; +- le player ; +- les systèmes visuels de monde ; +- les gants de hand tracking ; +- les systèmes de debug. + +Le hand tracking est centralisé dans +`src/providers/gameplay/HandTrackingProvider.tsx`. + +Il peut utiliser deux sources : + +- `browser` : MediaPipe JS dans le navigateur ; +- `backend` : backend Python local via WebSocket. + +L'activation est déclenchée par : + +- certaines étapes du repair game ; +- les zones d'interaction qui demandent explicitement les mains ; +- la scène Physique en debug, selon les objets présents. + +## Problème observé + +Les context lost arrivent dans plusieurs situations : + +- entrée dans une zone d'interaction ; +- lancement du hand tracking ; +- lancement d'un repair game ; +- scène Physique avec `TestMap`, `Physics`, `AnimatedModel`, waypoints GPS et + objets interactifs ; +- source browser JS ; +- source backend. + +Le fait que le crash existe avec les deux sources indique que le problème n'est +probablement pas limité au backend Python ni à MediaPipe JS seul. Le hand +tracking semble être un déclencheur fort, mais il arrive au moment où plusieurs +ressources GPU et systèmes runtime se réveillent ensemble. + +## Pourquoi c'est bloquant + +Ce bug bloque la feature principale du projet : + +- le repair game dépend du hand tracking pour valider certaines actions ; +- les interactions main sont nécessaires pour tester les objets grabbables ; +- un context lost casse la continuité du gameplay ; +- le joueur peut être replacé au spawn après reconstruction ; +- le pointer lock peut être perdu ; +- les logs deviennent difficiles à lire parce que le jeu tente de restaurer la + scène en boucle ; +- le comportement n'est pas fiable pour une démo ou un déploiement. + +Tant que ce problème n'est pas stable, on ne peut pas valider correctement : + +- la mission e-bike ; +- la mission pylône ; +- la mission ferme ; +- les interactions main ; +- le switch browser/backend ; +- le comportement en build de production. + +## Hypothèses principales + +### 1. Pression GPU au lancement du hand tracking + +MediaPipe browser peut créer ses propres ressources GPU. Si Three.js charge +déjà beaucoup de géométries, textures, ombres et modèles, l'ajout du hand +tracking peut faire passer le navigateur au-dessus d'une limite GPU. + +Le stash contient une tentative de mitigation en forçant MediaPipe browser et le +backend à utiliser le CPU. + +### 2. Activation trop brusque du runtime mains + +Les logs montrent des transitions rapides : + +```txt +Browser JS runtime starting +Runtime source selected +Runtime snapshot changed +Browser JS runtime stopped +Browser JS runtime starting +``` + +Ce type de start/stop rapide peut provoquer : + +- création webcam ; +- création MediaPipe ; +- montage des gants ; +- update du state React ; +- re-render du monde ; +- stress GPU au même moment. + +### 3. Les gants 3D sont montés trop tôt + +Si les gants de hand tracking sont montés avant d'avoir de vraies mains +détectées, le jeu charge et prépare des modèles GPU sans utilité immédiate. + +Le stash contient une tentative pour ne rendre les gants que lorsqu'une main +existe réellement dans le snapshot. + +### 4. Re-upload textures / GLTF trop agressif + +`src/utils/three/optimizeGLTFScene.ts` modifie des textures GLTF. Si cette +optimisation force trop souvent `needsUpdate`, mipmaps ou anisotropy, le +navigateur peut recharger beaucoup de textures vers le GPU. + +Le stash limite cette pression en évitant de forcer les mipmaps et en abaissant +l'anisotropy. + +### 5. Permission caméra au mauvais moment + +Demander la caméra au moment exact où le joueur entre dans une interaction ou +lance le repair game ajoute un gros événement runtime au pire moment. + +Le stash contient une tentative de warmup caméra pour obtenir la permission plus +tôt et réutiliser le stream au moment où le hand tracking devient nécessaire. + +### 6. La scène Physique ajoute du bruit + +La scène Physique est une scène de test volontairement riche : + +- `Physics` Rapier ; +- `GrabbableObject` ; +- `TriggerObject` ; +- `RepairGame` ; +- `AnimatedModel` ; +- GPS preview ; +- waypoints verts ; +- player ; +- debug overlay. + +Cette richesse est normale pour une scène de test, mais elle complique +l'investigation parce qu'elle active beaucoup de systèmes à la fois. + +## Fichiers modifiés dans le stash + +Le stash `stash@{0}` contient 28 fichiers modifiés, environ `+530 / -152`. +Il ne contient pas de fichiers untracked. + +| Fichier | Rôle dans l'investigation | +| --------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `README.md` | Note sur les commandes backend depuis la racine du repo. | +| `backend/README.md` | Documentation plus claire pour lancer le backend et réparer un `.venv` cassé. | +| `backend/hand_tracker.py` | Force le backend MediaPipe en CPU. | +| `docs/user/main-feature.md` | Ajustements de documentation utilisateur. | +| `public/sounds/dialogue/subtitles/fr/electricienne.srt` | Ajustements de sous-titres, pas central pour le context lost. | +| `public/sounds/dialogue/subtitles/fr/narrateur.srt` | Ajustements de sous-titres, pas central pour le context lost. | +| `src/components/debug/DebugPlayerModel.tsx` | Ajustements de modèle debug player. | +| `src/components/three/handTracking/HandTrackingGlove.tsx` | Retire le preload automatique des gants pour réduire la pression GPU. | +| `src/components/three/interaction/GrabbableObject.tsx` | Marque les grabbables qui nécessitent vraiment le hand tracking. | +| `src/components/three/interaction/InteractableObject.tsx` | Ajoute le flag `handTracking` aux interactables. | +| `src/data/debug/testSceneConfig.ts` | Stabilise la scène Physique : sol, GPS, hauteur des waypoints. | +| `src/data/handTrackingConfig.ts` | Ajoute délai d'activation, TTL warmup caméra, delegate CPU browser. | +| `src/data/player/playerConfig.ts` | Corrige le spawn Physique avec `PLAYER_EYE_HEIGHT`. | +| `src/hooks/debug/useSceneMode.ts` | Force `game` hors debug actif pour éviter des scènes debug en prod. | +| `src/hooks/handTracking/useBothFistsHold.ts` | Sort le hold des deux poings de `useFrame` R3F vers `requestAnimationFrame`. | +| `src/hooks/handTracking/useBrowserHandTracking.ts` | Encadre `detectForVideo`, release MediaPipe en cleanup, gère les erreurs. | +| `src/hooks/three/useTerrainHeight.ts` | Ajustements terrain, liés au snap/player. | +| `src/lib/handTracking/browserHandTracking.ts` | Force delegate CPU, garde une instance MediaPipe, ajoute `releaseBrowserHandLandmarker`. | +| `src/lib/handTracking/handTrackingSession.ts` | Ajoute warmup caméra, cache stream, timeout et consommation du stream préparé. | +| `src/managers/InteractionManager.ts` | Ajoute `handTrackingNearby` pour ne pas activer les mains sur toute interaction. | +| `src/pages/page.tsx` | Gestion WebGL context lost/restored, DPR fixe, antialias off, release MediaPipe au crash. | +| `src/providers/gameplay/HandTrackingProvider.tsx` | Ajoute activation différée, snapshot queued, warmup runtime. | +| `src/types/interaction/interaction.ts` | Ajoute `handTracking` et `handTrackingNearby` aux types interaction. | +| `src/utils/debug/Debug.ts` | Synchronise l'affichage du controller hand tracking source. | +| `src/utils/three/optimizeGLTFScene.ts` | Réduit la pression GPU des textures GLTF. | +| `src/world/World.tsx` | Ne rend les gants que si une main correspondante est détectée. | +| `src/world/debug/TestMap.tsx` | Nettoie les logs, stabilise waypoints/GPS/scène Physique. | +| `src/world/player/PlayerCamera.tsx` | Ajustements pointer lock/canvas ciblé. | + +## Fichiers actuellement modifiés dans le worktree + +Etat observé au moment de cette note : + +| Fichier | Statut | +| --------------------------------------------------------- | --------------------------------------------------------- | +| `public/models/talkie/*` | Beaucoup d'anciennes textures/fichiers `.gltf` supprimés. | +| `public/models/talkie/model.glb` | Nouveau fichier non suivi. | +| `src/components/three/handTracking/HandTrackingGlove.tsx` | Modifié. | +| `src/data/debug/testSceneConfig.ts` | Modifié. | +| `src/data/gameplay/repairMissions.ts` | Modifié. | +| `src/data/handTrackingConfig.ts` | Modifié. | +| `src/data/player/playerConfig.ts` | Modifié. | +| `src/data/world/mapLodConfig.ts` | Modifié. | +| `src/hooks/handTracking/useBrowserHandTracking.ts` | Modifié. | +| `src/hooks/handTracking/useRemoteHandTracking.ts` | Modifié. | +| `src/lib/handTracking/browserHandTracking.ts` | Modifié. | +| `src/lib/handTracking/handTrackingSession.ts` | Modifié. | +| `src/pages/page.tsx` | Modifié. | +| `src/providers/gameplay/HandTrackingProvider.tsx` | Modifié. | +| `src/utils/debug/Debug.ts` | Modifié. | +| `src/utils/three/optimizeGLTFScene.ts` | Modifié. | +| `src/world/World.tsx` | Modifié. | +| `src/world/debug/TestMap.tsx` | Modifié. | +| `src/world/player/Player.tsx` | Modifié. | +| `src/world/player/PlayerCamera.tsx` | Modifié. | +| `src/world/player/PlayerController.tsx` | Modifié. | +| `src/components/ui/RuntimeLoadingIndicator.tsx` | Nouveau fichier non suivi. | +| `src/hooks/handTracking/useHandTrackingRuntimeWarmup.ts` | Nouveau fichier non suivi. | +| `src/world/player/playerRuntimeSnapshot.ts` | Nouveau fichier non suivi. | + +Attention : les fichiers supprimés/nouveaux du talkie semblent être un sujet +séparé du context lost. Il faut les garder séparés dans les commits. + +## Fichiers directement impactés par le bug + +### Canvas et WebGL + +- `src/pages/page.tsx` +- `src/world/World.tsx` +- `src/utils/three/optimizeGLTFScene.ts` + +Ces fichiers influencent directement la charge GPU, la configuration du canvas, +les ressources GLTF et le comportement au context lost/restored. + +### Hand tracking + +- `src/providers/gameplay/HandTrackingProvider.tsx` +- `src/hooks/handTracking/useBrowserHandTracking.ts` +- `src/hooks/handTracking/useRemoteHandTracking.ts` +- `src/hooks/handTracking/useBothFistsHold.ts` +- `src/hooks/handTracking/useHandTrackingRuntimeWarmup.ts` +- `src/lib/handTracking/browserHandTracking.ts` +- `src/lib/handTracking/handTrackingSession.ts` +- `src/data/handTrackingConfig.ts` +- `src/components/three/handTracking/HandTrackingGlove.tsx` +- `backend/hand_tracker.py` + +Ces fichiers contrôlent le déclenchement, la source, la caméra, MediaPipe, le +backend et le rendu visuel des mains. + +### Interactions et repair game + +- `src/components/three/interaction/GrabbableObject.tsx` +- `src/components/three/interaction/InteractableObject.tsx` +- `src/managers/InteractionManager.ts` +- `src/types/interaction/interaction.ts` +- `src/components/three/gameplay/RepairGame.tsx` +- `src/hooks/gameplay/useRepairMissionStep.ts` +- `src/hooks/gameplay/useRepairMovementLocked.ts` + +Ces fichiers sont impactés parce que l'entrée dans une zone ou une étape repair +peut déclencher le hand tracking. + +### Player et restauration après crash + +- `src/world/player/Player.tsx` +- `src/world/player/PlayerCamera.tsx` +- `src/world/player/PlayerController.tsx` +- `src/world/player/playerRuntimeSnapshot.ts` +- `src/data/player/playerConfig.ts` + +Ces fichiers influencent le spawn, la caméra, le pointer lock, et la possibilité +de récupérer la dernière position après un context lost. + +### Scène Physique / debug + +- `src/world/debug/TestMap.tsx` +- `src/data/debug/testSceneConfig.ts` +- `src/components/debug/DebugPlayerModel.tsx` +- `src/hooks/debug/useSceneMode.ts` +- `src/utils/debug/Debug.ts` + +Ces fichiers ne sont pas forcément la cause racine, mais ils créent une scène de +stress utile pour reproduire le bug. + +## Ce que le stash essayait de corriger + +Le stash essaye de réduire le risque de context lost avec plusieurs leviers : + +1. passer MediaPipe browser/backend en CPU ; +2. libérer MediaPipe quand le runtime s'arrête ou quand WebGL saute ; +3. éviter de monter les gants sans mains détectées ; +4. retarder l'activation du hand tracking pour éviter les start/stop violents ; +5. demander la caméra plus tôt et réutiliser le stream ; +6. réduire la charge GPU du canvas avec DPR fixe et antialias off ; +7. limiter les re-uploads de textures GLTF ; +8. distinguer les interactions qui demandent vraiment le hand tracking ; +9. restaurer WebGL avec une limite pour éviter les boucles infinies ; +10. conserver la position du joueur après restauration. + +## Ce qui reste à prouver + +Il faut encore isoler le déclencheur exact : + +- crash avec hand tracking désactivé complètement ; +- crash avec source browser JS seulement ; +- crash avec source backend seulement ; +- crash avec gants 3D désactivés ; +- crash avec MediaPipe CPU ; +- crash avec `AnimatedModel` de TestMap désactivé ; +- crash avec GPS preview/waypoints désactivés ; +- crash avec shadows/antialias/DPR réduits ; +- crash en scène game réelle, pas seulement scène Physique. + +## Plan d'investigation recommandé + +1. Stabiliser le worktree et ne pas mélanger assets talkie, LOD, docs backend et + context lost dans le même commit. +2. Garder le stash tant que le fix final n'est pas validé. +3. Créer un commit ou patch isolé pour les logs context lost seulement. +4. Ajouter un switch debug qui permet de couper séparément : + - hand tracking runtime ; + - gants 3D ; + - MediaPipe browser ; + - backend ; + - GPS preview ; + - AnimatedModel de TestMap. +5. Reproduire le bug avec une matrice claire. +6. Garder les changements qui diminuent réellement les context lost. +7. Supprimer les logs temporaires une fois le diagnostic terminé. + +## Recommandation Git + +Ne pas supprimer le stash maintenant. + +Il contient du travail réel sur le context lost. Même s'il n'est pas parfait, il +sert de trace d'investigation et contient des morceaux utiles. + +Avant de le supprimer, sauvegarder le patch : + +```bash +git stash show -p stash@{0} > context-lost-stash.patch +``` + +Ensuite seulement, si tout a été repris dans des commits propres : + +```bash +git stash drop stash@{0} +``` + +## Commits logiques proposés + +Séparer en plusieurs commits pour éviter un gros commit illisible : + +1. `docs: document webgl context lost investigation` +2. `fix: reduce handtracking gpu pressure` +3. `fix: delay handtracking activation` +4. `fix: preserve player state after webgl restore` +5. `fix: stabilize physics debug scene` +6. `docs: clarify backend handtracking setup` diff --git a/public/assets/bg-site.png b/public/assets/bg-site.png deleted file mode 100644 index d3a1e36..0000000 --- a/public/assets/bg-site.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e32d667a6e17ca75437f7fde9bad637bfd691543f14e48d7bca82f95f993414 -size 1469658 diff --git a/public/assets/bg-site.webp b/public/assets/bg-site.webp new file mode 100644 index 0000000..a27f461 --- /dev/null +++ b/public/assets/bg-site.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2912bc92d3b01717b1a93858bd954cb444ecd093c64b1bd1bafe839171b45fa6 +size 1191438 diff --git a/public/assets/loader/Loader-1.png b/public/assets/loader/Loader-1.png new file mode 100644 index 0000000..92bdd46 --- /dev/null +++ b/public/assets/loader/Loader-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122ad8e04cbad8cd27a6e77990576b7ece0d8a2db1a4aeffd15d658ba04e79f1 +size 52441 diff --git a/public/assets/loader/Loader-2.png b/public/assets/loader/Loader-2.png new file mode 100644 index 0000000..380494d --- /dev/null +++ b/public/assets/loader/Loader-2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7df0a60a64d393125d018ad292dfeece4162d7939dfc664680f0239601bb1ed2 +size 49899 diff --git a/public/assets/loader/Loader-3.png b/public/assets/loader/Loader-3.png new file mode 100644 index 0000000..601c255 --- /dev/null +++ b/public/assets/loader/Loader-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:382decf495b43eafe7bc8b5d65e22003f2efb9b5df8b92143c980945afe0720d +size 48379 diff --git a/public/assets/loader/Loader-4.png b/public/assets/loader/Loader-4.png new file mode 100644 index 0000000..b428ccc --- /dev/null +++ b/public/assets/loader/Loader-4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aed2d260d03e4f4d68dbfcc018cc70973d0806f8c40e6c209b33a33b0be4ac26 +size 50374 diff --git a/public/assets/logo.png b/public/assets/logo.png new file mode 100644 index 0000000..f6dd236 --- /dev/null +++ b/public/assets/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b46b6642ccb5f83d9d16f7d9e9810f390107790fd3365ebd02dfba0de9f56289 +size 1309650 diff --git a/public/assets/logo/logo.jpg b/public/assets/logo/logo.jpg deleted file mode 100644 index 3617df6..0000000 --- a/public/assets/logo/logo.jpg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:814db18091a1a822dc2ebdef9f00400c4ff943e9aa1e43151e85b6ea1c4e98cc -size 149572 diff --git a/public/assets/world/UI/intro-mission-notification.png b/public/assets/world/UI/intro-mission-notification.png new file mode 100644 index 0000000..fe207c3 --- /dev/null +++ b/public/assets/world/UI/intro-mission-notification.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0c92f57ef14cfa7ec19c9e6a8ed32eaabb3f3db9ea57f1c1bcc6a0ad7c00825 +size 8467 diff --git a/public/assets/world/gps/cadran.png b/public/assets/world/gps/cadran.png new file mode 100644 index 0000000..652420c --- /dev/null +++ b/public/assets/world/gps/cadran.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65883c1760293f3a415268a59cae60d0b35de8760de752c8dedb4ab3e19c0e96 +size 531191 diff --git a/public/assets/world/gps/fleche.png b/public/assets/world/gps/fleche.png new file mode 100644 index 0000000..e146cc7 --- /dev/null +++ b/public/assets/world/gps/fleche.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:286164bc5aeb147abb145857cb56832f04a2d17afd50a3d9000ae81059b8201e +size 121079 diff --git a/public/favicon.ico b/public/favicon.ico index 23e6e2b..e13f6d9 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/models/arbre-LOD/model.glb b/public/models/arbre-LOD/model.glb new file mode 100644 index 0000000..b5246eb --- /dev/null +++ b/public/models/arbre-LOD/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7962823c3b07a0e8f5de35351fa45a16901198859478d07bc5389b2696c7451 +size 13900 diff --git a/public/models/buisson-LOD/model.glb b/public/models/buisson-LOD/model.glb new file mode 100644 index 0000000..3a7af71 --- /dev/null +++ b/public/models/buisson-LOD/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:755898b6d3f2b02c0092bcc79f42bedf501e1c9e1658a839cf69127700cc8111 +size 3868 diff --git a/public/models/ebike/Cable 1_occlusionRoughnessMetallic.png b/public/models/ebike/Cable 1_occlusionRoughnessMetallic.png deleted file mode 100644 index 8747c04..0000000 --- a/public/models/ebike/Cable 1_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:77cf06f5e7e08653f290c353cd4c37ff91c118602436be1256280e13e52cd173 -size 353293 diff --git a/public/models/ebike/Cable2_occlusionRoughnessMetallic.png b/public/models/ebike/Cable2_occlusionRoughnessMetallic.png deleted file mode 100644 index f0c1d5f..0000000 --- a/public/models/ebike/Cable2_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:937bba888a3f821daba2aec3653bbcc3f393234d51cd604dd9341d6320c92cb2 -size 355460 diff --git a/public/models/ebike/Carroserie_baseColor.png b/public/models/ebike/Carroserie_baseColor.png.png similarity index 100% rename from public/models/ebike/Carroserie_baseColor.png rename to public/models/ebike/Carroserie_baseColor.png.png diff --git a/public/models/ebike/Carroserie_normal.png b/public/models/ebike/Carroserie_normal.png.png similarity index 100% rename from public/models/ebike/Carroserie_normal.png rename to public/models/ebike/Carroserie_normal.png.png diff --git a/public/models/ebike/Carroserie_occlusionRoughnessMetallic.png b/public/models/ebike/Carroserie_occlusionRoughnessMetallic.png deleted file mode 100644 index 05ff7d9..0000000 --- a/public/models/ebike/Carroserie_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f6abf72de5fcfce9738ac6f49bc3352160b11f1af7680ae5da84266d5fb4aaa2 -size 261088 diff --git a/public/models/ebike/Ferail_baseColor.png b/public/models/ebike/Ferail_baseColor.png deleted file mode 100644 index b2e286e..0000000 --- a/public/models/ebike/Ferail_baseColor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:72fc78eb3273eb8ae80c523319de0e3e69924a5f7ec15d88a6330083ccfffd8f -size 276020 diff --git a/public/models/ebike/Ferail_baseColor.png.png b/public/models/ebike/Ferail_baseColor.png.png new file mode 100644 index 0000000..27b326e --- /dev/null +++ b/public/models/ebike/Ferail_baseColor.png.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5efb9b4a437da7efde7808c52faba59d3b597ad8d455f063b929160a5ed7bf95 +size 18255 diff --git a/public/models/ebike/Ferail_normal.png b/public/models/ebike/Ferail_normal.png index 2f2b172..e602afa 100644 --- a/public/models/ebike/Ferail_normal.png +++ b/public/models/ebike/Ferail_normal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efb159756d206de812e300c1bc40df771b5cd2606c4d856682af5873584b7761 -size 2554815 +oid sha256:562f2ae8de216488d15f04473102bd27fda33fe83bcb53f849d03de5a773178d +size 2554401 diff --git a/public/models/ebike/Ferail_occlusionRoughnessMetallic.png b/public/models/ebike/Ferail_occlusionRoughnessMetallic.png deleted file mode 100644 index 52689a4..0000000 --- a/public/models/ebike/Ferail_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:25a3b05c805e06b38fd94bb27eff7c8ae72ce4bf14fbdb15e59366ac2e1f45f1 -size 349895 diff --git a/public/models/ebike/Reservoir_baseColor.png b/public/models/ebike/Reservoir_baseColor.png deleted file mode 100644 index 7ec7dac..0000000 --- a/public/models/ebike/Reservoir_baseColor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81cda14600b32f5ab26e00d9db40924010e1beb1939c5eed0ce6386e358d3b1d -size 53203 diff --git a/public/models/ebike/Reservoir_baseColor.png.png b/public/models/ebike/Reservoir_baseColor.png.png new file mode 100644 index 0000000..8c57503 --- /dev/null +++ b/public/models/ebike/Reservoir_baseColor.png.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d927f0691f4e735916d778d036f8a1da4c74897eb4b67586ed38d42a066e7be +size 105520 diff --git a/public/models/ebike/Reservoir_normal.png b/public/models/ebike/Reservoir_normal.png deleted file mode 100644 index 1bfb989..0000000 --- a/public/models/ebike/Reservoir_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4702cd610157f13b0bf2c4bb04dd40489e80719e90fc7cb57f1c30f6a4cc2658 -size 2272453 diff --git a/public/models/ebike/Reservoir_normal.png.png b/public/models/ebike/Reservoir_normal.png.png new file mode 100644 index 0000000..8beb150 --- /dev/null +++ b/public/models/ebike/Reservoir_normal.png.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afa5b7b7bca7f3b1f935aaa90f9732c3e27c77ef7f6b3f149e0d6dbd97e234a8 +size 2856433 diff --git a/public/models/ebike/Reservoir_occlusionRoughnessMetallic.png b/public/models/ebike/Reservoir_occlusionRoughnessMetallic.png deleted file mode 100644 index b46ab8d..0000000 --- a/public/models/ebike/Reservoir_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7aec1cadb1ffd038a54e648b875609458725c1ef652959e252afa0f03a3f7f1e -size 16902 diff --git a/public/models/ebike/Sac_baseColor.png b/public/models/ebike/Sac_baseColor.png.png similarity index 100% rename from public/models/ebike/Sac_baseColor.png rename to public/models/ebike/Sac_baseColor.png.png diff --git a/public/models/ebike/Sac_normal.png b/public/models/ebike/Sac_normal.png.png similarity index 100% rename from public/models/ebike/Sac_normal.png rename to public/models/ebike/Sac_normal.png.png diff --git a/public/models/ebike/Sac_occlusionRoughnessMetallic.png b/public/models/ebike/Sac_occlusionRoughnessMetallic.png deleted file mode 100644 index 45e9c20..0000000 --- a/public/models/ebike/Sac_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c539f92a27e495d88074dc87f3cd463d66b96b7de73188957b5f6d361b1e7b95 -size 105767 diff --git a/public/models/ebike/Siege_baseColor.png b/public/models/ebike/Siege_baseColor.png.png similarity index 100% rename from public/models/ebike/Siege_baseColor.png rename to public/models/ebike/Siege_baseColor.png.png diff --git a/public/models/ebike/Siege_normal.png b/public/models/ebike/Siege_normal.png.png similarity index 100% rename from public/models/ebike/Siege_normal.png rename to public/models/ebike/Siege_normal.png.png diff --git a/public/models/ebike/Siege_occlusionRoughnessMetallic.png b/public/models/ebike/Siege_occlusionRoughnessMetallic.png deleted file mode 100644 index c6b768c..0000000 --- a/public/models/ebike/Siege_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0a11a02281f5443ef44ec0ccb93d4ab1b4972e16e673741834acda5a3b60390c -size 308036 diff --git a/public/models/ebike/color.png b/public/models/ebike/color.png deleted file mode 100644 index 45d6540..0000000 --- a/public/models/ebike/color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0f427aa9def171588521008b42339f804d9d7e1c9865b92ac35834ac2205d441 -size 409273 diff --git a/public/models/ebike/ebike.bin b/public/models/ebike/ebike.bin deleted file mode 100644 index 30c7a57..0000000 --- a/public/models/ebike/ebike.bin +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed6d61d76b9acb99c87b45b497dcd0214e2f0c0eb2ab7390e3a14d0debbe26b4 -size 3865056 diff --git a/public/models/ebike/model.bin b/public/models/ebike/model.bin index 4829829..20b2708 100644 --- a/public/models/ebike/model.bin +++ b/public/models/ebike/model.bin @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f476c9e9d1fa2437f83edf54d7702889b556520257fc0b5c3bec99aefdd5541 -size 3086528 +oid sha256:72f010b6a7fba861545141e46e154637d4bedf8625cd451d5a295526db1ceae2 +size 2134916 diff --git a/public/models/ebike/model.gltf b/public/models/ebike/model.gltf index 178b740..07eb62d 100644 --- a/public/models/ebike/model.gltf +++ b/public/models/ebike/model.gltf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73e93ccfb92f831905c5573f5b55f92592bf3dfffde876a678ac414416b20aa0 -size 2592 +oid sha256:3825a12bcf528d1801c904b098f536ee123af24de64fc92e55969f687da6e5de +size 103305 diff --git a/public/models/ebike/normal.png b/public/models/ebike/normal.png deleted file mode 100644 index a091c9e..0000000 --- a/public/models/ebike/normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1dda1b74dd9e72a248f8ff1c9e9442801a7399f913a2e239b2edce44422916e -size 695202 diff --git a/public/models/ebike/panneau_baseColor.png b/public/models/ebike/panneau_baseColor.png new file mode 100644 index 0000000..866ba7f --- /dev/null +++ b/public/models/ebike/panneau_baseColor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:499e858e82bb910f251386f57bb751743b0a557a70383a68e5b534a8e8e30854 +size 1138223 diff --git a/public/models/ebike/panneau_normal.png b/public/models/ebike/panneau_normal.png new file mode 100644 index 0000000..f513b6d --- /dev/null +++ b/public/models/ebike/panneau_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e7db9b77efbfb0c62ec4924b2522babb191ec4a5e51687dc4de8971f044a8f5 +size 2487930 diff --git a/public/models/ebike/phare_baseColor.png b/public/models/ebike/phare_baseColor.png deleted file mode 100644 index 05209c8..0000000 --- a/public/models/ebike/phare_baseColor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91c23d60933bb685bbb96bdae47672f5c7ec1a62552a56d0eeee1b22448cb66d -size 16905 diff --git a/public/models/ebike/phare_baseColor.png.png b/public/models/ebike/phare_baseColor.png.png new file mode 100644 index 0000000..3ce9bad --- /dev/null +++ b/public/models/ebike/phare_baseColor.png.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09100598a454aea0cf44fcdbb4badf34ba047ca7b727df06fe54306975395837 +size 250457 diff --git a/public/models/ebike/phare_normal.png b/public/models/ebike/phare_normal.png deleted file mode 100644 index 7919b63..0000000 --- a/public/models/ebike/phare_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a4a1e9491ca5d182d658a29095d24616f7927b8084a68733f5e2eca638b0e7f -size 2905767 diff --git a/public/models/ebike/phare_normal.png.png b/public/models/ebike/phare_normal.png.png new file mode 100644 index 0000000..d8a9bef --- /dev/null +++ b/public/models/ebike/phare_normal.png.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43e4e66f267dc71ccc7ed1d2c9f812c4aea0950e2044c537d87ecc8ecf0bf46c +size 2902634 diff --git a/public/models/ebike/phare_occlusionRoughnessMetallic.png b/public/models/ebike/phare_occlusionRoughnessMetallic.png deleted file mode 100644 index b0edb22..0000000 --- a/public/models/ebike/phare_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:808064a2e6581764a06ead7add5c07bf3d3bd33f3fefb9d76dfe96b8fd3ec3a3 -size 76759 diff --git a/public/models/ebike/pneu_baseColor.png b/public/models/ebike/pneu_baseColor.png.png similarity index 100% rename from public/models/ebike/pneu_baseColor.png rename to public/models/ebike/pneu_baseColor.png.png diff --git a/public/models/ebike/pneu_normal.png b/public/models/ebike/pneu_normal.png.png similarity index 100% rename from public/models/ebike/pneu_normal.png rename to public/models/ebike/pneu_normal.png.png diff --git a/public/models/ebike/pneu_occlusionRoughnessMetallic.png b/public/models/ebike/pneu_occlusionRoughnessMetallic.png deleted file mode 100644 index 1c9776d..0000000 --- a/public/models/ebike/pneu_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e148f1d006dbbc26e4e2d846b359c6099a49ceec3fca57373293aad70709257e -size 175094 diff --git a/public/models/ebike/refroidisseur_baseColor.png b/public/models/ebike/refroidisseur_baseColor.png.png similarity index 100% rename from public/models/ebike/refroidisseur_baseColor.png rename to public/models/ebike/refroidisseur_baseColor.png.png diff --git a/public/models/ebike/refroidisseur_normal.png b/public/models/ebike/refroidisseur_normal.png.png similarity index 100% rename from public/models/ebike/refroidisseur_normal.png rename to public/models/ebike/refroidisseur_normal.png.png diff --git a/public/models/ebike/refroidisseur_occlusionRoughnessMetallic.png b/public/models/ebike/refroidisseur_occlusionRoughnessMetallic.png deleted file mode 100644 index 7472735..0000000 --- a/public/models/ebike/refroidisseur_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:db6b826f7944bebcd541739518eb6f9c4b4fbc2039666f7e09f67c8249e4f42f -size 392559 diff --git a/public/models/ebike/resort_baseColor.png b/public/models/ebike/resort_baseColor.png.png similarity index 100% rename from public/models/ebike/resort_baseColor.png rename to public/models/ebike/resort_baseColor.png.png diff --git a/public/models/ebike/resort_normal.png b/public/models/ebike/resort_normal.png.png similarity index 100% rename from public/models/ebike/resort_normal.png rename to public/models/ebike/resort_normal.png.png diff --git a/public/models/ebike/resort_occlusionRoughnessMetallic.png b/public/models/ebike/resort_occlusionRoughnessMetallic.png deleted file mode 100644 index 106d8d6..0000000 --- a/public/models/ebike/resort_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1a1420af59e0ebc0d2707ca3e5148f4bb621fa73daabff45b6ed723f766836e9 -size 100634 diff --git a/public/models/electricienne-animated/Mat_baseColor.png b/public/models/electricienne-animated/Mat_baseColor.png deleted file mode 100644 index ba0de35..0000000 --- a/public/models/electricienne-animated/Mat_baseColor.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:142be230a66ff6bebe321b373e4785283624c3bb5f3565114a6acca6e2d056f2 -size 691735 diff --git a/public/models/electricienne-animated/Mat_diffuse.png b/public/models/electricienne-animated/Mat_diffuse.png new file mode 100644 index 0000000..ad54907 --- /dev/null +++ b/public/models/electricienne-animated/Mat_diffuse.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e3b747fcd521e7a24d586345925525f10a33412ffb041a7adbf1fbc49fb2d08 +size 727199 diff --git a/public/models/electricienne-animated/Mat_normal.png b/public/models/electricienne-animated/Mat_normal.png index c72e6e2..66ca144 100644 --- a/public/models/electricienne-animated/Mat_normal.png +++ b/public/models/electricienne-animated/Mat_normal.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f4580790707fc1fc505b3b725a523eac3e985353bc2e566a73ae2d983e87029 -size 1229760 +oid sha256:de14342c19b038a504840385d616c239851b90a289dac974fb6f93e7f3c03b99 +size 3374459 diff --git a/public/models/electricienne-animated/Mat_occlusionRoughnessMetallic.png b/public/models/electricienne-animated/Mat_occlusionRoughnessMetallic.png deleted file mode 100644 index dc6530c..0000000 --- a/public/models/electricienne-animated/Mat_occlusionRoughnessMetallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2abdb28a5b27842d8958480f97357a3603b2c0ab46db9ff6bf08e474600c5d49 -size 650826 diff --git a/public/models/electricienne-animated/electricienne.bin b/public/models/electricienne-animated/electricienne.bin new file mode 100644 index 0000000..a8a646b --- /dev/null +++ b/public/models/electricienne-animated/electricienne.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:df5e642df78807e9b7f41e240ba7a6aa6c27d0a7c12b5db42293e57a03bd1c2a +size 2893220 diff --git a/public/models/electricienne-animated/model.bin b/public/models/electricienne-animated/model.bin deleted file mode 100644 index 3a75871..0000000 --- a/public/models/electricienne-animated/model.bin +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:247407ee9bdb8fa5730a56df06872a224888cde1a4a0592c62d0157608b83f02 -size 2954520 diff --git a/public/models/electricienne-animated/model.gltf b/public/models/electricienne-animated/model.gltf index a6b86f9..3586956 100644 --- a/public/models/electricienne-animated/model.gltf +++ b/public/models/electricienne-animated/model.gltf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:93c52e55e65710316e38a2c43703afba3e545d7f9a36cc99e766046a2d138691 -size 47280 +oid sha256:20ac103bf35040b9d8b2cce0f8c9e0812a8bce629d7430fb28f689244b52d07d +size 86385 diff --git a/public/models/lafabrik/model.glb b/public/models/lafabrik/model.glb index f540ba6..cdcbbca 100644 --- a/public/models/lafabrik/model.glb +++ b/public/models/lafabrik/model.glb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:828356d45f504ce2bfcb3007267e6c7c31aea58c5f0b417f38c0e67475fad0e6 -size 88587392 +oid sha256:c9e4e66d77ee691dc442e91adf0116dfc63a60031aeffab9c601ae34bfc06e1b +size 88587196 diff --git a/public/models/persoprincipal/model.gltf b/public/models/persoprincipal/model.gltf index faa8ff2..c9ec264 100644 --- a/public/models/persoprincipal/model.gltf +++ b/public/models/persoprincipal/model.gltf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:955c5c954519afef152989a1fb4d187e115fd5893a6a8f7119e4818bf87dc3ac +oid sha256:7e3ed9f4faf333db1ea120580ff4ac6f9c072ac072093ff64d0ade2a1cdd7c2f size 3136 diff --git a/public/models/pylone-LOD/model.gltf b/public/models/pylone-LOD/model.gltf index e72d715..5bbcebd 100644 --- a/public/models/pylone-LOD/model.gltf +++ b/public/models/pylone-LOD/model.gltf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06f375b482357753b1cfb212fa4f8398e9da1aa234f8259a7b1e0df9d7572afd -size 8540 +oid sha256:6e465b84186e43c2b2bbcb89b3e40d7a9c0a0fe170434f2e9fad0cf320b67c9f +size 8561 diff --git a/public/models/pylone/model.gltf b/public/models/pylone/model.gltf index e72d715..5bbcebd 100644 --- a/public/models/pylone/model.gltf +++ b/public/models/pylone/model.gltf @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06f375b482357753b1cfb212fa4f8398e9da1aa234f8259a7b1e0df9d7572afd -size 8540 +oid sha256:6e465b84186e43c2b2bbcb89b3e40d7a9c0a0fe170434f2e9fad0cf320b67c9f +size 8561 diff --git a/public/models/sapin-LOD/model.glb b/public/models/sapin-LOD/model.glb new file mode 100644 index 0000000..96ee010 --- /dev/null +++ b/public/models/sapin-LOD/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:883871a80725f887de958d44a5e3c33deadbfdc546e307563cdf1fcac2b6a37f +size 9280 diff --git a/public/models/talkie-LOD/antenne_Base_color.png b/public/models/talkie-LOD/antenne_Base_color.png deleted file mode 100644 index db60934..0000000 --- a/public/models/talkie-LOD/antenne_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc5ba4130daed3b1edae078cc73ad5a4d9955c8c464abcbc6af2a80077842f7a -size 312866 diff --git a/public/models/talkie-LOD/antenne_Height.png b/public/models/talkie-LOD/antenne_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/antenne_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/antenne_Metallic.png b/public/models/talkie-LOD/antenne_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/antenne_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/antenne_Mixed_AO.png b/public/models/talkie-LOD/antenne_Mixed_AO.png deleted file mode 100644 index 3ba6e13..0000000 --- a/public/models/talkie-LOD/antenne_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3ec5ee97080be475cf3f3da71ca440dac8ccbe104510f519d1b4ee928d5b2b9 -size 187817 diff --git a/public/models/talkie-LOD/antenne_Roughness.png b/public/models/talkie-LOD/antenne_Roughness.png deleted file mode 100644 index e6233e4..0000000 --- a/public/models/talkie-LOD/antenne_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91c3fb77f1204027e6d0d46e59dc6b68cd13fc68099b20612b75749460a3c8b8 -size 69233 diff --git a/public/models/talkie-LOD/antenne_normal.png b/public/models/talkie-LOD/antenne_normal.png deleted file mode 100644 index bf16203..0000000 --- a/public/models/talkie-LOD/antenne_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8e995d1260a97d5f1c099aa51ccf35123316264751c940bb5e71e3d115c133 -size 205065 diff --git a/public/models/talkie-LOD/antenne_normal_opengl.png b/public/models/talkie-LOD/antenne_normal_opengl.png deleted file mode 100644 index 7c1dc2b..0000000 --- a/public/models/talkie-LOD/antenne_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1ec8c53b5e60fb3f13da3db4b787a6dd667f51c05eb92e925902b33c5fb68f5 -size 204007 diff --git a/public/models/talkie-LOD/boutona_Base_color.png b/public/models/talkie-LOD/boutona_Base_color.png deleted file mode 100644 index f493f55..0000000 --- a/public/models/talkie-LOD/boutona_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef47a91bfd020de0983e75bc8bc03277b651c894da0f006dc49d5bfb91e86623 -size 473199 diff --git a/public/models/talkie-LOD/boutona_Height.png b/public/models/talkie-LOD/boutona_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/boutona_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/boutona_Metallic.png b/public/models/talkie-LOD/boutona_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/boutona_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/boutona_Mixed_AO.png b/public/models/talkie-LOD/boutona_Mixed_AO.png deleted file mode 100644 index d28322d..0000000 --- a/public/models/talkie-LOD/boutona_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f45671cafcc91e40f67120f22f7b58c75a9bf5289f8dd67019d7c836253e8942 -size 244343 diff --git a/public/models/talkie-LOD/boutona_Roughness.png b/public/models/talkie-LOD/boutona_Roughness.png deleted file mode 100644 index daa6677..0000000 --- a/public/models/talkie-LOD/boutona_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adaedf0e287c99669b47a0e3e3b99ed2c6f1f5a761e69168418e56610d7d8f9a -size 54561 diff --git a/public/models/talkie-LOD/boutona_normal.png b/public/models/talkie-LOD/boutona_normal.png deleted file mode 100644 index 4cf6138..0000000 --- a/public/models/talkie-LOD/boutona_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c0e761804d0546dc6cd9d929fdecab8f6852866a6f2025c03c76c34b2040ee0 -size 175939 diff --git a/public/models/talkie-LOD/boutona_normal_opengl.png b/public/models/talkie-LOD/boutona_normal_opengl.png deleted file mode 100644 index 80416e2..0000000 --- a/public/models/talkie-LOD/boutona_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86c390222c74160382b92a5736664c37e3861937db2e2d58699643516fc52995 -size 176366 diff --git a/public/models/talkie-LOD/boutonb_Base_color.png b/public/models/talkie-LOD/boutonb_Base_color.png deleted file mode 100644 index ed59d49..0000000 --- a/public/models/talkie-LOD/boutonb_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84973c1c06bf83beacd0987c5d252560ec92e09701b3ac6b73289f39392c21da -size 496575 diff --git a/public/models/talkie-LOD/boutonb_Height.png b/public/models/talkie-LOD/boutonb_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/boutonb_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/boutonb_Metallic.png b/public/models/talkie-LOD/boutonb_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/boutonb_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/boutonb_Mixed_AO.png b/public/models/talkie-LOD/boutonb_Mixed_AO.png deleted file mode 100644 index 8292910..0000000 --- a/public/models/talkie-LOD/boutonb_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d58e8ec5e90125edd38f279ee1b9fee863b75d8396fef6a0c22a9af6a9c0a341 -size 239658 diff --git a/public/models/talkie-LOD/boutonb_Roughness.png b/public/models/talkie-LOD/boutonb_Roughness.png deleted file mode 100644 index acba391..0000000 --- a/public/models/talkie-LOD/boutonb_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad833228a99505d0fea7495970bfbf47d3942a86d457aca379b99d8224d8633f -size 54522 diff --git a/public/models/talkie-LOD/boutonb_normal.png b/public/models/talkie-LOD/boutonb_normal.png deleted file mode 100644 index 943917e..0000000 --- a/public/models/talkie-LOD/boutonb_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53e5d76fcd40986674d93f289cea51c6b839d720787d98ed4112164e20558bd2 -size 176052 diff --git a/public/models/talkie-LOD/boutonb_normal_opengl.png b/public/models/talkie-LOD/boutonb_normal_opengl.png deleted file mode 100644 index 47539e6..0000000 --- a/public/models/talkie-LOD/boutonb_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a1ea79411217de422f6f7fd160c673e01a2a09ebce1d95a6b5e63c8c1e5de18 -size 176723 diff --git a/public/models/talkie-LOD/cable1_Base_color.png b/public/models/talkie-LOD/cable1_Base_color.png deleted file mode 100644 index 0aa378c..0000000 --- a/public/models/talkie-LOD/cable1_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:967d8771fbcd5427bf0005b147ecae38ab53271c4a99980f34db1102d0cb35f0 -size 178767 diff --git a/public/models/talkie-LOD/cable1_Height.png b/public/models/talkie-LOD/cable1_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/cable1_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/cable1_Metallic.png b/public/models/talkie-LOD/cable1_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/cable1_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/cable1_Mixed_AO.png b/public/models/talkie-LOD/cable1_Mixed_AO.png deleted file mode 100644 index 2003ade..0000000 --- a/public/models/talkie-LOD/cable1_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73378e05b925c39d37e3cfdb1048d891b6216927fbd9eab111a61051d9c5fadd -size 142820 diff --git a/public/models/talkie-LOD/cable1_Roughness.png b/public/models/talkie-LOD/cable1_Roughness.png deleted file mode 100644 index c1c9096..0000000 --- a/public/models/talkie-LOD/cable1_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a2a10008bf8a90ca1e7ef831944d8e6b2c41d5aa9b5a1f20f49d7dcc942b9f2 -size 56819 diff --git a/public/models/talkie-LOD/cable1_normal.png b/public/models/talkie-LOD/cable1_normal.png deleted file mode 100644 index 8f1338d..0000000 --- a/public/models/talkie-LOD/cable1_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb4d0e26c8d16fcb471e2812585fb27ddfbce4500c526fce512d61f7611c5b99 -size 186292 diff --git a/public/models/talkie-LOD/cable1_normal_opengl.png b/public/models/talkie-LOD/cable1_normal_opengl.png deleted file mode 100644 index 456bcdb..0000000 --- a/public/models/talkie-LOD/cable1_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8854c9b53ac97ce6113d351aa528d9a6664e90947678aee05731695e034d1c79 -size 187073 diff --git a/public/models/talkie-LOD/cable2_Base_color.png b/public/models/talkie-LOD/cable2_Base_color.png deleted file mode 100644 index ba3be72..0000000 --- a/public/models/talkie-LOD/cable2_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b1bdd610bf135a20471bc2b65e53f2a2333ab6e845380f55fed93d45dce1897 -size 210840 diff --git a/public/models/talkie-LOD/cable2_Height.png b/public/models/talkie-LOD/cable2_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/cable2_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/cable2_Metallic.png b/public/models/talkie-LOD/cable2_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/cable2_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/cable2_Mixed_AO.png b/public/models/talkie-LOD/cable2_Mixed_AO.png deleted file mode 100644 index e3a64cc..0000000 --- a/public/models/talkie-LOD/cable2_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30e8e315f7950a91135bba6ac43d04563b1e501e83cfb4b57048320768602bc5 -size 167450 diff --git a/public/models/talkie-LOD/cable2_Roughness.png b/public/models/talkie-LOD/cable2_Roughness.png deleted file mode 100644 index a9440d4..0000000 --- a/public/models/talkie-LOD/cable2_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d9e3cc7a29c90c1712196c05915bea480124dddc0db47db9415f738f895660c -size 60898 diff --git a/public/models/talkie-LOD/cable2_normal.png b/public/models/talkie-LOD/cable2_normal.png deleted file mode 100644 index acc99fd..0000000 --- a/public/models/talkie-LOD/cable2_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b09eae40cd4d52c348933f49347691f08dc6f25d929ddadbeddd2157444c706f -size 195384 diff --git a/public/models/talkie-LOD/cable2_normal_opengl.png b/public/models/talkie-LOD/cable2_normal_opengl.png deleted file mode 100644 index d184f94..0000000 --- a/public/models/talkie-LOD/cable2_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff1ae64895328cae7bf8816a43eb38ca4db82b7979d02db927bc307fa7ec66c6 -size 196010 diff --git a/public/models/talkie-LOD/cadre_Base_color.png b/public/models/talkie-LOD/cadre_Base_color.png deleted file mode 100644 index afbe925..0000000 --- a/public/models/talkie-LOD/cadre_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:637e73b56419d8390e8aeb27433fdee21264c9d5b28e1caaf7e1357f6c191fd3 -size 280049 diff --git a/public/models/talkie-LOD/cadre_Height.png b/public/models/talkie-LOD/cadre_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/cadre_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/cadre_Metallic.png b/public/models/talkie-LOD/cadre_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/cadre_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/cadre_Mixed_AO.png b/public/models/talkie-LOD/cadre_Mixed_AO.png deleted file mode 100644 index 0151384..0000000 --- a/public/models/talkie-LOD/cadre_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1f9912860e5f508e0e29ef1e03943101cdb287a1c34bd0278ac237824255da5 -size 157683 diff --git a/public/models/talkie-LOD/cadre_Roughness.png b/public/models/talkie-LOD/cadre_Roughness.png deleted file mode 100644 index 264a6e4..0000000 --- a/public/models/talkie-LOD/cadre_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:afb28f17a6f419356f49088c8c5b8d12b6aaf2471ee7e35ef42e0fda3a5365f2 -size 53784 diff --git a/public/models/talkie-LOD/cadre_normal.png b/public/models/talkie-LOD/cadre_normal.png deleted file mode 100644 index 8d2a5a3..0000000 --- a/public/models/talkie-LOD/cadre_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:982a042d28061ce926c80293129985733aba79bc7115e0f5b55e4f4186808f5a -size 52817 diff --git a/public/models/talkie-LOD/cadre_normal_opengl.png b/public/models/talkie-LOD/cadre_normal_opengl.png deleted file mode 100644 index 46032a3..0000000 --- a/public/models/talkie-LOD/cadre_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58d63de38e7fefca7ec94b4057b37256c477dbd2749f950f0bd373932b52cbf9 -size 52737 diff --git a/public/models/talkie-LOD/e_cran_base_color.png b/public/models/talkie-LOD/e_cran_base_color.png deleted file mode 100644 index a8c2149..0000000 --- a/public/models/talkie-LOD/e_cran_base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f75363e70ffed07a182a3e61d7c301cb8fd053ac8ec784d56e9450335167dce8 -size 6502 diff --git a/public/models/talkie-LOD/e_cran_height.png b/public/models/talkie-LOD/e_cran_height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/e_cran_height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/e_cran_metallic.png b/public/models/talkie-LOD/e_cran_metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/e_cran_metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/e_cran_mixed_ao.png b/public/models/talkie-LOD/e_cran_mixed_ao.png deleted file mode 100644 index e91eb6e..0000000 --- a/public/models/talkie-LOD/e_cran_mixed_ao.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4f3fd92f328f0733f9cb9865209466c251f4edf4750654eb0385b519300cf34 -size 329712 diff --git a/public/models/talkie-LOD/e_cran_normal.png b/public/models/talkie-LOD/e_cran_normal.png deleted file mode 100644 index 175152b..0000000 --- a/public/models/talkie-LOD/e_cran_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a07dd2bf8fe1d37b9a74d2ca09b8a77d8a97029d41a8483e34ba30bd8e9efc04 -size 74315 diff --git a/public/models/talkie-LOD/e_cran_normal_opengl.png b/public/models/talkie-LOD/e_cran_normal_opengl.png deleted file mode 100644 index e17d9b3..0000000 --- a/public/models/talkie-LOD/e_cran_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca90a32ea8b1dadb11a5cf2e41d4d096920fd7a2ab493dec96682a479d4e7a8c -size 74316 diff --git a/public/models/talkie-LOD/e_cran_roughness.png b/public/models/talkie-LOD/e_cran_roughness.png deleted file mode 100644 index ad642fa..0000000 --- a/public/models/talkie-LOD/e_cran_roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:700f97e34547f14a62a45858218e56eb3715a278380c2f1f14be9ca6feaae959 -size 72768 diff --git a/public/models/talkie-LOD/hautparleur_Base_color.png b/public/models/talkie-LOD/hautparleur_Base_color.png deleted file mode 100644 index 9e43314..0000000 --- a/public/models/talkie-LOD/hautparleur_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36168017b9c2d6961f869f461812a53ed4bdb49f24f350452f4604009acb6fe8 -size 658014 diff --git a/public/models/talkie-LOD/hautparleur_Height.png b/public/models/talkie-LOD/hautparleur_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/hautparleur_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/hautparleur_Metallic.png b/public/models/talkie-LOD/hautparleur_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/hautparleur_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/hautparleur_Mixed_AO.png b/public/models/talkie-LOD/hautparleur_Mixed_AO.png deleted file mode 100644 index f2bfd28..0000000 --- a/public/models/talkie-LOD/hautparleur_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29ac8737a9eccff201ad351b4b403e93ed65c7a082052c1070455bc7c795bcbb -size 201135 diff --git a/public/models/talkie-LOD/hautparleur_Roughness.png b/public/models/talkie-LOD/hautparleur_Roughness.png deleted file mode 100644 index 17ac038..0000000 --- a/public/models/talkie-LOD/hautparleur_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f7c1e238150662981d8a4d003045021ee0cca21af14dc79a9180671c6a82621 -size 74349 diff --git a/public/models/talkie-LOD/hautparleur_normal.png b/public/models/talkie-LOD/hautparleur_normal.png deleted file mode 100644 index 5d89a1c..0000000 --- a/public/models/talkie-LOD/hautparleur_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65edaf688e31787da8deb864079aed068cc71775dbbde4c350481896c2d8a204 -size 98635 diff --git a/public/models/talkie-LOD/hautparleur_normal_opengl.png b/public/models/talkie-LOD/hautparleur_normal_opengl.png deleted file mode 100644 index 814a2b3..0000000 --- a/public/models/talkie-LOD/hautparleur_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c689c9d28d984f54e5647de38423f34da442b8e8cac076052bfa5df07457fb1c -size 98768 diff --git a/public/models/talkie-LOD/model.bin b/public/models/talkie-LOD/model.bin deleted file mode 100644 index 7e13375..0000000 --- a/public/models/talkie-LOD/model.bin +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:970717ff52a7275d1e6090a8699021ae57b74127b05c016eafb6dca45158420f -size 198456 diff --git a/public/models/talkie-LOD/model.gltf b/public/models/talkie-LOD/model.gltf deleted file mode 100644 index 5caa83b..0000000 --- a/public/models/talkie-LOD/model.gltf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:694acec3219e3cb0dbce0e9739bc3c6655dbf5360f104d50a52618e06d90c946 -size 63007 diff --git a/public/models/talkie-LOD/prise_Base_color.png b/public/models/talkie-LOD/prise_Base_color.png deleted file mode 100644 index 30d846f..0000000 --- a/public/models/talkie-LOD/prise_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e012c8775c6eb274a80856394fac6673f7beb754c2f78e1c51c842ac017fdd5 -size 472487 diff --git a/public/models/talkie-LOD/prise_Height.png b/public/models/talkie-LOD/prise_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/prise_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/prise_Metallic.png b/public/models/talkie-LOD/prise_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/prise_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/prise_Mixed_AO.png b/public/models/talkie-LOD/prise_Mixed_AO.png deleted file mode 100644 index 1ed1f71..0000000 --- a/public/models/talkie-LOD/prise_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a7ae156409544d00bf3332fb542aaec0711d4ce4c9d39d4bd573fb4f0212051 -size 230780 diff --git a/public/models/talkie-LOD/prise_Roughness.png b/public/models/talkie-LOD/prise_Roughness.png deleted file mode 100644 index c827dba..0000000 --- a/public/models/talkie-LOD/prise_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:161c97c6ad56619ad702cc955a26ea92203575f31bb35ebc8ca398b48aae9889 -size 100073 diff --git a/public/models/talkie-LOD/prise_normal.png b/public/models/talkie-LOD/prise_normal.png deleted file mode 100644 index 5bcffa1..0000000 --- a/public/models/talkie-LOD/prise_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c00bec06b1bbd954eb91872fa8cd817f247b275c16e6c372db52189ba6f3778 -size 212856 diff --git a/public/models/talkie-LOD/prise_normal_opengl.png b/public/models/talkie-LOD/prise_normal_opengl.png deleted file mode 100644 index 6bc3741..0000000 --- a/public/models/talkie-LOD/prise_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa8e8d378a595f8aafc4cd34ecac65426a03847a402f955b7b0280f0383d40eb -size 224961 diff --git a/public/models/talkie-LOD/talkie_Base_color.png b/public/models/talkie-LOD/talkie_Base_color.png deleted file mode 100644 index 295b322..0000000 --- a/public/models/talkie-LOD/talkie_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aead25dde6c940f353fa4a53f43e37dde80cd924ccced3c4f88a45dcc5fb4751 -size 610827 diff --git a/public/models/talkie-LOD/talkie_Height.png b/public/models/talkie-LOD/talkie_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/talkie_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/talkie_Metallic.png b/public/models/talkie-LOD/talkie_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/talkie_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/talkie_Mixed_AO.png b/public/models/talkie-LOD/talkie_Mixed_AO.png deleted file mode 100644 index a015f96..0000000 --- a/public/models/talkie-LOD/talkie_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78a4e5bc8affb8c7f529712ddcc4958982729d4bbc64163d93b4e3df8192f3e9 -size 229809 diff --git a/public/models/talkie-LOD/talkie_Roughness.png b/public/models/talkie-LOD/talkie_Roughness.png deleted file mode 100644 index 091f911..0000000 --- a/public/models/talkie-LOD/talkie_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:638da789f9d21dfa546cf053c5f0c4e47fc18eef3d7ac412d39bdbdf3b3974b1 -size 134252 diff --git a/public/models/talkie-LOD/talkie_normal.png b/public/models/talkie-LOD/talkie_normal.png deleted file mode 100644 index d3e9649..0000000 --- a/public/models/talkie-LOD/talkie_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fba8c5bce232fd16f3d3da18228438443bfcfab6f1673884e2c6439a799a342 -size 285066 diff --git a/public/models/talkie-LOD/talkie_normal_opengl.png b/public/models/talkie-LOD/talkie_normal_opengl.png deleted file mode 100644 index e58b36f..0000000 --- a/public/models/talkie-LOD/talkie_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:708382f0b0340e845f0cd4127b84ec5f837f5ca3a51f0c4064ba790374643c21 -size 287356 diff --git a/public/models/talkie-LOD/touches_Base_color.png b/public/models/talkie-LOD/touches_Base_color.png deleted file mode 100644 index eb7631c..0000000 --- a/public/models/talkie-LOD/touches_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47ef413967a461f9768b74d15a2ab1c8e3e3fe6fc9be027d8cf1e7268bb8a888 -size 126096 diff --git a/public/models/talkie-LOD/touches_Height.png b/public/models/talkie-LOD/touches_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/touches_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/touches_Metallic.png b/public/models/talkie-LOD/touches_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/touches_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/touches_Mixed_AO.png b/public/models/talkie-LOD/touches_Mixed_AO.png deleted file mode 100644 index 5673800..0000000 --- a/public/models/talkie-LOD/touches_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6884417f47afb4330b00f9f42fa1faf7b8bbd06d2ae9595cffe409a4d38cd461 -size 246760 diff --git a/public/models/talkie-LOD/touches_Roughness.png b/public/models/talkie-LOD/touches_Roughness.png deleted file mode 100644 index bd73a19..0000000 --- a/public/models/talkie-LOD/touches_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5804a17a4fd8691a606cbb3e671234f2c876dfc0e88e16d2f781bd35785167cb -size 56002 diff --git a/public/models/talkie-LOD/touches_normal.png b/public/models/talkie-LOD/touches_normal.png deleted file mode 100644 index 07f83c4..0000000 --- a/public/models/talkie-LOD/touches_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09d922e17cd400c32ef51462723e9c5ee9dfbe4c668e88e6057f9e7d60c8830e -size 59216 diff --git a/public/models/talkie-LOD/touches_normal_opengl.png b/public/models/talkie-LOD/touches_normal_opengl.png deleted file mode 100644 index ec98ce7..0000000 --- a/public/models/talkie-LOD/touches_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8cdadc48d7d365fa0f8e4f3b64a61a6fa95cf9bcafaf50dc83ea7cf90173f68b -size 59178 diff --git a/public/models/talkie-LOD/écran_Base_color.png b/public/models/talkie-LOD/écran_Base_color.png deleted file mode 100644 index b28a367..0000000 --- a/public/models/talkie-LOD/écran_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfbf65890f6d5019bf113246dcecb83c97e31d6e10b0429d5e89df9df67a58bd -size 6498 diff --git a/public/models/talkie-LOD/écran_Height.png b/public/models/talkie-LOD/écran_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie-LOD/écran_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie-LOD/écran_Metallic.png b/public/models/talkie-LOD/écran_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie-LOD/écran_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie-LOD/écran_Mixed_AO.png b/public/models/talkie-LOD/écran_Mixed_AO.png deleted file mode 100644 index e91eb6e..0000000 --- a/public/models/talkie-LOD/écran_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4f3fd92f328f0733f9cb9865209466c251f4edf4750654eb0385b519300cf34 -size 329712 diff --git a/public/models/talkie-LOD/écran_Normal.png b/public/models/talkie-LOD/écran_Normal.png deleted file mode 100644 index 175152b..0000000 --- a/public/models/talkie-LOD/écran_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a07dd2bf8fe1d37b9a74d2ca09b8a77d8a97029d41a8483e34ba30bd8e9efc04 -size 74315 diff --git a/public/models/talkie-LOD/écran_Normal_OpenGL.png b/public/models/talkie-LOD/écran_Normal_OpenGL.png deleted file mode 100644 index e17d9b3..0000000 --- a/public/models/talkie-LOD/écran_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca90a32ea8b1dadb11a5cf2e41d4d096920fd7a2ab493dec96682a479d4e7a8c -size 74316 diff --git a/public/models/talkie-LOD/écran_Roughness.png b/public/models/talkie-LOD/écran_Roughness.png deleted file mode 100644 index d89bffa..0000000 --- a/public/models/talkie-LOD/écran_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d560c80d755fdfff917c63d96d664cc4c9be963cc38ac63533418f7df1b0642c -size 72772 diff --git a/public/models/talkie/antenne_Base_color.png b/public/models/talkie/antenne_Base_color.png deleted file mode 100644 index db60934..0000000 --- a/public/models/talkie/antenne_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fc5ba4130daed3b1edae078cc73ad5a4d9955c8c464abcbc6af2a80077842f7a -size 312866 diff --git a/public/models/talkie/antenne_Height.png b/public/models/talkie/antenne_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/antenne_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/antenne_Metallic.png b/public/models/talkie/antenne_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/antenne_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/antenne_Mixed_AO.png b/public/models/talkie/antenne_Mixed_AO.png deleted file mode 100644 index 3ba6e13..0000000 --- a/public/models/talkie/antenne_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3ec5ee97080be475cf3f3da71ca440dac8ccbe104510f519d1b4ee928d5b2b9 -size 187817 diff --git a/public/models/talkie/antenne_Normal.png b/public/models/talkie/antenne_Normal.png deleted file mode 100644 index bf16203..0000000 --- a/public/models/talkie/antenne_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:da8e995d1260a97d5f1c099aa51ccf35123316264751c940bb5e71e3d115c133 -size 205065 diff --git a/public/models/talkie/antenne_Normal_OpenGL.png b/public/models/talkie/antenne_Normal_OpenGL.png deleted file mode 100644 index 7c1dc2b..0000000 --- a/public/models/talkie/antenne_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c1ec8c53b5e60fb3f13da3db4b787a6dd667f51c05eb92e925902b33c5fb68f5 -size 204007 diff --git a/public/models/talkie/antenne_Roughness.png b/public/models/talkie/antenne_Roughness.png deleted file mode 100644 index e6233e4..0000000 --- a/public/models/talkie/antenne_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:91c3fb77f1204027e6d0d46e59dc6b68cd13fc68099b20612b75749460a3c8b8 -size 69233 diff --git a/public/models/talkie/boutona_Base_color.png b/public/models/talkie/boutona_Base_color.png deleted file mode 100644 index f493f55..0000000 --- a/public/models/talkie/boutona_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef47a91bfd020de0983e75bc8bc03277b651c894da0f006dc49d5bfb91e86623 -size 473199 diff --git a/public/models/talkie/boutona_Height.png b/public/models/talkie/boutona_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/boutona_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/boutona_Metallic.png b/public/models/talkie/boutona_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/boutona_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/boutona_Mixed_AO.png b/public/models/talkie/boutona_Mixed_AO.png deleted file mode 100644 index d28322d..0000000 --- a/public/models/talkie/boutona_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f45671cafcc91e40f67120f22f7b58c75a9bf5289f8dd67019d7c836253e8942 -size 244343 diff --git a/public/models/talkie/boutona_Normal.png b/public/models/talkie/boutona_Normal.png deleted file mode 100644 index 4cf6138..0000000 --- a/public/models/talkie/boutona_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9c0e761804d0546dc6cd9d929fdecab8f6852866a6f2025c03c76c34b2040ee0 -size 175939 diff --git a/public/models/talkie/boutona_Normal_OpenGL.png b/public/models/talkie/boutona_Normal_OpenGL.png deleted file mode 100644 index 80416e2..0000000 --- a/public/models/talkie/boutona_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:86c390222c74160382b92a5736664c37e3861937db2e2d58699643516fc52995 -size 176366 diff --git a/public/models/talkie/boutona_Roughness.png b/public/models/talkie/boutona_Roughness.png deleted file mode 100644 index daa6677..0000000 --- a/public/models/talkie/boutona_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adaedf0e287c99669b47a0e3e3b99ed2c6f1f5a761e69168418e56610d7d8f9a -size 54561 diff --git a/public/models/talkie/boutonb_Base_color.png b/public/models/talkie/boutonb_Base_color.png deleted file mode 100644 index ed59d49..0000000 --- a/public/models/talkie/boutonb_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:84973c1c06bf83beacd0987c5d252560ec92e09701b3ac6b73289f39392c21da -size 496575 diff --git a/public/models/talkie/boutonb_Height.png b/public/models/talkie/boutonb_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/boutonb_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/boutonb_Metallic.png b/public/models/talkie/boutonb_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/boutonb_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/boutonb_Mixed_AO.png b/public/models/talkie/boutonb_Mixed_AO.png deleted file mode 100644 index 8292910..0000000 --- a/public/models/talkie/boutonb_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d58e8ec5e90125edd38f279ee1b9fee863b75d8396fef6a0c22a9af6a9c0a341 -size 239658 diff --git a/public/models/talkie/boutonb_Normal.png b/public/models/talkie/boutonb_Normal.png deleted file mode 100644 index 943917e..0000000 --- a/public/models/talkie/boutonb_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53e5d76fcd40986674d93f289cea51c6b839d720787d98ed4112164e20558bd2 -size 176052 diff --git a/public/models/talkie/boutonb_Normal_OpenGL.png b/public/models/talkie/boutonb_Normal_OpenGL.png deleted file mode 100644 index 47539e6..0000000 --- a/public/models/talkie/boutonb_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4a1ea79411217de422f6f7fd160c673e01a2a09ebce1d95a6b5e63c8c1e5de18 -size 176723 diff --git a/public/models/talkie/boutonb_Roughness.png b/public/models/talkie/boutonb_Roughness.png deleted file mode 100644 index acba391..0000000 --- a/public/models/talkie/boutonb_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ad833228a99505d0fea7495970bfbf47d3942a86d457aca379b99d8224d8633f -size 54522 diff --git a/public/models/talkie/cable1_Base_color.png b/public/models/talkie/cable1_Base_color.png deleted file mode 100644 index 0aa378c..0000000 --- a/public/models/talkie/cable1_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:967d8771fbcd5427bf0005b147ecae38ab53271c4a99980f34db1102d0cb35f0 -size 178767 diff --git a/public/models/talkie/cable1_Height.png b/public/models/talkie/cable1_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/cable1_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/cable1_Metallic.png b/public/models/talkie/cable1_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/cable1_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/cable1_Mixed_AO.png b/public/models/talkie/cable1_Mixed_AO.png deleted file mode 100644 index 2003ade..0000000 --- a/public/models/talkie/cable1_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:73378e05b925c39d37e3cfdb1048d891b6216927fbd9eab111a61051d9c5fadd -size 142820 diff --git a/public/models/talkie/cable1_Normal.png b/public/models/talkie/cable1_Normal.png deleted file mode 100644 index 8f1338d..0000000 --- a/public/models/talkie/cable1_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cb4d0e26c8d16fcb471e2812585fb27ddfbce4500c526fce512d61f7611c5b99 -size 186292 diff --git a/public/models/talkie/cable1_Normal_OpenGL.png b/public/models/talkie/cable1_Normal_OpenGL.png deleted file mode 100644 index 456bcdb..0000000 --- a/public/models/talkie/cable1_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8854c9b53ac97ce6113d351aa528d9a6664e90947678aee05731695e034d1c79 -size 187073 diff --git a/public/models/talkie/cable1_Roughness.png b/public/models/talkie/cable1_Roughness.png deleted file mode 100644 index c1c9096..0000000 --- a/public/models/talkie/cable1_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6a2a10008bf8a90ca1e7ef831944d8e6b2c41d5aa9b5a1f20f49d7dcc942b9f2 -size 56819 diff --git a/public/models/talkie/cable2_Base_color.png b/public/models/talkie/cable2_Base_color.png deleted file mode 100644 index ba3be72..0000000 --- a/public/models/talkie/cable2_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4b1bdd610bf135a20471bc2b65e53f2a2333ab6e845380f55fed93d45dce1897 -size 210840 diff --git a/public/models/talkie/cable2_Height.png b/public/models/talkie/cable2_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/cable2_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/cable2_Metallic.png b/public/models/talkie/cable2_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/cable2_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/cable2_Mixed_AO.png b/public/models/talkie/cable2_Mixed_AO.png deleted file mode 100644 index e3a64cc..0000000 --- a/public/models/talkie/cable2_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:30e8e315f7950a91135bba6ac43d04563b1e501e83cfb4b57048320768602bc5 -size 167450 diff --git a/public/models/talkie/cable2_Normal.png b/public/models/talkie/cable2_Normal.png deleted file mode 100644 index acc99fd..0000000 --- a/public/models/talkie/cable2_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b09eae40cd4d52c348933f49347691f08dc6f25d929ddadbeddd2157444c706f -size 195384 diff --git a/public/models/talkie/cable2_Normal_OpenGL.png b/public/models/talkie/cable2_Normal_OpenGL.png deleted file mode 100644 index d184f94..0000000 --- a/public/models/talkie/cable2_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ff1ae64895328cae7bf8816a43eb38ca4db82b7979d02db927bc307fa7ec66c6 -size 196010 diff --git a/public/models/talkie/cable2_Roughness.png b/public/models/talkie/cable2_Roughness.png deleted file mode 100644 index a9440d4..0000000 --- a/public/models/talkie/cable2_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0d9e3cc7a29c90c1712196c05915bea480124dddc0db47db9415f738f895660c -size 60898 diff --git a/public/models/talkie/cadre_Base_color.png b/public/models/talkie/cadre_Base_color.png deleted file mode 100644 index afbe925..0000000 --- a/public/models/talkie/cadre_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:637e73b56419d8390e8aeb27433fdee21264c9d5b28e1caaf7e1357f6c191fd3 -size 280049 diff --git a/public/models/talkie/cadre_Height.png b/public/models/talkie/cadre_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/cadre_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/cadre_Metallic.png b/public/models/talkie/cadre_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/cadre_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/cadre_Mixed_AO.png b/public/models/talkie/cadre_Mixed_AO.png deleted file mode 100644 index 0151384..0000000 --- a/public/models/talkie/cadre_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e1f9912860e5f508e0e29ef1e03943101cdb287a1c34bd0278ac237824255da5 -size 157683 diff --git a/public/models/talkie/cadre_Normal.png b/public/models/talkie/cadre_Normal.png deleted file mode 100644 index 8d2a5a3..0000000 --- a/public/models/talkie/cadre_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:982a042d28061ce926c80293129985733aba79bc7115e0f5b55e4f4186808f5a -size 52817 diff --git a/public/models/talkie/cadre_Normal_OpenGL.png b/public/models/talkie/cadre_Normal_OpenGL.png deleted file mode 100644 index 46032a3..0000000 --- a/public/models/talkie/cadre_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:58d63de38e7fefca7ec94b4057b37256c477dbd2749f950f0bd373932b52cbf9 -size 52737 diff --git a/public/models/talkie/cadre_Roughness.png b/public/models/talkie/cadre_Roughness.png deleted file mode 100644 index 264a6e4..0000000 --- a/public/models/talkie/cadre_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:afb28f17a6f419356f49088c8c5b8d12b6aaf2471ee7e35ef42e0fda3a5365f2 -size 53784 diff --git a/public/models/talkie/e_cran_base_color.png b/public/models/talkie/e_cran_base_color.png deleted file mode 100644 index a8c2149..0000000 --- a/public/models/talkie/e_cran_base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f75363e70ffed07a182a3e61d7c301cb8fd053ac8ec784d56e9450335167dce8 -size 6502 diff --git a/public/models/talkie/e_cran_height.png b/public/models/talkie/e_cran_height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/e_cran_height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/e_cran_metallic.png b/public/models/talkie/e_cran_metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/e_cran_metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/e_cran_mixed_ao.png b/public/models/talkie/e_cran_mixed_ao.png deleted file mode 100644 index e91eb6e..0000000 --- a/public/models/talkie/e_cran_mixed_ao.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4f3fd92f328f0733f9cb9865209466c251f4edf4750654eb0385b519300cf34 -size 329712 diff --git a/public/models/talkie/e_cran_normal.png b/public/models/talkie/e_cran_normal.png deleted file mode 100644 index 175152b..0000000 --- a/public/models/talkie/e_cran_normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a07dd2bf8fe1d37b9a74d2ca09b8a77d8a97029d41a8483e34ba30bd8e9efc04 -size 74315 diff --git a/public/models/talkie/e_cran_normal_opengl.png b/public/models/talkie/e_cran_normal_opengl.png deleted file mode 100644 index e17d9b3..0000000 --- a/public/models/talkie/e_cran_normal_opengl.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca90a32ea8b1dadb11a5cf2e41d4d096920fd7a2ab493dec96682a479d4e7a8c -size 74316 diff --git a/public/models/talkie/e_cran_roughness.png b/public/models/talkie/e_cran_roughness.png deleted file mode 100644 index ad642fa..0000000 --- a/public/models/talkie/e_cran_roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:700f97e34547f14a62a45858218e56eb3715a278380c2f1f14be9ca6feaae959 -size 72768 diff --git a/public/models/talkie/hautparleur_Base_color.png b/public/models/talkie/hautparleur_Base_color.png deleted file mode 100644 index 9e43314..0000000 --- a/public/models/talkie/hautparleur_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:36168017b9c2d6961f869f461812a53ed4bdb49f24f350452f4604009acb6fe8 -size 658014 diff --git a/public/models/talkie/hautparleur_Height.png b/public/models/talkie/hautparleur_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/hautparleur_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/hautparleur_Metallic.png b/public/models/talkie/hautparleur_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/hautparleur_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/hautparleur_Mixed_AO.png b/public/models/talkie/hautparleur_Mixed_AO.png deleted file mode 100644 index f2bfd28..0000000 --- a/public/models/talkie/hautparleur_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:29ac8737a9eccff201ad351b4b403e93ed65c7a082052c1070455bc7c795bcbb -size 201135 diff --git a/public/models/talkie/hautparleur_Normal.png b/public/models/talkie/hautparleur_Normal.png deleted file mode 100644 index 5d89a1c..0000000 --- a/public/models/talkie/hautparleur_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:65edaf688e31787da8deb864079aed068cc71775dbbde4c350481896c2d8a204 -size 98635 diff --git a/public/models/talkie/hautparleur_Normal_OpenGL.png b/public/models/talkie/hautparleur_Normal_OpenGL.png deleted file mode 100644 index 814a2b3..0000000 --- a/public/models/talkie/hautparleur_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c689c9d28d984f54e5647de38423f34da442b8e8cac076052bfa5df07457fb1c -size 98768 diff --git a/public/models/talkie/hautparleur_Roughness.png b/public/models/talkie/hautparleur_Roughness.png deleted file mode 100644 index 17ac038..0000000 --- a/public/models/talkie/hautparleur_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9f7c1e238150662981d8a4d003045021ee0cca21af14dc79a9180671c6a82621 -size 74349 diff --git a/public/models/talkie/model.bin b/public/models/talkie/model.bin deleted file mode 100644 index 7e13375..0000000 --- a/public/models/talkie/model.bin +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:970717ff52a7275d1e6090a8699021ae57b74127b05c016eafb6dca45158420f -size 198456 diff --git a/public/models/talkie/model.glb b/public/models/talkie/model.glb new file mode 100644 index 0000000..8fafee9 --- /dev/null +++ b/public/models/talkie/model.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d9e8136c7645a868ad874ee98afd508ff41d6be3f1b7220a889bb161be41af4 +size 6954256 diff --git a/public/models/talkie/model.gltf b/public/models/talkie/model.gltf deleted file mode 100644 index 5caa83b..0000000 --- a/public/models/talkie/model.gltf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:694acec3219e3cb0dbce0e9739bc3c6655dbf5360f104d50a52618e06d90c946 -size 63007 diff --git a/public/models/talkie/prise_Base_color.png b/public/models/talkie/prise_Base_color.png deleted file mode 100644 index 30d846f..0000000 --- a/public/models/talkie/prise_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7e012c8775c6eb274a80856394fac6673f7beb754c2f78e1c51c842ac017fdd5 -size 472487 diff --git a/public/models/talkie/prise_Height.png b/public/models/talkie/prise_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/prise_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/prise_Metallic.png b/public/models/talkie/prise_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/prise_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/prise_Mixed_AO.png b/public/models/talkie/prise_Mixed_AO.png deleted file mode 100644 index 1ed1f71..0000000 --- a/public/models/talkie/prise_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9a7ae156409544d00bf3332fb542aaec0711d4ce4c9d39d4bd573fb4f0212051 -size 230780 diff --git a/public/models/talkie/prise_Normal.png b/public/models/talkie/prise_Normal.png deleted file mode 100644 index 5bcffa1..0000000 --- a/public/models/talkie/prise_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c00bec06b1bbd954eb91872fa8cd817f247b275c16e6c372db52189ba6f3778 -size 212856 diff --git a/public/models/talkie/prise_Normal_OpenGL.png b/public/models/talkie/prise_Normal_OpenGL.png deleted file mode 100644 index 6bc3741..0000000 --- a/public/models/talkie/prise_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aa8e8d378a595f8aafc4cd34ecac65426a03847a402f955b7b0280f0383d40eb -size 224961 diff --git a/public/models/talkie/prise_Roughness.png b/public/models/talkie/prise_Roughness.png deleted file mode 100644 index c827dba..0000000 --- a/public/models/talkie/prise_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:161c97c6ad56619ad702cc955a26ea92203575f31bb35ebc8ca398b48aae9889 -size 100073 diff --git a/public/models/talkie/talkie_Base_color.png b/public/models/talkie/talkie_Base_color.png deleted file mode 100644 index 295b322..0000000 --- a/public/models/talkie/talkie_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aead25dde6c940f353fa4a53f43e37dde80cd924ccced3c4f88a45dcc5fb4751 -size 610827 diff --git a/public/models/talkie/talkie_Height.png b/public/models/talkie/talkie_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/talkie_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/talkie_Metallic.png b/public/models/talkie/talkie_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/talkie_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/talkie_Mixed_AO.png b/public/models/talkie/talkie_Mixed_AO.png deleted file mode 100644 index a015f96..0000000 --- a/public/models/talkie/talkie_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78a4e5bc8affb8c7f529712ddcc4958982729d4bbc64163d93b4e3df8192f3e9 -size 229809 diff --git a/public/models/talkie/talkie_Normal.png b/public/models/talkie/talkie_Normal.png deleted file mode 100644 index d3e9649..0000000 --- a/public/models/talkie/talkie_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3fba8c5bce232fd16f3d3da18228438443bfcfab6f1673884e2c6439a799a342 -size 285066 diff --git a/public/models/talkie/talkie_Normal_OpenGL.png b/public/models/talkie/talkie_Normal_OpenGL.png deleted file mode 100644 index e58b36f..0000000 --- a/public/models/talkie/talkie_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:708382f0b0340e845f0cd4127b84ec5f837f5ca3a51f0c4064ba790374643c21 -size 287356 diff --git a/public/models/talkie/talkie_Roughness.png b/public/models/talkie/talkie_Roughness.png deleted file mode 100644 index 091f911..0000000 --- a/public/models/talkie/talkie_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:638da789f9d21dfa546cf053c5f0c4e47fc18eef3d7ac412d39bdbdf3b3974b1 -size 134252 diff --git a/public/models/talkie/touches_Base_color.png b/public/models/talkie/touches_Base_color.png deleted file mode 100644 index eb7631c..0000000 --- a/public/models/talkie/touches_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:47ef413967a461f9768b74d15a2ab1c8e3e3fe6fc9be027d8cf1e7268bb8a888 -size 126096 diff --git a/public/models/talkie/touches_Height.png b/public/models/talkie/touches_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/touches_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/touches_Metallic.png b/public/models/talkie/touches_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/touches_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/touches_Mixed_AO.png b/public/models/talkie/touches_Mixed_AO.png deleted file mode 100644 index 5673800..0000000 --- a/public/models/talkie/touches_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6884417f47afb4330b00f9f42fa1faf7b8bbd06d2ae9595cffe409a4d38cd461 -size 246760 diff --git a/public/models/talkie/touches_Normal.png b/public/models/talkie/touches_Normal.png deleted file mode 100644 index 07f83c4..0000000 --- a/public/models/talkie/touches_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:09d922e17cd400c32ef51462723e9c5ee9dfbe4c668e88e6057f9e7d60c8830e -size 59216 diff --git a/public/models/talkie/touches_Normal_OpenGL.png b/public/models/talkie/touches_Normal_OpenGL.png deleted file mode 100644 index ec98ce7..0000000 --- a/public/models/talkie/touches_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8cdadc48d7d365fa0f8e4f3b64a61a6fa95cf9bcafaf50dc83ea7cf90173f68b -size 59178 diff --git a/public/models/talkie/touches_Roughness.png b/public/models/talkie/touches_Roughness.png deleted file mode 100644 index bd73a19..0000000 --- a/public/models/talkie/touches_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:5804a17a4fd8691a606cbb3e671234f2c876dfc0e88e16d2f781bd35785167cb -size 56002 diff --git a/public/models/talkie/écran_Base_color.png b/public/models/talkie/écran_Base_color.png deleted file mode 100644 index b28a367..0000000 --- a/public/models/talkie/écran_Base_color.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfbf65890f6d5019bf113246dcecb83c97e31d6e10b0429d5e89df9df67a58bd -size 6498 diff --git a/public/models/talkie/écran_Height.png b/public/models/talkie/écran_Height.png deleted file mode 100644 index 713980d..0000000 --- a/public/models/talkie/écran_Height.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:208daeea7306a6a576fbe1098c66d59571dd984635d7fb6c017fd767cde531ed -size 3178 diff --git a/public/models/talkie/écran_Metallic.png b/public/models/talkie/écran_Metallic.png deleted file mode 100644 index b46e54a..0000000 --- a/public/models/talkie/écran_Metallic.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:60041773ef2d493f3547aa1b0fbdf5b1bb548a3da39164384293ef5292b2c5b3 -size 1118 diff --git a/public/models/talkie/écran_Mixed_AO.png b/public/models/talkie/écran_Mixed_AO.png deleted file mode 100644 index e91eb6e..0000000 --- a/public/models/talkie/écran_Mixed_AO.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4f3fd92f328f0733f9cb9865209466c251f4edf4750654eb0385b519300cf34 -size 329712 diff --git a/public/models/talkie/écran_Normal.png b/public/models/talkie/écran_Normal.png deleted file mode 100644 index 175152b..0000000 --- a/public/models/talkie/écran_Normal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a07dd2bf8fe1d37b9a74d2ca09b8a77d8a97029d41a8483e34ba30bd8e9efc04 -size 74315 diff --git a/public/models/talkie/écran_Normal_OpenGL.png b/public/models/talkie/écran_Normal_OpenGL.png deleted file mode 100644 index e17d9b3..0000000 --- a/public/models/talkie/écran_Normal_OpenGL.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ca90a32ea8b1dadb11a5cf2e41d4d096920fd7a2ab493dec96682a479d4e7a8c -size 74316 diff --git a/public/models/talkie/écran_Roughness.png b/public/models/talkie/écran_Roughness.png deleted file mode 100644 index d89bffa..0000000 --- a/public/models/talkie/écran_Roughness.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d560c80d755fdfff917c63d96d664cc4c9be963cc38ac63533418f7df1b0642c -size 72772 diff --git a/public/sounds/dialogue/electricienne_aprèsmontage.mp3 b/public/sounds/dialogue/electricienne_aprèsmontage.mp3 new file mode 100644 index 0000000..b7a5af9 --- /dev/null +++ b/public/sounds/dialogue/electricienne_aprèsmontage.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cdec23a1d25330d9db9a91384b51776996a87da52d59ba264022a01aa794903 +size 46191 diff --git a/public/sounds/dialogue/electricienne_aurevoir.mp3 b/public/sounds/dialogue/electricienne_aurevoir.mp3 new file mode 100644 index 0000000..190200c --- /dev/null +++ b/public/sounds/dialogue/electricienne_aurevoir.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1e6d2a2c28499e2ed6806335e5037f515c6ba790c7be46b56dab4e3b717d854e +size 31216 diff --git a/public/sounds/dialogue/electricienne_welcome.mp3 b/public/sounds/dialogue/electricienne_welcome.mp3 new file mode 100644 index 0000000..1da1fcf --- /dev/null +++ b/public/sounds/dialogue/electricienne_welcome.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d0a24a7d2a68fdb30bd4776161cd9d9c17383a1d2a5f0190d0667b7f51f4be9 +size 72661 diff --git a/src/App.tsx b/src/App.tsx index c6ccffa..c90a626 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,15 @@ import { RouterProvider } from "@tanstack/react-router"; +import { SiteMobileBlocker } from "@/components/site/SiteMobileBlocker"; +import { useIsMobile } from "@/hooks/ui/useIsMobile"; import { router } from "@/router"; function App(): React.JSX.Element { + const isMobile = useIsMobile(); + + if (isMobile) { + return ; + } + return ; } diff --git a/src/components/debug/DebugOctreeVisualization.tsx b/src/components/debug/DebugOctreeVisualization.tsx new file mode 100644 index 0000000..8ff8ae7 --- /dev/null +++ b/src/components/debug/DebugOctreeVisualization.tsx @@ -0,0 +1,173 @@ +import { useMemo } from "react"; +import { Box3, BufferAttribute, BufferGeometry } from "three"; +import type { Octree } from "three-stdlib"; +import { + LA_FABRIK_CENTER, + isInsideLaFabrikFootprint, +} from "@/data/world/laFabrikConfig"; +import { useDebugVisualsStore } from "@/managers/stores/useDebugVisualsStore"; + +interface DebugOctreeVisualizationProps { + octree: Octree | null; +} + +interface OctreeNodeBox { + box: Box3; + depth: number; + triangleCount: number; + isLeaf: boolean; +} + +interface CollectOptions { + minDepth: number; + maxDepth: number; + leavesOnly: boolean; + fabrikOnly: boolean; +} + +const FABRIK_FILTER_PADDING = 1.5; +const FABRIK_FILTER_VERTICAL = 8; + +const BOX_VERTEX_INDEX_PAIRS: ReadonlyArray = [ + [0, 1], + [1, 3], + [3, 2], + [2, 0], + [4, 5], + [5, 7], + [7, 6], + [6, 4], + [0, 4], + [1, 5], + [2, 6], + [3, 7], +]; + +function boxIntersectsFabrik(box: Box3): boolean { + if (box.max.y < LA_FABRIK_CENTER[1] - FABRIK_FILTER_VERTICAL) return false; + if (box.min.y > LA_FABRIK_CENTER[1] + FABRIK_FILTER_VERTICAL) return false; + + // Sample box corners + center on XZ plane against the rotated fabrik footprint. + const samples: ReadonlyArray = [ + [box.min.x, box.min.z], + [box.min.x, box.max.z], + [box.max.x, box.min.z], + [box.max.x, box.max.z], + [(box.min.x + box.max.x) * 0.5, (box.min.z + box.max.z) * 0.5], + ]; + for (const [x, z] of samples) { + if (isInsideLaFabrikFootprint(x, z, FABRIK_FILTER_PADDING)) return true; + } + return false; +} + +function collectOctreeBoxes( + node: Octree, + options: CollectOptions, + depth = 0, + acc: OctreeNodeBox[] = [], +): OctreeNodeBox[] { + if (depth > options.maxDepth) return acc; + + const isLeaf = node.subTrees.length === 0; + const passesDepth = depth >= options.minDepth; + const passesLeafFilter = !options.leavesOnly || isLeaf; + const hasTriangles = node.triangles.length > 0; + const passesFabrikFilter = + !options.fabrikOnly || boxIntersectsFabrik(node.box); + + if (passesDepth && passesLeafFilter && hasTriangles && passesFabrikFilter) { + acc.push({ + box: node.box, + depth, + triangleCount: node.triangles.length, + isLeaf, + }); + } + + for (const sub of node.subTrees) { + collectOctreeBoxes(sub, options, depth + 1, acc); + } + + return acc; +} + +function buildOctreeLineGeometry( + nodes: readonly OctreeNodeBox[], +): BufferGeometry { + const positionsBuffer = new Float32Array( + nodes.length * BOX_VERTEX_INDEX_PAIRS.length * 2 * 3, + ); + + const corners: [number, number, number][] = Array.from({ length: 8 }, () => [ + 0, 0, 0, + ]); + + let positionsOffset = 0; + + for (const node of nodes) { + const { min, max } = node.box; + + corners[0] = [min.x, min.y, min.z]; + corners[1] = [max.x, min.y, min.z]; + corners[2] = [min.x, max.y, min.z]; + corners[3] = [max.x, max.y, min.z]; + corners[4] = [min.x, min.y, max.z]; + corners[5] = [max.x, min.y, max.z]; + corners[6] = [min.x, max.y, max.z]; + corners[7] = [max.x, max.y, max.z]; + + for (const [a, b] of BOX_VERTEX_INDEX_PAIRS) { + const ca = corners[a]!; + const cb = corners[b]!; + positionsBuffer[positionsOffset++] = ca[0]; + positionsBuffer[positionsOffset++] = ca[1]; + positionsBuffer[positionsOffset++] = ca[2]; + positionsBuffer[positionsOffset++] = cb[0]; + positionsBuffer[positionsOffset++] = cb[1]; + positionsBuffer[positionsOffset++] = cb[2]; + } + } + + const geometry = new BufferGeometry(); + geometry.setAttribute("position", new BufferAttribute(positionsBuffer, 3)); + return geometry; +} + +export function DebugOctreeVisualization({ + octree, +}: DebugOctreeVisualizationProps): React.JSX.Element | null { + const showOctree = useDebugVisualsStore((state) => state.showOctree); + const minDepth = useDebugVisualsStore((state) => state.octreeMinDepth); + const maxDepth = useDebugVisualsStore((state) => state.octreeMaxDepth); + const leavesOnly = useDebugVisualsStore((state) => state.octreeLeavesOnly); + const opacity = useDebugVisualsStore((state) => state.octreeOpacity); + const fabrikOnly = useDebugVisualsStore((state) => state.octreeFabrikOnly); + + const geometry = useMemo(() => { + if (!octree || !showOctree) return null; + const boxes = collectOctreeBoxes(octree, { + minDepth, + maxDepth, + leavesOnly, + fabrikOnly, + }); + if (boxes.length === 0) return null; + return buildOctreeLineGeometry(boxes); + }, [fabrikOnly, leavesOnly, maxDepth, minDepth, octree, showOctree]); + + if (!geometry) return null; + + return ( + + + + + ); +} diff --git a/src/components/debug/DebugPlayerModel.tsx b/src/components/debug/DebugPlayerModel.tsx new file mode 100644 index 0000000..fd4d6a3 --- /dev/null +++ b/src/components/debug/DebugPlayerModel.tsx @@ -0,0 +1,58 @@ +import { useEffect, useMemo, useRef } from "react"; +import * as THREE from "three"; +import { useFrame } from "@react-three/fiber"; +import { useGLTF } from "@react-three/drei"; + +const MODEL_PATH = "/models/persoprincipal/model.gltf"; +// Offset expressed in the camera's local space: +// - x: horizontal (0 = centered) +// - y: vertical relative to camera eye (negative = below) +// - z: forward (negative = in front of the camera) +const LOCAL_OFFSET = new THREE.Vector3(0, -1, -2.5); + +const eulerHelper = new THREE.Euler(); + +export function DebugPlayerModel(): React.JSX.Element { + const groupRef = useRef(null); + const { scene } = useGLTF(MODEL_PATH); + + const model = useMemo(() => { + const cloned = scene.clone(true); + cloned.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.castShadow = true; + child.receiveShadow = true; + child.frustumCulled = false; + } + }); + return cloned; + }, [scene]); + + useEffect( + () => () => { + model.clear(); + }, + [model], + ); + + useFrame(({ camera }) => { + const group = groupRef.current; + if (!group) return; + + // Place the model in front of the camera using its local space so it stays + // visible regardless of the camera pitch (top-down ebike view, etc.). + group.position.copy(LOCAL_OFFSET).applyMatrix4(camera.matrixWorld); + + // Keep the model upright and aligned with the camera yaw only. + eulerHelper.setFromQuaternion(camera.quaternion, "YXZ"); + group.rotation.set(0, eulerHelper.y, 0); + }); + + return ( + + + + ); +} + +useGLTF.preload(MODEL_PATH); diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index b49e3c4..6eb0455 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -2,17 +2,22 @@ import { useEffect, useRef, useState, useMemo, useCallback } from "react"; import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { EbikeGPSMap } from "@/components/ebike/EbikeGPSMap"; +import { EbikeSpeedometer } from "@/components/ebike/EbikeSpeedometer"; import { InteractableObject } from "@/components/three/interaction/InteractableObject"; import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; import { useClonedObject } from "@/hooks/three/useClonedObject"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; import { useEbikeSounds } from "@/hooks/ebike/useEbikeSounds"; +import { + getObjectBottomOffset, + useTerrainHeightSampler, +} from "@/hooks/three/useTerrainHeight"; import { animateCameraTransformTransition } from "@/world/GameCinematics"; import { useGameStore } from "@/managers/stores/useGameStore"; -import { PLAYER_EYE_HEIGHT } from "@/data/player/playerConfig"; import { EBIKE_CAMERA_TRANSFORM, EBIKE_DROP_PLAYER_TRANSFORM, + EBIKE_WORLD_SCALE, EBIKE_WORLD_ROTATION_Y, } from "@/data/ebike/ebikeConfig"; import type { Vector3Tuple } from "@/types/three/three"; @@ -31,12 +36,29 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { position: position, }); const model = useClonedObject(scene); + const terrainHeight = useTerrainHeightSampler(); + const parkedPosition = useMemo(() => { + const [x, y, z] = position; + const height = terrainHeight.getHeight(x, z) ?? y; + const bottomOffset = getObjectBottomOffset(model, [ + EBIKE_WORLD_SCALE, + EBIKE_WORLD_SCALE, + EBIKE_WORLD_SCALE, + ]); + + return [x, height + bottomOffset, z]; + }, [model, position, terrainHeight]); const movementMode = useGameStore((state) => state.player.movementMode); const mainState = useGameStore((state) => state.mainState); const ebikeStep = useGameStore((state) => state.ebike.currentStep); const setMissionStep = useGameStore((state) => state.setMissionStep); const camera = useThree((state) => state.camera); const updateEbikeSounds = useEbikeSounds(); + const repairGameOwnsEbikeModel = + mainState === "ebike" && + ebikeStep !== "locked" && + ebikeStep !== "waiting" && + ebikeStep !== "inspected"; // Map active mainState to target repair zone coordinate const destPos = useMemo(() => { @@ -58,19 +80,19 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { y: number; z: number; }>({ - x: position[0], - y: position[1], - z: position[2], + x: parkedPosition[0], + y: parkedPosition[1], + z: parkedPosition[2], }); const lastGpsUpdatePos = useRef( - new THREE.Vector3(...position), + new THREE.Vector3(...parkedPosition), ); // Use ref for internal state, and state for debug visualization (to avoid ref access during render) const restingPositionRef = useRef([ - position[0], - position[1] - PLAYER_EYE_HEIGHT, - position[2], + parkedPosition[0], + parkedPosition[1], + parkedPosition[2], ]); const restingRotationRef = useRef(EBIKE_WORLD_ROTATION_Y); const forkRef = useRef(null); @@ -79,11 +101,27 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { const [showCameraPoints, setShowCameraPoints] = useState(true); const [debugRestingPosition, setDebugRestingPosition] = useState([ - position[0], - position[1] - PLAYER_EYE_HEIGHT, - position[2], + parkedPosition[0], + parkedPosition[1], + parkedPosition[2], ]); + useEffect(() => { + if (movementMode === "ebike") return; + + restingPositionRef.current = parkedPosition; + restingRotationRef.current = EBIKE_WORLD_ROTATION_Y; + lastGpsUpdatePos.current.set(...parkedPosition); + + if (groupRef.current) { + groupRef.current.position.set(...parkedPosition); + groupRef.current.rotation.set(0, EBIKE_WORLD_ROTATION_Y, 0); + } + + window.ebikeParkedPosition = parkedPosition; + window.ebikeParkedRotation = EBIKE_WORLD_ROTATION_Y; + }, [movementMode, parkedPosition]); + useEffect(() => { if (model) { const fork = model.getObjectByName("fourche"); @@ -93,6 +131,17 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { } }, [model]); + useEffect(() => { + if (!model) return; + + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }); + }, [model]); + useEffect(() => { window.ebikeVisualGroup = groupRef; window.ebikeParkedPosition = restingPositionRef.current; @@ -169,16 +218,30 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { debugRestingPosition[1] + EBIKE_DROP_PLAYER_TRANSFORM.position[1], debugRestingPosition[2] + EBIKE_DROP_PLAYER_TRANSFORM.position[2], ]; + const interactionLabel = + mainState === "ebike" + ? "Réparer l'e-bike" + : movementMode === "walk" + ? "Monter sur le bike" + : "Descendre du bike"; const handleInteract = useCallback((): void => { if (window.ebikeBreakdownActive === true) return; if (movementMode === "walk") { - if (mainState === "ebike" && ebikeStep === "waiting") { + if ( + mainState === "ebike" && + (ebikeStep === "locked" || ebikeStep === "waiting") + ) { setMissionStep("ebike", "inspected"); return; } + if (mainState === "ebike" && ebikeStep === "inspected") { + setMissionStep("ebike", "fragmented"); + return; + } + const cameraOffset = new THREE.Vector3( ...EBIKE_CAMERA_TRANSFORM.position, ); @@ -258,51 +321,51 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { return ( <> - - - - - - - - + + + + + + + - {/* Dynamic 3D GPS Dashboard Screen */} - - + {/* Dynamic 3D GPS Dashboard Screen */} + + + + + + - + ) : null} - {showCameraPoints && ( + {showCameraPoints && !repairGameOwnsEbikeModel && ( <> diff --git a/src/components/ebike/EbikeGPSMap.tsx b/src/components/ebike/EbikeGPSMap.tsx index e51108c..08fab3e 100644 --- a/src/components/ebike/EbikeGPSMap.tsx +++ b/src/components/ebike/EbikeGPSMap.tsx @@ -89,6 +89,8 @@ export interface EbikeGPSMapProps { * Default: 1 */ zoom?: number; + + renderOrder?: number; } /** @@ -107,6 +109,7 @@ export const EbikeGPSMap: React.FC = ({ position = [0, 0, 0], canvasSize = 1024, zoom = 1, + renderOrder = 10_000, }) => { const [waypoints, setWaypoints] = useState([]); const [mapImage, setMapImage] = useState< @@ -506,12 +509,13 @@ export const EbikeGPSMap: React.FC = ({ }, [draw]); return ( - + diff --git a/src/components/ebike/EbikeSpeedometer.tsx b/src/components/ebike/EbikeSpeedometer.tsx new file mode 100644 index 0000000..82d7040 --- /dev/null +++ b/src/components/ebike/EbikeSpeedometer.tsx @@ -0,0 +1,90 @@ +import { useEffect, useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { useTexture } from "@react-three/drei"; +import * as THREE from "three"; + +const SPEEDOMETER_DIAL_TEXTURE = "/assets/world/gps/cadran.png"; +const SPEEDOMETER_NEEDLE_TEXTURE = "/assets/world/gps/fleche.png"; +const SPEEDOMETER_MIN_ANGLE = Math.PI / 2; +const SPEEDOMETER_MAX_ANGLE = -Math.PI / 2; +const SPEEDOMETER_RENDER_ORDER = 10_000; + +interface EbikeSpeedometerProps { + width?: number; + height?: number; +} + +export function EbikeSpeedometer({ + width = 0.9, + height = 0.5, +}: EbikeSpeedometerProps): React.JSX.Element { + const needleGroupRef = useRef(null); + const speedFactorRef = useRef(0); + const [dialTexture, needleTexture] = useTexture([ + SPEEDOMETER_DIAL_TEXTURE, + SPEEDOMETER_NEEDLE_TEXTURE, + ]) as [THREE.Texture, THREE.Texture]; + const needleWidth = width * 0.68; + const needleHeight = needleWidth / 2; + + useEffect(() => { + [dialTexture, needleTexture].forEach((texture) => { + texture.colorSpace = THREE.SRGBColorSpace; + texture.needsUpdate = true; + }); + }, [dialTexture, needleTexture]); + + useFrame((_, delta) => { + const targetSpeedFactor = THREE.MathUtils.clamp( + window.ebikeSpeedFactor ?? 0, + 0, + 1, + ); + speedFactorRef.current = THREE.MathUtils.lerp( + speedFactorRef.current, + targetSpeedFactor, + Math.min(1, delta * 10), + ); + + if (needleGroupRef.current) { + needleGroupRef.current.rotation.z = THREE.MathUtils.lerp( + SPEEDOMETER_MIN_ANGLE, + SPEEDOMETER_MAX_ANGLE, + speedFactorRef.current, + ); + } + }); + + return ( + + + + + + + + + + + + + + ); +} diff --git a/src/components/game/EbikeIntroSequence.tsx b/src/components/game/EbikeIntroSequence.tsx index 93cc754..3529619 100644 --- a/src/components/game/EbikeIntroSequence.tsx +++ b/src/components/game/EbikeIntroSequence.tsx @@ -1,11 +1,13 @@ import { useEffect, useRef, useState } from "react"; +import * as THREE from "three"; import { MissionNotification } from "@/components/ui/MissionNotification"; import { EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS, EBIKE_BREAKDOWN_DIALOGUE_ID, - EBIKE_INTRO_RIDE_DURATION_MS, + EBIKE_INTRO_BREAKDOWN_DISTANCE, EBIKE_SOUNDS, } from "@/data/ebike/ebikeConfig"; +import { INTRO_MISSION_NOTIFICATION_IMAGE_PATH } from "@/data/gameplay/missionNotifications"; import { AudioManager } from "@/managers/AudioManager"; import { useGameStore } from "@/managers/stores/useGameStore"; import { loadDialogueManifest } from "@/utils/dialogues/loadDialogueManifest"; @@ -20,6 +22,9 @@ export function EbikeIntroSequence(): React.JSX.Element | null { const completeIntro = useGameStore((state) => state.completeIntro); const [breakdownDialogueDone, setBreakdownDialogueDone] = useState(false); const hasStartedBreakdown = useRef(false); + const rideDistance = useRef(0); + const lastRidePosition = useRef(null); + const currentRidePosition = useRef(new THREE.Vector3()); useEffect(() => { if (introStep !== "await-ebike-mount" || movementMode !== "ebike") return; @@ -28,16 +33,45 @@ export function EbikeIntroSequence(): React.JSX.Element | null { }, [introStep, movementMode, setIntroStep]); useEffect(() => { - if (introStep !== "ebike-intro-ride") return undefined; + if (introStep !== "ebike-intro-ride") return; - const timeoutId = window.setTimeout(() => { - setIntroStep("ebike-breakdown"); - }, EBIKE_INTRO_RIDE_DURATION_MS); + rideDistance.current = 0; + lastRidePosition.current = null; + }, [introStep]); - return () => { - window.clearTimeout(timeoutId); + useEffect(() => { + if (introStep !== "ebike-intro-ride" || movementMode !== "ebike") { + return undefined; + } + + let animationFrameId = 0; + const tick = () => { + const parkedPosition = window.ebikeParkedPosition; + if (parkedPosition) { + currentRidePosition.current.set(...parkedPosition); + if (!lastRidePosition.current) { + lastRidePosition.current = currentRidePosition.current.clone(); + } else { + rideDistance.current += currentRidePosition.current.distanceTo( + lastRidePosition.current, + ); + lastRidePosition.current.copy(currentRidePosition.current); + } + + if (rideDistance.current >= EBIKE_INTRO_BREAKDOWN_DISTANCE) { + setIntroStep("ebike-breakdown"); + return; + } + } + + animationFrameId = window.requestAnimationFrame(tick); }; - }, [introStep, setIntroStep]); + + animationFrameId = window.requestAnimationFrame(tick); + return () => { + window.cancelAnimationFrame(animationFrameId); + }; + }, [introStep, movementMode, setIntroStep]); useEffect(() => { if (introStep !== "ebike-breakdown" || hasStartedBreakdown.current) { @@ -112,14 +146,37 @@ export function EbikeIntroSequence(): React.JSX.Element | null { return null; } - if (introStep !== "await-ebike-mount" && introStep !== "ebike-intro-ride") { + if (mainState == "pylon") { + if (pylonStep === "approaching") { + return ; + } + if (pylonStep === "narrator-outro") { + return ; + } return null; } + if ( + introStep !== "reveal" && + introStep !== "await-ebike-mount" && + introStep !== "ebike-intro-ride" && + introStep !== "ebike-breakdown" + ) { + return null; + } + + if (introStep === "ebike-breakdown") { + return ; + } + return ( ); } diff --git a/src/components/site/SiteMobileBlocker.tsx b/src/components/site/SiteMobileBlocker.tsx index acce806..dc84282 100644 --- a/src/components/site/SiteMobileBlocker.tsx +++ b/src/components/site/SiteMobileBlocker.tsx @@ -20,7 +20,7 @@ export function SiteMobileBlocker(): React.JSX.Element { }} > Logo Altera diff --git a/src/components/three/handTracking/HandTrackingGlove.tsx b/src/components/three/handTracking/HandTrackingGlove.tsx index 37e4518..d5c8904 100644 --- a/src/components/three/handTracking/HandTrackingGlove.tsx +++ b/src/components/three/handTracking/HandTrackingGlove.tsx @@ -1,7 +1,6 @@ import type { ReactNode } from "react"; import { Component, useEffect, useMemo, useRef } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import { useGLTF } from "@react-three/drei"; import * as THREE from "three"; import { SkeletonUtils } from "three-stdlib"; import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; @@ -362,6 +361,3 @@ export function HandTrackingGlove({ ); } - -useGLTF.preload(GLOVE_CONFIGS.left.modelPath); -useGLTF.preload(GLOVE_CONFIGS.right.modelPath); diff --git a/src/components/ui/GameSettingsMenu.tsx b/src/components/ui/GameSettingsMenu.tsx index abd52ce..f30c6f0 100644 --- a/src/components/ui/GameSettingsMenu.tsx +++ b/src/components/ui/GameSettingsMenu.tsx @@ -105,6 +105,9 @@ function GraphicsPresetButton({ const lodLabel = config.forceLodModels ? "LOD forcé" : `HD ${config.lodHighDetailDistance}m`; + const chunkLabel = config.chunkStreamingEnabled + ? formatChunkDistance(config.chunkLoadRadius) + : "All"; return ( ); diff --git a/src/components/ui/GameUI.tsx b/src/components/ui/GameUI.tsx index 687d71e..526c3b6 100644 --- a/src/components/ui/GameUI.tsx +++ b/src/components/ui/GameUI.tsx @@ -5,6 +5,7 @@ import { HandTrackingVisualizer } from "@/components/ui/HandTrackingVisualizer"; import { InteractPrompt } from "@/components/ui/InteractPrompt"; import { RepairMovementLockIndicator } from "@/components/ui/RepairMovementLockIndicator"; import { Subtitles } from "@/components/ui/Subtitles"; +import { TalkieDialogueOverlay } from "@/components/ui/TalkieDialogueOverlay"; export function GameUI(): React.JSX.Element { return ( @@ -15,6 +16,7 @@ export function GameUI(): React.JSX.Element { + ); diff --git a/src/components/ui/MissionNotification.tsx b/src/components/ui/MissionNotification.tsx index 8b2d968..7b64447 100644 --- a/src/components/ui/MissionNotification.tsx +++ b/src/components/ui/MissionNotification.tsx @@ -2,14 +2,20 @@ import { MISSION_NOTIFICATION_IMAGE_PATHS } from "@/data/gameplay/missionNotific import type { RepairMissionId } from "@/types/gameplay/repairMission"; interface MissionNotificationProps { - mission: RepairMissionId; + mission?: RepairMissionId; + imagePath?: string; visible?: boolean; } export function MissionNotification({ mission, + imagePath, visible = true, }: MissionNotificationProps): React.JSX.Element { + const src = + imagePath ?? (mission ? MISSION_NOTIFICATION_IMAGE_PATHS[mission] : ""); + const isVideo = src.toLowerCase().endsWith(".webm"); + return (
- Nouvel objectif de mission + {isVideo ? ( +
); diff --git a/src/components/ui/SceneLoadingOverlay.tsx b/src/components/ui/SceneLoadingOverlay.tsx index 831e646..c219cce 100644 --- a/src/components/ui/SceneLoadingOverlay.tsx +++ b/src/components/ui/SceneLoadingOverlay.tsx @@ -1,10 +1,18 @@ +import { useEffect, useState } from "react"; import { AppLoadingIndicator } from "@/components/ui/AppLoadingIndicator"; import type { SceneLoadingState } from "@/types/world/sceneLoading"; -const LOADING_BACKGROUND_PATH = "/assets/bg-site.png"; -const LOADING_LOGO_PATH = "/assets/logo/logo.jpg"; +const LOADING_BACKGROUND_PATH = "/assets/bg-site.webp"; +const LOADING_FRAME_RATE = 12; +const LOADING_FRAME_INTERVAL_MS = 1000 / LOADING_FRAME_RATE; +const LOADING_LOGO_FRAMES = [ + "/assets/loader/Loader-1.png", + "/assets/loader/Loader-2.png", + "/assets/loader/Loader-3.png", + "/assets/loader/Loader-4.png", +] as const; -for (const path of [LOADING_BACKGROUND_PATH, LOADING_LOGO_PATH]) { +for (const path of [LOADING_BACKGROUND_PATH, ...LOADING_LOGO_FRAMES]) { const image = new Image(); image.src = path; } @@ -16,8 +24,25 @@ interface SceneLoadingOverlayProps { export function SceneLoadingOverlay({ state, }: SceneLoadingOverlayProps): React.JSX.Element | null { + const [logoFrameIndex, setLogoFrameIndex] = useState(0); const isReady = state.status === "ready"; const progress = Math.round(Math.max(0, Math.min(1, state.progress)) * 100); + const logoFramePath = + LOADING_LOGO_FRAMES[logoFrameIndex] ?? LOADING_LOGO_FRAMES[0]; + + useEffect(() => { + if (isReady) return undefined; + + const intervalId = window.setInterval(() => { + setLogoFrameIndex( + (currentIndex) => (currentIndex + 1) % LOADING_LOGO_FRAMES.length, + ); + }, LOADING_FRAME_INTERVAL_MS); + + return () => { + window.clearInterval(intervalId); + }; + }, [isReady]); return (
diff --git a/src/components/ui/TalkieDialogueOverlay.tsx b/src/components/ui/TalkieDialogueOverlay.tsx new file mode 100644 index 0000000..21a7f4b --- /dev/null +++ b/src/components/ui/TalkieDialogueOverlay.tsx @@ -0,0 +1,35 @@ +import { Suspense } from "react"; +import { Canvas } from "@react-three/fiber"; +import { TalkieModel } from "@/components/ui/talkie/TalkieModel"; +import { TalkieSignalLines } from "@/components/ui/talkie/TalkieSignalLines"; +import { useTalkieDialogueOverlayState } from "@/hooks/ui/useTalkieDialogueOverlayState"; + +export function TalkieDialogueOverlay(): React.JSX.Element | null { + const { isNarratorDialogue, isVisible } = useTalkieDialogueOverlayState(); + + if (!isVisible) return null; + + return ( + + ); +} diff --git a/src/components/ui/talkie/TalkieModel.tsx b/src/components/ui/talkie/TalkieModel.tsx new file mode 100644 index 0000000..c0ec8ba --- /dev/null +++ b/src/components/ui/talkie/TalkieModel.tsx @@ -0,0 +1,82 @@ +import { useEffect, useMemo, useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { useGLTF } from "@react-three/drei"; +import * as THREE from "three"; +import gsap from "gsap"; +import type { Vector3Tuple } from "@/types/three/three"; + +const TALKIE_MODEL_PATH = "/models/talkie/model.glb"; +const TALKIE_REST_Y = -1.55; +const TALKIE_ACTIVE_Y = -0.38; +const TALKIE_BASE_ROTATION: Vector3Tuple = [0.08, -0.52, -0.04]; +const TALKIE_FLOAT_ROTATION_AMPLITUDE = THREE.MathUtils.degToRad(2.2); +const TALKIE_FLOAT_Y_AMPLITUDE = 0.055; + +interface TalkieModelProps { + active: boolean; +} + +export function TalkieModel({ active }: TalkieModelProps): React.JSX.Element { + const { scene } = useGLTF(TALKIE_MODEL_PATH); + const model = useMemo(() => scene.clone(true), [scene]); + const groupRef = useRef(null); + const floatRef = useRef(null); + + useEffect(() => { + model.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.castShadow = false; + child.receiveShadow = false; + child.frustumCulled = false; + } + }); + }, [model]); + + useEffect(() => { + const group = groupRef.current; + if (!group) return; + + gsap.killTweensOf(group.position); + gsap.to(group.position, { + y: active ? TALKIE_ACTIVE_Y : TALKIE_REST_Y, + duration: active ? 0.72 : 0.5, + ease: active ? "power3.out" : "power2.out", + }); + + return () => { + gsap.killTweensOf(group.position); + }; + }, [active]); + + useFrame(({ clock }) => { + if (!floatRef.current) return; + + const t = clock.getElapsedTime(); + floatRef.current.position.y = Math.sin(t * 1.2) * TALKIE_FLOAT_Y_AMPLITUDE; + + floatRef.current.rotation.x = + TALKIE_BASE_ROTATION[0] + + Math.sin(t * 0.7) * TALKIE_FLOAT_ROTATION_AMPLITUDE; + floatRef.current.rotation.y = + TALKIE_BASE_ROTATION[1] + + Math.sin(t * 0.55) * TALKIE_FLOAT_ROTATION_AMPLITUDE; + floatRef.current.rotation.z = + TALKIE_BASE_ROTATION[2] + + Math.sin(t * 0.8) * TALKIE_FLOAT_ROTATION_AMPLITUDE; + }); + + return ( + + + + + + ); +} + +useGLTF.preload(TALKIE_MODEL_PATH); diff --git a/src/components/ui/talkie/TalkieSignalLines.tsx b/src/components/ui/talkie/TalkieSignalLines.tsx new file mode 100644 index 0000000..0f99179 --- /dev/null +++ b/src/components/ui/talkie/TalkieSignalLines.tsx @@ -0,0 +1,19 @@ +interface TalkieSignalLinesProps { + side: "left" | "right"; +} + +export function TalkieSignalLines({ + side, +}: TalkieSignalLinesProps): React.JSX.Element { + return ( + + ); +} diff --git a/src/data/debug/testSceneConfig.ts b/src/data/debug/testSceneConfig.ts index 2d7f49b..617fb3c 100644 --- a/src/data/debug/testSceneConfig.ts +++ b/src/data/debug/testSceneConfig.ts @@ -1,13 +1,15 @@ import type { Vector3Tuple } from "@/types/three/three"; import type { RepairMissionId } from "@/types/gameplay/repairMission"; +const DEG_TO_RAD = Math.PI / 180; + export const TEST_SCENE_FLOOR_POSITION: Vector3Tuple = [0, -0.5, 0]; export const TEST_SCENE_FLOOR_SIZE: Vector3Tuple = [200, 1, 200]; export const TEST_SCENE_FLOOR_COLLIDER_HALF_EXTENTS: Vector3Tuple = [ 100, 0.5, 100, ]; -export const TEST_SCENE_GRABBABLE_POSITION: Vector3Tuple = [0, 1, -3]; +export const TEST_SCENE_GRABBABLE_POSITION: Vector3Tuple = [0, 0.25, -3]; export const TEST_SCENE_GRABBABLE_BOX_SIZE: Vector3Tuple = [0.5, 0.5, 0.5]; export const TEST_SCENE_GRABBABLE_COLOR = "#e07b39"; export const TEST_SCENE_GRABBABLE_ROUGHNESS = 0.6; @@ -23,6 +25,12 @@ export const TEST_SCENE_TRIGGER_METALNESS = 0.5; export const TEST_SCENE_REPAIR_ZONE_MARKER_RADIUS = 1.65; export const TEST_SCENE_REPAIR_ZONE_MARKER_TUBE_RADIUS = 0.045; +export const TEST_SCENE_GPS_PREVIEW_POSITION: Vector3Tuple = [0, 5, -4.8]; +export const TEST_SCENE_GPS_PREVIEW_ROTATION: Vector3Tuple = [ + -33 * DEG_TO_RAD, + 0, + 0, +]; export const GAME_REPAIR_ZONES = [ { diff --git a/src/data/ebike/ebikeConfig.ts b/src/data/ebike/ebikeConfig.ts index 583afc1..577a7e6 100644 --- a/src/data/ebike/ebikeConfig.ts +++ b/src/data/ebike/ebikeConfig.ts @@ -6,22 +6,22 @@ export interface CameraTransform { } export const EBIKE_CAMERA_TRANSFORM: CameraTransform = { - position: [-3.5, 6, 0], + position: [-2.6, 4.5, 0], rotation: [-10, -90, 0], }; export const EBIKE_DROP_PLAYER_TRANSFORM: CameraTransform = { - position: [0, 1.5, -3], + position: [0, 1.3, -2.25], rotation: [0, 0, 0], }; -export const EBIKE_WORLD_POSITION: Vector3Tuple = [61.5, 10, 62.4]; -export const EBIKE_WORLD_ROTATION_Y = 2.4107; +export const EBIKE_WORLD_POSITION: Vector3Tuple = [65, 0.8, 72]; +export const EBIKE_WORLD_ROTATION_Y = -2.5; +export const EBIKE_WORLD_SCALE = 0.35; -export const EBIKE_INTRO_RIDE_DURATION_MS = 5000; +export const EBIKE_INTRO_BREAKDOWN_DISTANCE = 15; export const EBIKE_BREAKDOWN_DIALOGUE_DELAY_MS = 250; -export const EBIKE_MAX_SPEED = 3; export const EBIKE_ACCELERATION_DURATION_MS = 2000; export const EBIKE_DECELERATION_DURATION_MS = 2000; diff --git a/src/data/galleryModels.ts b/src/data/galleryModels.ts index 8ac0c36..3b38774 100644 --- a/src/data/galleryModels.ts +++ b/src/data/galleryModels.ts @@ -143,7 +143,7 @@ export const galleryModels: GalleryModel[] = [ }, { id: "sapin", name: "Sapin", path: "/models/sapin/model.gltf" }, { id: "skybox", name: "Skybox", path: "/models/skybox/skybox.gltf" }, - { id: "talkie", name: "Talkie", path: "/models/talkie/model.gltf" }, + { id: "talkie", name: "Talkie", path: "/models/talkie/model.glb" }, { id: "terrain", name: "Terrain", path: "/models/terrain/model.gltf" }, { id: "tuyauxlac", diff --git a/src/data/gameplay/missionNotifications.ts b/src/data/gameplay/missionNotifications.ts index 82caf13..afbfbf0 100644 --- a/src/data/gameplay/missionNotifications.ts +++ b/src/data/gameplay/missionNotifications.ts @@ -1,8 +1,11 @@ import type { RepairMissionId } from "@/types/gameplay/repairMission"; +export const INTRO_MISSION_NOTIFICATION_IMAGE_PATH = + "/assets/world/UI/intro-mission-notification.png"; + export const MISSION_NOTIFICATION_IMAGE_PATHS: Record = { - ebike: "/assets/world/UI/ebike-mission-notification.png", - pylon: "/assets/world/UI/pylon-mission-notification.png", - farm: "/assets/world/UI/farm-mission-notification.png", + ebike: "/assets/world/UI/ebike-mission-notification.webm", + pylon: "/assets/world/UI/pylon-mission-notification.webm", + farm: "/assets/world/UI/farm-mission-notification.webm", }; diff --git a/src/data/gameplay/repairMissions.ts b/src/data/gameplay/repairMissions.ts index c2f84b8..d63b877 100644 --- a/src/data/gameplay/repairMissions.ts +++ b/src/data/gameplay/repairMissions.ts @@ -21,7 +21,7 @@ export const REPAIR_MISSIONS: Record = { "Repair the damaged cooling module before relaunching the bike", modelPath: "/models/ebike/model.gltf", modelScale: 0.3, - stageUiPath: "/assets/world/UI/ebike.webm", + stageUiPath: "/assets/world/UI/ebike-mission-notification.webm", interactUiPath: REPAIR_INTERACT_UI_PATH, brokenUiPath: REPAIR_BROKEN_UI_PATH, case: DEFAULT_REPAIR_CASE, @@ -41,11 +41,6 @@ export const REPAIR_MISSIONS: Record = { label: "Replacement cooling core", modelPath: "/models/refroidisseur/model.gltf", }, - { - id: "ebike-radio-distractor", - label: "Radio module", - modelPath: "/models/talkie/model.gltf", - }, { id: "ebike-glove-distractor", label: "Insulation glove", @@ -59,7 +54,7 @@ export const REPAIR_MISSIONS: Record = { description: "Restore the pylon lamp relay and damaged panel before reconnecting the grid", modelPath: "/models/pylone/model.gltf", - stageUiPath: "/assets/world/UI/centrale.webm", + stageUiPath: "/assets/world/UI/pylon-mission-notification.webm", interactUiPath: REPAIR_INTERACT_UI_PATH, brokenUiPath: REPAIR_BROKEN_UI_PATH, case: DEFAULT_REPAIR_CASE, @@ -104,7 +99,7 @@ export const REPAIR_MISSIONS: Record = { description: "Stabilize the irrigation loop and humidity sensor before restarting the farm", modelPath: "/models/fermeverticale/model.gltf", - stageUiPath: "/assets/world/UI/laferme.webm", + stageUiPath: "/assets/world/UI/farm-mission-notification.webm", interactUiPath: REPAIR_INTERACT_UI_PATH, brokenUiPath: REPAIR_BROKEN_UI_PATH, case: DEFAULT_REPAIR_CASE, @@ -134,11 +129,6 @@ export const REPAIR_MISSIONS: Record = { label: "Tree sensor", modelPath: "/models/sapin/model.gltf", }, - { - id: "farm-radio-distractor", - label: "Radio module", - modelPath: "/models/talkie/model.gltf", - }, ], }, }; diff --git a/src/data/handTrackingConfig.ts b/src/data/handTrackingConfig.ts index 0b8c773..2b28dda 100644 --- a/src/data/handTrackingConfig.ts +++ b/src/data/handTrackingConfig.ts @@ -8,3 +8,9 @@ export const HAND_TRACKING_BROWSER_WASM_URL = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.35/wasm"; export const HAND_TRACKING_BROWSER_MODEL_URL = "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task"; +export const HAND_TRACKING_BROWSER_DELEGATE: "CPU" | "GPU" = "CPU"; + +// Delay before the runtime actually starts after `enabled` flips to true. +// Absorbs React StrictMode's mount/unmount/mount cycle in dev and rapid +// `nearby` toggles at trigger borders. Invisible to the user (~5 frames). +export const HAND_TRACKING_RUNTIME_START_DELAY_MS = 80; diff --git a/src/data/player/playerConfig.ts b/src/data/player/playerConfig.ts index 7ce0ca2..f2c556d 100644 --- a/src/data/player/playerConfig.ts +++ b/src/data/player/playerConfig.ts @@ -1,4 +1,5 @@ import type { Vector3Tuple } from "@/types/three/three"; +import { LA_FABRIK_PLAYER_SPAWN } from "@/data/world/laFabrikConfig"; export const PLAYER_EYE_HEIGHT = 1.75; export const PLAYER_CAPSULE_RADIUS = 0.35; @@ -14,5 +15,13 @@ export const PLAYER_XZ_DAMPING_FACTOR = 8; export const PLAYER_FALL_RESPAWN_Y = -20; export const PLAYER_FALL_RESPAWN_DELAY = 3; -export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [59.5, 10, 64.64]; -export const PLAYER_SPAWN_POSITION_PHYSICS: Vector3Tuple = [0, 3, 0]; +export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [ + LA_FABRIK_PLAYER_SPAWN[0] + 1, + LA_FABRIK_PLAYER_SPAWN[1], + LA_FABRIK_PLAYER_SPAWN[2] - 1, +]; +export const PLAYER_SPAWN_POSITION_PHYSICS: Vector3Tuple = [ + 0, + PLAYER_EYE_HEIGHT, + 0, +]; diff --git a/src/data/site/siteConfig.ts b/src/data/site/siteConfig.ts index b5c42d0..5b90e4b 100644 --- a/src/data/site/siteConfig.ts +++ b/src/data/site/siteConfig.ts @@ -1,6 +1,6 @@ import type { CSSProperties } from "react"; -const BACKGROUND_IMAGE = "/assets/bg-site.png"; +const BACKGROUND_IMAGE = "/assets/bg-site.webp"; export const SITE_CONFIG = { backgroundImage: BACKGROUND_IMAGE, diff --git a/src/data/world/cloudConfig.ts b/src/data/world/cloudConfig.ts index 07615c3..c86aa13 100644 --- a/src/data/world/cloudConfig.ts +++ b/src/data/world/cloudConfig.ts @@ -19,7 +19,7 @@ export const CLOUD_DEFAULTS = { maxRotation: Math.PI * 2, minSpeedMultiplier: 0.4, maxSpeedMultiplier: 1, - castShadow: false, + castShadow: true, receiveShadow: false, }; diff --git a/src/data/world/graphicsConfig.ts b/src/data/world/graphicsConfig.ts index de081fc..43dbd19 100644 --- a/src/data/world/graphicsConfig.ts +++ b/src/data/world/graphicsConfig.ts @@ -1,11 +1,16 @@ -import { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig"; - -export const GRAPHICS_PRESET_KEYS = ["low", "medium", "high", "ultra"] as const; +export const GRAPHICS_PRESET_KEYS = [ + "low", + "medium", + "high", + "ultra", + "max", +] as const; export type GraphicsPreset = (typeof GRAPHICS_PRESET_KEYS)[number]; export interface GraphicsPresetConfig { chunkLoadRadius: number; + chunkStreamingEnabled: boolean; chunkUnloadRadius: number; fogEnabled: boolean; forceLodModels: boolean; @@ -18,6 +23,7 @@ export const GRAPHICS_PRESETS = { label: "Basse", chunkLoadRadius: 10, chunkUnloadRadius: 18, + chunkStreamingEnabled: true, fogEnabled: true, forceLodModels: true, lodHighDetailDistance: 0, @@ -26,25 +32,37 @@ export const GRAPHICS_PRESETS = { label: "Moyenne", chunkLoadRadius: 20, chunkUnloadRadius: 30, + chunkStreamingEnabled: true, fogEnabled: true, forceLodModels: true, lodHighDetailDistance: 0, }, high: { label: "High", - chunkLoadRadius: CHUNK_CONFIG.loadRadius, - chunkUnloadRadius: CHUNK_CONFIG.unloadRadius, + chunkLoadRadius: 30, + chunkUnloadRadius: 40, + chunkStreamingEnabled: true, fogEnabled: false, forceLodModels: false, - lodHighDetailDistance: 10, + lodHighDetailDistance: 20, }, ultra: { label: "Ultra", chunkLoadRadius: 50, chunkUnloadRadius: 65, + chunkStreamingEnabled: true, fogEnabled: false, forceLodModels: false, - lodHighDetailDistance: 20, + lodHighDetailDistance: 30, + }, + max: { + label: "Max", + chunkLoadRadius: 50, + chunkUnloadRadius: 65, + chunkStreamingEnabled: false, + fogEnabled: false, + forceLodModels: false, + lodHighDetailDistance: 50, }, } as const satisfies Record; diff --git a/src/data/world/laFabrikConfig.ts b/src/data/world/laFabrikConfig.ts new file mode 100644 index 0000000..356116d --- /dev/null +++ b/src/data/world/laFabrikConfig.ts @@ -0,0 +1,29 @@ +import type { Vector3Tuple } from "@/types/three/three"; + +export const LA_FABRIK_CENTER: Vector3Tuple = [59.4973, 6.2746, 64.6354]; +export const LA_FABRIK_ROTATION_Y = 2.4107; +export const LA_FABRIK_HALF_EXTENTS = { + x: 8.5, + z: 7.5, +} as const; +export const LA_FABRIK_PLAYER_SPAWN: Vector3Tuple = [59.5, 6.3, 64.64]; +export const LA_FABRIK_INITIAL_LOOK_AT: Vector3Tuple = [58, 7.3, 62.5]; +export const LA_FABRIK_INTERIOR_LIGHT_POSITION: Vector3Tuple = [59.5, 9, 64.64]; + +export function isInsideLaFabrikFootprint( + x: number, + z: number, + padding = 0, +): boolean { + const dx = x - LA_FABRIK_CENTER[0]; + const dz = z - LA_FABRIK_CENTER[2]; + const cos = Math.cos(-LA_FABRIK_ROTATION_Y); + const sin = Math.sin(-LA_FABRIK_ROTATION_Y); + const localX = dx * cos - dz * sin; + const localZ = dx * sin + dz * cos; + + return ( + Math.abs(localX) <= LA_FABRIK_HALF_EXTENTS.x + padding && + Math.abs(localZ) <= LA_FABRIK_HALF_EXTENTS.z + padding + ); +} diff --git a/src/data/world/lightingConfig.ts b/src/data/world/lightingConfig.ts index a61c06a..58a3d00 100644 --- a/src/data/world/lightingConfig.ts +++ b/src/data/world/lightingConfig.ts @@ -3,9 +3,9 @@ const SUN_LIGHT_COLOR = "#ffe2bf"; export const LIGHTING_DEFAULTS = { ambientColor: AMBIENT_LIGHT_COLOR, - ambientIntensity: 0.9, + ambientIntensity: 0.7, sunColor: SUN_LIGHT_COLOR, - sunIntensity: 2.2, + sunIntensity: 1.9, sunX: 70, sunY: 45, sunZ: 35, @@ -30,3 +30,12 @@ export const SUN_Y_STEP = 1; export const SUN_Z_MIN = -100; export const SUN_Z_MAX = 100; export const SUN_Z_STEP = 1; + +export const SHADOW_CONFIG = { + mapSize: 2048, + cameraSize: 95, + cameraNear: 0.5, + cameraFar: 300, + bias: 0, + normalBias: 0, +} as const; diff --git a/src/data/world/mapLodConfig.ts b/src/data/world/mapLodConfig.ts index 2bea581..91ff213 100644 --- a/src/data/world/mapLodConfig.ts +++ b/src/data/world/mapLodConfig.ts @@ -13,7 +13,9 @@ export const MAP_LOD_MODEL_PATHS = { lafabrik: "/models/lafabrik-LOD/model.gltf", maison1: "/models/maison1-LOD/model.gltf", panneauaffichage: "/models/panneauaffichage-LOD/model.gltf", - talkie: "/models/talkie-LOD/model.gltf", + arbre: "/models/arbre-LOD/model.glb", + buisson: "/models/buisson-LOD/model.glb", + sapin: "/models/sapin-LOD/model.glb", } as const satisfies Record; export function getMapLodModelPath(modelName: string): string | null { @@ -22,6 +24,19 @@ export function getMapLodModelPath(modelName: string): string | null { ); } +export const MAP_LOD_SCALE_MULTIPLIERS = { + sapin: 0.35, + buisson: 0.7, +} as const satisfies Partial>; + +export function getMapLodScaleMultiplier(modelName: string): number { + return ( + MAP_LOD_SCALE_MULTIPLIERS[ + modelName as keyof typeof MAP_LOD_SCALE_MULTIPLIERS + ] ?? 1 + ); +} + export function selectMapModelPathByDistance({ distance, modelName, diff --git a/src/data/world/octreeCollisionConfig.ts b/src/data/world/octreeCollisionConfig.ts new file mode 100644 index 0000000..3af672a --- /dev/null +++ b/src/data/world/octreeCollisionConfig.ts @@ -0,0 +1,61 @@ +import type { Vector3Tuple } from "@/types/three/three"; + +export interface OctreeCollisionBox { + center: Vector3Tuple; + size: Vector3Tuple; +} + +export interface MapOctreeCollisionBox extends OctreeCollisionBox { + bottomY: number; +} + +export const MAP_OCTREE_COLLISION_BOXES = { + immeuble1: { + center: [-0.0308, 5.8389, 0], + size: [17.2522, 11.6098, 9.2668], + bottomY: 0.034, + }, + maison1: { + center: [0, 1.3638, 0.0536], + size: [2.7813, 3.022, 2.8609], + bottomY: -0.1472, + }, +} as const satisfies Record; + +export const LA_FABRIK_INTERIOR_COLLISION_BOXES = [ + // NOTE: removed — this thin wall (size [0.2, 1.94, 3.71]) sat at x≈-6.93 and + // sealed the doorway despite the geometry having a hole there. The fabrik + // mesh octree already provides the surrounding wall collision, so this + // proxy was both redundant and bug-causing. + // { + // center: [-6.9351, 2.278, -0.0001], + // size: [0.2, 1.94, 3.711], + // }, + { + center: [0.8026, 0.719, -3.639], + size: [4.346, 1.108, 1.181], + }, + { + center: [-5.8519, 0.9362, 2.5742], + size: [1.67, 1.551, 2.566], + }, + { + center: [-2.0627, 1.4875, -1.2243], + size: [0.691, 0.723, 0.687], + }, + { + center: [-3.5502, 1.4378, -1.2485], + size: [1.055, 0.657, 0.563], + }, +] as const satisfies readonly OctreeCollisionBox[]; + +export const CHARACTER_OCTREE_COLLISION_BOX = { + center: [0, 0.875, 0], + size: [0.62, 1.75, 0.62], +} as const satisfies OctreeCollisionBox; + +export function hasMapOctreeCollisionBox( + name: string, +): name is keyof typeof MAP_OCTREE_COLLISION_BOXES { + return name in MAP_OCTREE_COLLISION_BOXES; +} diff --git a/src/hooks/debug/useDebugVisualsDebug.ts b/src/hooks/debug/useDebugVisualsDebug.ts new file mode 100644 index 0000000..be1538c --- /dev/null +++ b/src/hooks/debug/useDebugVisualsDebug.ts @@ -0,0 +1,66 @@ +import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; +import { useDebugVisualsStore } from "@/managers/stores/useDebugVisualsStore"; + +export function useDebugVisualsDebug(): void { + useDebugFolder("Debug", (folder) => { + const state = useDebugVisualsStore.getState(); + const controls = { + showPlayerModel: state.showPlayerModel, + showOctree: state.showOctree, + octreeMinDepth: state.octreeMinDepth, + octreeMaxDepth: state.octreeMaxDepth, + octreeLeavesOnly: state.octreeLeavesOnly, + octreeOpacity: state.octreeOpacity, + octreeFabrikOnly: state.octreeFabrikOnly, + }; + + folder + .add(controls, "showPlayerModel") + .name("Show Player Model") + .onChange((value: boolean) => { + useDebugVisualsStore.getState().setShowPlayerModel(value); + }); + + folder + .add(controls, "showOctree") + .name("Show Octree") + .onChange((value: boolean) => { + useDebugVisualsStore.getState().setShowOctree(value); + }); + + folder + .add(controls, "octreeLeavesOnly") + .name("Octree Leaves Only") + .onChange((value: boolean) => { + useDebugVisualsStore.getState().setOctreeLeavesOnly(value); + }); + + folder + .add(controls, "octreeMinDepth", 0, 10, 1) + .name("Octree Min Depth") + .onChange((value: number) => { + useDebugVisualsStore.getState().setOctreeMinDepth(value); + }); + + folder + .add(controls, "octreeMaxDepth", 0, 10, 1) + .name("Octree Max Depth") + .onChange((value: number) => { + useDebugVisualsStore.getState().setOctreeMaxDepth(value); + }); + + folder + .add(controls, "octreeOpacity", 0.05, 1, 0.05) + .name("Octree Opacity") + .onChange((value: number) => { + useDebugVisualsStore.getState().setOctreeOpacity(value); + }); + + folder + .add(controls, "octreeFabrikOnly") + .name("Octree Fabrik Only") + .onChange((value: boolean) => { + useDebugVisualsStore.getState().setOctreeFabrikOnly(value); + }); + }); +} diff --git a/src/hooks/handTracking/useBrowserHandTracking.ts b/src/hooks/handTracking/useBrowserHandTracking.ts index 73064fa..285fa2f 100644 --- a/src/hooks/handTracking/useBrowserHandTracking.ts +++ b/src/hooks/handTracking/useBrowserHandTracking.ts @@ -2,17 +2,20 @@ import { useEffect, useRef, useState } from "react"; import { HAND_TRACKING_FRAME_HEIGHT, HAND_TRACKING_FRAME_WIDTH, + HAND_TRACKING_RUNTIME_START_DELAY_MS, HAND_TRACKING_TARGET_FPS, } from "@/data/handTrackingConfig"; import { convertBrowserHandResult, getBrowserHandLandmarker, + releaseBrowserHandLandmarker, } from "@/lib/handTracking/browserHandTracking"; import { INITIAL_HAND_TRACKING_SNAPSHOT, getCameraStreamWithTimeout, } from "@/lib/handTracking/handTrackingSession"; import type { HandTrackingSnapshot } from "@/types/handTracking/handTracking"; +import { logger } from "@/utils/core/Logger"; interface UseBrowserHandTrackingOptions { enabled: boolean; @@ -34,8 +37,12 @@ export function useBrowserHandTracking({ } let cancelled = false; + let cleanedUp = false; const cleanup = (): void => { + if (cleanedUp) return; + cleanedUp = true; + if (intervalRef.current !== null) { window.clearInterval(intervalRef.current); intervalRef.current = null; @@ -44,6 +51,7 @@ export function useBrowserHandTracking({ streamRef.current?.getTracks().forEach((track) => track.stop()); streamRef.current = null; videoRef.current = null; + releaseBrowserHandLandmarker(); }; const start = async (): Promise => { @@ -111,24 +119,44 @@ export function useBrowserHandTracking({ intervalRef.current = window.setInterval(() => { if (video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) return; - const result = handLandmarker.detectForVideo( - video, - performance.now(), - ); - const hands = convertBrowserHandResult(result); + try { + const result = handLandmarker.detectForVideo( + video, + performance.now(), + ); + const hands = convertBrowserHandResult(result); - setSnapshot((current) => ({ - ...current, - hands, - usageStatus: hands.some((hand) => hand.isFist) - ? "active" - : "available", - error: null, - })); + setSnapshot((current) => ({ + ...current, + hands, + usageStatus: hands.some((hand) => hand.isFist) + ? "active" + : "available", + error: null, + })); + } catch (error) { + logger.error("HandTracking", "Browser JS runtime error", { + error: error instanceof Error ? error.message : String(error), + }); + cleanup(); + setSnapshot({ + hands: [], + status: "error", + usageStatus: "inactive", + serverStatus: "Browser JS", + error: + error instanceof Error + ? error.message + : "Browser hand tracking failed", + }); + } }, 1_000 / HAND_TRACKING_TARGET_FPS); } catch (error) { if (cancelled) return; + logger.error("HandTracking", "Browser JS runtime failed", { + error: error instanceof Error ? error.message : String(error), + }); setSnapshot({ hands: [], status: "error", @@ -142,10 +170,17 @@ export function useBrowserHandTracking({ } }; - void start(); + // Delay the actual start so that a StrictMode mount/unmount/mount + // cycle, or a rapid `enabled` toggle at a trigger border, does not + // spin up the camera + MediaPipe twice in a few milliseconds. + const startTimer = window.setTimeout(() => { + if (cancelled) return; + void start(); + }, HAND_TRACKING_RUNTIME_START_DELAY_MS); return () => { cancelled = true; + window.clearTimeout(startTimer); cleanup(); }; }, [enabled]); diff --git a/src/hooks/handTracking/useRemoteHandTracking.ts b/src/hooks/handTracking/useRemoteHandTracking.ts index f53236d..fafa568 100644 --- a/src/hooks/handTracking/useRemoteHandTracking.ts +++ b/src/hooks/handTracking/useRemoteHandTracking.ts @@ -4,6 +4,7 @@ import { HAND_TRACKING_FRAME_WIDTH, HAND_TRACKING_JPEG_QUALITY, HAND_TRACKING_RESPONSE_TIMEOUT_MS, + HAND_TRACKING_RUNTIME_START_DELAY_MS, HAND_TRACKING_TARGET_FPS, } from "@/data/handTrackingConfig"; import { getHandTrackingWsUrl } from "@/utils/handTracking/handTrackingEndpoint"; @@ -17,6 +18,7 @@ import type { HandTrackingServerMessage, HandTrackingSnapshot, } from "@/types/handTracking/handTracking"; +import { logger } from "@/utils/core/Logger"; interface UseRemoteHandTrackingOptions { enabled: boolean; @@ -100,6 +102,7 @@ export function useRemoteHandTracking({ } let cancelled = false; + let cleanedUp = false; const clearResponseTimeout = (): void => { if (responseTimeoutRef.current === null) return; @@ -108,6 +111,9 @@ export function useRemoteHandTracking({ }; const cleanup = (): void => { + if (cleanedUp) return; + cleanedUp = true; + if (sendIntervalRef.current !== null) { window.clearInterval(sendIntervalRef.current); sendIntervalRef.current = null; @@ -283,6 +289,9 @@ export function useRemoteHandTracking({ }; ws.onerror = () => { markResponseReceived(); + logger.error("HandTracking", "Backend WebSocket error", { + websocketUrl, + }); setSnapshot((current) => ({ ...current, status: "error", @@ -307,6 +316,10 @@ export function useRemoteHandTracking({ ); } catch (error) { if (cancelled) return; + logger.error("HandTracking", "Backend runtime failed", { + error: error instanceof Error ? error.message : String(error), + websocketUrl, + }); setSnapshot({ hands: [], status: "error", @@ -318,10 +331,17 @@ export function useRemoteHandTracking({ } }; - void start(); + // Delay the actual start so that a StrictMode mount/unmount/mount + // cycle, or a rapid `enabled` toggle at a trigger border, does not + // open the camera + WebSocket twice in a few milliseconds. + const startTimer = window.setTimeout(() => { + if (cancelled) return; + void start(); + }, HAND_TRACKING_RUNTIME_START_DELAY_MS); return () => { cancelled = true; + window.clearTimeout(startTimer); cleanup(); }; }, [enabled, websocketUrl]); diff --git a/src/hooks/three/useOctreeGraphNode.ts b/src/hooks/three/useOctreeGraphNode.ts index f6a0d0a..994ba0b 100644 --- a/src/hooks/three/useOctreeGraphNode.ts +++ b/src/hooks/three/useOctreeGraphNode.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from "react"; import type { RefObject } from "react"; -import type { Object3D } from "three"; +import { type Object3D } from "three"; import { Octree } from "three-stdlib"; import type { OctreeReadyHandler } from "@/types/three/three"; @@ -27,6 +27,7 @@ export function useOctreeGraphNode( const octree = new Octree(); octree.fromGraphNode(graphNode); + onOctreeReady(octree); }, [enabled, graphNodeRef, onOctreeReady, rebuildKey]); } diff --git a/src/hooks/three/useShadowMapWarmup.ts b/src/hooks/three/useShadowMapWarmup.ts new file mode 100644 index 0000000..5d9b16e --- /dev/null +++ b/src/hooks/three/useShadowMapWarmup.ts @@ -0,0 +1,105 @@ +import { useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { + Material, + Mesh, + type DirectionalLight, + type Scene, + type WebGLRenderer, +} from "three"; + +interface UseShadowMapWarmupOptions { + /** Light whose shadow map should be reallocated once the scene stabilizes. */ + light: React.RefObject; + scene: Scene; + gl: WebGLRenderer; + invalidate: () => void; + /** Frames the mesh count must remain unchanged to consider the scene stable. */ + stableFramesThreshold?: number; + /** Hard cap on how long we keep watching, in frames (~5s @60fps). */ + safetyCapFrames?: number; + /** Sample mesh count every N frames to keep the traversal cost negligible. */ + sampleEveryFrames?: number; +} + +export function useShadowMapWarmup({ + light, + scene, + gl, + invalidate, + stableFramesThreshold = 60, + safetyCapFrames = 300, + sampleEveryFrames = 6, +}: UseShadowMapWarmupOptions): void { + const meshCountRef = useRef(0); + const stableFramesRef = useRef(0); + const watchFramesRef = useRef(0); + const doneRef = useRef(false); + + useFrame(() => { + if (doneRef.current || !light.current) return; + + watchFramesRef.current += 1; + + if (watchFramesRef.current % sampleEveryFrames === 0) { + let meshCount = 0; + scene.traverse((object) => { + if (object instanceof Mesh) meshCount += 1; + }); + + if (meshCount !== meshCountRef.current) { + meshCountRef.current = meshCount; + stableFramesRef.current = 0; + } else { + stableFramesRef.current += sampleEveryFrames; + } + } + + const stableEnough = stableFramesRef.current >= stableFramesThreshold; + const safetyCapReached = watchFramesRef.current >= safetyCapFrames; + if (!stableEnough && !safetyCapReached) return; + + doneRef.current = true; + reallocateShadowMap(light.current); + invalidateAllMaterials(scene); + forceShadowPass(gl, scene, light.current); + invalidate(); + }); +} + +function reallocateShadowMap(light: DirectionalLight): void { + const shadowMap = light.shadow.map; + if (!shadowMap) return; + + shadowMap.dispose(); + light.shadow.map = null; +} + +function invalidateAllMaterials(scene: Scene): void { + const seen = new Set(); + scene.traverse((object) => { + if (!(object instanceof Mesh)) return; + const materials = Array.isArray(object.material) + ? object.material + : [object.material]; + for (const material of materials) { + if (!material || seen.has(material)) continue; + seen.add(material); + material.needsUpdate = true; + } + }); +} + +function forceShadowPass( + gl: WebGLRenderer, + scene: Scene, + light: DirectionalLight, +): void { + scene.updateMatrixWorld(true); + light.target.updateMatrixWorld(true); + light.updateMatrixWorld(true); + light.shadow.camera.updateMatrixWorld(true); + light.shadow.camera.updateProjectionMatrix(); + light.shadow.needsUpdate = true; + gl.shadowMap.needsUpdate = true; +} diff --git a/src/hooks/ui/useTalkieDialogueOverlayState.ts b/src/hooks/ui/useTalkieDialogueOverlayState.ts new file mode 100644 index 0000000..b1eb3c4 --- /dev/null +++ b/src/hooks/ui/useTalkieDialogueOverlayState.ts @@ -0,0 +1,27 @@ +import { GAME_STEPS } from "@/data/game/gameStateConfig"; +import { useGameStore } from "@/managers/stores/useGameStore"; +import { useSubtitleStore } from "@/managers/stores/useSubtitleStore"; + +const TALKIE_FIRST_VISIBLE_STEP = "reveal"; +const TALKIE_FIRST_VISIBLE_STEP_INDEX = GAME_STEPS.indexOf( + TALKIE_FIRST_VISIBLE_STEP, +); + +interface TalkieDialogueOverlayState { + isNarratorDialogue: boolean; + isVisible: boolean; +} + +export function useTalkieDialogueOverlayState(): TalkieDialogueOverlayState { + const activeSubtitle = useSubtitleStore((state) => state.activeSubtitle); + const mainState = useGameStore((state) => state.mainState); + const introStep = useGameStore((state) => state.intro.currentStep); + const introStepIndex = GAME_STEPS.indexOf(introStep); + + return { + isNarratorDialogue: activeSubtitle?.speaker === "Narrateur", + isVisible: + mainState !== "intro" || + introStepIndex >= TALKIE_FIRST_VISIBLE_STEP_INDEX, + }; +} diff --git a/src/hooks/world/useWorldSceneLoading.ts b/src/hooks/world/useWorldSceneLoading.ts index c629fa7..e82e92b 100644 --- a/src/hooks/world/useWorldSceneLoading.ts +++ b/src/hooks/world/useWorldSceneLoading.ts @@ -11,13 +11,10 @@ interface UseWorldSceneLoadingOptions { interface UseWorldSceneLoadingResult { octree: Octree | null; gameplayReady: boolean; - shouldWarmUpShadows: boolean; showGameStage: boolean; handleGameStageLoaded: () => void; handleGameMapLoaded: () => void; handleOctreeReady: (octree: Octree) => void; - handleShadowWarmupReady: () => void; - handleShadowWarmupStarted: () => void; } export function useWorldSceneLoading({ @@ -27,19 +24,13 @@ export function useWorldSceneLoading({ const [octree, setOctree] = useState(null); const [gameMapLoaded, setGameMapLoaded] = useState(false); const [gameStageLoaded, setGameStageLoaded] = useState(false); - const [shadowsReady, setShadowsReady] = useState(false); const showGameStage = sceneMode === "game" && gameMapLoaded; - const gameSceneReadyForShadows = - showGameStage && gameStageLoaded && octree !== null; - const shadowWarmupReady = sceneMode === "game" && gameSceneReadyForShadows; - const shouldWarmUpShadows = shadowWarmupReady && !shadowsReady; - const gameplayReady = gameSceneReadyForShadows && shadowsReady; + const gameplayReady = showGameStage && gameStageLoaded && octree !== null; const sceneReady = (sceneMode === "game" && gameplayReady) || (sceneMode === "physics" && octree !== null); const handleGameMapLoaded = useCallback(() => { - setShadowsReady(false); setGameMapLoaded(true); }, []); @@ -54,7 +45,6 @@ export function useWorldSceneLoading({ const handleOctreeReady = useCallback( (nextOctree: Octree) => { - setShadowsReady(false); setOctree(nextOctree); onLoadingStateChange?.({ currentStep: "Collision prête", @@ -65,23 +55,6 @@ export function useWorldSceneLoading({ [onLoadingStateChange], ); - const handleShadowWarmupStarted = useCallback(() => { - onLoadingStateChange?.({ - currentStep: "Activation des ombres", - progress: 0.97, - status: "loading", - }); - }, [onLoadingStateChange]); - - const handleShadowWarmupReady = useCallback(() => { - setShadowsReady(true); - onLoadingStateChange?.({ - currentStep: "Ombres prêtes", - progress: 0.99, - status: "loading", - }); - }, [onLoadingStateChange]); - useEffect(() => { onLoadingStateChange?.({ currentStep: "Initialisation du jeu", @@ -115,12 +88,9 @@ export function useWorldSceneLoading({ return { octree, gameplayReady, - shouldWarmUpShadows, showGameStage, handleGameStageLoaded, handleGameMapLoaded, handleOctreeReady, - handleShadowWarmupReady, - handleShadowWarmupStarted, }; } diff --git a/src/index.css b/src/index.css index 0aed3d3..5a47469 100644 --- a/src/index.css +++ b/src/index.css @@ -942,11 +942,11 @@ canvas { .scene-loading-overlay__logo { position: relative; z-index: 1; + display: block; width: clamp(180px, 28vw, 320px); max-height: min(38vh, 320px); - border-radius: 16px; - object-fit: cover; - box-shadow: 0 28px 80px rgba(0, 0, 0, 0.28); + height: auto; + object-fit: contain; } .scene-loading-overlay__footer { @@ -1237,6 +1237,114 @@ canvas { color: #f9a8d4; } +/* Dialogue talkie */ +.talkie-dialogue-overlay { + position: fixed; + left: 0; + bottom: 0; + z-index: 16; + width: clamp(190px, 18vw, 300px); + aspect-ratio: 1; + pointer-events: none; +} + +.talkie-dialogue-overlay__model-frame { + position: absolute; + inset: 0; + filter: drop-shadow(0 16px 22px rgba(0, 0, 0, 0.55)); +} + +.talkie-dialogue-overlay--active .talkie-dialogue-overlay__model-frame { + animation: talkie-radio-shake 1s ease-in-out infinite; +} + +.talkie-dialogue-overlay__model-frame canvas { + width: 100% !important; + height: 100% !important; +} + +.talkie-dialogue-overlay__signals { + position: absolute; + top: 52%; + z-index: 2; + width: 38%; + height: 52%; + overflow: visible; + opacity: 0.8; + animation: talkie-signal-pulse 1s ease-in-out infinite; +} + +.talkie-dialogue-overlay__signals--left { + right: 62%; + transform: translateY(-50%) scaleX(-1); +} + +.talkie-dialogue-overlay__signals--right { + left: 62%; + transform: translateY(-50%); +} + +.talkie-dialogue-overlay__signals path { + fill: none; + stroke: rgba(235, 244, 255, 0.9); + stroke-linecap: round; + stroke-width: 5; + filter: drop-shadow(0 0 7px rgba(125, 211, 252, 0.72)); +} + +.talkie-dialogue-overlay__signals path:nth-child(2) { + animation-delay: 90ms; + opacity: 0.75; +} + +.talkie-dialogue-overlay__signals path:nth-child(3) { + animation-delay: 180ms; + opacity: 0.55; +} + +@keyframes talkie-radio-shake { + 0%, + 11%, + 23%, + 100% { + transform: translate3d(0, 0, 0) rotate(0deg); + } + + 3%, + 15%, + 27% { + transform: translate3d(-2px, 1px, 0) rotate(-1.7deg); + } + + 6%, + 18%, + 30% { + transform: translate3d(2px, -1px, 0) rotate(1.7deg); + } + + 9%, + 21%, + 33% { + transform: translate3d(-1px, 0, 0) rotate(-0.8deg); + } +} + +@keyframes talkie-signal-pulse { + 0%, + 100% { + opacity: 0.28; + } + + 18%, + 38% { + opacity: 0.95; + } + + 60% { + opacity: 0.45; + } +} + /* In-game settings menu */ .game-settings-menu { position: fixed; @@ -1436,7 +1544,7 @@ canvas { } .game-settings-menu__choice-group--presets { - grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-columns: repeat(5, minmax(0, 1fr)); } .game-settings-menu__choice-group button, diff --git a/src/lib/handTracking/browserHandTracking.ts b/src/lib/handTracking/browserHandTracking.ts index 06b80b4..5daf76e 100644 --- a/src/lib/handTracking/browserHandTracking.ts +++ b/src/lib/handTracking/browserHandTracking.ts @@ -1,4 +1,5 @@ import { + HAND_TRACKING_BROWSER_DELEGATE, HAND_TRACKING_BROWSER_MODEL_URL, HAND_TRACKING_BROWSER_WASM_URL, } from "@/data/handTrackingConfig"; @@ -6,6 +7,7 @@ import type { HandTrackingHand, HandTrackingLandmark, } from "@/types/handTracking/handTracking"; +import { logger } from "@/utils/core/Logger"; type HandLandmarkerModule = typeof import("@mediapipe/tasks-vision"); type HandLandmarker = Awaited< @@ -14,6 +16,7 @@ type HandLandmarker = Awaited< type HandLandmarkerResult = ReturnType; let handLandmarkerPromise: Promise | null = null; +let handLandmarkerInstance: HandLandmarker | null = null; function averageLandmarks( landmarks: HandTrackingLandmark[], @@ -78,20 +81,46 @@ export async function getBrowserHandLandmarker(): Promise { HAND_TRACKING_BROWSER_WASM_URL, ); - return HandLandmarker.createFromOptions(vision, { + const handLandmarker = await HandLandmarker.createFromOptions(vision, { baseOptions: { modelAssetPath: HAND_TRACKING_BROWSER_MODEL_URL, - delegate: "GPU", + delegate: HAND_TRACKING_BROWSER_DELEGATE, }, numHands: 2, runningMode: "VIDEO", }); + + handLandmarkerInstance = handLandmarker; + return handLandmarker; }, ); return handLandmarkerPromise; } +export function releaseBrowserHandLandmarker(): void { + const activeLandmarker = handLandmarkerInstance; + const pendingLandmarker = handLandmarkerPromise; + + handLandmarkerInstance = null; + handLandmarkerPromise = null; + + if (activeLandmarker) { + activeLandmarker.close(); + return; + } + + void pendingLandmarker + ?.then((landmarker) => { + landmarker.close(); + }) + .catch((error: unknown) => { + logger.warn("HandTracking", "Browser JS landmarker release failed", { + error: error instanceof Error ? error.message : String(error), + }); + }); +} + export function convertBrowserHandResult( result: HandLandmarkerResult, ): HandTrackingHand[] { diff --git a/src/managers/stores/useDebugVisualsStore.ts b/src/managers/stores/useDebugVisualsStore.ts new file mode 100644 index 0000000..030e5c2 --- /dev/null +++ b/src/managers/stores/useDebugVisualsStore.ts @@ -0,0 +1,35 @@ +import { create } from "zustand"; + +interface DebugVisualsStore { + showPlayerModel: boolean; + setShowPlayerModel: (value: boolean) => void; + showOctree: boolean; + setShowOctree: (value: boolean) => void; + octreeMaxDepth: number; + setOctreeMaxDepth: (value: number) => void; + octreeMinDepth: number; + setOctreeMinDepth: (value: number) => void; + octreeLeavesOnly: boolean; + setOctreeLeavesOnly: (value: boolean) => void; + octreeOpacity: number; + setOctreeOpacity: (value: number) => void; + octreeFabrikOnly: boolean; + setOctreeFabrikOnly: (value: boolean) => void; +} + +export const useDebugVisualsStore = create((set) => ({ + showPlayerModel: false, + setShowPlayerModel: (showPlayerModel) => set({ showPlayerModel }), + showOctree: false, + setShowOctree: (showOctree) => set({ showOctree }), + octreeMaxDepth: 8, + setOctreeMaxDepth: (octreeMaxDepth) => set({ octreeMaxDepth }), + octreeMinDepth: 4, + setOctreeMinDepth: (octreeMinDepth) => set({ octreeMinDepth }), + octreeLeavesOnly: true, + setOctreeLeavesOnly: (octreeLeavesOnly) => set({ octreeLeavesOnly }), + octreeOpacity: 0.35, + setOctreeOpacity: (octreeOpacity) => set({ octreeOpacity }), + octreeFabrikOnly: false, + setOctreeFabrikOnly: (octreeFabrikOnly) => set({ octreeFabrikOnly }), +})); diff --git a/src/managers/stores/useSettingsStore.ts b/src/managers/stores/useSettingsStore.ts index 5bafec8..c46a147 100644 --- a/src/managers/stores/useSettingsStore.ts +++ b/src/managers/stores/useSettingsStore.ts @@ -1,4 +1,5 @@ import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; import { AudioManager } from "@/managers/AudioManager"; import type { AudioCategory } from "@/managers/AudioManager"; import type { SubtitleLanguage } from "@/types/settings/settings"; @@ -33,6 +34,8 @@ const DEFAULT_SETTINGS: SettingsState = { subtitleLanguage: "fr", }; +const SETTINGS_STORAGE_KEY = "la-fabrik-settings"; + function clampVolume(volume: number): number { return Math.max(0, Math.min(1, volume)); } @@ -46,36 +49,50 @@ function setAudioCategoryVolume( return nextVolume; } -function applyDefaultAudioSettings(): void { - AudioManager.getInstance().setCategoryVolume( - "music", - DEFAULT_SETTINGS.musicVolume, - ); - AudioManager.getInstance().setCategoryVolume( - "sfx", - DEFAULT_SETTINGS.sfxVolume, - ); +function applyAudioSettings( + settings: Pick, +): void { + AudioManager.getInstance().setCategoryVolume("music", settings.musicVolume); + AudioManager.getInstance().setCategoryVolume("sfx", settings.sfxVolume); AudioManager.getInstance().setCategoryVolume( "dialogue", - DEFAULT_SETTINGS.dialogueVolume, + settings.dialogueVolume, ); } -applyDefaultAudioSettings(); +applyAudioSettings(DEFAULT_SETTINGS); -export const useSettingsStore = create()((set) => ({ - ...DEFAULT_SETTINGS, - setSettingsMenuOpen: (isSettingsMenuOpen) => set({ isSettingsMenuOpen }), - setMusicVolume: (volume) => - set({ musicVolume: setAudioCategoryVolume("music", volume) }), - setSfxVolume: (volume) => - set({ sfxVolume: setAudioCategoryVolume("sfx", volume) }), - setDialogueVolume: (volume) => - set({ dialogueVolume: setAudioCategoryVolume("dialogue", volume) }), - setSubtitlesEnabled: (subtitlesEnabled) => set({ subtitlesEnabled }), - setSubtitleLanguage: (subtitleLanguage) => set({ subtitleLanguage }), - resetSettings: () => { - applyDefaultAudioSettings(); - set(DEFAULT_SETTINGS); - }, -})); +export const useSettingsStore = create()( + persist( + (set) => ({ + ...DEFAULT_SETTINGS, + setSettingsMenuOpen: (isSettingsMenuOpen) => set({ isSettingsMenuOpen }), + setMusicVolume: (volume) => + set({ musicVolume: setAudioCategoryVolume("music", volume) }), + setSfxVolume: (volume) => + set({ sfxVolume: setAudioCategoryVolume("sfx", volume) }), + setDialogueVolume: (volume) => + set({ dialogueVolume: setAudioCategoryVolume("dialogue", volume) }), + setSubtitlesEnabled: (subtitlesEnabled) => set({ subtitlesEnabled }), + setSubtitleLanguage: (subtitleLanguage) => set({ subtitleLanguage }), + resetSettings: () => { + applyAudioSettings(DEFAULT_SETTINGS); + set(DEFAULT_SETTINGS); + }, + }), + { + name: SETTINGS_STORAGE_KEY, + storage: createJSONStorage(() => window.localStorage), + partialize: (state) => ({ + dialogueVolume: state.dialogueVolume, + musicVolume: state.musicVolume, + sfxVolume: state.sfxVolume, + subtitleLanguage: state.subtitleLanguage, + subtitlesEnabled: state.subtitlesEnabled, + }), + onRehydrateStorage: () => (state) => { + if (state) applyAudioSettings(state); + }, + }, + ), +); diff --git a/src/managers/stores/useWorldSettingsStore.ts b/src/managers/stores/useWorldSettingsStore.ts index db1d71a..bdf6ad5 100644 --- a/src/managers/stores/useWorldSettingsStore.ts +++ b/src/managers/stores/useWorldSettingsStore.ts @@ -1,4 +1,5 @@ import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; import { CLOUD_DEFAULTS, type CloudState } from "@/data/world/cloudConfig"; import { FOG_CONFIG, type FogState } from "@/data/world/fogConfig"; import { WIND_DEFAULTS, type WindState } from "@/data/world/windConfig"; @@ -46,73 +47,89 @@ const DEFAULT_STATE: WorldSettingsState = { graphics: { ...GRAPHICS_DEFAULTS }, }; -export const useWorldSettingsStore = create()((set) => ({ - ...DEFAULT_STATE, +const WORLD_SETTINGS_STORAGE_KEY = "la-fabrik-world-settings"; - setClouds: (cloudsUpdate) => - set((state) => ({ - clouds: { ...state.clouds, ...cloudsUpdate }, - })), +export const useWorldSettingsStore = create()( + persist( + (set) => ({ + ...DEFAULT_STATE, - setFog: (fogUpdate) => - set((state) => ({ - fog: { ...state.fog, ...fogUpdate }, - })), + setClouds: (cloudsUpdate) => + set((state) => ({ + clouds: { ...state.clouds, ...cloudsUpdate }, + })), - setWind: (windUpdate) => - set((state) => ({ - wind: { ...state.wind, ...windUpdate }, - })), + setFog: (fogUpdate) => + set((state) => ({ + fog: { ...state.fog, ...fogUpdate }, + })), - setWindSpeed: (speed) => - set((state) => ({ - wind: { ...state.wind, speed }, - })), + setWind: (windUpdate) => + set((state) => ({ + wind: { ...state.wind, ...windUpdate }, + })), - setWindDirection: (direction) => - set((state) => ({ - wind: { ...state.wind, direction }, - })), + setWindSpeed: (speed) => + set((state) => ({ + wind: { ...state.wind, speed }, + })), - setWindStrength: (strength) => - set((state) => ({ - wind: { ...state.wind, strength }, - })), + setWindDirection: (direction) => + set((state) => ({ + wind: { ...state.wind, direction }, + })), - setGraphics: (graphicsUpdate) => - set((state) => ({ - graphics: { ...state.graphics, ...graphicsUpdate }, - })), + setWindStrength: (strength) => + set((state) => ({ + wind: { ...state.wind, strength }, + })), - setGraphicsPreset: (preset) => - set((state) => ({ - graphics: { ...state.graphics, preset }, - })), + setGraphics: (graphicsUpdate) => + set((state) => ({ + graphics: { ...state.graphics, ...graphicsUpdate }, + })), - setDynamicGrass: (dynamicGrass) => - set((state) => ({ - graphics: { ...state.graphics, dynamicGrass }, - })), + setGraphicsPreset: (preset) => + set((state) => ({ + graphics: { ...state.graphics, preset }, + })), - setDynamicTrees: (dynamicTrees) => - set((state) => ({ - graphics: { ...state.graphics, dynamicTrees }, - })), + setDynamicGrass: (dynamicGrass) => + set((state) => ({ + graphics: { ...state.graphics, dynamicGrass }, + })), - setDynamicClouds: (dynamicClouds) => - set((state) => ({ - graphics: { ...state.graphics, dynamicClouds }, - })), + setDynamicTrees: (dynamicTrees) => + set((state) => ({ + graphics: { ...state.graphics, dynamicTrees }, + })), - setShadowsEnabled: (shadowsEnabled) => - set((state) => ({ - graphics: { ...state.graphics, shadowsEnabled }, - })), + setDynamicClouds: (dynamicClouds) => + set((state) => ({ + graphics: { ...state.graphics, dynamicClouds }, + })), - setGrassDensity: (grassDensity) => - set((state) => ({ - graphics: { ...state.graphics, grassDensity }, - })), + setShadowsEnabled: (shadowsEnabled) => + set((state) => ({ + graphics: { ...state.graphics, shadowsEnabled }, + })), - resetToDefaults: () => set(DEFAULT_STATE), -})); + setGrassDensity: (grassDensity) => + set((state) => ({ + graphics: { ...state.graphics, grassDensity }, + })), + + resetToDefaults: () => set(DEFAULT_STATE), + }), + { + name: WORLD_SETTINGS_STORAGE_KEY, + storage: createJSONStorage(() => window.localStorage), + partialize: (state) => ({ + clouds: state.clouds, + fog: state.fog, + graphics: state.graphics, + wind: state.wind, + }), + }, + ), +); diff --git a/src/pages/page.tsx b/src/pages/page.tsx index 6d9d752..d6b42a5 100644 --- a/src/pages/page.tsx +++ b/src/pages/page.tsx @@ -15,7 +15,9 @@ import { } from "@/components/ui/intro"; import { SceneLoadingOverlay } from "@/components/ui/SceneLoadingOverlay"; import { INITIAL_SCENE_LOADING_STATE } from "@/data/world/sceneLoadingConfig"; +import { useDebugStore } from "@/hooks/debug/useDebugStore"; import { useTransientLoadingIndicator } from "@/hooks/ui/useTransientLoadingIndicator"; +import { releaseBrowserHandLandmarker } from "@/lib/handTracking/browserHandTracking"; import { AudioManager } from "@/managers/AudioManager"; import { useGameStore } from "@/managers/stores/useGameStore"; import { useWorldSettingsStore } from "@/managers/stores/useWorldSettingsStore"; @@ -26,6 +28,9 @@ import { logger } from "@/utils/core/Logger"; import { World } from "@/world/World"; const LOADING_TO_VIDEO_FADE_MS = 500; +const WEBGL_CONTEXT_RESTORE_DELAY_MS = 500; +const CANVAS_DPR: [number, number] = [1, 1]; +const registeredWebglContextCanvases = new WeakSet(); export function HomePage(): React.JSX.Element | null { const navigate = useNavigate(); @@ -38,6 +43,11 @@ export function HomePage(): React.JSX.Element | null { const graphicsPreset = useWorldSettingsStore( (state) => state.graphics.preset, ); + const cameraMode = useDebugStore((debug) => debug.getCameraMode()); + const handTrackingSource = useDebugStore((debug) => + debug.getHandTrackingSource(), + ); + const sceneMode = useDebugStore((debug) => debug.getSceneMode()); const dialogMessage = useGameStore( (state) => state.missionFlow.dialogMessage, ); @@ -48,9 +58,18 @@ export function HomePage(): React.JSX.Element | null { INITIAL_SCENE_LOADING_STATE, ); const sceneReadyRef = useRef(false); + const cameraModeRef = useRef(cameraMode); + const handTrackingSourceRef = useRef(handTrackingSource); + const sceneModeRef = useRef(sceneMode); const runtimeLoadingSignal = `${graphicsPreset}:${mainState}:${ebikeStep}:${pylonStep}:${farmStep}`; const previousRuntimeLoadingSignalRef = useRef(runtimeLoadingSignal); + useEffect(() => { + cameraModeRef.current = cameraMode; + handTrackingSourceRef.current = handTrackingSource; + sceneModeRef.current = sceneMode; + }, [cameraMode, handTrackingSource, sceneMode]); + useEffect(() => { sceneReadyRef.current = sceneLoadingState.status === "ready"; }, [sceneLoadingState.status]); @@ -131,24 +150,44 @@ export function HomePage(): React.JSX.Element | null { gl.shadowMap.enabled = true; gl.shadowMap.type = THREE.PCFShadowMap; gl.shadowMap.autoUpdate = true; + gl.shadowMap.needsUpdate = true; // The browser hands us a WEBGL_lose_context extension we can use to // ask the GPU to restore the context after a loss. Without this the // page stays frozen on a black canvas until the user reloads. const loseContextExt = gl.getContext().getExtension("WEBGL_lose_context"); + if (registeredWebglContextCanvases.has(canvas)) return; + registeredWebglContextCanvases.add(canvas); + const handleContextLost = (event: Event) => { event.preventDefault(); - logger.error("WebGL", "Context lost - attempting auto-restore"); + releaseBrowserHandLandmarker(); + + logger.error("WebGL", "Context lost - attempting auto-restore", { + cameraMode: cameraModeRef.current, + geometries: gl.info.memory.geometries, + handTrackingSource: handTrackingSourceRef.current, + sceneMode: sceneModeRef.current, + textures: gl.info.memory.textures, + }); // Give the GPU a moment to free resources before asking it back. - window.setTimeout(() => loseContextExt?.restoreContext(), 500); + window.setTimeout( + () => loseContextExt?.restoreContext(), + WEBGL_CONTEXT_RESTORE_DELAY_MS, + ); }; const handleContextRestored = () => { gl.shadowMap.enabled = true; gl.shadowMap.type = THREE.PCFShadowMap; gl.shadowMap.autoUpdate = true; - logger.info("WebGL", "Context restored"); + gl.shadowMap.needsUpdate = true; + logger.info("WebGL", "Context restored", { + cameraMode: cameraModeRef.current, + handTrackingSource: handTrackingSourceRef.current, + sceneMode: sceneModeRef.current, + }); }; canvas.addEventListener("webglcontextlost", handleContextLost); @@ -189,10 +228,12 @@ export function HomePage(): React.JSX.Element | null { state.currentStep); - const isMobile = useIsMobile(); - - if (isMobile) { - return ; - } if (currentStep === "disclaimer") { return ; diff --git a/src/utils/debug/Debug.ts b/src/utils/debug/Debug.ts index 026b9d1..24df6fe 100644 --- a/src/utils/debug/Debug.ts +++ b/src/utils/debug/Debug.ts @@ -1,14 +1,17 @@ import GUI from "lil-gui"; +import type { Controller } from "lil-gui"; import type { CameraMode, SceneMode } from "@/types/debug/debug"; import type { HandTrackingSource } from "@/types/handTracking/handTracking"; import { FOG_CONFIG } from "@/data/world/fogConfig"; import { EventEmitter } from "@/utils/core/EventEmitter"; import { isDebugEnabled } from "@/utils/debug/isDebugEnabled"; +import { logger } from "@/utils/core/Logger"; const DEBUG_CONTROLS_STORAGE_KEY = "la-fabrik-debug-controls"; interface StoredDebugControls { cameraMode: CameraMode; + handTrackingSource: HandTrackingSource; sceneMode: SceneMode; } @@ -25,6 +28,7 @@ const DEBUG_FOLDER_ORDER = [ "Hand Tracking", "Map", "Personnages", + "Debug", ] as const; function isRecord(value: unknown): value is Record { @@ -39,6 +43,10 @@ function isSceneMode(value: unknown): value is SceneMode { return value === "game" || value === "physics"; } +function isHandTrackingSource(value: unknown): value is HandTrackingSource { + return value === "browser" || value === "backend"; +} + function getStoredDebugControls(): Partial { try { const rawValue = window.localStorage.getItem(DEBUG_CONTROLS_STORAGE_KEY); @@ -51,6 +59,9 @@ function getStoredDebugControls(): Partial { ...(isCameraMode(parsedValue.cameraMode) ? { cameraMode: parsedValue.cameraMode } : {}), + ...(isHandTrackingSource(parsedValue.handTrackingSource) + ? { handTrackingSource: parsedValue.handTrackingSource } + : {}), ...(isSceneMode(parsedValue.sceneMode) ? { sceneMode: parsedValue.sceneMode } : {}), @@ -68,6 +79,7 @@ export class Debug { private readonly events = new EventEmitter(); private readonly folders = new Map(); private readonly folderRefCounts = new Map(); + private handTrackingSourceController: Controller | null = null; private readonly controls: { cameraMode: CameraMode; fogEnabled: boolean; @@ -94,7 +106,7 @@ export class Debug { this.controls = { cameraMode: storedControls.cameraMode ?? "player", fogEnabled: FOG_CONFIG.enabled, - handTrackingSource: "browser", + handTrackingSource: storedControls.handTrackingSource ?? "browser", showDebugOverlay: true, showHandTrackingSvg: false, showInteractionSpheres: false, @@ -151,16 +163,22 @@ export class Debug { this.emit(); }); - handTrackingFolder - ?.add(this.controls, "handTrackingSource", { - "Browser JS": "browser", - Backend: "backend", - }) - .name("Source") - .onChange((value: HandTrackingSource) => { - this.controls.handTrackingSource = value; - this.emit(); - }); + this.handTrackingSourceController = + handTrackingFolder + ?.add(this.controls, "handTrackingSource", { + "Browser JS": "browser", + Backend: "backend", + }) + .name("Source") + .onChange((value: HandTrackingSource) => { + const previousSource = this.controls.handTrackingSource; + this.controls.handTrackingSource = value; + logger.info("HandTracking", "Debug source changed", { + from: previousSource, + to: value, + }); + this.saveAndEmit(); + }) ?? null; } } @@ -245,8 +263,14 @@ export class Debug { } setHandTrackingSource(value: HandTrackingSource): void { + const previousSource = this.controls.handTrackingSource; this.controls.handTrackingSource = value; - this.emit(); + this.handTrackingSourceController?.updateDisplay(); + logger.info("HandTracking", "Settings source changed", { + from: previousSource, + to: value, + }); + this.saveAndEmit(); } getFogEnabled(): boolean { @@ -285,6 +309,7 @@ export class Debug { DEBUG_CONTROLS_STORAGE_KEY, JSON.stringify({ cameraMode: this.controls.cameraMode, + handTrackingSource: this.controls.handTrackingSource, sceneMode: this.controls.sceneMode, }), ); diff --git a/src/utils/three/optimizeGLTFScene.ts b/src/utils/three/optimizeGLTFScene.ts index 710ebbb..eec91eb 100644 --- a/src/utils/three/optimizeGLTFScene.ts +++ b/src/utils/three/optimizeGLTFScene.ts @@ -19,20 +19,21 @@ type TexturedMaterial = THREE.Material & Partial>; const optimizedTextures = new WeakSet(); +const MAX_GLTF_TEXTURE_ANISOTROPY = 2; function optimizeTexture(texture: THREE.Texture, maxAnisotropy: number): void { if (optimizedTextures.has(texture)) return; optimizedTextures.add(texture); - texture.anisotropy = Math.min(4, Math.max(1, maxAnisotropy)); + const nextAnisotropy = Math.min( + MAX_GLTF_TEXTURE_ANISOTROPY, + Math.max(1, maxAnisotropy), + ); - if (!(texture instanceof THREE.CompressedTexture)) { - texture.generateMipmaps = true; - texture.minFilter = THREE.LinearMipmapLinearFilter; - texture.magFilter = THREE.LinearFilter; + if (texture.anisotropy > nextAnisotropy) { + texture.anisotropy = nextAnisotropy; + texture.needsUpdate = true; } - - texture.needsUpdate = true; } function optimizeMaterialTextures( diff --git a/src/world/Environment.tsx b/src/world/Environment.tsx index 7333d0f..430bc66 100644 --- a/src/world/Environment.tsx +++ b/src/world/Environment.tsx @@ -15,24 +15,11 @@ import { SkyModel } from "@/components/three/world/SkyModel"; import { CloudSystem } from "@/world/clouds/CloudSystem"; import { FogSystem } from "@/world/fog/FogSystem"; import { GrassSystem } from "@/world/grass/GrassSystem"; -import { SceneShadowWarmup } from "@/world/SceneShadowWarmup"; import { VegetationSystem } from "@/world/vegetation/VegetationSystem"; import { WaterSystem } from "@/world/water/WaterSystem"; import { WorldPlane } from "@/world/WorldPlane"; -interface ShadowWarmupConfig { - active: boolean; - onReady: () => void; - onStarted: () => void; -} - -interface EnvironmentProps { - shadowWarmup?: ShadowWarmupConfig; -} - -export function Environment({ - shadowWarmup, -}: EnvironmentProps): React.JSX.Element { +export function Environment(): React.JSX.Element { const sceneMode = useSceneMode(); const groups = useMapPerformanceStore((state) => state.groups); const models = useMapPerformanceStore((state) => state.models); @@ -47,13 +34,6 @@ export function Environment({ return ( <> - {shadowWarmup ? ( - - ) : null} {showSky ? ( ( [], ); + const [proxyCollisionMapNodes, setProxyCollisionMapNodes] = useState< + MapNode[] + >([]); const [terrainNode, setTerrainNode] = useState(null); const [mapLoaded, setMapLoaded] = useState(false); const [settledMapNodeCount, setSettledMapNodeCount] = useState(0); @@ -134,6 +138,7 @@ export function GameMap({ (currentStep: string) => { setRenderMapNodes([]); setCollisionMapNodes([]); + setProxyCollisionMapNodes([]); setTerrainNode(null); setMapLoaded(true); settledMapNodesRef.current.clear(); @@ -191,6 +196,10 @@ export function GameMap({ const modelUrl = sceneData.models.get(node.name); return { node, modelUrl: modelUrl ?? null }; }); + const loadedProxyCollisionNodes = sceneData.mapNodes.filter( + (node) => + node.type === "Object3D" && hasMapOctreeCollisionBox(node.name), + ); const loadedTerrainNode = getTerrainMapNode(sceneData.mapNodes); const repairMissionAnchors = getRepairMissionMapAnchors( sceneData.mapNodes, @@ -211,6 +220,7 @@ export function GameMap({ setRenderMapNodes(loadedMapNodes); setCollisionMapNodes(loadedCollisionNodes); + setProxyCollisionMapNodes(loadedProxyCollisionNodes); setTerrainNode(loadedTerrainNode); setRepairMissionAnchors(repairMissionAnchors); setMapLoaded(true); @@ -285,6 +295,7 @@ export function GameMap({ buildOctree={buildOctree} mapReady={mapReady} nodes={collisionMapNodes} + proxyNodes={proxyCollisionMapNodes} onLoaded={onLoaded} onLoadingStateChange={onLoadingStateChange} onOctreeReady={onOctreeReady} diff --git a/src/world/GameMapCollision.tsx b/src/world/GameMapCollision.tsx index 9d038d7..d2eb246 100644 --- a/src/world/GameMapCollision.tsx +++ b/src/world/GameMapCollision.tsx @@ -17,9 +17,24 @@ import { normalizeMapScale, useTerrainHeightSampler, } from "@/hooks/three/useTerrainHeight"; +import { + CHARACTER_CONFIGS, + CHARACTER_IDS, + type CharacterId, +} from "@/data/world/characters/characterConfig"; +import { + CHARACTER_OCTREE_COLLISION_BOX, + LA_FABRIK_INTERIOR_COLLISION_BOXES, + MAP_OCTREE_COLLISION_BOXES, + hasMapOctreeCollisionBox, + type OctreeCollisionBox, +} from "@/data/world/octreeCollisionConfig"; +import { getMapModelScaleMultiplier } from "@/data/world/mapInstancingConfig"; +import { useCharacterDebugStore } from "@/managers/stores/useCharacterDebugStore"; +import { useGameStore } from "@/managers/stores/useGameStore"; import { WorldBoundsCollision } from "@/world/collision/WorldBoundsCollision"; import type { MapNode } from "@/types/map/mapScene"; -import type { OctreeReadyHandler } from "@/types/three/three"; +import type { OctreeReadyHandler, Vector3Tuple } from "@/types/three/three"; import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading"; import { logModelLoadError } from "@/utils/three/modelLoadLogger"; @@ -39,6 +54,7 @@ interface GameMapCollisionProps { buildOctree?: boolean; mapReady: boolean; nodes: readonly GameMapCollisionNode[]; + proxyNodes: readonly MapNode[]; onLoaded?: (() => void) | undefined; onLoadingStateChange?: SceneLoadingChangeHandler | undefined; onOctreeReady: OctreeReadyHandler; @@ -101,6 +117,7 @@ export function GameMapCollision({ buildOctree = true, mapReady, nodes, + proxyNodes, onLoaded, onLoadingStateChange, onOctreeReady, @@ -109,10 +126,28 @@ export function GameMapCollision({ const settledCollisionNodesRef = useRef(new Set()); const loadedNotifiedRef = useRef(false); const [settledCollisionNodeCount, setSettledCollisionNodeCount] = useState(0); + const mainState = useGameStore((state) => state.mainState); const terrainHeight = useTerrainHeightSampler(); const collisionNodes = nodes.filter(isCollisionNode); + const includeCharacterCollisions = mainState !== "ebike"; + const characterCollisionCount = includeCharacterCollisions + ? CHARACTER_IDS.length + : 0; + const collisionSourceCount = + collisionNodes.length + proxyNodes.length + characterCollisionCount; const collisionReady = mapReady && settledCollisionNodeCount >= collisionNodes.length; + const characterCollisionSignature = useCharacterDebugStore((state) => + includeCharacterCollisions + ? CHARACTER_IDS.map((id) => { + const character = state.characters[id]; + return [...character.position, ...character.rotation].join(","); + }).join("|") + : "characters-hidden", + ); + const collisionRebuildKey = collisionReady + ? `${collisionNodes.length}:${collisionSourceCount}:${characterCollisionSignature}` + : "pending"; const notifyLoaded = useCallback(() => { if (loadedNotifiedRef.current) return; @@ -144,14 +179,14 @@ export function GameMapCollision({ useOctreeGraphNode( groupRef, handleOctreeReady, - collisionReady ? collisionNodes.length : 0, - buildOctree && collisionReady && collisionNodes.length > 0, + collisionRebuildKey, + buildOctree && collisionReady && collisionSourceCount > 0, ); useEffect(() => { if (!mapReady) return; - if (collisionNodes.length === 0) { + if (collisionSourceCount === 0) { notifyLoaded(); return; } @@ -171,6 +206,7 @@ export function GameMapCollision({ }, [ buildOctree, collisionNodes.length, + collisionSourceCount, collisionReady, mapReady, notifyLoaded, @@ -180,6 +216,18 @@ export function GameMapCollision({ return ( {mapReady ? : null} + {mapReady + ? proxyNodes.map((node, index) => ( + + )) + : null} + {mapReady && includeCharacterCollisions ? ( + + ) : null} {mapReady ? collisionNodes.map((mapNode, index) => ( { + if (node.name !== "lafabrik") return; + + const isDoorSlab = (name: string): boolean => + name === "porte" || /^porte[._]\d+$/i.test(name); + const isDoorFrameThickenChild = (child: THREE.Object3D): boolean => + child.parent?.name === "Thicken"; + + const doorMeshes: THREE.Object3D[] = []; + sceneInstance.traverse((child) => { + if (isDoorSlab(child.name) || isDoorFrameThickenChild(child)) { + doorMeshes.push(child); + } + }); + for (const child of doorMeshes) { + child.removeFromParent(); + } + }, [node.name, sceneInstance]); const collisionPosition = useMemo(() => { if (node.name === "terrain") return position; @@ -237,11 +303,131 @@ function CollisionModelInstance({ }, [onLoaded]); return ( - + <> + + {node.name === "lafabrik" ? ( + + {LA_FABRIK_INTERIOR_COLLISION_BOXES.map((box, index) => ( + + ))} + + ) : null} + + ); +} + +function CollisionBox({ box }: { box: OctreeCollisionBox }): React.JSX.Element { + return ( + + + + + + + + + + + ); +} + +function createScaledMapNodeScale(node: MapNode): Vector3Tuple { + const baseScale = normalizeMapScale(node.scale); + const scaleMultiplier = getMapModelScaleMultiplier(node.name); + + return [ + baseScale[0] * scaleMultiplier, + baseScale[1] * scaleMultiplier, + baseScale[2] * scaleMultiplier, + ]; +} + +function MapCollisionBoxProxy({ + node, + terrainHeight, +}: { + node: MapNode; + terrainHeight: TerrainHeightSampler; +}): React.JSX.Element | null { + const collisionBox = hasMapOctreeCollisionBox(node.name) + ? MAP_OCTREE_COLLISION_BOXES[node.name] + : null; + const normalizedScale = useMemo(() => createScaledMapNodeScale(node), [node]); + const position = useMemo(() => { + const [x, y, z] = node.position; + if (!collisionBox) return [x, y, z] satisfies Vector3Tuple; + + const height = terrainHeight.getHeight(x, z); + const bottomOffset = -collisionBox.bottomY * normalizedScale[1]; + + return [x, (height ?? y) + bottomOffset, z] satisfies Vector3Tuple; + }, [collisionBox, node.position, normalizedScale, terrainHeight]); + + if (!collisionBox) return null; + + return ( + + + + ); +} + +function CharacterCollisionProxies({ + terrainHeight, +}: { + terrainHeight: TerrainHeightSampler; +}): React.JSX.Element { + return ( + <> + {CHARACTER_IDS.map((id) => ( + + ))} + + ); +} + +function CharacterCollisionProxy({ + id, + terrainHeight, +}: { + id: CharacterId; + terrainHeight: TerrainHeightSampler; +}): React.JSX.Element { + const config = CHARACTER_CONFIGS[id]; + const state = useCharacterDebugStore((store) => store.characters[id]); + const position = useMemo(() => { + const [x, y, z] = state.position; + const height = terrainHeight.getHeight(x, z); + + return [x, height ?? y, z] satisfies Vector3Tuple; + }, [state.position, terrainHeight]); + + return ( + + + ); } diff --git a/src/world/GameStageContent.tsx b/src/world/GameStageContent.tsx index 3e755c1..830575d 100644 --- a/src/world/GameStageContent.tsx +++ b/src/world/GameStageContent.tsx @@ -20,7 +20,13 @@ import { isPylonNarrativeStep } from "@/types/gameplay/repairMission"; import type { RepairMissionTriggerConfig } from "@/types/gameplay/repairMission"; import type { Vector3Tuple } from "@/types/three/three"; import { getRepairMissionPosition } from "@/utils/gameplay/repairMissionPosition"; -import { EBIKE_WORLD_POSITION } from "@/data/ebike/ebikeConfig"; +import { + EBIKE_WORLD_POSITION, + EBIKE_WORLD_ROTATION_Y, + EBIKE_WORLD_SCALE, +} from "@/data/ebike/ebikeConfig"; + +const EBIKE_CONFIG_KEY = `${EBIKE_WORLD_POSITION.join(",")}:${EBIKE_WORLD_ROTATION_Y}:${EBIKE_WORLD_SCALE}`; interface StageAnchorProps { color: string; diff --git a/src/world/Lighting.tsx b/src/world/Lighting.tsx index 87908b3..714f44c 100644 --- a/src/world/Lighting.tsx +++ b/src/world/Lighting.tsx @@ -1,10 +1,17 @@ import { useEffect, useRef } from "react"; import { useFrame, useThree } from "@react-three/fiber"; -import type { AmbientLight, DirectionalLight, Object3D } from "three"; +import { + PCFShadowMap, + type AmbientLight, + type DirectionalLight, + type Object3D, + type WebGLRenderer, +} from "three"; import { AMBIENT_INTENSITY_MAX, AMBIENT_INTENSITY_MIN, AMBIENT_INTENSITY_STEP, + SHADOW_CONFIG, SUN_INTENSITY_MAX, SUN_INTENSITY_MIN, SUN_INTENSITY_STEP, @@ -18,16 +25,51 @@ import { SUN_Z_MIN, SUN_Z_STEP, } from "@/data/world/lightingConfig"; +import { LA_FABRIK_INTERIOR_LIGHT_POSITION } from "@/data/world/laFabrikConfig"; import { useDebugFolder } from "@/hooks/debug/useDebugFolder"; +import { useShadowMapWarmup } from "@/hooks/three/useShadowMapWarmup"; import { LIGHTING_STATE } from "@/world/lightingState"; -const SHADOW_MAP_SIZE = 2048; -const SHADOW_CAMERA_SIZE = 95; -const SHADOW_CAMERA_NEAR = 0.5; -const SHADOW_CAMERA_FAR = 300; +function configureRendererShadows(gl: WebGLRenderer): void { + gl.shadowMap.enabled = true; + gl.shadowMap.type = PCFShadowMap; + gl.shadowMap.autoUpdate = true; +} + +function configureSunShadow(sun: DirectionalLight, sunTarget: Object3D): void { + sun.target = sunTarget; + sun.shadow.autoUpdate = true; + sun.shadow.bias = SHADOW_CONFIG.bias; + sun.shadow.normalBias = SHADOW_CONFIG.normalBias; + sun.shadow.mapSize.width = SHADOW_CONFIG.mapSize; + sun.shadow.mapSize.height = SHADOW_CONFIG.mapSize; + sun.shadow.camera.left = -SHADOW_CONFIG.cameraSize; + sun.shadow.camera.right = SHADOW_CONFIG.cameraSize; + sun.shadow.camera.top = SHADOW_CONFIG.cameraSize; + sun.shadow.camera.bottom = -SHADOW_CONFIG.cameraSize; + sun.shadow.camera.near = SHADOW_CONFIG.cameraNear; + sun.shadow.camera.far = SHADOW_CONFIG.cameraFar; + sun.shadow.camera.updateProjectionMatrix(); +} + +function placeSunRelativeToCamera( + sun: DirectionalLight, + sunTarget: Object3D, + cameraPosition: { x: number; z: number }, +): void { + sunTarget.position.set(cameraPosition.x, 0, cameraPosition.z); + sun.position.set( + cameraPosition.x + LIGHTING_STATE.sunX, + LIGHTING_STATE.sunY, + cameraPosition.z + LIGHTING_STATE.sunZ, + ); +} export function Lighting(): React.JSX.Element { const camera = useThree((state) => state.camera); + const gl = useThree((state) => state.gl); + const scene = useThree((state) => state.scene); + const invalidate = useThree((state) => state.invalidate); const ambient = useRef(null); const sun = useRef(null); const sunTarget = useRef(null); @@ -35,19 +77,16 @@ export function Lighting(): React.JSX.Element { useEffect(() => { if (!sun.current || !sunTarget.current) return; - sun.current.target = sunTarget.current; - sun.current.shadow.autoUpdate = true; - sun.current.shadow.needsUpdate = true; - sun.current.shadow.mapSize.width = SHADOW_MAP_SIZE; - sun.current.shadow.mapSize.height = SHADOW_MAP_SIZE; - sun.current.shadow.camera.left = -SHADOW_CAMERA_SIZE; - sun.current.shadow.camera.right = SHADOW_CAMERA_SIZE; - sun.current.shadow.camera.top = SHADOW_CAMERA_SIZE; - sun.current.shadow.camera.bottom = -SHADOW_CAMERA_SIZE; - sun.current.shadow.camera.near = SHADOW_CAMERA_NEAR; - sun.current.shadow.camera.far = SHADOW_CAMERA_FAR; - sun.current.shadow.camera.updateProjectionMatrix(); - }, []); + configureRendererShadows(gl); + configureSunShadow(sun.current, sunTarget.current); + // Prime the sun + target onto the camera before the first shadow pass so + // the initial shadow frustum already covers the visible scene; without + // this, the first frame is rendered with the default (origin-centered) + // frustum and shadows can appear absent until the player moves. + placeSunRelativeToCamera(sun.current, sunTarget.current, camera.position); + }, [camera, gl]); + + useShadowMapWarmup({ light: sun, scene, gl, invalidate }); useDebugFolder("Lighting", (folder) => { folder.addColor(LIGHTING_STATE, "ambientColor").name("Ambient Color"); @@ -87,19 +126,14 @@ export function Lighting(): React.JSX.Element { ambient.current.intensity = LIGHTING_STATE.ambientIntensity; } - if (sun.current && sunTarget.current) { - sunTarget.current.position.set(camera.position.x, 0, camera.position.z); - sunTarget.current.updateMatrixWorld(); - sun.current.position.set( - camera.position.x + LIGHTING_STATE.sunX, - LIGHTING_STATE.sunY, - camera.position.z + LIGHTING_STATE.sunZ, - ); - sun.current.color.set(LIGHTING_STATE.sunColor); - sun.current.intensity = LIGHTING_STATE.sunIntensity; - sun.current.updateMatrixWorld(); - sun.current.shadow.needsUpdate = true; - } + if (!sun.current || !sunTarget.current) return; + + placeSunRelativeToCamera(sun.current, sunTarget.current, camera.position); + sunTarget.current.updateMatrixWorld(); + sun.current.color.set(LIGHTING_STATE.sunColor); + sun.current.intensity = LIGHTING_STATE.sunIntensity; + sun.current.updateMatrixWorld(); + sun.current.shadow.needsUpdate = true; }); return ( @@ -121,6 +155,13 @@ export function Lighting(): React.JSX.Element { castShadow /> + ); } diff --git a/src/world/SceneShadowWarmup.tsx b/src/world/SceneShadowWarmup.tsx deleted file mode 100644 index 4f994bd..0000000 --- a/src/world/SceneShadowWarmup.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useEffect, useRef } from "react"; -import { useThree } from "@react-three/fiber"; -import * as THREE from "three"; - -interface SceneShadowWarmupProps { - active: boolean; - onReady: () => void; - onStarted: () => void; -} - -function markShadowLightForUpdate(object: THREE.Object3D): void { - if ( - !( - object instanceof THREE.DirectionalLight || - object instanceof THREE.PointLight || - object instanceof THREE.SpotLight - ) - ) { - return; - } - - if (!object.castShadow) return; - - object.updateMatrixWorld(true); - object.shadow.camera.updateProjectionMatrix(); - object.shadow.needsUpdate = true; -} - -function forceSceneShadowPass( - gl: THREE.WebGLRenderer, - scene: THREE.Scene, -): void { - gl.shadowMap.enabled = true; - gl.shadowMap.type = THREE.PCFShadowMap; - gl.shadowMap.autoUpdate = true; - gl.shadowMap.needsUpdate = true; - - scene.updateMatrixWorld(true); - scene.traverse((object) => { - if (object instanceof THREE.Mesh) { - object.updateMatrixWorld(true); - } - - markShadowLightForUpdate(object); - }); -} - -export function SceneShadowWarmup({ - active, - onReady, - onStarted, -}: SceneShadowWarmupProps): null { - const gl = useThree((state) => state.gl); - const scene = useThree((state) => state.scene); - const invalidate = useThree((state) => state.invalidate); - const isRunningRef = useRef(false); - - useEffect(() => { - if (!active) { - isRunningRef.current = false; - return undefined; - } - - if (isRunningRef.current) return undefined; - - isRunningRef.current = true; - onStarted(); - forceSceneShadowPass(gl, scene); - invalidate(); - - let firstFrame = 0; - let secondFrame = 0; - - firstFrame = window.requestAnimationFrame(() => { - forceSceneShadowPass(gl, scene); - invalidate(); - - secondFrame = window.requestAnimationFrame(() => { - forceSceneShadowPass(gl, scene); - invalidate(); - onReady(); - }); - }); - - return () => { - window.cancelAnimationFrame(firstFrame); - window.cancelAnimationFrame(secondFrame); - }; - }, [active, gl, invalidate, onReady, onStarted, scene]); - - return null; -} diff --git a/src/world/World.tsx b/src/world/World.tsx index 8a3b526..09af42f 100644 --- a/src/world/World.tsx +++ b/src/world/World.tsx @@ -4,17 +4,22 @@ import { PLAYER_SPAWN_POSITION_GAME, PLAYER_SPAWN_POSITION_PHYSICS, } from "@/data/player/playerConfig"; +import { LA_FABRIK_INITIAL_LOOK_AT } from "@/data/world/laFabrikConfig"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug"; import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug"; import { useCharacterDebug } from "@/hooks/debug/useCharacterDebug"; import { usePlayerPositionDebug } from "@/hooks/debug/usePlayerPositionDebug"; +import { useDebugVisualsDebug } from "@/hooks/debug/useDebugVisualsDebug"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot"; import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading"; import { useGameStore } from "@/managers/stores/useGameStore"; +import { useDebugVisualsStore } from "@/managers/stores/useDebugVisualsStore"; import { DebugCameraControls } from "@/components/debug/scene/DebugCameraControls"; import { DebugHelpers } from "@/components/debug/scene/DebugHelpers"; +import { DebugOctreeVisualization } from "@/components/debug/DebugOctreeVisualization"; +import { DebugPlayerModel } from "@/components/debug/DebugPlayerModel"; import { HandTrackingGlove } from "@/components/three/handTracking/HandTrackingGlove"; import { Environment } from "@/world/Environment"; import { GameCinematics } from "@/world/GameCinematics"; @@ -27,21 +32,35 @@ import { CharacterSystem } from "@/world/characters/CharacterSystem"; import { Player } from "@/world/player/Player"; import { TestMap } from "@/world/debug/TestMap"; import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading"; +import type { HandTrackingGloveHandedness } from "@/hooks/handTracking/useHandTrackingGloveStatus"; +import type { HandTrackingHand } from "@/types/handTracking/handTracking"; interface WorldProps { onLoadingStateChange?: SceneLoadingChangeHandler | undefined; } +function hasTrackedHand( + hands: HandTrackingHand[], + handedness: HandTrackingGloveHandedness, +): boolean { + return hands.some((hand) => hand.handedness.toLowerCase() === handedness); +} + export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element { useEnvironmentDebug(); useMapPerformanceDebug(); useCharacterDebug(); usePlayerPositionDebug(); + useDebugVisualsDebug(); const cameraMode = useCameraMode(); const sceneMode = useSceneMode(); const mainState = useGameStore((state) => state.mainState); - const { status, usageStatus } = useHandTrackingSnapshot(); + const showDebugPlayerModel = useDebugVisualsStore( + (state) => state.showPlayerModel, + ); + const showDebugOctree = useDebugVisualsStore((state) => state.showOctree); + const { hands, status, usageStatus } = useHandTrackingSnapshot(); const { octree, gameplayReady, @@ -49,36 +68,40 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element { handleGameStageLoaded, handleGameMapLoaded, handleOctreeReady, - handleShadowWarmupReady, - handleShadowWarmupStarted, - shouldWarmUpShadows, } = useWorldSceneLoading({ sceneMode, onLoadingStateChange }); const playerSpawnPosition = sceneMode === "game" ? PLAYER_SPAWN_POSITION_GAME : PLAYER_SPAWN_POSITION_PHYSICS; const showHandTrackingGloves = - sceneMode === "physics" || - (status !== "idle" && usageStatus !== "inactive"); + status === "connected" && usageStatus !== "inactive" && hands.length > 0; + const showLeftHandTrackingGlove = + showHandTrackingGloves && hasTrackedHand(hands, "left"); + const showRightHandTrackingGlove = + showHandTrackingGloves && hasTrackedHand(hands, "right"); const spawnPlayer = cameraMode !== "debug" && (sceneMode === "game" ? gameplayReady : octree !== null); return ( <> - + + {showDebugOctree ? : null} + {showDebugPlayerModel ? ( + + + + ) : null} {showHandTrackingGloves ? ( - - + {showLeftHandTrackingGlove ? ( + + ) : null} + {showRightHandTrackingGlove ? ( + + ) : null} ) : null} {cameraMode === "debug" ? : null} @@ -93,16 +116,22 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element { {showGameStage ? ( - + + + ) : null} {spawnPlayer ? ( - <> + {mainState === "outro" ? : null} {mainState !== "intro" ? : null} - - + + ) : null} ) : ( diff --git a/src/world/debug/TestMap.tsx b/src/world/debug/TestMap.tsx index 0056b21..e1d74b9 100644 --- a/src/world/debug/TestMap.tsx +++ b/src/world/debug/TestMap.tsx @@ -17,6 +17,8 @@ import { TEST_SCENE_GRABBABLE_METALNESS, TEST_SCENE_GRABBABLE_POSITION, TEST_SCENE_GRABBABLE_ROUGHNESS, + TEST_SCENE_GPS_PREVIEW_POSITION, + TEST_SCENE_GPS_PREVIEW_ROTATION, GAME_REPAIR_ZONES, TEST_SCENE_REPAIR_ZONE_MARKER_RADIUS, TEST_SCENE_REPAIR_ZONE_MARKER_TUBE_RADIUS, @@ -110,24 +112,17 @@ export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element { try { const parsed = JSON.parse(saved); if (Array.isArray(parsed) && parsed.length > 0) { - console.log( - `[TestMap] ${parsed.length} waypoints chargés depuis localStorage.`, - ); // Schedule state update to avoid synchronous setState in effect queueMicrotask(() => { if (!cancelled) setWaypoints(parsed); }); return; } - } catch (e) { - console.error("Failed to parse local storage waypoints", e); + } catch { + // Ignore parse errors — fall through to fetch fallback } } - // 2. Try public/roadNetwork.json - console.log( - "[TestMap] Tentative de chargement depuis /roadNetwork.json...", - ); fetch("/roadNetwork.json") .then((res) => { if (res.ok) return res.json(); @@ -136,14 +131,11 @@ export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element { .then((data) => { if (cancelled) return; if (Array.isArray(data)) { - console.log( - `[TestMap] ${data.length} waypoints chargés depuis /roadNetwork.json.`, - ); setWaypoints(data); } }) - .catch((err) => { - console.log("[TestMap] Aucun point d'A* trouvé par défaut.", err); + .catch(() => { + // No A* waypoints available — silent fallback }); return () => { @@ -253,7 +245,10 @@ export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element { {/* Dynamic Futuristic 3D GPS Dashboard Preview */} - + {/* Futuristic glowing screen frame (commented out to show true 3D transparency!) */} {/* diff --git a/src/world/grass/GrassPatch.tsx b/src/world/grass/GrassPatch.tsx index 1f1daba..877d18d 100644 --- a/src/world/grass/GrassPatch.tsx +++ b/src/world/grass/GrassPatch.tsx @@ -8,6 +8,11 @@ import { GRASS_COLORS, GRASS_CONFIG, } from "@/data/world/grassConfig"; +import { + LA_FABRIK_CENTER, + LA_FABRIK_HALF_EXTENTS, + LA_FABRIK_ROTATION_Y, +} from "@/data/world/laFabrikConfig"; import { grassFragmentShader, grassVertexShader, @@ -169,6 +174,17 @@ function createGrassMaterial( uMaxBladeHeight: { value: GRASS_CONFIG.maxBladeHeight }, uRandomHeightAmount: { value: GRASS_CONFIG.randomHeightAmount }, uSurfaceOffset: { value: GRASS_CONFIG.surfaceOffset }, + uLaFabrikCenter: { + value: new THREE.Vector2(LA_FABRIK_CENTER[0], LA_FABRIK_CENTER[2]), + }, + uLaFabrikHalfExtents: { + value: new THREE.Vector2( + LA_FABRIK_HALF_EXTENTS.x, + LA_FABRIK_HALF_EXTENTS.z, + ), + }, + uLaFabrikRotation: { value: LA_FABRIK_ROTATION_Y }, + uLaFabrikNoGrassFeather: { value: 1.4 }, }, }); } diff --git a/src/world/grass/grassShaders.ts b/src/world/grass/grassShaders.ts index 3f8b461..15bffbb 100644 --- a/src/world/grass/grassShaders.ts +++ b/src/world/grass/grassShaders.ts @@ -43,6 +43,10 @@ export const grassVertexShader = /* glsl */ ` uniform float uMaxBladeHeight; uniform float uRandomHeightAmount; uniform float uSurfaceOffset; + uniform vec2 uLaFabrikCenter; + uniform vec2 uLaFabrikHalfExtents; + uniform float uLaFabrikRotation; + uniform float uLaFabrikNoGrassFeather; float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); @@ -132,6 +136,18 @@ export const grassVertexShader = /* glsl */ ` smoothstep(uBoundingBoxMax.z, uBoundingBoxMax.z - 2.0, worldPos.z); heightModifier *= edgeFade * mix(0.45, 1.0, clumpMask); + vec2 laFabrikDelta = worldPos.xz - uLaFabrikCenter; + float laFabrikCos = cos(-uLaFabrikRotation); + float laFabrikSin = sin(-uLaFabrikRotation); + vec2 laFabrikLocal = vec2( + laFabrikDelta.x * laFabrikCos - laFabrikDelta.y * laFabrikSin, + laFabrikDelta.x * laFabrikSin + laFabrikDelta.y * laFabrikCos + ); + vec2 laFabrikDistance = abs(laFabrikLocal) - uLaFabrikHalfExtents; + float laFabrikOutsideDistance = max(laFabrikDistance.x, laFabrikDistance.y); + float laFabrikGrassMask = smoothstep(0.0, uLaFabrikNoGrassFeather, laFabrikOutsideDistance); + heightModifier *= laFabrikGrassMask; + float sideFactor = (color.r == 0.1) ? 1.0 : (color.b == 0.1) ? -1.0 : 0.0; float tipFactor = color.g; float width = smoothstep(0.02, uMaxBladeHeight * 0.85, heightModifier) * uBladeWidth * bladeVisibility; diff --git a/src/world/map-instancing/MapInstancingSystem.tsx b/src/world/map-instancing/MapInstancingSystem.tsx index c5be5e9..1cb4c10 100644 --- a/src/world/map-instancing/MapInstancingSystem.tsx +++ b/src/world/map-instancing/MapInstancingSystem.tsx @@ -149,6 +149,7 @@ export function MapInstancingSystem({ const streamingEnabled = streaming && CHUNK_CONFIG.enabled && + graphicsPresetConfig.chunkStreamingEnabled && sceneMode === "game" && cameraMode === "player"; diff --git a/src/world/player/Player.tsx b/src/world/player/Player.tsx index 230560d..cbd93a4 100644 --- a/src/world/player/Player.tsx +++ b/src/world/player/Player.tsx @@ -7,10 +7,12 @@ import { PlayerController } from "@/world/player/PlayerController"; interface PlayerProps { octree: Octree | null; + initialLookAt?: Vector3Tuple | undefined; spawnPosition: Vector3Tuple; } export function Player({ + initialLookAt, spawnPosition, octree, }: PlayerProps): React.JSX.Element { @@ -18,12 +20,17 @@ export function Player({ useLayoutEffect(() => { camera.position.set(...spawnPosition); - }, [camera, spawnPosition]); + if (initialLookAt) camera.lookAt(...initialLookAt); + }, [camera, initialLookAt, spawnPosition]); return ( <> - + ); } diff --git a/src/world/player/PlayerCamera.tsx b/src/world/player/PlayerCamera.tsx index 3a120a2..ca60af0 100644 --- a/src/world/player/PlayerCamera.tsx +++ b/src/world/player/PlayerCamera.tsx @@ -1,18 +1,29 @@ import { useEffect } from "react"; import { useThree } from "@react-three/fiber"; import { PointerLockControls } from "@react-three/drei"; +import { useSettingsStore } from "@/managers/stores/useSettingsStore"; import { setGlobalCamera } from "@/world/GameCinematics"; export function PlayerCamera(): React.JSX.Element { const camera = useThree((state) => state.camera); + const isSettingsMenuOpen = useSettingsStore( + (state) => state.isSettingsMenuOpen, + ); useEffect(() => { setGlobalCamera(camera); return () => { setGlobalCamera(null); - document.exitPointerLock(); + if (document.pointerLockElement) { + document.exitPointerLock(); + } }; }, [camera]); - return ; + return ( + + ); } diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 4c74d52..8b27a1a 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -33,8 +33,8 @@ import { EBIKE_ACCELERATION_DURATION_MS, EBIKE_CAMERA_TRANSFORM, EBIKE_DECELERATION_DURATION_MS, - EBIKE_MAX_SPEED, } from "@/data/ebike/ebikeConfig"; +import { useSceneMode } from "@/hooks/debug/useSceneMode"; /** Global window properties used for ebike communication */ interface EbikeGlobalState { @@ -75,6 +75,7 @@ const PLAYER_FLOOR_NORMAL_MIN = 0.15; const PLAYER_GROUND_SNAP_DISTANCE = 0.22; interface PlayerControllerProps { + initialLookAt?: Vector3Tuple | undefined; octree: Octree | null; spawnPosition: Vector3Tuple; } @@ -89,6 +90,7 @@ const _collisionCorrection = new THREE.Vector3(); function resetPlayerCapsule( capsule: Capsule, spawnPosition: Vector3Tuple, + initialLookAt: Vector3Tuple | undefined, camera: THREE.Camera, velocity: THREE.Vector3, ): void { @@ -100,6 +102,7 @@ function resetPlayerCapsule( capsule.end.set(...spawnPosition); velocity.set(0, 0, 0); camera.position.copy(capsule.end); + if (initialLookAt) camera.lookAt(...initialLookAt); } function createSpawnCapsule(spawnPosition: Vector3Tuple): Capsule { @@ -145,10 +148,12 @@ function getCapsuleFootY(capsule: Capsule): number { } export function PlayerController({ + initialLookAt, octree, spawnPosition, }: PlayerControllerProps): null { const camera = useThree((state) => state.camera); + const sceneMode = useSceneMode(); const movementLocked = useRepairMovementLocked(); const terrainHeight = useTerrainHeightSampler(); const movementLockedRef = useRef(movementLocked); @@ -234,6 +239,7 @@ export function PlayerController({ resetPlayerCapsule( capsule.current, spawnPosition, + initialLookAt, camera, velocity.current, ); @@ -241,7 +247,7 @@ export function PlayerController({ onFloor.current = false; wantsJump.current = false; initializedRef.current = true; - }, [camera, spawnPosition]); + }, [camera, initialLookAt, spawnPosition]); useEffect(() => { movementLockedRef.current = movementLocked; @@ -339,6 +345,7 @@ export function PlayerController({ resetPlayerCapsule( capsule.current, spawnPosition, + initialLookAt, camera, velocity.current, ); @@ -409,7 +416,7 @@ export function PlayerController({ } const movementSpeed = isEbikeMounted - ? EBIKE_MAX_SPEED * ebikeSpeedFactor.current + ? currentSpeed * ebikeSpeedFactor.current : currentSpeed; const accel = onFloor.current ? movementSpeed @@ -478,19 +485,21 @@ export function PlayerController({ } } - const groundHeight = terrainHeight.getHeight( - capsule.current.end.x, - capsule.current.end.z, - ); - if (groundHeight !== null && velocity.current.y <= 0) { - const groundOffset = getCapsuleFootY(capsule.current) - groundHeight; + if (sceneMode === "game") { + const groundHeight = terrainHeight.getHeight( + capsule.current.end.x, + capsule.current.end.z, + ); + if (groundHeight !== null && velocity.current.y <= 0) { + const groundOffset = getCapsuleFootY(capsule.current) - groundHeight; - if (groundOffset <= PLAYER_GROUND_SNAP_DISTANCE) { - capsule.current.translate( - _collisionCorrection.set(0, -groundOffset, 0), - ); - velocity.current.y = 0; - onFloor.current = true; + if (groundOffset <= PLAYER_GROUND_SNAP_DISTANCE) { + capsule.current.translate( + _collisionCorrection.set(0, -groundOffset, 0), + ); + velocity.current.y = 0; + onFloor.current = true; + } } } diff --git a/src/world/vegetation/VegetationSystem.tsx b/src/world/vegetation/VegetationSystem.tsx index c340633..a9f6431 100644 --- a/src/world/vegetation/VegetationSystem.tsx +++ b/src/world/vegetation/VegetationSystem.tsx @@ -1,8 +1,24 @@ -import { Suspense, useMemo } from "react"; +import { + Suspense, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useFrame, useThree } from "@react-three/fiber"; import { CHUNK_CONFIG } from "@/data/world/chunkStreamingConfig"; +import { + getMapLodModelPath, + getMapLodScaleMultiplier, + selectMapModelPathByDistance, +} from "@/data/world/mapLodConfig"; import { useCameraMode } from "@/hooks/debug/useCameraMode"; import { useSceneMode } from "@/hooks/debug/useSceneMode"; -import { useGraphicsPresetConfig } from "@/hooks/world/useGraphicsSettings"; +import { + useGraphicsPreset, + useGraphicsPresetConfig, +} from "@/hooks/world/useGraphicsSettings"; import { useVisibleWorldChunks } from "@/hooks/world/useVisibleWorldChunks"; import { isMapModelVisible, @@ -16,7 +32,9 @@ import { VEGETATION_TYPES, type VegetationType, } from "@/data/world/vegetationConfig"; +import { isInsideLaFabrikFootprint } from "@/data/world/laFabrikConfig"; import { createWorldInstanceChunks } from "@/utils/world/chunkInstances"; +import type { GraphicsPreset } from "@/data/world/graphicsConfig"; interface VegetationSystemProps { onlyMapName?: string | null; @@ -60,12 +78,87 @@ function createVegetationChunks( }); } +function removeLaFabrikVegetation( + instances: VegetationInstance[], +): VegetationInstance[] { + return instances.filter((instance) => { + const [x, , z] = instance.position; + return !isInsideLaFabrikFootprint(x, z, 1.2); + }); +} + +function areChunkModelPathsEqual( + a: ReadonlyMap, + b: ReadonlyMap, +): boolean { + return ( + a.size === b.size && [...a].every(([key, value]) => b.get(key) === value) + ); +} + +function useVegetationChunkModelPaths( + chunks: readonly VegetationChunk[], + preset: GraphicsPreset, +): ReadonlyMap { + const camera = useThree((state) => state.camera); + const lastUpdateRef = useRef(-CHUNK_CONFIG.updateInterval); + const modelPathsRef = useRef>(new Map()); + const [modelPaths, setModelPaths] = useState>( + () => new Map(), + ); + + const updateModelPaths = useCallback(() => { + const cameraX = camera.position.x; + const cameraZ = camera.position.z; + const next = new Map(); + + for (const chunk of chunks) { + let nearestDistance = Number.POSITIVE_INFINITY; + for (const instance of chunk.instances) { + const distance = Math.hypot( + instance.position[0] - cameraX, + instance.position[2] - cameraZ, + ); + if (distance < nearestDistance) nearestDistance = distance; + } + + const modelPath = selectMapModelPathByDistance({ + distance: nearestDistance, + modelName: VEGETATION_TYPES[chunk.type].mapName, + modelPath: chunk.modelPath, + preset, + }); + next.set(chunk.key, modelPath); + } + + if (areChunkModelPathsEqual(next, modelPathsRef.current)) return; + + modelPathsRef.current = next; + setModelPaths(next); + }, [camera, chunks, preset]); + + useEffect(() => { + updateModelPaths(); + }, [updateModelPaths]); + + useFrame(({ clock }) => { + const now = clock.elapsedTime * 1000; + if (now - lastUpdateRef.current < CHUNK_CONFIG.updateInterval) return; + lastUpdateRef.current = now; + + updateModelPaths(); + }); + + return modelPaths; +} + export function VegetationSystem({ onlyMapName = null, streaming = true, }: VegetationSystemProps): React.JSX.Element | null { const cameraMode = useCameraMode(); const sceneMode = useSceneMode(); + const graphicsPresetKey = useGraphicsPreset(); const graphicsPreset = useGraphicsPresetConfig(); const groups = useMapPerformanceStore((state) => state.groups); const models = useMapPerformanceStore((state) => state.models); @@ -73,6 +166,7 @@ export function VegetationSystem({ const streamingEnabled = streaming && CHUNK_CONFIG.enabled && + graphicsPreset.chunkStreamingEnabled && sceneMode === "game" && cameraMode === "player"; @@ -90,7 +184,10 @@ export function VegetationSystem({ const entry = data.get(config.mapName); if (!entry || entry.instances.length === 0) return []; - return createVegetationChunks(type, entry.instances); + const instances = removeLaFabrikVegetation(entry.instances); + if (instances.length === 0) return []; + + return createVegetationChunks(type, instances); }); }, [data, groups, models, onlyMapName]); @@ -99,25 +196,38 @@ export function VegetationSystem({ unloadRadius: graphicsPreset.chunkUnloadRadius, }); + const chunkModelPaths = useVegetationChunkModelPaths( + visibleChunks, + graphicsPresetKey, + ); + if (isLoading || !data) { return null; } return ( - {visibleChunks.map((chunk) => ( - - - - ))} + {visibleChunks.map((chunk) => { + const modelPath = chunkModelPaths.get(chunk.key) ?? chunk.modelPath; + const mapName = VEGETATION_TYPES[chunk.type].mapName; + const isLod = modelPath === getMapLodModelPath(mapName); + const scaleMultiplier = + chunk.scaleMultiplier * + (isLod ? getMapLodScaleMultiplier(mapName) : 1); + return ( + + + + ); + })} ); }