diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index 373041c80a..b0bf34a594 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -9,8 +9,8 @@ if (WIN32) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://static.oculus.com/sdk-downloads/0.6.0.0/1431634088/ovr_sdk_win_0.6.0.0.zip - URL_MD5 a3dfdab037a854fdcf7e6033fa8d7028 + URL http://static.oculus.com/sdk-downloads/0.6.0.1/Public/1435190862/ovr_sdk_win_0.6.0.1.zip + URL_MD5 4b3ef825f9a1d6d3035c9f6820687da9 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/cmake/externals/openvr/CMakeLists.txt b/cmake/externals/openvr/CMakeLists.txt index c9d821f655..2bb84ca637 100644 --- a/cmake/externals/openvr/CMakeLists.txt +++ b/cmake/externals/openvr/CMakeLists.txt @@ -26,16 +26,19 @@ if (WIN32) # FIXME need to account for different architectures set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/win32/openvr_api.lib CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32) elseif(APPLE) # FIXME need to account for different architectures set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/osx32) elseif(NOT ANDROID) # FIXME need to account for different architectures - set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux64/libopenvr_api.so CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/linux64) endif() diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt new file mode 100644 index 0000000000..c80b492509 --- /dev/null +++ b/cmake/externals/sixense/CMakeLists.txt @@ -0,0 +1,60 @@ +include(ExternalProject) +include(SelectLibraryConfigurations) + +set(EXTERNAL_NAME Sixense) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ./SixenseSDK_062612.zip + URL_MD5 10cc8dc470d2ac1244a88cf04bc549cc + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1 +) + +if (APPLE) + find_library(SIXENSE_LIBRARY_RELEASE lib/osx_x64/release_dll/libsixense_x64.dylib HINTS ${SIXENSE_SEARCH_DIRS}) + find_library(SIXENSE_LIBRARY_DEBUG lib/osx_x64/debug_dll/libsixensed_x64.dylib HINTS ${SIXENSE_SEARCH_DIRS}) +elseif (UNIX) + find_library(SIXENSE_LIBRARY_RELEASE lib/linux_x64/release/libsixense_x64.so HINTS ${SIXENSE_SEARCH_DIRS}) + # find_library(SIXENSE_LIBRARY_DEBUG lib/linux_x64/debug/libsixensed_x64.so HINTS ${SIXENSE_SEARCH_DIRS}) +elseif (WIN32) +endif () + + + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL) + +if (WIN32) + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(ARCH_DIR "x64") + set(ARCH_SUFFIX "_x64") + else() + set(ARCH_DIR "Win32") + set(ARCH_SUFFIX "") + endif() + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/win32) + +elseif(APPLE) + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/osx32/libopenvr_api.dylib CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/osx32) + +elseif(NOT ANDROID) + + # FIXME need to account for different architectures + set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/lib/linux32/libopenvr_api.so CACHE TYPE INTERNAL) + add_paths_to_fixup_libs(${SOURCE_DIR}/bin/linux32) + +endif() + diff --git a/cmake/macros/GroupSources.cmake b/cmake/macros/GroupSources.cmake new file mode 100644 index 0000000000..07e46afc94 --- /dev/null +++ b/cmake/macros/GroupSources.cmake @@ -0,0 +1,16 @@ +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +macro(GroupSources curdir) + file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) + foreach(child ${children}) + if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + GroupSources(${curdir}/${child}) + else() + string(REPLACE "/" "\\" groupname ${curdir}) + source_group(${groupname} FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child}) + endif() + endforeach() +endmacro() diff --git a/examples/controllers/handGrab.js b/examples/controllers/handGrab.js new file mode 100644 index 0000000000..224e66e323 --- /dev/null +++ b/examples/controllers/handGrab.js @@ -0,0 +1,331 @@ +// handGrab.js +// examples +// +// Created by Sam Gondelman on 8/3/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Allow avatar to grab the closest object to each hand and throw them +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.js"); + +var nullActionID = "00000000-0000-0000-0000-000000000000"; +var controllerID; +var controllerActive; +var leftHandObjectID = null; +var rightHandObjectID = null; +var leftHandActionID = nullActionID; +var rightHandActionID = nullActionID; + +var TRIGGER_THRESHOLD = 0.2; +var GRAB_RADIUS = 0.25; + +var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); +var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); +var ACTION1 = Controller.findAction("ACTION1"); +var ACTION2 = Controller.findAction("ACTION2"); + +var rightHandGrabAction = RIGHT_HAND_CLICK; +var leftHandGrabAction = LEFT_HAND_CLICK; + +var rightHandGrabValue = 0; +var leftHandGrabValue = 0; +var prevRightHandGrabValue = 0; +var prevLeftHandGrabValue = 0; + +var grabColor = { red: 0, green: 255, blue: 0}; +var releaseColor = { red: 0, green: 0, blue: 255}; + +var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() { + return { + x: 100, + y: 380 + }; +}); + +var BUTTON_SIZE = 32; +var SWORD_IMAGE = "https://hifi-public.s3.amazonaws.com/images/sword/sword.svg"; // replace this with a table icon +var CLEANUP_IMAGE = "http://s3.amazonaws.com/hifi-public/images/delete.png"; // cleanup table +var tableButton = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: SWORD_IMAGE, + alpha: 1 +}); +var cleanupButton = toolBar.addOverlay("image", { + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: CLEANUP_IMAGE, + alpha: 1 +}); + +var leftHandOverlay = Overlays.addOverlay("sphere", { + position: MyAvatar.getLeftPalmPosition(), + size: GRAB_RADIUS, + color: releaseColor, + alpha: 0.5, + solid: false + }); +var rightHandOverlay = Overlays.addOverlay("sphere", { + position: MyAvatar.getRightPalmPosition(), + size: GRAB_RADIUS, + color: releaseColor, + alpha: 0.5, + solid: false + }); + +var OBJECT_HEIGHT_OFFSET = 0.5; +var MIN_OBJECT_SIZE = 0.05; +var MAX_OBJECT_SIZE = 0.3; +var TABLE_DIMENSIONS = { + x: 10.0, + y: 0.2, + z: 5.0 +}; + +var GRAVITY = { + x: 0, + y: -2, + z: 0 +} + +var LEFT = 0; +var RIGHT = 1; + +var tableCreated = false; + +var NUM_OBJECTS = 100; +var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table + +var VELOCITY_MAG = 0.3; + +var MODELS = Array( + { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" }, + { modelURL: "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Vehicles/clara/spaceshuttle.fbx" }, + { modelURL: "https://s3.amazonaws.com/hifi-public/cozza13/apartment/Stargate.fbx" }, + { modelURL: "https://dl.dropboxusercontent.com/u/17344741/kelectricguitar10/kelectricguitar10.fbx" }, + { modelURL: "https://dl.dropboxusercontent.com/u/17344741/ktoilet10/ktoilet10.fbx" }, + { modelURL: "https://hifi-public.s3.amazonaws.com/models/props/MidCenturyModernLivingRoom/Interior/BilliardsTable.fbx" }, + { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/avatars/robotMedic/robotMedicRed/robotMedicRed.fst" }, + { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/avatars/robotMedic/robotMedicFaceRig/robotMedic.fst" }, + { modelURL: "https://hifi-public.s3.amazonaws.com/marketplace/contents/029db3d4-da2c-4cb2-9c08-b9612ba576f5/02949063e7c4aed42ad9d1a58461f56d.fst?1427169842" }, + { modelURL: "https://hifi-public.s3.amazonaws.com/models/props/MidCenturyModernLivingRoom/Interior/Bar.fbx" }, + { modelURL: "https://hifi-public.s3.amazonaws.com/marketplace/contents/96124d04-d603-4707-a5b3-e03bf47a53b2/1431770eba362c1c25c524126f2970fb.fst?1436924721" } + // { modelURL: "https://s3.amazonaws.com/hifi-public/marketplace/hificontent/Architecture/sketchfab/cudillero.fbx" }, + // { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/sets/musicality/musicality.fbx" }, + // { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/sets/statelyHome/statelyHome.fbx" } + ); + +function letGo(hand) { + var actionIDToRemove = (hand == LEFT) ? leftHandActionID : rightHandActionID; + var entityIDToEdit = (hand == LEFT) ? leftHandObjectID : rightHandObjectID; + var handVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmVelocity() : MyAvatar.getRightPalmVelocity(); + var handAngularVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmAngularVelocity() : + MyAvatar.getRightPalmAngularVelocity(); + if (actionIDToRemove != nullActionID && entityIDToEdit != null) { + Entities.deleteAction(entityIDToEdit, actionIDToRemove); + if (hand == LEFT) { + leftHandObjectID = null; + leftHandActionID = nullActionID; + } else { + rightHandObjectID = null; + rightHandActionID = nullActionID; + } + } +} + +function setGrabbedObject(hand) { + var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); + var entities = Entities.findEntities(handPosition, GRAB_RADIUS); + var objectID = null; + var minDistance = GRAB_RADIUS; + for (var i = 0; i < entities.length; i++) { + if ((hand == LEFT && entities[i] == rightHandObjectID) || + (hand == RIGHT) && entities[i] == leftHandObjectID) { + continue; + } else { + var distance = Vec3.distance(Entities.getEntityProperties(entities[i]).position, handPosition); + if (distance < minDistance) { + objectID = entities[i]; + minDistance = distance; + } + } + } + if (objectID == null) { + return false; + } + if (hand == LEFT) { + leftHandObjectID = objectID; + } else { + rightHandObjectID = objectID; + } + return true; +} + +function grab(hand) { + if (!setGrabbedObject(hand)) { + return; + } + var objectID = (hand == LEFT) ? leftHandObjectID : rightHandObjectID; + var handRotation = (hand == LEFT) ? MyAvatar.getLeftPalmRotation() : MyAvatar.getRightPalmRotation(); + + var objectRotation = Entities.getEntityProperties(objectID).rotation; + var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + var actionID = Entities.addAction("hold", objectID, { + relativePosition: { + x: 0.0, + y: 0.0, + z: 0.0 + }, + relativeRotation: offsetRotation, + hand: (hand == LEFT) ? "left" : "right", + timeScale: 0.05 + }); + if (actionID == nullActionID) { + if (hand == LEFT) { + leftHandObjectID = null; + } else { + rightHandObjectID = null; + } + } else { + // Entities.editEntity(objectID, { ignore}); + if (hand == LEFT) { + leftHandActionID = actionID; + } else { + rightHandActionID = actionID; + } + } +} + +function update() { + Overlays.editOverlay(leftHandOverlay, { position: MyAvatar.getLeftPalmPosition() }); + Overlays.editOverlay(rightHandOverlay, { position: MyAvatar.getRightPalmPosition() }); + + rightHandGrabValue = Controller.getActionValue(rightHandGrabAction); + leftHandGrabValue = Controller.getActionValue(leftHandGrabAction); + + if (rightHandGrabValue > TRIGGER_THRESHOLD && rightHandObjectID == null) { + Overlays.editOverlay(rightHandOverlay, { color: grabColor }); + grab(RIGHT); + } else if (rightHandGrabValue < TRIGGER_THRESHOLD && + prevRightHandGrabValue > TRIGGER_THRESHOLD) { + Overlays.editOverlay(rightHandOverlay, { color: releaseColor }); + letGo(RIGHT); + } + + if (leftHandGrabValue > TRIGGER_THRESHOLD && leftHandObjectID == null) { + Overlays.editOverlay(leftHandOverlay, { color: grabColor }); + grab(LEFT); + } else if (leftHandGrabValue < TRIGGER_THRESHOLD && + prevLeftHandGrabValue > TRIGGER_THRESHOLD) { + Overlays.editOverlay(leftHandOverlay, { color: releaseColor }); + letGo(LEFT); + } + + prevRightHandGrabValue = rightHandGrabValue; + prevLeftHandGrabValue = leftHandGrabValue; +} + +function cleanUp() { + letGo(RIGHT); + letGo(LEFT); + Overlays.deleteOverlay(leftHandOverlay); + Overlays.deleteOverlay(rightHandOverlay); + removeTable(); + toolBar.cleanup(); +} + +function onClick(event) { + if (event.deviceID != 0) { + return; + } + switch (Overlays.getOverlayAtPoint(event)) { + case tableButton: + if (!tableCreated) { + createTable(); + tableCreated = true; + } + break; + case cleanupButton: + if (tableCreated) { + removeTable(); + tableCreated = false; + } + break; + } +} + +randFloat = function(low, high) { + return low + Math.random() * (high - low); +} + +randInt = function(low, high) { + return Math.floor(randFloat(low, high)); +} + +function createTable() { + var tablePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(MyAvatar.orientation))); + tableEntities[0] = Entities.addEntity( { + type: "Box", + position: tablePosition, + dimensions: TABLE_DIMENSIONS, + rotation: MyAvatar.orientation, + color: { red: 255, green: 0, blue: 0 } + }); + + for (var i = 1; i < NUM_OBJECTS + 1; i++) { + var objectOffset = { x: TABLE_DIMENSIONS.x/2.0 * randFloat(-1, 1), + y: OBJECT_HEIGHT_OFFSET, + z: TABLE_DIMENSIONS.z/2.0 * randFloat(-1, 1) }; + var objectPosition = Vec3.sum(tablePosition, Vec3.multiplyQbyV(MyAvatar.orientation, objectOffset)); + var type; + var randType = randInt(0, 3); + switch (randType) { + case 0: + type = "Box"; + break; + case 1: + type = "Sphere"; + // break; + case 2: + type = "Model"; + break; + } + tableEntities[i] = Entities.addEntity( { + type: type, + position: objectPosition, + velocity: { x: randFloat(-VELOCITY_MAG, VELOCITY_MAG), + y: randFloat(-VELOCITY_MAG, VELOCITY_MAG), + z: randFloat(-VELOCITY_MAG, VELOCITY_MAG) }, + dimensions: { x: randFloat(MIN_OBJECT_SIZE, MAX_OBJECT_SIZE), + y: randFloat(MIN_OBJECT_SIZE, MAX_OBJECT_SIZE), + z: randFloat(MIN_OBJECT_SIZE, MAX_OBJECT_SIZE) }, + rotation: MyAvatar.orientation, + gravity: GRAVITY, + damping: 0.1, + restitution: 0.01, + density: 0.5, + collisionsWillMove: true, + color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) } + }); + if (type == "Model") { + var randModel = randInt(0, MODELS.length); + Entities.editEntity(tableEntities[i], { + shapeType: "box", + modelURL: MODELS[randModel].modelURL + }); + } + } +} + +function removeTable() { + for (var i = 0; i < tableEntities.length; i++) { + Entities.deleteEntity(tableEntities[i]); + } +} + +Script.scriptEnding.connect(cleanUp); +Script.update.connect(update); +Controller.mousePressEvent.connect(onClick); \ No newline at end of file diff --git a/examples/controllers/squeezeHands2.js b/examples/controllers/squeezeHands2.js new file mode 100644 index 0000000000..69f18f50a1 --- /dev/null +++ b/examples/controllers/squeezeHands2.js @@ -0,0 +1,79 @@ +// +// squeezeHands.js +// examples +// +// Created by Philip Rosedale on June 4, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + +var rightHandAnimation = HIFI_PUBLIC_BUCKET + "animations/RightHandAnimPhilip.fbx"; +var leftHandAnimation = HIFI_PUBLIC_BUCKET + "animations/LeftHandAnimPhilip.fbx"; + +var LEFT = 0; +var RIGHT = 1; + +var lastLeftFrame = 0; +var lastRightFrame = 0; + +var leftDirection = true; +var rightDirection = true; + +var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation? +var SMOOTH_FACTOR = 0.0; +var MAX_FRAMES = 30.0; + +var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); +var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); + +Script.update.connect(function(deltaTime) { + var leftTriggerValue = Controller.getActionValue(LEFT_HAND_CLICK); + var rightTriggerValue = Controller.getActionValue(RIGHT_HAND_CLICK); + + var leftFrame, rightFrame; + + // Average last few trigger frames together for a bit of smoothing + leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR; + rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR; + + if (!leftDirection) { + leftFrame = MAX_FRAMES - leftFrame; + } + if (!rightDirection) { + rightFrame = MAX_FRAMES - rightFrame; + } + + if ((leftTriggerValue == 1.0) && (leftDirection == true)) { + leftDirection = false; + lastLeftFrame = MAX_FRAMES - leftFrame; + } else if ((leftTriggerValue == 0.0) && (leftDirection == false)) { + leftDirection = true; + lastLeftFrame = leftFrame; + } + if ((rightTriggerValue == 1.0) && (rightDirection == true)) { + rightDirection = false; + lastRightFrame = MAX_FRAMES - rightFrame; + } else if ((rightTriggerValue == 0.0) && (rightDirection == false)) { + rightDirection = true; + lastRightFrame = rightFrame; + } + + if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){ + MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); + } + if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { + MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); + } + + lastLeftFrame = leftFrame; + lastRightFrame = rightFrame; +}); + +Script.scriptEnding.connect(function() { + MyAvatar.stopAnimation(leftHandAnimation); + MyAvatar.stopAnimation(rightHandAnimation); +}); \ No newline at end of file diff --git a/examples/example/games/sword.js b/examples/example/games/sword.js index 41223401c3..608fc30361 100644 --- a/examples/example/games/sword.js +++ b/examples/example/games/sword.js @@ -13,11 +13,15 @@ /*jslint vars: true*/ var Script, Entities, MyAvatar, Window, Overlays, Controller, Vec3, Quat, print, ToolBar, Settings; // Referenced globals provided by High Fidelity. Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.js"); +var zombieGameScriptURL = "https://hifi-public.s3.amazonaws.com/eric/scripts/zombieFight.js?v2"; +// var zombieGameScriptURL = "zombieFight.js"; +Script.include(zombieGameScriptURL); -var zombieFight; + +var zombieFight = new ZombieFight(); var hand = "right"; - +var zombieFight; var nullActionID = "00000000-0000-0000-0000-000000000000"; var controllerID; var controllerActive; @@ -78,7 +82,7 @@ var cleanupButton = toolBar.addOverlay("image", { var flasher; -var leftTriggerButton = 0; +var leftHandClick = 14; var leftTriggerValue = 0; var prevLeftTriggerValue = 0; @@ -88,7 +92,7 @@ var RIGHT = 1; var leftPalm = 2 * LEFT; var rightPalm = 2 * RIGHT; -var rightTriggerButton = 1; +var rightHandClick = 15; var prevRightTriggerValue = 0; var rightTriggerValue = 0; var TRIGGER_THRESHOLD = 0.2; @@ -357,8 +361,8 @@ function update() { } function updateControllerState() { - rightTriggerValue = Controller.getTriggerValue(rightTriggerButton); - leftTriggerValue = Controller.getTriggerValue(leftTriggerButton); + rightTriggerValue = Controller.getActionValue(rightHandClick); + leftTriggerValue = Controller.getActionValue(leftHandClick); if (rightTriggerValue > TRIGGER_THRESHOLD && !swordHeld) { grabSword("right") @@ -470,4 +474,4 @@ function onClick(event) { Script.scriptEnding.connect(cleanUp); Script.update.connect(update); -Controller.mousePressEvent.connect(onClick); \ No newline at end of file +Controller.mousePressEvent.connect(onClick); diff --git a/examples/hmdControls.js b/examples/hmdControls.js index 04c1dade0a..0f66d05b65 100644 --- a/examples/hmdControls.js +++ b/examples/hmdControls.js @@ -55,8 +55,8 @@ var warpLine = Overlays.addOverlay("line3d", { var velocity = { x: 0, y: 0, z: 0 }; var VERY_LONG_TIME = 1000000.0; -var active = Menu.isOptionChecked("Enable VR Mode"); -var prevVRMode = Menu.isOptionChecked("Enable VR Mode"); +var active = HMD.active; +var prevVRMode = HMD.active; var hmdControls = (function () { @@ -121,28 +121,28 @@ var hmdControls = (function () { velocity = Vec3.sum(velocity, direction); break; case findAction("YAW_LEFT"): - if (yawTimer < 0.0 && Menu.isOptionChecked("Enable VR Mode")) { + if (yawTimer < 0.0 && HMD.active) { yawChange = yawChange + (shifted ? SHIFT_MAG * VR_YAW_INCREMENT : VR_YAW_INCREMENT); yawTimer = CAMERA_UPDATE_TIME; - } else if (!Menu.isOptionChecked("Enable VR Mode")) { + } else if (!HMD.active) { yawChange = yawChange + (shifted ? SHIFT_MAG * YAW_INCREMENT : YAW_INCREMENT); } break; case findAction("YAW_RIGHT"): - if (yawTimer < 0.0 && Menu.isOptionChecked("Enable VR Mode")) { + if (yawTimer < 0.0 && HMD.active) { yawChange = yawChange - (shifted ? SHIFT_MAG * VR_YAW_INCREMENT : VR_YAW_INCREMENT); yawTimer = CAMERA_UPDATE_TIME; - } else if (!Menu.isOptionChecked("Enable VR Mode")) { + } else if (!HMD.active) { yawChange = yawChange - (shifted ? SHIFT_MAG * YAW_INCREMENT : YAW_INCREMENT); } break; case findAction("PITCH_DOWN"): - if (!Menu.isOptionChecked("Enable VR Mode")) { + if (!HMD.active) { pitchChange = pitchChange - (shifted ? SHIFT_MAG * PITCH_INCREMENT : PITCH_INCREMENT); } break; case findAction("PITCH_UP"): - if (!Menu.isOptionChecked("Enable VR Mode")) { + if (!HMD.active) { pitchChange = pitchChange + (shifted ? SHIFT_MAG * PITCH_INCREMENT : PITCH_INCREMENT); } break; @@ -175,9 +175,9 @@ var hmdControls = (function () { } function update(dt) { - if (prevVRMode != Menu.isOptionChecked("Enable VR Mode")) { - active = Menu.isOptionChecked("Enable VR Mode"); - prevVRMode = Menu.isOptionChecked("Enable VR Mode"); + if (prevVRMode != HMD.active) { + active = HMD.active; + prevVRMode = HMD.active; } if (yawTimer >= 0.0) { diff --git a/examples/notifications.js b/examples/notifications.js index f7c172ab24..63d94fbd92 100644 --- a/examples/notifications.js +++ b/examples/notifications.js @@ -130,7 +130,6 @@ var heights = []; var myAlpha = []; var arrays = []; var isOnHMD = false, - ENABLE_VR_MODE = "Enable VR Mode", NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. @@ -414,7 +413,7 @@ function update() { j, k; - if (isOnHMD !== Menu.isOptionChecked(ENABLE_VR_MODE)) { + if (isOnHMD !== HMD.active) { while (arrays.length > 0) { deleteNotification(0); } diff --git a/examples/progress.js b/examples/progress.js index 6dd26a9a11..0b033a1abf 100644 --- a/examples/progress.js +++ b/examples/progress.js @@ -41,7 +41,6 @@ SCALE_2D = 0.35, // Scale the SVGs for 2D display. background3D = {}, bar3D = {}, - ENABLE_VR_MODE_MENU_ITEM = "Enable VR Mode", PROGRESS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. PROGRESS_3D_DISTANCE = 0.602, // Horizontal distance from avatar position. PROGRESS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. @@ -157,7 +156,7 @@ eyePosition, avatarOrientation; - if (isOnHMD !== Menu.isOptionChecked(ENABLE_VR_MODE_MENU_ITEM)) { + if (isOnHMD !== HMD.active) { deleteOverlays(); isOnHMD = !isOnHMD; createOverlays(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f38a9e440a..9ad250bdcf 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "Faceshift" "Sixense" "LeapMotion" "RtMidi" "SDL2" "RSSDK" "3DConnexionClient" "iViewHMD") +set(OPTIONAL_EXTERNALS "Faceshift" "LeapMotion" "RtMidi" "RSSDK" "3DConnexionClient" "iViewHMD") foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) @@ -29,18 +29,6 @@ endif() configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVersion.h") -macro(GroupSources curdir) - file(GLOB children RELATIVE ${PROJECT_SOURCE_DIR}/${curdir} ${PROJECT_SOURCE_DIR}/${curdir}/*) - foreach(child ${children}) - if(IS_DIRECTORY ${PROJECT_SOURCE_DIR}/${curdir}/${child}) - GroupSources(${curdir}/${child}) - else() - string(REPLACE "/" "\\" groupname ${curdir}) - source_group(${groupname} FILES ${PROJECT_SOURCE_DIR}/${curdir}/${child}) - endif() - endforeach() -endmacro() - # grab the implementation and header files from src dirs file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") @@ -115,16 +103,12 @@ else() add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif() -# set up the external glm library add_dependency_external_projects(glm bullet) + +# set up the external glm library find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${GLM_INCLUDE_DIRS}) -add_dependency_external_projects(LibOVR) -find_package(LibOVR REQUIRED) -target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) - find_package(Bullet REQUIRED) # perform the system include hack for OS X to ignore warnings @@ -137,9 +121,10 @@ endif() target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) # link required hifi libraries -link_hifi_libraries(shared octree environment gpu model render fbx networking entities avatars - audio audio-client animation script-engine physics - render-utils entities-renderer ui auto-updater) +link_hifi_libraries(shared octree environment gpu model render fbx networking entities avatars + audio audio-client animation script-engine physics + render-utils entities-renderer ui auto-updater + plugins display-plugins input-plugins) add_dependency_external_projects(sdl2) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e53b614527..fa85aa2192 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -57,6 +57,8 @@ #include #include #include +#include + #include #include #include @@ -66,6 +68,8 @@ #include #include #include +#include +#include // this should probably be removed #include #include #include @@ -90,6 +94,7 @@ #include #include #include +#include #include #include "AudioClient.h" @@ -111,11 +116,8 @@ #include "devices/EyeTracker.h" #include "devices/Faceshift.h" #include "devices/Leapmotion.h" -#include "devices/MIDIManager.h" -#include "devices/OculusManager.h" #include "devices/RealSense.h" -#include "devices/SDL2Manager.h" -#include "devices/TV3DManager.h" +#include "devices/MIDIManager.h" #include "devices/3DConnexionClient.h" #include "scripting/AccountScriptingInterface.h" @@ -143,6 +145,7 @@ #include "ui/AddressBarDialog.h" #include "ui/UpdateDialog.h" +//#include // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU #if defined(Q_OS_WIN) @@ -289,6 +292,7 @@ bool setupEssentials(int& argc, char** argv) { auto autoUpdater = DependencyManager::set(); auto pathUtils = DependencyManager::set(); auto actionFactory = DependencyManager::set(); + auto userInputMapper = DependencyManager::set(); return true; } @@ -335,13 +339,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _isThrottleFPSEnabled(true), _aboutToQuit(false), _notifiedPacketVersionMismatchThisDomain(false), - _glWidget(new GLCanvas()), _domainConnectionRefusals(QList()), _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0), _applicationOverlay() { setInstance(this); + Plugin::setContainer(this); #ifdef Q_OS_WIN installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif @@ -520,11 +524,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : ResourceCache::setRequestLimit(3); + _glWidget = new GLCanvas(); _window->setCentralWidget(_glWidget); _window->restoreGeometry(); - _window->setVisible(true); + _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); #ifdef Q_OS_MAC @@ -540,17 +545,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); - _fullscreenMenuWidget->setParent(_glWidget); - _menuBarHeight = Menu::getInstance()->height(); - if (Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen)) { - setFullscreen(true); // Initialize menu bar show/hide - } + _offscreenContext = new OffscreenGlCanvas(); + _offscreenContext->create(_glWidget->context()->contextHandle()); + _offscreenContext->makeCurrent(); + initializeGL(); + _toolWindow = new ToolWindow(); _toolWindow->setWindowFlags(_toolWindow->windowFlags() | Qt::WindowStaysOnTopHint); _toolWindow->setWindowTitle("Tools"); - // initialization continues in initializeGL when OpenGL context is ready + _offscreenContext->makeCurrent(); // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); @@ -579,9 +584,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : this, &Application::checkSkeleton, Qt::QueuedConnection); // Setup the userInputMapper with the actions + auto userInputMapper = DependencyManager::get(); + connect(userInputMapper.data(), &UserInputMapper::actionEvent, &_controllerScriptingInterface, &AbstractControllerScriptingInterface::actionEvent); + // Setup the keyboardMouseDevice and the user input mapper with the default bindings - _keyboardMouseDevice.registerToUserInputMapper(_userInputMapper); - _keyboardMouseDevice.assignDefaultInputMapping(_userInputMapper); + _keyboardMouseDevice->registerToUserInputMapper(*userInputMapper); + _keyboardMouseDevice->assignDefaultInputMapping(*userInputMapper); // check first run... if (_firstRun.get()) { @@ -623,9 +631,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : #endif this->installEventFilter(this); - // The offscreen UI needs to intercept the mouse and keyboard - // events coming from the onscreen window - _glWidget->installEventFilter(DependencyManager::get().data()); // initialize our face trackers after loading the menu settings auto faceshiftTracker = DependencyManager::get(); @@ -643,10 +648,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : setActiveEyeTracker(); #endif + _oldHandMouseX[0] = -1; + _oldHandMouseY[0] = -1; + _oldHandMouseX[1] = -1; + _oldHandMouseY[1] = -1; + _oldHandLeftClick[0] = false; + _oldHandRightClick[0] = false; + _oldHandLeftClick[1] = false; + _oldHandRightClick[1] = false; + auto applicationUpdater = DependencyManager::get(); connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog); applicationUpdater->checkForUpdate(); + // Now that menu is initalized we can sync myAvatar with it's state. + _myAvatar->updateMotionBehaviorFromMenu(); + _myAvatar->updateStandingHMDModeFromMenu(); + // the 3Dconnexion device wants to be initiliazed after a window is displayed. ConnexionClient::getInstance().init(); @@ -656,7 +674,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : void Application::aboutToQuit() { emit beforeAboutToQuit(); - + getActiveDisplayPlugin()->deactivate(); _aboutToQuit = true; cleanupBeforeQuit(); } @@ -737,8 +755,13 @@ Application::~Application() { ModelEntityItem::cleanupLoadedAnimations(); - // stop the glWidget frame timer so it doesn't call paintGL - _glWidget->stopFrameTimer(); + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { + QString name = inputPlugin->getName(); + QAction* action = Menu::getInstance()->getActionForOption(name); + if (action->isChecked()) { + inputPlugin->deactivate(); + } + } // remove avatars from physics engine DependencyManager::get()->clearOtherAvatars(); @@ -781,7 +804,6 @@ void Application::initializeGL() { isInitialized = true; } #endif - // Where the gpuContext is initialized and where the TRUE Backend is created and assigned gpu::Context::init(); _gpuContext = std::make_shared(); @@ -794,7 +816,7 @@ void Application::initializeGL() { // texture resources initializeUi(); qCDebug(interfaceapp, "Initialized Offscreen UI."); - _glWidget->makeCurrent(); + _offscreenContext->makeCurrent(); // call Menu getInstance static method to set up the menu // Needs to happen AFTER the QML UI initialization @@ -841,36 +863,60 @@ void Application::initializeUi() { UpdateDialog::registerType(); auto offscreenUi = DependencyManager::get(); - offscreenUi->create(_glWidget->context()->contextHandle()); - offscreenUi->resize(_glWidget->size()); + offscreenUi->create(_offscreenContext->getContext()); offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); offscreenUi->load("RootMenu.qml"); + _glWidget->installEventFilter(offscreenUi.data()); VrMenu::load(); VrMenu::executeQueuedLambdas(); - offscreenUi->setMouseTranslator([this](const QPointF& p){ - if (OculusManager::isConnected()) { - glm::vec2 pos = _compositor.screenToOverlay(toGlm(p)); - return QPointF(pos.x, pos.y); + offscreenUi->setMouseTranslator([=](const QPointF& pt) { + QPointF result = pt; + auto displayPlugin = getActiveDisplayPlugin(); + if (displayPlugin->isHmd()) { + auto resultVec = _compositor.screenToOverlay(toGlm(pt)); + result = QPointF(resultVec.x, resultVec.y); } - return QPointF(p); + return result; }); offscreenUi->resume(); connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect & r){ static qreal oldDevicePixelRatio = 0; - qreal devicePixelRatio = _glWidget->devicePixelRatio(); + qreal devicePixelRatio = getActiveDisplayPlugin()->devicePixelRatio(); if (devicePixelRatio != oldDevicePixelRatio) { oldDevicePixelRatio = devicePixelRatio; qDebug() << "Device pixel ratio changed, triggering GL resize"; resizeGL(); } }); + + // This will set up the input plugins UI + _activeInputPlugins.clear(); + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { + QString name = inputPlugin->getName(); + if (name == KeyboardMouseDevice::NAME) { + _keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky + } + } + updateInputModes(); +} + +template +void doInBatch(RenderArgs* args, F f) { + gpu::Batch batch; + f(batch); + args->_context->render(batch); } void Application::paintGL() { PROFILE_RANGE(__FUNCTION__); - _glWidget->makeCurrent(); + if (nullptr == _displayPlugin) { + return; + } + auto displayPlugin = getActiveDisplayPlugin(); + displayPlugin->preRender(); + _offscreenContext->makeCurrent(); auto lodManager = DependencyManager::get(); @@ -880,10 +926,6 @@ void Application::paintGL() { RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); PerformanceTimer perfTimer("paintGL"); - //Need accurate frame timing for the oculus rift - if (OculusManager::isConnected()) { - OculusManager::beginFrameTiming(); - } PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); @@ -919,7 +961,6 @@ void Application::paintGL() { { PerformanceTimer perfTimer("renderOverlay"); - // NOTE: There is no batch associated with this renderArgs // the ApplicationOverlay class assumes it's viewport is setup to be the device size QSize size = qApp->getDeviceSize(); @@ -940,20 +981,18 @@ void Application::paintGL() { // Always use the default eye position, not the actual head eye position. // Using the latter will cause the camera to wobble with idle animations, // or with changes from the face tracker - _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); - if (!OculusManager::isConnected()) { - // If not using an HMD, grab the camera orientation directly + renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE; + + if (!getActiveDisplayPlugin()->isHmd()) { + _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); _myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation()); } else { - // In an HMD, people can look up and down with their actual neck, and the - // per-eye HMD pose will be applied later. So set the camera orientation - // to only the yaw, excluding pitch and roll, i.e. an orientation that - // is orthongonal to the (body's) Y axis - _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()); + mat4 camMat = _myAvatar->getSensorToWorldMatrix() * _myAvatar->getHMDSensorMatrix(); + _myCamera.setPosition(extractTranslation(camMat)); + _myCamera.setRotation(glm::quat_cast(camMat)); } - } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { - if (OculusManager::isConnected()) { + if (isHMDMode()) { _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()); } else { _myCamera.setRotation(_myAvatar->getHead()->getOrientation()); @@ -976,60 +1015,131 @@ void Application::paintGL() { } // Update camera position - if (!OculusManager::isConnected()) { + if (!isHMDMode()) { _myCamera.update(1.0f / _fps); } - if (OculusManager::isConnected()) { - //When in mirror mode, use camera rotation. Otherwise, use body rotation - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - OculusManager::display(_glWidget, &renderArgs, _myCamera.getRotation(), _myCamera.getPosition(), _myCamera); - } else { - OculusManager::display(_glWidget, &renderArgs, _myAvatar->getWorldAlignedOrientation(), _myAvatar->getDefaultEyePosition(), _myCamera); - } - } else if (TV3DManager::isConnected()) { - TV3DManager::display(&renderArgs, _myCamera); - } else { + + // Primary rendering pass + auto framebufferCache = DependencyManager::get(); + QSize size = framebufferCache->getFrameBufferSize(); + { PROFILE_RANGE(__FUNCTION__ "/mainRender"); // Viewport is assigned to the size of the framebuffer QSize size = DependencyManager::get()->getFrameBufferSize(); renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); - - displaySide(&renderArgs, _myCamera); - { - auto geometryCache = DependencyManager::get(); - auto primaryFbo = DependencyManager::get()->getPrimaryFramebufferDepthColor(); - gpu::Batch batch; - - if (renderArgs._renderMode == RenderArgs::MIRROR_RENDER_MODE) { - batch.blit(primaryFbo, glm::ivec4(0, 0, _renderResolution.x, _renderResolution.y), - nullptr, glm::ivec4(_glWidget->getDeviceSize().width(), 0, 0, _glWidget->getDeviceSize().height())); - } else { - batch.blit(primaryFbo, glm::ivec4(0, 0, _renderResolution.x, _renderResolution.y), - nullptr, glm::ivec4(0, 0, _glWidget->getDeviceSize().width(), _glWidget->getDeviceSize().height())); - } - - batch.setFramebuffer(nullptr); - - renderArgs._context->render(batch); + PROFILE_RANGE(__FUNCTION__ "/clear"); + doInBatch(&renderArgs, [&](gpu::Batch& batch) { + auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); + batch.setFramebuffer(primaryFbo); + // clear the normal and specular buffers + batch.clearFramebuffer( + gpu::Framebuffer::BUFFER_COLOR0 | + gpu::Framebuffer::BUFFER_COLOR1 | + gpu::Framebuffer::BUFFER_COLOR2 | + gpu::Framebuffer::BUFFER_DEPTH, + vec4(vec3(0), 1), 1.0, 0.0); + }); } - _compositor.displayOverlayTexture(&renderArgs); + if (displayPlugin->isStereo()) { + PROFILE_RANGE(__FUNCTION__ "/stereoRender"); + QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height())); + glEnable(GL_SCISSOR_TEST); + for_each_eye([&](Eye eye){ + // Load the view frustum, used by meshes + Camera eyeCamera; + if (qApp->isHMDMode()) { + // Allow the displayPlugin to compose the final eye transform, based on the most up-to-date head motion. + eyeCamera.setTransform(displayPlugin->getModelview(eye, _myAvatar->getSensorToWorldMatrix())); + } else { + eyeCamera.setTransform(displayPlugin->getModelview(eye, _myCamera.getTransform())); + } + eyeCamera.setProjection(displayPlugin->getProjection(eye, _myCamera.getProjection())); + renderArgs._viewport = toGlm(currentViewport); + doInBatch(&renderArgs, [&](gpu::Batch& batch) { + batch.setViewportTransform(renderArgs._viewport); + batch.setStateScissorRect(renderArgs._viewport); + }); + displaySide(&renderArgs, eyeCamera); + }, [&] { + currentViewport.moveLeft(currentViewport.width()); + }); + glDisable(GL_SCISSOR_TEST); + } else { + PROFILE_RANGE(__FUNCTION__ "/monoRender"); + renderArgs._viewport = gpu::Vec4i(0, 0, size.width(), size.height()); + // Viewport is assigned to the size of the framebuffer + doInBatch(&renderArgs, [&](gpu::Batch& batch) { + batch.setViewportTransform(renderArgs._viewport); + batch.setStateScissorRect(renderArgs._viewport); + }); + displaySide(&renderArgs, _myCamera); + } + + doInBatch(&renderArgs, [](gpu::Batch& batch){ + batch.setFramebuffer(nullptr); + }); } - - if (!OculusManager::isConnected() || OculusManager::allowSwap()) { - PROFILE_RANGE(__FUNCTION__ "/bufferSwap"); - _glWidget->swapBuffers(); + // Overlay Composition, needs to occur after screen space effects have completed + { + PROFILE_RANGE(__FUNCTION__ "/compositor"); + auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFbo)); + if (displayPlugin->isStereo()) { + QRect currentViewport(QPoint(0, 0), QSize(size.width() / 2, size.height())); + glClear(GL_DEPTH_BUFFER_BIT); + for_each_eye([&](Eye eye) { + renderArgs._viewport = toGlm(currentViewport); + if (displayPlugin->isHmd()) { + _compositor.displayOverlayTextureHmd(&renderArgs, eye); + } else { + _compositor.displayOverlayTexture(&renderArgs); + } + }, [&] { + currentViewport.moveLeft(currentViewport.width()); + }); + } else { + glViewport(0, 0, size.width(), size.height()); + _compositor.displayOverlayTexture(&renderArgs); + } } - if (OculusManager::isConnected()) { - OculusManager::endFrameTiming(); + // deliver final composited scene to the display plugin + { + PROFILE_RANGE(__FUNCTION__ "/pluginOutput"); + auto primaryFbo = framebufferCache->getPrimaryFramebuffer(); + GLuint finalTexture = gpu::GLBackend::getTextureID(primaryFbo->getRenderBuffer(0)); + uvec2 finalSize = toGlm(size); + // Ensure the rendering context commands are completed when rendering + GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + // Ensure the sync object is flushed to the driver thread before releasing the context + // CRITICAL for the mac driver apparently. + glFlush(); + _offscreenContext->doneCurrent(); + + // Switches to the display plugin context + displayPlugin->preDisplay(); + // Ensure all operations from the previous context are complete before we try to read the fbo + glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(sync); + + { + PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); + displayPlugin->display(finalTexture, finalSize); + } + + { + PROFILE_RANGE(__FUNCTION__ "/bufferSwap"); + displayPlugin->finishFrame(); + } } + + _offscreenContext->makeCurrent(); _frameCount++; - _numFramesSinceLastResize++; Stats::getInstance()->setRenderDetails(renderArgs._details); @@ -1052,6 +1162,7 @@ void Application::audioMuteToggled() { } void Application::faceTrackerMuteToggled() { + QAction* muteAction = Menu::getInstance()->getActionForOption(MenuOption::MuteFaceTracking); Q_CHECK_PTR(muteAction); bool isMuted = getSelectedFaceTracker()->isMuted(); @@ -1068,42 +1179,44 @@ void Application::showEditEntitiesHelp() { InfoView::show(INFO_EDIT_ENTITIES_PATH); } -void Application::resetCameras(Camera& camera, const glm::uvec2& size) { - if (OculusManager::isConnected()) { - OculusManager::configureCamera(camera); - } else if (TV3DManager::isConnected()) { - TV3DManager::configureCamera(camera, size.x, size.y); - } else { - camera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), (float)size.x / size.y, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); - } +void Application::resizeEvent(QResizeEvent * event) { + resizeGL(); } void Application::resizeGL() { PROFILE_RANGE(__FUNCTION__); + if (nullptr == _displayPlugin) { + return; + } + + auto displayPlugin = getActiveDisplayPlugin(); // Set the desired FBO texture size. If it hasn't changed, this does nothing. // Otherwise, it must rebuild the FBOs - QSize renderSize; - if (OculusManager::isConnected()) { - renderSize = OculusManager::getRenderTargetSize(); - } else { - renderSize = _glWidget->getDeviceSize() * getRenderResolutionScale(); - } - - if (_renderResolution != toGlm(renderSize)) { + uvec2 framebufferSize = getActiveDisplayPlugin()->getRecommendedRenderSize(); + uvec2 renderSize = uvec2(vec2(framebufferSize) * getRenderResolutionScale()); + if (_renderResolution != renderSize) { _numFramesSinceLastResize = 0; - _renderResolution = toGlm(renderSize); - DependencyManager::get()->setFrameBufferSize(renderSize); + _renderResolution = renderSize; + DependencyManager::get()->setFrameBufferSize(fromGlm(renderSize)); + // Possible change in aspect ratio loadViewFrustum(_myCamera, _viewFrustum); + float fov = glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES); + float aspectRatio = aspect(_renderResolution); + _myCamera.setProjection(glm::perspective(fov, aspectRatio, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)); } - resetCameras(_myCamera, _renderResolution); auto offscreenUi = DependencyManager::get(); - - auto canvasSize = _glWidget->size(); - offscreenUi->resize(canvasSize); - _glWidget->makeCurrent(); + auto uiSize = displayPlugin->getRecommendedUiSize(); + // Bit of a hack since there's no device pixel ratio change event I can find. + static qreal lastDevicePixelRatio = 0; + qreal devicePixelRatio = _window->devicePixelRatio(); + if (offscreenUi->size() != fromGlm(uiSize) || devicePixelRatio != lastDevicePixelRatio) { + offscreenUi->resize(fromGlm(uiSize)); + _offscreenContext->makeCurrent(); + lastDevicePixelRatio = devicePixelRatio; + } } bool Application::importSVOFromURL(const QString& urlString) { @@ -1213,8 +1326,10 @@ void Application::keyPressEvent(QKeyEvent* event) { return; } - if (activeWindow() == _window) { - _keyboardMouseDevice.keyPressEvent(event); + if (hasFocus()) { + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->keyPressEvent(event); + } bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); @@ -1343,9 +1458,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_J: if (isShifted) { _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() - 0.1f); - if (TV3DManager::isConnected()) { - TV3DManager::configureCamera(_myCamera, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); - } } else { _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(-0.001, 0, 0)); } @@ -1355,10 +1467,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_M: if (isShifted) { _viewFrustum.setFocalLength(_viewFrustum.getFocalLength() + 0.1f); - if (TV3DManager::isConnected()) { - TV3DManager::configureCamera(_myCamera, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight()); - } - } else { _myCamera.setEyeOffsetPosition(_myCamera.getEyeOffsetPosition() + glm::vec3(0.001, 0, 0)); } @@ -1402,6 +1510,9 @@ void Application::keyPressEvent(QKeyEvent* event) { Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)); cameraMenuChanged(); break; + case Qt::Key_O: + _overlayConductor.setEnabled(!_overlayConductor.getEnabled()); + break; case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; @@ -1440,8 +1551,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; } case Qt::Key_Escape: { - OculusManager::abandonCalibration(); - + getActiveDisplayPlugin()->abandonCalibration(); if (!event->isAutoRepeat()) { // this starts the HFCancelEvent HFBackEvent startBackEvent(HFBackEvent::startType()); @@ -1459,17 +1569,12 @@ void Application::keyPressEvent(QKeyEvent* event) { } -#define VR_MENU_ONLY_IN_HMD void Application::keyReleaseEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Alt && _altPressed && _window->isActiveWindow()) { -#ifdef VR_MENU_ONLY_IN_HMD - if (isHMDMode()) { -#endif + if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) { + if (getActiveDisplayPlugin()->isStereo()) { VrMenu::toggle(); -#ifdef VR_MENU_ONLY_IN_HMD } -#endif } _keysPressed.remove(event->key()); @@ -1481,7 +1586,9 @@ void Application::keyReleaseEvent(QKeyEvent* event) { return; } - _keyboardMouseDevice.keyReleaseEvent(event); + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->keyReleaseEvent(event); + } switch (event->key()) { case Qt::Key_Space: { @@ -1508,9 +1615,14 @@ void Application::keyReleaseEvent(QKeyEvent* event) { } void Application::focusOutEvent(QFocusEvent* event) { - _keyboardMouseDevice.focusOutEvent(event); - SixenseManager::getInstance().focusOutEvent(); - SDL2Manager::getInstance()->focusOutEvent(); + auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); + foreach(auto inputPlugin, inputPlugins) { + QString name = inputPlugin->getName(); + QAction* action = Menu::getInstance()->getActionForOption(name); + if (action && action->isChecked()) { + inputPlugin->pluginFocusOutEvent(); + } + } ConnexionData::getInstance().focusOutEvent(); // synthesize events for keys currently pressed, since we may not get their release events @@ -1533,18 +1645,6 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { return; } - if (Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen) - && !Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) { - // Show/hide menu bar in fullscreen - if (event->globalY() > _menuBarHeight) { - _fullscreenMenuWidget->setFixedHeight(0); - Menu::getInstance()->setFixedHeight(0); - } else { - _fullscreenMenuWidget->setFixedHeight(_menuBarHeight); - Menu::getInstance()->setFixedHeight(_menuBarHeight); - } - } - _entities.mouseMoveEvent(event, deviceID); _controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts @@ -1553,8 +1653,8 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { return; } - if (deviceID == 0) { - _keyboardMouseDevice.mouseMoveEvent(event, deviceID); + if (deviceID == 0 && Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->mouseMoveEvent(event, deviceID); } } @@ -1575,9 +1675,9 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { } - if (activeWindow() == _window) { - if (deviceID == 0) { - _keyboardMouseDevice.mousePressEvent(event); + if (hasFocus()) { + if (deviceID == 0 && Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->mousePressEvent(event); } if (event->button() == Qt::LeftButton) { @@ -1590,7 +1690,15 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { sendEvent(this, &actionEvent); } else if (event->button() == Qt::RightButton) { - // right click items here + // "right click" on controllers to toggle the overlay + if (deviceID > 0) { + _overlayConductor.setEnabled(!_overlayConductor.getEnabled()); + } + } else if (event->button() == Qt::MiddleButton) { + // mouse middle click to toggle the overlay + if (deviceID == 0) { + _overlayConductor.setEnabled(!_overlayConductor.getEnabled()); + } } } } @@ -1617,9 +1725,9 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { return; } - if (activeWindow() == _window) { - if (deviceID == 0) { - _keyboardMouseDevice.mouseReleaseEvent(event); + if (hasFocus()) { + if (deviceID == 0 && Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->mouseReleaseEvent(event); } if (event->button() == Qt::LeftButton) { @@ -1647,10 +1755,12 @@ void Application::touchUpdateEvent(QTouchEvent* event) { return; } - _keyboardMouseDevice.touchUpdateEvent(event); + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->touchUpdateEvent(event); + } bool validTouch = false; - if (activeWindow() == _window) { + if (hasFocus()) { const QList& tPoints = event->touchPoints(); _touchAvg = vec2(); int numTouches = tPoints.count(); @@ -1681,7 +1791,9 @@ void Application::touchBeginEvent(QTouchEvent* event) { return; } - _keyboardMouseDevice.touchBeginEvent(event); + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->touchBeginEvent(event); + } } @@ -1696,7 +1808,9 @@ void Application::touchEndEvent(QTouchEvent* event) { return; } - _keyboardMouseDevice.touchEndEvent(event); + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->touchEndEvent(event); + } // put any application specific touch behavior below here.. _touchDragStartedAvg = _touchAvg; @@ -1712,8 +1826,10 @@ void Application::wheelEvent(QWheelEvent* event) { if (_controllerScriptingInterface.isWheelCaptured()) { return; } - - _keyboardMouseDevice.wheelEvent(event); + + if (Menu::getInstance()->isOptionChecked(KeyboardMouseDevice::NAME)) { + _keyboardMouseDevice->wheelEvent(event); + } } void Application::dropEvent(QDropEvent *event) { @@ -1845,7 +1961,15 @@ void Application::idle() { { PerformanceTimer perfTimer("updateGL"); PerformanceWarning warn(showWarnings, "Application::idle()... updateGL()"); - _glWidget->updateGL(); + getActiveDisplayPlugin()->idle(); + auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); + foreach(auto inputPlugin, inputPlugins) { + QString name = inputPlugin->getName(); + QAction* action = Menu::getInstance()->getActionForOption(name); + if (action && action->isChecked()) { + inputPlugin->idle(); + } + } } { PerformanceTimer perfTimer("rest"); @@ -1860,13 +1984,19 @@ void Application::idle() { } } - // After finishing all of the above work, ensure the idle timer is set to the proper interval, + float secondsSinceLastUpdate = (float)timeSinceLastUpdate / 1000.0f; + _overlayConductor.update(secondsSinceLastUpdate); + // depending on whether we're throttling or not. // Once rendering is off on another thread we should be able to have Application::idle run at start(0) in // perpetuity and not expect events to get backed up. static const int IDLE_TIMER_DELAY_MS = 2; - idleTimer->start(_glWidget->isThrottleRendering() ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS); + int desiredInterval = getActiveDisplayPlugin()->isThrottled() ? THROTTLED_IDLE_TIMER_DELAY : IDLE_TIMER_DELAY_MS; + + if (idleTimer->interval() != desiredInterval) { + idleTimer->start(desiredInterval); + } } // check for any requested background downloads. @@ -1874,122 +2004,30 @@ void Application::idle() { lastIdleEnd = usecTimestampNow(); } -void Application::setFullscreen(bool fullscreen) { - if (Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen) != fullscreen) { - Menu::getInstance()->getActionForOption(MenuOption::Fullscreen)->setChecked(fullscreen); - } - - // The following code block is useful on platforms that can have a visible - // app menu in a fullscreen window. However the OSX mechanism hides the - // application menu for fullscreen apps, so the check is not required. -#ifndef Q_OS_MAC - if (Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) { - if (fullscreen) { - // Menu hide() disables menu commands, and show() after hide() doesn't work with Rift VR display. - // So set height instead. - _window->menuBar()->setMaximumHeight(0); - } else { - _window->menuBar()->setMaximumHeight(QWIDGETSIZE_MAX); - } - } else { - if (fullscreen) { - // Move menu to a QWidget floating above _glWidget so that show/hide doesn't adjust viewport. - _menuBarHeight = Menu::getInstance()->height(); - Menu::getInstance()->setParent(_fullscreenMenuWidget); - Menu::getInstance()->setFixedWidth(_window->windowHandle()->screen()->size().width()); - _fullscreenMenuWidget->show(); - } else { - // Restore menu to being part of MainWindow. - _fullscreenMenuWidget->hide(); - _window->setMenuBar(Menu::getInstance()); - _window->menuBar()->setMaximumHeight(QWIDGETSIZE_MAX); - } - } -#endif - - // Work around Qt bug that prevents floating menus being shown when in fullscreen mode. - // https://bugreports.qt.io/browse/QTBUG-41883 - // Known issue: Top-level menu items don't highlight when cursor hovers. This is probably a side-effect of the work-around. - // TODO: Remove this work-around once the bug has been fixed and restore the following lines. - //_window->setWindowState(fullscreen ? (_window->windowState() | Qt::WindowFullScreen) : - // (_window->windowState() & ~Qt::WindowFullScreen)); - _window->hide(); - if (fullscreen) { - _window->setWindowState(_window->windowState() | Qt::WindowFullScreen); - // The next line produces the following warning in the log: - // [WARNING][03 / 06 12:17 : 58] QWidget::setMinimumSize: (/ MainWindow) Negative sizes - // (0, -1) are not possible - // This is better than the alternative which is to have the window slightly less than fullscreen with a visible line - // of pixels for the remainder of the screen. - _window->setContentsMargins(0, 0, 0, -1); - } else { - _window->setWindowState(_window->windowState() & ~Qt::WindowFullScreen); - _window->setContentsMargins(0, 0, 0, 0); - } - - if (!_aboutToQuit) { - _window->show(); - } -} - -void Application::setEnable3DTVMode(bool enable3DTVMode) { - resizeGL(); -} - -void Application::setEnableVRMode(bool enableVRMode) { - if (Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode) != enableVRMode) { - Menu::getInstance()->getActionForOption(MenuOption::EnableVRMode)->setChecked(enableVRMode); - } - - if (enableVRMode) { - if (!OculusManager::isConnected()) { - // attempt to reconnect the Oculus manager - it's possible this was a workaround - // for the sixense crash - OculusManager::disconnect(); - OculusManager::connect(_glWidget->context()->contextHandle()); - _glWidget->setFocus(); - _glWidget->makeCurrent(); - } - OculusManager::recalibrate(); - } else { - OculusManager::abandonCalibration(); - OculusManager::disconnect(); - } - - resizeGL(); -} - void Application::setLowVelocityFilter(bool lowVelocityFilter) { - SixenseManager::getInstance().setLowVelocityFilter(lowVelocityFilter); + InputDevice::setLowVelocityFilter(lowVelocityFilter); } bool Application::mouseOnScreen() const { - if (OculusManager::isConnected()) { - ivec2 mouse = getMouse(); - if (!glm::all(glm::greaterThanEqual(mouse, ivec2()))) { - return false; - } - ivec2 size = toGlm(_glWidget->getDeviceSize()); - if (!glm::all(glm::lessThanEqual(mouse, size))) { - return false; - } + glm::ivec2 mousePosition = getTrueMouse(); + return (glm::all(glm::greaterThanEqual(mousePosition, glm::ivec2(0))) && + glm::all(glm::lessThanEqual(mousePosition, glm::ivec2(getCanvasSize())))); +} + +ivec2 Application::getMouseDragStarted() const { + if (isHMDMode()) { + return _compositor.screenToOverlay(getTrueMouseDragStarted()); } - return true; + return getTrueMouseDragStarted(); } ivec2 Application::getMouse() const { - if (OculusManager::isConnected()) { + if (isHMDMode()) { return _compositor.screenToOverlay(getTrueMouse()); } return getTrueMouse(); } -ivec2 Application::getMouseDragStarted() const { - if (OculusManager::isConnected()) { - return _compositor.screenToOverlay(getTrueMouseDragStarted()); - } - return getTrueMouseDragStarted(); -} ivec2 Application::getTrueMouseDragStarted() const { return _mouseDragStarted; @@ -2149,6 +2187,9 @@ void Application::loadSettings() { DependencyManager::get()->loadSettings(); DependencyManager::get()->loadSettings(); + // DONT CHECK IN + //DependencyManager::get()->setAutomaticLODAdjust(false); + Menu::getInstance()->loadSettings(); _myAvatar->loadData(); } @@ -2199,13 +2240,6 @@ void Application::init() { _mirrorCamera.setMode(CAMERA_MODE_MIRROR); - TV3DManager::connect(); - if (TV3DManager::isConnected()) { - QMetaObject::invokeMethod(Menu::getInstance()->getActionForOption(MenuOption::Fullscreen), - "trigger", - Qt::QueuedConnection); - } - _timerStart.start(); _lastTimeUpdated.start(); @@ -2220,18 +2254,7 @@ void Application::init() { DependencyManager::get()->loadSettings(addressLookupString); qCDebug(interfaceapp) << "Loaded settings"; - -#ifdef __APPLE__ - if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseEnabled)) { - // on OS X we only setup sixense if the user wants it on - this allows running without the hid_init crash - // if hydra support is temporarily not required - SixenseManager::getInstance().toggleSixense(true); - } -#else - // setup sixense - SixenseManager::getInstance().toggleSixense(true); -#endif - + Leapmotion::init(); RealSense::init(); @@ -2343,10 +2366,10 @@ void Application::updateMyAvatarLookAtPosition() { if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { // When I am in mirror mode, just look right at the camera (myself); don't switch gaze points because when physically // looking in a mirror one's eyes appear steady. - if (!OculusManager::isConnected()) { + if (!isHMDMode()) { lookAtSpot = _myCamera.getPosition(); } else { - lookAtSpot = _myCamera.getPosition() + OculusManager::getMidEyePosition(); + lookAtSpot = _myCamera.getPosition() + transformPoint(_myAvatar->getSensorToWorldMatrix(), extractTranslation(getHMDSensorPose())); } } else if (eyeTracker->isTracking() && (OculusManager::isConnected() || eyeTracker->isSimulating())) { // Look at the point that the user is looking at. @@ -2519,6 +2542,9 @@ void Application::update(float deltaTime) { updateLOD(); updateMouseRay(); // check what's under the mouse and update the mouse voxel + // update the avatar with a fresh HMD pose + _myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose()); + { PerformanceTimer perfTimer("devices"); DeviceTracker::updateAll(); @@ -2552,12 +2578,25 @@ void Application::update(float deltaTime) { _lastFaceTrackerUpdate = 0; } - SixenseManager::getInstance().update(deltaTime); - SDL2Manager::getInstance()->update(); } - _userInputMapper.update(deltaTime); - _keyboardMouseDevice.update(); + auto userInputMapper = DependencyManager::get(); + userInputMapper->setSensorToWorldMat(_myAvatar->getSensorToWorldMatrix()); + userInputMapper->update(deltaTime); + + // This needs to go after userInputMapper->update() because of the keyboard + bool jointsCaptured = false; + auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); + foreach(auto inputPlugin, inputPlugins) { + QString name = inputPlugin->getName(); + QAction* action = Menu::getInstance()->getActionForOption(name); + if (action && action->isChecked()) { + inputPlugin->pluginUpdate(deltaTime, jointsCaptured); + if (inputPlugin->isJointController()) { + jointsCaptured = true; + } + } + } // Dispatch input events _controllerScriptingInterface.updateInputControllers(); @@ -2566,12 +2605,12 @@ void Application::update(float deltaTime) { _myAvatar->clearDriveKeys(); if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) { if (!_controllerScriptingInterface.areActionsCaptured()) { - _myAvatar->setDriveKeys(FWD, _userInputMapper.getActionState(UserInputMapper::LONGITUDINAL_FORWARD)); - _myAvatar->setDriveKeys(BACK, _userInputMapper.getActionState(UserInputMapper::LONGITUDINAL_BACKWARD)); - _myAvatar->setDriveKeys(UP, _userInputMapper.getActionState(UserInputMapper::VERTICAL_UP)); - _myAvatar->setDriveKeys(DOWN, _userInputMapper.getActionState(UserInputMapper::VERTICAL_DOWN)); - _myAvatar->setDriveKeys(LEFT, _userInputMapper.getActionState(UserInputMapper::LATERAL_LEFT)); - _myAvatar->setDriveKeys(RIGHT, _userInputMapper.getActionState(UserInputMapper::LATERAL_RIGHT)); + _myAvatar->setDriveKeys(FWD, userInputMapper->getActionState(UserInputMapper::LONGITUDINAL_FORWARD)); + _myAvatar->setDriveKeys(BACK, userInputMapper->getActionState(UserInputMapper::LONGITUDINAL_BACKWARD)); + _myAvatar->setDriveKeys(UP, userInputMapper->getActionState(UserInputMapper::VERTICAL_UP)); + _myAvatar->setDriveKeys(DOWN, userInputMapper->getActionState(UserInputMapper::VERTICAL_DOWN)); + _myAvatar->setDriveKeys(LEFT, userInputMapper->getActionState(UserInputMapper::LATERAL_LEFT)); + _myAvatar->setDriveKeys(RIGHT, userInputMapper->getActionState(UserInputMapper::LATERAL_RIGHT)); if (deltaTime > FLT_EPSILON) { // For rotations what we really want are meausures of "angles per second" (in order to prevent // fps-dependent spin rates) so we need to scale the units of the controller contribution. @@ -2579,14 +2618,25 @@ void Application::update(float deltaTime) { // controllers to provide a delta_per_second value rather than a raw delta.) const float EXPECTED_FRAME_RATE = 60.0f; float timeFactor = EXPECTED_FRAME_RATE * deltaTime; - _myAvatar->setDriveKeys(ROT_UP, _userInputMapper.getActionState(UserInputMapper::PITCH_UP) / timeFactor); - _myAvatar->setDriveKeys(ROT_DOWN, _userInputMapper.getActionState(UserInputMapper::PITCH_DOWN) / timeFactor); - _myAvatar->setDriveKeys(ROT_LEFT, _userInputMapper.getActionState(UserInputMapper::YAW_LEFT) / timeFactor); - _myAvatar->setDriveKeys(ROT_RIGHT, _userInputMapper.getActionState(UserInputMapper::YAW_RIGHT) / timeFactor); + _myAvatar->setDriveKeys(ROT_UP, userInputMapper->getActionState(UserInputMapper::PITCH_UP) / timeFactor); + _myAvatar->setDriveKeys(ROT_DOWN, userInputMapper->getActionState(UserInputMapper::PITCH_DOWN) / timeFactor); + _myAvatar->setDriveKeys(ROT_LEFT, userInputMapper->getActionState(UserInputMapper::YAW_LEFT) / timeFactor); + _myAvatar->setDriveKeys(ROT_RIGHT, userInputMapper->getActionState(UserInputMapper::YAW_RIGHT) / timeFactor); } } - _myAvatar->setDriveKeys(BOOM_IN, _userInputMapper.getActionState(UserInputMapper::BOOM_IN)); - _myAvatar->setDriveKeys(BOOM_OUT, _userInputMapper.getActionState(UserInputMapper::BOOM_OUT)); + _myAvatar->setDriveKeys(BOOM_IN, userInputMapper->getActionState(UserInputMapper::BOOM_IN)); + _myAvatar->setDriveKeys(BOOM_OUT, userInputMapper->getActionState(UserInputMapper::BOOM_OUT)); + } + UserInputMapper::PoseValue leftHand = userInputMapper->getPoseState(UserInputMapper::LEFT_HAND); + UserInputMapper::PoseValue rightHand = userInputMapper->getPoseState(UserInputMapper::RIGHT_HAND); + Hand* hand = DependencyManager::get()->getMyAvatar()->getHand(); + setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX); + setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX); + if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) { + emulateMouse(hand, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK), + userInputMapper->getActionState(UserInputMapper::SHIFT), LEFT_HAND_INDEX); + emulateMouse(hand, userInputMapper->getActionState(UserInputMapper::RIGHT_HAND_CLICK), + userInputMapper->getActionState(UserInputMapper::SHIFT), RIGHT_HAND_INDEX); } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... @@ -2622,7 +2672,6 @@ void Application::update(float deltaTime) { _entitySimulation.applyActionChanges(); _entitySimulation.unlock(); - AvatarManager* avatarManager = DependencyManager::get().data(); _physicsEngine.deleteObjects(avatarManager->getObjectsToDelete()); _physicsEngine.addObjects(avatarManager->getObjectsToAdd()); @@ -2729,8 +2778,12 @@ void Application::update(float deltaTime) { QMetaObject::invokeMethod(DependencyManager::get().data(), "sendDownstreamAudioStatsPacket", Qt::QueuedConnection); } } + + // update sensorToWorldMatrix for rendering camera. + _myAvatar->updateSensorToWorldMatrix(); } + int Application::sendNackPackets() { if (Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) { @@ -2969,12 +3022,9 @@ void Application::queryOctree(NodeType_t serverType, PacketType::Value packetTyp }); } + bool Application::isHMDMode() const { - if (OculusManager::isConnected()) { - return true; - } else { - return false; - } + return getActiveDisplayPlugin()->isHmd(); } QRect Application::getDesirableApplicationGeometry() { @@ -3058,7 +3108,7 @@ QImage Application::renderAvatarBillboard(RenderArgs* renderArgs) { const int BILLBOARD_SIZE = 64; // Need to make sure the gl context is current here - _glWidget->makeCurrent(); + _offscreenContext->makeCurrent(); renderArgs->_renderMode = RenderArgs::DEFAULT_RENDER_MODE; renderRearViewMirror(renderArgs, QRect(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE), true); @@ -3253,7 +3303,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se PROFILE_RANGE(__FUNCTION__); PerformanceTimer perfTimer("display"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); - // transform by eye offset // load the view frustum loadViewFrustum(theCamera, _displayViewFrustum); @@ -3292,6 +3341,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se } renderArgs->_debugFlags = renderDebugFlags; _entities.render(renderArgs); + //ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, pendingChanges); } } @@ -3304,7 +3354,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se pendingChanges.resetItem(WorldBoxRenderData::_item, worldBoxRenderPayload); } else { - pendingChanges.updateItem(WorldBoxRenderData::_item, [](WorldBoxRenderData& payload) { payload._val++; @@ -3385,45 +3434,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se activeRenderingThread = nullptr; } -void Application::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const { - - // allow 3DTV/Oculus to override parameters from camera - _displayViewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - if (OculusManager::isConnected()) { - OculusManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - - } else if (TV3DManager::isConnected()) { - TV3DManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); - } -} - -glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) { - float horizontalScale = _glWidget->getDeviceWidth() / 2.0f; - float verticalScale = _glWidget->getDeviceHeight() / 2.0f; - - // -1,-1 is 0,windowHeight - // 1,1 is windowWidth,0 - - // -1,1 1,1 - // +-----------------------+ - // | | | - // | | | - // | -1,0 | | - // |-----------+-----------| - // | 0,0 | - // | | | - // | | | - // | | | - // +-----------------------+ - // -1,-1 1,-1 - - glm::vec2 screenPoint((projectedPoint.x + 1.0f) * horizontalScale, - ((projectedPoint.y + 1.0f) * -verticalScale) + _glWidget->getDeviceHeight()); - - return screenPoint; -} - void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool billboard) { auto originalViewport = renderArgs->_viewport; // Grab current viewport to reset it at the end @@ -3489,9 +3499,7 @@ void Application::resetSensors() { DependencyManager::get()->reset(); DependencyManager::get()->reset(); - OculusManager::reset(); - - //_leapmotion.reset(); + getActiveDisplayPlugin()->resetSensors(); QScreen* currentScreen = _window->windowHandle()->screen(); QWindow* mainWindow = _window->windowHandle(); @@ -4325,9 +4333,11 @@ void Application::takeSnapshot() { _snapshotShareDialog = new SnapshotShareDialog(fileName, _glWidget); } _snapshotShareDialog->show(); + } void Application::setVSyncEnabled() { + _glWidget->makeCurrent(); #if defined(Q_OS_WIN) bool vsyncOn = Menu::getInstance()->isOptionChecked(MenuOption::RenderTargetFramerateVSyncOn); if (wglewGetExtension("WGL_EXT_swap_control")) { @@ -4352,6 +4362,7 @@ void Application::setVSyncEnabled() { #else qCDebug(interfaceapp, "V-Sync is FORCED ON on this system\n"); #endif + _offscreenContext->makeCurrent(); } void Application::setThrottleFPSEnabled() { @@ -4517,37 +4528,36 @@ void Application::initPlugins() { void Application::shutdownPlugins() { } -glm::vec3 Application::getHeadPosition() const { - return OculusManager::getRelativePosition(); -} - - -glm::quat Application::getHeadOrientation() const { - return OculusManager::getOrientation(); -} - glm::uvec2 Application::getCanvasSize() const { return glm::uvec2(_glWidget->width(), _glWidget->height()); } +glm::uvec2 Application::getUiSize() const { + return getActiveDisplayPlugin()->getRecommendedUiSize(); +} + QSize Application::getDeviceSize() const { - return _glWidget->getDeviceSize(); -} - -ivec2 Application::getTrueMouse() const { - return toGlm(_glWidget->mapFromGlobal(QCursor::pos())); -} - -bool Application::isThrottleRendering() const { - return _glWidget->isThrottleRendering(); + return fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize()); } PickRay Application::computePickRay() const { return computePickRay(getTrueMouseX(), getTrueMouseY()); } +bool Application::isThrottleRendering() const { + return getActiveDisplayPlugin()->isThrottled(); +} + +ivec2 Application::getTrueMouse() const { + return toGlm(_glWidget->mapFromGlobal(QCursor::pos())); +} + bool Application::hasFocus() const { - return _glWidget->hasFocus(); + return getActiveDisplayPlugin()->hasFocus(); +} + +glm::vec2 Application::getViewportDimensions() const { + return toGlm(getDeviceSize()); } void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) { @@ -4562,12 +4572,226 @@ int Application::getMaxOctreePacketsPerSecond() { } qreal Application::getDevicePixelRatio() { - return _window ? _window->windowHandle()->devicePixelRatio() : 1.0; + return (_window && _window->windowHandle()) ? _window->windowHandle()->devicePixelRatio() : 1.0; +} + +DisplayPlugin * Application::getActiveDisplayPlugin() { + if (nullptr == _displayPlugin) { + updateDisplayMode(); + Q_ASSERT(_displayPlugin); + } + return _displayPlugin.data(); +} + +const DisplayPlugin * Application::getActiveDisplayPlugin() const { + return ((Application*)this)->getActiveDisplayPlugin(); +} + + +static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { + auto menu = Menu::getInstance(); + QString name = displayPlugin->getName(); + Q_ASSERT(!menu->menuItemExists(MenuOption::OutputMenu, name)); + + static QActionGroup* displayPluginGroup = nullptr; + if (!displayPluginGroup) { + displayPluginGroup = new QActionGroup(menu); + displayPluginGroup->setExclusive(true); + } + auto parent = menu->getMenu(MenuOption::OutputMenu); + auto action = menu->addActionToQMenuAndActionHash(parent, + name, 0, qApp, + SLOT(updateDisplayMode())); + action->setCheckable(true); + action->setChecked(active); + displayPluginGroup->addAction(action); + Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); +} + +static QVector> _currentDisplayPluginActions; + +void Application::updateDisplayMode() { + auto menu = Menu::getInstance(); + auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); + + static std::once_flag once; + std::call_once(once, [&] { + bool first = true; + foreach(auto displayPlugin, displayPlugins) { + addDisplayPluginToMenu(displayPlugin, first); + QObject::connect(displayPlugin.data(), &DisplayPlugin::requestRender, [this] { + paintGL(); + }); + QObject::connect(displayPlugin.data(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { + resizeGL(); + }); + + first = false; + } + }); + + + // Default to the first item on the list, in case none of the menu items match + DisplayPluginPointer newDisplayPlugin = displayPlugins.at(0); + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { + QString name = displayPlugin->getName(); + QAction* action = menu->getActionForOption(name); + if (action->isChecked()) { + newDisplayPlugin = displayPlugin; + break; + } + } + + auto offscreenUi = DependencyManager::get(); + DisplayPluginPointer oldDisplayPlugin = _displayPlugin; + if (oldDisplayPlugin != newDisplayPlugin) { + if (!_currentDisplayPluginActions.isEmpty()) { + auto menu = Menu::getInstance(); + foreach(auto itemInfo, _currentDisplayPluginActions) { + menu->removeMenuItem(itemInfo.first, itemInfo.second); + } + _currentDisplayPluginActions.clear(); + } + + if (newDisplayPlugin) { + _offscreenContext->makeCurrent(); + newDisplayPlugin->activate(); + _offscreenContext->makeCurrent(); + offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize())); + _offscreenContext->makeCurrent(); + } + + oldDisplayPlugin = _displayPlugin; + _displayPlugin = newDisplayPlugin; + + // Only show the hmd tools after the correct plugin has + // been activated so that it's UI is setup correctly + if (newDisplayPlugin->isHmd()) { + showDisplayPluginsTools(); + } + + if (oldDisplayPlugin) { + oldDisplayPlugin->deactivate(); + _offscreenContext->makeCurrent(); + } + emit activeDisplayPluginChanged(); + resetSensors(); + } + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); +} + +static QVector> _currentInputPluginActions; + + +static void addInputPluginToMenu(InputPluginPointer inputPlugin, bool active = false) { + auto menu = Menu::getInstance(); + QString name = inputPlugin->getName(); + Q_ASSERT(!menu->menuItemExists(MenuOption::InputMenu, name)); + + static QActionGroup* inputPluginGroup = nullptr; + if (!inputPluginGroup) { + inputPluginGroup = new QActionGroup(menu); + } + auto parent = menu->getMenu(MenuOption::InputMenu); + auto action = menu->addCheckableActionToQMenuAndActionHash(parent, + name, 0, active, qApp, + SLOT(updateInputModes())); + inputPluginGroup->addAction(action); + inputPluginGroup->setExclusive(false); + Q_ASSERT(menu->menuItemExists(MenuOption::InputMenu, name)); +} + + +void Application::updateInputModes() { + auto menu = Menu::getInstance(); + auto inputPlugins = PluginManager::getInstance()->getInputPlugins(); + static std::once_flag once; + std::call_once(once, [&] { + bool first = true; + foreach(auto inputPlugin, inputPlugins) { + addInputPluginToMenu(inputPlugin, first); + first = false; + } + }); + auto offscreenUi = DependencyManager::get(); + + InputPluginList newInputPlugins; + InputPluginList removedInputPlugins; + foreach(auto inputPlugin, inputPlugins) { + QString name = inputPlugin->getName(); + QAction* action = menu->getActionForOption(name); + if (action->isChecked() && !_activeInputPlugins.contains(inputPlugin)) { + _activeInputPlugins.append(inputPlugin); + newInputPlugins.append(inputPlugin); + } else if (!action->isChecked() && _activeInputPlugins.contains(inputPlugin)) { + _activeInputPlugins.removeOne(inputPlugin); + removedInputPlugins.append(inputPlugin); + } + } + + // A plugin was checked + if (newInputPlugins.size() > 0) { + foreach(auto newInputPlugin, newInputPlugins) { + newInputPlugin->activate(); + //newInputPlugin->installEventFilter(qApp); + //newInputPlugin->installEventFilter(offscreenUi.data()); + } + } + if (removedInputPlugins.size() > 0) { // A plugin was unchecked + foreach(auto removedInputPlugin, removedInputPlugins) { + removedInputPlugin->deactivate(); + //removedInputPlugin->removeEventFilter(qApp); + //removedInputPlugin->removeEventFilter(offscreenUi.data()); + } + } + + //if (newInputPlugins.size() > 0 || removedInputPlugins.size() > 0) { + // if (!_currentInputPluginActions.isEmpty()) { + // auto menu = Menu::getInstance(); + // foreach(auto itemInfo, _currentInputPluginActions) { + // menu->removeMenuItem(itemInfo.first, itemInfo.second); + // } + // _currentInputPluginActions.clear(); + // } + //} +} + +void Application::addMenu(const QString& menuName) { + Menu::getInstance()->addMenu(menuName); +} + +void Application::removeMenu(const QString& menuName) { + Menu::getInstance()->removeMenu(menuName); +} + +void Application::addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName) { + auto menu = Menu::getInstance(); + MenuWrapper* parentItem = menu->getMenu(path); + QAction* action = parentItem->addAction(name); + connect(action, &QAction::triggered, [=] { + onClicked(action->isChecked()); + }); + action->setCheckable(checkable); + action->setChecked(checked); + _currentDisplayPluginActions.push_back({ path, name }); + _currentInputPluginActions.push_back({ path, name }); +} + +void Application::removeMenuItem(const QString& menuName, const QString& menuItem) { + Menu::getInstance()->removeMenuItem(menuName, menuItem); +} + +bool Application::isOptionChecked(const QString& name) { + return Menu::getInstance()->isOptionChecked(name); +} + +void Application::setIsOptionChecked(const QString& path, bool checked) { + Menu::getInstance()->setIsOptionChecked(path, checked); } mat4 Application::getEyeProjection(int eye) const { if (isHMDMode()) { - return OculusManager::getEyeProjection(eye); + return getActiveDisplayPlugin()->getProjection((Eye)eye, _viewFrustum.getProjection()); } return _viewFrustum.getProjection(); @@ -4575,15 +4799,273 @@ mat4 Application::getEyeProjection(int eye) const { mat4 Application::getEyePose(int eye) const { if (isHMDMode()) { - return OculusManager::getEyePose(eye); + return getActiveDisplayPlugin()->getEyePose((Eye)eye); } return mat4(); } -mat4 Application::getHeadPose() const { +mat4 Application::getEyeOffset(int eye) const { if (isHMDMode()) { - return OculusManager::getHeadPose(); + mat4 identity; + return getActiveDisplayPlugin()->getModelview((Eye)eye, identity); + } + + return mat4(); +} + +mat4 Application::getHMDSensorPose() const { + if (isHMDMode()) { + return getActiveDisplayPlugin()->getHeadPose(); } return mat4(); } + +void Application::setFullscreen(const QScreen* target) { + if (!_window->isFullScreen()) { + _savedGeometry = _window->geometry(); + } +#ifdef Q_OS_MAC + _window->setGeometry(target->availableGeometry()); +#endif + _window->windowHandle()->setScreen((QScreen*)target); + _window->showFullScreen(); +} + +void Application::unsetFullscreen(const QScreen* avoid) { + _window->showNormal(); + + QRect targetGeometry = _savedGeometry; + if (avoid != nullptr) { + QRect avoidGeometry = avoid->geometry(); + if (avoidGeometry.contains(targetGeometry.topLeft())) { + QScreen* newTarget = primaryScreen(); + if (newTarget == avoid) { + foreach(auto screen, screens()) { + if (screen != avoid) { + newTarget = screen; + break; + } + } + } + targetGeometry = newTarget->availableGeometry(); + } + } +#ifdef Q_OS_MAC + QTimer* timer = new QTimer(); + timer->singleShot(2000, [=] { + _window->setGeometry(targetGeometry); + timer->deleteLater(); + }); +#else + _window->setGeometry(targetGeometry); +#endif +} + + +void Application::showDisplayPluginsTools() { + DependencyManager::get()->hmdTools(true); +} + +QGLWidget* Application::getPrimarySurface() { + return _glWidget; +} + +void Application::setActiveDisplayPlugin(const QString& pluginName) { + auto menu = Menu::getInstance(); + foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { + QString name = displayPlugin->getName(); + QAction* action = menu->getActionForOption(name); + if (pluginName == name) { + action->setChecked(true); + } + } + updateDisplayMode(); +} + +void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index) { + PalmData* palm; + bool foundHand = false; + for (size_t j = 0; j < hand->getNumPalms(); j++) { + if (hand->getPalms()[j].getSixenseID() == index) { + palm = &(hand->getPalms()[j]); + foundHand = true; + break; + } + } + if (!foundHand) { + PalmData newPalm(hand); + hand->getPalms().push_back(newPalm); + palm = &(hand->getPalms()[hand->getNumPalms() - 1]); + palm->setSixenseID(index); + } + + palm->setActive(pose.isValid()); + + // transform from sensor space, to world space, to avatar model space. + glm::mat4 poseMat = createMatFromQuatAndPos(pose.getRotation(), pose.getTranslation()); + glm::mat4 sensorToWorldMat = _myAvatar->getSensorToWorldMatrix(); + glm::mat4 modelMat = createMatFromQuatAndPos(_myAvatar->getOrientation(), _myAvatar->getPosition()); + glm::mat4 objectPose = glm::inverse(modelMat) * sensorToWorldMat * poseMat; + + glm::vec3 position = extractTranslation(objectPose); + glm::quat rotation = glm::quat_cast(objectPose); + + // Compute current velocity from position change + glm::vec3 rawVelocity; + if (deltaTime > 0.0f) { + rawVelocity = (position - palm->getRawPosition()) / deltaTime; + } else { + rawVelocity = glm::vec3(0.0f); + } + palm->setRawVelocity(rawVelocity); // meters/sec + + // Angular Velocity of Palm + glm::quat deltaRotation = rotation * glm::inverse(palm->getRawRotation()); + glm::vec3 angularVelocity(0.0f); + float rotationAngle = glm::angle(deltaRotation); + if ((rotationAngle > EPSILON) && (deltaTime > 0.0f)) { + angularVelocity = glm::normalize(glm::axis(deltaRotation)); + angularVelocity *= (rotationAngle / deltaTime); + palm->setRawAngularVelocity(angularVelocity); + } else { + palm->setRawAngularVelocity(glm::vec3(0.0f)); + } + + if (InputDevice::getLowVelocityFilter()) { + // Use a velocity sensitive filter to damp small motions and preserve large ones with + // no latency. + float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); + position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter); + rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter); + } + palm->setRawPosition(position); + palm->setRawRotation(rotation); + + // Store the one fingertip in the palm structure so we can track velocity + const float FINGER_LENGTH = 0.3f; // meters + const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); + const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; + glm::vec3 oldTipPosition = palm->getTipRawPosition(); + if (deltaTime > 0.0f) { + palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime); + } else { + palm->setTipVelocity(glm::vec3(0.0f)); + } + palm->setTipPosition(newTipPosition); +} + +void Application::emulateMouse(Hand* hand, float click, float shift, int index) { + // Locate the palm, if it exists and is active + PalmData* palm; + bool foundHand = false; + for (size_t j = 0; j < hand->getNumPalms(); j++) { + if (hand->getPalms()[j].getSixenseID() == index) { + palm = &(hand->getPalms()[j]); + foundHand = true; + break; + } + } + if (!foundHand || !palm->isActive()) { + return; + } + + // Process the mouse events + QPoint pos; + + unsigned int deviceID = index == 0 ? CONTROLLER_0_EVENT : CONTROLLER_1_EVENT; + + if (qApp->isHMDMode()) { + pos = qApp->getApplicationCompositor().getPalmClickLocation(palm); + } + else { + // Get directon relative to avatar orientation + glm::vec3 direction = glm::inverse(_myAvatar->getOrientation()) * palm->getFingerDirection(); + + // Get the angles, scaled between (-0.5,0.5) + float xAngle = (atan2(direction.z, direction.x) + M_PI_2); + float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)M_PI_2)); + auto canvasSize = qApp->getCanvasSize(); + // Get the pixel range over which the xAngle and yAngle are scaled + float cursorRange = canvasSize.x * InputDevice::getCursorPixelRangeMult(); + + pos.setX(canvasSize.x / 2.0f + cursorRange * xAngle); + pos.setY(canvasSize.y / 2.0f + cursorRange * yAngle); + + } + + //If we are off screen then we should stop processing, and if a trigger or bumper is pressed, + //we should unpress them. + if (pos.x() == INT_MAX) { + if (_oldHandLeftClick[index]) { + QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, 0); + + qApp->mouseReleaseEvent(&mouseEvent, deviceID); + + _oldHandLeftClick[index] = false; + } + if (_oldHandRightClick[index]) { + QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, Qt::RightButton, Qt::RightButton, 0); + + qApp->mouseReleaseEvent(&mouseEvent, deviceID); + + _oldHandRightClick[index] = false; + } + return; + } + + //If position has changed, emit a mouse move to the application + if (pos.x() != _oldHandMouseX[index] || pos.y() != _oldHandMouseY[index]) { + QMouseEvent mouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::NoButton, 0); + + // Only send the mouse event if the opposite left button isnt held down. + // Is this check necessary? + if (!_oldHandLeftClick[(int)(!index)]) { + qApp->mouseMoveEvent(&mouseEvent, deviceID); + } + } + _oldHandMouseX[index] = pos.x(); + _oldHandMouseY[index] = pos.y(); + + //We need separate coordinates for clicks, since we need to check if + //a magnification window was clicked on + int clickX = pos.x(); + int clickY = pos.y(); + //Set pos to the new click location, which may be the same if no magnification window is open + pos.setX(clickX); + pos.setY(clickY); + + // Right click + if (shift == 1.0f && click == 1.0f) { + if (!_oldHandRightClick[index]) { + _oldHandRightClick[index] = true; + + QMouseEvent mouseEvent(QEvent::MouseButtonPress, pos, Qt::RightButton, Qt::RightButton, 0); + + qApp->mousePressEvent(&mouseEvent, deviceID); + } + } else if (_oldHandRightClick[index]) { + QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, Qt::RightButton, Qt::RightButton, 0); + + qApp->mouseReleaseEvent(&mouseEvent, deviceID); + + _oldHandRightClick[index] = false; + } + + // Left click + if (shift != 1.0f && click == 1.0f) { + if (!_oldHandLeftClick[index]) { + _oldHandLeftClick[index] = true; + + QMouseEvent mouseEvent(QEvent::MouseButtonPress, pos, Qt::LeftButton, Qt::LeftButton, 0); + + qApp->mousePressEvent(&mouseEvent, deviceID); + } + } else if (_oldHandLeftClick[index]) { + QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, Qt::LeftButton, Qt::LeftButton, 0); + + qApp->mouseReleaseEvent(&mouseEvent, deviceID); + + _oldHandLeftClick[index] = false; + } +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 451fb2911c..0d308e7a8f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include "AudioClient.h" #include "Bookmarks.h" @@ -47,13 +49,12 @@ #include "Stars.h" #include "avatar/Avatar.h" #include "avatar/MyAvatar.h" -#include "devices/SixenseManager.h" +#include #include "scripting/ControllerScriptingInterface.h" #include "scripting/DialogsManagerScriptingInterface.h" #include "scripting/WebWindowClass.h" #include "ui/AudioStatsDialog.h" #include "ui/BandwidthDialog.h" -#include "ui/HMDToolsDialog.h" #include "ui/ModelsBrowser.h" #include "ui/OctreeStatsDialog.h" #include "ui/SnapshotShareDialog.h" @@ -62,10 +63,9 @@ #include "ui/overlays/Overlays.h" #include "ui/ApplicationOverlay.h" #include "ui/ApplicationCompositor.h" +#include "ui/OverlayConductor.h" #include "ui/RunningScriptsWidget.h" #include "ui/ToolWindow.h" -#include "ui/UserInputMapper.h" -#include "devices/KeyboardMouseDevice.h" #include "octree/OctreePacketProcessor.h" #include "UndoStackScriptingInterface.h" @@ -79,6 +79,7 @@ class QMouseEvent; class QSystemTrayIcon; class QTouchEvent; class QWheelEvent; +class OffscreenGlCanvas; class GLCanvas; class FaceTracker; @@ -86,6 +87,12 @@ class MainWindow; class Node; class ScriptEngine; +namespace gpu { + class Context; + typedef std::shared_ptr ContextPointer; +} + + static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; static const QString SVO_JSON_EXTENSION = ".svo.json"; @@ -124,7 +131,7 @@ class Application; typedef bool (Application::* AcceptURLMethod)(const QString &); -class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface { +class Application : public QApplication, public AbstractViewStateInterface, public AbstractScriptingServicesInterface, PluginContainer { Q_OBJECT friend class OctreePacketProcessor; @@ -136,7 +143,6 @@ public: static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); } static glm::vec3 getPositionForAudio() { return getInstance()->_myAvatar->getHead()->getPosition(); } static glm::quat getOrientationForAudio() { return getInstance()->_myAvatar->getHead()->getFinalOrientationInWorldFrame(); } - static UserInputMapper* getUserInputMapper() { return &getInstance()->_userInputMapper; } static void initPlugins(); static void shutdownPlugins(); @@ -179,6 +185,7 @@ public: bool eventFilter(QObject* object, QEvent* event); glm::uvec2 getCanvasSize() const; + glm::uvec2 getUiSize() const; QSize getDeviceSize() const; bool hasFocus() const; PickRay computePickRay() const; @@ -262,11 +269,6 @@ public: void displaySide(RenderArgs* renderArgs, Camera& whichCamera, bool selfAvatarOnly = false, bool billboard = false); virtual const glm::vec3& getShadowDistances() const { return _shadowDistances; } - - /// Computes the off-axis frustum parameters for the view frustum, taking mirroring into account. - virtual void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const; - virtual ViewFrustum* getCurrentViewFrustum() { return getDisplayViewFrustum(); } virtual QThread* getMainThread() { return thread(); } virtual float getSizeScale() const; @@ -277,6 +279,24 @@ public: virtual void endOverrideEnvironmentData() { _environment.endOverride(); } virtual qreal getDevicePixelRatio(); + // Plugin container support + virtual void addMenu(const QString& menuName); + virtual void removeMenu(const QString& menuName); + virtual void addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable, bool checked, const QString& groupName); + virtual void removeMenuItem(const QString& menuName, const QString& menuItem); + virtual bool isOptionChecked(const QString& name); + virtual void setIsOptionChecked(const QString& path, bool checked); + virtual void setFullscreen(const QScreen* target) override; + virtual void unsetFullscreen(const QScreen* avoid) override; + virtual void showDisplayPluginsTools() override; + virtual QGLWidget* getPrimarySurface() override; + + void setActiveDisplayPlugin(const QString& pluginName); +private: + DisplayPlugin * getActiveDisplayPlugin(); + const DisplayPlugin * getActiveDisplayPlugin() const; +public: + FileLogger* getLogger() { return _logger; } glm::vec2 getViewportDimensions() const; @@ -300,10 +320,9 @@ public: // rendering of several elements depend on that // TODO: carry that information on the Camera as a setting bool isHMDMode() const; - glm::quat getHeadOrientation() const; - glm::vec3 getHeadPosition() const; - glm::mat4 getHeadPose() const; + glm::mat4 getHMDSensorPose() const; glm::mat4 getEyePose(int eye) const; + glm::mat4 getEyeOffset(int eye) const; glm::mat4 getEyeProjection(int eye) const; QRect getDesirableApplicationGeometry(); @@ -353,6 +372,7 @@ signals: void fullAvatarURLChanged(const QString& newValue, const QString& modelName); void beforeAboutToQuit(); + void activeDisplayPluginChanged(); public slots: void setSessionUUID(const QUuid& sessionUUID); @@ -361,6 +381,8 @@ public slots: void nodeAdded(SharedNodePointer node); void nodeKilled(SharedNodePointer node); void packetSent(quint64 length); + void updateDisplayMode(); + void updateInputModes(); QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs); @@ -436,15 +458,8 @@ private slots: void connectedToDomain(const QString& hostname); - friend class HMDToolsDialog; - void setFullscreen(bool fullscreen); - void setEnable3DTVMode(bool enable3DTVMode); - void setEnableVRMode(bool enableVRMode); - void rotationModeChanged(); - glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint); - void closeMirrorView(); void restoreMirrorView(); void shrinkMirrorView(); @@ -472,6 +487,9 @@ private: void update(float deltaTime); + void setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index); + void emulateMouse(Hand* hand, float click, float shift, int index); + // Various helper functions called during update() void updateLOD(); void updateMouseRay(); @@ -500,6 +518,11 @@ private: int sendNackPackets(); bool _dependencyManagerIsSetup; + + OffscreenGlCanvas* _offscreenContext; + DisplayPluginPointer _displayPlugin; + InputPluginList _activeInputPlugins; + MainWindow* _window; ToolWindow* _toolWindow; @@ -537,11 +560,10 @@ private: OctreeQuery _octreeQuery; // NodeData derived class for querying octee cells from octree servers - KeyboardMouseDevice _keyboardMouseDevice; // Default input device, the good old keyboard mouse and maybe touchpad - UserInputMapper _userInputMapper; // User input mapper allowing to mapp different real devices to the action channels that the application has to offer - MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be) - Camera _myCamera; // My view onto the world - Camera _mirrorCamera; // Cammera for mirror view + KeyboardMouseDevice* _keyboardMouseDevice{ nullptr }; // Default input device, the good old keyboard mouse and maybe touchpad + MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be) + Camera _myCamera; // My view onto the world + Camera _mirrorCamera; // Cammera for mirror view QRect _mirrorViewRect; Setting::Handle _firstRun; @@ -628,9 +650,6 @@ private: void checkSkeleton(); - QWidget* _fullscreenMenuWidget = new QWidget(); - int _menuBarHeight; - QHash _acceptedExtensions; QList _domainConnectionRefusals; @@ -647,8 +666,16 @@ private: Overlays _overlays; ApplicationOverlay _applicationOverlay; ApplicationCompositor _compositor; + OverlayConductor _overlayConductor; + + int _oldHandMouseX[2]; + int _oldHandMouseY[2]; + bool _oldHandLeftClick[2]; + bool _oldHandRightClick[2]; int _numFramesSinceLastResize = 0; + bool _overlayEnabled = true; + QRect _savedGeometry; DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface(); }; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 889f63c2be..17c745bdba 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -46,11 +46,7 @@ QString modeToString(CameraMode mode) { } Camera::Camera() : - _mode(CAMERA_MODE_THIRD_PERSON), - _position(0.0f, 0.0f, 0.0f), - _projection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), 16.0f/9.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)), - _isKeepLookingAt(false), - _lookingAt(0.0f, 0.0f, 0.0f) + _projection(glm::perspective(glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES), 16.0f/9.0f, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP)) { } @@ -61,12 +57,33 @@ void Camera::update(float deltaTime) { return; } +void Camera::recompose() { + mat4 orientation = glm::mat4_cast(_rotation); + mat4 translation = glm::translate(mat4(), _position); + _transform = translation * orientation; +} + +void Camera::decompose() { + _position = vec3(_transform[3]); + _rotation = glm::quat_cast(_transform); +} + +void Camera::setTransform(const glm::mat4& transform) { + _transform = transform; + decompose(); +} + void Camera::setPosition(const glm::vec3& position) { - _position = position; + _position = position; + recompose(); + if (_isKeepLookingAt) { + lookAt(_lookingAt); + } } void Camera::setRotation(const glm::quat& rotation) { _rotation = rotation; + recompose(); if (_isKeepLookingAt) { lookAt(_lookingAt); } @@ -129,3 +146,21 @@ void Camera::keepLookingAt(const glm::vec3& point) { _isKeepLookingAt = true; _lookingAt = point; } + +void Camera::loadViewFrustum(ViewFrustum& frustum) const { + // We will use these below, from either the camera or head vectors calculated above + frustum.setProjection(getProjection()); + + // Set the viewFrustum up with the correct position and orientation of the camera + frustum.setPosition(getPosition()); + frustum.setOrientation(getRotation()); + + // Ask the ViewFrustum class to calculate our corners + frustum.calculate(); +} + +ViewFrustum Camera::toViewFrustum() const { + ViewFrustum result; + loadViewFrustum(result); + return result; +} diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 6eed39cf16..7f7515cf5f 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -43,24 +43,31 @@ public: void update( float deltaTime ); - void setRotation(const glm::quat& rotation); - void setProjection(const glm::mat4 & projection); + CameraMode getMode() const { return _mode; } void setMode(CameraMode m); - glm::quat getRotation() const { return _rotation; } - const glm::mat4& getProjection() const { return _projection; } - CameraMode getMode() const { return _mode; } + void loadViewFrustum(ViewFrustum& frustum) const; + ViewFrustum toViewFrustum() const; public slots: QString getModeString() const; void setModeString(const QString& mode); + glm::quat getRotation() const { return _rotation; } + void setRotation(const glm::quat& rotation); + glm::vec3 getPosition() const { return _position; } void setPosition(const glm::vec3& position); glm::quat getOrientation() const { return getRotation(); } void setOrientation(const glm::quat& orientation) { setRotation(orientation); } + const glm::mat4& getTransform() const { return _transform; } + void setTransform(const glm::mat4& transform); + + const glm::mat4& getProjection() const { return _projection; } + void setProjection(const glm::mat4& projection); + PickRay computePickRay(float x, float y); // These only work on independent cameras @@ -78,11 +85,17 @@ signals: void modeUpdated(const QString& newMode); private: - CameraMode _mode; + void recompose(); + void decompose(); + + CameraMode _mode{ CAMERA_MODE_THIRD_PERSON }; + glm::mat4 _transform; + glm::mat4 _projection; + + // derived glm::vec3 _position; glm::quat _rotation; - glm::mat4 _projection; - bool _isKeepLookingAt; + bool _isKeepLookingAt{ false }; glm::vec3 _lookingAt; }; diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 57c6ae40a7..0d0d335b67 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -9,12 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "Application.h" +#include "GLCanvas.h" + #include #include #include -#include "Application.h" -#include "GLCanvas.h" #include "MainWindow.h" const int MSECS_PER_FRAME_WHEN_THROTTLED = 66; @@ -63,7 +64,6 @@ int GLCanvas::getDeviceHeight() const { } void GLCanvas::initializeGL() { - Application::getInstance()->initializeGL(); setAttribute(Qt::WA_AcceptTouchEvents); setAcceptDrops(true); connect(Application::getInstance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(activeChanged(Qt::ApplicationState))); diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 6c53a17e04..bcaa9577d1 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -22,7 +22,7 @@ class GLCanvas : public QGLWidget { public: GLCanvas(); - + void stopFrameTimer(); bool isThrottleRendering() const; diff --git a/interface/src/MainWindow.cpp b/interface/src/MainWindow.cpp index e30de96890..16aedc4bb7 100644 --- a/interface/src/MainWindow.cpp +++ b/interface/src/MainWindow.cpp @@ -97,10 +97,6 @@ void MainWindow::changeEvent(QEvent* event) { } else { emit windowShown(true); } - - if (isFullScreen() != Menu::getInstance()->isOptionChecked(MenuOption::Fullscreen)) { - Menu::getInstance()->setIsOptionChecked(MenuOption::Fullscreen, isFullScreen()); - } } else if (event->type() == QEvent::ActivationChange) { if (isActiveWindow()) { emit windowShown(true); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 049c5de556..6ff479778b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -28,7 +28,6 @@ #include "devices/DdeFaceTracker.h" #include "devices/Faceshift.h" #include "devices/RealSense.h" -#include "devices/SixenseManager.h" #include "devices/3DConnexionClient.h" #include "MainWindow.h" #include "scripting/MenuScriptingInterface.h" @@ -221,9 +220,20 @@ Menu::Menu() { addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); + MenuWrapper* displayMenu = addMenu("Display"); + { + MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); + QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); + displayModeGroup->setExclusive(true); + } + MenuWrapper* avatarMenu = addMenu("Avatar"); QObject* avatar = DependencyManager::get()->getMyAvatar(); + MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu); + QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); + inputModeGroup->setExclusive(false); + MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); addActionToQMenuAndActionHash(avatarSizeMenu, MenuOption::IncreaseAvatarSize, @@ -242,26 +252,16 @@ Menu::Menu() { SLOT(resetSize())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::KeyboardMotorControl, - Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehavior())); + Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehaviorFromMenu())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ScriptedMotorControl, 0, true, - avatar, SLOT(updateMotionBehavior())); + avatar, SLOT(updateMotionBehaviorFromMenu())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, avatar, SLOT(updateMotionBehavior())); MenuWrapper* viewMenu = addMenu("View"); - - addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::Fullscreen, -#ifdef Q_OS_MAC - Qt::CTRL | Qt::META | Qt::Key_F, -#else - Qt::CTRL | Qt::Key_F, -#endif - false, - qApp, - SLOT(setFullscreen(bool))); + addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); MenuWrapper* cameraModeMenu = viewMenu->addMenu("Camera Mode"); QActionGroup* cameraModeGroup = new QActionGroup(cameraModeMenu); @@ -299,18 +299,11 @@ Menu::Menu() { dialogsManager.data(), SLOT(hmdTools(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::EnableVRMode, 0, - false, - qApp, - SLOT(setEnableVRMode(bool))); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0, - false, - qApp, - SLOT(setEnable3DTVMode(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::StandingHMDSensorMode, 0, false, + avatar, SLOT(updateStandingHMDModeFromMenu())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, @@ -471,30 +464,11 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandMouseInput, 0, true); + addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::LowVelocityFilter, 0, true, + qApp, SLOT(setLowVelocityFilter(bool))); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); - MenuWrapper* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); -#ifdef __APPLE__ - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, - MenuOption::SixenseEnabled, - 0, false, - &SixenseManager::getInstance(), - SLOT(toggleSixense(bool))); -#endif - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, - MenuOption::FilterSixense, - 0, - true, - &SixenseManager::getInstance(), - SLOT(setFilter(bool))); - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, - MenuOption::LowVelocityFilter, - 0, - true, - qApp, - SLOT(setLowVelocityFilter(bool))); - addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, MenuOption::SixenseMouseInput, 0, true); - MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7e1b76bf85..2231ddbe9a 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -80,6 +80,13 @@ public: const QKeySequence& shortcut = 0, QAction::MenuRole role = QAction::NoRole, int menuItemLocation = UNSPECIFIED_POSITION); + QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, + const QString& actionName, + const QKeySequence& shortcut = 0, + const bool checked = false, + const QObject* receiver = NULL, + const char* member = NULL, + int menuItemLocation = UNSPECIFIED_POSITION); void removeAction(MenuWrapper* menu, const QString& actionName); @@ -109,14 +116,6 @@ private: void addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName, int menuItemLocation = UNSPECIFIED_POSITION); - QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, - const QString& actionName, - const QKeySequence& shortcut = 0, - const bool checked = false, - const QObject* receiver = NULL, - const char* member = NULL, - int menuItemLocation = UNSPECIFIED_POSITION); - QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); @@ -186,23 +185,23 @@ namespace MenuOption { const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableCharacterController = "Enable avatar collisions"; - const QString EnableVRMode = "Enable VR Mode"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; const QString ExpandMyAvatarTiming = "Expand /myAvatar"; const QString ExpandOtherAvatarTiming = "Expand /otherAvatar"; const QString ExpandPaintGLTiming = "Expand /paintGL"; const QString ExpandUpdateTiming = "Expand /update"; const QString Faceshift = "Faceshift"; - const QString FilterSixense = "Smooth Sixense Movement"; const QString FirstPerson = "First Person"; const QString FivePointCalibration = "5 Point Calibration"; const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; - const QString Fullscreen = "Fullscreen"; const QString FullscreenMirror = "Fullscreen Mirror"; + const QString GlowWhenSpeaking = "Glow When Speaking"; + const QString HandMouseInput = "Enable Hand Mouse Input"; const QString HMDTools = "HMD Tools"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; + const QString InputMenu = "Avatar>Input Devices"; const QString KeyboardMotorControl = "Enable Keyboard Motor Control"; const QString LeapMotionOnHMD = "Leap Motion on HMD"; const QString LoadScript = "Open and Run Script File..."; @@ -222,6 +221,7 @@ namespace MenuOption { const QString OctreeStats = "Entity Statistics"; const QString OnePointCalibration = "1 Point Calibration"; const QString OnlyDisplayTopTen = "Only Display Top Ten"; + const QString OutputMenu = "Display>Mode"; const QString PackageModel = "Package Model..."; const QString Pair = "Pair"; const QString PhysicsShowOwned = "Highlight Simulation Ownership"; @@ -273,8 +273,7 @@ namespace MenuOption { const QString ShowIKConstraints = "Show IK Constraints"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; const QString ShowWhosLookingAtMe = "Show Who's Looking at Me"; - const QString SixenseEnabled = "Enable Hydra Support"; - const QString SixenseMouseInput = "Enable Sixense Mouse Input"; + const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; const QString SimulateEyeTracking = "Simulate"; const QString SMIEyeTracking = "SMI Eye Tracking"; const QString Stars = "Stars"; diff --git a/interface/src/UIUtil.cpp b/interface/src/UIUtil.cpp index af13ffe6c5..7b50975c92 100644 --- a/interface/src/UIUtil.cpp +++ b/interface/src/UIUtil.cpp @@ -12,8 +12,6 @@ #include #include -#include "GLCanvas.h" - #include "UIUtil.h" int UIUtil::getWindowTitleBarHeight(const QWidget* window) { diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 5a27b6089c..55809646c0 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -247,7 +247,7 @@ void Avatar::simulate(float deltaTime) { } void Avatar::slamPosition(const glm::vec3& newPosition) { - AvatarData::setPosition(newPosition); + setPosition(newPosition); _positionDeltaAccumulator = glm::vec3(0.0f); _velocity = glm::vec3(0.0f); _lastVelocity = glm::vec3(0.0f); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index ee59a01e07..1644f22b09 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -279,7 +279,7 @@ void AvatarManager::handleCollisionEvents(CollisionEvents& collisionEvents) { const QString& collisionSoundURL = myAvatar->getCollisionSoundURL(); if (!collisionSoundURL.isEmpty()) { const float velocityChange = glm::length(collision.velocityChange); - const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01; + const float MIN_AVATAR_COLLISION_ACCELERATION = 0.01f; const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION); if (!isSound) { diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 0ae395f61a..2cd87588d8 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -123,12 +123,15 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { auto eyeTracker = DependencyManager::get(); _isEyeTrackerConnected = eyeTracker->isTracking(); } - // Twist the upper body to follow the rotation of the head, but only do this with my avatar, - // since everyone else will see the full joint rotations for other people. - const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; - const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; - float currentTwist = getTorsoTwist(); - setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); + + if (!myAvatar->getStandingHMDSensorMode()) { + // Twist the upper body to follow the rotation of the head, but only do this with my avatar, + // since everyone else will see the full joint rotations for other people. + const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f; + const float BODY_FOLLOW_HEAD_FACTOR = 0.66f; + float currentTwist = getTorsoTwist(); + setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE); + } } if (!(_isFaceTrackerConnected || billboard)) { @@ -365,14 +368,20 @@ bool Head::isLookingAtMe() { glm::quat Head::getCameraOrientation() const { // NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so // you may wonder why this code is here. This method will be called while in Oculus mode to determine how - // to change the driving direction while in Oculus mode. It is used to support driving toward where you're + // to change the driving direction while in Oculus mode. It is used to support driving toward where you're // head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not // always the same. if (qApp->isHMDMode()) { - return getOrientation(); + MyAvatar* myAvatar = dynamic_cast(_owningAvatar); + if (myAvatar && myAvatar->getStandingHMDSensorMode()) { + return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation(); + } else { + return getOrientation(); + } + } else { + Avatar* owningAvatar = static_cast(_owningAvatar); + return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f))); } - Avatar* owningAvatar = static_cast(_owningAvatar); - return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f))); } glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const { diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 59eb899f54..18c29d49fe 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -34,7 +34,6 @@ #include #include "devices/Faceshift.h" -#include "devices/OculusManager.h" #include "Application.h" #include "AvatarManager.h" @@ -97,6 +96,15 @@ MyAvatar::MyAvatar(RigPointer rig) : _eyeContactTarget(LEFT_EYE), _realWorldFieldOfView("realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES), + _hmdSensorMatrix(), + _hmdSensorOrientation(), + _hmdSensorPosition(), + _bodySensorMatrix(), + _sensorToWorldMatrix(), + _standingHMDSensorMode(false), + _goToPending(false), + _goToPosition(), + _goToOrientation(), _rig(rig), _prevShouldDrawHead(true) { @@ -142,6 +150,13 @@ void MyAvatar::reset() { } void MyAvatar::update(float deltaTime) { + + if (_goToPending) { + setPosition(_goToPosition); + setOrientation(_goToOrientation); + _goToPending = false; + } + if (_referential) { _referential->update(); } @@ -149,6 +164,7 @@ void MyAvatar::update(float deltaTime) { Head* head = getHead(); head->relaxLean(deltaTime); updateFromTrackers(deltaTime); + // Get audio loudness data from audio input device auto audio = DependencyManager::get(); head->setAudioLoudness(audio->getLastInputLoudness()); @@ -228,6 +244,41 @@ void MyAvatar::simulate(float deltaTime) { maybeUpdateBillboard(); } +glm::mat4 MyAvatar::getSensorToWorldMatrix() const { + if (getStandingHMDSensorMode()) { + return _sensorToWorldMatrix; + } else { + return createMatFromQuatAndPos(getWorldAlignedOrientation(), getDefaultEyePosition()); + } +} + +// best called at start of main loop just after we have a fresh hmd pose. +// update internal body position from new hmd pose. +void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { + // update the sensorMatrices based on the new hmd pose + _hmdSensorMatrix = hmdSensorMatrix; + _hmdSensorPosition = extractTranslation(hmdSensorMatrix); + _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); + _bodySensorMatrix = deriveBodyFromHMDSensor(); + + if (getStandingHMDSensorMode()) { + // set the body position/orientation to reflect motion due to the head. + auto worldMat = _sensorToWorldMatrix * _bodySensorMatrix; + setPosition(extractTranslation(worldMat)); + setOrientation(glm::quat_cast(worldMat)); + } +} + +// best called at end of main loop, just before rendering. +// update sensor to world matrix from current body position and hmd sensor. +// This is so the correct camera can be used for rendering. +void MyAvatar::updateSensorToWorldMatrix() { + // update the sensor mat so that the body position will end up in the desired + // position when driven from the head. + glm::mat4 desiredMat = createMatFromQuatAndPos(getOrientation(), getPosition()); + _sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix); +} + // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; @@ -242,7 +293,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) { bool inFacetracker = tracker && !tracker->isMuted(); if (inHmd) { - estimatedPosition = qApp->getHeadPosition(); + estimatedPosition = extractTranslation(getHMDSensorMatrix()); estimatedPosition.x *= -1.0f; _trackedHeadPosition = estimatedPosition; @@ -286,15 +337,18 @@ void MyAvatar::updateFromTrackers(float deltaTime) { Head* head = getHead(); if (inHmd || isPlaying()) { - head->setDeltaPitch(estimatedRotation.x); - head->setDeltaYaw(estimatedRotation.y); + if (!getStandingHMDSensorMode()) { + head->setDeltaPitch(estimatedRotation.x); + head->setDeltaYaw(estimatedRotation.y); + head->setDeltaRoll(estimatedRotation.z); + } } else { float magnifyFieldOfView = qApp->getFieldOfView() / _realWorldFieldOfView.get(); head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView); head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); + head->setDeltaRoll(estimatedRotation.z); } - head->setDeltaRoll(estimatedRotation.z); // Update torso lean distance based on accelerometer data const float TORSO_LENGTH = 0.5f; @@ -309,10 +363,12 @@ void MyAvatar::updateFromTrackers(float deltaTime) { relativePosition.x = -relativePosition.x; } - head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)), - -MAX_LEAN, MAX_LEAN)); - head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)), - -MAX_LEAN, MAX_LEAN)); + if (!(inHmd && getStandingHMDSensorMode())) { + head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)), + -MAX_LEAN, MAX_LEAN)); + head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)), + -MAX_LEAN, MAX_LEAN)); + } } @@ -343,6 +399,22 @@ glm::vec3 MyAvatar::getLeftPalmPosition() { return leftHandPosition; } +glm::vec3 MyAvatar::getLeftPalmVelocity() { + const PalmData* palm = getHand()->getPalm(LEFT_HAND_INDEX); + if (palm != NULL) { + return palm->getVelocity(); + } + return glm::vec3(0.0f); +} + +glm::vec3 MyAvatar::getLeftPalmAngularVelocity() { + const PalmData* palm = getHand()->getPalm(LEFT_HAND_INDEX); + if (palm != NULL) { + return palm->getRawAngularVelocity(); + } + return glm::vec3(0.0f); +} + glm::quat MyAvatar::getLeftPalmRotation() { glm::quat leftRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); @@ -358,6 +430,22 @@ glm::vec3 MyAvatar::getRightPalmPosition() { return rightHandPosition; } +glm::vec3 MyAvatar::getRightPalmVelocity() { + const PalmData* palm = getHand()->getPalm(RIGHT_HAND_INDEX); + if (palm != NULL) { + return palm->getVelocity(); + } + return glm::vec3(0.0f); +} + +glm::vec3 MyAvatar::getRightPalmAngularVelocity() { + const PalmData* palm = getHand()->getPalm(RIGHT_HAND_INDEX); + if (palm != NULL) { + return palm->getRawAngularVelocity(); + } + return glm::vec3(0.0f); +} + glm::quat MyAvatar::getRightPalmRotation() { glm::quat rightRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); @@ -845,10 +933,8 @@ void MyAvatar::updateLookAtTargetAvatar() { gazeOffset = gazeOffset * HUMAN_EYE_SEPARATION / myEyeSeparation; if (Application::getInstance()->isHMDMode()) { - //avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getCamera()->getPosition() - // + OculusManager::getMidEyePosition() + gazeOffset); avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() - + OculusManager::getMidEyePosition() + gazeOffset); + + glm::vec3(qApp->getHMDSensorPose()[3]) + gazeOffset); } else { avatar->getHead()->setCorrectedLookAtPosition(Application::getInstance()->getViewFrustum()->getPosition() + gazeOffset); @@ -891,7 +977,7 @@ eyeContactTarget MyAvatar::getEyeContactTarget() { } glm::vec3 MyAvatar::getDefaultEyePosition() const { - return _position + getWorldAlignedOrientation() * _skeletonModel.getDefaultEyeModelPosition(); + return getPosition() + getWorldAlignedOrientation() * _skeletonModel.getDefaultEyeModelPosition(); } const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f; @@ -1274,10 +1360,13 @@ void MyAvatar::updateOrientation(float deltaTime) { glm::quat(glm::radians(glm::vec3(0.0f, _bodyYawDelta * deltaTime, 0.0f)))); if (qApp->isHMDMode()) { + glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation(); + glm::quat bodyOrientation = getWorldBodyOrientation(); + glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation; + // these angles will be in radians - glm::quat orientation = qApp->getHeadOrientation(); // ... so they need to be converted to degrees before we do math... - glm::vec3 euler = glm::eulerAngles(orientation) * DEGREES_PER_RADIAN; + glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN; //Invert yaw and roll when in mirror mode if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_MIRROR) { @@ -1340,6 +1429,8 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe glm::vec3 direction = front + right + up; float directionLength = glm::length(direction); + //qCDebug(interfaceapp, "direction = (%.5f, %.5f, %.5f)", direction.x, direction.y, direction.z); + // Compute motor magnitude if (directionLength > EPSILON) { direction /= directionLength; @@ -1444,7 +1535,6 @@ void MyAvatar::updatePosition(float deltaTime) { // update _moving flag based on speed const float MOVING_SPEED_THRESHOLD = 0.01f; _moving = speed > MOVING_SPEED_THRESHOLD; - } void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { @@ -1539,32 +1629,31 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", " << newPosition.y << ", " << newPosition.z; - glm::vec3 shiftedPosition = newPosition; - + _goToPending = true; + _goToPosition = newPosition; + _goToOrientation = getOrientation(); if (hasOrientation) { qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - new orientation is " - << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w; + << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w; // orient the user to face the target glm::quat quatOrientation = newOrientation; if (shouldFaceLocation) { - quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; - shiftedPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; + _goToPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; } - setOrientation(quatOrientation); + _goToOrientation = quatOrientation; } - slamPosition(shiftedPosition); emit transformChanged(); } -void MyAvatar::updateMotionBehavior() { +void MyAvatar::updateMotionBehaviorFromMenu() { Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; @@ -1579,6 +1668,11 @@ void MyAvatar::updateMotionBehavior() { _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); } +void MyAvatar::updateStandingHMDModeFromMenu() { + Menu* menu = Menu::getInstance(); + _standingHMDSensorMode = menu->isOptionChecked(MenuOption::StandingHMDSensorMode); +} + //Renders sixense laser pointers for UI selection with controllers void MyAvatar::renderLaserPointers(gpu::Batch& batch) { const float PALM_TIP_ROD_RADIUS = 0.002f; @@ -1630,3 +1724,37 @@ void MyAvatar::relayDriveKeysToCharacterController() { _characterController.jump(); } } + +glm::vec3 MyAvatar::getWorldBodyPosition() const { + return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix)); +} + +glm::quat MyAvatar::getWorldBodyOrientation() const { + return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix); +} + +// derive avatar body position and orientation from the current HMD Sensor location. +// results are in sensor space +glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { + + // HMD is in sensor space. + const glm::vec3 hmdPosition = getHMDSensorPosition(); + const glm::quat hmdOrientation = getHMDSensorOrientation(); + const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); + + // In sensor space, figure out where the avatar body should be, + // by applying offsets from the avatar's neck & head joints. + vec3 localEyes = _skeletonModel.getDefaultEyeModelPosition(); + vec3 localNeck(0.0f, 0.48f, 0.0f); // start with some kind of guess if the skeletonModel is not loaded yet. + _skeletonModel.getLocalNeckPosition(localNeck); + + // apply simplistic head/neck model + // eyeToNeck offset is relative full HMD orientation. + // while neckToRoot offset is only relative to HMDs yaw. + glm::vec3 eyeToNeck = hmdOrientation * (localNeck - localEyes); + glm::vec3 neckToRoot = hmdOrientationYawOnly * -localNeck; + glm::vec3 bodyPos = hmdPosition + eyeToNeck + neckToRoot; + + // avatar facing is determined solely by hmd orientation. + return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos); +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 13223b66b9..88649429f5 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -44,6 +44,20 @@ public: void update(float deltaTime); void preRender(RenderArgs* renderArgs); + const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; } + const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; } + const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; } + glm::mat4 getSensorToWorldMatrix() const; + + // best called at start of main loop just after we have a fresh hmd pose. + // update internal body position from new hmd pose. + void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); + + // best called at end of main loop, just before rendering. + // update sensor to world matrix from current body position and hmd sensor. + // This is so the correct camera can be used for rendering. + void updateSensorToWorldMatrix(); + void setLeanScale(float scale) { _leanScale = scale; } void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); } @@ -59,6 +73,7 @@ public: Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); + /// Stops an animation as identified by a URL. Q_INVOKABLE void stopAnimation(const QString& url); @@ -148,6 +163,8 @@ public: static const float ZOOM_MAX; static const float ZOOM_DEFAULT; + bool getStandingHMDSensorMode() const { return _standingHMDSensorMode; } + public slots: void increaseSize(); void decreaseSize(); @@ -162,11 +179,16 @@ public slots: glm::vec3 getThrust() { return _thrust; }; void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } - void updateMotionBehavior(); + void updateMotionBehaviorFromMenu(); + void updateStandingHMDModeFromMenu(); glm::vec3 getLeftPalmPosition(); + glm::vec3 getLeftPalmVelocity(); + glm::vec3 getLeftPalmAngularVelocity(); glm::quat getLeftPalmRotation(); glm::vec3 getRightPalmPosition(); + glm::vec3 getRightPalmVelocity(); + glm::vec3 getRightPalmAngularVelocity(); glm::quat getRightPalmRotation(); void clearReferential(); @@ -189,6 +211,8 @@ signals: private: + glm::vec3 getWorldBodyPosition() const; + glm::quat getWorldBodyOrientation() const; QByteArray toByteArray(); void simulate(float deltaTime); void updateFromTrackers(float deltaTime); @@ -224,6 +248,10 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); + // derive avatar body position and orientation from the current HMD Sensor location. + // results are in sensor space + glm::mat4 deriveBodyFromHMDSensor() const; + glm::vec3 _gravity; float _driveKeys[MAX_DRIVE_KEYS]; @@ -281,6 +309,26 @@ private: RigPointer _rig; bool _prevShouldDrawHead; + + // cache of the current HMD sensor position and orientation + // in sensor space. + glm::mat4 _hmdSensorMatrix; + glm::quat _hmdSensorOrientation; + glm::vec3 _hmdSensorPosition; + + // cache of the current body position and orientation of the avatar's body, + // in sensor space. + glm::mat4 _bodySensorMatrix; + + // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. + glm::mat4 _sensorToWorldMatrix; + + bool _standingHMDSensorMode; + + bool _goToPending; + glm::vec3 _goToPosition; + glm::quat _goToOrientation; + std::unordered_set _headBoneSet; }; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 11e4aca853..e870ba2140 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -392,6 +392,10 @@ bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { return isActive() && getJointPositionInWorldFrame(_geometry->getFBXGeometry().neckJointIndex, neckPosition); } +bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const { + return isActive() && getJointPosition(_geometry->getFBXGeometry().neckJointIndex, neckPosition); +} + bool SkeletonModel::getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const { if (!isActive()) { return false; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index a13d3c4e75..4ae615eadd 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -80,6 +80,8 @@ public: /// \return whether or not the neck was found bool getNeckPosition(glm::vec3& neckPosition) const; + bool getLocalNeckPosition(glm::vec3& neckPosition) const; + /// Returns the rotation of the neck joint's parent from default orientation /// \return whether or not the neck was found bool getNeckParentRotationFromDefaultOrientation(glm::quat& neckParentRotation) const; diff --git a/interface/src/devices/3DConnexionClient.cpp b/interface/src/devices/3DConnexionClient.cpp index 7459f7da2a..ff54804bef 100755 --- a/interface/src/devices/3DConnexionClient.cpp +++ b/interface/src/devices/3DConnexionClient.cpp @@ -1,981 +1,985 @@ -// -// 3DConnexionClient.cpp -// interface/src/devices -// -// Created by MarcelEdward Verhagen on 09-06-15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "3DConnexionClient.h" -#include "UserActivityLogger.h" - -const float MAX_AXIS = 75.0f; // max forward = 2x speed - -void ConnexionData::focusOutEvent() { - _axisStateMap.clear(); - _buttonPressedMap.clear(); -}; - -ConnexionData& ConnexionData::getInstance() { - static ConnexionData sharedInstance; - return sharedInstance; -} - -ConnexionData::ConnexionData() { -} - -void ConnexionData::handleAxisEvent() { - _axisStateMap[makeInput(ROTATION_AXIS_Y_POS).getChannel()] = (cc_rotation.y > 0.0f) ? cc_rotation.y / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(ROTATION_AXIS_Y_NEG).getChannel()] = (cc_rotation.y < 0.0f) ? -cc_rotation.y / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(POSITION_AXIS_X_POS).getChannel()] = (cc_position.x > 0.0f) ? cc_position.x / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(POSITION_AXIS_X_NEG).getChannel()] = (cc_position.x < 0.0f) ? -cc_position.x / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(POSITION_AXIS_Y_POS).getChannel()] = (cc_position.y > 0.0f) ? cc_position.y / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(POSITION_AXIS_Y_NEG).getChannel()] = (cc_position.y < 0.0f) ? -cc_position.y / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(POSITION_AXIS_Z_POS).getChannel()] = (cc_position.z > 0.0f) ? cc_position.z / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(POSITION_AXIS_Z_NEG).getChannel()] = (cc_position.z < 0.0f) ? -cc_position.z / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(ROTATION_AXIS_X_POS).getChannel()] = (cc_rotation.x > 0.0f) ? cc_rotation.x / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(ROTATION_AXIS_X_NEG).getChannel()] = (cc_rotation.x < 0.0f) ? -cc_rotation.x / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(ROTATION_AXIS_Z_POS).getChannel()] = (cc_rotation.z > 0.0f) ? cc_rotation.z / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(ROTATION_AXIS_Z_NEG).getChannel()] = (cc_rotation.z < 0.0f) ? -cc_rotation.z / MAX_AXIS : 0.0f; -} - -void ConnexionData::setButton(int lastButtonState) { - _buttonPressedMap.clear(); - _buttonPressedMap.insert(lastButtonState); -} - -void ConnexionData::registerToUserInputMapper(UserInputMapper& mapper) { - // Grab the current free device ID - _deviceID = mapper.getFreeDeviceID(); - - auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy("ConnexionClient")); - proxy->getButton = [this](const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; - proxy->getAxis = [this](const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; - proxy->getAvailabeInputs = [this]() -> QVector { - QVector availableInputs; - - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1), "Left button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2), "Right button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3), "Both buttons")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Y_NEG), "Move backward")); - availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Y_POS), "Move forward")); - availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_X_POS), "Move right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_X_NEG), "Move Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Z_POS), "Move up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Z_NEG), "Move down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Y_NEG), "Rotate backward")); - availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Y_POS), "Rotate forward")); - availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_X_POS), "Rotate right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_X_NEG), "Rotate left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Z_POS), "Rotate up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Z_NEG), "Rotate down")); - - return availableInputs; - }; - proxy->resetDeviceBindings = [this, &mapper]() -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; - mapper.registerDevice(_deviceID, proxy); -} - -void ConnexionData::assignDefaultInputMapping(UserInputMapper& mapper) { - const float JOYSTICK_MOVE_SPEED = 1.0f; - //const float DPAD_MOVE_SPEED = 0.5f; - const float JOYSTICK_YAW_SPEED = 0.5f; - const float JOYSTICK_PITCH_SPEED = 0.25f; - const float BOOM_SPEED = 0.1f; - - // Y axes are flipped (up is negative) - // postion: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(POSITION_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(POSITION_AXIS_Y_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(POSITION_AXIS_X_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(POSITION_AXIS_X_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(POSITION_AXIS_Z_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(POSITION_AXIS_Z_POS), JOYSTICK_MOVE_SPEED); - - // Rotation: Camera orientation with button 1 - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(ROTATION_AXIS_Z_POS), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(ROTATION_AXIS_Z_NEG), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(ROTATION_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(ROTATION_AXIS_Y_POS), JOYSTICK_PITCH_SPEED); - - // Button controls - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_1), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_2), BOOM_SPEED); - - // Zoom - // mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(ROTATION_AXIS_Z_NEG), BOOM_SPEED); - // mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(ROTATION_AXIS_Z_POS), BOOM_SPEED); - -} - -float ConnexionData::getButton(int channel) const { - if (!_buttonPressedMap.empty()) { - if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { - return 1.0f; - } else { - return 0.0f; - } - } - return 0.0f; -} - -float ConnexionData::getAxis(int channel) const { - auto axis = _axisStateMap.find(channel); - if (axis != _axisStateMap.end()) { - return (*axis).second; - } else { - return 0.0f; - } -} - -UserInputMapper::Input ConnexionData::makeInput(ConnexionData::ButtonChannel button) { - return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); -} - -UserInputMapper::Input ConnexionData::makeInput(ConnexionData::PositionChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); -} - -void ConnexionData::update() { - // the update is done in the ConnexionClient class. - // for windows in the nativeEventFilter the inputmapper is connected or registed or removed when an 3Dconnnexion device is attached or detached - // for osx the api will call DeviceAddedHandler or DeviceRemoveHandler when a 3Dconnexion device is attached or detached -} - -ConnexionClient& ConnexionClient::getInstance() { - static ConnexionClient sharedInstance; - return sharedInstance; -} - -#ifdef HAVE_3DCONNEXIONCLIENT - -#ifdef Q_OS_WIN - -void ConnexionClient::toggleConnexion(bool shouldEnable) { - ConnexionData& connexiondata = ConnexionData::getInstance(); - if (shouldEnable && connexiondata.getDeviceID() == 0) { - init(); - } - if (!shouldEnable && connexiondata.getDeviceID() != 0) { - destroy(); - } -} - -void ConnexionClient::init() { - if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { - fLast3dmouseInputTime = 0; - - InitializeRawInput(GetActiveWindow()); - - QAbstractEventDispatcher::instance()->installNativeEventFilter(this); - } -} - -void ConnexionClient::destroy() { - QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); - ConnexionData& connexiondata = ConnexionData::getInstance(); - int deviceid = connexiondata.getDeviceID(); - connexiondata.setDeviceID(0); - Application::getUserInputMapper()->removeDevice(deviceid); -} - -#define LOGITECH_VENDOR_ID 0x46d - -#ifndef RIDEV_DEVNOTIFY -#define RIDEV_DEVNOTIFY 0x00002000 -#endif - -const int TRACE_RIDI_DEVICENAME = 0; -const int TRACE_RIDI_DEVICEINFO = 0; - -#ifdef _WIN64 -typedef unsigned __int64 QWORD; -#endif - -// object angular velocity per mouse tick 0.008 milliradians per second per count -static const double k3dmouseAngularVelocity = 8.0e-6; // radians per second per count - -static const int kTimeToLive = 5; - -enum ConnexionPid { - CONNEXIONPID_SPACEPILOT = 0xc625, - CONNEXIONPID_SPACENAVIGATOR = 0xc626, - CONNEXIONPID_SPACEEXPLORER = 0xc627, - CONNEXIONPID_SPACENAVIGATORFORNOTEBOOKS = 0xc628, - CONNEXIONPID_SPACEPILOTPRO = 0xc629 -}; - -// e3dmouse_virtual_key -enum V3dk { - V3DK_INVALID = 0, - V3DK_MENU = 1, V3DK_FIT, - V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, V3DK_BOTTOM, V3DK_BACK, - V3DK_CW, V3DK_CCW, - V3DK_ISO1, V3DK_ISO2, - V3DK_1, V3DK_2, V3DK_3, V3DK_4, V3DK_5, V3DK_6, V3DK_7, V3DK_8, V3DK_9, V3DK_10, - V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL, - V3DK_ROTATE, V3DK_PANZOOM, V3DK_DOMINANT, - V3DK_PLUS, V3DK_MINUS -}; - -struct tag_VirtualKeys { - ConnexionPid pid; - size_t nKeys; - V3dk *vkeys; -}; - -// e3dmouse_virtual_key -static const V3dk SpaceExplorerKeys[] = { - V3DK_INVALID, // there is no button 0 - V3DK_1, V3DK_2, - V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, - V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL, - V3DK_FIT, V3DK_MENU, - V3DK_PLUS, V3DK_MINUS, - V3DK_ROTATE -}; - -//e3dmouse_virtual_key -static const V3dk SpacePilotKeys[] = { - V3DK_INVALID, - V3DK_1, V3DK_2, V3DK_3, V3DK_4, V3DK_5, V3DK_6, - V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, - V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL, - V3DK_FIT, V3DK_MENU, - V3DK_PLUS, V3DK_MINUS, - V3DK_DOMINANT, V3DK_ROTATE, -}; - -static const struct tag_VirtualKeys _3dmouseVirtualKeys[] = { - CONNEXIONPID_SPACEPILOT, - sizeof(SpacePilotKeys) / sizeof(SpacePilotKeys[0]), - const_cast(SpacePilotKeys), - CONNEXIONPID_SPACEEXPLORER, - sizeof(SpaceExplorerKeys) / sizeof(SpaceExplorerKeys[0]), - const_cast(SpaceExplorerKeys) -}; - -// Converts a hid device keycode (button identifier) of a pre-2009 3Dconnexion USB device to the standard 3d mouse virtual key definition. -// pid USB Product ID (PID) of 3D mouse device -// hidKeyCode Hid keycode as retrieved from a Raw Input packet -// return The standard 3d mouse virtual key (button identifier) or zero if an error occurs. - -// Converts a hid device keycode (button identifier) of a pre-2009 3Dconnexion USB device -// to the standard 3d mouse virtual key definition. -unsigned short HidToVirtualKey(unsigned long pid, unsigned short hidKeyCode) { - unsigned short virtualkey = hidKeyCode; - for (size_t i = 0; iremoveDevice(deviceid); - } - - if (!Is3dmouseAttached()) { - return false; - } - - MSG* message = (MSG*)(msg); - - if (message->message == WM_INPUT) { - HRAWINPUT hRawInput = reinterpret_cast(message->lParam); - OnRawInput(RIM_INPUT, hRawInput); - if (result != 0) { - result = 0; - } - return true; - } - return false; -} - -// Access the mouse parameters structure -I3dMouseParam& ConnexionClient::MouseParams() { - return f3dMouseParams; -} - -// Access the mouse parameters structure -const I3dMouseParam& ConnexionClient::MouseParams() const { - return f3dMouseParams; -} - -//Called with the processed motion data when a 3D mouse event is received -void ConnexionClient::Move3d(HANDLE device, std::vector& motionData) { - Q_UNUSED(device); - ConnexionData& connexiondata = ConnexionData::getInstance(); - connexiondata.cc_position = { motionData[0] * 1000, motionData[1] * 1000, motionData[2] * 1000 }; - connexiondata.cc_rotation = { motionData[3] * 1500, motionData[4] * 1500, motionData[5] * 1500 }; - connexiondata.handleAxisEvent(); -} - -//Called when a 3D mouse key is pressed -void ConnexionClient::On3dmouseKeyDown(HANDLE device, int virtualKeyCode) { - Q_UNUSED(device); - ConnexionData& connexiondata = ConnexionData::getInstance(); - connexiondata.setButton(virtualKeyCode); -} - -//Called when a 3D mouse key is released -void ConnexionClient::On3dmouseKeyUp(HANDLE device, int virtualKeyCode) { - Q_UNUSED(device); - ConnexionData& connexiondata = ConnexionData::getInstance(); - connexiondata.setButton(0); -} - -//Get an initialized array of PRAWINPUTDEVICE for the 3D devices -//pNumDevices returns the number of devices to register. Currently this is always 1. -static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) { - // Array of raw input devices to register - static RAWINPUTDEVICE sRawInputDevices[] = { - { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller - }; - - if (pNumDevices) { - *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]); - } - - return sRawInputDevices; -} - -//Detect the 3D mouse -bool ConnexionClient::Is3dmouseAttached() { - unsigned int numDevicesOfInterest = 0; - PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest); - - unsigned int nDevices = 0; - - if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { - return false; - } - - if (nDevices == 0) { - return false; - } - - std::vector rawInputDeviceList(nDevices); - if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-1)) { - return false; - } - - for (unsigned int i = 0; i < nDevices; ++i) { - RID_DEVICE_INFO rdi = { sizeof(rdi) }; - unsigned int cbSize = sizeof(rdi); - - if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) { - //skip non HID and non logitec (3DConnexion) devices - if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) { - continue; - } - - //check if devices matches Multi-axis Controller - for (unsigned int j = 0; j < numDevicesOfInterest; ++j) { - if (devicesToRegister[j].usUsage == rdi.hid.usUsage - && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) { - return true; - } - } - } - } - return false; -} - -// Initialize the window to recieve raw-input messages -// This needs to be called initially so that Windows will send the messages from the 3D mouse to the window. -bool ConnexionClient::InitializeRawInput(HWND hwndTarget) { - fWindow = hwndTarget; - - // Simply fail if there is no window - if (!hwndTarget) { - return false; - } - - unsigned int numDevices = 0; - PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevices); - - if (numDevices == 0) { - return false; - } - - // Get OS version. - OSVERSIONINFO osvi = { sizeof(OSVERSIONINFO), 0 }; - ::GetVersionEx(&osvi); - - unsigned int cbSize = sizeof(devicesToRegister[0]); - for (size_t i = 0; i < numDevices; i++) { - // Set the target window to use - //devicesToRegister[i].hwndTarget = hwndTarget; - - // If Vista or newer, enable receiving the WM_INPUT_DEVICE_CHANGE message. - if (osvi.dwMajorVersion >= 6) { - devicesToRegister[i].dwFlags |= RIDEV_DEVNOTIFY; - } - } - return (::RegisterRawInputDevices(devicesToRegister, numDevices, cbSize) != FALSE); -} - -//Get the raw input data from Windows -UINT ConnexionClient::GetRawInputBuffer(PRAWINPUT pData, PUINT pcbSize, UINT cbSizeHeader) { - //Includes workaround for incorrect alignment of the RAWINPUT structure on x64 os - //when running as Wow64 (copied directly from 3DConnexion code) -#ifdef _WIN64 - return ::GetRawInputBuffer(pData, pcbSize, cbSizeHeader); -#else - BOOL bIsWow64 = FALSE; - ::IsWow64Process(GetCurrentProcess(), &bIsWow64); - if (!bIsWow64 || pData == NULL) { - return ::GetRawInputBuffer(pData, pcbSize, cbSizeHeader); - } else { - HWND hwndTarget = fWindow; - - size_t cbDataSize = 0; - UINT nCount = 0; - PRAWINPUT pri = pData; - - MSG msg; - while (PeekMessage(&msg, hwndTarget, WM_INPUT, WM_INPUT, PM_NOREMOVE)) { - HRAWINPUT hRawInput = reinterpret_cast(msg.lParam); - size_t cbSize = *pcbSize - cbDataSize; - if (::GetRawInputData(hRawInput, RID_INPUT, pri, &cbSize, cbSizeHeader) == static_cast(-1)) { - if (nCount == 0) { - return static_cast(-1); - } else { - break; - } - } - ++nCount; - - // Remove the message for the data just read - PeekMessage(&msg, hwndTarget, WM_INPUT, WM_INPUT, PM_REMOVE); - - pri = NEXTRAWINPUTBLOCK(pri); - cbDataSize = reinterpret_cast(pri)-reinterpret_cast(pData); - if (cbDataSize >= *pcbSize) { - cbDataSize = *pcbSize; - break; - } - } - return nCount; - } -#endif -} - -// Process the raw input device data -// On3dmouseInput() does all the preprocessing of the rawinput device data before -// finally calling the Move3d method. -void ConnexionClient::On3dmouseInput() { - // Don't do any data processing in background - bool bIsForeground = (::GetActiveWindow() != NULL); - if (!bIsForeground) { - // set all cached data to zero so that a zero event is seen and the cached data deleted - for (std::map::iterator it = fDevice2Data.begin(); it != fDevice2Data.end(); it++) { - it->second.fAxes.assign(6, .0); - it->second.fIsDirty = true; - } - } - - DWORD dwNow = ::GetTickCount(); // Current time; - DWORD dwElapsedTime; // Elapsed time since we were last here - - if (0 == fLast3dmouseInputTime) { - dwElapsedTime = 10; // System timer resolution - } else { - dwElapsedTime = dwNow - fLast3dmouseInputTime; - if (fLast3dmouseInputTime > dwNow) { - dwElapsedTime = ~dwElapsedTime + 1; - } - if (dwElapsedTime<1) { - dwElapsedTime = 1; - } else if (dwElapsedTime > 500) { - // Check for wild numbers because the device was removed while sending data - dwElapsedTime = 10; - } - } - - //qDebug("On3DmouseInput() period is %dms\n", dwElapsedTime); - - float mouseData2Rotation = k3dmouseAngularVelocity; - // v = w * r, we don't know r yet so lets assume r=1.) - float mouseData2PanZoom = k3dmouseAngularVelocity; - - // Grab the I3dmouseParam interface - I3dMouseParam& i3dmouseParam = f3dMouseParams; - // Take a look at the users preferred speed setting and adjust the sensitivity accordingly - I3dMouseSensor::Speed speedSetting = i3dmouseParam.GetSpeed(); - // See "Programming for the 3D Mouse", Section 5.1.3 - float speed = (speedSetting == I3dMouseSensor::SPEED_LOW ? 0.25f : speedSetting == I3dMouseSensor::SPEED_HIGH ? 4.f : 1.f); - - // Multiplying by the following will convert the 3d mouse data to real world units - mouseData2PanZoom *= speed; - mouseData2Rotation *= speed; - - std::map::iterator iterator = fDevice2Data.begin(); - while (iterator != fDevice2Data.end()) { - - // If we have not received data for a while send a zero event - if ((--(iterator->second.fTimeToLive)) == 0) { - iterator->second.fAxes.assign(6, .0); - } else if ( !iterator->second.fIsDirty) { //!t_bPoll3dmouse && - // If we are not polling then only handle the data that was actually received - ++iterator; - continue; - } - iterator->second.fIsDirty = false; - - // get a copy of the device - HANDLE hdevice = iterator->first; - - // get a copy of the motion vectors and apply the user filters - std::vector motionData = iterator->second.fAxes; - - // apply the user filters - - // Pan Zoom filter - // See "Programming for the 3D Mouse", Section 5.1.2 - if (!i3dmouseParam.IsPanZoom()) { - // Pan zoom is switched off so set the translation vector values to zero - motionData[0] = motionData[1] = motionData[2] = 0.; - } - - // Rotate filter - // See "Programming for the 3D Mouse", Section 5.1.1 - if (!i3dmouseParam.IsRotate()) { - // Rotate is switched off so set the rotation vector values to zero - motionData[3] = motionData[4] = motionData[5] = 0.; - } - - // convert the translation vector into physical data - for (int axis = 0; axis < 3; axis++) { - motionData[axis] *= mouseData2PanZoom; - } - - // convert the directed Rotate vector into physical data - // See "Programming for the 3D Mouse", Section 7.2.2 - for (int axis = 3; axis < 6; axis++) { - motionData[axis] *= mouseData2Rotation; - } - - // Now that the data has had the filters and sensitivty settings applied - // calculate the displacements since the last view update - for (int axis = 0; axis < 6; axis++) { - motionData[axis] *= dwElapsedTime; - } - - // Now a bit of book keeping before passing on the data - if (iterator->second.IsZero()) { - iterator = fDevice2Data.erase(iterator); - } else { - ++iterator; - } - - // Work out which will be the next device - HANDLE hNextDevice = 0; - if (iterator != fDevice2Data.end()) { - hNextDevice = iterator->first; - } - - // Pass the 3dmouse input to the view controller - Move3d(hdevice, motionData); - - // Because we don't know what happened in the previous call, the cache might have - // changed so reload the iterator - iterator = fDevice2Data.find(hNextDevice); - } - - if (!fDevice2Data.empty()) { - fLast3dmouseInputTime = dwNow; - } else { - fLast3dmouseInputTime = 0; - } -} - -//Called when new raw input data is available -void ConnexionClient::OnRawInput(UINT nInputCode, HRAWINPUT hRawInput) { - const size_t cbSizeOfBuffer = 1024; - BYTE pBuffer[cbSizeOfBuffer]; - - PRAWINPUT pRawInput = reinterpret_cast(pBuffer); - UINT cbSize = cbSizeOfBuffer; - - if (::GetRawInputData(hRawInput, RID_INPUT, pRawInput, &cbSize, sizeof(RAWINPUTHEADER)) == static_cast(-1)) { - return; - } - - bool b3dmouseInput = TranslateRawInputData(nInputCode, pRawInput); - ::DefRawInputProc(&pRawInput, 1, sizeof(RAWINPUTHEADER)); - - // Check for any buffered messages - cbSize = cbSizeOfBuffer; - UINT nCount = this->GetRawInputBuffer(pRawInput, &cbSize, sizeof(RAWINPUTHEADER)); - if (nCount == (UINT)-1) { - qDebug("GetRawInputBuffer returned error %d\n", GetLastError()); - } - - while (nCount>0 && nCount != static_cast(-1)) { - PRAWINPUT pri = pRawInput; - UINT nInput; - for (nInput = 0; nInputGetRawInputBuffer(pRawInput, &cbSize, sizeof(RAWINPUTHEADER)); - } - - // If we have mouse input data for the app then tell tha app about it - if (b3dmouseInput) { - On3dmouseInput(); - } -} - -bool ConnexionClient::TranslateRawInputData(UINT nInputCode, PRAWINPUT pRawInput) { - bool bIsForeground = (nInputCode == RIM_INPUT); - - // We are not interested in keyboard or mouse data received via raw input - if (pRawInput->header.dwType != RIM_TYPEHID) { - return false; - } - - if (TRACE_RIDI_DEVICENAME == 1) { - UINT dwSize = 0; - if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, NULL, &dwSize) == 0) { - std::vector szDeviceName(dwSize + 1); - if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, &szDeviceName[0], &dwSize) > 0) { - qDebug("Device Name = %s\nDevice handle = 0x%x\n", &szDeviceName[0], pRawInput->header.hDevice); - } - } - } - - RID_DEVICE_INFO sRidDeviceInfo; - sRidDeviceInfo.cbSize = sizeof(RID_DEVICE_INFO); - UINT cbSize = sizeof(RID_DEVICE_INFO); - - if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICEINFO, &sRidDeviceInfo, &cbSize) == cbSize) { - if (TRACE_RIDI_DEVICEINFO == 1) { - switch (sRidDeviceInfo.dwType) { - case RIM_TYPEMOUSE: - qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEMOUSE\n"); - break; - case RIM_TYPEKEYBOARD: - qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEKEYBOARD\n"); - break; - case RIM_TYPEHID: - qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEHID\n"); - qDebug("\tVendor=0x%x\n\tProduct=0x%x\n\tUsagePage=0x%x\n\tUsage=0x%x\n", - sRidDeviceInfo.hid.dwVendorId, - sRidDeviceInfo.hid.dwProductId, - sRidDeviceInfo.hid.usUsagePage, - sRidDeviceInfo.hid.usUsage); - break; - } - } - - if (sRidDeviceInfo.hid.dwVendorId == LOGITECH_VENDOR_ID) { - if (pRawInput->data.hid.bRawData[0] == 0x01) { // Translation vector - TInputData& deviceData = fDevice2Data[pRawInput->header.hDevice]; - deviceData.fTimeToLive = kTimeToLive; - if (bIsForeground) { - short* pnRawData = reinterpret_cast(&pRawInput->data.hid.bRawData[1]); - // Cache the pan zoom data - deviceData.fAxes[0] = static_cast(pnRawData[0]); - deviceData.fAxes[1] = static_cast(pnRawData[1]); - deviceData.fAxes[2] = static_cast(pnRawData[2]); - - //qDebug("Pan/Zoom RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[0], pnRawData[1], pnRawData[2]); - - if (pRawInput->data.hid.dwSizeHid >= 13) { // Highspeed package - // Cache the rotation data - deviceData.fAxes[3] = static_cast(pnRawData[3]); - deviceData.fAxes[4] = static_cast(pnRawData[4]); - deviceData.fAxes[5] = static_cast(pnRawData[5]); - deviceData.fIsDirty = true; - - //qDebug("Rotation RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[3], pnRawData[4], pnRawData[5]); - return true; - } - } else { // Zero out the data if the app is not in forground - deviceData.fAxes.assign(6, 0.f); - } - } else if (pRawInput->data.hid.bRawData[0] == 0x02) { // Rotation vector - // If we are not in foreground do nothing - // The rotation vector was zeroed out with the translation vector in the previous message - if (bIsForeground) { - TInputData& deviceData = fDevice2Data[pRawInput->header.hDevice]; - deviceData.fTimeToLive = kTimeToLive; - - short* pnRawData = reinterpret_cast(&pRawInput->data.hid.bRawData[1]); - // Cache the rotation data - deviceData.fAxes[3] = static_cast(pnRawData[0]); - deviceData.fAxes[4] = static_cast(pnRawData[1]); - deviceData.fAxes[5] = static_cast(pnRawData[2]); - deviceData.fIsDirty = true; - - //qDebug("Rotation RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[0], pnRawData[1], pnRawData[2]); - - return true; - } - } else if (pRawInput->data.hid.bRawData[0] == 0x03) { // Keystate change - // this is a package that contains 3d mouse keystate information - // bit0=key1, bit=key2 etc. - - unsigned long dwKeystate = *reinterpret_cast(&pRawInput->data.hid.bRawData[1]); - - //qDebug("ButtonData =0x%x\n", dwKeystate); - - // Log the keystate changes - unsigned long dwOldKeystate = fDevice2Keystate[pRawInput->header.hDevice]; - if (dwKeystate != 0) { - fDevice2Keystate[pRawInput->header.hDevice] = dwKeystate; - } else { - fDevice2Keystate.erase(pRawInput->header.hDevice); - } - - // Only call the keystate change handlers if the app is in foreground - if (bIsForeground) { - unsigned long dwChange = dwKeystate ^ dwOldKeystate; - - for (int nKeycode = 1; nKeycode<33; nKeycode++) { - if (dwChange & 0x01) { - int nVirtualKeyCode = HidToVirtualKey(sRidDeviceInfo.hid.dwProductId, nKeycode); - if (nVirtualKeyCode) { - if (dwKeystate & 0x01) { - On3dmouseKeyDown(pRawInput->header.hDevice, nVirtualKeyCode); - } else { - On3dmouseKeyUp(pRawInput->header.hDevice, nVirtualKeyCode); - } - } - } - dwChange >>= 1; - dwKeystate >>= 1; - } - } - } - } - } - return false; -} - -MouseParameters::MouseParameters() : - fNavigation(NAVIGATION_OBJECT_MODE), - fPivot(PIVOT_AUTO), - fPivotVisibility(PIVOT_SHOW), - fIsLockHorizon(true), - fIsPanZoom(true), - fIsRotate(true), - fSpeed(SPEED_LOW) -{ -} - -bool MouseParameters::IsPanZoom() const { - return fIsPanZoom; -} - -bool MouseParameters::IsRotate() const { - return fIsRotate; -} - -MouseParameters::Speed MouseParameters::GetSpeed() const { - return fSpeed; -} - -void MouseParameters::SetPanZoom(bool isPanZoom) { - fIsPanZoom = isPanZoom; -} - -void MouseParameters::SetRotate(bool isRotate) { - fIsRotate = isRotate; -} - -void MouseParameters::SetSpeed(Speed speed) { - fSpeed = speed; -} - -MouseParameters::Navigation MouseParameters::GetNavigationMode() const { - return fNavigation; -} - -MouseParameters::Pivot MouseParameters::GetPivotMode() const { - return fPivot; -} - -MouseParameters::PivotVisibility MouseParameters::GetPivotVisibility() const { - return fPivotVisibility; -} - -bool MouseParameters::IsLockHorizon() const { - return fIsLockHorizon; -} - -void MouseParameters::SetLockHorizon(bool bOn) { - fIsLockHorizon=bOn; -} - -void MouseParameters::SetNavigationMode(Navigation navigation) { - fNavigation=navigation; -} - -void MouseParameters::SetPivotMode(Pivot pivot) { - if (fPivot!=PIVOT_MANUAL || pivot!=PIVOT_AUTO_OVERRIDE) { - fPivot = pivot; - } -} - -void MouseParameters::SetPivotVisibility(PivotVisibility visibility) { - fPivotVisibility = visibility; -} - -#else - -int fConnexionClientID; - -static ConnexionDeviceState lastState; - -static void DeviceAddedHandler(unsigned int connection); -static void DeviceRemovedHandler(unsigned int connection); -static void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument); - -void ConnexionClient::toggleConnexion(bool shouldEnable) { - if (shouldEnable && !Is3dmouseAttached()) { - init(); - } - if (!shouldEnable && Is3dmouseAttached()) { - destroy(); - } -} - -void ConnexionClient::init() { - // Make sure the framework is installed - if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { - // Install message handler and register our client - InstallConnexionHandlers(MessageHandler, DeviceAddedHandler, DeviceRemovedHandler); - // Either use this to take over in our application only... does not work - // fConnexionClientID = RegisterConnexionClient('MCTt', "\pConnexion Client Test", kConnexionClientModeTakeOver, kConnexionMaskAll); - - // ...or use this to take over system-wide - fConnexionClientID = RegisterConnexionClient(kConnexionClientWildcard, NULL, kConnexionClientModeTakeOver, kConnexionMaskAll); - ConnexionData& connexiondata = ConnexionData::getInstance(); - memcpy(&connexiondata.clientId, &fConnexionClientID, (long)sizeof(int)); - - // A separate API call is required to capture buttons beyond the first 8 - SetConnexionClientButtonMask(fConnexionClientID, kConnexionMaskAllButtons); - - // use default switches - ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchesDisabled, NULL); - - if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { - connexiondata.registerToUserInputMapper(*Application::getUserInputMapper()); - connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper()); - UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); - } - //let one axis be dominant - //ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL); - } -} - -void ConnexionClient::destroy() { - // Make sure the framework is installed - if (&InstallConnexionHandlers != NULL) { - // Unregister our client and clean up all handlers - if (fConnexionClientID) { - UnregisterConnexionClient(fConnexionClientID); - } - CleanupConnexionHandlers(); - fConnexionClientID = 0; - ConnexionData& connexiondata = ConnexionData::getInstance(); - if (connexiondata.getDeviceID()!=0) { - Application::getUserInputMapper()->removeDevice(connexiondata.getDeviceID()); - connexiondata.setDeviceID(0); - } - } -} - -void DeviceAddedHandler(unsigned int connection) { - ConnexionData& connexiondata = ConnexionData::getInstance(); - if (connexiondata.getDeviceID() == 0) { - qCWarning(interfaceapp) << "3Dconnexion device added "; - connexiondata.registerToUserInputMapper(*Application::getUserInputMapper()); - connexiondata.assignDefaultInputMapping(*Application::getUserInputMapper()); - UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); - } -} - -void DeviceRemovedHandler(unsigned int connection) { - ConnexionData& connexiondata = ConnexionData::getInstance(); - if (connexiondata.getDeviceID() != 0) { - qCWarning(interfaceapp) << "3Dconnexion device removed"; - Application::getUserInputMapper()->removeDevice(connexiondata.getDeviceID()); - connexiondata.setDeviceID(0); - } -} - -bool ConnexionClient::Is3dmouseAttached() { - int result; - if (fConnexionClientID) { - if (ConnexionControl(kConnexionCtlGetDeviceID, 0, &result)) { - return false; - } - return true; - } - return false; -} - -void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument) { - ConnexionDeviceState *state; - - switch (messageType) { - case kConnexionMsgDeviceState: - state = (ConnexionDeviceState*)messageArgument; - if (state->client == fConnexionClientID) { - ConnexionData& connexiondata = ConnexionData::getInstance(); - connexiondata.cc_position = { state->axis[0], state->axis[1], state->axis[2] }; - connexiondata.cc_rotation = { state->axis[3], state->axis[4], state->axis[5] }; - - connexiondata.handleAxisEvent(); - if (state->buttons != lastState.buttons) { - connexiondata.setButton(state->buttons); - } - memmove(&lastState, state, (long)sizeof(ConnexionDeviceState)); - } - break; - case kConnexionMsgPrefsChanged: - // the prefs have changed, do something - break; - default: - // other messageTypes can happen and should be ignored - break; - } - -} - -#endif // __APPLE__ - -#endif // HAVE_3DCONNEXIONCLIENT +// +// 3DConnexionClient.cpp +// interface/src/devices +// +// Created by MarcelEdward Verhagen on 09-06-15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "3DConnexionClient.h" +#include "UserActivityLogger.h" + +const float MAX_AXIS = 75.0f; // max forward = 2x speed + +void ConnexionData::focusOutEvent() { + _axisStateMap.clear(); + _buttonPressedMap.clear(); +}; + +ConnexionData& ConnexionData::getInstance() { + static ConnexionData sharedInstance; + return sharedInstance; +} + +ConnexionData::ConnexionData() { +} + +void ConnexionData::handleAxisEvent() { + _axisStateMap[makeInput(ROTATION_AXIS_Y_POS).getChannel()] = (cc_rotation.y > 0.0f) ? cc_rotation.y / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(ROTATION_AXIS_Y_NEG).getChannel()] = (cc_rotation.y < 0.0f) ? -cc_rotation.y / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(POSITION_AXIS_X_POS).getChannel()] = (cc_position.x > 0.0f) ? cc_position.x / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(POSITION_AXIS_X_NEG).getChannel()] = (cc_position.x < 0.0f) ? -cc_position.x / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(POSITION_AXIS_Y_POS).getChannel()] = (cc_position.y > 0.0f) ? cc_position.y / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(POSITION_AXIS_Y_NEG).getChannel()] = (cc_position.y < 0.0f) ? -cc_position.y / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(POSITION_AXIS_Z_POS).getChannel()] = (cc_position.z > 0.0f) ? cc_position.z / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(POSITION_AXIS_Z_NEG).getChannel()] = (cc_position.z < 0.0f) ? -cc_position.z / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(ROTATION_AXIS_X_POS).getChannel()] = (cc_rotation.x > 0.0f) ? cc_rotation.x / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(ROTATION_AXIS_X_NEG).getChannel()] = (cc_rotation.x < 0.0f) ? -cc_rotation.x / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(ROTATION_AXIS_Z_POS).getChannel()] = (cc_rotation.z > 0.0f) ? cc_rotation.z / MAX_AXIS : 0.0f; + _axisStateMap[makeInput(ROTATION_AXIS_Z_NEG).getChannel()] = (cc_rotation.z < 0.0f) ? -cc_rotation.z / MAX_AXIS : 0.0f; +} + +void ConnexionData::setButton(int lastButtonState) { + _buttonPressedMap.clear(); + _buttonPressedMap.insert(lastButtonState); +} + +void ConnexionData::registerToUserInputMapper(UserInputMapper& mapper) { + // Grab the current free device ID + _deviceID = mapper.getFreeDeviceID(); + + auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy("ConnexionClient")); + proxy->getButton = [this](const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; + proxy->getAxis = [this](const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; + proxy->getAvailabeInputs = [this]() -> QVector { + QVector availableInputs; + + availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1), "Left button")); + availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2), "Right button")); + availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3), "Both buttons")); + + availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Y_NEG), "Move backward")); + availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Y_POS), "Move forward")); + availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_X_POS), "Move right")); + availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_X_NEG), "Move Left")); + availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Z_POS), "Move up")); + availableInputs.append(UserInputMapper::InputPair(makeInput(POSITION_AXIS_Z_NEG), "Move down")); + availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Y_NEG), "Rotate backward")); + availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Y_POS), "Rotate forward")); + availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_X_POS), "Rotate right")); + availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_X_NEG), "Rotate left")); + availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Z_POS), "Rotate up")); + availableInputs.append(UserInputMapper::InputPair(makeInput(ROTATION_AXIS_Z_NEG), "Rotate down")); + + return availableInputs; + }; + proxy->resetDeviceBindings = [this, &mapper]() -> bool { + mapper.removeAllInputChannelsForDevice(_deviceID); + this->assignDefaultInputMapping(mapper); + return true; + }; + mapper.registerDevice(_deviceID, proxy); +} + +void ConnexionData::assignDefaultInputMapping(UserInputMapper& mapper) { + const float JOYSTICK_MOVE_SPEED = 1.0f; + //const float DPAD_MOVE_SPEED = 0.5f; + const float JOYSTICK_YAW_SPEED = 0.5f; + const float JOYSTICK_PITCH_SPEED = 0.25f; + const float BOOM_SPEED = 0.1f; + + // Y axes are flipped (up is negative) + // postion: Movement, strafing + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(POSITION_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(POSITION_AXIS_Y_POS), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(POSITION_AXIS_X_POS), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(POSITION_AXIS_X_NEG), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(POSITION_AXIS_Z_NEG), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(POSITION_AXIS_Z_POS), JOYSTICK_MOVE_SPEED); + + // Rotation: Camera orientation with button 1 + mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(ROTATION_AXIS_Z_POS), JOYSTICK_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(ROTATION_AXIS_Z_NEG), JOYSTICK_YAW_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(ROTATION_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED); + mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(ROTATION_AXIS_Y_POS), JOYSTICK_PITCH_SPEED); + + // Button controls + // Zoom + mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_1), BOOM_SPEED); + mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_2), BOOM_SPEED); + + // Zoom + // mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(ROTATION_AXIS_Z_NEG), BOOM_SPEED); + // mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(ROTATION_AXIS_Z_POS), BOOM_SPEED); + +} + +float ConnexionData::getButton(int channel) const { + if (!_buttonPressedMap.empty()) { + if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { + return 1.0f; + } else { + return 0.0f; + } + } + return 0.0f; +} + +float ConnexionData::getAxis(int channel) const { + auto axis = _axisStateMap.find(channel); + if (axis != _axisStateMap.end()) { + return (*axis).second; + } else { + return 0.0f; + } +} + +UserInputMapper::Input ConnexionData::makeInput(ConnexionData::ButtonChannel button) { + return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); +} + +UserInputMapper::Input ConnexionData::makeInput(ConnexionData::PositionChannel axis) { + return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +} + +void ConnexionData::update() { + // the update is done in the ConnexionClient class. + // for windows in the nativeEventFilter the inputmapper is connected or registed or removed when an 3Dconnnexion device is attached or detached + // for osx the api will call DeviceAddedHandler or DeviceRemoveHandler when a 3Dconnexion device is attached or detached +} + +ConnexionClient& ConnexionClient::getInstance() { + static ConnexionClient sharedInstance; + return sharedInstance; +} + +#ifdef HAVE_3DCONNEXIONCLIENT + +#ifdef Q_OS_WIN + +void ConnexionClient::toggleConnexion(bool shouldEnable) { + ConnexionData& connexiondata = ConnexionData::getInstance(); + if (shouldEnable && connexiondata.getDeviceID() == 0) { + init(); + } + if (!shouldEnable && connexiondata.getDeviceID() != 0) { + destroy(); + } +} + +void ConnexionClient::init() { + if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { + fLast3dmouseInputTime = 0; + + InitializeRawInput(GetActiveWindow()); + + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + } +} + +void ConnexionClient::destroy() { + QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); + ConnexionData& connexiondata = ConnexionData::getInstance(); + int deviceid = connexiondata.getDeviceID(); + connexiondata.setDeviceID(0); + Application::getUserInputMapper()->removeDevice(deviceid); +} + +#define LOGITECH_VENDOR_ID 0x46d + +#ifndef RIDEV_DEVNOTIFY +#define RIDEV_DEVNOTIFY 0x00002000 +#endif + +const int TRACE_RIDI_DEVICENAME = 0; +const int TRACE_RIDI_DEVICEINFO = 0; + +#ifdef _WIN64 +typedef unsigned __int64 QWORD; +#endif + +// object angular velocity per mouse tick 0.008 milliradians per second per count +static const double k3dmouseAngularVelocity = 8.0e-6; // radians per second per count + +static const int kTimeToLive = 5; + +enum ConnexionPid { + CONNEXIONPID_SPACEPILOT = 0xc625, + CONNEXIONPID_SPACENAVIGATOR = 0xc626, + CONNEXIONPID_SPACEEXPLORER = 0xc627, + CONNEXIONPID_SPACENAVIGATORFORNOTEBOOKS = 0xc628, + CONNEXIONPID_SPACEPILOTPRO = 0xc629 +}; + +// e3dmouse_virtual_key +enum V3dk { + V3DK_INVALID = 0, + V3DK_MENU = 1, V3DK_FIT, + V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, V3DK_BOTTOM, V3DK_BACK, + V3DK_CW, V3DK_CCW, + V3DK_ISO1, V3DK_ISO2, + V3DK_1, V3DK_2, V3DK_3, V3DK_4, V3DK_5, V3DK_6, V3DK_7, V3DK_8, V3DK_9, V3DK_10, + V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL, + V3DK_ROTATE, V3DK_PANZOOM, V3DK_DOMINANT, + V3DK_PLUS, V3DK_MINUS +}; + +struct tag_VirtualKeys { + ConnexionPid pid; + size_t nKeys; + V3dk *vkeys; +}; + +// e3dmouse_virtual_key +static const V3dk SpaceExplorerKeys[] = { + V3DK_INVALID, // there is no button 0 + V3DK_1, V3DK_2, + V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, + V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL, + V3DK_FIT, V3DK_MENU, + V3DK_PLUS, V3DK_MINUS, + V3DK_ROTATE +}; + +//e3dmouse_virtual_key +static const V3dk SpacePilotKeys[] = { + V3DK_INVALID, + V3DK_1, V3DK_2, V3DK_3, V3DK_4, V3DK_5, V3DK_6, + V3DK_TOP, V3DK_LEFT, V3DK_RIGHT, V3DK_FRONT, + V3DK_ESC, V3DK_ALT, V3DK_SHIFT, V3DK_CTRL, + V3DK_FIT, V3DK_MENU, + V3DK_PLUS, V3DK_MINUS, + V3DK_DOMINANT, V3DK_ROTATE, +}; + +static const struct tag_VirtualKeys _3dmouseVirtualKeys[] = { + CONNEXIONPID_SPACEPILOT, + sizeof(SpacePilotKeys) / sizeof(SpacePilotKeys[0]), + const_cast(SpacePilotKeys), + CONNEXIONPID_SPACEEXPLORER, + sizeof(SpaceExplorerKeys) / sizeof(SpaceExplorerKeys[0]), + const_cast(SpaceExplorerKeys) +}; + +// Converts a hid device keycode (button identifier) of a pre-2009 3Dconnexion USB device to the standard 3d mouse virtual key definition. +// pid USB Product ID (PID) of 3D mouse device +// hidKeyCode Hid keycode as retrieved from a Raw Input packet +// return The standard 3d mouse virtual key (button identifier) or zero if an error occurs. + +// Converts a hid device keycode (button identifier) of a pre-2009 3Dconnexion USB device +// to the standard 3d mouse virtual key definition. +unsigned short HidToVirtualKey(unsigned long pid, unsigned short hidKeyCode) { + unsigned short virtualkey = hidKeyCode; + for (size_t i = 0; iremoveDevice(deviceid); + } + + if (!Is3dmouseAttached()) { + return false; + } + + MSG* message = (MSG*)(msg); + + if (message->message == WM_INPUT) { + HRAWINPUT hRawInput = reinterpret_cast(message->lParam); + OnRawInput(RIM_INPUT, hRawInput); + if (result != 0) { + result = 0; + } + return true; + } + return false; +} + +// Access the mouse parameters structure +I3dMouseParam& ConnexionClient::MouseParams() { + return f3dMouseParams; +} + +// Access the mouse parameters structure +const I3dMouseParam& ConnexionClient::MouseParams() const { + return f3dMouseParams; +} + +//Called with the processed motion data when a 3D mouse event is received +void ConnexionClient::Move3d(HANDLE device, std::vector& motionData) { + Q_UNUSED(device); + ConnexionData& connexiondata = ConnexionData::getInstance(); + connexiondata.cc_position = { motionData[0] * 1000, motionData[1] * 1000, motionData[2] * 1000 }; + connexiondata.cc_rotation = { motionData[3] * 1500, motionData[4] * 1500, motionData[5] * 1500 }; + connexiondata.handleAxisEvent(); +} + +//Called when a 3D mouse key is pressed +void ConnexionClient::On3dmouseKeyDown(HANDLE device, int virtualKeyCode) { + Q_UNUSED(device); + ConnexionData& connexiondata = ConnexionData::getInstance(); + connexiondata.setButton(virtualKeyCode); +} + +//Called when a 3D mouse key is released +void ConnexionClient::On3dmouseKeyUp(HANDLE device, int virtualKeyCode) { + Q_UNUSED(device); + ConnexionData& connexiondata = ConnexionData::getInstance(); + connexiondata.setButton(0); +} + +//Get an initialized array of PRAWINPUTDEVICE for the 3D devices +//pNumDevices returns the number of devices to register. Currently this is always 1. +static PRAWINPUTDEVICE GetDevicesToRegister(unsigned int* pNumDevices) { + // Array of raw input devices to register + static RAWINPUTDEVICE sRawInputDevices[] = { + { 0x01, 0x08, 0x00, 0x00 } // Usage Page = 0x01 Generic Desktop Page, Usage Id= 0x08 Multi-axis Controller + }; + + if (pNumDevices) { + *pNumDevices = sizeof(sRawInputDevices) / sizeof(sRawInputDevices[0]); + } + + return sRawInputDevices; +} + +//Detect the 3D mouse +bool ConnexionClient::Is3dmouseAttached() { + unsigned int numDevicesOfInterest = 0; + PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevicesOfInterest); + + unsigned int nDevices = 0; + + if (::GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0) { + return false; + } + + if (nDevices == 0) { + return false; + } + + std::vector rawInputDeviceList(nDevices); + if (::GetRawInputDeviceList(&rawInputDeviceList[0], &nDevices, sizeof(RAWINPUTDEVICELIST)) == static_cast(-1)) { + return false; + } + + for (unsigned int i = 0; i < nDevices; ++i) { + RID_DEVICE_INFO rdi = { sizeof(rdi) }; + unsigned int cbSize = sizeof(rdi); + + if (GetRawInputDeviceInfo(rawInputDeviceList[i].hDevice, RIDI_DEVICEINFO, &rdi, &cbSize) > 0) { + //skip non HID and non logitec (3DConnexion) devices + if (rdi.dwType != RIM_TYPEHID || rdi.hid.dwVendorId != LOGITECH_VENDOR_ID) { + continue; + } + + //check if devices matches Multi-axis Controller + for (unsigned int j = 0; j < numDevicesOfInterest; ++j) { + if (devicesToRegister[j].usUsage == rdi.hid.usUsage + && devicesToRegister[j].usUsagePage == rdi.hid.usUsagePage) { + return true; + } + } + } + } + return false; +} + +// Initialize the window to recieve raw-input messages +// This needs to be called initially so that Windows will send the messages from the 3D mouse to the window. +bool ConnexionClient::InitializeRawInput(HWND hwndTarget) { + fWindow = hwndTarget; + + // Simply fail if there is no window + if (!hwndTarget) { + return false; + } + + unsigned int numDevices = 0; + PRAWINPUTDEVICE devicesToRegister = GetDevicesToRegister(&numDevices); + + if (numDevices == 0) { + return false; + } + + // Get OS version. + OSVERSIONINFO osvi = { sizeof(OSVERSIONINFO), 0 }; + ::GetVersionEx(&osvi); + + unsigned int cbSize = sizeof(devicesToRegister[0]); + for (size_t i = 0; i < numDevices; i++) { + // Set the target window to use + //devicesToRegister[i].hwndTarget = hwndTarget; + + // If Vista or newer, enable receiving the WM_INPUT_DEVICE_CHANGE message. + if (osvi.dwMajorVersion >= 6) { + devicesToRegister[i].dwFlags |= RIDEV_DEVNOTIFY; + } + } + return (::RegisterRawInputDevices(devicesToRegister, numDevices, cbSize) != FALSE); +} + +//Get the raw input data from Windows +UINT ConnexionClient::GetRawInputBuffer(PRAWINPUT pData, PUINT pcbSize, UINT cbSizeHeader) { + //Includes workaround for incorrect alignment of the RAWINPUT structure on x64 os + //when running as Wow64 (copied directly from 3DConnexion code) +#ifdef _WIN64 + return ::GetRawInputBuffer(pData, pcbSize, cbSizeHeader); +#else + BOOL bIsWow64 = FALSE; + ::IsWow64Process(GetCurrentProcess(), &bIsWow64); + if (!bIsWow64 || pData == NULL) { + return ::GetRawInputBuffer(pData, pcbSize, cbSizeHeader); + } else { + HWND hwndTarget = fWindow; + + size_t cbDataSize = 0; + UINT nCount = 0; + PRAWINPUT pri = pData; + + MSG msg; + while (PeekMessage(&msg, hwndTarget, WM_INPUT, WM_INPUT, PM_NOREMOVE)) { + HRAWINPUT hRawInput = reinterpret_cast(msg.lParam); + size_t cbSize = *pcbSize - cbDataSize; + if (::GetRawInputData(hRawInput, RID_INPUT, pri, &cbSize, cbSizeHeader) == static_cast(-1)) { + if (nCount == 0) { + return static_cast(-1); + } else { + break; + } + } + ++nCount; + + // Remove the message for the data just read + PeekMessage(&msg, hwndTarget, WM_INPUT, WM_INPUT, PM_REMOVE); + + pri = NEXTRAWINPUTBLOCK(pri); + cbDataSize = reinterpret_cast(pri)-reinterpret_cast(pData); + if (cbDataSize >= *pcbSize) { + cbDataSize = *pcbSize; + break; + } + } + return nCount; + } +#endif +} + +// Process the raw input device data +// On3dmouseInput() does all the preprocessing of the rawinput device data before +// finally calling the Move3d method. +void ConnexionClient::On3dmouseInput() { + // Don't do any data processing in background + bool bIsForeground = (::GetActiveWindow() != NULL); + if (!bIsForeground) { + // set all cached data to zero so that a zero event is seen and the cached data deleted + for (std::map::iterator it = fDevice2Data.begin(); it != fDevice2Data.end(); it++) { + it->second.fAxes.assign(6, .0); + it->second.fIsDirty = true; + } + } + + DWORD dwNow = ::GetTickCount(); // Current time; + DWORD dwElapsedTime; // Elapsed time since we were last here + + if (0 == fLast3dmouseInputTime) { + dwElapsedTime = 10; // System timer resolution + } else { + dwElapsedTime = dwNow - fLast3dmouseInputTime; + if (fLast3dmouseInputTime > dwNow) { + dwElapsedTime = ~dwElapsedTime + 1; + } + if (dwElapsedTime<1) { + dwElapsedTime = 1; + } else if (dwElapsedTime > 500) { + // Check for wild numbers because the device was removed while sending data + dwElapsedTime = 10; + } + } + + //qDebug("On3DmouseInput() period is %dms\n", dwElapsedTime); + + float mouseData2Rotation = k3dmouseAngularVelocity; + // v = w * r, we don't know r yet so lets assume r=1.) + float mouseData2PanZoom = k3dmouseAngularVelocity; + + // Grab the I3dmouseParam interface + I3dMouseParam& i3dmouseParam = f3dMouseParams; + // Take a look at the users preferred speed setting and adjust the sensitivity accordingly + I3dMouseSensor::Speed speedSetting = i3dmouseParam.GetSpeed(); + // See "Programming for the 3D Mouse", Section 5.1.3 + float speed = (speedSetting == I3dMouseSensor::SPEED_LOW ? 0.25f : speedSetting == I3dMouseSensor::SPEED_HIGH ? 4.f : 1.f); + + // Multiplying by the following will convert the 3d mouse data to real world units + mouseData2PanZoom *= speed; + mouseData2Rotation *= speed; + + std::map::iterator iterator = fDevice2Data.begin(); + while (iterator != fDevice2Data.end()) { + + // If we have not received data for a while send a zero event + if ((--(iterator->second.fTimeToLive)) == 0) { + iterator->second.fAxes.assign(6, .0); + } else if ( !iterator->second.fIsDirty) { //!t_bPoll3dmouse && + // If we are not polling then only handle the data that was actually received + ++iterator; + continue; + } + iterator->second.fIsDirty = false; + + // get a copy of the device + HANDLE hdevice = iterator->first; + + // get a copy of the motion vectors and apply the user filters + std::vector motionData = iterator->second.fAxes; + + // apply the user filters + + // Pan Zoom filter + // See "Programming for the 3D Mouse", Section 5.1.2 + if (!i3dmouseParam.IsPanZoom()) { + // Pan zoom is switched off so set the translation vector values to zero + motionData[0] = motionData[1] = motionData[2] = 0.; + } + + // Rotate filter + // See "Programming for the 3D Mouse", Section 5.1.1 + if (!i3dmouseParam.IsRotate()) { + // Rotate is switched off so set the rotation vector values to zero + motionData[3] = motionData[4] = motionData[5] = 0.; + } + + // convert the translation vector into physical data + for (int axis = 0; axis < 3; axis++) { + motionData[axis] *= mouseData2PanZoom; + } + + // convert the directed Rotate vector into physical data + // See "Programming for the 3D Mouse", Section 7.2.2 + for (int axis = 3; axis < 6; axis++) { + motionData[axis] *= mouseData2Rotation; + } + + // Now that the data has had the filters and sensitivty settings applied + // calculate the displacements since the last view update + for (int axis = 0; axis < 6; axis++) { + motionData[axis] *= dwElapsedTime; + } + + // Now a bit of book keeping before passing on the data + if (iterator->second.IsZero()) { + iterator = fDevice2Data.erase(iterator); + } else { + ++iterator; + } + + // Work out which will be the next device + HANDLE hNextDevice = 0; + if (iterator != fDevice2Data.end()) { + hNextDevice = iterator->first; + } + + // Pass the 3dmouse input to the view controller + Move3d(hdevice, motionData); + + // Because we don't know what happened in the previous call, the cache might have + // changed so reload the iterator + iterator = fDevice2Data.find(hNextDevice); + } + + if (!fDevice2Data.empty()) { + fLast3dmouseInputTime = dwNow; + } else { + fLast3dmouseInputTime = 0; + } +} + +//Called when new raw input data is available +void ConnexionClient::OnRawInput(UINT nInputCode, HRAWINPUT hRawInput) { + const size_t cbSizeOfBuffer = 1024; + BYTE pBuffer[cbSizeOfBuffer]; + + PRAWINPUT pRawInput = reinterpret_cast(pBuffer); + UINT cbSize = cbSizeOfBuffer; + + if (::GetRawInputData(hRawInput, RID_INPUT, pRawInput, &cbSize, sizeof(RAWINPUTHEADER)) == static_cast(-1)) { + return; + } + + bool b3dmouseInput = TranslateRawInputData(nInputCode, pRawInput); + ::DefRawInputProc(&pRawInput, 1, sizeof(RAWINPUTHEADER)); + + // Check for any buffered messages + cbSize = cbSizeOfBuffer; + UINT nCount = this->GetRawInputBuffer(pRawInput, &cbSize, sizeof(RAWINPUTHEADER)); + if (nCount == (UINT)-1) { + qDebug("GetRawInputBuffer returned error %d\n", GetLastError()); + } + + while (nCount>0 && nCount != static_cast(-1)) { + PRAWINPUT pri = pRawInput; + UINT nInput; + for (nInput = 0; nInputGetRawInputBuffer(pRawInput, &cbSize, sizeof(RAWINPUTHEADER)); + } + + // If we have mouse input data for the app then tell tha app about it + if (b3dmouseInput) { + On3dmouseInput(); + } +} + +bool ConnexionClient::TranslateRawInputData(UINT nInputCode, PRAWINPUT pRawInput) { + bool bIsForeground = (nInputCode == RIM_INPUT); + + // We are not interested in keyboard or mouse data received via raw input + if (pRawInput->header.dwType != RIM_TYPEHID) { + return false; + } + + if (TRACE_RIDI_DEVICENAME == 1) { + UINT dwSize = 0; + if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, NULL, &dwSize) == 0) { + std::vector szDeviceName(dwSize + 1); + if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICENAME, &szDeviceName[0], &dwSize) > 0) { + qDebug("Device Name = %s\nDevice handle = 0x%x\n", &szDeviceName[0], pRawInput->header.hDevice); + } + } + } + + RID_DEVICE_INFO sRidDeviceInfo; + sRidDeviceInfo.cbSize = sizeof(RID_DEVICE_INFO); + UINT cbSize = sizeof(RID_DEVICE_INFO); + + if (::GetRawInputDeviceInfo(pRawInput->header.hDevice, RIDI_DEVICEINFO, &sRidDeviceInfo, &cbSize) == cbSize) { + if (TRACE_RIDI_DEVICEINFO == 1) { + switch (sRidDeviceInfo.dwType) { + case RIM_TYPEMOUSE: + qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEMOUSE\n"); + break; + case RIM_TYPEKEYBOARD: + qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEKEYBOARD\n"); + break; + case RIM_TYPEHID: + qDebug("\tsRidDeviceInfo.dwType=RIM_TYPEHID\n"); + qDebug("\tVendor=0x%x\n\tProduct=0x%x\n\tUsagePage=0x%x\n\tUsage=0x%x\n", + sRidDeviceInfo.hid.dwVendorId, + sRidDeviceInfo.hid.dwProductId, + sRidDeviceInfo.hid.usUsagePage, + sRidDeviceInfo.hid.usUsage); + break; + } + } + + if (sRidDeviceInfo.hid.dwVendorId == LOGITECH_VENDOR_ID) { + if (pRawInput->data.hid.bRawData[0] == 0x01) { // Translation vector + TInputData& deviceData = fDevice2Data[pRawInput->header.hDevice]; + deviceData.fTimeToLive = kTimeToLive; + if (bIsForeground) { + short* pnRawData = reinterpret_cast(&pRawInput->data.hid.bRawData[1]); + // Cache the pan zoom data + deviceData.fAxes[0] = static_cast(pnRawData[0]); + deviceData.fAxes[1] = static_cast(pnRawData[1]); + deviceData.fAxes[2] = static_cast(pnRawData[2]); + + //qDebug("Pan/Zoom RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[0], pnRawData[1], pnRawData[2]); + + if (pRawInput->data.hid.dwSizeHid >= 13) { // Highspeed package + // Cache the rotation data + deviceData.fAxes[3] = static_cast(pnRawData[3]); + deviceData.fAxes[4] = static_cast(pnRawData[4]); + deviceData.fAxes[5] = static_cast(pnRawData[5]); + deviceData.fIsDirty = true; + + //qDebug("Rotation RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[3], pnRawData[4], pnRawData[5]); + return true; + } + } else { // Zero out the data if the app is not in forground + deviceData.fAxes.assign(6, 0.f); + } + } else if (pRawInput->data.hid.bRawData[0] == 0x02) { // Rotation vector + // If we are not in foreground do nothing + // The rotation vector was zeroed out with the translation vector in the previous message + if (bIsForeground) { + TInputData& deviceData = fDevice2Data[pRawInput->header.hDevice]; + deviceData.fTimeToLive = kTimeToLive; + + short* pnRawData = reinterpret_cast(&pRawInput->data.hid.bRawData[1]); + // Cache the rotation data + deviceData.fAxes[3] = static_cast(pnRawData[0]); + deviceData.fAxes[4] = static_cast(pnRawData[1]); + deviceData.fAxes[5] = static_cast(pnRawData[2]); + deviceData.fIsDirty = true; + + //qDebug("Rotation RI Data =\t0x%x,\t0x%x,\t0x%x\n", pnRawData[0], pnRawData[1], pnRawData[2]); + + return true; + } + } else if (pRawInput->data.hid.bRawData[0] == 0x03) { // Keystate change + // this is a package that contains 3d mouse keystate information + // bit0=key1, bit=key2 etc. + + unsigned long dwKeystate = *reinterpret_cast(&pRawInput->data.hid.bRawData[1]); + + //qDebug("ButtonData =0x%x\n", dwKeystate); + + // Log the keystate changes + unsigned long dwOldKeystate = fDevice2Keystate[pRawInput->header.hDevice]; + if (dwKeystate != 0) { + fDevice2Keystate[pRawInput->header.hDevice] = dwKeystate; + } else { + fDevice2Keystate.erase(pRawInput->header.hDevice); + } + + // Only call the keystate change handlers if the app is in foreground + if (bIsForeground) { + unsigned long dwChange = dwKeystate ^ dwOldKeystate; + + for (int nKeycode = 1; nKeycode<33; nKeycode++) { + if (dwChange & 0x01) { + int nVirtualKeyCode = HidToVirtualKey(sRidDeviceInfo.hid.dwProductId, nKeycode); + if (nVirtualKeyCode) { + if (dwKeystate & 0x01) { + On3dmouseKeyDown(pRawInput->header.hDevice, nVirtualKeyCode); + } else { + On3dmouseKeyUp(pRawInput->header.hDevice, nVirtualKeyCode); + } + } + } + dwChange >>= 1; + dwKeystate >>= 1; + } + } + } + } + } + return false; +} + +MouseParameters::MouseParameters() : + fNavigation(NAVIGATION_OBJECT_MODE), + fPivot(PIVOT_AUTO), + fPivotVisibility(PIVOT_SHOW), + fIsLockHorizon(true), + fIsPanZoom(true), + fIsRotate(true), + fSpeed(SPEED_LOW) +{ +} + +bool MouseParameters::IsPanZoom() const { + return fIsPanZoom; +} + +bool MouseParameters::IsRotate() const { + return fIsRotate; +} + +MouseParameters::Speed MouseParameters::GetSpeed() const { + return fSpeed; +} + +void MouseParameters::SetPanZoom(bool isPanZoom) { + fIsPanZoom = isPanZoom; +} + +void MouseParameters::SetRotate(bool isRotate) { + fIsRotate = isRotate; +} + +void MouseParameters::SetSpeed(Speed speed) { + fSpeed = speed; +} + +MouseParameters::Navigation MouseParameters::GetNavigationMode() const { + return fNavigation; +} + +MouseParameters::Pivot MouseParameters::GetPivotMode() const { + return fPivot; +} + +MouseParameters::PivotVisibility MouseParameters::GetPivotVisibility() const { + return fPivotVisibility; +} + +bool MouseParameters::IsLockHorizon() const { + return fIsLockHorizon; +} + +void MouseParameters::SetLockHorizon(bool bOn) { + fIsLockHorizon=bOn; +} + +void MouseParameters::SetNavigationMode(Navigation navigation) { + fNavigation=navigation; +} + +void MouseParameters::SetPivotMode(Pivot pivot) { + if (fPivot!=PIVOT_MANUAL || pivot!=PIVOT_AUTO_OVERRIDE) { + fPivot = pivot; + } +} + +void MouseParameters::SetPivotVisibility(PivotVisibility visibility) { + fPivotVisibility = visibility; +} + +#else + +int fConnexionClientID; + +static ConnexionDeviceState lastState; + +static void DeviceAddedHandler(unsigned int connection); +static void DeviceRemovedHandler(unsigned int connection); +static void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument); + +void ConnexionClient::toggleConnexion(bool shouldEnable) { + if (shouldEnable && !Is3dmouseAttached()) { + init(); + } + if (!shouldEnable && Is3dmouseAttached()) { + destroy(); + } +} + +void ConnexionClient::init() { + // Make sure the framework is installed + if (Menu::getInstance()->isOptionChecked(MenuOption::Connexion)) { + // Install message handler and register our client + InstallConnexionHandlers(MessageHandler, DeviceAddedHandler, DeviceRemovedHandler); + // Either use this to take over in our application only... does not work + // fConnexionClientID = RegisterConnexionClient('MCTt', "\pConnexion Client Test", kConnexionClientModeTakeOver, kConnexionMaskAll); + + // ...or use this to take over system-wide + fConnexionClientID = RegisterConnexionClient(kConnexionClientWildcard, NULL, kConnexionClientModeTakeOver, kConnexionMaskAll); + ConnexionData& connexiondata = ConnexionData::getInstance(); + memcpy(&connexiondata.clientId, &fConnexionClientID, (long)sizeof(int)); + + // A separate API call is required to capture buttons beyond the first 8 + SetConnexionClientButtonMask(fConnexionClientID, kConnexionMaskAllButtons); + + // use default switches + ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchesDisabled, NULL); + + if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { + auto userInputMapper = DependencyManager::get(); + connexiondata.registerToUserInputMapper(*userInputMapper); + connexiondata.assignDefaultInputMapping(*userInputMapper); + UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); + } + //let one axis be dominant + //ConnexionClientControl(fConnexionClientID, kConnexionCtlSetSwitches, kConnexionSwitchDominant | kConnexionSwitchEnableAll, NULL); + } +} + +void ConnexionClient::destroy() { + // Make sure the framework is installed + if (&InstallConnexionHandlers != NULL) { + // Unregister our client and clean up all handlers + if (fConnexionClientID) { + UnregisterConnexionClient(fConnexionClientID); + } + CleanupConnexionHandlers(); + fConnexionClientID = 0; + ConnexionData& connexiondata = ConnexionData::getInstance(); + if (connexiondata.getDeviceID()!=0) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(connexiondata.getDeviceID()); + connexiondata.setDeviceID(0); + } + } +} + +void DeviceAddedHandler(unsigned int connection) { + ConnexionData& connexiondata = ConnexionData::getInstance(); + if (connexiondata.getDeviceID() == 0) { + qCWarning(interfaceapp) << "3Dconnexion device added "; + auto userInputMapper = DependencyManager::get(); + connexiondata.registerToUserInputMapper(*userInputMapper); + connexiondata.assignDefaultInputMapping(*userInputMapper); + UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); + } +} + +void DeviceRemovedHandler(unsigned int connection) { + ConnexionData& connexiondata = ConnexionData::getInstance(); + if (connexiondata.getDeviceID() != 0) { + qCWarning(interfaceapp) << "3Dconnexion device removed"; + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(connexiondata.getDeviceID()); + connexiondata.setDeviceID(0); + } +} + +bool ConnexionClient::Is3dmouseAttached() { + int result; + if (fConnexionClientID) { + if (ConnexionControl(kConnexionCtlGetDeviceID, 0, &result)) { + return false; + } + return true; + } + return false; +} + +void MessageHandler(unsigned int connection, unsigned int messageType, void *messageArgument) { + ConnexionDeviceState *state; + + switch (messageType) { + case kConnexionMsgDeviceState: + state = (ConnexionDeviceState*)messageArgument; + if (state->client == fConnexionClientID) { + ConnexionData& connexiondata = ConnexionData::getInstance(); + connexiondata.cc_position = { state->axis[0], state->axis[1], state->axis[2] }; + connexiondata.cc_rotation = { state->axis[3], state->axis[4], state->axis[5] }; + + connexiondata.handleAxisEvent(); + if (state->buttons != lastState.buttons) { + connexiondata.setButton(state->buttons); + } + memmove(&lastState, state, (long)sizeof(ConnexionDeviceState)); + } + break; + case kConnexionMsgPrefsChanged: + // the prefs have changed, do something + break; + default: + // other messageTypes can happen and should be ignored + break; + } + +} + +#endif // __APPLE__ + +#endif // HAVE_3DCONNEXIONCLIENT diff --git a/interface/src/devices/3DConnexionClient.h b/interface/src/devices/3DConnexionClient.h index f0e9f10785..01e7883f47 100755 --- a/interface/src/devices/3DConnexionClient.h +++ b/interface/src/devices/3DConnexionClient.h @@ -11,12 +11,13 @@ #ifndef hifi_3DConnexionClient_h #define hifi_3DConnexionClient_h -#include -#include +#include +#include +#include + #include "InterfaceLogging.h" #include "Application.h" -#include "ui/UserInputMapper.h" #ifndef HAVE_3DCONNEXIONCLIENT class ConnexionClient : public QObject { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp deleted file mode 100644 index 16685df96f..0000000000 --- a/interface/src/devices/OculusManager.cpp +++ /dev/null @@ -1,887 +0,0 @@ -// -// OculusManager.cpp -// interface/src/devices -// -// Created by Stephen Birarda on 5/9/13. -// Refactored by Ben Arnold on 6/30/2014 -// Copyright 2012 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OculusManager.h" - -#include -#include -#include -#include -#include -#include -#include -#include - - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "InterfaceLogging.h" -#include "Application.h" -#include "ui/overlays/Text3DOverlay.h" - -template -void for_each_eye(Function function) { - for (ovrEyeType eye = ovrEyeType::ovrEye_Left; - eye < ovrEyeType::ovrEye_Count; - eye = static_cast(eye + 1)) { - function(eye); - } -} - -template -void for_each_eye(const ovrHmd & hmd, Function function) { - for (int i = 0; i < ovrEye_Count; ++i) { - ovrEyeType eye = hmd->EyeRenderOrder[i]; - function(eye); - } -} -enum CalibrationState { - UNCALIBRATED, - WAITING_FOR_DELTA, - WAITING_FOR_ZERO, - WAITING_FOR_ZERO_HELD, - CALIBRATED -}; - -inline glm::mat4 toGlm(const ovrMatrix4f & om) { - return glm::transpose(glm::make_mat4(&om.M[0][0])); -} - -inline glm::mat4 toGlm(const ovrFovPort & fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) { - return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true)); -} - -inline glm::vec3 toGlm(const ovrVector3f & ov) { - return glm::make_vec3(&ov.x); -} - -inline glm::vec2 toGlm(const ovrVector2f & ov) { - return glm::make_vec2(&ov.x); -} - -inline glm::ivec2 toGlm(const ovrVector2i & ov) { - return glm::ivec2(ov.x, ov.y); -} - -inline glm::uvec2 toGlm(const ovrSizei & ov) { - return glm::uvec2(ov.w, ov.h); -} - -inline glm::quat toGlm(const ovrQuatf & oq) { - return glm::make_quat(&oq.x); -} - -inline glm::mat4 toGlm(const ovrPosef & op) { - glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation)); - glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position)); - return translation * orientation; -} - -inline ovrMatrix4f ovrFromGlm(const glm::mat4 & m) { - ovrMatrix4f result; - glm::mat4 transposed(glm::transpose(m)); - memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16); - return result; -} - -inline ovrVector3f ovrFromGlm(const glm::vec3 & v) { - return{ v.x, v.y, v.z }; -} - -inline ovrVector2f ovrFromGlm(const glm::vec2 & v) { - return{ v.x, v.y }; -} - -inline ovrSizei ovrFromGlm(const glm::uvec2 & v) { - return{ (int)v.x, (int)v.y }; -} - -inline ovrQuatf ovrFromGlm(const glm::quat & q) { - return{ q.x, q.y, q.z, q.w }; -} - - - -#ifdef Q_OS_WIN - -// A base class for FBO wrappers that need to use the Oculus C -// API to manage textures via ovrHmd_CreateSwapTextureSetGL, -// ovrHmd_CreateMirrorTextureGL, etc -template -struct RiftFramebufferWrapper : public FramebufferWrapper { - ovrHmd hmd; - RiftFramebufferWrapper(const ovrHmd & hmd) : hmd(hmd) { - color = 0; - depth = 0; - }; - - void Resize(const uvec2 & size) { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oglplus::GetName(fbo)); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - this->size = size; - initColor(); - initDone(); - } - -protected: - virtual void initDepth() override final { - } -}; - -// A wrapper for constructing and using a swap texture set, -// where each frame you draw to a texture via the FBO, -// then submit it and increment to the next texture. -// The Oculus SDK manages the creation and destruction of -// the textures -struct SwapFramebufferWrapper : public RiftFramebufferWrapper { - SwapFramebufferWrapper(const ovrHmd & hmd) - : RiftFramebufferWrapper(hmd) { - } - - ~SwapFramebufferWrapper() { - if (color) { - ovrHmd_DestroySwapTextureSet(hmd, color); - color = nullptr; - } - } - - void Increment() { - ++color->CurrentIndex; - color->CurrentIndex %= color->TextureCount; - } - -protected: - virtual void initColor() override { - if (color) { - ovrHmd_DestroySwapTextureSet(hmd, color); - color = nullptr; - } - - ovrResult result = ovrHmd_CreateSwapTextureSetGL(hmd, GL_RGBA, size.x, size.y, &color); - Q_ASSERT(OVR_SUCCESS(result)); - - for (int i = 0; i < color->TextureCount; ++i) { - ovrGLTexture& ovrTex = (ovrGLTexture&)color->Textures[i]; - glBindTexture(GL_TEXTURE_2D, ovrTex.OGL.TexId); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - glBindTexture(GL_TEXTURE_2D, 0); - } - - virtual void initDone() override { - } - - virtual void onBind(oglplus::Framebuffer::Target target) override { - ovrGLTexture& tex = (ovrGLTexture&)(color->Textures[color->CurrentIndex]); - glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.OGL.TexId, 0); - } - - virtual void onUnbind(oglplus::Framebuffer::Target target) override { - glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - } -}; - - -// We use a FBO to wrap the mirror texture because it makes it easier to -// render to the screen via glBlitFramebuffer -struct MirrorFramebufferWrapper : public RiftFramebufferWrapper { - MirrorFramebufferWrapper(const ovrHmd & hmd) - : RiftFramebufferWrapper(hmd) { - } - - virtual ~MirrorFramebufferWrapper() { - if (color) { - ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); - color = nullptr; - } - } - -private: - void initColor() override { - if (color) { - ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); - color = nullptr; - } - ovrResult result = ovrHmd_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); - Q_ASSERT(OVR_SUCCESS(result)); - } - - void initDone() override { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oglplus::GetName(fbo)); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color->OGL.TexId, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - } -}; - -static SwapFramebufferWrapper* _swapFbo{ nullptr }; -static MirrorFramebufferWrapper* _mirrorFbo{ nullptr }; -static ovrLayerEyeFov _sceneLayer; - -#else - -static ovrTexture _eyeTextures[ovrEye_Count]; -static GlWindow* _outputWindow{ nullptr }; - -#endif - -static bool _isConnected = false; -static ovrHmd _ovrHmd; -static ovrFovPort _eyeFov[ovrEye_Count]; -static ovrEyeRenderDesc _eyeRenderDesc[ovrEye_Count]; -static ovrSizei _renderTargetSize; -static glm::mat4 _eyeProjection[ovrEye_Count]; -static unsigned int _frameIndex = 0; -static bool _frameTimingActive = false; -static Camera* _camera = NULL; -static ovrEyeType _activeEye = ovrEye_Count; -static bool _hswDismissed = false; - -static const float CALIBRATION_DELTA_MINIMUM_LENGTH = 0.02f; -static const float CALIBRATION_DELTA_MINIMUM_ANGLE = 5.0f * RADIANS_PER_DEGREE; -static const float CALIBRATION_ZERO_MAXIMUM_LENGTH = 0.01f; -static const float CALIBRATION_ZERO_MAXIMUM_ANGLE = 2.0f * RADIANS_PER_DEGREE; -static const quint64 CALIBRATION_ZERO_HOLD_TIME = 3000000; // usec -static const float CALIBRATION_MESSAGE_DISTANCE = 2.5f; -static CalibrationState _calibrationState; -static glm::vec3 _calibrationPosition; -static glm::quat _calibrationOrientation; -static quint64 _calibrationStartTime; -static int _calibrationMessage = 0; -static glm::vec3 _eyePositions[ovrEye_Count]; -// TODO expose this as a developer toggle -static bool _eyePerFrameMode = false; -static ovrEyeType _lastEyeRendered = ovrEye_Count; -static ovrSizei _recommendedTexSize = { 0, 0 }; -static float _offscreenRenderScale = 1.0; -static glm::mat4 _combinedProjection; -static ovrPosef _eyeRenderPoses[ovrEye_Count]; -static ovrRecti _eyeViewports[ovrEye_Count]; -static ovrVector3f _eyeOffsets[ovrEye_Count]; - - -glm::vec3 OculusManager::getLeftEyePosition() { return _eyePositions[ovrEye_Left]; } -glm::vec3 OculusManager::getRightEyePosition() { return _eyePositions[ovrEye_Right]; } -glm::vec3 OculusManager::getMidEyePosition() { return (_eyePositions[ovrEye_Left] + _eyePositions[ovrEye_Right]) / 2.0f; } - -void OculusManager::connect(QOpenGLContext* shareContext) { - qCDebug(interfaceapp) << "Oculus SDK" << OVR_VERSION_STRING; - - ovrInitParams initParams; memset(&initParams, 0, sizeof(initParams)); - -#ifdef DEBUG - initParams.Flags |= ovrInit_Debug; -#endif - - ovr_Initialize(&initParams); - -#ifdef Q_OS_WIN - - ovrResult res = ovrHmd_Create(0, &_ovrHmd); -#ifdef DEBUG - if (!OVR_SUCCESS(res)) { - res = ovrHmd_CreateDebug(ovrHmd_DK2, &_ovrHmd); - Q_ASSERT(OVR_SUCCESS(res)); - } -#endif - -#else - - _ovrHmd = ovrHmd_Create(0); -#ifdef DEBUG - if (!_ovrHmd) { - _ovrHmd = ovrHmd_CreateDebug(ovrHmd_DK2); - } -#endif - -#endif - - if (!_ovrHmd) { - _isConnected = false; - - // we're definitely not in "VR mode" so tell the menu that - Menu::getInstance()->getActionForOption(MenuOption::EnableVRMode)->setChecked(false); - ovr_Shutdown(); - return; - } - - _calibrationState = UNCALIBRATED; - if (!_isConnected) { - UserActivityLogger::getInstance().connectedDevice("hmd", "oculus"); - } - _isConnected = true; - - for_each_eye([&](ovrEyeType eye) { - _eyeFov[eye] = _ovrHmd->DefaultEyeFov[eye]; - _eyeProjection[eye] = toGlm(ovrMatrix4f_Projection(_eyeFov[eye], - DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded)); - ovrEyeRenderDesc erd = ovrHmd_GetRenderDesc(_ovrHmd, eye, _eyeFov[eye]); - _eyeOffsets[eye] = erd.HmdToEyeViewOffset; - }); - ovrFovPort combinedFov = _ovrHmd->MaxEyeFov[0]; - combinedFov.RightTan = _ovrHmd->MaxEyeFov[1].RightTan; - _combinedProjection = toGlm(ovrMatrix4f_Projection(combinedFov, - DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded)); - - _recommendedTexSize = ovrHmd_GetFovTextureSize(_ovrHmd, ovrEye_Left, _eyeFov[ovrEye_Left], 1.0f); - _renderTargetSize = { _recommendedTexSize.w * 2, _recommendedTexSize.h }; - -#ifdef Q_OS_WIN - - _mirrorFbo = new MirrorFramebufferWrapper(_ovrHmd); - _swapFbo = new SwapFramebufferWrapper(_ovrHmd); - _swapFbo->Init(toGlm(_renderTargetSize)); - _sceneLayer.ColorTexture[0] = _swapFbo->color; - _sceneLayer.ColorTexture[1] = nullptr; - _sceneLayer.Viewport[0].Pos = { 0, 0 }; - _sceneLayer.Viewport[0].Size = _recommendedTexSize; - _sceneLayer.Viewport[1].Pos = { _recommendedTexSize.w, 0 }; - _sceneLayer.Viewport[1].Size = _recommendedTexSize; - _sceneLayer.Header.Type = ovrLayerType_EyeFov; - _sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; - for_each_eye([&](ovrEyeType eye) { - _eyeViewports[eye] = _sceneLayer.Viewport[eye]; - _sceneLayer.Fov[eye] = _eyeFov[eye]; - }); - - - -#else - _outputWindow = new GlWindow(shareContext); - _outputWindow->show(); -// _outputWindow->setFlags(Qt::FramelessWindowHint ); -// _outputWindow->resize(_ovrHmd->Resolution.w, _ovrHmd->Resolution.h); -// _outputWindow->setPosition(_ovrHmd->WindowsPos.x, _ovrHmd->WindowsPos.y); - ivec2 desiredPosition = toGlm(_ovrHmd->WindowsPos); - foreach(QScreen* screen, qGuiApp->screens()) { - ivec2 screenPosition = toGlm(screen->geometry().topLeft()); - if (screenPosition == desiredPosition) { - _outputWindow->setScreen(screen); - break; - } - } - _outputWindow->showFullScreen(); - _outputWindow->makeCurrent(); - - ovrGLConfig cfg; - memset(&cfg, 0, sizeof(cfg)); - cfg.OGL.Header.API = ovrRenderAPI_OpenGL; - cfg.OGL.Header.BackBufferSize = _ovrHmd->Resolution; - cfg.OGL.Header.Multisample = 0; - - int distortionCaps = 0 - | ovrDistortionCap_Vignette - | ovrDistortionCap_Overdrive - | ovrDistortionCap_TimeWarp; - - int configResult = ovrHmd_ConfigureRendering(_ovrHmd, &cfg.Config, - distortionCaps, _eyeFov, _eyeRenderDesc); - assert(configResult); - Q_UNUSED(configResult); - - _outputWindow->doneCurrent(); - - for_each_eye([&](ovrEyeType eye) { - //Get texture size - _eyeTextures[eye].Header.API = ovrRenderAPI_OpenGL; - _eyeTextures[eye].Header.TextureSize = _renderTargetSize; - _eyeTextures[eye].Header.RenderViewport.Pos = { 0, 0 }; - _eyeTextures[eye].Header.RenderViewport.Size = _renderTargetSize; - _eyeTextures[eye].Header.RenderViewport.Size.w /= 2; - }); - _eyeTextures[ovrEye_Right].Header.RenderViewport.Pos.x = _recommendedTexSize.w; - for_each_eye([&](ovrEyeType eye) { - _eyeViewports[eye] = _eyeTextures[eye].Header.RenderViewport; - }); -#endif - - ovrHmd_SetEnabledCaps(_ovrHmd, - ovrHmdCap_LowPersistence | ovrHmdCap_DynamicPrediction); - - ovrHmd_ConfigureTracking(_ovrHmd, - ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, - ovrTrackingCap_Orientation); - - if (!_camera) { - _camera = new Camera; - configureCamera(*_camera); // no need to use screen dimensions; they're ignored - } -} - -//Disconnects and deallocates the OR -void OculusManager::disconnect() { - if (_isConnected) { - -#ifdef Q_OS_WIN - if (_swapFbo) { - delete _swapFbo; - _swapFbo = nullptr; - } - - if (_mirrorFbo) { - delete _mirrorFbo; - _mirrorFbo = nullptr; - } -#else - _outputWindow->showNormal(); - _outputWindow->deleteLater(); - _outputWindow = nullptr; -#endif - - if (_ovrHmd) { - ovrHmd_Destroy(_ovrHmd); - _ovrHmd = nullptr; - } - ovr_Shutdown(); - - _isConnected = false; - // Prepare to potentially have to dismiss the HSW again - // if the user re-enables VR - _hswDismissed = false; - } -} - -void positionCalibrationBillboard(Text3DOverlay* billboard) { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - glm::quat headOrientation = myAvatar->getHeadOrientation(); - headOrientation.x = 0; - headOrientation.z = 0; - glm::normalize(headOrientation); - billboard->setPosition(myAvatar->getHeadPosition() - + headOrientation * glm::vec3(0.0f, 0.0f, -CALIBRATION_MESSAGE_DISTANCE)); - billboard->setRotation(headOrientation); -} - -void calibrate(const glm::vec3& position, const glm::quat& orientation) { - static QString instructionMessage = "Hold still to calibrate"; - static QString progressMessage; - static Text3DOverlay* billboard; - - switch (_calibrationState) { - - case UNCALIBRATED: - if (position != glm::vec3() && orientation != glm::quat()) { // Handle zero values at start-up. - _calibrationPosition = position; - _calibrationOrientation = orientation; - _calibrationState = WAITING_FOR_DELTA; - } - break; - - case WAITING_FOR_DELTA: - if (glm::length(position - _calibrationPosition) > CALIBRATION_DELTA_MINIMUM_LENGTH - || glm::angle(orientation * glm::inverse(_calibrationOrientation)) > CALIBRATION_DELTA_MINIMUM_ANGLE) { - _calibrationPosition = position; - _calibrationOrientation = orientation; - _calibrationState = WAITING_FOR_ZERO; - } - break; - - case WAITING_FOR_ZERO: - if (glm::length(position - _calibrationPosition) < CALIBRATION_ZERO_MAXIMUM_LENGTH - && glm::angle(orientation * glm::inverse(_calibrationOrientation)) < CALIBRATION_ZERO_MAXIMUM_ANGLE) { - _calibrationStartTime = usecTimestampNow(); - _calibrationState = WAITING_FOR_ZERO_HELD; - - if (!_calibrationMessage) { - qCDebug(interfaceapp) << "Hold still to calibrate HMD"; - - billboard = new Text3DOverlay(); - billboard->setDimensions(glm::vec2(2.0f, 1.25f)); - billboard->setTopMargin(0.35f); - billboard->setLeftMargin(0.28f); - billboard->setText(instructionMessage); - billboard->setAlpha(0.5f); - billboard->setLineHeight(0.1f); - billboard->setIsFacingAvatar(false); - positionCalibrationBillboard(billboard); - - _calibrationMessage = Application::getInstance()->getOverlays().addOverlay(billboard); - } - - progressMessage = ""; - } else { - _calibrationPosition = position; - _calibrationOrientation = orientation; - } - break; - - case WAITING_FOR_ZERO_HELD: - if (glm::length(position - _calibrationPosition) < CALIBRATION_ZERO_MAXIMUM_LENGTH - && glm::angle(orientation * glm::inverse(_calibrationOrientation)) < CALIBRATION_ZERO_MAXIMUM_ANGLE) { - if ((usecTimestampNow() - _calibrationStartTime) > CALIBRATION_ZERO_HOLD_TIME) { - _calibrationState = CALIBRATED; - qCDebug(interfaceapp) << "HMD calibrated"; - Application::getInstance()->getOverlays().deleteOverlay(_calibrationMessage); - _calibrationMessage = 0; - Application::getInstance()->resetSensors(); - } else { - quint64 quarterSeconds = (usecTimestampNow() - _calibrationStartTime) / 250000; - if (quarterSeconds + 1 > (quint64)progressMessage.length()) { - // 3...2...1... - if (quarterSeconds == 4 * (quarterSeconds / 4)) { - quint64 wholeSeconds = CALIBRATION_ZERO_HOLD_TIME / 1000000 - quarterSeconds / 4; - - if (wholeSeconds == 3) { - positionCalibrationBillboard(billboard); - } - - progressMessage += QString::number(wholeSeconds); - } else { - progressMessage += "."; - } - billboard->setText(instructionMessage + "\n\n" + progressMessage); - } - } - } else { - _calibrationPosition = position; - _calibrationOrientation = orientation; - _calibrationState = WAITING_FOR_ZERO; - } - break; - default: - break; - - } -} - -void OculusManager::recalibrate() { - _calibrationState = UNCALIBRATED; -} - -void OculusManager::abandonCalibration() { - _calibrationState = CALIBRATED; - if (_calibrationMessage) { - qCDebug(interfaceapp) << "Abandoned HMD calibration"; - Application::getInstance()->getOverlays().deleteOverlay(_calibrationMessage); - _calibrationMessage = 0; - } -} - - -bool OculusManager::isConnected() { - return _isConnected; -} - -//Begins the frame timing for oculus prediction purposes -void OculusManager::beginFrameTiming() { - if (_frameTimingActive) { - printf("WARNING: Called OculusManager::beginFrameTiming() twice in a row, need to call OculusManager::endFrameTiming()."); - } - _frameTimingActive = true; -} - -bool OculusManager::allowSwap() { - return false; -} - -//Ends frame timing -void OculusManager::endFrameTiming() { - _frameIndex++; - _frameTimingActive = false; -} - -//Sets the camera FoV and aspect ratio -void OculusManager::configureCamera(Camera& camera) { - if (_activeEye == ovrEye_Count) { - // When not rendering, provide a FOV encompasing both eyes - camera.setProjection(_combinedProjection); - return; - } - camera.setProjection(_eyeProjection[_activeEye]); -} - -//Displays everything for the oculus, frame timing must be active -void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera) { - -#ifdef DEBUG - // Ensure the frame counter always increments by exactly 1 - static int oldFrameIndex = -1; - assert(oldFrameIndex == -1 || (unsigned int)oldFrameIndex == _frameIndex - 1); - oldFrameIndex = _frameIndex; -#endif - -#ifndef Q_OS_WIN - - // FIXME: we need a better way of responding to the HSW. In particular - // we need to ensure that it's only displayed once per session, rather than - // every time the user toggles VR mode, and we need to hook it up to actual - // keyboard input. OVR claim they are refactoring HSW - // https://forums.oculus.com/viewtopic.php?f=20&t=21720#p258599 - static ovrHSWDisplayState hasWarningState; - if (!_hswDismissed) { - ovrHmd_GetHSWDisplayState(_ovrHmd, &hasWarningState); - if (hasWarningState.Displayed) { - ovrHmd_DismissHSWDisplay(_ovrHmd); - } else { - _hswDismissed = true; - } - } -#endif - - - //beginFrameTiming must be called before display - if (!_frameTimingActive) { - printf("WARNING: Called OculusManager::display() without calling OculusManager::beginFrameTiming() first."); - return; - } - - auto primaryFBO = DependencyManager::get()->getPrimaryFramebuffer(); - glBindFramebuffer(GL_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(primaryFBO)); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glm::quat orientation; - glm::vec3 trackerPosition; - auto deviceSize = qApp->getDeviceSize(); - - ovrTrackingState ts = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); - ovrVector3f ovrHeadPosition = ts.HeadPose.ThePose.Position; - - trackerPosition = glm::vec3(ovrHeadPosition.x, ovrHeadPosition.y, ovrHeadPosition.z); - - if (_calibrationState != CALIBRATED) { - ovrQuatf ovrHeadOrientation = ts.HeadPose.ThePose.Orientation; - orientation = glm::quat(ovrHeadOrientation.w, ovrHeadOrientation.x, ovrHeadOrientation.y, ovrHeadOrientation.z); - calibrate(trackerPosition, orientation); - } - - trackerPosition = bodyOrientation * trackerPosition; - ovrPosef eyePoses[ovrEye_Count]; - ovrHmd_GetEyePoses(_ovrHmd, _frameIndex, _eyeOffsets, eyePoses, nullptr); -#ifndef Q_OS_WIN - ovrHmd_BeginFrame(_ovrHmd, _frameIndex); -#endif - //Render each eye into an fbo - for_each_eye(_ovrHmd, [&](ovrEyeType eye){ - // If we're in eye-per-frame mode, only render one eye - // per call to display, and allow timewarp to correct for - // the other eye. Poor man's perf improvement - if (_eyePerFrameMode && eye == _lastEyeRendered) { - return; - } - _lastEyeRendered = _activeEye = eye; - _eyeRenderPoses[eye] = eyePoses[eye]; - // Set the camera rotation for this eye - - _eyePositions[eye] = toGlm(_eyeRenderPoses[eye].Position); - _eyePositions[eye] = whichCamera.getRotation() * _eyePositions[eye]; - quat eyeRotation = toGlm(_eyeRenderPoses[eye].Orientation); - - // Update our camera to what the application camera is doing - _camera->setRotation(whichCamera.getRotation() * eyeRotation); - _camera->setPosition(whichCamera.getPosition() + _eyePositions[eye]); - configureCamera(*_camera); - _camera->update(1.0f / Application::getInstance()->getFps()); - - ovrRecti & vp = _eyeViewports[eye]; - vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; - vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; - glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); - - renderArgs->_viewport = glm::ivec4(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); - renderArgs->_renderSide = RenderArgs::MONO; - qApp->displaySide(renderArgs, *_camera); - qApp->getApplicationCompositor().displayOverlayTextureHmd(renderArgs, eye); - }); - _activeEye = ovrEye_Count; - - gpu::FramebufferPointer finalFbo; - finalFbo = DependencyManager::get()->getPrimaryFramebuffer(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // restore our normal viewport - glViewport(0, 0, deviceSize.width(), deviceSize.height()); - -#ifdef Q_OS_WIN - auto srcFboSize = finalFbo->getSize(); - - - // Blit to the oculus provided texture - glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); - _swapFbo->Bound(oglplus::Framebuffer::Target::Draw, [&] { - glBlitFramebuffer( - 0, 0, srcFboSize.x, srcFboSize.y, - 0, 0, _swapFbo->size.x, _swapFbo->size.y, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - }); - - // Blit to the onscreen window - auto destWindowSize = qApp->getDeviceSize(); - glBlitFramebuffer( - 0, 0, srcFboSize.x, srcFboSize.y, - 0, 0, destWindowSize.width(), destWindowSize.height(), - GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - // Submit the frame to the Oculus SDK for timewarp and distortion - for_each_eye([&](ovrEyeType eye) { - _sceneLayer.RenderPose[eye] = _eyeRenderPoses[eye]; - }); - auto header = &_sceneLayer.Header; - ovrResult res = ovrHmd_SubmitFrame(_ovrHmd, _frameIndex, nullptr, &header, 1); - Q_ASSERT(OVR_SUCCESS(res)); - _swapFbo->Increment(); -#else - GLsync syncObject = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - glFlush(); - - - _outputWindow->makeCurrent(); - // force the compositing context to wait for the texture - // rendering to complete before it starts the distortion rendering, - // but without triggering a CPU/GPU synchronization - glWaitSync(syncObject, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(syncObject); - - GLuint textureId = gpu::GLBackend::getTextureID(finalFbo->getRenderBuffer(0)); - for_each_eye([&](ovrEyeType eye) { - ovrGLTexture & glEyeTexture = reinterpret_cast(_eyeTextures[eye]); - glEyeTexture.OGL.TexId = textureId; - - }); - - // restore our normal viewport - ovrHmd_EndFrame(_ovrHmd, _eyeRenderPoses, _eyeTextures); - glCanvas->makeCurrent(); -#endif - - - // in order to account account for changes in the pick ray caused by head movement - // we need to force a mouse move event on every frame (perhaps we could change this - // to based on the head moving a minimum distance from the last position in which we - // sent?) - { - QMouseEvent mouseEvent(QEvent::MouseMove, glCanvas->mapFromGlobal(QCursor::pos()), - Qt::NoButton, Qt::NoButton, 0); - qApp->mouseMoveEvent(&mouseEvent, 0); - } - - - -} - -//Tries to reconnect to the sensors -void OculusManager::reset() { - if (_isConnected) { - ovrHmd_RecenterPose(_ovrHmd); - } -} - -glm::vec3 OculusManager::getRelativePosition() { - ovrTrackingState trackingState = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); - return toGlm(trackingState.HeadPose.ThePose.Position); -} - -glm::quat OculusManager::getOrientation() { - ovrTrackingState trackingState = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); - return toGlm(trackingState.HeadPose.ThePose.Orientation); -} - -QSize OculusManager::getRenderTargetSize() { - QSize rv; - rv.setWidth(_renderTargetSize.w); - rv.setHeight(_renderTargetSize.h); - return rv; -} - -void OculusManager::overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) { - if (_activeEye != ovrEye_Count) { - const ovrFovPort& port = _eyeFov[_activeEye]; - right = nearVal * port.RightTan; - left = -nearVal * port.LeftTan; - top = nearVal * port.UpTan; - bottom = -nearVal * port.DownTan; - } -} - -int OculusManager::getHMDScreen() { -#ifdef Q_OS_WIN - return -1; -#else - int hmdScreenIndex = -1; // unknown - // TODO: it might be smarter to handle multiple HMDs connected in this case. but for now, - // we will simply assume the initialization code that set up _ovrHmd picked the best hmd - - if (_ovrHmd) { - QString productNameFromOVR = _ovrHmd->ProductName; - - int hmdWidth = _ovrHmd->Resolution.w; - int hmdHeight = _ovrHmd->Resolution.h; - int hmdAtX = _ovrHmd->WindowsPos.x; - int hmdAtY = _ovrHmd->WindowsPos.y; - - // we will score the likelihood that each screen is a match based on the following - // rubrik of potential matching features - const int EXACT_NAME_MATCH = 100; - const int SIMILAR_NAMES = 10; - const int EXACT_LOCATION_MATCH = 50; - const int EXACT_RESOLUTION_MATCH = 25; - - int bestMatchScore = 0; - - // look at the display list and see if we can find the best match - QDesktopWidget* desktop = QApplication::desktop(); - int screenNumber = 0; - foreach (QScreen* screen, QGuiApplication::screens()) { - QString screenName = screen->name(); - QRect screenRect = desktop->screenGeometry(screenNumber); - - int screenScore = 0; - if (screenName == productNameFromOVR) { - screenScore += EXACT_NAME_MATCH; - } - if (similarStrings(screenName, productNameFromOVR)) { - screenScore += SIMILAR_NAMES; - } - if (hmdWidth == screenRect.width() && hmdHeight == screenRect.height()) { - screenScore += EXACT_RESOLUTION_MATCH; - } - if (hmdAtX == screenRect.x() && hmdAtY == screenRect.y()) { - screenScore += EXACT_LOCATION_MATCH; - } - if (screenScore > bestMatchScore) { - bestMatchScore = screenScore; - hmdScreenIndex = screenNumber; - } - - screenNumber++; - } - } - return hmdScreenIndex; -#endif -} - -mat4 OculusManager::getEyeProjection(int eye) { - return _eyeProjection[eye]; -} - -mat4 OculusManager::getEyePose(int eye) { - return toGlm(_eyeRenderPoses[eye]); -} - -mat4 OculusManager::getHeadPose() { - ovrTrackingState ts = ovrHmd_GetTrackingState(_ovrHmd, ovr_GetTimeInSeconds()); - return toGlm(ts.HeadPose.ThePose); -} diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h deleted file mode 100644 index 87f022a294..0000000000 --- a/interface/src/devices/OculusManager.h +++ /dev/null @@ -1,61 +0,0 @@ -// -// OculusManager.h -// interface/src/devices -// -// Created by Stephen Birarda on 5/9/13. -// Refactored by Ben Arnold on 6/30/2014 -// Copyright 2012 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_OculusManager_h -#define hifi_OculusManager_h - -#include -#include -#include - -#include - -#include "RenderArgs.h" - -class QOpenGLContext; -class QGLWidget; -class Camera; - -/// Handles interaction with the Oculus Rift. -class OculusManager { -public: - static void connect(QOpenGLContext* shareContext); - static void disconnect(); - static bool isConnected(); - static void recalibrate(); - static void abandonCalibration(); - static void beginFrameTiming(); - static void endFrameTiming(); - static bool allowSwap(); - static void configureCamera(Camera& camera); - static void display(QGLWidget * glCanvas, RenderArgs* renderArgs, const glm::quat &bodyOrientation, const glm::vec3 &position, Camera& whichCamera); - static void reset(); - - static glm::vec3 getRelativePosition(); - static glm::quat getOrientation(); - static QSize getRenderTargetSize(); - - static void overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane); - - static glm::vec3 getLeftEyePosition(); - static glm::vec3 getRightEyePosition(); - static glm::vec3 getMidEyePosition(); - - static int getHMDScreen(); - - static glm::mat4 getEyeProjection(int eye); - static glm::mat4 getEyePose(int eye); - static glm::mat4 getHeadPose(); -}; - -#endif // hifi_OculusManager_h diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp deleted file mode 100644 index 5dee5988c1..0000000000 --- a/interface/src/devices/TV3DManager.cpp +++ /dev/null @@ -1,151 +0,0 @@ -// -// TV3DManager.cpp -// interface/src/devices -// -// Created by Brad Hefta-Gaub on 12/24/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "TV3DManager.h" - -#include -#include - -#include - -#include "Application.h" -#include "Menu.h" - -int TV3DManager::_screenWidth = 1; -int TV3DManager::_screenHeight = 1; -double TV3DManager::_aspect = 1.0; -eyeFrustum TV3DManager::_leftEye; -eyeFrustum TV3DManager::_rightEye; -eyeFrustum* TV3DManager::_activeEye = NULL; - - -bool TV3DManager::isConnected() { - return Menu::getInstance()->isOptionChecked(MenuOption::Enable3DTVMode); -} - -void TV3DManager::connect() { - auto deviceSize = qApp->getDeviceSize(); - configureCamera(*(qApp->getCamera()), deviceSize.width(), deviceSize.height()); -} - - -// The basic strategy of this stereoscopic rendering is explained here: -// http://www.orthostereo.com/geometryopengl.html -void TV3DManager::setFrustum(Camera& whichCamera) { - const double DTR = 0.0174532925; // degree to radians - const double IOD = 0.05; //intraocular distance - double fovy = DEFAULT_FIELD_OF_VIEW_DEGREES; // field of view in y-axis - double nearZ = DEFAULT_NEAR_CLIP; // near clipping plane - double screenZ = 0.25f; // screen projection plane - - double top = nearZ * tan(DTR * fovy / 2.0); //sets top of frustum based on fovy and near clipping plane - double right = _aspect * top; // sets right of frustum based on aspect ratio - double frustumshift = (IOD / 2) * nearZ / screenZ; - - _leftEye.top = top; - _leftEye.bottom = -top; - _leftEye.left = -right + frustumshift; - _leftEye.right = right + frustumshift; - _leftEye.modelTranslation = IOD / 2; - - _rightEye.top = top; - _rightEye.bottom = -top; - _rightEye.left = -right - frustumshift; - _rightEye.right = right - frustumshift; - _rightEye.modelTranslation = -IOD / 2; -} - -void TV3DManager::configureCamera(Camera& whichCamera, int screenWidth, int screenHeight) { -#ifdef THIS_CURRENTLY_BROKEN_WAITING_FOR_DISPLAY_PLUGINS - if (screenHeight == 0) { - screenHeight = 1; // prevent divide by 0 - } - _screenWidth = screenWidth; - _screenHeight = screenHeight; - _aspect= (double)_screenWidth / (double)_screenHeight; - setFrustum(whichCamera); - - glViewport (0, 0, _screenWidth, _screenHeight); // sets drawing viewport -#endif -} - -void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) { - -#ifdef THIS_CURRENTLY_BROKEN_WAITING_FOR_DISPLAY_PLUGINS - - double nearZ = DEFAULT_NEAR_CLIP; // near clipping plane - double farZ = DEFAULT_FAR_CLIP; // far clipping plane - - // left eye portal - int portalX = 0; - int portalY = 0; - QSize deviceSize = qApp->getDeviceSize() * - qApp->getRenderResolutionScale(); - int portalW = deviceSize.width() / 2; - int portalH = deviceSize.height(); - - - // FIXME - glow effect is removed, 3D TV mode broken until we get display plugins working - DependencyManager::get()->prepare(renderArgs); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - Camera eyeCamera; - eyeCamera.setRotation(whichCamera.getRotation()); - eyeCamera.setPosition(whichCamera.getPosition()); - - glEnable(GL_SCISSOR_TEST); - forEachEye([&](eyeFrustum& eye){ - _activeEye = &eye; - glViewport(portalX, portalY, portalW, portalH); - glScissor(portalX, portalY, portalW, portalH); - renderArgs->_viewport = glm::ivec4(portalX, portalY, portalW, portalH); - - glm::mat4 projection = glm::frustum(eye.left, eye.right, eye.bottom, eye.top, nearZ, farZ); - projection = glm::translate(projection, vec3(eye.modelTranslation, 0, 0)); - eyeCamera.setProjection(projection); - renderArgs->_renderSide = RenderArgs::MONO; - qApp->displaySide(renderArgs, eyeCamera, false); - qApp->getApplicationCompositor().displayOverlayTexture(renderArgs); - _activeEye = NULL; - }, [&]{ - // render right side view - portalX = deviceSize.width() / 2; - }); - glDisable(GL_SCISSOR_TEST); - - // FIXME - glow effect is removed, 3D TV mode broken until we get display plugins working - auto finalFbo = DependencyManager::get()->render(renderArgs); - auto fboSize = finalFbo->getSize(); - // Get the ACTUAL device size for the BLIT - deviceSize = qApp->getDeviceSize(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBlitFramebuffer(0, 0, fboSize.x, fboSize.y, - 0, 0, deviceSize.width(), deviceSize.height(), - GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - // reset the viewport to how we started - glViewport(0, 0, deviceSize.width(), deviceSize.height()); - -#endif -} - -void TV3DManager::overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) { - if (_activeEye) { - left = _activeEye->left; - right = _activeEye->right; - bottom = _activeEye->bottom; - top = _activeEye->top; - } -} diff --git a/interface/src/devices/TV3DManager.h b/interface/src/devices/TV3DManager.h deleted file mode 100644 index 96ee79f7d1..0000000000 --- a/interface/src/devices/TV3DManager.h +++ /dev/null @@ -1,64 +0,0 @@ -// -// TV3DManager.h -// interface/src/devices -// -// Created by Brad Hefta-Gaub on 12/24/2013. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_TV3DManager_h -#define hifi_TV3DManager_h - -#include - -#include - -class Camera; -class RenderArgs; - -struct eyeFrustum { - double left; - double right; - double bottom; - double top; - float modelTranslation; -}; - - -/// Handles interaction with 3D TVs -class TV3DManager { -public: - static void connect(); - static bool isConnected(); - static void configureCamera(Camera& camera, int screenWidth, int screenHeight); - static void display(RenderArgs* renderArgs, Camera& whichCamera); - static void overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane); -private: - static void setFrustum(Camera& whichCamera); - static int _screenWidth; - static int _screenHeight; - static double _aspect; - static eyeFrustum _leftEye; - static eyeFrustum _rightEye; - static eyeFrustum* _activeEye; - - // The first function is the code executed for each eye - // while the second is code to be executed between the two eyes. - // The use case here is to modify the output viewport coordinates - // for the new eye. - // FIXME: we'd like to have a default empty lambda for the second parameter, - // but gcc 4.8.1 complains about it due to a bug. See - // http://stackoverflow.com/questions/25490662/lambda-as-default-parameter-to-a-member-function-template - template - static void forEachEye(F f, FF ff) { - f(_leftEye); - ff(); - f(_rightEye); - } -}; - -#endif // hifi_TV3DManager_h diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index feceecc3fd..5301429fd9 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -11,15 +11,16 @@ #include #include -#include #include #include #include "Application.h" #include "devices/MotionTracker.h" -#include "devices/SixenseManager.h" #include "ControllerScriptingInterface.h" +// TODO: this needs to be removed, as well as any related controller-specific information +#include + ControllerScriptingInterface::ControllerScriptingInterface() : _mouseCaptured(false), @@ -82,13 +83,14 @@ void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::In QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action) { QScriptValue obj = engine->newObject(); - QVector inputChannels = Application::getUserInputMapper()->getInputChannelsForAction(action); + auto userInputMapper = DependencyManager::get(); + QVector inputChannels = userInputMapper->getInputChannelsForAction(action); QScriptValue _inputChannels = engine->newArray(inputChannels.size()); for (int i = 0; i < inputChannels.size(); i++) { _inputChannels.setProperty(i, inputChannelToScriptValue(engine, inputChannels[i])); } obj.setProperty("action", (int) action); - obj.setProperty("actionName", Application::getUserInputMapper()->getActionName(action)); + obj.setProperty("actionName", userInputMapper->getActionName(action)); obj.setProperty("inputChannels", _inputChannels); return obj; } @@ -376,7 +378,7 @@ void ControllerScriptingInterface::releaseJoystick(int joystickIndex) { } glm::vec2 ControllerScriptingInterface::getViewportDimensions() const { - return Application::getInstance()->getCanvasSize(); + return Application::getInstance()->getUiSize(); } AbstractInputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) { @@ -428,43 +430,59 @@ void ControllerScriptingInterface::updateInputControllers() { } QVector ControllerScriptingInterface::getAllActions() { - return Application::getUserInputMapper()->getAllActions(); + return DependencyManager::get()->getAllActions(); } QVector ControllerScriptingInterface::getInputChannelsForAction(UserInputMapper::Action action) { - return Application::getUserInputMapper()->getInputChannelsForAction(action); + return DependencyManager::get()->getInputChannelsForAction(action); } QString ControllerScriptingInterface::getDeviceName(unsigned int device) { - return Application::getUserInputMapper()->getDeviceName((unsigned short) device); + return DependencyManager::get()->getDeviceName((unsigned short)device); } QVector ControllerScriptingInterface::getAllInputsForDevice(unsigned int device) { - return Application::getUserInputMapper()->getAllInputsForDevice(device); + return DependencyManager::get()->getAllInputsForDevice(device); } bool ControllerScriptingInterface::addInputChannel(UserInputMapper::InputChannel inputChannel) { - return Application::getUserInputMapper()->addInputChannel(inputChannel._action, inputChannel._input, inputChannel._modifier, inputChannel._scale); + return DependencyManager::get()->addInputChannel(inputChannel._action, inputChannel._input, inputChannel._modifier, inputChannel._scale); } bool ControllerScriptingInterface::removeInputChannel(UserInputMapper::InputChannel inputChannel) { - return Application::getUserInputMapper()->removeInputChannel(inputChannel); + return DependencyManager::get()->removeInputChannel(inputChannel); } QVector ControllerScriptingInterface::getAvailableInputs(unsigned int device) { - return Application::getUserInputMapper()->getAvailableInputs((unsigned short) device); + return DependencyManager::get()->getAvailableInputs((unsigned short)device); } void ControllerScriptingInterface::resetAllDeviceBindings() { - Application::getUserInputMapper()->resetAllDeviceBindings(); + DependencyManager::get()->resetAllDeviceBindings(); } void ControllerScriptingInterface::resetDevice(unsigned int device) { - Application::getUserInputMapper()->resetDevice(device); + DependencyManager::get()->resetDevice(device); } int ControllerScriptingInterface::findDevice(QString name) { - return Application::getUserInputMapper()->findDevice(name); + return DependencyManager::get()->findDevice(name); +} + +float ControllerScriptingInterface::getActionValue(int action) { + return DependencyManager::get()->getActionState(UserInputMapper::Action(action)); +} + +int ControllerScriptingInterface::findAction(QString actionName) { + auto userInputMapper = DependencyManager::get(); + auto actions = getAllActions(); + for (auto action : actions) { + if (userInputMapper->getActionName(action) == actionName) { + return action; + } + } + // If the action isn't found, return -1 + return -1; } InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) : @@ -502,4 +520,4 @@ const unsigned int INPUTCONTROLLER_KEY_DEVICE_MASK = 16; InputController::Key InputController::getKey() const { return (((_deviceTrackerId & INPUTCONTROLLER_KEY_DEVICE_MASK) << INPUTCONTROLLER_KEY_DEVICE_OFFSET) | _subTrackerId); -} \ No newline at end of file +} diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index f15ee43ca3..e63fa42a62 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -14,7 +14,7 @@ #include -#include "ui/UserInputMapper.h" +#include #include class PalmData; @@ -86,15 +86,24 @@ public: public slots: Q_INVOKABLE virtual QVector getAllActions(); - Q_INVOKABLE virtual QVector getInputChannelsForAction(UserInputMapper::Action action); - Q_INVOKABLE virtual QString getDeviceName(unsigned int device); - Q_INVOKABLE virtual QVector getAllInputsForDevice(unsigned int device); + Q_INVOKABLE virtual bool addInputChannel(UserInputMapper::InputChannel inputChannel); Q_INVOKABLE virtual bool removeInputChannel(UserInputMapper::InputChannel inputChannel); + Q_INVOKABLE virtual QVector getInputChannelsForAction(UserInputMapper::Action action); + Q_INVOKABLE virtual QVector getAvailableInputs(unsigned int device); - Q_INVOKABLE virtual void resetAllDeviceBindings(); + Q_INVOKABLE virtual QVector getAllInputsForDevice(unsigned int device); + + Q_INVOKABLE virtual QString getDeviceName(unsigned int device); + + Q_INVOKABLE virtual float getActionValue(int action); + Q_INVOKABLE virtual void resetDevice(unsigned int device); + Q_INVOKABLE virtual void resetAllDeviceBindings(); Q_INVOKABLE virtual int findDevice(QString name); + + Q_INVOKABLE virtual int findAction(QString actionName); + virtual bool isPrimaryButtonPressed() const; virtual glm::vec2 getPrimaryJoystickPosition() const; diff --git a/interface/src/scripting/WebWindowClass.cpp b/interface/src/scripting/WebWindowClass.cpp index f187de95d2..5c3cb6c42f 100644 --- a/interface/src/scripting/WebWindowClass.cpp +++ b/interface/src/scripting/WebWindowClass.cpp @@ -122,6 +122,14 @@ void WebWindowClass::setURL(const QString& url) { _webView->setUrl(url); } +void WebWindowClass::setPosition(int x, int y) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setPosition", Qt::AutoConnection, Q_ARG(int, x), Q_ARG(int, y)); + return; + } + _windowWidget->move(x, y); +} + void WebWindowClass::raise() { QMetaObject::invokeMethod(_windowWidget, "showNormal", Qt::AutoConnection); QMetaObject::invokeMethod(_windowWidget, "raise", Qt::AutoConnection); diff --git a/interface/src/scripting/WebWindowClass.h b/interface/src/scripting/WebWindowClass.h index c44898fa97..43dbef0392 100644 --- a/interface/src/scripting/WebWindowClass.h +++ b/interface/src/scripting/WebWindowClass.h @@ -43,6 +43,7 @@ public: public slots: void setVisible(bool visible); + void setPosition(int x, int y); QString getURL() const { return _webView->url().url(); } void setURL(const QString& url); void raise(); diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 9bda88b3bf..abc1e49101 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -11,6 +11,10 @@ #include "ApplicationCompositor.h" +#include + +#include + #include #include @@ -21,6 +25,8 @@ #include "Tooltip.h" #include "Application.h" +#include // TODO: any references to sixense should be removed here +#include // Used to animate the magnification windows @@ -106,7 +112,9 @@ bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, } } -ApplicationCompositor::ApplicationCompositor() { +ApplicationCompositor::ApplicationCompositor() : + _alphaPropertyAnimation(new QPropertyAnimation(this, "alpha")) +{ memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); memset(_magSizeMult, 0, sizeof(_magSizeMult)); @@ -163,6 +171,8 @@ ApplicationCompositor::ApplicationCompositor() { } } }); + + _alphaPropertyAnimation.reset(new QPropertyAnimation(this, "alpha")); } ApplicationCompositor::~ApplicationCompositor() { @@ -184,7 +194,8 @@ void ApplicationCompositor::bindCursorTexture(gpu::Batch& batch, uint8_t cursorI // Draws the FBO texture for the screen void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { PROFILE_RANGE(__FUNCTION__); - if (_alpha == 0.0f) { + + if (_alpha <= 0.0f) { return; } @@ -204,7 +215,7 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { auto geometryCache = DependencyManager::get(); geometryCache->useSimpleDrawPipeline(batch); - batch.setViewportTransform(glm::ivec4(0, 0, deviceSize.width(), deviceSize.height())); + batch.setViewportTransform(renderArgs->_viewport); batch.setModelTransform(Transform()); batch.setViewTransform(Transform()); batch.setProjectionTransform(mat4()); @@ -232,15 +243,17 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) { } -vec2 getPolarCoordinates(const PalmData& palm) { +vec2 ApplicationCompositor::getPolarCoordinates(const PalmData& palm) const { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarOrientation = myAvatar->getOrientation(); - auto eyePos = myAvatar->getDefaultEyePosition(); glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); - // Direction of the tip relative to the eye - glm::vec3 tipDirection = tip - eyePos; - // orient into avatar space - tipDirection = glm::inverse(avatarOrientation) * tipDirection; + glm::vec3 relativePos = myAvatar->getDefaultEyePosition(); + glm::quat rotation = myAvatar->getOrientation(); + if (Menu::getInstance()->isOptionChecked(MenuOption::StandingHMDSensorMode)) { + relativePos = _modelTransform.getTranslation(); + rotation = _modelTransform.getRotation(); + } + glm::vec3 tipDirection = tip - relativePos; + tipDirection = glm::inverse(rotation) * tipDirection; // Normalize for trig functions tipDirection = glm::normalize(tipDirection); // Convert to polar coordinates @@ -251,7 +264,8 @@ vec2 getPolarCoordinates(const PalmData& palm) { // Draws the FBO texture for Oculus rift. void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int eye) { PROFILE_RANGE(__FUNCTION__); - if (_alpha == 0.0f) { + + if (_alpha <= 0.0f) { return; } @@ -278,11 +292,13 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int batch.setResourceTexture(0, overlayFramebuffer->getRenderBuffer(0)); - batch.setViewTransform(Transform()); - batch.setProjectionTransform(qApp->getEyeProjection(eye)); + mat4 camMat; + _cameraBaseTransform.getMatrix(camMat); + camMat = camMat * qApp->getEyePose(eye); + batch.setViewportTransform(renderArgs->_viewport); + batch.setViewTransform(camMat); - mat4 eyePose = qApp->getEyePose(eye); - glm::mat4 overlayXfm = glm::inverse(eyePose); + batch.setProjectionTransform(qApp->getEyeProjection(eye)); #ifdef DEBUG_OVERLAY { @@ -291,7 +307,9 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int } #else { - batch.setModelTransform(overlayXfm); + //batch.setModelTransform(overlayXfm); + + batch.setModelTransform(_modelTransform); drawSphereSection(batch); } #endif @@ -302,8 +320,11 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int bindCursorTexture(batch); - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); //Controller Pointers + glm::mat4 overlayXfm; + _modelTransform.getMatrix(overlayXfm); + + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { @@ -345,13 +366,18 @@ void ApplicationCompositor::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& or // We need the RAW camera orientation and position, because this is what the overlay is // rendered relative to - const glm::vec3 overlayPosition = qApp->getCamera()->getPosition(); - const glm::quat overlayOrientation = qApp->getCamera()->getRotation(); + glm::vec3 overlayPosition = qApp->getCamera()->getPosition(); + glm::quat overlayOrientation = qApp->getCamera()->getRotation(); + + if (Menu::getInstance()->isOptionChecked(MenuOption::StandingHMDSensorMode)) { + overlayPosition = _modelTransform.getTranslation(); + overlayOrientation = _modelTransform.getRotation(); + } // Intersection UI overlay space glm::vec3 worldSpaceDirection = overlayOrientation * overlaySpaceDirection; glm::vec3 worldSpaceIntersection = (glm::normalize(worldSpaceDirection) * _oculusUIRadius) + overlayPosition; - glm::vec3 worldSpaceHeadPosition = (overlayOrientation * glm::vec3(qApp->getHeadPose()[3])) + overlayPosition; + glm::vec3 worldSpaceHeadPosition = (overlayOrientation * extractTranslation(qApp->getHMDSensorPose())) + overlayPosition; // Intersection in world space origin = worldSpaceHeadPosition; @@ -410,13 +436,15 @@ bool ApplicationCompositor::calculateRayUICollisionPoint(const glm::vec3& positi void ApplicationCompositor::renderPointers(gpu::Batch& batch) { if (qApp->isHMDMode() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) { //If we are in oculus, render reticle later + auto trueMouse = qApp->getTrueMouse(); + trueMouse /= qApp->getCanvasSize(); QPoint position = QPoint(qApp->getTrueMouseX(), qApp->getTrueMouseY()); _reticlePosition[MOUSE] = position; _reticleActive[MOUSE] = true; _magActive[MOUSE] = _magnifier; _reticleActive[LEFT_CONTROLLER] = false; _reticleActive[RIGHT_CONTROLLER] = false; - } else if (qApp->getLastMouseMoveWasSimulated() && Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput)) { + } else if (qApp->getLastMouseMoveWasSimulated() && Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) { //only render controller pointer if we aren't already rendering a mouse pointer _reticleActive[MOUSE] = false; _magActive[MOUSE] = false; @@ -491,6 +519,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { auto canvasSize = qApp->getCanvasSize(); int mouseX, mouseY; + // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(myAvatar->getOrientation()) * palmData->getFingerDirection(); @@ -499,7 +528,7 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)PI_OVER_TWO)); // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = canvasSize.x * SixenseManager::getInstance().getCursorPixelRangeMult(); + float cursorRange = canvasSize.x * InputDevice::getCursorPixelRangeMult(); mouseX = (canvasSize.x / 2.0f + cursorRange * xAngle); mouseY = (canvasSize.y / 2.0f + cursorRange * yAngle); @@ -611,6 +640,19 @@ void ApplicationCompositor::drawSphereSection(gpu::Batch& batch) { batch.setInputFormat(streamFormat); static const int VERTEX_STRIDE = sizeof(vec3) + sizeof(vec2) + sizeof(vec4); + + if (_prevAlpha != _alpha) { + // adjust alpha by munging vertex color alpha. + // FIXME we should probably just use a uniform for this. + float* floatPtr = reinterpret_cast(_hemiVertices->editData()); + const auto ALPHA_FLOAT_OFFSET = (sizeof(vec3) + sizeof(vec2) + sizeof(vec3)) / sizeof(float); + const auto VERTEX_FLOAT_STRIDE = (sizeof(vec3) + sizeof(vec2) + sizeof(vec4)) / sizeof(float); + const auto NUM_VERTS = _hemiVertices->getSize() / VERTEX_STRIDE; + for (size_t i = 0; i < NUM_VERTS; i++) { + floatPtr[i * VERTEX_FLOAT_STRIDE + ALPHA_FLOAT_OFFSET] = _alpha; + } + } + gpu::BufferView posView(_hemiVertices, 0, _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::POSITION)._element); gpu::BufferView uvView(_hemiVertices, sizeof(vec3), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::TEXCOORD)._element); gpu::BufferView colView(_hemiVertices, sizeof(vec3) + sizeof(vec2), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::COLOR)._element); @@ -700,3 +742,29 @@ void ApplicationCompositor::updateTooltips() { } } } + +static const float FADE_DURATION = 500.0f; +void ApplicationCompositor::fadeIn() { + _fadeInAlpha = true; + + _alphaPropertyAnimation->setDuration(FADE_DURATION); + _alphaPropertyAnimation->setStartValue(_alpha); + _alphaPropertyAnimation->setEndValue(1.0f); + _alphaPropertyAnimation->start(); +} +void ApplicationCompositor::fadeOut() { + _fadeInAlpha = false; + + _alphaPropertyAnimation->setDuration(FADE_DURATION); + _alphaPropertyAnimation->setStartValue(_alpha); + _alphaPropertyAnimation->setEndValue(0.0f); + _alphaPropertyAnimation->start(); +} + +void ApplicationCompositor::toggle() { + if (_fadeInAlpha) { + fadeOut(); + } else { + fadeIn(); + } +} diff --git a/interface/src/ui/ApplicationCompositor.h b/interface/src/ui/ApplicationCompositor.h index 8ae6f0930e..704d53dcfa 100644 --- a/interface/src/ui/ApplicationCompositor.h +++ b/interface/src/ui/ApplicationCompositor.h @@ -10,6 +10,7 @@ #define hifi_ApplicationCompositor_h #include +#include #include #include @@ -33,6 +34,8 @@ const float DEFAULT_HMD_UI_ANGULAR_SIZE = 72.0f; // facilities of this class class ApplicationCompositor : public QObject { Q_OBJECT + + Q_PROPERTY(float alpha READ getAlpha WRITE setAlpha) public: ApplicationCompositor(); ~ApplicationCompositor(); @@ -64,6 +67,19 @@ public: void computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const; uint32_t getOverlayTexture() const; + void setCameraBaseTransform(const Transform& transform) { _cameraBaseTransform = transform; } + const Transform& getCameraBaseTransform() const { return _cameraBaseTransform; } + + void setModelTransform(const Transform& transform) { _modelTransform = transform; } + const Transform& getModelTransform() const { return _modelTransform; } + + void fadeIn(); + void fadeOut(); + void toggle(); + + float getAlpha() const { return _alpha; } + void setAlpha(float alpha) { _alpha = alpha; } + static glm::vec2 directionToSpherical(const glm::vec3 & direction); static glm::vec3 sphericalToDirection(const glm::vec2 & sphericalPos); static glm::vec2 screenToSpherical(const glm::vec2 & screenPos); @@ -78,7 +94,8 @@ private: void renderPointers(gpu::Batch& batch); void renderControllerPointers(gpu::Batch& batch); - void renderPointersOculus(gpu::Batch& batch); + + vec2 getPolarCoordinates(const PalmData& palm) const; // Support for hovering and tooltips static EntityItemID _noItemId; @@ -100,6 +117,8 @@ private: bool _magnifier{ true }; float _alpha{ 1.0f }; + float _prevAlpha{ 1.0f }; + float _fadeInAlpha{ true }; float _oculusUIRadius{ 1.0f }; QMap _cursors; @@ -115,6 +134,11 @@ private: glm::vec3 _previousMagnifierBottomRight; glm::vec3 _previousMagnifierTopLeft; glm::vec3 _previousMagnifierTopRight; + + Transform _modelTransform; + Transform _cameraBaseTransform; + + std::unique_ptr _alphaPropertyAnimation; }; #endif // hifi_ApplicationCompositor_h diff --git a/interface/src/ui/AvatarAppearanceDialog.cpp b/interface/src/ui/AvatarAppearanceDialog.cpp index 3ab99c141d..54e48dca26 100644 --- a/interface/src/ui/AvatarAppearanceDialog.cpp +++ b/interface/src/ui/AvatarAppearanceDialog.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include "Application.h" diff --git a/interface/src/ui/HMDToolsDialog.cpp b/interface/src/ui/HMDToolsDialog.cpp index 9afd0f51c7..cc596e5e55 100644 --- a/interface/src/ui/HMDToolsDialog.cpp +++ b/interface/src/ui/HMDToolsDialog.cpp @@ -19,48 +19,59 @@ #include #include +#include +#include + #include "MainWindow.h" #include "Menu.h" #include "ui/DialogsManager.h" #include "ui/HMDToolsDialog.h" -#include "devices/OculusManager.h" + +static const int WIDTH = 350; +static const int HEIGHT = 100; HMDToolsDialog::HMDToolsDialog(QWidget* parent) : - QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint) , - _previousScreen(NULL), - _hmdScreen(NULL), - _hmdScreenNumber(-1), - _switchModeButton(NULL), - _debugDetails(NULL), - _previousDialogScreen(NULL), - _inHDMMode(false) + QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint) { - this->setWindowTitle("HMD Tools"); + // FIXME do we want to support more than one connected HMD? It seems like a pretty corner case + foreach(auto displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) { + // The first plugin is always the standard 2D display, by convention + if (_defaultPluginName.isEmpty()) { + _defaultPluginName = displayPlugin->getName(); + continue; + } + + if (displayPlugin->isHmd()) { + // Not all HMD's have corresponding screens + if (displayPlugin->getHmdScreen() >= 0) { + _hmdScreenNumber = displayPlugin->getHmdScreen(); + } + _hmdPluginName = displayPlugin->getName(); + break; + } + } + + setWindowTitle("HMD Tools"); // Create layouter - QFormLayout* form = new QFormLayout(); - const int WIDTH = 350; + { + QFormLayout* form = new QFormLayout(); + // Add a button to enter + _switchModeButton = new QPushButton("Toggle HMD Mode"); + if (_hmdPluginName.isEmpty()) { + _switchModeButton->setEnabled(false); + } + // Add a button to enter + _switchModeButton->setFixedWidth(WIDTH); + form->addRow("", _switchModeButton); + // Create a label with debug details... + _debugDetails = new QLabel(); + _debugDetails->setFixedSize(WIDTH, HEIGHT); + form->addRow("", _debugDetails); + setLayout(form); + } - // Add a button to enter - _switchModeButton = new QPushButton("Enter HMD Mode"); - _switchModeButton->setFixedWidth(WIDTH); - form->addRow("", _switchModeButton); - connect(_switchModeButton,SIGNAL(clicked(bool)),this,SLOT(switchModeClicked(bool))); - - // Create a label with debug details... - _debugDetails = new QLabel(); - _debugDetails->setText(getDebugDetails()); - const int HEIGHT = 100; - _debugDetails->setFixedSize(WIDTH, HEIGHT); - form->addRow("", _debugDetails); - - this->QDialog::setLayout(form); - - Application::getInstance()->getWindow()->activateWindow(); - - // watch for our application window moving screens. If it does we want to update our screen details - QWindow* mainWindow = Application::getInstance()->getWindow()->windowHandle(); - connect(mainWindow, &QWindow::screenChanged, this, &HMDToolsDialog::applicationWindowScreenChanged); + qApp->getWindow()->activateWindow(); // watch for our dialog window moving screens. If it does we want to enforce our rules about // what screens we're allowed on @@ -82,11 +93,31 @@ HMDToolsDialog::HMDToolsDialog(QWidget* parent) : watchWindow(dialogsManager->getLodToolsDialog()->windowHandle()); } + connect(_switchModeButton, &QPushButton::clicked, [this]{ + toggleHMDMode(); + }); + // when the application is about to quit, leave HDM mode - connect(Application::getInstance(), SIGNAL(beforeAboutToQuit()), this, SLOT(aboutToQuit())); + connect(qApp, &Application::beforeAboutToQuit, [this]{ + // FIXME this is ineffective because it doesn't trigger the menu to + // save the fact that VR Mode is not checked. + leaveHMDMode(); + }); + + connect(qApp, &Application::activeDisplayPluginChanged, [this]{ + updateUi(); + }); + + // watch for our application window moving screens. If it does we want to update our screen details + QWindow* mainWindow = Application::getInstance()->getWindow()->windowHandle(); + connect(mainWindow, &QWindow::screenChanged, [this]{ + updateUi(); + }); // keep track of changes to the number of screens connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, this, &HMDToolsDialog::screenCountChanged); + + updateUi(); } HMDToolsDialog::~HMDToolsDialog() { @@ -96,18 +127,13 @@ HMDToolsDialog::~HMDToolsDialog() { _windowWatchers.clear(); } -void HMDToolsDialog::applicationWindowScreenChanged(QScreen* screen) { - _debugDetails->setText(getDebugDetails()); -} - QString HMDToolsDialog::getDebugDetails() const { QString results; - int hmdScreenNumber = OculusManager::getHMDScreen(); - if (hmdScreenNumber >= 0) { - results += "HMD Screen: " + QGuiApplication::screens()[hmdScreenNumber]->name() + "\n"; + if (_hmdScreenNumber >= 0) { + results += "HMD Screen: " + QGuiApplication::screens()[_hmdScreenNumber]->name() + "\n"; } else { - results += "HMD Screen Name: Unknown\n"; + results += "HMD Screen Name: N/A\n"; } int desktopPrimaryScreenNumber = QApplication::desktop()->primaryScreen(); @@ -122,37 +148,25 @@ QString HMDToolsDialog::getDebugDetails() const { return results; } -void HMDToolsDialog::switchModeClicked(bool checked) { - if (!_inHDMMode) { - enterHDMMode(); +void HMDToolsDialog::toggleHMDMode() { + if (!qApp->isHMDMode()) { + enterHMDMode(); } else { - leaveHDMMode(); + leaveHMDMode(); } } -void HMDToolsDialog::enterHDMMode() { - if (!_inHDMMode) { - _switchModeButton->setText("Leave HMD Mode"); - _debugDetails->setText(getDebugDetails()); - - // if we're on a single screen setup, then hide our tools window when entering HMD mode - if (QApplication::desktop()->screenCount() == 1) { - close(); - } - - Application::getInstance()->setEnableVRMode(true); - - _inHDMMode = true; - } -} - -void HMDToolsDialog::leaveHDMMode() { - if (_inHDMMode) { - _switchModeButton->setText("Enter HMD Mode"); - _debugDetails->setText(getDebugDetails()); - Application::getInstance()->setEnableVRMode(false); +void HMDToolsDialog::enterHMDMode() { + if (!qApp->isHMDMode()) { + Application::getInstance()->setActiveDisplayPlugin(_hmdPluginName); + Application::getInstance()->getWindow()->activateWindow(); + } +} + +void HMDToolsDialog::leaveHMDMode() { + if (qApp->isHMDMode()) { + Application::getInstance()->setActiveDisplayPlugin(_defaultPluginName); Application::getInstance()->getWindow()->activateWindow(); - _inHDMMode = false; } } @@ -163,7 +177,7 @@ void HMDToolsDialog::reject() { void HMDToolsDialog::closeEvent(QCloseEvent* event) { // TODO: consider if we want to prevent closing of this window with event->ignore(); - this->QDialog::closeEvent(event); + QDialog::closeEvent(event); emit closed(); } @@ -174,9 +188,15 @@ void HMDToolsDialog::centerCursorOnWidget(QWidget* widget) { QCursor::setPos(screen, windowCenter); } +void HMDToolsDialog::updateUi() { + _switchModeButton->setText(qApp->isHMDMode() ? "Leave HMD Mode" : "Enter HMD Mode"); + _debugDetails->setText(getDebugDetails()); +} + void HMDToolsDialog::showEvent(QShowEvent* event) { // center the cursor on the hmd tools dialog centerCursorOnWidget(this); + updateUi(); } void HMDToolsDialog::hideEvent(QHideEvent* event) { @@ -184,33 +204,31 @@ void HMDToolsDialog::hideEvent(QHideEvent* event) { centerCursorOnWidget(Application::getInstance()->getWindow()); } - -void HMDToolsDialog::aboutToQuit() { - if (_inHDMMode) { - // FIXME this is ineffective because it doesn't trigger the menu to - // save the fact that VR Mode is not checked. - leaveHDMMode(); - } -} - void HMDToolsDialog::screenCountChanged(int newCount) { - if (!OculusManager::isConnected()) { - //OculusManager::connect(); + int hmdScreenNumber = -1; + auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); + foreach(auto dp, displayPlugins) { + if (dp->isHmd()) { + if (dp->getHmdScreen() >= 0) { + hmdScreenNumber = dp->getHmdScreen(); + } + break; + } } - int hmdScreenNumber = OculusManager::getHMDScreen(); - if (_inHDMMode && _hmdScreenNumber != hmdScreenNumber) { + if (qApp->isHMDMode() && _hmdScreenNumber != hmdScreenNumber) { qDebug() << "HMD Display changed WHILE IN HMD MODE"; - leaveHDMMode(); + leaveHMDMode(); // if there is a new best HDM screen then go back into HDM mode after done leaving if (hmdScreenNumber >= 0) { - qDebug() << "Trying to go back into HDM Mode"; + qDebug() << "Trying to go back into HMD Mode"; const int SLIGHT_DELAY = 2000; - QTimer::singleShot(SLIGHT_DELAY, this, SLOT(enterHDMMode())); + QTimer::singleShot(SLIGHT_DELAY, [this]{ + enterHMDMode(); + }); } } - _debugDetails->setText(getDebugDetails()); } void HMDToolsDialog::watchWindow(QWindow* window) { @@ -247,9 +265,8 @@ void HMDWindowWatcher::windowScreenChanged(QScreen* screen) { // if we have more than one screen, and a known hmdScreen then try to // keep our dialog off of the hmdScreen if (QApplication::desktop()->screenCount() > 1) { - + int hmdScreenNumber = _hmdTools->_hmdScreenNumber; // we want to use a local variable here because we are not necesarily in HMD mode - int hmdScreenNumber = OculusManager::getHMDScreen(); if (hmdScreenNumber >= 0) { QScreen* hmdScreen = QGuiApplication::screens()[hmdScreenNumber]; if (screen == hmdScreen) { diff --git a/interface/src/ui/HMDToolsDialog.h b/interface/src/ui/HMDToolsDialog.h index c16957f2d7..11cab91673 100644 --- a/interface/src/ui/HMDToolsDialog.h +++ b/interface/src/ui/HMDToolsDialog.h @@ -34,9 +34,6 @@ signals: public slots: void reject(); - void switchModeClicked(bool checked); - void applicationWindowScreenChanged(QScreen* screen); - void aboutToQuit(); void screenCountChanged(int newCount); protected: @@ -46,20 +43,24 @@ protected: private: void centerCursorOnWidget(QWidget* widget); - void enterHDMMode(); - void leaveHDMMode(); + void enterHMDMode(); + void leaveHMDMode(); + void toggleHMDMode(); + void updateUi(); - QScreen* _previousScreen; - QScreen* _hmdScreen; - int _hmdScreenNumber; - QPushButton* _switchModeButton; - QLabel* _debugDetails; + QScreen* _previousScreen{ nullptr }; + QScreen* _hmdScreen{ nullptr }; + int _hmdScreenNumber{ -1 }; + QPushButton* _switchModeButton{ nullptr }; + QLabel* _debugDetails{ nullptr }; QRect _previousDialogRect; - QScreen* _previousDialogScreen; - bool _inHDMMode; + QScreen* _previousDialogScreen{ nullptr }; + QString _hmdPluginName; + QString _defaultPluginName; QHash _windowWatchers; + friend class HMDWindowWatcher; }; diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp new file mode 100644 index 0000000000..21165b8c20 --- /dev/null +++ b/interface/src/ui/OverlayConductor.cpp @@ -0,0 +1,157 @@ +// +// OverlayConductor.cpp +// interface/src/ui +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Application.h" +#include "InterfaceLogging.h" +#include "avatar/AvatarManager.h" + +#include "OverlayConductor.h" + +OverlayConductor::OverlayConductor() { +} + +OverlayConductor::~OverlayConductor() { +} + +void OverlayConductor::update(float dt) { + + updateMode(); + + switch (_mode) { + case SITTING: { + // when sitting, the overlay is at the origin, facing down the -z axis. + // the camera is taken directly from the HMD. + Transform identity; + qApp->getApplicationCompositor().setModelTransform(identity); + qApp->getApplicationCompositor().setCameraBaseTransform(identity); + break; + } + case STANDING: { + // when standing, the overlay is at a reference position, which is set when the overlay is + // enabled. The camera is taken directly from the HMD, but in world space. + // So the sensorToWorldMatrix must be applied. + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + Transform t; + t.evalFromRawMatrix(myAvatar->getSensorToWorldMatrix()); + qApp->getApplicationCompositor().setCameraBaseTransform(t); + + // detect when head moves out side of sweet spot, or looks away. + mat4 headMat = myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose(); + vec3 headWorldPos = extractTranslation(headMat); + vec3 headForward = glm::quat_cast(headMat) * glm::vec3(0.0f, 0.0f, -1.0f); + Transform modelXform = qApp->getApplicationCompositor().getModelTransform(); + vec3 compositorWorldPos = modelXform.getTranslation(); + vec3 compositorForward = modelXform.getRotation() * glm::vec3(0.0f, 0.0f, -1.0f); + const float MAX_COMPOSITOR_DISTANCE = 0.6f; + const float MAX_COMPOSITOR_ANGLE = 110.0f; + if (_enabled && (glm::distance(headWorldPos, compositorWorldPos) > MAX_COMPOSITOR_DISTANCE || + glm::dot(headForward, compositorForward) < cosf(glm::radians(MAX_COMPOSITOR_ANGLE)))) { + // fade out the overlay + setEnabled(false); + } + break; + } + case FLAT: + // do nothing + break; + } +} + +void OverlayConductor::updateMode() { + + Mode newMode; + if (qApp->isHMDMode()) { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + if (myAvatar->getStandingHMDSensorMode()) { + newMode = STANDING; + } else { + newMode = SITTING; + } + } else { + newMode = FLAT; + } + + if (newMode != _mode) { + switch (newMode) { + case SITTING: { + // enter the SITTING state + // place the overlay at origin + Transform identity; + qApp->getApplicationCompositor().setModelTransform(identity); + break; + } + case STANDING: { + // enter the STANDING state + // place the overlay at the current hmd position in world space + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); + Transform t; + t.setTranslation(extractTranslation(camMat)); + t.setRotation(glm::quat_cast(camMat)); + qApp->getApplicationCompositor().setModelTransform(t); + break; + } + + case FLAT: + // do nothing + break; + } + } + + _mode = newMode; +} + +void OverlayConductor::setEnabled(bool enabled) { + + if (enabled == _enabled) { + return; + } + + if (_enabled) { + // alpha fadeOut the overlay mesh. + qApp->getApplicationCompositor().fadeOut(); + + // disable mouse clicks from script + qApp->getOverlays().disable(); + + // disable QML events + auto offscreenUi = DependencyManager::get(); + offscreenUi->getRootItem()->setEnabled(false); + + _enabled = false; + } else { + // alpha fadeIn the overlay mesh. + qApp->getApplicationCompositor().fadeIn(); + + // enable mouse clicks from script + qApp->getOverlays().enable(); + + // enable QML events + auto offscreenUi = DependencyManager::get(); + offscreenUi->getRootItem()->setEnabled(true); + + if (_mode == STANDING) { + // place the overlay at the current hmd position in world space + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + auto camMat = cancelOutRollAndPitch(myAvatar->getSensorToWorldMatrix() * qApp->getHMDSensorPose()); + Transform t; + t.setTranslation(extractTranslation(camMat)); + t.setRotation(glm::quat_cast(camMat)); + qApp->getApplicationCompositor().setModelTransform(t); + } + + _enabled = true; + } +} + +bool OverlayConductor::getEnabled() const { + return _enabled; +} + diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h new file mode 100644 index 0000000000..4b8c0134b5 --- /dev/null +++ b/interface/src/ui/OverlayConductor.h @@ -0,0 +1,36 @@ +// +// OverlayConductor.h +// interface/src/ui +// +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OverlayConductor_h +#define hifi_OverlayConductor_h + +class OverlayConductor { +public: + OverlayConductor(); + ~OverlayConductor(); + + void update(float dt); + void setEnabled(bool enable); + bool getEnabled() const; + +private: + void updateMode(); + + enum Mode { + FLAT, + SITTING, + STANDING + }; + + Mode _mode = FLAT; + bool _enabled = true; +}; + +#endif diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 121b7d611c..8e9c164563 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include // TODO: This should be replaced with InputDevice/InputPlugin, or something similar #include #include "Application.h" @@ -177,10 +177,13 @@ void PreferencesDialog::loadPreferences() { ui.maxOctreePPSSpin->setValue(qApp->getMaxOctreePacketsPerSecond()); +#if 0 ui.oculusUIAngularSizeSpin->setValue(qApp->getApplicationCompositor().getHmdUIAngularSize()); +#endif + + ui.sixenseReticleMoveSpeedSpin->setValue(InputDevice::getReticleMoveSpeed()); SixenseManager& sixense = SixenseManager::getInstance(); - ui.sixenseReticleMoveSpeedSpin->setValue(sixense.getReticleMoveSpeed()); ui.invertSixenseButtonsCheckBox->setChecked(sixense.getInvertButtons()); // LOD items @@ -244,7 +247,7 @@ void PreferencesDialog::savePreferences() { qApp->getApplicationCompositor().setHmdUIAngularSize(ui.oculusUIAngularSizeSpin->value()); SixenseManager& sixense = SixenseManager::getInstance(); - sixense.setReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); + InputDevice::setReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); sixense.setInvertButtons(ui.invertSixenseButtonsCheckBox->isChecked()); auto audio = DependencyManager::get(); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 2bfe92a504..41bd379c32 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include "Application.h" diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index db140e53e2..d7c4bb5ed7 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index cb235b37fb..39b8892f13 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -104,7 +104,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { auto geometryCache = DependencyManager::get(); auto textureCache = DependencyManager::get(); - auto size = qApp->getCanvasSize(); + auto size = qApp->getUiSize(); int width = size.x; int height = size.y; mat4 legacyProjection = glm::ortho(0, width, height, 0, -1000, 1000); @@ -123,6 +123,16 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { } } +void Overlays::disable() { + QWriteLocker lock(&_lock); + _enabled = false; +} + +void Overlays::enable() { + QWriteLocker lock(&_lock); + _enabled = true; +} + Overlay::Pointer Overlays::getOverlay(unsigned int id) const { if (_overlaysHUD.contains(id)) { return _overlaysHUD[id]; @@ -323,6 +333,9 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { } QReadLocker lock(&_lock); + if (!_enabled) { + return 0; + } QMapIterator i(_overlaysHUD); i.toBack(); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 7afcf8a9b1..212b7b227d 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -65,6 +65,8 @@ public: void init(); void update(float deltatime); void renderHUD(RenderArgs* renderArgs); + void disable(); + void enable(); Overlay::Pointer getOverlay(unsigned int id) const; OverlayPanel::Pointer getPanel(unsigned int id) const { return _panels[id]; } @@ -147,6 +149,7 @@ private: QReadWriteLock _lock; QReadWriteLock _deleteLock; QScriptEngine* _scriptEngine; + bool _enabled = true; }; diff --git a/interface/ui/temp.qml b/interface/ui/temp.qml new file mode 100644 index 0000000000..9617a0a8b7 --- /dev/null +++ b/interface/ui/temp.qml @@ -0,0 +1,33 @@ +import QtQuick 2.4 +import QtQuick.Controls 2.3 +import QtQuick.Controls.Styles 1.3 + + +Item { + implicitHeight: 200 + implicitWidth: 800 + + + TextArea { + id: gutter + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + style: TextAreaStyle { + backgroundColor: "grey" + } + width: 16 + text: ">" + font.family: "Lucida Console" + } + TextArea { + anchors.left: gutter.right + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + text: "undefined" + font.family: "Lucida Console" + + } +} + diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 4ebb935464..dbc7b9b9ec 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -192,7 +192,7 @@ public: void setBodyRoll(float bodyRoll) { _bodyRoll = bodyRoll; } glm::quat getOrientation() const; - void setOrientation(const glm::quat& orientation, bool overideReferential = false); + virtual void setOrientation(const glm::quat& orientation, bool overideReferential = false); glm::quat getHeadOrientation() const { return _headData->getOrientation(); } void setHeadOrientation(const glm::quat& orientation) { _headData->setOrientation(orientation); } diff --git a/libraries/display-plugins/CMakeLists.txt b/libraries/display-plugins/CMakeLists.txt new file mode 100644 index 0000000000..321b13f191 --- /dev/null +++ b/libraries/display-plugins/CMakeLists.txt @@ -0,0 +1,34 @@ +set(TARGET_NAME display-plugins) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library(OpenGL) + +setup_hifi_opengl() + +link_hifi_libraries(shared plugins gpu render-utils) + +GroupSources("src/display-plugins") + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + +add_dependency_external_projects(boostconfig) +find_package(BoostConfig REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${BOOSTCONFIG_INCLUDE_DIRS}) + +add_dependency_external_projects(oglplus) +find_package(OGLPLUS REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) + +add_dependency_external_projects(LibOVR) +find_package(LibOVR REQUIRED) +target_include_directories(${TARGET_NAME} PRIVATE ${LIBOVR_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES}) + +if (WIN32) + add_dependency_external_projects(OpenVR) + find_package(OpenVR REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) +endif() \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp new file mode 100644 index 0000000000..ce6467cd55 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -0,0 +1,36 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Basic2DWindowOpenGLDisplayPlugin.h" + +#include +#include + +const QString Basic2DWindowOpenGLDisplayPlugin::NAME("2D Display"); + +const QString MENU_PARENT = "View"; +const QString MENU_NAME = "Display Options"; +const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +const QString FULLSCREEN = "Fullscreen"; + +const QString& Basic2DWindowOpenGLDisplayPlugin::getName() const { + return NAME; +} + +void Basic2DWindowOpenGLDisplayPlugin::activate() { +// container->addMenu(MENU_PATH); +// container->addMenuItem(MENU_PATH, FULLSCREEN, +// [this] (bool clicked) { this->setFullscreen(clicked); }, +// true, false); + MainWindowOpenGLDisplayPlugin::activate(); +} + +void Basic2DWindowOpenGLDisplayPlugin::deactivate() { +// container->removeMenuItem(MENU_NAME, FULLSCREEN); +// container->removeMenu(MENU_PATH); + MainWindowOpenGLDisplayPlugin::deactivate(); +} diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h new file mode 100644 index 0000000000..d19326f007 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "MainWindowOpenGLDisplayPlugin.h" + +class Basic2DWindowOpenGLDisplayPlugin : public MainWindowOpenGLDisplayPlugin { + Q_OBJECT + +public: + virtual void activate() override; + virtual void deactivate() override; + + virtual const QString & getName() const override; + +private: + static const QString NAME; +}; diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp new file mode 100644 index 0000000000..2316ff70c4 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.cpp @@ -0,0 +1,52 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "DisplayPlugin.h" + +#include + +#include "NullDisplayPlugin.h" +#include "stereo/SideBySideStereoDisplayPlugin.h" +#include "stereo/InterleavedStereoDisplayPlugin.h" +#include "Basic2DWindowOpenGLDisplayPlugin.h" + +#include "openvr/OpenVrDisplayPlugin.h" +#include "oculus/Oculus_0_5_DisplayPlugin.h" +#include "oculus/Oculus_0_6_DisplayPlugin.h" + +// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class +DisplayPluginList getDisplayPlugins() { + DisplayPlugin* PLUGIN_POOL[] = { + new Basic2DWindowOpenGLDisplayPlugin(), +#ifdef DEBUG + new NullDisplayPlugin(), +#endif + + // Stereo modes + // FIXME fix stereo display plugins + //new SideBySideStereoDisplayPlugin(), + //new InterleavedStereoDisplayPlugin(), + + // HMDs + new Oculus_0_5_DisplayPlugin(), + new Oculus_0_6_DisplayPlugin(), +#ifdef Q_OS_WIN + new OpenVrDisplayPlugin(), +#endif + nullptr + }; + + DisplayPluginList result; + for (int i = 0; PLUGIN_POOL[i]; ++i) { + DisplayPlugin * plugin = PLUGIN_POOL[i]; + if (plugin->isSupported()) { + plugin->init(); + result.push_back(DisplayPluginPointer(plugin)); + } + } + return result; +} diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h new file mode 100644 index 0000000000..45a5923a1f --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -0,0 +1,128 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "plugins/Plugin.h" + +#include +#include +#include + +#include "gpu/GPUConfig.h" + +#include +#include +#include + +enum Eye { + Left, + Right, + Mono +}; + +/* + * Helper method to iterate over each eye + */ +template +void for_each_eye(F f) { + f(Left); + f(Right); +} + +/* + * Helper method to iterate over each eye, with an additional lambda to take action between the eyes + */ +template +void for_each_eye(F f, FF ff) { + f(Eye::Left); + ff(); + f(Eye::Right); +} + +class QWindow; + +class DisplayPlugin : public Plugin { + Q_OBJECT +public: + virtual bool isHmd() const { return false; } + virtual int getHmdScreen() const { return -1; } + /// By default, all HMDs are stereo + virtual bool isStereo() const { return isHmd(); } + virtual bool isThrottled() const { return false; } + + // Rendering support + + /** + * Called by the application before the frame rendering. Can be used for + * render timing related calls (for instance, the Oculus begin frame timing + * call) + */ + virtual void preRender() = 0; + /** + * Called by the application immediately before calling the display function. + * For OpenGL based plugins, this is the best place to put activate the output + * OpenGL context + */ + virtual void preDisplay() = 0; + + /** + * Sends the scene texture to the display plugin. + */ + virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) = 0; + + /** + * Called by the application immeidately after display. For OpenGL based + * displays, this is the best place to put the buffer swap + */ + virtual void finishFrame() = 0; + + // Does the rendering surface have current focus? + virtual bool hasFocus() const = 0; + + // The size of the rendering target (may be larger than the device size due to distortion) + virtual glm::uvec2 getRecommendedRenderSize() const = 0; + + // The size of the UI + virtual glm::uvec2 getRecommendedUiSize() const { + return getRecommendedRenderSize(); + } + + // Stereo specific methods + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const { + return baseProjection; + } + + virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const { + return glm::inverse(getEyePose(eye)) * baseModelview; + } + + // HMD specific methods + // TODO move these into another class + virtual glm::mat4 getEyePose(Eye eye) const { + static const glm::mat4 pose; return pose; + } + + virtual glm::mat4 getHeadPose() const { + static const glm::mat4 pose; return pose; + } + + virtual void abandonCalibration() {} + virtual void resetSensors() {} + virtual float devicePixelRatio() { return 1.0; } + + //// The window for the surface, used for event interception. May be null. + //virtual QWindow* getWindow() const = 0; + + //virtual void installEventFilter(QObject* filter) {} + //virtual void removeEventFilter(QObject* filter) {} + +signals: + void recommendedFramebufferSizeChanged(const QSize & size); + void requestRender(); +}; + diff --git a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp new file mode 100644 index 0000000000..68fe92c943 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.cpp @@ -0,0 +1,9 @@ + +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "MainWindowOpenGLDisplayPlugin.h" diff --git a/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h new file mode 100644 index 0000000000..5b28ec7c21 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/MainWindowOpenGLDisplayPlugin.h @@ -0,0 +1,13 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "WindowOpenGLDisplayPlugin.h" + +class MainWindowOpenGLDisplayPlugin : public WindowOpenGLDisplayPlugin { +}; diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp new file mode 100644 index 0000000000..e5a96d167e --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -0,0 +1,32 @@ +// +// NullDisplayPlugin.cpp +// +// Created by Bradley Austin Davis on 2014/04/13. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "NullDisplayPlugin.h" + +const QString NullDisplayPlugin::NAME("NullDisplayPlugin"); + +const QString & NullDisplayPlugin::getName() const { + return NAME; +} + +glm::uvec2 NullDisplayPlugin::getRecommendedRenderSize() const { + return glm::uvec2(100, 100); +} + +bool NullDisplayPlugin::hasFocus() const { + return false; +} + +void NullDisplayPlugin::preRender() {} +void NullDisplayPlugin::preDisplay() {} +void NullDisplayPlugin::display(GLuint sceneTexture, const glm::uvec2& sceneSize) {} +void NullDisplayPlugin::finishFrame() {} + +void NullDisplayPlugin::activate() {} +void NullDisplayPlugin::deactivate() {} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h new file mode 100644 index 0000000000..90e717b5ee --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "DisplayPlugin.h" + +class NullDisplayPlugin : public DisplayPlugin { +public: + + virtual ~NullDisplayPlugin() final {} + virtual const QString & getName() const override; + + void activate() override; + void deactivate() override; + + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual bool hasFocus() const override; + virtual void preRender() override; + virtual void preDisplay() override; + virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; + virtual void finishFrame() override; + +private: + static const QString NAME; +}; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp new file mode 100644 index 0000000000..7269c9410c --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -0,0 +1,117 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "OpenGLDisplayPlugin.h" + +#include +#include + +#include +#include + + +OpenGLDisplayPlugin::OpenGLDisplayPlugin() { + connect(&_timer, &QTimer::timeout, this, [&] { + emit requestRender(); + }); +} + +OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { +} + +void OpenGLDisplayPlugin::preDisplay() { + makeCurrent(); +}; + +void OpenGLDisplayPlugin::preRender() { + // NOOP +} + +void OpenGLDisplayPlugin::finishFrame() { + swapBuffers(); + doneCurrent(); +}; + +void OpenGLDisplayPlugin::customizeContext() { + using namespace oglplus; + Context::BlendFunc(BlendFunction::SrcAlpha, BlendFunction::OneMinusSrcAlpha); + Context::Disable(Capability::Blend); + Context::Disable(Capability::DepthTest); + Context::Disable(Capability::CullFace); + + + _program = loadDefaultShader(); + _plane = loadPlane(_program); +} + +void OpenGLDisplayPlugin::activate() { + _timer.start(1); +} + +void OpenGLDisplayPlugin::deactivate() { + _timer.stop(); + + makeCurrent(); + Q_ASSERT(0 == glGetError()); + _program.reset(); + _plane.reset(); + doneCurrent(); +} + +// Pressing Alt (and Meta) key alone activates the menubar because its style inherits the +// SHMenuBarAltKeyNavigation from QWindowsStyle. This makes it impossible for a scripts to +// receive keyPress events for the Alt (and Meta) key in a reliable manner. +// +// This filter catches events before QMenuBar can steal the keyboard focus. +// The idea was borrowed from +// http://www.archivum.info/qt-interest@trolltech.com/2006-09/00053/Re-(Qt4)-Alt-key-focus-QMenuBar-(solved).html + +// Pass input events on to the application +bool OpenGLDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::Wheel: + + case QEvent::TouchBegin: + case QEvent::TouchEnd: + case QEvent::TouchUpdate: + + case QEvent::FocusIn: + case QEvent::FocusOut: + + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ShortcutOverride: + + case QEvent::DragEnter: + case QEvent::Drop: + + case QEvent::Resize: + if (QCoreApplication::sendEvent(QCoreApplication::instance(), event)) { + return true; + } + break; + } + return false; +} + +void OpenGLDisplayPlugin::display( + GLuint finalTexture, const glm::uvec2& sceneSize) { + using namespace oglplus; + uvec2 size = getRecommendedRenderSize(); + Context::Viewport(size.x, size.y); + glBindTexture(GL_TEXTURE_2D, finalTexture); + drawUnitQuad(); +} + +void OpenGLDisplayPlugin::drawUnitQuad() { + _program->Bind(); + _plane->Draw(); +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h new file mode 100644 index 0000000000..9986610a50 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -0,0 +1,45 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +#include "DisplayPlugin.h" +#include "OglplusHelpers.h" + +class GlWindow; +class QOpenGLContext; + +class OpenGLDisplayPlugin : public DisplayPlugin { +public: + OpenGLDisplayPlugin(); + virtual ~OpenGLDisplayPlugin(); + virtual void preRender() override; + virtual void preDisplay() override; + virtual void finishFrame() override; + + virtual void activate() override; + virtual void deactivate() override; + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + + virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; + +protected: + virtual void customizeContext(); + virtual void drawUnitQuad(); + virtual void makeCurrent() = 0; + virtual void doneCurrent() = 0; + virtual void swapBuffers() = 0; + + QTimer _timer; + ProgramPtr _program; + ShapeWrapperPtr _plane; +}; + + diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp new file mode 100644 index 0000000000..658aa2c767 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.cpp @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "WindowOpenGLDisplayPlugin.h" + +#include +#include + +#include "plugins/PluginContainer.h" + +WindowOpenGLDisplayPlugin::WindowOpenGLDisplayPlugin() { +} + +glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedRenderSize() const { + uvec2 result; + if (_window) { + result = toGlm(_window->geometry().size() * _window->devicePixelRatio()); + } + return result; +} + +glm::uvec2 WindowOpenGLDisplayPlugin::getRecommendedUiSize() const { + uvec2 result; + if (_window) { + result = toGlm(_window->geometry().size()); + } + return result; +} + +bool WindowOpenGLDisplayPlugin::hasFocus() const { + return _window ? _window->hasFocus() : false; +} + +void WindowOpenGLDisplayPlugin::activate() { + OpenGLDisplayPlugin::activate(); + _window = CONTAINER->getPrimarySurface(); + _window->makeCurrent(); + customizeContext(); + _window->doneCurrent(); +} + +void WindowOpenGLDisplayPlugin::deactivate() { + OpenGLDisplayPlugin::deactivate(); + _window = nullptr; +} + +void WindowOpenGLDisplayPlugin::makeCurrent() { + _window->makeCurrent(); +} + +void WindowOpenGLDisplayPlugin::doneCurrent() { + _window->doneCurrent(); +} + +void WindowOpenGLDisplayPlugin::swapBuffers() { + _window->swapBuffers(); +} diff --git a/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h new file mode 100644 index 0000000000..c75cf1484c --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/WindowOpenGLDisplayPlugin.h @@ -0,0 +1,28 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "OpenGLDisplayPlugin.h" + +class QGLWidget; + +class WindowOpenGLDisplayPlugin : public OpenGLDisplayPlugin { +public: + WindowOpenGLDisplayPlugin(); + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override; + virtual bool hasFocus() const override; + virtual void activate() override; + virtual void deactivate() override; + +protected: + virtual void makeCurrent() override; + virtual void doneCurrent() override; + virtual void swapBuffers() override; + QGLWidget* _window{ nullptr }; +}; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp new file mode 100644 index 0000000000..7d0fb705df --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp @@ -0,0 +1,76 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "OculusBaseDisplayPlugin.h" + +#include + +#include "OculusHelpers.h" + + +using namespace Oculus; + +void OculusBaseDisplayPlugin::activate() { + glm::uvec2 eyeSizes[2]; + ovr_for_each_eye([&](ovrEyeType eye) { + _eyeFovs[eye] = _hmd->MaxEyeFov[eye]; + ovrEyeRenderDesc& erd = _eyeRenderDescs[eye] = ovrHmd_GetRenderDesc(_hmd, eye, _eyeFovs[eye]); + ovrMatrix4f ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, ovrProjection_RightHanded); + _eyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + ovrPerspectiveProjection = + ovrMatrix4f_Projection(erd.Fov, 0.001f, 10.0f, ovrProjection_RightHanded); + _compositeEyeProjections[eye] = toGlm(ovrPerspectiveProjection); + + _eyeOffsets[eye] = erd.HmdToEyeViewOffset; + eyeSizes[eye] = toGlm(ovrHmd_GetFovTextureSize(_hmd, eye, erd.Fov, 1.0f)); + }); + _desiredFramebufferSize = uvec2( + eyeSizes[0].x + eyeSizes[1].x, + std::max(eyeSizes[0].y, eyeSizes[1].y)); + + _frameIndex = 0; + + if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, + ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { + qFatal("Could not attach to sensor device"); + } + + MainWindowOpenGLDisplayPlugin::activate(); +} + +uvec2 OculusBaseDisplayPlugin::getRecommendedRenderSize() const { + return _desiredFramebufferSize; +} + +void OculusBaseDisplayPlugin::preRender() { + ovrHmd_GetEyePoses(_hmd, _frameIndex, _eyeOffsets, _eyePoses, nullptr); +} + +glm::mat4 OculusBaseDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + return _eyeProjections[eye]; +} + +glm::mat4 OculusBaseDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelview) const { + return baseModelview * toGlm(_eyePoses[eye]); +} + +void OculusBaseDisplayPlugin::resetSensors() { + ovrHmd_RecenterPose(_hmd); +} + +glm::mat4 OculusBaseDisplayPlugin::getEyePose(Eye eye) const { + return toGlm(_eyePoses[eye]); +} + +// Should NOT be used for rendering as this will mess up timewarp. Use the getModelview() method above for +// any use of head poses for rendering, ensuring you use the correct eye +glm::mat4 OculusBaseDisplayPlugin::getHeadPose() const { + ovrTrackingState state = ovrHmd_GetTrackingState(_hmd, 0.0f); + return toGlm(state.HeadPose.ThePose); +} diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h new file mode 100644 index 0000000000..b338e2709b --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "../MainWindowOpenGLDisplayPlugin.h" + +class OculusBaseDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +public: + // Stereo specific methods + virtual bool isHmd() const override { return true; } + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; + virtual void activate() override; + virtual void preRender() override; + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } + virtual void resetSensors() override; + virtual glm::mat4 getEyePose(Eye eye) const override; + virtual glm::mat4 getHeadPose() const override; + +}; + +#if (OVR_MAJOR_VERSION < 6) +#define OVR_SUCCESS(x) x +#endif diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.cpp new file mode 100644 index 0000000000..f93580e5a3 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.cpp @@ -0,0 +1,24 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OculusHelpers.h" + + +namespace Oculus { + ovrHmd _hmd; + unsigned int _frameIndex{ 0 }; + ovrEyeRenderDesc _eyeRenderDescs[2]; + ovrPosef _eyePoses[2]; + ovrVector3f _eyeOffsets[2]; + ovrFovPort _eyeFovs[2]; + mat4 _eyeProjections[2]; + mat4 _compositeEyeProjections[2]; + uvec2 _desiredFramebufferSize; +} + + diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h new file mode 100644 index 0000000000..d4e1bec312 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusHelpers.h @@ -0,0 +1,88 @@ +// +// Created by Bradley Austin Davis on 2015/05/26 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include +#include +#include + +// Convenience method for looping over each eye with a lambda +template +inline void ovr_for_each_eye(Function function) { + for (ovrEyeType eye = ovrEyeType::ovrEye_Left; + eye < ovrEyeType::ovrEye_Count; + eye = static_cast(eye + 1)) { + function(eye); + } +} + +inline glm::mat4 toGlm(const ovrMatrix4f & om) { + return glm::transpose(glm::make_mat4(&om.M[0][0])); +} + +inline glm::mat4 toGlm(const ovrFovPort & fovport, float nearPlane = 0.01f, float farPlane = 10000.0f) { + return toGlm(ovrMatrix4f_Projection(fovport, nearPlane, farPlane, true)); +} + +inline glm::vec3 toGlm(const ovrVector3f & ov) { + return glm::make_vec3(&ov.x); +} + +inline glm::vec2 toGlm(const ovrVector2f & ov) { + return glm::make_vec2(&ov.x); +} + +inline glm::uvec2 toGlm(const ovrSizei & ov) { + return glm::uvec2(ov.w, ov.h); +} + +inline glm::quat toGlm(const ovrQuatf & oq) { + return glm::make_quat(&oq.x); +} + +inline glm::mat4 toGlm(const ovrPosef & op) { + glm::mat4 orientation = glm::mat4_cast(toGlm(op.Orientation)); + glm::mat4 translation = glm::translate(glm::mat4(), toGlm(op.Position)); + return translation * orientation; +} + +inline ovrMatrix4f ovrFromGlm(const glm::mat4 & m) { + ovrMatrix4f result; + glm::mat4 transposed(glm::transpose(m)); + memcpy(result.M, &(transposed[0][0]), sizeof(float) * 16); + return result; +} + +inline ovrVector3f ovrFromGlm(const glm::vec3 & v) { + return{ v.x, v.y, v.z }; +} + +inline ovrVector2f ovrFromGlm(const glm::vec2 & v) { + return{ v.x, v.y }; +} + +inline ovrSizei ovrFromGlm(const glm::uvec2 & v) { + return{ (int)v.x, (int)v.y }; +} + +inline ovrQuatf ovrFromGlm(const glm::quat & q) { + return{ q.x, q.y, q.z, q.w }; +} + +namespace Oculus { + extern ovrHmd _hmd; + extern unsigned int _frameIndex; + extern ovrEyeRenderDesc _eyeRenderDescs[2]; + extern ovrPosef _eyePoses[2]; + extern ovrVector3f _eyeOffsets[2]; + extern ovrFovPort _eyeFovs[2]; + extern mat4 _eyeProjections[2]; + extern mat4 _compositeEyeProjections[2]; + extern uvec2 _desiredFramebufferSize; +} diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp new file mode 100644 index 0000000000..86ee3b41f2 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.cpp @@ -0,0 +1,192 @@ +// +// Created by Bradley Austin Davis on 2014/04/13. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Oculus_0_5_DisplayPlugin.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include + +#include +#include + +#include "plugins/PluginContainer.h" +#include "OculusHelpers.h" + +using namespace Oculus; +ovrTexture _eyeTextures[2]; +int _hmdScreen{ -1 }; +bool _hswDismissed{ false }; + +DisplayPlugin* makeOculusDisplayPlugin() { + return new Oculus_0_5_DisplayPlugin(); +} + +using namespace oglplus; + +const QString Oculus_0_5_DisplayPlugin::NAME("Oculus Rift (0.5)"); + +const QString & Oculus_0_5_DisplayPlugin::getName() const { + return NAME; +} + + +bool Oculus_0_5_DisplayPlugin::isSupported() const { +#if (OVR_MAJOR_VERSION == 5) + if (!ovr_Initialize(nullptr)) { + return false; + } + bool result = false; + if (ovrHmd_Detect() > 0) { + result = true; + } + + auto hmd = ovrHmd_Create(0); + if (hmd) { + QPoint targetPosition{ hmd->WindowsPos.x, hmd->WindowsPos.y }; + auto screens = qApp->screens(); + for(int i = 0; i < screens.size(); ++i) { + auto screen = screens[i]; + QPoint position = screen->geometry().topLeft(); + if (position == targetPosition) { + _hmdScreen = i; + break; + } + } + } + + ovr_Shutdown(); + return result; +#else + return false; +#endif +} + +void Oculus_0_5_DisplayPlugin::activate() { +#if (OVR_MAJOR_VERSION == 5) + if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { + Q_ASSERT(false); + qFatal("Failed to Initialize SDK"); + } + _hswDismissed = false; + _hmd = ovrHmd_Create(0); + if (!_hmd) { + qFatal("Failed to acquire HMD"); + } + + OculusBaseDisplayPlugin::activate(); + int screen = getHmdScreen(); + if (screen != -1) { + CONTAINER->setFullscreen(qApp->screens()[screen]); + } + + _window->installEventFilter(this); + _window->makeCurrent(); + ovrGLConfig config; memset(&config, 0, sizeof(ovrRenderAPIConfig)); + auto& header = config.Config.Header; + header.API = ovrRenderAPI_OpenGL; + header.BackBufferSize = _hmd->Resolution; + header.Multisample = 1; + int distortionCaps = 0 + | ovrDistortionCap_TimeWarp + ; + + memset(_eyeTextures, 0, sizeof(ovrTexture) * 2); + ovr_for_each_eye([&](ovrEyeType eye) { + auto& header = _eyeTextures[eye].Header; + header.API = ovrRenderAPI_OpenGL; + header.TextureSize = { (int)_desiredFramebufferSize.x, (int)_desiredFramebufferSize.y }; + header.RenderViewport.Size = header.TextureSize; + header.RenderViewport.Size.w /= 2; + if (eye == ovrEye_Right) { + header.RenderViewport.Pos.x = header.RenderViewport.Size.w; + } + }); + + ovrEyeRenderDesc _eyeRenderDescs[ovrEye_Count]; + ovrBool result = ovrHmd_ConfigureRendering(_hmd, &config.Config, distortionCaps, _eyeFovs, _eyeRenderDescs); + Q_ASSERT(result); +#endif +} + +void Oculus_0_5_DisplayPlugin::deactivate() { +#if (OVR_MAJOR_VERSION == 5) + _window->removeEventFilter(this); + + OculusBaseDisplayPlugin::deactivate(); + + QScreen* riftScreen = nullptr; + if (_hmdScreen >= 0) { + riftScreen = qApp->screens()[_hmdScreen]; + } + CONTAINER->unsetFullscreen(riftScreen); + + ovrHmd_Destroy(_hmd); + _hmd = nullptr; + ovr_Shutdown(); +#endif +} + +void Oculus_0_5_DisplayPlugin::preRender() { +#if (OVR_MAJOR_VERSION == 5) + OculusBaseDisplayPlugin::preRender(); + ovrHmd_BeginFrame(_hmd, _frameIndex); +#endif +} + +void Oculus_0_5_DisplayPlugin::preDisplay() { + _window->makeCurrent(); +} + +void Oculus_0_5_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { + ++_frameIndex; +#if (OVR_MAJOR_VERSION == 5) + ovr_for_each_eye([&](ovrEyeType eye) { + reinterpret_cast(_eyeTextures[eye]).OGL.TexId = finalTexture; + }); + ovrHmd_EndFrame(_hmd, _eyePoses, _eyeTextures); +#endif +} + +// Pass input events on to the application +bool Oculus_0_5_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { +#if (OVR_MAJOR_VERSION == 5) + if (!_hswDismissed && (event->type() == QEvent::KeyPress)) { + static ovrHSWDisplayState hswState; + ovrHmd_GetHSWDisplayState(_hmd, &hswState); + if (hswState.Displayed) { + ovrHmd_DismissHSWDisplay(_hmd); + } else { + _hswDismissed = true; + } + } +#endif + return OculusBaseDisplayPlugin::eventFilter(receiver, event); +} + +// FIXME mirroring tot he main window is diffucult on OSX because it requires that we +// trigger a swap, which causes the client to wait for the v-sync of the main screen running +// at 60 Hz. This would introduce judder. Perhaps we can push mirroring to a separate +// thread +void Oculus_0_5_DisplayPlugin::finishFrame() { + _window->doneCurrent(); +}; + +int Oculus_0_5_DisplayPlugin::getHmdScreen() const { + return _hmdScreen; +} diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h new file mode 100644 index 0000000000..b539d07fb0 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_5_DisplayPlugin.h @@ -0,0 +1,37 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "OculusBaseDisplayPlugin.h" + +#include + +class Oculus_0_5_DisplayPlugin : public OculusBaseDisplayPlugin { +public: + virtual bool isSupported() const override; + virtual const QString & getName() const override; + + virtual void activate() override; + virtual void deactivate() override; + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + + virtual int getHmdScreen() const override; + +protected: + virtual void preRender() override; + virtual void preDisplay() override; + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; +}; + + diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp new file mode 100644 index 0000000000..6ed8977f47 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.cpp @@ -0,0 +1,370 @@ +// +// Created by Bradley Austin Davis on 2014/04/13. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Oculus_0_6_DisplayPlugin.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + + +#include +#include +#include +#include +#include + +#include +#include + +#include "OculusHelpers.h" + +using namespace Oculus; +#if (OVR_MAJOR_VERSION == 6) +SwapFboPtr _sceneFbo; +MirrorFboPtr _mirrorFbo; +ovrLayerEyeFov _sceneLayer; + +// A base class for FBO wrappers that need to use the Oculus C +// API to manage textures via ovrHmd_CreateSwapTextureSetGL, +// ovrHmd_CreateMirrorTextureGL, etc +template +struct RiftFramebufferWrapper : public FramebufferWrapper { + ovrHmd hmd; + RiftFramebufferWrapper(const ovrHmd & hmd) : hmd(hmd) { + color = 0; + depth = 0; + }; + + void Resize(const uvec2 & size) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oglplus::GetName(fbo)); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + this->size = size; + initColor(); + initDone(); + } + +protected: + virtual void initDepth() override final { + } +}; + +// A wrapper for constructing and using a swap texture set, +// where each frame you draw to a texture via the FBO, +// then submit it and increment to the next texture. +// The Oculus SDK manages the creation and destruction of +// the textures +struct SwapFramebufferWrapper : public RiftFramebufferWrapper { + SwapFramebufferWrapper(const ovrHmd & hmd) + : RiftFramebufferWrapper(hmd) { + } + + ~SwapFramebufferWrapper() { + if (color) { + ovrHmd_DestroySwapTextureSet(hmd, color); + color = nullptr; + } + } + + void Increment() { + ++color->CurrentIndex; + color->CurrentIndex %= color->TextureCount; + } + +protected: + virtual void initColor() override { + if (color) { + ovrHmd_DestroySwapTextureSet(hmd, color); + color = nullptr; + } + + if (!OVR_SUCCESS(ovrHmd_CreateSwapTextureSetGL(hmd, GL_RGBA, size.x, size.y, &color))) { + qFatal("Unable to create swap textures"); + } + + for (int i = 0; i < color->TextureCount; ++i) { + ovrGLTexture& ovrTex = (ovrGLTexture&)color->Textures[i]; + glBindTexture(GL_TEXTURE_2D, ovrTex.OGL.TexId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + glBindTexture(GL_TEXTURE_2D, 0); + } + + virtual void initDone() override { + } + + virtual void onBind(oglplus::Framebuffer::Target target) override { + ovrGLTexture& tex = (ovrGLTexture&)(color->Textures[color->CurrentIndex]); + glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.OGL.TexId, 0); + } + + virtual void onUnbind(oglplus::Framebuffer::Target target) override { + glFramebufferTexture2D(toEnum(target), GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + } +}; + + +// We use a FBO to wrap the mirror texture because it makes it easier to +// render to the screen via glBlitFramebuffer +struct MirrorFramebufferWrapper : public RiftFramebufferWrapper { + MirrorFramebufferWrapper(const ovrHmd & hmd) + : RiftFramebufferWrapper(hmd) { } + + virtual ~MirrorFramebufferWrapper() { + if (color) { + ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); + color = nullptr; + } + } + +private: + void initColor() override { + if (color) { + ovrHmd_DestroyMirrorTexture(hmd, (ovrTexture*)color); + color = nullptr; + } + ovrResult result = ovrHmd_CreateMirrorTextureGL(hmd, GL_RGBA, size.x, size.y, (ovrTexture**)&color); + Q_ASSERT(OVR_SUCCESS(result)); + } + + void initDone() override { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oglplus::GetName(fbo)); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color->OGL.TexId, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + } +}; + +#endif + + +const QString Oculus_0_6_DisplayPlugin::NAME("Oculus Rift"); + +const QString & Oculus_0_6_DisplayPlugin::getName() const { + return NAME; +} + +bool Oculus_0_6_DisplayPlugin::isSupported() const { +#if (OVR_MAJOR_VERSION == 6) + if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { + return false; + } + bool result = false; + if (ovrHmd_Detect() > 0) { + result = true; + } + ovr_Shutdown(); + return result; +#else + return false; +#endif +} + + +#if (OVR_MAJOR_VERSION == 6) +ovrLayerEyeFov& getSceneLayer() { + return _sceneLayer; +} +#endif + +//static gpu::TexturePointer _texture; + +void Oculus_0_6_DisplayPlugin::activate() { +#if (OVR_MAJOR_VERSION == 6) + if (!OVR_SUCCESS(ovr_Initialize(nullptr))) { + Q_ASSERT(false); + qFatal("Failed to Initialize SDK"); + } + if (!OVR_SUCCESS(ovrHmd_Create(0, &_hmd))) { + Q_ASSERT(false); + qFatal("Failed to acquire HMD"); + } + + OculusBaseDisplayPlugin::activate(); + + // Parent class relies on our _hmd intialization, so it must come after that. + ovrLayerEyeFov& sceneLayer = getSceneLayer(); + memset(&sceneLayer, 0, sizeof(ovrLayerEyeFov)); + sceneLayer.Header.Type = ovrLayerType_EyeFov; + sceneLayer.Header.Flags = ovrLayerFlag_TextureOriginAtBottomLeft; + ovr_for_each_eye([&](ovrEyeType eye) { + ovrFovPort & fov = sceneLayer.Fov[eye] = _eyeRenderDescs[eye].Fov; + ovrSizei & size = sceneLayer.Viewport[eye].Size = ovrHmd_GetFovTextureSize(_hmd, eye, fov, 1.0f); + sceneLayer.Viewport[eye].Pos = { eye == ovrEye_Left ? 0 : size.w, 0 }; + }); + // We're rendering both eyes to the same texture, so only one of the + // pointers is populated + sceneLayer.ColorTexture[0] = _sceneFbo->color; + // not needed since the structure was zeroed on init, but explicit + sceneLayer.ColorTexture[1] = nullptr; + + PerformanceTimer::setActive(true); + + if (!OVR_SUCCESS(ovrHmd_ConfigureTracking(_hmd, + ovrTrackingCap_Orientation | ovrTrackingCap_Position | ovrTrackingCap_MagYawCorrection, 0))) { + qFatal("Could not attach to sensor device"); + } +#endif +} + +void Oculus_0_6_DisplayPlugin::customizeContext() { +#if (OVR_MAJOR_VERSION == 6) + OculusBaseDisplayPlugin::customizeContext(); + + //_texture = DependencyManager::get()-> + // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); + uvec2 mirrorSize = toGlm(_window->geometry().size()); + _mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd)); + _mirrorFbo->Init(mirrorSize); + + _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); + _sceneFbo->Init(getRecommendedRenderSize()); +#endif +} + +void Oculus_0_6_DisplayPlugin::deactivate() { +#if (OVR_MAJOR_VERSION == 6) + makeCurrent(); + _sceneFbo.reset(); + _mirrorFbo.reset(); + doneCurrent(); + PerformanceTimer::setActive(false); + + OculusBaseDisplayPlugin::deactivate(); + + ovrHmd_Destroy(_hmd); + _hmd = nullptr; + ovr_Shutdown(); +#endif +} + +void Oculus_0_6_DisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { +#if (OVR_MAJOR_VERSION == 6) + using namespace oglplus; + // Need to make sure only the display plugin is responsible for + // controlling vsync + wglSwapIntervalEXT(0); + + _sceneFbo->Bound([&] { + auto size = _sceneFbo->size; + Context::Viewport(size.x, size.y); + glBindTexture(GL_TEXTURE_2D, finalTexture); + drawUnitQuad(); + }); + + ovrLayerEyeFov& sceneLayer = getSceneLayer(); + ovr_for_each_eye([&](ovrEyeType eye) { + sceneLayer.RenderPose[eye] = _eyePoses[eye]; + }); + + auto windowSize = toGlm(_window->size()); + + /* + Two alternatives for mirroring to the screen, the first is to copy our own composited + scene to the window framebuffer, before distortion. Note this only works if we're doing + ui compositing ourselves, and not relying on the Oculus SDK compositor (or we don't want + the UI visible in the output window (unlikely). This should be done before + _sceneFbo->Increment or we're be using the wrong texture + */ + //_sceneFbo->Bound(GL_READ_FRAMEBUFFER, [&] { + // glBlitFramebuffer( + // 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, + // 0, 0, windowSize.x, _mirrorFbo.y, + // GL_COLOR_BUFFER_BIT, GL_NEAREST); + //}); + + { + PerformanceTimer("OculusSubmit"); + ovrLayerHeader* layers = &sceneLayer.Header; + ovrResult result = ovrHmd_SubmitFrame(_hmd, _frameIndex, nullptr, &layers, 1); + } + _sceneFbo->Increment(); + + /* + The other alternative for mirroring is to use the Oculus mirror texture support, which + will contain the post-distorted and fully composited scene regardless of how many layers + we send. + */ + auto mirrorSize = _mirrorFbo->size; + _mirrorFbo->Bound(Framebuffer::Target::Read, [&] { + Context::BlitFramebuffer( + 0, mirrorSize.y, mirrorSize.x, 0, + 0, 0, windowSize.x, windowSize.y, + BufferSelectBit::ColorBuffer, BlitFilter::Nearest); + }); + + ++_frameIndex; +#endif +} + +// Pass input events on to the application +bool Oculus_0_6_DisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { +#if (OVR_MAJOR_VERSION == 6) + if (event->type() == QEvent::Resize) { + QResizeEvent* resizeEvent = static_cast(event); + qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height(); + auto newSize = toGlm(resizeEvent->size()); + makeCurrent(); + _mirrorFbo->Resize(newSize); + doneCurrent(); + } +#endif + return OculusBaseDisplayPlugin::eventFilter(receiver, event); +} + +/* + The swapbuffer call here is only required if we want to mirror the content to the screen. + However, it should only be done if we can reliably disable v-sync on the mirror surface, + otherwise the swapbuffer delay will interefere with the framerate of the headset +*/ +void Oculus_0_6_DisplayPlugin::finishFrame() { + swapBuffers(); + doneCurrent(); +}; + + +#if 0 +/* +An alternative way to render the UI is to pass it specifically as a composition layer to +the Oculus SDK which should technically result in higher quality. However, the SDK doesn't +have a mechanism to present the image as a sphere section, which is our desired look. +*/ +ovrLayerQuad& uiLayer = getUiLayer(); +if (nullptr == uiLayer.ColorTexture || overlaySize != _uiFbo->size) { + _uiFbo->Resize(overlaySize); + uiLayer.ColorTexture = _uiFbo->color; + uiLayer.Viewport.Size.w = overlaySize.x; + uiLayer.Viewport.Size.h = overlaySize.y; + float overlayAspect = aspect(overlaySize); + uiLayer.QuadSize.x = 1.0f; + uiLayer.QuadSize.y = 1.0f / overlayAspect; +} + +_uiFbo->Bound([&] { + Q_ASSERT(0 == glGetError()); + using namespace oglplus; + Context::Viewport(_uiFbo->size.x, _uiFbo->size.y); + glClearColor(0, 0, 0, 0); + Context::Clear().ColorBuffer(); + + _program->Bind(); + glBindTexture(GL_TEXTURE_2D, overlayTexture); + _plane->Use(); + _plane->Draw(); + Q_ASSERT(0 == glGetError()); +}); +#endif diff --git a/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h new file mode 100644 index 0000000000..0fde5e76b3 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/oculus/Oculus_0_6_DisplayPlugin.h @@ -0,0 +1,41 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "OculusBaseDisplayPlugin.h" + +#include + +class OffscreenGlCanvas; +struct SwapFramebufferWrapper; +struct MirrorFramebufferWrapper; + +using SwapFboPtr = QSharedPointer; +using MirrorFboPtr = QSharedPointer; + +class Oculus_0_6_DisplayPlugin : public OculusBaseDisplayPlugin { +public: + virtual bool isSupported() const override; + virtual const QString & getName() const override; + + virtual void activate() override; + virtual void deactivate() override; + + + virtual bool eventFilter(QObject* receiver, QEvent* event) override; + +protected: + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + virtual void customizeContext() override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; +}; + diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp new file mode 100644 index 0000000000..181546d428 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.cpp @@ -0,0 +1,197 @@ +// +// Created by Bradley Austin Davis on 2015/05/12 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "OpenVrDisplayPlugin.h" + +#if defined(Q_OS_WIN) + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "OpenVrHelpers.h" +#include "GLMHelpers.h" + +#include +Q_DECLARE_LOGGING_CATEGORY(displayplugins) +Q_LOGGING_CATEGORY(displayplugins, "hifi.displayplugins") + +const QString OpenVrDisplayPlugin::NAME("OpenVR (Vive)"); + +const QString StandingHMDSensorMode = "Standing HMD Sensor Mode"; // this probably shouldn't be hardcoded here + +const QString & OpenVrDisplayPlugin::getName() const { + return NAME; +} + +vr::IVRSystem* _hmd{ nullptr }; +int hmdRefCount = 0; +static vr::IVRCompositor* _compositor{ nullptr }; +vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; +mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; +static mat4 _sensorResetMat; +static uvec2 _windowSize; +static ivec2 _windowPosition; +static uvec2 _renderTargetSize; + +struct PerEyeData { + uvec2 _viewportOrigin; + uvec2 _viewportSize; + mat4 _projectionMatrix; + mat4 _eyeOffset; + mat4 _pose; +}; + +static PerEyeData _eyesData[2]; + + +template +void openvr_for_each_eye(F f) { + f(vr::Hmd_Eye::Eye_Left); + f(vr::Hmd_Eye::Eye_Right); +} + +mat4 toGlm(const vr::HmdMatrix44_t& m) { + return glm::transpose(glm::make_mat4(&m.m[0][0])); +} + +mat4 toGlm(const vr::HmdMatrix34_t& m) { + mat4 result = mat4( + m.m[0][0], m.m[1][0], m.m[2][0], 0.0, + m.m[0][1], m.m[1][1], m.m[2][1], 0.0, + m.m[0][2], m.m[1][2], m.m[2][2], 0.0, + m.m[0][3], m.m[1][3], m.m[2][3], 1.0f); + return result; +} + +bool OpenVrDisplayPlugin::isSupported() const { + bool success = vr::VR_IsHmdPresent(); + if (success) { + vr::HmdError eError = vr::HmdError_None; + auto hmd = vr::VR_Init(&eError); + success = (hmd != nullptr); + vr::VR_Shutdown(); + } + return success; +} + +void OpenVrDisplayPlugin::activate() { + CONTAINER->setIsOptionChecked(StandingHMDSensorMode, true); + + hmdRefCount++; + vr::HmdError eError = vr::HmdError_None; + if (!_hmd) { + _hmd = vr::VR_Init(&eError); + Q_ASSERT(eError == vr::HmdError_None); + } + Q_ASSERT(_hmd); + + _hmd->GetWindowBounds(&_windowPosition.x, &_windowPosition.y, &_windowSize.x, &_windowSize.y); + _hmd->GetRecommendedRenderTargetSize(&_renderTargetSize.x, &_renderTargetSize.y); + // Recommended render target size is per-eye, so double the X size for + // left + right eyes + _renderTargetSize.x *= 2; + openvr_for_each_eye([&](vr::Hmd_Eye eye) { + PerEyeData& eyeData = _eyesData[eye]; + _hmd->GetEyeOutputViewport(eye, + &eyeData._viewportOrigin.x, &eyeData._viewportOrigin.y, + &eyeData._viewportSize.x, &eyeData._viewportSize.y); + eyeData._projectionMatrix = toGlm(_hmd->GetProjectionMatrix(eye, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP, vr::API_OpenGL)); + eyeData._eyeOffset = toGlm(_hmd->GetEyeToHeadTransform(eye)); + }); + + + _compositor = (vr::IVRCompositor*)vr::VR_GetGenericInterface(vr::IVRCompositor_Version, &eError); + Q_ASSERT(eError == vr::HmdError_None); + Q_ASSERT(_compositor); + + _compositor->SetGraphicsDevice(vr::Compositor_DeviceType_OpenGL, NULL); + + uint32_t unSize = _compositor->GetLastError(NULL, 0); + if (unSize > 1) { + char* buffer = new char[unSize]; + _compositor->GetLastError(buffer, unSize); + printf("Compositor - %s\n", buffer); + delete[] buffer; + } + Q_ASSERT(unSize <= 1); + MainWindowOpenGLDisplayPlugin::activate(); +} + +void OpenVrDisplayPlugin::deactivate() { + CONTAINER->setIsOptionChecked(StandingHMDSensorMode, false); + + hmdRefCount--; + + if (hmdRefCount == 0 && _hmd) { + vr::VR_Shutdown(); + _hmd = nullptr; + } + _compositor = nullptr; + MainWindowOpenGLDisplayPlugin::deactivate(); +} + +uvec2 OpenVrDisplayPlugin::getRecommendedRenderSize() const { + return _renderTargetSize; +} + +mat4 OpenVrDisplayPlugin::getProjection(Eye eye, const mat4& baseProjection) const { + return _eyesData[eye]._projectionMatrix; +} + +glm::mat4 OpenVrDisplayPlugin::getModelview(Eye eye, const mat4& baseModelview) const { + return baseModelview * getEyePose(eye); +} + +void OpenVrDisplayPlugin::resetSensors() { + _sensorResetMat = glm::inverse(cancelOutRollAndPitch(_trackedDevicePoseMat4[0])); +} + +glm::mat4 OpenVrDisplayPlugin::getEyePose(Eye eye) const { + return getHeadPose() * _eyesData[eye]._eyeOffset; +} + +glm::mat4 OpenVrDisplayPlugin::getHeadPose() const { + return _trackedDevicePoseMat4[0]; +} + +void OpenVrDisplayPlugin::customizeContext() { + MainWindowOpenGLDisplayPlugin::customizeContext(); +} + +void OpenVrDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { + // Flip y-axis since GL UV coords are backwards. + static vr::Compositor_TextureBounds leftBounds{ 0, 1, 0.5f, 0 }; + static vr::Compositor_TextureBounds rightBounds{ 0.5f, 1, 1, 0 }; + _compositor->Submit(vr::Eye_Left, (void*)finalTexture, &leftBounds); + _compositor->Submit(vr::Eye_Right, (void*)finalTexture, &rightBounds); + glFinish(); +} + +void OpenVrDisplayPlugin::finishFrame() { +// swapBuffers(); + doneCurrent(); + _compositor->WaitGetPoses(_trackedDevicePose, vr::k_unMaxTrackedDeviceCount); + for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + _trackedDevicePoseMat4[i] = _sensorResetMat * toGlm(_trackedDevicePose[i].mDeviceToAbsoluteTracking); + } + openvr_for_each_eye([&](vr::Hmd_Eye eye) { + _eyesData[eye]._pose = _trackedDevicePoseMat4[0]; + }); +}; + +#endif + diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h new file mode 100644 index 0000000000..608a869341 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrDisplayPlugin.h @@ -0,0 +1,47 @@ +// +// Created by Bradley Austin Davis on 2015/06/12 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +#if defined(Q_OS_WIN) + +#include "../MainWindowOpenGLDisplayPlugin.h" + +class OpenVrDisplayPlugin : public MainWindowOpenGLDisplayPlugin { +public: + virtual bool isSupported() const override; + virtual const QString & getName() const override; + virtual bool isHmd() const override { return true; } + + virtual void activate() override; + virtual void deactivate() override; + + virtual glm::uvec2 getRecommendedRenderSize() const override; + virtual glm::uvec2 getRecommendedUiSize() const override { return uvec2(1920, 1080); } + + // Stereo specific methods + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; + virtual void resetSensors() override; + + virtual glm::mat4 getEyePose(Eye eye) const override; + virtual glm::mat4 getHeadPose() const override; + +protected: + virtual void display(GLuint finalTexture, const glm::uvec2& sceneSize) override; + virtual void customizeContext() override; + // Do not perform swap in finish + virtual void finishFrame() override; + +private: + static const QString NAME; +}; + +#endif + diff --git a/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h b/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h new file mode 100644 index 0000000000..761bef8cfc --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/openvr/OpenVrHelpers.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis on 2015/06/12 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#if defined(Q_OS_WIN) +#include +#include +#include +#include +#endif + diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp new file mode 100644 index 0000000000..7d017f714d --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.cpp @@ -0,0 +1,33 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "InterleavedStereoDisplayPlugin.h" + +#include +#include + +#include +#include +#include + +#include + +const QString InterleavedStereoDisplayPlugin::NAME("Interleaved Stereo Display"); + +const QString & InterleavedStereoDisplayPlugin::getName() const { + return NAME; +} + +InterleavedStereoDisplayPlugin::InterleavedStereoDisplayPlugin() { +} + +void InterleavedStereoDisplayPlugin::customizeContext() { + StereoDisplayPlugin::customizeContext(); + // Set up the stencil buffers? Or use a custom shader? + +} diff --git a/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h new file mode 100644 index 0000000000..1dd38efed5 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/stereo/InterleavedStereoDisplayPlugin.h @@ -0,0 +1,23 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "StereoDisplayPlugin.h" + +class InterleavedStereoDisplayPlugin : public StereoDisplayPlugin { + Q_OBJECT +public: + InterleavedStereoDisplayPlugin(); + virtual const QString & getName() const override; + + // initialize OpenGL context settings needed by the plugin + virtual void customizeContext() override; + +private: + static const QString NAME; +}; diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp new file mode 100644 index 0000000000..e348143250 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.cpp @@ -0,0 +1,28 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SideBySideStereoDisplayPlugin.h" + +#include +#include + +#include +#include +#include + +#include + +const QString SideBySideStereoDisplayPlugin::NAME("SBS Stereo Display"); + +const QString & SideBySideStereoDisplayPlugin::getName() const { + return NAME; +} + +SideBySideStereoDisplayPlugin::SideBySideStereoDisplayPlugin() { +} + diff --git a/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h new file mode 100644 index 0000000000..ead9ea7dc4 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/stereo/SideBySideStereoDisplayPlugin.h @@ -0,0 +1,19 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "StereoDisplayPlugin.h" + +class SideBySideStereoDisplayPlugin : public StereoDisplayPlugin { + Q_OBJECT +public: + SideBySideStereoDisplayPlugin(); + virtual const QString & getName() const override; +private: + static const QString NAME; +}; diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp new file mode 100644 index 0000000000..c741967328 --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.cpp @@ -0,0 +1,57 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "StereoDisplayPlugin.h" + +#include +#include + +#include +#include +#include +#include + +StereoDisplayPlugin::StereoDisplayPlugin() { +} + +bool StereoDisplayPlugin::isSupported() const { + // FIXME this should attempt to do a scan for supported 3D output + return true; +} + +// FIXME make this into a setting that can be adjusted +const float DEFAULT_IPD = 0.064f; +const float HALF_DEFAULT_IPD = DEFAULT_IPD / 2.0f; + +glm::mat4 StereoDisplayPlugin::getProjection(Eye eye, const glm::mat4& baseProjection) const { + // Refer to http://www.nvidia.com/content/gtc-2010/pdfs/2010_gtc2010.pdf on creating + // stereo projection matrices. Do NOT use "toe-in", use translation. + + float nearZ = DEFAULT_NEAR_CLIP; // near clipping plane + float screenZ = 0.25f; // screen projection plane + // FIXME verify this is the right calculation + float frustumshift = HALF_DEFAULT_IPD * nearZ / screenZ; + if (eye == Right) { + frustumshift = -frustumshift; + } + return glm::translate(baseProjection, vec3(frustumshift, 0, 0)); +} + +glm::mat4 StereoDisplayPlugin::getModelview(Eye eye, const glm::mat4& baseModelview) const { + float modelviewShift = HALF_DEFAULT_IPD; + if (eye == Left) { + modelviewShift = -modelviewShift; + } + return baseModelview * glm::translate(mat4(), vec3(modelviewShift, 0, 0)); +} + +void StereoDisplayPlugin::activate() { + WindowOpenGLDisplayPlugin::activate(); + CONTAINER->setFullscreen(qApp->primaryScreen()); + // FIXME Add menu items +} diff --git a/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h new file mode 100644 index 0000000000..bb1a4fd42c --- /dev/null +++ b/libraries/display-plugins/src/display-plugins/stereo/StereoDisplayPlugin.h @@ -0,0 +1,24 @@ +// +// Created by Bradley Austin Davis on 2015/05/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "../MainWindowOpenGLDisplayPlugin.h" + +class StereoDisplayPlugin : public MainWindowOpenGLDisplayPlugin { + Q_OBJECT +public: + StereoDisplayPlugin(); + virtual bool isStereo() const override final { return true; } + virtual bool isSupported() const override final; + + virtual void activate() override; + + virtual glm::mat4 getProjection(Eye eye, const glm::mat4& baseProjection) const override; + virtual glm::mat4 getModelview(Eye eye, const glm::mat4& baseModelview) const override; + +}; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 09cc1a698a..50f79e2ff3 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -96,7 +96,6 @@ void EntityTreeRenderer::clear() { auto scene = _viewState->getMain3DScene(); render::PendingChanges pendingChanges; - foreach(auto entity, _entitiesInScene) { entity->removeFromScene(entity, scene, pendingChanges); } diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 2b4626c2c3..63b83badb7 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -174,7 +174,6 @@ uint32_t toRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { } void RenderableParticleEffectEntityItem::updateRenderItem() { - if (!_scene) { return; } @@ -212,8 +211,7 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { } render::PendingChanges pendingChanges; - pendingChanges.updateItem(_renderItemId, [&](ParticlePayload& payload) { - + pendingChanges.updateItem(_renderItemId, [this](ParticlePayload& payload) { // update vertex buffer auto vertexBuffer = payload.getVertexBuffer(); size_t numBytes = sizeof(Vertex) * _vertices.size(); diff --git a/libraries/gpu/src/gpu/GPUConfig.h b/libraries/gpu/src/gpu/GPUConfig.h index 0aa7ce4020..45ed049b8c 100644 --- a/libraries/gpu/src/gpu/GPUConfig.h +++ b/libraries/gpu/src/gpu/GPUConfig.h @@ -12,6 +12,7 @@ #ifndef gpu__GPUConfig__ #define gpu__GPUConfig__ + #define GL_GLEXT_PROTOTYPES 1 #define GPU_CORE 1 @@ -24,6 +25,9 @@ #define GPU_FEATURE_PROFILE GPU_CORE #define GPU_INPUT_PROFILE GPU_CORE_41 +#include +#include + #elif defined(WIN32) #include #include @@ -42,4 +46,5 @@ #endif + #endif diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt new file mode 100644 index 0000000000..c3ded6c587 --- /dev/null +++ b/libraries/input-plugins/CMakeLists.txt @@ -0,0 +1,72 @@ +set(TARGET_NAME input-plugins) + +# set a default root dir for each of our optional externals if it was not passed +set(OPTIONAL_EXTERNALS "SDL2" "Sixense") +foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) + string(TOUPPER ${EXTERNAL} ${EXTERNAL}_UPPERCASE) + if (NOT ${${EXTERNAL}_UPPERCASE}_ROOT_DIR) + string(TOLOWER ${EXTERNAL} ${EXTERNAL}_LOWERCASE) + set(${${EXTERNAL}_UPPERCASE}_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/${${EXTERNAL}_LOWERCASE}") + endif () +endforeach() + +setup_hifi_library() + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +link_hifi_libraries(shared plugins gpu render-utils) + +GroupSources("src/input-plugins") + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + +if (WIN32) + add_dependency_external_projects(OpenVR) + find_package(OpenVR REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) +endif() + +#add_dependency_external_projects(Sixense) +#find_package(Sixense REQUIRED) +#target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS}) +#target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES}) + +# perform standard include and linking for found externals +foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) + + if (${${EXTERNAL}_UPPERCASE}_REQUIRED) + find_package(${EXTERNAL} REQUIRED) + else () + find_package(${EXTERNAL}) + endif () + + if (${${EXTERNAL}_UPPERCASE}_FOUND AND NOT DISABLE_${${EXTERNAL}_UPPERCASE}) + add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE}) + + # include the library directories (ignoring warnings) + if (NOT ${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS) + set(${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIR}) + endif () + + include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) + + # perform the system include hack for OS X to ignore warnings + if (APPLE) + foreach(EXTERNAL_INCLUDE_DIR ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${EXTERNAL_INCLUDE_DIR}") + endforeach() + endif () + + if (NOT ${${EXTERNAL}_UPPERCASE}_LIBRARIES) + set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY}) + endif () + + if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE") + target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES}) + elseif (APPLE AND NOT INSTALLER_BUILD) + add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\") + endif () + endif () +endforeach() \ No newline at end of file diff --git a/libraries/input-plugins/src/input-plugins/InputDevice.cpp b/libraries/input-plugins/src/input-plugins/InputDevice.cpp new file mode 100644 index 0000000000..351d5b6d1d --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/InputDevice.cpp @@ -0,0 +1,59 @@ +// +// InputDevice.cpp +// input-plugins/src/input-plugins +// +// Created by Sam Gondelman on 7/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "InputDevice.h" + +bool InputDevice::_lowVelocityFilter = false; + +const float DEFAULT_HAND_RETICLE_MOVE_SPEED = 37.5f; +float InputDevice::reticleMoveSpeed = DEFAULT_HAND_RETICLE_MOVE_SPEED; + +//Constants for getCursorPixelRangeMultiplier() +const float MIN_PIXEL_RANGE_MULT = 0.4f; +const float MAX_PIXEL_RANGE_MULT = 2.0f; +const float RANGE_MULT = (MAX_PIXEL_RANGE_MULT - MIN_PIXEL_RANGE_MULT) * 0.01f; + +//Returns a multiplier to be applied to the cursor range for the controllers +float InputDevice::getCursorPixelRangeMult() { + //scales (0,100) to (MINIMUM_PIXEL_RANGE_MULT, MAXIMUM_PIXEL_RANGE_MULT) + return InputDevice::reticleMoveSpeed * RANGE_MULT + MIN_PIXEL_RANGE_MULT; +} + +float InputDevice::getButton(int channel) const { + if (!_buttonPressedMap.empty()) { + if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { + return 1.0f; + } + else { + return 0.0f; + } + } + return 0.0f; +} + +float InputDevice::getAxis(int channel) const { + auto axis = _axisStateMap.find(channel); + if (axis != _axisStateMap.end()) { + return (*axis).second; + } + else { + return 0.0f; + } +} + +UserInputMapper::PoseValue InputDevice::getPose(int channel) const { + auto pose = _poseStateMap.find(channel); + if (pose != _poseStateMap.end()) { + return (*pose).second; + } + else { + return UserInputMapper::PoseValue(); + } +} diff --git a/libraries/input-plugins/src/input-plugins/InputDevice.h b/libraries/input-plugins/src/input-plugins/InputDevice.h new file mode 100644 index 0000000000..4dbb141832 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/InputDevice.h @@ -0,0 +1,68 @@ +// +// InputDevice.h +// input-plugins/src/input-plugins +// +// Created by Sam Gondelman on 7/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include "UserInputMapper.h" + +// Event types for each controller +const unsigned int CONTROLLER_0_EVENT = 1500U; +const unsigned int CONTROLLER_1_EVENT = 1501U; + +// NOTE: If something inherits from both InputDevice and InputPlugin, InputPlugin must go first. +// e.g. class Example : public InputPlugin, public InputDevice +// instead of class Example : public InputDevice, public InputPlugin +class InputDevice { +public: + InputDevice(const QString& name) : _name(name) {} + + typedef std::unordered_set ButtonPressedMap; + typedef std::map AxisStateMap; + typedef std::map PoseStateMap; + + // Get current state for each channel + float getButton(int channel) const; + float getAxis(int channel) const; + UserInputMapper::PoseValue getPose(int channel) const; + + virtual void registerToUserInputMapper(UserInputMapper& mapper) = 0; + virtual void assignDefaultInputMapping(UserInputMapper& mapper) = 0; + + // Update call MUST be called once per simulation loop + // It takes care of updating the action states and deltas + virtual void update(float deltaTime, bool jointsCaptured) = 0; + + virtual void focusOutEvent() = 0; + + int getDeviceID() { return _deviceID; } + + static float getCursorPixelRangeMult(); + static float getReticleMoveSpeed() { return reticleMoveSpeed; } + static void setReticleMoveSpeed(float sixenseReticleMoveSpeed) { reticleMoveSpeed = sixenseReticleMoveSpeed; } + + static bool getLowVelocityFilter() { return _lowVelocityFilter; }; + +public slots: + static void setLowVelocityFilter(bool newLowVelocityFilter) { _lowVelocityFilter = newLowVelocityFilter; }; + +protected: + int _deviceID = 0; + + QString _name; + + ButtonPressedMap _buttonPressedMap; + AxisStateMap _axisStateMap; + PoseStateMap _poseStateMap; + + static bool _lowVelocityFilter; + +private: + static float reticleMoveSpeed; +}; \ No newline at end of file diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp new file mode 100644 index 0000000000..ea1639ec66 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -0,0 +1,39 @@ +// +// InputPlugin.cpp +// input-plugins/src/input-plugins +// +// Created by Sam Gondelman on 7/13/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "InputPlugin.h" + +#include + +#include "KeyboardMouseDevice.h" +#include "SDL2Manager.h" +#include "SixenseManager.h" +#include "ViveControllerManager.h" + +// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class +InputPluginList getInputPlugins() { + InputPlugin* PLUGIN_POOL[] = { + new KeyboardMouseDevice(), + new SDL2Manager(), + new SixenseManager(), + new ViveControllerManager(), + nullptr + }; + + InputPluginList result; + for (int i = 0; PLUGIN_POOL[i]; ++i) { + InputPlugin * plugin = PLUGIN_POOL[i]; + if (plugin->isSupported()) { + plugin->init(); + result.push_back(InputPluginPointer(plugin)); + } + } + return result; +} diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.h b/libraries/input-plugins/src/input-plugins/InputPlugin.h new file mode 100644 index 0000000000..787922e04c --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.h @@ -0,0 +1,23 @@ +// +// InputPlugin.h +// input-plugins/src/input-plugins +// +// Created by Sam Gondelman on 7/13/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include + +class InputPlugin : public Plugin { +public: + virtual bool isJointController() const = 0; + + virtual void pluginFocusOutEvent() = 0; + + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) = 0; +}; + diff --git a/interface/src/devices/Joystick.cpp b/libraries/input-plugins/src/input-plugins/Joystick.cpp similarity index 95% rename from interface/src/devices/Joystick.cpp rename to libraries/input-plugins/src/input-plugins/Joystick.cpp index 8cb1c3fa6b..d0e2705e98 100644 --- a/interface/src/devices/Joystick.cpp +++ b/libraries/input-plugins/src/input-plugins/Joystick.cpp @@ -1,6 +1,6 @@ // // Joystick.cpp -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Stephen Birarda on 2014-09-23. // Copyright 2014 High Fidelity, Inc. @@ -13,8 +13,6 @@ #include -#include "Application.h" - #include "Joystick.h" const float CONTROLLER_THRESHOLD = 0.3f; @@ -23,6 +21,7 @@ const float CONTROLLER_THRESHOLD = 0.3f; const float MAX_AXIS = 32768.0f; Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) : + InputDevice(name), _sdlGameController(sdlGameController), _sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)), _instanceId(instanceId) @@ -42,7 +41,7 @@ void Joystick::closeJoystick() { #endif } -void Joystick::update() { +void Joystick::update(float deltaTime, bool jointsCaptured) { for (auto axisState : _axisStateMap) { if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { _axisStateMap[axisState.first] = 0.0f; @@ -214,26 +213,6 @@ void Joystick::assignDefaultInputMapping(UserInputMapper& mapper) { #endif } -float Joystick::getButton(int channel) const { - if (!_buttonPressedMap.empty()) { - if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { - return 1.0f; - } else { - return 0.0f; - } - } - return 0.0f; -} - -float Joystick::getAxis(int channel) const { - auto axis = _axisStateMap.find(channel); - if (axis != _axisStateMap.end()) { - return (*axis).second; - } else { - return 0.0f; - } -} - #ifdef HAVE_SDL2 UserInputMapper::Input Joystick::makeInput(SDL_GameControllerButton button) { return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); diff --git a/interface/src/devices/Joystick.h b/libraries/input-plugins/src/input-plugins/Joystick.h similarity index 71% rename from interface/src/devices/Joystick.h rename to libraries/input-plugins/src/input-plugins/Joystick.h index c546a82aaa..2ba89da052 100644 --- a/interface/src/devices/Joystick.h +++ b/libraries/input-plugins/src/input-plugins/Joystick.h @@ -1,6 +1,6 @@ // // Joystick.h -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Stephen Birarda on 2014-09-23. // Copyright 2014 High Fidelity, Inc. @@ -20,11 +20,10 @@ #undef main #endif -#include "ui/UserInputMapper.h" +#include "InputDevice.h" -class Joystick : public QObject { +class Joystick : public QObject, public InputDevice { Q_OBJECT - Q_PROPERTY(QString name READ getName) #ifdef HAVE_SDL2 @@ -44,27 +43,23 @@ public: RIGHT_SHOULDER, LEFT_SHOULDER, }; + + const QString& getName() const { return _name; } + + // Device functions + virtual void registerToUserInputMapper(UserInputMapper& mapper) override; + virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; - Joystick(); + Joystick() : InputDevice("Joystick") {} ~Joystick(); - typedef std::unordered_set ButtonPressedMap; - typedef std::map AxisStateMap; - - float getButton(int channel) const; - float getAxis(int channel) const; - #ifdef HAVE_SDL2 UserInputMapper::Input makeInput(SDL_GameControllerButton button); #endif UserInputMapper::Input makeInput(Joystick::JoystickAxisChannel axis); - void registerToUserInputMapper(UserInputMapper& mapper); - void assignDefaultInputMapping(UserInputMapper& mapper); - - void update(); - void focusOutEvent(); - #ifdef HAVE_SDL2 Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController); #endif @@ -76,27 +71,16 @@ public: void handleButtonEvent(const SDL_ControllerButtonEvent& event); #endif - const QString& getName() const { return _name; } #ifdef HAVE_SDL2 int getInstanceId() const { return _instanceId; } #endif - int getDeviceID() { return _deviceID; } - private: #ifdef HAVE_SDL2 SDL_GameController* _sdlGameController; SDL_Joystick* _sdlJoystick; SDL_JoystickID _instanceId; #endif - - QString _name; - -protected: - int _deviceID = 0; - - ButtonPressedMap _buttonPressedMap; - AxisStateMap _axisStateMap; }; #endif // hifi_Joystick_h diff --git a/interface/src/devices/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp similarity index 96% rename from interface/src/devices/KeyboardMouseDevice.cpp rename to libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 6eb675114b..36ae643a8e 100755 --- a/interface/src/devices/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -1,7 +1,6 @@ - // // KeyboardMouseDevice.cpp -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Sam Gateau on 4/27/15. // Copyright 2015 High Fidelity, Inc. @@ -11,7 +10,9 @@ // #include "KeyboardMouseDevice.h" -void KeyboardMouseDevice::update() { +const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; + +void KeyboardMouseDevice::update(float deltaTime, bool jointsCaptured) { _axisStateMap.clear(); // For touch event, we need to check that the last event is not too long ago @@ -27,7 +28,7 @@ void KeyboardMouseDevice::update() { } } -void KeyboardMouseDevice::focusOutEvent(QFocusEvent* event) { +void KeyboardMouseDevice::focusOutEvent() { _buttonPressedMap.clear(); }; @@ -159,7 +160,7 @@ void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) { // Grab the current free device ID _deviceID = mapper.getFreeDeviceID(); - auto proxy = std::make_shared("Keyboard"); + auto proxy = std::make_shared(_name); proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; proxy->getAvailabeInputs = [this] () -> QVector { @@ -282,23 +283,3 @@ void KeyboardMouseDevice::assignDefaultInputMapping(UserInputMapper& mapper) { mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(Qt::Key_T)); } -float KeyboardMouseDevice::getButton(int channel) const { - if (!_buttonPressedMap.empty()) { - if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { - return 1.0f; - } else { - return 0.0f; - } - } - return 0.0f; -} - -float KeyboardMouseDevice::getAxis(int channel) const { - auto axis = _axisStateMap.find(channel); - if (axis != _axisStateMap.end()) { - return (*axis).second; - } else { - return 0.0f; - } -} - diff --git a/interface/src/devices/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h old mode 100755 new mode 100644 similarity index 70% rename from interface/src/devices/KeyboardMouseDevice.h rename to libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index ce044b2a13..70e2ee5d34 --- a/interface/src/devices/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -1,6 +1,6 @@ // // KeyboardMouseDevice.h -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Sam Gateau on 4/27/15. // Copyright 2015 High Fidelity, Inc. @@ -14,11 +14,12 @@ #include #include -#include "ui/UserInputMapper.h" +#include "InputDevice.h" +#include "InputPlugin.h" -class KeyboardMouseDevice { +class KeyboardMouseDevice : public InputPlugin, public InputDevice { + Q_OBJECT public: - enum KeyboardChannel { KEYBOARD_FIRST = 0, KEYBOARD_LAST = 255, @@ -53,10 +54,24 @@ public: TOUCH_BUTTON_PRESS = TOUCH_AXIS_Y_NEG + 1, }; - typedef std::unordered_set ButtonPressedMap; - typedef std::map AxisStateMap; // 8 axes + KeyboardMouseDevice() : InputDevice("Keyboard") {} - void focusOutEvent(QFocusEvent* event); + // Plugin functions + virtual bool isSupported() const override { return true; } + virtual bool isJointController() const override { return false; } + const QString& getName() const { return NAME; } + + virtual void activate() override {}; + virtual void deactivate() override {}; + + virtual void pluginFocusOutEvent() override { focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override { update(deltaTime, jointsCaptured); } + + // Device functions + virtual void registerToUserInputMapper(UserInputMapper& mapper) override; + virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -70,32 +85,17 @@ public: void touchUpdateEvent(const QTouchEvent* event); void wheelEvent(QWheelEvent* event); - - // Get current state for each channels - float getButton(int channel) const; - float getAxis(int channel) const; - + // Let's make it easy for Qt because we assume we love Qt forever UserInputMapper::Input makeInput(Qt::Key code); UserInputMapper::Input makeInput(Qt::MouseButton code); UserInputMapper::Input makeInput(KeyboardMouseDevice::MouseAxisChannel axis); UserInputMapper::Input makeInput(KeyboardMouseDevice::TouchAxisChannel axis); UserInputMapper::Input makeInput(KeyboardMouseDevice::TouchButtonChannel button); - - KeyboardMouseDevice() {} - void registerToUserInputMapper(UserInputMapper& mapper); - void assignDefaultInputMapping(UserInputMapper& mapper); - - // Update call MUST be called once per simulation loop - // It takes care of updating the action states and deltas - void update(); + static const QString NAME; protected: - ButtonPressedMap _buttonPressedMap; - AxisStateMap _axisStateMap; - - int _deviceID = 0; QPoint _lastCursor; glm::vec2 _lastTouch; bool _isTouching = false; diff --git a/interface/src/devices/SDL2Manager.cpp b/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp similarity index 70% rename from interface/src/devices/SDL2Manager.cpp rename to libraries/input-plugins/src/input-plugins/SDL2Manager.cpp index e039a80b0d..28874efdb0 100644 --- a/interface/src/devices/SDL2Manager.cpp +++ b/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp @@ -1,6 +1,6 @@ // // SDL2Manager.cpp -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Sam Gondelman on 6/5/15. // Copyright 2015 High Fidelity, Inc. @@ -15,10 +15,10 @@ #include #include -#include "Application.h" - #include "SDL2Manager.h" +const QString SDL2Manager::NAME = "SDL2"; + #ifdef HAVE_SDL2 SDL_JoystickID SDL2Manager::getInstanceId(SDL_GameController* controller) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller); @@ -32,48 +32,56 @@ _openJoysticks(), #endif _isInitialized(false) { +} + +void SDL2Manager::init() { #ifdef HAVE_SDL2 bool initSuccess = (SDL_Init(SDL_INIT_GAMECONTROLLER) == 0); - + if (initSuccess) { int joystickCount = SDL_NumJoysticks(); - + for (int i = 0; i < joystickCount; i++) { SDL_GameController* controller = SDL_GameControllerOpen(i); - + if (controller) { SDL_JoystickID id = getInstanceId(controller); if (!_openJoysticks.contains(id)) { Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); _openJoysticks[id] = joystick; - joystick->registerToUserInputMapper(*Application::getUserInputMapper()); - joystick->assignDefaultInputMapping(*Application::getUserInputMapper()); + auto userInputMapper = DependencyManager::get(); + joystick->registerToUserInputMapper(*userInputMapper); + joystick->assignDefaultInputMapping(*userInputMapper); emit joystickAdded(joystick); } } } - + _isInitialized = true; - } else { + } + else { qDebug() << "Error initializing SDL2 Manager"; } #endif } -SDL2Manager::~SDL2Manager() { +void SDL2Manager::deinit() { #ifdef HAVE_SDL2 qDeleteAll(_openJoysticks); - + SDL_Quit(); #endif } -SDL2Manager* SDL2Manager::getInstance() { - static SDL2Manager sharedInstance; - return &sharedInstance; +bool SDL2Manager::isSupported() const { +#ifdef HAVE_SDL2 + return true; +#else + return false; +#endif } -void SDL2Manager::focusOutEvent() { +void SDL2Manager::pluginFocusOutEvent() { #ifdef HAVE_SDL2 for (auto joystick : _openJoysticks) { joystick->focusOutEvent(); @@ -81,11 +89,12 @@ void SDL2Manager::focusOutEvent() { #endif } -void SDL2Manager::update() { +void SDL2Manager::pluginUpdate(float deltaTime, bool jointsCaptured) { #ifdef HAVE_SDL2 if (_isInitialized) { + auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { - joystick->update(); + joystick->update(deltaTime, jointsCaptured); } PerformanceTimer perfTimer("SDL2Manager::update"); @@ -111,18 +120,6 @@ void SDL2Manager::update() { HFBackEvent backEvent(backType); qApp->sendEvent(qApp, &backEvent); - } else if (event.cbutton.button == SDL_CONTROLLER_BUTTON_A) { - // this will either start or stop a global action event - QEvent::Type actionType = (event.type == SDL_CONTROLLERBUTTONDOWN) - ? HFActionEvent::startType() - : HFActionEvent::endType(); - - // global action events fire in the center of the screen - Application* app = Application::getInstance(); - PickRay pickRay = app->getCamera()->computePickRay(app->getTrueMouseX(), - app->getTrueMouseY()); - HFActionEvent actionEvent(actionType, pickRay); - qApp->sendEvent(qApp, &actionEvent); } } else if (event.type == SDL_CONTROLLERDEVICEADDED) { @@ -132,14 +129,14 @@ void SDL2Manager::update() { if (!_openJoysticks.contains(id)) { Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); _openJoysticks[id] = joystick; - joystick->registerToUserInputMapper(*Application::getUserInputMapper()); - joystick->assignDefaultInputMapping(*Application::getUserInputMapper()); + joystick->registerToUserInputMapper(*userInputMapper); + joystick->assignDefaultInputMapping(*userInputMapper); emit joystickAdded(joystick); } } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { Joystick* joystick = _openJoysticks[event.cdevice.which]; _openJoysticks.remove(event.cdevice.which); - Application::getUserInputMapper()->removeDevice(joystick->getDeviceID()); + userInputMapper->removeDevice(joystick->getDeviceID()); emit joystickRemoved(joystick); } } diff --git a/interface/src/devices/SDL2Manager.h b/libraries/input-plugins/src/input-plugins/SDL2Manager.h similarity index 79% rename from interface/src/devices/SDL2Manager.h rename to libraries/input-plugins/src/input-plugins/SDL2Manager.h index 0a32570ee2..f017e2cc65 100644 --- a/interface/src/devices/SDL2Manager.h +++ b/libraries/input-plugins/src/input-plugins/SDL2Manager.h @@ -1,6 +1,6 @@ // // SDL2Manager.h -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Sam Gondelman on 6/5/15. // Copyright 2015 High Fidelity, Inc. @@ -16,22 +16,29 @@ #include #endif -#include "ui/UserInputMapper.h" +#include "InputPlugin.h" +#include "UserInputMapper.h" -#include "devices/Joystick.h" +#include "Joystick.h" -class SDL2Manager : public QObject { +class SDL2Manager : public InputPlugin { Q_OBJECT public: SDL2Manager(); - ~SDL2Manager(); - void focusOutEvent(); + // Plugin functions + virtual bool isSupported() const override; + virtual bool isJointController() const override { return false; } + const QString& getName() const { return NAME; } + + virtual void init() override; + virtual void deinit() override; + virtual void activate() override {}; + virtual void deactivate() override {}; - void update(); - - static SDL2Manager* getInstance(); + virtual void pluginFocusOutEvent() override; + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override; signals: void joystickAdded(Joystick* joystick); @@ -70,12 +77,11 @@ private: int buttonPressed() const { return SDL_PRESSED; } int buttonRelease() const { return SDL_RELEASED; } -#endif - -#ifdef HAVE_SDL2 + QMap _openJoysticks; #endif bool _isInitialized; + static const QString NAME; }; #endif // hifi__SDL2Manager_h diff --git a/interface/src/devices/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp similarity index 50% rename from interface/src/devices/SixenseManager.cpp rename to libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 45fb2fa1ed..ec7f1ee9bb 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -1,6 +1,6 @@ // // SixenseManager.cpp -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Andrzej Kapolka on 11/15/13. // Copyright 2013 High Fidelity, Inc. @@ -11,14 +11,20 @@ #include -#include +#include + #include #include -#include "Application.h" +#include "NumericalConstants.h" +#include #include "SixenseManager.h" #include "UserActivityLogger.h" -#include "InterfaceLogging.h" + +// TODO: This should not be here +#include +Q_DECLARE_LOGGING_CATEGORY(inputplugins) +Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") // These bits aren't used for buttons, so they can be used as masks: const unsigned int LEFT_MASK = 0; @@ -47,41 +53,89 @@ typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData #endif +const QString SixenseManager::NAME = "Sixense"; + +const QString MENU_PARENT = "Avatar"; +const QString MENU_NAME = "Sixense"; +const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; + SixenseManager& SixenseManager::getInstance() { static SixenseManager sharedInstance; return sharedInstance; } SixenseManager::SixenseManager() : + InputDevice("Hydra"), #if defined(HAVE_SIXENSE) && defined(__APPLE__) _sixenseLibrary(NULL), #endif - _isInitialized(false), - _isEnabled(true), _hydrasConnected(false) { - _triggerPressed[0] = false; - _bumperPressed[0] = false; - _oldX[0] = -1; - _oldY[0] = -1; - _triggerPressed[1] = false; - _bumperPressed[1] = false; - _oldX[1] = -1; - _oldY[1] = -1; - _prevPalms[0] = nullptr; - _prevPalms[1] = nullptr; + } -SixenseManager::~SixenseManager() { -#ifdef HAVE_SIXENSE_ +bool SixenseManager::isSupported() const { +#ifdef HAVE_SIXENSE + return true; +#else + return false; +#endif +} + +void SixenseManager::activate() { +#ifdef HAVE_SIXENSE + _calibrationState = CALIBRATION_STATE_IDLE; + // By default we assume the _neckBase (in orb frame) is as high above the orb + // as the "torso" is below it. + _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z); + + CONTAINER->addMenu(MENU_PATH); + CONTAINER->addMenuItem(MENU_PATH, TOGGLE_SMOOTH, + [this] (bool clicked) { this->setFilter(clicked); }, + true, true); - if (_isInitialized) { #ifdef __APPLE__ - SixenseBaseFunction sixenseExit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseExit"); + + if (!_sixenseLibrary) { + +#ifdef SIXENSE_LIB_FILENAME + _sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME); +#else + const QString SIXENSE_LIBRARY_NAME = "libsixense_x64"; + QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/" + + SIXENSE_LIBRARY_NAME; + + _sixenseLibrary = new QLibrary(frameworkSixenseLibrary); +#endif + } + + if (_sixenseLibrary->load()){ + qCDebug(inputplugins) << "Loaded sixense library for hydra support -" << _sixenseLibrary->fileName(); + } else { + qCDebug(inputplugins) << "Sixense library at" << _sixenseLibrary->fileName() << "failed to load." + << "Continuing without hydra support."; + return; + } + + SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit"); +#endif + sixenseInit(); +#endif +} + +void SixenseManager::deactivate() { +#ifdef HAVE_SIXENSE + CONTAINER->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH); + CONTAINER->removeMenu(MENU_PATH); + + _poseStateMap.clear(); + +#ifdef __APPLE__ + SixenseBaseFunction sixenseExit = (SixenseBaseFunction)_sixenseLibrary->resolve("sixenseExit"); #endif - sixenseExit(); - } + sixenseExit(); #ifdef __APPLE__ delete _sixenseLibrary; @@ -90,245 +144,103 @@ SixenseManager::~SixenseManager() { #endif } -void SixenseManager::initialize() { -#ifdef HAVE_SIXENSE - - if (!_isInitialized) { - _lowVelocityFilter = false; - _controllersAtBase = true; - _calibrationState = CALIBRATION_STATE_IDLE; - // By default we assume the _neckBase (in orb frame) is as high above the orb - // as the "torso" is below it. - _neckBase = glm::vec3(NECK_X, -NECK_Y, NECK_Z); - -#ifdef __APPLE__ - - if (!_sixenseLibrary) { - -#ifdef SIXENSE_LIB_FILENAME - _sixenseLibrary = new QLibrary(SIXENSE_LIB_FILENAME); -#else - const QString SIXENSE_LIBRARY_NAME = "libsixense_x64"; - QString frameworkSixenseLibrary = QCoreApplication::applicationDirPath() + "/../Frameworks/" - + SIXENSE_LIBRARY_NAME; - - _sixenseLibrary = new QLibrary(frameworkSixenseLibrary); -#endif - } - - if (_sixenseLibrary->load()){ - qCDebug(interfaceapp) << "Loaded sixense library for hydra support -" << _sixenseLibrary->fileName(); - } else { - qCDebug(interfaceapp) << "Sixense library at" << _sixenseLibrary->fileName() << "failed to load." - << "Continuing without hydra support."; - return; - } - - SixenseBaseFunction sixenseInit = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseInit"); -#endif - sixenseInit(); - - _isInitialized = true; - } - -#endif -} - void SixenseManager::setFilter(bool filter) { #ifdef HAVE_SIXENSE - - if (_isInitialized) { #ifdef __APPLE__ - SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled"); + SixenseTakeIntFunction sixenseSetFilterEnabled = (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseSetFilterEnabled"); #endif - - if (filter) { - sixenseSetFilterEnabled(1); - } else { - sixenseSetFilterEnabled(0); - } - } - + int newFilter = filter ? 1 : 0; + sixenseSetFilterEnabled(newFilter); #endif } -void SixenseManager::update(float deltaTime) { +void SixenseManager::update(float deltaTime, bool jointsCaptured) { #ifdef HAVE_SIXENSE - Hand* hand = DependencyManager::get()->getMyAvatar()->getHand(); - if (_isInitialized && _isEnabled) { - _buttonPressedMap.clear(); + _buttonPressedMap.clear(); + #ifdef __APPLE__ - SixenseBaseFunction sixenseGetNumActiveControllers = - (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers"); + SixenseBaseFunction sixenseGetNumActiveControllers = + (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers"); #endif - if (sixenseGetNumActiveControllers() == 0) { - _hydrasConnected = false; - if (_deviceID != 0) { - Application::getUserInputMapper()->removeDevice(_deviceID); - _deviceID = 0; - if (_prevPalms[0]) { - _prevPalms[0]->setActive(false); - } - if (_prevPalms[1]) { - _prevPalms[1]->setActive(false); - } - } - return; + auto userInputMapper = DependencyManager::get(); + + if (sixenseGetNumActiveControllers() == 0) { + _hydrasConnected = false; + if (_deviceID != 0) { + userInputMapper->removeDevice(_deviceID); + _deviceID = 0; + _poseStateMap.clear(); } + return; + } - PerformanceTimer perfTimer("sixense"); - if (!_hydrasConnected) { - _hydrasConnected = true; - registerToUserInputMapper(*Application::getUserInputMapper()); - getInstance().assignDefaultInputMapping(*Application::getUserInputMapper()); - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); + PerformanceTimer perfTimer("sixense"); + if (!_hydrasConnected) { + _hydrasConnected = true; + registerToUserInputMapper(*userInputMapper); + assignDefaultInputMapping(*userInputMapper); + UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); + } + +#ifdef __APPLE__ + SixenseBaseFunction sixenseGetMaxControllers = + (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers"); +#endif + + int maxControllers = sixenseGetMaxControllers(); + + // we only support two controllers + sixenseControllerData controllers[2]; + +#ifdef __APPLE__ + SixenseTakeIntFunction sixenseIsControllerEnabled = + (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled"); + + SixenseTakeIntAndSixenseControllerData sixenseGetNewestData = + (SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData"); +#endif + + int numActiveControllers = 0; + for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) { + if (!sixenseIsControllerEnabled(i)) { + continue; } + sixenseControllerData* data = controllers + numActiveControllers; + ++numActiveControllers; + sixenseGetNewestData(i, data); -#ifdef __APPLE__ - SixenseBaseFunction sixenseGetMaxControllers = - (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers"); -#endif - - int maxControllers = sixenseGetMaxControllers(); - - // we only support two controllers - sixenseControllerData controllers[2]; - -#ifdef __APPLE__ - SixenseTakeIntFunction sixenseIsControllerEnabled = - (SixenseTakeIntFunction) _sixenseLibrary->resolve("sixenseIsControllerEnabled"); - - SixenseTakeIntAndSixenseControllerData sixenseGetNewestData = - (SixenseTakeIntAndSixenseControllerData) _sixenseLibrary->resolve("sixenseGetNewestData"); -#endif - int numControllersAtBase = 0; - int numActiveControllers = 0; - for (int i = 0; i < maxControllers && numActiveControllers < 2; i++) { - if (!sixenseIsControllerEnabled(i)) { - continue; - } - sixenseControllerData* data = controllers + numActiveControllers; - ++numActiveControllers; - sixenseGetNewestData(i, data); - - // Set palm position and normal based on Hydra position/orientation - - // Either find a palm matching the sixense controller, or make a new one - PalmData* palm; - bool foundHand = false; - for (size_t j = 0; j < hand->getNumPalms(); j++) { - if (hand->getPalms()[j].getSixenseID() == data->controller_index) { - palm = &(hand->getPalms()[j]); - _prevPalms[numActiveControllers - 1] = palm; - foundHand = true; - } - } - if (!foundHand) { - PalmData newPalm(hand); - hand->getPalms().push_back(newPalm); - palm = &(hand->getPalms()[hand->getNumPalms() - 1]); - palm->setSixenseID(data->controller_index); - _prevPalms[numActiveControllers - 1] = palm; - qCDebug(interfaceapp, "Found new Sixense controller, ID %i", data->controller_index); - } - - // Disable the hands (and return to default pose) if both controllers are at base station - if (foundHand) { - palm->setActive(!_controllersAtBase); - } else { - palm->setActive(false); // if this isn't a Sixsense ID palm, always make it inactive - } - - // Read controller buttons and joystick into the hand - palm->setControllerButtons(data->buttons); - palm->setTrigger(data->trigger); - palm->setJoystick(data->joystick_x, data->joystick_y); - - // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. - glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); - position *= METERS_PER_MILLIMETER; - - // Check to see if this hand/controller is on the base - const float CONTROLLER_AT_BASE_DISTANCE = 0.075f; - if (glm::length(position) < CONTROLLER_AT_BASE_DISTANCE) { - numControllersAtBase++; - palm->setActive(false); - } else { - handleButtonEvent(data->buttons, numActiveControllers - 1); - handleAxisEvent(data->joystick_x, data->joystick_y, data->trigger, numActiveControllers - 1); - - // Emulate the mouse so we can use scripts - if (Menu::getInstance()->isOptionChecked(MenuOption::SixenseMouseInput) && !_controllersAtBase) { - emulateMouse(palm, numActiveControllers - 1); - } - } - - // Transform the measured position into body frame. - glm::vec3 neck = _neckBase; - // Zeroing y component of the "neck" effectively raises the measured position a little bit. - neck.y = 0.0f; - position = _orbRotation * (position - neck); + // NOTE: Sixense API returns pos data in millimeters but we IMMEDIATELY convert to meters. + glm::vec3 position(data->pos[0], data->pos[1], data->pos[2]); + position *= METERS_PER_MILLIMETER; + + // Check to see if this hand/controller is on the base + const float CONTROLLER_AT_BASE_DISTANCE = 0.075f; + if (glm::length(position) >= CONTROLLER_AT_BASE_DISTANCE) { + handleButtonEvent(data->buttons, numActiveControllers - 1); + handleAxisEvent(data->joystick_x, data->joystick_y, data->trigger, numActiveControllers - 1); // Rotation of Palm glm::quat rotation(data->rot_quat[3], -data->rot_quat[0], data->rot_quat[1], -data->rot_quat[2]); rotation = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)) * _orbRotation * rotation; - // Compute current velocity from position change - glm::vec3 rawVelocity; - if (deltaTime > 0.0f) { - rawVelocity = (position - palm->getRawPosition()) / deltaTime; + if (!jointsCaptured) { + handlePoseEvent(position, rotation, numActiveControllers - 1); } else { - rawVelocity = glm::vec3(0.0f); + _poseStateMap.clear(); } - palm->setRawVelocity(rawVelocity); // meters/sec - - // adjustment for hydra controllers fit into hands - float sign = (i == 0) ? -1.0f : 1.0f; - rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f)); - - // Angular Velocity of Palm - glm::quat deltaRotation = rotation * glm::inverse(palm->getRawRotation()); - glm::vec3 angularVelocity(0.0f); - float rotationAngle = glm::angle(deltaRotation); - if ((rotationAngle > EPSILON) && (deltaTime > 0.0f)) { - angularVelocity = glm::normalize(glm::axis(deltaRotation)); - angularVelocity *= (rotationAngle / deltaTime); - palm->setRawAngularVelocity(angularVelocity); - } else { - palm->setRawAngularVelocity(glm::vec3(0.0f)); - } - - if (_lowVelocityFilter) { - // Use a velocity sensitive filter to damp small motions and preserve large ones with - // no latency. - float velocityFilter = glm::clamp(1.0f - glm::length(rawVelocity), 0.0f, 1.0f); - position = palm->getRawPosition() * velocityFilter + position * (1.0f - velocityFilter); - rotation = safeMix(palm->getRawRotation(), rotation, 1.0f - velocityFilter); - palm->setRawPosition(position); - palm->setRawRotation(rotation); - } else { - palm->setRawPosition(position); - palm->setRawRotation(rotation); - } - - // Store the one fingertip in the palm structure so we can track velocity - const float FINGER_LENGTH = 0.3f; // meters - const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); - const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; - glm::vec3 oldTipPosition = palm->getTipRawPosition(); - if (deltaTime > 0.0f) { - palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime); - } else { - palm->setTipVelocity(glm::vec3(0.0f)); - } - palm->setTipPosition(newTipPosition); + } else { + _poseStateMap[(numActiveControllers - 1) == 0 ? LEFT_HAND : RIGHT_HAND] = UserInputMapper::PoseValue(); } + +// // Read controller buttons and joystick into the hand +// palm->setControllerButtons(data->buttons); +// palm->setTrigger(data->trigger); +// palm->setJoystick(data->joystick_x, data->joystick_y); + } - if (numActiveControllers == 2) { - updateCalibration(controllers); - } - _controllersAtBase = (numControllersAtBase == 2); + if (numActiveControllers == 2) { + updateCalibration(controllers); } for (auto axisState : _axisStateMap) { @@ -339,26 +251,6 @@ void SixenseManager::update(float deltaTime) { #endif // HAVE_SIXENSE } -//Constants for getCursorPixelRangeMultiplier() -const float MIN_PIXEL_RANGE_MULT = 0.4f; -const float MAX_PIXEL_RANGE_MULT = 2.0f; -const float RANGE_MULT = (MAX_PIXEL_RANGE_MULT - MIN_PIXEL_RANGE_MULT) * 0.01f; - -//Returns a multiplier to be applied to the cursor range for the controllers -float SixenseManager::getCursorPixelRangeMult() const { - //scales (0,100) to (MINIMUM_PIXEL_RANGE_MULT, MAXIMUM_PIXEL_RANGE_MULT) - return _reticleMoveSpeed * RANGE_MULT + MIN_PIXEL_RANGE_MULT; -} - -void SixenseManager::toggleSixense(bool shouldEnable) { - if (shouldEnable && !isInitialized()) { - initialize(); - setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); - setLowVelocityFilter(Menu::getInstance()->isOptionChecked(MenuOption::LowVelocityFilter)); - } - setIsEnabled(shouldEnable); -} - #ifdef HAVE_SIXENSE // the calibration sequence is: @@ -372,7 +264,8 @@ const float MINIMUM_ARM_REACH = 0.3f; // meters const float MAXIMUM_NOISE_LEVEL = 0.05f; // meters const quint64 LOCK_DURATION = USECS_PER_SECOND / 4; // time for lock to be acquired -void SixenseManager::updateCalibration(const sixenseControllerData* controllers) { +void SixenseManager::updateCalibration(void* controllersX) { + auto controllers = reinterpret_cast(controllersX); const sixenseControllerData* dataLeft = controllers; const sixenseControllerData* dataRight = controllers + 1; @@ -397,11 +290,11 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) glm::vec3 zAxis = glm::normalize(glm::cross(xAxis, yAxis)); xAxis = glm::normalize(glm::cross(yAxis, zAxis)); _orbRotation = glm::inverse(glm::quat_cast(glm::mat3(xAxis, yAxis, zAxis))); - qCDebug(interfaceapp, "succeess: sixense calibration"); + qCDebug(inputplugins, "succeess: sixense calibration"); } break; default: - qCDebug(interfaceapp, "failed: sixense calibration"); + qCDebug(inputplugins, "failed: sixense calibration"); break; } @@ -420,7 +313,7 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) if (_calibrationState == CALIBRATION_STATE_IDLE) { float reach = glm::distance(positionLeft, positionRight); if (reach > 2.0f * MINIMUM_ARM_REACH) { - qCDebug(interfaceapp, "started: sixense calibration"); + qCDebug(inputplugins, "started: sixense calibration"); _averageLeft = positionLeft; _averageRight = positionRight; _reachLeft = _averageLeft; @@ -453,7 +346,7 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) _lastDistance = 0.0f; _reachUp = 0.5f * (_reachLeft + _reachRight); _calibrationState = CALIBRATION_STATE_Y; - qCDebug(interfaceapp, "success: sixense calibration: left"); + qCDebug(inputplugins, "success: sixense calibration: left"); } } else if (_calibrationState == CALIBRATION_STATE_Y) { @@ -472,7 +365,7 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) _lastDistance = 0.0f; _lockExpiry = now + LOCK_DURATION; _calibrationState = CALIBRATION_STATE_Z; - qCDebug(interfaceapp, "success: sixense calibration: up"); + qCDebug(inputplugins, "success: sixense calibration: up"); } } } @@ -494,7 +387,7 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) if (fabsf(_lastDistance) > 0.05f * MINIMUM_ARM_REACH) { // lock has expired so clamp the data and move on _calibrationState = CALIBRATION_STATE_COMPLETE; - qCDebug(interfaceapp, "success: sixense calibration: forward"); + qCDebug(inputplugins, "success: sixense calibration: forward"); // TODO: it is theoretically possible to detect that the controllers have been // accidentally switched (left hand is holding right controller) and to swap the order. } @@ -502,124 +395,6 @@ void SixenseManager::updateCalibration(const sixenseControllerData* controllers) } } -//Injecting mouse movements and clicks -void SixenseManager::emulateMouse(PalmData* palm, int index) { - MyAvatar* avatar = DependencyManager::get()->getMyAvatar(); - QPoint pos; - - Qt::MouseButton bumperButton; - Qt::MouseButton triggerButton; - - unsigned int deviceID = index == 0 ? CONTROLLER_0_EVENT : CONTROLLER_1_EVENT; - - if (_invertButtons) { - bumperButton = Qt::LeftButton; - triggerButton = Qt::RightButton; - } else { - bumperButton = Qt::RightButton; - triggerButton = Qt::LeftButton; - } - - if (Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) { - pos = qApp->getApplicationCompositor().getPalmClickLocation(palm); - } else { - // Get directon relative to avatar orientation - glm::vec3 direction = glm::inverse(avatar->getOrientation()) * palm->getFingerDirection(); - - // Get the angles, scaled between (-0.5,0.5) - float xAngle = (atan2(direction.z, direction.x) + PI_OVER_TWO); - float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)PI_OVER_TWO)); - auto canvasSize = qApp->getCanvasSize(); - // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = canvasSize.x * getCursorPixelRangeMult(); - - pos.setX(canvasSize.x / 2.0f + cursorRange * xAngle); - pos.setY(canvasSize.y / 2.0f + cursorRange * yAngle); - - } - - //If we are off screen then we should stop processing, and if a trigger or bumper is pressed, - //we should unpress them. - if (pos.x() == INT_MAX) { - if (_bumperPressed[index]) { - QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, bumperButton, bumperButton, 0); - - qApp->mouseReleaseEvent(&mouseEvent, deviceID); - - _bumperPressed[index] = false; - } - if (_triggerPressed[index]) { - QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, triggerButton, triggerButton, 0); - - qApp->mouseReleaseEvent(&mouseEvent, deviceID); - - _triggerPressed[index] = false; - } - return; - } - - //If position has changed, emit a mouse move to the application - if (pos.x() != _oldX[index] || pos.y() != _oldY[index]) { - QMouseEvent mouseEvent(QEvent::MouseMove, pos, Qt::NoButton, Qt::NoButton, 0); - - //Only send the mouse event if the opposite left button isnt held down. - if (triggerButton == Qt::LeftButton) { - if (!_triggerPressed[(int)(!index)]) { - qApp->mouseMoveEvent(&mouseEvent, deviceID); - } - } else { - if (!_bumperPressed[(int)(!index)]) { - qApp->mouseMoveEvent(&mouseEvent, deviceID); - } - } - } - _oldX[index] = pos.x(); - _oldY[index] = pos.y(); - - - //We need separate coordinates for clicks, since we need to check if - //a magnification window was clicked on - int clickX = pos.x(); - int clickY = pos.y(); - //Set pos to the new click location, which may be the same if no magnification window is open - pos.setX(clickX); - pos.setY(clickY); - - //Check for bumper press ( Right Click ) - if (palm->getControllerButtons() & BUTTON_FWD) { - if (!_bumperPressed[index]) { - _bumperPressed[index] = true; - - QMouseEvent mouseEvent(QEvent::MouseButtonPress, pos, bumperButton, bumperButton, 0); - - qApp->mousePressEvent(&mouseEvent, deviceID); - } - } else if (_bumperPressed[index]) { - QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, bumperButton, bumperButton, 0); - - qApp->mouseReleaseEvent(&mouseEvent, deviceID); - - _bumperPressed[index] = false; - } - - //Check for trigger press ( Left Click ) - if (palm->getTrigger() == 1.0f) { - if (!_triggerPressed[index]) { - _triggerPressed[index] = true; - - QMouseEvent mouseEvent(QEvent::MouseButtonPress, pos, triggerButton, triggerButton, 0); - - qApp->mousePressEvent(&mouseEvent, deviceID); - } - } else if (_triggerPressed[index]) { - QMouseEvent mouseEvent(QEvent::MouseButtonRelease, pos, triggerButton, triggerButton, 0); - - qApp->mouseReleaseEvent(&mouseEvent, deviceID); - - _triggerPressed[index] = false; - } -} - #endif // HAVE_SIXENSE void SixenseManager::focusOutEvent() { @@ -659,13 +434,30 @@ void SixenseManager::handleButtonEvent(unsigned int buttons, int index) { } } +void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int index) { +#ifdef HAVE_SIXENSE + // Transform the measured position into body frame. + glm::vec3 neck = _neckBase; + // Set y component of the "neck" to raise the measured position a little bit. + neck.y = 0.5f; + position = _orbRotation * (position - neck); + + // adjustment for hydra controllers fit into hands + float sign = (index == 0) ? -1.0f : 1.0f; + rotation *= glm::angleAxis(sign * PI/4.0f, glm::vec3(0.0f, 0.0f, 1.0f)); + + _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); +#endif // HAVE_SIXENSE +} + void SixenseManager::registerToUserInputMapper(UserInputMapper& mapper) { // Grab the current free device ID _deviceID = mapper.getFreeDeviceID(); - auto proxy = std::make_shared("Hydra"); + auto proxy = std::make_shared(_name); proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; + proxy->getPose = [this](const UserInputMapper::Input& input, int timestamp) -> UserInputMapper::PoseValue { return this->getPose(input.getChannel()); }; proxy->getAvailabeInputs = [this] () -> QVector { QVector availableInputs; availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_0, 0), "Left Start")); @@ -739,29 +531,15 @@ void SixenseManager::assignDefaultInputMapping(UserInputMapper& mapper) { mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(BUTTON_4, 0)); mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(BUTTON_4, 1)); + + mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND)); + mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND)); + + mapper.addInputChannel(UserInputMapper::LEFT_HAND_CLICK, makeInput(BUTTON_FWD, 0)); + mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BUTTON_FWD, 1)); } -float SixenseManager::getButton(int channel) const { - if (!_buttonPressedMap.empty()) { - if (_buttonPressedMap.find(channel) != _buttonPressedMap.end()) { - return 1.0f; - } else { - return 0.0f; - } - } - return 0.0f; -} - -float SixenseManager::getAxis(int channel) const { - auto axis = _axisStateMap.find(channel); - if (axis != _axisStateMap.end()) { - return (*axis).second; - } else { - return 0.0f; - } -} - UserInputMapper::Input SixenseManager::makeInput(unsigned int button, int index) { return UserInputMapper::Input(_deviceID, button | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::BUTTON); } @@ -769,3 +547,7 @@ UserInputMapper::Input SixenseManager::makeInput(unsigned int button, int index) UserInputMapper::Input SixenseManager::makeInput(SixenseManager::JoystickAxisChannel axis, int index) { return UserInputMapper::Input(_deviceID, axis | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::AXIS); } + +UserInputMapper::Input SixenseManager::makeInput(JointChannel joint) { + return UserInputMapper::Input(_deviceID, joint, UserInputMapper::ChannelType::POSE); +} diff --git a/interface/src/devices/SixenseManager.h b/libraries/input-plugins/src/input-plugins/SixenseManager.h similarity index 54% rename from interface/src/devices/SixenseManager.h rename to libraries/input-plugins/src/input-plugins/SixenseManager.h index c27b3ca0e2..03482287d9 100644 --- a/interface/src/devices/SixenseManager.h +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.h @@ -1,6 +1,6 @@ // // SixenseManager.h -// interface/src/devices +// input-plugins/src/input-plugins // // Created by Andrzej Kapolka on 11/15/13. // Copyright 2013 High Fidelity, Inc. @@ -12,23 +12,22 @@ #ifndef hifi_SixenseManager_h #define hifi_SixenseManager_h -#include -#include - #ifdef HAVE_SIXENSE #include #include #include "sixense.h" #ifdef __APPLE__ + #include #include #endif #endif -#include "ui/UserInputMapper.h" +#include "InputPlugin.h" +#include "InputDevice.h" -class PalmData; +class QLibrary; const unsigned int BUTTON_0 = 1U << 0; // the skinny button between 1 and 2 const unsigned int BUTTON_1 = 1U << 5; @@ -38,15 +37,10 @@ const unsigned int BUTTON_4 = 1U << 4; const unsigned int BUTTON_FWD = 1U << 7; const unsigned int BUTTON_TRIGGER = 1U << 8; -// Event type that represents using the controller -const unsigned int CONTROLLER_0_EVENT = 1500U; -const unsigned int CONTROLLER_1_EVENT = 1501U; - -const float DEFAULT_SIXENSE_RETICLE_MOVE_SPEED = 37.5f; const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false; -/// Handles interaction with the Sixense SDK (e.g., Razer Hydra). -class SixenseManager : public QObject { +// Handles interaction with the Sixense SDK (e.g., Razer Hydra). +class SixenseManager : public InputPlugin, public InputDevice { Q_OBJECT public: enum JoystickAxisChannel { @@ -57,51 +51,49 @@ public: BACK_TRIGGER = 1U << 6, }; + enum JointChannel { + LEFT_HAND = 0, + RIGHT_HAND, + }; + + SixenseManager(); + static SixenseManager& getInstance(); - - void initialize(); - bool isInitialized() const { return _isInitialized; } - - void setIsEnabled(bool isEnabled) { _isEnabled = isEnabled; } - - void update(float deltaTime); - float getCursorPixelRangeMult() const; - - float getReticleMoveSpeed() const { return _reticleMoveSpeed; } - void setReticleMoveSpeed(float sixenseReticleMoveSpeed) { _reticleMoveSpeed = sixenseReticleMoveSpeed; } + + // Plugin functions + virtual bool isSupported() const override; + virtual bool isJointController() const override { return true; } + const QString& getName() const { return NAME; } + + virtual void activate() override; + virtual void deactivate() override; + + virtual void pluginFocusOutEvent() override { focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override { update(deltaTime, jointsCaptured); } + + // Device functions + virtual void registerToUserInputMapper(UserInputMapper& mapper) override; + virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + bool getInvertButtons() const { return _invertButtons; } void setInvertButtons(bool invertSixenseButtons) { _invertButtons = invertSixenseButtons; } - typedef std::unordered_set ButtonPressedMap; - typedef std::map AxisStateMap; - - float getButton(int channel) const; - float getAxis(int channel) const; - UserInputMapper::Input makeInput(unsigned int button, int index); UserInputMapper::Input makeInput(JoystickAxisChannel axis, int index); - - void registerToUserInputMapper(UserInputMapper& mapper); - void assignDefaultInputMapping(UserInputMapper& mapper); - - void update(); - void focusOutEvent(); - -public slots: - void toggleSixense(bool shouldEnable); - void setFilter(bool filter); - void setLowVelocityFilter(bool lowVelocityFilter) { _lowVelocityFilter = lowVelocityFilter; }; + UserInputMapper::Input makeInput(JointChannel joint); -private: - SixenseManager(); - ~SixenseManager(); - +public slots: + void setFilter(bool filter); + +private: void handleButtonEvent(unsigned int buttons, int index); void handleAxisEvent(float x, float y, float trigger, int index); -#ifdef HAVE_SIXENSE - void updateCalibration(const sixenseControllerData* controllers); - void emulateMouse(PalmData* palm, int index); + void handlePoseEvent(glm::vec3 position, glm::quat rotation, int index); + void updateCalibration(void* controllers); + int _calibrationState; // these are calibration results @@ -123,29 +115,11 @@ private: QLibrary* _sixenseLibrary; #endif -#endif - bool _isInitialized; - bool _isEnabled; bool _hydrasConnected; - // for mouse emulation with the two controllers - bool _triggerPressed[2]; - bool _bumperPressed[2]; - int _oldX[2]; - int _oldY[2]; - PalmData* _prevPalms[2]; - - bool _lowVelocityFilter; - bool _controllersAtBase; - - float _reticleMoveSpeed = DEFAULT_SIXENSE_RETICLE_MOVE_SPEED; bool _invertButtons = DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS; -protected: - int _deviceID = 0; - - ButtonPressedMap _buttonPressedMap; - AxisStateMap _axisStateMap; + static const QString NAME; }; #endif // hifi_SixenseManager_h diff --git a/interface/src/ui/UserInputMapper.cpp b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp similarity index 91% rename from interface/src/ui/UserInputMapper.cpp rename to libraries/input-plugins/src/input-plugins/UserInputMapper.cpp index d42498c9a9..f6973cb31e 100755 --- a/interface/src/ui/UserInputMapper.cpp +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp @@ -1,6 +1,6 @@ // // UserInputMapper.cpp -// interface/src/ui +// input-plugins/src/input-plugins // // Created by Sam Gateau on 4/27/15. // Copyright 2015 High Fidelity, Inc. @@ -8,15 +8,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - -#include "Application.h" #include "UserInputMapper.h" - -// UserInputMapper Class - // Default contruct allocate the poutput size with the current hardcoded action channels UserInputMapper::UserInputMapper() { assignDefaulActionScales(); @@ -158,6 +152,10 @@ void UserInputMapper::update(float deltaTime) { for (auto& channel : _actionStates) { channel = 0.0f; } + + for (auto& channel : _poseStates) { + channel = PoseValue(); + } int currentTimestamp = 0; @@ -193,8 +191,10 @@ void UserInputMapper::update(float deltaTime) { _actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp); break; } - case ChannelType::JOINT: { - // _channelStates[channelInput.first].jointVal = deviceProxy->getJoint(inputID, currentTimestamp); + case ChannelType::POSE: { + if (!_poseStates[channelInput.first].isValid()) { + _poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp); + } break; } default: { @@ -211,8 +211,9 @@ void UserInputMapper::update(float deltaTime) { for (auto i = 0; i < NUM_ACTIONS; i++) { _actionStates[i] *= _actionScales[i]; if (_actionStates[i] > 0) { - emit Application::getInstance()->getControllerScriptingInterface()->actionEvent(i, _actionStates[i]); + emit actionEvent(i, _actionStates[i]); } + // TODO: emit signal for pose changes } } @@ -247,6 +248,10 @@ void UserInputMapper::assignDefaulActionScales() { _actionScales[PITCH_UP] = 1.0f; // 1 degree per unit _actionScales[BOOM_IN] = 0.5f; // .5m per unit _actionScales[BOOM_OUT] = 0.5f; // .5m per unit + _actionScales[LEFT_HAND] = 1.0f; // default + _actionScales[RIGHT_HAND] = 1.0f; // default + _actionScales[LEFT_HAND_CLICK] = 1.0f; // on + _actionScales[RIGHT_HAND_CLICK] = 1.0f; // on _actionStates[SHIFT] = 1.0f; // on _actionStates[ACTION1] = 1.0f; // default _actionStates[ACTION2] = 1.0f; // default @@ -267,6 +272,10 @@ void UserInputMapper::createActionNames() { _actionNames[PITCH_UP] = "PITCH_UP"; _actionNames[BOOM_IN] = "BOOM_IN"; _actionNames[BOOM_OUT] = "BOOM_OUT"; + _actionNames[LEFT_HAND] = "LEFT_HAND"; + _actionNames[RIGHT_HAND] = "RIGHT_HAND"; + _actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK"; + _actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK"; _actionNames[SHIFT] = "SHIFT"; _actionNames[ACTION1] = "ACTION1"; _actionNames[ACTION2] = "ACTION2"; diff --git a/interface/src/ui/UserInputMapper.h b/libraries/input-plugins/src/input-plugins/UserInputMapper.h similarity index 84% rename from interface/src/ui/UserInputMapper.h rename to libraries/input-plugins/src/input-plugins/UserInputMapper.h index 800f181dcb..fca19af3d7 100755 --- a/interface/src/ui/UserInputMapper.h +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.h @@ -1,6 +1,6 @@ // // UserInputMapper.h -// interface/src/ui +// input-plugins/src/input-plugins // // Created by Sam Gateau on 4/27/15. // Copyright 2015 High Fidelity, Inc. @@ -13,14 +13,17 @@ #define hifi_UserInputMapper_h #include -#include #include #include #include +#include +#include + -class UserInputMapper : public QObject { +class UserInputMapper : public QObject, public Dependency { Q_OBJECT + SINGLETON_DEPENDENCY Q_ENUMS(Action) public: typedef unsigned short uint16; @@ -30,7 +33,7 @@ public: UNKNOWN = 0, BUTTON = 1, AXIS, - JOINT, + POSE, }; // Input is the unique identifier to find a n input channel of a particular device @@ -61,7 +64,7 @@ public: bool isButton() const { return getType() == ChannelType::BUTTON; } bool isAxis() const { return getType() == ChannelType::AXIS; } - bool isJoint() const { return getType() == ChannelType::JOINT; } + bool isPose() const { return getType() == ChannelType::POSE; } // WORKAROUND: the explicit initializer here avoids a bug in GCC-4.8.2 (but not found in 4.9.2) // where the default initializer (a C++-11ism) for the union data above is not applied. @@ -77,19 +80,26 @@ public: // Modifiers are just button inputID typedef std::vector< Input > Modifiers; - class JointValue { + class PoseValue { public: - glm::vec3 translation{ 0.0f }; - glm::quat rotation; + glm::vec3 _translation{ 0.0f }; + glm::quat _rotation; + bool _valid; - JointValue() {}; - JointValue(const JointValue&) = default; - JointValue& operator = (const JointValue&) = default; + PoseValue() : _valid(false) {}; + PoseValue(glm::vec3 translation, glm::quat rotation) : _translation(translation), _rotation(rotation), _valid(true) {} + PoseValue(const PoseValue&) = default; + PoseValue& operator = (const PoseValue&) = default; + bool operator ==(const PoseValue& right) const { return _translation == right.getTranslation() && _rotation == right.getRotation() && _valid == right.isValid(); } + + bool isValid() const { return _valid; } + glm::vec3 getTranslation() const { return _translation; } + glm::quat getRotation() const { return _rotation; } }; typedef std::function ButtonGetter; typedef std::function AxisGetter; - typedef std::function JointGetter; + typedef std::function PoseGetter; typedef QPair InputPair; typedef std::function ()> AvailableInputGetter; typedef std::function ResetBindings; @@ -102,8 +112,8 @@ public: QString _name; ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; - AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; }; - JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); }; + AxisGetter getAxis = [] (const Input& input, int timestamp) -> float { return 0.0f; }; + PoseGetter getPose = [] (const Input& input, int timestamp) -> PoseValue { return PoseValue(); }; AvailableInputGetter getAvailabeInputs = [] () -> AvailableInput { return QVector(); }; ResetBindings resetDeviceBindings = [] () -> bool { return true; }; @@ -140,6 +150,12 @@ public: BOOM_IN, BOOM_OUT, + LEFT_HAND, + RIGHT_HAND, + + LEFT_HAND_CLICK, + RIGHT_HAND_CLICK, + SHIFT, ACTION1, @@ -154,6 +170,7 @@ public: QVector getAllActions(); QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; } float getActionState(Action action) const { return _actionStates[action]; } + PoseValue getPoseState(Action action) const { return _poseStates[action]; } void assignDefaulActionScales(); // Add input channel to the mapper and check that all the used channels are registered. @@ -206,8 +223,15 @@ public: // Update means go grab all the device input channels and update the output channel values void update(float deltaTime); + void setSensorToWorldMat(glm::mat4 sensorToWorldMat) { _sensorToWorldMat = sensorToWorldMat; } + glm::mat4 getSensorToWorldMat() { return _sensorToWorldMat; } + UserInputMapper(); +signals: + void actionEvent(int action, float state); + + protected: typedef std::map DevicesMap; DevicesMap _registeredDevices; @@ -221,6 +245,9 @@ protected: std::vector _actionStates = std::vector(NUM_ACTIONS, 0.0f); std::vector _actionScales = std::vector(NUM_ACTIONS, 1.0f); + std::vector _poseStates = std::vector(NUM_ACTIONS); + + glm::mat4 _sensorToWorldMat; }; Q_DECLARE_METATYPE(UserInputMapper::InputPair) diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp new file mode 100644 index 0000000000..dbcaa05143 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -0,0 +1,415 @@ +// +// ViveControllerManager.cpp +// input-plugins/src/input-plugins +// +// Created by Sam Gondelman on 6/29/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ViveControllerManager.h" + +#include + +#include "GeometryCache.h" +#include +#include +#include +#include +#include "NumericalConstants.h" +#include +#include "UserActivityLogger.h" + +#ifdef Q_OS_WIN +extern vr::IVRSystem* _hmd; +extern int hmdRefCount; +extern vr::TrackedDevicePose_t _trackedDevicePose[vr::k_unMaxTrackedDeviceCount]; +extern mat4 _trackedDevicePoseMat4[vr::k_unMaxTrackedDeviceCount]; +#endif + +const unsigned int LEFT_MASK = 0U; +const unsigned int RIGHT_MASK = 1U; + +const uint64_t VR_BUTTON_A = 1U << 1; +const uint64_t VR_GRIP_BUTTON = 1U << 2; +const uint64_t VR_TRACKPAD_BUTTON = 1ULL << 32; +const uint64_t VR_TRIGGER_BUTTON = 1ULL << 33; + +const unsigned int BUTTON_A = 1U << 1; +const unsigned int GRIP_BUTTON = 1U << 2; +const unsigned int TRACKPAD_BUTTON = 1U << 3; +const unsigned int TRIGGER_BUTTON = 1U << 4; + +const float CONTROLLER_LENGTH_OFFSET = 0.175f; +const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; + +const QString ViveControllerManager::NAME = "OpenVR"; + +const QString MENU_PARENT = "Avatar"; +const QString MENU_NAME = "Vive Controllers"; +const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; +const QString RENDER_CONTROLLERS = "Render Hand Controllers"; + +ViveControllerManager::ViveControllerManager() : + InputDevice("SteamVR Controller"), + _trackedControllers(0), + _modelLoaded(false), + _leftHandRenderID(0), + _rightHandRenderID(0), + _renderControllers(false) +{ + +} + +bool ViveControllerManager::isSupported() const { +#ifdef Q_OS_WIN + return vr::VR_IsHmdPresent(); +#else + return false; +#endif +} + +void ViveControllerManager::activate() { +#ifdef Q_OS_WIN + CONTAINER->addMenu(MENU_PATH); + CONTAINER->addMenuItem(MENU_PATH, RENDER_CONTROLLERS, + [this] (bool clicked) { this->setRenderControllers(clicked); }, + true, true); + + hmdRefCount++; + if (!_hmd) { + vr::HmdError eError = vr::HmdError_None; + _hmd = vr::VR_Init(&eError); + Q_ASSERT(eError == vr::HmdError_None); + } + Q_ASSERT(_hmd); + + vr::RenderModel_t model; + if (!_hmd->LoadRenderModel(CONTROLLER_MODEL_STRING.toStdString().c_str(), &model)) { + qDebug("Unable to load render model %s\n", CONTROLLER_MODEL_STRING); + } else { + model::Mesh* mesh = new model::Mesh(); + model::MeshPointer meshPtr(mesh); + _modelGeometry.setMesh(meshPtr); + + auto indexBuffer = new gpu::Buffer(3 * model.unTriangleCount * sizeof(uint16_t), (gpu::Byte*)model.rIndexData); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + auto indexBufferView = new gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT16, gpu::RAW)); + mesh->setIndexBuffer(*indexBufferView); + + auto vertexBuffer = new gpu::Buffer(model.unVertexCount * sizeof(vr::RenderModel_Vertex_t), + (gpu::Byte*)model.rVertexData); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + auto vertexBufferView = new gpu::BufferView(vertexBufferPtr, + 0, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(vr::RenderModel_Vertex_t), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)); + mesh->setVertexBuffer(*vertexBufferView); + mesh->addAttribute(gpu::Stream::NORMAL, + gpu::BufferView(vertexBufferPtr, + sizeof(float) * 3, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(vr::RenderModel_Vertex_t), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + //mesh->addAttribute(gpu::Stream::TEXCOORD, + // gpu::BufferView(vertexBufferPtr, + // 2 * sizeof(float) * 3, + // vertexBufferPtr->getSize() - sizeof(float) * 2, + // sizeof(vr::RenderModel_Vertex_t), + // gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::RAW))); + + gpu::Element formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + gpu::Element formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + _texture = gpu::TexturePointer( + gpu::Texture::create2D(formatGPU, model.diffuseTexture.unWidth, model.diffuseTexture.unHeight, + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, model.diffuseTexture.unWidth * model.diffuseTexture.unHeight * 4 * sizeof(uint8_t), model.diffuseTexture.rubTextureMapData); + _texture->autoGenerateMips(-1); + + _modelLoaded = true; + _renderControllers = true; + } +#endif +} + +void ViveControllerManager::deactivate() { +#ifdef Q_OS_WIN + CONTAINER->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); + CONTAINER->removeMenu(MENU_PATH); + + hmdRefCount--; + + if (hmdRefCount == 0 && _hmd) { + vr::VR_Shutdown(); + _hmd = nullptr; + } + _poseStateMap.clear(); +#endif +} + +void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges) { + PerformanceTimer perfTimer("ViveControllerManager::updateRendering"); + + if (_modelLoaded) { + //auto controllerPayload = new render::Payload(this); + //auto controllerPayloadPointer = ViveControllerManager::PayloadPointer(controllerPayload); + //if (_leftHandRenderID == 0) { + // _leftHandRenderID = scene->allocateID(); + // pendingChanges.resetItem(_leftHandRenderID, controllerPayloadPointer); + //} + //pendingChanges.updateItem(_leftHandRenderID, ); + + + UserInputMapper::PoseValue leftHand = _poseStateMap[makeInput(JointChannel::LEFT_HAND).getChannel()]; + UserInputMapper::PoseValue rightHand = _poseStateMap[makeInput(JointChannel::RIGHT_HAND).getChannel()]; + + gpu::Batch batch; + auto geometryCache = DependencyManager::get(); + geometryCache->useSimpleDrawPipeline(batch); + DependencyManager::get()->bindSimpleProgram(batch, true); + + auto mesh = _modelGeometry.getMesh(); + batch.setInputFormat(mesh->getVertexFormat()); + //batch._glBindTexture(GL_TEXTURE_2D, _uexture); + + if (leftHand.isValid()) { + renderHand(leftHand, batch, LEFT_HAND); + } + if (rightHand.isValid()) { + renderHand(rightHand, batch, RIGHT_HAND); + } + + args->_context->syncCache(); + args->_context->render(batch); + } +} + +void ViveControllerManager::renderHand(UserInputMapper::PoseValue pose, gpu::Batch& batch, int index) { + auto userInputMapper = DependencyManager::get(); + Transform transform(userInputMapper->getSensorToWorldMat()); + transform.postTranslate(pose.getTranslation() + pose.getRotation() * glm::vec3(0, 0, CONTROLLER_LENGTH_OFFSET)); + + int sign = index == LEFT_HAND ? 1.0f : -1.0f; + glm::quat rotation = pose.getRotation() * glm::angleAxis(PI, glm::vec3(1.0f, 0.0f, 0.0f)) * glm::angleAxis(sign * PI_OVER_TWO, glm::vec3(0.0f, 0.0f, 1.0f)); + transform.postRotate(rotation); + + batch.setModelTransform(transform); + + auto mesh = _modelGeometry.getMesh(); + batch.setInputBuffer(gpu::Stream::POSITION, mesh->getVertexBuffer()); + batch.setInputBuffer(gpu::Stream::NORMAL, + mesh->getVertexBuffer()._buffer, + sizeof(float) * 3, + mesh->getVertexBuffer()._stride); + //batch.setInputBuffer(gpu::Stream::TEXCOORD, + // mesh->getVertexBuffer()._buffer, + // 2 * 3 * sizeof(float), + // mesh->getVertexBuffer()._stride); + batch.setIndexBuffer(gpu::UINT16, mesh->getIndexBuffer()._buffer, 0); + batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); +} + +void ViveControllerManager::update(float deltaTime, bool jointsCaptured) { +#ifdef Q_OS_WIN + _poseStateMap.clear(); + + // TODO: This shouldn't be necessary + if (!_hmd) { + return; + } + + _buttonPressedMap.clear(); + + PerformanceTimer perfTimer("ViveControllerManager::update"); + + int numTrackedControllers = 0; + + for (vr::TrackedDeviceIndex_t device = vr::k_unTrackedDeviceIndex_Hmd + 1; + device < vr::k_unMaxTrackedDeviceCount && numTrackedControllers < 2; ++device) { + + if (!_hmd->IsTrackedDeviceConnected(device)) { + continue; + } + + if(_hmd->GetTrackedDeviceClass(device) != vr::TrackedDeviceClass_Controller) { + continue; + } + + if (!_trackedDevicePose[device].bPoseIsValid) { + continue; + } + + numTrackedControllers++; + + const mat4& mat = _trackedDevicePoseMat4[device]; + + if (!jointsCaptured) { + handlePoseEvent(mat, numTrackedControllers - 1); + } + + // handle inputs + vr::VRControllerState_t controllerState = vr::VRControllerState_t(); + if (_hmd->GetControllerState(device, &controllerState)) { + //qDebug() << (numTrackedControllers == 1 ? "Left: " : "Right: "); + //qDebug() << "Trackpad: " << controllerState.rAxis[0].x << " " << controllerState.rAxis[0].y; + //qDebug() << "Trigger: " << controllerState.rAxis[1].x << " " << controllerState.rAxis[1].y; + handleButtonEvent(controllerState.ulButtonPressed, numTrackedControllers - 1); + for (int i = 0; i < vr::k_unControllerStateAxisCount; i++) { + handleAxisEvent(Axis(i), controllerState.rAxis[i].x, controllerState.rAxis[i].y, numTrackedControllers - 1); + } + } + } + + auto userInputMapper = DependencyManager::get(); + + if (numTrackedControllers == 0) { + if (_deviceID != 0) { + userInputMapper->removeDevice(_deviceID); + _deviceID = 0; + _poseStateMap.clear(); + } + } + + if (_trackedControllers == 0 && numTrackedControllers > 0) { + registerToUserInputMapper(*userInputMapper); + assignDefaultInputMapping(*userInputMapper); + UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); + } + + _trackedControllers = numTrackedControllers; +#endif +} + +void ViveControllerManager::focusOutEvent() { + _axisStateMap.clear(); + _buttonPressedMap.clear(); +}; + +void ViveControllerManager::handleAxisEvent(Axis axis, float x, float y, int index) { + if (axis == TRACKPAD_AXIS) { + _axisStateMap[makeInput(AXIS_Y_POS, index).getChannel()] = (y > 0.0f) ? y : 0.0f; + _axisStateMap[makeInput(AXIS_Y_NEG, index).getChannel()] = (y < 0.0f) ? -y : 0.0f; + _axisStateMap[makeInput(AXIS_X_POS, index).getChannel()] = (x > 0.0f) ? x : 0.0f; + _axisStateMap[makeInput(AXIS_X_NEG, index).getChannel()] = (x < 0.0f) ? -x : 0.0f; + } else if (axis == TRIGGER_AXIS) { + _axisStateMap[makeInput(BACK_TRIGGER, index).getChannel()] = x; + } +} + +void ViveControllerManager::handleButtonEvent(uint64_t buttons, int index) { + if (buttons & VR_BUTTON_A) { + _buttonPressedMap.insert(makeInput(BUTTON_A, index).getChannel()); + } + if (buttons & VR_GRIP_BUTTON) { + _buttonPressedMap.insert(makeInput(GRIP_BUTTON, index).getChannel()); + } + if (buttons & VR_TRACKPAD_BUTTON) { + _buttonPressedMap.insert(makeInput(TRACKPAD_BUTTON, index).getChannel()); + } + if (buttons & VR_TRIGGER_BUTTON) { + _buttonPressedMap.insert(makeInput(TRIGGER_BUTTON, index).getChannel()); + } +} + +void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) { + glm::vec3 position = extractTranslation(mat); + glm::quat rotation = glm::quat_cast(mat); + + // Flip the rotation appropriately for each hand + int sign = index == LEFT_HAND ? 1.0f : -1.0f; + rotation = rotation * glm::angleAxis(PI, glm::vec3(1.0f, 0.0f, 0.0f)) * glm::angleAxis(sign * PI_OVER_TWO, glm::vec3(0.0f, 0.0f, 1.0f)); + + position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET); + + _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); +} + +void ViveControllerManager::registerToUserInputMapper(UserInputMapper& mapper) { + // Grab the current free device ID + _deviceID = mapper.getFreeDeviceID(); + + auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy(_name)); + proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; + proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; + proxy->getPose = [this](const UserInputMapper::Input& input, int timestamp) -> UserInputMapper::PoseValue { return this->getPose(input.getChannel()); }; + proxy->getAvailabeInputs = [this] () -> QVector { + QVector availableInputs; + availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_HAND), "Left Hand")); + + availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_A, 0), "Left Button A")); + availableInputs.append(UserInputMapper::InputPair(makeInput(GRIP_BUTTON, 0), "Left Grip Button")); + availableInputs.append(UserInputMapper::InputPair(makeInput(TRACKPAD_BUTTON, 0), "Left Trackpad Button")); + availableInputs.append(UserInputMapper::InputPair(makeInput(TRIGGER_BUTTON, 0), "Left Trigger Button")); + + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 0), "Left Trackpad Up")); + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 0), "Left Trackpad Down")); + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 0), "Left Trackpad Right")); + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 0), "Left Trackpad Left")); + availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 0), "Left Back Trigger")); + + + availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_HAND), "Right Hand")); + + availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_A, 1), "Right Button A")); + availableInputs.append(UserInputMapper::InputPair(makeInput(GRIP_BUTTON, 1), "Right Grip Button")); + availableInputs.append(UserInputMapper::InputPair(makeInput(TRACKPAD_BUTTON, 1), "Right Trackpad Button")); + availableInputs.append(UserInputMapper::InputPair(makeInput(TRIGGER_BUTTON, 1), "Right Trigger Button")); + + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 1), "Right Trackpad Up")); + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 1), "Right Trackpad Down")); + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 1), "Right Trackpad Right")); + availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 1), "Right Trackpad Left")); + availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 1), "Right Back Trigger")); + + return availableInputs; + }; + proxy->resetDeviceBindings = [this, &mapper] () -> bool { + mapper.removeAllInputChannelsForDevice(_deviceID); + this->assignDefaultInputMapping(mapper); + return true; + }; + mapper.registerDevice(_deviceID, proxy); +} + +void ViveControllerManager::assignDefaultInputMapping(UserInputMapper& mapper) { + const float JOYSTICK_MOVE_SPEED = 1.0f; + + // Left Trackpad: Movement, strafing + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(AXIS_Y_POS, 0), makeInput(TRACKPAD_BUTTON, 0), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(AXIS_Y_NEG, 0), makeInput(TRACKPAD_BUTTON, 0), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(AXIS_X_POS, 0), makeInput(TRACKPAD_BUTTON, 0), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(AXIS_X_NEG, 0), makeInput(TRACKPAD_BUTTON, 0), JOYSTICK_MOVE_SPEED); + + // Right Trackpad: Vertical movement, zooming + mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(AXIS_Y_POS, 1), makeInput(TRACKPAD_BUTTON, 1), JOYSTICK_MOVE_SPEED); + mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(AXIS_Y_NEG, 1), makeInput(TRACKPAD_BUTTON, 1), JOYSTICK_MOVE_SPEED); + + // Buttons + mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_A, 0)); + mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_A, 1)); + + mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(GRIP_BUTTON, 0)); + mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(GRIP_BUTTON, 1)); + + mapper.addInputChannel(UserInputMapper::LEFT_HAND_CLICK, makeInput(TRIGGER_BUTTON, 0)); + mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(TRIGGER_BUTTON, 1)); + + // Hands + mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND)); + mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND)); +} + +UserInputMapper::Input ViveControllerManager::makeInput(unsigned int button, int index) { + return UserInputMapper::Input(_deviceID, button | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::BUTTON); +} + +UserInputMapper::Input ViveControllerManager::makeInput(ViveControllerManager::JoystickAxisChannel axis, int index) { + return UserInputMapper::Input(_deviceID, axis | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::AXIS); +} + +UserInputMapper::Input ViveControllerManager::makeInput(JointChannel joint) { + return UserInputMapper::Input(_deviceID, joint, UserInputMapper::ChannelType::POSE); +} diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.h b/libraries/input-plugins/src/input-plugins/ViveControllerManager.h new file mode 100644 index 0000000000..98f32b9f35 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.h @@ -0,0 +1,99 @@ +// +// ViveControllerManager.h +// input-plugins/src/input-plugins +// +// Created by Sam Gondelman on 6/29/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi__ViveControllerManager +#define hifi__ViveControllerManager + +#include +#include + +#include + +#include +#include +#include "InputDevice.h" +#include "InputPlugin.h" +#include +#include + +class ViveControllerManager : public InputPlugin, public InputDevice { + Q_OBJECT +public: + enum JoystickAxisChannel { + AXIS_Y_POS = 1U << 1, + AXIS_Y_NEG = 1U << 2, + AXIS_X_POS = 1U << 3, + AXIS_X_NEG = 1U << 4, + BACK_TRIGGER = 1U << 5, + }; + + enum Axis { + TRACKPAD_AXIS = 0, + TRIGGER_AXIS, + AXIS_2, + AXIS_3, + AXIS_4, + }; + + enum JointChannel { + LEFT_HAND = 0, + RIGHT_HAND, + }; + + ViveControllerManager(); + + // Plugin functions + virtual bool isSupported() const override; + virtual bool isJointController() const override { return true; } + const QString& getName() const { return NAME; } + + virtual void activate() override; + virtual void deactivate() override; + + virtual void pluginFocusOutEvent() override { focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override { update(deltaTime, jointsCaptured); } + + // Device functions + virtual void registerToUserInputMapper(UserInputMapper& mapper) override; + virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + void updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges); + + void setRenderControllers(bool renderControllers) { _renderControllers = renderControllers; } + + UserInputMapper::Input makeInput(unsigned int button, int index); + UserInputMapper::Input makeInput(JoystickAxisChannel axis, int index); + UserInputMapper::Input makeInput(JointChannel joint); + +private: + void renderHand(UserInputMapper::PoseValue pose, gpu::Batch& batch, int index); + + void handleButtonEvent(uint64_t buttons, int index); + void handleAxisEvent(Axis axis, float x, float y, int index); + void handlePoseEvent(const mat4& mat, int index); + + int _trackedControllers; + + bool _modelLoaded; + model::Geometry _modelGeometry; + gpu::TexturePointer _texture; + + int _leftHandRenderID; + int _rightHandRenderID; + + bool _renderControllers; + + static const QString NAME; +}; + +#endif // hifi__ViveControllerManager diff --git a/libraries/physics/src/DynamicCharacterController.cpp b/libraries/physics/src/DynamicCharacterController.cpp index ebdba5f754..22f84eccec 100644 --- a/libraries/physics/src/DynamicCharacterController.cpp +++ b/libraries/physics/src/DynamicCharacterController.cpp @@ -7,6 +7,7 @@ #include "BulletUtil.h" #include "DynamicCharacterController.h" +#include "PhysicsLogging.h" const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f); const float DEFAULT_GRAVITY = -5.0f; @@ -413,4 +414,3 @@ void DynamicCharacterController::postSimulation() { _avatarData->setVelocity(bulletToGLM(_rigidBody->getLinearVelocity())); } } - diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt new file mode 100644 index 0000000000..28b136ccf4 --- /dev/null +++ b/libraries/plugins/CMakeLists.txt @@ -0,0 +1,12 @@ +set(TARGET_NAME plugins) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +setup_hifi_library(OpenGL) + +link_hifi_libraries(shared) + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + + diff --git a/libraries/plugins/src/plugins/Plugin.cpp b/libraries/plugins/src/plugins/Plugin.cpp new file mode 100644 index 0000000000..e0cacda474 --- /dev/null +++ b/libraries/plugins/src/plugins/Plugin.cpp @@ -0,0 +1,15 @@ +#include "Plugin.h" + +PluginContainer* Plugin::CONTAINER{ nullptr }; + +void Plugin::setContainer(PluginContainer* container) { + CONTAINER = container; +} + +bool Plugin::isSupported() const { return true; } + +void Plugin::init() {} + +void Plugin::deinit() {} + +void Plugin::idle() {} diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h new file mode 100644 index 0000000000..a2edbc8236 --- /dev/null +++ b/libraries/plugins/src/plugins/Plugin.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +class PluginContainer; + +class Plugin : public QObject { +public: + virtual const QString& getName() const = 0; + virtual bool isSupported() const; + + static void setContainer(PluginContainer* container); + + /// Called when plugin is initially loaded, typically at application start + virtual void init(); + + /// Called when application is shutting down + virtual void deinit(); + + /// Called when a plugin is being activated for use. May be called multiple times. + virtual void activate() = 0; + /// Called when a plugin is no longer being used. May be called multiple times. + virtual void deactivate() = 0; + + /** + * Called by the application during it's idle phase. If the plugin needs to do + * CPU intensive work, it should launch a thread for that, rather than trying to + * do long operations in the idle call + */ + virtual void idle(); + +protected: + static PluginContainer* CONTAINER; +}; diff --git a/libraries/plugins/src/plugins/PluginContainer.cpp b/libraries/plugins/src/plugins/PluginContainer.cpp new file mode 100644 index 0000000000..b27f076eb6 --- /dev/null +++ b/libraries/plugins/src/plugins/PluginContainer.cpp @@ -0,0 +1,15 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "PluginContainer.h" + +static PluginContainer* INSTANCE{ nullptr }; + +PluginContainer::PluginContainer() { + Q_ASSERT(!INSTANCE); + INSTANCE = this; +}; diff --git a/libraries/plugins/src/plugins/PluginContainer.h b/libraries/plugins/src/plugins/PluginContainer.h new file mode 100644 index 0000000000..85c5c814de --- /dev/null +++ b/libraries/plugins/src/plugins/PluginContainer.h @@ -0,0 +1,29 @@ +// +// Created by Bradley Austin Davis on 2015/08/08 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once + +#include +#include + +class QGLWidget; +class QScreen; + +class PluginContainer { +public: + PluginContainer(); + virtual void addMenu(const QString& menuName) = 0; + virtual void removeMenu(const QString& menuName) = 0; + virtual void addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0; + virtual void removeMenuItem(const QString& menuName, const QString& menuItem) = 0; + virtual bool isOptionChecked(const QString& name) = 0; + virtual void setIsOptionChecked(const QString& path, bool checked) = 0; + virtual void setFullscreen(const QScreen* targetScreen) = 0; + virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0; + virtual void showDisplayPluginsTools() = 0; + virtual QGLWidget* getPrimarySurface() = 0; +}; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp new file mode 100644 index 0000000000..ffee9905a0 --- /dev/null +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -0,0 +1,31 @@ +#include "PluginManager.h" +#include + + +PluginManager* PluginManager::getInstance() { + static PluginManager _manager; + return &_manager; +} + +// TODO migrate to a DLL model where plugins are discovered and loaded at runtime by the PluginManager class +extern DisplayPluginList getDisplayPlugins(); +extern InputPluginList getInputPlugins(); + +const DisplayPluginList& PluginManager::getDisplayPlugins() { + static DisplayPluginList displayPlugins; + static std::once_flag once; + std::call_once(once, [&] { + displayPlugins = ::getDisplayPlugins(); + }); + return displayPlugins; +} + +const InputPluginList& PluginManager::getInputPlugins() { + static InputPluginList inputPlugins; + static std::once_flag once; + std::call_once(once, [&] { + inputPlugins = ::getInputPlugins(); + }); + return inputPlugins; +} + diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h new file mode 100644 index 0000000000..88dba3366e --- /dev/null +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Plugin.h" +#include +#include +#include + +class DisplayPlugin; +class InputPlugin; + +using DisplayPluginPointer = QSharedPointer; +using DisplayPluginList = QVector; +using InputPluginPointer = QSharedPointer; +using InputPluginList = QVector; + +class PluginManager : public QObject { +public: + static PluginManager* getInstance(); + + const DisplayPluginList& getDisplayPlugins(); + const InputPluginList& getInputPlugins(); +}; diff --git a/libraries/render-utils/CMakeLists.txt b/libraries/render-utils/CMakeLists.txt index 357b9284fd..973e8b562a 100644 --- a/libraries/render-utils/CMakeLists.txt +++ b/libraries/render-utils/CMakeLists.txt @@ -12,6 +12,14 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) +add_dependency_external_projects(boostconfig) +find_package(BoostConfig REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${BOOSTCONFIG_INCLUDE_DIRS}) + +add_dependency_external_projects(oglplus) +find_package(OGLPLUS REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) + if (WIN32) if (USE_NSIGHT) # try to find the Nsight package and add it to the build if we find it @@ -22,14 +30,6 @@ if (WIN32) target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") endif () endif() - - add_dependency_external_projects(boostconfig) - find_package(BoostConfig REQUIRED) - target_include_directories(${TARGET_NAME} PUBLIC ${BOOSTCONFIG_INCLUDE_DIRS}) - - add_dependency_external_projects(oglplus) - find_package(OGLPLUS REQUIRED) - target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS}) endif (WIN32) link_hifi_libraries(animation fbx shared gpu model render environment) diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 53214b3d5b..a9cd7db20c 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -33,10 +33,6 @@ public: /// Returns the shadow distances for the current view state virtual const glm::vec3& getShadowDistances() const = 0; - /// Computes the off-axis frustum parameters for the view frustum, taking mirroring into account. - virtual void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, - float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const = 0; - /// gets the current view frustum for rendering the view state virtual ViewFrustum* getCurrentViewFrustum() = 0; diff --git a/libraries/render-utils/src/GlWindow.cpp b/libraries/render-utils/src/GlWindow.cpp index ec294dca5e..e97ee776dc 100644 --- a/libraries/render-utils/src/GlWindow.cpp +++ b/libraries/render-utils/src/GlWindow.cpp @@ -27,33 +27,25 @@ GlWindow::GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext) { } GlWindow::~GlWindow() { -#ifdef DEBUG - if (_logger) { - makeCurrent(); - delete _logger; - _logger = nullptr; - } -#endif _context->doneCurrent(); _context->deleteLater(); _context = nullptr; } - -void GlWindow::makeCurrent() { - _context->makeCurrent(this); -#ifdef DEBUG - if (!_logger) { - _logger = new QOpenGLDebugLogger(this); - if (_logger->initialize()) { - connect(_logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage& message) { - qDebug() << message; - }); - _logger->disableMessages(QOpenGLDebugMessage::AnySource, QOpenGLDebugMessage::AnyType, QOpenGLDebugMessage::NotificationSeverity); - _logger->startLogging(QOpenGLDebugLogger::LoggingMode::SynchronousLogging); - } - } -#endif +bool GlWindow::makeCurrent() { + bool makeCurrentResult = _context->makeCurrent(this); + Q_ASSERT(makeCurrentResult); + + std::call_once(_reportOnce, []{ + qDebug() << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); + qDebug() << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); + qDebug() << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR)); + qDebug() << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); + }); + + QOpenGLContext * currentContext = QOpenGLContext::currentContext(); + Q_ASSERT(_context == currentContext); + return makeCurrentResult; } void GlWindow::doneCurrent() { diff --git a/libraries/render-utils/src/GlWindow.h b/libraries/render-utils/src/GlWindow.h index 57108c6e37..d62d891c12 100644 --- a/libraries/render-utils/src/GlWindow.h +++ b/libraries/render-utils/src/GlWindow.h @@ -10,6 +10,7 @@ #ifndef hifi_GlWindow_h #define hifi_GlWindow_h +#include #include class QOpenGLContext; @@ -20,14 +21,12 @@ public: GlWindow(QOpenGLContext* shareContext = nullptr); GlWindow(const QSurfaceFormat& format, QOpenGLContext* shareContext = nullptr); virtual ~GlWindow(); - void makeCurrent(); + bool makeCurrent(); void doneCurrent(); void swapBuffers(); private: + std::once_flag _reportOnce; QOpenGLContext* _context{ nullptr }; -#ifdef DEBUG - QOpenGLDebugLogger* _logger{ nullptr }; -#endif }; #endif diff --git a/libraries/render-utils/src/OffscreenGlCanvas.cpp b/libraries/render-utils/src/OffscreenGlCanvas.cpp index 9154149809..246537dd0e 100644 --- a/libraries/render-utils/src/OffscreenGlCanvas.cpp +++ b/libraries/render-utils/src/OffscreenGlCanvas.cpp @@ -12,6 +12,7 @@ #include "OffscreenGlCanvas.h" +#include #include #include #include @@ -36,16 +37,7 @@ void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) { _context->setFormat(sharedContext->format()); _context->setShareContext(sharedContext); } else { - QSurfaceFormat format; - format.setDepthBufferSize(16); - format.setStencilBufferSize(8); - format.setMajorVersion(4); - format.setMinorVersion(1); - format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile); -#ifdef DEBUG - format.setOption(QSurfaceFormat::DebugContext); -#endif - _context->setFormat(format); + _context->setFormat(getDefaultOpenGlSurfaceFormat()); } _context->create(); @@ -56,6 +48,14 @@ void OffscreenGlCanvas::create(QOpenGLContext* sharedContext) { bool OffscreenGlCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); + Q_ASSERT(result); + + std::call_once(_reportOnce, []{ + qDebug() << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); + qDebug() << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); + qDebug() << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR)); + qDebug() << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); + }); #ifdef DEBUG if (result && !_logger) { diff --git a/libraries/render-utils/src/OffscreenGlCanvas.h b/libraries/render-utils/src/OffscreenGlCanvas.h index 0fa226a30d..94014adf98 100644 --- a/libraries/render-utils/src/OffscreenGlCanvas.h +++ b/libraries/render-utils/src/OffscreenGlCanvas.h @@ -12,6 +12,7 @@ #ifndef hifi_OffscreenGlCanvas_h #define hifi_OffscreenGlCanvas_h +#include #include class QOpenGLContext; @@ -30,6 +31,7 @@ public: } protected: + std::once_flag _reportOnce; QOpenGLContext* _context; QOffscreenSurface* _offscreenSurface; #ifdef DEBUG diff --git a/libraries/render-utils/src/OffscreenQmlSurface.cpp b/libraries/render-utils/src/OffscreenQmlSurface.cpp index 056f9dbc6d..d5c495b414 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.cpp +++ b/libraries/render-utils/src/OffscreenQmlSurface.cpp @@ -309,6 +309,9 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec // bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* event) { + if (_quickWindow == originalDestination) { + return false; + } // Only intercept events while we're in an active state if (_paused) { return false; @@ -414,3 +417,7 @@ void OffscreenQmlSurface::setProxyWindow(QWindow* window) { QQuickWindow* OffscreenQmlSurface::getWindow() { return _quickWindow; } + +QSize OffscreenQmlSurface::size() const { + return _quickWindow->geometry().size(); +} diff --git a/libraries/render-utils/src/OffscreenQmlSurface.h b/libraries/render-utils/src/OffscreenQmlSurface.h index 1fbf69ef4d..6eb886044b 100644 --- a/libraries/render-utils/src/OffscreenQmlSurface.h +++ b/libraries/render-utils/src/OffscreenQmlSurface.h @@ -39,6 +39,7 @@ public: void create(QOpenGLContext* context); void resize(const QSize& size); + QSize size() const; QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { return load(QUrl(qmlSourceFile), f); diff --git a/libraries/render-utils/src/OglplusHelpers.cpp b/libraries/render-utils/src/OglplusHelpers.cpp index 2205893d46..7d4a2f18bf 100644 --- a/libraries/render-utils/src/OglplusHelpers.cpp +++ b/libraries/render-utils/src/OglplusHelpers.cpp @@ -1,5 +1,3 @@ -#ifdef Q_OS_WIN - // // Created by Bradley Austin Davis on 2015/05/29 // Copyright 2015 High Fidelity, Inc. @@ -8,6 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OglplusHelpers.h" +#include using namespace oglplus; using namespace oglplus::shapes; @@ -15,16 +14,13 @@ using namespace oglplus::shapes; static const char * SIMPLE_TEXTURED_VS = R"VS(#version 410 core #pragma line __LINE__ -uniform mat4 Projection = mat4(1); -uniform mat4 ModelView = mat4(1); - -layout(location = 0) in vec3 Position; -layout(location = 1) in vec2 TexCoord; +in vec3 Position; +in vec2 TexCoord; out vec2 vTexCoord; void main() { - gl_Position = Projection * ModelView * vec4(Position, 1); + gl_Position = vec4(Position, 1); vTexCoord = TexCoord; } @@ -34,15 +30,13 @@ static const char * SIMPLE_TEXTURED_FS = R"FS(#version 410 core #pragma line __LINE__ uniform sampler2D sampler; -uniform float Alpha = 1.0; in vec2 vTexCoord; -out vec4 vFragColor; +out vec4 FragColor; void main() { - vec4 c = texture(sampler, vTexCoord); - c.a = min(Alpha, c.a); - vFragColor = c; + + FragColor = texture(sampler, vTexCoord); } )FS"; @@ -57,7 +51,7 @@ ProgramPtr loadDefaultShader() { void compileProgram(ProgramPtr & result, const std::string& vs, const std::string& fs) { using namespace oglplus; try { - result = QSharedPointer::create(); + result = std::make_shared(); // attach the shaders to the program result->AttachShader( VertexShader() @@ -72,6 +66,7 @@ void compileProgram(ProgramPtr & result, const std::string& vs, const std::strin result->Link(); } catch (ProgramBuildError & err) { Q_UNUSED(err); + qWarning() << err.Log().c_str(); Q_ASSERT_X(false, "compileProgram", "Failed to build shader program"); qFatal((const char*)err.Message); result.reset(); @@ -322,4 +317,3 @@ ShapeWrapperPtr loadSphereSection(ProgramPtr program, float fov, float aspect, i new shapes::ShapeWrapper({ "Position", "TexCoord" }, SphereSection(fov, aspect, slices, stacks), *program) ); } -#endif \ No newline at end of file diff --git a/libraries/render-utils/src/OglplusHelpers.h b/libraries/render-utils/src/OglplusHelpers.h index 47e7331ce0..569e0be7a3 100644 --- a/libraries/render-utils/src/OglplusHelpers.h +++ b/libraries/render-utils/src/OglplusHelpers.h @@ -12,33 +12,14 @@ #include -#ifdef Q_OS_WIN #include "GLMHelpers.h" #define OGLPLUS_USE_GLCOREARB_H 0 - -#if defined(__APPLE__) - -#define OGLPLUS_USE_GL3_H 1 - -#elif defined(WIN32) - #define OGLPLUS_USE_GLEW 1 -#pragma warning(disable : 4068) - -#elif defined(ANDROID) - -#else - -#define OGLPLUS_USE_GLCOREARB_H 1 - -#endif - - - #define OGLPLUS_USE_BOOST_CONFIG 1 #define OGLPLUS_NO_SITE_CONFIG 1 #define OGLPLUS_LOW_PROFILE 1 + #include #include @@ -170,4 +151,3 @@ protected: }; using BasicFramebufferWrapperPtr = std::shared_ptr; -#endif diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 4e1087d142..fb6782e011 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -184,8 +184,8 @@ void Scene::processPendingChangesQueue() { // Now we know for sure that we have enough items in the array to // capture anything coming from the pendingChanges resetItems(consolidatedPendingChanges._resetItems, consolidatedPendingChanges._resetPayloads); - removeItems(consolidatedPendingChanges._removedItems); updateItems(consolidatedPendingChanges._updatedItems, consolidatedPendingChanges._updateFunctors); + removeItems(consolidatedPendingChanges._removedItems); // ready to go back to rendering activities _itemsMutex.unlock(); diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index d5b2917369..138d3f2c20 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -313,27 +313,31 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f return (positionDistance <= similarEnough); } -glm::uvec2 toGlm(const QSize & size) { +glm::uvec2 toGlm(const QSize& size) { return glm::uvec2(size.width(), size.height()); } -glm::ivec2 toGlm(const QPoint & pt) { +glm::ivec2 toGlm(const QPoint& pt) { return glm::ivec2(pt.x(), pt.y()); } -glm::vec2 toGlm(const QPointF & pt) { +glm::vec2 toGlm(const QPointF& pt) { return glm::vec2(pt.x(), pt.y()); } -glm::vec3 toGlm(const xColor & color) { +glm::vec3 toGlm(const xColor& color) { static const float MAX_COLOR = 255.0f; return glm::vec3(color.red, color.green, color.blue) / MAX_COLOR; } -glm::vec4 toGlm(const QColor & color) { +glm::vec4 toGlm(const QColor& color) { return glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } +ivec4 toGlm(const QRect& rect) { + return ivec4(rect.x(), rect.y(), rect.width(), rect.height()); +} + QMatrix4x4 fromGlm(const glm::mat4 & m) { return QMatrix4x4(&m[0][0]).transposed(); } @@ -347,4 +351,44 @@ QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) { return result; } +// create matrix from orientation and position +glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p) { + glm::mat4 m = glm::mat4_cast(q); + m[3] = glm::vec4(p, 1); + return m; +} +// cancel out roll and pitch +glm::quat cancelOutRollAndPitch(const glm::quat& q) { + glm::vec3 xAxis = q * glm::vec3(1, 0, 0); + glm::vec3 yAxis = q * glm::vec3(0, 1, 0); + glm::vec3 zAxis = q * glm::vec3(0, 0, 1); + + // cancel out the roll and pitch + glm::vec3 newZ = (zAxis.x == 0 && zAxis.z == 0) ? vec3(1, 0, 0) : glm::normalize(vec3(zAxis.x, 0, zAxis.z)); + glm::vec3 newX = glm::cross(vec3(0, 1, 0), newZ); + glm::vec3 newY = glm::cross(newZ, newX); + + glm::mat4 temp(glm::vec4(newX, 0), glm::vec4(newY, 0), glm::vec4(newZ, 0), glm::vec4(0, 0, 0, 1)); + return glm::quat_cast(temp); +} + +// cancel out roll and pitch +glm::mat4 cancelOutRollAndPitch(const glm::mat4& m) { + glm::vec3 xAxis = glm::vec3(m[0]); + glm::vec3 yAxis = glm::vec3(m[1]); + glm::vec3 zAxis = glm::vec3(m[2]); + + // cancel out the roll and pitch + glm::vec3 newZ = (zAxis.x == 0 && zAxis.z == 0) ? vec3(1, 0, 0) : glm::normalize(vec3(zAxis.x, 0, zAxis.z)); + glm::vec3 newX = glm::cross(vec3(0, 1, 0), newZ); + glm::vec3 newY = glm::cross(newZ, newX); + + glm::mat4 temp(glm::vec4(newX, 0), glm::vec4(newY, 0), glm::vec4(newZ, 0), m[3]); + return temp; +} + +glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p) { + glm::vec4 temp = m * glm::vec4(p, 1); + return glm::vec3(temp.x / temp.w, temp.y / temp.w, temp.z / temp.w); +} diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 6874f3b391..79addbc5f1 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -18,9 +18,12 @@ #include // Bring the most commonly used GLM types into the default namespace -using glm::ivec3; using glm::ivec2; +using glm::ivec3; +using glm::ivec4; using glm::uvec2; +using glm::uvec3; +using glm::uvec4; using glm::mat3; using glm::mat4; using glm::vec2; @@ -115,11 +118,12 @@ bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientio const float POSITION_SIMILAR_ENOUGH = 0.1f; // 0.1 meter bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, float similarEnough = POSITION_SIMILAR_ENOUGH); -glm::uvec2 toGlm(const QSize & size); -glm::ivec2 toGlm(const QPoint & pt); -glm::vec2 toGlm(const QPointF & pt); -glm::vec3 toGlm(const xColor & color); -glm::vec4 toGlm(const QColor & color); +uvec2 toGlm(const QSize& size); +ivec2 toGlm(const QPoint& pt); +vec2 toGlm(const QPointF& pt); +vec3 toGlm(const xColor& color); +vec4 toGlm(const QColor& color); +ivec4 toGlm(const QRect& rect); QSize fromGlm(const glm::ivec2 & v); QMatrix4x4 fromGlm(const glm::mat4 & m); @@ -150,4 +154,9 @@ T toNormalizedDeviceScale(const T& value, const T& size) { #define PITCH(euler) euler.x #define ROLL(euler) euler.z +glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p); +glm::quat cancelOutRollAndPitch(const glm::quat& q); +glm::mat4 cancelOutRollAndPitch(const glm::mat4& m); +glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p); + #endif // hifi_GLMHelpers_h diff --git a/tests/ui/src/main.cpp b/tests/ui/src/main.cpp index f4088dd885..0efc150f93 100644 --- a/tests/ui/src/main.cpp +++ b/tests/ui/src/main.cpp @@ -81,157 +81,7 @@ class MenuConstants : public QObject{ public: enum Item { - AboutApp, - AddRemoveFriends, - AddressBar, - AlignForearmsWithWrists, - AlternateIK, - Animations, - Atmosphere, - Attachments, - AudioNoiseReduction, - AudioScope, - AudioScopeFiftyFrames, - AudioScopeFiveFrames, - AudioScopeFrames, - AudioScopePause, - AudioScopeTwentyFrames, - AudioStats, - AudioStatsShowInjectedStreams, - BandwidthDetails, - BlueSpeechSphere, - BookmarkLocation, - Bookmarks, - CascadedShadows, - CachesSize, - Chat, - Collisions, - Connexion, - Console, - ControlWithSpeech, - CopyAddress, - CopyPath, - DebugAmbientOcclusion, - DecreaseAvatarSize, - DeleteBookmark, - DisableActivityLogger, - DisableLightEntities, - DisableNackPackets, - DiskCacheEditor, - DisplayHands, - DisplayHandTargets, - DisplayModelBounds, - DisplayModelTriangles, - DisplayModelElementChildProxies, - DisplayModelElementProxy, - DisplayDebugTimingDetails, - DontDoPrecisionPicking, - DontRenderEntitiesAsScene, - EchoLocalAudio, - EchoServerAudio, - EditEntitiesHelp, - Enable3DTVMode, - EnableCharacterController, - EnableVRMode, - ExpandMyAvatarSimulateTiming, - ExpandMyAvatarTiming, - ExpandOtherAvatarTiming, - ExpandPaintGLTiming, - ExpandUpdateTiming, - Faceshift, - FilterSixense, - FirstPerson, - FrameTimer, - Fullscreen, - FullscreenMirror, - GlowWhenSpeaking, - NamesAboveHeads, - GoToUser, - HMDTools, - IncreaseAvatarSize, - KeyboardMotorControl, - LeapMotionOnHMD, - LoadScript, - LoadScriptURL, - LoadRSSDKFile, - LodTools, - Login, - Log, - LowVelocityFilter, - Mirror, - MuteAudio, - MuteEnvironment, - MuteFaceTracking, - NoFaceTracking, - NoShadows, - OctreeStats, - OffAxisProjection, - OnlyDisplayTopTen, - PackageModel, - Pair, - PipelineWarnings, - Preferences, - Quit, - ReloadAllScripts, - RenderBoundingCollisionShapes, - RenderFocusIndicator, - RenderHeadCollisionShapes, - RenderLookAtVectors, RenderLookAtTargets, - RenderSkeletonCollisionShapes, - RenderTargetFramerate, - RenderTargetFramerateUnlimited, - RenderTargetFramerate60, - RenderTargetFramerate50, - RenderTargetFramerate40, - RenderTargetFramerate30, - RenderTargetFramerateVSyncOn, - RenderResolution, - RenderResolutionOne, - RenderResolutionTwoThird, - RenderResolutionHalf, - RenderResolutionThird, - RenderResolutionQuarter, - RenderAmbientLight, - RenderAmbientLightGlobal, - RenderAmbientLight0, - RenderAmbientLight1, - RenderAmbientLight2, - RenderAmbientLight3, - RenderAmbientLight4, - RenderAmbientLight5, - RenderAmbientLight6, - RenderAmbientLight7, - RenderAmbientLight8, - RenderAmbientLight9, - ResetAvatarSize, - ResetSensors, - RunningScripts, - RunTimingTests, - ScriptEditor, - ScriptedMotorControl, - ShowBordersEntityNodes, - ShowIKConstraints, - SimpleShadows, - SixenseEnabled, - SixenseMouseInput, - SixenseLasers, - Stars, - Stats, - StereoAudio, - StopAllScripts, - SuppressShortTimings, - TestPing, - ToolWindow, - TransmitterDrive, - TurnWithHead, - UseAudioForMouth, - UseCamera, - VelocityFilter, - VisibleToEveryone, - VisibleToFriends, - VisibleToNoOne, - Wireframe, }; public: @@ -332,7 +182,6 @@ public: MessageDialog::registerType(); VrMenu::registerType(); InfoView::registerType(); - qmlRegisterType("Hifi", 1, 0, "MenuConstants"); auto offscreenUi = DependencyManager::get();