Compare commits
	
		
			6 Commits
		
	
	
		
			6b3a10070f
			...
			0f7657ad83
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0f7657ad83 | |||
| 005222bde5 | |||
| 8e413f0d1c | |||
| 17ee906f90 | |||
| e5ae05e737 | |||
| c811b5b1e5 | 
| @ -1,13 +1,14 @@ | |||||||
| import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; | import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; | ||||||
| import { Template } from "./Template"; | import { Template } from "./Template"; | ||||||
|  | import { Dynamics } from "./Dynamics"; | ||||||
|  | import { Brownian } from "./Brownian"; | ||||||
|  | import { BoundaryConstraint } from "./BoundaryConstraint";  | ||||||
| 
 | 
 | ||||||
| const AllBehaviors: IAnyBehaviorRecorder[] = new Array(4).fill(0).map((_, i) => { | const AllBehaviors: IAnyBehaviorRecorder[] = [ | ||||||
|     let behavior = new BehaviorRecorder(Template); |     new BehaviorRecorder(Dynamics), | ||||||
|     behavior.behaviorId = behavior.behaviorId + i; |     new BehaviorRecorder(Brownian), | ||||||
|     behavior.behaviorName = behavior.behaviorName + Math.random().toString(36).slice(-6); |     new BehaviorRecorder(BoundaryConstraint) | ||||||
|     behavior.category = "Category" + Math.floor(Math.random() * 3).toString(); | ] | ||||||
|     return behavior; |  | ||||||
| }); |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 分类词条 |  * 分类词条 | ||||||
|  | |||||||
							
								
								
									
										74
									
								
								source/Behavior/BoundaryConstraint.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								source/Behavior/BoundaryConstraint.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,74 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Label } from "@Model/Label"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | import { Range } from "@Model/Range"; | ||||||
|  | 
 | ||||||
|  | type IBoundaryConstraintBehaviorParameter = { | ||||||
|  |     range: "LR" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IBoundaryConstraintBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, IBoundaryConstraintBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "BoundaryConstraint"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "Running"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  |     public override category: string = "$Physics"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  | 		range: { | ||||||
|  | 			type: "LR", | ||||||
|  | 			name: "$range", | ||||||
|  | 			defaultValue: undefined | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  |     public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "边界约束", | ||||||
|  |             "EN_US": "Boundary constraint" | ||||||
|  |         }, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "个体越出边界后将主动返回", | ||||||
|  |             "EN_US": "Individuals will return actively after crossing the border" | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public effect(individual: Individual, group: Group, model: Model, t: number): void { | ||||||
|  |         let rangeList: Range[] = []; | ||||||
|  | 		if (this.parameter.range instanceof Range) { | ||||||
|  | 			rangeList.push(this.parameter.range); | ||||||
|  | 		} | ||||||
|  | 		if (this.parameter.range instanceof Label) { | ||||||
|  | 			rangeList = model.getObjectByLabel(this.parameter.range).filter((obj) => { | ||||||
|  | 				return obj instanceof Range | ||||||
|  | 			}) as any; | ||||||
|  | 		} | ||||||
|  | 		for (let i = 0; i < rangeList.length; i++) { | ||||||
|  | 
 | ||||||
|  | 			let rx = rangeList[i].position[0] - individual.position[0]; | ||||||
|  | 			let ry = rangeList[i].position[1] - individual.position[1]; | ||||||
|  | 			let rz = rangeList[i].position[2] - individual.position[2]; | ||||||
|  | 
 | ||||||
|  | 			let ox = Math.abs(rx) > rangeList[i].radius[0]; | ||||||
|  | 			let oy = Math.abs(ry) > rangeList[i].radius[1]; | ||||||
|  | 			let oz = Math.abs(rz) > rangeList[i].radius[2]; | ||||||
|  | 
 | ||||||
|  | 			individual.applyForce( | ||||||
|  | 				ox ? rx : 0, | ||||||
|  | 				oy ? ry : 0, | ||||||
|  | 				oz ? rz : 0 | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { BoundaryConstraint }; | ||||||
							
								
								
									
										109
									
								
								source/Behavior/Brownian.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								source/Behavior/Brownian.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,109 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | 
 | ||||||
|  | type IBrownianBehaviorParameter = { | ||||||
|  |     maxFrequency: "number", | ||||||
|  | 	minFrequency: "number", | ||||||
|  | 	maxStrength: "number", | ||||||
|  | 	minStrength: "number" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IBrownianBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class Brownian extends Behavior<IBrownianBehaviorParameter, IBrownianBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "Brownian"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "Running"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  | 	public override category: string = "$Physics"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  | 		maxFrequency: { | ||||||
|  | 			type: "number", | ||||||
|  | 			name: "$Max.Frequency", | ||||||
|  | 			defaultValue: 5, | ||||||
|  | 			numberStep: .1, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		}, | ||||||
|  | 		minFrequency: { | ||||||
|  | 			type: "number", | ||||||
|  | 			name: "$Min.Frequency", | ||||||
|  | 			defaultValue: 0, | ||||||
|  | 			numberStep: .1, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		}, | ||||||
|  | 		maxStrength: { | ||||||
|  | 			type: "number", | ||||||
|  | 			name: "$Max.Strength", | ||||||
|  | 			defaultValue: 10, | ||||||
|  | 			numberStep: .01, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		}, | ||||||
|  | 		minStrength: { | ||||||
|  | 			type: "number", | ||||||
|  | 			name: "$Min.Strength", | ||||||
|  | 			defaultValue: 0, | ||||||
|  | 			numberStep: .01, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  |     public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "布朗运动", | ||||||
|  |             "EN_US": "Brownian motion" | ||||||
|  |         }, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "一种无规则的随机运动", | ||||||
|  |             "EN_US": "An irregular random motion" | ||||||
|  |         }, | ||||||
|  | 		"$Max.Frequency": { | ||||||
|  |             "ZH_CN": "最大频率", | ||||||
|  |             "EN_US": "Maximum frequency" | ||||||
|  |         }, | ||||||
|  | 		"$Min.Frequency": { | ||||||
|  |             "ZH_CN": "最小频率", | ||||||
|  |             "EN_US": "Minimum frequency" | ||||||
|  |         }, | ||||||
|  | 		"$Max.Strength": { | ||||||
|  |             "ZH_CN": "最大强度", | ||||||
|  |             "EN_US": "Maximum strength" | ||||||
|  |         }, | ||||||
|  | 		"$Min.Strength": { | ||||||
|  |             "ZH_CN": "最小强度", | ||||||
|  |             "EN_US": "Minimum strength" | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  | 	public effect(individual: Individual, group: Group, model: Model, t: number): void { | ||||||
|  | 
 | ||||||
|  | 		const {maxFrequency, minFrequency, maxStrength, minStrength} = this.parameter; | ||||||
|  | 
 | ||||||
|  | 		let nextTime = individual.getData("Brownian.nextTime") ??  | ||||||
|  | 		minFrequency +  Math.random() * (maxFrequency - minFrequency); | ||||||
|  | 		let currentTime = individual.getData("Brownian.currentTime") ?? 0; | ||||||
|  | 		 | ||||||
|  | 		currentTime += t; | ||||||
|  | 		if (currentTime > nextTime) { | ||||||
|  | 			individual.applyForce( | ||||||
|  | 				minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength), | ||||||
|  | 				minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength), | ||||||
|  | 				minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength) | ||||||
|  | 			); | ||||||
|  | 			nextTime = minFrequency +  Math.random() * (maxFrequency - minFrequency); | ||||||
|  | 			currentTime = 0; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		individual.setData("Brownian.nextTime", nextTime); | ||||||
|  | 		individual.setData("Brownian.currentTime", currentTime); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { Brownian }; | ||||||
							
								
								
									
										140
									
								
								source/Behavior/Dynamics.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								source/Behavior/Dynamics.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import Group from "@Model/Group"; | ||||||
|  | import Individual from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | 
 | ||||||
|  | type IDynamicsBehaviorParameter = { | ||||||
|  |     mass: "number", | ||||||
|  | 	maxAcceleration: "number", | ||||||
|  | 	maxVelocity: "number", | ||||||
|  | 	resistance: "number" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IDynamicsBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "Dynamics"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "Running"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  | 	public override category: string = "$Physics"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  | 		mass: { | ||||||
|  | 			name: "$Mass", | ||||||
|  | 			type: "number", | ||||||
|  | 			defaultValue: 1, | ||||||
|  | 			numberStep: .01, | ||||||
|  | 			numberMin: .001 | ||||||
|  | 		}, | ||||||
|  | 		maxAcceleration: { | ||||||
|  | 			name: "$Max.Acceleration", | ||||||
|  | 			type: "number", | ||||||
|  | 			defaultValue: 5, | ||||||
|  | 			numberStep: .1, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		}, | ||||||
|  | 		maxVelocity: { | ||||||
|  | 			name: "$Max.Velocity", | ||||||
|  | 			type: "number", | ||||||
|  | 			defaultValue: 10, | ||||||
|  | 			numberStep: .1, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		}, | ||||||
|  | 		resistance: { | ||||||
|  | 			name: "$Resistance", | ||||||
|  | 			type: "number", | ||||||
|  | 			defaultValue: 0.1, | ||||||
|  | 			numberStep: .1, | ||||||
|  | 			numberMin: 0 | ||||||
|  | 		} | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  |     public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "动力学", | ||||||
|  |             "EN_US": "Dynamics" | ||||||
|  |         }, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "一切可以运动物体的必要行为,执行物理法则。", | ||||||
|  |             "EN_US": "All necessary behaviors that can move objects and implement the laws of physics." | ||||||
|  |         }, | ||||||
|  | 		"$Mass": { | ||||||
|  | 			"ZH_CN": "质量 (Kg)", | ||||||
|  |             "EN_US": "Mass (Kg)" | ||||||
|  | 		}, | ||||||
|  | 		"$Max.Acceleration": { | ||||||
|  | 			"ZH_CN": "最大加速度 (m/s²)", | ||||||
|  |             "EN_US": "Maximum acceleration (m/s²)" | ||||||
|  | 		}, | ||||||
|  | 		"$Max.Velocity": { | ||||||
|  | 			"ZH_CN": "最大速度 (m/s)", | ||||||
|  |             "EN_US": "Maximum velocity (m/s)" | ||||||
|  | 		}, | ||||||
|  | 		"$Physics": { | ||||||
|  | 			"ZH_CN": "物理", | ||||||
|  |             "EN_US": "Physics" | ||||||
|  | 		} | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  | 	public override finalEffect(individual: Individual, group: Group, model: Model, t: number): void { | ||||||
|  | 
 | ||||||
|  | 		// 计算当前速度
 | ||||||
|  | 		const currentV = individual.vectorLength(individual.velocity); | ||||||
|  | 
 | ||||||
|  | 		// 计算阻力
 | ||||||
|  | 		const resistance = currentV * currentV * this.parameter.resistance; | ||||||
|  | 
 | ||||||
|  | 		// 应用阻力
 | ||||||
|  | 		if (currentV) { | ||||||
|  | 			individual.applyForce( | ||||||
|  | 				(- individual.velocity[0] / currentV) * resistance, | ||||||
|  | 				(- individual.velocity[1] / currentV) * resistance, | ||||||
|  | 				(- individual.velocity[2] / currentV) * resistance | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		// 计算加速度
 | ||||||
|  | 		individual.acceleration[0] = individual.force[0] / this.parameter.mass; | ||||||
|  | 		individual.acceleration[1] = individual.force[1] / this.parameter.mass; | ||||||
|  | 		individual.acceleration[2] = individual.force[2] / this.parameter.mass; | ||||||
|  | 
 | ||||||
|  | 		// 加速度约束
 | ||||||
|  | 		const lengthA = individual.vectorLength(individual.acceleration); | ||||||
|  | 		if (lengthA > this.parameter.maxAcceleration) { | ||||||
|  | 			individual.acceleration[0] = individual.acceleration[0] * this.parameter.maxAcceleration / lengthA; | ||||||
|  | 			individual.acceleration[1] = individual.acceleration[1] * this.parameter.maxAcceleration / lengthA; | ||||||
|  | 			individual.acceleration[2] = individual.acceleration[2] * this.parameter.maxAcceleration / lengthA; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 计算速度
 | ||||||
|  | 		individual.velocity[0] = individual.velocity[0] + individual.acceleration[0] * t; | ||||||
|  | 		individual.velocity[1] = individual.velocity[1] + individual.acceleration[1] * t; | ||||||
|  | 		individual.velocity[2] = individual.velocity[2] + individual.acceleration[2] * t; | ||||||
|  | 
 | ||||||
|  | 		// 速度约束
 | ||||||
|  | 		const lengthV = individual.vectorLength(individual.velocity); | ||||||
|  | 		if (lengthV > this.parameter.maxVelocity) { | ||||||
|  | 			individual.velocity[0] = individual.velocity[0] * this.parameter.maxVelocity / lengthV; | ||||||
|  | 			individual.velocity[1] = individual.velocity[1] * this.parameter.maxVelocity / lengthV; | ||||||
|  | 			individual.velocity[2] = individual.velocity[2] * this.parameter.maxVelocity / lengthV; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// 应用速度
 | ||||||
|  | 		individual.position[0] = individual.position[0] + individual.velocity[0] * t; | ||||||
|  | 		individual.position[1] = individual.position[1] + individual.velocity[1] * t; | ||||||
|  | 		individual.position[2] = individual.position[2] + individual.velocity[2] * t; | ||||||
|  | 
 | ||||||
|  | 		// 清除受力
 | ||||||
|  | 		individual.force[0] = 0; | ||||||
|  | 		individual.force[1] = 0; | ||||||
|  | 		individual.force[2] = 0; | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { Dynamics }; | ||||||
| @ -1,4 +1,7 @@ | |||||||
| import { Behavior } from "@Model/Behavior"; | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
| 
 | 
 | ||||||
| type ITemplateBehaviorParameter = { | type ITemplateBehaviorParameter = { | ||||||
|      |      | ||||||
| @ -16,7 +19,9 @@ class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEve | |||||||
| 
 | 
 | ||||||
|     public override describe: string = "$Intro"; |     public override describe: string = "$Intro"; | ||||||
| 
 | 
 | ||||||
|     terms: Record<string, Record<string, string>> = { |     public override category: string = "$Category"; | ||||||
|  | 
 | ||||||
|  |     public override terms: Record<string, Record<string, string>> = { | ||||||
|         "$Title": { |         "$Title": { | ||||||
|             "ZH_CN": "行为", |             "ZH_CN": "行为", | ||||||
|             "EN_US": "Behavior" |             "EN_US": "Behavior" | ||||||
| @ -26,6 +31,10 @@ class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEve | |||||||
|             "EN_US": "This is a template behavior" |             "EN_US": "This is a template behavior" | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  | 
 | ||||||
|  |     public effect(individual: Individual, group: Group, model: Model, t: number): void { | ||||||
|  |          | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { Template }; | export { Template }; | ||||||
| @ -120,7 +120,7 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinSettingProps> { | |||||||
| 			</div> | 			</div> | ||||||
| 			<div className="behavior-content-view"> | 			<div className="behavior-content-view"> | ||||||
| 				{this.renderTerm(behavior, name, "title-view", needLocal)} | 				{this.renderTerm(behavior, name, "title-view", needLocal)} | ||||||
| 				{this.renderTerm(behavior, info, "info-view", needLocal)} | 				{this.renderTerm(behavior, info, "info-view", true)} | ||||||
| 			</div> | 			</div> | ||||||
| 			<div className="behavior-action-view"> | 			<div className="behavior-action-view"> | ||||||
| 				{this.renderActionButton(behavior)} | 				{this.renderActionButton(behavior)} | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ interface ICommandBarProps { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @useSetting | @useSetting | ||||||
| @useStatusWithEvent("mouseModChange") | @useStatusWithEvent("mouseModChange", "actuatorStartChange") | ||||||
| class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixinStatusProps> { | class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixinStatusProps> { | ||||||
| 
 | 
 | ||||||
|     render(): ReactNode { |     render(): ReactNode { | ||||||
| @ -34,7 +34,13 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi | |||||||
|         > |         > | ||||||
|             <div> |             <div> | ||||||
|                 {this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} |                 {this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} | ||||||
|                 {this.getRenderButton({ iconName: "Play", i18NKey: "Command.Bar.Play.Info" })} |                 {this.getRenderButton({ | ||||||
|  |                     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.getRenderButton({ |                 {this.getRenderButton({ | ||||||
|                     iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info",  |                     iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info",  | ||||||
|                     active: mouseMod === MouseMod.Drag, |                     active: mouseMod === MouseMod.Drag, | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ class HeaderBar extends Component< | |||||||
|         return (t: number) => { |         return (t: number) => { | ||||||
|             let newState: HeaderBarState = {} as any; |             let newState: HeaderBarState = {} as any; | ||||||
|             newState[type] = 1 / t; |             newState[type] = 1 / t; | ||||||
|             if (this.updateTime > 60) { |             if (this.updateTime > 20) { | ||||||
|                 this.updateTime = 0; |                 this.updateTime = 0; | ||||||
|                 this.setState(newState); |                 this.setState(newState); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import { I18N } from "@Component/Localization/Localization"; | |||||||
| import { superConnectWithEvent, superConnect } from "./Context"; | import { superConnectWithEvent, superConnect } from "./Context"; | ||||||
| import { PopupController } from "./Popups"; | import { PopupController } from "./Popups"; | ||||||
| import { Behavior } from "@Model/Behavior"; | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Actuator } from "@Model/Actuator"; | ||||||
| 
 | 
 | ||||||
| function randomColor(unNormal: boolean = false) { | function randomColor(unNormal: boolean = false) { | ||||||
|     const color = [ |     const color = [ | ||||||
| @ -44,6 +45,7 @@ interface IStatusEvent { | |||||||
|     individualChange: void; |     individualChange: void; | ||||||
|     behaviorChange: void; |     behaviorChange: void; | ||||||
|     popupChange: void; |     popupChange: void; | ||||||
|  |     actuatorStartChange: void; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| class Status extends Emitter<IStatusEvent> { | class Status extends Emitter<IStatusEvent> { | ||||||
| @ -71,6 +73,11 @@ class Status extends Emitter<IStatusEvent> { | |||||||
|      */ |      */ | ||||||
|     public model: Model = new Model(); |     public model: Model = new Model(); | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 执行器 | ||||||
|  |      */ | ||||||
|  |     public actuator: Actuator; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 弹窗 |      * 弹窗 | ||||||
|      */ |      */ | ||||||
| @ -104,8 +111,14 @@ class Status extends Emitter<IStatusEvent> { | |||||||
|     public constructor() { |     public constructor() { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|  |         // 初始化执行器
 | ||||||
|  |         this.actuator = new Actuator(this.model); | ||||||
|  | 
 | ||||||
|  |         // 执行器开启事件
 | ||||||
|  |         this.actuator.on("startChange", () => { this.emit("actuatorStartChange") }); | ||||||
|  | 
 | ||||||
|         // 循环事件
 |         // 循环事件
 | ||||||
|         this.model.on("loop", (t) => { this.emit("physicsLoop", t) }); |         this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); | ||||||
| 
 | 
 | ||||||
|         // 对象变化事件
 |         // 对象变化事件
 | ||||||
|         this.model.on("objectChange", () => this.emit("objectChange")); |         this.model.on("objectChange", () => this.emit("objectChange")); | ||||||
|  | |||||||
							
								
								
									
										125
									
								
								source/Model/Actuator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								source/Model/Actuator.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,125 @@ | |||||||
|  | import { Model } from "./Model"; | ||||||
|  | import { Emitter } from "./Emitter";  | ||||||
|  | 
 | ||||||
|  | interface IActuatorEvent { | ||||||
|  | 	startChange: boolean; | ||||||
|  | 	loop: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * 模型执行器 | ||||||
|  |  */ | ||||||
|  | class Actuator extends Emitter<IActuatorEvent> { | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * 速度系数 | ||||||
|  | 	 */ | ||||||
|  | 	public speed: number = 1; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * 模拟帧率 | ||||||
|  | 	 */ | ||||||
|  | 	public fps: number = 36; | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * 仿真是否进行 | ||||||
|  | 	 */ | ||||||
|  | 	private startFlag: boolean = 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 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...") | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				this.alignTimer += durTime; | ||||||
|  | 				if (this.alignTimer > (1 / this.fps)) { | ||||||
|  | 					this.model.update(this.alignTimer * this.speed); | ||||||
|  | 					this.emit("loop", this.alignTimer); | ||||||
|  | 					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 } | ||||||
| @ -64,7 +64,7 @@ interface IBehaviorParameterOptionItem<T extends IParamType = IParamType> { | |||||||
|     /** |     /** | ||||||
|      * 参数类型 |      * 参数类型 | ||||||
|      */ |      */ | ||||||
|     type: T; |     type: T | string; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 参数默认值 |      * 参数默认值 | ||||||
| @ -202,7 +202,7 @@ class BehaviorRecorder< | |||||||
|      * 获取下一个 ID |      * 获取下一个 ID | ||||||
|      */ |      */ | ||||||
|     public getNextId() { |     public getNextId() { | ||||||
|         return `B-${this.behaviorName}-${this.nameIndex ++}`; |         return `B-${this.behaviorId}-${this.nameIndex ++}`; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -269,6 +269,7 @@ class BehaviorRecorder< | |||||||
|         this.behaviorId = this.behaviorInstance.behaviorId; |         this.behaviorId = this.behaviorInstance.behaviorId; | ||||||
|         this.behaviorName = this.behaviorInstance.behaviorName; |         this.behaviorName = this.behaviorInstance.behaviorName; | ||||||
|         this.describe = this.behaviorInstance.describe; |         this.describe = this.behaviorInstance.describe; | ||||||
|  |         this.category = this.behaviorInstance.category; | ||||||
|         this.terms = this.behaviorInstance.terms; |         this.terms = this.behaviorInstance.terms; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -371,7 +372,7 @@ class Behavior< | |||||||
|      * @param model 模型 |      * @param model 模型 | ||||||
|      * @param t 经过时间 |      * @param t 经过时间 | ||||||
|      */ |      */ | ||||||
|     public beforeEffect(individual: Individual, group: Group, model: Model, t: number): void {}; |     public effect(individual: Individual, group: Group, model: Model, t: number): void {}; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 作用影响于个体 |      * 作用影响于个体 | ||||||
| @ -380,7 +381,7 @@ class Behavior< | |||||||
|      * @param model 模型 |      * @param model 模型 | ||||||
|      * @param t 经过时间 |      * @param t 经过时间 | ||||||
|      */ |      */ | ||||||
|     public effect(individual: Individual, group: Group, model: Model, t: number): void {}; |     public afterEffect(individual: Individual, group: Group, model: Model, t: number): void {}; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 全部影响作用后 |      * 全部影响作用后 | ||||||
| @ -389,7 +390,7 @@ class Behavior< | |||||||
|      * @param model 模型 |      * @param model 模型 | ||||||
|      * @param t 经过时间 |      * @param t 经过时间 | ||||||
|      */ |      */ | ||||||
|     public afterEffect(individual: Individual, group: Group, model: Model, t: number): void {}; |     public finalEffect(individual: Individual, group: Group, model: Model, t: number): void {}; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -330,7 +330,7 @@ class Group extends CtrlObject { | |||||||
|      * 执行行为影响 |      * 执行行为影响 | ||||||
| 	 * @param | 	 * @param | ||||||
|      */ |      */ | ||||||
| 	public runner(t: number, effectType: "beforeEffect" | "effect" | "afterEffect" ): void { | 	public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void { | ||||||
| 		this.individuals.forEach((individual) => { | 		this.individuals.forEach((individual) => { | ||||||
| 			for(let j = 0; j < this.behaviors.length; j++) { | 			for(let j = 0; j < this.behaviors.length; j++) { | ||||||
| 				this.behaviors[j][effectType](individual, this, this.model, t); | 				this.behaviors[j][effectType](individual, this, this.model, t); | ||||||
|  | |||||||
| @ -12,9 +12,9 @@ class Individual { | |||||||
|      * @param y y 坐标 |      * @param y y 坐标 | ||||||
|      * @param z z 坐标 |      * @param z z 坐标 | ||||||
|      */ |      */ | ||||||
|     public static vectorLength(x: number[]): number; |     public vectorLength(x: number[]): number; | ||||||
|     public static vectorLength(x: number, y: number, z: number): number; |     public vectorLength(x: number, y: number, z: number): number; | ||||||
|     public static vectorLength(x: number | number[], y?: number, z?: number): number { |     public vectorLength(x: number | number[], y?: number, z?: number): number { | ||||||
|         if (Array.isArray(x)) { |         if (Array.isArray(x)) { | ||||||
|             return ((x[0] ?? 0)**2 + (x[1] ?? 0)**2 + (x[2] ?? 0)**2)**.5; |             return ((x[0] ?? 0)**2 + (x[1] ?? 0)**2 + (x[2] ?? 0)**2)**.5; | ||||||
|         } else { |         } else { | ||||||
| @ -28,10 +28,10 @@ class Individual { | |||||||
|      * @param y y 坐标 |      * @param y y 坐标 | ||||||
|      * @param z z 坐标 |      * @param z z 坐标 | ||||||
|      */ |      */ | ||||||
|     public static vectorNormalize(x: number[]): [number, number, number]; |     public vectorNormalize(x: number[]): [number, number, number]; | ||||||
|     public static vectorNormalize(x: number, y: number, z: number): [number, number, number]; |     public vectorNormalize(x: number, y: number, z: number): [number, number, number]; | ||||||
|     public static vectorNormalize(x: number | number[], y?: number, z?: number): [number, number, number] { |     public vectorNormalize(x: number | number[], y?: number, z?: number): [number, number, number] { | ||||||
|         let length = Individual.vectorLength(x as number, y as number, z as number); |         let length = this.vectorLength(x as number, y as number, z as number); | ||||||
|         if (Array.isArray(x)) { |         if (Array.isArray(x)) { | ||||||
|             return [ |             return [ | ||||||
|                 (x[0] ?? 0) / length, |                 (x[0] ?? 0) / length, | ||||||
| @ -52,6 +52,39 @@ class Individual { | |||||||
| 	 */ | 	 */ | ||||||
| 	public position: number[] = [0, 0, 0]; | 	public position: number[] = [0, 0, 0]; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 速度 | ||||||
|  |      */ | ||||||
|  |     public velocity: number[] = [0, 0, 0]; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 加速度 | ||||||
|  |      */ | ||||||
|  |     public acceleration: number[] = [0, 0, 0]; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 作用力 | ||||||
|  |      */ | ||||||
|  |     public force: number[] = [0, 0, 0]; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * 施加力 | ||||||
|  |      */ | ||||||
|  |     public applyForce(x: number[]): [number, number, number]; | ||||||
|  |     public applyForce(x: number, y: number, z: number): [number, number, number]; | ||||||
|  |     public applyForce(x: number | number[], y?: number, z?: number): [number, number, number] { | ||||||
|  |         if (Array.isArray(x)) { | ||||||
|  |             this.force[0] += x[0] ?? 0; | ||||||
|  |             this.force[1] += x[1] ?? 0; | ||||||
|  |             this.force[2] += x[2] ?? 0; | ||||||
|  |         } else { | ||||||
|  |             this.force[0] += x ?? 0; | ||||||
|  |             this.force[1] += y ?? 0; | ||||||
|  |             this.force[2] += z ?? 0; | ||||||
|  |         } | ||||||
|  |         return this.force as [number, number, number]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 所属群组 | 	 * 所属群组 | ||||||
| 	 */ | 	 */ | ||||||
| @ -107,7 +140,7 @@ class Individual { | |||||||
|      * @param position 目标位置 |      * @param position 目标位置 | ||||||
|      */ |      */ | ||||||
|     public distanceTo(position: Individual | number[]): number { |     public distanceTo(position: Individual | number[]): number { | ||||||
|         return Individual.vectorLength(this.vectorTo(position)); |         return this.vectorLength(this.vectorTo(position)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -8,7 +8,6 @@ import { Label } from "./Label"; | |||||||
| import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior"; | import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior"; | ||||||
| 
 | 
 | ||||||
| type ModelEvent = { | type ModelEvent = { | ||||||
|     loop: number; |  | ||||||
|     labelChange: Label[]; |     labelChange: Label[]; | ||||||
|     objectChange: CtrlObject[]; |     objectChange: CtrlObject[]; | ||||||
|     individualChange: Group; |     individualChange: Group; | ||||||
| @ -259,7 +258,7 @@ class Model extends Emitter<ModelEvent> { | |||||||
|         for (let i = 0; i < this.objectPool.length; i++) { |         for (let i = 0; i < this.objectPool.length; i++) { | ||||||
|             let object = this.objectPool[i]; |             let object = this.objectPool[i]; | ||||||
|             if (object instanceof Group && object.update) { |             if (object instanceof Group && object.update) { | ||||||
|                 object.runner(t, "beforeEffect"); |                 object.runner(t, "effect"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -267,7 +266,7 @@ class Model extends Emitter<ModelEvent> { | |||||||
|         for (let i = 0; i < this.objectPool.length; i++) { |         for (let i = 0; i < this.objectPool.length; i++) { | ||||||
|             let object = this.objectPool[i]; |             let object = this.objectPool[i]; | ||||||
|             if (object instanceof Group && object.update) { |             if (object instanceof Group && object.update) { | ||||||
|                 object.runner(t, "effect"); |                 object.runner(t, "afterEffect"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -275,13 +274,11 @@ class Model extends Emitter<ModelEvent> { | |||||||
|         for (let i = 0; i < this.objectPool.length; i++) { |         for (let i = 0; i < this.objectPool.length; i++) { | ||||||
|             let object = this.objectPool[i]; |             let object = this.objectPool[i]; | ||||||
|             if (object instanceof Group && object.update) { |             if (object instanceof Group && object.update) { | ||||||
|                 object.runner(t, "afterEffect"); |                 object.runner(t, "finalEffect"); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.draw(); |         this.draw(); | ||||||
| 
 |  | ||||||
|         this.emit("loop", t); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public draw() { |     public draw() { | ||||||
|  | |||||||
| @ -10,6 +10,7 @@ import { RootContainer } from "@Component/Container/RootContainer"; | |||||||
| import { LayoutDirection } from "@Context/Layout"; | import { LayoutDirection } from "@Context/Layout"; | ||||||
| import { CommandBar } from "@Component/CommandBar/CommandBar"; | import { CommandBar } from "@Component/CommandBar/CommandBar"; | ||||||
| import { Popup } from "@Component/Popup/Popup"; | import { Popup } from "@Component/Popup/Popup"; | ||||||
|  | import { AllBehaviors } from "@Behavior/Behavior"; | ||||||
| import "./SimulatorWeb.scss"; | import "./SimulatorWeb.scss"; | ||||||
| 
 | 
 | ||||||
| initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/"); | initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/"); | ||||||
| @ -54,6 +55,16 @@ class SimulatorWeb extends Component { | |||||||
|             this.status.model.update(0); |             this.status.model.update(0); | ||||||
|             this.status.newLabel().name = "New Label"; |             this.status.newLabel().name = "New Label"; | ||||||
|             this.status.newLabel().name = "Test Label 01"; |             this.status.newLabel().name = "Test Label 01"; | ||||||
|  |             let dynamic = this.status.model.addBehavior(AllBehaviors[0]); | ||||||
|  |             dynamic.name = "dynamic"; | ||||||
|  |             let brownian = this.status.model.addBehavior(AllBehaviors[1]); | ||||||
|  |             brownian.name = "brownian"; | ||||||
|  |             let boundary = this.status.model.addBehavior(AllBehaviors[2]); | ||||||
|  |             boundary.name = "boundary"; | ||||||
|  |             boundary.parameter.range = this.status.model.allRangeLabel; | ||||||
|  |             group.addBehavior(dynamic); | ||||||
|  |             group.addBehavior(brownian); | ||||||
|  |             group.addBehavior(boundary); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         (window as any).s = this; |         (window as any).s = this; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user