aframe-extras.pathfinding.js 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533
  1. (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  2. 'use strict';
  3. require('./src/pathfinding');
  4. },{"./src/pathfinding":2}],2:[function(require,module,exports){
  5. 'use strict';
  6. require('./nav-mesh');
  7. require('./nav-agent');
  8. require('./system');
  9. },{"./nav-agent":3,"./nav-mesh":4,"./system":5}],3:[function(require,module,exports){
  10. 'use strict';
  11. module.exports = AFRAME.registerComponent('nav-agent', {
  12. schema: {
  13. destination: { type: 'vec3' },
  14. active: { default: false },
  15. speed: { default: 2 }
  16. },
  17. init: function init() {
  18. this.system = this.el.sceneEl.systems.nav;
  19. this.system.addAgent(this);
  20. this.group = null;
  21. this.path = [];
  22. this.raycaster = new THREE.Raycaster();
  23. },
  24. remove: function remove() {
  25. this.system.removeAgent(this);
  26. },
  27. update: function update() {
  28. this.path.length = 0;
  29. },
  30. updateNavLocation: function updateNavLocation() {
  31. this.group = null;
  32. this.path = [];
  33. },
  34. tick: function () {
  35. var vDest = new THREE.Vector3();
  36. var vDelta = new THREE.Vector3();
  37. var vNext = new THREE.Vector3();
  38. return function (t, dt) {
  39. var el = this.el;
  40. var data = this.data;
  41. var raycaster = this.raycaster;
  42. var speed = data.speed * dt / 1000;
  43. if (!data.active) return;
  44. // Use PatrolJS pathfinding system to get shortest path to target.
  45. if (!this.path.length) {
  46. var position = this.el.object3D.position;
  47. this.group = this.group || this.system.getGroup(position);
  48. this.path = this.system.getPath(position, vDest.copy(data.destination), this.group) || [];
  49. el.emit('nav-start');
  50. }
  51. // If no path is found, exit.
  52. if (!this.path.length) {
  53. console.warn('[nav] Unable to find path to %o.', data.destination);
  54. this.el.setAttribute('nav-agent', { active: false });
  55. el.emit('nav-end');
  56. return;
  57. }
  58. // Current segment is a vector from current position to next waypoint.
  59. var vCurrent = el.object3D.position;
  60. var vWaypoint = this.path[0];
  61. vDelta.subVectors(vWaypoint, vCurrent);
  62. var distance = vDelta.length();
  63. var gazeTarget = void 0;
  64. if (distance < speed) {
  65. // If <1 step from current waypoint, discard it and move toward next.
  66. this.path.shift();
  67. // After discarding the last waypoint, exit pathfinding.
  68. if (!this.path.length) {
  69. this.el.setAttribute('nav-agent', { active: false });
  70. el.emit('nav-end');
  71. return;
  72. }
  73. vNext.copy(vCurrent);
  74. gazeTarget = this.path[0];
  75. } else {
  76. // If still far away from next waypoint, find next position for
  77. // the current frame.
  78. vNext.copy(vDelta.setLength(speed)).add(vCurrent);
  79. gazeTarget = vWaypoint;
  80. }
  81. // Look at the next waypoint.
  82. gazeTarget.y = vCurrent.y;
  83. el.object3D.lookAt(gazeTarget);
  84. // Raycast against the nav mesh, to keep the agent moving along the
  85. // ground, not traveling in a straight line from higher to lower waypoints.
  86. raycaster.ray.origin.copy(vNext);
  87. raycaster.ray.origin.y += 1.5;
  88. raycaster.ray.direction.y = -1;
  89. var intersections = raycaster.intersectObject(this.system.getNavMesh());
  90. if (!intersections.length) {
  91. // Raycasting failed. Step toward the waypoint and hope for the best.
  92. vCurrent.copy(vNext);
  93. } else {
  94. // Re-project next position onto nav mesh.
  95. vDelta.subVectors(intersections[0].point, vCurrent);
  96. vCurrent.add(vDelta.setLength(speed));
  97. }
  98. };
  99. }()
  100. });
  101. },{}],4:[function(require,module,exports){
  102. 'use strict';
  103. /**
  104. * nav-mesh
  105. *
  106. * Waits for a mesh to be loaded on the current entity, then sets it as the
  107. * nav mesh in the pathfinding system.
  108. */
  109. module.exports = AFRAME.registerComponent('nav-mesh', {
  110. init: function init() {
  111. this.system = this.el.sceneEl.systems.nav;
  112. this.hasLoadedNavMesh = false;
  113. this.el.addEventListener('model-loaded', this.loadNavMesh.bind(this));
  114. },
  115. play: function play() {
  116. if (!this.hasLoadedNavMesh) this.loadNavMesh();
  117. },
  118. loadNavMesh: function loadNavMesh() {
  119. var object = this.el.getObject3D('mesh');
  120. var scene = this.el.sceneEl.object3D;
  121. if (!object) return;
  122. var navMesh = void 0;
  123. object.traverse(function (node) {
  124. if (node.isMesh) navMesh = node;
  125. });
  126. if (!navMesh) return;
  127. var navMeshGeometry = navMesh.geometry.isBufferGeometry ? new THREE.Geometry().fromBufferGeometry(navMesh.geometry) : navMesh.geometry.clone();
  128. scene.updateMatrixWorld();
  129. navMeshGeometry.applyMatrix(navMesh.matrixWorld);
  130. this.system.setNavMeshGeometry(navMeshGeometry);
  131. this.hasLoadedNavMesh = true;
  132. }
  133. });
  134. },{}],5:[function(require,module,exports){
  135. 'use strict';
  136. var Path = require('three-pathfinding');
  137. var pathfinder = new Path();
  138. var ZONE = 'level';
  139. /**
  140. * nav
  141. *
  142. * Pathfinding system, using PatrolJS.
  143. */
  144. module.exports = AFRAME.registerSystem('nav', {
  145. init: function init() {
  146. this.navMesh = null;
  147. this.agents = new Set();
  148. },
  149. /**
  150. * @param {THREE.Geometry} geometry
  151. */
  152. setNavMeshGeometry: function setNavMeshGeometry(geometry) {
  153. this.navMesh = new THREE.Mesh(geometry);
  154. pathfinder.setZoneData(ZONE, Path.createZone(geometry));
  155. Array.from(this.agents).forEach(function (agent) {
  156. return agent.updateNavLocation();
  157. });
  158. },
  159. /**
  160. * @return {THREE.Mesh}
  161. */
  162. getNavMesh: function getNavMesh() {
  163. return this.navMesh;
  164. },
  165. /**
  166. * @param {NavAgent} ctrl
  167. */
  168. addAgent: function addAgent(ctrl) {
  169. this.agents.add(ctrl);
  170. },
  171. /**
  172. * @param {NavAgent} ctrl
  173. */
  174. removeAgent: function removeAgent(ctrl) {
  175. this.agents.delete(ctrl);
  176. },
  177. /**
  178. * @param {THREE.Vector3} start
  179. * @param {THREE.Vector3} end
  180. * @param {number} groupID
  181. * @return {Array<THREE.Vector3>}
  182. */
  183. getPath: function getPath(start, end, groupID) {
  184. return pathfinder.findPath(start, end, ZONE, groupID);
  185. },
  186. /**
  187. * @param {THREE.Vector3} position
  188. * @return {number}
  189. */
  190. getGroup: function getGroup(position) {
  191. return pathfinder.getGroup(ZONE, position);
  192. },
  193. /**
  194. * @param {THREE.Vector3} position
  195. * @param {number} groupID
  196. * @return {Node}
  197. */
  198. getNode: function getNode(position, groupID) {
  199. return pathfinder.getClosestNode(position, ZONE, groupID, true);
  200. },
  201. /**
  202. * @param {THREE.Vector3} start Starting position.
  203. * @param {THREE.Vector3} end Desired ending position.
  204. * @param {number} groupID
  205. * @param {Node} node
  206. * @param {THREE.Vector3} endTarget (Output) Adjusted step end position.
  207. * @return {Node} Current node, after step is taken.
  208. */
  209. clampStep: function clampStep(start, end, groupID, node, endTarget) {
  210. if (!this.navMesh || !node) {
  211. endTarget.copy(end);
  212. return this.navMesh ? this.getNode(end, groupID) : null;
  213. }
  214. return pathfinder.clampStep(start, end, node, ZONE, groupID, endTarget);
  215. }
  216. });
  217. },{"three-pathfinding":10}],6:[function(require,module,exports){
  218. 'use strict';
  219. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  220. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  221. var BinaryHeap = require('./BinaryHeap');
  222. var utils = require('./utils.js');
  223. var AStar = function () {
  224. function AStar() {
  225. _classCallCheck(this, AStar);
  226. }
  227. _createClass(AStar, null, [{
  228. key: 'init',
  229. value: function init(graph) {
  230. for (var x = 0; x < graph.length; x++) {
  231. //for(var x in graph) {
  232. var node = graph[x];
  233. node.f = 0;
  234. node.g = 0;
  235. node.h = 0;
  236. node.cost = 1.0;
  237. node.visited = false;
  238. node.closed = false;
  239. node.parent = null;
  240. }
  241. }
  242. }, {
  243. key: 'cleanUp',
  244. value: function cleanUp(graph) {
  245. for (var x = 0; x < graph.length; x++) {
  246. var node = graph[x];
  247. delete node.f;
  248. delete node.g;
  249. delete node.h;
  250. delete node.cost;
  251. delete node.visited;
  252. delete node.closed;
  253. delete node.parent;
  254. }
  255. }
  256. }, {
  257. key: 'heap',
  258. value: function heap() {
  259. return new BinaryHeap(function (node) {
  260. return node.f;
  261. });
  262. }
  263. }, {
  264. key: 'search',
  265. value: function search(graph, start, end) {
  266. this.init(graph);
  267. //heuristic = heuristic || astar.manhattan;
  268. var openHeap = this.heap();
  269. openHeap.push(start);
  270. while (openHeap.size() > 0) {
  271. // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
  272. var currentNode = openHeap.pop();
  273. // End case -- result has been found, return the traced path.
  274. if (currentNode === end) {
  275. var curr = currentNode;
  276. var ret = [];
  277. while (curr.parent) {
  278. ret.push(curr);
  279. curr = curr.parent;
  280. }
  281. this.cleanUp(ret);
  282. return ret.reverse();
  283. }
  284. // Normal case -- move currentNode from open to closed, process each of its neighbours.
  285. currentNode.closed = true;
  286. // Find all neighbours for the current node. Optionally find diagonal neighbours as well (false by default).
  287. var neighbours = this.neighbours(graph, currentNode);
  288. for (var i = 0, il = neighbours.length; i < il; i++) {
  289. var neighbour = neighbours[i];
  290. if (neighbour.closed) {
  291. // Not a valid node to process, skip to next neighbour.
  292. continue;
  293. }
  294. // The g score is the shortest distance from start to current node.
  295. // We need to check if the path we have arrived at this neighbour is the shortest one we have seen yet.
  296. var gScore = currentNode.g + neighbour.cost;
  297. var beenVisited = neighbour.visited;
  298. if (!beenVisited || gScore < neighbour.g) {
  299. // Found an optimal (so far) path to this node. Take score for node to see how good it is.
  300. neighbour.visited = true;
  301. neighbour.parent = currentNode;
  302. if (!neighbour.centroid || !end.centroid) throw new Error('Unexpected state');
  303. neighbour.h = neighbour.h || this.heuristic(neighbour.centroid, end.centroid);
  304. neighbour.g = gScore;
  305. neighbour.f = neighbour.g + neighbour.h;
  306. if (!beenVisited) {
  307. // Pushing to heap will put it in proper place based on the 'f' value.
  308. openHeap.push(neighbour);
  309. } else {
  310. // Already seen the node, but since it has been rescored we need to reorder it in the heap
  311. openHeap.rescoreElement(neighbour);
  312. }
  313. }
  314. }
  315. }
  316. // No result was found - empty array signifies failure to find path.
  317. return [];
  318. }
  319. }, {
  320. key: 'heuristic',
  321. value: function heuristic(pos1, pos2) {
  322. return utils.distanceToSquared(pos1, pos2);
  323. }
  324. }, {
  325. key: 'neighbours',
  326. value: function neighbours(graph, node) {
  327. var ret = [];
  328. for (var e = 0; e < node.neighbours.length; e++) {
  329. ret.push(graph[node.neighbours[e]]);
  330. }
  331. return ret;
  332. }
  333. }]);
  334. return AStar;
  335. }();
  336. module.exports = AStar;
  337. },{"./BinaryHeap":7,"./utils.js":11}],7:[function(require,module,exports){
  338. "use strict";
  339. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  340. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  341. // javascript-astar
  342. // http://github.com/bgrins/javascript-astar
  343. // Freely distributable under the MIT License.
  344. // Implements the astar search algorithm in javascript using a binary heap.
  345. var BinaryHeap = function () {
  346. function BinaryHeap(scoreFunction) {
  347. _classCallCheck(this, BinaryHeap);
  348. this.content = [];
  349. this.scoreFunction = scoreFunction;
  350. }
  351. _createClass(BinaryHeap, [{
  352. key: "push",
  353. value: function push(element) {
  354. // Add the new element to the end of the array.
  355. this.content.push(element);
  356. // Allow it to sink down.
  357. this.sinkDown(this.content.length - 1);
  358. }
  359. }, {
  360. key: "pop",
  361. value: function pop() {
  362. // Store the first element so we can return it later.
  363. var result = this.content[0];
  364. // Get the element at the end of the array.
  365. var end = this.content.pop();
  366. // If there are any elements left, put the end element at the
  367. // start, and let it bubble up.
  368. if (this.content.length > 0) {
  369. this.content[0] = end;
  370. this.bubbleUp(0);
  371. }
  372. return result;
  373. }
  374. }, {
  375. key: "remove",
  376. value: function remove(node) {
  377. var i = this.content.indexOf(node);
  378. // When it is found, the process seen in 'pop' is repeated
  379. // to fill up the hole.
  380. var end = this.content.pop();
  381. if (i !== this.content.length - 1) {
  382. this.content[i] = end;
  383. if (this.scoreFunction(end) < this.scoreFunction(node)) {
  384. this.sinkDown(i);
  385. } else {
  386. this.bubbleUp(i);
  387. }
  388. }
  389. }
  390. }, {
  391. key: "size",
  392. value: function size() {
  393. return this.content.length;
  394. }
  395. }, {
  396. key: "rescoreElement",
  397. value: function rescoreElement(node) {
  398. this.sinkDown(this.content.indexOf(node));
  399. }
  400. }, {
  401. key: "sinkDown",
  402. value: function sinkDown(n) {
  403. // Fetch the element that has to be sunk.
  404. var element = this.content[n];
  405. // When at 0, an element can not sink any further.
  406. while (n > 0) {
  407. // Compute the parent element's index, and fetch it.
  408. var parentN = (n + 1 >> 1) - 1;
  409. var parent = this.content[parentN];
  410. if (this.scoreFunction(element) < this.scoreFunction(parent)) {
  411. // Swap the elements if the parent is greater.
  412. this.content[parentN] = element;
  413. this.content[n] = parent;
  414. // Update 'n' to continue at the new position.
  415. n = parentN;
  416. } else {
  417. // Found a parent that is less, no need to sink any further.
  418. break;
  419. }
  420. }
  421. }
  422. }, {
  423. key: "bubbleUp",
  424. value: function bubbleUp(n) {
  425. // Look up the target element and its score.
  426. var length = this.content.length,
  427. element = this.content[n],
  428. elemScore = this.scoreFunction(element);
  429. while (true) {
  430. // Compute the indices of the child elements.
  431. var child2N = n + 1 << 1,
  432. child1N = child2N - 1;
  433. // This is used to store the new position of the element,
  434. // if any.
  435. var swap = null;
  436. var child1Score = void 0;
  437. // If the first child exists (is inside the array)...
  438. if (child1N < length) {
  439. // Look it up and compute its score.
  440. var child1 = this.content[child1N];
  441. child1Score = this.scoreFunction(child1);
  442. // If the score is less than our element's, we need to swap.
  443. if (child1Score < elemScore) {
  444. swap = child1N;
  445. }
  446. }
  447. // Do the same checks for the other child.
  448. if (child2N < length) {
  449. var child2 = this.content[child2N],
  450. child2Score = this.scoreFunction(child2);
  451. if (child2Score < (swap === null ? elemScore : child1Score)) {
  452. swap = child2N;
  453. }
  454. }
  455. // If the element needs to be moved, swap it, and continue.
  456. if (swap !== null) {
  457. this.content[n] = this.content[swap];
  458. this.content[swap] = element;
  459. n = swap;
  460. }
  461. // Otherwise, we are done.
  462. else {
  463. break;
  464. }
  465. }
  466. }
  467. }]);
  468. return BinaryHeap;
  469. }();
  470. module.exports = BinaryHeap;
  471. },{}],8:[function(require,module,exports){
  472. 'use strict';
  473. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  474. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  475. var utils = require('./utils');
  476. var polygonId = 1;
  477. var Builder = function () {
  478. function Builder() {
  479. _classCallCheck(this, Builder);
  480. }
  481. _createClass(Builder, null, [{
  482. key: 'buildZone',
  483. /**
  484. * Constructs groups from the given navigation mesh.
  485. * @param {THREE.Geometry} geometry
  486. * @return {Zone}
  487. */
  488. value: function buildZone(geometry) {
  489. var _this = this;
  490. var navMesh = this._buildNavigationMesh(geometry);
  491. var zone = {};
  492. navMesh.vertices.forEach(function (v) {
  493. v.x = utils.roundNumber(v.x, 2);
  494. v.y = utils.roundNumber(v.y, 2);
  495. v.z = utils.roundNumber(v.z, 2);
  496. });
  497. zone.vertices = navMesh.vertices;
  498. var groups = this._buildPolygonGroups(navMesh);
  499. zone.groups = [];
  500. var findPolygonIndex = function findPolygonIndex(group, p) {
  501. for (var i = 0; i < group.length; i++) {
  502. if (p === group[i]) return i;
  503. }
  504. };
  505. groups.forEach(function (group) {
  506. var newGroup = [];
  507. group.forEach(function (p) {
  508. var neighbours = p.neighbours.map(function (n) {
  509. return findPolygonIndex(group, n);
  510. });
  511. // Build a portal list to each neighbour
  512. var portals = p.neighbours.map(function (n) {
  513. return _this._getSharedVerticesInOrder(p, n);
  514. });
  515. p.centroid.x = utils.roundNumber(p.centroid.x, 2);
  516. p.centroid.y = utils.roundNumber(p.centroid.y, 2);
  517. p.centroid.z = utils.roundNumber(p.centroid.z, 2);
  518. newGroup.push({
  519. id: findPolygonIndex(group, p),
  520. neighbours: neighbours,
  521. vertexIds: p.vertexIds,
  522. centroid: p.centroid,
  523. portals: portals
  524. });
  525. });
  526. zone.groups.push(newGroup);
  527. });
  528. return zone;
  529. }
  530. /**
  531. * Constructs a navigation mesh from the given geometry.
  532. * @param {THREE.Geometry} geometry
  533. * @return {Object}
  534. */
  535. }, {
  536. key: '_buildNavigationMesh',
  537. value: function _buildNavigationMesh(geometry) {
  538. utils.computeCentroids(geometry);
  539. geometry.mergeVertices();
  540. return this._buildPolygonsFromGeometry(geometry);
  541. }
  542. }, {
  543. key: '_buildPolygonGroups',
  544. value: function _buildPolygonGroups(navigationMesh) {
  545. var polygons = navigationMesh.polygons;
  546. var polygonGroups = [];
  547. var groupCount = 0;
  548. var spreadGroupId = function spreadGroupId(polygon) {
  549. polygon.neighbours.forEach(function (neighbour) {
  550. if (neighbour.group === undefined) {
  551. neighbour.group = polygon.group;
  552. spreadGroupId(neighbour);
  553. }
  554. });
  555. };
  556. polygons.forEach(function (polygon) {
  557. if (polygon.group === undefined) {
  558. polygon.group = groupCount++;
  559. // Spread it
  560. spreadGroupId(polygon);
  561. }
  562. if (!polygonGroups[polygon.group]) polygonGroups[polygon.group] = [];
  563. polygonGroups[polygon.group].push(polygon);
  564. });
  565. return polygonGroups;
  566. }
  567. }, {
  568. key: '_buildPolygonNeighbours',
  569. value: function _buildPolygonNeighbours(polygon, navigationMesh) {
  570. polygon.neighbours = [];
  571. // All other nodes that contain at least two of our vertices are our neighbours
  572. for (var i = 0, len = navigationMesh.polygons.length; i < len; i++) {
  573. if (polygon === navigationMesh.polygons[i]) continue;
  574. // Don't check polygons that are too far, since the intersection tests take a long time
  575. if (polygon.centroid.distanceToSquared(navigationMesh.polygons[i].centroid) > 100 * 100) continue;
  576. var matches = utils.array_intersect(polygon.vertexIds, navigationMesh.polygons[i].vertexIds);
  577. if (matches.length >= 2) {
  578. polygon.neighbours.push(navigationMesh.polygons[i]);
  579. }
  580. }
  581. }
  582. }, {
  583. key: '_buildPolygonsFromGeometry',
  584. value: function _buildPolygonsFromGeometry(geometry) {
  585. var _this2 = this;
  586. var polygons = [];
  587. var vertices = geometry.vertices;
  588. var faceVertexUvs = geometry.faceVertexUvs;
  589. // Convert the faces into a custom format that supports more than 3 vertices
  590. geometry.faces.forEach(function (face) {
  591. polygons.push({
  592. id: polygonId++,
  593. vertexIds: [face.a, face.b, face.c],
  594. centroid: face.centroid,
  595. normal: face.normal,
  596. neighbours: []
  597. });
  598. });
  599. var navigationMesh = {
  600. polygons: polygons,
  601. vertices: vertices,
  602. faceVertexUvs: faceVertexUvs
  603. };
  604. // Build a list of adjacent polygons
  605. polygons.forEach(function (polygon) {
  606. _this2._buildPolygonNeighbours(polygon, navigationMesh);
  607. });
  608. return navigationMesh;
  609. }
  610. }, {
  611. key: '_getSharedVerticesInOrder',
  612. value: function _getSharedVerticesInOrder(a, b) {
  613. var aList = a.vertexIds;
  614. var bList = b.vertexIds;
  615. var sharedVertices = [];
  616. aList.forEach(function (vId) {
  617. if (bList.includes(vId)) {
  618. sharedVertices.push(vId);
  619. }
  620. });
  621. if (sharedVertices.length < 2) return [];
  622. if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
  623. // Vertices on both edges are bad, so shift them once to the left
  624. aList.push(aList.shift());
  625. }
  626. if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
  627. // Vertices on both edges are bad, so shift them once to the left
  628. bList.push(bList.shift());
  629. }
  630. // Again!
  631. sharedVertices.length = 0;
  632. aList.forEach(function (vId) {
  633. if (bList.includes(vId)) {
  634. sharedVertices.push(vId);
  635. }
  636. });
  637. return sharedVertices;
  638. }
  639. }]);
  640. return Builder;
  641. }();
  642. module.exports = Builder;
  643. },{"./utils":11}],9:[function(require,module,exports){
  644. 'use strict';
  645. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  646. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  647. var utils = require('./utils');
  648. var Channel = function () {
  649. function Channel() {
  650. _classCallCheck(this, Channel);
  651. this.portals = [];
  652. }
  653. _createClass(Channel, [{
  654. key: 'push',
  655. value: function push(p1, p2) {
  656. if (p2 === undefined) p2 = p1;
  657. this.portals.push({
  658. left: p1,
  659. right: p2
  660. });
  661. }
  662. }, {
  663. key: 'stringPull',
  664. value: function stringPull() {
  665. var portals = this.portals;
  666. var pts = [];
  667. // Init scan state
  668. var portalApex = void 0,
  669. portalLeft = void 0,
  670. portalRight = void 0;
  671. var apexIndex = 0,
  672. leftIndex = 0,
  673. rightIndex = 0;
  674. portalApex = portals[0].left;
  675. portalLeft = portals[0].left;
  676. portalRight = portals[0].right;
  677. // Add start point.
  678. pts.push(portalApex);
  679. for (var i = 1; i < portals.length; i++) {
  680. var left = portals[i].left;
  681. var right = portals[i].right;
  682. // Update right vertex.
  683. if (utils.triarea2(portalApex, portalRight, right) <= 0.0) {
  684. if (utils.vequal(portalApex, portalRight) || utils.triarea2(portalApex, portalLeft, right) > 0.0) {
  685. // Tighten the funnel.
  686. portalRight = right;
  687. rightIndex = i;
  688. } else {
  689. // Right over left, insert left to path and restart scan from portal left point.
  690. pts.push(portalLeft);
  691. // Make current left the new apex.
  692. portalApex = portalLeft;
  693. apexIndex = leftIndex;
  694. // Reset portal
  695. portalLeft = portalApex;
  696. portalRight = portalApex;
  697. leftIndex = apexIndex;
  698. rightIndex = apexIndex;
  699. // Restart scan
  700. i = apexIndex;
  701. continue;
  702. }
  703. }
  704. // Update left vertex.
  705. if (utils.triarea2(portalApex, portalLeft, left) >= 0.0) {
  706. if (utils.vequal(portalApex, portalLeft) || utils.triarea2(portalApex, portalRight, left) < 0.0) {
  707. // Tighten the funnel.
  708. portalLeft = left;
  709. leftIndex = i;
  710. } else {
  711. // Left over right, insert right to path and restart scan from portal right point.
  712. pts.push(portalRight);
  713. // Make current right the new apex.
  714. portalApex = portalRight;
  715. apexIndex = rightIndex;
  716. // Reset portal
  717. portalLeft = portalApex;
  718. portalRight = portalApex;
  719. leftIndex = apexIndex;
  720. rightIndex = apexIndex;
  721. // Restart scan
  722. i = apexIndex;
  723. continue;
  724. }
  725. }
  726. }
  727. if (pts.length === 0 || !utils.vequal(pts[pts.length - 1], portals[portals.length - 1].left)) {
  728. // Append last point to path.
  729. pts.push(portals[portals.length - 1].left);
  730. }
  731. this.path = pts;
  732. return pts;
  733. }
  734. }]);
  735. return Channel;
  736. }();
  737. module.exports = Channel;
  738. },{"./utils":11}],10:[function(require,module,exports){
  739. 'use strict';
  740. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  741. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  742. /* global THREE */
  743. var utils = require('./utils');
  744. var AStar = require('./AStar');
  745. var Builder = require('./Builder');
  746. var Channel = require('./Channel');
  747. /**
  748. * Defines an instance of the pathfinding module, with one or more zones.
  749. */
  750. var Path = function () {
  751. function Path() {
  752. _classCallCheck(this, Path);
  753. this.zones = {};
  754. }
  755. /**
  756. * (Static) Builds a zone/node set from navigation mesh geometry.
  757. * @param {THREE.Geometry} geometry
  758. * @return {Zone}
  759. */
  760. _createClass(Path, [{
  761. key: 'setZoneData',
  762. /**
  763. * Sets data for the given zone.
  764. * @param {string} zoneID
  765. * @param {Zone} zone
  766. */
  767. value: function setZoneData(zoneID, zone) {
  768. this.zones[zoneID] = zone;
  769. }
  770. /**
  771. * Returns closest node group ID for given position.
  772. * @param {string} zoneID
  773. * @param {THREE.Vector3} position
  774. * @return {number}
  775. */
  776. }, {
  777. key: 'getGroup',
  778. value: function getGroup(zoneID, position) {
  779. if (!this.zones[zoneID]) return null;
  780. var closestNodeGroup = null;
  781. var distance = Math.pow(50, 2);
  782. this.zones[zoneID].groups.forEach(function (group, index) {
  783. group.forEach(function (node) {
  784. var measuredDistance = utils.distanceToSquared(node.centroid, position);
  785. if (measuredDistance < distance) {
  786. closestNodeGroup = index;
  787. distance = measuredDistance;
  788. }
  789. });
  790. });
  791. return closestNodeGroup;
  792. }
  793. /**
  794. * Returns a random node within a given range of a given position.
  795. * @param {string} zoneID
  796. * @param {number} groupID
  797. * @param {THREE.Vector3} nearPosition
  798. * @param {number} nearRange
  799. * @return {Node}
  800. */
  801. }, {
  802. key: 'getRandomNode',
  803. value: function getRandomNode(zoneID, groupID, nearPosition, nearRange) {
  804. if (!this.zones[zoneID]) return new THREE.Vector3();
  805. nearPosition = nearPosition || null;
  806. nearRange = nearRange || 0;
  807. var candidates = [];
  808. var polygons = this.zones[zoneID].groups[groupID];
  809. polygons.forEach(function (p) {
  810. if (nearPosition && nearRange) {
  811. if (utils.distanceToSquared(nearPosition, p.centroid) < nearRange * nearRange) {
  812. candidates.push(p.centroid);
  813. }
  814. } else {
  815. candidates.push(p.centroid);
  816. }
  817. });
  818. return utils.sample(candidates) || new THREE.Vector3();
  819. }
  820. /**
  821. * Returns the closest node to the target position.
  822. * @param {THREE.Vector3} position
  823. * @param {string} zoneID
  824. * @param {number} groupID
  825. * @param {boolean} checkPolygon
  826. * @return {Node}
  827. */
  828. }, {
  829. key: 'getClosestNode',
  830. value: function getClosestNode(position, zoneID, groupID) {
  831. var checkPolygon = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
  832. var nodes = this.zones[zoneID].groups[groupID];
  833. var vertices = this.zones[zoneID].vertices;
  834. var closestNode = null;
  835. var closestDistance = Infinity;
  836. nodes.forEach(function (node) {
  837. var distance = utils.distanceToSquared(node.centroid, position);
  838. if (distance < closestDistance && (!checkPolygon || utils.isVectorInPolygon(position, node, vertices))) {
  839. closestNode = node;
  840. closestDistance = distance;
  841. }
  842. });
  843. return closestNode;
  844. }
  845. /**
  846. * Returns a path between given start and end points. If a complete path
  847. * cannot be found, will return the nearest endpoint available.
  848. *
  849. * @param {THREE.Vector3} startPosition Start position.
  850. * @param {THREE.Vector3} targetPosition Destination.
  851. * @param {string} zoneID ID of current zone.
  852. * @param {number} groupID Current group ID.
  853. * @return {Array<THREE.Vector3>} Array of points defining the path.
  854. */
  855. }, {
  856. key: 'findPath',
  857. value: function findPath(startPosition, targetPosition, zoneID, groupID) {
  858. var nodes = this.zones[zoneID].groups[groupID];
  859. var vertices = this.zones[zoneID].vertices;
  860. var closestNode = this.getClosestNode(startPosition, zoneID, groupID);
  861. var farthestNode = this.getClosestNode(targetPosition, zoneID, groupID, true);
  862. // If we can't find any node, just go straight to the target
  863. if (!closestNode || !farthestNode) {
  864. return null;
  865. }
  866. var paths = AStar.search(nodes, closestNode, farthestNode);
  867. var getPortalFromTo = function getPortalFromTo(a, b) {
  868. for (var i = 0; i < a.neighbours.length; i++) {
  869. if (a.neighbours[i] === b.id) {
  870. return a.portals[i];
  871. }
  872. }
  873. };
  874. // We have the corridor, now pull the rope.
  875. var channel = new Channel();
  876. channel.push(startPosition);
  877. for (var i = 0; i < paths.length; i++) {
  878. var polygon = paths[i];
  879. var nextPolygon = paths[i + 1];
  880. if (nextPolygon) {
  881. var portals = getPortalFromTo(polygon, nextPolygon);
  882. channel.push(vertices[portals[0]], vertices[portals[1]]);
  883. }
  884. }
  885. channel.push(targetPosition);
  886. channel.stringPull();
  887. // Return the path, omitting first position (which is already known).
  888. var path = channel.path.map(function (c) {
  889. return new THREE.Vector3(c.x, c.y, c.z);
  890. });
  891. path.shift();
  892. return path;
  893. }
  894. }], [{
  895. key: 'createZone',
  896. value: function createZone(geometry) {
  897. return Builder.buildZone(geometry);
  898. }
  899. }]);
  900. return Path;
  901. }();
  902. /**
  903. * Clamps a step along the navmesh, given start and desired endpoint. May be
  904. * used to constrain first-person / WASD controls.
  905. *
  906. * @param {THREE.Vector3} start
  907. * @param {THREE.Vector3} end Desired endpoint.
  908. * @param {Node} node
  909. * @param {string} zoneID
  910. * @param {number} groupID
  911. * @param {THREE.Vector3} endTarget Updated endpoint.
  912. * @return {Node} Updated node.
  913. */
  914. Path.prototype.clampStep = function () {
  915. var point = new THREE.Vector3();
  916. var plane = new THREE.Plane();
  917. var triangle = new THREE.Triangle();
  918. var closestNode = void 0;
  919. var closestPoint = new THREE.Vector3();
  920. var closestDistance = void 0;
  921. return function (start, end, node, zoneID, groupID, endTarget) {
  922. var vertices = this.zones[zoneID].vertices;
  923. var nodes = this.zones[zoneID].groups[groupID];
  924. var nodeQueue = [node];
  925. var nodeDepth = {};
  926. nodeDepth[node.id] = 0;
  927. closestNode = undefined;
  928. closestPoint.set(0, 0, 0);
  929. closestDistance = Infinity;
  930. // Project the step along the current node.
  931. plane.setFromCoplanarPoints(vertices[node.vertexIds[0]], vertices[node.vertexIds[1]], vertices[node.vertexIds[2]]);
  932. plane.projectPoint(end, point);
  933. end.copy(point);
  934. for (var currentNode = nodeQueue.pop(); currentNode; currentNode = nodeQueue.pop()) {
  935. triangle.set(vertices[currentNode.vertexIds[0]], vertices[currentNode.vertexIds[1]], vertices[currentNode.vertexIds[2]]);
  936. triangle.closestPointToPoint(end, point);
  937. if (point.distanceToSquared(end) < closestDistance) {
  938. closestNode = currentNode;
  939. closestPoint.copy(point);
  940. closestDistance = point.distanceToSquared(end);
  941. }
  942. var depth = nodeDepth[currentNode];
  943. if (depth > 2) continue;
  944. for (var i = 0; i < currentNode.neighbours.length; i++) {
  945. var neighbour = nodes[currentNode.neighbours[i]];
  946. if (neighbour.id in nodeDepth) continue;
  947. nodeQueue.push(neighbour);
  948. nodeDepth[neighbour.id] = depth + 1;
  949. }
  950. }
  951. endTarget.copy(closestPoint);
  952. return closestNode;
  953. };
  954. }();
  955. /**
  956. * Defines a zone of interconnected groups on a navigation mesh.
  957. *
  958. * @type {Object}
  959. * @property {Array<Group>} groups
  960. * @property {Array<THREE.Vector3} vertices
  961. */
  962. var Zone = {}; // jshint ignore:line
  963. /**
  964. * Defines a group within a navigation mesh.
  965. *
  966. * @type {Object}
  967. */
  968. var Group = {}; // jshint ignore:line
  969. /**
  970. * Defines a node (or polygon) within a group.
  971. *
  972. * @type {Object}
  973. * @property {number} id
  974. * @property {Array<number>} neighbours IDs of neighboring nodes.
  975. * @property {Array<number} vertexIds
  976. * @property {THREE.Vector3} centroid
  977. * @property {Array<Array<number>>} portals Array of portals, each defined by two vertex IDs.
  978. * @property {boolean} closed
  979. * @property {number} cost
  980. */
  981. var Node = {}; // jshint ignore:line
  982. module.exports = Path;
  983. },{"./AStar":6,"./Builder":8,"./Channel":9,"./utils":11}],11:[function(require,module,exports){
  984. 'use strict';
  985. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  986. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  987. var Utils = function () {
  988. function Utils() {
  989. _classCallCheck(this, Utils);
  990. }
  991. _createClass(Utils, null, [{
  992. key: 'computeCentroids',
  993. value: function computeCentroids(geometry) {
  994. var f, fl, face;
  995. for (f = 0, fl = geometry.faces.length; f < fl; f++) {
  996. face = geometry.faces[f];
  997. face.centroid = new THREE.Vector3(0, 0, 0);
  998. face.centroid.add(geometry.vertices[face.a]);
  999. face.centroid.add(geometry.vertices[face.b]);
  1000. face.centroid.add(geometry.vertices[face.c]);
  1001. face.centroid.divideScalar(3);
  1002. }
  1003. }
  1004. }, {
  1005. key: 'roundNumber',
  1006. value: function roundNumber(number, decimals) {
  1007. var newnumber = Number(number + '').toFixed(parseInt(decimals));
  1008. return parseFloat(newnumber);
  1009. }
  1010. }, {
  1011. key: 'sample',
  1012. value: function sample(list) {
  1013. return list[Math.floor(Math.random() * list.length)];
  1014. }
  1015. }, {
  1016. key: 'mergeVertexIds',
  1017. value: function mergeVertexIds(aList, bList) {
  1018. var sharedVertices = [];
  1019. aList.forEach(function (vID) {
  1020. if (bList.indexOf(vID) >= 0) {
  1021. sharedVertices.push(vID);
  1022. }
  1023. });
  1024. if (sharedVertices.length < 2) return [];
  1025. if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
  1026. // Vertices on both edges are bad, so shift them once to the left
  1027. aList.push(aList.shift());
  1028. }
  1029. if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
  1030. // Vertices on both edges are bad, so shift them once to the left
  1031. bList.push(bList.shift());
  1032. }
  1033. // Again!
  1034. sharedVertices = [];
  1035. aList.forEach(function (vId) {
  1036. if (bList.includes(vId)) {
  1037. sharedVertices.push(vId);
  1038. }
  1039. });
  1040. var clockwiseMostSharedVertex = sharedVertices[1];
  1041. var counterClockwiseMostSharedVertex = sharedVertices[0];
  1042. var cList = aList.slice();
  1043. while (cList[0] !== clockwiseMostSharedVertex) {
  1044. cList.push(cList.shift());
  1045. }
  1046. var c = 0;
  1047. var temp = bList.slice();
  1048. while (temp[0] !== counterClockwiseMostSharedVertex) {
  1049. temp.push(temp.shift());
  1050. if (c++ > 10) throw new Error('Unexpected state');
  1051. }
  1052. // Shave
  1053. temp.shift();
  1054. temp.pop();
  1055. cList = cList.concat(temp);
  1056. return cList;
  1057. }
  1058. }, {
  1059. key: 'setPolygonCentroid',
  1060. value: function setPolygonCentroid(polygon, navigationMesh) {
  1061. var sum = new THREE.Vector3();
  1062. var vertices = navigationMesh.vertices;
  1063. polygon.vertexIds.forEach(function (vId) {
  1064. sum.add(vertices[vId]);
  1065. });
  1066. sum.divideScalar(polygon.vertexIds.length);
  1067. polygon.centroid.copy(sum);
  1068. }
  1069. }, {
  1070. key: 'cleanPolygon',
  1071. value: function cleanPolygon(polygon, navigationMesh) {
  1072. var newVertexIds = [];
  1073. var vertices = navigationMesh.vertices;
  1074. for (var i = 0; i < polygon.vertexIds.length; i++) {
  1075. var vertex = vertices[polygon.vertexIds[i]];
  1076. var nextVertexId, previousVertexId;
  1077. var nextVertex, previousVertex;
  1078. if (i === 0) {
  1079. nextVertexId = polygon.vertexIds[1];
  1080. previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 1];
  1081. } else if (i === polygon.vertexIds.length - 1) {
  1082. nextVertexId = polygon.vertexIds[0];
  1083. previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 2];
  1084. } else {
  1085. nextVertexId = polygon.vertexIds[i + 1];
  1086. previousVertexId = polygon.vertexIds[i - 1];
  1087. }
  1088. nextVertex = vertices[nextVertexId];
  1089. previousVertex = vertices[previousVertexId];
  1090. var a = nextVertex.clone().sub(vertex);
  1091. var b = previousVertex.clone().sub(vertex);
  1092. var angle = a.angleTo(b);
  1093. if (angle > Math.PI - 0.01 && angle < Math.PI + 0.01) {
  1094. // Remove the neighbours who had this vertex
  1095. var goodNeighbours = [];
  1096. polygon.neighbours.forEach(function (neighbour) {
  1097. if (!neighbour.vertexIds.includes(polygon.vertexIds[i])) {
  1098. goodNeighbours.push(neighbour);
  1099. }
  1100. });
  1101. polygon.neighbours = goodNeighbours;
  1102. // TODO cleanup the list of vertices and rebuild vertexIds for all polygons
  1103. } else {
  1104. newVertexIds.push(polygon.vertexIds[i]);
  1105. }
  1106. }
  1107. polygon.vertexIds = newVertexIds;
  1108. this.setPolygonCentroid(polygon, navigationMesh);
  1109. }
  1110. }, {
  1111. key: 'isConvex',
  1112. value: function isConvex(polygon, navigationMesh) {
  1113. var vertices = navigationMesh.vertices;
  1114. if (polygon.vertexIds.length < 3) return false;
  1115. var convex = true;
  1116. var total = 0;
  1117. var results = [];
  1118. for (var i = 0; i < polygon.vertexIds.length; i++) {
  1119. var vertex = vertices[polygon.vertexIds[i]];
  1120. var nextVertex, previousVertex;
  1121. if (i === 0) {
  1122. nextVertex = vertices[polygon.vertexIds[1]];
  1123. previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 1]];
  1124. } else if (i === polygon.vertexIds.length - 1) {
  1125. nextVertex = vertices[polygon.vertexIds[0]];
  1126. previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 2]];
  1127. } else {
  1128. nextVertex = vertices[polygon.vertexIds[i + 1]];
  1129. previousVertex = vertices[polygon.vertexIds[i - 1]];
  1130. }
  1131. var a = nextVertex.clone().sub(vertex);
  1132. var b = previousVertex.clone().sub(vertex);
  1133. var angle = a.angleTo(b);
  1134. total += angle;
  1135. if (angle === Math.PI || angle === 0) return false;
  1136. var r = a.cross(b).y;
  1137. results.push(r);
  1138. }
  1139. // if ( total > (polygon.vertexIds.length-2)*Math.PI ) return false;
  1140. results.forEach(function (r) {
  1141. if (r === 0) convex = false;
  1142. });
  1143. if (results[0] > 0) {
  1144. results.forEach(function (r) {
  1145. if (r < 0) convex = false;
  1146. });
  1147. } else {
  1148. results.forEach(function (r) {
  1149. if (r > 0) convex = false;
  1150. });
  1151. }
  1152. return convex;
  1153. }
  1154. }, {
  1155. key: 'distanceToSquared',
  1156. value: function distanceToSquared(a, b) {
  1157. var dx = a.x - b.x;
  1158. var dy = a.y - b.y;
  1159. var dz = a.z - b.z;
  1160. return dx * dx + dy * dy + dz * dz;
  1161. }
  1162. //+ Jonas Raoni Soares Silva
  1163. //@ http://jsfromhell.com/math/is-point-in-poly [rev. #0]
  1164. }, {
  1165. key: 'isPointInPoly',
  1166. value: function isPointInPoly(poly, pt) {
  1167. for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) {
  1168. (poly[i].z <= pt.z && pt.z < poly[j].z || poly[j].z <= pt.z && pt.z < poly[i].z) && pt.x < (poly[j].x - poly[i].x) * (pt.z - poly[i].z) / (poly[j].z - poly[i].z) + poly[i].x && (c = !c);
  1169. }return c;
  1170. }
  1171. }, {
  1172. key: 'isVectorInPolygon',
  1173. value: function isVectorInPolygon(vector, polygon, vertices) {
  1174. // reference point will be the centroid of the polygon
  1175. // We need to rotate the vector as well as all the points which the polygon uses
  1176. var lowestPoint = 100000;
  1177. var highestPoint = -100000;
  1178. var polygonVertices = [];
  1179. polygon.vertexIds.forEach(function (vId) {
  1180. lowestPoint = Math.min(vertices[vId].y, lowestPoint);
  1181. highestPoint = Math.max(vertices[vId].y, highestPoint);
  1182. polygonVertices.push(vertices[vId]);
  1183. });
  1184. if (vector.y < highestPoint + 0.5 && vector.y > lowestPoint - 0.5 && this.isPointInPoly(polygonVertices, vector)) {
  1185. return true;
  1186. }
  1187. return false;
  1188. }
  1189. }, {
  1190. key: 'triarea2',
  1191. value: function triarea2(a, b, c) {
  1192. var ax = b.x - a.x;
  1193. var az = b.z - a.z;
  1194. var bx = c.x - a.x;
  1195. var bz = c.z - a.z;
  1196. return bx * az - ax * bz;
  1197. }
  1198. }, {
  1199. key: 'vequal',
  1200. value: function vequal(a, b) {
  1201. return this.distanceToSquared(a, b) < 0.00001;
  1202. }
  1203. }, {
  1204. key: 'array_intersect',
  1205. value: function array_intersect() {
  1206. var i = void 0,
  1207. shortest = void 0,
  1208. nShortest = void 0,
  1209. n = void 0,
  1210. len = void 0,
  1211. ret = [],
  1212. obj = {},
  1213. nOthers = void 0;
  1214. nOthers = arguments.length - 1;
  1215. nShortest = arguments[0].length;
  1216. shortest = 0;
  1217. for (i = 0; i <= nOthers; i++) {
  1218. n = arguments[i].length;
  1219. if (n < nShortest) {
  1220. shortest = i;
  1221. nShortest = n;
  1222. }
  1223. }
  1224. for (i = 0; i <= nOthers; i++) {
  1225. n = i === shortest ? 0 : i || shortest; //Read the shortest array first. Read the first array instead of the shortest
  1226. len = arguments[n].length;
  1227. for (var j = 0; j < len; j++) {
  1228. var elem = arguments[n][j];
  1229. if (obj[elem] === i - 1) {
  1230. if (i === nOthers) {
  1231. ret.push(elem);
  1232. obj[elem] = 0;
  1233. } else {
  1234. obj[elem] = i;
  1235. }
  1236. } else if (i === 0) {
  1237. obj[elem] = 0;
  1238. }
  1239. }
  1240. }
  1241. return ret;
  1242. }
  1243. }]);
  1244. return Utils;
  1245. }();
  1246. module.exports = Utils;
  1247. },{}]},{},[1]);