@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
804 lines • 31.8 kB
JavaScript
import { Logger } from "../../Misc/logger.js";
import { VertexData } from "../../Meshes/mesh.vertexData.js";
import { Mesh } from "../../Meshes/mesh.js";
import { Epsilon, Vector3, Matrix } from "../../Maths/math.js";
import { Observable } from "../../Misc/observable.js";
import { VertexBuffer } from "../../Buffers/buffer.js";
/**
* RecastJS navigation plugin
*/
export class RecastJSPlugin {
/**
* Initializes the recastJS plugin
* @param recastInjection can be used to inject your own recast reference
*/
constructor(recastInjection = Recast) {
/**
* Reference to the Recast library
*/
this.bjsRECAST = {};
/**
* plugin name
*/
this.name = "RecastJSPlugin";
this._maximumSubStepCount = 10;
this._timeStep = 1 / 60;
this._timeFactor = 1;
this._worker = null;
if (typeof recastInjection === "function") {
Logger.Error("RecastJS is not ready. Please make sure you await Recast() before using the plugin.");
}
else {
this.bjsRECAST = recastInjection;
}
if (!this.isSupported()) {
Logger.Error("RecastJS is not available. Please make sure you included the js file.");
return;
}
this.setTimeStep();
this._tempVec1 = new this.bjsRECAST.Vec3();
this._tempVec2 = new this.bjsRECAST.Vec3();
}
/**
* Set worker URL to be used when generating a new navmesh
* @param workerURL url string
* @returns boolean indicating if worker is created
*/
setWorkerURL(workerURL) {
if (window && window.Worker) {
this._worker = new Worker(workerURL);
return true;
}
return false;
}
/**
* Set the time step of the navigation tick update.
* Default is 1/60.
* A value of 0 will disable fixed time update
* @param newTimeStep the new timestep to apply to this world.
*/
setTimeStep(newTimeStep = 1 / 60) {
this._timeStep = newTimeStep;
}
/**
* Get the time step of the navigation tick update.
* @returns the current time step
*/
getTimeStep() {
return this._timeStep;
}
/**
* If delta time in navigation tick update is greater than the time step
* a number of sub iterations are done. If more iterations are need to reach deltatime
* they will be discarded.
* A value of 0 will set to no maximum and update will use as many substeps as needed
* @param newStepCount the maximum number of iterations
*/
setMaximumSubStepCount(newStepCount = 10) {
this._maximumSubStepCount = newStepCount;
}
/**
* Get the maximum number of iterations per navigation tick update
* @returns the maximum number of iterations
*/
getMaximumSubStepCount() {
return this._maximumSubStepCount;
}
/**
* Time factor applied when updating crowd agents (default 1). A value of 0 will pause crowd updates.
* @param value the time factor applied at update
*/
set timeFactor(value) {
this._timeFactor = Math.max(value, 0);
}
/**
* Get the time factor used for crowd agent update
* @returns the time factor
*/
get timeFactor() {
return this._timeFactor;
}
/**
* Creates a navigation mesh
* @param meshes array of all the geometry used to compute the navigation mesh
* @param parameters bunch of parameters used to filter geometry
* @param completion callback when data is available from the worker. Not used without a worker
*/
createNavMesh(meshes, parameters, completion) {
if (this._worker && !completion) {
Logger.Warn("A worker is avaible but no completion callback. Defaulting to blocking navmesh creation");
}
else if (!this._worker && completion) {
Logger.Warn("A completion callback is avaible but no worker. Defaulting to blocking navmesh creation");
}
this.navMesh = new this.bjsRECAST.NavMesh();
let index;
let tri;
let pt;
const indices = [];
const positions = [];
let offset = 0;
for (index = 0; index < meshes.length; index++) {
if (meshes[index]) {
const mesh = meshes[index];
const meshIndices = mesh.getIndices();
if (!meshIndices) {
continue;
}
const meshPositions = mesh.getVerticesData(VertexBuffer.PositionKind, false, false);
if (!meshPositions) {
continue;
}
const worldMatrices = [];
const worldMatrix = mesh.computeWorldMatrix(true);
if (mesh.hasThinInstances) {
const thinMatrices = mesh.thinInstanceGetWorldMatrices();
for (let instanceIndex = 0; instanceIndex < thinMatrices.length; instanceIndex++) {
const tmpMatrix = new Matrix();
const thinMatrix = thinMatrices[instanceIndex];
thinMatrix.multiplyToRef(worldMatrix, tmpMatrix);
worldMatrices.push(tmpMatrix);
}
}
else {
worldMatrices.push(worldMatrix);
}
for (let matrixIndex = 0; matrixIndex < worldMatrices.length; matrixIndex++) {
const wm = worldMatrices[matrixIndex];
for (tri = 0; tri < meshIndices.length; tri++) {
indices.push(meshIndices[tri] + offset);
}
const transformed = Vector3.Zero();
const position = Vector3.Zero();
for (pt = 0; pt < meshPositions.length; pt += 3) {
Vector3.FromArrayToRef(meshPositions, pt, position);
Vector3.TransformCoordinatesToRef(position, wm, transformed);
positions.push(transformed.x, transformed.y, transformed.z);
}
offset += meshPositions.length / 3;
}
}
}
if (this._worker && completion) {
// spawn worker and send message
this._worker.postMessage([positions, offset, indices, indices.length, parameters]);
this._worker.onmessage = function (e) {
completion(e.data);
};
}
else {
// blocking calls
const rc = new this.bjsRECAST.rcConfig();
rc.cs = parameters.cs;
rc.ch = parameters.ch;
rc.borderSize = parameters.borderSize ? parameters.borderSize : 0;
rc.tileSize = parameters.tileSize ? parameters.tileSize : 0;
rc.walkableSlopeAngle = parameters.walkableSlopeAngle;
rc.walkableHeight = parameters.walkableHeight;
rc.walkableClimb = parameters.walkableClimb;
rc.walkableRadius = parameters.walkableRadius;
rc.maxEdgeLen = parameters.maxEdgeLen;
rc.maxSimplificationError = parameters.maxSimplificationError;
rc.minRegionArea = parameters.minRegionArea;
rc.mergeRegionArea = parameters.mergeRegionArea;
rc.maxVertsPerPoly = parameters.maxVertsPerPoly;
rc.detailSampleDist = parameters.detailSampleDist;
rc.detailSampleMaxError = parameters.detailSampleMaxError;
this.navMesh.build(positions, offset, indices, indices.length, rc);
}
}
/**
* Create a navigation mesh debug mesh
* @param scene is where the mesh will be added
* @returns debug display mesh
*/
createDebugNavMesh(scene) {
let tri;
let pt;
const debugNavMesh = this.navMesh.getDebugNavMesh();
const triangleCount = debugNavMesh.getTriangleCount();
const indices = [];
const positions = [];
for (tri = 0; tri < triangleCount * 3; tri++) {
indices.push(tri);
}
for (tri = 0; tri < triangleCount; tri++) {
for (pt = 0; pt < 3; pt++) {
const point = debugNavMesh.getTriangle(tri).getPoint(pt);
positions.push(point.x, point.y, point.z);
}
}
const mesh = new Mesh("NavMeshDebug", scene);
const vertexData = new VertexData();
vertexData.indices = indices;
vertexData.positions = positions;
vertexData.applyToMesh(mesh, false);
return mesh;
}
/**
* Get a navigation mesh constrained position, closest to the parameter position
* @param position world position
* @returns the closest point to position constrained by the navigation mesh
*/
getClosestPoint(position) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
const ret = this.navMesh.getClosestPoint(this._tempVec1);
const pr = new Vector3(ret.x, ret.y, ret.z);
return pr;
}
/**
* Get a navigation mesh constrained position, closest to the parameter position
* @param position world position
* @param result output the closest point to position constrained by the navigation mesh
*/
getClosestPointToRef(position, result) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
const ret = this.navMesh.getClosestPoint(this._tempVec1);
result.set(ret.x, ret.y, ret.z);
}
/**
* Get a navigation mesh constrained position, within a particular radius
* @param position world position
* @param maxRadius the maximum distance to the constrained world position
* @returns the closest point to position constrained by the navigation mesh
*/
getRandomPointAround(position, maxRadius) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
const ret = this.navMesh.getRandomPointAround(this._tempVec1, maxRadius);
const pr = new Vector3(ret.x, ret.y, ret.z);
return pr;
}
/**
* Get a navigation mesh constrained position, within a particular radius
* @param position world position
* @param maxRadius the maximum distance to the constrained world position
* @param result output the closest point to position constrained by the navigation mesh
*/
getRandomPointAroundToRef(position, maxRadius, result) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
const ret = this.navMesh.getRandomPointAround(this._tempVec1, maxRadius);
result.set(ret.x, ret.y, ret.z);
}
/**
* Compute the final position from a segment made of destination-position
* @param position world position
* @param destination world position
* @returns the resulting point along the navmesh
*/
moveAlong(position, destination) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
this._tempVec2.x = destination.x;
this._tempVec2.y = destination.y;
this._tempVec2.z = destination.z;
const ret = this.navMesh.moveAlong(this._tempVec1, this._tempVec2);
const pr = new Vector3(ret.x, ret.y, ret.z);
return pr;
}
/**
* Compute the final position from a segment made of destination-position
* @param position world position
* @param destination world position
* @param result output the resulting point along the navmesh
*/
moveAlongToRef(position, destination, result) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
this._tempVec2.x = destination.x;
this._tempVec2.y = destination.y;
this._tempVec2.z = destination.z;
const ret = this.navMesh.moveAlong(this._tempVec1, this._tempVec2);
result.set(ret.x, ret.y, ret.z);
}
_convertNavPathPoints(navPath) {
let pt;
const pointCount = navPath.getPointCount();
const positions = [];
for (pt = 0; pt < pointCount; pt++) {
const p = navPath.getPoint(pt);
positions.push(new Vector3(p.x, p.y, p.z));
}
return positions;
}
/**
* Compute a navigation path from start to end. Returns an empty array if no path can be computed
* Path is straight.
* @param start world position
* @param end world position
* @returns array containing world position composing the path
*/
computePath(start, end) {
this._tempVec1.x = start.x;
this._tempVec1.y = start.y;
this._tempVec1.z = start.z;
this._tempVec2.x = end.x;
this._tempVec2.y = end.y;
this._tempVec2.z = end.z;
const navPath = this.navMesh.computePath(this._tempVec1, this._tempVec2);
return this._convertNavPathPoints(navPath);
}
/**
* Compute a navigation path from start to end. Returns an empty array if no path can be computed.
* Path follows navigation mesh geometry.
* @param start world position
* @param end world position
* @returns array containing world position composing the path
*/
computePathSmooth(start, end) {
this._tempVec1.x = start.x;
this._tempVec1.y = start.y;
this._tempVec1.z = start.z;
this._tempVec2.x = end.x;
this._tempVec2.y = end.y;
this._tempVec2.z = end.z;
const navPath = this.navMesh.computePathSmooth(this._tempVec1, this._tempVec2);
return this._convertNavPathPoints(navPath);
}
/**
* Create a new Crowd so you can add agents
* @param maxAgents the maximum agent count in the crowd
* @param maxAgentRadius the maximum radius an agent can have
* @param scene to attach the crowd to
* @returns the crowd you can add agents to
*/
createCrowd(maxAgents, maxAgentRadius, scene) {
const crowd = new RecastJSCrowd(this, maxAgents, maxAgentRadius, scene);
return crowd;
}
/**
* Set the Bounding box extent for doing spatial queries (getClosestPoint, getRandomPointAround, ...)
* The queries will try to find a solution within those bounds
* default is (1,1,1)
* @param extent x,y,z value that define the extent around the queries point of reference
*/
setDefaultQueryExtent(extent) {
this._tempVec1.x = extent.x;
this._tempVec1.y = extent.y;
this._tempVec1.z = extent.z;
this.navMesh.setDefaultQueryExtent(this._tempVec1);
}
/**
* Get the Bounding box extent specified by setDefaultQueryExtent
* @returns the box extent values
*/
getDefaultQueryExtent() {
const p = this.navMesh.getDefaultQueryExtent();
return new Vector3(p.x, p.y, p.z);
}
/**
* build the navmesh from a previously saved state using getNavmeshData
* @param data the Uint8Array returned by getNavmeshData
*/
buildFromNavmeshData(data) {
const nDataBytes = data.length * data.BYTES_PER_ELEMENT;
const dataPtr = this.bjsRECAST._malloc(nDataBytes);
const dataHeap = new Uint8Array(this.bjsRECAST.HEAPU8.buffer, dataPtr, nDataBytes);
dataHeap.set(data);
const buf = new this.bjsRECAST.NavmeshData();
buf.dataPointer = dataHeap.byteOffset;
buf.size = data.length;
this.navMesh = new this.bjsRECAST.NavMesh();
this.navMesh.buildFromNavmeshData(buf);
// Free memory
this.bjsRECAST._free(dataHeap.byteOffset);
}
/**
* returns the navmesh data that can be used later. The navmesh must be built before retrieving the data
* @returns data the Uint8Array that can be saved and reused
*/
getNavmeshData() {
const navmeshData = this.navMesh.getNavmeshData();
const arrView = new Uint8Array(this.bjsRECAST.HEAPU8.buffer, navmeshData.dataPointer, navmeshData.size);
const ret = new Uint8Array(navmeshData.size);
ret.set(arrView);
this.navMesh.freeNavmeshData(navmeshData);
return ret;
}
/**
* Get the Bounding box extent result specified by setDefaultQueryExtent
* @param result output the box extent values
*/
getDefaultQueryExtentToRef(result) {
const p = this.navMesh.getDefaultQueryExtent();
result.set(p.x, p.y, p.z);
}
/**
* Disposes
*/
dispose() { }
/**
* Creates a cylinder obstacle and add it to the navigation
* @param position world position
* @param radius cylinder radius
* @param height cylinder height
* @returns the obstacle freshly created
*/
addCylinderObstacle(position, radius, height) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
return this.navMesh.addCylinderObstacle(this._tempVec1, radius, height);
}
/**
* Creates an oriented box obstacle and add it to the navigation
* @param position world position
* @param extent box size
* @param angle angle in radians of the box orientation on Y axis
* @returns the obstacle freshly created
*/
addBoxObstacle(position, extent, angle) {
this._tempVec1.x = position.x;
this._tempVec1.y = position.y;
this._tempVec1.z = position.z;
this._tempVec2.x = extent.x;
this._tempVec2.y = extent.y;
this._tempVec2.z = extent.z;
return this.navMesh.addBoxObstacle(this._tempVec1, this._tempVec2, angle);
}
/**
* Removes an obstacle created by addCylinderObstacle or addBoxObstacle
* @param obstacle obstacle to remove from the navigation
*/
removeObstacle(obstacle) {
this.navMesh.removeObstacle(obstacle);
}
/**
* If this plugin is supported
* @returns true if plugin is supported
*/
isSupported() {
return this.bjsRECAST !== undefined;
}
/**
* Returns the seed used for randomized functions like `getRandomPointAround`
* @returns seed number
*/
getRandomSeed() {
return this.bjsRECAST._getRandomSeed();
}
/**
* Set the seed used for randomized functions like `getRandomPointAround`
* @param seed number used as seed for random functions
*/
setRandomSeed(seed) {
this.bjsRECAST._setRandomSeed(seed);
}
}
/**
* Recast detour crowd implementation
*/
export class RecastJSCrowd {
/**
* Constructor
* @param plugin recastJS plugin
* @param maxAgents the maximum agent count in the crowd
* @param maxAgentRadius the maximum radius an agent can have
* @param scene to attach the crowd to
* @returns the crowd you can add agents to
*/
constructor(plugin, maxAgents, maxAgentRadius, scene) {
/**
* Link to the detour crowd
*/
this.recastCrowd = {};
/**
* One transform per agent
*/
this.transforms = new Array();
/**
* All agents created
*/
this.agents = new Array();
/**
* agents reach radius
*/
this.reachRadii = new Array();
/**
* true when a destination is active for an agent and notifier hasn't been notified of reach
*/
this._agentDestinationArmed = new Array();
/**
* agent current target
*/
this._agentDestination = new Array();
/**
* Observer for crowd updates
*/
this._onBeforeAnimationsObserver = null;
/**
* Fires each time an agent is in reach radius of its destination
*/
this.onReachTargetObservable = new Observable();
this.bjsRECASTPlugin = plugin;
this.recastCrowd = new this.bjsRECASTPlugin.bjsRECAST.Crowd(maxAgents, maxAgentRadius, this.bjsRECASTPlugin.navMesh.getNavMesh());
this._scene = scene;
this._onBeforeAnimationsObserver = scene.onBeforeAnimationsObservable.add(() => {
this.update(scene.getEngine().getDeltaTime() * 0.001 * plugin.timeFactor);
});
}
/**
* Add a new agent to the crowd with the specified parameter a corresponding transformNode.
* You can attach anything to that node. The node position is updated in the scene update tick.
* @param pos world position that will be constrained by the navigation mesh
* @param parameters agent parameters
* @param transform hooked to the agent that will be update by the scene
* @returns agent index
*/
addAgent(pos, parameters, transform) {
const agentParams = new this.bjsRECASTPlugin.bjsRECAST.dtCrowdAgentParams();
agentParams.radius = parameters.radius;
agentParams.height = parameters.height;
agentParams.maxAcceleration = parameters.maxAcceleration;
agentParams.maxSpeed = parameters.maxSpeed;
agentParams.collisionQueryRange = parameters.collisionQueryRange;
agentParams.pathOptimizationRange = parameters.pathOptimizationRange;
agentParams.separationWeight = parameters.separationWeight;
agentParams.updateFlags = 7;
agentParams.obstacleAvoidanceType = 0;
agentParams.queryFilterType = 0;
agentParams.userData = 0;
const agentIndex = this.recastCrowd.addAgent(new this.bjsRECASTPlugin.bjsRECAST.Vec3(pos.x, pos.y, pos.z), agentParams);
this.transforms.push(transform);
this.agents.push(agentIndex);
this.reachRadii.push(parameters.reachRadius ? parameters.reachRadius : parameters.radius);
this._agentDestinationArmed.push(false);
this._agentDestination.push(new Vector3(0, 0, 0));
return agentIndex;
}
/**
* Returns the agent position in world space
* @param index agent index returned by addAgent
* @returns world space position
*/
getAgentPosition(index) {
const agentPos = this.recastCrowd.getAgentPosition(index);
return new Vector3(agentPos.x, agentPos.y, agentPos.z);
}
/**
* Returns the agent position result in world space
* @param index agent index returned by addAgent
* @param result output world space position
*/
getAgentPositionToRef(index, result) {
const agentPos = this.recastCrowd.getAgentPosition(index);
result.set(agentPos.x, agentPos.y, agentPos.z);
}
/**
* Returns the agent velocity in world space
* @param index agent index returned by addAgent
* @returns world space velocity
*/
getAgentVelocity(index) {
const agentVel = this.recastCrowd.getAgentVelocity(index);
return new Vector3(agentVel.x, agentVel.y, agentVel.z);
}
/**
* Returns the agent velocity result in world space
* @param index agent index returned by addAgent
* @param result output world space velocity
*/
getAgentVelocityToRef(index, result) {
const agentVel = this.recastCrowd.getAgentVelocity(index);
result.set(agentVel.x, agentVel.y, agentVel.z);
}
/**
* Returns the agent next target point on the path
* @param index agent index returned by addAgent
* @returns world space position
*/
getAgentNextTargetPath(index) {
const pathTargetPos = this.recastCrowd.getAgentNextTargetPath(index);
return new Vector3(pathTargetPos.x, pathTargetPos.y, pathTargetPos.z);
}
/**
* Returns the agent next target point on the path
* @param index agent index returned by addAgent
* @param result output world space position
*/
getAgentNextTargetPathToRef(index, result) {
const pathTargetPos = this.recastCrowd.getAgentNextTargetPath(index);
result.set(pathTargetPos.x, pathTargetPos.y, pathTargetPos.z);
}
/**
* Gets the agent state
* @param index agent index returned by addAgent
* @returns agent state
*/
getAgentState(index) {
return this.recastCrowd.getAgentState(index);
}
/**
* returns true if the agent in over an off mesh link connection
* @param index agent index returned by addAgent
* @returns true if over an off mesh link connection
*/
overOffmeshConnection(index) {
return this.recastCrowd.overOffmeshConnection(index);
}
/**
* Asks a particular agent to go to a destination. That destination is constrained by the navigation mesh
* @param index agent index returned by addAgent
* @param destination targeted world position
*/
agentGoto(index, destination) {
this.recastCrowd.agentGoto(index, new this.bjsRECASTPlugin.bjsRECAST.Vec3(destination.x, destination.y, destination.z));
// arm observer
const item = this.agents.indexOf(index);
if (item > -1) {
this._agentDestinationArmed[item] = true;
this._agentDestination[item].set(destination.x, destination.y, destination.z);
}
}
/**
* Teleport the agent to a new position
* @param index agent index returned by addAgent
* @param destination targeted world position
*/
agentTeleport(index, destination) {
this.recastCrowd.agentTeleport(index, new this.bjsRECASTPlugin.bjsRECAST.Vec3(destination.x, destination.y, destination.z));
}
/**
* Update agent parameters
* @param index agent index returned by addAgent
* @param parameters agent parameters
*/
updateAgentParameters(index, parameters) {
const agentParams = this.recastCrowd.getAgentParameters(index);
if (parameters.radius !== undefined) {
agentParams.radius = parameters.radius;
}
if (parameters.height !== undefined) {
agentParams.height = parameters.height;
}
if (parameters.maxAcceleration !== undefined) {
agentParams.maxAcceleration = parameters.maxAcceleration;
}
if (parameters.maxSpeed !== undefined) {
agentParams.maxSpeed = parameters.maxSpeed;
}
if (parameters.collisionQueryRange !== undefined) {
agentParams.collisionQueryRange = parameters.collisionQueryRange;
}
if (parameters.pathOptimizationRange !== undefined) {
agentParams.pathOptimizationRange = parameters.pathOptimizationRange;
}
if (parameters.separationWeight !== undefined) {
agentParams.separationWeight = parameters.separationWeight;
}
this.recastCrowd.setAgentParameters(index, agentParams);
}
/**
* remove a particular agent previously created
* @param index agent index returned by addAgent
*/
removeAgent(index) {
this.recastCrowd.removeAgent(index);
const item = this.agents.indexOf(index);
if (item > -1) {
this.agents.splice(item, 1);
this.transforms.splice(item, 1);
this.reachRadii.splice(item, 1);
this._agentDestinationArmed.splice(item, 1);
this._agentDestination.splice(item, 1);
}
}
/**
* get the list of all agents attached to this crowd
* @returns list of agent indices
*/
getAgents() {
return this.agents;
}
/**
* Tick update done by the Scene. Agent position/velocity/acceleration is updated by this function
* @param deltaTime in seconds
*/
update(deltaTime) {
// update obstacles
this.bjsRECASTPlugin.navMesh.update();
if (deltaTime <= Epsilon) {
return;
}
// update crowd
const timeStep = this.bjsRECASTPlugin.getTimeStep();
const maxStepCount = this.bjsRECASTPlugin.getMaximumSubStepCount();
if (timeStep <= Epsilon) {
this.recastCrowd.update(deltaTime);
}
else {
let iterationCount = Math.floor(deltaTime / timeStep);
if (maxStepCount && iterationCount > maxStepCount) {
iterationCount = maxStepCount;
}
if (iterationCount < 1) {
iterationCount = 1;
}
const step = deltaTime / iterationCount;
for (let i = 0; i < iterationCount; i++) {
this.recastCrowd.update(step);
}
}
// update transforms
for (let index = 0; index < this.agents.length; index++) {
// update transform position
const agentIndex = this.agents[index];
const agentPosition = this.getAgentPosition(agentIndex);
this.transforms[index].position = agentPosition;
// check agent reach destination
if (this._agentDestinationArmed[index]) {
const dx = agentPosition.x - this._agentDestination[index].x;
const dz = agentPosition.z - this._agentDestination[index].z;
const radius = this.reachRadii[index];
const groundY = this._agentDestination[index].y - this.reachRadii[index];
const ceilingY = this._agentDestination[index].y + this.reachRadii[index];
const distanceXZSquared = dx * dx + dz * dz;
if (agentPosition.y > groundY && agentPosition.y < ceilingY && distanceXZSquared < radius * radius) {
this._agentDestinationArmed[index] = false;
this.onReachTargetObservable.notifyObservers({ agentIndex: agentIndex, destination: this._agentDestination[index] });
}
}
}
}
/**
* Set the Bounding box extent for doing spatial queries (getClosestPoint, getRandomPointAround, ...)
* The queries will try to find a solution within those bounds
* default is (1,1,1)
* @param extent x,y,z value that define the extent around the queries point of reference
*/
setDefaultQueryExtent(extent) {
const ext = new this.bjsRECASTPlugin.bjsRECAST.Vec3(extent.x, extent.y, extent.z);
this.recastCrowd.setDefaultQueryExtent(ext);
}
/**
* Get the Bounding box extent specified by setDefaultQueryExtent
* @returns the box extent values
*/
getDefaultQueryExtent() {
const p = this.recastCrowd.getDefaultQueryExtent();
return new Vector3(p.x, p.y, p.z);
}
/**
* Get the Bounding box extent result specified by setDefaultQueryExtent
* @param result output the box extent values
*/
getDefaultQueryExtentToRef(result) {
const p = this.recastCrowd.getDefaultQueryExtent();
result.set(p.x, p.y, p.z);
}
/**
* Get the next corner points composing the path (max 4 points)
* @param index agent index returned by addAgent
* @returns array containing world position composing the path
*/
getCorners(index) {
let pt;
const navPath = this.recastCrowd.getCorners(index);
const pointCount = navPath.getPointCount();
const positions = [];
for (pt = 0; pt < pointCount; pt++) {
const p = navPath.getPoint(pt);
positions.push(new Vector3(p.x, p.y, p.z));
}
return positions;
}
/**
* Release all resources
*/
dispose() {
this.recastCrowd.destroy();
this._scene.onBeforeAnimationsObservable.remove(this._onBeforeAnimationsObserver);
this._onBeforeAnimationsObserver = null;
this.onReachTargetObservable.clear();
}
}
//# sourceMappingURL=recastJSPlugin.js.map