Updating magballs to omnitool

This commit is contained in:
Brad Davis 2015-09-02 18:01:47 -07:00
parent ea415071b4
commit 5717b77837
10 changed files with 289 additions and 406 deletions

View file

@ -9,17 +9,6 @@
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
// FIXME make this editable through some script UI, so the user can customize the size of the structure built
SCALE = 0.5;
BALL_SIZE = 0.08 * SCALE;
STICK_LENGTH = 0.24 * SCALE;
DEBUG_MAGSTICKS = true;
CUSTOM_DATA_NAME = "magBalls";
BALL_NAME = "MagBall";
EDGE_NAME = "MagStick";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
COLORS = {
@ -70,71 +59,5 @@ COLORS = {
}
}
BALL_RADIUS = BALL_SIZE / 2.0;
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
BALL_DIMENSIONS = {
x: BALL_SIZE,
y: BALL_SIZE,
z: BALL_SIZE
};
BALL_COLOR = {
red: 128,
green: 128,
blue: 128
};
STICK_DIMENSIONS = {
x: STICK_LENGTH / 6,
y: STICK_LENGTH / 6,
z: STICK_LENGTH
};
BALL_DISTANCE = STICK_LENGTH + BALL_SIZE;
BALL_PROTOTYPE = {
type: "Sphere",
name: BALL_NAME,
dimensions: BALL_DIMENSIONS,
color: BALL_COLOR,
ignoreCollisions: true,
collisionsWillMove: false
};
// 2 millimeters
BALL_EPSILON = (.002) / BALL_DISTANCE;
LINE_DIMENSIONS = {
x: 5,
y: 5,
z: 5
}
LINE_PROTOTYPE = {
type: "Line",
name: EDGE_NAME,
color: COLORS.CYAN,
dimensions: LINE_DIMENSIONS,
lineWidth: 5,
visible: true,
ignoreCollisions: true,
collisionsWillMove: false,
}
EDGE_PROTOTYPE = LINE_PROTOTYPE;
// var EDGE_PROTOTYPE = {
// type: "Sphere",
// name: EDGE_NAME,
// color: { red: 0, green: 255, blue: 255 },
// //dimensions: STICK_DIMENSIONS,
// dimensions: { x: 0.02, y: 0.02, z: 0.02 },
// rotation: rotation,
// visible: true,
// ignoreCollisions: true,
// collisionsWillMove: false
// }

View file

@ -199,6 +199,10 @@ OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) {
return resultId;
}
OmniTool.prototype.getPosition = function() {
return this.model.tipPosition;
}
OmniTool.prototype.onEnterNearestOmniEntity = function() {
this.nearestOmniEntity.inside = true;
this.highlighter.highlight(this.nearestOmniEntity.id);

View file

@ -76,14 +76,6 @@ getEntityCustomData = function(customKey, id, defaultValue) {
return userData[customKey] ? userData[customKey] : defaultValue;
}
getMagBallsData = function(id) {
return getEntityCustomData(CUSTOM_DATA_NAME, id, {});
}
setMagBallsData = function(id, value) {
setEntityCustomData(CUSTOM_DATA_NAME, id, value);
}
mergeObjects = function(proto, custom) {
var result = {};
for (var attrname in proto) {

113
examples/toys/magBalls.js Normal file
View file

@ -0,0 +1,113 @@
//
// Created by Bradley Austin Davis on 2015/08/25
// 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
//
// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js
Script.include("../toys/magBalls/constants.js");
Script.include("../toys/magBalls/graph.js");
Script.include("../toys/magBalls/edgeSpring.js");
Script.include("../toys/magBalls/magBalls.js");
OmniToolModuleType = "MagBallsController"
OmniToolModules.MagBallsController = function(omniTool, entityId) {
this.omniTool = omniTool;
this.entityId = entityId;
this.highlighter = new Highlighter();
this.magBalls = new MagBalls();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
var MAG_BALLS_DATA_NAME = "magBalls";
getMagBallsData = function(id) {
return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {});
}
setMagBallsData = function(id, value) {
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
}
//var magBalls = new MagBalls();
// DEBUGGING ONLY - Clear any previous balls
// magBalls.clear();
OmniToolModules.MagBallsController.prototype.onClick = function() {
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
this.selected = this.highlighter.hightlighted;
logDebug("This selected: " + this.selected);
if (!this.selected) {
this.selected = this.magBalls.createBall(this.tipPosition);
}
this.magBalls.selectBall(this.selected);
this.highlighter.highlight(null);
logDebug("Selected " + this.selected);
}
OmniToolModules.MagBallsController.prototype.onRelease = function() {
logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition));
this.clearGhostEdges();
if (this.selected) {
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
}
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
this.tipPosition = this.omniTool.getPosition();
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
if (!target) {
this.magBalls.onUpdate(deltaTime);
}
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
BallController.prototype.onUnload = function() {
this.clearGhostEdges();
}

View file

@ -1,103 +0,0 @@
Script.include("handController.js");
Script.include("highlighter.js");
BallController = function(side, magBalls) {
HandController.call(this, side);
this.magBalls = magBalls;
this.highlighter = new Highlighter();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
BallController.prototype = Object.create( HandController.prototype );
BallController.prototype.onUpdate = function(deltaTime) {
HandController.prototype.onUpdate.call(this, deltaTime);
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
BallController.prototype.onClick = function() {
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(null);
}
BallController.prototype.onRelease = function() {
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
BallController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
BallController.prototype.onCleanup = function() {
HandController.prototype.onCleanup.call(this);
this.clearGhostEdges();
}
BallController.prototype.onAltClick = function() {
return;
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
if (!target) {
logDebug(target);
return;
}
// FIXME move to delete shape
var toDelete = {};
var deleteQueue = [ target ];
while (deleteQueue.length) {
var curNode = deleteQueue.shift();
if (toDelete[curNode]) {
continue;
}
toDelete[curNode] = true;
for (var nodeId in this.magBalls.getConnectedNodes(curNode)) {
deleteQueue.push(nodeId);
}
}
for (var nodeId in toDelete) {
this.magBalls.destroyNode(nodeId);
}
}
BallController.prototype.onAltRelease = function() {
}

View file

@ -0,0 +1,77 @@
// FIXME make this editable through some script UI, so the user can customize the size of the structure built
SCALE = 0.5;
BALL_SIZE = 0.08 * SCALE;
STICK_LENGTH = 0.24 * SCALE;
DEBUG_MAGSTICKS = true;
BALL_NAME = "MagBall";
EDGE_NAME = "MagStick";
BALL_RADIUS = BALL_SIZE / 2.0;
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
BALL_DIMENSIONS = {
x: BALL_SIZE,
y: BALL_SIZE,
z: BALL_SIZE
};
BALL_COLOR = {
red: 128,
green: 128,
blue: 128
};
STICK_DIMENSIONS = {
x: STICK_LENGTH / 6,
y: STICK_LENGTH / 6,
z: STICK_LENGTH
};
BALL_DISTANCE = STICK_LENGTH + BALL_SIZE;
BALL_PROTOTYPE = {
type: "Sphere",
name: BALL_NAME,
dimensions: BALL_DIMENSIONS,
color: BALL_COLOR,
ignoreCollisions: true,
collisionsWillMove: false
};
// 2 millimeters
BALL_EPSILON = (.002) / BALL_DISTANCE;
LINE_DIMENSIONS = {
x: 5,
y: 5,
z: 5
}
LINE_PROTOTYPE = {
type: "Line",
name: EDGE_NAME,
color: COLORS.CYAN,
dimensions: LINE_DIMENSIONS,
lineWidth: 5,
visible: true,
ignoreCollisions: true,
collisionsWillMove: false,
}
EDGE_PROTOTYPE = LINE_PROTOTYPE;
// var EDGE_PROTOTYPE = {
// type: "Sphere",
// name: EDGE_NAME,
// color: { red: 0, green: 255, blue: 255 },
// //dimensions: STICK_DIMENSIONS,
// dimensions: { x: 0.02, y: 0.02, z: 0.02 },
// rotation: rotation,
// visible: true,
// ignoreCollisions: true,
// collisionsWillMove: false
// }

View file

@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) {
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]);

View file

@ -1,128 +0,0 @@
//
// 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
//
LEFT_CONTROLLER = 0;
RIGHT_CONTROLLER = 1;
// FIXME add a customizable wand model and a mechanism to switch between wands
HandController = function(side) {
this.side = side;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.action = findAction(side ? "ACTION2" : "ACTION1");
this.altAction = findAction(side ? "ACTION1" : "ACTION2");
this.active = false;
this.tipScale = 1.4;
this.pointer = Overlays.addOverlay("sphere", {
position: ZERO_VECTOR,
size: 0.01,
color: COLORS.YELLOW,
alpha: 1.0,
solid: true,
visible: false,
});
// Connect to desired events
var _this = this;
Controller.actionEvent.connect(function(action, state) {
_this.onActionEvent(action, state);
});
Script.update.connect(function(deltaTime) {
_this.onUpdate(deltaTime);
});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
}
HandController.prototype.onActionEvent = function(action, state) {
var spatialControlCount = Controller.getNumberOfSpatialControls();
// If only 2 spacial controls, then we only have one controller active, so use either button
// otherwise, only use the specified action
if (action == this.action) {
if (state) {
this.onClick();
} else {
this.onRelease();
}
}
if (action == this.altAction) {
if (state) {
this.onAltClick();
} else {
this.onAltRelease();
}
}
}
HandController.prototype.setActive = function(active) {
if (active == this.active) {
return;
}
logDebug("Hand controller changing active state: " + active);
this.active = active;
Overlays.editOverlay(this.pointer, {
visible: this.active
});
Entities.editEntity(this.wand, {
visible: this.active
});
}
HandController.prototype.updateControllerState = function() {
// FIXME this returns data if either the left or right controller is not on the base
this.palmPos = Controller.getSpatialControlPosition(this.palm);
var tipPos = Controller.getSpatialControlPosition(this.tip);
this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.palmPos) > 0.001);
//logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1));
//if (this.active) {
// logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos))
//}
}
HandController.prototype.onCleanup = function() {
Overlays.deleteOverlay(this.pointer);
}
HandController.prototype.onUpdate = function(deltaTime) {
this.updateControllerState();
if (this.active) {
Overlays.editOverlay(this.pointer, {
position: this.tipPosition
});
Entities.editEntity(this.wand, {
position: this.tipPosition
});
}
}
HandController.prototype.onClick = function() {
logDebug("Base hand controller does nothing on click");
}
HandController.prototype.onRelease = function() {
logDebug("Base hand controller does nothing on release");
}
HandController.prototype.onAltClick = function() {
logDebug("Base hand controller does nothing on alt click");
}
HandController.prototype.onAltRelease = function() {
logDebug("Base hand controller does nothing on alt click");
}

View file

@ -8,30 +8,34 @@
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.REFRESH_WAIT_TICKS = 10;
this.MAX_VARIANCE = 0.25;
this.lastUpdateAge = 0;
this.stable = false;
this.stable = true;
this.adjustIterations = 0;
this.selectedNodes = {};
this.edgeObjects = {};
this.unstableEdges = {};
this.refresh();
var _this = this;
Script.update.connect(function(deltaTime) {
_this.onUpdate(deltaTime);
});
//Script.update.connect(function(deltaTime) {
// _this.onUpdate(deltaTime);
//});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
Entities.addingEntity.connect(function(entityId) {
_this.onEntityAdded(entityId);
});
}
MagBalls.prototype = Object.create( Graph.prototype );
@ -40,14 +44,23 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
this.lastUpdateAge += deltaTime;
if (this.lastUpdateAge > UPDATE_INTERVAL) {
this.lastUpdateAge = 0;
if (!this.stable) {
if (this.refreshNeeded) {
if (++this.refreshNeeded > this.REFRESH_WAIT_TICKS) {
logDebug("Refreshing");
this.refresh();
this.refreshNeeded = 0;
}
}
if (!this.stable && !Object.keys(this.selectedNodes).length) {
this.adjustIterations += 1;
// logDebug("Update");
var adjusted = false;
var nodeAdjustResults = {};
var fixupEdges = {};
for(var edgeId in this.edges) {
if (!this.unstableEdges[edgeId]) {
continue;
}
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
}
for (var nodeId in nodeAdjustResults) {
@ -72,8 +85,12 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
}, ((UPDATE_INTERVAL * 1000) / 2));
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
if (adjusted) {
logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning");
}
this.adjustIterations = 0;
this.stable = true;
this.unstableEdges = {};
}
}
}
@ -118,7 +135,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
// Check distance to attempt
var distance = this.getNodeDistance(nodeId, otherNodeId);
var variance = this.getVariance(distance);
if (Math.abs(variance) > 0.25) {
if (Math.abs(variance) > this.MAX_VARIANCE) {
continue;
}
@ -127,26 +144,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
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.breakEdges = function(nodeId) {
//var unstableNodes = this.findShape(Object.keys.target);
//for (var node in unstableNodes) {
// this.unstableNodes[node] = true;
//}
Graph.prototype.breakEdges.call(this, nodeId);
}
MagBalls.prototype.createBall = function(position) {
var created = this.createNode({ position: position });
this.selectBall(created);
return created;
}
MagBalls.prototype.selectBall = function(selected) {
if (!selected) {
return;
}
// stop updating shapes while manipulating
this.stable = true;
this.selectedNodes[selected] = true;
this.breakEdges(selected);
}
MagBalls.prototype.releaseBall = function(releasedBall) {
delete this.selectedNodes[releasedBall];
logDebug("Released ball: " + releasedBall);
var releasePosition = this.getNodePosition(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.
@ -169,10 +198,34 @@ MagBalls.prototype.releaseBall = function(releasedBall) {
for (var otherBallId in targets) {
this.createEdge(otherBallId, releasedBall);
}
var unstableNodes = this.findShape(releasedBall);
for (var nodeId in unstableNodes) {
for (var edgeId in this.nodes[nodeId]) {
this.unstableEdges[edgeId] = true;
}
}
this.validate();
}
MagBalls.prototype.findShape = function(nodeId) {
var result = {};
var queue = [ nodeId ];
while (queue.length) {
var curNode = queue.shift();
if (result[curNode]) {
continue;
}
result[curNode] = true;
for (var otherNodeId in this.getConnectedNodes(curNode)) {
queue.push(otherNodeId);
}
}
return result;
}
MagBalls.prototype.getVariance = function(distance) {
// FIXME different balls or edges might have different ideas of variance...
// let something else handle this
@ -263,8 +316,11 @@ MagBalls.prototype.refresh = function() {
Script.setTimeout(function() {
for (var i in deleteEdges) {
var edgeId = deleteEdges[i];
logDebug("deleting invalid edge " + edgeId);
Entities.deleteEntity(edgeId);
//logDebug("deleting invalid edge " + edgeId);
//Entities.deleteEntity(edgeId);
Entities.editEntity(edgeId, {
color: COLORS.RED
})
}
}, 1000);
}
@ -291,3 +347,14 @@ MagBalls.prototype.fixupEdge = function(edgeId) {
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
}
MagBalls.prototype.onEntityAdded = function(entityId) {
// We already have it
if (this.nodes[entityId] || this.edges[entityId]) {
return;
}
var properties = Entities.getEntityProperties(entityId);
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
this.refreshNeeded = 1;
}
}

View file

@ -1,66 +0,0 @@
Script.include("handController.js");
MenuController = function(side, magBalls) {
HandController.call(this, side);
}
MenuController.prototype = Object.create( HandController.prototype );
MenuController.prototype.onUpdate = function(deltaTime) {
HandController.prototype.onUpdate.call(this, deltaTime);
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
MenuController.prototype.onClick = function() {
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(null);
}
MenuController.prototype.onRelease = function() {
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
MenuController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
MenuController.prototype.onCleanup = function() {
HandController.prototype.onCleanup.call(this);
this.clearGhostEdges();
}