diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js
index c602c36382..44801ef737 100644
--- a/examples/defaultScripts.js
+++ b/examples/defaultScripts.js
@@ -17,3 +17,4 @@ Script.load("users.js");
Script.load("grab.js");
Script.load("directory.js");
Script.load("dialTone.js");
+Script.load("libraries/omniTool.js");
diff --git a/examples/html/magBalls/addMode.html b/examples/html/magBalls/addMode.html
new file mode 100644
index 0000000000..f1db142daa
--- /dev/null
+++ b/examples/html/magBalls/addMode.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ Add
+
+
\ No newline at end of file
diff --git a/examples/html/magBalls/deleteMode.html b/examples/html/magBalls/deleteMode.html
new file mode 100644
index 0000000000..94f23fdef8
--- /dev/null
+++ b/examples/html/magBalls/deleteMode.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ Delete
+
+
\ No newline at end of file
diff --git a/examples/html/magBalls/magBalls.css b/examples/html/magBalls/magBalls.css
new file mode 100644
index 0000000000..339f094ea7
--- /dev/null
+++ b/examples/html/magBalls/magBalls.css
@@ -0,0 +1,14 @@
+.container {
+ display:table;
+ position:absolute;
+ width: 100%;
+ height: 100%;
+}
+
+span {
+ font-size: 25vw;
+ display: table-cell;
+ vertical-align: middle;
+ line-height: normal;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/examples/html/magBalls/moveMode.html b/examples/html/magBalls/moveMode.html
new file mode 100644
index 0000000000..eae53be635
--- /dev/null
+++ b/examples/html/magBalls/moveMode.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+ Move
+
+
\ No newline at end of file
diff --git a/examples/libraries/avatarRelativeOverlays.js b/examples/libraries/avatarRelativeOverlays.js
new file mode 100644
index 0000000000..b12971037c
--- /dev/null
+++ b/examples/libraries/avatarRelativeOverlays.js
@@ -0,0 +1,55 @@
+
+AvatarRelativeOverlays = function() {
+ // id -> position & rotation
+ this.overlays = {};
+ this.lastAvatarTransform = {
+ position: ZERO_VECTOR,
+ rotation: IDENTITY_QUATERNION,
+ };
+}
+
+// FIXME judder in movement is annoying.... add an option to
+// automatically hide all overlays when the position or orientation change and then
+// restore the ones that were previously visible once the movement stops.
+AvatarRelativeOverlays.prototype.onUpdate = function(deltaTime) {
+ // cache avatar position and orientation and only update on change
+ if (Vec3.equal(this.lastAvatarTransform.position, MyAvatar.position) &&
+ Quat.equal(this.lastAvatarTransform.rotation, MyAvatar.orientation)) {
+ return;
+ }
+
+ this.lastAvatarTransform.position = MyAvatar.position;
+ this.lastAvatarTransform.rotation = MyAvatar.orientation;
+ for (var overlayId in this.overlays) {
+ this.updateOverlayTransform(overlayId);
+ }
+}
+
+AvatarRelativeOverlays.prototype.updateOverlayTransform = function(overlayId) {
+ Overlays.editOverlay(overlayId, {
+ position: getEyeRelativePosition(this.overlays[overlayId].position),
+ rotation: getAvatarRelativeRotation(this.overlays[overlayId].rotation),
+ })
+}
+
+AvatarRelativeOverlays.prototype.addOverlay = function(type, overlayDefinition) {
+ var overlayId = Overlays.addOverlay(type, overlayDefinition);
+ if (!overlayId) {
+ logDebug("Failed to create overlay of type " + type);
+ return;
+ }
+ this.overlays[overlayId] = {
+ position: overlayDefinition.position || ZERO_VECTOR,
+ rotation: overlayDefinition.rotation || IDENTITY_QUATERNION,
+ };
+ this.updateOverlayTransform(overlayId);
+ return overlayId;
+}
+
+AvatarRelativeOverlays.prototype.deleteAll = function() {
+ for (var overlayId in this.overlays) {
+ Overlays.deleteOverlay(overlayId);
+ }
+ this.overlays = {};
+}
+
diff --git a/examples/libraries/constants.js b/examples/libraries/constants.js
index 3ed7c02633..117e72267c 100644
--- a/examples/libraries/constants.js
+++ b/examples/libraries/constants.js
@@ -11,6 +11,8 @@ STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
+IDENTITY_QUATERNION = { w: 1, x: 0, y: 0, z: 0 };
+
COLORS = {
WHITE: {
red: 255,
diff --git a/examples/libraries/highlighter.js b/examples/libraries/highlighter.js
index b3550b6c8a..33aa445b88 100644
--- a/examples/libraries/highlighter.js
+++ b/examples/libraries/highlighter.js
@@ -29,7 +29,7 @@ var SELECTION_OVERLAY = {
Highlighter = function() {
this.highlightCube = Overlays.addOverlay("cube", this.SELECTION_OVERLAY);
- this.hightlighted = null;
+ this.highlighted = null;
var _this = this;
Script.scriptEnding.connect(function() {
_this.onCleanup();
@@ -40,9 +40,9 @@ Highlighter.prototype.onCleanup = function() {
Overlays.deleteOverlay(this.highlightCube);
}
-Highlighter.prototype.highlight = function(entityId) {
- if (entityId != this.hightlighted) {
- this.hightlighted = entityId;
+Highlighter.prototype.highlight = function(entityIdOrPosition) {
+ if (entityIdOrPosition != this.highlighted) {
+ this.highlighted = entityIdOrPosition;
this.updateHighlight();
}
}
@@ -53,6 +53,13 @@ Highlighter.prototype.setSize = function(newSize) {
});
}
+Highlighter.prototype.setColor = function(color) {
+ Overlays.editOverlay(this.highlightCube, {
+ color: color
+ });
+}
+
+
Highlighter.prototype.setRotation = function(newRotation) {
Overlays.editOverlay(this.highlightCube, {
rotation: newRotation
@@ -60,11 +67,15 @@ Highlighter.prototype.setRotation = function(newRotation) {
}
Highlighter.prototype.updateHighlight = function() {
- if (this.hightlighted) {
- var properties = Entities.getEntityProperties(this.hightlighted);
+ if (this.highlighted) {
+ var position = this.highlighted;
+ if (typeof this.highlighted === "string") {
+ var properties = Entities.getEntityProperties(this.highlighted);
+ position = properties.position;
+ }
// logDebug("Making highlight " + this.highlightCube + " visible @ " + vec3toStr(properties.position));
Overlays.editOverlay(this.highlightCube, {
- position: properties.position,
+ position: position,
visible: true
});
} else {
diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js
index 724f30c548..26c299cdfb 100644
--- a/examples/libraries/omniTool.js
+++ b/examples/libraries/omniTool.js
@@ -11,13 +11,14 @@ Script.include("utils.js");
Script.include("highlighter.js");
Script.include("omniTool/models/modelBase.js");
Script.include("omniTool/models/wand.js");
+Script.include("omniTool/models/invisibleWand.js");
OmniToolModules = {};
OmniToolModuleType = null;
OmniTool = function(side) {
this.OMNI_KEY = "OmniTool";
- this.MAX_FRAMERATE = 30;
+ this.MAX_FRAMERATE = 60;
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.SIDE = side;
this.PALM = 2 * side;
@@ -28,6 +29,7 @@ OmniTool = function(side) {
this.ignoreEntities = {};
this.nearestOmniEntity = {
id: null,
+
inside: false,
position: null,
distance: Infinity,
@@ -38,13 +40,11 @@ OmniTool = function(side) {
this.activeOmniEntityId = null;
this.lastUpdateInterval = 0;
- this.tipLength = 0.4;
this.active = false;
this.module = null;
this.moduleEntityId = null;
this.lastScanPosition = ZERO_VECTOR;
- this.model = new Wand();
- this.model.setLength(this.tipLength);
+ this.showWand(false);
// Connect to desired events
var _this = this;
@@ -65,6 +65,23 @@ OmniTool = function(side) {
});
}
+OmniTool.prototype.showWand = function(show) {
+ if (this.model && this.model.onCleanup) {
+ this.model.onCleanup();
+ }
+ logDebug("Showing wand: " + show);
+ if (show) {
+ this.model = new Wand();
+ this.model.setLength(0.4);
+ this.model.setVisible(true);
+ } else {
+ this.model = new InvisibleWand();
+ this.model.setLength(0.1);
+ this.model.setVisible(true);
+ }
+}
+
+
OmniTool.prototype.onCleanup = function(action) {
this.unloadModule();
}
@@ -110,10 +127,9 @@ OmniTool.prototype.setActive = function(active) {
if (active === this.active) {
return;
}
- logDebug("omnitool changing active state: " + active);
+ logDebug("OmniTool changing active state: " + active);
this.active = active;
this.model.setVisible(this.active);
-
if (this.module && this.module.onActiveChanged) {
this.module.onActiveChanged(this.side);
}
@@ -125,14 +141,25 @@ OmniTool.prototype.onUpdate = function(deltaTime) {
this.position = Controller.getSpatialControlPosition(this.PALM);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.position) > 0.001);
+ if (!this.active) {
+ return;
+ }
+
+
+ if (this.model) {
+ // Update the wand
+ var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
+ this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
+ this.model.setTransform({
+ rotation: this.rotation,
+ position: this.position,
+ });
+
+ if (this.model.onUpdate) {
+ this.model.onUpdate(deltaTime);
+ }
+ }
- var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
- this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
-
- this.model.setTransform({
- rotation: this.rotation,
- position: this.position,
- });
this.scan();
@@ -144,6 +171,19 @@ OmniTool.prototype.onUpdate = function(deltaTime) {
OmniTool.prototype.onClick = function() {
// First check to see if the user is switching to a new omni module
if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) {
+
+ // If this is already the active entity, turn it off
+ // FIXME add a flag to allow omni modules to cause this entity to be
+ // ignored in order to support items that will be picked up.
+ if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) {
+ this.showWand(false);
+ this.unloadModule();
+ this.highlighter.setColor("White");
+ return;
+ }
+
+ this.showWand(true);
+ this.highlighter.setColor("Red");
this.activateNewOmniModule();
return;
}
@@ -157,12 +197,10 @@ OmniTool.prototype.onClick = function() {
}
OmniTool.prototype.onRelease = function() {
- // FIXME how to I switch to a new module?
if (this.module && this.module.onRelease) {
this.module.onRelease();
return;
}
- logDebug("Base omnitool does nothing on release");
}
// FIXME resturn a structure of all nearby entities to distances
@@ -210,6 +248,11 @@ OmniTool.prototype.getPosition = function() {
OmniTool.prototype.onEnterNearestOmniEntity = function() {
this.nearestOmniEntity.inside = true;
this.highlighter.highlight(this.nearestOmniEntity.id);
+ if (this.moduleEntityId && this.moduleEntityId == this.nearestOmniEntity.id) {
+ this.highlighter.setColor("Red");
+ } else {
+ this.highlighter.setColor("White");
+ }
logDebug("On enter omniEntity " + this.nearestOmniEntity.id);
}
diff --git a/examples/libraries/omniTool/models/invisibleWand.js b/examples/libraries/omniTool/models/invisibleWand.js
new file mode 100644
index 0000000000..63e0945af3
--- /dev/null
+++ b/examples/libraries/omniTool/models/invisibleWand.js
@@ -0,0 +1,6 @@
+
+InvisibleWand = function() {
+}
+
+InvisibleWand.prototype = Object.create( ModelBase.prototype );
+
diff --git a/examples/libraries/omniTool/models/modelBase.js b/examples/libraries/omniTool/models/modelBase.js
index 7697856d3f..87f93b1204 100644
--- a/examples/libraries/omniTool/models/modelBase.js
+++ b/examples/libraries/omniTool/models/modelBase.js
@@ -17,3 +17,9 @@ ModelBase.prototype.setTransform = function(transform) {
this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 });
this.tipPosition = Vec3.sum(this.position, this.tipVector);
}
+
+ModelBase.prototype.setTipColors = function(color1, color2) {
+}
+
+ModelBase.prototype.onCleanup = function() {
+}
diff --git a/examples/libraries/omniTool/models/wand.js b/examples/libraries/omniTool/models/wand.js
index 8f0fe92b53..af28afb81c 100644
--- a/examples/libraries/omniTool/models/wand.js
+++ b/examples/libraries/omniTool/models/wand.js
@@ -1,22 +1,18 @@
Wand = function() {
// Max updates fps
- this.MAX_FRAMERATE = 30
- this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.DEFAULT_TIP_COLORS = [ {
red: 128,
green: 128,
blue: 128,
}, {
- red: 64,
- green: 64,
- blue: 64,
+ red: 0,
+ green: 0,
+ blue: 0,
}];
this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45);
- // FIXME does this need to be a member of this?
- this.lastUpdateInterval = 0;
-
+
this.pointers = [
Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
@@ -45,17 +41,7 @@ Wand = function() {
var _this = this;
Script.scriptEnding.connect(function() {
- Overlays.deleteOverlay(_this.pointers[0]);
- Overlays.deleteOverlay(_this.pointers[1]);
- Overlays.deleteOverlay(_this.wand);
- });
-
- Script.update.connect(function(deltaTime) {
- _this.lastUpdateInterval += deltaTime;
- if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
- _this.onUpdate(_this.lastUpdateInterval);
- _this.lastUpdateInterval = 0;
- }
+ _this.onCleanup();
});
}
@@ -76,7 +62,6 @@ Wand.prototype.setVisible = function(visible) {
Wand.prototype.setTransform = function(transform) {
ModelBase.prototype.setTransform.call(this, transform);
-
var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector));
Overlays.editOverlay(this.pointers[0], {
position: this.tipPosition,
@@ -106,7 +91,10 @@ Wand.prototype.setTipColors = function(color1, color2) {
}
Wand.prototype.onUpdate = function(deltaTime) {
+ logDebug("Z4");
+
if (this.visible) {
+ logDebug("5");
var time = new Date().getTime() / 250;
var scale1 = Math.abs(Math.sin(time));
var scale2 = Math.abs(Math.cos(time));
@@ -118,3 +106,9 @@ Wand.prototype.onUpdate = function(deltaTime) {
});
}
}
+
+Wand.prototype.onCleanup = function() {
+ Overlays.deleteOverlay(this.pointers[0]);
+ Overlays.deleteOverlay(this.pointers[1]);
+ Overlays.deleteOverlay(this.wand);
+}
\ No newline at end of file
diff --git a/examples/libraries/omniTool/modules/test.js b/examples/libraries/omniTool/modules/test.js
index 1ca806affa..9f7191b2d0 100644
--- a/examples/libraries/omniTool/modules/test.js
+++ b/examples/libraries/omniTool/modules/test.js
@@ -1,9 +1,36 @@
-OmniToolModules.Test = function() {
+Script.include("avatarRelativeOverlays.js");
+
+OmniToolModules.Test = function(omniTool, activeEntityId) {
+ this.omniTool = omniTool;
+ this.activeEntityId = activeEntityId;
+ this.avatarOverlays = new AvatarRelativeOverlays();
}
+OmniToolModules.Test.prototype.onUnload = function() {
+ if (this.testOverlay) {
+ Overlays.deleteOverlay(this.testOverlay);
+ this.testOverlay = 0;
+ }
+}
+
+var CUBE_POSITION = {
+ x: 0.1,
+ y: -0.1,
+ z: -0.4
+};
+
OmniToolModules.Test.prototype.onClick = function() {
- logDebug("Test module onClick");
+ if (this.testOverlay) {
+ Overlays.deleteOverlay(this.testOverlay);
+ this.testOverlay = 0;
+ }
}
+
+OmniToolModules.Test.prototype.onUpdate = function(deltaTime) {
+ this.avatarOverlays.onUpdate(deltaTime);
+}
+
+
OmniToolModuleType = "Test"
\ No newline at end of file
diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js
index 1c2816e7a0..c6143a51a8 100644
--- a/examples/libraries/utils.js
+++ b/examples/libraries/utils.js
@@ -11,6 +11,15 @@ vec3toStr = function (v, digits) {
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
}
+
+colorMix = function(colorA, colorB, mix) {
+ var result = {};
+ for (var key in colorA) {
+ result[key] = (colorA[key] * (1 - mix)) + (colorB[key] * mix);
+ }
+ return result;
+}
+
scaleLine = function (start, end, scale) {
var v = Vec3.subtract(end, start);
var length = Vec3.length(v);
@@ -127,3 +136,13 @@ findSpherePointHit = function(sphereCenter, sphereRadius, point) {
findSphereSphereHit = function(firstCenter, firstRadius, secondCenter, secondRadius) {
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
}
+
+// Given a vec3 v, return a vec3 that is the same vector relative to the avatars
+// DEFAULT eye position, rotated into the avatars reference frame.
+getEyeRelativePosition = function(v) {
+ return Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(MyAvatar.orientation, v));
+}
+
+getAvatarRelativeRotation = function(q) {
+ return Quat.multiply(MyAvatar.orientation, q);
+}
diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js
index 8e441901a2..7c11a94e06 100644
--- a/examples/toys/magBalls.js
+++ b/examples/toys/magBalls.js
@@ -11,21 +11,10 @@ Script.include("../toys/magBalls/constants.js");
Script.include("../toys/magBalls/graph.js");
Script.include("../toys/magBalls/edgeSpring.js");
Script.include("../toys/magBalls/magBalls.js");
+Script.include("avatarRelativeOverlays.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, {});
}
@@ -34,54 +23,177 @@ setMagBallsData = function(id, value) {
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
}
-//var magBalls = new MagBalls();
-// DEBUGGING ONLY - Clear any previous balls
-// magBalls.clear();
+var UI_BALL_RADIUS = 0.01;
+var MODE_INFO = { };
-OmniToolModules.MagBallsController.prototype.onClick = function() {
- logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
+MODE_INFO[BALL_EDIT_MODE_ADD] = {
+ uiPosition: {
+ x: 0.15,
+ y: -0.08,
+ z: -0.35,
+ },
+ colors: [ COLORS.GREEN, COLORS.BLUE ],
+ // FIXME use an http path or find a way to get the relative path to the file
+ url: "file:///" + Script.resolvePath('../html/magBalls/addMode.html').replace("c:", "C:"),
+};
+
+MODE_INFO[BALL_EDIT_MODE_DELETE] = {
+ uiPosition: {
+ x: 0.20,
+ y: -0.08,
+ z: -0.32,
+ },
+ colors: [ COLORS.RED, COLORS.BLUE ],
+ // FIXME use an http path or find a way to get the relative path to the file
+ url: "file:///" + Script.resolvePath('../html/magBalls/deleteMode.html').replace("c:", "C:"),
+};
+
+
+var UI_POSITION_MODE_LABEL = Vec3.multiply(0.5,
+ Vec3.sum(MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition,
+ MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition));
+
+UI_POSITION_MODE_LABEL.y = -0.02;
+
+var UI_BALL_PROTOTYPE = {
+ size: UI_BALL_RADIUS * 2.0,
+ alpha: 1.0,
+ solid: true,
+ visible: true,
+}
+
+OmniToolModules.MagBallsController = function(omniTool, entityId) {
+ this.omniTool = omniTool;
+ this.entityId = entityId;
+
+ // In hold mode, holding a ball requires that you keep the action
+ // button pressed, while if this is false, clicking on a ball selects
+ // it and clicking again will drop it.
+ this.holdMode = true;
+
+ this.highlighter = new Highlighter();
+ this.magBalls = new MagBalls();
+ this.highlighter.setSize(BALL_SIZE);
+ this.ghostEdges = {};
+ this.selectionRadiusMultipler = 1.5;
+ this.uiOverlays = new AvatarRelativeOverlays();
- 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);
+
+ // create the overlay relative to the avatar
+ this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, {
+ color: MODE_INFO[BALL_EDIT_MODE_ADD].colors[0],
+ position: MODE_INFO[BALL_EDIT_MODE_ADD].uiPosition,
+ }));
+ this.uiOverlays.addOverlay("sphere", mergeObjects(UI_BALL_PROTOTYPE, {
+ color: MODE_INFO[BALL_EDIT_MODE_DELETE].colors[0],
+ position: MODE_INFO[BALL_EDIT_MODE_DELETE].uiPosition,
+ }));
+
+ // FIXME find the proper URLs to use
+ this.modeLabel = this.uiOverlays.addOverlay("web3d", {
+ isFacingAvatar: true,
+ alpha: 1.0,
+ dimensions: { x: 0.16, y: 0.12, z: 0.001},
+ color: "White",
+ position: UI_POSITION_MODE_LABEL,
+ });
+
+ this.setMode(BALL_EDIT_MODE_ADD);
+
+ // DEBUGGING ONLY - Fix old, bad edge bounding boxes
+ //for (var edgeId in this.magBalls.edges) {
+ // Entities.editEntity(edgeId, {
+ // dimensions: LINE_DIMENSIONS,
+ // });
+ //}
+ // DEBUGGING ONLY - Clear any previous balls
+ // this.magBalls.clear();
+ // DEBUGGING ONLY - Attempt to fix connections between balls
+ // and delete bad connections. Warning... if you haven't looked around
+ // and caused the domain server to send you all the nearby balls as well as the connections,
+ // this can break your structures
+ // this.magBalls.repair();
}
-OmniToolModules.MagBallsController.prototype.onRelease = function() {
- logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition));
+OmniToolModules.MagBallsController.prototype.onUnload = function() {
this.clearGhostEdges();
- if (this.selected) {
- this.magBalls.releaseBall(this.selected);
- this.selected = null;
- }
+ this.uiOverlays.deleteAll();
}
-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);
+
+OmniToolModules.MagBallsController.prototype.setMode = function(mode) {
+ if (mode === this.mode) {
+ return;
+ }
+
+ logDebug("Changing mode to '" + mode + "'");
+ Overlays.editOverlay(this.modeLabel, {
+ url: MODE_INFO[mode].url
+ });
+
+ this.mode = mode;
+ var color1;
+ var color2;
+ switch (this.mode) {
+ case BALL_EDIT_MODE_ADD:
+ color1 = COLORS.BLUE;
+ color2 = COLORS.GREEN;
+ break;
+
+ case BALL_EDIT_MODE_MOVE:
+ color1 = COLORS.GREEN;
+ color2 = COLORS.LIGHT_GREEN;
+ break;
+
+ case BALL_EDIT_MODE_DELETE:
+ color1 = COLORS.RED;
+ color2 = COLORS.BLUE;
+ break;
+
+ case BALL_EDIT_MODE_DELETE_SHAPE:
+ color1 = COLORS.RED;
+ color2 = COLORS.YELLOW;
+ break;
+ }
+ this.omniTool.model.setTipColors(color1, color2);
+
+}
+
+OmniToolModules.MagBallsController.prototype.findUiBallHit = function() {
+ var result = null;
+ for (var mode in MODE_INFO) {
+ var modeInfo = MODE_INFO[mode];
+ var spherePoint = getEyeRelativePosition(modeInfo.uiPosition);
+ if (findSpherePointHit(spherePoint, UI_BALL_RADIUS * 2, this.tipPosition)) {
+ this.highlighter.highlight(spherePoint);
+ this.highlighter.setColor("White");
+ // FIXME why doesn't this work?
+ this.highlighter.setSize(UI_BALL_RADIUS * 4);
+ return mode;
}
+ }
+ return;
+}
+
+OmniToolModules.MagBallsController.prototype.onUpdateSelected = function(deltaTime) {
+ if (!this.selected) {
return;
}
- this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
+ var targetPosition = this.magBalls.getNodePosition(ballId);
+ var distance = Vec3.distance(targetPosition, this.tipPosition);
+ var variance = this.magBalls.getVariance(distance);
+ var mix = Math.abs(variance) / this.magBalls.MAX_VARIANCE;
+ var color = colorMix(COLORS.YELLOW, COLORS.RED, mix);
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
- color: COLORS.RED,
+ color: color,
alpha: 1,
lineWidth: 5,
visible: true,
@@ -89,6 +201,7 @@ OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
+ color: color,
});
}
}
@@ -100,6 +213,95 @@ OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
}
}
+OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
+ this.tipPosition = this.omniTool.getPosition();
+ this.uiOverlays.onUpdate(deltaTime);
+
+ this.onUpdateSelected();
+
+ if (this.findUiBallHit()) {
+ return;
+ }
+
+ if (!this.selected) {
+ // Find the highlight target and set it.
+ var target = this.magBalls.findNearestNode(this.tipPosition, BALL_RADIUS * this.selectionRadiusMultipler);
+ this.highlighter.highlight(target);
+ this.highlighter.setColor(MODE_INFO[this.mode].colors[0]);
+ if (!target) {
+ this.magBalls.onUpdate(deltaTime);
+ }
+ return;
+ }
+}
+
+OmniToolModules.MagBallsController.prototype.deselect = function() {
+ if (!this.selected) {
+ return false
+ }
+ this.clearGhostEdges();
+ this.magBalls.releaseBall(this.selected);
+ this.selected = null;
+ return true;
+}
+
+
+OmniToolModules.MagBallsController.prototype.onClick = function() {
+ var newMode = this.findUiBallHit();
+ if (newMode) {
+ if (this.selected) {
+ this.magBalls.destroyNode(highlighted);
+ this.selected = null;
+ }
+ this.setMode(newMode);
+ return;
+ }
+
+ if (this.deselect()) {
+ return;
+ }
+
+ logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
+
+ // TODO add checking against UI shapes for adding or deleting balls.
+ var highlighted = this.highlighter.highlighted;
+ if (this.mode == BALL_EDIT_MODE_ADD && !highlighted) {
+ highlighted = this.magBalls.createBall(this.tipPosition);
+ }
+
+ // Nothing to select or create means we're done here.
+ if (!highlighted) {
+ return;
+ }
+
+ switch (this.mode) {
+ case BALL_EDIT_MODE_ADD:
+ case BALL_EDIT_MODE_MOVE:
+ this.magBalls.selectBall(highlighted);
+ this.selected = highlighted;
+ logDebug("Selected " + this.selected);
+ break;
+
+ case BALL_EDIT_MODE_DELETE:
+ this.magBalls.destroyNode(highlighted);
+ break;
+
+ case BALL_EDIT_MODE_DELETE_SHAPE:
+ logDebug("Not implemented yet");
+ break;
+ }
+
+ if (this.selected) {
+ this.highlighter.highlight(null);
+ }
+}
+
+OmniToolModules.MagBallsController.prototype.onRelease = function() {
+ if (this.holdMode) {
+ this.deselect();
+ }
+}
+
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
@@ -108,6 +310,3 @@ OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
}
-BallController.prototype.onUnload = function() {
- this.clearGhostEdges();
-}
diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js
index b692f0908c..d9dee94329 100644
--- a/examples/toys/magBalls/constants.js
+++ b/examples/toys/magBalls/constants.js
@@ -1,8 +1,10 @@
+MAG_BALLS_DATA_NAME = "magBalls";
+
// 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;
+MAG_BALLS_SCALE = 0.5;
+BALL_SIZE = 0.08 * MAG_BALLS_SCALE;
+STICK_LENGTH = 0.24 * MAG_BALLS_SCALE;
DEBUG_MAGSTICKS = true;
@@ -11,8 +13,6 @@ EDGE_NAME = "MagStick";
BALL_RADIUS = BALL_SIZE / 2.0;
-BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
-
BALL_DIMENSIONS = {
x: BALL_SIZE,
y: BALL_SIZE,
@@ -45,10 +45,13 @@ BALL_PROTOTYPE = {
// 2 millimeters
BALL_EPSILON = (.002) / BALL_DISTANCE;
+// FIXME better handling of the line bounding box would require putting the
+// origin in the middle of the line, not at one end
+LINE_DIAGONAL = Math.sqrt((STICK_LENGTH * STICK_LENGTH * 2) * 3) * 1.1;
LINE_DIMENSIONS = {
- x: 5,
- y: 5,
- z: 5
+ x: LINE_DIAGONAL,
+ y: LINE_DIAGONAL,
+ z: LINE_DIAGONAL
}
LINE_PROTOTYPE = {
@@ -75,3 +78,9 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
// ignoreCollisions: true,
// collisionsWillMove: false
// }
+
+
+BALL_EDIT_MODE_ADD = "add";
+BALL_EDIT_MODE_MOVE = "move";
+BALL_EDIT_MODE_DELETE = "delete";
+BALL_EDIT_MODE_DELETE_SHAPE = "deleteShape";
diff --git a/examples/toys/magBalls/debugUtils.js b/examples/toys/magBalls/debugUtils.js
index 8dadd34679..e69de29bb2 100644
--- a/examples/toys/magBalls/debugUtils.js
+++ b/examples/toys/magBalls/debugUtils.js
@@ -1,95 +0,0 @@
-findMatchingNode = function(position, nodePositions) {
- for (var nodeId in nodePositions) {
- var nodePos = nodePositions[nodeId];
- var distance = Vec3.distance(position, nodePos);
- if (distance < 0.03) {
- return nodeId;
- }
- }
-}
-
-repairConnections = function() {
- var ids = Entities.findEntities(MyAvatar.position, 50);
-
- // Find all the balls and record their positions
- var nodePositions = {};
- for (var i in ids) {
- var id = ids[i];
- var properties = Entities.getEntityProperties(id);
- if (properties.name == BALL_NAME) {
- nodePositions[id] = properties.position;
- }
- }
-
- // Now check all the edges to see if they're valid (point to balls)
- // and ensure that the balls point back to them
- var ballsToEdges = {};
- for (var i in ids) {
- var id = ids[i];
- var properties = Entities.getEntityProperties(id);
- if (properties.name == EDGE_NAME) {
- var startPos = properties.position;
- var endPos = Vec3.sum(startPos, properties.linePoints[1]);
- var magBallData = getMagBallsData(id);
- var update = false;
- if (!magBallData.start) {
- var startNode = findMatchingNode(startPos, nodePositions);
- if (startNode) {
- logDebug("Found start node " + startNode)
- magBallData.start = startNode;
- update = true;
- }
- }
- if (!magBallData.end) {
- var endNode = findMatchingNode(endPos, nodePositions);
- if (endNode) {
- logDebug("Found end node " + endNode)
- magBallData.end = endNode;
- update = true;
- }
- }
- if (!magBallData.start || !magBallData.end) {
- logDebug("Didn't find both ends");
- Entities.deleteEntity(id);
- continue;
- }
- if (!ballsToEdges[magBallData.start]) {
- ballsToEdges[magBallData.start] = [ id ];
- } else {
- ballsToEdges[magBallData.start].push(id);
- }
- if (!ballsToEdges[magBallData.end]) {
- ballsToEdges[magBallData.end] = [ id ];
- } else {
- ballsToEdges[magBallData.end].push(id);
- }
- if (update) {
- logDebug("Updating incomplete edge " + id);
- magBallData.length = BALL_DISTANCE;
- setMagBallsData(id, magBallData);
- }
- }
- }
- for (var nodeId in ballsToEdges) {
- var magBallData = getMagBallsData(nodeId);
- var edges = magBallData.edges || [];
- var edgeHash = {};
- for (var i in edges) {
- edgeHash[edges[i]] = true;
- }
- var update = false;
- for (var i in ballsToEdges[nodeId]) {
- var edgeId = ballsToEdges[nodeId][i];
- if (!edgeHash[edgeId]) {
- update = true;
- edgeHash[edgeId] = true;
- edges.push(edgeId);
- }
- }
- if (update) {
- logDebug("Fixing node with missing edge data");
- magBallData.edges = edges;
- setMagBallsData(nodeId, magBallData);
- }
- }
-}
diff --git a/examples/toys/magBalls/edgeSpring.js b/examples/toys/magBalls/edgeSpring.js
index 852c9257c2..e1b717c39b 100644
--- a/examples/toys/magBalls/edgeSpring.js
+++ b/examples/toys/magBalls/edgeSpring.js
@@ -9,7 +9,10 @@ EdgeSpring = function(edgeId, graph) {
this.desiredLength = magBallsData.length || BALL_DISTANCE;
}
-EdgeSpring.prototype.adjust = function(results) {
+// FIXME as iterations increase, start introducing some randomness
+// to the adjustment so that we avoid false equilibriums
+// Alternatively, larger iterations could increase the acceptable variance
+EdgeSpring.prototype.adjust = function(results, iterations) {
var startPos = this.getAdjustedPosition(this.start, results);
var endPos = this.getAdjustedPosition(this.end, results);
var vector = Vec3.subtract(endPos, startPos);
diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js
index 307be9f5e1..b689fb1272 100644
--- a/examples/toys/magBalls/magBalls.js
+++ b/examples/toys/magBalls/magBalls.js
@@ -25,10 +25,6 @@ MagBalls = function() {
this.refresh();
var _this = this;
- //Script.update.connect(function(deltaTime) {
- // _this.onUpdate(deltaTime);
- //});
-
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
@@ -61,8 +57,13 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
if (!this.unstableEdges[edgeId]) {
continue;
}
- adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
+ // FIXME need to add some randomness to this so that objects don't hit a
+ // false equilibrium
+ // FIXME should this be done node-wise, to more easily account for the number of edge
+ // connections for a node?
+ adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults, this.adjustIterations);
}
+
for (var nodeId in nodeAdjustResults) {
var curPos = this.getNodePosition(nodeId);
var newPos = nodeAdjustResults[nodeId];
@@ -71,18 +72,24 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
fixupEdges[edgeId] = true;
}
// logDebug("Moving node Id " + nodeId + " " + (distance * 1000).toFixed(3) + " mm");
- Entities.editEntity(nodeId, { position: newPos, color: COLORS.RED });
+ Entities.editEntity(nodeId, {
+ position: newPos,
+ // DEBUGGING, flashes moved balls
+ // color: COLORS.RED
+ });
}
+ // DEBUGGING, flashes moved balls
+ //Script.setTimeout(function(){
+ // for (var nodeId in nodeAdjustResults) {
+ // Entities.editEntity(nodeId, { color: BALL_COLOR });
+ // }
+ //}, ((UPDATE_INTERVAL * 1000) / 2));
+
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) {
if (adjusted) {
@@ -357,4 +364,96 @@ MagBalls.prototype.onEntityAdded = function(entityId) {
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
this.refreshNeeded = 1;
}
-}
\ No newline at end of file
+}
+
+findMatchingNode = function(position, nodePositions) {
+ for (var nodeId in nodePositions) {
+ var nodePos = nodePositions[nodeId];
+ var distance = Vec3.distance(position, nodePos);
+ if (distance < 0.03) {
+ return nodeId;
+ }
+ }
+}
+
+
+MagBalls.prototype.repair = function() {
+ // Find all the balls and record their positions
+ var nodePositions = {};
+ for (var nodeId in this.nodes) {
+ nodePositions[nodeId] = this.getNodePosition(nodeId);
+ }
+
+ // Now check all the edges to see if they're valid (point to balls)
+ // and ensure that the balls point back to them
+ var ballsToEdges = {};
+
+ // WARNING O(n^2) algorithm, every edge that is broken does
+ // an O(N) search against the nodes
+ for (var edgeId in this.edges) {
+ var properties = Entities.getEntityProperties(edgeId);
+ var startPos = properties.position;
+ var endPos = Vec3.sum(startPos, properties.linePoints[1]);
+ var magBallData = getMagBallsData(edgeId);
+ var update = false;
+ if (!magBallData.start) {
+ var startNode = findMatchingNode(startPos, nodePositions);
+ if (startNode) {
+ logDebug("Found start node " + startNode)
+ magBallData.start = startNode;
+ update = true;
+ }
+ }
+ if (!magBallData.end) {
+ var endNode = findMatchingNode(endPos, nodePositions);
+ if (endNode) {
+ logDebug("Found end node " + endNode)
+ magBallData.end = endNode;
+ update = true;
+ }
+ }
+ if (!magBallData.start || !magBallData.end) {
+ logDebug("Didn't find both ends");
+ this.destroyEdge(edgeId);
+ continue;
+ }
+ if (!ballsToEdges[magBallData.start]) {
+ ballsToEdges[magBallData.start] = [ edgeId ];
+ } else {
+ ballsToEdges[magBallData.start].push(edgeId);
+ }
+ if (!ballsToEdges[magBallData.end]) {
+ ballsToEdges[magBallData.end] = [ edgeId ];
+ } else {
+ ballsToEdges[magBallData.end].push(edgeId);
+ }
+ if (update) {
+ logDebug("Updating incomplete edge " + edgeId);
+ magBallData.length = BALL_DISTANCE;
+ setMagBallsData(edgeId, magBallData);
+ }
+ }
+ for (var nodeId in ballsToEdges) {
+ var magBallData = getMagBallsData(nodeId);
+ var edges = magBallData.edges || [];
+ var edgeHash = {};
+ for (var i in edges) {
+ edgeHash[edges[i]] = true;
+ }
+ var update = false;
+ for (var i in ballsToEdges[nodeId]) {
+ var edgeId = ballsToEdges[nodeId][i];
+ if (!edgeHash[edgeId]) {
+ update = true;
+ edgeHash[edgeId] = true;
+ edges.push(edgeId);
+ }
+ }
+ if (update) {
+ logDebug("Fixing node with missing edge data");
+ magBallData.edges = edges;
+ setMagBallsData(nodeId, magBallData);
+ }
+ }
+}
+
diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp
index c173c5927f..f14fcf4dc0 100644
--- a/interface/src/ui/overlays/Web3DOverlay.cpp
+++ b/interface/src/ui/overlays/Web3DOverlay.cpp
@@ -147,7 +147,12 @@ QScriptValue Web3DOverlay::getProperty(const QString& property) {
void Web3DOverlay::setURL(const QString& url) {
_url = url;
- _isLoaded = false;
+ if (_webSurface) {
+ AbstractViewStateInterface::instance()->postLambdaEvent([this, url] {
+ _webSurface->getRootItem()->setProperty("url", url);
+ });
+ }
+
}
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {
diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv
index 99f404eaec..b22bb36d83 100644
--- a/libraries/render-utils/src/simple.slv
+++ b/libraries/render-utils/src/simple.slv
@@ -23,10 +23,12 @@
out vec3 _normal;
out vec3 _color;
out vec2 _texCoord0;
+out vec4 _position;
void main(void) {
_color = inColor.rgb;
_texCoord0 = inTexCoord0.st;
+ _position = inPosition;
// standard transform
TransformCamera cam = getTransformCamera();