Needs a lot of cleanup. Data has been de-duplicated, and where identical copies existed, one of them has been replaced with a symlink. Some files have been excluded, such as binaries, installers and debug dumps. Some of that may still be present.
285 lines
No EOL
7.4 KiB
JavaScript
285 lines
No EOL
7.4 KiB
JavaScript
//
|
|
// Created by Bradley Austin Davis on 2015/08/29
|
|
// Copyright 2015 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
// A collection of nodes and edges connecting them.
|
|
Graph = function() {
|
|
/* Structure of nodes tree
|
|
this.nodes: {
|
|
nodeId1: {
|
|
edgeId1: true
|
|
}
|
|
nodeId2: {
|
|
edgeId1: true
|
|
},
|
|
// Nodes can many edges
|
|
nodeId3: {
|
|
edgeId2: true
|
|
edgeId3: true
|
|
edgeId4: true
|
|
edgeId5: true
|
|
},
|
|
// Nodes can have 0 edges
|
|
nodeId5: {
|
|
},
|
|
...
|
|
}
|
|
*/
|
|
this.nodes = {};
|
|
/* Structure of edge tree
|
|
this.edges: {
|
|
edgeId1: {
|
|
// Every edge should have exactly two
|
|
nodeId1: true
|
|
nodeId2: true
|
|
},
|
|
edgeId2: {
|
|
nodeId3: true
|
|
nodeId4: true
|
|
},
|
|
...
|
|
}
|
|
*/
|
|
this.edges = {};
|
|
}
|
|
|
|
Graph.prototype.createNodeEntity = function(properties) {
|
|
throw "Unimplemented";
|
|
}
|
|
|
|
Graph.prototype.createNode = function(properties) {
|
|
var nodeId = this.createNodeEntity(properties);
|
|
this.nodes[nodeId] = {};
|
|
this.validate();
|
|
return nodeId;
|
|
}
|
|
|
|
Graph.prototype.createEdgeEntity = function(nodeA, nodeB) {
|
|
throw "Unimplemented";
|
|
}
|
|
|
|
Graph.prototype.createEdge = function(nodeA, nodeB) {
|
|
if (nodeA == nodeB) {
|
|
throw "Error: self connection not supported";
|
|
}
|
|
var newEdgeId = this.createEdgeEntity(nodeA, nodeB);
|
|
|
|
// Create the bidirectional linkage
|
|
this.edges[newEdgeId] = {};
|
|
this.edges[newEdgeId][nodeA] = true;
|
|
this.edges[newEdgeId][nodeB] = true;
|
|
this.nodes[nodeA][newEdgeId] = true;
|
|
this.nodes[nodeB][newEdgeId] = true;
|
|
|
|
this.validate();
|
|
}
|
|
|
|
Graph.prototype.getEdges = function(nodeId) {
|
|
var edges = this.nodes[nodeId];
|
|
var result = {};
|
|
for (var edgeId in edges) {
|
|
for (var otherNodeId in this.edges[edgeId]) {
|
|
if (otherNodeId != nodeId) {
|
|
result[edgeId] = otherNodeId;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Graph.prototype.getConnectedNodes = function(nodeId) {
|
|
var edges = this.getEdges(nodeId);
|
|
var result = {};
|
|
for (var edgeId in edges) {
|
|
var otherNodeId = edges[edgeId];
|
|
result[otherNodeId] = edgeId;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Graph.prototype.getNodesForEdge = function(edgeId) {
|
|
return Object.keys(this.edges[edgeId]);
|
|
}
|
|
|
|
Graph.prototype.getEdgeLength = function(edgeId) {
|
|
var nodesInEdge = Object.keys(this.edges[edgeId]);
|
|
return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]);
|
|
}
|
|
|
|
Graph.prototype.getNodeDistance = function(a, b) {
|
|
var apos = this.getNodePosition(a);
|
|
var bpos = this.getNodePosition(b);
|
|
return Vec3.distance(apos, bpos);
|
|
}
|
|
|
|
Graph.prototype.getNodePosition = function(node) {
|
|
var properties = Entities.getEntityProperties(node);
|
|
return properties.position;
|
|
}
|
|
|
|
Graph.prototype.breakEdges = function(nodeId) {
|
|
for (var edgeId in this.nodes[nodeId]) {
|
|
this.destroyEdge(edgeId);
|
|
}
|
|
}
|
|
|
|
Graph.prototype.findNearestNode = function(position, maxDist) {
|
|
var resultId = null;
|
|
var resultDist = 0;
|
|
for (var nodeId in this.nodes) {
|
|
var nodePosition = this.getNodePosition(nodeId);
|
|
var curDist = Vec3.distance(nodePosition, position);
|
|
if (!maxDist || curDist <= maxDist) {
|
|
if (!resultId || curDist < resultDist) {
|
|
resultId = nodeId;
|
|
resultDist = curDist;
|
|
}
|
|
}
|
|
}
|
|
return resultId;
|
|
}
|
|
|
|
Graph.prototype.findMatchingNodes = function(selector) {
|
|
var result = {};
|
|
for (var nodeId in this.nodes) {
|
|
if (selector(nodeId)) {
|
|
result[nodeId] = true;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Graph.prototype.destroyEdge = function(edgeId) {
|
|
logDebug("Deleting edge " + edgeId);
|
|
for (var nodeId in this.edges[edgeId]) {
|
|
delete this.nodes[nodeId][edgeId];
|
|
}
|
|
delete this.edges[edgeId];
|
|
Entities.deleteEntity(edgeId);
|
|
this.validate();
|
|
}
|
|
|
|
Graph.prototype.destroyNode = function(nodeId) {
|
|
logDebug("Deleting node " + nodeId);
|
|
this.breakEdges(nodeId);
|
|
delete this.nodes[nodeId];
|
|
Entities.deleteEntity(nodeId);
|
|
this.validate();
|
|
}
|
|
|
|
Graph.prototype.deleteAll = function() {
|
|
var nodeIds = Object.keys(this.nodes);
|
|
for (var i in nodeIds) {
|
|
var nodeId = nodeIds[i];
|
|
this.destroyNode(nodeId);
|
|
}
|
|
}
|
|
|
|
Graph.prototype.areConnected = function(nodeIdA, nodeIdB) {
|
|
for (var edgeId in this.nodes[nodeIdA]) {
|
|
if (this.nodes[nodeIdB][edgeId]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
forEachValue = function(val, operation) {
|
|
if( typeof val === 'string' ) {
|
|
operation(val);
|
|
} else if (typeof val === 'object') {
|
|
if (val.constructor === Array) {
|
|
for (var i in val) {
|
|
operation(val[i]);
|
|
}
|
|
} else {
|
|
for (var v in val) {
|
|
operation(v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Graph.prototype.findShortestPath = function(start, end, options) {
|
|
var queue = [ start ];
|
|
var prev = {};
|
|
if (options && options.exclude) {
|
|
forEachValue(options.exclude, function(value) {
|
|
prev[value] = value;
|
|
});
|
|
logDebug("exclude " + prev);
|
|
}
|
|
var found = false;
|
|
while (!found && Object.keys(queue).length) {
|
|
var current = queue.shift();
|
|
for (var ballId in this.getConnectedNodes(current)) {
|
|
if (prev[ballId]) {
|
|
// already visited node
|
|
continue;
|
|
}
|
|
// record optimal path
|
|
prev[ballId] = current;
|
|
if (ballId == end) {
|
|
found = true;
|
|
break;
|
|
}
|
|
queue.push(ballId);
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
logDebug("Exhausted search");
|
|
return;
|
|
}
|
|
|
|
var result = [ end ];
|
|
while (result[0] != start) {
|
|
result.unshift(prev[result[0]]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Graph.prototype.validate = function() {
|
|
var error = false;
|
|
for (nodeId in this.nodes) {
|
|
for (edgeId in this.nodes[nodeId]) {
|
|
var edge = this.edges[edgeId];
|
|
if (!edge) {
|
|
logError("Error: node " + nodeId + " refers to unknown edge " + edgeId);
|
|
error = true;
|
|
continue;
|
|
}
|
|
if (!edge[nodeId]) {
|
|
logError("Error: node " + nodeId + " refers to edge " + edgeId + " but not vice versa");
|
|
error = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (edgeId in this.edges) {
|
|
for (nodeId in this.edges[edgeId]) {
|
|
var node = this.nodes[nodeId];
|
|
if (!node) {
|
|
logError("Error: edge " + edgeId + " refers to unknown node " + nodeId);
|
|
error = true;
|
|
continue;
|
|
}
|
|
if (!node[edgeId]) {
|
|
logError("Error: edge " + edgeId + " refers to node " + nodeId + " but not vice versa");
|
|
error = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
logDebug(JSON.stringify({ edges: this.edges, balls: this.nodes }, null, 2));
|
|
}
|
|
return error;
|
|
} |