fix(world): openRing() stürzt bei leerem oder fehlendem Außenring nicht mehr ab
Ruling 47 (Fix-Runde 1, Befund A): Die Längenprüfung stand erst im return, destrukturiert wurde davor. extrudePolygons/flatPolygons warfen dadurch bei einem Polygon mit leerem Außenring ([[]]) oder ganz ohne Ringe ([]), statt wie der Rest des Moduls zu null zu degradieren. Längenprüfung nach vorn gezogen und zusätzlich raw[0] am Aufrufort auf [] defaultet, da ein Polygon ganz ohne Ringe openRing() ein undefined statt eines leeren Rings übergibt — die Prüfung allein in openRing() fängt diesen zweiten Fall nicht ab. Dazu Ruling 49: Regressionstest für die Wickelrichtung (Außenwände nach außen, Lochwände nach innen, Dach nach oben) anhand von COURTYARD. Bestand beim ersten Lauf unverändert, keine Anpassung am Modul nötig. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4c82b14a57
commit
962bfef778
2 files changed
+58
-3
No files matched your search
@@ -108,4 +108,48 @@ describe("geometry", () => {
|
||||
const geo = flatPolygons([COURTYARD_CLOSED], { y: 0, uvScale: 4 });
|
||||
expect(footprintArea(geo)).toBeCloseTo(800, 6);
|
||||
});
|
||||
|
||||
// Fix-Runde 1, Befund A / Ruling 47: ein Polygon mit leerem Außenring oder ganz ohne
|
||||
// Ringe muss wie eine leere Fläche zu null degradieren, nicht werfen.
|
||||
it("gibt bei einem Polygon mit leerem Außenring oder ganz ohne Ringe null zurück, statt zu werfen", () => {
|
||||
expect(extrudePolygons([[[]]], { base: 0, top: 10, uvScale: 4 })).toBeNull();
|
||||
expect(extrudePolygons([[]], { base: 0, top: 10, uvScale: 4 })).toBeNull();
|
||||
expect(flatPolygons([[[]]], { y: 0, uvScale: 4 })).toBeNull();
|
||||
expect(flatPolygons([[]], { y: 0, uvScale: 4 })).toBeNull();
|
||||
});
|
||||
|
||||
// Fix-Runde 1, Befund B / Ruling 49.
|
||||
it("wendet Außenwände nach außen, Lochwände nach innen und Deckflächen nach oben", () => {
|
||||
const geo = extrudePolygons([COURTYARD], { base: 0, top: 10, uvScale: 4 });
|
||||
const pos = geo.getAttribute("position"), idx = geo.getIndex();
|
||||
const MITTE = 15; // Außenring 30x30 und Loch 10x10 teilen den Mittelpunkt
|
||||
let deckel = 0, aussen = 0, innen = 0;
|
||||
for (let t = 0; t < idx.count; t += 3) {
|
||||
const p = [0, 1, 2].map((k) => {
|
||||
const i = idx.getX(t + k);
|
||||
return [pos.getX(i), pos.getY(i), pos.getZ(i)];
|
||||
});
|
||||
const u = [0, 1, 2].map((a) => p[1][a] - p[0][a]);
|
||||
const v = [0, 1, 2].map((a) => p[2][a] - p[0][a]);
|
||||
const n = [u[1] * v[2] - u[2] * v[1],
|
||||
u[2] * v[0] - u[0] * v[2],
|
||||
u[0] * v[1] - u[1] * v[0]];
|
||||
const mid = [0, 1, 2].map((a) => (p[0][a] + p[1][a] + p[2][a]) / 3);
|
||||
if (Math.abs(n[0]) < 1e-6 && Math.abs(n[2]) < 1e-6) {
|
||||
expect(n[1]).toBeGreaterThan(0); // Deckfläche nach oben
|
||||
deckel++;
|
||||
continue;
|
||||
}
|
||||
const radial = [mid[0] - MITTE, mid[2] - MITTE];
|
||||
const dot = n[0] * radial[0] + n[2] * radial[1];
|
||||
if (Math.max(Math.abs(radial[0]), Math.abs(radial[1])) > 10) {
|
||||
expect(dot).toBeGreaterThan(0); // Außenwand zeigt vom Mittelpunkt weg
|
||||
aussen++;
|
||||
} else {
|
||||
expect(dot).toBeLessThan(0); // Lochwand zeigt in den Hof hinein
|
||||
innen++;
|
||||
}
|
||||
}
|
||||
expect([deckel, aussen, innen]).toEqual([8, 8, 8]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user