Merge pull request 'Add tracking assimilate attacking behaviot' (#39) from dev-mrkbear into master
Reviewed-on: http://git.mrkbear.com/MrKBear/living-together/pulls/39
This commit is contained in:
		
						commit
						0fc5d4c6d4
					
				| @ -1,14 +1,22 @@ | |||||||
| import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; | import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; | ||||||
| import { Template } from "@Behavior/Template"; | import { Template } from "@Behavior/Template"; | ||||||
| import { Dynamics } from "@Behavior/Dynamics"; | import { PhysicsDynamics } from "@Behavior/PhysicsDynamics"; | ||||||
| import { Brownian } from "@Behavior/Brownian"; | import { Brownian } from "@Behavior/Brownian"; | ||||||
| import { BoundaryConstraint } from "@Behavior/BoundaryConstraint"; | import { BoundaryConstraint } from "@Behavior/BoundaryConstraint"; | ||||||
|  | import { Tracking } from "@Behavior/Tracking"; | ||||||
|  | import { ContactAttacking } from "@Behavior/ContactAttacking"; | ||||||
|  | import { ContactAssimilate } from "@Behavior/ContactAssimilate"; | ||||||
|  | import { DelayAssimilate } from "@Behavior/DelayAssimilate"; | ||||||
| 
 | 
 | ||||||
| const AllBehaviors: IAnyBehaviorRecorder[] = [ | const AllBehaviors: IAnyBehaviorRecorder[] = [ | ||||||
|     new BehaviorRecorder(Template), |     new BehaviorRecorder(Template), | ||||||
|     new BehaviorRecorder(Dynamics), |     new BehaviorRecorder(PhysicsDynamics), | ||||||
|     new BehaviorRecorder(Brownian), |     new BehaviorRecorder(Brownian), | ||||||
|     new BehaviorRecorder(BoundaryConstraint), |     new BehaviorRecorder(BoundaryConstraint), | ||||||
|  |     new BehaviorRecorder(Tracking), | ||||||
|  |     new BehaviorRecorder(ContactAttacking), | ||||||
|  |     new BehaviorRecorder(ContactAssimilate), | ||||||
|  |     new BehaviorRecorder(DelayAssimilate), | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -54,4 +62,13 @@ function categoryBehaviors(behaviors: IAnyBehaviorRecorder[]): ICategory[] { | |||||||
|     return res; |     return res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { AllBehaviors, AllBehaviorsWithCategory, ICategory as ICategoryBehavior }; | function getBehaviorById(id: string): IAnyBehaviorRecorder { | ||||||
|  |     for (let i = 0; i < AllBehaviors.length; i++) { | ||||||
|  |         if (AllBehaviors[i].behaviorId === id) { | ||||||
|  |             return AllBehaviors[i]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return getBehaviorById("Template"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { AllBehaviors, AllBehaviorsWithCategory, getBehaviorById, ICategory as ICategoryBehavior }; | ||||||
| @ -17,7 +17,7 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, | |||||||
| 
 | 
 | ||||||
|     public override behaviorName: string = "$Title"; |     public override behaviorName: string = "$Title"; | ||||||
| 
 | 
 | ||||||
|     public override iconName: string = "Running"; |     public override iconName: string = "Quantity"; | ||||||
| 
 | 
 | ||||||
|     public override describe: string = "$Intro"; |     public override describe: string = "$Intro"; | ||||||
| 
 | 
 | ||||||
| @ -28,7 +28,7 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, | |||||||
| 		strength: { type: "number", name: "$Strength", defaultValue: 1, numberMin: 0, numberStep: .1 } | 		strength: { type: "number", name: "$Strength", defaultValue: 1, numberMin: 0, numberStep: .1 } | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
|     public effect(individual: Individual, group: Group, model: Model, t: number): void { |     public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
|         let rangeList: Range[] = this.parameter.range.objects; |         let rangeList: Range[] = this.parameter.range.objects; | ||||||
| 
 | 
 | ||||||
| 		let fx = 0; | 		let fx = 0; | ||||||
| @ -57,7 +57,6 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, | |||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 			} else { | 			} else { | ||||||
| 
 |  | ||||||
| 				fx = 0; | 				fx = 0; | ||||||
| 				fy = 0; | 				fy = 0; | ||||||
| 				fz = 0; | 				fz = 0; | ||||||
| @ -65,11 +64,13 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		individual.applyForce( |         if (fLen && fLen !== Infinity) { | ||||||
| 			fx * this.parameter.strength, |             individual.applyForce( | ||||||
| 			fy * this.parameter.strength, |                 fx * this.parameter.strength / fLen, | ||||||
| 			fz * this.parameter.strength |                 fy * this.parameter.strength / fLen, | ||||||
| 		); |                 fz * this.parameter.strength / fLen | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 	public override terms: Record<string, Record<string, string>> = { | 	public override terms: Record<string, Record<string, string>> = { | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ class Brownian extends Behavior<IBrownianBehaviorParameter, IBrownianBehaviorEve | |||||||
| 
 | 
 | ||||||
|     public override behaviorName: string = "$Title"; |     public override behaviorName: string = "$Title"; | ||||||
| 
 | 
 | ||||||
|     public override iconName: string = "Running"; |     public override iconName: string = "ScatterChart"; | ||||||
| 
 | 
 | ||||||
|     public override describe: string = "$Intro"; |     public override describe: string = "$Intro"; | ||||||
| 
 | 
 | ||||||
| @ -31,7 +31,7 @@ class Brownian extends Behavior<IBrownianBehaviorParameter, IBrownianBehaviorEve | |||||||
| 		minStrength: { type: "number", name: "$Min.Strength", defaultValue: 0, numberStep: .01, numberMin: 0 } | 		minStrength: { type: "number", name: "$Min.Strength", defaultValue: 0, numberStep: .01, numberMin: 0 } | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	public effect(individual: Individual, group: Group, model: Model, t: number): void { | 	public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
| 
 | 
 | ||||||
| 		const {maxFrequency, minFrequency, maxStrength, minStrength} = this.parameter; | 		const {maxFrequency, minFrequency, maxStrength, minStrength} = this.parameter; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								source/Behavior/ContactAssimilate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								source/Behavior/ContactAssimilate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | 
 | ||||||
|  | type IContactAssimilateBehaviorParameter = { | ||||||
|  |     target: "CLG", | ||||||
|  |     assimilate: "CG", | ||||||
|  | 	success: "number", | ||||||
|  |     range: "number" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IContactAssimilateBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class ContactAssimilate extends Behavior<IContactAssimilateBehaviorParameter, IContactAssimilateBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "ContactAssimilate"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "SyncStatus"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  |     public override category: string = "$Interactive"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  | 		target: { type: "CLG", name: "$Target" }, | ||||||
|  |         assimilate: { type: "CG", name: "$Assimilate" }, | ||||||
|  |         range: { type: "number", name: "$Range", defaultValue: .05, numberMin: 0, numberStep: .01 }, | ||||||
|  |         success: { type: "number", name: "$Success", defaultValue: 90, numberMin: 0, numberMax: 100, numberStep: 5 } | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  |     public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
|  | 
 | ||||||
|  |         let assimilateGroup = this.parameter.assimilate.objects; | ||||||
|  |         if (!assimilateGroup) return; | ||||||
|  | 
 | ||||||
|  |         for (let i = 0; i < this.parameter.target.objects.length; i++) { | ||||||
|  | 			const targetGroup = this.parameter.target.objects[i]; | ||||||
|  | 
 | ||||||
|  | 			targetGroup.individuals.forEach((targetIndividual) => { | ||||||
|  | 
 | ||||||
|  | 				// 排除自己
 | ||||||
|  | 				if (targetIndividual === individual) return; | ||||||
|  | 				let dis = targetIndividual.distanceTo(individual); | ||||||
|  | 
 | ||||||
|  |                 // 进入同化范围
 | ||||||
|  | 				if (dis <= this.parameter.range) { | ||||||
|  | 					 | ||||||
|  |                     // 成功判定
 | ||||||
|  |                     if (Math.random() * 100 < this.parameter.success) { | ||||||
|  |                         targetIndividual.transfer(assimilateGroup!); | ||||||
|  |                     } | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 	public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "接触同化", | ||||||
|  |             "EN_US": "Contact Assimilate" | ||||||
|  |         }, | ||||||
|  | 		"$Target": { | ||||||
|  | 			"ZH_CN": "从哪个群...", | ||||||
|  | 			"EN_US": "From group..." | ||||||
|  | 		}, | ||||||
|  |         "$Assimilate": { | ||||||
|  |             "ZH_CN": "到哪个群...", | ||||||
|  | 			"EN_US": "To group..." | ||||||
|  |         }, | ||||||
|  |         "$Range": { | ||||||
|  |             "ZH_CN": "同化范围 (m)", | ||||||
|  | 			"EN_US": "Assimilate range (m)" | ||||||
|  |         }, | ||||||
|  | 		"$Success": { | ||||||
|  | 			"ZH_CN": "成功率 (%)", | ||||||
|  | 			"EN_US": "Success rate (%)" | ||||||
|  | 		}, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "将进入同化范围内的个体同化至另一个群", | ||||||
|  |             "EN_US": "Assimilate individuals who enter the assimilation range to another group" | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { ContactAssimilate }; | ||||||
							
								
								
									
										79
									
								
								source/Behavior/ContactAttacking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								source/Behavior/ContactAttacking.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | 
 | ||||||
|  | type IContactAttackingBehaviorParameter = { | ||||||
|  |     target: "CLG", | ||||||
|  | 	success: "number", | ||||||
|  |     range: "number" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IContactAttackingBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class ContactAttacking extends Behavior<IContactAttackingBehaviorParameter, IContactAttackingBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "ContactAttacking"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "DefenderTVM"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  |     public override category: string = "$Interactive"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  | 		target: { type: "CLG", name: "$Target" }, | ||||||
|  |         range: { type: "number", name: "$Range", defaultValue: .05, numberMin: 0, numberStep: .01 }, | ||||||
|  |         success: { type: "number", name: "$Success", defaultValue: 90, numberMin: 0, numberMax: 100, numberStep: 5 } | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  |     public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
|  | 
 | ||||||
|  |         for (let i = 0; i < this.parameter.target.objects.length; i++) { | ||||||
|  | 			const targetGroup = this.parameter.target.objects[i]; | ||||||
|  | 
 | ||||||
|  | 			targetGroup.individuals.forEach((targetIndividual) => { | ||||||
|  | 
 | ||||||
|  | 				// 排除自己
 | ||||||
|  | 				if (targetIndividual === individual) return; | ||||||
|  | 				let dis = targetIndividual.distanceTo(individual); | ||||||
|  | 
 | ||||||
|  |                 // 进入攻击范围
 | ||||||
|  | 				if (dis <= this.parameter.range) { | ||||||
|  | 					 | ||||||
|  |                     // 成功判定
 | ||||||
|  |                     if (Math.random() * 100 < this.parameter.success) { | ||||||
|  |                         targetIndividual.die(); | ||||||
|  |                     } | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 	public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "接触攻击", | ||||||
|  |             "EN_US": "Contact Attacking" | ||||||
|  |         }, | ||||||
|  | 		"$Target": { | ||||||
|  | 			"ZH_CN": "攻击目标", | ||||||
|  | 			"EN_US": "Attacking target" | ||||||
|  | 		}, | ||||||
|  |         "$Range": { | ||||||
|  |             "ZH_CN": "攻击范围 (m)", | ||||||
|  | 			"EN_US": "Attacking range (m)" | ||||||
|  |         }, | ||||||
|  | 		"$Success": { | ||||||
|  | 			"ZH_CN": "成功率 (%)", | ||||||
|  | 			"EN_US": "Success rate (%)" | ||||||
|  | 		}, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "攻击进入共进范围的目标群个体", | ||||||
|  |             "EN_US": "Attack the target group and individual entering the range" | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { ContactAttacking }; | ||||||
							
								
								
									
										96
									
								
								source/Behavior/DelayAssimilate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								source/Behavior/DelayAssimilate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,96 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | 
 | ||||||
|  | type IDelayAssimilateBehaviorParameter = { | ||||||
|  |     target: "CG", | ||||||
|  |     maxDelay: "number", | ||||||
|  | 	minDelay: "number", | ||||||
|  | 	success: "number" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IDelayAssimilateBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class DelayAssimilate extends Behavior<IDelayAssimilateBehaviorParameter, IDelayAssimilateBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "DelayAssimilate"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "FunctionalManagerDashboard"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  | 	public override category: string = "$Initiative"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  |         target: { type: "CG", name: "$Target" }, | ||||||
|  | 		maxDelay: { type: "number", name: "$Max.Delay", defaultValue: 20, numberStep: 1, numberMin: 0 }, | ||||||
|  | 		minDelay: { type: "number", name: "$Min.Delay", defaultValue: 5, numberStep: 1, numberMin: 0 }, | ||||||
|  | 		success: { type: "number", name: "$Success", defaultValue: 90, numberStep: 5, numberMin: 0 } | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
|  | 
 | ||||||
|  |         let assimilateGroup = this.parameter.target.objects; | ||||||
|  |         if (!assimilateGroup) return; | ||||||
|  |          | ||||||
|  |         const {maxDelay, minDelay, success} = this.parameter; | ||||||
|  | 
 | ||||||
|  | 		let nextTime = individual.getData("DelayAssimilate.nextTime") ??  | ||||||
|  | 		minDelay +  Math.random() * (maxDelay - minDelay); | ||||||
|  | 		let currentTime = individual.getData("DelayAssimilate.currentTime") ?? 0; | ||||||
|  | 		 | ||||||
|  | 		currentTime += t; | ||||||
|  | 		if (currentTime > nextTime) { | ||||||
|  | 		 | ||||||
|  |             // 成功判定
 | ||||||
|  |             if (Math.random() * 100 < success) { | ||||||
|  |                 individual.transfer(assimilateGroup); | ||||||
|  |                 nextTime = undefined; | ||||||
|  |                 currentTime = undefined; | ||||||
|  |             } else { | ||||||
|  | 
 | ||||||
|  |                 nextTime = minDelay +  Math.random() * (maxDelay - minDelay); | ||||||
|  | 			    currentTime = 0; | ||||||
|  |             } | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		individual.setData("DelayAssimilate.nextTime", nextTime); | ||||||
|  | 		individual.setData("DelayAssimilate.currentTime", currentTime); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "延迟同化", | ||||||
|  |             "EN_US": "Delayed assimilation" | ||||||
|  |         }, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "随着时间的推移,个体逐渐向另一个群同化。", | ||||||
|  |             "EN_US": "Over time, individuals gradually assimilate to another group." | ||||||
|  |         }, | ||||||
|  |         "$Target": { | ||||||
|  |             "ZH_CN": "同化目标", | ||||||
|  |             "EN_US": "Assimilation target" | ||||||
|  |         }, | ||||||
|  | 		"$Max.Delay": { | ||||||
|  |             "ZH_CN": "最长时间", | ||||||
|  |             "EN_US": "Longest time" | ||||||
|  |         }, | ||||||
|  | 		"$Min.Delay": { | ||||||
|  |             "ZH_CN": "最短时间", | ||||||
|  |             "EN_US": "Shortest time" | ||||||
|  |         }, | ||||||
|  | 		"$Success": { | ||||||
|  |             "ZH_CN": "成功率", | ||||||
|  |             "EN_US": "Minimum strength" | ||||||
|  |         }, | ||||||
|  |         "$Initiative": { | ||||||
|  |             "ZH_CN": "主动", | ||||||
|  |             "EN_US": "Initiative" | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { DelayAssimilate }; | ||||||
| @ -3,22 +3,23 @@ import Group from "@Model/Group"; | |||||||
| import Individual from "@Model/Individual"; | import Individual from "@Model/Individual"; | ||||||
| import { Model } from "@Model/Model"; | import { Model } from "@Model/Model"; | ||||||
| 
 | 
 | ||||||
| type IDynamicsBehaviorParameter = { | type IPhysicsDynamicsBehaviorParameter = { | ||||||
|     mass: "number", |     mass: "number", | ||||||
| 	maxAcceleration: "number", | 	maxAcceleration: "number", | ||||||
| 	maxVelocity: "number", | 	maxVelocity: "number", | ||||||
| 	resistance: "number" | 	resistance: "number", | ||||||
|  | 	limit: "boolean" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type IDynamicsBehaviorEvent = {} | type IPhysicsDynamicsBehaviorEvent = {} | ||||||
| 
 | 
 | ||||||
| class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEvent> { | class PhysicsDynamics extends Behavior<IPhysicsDynamicsBehaviorParameter, IPhysicsDynamicsBehaviorEvent> { | ||||||
| 
 | 
 | ||||||
|     public override behaviorId: string = "Dynamics"; |     public override behaviorId: string = "PhysicsDynamics"; | ||||||
| 
 | 
 | ||||||
|     public override behaviorName: string = "$Title"; |     public override behaviorName: string = "$Title"; | ||||||
| 
 | 
 | ||||||
|     public override iconName: string = "Running"; |     public override iconName: string = "SliderHandleSize"; | ||||||
| 
 | 
 | ||||||
|     public override describe: string = "$Intro"; |     public override describe: string = "$Intro"; | ||||||
| 
 | 
 | ||||||
| @ -26,12 +27,19 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | |||||||
| 
 | 
 | ||||||
| 	public override parameterOption = { | 	public override parameterOption = { | ||||||
| 		mass: { name: "$Mass", type: "number", defaultValue: 1, numberStep: .01, numberMin: .001 }, | 		mass: { name: "$Mass", type: "number", defaultValue: 1, numberStep: .01, numberMin: .001 }, | ||||||
| 		maxAcceleration: { name: "$Max.Acceleration", type: "number", defaultValue: 5, numberStep: .1, numberMin: 0 }, | 		resistance: { name: "$Resistance", type: "number", defaultValue: 2.8, numberStep: .1, numberMin: 0 }, | ||||||
| 		maxVelocity: { name: "$Max.Velocity", type: "number", defaultValue: 10, numberStep: .1, numberMin: 0 }, | 		limit: { name: "$Limit", type: "boolean", defaultValue: true }, | ||||||
| 		resistance: { name: "$Resistance", type: "number", defaultValue: 0.5, numberStep: .1, numberMin: 0 } | 		maxAcceleration: { | ||||||
|  | 			name: "$Max.Acceleration", type: "number", defaultValue: 6.25,  | ||||||
|  | 			numberStep: .1, numberMin: 0, condition: { key: "limit", value: true } | ||||||
|  | 		}, | ||||||
|  | 		maxVelocity: { | ||||||
|  | 			name: "$Max.Velocity", type: "number", defaultValue: 12.5, | ||||||
|  | 			numberStep: .1, numberMin: 0, condition: { key: "limit", value: true } | ||||||
|  | 		}, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	public override finalEffect(individual: Individual, group: Group, model: Model, t: number): void { | 	public override finalEffect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
| 
 | 
 | ||||||
| 		// 计算当前速度
 | 		// 计算当前速度
 | ||||||
| 		const currentV = individual.vectorLength(individual.velocity); | 		const currentV = individual.vectorLength(individual.velocity); | ||||||
| @ -54,24 +62,28 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | |||||||
| 		individual.acceleration[2] = individual.force[2] / this.parameter.mass; | 		individual.acceleration[2] = individual.force[2] / this.parameter.mass; | ||||||
| 
 | 
 | ||||||
| 		// 加速度约束
 | 		// 加速度约束
 | ||||||
| 		const lengthA = individual.vectorLength(individual.acceleration); | 		if (this.parameter.limit) { | ||||||
| 		if (lengthA > this.parameter.maxAcceleration) { | 			const lengthA = individual.vectorLength(individual.acceleration); | ||||||
| 			individual.acceleration[0] = individual.acceleration[0] * this.parameter.maxAcceleration / lengthA; | 			if (lengthA > this.parameter.maxAcceleration) { | ||||||
| 			individual.acceleration[1] = individual.acceleration[1] * this.parameter.maxAcceleration / lengthA; | 				individual.acceleration[0] = individual.acceleration[0] * this.parameter.maxAcceleration / lengthA; | ||||||
| 			individual.acceleration[2] = individual.acceleration[2] * 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[0] = individual.velocity[0] + individual.acceleration[0] * t; | ||||||
| 		individual.velocity[1] = individual.velocity[1] + individual.acceleration[1] * t; | 		individual.velocity[1] = individual.velocity[1] + individual.acceleration[1] * t; | ||||||
| 		individual.velocity[2] = individual.velocity[2] + individual.acceleration[2] * t; | 		individual.velocity[2] = individual.velocity[2] + individual.acceleration[2] * t; | ||||||
| 
 | 
 | ||||||
| 		// 速度约束
 | 		// 速度约束
 | ||||||
| 		const lengthV = individual.vectorLength(individual.velocity); | 		if (this.parameter.limit) { | ||||||
| 		if (lengthV > this.parameter.maxVelocity) { | 			const lengthV = individual.vectorLength(individual.velocity); | ||||||
| 			individual.velocity[0] = individual.velocity[0] * this.parameter.maxVelocity / lengthV; | 			if (lengthV > this.parameter.maxVelocity) { | ||||||
| 			individual.velocity[1] = individual.velocity[1] * this.parameter.maxVelocity / lengthV; | 				individual.velocity[0] = individual.velocity[0] * this.parameter.maxVelocity / lengthV; | ||||||
| 			individual.velocity[2] = individual.velocity[2] * this.parameter.maxVelocity / lengthV; | 				individual.velocity[1] = individual.velocity[1] * this.parameter.maxVelocity / lengthV; | ||||||
|  | 				individual.velocity[2] = individual.velocity[2] * this.parameter.maxVelocity / lengthV; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// 应用速度
 | 		// 应用速度
 | ||||||
| @ -87,13 +99,17 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | |||||||
| 	 | 	 | ||||||
|     public override terms: Record<string, Record<string, string>> = { |     public override terms: Record<string, Record<string, string>> = { | ||||||
|         "$Title": { |         "$Title": { | ||||||
|             "ZH_CN": "动力学", |             "ZH_CN": "物理动力学", | ||||||
|             "EN_US": "Dynamics" |             "EN_US": "Physics dynamics" | ||||||
|         }, |         }, | ||||||
|         "$Intro": { |         "$Intro": { | ||||||
|             "ZH_CN": "一切可以运动物体的必要行为,执行物理法则。", |             "ZH_CN": "一切按照物理规则运动物体的行为, 按照牛顿经典物理运动公式执行。", | ||||||
|             "EN_US": "All necessary behaviors that can move objects and implement the laws of physics." |             "EN_US": "The behavior of all moving objects according to physical rules is carried out according to Newton's classical physical motion formula." | ||||||
|         }, |         }, | ||||||
|  | 		"$Limit": { | ||||||
|  | 			"ZH_CN": "开启限制", | ||||||
|  |             "EN_US": "Enable limit" | ||||||
|  | 		}, | ||||||
| 		"$Mass": { | 		"$Mass": { | ||||||
| 			"ZH_CN": "质量 (Kg)", | 			"ZH_CN": "质量 (Kg)", | ||||||
|             "EN_US": "Mass (Kg)" |             "EN_US": "Mass (Kg)" | ||||||
| @ -117,4 +133,4 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export { Dynamics }; | export { PhysicsDynamics }; | ||||||
| @ -14,6 +14,8 @@ type ITemplateBehaviorParameter = { | |||||||
|     testLR: "LR"; |     testLR: "LR"; | ||||||
|     testLG: "LG"; |     testLG: "LG"; | ||||||
|     testVec: "vec"; |     testVec: "vec"; | ||||||
|  |     testCG: "CG", | ||||||
|  |     testCLG: "CLG", | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type ITemplateBehaviorEvent = {} | type ITemplateBehaviorEvent = {} | ||||||
| @ -42,10 +44,12 @@ class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEve | |||||||
|         testG: { name: "$Test", type: "G" }, |         testG: { name: "$Test", type: "G" }, | ||||||
|         testLR: { name: "$Test", type: "LR" }, |         testLR: { name: "$Test", type: "LR" }, | ||||||
|         testLG: { name: "$Test", type: "LG" }, |         testLG: { name: "$Test", type: "LG" }, | ||||||
|  |         testCG: { name: "$Test", type: "CG" }, | ||||||
|  |         testCLG: { name: "$Test", type: "CLG" }, | ||||||
|         testVec: { name: "$Test", type: "vec", defaultValue: [1, 2, 3], numberMax: 10, numberMin: 0, numberStep: 1 } |         testVec: { name: "$Test", type: "vec", defaultValue: [1, 2, 3], numberMax: 10, numberMin: 0, numberStep: 1 } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     public effect(individual: Individual, group: Group, model: Model, t: number): void { |     public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
|          |          | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										157
									
								
								source/Behavior/Tracking.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								source/Behavior/Tracking.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | |||||||
|  | import { Behavior } from "@Model/Behavior"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
|  | import { Individual } from "@Model/Individual"; | ||||||
|  | import { Model } from "@Model/Model"; | ||||||
|  | 
 | ||||||
|  | type ITrackingBehaviorParameter = { | ||||||
|  |     target: "CLG", | ||||||
|  | 	strength: "number", | ||||||
|  |     range: "number", | ||||||
|  |     lock: "boolean" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type ITrackingBehaviorEvent = {} | ||||||
|  | 
 | ||||||
|  | class Tracking extends Behavior<ITrackingBehaviorParameter, ITrackingBehaviorEvent> { | ||||||
|  | 
 | ||||||
|  |     public override behaviorId: string = "Tracking"; | ||||||
|  | 
 | ||||||
|  |     public override behaviorName: string = "$Title"; | ||||||
|  | 
 | ||||||
|  |     public override iconName: string = "Bullseye"; | ||||||
|  | 
 | ||||||
|  |     public override describe: string = "$Intro"; | ||||||
|  | 
 | ||||||
|  |     public override category: string = "$Interactive"; | ||||||
|  | 
 | ||||||
|  | 	public override parameterOption = { | ||||||
|  | 		target: { type: "CLG", name: "$Target" }, | ||||||
|  |         lock: { type: "boolean", name: "$Lock", defaultValue: false }, | ||||||
|  |         range: { type: "number", name: "$Range", defaultValue: 4, numberMin: 0, numberStep: .1 }, | ||||||
|  | 		strength: { type: "number", name: "$Strength", defaultValue: 1, numberMin: 0, numberStep: .1 } | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  |     private target: Individual | undefined = undefined; | ||||||
|  |     private currentDistant: number = Infinity; | ||||||
|  | 
 | ||||||
|  |     private searchTarget(individual: Individual) { | ||||||
|  | 
 | ||||||
|  | 		for (let i = 0; i < this.parameter.target.objects.length; i++) { | ||||||
|  | 			const targetGroup = this.parameter.target.objects[i]; | ||||||
|  | 
 | ||||||
|  | 			targetGroup.individuals.forEach((targetIndividual) => { | ||||||
|  | 
 | ||||||
|  | 				// 排除自己
 | ||||||
|  | 				if (targetIndividual === individual) return; | ||||||
|  | 				let dis = targetIndividual.distanceTo(individual); | ||||||
|  | 
 | ||||||
|  | 				if (dis < this.currentDistant && dis <= this.parameter.range) { | ||||||
|  | 					this.target = targetIndividual; | ||||||
|  | 					this.currentDistant = dis; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private clearTarget() { | ||||||
|  |         this.target = undefined as Individual | undefined; | ||||||
|  |         this.currentDistant = Infinity; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public effect = (individual: Individual, group: Group, model: Model, t: number): void => { | ||||||
|  | 
 | ||||||
|  |         this.clearTarget(); | ||||||
|  | 
 | ||||||
|  |         if (this.parameter.lock) { | ||||||
|  | 
 | ||||||
|  |             let isValidTarget = false; | ||||||
|  |             this.target = individual.getData("Tracking.lock.target"); | ||||||
|  | 
 | ||||||
|  |             if (this.target) { | ||||||
|  | 
 | ||||||
|  |                 // 校验目标所在的群是否仍是目标
 | ||||||
|  |                 let isInTarget = false; | ||||||
|  |                 for (let i = 0; i < this.parameter.target.objects.length; i++) { | ||||||
|  |                     if (this.parameter.target.objects[i].equal(this.target.group)) { | ||||||
|  |                         isInTarget = true; | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // 如果还在目标范围内,校验距离
 | ||||||
|  |                 if (isInTarget) { | ||||||
|  |                     let dis = individual.distanceTo(this.target); | ||||||
|  | 
 | ||||||
|  |                     // 校验成功
 | ||||||
|  |                     if (dis <= this.parameter.range) { | ||||||
|  |                         this.currentDistant = dis; | ||||||
|  |                         isValidTarget = true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // 如果目标无效,尝试搜索新的目标
 | ||||||
|  |             if (!isValidTarget) { | ||||||
|  | 
 | ||||||
|  |                 this.clearTarget(); | ||||||
|  |                 this.searchTarget(individual); | ||||||
|  | 
 | ||||||
|  |                 // 如果成功搜索,缓存目标
 | ||||||
|  |                 if (this.target && this.currentDistant && this.currentDistant !== Infinity) { | ||||||
|  |                     individual.setData("Tracking.lock.target", this.target); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // 搜索失败,清除目标
 | ||||||
|  |                 else { | ||||||
|  |                     individual.setData("Tracking.lock.target", undefined); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         else { | ||||||
|  |             this.searchTarget(individual); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 		if (this.target && this.currentDistant && this.currentDistant !== Infinity) { | ||||||
|  | 			individual.applyForce( | ||||||
|  | 				(this.target.position[0] - individual.position[0]) * this.parameter.strength / this.currentDistant, | ||||||
|  | 				(this.target.position[1] - individual.position[1]) * this.parameter.strength / this.currentDistant, | ||||||
|  | 				(this.target.position[2] - individual.position[2]) * this.parameter.strength / this.currentDistant | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 	public override terms: Record<string, Record<string, string>> = { | ||||||
|  |         "$Title": { | ||||||
|  |             "ZH_CN": "追踪", | ||||||
|  |             "EN_US": "Tracking" | ||||||
|  |         }, | ||||||
|  | 		"$Target": { | ||||||
|  | 			"ZH_CN": "追踪目标", | ||||||
|  | 			"EN_US": "Tracking target" | ||||||
|  | 		}, | ||||||
|  |         "$Lock": { | ||||||
|  |             "ZH_CN": "追踪锁定", | ||||||
|  | 			"EN_US": "Tracking lock" | ||||||
|  |         }, | ||||||
|  |         "$Range": { | ||||||
|  |             "ZH_CN": "追踪范围 (m)", | ||||||
|  | 			"EN_US": "Tracking range (m)" | ||||||
|  |         }, | ||||||
|  | 		"$Strength": { | ||||||
|  | 			"ZH_CN": "追踪强度系数", | ||||||
|  | 			"EN_US": "Tracking intensity coefficient" | ||||||
|  | 		}, | ||||||
|  |         "$Intro": { | ||||||
|  |             "ZH_CN": "个体将主动向最近的目标群个体发起追踪", | ||||||
|  |             "EN_US": "The individual will actively initiate tracking to the nearest target group individual" | ||||||
|  |         }, | ||||||
|  | 		"$Interactive": { | ||||||
|  | 			"ZH_CN": "交互", | ||||||
|  |             "EN_US": "Interactive" | ||||||
|  | 		} | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export { Tracking }; | ||||||
| @ -94,8 +94,8 @@ div.behavior-list { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             div.behavior-content-view { |             div.behavior-content-view { | ||||||
|                 width: calc( 100% - 50px ); |                 width: calc( 100% - 55px ); | ||||||
|                 padding-right: 5px; |                 padding-right: 10px; | ||||||
|                 max-width: 125px; |                 max-width: 125px; | ||||||
|                 height: $behavior-item-height; |                 height: $behavior-item-height; | ||||||
|                 display: flex; |                 display: flex; | ||||||
|  | |||||||
| @ -119,6 +119,10 @@ div.app-container { | |||||||
| 		flex-shrink: 1; | 		flex-shrink: 1; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	div.app-panel.hide-scrollbar { | ||||||
|  | 		overflow: hidden; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	div.app-panel.hide-scrollbar::-webkit-scrollbar { | 	div.app-panel.hide-scrollbar::-webkit-scrollbar { | ||||||
| 		width : 0;  /*高宽分别对应横竖滚动条的尺寸*/ | 		width : 0;  /*高宽分别对应横竖滚动条的尺寸*/ | ||||||
| 		height: 0; | 		height: 0; | ||||||
|  | |||||||
| @ -59,6 +59,10 @@ class ObjectPicker extends Component<IObjectPickerProps & IMixinStatusProps, IOb | |||||||
|                     option.push(this.props.status.model.objectPool[j]); |                     option.push(this.props.status.model.objectPool[j]); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (this.props.type.includes("C")) { | ||||||
|  |                 option.push(this.props.status.model.currentGroupLabel); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         return option; |         return option; | ||||||
|  | |||||||
| @ -32,9 +32,15 @@ class Parameter<P extends IParameter> extends Component<IParameterProps<P> & IMi | |||||||
|     private renderParameter<K extends keyof P> |     private renderParameter<K extends keyof P> | ||||||
|     (key: K, option: IParameterOptionItem<P[K]>, value: IParamValue<P[K]>): ReactNode { |     (key: K, option: IParameterOptionItem<P[K]>, value: IParamValue<P[K]>): ReactNode { | ||||||
| 
 | 
 | ||||||
|         const language = this.props.setting?.language ?? "EN_US"; |  | ||||||
|         const indexKey = `${this.props.key}-${key}`; |         const indexKey = `${this.props.key}-${key}`; | ||||||
|  | 
 | ||||||
|  |         // 条件检测
 | ||||||
|  |         if (option.condition && this.props.value[option.condition.key] !==  option.condition.value) { | ||||||
|  |             return <Fragment key={indexKey}/>; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const type = option.type; |         const type = option.type; | ||||||
|  |         const language = this.props.setting?.language ?? "EN_US";         | ||||||
|         let keyI18n: string, keyI18nOption: Record<string, string> | undefined; |         let keyI18n: string, keyI18nOption: Record<string, string> | undefined; | ||||||
| 
 | 
 | ||||||
|         // Custom I18N
 |         // Custom I18N
 | ||||||
|  | |||||||
| @ -51,16 +51,25 @@ function getObjectDisplayInfo(item?: IPickerListItem): IDisplayInfo { | |||||||
| 	if (item instanceof Label) { | 	if (item instanceof Label) { | ||||||
| 
 | 
 | ||||||
| 		if (item.isBuildIn) { | 		if (item.isBuildIn) { | ||||||
|  | 
 | ||||||
|             internal = true; |             internal = true; | ||||||
|             allLabel = true; |             allLabel = true; | ||||||
|             color = "transparent"; |             color = "transparent"; | ||||||
|  | 
 | ||||||
|             if (item.id === "AllRange") { |             if (item.id === "AllRange") { | ||||||
|                 icon = "ProductList"; |                 icon = "ProductList"; | ||||||
|                 name = "Build.In.Label.Name.All.Range"; |                 name = "Build.In.Label.Name.All.Range"; | ||||||
|             } else if (item.id === "AllGroup") { |             } | ||||||
|  | 			 | ||||||
|  | 			else if (item.id === "AllGroup") { | ||||||
|                 icon = "SizeLegacy"; |                 icon = "SizeLegacy"; | ||||||
|                 name = "Build.In.Label.Name.All.Group"; |                 name = "Build.In.Label.Name.All.Group"; | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  | 			else if (item.id === "CurrentGroupLabel") { | ||||||
|  | 				icon = "TriangleShape"; | ||||||
|  |                 name = "Build.In.Label.Name.Current.Group"; | ||||||
|  | 			} | ||||||
|         }  |         }  | ||||||
|          |          | ||||||
|         else { |         else { | ||||||
|  | |||||||
| @ -57,8 +57,11 @@ const EN_US = { | |||||||
|     "Popup.Action.No": "Cancel", |     "Popup.Action.No": "Cancel", | ||||||
|     "Popup.Action.Objects.Confirm.Title": "Confirm Delete", |     "Popup.Action.Objects.Confirm.Title": "Confirm Delete", | ||||||
|     "Popup.Action.Objects.Confirm.Delete": "Delete", |     "Popup.Action.Objects.Confirm.Delete": "Delete", | ||||||
|  |     "Popup.Action.Objects.Confirm.Restore.Title": "Confirm Restore", | ||||||
|  |     "Popup.Action.Objects.Confirm.Restore": "Restore", | ||||||
|     "Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.", |     "Popup.Delete.Objects.Confirm": "Are you sure you want to delete this object(s)? The object is deleted and cannot be recalled.", | ||||||
|     "Popup.Delete.Behavior.Confirm": "Are you sure you want to delete this behavior? The behavior is deleted and cannot be recalled.", |     "Popup.Delete.Behavior.Confirm": "Are you sure you want to delete this behavior? The behavior is deleted and 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.Add.Behavior.Title": "Add behavior", |     "Popup.Add.Behavior.Title": "Add behavior", | ||||||
|     "Popup.Add.Behavior.Action.Add": "Add all select behavior", |     "Popup.Add.Behavior.Action.Add": "Add all select behavior", | ||||||
| @ -68,6 +71,7 @@ const EN_US = { | |||||||
|     "Popup.Behavior.Info.Confirm": "OK, I know it", |     "Popup.Behavior.Info.Confirm": "OK, I know it", | ||||||
|     "Build.In.Label.Name.All.Group": "All group", |     "Build.In.Label.Name.All.Group": "All group", | ||||||
|     "Build.In.Label.Name.All.Range": "All range", |     "Build.In.Label.Name.All.Range": "All range", | ||||||
|  |     "Build.In.Label.Name.Current.Group": "Current group", | ||||||
|     "Common.Search.Placeholder": "Search in here...", |     "Common.Search.Placeholder": "Search in here...", | ||||||
|     "Common.No.Data": "No Data", |     "Common.No.Data": "No Data", | ||||||
|     "Common.No.Unknown.Error": "Unknown error", |     "Common.No.Unknown.Error": "Unknown error", | ||||||
| @ -107,6 +111,7 @@ const EN_US = { | |||||||
|     "Common.Attr.Key.Generation.Error.Invalid.Label": "The specified label has expired", |     "Common.Attr.Key.Generation.Error.Invalid.Label": "The specified label has expired", | ||||||
|     "Common.Attr.Key.Kill.Random": "Random kill", |     "Common.Attr.Key.Kill.Random": "Random kill", | ||||||
|     "Common.Attr.Key.Kill.Count": "Kill count", |     "Common.Attr.Key.Kill.Count": "Kill count", | ||||||
|  |     "Common.Attr.Key.Behavior.Restore": "Restore default parameters", | ||||||
|     "Common.Render.Attr.Key.Display.Shape": "Display Shape", |     "Common.Render.Attr.Key.Display.Shape": "Display Shape", | ||||||
|     "Common.Render.Attr.Key.Display.Shape.Square": "Square", |     "Common.Render.Attr.Key.Display.Shape.Square": "Square", | ||||||
|     "Common.Render.Attr.Key.Display.Shape.Hollow.Square": "Hollow square", |     "Common.Render.Attr.Key.Display.Shape.Hollow.Square": "Hollow square", | ||||||
|  | |||||||
| @ -57,17 +57,21 @@ const ZH_CN = { | |||||||
|     "Popup.Action.No": "取消", |     "Popup.Action.No": "取消", | ||||||
|     "Popup.Action.Objects.Confirm.Title": "删除确认", |     "Popup.Action.Objects.Confirm.Title": "删除确认", | ||||||
|     "Popup.Action.Objects.Confirm.Delete": "删除", |     "Popup.Action.Objects.Confirm.Delete": "删除", | ||||||
|  |     "Popup.Action.Objects.Confirm.Restore.Title": "重置确认", | ||||||
|  |     "Popup.Action.Objects.Confirm.Restore": "重置", | ||||||
|     "Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。", |     "Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。", | ||||||
|     "Popup.Delete.Behavior.Confirm": "你确定要删除这个行为吗?行为被删除将无法撤回。", |     "Popup.Delete.Behavior.Confirm": "你确定要删除这个行为吗?行为被删除将无法撤回。", | ||||||
|  |     "Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。", | ||||||
|     "Popup.Setting.Title": "首选项设置", |     "Popup.Setting.Title": "首选项设置", | ||||||
|     "Popup.Add.Behavior.Title": "添加行为", |     "Popup.Add.Behavior.Title": "添加行为", | ||||||
|     "Popup.Add.Behavior.Action.Add": "添加全部选中行为", |     "Popup.Add.Behavior.Action.Add": "添加全部选中行为", | ||||||
|     "Popup.Add.Behavior.Select.Counter": "找不到名为 \"{name}\" 的行为", |     "Popup.Add.Behavior.Select.Counter": "已选择 {count} 个行为", | ||||||
|     "Popup.Add.Behavior.Select.Nodata": "Could not find behavior named \"{name}\"", |     "Popup.Add.Behavior.Select.Nodata": "找不到名为 \"{name}\" 的行为", | ||||||
|     "Popup.Behavior.Info.Title": "行为详情: {behavior}", |     "Popup.Behavior.Info.Title": "行为详情: {behavior}", | ||||||
|     "Popup.Behavior.Info.Confirm": "好的, 我知道了", |     "Popup.Behavior.Info.Confirm": "好的, 我知道了", | ||||||
|     "Build.In.Label.Name.All.Group": "全部群", |     "Build.In.Label.Name.All.Group": "全部群", | ||||||
|     "Build.In.Label.Name.All.Range": "全部范围", |     "Build.In.Label.Name.All.Range": "全部范围", | ||||||
|  |     "Build.In.Label.Name.Current.Group": "当前群", | ||||||
|     "Common.Search.Placeholder": "在此处搜索...", |     "Common.Search.Placeholder": "在此处搜索...", | ||||||
|     "Common.No.Data": "暂无数据", |     "Common.No.Data": "暂无数据", | ||||||
|     "Common.No.Unknown.Error": "未知错误", |     "Common.No.Unknown.Error": "未知错误", | ||||||
| @ -107,6 +111,7 @@ const ZH_CN = { | |||||||
|     "Common.Attr.Key.Generation.Error.Invalid.Label": "指定的标签已失效", |     "Common.Attr.Key.Generation.Error.Invalid.Label": "指定的标签已失效", | ||||||
|     "Common.Attr.Key.Kill.Random": "随机消除", |     "Common.Attr.Key.Kill.Random": "随机消除", | ||||||
|     "Common.Attr.Key.Kill.Count": "消除数量", |     "Common.Attr.Key.Kill.Count": "消除数量", | ||||||
|  |     "Common.Attr.Key.Behavior.Restore": "还原默认参数", | ||||||
|     "Common.Render.Attr.Key.Display.Shape": "显示形状", |     "Common.Render.Attr.Key.Display.Shape": "显示形状", | ||||||
|     "Common.Render.Attr.Key.Display.Shape.Square": "方形", |     "Common.Render.Attr.Key.Display.Shape.Square": "方形", | ||||||
|     "Common.Render.Attr.Key.Display.Shape.Hollow.Square": "空心方形", |     "Common.Render.Attr.Key.Display.Shape.Hollow.Square": "空心方形", | ||||||
|  | |||||||
| @ -158,6 +158,11 @@ class Behavior< | |||||||
|      */ |      */ | ||||||
|     public parameter: IParameterValue<P>; |     public parameter: IParameterValue<P>; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 指定当前群的 Key | ||||||
|  |      */ | ||||||
|  |     public currentGroupKey: Array<keyof P> = []; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 对象参数列表 |      * 对象参数列表 | ||||||
|      */ |      */ | ||||||
| @ -222,7 +227,7 @@ class Behavior< | |||||||
|      * @param model 模型 |      * @param model 模型 | ||||||
|      * @param t 经过时间 |      * @param t 经过时间 | ||||||
|      */ |      */ | ||||||
|     public effect(individual: Individual, group: Group, model: Model, t: number): void {}; |     public effect?: (individual: Individual, group: Group, model: Model, t: number) => void; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 作用影响于个体 |      * 作用影响于个体 | ||||||
| @ -231,7 +236,7 @@ class Behavior< | |||||||
|      * @param model 模型 |      * @param model 模型 | ||||||
|      * @param t 经过时间 |      * @param t 经过时间 | ||||||
|      */ |      */ | ||||||
|     public afterEffect(individual: Individual, group: Group, model: Model, t: number): void {}; |     public afterEffect?: (individual: Individual, group: Group, model: Model, t: number) => void; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 全部影响作用后 |      * 全部影响作用后 | ||||||
| @ -240,7 +245,7 @@ class Behavior< | |||||||
|      * @param model 模型 |      * @param model 模型 | ||||||
|      * @param t 经过时间 |      * @param t 经过时间 | ||||||
|      */ |      */ | ||||||
|     public finalEffect(individual: Individual, group: Group, model: Model, t: number): void {}; |     public finalEffect?: (individual: Individual, group: Group, model: Model, t: number) => void; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -61,8 +61,8 @@ class CtrlObject extends LabelObject { | |||||||
|     /** |     /** | ||||||
|      * 判断是否为相同对象 |      * 判断是否为相同对象 | ||||||
|      */ |      */ | ||||||
|     public equal(obj: CtrlObject): boolean { |     public equal(obj?: CtrlObject): boolean { | ||||||
|         return this === obj || this.id === obj.id; |         return this === obj || this.id === obj?.id; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import { Individual } from "@Model/Individual"; | import { Individual } from "@Model/Individual"; | ||||||
| import { CtrlObject } from "@Model/CtrlObject"; | import { CtrlObject } from "@Model/CtrlObject"; | ||||||
| import type { Behavior } from "@Model/Behavior";  | import type { Behavior, IAnyBehavior } from "@Model/Behavior";  | ||||||
| import { Label } from "@Model/Label"; | import { Label } from "@Model/Label"; | ||||||
| import { Range } from "@Model/Range"; | import { Range } from "@Model/Range"; | ||||||
| import { Model, ObjectID } from "@Model/Model"; | import { Model, ObjectID } from "@Model/Model"; | ||||||
| @ -39,7 +39,7 @@ class Group extends CtrlObject { | |||||||
|     /** |     /** | ||||||
|      * 生成个数 |      * 生成个数 | ||||||
|      */ |      */ | ||||||
|     public genCount: number = 1; |     public genCount: number = 100; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 生成错误信息 |      * 生成错误信息 | ||||||
| @ -54,7 +54,7 @@ class Group extends CtrlObject { | |||||||
|     /** |     /** | ||||||
|      * 删除个数 |      * 删除个数 | ||||||
|      */ |      */ | ||||||
|     public killCount: number = 1; |     public killCount: number = 100; | ||||||
| 
 | 
 | ||||||
|     private genInSingleRange(count: number, range: Range) { |     private genInSingleRange(count: number, range: Range) { | ||||||
|         for (let i = 0; i < count; i++) { |         for (let i = 0; i < count; i++) { | ||||||
| @ -272,9 +272,11 @@ class Group extends CtrlObject { | |||||||
|     public remove(individual: Individual[] | Individual): this { |     public remove(individual: Individual[] | Individual): this { | ||||||
|         if (Array.isArray(individual)) { |         if (Array.isArray(individual)) { | ||||||
|             for (let i = 0; i < individual.length; i++) { |             for (let i = 0; i < individual.length; i++) { | ||||||
|  |                 individual[i].group = undefined; | ||||||
|                 this.individuals.delete(individual[i]); |                 this.individuals.delete(individual[i]); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|  |             individual.group = undefined; | ||||||
|             this.individuals.delete(individual); |             this.individuals.delete(individual); | ||||||
|         } |         } | ||||||
|         return this; |         return this; | ||||||
| @ -308,7 +310,7 @@ class Group extends CtrlObject { | |||||||
| 	/** | 	/** | ||||||
| 	 * 行为列表 | 	 * 行为列表 | ||||||
| 	 */ | 	 */ | ||||||
| 	public behaviors: Behavior[] = []; | 	public behaviors: IAnyBehavior[] = []; | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 添加行为 | 	 * 添加行为 | ||||||
| @ -358,15 +360,37 @@ class Group extends CtrlObject { | |||||||
| 	 * @param | 	 * @param | ||||||
|      */ |      */ | ||||||
| 	public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void { | 	public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void { | ||||||
| 		this.individuals.forEach((individual) => { | 
 | ||||||
| 			for(let j = 0; j < this.behaviors.length; j++) { |         for(let j = 0; j < this.behaviors.length; j++) { | ||||||
|                 if (this.behaviors[j].isDeleted()) { | 
 | ||||||
|                     continue; |             const behavior = this.behaviors[j]; | ||||||
|  |             if (behavior.isDeleted()) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const runnerFunction = behavior[effectType]; | ||||||
|  |             if (!runnerFunction) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             for (let k = 0; k < behavior.currentGroupKey.length; k++) { | ||||||
|  | 
 | ||||||
|  |                 let parameterCache = behavior.parameter[ | ||||||
|  |                     behavior.currentGroupKey[k] as string | ||||||
|  |                 ]; | ||||||
|  | 
 | ||||||
|  |                 if (Array.isArray(parameterCache?.objects)) { | ||||||
|  |                     parameterCache.objects = [this]; | ||||||
|  | 
 | ||||||
|                 } else { |                 } else { | ||||||
|                     this.behaviors[j][effectType](individual, this, this.model, t); |                     parameterCache.objects = this; | ||||||
|                 } |                 } | ||||||
| 			} |             } | ||||||
| 		}); | 
 | ||||||
|  |             this.individuals.forEach((individual) => { | ||||||
|  |                 runnerFunction(individual, this, this.model, t); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -88,7 +88,7 @@ class Individual { | |||||||
| 	/** | 	/** | ||||||
| 	 * 所属群组 | 	 * 所属群组 | ||||||
| 	 */ | 	 */ | ||||||
| 	public group: Group; | 	public group: Group | undefined; | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 初始化 | 	 * 初始化 | ||||||
| @ -97,11 +97,16 @@ class Individual { | |||||||
| 		this.group = group; | 		this.group = group; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |     public isDie(): boolean { | ||||||
|  |         return !!this.group; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 死亡 |      * 死亡 | ||||||
|      */ |      */ | ||||||
|     public die(): this { |     public die(): this { | ||||||
|         this.group.remove(this); |         this.group?.remove(this); | ||||||
|  |         this.group = undefined; | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -110,7 +115,7 @@ class Individual { | |||||||
|      * @param newGroup 新群体 |      * @param newGroup 新群体 | ||||||
|      */ |      */ | ||||||
|     public transfer(newGroup: Group): this { |     public transfer(newGroup: Group): this { | ||||||
|         this.group.remove(this); |         this.group?.remove(this); | ||||||
|         newGroup.add(this); |         newGroup.add(this); | ||||||
|         this.group = newGroup; |         this.group = newGroup; | ||||||
|         return this; |         return this; | ||||||
|  | |||||||
| @ -66,6 +66,11 @@ class Model extends Emitter<ModelEvent> { | |||||||
|      */ |      */ | ||||||
|     public allGroupLabel = new Label(this, "AllGroup").setBuildInLabel(); |     public allGroupLabel = new Label(this, "AllGroup").setBuildInLabel(); | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 内置标签-全部群标签 | ||||||
|  |      */ | ||||||
|  |     public currentGroupLabel = new Label(this, "CurrentGroupLabel").setBuildInLabel(); | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 添加标签 |      * 添加标签 | ||||||
|      */ |      */ | ||||||
| @ -223,6 +228,7 @@ class Model extends Emitter<ModelEvent> { | |||||||
|     public updateBehaviorParameter() { |     public updateBehaviorParameter() { | ||||||
|         for (let i = 0; i < this.behaviorPool.length; i++) { |         for (let i = 0; i < this.behaviorPool.length; i++) { | ||||||
|             const behavior = this.behaviorPool[i]; |             const behavior = this.behaviorPool[i]; | ||||||
|  |             behavior.currentGroupKey = []; | ||||||
|              |              | ||||||
|             for (let key in behavior.parameterOption) { |             for (let key in behavior.parameterOption) { | ||||||
|                 switch (behavior.parameterOption[key].type) { |                 switch (behavior.parameterOption[key].type) { | ||||||
| @ -236,11 +242,17 @@ class Model extends Emitter<ModelEvent> { | |||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
| 
 | 
 | ||||||
|  |                     case "CG": | ||||||
|                     case "G": |                     case "G": | ||||||
|                         const dataG: IParamValue<"G"> = behavior.parameter[key]; |                         const dataG: IParamValue<"CG"> = behavior.parameter[key]; | ||||||
|                         dataG.objects = undefined; |                         dataG.objects = undefined; | ||||||
|  | 
 | ||||||
|  |                         if (dataG.picker instanceof Label && dataG.picker.id === this.currentGroupLabel.id) { | ||||||
|  |                             behavior.currentGroupKey.push(key); | ||||||
|  |                             dataG.objects = undefined; | ||||||
|  |                         } | ||||||
|                          |                          | ||||||
|                         if (dataG.picker instanceof Group && !dataG.picker.isDeleted()) { |                         else if (dataG.picker instanceof Group && !dataG.picker.isDeleted()) { | ||||||
|                             dataG.objects = dataG.picker; |                             dataG.objects = dataG.picker; | ||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
| @ -260,8 +272,9 @@ class Model extends Emitter<ModelEvent> { | |||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
| 
 | 
 | ||||||
|  |                     case "CLG": | ||||||
|                     case "LG": |                     case "LG": | ||||||
|                         const dataLG: IParamValue<"LG"> = behavior.parameter[key]; |                         const dataLG: IParamValue<"CLG"> = behavior.parameter[key]; | ||||||
|                         dataLG.objects = []; |                         dataLG.objects = []; | ||||||
| 
 | 
 | ||||||
|                         if (dataLG.picker instanceof Group && !dataLG.picker.isDeleted()) { |                         if (dataLG.picker instanceof Group && !dataLG.picker.isDeleted()) { | ||||||
| @ -269,9 +282,16 @@ class Model extends Emitter<ModelEvent> { | |||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         if (dataLG.picker instanceof Label && !dataLG.picker.isDeleted()) { |                         if (dataLG.picker instanceof Label && !dataLG.picker.isDeleted()) { | ||||||
|                             dataLG.objects = this.getObjectByLabel(dataLG.picker).filter((obj) => { | 
 | ||||||
|                                 return obj instanceof Group; |                             if (dataLG.picker.id === this.currentGroupLabel.id) { | ||||||
|                             }) as any; |                                 behavior.currentGroupKey.push(key); | ||||||
|  |                                 dataLG.objects = []; | ||||||
|  | 
 | ||||||
|  |                             } else { | ||||||
|  |                                 dataLG.objects = this.getObjectByLabel(dataLG.picker).filter((obj) => { | ||||||
|  |                                     return obj instanceof Group; | ||||||
|  |                                 }) as any; | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                         break; |                         break; | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -22,6 +22,8 @@ type IMapObjectParamTypeKeyToType = { | |||||||
|     "G": IObjectParamCacheType<Group | undefined>; |     "G": IObjectParamCacheType<Group | undefined>; | ||||||
|     "LR": IObjectParamCacheType<Label | Range | undefined, Range[]>; |     "LR": IObjectParamCacheType<Label | Range | undefined, Range[]>; | ||||||
|     "LG": IObjectParamCacheType<Label | Group | undefined, Group[]>; |     "LG": IObjectParamCacheType<Label | Group | undefined, Group[]>; | ||||||
|  |     "CG": IObjectParamCacheType<Label | Group | undefined, Group | undefined>; | ||||||
|  |     "CLG": IObjectParamCacheType<Label | Group | undefined, Group[]>; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type IMapVectorParamTypeKeyToType = { | type IMapVectorParamTypeKeyToType = { | ||||||
| @ -41,7 +43,7 @@ type IParamValue<K extends IParamType> = AllMapType[K]; | |||||||
| /** | /** | ||||||
|  * 特殊对象类型判定 |  * 特殊对象类型判定 | ||||||
|  */ |  */ | ||||||
| const objectTypeListEnumSet = new Set<string>(["R", "G", "LR", "LG"]); | const objectTypeListEnumSet = new Set<string>(["R", "G", "LR", "LG", "CG", "CLG"]); | ||||||
| 
 | 
 | ||||||
|  /** |  /** | ||||||
|   * 对象断言表达式 |   * 对象断言表达式 | ||||||
| @ -113,6 +115,11 @@ interface IParameterOptionItem<T extends IParamType = IParamType> { | |||||||
|      */ |      */ | ||||||
|     colorNormal?: boolean; |     colorNormal?: boolean; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 显示条件 | ||||||
|  |      */ | ||||||
|  |     condition?: {key: string, value: any}; | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * 全部选项 |      * 全部选项 | ||||||
|      */ |      */ | ||||||
| @ -163,6 +170,7 @@ function getDefaultValue<P extends IParameter> (option: IParameterOption<P>): IP | |||||||
|                     defaultObj[key] = [0, 0, 0] as any; |                     defaultObj[key] = [0, 0, 0] as any; | ||||||
|                     break; |                     break; | ||||||
|                  |                  | ||||||
|  |                 case "CG": | ||||||
|                 case "G": |                 case "G": | ||||||
|                 case "R": |                 case "R": | ||||||
|                     defaultObj[key] = { |                     defaultObj[key] = { | ||||||
| @ -171,6 +179,7 @@ function getDefaultValue<P extends IParameter> (option: IParameterOption<P>): IP | |||||||
|                     } as any; |                     } as any; | ||||||
|                     break; |                     break; | ||||||
| 
 | 
 | ||||||
|  |                 case "CLG": | ||||||
|                 case "LR": |                 case "LR": | ||||||
|                 case "LG": |                 case "LG": | ||||||
|                     defaultObj[key] = { |                     defaultObj[key] = { | ||||||
|  | |||||||
| @ -6,11 +6,12 @@ import { ClassicRenderer } from "@GLRender/ClassicRenderer"; | |||||||
| import { initializeIcons } from '@fluentui/font-icons-mdl2'; | import { initializeIcons } from '@fluentui/font-icons-mdl2'; | ||||||
| import { RootContainer } from "@Component/Container/RootContainer"; | import { RootContainer } from "@Component/Container/RootContainer"; | ||||||
| import { LayoutDirection } from "@Context/Layout"; | import { LayoutDirection } from "@Context/Layout"; | ||||||
| import { AllBehaviors } from "@Behavior/Behavior"; | import { AllBehaviors, getBehaviorById } from "@Behavior/Behavior"; | ||||||
| import { CommandBar } from "@Component/CommandBar/CommandBar"; | import { CommandBar } from "@Component/CommandBar/CommandBar"; | ||||||
| import { HeaderBar } from "@Component/HeaderBar/HeaderBar"; | import { HeaderBar } from "@Component/HeaderBar/HeaderBar"; | ||||||
| import { Popup } from "@Component/Popup/Popup"; | import { Popup } from "@Component/Popup/Popup"; | ||||||
| import { Entry } from "../Entry/Entry"; | import { Entry } from "../Entry/Entry"; | ||||||
|  | import { Group } from "@Model/Group"; | ||||||
| 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/"); | ||||||
| @ -40,18 +41,22 @@ class SimulatorWeb extends Component { | |||||||
|         this.status.bindRenderer(classicRender); |         this.status.bindRenderer(classicRender); | ||||||
|         this.status.setting = this.setting; |         this.status.setting = this.setting; | ||||||
| 
 | 
 | ||||||
|         // 测试代码
 |         const randomPosition = (group: Group) => { | ||||||
|         if (true) { |  | ||||||
|             let group = this.status.newGroup(); |  | ||||||
|             let range = this.status.newRange(); |  | ||||||
|             range.color = [.1, .5, .9]; |  | ||||||
|             group.new(100); |  | ||||||
|             group.color = [.8, .1, .6]; |  | ||||||
|             group.individuals.forEach((individual) => { |             group.individuals.forEach((individual) => { | ||||||
|                 individual.position[0] = (Math.random() - .5) * 2; |                 individual.position[0] = (Math.random() - .5) * 2; | ||||||
|                 individual.position[1] = (Math.random() - .5) * 2; |                 individual.position[1] = (Math.random() - .5) * 2; | ||||||
|                 individual.position[2] = (Math.random() - .5) * 2; |                 individual.position[2] = (Math.random() - .5) * 2; | ||||||
|             }) |             }) | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         // 测试代码
 | ||||||
|  |         if (false) { | ||||||
|  |             let group = this.status.newGroup(); | ||||||
|  |             let range = this.status.newRange(); | ||||||
|  |             range.color = [.1, .5, .9]; | ||||||
|  |             group.new(100); | ||||||
|  |             group.color = [.8, .1, .6]; | ||||||
|  |             randomPosition(group); | ||||||
|             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"; | ||||||
| @ -70,6 +75,84 @@ class SimulatorWeb extends Component { | |||||||
|             group.addBehavior(boundary); |             group.addBehavior(boundary); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         // 鱼群模型测试
 | ||||||
|  |         if (true) { | ||||||
|  |             let fish1 = this.status.newGroup(); | ||||||
|  |             let fish2 = this.status.newGroup(); | ||||||
|  |             let shark = this.status.newGroup(); | ||||||
|  |             let range = this.status.newRange(); | ||||||
|  | 
 | ||||||
|  |             range.displayName = "Experimental site"; | ||||||
|  |             range.color = [.8, .1, .6]; | ||||||
|  | 
 | ||||||
|  |             fish1.new(100); | ||||||
|  |             fish1.displayName = "Fish A";             | ||||||
|  |             fish1.color = [.1, .5, .9]; | ||||||
|  |             randomPosition(fish1); | ||||||
|  | 
 | ||||||
|  |             fish2.new(50); | ||||||
|  |             fish2.displayName = "Fish B";             | ||||||
|  |             fish2.color = [.3, .2, .9]; | ||||||
|  |             randomPosition(fish2); | ||||||
|  | 
 | ||||||
|  |             shark.new(3); | ||||||
|  |             shark.displayName = "Shark"; | ||||||
|  |             shark.color = [.8, .2, .3]; | ||||||
|  |             shark.renderParameter.size = 100; | ||||||
|  |             shark.renderParameter.shape = "5"; | ||||||
|  |             randomPosition(shark); | ||||||
|  | 
 | ||||||
|  |             this.status.model.update(0); | ||||||
|  |             let fishLabel = this.status.newLabel(); | ||||||
|  |             fishLabel.name = "Fish"; | ||||||
|  |             fish1.addLabel(fishLabel); | ||||||
|  |             fish2.addLabel(fishLabel); | ||||||
|  | 
 | ||||||
|  |             let template = this.status.model.addBehavior(getBehaviorById("Template")); | ||||||
|  |             template.name = "Template"; template.color = [150, 20, 220]; | ||||||
|  | 
 | ||||||
|  |             let dynamicFish = this.status.model.addBehavior(getBehaviorById("PhysicsDynamics")); | ||||||
|  |             dynamicFish.name = "Dynamic Fish"; dynamicFish.color = [250, 200, 80]; | ||||||
|  | 
 | ||||||
|  |             let dynamicShark = this.status.model.addBehavior(getBehaviorById("PhysicsDynamics")); | ||||||
|  |             dynamicShark.name = "Dynamic Shark"; dynamicShark.color = [250, 200, 80]; | ||||||
|  |             dynamicShark.parameter.maxAcceleration = 8.5; | ||||||
|  |             dynamicShark.parameter.maxVelocity = 15.8; | ||||||
|  |             dynamicShark.parameter.resistance = 3.6; | ||||||
|  | 
 | ||||||
|  |             let brownian = this.status.model.addBehavior(getBehaviorById("Brownian")); | ||||||
|  |             brownian.name = "Brownian"; brownian.color = [200, 80, 250]; | ||||||
|  | 
 | ||||||
|  |             let boundary = this.status.model.addBehavior(getBehaviorById("BoundaryConstraint")); | ||||||
|  |             boundary.name = "Boundary"; boundary.color = [80, 200, 250]; | ||||||
|  |             boundary.parameter.range.picker = this.status.model.allRangeLabel; | ||||||
|  | 
 | ||||||
|  |             let tracking = this.status.model.addBehavior(getBehaviorById("Tracking")); | ||||||
|  |             tracking.name = "Tracking"; tracking.color = [80, 200, 250]; | ||||||
|  |             tracking.parameter.target.picker = fishLabel; | ||||||
|  | 
 | ||||||
|  |             let attacking = this.status.model.addBehavior(getBehaviorById("ContactAttacking")); | ||||||
|  |             attacking.name = "Contact Attacking"; attacking.color = [120, 100, 250]; | ||||||
|  |             attacking.parameter.target.picker = fishLabel; | ||||||
|  | 
 | ||||||
|  |             fish1.addBehavior(dynamicFish); | ||||||
|  |             fish1.addBehavior(brownian); | ||||||
|  |             fish1.addBehavior(boundary); | ||||||
|  | 
 | ||||||
|  |             fish2.addBehavior(dynamicFish); | ||||||
|  |             fish2.addBehavior(brownian); | ||||||
|  |             fish2.addBehavior(boundary); | ||||||
|  | 
 | ||||||
|  |             shark.addBehavior(dynamicShark); | ||||||
|  |             shark.addBehavior(boundary); | ||||||
|  |             shark.addBehavior(tracking); | ||||||
|  |             shark.addBehavior(attacking); | ||||||
|  | 
 | ||||||
|  |             setTimeout(() => { | ||||||
|  |                 this.status.model.updateBehaviorParameter(); | ||||||
|  |             }, 200) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         (window as any).s = this; |         (window as any).s = this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { Component, ReactNode} from "react"; | import { Component, ReactNode} from "react"; | ||||||
| import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; | import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; | ||||||
| import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||||
|  | import { getDefaultValue } from "@Model/Parameter"; | ||||||
| import { IAnyBehavior } from "@Model/Behavior"; | import { IAnyBehavior } from "@Model/Behavior"; | ||||||
| import { Message } from "@Input/Message/Message"; | import { Message } from "@Input/Message/Message"; | ||||||
| import { AttrInput } from "@Input/AttrInput/AttrInput"; | import { AttrInput } from "@Input/AttrInput/AttrInput"; | ||||||
| @ -12,9 +13,17 @@ import "./BehaviorDetails.scss"; | |||||||
| 
 | 
 | ||||||
| interface IBehaviorDetailsProps {} | interface IBehaviorDetailsProps {} | ||||||
| 
 | 
 | ||||||
|  | interface IBehaviorDetailsState { | ||||||
|  |     updateId: number; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| @useSettingWithEvent("language") | @useSettingWithEvent("language") | ||||||
| @useStatusWithEvent("focusBehaviorChange", "behaviorAttrChange") | @useStatusWithEvent("focusBehaviorChange", "behaviorAttrChange") | ||||||
| class BehaviorDetails extends Component<IBehaviorDetailsProps & IMixinStatusProps & IMixinSettingProps> { | class BehaviorDetails extends Component<IBehaviorDetailsProps & IMixinStatusProps & IMixinSettingProps, IBehaviorDetailsState> { | ||||||
|  | 
 | ||||||
|  |     public state: Readonly<IBehaviorDetailsState> = { | ||||||
|  |         updateId: 1 | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     private handelDeleteBehavior = (behavior: IAnyBehavior) => { |     private handelDeleteBehavior = (behavior: IAnyBehavior) => { | ||||||
|         if (this.props.status) { |         if (this.props.status) { | ||||||
| @ -32,6 +41,28 @@ class BehaviorDetails extends Component<IBehaviorDetailsProps & IMixinStatusProp | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private handelRestoreBehavior = (behavior: IAnyBehavior) => { | ||||||
|  |         if (this.props.status) { | ||||||
|  |             const status = this.props.status; | ||||||
|  |             status.popup.showPopup(ConfirmPopup, { | ||||||
|  |                 infoI18n: "Popup.Restore.Behavior.Confirm", | ||||||
|  |                 titleI18N: "Popup.Action.Objects.Confirm.Restore.Title", | ||||||
|  |                 yesI18n: "Popup.Action.Objects.Confirm.Restore", | ||||||
|  |                 red: "yes", | ||||||
|  |                 yes: () => { | ||||||
|  |                     status.changeBehaviorAttrib( | ||||||
|  |                         behavior.id, "parameter", | ||||||
|  |                         getDefaultValue(behavior.parameterOption) as any, | ||||||
|  |                         true | ||||||
|  |                     ); | ||||||
|  |                     this.setState({ | ||||||
|  |                         updateId: this.state.updateId + 1 | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| 	private renderFrom(behavior: IAnyBehavior): ReactNode { | 	private renderFrom(behavior: IAnyBehavior): ReactNode { | ||||||
|          |          | ||||||
| 		return <> | 		return <> | ||||||
| @ -60,9 +91,17 @@ class BehaviorDetails extends Component<IBehaviorDetailsProps & IMixinStatusProp | |||||||
| 					this.handelDeleteBehavior(behavior) | 					this.handelDeleteBehavior(behavior) | ||||||
| 				}} | 				}} | ||||||
| 			/> | 			/> | ||||||
|  | 
 | ||||||
|  |             <TogglesInput | ||||||
|  | 				keyI18n="Common.Attr.Key.Behavior.Restore" red | ||||||
|  | 				onIconName="ReplyAll" offIconName="ReplyAll" | ||||||
|  | 				valueChange={() => { | ||||||
|  | 					this.handelRestoreBehavior(behavior) | ||||||
|  | 				}} | ||||||
|  | 			/> | ||||||
|              |              | ||||||
|             <Parameter |             <Parameter | ||||||
|                 key={behavior.id} |                 key={`${behavior.id}-${this.state.updateId}`} | ||||||
|                 option={behavior.parameterOption} |                 option={behavior.parameterOption} | ||||||
|                 value={behavior.parameter} |                 value={behavior.parameter} | ||||||
|                 i18n={(name, language) => behavior.getTerms(name, language)} |                 i18n={(name, language) => behavior.getTerms(name, language)} | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user