From c8069ba73ce3ee80cf63d96cf4c49dc4dde38b95 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 2 Sep 2015 22:37:09 -0700 Subject: [PATCH] Working on magballs polish --- examples/defaultScripts.js | 1 + examples/html/magBalls/addMode.html | 25 ++ examples/html/magBalls/deleteMode.html | 25 ++ examples/html/magBalls/magBalls.css | 14 + examples/html/magBalls/moveMode.html | 25 ++ examples/libraries/avatarRelativeOverlays.js | 55 ++++ examples/libraries/constants.js | 2 + examples/libraries/highlighter.js | 25 +- examples/libraries/omniTool.js | 73 ++++- .../omniTool/models/invisibleWand.js | 6 + .../libraries/omniTool/models/modelBase.js | 6 + examples/libraries/omniTool/models/wand.js | 34 +-- examples/libraries/omniTool/modules/test.js | 31 +- examples/libraries/utils.js | 19 ++ examples/toys/magBalls.js | 287 +++++++++++++++--- examples/toys/magBalls/constants.js | 25 +- examples/toys/magBalls/debugUtils.js | 95 ------ examples/toys/magBalls/edgeSpring.js | 5 +- examples/toys/magBalls/magBalls.js | 125 +++++++- interface/src/ui/overlays/Web3DOverlay.cpp | 7 +- libraries/render-utils/src/simple.slv | 2 + 21 files changed, 681 insertions(+), 206 deletions(-) create mode 100644 examples/html/magBalls/addMode.html create mode 100644 examples/html/magBalls/deleteMode.html create mode 100644 examples/html/magBalls/magBalls.css create mode 100644 examples/html/magBalls/moveMode.html create mode 100644 examples/libraries/avatarRelativeOverlays.js create mode 100644 examples/libraries/omniTool/models/invisibleWand.js 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();