918 lines
29 KiB
TypeScript
918 lines
29 KiB
TypeScript
import type { ProjectDocument, ProjectScene } from "../document/scene-document";
|
|
import { getScenePathLabel, getScenePaths } from "../document/paths";
|
|
import {
|
|
createActiveSceneControlTargetRef,
|
|
createActivateCameraRigOverrideControlEffect,
|
|
createFollowActorPathControlEffect,
|
|
createActorControlTargetRef,
|
|
createCameraRigControlTargetRef,
|
|
createClearCameraRigOverrideControlEffect,
|
|
createInteractionControlTargetRef,
|
|
createLightControlTargetRef,
|
|
createModelInstanceControlTargetRef,
|
|
createPlayActorAnimationControlEffect,
|
|
createPlayModelAnimationControlEffect,
|
|
createPlaySoundControlEffect,
|
|
createSetActorPresenceControlEffect,
|
|
createSetAmbientLightColorControlEffect,
|
|
createSetAmbientLightIntensityControlEffect,
|
|
createSetInteractionEnabledControlEffect,
|
|
createSetLightColorControlEffect,
|
|
createSetLightEnabledControlEffect,
|
|
createSetLightIntensityControlEffect,
|
|
createSetModelInstanceVisibleControlEffect,
|
|
createProjectGlobalControlTargetRef,
|
|
createSetSoundVolumeControlEffect,
|
|
createSetSunLightColorControlEffect,
|
|
createSetSunLightIntensityControlEffect,
|
|
createSoundEmitterControlTargetRef,
|
|
createStopModelAnimationControlEffect,
|
|
createStopSoundControlEffect,
|
|
formatControlTargetRef,
|
|
getControlTargetRefKey,
|
|
type ControlEffect,
|
|
type ControlTargetRef
|
|
} from "../controls/control-surface";
|
|
import { getModelInstances } from "../assets/model-instances";
|
|
import { getModelInstanceDisplayLabel } from "../assets/model-instance-labels";
|
|
import { getSortedEntityDisplayLabels } from "../entities/entity-labels";
|
|
import { listProjectNpcActors } from "../entities/npc-actor-registry";
|
|
|
|
export const PROJECT_SCHEDULE_EFFECT_OPTION_IDS = [
|
|
"camera.activate",
|
|
"camera.clear",
|
|
"actor.present",
|
|
"actor.hidden",
|
|
"actor.playAnimation",
|
|
"actor.followPath",
|
|
"model.playAnimation",
|
|
"model.stopAnimation",
|
|
"model.visible",
|
|
"model.hidden",
|
|
"sound.play",
|
|
"sound.stop",
|
|
"sound.volume",
|
|
"interaction.enabled",
|
|
"interaction.disabled",
|
|
"light.enabled",
|
|
"light.disabled",
|
|
"light.intensity",
|
|
"light.color",
|
|
"scene.ambientIntensity",
|
|
"scene.ambientColor",
|
|
"scene.sunIntensity",
|
|
"scene.sunColor"
|
|
] as const;
|
|
|
|
export type ProjectScheduleEffectOptionId =
|
|
(typeof PROJECT_SCHEDULE_EFFECT_OPTION_IDS)[number];
|
|
|
|
export interface ProjectScheduleEffectOption {
|
|
id: ProjectScheduleEffectOptionId;
|
|
label: string;
|
|
valueKind: "none" | "number" | "color" | "animation" | "path";
|
|
valueLabel?: string;
|
|
min?: number;
|
|
step?: number;
|
|
}
|
|
|
|
export interface ProjectSchedulePathOption {
|
|
pathId: string;
|
|
label: string;
|
|
loop: boolean;
|
|
}
|
|
|
|
export interface ProjectScheduleTargetOptionDefaults {
|
|
actorAnimationClipNames?: string[];
|
|
actorAnimationLoop?: boolean;
|
|
actorPathOptions?: ProjectSchedulePathOption[];
|
|
actorPathSpeed?: number;
|
|
animationClipNames?: string[];
|
|
animationLoop?: boolean;
|
|
soundVolume?: number;
|
|
lightIntensity?: number;
|
|
lightColorHex?: string;
|
|
ambientLightIntensity?: number;
|
|
ambientLightColorHex?: string;
|
|
sunLightIntensity?: number;
|
|
sunLightColorHex?: string;
|
|
}
|
|
|
|
export interface ProjectScheduleTargetOption {
|
|
key: string;
|
|
target: ControlTargetRef;
|
|
label: string;
|
|
subtitle: string;
|
|
groupLabel: string;
|
|
defaults: ProjectScheduleTargetOptionDefaults;
|
|
}
|
|
|
|
const PROJECT_SCHEDULE_GROUP_ORDER: Record<string, number> = {
|
|
Project: 0,
|
|
"Scene Lighting": 1,
|
|
"Camera Rigs": 2,
|
|
Actors: 3,
|
|
"Model Instances": 4,
|
|
"Sound Emitters": 5,
|
|
Interactions: 6,
|
|
Lights: 7,
|
|
Other: 8
|
|
};
|
|
|
|
const PROJECT_SCHEDULE_EFFECT_OPTIONS: Record<
|
|
ProjectScheduleEffectOptionId,
|
|
ProjectScheduleEffectOption
|
|
> = {
|
|
"camera.activate": {
|
|
id: "camera.activate",
|
|
label: "Activate Camera Override",
|
|
valueKind: "none"
|
|
},
|
|
"camera.clear": {
|
|
id: "camera.clear",
|
|
label: "Clear Camera Override",
|
|
valueKind: "none"
|
|
},
|
|
"actor.present": {
|
|
id: "actor.present",
|
|
label: "Present",
|
|
valueKind: "none"
|
|
},
|
|
"actor.hidden": {
|
|
id: "actor.hidden",
|
|
label: "Hidden",
|
|
valueKind: "none"
|
|
},
|
|
"actor.playAnimation": {
|
|
id: "actor.playAnimation",
|
|
label: "Play Animation",
|
|
valueKind: "animation"
|
|
},
|
|
"actor.followPath": {
|
|
id: "actor.followPath",
|
|
label: "Follow Path",
|
|
valueKind: "path"
|
|
},
|
|
"model.playAnimation": {
|
|
id: "model.playAnimation",
|
|
label: "Play Animation",
|
|
valueKind: "animation"
|
|
},
|
|
"model.stopAnimation": {
|
|
id: "model.stopAnimation",
|
|
label: "Stop Animation",
|
|
valueKind: "none"
|
|
},
|
|
"model.visible": {
|
|
id: "model.visible",
|
|
label: "Visible",
|
|
valueKind: "none"
|
|
},
|
|
"model.hidden": {
|
|
id: "model.hidden",
|
|
label: "Hidden",
|
|
valueKind: "none"
|
|
},
|
|
"sound.play": {
|
|
id: "sound.play",
|
|
label: "Play Sound",
|
|
valueKind: "none"
|
|
},
|
|
"sound.stop": {
|
|
id: "sound.stop",
|
|
label: "Stop Sound",
|
|
valueKind: "none"
|
|
},
|
|
"sound.volume": {
|
|
id: "sound.volume",
|
|
label: "Set Volume",
|
|
valueKind: "number",
|
|
valueLabel: "Volume",
|
|
min: 0,
|
|
step: 0.05
|
|
},
|
|
"interaction.enabled": {
|
|
id: "interaction.enabled",
|
|
label: "Enabled",
|
|
valueKind: "none"
|
|
},
|
|
"interaction.disabled": {
|
|
id: "interaction.disabled",
|
|
label: "Disabled",
|
|
valueKind: "none"
|
|
},
|
|
"light.enabled": {
|
|
id: "light.enabled",
|
|
label: "Enabled",
|
|
valueKind: "none"
|
|
},
|
|
"light.disabled": {
|
|
id: "light.disabled",
|
|
label: "Disabled",
|
|
valueKind: "none"
|
|
},
|
|
"light.intensity": {
|
|
id: "light.intensity",
|
|
label: "Set Intensity",
|
|
valueKind: "number",
|
|
valueLabel: "Intensity",
|
|
min: 0,
|
|
step: 0.1
|
|
},
|
|
"light.color": {
|
|
id: "light.color",
|
|
label: "Set Color",
|
|
valueKind: "color",
|
|
valueLabel: "Color"
|
|
},
|
|
"scene.ambientIntensity": {
|
|
id: "scene.ambientIntensity",
|
|
label: "Ambient Intensity",
|
|
valueKind: "number",
|
|
valueLabel: "Intensity",
|
|
min: 0,
|
|
step: 0.1
|
|
},
|
|
"scene.ambientColor": {
|
|
id: "scene.ambientColor",
|
|
label: "Ambient Color",
|
|
valueKind: "color",
|
|
valueLabel: "Color"
|
|
},
|
|
"scene.sunIntensity": {
|
|
id: "scene.sunIntensity",
|
|
label: "Sun Intensity",
|
|
valueKind: "number",
|
|
valueLabel: "Intensity",
|
|
min: 0,
|
|
step: 0.1
|
|
},
|
|
"scene.sunColor": {
|
|
id: "scene.sunColor",
|
|
label: "Sun Color",
|
|
valueKind: "color",
|
|
valueLabel: "Color"
|
|
}
|
|
};
|
|
|
|
const INTERACTION_UNSUPPORTED_PROJECT_SCHEDULE_EFFECT_OPTION_IDS = new Set<ProjectScheduleEffectOptionId>([
|
|
"actor.playAnimation",
|
|
"actor.followPath"
|
|
]);
|
|
|
|
function compareProjectScheduleTargetOptions(
|
|
left: ProjectScheduleTargetOption,
|
|
right: ProjectScheduleTargetOption
|
|
): number {
|
|
return (
|
|
(PROJECT_SCHEDULE_GROUP_ORDER[left.groupLabel] ?? PROJECT_SCHEDULE_GROUP_ORDER.Other) -
|
|
(PROJECT_SCHEDULE_GROUP_ORDER[right.groupLabel] ?? PROJECT_SCHEDULE_GROUP_ORDER.Other) ||
|
|
left.label.localeCompare(right.label) ||
|
|
left.subtitle.localeCompare(right.subtitle) ||
|
|
left.key.localeCompare(right.key)
|
|
);
|
|
}
|
|
|
|
function getSceneTargetSubtitle(scene: ProjectScene): string {
|
|
return `${scene.name} · ${scene.id}`;
|
|
}
|
|
|
|
function createSceneLightingTargetOption(
|
|
projectDocument: ProjectDocument
|
|
): ProjectScheduleTargetOption {
|
|
const activeScene =
|
|
projectDocument.scenes[projectDocument.activeSceneId] ??
|
|
Object.values(projectDocument.scenes)[0];
|
|
|
|
return {
|
|
key: getControlTargetRefKey(createActiveSceneControlTargetRef()),
|
|
target: createActiveSceneControlTargetRef(),
|
|
label: "Active Scene Lighting",
|
|
subtitle:
|
|
activeScene === undefined
|
|
? "Ambient and sun lighting of the active scene."
|
|
: `Ambient and sun lighting · ${getSceneTargetSubtitle(activeScene)}`,
|
|
groupLabel: "Scene Lighting",
|
|
defaults: {
|
|
ambientLightIntensity: activeScene?.world.ambientLight.intensity ?? 1,
|
|
ambientLightColorHex: activeScene?.world.ambientLight.colorHex ?? "#ffffff",
|
|
sunLightIntensity: activeScene?.world.sunLight.intensity ?? 1,
|
|
sunLightColorHex: activeScene?.world.sunLight.colorHex ?? "#ffffff"
|
|
}
|
|
};
|
|
}
|
|
|
|
function createProjectEventTargetOption(): ProjectScheduleTargetOption {
|
|
return {
|
|
key: getControlTargetRefKey(createProjectGlobalControlTargetRef()),
|
|
target: createProjectGlobalControlTargetRef(),
|
|
label: "Project Events",
|
|
subtitle: "Timeline-triggered sequences and global one-shot events.",
|
|
groupLabel: "Project",
|
|
defaults: {}
|
|
};
|
|
}
|
|
|
|
function createFallbackProjectScheduleTargetOption(
|
|
target: ControlTargetRef
|
|
): ProjectScheduleTargetOption {
|
|
return {
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label: formatControlTargetRef(target),
|
|
subtitle: "Target is missing or no longer available in the current project.",
|
|
groupLabel: "Other",
|
|
defaults: {}
|
|
};
|
|
}
|
|
|
|
export function listProjectScheduleTargetOptions(
|
|
projectDocument: ProjectDocument
|
|
): ProjectScheduleTargetOption[] {
|
|
const options = new Map<string, ProjectScheduleTargetOption>();
|
|
|
|
const pushOption = (option: ProjectScheduleTargetOption) => {
|
|
if (!options.has(option.key)) {
|
|
options.set(option.key, option);
|
|
}
|
|
};
|
|
|
|
pushOption(createProjectEventTargetOption());
|
|
pushOption(createSceneLightingTargetOption(projectDocument));
|
|
|
|
for (const actor of listProjectNpcActors(projectDocument)) {
|
|
const uniqueUsage = actor.usages.length === 1 ? actor.usages[0] ?? null : null;
|
|
const actorScene =
|
|
uniqueUsage === null
|
|
? null
|
|
: projectDocument.scenes[uniqueUsage.sceneId] ?? null;
|
|
const actorEntity =
|
|
uniqueUsage === null || actorScene === null
|
|
? null
|
|
: actorScene.entities[uniqueUsage.entityId];
|
|
const actorModelAsset =
|
|
actorEntity?.kind === "npc" && actorEntity.modelAssetId !== null
|
|
? projectDocument.assets[actorEntity.modelAssetId]
|
|
: undefined;
|
|
const actorAnimationClipNames =
|
|
actorModelAsset !== undefined && actorModelAsset.kind === "model"
|
|
? [...actorModelAsset.metadata.animationNames]
|
|
: [];
|
|
const actorPathOptions =
|
|
actorScene === null
|
|
? []
|
|
: getScenePaths(actorScene.paths)
|
|
.filter((path) => path.enabled)
|
|
.map((path, index) => ({
|
|
pathId: path.id,
|
|
label: getScenePathLabel(path, index),
|
|
loop: path.loop
|
|
}));
|
|
const usageLabel =
|
|
uniqueUsage === null
|
|
? actor.usages.length > 1
|
|
? `${actor.usages.length} usages`
|
|
: actor.actorId
|
|
: `${uniqueUsage.sceneName} · ${actor.actorId}`;
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(createActorControlTargetRef(actor.actorId)),
|
|
target: createActorControlTargetRef(actor.actorId),
|
|
label: actor.label,
|
|
subtitle: usageLabel,
|
|
groupLabel: "Actors",
|
|
defaults: {
|
|
actorAnimationClipNames,
|
|
actorAnimationLoop: true,
|
|
actorPathOptions,
|
|
actorPathSpeed: 1
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const scene of Object.values(projectDocument.scenes)) {
|
|
for (const modelInstance of getModelInstances(scene.modelInstances)) {
|
|
const asset = projectDocument.assets[modelInstance.assetId];
|
|
const target = createModelInstanceControlTargetRef(modelInstance.id);
|
|
const animationClipNames =
|
|
asset !== undefined && asset.kind === "model"
|
|
? [...asset.metadata.animationNames]
|
|
: [];
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label: getModelInstanceDisplayLabel(modelInstance, projectDocument.assets),
|
|
subtitle: getSceneTargetSubtitle(scene),
|
|
groupLabel: "Model Instances",
|
|
defaults: {
|
|
animationClipNames,
|
|
animationLoop: true
|
|
}
|
|
});
|
|
}
|
|
|
|
for (const { entity, label } of getSortedEntityDisplayLabels(
|
|
scene.entities,
|
|
projectDocument.assets
|
|
)) {
|
|
switch (entity.kind) {
|
|
case "soundEmitter": {
|
|
if (entity.audioAssetId === null) {
|
|
break;
|
|
}
|
|
|
|
const target = createSoundEmitterControlTargetRef(entity.id);
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label,
|
|
subtitle: getSceneTargetSubtitle(scene),
|
|
groupLabel: "Sound Emitters",
|
|
defaults: {
|
|
soundVolume: entity.volume
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case "cameraRig": {
|
|
const target = createCameraRigControlTargetRef(entity.id);
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label,
|
|
subtitle: getSceneTargetSubtitle(scene),
|
|
groupLabel: "Camera Rigs",
|
|
defaults: {}
|
|
});
|
|
break;
|
|
}
|
|
case "interactable": {
|
|
const target = createInteractionControlTargetRef("interactable", entity.id);
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label,
|
|
subtitle: getSceneTargetSubtitle(scene),
|
|
groupLabel: "Interactions",
|
|
defaults: {}
|
|
});
|
|
break;
|
|
}
|
|
case "pointLight": {
|
|
const target = createLightControlTargetRef("pointLight", entity.id);
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label,
|
|
subtitle: getSceneTargetSubtitle(scene),
|
|
groupLabel: "Lights",
|
|
defaults: {
|
|
lightIntensity: entity.intensity,
|
|
lightColorHex: entity.colorHex
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
case "spotLight": {
|
|
const target = createLightControlTargetRef("spotLight", entity.id);
|
|
|
|
pushOption({
|
|
key: getControlTargetRefKey(target),
|
|
target,
|
|
label,
|
|
subtitle: getSceneTargetSubtitle(scene),
|
|
groupLabel: "Lights",
|
|
defaults: {
|
|
lightIntensity: entity.intensity,
|
|
lightColorHex: entity.colorHex
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return [...options.values()].sort(compareProjectScheduleTargetOptions);
|
|
}
|
|
|
|
export function getProjectScheduleTargetOptionByKey(
|
|
targetOptions: ProjectScheduleTargetOption[],
|
|
targetKey: string
|
|
): ProjectScheduleTargetOption | null {
|
|
return (
|
|
targetOptions.find((candidate) => candidate.key === targetKey) ?? null
|
|
);
|
|
}
|
|
|
|
export function getProjectScheduleTargetOptionForRoutine(
|
|
targetOptions: ProjectScheduleTargetOption[],
|
|
target: ControlTargetRef
|
|
): ProjectScheduleTargetOption {
|
|
return (
|
|
getProjectScheduleTargetOptionByKey(
|
|
targetOptions,
|
|
getControlTargetRefKey(target)
|
|
) ?? createFallbackProjectScheduleTargetOption(target)
|
|
);
|
|
}
|
|
|
|
export function listProjectScheduleEffectOptions(
|
|
targetOption: ProjectScheduleTargetOption
|
|
): ProjectScheduleEffectOption[] {
|
|
switch (targetOption.target.kind) {
|
|
case "actor":
|
|
return [
|
|
...((targetOption.defaults.actorAnimationClipNames?.length ?? 0) > 0
|
|
? [PROJECT_SCHEDULE_EFFECT_OPTIONS["actor.playAnimation"]]
|
|
: []),
|
|
...((targetOption.defaults.actorPathOptions?.length ?? 0) > 0
|
|
? [PROJECT_SCHEDULE_EFFECT_OPTIONS["actor.followPath"]]
|
|
: [])
|
|
];
|
|
case "modelInstance": {
|
|
const options: ProjectScheduleEffectOption[] = [];
|
|
|
|
if ((targetOption.defaults.animationClipNames?.length ?? 0) > 0) {
|
|
options.push(
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["model.playAnimation"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["model.stopAnimation"]
|
|
);
|
|
}
|
|
|
|
options.push(
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["model.visible"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["model.hidden"]
|
|
);
|
|
return options;
|
|
}
|
|
case "entity":
|
|
if (targetOption.target.entityKind === "cameraRig") {
|
|
return [
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["camera.activate"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["camera.clear"]
|
|
];
|
|
}
|
|
|
|
if (targetOption.target.entityKind === "soundEmitter") {
|
|
return [
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["sound.play"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["sound.stop"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["sound.volume"]
|
|
];
|
|
}
|
|
|
|
return [
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["light.enabled"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["light.disabled"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["light.intensity"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["light.color"]
|
|
];
|
|
case "interaction":
|
|
return [
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["interaction.enabled"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["interaction.disabled"]
|
|
];
|
|
case "scene":
|
|
return [
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["scene.ambientIntensity"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["scene.ambientColor"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["scene.sunIntensity"],
|
|
PROJECT_SCHEDULE_EFFECT_OPTIONS["scene.sunColor"]
|
|
];
|
|
case "global":
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function listProjectInteractionControlEffectOptions(
|
|
targetOption: ProjectScheduleTargetOption
|
|
): ProjectScheduleEffectOption[] {
|
|
return listProjectScheduleEffectOptions(targetOption).filter(
|
|
(option) =>
|
|
!INTERACTION_UNSUPPORTED_PROJECT_SCHEDULE_EFFECT_OPTION_IDS.has(option.id)
|
|
);
|
|
}
|
|
|
|
export function listProjectInteractionControlTargetOptions(
|
|
targetOptions: ProjectScheduleTargetOption[]
|
|
): ProjectScheduleTargetOption[] {
|
|
return targetOptions.filter(
|
|
(targetOption) =>
|
|
listProjectInteractionControlEffectOptions(targetOption).length > 0
|
|
);
|
|
}
|
|
|
|
export function getProjectSequenceControlStepClassForEffectOptionId(
|
|
effectOptionId: ProjectScheduleEffectOptionId
|
|
): "held" | "impulse" {
|
|
switch (effectOptionId) {
|
|
case "camera.activate":
|
|
case "camera.clear":
|
|
return "held";
|
|
case "model.playAnimation":
|
|
case "model.stopAnimation":
|
|
case "sound.play":
|
|
case "sound.stop":
|
|
return "impulse";
|
|
case "actor.present":
|
|
case "actor.hidden":
|
|
case "actor.playAnimation":
|
|
case "actor.followPath":
|
|
case "model.visible":
|
|
case "model.hidden":
|
|
case "sound.volume":
|
|
case "interaction.enabled":
|
|
case "interaction.disabled":
|
|
case "light.enabled":
|
|
case "light.disabled":
|
|
case "light.intensity":
|
|
case "light.color":
|
|
case "scene.ambientIntensity":
|
|
case "scene.ambientColor":
|
|
case "scene.sunIntensity":
|
|
case "scene.sunColor":
|
|
return "held";
|
|
}
|
|
}
|
|
|
|
export function getProjectScheduleEffectOptionId(
|
|
effect: ControlEffect
|
|
): ProjectScheduleEffectOptionId {
|
|
switch (effect.type) {
|
|
case "setProjectTimePaused":
|
|
throw new Error(
|
|
"Project time pause is intentionally not exposed in the schedule editor because a scheduler routine cannot safely pause its own project clock."
|
|
);
|
|
case "activateCameraRigOverride":
|
|
return "camera.activate";
|
|
case "clearCameraRigOverride":
|
|
return "camera.clear";
|
|
case "setActorPresence":
|
|
return effect.active ? "actor.present" : "actor.hidden";
|
|
case "playActorAnimation":
|
|
return "actor.playAnimation";
|
|
case "followActorPath":
|
|
return "actor.followPath";
|
|
case "playModelAnimation":
|
|
return "model.playAnimation";
|
|
case "stopModelAnimation":
|
|
return "model.stopAnimation";
|
|
case "setModelInstanceVisible":
|
|
return effect.visible ? "model.visible" : "model.hidden";
|
|
case "playSound":
|
|
return "sound.play";
|
|
case "stopSound":
|
|
return "sound.stop";
|
|
case "setSoundVolume":
|
|
return "sound.volume";
|
|
case "setInteractionEnabled":
|
|
return effect.enabled ? "interaction.enabled" : "interaction.disabled";
|
|
case "setLightEnabled":
|
|
return effect.enabled ? "light.enabled" : "light.disabled";
|
|
case "setLightIntensity":
|
|
return "light.intensity";
|
|
case "setLightColor":
|
|
return "light.color";
|
|
case "setAmbientLightIntensity":
|
|
return "scene.ambientIntensity";
|
|
case "setAmbientLightColor":
|
|
return "scene.ambientColor";
|
|
case "setSunLightIntensity":
|
|
return "scene.sunIntensity";
|
|
case "setSunLightColor":
|
|
return "scene.sunColor";
|
|
}
|
|
}
|
|
|
|
export function createProjectScheduleEffectFromOption(options: {
|
|
targetOption: ProjectScheduleTargetOption;
|
|
effectOptionId: ProjectScheduleEffectOptionId;
|
|
previousEffect?: ControlEffect | null;
|
|
}): ControlEffect {
|
|
const previousEffect = options.previousEffect ?? null;
|
|
const { targetOption } = options;
|
|
|
|
switch (options.effectOptionId) {
|
|
case "camera.activate":
|
|
return createActivateCameraRigOverrideControlEffect({
|
|
target: createCameraRigControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createCameraRigControlTargetRef>).entityId
|
|
)
|
|
});
|
|
case "camera.clear":
|
|
return createClearCameraRigOverrideControlEffect({
|
|
target: createCameraRigControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createCameraRigControlTargetRef>).entityId
|
|
)
|
|
});
|
|
case "actor.present":
|
|
return createSetActorPresenceControlEffect({
|
|
target: createActorControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createActorControlTargetRef>).actorId
|
|
),
|
|
active: true
|
|
});
|
|
case "actor.hidden":
|
|
return createSetActorPresenceControlEffect({
|
|
target: createActorControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createActorControlTargetRef>).actorId
|
|
),
|
|
active: false
|
|
});
|
|
case "actor.playAnimation":
|
|
return createPlayActorAnimationControlEffect({
|
|
target: createActorControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createActorControlTargetRef>).actorId
|
|
),
|
|
clipName:
|
|
previousEffect?.type === "playActorAnimation" &&
|
|
(targetOption.defaults.actorAnimationClipNames ?? []).includes(
|
|
previousEffect.clipName
|
|
)
|
|
? previousEffect.clipName
|
|
: targetOption.defaults.actorAnimationClipNames?.[0] ?? "Animation",
|
|
loop:
|
|
previousEffect?.type === "playActorAnimation"
|
|
? previousEffect.loop
|
|
: targetOption.defaults.actorAnimationLoop
|
|
});
|
|
case "actor.followPath":
|
|
return createFollowActorPathControlEffect({
|
|
target: createActorControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createActorControlTargetRef>).actorId
|
|
),
|
|
pathId:
|
|
previousEffect?.type === "followActorPath" &&
|
|
(targetOption.defaults.actorPathOptions ?? []).some(
|
|
(pathOption) => pathOption.pathId === previousEffect.pathId
|
|
)
|
|
? previousEffect.pathId
|
|
: targetOption.defaults.actorPathOptions?.[0]?.pathId ?? "path",
|
|
speed:
|
|
previousEffect?.type === "followActorPath"
|
|
? previousEffect.speed
|
|
: targetOption.defaults.actorPathSpeed ?? 1,
|
|
loop:
|
|
previousEffect?.type === "followActorPath"
|
|
? previousEffect.loop
|
|
: targetOption.defaults.actorPathOptions?.[0]?.loop ?? false,
|
|
smoothPath:
|
|
previousEffect?.type === "followActorPath"
|
|
? previousEffect.smoothPath
|
|
: true,
|
|
progressMode: "deriveFromTime"
|
|
});
|
|
case "model.playAnimation":
|
|
return createPlayModelAnimationControlEffect({
|
|
target: createModelInstanceControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createModelInstanceControlTargetRef>).modelInstanceId
|
|
),
|
|
clipName:
|
|
previousEffect?.type === "playModelAnimation" &&
|
|
(targetOption.defaults.animationClipNames ?? []).includes(
|
|
previousEffect.clipName
|
|
)
|
|
? previousEffect.clipName
|
|
: targetOption.defaults.animationClipNames?.[0] ?? "Animation",
|
|
loop:
|
|
previousEffect?.type === "playModelAnimation"
|
|
? previousEffect.loop
|
|
: targetOption.defaults.animationLoop
|
|
});
|
|
case "model.stopAnimation":
|
|
return createStopModelAnimationControlEffect({
|
|
target: createModelInstanceControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createModelInstanceControlTargetRef>).modelInstanceId
|
|
)
|
|
});
|
|
case "model.visible":
|
|
return createSetModelInstanceVisibleControlEffect({
|
|
target: createModelInstanceControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createModelInstanceControlTargetRef>).modelInstanceId
|
|
),
|
|
visible: true
|
|
});
|
|
case "model.hidden":
|
|
return createSetModelInstanceVisibleControlEffect({
|
|
target: createModelInstanceControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createModelInstanceControlTargetRef>).modelInstanceId
|
|
),
|
|
visible: false
|
|
});
|
|
case "sound.play":
|
|
return createPlaySoundControlEffect({
|
|
target: createSoundEmitterControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createSoundEmitterControlTargetRef>).entityId
|
|
)
|
|
});
|
|
case "sound.stop":
|
|
return createStopSoundControlEffect({
|
|
target: createSoundEmitterControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createSoundEmitterControlTargetRef>).entityId
|
|
)
|
|
});
|
|
case "sound.volume":
|
|
return createSetSoundVolumeControlEffect({
|
|
target: createSoundEmitterControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createSoundEmitterControlTargetRef>).entityId
|
|
),
|
|
volume:
|
|
previousEffect?.type === "setSoundVolume"
|
|
? previousEffect.volume
|
|
: targetOption.defaults.soundVolume ?? 1
|
|
});
|
|
case "interaction.enabled":
|
|
return createSetInteractionEnabledControlEffect({
|
|
target: createInteractionControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createInteractionControlTargetRef>).interactionKind,
|
|
(targetOption.target as ReturnType<typeof createInteractionControlTargetRef>).entityId
|
|
),
|
|
enabled: true
|
|
});
|
|
case "interaction.disabled":
|
|
return createSetInteractionEnabledControlEffect({
|
|
target: createInteractionControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createInteractionControlTargetRef>).interactionKind,
|
|
(targetOption.target as ReturnType<typeof createInteractionControlTargetRef>).entityId
|
|
),
|
|
enabled: false
|
|
});
|
|
case "light.enabled":
|
|
return createSetLightEnabledControlEffect({
|
|
target: createLightControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityKind,
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityId
|
|
),
|
|
enabled: true
|
|
});
|
|
case "light.disabled":
|
|
return createSetLightEnabledControlEffect({
|
|
target: createLightControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityKind,
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityId
|
|
),
|
|
enabled: false
|
|
});
|
|
case "light.intensity":
|
|
return createSetLightIntensityControlEffect({
|
|
target: createLightControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityKind,
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityId
|
|
),
|
|
intensity:
|
|
previousEffect?.type === "setLightIntensity"
|
|
? previousEffect.intensity
|
|
: targetOption.defaults.lightIntensity ?? 1
|
|
});
|
|
case "light.color":
|
|
return createSetLightColorControlEffect({
|
|
target: createLightControlTargetRef(
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityKind,
|
|
(targetOption.target as ReturnType<typeof createLightControlTargetRef>).entityId
|
|
),
|
|
colorHex:
|
|
previousEffect?.type === "setLightColor"
|
|
? previousEffect.colorHex
|
|
: targetOption.defaults.lightColorHex ?? "#ffffff"
|
|
});
|
|
case "scene.ambientIntensity":
|
|
return createSetAmbientLightIntensityControlEffect({
|
|
target: createActiveSceneControlTargetRef(),
|
|
intensity:
|
|
previousEffect?.type === "setAmbientLightIntensity"
|
|
? previousEffect.intensity
|
|
: targetOption.defaults.ambientLightIntensity ?? 1
|
|
});
|
|
case "scene.ambientColor":
|
|
return createSetAmbientLightColorControlEffect({
|
|
target: createActiveSceneControlTargetRef(),
|
|
colorHex:
|
|
previousEffect?.type === "setAmbientLightColor"
|
|
? previousEffect.colorHex
|
|
: targetOption.defaults.ambientLightColorHex ?? "#ffffff"
|
|
});
|
|
case "scene.sunIntensity":
|
|
return createSetSunLightIntensityControlEffect({
|
|
target: createActiveSceneControlTargetRef(),
|
|
intensity:
|
|
previousEffect?.type === "setSunLightIntensity"
|
|
? previousEffect.intensity
|
|
: targetOption.defaults.sunLightIntensity ?? 1
|
|
});
|
|
case "scene.sunColor":
|
|
return createSetSunLightColorControlEffect({
|
|
target: createActiveSceneControlTargetRef(),
|
|
colorHex:
|
|
previousEffect?.type === "setSunLightColor"
|
|
? previousEffect.colorHex
|
|
: targetOption.defaults.sunLightColorHex ?? "#ffffff"
|
|
});
|
|
}
|
|
}
|