From 476866780391975ad30bcb2da1bcee3ccfe82355 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 11 May 2022 11:43:41 +0800 Subject: [PATCH] Add sample tracking behavior --- source/Behavior/Behavior.ts | 2 + source/Behavior/SampleTracking.ts | 154 ++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 source/Behavior/SampleTracking.ts diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts index 147427e..3abf452 100644 --- a/source/Behavior/Behavior.ts +++ b/source/Behavior/Behavior.ts @@ -12,6 +12,7 @@ import { DirectionCluster } from "@Behavior/DirectionCluster"; import { CentralCluster } from "@Behavior/CentralCluster"; import { Manufacture } from "@Behavior/Manufacture"; import { Wastage } from "@Behavior/Wastage"; +import { SampleTracking } from "@Behavior/SampleTracking"; const AllBehaviors: IAnyBehaviorRecorder[] = [ new BehaviorRecorder(Template), @@ -27,6 +28,7 @@ const AllBehaviors: IAnyBehaviorRecorder[] = [ new BehaviorRecorder(CentralCluster), new BehaviorRecorder(Manufacture), new BehaviorRecorder(Wastage), + new BehaviorRecorder(SampleTracking), ] /** diff --git a/source/Behavior/SampleTracking.ts b/source/Behavior/SampleTracking.ts new file mode 100644 index 0000000..83d30fc --- /dev/null +++ b/source/Behavior/SampleTracking.ts @@ -0,0 +1,154 @@ +import { Behavior } from "@Model/Behavior"; +import { Group } from "@Model/Group"; +import { Individual } from "@Model/Individual"; +import { Model } from "@Model/Model"; + +type ISampleTrackingBehaviorParameter = { + target: "CLG", + strength: "number", + range: "number", + angle: "number", + accuracy: "number" +} + +type ISampleTrackingBehaviorEvent = {} + +class SampleTracking extends Behavior { + + public override behaviorId: string = "SampleTracking"; + + public override behaviorName: string = "$Title"; + + public override iconName: string = "Video360Generic"; + + public override describe: string = "$Intro"; + + public override category: string = "$Initiative"; + + public override parameterOption = { + target: { type: "CLG", name: "$Target" }, + range: { type: "number", name: "$Range", defaultValue: 4, numberMin: 0, numberStep: .1 }, + angle: { type: "number", name: "$Angle", defaultValue: 180, numberMin: 0, numberMax: 360, numberStep: 5 }, + strength: { type: "number", name: "$Strength", defaultValue: 1, numberMin: 0, numberStep: .1 }, + accuracy: { type: "number", name: "$Accuracy", defaultValue: 5, numberMin: 0, numberMax: 180, numberStep: 1 } + }; + + private angle2Vector(v1: number[], v2: number[]): number { + return Math.acos( + (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]) / + ( + (v1[0] ** 2 + v1[1] ** 2 + v1[2] ** 2) ** 0.5 * + (v2[0] ** 2 + v2[1] ** 2 + v2[2] ** 2) ** 0.5 + ) + ) * 180 / Math.PI; + } + + public effect = (individual: Individual, group: Group, model: Model, t: number): void => { + + const dirArr: number[][] = []; const valArr: number[] = []; + + for (let i = 0; i < this.parameter.target.objects.length; i++) { + const targetGroup = this.parameter.target.objects[i]; + + targetGroup.individuals.forEach((targetIndividual) => { + + // 计算距离 + let dis = targetIndividual.distanceTo(individual); + if (dis > this.parameter.range) return; + + // 计算方向 + let targetDir = [ + targetIndividual.position[0] - individual.position[0], + targetIndividual.position[1] - individual.position[1], + targetIndividual.position[2] - individual.position[2] + ]; + + // 计算视线角度 + let angle = this.angle2Vector(individual.velocity, targetDir); + + // 在可视角度内 + if (angle < (this.parameter.angle ?? 360) / 2) { + + // 采样 + let isFindNest = false; + for (let i = 0; i < valArr.length; i++) { + + // 计算采样角度 + let sampleAngle = this.angle2Vector(dirArr[i], targetDir); + + // 小于采样精度,合并 + if (sampleAngle < this.parameter.accuracy ?? 5) { + dirArr[i][0] += targetDir[0]; + dirArr[i][1] += targetDir[1]; + dirArr[i][2] += targetDir[2]; + valArr[i] += targetIndividual.getData("Wastage.Pheromone") ?? 0; + isFindNest = true; + } + } + + if (!isFindNest) { + + // 保存 + dirArr.push(targetDir); + valArr.push(targetIndividual.getData("Wastage.Pheromone") ?? 0); + } + } + }); + } + + // 计算最大方向 + let maxVal = -1; let maxDir: number[] | undefined; + for (let i = 0; i < valArr.length; i++) { + if (valArr[i] > maxVal) { + maxVal = valArr[i]; + maxDir = dirArr[i]; + } + } + + if (maxDir) { + const dir = individual.vectorNormalize(maxDir); + individual.applyForce( + dir[0] * this.parameter.strength, + dir[1] * this.parameter.strength, + dir[2] * this.parameter.strength + ); + } + } + + public override terms: Record> = { + "$Title": { + "ZH_CN": "采样追踪", + "EN_US": "Sample tracking" + }, + "$Target": { + "ZH_CN": "追踪目标", + "EN_US": "Tracking target" + }, + "$Accuracy": { + "ZH_CN": "采样精度", + "EN_US": "Sampling accuracy" + }, + "$Range": { + "ZH_CN": "追踪范围 (m)", + "EN_US": "Tracking range (m)" + }, + "$Strength": { + "ZH_CN": "追踪强度系数", + "EN_US": "Tracking intensity coefficient" + }, + "$Intro": { + "ZH_CN": "个体将主动向目标个体较多的方向发起追踪", + "EN_US": "Individuals will actively initiate tracking in the direction of more target individuals" + }, + "$Interactive": { + "ZH_CN": "交互", + "EN_US": "Interactive" + }, + "$Angle": { + "ZH_CN": "可视角度", + "EN_US": "Viewing angle" + } + }; +} + +export { SampleTracking }; \ No newline at end of file