Old stuff/old_sites/threejs/8_perso_move_click/js/lib/patrol.js
(Deskargatu)
/*
PatrolJS - Navigation mesh toolkit for ThreeJS
http://github.com/nickjanssen/patroljs
Licensed under MIT
*/
(function (exports) {
var _, THREE, ProgressBar;
if (typeof module !== 'undefined' && module.exports) {
_ = require('underscore');
THREE = require('three');
ProgressBar = require('progress');
}
else {
_ = window._;
THREE = window.THREE;
// stub in the browser
ProgressBar = function () {
return {
tick: function () {}
};
};
}
var computeCentroids = function (geometry) {
var f, fl, face;
for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
face = geometry.faces[ f ];
face.centroid = new THREE.Vector3( 0, 0, 0 );
face.centroid.add( geometry.vertices[ face.a ] );
face.centroid.add( geometry.vertices[ face.b ] );
face.centroid.add( geometry.vertices[ face.c ] );
face.centroid.divideScalar( 3 );
}
};
function roundNumber(number, decimals) {
var newnumber = new Number(number + '').toFixed(parseInt(decimals));
return parseFloat(newnumber);
}
var nodeIdCount = 1;
var polygonId = 1;
var bar;
var barConfig = {
// complete: "X",
// incomplete: ".",
width: 30
};
var mergeVertexIds = function (aList, bList) {
var sharedVertices = [];
_.each(aList, function (vId) {
if (_.contains(bList, vId)) {
sharedVertices.push(vId);
}
});
if (sharedVertices.length < 2) return [];
// console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
if (_.contains(sharedVertices, aList[0]) && _.contains(sharedVertices, aList[aList.length - 1])) {
// Vertices on both edges are bad, so shift them once to the left
aList.push(aList.shift());
}
if (_.contains(sharedVertices, bList[0]) && _.contains(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 (_.contains(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) debugger;
}
// Shave
temp.shift();
temp.pop();
cList = cList.concat(temp);
// console.log("aList:", aList, ", bList:", bList, ", cList:", cList, ", sharedVertices:", sharedVertices);
return cList;
};
var setPolygonCentroid = function (polygon, navigationMesh) {
var sum = new THREE.Vector3();
var vertices = navigationMesh.vertices;
_.each(polygon.vertexIds, function (vId) {
sum.add(vertices[vId]);
});
sum.divideScalar(polygon.vertexIds.length);
polygon.centroid.copy(sum);
};
var cleanPolygon = function (polygon, navigationMesh) {
var newVertexIds = [];
var vertices = navigationMesh.vertices;
for (var i = 0; i < polygon.vertexIds.length; i++) {
var vertex = 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 = vertices[nextVertexId];
previousVertex = 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 = [];
_.each(polygon.neighbours, function (neighbour) {
if (!_.contains(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;
setPolygonCentroid(polygon, navigationMesh);
};
var 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 = vertices[polygon.vertexIds[i]];
var nextVertex, previousVertex;
// console.log("nextVertex: ", nextVertex);
if (i === 0) {
nextVertex = vertices[polygon.vertexIds[1]];
previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 1]];
} else if (i === polygon.vertexIds.length - 1) {
nextVertex = vertices[polygon.vertexIds[0]];
previousVertex = vertices[polygon.vertexIds[polygon.vertexIds.length - 2]];
} else {
nextVertex = vertices[polygon.vertexIds[i + 1]];
previousVertex = 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 = a.cross(b).y;
results.push(r);
// console.log("pushed: ", r);
}
// if ( total > (polygon.vertexIds.length-2)*Math.PI ) return false;
_.each(results, function (r) {
if (r === 0) convex = false;
});
if (results[0] > 0) {
_.each(results, function (r) {
if (r < 0) convex = false;
});
} else {
_.each(results, 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;
};
var buildPolygonGroups = function (navigationMesh) {
var polygons = navigationMesh.polygons;
var vertices = navigationMesh.vertices;
bar = new ProgressBar('Building polygon groups[:bar] :percent', _.extend(barConfig, {
total: polygons.length
}));
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, i) {
bar.tick();
if (_.isUndefined(polygon.group)) {
polygon.group = groupCount++;
// Spread it
spreadGroupId(polygon);
}
if (!polygonGroups[polygon.group]) polygonGroups[polygon.group] = [];
polygonGroups[polygon.group].push(polygon);
});
// Separate groups for testing
// var count = 0;
// _.each(polygonGroups, function(polygonGroup) {
// var done = {};
// count += 0.01;
// _.each(polygonGroup, function(polygon) {
// _.each(polygon.vertexIds, function(vId) {
// if ( !done[vId] ) vertices[vId].y += count;
// done[vId] = true;
// });
// });
// });
console.log("Groups built: ", polygonGroups.length);
return polygonGroups;
};
function array_intersect() {
var i, all, 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;
}
var 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 (polygon.centroid.distanceToSquared(navigationMesh.polygons[i].centroid) > 100 * 100) continue;
var matches = 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]);
}
}
};
var buildPolygonsFromGeometry = function (geometry) {
console.log("Vertices:", geometry.vertices.length, "polygons:", geometry.faces.length);
var polygons = [];
var vertices = geometry.vertices;
var faceVertexUvs = geometry.faceVertexUvs;
bar = new ProgressBar('Building polygons [:bar] :percent', _.extend(barConfig, {
total: geometry.faces.length
}));
// Convert the faces into a custom format that supports more than 3 vertices
_.each(geometry.faces, function (face) {
bar.tick();
polygons.push({
id: polygonId++,
vertexIds: [face.a, face.b, face.c],
centroid: face.centroid,
normal: face.normal,
neighbours: []
});
});
var navigationMesh = {
polygons: polygons,
vertices: vertices,
faceVertexUvs: faceVertexUvs
};
bar = new ProgressBar('Calculating neighbours [:bar] :percent', _.extend(barConfig, {
total: polygons.length
}));
// Build a list of adjacent polygons
_.each(polygons, function (polygon) {
bar.tick();
buildPolygonNeighbours(polygon, navigationMesh);
});
return navigationMesh;
};
var cleanNavigationMesh = function (navigationMesh) {
var polygons = navigationMesh.polygons;
var vertices = navigationMesh.vertices;
// Remove steep triangles
var up = new THREE.Vector3(0, 1, 0);
polygons = _.filter(polygons, function (polygon) {
var angle = Math.acos(up.dot(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(polygon.normal.dot(otherPolygon.normal)) < 0.01) {
// That's pretty equal alright!
// Merge otherPolygon with polygon
var testVertexIdList = [];
var testPolygon = {
vertexIds: mergeVertexIds(polygon.vertexIds, otherPolygon.vertexIds),
neighbours: polygon.neighbours,
normal: polygon.normal.clone(),
centroid: polygon.centroid.clone()
};
cleanPolygon(testPolygon, navigationMesh);
if (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 = mergeVertexIds(polygon.vertexIds, otherPolygon.vertexIds);
// console.log(polygon.vertexIds);
// console.log("Merge!");
cleanPolygon(polygon, navigationMesh);
keepLooking = true;
}
}
});
}
if (!polygon.toBeDeleted) {
newPolygons.push(polygon);
}
});
var isUsed = function (vId) {
var contains = false;
_.each(newPolygons, function (p) {
if (!contains && _.contains(p.vertexIds, vId)) {
contains = true;
}
});
return contains;
};
// Clean vertices
var keepChecking = false;
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;
};
var buildNavigationMesh = function (geometry) {
// Prepare geometry
computeCentroids(geometry);
geometry.mergeVertices();
// THREE.GeometryUtils.triangulateQuads(geometry);
// console.log("vertices:", geometry.vertices.length, "polygons:", geometry.faces.length);
var navigationMesh = 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;
};
var getSharedVerticesInOrder = function (a, b) {
var aList = a.vertexIds;
var bList = b.vertexIds;
var sharedVertices = [];
_.each(aList, function (vId) {
if (_.contains(bList, vId)) {
sharedVertices.push(vId);
}
});
if (sharedVertices.length < 2) return [];
// console.log("TRYING aList:", aList, ", bList:", bList, ", sharedVertices:", sharedVertices);
if (_.contains(sharedVertices, aList[0]) && _.contains(sharedVertices, aList[aList.length - 1])) {
// Vertices on both edges are bad, so shift them once to the left
aList.push(aList.shift());
}
if (_.contains(sharedVertices, bList[0]) && _.contains(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 (_.contains(bList, vId)) {
sharedVertices.push(vId);
}
});
return sharedVertices;
};
var groupNavMesh = function (navigationMesh) {
var saveObj = {};
_.each(navigationMesh.vertices, function (v) {
v.x = roundNumber(v.x, 2);
v.y = roundNumber(v.y, 2);
v.z = roundNumber(v.z, 2);
});
saveObj.vertices = navigationMesh.vertices;
var groups = buildPolygonGroups(navigationMesh);
saveObj.groups = [];
var findPolygonIndex = function (group, p) {
for (var i = 0; i < group.length; i++) {
if (p === group[i]) return i;
}
};
bar = new ProgressBar('Grouping [:bar] :percent', _.extend(barConfig, {
total: groups.length
}));
_.each(groups, function (group) {
bar.tick();
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(getSharedVerticesInOrder(p, n));
});
p.centroid.x = roundNumber(p.centroid.x, 2);
p.centroid.y = roundNumber(p.centroid.y, 2);
p.centroid.z = roundNumber(p.centroid.z, 2);
newGroup.push({
id: findPolygonIndex(group, p),
neighbours: neighbours,
vertexIds: p.vertexIds,
centroid: p.centroid,
portals: portals
});
});
saveObj.groups.push(newGroup);
});
return saveObj;
};
// javascript-astar
// http://github.com/bgrins/javascript-astar
// Freely distributable under the MIT License.
// Implements the astar search algorithm in javascript using a binary heap.
function BinaryHeap(scoreFunction) {
this.content = [];
this.scoreFunction = scoreFunction;
}
BinaryHeap.prototype = {
push: function (element) {
// Add the new element to the end of the array.
this.content.push(element);
// Allow it to sink down.
this.sinkDown(this.content.length - 1);
},
pop: function () {
// Store the first element so we can return it later.
var result = this.content[0];
// Get the element at the end of the array.
var end = this.content.pop();
// If there are any elements left, put the end element at the
// start, and let it bubble up.
if (this.content.length > 0) {
this.content[0] = end;
this.bubbleUp(0);
}
return result;
},
remove: function (node) {
var i = this.content.indexOf(node);
// When it is found, the process seen in 'pop' is repeated
// to fill up the hole.
var end = this.content.pop();
if (i !== this.content.length - 1) {
this.content[i] = end;
if (this.scoreFunction(end) < this.scoreFunction(node)) {
this.sinkDown(i);
} else {
this.bubbleUp(i);
}
}
},
size: function () {
return this.content.length;
},
rescoreElement: function (node) {
this.sinkDown(this.content.indexOf(node));
},
sinkDown: function (n) {
// Fetch the element that has to be sunk.
var element = this.content[n];
// When at 0, an element can not sink any further.
while (n > 0) {
// Compute the parent element's index, and fetch it.
var parentN = ((n + 1) >> 1) - 1,
parent = this.content[parentN];
// Swap the elements if the parent is greater.
if (this.scoreFunction(element) < this.scoreFunction(parent)) {
this.content[parentN] = element;
this.content[n] = parent;
// Update 'n' to continue at the new position.
n = parentN;
}
// Found a parent that is less, no need to sink any further.
else {
break;
}
}
},
bubbleUp: function (n) {
// Look up the target element and its score.
var length = this.content.length,
element = this.content[n],
elemScore = this.scoreFunction(element);
while (true) {
// Compute the indices of the child elements.
var child2N = (n + 1) << 1,
child1N = child2N - 1;
// This is used to store the new position of the element,
// if any.
var swap = null;
// If the first child exists (is inside the array)...
if (child1N < length) {
// Look it up and compute its score.
var child1 = this.content[child1N],
child1Score = this.scoreFunction(child1);
// If the score is less than our element's, we need to swap.
if (child1Score < elemScore)
swap = child1N;
}
// Do the same checks for the other child.
if (child2N < length) {
var child2 = this.content[child2N],
child2Score = this.scoreFunction(child2);
if (child2Score < (swap === null ? elemScore : child1Score)) {
swap = child2N;
}
}
// If the element needs to be moved, swap it, and continue.
if (swap !== null) {
this.content[n] = this.content[swap];
this.content[swap] = element;
n = swap;
}
// Otherwise, we are done.
else {
break;
}
}
}
};
var distanceToSquared = function (a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dz = a.z - b.z;
return dx * dx + dy * dy + dz * dz;
};
var astar = {
init: function (graph) {
for (var x = 0; x < graph.length; x++) {
//for(var x in graph) {
var node = graph[x];
node.f = 0;
node.g = 0;
node.h = 0;
node.cost = 1.0;
node.visited = false;
node.closed = false;
node.parent = null;
}
},
cleanUp: function (graph) {
for (var x = 0; x < graph.length; x++) {
var node = graph[x];
delete node.f;
delete node.g;
delete node.h;
delete node.cost;
delete node.visited;
delete node.closed;
delete node.parent;
}
},
heap: function () {
return new BinaryHeap(function (node) {
return node.f;
});
},
search: function (graph, start, end) {
astar.init(graph);
//heuristic = heuristic || astar.manhattan;
var openHeap = astar.heap();
openHeap.push(start);
while (openHeap.size() > 0) {
// Grab the lowest f(x) to process next. Heap keeps this sorted for us.
var currentNode = openHeap.pop();
// End case -- result has been found, return the traced path.
if (currentNode === end) {
var curr = currentNode;
var ret = [];
while (curr.parent) {
ret.push(curr);
curr = curr.parent;
}
this.cleanUp(ret);
return ret.reverse();
}
// Normal case -- move currentNode from open to closed, process each of its neighbours.
currentNode.closed = true;
// Find all neighbours for the current node. Optionally find diagonal neighbours as well (false by default).
var neighbours = astar.neighbours(graph, currentNode);
for (var i = 0, il = neighbours.length; i < il; i++) {
var neighbour = neighbours[i];
if (neighbour.closed) {
// Not a valid node to process, skip to next neighbour.
continue;
}
// The g score is the shortest distance from start to current node.
// We need to check if the path we have arrived at this neighbour is the shortest one we have seen yet.
var gScore = currentNode.g + neighbour.cost;
var beenVisited = neighbour.visited;
if (!beenVisited || gScore < neighbour.g) {
// Found an optimal (so far) path to this node. Take score for node to see how good it is.
neighbour.visited = true;
neighbour.parent = currentNode;
if (!neighbour.centroid || !end.centroid) debugger;
neighbour.h = neighbour.h || astar.heuristic(neighbour.centroid, end.centroid);
neighbour.g = gScore;
neighbour.f = neighbour.g + neighbour.h;
if (!beenVisited) {
// Pushing to heap will put it in proper place based on the 'f' value.
openHeap.push(neighbour);
} else {
// Already seen the node, but since it has been rescored we need to reorder it in the heap
openHeap.rescoreElement(neighbour);
}
}
}
}
// No result was found - empty array signifies failure to find path.
return [];
},
heuristic: function (pos1, pos2) {
return distanceToSquared(pos1, pos2);
},
neighbours: function (graph, node) {
var ret = [];
for (var e = 0; e < node.neighbours.length; e++) {
ret.push(graph[node.neighbours[e]]);
}
return ret;
}
};
var distanceToSquared = function (a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dz = a.z - b.z;
return dx * dx + dy * dy + dz * dz;
};
//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/math/is-point-in-poly [rev. #0]
function isPointInPoly(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;
}
function isVectorInPolygon(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 center = polygon.centroid;
var lowestPoint = 100000;
var highestPoint = -100000;
var polygonVertices = [];
_.each(polygon.vertexIds, function (vId) {
lowestPoint = Math.min(vertices[vId].y, lowestPoint);
highestPoint = Math.max(vertices[vId].y, highestPoint);
polygonVertices.push(vertices[vId]);
});
if (vector.y < highestPoint + 0.5 && vector.y > lowestPoint - 0.5 &&
isPointInPoly(polygonVertices, vector)) {
return true;
}
return false;
}
function triarea2(a, b, c) {
var ax = b.x - a.x;
var az = b.z - a.z;
var bx = c.x - a.x;
var bz = c.z - a.z;
return bx * az - ax * bz;
}
function vequal(a, b) {
return distanceToSquared(a, b) < 0.00001;
}
function Channel() {
this.portals = [];
}
Channel.prototype.push = function (p1, p2) {
if (p2 === undefined) p2 = p1;
this.portals.push({
left: p1,
right: p2
});
};
Channel.prototype.stringPull = function () {
var portals = this.portals;
var pts = [];
// Init scan state
var portalApex, portalLeft, portalRight;
var apexIndex = 0,
leftIndex = 0,
rightIndex = 0;
portalApex = portals[0].left;
portalLeft = portals[0].left;
portalRight = portals[0].right;
// Add start point.
pts.push(portalApex);
for (var i = 1; i < portals.length; i++) {
var left = portals[i].left;
var right = portals[i].right;
// Update right vertex.
if (triarea2(portalApex, portalRight, right) <= 0.0) {
if (vequal(portalApex, portalRight) || triarea2(portalApex, portalLeft, right) > 0.0) {
// Tighten the funnel.
portalRight = right;
rightIndex = i;
} else {
// Right over left, insert left to path and restart scan from portal left point.
pts.push(portalLeft);
// Make current left the new apex.
portalApex = portalLeft;
apexIndex = leftIndex;
// Reset portal
portalLeft = portalApex;
portalRight = portalApex;
leftIndex = apexIndex;
rightIndex = apexIndex;
// Restart scan
i = apexIndex;
continue;
}
}
// Update left vertex.
if (triarea2(portalApex, portalLeft, left) >= 0.0) {
if (vequal(portalApex, portalLeft) || triarea2(portalApex, portalRight, left) < 0.0) {
// Tighten the funnel.
portalLeft = left;
leftIndex = i;
} else {
// Left over right, insert right to path and restart scan from portal right point.
pts.push(portalRight);
// Make current right the new apex.
portalApex = portalRight;
apexIndex = rightIndex;
// Reset portal
portalLeft = portalApex;
portalRight = portalApex;
leftIndex = apexIndex;
rightIndex = apexIndex;
// Restart scan
i = apexIndex;
continue;
}
}
}
if ((pts.length === 0) || (!vequal(pts[pts.length - 1], portals[portals.length - 1].left))) {
// Append last point to path.
pts.push(portals[portals.length - 1].left);
}
this.path = pts;
return pts;
};
var zoneNodes = {};
var path = null;
_.extend(exports, {
buildNodes: function (geometry) {
var navigationMesh = buildNavigationMesh(geometry);
var zoneNodes = groupNavMesh(navigationMesh);
return zoneNodes;
},
setZoneData: function (zone, data) {
zoneNodes[zone] = data;
},
getGroup: function (zone, position) {
if (!zoneNodes[zone]) return null;
var closestNodeGroup = null;
var distance = Math.pow(50, 2);
_.each(zoneNodes[zone].groups, function (group, index) {
_.each(group, function (node) {
var measuredDistance = distanceToSquared(node.centroid, position);
if (measuredDistance < distance) {
closestNodeGroup = index;
distance = measuredDistance;
}
});
});
return closestNodeGroup;
},
getRandomNode: function (zone, group, nearPosition, nearRange) {
if (!zoneNodes[zone]) return new THREE.Vector3();
nearPosition = nearPosition || null;
nearRange = nearRange || 0;
var candidates = [];
var polygons = zoneNodes[zone].groups[group];
_.each(polygons, function (p) {
if (nearPosition && nearRange) {
if (distanceToSquared(nearPosition, p.centroid) < nearRange * nearRange) {
candidates.push(p.centroid);
}
} else {
candidates.push(p.centroid);
}
});
return _.sample(candidates) || new THREE.Vector3();
},
findPath: function (startPosition, targetPosition, zone, group) {
var allNodes = zoneNodes[zone].groups[group];
var vertices = zoneNodes[zone].vertices;
var closestNode = null;
var distance = Math.pow(50, 2);
_.each(allNodes, function (node) {
var measuredDistance = distanceToSquared(node.centroid, startPosition);
if (measuredDistance < distance) {
closestNode = node;
distance = measuredDistance;
}
}, this);
var farthestNode = null;
distance = Math.pow(50, 2);
_.each(allNodes, function (node) {
var measuredDistance = distanceToSquared(node.centroid, targetPosition);
if (measuredDistance < distance &&
isVectorInPolygon(targetPosition, node, vertices)) {
farthestNode = node;
distance = measuredDistance;
}
}, this);
// If we can't find any node, just go straight to the target
if (!closestNode || !farthestNode) {
return null;
}
var paths = astar.search(allNodes, closestNode, farthestNode);
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(
vertices[portals[0]],
vertices[portals[1]]
);
}
}
channel.push(targetPosition);
channel.stringPull();
var threeVectors = [];
_.each(channel.path, function (c) {
var vec = new THREE.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) {
threeVectors.push(vec);
// }
});
// We don't need the first one, as we already know our start position
threeVectors.shift();
return threeVectors;
}
});
})(typeof exports === 'undefined' ? this['patrol'] = {} : exports);