// // 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"); Script.include("omniTool/models/invisibleWand.js"); OmniToolModules = {}; OmniToolModuleType = null; LOG_DEBUG = 1; OmniTool = function(left) { this.OMNI_KEY = "OmniTool"; this.MAX_FRAMERATE = 60; this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE this.left = left; this.triggered = false; var actions = Controller.Actions; var standard = Controller.Standard; this.palmControl = left ? actions.LeftHand : actions.RightHand; logDebug("Init OmniTool " + (left ? "left" : "right")); 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.active = false; this.module = null; this.moduleEntityId = null; this.lastScanPosition = ZERO_VECTOR; this.showWand(false); // Connect to desired events var that = this; Script.update.connect(function(deltaTime) { that.lastUpdateInterval += deltaTime; if (that.lastUpdateInterval >= that.UPDATE_INTERVAL) { that.onUpdate(that.lastUpdateInterval); that.lastUpdateInterval = 0; } }); Script.scriptEnding.connect(function() { that.onCleanup(); }); this.mapping = Controller.newMapping(); this.mapping.from(left ? standard.LeftPrimaryThumb : standard.RightPrimaryThumb).to(function(value){ that.onUpdateTrigger(value); }) this.mapping.enable(); } 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.mapping.disable(); this.unloadModule(); } OmniTool.prototype.onUpdateTrigger = function (value) { //logDebug("Trigger update value " + value); var triggered = value != 0; if (triggered != this.triggered) { this.triggered = triggered; if (this.triggered) { this.onClick(); } else { this.onRelease(); } } } 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 " + this.left + " 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.pose = Controller.getPoseValue(this.palmControl); this.position = this.left ? MyAvatar.leftHandTipPosition : MyAvatar.rightHandTipPosition; // 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 = this.pose.rotation; this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation); this.model.setTransform({ rotation: this.rotation, position: this.position, }); if (this.model.onUpdate) { this.model.onUpdate(deltaTime); } } 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) { // 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; } // 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() { if (this.module && this.module.onRelease) { this.module.onRelease(); return; } } // 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.getPosition = function() { return this.model.tipPosition; } 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); } 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() { logDebug("Unloading omniTool module") 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(true), new OmniTool(false) ];