diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index 26edc8609c..b5681fef3f 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -248,6 +248,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var candidatePlugin = controllerDispatcherPlugins[orderedPluginName]; if (_this.slotsAreAvailableForPlugin(candidatePlugin)) { + //print(orderedPluginName); var readiness = candidatePlugin.isReady(controllerData, deltaTime); if (readiness.active) { // this plugin will start. add it to the list of running plugins and mark the diff --git a/scripts/system/controllers/controllerModules/cloneEntity.js b/scripts/system/controllers/controllerModules/cloneEntity.js index 0539ee983a..cfe9cb56da 100644 --- a/scripts/system/controllers/controllerModules/cloneEntity.js +++ b/scripts/system/controllers/controllerModules/cloneEntity.js @@ -49,7 +49,7 @@ if (typeof Object.assign != 'function') { this.previouslyUnhooked = {}; this.parameters = makeDispatcherModuleParameters( - 150, + 300, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); @@ -78,12 +78,15 @@ if (typeof Object.assign != 'function') { } if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - if (this.waiting) { - return makeRunningValues(false, [], []); + if (!this.waiting) { + this.waiting = true; + return makeRunningValues(true, [], []); } - this.waiting = true; } + return makeRunningValues(false, [], []); + }; + this.run = function (controllerData, deltaTime) { var cloneableProps = this.getTargetProps(controllerData); if (!cloneableProps) { return makeRunningValues(false, [], []); @@ -133,22 +136,18 @@ if (typeof Object.assign != 'function') { cProperties.userData = JSON.stringify(cUserData); // var cloneID = Entities.addEntity(cProperties); - return makeRunningValues(false, [], []); }; - this.run = function (controllerData) { - }; - this.cleanup = function () { }; } - var leftNearParentingGrabEntity = new CloneEntity(LEFT_HAND); - var rightNearParentingGrabEntity = new CloneEntity(RIGHT_HAND); + var leftCloneEntity = new CloneEntity(LEFT_HAND); + var rightCloneEntity = new CloneEntity(RIGHT_HAND); - enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); - enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); + enableDispatcherModule("LeftCloneEntity", leftCloneEntity); + enableDispatcherModule("RightCloneEntity", rightCloneEntity); this.cleanup = function () { leftNearParentingGrabEntity.cleanup(); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index a82a97b6eb..3c8fba6c24 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -317,7 +317,20 @@ Script.include("/~/system/libraries/controllers.js"); this.grabbedThingID = null; }; + this.pointingOnTablet = function(controllerData) { + var target = controllerData.rayPicks[this.hand].objectID; + + if (target === HMD.homeButtonID || target === HMD.tabletScreenID) { + return true; + } + return false + }; + this.isReady = function (controllerData) { + if (this.pointingOnTablet(controllerData)) { + return makeRunningValues(false, [], []); + } + this.distanceHolding = false; this.distanceRotating = false; @@ -330,7 +343,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.pointingOnTablet(controllerData)) { this.endNearGrabAction(); this.laserPointerOff(); return makeRunningValues(false, [], []); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 2332fdd32c..cb004acd03 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -30,7 +30,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.parameters = makeDispatcherModuleParameters( 500, - this.hand === RIGHT_HAND ? ["rightHand", "rightHandTrigger"] : ["leftHand", "leftHandTrigger"], + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); @@ -134,7 +134,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var handPosition = controllerData.controllerLocations[this.hand].position; var distance = Vec3.distance(props.position, handPosition); if (distance > NEAR_GRAB_RADIUS) { - break; + continue; } if (entityIsGrabbable(props)) { // if we've attempted to grab a child, roll up to the root of the tree @@ -148,7 +148,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return null; }; - this.isReady = function (controllerData) { + this.isReady = function (controllerData, deltaTime) { this.targetEntityID = null; this.grabbing = false; @@ -169,7 +169,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); } }; - this.run = function (controllerData) { + this.run = function (controllerData, deltaTime) { if (this.grabbing) { if (controllerData.triggerClicks[this.hand] == 0) { this.endNearParentingGrabEntity(); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js index 58b6a12090..399814a0ed 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabOverlay.js @@ -13,6 +13,7 @@ */ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +var GRAB_RADIUS = 0.35; (function() { @@ -123,6 +124,19 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.grabbedThingID = null; }; + this.getTargetID = function(overlays, controllerData) { + for (var i = 0; i < overlays.length; i++) { + var overlayPosition = Overlays.getProperty(overlays[i], "position"); + var handPosition = controllerData.controllerLocations[this.hand].position; + var distance = Vec3.distance(overlayPosition, handPosition); + if (distance <= GRAB_RADIUS) { + return overlays[i]; + } + } + return null; + }; + + this.isReady = function (controllerData) { if (controllerData.triggerClicks[this.hand] == 0) { return makeRunningValues(false, [], []); @@ -135,8 +149,9 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); return Overlays.getProperty(overlayID, "grabbable"); }); - if (grabbableOverlays.length > 0) { - this.grabbedThingID = grabbableOverlays[0]; + var targetID = this.getTargetID(grabbableOverlays, controllerData); + if (targetID) { + this.grabbedThingID = targetID; this.startNearParentingGrabOverlay(controllerData); return makeRunningValues(true, [this.grabbedThingID], []); } else { diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index 141244b60b..fd24aeefa2 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); this.previouslyUnhooked = {}; this.parameters = makeDispatcherModuleParameters( - 200, + 520, this.hand === RIGHT_HAND ? ["rightHandTrigger"] : ["leftHandTrigger"], [], 100); @@ -42,7 +42,7 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); var handPosition = controllerData.controllerLocations[this.hand].position; var distance = Vec3.distance(props.position, handPosition); if (distance > NEAR_GRAB_RADIUS) { - break; + continue; } if (entityWantsNearTrigger(props)) { return props; @@ -57,7 +57,6 @@ Script.include("/~/system/controllers/controllerDispatcherUtils.js"); }; this.continueNearTrigger = function (controllerData) { - print("-------> continue near trigger <-------"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "continueNearTrigger", args); }; diff --git a/scripts/system/controllers/controllerModules/tabletLaserInput.js b/scripts/system/controllers/controllerModules/tabletLaserInput.js new file mode 100644 index 0000000000..c7098d4e9a --- /dev/null +++ b/scripts/system/controllers/controllerModules/tabletLaserInput.js @@ -0,0 +1,626 @@ +"use strict" + +// tabletLaserInput.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, + Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, ZERO_VEC, + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset +*/ + + + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true + }; + var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID + }; + + var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; + + var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + + + // triggered when stylus presses a web overlay/entity + var HAPTIC_STYLUS_STRENGTH = 1.0; + var HAPTIC_STYLUS_DURATION = 20.0; + + 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); + } + } + + // will return undefined if overlayID does not exist. + function calculateStylusTargetFromOverlay(stylusTip, overlayID) { + var overlayPosition = Overlays.getProperty(overlayID, "position"); + if (overlayPosition === undefined) { + return; + } + + // project stylusTip onto overlay plane. + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + if (overlayRotation === undefined) { + return; + } + 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"); + if (resolution === undefined) { + return; + } + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + if (scale === undefined) { + return; + } + 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 === undefined) { + return; + } + 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 + }; + } + + // will return undefined if entity does not exist. + function calculateStylusTargetFromEntity(stylusTip, props) { + if (props.rotation === undefined) { + // if rotation is missing from props object, then this entity has probably been deleted. + return; + } + + // 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: props.id, + entityProps: props, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.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; + } + + function getFingerWorldLocation(hand) { + var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; + + var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); + var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); + var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); + var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); + var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); + + return { + position: worldFingerPosition, + orientation: worldFingerRotation, + rotation: worldFingerRotation, + valid: true + }; + } + + function distance2D(a, b) { + var dx = (a.x - b.x); + var dy = (a.y - b.y); + return Math.sqrt(dx * dx + dy * dy); + } + + function TabletLaserInput(hand) { + this.hand = hand; + this.previousStylusTouchingTarget = false; + this.stylusTouchingTarget = false; + this.tabletScreenID = HMD.tabletScreenID; + + this.useFingerInsteadOfStylus = false; + this.fingerPointing = false; + + + this.parameters = makeDispatcherModuleParameters( + 200, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.getOtherHandController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + }; + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.stealTouchFocus = function(laserTarget) { + // 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(laserTarget) { + + // 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(laserTarget) { + 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(HMD.tabletScreenID, pointerEvent); + Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); + Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); + this.hoverOverlay = null; + } + }; + + this.updateLaserPointer = function(controllerData) { + var RADIUS = 0.005; + var dim = { x: RADIUS, y: RADIUS, z: RADIUS }; + + var mode = "none"; + + if (controllerData.triggerClicks[this.hand]) { + mode = "full"; + } else if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + mode = "half"; + } else { + LaserPointers.disableLaserPointer(this.laserPointer); + return; + } + + if (mode === "full") { + this.fullEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, mode, {path: fullPath, end: this.fullEnd}); + } else if (mode === "half") { + this.halfEnd.dimensions = dim; + LaserPointers.editRenderState(this.laserPointer, mode, {path: halfPath, end: this.halfEnd}); + } + + LaserPointers.enableLaserPointer(this.laserPointer); + LaserPointers.setRenderState(this.laserPointer, mode); + }; + + 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.processLaser = function(controllerData) { + if (controllerData.rayPicks[this.hand].objectID === HMD.tabletScreenID) { + + } + this.homeButtonTouched = false; + }; + + this.laserTouchingEnter = 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.0381; + this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; + }; + + this.laserTouchingExit = function () { + + if (this.stylusTarget === undefined) { + return; + } + + // 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.laserTouching = function (controllerData, dt) { + + this.touchingEnterTimer += dt; + + if (this.stylusTarget.entityID) { + this.stylusTarget = calculateStylusTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); + } else if (this.stylusTarget.overlayID) { + this.stylusTarget = calculateStylusTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); + } + + var TABLET_MIN_TOUCH_DISTANCE = -0.1; + var TABLET_MAX_TOUCH_DISTANCE = 0.01; + + 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.stylusTouchingTarget = false; + } + } else { + this.stylusTouchingTarget = false; + } + }; + + this.pointingAtTablet = function(target) { + return (target === HMD.tabletID); + }; + + this.pointingAtTabletScreen = function(target) { + return (target === HMD.tabletScreenID); + } + + this.pointingAtHomeButton = function(target) { + return (target === HMD.homeButtonID); + } + + this.isReady = function (controllerData) { + var target = controllerData.rayPicks[this.hand].objectID; + if (this.pointingAtTabletScreen(target) || this.pointingAtHomeButton(target) || this.pointingAtTablet(target)) { + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + return makeRunningValues(true, [], []); + } + } + //this.processLaser(controllerData); + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData, deltaTime) { + var target = controllerData.rayPicks[this.hand].objectID; + if (!this.pointingAtTabletScreen(target) && !this.pointingAtHomeButton(target) && !this.pointingAtTablet(target)) { + LaserPointers.disableLaserPointer(this.laserPointer); + return makeRunningValues(false, [], []); + } + this.updateLaserPointer(controllerData); + //this.updateFingerAsStylusSetting(); + +/* if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) { + this.stylusTouchingEnter(); + } + if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) { + this.stylusTouchingExit(); + } + this.previousStylusTouchingTarget = this.stylusTouchingTarget; + + if (this.stylusTouchingTarget) { + this.stylusTouching(controllerData, deltaTime); + }*/ + /*if (this.processLaser(controllerData)) { + return makeRunningValues(true, [], []); + } else { + return makeRunningValues(false, [], []); + }*/ + + return makeRunningValues(true, [], []); + }; + + this.cleanup = function () { + LaserPointers.disableLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.laserPointer); + }; + + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (this.hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + }; + + var leftTabletLaserInput = new TabletLaserInput(LEFT_HAND); + var rightTabletLaserInput = new TabletLaserInput(RIGHT_HAND); + + enableDispatcherModule("LeftTabletLaserInput", leftTabletLaserInput); + enableDispatcherModule("RightTabletLaserInput", rightTabletLaserInput); + + this.cleanup = function () { + leftTabletStylusInput.cleanup(); + rightTabletStylusInput.cleanup(); + disableDispatcherModule("LeftTabletLaserInput"); + disableDispatcherModule("RightTabletLaserInput"); + }; + Script.scriptEnding.connect(this.cleanup); +}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index b69b2a6e3f..65d63b7bec 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -26,6 +26,7 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", "controllerModules/cloneEntity.js", + "controllerModules/tabletLaserInput.js", "teleport.js" ];