auto-git:
[change] tests/domain/build-runtime-scene.test.ts [change] tests/domain/scene-document-validation.test.ts [change] tests/serialization/scene-document-json.test.ts [change] tests/unit/entity-instances.test.ts [change] tests/unit/viewport-focus.test.ts
This commit is contained in:
@@ -13,6 +13,8 @@ import { createDefaultProjectTimeSettings } from "../../src/document/project-tim
|
||||
import { createTerrain } from "../../src/document/terrains";
|
||||
import { createEmptySceneDocument } from "../../src/document/scene-document";
|
||||
import {
|
||||
createCameraRigEntity,
|
||||
createCameraRigEntityTargetRef,
|
||||
DEFAULT_PLAYER_START_MOVE_SPEED,
|
||||
DEFAULT_PLAYER_START_MOVEMENT_CAPABILITIES,
|
||||
createNpcEntity,
|
||||
@@ -42,6 +44,86 @@ import { createFixtureLoadedModelAssetFromGeometry } from "../helpers/model-coll
|
||||
const defaultMovementTemplate = createPlayerStartMovementTemplate();
|
||||
|
||||
describe("buildRuntimeSceneFromDocument", () => {
|
||||
it("builds enabled fixed camera rigs into runtime entities", () => {
|
||||
const interactable = createInteractableEntity({
|
||||
id: "entity-interactable-camera-anchor",
|
||||
position: {
|
||||
x: 4,
|
||||
y: 1,
|
||||
z: -2
|
||||
},
|
||||
prompt: "Anchor"
|
||||
});
|
||||
const cameraRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-overlook",
|
||||
position: {
|
||||
x: 8,
|
||||
y: 3,
|
||||
z: -6
|
||||
},
|
||||
priority: 7,
|
||||
defaultActive: false,
|
||||
target: createCameraRigEntityTargetRef(interactable.id),
|
||||
targetOffset: {
|
||||
x: 0,
|
||||
y: 1.5,
|
||||
z: 0
|
||||
},
|
||||
transitionMode: "blend",
|
||||
transitionDurationSeconds: 0.6,
|
||||
lookAround: {
|
||||
enabled: true,
|
||||
yawLimitDegrees: 10,
|
||||
pitchLimitDegrees: 6,
|
||||
recenterSpeed: 5
|
||||
}
|
||||
});
|
||||
const disabledCameraRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-disabled",
|
||||
enabled: false
|
||||
});
|
||||
|
||||
const runtimeScene = buildRuntimeSceneFromDocument({
|
||||
...createEmptySceneDocument({ name: "Camera Rig Runtime Scene" }),
|
||||
entities: {
|
||||
[interactable.id]: interactable,
|
||||
[cameraRig.id]: cameraRig,
|
||||
[disabledCameraRig.id]: disabledCameraRig
|
||||
}
|
||||
});
|
||||
|
||||
expect(runtimeScene.entities.cameraRigs).toEqual([
|
||||
{
|
||||
entityId: cameraRig.id,
|
||||
rigType: "fixed",
|
||||
priority: 7,
|
||||
defaultActive: false,
|
||||
position: {
|
||||
x: 8,
|
||||
y: 3,
|
||||
z: -6
|
||||
},
|
||||
target: {
|
||||
kind: "entity",
|
||||
entityId: interactable.id
|
||||
},
|
||||
targetOffset: {
|
||||
x: 0,
|
||||
y: 1.5,
|
||||
z: 0
|
||||
},
|
||||
transitionMode: "blend",
|
||||
transitionDurationSeconds: 0.6,
|
||||
lookAround: {
|
||||
enabled: true,
|
||||
yawLimitDegrees: 10,
|
||||
pitchLimitDegrees: 6,
|
||||
recenterSpeed: 5
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it("builds runtime brush data, colliders, and an authored player spawn from the document", () => {
|
||||
const brush = createBoxBrush({
|
||||
id: "brush-room-floor",
|
||||
|
||||
@@ -17,6 +17,9 @@ import { createScenePath } from "../../src/document/paths";
|
||||
import { createEmptySceneDocument } from "../../src/document/scene-document";
|
||||
import { validateSceneDocument } from "../../src/document/scene-document-validation";
|
||||
import {
|
||||
createCameraRigActorTargetRef,
|
||||
createCameraRigEntity,
|
||||
createCameraRigEntityTargetRef,
|
||||
createPointLightEntity,
|
||||
createInteractableEntity,
|
||||
createNpcEntity,
|
||||
@@ -753,6 +756,81 @@ describe("validateSceneDocument", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("validates fixed camera rig targets and authored settings", () => {
|
||||
const otherCameraRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-other"
|
||||
});
|
||||
const missingActorRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-missing-actor",
|
||||
target: createCameraRigActorTargetRef("actor-missing")
|
||||
});
|
||||
const missingEntityRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-missing-entity",
|
||||
target: createCameraRigEntityTargetRef("entity-missing")
|
||||
});
|
||||
const selfTargetRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-self"
|
||||
});
|
||||
const cameraTargetRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-camera-target",
|
||||
target: createCameraRigEntityTargetRef(otherCameraRig.id)
|
||||
});
|
||||
const invalidRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-invalid"
|
||||
});
|
||||
|
||||
selfTargetRig.target = createCameraRigEntityTargetRef(selfTargetRig.id);
|
||||
invalidRig.priority = Number.NaN;
|
||||
invalidRig.targetOffset = {
|
||||
x: Number.POSITIVE_INFINITY,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
invalidRig.lookAround.enabled = "yes" as unknown as boolean;
|
||||
invalidRig.lookAround.pitchLimitDegrees = Number.NaN;
|
||||
|
||||
const validation = validateSceneDocument({
|
||||
...createEmptySceneDocument(),
|
||||
entities: {
|
||||
[otherCameraRig.id]: otherCameraRig,
|
||||
[missingActorRig.id]: missingActorRig,
|
||||
[missingEntityRig.id]: missingEntityRig,
|
||||
[selfTargetRig.id]: selfTargetRig,
|
||||
[cameraTargetRig.id]: cameraTargetRig,
|
||||
[invalidRig.id]: invalidRig
|
||||
}
|
||||
});
|
||||
|
||||
expect(validation.errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
code: "missing-camera-rig-target-actor"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "missing-camera-rig-target-entity"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "camera-rig-self-target"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "invalid-camera-rig-target-entity-kind"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "invalid-camera-rig-priority"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "invalid-camera-rig-target-offset"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "invalid-camera-rig-look-around-enabled"
|
||||
}),
|
||||
expect.objectContaining({
|
||||
code: "invalid-camera-rig-look-around-pitch-limit"
|
||||
})
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("detects missing and invalid audio asset references on Sound Emitters", () => {
|
||||
const audioAsset = {
|
||||
id: "asset-audio-main",
|
||||
|
||||
@@ -53,7 +53,9 @@ import {
|
||||
} from "../../src/document/scene-document";
|
||||
import { migrateSceneDocument } from "../../src/document/migrate-scene-document";
|
||||
import {
|
||||
createCameraRigEntity,
|
||||
createNpcAlwaysPresence,
|
||||
createCameraRigWorldPointTargetRef,
|
||||
createNpcEntity,
|
||||
createNpcTimeWindowPresence,
|
||||
createPointLightEntity,
|
||||
@@ -970,6 +972,103 @@ describe("scene document JSON", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("round-trips a document containing an authored Camera Rig entity", () => {
|
||||
const cameraRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-main",
|
||||
name: "Overlook",
|
||||
position: {
|
||||
x: 6,
|
||||
y: 4,
|
||||
z: -8
|
||||
},
|
||||
priority: 5,
|
||||
defaultActive: false,
|
||||
target: createCameraRigWorldPointTargetRef({
|
||||
x: 1,
|
||||
y: 1.75,
|
||||
z: -2
|
||||
}),
|
||||
targetOffset: {
|
||||
x: 0,
|
||||
y: 0.25,
|
||||
z: 0
|
||||
},
|
||||
transitionMode: "blend",
|
||||
transitionDurationSeconds: 0.8,
|
||||
lookAround: {
|
||||
enabled: true,
|
||||
yawLimitDegrees: 15,
|
||||
pitchLimitDegrees: 9,
|
||||
recenterSpeed: 4
|
||||
}
|
||||
});
|
||||
const document = {
|
||||
...createEmptySceneDocument({ name: "Camera Rig Scene" }),
|
||||
entities: {
|
||||
[cameraRig.id]: cameraRig
|
||||
}
|
||||
};
|
||||
|
||||
expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
|
||||
document
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates version 73 camera rigs to include fixed-rig defaults", () => {
|
||||
const cameraRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-legacy",
|
||||
name: "Legacy Camera",
|
||||
position: {
|
||||
x: -4,
|
||||
y: 3,
|
||||
z: 9
|
||||
},
|
||||
target: createCameraRigWorldPointTargetRef({
|
||||
x: 2,
|
||||
y: 1,
|
||||
z: -3
|
||||
})
|
||||
});
|
||||
const legacyDocument = {
|
||||
...createEmptySceneDocument({ name: "Legacy Camera Rig Scene" }),
|
||||
entities: {
|
||||
[cameraRig.id]: cameraRig
|
||||
}
|
||||
};
|
||||
const serializedLegacyDocument = JSON.parse(
|
||||
serializeSceneDocument(legacyDocument)
|
||||
) as {
|
||||
version: number;
|
||||
entities: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
const serializedCameraRig = serializedLegacyDocument.entities[cameraRig.id];
|
||||
|
||||
serializedLegacyDocument.version =
|
||||
CAMERA_RIG_ENTITY_SCENE_DOCUMENT_VERSION - 1;
|
||||
delete serializedCameraRig.priority;
|
||||
delete serializedCameraRig.defaultActive;
|
||||
delete serializedCameraRig.targetOffset;
|
||||
delete serializedCameraRig.transitionMode;
|
||||
delete serializedCameraRig.transitionDurationSeconds;
|
||||
delete serializedCameraRig.lookAround;
|
||||
|
||||
const migratedDocument = parseSceneDocumentJson(
|
||||
JSON.stringify(serializedLegacyDocument)
|
||||
);
|
||||
|
||||
expect(migratedDocument.entities[cameraRig.id]).toEqual(
|
||||
createCameraRigEntity({
|
||||
id: cameraRig.id,
|
||||
name: cameraRig.name,
|
||||
visible: cameraRig.visible,
|
||||
enabled: cameraRig.enabled,
|
||||
position: cameraRig.position,
|
||||
rigType: cameraRig.rigType,
|
||||
target: cameraRig.target
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("migrates version 14 documents without entity names", () => {
|
||||
const pointLight = createPointLightEntity({
|
||||
id: "entity-point-light-legacy",
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
DEFAULT_CAMERA_RIG_DEFAULT_ACTIVE,
|
||||
DEFAULT_CAMERA_RIG_LOOK_AROUND_ENABLED,
|
||||
DEFAULT_CAMERA_RIG_LOOK_AROUND_PITCH_LIMIT_DEGREES,
|
||||
DEFAULT_CAMERA_RIG_LOOK_AROUND_RECENTER_SPEED,
|
||||
DEFAULT_CAMERA_RIG_LOOK_AROUND_YAW_LIMIT_DEGREES,
|
||||
DEFAULT_CAMERA_RIG_PRIORITY,
|
||||
DEFAULT_CAMERA_RIG_TARGET_OFFSET,
|
||||
DEFAULT_CAMERA_RIG_TRANSITION_DURATION_SECONDS,
|
||||
DEFAULT_CAMERA_RIG_TRANSITION_MODE,
|
||||
DEFAULT_POINT_LIGHT_COLOR_HEX,
|
||||
DEFAULT_POINT_LIGHT_DISTANCE,
|
||||
DEFAULT_POINT_LIGHT_INTENSITY,
|
||||
@@ -86,6 +95,26 @@ describe("entity registry defaults", () => {
|
||||
distance: DEFAULT_POINT_LIGHT_DISTANCE
|
||||
});
|
||||
|
||||
expect(createDefaultEntityInstance("cameraRig")).toMatchObject({
|
||||
kind: "cameraRig",
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
rigType: "fixed",
|
||||
priority: DEFAULT_CAMERA_RIG_PRIORITY,
|
||||
defaultActive: DEFAULT_CAMERA_RIG_DEFAULT_ACTIVE,
|
||||
target: {
|
||||
kind: "player"
|
||||
},
|
||||
targetOffset: DEFAULT_CAMERA_RIG_TARGET_OFFSET,
|
||||
transitionMode: DEFAULT_CAMERA_RIG_TRANSITION_MODE,
|
||||
transitionDurationSeconds: DEFAULT_CAMERA_RIG_TRANSITION_DURATION_SECONDS,
|
||||
lookAround: {
|
||||
enabled: DEFAULT_CAMERA_RIG_LOOK_AROUND_ENABLED,
|
||||
yawLimitDegrees: DEFAULT_CAMERA_RIG_LOOK_AROUND_YAW_LIMIT_DEGREES,
|
||||
pitchLimitDegrees: DEFAULT_CAMERA_RIG_LOOK_AROUND_PITCH_LIMIT_DEGREES,
|
||||
recenterSpeed: DEFAULT_CAMERA_RIG_LOOK_AROUND_RECENTER_SPEED
|
||||
}
|
||||
});
|
||||
|
||||
expect(createDefaultEntityInstance("spotLight")).toMatchObject({
|
||||
kind: "spotLight",
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
|
||||
@@ -5,7 +5,13 @@ import { createBoxBrush } from "../../src/document/brushes";
|
||||
import { createScenePath } from "../../src/document/paths";
|
||||
import { createEmptySceneDocument } from "../../src/document/scene-document";
|
||||
import { createTerrain } from "../../src/document/terrains";
|
||||
import { createPointLightEntity, createPlayerStartEntity, createSpotLightEntity, createTriggerVolumeEntity } from "../../src/entities/entity-instances";
|
||||
import {
|
||||
createCameraRigEntity,
|
||||
createPointLightEntity,
|
||||
createPlayerStartEntity,
|
||||
createSpotLightEntity,
|
||||
createTriggerVolumeEntity
|
||||
} from "../../src/entities/entity-instances";
|
||||
import { resolveViewportFocusTarget } from "../../src/viewport-three/viewport-focus";
|
||||
|
||||
describe("resolveViewportFocusTarget", () => {
|
||||
@@ -169,6 +175,38 @@ describe("resolveViewportFocusTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("frames the selected Camera Rig helper", () => {
|
||||
const cameraRig = createCameraRigEntity({
|
||||
id: "entity-camera-rig-focus",
|
||||
position: {
|
||||
x: -3,
|
||||
y: 2,
|
||||
z: 5
|
||||
}
|
||||
});
|
||||
const document = {
|
||||
...createEmptySceneDocument(),
|
||||
entities: {
|
||||
[cameraRig.id]: cameraRig
|
||||
}
|
||||
};
|
||||
|
||||
const focusTarget = resolveViewportFocusTarget(document, {
|
||||
kind: "entities",
|
||||
ids: [cameraRig.id]
|
||||
});
|
||||
|
||||
expect(focusTarget).toEqual({
|
||||
center: {
|
||||
x: -3,
|
||||
y: 2.28,
|
||||
z: 5
|
||||
},
|
||||
radius: expect.any(Number)
|
||||
});
|
||||
expect(focusTarget?.radius).toBeGreaterThan(0.45);
|
||||
});
|
||||
|
||||
it("frames the selected Path around its authored point bounds", () => {
|
||||
const path = createScenePath({
|
||||
id: "path-focus",
|
||||
|
||||
Reference in New Issue
Block a user