From ea415071b48d1c3060c6b455536e54e951aa90fe Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 22:54:23 -0700 Subject: [PATCH 1/2] OmniTool first pass --- .../{toys/magBalls => libraries}/constants.js | 0 .../magBalls => libraries}/highlighter.js | 6 + examples/libraries/omniTool.js | 300 ++++++++++++++++++ .../libraries/omniTool/models/modelBase.js | 19 ++ examples/libraries/omniTool/models/wand.js | 120 +++++++ examples/libraries/omniTool/modules/test.js | 9 + .../{toys/magBalls => libraries}/utils.js | 8 +- examples/toys/magBalls/magBallsMain.js | 26 -- 8 files changed, 461 insertions(+), 27 deletions(-) rename examples/{toys/magBalls => libraries}/constants.js (100%) rename examples/{toys/magBalls => libraries}/highlighter.js (92%) create mode 100644 examples/libraries/omniTool.js create mode 100644 examples/libraries/omniTool/models/modelBase.js create mode 100644 examples/libraries/omniTool/models/wand.js create mode 100644 examples/libraries/omniTool/modules/test.js rename examples/{toys/magBalls => libraries}/utils.js (93%) delete mode 100644 examples/toys/magBalls/magBallsMain.js diff --git a/examples/toys/magBalls/constants.js b/examples/libraries/constants.js similarity index 100% rename from examples/toys/magBalls/constants.js rename to examples/libraries/constants.js diff --git a/examples/toys/magBalls/highlighter.js b/examples/libraries/highlighter.js similarity index 92% rename from examples/toys/magBalls/highlighter.js rename to examples/libraries/highlighter.js index 149d9ec5b7..b3550b6c8a 100644 --- a/examples/toys/magBalls/highlighter.js +++ b/examples/libraries/highlighter.js @@ -53,6 +53,12 @@ Highlighter.prototype.setSize = function(newSize) { }); } +Highlighter.prototype.setRotation = function(newRotation) { + Overlays.editOverlay(this.highlightCube, { + rotation: newRotation + }); +} + Highlighter.prototype.updateHighlight = function() { if (this.hightlighted) { var properties = Entities.getEntityProperties(this.hightlighted); diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js new file mode 100644 index 0000000000..643b789a0e --- /dev/null +++ b/examples/libraries/omniTool.js @@ -0,0 +1,300 @@ +// +// Created by Bradley Austin Davis on 2015/09/01 +// 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 +// + +Script.include("constants.js"); +Script.include("utils.js"); +Script.include("highlighter.js"); +Script.include("omniTool/models/modelBase.js"); +Script.include("omniTool/models/wand.js"); + +OmniToolModules = {}; +OmniToolModuleType = null; + +OmniTool = function(side) { + this.OMNI_KEY = "OmniTool"; + this.MAX_FRAMERATE = 30; + this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE + this.SIDE = side; + this.PALM = 2 * side; + this.ACTION = findAction(side ? "ACTION2" : "ACTION1"); + this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2"); + + this.highlighter = new Highlighter(); + this.ignoreEntities = {}; + this.nearestOmniEntity = { + id: null, + inside: false, + position: null, + distance: Infinity, + radius: 0, + omniProperties: {}, + boundingBox: null, + }; + + 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); + + // Connect to desired events + var _this = this; + Controller.actionEvent.connect(function(action, state) { + _this.onActionEvent(action, state); + }); + + Script.update.connect(function(deltaTime) { + _this.lastUpdateInterval += deltaTime; + if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) { + _this.onUpdate(_this.lastUpdateInterval); + _this.lastUpdateInterval = 0; + } + }); + + Script.scriptEnding.connect(function() { + _this.onCleanup(); + }); +} + +OmniTool.prototype.onActionEvent = function(action, state) { + // FIXME figure out the issues when only one spatial controller is active + // logDebug("Action: " + action + " " + state); + + if (this.module && this.module.onActionEvent) { + this.module.onActionEvent(action, state); + } + + if (action == this.ACTION) { + if (state) { + this.onClick(); + } else { + this.onRelease(); + } + } + + // FIXME Does not work + //// with only one controller active (listed as 2 here because 'tip' + 'palm') + //// then treat the alt action button as the action button +} + +OmniTool.prototype.getOmniToolData = function(entityId) { + return getEntityCustomData(this.OMNI_KEY, entityId, null); +} + +OmniTool.prototype.setOmniToolData = function(entityId, data) { + setEntityCustomData(this.OMNI_KEY, entityId, data); +} + +OmniTool.prototype.updateOmniToolData = function(entityId, data) { + var currentData = this.getOmniToolData(entityId) || {}; + for (var key in data) { + currentData[key] = data[key]; + } + setEntityCustomData(this.OMNI_KEY, entityId, currentData); +} + +OmniTool.prototype.setActive = function(active) { + if (active === this.active) { + return; + } + 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); + } +} + + +OmniTool.prototype.onUpdate = function(deltaTime) { + // FIXME this returns data if either the left or right controller is not on the base + this.position = Controller.getSpatialControlPosition(this.PALM); + // When on the base, hydras report a position of 0 + this.setActive(Vec3.length(this.position) > 0.001); + + var rawRotation = Controller.getSpatialControlRawRotation(this.PALM); + this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation); + + this.model.setTransform({ + rotation: this.rotation, + position: this.position, + }); + + this.scan(); + + if (this.module && this.module.onUpdate) { + this.module.onUpdate(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) { + this.activateNewOmniModule(); + return; + } + + // Next check if there is an active module and if so propagate the click + // FIXME how to I switch to a new module? + if (this.module && this.module.onClick) { + this.module.onClick(); + return; + } +} + +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 +OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) { + if (!maxDistance) { + maxDistance = 2.0; + } + var resultDistance = Infinity; + var resultId = null; + var resultProperties = null; + var resultOmniData = null; + var ids = Entities.findEntities(this.model.tipPosition, maxDistance); + for (var i in ids) { + var entityId = ids[i]; + if (this.ignoreEntities[entityId]) { + continue; + } + var omniData = this.getOmniToolData(entityId); + if (!omniData) { + // FIXME find a place to flush this information + this.ignoreEntities[entityId] = true; + continue; + } + + // Let searchers query specifically + if (selector && !selector(entityId, omniData)) { + continue; + } + + var properties = Entities.getEntityProperties(entityId); + var distance = Vec3.distance(this.model.tipPosition, properties.position); + if (distance < resultDistance) { + resultDistance = distance; + resultId = entityId; + } + } + + return resultId; +} + +OmniTool.prototype.onEnterNearestOmniEntity = function() { + this.nearestOmniEntity.inside = true; + this.highlighter.highlight(this.nearestOmniEntity.id); + logDebug("On enter omniEntity " + this.nearestOmniEntity.id); +} + +OmniTool.prototype.onLeaveNearestOmniEntity = function() { + this.nearestOmniEntity.inside = false; + this.highlighter.highlight(null); + logDebug("On leave omniEntity " + this.nearestOmniEntity.id); +} + +OmniTool.prototype.setNearestOmniEntity = function(entityId) { + if (entityId && entityId !== this.nearestOmniEntity.id) { + if (this.nearestOmniEntity.id && this.nearestOmniEntity.inside) { + this.onLeaveNearestOmniEntity(); + } + this.nearestOmniEntity.id = entityId; + this.nearestOmniEntity.omniProperties = this.getOmniToolData(entityId); + var properties = Entities.getEntityProperties(entityId); + this.nearestOmniEntity.position = properties.position; + // FIXME use a real bounding box, not a sphere + var bbox = properties.boundingBox; + this.nearestOmniEntity.radius = Vec3.length(Vec3.subtract(bbox.center, bbox.brn)); + this.highlighter.setRotation(properties.rotation); + this.highlighter.setSize(Vec3.multiply(1.05, bbox.dimensions)); + } +} + +OmniTool.prototype.scan = function() { + var scanDistance = Vec3.distance(this.model.tipPosition, this.lastScanPosition); + + if (scanDistance < 0.005) { + return; + } + + this.lastScanPosition = this.model.tipPosition; + + this.setNearestOmniEntity(this.findNearestOmniEntity()); + if (this.nearestOmniEntity.id) { + var distance = Vec3.distance(this.model.tipPosition, this.nearestOmniEntity.position); + // track distance on a half centimeter basis + if (Math.abs(this.nearestOmniEntity.distance - distance) > 0.005) { + this.nearestOmniEntity.distance = distance; + if (!this.nearestOmniEntity.inside && distance < this.nearestOmniEntity.radius) { + this.onEnterNearestOmniEntity(); + } + + if (this.nearestOmniEntity.inside && distance > this.nearestOmniEntity.radius + 0.01) { + this.onLeaveNearestOmniEntity(); + } + } + } +} + +OmniTool.prototype.unloadModule = function() { + if (this.module && this.module.onUnload) { + this.module.onUnload(); + } + this.module = null; + this.moduleEntityId = null; +} + +OmniTool.prototype.activateNewOmniModule = function() { + // Support the ability for scripts to just run without replacing the current module + var script = this.nearestOmniEntity.omniProperties.script; + if (script.indexOf("/") < 0) { + script = "omniTool/modules/" + script; + } + + // Reset the tool type + OmniToolModuleType = null; + logDebug("Including script path: " + script); + try { + Script.include(script); + } catch(err) { + logWarn("Failed to include script: " + script + "\n" + err); + return; + } + + // If we're building a new module, unload the old one + if (OmniToolModuleType) { + logDebug("New OmniToolModule: " + OmniToolModuleType); + this.unloadModule(); + + try { + this.module = new OmniToolModules[OmniToolModuleType](this, this.nearestOmniEntity.id); + this.moduleEntityId = this.nearestOmniEntity.id; + if (this.module.onLoad) { + this.module.onLoad(); + } + } catch(err) { + logWarn("Failed to instantiate new module: " + err); + } + } +} + +// FIXME find a good way to sync the two omni tools +OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ]; diff --git a/examples/libraries/omniTool/models/modelBase.js b/examples/libraries/omniTool/models/modelBase.js new file mode 100644 index 0000000000..7697856d3f --- /dev/null +++ b/examples/libraries/omniTool/models/modelBase.js @@ -0,0 +1,19 @@ + +ModelBase = function() { + this.length = 0.2; +} + +ModelBase.prototype.setVisible = function(visible) { + this.visible = visible; +} + +ModelBase.prototype.setLength = function(length) { + this.length = length; +} + +ModelBase.prototype.setTransform = function(transform) { + this.rotation = transform.rotation; + this.position = transform.position; + this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 }); + this.tipPosition = Vec3.sum(this.position, this.tipVector); +} diff --git a/examples/libraries/omniTool/models/wand.js b/examples/libraries/omniTool/models/wand.js new file mode 100644 index 0000000000..8f0fe92b53 --- /dev/null +++ b/examples/libraries/omniTool/models/wand.js @@ -0,0 +1,120 @@ + +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, + }]; + 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, + color: this.DEFAULT_TIP_COLORS[0], + alpha: 1.0, + solid: true, + visible: false, + }), + Overlays.addOverlay("cube", { + position: ZERO_VECTOR, + color: this.DEFAULT_TIP_COLORS[1], + alpha: 1.0, + solid: true, + visible: false, + }) + ]; + + this.wand = Overlays.addOverlay("cube", { + position: ZERO_VECTOR, + color: COLORS.WHITE, + dimensions: { x: 0.01, y: 0.01, z: 0.2 }, + alpha: 1.0, + solid: true, + visible: false + }); + + 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; + } + }); +} + +Wand.prototype = Object.create( ModelBase.prototype ); + +Wand.prototype.setVisible = function(visible) { + ModelBase.prototype.setVisible.call(this, visible); + Overlays.editOverlay(this.pointers[0], { + visible: this.visible + }); + Overlays.editOverlay(this.pointers[1], { + visible: this.visible + }); + Overlays.editOverlay(this.wand, { + visible: this.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, + rotation: this.rotation, + visible: true, + }); + Overlays.editOverlay(this.pointers[1], { + position: this.tipPosition, + rotation: Quat.multiply(this.POINTER_ROTATION, this.rotation), + visible: true, + }); + Overlays.editOverlay(this.wand, { + dimensions: { x: 0.01, y: this.length * 0.9, z: 0.01 }, + position: wandPosition, + rotation: this.rotation, + visible: true, + }); +} + +Wand.prototype.setTipColors = function(color1, color2) { + Overlays.editOverlay(this.pointers[0], { + color: color1 || this.DEFAULT_TIP_COLORS[0], + }); + Overlays.editOverlay(this.pointers[1], { + color: color2 || this.DEFAULT_TIP_COLORS[1], + }); +} + +Wand.prototype.onUpdate = function(deltaTime) { + if (this.visible) { + var time = new Date().getTime() / 250; + var scale1 = Math.abs(Math.sin(time)); + var scale2 = Math.abs(Math.cos(time)); + Overlays.editOverlay(this.pointers[0], { + scale: scale1 * 0.01, + }); + Overlays.editOverlay(this.pointers[1], { + scale: scale2 * 0.01, + }); + } +} diff --git a/examples/libraries/omniTool/modules/test.js b/examples/libraries/omniTool/modules/test.js new file mode 100644 index 0000000000..1ca806affa --- /dev/null +++ b/examples/libraries/omniTool/modules/test.js @@ -0,0 +1,9 @@ + +OmniToolModules.Test = function() { +} + +OmniToolModules.Test.prototype.onClick = function() { + logDebug("Test module onClick"); +} + +OmniToolModuleType = "Test" \ No newline at end of file diff --git a/examples/toys/magBalls/utils.js b/examples/libraries/utils.js similarity index 93% rename from examples/toys/magBalls/utils.js rename to examples/libraries/utils.js index ea1446f858..b15ea6c313 100644 --- a/examples/toys/magBalls/utils.js +++ b/examples/libraries/utils.js @@ -53,11 +53,17 @@ getEntityUserData = function(id) { var results = null; var properties = Entities.getEntityProperties(id); if (properties.userData) { - results = JSON.parse(properties.userData); + try { + results = JSON.parse(properties.userData); + } catch(err) { + logDebug(err); + logDebug(properties.userData); + } } return results ? results : {}; } + // Non-destructively modify the user data of an entity. setEntityCustomData = function(customKey, id, data) { var userData = getEntityUserData(id); diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js deleted file mode 100644 index 4eb98fab26..0000000000 --- a/examples/toys/magBalls/magBallsMain.js +++ /dev/null @@ -1,26 +0,0 @@ -// -// 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 -// - -Script.include("../../libraries/htmlColors.js"); -Script.include("constants.js"); -Script.include("utils.js"); -Script.include("magBalls.js"); - -Script.include("ballController.js"); - -var magBalls = new MagBalls(); - -// Clear any previous balls -// magBalls.clear(); - -MenuController = function(side) { - HandController.call(this, side); -} - -// FIXME resolve some of the issues with dual controllers before allowing both controllers active -var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ]; From 5717b7783748ff543403d877e3770f594270f319 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 2 Sep 2015 18:01:47 -0700 Subject: [PATCH 2/2] Updating magballs to omnitool --- examples/libraries/constants.js | 77 -------------- examples/libraries/omniTool.js | 4 + examples/libraries/utils.js | 8 -- examples/toys/magBalls.js | 113 ++++++++++++++++++++ examples/toys/magBalls/ballController.js | 103 ------------------ examples/toys/magBalls/constants.js | 77 ++++++++++++++ examples/toys/magBalls/graph.js | 4 + examples/toys/magBalls/handController.js | 128 ----------------------- examples/toys/magBalls/magBalls.js | 115 +++++++++++++++----- examples/toys/magBalls/menuController.js | 66 ------------ 10 files changed, 289 insertions(+), 406 deletions(-) create mode 100644 examples/toys/magBalls.js delete mode 100644 examples/toys/magBalls/ballController.js create mode 100644 examples/toys/magBalls/constants.js delete mode 100644 examples/toys/magBalls/handController.js delete mode 100644 examples/toys/magBalls/menuController.js diff --git a/examples/libraries/constants.js b/examples/libraries/constants.js index d154910f91..3ed7c02633 100644 --- a/examples/libraries/constants.js +++ b/examples/libraries/constants.js @@ -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 -// } diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js index 643b789a0e..c9f041d672 100644 --- a/examples/libraries/omniTool.js +++ b/examples/libraries/omniTool.js @@ -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); diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index b15ea6c313..6e6012cfe3 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -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) { diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js new file mode 100644 index 0000000000..8e441901a2 --- /dev/null +++ b/examples/toys/magBalls.js @@ -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(); +} diff --git a/examples/toys/magBalls/ballController.js b/examples/toys/magBalls/ballController.js deleted file mode 100644 index 0f178b2804..0000000000 --- a/examples/toys/magBalls/ballController.js +++ /dev/null @@ -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() { -} diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js new file mode 100644 index 0000000000..b692f0908c --- /dev/null +++ b/examples/toys/magBalls/constants.js @@ -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 +// } diff --git a/examples/toys/magBalls/graph.js b/examples/toys/magBalls/graph.js index df02ee3628..198c1b0c16 100644 --- a/examples/toys/magBalls/graph.js +++ b/examples/toys/magBalls/graph.js @@ -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]); diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js deleted file mode 100644 index 4aba43d412..0000000000 --- a/examples/toys/magBalls/handController.js +++ /dev/null @@ -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"); -} - - diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js index 187c550073..307be9f5e1 100644 --- a/examples/toys/magBalls/magBalls.js +++ b/examples/toys/magBalls/magBalls.js @@ -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; + } +} \ No newline at end of file diff --git a/examples/toys/magBalls/menuController.js b/examples/toys/magBalls/menuController.js deleted file mode 100644 index 0a076d1ff8..0000000000 --- a/examples/toys/magBalls/menuController.js +++ /dev/null @@ -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(); -}