diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts index b603a2f..3abf452 100644 --- a/source/Behavior/Behavior.ts +++ b/source/Behavior/Behavior.ts @@ -10,6 +10,9 @@ import { DelayAssimilate } from "@Behavior/DelayAssimilate"; import { Avoidance } from "@Behavior/Avoidance"; 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), @@ -23,6 +26,9 @@ const AllBehaviors: IAnyBehaviorRecorder[] = [ new BehaviorRecorder(Avoidance), new BehaviorRecorder(DirectionCluster), new BehaviorRecorder(CentralCluster), + new BehaviorRecorder(Manufacture), + new BehaviorRecorder(Wastage), + new BehaviorRecorder(SampleTracking), ] /** diff --git a/source/Behavior/BoundaryConstraint.ts b/source/Behavior/BoundaryConstraint.ts index d77b3d5..2d2aee3 100644 --- a/source/Behavior/BoundaryConstraint.ts +++ b/source/Behavior/BoundaryConstraint.ts @@ -48,11 +48,17 @@ class BoundaryConstraint extends Behavior { const {maxFrequency, minFrequency, maxStrength, minStrength} = this.parameter; @@ -41,11 +122,49 @@ class Brownian extends Behavior nextTime) { + + let randomDir: number[]; + + // 开启角度限制 + if (this.parameter.dirLimit) { + + // 计算当前速度大小 + const vLen = individual.vectorLength(individual.velocity); + + // 随机旋转算法 + if (vLen > 0) { + randomDir = this.randomFocusRange( + [ + individual.velocity[0] / vLen, + individual.velocity[1] / vLen, + individual.velocity[2] / vLen + ], + this.parameter.angle / 2 + ); + + if (isNaN(randomDir[0]) || isNaN(randomDir[1]) || isNaN(randomDir[2])) { + randomDir = this.randomFocus360() + } + } + + else { + randomDir = this.randomFocus360() + } + } + + // 随机生成算法 + else { + randomDir = this.randomFocus360() + } + + const randomLength = minStrength + Math.random() * (maxStrength - minStrength); + individual.applyForce( - minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength), - minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength), - minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength) + randomDir[0] * randomLength, + randomDir[1] * randomLength, + randomDir[2] * randomLength ); + nextTime = minFrequency + Math.random() * (maxFrequency - minFrequency); currentTime = 0; } @@ -78,7 +197,15 @@ class Brownian extends Behavior { - 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]; @@ -50,7 +49,18 @@ class ContactAssimilate extends Behavior { @@ -46,6 +48,10 @@ class ContactAttacking extends Behavior { + + public override behaviorId: string = "Manufacture"; + + public override behaviorName: string = "$Title"; + + public override iconName: string = "ProductionFloorManagement"; + + public override describe: string = "$Intro"; + + public override category: string = "$Initiative"; + + public override parameterOption = { + genTarget: { type: "CG", name: "$Gen.Target" }, + maxFrequency: { type: "number", name: "$Max.Frequency", defaultValue: 5, numberStep: .1, numberMin: 0 }, + minFrequency: { type: "number", name: "$Min.Frequency", defaultValue: 0, numberStep: .1, numberMin: 0 } + }; + + public effect = (individual: Individual, group: Group, model: Model, t: number): void => { + + const {genTarget, maxFrequency, minFrequency} = this.parameter; + + if (genTarget.objects) { + + let nextTime = individual.getData("Manufacture.nextTime") ?? + minFrequency + Math.random() * (maxFrequency - minFrequency); + let currentTime = individual.getData("Manufacture.currentTime") ?? 0; + + if (currentTime > nextTime) { + + // 生成个体 + let newIndividual = genTarget.objects.new(1); + newIndividual.position = individual.position.concat([]); + + nextTime = minFrequency + Math.random() * (maxFrequency - minFrequency); + currentTime = 0; + } + + currentTime += t; + + individual.setData("Manufacture.nextTime", nextTime); + individual.setData("Manufacture.currentTime", currentTime); + } + } + + public override terms: Record> = { + "$Title": { + "ZH_CN": "生产", + "EN_US": "Manufacture" + }, + "$Intro": { + "ZH_CN": "在指定的群创造新的个体", + "EN_US": "Create new individuals in a given group" + }, + "$Gen.Target": { + "ZH_CN": "目标群", + "EN_US": "Target group" + }, + "$Max.Frequency": { + "ZH_CN": "最大频率", + "EN_US": "Maximum frequency" + }, + "$Min.Frequency": { + "ZH_CN": "最小频率", + "EN_US": "Minimum frequency" + } + }; +} + +export { Manufacture }; \ No newline at end of file diff --git a/source/Behavior/SampleTracking.ts b/source/Behavior/SampleTracking.ts new file mode 100644 index 0000000..5e56141 --- /dev/null +++ b/source/Behavior/SampleTracking.ts @@ -0,0 +1,160 @@ +import { Behavior } from "@Model/Behavior"; +import { Group } from "@Model/Group"; +import { Individual } from "@Model/Individual"; +import { Model } from "@Model/Model"; + +type ISampleTrackingBehaviorParameter = { + target: "CLG", + key: "string", + 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" }, + key: { type: "string", name: "$Key"}, + 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(this.parameter.key) ?? 0; + isFindNest = true; + } + } + + if (!isFindNest) { + + // 保存 + dirArr.push(targetDir); + valArr.push(targetIndividual.getData(this.parameter.key) ?? 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" + }, + "$Key": { + "ZH_CN": "计算键值", + "EN_US": "Calculate key value" + }, + "$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 diff --git a/source/Behavior/Tracking.ts b/source/Behavior/Tracking.ts index 3ea2ed8..01d8cf1 100644 --- a/source/Behavior/Tracking.ts +++ b/source/Behavior/Tracking.ts @@ -7,6 +7,7 @@ type ITrackingBehaviorParameter = { target: "CLG", strength: "number", range: "number", + angle: "number", lock: "boolean" } @@ -28,12 +29,23 @@ class Tracking extends Behavior { + + public override behaviorId: string = "Wastage"; + + public override behaviorName: string = "$Title"; + + public override iconName: string = "BackgroundColor"; + + public override describe: string = "$Intro"; + + public override category: string = "$Initiative"; + + public override parameterOption = { + key: { type: "string", name: "$Key" }, + value: { type: "number", name: "$Value", defaultValue: 100, numberStep: 1 }, + speed: { type: "number", name: "$Speed", defaultValue: 1, numberStep: .1 }, + threshold: { type: "number", name: "$Threshold", defaultValue: 0, numberStep: 1 }, + kill: { type: "boolean", name: "$Kill", defaultValue: true }, + assimilate: { type: "CG", name: "$Assimilate", condition: { key: "kill", value: false } } + }; + + public effect = (individual: Individual, group: Group, model: Model, t: number): void => { + const key = this.parameter.key; + if (!key) return; + + let currentValue = individual.getData(`Wastage.${key}`) ?? this.parameter.value; + currentValue -= this.parameter.speed * t; + + // 超过阈值 + if (currentValue < this.parameter.threshold) { + + currentValue = undefined; + + // 杀死个体 + if (this.parameter.kill) { + individual.die(); + } + + // 开启同化 + else if (this.parameter.assimilate.objects) { + individual.transfer(this.parameter.assimilate.objects); + } + } + + individual.setData(`Wastage.${key}`, currentValue); + } + + public override terms: Record> = { + "$Title": { + "ZH_CN": "流逝", + "EN_US": "Wastage" + }, + "$Intro": { + "ZH_CN": "随着时间流逝", + "EN_US": "As time goes by" + }, + "$Key": { + "ZH_CN": "元数据", + "EN_US": "Metadata" + }, + "$Value": { + "ZH_CN": "默认数值", + "EN_US": "Default value" + }, + "$Speed": { + "ZH_CN": "流逝速度 (c/s)", + "EN_US": "Passing speed (c/s)" + }, + "$Threshold": { + "ZH_CN": "阈值", + "EN_US": "Threshold" + }, + "$Kill": { + "ZH_CN": "死亡", + "EN_US": "Death" + }, + "$Assimilate": { + "ZH_CN": "同化", + "EN_US": "Assimilate" + } + }; +} + +export { Wastage }; \ No newline at end of file