456 lines
8.3 KiB
TypeScript
456 lines
8.3 KiB
TypeScript
import { Model } from "@Model/Model";
|
|
import { Emitter } from "@Model/Emitter";
|
|
import { Clip, IFrame } from "@Model/Clip";
|
|
|
|
enum ActuatorModel {
|
|
Play = 1,
|
|
Record = 2,
|
|
View = 3,
|
|
Offline = 4
|
|
}
|
|
|
|
interface IActuatorEvent {
|
|
startChange: boolean;
|
|
record: number;
|
|
loop: number;
|
|
offline: number;
|
|
modelUpdate: void;
|
|
}
|
|
|
|
/**
|
|
* 模型执行器
|
|
*/
|
|
class Actuator extends Emitter<IActuatorEvent> {
|
|
|
|
/**
|
|
* 速度系数
|
|
*/
|
|
public speed: number = 1;
|
|
|
|
/**
|
|
* 模拟帧率
|
|
*/
|
|
public fps: number = 36;
|
|
|
|
/**
|
|
* 仿真是否进行
|
|
*/
|
|
private startFlag: boolean = false;
|
|
|
|
/**
|
|
* 模式
|
|
*/
|
|
public mod: ActuatorModel = ActuatorModel.View;
|
|
|
|
/**
|
|
* 录制剪辑
|
|
*/
|
|
public recordClip?: Clip;
|
|
|
|
/**
|
|
* 播放剪辑
|
|
*/
|
|
public playClip?: Clip;
|
|
|
|
/**
|
|
* 播放帧
|
|
*/
|
|
public playFrame?: IFrame;
|
|
|
|
/**
|
|
* 播放帧数
|
|
*/
|
|
public playFrameId: number = 0;
|
|
|
|
/**
|
|
* 开始录制
|
|
*/
|
|
public startRecord(clip: Clip) {
|
|
|
|
// 记录录制片段
|
|
this.recordClip = clip;
|
|
clip.isRecording = true;
|
|
|
|
// 如果仿真未开启,开启仿真
|
|
if (!this.start()) this.start(true);
|
|
|
|
// 设置状态
|
|
this.mod = ActuatorModel.Record;
|
|
}
|
|
|
|
/**
|
|
* 结束录制
|
|
*/
|
|
public endRecord() {
|
|
|
|
this.recordClip && (this.recordClip.isRecording = false);
|
|
this.recordClip = undefined;
|
|
|
|
// 如果仿真未停止,停止仿真
|
|
if (this.start()) this.start(false);
|
|
|
|
// 设置状态
|
|
this.mod = ActuatorModel.View;
|
|
}
|
|
|
|
public startPlay(clip: Clip) {
|
|
|
|
// 如果仿真正在进行,停止仿真
|
|
if (this.start()) this.start(false);
|
|
|
|
// 如果正在录制,阻止播放
|
|
if (this.mod === ActuatorModel.Record) {
|
|
return;
|
|
}
|
|
|
|
// 如果正在播放,暂停播放
|
|
if (this.mod === ActuatorModel.Play) {
|
|
this.pausePlay();
|
|
}
|
|
|
|
// 设置播放对象
|
|
this.playClip = clip;
|
|
|
|
// 设置播放帧数
|
|
this.playFrameId = 0;
|
|
this.playFrame = clip.frames[this.playFrameId];
|
|
|
|
// 播放第一帧
|
|
clip.play(this.playFrame);
|
|
|
|
// 激发时钟状态事件
|
|
this.emit("startChange", true);
|
|
}
|
|
|
|
public endPlay() {
|
|
|
|
// 如果正在播放,暂停播放
|
|
if (this.mod === ActuatorModel.Play) {
|
|
this.pausePlay();
|
|
}
|
|
|
|
// 更新模式
|
|
this.mod = ActuatorModel.View;
|
|
|
|
// 清除状态
|
|
this.playClip = undefined;
|
|
this.playFrameId = 0;
|
|
this.playFrame = undefined;
|
|
|
|
// 渲染模型
|
|
this.model.draw();
|
|
|
|
// 激发时钟状态事件
|
|
this.emit("startChange", false);
|
|
}
|
|
|
|
/**
|
|
* 是否播放完毕
|
|
*/
|
|
public isPlayEnd() {
|
|
if (this.playClip && this.playFrame) {
|
|
if (this.playFrameId >= (this.playClip.frames.length - 1)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public playing() {
|
|
|
|
// 如果播放完毕了,从头开始播放
|
|
if (this.isPlayEnd() && this.playClip) {
|
|
this.startPlay(this.playClip);
|
|
}
|
|
|
|
// 更新模式
|
|
this.mod = ActuatorModel.Play;
|
|
|
|
// 启动播放时钟
|
|
this.playTicker();
|
|
|
|
// 激发时钟状态事件
|
|
this.emit("startChange", false);
|
|
}
|
|
|
|
public pausePlay() {
|
|
|
|
// 更新模式
|
|
this.mod = ActuatorModel.View;
|
|
|
|
// 激发时钟状态事件
|
|
this.emit("startChange", false);
|
|
}
|
|
|
|
/**
|
|
* 主时钟状态控制
|
|
*/
|
|
public start(start?: boolean): boolean {
|
|
if (start === undefined) {
|
|
return this.startFlag;
|
|
} else {
|
|
this.startFlag = start;
|
|
this.lastTime = 0;
|
|
this.alignTimer = 0;
|
|
this.emit("startChange", start);
|
|
return start;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 绑定模型
|
|
*/
|
|
public model: Model;
|
|
|
|
/**
|
|
* 上一帧的时间
|
|
*/
|
|
private lastTime: number = 0;
|
|
|
|
/**
|
|
* 对其计时器
|
|
*/
|
|
private alignTimer: number = 0;
|
|
|
|
public tickerType: 1 | 2 = 2;
|
|
|
|
private playTickerTimer?: number;
|
|
|
|
/**
|
|
* 设置播放进度
|
|
*/
|
|
public setPlayProcess(id: number) {
|
|
if (this.playClip && id >= 0 && id < this.playClip.frames.length) {
|
|
|
|
// 跳转值这帧
|
|
this.playFrameId = id;
|
|
this.playFrame = this.playClip.frames[this.playFrameId];
|
|
this.emit("record", this.playFrame.duration);
|
|
|
|
if (this.mod !== ActuatorModel.Play) {
|
|
this.playClip.play(this.playFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 离线渲染参数
|
|
*/
|
|
public offlineAllFrame: number = 0;
|
|
public offlineCurrentFrame: number = 0;
|
|
private offlineRenderTickTimer?: number;
|
|
|
|
/**
|
|
* 关闭离线渲染
|
|
*/
|
|
public endOfflineRender() {
|
|
|
|
// 清除 timer
|
|
clearTimeout(this.offlineRenderTickTimer);
|
|
|
|
this.recordClip && (this.recordClip.isRecording = false);
|
|
this.recordClip = undefined;
|
|
|
|
// 设置状态
|
|
this.mod = ActuatorModel.View;
|
|
|
|
// 激发结束事件
|
|
this.start(false);
|
|
this.emit("record", 0);
|
|
}
|
|
|
|
/**
|
|
* 离线渲染 tick
|
|
*/
|
|
private offlineRenderTick(dt: number) {
|
|
|
|
if (this.mod !== ActuatorModel.Offline) {
|
|
return;
|
|
}
|
|
|
|
if (this.offlineCurrentFrame >= this.offlineAllFrame) {
|
|
return this.endOfflineRender();
|
|
}
|
|
|
|
// 更新模型
|
|
this.model.update(dt);
|
|
|
|
// 录制
|
|
this.recordClip?.record(dt);
|
|
|
|
// 限制更新频率
|
|
if (this.offlineCurrentFrame % 10 === 0) {
|
|
this.emit("offline", dt);
|
|
}
|
|
|
|
this.offlineCurrentFrame++
|
|
|
|
if (this.offlineCurrentFrame <= this.offlineAllFrame) {
|
|
|
|
// 下一个 tick
|
|
this.offlineRenderTickTimer = setTimeout(() => this.offlineRenderTick(dt)) as any;
|
|
|
|
} else {
|
|
this.endOfflineRender();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 离线渲染
|
|
*/
|
|
public offlineRender(clip: Clip, time: number, fps: number) {
|
|
|
|
// 记录录制片段
|
|
this.recordClip = clip;
|
|
clip.isRecording = true;
|
|
|
|
// 如果仿真正在进行,停止仿真
|
|
if (this.start()) this.start(false);
|
|
|
|
// 如果正在录制,阻止
|
|
if (this.mod === ActuatorModel.Record || this.mod === ActuatorModel.Offline) {
|
|
return;
|
|
}
|
|
|
|
// 如果正在播放,暂停播放
|
|
if (this.mod === ActuatorModel.Play) {
|
|
this.pausePlay();
|
|
}
|
|
|
|
// 设置状态
|
|
this.mod = ActuatorModel.Offline;
|
|
|
|
// 计算帧数
|
|
this.offlineCurrentFrame = 0;
|
|
this.offlineAllFrame = Math.round(time * fps) - 1;
|
|
let dt = time / this.offlineAllFrame;
|
|
|
|
// 第一帧渲染
|
|
clip.record(0);
|
|
|
|
// 开启时钟
|
|
this.offlineRenderTick(dt);
|
|
|
|
this.emit("record", dt);
|
|
}
|
|
|
|
/**
|
|
* 播放时钟
|
|
*/
|
|
private playTicker() {
|
|
|
|
if (this.playClip && this.playFrame && this.mod === ActuatorModel.Play) {
|
|
|
|
// 播放当前帧
|
|
this.playClip.play(this.playFrame);
|
|
|
|
// 没有完成播放,继续播放
|
|
if (!this.isPlayEnd()) {
|
|
|
|
// 跳转值下一帧
|
|
this.playFrameId ++;
|
|
this.playFrame = this.playClip.frames[this.playFrameId];
|
|
this.emit("record", this.playFrame.duration);
|
|
|
|
// 清除计时器,保证时钟唯一性
|
|
clearTimeout(this.playTickerTimer);
|
|
|
|
// 延时
|
|
this.playTickerTimer = setTimeout(() => {
|
|
this.playTicker();
|
|
}, this.playFrame.duration * 1000) as any;
|
|
|
|
} else {
|
|
this.pausePlay();
|
|
}
|
|
} else {
|
|
this.pausePlay();
|
|
}
|
|
}
|
|
|
|
private ticker(t: number) {
|
|
if (this.startFlag && t !== 0) {
|
|
if (this.lastTime === 0) {
|
|
this.lastTime = t;
|
|
} else {
|
|
let durTime = (t - this.lastTime) / 1000;
|
|
this.lastTime = t;
|
|
|
|
// 丢帧判定
|
|
if (durTime > 0.1) {
|
|
console.log("Actuator: Ticker dur time error. dropping...")
|
|
} else {
|
|
this.alignTimer += durTime;
|
|
if (this.alignTimer > (1 / this.fps)) {
|
|
|
|
// 更新模型
|
|
this.model.update(this.alignTimer * this.speed);
|
|
|
|
// 绘制模型
|
|
this.model.draw();
|
|
|
|
// 录制模型
|
|
if (
|
|
this.mod === ActuatorModel.Record ||
|
|
this.mod === ActuatorModel.Offline
|
|
) {
|
|
this.recordClip?.record(this.alignTimer * this.speed);
|
|
this.emit("record", this.alignTimer);
|
|
}
|
|
|
|
this.emit("loop", this.alignTimer);
|
|
this.emit("modelUpdate");
|
|
this.alignTimer = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
else {
|
|
this.emit("loop", Infinity);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 帧率对其时钟
|
|
* 1、使用 requestAnimationFrame 保证最高的性能
|
|
* 2、最大模拟帧率只能小于 60
|
|
* 3、可能出现帧率对其问题
|
|
*/
|
|
private tickerAlign = (t: number) => {
|
|
this.ticker(t);
|
|
requestAnimationFrame(this.tickerAlign);
|
|
}
|
|
|
|
/**
|
|
* 精确时钟
|
|
*/
|
|
private tickerExp = () => {
|
|
this.ticker(window.performance.now());
|
|
setTimeout(this.tickerExp, (1 / this.fps) * 1000);
|
|
}
|
|
|
|
/**
|
|
* 执行器
|
|
*/
|
|
private runTicker = (t: number) => {
|
|
if (this.tickerType === 1) {
|
|
this.ticker(t);
|
|
requestAnimationFrame(this.runTicker);
|
|
} else {
|
|
this.ticker(window.performance.now());
|
|
setTimeout(this.runTicker, (1 / this.fps) * 1000);
|
|
}
|
|
}
|
|
|
|
public constructor(model: Model) {
|
|
super();
|
|
this.model = model;
|
|
this.runTicker(0);
|
|
}
|
|
}
|
|
|
|
export { Actuator, ActuatorModel } |