Files
webeditor3d/tests/serialization/control-interaction-links.test.ts

255 lines
7.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
createActiveSceneControlTargetRef,
createLightControlTargetRef,
createModelInstanceControlTargetRef,
createSetAmbientLightColorControlEffect,
createSetLightEnabledControlEffect,
createSetModelInstanceVisibleControlEffect,
createSetSoundVolumeControlEffect,
createSoundEmitterControlTargetRef
} from "../../src/controls/control-surface";
import {
createEmptySceneDocument,
NPC_PRESENCE_SCENE_DOCUMENT_VERSION,
PROJECT_SCHEDULER_FOUNDATION_SCENE_DOCUMENT_VERSION,
SCENE_DOCUMENT_VERSION
} from "../../src/document/scene-document";
import { migrateSceneDocument } from "../../src/document/migrate-scene-document";
import {
createPointLightEntity,
createSoundEmitterEntity,
createTriggerVolumeEntity
} from "../../src/entities/entity-instances";
import {
createControlInteractionLink,
createPlaySoundInteractionLink
} from "../../src/interactions/interaction-links";
import {
createProjectAssetStorageKey,
type AudioAssetRecord,
type ModelAssetRecord
} from "../../src/assets/project-assets";
import { createModelInstance } from "../../src/assets/model-instances";
import {
parseSceneDocumentJson,
serializeSceneDocument
} from "../../src/serialization/scene-document-json";
describe("control interaction link serialization", () => {
it("round-trips authored control interaction links", () => {
const triggerVolume = createTriggerVolumeEntity({
id: "entity-trigger-main"
});
const pointLight = createPointLightEntity({
id: "entity-point-light-main"
});
const document = {
...createEmptySceneDocument(),
entities: {
[triggerVolume.id]: triggerVolume,
[pointLight.id]: pointLight
},
interactionLinks: {
"link-light-off": createControlInteractionLink({
id: "link-light-off",
sourceEntityId: triggerVolume.id,
trigger: "enter",
effect: createSetLightEnabledControlEffect({
target: createLightControlTargetRef("pointLight", pointLight.id),
enabled: false
})
})
}
};
expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
document
);
});
it("round-trips expanded control effect families", () => {
const audioAsset = {
id: "asset-audio-main",
kind: "audio" as const,
sourceName: "loop.ogg",
mimeType: "audio/ogg",
storageKey: createProjectAssetStorageKey("asset-audio-main"),
byteLength: 1024,
metadata: {
kind: "audio" as const,
durationSeconds: 3,
channelCount: 2,
sampleRateHz: 48000,
warnings: []
}
} satisfies AudioAssetRecord;
const modelAsset = {
id: "asset-model-main",
kind: "model" as const,
sourceName: "animated.glb",
mimeType: "model/gltf-binary",
storageKey: createProjectAssetStorageKey("asset-model-main"),
byteLength: 2048,
metadata: {
kind: "model" as const,
format: "glb" as const,
sceneName: null,
nodeCount: 1,
meshCount: 1,
materialNames: [],
textureNames: [],
animationNames: ["Idle"],
boundingBox: null,
warnings: []
}
} satisfies ModelAssetRecord;
const triggerVolume = createTriggerVolumeEntity({
id: "entity-trigger-main"
});
const pointLight = createPointLightEntity({
id: "entity-point-light-main"
});
const soundEmitter = createSoundEmitterEntity({
id: "entity-sound-main",
audioAssetId: audioAsset.id
});
const modelInstance = createModelInstance({
id: "model-instance-main",
assetId: modelAsset.id
});
const document = {
...createEmptySceneDocument(),
assets: {
[audioAsset.id]: audioAsset,
[modelAsset.id]: modelAsset
},
modelInstances: {
[modelInstance.id]: modelInstance
},
entities: {
[triggerVolume.id]: triggerVolume,
[pointLight.id]: pointLight,
[soundEmitter.id]: soundEmitter
},
interactionLinks: {
"link-hide-model": createControlInteractionLink({
id: "link-hide-model",
sourceEntityId: triggerVolume.id,
effect: createSetModelInstanceVisibleControlEffect({
target: createModelInstanceControlTargetRef(modelInstance.id),
visible: false
})
}),
"link-sound-volume": createControlInteractionLink({
id: "link-sound-volume",
sourceEntityId: triggerVolume.id,
effect: createSetSoundVolumeControlEffect({
target: createSoundEmitterControlTargetRef(soundEmitter.id),
volume: 0.25
})
}),
"link-ambient-color": createControlInteractionLink({
id: "link-ambient-color",
sourceEntityId: triggerVolume.id,
effect: createSetAmbientLightColorControlEffect({
target: createActiveSceneControlTargetRef(),
colorHex: "#112233"
})
})
}
};
expect(parseSceneDocumentJson(serializeSceneDocument(document))).toEqual(
document
);
});
it("migrates v44 documents without changing existing interaction links", () => {
const audioAsset = {
id: "asset-audio-main",
kind: "audio" as const,
sourceName: "loop.ogg",
mimeType: "audio/ogg",
storageKey: createProjectAssetStorageKey("asset-audio-main"),
byteLength: 1024,
metadata: {
kind: "audio" as const,
durationSeconds: 3,
channelCount: 2,
sampleRateHz: 48000,
warnings: []
}
} satisfies AudioAssetRecord;
const triggerVolume = createTriggerVolumeEntity({
id: "entity-trigger-main"
});
const soundEmitter = createSoundEmitterEntity({
id: "entity-sound-main",
audioAssetId: audioAsset.id
});
const legacyDocument = {
...createEmptySceneDocument(),
version: NPC_PRESENCE_SCENE_DOCUMENT_VERSION,
assets: {
[audioAsset.id]: audioAsset
},
entities: {
[triggerVolume.id]: triggerVolume,
[soundEmitter.id]: soundEmitter
},
interactionLinks: {
"link-play-sound": createPlaySoundInteractionLink({
id: "link-play-sound",
sourceEntityId: triggerVolume.id,
trigger: "enter",
targetSoundEmitterId: soundEmitter.id
})
}
};
const migratedDocument = migrateSceneDocument(legacyDocument);
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
expect(migratedDocument.interactionLinks).toEqual(
legacyDocument.interactionLinks
);
});
it("migrates v46 control interaction documents without rewriting effects", () => {
const triggerVolume = createTriggerVolumeEntity({
id: "entity-trigger-main"
});
const pointLight = createPointLightEntity({
id: "entity-point-light-main"
});
const legacyDocument = {
...createEmptySceneDocument(),
version: PROJECT_SCHEDULER_FOUNDATION_SCENE_DOCUMENT_VERSION,
entities: {
[triggerVolume.id]: triggerVolume,
[pointLight.id]: pointLight
},
interactionLinks: {
"link-light-off": createControlInteractionLink({
id: "link-light-off",
sourceEntityId: triggerVolume.id,
trigger: "enter",
effect: createSetLightEnabledControlEffect({
target: createLightControlTargetRef("pointLight", pointLight.id),
enabled: false
})
})
}
};
const migratedDocument = migrateSceneDocument(legacyDocument);
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
expect(migratedDocument.interactionLinks).toEqual(
legacyDocument.interactionLinks
);
});
});