snake/assets/Scripts/RVO/Simulator.ts
2023-05-21 15:53:32 +08:00

236 lines
7.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Vec2 } from "cc";
import { Agent } from "./Agent";
import { Obstacle, RVOMath, Vector2 } from "./Common";
import { KdTree } from "./kdtree";
export class Simulator {
private agentId: number = 0;
private agentIdLst: number[] = [];
aid2agent: { [key: string]: Agent } = Object.create(null);
obstacles: Obstacle[] = [];
kdTree: KdTree = new KdTree();
defaultAgent: Agent; // Agent
time: number = 0.0;
private static _inst: Simulator;
static get instance(): Simulator {
if (!Simulator._inst) {
Simulator._inst = new Simulator();
}
return Simulator._inst;
}
getAgent(idx: number) {
return this.aid2agent[this.agentIdLst[idx]];
}
getAgentByAid(aid: number) {
return this.aid2agent[aid];
}
getGlobalTime() {
return this.time;
};
getNumAgents() {
// console.log("getNumAgents ::", this.agentIdLst.length, this.agentIdLst)
return this.agentIdLst.length;
};
getAgentAidByIdx(idx: number) {
return this.agentIdLst[idx]
}
setAgentPrefVelocity(aid: number, velocity: Vector2 | Vec2) {
this.aid2agent[aid].prefVelocity_.copy(velocity);
}
getAgentPosition(aid: number) {
if (this.aid2agent[aid]) {//为什么移除了 还会进入这个aid的检测
return this.aid2agent[aid].position_;
}
return null
}
getAgentPrefVelocity(aid: number) {
return this.aid2agent[aid].prefVelocity_;
}
getAgentVelocity(aid: number) {
return this.aid2agent[aid].velocity_;
}
getAgentRadius(aid: number) {
return this.aid2agent[aid].radius_;
}
getAgentOrcaLines(aid: number) {
return this.aid2agent[aid].orcaLines_;
}
/**
* 添加动态避障管理对象
* @param position 初始位置
* @param radius 检测半径
* @param maxSpeed 最大速度
* @param velocity 初始线速度(向量)
* @param mass 转向质量
* @returns
*/
addAgent(position: Vector2 | Vec2, radius: number = null, maxSpeed: number = null, velocity: Vector2 = null, mass: number = null) {
if (!this.defaultAgent) {
throw new Error("no default agent");
}
let agent = new Agent();
agent.position_.copy(position);
agent.maxNeighbors_ = this.defaultAgent.maxNeighbors_;
agent.maxSpeed_ = maxSpeed || this.defaultAgent.maxSpeed_;
agent.neighborDist = this.defaultAgent.neighborDist;
agent.radius_ = radius || this.defaultAgent.radius_;
agent.timeHorizon = this.defaultAgent.timeHorizon;
agent.timeHorizonObst = this.defaultAgent.timeHorizonObst;
agent.velocity_.copy(velocity || this.defaultAgent.velocity_);
agent.id = this.agentId++;
if (mass && mass >= 0) {
agent.mass = mass
}
this.aid2agent[agent.id] = agent;
this.agentIdLst.push(agent.id);
return agent.id;
}
removeAgent(aid: number) {
if (this.hasAgent(aid)) {
let idx = this.agentIdLst.indexOf(aid);
if (idx >= 0) {
// this.agentIdLst.splice(idx, 1) //用高效伪移除
this.agentIdLst[idx] = this.agentIdLst[this.agentIdLst.length - 1];
this.agentIdLst.length--;
}
delete this.aid2agent[aid];
}
}
hasAgent(aid: number) {
return !!this.aid2agent[aid];
}
setAgentMass(agentNo: number, mass: number) {
this.aid2agent[agentNo].mass = mass;
}
getAgentMass(agentNo: number) {
return this.aid2agent[agentNo].mass;
}
setAgentRadius(agentNo: number, radius: number) {
this.aid2agent[agentNo].radius_ = radius;
}
/**
*
* @param neighborDist 在寻找周围邻居的搜索距离,这个值设置过大,会让小球在很远距离时做出避障行为
* @param maxNeighbors 寻找周围邻居的最大数目,这个值设置越大,最终计算的速度越精确,但会增大计算量
* @param timeHorizon 代表计算动态的物体时的时间窗口
* @param timeHorizonObst 代表计算静态的物体时的时间窗口比如在RTS游戏中小兵向城墙移动时没必要做出避障这个值需要 设置得很小
* @param radius 代表计算ORCA时的小球的半径这个值不一定与小球实际显示的半径一样偏小有利于小球移动顺畅
* @param maxSpeed 小球最大速度值
* @param velocity 小球初始速度
*/
setAgentDefaults(neighborDist: number, maxNeighbors: number, timeHorizon: number, timeHorizonObst: number, radius: number, maxSpeed: number, velocity: Vector2) {
if (!this.defaultAgent) {
this.defaultAgent = new Agent();
}
this.defaultAgent.maxNeighbors_ = maxNeighbors;
this.defaultAgent.maxSpeed_ = maxSpeed;
this.defaultAgent.neighborDist = neighborDist;
this.defaultAgent.radius_ = radius;
this.defaultAgent.timeHorizon = timeHorizon;
this.defaultAgent.timeHorizonObst = timeHorizonObst;
this.defaultAgent.velocity_ = velocity;
}
run(dt: number) {
this.kdTree.buildAgentTree(this.getNumAgents());
let agentNum = this.agentIdLst.length;
for (let i = 0; i < agentNum; i++) {
this.aid2agent[this.agentIdLst[i]].computeNeighbors(this);
this.aid2agent[this.agentIdLst[i]].computeNewVelocity(dt);
}
for (let i = 0; i < agentNum; i++) {
this.aid2agent[this.agentIdLst[i]].update(dt);
}
this.time += dt;
}
addObstacle(vertices: Vector2[]) {
if (vertices.length < 2) {
return -1;
}
let obstacleNo = this.obstacles.length;
for (let i = 0; i < vertices.length; ++i) {
let obstacle = new Obstacle();
obstacle.point = vertices[i];
if (i != 0) {
obstacle.previous = this.obstacles[this.obstacles.length - 1];
obstacle.previous.next = obstacle;
}
if (i == vertices.length - 1) {
obstacle.next = this.obstacles[obstacleNo];
obstacle.next.previous = obstacle;
}
obstacle.direction = RVOMath.normalize(vertices[(i == vertices.length - 1 ? 0 : i + 1)].minus(vertices[i]));
if (vertices.length == 2) {
obstacle.convex = true;
}
else {
obstacle.convex = (RVOMath.leftOf(vertices[(i == 0 ? vertices.length - 1 : i - 1)], vertices[i], vertices[(i == vertices.length - 1 ? 0 : i + 1)]) >= 0);
}
obstacle.id = this.obstacles.length;
this.obstacles.push(obstacle);
}
return obstacleNo;
}
processObstacles() {
this.kdTree.buildObstacleTree();
};
queryVisibility(point1: Vector2, point2: Vector2, radius: number) {
return this.kdTree.queryVisibility(point1, point2, radius);
};
getObstacles() {
return this.obstacles;
}
clear() {
this.agentIdLst.length = 0;
this.agentId = 0;
this.aid2agent = Object.create(null);
this.defaultAgent = null;
this.kdTree = new KdTree();
this.obstacles.length = 0;
}
}