aframe-extras.pathfinding.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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":3}],2:[function(require,module,exports){
  5. var e=function(){};e.computeCentroids=function(e){var t,n,r;for(t=0,n=e.faces.length;t<n;t++)(r=e.faces[t]).centroid=new THREE.Vector3(0,0,0),r.centroid.add(e.vertices[r.a]),r.centroid.add(e.vertices[r.b]),r.centroid.add(e.vertices[r.c]),r.centroid.divideScalar(3)},e.roundNumber=function(e,t){return Number(e.toFixed(t))},e.sample=function(e){return e[Math.floor(Math.random()*e.length)]},e.mergeVertexIds=function(e,t){var n=[];if(e.forEach(function(e){t.indexOf(e)>=0&&n.push(e)}),n.length<2)return[];n.includes(e[0])&&n.includes(e[e.length-1])&&e.push(e.shift()),n.includes(t[0])&&n.includes(t[t.length-1])&&t.push(t.shift()),n=[],e.forEach(function(e){t.includes(e)&&n.push(e)});for(var r=n[1],o=n[0],i=e.slice();i[0]!==r;)i.push(i.shift());for(var s=0,u=t.slice();u[0]!==o;)if(u.push(u.shift()),s++>10)throw new Error("Unexpected state");return u.shift(),u.pop(),i=i.concat(u)},e.setPolygonCentroid=function(e,t){var n=new THREE.Vector3,r=t.vertices;e.vertexIds.forEach(function(e){n.add(r[e])}),n.divideScalar(e.vertexIds.length),e.centroid.copy(n)},e.cleanPolygon=function(e,t){for(var n=[],r=t.vertices,o=0;o<e.vertexIds.length;o++){var i,s,u,c=r[e.vertexIds[o]];0===o?(i=e.vertexIds[1],s=e.vertexIds[e.vertexIds.length-1]):o===e.vertexIds.length-1?(i=e.vertexIds[0],s=e.vertexIds[e.vertexIds.length-2]):(i=e.vertexIds[o+1],s=e.vertexIds[o-1]),u=r[s];var h=r[i].clone().sub(c),a=u.clone().sub(c),d=h.angleTo(a);if(d>Math.PI-.01&&d<Math.PI+.01){var f=[];e.neighbours.forEach(function(t){t.vertexIds.includes(e.vertexIds[o])||f.push(t)}),e.neighbours=f}else n.push(e.vertexIds[o])}e.vertexIds=n,this.setPolygonCentroid(e,t)},e.isConvex=function(e,t){var n=t.vertices;if(e.vertexIds.length<3)return!1;for(var r=!0,o=[],i=0;i<e.vertexIds.length;i++){var s,u,c=n[e.vertexIds[i]];0===i?(s=n[e.vertexIds[1]],u=n[e.vertexIds[e.vertexIds.length-1]]):i===e.vertexIds.length-1?(s=n[e.vertexIds[0]],u=n[e.vertexIds[e.vertexIds.length-2]]):(s=n[e.vertexIds[i+1]],u=n[e.vertexIds[i-1]]);var h=s.clone().sub(c),a=u.clone().sub(c),d=h.angleTo(a);if(d===Math.PI||0===d)return!1;var f=h.cross(a).y;o.push(f)}return o.forEach(function(e){0===e&&(r=!1)}),o.forEach(o[0]>0?function(e){e<0&&(r=!1)}:function(e){e>0&&(r=!1)}),r},e.distanceToSquared=function(e,t){var n=e.x-t.x,r=e.y-t.y,o=e.z-t.z;return n*n+r*r+o*o},e.isPointInPoly=function(e,t){for(var n=!1,r=-1,o=e.length,i=o-1;++r<o;i=r)(e[r].z<=t.z&&t.z<e[i].z||e[i].z<=t.z&&t.z<e[r].z)&&t.x<(e[i].x-e[r].x)*(t.z-e[r].z)/(e[i].z-e[r].z)+e[r].x&&(n=!n);return n},e.isVectorInPolygon=function(e,t,n){var r=1e5,o=-1e5,i=[];return t.vertexIds.forEach(function(e){r=Math.min(n[e].y,r),o=Math.max(n[e].y,o),i.push(n[e])}),!!(e.y<o+.5&&e.y>r-.5&&this.isPointInPoly(i,e))},e.triarea2=function(e,t,n){return(n.x-e.x)*(t.z-e.z)-(t.x-e.x)*(n.z-e.z)},e.vequal=function(e,t){return this.distanceToSquared(e,t)<1e-5};var t=function(e){this.content=[],this.scoreFunction=e};t.prototype.push=function(e){this.content.push(e),this.sinkDown(this.content.length-1)},t.prototype.pop=function(){var e=this.content[0],t=this.content.pop();return this.content.length>0&&(this.content[0]=t,this.bubbleUp(0)),e},t.prototype.remove=function(e){var t=this.content.indexOf(e),n=this.content.pop();t!==this.content.length-1&&(this.content[t]=n,this.scoreFunction(n)<this.scoreFunction(e)?this.sinkDown(t):this.bubbleUp(t))},t.prototype.size=function(){return this.content.length},t.prototype.rescoreElement=function(e){this.sinkDown(this.content.indexOf(e))},t.prototype.sinkDown=function(e){for(var t=this.content[e];e>0;){var n=(e+1>>1)-1,r=this.content[n];if(!(this.scoreFunction(t)<this.scoreFunction(r)))break;this.content[n]=t,this.content[e]=r,e=n}},t.prototype.bubbleUp=function(e){for(var t=this.content.length,n=this.content[e],r=this.scoreFunction(n);;){var o=e+1<<1,i=o-1,s=null,u=void 0;if(i<t)(u=this.scoreFunction(this.content[i]))<r&&(s=i);if(o<t)this.scoreFunction(this.content[o])<(null===s?r:u)&&(s=o);if(null===s)break;this.content[e]=this.content[s],this.content[s]=n,e=s}};var n=function(){};n.init=function(e){for(var t=0;t<e.length;t++){var n=e[t];n.f=0,n.g=0,n.h=0,n.cost=1,n.visited=!1,n.closed=!1,n.parent=null}},n.cleanUp=function(e){for(var t=0;t<e.length;t++){var n=e[t];delete n.f,delete n.g,delete n.h,delete n.cost,delete n.visited,delete n.closed,delete n.parent}},n.heap=function(){return new t(function(e){return e.f})},n.search=function(e,t,n){this.init(e);var r=this.heap();for(r.push(t);r.size()>0;){var o=r.pop();if(o===n){for(var i=o,s=[];i.parent;)s.push(i),i=i.parent;return this.cleanUp(s),s.reverse()}o.closed=!0;for(var u=this.neighbours(e,o),c=0,h=u.length;c<h;c++){var a=u[c];if(!a.closed){var d=o.g+a.cost,f=a.visited;if(!f||d<a.g){if(a.visited=!0,a.parent=o,!a.centroid||!n.centroid)throw new Error("Unexpected state");a.h=a.h||this.heuristic(a.centroid,n.centroid),a.g=d,a.f=a.g+a.h,f?r.rescoreElement(a):r.push(a)}}}}return[]},n.heuristic=function(t,n){return e.distanceToSquared(t,n)},n.neighbours=function(e,t){for(var n=[],r=0;r<t.neighbours.length;r++)n.push(e[t.neighbours[r]]);return n};var r=1,o=function(){};o.buildZone=function(t){var n=this,r=this._buildNavigationMesh(t),o={};r.vertices.forEach(function(t){t.x=e.roundNumber(t.x,2),t.y=e.roundNumber(t.y,2),t.z=e.roundNumber(t.z,2)}),o.vertices=r.vertices;var i=this._buildPolygonGroups(r);o.groups=[];var s=function(e,t){for(var n=0;n<e.length;n++)if(t===e[n])return n};return i.forEach(function(t){var r=[];t.forEach(function(o){var i=o.neighbours.map(function(e){return s(t,e)}),u=o.neighbours.map(function(e){return n._getSharedVerticesInOrder(o,e)});o.centroid.x=e.roundNumber(o.centroid.x,2),o.centroid.y=e.roundNumber(o.centroid.y,2),o.centroid.z=e.roundNumber(o.centroid.z,2),r.push({id:s(t,o),neighbours:i,vertexIds:o.vertexIds,centroid:o.centroid,portals:u})}),o.groups.push(r)}),o},o._buildNavigationMesh=function(t){return e.computeCentroids(t),t.mergeVertices(),this._buildPolygonsFromGeometry(t)},o._buildPolygonGroups=function(e){var t=[],n=0,r=function(e){e.neighbours.forEach(function(t){void 0===t.group&&(t.group=e.group,r(t))})};return e.polygons.forEach(function(e){void 0===e.group&&(e.group=n++,r(e)),t[e.group]||(t[e.group]=[]),t[e.group].push(e)}),t},o._buildPolygonNeighbours=function(e,t,n){var r=new Set,o=n.get(e.vertexIds[0]),i=n.get(e.vertexIds[1]),s=n.get(e.vertexIds[2]);o.forEach(function(e){(i.has(e)||s.has(e))&&r.add(t.polygons[e])}),i.forEach(function(e){s.has(e)&&r.add(t.polygons[e])}),e.neighbours=Array.from(r)},o._buildPolygonsFromGeometry=function(e){for(var t=this,n=[],o=e.vertices,i=e.faceVertexUvs,s=new Map,u=0;u<o.length;u++)s.set(u,new Set);e.faces.forEach(function(e){n.push({id:r++,vertexIds:[e.a,e.b,e.c],centroid:e.centroid,normal:e.normal,neighbours:[]}),s.get(e.a).add(n.length-1),s.get(e.b).add(n.length-1),s.get(e.c).add(n.length-1)});var c={polygons:n,vertices:o,faceVertexUvs:i};return n.forEach(function(e){t._buildPolygonNeighbours(e,c,s)}),c},o._getSharedVerticesInOrder=function(e,t){var n=e.vertexIds,r=t.vertexIds,o=new Set;if(n.forEach(function(e){r.includes(e)&&o.add(e)}),o.size<2)return[];o.has(n[0])&&o.has(n[n.length-1])&&n.push(n.shift()),o.has(r[0])&&o.has(r[r.length-1])&&r.push(r.shift());var i=[];return n.forEach(function(e){r.includes(e)&&i.push(e)}),i};var i=function(){this.portals=[]};i.prototype.push=function(e,t){void 0===t&&(t=e),this.portals.push({left:e,right:t})},i.prototype.stringPull=function(){var t,n,r,o=this.portals,i=[],s=0,u=0,c=0;n=o[0].left,r=o[0].right,i.push(t=o[0].left);for(var h=1;h<o.length;h++){var a=o[h].left,d=o[h].right;if(e.triarea2(t,r,d)<=0){if(!(e.vequal(t,r)||e.triarea2(t,n,d)>0)){i.push(n),n=t=n,r=t,u=s=u,c=s,h=s;continue}r=d,c=h}if(e.triarea2(t,n,a)>=0){if(!(e.vequal(t,n)||e.triarea2(t,r,a)<0)){i.push(r),n=t=r,r=t,u=s=c,c=s,h=s;continue}n=a,u=h}}return 0!==i.length&&e.vequal(i[i.length-1],o[o.length-1].left)||i.push(o[o.length-1].left),this.path=i,i};var s,u,c,h,a,d,f=function(){this.zones={}};f.createZone=function(e){return o.buildZone(e)},f.prototype.setZoneData=function(e,t){this.zones[e]=t},f.prototype.getGroup=function(t,n){if(!this.zones[t])return null;var r=null,o=Math.pow(50,2);return this.zones[t].groups.forEach(function(t,i){t.forEach(function(t){var s=e.distanceToSquared(t.centroid,n);s<o&&(r=i,o=s)})}),r},f.prototype.getRandomNode=function(t,n,r,o){if(!this.zones[t])return new THREE.Vector3;r=r||null,o=o||0;var i=[];return this.zones[t].groups[n].forEach(function(t){r&&o?e.distanceToSquared(r,t.centroid)<o*o&&i.push(t.centroid):i.push(t.centroid)}),e.sample(i)||new THREE.Vector3},f.prototype.getClosestNode=function(t,n,r,o){void 0===o&&(o=!1);var i=this.zones[n].vertices,s=null,u=Infinity;return this.zones[n].groups[r].forEach(function(n){var r=e.distanceToSquared(n.centroid,t);r<u&&(!o||e.isVectorInPolygon(t,n,i))&&(s=n,u=r)}),s},f.prototype.findPath=function(e,t,r,o){var s=this.zones[r].groups[o],u=this.zones[r].vertices,c=this.getClosestNode(e,r,o),h=this.getClosestNode(t,r,o,!0);if(!c||!h)return null;var a=n.search(s,c,h),d=function(e,t){for(var n=0;n<e.neighbours.length;n++)if(e.neighbours[n]===t.id)return e.portals[n]},f=new i;f.push(e);for(var l=0;l<a.length;l++){var v=a[l+1];if(v){var p=d(a[l],v);f.push(u[p[0]],u[p[1]])}}f.push(t),f.stringPull();var g=f.path.map(function(e){return new THREE.Vector3(e.x,e.y,e.z)});return g.shift(),g},f.prototype.clampStep=(c=new THREE.Vector3,h=new THREE.Plane,a=new THREE.Triangle,d=new THREE.Vector3,function(e,t,n,r,o,i){var f=this.zones[r].vertices,l=this.zones[r].groups[o],v=[n],p={};p[n.id]=0,s=void 0,d.set(0,0,0),u=Infinity,h.setFromCoplanarPoints(f[n.vertexIds[0]],f[n.vertexIds[1]],f[n.vertexIds[2]]),h.projectPoint(t,c),t.copy(c);for(var g=v.pop();g;g=v.pop()){a.set(f[g.vertexIds[0]],f[g.vertexIds[1]],f[g.vertexIds[2]]),a.closestPointToPoint(t,c),c.distanceToSquared(t)<u&&(s=g,d.copy(c),u=c.distanceToSquared(t));var x=p[g];if(!(x>2))for(var I=0;I<g.neighbours.length;I++){var b=l[g.neighbours[I]];b.id in p||(v.push(b),p[b.id]=x+1)}}return i.copy(d),s}),exports.Pathfinding=f;
  6. },{}],3:[function(require,module,exports){
  7. 'use strict';
  8. require('./nav-mesh');
  9. require('./nav-agent');
  10. require('./system');
  11. },{"./nav-agent":4,"./nav-mesh":5,"./system":6}],4:[function(require,module,exports){
  12. 'use strict';
  13. module.exports = AFRAME.registerComponent('nav-agent', {
  14. schema: {
  15. destination: { type: 'vec3' },
  16. active: { default: false },
  17. speed: { default: 2 }
  18. },
  19. init: function init() {
  20. this.system = this.el.sceneEl.systems.nav;
  21. this.system.addAgent(this);
  22. this.group = null;
  23. this.path = [];
  24. this.raycaster = new THREE.Raycaster();
  25. },
  26. remove: function remove() {
  27. this.system.removeAgent(this);
  28. },
  29. update: function update() {
  30. this.path.length = 0;
  31. },
  32. updateNavLocation: function updateNavLocation() {
  33. this.group = null;
  34. this.path = [];
  35. },
  36. tick: function () {
  37. var vDest = new THREE.Vector3();
  38. var vDelta = new THREE.Vector3();
  39. var vNext = new THREE.Vector3();
  40. return function (t, dt) {
  41. var el = this.el;
  42. var data = this.data;
  43. var raycaster = this.raycaster;
  44. var speed = data.speed * dt / 1000;
  45. if (!data.active) return;
  46. // Use PatrolJS pathfinding system to get shortest path to target.
  47. if (!this.path.length) {
  48. var position = this.el.object3D.position;
  49. this.group = this.group || this.system.getGroup(position);
  50. this.path = this.system.getPath(position, vDest.copy(data.destination), this.group) || [];
  51. el.emit('navigation-start');
  52. }
  53. // If no path is found, exit.
  54. if (!this.path.length) {
  55. console.warn('[nav] Unable to find path to %o.', data.destination);
  56. this.el.setAttribute('nav-agent', { active: false });
  57. el.emit('navigation-end');
  58. return;
  59. }
  60. // Current segment is a vector from current position to next waypoint.
  61. var vCurrent = el.object3D.position;
  62. var vWaypoint = this.path[0];
  63. vDelta.subVectors(vWaypoint, vCurrent);
  64. var distance = vDelta.length();
  65. var gazeTarget = void 0;
  66. if (distance < speed) {
  67. // If <1 step from current waypoint, discard it and move toward next.
  68. this.path.shift();
  69. // After discarding the last waypoint, exit pathfinding.
  70. if (!this.path.length) {
  71. this.el.setAttribute('nav-agent', { active: false });
  72. el.emit('navigation-end');
  73. return;
  74. }
  75. vNext.copy(vCurrent);
  76. gazeTarget = this.path[0];
  77. } else {
  78. // If still far away from next waypoint, find next position for
  79. // the current frame.
  80. vNext.copy(vDelta.setLength(speed)).add(vCurrent);
  81. gazeTarget = vWaypoint;
  82. }
  83. // Look at the next waypoint.
  84. gazeTarget.y = vCurrent.y;
  85. el.object3D.lookAt(gazeTarget);
  86. // Raycast against the nav mesh, to keep the agent moving along the
  87. // ground, not traveling in a straight line from higher to lower waypoints.
  88. raycaster.ray.origin.copy(vNext);
  89. raycaster.ray.origin.y += 1.5;
  90. raycaster.ray.direction.y = -1;
  91. var intersections = raycaster.intersectObject(this.system.getNavMesh());
  92. if (!intersections.length) {
  93. // Raycasting failed. Step toward the waypoint and hope for the best.
  94. vCurrent.copy(vNext);
  95. } else {
  96. // Re-project next position onto nav mesh.
  97. vDelta.subVectors(intersections[0].point, vCurrent);
  98. vCurrent.add(vDelta.setLength(speed));
  99. }
  100. };
  101. }()
  102. });
  103. },{}],5:[function(require,module,exports){
  104. 'use strict';
  105. /**
  106. * nav-mesh
  107. *
  108. * Waits for a mesh to be loaded on the current entity, then sets it as the
  109. * nav mesh in the pathfinding system.
  110. */
  111. module.exports = AFRAME.registerComponent('nav-mesh', {
  112. init: function init() {
  113. this.system = this.el.sceneEl.systems.nav;
  114. this.hasLoadedNavMesh = false;
  115. this.el.addEventListener('object3dset', this.loadNavMesh.bind(this));
  116. },
  117. play: function play() {
  118. if (!this.hasLoadedNavMesh) this.loadNavMesh();
  119. },
  120. loadNavMesh: function loadNavMesh() {
  121. var object = this.el.getObject3D('mesh');
  122. var scene = this.el.sceneEl.object3D;
  123. if (!object) return;
  124. var navMesh = void 0;
  125. object.traverse(function (node) {
  126. if (node.isMesh) navMesh = node;
  127. });
  128. if (!navMesh) return;
  129. var navMeshGeometry = navMesh.geometry.isBufferGeometry ? new THREE.Geometry().fromBufferGeometry(navMesh.geometry) : navMesh.geometry.clone();
  130. scene.updateMatrixWorld();
  131. navMeshGeometry.applyMatrix(navMesh.matrixWorld);
  132. this.system.setNavMeshGeometry(navMeshGeometry);
  133. this.hasLoadedNavMesh = true;
  134. }
  135. });
  136. },{}],6:[function(require,module,exports){
  137. 'use strict';
  138. var _require = require('three-pathfinding'),
  139. Pathfinding = _require.Pathfinding;
  140. var pathfinder = new Pathfinding();
  141. var ZONE = 'level';
  142. /**
  143. * nav
  144. *
  145. * Pathfinding system, using PatrolJS.
  146. */
  147. module.exports = AFRAME.registerSystem('nav', {
  148. init: function init() {
  149. this.navMesh = null;
  150. this.agents = new Set();
  151. },
  152. /**
  153. * @param {THREE.Geometry} geometry
  154. */
  155. setNavMeshGeometry: function setNavMeshGeometry(geometry) {
  156. this.navMesh = new THREE.Mesh(geometry);
  157. pathfinder.setZoneData(ZONE, Pathfinding.createZone(geometry));
  158. Array.from(this.agents).forEach(function (agent) {
  159. return agent.updateNavLocation();
  160. });
  161. },
  162. /**
  163. * @return {THREE.Mesh}
  164. */
  165. getNavMesh: function getNavMesh() {
  166. return this.navMesh;
  167. },
  168. /**
  169. * @param {NavAgent} ctrl
  170. */
  171. addAgent: function addAgent(ctrl) {
  172. this.agents.add(ctrl);
  173. },
  174. /**
  175. * @param {NavAgent} ctrl
  176. */
  177. removeAgent: function removeAgent(ctrl) {
  178. this.agents.delete(ctrl);
  179. },
  180. /**
  181. * @param {THREE.Vector3} start
  182. * @param {THREE.Vector3} end
  183. * @param {number} groupID
  184. * @return {Array<THREE.Vector3>}
  185. */
  186. getPath: function getPath(start, end, groupID) {
  187. return this.navMesh ? pathfinder.findPath(start, end, ZONE, groupID) : null;
  188. },
  189. /**
  190. * @param {THREE.Vector3} position
  191. * @return {number}
  192. */
  193. getGroup: function getGroup(position) {
  194. return this.navMesh ? pathfinder.getGroup(ZONE, position) : null;
  195. },
  196. /**
  197. * @param {THREE.Vector3} position
  198. * @param {number} groupID
  199. * @return {Node}
  200. */
  201. getNode: function getNode(position, groupID) {
  202. return this.navMesh ? pathfinder.getClosestNode(position, ZONE, groupID, true) : null;
  203. },
  204. /**
  205. * @param {THREE.Vector3} start Starting position.
  206. * @param {THREE.Vector3} end Desired ending position.
  207. * @param {number} groupID
  208. * @param {Node} node
  209. * @param {THREE.Vector3} endTarget (Output) Adjusted step end position.
  210. * @return {Node} Current node, after step is taken.
  211. */
  212. clampStep: function clampStep(start, end, groupID, node, endTarget) {
  213. if (!this.navMesh) {
  214. endTarget.copy(end);
  215. return null;
  216. } else if (!node) {
  217. endTarget.copy(end);
  218. return this.getNode(end, groupID);
  219. }
  220. return pathfinder.clampStep(start, end, node, ZONE, groupID, endTarget);
  221. }
  222. });
  223. },{"three-pathfinding":2}]},{},[1]);