From c811b5b1e59db1b20a4e430044f7b7d7be54ad26 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 30 Mar 2022 12:34:10 +0800 Subject: [PATCH 1/4] Add actuator model --- source/Component/CommandBar/CommandBar.tsx | 10 +- source/Component/HeaderBar/HeaderBar.tsx | 2 +- source/Context/Status.tsx | 15 ++- source/Model/Actuator.ts | 125 +++++++++++++++++++++ source/Model/Model.ts | 3 - 5 files changed, 148 insertions(+), 7 deletions(-) create mode 100644 source/Model/Actuator.ts diff --git a/source/Component/CommandBar/CommandBar.tsx b/source/Component/CommandBar/CommandBar.tsx index 4ae4121..2bb2f22 100644 --- a/source/Component/CommandBar/CommandBar.tsx +++ b/source/Component/CommandBar/CommandBar.tsx @@ -15,7 +15,7 @@ interface ICommandBarProps { } @useSetting -@useStatusWithEvent("mouseModChange") +@useStatusWithEvent("mouseModChange", "actuatorStartChange") class CommandBar extends Component { render(): ReactNode { @@ -34,7 +34,13 @@ class CommandBar extends Component
{this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })} - {this.getRenderButton({ iconName: "Play", i18NKey: "Command.Bar.Play.Info" })} + {this.getRenderButton({ + iconName: this.props.status?.actuator.start() ? "Pause" : "Play", + i18NKey: "Command.Bar.Play.Info", + click: () => this.props.status ? this.props.status.actuator.start( + !this.props.status.actuator.start() + ) : undefined + })} {this.getRenderButton({ iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info", active: mouseMod === MouseMod.Drag, diff --git a/source/Component/HeaderBar/HeaderBar.tsx b/source/Component/HeaderBar/HeaderBar.tsx index 5b51313..0eb6d2e 100644 --- a/source/Component/HeaderBar/HeaderBar.tsx +++ b/source/Component/HeaderBar/HeaderBar.tsx @@ -42,7 +42,7 @@ class HeaderBar extends Component< return (t: number) => { let newState: HeaderBarState = {} as any; newState[type] = 1 / t; - if (this.updateTime > 60) { + if (this.updateTime > 20) { this.updateTime = 0; this.setState(newState); } diff --git a/source/Context/Status.tsx b/source/Context/Status.tsx index b7d9069..91a2f6b 100644 --- a/source/Context/Status.tsx +++ b/source/Context/Status.tsx @@ -12,6 +12,7 @@ import { I18N } from "@Component/Localization/Localization"; import { superConnectWithEvent, superConnect } from "./Context"; import { PopupController } from "./Popups"; import { Behavior } from "@Model/Behavior"; +import { Actuator } from "@Model/Actuator"; function randomColor(unNormal: boolean = false) { const color = [ @@ -44,6 +45,7 @@ interface IStatusEvent { individualChange: void; behaviorChange: void; popupChange: void; + actuatorStartChange: void; } class Status extends Emitter { @@ -71,6 +73,11 @@ class Status extends Emitter { */ public model: Model = new Model(); + /** + * 执行器 + */ + public actuator: Actuator; + /** * 弹窗 */ @@ -104,8 +111,14 @@ class Status extends Emitter { public constructor() { super(); + // 初始化执行器 + this.actuator = new Actuator(this.model); + + // 执行器开启事件 + this.actuator.on("startChange", () => { this.emit("actuatorStartChange") }); + // 循环事件 - this.model.on("loop", (t) => { this.emit("physicsLoop", t) }); + this.actuator.on("loop", (t) => { this.emit("physicsLoop", t) }); // 对象变化事件 this.model.on("objectChange", () => this.emit("objectChange")); diff --git a/source/Model/Actuator.ts b/source/Model/Actuator.ts new file mode 100644 index 0000000..83ba8ad --- /dev/null +++ b/source/Model/Actuator.ts @@ -0,0 +1,125 @@ +import { Model } from "./Model"; +import { Emitter } from "./Emitter"; + +interface IActuatorEvent { + startChange: boolean; + loop: number; +} + +/** + * 模型执行器 + */ +class Actuator extends Emitter { + + /** + * 速度系数 + */ + public speed: number = 1; + + /** + * 模拟帧率 + */ + public fps: number = 24; + + /** + * 仿真是否进行 + */ + private startFlag: boolean = false; + + /** + * 主时钟状态控制 + */ + public start(start?: boolean): boolean { + if (start === undefined) { + return this.startFlag; + } else { + this.startFlag = start; + this.lastTime = 0; + this.alignTimer = 0; + this.emit("startChange", start); + return start; + } + } + + /** + * 绑定模型 + */ + public model: Model; + + /** + * 上一帧的时间 + */ + private lastTime: number = 0; + + /** + * 对其计时器 + */ + private alignTimer: number = 0; + + public tickerType: 1 | 2 = 1; + + private ticker(t: number) { + if (this.startFlag && t !== 0) { + if (this.lastTime === 0) { + this.lastTime = t; + } else { + let durTime = (t - this.lastTime) / 1000; + this.lastTime = t; + + // 丢帧判定 + if (durTime > 0.1) { + console.log("Actuator: Ticker dur time error. dropping...") + } + + this.alignTimer += durTime; + if (this.alignTimer > (1 / this.fps)) { + this.model.update(this.alignTimer * this.speed); + this.emit("loop", this.alignTimer); + this.alignTimer = 0; + } + } + } else { + this.emit("loop", Infinity); + } + } + + /** + * 帧率对其时钟 + * 1、使用 requestAnimationFrame 保证最高的性能 + * 2、最大模拟帧率只能小于 60 + * 3、可能出现帧率对其问题 + */ + private tickerAlign = (t: number) => { + this.ticker(t); + requestAnimationFrame(this.tickerAlign); + } + + /** + * 精确时钟 + */ + private tickerExp = () => { + this.ticker(window.performance.now()); + setTimeout(this.tickerExp, (1 / this.fps) * 1000); + } + + /** + * 执行器 + */ + private runTicker = (t: number) => { + if (this.tickerType === 1) { + this.ticker(t); + requestAnimationFrame(this.runTicker); + } else { + this.ticker(window.performance.now()); + setTimeout(this.runTicker, (1 / this.fps) * 1000); + } + } + + public constructor(model: Model) { + super(); + this.model = model; + this.runTicker(0); + } +} + +export { Actuator } \ No newline at end of file diff --git a/source/Model/Model.ts b/source/Model/Model.ts index a28f07b..23ad788 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -8,7 +8,6 @@ import { Label } from "./Label"; import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior"; type ModelEvent = { - loop: number; labelChange: Label[]; objectChange: CtrlObject[]; individualChange: Group; @@ -280,8 +279,6 @@ class Model extends Emitter { } this.draw(); - - this.emit("loop", t); } public draw() { From e5ae05e737df852a7b32a3773ac15532f6b68c6e Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 30 Mar 2022 16:19:29 +0800 Subject: [PATCH 2/4] Add dynamics behavior --- source/Behavior/Behavior.ts | 13 +- source/Behavior/Dynamics.ts | 122 ++++++++++++++++++ .../Component/BehaviorList/BehaviorList.tsx | 2 +- source/Model/Behavior.ts | 9 +- source/Model/Group.ts | 2 +- source/Model/Individual.ts | 49 +++++-- source/Model/Model.ts | 6 +- 7 files changed, 179 insertions(+), 24 deletions(-) create mode 100644 source/Behavior/Dynamics.ts diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts index 123f8b3..dce8051 100644 --- a/source/Behavior/Behavior.ts +++ b/source/Behavior/Behavior.ts @@ -1,13 +1,10 @@ import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; import { Template } from "./Template"; +import { Dynamics } from "./Dynamics"; -const AllBehaviors: IAnyBehaviorRecorder[] = new Array(4).fill(0).map((_, i) => { - let behavior = new BehaviorRecorder(Template); - behavior.behaviorId = behavior.behaviorId + i; - behavior.behaviorName = behavior.behaviorName + Math.random().toString(36).slice(-6); - behavior.category = "Category" + Math.floor(Math.random() * 3).toString(); - return behavior; -}); +const AllBehaviors: IAnyBehaviorRecorder[] = [ + new BehaviorRecorder(Dynamics) +] /** * 分类词条 @@ -52,4 +49,6 @@ function categoryBehaviors(behaviors: IAnyBehaviorRecorder[]): ICategory[] { return res; } +console.log(AllBehaviorsWithCategory) + export { AllBehaviors, AllBehaviorsWithCategory, ICategory as ICategoryBehavior }; \ No newline at end of file diff --git a/source/Behavior/Dynamics.ts b/source/Behavior/Dynamics.ts new file mode 100644 index 0000000..d944fc4 --- /dev/null +++ b/source/Behavior/Dynamics.ts @@ -0,0 +1,122 @@ +import { Behavior } from "@Model/Behavior"; +import Group from "@Model/Group"; +import Individual from "@Model/Individual"; +import { Model } from "@Model/Model"; + +type IDynamicsBehaviorParameter = { + mass: "number", + maxAcceleration: "number", + maxVelocity: "number", + resistance: "number" +} + +type IDynamicsBehaviorEvent = {} + +class Dynamics extends Behavior { + + public override behaviorId: string = "Dynamics"; + + public override behaviorName: string = "$Title"; + + public override iconName: string = "Running"; + + public override describe: string = "$Intro"; + + public override category: string = "$Physics"; + + public override parameterOption = { + mass: { + name: "$Mass", + type: "number", + defaultValue: 1, + numberStep: .01, + numberMin: .001 + }, + maxAcceleration: { + name: "$Max.Acceleration", + type: "number", + defaultValue: 0.5, + numberStep: .01, + numberMin: 0 + }, + maxVelocity: { + name: "$Max.Velocity", + type: "number", + defaultValue: 0.5, + numberStep: .01, + numberMin: 0 + }, + resistance: { + name: "$Resistance", + type: "number", + defaultValue: .01, + numberStep: .001, + numberMin: 0 + } + }; + + public override terms: Record> = { + "$Title": { + "ZH_CN": "动力学", + "EN_US": "Dynamics" + }, + "$Intro": { + "ZH_CN": "一切可以运动物体的必要行为,执行物理法则。", + "EN_US": "All necessary behaviors that can move objects and implement the laws of physics." + }, + "$Mass": { + "ZH_CN": "质量 (Kg)", + "EN_US": "Mass (Kg)" + }, + "$Max.Acceleration": { + "ZH_CN": "最大加速度 (m/s²)", + "EN_US": "Maximum acceleration (m/s²)" + }, + "$Max.Velocity": { + "ZH_CN": "最大速度 (m/s)", + "EN_US": "Maximum velocity (m/s)" + }, + "$Physics": { + "ZH_CN": "物理", + "EN_US": "Physics" + } + }; + + public override finalEffect(individual: Individual, group: Group, model: Model, t: number): void { + + // 计算当前速度 + const currentV = individual.vectorLength(individual.velocity); + + // 计算阻力 + const resistance = Math.max(1 - currentV * currentV * this.parameter.resistance, 0); + + // 计算加速度 + individual.acceleration[0] = individual.force[0] * resistance / this.parameter.mass; + individual.acceleration[1] = individual.force[1] * resistance / this.parameter.mass; + individual.acceleration[2] = individual.force[2] * resistance / this.parameter.mass; + + // 加速度约束 + const overA = Math.max(individual.vectorLength(individual.acceleration) - this.parameter.maxAcceleration, 0); + individual.acceleration[0] = individual.acceleration[0] - individual.acceleration[0] * overA; + individual.acceleration[1] = individual.acceleration[1] - individual.acceleration[1] * overA; + individual.acceleration[2] = individual.acceleration[2] - individual.acceleration[2] * overA; + + // 计算速度 + individual.velocity[0] = individual.velocity[0] + individual.acceleration[0] * t; + individual.velocity[1] = individual.velocity[1] + individual.acceleration[1] * t; + individual.velocity[2] = individual.velocity[2] + individual.acceleration[2] * t; + + // 速度约束 + const overV = Math.max(individual.vectorLength(individual.velocity) - this.parameter.maxVelocity, 0); + individual.velocity[0] = individual.velocity[0] - individual.velocity[0] * overV; + individual.velocity[1] = individual.velocity[1] - individual.velocity[1] * overV; + individual.velocity[2] = individual.velocity[2] - individual.velocity[2] * overV; + + // 应用速度 + individual.position[0] = individual.position[0] + individual.velocity[0] * t; + individual.position[1] = individual.position[1] + individual.velocity[1] * t; + individual.position[2] = individual.position[2] + individual.velocity[2] * t; + }; +} + +export { Dynamics }; \ No newline at end of file diff --git a/source/Component/BehaviorList/BehaviorList.tsx b/source/Component/BehaviorList/BehaviorList.tsx index e9624a7..5308c49 100644 --- a/source/Component/BehaviorList/BehaviorList.tsx +++ b/source/Component/BehaviorList/BehaviorList.tsx @@ -120,7 +120,7 @@ class BehaviorList extends Component {
{this.renderTerm(behavior, name, "title-view", needLocal)} - {this.renderTerm(behavior, info, "info-view", needLocal)} + {this.renderTerm(behavior, info, "info-view", true)}
{this.renderActionButton(behavior)} diff --git a/source/Model/Behavior.ts b/source/Model/Behavior.ts index dbc7d58..20f7acf 100644 --- a/source/Model/Behavior.ts +++ b/source/Model/Behavior.ts @@ -64,7 +64,7 @@ interface IBehaviorParameterOptionItem { /** * 参数类型 */ - type: T; + type: T | string; /** * 参数默认值 @@ -269,6 +269,7 @@ class BehaviorRecorder< this.behaviorId = this.behaviorInstance.behaviorId; this.behaviorName = this.behaviorInstance.behaviorName; this.describe = this.behaviorInstance.describe; + this.category = this.behaviorInstance.category; this.terms = this.behaviorInstance.terms; } } @@ -371,7 +372,7 @@ class Behavior< * @param model 模型 * @param t 经过时间 */ - public beforeEffect(individual: Individual, group: Group, model: Model, t: number): void {}; + public effect(individual: Individual, group: Group, model: Model, t: number): void {}; /** * 作用影响于个体 @@ -380,7 +381,7 @@ class Behavior< * @param model 模型 * @param t 经过时间 */ - public effect(individual: Individual, group: Group, model: Model, t: number): void {}; + public afterEffect(individual: Individual, group: Group, model: Model, t: number): void {}; /** * 全部影响作用后 @@ -389,7 +390,7 @@ class Behavior< * @param model 模型 * @param t 经过时间 */ - public afterEffect(individual: Individual, group: Group, model: Model, t: number): void {}; + public finalEffect(individual: Individual, group: Group, model: Model, t: number): void {}; } diff --git a/source/Model/Group.ts b/source/Model/Group.ts index 5e81e0f..21dcf27 100644 --- a/source/Model/Group.ts +++ b/source/Model/Group.ts @@ -330,7 +330,7 @@ class Group extends CtrlObject { * 执行行为影响 * @param */ - public runner(t: number, effectType: "beforeEffect" | "effect" | "afterEffect" ): void { + public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void { this.individuals.forEach((individual) => { for(let j = 0; j < this.behaviors.length; j++) { this.behaviors[j][effectType](individual, this, this.model, t); diff --git a/source/Model/Individual.ts b/source/Model/Individual.ts index 496a565..092edde 100644 --- a/source/Model/Individual.ts +++ b/source/Model/Individual.ts @@ -12,9 +12,9 @@ class Individual { * @param y y 坐标 * @param z z 坐标 */ - public static vectorLength(x: number[]): number; - public static vectorLength(x: number, y: number, z: number): number; - public static vectorLength(x: number | number[], y?: number, z?: number): number { + public vectorLength(x: number[]): number; + public vectorLength(x: number, y: number, z: number): number; + public vectorLength(x: number | number[], y?: number, z?: number): number { if (Array.isArray(x)) { return ((x[0] ?? 0)**2 + (x[1] ?? 0)**2 + (x[2] ?? 0)**2)**.5; } else { @@ -28,10 +28,10 @@ class Individual { * @param y y 坐标 * @param z z 坐标 */ - public static vectorNormalize(x: number[]): [number, number, number]; - public static vectorNormalize(x: number, y: number, z: number): [number, number, number]; - public static vectorNormalize(x: number | number[], y?: number, z?: number): [number, number, number] { - let length = Individual.vectorLength(x as number, y as number, z as number); + public vectorNormalize(x: number[]): [number, number, number]; + public vectorNormalize(x: number, y: number, z: number): [number, number, number]; + public vectorNormalize(x: number | number[], y?: number, z?: number): [number, number, number] { + let length = this.vectorLength(x as number, y as number, z as number); if (Array.isArray(x)) { return [ (x[0] ?? 0) / length, @@ -52,6 +52,39 @@ class Individual { */ public position: number[] = [0, 0, 0]; + /** + * 速度 + */ + public velocity: number[] = [0, 0, 0]; + + /** + * 加速度 + */ + public acceleration: number[] = [0, 0, 0]; + + /** + * 作用力 + */ + public force: number[] = [0, 0, 0]; + + /** + * 施加力 + */ + public applyForce(x: number[]): [number, number, number]; + public applyForce(x: number, y: number, z: number): [number, number, number]; + public applyForce(x: number | number[], y?: number, z?: number): [number, number, number] { + if (Array.isArray(x)) { + this.force[0] += x[0] ?? 0; + this.force[1] += x[1] ?? 0; + this.force[2] += x[2] ?? 0; + } else { + this.force[0] += x ?? 0; + this.force[1] += y ?? 0; + this.force[2] += z ?? 0; + } + return this.force as [number, number, number]; + } + /** * 所属群组 */ @@ -107,7 +140,7 @@ class Individual { * @param position 目标位置 */ public distanceTo(position: Individual | number[]): number { - return Individual.vectorLength(this.vectorTo(position)); + return this.vectorLength(this.vectorTo(position)); } /** diff --git a/source/Model/Model.ts b/source/Model/Model.ts index 23ad788..8c0b06c 100644 --- a/source/Model/Model.ts +++ b/source/Model/Model.ts @@ -258,7 +258,7 @@ class Model extends Emitter { for (let i = 0; i < this.objectPool.length; i++) { let object = this.objectPool[i]; if (object instanceof Group && object.update) { - object.runner(t, "beforeEffect"); + object.runner(t, "effect"); } } @@ -266,7 +266,7 @@ class Model extends Emitter { for (let i = 0; i < this.objectPool.length; i++) { let object = this.objectPool[i]; if (object instanceof Group && object.update) { - object.runner(t, "effect"); + object.runner(t, "afterEffect"); } } @@ -274,7 +274,7 @@ class Model extends Emitter { for (let i = 0; i < this.objectPool.length; i++) { let object = this.objectPool[i]; if (object instanceof Group && object.update) { - object.runner(t, "afterEffect"); + object.runner(t, "finalEffect"); } } From 17ee906f90ba382fa6930bedd02f142f6f9ff1a5 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Wed, 30 Mar 2022 17:19:45 +0800 Subject: [PATCH 3/4] Add brownian behavior --- source/Behavior/Behavior.ts | 4 +- source/Behavior/Brownian.ts | 109 ++++++++++++++++++++++ source/Behavior/Template.ts | 11 ++- source/Model/Behavior.ts | 2 +- source/Page/SimulatorWeb/SimulatorWeb.tsx | 7 ++ 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 source/Behavior/Brownian.ts diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts index dce8051..90e7119 100644 --- a/source/Behavior/Behavior.ts +++ b/source/Behavior/Behavior.ts @@ -1,9 +1,11 @@ import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; import { Template } from "./Template"; import { Dynamics } from "./Dynamics"; +import { Brownian } from "./Brownian"; const AllBehaviors: IAnyBehaviorRecorder[] = [ - new BehaviorRecorder(Dynamics) + new BehaviorRecorder(Dynamics), + new BehaviorRecorder(Brownian) ] /** diff --git a/source/Behavior/Brownian.ts b/source/Behavior/Brownian.ts new file mode 100644 index 0000000..094d00b --- /dev/null +++ b/source/Behavior/Brownian.ts @@ -0,0 +1,109 @@ +import { Behavior } from "@Model/Behavior"; +import { Group } from "@Model/Group"; +import { Individual } from "@Model/Individual"; +import { Model } from "@Model/Model"; + +type IBrownianBehaviorParameter = { + maxFrequency: "number", + minFrequency: "number", + maxStrength: "number", + minStrength: "number" +} + +type IBrownianBehaviorEvent = {} + +class Brownian extends Behavior { + + public override behaviorId: string = "Brownian"; + + public override behaviorName: string = "$Title"; + + public override iconName: string = "Running"; + + public override describe: string = "$Intro"; + + public override category: string = "$Physics"; + + public override parameterOption = { + 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: .1, + numberStep: .01, + numberMin: 0 + }, + minStrength: { + type: "number", + name: "$Min.Strength", + defaultValue: 0, + numberStep: .01, + numberMin: 0 + } + }; + + public override terms: Record> = { + "$Title": { + "ZH_CN": "布朗运动", + "EN_US": "Brownian motion" + }, + "$Intro": { + "ZH_CN": "一种无规则的随机运动", + "EN_US": "An irregular random motion" + }, + "$Max.Frequency": { + "ZH_CN": "最大频率", + "EN_US": "Maximum frequency" + }, + "$Min.Frequency": { + "ZH_CN": "最小频率", + "EN_US": "Minimum frequency" + }, + "$Max.Strength": { + "ZH_CN": "最大强度", + "EN_US": "Maximum strength" + }, + "$Min.Strength": { + "ZH_CN": "最小强度", + "EN_US": "Minimum strength" + } + }; + + public effect(individual: Individual, group: Group, model: Model, t: number): void { + + const {maxFrequency, minFrequency, maxStrength, minStrength} = this.parameter; + + let nextTime = individual.getData("Brownian.nextTime") ?? + minFrequency + Math.random() * (maxFrequency - minFrequency); + let currentTime = individual.getData("Brownian.currentTime") ?? 0; + + currentTime += t; + if (currentTime > nextTime) { + individual.applyForce( + minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength), + minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength), + minStrength + (Math.random() * 2 - 1) * (maxStrength - minStrength) + ); + nextTime = minFrequency + Math.random() * (maxFrequency - minFrequency); + currentTime = 0; + } + + individual.setData("Brownian.nextTime", nextTime); + individual.setData("Brownian.currentTime", currentTime); + } +} + +export { Brownian }; \ No newline at end of file diff --git a/source/Behavior/Template.ts b/source/Behavior/Template.ts index fca9fd2..b862559 100644 --- a/source/Behavior/Template.ts +++ b/source/Behavior/Template.ts @@ -1,4 +1,7 @@ import { Behavior } from "@Model/Behavior"; +import { Group } from "@Model/Group"; +import { Individual } from "@Model/Individual"; +import { Model } from "@Model/Model"; type ITemplateBehaviorParameter = { @@ -16,7 +19,9 @@ class Template extends Behavior> = { + public override category: string = "$Category"; + + public override terms: Record> = { "$Title": { "ZH_CN": "行为", "EN_US": "Behavior" @@ -26,6 +31,10 @@ class Template extends Behavior Date: Wed, 30 Mar 2022 19:14:24 +0800 Subject: [PATCH 4/4] Add boundary constraint behavior --- source/Behavior/Behavior.ts | 6 +- source/Behavior/BoundaryConstraint.ts | 74 +++++++++++++++++++++++ source/Behavior/Brownian.ts | 2 +- source/Behavior/Dynamics.ts | 54 +++++++++++------ source/Model/Actuator.ts | 4 +- source/Page/SimulatorWeb/SimulatorWeb.tsx | 4 ++ 6 files changed, 120 insertions(+), 24 deletions(-) create mode 100644 source/Behavior/BoundaryConstraint.ts diff --git a/source/Behavior/Behavior.ts b/source/Behavior/Behavior.ts index 90e7119..0309072 100644 --- a/source/Behavior/Behavior.ts +++ b/source/Behavior/Behavior.ts @@ -2,10 +2,12 @@ import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior"; import { Template } from "./Template"; import { Dynamics } from "./Dynamics"; import { Brownian } from "./Brownian"; +import { BoundaryConstraint } from "./BoundaryConstraint"; const AllBehaviors: IAnyBehaviorRecorder[] = [ new BehaviorRecorder(Dynamics), - new BehaviorRecorder(Brownian) + new BehaviorRecorder(Brownian), + new BehaviorRecorder(BoundaryConstraint) ] /** @@ -51,6 +53,4 @@ function categoryBehaviors(behaviors: IAnyBehaviorRecorder[]): ICategory[] { return res; } -console.log(AllBehaviorsWithCategory) - export { AllBehaviors, AllBehaviorsWithCategory, ICategory as ICategoryBehavior }; \ No newline at end of file diff --git a/source/Behavior/BoundaryConstraint.ts b/source/Behavior/BoundaryConstraint.ts new file mode 100644 index 0000000..4e36d70 --- /dev/null +++ b/source/Behavior/BoundaryConstraint.ts @@ -0,0 +1,74 @@ +import { Behavior } from "@Model/Behavior"; +import { Group } from "@Model/Group"; +import { Individual } from "@Model/Individual"; +import { Label } from "@Model/Label"; +import { Model } from "@Model/Model"; +import { Range } from "@Model/Range"; + +type IBoundaryConstraintBehaviorParameter = { + range: "LR" +} + +type IBoundaryConstraintBehaviorEvent = {} + +class BoundaryConstraint extends Behavior { + + public override behaviorId: string = "BoundaryConstraint"; + + public override behaviorName: string = "$Title"; + + public override iconName: string = "Running"; + + public override describe: string = "$Intro"; + + public override category: string = "$Physics"; + + public override parameterOption = { + range: { + type: "LR", + name: "$range", + defaultValue: undefined + } + }; + + public override terms: Record> = { + "$Title": { + "ZH_CN": "边界约束", + "EN_US": "Boundary constraint" + }, + "$Intro": { + "ZH_CN": "个体越出边界后将主动返回", + "EN_US": "Individuals will return actively after crossing the border" + } + }; + + public effect(individual: Individual, group: Group, model: Model, t: number): void { + let rangeList: Range[] = []; + if (this.parameter.range instanceof Range) { + rangeList.push(this.parameter.range); + } + if (this.parameter.range instanceof Label) { + rangeList = model.getObjectByLabel(this.parameter.range).filter((obj) => { + return obj instanceof Range + }) as any; + } + for (let i = 0; i < rangeList.length; i++) { + + let rx = rangeList[i].position[0] - individual.position[0]; + let ry = rangeList[i].position[1] - individual.position[1]; + let rz = rangeList[i].position[2] - individual.position[2]; + + let ox = Math.abs(rx) > rangeList[i].radius[0]; + let oy = Math.abs(ry) > rangeList[i].radius[1]; + let oz = Math.abs(rz) > rangeList[i].radius[2]; + + individual.applyForce( + ox ? rx : 0, + oy ? ry : 0, + oz ? rz : 0 + ) + } + } +} + +export { BoundaryConstraint }; \ No newline at end of file diff --git a/source/Behavior/Brownian.ts b/source/Behavior/Brownian.ts index 094d00b..c468d20 100644 --- a/source/Behavior/Brownian.ts +++ b/source/Behavior/Brownian.ts @@ -42,7 +42,7 @@ class Brownian extends Behavior 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; @@ -107,15 +118,22 @@ class Dynamics extends Behavior 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; individual.position[1] = individual.position[1] + individual.velocity[1] * t; individual.position[2] = individual.position[2] + individual.velocity[2] * t; + + // 清除受力 + individual.force[0] = 0; + individual.force[1] = 0; + individual.force[2] = 0; }; } diff --git a/source/Model/Actuator.ts b/source/Model/Actuator.ts index 83ba8ad..64e93b5 100644 --- a/source/Model/Actuator.ts +++ b/source/Model/Actuator.ts @@ -19,7 +19,7 @@ class Actuator extends Emitter { /** * 模拟帧率 */ - public fps: number = 24; + public fps: number = 36; /** * 仿真是否进行 @@ -56,7 +56,7 @@ class Actuator extends Emitter { */ private alignTimer: number = 0; - public tickerType: 1 | 2 = 1; + public tickerType: 1 | 2 = 2; private ticker(t: number) { if (this.startFlag && t !== 0) { diff --git a/source/Page/SimulatorWeb/SimulatorWeb.tsx b/source/Page/SimulatorWeb/SimulatorWeb.tsx index 9a84e5d..a006f17 100644 --- a/source/Page/SimulatorWeb/SimulatorWeb.tsx +++ b/source/Page/SimulatorWeb/SimulatorWeb.tsx @@ -59,8 +59,12 @@ class SimulatorWeb extends Component { dynamic.name = "dynamic"; let brownian = this.status.model.addBehavior(AllBehaviors[1]); brownian.name = "brownian"; + let boundary = this.status.model.addBehavior(AllBehaviors[2]); + boundary.name = "boundary"; + boundary.parameter.range = this.status.model.allRangeLabel; group.addBehavior(dynamic); group.addBehavior(brownian); + group.addBehavior(boundary); } (window as any).s = this;