recastJSPlugin.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. import { Logger } from "../../Misc/logger.js";
  2. import { VertexData } from "../../Meshes/mesh.vertexData.js";
  3. import { Mesh } from "../../Meshes/mesh.js";
  4. import { Epsilon, Vector3, Matrix } from "../../Maths/math.js";
  5. import { Observable } from "../../Misc/observable.js";
  6. import { VertexBuffer } from "../../Buffers/buffer.js";
  7. /**
  8. * RecastJS navigation plugin
  9. */
  10. export class RecastJSPlugin {
  11. /**
  12. * Initializes the recastJS plugin
  13. * @param recastInjection can be used to inject your own recast reference
  14. */
  15. constructor(recastInjection = Recast) {
  16. /**
  17. * Reference to the Recast library
  18. */
  19. this.bjsRECAST = {};
  20. /**
  21. * plugin name
  22. */
  23. this.name = "RecastJSPlugin";
  24. this._maximumSubStepCount = 10;
  25. this._timeStep = 1 / 60;
  26. this._timeFactor = 1;
  27. this._worker = null;
  28. if (typeof recastInjection === "function") {
  29. Logger.Error("RecastJS is not ready. Please make sure you await Recast() before using the plugin.");
  30. }
  31. else {
  32. this.bjsRECAST = recastInjection;
  33. }
  34. if (!this.isSupported()) {
  35. Logger.Error("RecastJS is not available. Please make sure you included the js file.");
  36. return;
  37. }
  38. this.setTimeStep();
  39. this._tempVec1 = new this.bjsRECAST.Vec3();
  40. this._tempVec2 = new this.bjsRECAST.Vec3();
  41. }
  42. /**
  43. * Set worker URL to be used when generating a new navmesh
  44. * @param workerURL url string
  45. * @returns boolean indicating if worker is created
  46. */
  47. setWorkerURL(workerURL) {
  48. if (window && window.Worker) {
  49. this._worker = new Worker(workerURL);
  50. return true;
  51. }
  52. return false;
  53. }
  54. /**
  55. * Set the time step of the navigation tick update.
  56. * Default is 1/60.
  57. * A value of 0 will disable fixed time update
  58. * @param newTimeStep the new timestep to apply to this world.
  59. */
  60. setTimeStep(newTimeStep = 1 / 60) {
  61. this._timeStep = newTimeStep;
  62. }
  63. /**
  64. * Get the time step of the navigation tick update.
  65. * @returns the current time step
  66. */
  67. getTimeStep() {
  68. return this._timeStep;
  69. }
  70. /**
  71. * If delta time in navigation tick update is greater than the time step
  72. * a number of sub iterations are done. If more iterations are need to reach deltatime
  73. * they will be discarded.
  74. * A value of 0 will set to no maximum and update will use as many substeps as needed
  75. * @param newStepCount the maximum number of iterations
  76. */
  77. setMaximumSubStepCount(newStepCount = 10) {
  78. this._maximumSubStepCount = newStepCount;
  79. }
  80. /**
  81. * Get the maximum number of iterations per navigation tick update
  82. * @returns the maximum number of iterations
  83. */
  84. getMaximumSubStepCount() {
  85. return this._maximumSubStepCount;
  86. }
  87. /**
  88. * Time factor applied when updating crowd agents (default 1). A value of 0 will pause crowd updates.
  89. * @param value the time factor applied at update
  90. */
  91. set timeFactor(value) {
  92. this._timeFactor = Math.max(value, 0);
  93. }
  94. /**
  95. * Get the time factor used for crowd agent update
  96. * @returns the time factor
  97. */
  98. get timeFactor() {
  99. return this._timeFactor;
  100. }
  101. /**
  102. * Creates a navigation mesh
  103. * @param meshes array of all the geometry used to compute the navigation mesh
  104. * @param parameters bunch of parameters used to filter geometry
  105. * @param completion callback when data is available from the worker. Not used without a worker
  106. */
  107. createNavMesh(meshes, parameters, completion) {
  108. if (this._worker && !completion) {
  109. Logger.Warn("A worker is avaible but no completion callback. Defaulting to blocking navmesh creation");
  110. }
  111. else if (!this._worker && completion) {
  112. Logger.Warn("A completion callback is avaible but no worker. Defaulting to blocking navmesh creation");
  113. }
  114. this.navMesh = new this.bjsRECAST.NavMesh();
  115. let index;
  116. let tri;
  117. let pt;
  118. const indices = [];
  119. const positions = [];
  120. let offset = 0;
  121. for (index = 0; index < meshes.length; index++) {
  122. if (meshes[index]) {
  123. const mesh = meshes[index];
  124. const meshIndices = mesh.getIndices();
  125. if (!meshIndices) {
  126. continue;
  127. }
  128. const meshPositions = mesh.getVerticesData(VertexBuffer.PositionKind, false, false);
  129. if (!meshPositions) {
  130. continue;
  131. }
  132. const worldMatrices = [];
  133. const worldMatrix = mesh.computeWorldMatrix(true);
  134. if (mesh.hasThinInstances) {
  135. const thinMatrices = mesh.thinInstanceGetWorldMatrices();
  136. for (let instanceIndex = 0; instanceIndex < thinMatrices.length; instanceIndex++) {
  137. const tmpMatrix = new Matrix();
  138. const thinMatrix = thinMatrices[instanceIndex];
  139. thinMatrix.multiplyToRef(worldMatrix, tmpMatrix);
  140. worldMatrices.push(tmpMatrix);
  141. }
  142. }
  143. else {
  144. worldMatrices.push(worldMatrix);
  145. }
  146. for (let matrixIndex = 0; matrixIndex < worldMatrices.length; matrixIndex++) {
  147. const wm = worldMatrices[matrixIndex];
  148. for (tri = 0; tri < meshIndices.length; tri++) {
  149. indices.push(meshIndices[tri] + offset);
  150. }
  151. const transformed = Vector3.Zero();
  152. const position = Vector3.Zero();
  153. for (pt = 0; pt < meshPositions.length; pt += 3) {
  154. Vector3.FromArrayToRef(meshPositions, pt, position);
  155. Vector3.TransformCoordinatesToRef(position, wm, transformed);
  156. positions.push(transformed.x, transformed.y, transformed.z);
  157. }
  158. offset += meshPositions.length / 3;
  159. }
  160. }
  161. }
  162. if (this._worker && completion) {
  163. // spawn worker and send message
  164. this._worker.postMessage([positions, offset, indices, indices.length, parameters]);
  165. this._worker.onmessage = function (e) {
  166. completion(e.data);
  167. };
  168. }
  169. else {
  170. // blocking calls
  171. const rc = new this.bjsRECAST.rcConfig();
  172. rc.cs = parameters.cs;
  173. rc.ch = parameters.ch;
  174. rc.borderSize = parameters.borderSize ? parameters.borderSize : 0;
  175. rc.tileSize = parameters.tileSize ? parameters.tileSize : 0;
  176. rc.walkableSlopeAngle = parameters.walkableSlopeAngle;
  177. rc.walkableHeight = parameters.walkableHeight;
  178. rc.walkableClimb = parameters.walkableClimb;
  179. rc.walkableRadius = parameters.walkableRadius;
  180. rc.maxEdgeLen = parameters.maxEdgeLen;
  181. rc.maxSimplificationError = parameters.maxSimplificationError;
  182. rc.minRegionArea = parameters.minRegionArea;
  183. rc.mergeRegionArea = parameters.mergeRegionArea;
  184. rc.maxVertsPerPoly = parameters.maxVertsPerPoly;
  185. rc.detailSampleDist = parameters.detailSampleDist;
  186. rc.detailSampleMaxError = parameters.detailSampleMaxError;
  187. this.navMesh.build(positions, offset, indices, indices.length, rc);
  188. }
  189. }
  190. /**
  191. * Create a navigation mesh debug mesh
  192. * @param scene is where the mesh will be added
  193. * @returns debug display mesh
  194. */
  195. createDebugNavMesh(scene) {
  196. let tri;
  197. let pt;
  198. const debugNavMesh = this.navMesh.getDebugNavMesh();
  199. const triangleCount = debugNavMesh.getTriangleCount();
  200. const indices = [];
  201. const positions = [];
  202. for (tri = 0; tri < triangleCount * 3; tri++) {
  203. indices.push(tri);
  204. }
  205. for (tri = 0; tri < triangleCount; tri++) {
  206. for (pt = 0; pt < 3; pt++) {
  207. const point = debugNavMesh.getTriangle(tri).getPoint(pt);
  208. positions.push(point.x, point.y, point.z);
  209. }
  210. }
  211. const mesh = new Mesh("NavMeshDebug", scene);
  212. const vertexData = new VertexData();
  213. vertexData.indices = indices;
  214. vertexData.positions = positions;
  215. vertexData.applyToMesh(mesh, false);
  216. return mesh;
  217. }
  218. /**
  219. * Get a navigation mesh constrained position, closest to the parameter position
  220. * @param position world position
  221. * @returns the closest point to position constrained by the navigation mesh
  222. */
  223. getClosestPoint(position) {
  224. this._tempVec1.x = position.x;
  225. this._tempVec1.y = position.y;
  226. this._tempVec1.z = position.z;
  227. const ret = this.navMesh.getClosestPoint(this._tempVec1);
  228. const pr = new Vector3(ret.x, ret.y, ret.z);
  229. return pr;
  230. }
  231. /**
  232. * Get a navigation mesh constrained position, closest to the parameter position
  233. * @param position world position
  234. * @param result output the closest point to position constrained by the navigation mesh
  235. */
  236. getClosestPointToRef(position, result) {
  237. this._tempVec1.x = position.x;
  238. this._tempVec1.y = position.y;
  239. this._tempVec1.z = position.z;
  240. const ret = this.navMesh.getClosestPoint(this._tempVec1);
  241. result.set(ret.x, ret.y, ret.z);
  242. }
  243. /**
  244. * Get a navigation mesh constrained position, within a particular radius
  245. * @param position world position
  246. * @param maxRadius the maximum distance to the constrained world position
  247. * @returns the closest point to position constrained by the navigation mesh
  248. */
  249. getRandomPointAround(position, maxRadius) {
  250. this._tempVec1.x = position.x;
  251. this._tempVec1.y = position.y;
  252. this._tempVec1.z = position.z;
  253. const ret = this.navMesh.getRandomPointAround(this._tempVec1, maxRadius);
  254. const pr = new Vector3(ret.x, ret.y, ret.z);
  255. return pr;
  256. }
  257. /**
  258. * Get a navigation mesh constrained position, within a particular radius
  259. * @param position world position
  260. * @param maxRadius the maximum distance to the constrained world position
  261. * @param result output the closest point to position constrained by the navigation mesh
  262. */
  263. getRandomPointAroundToRef(position, maxRadius, result) {
  264. this._tempVec1.x = position.x;
  265. this._tempVec1.y = position.y;
  266. this._tempVec1.z = position.z;
  267. const ret = this.navMesh.getRandomPointAround(this._tempVec1, maxRadius);
  268. result.set(ret.x, ret.y, ret.z);
  269. }
  270. /**
  271. * Compute the final position from a segment made of destination-position
  272. * @param position world position
  273. * @param destination world position
  274. * @returns the resulting point along the navmesh
  275. */
  276. moveAlong(position, destination) {
  277. this._tempVec1.x = position.x;
  278. this._tempVec1.y = position.y;
  279. this._tempVec1.z = position.z;
  280. this._tempVec2.x = destination.x;
  281. this._tempVec2.y = destination.y;
  282. this._tempVec2.z = destination.z;
  283. const ret = this.navMesh.moveAlong(this._tempVec1, this._tempVec2);
  284. const pr = new Vector3(ret.x, ret.y, ret.z);
  285. return pr;
  286. }
  287. /**
  288. * Compute the final position from a segment made of destination-position
  289. * @param position world position
  290. * @param destination world position
  291. * @param result output the resulting point along the navmesh
  292. */
  293. moveAlongToRef(position, destination, result) {
  294. this._tempVec1.x = position.x;
  295. this._tempVec1.y = position.y;
  296. this._tempVec1.z = position.z;
  297. this._tempVec2.x = destination.x;
  298. this._tempVec2.y = destination.y;
  299. this._tempVec2.z = destination.z;
  300. const ret = this.navMesh.moveAlong(this._tempVec1, this._tempVec2);
  301. result.set(ret.x, ret.y, ret.z);
  302. }
  303. _convertNavPathPoints(navPath) {
  304. let pt;
  305. const pointCount = navPath.getPointCount();
  306. const positions = [];
  307. for (pt = 0; pt < pointCount; pt++) {
  308. const p = navPath.getPoint(pt);
  309. positions.push(new Vector3(p.x, p.y, p.z));
  310. }
  311. return positions;
  312. }
  313. /**
  314. * Compute a navigation path from start to end. Returns an empty array if no path can be computed
  315. * Path is straight.
  316. * @param start world position
  317. * @param end world position
  318. * @returns array containing world position composing the path
  319. */
  320. computePath(start, end) {
  321. this._tempVec1.x = start.x;
  322. this._tempVec1.y = start.y;
  323. this._tempVec1.z = start.z;
  324. this._tempVec2.x = end.x;
  325. this._tempVec2.y = end.y;
  326. this._tempVec2.z = end.z;
  327. const navPath = this.navMesh.computePath(this._tempVec1, this._tempVec2);
  328. return this._convertNavPathPoints(navPath);
  329. }
  330. /**
  331. * Compute a navigation path from start to end. Returns an empty array if no path can be computed.
  332. * Path follows navigation mesh geometry.
  333. * @param start world position
  334. * @param end world position
  335. * @returns array containing world position composing the path
  336. */
  337. computePathSmooth(start, end) {
  338. this._tempVec1.x = start.x;
  339. this._tempVec1.y = start.y;
  340. this._tempVec1.z = start.z;
  341. this._tempVec2.x = end.x;
  342. this._tempVec2.y = end.y;
  343. this._tempVec2.z = end.z;
  344. const navPath = this.navMesh.computePathSmooth(this._tempVec1, this._tempVec2);
  345. return this._convertNavPathPoints(navPath);
  346. }
  347. /**
  348. * Create a new Crowd so you can add agents
  349. * @param maxAgents the maximum agent count in the crowd
  350. * @param maxAgentRadius the maximum radius an agent can have
  351. * @param scene to attach the crowd to
  352. * @returns the crowd you can add agents to
  353. */
  354. createCrowd(maxAgents, maxAgentRadius, scene) {
  355. const crowd = new RecastJSCrowd(this, maxAgents, maxAgentRadius, scene);
  356. return crowd;
  357. }
  358. /**
  359. * Set the Bounding box extent for doing spatial queries (getClosestPoint, getRandomPointAround, ...)
  360. * The queries will try to find a solution within those bounds
  361. * default is (1,1,1)
  362. * @param extent x,y,z value that define the extent around the queries point of reference
  363. */
  364. setDefaultQueryExtent(extent) {
  365. this._tempVec1.x = extent.x;
  366. this._tempVec1.y = extent.y;
  367. this._tempVec1.z = extent.z;
  368. this.navMesh.setDefaultQueryExtent(this._tempVec1);
  369. }
  370. /**
  371. * Get the Bounding box extent specified by setDefaultQueryExtent
  372. * @returns the box extent values
  373. */
  374. getDefaultQueryExtent() {
  375. const p = this.navMesh.getDefaultQueryExtent();
  376. return new Vector3(p.x, p.y, p.z);
  377. }
  378. /**
  379. * build the navmesh from a previously saved state using getNavmeshData
  380. * @param data the Uint8Array returned by getNavmeshData
  381. */
  382. buildFromNavmeshData(data) {
  383. const nDataBytes = data.length * data.BYTES_PER_ELEMENT;
  384. const dataPtr = this.bjsRECAST._malloc(nDataBytes);
  385. const dataHeap = new Uint8Array(this.bjsRECAST.HEAPU8.buffer, dataPtr, nDataBytes);
  386. dataHeap.set(data);
  387. const buf = new this.bjsRECAST.NavmeshData();
  388. buf.dataPointer = dataHeap.byteOffset;
  389. buf.size = data.length;
  390. this.navMesh = new this.bjsRECAST.NavMesh();
  391. this.navMesh.buildFromNavmeshData(buf);
  392. // Free memory
  393. this.bjsRECAST._free(dataHeap.byteOffset);
  394. }
  395. /**
  396. * returns the navmesh data that can be used later. The navmesh must be built before retrieving the data
  397. * @returns data the Uint8Array that can be saved and reused
  398. */
  399. getNavmeshData() {
  400. const navmeshData = this.navMesh.getNavmeshData();
  401. const arrView = new Uint8Array(this.bjsRECAST.HEAPU8.buffer, navmeshData.dataPointer, navmeshData.size);
  402. const ret = new Uint8Array(navmeshData.size);
  403. ret.set(arrView);
  404. this.navMesh.freeNavmeshData(navmeshData);
  405. return ret;
  406. }
  407. /**
  408. * Get the Bounding box extent result specified by setDefaultQueryExtent
  409. * @param result output the box extent values
  410. */
  411. getDefaultQueryExtentToRef(result) {
  412. const p = this.navMesh.getDefaultQueryExtent();
  413. result.set(p.x, p.y, p.z);
  414. }
  415. /**
  416. * Disposes
  417. */
  418. dispose() { }
  419. /**
  420. * Creates a cylinder obstacle and add it to the navigation
  421. * @param position world position
  422. * @param radius cylinder radius
  423. * @param height cylinder height
  424. * @returns the obstacle freshly created
  425. */
  426. addCylinderObstacle(position, radius, height) {
  427. this._tempVec1.x = position.x;
  428. this._tempVec1.y = position.y;
  429. this._tempVec1.z = position.z;
  430. return this.navMesh.addCylinderObstacle(this._tempVec1, radius, height);
  431. }
  432. /**
  433. * Creates an oriented box obstacle and add it to the navigation
  434. * @param position world position
  435. * @param extent box size
  436. * @param angle angle in radians of the box orientation on Y axis
  437. * @returns the obstacle freshly created
  438. */
  439. addBoxObstacle(position, extent, angle) {
  440. this._tempVec1.x = position.x;
  441. this._tempVec1.y = position.y;
  442. this._tempVec1.z = position.z;
  443. this._tempVec2.x = extent.x;
  444. this._tempVec2.y = extent.y;
  445. this._tempVec2.z = extent.z;
  446. return this.navMesh.addBoxObstacle(this._tempVec1, this._tempVec2, angle);
  447. }
  448. /**
  449. * Removes an obstacle created by addCylinderObstacle or addBoxObstacle
  450. * @param obstacle obstacle to remove from the navigation
  451. */
  452. removeObstacle(obstacle) {
  453. this.navMesh.removeObstacle(obstacle);
  454. }
  455. /**
  456. * If this plugin is supported
  457. * @returns true if plugin is supported
  458. */
  459. isSupported() {
  460. return this.bjsRECAST !== undefined;
  461. }
  462. /**
  463. * Returns the seed used for randomized functions like `getRandomPointAround`
  464. * @returns seed number
  465. */
  466. getRandomSeed() {
  467. return this.bjsRECAST._getRandomSeed();
  468. }
  469. /**
  470. * Set the seed used for randomized functions like `getRandomPointAround`
  471. * @param seed number used as seed for random functions
  472. */
  473. setRandomSeed(seed) {
  474. this.bjsRECAST._setRandomSeed(seed);
  475. }
  476. }
  477. /**
  478. * Recast detour crowd implementation
  479. */
  480. export class RecastJSCrowd {
  481. /**
  482. * Constructor
  483. * @param plugin recastJS plugin
  484. * @param maxAgents the maximum agent count in the crowd
  485. * @param maxAgentRadius the maximum radius an agent can have
  486. * @param scene to attach the crowd to
  487. * @returns the crowd you can add agents to
  488. */
  489. constructor(plugin, maxAgents, maxAgentRadius, scene) {
  490. /**
  491. * Link to the detour crowd
  492. */
  493. this.recastCrowd = {};
  494. /**
  495. * One transform per agent
  496. */
  497. this.transforms = new Array();
  498. /**
  499. * All agents created
  500. */
  501. this.agents = new Array();
  502. /**
  503. * agents reach radius
  504. */
  505. this.reachRadii = new Array();
  506. /**
  507. * true when a destination is active for an agent and notifier hasn't been notified of reach
  508. */
  509. this._agentDestinationArmed = new Array();
  510. /**
  511. * agent current target
  512. */
  513. this._agentDestination = new Array();
  514. /**
  515. * Observer for crowd updates
  516. */
  517. this._onBeforeAnimationsObserver = null;
  518. /**
  519. * Fires each time an agent is in reach radius of its destination
  520. */
  521. this.onReachTargetObservable = new Observable();
  522. this.bjsRECASTPlugin = plugin;
  523. this.recastCrowd = new this.bjsRECASTPlugin.bjsRECAST.Crowd(maxAgents, maxAgentRadius, this.bjsRECASTPlugin.navMesh.getNavMesh());
  524. this._scene = scene;
  525. this._onBeforeAnimationsObserver = scene.onBeforeAnimationsObservable.add(() => {
  526. this.update(scene.getEngine().getDeltaTime() * 0.001 * plugin.timeFactor);
  527. });
  528. }
  529. /**
  530. * Add a new agent to the crowd with the specified parameter a corresponding transformNode.
  531. * You can attach anything to that node. The node position is updated in the scene update tick.
  532. * @param pos world position that will be constrained by the navigation mesh
  533. * @param parameters agent parameters
  534. * @param transform hooked to the agent that will be update by the scene
  535. * @returns agent index
  536. */
  537. addAgent(pos, parameters, transform) {
  538. const agentParams = new this.bjsRECASTPlugin.bjsRECAST.dtCrowdAgentParams();
  539. agentParams.radius = parameters.radius;
  540. agentParams.height = parameters.height;
  541. agentParams.maxAcceleration = parameters.maxAcceleration;
  542. agentParams.maxSpeed = parameters.maxSpeed;
  543. agentParams.collisionQueryRange = parameters.collisionQueryRange;
  544. agentParams.pathOptimizationRange = parameters.pathOptimizationRange;
  545. agentParams.separationWeight = parameters.separationWeight;
  546. agentParams.updateFlags = 7;
  547. agentParams.obstacleAvoidanceType = 0;
  548. agentParams.queryFilterType = 0;
  549. agentParams.userData = 0;
  550. const agentIndex = this.recastCrowd.addAgent(new this.bjsRECASTPlugin.bjsRECAST.Vec3(pos.x, pos.y, pos.z), agentParams);
  551. this.transforms.push(transform);
  552. this.agents.push(agentIndex);
  553. this.reachRadii.push(parameters.reachRadius ? parameters.reachRadius : parameters.radius);
  554. this._agentDestinationArmed.push(false);
  555. this._agentDestination.push(new Vector3(0, 0, 0));
  556. return agentIndex;
  557. }
  558. /**
  559. * Returns the agent position in world space
  560. * @param index agent index returned by addAgent
  561. * @returns world space position
  562. */
  563. getAgentPosition(index) {
  564. const agentPos = this.recastCrowd.getAgentPosition(index);
  565. return new Vector3(agentPos.x, agentPos.y, agentPos.z);
  566. }
  567. /**
  568. * Returns the agent position result in world space
  569. * @param index agent index returned by addAgent
  570. * @param result output world space position
  571. */
  572. getAgentPositionToRef(index, result) {
  573. const agentPos = this.recastCrowd.getAgentPosition(index);
  574. result.set(agentPos.x, agentPos.y, agentPos.z);
  575. }
  576. /**
  577. * Returns the agent velocity in world space
  578. * @param index agent index returned by addAgent
  579. * @returns world space velocity
  580. */
  581. getAgentVelocity(index) {
  582. const agentVel = this.recastCrowd.getAgentVelocity(index);
  583. return new Vector3(agentVel.x, agentVel.y, agentVel.z);
  584. }
  585. /**
  586. * Returns the agent velocity result in world space
  587. * @param index agent index returned by addAgent
  588. * @param result output world space velocity
  589. */
  590. getAgentVelocityToRef(index, result) {
  591. const agentVel = this.recastCrowd.getAgentVelocity(index);
  592. result.set(agentVel.x, agentVel.y, agentVel.z);
  593. }
  594. /**
  595. * Returns the agent next target point on the path
  596. * @param index agent index returned by addAgent
  597. * @returns world space position
  598. */
  599. getAgentNextTargetPath(index) {
  600. const pathTargetPos = this.recastCrowd.getAgentNextTargetPath(index);
  601. return new Vector3(pathTargetPos.x, pathTargetPos.y, pathTargetPos.z);
  602. }
  603. /**
  604. * Returns the agent next target point on the path
  605. * @param index agent index returned by addAgent
  606. * @param result output world space position
  607. */
  608. getAgentNextTargetPathToRef(index, result) {
  609. const pathTargetPos = this.recastCrowd.getAgentNextTargetPath(index);
  610. result.set(pathTargetPos.x, pathTargetPos.y, pathTargetPos.z);
  611. }
  612. /**
  613. * Gets the agent state
  614. * @param index agent index returned by addAgent
  615. * @returns agent state
  616. */
  617. getAgentState(index) {
  618. return this.recastCrowd.getAgentState(index);
  619. }
  620. /**
  621. * returns true if the agent in over an off mesh link connection
  622. * @param index agent index returned by addAgent
  623. * @returns true if over an off mesh link connection
  624. */
  625. overOffmeshConnection(index) {
  626. return this.recastCrowd.overOffmeshConnection(index);
  627. }
  628. /**
  629. * Asks a particular agent to go to a destination. That destination is constrained by the navigation mesh
  630. * @param index agent index returned by addAgent
  631. * @param destination targeted world position
  632. */
  633. agentGoto(index, destination) {
  634. this.recastCrowd.agentGoto(index, new this.bjsRECASTPlugin.bjsRECAST.Vec3(destination.x, destination.y, destination.z));
  635. // arm observer
  636. const item = this.agents.indexOf(index);
  637. if (item > -1) {
  638. this._agentDestinationArmed[item] = true;
  639. this._agentDestination[item].set(destination.x, destination.y, destination.z);
  640. }
  641. }
  642. /**
  643. * Teleport the agent to a new position
  644. * @param index agent index returned by addAgent
  645. * @param destination targeted world position
  646. */
  647. agentTeleport(index, destination) {
  648. this.recastCrowd.agentTeleport(index, new this.bjsRECASTPlugin.bjsRECAST.Vec3(destination.x, destination.y, destination.z));
  649. }
  650. /**
  651. * Update agent parameters
  652. * @param index agent index returned by addAgent
  653. * @param parameters agent parameters
  654. */
  655. updateAgentParameters(index, parameters) {
  656. const agentParams = this.recastCrowd.getAgentParameters(index);
  657. if (parameters.radius !== undefined) {
  658. agentParams.radius = parameters.radius;
  659. }
  660. if (parameters.height !== undefined) {
  661. agentParams.height = parameters.height;
  662. }
  663. if (parameters.maxAcceleration !== undefined) {
  664. agentParams.maxAcceleration = parameters.maxAcceleration;
  665. }
  666. if (parameters.maxSpeed !== undefined) {
  667. agentParams.maxSpeed = parameters.maxSpeed;
  668. }
  669. if (parameters.collisionQueryRange !== undefined) {
  670. agentParams.collisionQueryRange = parameters.collisionQueryRange;
  671. }
  672. if (parameters.pathOptimizationRange !== undefined) {
  673. agentParams.pathOptimizationRange = parameters.pathOptimizationRange;
  674. }
  675. if (parameters.separationWeight !== undefined) {
  676. agentParams.separationWeight = parameters.separationWeight;
  677. }
  678. this.recastCrowd.setAgentParameters(index, agentParams);
  679. }
  680. /**
  681. * remove a particular agent previously created
  682. * @param index agent index returned by addAgent
  683. */
  684. removeAgent(index) {
  685. this.recastCrowd.removeAgent(index);
  686. const item = this.agents.indexOf(index);
  687. if (item > -1) {
  688. this.agents.splice(item, 1);
  689. this.transforms.splice(item, 1);
  690. this.reachRadii.splice(item, 1);
  691. this._agentDestinationArmed.splice(item, 1);
  692. this._agentDestination.splice(item, 1);
  693. }
  694. }
  695. /**
  696. * get the list of all agents attached to this crowd
  697. * @returns list of agent indices
  698. */
  699. getAgents() {
  700. return this.agents;
  701. }
  702. /**
  703. * Tick update done by the Scene. Agent position/velocity/acceleration is updated by this function
  704. * @param deltaTime in seconds
  705. */
  706. update(deltaTime) {
  707. // update obstacles
  708. this.bjsRECASTPlugin.navMesh.update();
  709. if (deltaTime <= Epsilon) {
  710. return;
  711. }
  712. // update crowd
  713. const timeStep = this.bjsRECASTPlugin.getTimeStep();
  714. const maxStepCount = this.bjsRECASTPlugin.getMaximumSubStepCount();
  715. if (timeStep <= Epsilon) {
  716. this.recastCrowd.update(deltaTime);
  717. }
  718. else {
  719. let iterationCount = Math.floor(deltaTime / timeStep);
  720. if (maxStepCount && iterationCount > maxStepCount) {
  721. iterationCount = maxStepCount;
  722. }
  723. if (iterationCount < 1) {
  724. iterationCount = 1;
  725. }
  726. const step = deltaTime / iterationCount;
  727. for (let i = 0; i < iterationCount; i++) {
  728. this.recastCrowd.update(step);
  729. }
  730. }
  731. // update transforms
  732. for (let index = 0; index < this.agents.length; index++) {
  733. // update transform position
  734. const agentIndex = this.agents[index];
  735. const agentPosition = this.getAgentPosition(agentIndex);
  736. this.transforms[index].position = agentPosition;
  737. // check agent reach destination
  738. if (this._agentDestinationArmed[index]) {
  739. const dx = agentPosition.x - this._agentDestination[index].x;
  740. const dz = agentPosition.z - this._agentDestination[index].z;
  741. const radius = this.reachRadii[index];
  742. const groundY = this._agentDestination[index].y - this.reachRadii[index];
  743. const ceilingY = this._agentDestination[index].y + this.reachRadii[index];
  744. const distanceXZSquared = dx * dx + dz * dz;
  745. if (agentPosition.y > groundY && agentPosition.y < ceilingY && distanceXZSquared < radius * radius) {
  746. this._agentDestinationArmed[index] = false;
  747. this.onReachTargetObservable.notifyObservers({ agentIndex: agentIndex, destination: this._agentDestination[index] });
  748. }
  749. }
  750. }
  751. }
  752. /**
  753. * Set the Bounding box extent for doing spatial queries (getClosestPoint, getRandomPointAround, ...)
  754. * The queries will try to find a solution within those bounds
  755. * default is (1,1,1)
  756. * @param extent x,y,z value that define the extent around the queries point of reference
  757. */
  758. setDefaultQueryExtent(extent) {
  759. const ext = new this.bjsRECASTPlugin.bjsRECAST.Vec3(extent.x, extent.y, extent.z);
  760. this.recastCrowd.setDefaultQueryExtent(ext);
  761. }
  762. /**
  763. * Get the Bounding box extent specified by setDefaultQueryExtent
  764. * @returns the box extent values
  765. */
  766. getDefaultQueryExtent() {
  767. const p = this.recastCrowd.getDefaultQueryExtent();
  768. return new Vector3(p.x, p.y, p.z);
  769. }
  770. /**
  771. * Get the Bounding box extent result specified by setDefaultQueryExtent
  772. * @param result output the box extent values
  773. */
  774. getDefaultQueryExtentToRef(result) {
  775. const p = this.recastCrowd.getDefaultQueryExtent();
  776. result.set(p.x, p.y, p.z);
  777. }
  778. /**
  779. * Get the next corner points composing the path (max 4 points)
  780. * @param index agent index returned by addAgent
  781. * @returns array containing world position composing the path
  782. */
  783. getCorners(index) {
  784. let pt;
  785. const navPath = this.recastCrowd.getCorners(index);
  786. const pointCount = navPath.getPointCount();
  787. const positions = [];
  788. for (pt = 0; pt < pointCount; pt++) {
  789. const p = navPath.getPoint(pt);
  790. positions.push(new Vector3(p.x, p.y, p.z));
  791. }
  792. return positions;
  793. }
  794. /**
  795. * Release all resources
  796. */
  797. dispose() {
  798. this.recastCrowd.destroy();
  799. this._scene.onBeforeAnimationsObservable.remove(this._onBeforeAnimationsObserver);
  800. this._onBeforeAnimationsObserver = null;
  801. this.onReachTargetObservable.clear();
  802. }
  803. }
  804. //# sourceMappingURL=recastJSPlugin.js.map