Add clip archive function & optmize clip frame structor & clip details panel #50

Merged
MrKBear merged 3 commits from dev-mrkbear into master 2022-05-02 23:57:00 +08:00
2 changed files with 328 additions and 20 deletions
Showing only changes of commit 2b53ccea2b - Show all commits

View File

@ -8,6 +8,7 @@ import { IArchiveIndividual, Individual } from "@Model/Individual";
import { Behavior, IArchiveBehavior } from "@Model/Behavior"; import { Behavior, IArchiveBehavior } from "@Model/Behavior";
import { getBehaviorById } from "@Behavior/Behavior"; import { getBehaviorById } from "@Behavior/Behavior";
import { IArchiveParseFn, IObjectParamArchiveType, IRealObjectType } from "@Model/Parameter"; import { IArchiveParseFn, IObjectParamArchiveType, IRealObjectType } from "@Model/Parameter";
import { Clip, IArchiveClip } from "@Model/Clip";
interface IArchiveEvent { interface IArchiveEvent {
fileSave: Archive; fileSave: Archive;
@ -20,6 +21,7 @@ interface IArchiveObject {
objectPool: IArchiveCtrlObject[]; objectPool: IArchiveCtrlObject[];
labelPool: IArchiveLabel[]; labelPool: IArchiveLabel[];
behaviorPool: IArchiveBehavior[]; behaviorPool: IArchiveBehavior[];
clipPool: IArchiveClip[];
} }
class Archive extends Emitter<IArchiveEvent> { class Archive extends Emitter<IArchiveEvent> {
@ -51,7 +53,7 @@ class Archive extends Emitter<IArchiveEvent> {
// 存贮 CtrlObject // 存贮 CtrlObject
const objectPool: IArchiveCtrlObject[] = []; const objectPool: IArchiveCtrlObject[] = [];
model.objectPool.forEach(obj => { model.objectPool?.forEach(obj => {
let archiveObject = obj.toArchive(); let archiveObject = obj.toArchive();
// 处理每个群的个体 // 处理每个群的个体
@ -60,7 +62,7 @@ class Archive extends Emitter<IArchiveEvent> {
const group: Group = obj as Group; const group: Group = obj as Group;
const individuals: IArchiveIndividual[] = []; const individuals: IArchiveIndividual[] = [];
group.individuals.forEach((item) => { group.individuals?.forEach((item) => {
individuals.push(item.toArchive()); individuals.push(item.toArchive());
}); });
@ -72,22 +74,29 @@ class Archive extends Emitter<IArchiveEvent> {
// 存储 Label // 存储 Label
const labelPool: IArchiveLabel[] = []; const labelPool: IArchiveLabel[] = [];
model.labelPool.forEach(obj => { model.labelPool?.forEach(obj => {
labelPool.push(obj.toArchive()); labelPool.push(obj.toArchive());
}); });
// 存储全部行为 // 存储全部行为
const behaviorPool: IArchiveBehavior[] = []; const behaviorPool: IArchiveBehavior[] = [];
model.behaviorPool.forEach(obj => { model.behaviorPool?.forEach(obj => {
behaviorPool.push(obj.toArchive()); behaviorPool.push(obj.toArchive());
}); });
// 存储全部剪辑片段
const clipPool: IArchiveClip[] = [];
model.clipPool?.forEach(obj => {
clipPool.push(obj.toArchive());
});
// 生成存档对象 // 生成存档对象
const fileData: IArchiveObject = { const fileData: IArchiveObject = {
nextIndividualId: model.nextIndividualId, nextIndividualId: model.nextIndividualId,
objectPool: objectPool, objectPool: objectPool,
labelPool: labelPool, labelPool: labelPool,
behaviorPool: behaviorPool behaviorPool: behaviorPool,
clipPool: clipPool
}; };
return JSON.stringify(fileData); return JSON.stringify(fileData);
@ -105,7 +114,7 @@ class Archive extends Emitter<IArchiveEvent> {
// 实例化全部对象 // 实例化全部对象
const objectPool: CtrlObject[] = []; const objectPool: CtrlObject[] = [];
const individualPool: Individual[] = []; const individualPool: Individual[] = [];
archive.objectPool.forEach((obj) => { archive.objectPool?.forEach((obj) => {
let ctrlObject: CtrlObject | undefined = undefined; let ctrlObject: CtrlObject | undefined = undefined;
@ -116,7 +125,7 @@ class Archive extends Emitter<IArchiveEvent> {
// 实例化全部个体 // 实例化全部个体
const individuals: Array<Individual> = []; const individuals: Array<Individual> = [];
archiveGroup.individuals.forEach((item) => { archiveGroup.individuals?.forEach((item) => {
const newIndividual = new Individual(newGroup); const newIndividual = new Individual(newGroup);
newIndividual.id = item.id; newIndividual.id = item.id;
individuals.push(newIndividual); individuals.push(newIndividual);
@ -140,7 +149,7 @@ class Archive extends Emitter<IArchiveEvent> {
// 实例化全部标签 // 实例化全部标签
const labelPool: Label[] = []; const labelPool: Label[] = [];
archive.labelPool.forEach((item) => { archive.labelPool?.forEach((item) => {
const newLabel = new Label(model); const newLabel = new Label(model);
newLabel.id = item.id; newLabel.id = item.id;
labelPool.push(newLabel); labelPool.push(newLabel);
@ -148,13 +157,21 @@ class Archive extends Emitter<IArchiveEvent> {
// 实例化全部行为 // 实例化全部行为
const behaviorPool: Behavior[] = []; const behaviorPool: Behavior[] = [];
archive.behaviorPool.forEach((item) => { archive.behaviorPool?.forEach((item) => {
const recorder = getBehaviorById(item.behaviorId); const recorder = getBehaviorById(item.behaviorId);
const newBehavior = recorder.new(); const newBehavior = recorder.new();
newBehavior.id = item.id; newBehavior.id = item.id;
behaviorPool.push(newBehavior); 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] const buildInLabel = [model.allGroupLabel, model.allRangeLabel, model.currentGroupLabel]
@ -238,6 +255,12 @@ class Archive extends Emitter<IArchiveEvent> {
item.fromArchive(archive.behaviorPool[index], parseFunction); item.fromArchive(archive.behaviorPool[index], parseFunction);
return item; return item;
}); });
// 加载剪辑
model.clipPool = clipPool.map((item, index) => {
item.fromArchive(archive.clipPool[index], parseFunction);
return item;
});
} }
/** /**

View File

@ -2,6 +2,7 @@ import { IAnyObject, Model } from "@Model/Model";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import { Group } from "@Model/Group"; import { Group } from "@Model/Group";
import { Range } from "@Model/Range"; import { Range } from "@Model/Range";
import { archiveObject2Parameter, IArchiveParseFn, parameter2ArchiveObject } from "@Model/Parameter";
interface IDrawCommand { interface IDrawCommand {
type: "points" | "cube"; type: "points" | "cube";
@ -18,6 +19,13 @@ interface IFrame {
process: number; process: number;
} }
interface IArchiveClip {
id: string;
time: number;
name: string;
frames: IFrame[];
}
/** /**
* *
*/ */
@ -50,6 +58,127 @@ class Clip {
*/ */
public isRecording: boolean = false; 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; object.renderParameter.color = object.color;
if (object.display && object instanceof Group) { if (object.display && object instanceof Group) {
commands.push({
// 获取上一帧指令
const lastCommand = this.getCommandFromLastFrame("points", object.id);
// 记录
const recodeData: IDrawCommand = {
type: "points", type: "points",
id: object.id, id: object.id,
data: object.exportPositionData(), data: object.exportPositionData()
parameter: object.renderParameter }
});
// 对比校验
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) { if (object.display && object instanceof Range) {
commands.push({
// 获取上一帧指令
const lastCommand = this.getCommandFromLastFrame("cube", object.id);
// 记录
const recodeData: IDrawCommand = {
type: "cube", type: "cube",
id: object.id, id: object.id
position: object.position, }
radius: object.radius,
parameter: object.renderParameter // 释放上一帧的内存
}); 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; 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.model = model;
this.id = uuid(); this.id = uuid();
} }
public toArchive(): IArchiveClip {
return {
id: this.id,
time: this.time,
name: this.name,
frames: this.compressed()
};
} }
export { Clip, IFrame }; 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, IArchiveClip };