From e3681f2f325b237de9cb9d7a9f96736e59fc1ecc Mon Sep 17 00:00:00 2001 From: MrKBear Date: Mon, 2 May 2022 16:09:02 +0800 Subject: [PATCH 1/2] Add clip details panel --- source/Context/Status.tsx | 13 ++++ source/Localization/EN-US.ts | 3 + source/Localization/ZH-CN.ts | 3 + source/Model/Model.ts | 8 +++ .../SimulatorDesktop/SimulatorDesktop.tsx | 4 +- source/Page/SimulatorWeb/SimulatorWeb.tsx | 4 +- source/Panel/ClipDetails/ClipDetails.scss | 0 source/Panel/ClipDetails/ClipDetails.tsx | 60 +++++++++++++++++++ source/Panel/ClipPlayer/ClipPlayer.tsx | 7 ++- source/Panel/ClipPlayer/ClipRecorder.tsx | 2 +- source/Panel/Panel.tsx | 6 ++ 11 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 source/Panel/ClipDetails/ClipDetails.scss create mode 100644 source/Panel/ClipDetails/ClipDetails.tsx diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index 868ea03..adf06fb 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -53,6 +53,7 @@ interface IStatusEvent { labelAttrChange: void; groupAttrChange: void; behaviorAttrChange: void; + clipAttrChange: void; individualChange: void; behaviorChange: void; popupChange: void; @@ -286,6 +287,18 @@ class Status extends Emitter { } } + /** + * 修改剪辑属性 + */ + public changeClipAttrib + (id: ObjectID, key: K, val: Clip[K]) { + const clip = this.model.getClipById(id); + if (clip && clip instanceof Clip) { + clip[key] = val; + this.emit("clipAttrChange"); + } + } + public addGroupBehavior(id: ObjectID, val: Behavior) { const group = this.model.getObjectById(id); if (group && group instanceof Group) { diff --git a/source/Localization/EN-US.ts b/source/Localization/EN-US.ts index 2c9b0f6..edd5f87 100644 --- a/source/Localization/EN-US.ts +++ b/source/Localization/EN-US.ts @@ -56,6 +56,8 @@ const EN_US = { "Panel.Info.Behavior.Details.View": "Edit view Behavior attributes", "Panel.Title.Behavior.Clip.Player": "Recording", "Panel.Info.Behavior.Clip.Player": "Pre render recorded data", + "Panel.Title.Behavior.Clip.Details": "Clip", + "Panel.Info.Behavior.Clip.Details": "Edit view clip attributes", "Panel.Info.Behavior.Clip.Time.Formate": "{current} / {all} / {fps}fps", "Panel.Info.Behavior.Clip.Record.Formate": "Record: {time}", "Panel.Info.Behavior.Clip.Uname.Clip": "Waiting for recording...", @@ -161,6 +163,7 @@ const EN_US = { "Panel.Info.Behavior.Details.Parameter.Key.Vec.Y": "{key} Y", "Panel.Info.Behavior.Details.Parameter.Key.Vec.Z": "{key} Z", "Panel.Info.Clip.List.Error.Nodata": "There is no clip, please click the record button to record, or click the plus sign to create", + "Panel.Info.Clip.Details.Error.Nodata": "Specify a clip to view an attribute", "Info.Hint.Save.After.Close": "Any unsaved progress will be lost. Are you sure you want to continue?", "Info.Hint.Load.File.Title": "Load save", "Info.Hint.Load.File.Intro": "Release to load the dragged save file", diff --git a/source/Localization/ZH-CN.ts b/source/Localization/ZH-CN.ts index d01f108..cf32a73 100644 --- a/source/Localization/ZH-CN.ts +++ b/source/Localization/ZH-CN.ts @@ -56,6 +56,8 @@ const ZH_CN = { "Panel.Info.Behavior.Details.View": "编辑查看行为属性", "Panel.Title.Behavior.Clip.Player": "录制", "Panel.Info.Behavior.Clip.Player": "预渲染录制数据", + "Panel.Title.Behavior.Clip.Details": "剪辑", + "Panel.Info.Behavior.Clip.Details": "编辑查看剪辑片段属性", "Panel.Info.Behavior.Clip.Time.Formate": "{current} / {all} / {fps} fps", "Panel.Info.Behavior.Clip.Record.Formate": "录制: {time}", "Panel.Info.Behavior.Clip.Uname.Clip": "等待录制...", @@ -161,6 +163,7 @@ const ZH_CN = { "Panel.Info.Behavior.Details.Parameter.Key.Vec.Y": "{key} Y 坐标", "Panel.Info.Behavior.Details.Parameter.Key.Vec.Z": "{key} Z 坐标", "Panel.Info.Clip.List.Error.Nodata": "没有剪辑片段,请点击录制按钮录制,或者点击加号创建", + "Panel.Info.Clip.Details.Error.Nodata": "请指定一个剪辑片段以查看属性", "Info.Hint.Save.After.Close": "任何未保存的进度都会丢失, 确定要继续吗?", "Info.Hint.Load.File.Title": "加载存档", "Info.Hint.Load.File.Intro": "释放以加载拽入的存档", diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 612939a..b579e50 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -349,6 +349,14 @@ class Model extends Emitter { return newClip; } + public getClipById(id: ObjectID): Clip | undefined { + for (let i = 0; i < this.clipPool.length; i++) { + if (this.clipPool[i].id.toString() === id.toString()) { + return this.clipPool[i]; + } + } + } + /** * 删除一个剪辑片段 */ diff --git a/source/Page/SimulatorDesktop/SimulatorDesktop.tsx b/source/Page/SimulatorDesktop/SimulatorDesktop.tsx index 63b0e8d..06b5639 100644 --- a/source/Page/SimulatorDesktop/SimulatorDesktop.tsx +++ b/source/Page/SimulatorDesktop/SimulatorDesktop.tsx @@ -64,7 +64,7 @@ class SimulatorDesktop extends Component { items: [ {panels: ["RenderView"]}, { - items: [{panels: ["BehaviorList"]}, {panels: ["LabelList"]}], + items: [{panels: ["BehaviorList", "ClipPlayer"]}, {panels: ["LabelList"]}], scale: 80, layout: LayoutDirection.X } @@ -76,7 +76,7 @@ class SimulatorDesktop extends Component { items: [{ panels: ["ObjectList"] }, { - panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails"] + panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails", "ClipDetails"] }], scale: 30, layout: LayoutDirection.Y diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index cfdfd1f..a520ff0 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -55,7 +55,7 @@ class SimulatorWeb extends Component { items: [ {panels: ["RenderView"]}, { - items: [{panels: ["ClipPlayer", "BehaviorList"]}, {panels: ["LabelList"]}], + items: [{panels: ["BehaviorList", "ClipPlayer"]}, {panels: ["LabelList"]}], scale: 80, layout: LayoutDirection.X } @@ -67,7 +67,7 @@ class SimulatorWeb extends Component { items: [{ panels: ["ObjectList"] }, { - panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails"] + panels: ["GroupDetails", "RangeDetails", "LabelDetails", "BehaviorDetails", "ClipDetails"] }], scale: 30, layout: LayoutDirection.Y diff --git a/source/Panel/ClipDetails/ClipDetails.scss b/source/Panel/ClipDetails/ClipDetails.scss new file mode 100644 index 0000000..e69de29 diff --git a/source/Panel/ClipDetails/ClipDetails.tsx b/source/Panel/ClipDetails/ClipDetails.tsx new file mode 100644 index 0000000..9176225 --- /dev/null +++ b/source/Panel/ClipDetails/ClipDetails.tsx @@ -0,0 +1,60 @@ +import { Component, ReactNode } from "react"; +import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; +import { TogglesInput } from "@Input/TogglesInput/TogglesInput"; +import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; +import { AttrInput } from "@Input/AttrInput/AttrInput"; +import { Message } from "@Input/Message/Message"; +import { Clip } from "@Model/Clip"; +import "./ClipDetails.scss"; + +@useStatusWithEvent("focusClipChange", "clipAttrChange") +class ClipDetails extends Component { + + private renderFrom(clip: Clip) { + return <> + + + + { + if (this.props.status) { + this.props.status.changeClipAttrib(clip.id, "name", value); + } + }} + /> + + { + const status = this.props.status; + if (status) { + status.popup.showPopup(ConfirmPopup, { + infoI18n: "Popup.Delete.Clip.Confirm", + titleI18N: "Popup.Action.Objects.Confirm.Title", + yesI18n: "Popup.Action.Objects.Confirm.Delete", + red: "yes", + yes: () => { + status.setClipObject(); + this.props.status?.actuator.endPlay(); + status.model.deleteClip(clip.id); + } + }); + } + }} + /> + + ; + } + + public render(): ReactNode { + if (this.props.status && this.props.status.focusClip) { + return this.renderFrom(this.props.status.focusClip); + } + return ; + } +} + +export { ClipDetails }; \ No newline at end of file diff --git a/source/Panel/ClipPlayer/ClipPlayer.tsx b/source/Panel/ClipPlayer/ClipPlayer.tsx index 9d27929..1fb8009 100644 --- a/source/Panel/ClipPlayer/ClipPlayer.tsx +++ b/source/Panel/ClipPlayer/ClipPlayer.tsx @@ -1,6 +1,7 @@ import { Component, ReactNode } from "react"; import { ClipList } from "@Component/ClipList/ClipList"; import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; +import { useSetting, IMixinSettingProps } from "@Context/Setting"; import { BackgroundLevel, FontLevel, Theme } from "@Component/Theme/Theme"; import { Message } from "@Input/Message/Message"; import { Clip } from "@Model/Clip"; @@ -9,8 +10,9 @@ import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; import { OfflineRender } from "@Component/OfflineRender/OfflineRender" import "./ClipPlayer.scss"; -@useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange") -class ClipPlayer extends Component { +@useSetting +@useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange", "clipAttrChange") +class ClipPlayer extends Component { private isInnerClick: boolean = false; @@ -57,6 +59,7 @@ class ClipPlayer extends Component { this.isInnerClick = true; this.props.status?.setClipObject(clip); this.props.status?.actuator.startPlay(clip); + this.props.setting?.layout.focus("ClipDetails"); }} />; } diff --git a/source/Panel/ClipPlayer/ClipRecorder.tsx b/source/Panel/ClipPlayer/ClipRecorder.tsx index 46fda3f..f29780e 100644 --- a/source/Panel/ClipPlayer/ClipRecorder.tsx +++ b/source/Panel/ClipPlayer/ClipRecorder.tsx @@ -3,7 +3,7 @@ import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; import { Recorder } from "@Component/Recorder/Recorder"; import { ActuatorModel } from "@Model/Actuator"; -@useStatusWithEvent("actuatorStartChange", "focusClipChange", "recordLoop") +@useStatusWithEvent("actuatorStartChange", "focusClipChange", "recordLoop", "clipAttrChange") class ClipRecorder extends Component { public render(): ReactNode { diff --git a/source/Panel/Panel.tsx b/source/Panel/Panel.tsx index ef3542b..b4cc8c1 100644 --- a/source/Panel/Panel.tsx +++ b/source/Panel/Panel.tsx @@ -12,6 +12,7 @@ import { BehaviorList } from "@Panel/BehaviorList/BehaviorList"; import { BehaviorDetails } from "@Panel/BehaviorDetails/BehaviorDetails"; import { ClipPlayer } from "@Panel/ClipPlayer/ClipPlayer"; import { ClipRecorder } from "@Panel/ClipPlayer/ClipRecorder"; +import { ClipDetails } from "@Panel/ClipDetails/ClipDetails"; interface IPanelInfo { nameKey: string; @@ -34,6 +35,7 @@ type PanelId = "" | "BehaviorList" // 行为列表 | "BehaviorDetails" // 行为属性 | "ClipPlayer" // 剪辑影片 +| "ClipDetails" // 剪辑详情 ; const PanelInfoMap = new Map(); @@ -73,6 +75,10 @@ PanelInfoMap.set("ClipPlayer", { nameKey: "Panel.Title.Behavior.Clip.Player", introKay: "Panel.Info.Behavior.Clip.Player", class: ClipPlayer, header: ClipRecorder, hidePadding: true }); +PanelInfoMap.set("ClipDetails", { + nameKey: "Panel.Title.Behavior.Clip.Details", introKay: "Panel.Info.Behavior.Clip.Details", + class: ClipDetails +}); function getPanelById(panelId: PanelId): ReactNode { switch (panelId) { From 2b53ccea2bdd1d21949b7f13f2e9f01fa7a3c287 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Mon, 2 May 2022 23:55:06 +0800 Subject: [PATCH 2/2] Add clip archive function & optmize clip frame structor --- source/Model/Archive.ts | 41 ++++-- source/Model/Clip.ts | 307 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 328 insertions(+), 20 deletions(-) diff --git a/source/Model/Archive.ts b/source/Model/Archive.ts index 92688a1..e9e102e 100644 --- a/source/Model/Archive.ts +++ b/source/Model/Archive.ts @@ -8,6 +8,7 @@ import { IArchiveIndividual, Individual } from "@Model/Individual"; import { Behavior, IArchiveBehavior } from "@Model/Behavior"; import { getBehaviorById } from "@Behavior/Behavior"; import { IArchiveParseFn, IObjectParamArchiveType, IRealObjectType } from "@Model/Parameter"; +import { Clip, IArchiveClip } from "@Model/Clip"; interface IArchiveEvent { fileSave: Archive; @@ -20,6 +21,7 @@ interface IArchiveObject { objectPool: IArchiveCtrlObject[]; labelPool: IArchiveLabel[]; behaviorPool: IArchiveBehavior[]; + clipPool: IArchiveClip[]; } class Archive extends Emitter { @@ -51,7 +53,7 @@ class Archive extends Emitter { // 存贮 CtrlObject const objectPool: IArchiveCtrlObject[] = []; - model.objectPool.forEach(obj => { + model.objectPool?.forEach(obj => { let archiveObject = obj.toArchive(); // 处理每个群的个体 @@ -60,7 +62,7 @@ class Archive extends Emitter { const group: Group = obj as Group; const individuals: IArchiveIndividual[] = []; - group.individuals.forEach((item) => { + group.individuals?.forEach((item) => { individuals.push(item.toArchive()); }); @@ -72,22 +74,29 @@ class Archive extends Emitter { // 存储 Label const labelPool: IArchiveLabel[] = []; - model.labelPool.forEach(obj => { + model.labelPool?.forEach(obj => { labelPool.push(obj.toArchive()); }); // 存储全部行为 const behaviorPool: IArchiveBehavior[] = []; - model.behaviorPool.forEach(obj => { + model.behaviorPool?.forEach(obj => { behaviorPool.push(obj.toArchive()); }); + // 存储全部剪辑片段 + const clipPool: IArchiveClip[] = []; + model.clipPool?.forEach(obj => { + clipPool.push(obj.toArchive()); + }); + // 生成存档对象 const fileData: IArchiveObject = { nextIndividualId: model.nextIndividualId, objectPool: objectPool, labelPool: labelPool, - behaviorPool: behaviorPool + behaviorPool: behaviorPool, + clipPool: clipPool }; return JSON.stringify(fileData); @@ -105,7 +114,7 @@ class Archive extends Emitter { // 实例化全部对象 const objectPool: CtrlObject[] = []; const individualPool: Individual[] = []; - archive.objectPool.forEach((obj) => { + archive.objectPool?.forEach((obj) => { let ctrlObject: CtrlObject | undefined = undefined; @@ -116,7 +125,7 @@ class Archive extends Emitter { // 实例化全部个体 const individuals: Array = []; - archiveGroup.individuals.forEach((item) => { + archiveGroup.individuals?.forEach((item) => { const newIndividual = new Individual(newGroup); newIndividual.id = item.id; individuals.push(newIndividual); @@ -140,7 +149,7 @@ class Archive extends Emitter { // 实例化全部标签 const labelPool: Label[] = []; - archive.labelPool.forEach((item) => { + archive.labelPool?.forEach((item) => { const newLabel = new Label(model); newLabel.id = item.id; labelPool.push(newLabel); @@ -148,13 +157,21 @@ class Archive extends Emitter { // 实例化全部行为 const behaviorPool: Behavior[] = []; - archive.behaviorPool.forEach((item) => { + archive.behaviorPool?.forEach((item) => { const recorder = getBehaviorById(item.behaviorId); const newBehavior = recorder.new(); newBehavior.id = item.id; behaviorPool.push(newBehavior); }); + // 实例化全部剪辑 + const clipPool: Clip[] = []; + archive.clipPool?.forEach((item) => { + const newClip = new Clip(model); + newClip.id = item.id; + clipPool.push(newClip); + }); + // 内置标签集合 const buildInLabel = [model.allGroupLabel, model.allRangeLabel, model.currentGroupLabel] @@ -238,6 +255,12 @@ class Archive extends Emitter { item.fromArchive(archive.behaviorPool[index], parseFunction); return item; }); + + // 加载剪辑 + model.clipPool = clipPool.map((item, index) => { + item.fromArchive(archive.clipPool[index], parseFunction); + return item; + }); } /** diff --git a/source/Model/Clip.ts b/source/Model/Clip.ts index be292d6..f9cf1cc 100644 --- a/source/Model/Clip.ts +++ b/source/Model/Clip.ts @@ -2,6 +2,7 @@ import { IAnyObject, Model } from "@Model/Model"; import { v4 as uuid } from "uuid"; import { Group } from "@Model/Group"; import { Range } from "@Model/Range"; +import { archiveObject2Parameter, IArchiveParseFn, parameter2ArchiveObject } from "@Model/Parameter"; interface IDrawCommand { type: "points" | "cube"; @@ -18,6 +19,13 @@ interface IFrame { process: number; } +interface IArchiveClip { + id: string; + time: number; + name: string; + frames: IFrame[]; +} + /** * 剪辑片段 */ @@ -50,6 +58,127 @@ class Clip { */ public isRecording: boolean = false; + /** + * 判断两个 RenderParameter 是否相同 + */ + public isRenderParameterEqual(p1?: IAnyObject, p2?: IAnyObject, r: boolean = true): boolean { + + if ((p1 && !p2) || (!p1 && p2)) { + return false; + } + + if (!p1 && !p2) { + return true; + } + + for (let key in p1!) { + + // 对象递归校验 + if (typeof p1[key] === "object" && !Array.isArray(p1[key])) { + + if (!(typeof (p2 as any)[key] === "object" && !Array.isArray((p2 as any)[key]))) { + return false; + } + + // 递归校验 + if (r) { + if (!this.isRenderParameterEqual(p1[key], (p2 as any)[key], false)) { + return false; + } + } + + // 浅校验 + else { + if (p1[key] !== (p2 as any)[key]) { + return false; + } + } + } + + // 数组遍历校验 + else if (Array.isArray(p1[key])) { + + if (!Array.isArray((p2 as any)[key])) { + return false; + } + + for (let j = 0; j < p1[key].length; j++) { + if (p1[key][j] !== (p2 as any)[key][j]) { + return false; + } + } + } + + // 数值直接校验 + else if (p1[key] !== (p2 as any)[key]) { + return false; + } + } + + return true; + } + + public cloneRenderParameter(p?: IAnyObject, res: IAnyObject = {}, r: boolean = true): IAnyObject | undefined { + + if (!p) return undefined; + + for (let key in p) { + + // 对象递归克隆 + if (typeof p[key] === "object" && !Array.isArray(p[key]) && r) { + this.cloneRenderParameter(p[key], res, false); + } + + // 数组克隆 + else if (Array.isArray(p[key])) { + (res as any)[key] = p[key].concat([]); + } + + // 数值克隆 + else { + (res as any)[key] = p[key]; + } + } + + return res; + } + + public isArrayEqual(a1?: number[], a2?: number[]): boolean { + + if ((a1 && !a2) || (!a1 && a2)) { + return false; + } + + if (!a1 && !a2) { + return true; + } + + for (let i = 0; i < a1!.length; i++) { + if (a1![i] !== a2![i]) { + return false; + } + } + + return true; + } + + /** + * 从上一帧获取指令数据 + */ + public getCommandFromLastFrame(type: IDrawCommand["type"], id: string, frame?: IFrame): IDrawCommand | undefined { + let lastCommand: IDrawCommand[] = (frame ?? this.frames[this.frames.length - 1])?.commands; + + if (lastCommand) { + for (let i = 0; i < lastCommand.length; i++) { + if (type === lastCommand[i].type && id === lastCommand[i].id) { + return lastCommand[i]; + } + } + } + + return undefined; + } + /** * 录制一帧 */ @@ -62,23 +191,59 @@ class Clip { object.renderParameter.color = object.color; if (object.display && object instanceof Group) { - commands.push({ + + // 获取上一帧指令 + const lastCommand = this.getCommandFromLastFrame("points", object.id); + + // 记录 + const recodeData: IDrawCommand = { type: "points", id: object.id, - data: object.exportPositionData(), - parameter: object.renderParameter - }); + data: object.exportPositionData() + } + + // 对比校验 + if (this.isRenderParameterEqual(object.renderParameter, lastCommand?.parameter)) { + recodeData.parameter = lastCommand?.parameter; + } else { + recodeData.parameter = this.cloneRenderParameter(object.renderParameter); + } + + commands.push(recodeData); } if (object.display && object instanceof Range) { - commands.push({ + + // 获取上一帧指令 + const lastCommand = this.getCommandFromLastFrame("cube", object.id); + + // 记录 + const recodeData: IDrawCommand = { type: "cube", - id: object.id, - position: object.position, - radius: object.radius, - parameter: object.renderParameter - }); + id: object.id + } + + // 释放上一帧的内存 + if (this.isArrayEqual(object.position, lastCommand?.position)) { + recodeData.position = lastCommand?.position; + } else { + recodeData.position = object.position.concat([]); + } + + if (this.isArrayEqual(object.radius, lastCommand?.radius)) { + recodeData.radius = lastCommand?.radius; + } else { + recodeData.radius = object.radius.concat([]); + } + + if (this.isRenderParameterEqual(object.renderParameter, lastCommand?.parameter)) { + recodeData.parameter = lastCommand?.parameter; + } else { + recodeData.parameter = this.cloneRenderParameter(object.renderParameter); + } + + commands.push(recodeData); } } @@ -95,6 +260,110 @@ class Clip { return frame; } + public readonly LastFrameData: "@L" = "@L"; + + /** + * 压缩帧数据 + */ + public compressed(): IFrame[] { + const resFrame: IFrame[] = []; + + for (let i = 0; i < this.frames.length; i++) { + const commands = this.frames[i].commands; + const res: IDrawCommand[] = []; + + // 处理指令 + for (let j = 0; j < commands.length; j++) { + + // 压缩指令 + const command: IDrawCommand = { + id: commands[j].id, + type: commands[j].type + }; + + // 搜索上一帧相同指令 + const lastCommand = this.frames[i - 1] ? + this.getCommandFromLastFrame(command.type, command.id, this.frames[i - 1]) : + undefined; + + // 记录 + command.data = (lastCommand?.data === commands[j].data) ? + this.LastFrameData as any : Array.from(commands[j].data ?? []); + + command.position = (lastCommand?.position === commands[j].position) ? + this.LastFrameData as any : commands[j].position?.concat([]); + + command.radius = (lastCommand?.radius === commands[j].radius) ? + this.LastFrameData as any : commands[j].radius?.concat([]); + + command.parameter = (lastCommand?.parameter === commands[j].parameter) ? + this.LastFrameData as any : parameter2ArchiveObject(commands[j].parameter as any); + + res.push(command); + } + + resFrame.push({ + duration: this.frames[i].duration, + process: this.frames[i].process, + commands: res + }) + } + + return resFrame; + }; + + /** + * 加载压缩帧数据 + */ + public uncompressed(frames: IFrame[], paster: IArchiveParseFn): IFrame[] { + const resFrame: IFrame[] = []; + + for (let i = 0; i < frames.length; i++) { + const commands = frames[i].commands; + const res: IDrawCommand[] = []; + + // 处理指令 + for (let j = 0; j < commands.length; j++) { + + // 压缩指令 + const command: IDrawCommand = { + id: commands[j].id, + type: commands[j].type + }; + + // 搜索上一帧相同指令 + const lastCommand = resFrame[resFrame.length - 1] ? + this.getCommandFromLastFrame(command.type, command.id, resFrame[resFrame.length - 1]) : + undefined; + + console.log(lastCommand); + + // 记录 + command.data = (this.LastFrameData as any === commands[j].data) ? + lastCommand?.data : new Float32Array(commands[j].data ?? []); + + command.position = (this.LastFrameData as any === commands[j].position) ? + lastCommand?.position : commands[j].position; + + command.radius = (this.LastFrameData as any === commands[j].radius) ? + lastCommand?.radius : commands[j].radius; + + command.parameter = (this.LastFrameData as any === commands[j].parameter) ? + lastCommand?.parameter : archiveObject2Parameter(commands[j].parameter as any, paster); + + res.push(command); + } + + resFrame.push({ + duration: frames[i].duration, + process: frames[i].process, + commands: res + }) + } + + return resFrame; + } + /** * 播放一帧 */ @@ -125,6 +394,22 @@ class Clip { this.model = model; this.id = uuid(); } + + public toArchive(): IArchiveClip { + return { + id: this.id, + time: this.time, + name: this.name, + frames: this.compressed() + }; + } + + public fromArchive(archive: IArchiveClip, paster: IArchiveParseFn): void { + this.id = archive.id, + this.time = archive.time, + this.name = archive.name, + this.frames = this.uncompressed(archive.frames, paster); + } } -export { Clip, IFrame }; \ No newline at end of file +export { Clip, IFrame, IArchiveClip }; \ No newline at end of file