From b0ad9a8110b8206115700e30ddd48d26959218a7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 7 Mar 2017 10:03:38 -0800 Subject: [PATCH 1/6] Bug fix for potential crash when getting DebugDraw::instance --- interface/src/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ba63f4f064..12ee2d09b3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -608,6 +608,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } } + // make sure the debug draw singleton is initialized on the main thread. + DebugDraw::getInstance().removeMarker(""); _runningMarker.startRunningMarker(); From 1b8a624edb81e8f62e2cb257b74e588653daaab2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 7 Mar 2017 16:07:30 -0800 Subject: [PATCH 2/6] Fix for potential crash due to DebugDraw data race. --- libraries/render-utils/src/AnimDebugDraw.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index c8746d5c60..6066385847 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -346,7 +346,9 @@ void AnimDebugDraw::update() { numVerts += (int)markerMap.size() * VERTICES_PER_BONE; auto myAvatarMarkerMap = DebugDraw::getInstance().getMyAvatarMarkerMap(); numVerts += (int)myAvatarMarkerMap.size() * VERTICES_PER_BONE; - numVerts += (int)DebugDraw::getInstance().getRays().size() * VERTICES_PER_RAY; + auto rays = DebugDraw::getInstance().getRays(); + DebugDraw::getInstance().clearRays(); + numVerts += (int)rays.size() * VERTICES_PER_RAY; // allocate verts! std::vector vertices; @@ -398,10 +400,9 @@ void AnimDebugDraw::update() { } // draw rays from shared DebugDraw singleton - for (auto& iter : DebugDraw::getInstance().getRays()) { + for (auto& iter : rays) { addLine(std::get<0>(iter), std::get<1>(iter), std::get<2>(iter), v); } - DebugDraw::getInstance().clearRays(); data._vertexBuffer->resize(sizeof(AnimDebugDrawData::Vertex) * numVerts); data._vertexBuffer->setSubData(0, vertices); From c7cd0fdc3821ac932ad4e86e02c2797c5800182c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 7 Mar 2017 18:13:42 -0800 Subject: [PATCH 3/6] Add DebugDraw to .eslintrc config file --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index c708decc51..9635142d1a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { "Clipboard": false, "Controller": false, "DialogsManager": false, + "DebugDraw": false, "Entities": false, "FaceTracker": false, "GlobalServices": false, From 836c701cb3113572193fcbe735d2c21ed58f7921 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 7 Mar 2017 18:19:32 -0800 Subject: [PATCH 4/6] More accurate and responsive stylus and finger touching. The algorithm used to detect when and where the stylus or finger is touching the tablet has been improved. * hovering the finger/stylus over the surface of the tablet should cause buttons to highlight. * flicking or using the stylus like a drum stick, should more accurately click buttons on the tablet. * stabbing the tablet quickly, should also more accurately trigger button presses. * moving the hand/stylus from behind the tablet should be less likely to cause press events. --- .../system/controllers/handControllerGrab.js | 861 ++++++++++-------- scripts/system/libraries/WebTablet.js | 3 +- 2 files changed, 491 insertions(+), 373 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index aa0a3d9abd..d966279e51 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -71,12 +71,6 @@ var EQUIP_SPHERE_SCALE_FACTOR = 0.65; var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; var WEB_STYLUS_LENGTH = 0.2; var WEB_TOUCH_Y_OFFSET = 0.05; // how far forward (or back with a negative number) to slide stylus in hand -var WEB_TOUCH_TOO_CLOSE = 0.03; // if the stylus is pushed far though the web surface, don't consider it touching -var WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE = 0.01; - -var FINGER_TOUCH_Y_OFFSET = -0.02; -var FINGER_TOUCH_MIN = -0.01 - FINGER_TOUCH_Y_OFFSET; -var FINGER_TOUCH_MAX = 0.01 - FINGER_TOUCH_Y_OFFSET; // // distant manipulation @@ -137,7 +131,6 @@ var GRAB_POINT_SPHERE_ALPHA = 0.85; // // other constants // - var RIGHT_HAND = 1; var LEFT_HAND = 0; @@ -214,10 +207,9 @@ var STATE_NEAR_GRABBING = 4; var STATE_NEAR_TRIGGER = 5; var STATE_FAR_TRIGGER = 6; var STATE_HOLD = 7; -var STATE_ENTITY_STYLUS_TOUCHING = 8; -var STATE_ENTITY_LASER_TOUCHING = 9; -var STATE_OVERLAY_STYLUS_TOUCHING = 10; -var STATE_OVERLAY_LASER_TOUCHING = 11; +var STATE_ENTITY_LASER_TOUCHING = 8; +var STATE_OVERLAY_LASER_TOUCHING = 9; +var STATE_STYLUS_TOUCHING = 10; var CONTROLLER_STATE_MACHINE = {}; @@ -261,29 +253,23 @@ CONTROLLER_STATE_MACHINE[STATE_FAR_TRIGGER] = { enterMethod: "farTriggerEnter", updateMethod: "farTrigger" }; -CONTROLLER_STATE_MACHINE[STATE_ENTITY_STYLUS_TOUCHING] = { - name: "entityStylusTouching", - enterMethod: "entityTouchingEnter", - exitMethod: "entityTouchingExit", - updateMethod: "entityTouching" -}; CONTROLLER_STATE_MACHINE[STATE_ENTITY_LASER_TOUCHING] = { name: "entityLaserTouching", - enterMethod: "entityTouchingEnter", - exitMethod: "entityTouchingExit", - updateMethod: "entityTouching" -}; -CONTROLLER_STATE_MACHINE[STATE_OVERLAY_STYLUS_TOUCHING] = { - name: "overlayStylusTouching", - enterMethod: "overlayTouchingEnter", - exitMethod: "overlayTouchingExit", - updateMethod: "overlayTouching" + enterMethod: "entityLaserTouchingEnter", + exitMethod: "entityLaserTouchingExit", + updateMethod: "entityLaserTouching" }; CONTROLLER_STATE_MACHINE[STATE_OVERLAY_LASER_TOUCHING] = { name: "overlayLaserTouching", - enterMethod: "overlayTouchingEnter", - exitMethod: "overlayTouchingExit", - updateMethod: "overlayTouching" + enterMethod: "overlayLaserTouchingEnter", + exitMethod: "overlayLaserTouchingExit", + updateMethod: "overlayLaserTouching" +}; +CONTROLLER_STATE_MACHINE[STATE_STYLUS_TOUCHING] = { + name: "stylusTouching", + enterMethod: "stylusTouchingEnter", + exitMethod: "stylusTouchingExit", + updateMethod: "stylusTouching" }; function getFingerWorldLocation(hand) { @@ -295,13 +281,8 @@ function getFingerWorldLocation(hand) { var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); - // local y offset. - var localYOffset = Vec3.multiplyQbyV(worldFingerRotation, {x: 0, y: FINGER_TOUCH_Y_OFFSET, z: 0}); - - var offsetWorldFingerPosition = Vec3.sum(worldFingerPosition, localYOffset); - return { - position: offsetWorldFingerPosition, + position: worldFingerPosition, orientation: worldFingerRotation, rotation: worldFingerRotation, valid: true @@ -567,6 +548,122 @@ function restore2DMode() { } } +function stylusTargetHasKeyboardFocus(stylusTarget) { + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + return Entities.keyboardFocusEntity === stylusTarget.entityID; + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + return Overlays.keyboardFocusOverlay === stylusTarget.overlayID; + } +} + +function setKeyboardFocusOnStylusTarget(stylusTarget) { + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID && Entities.wantsHandControllerPointerEvents(stylusTarget.entityID)) { + Overlays.keyboardFocusOverlay = NULL_UUID; + Entities.keyboardFocusEntity = stylusTarget.entityID; + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.keyboardFocusOverlay = stylusTarget.overlayID; + Entities.keyboardFocusEntity = NULL_UUID; + } +} + +function sendHoverEnterEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "None" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendHoverEnterEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendHoverEnterOverlay(stylusTarget.overlayID, pointerEvent); + } +} + +function sendHoverOverEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "None" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoverOverEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + Overlays.sendHoverOverOverlay(stylusTarget.overlayID, pointerEvent); + } +} + +function sendTouchStartEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Press", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMousePressOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendClickDownOnEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMousePressOnOverlay(stylusTarget.overlayID, pointerEvent); + } +} + +function sendTouchEndEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Release", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary" + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseReleaseOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendClickReleaseOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoverLeaveEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(stylusTarget.overlayID, pointerEvent); + } +} + +function sendTouchMoveEventToStylusTarget(hand, stylusTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: stylusTarget.position2D, + pos3D: stylusTarget.position, + normal: stylusTarget.normal, + direction: Vec3.subtract(ZERO_VEC, stylusTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (stylusTarget.entityID && stylusTarget.entityID !== NULL_UUID) { + Entities.sendMouseMoveOnEntity(stylusTarget.entityID, pointerEvent); + Entities.sendHoldingClickOnEntity(stylusTarget.entityID, pointerEvent); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(stylusTarget.overlayID, pointerEvent); + } +} + // EntityPropertiesCache is a helper class that contains a cache of entity properties. // the hope is to prevent excess calls to Entity.getEntityProperties() // @@ -888,6 +985,16 @@ function MyController(hand) { this.useFingerInsteadOfStylus = false; + // initialize stylus tip + var DEFAULT_STYLUS_TIP = { + position: {x: 0, y: 0, z: 0}, + orientation: {x: 0, y: 0, z: 0, w: 0}, + rotation: {x: 0, y: 0, z: 0, w: 0}, + velocity: {x: 0, y: 0, z: 0}, + valid: false + }; + this.stylusTip = DEFAULT_STYLUS_TIP; + var _this = this; var suppressedIn2D = [STATE_OFF, STATE_SEARCHING]; @@ -897,10 +1004,36 @@ function MyController(hand) { return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; + this.updateStylusTip = function() { + if (this.useFingerInsteadOfStylus) { + this.stylusTip = getFingerWorldLocation(this.hand); + } else { + this.stylusTip = getControllerWorldLocation(this.handToController(), true); + + // translate tip forward according to constant. + var TIP_OFFSET = {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0}; + this.stylusTip.position = Vec3.sum(this.stylusTip.position, Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET)); + } + + // compute tip velocity from hand controller motion, it is more accurate then computing it from previous positions. + var pose = Controller.getPoseValue(this.handToController()); + if (pose.valid) { + var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation)); + var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); + var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity); + var tipVelocity = Vec3.sum(worldControllerLinearVel, Vec3.cross(worldControllerAngularVel, Vec3.subtract(this.stylusTip.position, worldControllerPos))); + this.stylusTip.velocity = tipVelocity; + } else { + this.stylusTip.velocity = {x: 0, y: 0, z: 0}; + } + }; + this.update = function(deltaTime, timestamp) { this.updateSmoothedTrigger(); this.maybeScaleMyAvatar(); + this.updateStylusTip(); + var DEFAULT_USE_FINGER_AS_STYLUS = false; var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus"); if (USE_FINGER_AS_STYLUS === "") { @@ -916,10 +1049,7 @@ function MyController(hand) { // Most hand input is disabled, because we are interacting with the 2d hud. // However, we still should check for collisions of the stylus with the web overlay. - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - this.processStylus(controllerLocation.position); - + this.processStylus(); this.turnOffVisualizations(); return; } @@ -949,7 +1079,7 @@ function MyController(hand) { if ((isInEditMode() && this.grabbedThingID !== HMD.tabletID) && (newState !== STATE_OFF && newState !== STATE_SEARCHING && - newState !== STATE_OVERLAY_STYLUS_TOUCHING && + newState !== STATE_STYLUS_TOUCHING && newState !== STATE_OVERLAY_LASER_TOUCHING)) { return; } @@ -1084,10 +1214,6 @@ function MyController(hand) { } Overlays.deleteOverlay(this.stylus); this.stylus = null; - if (this.stylusTip) { - Overlays.deleteOverlay(this.stylusTip); - this.stylusTip = null; - } }; this.overlayLineOn = function(closePoint, farPoint, color, farParentID) { @@ -1291,66 +1417,250 @@ function MyController(hand) { return _this.rawThumbValue < THUMB_ON_VALUE; }; - this.processStylus = function(worldHandPosition) { + // returns object with the following fields + // * entityID - if non null, this entityID is the closest to the stylusTip. + // * overlayID - if non null, this overlayID is the closest to the stylusTip. + // * distance - distance in meters from the stylus to the surface of the stylusTarget. + // * position - position on the surface of the stylusTarget that is nearest to the stylusTip. (world space) + // * position2D - postion on surface of the stylusTarget ready for use for pointerEvent.pos2D + // * normal - normal vector of the surface. (world space) + // * valid - if false, all other fields are invalid. + this.calculateNearestStylusTargetFromCandidates = function (candidates, cullSide) { - var performRayTest = false; - if (this.useFingerInsteadOfStylus) { - this.hideStylus(); - performRayTest = true; - } else { - var i; + // now attempt to find the nearest stylusTarget. - // see if the hand is near a tablet or web-entity - var candidateEntities = Entities.findEntities(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); - entityPropertiesCache.addEntities(candidateEntities); - for (i = 0; i < candidateEntities.length; i++) { - var props = entityPropertiesCache.getProps(candidateEntities[i]); - if (props && (props.type == "Web" || this.isTablet(candidateEntities[i]))) { - performRayTest = true; - break; + var nearestStylusTarget = { + entityID: null, + overlayID: null, + distance: WEB_DISPLAY_STYLUS_DISTANCE, + position: {x: 0, y: 0, z: 0}, + normalizedPosition: {x: 0, y: 0, z: 0}, + normal: {x: 0, y: 0, z: 1}, + valid: false + }; + + if (candidates.entities.length > 0 || candidates.overlays.length > 0) { + var i, props, entityID, normal, distance, position, invRot, localPos, invDimensions, normalizedPos, position2D; + for (i = 0; i < candidates.entities.length; i++) { + entityID = candidates.entities[i]; + props = entityPropertiesCache.getProps(entityID); + + // entity could have been deleted. + if (props === undefined) { + continue; } - } - if (!performRayTest) { - var candidateOverlays = Overlays.findOverlays(worldHandPosition, WEB_DISPLAY_STYLUS_DISTANCE); - for (i = 0; i < candidateOverlays.length; i++) { - if (this.isTablet(candidateOverlays[i])) { - performRayTest = true; - break; + normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + distance = Vec3.dot(Vec3.subtract(this.stylusTip.position, props.position), normal); + position = Vec3.subtract(this.stylusTip.position, Vec3.multiply(normal, distance)); + + if (distance < nearestStylusTarget.distance) { + + invRot = Quat.inverse(props.rotation); + localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + if (normalizedPos.x >= 0 && normalizedPos.y >= 0 && normalizedPos.x <= 1 && normalizedPos.y <= 1) { + position2D = { x: normalizedPos.x * props.dimensions.x, y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis + nearestStylusTarget = { + entityID: entityID, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + valid: true + }; } } } - if (performRayTest) { - this.showStylus(); - } else { - this.hideStylus(); + for (i = 0; i < candidates.overlays.length; i++) { + var overlayID = candidates.overlays[i]; + var overlayPosition = Overlays.getProperty(overlayID, "position"); + + // overlay could have been deleted. + if (overlayPosition === undefined) { + continue; + } + + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); + distance = Vec3.dot(Vec3.subtract(this.stylusTip.position, overlayPosition), normal); + position = Vec3.subtract(this.stylusTip.position, Vec3.multiply(normal, distance)); + + if (distance < nearestStylusTarget.distance) { + + invRot = Quat.inverse(overlayRotation); + localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + + var dpi = Overlays.getProperty(overlayID, "dpi"); + var dimensions; + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + } + } + + invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; + normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); + + if (!cullSide || (normalizedPos.x >= 0 && normalizedPos.y >= 0 && normalizedPos.x <= 1 && normalizedPos.y <= 1)) { + position2D = { x: normalizedPos.x * dimensions.x, y: (1 - normalizedPos.y) * dimensions.y }; // flip y-axis + nearestStylusTarget = { + entityID: null, + overlayID: overlayID, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + valid: true + }; + } + } } } - if (performRayTest) { - var rayPickInfo = this.calcRayPickInfo(this.hand, this.useFingerInsteadOfStylus); - var max, min; - if (this.useFingerInsteadOfStylus) { - max = FINGER_TOUCH_MAX; - min = FINGER_TOUCH_MIN; - } else { - max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET; - min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE; - } + return nearestStylusTarget; + }; - if (rayPickInfo.distance < max && rayPickInfo.distance > min) { - this.handleStylusOnHomeButton(rayPickInfo); - if (this.handleStylusOnWebEntity(rayPickInfo)) { - return; - } - if (this.handleStylusOnWebOverlay(rayPickInfo)) { - return; - } - } else { - this.homeButtonTouched = false; + this.stealTouchFocus = function(stylusTarget) { + // send hover events to target + // record the entity or overlay we are hovering over. + if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) || + (stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) { + this.getOtherHandController().relinquishTouchFocus(); + } + this.requestTouchFocus(stylusTarget); + }; + + this.requestTouchFocus = function(stylusTarget) { + + // send hover events to target if we can. + // record the entity or overlay we are hovering over. + if (stylusTarget.entityID && stylusTarget.entityID !== this.hoverEntity && stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { + this.hoverEntity = stylusTarget.entityID; + sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + } else if (stylusTarget.overlayID && stylusTarget.overlayID !== this.hoverOverlay && stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { + this.hoverOverlay = stylusTarget.overlayID; + sendHoverEnterEventToStylusTarget(this.hand, stylusTarget); + } + }; + + this.hasTouchFocus = function(stylusTarget) { + return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) || + (stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay)); + }; + + this.relinquishTouchFocus = function() { + + // send hover leave event. + var pointerEvent = { type: "Move", id: this.hand + 1 }; + if (this.hoverEntity) { + Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); + this.hoverEntity = null; + } else if (this.hoverOverlay) { + Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); + this.hoverOverlay = null; + } + }; + + this.processStylus = function() { + if (!this.stylusTip.valid) { + this.hideStylus(); + return; + } + + if (this.useFingerInsteadOfStylus) { + this.hideStylus(); + } + + var tipPosition = this.stylusTip.position; + + var candidates = { + entities: [], + overlays: [] + }; + + // find web-entites near the stylusTip. + var candidateEntities = Entities.findEntities(tipPosition, WEB_DISPLAY_STYLUS_DISTANCE); + entityPropertiesCache.addEntities(candidateEntities); + var i, props; + for (i = 0; i < candidateEntities.length; i++) { + props = entityPropertiesCache.getProps(candidateEntities[i]); + if (props && (props.type === "Web" || this.isTablet(candidateEntities[i]))) { + candidates.entities.push(candidateEntities[i]); } } + + // add the tabletScreen, if it is valid + if (HMD.tabletScreenID && HMD.tabletScreenID !== NULL_UUID) { + candidates.overlays.push(HMD.tabletScreenID); + } + + // add the tablet home button. + if (HMD.homeButtonID && HMD.homeButtonID !== NULL_UUID) { + candidates.overlays.push(HMD.homeButtonID); + } + + var nearestStylusTarget = this.calculateNearestStylusTargetFromCandidates(candidates, true); + + if (!this.useFingerInsteadOfStylus && nearestStylusTarget.valid) { + this.showStylus(); + } else { + this.hideStylus(); + } + + var TABLET_MIN_HOVER_DISTANCE = 0.01; + var TABLET_MAX_HOVER_DISTANCE = 0.1; + var TABLET_MIN_TOUCH_DISTANCE = -0.05; + var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; + + if (nearestStylusTarget.valid && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + nearestStylusTarget.distance < TABLET_MAX_HOVER_DISTANCE) { + + this.requestTouchFocus(nearestStylusTarget); + + if (!stylusTargetHasKeyboardFocus(nearestStylusTarget)) { + setKeyboardFocusOnStylusTarget(nearestStylusTarget); + } + + if (this.hasTouchFocus(nearestStylusTarget)) { + sendHoverOverEventToStylusTarget(this.hand, nearestStylusTarget); + } + + // filter out presses when tip is moving away from tablet. + if (nearestStylusTarget.valid && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + nearestStylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE && Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0) { + + var name; + if (nearestStylusTarget.entityID) { + name = entityPropertiesCache.getProps(nearestStylusTarget.entityID).name; + this.stylusTarget = nearestStylusTarget; + this.setState(STATE_STYLUS_TOUCHING, "begin touching entity '" + name + "'"); + } else if (nearestStylusTarget.overlayID) { + name = Overlays.getProperty(nearestStylusTarget.overlayID, "name"); + this.stylusTarget = nearestStylusTarget; + this.setState(STATE_STYLUS_TOUCHING, "begin touching overlay '" + name + "'"); + } + } + } else { + this.relinquishTouchFocus(); + } + + this.homeButtonTouched = false; }; this.off = function(deltaTime, timestamp) { @@ -1406,25 +1716,7 @@ function MyController(hand) { this.grabPointSphereOff(); } - this.processStylus(worldHandPosition); - }; - - this.handleStylusOnHomeButton = function(rayPickInfo) { - if (rayPickInfo.overlayID) { - var homeButton = rayPickInfo.overlayID; - var hmdHomeButton = HMD.homeButtonID; - if (homeButton === hmdHomeButton) { - if (this.homeButtonTouched === false) { - this.homeButtonTouched = true; - Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); - Messages.sendLocalMessage("home", homeButton); - } - } else { - this.homeButtonTouched = false; - } - } else { - this.homeButtonTouched = false; - } + this.processStylus(); }; this.handleLaserOnHomeButton = function(rayPickInfo) { @@ -1464,27 +1756,27 @@ function MyController(hand) { // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND - // @param {bool} if true use the world position/orientation of the index finger to cast the ray from. + // @param {object} if set, use this as as the pick ray, expects origin, direction, and length fields. // @returns {object} returns object with two keys entityID and distance // - this.calcRayPickInfo = function(hand, useFingerInsteadOfController) { + this.calcRayPickInfo = function(hand, pickRayOverride) { - var controllerLocation; - if (useFingerInsteadOfController) { - controllerLocation = getFingerWorldLocation(hand); + var pickRay; + if (pickRayOverride) { + pickRay = pickRayOverride; } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } - var worldHandPosition = controllerLocation.position; - var worldHandRotation = controllerLocation.orientation; + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; - var pickRay = { - origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), - Quat.getFront(Camera.orientation), - HAND_HEAD_MIX_RATIO), - length: PICK_MAX_DISTANCE - }; + pickRay = { + origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, + direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), + Quat.getFront(Camera.orientation), + HAND_HEAD_MIX_RATIO), + length: PICK_MAX_DISTANCE + }; + } var result = { entityID: null, @@ -1943,134 +2235,6 @@ function MyController(hand) { return false; }; - this.handleStylusOnWebEntity = function (rayPickInfo) { - var pointerEvent; - - if (rayPickInfo.entityID && Entities.wantsHandControllerPointerEvents(rayPickInfo.entityID)) { - var entity = rayPickInfo.entityID; - var name = entityPropertiesCache.getProps(entity).name; - - if (Entities.keyboardFocusEntity != entity) { - Overlays.keyboardFocusOverlay = 0; - Entities.keyboardFocusEntity = entity; - - pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - this.hoverEntity = entity; - Entities.sendHoverEnterEntity(entity, pointerEvent); - } - - // send mouse events for button highlights and tooltips. - if (this.hand == mostRecentSearchingHand || - (this.hand !== mostRecentSearchingHand && - this.getOtherHandController().state !== STATE_SEARCHING && - this.getOtherHandController().state !== STATE_ENTITY_STYLUS_TOUCHING && - this.getOtherHandController().state !== STATE_ENTITY_LASER_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_STYLUS_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_LASER_TOUCHING)) { - - // most recently searching hand has priority over other hand, for the purposes of button highlighting. - pointerEvent = { - type: "Move", - id: this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(entity, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - Entities.sendMouseMoveOnEntity(entity, pointerEvent); - Entities.sendHoverOverEntity(entity, pointerEvent); - } - - this.grabbedThingID = entity; - this.grabbedIsOverlay = false; - this.setState(STATE_ENTITY_STYLUS_TOUCHING, "begin touching entity '" + name + "'"); - return true; - - } else if (this.hoverEntity) { - pointerEvent = { - type: "Move", - id: this.hand + 1 - }; - Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); - this.hoverEntity = null; - } - - return false; - }; - - this.handleStylusOnWebOverlay = function (rayPickInfo) { - var pointerEvent; - if (rayPickInfo.overlayID) { - var overlay = rayPickInfo.overlayID; - if (Overlays.keyboardFocusOverlay != overlay) { - Entities.keyboardFocusEntity = null; - Overlays.keyboardFocusOverlay = overlay; - - pointerEvent = { - type: "Move", - id: HARDWARE_MOUSE_ID, - pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - this.hoverOverlay = overlay; - Overlays.sendHoverEnterOverlay(overlay, pointerEvent); - } - - // Send mouse events for button highlights and tooltips. - if (this.hand == mostRecentSearchingHand || - (this.hand !== mostRecentSearchingHand && - this.getOtherHandController().state !== STATE_SEARCHING && - this.getOtherHandController().state !== STATE_ENTITY_STYLUS_TOUCHING && - this.getOtherHandController().state !== STATE_ENTITY_LASER_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_STYLUS_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_LASER_TOUCHING)) { - - // most recently searching hand has priority over other hand, for the purposes of button highlighting. - pointerEvent = { - type: "Move", - id: HARDWARE_MOUSE_ID, - pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.normal, - direction: rayPickInfo.searchRay.direction, - button: "None" - }; - - Overlays.sendMouseMoveOnOverlay(overlay, pointerEvent); - Overlays.sendHoverOverOverlay(overlay, pointerEvent); - } - - this.grabbedOverlay = overlay; - this.setState(STATE_OVERLAY_STYLUS_TOUCHING, "begin touching overlay '" + overlay + "'"); - return true; - - } else if (this.hoverOverlay) { - pointerEvent = { - type: "Move", - id: HARDWARE_MOUSE_ID - }; - Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); - this.hoverOverlay = null; - } - - return false; - }; - this.handleLaserOnWebEntity = function(rayPickInfo) { var pointerEvent; if (rayPickInfo.entityID && Entities.wantsHandControllerPointerEvents(rayPickInfo.entityID)) { @@ -2100,9 +2264,8 @@ function MyController(hand) { if (this.hand == mostRecentSearchingHand || (this.hand !== mostRecentSearchingHand && this.getOtherHandController().state !== STATE_SEARCHING && - this.getOtherHandController().state !== STATE_ENTITY_STYLUS_TOUCHING && + this.getOtherHandController().state !== STATE_STYLUS_TOUCHING && this.getOtherHandController().state !== STATE_ENTITY_LASER_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_STYLUS_TOUCHING && this.getOtherHandController().state !== STATE_OVERLAY_LASER_TOUCHING)) { // most recently searching hand has priority over other hand, for the purposes of button highlighting. @@ -2167,9 +2330,8 @@ function MyController(hand) { if (this.hand == mostRecentSearchingHand || (this.hand !== mostRecentSearchingHand && this.getOtherHandController().state !== STATE_SEARCHING && - this.getOtherHandController().state !== STATE_ENTITY_STYLUS_TOUCHING && + this.getOtherHandController().state !== STATE_STYLUS_TOUCHING && this.getOtherHandController().state !== STATE_ENTITY_LASER_TOUCHING && - this.getOtherHandController().state !== STATE_OVERLAY_STYLUS_TOUCHING && this.getOtherHandController().state !== STATE_OVERLAY_LASER_TOUCHING)) { // most recently searching hand has priority over other hand, for the purposes of button highlighting. @@ -2699,10 +2861,6 @@ function MyController(hand) { isClone = true; if (count > limit) { - delete cloneableProps; - delete lifetime; - delete cUserData; - delete cProperties; return; } @@ -3096,14 +3254,9 @@ function MyController(hand) { this.release(); }; - this.entityTouchingEnter = function() { + this.entityLaserTouchingEnter = function() { // test for intersection between controller laser and web entity plane. - var controllerLocation; - if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { - controllerLocation = getFingerWorldLocation(this.hand); - } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { var pointerEvent = { @@ -3126,26 +3279,15 @@ function MyController(hand) { this.deadspotExpired = false; var LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.026; // radians ~ 1.2 degrees - var STYLUS_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.314; // radians ~ 18 degrees - var theta = this.state === STATE_ENTITY_STYLUS_TOUCHING ? STYLUS_PRESS_TO_MOVE_DEADSPOT_ANGLE : LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE; - this.deadspotRadius = Math.tan(theta) * intersectInfo.distance; // dead spot radius in meters + this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE) * intersectInfo.distance; // dead spot radius in meters } - if (this.state == STATE_ENTITY_STYLUS_TOUCHING) { - Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); - } else if (this.state == STATE_ENTITY_LASER_TOUCHING) { - Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); - } + Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); }; - this.entityTouchingExit = function() { + this.entityLaserTouchingExit = function() { // test for intersection between controller laser and web entity plane. - var controllerLocation; - if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { - controllerLocation = getFingerWorldLocation(this.hand); - } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { var pointerEvent; @@ -3174,7 +3316,7 @@ function MyController(hand) { this.grabbedOverlay = null; }; - this.entityTouching = function(dt) { + this.entityLaserTouching = function(dt) { this.touchingEnterTimer += dt; @@ -3186,28 +3328,10 @@ function MyController(hand) { } // test for intersection between controller laser and web entity plane. - var controllerLocation; - if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { - controllerLocation = getFingerWorldLocation(this.hand); - } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var intersectInfo = handLaserIntersectEntity(this.grabbedThingID, controllerLocation); if (intersectInfo) { - var max; - if (this.useFingerInsteadOfStylus && this.state === STATE_ENTITY_STYLUS_TOUCHING) { - max = FINGER_TOUCH_MAX; - } else { - max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET; - } - - if (this.state == STATE_ENTITY_STYLUS_TOUCHING && - intersectInfo.distance > max) { - this.setState(STATE_OFF, "pulled away from web entity"); - return; - } - if (Entities.keyboardFocusEntity != this.grabbedThingID) { Overlays.keyboardFocusOverlay = 0; Entities.keyboardFocusEntity = this.grabbedThingID; @@ -3244,14 +3368,9 @@ function MyController(hand) { } }; - this.overlayTouchingEnter = function () { + this.overlayLaserTouchingEnter = function () { // Test for intersection between controller laser and Web overlay plane. - var controllerLocation; - if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { - controllerLocation = getFingerWorldLocation(this.hand); - } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); if (intersectInfo) { var pointerEvent = { @@ -3273,26 +3392,15 @@ function MyController(hand) { this.deadspotExpired = false; var LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.026; // radians ~ 1.2 degrees - var STYLUS_PRESS_TO_MOVE_DEADSPOT_ANGLE = 0.314; // radians ~ 18 degrees - var theta = this.state === STATE_OVERLAY_STYLUS_TOUCHING ? STYLUS_PRESS_TO_MOVE_DEADSPOT_ANGLE : LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE; - this.deadspotRadius = Math.tan(theta) * intersectInfo.distance; // dead spot radius in meters + this.deadspotRadius = Math.tan(LASER_PRESS_TO_MOVE_DEADSPOT_ANGLE) * intersectInfo.distance; // dead spot radius in meters } - if (this.state == STATE_OVERLAY_STYLUS_TOUCHING) { - Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); - } else if (this.state == STATE_OVERLAY_LASER_TOUCHING) { - Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); - } + Controller.triggerHapticPulse(HAPTIC_LASER_UI_STRENGTH, HAPTIC_LASER_UI_DURATION, this.hand); }; - this.overlayTouchingExit = function () { + this.overlayLaserTouchingExit = function () { // Test for intersection between controller laser and Web overlay plane. - var controllerLocation; - if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { - controllerLocation = getFingerWorldLocation(this.hand); - } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); if (intersectInfo) { var pointerEvent; @@ -3337,66 +3445,22 @@ function MyController(hand) { this.grabbedOverlay = null; }; - this.overlayTouching = function (dt) { + this.overlayLaserTouching = function (dt) { this.touchingEnterTimer += dt; - if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && this.triggerSmoothedSqueezed()) { - return; - } - if (this.state == STATE_OVERLAY_LASER_TOUCHING && !this.triggerSmoothedGrab()) { this.setState(STATE_OFF, "released trigger"); return; } // Test for intersection between controller laser and Web overlay plane. - var controllerLocation; - if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { - controllerLocation = getFingerWorldLocation(this.hand); - } else { - controllerLocation = getControllerWorldLocation(this.handToController(), true); - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation); if (intersectInfo) { - var max, min; - if (this.useFingerInsteadOfStylus && this.state === STATE_OVERLAY_STYLUS_TOUCHING) { - max = FINGER_TOUCH_MAX; - min = FINGER_TOUCH_MIN; - } else { - max = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_Y_OFFSET + WEB_TOUCH_Y_TOUCH_DEADZONE_SIZE; - min = WEB_STYLUS_LENGTH / 2.0 + WEB_TOUCH_TOO_CLOSE; - } - - if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && intersectInfo.distance > max) { - this.grabbedThingID = null; - this.setState(STATE_OFF, "pulled away from overlay"); - return; - } - var pos2D = projectOntoOverlayXYPlane(this.grabbedOverlay, intersectInfo.point); var pos3D = intersectInfo.point; - if (this.state == STATE_OVERLAY_STYLUS_TOUCHING && - !this.tabletStabbed && - intersectInfo.distance < min) { - // they've stabbed the tablet, don't send events until they pull back - this.tabletStabbed = true; - this.tabletStabbedPos2D = pos2D; - this.tabletStabbedPos3D = pos3D; - return; - } - - if (this.tabletStabbed) { - var origin = {x: this.tabletStabbedPos2D.x, y: this.tabletStabbedPos2D.y, z: 0}; - var point = {x: pos2D.x, y: pos2D.y, z: 0}; - var offset = Vec3.distance(origin, point); - var radius = 0.05; - if (offset < radius) { - return; - } - } - if (Overlays.keyboardFocusOverlay != this.grabbedOverlay) { Entities.keyboardFocusEntity = null; Overlays.keyboardFocusOverlay = this.grabbedOverlay; @@ -3432,6 +3496,61 @@ function MyController(hand) { } }; + this.stylusTouchingEnter = function () { + this.stealTouchFocus(this.stylusTarget); + sendTouchStartEventToStylusTarget(this.hand, this.stylusTarget); + Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); + + this.touchingEnterTimer = 0; + this.touchingEnterStylusTarget = this.stylusTarget; + this.deadspotExpired = false; + + var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.025; + this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; // meters + }; + + this.stylusTouchingExit = function () { + + // special case to handle home button. + if (this.stylusTarget.overlayID === HMD.homeButtonID) { + Messages.sendLocalMessage("home", this.stylusTarget.overlayID); + } + + // send press event + if (this.deadspotExpired) { + sendTouchEndEventToStylusTarget(this.hand, this.stylusTarget); + } else { + sendTouchEndEventToStylusTarget(this.hand, this.touchingEnterStylusTarget); + } + }; + + this.stylusTouching = function (dt) { + + this.touchingEnterTimer += dt; + + var candidates = { entities: [], overlays: [] }; + if (this.stylusTarget.entityID && this.stylusTarget.entityID !== NULL_UUID) { + candidates.entities.push(this.stylusTarget.entityID); + } else if (this.stylusTarget.overlayID && this.stylusTarget.overlayID !== NULL_UUID) { + candidates.overlays.push(this.stylusTarget.overlayID); + } + this.stylusTarget = this.calculateNearestStylusTargetFromCandidates(candidates, false); + + var TABLET_MIN_TOUCH_DISTANCE = -0.1; + var TABLET_MAX_TOUCH_DISTANCE = 0.01; + + if (this.stylusTarget.valid && this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { + + var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + Vec3.distance(this.stylusTarget.position, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { + sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); + } + } else { + this.setState(STATE_OFF, "hand moved away from tablet"); + } + }; + this.release = function() { this.turnOffVisualizations(); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index dd2aaf346b..8859b13624 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -167,11 +167,10 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { isAA: HMD.active }); - var HOME_BUTTON_Y_OFFSET = (this.height / 2) - 0.009; + var HOME_BUTTON_Y_OFFSET = (this.height / 2) - (this.height / 20); this.homeButtonID = Overlays.addOverlay("sphere", { name: "homeButton", localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - localRotation: Quat.angleAxis(0, Y_AXIS), dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, alpha: 0.0, visible: true, From 1acc5ea760e5fe1d8a4eafad055e3620a8f998f5 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 8 Mar 2017 19:03:56 -0800 Subject: [PATCH 5/6] Finger now points when near tablet + deadspot added when touching the tablet --- scripts/defaultScripts.js | 2 +- .../system/controllers/handControllerGrab.js | 341 +++++++++++------- scripts/system/controllers/squeezeHands.js | 84 ++++- scripts/system/fingerPaint.js | 14 +- scripts/system/libraries/WebTablet.js | 1 + 5 files changed, 281 insertions(+), 161 deletions(-) diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 5d8813e988..27efab134a 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -27,10 +27,10 @@ var DEFAULT_SCRIPTS = [ "system/tablet-users.js", "system/selectAudioDevice.js", "system/notifications.js", + "system/controllers/squeezeHands.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrab.js", "system/controllers/handControllerPointer.js", - "system/controllers/squeezeHands.js", "system/controllers/grab.js", "system/controllers/teleport.js", "system/controllers/toggleAdvancedMovementForHandControllers.js", diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index d966279e51..abd0484ec4 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -272,6 +272,12 @@ CONTROLLER_STATE_MACHINE[STATE_STYLUS_TOUCHING] = { updateMethod: "stylusTouching" }; +function distance2D(a, b) { + var dx = (a.x - b.x); + var dy = (a.y - b.y); + return Math.sqrt(dx * dx + dy * dy); +} + function getFingerWorldLocation(hand) { var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; @@ -664,6 +670,125 @@ function sendTouchMoveEventToStylusTarget(hand, stylusTarget) { } } +function calculateStylusTargetFromEntity(stylusTip, entityID) { + var props = entityPropertiesCache.getProps(entityID); + + // entity could have been deleted. + if (props === undefined) { + return undefined; + } + + // project stylus tip onto entity plane. + var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + var distance = Vec3.dot(Vec3.subtract(stylusTip.position, props.position), normal); + var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + + // generate normalized coordinates + var invRot = Quat.inverse(props.rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * props.dimensions.x, y: (1 - normalizedPosition.y) * props.dimensions.y }; // flip y-axis + + return { + entityID: entityID, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.dimensions, + valid: true + }; +} + +function calculateStylusTargetFromOverlay(stylusTip, overlayID) { + var overlayPosition = Overlays.getProperty(overlayID, "position"); + + // overlay could have been deleted. + if (overlayPosition === undefined) { + return undefined; + } + + // project stylusTip onto overlay plane. + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); + var distance = Vec3.dot(Vec3.subtract(stylusTip.position, overlayPosition), normal); + var position = Vec3.subtract(stylusTip.position, Vec3.multiply(normal, distance)); + + // calclulate normalized position + var invRot = Quat.inverse(overlayRotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + var dpi = Overlays.getProperty(overlayID, "dpi"); + var dimensions; + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + } + } + var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); + + // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { x: normalizedPosition.x * dimensions.x, y: (1 - normalizedPosition.y) * dimensions.y }; // flip y-axis + + return { + entityID: null, + overlayID: overlayID, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: dimensions, + valid: true + }; +} + +function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { + for (var i = 0; i < stylusTargets.length; i++) { + var stylusTarget = stylusTargets[i]; + + // check to see if the projected stylusTip is within within the 2d border + var borderMin = {x: -edgeBorder, y: -edgeBorder}; + var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder}; + if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance && + stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y && + stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) { + return true; + } + } + return false; +} + +function calculateNearestStylusTarget(stylusTargets) { + var nearestStylusTarget; + + for (var i = 0; i < stylusTargets.length; i++) { + var stylusTarget = stylusTargets[i]; + + if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) && + stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 && + stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) { + nearestStylusTarget = stylusTarget; + } + } + + return nearestStylusTarget; +}; + // EntityPropertiesCache is a helper class that contains a cache of entity properties. // the hope is to prevent excess calls to Entity.getEntityProperties() // @@ -984,6 +1109,7 @@ function MyController(hand) { this.tabletStabbedPos3D = null; this.useFingerInsteadOfStylus = false; + this.fingerPointing = false; // initialize stylus tip var DEFAULT_STYLUS_TIP = { @@ -1417,123 +1543,6 @@ function MyController(hand) { return _this.rawThumbValue < THUMB_ON_VALUE; }; - // returns object with the following fields - // * entityID - if non null, this entityID is the closest to the stylusTip. - // * overlayID - if non null, this overlayID is the closest to the stylusTip. - // * distance - distance in meters from the stylus to the surface of the stylusTarget. - // * position - position on the surface of the stylusTarget that is nearest to the stylusTip. (world space) - // * position2D - postion on surface of the stylusTarget ready for use for pointerEvent.pos2D - // * normal - normal vector of the surface. (world space) - // * valid - if false, all other fields are invalid. - this.calculateNearestStylusTargetFromCandidates = function (candidates, cullSide) { - - // now attempt to find the nearest stylusTarget. - - var nearestStylusTarget = { - entityID: null, - overlayID: null, - distance: WEB_DISPLAY_STYLUS_DISTANCE, - position: {x: 0, y: 0, z: 0}, - normalizedPosition: {x: 0, y: 0, z: 0}, - normal: {x: 0, y: 0, z: 1}, - valid: false - }; - - if (candidates.entities.length > 0 || candidates.overlays.length > 0) { - var i, props, entityID, normal, distance, position, invRot, localPos, invDimensions, normalizedPos, position2D; - for (i = 0; i < candidates.entities.length; i++) { - entityID = candidates.entities[i]; - props = entityPropertiesCache.getProps(entityID); - - // entity could have been deleted. - if (props === undefined) { - continue; - } - - normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); - Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); - distance = Vec3.dot(Vec3.subtract(this.stylusTip.position, props.position), normal); - position = Vec3.subtract(this.stylusTip.position, Vec3.multiply(normal, distance)); - - if (distance < nearestStylusTarget.distance) { - - invRot = Quat.inverse(props.rotation); - localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); - invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; - normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); - - if (normalizedPos.x >= 0 && normalizedPos.y >= 0 && normalizedPos.x <= 1 && normalizedPos.y <= 1) { - position2D = { x: normalizedPos.x * props.dimensions.x, y: (1 - normalizedPos.y) * props.dimensions.y }; // flip y-axis - nearestStylusTarget = { - entityID: entityID, - overlayID: null, - distance: distance, - position: position, - position2D: position2D, - normal: normal, - valid: true - }; - } - } - } - - for (i = 0; i < candidates.overlays.length; i++) { - var overlayID = candidates.overlays[i]; - var overlayPosition = Overlays.getProperty(overlayID, "position"); - - // overlay could have been deleted. - if (overlayPosition === undefined) { - continue; - } - - var overlayRotation = Overlays.getProperty(overlayID, "rotation"); - normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); - distance = Vec3.dot(Vec3.subtract(this.stylusTip.position, overlayPosition), normal); - position = Vec3.subtract(this.stylusTip.position, Vec3.multiply(normal, distance)); - - if (distance < nearestStylusTarget.distance) { - - invRot = Quat.inverse(overlayRotation); - localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); - - var dpi = Overlays.getProperty(overlayID, "dpi"); - var dimensions; - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. - } - } - - invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; - normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); - - if (!cullSide || (normalizedPos.x >= 0 && normalizedPos.y >= 0 && normalizedPos.x <= 1 && normalizedPos.y <= 1)) { - position2D = { x: normalizedPos.x * dimensions.x, y: (1 - normalizedPos.y) * dimensions.y }; // flip y-axis - nearestStylusTarget = { - entityID: null, - overlayID: overlayID, - distance: distance, - position: position, - position2D: position2D, - normal: normal, - valid: true - }; - } - } - } - } - - return nearestStylusTarget; - }; - this.stealTouchFocus = function(stylusTarget) { // send hover events to target // record the entity or overlay we are hovering over. @@ -1577,8 +1586,23 @@ function MyController(hand) { } }; + this.pointFinger = function(value) { + var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; + if (this.fingerPointing !== value) { + var message; + if (this.hand === RIGHT_HAND) { + message = { pointRightIndex: value }; + } else { + message = { pointLeftIndex: value }; + } + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true); + this.fingerPointing = value; + } + }; + this.processStylus = function() { if (!this.stylusTip.valid) { + this.pointFinger(false); this.hideStylus(); return; } @@ -1615,20 +1639,53 @@ function MyController(hand) { candidates.overlays.push(HMD.homeButtonID); } - var nearestStylusTarget = this.calculateNearestStylusTargetFromCandidates(candidates, true); + // build list of stylus targets + var stylusTargets = []; + if (candidates.entities.length > 0 || candidates.overlays.length > 0) { + var stylusTarget; + for (i = 0; i < candidates.entities.length; i++) { + stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, candidates.entities[i]); + if (stylusTarget) { + stylusTargets.push(stylusTarget); + } + } - if (!this.useFingerInsteadOfStylus && nearestStylusTarget.valid) { - this.showStylus(); - } else { - this.hideStylus(); + for (i = 0; i < candidates.overlays.length; i++) { + stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, candidates.overlays[i]); + if (stylusTarget) { + stylusTargets.push(stylusTarget); + } + } } var TABLET_MIN_HOVER_DISTANCE = 0.01; var TABLET_MAX_HOVER_DISTANCE = 0.1; var TABLET_MIN_TOUCH_DISTANCE = -0.05; var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; + var EDGE_BORDER = 0.075; - if (nearestStylusTarget.valid && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && + var hysteresisOffset = 0.0; + if (this.isNearStylusTarget) { + hysteresisOffset = 0.05; + } + + this.isNearStylusTarget = isNearStylusTarget(stylusTargets, EDGE_BORDER + hysteresisOffset, + TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset, WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset); + + if (this.isNearStylusTarget) { + if (!this.useFingerInsteadOfStylus) { + this.showStylus(); + } else { + this.pointFinger(true); + } + } else { + this.hideStylus(); + this.pointFinger(false); + } + + var nearestStylusTarget = calculateNearestStylusTarget(stylusTargets); + + if (nearestStylusTarget && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && nearestStylusTarget.distance < TABLET_MAX_HOVER_DISTANCE) { this.requestTouchFocus(nearestStylusTarget); @@ -1642,8 +1699,11 @@ function MyController(hand) { } // filter out presses when tip is moving away from tablet. + // ensure that stylus is within bounding box by checking normalizedPosition if (nearestStylusTarget.valid && nearestStylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && - nearestStylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE && Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0) { + nearestStylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE && Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0 && + nearestStylusTarget.normalizedPosition.x >= 0 && nearestStylusTarget.normalizedPosition.x <= 1 && + nearestStylusTarget.normalizedPosition.y >= 0 && nearestStylusTarget.normalizedPosition.y <= 1) { var name; if (nearestStylusTarget.entityID) { @@ -3505,8 +3565,8 @@ function MyController(hand) { this.touchingEnterStylusTarget = this.stylusTarget; this.deadspotExpired = false; - var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.025; - this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; // meters + var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0381; + this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; }; this.stylusTouchingExit = function () { @@ -3528,26 +3588,29 @@ function MyController(hand) { this.touchingEnterTimer += dt; - var candidates = { entities: [], overlays: [] }; - if (this.stylusTarget.entityID && this.stylusTarget.entityID !== NULL_UUID) { - candidates.entities.push(this.stylusTarget.entityID); - } else if (this.stylusTarget.overlayID && this.stylusTarget.overlayID !== NULL_UUID) { - candidates.overlays.push(this.stylusTarget.overlayID); + if (this.stylusTarget.entityID) { + entityPropertiesCache.addEntity(this.stylusTarget.entityID); + this.stylusTarget = calcualteStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityID); + } else if (this.stylusTarget.overlayID) { + this.stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); } - this.stylusTarget = this.calculateNearestStylusTargetFromCandidates(candidates, false); var TABLET_MIN_TOUCH_DISTANCE = -0.1; var TABLET_MAX_TOUCH_DISTANCE = 0.01; - if (this.stylusTarget.valid && this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { - - var POINTER_PRESS_TO_MOVE_DELAY = 0.25; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - Vec3.distance(this.stylusTarget.position, this.touchingEnterPointerEvent.pos3D) > this.deadspotRadius) { - sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); + if (this.stylusTarget) { + if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { + var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds + if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || + distance2D(this.stylusTarget.position2D, this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { + sendTouchMoveEventToStylusTarget(this.hand, this.stylusTarget); + this.deadspotExpired = true; + } + } else { + this.setState(STATE_OFF, "hand moved away from touch surface"); } } else { - this.setState(STATE_OFF, "hand moved away from tablet"); + this.setState(STATE_OFF, "touch surface was destroyed"); } }; diff --git a/scripts/system/controllers/squeezeHands.js b/scripts/system/controllers/squeezeHands.js index 75e6249dd6..c9de473e28 100644 --- a/scripts/system/controllers/squeezeHands.js +++ b/scripts/system/controllers/squeezeHands.js @@ -11,6 +11,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ (function() { // BEGIN LOCAL_SCOPE @@ -25,7 +26,11 @@ var OVERLAY_RAMP_RATE = 8.0; var animStateHandlerID; -var isBothIndexesPointing = false; +var leftIndexPointingOverride = 0; +var rightIndexPointingOverride = 0; +var leftThumbRaisedOverride = 0; +var rightThumbRaisedOverride = 0; + var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; var isLeftIndexPointing = false; @@ -53,7 +58,7 @@ function init() { "leftHandOverlayAlpha", "leftHandGraspAlpha", "rightHandOverlayAlpha", "rightHandGraspAlpha", "isLeftHandGrasp", "isLeftIndexPoint", "isLeftThumbRaise", "isLeftIndexPointAndThumbRaise", - "isRightHandGrasp", "isRightIndexPoint", "isRightThumbRaise", "isRightIndexPointAndThumbRaise", + "isRightHandGrasp", "isRightIndexPoint", "isRightThumbRaise", "isRightIndexPointAndThumbRaise" ] ); Messages.subscribe(HIFI_POINT_INDEX_MESSAGE_CHANNEL); @@ -66,21 +71,23 @@ function animStateHandler(props) { leftHandGraspAlpha: lastLeftTrigger, rightHandOverlayAlpha: rightHandOverlayAlpha, rightHandGraspAlpha: lastRightTrigger, - isLeftHandGrasp: !isBothIndexesPointing && !isLeftIndexPointing && !isLeftThumbRaised, - isLeftIndexPoint: (isBothIndexesPointing || isLeftIndexPointing) && !isLeftThumbRaised, - isLeftThumbRaise: !isBothIndexesPointing && !isLeftIndexPointing && isLeftThumbRaised, - isLeftIndexPointAndThumbRaise: (isBothIndexesPointing || isLeftIndexPointing) && isLeftThumbRaised, - isRightHandGrasp: !isBothIndexesPointing && !isRightIndexPointing && !isRightThumbRaised, - isRightIndexPoint: (isBothIndexesPointing || isRightIndexPointing) && !isRightThumbRaised, - isRightThumbRaise: !isBothIndexesPointing && !isRightIndexPointing && isRightThumbRaised, - isRightIndexPointAndThumbRaise: (isBothIndexesPointing || isRightIndexPointing) && isRightThumbRaised + + isLeftHandGrasp: !isLeftIndexPointing && !isLeftThumbRaised, + isLeftIndexPoint: isLeftIndexPointing && !isLeftThumbRaised, + isLeftThumbRaise: !isLeftIndexPointing && isLeftThumbRaised, + isLeftIndexPointAndThumbRaise: isLeftIndexPointing && isLeftThumbRaised, + + isRightHandGrasp: !isRightIndexPointing && !isRightThumbRaised, + isRightIndexPoint: isRightIndexPointing && !isRightThumbRaised, + isRightThumbRaise: !isRightIndexPointing && isRightThumbRaised, + isRightIndexPointAndThumbRaise: isRightIndexPointing && isRightThumbRaised }; } function update(dt) { var leftTrigger = clamp(Controller.getValue(Controller.Standard.LT) + Controller.getValue(Controller.Standard.LeftGrip), 0, 1); var rightTrigger = clamp(Controller.getValue(Controller.Standard.RT) + Controller.getValue(Controller.Standard.RightGrip), 0, 1); - + // Average last few trigger values together for a bit of smoothing var tau = clamp(dt / TRIGGER_SMOOTH_TIMESCALE, 0, 1); lastLeftTrigger = lerp(leftTrigger, lastLeftTrigger, tau); @@ -103,18 +110,61 @@ function update(dt) { } // Pointing index fingers and raising thumbs - isLeftIndexPointing = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftIndexPoint) === 1; - isRightIndexPointing = rightHandPose.valid && Controller.getValue(Controller.Standard.RightIndexPoint) === 1; - isLeftThumbRaised = leftHandPose.valid && Controller.getValue(Controller.Standard.LeftThumbUp) === 1; - isRightThumbRaised = rightHandPose.valid && Controller.getValue(Controller.Standard.RightThumbUp) === 1; + isLeftIndexPointing = (leftIndexPointingOverride > 0) || (leftHandPose.valid && Controller.getValue(Controller.Standard.LeftIndexPoint) === 1); + isRightIndexPointing = (rightIndexPointingOverride > 0) || (rightHandPose.valid && Controller.getValue(Controller.Standard.RightIndexPoint) === 1); + isLeftThumbRaised = (leftThumbRaisedOverride > 0) || (leftHandPose.valid && Controller.getValue(Controller.Standard.LeftThumbUp) === 1); + isRightThumbRaised = (rightThumbRaisedOverride > 0) || (rightHandPose.valid && Controller.getValue(Controller.Standard.RightThumbUp) === 1); } function handleMessages(channel, message, sender) { if (sender === MyAvatar.sessionUUID && channel === HIFI_POINT_INDEX_MESSAGE_CHANNEL) { var data = JSON.parse(message); + if (data.pointIndex !== undefined) { - print("pointIndex: " + data.pointIndex); - isBothIndexesPointing = data.pointIndex; + if (data.pointIndex) { + leftIndexPointingOverride++; + rightIndexPointingOverride++; + } else { + leftIndexPointingOverride--; + rightIndexPointingOverride--; + } + } + if (data.pointLeftIndex !== undefined) { + if (data.pointLeftIndex) { + leftIndexPointingOverride++; + } else { + leftIndexPointingOverride--; + } + } + if (data.pointRightIndex !== undefined) { + if (data.pointRightIndex) { + rightIndexPointingOverride++; + } else { + rightIndexPointingOverride--; + } + } + if (data.raiseThumbs !== undefined) { + if (data.raiseThumbs) { + leftThumbRaisedOverride++; + rightThumbRaisedOverride++; + } else { + leftThumbRaisedOverride--; + rightThumbRaisedOverride--; + } + } + if (data.raiseLeftThumb !== undefined) { + if (data.raiseLeftThumb) { + leftThumbRaisedOverride++; + } else { + leftThumbRaisedOverride--; + } + } + if (data.raiseRightThumb !== undefined) { + if (data.raiseRightThumb) { + rightThumbRaisedOverride++; + } else { + rightThumbRaisedOverride--; + } } } } diff --git a/scripts/system/fingerPaint.js b/scripts/system/fingerPaint.js index 959f594212..27206ef9fa 100644 --- a/scripts/system/fingerPaint.js +++ b/scripts/system/fingerPaint.js @@ -13,6 +13,7 @@ button, BUTTON_NAME = "PAINT", isFingerPainting = false, + shouldPointFingers = false, leftHand = null, rightHand = null, leftBrush = null, @@ -308,9 +309,14 @@ Messages.sendMessage(HIFI_POINTER_DISABLE_MESSAGE_CHANNEL, JSON.stringify({ pointerEnabled: enabled }), true); - Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ - pointIndex: !enabled - }), true); + + var newShouldPointFingers = !enabled; + if (newShouldPointFingers !== shouldPointFingers) { + Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify({ + pointIndex: newShouldPointFingers + }), true); + shouldPointFingers = newShouldPointFingers; + } } function enableProcessing() { @@ -430,4 +436,4 @@ setUp(); Script.scriptEnding.connect(tearDown); -}()); \ No newline at end of file +}()); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 8859b13624..c7706da28b 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -171,6 +171,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly) { this.homeButtonID = Overlays.addOverlay("sphere", { name: "homeButton", localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, + localRotation: {x: 0, y: 1, z: 0, w: 0}, dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, alpha: 0.0, visible: true, From 6f53aaed5c42a704c12aee69e3b60c2915c79ccb Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 9 Mar 2017 14:04:13 -0800 Subject: [PATCH 6/6] Small simplification of building stylusTarget list. --- .../system/controllers/handControllerGrab.js | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index abd0484ec4..284bb3af15 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -1618,44 +1618,26 @@ function MyController(hand) { overlays: [] }; - // find web-entites near the stylusTip. + // build list of stylus targets, near the stylusTip + var stylusTargets = []; var candidateEntities = Entities.findEntities(tipPosition, WEB_DISPLAY_STYLUS_DISTANCE); entityPropertiesCache.addEntities(candidateEntities); var i, props; for (i = 0; i < candidateEntities.length; i++) { props = entityPropertiesCache.getProps(candidateEntities[i]); if (props && (props.type === "Web" || this.isTablet(candidateEntities[i]))) { - candidates.entities.push(candidateEntities[i]); + stylusTargets.push(calculateStylusTargetFromEntity(this.stylusTip, candidateEntities[i])); } } // add the tabletScreen, if it is valid if (HMD.tabletScreenID && HMD.tabletScreenID !== NULL_UUID) { - candidates.overlays.push(HMD.tabletScreenID); + stylusTargets.push(calculateStylusTargetFromOverlay(this.stylusTip, HMD.tabletScreenID)); } // add the tablet home button. if (HMD.homeButtonID && HMD.homeButtonID !== NULL_UUID) { - candidates.overlays.push(HMD.homeButtonID); - } - - // build list of stylus targets - var stylusTargets = []; - if (candidates.entities.length > 0 || candidates.overlays.length > 0) { - var stylusTarget; - for (i = 0; i < candidates.entities.length; i++) { - stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, candidates.entities[i]); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - - for (i = 0; i < candidates.overlays.length; i++) { - stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, candidates.overlays[i]); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } + stylusTargets.push(calculateStylusTargetFromOverlay(this.stylusTip, HMD.homeButtonID)); } var TABLET_MIN_HOVER_DISTANCE = 0.01;