Merge pull request 'Add actuator & dynamics behavior & brownian behavior & boundary constraint behavior' (#30) from dev-mrkbear into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: http://git.mrkbear.com/MrKBear/living-together/pulls/30
This commit is contained in:
commit
0f7657ad83
@ -1,13 +1,14 @@
|
|||||||
import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior";
|
import { BehaviorRecorder, IAnyBehaviorRecorder } from "@Model/Behavior";
|
||||||
import { Template } from "./Template";
|
import { Template } from "./Template";
|
||||||
|
import { Dynamics } from "./Dynamics";
|
||||||
|
import { Brownian } from "./Brownian";
|
||||||
|
import { BoundaryConstraint } from "./BoundaryConstraint";
|
||||||
|
|
||||||
const AllBehaviors: IAnyBehaviorRecorder[] = new Array(4).fill(0).map((_, i) => {
|
const AllBehaviors: IAnyBehaviorRecorder[] = [
|
||||||
let behavior = new BehaviorRecorder(Template);
|
new BehaviorRecorder(Dynamics),
|
||||||
behavior.behaviorId = behavior.behaviorId + i;
|
new BehaviorRecorder(Brownian),
|
||||||
behavior.behaviorName = behavior.behaviorName + Math.random().toString(36).slice(-6);
|
new BehaviorRecorder(BoundaryConstraint)
|
||||||
behavior.category = "Category" + Math.floor(Math.random() * 3).toString();
|
]
|
||||||
return behavior;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分类词条
|
* 分类词条
|
||||||
|
74
source/Behavior/BoundaryConstraint.ts
Normal file
74
source/Behavior/BoundaryConstraint.ts
Normal file
@ -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<IBoundaryConstraintBehaviorParameter, IBoundaryConstraintBehaviorEvent> {
|
||||||
|
|
||||||
|
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<string, Record<string, string>> = {
|
||||||
|
"$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 };
|
109
source/Behavior/Brownian.ts
Normal file
109
source/Behavior/Brownian.ts
Normal file
@ -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<IBrownianBehaviorParameter, IBrownianBehaviorEvent> {
|
||||||
|
|
||||||
|
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: 10,
|
||||||
|
numberStep: .01,
|
||||||
|
numberMin: 0
|
||||||
|
},
|
||||||
|
minStrength: {
|
||||||
|
type: "number",
|
||||||
|
name: "$Min.Strength",
|
||||||
|
defaultValue: 0,
|
||||||
|
numberStep: .01,
|
||||||
|
numberMin: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public override terms: Record<string, Record<string, string>> = {
|
||||||
|
"$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 };
|
140
source/Behavior/Dynamics.ts
Normal file
140
source/Behavior/Dynamics.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
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<IDynamicsBehaviorParameter, IDynamicsBehaviorEvent> {
|
||||||
|
|
||||||
|
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: 5,
|
||||||
|
numberStep: .1,
|
||||||
|
numberMin: 0
|
||||||
|
},
|
||||||
|
maxVelocity: {
|
||||||
|
name: "$Max.Velocity",
|
||||||
|
type: "number",
|
||||||
|
defaultValue: 10,
|
||||||
|
numberStep: .1,
|
||||||
|
numberMin: 0
|
||||||
|
},
|
||||||
|
resistance: {
|
||||||
|
name: "$Resistance",
|
||||||
|
type: "number",
|
||||||
|
defaultValue: 0.1,
|
||||||
|
numberStep: .1,
|
||||||
|
numberMin: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public override terms: Record<string, Record<string, string>> = {
|
||||||
|
"$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 = currentV * currentV * this.parameter.resistance;
|
||||||
|
|
||||||
|
// 应用阻力
|
||||||
|
if (currentV) {
|
||||||
|
individual.applyForce(
|
||||||
|
(- individual.velocity[0] / currentV) * resistance,
|
||||||
|
(- individual.velocity[1] / currentV) * resistance,
|
||||||
|
(- individual.velocity[2] / currentV) * resistance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算加速度
|
||||||
|
individual.acceleration[0] = individual.force[0] / this.parameter.mass;
|
||||||
|
individual.acceleration[1] = individual.force[1] / this.parameter.mass;
|
||||||
|
individual.acceleration[2] = individual.force[2] / this.parameter.mass;
|
||||||
|
|
||||||
|
// 加速度约束
|
||||||
|
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;
|
||||||
|
individual.velocity[1] = individual.velocity[1] + individual.acceleration[1] * t;
|
||||||
|
individual.velocity[2] = individual.velocity[2] + individual.acceleration[2] * t;
|
||||||
|
|
||||||
|
// 速度约束
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Dynamics };
|
@ -1,4 +1,7 @@
|
|||||||
import { Behavior } from "@Model/Behavior";
|
import { Behavior } from "@Model/Behavior";
|
||||||
|
import { Group } from "@Model/Group";
|
||||||
|
import { Individual } from "@Model/Individual";
|
||||||
|
import { Model } from "@Model/Model";
|
||||||
|
|
||||||
type ITemplateBehaviorParameter = {
|
type ITemplateBehaviorParameter = {
|
||||||
|
|
||||||
@ -16,7 +19,9 @@ class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEve
|
|||||||
|
|
||||||
public override describe: string = "$Intro";
|
public override describe: string = "$Intro";
|
||||||
|
|
||||||
terms: Record<string, Record<string, string>> = {
|
public override category: string = "$Category";
|
||||||
|
|
||||||
|
public override terms: Record<string, Record<string, string>> = {
|
||||||
"$Title": {
|
"$Title": {
|
||||||
"ZH_CN": "行为",
|
"ZH_CN": "行为",
|
||||||
"EN_US": "Behavior"
|
"EN_US": "Behavior"
|
||||||
@ -26,6 +31,10 @@ class Template extends Behavior<ITemplateBehaviorParameter, ITemplateBehaviorEve
|
|||||||
"EN_US": "This is a template behavior"
|
"EN_US": "This is a template behavior"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public effect(individual: Individual, group: Group, model: Model, t: number): void {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Template };
|
export { Template };
|
@ -120,7 +120,7 @@ class BehaviorList extends Component<IBehaviorListProps & IMixinSettingProps> {
|
|||||||
</div>
|
</div>
|
||||||
<div className="behavior-content-view">
|
<div className="behavior-content-view">
|
||||||
{this.renderTerm(behavior, name, "title-view", needLocal)}
|
{this.renderTerm(behavior, name, "title-view", needLocal)}
|
||||||
{this.renderTerm(behavior, info, "info-view", needLocal)}
|
{this.renderTerm(behavior, info, "info-view", true)}
|
||||||
</div>
|
</div>
|
||||||
<div className="behavior-action-view">
|
<div className="behavior-action-view">
|
||||||
{this.renderActionButton(behavior)}
|
{this.renderActionButton(behavior)}
|
||||||
|
@ -15,7 +15,7 @@ interface ICommandBarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@useSetting
|
@useSetting
|
||||||
@useStatusWithEvent("mouseModChange")
|
@useStatusWithEvent("mouseModChange", "actuatorStartChange")
|
||||||
class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixinStatusProps> {
|
class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixinStatusProps> {
|
||||||
|
|
||||||
render(): ReactNode {
|
render(): ReactNode {
|
||||||
@ -34,7 +34,13 @@ class CommandBar extends Component<ICommandBarProps & IMixinSettingProps & IMixi
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{this.getRenderButton({ iconName: "Save", i18NKey: "Command.Bar.Save.Info" })}
|
{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({
|
{this.getRenderButton({
|
||||||
iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info",
|
iconName: "HandsFree", i18NKey: "Command.Bar.Drag.Info",
|
||||||
active: mouseMod === MouseMod.Drag,
|
active: mouseMod === MouseMod.Drag,
|
||||||
|
@ -42,7 +42,7 @@ class HeaderBar extends Component<
|
|||||||
return (t: number) => {
|
return (t: number) => {
|
||||||
let newState: HeaderBarState = {} as any;
|
let newState: HeaderBarState = {} as any;
|
||||||
newState[type] = 1 / t;
|
newState[type] = 1 / t;
|
||||||
if (this.updateTime > 60) {
|
if (this.updateTime > 20) {
|
||||||
this.updateTime = 0;
|
this.updateTime = 0;
|
||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import { I18N } from "@Component/Localization/Localization";
|
|||||||
import { superConnectWithEvent, superConnect } from "./Context";
|
import { superConnectWithEvent, superConnect } from "./Context";
|
||||||
import { PopupController } from "./Popups";
|
import { PopupController } from "./Popups";
|
||||||
import { Behavior } from "@Model/Behavior";
|
import { Behavior } from "@Model/Behavior";
|
||||||
|
import { Actuator } from "@Model/Actuator";
|
||||||
|
|
||||||
function randomColor(unNormal: boolean = false) {
|
function randomColor(unNormal: boolean = false) {
|
||||||
const color = [
|
const color = [
|
||||||
@ -44,6 +45,7 @@ interface IStatusEvent {
|
|||||||
individualChange: void;
|
individualChange: void;
|
||||||
behaviorChange: void;
|
behaviorChange: void;
|
||||||
popupChange: void;
|
popupChange: void;
|
||||||
|
actuatorStartChange: void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Status extends Emitter<IStatusEvent> {
|
class Status extends Emitter<IStatusEvent> {
|
||||||
@ -71,6 +73,11 @@ class Status extends Emitter<IStatusEvent> {
|
|||||||
*/
|
*/
|
||||||
public model: Model = new Model();
|
public model: Model = new Model();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行器
|
||||||
|
*/
|
||||||
|
public actuator: Actuator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 弹窗
|
* 弹窗
|
||||||
*/
|
*/
|
||||||
@ -104,8 +111,14 @@ class Status extends Emitter<IStatusEvent> {
|
|||||||
public constructor() {
|
public constructor() {
|
||||||
super();
|
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"));
|
this.model.on("objectChange", () => this.emit("objectChange"));
|
||||||
|
125
source/Model/Actuator.ts
Normal file
125
source/Model/Actuator.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import { Model } from "./Model";
|
||||||
|
import { Emitter } from "./Emitter";
|
||||||
|
|
||||||
|
interface IActuatorEvent {
|
||||||
|
startChange: boolean;
|
||||||
|
loop: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模型执行器
|
||||||
|
*/
|
||||||
|
class Actuator extends Emitter<IActuatorEvent> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 速度系数
|
||||||
|
*/
|
||||||
|
public speed: number = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模拟帧率
|
||||||
|
*/
|
||||||
|
public fps: number = 36;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仿真是否进行
|
||||||
|
*/
|
||||||
|
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 = 2;
|
||||||
|
|
||||||
|
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 }
|
@ -64,7 +64,7 @@ interface IBehaviorParameterOptionItem<T extends IParamType = IParamType> {
|
|||||||
/**
|
/**
|
||||||
* 参数类型
|
* 参数类型
|
||||||
*/
|
*/
|
||||||
type: T;
|
type: T | string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 参数默认值
|
* 参数默认值
|
||||||
@ -202,7 +202,7 @@ class BehaviorRecorder<
|
|||||||
* 获取下一个 ID
|
* 获取下一个 ID
|
||||||
*/
|
*/
|
||||||
public getNextId() {
|
public getNextId() {
|
||||||
return `B-${this.behaviorName}-${this.nameIndex ++}`;
|
return `B-${this.behaviorId}-${this.nameIndex ++}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -269,6 +269,7 @@ class BehaviorRecorder<
|
|||||||
this.behaviorId = this.behaviorInstance.behaviorId;
|
this.behaviorId = this.behaviorInstance.behaviorId;
|
||||||
this.behaviorName = this.behaviorInstance.behaviorName;
|
this.behaviorName = this.behaviorInstance.behaviorName;
|
||||||
this.describe = this.behaviorInstance.describe;
|
this.describe = this.behaviorInstance.describe;
|
||||||
|
this.category = this.behaviorInstance.category;
|
||||||
this.terms = this.behaviorInstance.terms;
|
this.terms = this.behaviorInstance.terms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,7 +372,7 @@ class Behavior<
|
|||||||
* @param model 模型
|
* @param model 模型
|
||||||
* @param t 经过时间
|
* @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 model 模型
|
||||||
* @param t 经过时间
|
* @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 model 模型
|
||||||
* @param t 经过时间
|
* @param t 经过时间
|
||||||
*/
|
*/
|
||||||
public afterEffect(individual: Individual, group: Group, model: Model, t: number): void {};
|
public finalEffect(individual: Individual, group: Group, model: Model, t: number): void {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ class Group extends CtrlObject {
|
|||||||
* 执行行为影响
|
* 执行行为影响
|
||||||
* @param
|
* @param
|
||||||
*/
|
*/
|
||||||
public runner(t: number, effectType: "beforeEffect" | "effect" | "afterEffect" ): void {
|
public runner(t: number, effectType: "finalEffect" | "effect" | "afterEffect" ): void {
|
||||||
this.individuals.forEach((individual) => {
|
this.individuals.forEach((individual) => {
|
||||||
for(let j = 0; j < this.behaviors.length; j++) {
|
for(let j = 0; j < this.behaviors.length; j++) {
|
||||||
this.behaviors[j][effectType](individual, this, this.model, t);
|
this.behaviors[j][effectType](individual, this, this.model, t);
|
||||||
|
@ -12,9 +12,9 @@ class Individual {
|
|||||||
* @param y y 坐标
|
* @param y y 坐标
|
||||||
* @param z z 坐标
|
* @param z z 坐标
|
||||||
*/
|
*/
|
||||||
public static vectorLength(x: number[]): number;
|
public vectorLength(x: number[]): number;
|
||||||
public static vectorLength(x: number, y: number, z: number): number;
|
public vectorLength(x: number, y: number, z: number): number;
|
||||||
public static vectorLength(x: number | number[], y?: number, z?: number): number {
|
public vectorLength(x: number | number[], y?: number, z?: number): number {
|
||||||
if (Array.isArray(x)) {
|
if (Array.isArray(x)) {
|
||||||
return ((x[0] ?? 0)**2 + (x[1] ?? 0)**2 + (x[2] ?? 0)**2)**.5;
|
return ((x[0] ?? 0)**2 + (x[1] ?? 0)**2 + (x[2] ?? 0)**2)**.5;
|
||||||
} else {
|
} else {
|
||||||
@ -28,10 +28,10 @@ class Individual {
|
|||||||
* @param y y 坐标
|
* @param y y 坐标
|
||||||
* @param z z 坐标
|
* @param z z 坐标
|
||||||
*/
|
*/
|
||||||
public static vectorNormalize(x: number[]): [number, number, number];
|
public vectorNormalize(x: number[]): [number, number, number];
|
||||||
public static vectorNormalize(x: number, y: number, z: number): [number, number, number];
|
public vectorNormalize(x: number, y: number, z: number): [number, number, number];
|
||||||
public static vectorNormalize(x: number | number[], y?: number, z?: number): [number, number, number] {
|
public vectorNormalize(x: number | number[], y?: number, z?: number): [number, number, number] {
|
||||||
let length = Individual.vectorLength(x as number, y as number, z as number);
|
let length = this.vectorLength(x as number, y as number, z as number);
|
||||||
if (Array.isArray(x)) {
|
if (Array.isArray(x)) {
|
||||||
return [
|
return [
|
||||||
(x[0] ?? 0) / length,
|
(x[0] ?? 0) / length,
|
||||||
@ -52,6 +52,39 @@ class Individual {
|
|||||||
*/
|
*/
|
||||||
public position: number[] = [0, 0, 0];
|
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 目标位置
|
* @param position 目标位置
|
||||||
*/
|
*/
|
||||||
public distanceTo(position: Individual | number[]): number {
|
public distanceTo(position: Individual | number[]): number {
|
||||||
return Individual.vectorLength(this.vectorTo(position));
|
return this.vectorLength(this.vectorTo(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -8,7 +8,6 @@ import { Label } from "./Label";
|
|||||||
import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior";
|
import { Behavior, IAnyBehavior, IAnyBehaviorRecorder } from "./Behavior";
|
||||||
|
|
||||||
type ModelEvent = {
|
type ModelEvent = {
|
||||||
loop: number;
|
|
||||||
labelChange: Label[];
|
labelChange: Label[];
|
||||||
objectChange: CtrlObject[];
|
objectChange: CtrlObject[];
|
||||||
individualChange: Group;
|
individualChange: Group;
|
||||||
@ -259,7 +258,7 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
for (let i = 0; i < this.objectPool.length; i++) {
|
for (let i = 0; i < this.objectPool.length; i++) {
|
||||||
let object = this.objectPool[i];
|
let object = this.objectPool[i];
|
||||||
if (object instanceof Group && object.update) {
|
if (object instanceof Group && object.update) {
|
||||||
object.runner(t, "beforeEffect");
|
object.runner(t, "effect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +266,7 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
for (let i = 0; i < this.objectPool.length; i++) {
|
for (let i = 0; i < this.objectPool.length; i++) {
|
||||||
let object = this.objectPool[i];
|
let object = this.objectPool[i];
|
||||||
if (object instanceof Group && object.update) {
|
if (object instanceof Group && object.update) {
|
||||||
object.runner(t, "effect");
|
object.runner(t, "afterEffect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,13 +274,11 @@ class Model extends Emitter<ModelEvent> {
|
|||||||
for (let i = 0; i < this.objectPool.length; i++) {
|
for (let i = 0; i < this.objectPool.length; i++) {
|
||||||
let object = this.objectPool[i];
|
let object = this.objectPool[i];
|
||||||
if (object instanceof Group && object.update) {
|
if (object instanceof Group && object.update) {
|
||||||
object.runner(t, "afterEffect");
|
object.runner(t, "finalEffect");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.draw();
|
this.draw();
|
||||||
|
|
||||||
this.emit("loop", t);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw() {
|
public draw() {
|
||||||
|
@ -10,6 +10,7 @@ import { RootContainer } from "@Component/Container/RootContainer";
|
|||||||
import { LayoutDirection } from "@Context/Layout";
|
import { LayoutDirection } from "@Context/Layout";
|
||||||
import { CommandBar } from "@Component/CommandBar/CommandBar";
|
import { CommandBar } from "@Component/CommandBar/CommandBar";
|
||||||
import { Popup } from "@Component/Popup/Popup";
|
import { Popup } from "@Component/Popup/Popup";
|
||||||
|
import { AllBehaviors } from "@Behavior/Behavior";
|
||||||
import "./SimulatorWeb.scss";
|
import "./SimulatorWeb.scss";
|
||||||
|
|
||||||
initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/");
|
initializeIcons("https://img.mrkbear.com/fabric-cdn-prod_20210407.001/");
|
||||||
@ -54,6 +55,16 @@ class SimulatorWeb extends Component {
|
|||||||
this.status.model.update(0);
|
this.status.model.update(0);
|
||||||
this.status.newLabel().name = "New Label";
|
this.status.newLabel().name = "New Label";
|
||||||
this.status.newLabel().name = "Test Label 01";
|
this.status.newLabel().name = "Test Label 01";
|
||||||
|
let dynamic = this.status.model.addBehavior(AllBehaviors[0]);
|
||||||
|
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;
|
(window as any).s = this;
|
||||||
|
Loading…
Reference in New Issue
Block a user