babylon-navigation-mesh
Version:
A toolkit to move on navigation mesh with BABYLONJS
1,009 lines (721 loc) • 29 kB
JavaScript
"use strict";
var Class = require("abitbol");
var _ = require("lodash");
var Astar = require("./Astar.js");
var Channel = require("./Channel.js");
var BABYLON = require("babylonjs");
/**
* This component generates screenshots of 3D models, in order to preview them when they are rendered.
*
* @class Focus3D
* @constructor
*/
var Navigation = Class.$extend({
__init__: function () {
this.zoneNodes = {};
this.astar = new Astar();
this.yTolerance = 1;
},
buildNodes: function (mesh) {
var navigationMesh = this._buildNavigationMesh(mesh.geometry);
var zoneNodes = this._groupNavMesh(navigationMesh);
return zoneNodes;
},
setZoneData: function (zone, data) {
this.zoneNodes[zone] = data;
},
setHeightTolerance: function(tolerance) {
this.yTolerance = tolerance;
},
getGroup: function (zone, position) {
if (!this.zoneNodes[zone]) {
return null;
}
var closestNodeGroup = null;
var distance = Infinity;
_.each(this.zoneNodes[zone].groups, function (group, index) {
_.each(group, function (node) {
var measuredDistance = BABYLON.Vector3.DistanceSquared(node.centroid, position);
if (measuredDistance < distance) {
closestNodeGroup = index;
distance = measuredDistance;
}
});
});
return closestNodeGroup;
},
getRandomNode: function (zone, group, nearPosition, nearRange) {
if (!this.zoneNodes[zone]) return new BABYLON.Vector3();
nearPosition = nearPosition || null;
nearRange = nearRange || 0;
var candidates = [];
var polygons = this.zoneNodes[zone].groups[group];
_.each(polygons, function (p) {
if (nearPosition && nearRange) {
if (BABYLON.Vector3.DistanceSquared(nearPosition, p.centroid) < nearRange * nearRange) {
candidates.push(p.centroid);
}
} else {
candidates.push(p.centroid);
}
});
return _.sample(candidates) || new BABYLON.Vector3();
},
projectOnNavmesh: function (position, zone, group) {
var allNodes = this.zoneNodes[zone].groups[group];
var vertices = this.zoneNodes[zone].vertices;
var closestNode = null;
var distance = Infinity;
var finalProj = null,
proj = null,
node = null,
measuredDistance = 0;
for (var i = 0; i < allNodes.length; i++) {
node = allNodes[i];
proj = this._getProjectionOnNode(position, node, vertices);
measuredDistance = BABYLON.Vector3.DistanceSquared(proj, position);
if (measuredDistance < distance) {
distance = measuredDistance;
//this.meshes[3].position.copyFrom(proj);
finalProj = proj;
closestNode = node;
}
}
return finalProj;
},
_projectPointOnPlane: function (point, plane) {
var coef = BABYLON.Vector3.Dot(point, plane.normal) + plane.d;
var proj = point.subtract(plane.normal.scale(coef));
return proj;
},
_getProjectionOnNode: function (position, node, vertices) {
var A = this.getVectorFrom(vertices, node.vertexIds[0]);
var B = this.getVectorFrom(vertices, node.vertexIds[1]);
var C = this.getVectorFrom(vertices, node.vertexIds[2]);
var u = B.subtract(A);
var v = C.subtract(A);
var n = BABYLON.Vector3.Cross(u, v).normalize();
var plane = {
normal: n,
d: -BABYLON.Vector3.Dot(A, n)
};
var p = this._projectPointOnPlane(position, plane);
// Compute barycentric coordinates (u, v, w) for
// point p with respect to triangle (a, b, c)
var barycentric = function (p, a, b, c) {
var ret = {};
var v0 = c.subtract(a),
v1 = b.subtract(a),
v2 = p.subtract(a);
var d00 = BABYLON.Vector3.Dot(v0, v0);
var d01 = BABYLON.Vector3.Dot(v0, v1);
var d02 = BABYLON.Vector3.Dot(v0, v2);
var d11 = BABYLON.Vector3.Dot(v1, v1);
var d12 = BABYLON.Vector3.Dot(v1, v2);
var denom = d00 * d11 - d01 * d01;
ret.u = (d11 * d02 - d01 * d12) / denom;
ret.v = (d00 * d12 - d01 * d02) / denom;
ret.w = 1 - ret.u - ret.v;
return ret;
};
var bary = barycentric(p, A, B, C);
bary.u = Math.min(Math.max(bary.u, 0), 1);
bary.v = Math.min(Math.max(bary.v, 0), 1);
if (bary.u + bary.v >= 1) {
var sum = bary.u + bary.v;
bary.u /= sum;
bary.v /= sum;
}
var proj = A.add(B.subtract(A).scale(bary.v).add(C.subtract(A).scale(bary.u)));
return proj;
},
findPath: function (startPosition, targetPosition, zone, group) {
var allNodes = this.zoneNodes[zone].groups[group];
var vertices = this.zoneNodes[zone].vertices;
var startingNode = null;
for(var i = 0; i < allNodes.length; i++) {
if (this._isVectorInPolygon(startPosition, allNodes[i], vertices)) {
startingNode = allNodes[i];
break;
}
}
var endNode = null;
for(var i = 0; i < allNodes.length; i++) {
if (this._isVectorInPolygon(targetPosition, allNodes[i], vertices)) {
endNode = allNodes[i];
break;
}
}
// If we can't find any node, theres no path to target
if (!startingNode || !endNode) {
return null;
}
if (startingNode.id != endNode.id) { // if the starting node and target node are at the same polygon skip searching and funneling as there is no obstacle.
var paths = this.astar.search(allNodes, startingNode, endNode);
} else {
vectors = [];
vectors.push(new BABYLON.Vector3(targetPosition.x, targetPosition.y, targetPosition.z));
return vectors;
}
var getPortalFromTo = function (a, b) {
for (var i = 0; i < a.neighbours.length; i++) {
if (a.neighbours[i] === b.id) {
return a.portals[i];
}
}
};
// We got the corridor
// Now pull the rope
var channel = new Channel();
channel.push(startPosition);
for (var i = 0; i < paths.length; i++) {
var polygon = paths[i];
var nextPolygon = paths[i + 1];
if (nextPolygon) {
var portals = getPortalFromTo(polygon, nextPolygon);
channel.push(
this.getVectorFrom(vertices, portals[0]),
this.getVectorFrom(vertices, portals[1])
);
}
}
channel.push(targetPosition);
channel.stringPull();
var vectors = [];
channel.path.forEach(function (c) {
var vec = new BABYLON.Vector3(c.x, c.y, c.z);
// console.log(vec.clone().sub(startPosition).length());
// Ensure the intermediate steps aren't too close to the start position
// var dist = vec.clone().sub(startPosition).lengthSq();
// if (dist > 0.01 * 0.01) {
vectors.push(vec);
// }
});
// We don't need the first one, as we already know our start position
vectors.shift();
return vectors;
},
_isPointInPoly: function (poly, pt) {
for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((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);
return c;
},
_isVectorInPolygon: function (vector, polygon, vertices) {
// reference point will be the centroid of the polygon
// We need to rotate the vector as well as all the points which the polygon uses
var lowestPoint = 100000;
var highestPoint = -100000;
var polygonVertices = [];
_.each(polygon.vertexIds, function (vId) {
var point = this.getVectorFrom(vertices, vId);
lowestPoint = Math.min(point.y, lowestPoint);
highestPoint = Math.max(point.y, highestPoint);
polygonVertices.push(point);
}.bind(this));
if (vector.y < highestPoint + this.yTolerance && vector.y > lowestPoint - this.yTolerance &&
this._isPointInPoly(polygonVertices, vector)) {
return true;
}
return false;
},
_computeCentroids: function (geometry) {
var centroids = [];
var indices = geometry.getIndices();
var vertices = geometry.getVerticesData(BABYLON.VertexBuffer.PositionKind);
var c = new BABYLON.Vector3(0, 0, 0);
for (var f = 0; f < indices.length; f += 3) {
var p1 = this.getVectorFrom(vertices, indices[f]);
var p2 = this.getVectorFrom(vertices, indices[f + 1]);
var p3 = this.getVectorFrom(vertices, indices[f + 2]);
c.copyFromFloats(0, 0, 0);
c.addInPlace(p1);
c.addInPlace(p2);
c.addInPlace(p3);
c.scaleInPlace(1 / 3);
centroids.push(c.clone());
}
geometry.centroids = centroids;
},
_roundNumber: function (number, decimals) {
var newnumber = new Number(number + '').toFixed(parseInt(decimals));
return parseFloat(newnumber);
},
_mergeVertexIds: function (aList, bList) {
var sharedVertices = [];
aList.forEach(function (vId) {
if (_.includes(bList, vId)) {
sharedVertices.push(vId);
}
});
if (sharedVertices.length < 2) return [];
// console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
if (_.includes(sharedVertices, aList[0]) && _.includes(sharedVertices, aList[aList.length - 1])) {
// Vertices on both edges are bad, so shift them once to the left
aList.push(aList.shift());
}
if (_.includes(sharedVertices, bList[0]) && _.includes(sharedVertices, bList[bList.length - 1])) {
// Vertices on both edges are bad, so shift them once to the left
bList.push(bList.shift());
}
// Again!
sharedVertices = [];
aList.forEach(function (vId) {
if (_.includes(bList, vId)) {
sharedVertices.push(vId);
}
});
var clockwiseMostSharedVertex = sharedVertices[1];
var counterClockwiseMostSharedVertex = sharedVertices[0];
var cList = _.clone(aList);
while (cList[0] !== clockwiseMostSharedVertex) {
cList.push(cList.shift());
}
var c = 0;
var temp = _.clone(bList);
while (temp[0] !== counterClockwiseMostSharedVertex) {
temp.push(temp.shift());
if (c++ > 10) break;
}
// Shave
temp.shift();
temp.pop();
cList = cList.concat(temp);
// console.log("aList:", aList, ", bList:", bList, ", cList:", cList, ", sharedVertices:", sharedVertices);
return cList;
},
_setPolygonCentroid: function (polygon, navigationMesh) {
var sum = new BABYLON.Vector3(0, 0, 0);
var vertices = navigationMesh.vertices;
_.each(polygon.vertexIds, function (vId) {
sum.x += vertices[vId * 3];
sum.y += vertices[vId * 3 + 1];
sum.z += vertices[vId * 3 + 2];
});
sum.scaleInPlace(1 / polygon.vertexIds.length);
polygon.centroid.copyFrom(sum);
},
getVectorFrom: function (vertices, id, _vector) {
if (_vector) {
_vector.copyFromFloats(vertices[id * 3], vertices[id * 3 + 1], vertices[id * 3 + 2]);
return _vector;
}
return new BABYLON.Vector3(vertices[id * 3], vertices[id * 3 + 1], vertices[id * 3 + 2]);
},
_cleanPolygon: function (polygon, navigationMesh) {
var newVertexIds = [];
var vertices = navigationMesh.vertices;
for (var i = 0; i < polygon.vertexIds.length; i++) {
var vertex = this.getVectorFrom(vertices, polygon.vertexIds[i]);
var nextVertexId, previousVertexId;
var nextVertex, previousVertex;
// console.log("nextVertex: ", nextVertex);
if (i === 0) {
nextVertexId = polygon.vertexIds[1];
previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 1];
} else if (i === polygon.vertexIds.length - 1) {
nextVertexId = polygon.vertexIds[0];
previousVertexId = polygon.vertexIds[polygon.vertexIds.length - 2];
} else {
nextVertexId = polygon.vertexIds[i + 1];
previousVertexId = polygon.vertexIds[i - 1];
}
nextVertex = this.getVectorFrom(vertices, nextVertexId);
previousVertex = this.getVectorFrom(vertices, previousVertexId);
var a = nextVertex.clone().sub(vertex);
var b = previousVertex.clone().sub(vertex);
var angle = a.angleTo(b);
// console.log(angle);
if (angle > Math.PI - 0.01 && angle < Math.PI + 0.01) {
// Unneccesary vertex
// console.log("Unneccesary vertex: ", polygon.vertexIds[i]);
// console.log("Angle between "+previousVertexId+", "+polygon.vertexIds[i]+" "+nextVertexId+" was: ", angle);
// Remove the neighbours who had this vertex
var goodNeighbours = [];
polygon.neighbours.forEach(function (neighbour) {
if (!_.includes(neighbour.vertexIds, polygon.vertexIds[i])) {
goodNeighbours.push(neighbour);
}
});
polygon.neighbours = goodNeighbours;
// TODO cleanup the list of vertices and rebuild vertexIds for all polygons
} else {
newVertexIds.push(polygon.vertexIds[i]);
}
}
// console.log("New vertexIds: ", newVertexIds);
polygon.vertexIds = newVertexIds;
this._setPolygonCentroid(polygon, navigationMesh);
},
_isConvex: function (polygon, navigationMesh) {
var vertices = navigationMesh.vertices;
if (polygon.vertexIds.length < 3) return false;
var convex = true;
var total = 0;
var results = [];
for (var i = 0; i < polygon.vertexIds.length; i++) {
var vertex = this.getVectorFrom(vertices, polygon.vertexIds[i]);
var nextVertex, previousVertex;
// console.log("nextVertex: ", nextVertex);
if (i === 0) {
nextVertex = this.getVectorFrom(vertices, polygon.vertexIds[1]);
previousVertex = this.getVectorFrom(vertices, polygon.vertexIds[polygon.vertexIds.length - 1]);
} else if (i === polygon.vertexIds.length - 1) {
nextVertex = this.getVectorFrom(vertices, polygon.vertexIds[0]);
previousVertex = this.getVectorFrom(vertices, polygon.vertexIds[polygon.vertexIds.length - 2]);
} else {
nextVertex = this.getVectorFrom(vertices, polygon.vertexIds[i + 1]);
previousVertex = this.getVectorFrom(vertices, polygon.vertexIds[i - 1]);
}
var a = nextVertex.clone().sub(vertex);
var b = previousVertex.clone().sub(vertex);
var angle = a.angleTo(b);
total += angle;
// console.log(angle);
if (angle === Math.PI || angle === 0) return false;
var r = BABYLON.Vector3.Cross(a, b).y;
results.push(r);
// console.log("pushed: ", r);
}
// if ( total > (polygon.vertexIds.length-2)*Math.PI ) return false;
results.forEach(function (r) {
if (r === 0) convex = false;
});
if (results[0] > 0) {
results.forEach(function (r) {
if (r < 0) convex = false;
});
} else {
results.forEach(function (r) {
if (r > 0) convex = false;
});
}
// console.log("allowed: "+total+", max: "+(polygon.vertexIds.length-2)*Math.PI);
// if ( total > (polygon.vertexIds.length-2)*Math.PI ) convex = false;
// console.log("Convex: "+(convex ? "true": "false"));
return convex;
},
_buildPolygonGroups: function (navigationMesh) {
var polygons = navigationMesh.polygons;
var polygonGroups = [];
var groupCount = 0;
var spreadGroupId = function (polygon) {
_.each(polygon.neighbours, function (neighbour) {
if (_.isUndefined(neighbour.group)) {
neighbour.group = polygon.group;
spreadGroupId(neighbour);
}
});
};
_.each(polygons, function (polygon) {
if (_.isUndefined(polygon.group)) {
polygon.group = groupCount++;
// Spread it
spreadGroupId(polygon);
}
if (!polygonGroups[polygon.group]) polygonGroups[polygon.group] = [];
polygonGroups[polygon.group].push(polygon);
});
console.log("Groups built: ", polygonGroups.length);
return polygonGroups;
},
_array_intersect: function () {
var i, shortest, nShortest, n, len, ret = [],
obj = {},
nOthers;
nOthers = arguments.length - 1;
nShortest = arguments[0].length;
shortest = 0;
for (i = 0; i <= nOthers; i++) {
n = arguments[i].length;
if (n < nShortest) {
shortest = i;
nShortest = n;
}
}
for (i = 0; i <= nOthers; i++) {
n = (i === shortest) ? 0 : (i || shortest); //Read the shortest array first. Read the first array instead of the shortest
len = arguments[n].length;
for (var j = 0; j < len; j++) {
var elem = arguments[n][j];
if (obj[elem] === i - 1) {
if (i === nOthers) {
ret.push(elem);
obj[elem] = 0;
} else {
obj[elem] = i;
}
} else if (i === 0) {
obj[elem] = 0;
}
}
}
return ret;
},
_buildPolygonNeighbours: function (polygon, navigationMesh) {
polygon.neighbours = [];
// All other nodes that contain at least two of our vertices are our neighbours
for (var i = 0, len = navigationMesh.polygons.length; i < len; i++) {
if (polygon === navigationMesh.polygons[i]) continue;
// Don't check polygons that are too far, since the intersection tests take a long time
if (BABYLON.Vector3.DistanceSquared(polygon.centroid, navigationMesh.polygons[i].centroid) > 100 * 100) continue;
var matches = this._array_intersect(polygon.vertexIds, navigationMesh.polygons[i].vertexIds);
// var matches = _.intersection(polygon.vertexIds, navigationMesh.polygons[i].vertexIds);
if (matches.length >= 2) {
polygon.neighbours.push(navigationMesh.polygons[i]);
}
}
},
_buildPolygonsFromGeometry: function (geometry) {
var polygons = [];
var vertices = geometry.getVerticesData(BABYLON.VertexBuffer.PositionKind);
var indices = geometry.getIndices();
var polygonId = 1;
console.log("Vertices:", vertices.length / 3, "polygons:", indices.length / 3);
// Convert the faces into a custom format that supports more than 3 vertices
for (var i = 0; i < indices.length; i += 3) {
var a = this.getVectorFrom(vertices, indices[i]);
var b = this.getVectorFrom(vertices, indices[i + 1]);
var c = this.getVectorFrom(vertices, indices[i + 2]);
var normal = BABYLON.Vector3.Cross(b.subtract(a), b.subtract(c)).normalize();
polygons.push({
id: polygonId++,
vertexIds: [indices[i], indices[i + 1], indices[i + 2]],
centroid: geometry.centroids[i / 3],
normal: normal,
neighbours: []
});
}
var navigationMesh = {
polygons: polygons,
vertices: vertices
};
// Build a list of adjacent polygons
_.each(polygons, function (polygon) {
this._buildPolygonNeighbours(polygon, navigationMesh);
}.bind(this));
return navigationMesh;
},
_cleanNavigationMesh: function (navigationMesh) {
var polygons = navigationMesh.polygons;
var vertices = navigationMesh.vertices;
// Remove steep triangles
var up = new BABYLON.Vector3(0, 1, 0);
polygons = _.filter(polygons, function (polygon) {
var angle = Math.acos(BABYLON.Vector3.Dot(up, polygon.normal));
return angle < (Math.PI / 4);
});
// Remove unnecessary edges using the Hertel-Mehlhorn algorithm
// 1. Find a pair of adjacent nodes (i.e., two nodes that share an edge between them)
// whose normals are nearly identical (i.e., their surfaces face the same direction).
var newPolygons = [];
_.each(polygons, function (polygon) {
if (polygon.toBeDeleted) return;
var keepLooking = true;
while (keepLooking) {
keepLooking = false;
_.each(polygon.neighbours, function (otherPolygon) {
if (polygon === otherPolygon) return;
if (Math.acos(BABYLON.Vector3.Dot(polygon.normal, otherPolygon.normal)) < 0.01) {
// That's pretty equal alright!
// Merge otherPolygon with polygon
var testPolygon = {
vertexIds: this._mergeVertexIds(polygon.vertexIds, otherPolygon.vertexIds),
neighbours: polygon.neighbours,
normal: polygon.normal.clone(),
centroid: polygon.centroid.clone()
};
this._cleanPolygon(testPolygon, navigationMesh);
if (this._isConvex(testPolygon, navigationMesh)) {
otherPolygon.toBeDeleted = true;
// Inherit the neighbours from the to be merged polygon, except ourself
_.each(otherPolygon.neighbours, function (otherPolygonNeighbour) {
// Set this poly to be merged to be no longer our neighbour
otherPolygonNeighbour.neighbours = _.without(otherPolygonNeighbour.neighbours, otherPolygon);
if (otherPolygonNeighbour !== polygon) {
// Tell the old Polygon's neighbours about the new neighbour who has merged
otherPolygonNeighbour.neighbours.push(polygon);
} else {
// For ourself, we don't need to know about ourselves
// But we inherit the old neighbours
polygon.neighbours = polygon.neighbours.concat(otherPolygon.neighbours);
polygon.neighbours = _.uniq(polygon.neighbours);
// Without ourselves in it!
polygon.neighbours = _.without(polygon.neighbours, polygon);
}
});
polygon.vertexIds = this._mergeVertexIds(polygon.vertexIds, otherPolygon.vertexIds);
this._cleanPolygon(polygon, navigationMesh);
keepLooking = true;
}
}
}.bind(this));
}
if (!polygon.toBeDeleted) {
newPolygons.push(polygon);
}
});
var isUsed = function (vId) {
var contains = false;
_.each(newPolygons, function (p) {
if (!contains && _.includes(p.vertexIds, vId)) {
contains = true;
}
});
return contains;
};
// Clean vertices
for (var i = 0; i < vertices.length; i++) {
if (!isUsed(i)) {
// Decrement all vertices that are higher than i
_.each(newPolygons, function (p) {
for (var j = 0; j < p.vertexIds.length; j++) {
if (p.vertexIds[j] > i) {
p.vertexIds[j]--;
}
}
});
vertices.splice(i, 1);
i--;
}
}
navigationMesh.polygons = newPolygons;
navigationMesh.vertices = vertices;
},
_buildNavigationMesh: function (geometry) {
// Prepare geometry
this._computeCentroids(geometry);
this._mergeVertices(geometry);
// BABYLON.GeometryUtils.triangulateQuads(geometry);
// console.log("vertices:", geometry.vertices.length, "polygons:", geometry.faces.length);
var navigationMesh = this._buildPolygonsFromGeometry(geometry);
// cleanNavigationMesh(navigationMesh);
// console.log("Pre-clean:", navigationMesh.polygons.length, "polygons,", navigationMesh.vertices.length, "vertices.");
// console.log("")
// console.log("Vertices:", navigationMesh.vertices.length, "polygons,", navigationMesh.polygons.length, "vertices.");
return navigationMesh;
},
_mergeVertices: function (geometry) {
var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)
var unique = [],
changes = [];
var v, key;
var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001
var precision = Math.pow(10, precisionPoints);
var indices;
var ind = geometry.getIndices(),
vert = geometry.getVerticesData(BABYLON.VertexBuffer.PositionKind);
for (var i = 0; i < vert.length; i += 3) {
v = new BABYLON.Vector3(vert[i], vert[i + 1], vert[i + 2]);
key = Math.round(v.x * precision) + '_' + Math.round(v.y * precision) + '_' + Math.round(v.z * precision);
if (verticesMap[key] === undefined) {
verticesMap[key] = i / 3;
unique.push(v.clone());
changes[i / 3] = unique.length - 1;
} else {
//console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
changes[i / 3] = changes[verticesMap[key]];
}
}
// if faces are completely degenerate after merging vertices, we
// have to remove them from the geometry.
var faceIndicesToRemove = [];
for (i = 0; i < ind.length; i += 3) {
ind[i] = changes[ind[i]];
ind[i + 1] = changes[ind[i + 1]];
ind[i + 2] = changes[ind[i + 2]];
indices = [ind[i], ind[i + 1], ind[i + 2]];
var dupIndex = -1;
// if any duplicate vertices are found in a Face3
// we have to remove the face as nothing can be saved
for (var n = 0; n < 3; n++) {
if (indices[n] === indices[(n + 1) % 3]) {
dupIndex = n;
faceIndicesToRemove.push(i);
break;
}
}
}
for (i = faceIndicesToRemove.length - 1; i >= 0; i--) {
var idx = faceIndicesToRemove[i];
ind.splice(idx, 3);
}
// Use unique set of vertices
var diff = vert.length / 3 - unique.length;
vert = [];
for (i = 0; i < unique.length; i++) {
vert.push(unique[i].x, unique[i].y, unique[i].z);
}
geometry.setIndices(ind);
geometry.setVerticesData(BABYLON.VertexBuffer.PositionKind, vert);
return diff;
},
_getSharedVerticesInOrder: function (a, b) {
var aList = a.vertexIds;
var bList = b.vertexIds;
var sharedVertices = [];
_.each(aList, function (vId) {
if (_.includes(bList, vId)) {
sharedVertices.push(vId);
}
});
if (sharedVertices.length < 2) return [];
// console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
if (_.includes(sharedVertices, aList[0]) && _.includes(sharedVertices, aList[aList.length - 1])) {
// Vertices on both edges are bad, so shift them once to the left
aList.push(aList.shift());
}
if (_.includes(sharedVertices, bList[0]) && _.includes(sharedVertices, bList[bList.length - 1])) {
// Vertices on both edges are bad, so shift them once to the left
bList.push(bList.shift());
}
// Again!
sharedVertices = [];
_.each(aList, function (vId) {
if (_.includes(bList, vId)) {
sharedVertices.push(vId);
}
});
return sharedVertices;
},
_groupNavMesh: function (navigationMesh) {
var saveObj = {};
_.each(navigationMesh.vertices, function (v) {
v = this._roundNumber(v, 2);
}.bind(this));
saveObj.vertices = navigationMesh.vertices;
var groups = this._buildPolygonGroups(navigationMesh);
saveObj.groups = [];
var findPolygonIndex = function (group, p) {
for (var i = 0; i < group.length; i++) {
if (p === group[i]) return i;
}
};
_.each(groups, function (group) {
var newGroup = [];
_.each(group, function (p) {
var neighbours = [];
_.each(p.neighbours, function (n) {
neighbours.push(findPolygonIndex(group, n));
});
// Build a portal list to each neighbour
var portals = [];
_.each(p.neighbours, function (n) {
portals.push(this._getSharedVerticesInOrder(p, n));
}.bind(this));
p.centroid.x = this._roundNumber(p.centroid.x, 2);
p.centroid.y = this._roundNumber(p.centroid.y, 2);
p.centroid.z = this._roundNumber(p.centroid.z, 2);
newGroup.push({
id: findPolygonIndex(group, p),
neighbours: neighbours,
vertexIds: p.vertexIds,
centroid: p.centroid,
portals: portals
});
}.bind(this));
saveObj.groups.push(newGroup);
}.bind(this));
return saveObj;
},
});
module.exports = Navigation;