98 lines
5.2 KiB
JavaScript
98 lines
5.2 KiB
JavaScript
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");
|
||
});
|
||
});
|