overte-HifiExperiments/script-archive/magBalls/graph.js
2016-04-26 11:18:22 -07:00

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;
}