From cf711489351410439ba78bdc38d92dc6748e6d79 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Mon, 25 May 2026 18:02:36 +0200 Subject: [PATCH] fix: smooth gallery preview seams --- docs/user/gallery.md | 1 + src/index.css | 78 +++++++++++++++++++------------------- src/pages/gallery/page.tsx | 53 +++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 41 deletions(-) diff --git a/docs/user/gallery.md b/docs/user/gallery.md index d3e958a..294972e 100644 --- a/docs/user/gallery.md +++ b/docs/user/gallery.md @@ -23,6 +23,7 @@ Cette page sert à remercier et valoriser le travail des designers du projet La - `SkyModel` réutilise la skybox du jeu, avec un matériau non éclairé uniquement dans la galerie pour éviter que certaines faces deviennent noires avec une caméra orbitale libre. - Les lumières reprennent les valeurs par défaut du jeu, puis peuvent être ajustées dans le panneau latéral. - `OrbitControls` autorise une orbite verticale complète pour inspecter le dessous des modèles. +- Le viewer désactive les normal maps dans la preview pour limiter les coutures visibles sur certains exports découpés en plusieurs meshes. - Les animations GLTF présentes dans un modèle sont lancées automatiquement. - Un diagnostic simple inspecte les matériaux chargés pour signaler les textures absentes ou non exploitables. diff --git a/src/index.css b/src/index.css index 475103a..1cfdbaf 100644 --- a/src/index.css +++ b/src/index.css @@ -36,8 +36,9 @@ canvas { width: 100vw; height: 100vh; overflow: hidden; - background: #05070c; - color: #f8fafc; + background: #050505; + color: #f4efe7; + font-family: "Helvetica Neue", Helvetica, Inter, Arial, sans-serif; } .gallery-title { @@ -46,7 +47,7 @@ canvas { right: clamp(18px, 3vw, 38px); z-index: 2; margin: 0; - color: rgba(248, 250, 252, 0.92); + color: #f4efe7; font-size: clamp(18px, 2vw, 26px); font-weight: 700; letter-spacing: 0.32em; @@ -78,12 +79,11 @@ canvas { grid-template-columns: 54px minmax(190px, 340px) 54px; align-items: center; overflow: hidden; - border: 1px solid rgba(248, 250, 252, 0.18); - border-radius: 999px; - background: rgba(3, 7, 18, 0.72); - box-shadow: 0 18px 52px rgba(0, 0, 0, 0.32); + border: 2px solid #d8d0c4; + border-radius: 0; + background: #050505; + box-shadow: none; transform: translateX(50%); - backdrop-filter: blur(18px); } .gallery-bottom-bar button { @@ -93,7 +93,7 @@ canvas { height: 54px; border: 0; background: transparent; - color: rgba(248, 250, 252, 0.82); + color: #f4efe7; cursor: pointer; transition: background 160ms ease, @@ -102,8 +102,8 @@ canvas { .gallery-bottom-bar button:hover, .gallery-bottom-bar button:focus-visible { - background: rgba(248, 250, 252, 0.1); - color: #f8fafc; + background: #f4efe7; + color: #050505; outline: none; } @@ -112,15 +112,15 @@ canvas { place-items: center; min-height: 54px; padding: 0 20px; - border-right: 1px solid rgba(248, 250, 252, 0.14); - border-left: 1px solid rgba(248, 250, 252, 0.14); + border-right: 2px solid #d8d0c4; + border-left: 2px solid #d8d0c4; text-align: center; } .gallery-model-info span { max-width: 100%; overflow: hidden; - color: #f8fafc; + color: #f4efe7; font-size: 15px; font-weight: 700; letter-spacing: 0.03em; @@ -131,7 +131,7 @@ canvas { .gallery-model-info small { margin-top: 2px; - color: rgba(203, 213, 225, 0.62); + color: #a9a196; font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 600; @@ -147,26 +147,25 @@ canvas { gap: 8px; max-width: min(320px, calc(100vw - 36px)); padding: 10px 13px; - border: 1px solid rgba(248, 250, 252, 0.14); - border-radius: 999px; - background: rgba(3, 7, 18, 0.58); - color: rgba(226, 232, 240, 0.86); + border: 2px solid #d8d0c4; + border-radius: 0; + background: #050505; + color: #d8d0c4; font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; font-weight: 700; - backdrop-filter: blur(16px); } .gallery-texture-status--ok { - color: #bbf7d0; + color: #d8d0c4; } .gallery-texture-status--warning { - color: #fde68a; + color: #f4efe7; } .gallery-texture-status--loading { - color: rgba(226, 232, 240, 0.72); + color: #a9a196; } .gallery-light-panel { @@ -189,30 +188,29 @@ canvas { place-items: center; width: 42px; height: 42px; - border: 1px solid rgba(248, 250, 252, 0.14); + border: 2px solid #d8d0c4; border-right: 0; - border-radius: 999px 0 0 999px; - background: rgba(3, 7, 18, 0.68); - color: rgba(248, 250, 252, 0.84); + border-radius: 0; + background: #050505; + color: #f4efe7; cursor: pointer; - backdrop-filter: blur(14px); } .gallery-light-panel-toggle:hover, .gallery-light-panel-toggle:focus-visible { - color: #f8fafc; + background: #f4efe7; + color: #050505; outline: none; } .gallery-light-panel-content { width: 236px; padding: 16px; - border: 1px solid rgba(248, 250, 252, 0.14); + border: 2px solid #d8d0c4; border-right: 0; - border-radius: 18px 0 0 18px; - background: rgba(3, 7, 18, 0.72); - box-shadow: 0 18px 52px rgba(0, 0, 0, 0.28); - backdrop-filter: blur(18px); + border-radius: 0; + background: #050505; + box-shadow: none; } .gallery-light-panel-content header { @@ -223,7 +221,7 @@ canvas { } .gallery-light-panel-content header span { - color: rgba(248, 250, 252, 0.86); + color: #f4efe7; font-size: 12px; font-weight: 800; letter-spacing: 0.18em; @@ -232,7 +230,7 @@ canvas { .gallery-light-panel-content header button { border: 0; background: transparent; - color: rgba(203, 213, 225, 0.72); + color: #a9a196; cursor: pointer; font-size: 12px; font-weight: 700; @@ -240,7 +238,7 @@ canvas { .gallery-light-panel-content header button:hover, .gallery-light-panel-content header button:focus-visible { - color: #f8fafc; + color: #f4efe7; outline: none; } @@ -254,20 +252,20 @@ canvas { display: flex; align-items: center; justify-content: space-between; - color: rgba(226, 232, 240, 0.78); + color: #d8d0c4; font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; font-weight: 700; } .gallery-light-control strong { - color: rgba(248, 250, 252, 0.88); + color: #f4efe7; font-variant-numeric: tabular-nums; } .gallery-light-control input { width: 100%; - accent-color: #dbeafe; + accent-color: #f4efe7; } @media (max-width: 720px) { diff --git a/src/pages/gallery/page.tsx b/src/pages/gallery/page.tsx index 92f0378..a925d2d 100644 --- a/src/pages/gallery/page.tsx +++ b/src/pages/gallery/page.tsx @@ -143,9 +143,15 @@ function GalleryModelPreview({ }: GalleryModelPreviewProps): React.JSX.Element { const groupRef = useRef(null); const { animations, scene } = useGLTF(model.path); - const modelScene = useMemo(() => scene.clone(true), [scene]); + const modelScene = useMemo(() => createGalleryModelScene(scene), [scene]); const { actions } = useAnimations(animations, groupRef); + useEffect(() => { + return () => { + disposeGalleryModelMaterials(modelScene); + }; + }, [modelScene]); + useEffect(() => { onTextureDiagnosticReady(getTextureDiagnostic(model.id, modelScene)); }, [model.id, modelScene, onTextureDiagnosticReady]); @@ -173,6 +179,51 @@ function GalleryModelPreview({ ); } +function createGalleryModelScene(scene: THREE.Object3D): THREE.Object3D { + const modelScene = scene.clone(true); + + modelScene.traverse((object) => { + if (!(object instanceof THREE.Mesh)) return; + + object.material = Array.isArray(object.material) + ? object.material.map(createGalleryMaterial) + : createGalleryMaterial(object.material); + }); + + return modelScene; +} + +function createGalleryMaterial(material: THREE.Material): THREE.Material { + const galleryMaterial = material.clone(); + const materialWithNormalMap = galleryMaterial as THREE.Material & { + normalMap?: THREE.Texture | null; + }; + + galleryMaterial.side = THREE.DoubleSide; + + if (materialWithNormalMap.normalMap) { + materialWithNormalMap.normalMap = null; + galleryMaterial.needsUpdate = true; + } + + return galleryMaterial; +} + +function disposeGalleryModelMaterials(modelScene: THREE.Object3D): void { + modelScene.traverse((object) => { + if (!(object instanceof THREE.Mesh)) return; + + if (Array.isArray(object.material)) { + for (const material of object.material) { + material.dispose(); + } + return; + } + + object.material.dispose(); + }); +} + function GalleryScene({ lighting, model,