overte-HifiExperiments/examples/toys/magBalls/magBalls.js
2015-08-30 23:50:02 -07:00

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