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