LOD: fige les jumeaux de fente (slit twins) + blend UV linéaire (le fold est supprimé)
- weld: Δ UV = 0.5 exact n'est plus soudé (ambigu: fente à sa plus large vs saut légitime — la colonne u=1 du cône vs le chart disque du cap tombait exactement dessus et mélangeait les charts) - collapse: les UV se blendent linéairement. Le fold par coordonnée (Δ entier → 0) figeait l'UV du sommet sur les vertices de base (bande de rayures, signalée par l'utilisateur) — il était inutile: les jumeaux de fente sont gelés, aucun repli ne traverse la fente - welded renvoie un struct Welded (clippy) - test cône: dual-chart (fan latéral bilinéaire + disque du cap), surface latérale r = λ; test seam/span réécrit selon la sémantique finale - doc: gpu-driven.md, ARCHI_CPU_GPU.md (box LOD), DRAFT.md (D10) alignés sur la sémantique finale (gel des jumeaux, blend linéaire, seuil strict) - AGENTS.md: gotcha 'ne jamais folder un saut entier de tuile' 102 tests passent, demo lance et rend sans erreur.
This commit is contained in:
+17
-10
@@ -123,8 +123,11 @@ capot**. Mécanisme : **quadric edge collapse** (Garland–Heckbert) sur le mail
|
||||
```
|
||||
Geometry::decimated(&self, target_triangles: u32) -> Geometry (pure, sans GPU)
|
||||
```
|
||||
- Weld préalable (tolérance relative 1e-6 — grille + 27 voisins : les seams trigonométriques
|
||||
diffèrent d'environ 1e-16, l'égalité exacte ne suffit pas), triangles dégénérés jetés, puis
|
||||
- Weld préalable **conscient des attributs** (tolérance relative 1e-6 — grille + 27 voisins : les
|
||||
seams trigonométriques diffèrent d'environ 1e-16, l'égalité exacte ne suffit pas ; un doublon ne
|
||||
fusionne que si UV strictement < ½ tuile par coordonnée — un Δ = ½ exact est ambigu, fente à sa
|
||||
plus large vs saut légitime — ET normales à ~25° ; les paires refusées à UV écart d'entier sont
|
||||
**enregistrées** comme jumeaux de fente), triangles dégénérés jetés, puis
|
||||
**quadric edge collapse** : file de priorité des arêtes classées par coût (erreur quadrique de
|
||||
l'arête + longueur d'arête) ; on replie la plus bon marché jusqu'à la cible. Un repli **interne**
|
||||
fusionne les 2 triangles incidents (ils dégénèrent — −2 faces) et re-mappe les voisins :
|
||||
@@ -133,13 +136,16 @@ Geometry::decimated(&self, target_triangles: u32) -> Geometry (pure, sans GPU)
|
||||
Garde-fous : arête non-manifold (≥ 3 faces) ou face en double (pli) → repli rejeté. La cible
|
||||
est clampée à `[1, T]` et atteinte au mieux (best effort : la granularité −2/−1 peut s'en
|
||||
écarter d'un ou deux triangles — jamais de géométrie corrompue).
|
||||
- Sortie : re-indexation (le weld ci-dessus), **normales lisses recalculées** sur les faces
|
||||
survivantes (normales de faces accumulées par coin soudé, puis normalisées). UVs/couleurs :
|
||||
le vertex **déplacé** par un repli reçoit l'**interpolation** des UVs/couleurs de la paire
|
||||
(même λ que son nouveau point optimal) — la texture reste attachée à la surface et se
|
||||
grossit doucement d'un niveau à l'autre (pas de saut radical) ; **jamais d'interpolation à
|
||||
travers une seam UV** (extrémités à plus de 0.5 de tuile d'écart → la cible garde son UV) ;
|
||||
un groupe soudé garde l'UV du premier vertex rencontré (déterministe).
|
||||
- Sortie : re-indexation (le weld ci-dessus). Normales : **héritées de la source, jamais
|
||||
recalculées** (elles passent telles quelles et sont interpolées par le repli — l'éclairage
|
||||
reste identique au niveau 0 quelle que soit l'orientation de la source). UVs/couleurs :
|
||||
le vertex **déplacé** par un repli reçoit le **blend linéaire** de la paire (même λ que son
|
||||
nouveau point optimal) — le chart est bilinéaire, donc le blend est la valeur exacte du chart
|
||||
au nouveau point : la texture reste attachée à la surface et se grossit doucement d'un niveau
|
||||
à l'autre (pas de saut radical) ; **jamais de blend à travers une seam UV** — les jumeaux de
|
||||
fente (même position, UV écart d'entier) sont **gelés** : toute arête qui y touche est exclue
|
||||
de la file, donc aucun repli ne traverse la fente (c'est le gel qui protège, pas un rejet de
|
||||
blend) ; un groupe soudé garde l'UV du premier vertex rencontré (déterministe).
|
||||
- Non indexé à l'entrée → weld par position préalable (la sortie est **toujours indexée**).
|
||||
Triangles dégénérés jetés.
|
||||
- **Déterministe** (file de priorité, tie-break par identifiants de vertex, aucun aléatoire) →
|
||||
@@ -263,7 +269,8 @@ pub fn projected_radius_px(center_world: Vec3, radius: f32, view: Mat4, proj: Ma
|
||||
|
||||
/// Quadric edge collapse (D10, Garland–Heckbert) : renvoie un `Geometry` indexé avec
|
||||
/// ~`target_triangles` triangles (best effort ±1–2, clamp `[1, T]`) — les arêtes au coût
|
||||
/// quadrique le plus faible partent en premier, normales lisses recalculées. Déterministe.
|
||||
/// quadrique le plus faible partent en premier, normales **héritées** de la source (jamais
|
||||
/// recalculées), UVs/couleurs blendés linéairement, jumeaux de fente gelés. Déterministe.
|
||||
pub fn decimated(&self, target_triangles: u32) -> Geometry; // sur Geometry
|
||||
```
|
||||
|
||||
|
||||
@@ -43,11 +43,16 @@ Ce document sert de spécification technique et de trame d'implémentation pour
|
||||
> le GPU n'effectue que le mappage niveau → ligne de la table LOD du mesh. Les niveaux d'un mesh sont
|
||||
> générés par **quadric edge collapse** (Garland–Heckbert) au setup (`Geometry::decimated` : les
|
||||
> arêtes au coût quadrique minimal sont repliées en premier ; soudure **consciente des attributs**
|
||||
> — un doublon ne fusionne que si UV proches (≤ ½ tuile) ET normales proches (dot > 0.9) — ; un mesh
|
||||
> sans couture reste fermé, sur un mesh couturé les lèvres de couture sont protégées (pas de trous,
|
||||
> pas de « books ») ; UVs/couleurs/normales interpolés au repli, normales **héritées** de la source
|
||||
> (jamais recalculées — l'éclairage reste identique au niveau 0 quelle que soit l'orientation source),
|
||||
> jamais à travers une seam UV ; rebase u16) et **empilés dans les buffers vertex/index du mesh** (offsets en
|
||||
> — un doublon ne fusionne que si UV strictement < ½ tuile par coordonnée (un Δ = ½ exact est
|
||||
> ambigu : fente à sa plus large vs saut légitime) ET normales proches (dot > 0.9) ; les paires
|
||||
> refusées à UV écart d'entier sont **enregistrées** (jumeaux de fente) — ; un mesh sans couture
|
||||
> reste fermé (pas de trous, pas de « books »), et sur un mesh couturé les jumeaux de fente sont
|
||||
> **gelés** (toute arête qui y touche est exclue de la file — la fente zéro-largeur reste fermée à
|
||||
> tous les niveaux) ; UVs/couleurs/normales **blendés linéairement** au repli (le chart est
|
||||
> bilinéaire → exact au nouveau point — jamais de fold de tuile, qui figeait l'UV d'un sommet sur
|
||||
> les vertices de base), normales **héritées** de la source (jamais recalculées — l'éclairage reste
|
||||
> identique au niveau 0 quelle que soit l'orientation source) ; rebase u16) et **empilés dans les
|
||||
> buffers vertex/index du mesh** (offsets en
|
||||
> unités d'élément, pas d'octet — c'est ce qu'exigent les arguments `drawIndirect*` de WebGPU ; plafond u16 :
|
||||
> 65 535 sommets/mesh, 4 niveaux max). LOD activé par défaut ; `set_lod_enabled(false)` restaure un rendu
|
||||
> bit-à-bit identique au pré-LOD (niveau 0 partout = comptes complets). Détail : `docs/user/gpu-driven.md`
|
||||
|
||||
+14
-9
@@ -98,15 +98,20 @@ CPU):
|
||||
cheapest-first; an interior collapse merges both incident triangles (−2 faces) and remaps the
|
||||
neighbours — no new face, so a **seam-free mesh stays closed** (no holes, no non-manifold
|
||||
"books") and a boundary collapse removes one face; duplicate corners are welded **aware of
|
||||
their attributes** (relative position tolerance 1e-6, merged only when the UVs are within half
|
||||
a tile and the normals within ~25° — a seam or a hard edge stays a separate corner, so a
|
||||
collapse can never cross a texture chart or a shading break; on a mesh with a UV seam the
|
||||
seam's lips are then *protected* — their faces never collapse while interior ones remain — so
|
||||
the surface stays geometrically complete); a survivor **moved** by a collapse gets its
|
||||
UV/color/normal **interpolated** between the collapsed endpoints (same λ as its new position —
|
||||
texture and shading stay attached to the surface and coarsen smoothly across levels, never
|
||||
across a UV seam; normals are **inherited from the source, never recomputed**, so the lighting
|
||||
is identical to level 0 whatever the source's winding), and the
|
||||
their attributes** (relative position tolerance 1e-6, merged only when the UVs are strictly
|
||||
less than half a tile apart on both coordinates — an offset of exactly ½ is ambiguous: a wrap
|
||||
seam at its widest or a legitimate half-tile jump — and the normals within ~25°; a seam or a
|
||||
hard edge therefore stays a separate corner, and the weld *records* the integer-apart pairs it
|
||||
refused); on a mesh with a UV seam those **seam twins are frozen** — every edge touching one
|
||||
is excluded from the collapse queue — so the zero-width slit stays closed at every level, and
|
||||
the rim protection (no boundary collapse while any interior edge remains) keeps the surface
|
||||
geometrically complete; a survivor **moved** by a collapse gets its UV/color/normal
|
||||
**blended linearly** between the collapsed endpoints (same λ as its new position — the chart
|
||||
is bilinear, so the blend is the exact chart value at the new point: texture and shading stay
|
||||
attached to the surface and coarsen smoothly across levels, and a seam is never crossed because
|
||||
its twins are frozen, not because a blend is rejected; normals are **inherited from the
|
||||
source, never recomputed**, so the lighting is identical to level 0 whatever the source's
|
||||
winding), and the
|
||||
levels are **packed into the mesh's single
|
||||
vertex/index buffers** (see the constraint below). Level 0 is always your exact geometry.
|
||||
2. **Per frame (CPU).** For each entity, the bounding sphere used by culling is projected to screen
|
||||
|
||||
Reference in New Issue
Block a user