auto-git:
[change] src/viewport-three/viewport-host.ts [change] tests/domain/build-runtime-scene.test.ts [change] tests/domain/custom-material.command.test.ts [change] tests/domain/delete-project-asset.test.ts [change] tests/domain/scene-document-validation.test.ts [change] tests/serialization/project-document-json.test.ts [change] tests/serialization/project-package.test.ts [change] tests/serialization/scene-document-json.test.ts [change] tests/unit/whitebox-material-inspector.integration.test.tsx
This commit is contained in:
@@ -280,7 +280,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
});
|
||||
|
||||
const runtimeScene = buildRuntimeSceneFromDocument({
|
||||
...createEmptySceneDocument({ name: "Mapped Rail Camera Rig Runtime Scene" }),
|
||||
...createEmptySceneDocument({
|
||||
name: "Mapped Rail Camera Rig Runtime Scene"
|
||||
}),
|
||||
paths: {
|
||||
[path.id]: path
|
||||
},
|
||||
@@ -683,7 +685,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
expect(runtimeScene.time).not.toBe(document.time);
|
||||
expect(runtimeScene.world).toEqual(document.world);
|
||||
expect(runtimeScene.world).not.toBe(document.world);
|
||||
expect(runtimeScene.world.sunLight.direction).not.toBe(document.world.sunLight.direction);
|
||||
expect(runtimeScene.world.sunLight.direction).not.toBe(
|
||||
document.world.sunLight.direction
|
||||
);
|
||||
expect(runtimeScene.brushes).toHaveLength(1);
|
||||
expect(runtimeScene.modelInstances).toEqual([
|
||||
{
|
||||
@@ -832,7 +836,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
y: 0,
|
||||
z: 0
|
||||
});
|
||||
expect(runtimeScene.brushes[0].faces.posY.material?.id).toBe("starter-concrete-checker");
|
||||
expect(runtimeScene.brushes[0].faces.posY.material?.id).toBe(
|
||||
"starter-concrete-checker"
|
||||
);
|
||||
expect(runtimeScene.colliders).toHaveLength(2);
|
||||
expect(runtimeScene.colliders[0]).toMatchObject({
|
||||
kind: "trimesh",
|
||||
@@ -863,7 +869,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
});
|
||||
const brushCollider = runtimeScene.colliders[0];
|
||||
if (brushCollider.kind !== "trimesh") {
|
||||
throw new Error(`Expected a trimesh brush collider, received ${brushCollider.kind}.`);
|
||||
throw new Error(
|
||||
`Expected a trimesh brush collider, received ${brushCollider.kind}.`
|
||||
);
|
||||
}
|
||||
expect(Array.from(brushCollider.vertices)).toHaveLength(24);
|
||||
expect(Array.from(brushCollider.indices)).toHaveLength(36);
|
||||
@@ -905,8 +913,7 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
allowLookInputTargetSwitch: playerStart.allowLookInputTargetSwitch,
|
||||
targetButtonCyclesActiveTarget:
|
||||
playerStart.targetButtonCyclesActiveTarget,
|
||||
invertMouseCameraHorizontal:
|
||||
playerStart.invertMouseCameraHorizontal,
|
||||
invertMouseCameraHorizontal: playerStart.invertMouseCameraHorizontal,
|
||||
movement: {
|
||||
templateKind: "default",
|
||||
moveSpeed: DEFAULT_PLAYER_START_MOVE_SPEED,
|
||||
@@ -1377,28 +1384,30 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
[overnightNpc.id]: overnightNpc
|
||||
}
|
||||
};
|
||||
document.scheduler.routines["routine-daytime"] = createProjectScheduleRoutine({
|
||||
id: "routine-daytime",
|
||||
title: "Day Shift",
|
||||
target: createActorControlTargetRef(daytimeNpc.actorId),
|
||||
startHour: 9,
|
||||
endHour: 17,
|
||||
effect: createSetActorPresenceControlEffect({
|
||||
document.scheduler.routines["routine-daytime"] =
|
||||
createProjectScheduleRoutine({
|
||||
id: "routine-daytime",
|
||||
title: "Day Shift",
|
||||
target: createActorControlTargetRef(daytimeNpc.actorId),
|
||||
active: true
|
||||
})
|
||||
});
|
||||
document.scheduler.routines["routine-overnight"] = createProjectScheduleRoutine({
|
||||
id: "routine-overnight",
|
||||
title: "Night Shift",
|
||||
target: createActorControlTargetRef(overnightNpc.actorId),
|
||||
startHour: 22,
|
||||
endHour: 2,
|
||||
effect: createSetActorPresenceControlEffect({
|
||||
startHour: 9,
|
||||
endHour: 17,
|
||||
effect: createSetActorPresenceControlEffect({
|
||||
target: createActorControlTargetRef(daytimeNpc.actorId),
|
||||
active: true
|
||||
})
|
||||
});
|
||||
document.scheduler.routines["routine-overnight"] =
|
||||
createProjectScheduleRoutine({
|
||||
id: "routine-overnight",
|
||||
title: "Night Shift",
|
||||
target: createActorControlTargetRef(overnightNpc.actorId),
|
||||
active: true
|
||||
})
|
||||
});
|
||||
startHour: 22,
|
||||
endHour: 2,
|
||||
effect: createSetActorPresenceControlEffect({
|
||||
target: createActorControlTargetRef(overnightNpc.actorId),
|
||||
active: true
|
||||
})
|
||||
});
|
||||
|
||||
const daytimeRuntimeScene = buildRuntimeSceneFromDocument(document, {
|
||||
runtimeClock: {
|
||||
@@ -1625,9 +1634,12 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
|
||||
it("blocks first-person runtime builds when PlayerStart is missing", () => {
|
||||
expect(() =>
|
||||
buildRuntimeSceneFromDocument(createEmptySceneDocument({ name: "Missing Player Start" }), {
|
||||
navigationMode: "firstPerson"
|
||||
})
|
||||
buildRuntimeSceneFromDocument(
|
||||
createEmptySceneDocument({ name: "Missing Player Start" }),
|
||||
{
|
||||
navigationMode: "firstPerson"
|
||||
}
|
||||
)
|
||||
).toThrow("First-person run requires an authored Player Start");
|
||||
});
|
||||
|
||||
@@ -1765,7 +1777,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
);
|
||||
|
||||
expect(runtimeScene.playerStart?.entityId).toBe(enabledPlayerStart.id);
|
||||
expect(runtimeScene.brushes.map((brush) => brush.id)).toEqual([hiddenBrush.id]);
|
||||
expect(runtimeScene.brushes.map((brush) => brush.id)).toEqual([
|
||||
hiddenBrush.id
|
||||
]);
|
||||
expect(runtimeScene.brushes[0]?.visible).toBe(false);
|
||||
expect(runtimeScene.modelInstances).toEqual([
|
||||
expect.objectContaining({
|
||||
@@ -1856,7 +1870,10 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
z: 8
|
||||
}
|
||||
});
|
||||
const { asset, loadedAsset } = createFixtureLoadedModelAssetFromGeometry("asset-runtime-collider", new BoxGeometry(1, 2, 1));
|
||||
const { asset, loadedAsset } = createFixtureLoadedModelAssetFromGeometry(
|
||||
"asset-runtime-collider",
|
||||
new BoxGeometry(1, 2, 1)
|
||||
);
|
||||
const modelInstance = createModelInstance({
|
||||
id: "model-instance-runtime-collider",
|
||||
assetId: asset.id,
|
||||
@@ -1944,7 +1961,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
runtimeScene.colliders[0] === undefined ||
|
||||
runtimeScene.colliders[0].source !== "terrain"
|
||||
) {
|
||||
throw new Error("Expected the runtime collider to be an authored terrain heightfield.");
|
||||
throw new Error(
|
||||
"Expected the runtime collider to be an authored terrain heightfield."
|
||||
);
|
||||
}
|
||||
|
||||
expect(Array.from(runtimeScene.colliders[0].heights)).toEqual(
|
||||
@@ -2142,13 +2161,13 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
})
|
||||
})
|
||||
]);
|
||||
expect(runtimeScene.splineCorridorClipIntervalsByPath.get(pathA.id)).toEqual(
|
||||
[
|
||||
expect.objectContaining({
|
||||
junctionId: junction.id
|
||||
})
|
||||
]
|
||||
);
|
||||
expect(
|
||||
runtimeScene.splineCorridorClipIntervalsByPath.get(pathA.id)
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
junctionId: junction.id
|
||||
})
|
||||
]);
|
||||
});
|
||||
|
||||
it("adds spline corridor box colliders for collision-enabled edges and repeaters", () => {
|
||||
@@ -2249,7 +2268,10 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
it("adds static-simple imported-model colliders as compound box pieces", () => {
|
||||
const wallGeometry = new PlaneGeometry(4, 4, 4, 4);
|
||||
wallGeometry.rotateY(Math.PI * 0.5);
|
||||
const { asset, loadedAsset } = createFixtureLoadedModelAssetFromGeometry("asset-runtime-static-simple", wallGeometry);
|
||||
const { asset, loadedAsset } = createFixtureLoadedModelAssetFromGeometry(
|
||||
"asset-runtime-static-simple",
|
||||
wallGeometry
|
||||
);
|
||||
const modelInstance = createModelInstance({
|
||||
id: "model-instance-runtime-static-simple",
|
||||
assetId: asset.id,
|
||||
@@ -2266,7 +2288,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||
{
|
||||
...createEmptySceneDocument({ name: "Imported Static Simple Collider Scene" }),
|
||||
...createEmptySceneDocument({
|
||||
name: "Imported Static Simple Collider Scene"
|
||||
}),
|
||||
assets: {
|
||||
[asset.id]: asset
|
||||
},
|
||||
@@ -2292,11 +2316,18 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
decomposition: "surface-voxel-boxes"
|
||||
});
|
||||
|
||||
if (runtimeScene.colliders[0].source !== "modelInstance" || runtimeScene.colliders[0].kind !== "compound") {
|
||||
throw new Error("Expected the runtime collider to be a generated compound model collider.");
|
||||
if (
|
||||
runtimeScene.colliders[0].source !== "modelInstance" ||
|
||||
runtimeScene.colliders[0].kind !== "compound"
|
||||
) {
|
||||
throw new Error(
|
||||
"Expected the runtime collider to be a generated compound model collider."
|
||||
);
|
||||
}
|
||||
|
||||
expect(runtimeScene.colliders[0].pieces.every((piece) => piece.kind === "box")).toBe(true);
|
||||
expect(
|
||||
runtimeScene.colliders[0].pieces.every((piece) => piece.kind === "box")
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("preserves rotated whitebox box transforms for runner rendering and collision bounds", () => {
|
||||
@@ -2537,31 +2568,32 @@ describe("buildRuntimeSceneFromDocument", () => {
|
||||
document.assets[asset.id] = asset;
|
||||
document.entities[npc.id] = npc;
|
||||
document.paths[path.id] = path;
|
||||
document.scheduler.routines["routine-patrol"] = createProjectScheduleRoutine({
|
||||
id: "routine-patrol",
|
||||
title: "Patrolling",
|
||||
target: actorTarget,
|
||||
startHour: 9,
|
||||
endHour: 13,
|
||||
effects: [
|
||||
createSetActorPresenceControlEffect({
|
||||
target: actorTarget,
|
||||
active: true
|
||||
}),
|
||||
createPlayActorAnimationControlEffect({
|
||||
target: actorTarget,
|
||||
clipName: "Walk",
|
||||
loop: true
|
||||
}),
|
||||
createFollowActorPathControlEffect({
|
||||
target: actorTarget,
|
||||
pathId: path.id,
|
||||
speed: 2,
|
||||
loop: false,
|
||||
progressMode: "deriveFromTime"
|
||||
})
|
||||
]
|
||||
});
|
||||
document.scheduler.routines["routine-patrol"] =
|
||||
createProjectScheduleRoutine({
|
||||
id: "routine-patrol",
|
||||
title: "Patrolling",
|
||||
target: actorTarget,
|
||||
startHour: 9,
|
||||
endHour: 13,
|
||||
effects: [
|
||||
createSetActorPresenceControlEffect({
|
||||
target: actorTarget,
|
||||
active: true
|
||||
}),
|
||||
createPlayActorAnimationControlEffect({
|
||||
target: actorTarget,
|
||||
clipName: "Walk",
|
||||
loop: true
|
||||
}),
|
||||
createFollowActorPathControlEffect({
|
||||
target: actorTarget,
|
||||
pathId: path.id,
|
||||
speed: 2,
|
||||
loop: false,
|
||||
progressMode: "deriveFromTime"
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
const runtimeScene = buildRuntimeSceneFromDocument(document, {
|
||||
runtimeClock: {
|
||||
|
||||
@@ -9,10 +9,7 @@ import {
|
||||
createProjectAssetStorageKey,
|
||||
type ImageAssetRecord
|
||||
} from "../../src/assets/project-assets";
|
||||
import {
|
||||
BOX_FACE_IDS,
|
||||
type WhiteboxFaceId
|
||||
} from "../../src/document/brushes";
|
||||
import { BOX_FACE_IDS, type WhiteboxFaceId } from "../../src/document/brushes";
|
||||
import { createCustomMaterialDef } from "../../src/materials/starter-material-library";
|
||||
|
||||
function createImageAsset(id: string): ImageAssetRecord {
|
||||
|
||||
@@ -318,7 +318,9 @@ describe("deleteProjectAssetFromProjectDocument", () => {
|
||||
);
|
||||
const nextScene = nextProjectDocument.scenes[fixture.sceneId];
|
||||
|
||||
expect(nextProjectDocument.foliagePrototypes[foliagePrototype.id]).toBeUndefined();
|
||||
expect(
|
||||
nextProjectDocument.foliagePrototypes[foliagePrototype.id]
|
||||
).toBeUndefined();
|
||||
expect(nextScene.foliageLayers[foliageLayer.id]?.prototypeIds).toEqual([
|
||||
bundledPrototype.id
|
||||
]);
|
||||
|
||||
@@ -140,9 +140,7 @@ describe("validateSceneDocument", () => {
|
||||
id: "asset-invalid-material-map-hdr",
|
||||
sourceName: "studio.hdr",
|
||||
mimeType: "image/vnd.radiance",
|
||||
storageKey: createProjectAssetStorageKey(
|
||||
"asset-invalid-material-map-hdr"
|
||||
)
|
||||
storageKey: createProjectAssetStorageKey("asset-invalid-material-map-hdr")
|
||||
};
|
||||
const audioAsset: AudioAssetRecord = {
|
||||
id: "asset-invalid-material-map-audio",
|
||||
|
||||
@@ -218,9 +218,9 @@ describe("project document JSON", () => {
|
||||
|
||||
document.scenes[document.activeSceneId]!.foliageLayers[layer.id] = layer;
|
||||
|
||||
expect(parseProjectDocumentJson(serializeProjectDocument(document))).toEqual(
|
||||
document
|
||||
);
|
||||
expect(
|
||||
parseProjectDocumentJson(serializeProjectDocument(document))
|
||||
).toEqual(document);
|
||||
});
|
||||
|
||||
it("round-trips terrain foliage masks in project scenes", () => {
|
||||
@@ -256,9 +256,9 @@ describe("project document JSON", () => {
|
||||
scene.foliageLayers[layer.id] = layer;
|
||||
scene.terrains[terrain.id] = terrain;
|
||||
|
||||
expect(parseProjectDocumentJson(serializeProjectDocument(document))).toEqual(
|
||||
document
|
||||
);
|
||||
expect(
|
||||
parseProjectDocumentJson(serializeProjectDocument(document))
|
||||
).toEqual(document);
|
||||
});
|
||||
|
||||
it("round-trips NPC dialogue references in project scenes", () => {
|
||||
|
||||
@@ -316,9 +316,7 @@ describe("project package serialization", () => {
|
||||
kind: "image",
|
||||
sourceName: "custom-material-map.png",
|
||||
mimeType: "image/png",
|
||||
storageKey: createProjectAssetStorageKey(
|
||||
"asset-image-material-package"
|
||||
),
|
||||
storageKey: createProjectAssetStorageKey("asset-image-material-package"),
|
||||
byteLength: 4,
|
||||
metadata: {
|
||||
kind: "image",
|
||||
@@ -373,7 +371,9 @@ describe("project package serialization", () => {
|
||||
"assets/asset-image-material-package/custom-material-map.png"
|
||||
])
|
||||
);
|
||||
await expect(restoredStorage.getAsset(imageAsset.storageKey)).resolves.toEqual({
|
||||
await expect(
|
||||
restoredStorage.getAsset(imageAsset.storageKey)
|
||||
).resolves.toEqual({
|
||||
files: {
|
||||
[imageAsset.sourceName]: {
|
||||
bytes: expect.any(ArrayBuffer),
|
||||
|
||||
@@ -186,22 +186,7 @@ describe("scene document JSON", () => {
|
||||
}
|
||||
],
|
||||
paintWeights: [
|
||||
0.2,
|
||||
0.3,
|
||||
0.1,
|
||||
0,
|
||||
0.4,
|
||||
0.1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0.5,
|
||||
0,
|
||||
0,
|
||||
0.25,
|
||||
0.25,
|
||||
0.25,
|
||||
0
|
||||
0.2, 0.3, 0.1, 0, 0.4, 0.1, 0, 0, 0, 0.5, 0, 0, 0.25, 0.25, 0.25, 0
|
||||
]
|
||||
});
|
||||
const document = createEmptySceneDocument({
|
||||
@@ -1073,9 +1058,9 @@ describe("scene document JSON", () => {
|
||||
expect(
|
||||
migratedDocument.paths[path.id]?.road.edges.right.collisionEnabled
|
||||
).toBe(false);
|
||||
expect(migratedDocument.paths[path.id]?.repeaters[0]?.collisionEnabled).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
migratedDocument.paths[path.id]?.repeaters[0]?.collisionEnabled
|
||||
).toBe(false);
|
||||
expect(SPLINE_CORRIDOR_COLLISION_SCENE_DOCUMENT_VERSION).toBe(106);
|
||||
});
|
||||
|
||||
@@ -1175,7 +1160,8 @@ describe("scene document JSON", () => {
|
||||
splineCorridorJunctions: Record<string, { shapeMode?: unknown }>;
|
||||
};
|
||||
|
||||
legacyDocument.version = SPLINE_CORRIDOR_JUNCTION_EDGE_SCENE_DOCUMENT_VERSION;
|
||||
legacyDocument.version =
|
||||
SPLINE_CORRIDOR_JUNCTION_EDGE_SCENE_DOCUMENT_VERSION;
|
||||
delete legacyDocument.splineCorridorJunctions[junction.id]?.shapeMode;
|
||||
|
||||
const migratedDocument = migrateSceneDocument(legacyDocument);
|
||||
@@ -3276,12 +3262,13 @@ describe("scene document JSON", () => {
|
||||
document.brushes[targetBrush.id] = targetBrush;
|
||||
document.entities[linkedNpc.id] = linkedNpc;
|
||||
document.entities[silentNpc.id] = silentNpc;
|
||||
document.interactionLinks["link-npc-toggle"] = createToggleVisibilityInteractionLink({
|
||||
id: "link-npc-toggle",
|
||||
sourceEntityId: linkedNpc.id,
|
||||
trigger: "click",
|
||||
targetBrushId: targetBrush.id
|
||||
});
|
||||
document.interactionLinks["link-npc-toggle"] =
|
||||
createToggleVisibilityInteractionLink({
|
||||
id: "link-npc-toggle",
|
||||
sourceEntityId: linkedNpc.id,
|
||||
trigger: "click",
|
||||
targetBrushId: targetBrush.id
|
||||
});
|
||||
|
||||
const legacyDocument = JSON.parse(
|
||||
serializeSceneDocument(document)
|
||||
|
||||
@@ -143,18 +143,19 @@ describe("whitebox material inspector", () => {
|
||||
.mockImplementation(() => undefined);
|
||||
const { container, store, brush } = await renderMaterialInspectorFixture();
|
||||
|
||||
expect(
|
||||
container.querySelector(".material-browser button")
|
||||
).toHaveAttribute("data-testid", "material-button-default-whitebox");
|
||||
expect(screen.getByTestId("material-button-default-whitebox")).toHaveTextContent(
|
||||
"Default Whitebox"
|
||||
expect(container.querySelector(".material-browser button")).toHaveAttribute(
|
||||
"data-testid",
|
||||
"material-button-default-whitebox"
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId("material-button-default-whitebox")
|
||||
).toHaveTextContent("Default Whitebox");
|
||||
|
||||
fireEvent.click(screen.getByTestId("create-custom-material"));
|
||||
|
||||
const customMaterial = Object.values(store.getState().document.materials).find(
|
||||
(material) => material.kind === "custom"
|
||||
);
|
||||
const customMaterial = Object.values(
|
||||
store.getState().document.materials
|
||||
).find((material) => material.kind === "custom");
|
||||
|
||||
expect(customMaterial).toBeDefined();
|
||||
expect(
|
||||
@@ -186,19 +187,23 @@ describe("whitebox material inspector", () => {
|
||||
throw new Error("Expected a custom material to be created.");
|
||||
}
|
||||
|
||||
expect(store.getState().document.materials[editedMaterialId]).toMatchObject({
|
||||
kind: "custom",
|
||||
albedoColorHex: "#224466",
|
||||
roughness: 0.37,
|
||||
metallic: 0.58
|
||||
});
|
||||
expect(store.getState().document.materials[editedMaterialId]).toMatchObject(
|
||||
{
|
||||
kind: "custom",
|
||||
albedoColorHex: "#224466",
|
||||
roughness: 0.37,
|
||||
metallic: 0.58
|
||||
}
|
||||
);
|
||||
|
||||
const exportedProject = store.exportDocumentJson();
|
||||
const reloadedStore = createEditorStore();
|
||||
|
||||
reloadedStore.importDocumentJson(exportedProject);
|
||||
|
||||
expect(reloadedStore.getState().document.materials[editedMaterialId]).toMatchObject({
|
||||
expect(
|
||||
reloadedStore.getState().document.materials[editedMaterialId]
|
||||
).toMatchObject({
|
||||
kind: "custom",
|
||||
albedoColorHex: "#224466",
|
||||
roughness: 0.37,
|
||||
|
||||
Reference in New Issue
Block a user