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 { BehaviorPopup } from "@Component/BehaviorPopup/BehaviorPopup"; | ||||||
| import { MouseMod } from "@GLRender/ClassicRenderer"; | import { MouseMod } from "@GLRender/ClassicRenderer"; | ||||||
| import { ArchiveSave } from "@Context/Archive"; | import { ArchiveSave } from "@Context/Archive"; | ||||||
|  | import { ActuatorModel } from "@Model/Actuator"; | ||||||
| import "./CommandBar.scss"; | import "./CommandBar.scss"; | ||||||
| 
 | 
 | ||||||
| const COMMAND_BAR_WIDTH = 45; | const COMMAND_BAR_WIDTH = 45; | ||||||
| @ -50,6 +51,62 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm | |||||||
|         isSaveRunning: false |         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 { |     public render(): ReactNode { | ||||||
| 
 | 
 | ||||||
|         const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; |         const mouseMod = this.props.status?.mouseMod ?? MouseMod.Drag; | ||||||
| @ -84,13 +141,7 @@ class CommandBar extends Component<IMixinSettingProps & IMixinStatusProps, IComm | |||||||
|                     }} |                     }} | ||||||
|                 /> |                 /> | ||||||
| 
 | 
 | ||||||
|                 <CommandButton |                 {this.renderPlayActionButton()} | ||||||
|                     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} |  | ||||||
|                 /> |  | ||||||
| 
 | 
 | ||||||
|                 <CommandButton |                 <CommandButton | ||||||
|                     iconName="HandsFree" |                     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 { | 		span.ms-Slider-inactive { | ||||||
| 			height: 3px; | 			height: 3px; | ||||||
| 			animation: none; | 			transition: none; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ interface IRecorderProps { | |||||||
| 	allTime?: number; | 	allTime?: number; | ||||||
| 	currentTime?: number; | 	currentTime?: number; | ||||||
| 	action?: () => void; | 	action?: () => void; | ||||||
|  | 	valueChange?: (value: number) => any; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class Recorder extends Component<IRecorderProps> { | class Recorder extends Component<IRecorderProps> { | ||||||
| @ -85,19 +86,38 @@ class Recorder extends Component<IRecorderProps> { | |||||||
| 				max={this.props.allFrame} | 				max={this.props.allFrame} | ||||||
| 				className={"recorder-slider" + (isSliderDisable ? " disable" : "")} | 				className={"recorder-slider" + (isSliderDisable ? " disable" : "")} | ||||||
| 				showValue={false} | 				showValue={false} | ||||||
|  | 				onChange={(value) => { | ||||||
|  | 					if (this.props.valueChange && !isSliderDisable) { | ||||||
|  | 						this.props.valueChange(value); | ||||||
|  | 					} | ||||||
|  | 				}} | ||||||
| 			/> | 			/> | ||||||
| 			<div className="recorder-content"> | 			<div className="recorder-content"> | ||||||
| 				<div className="time-view"> | 				<div className="time-view"> | ||||||
| 					{this.getRecordInfo()} | 					{this.getRecordInfo()} | ||||||
| 				</div> | 				</div> | ||||||
| 				<div className="ctrl-button"> | 				<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"/> | 						<Icon iconName="Back"/> | ||||||
| 					</div> | 					</div> | ||||||
| 					<div className="ctrl-action ctrl-action-main" onClick={this.props.action}> | 					<div className="ctrl-action ctrl-action-main" onClick={this.props.action}> | ||||||
| 						<Icon iconName={this.getActionIcon()}/> | 						<Icon iconName={this.getActionIcon()}/> | ||||||
| 					</div> | 					</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"/> | 						<Icon iconName="Forward"/> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
|  | |||||||
| @ -37,6 +37,7 @@ interface IStatusEvent { | |||||||
|     renderLoop: number; |     renderLoop: number; | ||||||
|     physicsLoop: number; |     physicsLoop: number; | ||||||
|     recordLoop: number; |     recordLoop: number; | ||||||
|  |     offlineLoop: number; | ||||||
|     mouseModChange: void; |     mouseModChange: void; | ||||||
|     focusObjectChange: void; |     focusObjectChange: void; | ||||||
|     focusLabelChange: void; |     focusLabelChange: void; | ||||||
| @ -129,6 +130,7 @@ class Status extends Emitter<IStatusEvent> { | |||||||
|         // 循环事件
 |         // 循环事件
 | ||||||
|         this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); |         this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); | ||||||
|         this.actuator.on("record", (t) => { this.emit("recordLoop", 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")); |         this.model.on("objectChange", () => this.emit("objectChange")); | ||||||
| @ -424,7 +426,7 @@ class Status extends Emitter<IStatusEvent> { | |||||||
|         return label; |         return label; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public newClip() { |     public getNewClipName() { | ||||||
|         let searchKey = I18N(this.setting.language, "Object.List.New.Clip", { id: "" }); |         let searchKey = I18N(this.setting.language, "Object.List.New.Clip", { id: "" }); | ||||||
|         let nextIndex = 1; |         let nextIndex = 1; | ||||||
|         this.model.clipPool.forEach((obj) => { |         this.model.clipPool.forEach((obj) => { | ||||||
| @ -432,11 +434,13 @@ class Status extends Emitter<IStatusEvent> { | |||||||
|                 obj.name, searchKey |                 obj.name, searchKey | ||||||
|             )); |             )); | ||||||
|         }); |         }); | ||||||
|         const clip = this.model.addClip( |         return I18N(this.setting.language, "Object.List.New.Clip", { | ||||||
|             I18N(this.setting.language, "Object.List.New.Clip", { |             id: nextIndex.toString() | ||||||
|                 id: nextIndex.toString() |         }); | ||||||
|             }) |     } | ||||||
|         ); | 
 | ||||||
|  |     public newClip() { | ||||||
|  |         const clip = this.model.addClip(this.getNewClipName()); | ||||||
|         return clip; |         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.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.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.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.Title": "Load save", | ||||||
|     "Popup.Load.Save.confirm": "Got it", |     "Popup.Load.Save.confirm": "Got it", | ||||||
|     "Popup.Load.Save.Overwrite": "Overwrite and continue", |     "Popup.Load.Save.Overwrite": "Overwrite and continue", | ||||||
|  | |||||||
| @ -72,6 +72,16 @@ const ZH_CN = { | |||||||
|     "Popup.Delete.Clip.Confirm": "你确定删除这个剪辑片段,剪辑片段删除后将无法恢复。", |     "Popup.Delete.Clip.Confirm": "你确定删除这个剪辑片段,剪辑片段删除后将无法恢复。", | ||||||
|     "Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。", |     "Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。", | ||||||
|     "Popup.Setting.Title": "首选项设置", |     "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.Title": "加载存档", | ||||||
|     "Popup.Load.Save.confirm": "我知道了", |     "Popup.Load.Save.confirm": "我知道了", | ||||||
|     "Popup.Load.Save.Overwrite": "覆盖并继续", |     "Popup.Load.Save.Overwrite": "覆盖并继续", | ||||||
|  | |||||||
| @ -13,6 +13,7 @@ interface IActuatorEvent { | |||||||
| 	startChange: boolean; | 	startChange: boolean; | ||||||
| 	record: number; | 	record: number; | ||||||
| 	loop: number; | 	loop: number; | ||||||
|  | 	offline: number; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -217,6 +218,124 @@ class Actuator extends Emitter<IActuatorEvent> { | |||||||
| 
 | 
 | ||||||
| 	private playTickerTimer?: number; | 	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 { Clip } from "@Model/Clip"; | ||||||
| import { ActuatorModel } from "@Model/Actuator"; | import { ActuatorModel } from "@Model/Actuator"; | ||||||
| import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; | import { ConfirmPopup } from "@Component/ConfirmPopup/ConfirmPopup"; | ||||||
|  | import { OfflineRender } from "@Component/OfflineRender/OfflineRender" | ||||||
| import "./ClipPlayer.scss"; | import "./ClipPlayer.scss"; | ||||||
| 
 | 
 | ||||||
| @useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange") | @useStatusWithEvent("clipChange", "focusClipChange", "actuatorStartChange") | ||||||
| @ -50,6 +51,7 @@ class ClipPlayer extends Component<IMixinStatusProps> { | |||||||
| 			}} | 			}} | ||||||
| 			add={() => { | 			add={() => { | ||||||
| 				this.isInnerClick = true; | 				this.isInnerClick = true; | ||||||
|  | 				this.props.status?.popup.showPopup(OfflineRender, {}); | ||||||
| 			}} | 			}} | ||||||
| 			click={(clip) => { | 			click={(clip) => { | ||||||
| 				this.isInnerClick = true; | 				this.isInnerClick = true; | ||||||
|  | |||||||
| @ -90,9 +90,12 @@ class ClipRecorder extends Component<IMixinStatusProps> { | |||||||
| 
 | 
 | ||||||
| 					// 启动播放时钟
 | 					// 启动播放时钟
 | ||||||
| 					this.props.status?.actuator.pausePlay(); | 					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