144 lines
8.2 KiB
TypeScript
144 lines
8.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const root = path.resolve(import.meta.dirname, "../../..");
|
|
const blend = fs.readFileSync(path.join(root, "tests/files/web/basic_scene.blend"));
|
|
const expected = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M11-02/lighting-field-roundtrip.json"), "utf8"));
|
|
|
|
function expectClose(actual: unknown, reference: unknown): void {
|
|
if (typeof reference === "number") {
|
|
expect(actual).toBeCloseTo(reference, 5);
|
|
return;
|
|
}
|
|
if (Array.isArray(reference)) {
|
|
expect(Array.isArray(actual)).toBe(true);
|
|
expect(actual).toHaveLength(reference.length);
|
|
reference.forEach((value, index) => expectClose((actual as unknown[])[index], value));
|
|
return;
|
|
}
|
|
if (reference && typeof reference === "object") {
|
|
expect(actual && typeof actual === "object").toBe(true);
|
|
for (const [field, value] of Object.entries(reference)) {
|
|
expectClose((actual as Record<string, unknown>)[field], value);
|
|
}
|
|
return;
|
|
}
|
|
expect(actual).toEqual(reference);
|
|
}
|
|
|
|
test("M11-02 edits, undoes, redoes, reopens and maps supported lighting fields", async ({ page }) => {
|
|
test.setTimeout(60_000);
|
|
await page.goto("/");
|
|
const result = await page.evaluate(async (input) => {
|
|
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
|
const { configurePBRCamera, configurePBRLight, createPBRLight } = await import("/src/three-adapter/pbr.ts");
|
|
const { Object3D, PerspectiveCamera } = await import("/src/vendor/three/three.module.js");
|
|
const client = new WebEngineClient({ timeoutMs: 30_000 });
|
|
const reopened = new WebEngineClient({ timeoutMs: 30_000 });
|
|
try {
|
|
const opened = await client.openBlend(Uint8Array.from(input).buffer);
|
|
const camera = opened.snapshot.cameras.find((item) => item.id === "camera:Camera.001")!;
|
|
const light = opened.snapshot.lights.find((item) => item.id === "light:Area")!;
|
|
const world = opened.snapshot.worlds.find((item) => item.id === "world:World")!;
|
|
const revisions = [opened.snapshot.revision];
|
|
|
|
const cameraEdit = await client.applyCommand({ type: "setCameraProperties", dataId: camera.id, properties: {
|
|
lensMm: 35, sensorWidthMm: 32, sensorHeightMm: 18, sensorFit: 2, shift: [0.1, -0.2],
|
|
near: 0.2, far: 500, orthoScale: 8,
|
|
depthOfField: { enabled: true, focusDistance: 4.5, apertureFStop: 1.8, apertureBlades: 7, apertureRotation: 0.25, apertureRatio: 1.2 },
|
|
} });
|
|
revisions.push(cameraEdit.snapshot.revision);
|
|
const cameraUndo = await client.applyCommand({ type: "undo" });
|
|
revisions.push(cameraUndo.snapshot.revision);
|
|
const cameraRedo = await client.applyCommand({ type: "redo" });
|
|
revisions.push(cameraRedo.snapshot.revision);
|
|
|
|
const lightEdit = await client.applyCommand({ type: "setLightProperties", dataId: light.id, properties: {
|
|
color: [0.25, 0.5, 0.75], energy: 400, exposure: 1, temperature: 5000,
|
|
useTemperature: true, castsShadow: false, radius: 0.3, spotAngle: 1.1,
|
|
spotBlend: 0.25, areaSize: 3, areaSizeY: 2, areaSpread: 2.4, sunAngle: 0.1,
|
|
} });
|
|
revisions.push(lightEdit.snapshot.revision);
|
|
const lightUndo = await client.applyCommand({ type: "undo" });
|
|
revisions.push(lightUndo.snapshot.revision);
|
|
const lightRedo = await client.applyCommand({ type: "redo" });
|
|
revisions.push(lightRedo.snapshot.revision);
|
|
|
|
const worldEdit = await client.applyCommand({ type: "setWorldProperties", dataId: world.id, properties: {
|
|
color: [0.1, 0.2, 0.3], exposure: 0.5,
|
|
mist: { enabled: true, type: "LINEAR", start: 2, depth: 50, intensity: 0.2, height: 3 },
|
|
} });
|
|
revisions.push(worldEdit.snapshot.revision);
|
|
const worldUndo = await client.applyCommand({ type: "undo" });
|
|
revisions.push(worldUndo.snapshot.revision);
|
|
const worldRedo = await client.applyCommand({ type: "redo" });
|
|
revisions.push(worldRedo.snapshot.revision);
|
|
|
|
const saved = await client.saveBlend();
|
|
const reopenedResult = await reopened.openBlend(saved);
|
|
const reopenedCamera = reopenedResult.snapshot.cameras.find((item) => item.id === camera.id)!;
|
|
const reopenedLight = reopenedResult.snapshot.lights.find((item) => item.id === light.id)!;
|
|
const reopenedWorld = reopenedResult.snapshot.worlds.find((item) => item.id === world.id)!;
|
|
const mappedCamera = new PerspectiveCamera();
|
|
configurePBRCamera(mappedCamera, reopenedCamera);
|
|
const horizontalCamera = new PerspectiveCamera();
|
|
configurePBRCamera(horizontalCamera, { ...reopenedCamera, sensorFit: 1 });
|
|
const spot = createPBRLight({ ...reopenedLight, lightType: 2 });
|
|
const area = createPBRLight({ ...reopenedLight, lightType: 4 });
|
|
const lightNode = reopenedResult.snapshot.nodes.find((item) => item.dataId === light.id)!;
|
|
configurePBRLight(spot, lightNode, new Object3D());
|
|
return {
|
|
revisions,
|
|
cameraUndoLens: cameraUndo.snapshot.cameras.find((item) => item.id === camera.id)!.lensMm,
|
|
cameraRedoLens: cameraRedo.snapshot.cameras.find((item) => item.id === camera.id)!.lensMm,
|
|
lightUndoEnergy: lightUndo.snapshot.lights.find((item) => item.id === light.id)!.energy,
|
|
lightRedoEnergy: lightRedo.snapshot.lights.find((item) => item.id === light.id)!.energy,
|
|
worldUndoColor: worldUndo.snapshot.worlds.find((item) => item.id === world.id)!.color,
|
|
worldRedoColor: worldRedo.snapshot.worlds.find((item) => item.id === world.id)!.color,
|
|
camera: reopenedCamera,
|
|
light: reopenedLight,
|
|
world: reopenedWorld,
|
|
viewport: {
|
|
camera: { fov: mappedCamera.fov, horizontalFov: horizontalCamera.fov, near: mappedCamera.near, far: mappedCamera.far, filmGauge: mappedCamera.filmGauge, filmOffset: mappedCamera.filmOffset },
|
|
spot: { color: spot.color.toArray(), intensity: spot.intensity, angle: "angle" in spot ? spot.angle : 0, penumbra: "penumbra" in spot ? spot.penumbra : 0, castShadow: spot.castShadow },
|
|
area: { intensity: area.intensity, width: "width" in area ? area.width : 0, height: "height" in area ? area.height : 0 },
|
|
worldColor: reopenedWorld.color,
|
|
},
|
|
};
|
|
}
|
|
finally {
|
|
client.terminate();
|
|
reopened.terminate();
|
|
}
|
|
}, Array.from(blend));
|
|
|
|
expect(result.revisions.every((revision, index) => index === 0 || revision === result.revisions[index - 1] + 1)).toBe(true);
|
|
expect(result.cameraUndoLens).toBe(50);
|
|
expect(result.cameraRedoLens).toBe(expected.camera.lensMm);
|
|
expect(result.lightUndoEnergy).toBe(800);
|
|
expect(result.lightRedoEnergy).toBe(expected.light.energy);
|
|
expect(result.worldUndoColor).not.toEqual(expected.world.color);
|
|
expectClose(result.worldRedoColor, expected.world.color);
|
|
for (const [field, value] of Object.entries(expected.camera).filter(([field]) => !field.startsWith("viewport"))) {
|
|
expectClose(result.camera[field as keyof typeof result.camera], value);
|
|
}
|
|
for (const [field, value] of Object.entries(expected.light).filter(([field]) => !field.startsWith("viewport"))) {
|
|
expectClose(result.light[field as keyof typeof result.light], value);
|
|
}
|
|
expectClose(result.world, expected.world);
|
|
expect(result.viewport.camera.fov).toBeCloseTo(expected.camera.viewportFov, 5);
|
|
expect(result.viewport.camera.horizontalFov).toBeCloseTo(expected.camera.viewportHorizontalFov, 5);
|
|
expectClose(result.viewport.camera.near, expected.camera.near);
|
|
expectClose(result.viewport.camera.far, expected.camera.far);
|
|
expectClose(result.viewport.camera.filmGauge, expected.camera.viewportFilmGauge);
|
|
expect(result.viewport.camera.filmOffset).toBeCloseTo(expected.camera.viewportFilmOffset, 6);
|
|
expectClose(result.viewport.spot.intensity, expected.light.viewportIntensity);
|
|
expectClose(result.viewport.spot.color, expected.light.viewportColor);
|
|
expectClose(result.viewport.spot.angle, expected.light.spotAngle);
|
|
expectClose(result.viewport.spot.penumbra, expected.light.spotBlend);
|
|
expect(result.viewport.spot.castShadow).toBe(false);
|
|
expectClose(result.viewport.area, { intensity: expected.light.viewportIntensity, width: expected.light.areaSize, height: expected.light.areaSizeY });
|
|
expectClose(result.viewport.worldColor, expected.world.color);
|
|
});
|