feat(assets): Aufbereitung für Orte mit Texturgrößen je Material und Vereinfachung
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ebe8adb8b
commit
6c291c4106
2 files changed
+239
No files matched your search
@@ -0,0 +1,97 @@
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { Document } from "@gltf-transform/core";
|
||||
import sharp from "sharp";
|
||||
import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs";
|
||||
import { FAR_TEXTURE_SIZE, NEAR_TEXTURE_SIZE, optimizePlace } from "../tools/assets/places.mjs";
|
||||
|
||||
/** 1100 px große Streifen. seed macht die Bilder verschieden, sonst legt dedup() sie zusammen. */
|
||||
function stripes(seed) {
|
||||
const size = 1100;
|
||||
const data = Buffer.alloc(size * size * 3);
|
||||
for (let i = 0; i < data.length; i++) data[i] = (Math.floor(i / 3) % size) * seed & 0xff;
|
||||
return sharp(data, { raw: { width: size, height: size, channels: 3 } }).png().toBuffer();
|
||||
}
|
||||
|
||||
/** n × n Quadrate in der Ebene (2·n² Dreiecke), indiziert. */
|
||||
function grid(document, n) {
|
||||
const positions = [];
|
||||
const indices = [];
|
||||
for (let z = 0; z <= n; z++) for (let x = 0; x <= n; x++) positions.push(x, 0, z);
|
||||
for (let z = 0; z < n; z++) {
|
||||
for (let x = 0; x < n; x++) {
|
||||
const a = z * (n + 1) + x, b = a + 1, c = a + n + 1, d = c + 1;
|
||||
indices.push(a, c, b, b, c, d);
|
||||
}
|
||||
}
|
||||
return document.createPrimitive()
|
||||
.setAttribute("POSITION", document.createAccessor().setType("VEC3").setArray(new Float32Array(positions)))
|
||||
.setIndices(document.createAccessor().setType("SCALAR").setArray(new Uint32Array(indices)));
|
||||
}
|
||||
|
||||
/** Ein Container (Nahfläche mit Bump) und ein Grasstück (Pflanze mit Normal-Map), je ein Knoten. */
|
||||
async function sampleDocument() {
|
||||
const document = new Document();
|
||||
document.createBuffer();
|
||||
const bumpExtension = document.createExtension(EXTMaterialsBump);
|
||||
const [near, nearBump, far, farNormal] = await Promise.all([1, 3, 5, 7].map(stripes));
|
||||
const texture = (name, image) => document.createTexture(name).setImage(image).setMimeType("image/png");
|
||||
|
||||
const container = document.createMaterial("cont_a_side").setBaseColorTexture(texture("near", near));
|
||||
container.setExtension(EXT_MATERIALS_BUMP,
|
||||
bumpExtension.createBump().setBumpFactor(2.2).setBumpTexture(texture("nearBump", nearBump)));
|
||||
const grass = document.createMaterial("grass_medium_01")
|
||||
.setBaseColorTexture(texture("far", far))
|
||||
.setNormalTexture(texture("farNormal", farNormal));
|
||||
|
||||
const scene = document.createScene();
|
||||
scene.addChild(document.createNode("container").setMesh(document.createMesh().addPrimitive(grid(document, 1).setMaterial(container))));
|
||||
scene.addChild(document.createNode("grass").setMesh(document.createMesh().addPrimitive(grid(document, 40).setMaterial(grass))));
|
||||
return { document, container, grass };
|
||||
}
|
||||
|
||||
const trianglesOf = (document, name) => document.getRoot().listMeshes()
|
||||
.flatMap((mesh) => mesh.listPrimitives())
|
||||
.filter((primitive) => primitive.getMaterial().getName() === name)
|
||||
.reduce((sum, primitive) => sum + primitive.getIndices().getCount() / 3, 0);
|
||||
|
||||
describe("optimizePlace", () => {
|
||||
let sample;
|
||||
beforeAll(async () => {
|
||||
sample = await sampleDocument();
|
||||
await optimizePlace(sample.document);
|
||||
}, 60000);
|
||||
|
||||
// Bruch: Nahflächen so unscharf wie Pflanzen, Größen vertauscht, oder die Bump-Map zählt nicht
|
||||
// zu ihrem Material (sie hängt über die Erweiterung dran, nicht direkt am Material).
|
||||
it("verkleinert Texturen je nach Material auf 1024 oder 512 px und wandelt sie in WebP", () => {
|
||||
const { document, container, grass } = sample;
|
||||
const bumpTexture = container.getExtension(EXT_MATERIALS_BUMP).getBumpTexture();
|
||||
|
||||
expect(container.getBaseColorTexture().getSize()).toEqual([NEAR_TEXTURE_SIZE, NEAR_TEXTURE_SIZE]);
|
||||
expect(bumpTexture.getSize()).toEqual([NEAR_TEXTURE_SIZE, NEAR_TEXTURE_SIZE]);
|
||||
expect(grass.getBaseColorTexture().getSize()).toEqual([FAR_TEXTURE_SIZE, FAR_TEXTURE_SIZE]);
|
||||
expect(grass.getNormalTexture().getSize()).toEqual([FAR_TEXTURE_SIZE, FAR_TEXTURE_SIZE]);
|
||||
for (const texture of document.getRoot().listTextures()) expect(texture.getMimeType()).toBe("image/webp");
|
||||
});
|
||||
|
||||
// Bruch: die Vereinfachung trifft Container und Schuppen (Löcher in Wänden) oder lässt Pflanzen
|
||||
// unberührt (die Köcherbäume blieben bei 2,2 Mio. Dreiecken).
|
||||
it("vereinfacht nur Pflanzen und Kleinteile und behält die Materialnamen", () => {
|
||||
const { document } = sample;
|
||||
|
||||
expect(trianglesOf(document, "cont_a_side")).toBe(2);
|
||||
expect(trianglesOf(document, "grass_medium_01")).toBeGreaterThan(0);
|
||||
expect(trianglesOf(document, "grass_medium_01")).toBeLessThanOrEqual(3200 * 0.5);
|
||||
});
|
||||
|
||||
// Bruch: das Wellblech geht verloren, die Geometrie bleibt unkomprimiert, oder es entstehen
|
||||
// GPU-Instanzen, deren Matrizen die Kollision nicht liest.
|
||||
it("erhält die Bump-Map, komprimiert mit meshopt und legt keine Instanzen an", () => {
|
||||
const { document, container } = sample;
|
||||
const used = document.getRoot().listExtensionsUsed().map((extension) => extension.extensionName);
|
||||
|
||||
expect(container.getExtension(EXT_MATERIALS_BUMP).getBumpFactor()).toBe(2.2);
|
||||
expect(used).toEqual(expect.arrayContaining([EXT_MATERIALS_BUMP, "EXT_meshopt_compression", "EXT_texture_webp"]));
|
||||
expect(used).not.toContain("EXT_mesh_gpu_instancing");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user