"use strict"; // touchEventUtils.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, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, controllerDispatcher.ZERO_VEC, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset */ var controllerDispatcher = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); function touchTargetHasKeyboardFocus(touchTarget) { if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) { return Entities.keyboardFocusEntity === touchTarget.entityID; } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { return Overlays.keyboardFocusOverlay === touchTarget.overlayID; } } function setKeyboardFocusOnTouchTarget(touchTarget) { if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL && Entities.wantsHandControllerPointerEvents(touchTarget.entityID)) { Overlays.keyboardFocusOverlay = Uuid.NULL; Entities.keyboardFocusEntity = touchTarget.entityID; } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { Overlays.keyboardFocusOverlay = touchTarget.overlayID; Entities.keyboardFocusEntity = Uuid.NULL; } } function sendHoverEnterEventToTouchTarget(hand, touchTarget) { var pointerEvent = { type: "Move", id: hand + 1, // 0 is reserved for hardware mouse pos2D: touchTarget.position2D, pos3D: touchTarget.position, normal: touchTarget.normal, direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), button: "None" }; if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) { Entities.sendHoverEnterEntity(touchTarget.entityID, pointerEvent); } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { Overlays.sendHoverEnterOverlay(touchTarget.overlayID, pointerEvent); } } function sendHoverOverEventToTouchTarget(hand, touchTarget) { var pointerEvent = { type: "Move", id: hand + 1, // 0 is reserved for hardware mouse pos2D: touchTarget.position2D, pos3D: touchTarget.position, normal: touchTarget.normal, direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), button: "None" }; if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) { Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent); Entities.sendHoverOverEntity(touchTarget.entityID, pointerEvent); } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent); Overlays.sendHoverOverOverlay(touchTarget.overlayID, pointerEvent); } } function sendTouchStartEventToTouchTarget(hand, touchTarget) { var pointerEvent = { type: "Press", id: hand + 1, // 0 is reserved for hardware mouse pos2D: touchTarget.position2D, pos3D: touchTarget.position, normal: touchTarget.normal, direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), button: "Primary", isPrimaryHeld: true }; if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) { Entities.sendMousePressOnEntity(touchTarget.entityID, pointerEvent); Entities.sendClickDownOnEntity(touchTarget.entityID, pointerEvent); } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { Overlays.sendMousePressOnOverlay(touchTarget.overlayID, pointerEvent); } } function sendTouchEndEventToTouchTarget(hand, touchTarget) { var pointerEvent = { type: "Release", id: hand + 1, // 0 is reserved for hardware mouse pos2D: touchTarget.position2D, pos3D: touchTarget.position, normal: touchTarget.normal, direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), button: "Primary" }; if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) { Entities.sendMouseReleaseOnEntity(touchTarget.entityID, pointerEvent); Entities.sendClickReleaseOnEntity(touchTarget.entityID, pointerEvent); Entities.sendHoverLeaveEntity(touchTarget.entityID, pointerEvent); } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { Overlays.sendMouseReleaseOnOverlay(touchTarget.overlayID, pointerEvent); } } function sendTouchMoveEventToTouchTarget(hand, touchTarget) { var pointerEvent = { type: "Move", id: hand + 1, // 0 is reserved for hardware mouse pos2D: touchTarget.position2D, pos3D: touchTarget.position, normal: touchTarget.normal, direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), button: "Primary", isPrimaryHeld: true }; if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) { Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent); Entities.sendHoldingClickOnEntity(touchTarget.entityID, pointerEvent); } else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) { Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent); } } function composeTouchTargetFromIntersection(intersection) { var isEntity = (intersection.type === Picks.INTERSECTED_ENTITY); var objectID = intersection.objectID; var worldPos = intersection.intersection; var props = null; if (isEntity) { props = Entities.getProperties(intersection.objectID); } var position2D =(isEntity ? controllerDispatcher.projectOntoEntityXYPlane(objectID, worldPos, props) : controllerDispatcher.projectOntoOverlayXYPlane(objectID, worldPos)); return { entityID: isEntity ? objectID : null, overlayID: isEntity ? null : objectID, distance: intersection.distance, position: worldPos, position2D: position2D, normal: intersection.surfaceNormal }; } // will return undefined if overlayID does not exist. function calculateTouchTargetFromOverlay(touchTip, overlayID) { var overlayPosition = Overlays.getProperty(overlayID, "position"); if (overlayPosition === undefined) { return; } // project touchTip 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(touchTip.position, overlayPosition), normal); var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance)); // calclulate normalized position var invRot = Quat.inverse(overlayRotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); var dimensions = Overlays.getProperty(overlayID, "dimensions"); if (dimensions === undefined) { return; } dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension 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 calculateTouchTargetFromEntity(touchTip, props) { if (props.rotation === undefined) { // if rotation is missing from props object, then this entity has probably been deleted. return; } // project touch 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(touchTip.position, props.position), normal); var position = Vec3.subtract(touchTip.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 }; } module.exports = { calculateTouchTargetFromEntity: calculateTouchTargetFromEntity, calculateTouchTargetFromOverlay: calculateTouchTargetFromOverlay, touchTargetHasKeyboardFocus: touchTargetHasKeyboardFocus, setKeyboardFocusOnTouchTarget: setKeyboardFocusOnTouchTarget, sendHoverEnterEventToTouchTarget: sendHoverEnterEventToTouchTarget, sendHoverOverEventToTouchTarget: sendHoverOverEventToTouchTarget, sendTouchStartEventToTouchTarget: sendTouchStartEventToTouchTarget, sendTouchEndEventToTouchTarget: sendTouchEndEventToTouchTarget, sendTouchMoveEventToTouchTarget: sendTouchMoveEventToTouchTarget, composeTouchTargetFromIntersection: composeTouchTargetFromIntersection };