mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 07:33:10 +02:00
293 lines
9.1 KiB
JavaScript
293 lines
9.1 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
|
|
//
|
|
|
|
var UPDATE_INTERVAL = 0.1;
|
|
|
|
Script.include("graph.js");
|
|
Script.include("edgeSpring.js");
|
|
|
|
// A collection of balls and edges connecting them.
|
|
MagBalls = function() {
|
|
Graph.call(this);
|
|
|
|
this.MAX_ADJUST_ITERATIONS = 100;
|
|
this.lastUpdateAge = 0;
|
|
this.stable = false;
|
|
this.adjustIterations = 0;
|
|
this.selectedNodes = {};
|
|
this.edgeObjects = {};
|
|
|
|
this.refresh();
|
|
|
|
var _this = this;
|
|
Script.update.connect(function(deltaTime) {
|
|
_this.onUpdate(deltaTime);
|
|
});
|
|
|
|
Script.scriptEnding.connect(function() {
|
|
_this.onCleanup();
|
|
});
|
|
}
|
|
|
|
MagBalls.prototype = Object.create( Graph.prototype );
|
|
|
|
MagBalls.prototype.onUpdate = function(deltaTime) {
|
|
this.lastUpdateAge += deltaTime;
|
|
if (this.lastUpdateAge > UPDATE_INTERVAL) {
|
|
this.lastUpdateAge = 0;
|
|
if (!this.stable) {
|
|
this.adjustIterations += 1;
|
|
// logDebug("Update");
|
|
var adjusted = false;
|
|
var nodeAdjustResults = {};
|
|
var fixupEdges = {};
|
|
|
|
for(var edgeId in this.edges) {
|
|
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
|
|
}
|
|
for (var nodeId in nodeAdjustResults) {
|
|
var curPos = this.getNodePosition(nodeId);
|
|
var newPos = nodeAdjustResults[nodeId];
|
|
var distance = Vec3.distance(curPos, newPos);
|
|
for (var edgeId in this.nodes[nodeId]) {
|
|
fixupEdges[edgeId] = true;
|
|
}
|
|
// logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm");
|
|
Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED });
|
|
}
|
|
|
|
for (var edgeId in fixupEdges) {
|
|
this.fixupEdge(edgeId);
|
|
}
|
|
|
|
Script.setTimeout(function(){
|
|
for (var nodeId in nodeAdjustResults) {
|
|
Entities.editEntity(nodeId, { color: BALL_COLOR });
|
|
}
|
|
}, ((UPDATE_INTERVAL * 1000) / 2));
|
|
|
|
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
|
|
this.adjustIterations = 0;
|
|
this.stable = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MagBalls.prototype.createNodeEntity = function(customProperies) {
|
|
var nodeId = Entities.addEntity(mergeObjects(BALL_PROTOTYPE, customProperies));
|
|
return nodeId;
|
|
}
|
|
|
|
MagBalls.prototype.createEdgeEntity = function(nodeIdA, nodeIdB) {
|
|
var apos = this.getNodePosition(nodeIdA);
|
|
var bpos = this.getNodePosition(nodeIdB);
|
|
var edgeId = Entities.addEntity(mergeObjects(EDGE_PROTOTYPE, {
|
|
position: apos,
|
|
linePoints: [ ZERO_VECTOR, Vec3.subtract(bpos, apos) ],
|
|
userData: JSON.stringify({
|
|
magBalls: {
|
|
start: nodeIdA,
|
|
end: nodeIdB,
|
|
length: BALL_DISTANCE
|
|
}
|
|
})
|
|
}));
|
|
this.edgeObjects[edgeId] = new EdgeSpring(edgeId, this);
|
|
return edgeId;
|
|
}
|
|
|
|
MagBalls.prototype.findPotentialEdges = function(nodeId) {
|
|
var variances = {};
|
|
for (var otherNodeId in this.nodes) {
|
|
// can't self connect
|
|
if (otherNodeId == nodeId) {
|
|
continue;
|
|
}
|
|
|
|
// can't doubly connect
|
|
if (this.areConnected(otherNodeId, nodeId)) {
|
|
continue;
|
|
}
|
|
|
|
// Check distance to attempt
|
|
var distance = this.getNodeDistance(nodeId, otherNodeId);
|
|
var variance = this.getVariance(distance);
|
|
if (Math.abs(variance) > 0.25) {
|
|
continue;
|
|
}
|
|
|
|
variances[otherNodeId] = variance;
|
|
}
|
|
return variances;
|
|
}
|
|
|
|
MagBalls.prototype.grabBall = function(position, maxDist) {
|
|
var selected = this.findNearestNode(position, maxDist);
|
|
if (!selected) {
|
|
selected = this.createNode({ position: position });
|
|
}
|
|
if (selected) {
|
|
this.stable = true;
|
|
this.breakEdges(selected);
|
|
this.selectedNodes[selected] = true;
|
|
}
|
|
return selected;
|
|
}
|
|
|
|
MagBalls.prototype.releaseBall = function(releasedBall) {
|
|
delete this.selectedNodes[releasedBall];
|
|
logDebug("Released ball: " + releasedBall);
|
|
|
|
this.stable = false;
|
|
|
|
var releasePosition = this.getNodePosition(releasedBall);
|
|
|
|
// iterate through the other balls and ensure we don't intersect with
|
|
// any of them. If we do, just delete this ball and return.
|
|
// FIXME (play a pop sound)
|
|
for (var nodeId in this.nodes) {
|
|
if (nodeId == releasedBall) {
|
|
continue;
|
|
}
|
|
var distance = this.getNodeDistance(releasedBall, nodeId);
|
|
if (distance < BALL_SIZE) {
|
|
this.destroyNode(releasedBall);
|
|
return;
|
|
}
|
|
}
|
|
|
|
var targets = this.findPotentialEdges(releasedBall);
|
|
if (!targets || !Object.keys(targets).length) {
|
|
// this.destroyNode(releasedBall);
|
|
}
|
|
for (var otherBallId in targets) {
|
|
this.createEdge(otherBallId, releasedBall);
|
|
}
|
|
this.validate();
|
|
}
|
|
|
|
|
|
MagBalls.prototype.getVariance = function(distance) {
|
|
// FIXME different balls or edges might have different ideas of variance...
|
|
// let something else handle this
|
|
var offset = (BALL_DISTANCE - distance);
|
|
var variance = offset / BALL_DISTANCE
|
|
return variance;
|
|
}
|
|
|
|
// remove unconnected balls
|
|
MagBalls.prototype.clean = function() {
|
|
// do nothing unless there are at least 2 balls and one edge
|
|
if (Object.keys(this.nodes).length < 2 || !Object.keys(this.edges).length) {
|
|
return;
|
|
}
|
|
var disconnectedNodes = {};
|
|
for (var nodeId in this.nodes) {
|
|
if (!Object.keys(this.nodes[nodeId]).length) {
|
|
disconnectedNodes[nodeId] = true;
|
|
}
|
|
}
|
|
for (var nodeId in disconnectedNodes) {
|
|
this.destroyNode(nodeId);
|
|
}
|
|
}
|
|
|
|
// remove all balls
|
|
MagBalls.prototype.clear = function() {
|
|
if (DEBUG_MAGSTICKS) {
|
|
this.deleteAll();
|
|
var ids = Entities.findEntities(MyAvatar.position, 50);
|
|
var result = [];
|
|
ids.forEach(function(id) {
|
|
var properties = Entities.getEntityProperties(id);
|
|
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
|
|
Entities.deleteEntity(id);
|
|
}
|
|
}, this);
|
|
}
|
|
}
|
|
|
|
MagBalls.prototype.destroyEdge = function(edgeId) {
|
|
Graph.prototype.destroyEdge.call(this, edgeId);
|
|
delete this.edgeObjects[edgeId];
|
|
}
|
|
|
|
MagBalls.prototype.destroyNode = function(nodeId) {
|
|
Graph.prototype.destroyNode.call(this, nodeId);
|
|
}
|
|
|
|
// Scan the entity tree and load all the objects in range
|
|
MagBalls.prototype.refresh = function() {
|
|
var ids = Entities.findEntities(MyAvatar.position, 50);
|
|
for (var i in ids) {
|
|
var id = ids[i];
|
|
var properties = Entities.getEntityProperties(id);
|
|
if (properties.name == BALL_NAME) {
|
|
this.nodes[id] = {};
|
|
}
|
|
}
|
|
|
|
var deleteEdges = [];
|
|
for (var i in ids) {
|
|
var id = ids[i];
|
|
var properties = Entities.getEntityProperties(id);
|
|
if (properties.name == EDGE_NAME) {
|
|
var edgeId = id;
|
|
this.edges[edgeId] = {};
|
|
var magBallData = getMagBallsData(id);
|
|
if (!magBallData.start || !magBallData.end) {
|
|
logWarn("Edge information is missing for " + id);
|
|
continue;
|
|
}
|
|
if (!this.nodes[magBallData.start] || !this.nodes[magBallData.end]) {
|
|
logWarn("Edge " + id + " refers to unknown nodes: " + JSON.stringify(magBallData));
|
|
Entities.editEntity(id, { color: COLORS.RED });
|
|
deleteEdges.push(id);
|
|
continue;
|
|
}
|
|
this.nodes[magBallData.start][edgeId] = true;
|
|
this.nodes[magBallData.end][edgeId] = true;
|
|
this.edges[edgeId][magBallData.start] = true;
|
|
this.edges[edgeId][magBallData.end] = true;
|
|
this.edgeObjects[id] = new EdgeSpring(id, this);
|
|
}
|
|
}
|
|
|
|
if (deleteEdges.length) {
|
|
Script.setTimeout(function() {
|
|
for (var i in deleteEdges) {
|
|
var edgeId = deleteEdges[i];
|
|
logDebug("deleting invalid edge " + edgeId);
|
|
Entities.deleteEntity(edgeId);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
var edgeCount = Object.keys(this.edges).length;
|
|
var nodeCount = Object.keys(this.nodes).length;
|
|
logDebug("Found " + nodeCount + " nodes and " + edgeCount + " edges ");
|
|
this.validate();
|
|
}
|
|
|
|
|
|
MagBalls.prototype.findEdgeParams = function(startBall, endBall) {
|
|
var startBallPos = this.getNodePosition(startBall);
|
|
var endBallPos = this.getNodePosition(endBall);
|
|
var vector = Vec3.subtract(endBallPos, startBallPos);
|
|
return {
|
|
position: startBallPos,
|
|
linePoints: [ ZERO_VECTOR, vector ]
|
|
};
|
|
}
|
|
|
|
MagBalls.prototype.fixupEdge = function(edgeId) {
|
|
var ballsInEdge = Object.keys(this.edges[edgeId]);
|
|
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
|
|
}
|
|
|