Add ant behavior #54
@ -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),
|
||||
]
|
||||
|
||||
/**
|
||||
|
@ -48,11 +48,17 @@ class BoundaryConstraint extends Behavior<IBoundaryConstraintBehaviorParameter,
|
||||
|
||||
if (ox || oy || oz) {
|
||||
|
||||
let currentFLen = individual.vectorLength(rx, ry, rz);
|
||||
const backFocus: number[] = [0, 0, 0];
|
||||
|
||||
if (ox) backFocus[0] = rx - rx * rangeList[i].radius[0] / Math.abs(rx);
|
||||
if (oy) backFocus[1] = ry - ry * rangeList[i].radius[1] / Math.abs(ry);
|
||||
if (oz) backFocus[2] = rz - rz * rangeList[i].radius[2] / Math.abs(rz);
|
||||
|
||||
let currentFLen = individual.vectorLength(backFocus);
|
||||
if (currentFLen < fLen) {
|
||||
fx = rx;
|
||||
fy = ry;
|
||||
fz = rz;
|
||||
fx = backFocus[0];
|
||||
fy = backFocus[1];
|
||||
fz = backFocus[2];
|
||||
fLen = currentFLen;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,9 @@ type IBrownianBehaviorParameter = {
|
||||
maxFrequency: "number",
|
||||
minFrequency: "number",
|
||||
maxStrength: "number",
|
||||
minStrength: "number"
|
||||
minStrength: "number",
|
||||
dirLimit: "boolean",
|
||||
angle: "number"
|
||||
}
|
||||
|
||||
type IBrownianBehaviorEvent = {}
|
||||
@ -28,9 +30,88 @@ class Brownian extends Behavior<IBrownianBehaviorParameter, IBrownianBehaviorEve
|
||||
maxFrequency: { type: "number", name: "$Max.Frequency", defaultValue: 5, numberStep: .1, numberMin: 0 },
|
||||
minFrequency: { type: "number", name: "$Min.Frequency", defaultValue: 0, numberStep: .1, numberMin: 0 },
|
||||
maxStrength: { type: "number", name: "$Max.Strength", defaultValue: 10, numberStep: .01, numberMin: 0 },
|
||||
minStrength: { type: "number", name: "$Min.Strength", defaultValue: 0, numberStep: .01, numberMin: 0 }
|
||||
minStrength: { type: "number", name: "$Min.Strength", defaultValue: 0, numberStep: .01, numberMin: 0 },
|
||||
dirLimit: { type: "boolean", name: "$Direction.Limit", defaultValue: false },
|
||||
angle: {
|
||||
type: "number", name: "$Angle", defaultValue: 180, numberStep: 5,
|
||||
numberMin: 0, numberMax: 360, condition: { key: "dirLimit", value: true }
|
||||
}
|
||||
};
|
||||
|
||||
private randomFocus360(): number[] {
|
||||
let randomVec = [
|
||||
Math.random() * 2 - 1,
|
||||
Math.random() * 2 - 1,
|
||||
Math.random() * 2 - 1
|
||||
];
|
||||
|
||||
let randomVecLen = (randomVec[0] ** 2 + randomVec[1] ** 2 + randomVec[2] ** 2) ** 0.5;
|
||||
return [randomVec[0] / randomVecLen, randomVec[1] / randomVecLen, randomVec[2] / randomVecLen];
|
||||
}
|
||||
|
||||
private rotateWithVec(vec: number[], r: number[], ang: number) {
|
||||
|
||||
const cos = Math.cos(ang); const sin = Math.sin(ang);
|
||||
const a1 = r[0] ?? 0; const a2 = r[1] ?? 0; const a3 = r[2] ?? 0;
|
||||
|
||||
return [
|
||||
(cos + (1 - cos) * a1 * a1) * (vec[0] ?? 0) +
|
||||
((1 - cos) * a1 * a2 - sin * a3) * (vec[1] ?? 0) +
|
||||
((1 - cos) * a1 * a3 + sin * a2) * (vec[2] ?? 0),
|
||||
|
||||
((1 - cos) * a1 * a2 + sin * a3) * (vec[0] ?? 0) +
|
||||
(cos + (1 - cos) * a2 * a2) * (vec[1] ?? 0) +
|
||||
((1 - cos) * a2 * a3 - sin * a1) * (vec[2] ?? 0),
|
||||
|
||||
((1 - cos) * a1 * a3 - sin * a2) * (vec[0] ?? 0) +
|
||||
((1 - cos) * a2 * a3 + sin * a1) * (vec[1] ?? 0) +
|
||||
(cos + (1 - cos) * a3 * a3) * (vec[2] ?? 0)
|
||||
]
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private randomFocusRange(dir: number[], angle: number): number[] {
|
||||
|
||||
// 计算 X-Z 投影
|
||||
let pxz = [dir[0] ?? 0, 0, dir[2] ?? 0];
|
||||
|
||||
// 通过叉乘计算垂直向量
|
||||
let dxz: number[];
|
||||
|
||||
// 如果投影向量没有长度,使用单位向量
|
||||
if (pxz[0] ** 2 + pxz[2] ** 2 === 0) {
|
||||
dxz = [0, 0, 1];
|
||||
}
|
||||
|
||||
// 通过叉乘计算垂直轴线
|
||||
else {
|
||||
dxz = [
|
||||
dir[1] * pxz[2] - pxz[1] * dir[2],
|
||||
dir[2] * pxz[0] - pxz[2] * dir[0],
|
||||
dir[0] * pxz[1] - pxz[0] * dir[1]
|
||||
];
|
||||
|
||||
let lenDxz = (dxz[0] ** 2 + dxz[1] ** 2 + dxz[2] ** 2) ** 0.5;
|
||||
dxz = [dxz[0] / lenDxz, dxz[1] / lenDxz, dxz[2] / lenDxz];
|
||||
}
|
||||
|
||||
// 航偏角 360 随机旋转
|
||||
let randomH = this.rotateWithVec(dxz, dir, Math.random() * Math.PI * 2);
|
||||
|
||||
// 俯仰角 180 * R 随机旋转
|
||||
let randomP = this.rotateWithVec(dir, randomH, (Math.random() - 0.5) * 2 * angle * Math.PI / 180);
|
||||
|
||||
return randomP;
|
||||
}
|
||||
|
||||
public effect = (individual: Individual, group: Group, model: Model, t: number): void => {
|
||||
|
||||
const {maxFrequency, minFrequency, maxStrength, minStrength} = this.parameter;
|
||||
@ -41,11 +122,49 @@ class Brownian extends Behavior<IBrownianBehaviorParameter, IBrownianBehaviorEve
|
||||
|
||||
currentTime += t;
|
||||
if (currentTime > 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<IBrownianBehaviorParameter, IBrownianBehaviorEve
|
||||
"$Min.Strength": {
|
||||
"ZH_CN": "最小强度",
|
||||
"EN_US": "Minimum strength"
|
||||
}
|
||||
},
|
||||
"$Direction.Limit": {
|
||||
"ZH_CN": "开启角度限制",
|
||||
"EN_US": "Enable limit angle"
|
||||
},
|
||||
"$Angle": {
|
||||
"ZH_CN": "限制立体角 (deg)",
|
||||
"EN_US": "Restricted solid angle (deg)"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { Model } from "@Model/Model";
|
||||
type IContactAssimilateBehaviorParameter = {
|
||||
target: "CLG",
|
||||
assimilate: "CG",
|
||||
self: "CG",
|
||||
success: "number",
|
||||
range: "number"
|
||||
}
|
||||
@ -27,15 +28,13 @@ class ContactAssimilate extends Behavior<IContactAssimilateBehaviorParameter, IC
|
||||
public override parameterOption = {
|
||||
target: { type: "CLG", name: "$Target" },
|
||||
assimilate: { type: "CG", name: "$Assimilate" },
|
||||
self: { type: "CG", name: "$Self" },
|
||||
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];
|
||||
|
||||
@ -50,7 +49,18 @@ class ContactAssimilate extends Behavior<IContactAssimilateBehaviorParameter, IC
|
||||
|
||||
// 成功判定
|
||||
if (Math.random() * 100 < this.parameter.success) {
|
||||
targetIndividual.transfer(assimilateGroup!);
|
||||
|
||||
// 同化目标
|
||||
let assimilateGroup = this.parameter.assimilate.objects;
|
||||
if (assimilateGroup) {
|
||||
targetIndividual.transfer(assimilateGroup);
|
||||
}
|
||||
|
||||
// 同化目标
|
||||
let selfGroup = this.parameter.self.objects;
|
||||
if (selfGroup) {
|
||||
individual.transfer(selfGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -70,6 +80,10 @@ class ContactAssimilate extends Behavior<IContactAssimilateBehaviorParameter, IC
|
||||
"ZH_CN": "到哪个群...",
|
||||
"EN_US": "To group..."
|
||||
},
|
||||
"$Self": {
|
||||
"ZH_CN": "自身同化到...",
|
||||
"EN_US": "Self assimilate to..."
|
||||
},
|
||||
"$Range": {
|
||||
"ZH_CN": "同化范围 (m)",
|
||||
"EN_US": "Assimilate range (m)"
|
||||
|
@ -6,7 +6,8 @@ import { Model } from "@Model/Model";
|
||||
type IContactAttackingBehaviorParameter = {
|
||||
target: "CLG",
|
||||
success: "number",
|
||||
range: "number"
|
||||
range: "number",
|
||||
assimilate: "CG",
|
||||
}
|
||||
|
||||
type IContactAttackingBehaviorEvent = {}
|
||||
@ -26,7 +27,8 @@ class ContactAttacking extends Behavior<IContactAttackingBehaviorParameter, ICon
|
||||
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 }
|
||||
success: { type: "number", name: "$Success", defaultValue: 90, numberMin: 0, numberMax: 100, numberStep: 5 },
|
||||
assimilate: { type: "CG", name: "$Assimilate"}
|
||||
};
|
||||
|
||||
public effect = (individual: Individual, group: Group, model: Model, t: number): void => {
|
||||
@ -46,6 +48,10 @@ class ContactAttacking extends Behavior<IContactAttackingBehaviorParameter, ICon
|
||||
// 成功判定
|
||||
if (Math.random() * 100 < this.parameter.success) {
|
||||
targetIndividual.die();
|
||||
|
||||
if (this.parameter.assimilate?.objects) {
|
||||
individual.transfer(this.parameter.assimilate.objects);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -72,6 +78,10 @@ class ContactAttacking extends Behavior<IContactAttackingBehaviorParameter, ICon
|
||||
"$Intro": {
|
||||
"ZH_CN": "攻击进入共进范围的目标群个体",
|
||||
"EN_US": "Attack the target group and individual entering the range"
|
||||
},
|
||||
"$Assimilate": {
|
||||
"ZH_CN": "同化",
|
||||
"EN_US": "Assimilate"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
83
source/Behavior/Manufacture.ts
Normal file
83
source/Behavior/Manufacture.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { Group } from "@Model/Group";
|
||||
import { Individual } from "@Model/Individual";
|
||||
import { Model } from "@Model/Model";
|
||||
|
||||
type IManufactureBehaviorParameter = {
|
||||
maxFrequency: "number",
|
||||
minFrequency: "number",
|
||||
genTarget: "CG",
|
||||
}
|
||||
|
||||
type IManufactureBehaviorEvent = {}
|
||||
|
||||
class Manufacture extends Behavior<IManufactureBehaviorParameter, IManufactureBehaviorEvent> {
|
||||
|
||||
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<string, Record<string, string>> = {
|
||||
"$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 };
|
160
source/Behavior/SampleTracking.ts
Normal file
160
source/Behavior/SampleTracking.ts
Normal file
@ -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<ISampleTrackingBehaviorParameter, ISampleTrackingBehaviorEvent> {
|
||||
|
||||
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<string, Record<string, string>> = {
|
||||
"$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 };
|
@ -7,6 +7,7 @@ type ITrackingBehaviorParameter = {
|
||||
target: "CLG",
|
||||
strength: "number",
|
||||
range: "number",
|
||||
angle: "number",
|
||||
lock: "boolean"
|
||||
}
|
||||
|
||||
@ -28,12 +29,23 @@ class Tracking extends Behavior<ITrackingBehaviorParameter, ITrackingBehaviorEve
|
||||
target: { type: "CLG", name: "$Target" },
|
||||
lock: { type: "boolean", name: "$Lock", defaultValue: false },
|
||||
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 }
|
||||
};
|
||||
|
||||
private target: Individual | undefined = undefined;
|
||||
private currentDistant: number = Infinity;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private searchTarget(individual: Individual) {
|
||||
|
||||
for (let i = 0; i < this.parameter.target.objects.length; i++) {
|
||||
@ -46,8 +58,21 @@ class Tracking extends Behavior<ITrackingBehaviorParameter, ITrackingBehaviorEve
|
||||
let dis = targetIndividual.distanceTo(individual);
|
||||
|
||||
if (dis < this.currentDistant && dis <= this.parameter.range) {
|
||||
this.target = targetIndividual;
|
||||
this.currentDistant = dis;
|
||||
|
||||
// 计算目标方位
|
||||
const targetDir = [
|
||||
targetIndividual.position[0] - individual.position[0],
|
||||
targetIndividual.position[1] - individual.position[1],
|
||||
targetIndividual.position[2] - individual.position[2]
|
||||
];
|
||||
|
||||
// 计算角度
|
||||
const angle = this.angle2Vector(individual.velocity, targetDir);
|
||||
|
||||
if (angle < (this.parameter.angle ?? 360) / 2) {
|
||||
this.target = targetIndividual;
|
||||
this.currentDistant = dis;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@ -150,7 +175,11 @@ class Tracking extends Behavior<ITrackingBehaviorParameter, ITrackingBehaviorEve
|
||||
"$Interactive": {
|
||||
"ZH_CN": "交互",
|
||||
"EN_US": "Interactive"
|
||||
}
|
||||
},
|
||||
"$Angle": {
|
||||
"ZH_CN": "可视角度",
|
||||
"EN_US": "Viewing angle"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
100
source/Behavior/Wastage.ts
Normal file
100
source/Behavior/Wastage.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { Behavior } from "@Model/Behavior";
|
||||
import { Group } from "@Model/Group";
|
||||
import { Individual } from "@Model/Individual";
|
||||
import { Model } from "@Model/Model";
|
||||
|
||||
type IWastageBehaviorParameter = {
|
||||
key: "string",
|
||||
value: "number",
|
||||
speed: "number",
|
||||
threshold: "number",
|
||||
kill: "boolean",
|
||||
assimilate: "CG"
|
||||
}
|
||||
|
||||
type IWastageBehaviorEvent = {}
|
||||
|
||||
class Wastage extends Behavior<IWastageBehaviorParameter, IWastageBehaviorEvent> {
|
||||
|
||||
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<string, Record<string, string>> = {
|
||||
"$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 };
|
Loading…
Reference in New Issue
Block a user