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:
@@ -971,7 +971,9 @@ export function resolveViewportWorldState(options: {
|
|||||||
}): ViewportResolvedWorldState {
|
}): ViewportResolvedWorldState {
|
||||||
const world = options.currentSimulationScene?.world ?? options.currentWorld;
|
const world = options.currentSimulationScene?.world ?? options.currentWorld;
|
||||||
const timeSettings =
|
const timeSettings =
|
||||||
options.currentSimulationScene?.time ?? options.currentDocument?.time ?? null;
|
options.currentSimulationScene?.time ??
|
||||||
|
options.currentDocument?.time ??
|
||||||
|
null;
|
||||||
|
|
||||||
if (world === null) {
|
if (world === null) {
|
||||||
return {
|
return {
|
||||||
@@ -1528,10 +1530,7 @@ export class ViewportHost {
|
|||||||
this.terrainGridResizeOverlayGroup.add(visual.group);
|
this.terrainGridResizeOverlayGroup.add(visual.group);
|
||||||
}
|
}
|
||||||
|
|
||||||
applyRendererRenderCategory(
|
applyRendererRenderCategory(this.terrainGridResizeOverlayGroup, "overlay");
|
||||||
this.terrainGridResizeOverlayGroup,
|
|
||||||
"overlay"
|
|
||||||
);
|
|
||||||
this.scene.add(this.terrainGridResizeOverlayGroup);
|
this.scene.add(this.terrainGridResizeOverlayGroup);
|
||||||
this.scene.add(this.pathGroup);
|
this.scene.add(this.pathGroup);
|
||||||
this.scene.add(this.entityGroup);
|
this.scene.add(this.entityGroup);
|
||||||
@@ -2790,17 +2789,13 @@ export class ViewportHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private applyWorld() {
|
private applyWorld() {
|
||||||
const {
|
const { world, timeSettings, resolvedTime, resolvedWorld } =
|
||||||
world,
|
resolveViewportWorldState({
|
||||||
timeSettings,
|
currentWorld: this.currentWorld,
|
||||||
resolvedTime,
|
currentDocument: this.currentDocument,
|
||||||
resolvedWorld
|
currentSimulationScene: this.currentSimulationScene,
|
||||||
} = resolveViewportWorldState({
|
currentSimulationClock: this.currentSimulationClock
|
||||||
currentWorld: this.currentWorld,
|
});
|
||||||
currentDocument: this.currentDocument,
|
|
||||||
currentSimulationScene: this.currentSimulationScene,
|
|
||||||
currentSimulationClock: this.currentSimulationClock
|
|
||||||
});
|
|
||||||
|
|
||||||
if (world === null) {
|
if (world === null) {
|
||||||
return;
|
return;
|
||||||
@@ -7705,7 +7700,10 @@ export class ViewportHost {
|
|||||||
color:
|
color:
|
||||||
materialDef === null
|
materialDef === null
|
||||||
? fallbackColor
|
? fallbackColor
|
||||||
: Number.parseInt(materialDef.swatchColorHex.replace("#", ""), 16),
|
: Number.parseInt(
|
||||||
|
materialDef.swatchColorHex.replace("#", ""),
|
||||||
|
16
|
||||||
|
),
|
||||||
wireframe: true,
|
wireframe: true,
|
||||||
side: DoubleSide
|
side: DoubleSide
|
||||||
})
|
})
|
||||||
@@ -7773,7 +7771,9 @@ export class ViewportHost {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createRoadJunctionMaterial(junction: SplineCorridorJunction): Material {
|
private createRoadJunctionMaterial(
|
||||||
|
junction: SplineCorridorJunction
|
||||||
|
): Material {
|
||||||
return this.createRoadGeneratedMaterial(
|
return this.createRoadGeneratedMaterial(
|
||||||
junction.materialId,
|
junction.materialId,
|
||||||
PATH_JUNCTION_PREVIEW_COLOR
|
PATH_JUNCTION_PREVIEW_COLOR
|
||||||
@@ -7802,7 +7802,10 @@ export class ViewportHost {
|
|||||||
const meshes: Array<Mesh<BufferGeometry, Material>> = [];
|
const meshes: Array<Mesh<BufferGeometry, Material>> = [];
|
||||||
|
|
||||||
if (surfaceGeometry !== null) {
|
if (surfaceGeometry !== null) {
|
||||||
const mesh = new Mesh(surfaceGeometry, this.createRoadSurfaceMaterial(path));
|
const mesh = new Mesh(
|
||||||
|
surfaceGeometry,
|
||||||
|
this.createRoadSurfaceMaterial(path)
|
||||||
|
);
|
||||||
mesh.castShadow = false;
|
mesh.castShadow = false;
|
||||||
mesh.receiveShadow = true;
|
mesh.receiveShadow = true;
|
||||||
mesh.userData.pathId = path.id;
|
mesh.userData.pathId = path.id;
|
||||||
@@ -7825,7 +7828,10 @@ export class ViewportHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const edge = path.road.edges[side];
|
const edge = path.road.edges[side];
|
||||||
const edgeMesh = new Mesh(edgeGeometry, this.createRoadEdgeMaterial(edge));
|
const edgeMesh = new Mesh(
|
||||||
|
edgeGeometry,
|
||||||
|
this.createRoadEdgeMaterial(edge)
|
||||||
|
);
|
||||||
edgeMesh.castShadow = false;
|
edgeMesh.castShadow = false;
|
||||||
edgeMesh.receiveShadow = true;
|
edgeMesh.receiveShadow = true;
|
||||||
edgeMesh.userData.pathId = path.id;
|
edgeMesh.userData.pathId = path.id;
|
||||||
@@ -7851,7 +7857,10 @@ export class ViewportHost {
|
|||||||
const meshes: Array<Mesh<BufferGeometry, Material>> = [];
|
const meshes: Array<Mesh<BufferGeometry, Material>> = [];
|
||||||
|
|
||||||
if (geometry !== null) {
|
if (geometry !== null) {
|
||||||
const mesh = new Mesh(geometry, this.createRoadJunctionMaterial(junction));
|
const mesh = new Mesh(
|
||||||
|
geometry,
|
||||||
|
this.createRoadJunctionMaterial(junction)
|
||||||
|
);
|
||||||
mesh.castShadow = false;
|
mesh.castShadow = false;
|
||||||
mesh.receiveShadow = true;
|
mesh.receiveShadow = true;
|
||||||
mesh.userData.splineCorridorJunctionId = junction.id;
|
mesh.userData.splineCorridorJunctionId = junction.id;
|
||||||
@@ -7872,7 +7881,10 @@ export class ViewportHost {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (edgeGeometry !== null) {
|
if (edgeGeometry !== null) {
|
||||||
const edgeMesh = new Mesh(edgeGeometry, this.createRoadEdgeMaterial(edge));
|
const edgeMesh = new Mesh(
|
||||||
|
edgeGeometry,
|
||||||
|
this.createRoadEdgeMaterial(edge)
|
||||||
|
);
|
||||||
edgeMesh.castShadow = false;
|
edgeMesh.castShadow = false;
|
||||||
edgeMesh.receiveShadow = true;
|
edgeMesh.receiveShadow = true;
|
||||||
edgeMesh.userData.splineCorridorJunctionId = junction.id;
|
edgeMesh.userData.splineCorridorJunctionId = junction.id;
|
||||||
@@ -7937,7 +7949,10 @@ export class ViewportHost {
|
|||||||
for (const mesh of renderObjects.meshes) {
|
for (const mesh of renderObjects.meshes) {
|
||||||
this.roadSurfaceGroup.add(mesh);
|
this.roadSurfaceGroup.add(mesh);
|
||||||
}
|
}
|
||||||
this.roadSurfaceRenderObjects.set(`junction:${junction.id}`, renderObjects);
|
this.roadSurfaceRenderObjects.set(
|
||||||
|
`junction:${junction.id}`,
|
||||||
|
renderObjects
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.applyShadowState();
|
this.applyShadowState();
|
||||||
@@ -8199,7 +8214,8 @@ export class ViewportHost {
|
|||||||
resolvedPath.segments[0]!.start.z
|
resolvedPath.segments[0]!.start.z
|
||||||
),
|
),
|
||||||
...resolvedPath.segments.map(
|
...resolvedPath.segments.map(
|
||||||
(segment) => new Vector3(segment.end.x, segment.end.y, segment.end.z)
|
(segment) =>
|
||||||
|
new Vector3(segment.end.x, segment.end.y, segment.end.z)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -8234,7 +8250,9 @@ export class ViewportHost {
|
|||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPathSegmentMeshes(path: ScenePath): PathRenderObjects["segments"] {
|
private createPathSegmentMeshes(
|
||||||
|
path: ScenePath
|
||||||
|
): PathRenderObjects["segments"] {
|
||||||
const resolvedPath = resolveScenePath(path, {
|
const resolvedPath = resolveScenePath(path, {
|
||||||
terrains: this.getPathTerrainGlueTerrains()
|
terrains: this.getPathTerrainGlueTerrains()
|
||||||
});
|
});
|
||||||
@@ -8373,7 +8391,11 @@ export class ViewportHost {
|
|||||||
segment.start.y,
|
segment.start.y,
|
||||||
segment.start.z
|
segment.start.z
|
||||||
);
|
);
|
||||||
const endCenter = new Vector3(segment.end.x, segment.end.y, segment.end.z);
|
const endCenter = new Vector3(
|
||||||
|
segment.end.x,
|
||||||
|
segment.end.y,
|
||||||
|
segment.end.z
|
||||||
|
);
|
||||||
const leftStart = this.resolvePathRoadPreviewPoint(
|
const leftStart = this.resolvePathRoadPreviewPoint(
|
||||||
path,
|
path,
|
||||||
startCenter.clone().add(perpendicular),
|
startCenter.clone().add(perpendicular),
|
||||||
@@ -8550,7 +8572,11 @@ export class ViewportHost {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
marker.position.set(options.center.x, options.center.y + 0.08, options.center.z);
|
marker.position.set(
|
||||||
|
options.center.x,
|
||||||
|
options.center.y + 0.08,
|
||||||
|
options.center.z
|
||||||
|
);
|
||||||
marker.rotation.x = Math.PI * 0.5;
|
marker.rotation.x = Math.PI * 0.5;
|
||||||
marker.renderOrder = PATH_RENDER_ORDER + 4;
|
marker.renderOrder = PATH_RENDER_ORDER + 4;
|
||||||
marker.userData.nonPickable = true;
|
marker.userData.nonPickable = true;
|
||||||
@@ -8689,7 +8715,8 @@ export class ViewportHost {
|
|||||||
const resolvedPointPositions =
|
const resolvedPointPositions =
|
||||||
this.createResolvedPathPointPositionMap(path);
|
this.createResolvedPathPointPositionMap(path);
|
||||||
const pointMeshes = path.points.map((point) => {
|
const pointMeshes = path.points.map((point) => {
|
||||||
const pointPosition = resolvedPointPositions.get(point.id) ?? point.position;
|
const pointPosition =
|
||||||
|
resolvedPointPositions.get(point.id) ?? point.position;
|
||||||
const outlineMesh = new Mesh(
|
const outlineMesh = new Mesh(
|
||||||
new SphereGeometry(PATH_POINT_OUTLINE_RADIUS, 16, 12),
|
new SphereGeometry(PATH_POINT_OUTLINE_RADIUS, 16, 12),
|
||||||
new MeshBasicMaterial({
|
new MeshBasicMaterial({
|
||||||
@@ -8824,7 +8851,8 @@ export class ViewportHost {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pointPosition = resolvedPointPositions.get(point.id) ?? point.position;
|
const pointPosition =
|
||||||
|
resolvedPointPositions.get(point.id) ?? point.position;
|
||||||
|
|
||||||
pointMesh.outlineMesh.position.set(
|
pointMesh.outlineMesh.position.set(
|
||||||
pointPosition.x,
|
pointPosition.x,
|
||||||
@@ -11546,7 +11574,10 @@ export class ViewportHost {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hits = this.raycaster.intersectObjects(renderObjects.pickMeshes, true);
|
const hits = this.raycaster.intersectObjects(
|
||||||
|
renderObjects.pickMeshes,
|
||||||
|
true
|
||||||
|
);
|
||||||
const width = getTerrainFootprintWidth(terrain);
|
const width = getTerrainFootprintWidth(terrain);
|
||||||
const depth = getTerrainFootprintDepth(terrain);
|
const depth = getTerrainFootprintDepth(terrain);
|
||||||
|
|
||||||
@@ -11660,8 +11691,12 @@ export class ViewportHost {
|
|||||||
? terrain.position.z
|
? terrain.position.z
|
||||||
: terrain.position.z + depth * alpha;
|
: terrain.position.z + depth * alpha;
|
||||||
const height =
|
const height =
|
||||||
sampleDocumentTerrainHeightAtWorldPosition(terrain, worldX, worldZ, true) ??
|
sampleDocumentTerrainHeightAtWorldPosition(
|
||||||
0;
|
terrain,
|
||||||
|
worldX,
|
||||||
|
worldZ,
|
||||||
|
true
|
||||||
|
) ?? 0;
|
||||||
|
|
||||||
return new Vector3(
|
return new Vector3(
|
||||||
worldX,
|
worldX,
|
||||||
@@ -11781,7 +11816,10 @@ export class ViewportHost {
|
|||||||
const arrowEnd = midpoint.clone().addScaledVector(direction, arrowLength);
|
const arrowEnd = midpoint.clone().addScaledVector(direction, arrowLength);
|
||||||
const shaftEnd = arrowEnd
|
const shaftEnd = arrowEnd
|
||||||
.clone()
|
.clone()
|
||||||
.addScaledVector(direction, -TERRAIN_GRID_RESIZE_ARROW_HEAD_LENGTH * 0.72);
|
.addScaledVector(
|
||||||
|
direction,
|
||||||
|
-TERRAIN_GRID_RESIZE_ARROW_HEAD_LENGTH * 0.72
|
||||||
|
);
|
||||||
const shaftLength = Math.max(0.1, arrowStart.distanceTo(shaftEnd));
|
const shaftLength = Math.max(0.1, arrowStart.distanceTo(shaftEnd));
|
||||||
const shaftCenter = arrowStart.clone().add(shaftEnd).multiplyScalar(0.5);
|
const shaftCenter = arrowStart.clone().add(shaftEnd).multiplyScalar(0.5);
|
||||||
|
|
||||||
@@ -11796,8 +11834,10 @@ export class ViewportHost {
|
|||||||
visual.pickShaft.scale.set(1, shaftLength, 1);
|
visual.pickShaft.scale.set(1, shaftLength, 1);
|
||||||
|
|
||||||
const previousArrowGeometry = visual.line.geometry;
|
const previousArrowGeometry = visual.line.geometry;
|
||||||
visual.line.geometry =
|
visual.line.geometry = new BufferGeometry().setFromPoints([
|
||||||
new BufferGeometry().setFromPoints([arrowStart, arrowEnd]);
|
arrowStart,
|
||||||
|
arrowEnd
|
||||||
|
]);
|
||||||
previousArrowGeometry.dispose();
|
previousArrowGeometry.dispose();
|
||||||
visual.headOutline.position
|
visual.headOutline.position
|
||||||
.copy(arrowEnd)
|
.copy(arrowEnd)
|
||||||
@@ -11984,7 +12024,7 @@ export class ViewportHost {
|
|||||||
this.hasTerrainGridResizeChanged(
|
this.hasTerrainGridResizeChanged(
|
||||||
activeDrag.baseTerrain,
|
activeDrag.baseTerrain,
|
||||||
finalPreviewTerrain
|
finalPreviewTerrain
|
||||||
);
|
);
|
||||||
this.activeTerrainGridResizeDrag = null;
|
this.activeTerrainGridResizeDrag = null;
|
||||||
this.terrainGridResizeHover = null;
|
this.terrainGridResizeHover = null;
|
||||||
|
|
||||||
@@ -12041,8 +12081,7 @@ export class ViewportHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const targetTerrainId = this.currentTerrainBrushState.terrainId;
|
const targetTerrainId = this.currentTerrainBrushState.terrainId;
|
||||||
const raycastObjects =
|
const raycastObjects = this.getTerrainBrushRaycastObjects(targetTerrainId);
|
||||||
this.getTerrainBrushRaycastObjects(targetTerrainId);
|
|
||||||
|
|
||||||
if (raycastObjects.length === 0) {
|
if (raycastObjects.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -280,7 +280,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const runtimeScene = buildRuntimeSceneFromDocument({
|
const runtimeScene = buildRuntimeSceneFromDocument({
|
||||||
...createEmptySceneDocument({ name: "Mapped Rail Camera Rig Runtime Scene" }),
|
...createEmptySceneDocument({
|
||||||
|
name: "Mapped Rail Camera Rig Runtime Scene"
|
||||||
|
}),
|
||||||
paths: {
|
paths: {
|
||||||
[path.id]: path
|
[path.id]: path
|
||||||
},
|
},
|
||||||
@@ -683,7 +685,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
expect(runtimeScene.time).not.toBe(document.time);
|
expect(runtimeScene.time).not.toBe(document.time);
|
||||||
expect(runtimeScene.world).toEqual(document.world);
|
expect(runtimeScene.world).toEqual(document.world);
|
||||||
expect(runtimeScene.world).not.toBe(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.brushes).toHaveLength(1);
|
||||||
expect(runtimeScene.modelInstances).toEqual([
|
expect(runtimeScene.modelInstances).toEqual([
|
||||||
{
|
{
|
||||||
@@ -832,7 +836,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
y: 0,
|
y: 0,
|
||||||
z: 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).toHaveLength(2);
|
||||||
expect(runtimeScene.colliders[0]).toMatchObject({
|
expect(runtimeScene.colliders[0]).toMatchObject({
|
||||||
kind: "trimesh",
|
kind: "trimesh",
|
||||||
@@ -863,7 +869,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
});
|
});
|
||||||
const brushCollider = runtimeScene.colliders[0];
|
const brushCollider = runtimeScene.colliders[0];
|
||||||
if (brushCollider.kind !== "trimesh") {
|
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.vertices)).toHaveLength(24);
|
||||||
expect(Array.from(brushCollider.indices)).toHaveLength(36);
|
expect(Array.from(brushCollider.indices)).toHaveLength(36);
|
||||||
@@ -905,8 +913,7 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
allowLookInputTargetSwitch: playerStart.allowLookInputTargetSwitch,
|
allowLookInputTargetSwitch: playerStart.allowLookInputTargetSwitch,
|
||||||
targetButtonCyclesActiveTarget:
|
targetButtonCyclesActiveTarget:
|
||||||
playerStart.targetButtonCyclesActiveTarget,
|
playerStart.targetButtonCyclesActiveTarget,
|
||||||
invertMouseCameraHorizontal:
|
invertMouseCameraHorizontal: playerStart.invertMouseCameraHorizontal,
|
||||||
playerStart.invertMouseCameraHorizontal,
|
|
||||||
movement: {
|
movement: {
|
||||||
templateKind: "default",
|
templateKind: "default",
|
||||||
moveSpeed: DEFAULT_PLAYER_START_MOVE_SPEED,
|
moveSpeed: DEFAULT_PLAYER_START_MOVE_SPEED,
|
||||||
@@ -1377,28 +1384,30 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
[overnightNpc.id]: overnightNpc
|
[overnightNpc.id]: overnightNpc
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.scheduler.routines["routine-daytime"] = createProjectScheduleRoutine({
|
document.scheduler.routines["routine-daytime"] =
|
||||||
id: "routine-daytime",
|
createProjectScheduleRoutine({
|
||||||
title: "Day Shift",
|
id: "routine-daytime",
|
||||||
target: createActorControlTargetRef(daytimeNpc.actorId),
|
title: "Day Shift",
|
||||||
startHour: 9,
|
|
||||||
endHour: 17,
|
|
||||||
effect: createSetActorPresenceControlEffect({
|
|
||||||
target: createActorControlTargetRef(daytimeNpc.actorId),
|
target: createActorControlTargetRef(daytimeNpc.actorId),
|
||||||
active: true
|
startHour: 9,
|
||||||
})
|
endHour: 17,
|
||||||
});
|
effect: createSetActorPresenceControlEffect({
|
||||||
document.scheduler.routines["routine-overnight"] = createProjectScheduleRoutine({
|
target: createActorControlTargetRef(daytimeNpc.actorId),
|
||||||
id: "routine-overnight",
|
active: true
|
||||||
title: "Night Shift",
|
})
|
||||||
target: createActorControlTargetRef(overnightNpc.actorId),
|
});
|
||||||
startHour: 22,
|
document.scheduler.routines["routine-overnight"] =
|
||||||
endHour: 2,
|
createProjectScheduleRoutine({
|
||||||
effect: createSetActorPresenceControlEffect({
|
id: "routine-overnight",
|
||||||
|
title: "Night Shift",
|
||||||
target: createActorControlTargetRef(overnightNpc.actorId),
|
target: createActorControlTargetRef(overnightNpc.actorId),
|
||||||
active: true
|
startHour: 22,
|
||||||
})
|
endHour: 2,
|
||||||
});
|
effect: createSetActorPresenceControlEffect({
|
||||||
|
target: createActorControlTargetRef(overnightNpc.actorId),
|
||||||
|
active: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
const daytimeRuntimeScene = buildRuntimeSceneFromDocument(document, {
|
const daytimeRuntimeScene = buildRuntimeSceneFromDocument(document, {
|
||||||
runtimeClock: {
|
runtimeClock: {
|
||||||
@@ -1625,9 +1634,12 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
|
|
||||||
it("blocks first-person runtime builds when PlayerStart is missing", () => {
|
it("blocks first-person runtime builds when PlayerStart is missing", () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
buildRuntimeSceneFromDocument(createEmptySceneDocument({ name: "Missing Player Start" }), {
|
buildRuntimeSceneFromDocument(
|
||||||
navigationMode: "firstPerson"
|
createEmptySceneDocument({ name: "Missing Player Start" }),
|
||||||
})
|
{
|
||||||
|
navigationMode: "firstPerson"
|
||||||
|
}
|
||||||
|
)
|
||||||
).toThrow("First-person run requires an authored Player Start");
|
).toThrow("First-person run requires an authored Player Start");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1765,7 +1777,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(runtimeScene.playerStart?.entityId).toBe(enabledPlayerStart.id);
|
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.brushes[0]?.visible).toBe(false);
|
||||||
expect(runtimeScene.modelInstances).toEqual([
|
expect(runtimeScene.modelInstances).toEqual([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -1856,7 +1870,10 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
z: 8
|
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({
|
const modelInstance = createModelInstance({
|
||||||
id: "model-instance-runtime-collider",
|
id: "model-instance-runtime-collider",
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
@@ -1944,7 +1961,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
runtimeScene.colliders[0] === undefined ||
|
runtimeScene.colliders[0] === undefined ||
|
||||||
runtimeScene.colliders[0].source !== "terrain"
|
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(
|
expect(Array.from(runtimeScene.colliders[0].heights)).toEqual(
|
||||||
@@ -2142,13 +2161,13 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
expect(runtimeScene.splineCorridorClipIntervalsByPath.get(pathA.id)).toEqual(
|
expect(
|
||||||
[
|
runtimeScene.splineCorridorClipIntervalsByPath.get(pathA.id)
|
||||||
expect.objectContaining({
|
).toEqual([
|
||||||
junctionId: junction.id
|
expect.objectContaining({
|
||||||
})
|
junctionId: junction.id
|
||||||
]
|
})
|
||||||
);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds spline corridor box colliders for collision-enabled edges and repeaters", () => {
|
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", () => {
|
it("adds static-simple imported-model colliders as compound box pieces", () => {
|
||||||
const wallGeometry = new PlaneGeometry(4, 4, 4, 4);
|
const wallGeometry = new PlaneGeometry(4, 4, 4, 4);
|
||||||
wallGeometry.rotateY(Math.PI * 0.5);
|
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({
|
const modelInstance = createModelInstance({
|
||||||
id: "model-instance-runtime-static-simple",
|
id: "model-instance-runtime-static-simple",
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
@@ -2266,7 +2288,9 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
|
|
||||||
const runtimeScene = buildRuntimeSceneFromDocument(
|
const runtimeScene = buildRuntimeSceneFromDocument(
|
||||||
{
|
{
|
||||||
...createEmptySceneDocument({ name: "Imported Static Simple Collider Scene" }),
|
...createEmptySceneDocument({
|
||||||
|
name: "Imported Static Simple Collider Scene"
|
||||||
|
}),
|
||||||
assets: {
|
assets: {
|
||||||
[asset.id]: asset
|
[asset.id]: asset
|
||||||
},
|
},
|
||||||
@@ -2292,11 +2316,18 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
decomposition: "surface-voxel-boxes"
|
decomposition: "surface-voxel-boxes"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (runtimeScene.colliders[0].source !== "modelInstance" || runtimeScene.colliders[0].kind !== "compound") {
|
if (
|
||||||
throw new Error("Expected the runtime collider to be a generated compound model collider.");
|
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", () => {
|
it("preserves rotated whitebox box transforms for runner rendering and collision bounds", () => {
|
||||||
@@ -2537,31 +2568,32 @@ describe("buildRuntimeSceneFromDocument", () => {
|
|||||||
document.assets[asset.id] = asset;
|
document.assets[asset.id] = asset;
|
||||||
document.entities[npc.id] = npc;
|
document.entities[npc.id] = npc;
|
||||||
document.paths[path.id] = path;
|
document.paths[path.id] = path;
|
||||||
document.scheduler.routines["routine-patrol"] = createProjectScheduleRoutine({
|
document.scheduler.routines["routine-patrol"] =
|
||||||
id: "routine-patrol",
|
createProjectScheduleRoutine({
|
||||||
title: "Patrolling",
|
id: "routine-patrol",
|
||||||
target: actorTarget,
|
title: "Patrolling",
|
||||||
startHour: 9,
|
target: actorTarget,
|
||||||
endHour: 13,
|
startHour: 9,
|
||||||
effects: [
|
endHour: 13,
|
||||||
createSetActorPresenceControlEffect({
|
effects: [
|
||||||
target: actorTarget,
|
createSetActorPresenceControlEffect({
|
||||||
active: true
|
target: actorTarget,
|
||||||
}),
|
active: true
|
||||||
createPlayActorAnimationControlEffect({
|
}),
|
||||||
target: actorTarget,
|
createPlayActorAnimationControlEffect({
|
||||||
clipName: "Walk",
|
target: actorTarget,
|
||||||
loop: true
|
clipName: "Walk",
|
||||||
}),
|
loop: true
|
||||||
createFollowActorPathControlEffect({
|
}),
|
||||||
target: actorTarget,
|
createFollowActorPathControlEffect({
|
||||||
pathId: path.id,
|
target: actorTarget,
|
||||||
speed: 2,
|
pathId: path.id,
|
||||||
loop: false,
|
speed: 2,
|
||||||
progressMode: "deriveFromTime"
|
loop: false,
|
||||||
})
|
progressMode: "deriveFromTime"
|
||||||
]
|
})
|
||||||
});
|
]
|
||||||
|
});
|
||||||
|
|
||||||
const runtimeScene = buildRuntimeSceneFromDocument(document, {
|
const runtimeScene = buildRuntimeSceneFromDocument(document, {
|
||||||
runtimeClock: {
|
runtimeClock: {
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ import {
|
|||||||
createProjectAssetStorageKey,
|
createProjectAssetStorageKey,
|
||||||
type ImageAssetRecord
|
type ImageAssetRecord
|
||||||
} from "../../src/assets/project-assets";
|
} from "../../src/assets/project-assets";
|
||||||
import {
|
import { BOX_FACE_IDS, type WhiteboxFaceId } from "../../src/document/brushes";
|
||||||
BOX_FACE_IDS,
|
|
||||||
type WhiteboxFaceId
|
|
||||||
} from "../../src/document/brushes";
|
|
||||||
import { createCustomMaterialDef } from "../../src/materials/starter-material-library";
|
import { createCustomMaterialDef } from "../../src/materials/starter-material-library";
|
||||||
|
|
||||||
function createImageAsset(id: string): ImageAssetRecord {
|
function createImageAsset(id: string): ImageAssetRecord {
|
||||||
|
|||||||
@@ -318,7 +318,9 @@ describe("deleteProjectAssetFromProjectDocument", () => {
|
|||||||
);
|
);
|
||||||
const nextScene = nextProjectDocument.scenes[fixture.sceneId];
|
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([
|
expect(nextScene.foliageLayers[foliageLayer.id]?.prototypeIds).toEqual([
|
||||||
bundledPrototype.id
|
bundledPrototype.id
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -140,9 +140,7 @@ describe("validateSceneDocument", () => {
|
|||||||
id: "asset-invalid-material-map-hdr",
|
id: "asset-invalid-material-map-hdr",
|
||||||
sourceName: "studio.hdr",
|
sourceName: "studio.hdr",
|
||||||
mimeType: "image/vnd.radiance",
|
mimeType: "image/vnd.radiance",
|
||||||
storageKey: createProjectAssetStorageKey(
|
storageKey: createProjectAssetStorageKey("asset-invalid-material-map-hdr")
|
||||||
"asset-invalid-material-map-hdr"
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
const audioAsset: AudioAssetRecord = {
|
const audioAsset: AudioAssetRecord = {
|
||||||
id: "asset-invalid-material-map-audio",
|
id: "asset-invalid-material-map-audio",
|
||||||
|
|||||||
@@ -218,9 +218,9 @@ describe("project document JSON", () => {
|
|||||||
|
|
||||||
document.scenes[document.activeSceneId]!.foliageLayers[layer.id] = layer;
|
document.scenes[document.activeSceneId]!.foliageLayers[layer.id] = layer;
|
||||||
|
|
||||||
expect(parseProjectDocumentJson(serializeProjectDocument(document))).toEqual(
|
expect(
|
||||||
document
|
parseProjectDocumentJson(serializeProjectDocument(document))
|
||||||
);
|
).toEqual(document);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("round-trips terrain foliage masks in project scenes", () => {
|
it("round-trips terrain foliage masks in project scenes", () => {
|
||||||
@@ -256,9 +256,9 @@ describe("project document JSON", () => {
|
|||||||
scene.foliageLayers[layer.id] = layer;
|
scene.foliageLayers[layer.id] = layer;
|
||||||
scene.terrains[terrain.id] = terrain;
|
scene.terrains[terrain.id] = terrain;
|
||||||
|
|
||||||
expect(parseProjectDocumentJson(serializeProjectDocument(document))).toEqual(
|
expect(
|
||||||
document
|
parseProjectDocumentJson(serializeProjectDocument(document))
|
||||||
);
|
).toEqual(document);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("round-trips NPC dialogue references in project scenes", () => {
|
it("round-trips NPC dialogue references in project scenes", () => {
|
||||||
|
|||||||
@@ -316,9 +316,7 @@ describe("project package serialization", () => {
|
|||||||
kind: "image",
|
kind: "image",
|
||||||
sourceName: "custom-material-map.png",
|
sourceName: "custom-material-map.png",
|
||||||
mimeType: "image/png",
|
mimeType: "image/png",
|
||||||
storageKey: createProjectAssetStorageKey(
|
storageKey: createProjectAssetStorageKey("asset-image-material-package"),
|
||||||
"asset-image-material-package"
|
|
||||||
),
|
|
||||||
byteLength: 4,
|
byteLength: 4,
|
||||||
metadata: {
|
metadata: {
|
||||||
kind: "image",
|
kind: "image",
|
||||||
@@ -373,7 +371,9 @@ describe("project package serialization", () => {
|
|||||||
"assets/asset-image-material-package/custom-material-map.png"
|
"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: {
|
files: {
|
||||||
[imageAsset.sourceName]: {
|
[imageAsset.sourceName]: {
|
||||||
bytes: expect.any(ArrayBuffer),
|
bytes: expect.any(ArrayBuffer),
|
||||||
|
|||||||
@@ -186,22 +186,7 @@ describe("scene document JSON", () => {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
paintWeights: [
|
paintWeights: [
|
||||||
0.2,
|
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.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({
|
const document = createEmptySceneDocument({
|
||||||
@@ -1073,9 +1058,9 @@ describe("scene document JSON", () => {
|
|||||||
expect(
|
expect(
|
||||||
migratedDocument.paths[path.id]?.road.edges.right.collisionEnabled
|
migratedDocument.paths[path.id]?.road.edges.right.collisionEnabled
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
expect(migratedDocument.paths[path.id]?.repeaters[0]?.collisionEnabled).toBe(
|
expect(
|
||||||
false
|
migratedDocument.paths[path.id]?.repeaters[0]?.collisionEnabled
|
||||||
);
|
).toBe(false);
|
||||||
expect(SPLINE_CORRIDOR_COLLISION_SCENE_DOCUMENT_VERSION).toBe(106);
|
expect(SPLINE_CORRIDOR_COLLISION_SCENE_DOCUMENT_VERSION).toBe(106);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1175,7 +1160,8 @@ describe("scene document JSON", () => {
|
|||||||
splineCorridorJunctions: Record<string, { shapeMode?: unknown }>;
|
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;
|
delete legacyDocument.splineCorridorJunctions[junction.id]?.shapeMode;
|
||||||
|
|
||||||
const migratedDocument = migrateSceneDocument(legacyDocument);
|
const migratedDocument = migrateSceneDocument(legacyDocument);
|
||||||
@@ -3276,12 +3262,13 @@ describe("scene document JSON", () => {
|
|||||||
document.brushes[targetBrush.id] = targetBrush;
|
document.brushes[targetBrush.id] = targetBrush;
|
||||||
document.entities[linkedNpc.id] = linkedNpc;
|
document.entities[linkedNpc.id] = linkedNpc;
|
||||||
document.entities[silentNpc.id] = silentNpc;
|
document.entities[silentNpc.id] = silentNpc;
|
||||||
document.interactionLinks["link-npc-toggle"] = createToggleVisibilityInteractionLink({
|
document.interactionLinks["link-npc-toggle"] =
|
||||||
id: "link-npc-toggle",
|
createToggleVisibilityInteractionLink({
|
||||||
sourceEntityId: linkedNpc.id,
|
id: "link-npc-toggle",
|
||||||
trigger: "click",
|
sourceEntityId: linkedNpc.id,
|
||||||
targetBrushId: targetBrush.id
|
trigger: "click",
|
||||||
});
|
targetBrushId: targetBrush.id
|
||||||
|
});
|
||||||
|
|
||||||
const legacyDocument = JSON.parse(
|
const legacyDocument = JSON.parse(
|
||||||
serializeSceneDocument(document)
|
serializeSceneDocument(document)
|
||||||
|
|||||||
@@ -143,18 +143,19 @@ describe("whitebox material inspector", () => {
|
|||||||
.mockImplementation(() => undefined);
|
.mockImplementation(() => undefined);
|
||||||
const { container, store, brush } = await renderMaterialInspectorFixture();
|
const { container, store, brush } = await renderMaterialInspectorFixture();
|
||||||
|
|
||||||
expect(
|
expect(container.querySelector(".material-browser button")).toHaveAttribute(
|
||||||
container.querySelector(".material-browser button")
|
"data-testid",
|
||||||
).toHaveAttribute("data-testid", "material-button-default-whitebox");
|
"material-button-default-whitebox"
|
||||||
expect(screen.getByTestId("material-button-default-whitebox")).toHaveTextContent(
|
|
||||||
"Default Whitebox"
|
|
||||||
);
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByTestId("material-button-default-whitebox")
|
||||||
|
).toHaveTextContent("Default Whitebox");
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId("create-custom-material"));
|
fireEvent.click(screen.getByTestId("create-custom-material"));
|
||||||
|
|
||||||
const customMaterial = Object.values(store.getState().document.materials).find(
|
const customMaterial = Object.values(
|
||||||
(material) => material.kind === "custom"
|
store.getState().document.materials
|
||||||
);
|
).find((material) => material.kind === "custom");
|
||||||
|
|
||||||
expect(customMaterial).toBeDefined();
|
expect(customMaterial).toBeDefined();
|
||||||
expect(
|
expect(
|
||||||
@@ -186,19 +187,23 @@ describe("whitebox material inspector", () => {
|
|||||||
throw new Error("Expected a custom material to be created.");
|
throw new Error("Expected a custom material to be created.");
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(store.getState().document.materials[editedMaterialId]).toMatchObject({
|
expect(store.getState().document.materials[editedMaterialId]).toMatchObject(
|
||||||
kind: "custom",
|
{
|
||||||
albedoColorHex: "#224466",
|
kind: "custom",
|
||||||
roughness: 0.37,
|
albedoColorHex: "#224466",
|
||||||
metallic: 0.58
|
roughness: 0.37,
|
||||||
});
|
metallic: 0.58
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const exportedProject = store.exportDocumentJson();
|
const exportedProject = store.exportDocumentJson();
|
||||||
const reloadedStore = createEditorStore();
|
const reloadedStore = createEditorStore();
|
||||||
|
|
||||||
reloadedStore.importDocumentJson(exportedProject);
|
reloadedStore.importDocumentJson(exportedProject);
|
||||||
|
|
||||||
expect(reloadedStore.getState().document.materials[editedMaterialId]).toMatchObject({
|
expect(
|
||||||
|
reloadedStore.getState().document.materials[editedMaterialId]
|
||||||
|
).toMatchObject({
|
||||||
kind: "custom",
|
kind: "custom",
|
||||||
albedoColorHex: "#224466",
|
albedoColorHex: "#224466",
|
||||||
roughness: 0.37,
|
roughness: 0.37,
|
||||||
|
|||||||
Reference in New Issue
Block a user