mirror of
https://github.com/lubosz/overte.git
synced 2025-04-05 22:42:33 +02:00
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;
|
|
} |