diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 35af5f4eae..2c024c5bf0 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -22,3 +22,4 @@ Script.load("grab.js"); Script.load("directory.js"); Script.load("dialTone.js"); Script.load("attachedEntitiesManager.js"); +Script.load("depthReticle.js"); diff --git a/examples/depthReticle.js b/examples/depthReticle.js index 4b649f49b6..14a5ba5ff3 100644 --- a/examples/depthReticle.js +++ b/examples/depthReticle.js @@ -5,6 +5,8 @@ // Copyright 2016 High Fidelity, Inc. // // When used in HMD, this script will make the reticle depth track to any clickable item in view. +// This script also handles auto-hiding the reticle after inactivity, as well as having the reticle +// seek the look at position upon waking up. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -17,8 +19,89 @@ var desiredDepth = APPARENT_2D_OVERLAY_DEPTH; var TIME_BETWEEN_DEPTH_CHECKS = 100; var MINIMUM_DEPTH_ADJUST = 0.01; var NON_LINEAR_DIVISOR = 2; +var MINIMUM_SEEK_DISTANCE = 0.01; -Script.update.connect(function(deltaTime) { +var lastMouseMove = Date.now(); +var lastMouseX = Reticle.position.x; +var lastMouseY = Reticle.position.y; +var HIDE_STATIC_MOUSE_AFTER = 3000; // 3 seconds +var shouldSeekToLookAt = false; +var fastMouseMoves = 0; +var averageMouseVelocity = 0; +var WEIGHTING = 1/20; // simple moving average over last 20 samples +var ONE_MINUS_WEIGHTING = 1 - WEIGHTING; +var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50; + +Controller.mouseMoveEvent.connect(function(mouseEvent) { + var now = Date.now(); + + // if the reticle is hidden, show it... + if (!Reticle.visible) { + Reticle.visible = true; + if (HMD.active) { + shouldSeekToLookAt = true; + } + } else { + // even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it) + // then they are probably looking for it, and we should move into seekToLookAt mode + if (HMD.active && !shouldSeekToLookAt) { + var dx = Reticle.position.x - lastMouseX; + var dy = Reticle.position.y - lastMouseY; + var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move + var mouseMoveDistance = Math.sqrt((dx*dx) + (dy*dy)); + var mouseVelocity = mouseMoveDistance / dt; + averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * mouseVelocity); + if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) { + shouldSeekToLookAt = true; + } + } + } + lastMouseMove = now; + lastMouseX = mouseEvent.x; + lastMouseY = mouseEvent.y; +}); + +function seekToLookAt() { + // if we're currently seeking the lookAt move the mouse toward the lookat + if (shouldSeekToLookAt) { + averageMouseVelocity = 0; // reset this, these never count for movement... + var lookAt2D = HMD.getHUDLookAtPosition2D(); + var currentReticlePosition = Reticle.position; + var distanceBetweenX = lookAt2D.x - Reticle.position.x; + var distanceBetweenY = lookAt2D.y - Reticle.position.y; + var moveX = distanceBetweenX / NON_LINEAR_DIVISOR; + var moveY = distanceBetweenY / NON_LINEAR_DIVISOR; + var newPosition = { x: Reticle.position.x + moveX, y: Reticle.position.y + moveY }; + var closeEnoughX = false; + var closeEnoughY = false; + if (moveX < MINIMUM_SEEK_DISTANCE) { + newPosition.x = lookAt2D.x; + closeEnoughX = true; + } + if (moveY < MINIMUM_SEEK_DISTANCE) { + newPosition.y = lookAt2D.y; + closeEnoughY = true; + } + Reticle.position = newPosition; + if (closeEnoughX && closeEnoughY) { + shouldSeekToLookAt = false; + } + } +} + +function autoHideReticle() { + // if we haven't moved in a long period of time, and we're not pointing at some + // system overlay (like a window), then hide the reticle + if (Reticle.visible && !Reticle.pointingAtSystemOverlay) { + var now = Date.now(); + var timeSinceLastMouseMove = now - lastMouseMove; + if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) { + Reticle.visible = false; + } + } +} + +function checkReticleDepth() { var now = Date.now(); var timeSinceLastDepthCheck = now - lastDepthCheckTime; if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) { @@ -56,6 +139,9 @@ Script.update.connect(function(deltaTime) { } } +} + +function moveToDesiredDepth() { // move the reticle toward the desired depth if (desiredDepth != Reticle.depth) { @@ -69,4 +155,13 @@ Script.update.connect(function(deltaTime) { Reticle.setDepth(newDepth); } +} + +Script.update.connect(function(deltaTime) { + autoHideReticle(); // auto hide reticle for desktop or HMD mode + if (HMD.active) { + seekToLookAt(); // handle moving the reticle toward the look at + checkReticleDepth(); // make sure reticle is at correct depth + moveToDesiredDepth(); // move the fade the reticle to the desired depth + } }); diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 59d794d7cb..e8e6a0a956 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -212,19 +212,21 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); //draw the mouse pointer - // Get the mouse coordinates and convert to NDC [-1, 1] - vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas... - vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); - // Invert the Y axis - mousePosition.y *= -1.0f; + if (getReticleVisible()) { + // Get the mouse coordinates and convert to NDC [-1, 1] + vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas... + vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); + // Invert the Y axis + mousePosition.y *= -1.0f; - Transform model; - model.setTranslation(vec3(mousePosition, 0)); - vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; - model.setScale(vec3(mouseSize, 1.0f)); - batch.setModelTransform(model); - bindCursorTexture(batch); - geometryCache->renderUnitQuad(batch, vec4(1)); + Transform model; + model.setTranslation(vec3(mousePosition, 0)); + vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; + model.setScale(vec3(mouseSize, 1.0f)); + batch.setModelTransform(model); + bindCursorTexture(batch); + geometryCache->renderUnitQuad(batch, vec4(1)); + } }); }