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 { Template } from "@Behavior/Template"; | ||||
| import { Dynamics } from "@Behavior/Dynamics"; | ||||
| import { PhysicsDynamics } from "@Behavior/PhysicsDynamics"; | ||||
| import { Brownian } from "@Behavior/Brownian"; | ||||
| 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[] = [ | ||||
|     new BehaviorRecorder(Template), | ||||
|     new BehaviorRecorder(Dynamics), | ||||
|     new BehaviorRecorder(PhysicsDynamics), | ||||
|     new BehaviorRecorder(Brownian), | ||||
|     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; | ||||
| } | ||||
| 
 | ||||
| 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 iconName: string = "Running"; | ||||
|     public override iconName: string = "Quantity"; | ||||
| 
 | ||||
|     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 } | ||||
| 	}; | ||||
| 
 | ||||
|     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 fx = 0; | ||||
| @ -57,7 +57,6 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, | ||||
| 				} | ||||
| 
 | ||||
| 			} else { | ||||
| 
 | ||||
| 				fx = 0; | ||||
| 				fy = 0; | ||||
| 				fz = 0; | ||||
| @ -65,12 +64,14 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter, | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
|         if (fLen && fLen !== Infinity) { | ||||
|             individual.applyForce( | ||||
| 			fx * this.parameter.strength, | ||||
| 			fy * this.parameter.strength, | ||||
| 			fz * this.parameter.strength | ||||
|                 fx * this.parameter.strength / fLen, | ||||
|                 fy * this.parameter.strength / fLen, | ||||
|                 fz * this.parameter.strength / fLen | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 	public override terms: Record<string, Record<string, string>> = { | ||||
|         "$Title": { | ||||
|  | ||||
| @ -18,7 +18,7 @@ class Brownian extends Behavior<IBrownianBehaviorParameter, IBrownianBehaviorEve | ||||
| 
 | ||||
|     public override behaviorName: string = "$Title"; | ||||
| 
 | ||||
|     public override iconName: string = "Running"; | ||||
|     public override iconName: string = "ScatterChart"; | ||||
| 
 | ||||
|     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 } | ||||
| 	}; | ||||
| 
 | ||||
| 	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; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										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 { Model } from "@Model/Model"; | ||||
| 
 | ||||
| type IDynamicsBehaviorParameter = { | ||||
| type IPhysicsDynamicsBehaviorParameter = { | ||||
|     mass: "number", | ||||
| 	maxAcceleration: "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 iconName: string = "Running"; | ||||
|     public override iconName: string = "SliderHandleSize"; | ||||
| 
 | ||||
|     public override describe: string = "$Intro"; | ||||
| 
 | ||||
| @ -26,12 +27,19 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | ||||
| 
 | ||||
| 	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.5, numberStep: .1, numberMin: 0 } | ||||
| 		resistance: { name: "$Resistance", type: "number", defaultValue: 2.8, numberStep: .1, numberMin: 0 }, | ||||
| 		limit: { name: "$Limit", type: "boolean", defaultValue: true }, | ||||
| 		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); | ||||
| @ -54,12 +62,14 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | ||||
| 		individual.acceleration[2] = individual.force[2] / this.parameter.mass; | ||||
| 
 | ||||
| 		// 加速度约束
 | ||||
| 		if (this.parameter.limit) { | ||||
| 			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; | ||||
| @ -67,12 +77,14 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | ||||
| 		individual.velocity[2] = individual.velocity[2] + individual.acceleration[2] * t; | ||||
| 
 | ||||
| 		// 速度约束
 | ||||
| 		if (this.parameter.limit) { | ||||
| 			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; | ||||
| @ -87,12 +99,16 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | ||||
| 	 | ||||
|     public override terms: Record<string, Record<string, string>> = { | ||||
|         "$Title": { | ||||
|             "ZH_CN": "动力学", | ||||
|             "EN_US": "Dynamics" | ||||
|             "ZH_CN": "物理动力学", | ||||
|             "EN_US": "Physics dynamics" | ||||
|         }, | ||||
|         "$Intro": { | ||||
|             "ZH_CN": "一切可以运动物体的必要行为,执行物理法则。", | ||||
|             "EN_US": "All necessary behaviors that can move objects and implement the laws of physics." | ||||
|             "ZH_CN": "一切按照物理规则运动物体的行为, 按照牛顿经典物理运动公式执行。", | ||||
|             "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": { | ||||
| 			"ZH_CN": "质量 (Kg)", | ||||
| @ -117,4 +133,4 @@ class Dynamics extends Behavior<IDynamicsBehaviorParameter, IDynamicsBehaviorEve | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| export { Dynamics }; | ||||
| export { PhysicsDynamics }; | ||||
| @ -14,6 +14,8 @@ type ITemplateBehaviorParameter = { | ||||
|     testLR: "LR"; | ||||
|     testLG: "LG"; | ||||
|     testVec: "vec"; | ||||
|     testCG: "CG", | ||||
|     testCLG: "CLG", | ||||
| } | ||||
| 
 | ||||
| type ITemplateBehaviorEvent = {} | ||||
| @ -42,10 +44,12 @@ class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEve | ||||
|         testG: { name: "$Test", type: "G" }, | ||||
|         testLR: { name: "$Test", type: "LR" }, | ||||
|         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 } | ||||
|     }; | ||||
| 
 | ||||
|     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 { | ||||
|                 width: calc( 100% - 50px ); | ||||
|                 padding-right: 5px; | ||||
|                 width: calc( 100% - 55px ); | ||||
|                 padding-right: 10px; | ||||
|                 max-width: 125px; | ||||
|                 height: $behavior-item-height; | ||||
|                 display: flex; | ||||
|  | ||||
| @ -119,6 +119,10 @@ div.app-container { | ||||
| 		flex-shrink: 1; | ||||
| 	} | ||||
| 
 | ||||
| 	div.app-panel.hide-scrollbar { | ||||
| 		overflow: hidden; | ||||
| 	} | ||||
| 
 | ||||
| 	div.app-panel.hide-scrollbar::-webkit-scrollbar { | ||||
| 		width : 0;  /*高宽分别对应横竖滚动条的尺寸*/ | ||||
| 		height: 0; | ||||
|  | ||||
| @ -59,6 +59,10 @@ class ObjectPicker extends Component<IObjectPickerProps & IMixinStatusProps, IOb | ||||
|                     option.push(this.props.status.model.objectPool[j]); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (this.props.type.includes("C")) { | ||||
|                 option.push(this.props.status.model.currentGroupLabel); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         return option; | ||||
|  | ||||
| @ -32,9 +32,15 @@ class Parameter<P extends IParameter> extends Component<IParameterProps<P> & IMi | ||||
|     private renderParameter<K extends keyof P> | ||||
|     (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}`; | ||||
| 
 | ||||
|         // 条件检测
 | ||||
|         if (option.condition && this.props.value[option.condition.key] !==  option.condition.value) { | ||||
|             return <Fragment key={indexKey}/>; | ||||
|         } | ||||
| 
 | ||||
|         const type = option.type; | ||||
|         const language = this.props.setting?.language ?? "EN_US";         | ||||
|         let keyI18n: string, keyI18nOption: Record<string, string> | undefined; | ||||
| 
 | ||||
|         // Custom I18N
 | ||||
|  | ||||
| @ -51,16 +51,25 @@ function getObjectDisplayInfo(item?: IPickerListItem): IDisplayInfo { | ||||
| 	if (item instanceof Label) { | ||||
| 
 | ||||
| 		if (item.isBuildIn) { | ||||
| 
 | ||||
|             internal = true; | ||||
|             allLabel = true; | ||||
|             color = "transparent"; | ||||
| 
 | ||||
|             if (item.id === "AllRange") { | ||||
|                 icon = "ProductList"; | ||||
|                 name = "Build.In.Label.Name.All.Range"; | ||||
|             } else if (item.id === "AllGroup") { | ||||
|             } | ||||
| 			 | ||||
| 			else if (item.id === "AllGroup") { | ||||
|                 icon = "SizeLegacy"; | ||||
|                 name = "Build.In.Label.Name.All.Group"; | ||||
|             } | ||||
| 
 | ||||
| 			else if (item.id === "CurrentGroupLabel") { | ||||
| 				icon = "TriangleShape"; | ||||
|                 name = "Build.In.Label.Name.Current.Group"; | ||||
| 			} | ||||
|         }  | ||||
|          | ||||
|         else { | ||||
|  | ||||
| @ -57,8 +57,11 @@ const EN_US = { | ||||
|     "Popup.Action.No": "Cancel", | ||||
|     "Popup.Action.Objects.Confirm.Title": "Confirm 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.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.Add.Behavior.Title": "Add 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", | ||||
|     "Build.In.Label.Name.All.Group": "All group", | ||||
|     "Build.In.Label.Name.All.Range": "All range", | ||||
|     "Build.In.Label.Name.Current.Group": "Current group", | ||||
|     "Common.Search.Placeholder": "Search in here...", | ||||
|     "Common.No.Data": "No Data", | ||||
|     "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.Kill.Random": "Random kill", | ||||
|     "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.Square": "Square", | ||||
|     "Common.Render.Attr.Key.Display.Shape.Hollow.Square": "Hollow square", | ||||
|  | ||||
| @ -57,17 +57,21 @@ const ZH_CN = { | ||||
|     "Popup.Action.No": "取消", | ||||
|     "Popup.Action.Objects.Confirm.Title": "删除确认", | ||||
|     "Popup.Action.Objects.Confirm.Delete": "删除", | ||||
|     "Popup.Action.Objects.Confirm.Restore.Title": "重置确认", | ||||
|     "Popup.Action.Objects.Confirm.Restore": "重置", | ||||
|     "Popup.Delete.Objects.Confirm": "你确定要删除这个(些)对象吗?对象被删除将无法撤回。", | ||||
|     "Popup.Delete.Behavior.Confirm": "你确定要删除这个行为吗?行为被删除将无法撤回。", | ||||
|     "Popup.Restore.Behavior.Confirm": "你确定要重置此行为的全部参数吗?此操作无法撤回。", | ||||
|     "Popup.Setting.Title": "首选项设置", | ||||
|     "Popup.Add.Behavior.Title": "添加行为", | ||||
|     "Popup.Add.Behavior.Action.Add": "添加全部选中行为", | ||||
|     "Popup.Add.Behavior.Select.Counter": "找不到名为 \"{name}\" 的行为", | ||||
|     "Popup.Add.Behavior.Select.Nodata": "Could not find behavior named \"{name}\"", | ||||
|     "Popup.Add.Behavior.Select.Counter": "已选择 {count} 个行为", | ||||
|     "Popup.Add.Behavior.Select.Nodata": "找不到名为 \"{name}\" 的行为", | ||||
|     "Popup.Behavior.Info.Title": "行为详情: {behavior}", | ||||
|     "Popup.Behavior.Info.Confirm": "好的, 我知道了", | ||||
|     "Build.In.Label.Name.All.Group": "全部群", | ||||
|     "Build.In.Label.Name.All.Range": "全部范围", | ||||
|     "Build.In.Label.Name.Current.Group": "当前群", | ||||
|     "Common.Search.Placeholder": "在此处搜索...", | ||||
|     "Common.No.Data": "暂无数据", | ||||
|     "Common.No.Unknown.Error": "未知错误", | ||||
| @ -107,6 +111,7 @@ const ZH_CN = { | ||||
|     "Common.Attr.Key.Generation.Error.Invalid.Label": "指定的标签已失效", | ||||
|     "Common.Attr.Key.Kill.Random": "随机消除", | ||||
|     "Common.Attr.Key.Kill.Count": "消除数量", | ||||
|     "Common.Attr.Key.Behavior.Restore": "还原默认参数", | ||||
|     "Common.Render.Attr.Key.Display.Shape": "显示形状", | ||||
|     "Common.Render.Attr.Key.Display.Shape.Square": "方形", | ||||
|     "Common.Render.Attr.Key.Display.Shape.Hollow.Square": "空心方形", | ||||
|  | ||||
| @ -158,6 +158,11 @@ class Behavior< | ||||
|      */ | ||||
|     public parameter: IParameterValue<P>; | ||||
| 
 | ||||
|     /** | ||||
|      * 指定当前群的 Key | ||||
|      */ | ||||
|     public currentGroupKey: Array<keyof P> = []; | ||||
| 
 | ||||
|     /** | ||||
|      * 对象参数列表 | ||||
|      */ | ||||
| @ -222,7 +227,7 @@ class Behavior< | ||||
|      * @param model 模型 | ||||
|      * @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 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 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 { | ||||
|         return this === obj || this.id === obj.id; | ||||
|     public equal(obj?: CtrlObject): boolean { | ||||
|         return this === obj || this.id === obj?.id; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { Individual } from "@Model/Individual"; | ||||
| import { CtrlObject } from "@Model/CtrlObject"; | ||||
| import type { Behavior } from "@Model/Behavior";  | ||||
| import type { Behavior, IAnyBehavior } from "@Model/Behavior";  | ||||
| import { Label } from "@Model/Label"; | ||||
| import { Range } from "@Model/Range"; | ||||
| 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) { | ||||
|         for (let i = 0; i < count; i++) { | ||||
| @ -272,9 +272,11 @@ class Group extends CtrlObject { | ||||
|     public remove(individual: Individual[] | Individual): this { | ||||
|         if (Array.isArray(individual)) { | ||||
|             for (let i = 0; i < individual.length; i++) { | ||||
|                 individual[i].group = undefined; | ||||
|                 this.individuals.delete(individual[i]); | ||||
|             } | ||||
|         } else { | ||||
|             individual.group = undefined; | ||||
|             this.individuals.delete(individual); | ||||
|         } | ||||
|         return this; | ||||
| @ -308,7 +310,7 @@ class Group extends CtrlObject { | ||||
| 	/** | ||||
| 	 * 行为列表 | ||||
| 	 */ | ||||
| 	public behaviors: Behavior[] = []; | ||||
| 	public behaviors: IAnyBehavior[] = []; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * 添加行为 | ||||
| @ -358,16 +360,38 @@ class Group extends CtrlObject { | ||||
| 	 * @param | ||||
|      */ | ||||
| 	public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void { | ||||
| 		this.individuals.forEach((individual) => { | ||||
| 
 | ||||
|         for(let j = 0; j < this.behaviors.length; j++) { | ||||
|                 if (this.behaviors[j].isDeleted()) { | ||||
| 
 | ||||
|             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 { | ||||
|                     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; | ||||
| 	} | ||||
| 
 | ||||
|     public isDie(): boolean { | ||||
|         return !!this.group; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 死亡 | ||||
|      */ | ||||
|     public die(): this { | ||||
|         this.group.remove(this); | ||||
|         this.group?.remove(this); | ||||
|         this.group = undefined; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
| @ -110,7 +115,7 @@ class Individual { | ||||
|      * @param newGroup 新群体 | ||||
|      */ | ||||
|     public transfer(newGroup: Group): this { | ||||
|         this.group.remove(this); | ||||
|         this.group?.remove(this); | ||||
|         newGroup.add(this); | ||||
|         this.group = newGroup; | ||||
|         return this; | ||||
|  | ||||
| @ -66,6 +66,11 @@ class Model extends Emitter<ModelEvent> { | ||||
|      */ | ||||
|     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() { | ||||
|         for (let i = 0; i < this.behaviorPool.length; i++) { | ||||
|             const behavior = this.behaviorPool[i]; | ||||
|             behavior.currentGroupKey = []; | ||||
|              | ||||
|             for (let key in behavior.parameterOption) { | ||||
|                 switch (behavior.parameterOption[key].type) { | ||||
| @ -236,11 +242,17 @@ class Model extends Emitter<ModelEvent> { | ||||
|                         } | ||||
|                         break; | ||||
| 
 | ||||
|                     case "CG": | ||||
|                     case "G": | ||||
|                         const dataG: IParamValue<"G"> = behavior.parameter[key]; | ||||
|                         const dataG: IParamValue<"CG"> = behavior.parameter[key]; | ||||
|                         dataG.objects = undefined; | ||||
| 
 | ||||
|                         if (dataG.picker instanceof Group && !dataG.picker.isDeleted()) { | ||||
|                         if (dataG.picker instanceof Label && dataG.picker.id === this.currentGroupLabel.id) { | ||||
|                             behavior.currentGroupKey.push(key); | ||||
|                             dataG.objects = undefined; | ||||
|                         } | ||||
|                          | ||||
|                         else if (dataG.picker instanceof Group && !dataG.picker.isDeleted()) { | ||||
|                             dataG.objects = dataG.picker; | ||||
|                         } | ||||
|                         break; | ||||
| @ -260,8 +272,9 @@ class Model extends Emitter<ModelEvent> { | ||||
|                         } | ||||
|                         break; | ||||
| 
 | ||||
|                     case "CLG": | ||||
|                     case "LG": | ||||
|                         const dataLG: IParamValue<"LG"> = behavior.parameter[key]; | ||||
|                         const dataLG: IParamValue<"CLG"> = behavior.parameter[key]; | ||||
|                         dataLG.objects = []; | ||||
| 
 | ||||
|                         if (dataLG.picker instanceof Group && !dataLG.picker.isDeleted()) { | ||||
| @ -269,10 +282,17 @@ class Model extends Emitter<ModelEvent> { | ||||
|                         } | ||||
| 
 | ||||
|                         if (dataLG.picker instanceof Label && !dataLG.picker.isDeleted()) { | ||||
| 
 | ||||
|                             if (dataLG.picker.id === this.currentGroupLabel.id) { | ||||
|                                 behavior.currentGroupKey.push(key); | ||||
|                                 dataLG.objects = []; | ||||
| 
 | ||||
|                             } else { | ||||
|                                 dataLG.objects = this.getObjectByLabel(dataLG.picker).filter((obj) => { | ||||
|                                     return obj instanceof Group; | ||||
|                                 }) as any; | ||||
|                             } | ||||
|                         } | ||||
|                         break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -22,6 +22,8 @@ type IMapObjectParamTypeKeyToType = { | ||||
|     "G": IObjectParamCacheType<Group | undefined>; | ||||
|     "LR": IObjectParamCacheType<Label | Range | undefined, Range[]>; | ||||
|     "LG": IObjectParamCacheType<Label | Group | undefined, Group[]>; | ||||
|     "CG": IObjectParamCacheType<Label | Group | undefined, Group | undefined>; | ||||
|     "CLG": IObjectParamCacheType<Label | Group | undefined, Group[]>; | ||||
| } | ||||
| 
 | ||||
| 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; | ||||
| 
 | ||||
|     /** | ||||
|      * 显示条件 | ||||
|      */ | ||||
|     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; | ||||
|                     break; | ||||
|                  | ||||
|                 case "CG": | ||||
|                 case "G": | ||||
|                 case "R": | ||||
|                     defaultObj[key] = { | ||||
| @ -171,6 +179,7 @@ function getDefaultValue<P extends IParameter> (option: IParameterOption<P>): IP | ||||
|                     } as any; | ||||
|                     break; | ||||
| 
 | ||||
|                 case "CLG": | ||||
|                 case "LR": | ||||
|                 case "LG": | ||||
|                     defaultObj[key] = { | ||||
|  | ||||
| @ -6,11 +6,12 @@ import { ClassicRenderer } from "@GLRender/ClassicRenderer"; | ||||
| import { initializeIcons } from '@fluentui/font-icons-mdl2'; | ||||
| import { RootContainer } from "@Component/Container/RootContainer"; | ||||
| import { LayoutDirection } from "@Context/Layout"; | ||||
| import { AllBehaviors } from "@Behavior/Behavior"; | ||||
| import { AllBehaviors, getBehaviorById } from "@Behavior/Behavior"; | ||||
| import { CommandBar } from "@Component/CommandBar/CommandBar"; | ||||
| import { HeaderBar } from "@Component/HeaderBar/HeaderBar"; | ||||
| import { Popup } from "@Component/Popup/Popup"; | ||||
| import { Entry } from "../Entry/Entry"; | ||||
| import { Group } from "@Model/Group"; | ||||
| import "./SimulatorWeb.scss"; | ||||
| 
 | ||||
| initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/"); | ||||
| @ -40,18 +41,22 @@ class SimulatorWeb extends Component { | ||||
|         this.status.bindRenderer(classicRender); | ||||
|         this.status.setting = this.setting; | ||||
| 
 | ||||
|         // 测试代码
 | ||||
|         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]; | ||||
|         const randomPosition = (group: Group) => { | ||||
|             group.individuals.forEach((individual) => { | ||||
|                 individual.position[0] = (Math.random() - .5) * 2; | ||||
|                 individual.position[1] = (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.newLabel().name = "New Label"; | ||||
|             this.status.newLabel().name = "Test Label 01"; | ||||
| @ -70,6 +75,84 @@ class SimulatorWeb extends Component { | ||||
|             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; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import { Component, ReactNode} from "react"; | ||||
| import { useSettingWithEvent, IMixinSettingProps } from "@Context/Setting"; | ||||
| import { useStatusWithEvent, IMixinStatusProps } from "@Context/Status"; | ||||
| import { getDefaultValue } from "@Model/Parameter"; | ||||
| import { IAnyBehavior } from "@Model/Behavior"; | ||||
| import { Message } from "@Input/Message/Message"; | ||||
| import { AttrInput } from "@Input/AttrInput/AttrInput"; | ||||
| @ -12,9 +13,17 @@ import "./BehaviorDetails.scss"; | ||||
| 
 | ||||
| interface IBehaviorDetailsProps {} | ||||
| 
 | ||||
| interface IBehaviorDetailsState { | ||||
|     updateId: number; | ||||
| } | ||||
| 
 | ||||
| @useSettingWithEvent("language") | ||||
| @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) => { | ||||
|         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 { | ||||
|          | ||||
| 		return <> | ||||
| @ -61,8 +92,16 @@ class BehaviorDetails extends Component<IBehaviorDetailsProps & IMixinStatusProp | ||||
| 				}} | ||||
| 			/> | ||||
| 
 | ||||
|             <TogglesInput | ||||
| 				keyI18n="Common.Attr.Key.Behavior.Restore" red | ||||
| 				onIconName="ReplyAll" offIconName="ReplyAll" | ||||
| 				valueChange={() => { | ||||
| 					this.handelRestoreBehavior(behavior) | ||||
| 				}} | ||||
| 			/> | ||||
|              | ||||
|             <Parameter | ||||
|                 key={behavior.id} | ||||
|                 key={`${behavior.id}-${this.state.updateId}`} | ||||
|                 option={behavior.parameterOption} | ||||
|                 value={behavior.parameter} | ||||
|                 i18n={(name, language) => behavior.getTerms(name, language)} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user