Files
gta6/tests/placesPipeline.test.js
T

98 lines
5.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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");
});
});