/** * 3D Foundation Project * Copyright 2024 Smithsonian Institution * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Document from "@ff/core/Document"; import CVTask, { types } from "./CVTask"; import ActionsTaskView from "../ui/story/ActionsTaskView"; import { Node } from "@ff/graph/Component"; import CVDocument from "./CVDocument"; import CVActionManager from "./CVActionManager"; import { EActionTrigger, EActionType, EActionPlayStyle, TActionTrigger, TActionType, TActionPlayStyle } from "client/schema/meta"; import CVMeta from "./CVMeta"; import NVNode from "client/nodes/NVNode"; import CVModel2 from "./CVModel2"; import CVAnnotationView from "./CVAnnotationView"; import { ELanguageType } from "client/schema/common"; import CVTours from "./CVTours"; //////////////////////////////////////////////////////////////////////////////// export default class CVActionsTask extends CVTask { static readonly typeName: string = "CVActionsTask"; static readonly text: string = "Action"; static readonly icon: string = "video"; private _defaultCount: number = 0; private _actionIds: string[] = [] protected static readonly ins = { create: types.Event("Action.Create"), delete: types.Event("Action.Delete"), activeId: types.String("Action.ActiveId", ""), name: types.String("Action.Name", ""), type: types.Enum("Action.Type", EActionType, EActionType.PlayAnimation), trigger: types.Enum("Action.Trigger", EActionTrigger, EActionTrigger.OnClick), style: types.Enum("Action.Style", EActionPlayStyle, EActionPlayStyle.Single), speed: types.Number("Action.Speed", {preset: 1}), audio: types.Option("Action.Audio", ["None"], 0), animation: types.Option("Action.Animation", ["None"], 0), annotation: types.Option("Action.AnnotationT", ["None"], 0), actionAnnotation: types.Option("Action.Annotation", ["None"], 0), tour: types.Option("Action.Tour", ["None"], 0), tourStep: types.Option("Action.TourStep", ["None"], 0), syncWith: types.Option("Action.SyncWith", ["None"], 0), action: types.Option("Action.Action", ["None"], 0), clamp: types.Boolean("Action.ClampOnEnd", false), }; protected static readonly outs = { }; ins = this.addInputs(CVActionsTask.ins); outs = this.addOutputs(CVActionsTask.outs); actionManager: CVActionManager = null; meta: CVMeta = null; get actions() { return this.meta && this.meta.actions.items; } protected get tours() { return this.getSystemComponent(CVTours); } constructor(node: Node, id: string) { super(node, id); } create() { super.create(); this.startObserving(); } dispose() { this.stopObserving(); super.dispose(); } createView() { return new ActionsTaskView(this); } activateTask() { super.activateTask(); this.meta ? this.synchAnnotationOptions(this.meta.getComponent(CVModel2)) : null; this.synchTourOptions(); this.synchActionOptions(); } deactivateTask() { super.deactivateTask(); } update() { const { ins } = this; const meta = this.meta; if(meta) { if(ins.activeId.changed) { this.onActionChange(); return true; } if (ins.create.changed) { const action = { id: Document.generateId(), name: "Action" + this._defaultCount++, type: EActionType[EActionType.PlayAnimation] as TActionType, trigger: EActionTrigger[EActionTrigger.OnClick] as TActionTrigger, style: EActionPlayStyle[EActionPlayStyle.Single] as TActionPlayStyle, speed: 1.0, audioId: "", animation: "" }; meta.actions.items = [action]; ins.activeId.setValue(action.id); this.onActionChange(); this.synchActionOptions(); this.actionManager.refreshActions(); return true; } if (ins.delete.changed) { meta.actions.remove(ins.activeId.value); ins.activeId.setValue(""); this.synchActionOptions(); this.actionManager.refreshActions(); return true; } const action = meta.actions.get(ins.activeId.value); if (action && (ins.type.changed || ins.trigger.changed || ins.animation.changed || ins.style.changed || ins.speed.changed || ins.clamp.changed)) { action.type = EActionType[ins.type.value] as TActionType; action.trigger = EActionTrigger[ins.trigger.value] as TActionTrigger; action.style = EActionPlayStyle[ins.style.value] as TActionPlayStyle; action.speed = ins.speed.value; action.clamp = ins.clamp.value; action.animation = EActionType[action.type] == EActionType.PlayAnimation ? ins.animation.getOptionText() : ""; action.audioId = EActionType[action.type] == EActionType.PlayAudio ? action.audioId : ""; } if(ins.audio.changed) { const audioManager = this.activeDocument.setup.audio; const id = ins.audio.value > 0 ? audioManager.getAudioList()[ins.audio.value - 1].id : ""; action.audioId = id; } if(ins.syncWith.changed) { action.syncWith = ins.syncWith.value > 0 ? ins.syncWith.getOptionText() : ""; } if(ins.annotation.changed) { const id = ins.annotation.value > 0 ? meta.getComponent(CVAnnotationView).getAnnotations()[ins.annotation.value - 1].id : undefined; action.annotationId = id; } if(ins.actionAnnotation.changed) { const id = ins.actionAnnotation.value > 0 ? meta.getComponent(CVAnnotationView).getAnnotations()[ins.actionAnnotation.value - 1].id : undefined; action.actionAnnoId = id; } if(ins.name.changed) { action.name = ins.name.value; this.synchActionOptions(); } if(ins.tour.changed || ins.tourStep.changed) { if(ins.tour.changed) { this.synchTourStepOptions(); } action.triggerDetail = this.ins.tour.value > 0 ? this.tours.tours[this.ins.tour.value-1].steps[this.ins.tourStep.value].id : ""; } if(ins.action.changed) { action.triggerDetail = ins.action.value > 0 ? this._actionIds[ins.action.value] : undefined; } // handle action types UI update if(ins.type.changed) { if(EActionType[action.type] == EActionType.PlayAnimation) { ins.audio.setValue(null , true, true); } else { ins.animation.setValue(null , true, true); } } if(action) { this.actionManager.stopAction(action); } } return true; } protected onActionChange() { const ins = this.ins; const action = this.meta && this.meta.actions.get(ins.activeId.value); const audioManager = this.activeDocument.setup.audio; if(action) { ins.name.setValue(action.name); ins.type.setValue(EActionType[action.type], true); ins.trigger.setValue(EActionTrigger[action.trigger], true); ins.animation.setValue(action.animation ? ins.animation.schema.options.indexOf(action.animation) : 0); ins.audio.setValue(action.audioId ? audioManager.getAudioList().findIndex(clip => clip.id == action.audioId) + 1 : 0); ins.annotation.setValue(action.annotationId ? this.meta.getComponent(CVAnnotationView).getAnnotations().findIndex(anno => anno.id == action.annotationId) + 1 : null); ins.actionAnnotation.setValue(action.actionAnnoId ? this.meta.getComponent(CVAnnotationView).getAnnotations().findIndex(anno => anno.id == action.actionAnnoId) + 1 : null); ins.style.setValue(action.style ? EActionPlayStyle[action.style] : EActionPlayStyle.Single); ins.speed.setValue(action.speed); ins.clamp.setValue(action.clamp); ins.syncWith.setValue(action.syncWith ? ins.syncWith.schema.options.indexOf(action.syncWith) : 0); ins.action.setValue(ins.trigger.value === EActionTrigger.OnActionEnd || ins.trigger.value === EActionTrigger.OnActionBegin ? this._actionIds.indexOf(action.triggerDetail) : 0); const isTour = ins.trigger.value === EActionTrigger.OnTourStep; if(action.triggerDetail?.includes("\x1F")) { // Support for updating deprecated v0.63 tour step triggers const detail = action.triggerDetail ? action.triggerDetail.split("\x1F") : []; ins.tour.setValue(isTour ? ins.tour.schema.options.indexOf(detail[0]) : 0); this.synchTourStepOptions(); ins.tourStep.setValue(isTour ? ins.tourStep.schema.options.indexOf(detail[1]) : 0); action.triggerDetail = this.ins.tour.value > 0 ? this.tours.tours[this.ins.tour.value-1].steps[this.ins.tourStep.value].id : ""; } else { const tour = this.tours.tours.find((entry) => entry.steps.some(step => step.id === action.triggerDetail)); ins.tour.setValue(isTour ? ins.tour.schema.options.indexOf(tour.titles[ELanguageType[this.activeDocument.setup.language.outs.uiLanguage.value]] || tour.title) : 0); this.synchTourStepOptions(); ins.tourStep.setValue(isTour ? tour.steps.findIndex(step => step.id === action.triggerDetail) : 0); } } } protected onActiveNode(previous: NVNode, next: NVNode) { const model = next ? next.getComponent(CVModel2, true) : null; if (previous) { this.meta = null; } if (model) { if (!next.meta) { next.createComponent(CVMeta); } // Load animation options const animOptions = ["None"]; model.object3D.traverse(object => { if (object.animations.length > 0) { object.animations.forEach((anim) => { animOptions.push(anim.name); }) } }); this.ins.animation.setOptions(animOptions); this.ins.syncWith.setOptions(animOptions); this.synchAnnotationOptions(model); this.ins.activeId.setValue(""); this.meta = next.meta; } } protected onActiveDocument(previous: CVDocument, next: CVDocument) { super.onActiveDocument(previous, next); if (previous) { previous.setup.audio.outs.updated.off("value", this.synchAudioOptions, this); this.actionManager = null; } if (next) { this.actionManager = next.setup.actions; next.setup.audio.outs.updated.on("value", this.synchAudioOptions, this); } } // Update audio options protected synchAudioOptions() { const audioManager = this.activeDocument.setup.audio; const options = ["None"]; options.push(...audioManager.getAudioList().map(clip => clip.name)); this.ins.audio.setOptions(options); } // Update annotation options protected synchAnnotationOptions(model: CVModel2) { const annoOptions = ["None"]; model.getComponent(CVAnnotationView).getAnnotations().forEach((anno) => { annoOptions.push(anno.title); }); this.ins.annotation.setOptions(annoOptions); this.ins.actionAnnotation.setOptions(annoOptions); } // Update tour options protected synchTourOptions() { const tours = this.activeDocument.setup.tours; const tourOptions = ["None"]; tourOptions.push(...tours.tours.map(tour => tour.titles[ELanguageType[this.activeDocument.setup.language.outs.uiLanguage.value]] || tour.title)); this.ins.tour.setOptions(tourOptions); } // Update tour step options protected synchTourStepOptions() { const tours = this.activeDocument.setup.tours.tours; const tour = tours.find((tour) => (tour.titles[ELanguageType[this.activeDocument.setup.language.outs.uiLanguage.value]] || tour.title) === this.ins.tour.getOptionText() ); const stepOptions = ["None"]; if(tour) { stepOptions.splice(0, 1, ...[...Array(tour.steps.length).keys()].map(x => (x + 1).toString())); } this.ins.tourStep.setOptions(stepOptions); } // Update action options protected synchActionOptions() { const actionOptions = ["None"]; this._actionIds.length = 0; this._actionIds.push("0"); this.getSystemComponents(CVMeta).forEach((meta) => { actionOptions.push(...meta.actions.items.map(action => action.name)); this._actionIds.push(...meta.actions.items.map(action => action.id)); }); this.ins.action.setOptions(actionOptions); } }