Compare commits
	
		
			3 Commits
		
	
	
		
			a0547095e2
			...
			a689d23b5f
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a689d23b5f | |||
| be22102f95 | |||
| 53ae625c92 | 
| @ -9,6 +9,7 @@ import { SettingPopup } from "@Component/SettingPopup/SettingPopup"; | ||||
| import { BehaviorPopup } from "@Component/BehaviorPopup/BehaviorPopup"; | ||||
| import { MouseMod } from "@GLRender/ClassicRenderer"; | ||||
| import { ArchiveSave } from "@Context/Archive"; | ||||
| import { ActuatorModel } from "@Model/Actuator"; | ||||
| import "./CommandBar.scss"; | ||||
| 
 | ||||
| const COMMAND_BAR_WIDTH = 45; | ||||
| @ -50,6 +51,62 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm | ||||
|         isSaveRunning: false | ||||
|     }; | ||||
| 
 | ||||
|     private renderPlayActionButton(): ReactNode { | ||||
| 
 | ||||
|         let icon: string = "Play"; | ||||
|         let handel: () => any = () => {}; | ||||
| 
 | ||||
|         // 播放模式
 | ||||
|         if (this.props.status?.focusClip) { | ||||
| 
 | ||||
|             // 暂停播放
 | ||||
|             if (this.props.status?.actuator.mod === ActuatorModel.Play) { | ||||
|                 icon = "Pause"; | ||||
|                 handel = () => { | ||||
|                     this.props.status?.actuator.pausePlay(); | ||||
| 					console.log("ClipRecorder: Pause play..."); | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             // 开始播放
 | ||||
|             else { | ||||
|                 icon = "Play"; | ||||
|                 handel = () => { | ||||
|                     this.props.status?.actuator.playing(); | ||||
| 					console.log("ClipRecorder: Play start..."); | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         // 正在录制中
 | ||||
|         else if ( | ||||
|             this.props.status?.actuator.mod === ActuatorModel.Record || | ||||
| 			this.props.status?.actuator.mod === ActuatorModel.Offline | ||||
|         ) { | ||||
| 
 | ||||
|             // 暂停录制
 | ||||
|             icon = "Stop"; | ||||
|             handel = () => { | ||||
|                 this.props.status?.actuator.endRecord(); | ||||
|                 console.log("ClipRecorder: Rec end..."); | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         // 正常控制主时钟
 | ||||
|         else { | ||||
|             icon = this.props.status?.actuator.start() ? "Pause" : "Play"; | ||||
|             handel = () => this.props.status?.actuator.start( | ||||
|                 !this.props.status?.actuator.start() | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         return <CommandButton | ||||
|             iconName={icon} | ||||
|             i18NKey="Command.Bar.Play.Info" | ||||
|             click={handel} | ||||
|         />; | ||||
|     } | ||||
| 
 | ||||
|     public render(): ReactNode { | ||||
| 
 | ||||
|         const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; | ||||
| @ -84,13 +141,7 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm | ||||
|                     }} | ||||
|                 /> | ||||
| 
 | ||||
|                 <CommandButton | ||||
|                     iconName={this.props.status?.actuator.start() ? "Pause" : "Play"} | ||||
|                     i18NKey="Command.Bar.Play.Info" | ||||
|                     click={() => this.props.status ? this.props.status.actuator.start( | ||||
|                         !this.props.status.actuator.start() | ||||
|                     ) : undefined} | ||||
|                 /> | ||||
|                 {this.renderPlayActionButton()} | ||||
| 
 | ||||
|                 <CommandButton | ||||
|                     iconName="HandsFree" | ||||
|  | ||||
							
								
								
									
										8
									
								
								source/Component/OfflineRender/OfflineRender.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								source/Component/OfflineRender/OfflineRender.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| @import "../Theme/Theme.scss"; | ||||
| 
 | ||||
| div.offline-render-popup { | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	box-sizing: border-box; | ||||
| 	padding: 10px; | ||||
| } | ||||
							
								
								
									
										124
									
								
								source/Component/OfflineRender/OfflineRender.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								source/Component/OfflineRender/OfflineRender.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,124 @@ | ||||
| import { Component, ReactNode } from "react"; | ||||
| import { Popup } from "@Context/Popups"; | ||||
| import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||
| import { Localization } from "@Component/Localization/Localization"; | ||||
| import { AttrInput } from "@Input/AttrInput/AttrInput"; | ||||
| import { Message } from "@Input/Message/Message"; | ||||
| import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup"; | ||||
| import { ProcessPopup } from "@Component/ProcessPopup/ProcessPopup"; | ||||
| import { Emitter } from "@Model/Emitter"; | ||||
| import "./OfflineRender.scss"; | ||||
| 
 | ||||
| interface IOfflineRenderProps { | ||||
| 	close?: () => any; | ||||
| } | ||||
| 
 | ||||
| interface IOfflineRenderState { | ||||
| 	time: number; | ||||
| 	fps: number; | ||||
| 	name: string; | ||||
| } | ||||
| 
 | ||||
| class OfflineRender extends Popup<IOfflineRenderProps> { | ||||
| 
 | ||||
| 	public minWidth: number = 250; | ||||
| 	public minHeight: number = 150; | ||||
| 	public width: number = 400; | ||||
| 	public height: number = 300; | ||||
| 
 | ||||
| 	public maskForSelf: boolean = true; | ||||
| 
 | ||||
| 	public onRenderHeader(): ReactNode { | ||||
| 		return <Localization i18nKey="Popup.Offline.Render.Title"/> | ||||
| 	} | ||||
| 
 | ||||
| 	public render(): ReactNode { | ||||
| 		return <OfflineRenderComponent {...this.props} close={() => { | ||||
| 			this.close(); | ||||
| 		}}/> | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @useStatusWithEvent() | ||||
| class OfflineRenderComponent extends Component<IOfflineRenderProps & IMixinStatusProps, IOfflineRenderState> { | ||||
| 
 | ||||
| 	public constructor(props: IOfflineRenderProps & IMixinStatusProps) { | ||||
| 		super(props); | ||||
| 		this.state = { | ||||
| 			name: this.props.status?.getNewClipName() ?? "", | ||||
| 			time: 10, | ||||
| 			fps: 60 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	public render(): ReactNode { | ||||
| 		return <ConfirmContent | ||||
| 			className="offline-render-popup" | ||||
| 			actions={[{ | ||||
| 				i18nKey: "Popup.Offline.Render.Input.Start", | ||||
| 				onClick: () => { | ||||
| 
 | ||||
| 					// 获取新实例
 | ||||
| 					let newClip = this.props.status?.newClip(); | ||||
| 
 | ||||
| 					if (newClip) { | ||||
| 						newClip.name = this.state.name; | ||||
| 						this.props.status?.actuator.offlineRender(newClip, this.state.time, this.state.fps); | ||||
| 
 | ||||
| 						// 开启进度条弹窗
 | ||||
| 						this.props.status?.popup.showPopup(ProcessPopup, {}); | ||||
| 					} | ||||
| 
 | ||||
| 					// 关闭这个弹窗
 | ||||
| 					this.props.close && this.props.close(); | ||||
| 				} | ||||
| 			}]} | ||||
| 		> | ||||
| 
 | ||||
| 			<Message i18nKey="Popup.Offline.Render.Message" isTitle first/> | ||||
| 
 | ||||
| 			<AttrInput | ||||
|                 id={"Render-Name"} | ||||
| 				value={this.state.name} | ||||
| 				keyI18n="Popup.Offline.Render.Input.Name" | ||||
| 				maxLength={15} | ||||
|                 valueChange={(val) => { | ||||
|                     this.setState({ | ||||
| 						name: val | ||||
| 					}); | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
| 			<AttrInput | ||||
| 				isNumber | ||||
|                 id={"Render-Time"} | ||||
| 				value={this.state.time} | ||||
| 				keyI18n="Popup.Offline.Render.Input.Time" | ||||
| 				max={3600} | ||||
| 				min={1} | ||||
|                 valueChange={(val) => { | ||||
|                     this.setState({ | ||||
| 						time: parseFloat(val) | ||||
| 					}); | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
| 			<AttrInput | ||||
| 				isNumber | ||||
|                 id={"Render-FPS"} | ||||
| 				max={1000} | ||||
| 				min={1} | ||||
| 				value={this.state.fps} | ||||
| 				keyI18n="Popup.Offline.Render.Input.Fps" | ||||
|                 valueChange={(val) => { | ||||
|                     this.setState({ | ||||
| 						fps: parseFloat(val) | ||||
| 					}); | ||||
|                 }} | ||||
|             /> | ||||
| 
 | ||||
| 		</ConfirmContent> | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export { OfflineRender }; | ||||
							
								
								
									
										42
									
								
								source/Component/ProcessPopup/ProcessPopup.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								source/Component/ProcessPopup/ProcessPopup.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| @import "../Theme/Theme.scss"; | ||||
| 
 | ||||
| div.process-popup { | ||||
| 	width: 100%; | ||||
| 	height: 100%; | ||||
| 	box-sizing: border-box; | ||||
| 	padding: 10px; | ||||
| 
 | ||||
| 	div.ms-ProgressIndicator { | ||||
| 		transform: none; | ||||
| 
 | ||||
| 		div.ms-ProgressIndicator-progressTrack { | ||||
| 			transform: none; | ||||
| 		} | ||||
| 	 | ||||
| 		div.ms-ProgressIndicator-progressBar { | ||||
| 			transform: none; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| div.confirm-root.dark div.ms-ProgressIndicator { | ||||
| 
 | ||||
| 	div.ms-ProgressIndicator-progressTrack { | ||||
| 		background-color: $lt-bg-color-lvl3-dark; | ||||
| 	} | ||||
| 
 | ||||
| 	div.ms-ProgressIndicator-progressBar { | ||||
| 		background-color: $lt-font-color-normal-dark; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| div.confirm-root.light div.ms-ProgressIndicator { | ||||
| 
 | ||||
| 	div.ms-ProgressIndicator-progressTrack { | ||||
| 		background-color: $lt-bg-color-lvl3-light; | ||||
| 	} | ||||
| 
 | ||||
| 	div.ms-ProgressIndicator-progressBar { | ||||
| 		background-color: $lt-font-color-normal-light; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										89
									
								
								source/Component/ProcessPopup/ProcessPopup.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								source/Component/ProcessPopup/ProcessPopup.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| import { Component, ReactNode } from "react"; | ||||
| import { Popup } from "@Context/Popups"; | ||||
| import { Localization } from "@Component/Localization/Localization"; | ||||
| import { ConfirmContent } from "@Component/ConfirmPopup/ConfirmPopup"; | ||||
| import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||
| import { ProgressIndicator } from "@fluentui/react"; | ||||
| import { ActuatorModel } from "@Model/Actuator"; | ||||
| import "./ProcessPopup.scss"; | ||||
| 
 | ||||
| interface IProcessPopupProps { | ||||
| 	close?: () => void; | ||||
| } | ||||
| 
 | ||||
| class ProcessPopup extends Popup<IProcessPopupProps> { | ||||
| 
 | ||||
| 	public minWidth: number = 400; | ||||
| 	public minHeight: number = 150; | ||||
| 	public width: number = 400; | ||||
| 	public height: number = 150; | ||||
| 
 | ||||
| 	public maskForSelf: boolean = true; | ||||
| 
 | ||||
| 	public onClose(): void {} | ||||
| 
 | ||||
| 	public onRenderHeader(): ReactNode { | ||||
| 		return <Localization i18nKey="Popup.Offline.Render.Process.Title"/> | ||||
| 	} | ||||
| 
 | ||||
| 	public render(): ReactNode { | ||||
| 		return <ProcessPopupComponent {...this.props} close={() => this.close()}/> | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @useStatusWithEvent("offlineLoop", "actuatorStartChange", "recordLoop") | ||||
| class ProcessPopupComponent extends Component<IProcessPopupProps & IMixinStatusProps> { | ||||
| 
 | ||||
| 	public render(): ReactNode { | ||||
| 
 | ||||
| 		let current = this.props.status?.actuator.offlineCurrentFrame ?? 0; | ||||
| 		let all = this.props.status?.actuator.offlineAllFrame ?? 0; | ||||
| 
 | ||||
| 		const isRendering = this.props.status?.actuator.mod === ActuatorModel.Offline; | ||||
| 		let i18nKey = ""; | ||||
| 		let color: undefined | "red"; | ||||
| 		let onClick = () => {}; | ||||
| 
 | ||||
| 		if (isRendering) { | ||||
| 			i18nKey = "Popup.Offline.Render.Input.End"; | ||||
| 			color = "red"; | ||||
| 			onClick = () => { | ||||
| 				this.props.status?.actuator.endOfflineRender(); | ||||
| 				this.forceUpdate(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		else { | ||||
| 			i18nKey = "Popup.Offline.Render.Input.Finished"; | ||||
| 			onClick = () => { | ||||
| 				this.props.close && this.props.close(); | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return <ConfirmContent | ||||
| 			className="process-popup" | ||||
| 			actions={[{ | ||||
| 				i18nKey: i18nKey, | ||||
| 				color: color, | ||||
| 				onClick: onClick | ||||
| 			}]} | ||||
| 		> | ||||
| 
 | ||||
| 			<ProgressIndicator | ||||
| 				percentComplete={current / all} | ||||
| 				barHeight={3} | ||||
| 			/> | ||||
| 
 | ||||
| 			<Localization | ||||
| 				i18nKey="Popup.Offline.Render.Process" | ||||
| 				options={{ | ||||
| 					current: current.toString(), | ||||
| 					all: all.toString() | ||||
| 				}} | ||||
| 			/> | ||||
| 
 | ||||
| 		</ConfirmContent> | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| export { ProcessPopup }; | ||||
| @ -30,7 +30,7 @@ div.recorder-root { | ||||
| 		 | ||||
| 		span.ms-Slider-inactive { | ||||
| 			height: 3px; | ||||
| 			animation: none; | ||||
| 			transition: none; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -14,6 +14,7 @@ interface IRecorderProps { | ||||
| 	allTime?: number; | ||||
| 	currentTime?: number; | ||||
| 	action?: () => void; | ||||
| 	valueChange?: (value: number) => any; | ||||
| } | ||||
| 
 | ||||
| class Recorder extends Component<IRecorderProps> { | ||||
| @ -85,19 +86,38 @@ class Recorder extends Component<IRecorderProps> { | ||||
| 				max={this.props.allFrame} | ||||
| 				className={"recorder-slider" + (isSliderDisable ? " disable" : "")} | ||||
| 				showValue={false} | ||||
| 				onChange={(value) => { | ||||
| 					if (this.props.valueChange && !isSliderDisable) { | ||||
| 						this.props.valueChange(value); | ||||
| 					} | ||||
| 				}} | ||||
| 			/> | ||||
| 			<div className="recorder-content"> | ||||
| 				<div className="time-view"> | ||||
| 					{this.getRecordInfo()} | ||||
| 				</div> | ||||
| 				<div className="ctrl-button"> | ||||
| 					<div className={"ctrl-action" + (isJumpDisable ? " disable" : "")}> | ||||
| 					<div | ||||
| 						className={"ctrl-action" + (isJumpDisable ? " disable" : "")} | ||||
| 						onClick={() => { | ||||
| 							if (this.props.valueChange && !isJumpDisable && this.props.currentFrame !== undefined) { | ||||
| 								this.props.valueChange(this.props.currentFrame - 1); | ||||
| 							} | ||||
| 						}} | ||||
| 					> | ||||
| 						<Icon iconName="Back"/> | ||||
| 					</div> | ||||
| 					<div className="ctrl-action ctrl-action-main" onClick={this.props.action}> | ||||
| 						<Icon iconName={this.getActionIcon()}/> | ||||
| 					</div> | ||||
| 					<div className={"ctrl-action" + (isJumpDisable ? " disable" : "")}> | ||||
| 					<div | ||||
| 						className={"ctrl-action" + (isJumpDisable ? " disable" : "")} | ||||
| 						onClick={() => { | ||||
| 							if (this.props.valueChange && !isJumpDisable && this.props.currentFrame !== undefined) { | ||||
| 								this.props.valueChange(this.props.currentFrame + 1); | ||||
| 							} | ||||
| 						}} | ||||
| 					> | ||||
| 						<Icon iconName="Forward"/> | ||||
| 					</div> | ||||
| 				</div> | ||||
|  | ||||
| @ -37,6 +37,7 @@ interface IStatusEvent { | ||||
|     renderLoop: number; | ||||
|     physicsLoop: number; | ||||
|     recordLoop: number; | ||||
|     offlineLoop: number; | ||||
|     mouseModChange: void; | ||||
|     focusObjectChange: void; | ||||
|     focusLabelChange: void; | ||||
| @ -129,6 +130,7 @@ class Status extends Emitter<IStatusEvent> { | ||||
|         // 循环事件
 | ||||
|         this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); | ||||
|         this.actuator.on("record", (t) => { this.emit("recordLoop", t) }); | ||||
|         this.actuator.on("offline", (t) => { this.emit("offlineLoop", t) }); | ||||
| 
 | ||||
|         // 对象变化事件
 | ||||
|         this.model.on("objectChange", () => this.emit("objectChange")); | ||||
| @ -424,7 +426,7 @@ class Status extends Emitter<IStatusEvent> { | ||||
|         return label; | ||||
|     } | ||||
| 
 | ||||
|     public newClip() { | ||||
|     public getNewClipName() { | ||||
|         let searchKey = I18N(this.setting.language, "Object.List.New.Clip", { id: "" }); | ||||
|         let nextIndex = 1; | ||||
|         this.model.clipPool.forEach((obj) => { | ||||
| @ -432,11 +434,13 @@ class Status extends Emitter<IStatusEvent> { | ||||
|                 obj.name, searchKey | ||||
|             )); | ||||
|         }); | ||||
|         const clip = this.model.addClip( | ||||
|             I18N(this.setting.language, "Object.List.New.Clip", { | ||||
|         return I18N(this.setting.language, "Object.List.New.Clip", { | ||||
|             id: nextIndex.toString() | ||||
|             }) | ||||
|         ); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public newClip() { | ||||
|         const clip = this.model.addClip(this.getNewClipName()); | ||||
|         return clip; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -72,6 +72,16 @@ const EN_US = { | ||||
|     "Popup.Delete.Clip.Confirm": "Are you sure you want to delete this clip? The clip cannot be restored after deletion.", | ||||
|     "Popup.Restore.Behavior.Confirm": "Are you sure you want to reset all parameters of this behavior? This operation cannot be recalled.", | ||||
|     "Popup.Setting.Title": "Preferences setting", | ||||
|     "Popup.Offline.Render.Title": "Offline rendering", | ||||
|     "Popup.Offline.Render.Process.Title": "Rendering progress", | ||||
|     "Popup.Offline.Render.Message": "Rendering Parameters", | ||||
|     "Popup.Offline.Render.Input.Name": "Clip name", | ||||
|     "Popup.Offline.Render.Input.Time": "Duration (s)", | ||||
|     "Popup.Offline.Render.Input.Fps": "FPS (f/s)", | ||||
|     "Popup.Offline.Render.Input.Start": "Start rendering", | ||||
|     "Popup.Offline.Render.Input.End": "Terminate rendering", | ||||
|     "Popup.Offline.Render.Input.Finished": "Finished", | ||||
|     "Popup.Offline.Render.Process": "Number of frames completed: {current} / {all}", | ||||
|     "Popup.Load.Save.Title": "Load save", | ||||
|     "Popup.Load.Save.confirm": "Got it", | ||||
|     "Popup.Load.Save.Overwrite": "Overwrite and continue", | ||||
|  | ||||
| @ -72,6 +72,16 @@ const ZH_CN = { | ||||
|     "Popup.Delete.Clip.Confirm": "你确定删除这个剪辑片段,剪辑片段删除后将无法恢复。", | ||||
|     "Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。", | ||||
|     "Popup.Setting.Title": "首选项设置", | ||||
|     "Popup.Offline.Render.Title": "离线渲染", | ||||
|     "Popup.Offline.Render.Process.Title": "渲染进度", | ||||
|     "Popup.Offline.Render.Message": "渲染参数", | ||||
|     "Popup.Offline.Render.Input.Name": "剪辑名称", | ||||
|     "Popup.Offline.Render.Input.Time": "时长 (s)", | ||||
|     "Popup.Offline.Render.Input.Fps": "帧率 (f/s)", | ||||
|     "Popup.Offline.Render.Input.Start": "开始渲染", | ||||
|     "Popup.Offline.Render.Input.End": "终止渲染", | ||||
|     "Popup.Offline.Render.Input.Finished": "完成", | ||||
|     "Popup.Offline.Render.Process": "完成帧数: {current} / {all}", | ||||
|     "Popup.Load.Save.Title": "加载存档", | ||||
|     "Popup.Load.Save.confirm": "我知道了", | ||||
|     "Popup.Load.Save.Overwrite": "覆盖并继续", | ||||
|  | ||||
| @ -13,6 +13,7 @@ interface IActuatorEvent { | ||||
| 	startChange: boolean; | ||||
| 	record: number; | ||||
| 	loop: number; | ||||
| 	offline: number; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -217,6 +218,124 @@ class Actuator extends Emitter<IActuatorEvent> { | ||||
| 
 | ||||
| 	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); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 播放时钟 | ||||
| 	 */ | ||||
|  | ||||
| @ -6,6 +6,7 @@ import { Message } from "@Input/Message/Message"; | ||||
| import { Clip } from "@Model/Clip"; | ||||
| import { ActuatorModel } from "@Model/Actuator"; | ||||
| import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; | ||||
| import { OfflineRender } from "@Component/OfflineRender/OfflineRender" | ||||
| import "./ClipPlayer.scss"; | ||||
| 
 | ||||
| @useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange") | ||||
| @ -50,6 +51,7 @@ class ClipPlayer extends Component<IMixinStatusProps> { | ||||
| 			}} | ||||
| 			add={() => { | ||||
| 				this.isInnerClick = true; | ||||
| 				this.props.status?.popup.showPopup(OfflineRender, {}); | ||||
| 			}} | ||||
| 			click={(clip) => { | ||||
| 				this.isInnerClick = true; | ||||
|  | ||||
| @ -90,9 +90,12 @@ class ClipRecorder extends Component<IMixinStatusProps> { | ||||
| 
 | ||||
| 					// 启动播放时钟
 | ||||
| 					this.props.status?.actuator.pausePlay(); | ||||
| 					console.log("ClipRecorder: Pause start..."); | ||||
| 					console.log("ClipRecorder: Pause play..."); | ||||
| 				} | ||||
| 			}} | ||||
| 			valueChange={(value) => { | ||||
| 				this.props.status?.actuator.setPlayProcess(value); | ||||
| 			}} | ||||
| 		/> | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user