From a73f9fb951ce359b9eeafb242598a0c5f0bc745e Mon Sep 17 00:00:00 2001 From: math-pixel <59537610+math-pixel@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:21:52 +0200 Subject: [PATCH] fixed ebike --- .../Cable 1_occlusionRoughnessMetallic.png | 3 - .../Cable2_occlusionRoughnessMetallic.png | 3 - ...Color.png => Carroserie_baseColor.png.png} | 0 ...e_normal.png => Carroserie_normal.png.png} | 0 .../Carroserie_occlusionRoughnessMetallic.png | 3 - public/models/ebike/Ferail_baseColor.png | 3 - public/models/ebike/Ferail_baseColor.png.png | 3 + public/models/ebike/Ferail_normal.png | 4 +- .../Ferail_occlusionRoughnessMetallic.png | 3 - public/models/ebike/Reservoir_baseColor.png | 3 - .../models/ebike/Reservoir_baseColor.png.png | 3 + public/models/ebike/Reservoir_normal.png | 3 - public/models/ebike/Reservoir_normal.png.png | 3 + .../Reservoir_occlusionRoughnessMetallic.png | 3 - ...ac_baseColor.png => Sac_baseColor.png.png} | 0 .../{Sac_normal.png => Sac_normal.png.png} | 0 .../ebike/Sac_occlusionRoughnessMetallic.png | 3 - ..._baseColor.png => Siege_baseColor.png.png} | 0 ...{Siege_normal.png => Siege_normal.png.png} | 0 .../Siege_occlusionRoughnessMetallic.png | 3 - public/models/ebike/color.png | 3 - public/models/ebike/ebike.bin | 3 - public/models/ebike/model.bin | 4 +- public/models/ebike/model.gltf | 4 +- public/models/ebike/normal.png | 3 - public/models/ebike/panneau_baseColor.png | 3 + public/models/ebike/panneau_normal.png | 3 + public/models/ebike/phare_baseColor.png | 3 - public/models/ebike/phare_baseColor.png.png | 3 + public/models/ebike/phare_normal.png | 3 - public/models/ebike/phare_normal.png.png | 3 + .../phare_occlusionRoughnessMetallic.png | 3 - ...u_baseColor.png => pneu_baseColor.png.png} | 0 .../{pneu_normal.png => pneu_normal.png.png} | 0 .../ebike/pneu_occlusionRoughnessMetallic.png | 3 - ...or.png => refroidisseur_baseColor.png.png} | 0 ...ormal.png => refroidisseur_normal.png.png} | 0 ...froidisseur_occlusionRoughnessMetallic.png | 3 - ...baseColor.png => resort_baseColor.png.png} | 0 ...esort_normal.png => resort_normal.png.png} | 0 .../resort_occlusionRoughnessMetallic.png | 3 - src/components/ebike/Ebike.tsx | 165 +++++++++++++++--- 42 files changed, 163 insertions(+), 92 deletions(-) delete mode 100644 public/models/ebike/Cable 1_occlusionRoughnessMetallic.png delete mode 100644 public/models/ebike/Cable2_occlusionRoughnessMetallic.png rename public/models/ebike/{Carroserie_baseColor.png => Carroserie_baseColor.png.png} (100%) rename public/models/ebike/{Carroserie_normal.png => Carroserie_normal.png.png} (100%) delete mode 100644 public/models/ebike/Carroserie_occlusionRoughnessMetallic.png delete mode 100644 public/models/ebike/Ferail_baseColor.png create mode 100644 public/models/ebike/Ferail_baseColor.png.png delete mode 100644 public/models/ebike/Ferail_occlusionRoughnessMetallic.png delete mode 100644 public/models/ebike/Reservoir_baseColor.png create mode 100644 public/models/ebike/Reservoir_baseColor.png.png delete mode 100644 public/models/ebike/Reservoir_normal.png create mode 100644 public/models/ebike/Reservoir_normal.png.png delete mode 100644 public/models/ebike/Reservoir_occlusionRoughnessMetallic.png rename public/models/ebike/{Sac_baseColor.png => Sac_baseColor.png.png} (100%) rename public/models/ebike/{Sac_normal.png => Sac_normal.png.png} (100%) delete mode 100644 public/models/ebike/Sac_occlusionRoughnessMetallic.png rename public/models/ebike/{Siege_baseColor.png => Siege_baseColor.png.png} (100%) rename public/models/ebike/{Siege_normal.png => Siege_normal.png.png} (100%) delete mode 100644 public/models/ebike/Siege_occlusionRoughnessMetallic.png delete mode 100644 public/models/ebike/color.png delete mode 100644 public/models/ebike/ebike.bin delete mode 100644 public/models/ebike/normal.png create mode 100644 public/models/ebike/panneau_baseColor.png create mode 100644 public/models/ebike/panneau_normal.png delete mode 100644 public/models/ebike/phare_baseColor.png create mode 100644 public/models/ebike/phare_baseColor.png.png delete mode 100644 public/models/ebike/phare_normal.png create mode 100644 public/models/ebike/phare_normal.png.png delete mode 100644 public/models/ebike/phare_occlusionRoughnessMetallic.png rename public/models/ebike/{pneu_baseColor.png => pneu_baseColor.png.png} (100%) rename public/models/ebike/{pneu_normal.png => pneu_normal.png.png} (100%) delete mode 100644 public/models/ebike/pneu_occlusionRoughnessMetallic.png rename public/models/ebike/{refroidisseur_baseColor.png => refroidisseur_baseColor.png.png} (100%) rename public/models/ebike/{refroidisseur_normal.png => refroidisseur_normal.png.png} (100%) delete mode 100644 public/models/ebike/refroidisseur_occlusionRoughnessMetallic.png rename public/models/ebike/{resort_baseColor.png => resort_baseColor.png.png} (100%) rename public/models/ebike/{resort_normal.png => resort_normal.png.png} (100%) delete mode 100644 public/models/ebike/resort_occlusionRoughnessMetallic.png 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..872a8d9 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:c40a3e0a6a1a7101e5b6d05ae88f3f46cd54271b4617c2aaec805e4bca1fb437 +size 2152008 diff --git a/public/models/ebike/model.gltf b/public/models/ebike/model.gltf index 178b740..53b91f2 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:7e1b7c581c3416a2563f20a8331021e09e0861973bf615b5e13f343a5b71757e +size 80265 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/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index 502df8c..a4c6215 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -25,6 +25,12 @@ import "@/types/ebike/ebikeWindow"; const EBIKE_MODEL_PATH = "/models/ebike/model.gltf"; +// Reusable vectors — allocated once to avoid per-frame GC pressure +const _phareWorldPos = new THREE.Vector3(); +const _bikeForward = new THREE.Vector3(); +const _aimDir = new THREE.Vector3(); +const _up = new THREE.Vector3(0, 1, 0); + interface EbikeProps { position: Vector3Tuple; } @@ -53,6 +59,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { const ebikeStep = useGameStore((state) => state.ebike.currentStep); const setMissionStep = useGameStore((state) => state.setMissionStep); const camera = useThree((state) => state.camera); + const threeScene = useThree((state) => state.scene); const updateEbikeSounds = useEbikeSounds(); const repairGameOwnsEbikeModel = mainState === "ebike" && @@ -96,6 +103,19 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { ]); const restingRotationRef = useRef(EBIKE_WORLD_ROTATION_Y); const forkRef = useRef(null); + const phareRef = useRef(null); + const headlightRef = useRef(null); + // SpotLight target — must live in the scene to define the cone direction. + const headlightTarget = useMemo(() => new THREE.Object3D(), []); + // Original quaternion of the Fourche node — rotation is applied on top of this. + const forkInitialQuatRef = useRef(new THREE.Quaternion()); + // Smoothed steer angle for the fork (avoids direct Euler manipulation). + const forkAngleRef = useRef(0); + // Ref copy of movementMode — useFrame closures can capture stale React state. + const movementModeRef = useRef(movementMode); + // Becomes true the first time the player mounts. After that, dismounting + // must NOT reset position back to the original spawn point. + const hasRiddenRef = useRef(false); // State for debug visualization (synced from refs during useFrame) const [showCameraPoints, setShowCameraPoints] = useState(true); @@ -106,9 +126,42 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { parkedPosition[2], ]); + // Keep movementModeRef in sync — useFrame closures capture React state at + // render time and can become stale between renders. useEffect(() => { - if (movementMode === "ebike") return; + movementModeRef.current = movementMode; + }, [movementMode]); + // SpotLight target must be in the scene to define the cone direction. + useEffect(() => { + threeScene.add(headlightTarget); + return () => { threeScene.remove(headlightTarget); }; + }, [threeScene, headlightTarget]); + + // Link the target to the SpotLight once it mounts. + useEffect(() => { + if (headlightRef.current) { + headlightRef.current.target = headlightTarget; + } + }, [headlightTarget]); + + useEffect(() => { + if (movementMode === "ebike") { + // Player just mounted — mark as ridden so we never reset position again. + hasRiddenRef.current = true; + return; + } + + if (hasRiddenRef.current) { + // Player dismounted: keep the position the bike was left at. + // Just make sure the window vars are up to date for the next mount. + window.ebikeParkedPosition = restingPositionRef.current; + window.ebikeParkedRotation = restingRotationRef.current; + return; + } + + // Bike has never been ridden yet — safe to (re)place it at the spawn point. + // This also fires when parkedPosition recalculates (e.g. terrain loads late). restingPositionRef.current = parkedPosition; restingRotationRef.current = EBIKE_WORLD_ROTATION_Y; lastGpsUpdatePos.current.set(...parkedPosition); @@ -125,33 +178,22 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { useEffect(() => { if (!model) return; - // Full recursive search — case-insensitive so it survives export renames. - // Also tries the exact path Moto > * > Fourche as a fallback. let forkNode: THREE.Object3D | null = null; - model.traverse((child) => { - if (child.name.toLowerCase() === "fourche") { - forkNode = child; - } + if (child.name.toLowerCase() === "fourche") forkNode = child; + if (child.name === "Phare") phareRef.current = child; }); if (forkNode) { forkRef.current = forkNode; + // Snapshot the rest-pose quaternion — steering is applied on top of this. + forkInitialQuatRef.current.copy((forkNode as THREE.Object3D).quaternion); + forkAngleRef.current = 0; console.log("[Ebike] Fork found:", (forkNode as THREE.Object3D).name); } else { - // Print the full hierarchy tree so you can read the exact node names. - const lines: string[] = []; - function printTree(obj: THREE.Object3D, indent: number): void { - lines.push(" ".repeat(indent * 2) + (obj.name || "(unnamed)")); - for (const child of obj.children) { - printTree(child, indent + 1); - } - } - printTree(model, 0); - console.warn( - '[Ebike] No node matching "fourche" (case-insensitive) found.\nFull hierarchy:\n' + - lines.join("\n"), - ); + const names: string[] = []; + model.traverse((c) => { if (c.name) names.push(c.name); }); + console.warn("[Ebike] Fork not found. All nodes:", names); } }, [model]); @@ -178,8 +220,43 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { }, []); useFrame((_, delta) => { + // ── SpotLight headlight — tune the constants below ──────────────────────── + // ── SpotLight headlight — tune these four constants ─────────────────────── + const LIGHT_OFFSET_X = -0.7; // position : left(-) / right(+) + const LIGHT_OFFSET_Y = 1.5; // position : down(-) / up(+) + const LIGHT_OFFSET_Z = 0; // position : backward(-) / forward(+) + const LIGHT_AIM_DEG = 90; // aim rotation around Y : 0=forward, -90=left, +90=right + const LIGHT_TARGET_DIST = 20; // metres devant la position de la lumière + // ───────────────────────────────────────────────────────────────────────── + if (headlightRef.current && phareRef.current && groupRef.current) { + phareRef.current.getWorldPosition(_phareWorldPos); + groupRef.current.getWorldDirection(_bikeForward); + + // Position offset in bike-local space (no GC — reusing module-level vectors) + const right = _bikeForward.clone().cross(_up).normalize(); + _phareWorldPos + .addScaledVector(right, LIGHT_OFFSET_X) + .addScaledVector(_up, LIGHT_OFFSET_Y) + .addScaledVector(_bikeForward, LIGHT_OFFSET_Z); + + headlightRef.current.position.copy(_phareWorldPos); + + // Aim direction: rotate forward around Y by LIGHT_AIM_DEG + _aimDir + .copy(_bikeForward) + .applyAxisAngle(_up, THREE.MathUtils.degToRad(LIGHT_AIM_DEG)); + + headlightTarget.position + .copy(_phareWorldPos) + .addScaledVector(_aimDir, LIGHT_TARGET_DIST); + headlightTarget.updateMatrixWorld(); + } + // ────────────────────────────────────────────────────────────────────────── + if (groupRef.current) { - if (movementMode === "ebike") { + // Use the ref — not the React state — to avoid stale closure bugs in + // R3F's frame loop (the state value may not update until the next render). + if (movementModeRef.current === "ebike") { // Sound plays whenever the bike is actually moving (speedFactor > 5 %), // not only while the input key is held. updateEbikeSounds({ @@ -195,16 +272,31 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { ]; restingRotationRef.current = groupRef.current.rotation.y; - // Smoothly rotate the front fork ("fourche") on its local Z axis + // ── Fork steering via quaternion ────────────────────────────────────── + // We rotate around the fork's LOCAL Y axis (steering tube) by composing + // a fresh quaternion on top of the rest-pose snapshot taken at load time. + // This is axis-agnostic: correct regardless of how Blender exported the node. + // Tune FORK_ANGLE (radians) or negate it if the visual direction is wrong. + const FORK_ANGLE = 0.12; // 10° const steerFactor = window.ebikeSteerFactor ?? 0; + if (forkRef.current) { - // 10 degrees = 0.175 radians - const targetForkRotation = steerFactor * 0.175; - forkRef.current.rotation.z = THREE.MathUtils.lerp( - forkRef.current.rotation.z, - targetForkRotation, + // Smooth the angle separately so we can apply it cleanly each frame. + forkAngleRef.current = THREE.MathUtils.lerp( + forkAngleRef.current, + steerFactor * FORK_ANGLE, 12 * delta, ); + // Build steer quat around LOCAL Y of the fork node. + const steerQuat = new THREE.Quaternion().setFromAxisAngle( + new THREE.Vector3(0, 1, 0), + forkAngleRef.current, + ); + // Apply on top of rest-pose: Q_final = Q_rest × Q_steer + forkRef.current.quaternion.multiplyQuaternions( + forkInitialQuatRef.current, + steerQuat, + ); } // Throttled GPS start position update to prevent performance loss @@ -223,9 +315,10 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { groupRef.current.position.set(...restingPositionRef.current); groupRef.current.rotation.set(0, restingRotationRef.current, 0); - // Reset fork rotation when parked + // Reset fork to rest-pose when parked if (forkRef.current) { - forkRef.current.rotation.z = 0; + forkRef.current.quaternion.copy(forkInitialQuatRef.current); + forkAngleRef.current = 0; } } window.ebikeParkedPosition = restingPositionRef.current; @@ -400,6 +493,20 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { ) : null} + {/* SpotLight headlight — cone aimed forward, position & target via useFrame */} + {!repairGameOwnsEbikeModel && ( + + )} + {showCameraPoints && !repairGameOwnsEbikeModel && ( <> {/*