aframe-extras.pathfinding.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215
  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. require('./src/pathfinding').registerAll();
  3. },{"./src/pathfinding":2}],2:[function(require,module,exports){
  4. module.exports = {
  5. 'nav-mesh': require('./nav-mesh'),
  6. 'nav-controller': require('./nav-controller'),
  7. 'system': require('./system'),
  8. registerAll: function (AFRAME) {
  9. if (this._registered) return;
  10. AFRAME = AFRAME || window.AFRAME;
  11. if (!AFRAME.components['nav-mesh']) {
  12. AFRAME.registerComponent('nav-mesh', this['nav-mesh']);
  13. }
  14. if (!AFRAME.components['nav-controller']) {
  15. AFRAME.registerComponent('nav-controller', this['nav-controller']);
  16. }
  17. if (!AFRAME.systems.nav) {
  18. AFRAME.registerSystem('nav', this.system);
  19. }
  20. this._registered = true;
  21. }
  22. };
  23. },{"./nav-controller":3,"./nav-mesh":4,"./system":5}],3:[function(require,module,exports){
  24. module.exports = {
  25. schema: {
  26. destination: {type: 'vec3'},
  27. active: {default: false},
  28. speed: {default: 2}
  29. },
  30. init: function () {
  31. this.system = this.el.sceneEl.systems.nav;
  32. this.system.addController(this);
  33. this.path = [];
  34. this.raycaster = new THREE.Raycaster();
  35. },
  36. remove: function () {
  37. this.system.removeController(this);
  38. },
  39. update: function () {
  40. this.path.length = 0;
  41. },
  42. tick: (function () {
  43. var vDest = new THREE.Vector3();
  44. var vDelta = new THREE.Vector3();
  45. var vNext = new THREE.Vector3();
  46. return function (t, dt) {
  47. var el = this.el;
  48. var data = this.data;
  49. var raycaster = this.raycaster;
  50. var speed = data.speed * dt / 1000;
  51. if (!data.active) return;
  52. // Use PatrolJS pathfinding system to get shortest path to target.
  53. if (!this.path.length) {
  54. this.path = this.system.getPath(this.el.object3D, vDest.copy(data.destination));
  55. this.path = this.path || [];
  56. el.emit('nav-start');
  57. }
  58. // If no path is found, exit.
  59. if (!this.path.length) {
  60. console.warn('[nav] Unable to find path to %o.', data.destination);
  61. this.el.setAttribute('nav-controller', {active: false});
  62. el.emit('nav-end');
  63. return;
  64. }
  65. // Current segment is a vector from current position to next waypoint.
  66. var vCurrent = el.object3D.position;
  67. var vWaypoint = this.path[0];
  68. vDelta.subVectors(vWaypoint, vCurrent);
  69. var distance = vDelta.length();
  70. var gazeTarget;
  71. if (distance < speed) {
  72. // If <1 step from current waypoint, discard it and move toward next.
  73. this.path.shift();
  74. // After discarding the last waypoint, exit pathfinding.
  75. if (!this.path.length) {
  76. this.el.setAttribute('nav-controller', {active: false});
  77. el.emit('nav-end');
  78. return;
  79. } else {
  80. gazeTarget = this.path[0];
  81. }
  82. } else {
  83. // If still far away from next waypoint, find next position for
  84. // the current frame.
  85. vNext.copy(vDelta.setLength(speed)).add(vCurrent);
  86. gazeTarget = vWaypoint;
  87. }
  88. // Look at the next waypoint.
  89. gazeTarget.y = vCurrent.y;
  90. el.object3D.lookAt(gazeTarget);
  91. // Raycast against the nav mesh, to keep the controller moving along the
  92. // ground, not traveling in a straight line from higher to lower waypoints.
  93. raycaster.ray.origin.copy(vNext);
  94. raycaster.ray.origin.y += 1.5;
  95. raycaster.ray.direction.y = -1;
  96. var intersections = raycaster.intersectObject(this.system.getNavMesh());
  97. if (!intersections.length) {
  98. // Raycasting failed. Step toward the waypoint and hope for the best.
  99. vCurrent.copy(vNext);
  100. } else {
  101. // Re-project next position onto nav mesh.
  102. vDelta.subVectors(intersections[0].point, vCurrent);
  103. vCurrent.add(vDelta.setLength(speed));
  104. }
  105. };
  106. }())
  107. };
  108. },{}],4:[function(require,module,exports){
  109. /**
  110. * nav-mesh
  111. *
  112. * Waits for a mesh to be loaded on the current entity, then sets it as the
  113. * nav mesh in the pathfinding system.
  114. */
  115. module.exports = {
  116. init: function () {
  117. this.system = this.el.sceneEl.systems.nav;
  118. this.loadNavMesh();
  119. this.el.addEventListener('model-loaded', this.loadNavMesh.bind(this));
  120. },
  121. loadNavMesh: function () {
  122. var object = this.el.getObject3D('mesh');
  123. if (!object) return;
  124. var navMesh;
  125. object.traverse(function (node) {
  126. if (node.isMesh) navMesh = node;
  127. });
  128. if (!navMesh) return;
  129. this.system.setNavMesh(navMesh);
  130. }
  131. };
  132. },{}],5:[function(require,module,exports){
  133. var Path = require('three-pathfinding');
  134. /**
  135. * nav
  136. *
  137. * Pathfinding system, using PatrolJS.
  138. */
  139. module.exports = {
  140. init: function () {
  141. this.navMesh = null;
  142. this.nodes = null;
  143. this.controllers = new Set();
  144. },
  145. /**
  146. * @param {THREE.Mesh} mesh
  147. */
  148. setNavMesh: function (mesh) {
  149. var geometry = mesh.geometry.isBufferGeometry
  150. ? new THREE.Geometry().fromBufferGeometry(mesh.geometry)
  151. : mesh.geometry;
  152. this.navMesh = new THREE.Mesh(geometry);
  153. this.nodes = Path.buildNodes(this.navMesh.geometry);
  154. Path.setZoneData('level', this.nodes);
  155. },
  156. /**
  157. * @return {THREE.Mesh}
  158. */
  159. getNavMesh: function () {
  160. return this.navMesh;
  161. },
  162. /**
  163. * @param {NavController} ctrl
  164. */
  165. addController: function (ctrl) {
  166. this.controllers.add(ctrl);
  167. },
  168. /**
  169. * @param {NavController} ctrl
  170. */
  171. removeController: function (ctrl) {
  172. this.controllers.remove(ctrl);
  173. },
  174. /**
  175. * @param {NavController} ctrl
  176. * @param {THREE.Vector3} target
  177. * @return {Array<THREE.Vector3>}
  178. */
  179. getPath: function (ctrl, target) {
  180. var start = ctrl.el.object3D.position;
  181. // TODO(donmccurdy): Current group should be cached.
  182. var group = Path.getGroup('level', start);
  183. return Path.findPath(start, target, 'level', group);
  184. }
  185. };
  186. },{"three-pathfinding":9}],6:[function(require,module,exports){
  187. const BinaryHeap = require('./BinaryHeap');
  188. const utils = require('./utils.js');
  189. class AStar {
  190. static init (graph) {
  191. for (let x = 0; x < graph.length; x++) {
  192. //for(var x in graph) {
  193. const node = graph[x];
  194. node.f = 0;
  195. node.g = 0;
  196. node.h = 0;
  197. node.cost = 1.0;
  198. node.visited = false;
  199. node.closed = false;
  200. node.parent = null;
  201. }
  202. }
  203. static cleanUp (graph) {
  204. for (let x = 0; x < graph.length; x++) {
  205. const node = graph[x];
  206. delete node.f;
  207. delete node.g;
  208. delete node.h;
  209. delete node.cost;
  210. delete node.visited;
  211. delete node.closed;
  212. delete node.parent;
  213. }
  214. }
  215. static heap () {
  216. return new BinaryHeap(function (node) {
  217. return node.f;
  218. });
  219. }
  220. static search (graph, start, end) {
  221. this.init(graph);
  222. //heuristic = heuristic || astar.manhattan;
  223. const openHeap = this.heap();
  224. openHeap.push(start);
  225. while (openHeap.size() > 0) {
  226. // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
  227. const currentNode = openHeap.pop();
  228. // End case -- result has been found, return the traced path.
  229. if (currentNode === end) {
  230. let curr = currentNode;
  231. const ret = [];
  232. while (curr.parent) {
  233. ret.push(curr);
  234. curr = curr.parent;
  235. }
  236. this.cleanUp(ret);
  237. return ret.reverse();
  238. }
  239. // Normal case -- move currentNode from open to closed, process each of its neighbours.
  240. currentNode.closed = true;
  241. // Find all neighbours for the current node. Optionally find diagonal neighbours as well (false by default).
  242. const neighbours = this.neighbours(graph, currentNode);
  243. for (let i = 0, il = neighbours.length; i < il; i++) {
  244. const neighbour = neighbours[i];
  245. if (neighbour.closed) {
  246. // Not a valid node to process, skip to next neighbour.
  247. continue;
  248. }
  249. // The g score is the shortest distance from start to current node.
  250. // We need to check if the path we have arrived at this neighbour is the shortest one we have seen yet.
  251. const gScore = currentNode.g + neighbour.cost;
  252. const beenVisited = neighbour.visited;
  253. if (!beenVisited || gScore < neighbour.g) {
  254. // Found an optimal (so far) path to this node. Take score for node to see how good it is.
  255. neighbour.visited = true;
  256. neighbour.parent = currentNode;
  257. if (!neighbour.centroid || !end.centroid) throw new Error('Unexpected state');
  258. neighbour.h = neighbour.h || this.heuristic(neighbour.centroid, end.centroid);
  259. neighbour.g = gScore;
  260. neighbour.f = neighbour.g + neighbour.h;
  261. if (!beenVisited) {
  262. // Pushing to heap will put it in proper place based on the 'f' value.
  263. openHeap.push(neighbour);
  264. } else {
  265. // Already seen the node, but since it has been rescored we need to reorder it in the heap
  266. openHeap.rescoreElement(neighbour);
  267. }
  268. }
  269. }
  270. }
  271. // No result was found - empty array signifies failure to find path.
  272. return [];
  273. }
  274. static heuristic (pos1, pos2) {
  275. return utils.distanceToSquared(pos1, pos2);
  276. }
  277. static neighbours (graph, node) {
  278. const ret = [];
  279. for (let e = 0; e < node.neighbours.length; e++) {
  280. ret.push(graph[node.neighbours[e]]);
  281. }
  282. return ret;
  283. }
  284. }
  285. module.exports = AStar;
  286. },{"./BinaryHeap":7,"./utils.js":10}],7:[function(require,module,exports){
  287. // javascript-astar
  288. // http://github.com/bgrins/javascript-astar
  289. // Freely distributable under the MIT License.
  290. // Implements the astar search algorithm in javascript using a binary heap.
  291. class BinaryHeap {
  292. constructor (scoreFunction) {
  293. this.content = [];
  294. this.scoreFunction = scoreFunction;
  295. }
  296. push (element) {
  297. // Add the new element to the end of the array.
  298. this.content.push(element);
  299. // Allow it to sink down.
  300. this.sinkDown(this.content.length - 1);
  301. }
  302. pop () {
  303. // Store the first element so we can return it later.
  304. const result = this.content[0];
  305. // Get the element at the end of the array.
  306. const end = this.content.pop();
  307. // If there are any elements left, put the end element at the
  308. // start, and let it bubble up.
  309. if (this.content.length > 0) {
  310. this.content[0] = end;
  311. this.bubbleUp(0);
  312. }
  313. return result;
  314. }
  315. remove (node) {
  316. const i = this.content.indexOf(node);
  317. // When it is found, the process seen in 'pop' is repeated
  318. // to fill up the hole.
  319. const end = this.content.pop();
  320. if (i !== this.content.length - 1) {
  321. this.content[i] = end;
  322. if (this.scoreFunction(end) < this.scoreFunction(node)) {
  323. this.sinkDown(i);
  324. } else {
  325. this.bubbleUp(i);
  326. }
  327. }
  328. }
  329. size () {
  330. return this.content.length;
  331. }
  332. rescoreElement (node) {
  333. this.sinkDown(this.content.indexOf(node));
  334. }
  335. sinkDown (n) {
  336. // Fetch the element that has to be sunk.
  337. const element = this.content[n];
  338. // When at 0, an element can not sink any further.
  339. while (n > 0) {
  340. // Compute the parent element's index, and fetch it.
  341. const parentN = ((n + 1) >> 1) - 1;
  342. const parent = this.content[parentN];
  343. if (this.scoreFunction(element) < this.scoreFunction(parent)) {
  344. // Swap the elements if the parent is greater.
  345. this.content[parentN] = element;
  346. this.content[n] = parent;
  347. // Update 'n' to continue at the new position.
  348. n = parentN;
  349. } else {
  350. // Found a parent that is less, no need to sink any further.
  351. break;
  352. }
  353. }
  354. }
  355. bubbleUp (n) {
  356. // Look up the target element and its score.
  357. const length = this.content.length,
  358. element = this.content[n],
  359. elemScore = this.scoreFunction(element);
  360. while (true) {
  361. // Compute the indices of the child elements.
  362. const child2N = (n + 1) << 1,
  363. child1N = child2N - 1;
  364. // This is used to store the new position of the element,
  365. // if any.
  366. let swap = null;
  367. let child1Score;
  368. // If the first child exists (is inside the array)...
  369. if (child1N < length) {
  370. // Look it up and compute its score.
  371. const child1 = this.content[child1N];
  372. child1Score = this.scoreFunction(child1);
  373. // If the score is less than our element's, we need to swap.
  374. if (child1Score < elemScore) {
  375. swap = child1N;
  376. }
  377. }
  378. // Do the same checks for the other child.
  379. if (child2N < length) {
  380. const child2 = this.content[child2N],
  381. child2Score = this.scoreFunction(child2);
  382. if (child2Score < (swap === null ? elemScore : child1Score)) {
  383. swap = child2N;
  384. }
  385. }
  386. // If the element needs to be moved, swap it, and continue.
  387. if (swap !== null) {
  388. this.content[n] = this.content[swap];
  389. this.content[swap] = element;
  390. n = swap;
  391. }
  392. // Otherwise, we are done.
  393. else {
  394. break;
  395. }
  396. }
  397. }
  398. }
  399. module.exports = BinaryHeap;
  400. },{}],8:[function(require,module,exports){
  401. const utils = require('./utils');
  402. class Channel {
  403. constructor () {
  404. this.portals = [];
  405. }
  406. push (p1, p2) {
  407. if (p2 === undefined) p2 = p1;
  408. this.portals.push({
  409. left: p1,
  410. right: p2
  411. });
  412. }
  413. stringPull () {
  414. const portals = this.portals;
  415. const pts = [];
  416. // Init scan state
  417. let portalApex, portalLeft, portalRight;
  418. let apexIndex = 0,
  419. leftIndex = 0,
  420. rightIndex = 0;
  421. portalApex = portals[0].left;
  422. portalLeft = portals[0].left;
  423. portalRight = portals[0].right;
  424. // Add start point.
  425. pts.push(portalApex);
  426. for (let i = 1; i < portals.length; i++) {
  427. const left = portals[i].left;
  428. const right = portals[i].right;
  429. // Update right vertex.
  430. if (utils.triarea2(portalApex, portalRight, right) <= 0.0) {
  431. if (utils.vequal(portalApex, portalRight) || utils.triarea2(portalApex, portalLeft, right) > 0.0) {
  432. // Tighten the funnel.
  433. portalRight = right;
  434. rightIndex = i;
  435. } else {
  436. // Right over left, insert left to path and restart scan from portal left point.
  437. pts.push(portalLeft);
  438. // Make current left the new apex.
  439. portalApex = portalLeft;
  440. apexIndex = leftIndex;
  441. // Reset portal
  442. portalLeft = portalApex;
  443. portalRight = portalApex;
  444. leftIndex = apexIndex;
  445. rightIndex = apexIndex;
  446. // Restart scan
  447. i = apexIndex;
  448. continue;
  449. }
  450. }
  451. // Update left vertex.
  452. if (utils.triarea2(portalApex, portalLeft, left) >= 0.0) {
  453. if (utils.vequal(portalApex, portalLeft) || utils.triarea2(portalApex, portalRight, left) < 0.0) {
  454. // Tighten the funnel.
  455. portalLeft = left;
  456. leftIndex = i;
  457. } else {
  458. // Left over right, insert right to path and restart scan from portal right point.
  459. pts.push(portalRight);
  460. // Make current right the new apex.
  461. portalApex = portalRight;
  462. apexIndex = rightIndex;
  463. // Reset portal
  464. portalLeft = portalApex;
  465. portalRight = portalApex;
  466. leftIndex = apexIndex;
  467. rightIndex = apexIndex;
  468. // Restart scan
  469. i = apexIndex;
  470. continue;
  471. }
  472. }
  473. }
  474. if ((pts.length === 0) || (!utils.vequal(pts[pts.length - 1], portals[portals.length - 1].left))) {
  475. // Append last point to path.
  476. pts.push(portals[portals.length - 1].left);
  477. }
  478. this.path = pts;
  479. return pts;
  480. }
  481. }
  482. module.exports = Channel;
  483. },{"./utils":10}],9:[function(require,module,exports){
  484. const utils = require('./utils');
  485. const AStar = require('./AStar');
  486. const Channel = require('./Channel');
  487. var polygonId = 1;
  488. var buildPolygonGroups = function (navigationMesh) {
  489. var polygons = navigationMesh.polygons;
  490. var polygonGroups = [];
  491. var groupCount = 0;
  492. var spreadGroupId = function (polygon) {
  493. polygon.neighbours.forEach((neighbour) => {
  494. if (neighbour.group === undefined) {
  495. neighbour.group = polygon.group;
  496. spreadGroupId(neighbour);
  497. }
  498. });
  499. };
  500. polygons.forEach((polygon) => {
  501. if (polygon.group === undefined) {
  502. polygon.group = groupCount++;
  503. // Spread it
  504. spreadGroupId(polygon);
  505. }
  506. if (!polygonGroups[polygon.group]) polygonGroups[polygon.group] = [];
  507. polygonGroups[polygon.group].push(polygon);
  508. });
  509. console.log('Groups built: ', polygonGroups.length);
  510. return polygonGroups;
  511. };
  512. var buildPolygonNeighbours = function (polygon, navigationMesh) {
  513. polygon.neighbours = [];
  514. // All other nodes that contain at least two of our vertices are our neighbours
  515. for (var i = 0, len = navigationMesh.polygons.length; i < len; i++) {
  516. if (polygon === navigationMesh.polygons[i]) continue;
  517. // Don't check polygons that are too far, since the intersection tests take a long time
  518. if (polygon.centroid.distanceToSquared(navigationMesh.polygons[i].centroid) > 100 * 100) continue;
  519. var matches = utils.array_intersect(polygon.vertexIds, navigationMesh.polygons[i].vertexIds);
  520. if (matches.length >= 2) {
  521. polygon.neighbours.push(navigationMesh.polygons[i]);
  522. }
  523. }
  524. };
  525. var buildPolygonsFromGeometry = function (geometry) {
  526. console.log('Vertices:', geometry.vertices.length, 'polygons:', geometry.faces.length);
  527. var polygons = [];
  528. var vertices = geometry.vertices;
  529. var faceVertexUvs = geometry.faceVertexUvs;
  530. // Convert the faces into a custom format that supports more than 3 vertices
  531. geometry.faces.forEach((face) => {
  532. polygons.push({
  533. id: polygonId++,
  534. vertexIds: [face.a, face.b, face.c],
  535. centroid: face.centroid,
  536. normal: face.normal,
  537. neighbours: []
  538. });
  539. });
  540. var navigationMesh = {
  541. polygons: polygons,
  542. vertices: vertices,
  543. faceVertexUvs: faceVertexUvs
  544. };
  545. // Build a list of adjacent polygons
  546. polygons.forEach((polygon) => {
  547. buildPolygonNeighbours(polygon, navigationMesh);
  548. });
  549. return navigationMesh;
  550. };
  551. var buildNavigationMesh = function (geometry) {
  552. // Prepare geometry
  553. utils.computeCentroids(geometry);
  554. geometry.mergeVertices();
  555. return buildPolygonsFromGeometry(geometry);
  556. };
  557. var getSharedVerticesInOrder = function (a, b) {
  558. var aList = a.vertexIds;
  559. var bList = b.vertexIds;
  560. var sharedVertices = [];
  561. aList.forEach((vId) => {
  562. if (bList.includes(vId)) {
  563. sharedVertices.push(vId);
  564. }
  565. });
  566. if (sharedVertices.length < 2) return [];
  567. // console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
  568. if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
  569. // Vertices on both edges are bad, so shift them once to the left
  570. aList.push(aList.shift());
  571. }
  572. if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
  573. // Vertices on both edges are bad, so shift them once to the left
  574. bList.push(bList.shift());
  575. }
  576. // Again!
  577. sharedVertices = [];
  578. aList.forEach((vId) => {
  579. if (bList.includes(vId)) {
  580. sharedVertices.push(vId);
  581. }
  582. });
  583. return sharedVertices;
  584. };
  585. var groupNavMesh = function (navigationMesh) {
  586. var saveObj = {};
  587. navigationMesh.vertices.forEach((v) => {
  588. v.x = utils.roundNumber(v.x, 2);
  589. v.y = utils.roundNumber(v.y, 2);
  590. v.z = utils.roundNumber(v.z, 2);
  591. });
  592. saveObj.vertices = navigationMesh.vertices;
  593. var groups = buildPolygonGroups(navigationMesh);
  594. saveObj.groups = [];
  595. var findPolygonIndex = function (group, p) {
  596. for (var i = 0; i < group.length; i++) {
  597. if (p === group[i]) return i;
  598. }
  599. };
  600. groups.forEach((group) => {
  601. var newGroup = [];
  602. group.forEach((p) => {
  603. var neighbours = [];
  604. p.neighbours.forEach((n) => {
  605. neighbours.push(findPolygonIndex(group, n));
  606. });
  607. // Build a portal list to each neighbour
  608. var portals = [];
  609. p.neighbours.forEach((n) => {
  610. portals.push(getSharedVerticesInOrder(p, n));
  611. });
  612. p.centroid.x = utils.roundNumber(p.centroid.x, 2);
  613. p.centroid.y = utils.roundNumber(p.centroid.y, 2);
  614. p.centroid.z = utils.roundNumber(p.centroid.z, 2);
  615. newGroup.push({
  616. id: findPolygonIndex(group, p),
  617. neighbours: neighbours,
  618. vertexIds: p.vertexIds,
  619. centroid: p.centroid,
  620. portals: portals
  621. });
  622. });
  623. saveObj.groups.push(newGroup);
  624. });
  625. return saveObj;
  626. };
  627. var zoneNodes = {};
  628. module.exports = {
  629. buildNodes: function (geometry) {
  630. var navigationMesh = buildNavigationMesh(geometry);
  631. var zoneNodes = groupNavMesh(navigationMesh);
  632. return zoneNodes;
  633. },
  634. setZoneData: function (zone, data) {
  635. zoneNodes[zone] = data;
  636. },
  637. getGroup: function (zone, position) {
  638. if (!zoneNodes[zone]) return null;
  639. var closestNodeGroup = null;
  640. var distance = Math.pow(50, 2);
  641. zoneNodes[zone].groups.forEach((group, index) => {
  642. group.forEach((node) => {
  643. var measuredDistance = utils.distanceToSquared(node.centroid, position);
  644. if (measuredDistance < distance) {
  645. closestNodeGroup = index;
  646. distance = measuredDistance;
  647. }
  648. });
  649. });
  650. return closestNodeGroup;
  651. },
  652. getRandomNode: function (zone, group, nearPosition, nearRange) {
  653. if (!zoneNodes[zone]) return new THREE.Vector3();
  654. nearPosition = nearPosition || null;
  655. nearRange = nearRange || 0;
  656. var candidates = [];
  657. var polygons = zoneNodes[zone].groups[group];
  658. polygons.forEach((p) => {
  659. if (nearPosition && nearRange) {
  660. if (utils.distanceToSquared(nearPosition, p.centroid) < nearRange * nearRange) {
  661. candidates.push(p.centroid);
  662. }
  663. } else {
  664. candidates.push(p.centroid);
  665. }
  666. });
  667. return utils.sample(candidates) || new THREE.Vector3();
  668. },
  669. getClosestNode: function (position, zone, group, checkPolygon = false) {
  670. const nodes = zoneNodes[zone].groups[group];
  671. const vertices = zoneNodes[zone].vertices;
  672. let closestNode = null;
  673. let closestDistance = Infinity;
  674. nodes.forEach((node) => {
  675. const distance = utils.distanceToSquared(node.centroid, position);
  676. if (distance < closestDistance
  677. && (!checkPolygon || utils.isVectorInPolygon(position, node, vertices))) {
  678. closestNode = node;
  679. closestDistance = distance;
  680. }
  681. });
  682. return closestNode;
  683. },
  684. findPath: function (startPosition, targetPosition, zone, group) {
  685. const nodes = zoneNodes[zone].groups[group];
  686. const vertices = zoneNodes[zone].vertices;
  687. const closestNode = this.getClosestNode(startPosition, zone, group);
  688. const farthestNode = this.getClosestNode(targetPosition, zone, group, true);
  689. // If we can't find any node, just go straight to the target
  690. if (!closestNode || !farthestNode) {
  691. return null;
  692. }
  693. const paths = AStar.search(nodes, closestNode, farthestNode);
  694. const getPortalFromTo = function (a, b) {
  695. for (var i = 0; i < a.neighbours.length; i++) {
  696. if (a.neighbours[i] === b.id) {
  697. return a.portals[i];
  698. }
  699. }
  700. };
  701. // We have the corridor, now pull the rope.
  702. const channel = new Channel();
  703. channel.push(startPosition);
  704. for (let i = 0; i < paths.length; i++) {
  705. const polygon = paths[i];
  706. const nextPolygon = paths[i + 1];
  707. if (nextPolygon) {
  708. const portals = getPortalFromTo(polygon, nextPolygon);
  709. channel.push(
  710. vertices[portals[0]],
  711. vertices[portals[1]]
  712. );
  713. }
  714. }
  715. channel.push(targetPosition);
  716. channel.stringPull();
  717. // Return the path, omitting first position (which is already known).
  718. const path = channel.path.map((c) => new THREE.Vector3(c.x, c.y, c.z));
  719. path.shift();
  720. return path;
  721. }
  722. };
  723. },{"./AStar":6,"./Channel":8,"./utils":10}],10:[function(require,module,exports){
  724. class Utils {
  725. static computeCentroids (geometry) {
  726. var f, fl, face;
  727. for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
  728. face = geometry.faces[ f ];
  729. face.centroid = new THREE.Vector3( 0, 0, 0 );
  730. face.centroid.add( geometry.vertices[ face.a ] );
  731. face.centroid.add( geometry.vertices[ face.b ] );
  732. face.centroid.add( geometry.vertices[ face.c ] );
  733. face.centroid.divideScalar( 3 );
  734. }
  735. }
  736. static roundNumber (number, decimals) {
  737. var newnumber = Number(number + '').toFixed(parseInt(decimals));
  738. return parseFloat(newnumber);
  739. }
  740. static sample (list) {
  741. return list[Math.floor(Math.random() * list.length)];
  742. }
  743. static mergeVertexIds (aList, bList) {
  744. var sharedVertices = [];
  745. aList.forEach((vID) => {
  746. if (bList.indexOf(vID) >= 0) {
  747. sharedVertices.push(vID);
  748. }
  749. });
  750. if (sharedVertices.length < 2) return [];
  751. if (sharedVertices.includes(aList[0]) && sharedVertices.includes(aList[aList.length - 1])) {
  752. // Vertices on both edges are bad, so shift them once to the left
  753. aList.push(aList.shift());
  754. }
  755. if (sharedVertices.includes(bList[0]) && sharedVertices.includes(bList[bList.length - 1])) {
  756. // Vertices on both edges are bad, so shift them once to the left
  757. bList.push(bList.shift());
  758. }
  759. // Again!
  760. sharedVertices = [];
  761. aList.forEach((vId) => {
  762. if (bList.includes(vId)) {
  763. sharedVertices.push(vId);
  764. }
  765. });
  766. var clockwiseMostSharedVertex = sharedVertices[1];
  767. var counterClockwiseMostSharedVertex = sharedVertices[0];
  768. var cList = aList.slice();
  769. while (cList[0] !== clockwiseMostSharedVertex) {
  770. cList.push(cList.shift());
  771. }
  772. var c = 0;
  773. var temp = bList.slice();
  774. while (temp[0] !== counterClockwiseMostSharedVertex) {
  775. temp.push(temp.shift());
  776. if (c++ > 10) throw new Error('Unexpected state');
  777. }
  778. // Shave
  779. temp.shift();
  780. temp.pop();
  781. cList = cList.concat(temp);
  782. return cList;
  783. }
  784. static setPolygonCentroid (polygon, navigationMesh) {
  785. var sum = new THREE.Vector3();
  786. var vertices = navigationMesh.vertices;
  787. polygon.vertexIds.forEach((vId) => {
  788. sum.add(vertices[vId]);
  789. });
  790. sum.divideScalar(polygon.vertexIds.length);
  791. polygon.centroid.copy(sum);
  792. }
  793. static cleanPolygon (polygon, navigationMesh) {
  794. var newVertexIds = [];
  795. var vertices = navigationMesh.vertices;
  796. for (var i = 0; i < polygon.vertexIds.length; i++) {
  797. var vertex = vertices[polygon.vertexIds[i]];
  798. var nextVertexId, previousVertexId;
  799. var nextVertex, previousVertex;
  800. // console.log("nextVertex: ", nextVertex);
  801. if (i === 0) {
  802. nextVertexId = polygon.vertexIds[1];
  803. previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 1];
  804. } else if (i === polygon.vertexIds.length - 1) {
  805. nextVertexId = polygon.vertexIds[0];
  806. previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 2];
  807. } else {
  808. nextVertexId = polygon.vertexIds[i + 1];
  809. previousVertexId = polygon.vertexIds[i - 1];
  810. }
  811. nextVertex = vertices[nextVertexId];
  812. previousVertex = vertices[previousVertexId];
  813. var a = nextVertex.clone().sub(vertex);
  814. var b = previousVertex.clone().sub(vertex);
  815. var angle = a.angleTo(b);
  816. // console.log(angle);
  817. if (angle > Math.PI - 0.01 && angle < Math.PI + 0.01) {
  818. // Unneccesary vertex
  819. // console.log("Unneccesary vertex: ", polygon.vertexIds[i]);
  820. // console.log("Angle between "+previousVertexId+", "+polygon.vertexIds[i]+" "+nextVertexId+" was: ", angle);
  821. // Remove the neighbours who had this vertex
  822. var goodNeighbours = [];
  823. polygon.neighbours.forEach((neighbour) => {
  824. if (!neighbour.vertexIds.includes(polygon.vertexIds[i])) {
  825. goodNeighbours.push(neighbour);
  826. }
  827. });
  828. polygon.neighbours = goodNeighbours;
  829. // TODO cleanup the list of vertices and rebuild vertexIds for all polygons
  830. } else {
  831. newVertexIds.push(polygon.vertexIds[i]);
  832. }
  833. }
  834. // console.log("New vertexIds: ", newVertexIds);
  835. polygon.vertexIds = newVertexIds;
  836. setPolygonCentroid(polygon, navigationMesh);
  837. }
  838. static isConvex (polygon, navigationMesh) {
  839. var vertices = navigationMesh.vertices;
  840. if (polygon.vertexIds.length < 3) return false;
  841. var convex = true;
  842. var total = 0;
  843. var results = [];
  844. for (var i = 0; i < polygon.vertexIds.length; i++) {
  845. var vertex = vertices[polygon.vertexIds[i]];
  846. var nextVertex, previousVertex;
  847. if (i === 0) {
  848. nextVertex = vertices[polygon.vertexIds[1]];
  849. previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 1]];
  850. } else if (i === polygon.vertexIds.length - 1) {
  851. nextVertex = vertices[polygon.vertexIds[0]];
  852. previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 2]];
  853. } else {
  854. nextVertex = vertices[polygon.vertexIds[i + 1]];
  855. previousVertex = vertices[polygon.vertexIds[i - 1]];
  856. }
  857. var a = nextVertex.clone().sub(vertex);
  858. var b = previousVertex.clone().sub(vertex);
  859. var angle = a.angleTo(b);
  860. total += angle;
  861. if (angle === Math.PI || angle === 0) return false;
  862. var r = a.cross(b).y;
  863. results.push(r);
  864. }
  865. // if ( total > (polygon.vertexIds.length-2)*Math.PI ) return false;
  866. results.forEach((r) => {
  867. if (r === 0) convex = false;
  868. });
  869. if (results[0] > 0) {
  870. results.forEach((r) => {
  871. if (r < 0) convex = false;
  872. });
  873. } else {
  874. results.forEach((r) => {
  875. if (r > 0) convex = false;
  876. });
  877. }
  878. return convex;
  879. }
  880. static distanceToSquared (a, b) {
  881. var dx = a.x - b.x;
  882. var dy = a.y - b.y;
  883. var dz = a.z - b.z;
  884. return dx * dx + dy * dy + dz * dz;
  885. }
  886. //+ Jonas Raoni Soares Silva
  887. //@ http://jsfromhell.com/math/is-point-in-poly [rev. #0]
  888. static isPointInPoly (poly, pt) {
  889. for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
  890. ((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);
  891. return c;
  892. }
  893. static isVectorInPolygon (vector, polygon, vertices) {
  894. // reference point will be the centroid of the polygon
  895. // We need to rotate the vector as well as all the points which the polygon uses
  896. var lowestPoint = 100000;
  897. var highestPoint = -100000;
  898. var polygonVertices = [];
  899. polygon.vertexIds.forEach((vId) => {
  900. lowestPoint = Math.min(vertices[vId].y, lowestPoint);
  901. highestPoint = Math.max(vertices[vId].y, highestPoint);
  902. polygonVertices.push(vertices[vId]);
  903. });
  904. if (vector.y < highestPoint + 0.5 && vector.y > lowestPoint - 0.5 &&
  905. this.isPointInPoly(polygonVertices, vector)) {
  906. return true;
  907. }
  908. return false;
  909. }
  910. static triarea2 (a, b, c) {
  911. var ax = b.x - a.x;
  912. var az = b.z - a.z;
  913. var bx = c.x - a.x;
  914. var bz = c.z - a.z;
  915. return bx * az - ax * bz;
  916. }
  917. static vequal (a, b) {
  918. return this.distanceToSquared(a, b) < 0.00001;
  919. }
  920. static array_intersect () {
  921. let i, shortest, nShortest, n, len, ret = [],
  922. obj = {},
  923. nOthers;
  924. nOthers = arguments.length - 1;
  925. nShortest = arguments[0].length;
  926. shortest = 0;
  927. for (i = 0; i <= nOthers; i++) {
  928. n = arguments[i].length;
  929. if (n < nShortest) {
  930. shortest = i;
  931. nShortest = n;
  932. }
  933. }
  934. for (i = 0; i <= nOthers; i++) {
  935. n = (i === shortest) ? 0 : (i || shortest); //Read the shortest array first. Read the first array instead of the shortest
  936. len = arguments[n].length;
  937. for (var j = 0; j < len; j++) {
  938. var elem = arguments[n][j];
  939. if (obj[elem] === i - 1) {
  940. if (i === nOthers) {
  941. ret.push(elem);
  942. obj[elem] = 0;
  943. } else {
  944. obj[elem] = i;
  945. }
  946. } else if (i === 0) {
  947. obj[elem] = 0;
  948. }
  949. }
  950. }
  951. return ret;
  952. }
  953. }
  954. module.exports = Utils;
  955. },{}]},{},[1]);