navmesh.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896
  1. /**
  2. * Copyright (C) 2014-2016 Triumph LLC
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. "use strict";
  18. /**
  19. * Navigation mesh internal API.
  20. * @name navmesh
  21. * @namespace
  22. * @exports exports as navmesh
  23. */
  24. b4w.module["__navmesh"] = function(exports, require) {
  25. var m_geom = require("__geometry");
  26. var m_math = require("__math");
  27. var m_mat4 = require("__mat4");
  28. var m_util = require("__util");
  29. var m_vec3 = require("__vec3");
  30. var m_vec4 = require("__vec4");
  31. // TODO: reduce count of tmp variables
  32. var _vec3_tmp = m_vec3.create();
  33. var _vec3_tmp2 = m_vec3.create();
  34. var _vec4_tmp = m_vec4.create();
  35. var _vec4_tmp2 = m_vec4.create();
  36. var _vec4_tmp3 = m_vec4.create();
  37. var _mat4_tmp = m_mat4.create();
  38. var _mat4_tmp2 = m_mat4.create();
  39. var _mat4_tmp3 = m_mat4.create();
  40. var _accum_left_portal_tmp = m_vec3.create();
  41. var _accum_right_portal_tmp = m_vec3.create();
  42. var _accum_left_tmp = m_vec3.create();
  43. var _accum_right_tmp = m_vec3.create();
  44. var _apex_normal_tmp = m_vec3.create();
  45. function merge_vertices(geometry) {
  46. var vertices_map = {}; // Hashmap for looking up vertex by position
  47. // coordinates (and making sure they are unique)
  48. var unique = [];
  49. var changes = [];
  50. var v, key;
  51. var precision_points = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
  52. var precision = Math.pow(10, precision_points);
  53. var i, il, face;
  54. var indices;
  55. for (i = 0, il = geometry.vertices.length; i < il; i++) {
  56. v = geometry.vertices[i];
  57. key = Math.round(v[0] * precision) + '_' + Math.round(v[1] * precision) +
  58. '_' + Math.round(v[2] * precision);
  59. if (vertices_map[key] === undefined) {
  60. vertices_map[key] = i;
  61. unique.push(v);
  62. changes[i] = unique.length - 1;
  63. } else {
  64. changes[i] = changes[vertices_map[key]];
  65. }
  66. }
  67. // If faces are completely degenerate after merging vertices, we
  68. // have to remove them from the geometry.
  69. var face_indices_to_remove = [];
  70. for (i = 0, il = geometry.faces.length; i < il; i++) {
  71. face = geometry.faces[i];
  72. face.indices[0] = changes[face.indices[0]];
  73. face.indices[1] = changes[face.indices[1]];
  74. face.indices[2] = changes[face.indices[2]];
  75. indices = [face.indices[0], face.indices[1], face.indices[2]];
  76. var dup_index = -1;
  77. // If any duplicate vertices are found in a Face3
  78. // we have to remove the face as nothing can be saved
  79. for (var n = 0; n < 3; n++) {
  80. if (indices[n] == indices[(n + 1) % 3]) {
  81. dup_index = n;
  82. face_indices_to_remove.push(i);
  83. break;
  84. }
  85. }
  86. }
  87. for (i = face_indices_to_remove.length - 1; i >= 0; i--) {
  88. var idx = face_indices_to_remove[i];
  89. geometry.faces.splice(idx, 1);
  90. }
  91. // Use unique set of vertices
  92. var diff = geometry.vertices.length - unique.length;
  93. geometry.vertices.length = 0;
  94. for (i in unique) {
  95. geometry.vertices.push(unique[i])
  96. }
  97. return diff;
  98. }
  99. function get_edge_key(vertex_ids_1, vertex_ids_2) {
  100. if (vertex_ids_1 > vertex_ids_2)
  101. return vertex_ids_2 + "-" + vertex_ids_1;
  102. else
  103. return vertex_ids_1 + "-" + vertex_ids_2;
  104. }
  105. function build_polygons_from_geometry(geometry) {
  106. var polygons = [];
  107. var vertices = geometry.vertices;
  108. // Convert the faces into a custom format that supports more than 3 vertices
  109. var polygon_id = 0;
  110. for (var i = 0; i < geometry.faces.length; i++) {
  111. var face = geometry.faces[i];
  112. polygons.push({
  113. id: polygon_id++,
  114. vertex_ids: face.indices,
  115. centroid: face.centroid,
  116. normal: face.normal,
  117. neighbours: []
  118. });
  119. }
  120. var navigation_mesh = {
  121. polygons: polygons,
  122. vertices: vertices
  123. };
  124. // Build a list of adjacent polygons
  125. var dict_edge_polygon = {};
  126. for (var i = 0; i < polygons.length; i++) {
  127. for (var j = 0; j < 3; j++) {
  128. var edge_key = get_edge_key(polygons[i].vertex_ids[j],
  129. polygons[i].vertex_ids[(j + 1) % 3]);
  130. if (!(edge_key in dict_edge_polygon))
  131. dict_edge_polygon[edge_key] = [];
  132. dict_edge_polygon[edge_key].push(polygons[i]);
  133. }
  134. }
  135. var keys = Object.keys(dict_edge_polygon);
  136. for (var i = 0, l = keys.length; i < l; i++) {
  137. var edge_key = keys[i];
  138. for (var j = 0; j < dict_edge_polygon[edge_key].length; j++)
  139. for (var k = j + 1; k < dict_edge_polygon[edge_key].length; k++) {
  140. dict_edge_polygon[edge_key][j].neighbours.push(
  141. dict_edge_polygon[edge_key][k]);
  142. dict_edge_polygon[edge_key][k].neighbours.push(
  143. dict_edge_polygon[edge_key][j]);
  144. }
  145. }
  146. return navigation_mesh;
  147. }
  148. function compute_centroids(geometry) {
  149. var f, face;
  150. for (f = 0; f < geometry.faces.length; f++) {
  151. face = geometry.faces[f];
  152. m_vec3.add(geometry.vertices[face.indices[0]], geometry.vertices[face.indices[1]], _vec3_tmp);
  153. m_vec3.add(_vec3_tmp, geometry.vertices[face.indices[2]], _vec3_tmp);
  154. m_vec3.scale(_vec3_tmp, 1/3, face.centroid);
  155. }
  156. }
  157. function compute_normals(geometry) {
  158. for (var i = 0; i < geometry.faces.length; i++) {
  159. var face = geometry.faces[i];
  160. var v1 = m_vec3.subtract(geometry.vertices[face.indices[1]],
  161. geometry.vertices[face.indices[0]], _vec3_tmp);
  162. var v2 = m_vec3.subtract(geometry.vertices[face.indices[2]],
  163. geometry.vertices[face.indices[0]], _vec3_tmp2);
  164. var normal = m_vec3.cross(v1, v2, _vec3_tmp);
  165. m_vec3.normalize(normal, normal);
  166. m_vec3.copy(normal, face.normal);
  167. }
  168. }
  169. function build_navigation_mesh(geometry) {
  170. compute_centroids(geometry);
  171. compute_normals(geometry);
  172. merge_vertices(geometry);
  173. var navmesh = build_polygons_from_geometry(geometry);
  174. return navmesh
  175. }
  176. function round_number(number, decimals) {
  177. return parseFloat(number.toFixed(decimals));
  178. }
  179. function build_islands(navmesh) {
  180. var polygons = navmesh.polygons;
  181. var islands = [];
  182. var island_count = 0;
  183. var spread_island_id = function (polygon) {
  184. var neighbours = polygon.neighbours;
  185. for (var i = 0; i < neighbours.length; i++) {
  186. var neighbour = neighbours[i];
  187. if (neighbour.island == undefined) {
  188. neighbour.island = polygon.island;
  189. spread_island_id(neighbour);
  190. }
  191. }
  192. };
  193. for (var i = 0; i < polygons.length; i++) {
  194. var polygon = polygons[i];
  195. if (polygon.island == undefined) {
  196. polygon.island = island_count++;
  197. spread_island_id(polygon)
  198. }
  199. if (!islands[polygon.island])
  200. islands[polygon.island] = [];
  201. islands[polygon.island].push(polygons[i]);
  202. }
  203. return islands;
  204. }
  205. function indexOf(l, v) {
  206. if (l.indexOf)
  207. return l.indexOf(v);
  208. else
  209. for (var i = 0; i < l.length; i++) {
  210. if (l[i] == v)
  211. return i;
  212. }
  213. return -1;
  214. }
  215. var get_shared_vertices_in_order = function (a, b) {
  216. function shift_l(uintvec) {
  217. var a = uintvec[0];
  218. uintvec[0] = uintvec[1];
  219. uintvec[1] = uintvec[2];
  220. uintvec[2] = a;
  221. }
  222. var a_list = a.vertex_ids;
  223. var b_list = b.vertex_ids;
  224. var shared_vertices = [];
  225. for (var i = 0; i < a_list.length; i++) {
  226. if (indexOf(b_list, a_list[i]) >= 0) {
  227. shared_vertices.push(a_list[i])
  228. }
  229. }
  230. if (shared_vertices.length < 2)
  231. return [];
  232. if (indexOf(shared_vertices, a_list[0]) >= 0 &&
  233. indexOf(shared_vertices, a_list[a_list.length - 1]) >= 0) {
  234. // Vertices on both edges are bad, so shift them once to the left
  235. shift_l(a_list);
  236. }
  237. if (indexOf(shared_vertices, b_list[0]) >= 0 &&
  238. indexOf(shared_vertices, b_list[b_list.length - 1]) >= 0) {
  239. // Vertices on both edges are bad, so shift them once to the left
  240. shift_l(b_list);
  241. }
  242. // Again!
  243. shared_vertices = [];
  244. for (var i = 0; i < a_list.length; i++) {
  245. if (indexOf(b_list, a_list[i]) >= 0)
  246. shared_vertices.push(a_list[i]);
  247. }
  248. return shared_vertices;
  249. };
  250. function group_navmesh(navmesh) {
  251. var ret = {};
  252. var vert = navmesh.vertices;
  253. for (var i = 0; i < vert.length; i++) {
  254. vert[i][0] = round_number(vert[i][0], 2);
  255. vert[i][1] = round_number(vert[i][1], 2);
  256. vert[i][2] = round_number(vert[i][2], 2);
  257. }
  258. ret.vertices = navmesh.vertices;
  259. var islands = build_islands(navmesh);
  260. ret.islands = [];
  261. var find_polygon_index = function (island, p) {
  262. for (var i = 0; i < island.length; i++) {
  263. if (p === island[i]) return i;
  264. }
  265. };
  266. for (var i = 0; i < islands.length; i++) {
  267. var new_island = [];
  268. var island = islands[i]
  269. for (var j = 0; j < island.length; j++) {
  270. var neighbours = [];
  271. var poly = island[j];
  272. for (var k = 0; k < poly.neighbours.length; k++) {
  273. neighbours.push(find_polygon_index(island, poly.neighbours[k]));
  274. }
  275. // Build a portal list to each neighbour
  276. var portals = [];
  277. for (var k = 0; k < poly.neighbours.length; k++) {
  278. portals.push(get_shared_vertices_in_order(poly, poly.neighbours[k]));
  279. }
  280. poly.centroid[0] = round_number(poly.centroid[0], 2);
  281. poly.centroid[1] = round_number(poly.centroid[1], 2);
  282. poly.centroid[2] = round_number(poly.centroid[2], 2);
  283. new_island.push({
  284. id: find_polygon_index(island, poly),
  285. neighbours: neighbours,
  286. vertex_ids: poly.vertex_ids,
  287. centroid: poly.centroid,
  288. normal: poly.normal,
  289. portals: portals,
  290. // astar
  291. f: 0,
  292. g: 0,
  293. h: 0,
  294. cost: 1.0,
  295. visited: false,
  296. closed: false,
  297. parent: null
  298. // end astar
  299. });
  300. }
  301. ret.islands.push(new_island);
  302. }
  303. return ret;
  304. }
  305. exports.navmesh_build_from_bufs_data = function(bufs_data) {
  306. var vertices = m_geom.get_vbo_source_by_type(bufs_data.vbo_source_data, m_geom.VBO_FLOAT);
  307. var indices = bufs_data.ibo_array;
  308. var faces = [];
  309. for (var i = 0; i < indices.length; i += 3) {
  310. faces.push({
  311. indices: new Uint32Array([indices[i], indices[i + 1], indices[i + 2]]),
  312. centroid: new Float32Array(3),
  313. normal: new Float32Array(3)
  314. })
  315. }
  316. var vert = [];
  317. for (var i = 0; i < vertices.length; i += 3) {
  318. var v = new Float32Array(3);
  319. v[0] = vertices[i];
  320. v[1] = vertices[i + 1];
  321. v[2] = vertices[i + 2];
  322. vert.push(v);
  323. }
  324. var geometry = {
  325. vertices: vert,
  326. faces: faces
  327. };
  328. return group_navmesh(build_navigation_mesh(geometry))
  329. };
  330. exports.navmesh_get_island = function(navmesh, position, distance_to_closest) {
  331. var closest_node_group = null;
  332. var distance = Number.MAX_VALUE;
  333. var islands = navmesh.islands;
  334. for (var i = 0; i < islands.length; i++) {
  335. for (var j = 0; j < islands[i].length; j++) {
  336. var node = islands[i][j];
  337. m_vec3.subtract(node.centroid, position, _vec3_tmp);
  338. var measured_distance = distance_to_closest(position, node.centroid,
  339. node.vertex_ids, navmesh.vertices, distance);
  340. if (measured_distance < distance) {
  341. closest_node_group = i;
  342. distance = measured_distance;
  343. }
  344. }
  345. }
  346. return closest_node_group;
  347. };
  348. /**
  349. * A* search algorithm
  350. * https://github.com/bgrins/javascript-astar/
  351. */
  352. function astar_search(graph, start_node, end_node, start_pos, target_pos, vertices) {
  353. function init(graph) {
  354. for (var x = 0; x < graph.length; x++) {
  355. var node = graph[x];
  356. node.f = 0;
  357. node.g = 0;
  358. node.h = 0;
  359. node.cost = 1.0;
  360. node.visited = false;
  361. node.closed = false;
  362. node.parent = null;
  363. }
  364. }
  365. function heuristic(pos1, pos2) {
  366. m_vec3.subtract(pos1, pos2, _vec3_tmp)
  367. return m_vec3.dot(_vec3_tmp, _vec3_tmp);
  368. }
  369. function get_neighbours(graph, node) {
  370. var ret = [];
  371. for (var e = 0; e < node.neighbours.length; e++) {
  372. ret.push(graph[node.neighbours[e]]);
  373. }
  374. return ret;
  375. }
  376. init(graph);
  377. var open_heap = m_math.binary_heap_new(function (node) {
  378. return node.f;
  379. });
  380. m_math.binary_heap_push(open_heap, start_node);
  381. while (open_heap.content.length > 0) {
  382. // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
  383. var current_node = m_math.binary_heap_pop(open_heap);
  384. // End case -- result has been found, return the traced path.
  385. if (current_node === end_node) {
  386. var curr = current_node;
  387. var ret = [];
  388. while (curr.parent) {
  389. ret.push(curr);
  390. curr = curr.parent;
  391. }
  392. // push first step of path
  393. ret.push(start_node)
  394. return ret.reverse();
  395. }
  396. // Normal case -- move current_node from open to closed, process each
  397. // of its neighbours.
  398. current_node.closed = true;
  399. // Find all neighbours for the current node. Optionally find diagonal
  400. // neighbours as well (false by default).
  401. var neighbours = get_neighbours(graph, current_node);
  402. for (var i = 0, il = neighbours.length; i < il; i++) {
  403. var neighbour = neighbours[i];
  404. if (neighbour.closed) {
  405. // Not a valid node to process, skip to next neighbour.
  406. continue;
  407. }
  408. // The g score is the shortest distance from start to current node.
  409. // We need to check if the path we have arrived at this neighbour
  410. // is the shortest one we have seen yet.
  411. var g_score = current_node.g + m_vec3.dist(neighbour.centroid,
  412. current_node.centroid);
  413. var been_visited = neighbour.visited;
  414. if (!been_visited || g_score < neighbour.g) {
  415. // Found an optimal (so far) path to this node.
  416. // Take score for node to see how good it is.
  417. neighbour.visited = true;
  418. neighbour.parent = current_node;
  419. neighbour.h = neighbour.h || (heuristic(neighbour.centroid, target_pos) + heuristic(neighbour.centroid, start_pos));
  420. neighbour.g = g_score;
  421. neighbour.f = neighbour.g + neighbour.h;
  422. if (!been_visited) {
  423. // Pushing to heap will put it in proper place based on the 'f' value.
  424. m_math.binary_heap_push(open_heap, neighbour);
  425. } else {
  426. // Already seen the node, but since it has been rescored we
  427. // need to reorder it in the heap
  428. m_math.binary_heap_rescore_element(open_heap, neighbour);
  429. }
  430. }
  431. }
  432. }
  433. // No result was found - empty array signifies failure to find path.
  434. return [];
  435. }
  436. function push_vec3(array, vec3) {
  437. array.push(vec3[0]);
  438. array.push(vec3[1]);
  439. array.push(vec3[2]);
  440. }
  441. function get_rotation_sign(origin, p1, p2, normal) {
  442. var dir_o_p1 = m_vec3.subtract(p1, origin, _vec3_tmp);
  443. var dir_o_p2 = m_vec3.subtract(p2, origin, _vec3_tmp2);
  444. var cross = m_vec3.cross(dir_o_p1, dir_o_p2, _vec3_tmp);
  445. return m_util.sign(m_vec3.dot(normal, cross));
  446. }
  447. function vequal(a, b) {
  448. m_vec3.subtract(a, b, _vec3_tmp);
  449. return m_vec3.dot(_vec3_tmp, _vec3_tmp) < 0.00001;
  450. }
  451. // uses _vec3_tmp, _vec3_tmp2, _mat4_tmp3
  452. function update_accum_mat(accum_mat, curr_portal, prev_portal) {
  453. var curr_portal_dir = m_vec3.subtract(curr_portal.right,
  454. curr_portal.left, _vec3_tmp);
  455. var prev_normal = prev_portal.normal;
  456. var curr_normal = curr_portal.normal;
  457. var angle = Math.acos(m_vec3.dot(prev_normal, curr_normal));
  458. var is_right = m_util.sign(m_vec3.dot(curr_portal_dir,
  459. m_vec3.cross(prev_normal, curr_normal, _vec3_tmp2)));
  460. angle *= -is_right;
  461. var mat = m_mat4.identity(_mat4_tmp3);
  462. m_mat4.translate(mat, curr_portal.left, mat);
  463. m_mat4.rotate(mat, angle, curr_portal_dir, mat)
  464. m_mat4.translate(mat, m_vec3.scale(curr_portal.left, -1, _vec3_tmp2), mat);
  465. m_mat4.multiply(accum_mat, mat, accum_mat)
  466. return accum_mat;
  467. }
  468. // uses _vec3_tmp, _vec4_tmp, _vec4_tmp2, _vec4_tmp3
  469. function get_point_on_navmesh(accum_begin_apex, accum_end_apex,
  470. begin_portal, end_portal,
  471. first_poly_normal, interapex_accum_mat, dest) {
  472. // four-dimensional point vector (w === 1)
  473. var accum_begin_portal = m_vec3.transformMat4(begin_portal,
  474. interapex_accum_mat, _vec4_tmp);
  475. accum_begin_portal[3] = 1;
  476. var accum_end_portal = m_vec3.transformMat4(end_portal,
  477. interapex_accum_mat, _vec3_tmp);
  478. // four-dimensional direction vector (w === 0)
  479. var accum_portal_dir = m_vec3.subtract(accum_end_portal,
  480. accum_begin_portal, _vec4_tmp2);
  481. var accum_apex_dir = m_vec3.subtract(accum_end_apex, accum_begin_apex,
  482. _vec3_tmp);
  483. var normal = m_vec3.cross(first_poly_normal, accum_apex_dir, _vec4_tmp3);
  484. if (Math.abs(m_vec3.dot(normal, accum_portal_dir)) < 0.01) {
  485. var t = 1/2;
  486. } else {
  487. // four-dimensional plane representation
  488. m_vec3.normalize(normal, normal);
  489. normal[3] = - m_vec3.dot(normal, accum_end_apex);
  490. var t = - m_vec4.dot(normal, accum_begin_portal) /
  491. m_vec4.dot(normal, accum_portal_dir);
  492. }
  493. var portal_dir = m_vec3.subtract(end_portal, begin_portal, _vec3_tmp);
  494. return m_vec3.scaleAndAdd(begin_portal, portal_dir, t, dest);
  495. }
  496. // uses _mat4_tmp
  497. // dep: uses _vec3_tmp, _vec3_tmp2, _vec4_tmp, _vec4_tmp2, _vec4_tmp3, _mat4_tmp3
  498. function update_crucial_on_navmesh(portals, accum_new_apex, new_apex_index,
  499. old_portal_apex, old_apex_index, apex_normal, pts_dest, return_normals,
  500. normals_dest) {
  501. var interapex_accum_mat = m_mat4.identity(_mat4_tmp);
  502. for (var j = old_apex_index; j < new_apex_index; j++) {
  503. if (portals[j].is_crucial) {
  504. if (j > old_apex_index || !j) {
  505. // use m_vec3.create(), bcz it is a new point.
  506. var navmesh_point = get_point_on_navmesh(
  507. accum_new_apex, old_portal_apex,
  508. portals[j].left, portals[j].right,
  509. apex_normal, interapex_accum_mat,
  510. m_vec3.create());
  511. push_vec3(pts_dest, navmesh_point);
  512. if (return_normals)
  513. push_vec3(normals_dest, portals[j].normal);
  514. if (j)
  515. update_accum_mat(interapex_accum_mat, portals[j], portals[j-1], j);
  516. }
  517. }
  518. }
  519. }
  520. /**
  521. * Pulling the string
  522. * https://skatgame.net/mburo/ps/thesis_demyen_2006.pdf
  523. */
  524. function string_pull(portals, return_normals) {
  525. var pts = [];
  526. var normals = [];
  527. // Init scan state
  528. var portal_apex, portal_left, portal_right;
  529. var accum_portal_left, accum_portal_right, apex_normal;
  530. var apex_index = 0;
  531. var left_index = 0;
  532. var right_index = 0;
  533. portal_apex = portals[0].right;
  534. portal_left = portals[0].left;
  535. portal_right = portals[0].right;
  536. accum_portal_left = m_vec3.copy(portal_left, _accum_left_portal_tmp);
  537. accum_portal_right = m_vec3.copy(portal_right, _accum_right_portal_tmp);
  538. apex_normal = m_vec3.copy(portals[0].normal, _apex_normal_tmp);
  539. // Add start point.
  540. push_vec3(pts, portal_apex);
  541. if (return_normals)
  542. push_vec3(normals, portals[0].normal);
  543. var accum_mat = m_mat4.identity(_mat4_tmp2);
  544. function update_apex(point, index) {
  545. apex_index = index;
  546. left_index = apex_index;
  547. right_index = apex_index;
  548. portal_apex = point;
  549. portal_left = portal_apex;
  550. portal_right = portal_apex;
  551. m_vec3.copy(point, accum_portal_left);
  552. m_vec3.copy(point, accum_portal_right);
  553. m_vec3.copy(portals[apex_index].normal, apex_normal);
  554. }
  555. for (var i = 1; i < portals.length; i++) {
  556. var left = portals[i].left;
  557. var right = portals[i].right;
  558. var accum_left = m_vec3.transformMat4(left, accum_mat, _accum_left_tmp);
  559. var accum_right = m_vec3.transformMat4(right, accum_mat, _accum_right_tmp);
  560. if (portals[i].is_crucial)
  561. update_accum_mat(accum_mat, portals[i], portals[i-1]);
  562. // Update right vertex.
  563. if (get_rotation_sign(portal_apex, accum_portal_right, accum_right,
  564. apex_normal) <= 0.0) {
  565. var eq = vequal(portal_apex, portal_right);
  566. if (eq || get_rotation_sign(portal_apex, accum_portal_left,
  567. accum_right, apex_normal) > 0.0) {
  568. // Tighten the funnel.
  569. portal_right = right;
  570. m_vec3.copy(accum_right, accum_portal_right);
  571. right_index = i;
  572. } else {
  573. var left_is_apex = vequal(accum_portal_left, portal_apex);
  574. if (!left_is_apex)
  575. update_crucial_on_navmesh(portals, accum_portal_left,
  576. left_index, portal_apex, apex_index, apex_normal, pts,
  577. return_normals, normals);
  578. accum_mat = m_mat4.identity(accum_mat);
  579. // Make current left the new apex.
  580. // Right over left, insert left to path and
  581. // restart scan from portal left point.
  582. update_apex(portal_left, left_index);
  583. // Restart scan
  584. i = apex_index;
  585. if (!left_is_apex) {
  586. push_vec3(pts, portal_apex);
  587. if (return_normals)
  588. push_vec3(normals, portals[i].normal);
  589. }
  590. continue;
  591. }
  592. }
  593. // Update left vertex.
  594. if (get_rotation_sign(portal_apex, accum_portal_left, accum_left,
  595. apex_normal) >= 0.0) {
  596. var eq = vequal(portal_apex, portal_left);
  597. if (eq || get_rotation_sign(portal_apex, accum_portal_right,
  598. accum_left, apex_normal) < 0.0) {
  599. // Tighten the funnel.
  600. portal_left = left;
  601. m_vec3.copy(accum_left, accum_portal_left);
  602. left_index = i;
  603. } else {
  604. var right_is_apex = vequal(accum_portal_right, portal_apex);
  605. if (!right_is_apex)
  606. update_crucial_on_navmesh(portals, accum_portal_right,
  607. right_index, portal_apex, apex_index, apex_normal, pts,
  608. return_normals, normals);
  609. accum_mat = m_mat4.identity(accum_mat);
  610. // Make current right the new apex.
  611. // Left over right, insert right to path and
  612. // restart scan from portal right point.
  613. update_apex(portal_right, right_index);
  614. // Restart scan
  615. i = apex_index;
  616. if (!right_is_apex) {
  617. push_vec3(pts, portal_apex);
  618. if (return_normals)
  619. push_vec3(normals, portals[i].normal);
  620. }
  621. continue;
  622. }
  623. }
  624. }
  625. var last_index = portals.length - 1;
  626. var last_portal_left = portals[last_index].left;
  627. var is_last_apex = vequal(portal_apex, last_portal_left);
  628. if (!is_last_apex) {
  629. update_crucial_on_navmesh(portals, accum_portal_left, last_index,
  630. portal_apex, apex_index, apex_normal, pts, return_normals,
  631. normals);
  632. }
  633. if (!is_last_apex || !apex_index) {
  634. // Append last point to path.
  635. push_vec3(pts, last_portal_left);
  636. }
  637. if (return_normals) {
  638. push_vec3(normals, portals[last_index].normal);
  639. return {
  640. "positions": new Float32Array(pts),
  641. "normals": new Float32Array(normals)
  642. }
  643. } else
  644. return {
  645. "positions": new Float32Array(pts),
  646. "normals": null
  647. }
  648. }
  649. function is_point_in_poly(poly, pt) {
  650. for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
  651. ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) ||
  652. (poly[j][1] <= pt[1] && pt[1] < poly[i][1])) &&
  653. (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) /
  654. (poly[j][1] - poly[i][1]) + poly[i][0]) && (c = !c);
  655. return c;
  656. }
  657. exports.is_vector_in_poly = is_vector_in_poly;
  658. function is_vector_in_poly(vector, vertex_ids, vertices) {
  659. var polygon_vertices = [];
  660. for (var i = 0; i < vertex_ids.length; i++) {
  661. var id = vertex_ids[i];
  662. polygon_vertices.push(vertices[id]);
  663. }
  664. if (is_point_in_poly(polygon_vertices, vector))
  665. return true;
  666. return false;
  667. }
  668. function get_navmesh_closest_node(all_nodes, vertices, position,
  669. allowed_distance, distance_function) {
  670. var closest_node = null;
  671. var distance = Number.MAX_VALUE;
  672. for (var i = 0; i < all_nodes.length; i++) {
  673. var node = all_nodes[i];
  674. var measured_distance = distance_function(position, node.centroid,
  675. node.vertex_ids, vertices, distance);
  676. if (measured_distance < distance) {
  677. closest_node = node;
  678. distance = measured_distance;
  679. }
  680. }
  681. if (allowed_distance > 0 && distance > allowed_distance)
  682. return null;
  683. else
  684. return closest_node;
  685. }
  686. function get_portal_from_to(a, b) {
  687. for (var i = 0; i < a.neighbours.length; i++) {
  688. if (a.neighbours[i] === b.id) {
  689. return a.portals[i];
  690. }
  691. }
  692. };
  693. function channel_push(portals, p1, p2, is_crucial, normal) {
  694. portals.push({
  695. left: p1,
  696. right: p2,
  697. is_crucial: is_crucial,
  698. normal: normal
  699. });
  700. }
  701. function get_pulled_string(path, start_pos, target_pos, vertices, return_normals) {
  702. var channel_portals = [];
  703. channel_push(channel_portals, start_pos, start_pos, false,
  704. path[0] && path[0].normal || m_util.AXIS_Z);
  705. for (var i = 0; i < path.length; i++) {
  706. var polygon = path[i];
  707. var next_polygon = path[i + 1];
  708. if (next_polygon) {
  709. // TODO: remove magic constant
  710. var is_crucial = Math.abs(m_vec3.dot(polygon.normal,
  711. next_polygon.normal)) < 0.999;
  712. var portals = get_portal_from_to(polygon, next_polygon);
  713. channel_push(channel_portals,
  714. vertices[portals[0]],
  715. vertices[portals[1]],
  716. is_crucial,
  717. next_polygon.normal
  718. );
  719. }
  720. }
  721. channel_push(channel_portals, target_pos, target_pos, false,
  722. channel_portals[channel_portals.length - 1].normal);
  723. return string_pull(channel_portals, return_normals);
  724. }
  725. exports.navmesh_find_path = function(navmesh, start_pos, target_pos, options) {
  726. var path = find_path(navmesh, start_pos, target_pos, options);
  727. if (!path || !path.length)
  728. return null;
  729. // We got the corridor
  730. // Now pull the rope
  731. if (options.do_not_pull_string) {
  732. return get_centroid_string(path, options.return_normals);
  733. } else {
  734. var vertices = navmesh.vertices;
  735. return get_pulled_string(path, start_pos, target_pos, vertices,
  736. options.return_normals);
  737. }
  738. }
  739. function get_centroid_string(path, return_normals) {
  740. if (return_normals) {
  741. var string = new Float32Array(3 * path.length);
  742. for (var i = 0; i < path.length; i++)
  743. string.set(path[i].centroid, 3 * i);
  744. return {
  745. "positions": string,
  746. "normals": null
  747. };
  748. } else {
  749. var positions = new Float32Array(3 * path.length);
  750. var normals = new Float32Array(3 * path.length);
  751. for (var i = 0; i < path.length; i++) {
  752. positions.set(path[i].centroid, 3 * i);
  753. normals.set(path[i].normal, 3 * i)
  754. }
  755. return {
  756. "positions": positions,
  757. "normals": normals
  758. };
  759. }
  760. }
  761. function find_path(navmesh, start_pos, target_pos, options) {
  762. var island = options.island;
  763. var all_nodes = navmesh.islands[island];
  764. var vertices = navmesh.vertices;
  765. var start_node = get_navmesh_closest_node(all_nodes, vertices, start_pos,
  766. options.allowed_distance, options.distance_to_closest);
  767. if (!start_node)
  768. return null;
  769. var target_node = get_navmesh_closest_node(all_nodes, vertices, target_pos,
  770. options.allowed_distance, options.distance_to_farthest);
  771. if (!target_node)
  772. return null;
  773. return astar_search(all_nodes, start_node, target_node, start_pos,
  774. target_pos, vertices);
  775. }
  776. }