Files
workinf_Blender_Wasm/web/tests/e2e/paint-depth-visibility.spec.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

117 lines
5.5 KiB
TypeScript

import { expect, test } from "@playwright/test";
test("M9-09 derives the same occlusion set from real main-thread and Offscreen GPU depth passes", async ({ page }) => {
test.setTimeout(60_000);
await page.goto("/");
const result = await page.evaluate(async () => {
const { ViewportRenderer } = await import("/src/three-adapter/viewport.ts");
const { OffscreenViewportRenderer } = await import("/src/three-adapter/offscreen-viewport.ts");
const normalize = (value: number[]) => {
const length = Math.hypot(...value);
return value.map((component) => component / length);
};
const cross = (left: number[], right: number[]) => [
left[1] * right[2] - left[2] * right[1],
left[2] * right[0] - left[0] * right[2],
left[0] * right[1] - left[1] * right[0],
];
const add = (...values: number[][]) => values[0].map((_, axis) => values.reduce((sum, value) => sum + value[axis], 0));
const scale = (value: number[], factor: number) => value.map((component) => component * factor);
const camera = [
7 * Math.cos(0.55) * Math.cos(-Math.PI / 4),
7 * Math.cos(0.55) * Math.sin(-Math.PI / 4),
7 * Math.sin(0.55),
];
const direction = normalize(camera.map((component) => -component));
const horizontal = normalize(cross(direction, [0, 0, 1]));
const vertical = normalize(cross(direction, horizontal));
const front = [
add(scale(horizontal, -2), scale(vertical, -2)),
add(scale(horizontal, -2), scale(vertical, 2)),
add(scale(horizontal, 2), scale(vertical, 2)),
add(scale(horizontal, 2), scale(vertical, -2)),
];
const behind = scale(direction, 1.5);
const back = [
add(behind, scale(horizontal, -0.12), scale(vertical, -0.08)),
add(behind, scale(horizontal, 0.12), scale(vertical, -0.08)),
add(behind, scale(vertical, 0.12)),
];
const toBlender = (value: number[]) => [value[0], -value[2], value[1]];
const positions = new Float32Array([...front, ...back].flatMap(toBlender));
const indices = new Uint32Array([0, 1, 2, 0, 2, 3, 4, 6, 5]);
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
const snapshot = {
schemaVersion: 1 as const,
revision: 9,
sceneId: "scene:paint-depth",
source: { kind: "mock" as const },
coordinateSystem: { upAxis: "Z" as const, forwardAxis: "-Y" as const, handedness: "RIGHT" as const, unitSystem: 0, unitScale: 1 },
nodes: [{
id: "object:PaintDepth",
name: "PaintDepth",
type: "MESH" as const,
parentId: null,
dataId: "mesh:PaintDepth",
visible: true,
selectable: true,
localMatrix: identity,
worldMatrix: identity,
transform: { translation: [0, 0, 0] as [number, number, number], rotationEuler: [0, 0, 0] as [number, number, number], scale: [1, 1, 1] as [number, number, number], rotationMode: 1 },
}],
meshes: [{ id: "mesh:PaintDepth", name: "PaintDepth", vertexCount: 7, edgeCount: 0, faceCount: 3, cornerCount: 9, triangleCount: 3, geometryStatus: "binary" as const }],
materials: [], cameras: [], lights: [], worlds: [], images: [], animations: [], collections: [], scenes: [],
activeObjectId: "object:PaintDepth",
frame: { current: 1, start: 1, end: 1 },
};
const geometry = {
schemaVersion: 1 as const,
meshId: "mesh:PaintDepth",
byteLength: positions.byteLength + indices.byteLength,
positions: positions.buffer,
indices: indices.buffer,
};
const request = { schemaVersion: 1 as const, objectId: "object:PaintDepth", meshId: "mesh:PaintDepth", revision: 9, vertexIndices: [0, 1, 2, 3, 4, 5, 6] };
const createCanvas = () => {
const canvas = document.createElement("canvas");
canvas.style.width = "320px";
canvas.style.height = "240px";
document.body.append(canvas);
return canvas;
};
const mainCanvas = createCanvas();
const mainRenderer = new ViewportRenderer(mainCanvas);
mainRenderer.setSnapshot(snapshot, [geometry]);
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const main = await mainRenderer.samplePaintVisibility(request);
mainRenderer.dispose();
mainCanvas.remove();
const offscreenCanvas = createCanvas();
const offscreenRenderer = new OffscreenViewportRenderer(offscreenCanvas);
offscreenRenderer.setSnapshot(snapshot, [geometry]);
let stale = "";
try { await offscreenRenderer.samplePaintVisibility({ ...request, revision: 8 }); }
catch (error) { stale = error instanceof Error ? error.message : String(error); }
const offscreen = await offscreenRenderer.samplePaintVisibility(request);
offscreenRenderer.dispose();
offscreenCanvas.remove();
return { main, offscreen, stale };
});
expect(result.main.backend).toBe("MAIN_THREAD_WEBGL2");
expect(result.offscreen.backend).toBe("OFFSCREEN_WEBGL2");
expect(result.main.source).toBe("GPU_RGBA_DEPTH_READBACK");
expect(result.main.occluderPixelCount).toBeGreaterThan(0);
expect(result.offscreen.occluderPixelCount).toBeGreaterThan(0);
expect(result.main.depthReadbackBytes).toBe(result.main.width * result.main.height * 4);
expect(result.offscreen.depthReadbackBytes).toBe(result.offscreen.width * result.offscreen.height * 4);
expect(result.main.visibleVertexIndices).toEqual([0, 1, 2, 3]);
expect(result.offscreen.visibleVertexIndices).toEqual(result.main.visibleVertexIndices);
expect(result.stale).toContain("REVISION_CONFLICT");
});