fix(props): Straßenlaternen auf glaubwürdige Höhe skaliert, Fuß auf den Boden gehoben
street_lamp_02.glb ist nur 1,657 m hoch (per gltf-transform inspect gemessen:
bboxMax.y 1.26478 - bboxMin.y -0.39258) und ihr Fuß liegt unter dem Ursprung.
#buildLamps setzte das Modell bisher unskaliert auf y=0 — der Fuß steckt im Boden,
und 1,66 m ist ohnehin zu klein neben 10-18 m hohen Palmen und 10-28 m breiten
Straßen dieser Karte. Poly Havens eigene Angabe ("1.7m tall") bestätigt, dass die
Rohgröße korrekt gemessen ist — der Fehler liegt in der Verwendung dieser Rohgröße
in diesem Maßstab, nicht in der Messung selbst.
lampFit(minY, maxY, targetHeight) leitet Skalierung und Fuß-Anhebung aus der
gemessenen bbox her; STREET_LAMP_HEIGHT = 4.0 m als benannte Zielgröße (Mitte des
für dekorative Straßenlaternen üblichen 3-6-m-Bereichs). load() wendet beides auf
die Modellwurzel an, bevor die Teil-Matrizen (Mast, Glas) eingesammelt werden, damit
#buildLamps() unverändert bleiben kann.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b273bb09cc
commit
ad9bf63b36
2 files changed
+82
-5
No files matched your search
+39
-4
@@ -1,4 +1,4 @@
|
|||||||
import { CylinderGeometry, Group, InstancedMesh, Matrix4, Mesh, MeshStandardMaterial,
|
import { Box3, CylinderGeometry, Group, InstancedMesh, Matrix4, Mesh, MeshStandardMaterial,
|
||||||
Object3D, RepeatWrapping, SRGBColorSpace, TextureLoader } from "three";
|
Object3D, RepeatWrapping, SRGBColorSpace, TextureLoader } from "three";
|
||||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||||
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
|
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";
|
||||||
@@ -8,6 +8,31 @@ const PALM_MODEL_HEIGHT = 439.74; // Einheiten im OBJ, gemessen
|
|||||||
const TRUNK_RADIUS = 0.22;
|
const TRUNK_RADIUS = 0.22;
|
||||||
const LAMP_VERGE = [6, 8, 10, 12, 14, 16]; // Meter, von innen nach außen (Ruling 60)
|
const LAMP_VERGE = [6, 8, 10, 12, 14, 16]; // Meter, von innen nach außen (Ruling 60)
|
||||||
|
|
||||||
|
// Befund B (Fix-Runde 3): street_lamp_02.glb ist mit `npx @gltf-transform/cli@4.5.0 inspect`
|
||||||
|
// vermessen nur 1,65736 m hoch (bboxMax.y 1.26478 - bboxMin.y -0.39258) — Poly Haven selbst
|
||||||
|
// nennt das Modell "1.7m tall" (https://polyhaven.com/a/street_lamp_02), die Messung bestätigt
|
||||||
|
// also die Rohgröße des Assets. Zu klein ist sie trotzdem: dekorative Straßen-/Parklaternen
|
||||||
|
// liegen real üblicherweise bei 3-6 m, und neben 10-18 m hohen Palmen (PALM_MODEL_HEIGHT-
|
||||||
|
// Skalierung oben) und 10-28 m breiten Straßen dieser Karte wirkt 1,66 m spielzeughaft klein.
|
||||||
|
// 4 m liegt mittig in diesem Bereich und ist mehr als das Doppelte der Rohgröße — eine
|
||||||
|
// bewusste, keine zufällige Skalierung (anders als bei den Palmen: alle Laternen sind gleich
|
||||||
|
// hoch, wie ein echtes Serienprodukt).
|
||||||
|
export const STREET_LAMP_HEIGHT = 4.0; // Meter, Zielhöhe (benannte Konstante, s. Bericht)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skalierung und Y-Anhebung, damit ein Modell mit gemessener lokaler bbox [minY, maxY] genau
|
||||||
|
* targetHeight hoch dasteht und sein tiefster Punkt (der Fuß, minY) nach der Skalierung exakt
|
||||||
|
* auf y = 0 zu stehen kommt — unabhängig davon, ob der Modellursprung selbst am Fuß liegt.
|
||||||
|
* street_lamp_02.glb liegt das nicht: bboxMin.y ist negativ, der Fuß also unter dem Ursprung
|
||||||
|
* (Befund B) — ohne die Anhebung um |bboxMin.y| * scale stünde das skalierte Modell weiterhin
|
||||||
|
* teilweise im Boden.
|
||||||
|
*/
|
||||||
|
export function lampFit(minY, maxY, targetHeight = STREET_LAMP_HEIGHT) {
|
||||||
|
const modelHeight = maxY - minY;
|
||||||
|
const scale = modelHeight > 0 ? targetHeight / modelHeight : 1;
|
||||||
|
return { scale, liftY: -minY * scale };
|
||||||
|
}
|
||||||
|
|
||||||
/** Kleiner, wiederholbarer Zufall aus einem Schlüssel. */
|
/** Kleiner, wiederholbarer Zufall aus einem Schlüssel. */
|
||||||
function hashUnit(seed, key) {
|
function hashUnit(seed, key) {
|
||||||
let h = 2166136261 ^ seed;
|
let h = 2166136261 ^ seed;
|
||||||
@@ -126,6 +151,14 @@ export class PropLayer {
|
|||||||
// Referenzcode fehlte das.
|
// Referenzcode fehlte das.
|
||||||
gltfLoader.setMeshoptDecoder(MeshoptDecoder);
|
gltfLoader.setMeshoptDecoder(MeshoptDecoder);
|
||||||
this.lamp = (await gltfLoader.loadAsync("./models/props/street_lamp_02.glb")).scene;
|
this.lamp = (await gltfLoader.loadAsync("./models/props/street_lamp_02.glb")).scene;
|
||||||
|
// Befund B: Skalierung und Fuß-Anhebung auf die Modellwurzel anwenden, BEVOR unten die
|
||||||
|
// Teil-Matrizen eingesammelt werden — jedes lampParts[i].matrix ist matrixWorld relativ
|
||||||
|
// zu dieser Wurzel und bekommt scale/liftY dadurch automatisch mit; #buildLamps() selbst
|
||||||
|
// bleibt unverändert (platziert weiterhin nur bei y = 0 auf dem Boden).
|
||||||
|
const bbox = new Box3().setFromObject(this.lamp);
|
||||||
|
const { scale, liftY } = lampFit(bbox.min.y, bbox.max.y);
|
||||||
|
this.lamp.scale.setScalar(scale);
|
||||||
|
this.lamp.position.y = liftY;
|
||||||
// Anders als die einteilige Palme hat street_lamp_02.glb zwei Materialien — Metallmast
|
// Anders als die einteilige Palme hat street_lamp_02.glb zwei Materialien — Metallmast
|
||||||
// und durchsichtiges Laternenglas (glTF-Mesh "street_lamp_02_stem_mesh", primitives:
|
// und durchsichtiges Laternenglas (glTF-Mesh "street_lamp_02_stem_mesh", primitives:
|
||||||
// [material 1, material 0], per gltf-transform inspect nachgeprüft) — GLTFLoader baut
|
// [material 1, material 0], per gltf-transform inspect nachgeprüft) — GLTFLoader baut
|
||||||
@@ -187,9 +220,11 @@ export class PropLayer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Laternen entlang der Mittellinien (lampPoints), seitlich zur Fahrbahnkante versetzt
|
* Laternen entlang der Mittellinien (lampPoints), seitlich zur Fahrbahnkante versetzt
|
||||||
* (vergeLampPoints, Ruling 60 — Fix-Runde 1; siehe dort für die Begründung). Das Modell
|
* (vergeLampPoints, Ruling 60 — Fix-Runde 1; siehe dort für die Begründung). Skalierung
|
||||||
* ist bei 1,7 m laut Poly-Haven-Angabe bereits maßstäblich, anders als die Palme also
|
* auf STREET_LAMP_HEIGHT und die Fuß-Anhebung stecken bereits in this.lampParts[*].matrix
|
||||||
* keine Zufallsskalierung.
|
* (s. load(), Befund B / Fix-Runde 3) — hier wird jede Laterne nur noch bei y = 0 auf den
|
||||||
|
* Boden gesetzt. Anders als bei der Palme keine Zufallsskalierung je Instanz: jede Laterne
|
||||||
|
* ist gleich hoch, wie ein echtes Serienprodukt.
|
||||||
*/
|
*/
|
||||||
#buildLamps(chunk, visual) {
|
#buildLamps(chunk, visual) {
|
||||||
const centerlinePoints = lampPoints(chunk.centerlines ?? []);
|
const centerlinePoints = lampPoints(chunk.centerlines ?? []);
|
||||||
|
|||||||
+43
-1
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { lampPoints, palmTransforms, pointInPolygon, vergeLampPoints } from "../src/world/props.js";
|
import { STREET_LAMP_HEIGHT, lampFit, lampPoints, palmTransforms, pointInPolygon, vergeLampPoints } from "../src/world/props.js";
|
||||||
|
|
||||||
describe("props", () => {
|
describe("props", () => {
|
||||||
it("setzt Laternen im gewünschten Abstand entlang der Linie", () => {
|
it("setzt Laternen im gewünschten Abstand entlang der Linie", () => {
|
||||||
@@ -105,3 +105,45 @@ describe("vergeLampPoints", () => {
|
|||||||
expect(out[0].z).toBeCloseTo(6);
|
expect(out[0].z).toBeCloseTo(6);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fix-Runde 3, Befund B: street_lamp_02.glb ist mit `npx @gltf-transform/cli@4.5.0 inspect`
|
||||||
|
// vermessen bboxMin.y=-0.39258 / bboxMax.y=1.26478 (Modellhöhe 1,65736 m) — der Fuß liegt
|
||||||
|
// unter dem Ursprung, #buildLamps setzte das Modell bisher unskaliert auf y=0, der Fuß steckt
|
||||||
|
// also im Boden, und 1,66 m ist ohnehin zu klein für eine Straßenlaterne (s. Bericht).
|
||||||
|
// lampFit() ist rein: sie bekommt die gemessene bbox und die Zielhöhe hinein, keine
|
||||||
|
// Seiteneffekte, kein GLTFLoader — PropLayer.load() ist der einzige Aufrufer.
|
||||||
|
describe("lampFit", () => {
|
||||||
|
const BBOX_MIN_Y = -0.39258;
|
||||||
|
const BBOX_MAX_Y = 1.26478;
|
||||||
|
|
||||||
|
it("skaliert die gemessene Modellhöhe exakt auf die Zielhöhe", () => {
|
||||||
|
const { scale } = lampFit(BBOX_MIN_Y, BBOX_MAX_Y, STREET_LAMP_HEIGHT);
|
||||||
|
const modelHeight = BBOX_MAX_Y - BBOX_MIN_Y;
|
||||||
|
expect(scale * modelHeight).toBeCloseTo(STREET_LAMP_HEIGHT, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hebt das skalierte Modell so an, dass sein Fuß (bboxMin.y) auf y = 0 zu stehen kommt", () => {
|
||||||
|
const { scale, liftY } = lampFit(BBOX_MIN_Y, BBOX_MAX_Y, STREET_LAMP_HEIGHT);
|
||||||
|
expect(BBOX_MIN_Y * scale + liftY).toBeCloseTo(0, 5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("STREET_LAMP_HEIGHT ist eine glaubwürdige Straßenlaternenhöhe, keine 1,66 m", () => {
|
||||||
|
// 3-6 m ist der in Quellen genannte Bereich für dekorative/ornamentale Straßen- und
|
||||||
|
// Parklaternen (s. Bericht); das Modell selbst ist mit 1,66 m unstrittig zu klein.
|
||||||
|
expect(STREET_LAMP_HEIGHT).toBeGreaterThanOrEqual(3);
|
||||||
|
expect(STREET_LAMP_HEIGHT).toBeLessThanOrEqual(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leitet den Skalierungsfaktor aus der übergebenen Zielhöhe her, statt eine Zahl zu raten", () => {
|
||||||
|
expect(lampFit(0, 2, 4).scale).toBeCloseTo(2);
|
||||||
|
expect(lampFit(0, 4, 4).scale).toBeCloseTo(1);
|
||||||
|
expect(lampFit(0, 1, 4).scale).toBeCloseTo(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("liefert scale 1 und hebt nur um |bboxMin.y| an, wenn keine Zielhöhe übergeben wird und die Modellhöhe 0 ist (Randfall)", () => {
|
||||||
|
// Entartete bbox (maxY === minY) darf nicht durch 0 teilen.
|
||||||
|
const { scale, liftY } = lampFit(2, 2, STREET_LAMP_HEIGHT);
|
||||||
|
expect(scale).toBe(1);
|
||||||
|
expect(liftY).toBeCloseTo(-2);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user