diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index e543b3aa21..5b92dfba99 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -1,12 +1,12 @@ set(TARGET_NAME assignment-client) -setup_hifi_project(Core Gui Network Script Widgets WebSockets) +setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets) # link in the shared libraries link_hifi_libraries( audio avatars octree environment gpu model fbx entities networking animation shared script-engine embedded-webserver - physics + controllers physics ) include_application_version() diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index f4f98114d0..bf5f9c3b7f 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -74,7 +74,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); // make sure we output process IDs for a child AC otherwise it's insane to parse - LogHandler::getInstance().setShouldOutputPID(true); + LogHandler::getInstance().setShouldOutputProcessID(true); // setup our _requestAssignment member variable from the passed arguments _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 58cf3c49f3..7b3d5695e1 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -44,7 +44,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME); // make sure we output process IDs for a monitor otherwise it's insane to parse - LogHandler::getInstance().setShouldOutputPID(true); + LogHandler::getInstance().setShouldOutputProcessID(true); // create a NodeList so we can receive stats from children DependencyManager::registerInheritance(); diff --git a/cmake/modules/FindiViewHMD.cmake b/cmake/modules/FindiViewHMD.cmake index f7b13f8124..e408c92380 100644 --- a/cmake/modules/FindiViewHMD.cmake +++ b/cmake/modules/FindiViewHMD.cmake @@ -21,45 +21,17 @@ if (WIN32) hifi_library_search_hints("iViewHMD") find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_path(IVIEWHMD_API_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs/x86 HINTS ${IVIEWHMD_SEARCH_DIRS}) + find_path(IVIEWHMD_API_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs/x86 HINTS ${IVIEWHMD_SEARCH_DIRS}) list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_API_DLL_PATH) - set(IVIEWHMD_DLLS - avcodec-53.dll - avformat-53.dll - avutil-51.dll - libboost_filesystem-mgw45-mt-1_49.dll - libboost_system-mgw45-mt-1_49.dll - libboost_thread-mgw45-mt-1_49.dll - libgcc_s_dw2-1.dll - libiViewNG-LibCore.dll - libopencv_calib3d244.dll - libopencv_core244.dll - libopencv_features2d244.dll - libopencv_flann244.dll - libopencv_highgui244.dll - libopencv_imgproc244.dll - libopencv_legacy244.dll - libopencv_ml244.dll - libopencv_video244.dll - libstdc++-6.dll - opencv_core220.dll - opencv_highgui220.dll - opencv_imgproc220.dll - swscale-2.dll - ) - - foreach(IVIEWHMD_DLL ${IVIEWHMD_DLLS}) - find_path(IVIEWHMD_DLL_PATH ${IVIEWHMD_DLL} PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) - list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_DLL_PATH) - list(APPEND IVIEWHMD_DLL_PATHS ${IVIEWHMD_DLL_PATH}) - endforeach() + find_path(IVIEWHMD_DLL_PATH_3RD_PARTY libiViewNG.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) + list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_DLL_PATH_3RD_PARTY) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(IVIEWHMD DEFAULT_MSG ${IVIEWHMD_REQUIREMENTS}) - add_paths_to_fixup_libs(${IVIEWHMD_API_DLL_PATH} ${IVIEWHMD_DLL_PATHS}) + add_paths_to_fixup_libs(${IVIEWHMD_API_DLL_PATH} ${IVIEWHMD_DLL_PATH_3RD_PARTY}) mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS) diff --git a/examples/clap.js b/examples/clap.js index 4ec740e3f8..9b21075ae7 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -71,8 +71,8 @@ function maybePlaySound(deltaTime) { var palm2Position = MyAvatar.getRightPalmPosition(); var distanceBetween = Vec3.length(Vec3.subtract(palm1Position, palm2Position)); - var palm1Velocity = Controller.getSpatialControlVelocity(1); - var palm2Velocity = Controller.getSpatialControlVelocity(3); + var palm1Velocity = Controller.getPoseValue(Controller.Standard.LeftHand).velocity; + var palm2Velocity = Controller.getPoseValue(Controller.Standard.RightHand).velocity; var closingVelocity = Vec3.length(Vec3.subtract(palm1Velocity, palm2Velocity)); const CLAP_SPEED = 0.7; diff --git a/examples/controllers/controllerMappings.js b/examples/controllers/controllerMappings.js new file mode 100644 index 0000000000..3848f62096 --- /dev/null +++ b/examples/controllers/controllerMappings.js @@ -0,0 +1,101 @@ + +// +// controllerScriptingExamples.js +// examples +// +// Created by Sam Gondelman on 6/2/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 +// + +// Assumes you only have the default keyboard connected + +/*myFirstMapping = function() { +return { + "name": "example", + "channels": [ + { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, + { "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" }, + + { "from": "Keyboard.Left", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.Right", "to": "Actions.LATERAL_RIGHT" }, + + { "from": "Keyboard.A", "to": "Actions.YAW_LEFT" }, + { "from": "Keyboard.D", "to": "Actions.YAW_RIGHT" }, + + { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, + { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, + { + "from": "Standard.LX", + "filters": [ { + "type": "scale", + "params": [2.0], + } + ], + "to": "Actions.LATERAL_LEFT", + }, { + "from": "Keyboard.B", + "to": "Actions.Yaw" + } + ] +} +} +*/ +mySecondMapping = function() { +return { + "name": "example2", + "channels": [ + { "from": "Standard.LY", "to": "Actions.TranslateZ" }, + { "from": "Standard.LX", "to": "Actions.Yaw" }, + ] +} +} + +//Script.include('mapping-test0.json'); +/*var myFirstMappingJSON = myFirstMapping(); +print('myFirstMappingJSON' + JSON.stringify(myFirstMappingJSON)); + +var mapping = Controller.parseMapping(JSON.stringify(myFirstMappingJSON)); + + +Controller.enableMapping("example3"); + +var mySecondMappingJSON = mySecondMapping(); +print('mySecondMappingJSON' + JSON.stringify(mySecondMappingJSON)); + +var mapping2 = Controller.parseMapping(JSON.stringify(mySecondMappingJSON)); +mapping2.enable(); + +Controller.enableMapping("example2"); +*/ +var mapping3 = Controller.loadMapping(Script.resolvePath("example3.json")); +Controller.enableMapping("example3"); + +/* +Object.keys(Controller.Standard).forEach(function (input) { + print("Controller.Standard." + input + ":" + Controller.Standard[input]); +}); + +Object.keys(Controller.Hardware).forEach(function (deviceName) { + Object.keys(Controller.Hardware[deviceName]).forEach(function (input) { + print("Controller.Hardware." + deviceName + "." + input + ":" + Controller.Hardware[deviceName][input]); + }); +}); + +Object.keys(Controller.Actions).forEach(function (actionName) { + print("Controller.Actions." + actionName + ":" + Controller.Actions[actionName]); +}); +*/ + + +Controller.hardwareChanged.connect(function () { + print("hardwareChanged ---------------------------------------------------"); + Object.keys(Controller.Hardware).forEach(function (deviceName) { + Object.keys(Controller.Hardware[deviceName]).forEach(function (input) { + print("Controller.Hardware." + deviceName + "." + input + ":" + Controller.Hardware[deviceName][input]); + }); + }); + print("-------------------------------------------------------------------"); +}); \ No newline at end of file diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 815f903bfa..60e5286ad8 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -176,6 +176,7 @@ function MyController(hand, triggerAction) { this.state = STATE_OFF; this.pointer = null; // entity-id of line object this.triggerValue = 0; // rolling average of trigger value + this.rawTriggerValue = 0; var _this = this; @@ -272,12 +273,17 @@ function MyController(hand, triggerAction) { this.pointer = null; }; - this.updateSmoothedTrigger = function() { - var triggerValue = Controller.getActionValue(this.triggerAction); + this.eitherTrigger = function (value) { + _this.rawTriggerValue = value; + }; + + this.updateSmoothedTrigger = function () { + var triggerValue = this.rawTriggerValue; // smooth out trigger value this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); - } + + }; this.triggerSmoothedSqueezed = function() { return this.triggerValue > TRIGGER_ON_VALUE; @@ -287,8 +293,8 @@ function MyController(hand, triggerAction) { return this.triggerValue < TRIGGER_OFF_VALUE; }; - this.triggerSqueezed = function() { - var triggerValue = Controller.getActionValue(this.triggerAction); + this.triggerSqueezed = function() { + var triggerValue = this.rawTriggerValue; return triggerValue > TRIGGER_ON_VALUE; }; @@ -430,12 +436,12 @@ function MyController(hand, triggerAction) { }; this.distanceHolding = function() { - - var handControllerPosition = Controller.getSpatialControlPosition(this.palm); - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation", "gravity", - "ignoreForCollisions", - "collisionsWillMove"]); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation", + "gravity", "ignoreForCollisions", + var now = Date.now(); // add the action and initialize some variables @@ -482,8 +488,9 @@ function MyController(hand, triggerAction) { } var handPosition = this.getHandPosition(); - var handControllerPosition = Controller.getSpatialControlPosition(this.palm); - var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]); this.lineOn(handPosition, Vec3.subtract(grabbedProperties.position, handPosition), INTERSECT_COLOR); @@ -633,7 +640,7 @@ function MyController(hand, triggerAction) { } - this.currentHandControllerTipPosition = Controller.getSpatialControlPosition(this.tip); + this.currentHandControllerTipPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition;; this.currentObjectTime = Date.now(); }; @@ -651,7 +658,7 @@ function MyController(hand, triggerAction) { // of it's actual offset, let's try imparting a velocity which is at a fixed radius // from the palm. - var handControllerPosition = Controller.getSpatialControlPosition(this.tip); + var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; var now = Date.now(); var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters @@ -899,8 +906,16 @@ function MyController(hand, triggerAction) { }; } -var rightController = new MyController(RIGHT_HAND, Controller.findAction("RIGHT_HAND_CLICK")); -var leftController = new MyController(LEFT_HAND, Controller.findAction("LEFT_HAND_CLICK")); +var rightController = new MyController(RIGHT_HAND, Controller.Standard.RT); +var leftController = new MyController(LEFT_HAND, Controller.Standard.LT); + +var MAPPING_NAME = "com.highfidelity.handControllerGrab"; + +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from([Controller.Standard.RB, Controller.Standard.RT]).to(rightController.eitherTrigger); +mapping.from([Controller.Standard.LB, Controller.Standard.LT]).to(leftController.eitherTrigger); +Controller.enableMapping(MAPPING_NAME); + function update() { rightController.update(); @@ -910,6 +925,7 @@ function update() { function cleanup() { rightController.cleanup(); leftController.cleanup(); + Controller.disableMapping(MAPPING_NAME); } Script.scriptEnding.connect(cleanup); diff --git a/examples/controllers/handPosesDebug.js b/examples/controllers/handPosesDebug.js new file mode 100644 index 0000000000..e770abd957 --- /dev/null +++ b/examples/controllers/handPosesDebug.js @@ -0,0 +1,102 @@ +// +// handPosesDebug.js +// examples +// +// 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 +// + + + +function makeSphere(color) { + var SPHERE_SIZE = 0.05; + var sphere = Overlays.addOverlay("sphere", { + position: { x: 0, y: 0, z: 0 }, + size: SPHERE_SIZE, + color: color, + alpha: 1.0, + solid: true, + visible: true, + }); + + return sphere; +} + + +var NUM_HANDS = 2; +var NUM_SPHERES_PER_HAND = 2; +var LEFT_HAND = 0; +var RIGHT_HAND = 1; + +var COLORS = [ { red: 255, green: 0, blue: 0 }, { red: 0, green: 0, blue: 255 } ]; + + +function index(handNum, indexNum) { + return handNum * NUM_HANDS + indexNum; +} + +var app = {}; + + +function setup() { + app.spheres = new Array(); + + for (var h = 0; h < NUM_HANDS; h++) { + for (var s = 0; s < NUM_SPHERES_PER_HAND; s++) { + var i = index(h, s); + app.spheres[i] = makeSphere(COLORS[h]); + print("Added Sphere num " + i + " = " + JSON.stringify(app.spheres[i])); + } + } +} + +function updateHand(handNum, deltaTime) { + var pose; + var handName = "right"; + if (handNum == LEFT_HAND) { + pose = MyAvatar.getLeftHandPose(); + handName = "left"; + } else { + pose = MyAvatar.getRightHandPose(); + handName = "right"; + } + + if (pose.valid) { + //print(handName + " hand moving" + JSON.stringify(pose)); + Overlays.editOverlay(app.spheres[index(handNum, 0)], { + position: pose.translation, + visible: true, + }); + var vpos = Vec3.sum(Vec3.multiply(10 * deltaTime, pose.velocity), pose.translation); + Overlays.editOverlay(app.spheres[index(handNum, 1)], { + position: vpos, + visible: true, + }); + } else { + Overlays.editOverlay(app.spheres[index(handNum, 0)], { + visible: false + }); + + Overlays.editOverlay(app.spheres[index(handNum, 1)], { + visible: false + }); + } +} + +function update(deltaTime) { + updateHand(LEFT_HAND, deltaTime); + updateHand(RIGHT_HAND, deltaTime); +} + +function scriptEnding() { + print("Removing spheres = " + JSON.stringify(app.spheres)); + for (var i = 0; i < app.spheres.length; i++) { + Overlays.deleteOverlay(app.spheres[i]); + } +} + +setup(); +Script.update.connect(update); +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/controllers/hydra/airGuitar.js b/examples/controllers/hydra/airGuitar.js index 7fbbfc48bd..f8606808c1 100644 --- a/examples/controllers/hydra/airGuitar.js +++ b/examples/controllers/hydra/airGuitar.js @@ -32,6 +32,8 @@ var guitarModel = HIFI_PUBLIC_BUCKET + "models/attachments/guitar.fst"; // Load sounds that will be played +var heyManWave = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/sounds/KenDoll_1%2303.wav"); + var chords = new Array(); // Nylon string guitar chords[1] = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guitars/Guitar+-+Nylon+A.raw"); @@ -56,97 +58,128 @@ var NUM_GUITARS = 3; var guitarSelector = NUM_CHORDS; var whichChord = 1; -var leftHanded = true; +var leftHanded = false; +var strumHand, chordHand, strumTrigger, chordTrigger; if (leftHanded) { - var strumHand = 0; - var chordHand = 1; + strumHand = Controller.Standard.LeftHand; + chordHand = Controller.Standard.RightHand; + strumTrigger = Controller.Standard.LT; + chordTrigger = Controller.Standard.RT; + changeGuitar = Controller.Standard.RB; + chord1 = Controller.Standard.X; + chord2 = Controller.Standard.Y; + chord3 = Controller.Standard.A; + chord4 = Controller.Standard.B; } else { - var strumHand = 1; - var chordHand = 0; + strumHand = Controller.Standard.RightHand; + chordHand = Controller.Standard.LeftHand; + strumTrigger = Controller.Standard.RT; + chordTrigger = Controller.Standard.LT; + changeGuitar = Controller.Standard.LB; + chord1 = Controller.Standard.DU; // these may not be correct, maybe we should map directly to Hydra?? + chord2 = Controller.Standard.DD; + chord3 = Controller.Standard.DL; + chord4 = Controller.Standard.DR; } var lastPosition = { x: 0.0, y: 0.0, z: 0.0 }; -var soundPlaying = false; +var audioInjector = null; var selectorPressed = false; var position; -MyAvatar.attach(guitarModel, "Hips", {x: -0.2, y: 0.0, z: 0.1}, Quat.fromPitchYawRollDegrees(90, 00, 90), 1.0); +MyAvatar.attach(guitarModel, "Hips", {x: leftHanded ? -0.2 : 0.2, y: 0.0, z: 0.1}, Quat.fromPitchYawRollDegrees(90, 00, leftHanded ? 75 : -75), 1.0); function checkHands(deltaTime) { - for (var palm = 0; palm < 2; palm++) { - var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); - var volume = length(palmVelocity) / 5.0; - var position = Controller.getSpatialControlPosition(palm * 2 + 1); - var myPelvis = MyAvatar.position; - var trigger = Controller.getTriggerValue(strumHand); - var chord = Controller.getTriggerValue(chordHand); + var strumVelocity = Controller.getPoseValue(strumHand).velocity; + var volume = length(strumVelocity) / 5.0; - if (volume > 1.0) volume = 1.0; - if ((chord > 0.1) && soundPlaying.isPlaying) { - // If chord finger trigger pulled, stop current chord - print("stopped sound"); - soundPlaying.stop(); + if (volume == 0.0) { + volume = 1.0; + } + + var strumHandPosition = leftHanded ? MyAvatar.leftHandPosition : MyAvatar.rightHandPosition; + var myPelvis = MyAvatar.position; + var strumming = Controller.getValue(strumTrigger); + var chord = Controller.getValue(chordTrigger); + + if (volume > 1.0) volume = 1.0; + if ((chord > 0.1) && audioInjector && audioInjector.isPlaying) { + // If chord finger trigger pulled, stop current chord + print("stopping chord because cord trigger pulled"); + audioInjector.stop(); + } + + // Change guitars if button FWD (5) pressed + if (Controller.getValue(changeGuitar)) { + if (!selectorPressed) { + print("changeGuitar:" + changeGuitar); + guitarSelector += NUM_CHORDS; + if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { + guitarSelector = 0; + } + print("new guitarBase: " + guitarSelector); + stopAudio(true); + selectorPressed = true; } + } else { + selectorPressed = false; + } - var BUTTON_COUNT = 6; + if (Controller.getValue(chord1)) { + whichChord = 1; + stopAudio(true); + } else if (Controller.getValue(chord2)) { + whichChord = 2; + stopAudio(true); + } else if (Controller.getValue(chord3)) { + whichChord = 3; + stopAudio(true); + } else if (Controller.getValue(chord4)) { + whichChord = 4; + stopAudio(true); + } - // Change guitars if button FWD (5) pressed - if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 5)) { - if (!selectorPressed) { - guitarSelector += NUM_CHORDS; - if (guitarSelector >= NUM_CHORDS * NUM_GUITARS) { - guitarSelector = 0; - } - selectorPressed = true; - } - } else { - selectorPressed = false; - } + var STRUM_HEIGHT_ABOVE_PELVIS = 0.10; + var strummingHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS; - if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 1)) { - whichChord = 1; - } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 2)) { - whichChord = 2; - } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 3)) { - whichChord = 3; - } else if (Controller.isButtonPressed(chordHand * BUTTON_COUNT + 4)) { - whichChord = 4; - } + var strumNowAbove = strumHandPosition.y > strummingHeight; + var strumNowBelow = strumHandPosition.y <= strummingHeight; + var strumWasAbove = lastPosition.y > strummingHeight; + var strumWasBelow = lastPosition.y <= strummingHeight; + var strumUp = strumNowAbove && strumWasBelow; + var strumDown = strumNowBelow && strumWasAbove; - if (palm == strumHand) { + if ((strumUp || strumDown) && (strumming > 0.1)) { + // If hand passes downward or upward through 'strings', and finger trigger pulled, play + playChord(strumHandPosition, volume); + } + lastPosition = strumHandPosition; +} - var STRUM_HEIGHT_ABOVE_PELVIS = 0.10; - var strumTriggerHeight = myPelvis.y + STRUM_HEIGHT_ABOVE_PELVIS; - //printVector(position); - if ( ( ((position.y < strumTriggerHeight) && (lastPosition.y >= strumTriggerHeight)) || - ((position.y > strumTriggerHeight) && (lastPosition.y <= strumTriggerHeight)) ) && (trigger > 0.1) ){ - // If hand passes downward or upward through 'strings', and finger trigger pulled, play - playChord(position, volume); - } - lastPosition = Controller.getSpatialControlPosition(palm * 2 + 1); - } +function stopAudio(killInjector) { + if (audioInjector && audioInjector.isPlaying) { + print("stopped sound"); + audioInjector.stop(); + } + if (killInjector) { + audioInjector = null; } } + function playChord(position, volume) { - if (soundPlaying.isPlaying) { - print("stopped sound"); - soundPlaying.stop(); + stopAudio(); + print("Played sound: " + whichChord + " at volume " + volume); + if (!audioInjector) { + var index = guitarSelector + whichChord; + var chord = chords[guitarSelector + whichChord]; + audioInjector = Audio.playSound(chord, { position: position, volume: volume }); + } else { + audioInjector.restart(); } - - print("Played sound: " + whichChord + " at volume " + options.volume); - if (!soundPlaying) { - soundPlaying = Audio.playSound(chords[guitarSelector + whichChord], { - position: position, - volume: volume - }); - } else { - soundPlaying.restart(); - } - } function keyPressEvent(event) { @@ -154,15 +187,19 @@ function keyPressEvent(event) { keyVolume = 0.4; if (event.text == "1") { whichChord = 1; + stopAudio(true); playChord(MyAvatar.position, keyVolume); } else if (event.text == "2") { whichChord = 2; + stopAudio(true); playChord(MyAvatar.position, keyVolume); } else if (event.text == "3") { whichChord = 3; + stopAudio(true); playChord(MyAvatar.position, keyVolume); } else if (event.text == "4") { whichChord = 4; + stopAudio(true); playChord(MyAvatar.position, keyVolume); } } @@ -170,6 +207,7 @@ function keyPressEvent(event) { function scriptEnding() { MyAvatar.detachOne(guitarModel); } + // Connect a call back that happens every frame Script.update.connect(checkHands); Script.scriptEnding.connect(scriptEnding); diff --git a/examples/controllers/hydra/drumStick.js b/examples/controllers/hydra/drumStick.js index e59528aa80..97a8bd856d 100644 --- a/examples/controllers/hydra/drumStick.js +++ b/examples/controllers/hydra/drumStick.js @@ -43,7 +43,8 @@ strokeSpeed[1] = 0.0; function checkSticks(deltaTime) { for (var palm = 0; palm < 2; palm++) { - var palmVelocity = Controller.getSpatialControlVelocity(palm * 2 + 1); + var handPose = (palm == 0) ? MyAvatar.leftHandPose : MyAvatar.rightHandPose; + var palmVelocity = handPose.velocity; var speed = length(palmVelocity); const TRIGGER_SPEED = 0.30; // Lower this value to let you 'drum' more gently @@ -64,7 +65,7 @@ function checkSticks(deltaTime) { if ((palmVelocity.y > 0.0) || (speed < STOP_SPEED)) { state[palm] = 0; - var options = { position: Controller.getSpatialControlPosition(palm * 2 + 1) }; + var options = { position: handPose.translation }; if (strokeSpeed[palm] > 1.0) { strokeSpeed[palm] = 1.0; } options.volume = strokeSpeed[palm]; diff --git a/examples/controllers/hydra/frisbee.js b/examples/controllers/hydra/frisbee.js index 78d8e77a90..46550d8e76 100644 --- a/examples/controllers/hydra/frisbee.js +++ b/examples/controllers/hydra/frisbee.js @@ -130,29 +130,38 @@ function Hand(name, palm, tip, forwardButton, button3, trigger) { this.trigger = trigger; this.holdingFrisbee = false; this.entity = false; - this.palmPosition = function() { return Controller.getSpatialControlPosition(this.palm); } + this.palmPosition = function () { + return this.palm == LEFT_PALM ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation; + }; + this.grabButtonPressed = function() { return ( - Controller.isButtonPressed(this.forwardButton) || - Controller.isButtonPressed(this.button3) || - Controller.getTriggerValue(this.trigger) > 0.5 + Controller.getValue(this.forwardButton) || + Controller.getValue(this.button3) || + Controller.getValue(this.trigger) > 0.5 ) }; - this.holdPosition = function() { return this.palm == LEFT_PALM ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(); }; + this.holdPosition = function () { + return this.palm == LEFT_PALM ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation; + }; + this.holdRotation = function() { - var q = Controller.getSpatialControlRawRotation(this.palm); + var q = (this.palm == LEFT_PALM) ? Controller.getPoseValue(Controller.Standard.leftHand).rotation + : Controller.getPoseValue(Controller.Standard.rightHand).rotation; q = Quat.multiply(MyAvatar.orientation, q); return {x: q.x, y: q.y, z: q.z, w: q.w}; }; - this.tipVelocity = function() { return Controller.getSpatialControlVelocity(this.tip); }; + this.tipVelocity = function () { + return this.tip == LEFT_TIP ? MyAvatar.leftHandTipPose.velocity : MyAvatar.rightHandTipPose.velocity; + }; } function MouseControl(button) { this.button = button; } -var leftHand = new Hand("LEFT", LEFT_PALM, LEFT_TIP, LEFT_BUTTON_FWD, LEFT_BUTTON_3, 0); -var rightHand = new Hand("RIGHT", RIGHT_PALM, RIGHT_TIP, RIGHT_BUTTON_FWD, RIGHT_BUTTON_3, 1); +var leftHand = new Hand("LEFT", LEFT_PALM, LEFT_TIP, Controller.Standard.LB, Controller.Standard.LeftPrimaryThumb, Controller.Standard.LT); +var rightHand = new Hand("RIGHT", RIGHT_PALM, RIGHT_TIP, Controller.Standard.RB, Controller.Standard.RightPrimaryThumb, Controller.Standard.RT); var leftMouseControl = new MouseControl("LEFT"); var middleMouseControl = new MouseControl("MIDDLE"); @@ -302,12 +311,7 @@ function initToolBar() { } function hydraCheck() { - var numberOfButtons = Controller.getNumberOfButtons(); - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - hydrasConnected = (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2); - return true;//hydrasConnected; + return Controller.Hardware.Hydra !== undefined; } function checkController(deltaTime) { diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index a90960a330..576c4335f6 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -15,24 +15,64 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// FIXME kickback functionality was removed because the joint setting interface in +// MyAvatar has apparently changed, breaking it. + +Script.include("../../libraries/utils.js"); +Script.include("../../libraries/constants.js"); +Script.include("../../libraries/toolBars.js"); + HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; -var RED = { red: 255, green: 0, blue: 0 }; var LASER_WIDTH = 2; +var POSE_CONTROLS = [ Controller.Standard.LeftHand, Controller.Standard.RightHand ]; +var TRIGGER_CONTROLS = [ Controller.Standard.LT, Controller.Standard.RT ]; +var MIN_THROWER_DELAY = 1000; +var MAX_THROWER_DELAY = 1000; +var RELOAD_INTERVAL = 5; +var GUN_MODEL = HIFI_PUBLIC_BUCKET + "cozza13/gun/m1911-handgun+1.fbx?v=4"; +var BULLET_VELOCITY = 10.0; +var GUN_OFFSETS = [ { + x: -0.04, + y: 0.26, + z: 0.04 +}, { + x: 0.04, + y: 0.26, + z: 0.04 +} ]; +var GUN_ORIENTATIONS = [ Quat.fromPitchYawRollDegrees(0, 90, 90), Quat.fromPitchYawRollDegrees(0, -90, 270) ]; + +var BARREL_OFFSETS = [ { + x: -0.12, + y: 0.12, + z: 0.04 +}, { + x: 0.12, + y: 0.12, + z: 0.04 +} ]; + +var mapping = Controller.newMapping(); +var validPoses = [ false, false ]; +var barrelVectors = [ 0, 0 ]; +var barrelTips = [ 0, 0 ]; var pointer = []; + pointer.push(Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: RED, + start: ZERO_VECTOR, + end: ZERO_VECTOR, + color: COLORS.RED, alpha: 1, visible: true, lineWidth: LASER_WIDTH })); + pointer.push(Overlays.addOverlay("line3d", { - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: RED, + start: ZERO_VECTOR, + end: ZERO_VECTOR, + color: COLORS.RED, alpha: 1, visible: true, lineWidth: LASER_WIDTH @@ -42,135 +82,116 @@ function getRandomFloat(min, max) { return Math.random() * (max - min) + min; } -var lastX = 0; -var lastY = 0; -var yawFromMouse = 0; -var pitchFromMouse = 0; -var isMouseDown = false; - -var MIN_THROWER_DELAY = 1000; -var MAX_THROWER_DELAY = 1000; -var LEFT_BUTTON_3 = 3; -var RELOAD_INTERVAL = 5; - -var KICKBACK_ANGLE = 15; -var elbowKickAngle = 0.0; -var rotationBeforeKickback; - var showScore = false; - - -// Load some sound to use for loading and firing +// Load some sound to use for loading and firing var fireSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/GUN-SHOT2.raw"); var loadSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/Gun_Reload_Weapon22.raw"); var impactSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Guns/BulletImpact2.raw"); var targetHitSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/hit.raw"); var targetLaunchSound = SoundCache.getSound(HIFI_PUBLIC_BUCKET + "sounds/Space%20Invaders/shoot.raw"); -var gunModel = "https://s3.amazonaws.com/hifi-public/cozza13/gun/m1911-handgun+1.fbx?v=4"; - var audioOptions = { - volume: 0.9 + volume: 0.9 } var shotsFired = 0; -var shotTime = new Date(); - -var activeControllers = 0; - -// initialize our controller triggers -var triggerPulled = new Array(); -var numberOfTriggers = Controller.getNumberOfTriggers(); -for (t = 0; t < numberOfTriggers; t++) { - triggerPulled[t] = false; -} - -var isLaunchButtonPressed = false; -var score = 0; +var shotTime = new Date(); +var isLaunchButtonPressed = false; +var score = 0; var bulletID = false; var targetID = false; -// Create overlay buttons and reticle - +// Create overlay buttons and reticle var BUTTON_SIZE = 32; var PADDING = 3; var NUM_BUTTONS = 3; - var screenSize = Controller.getViewportDimensions(); var startX = screenSize.x / 2 - (NUM_BUTTONS * (BUTTON_SIZE + PADDING)) / 2; -Script.include(["../../libraries/toolBars.js"]); -var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.gun.toolbar", function (screenSize) { +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.gun.toolbar", function(screenSize) { return { x: startX, y: (screenSize.y - (BUTTON_SIZE + PADDING)), }; }); -var reticle = Overlays.addOverlay("image", { - x: screenSize.x / 2 - (BUTTON_SIZE / 2), - y: screenSize.y / 2 - (BUTTON_SIZE / 2), - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: HIFI_PUBLIC_BUCKET + "images/gun/crosshairs.svg", - alpha: 1 - }); var offButton = toolBar.addOverlay("image", { - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: HIFI_PUBLIC_BUCKET + "images/gun/close.svg", - alpha: 1 - }); + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/gun/close.svg", + alpha: 1 +}); startX += BUTTON_SIZE + PADDING; var platformButton = toolBar.addOverlay("image", { - x: startX, - y: screenSize.y - (BUTTON_SIZE + PADDING), - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: HIFI_PUBLIC_BUCKET + "images/gun/platform-targets.svg", - alpha: 1 - }); + x: startX, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/gun/platform-targets.svg", + alpha: 1 +}); startX += BUTTON_SIZE + PADDING; var gridButton = toolBar.addOverlay("image", { - x: startX, - y: screenSize.y - (BUTTON_SIZE + PADDING), - width: BUTTON_SIZE, - height: BUTTON_SIZE, - imageURL: HIFI_PUBLIC_BUCKET + "images/gun/floating-targets.svg", - alpha: 1 - }); + x: startX, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/gun/floating-targets.svg", + alpha: 1 +}); if (showScore) { var text = Overlays.addOverlay("text", { - x: screenSize.x / 2 - 100, - y: screenSize.y / 2 - 50, - width: 150, - height: 50, - color: { red: 0, green: 0, blue: 0}, - textColor: { red: 255, green: 0, blue: 0}, - topMargin: 4, - leftMargin: 4, - text: "Score: " + score - }); + x: screenSize.x / 2 - 100, + y: screenSize.y / 2 - 50, + width: 150, + height: 50, + color: { + red: 0, + green: 0, + blue: 0 + }, + textColor: { + red: 255, + green: 0, + blue: 0 + }, + topMargin: 4, + leftMargin: 4, + text: "Score: " + score + }); } -var BULLET_VELOCITY = 10.0; - function entityCollisionWithEntity(entity1, entity2, collision) { if (entity2 === targetID) { score++; if (showScore) { - Overlays.editOverlay(text, { text: "Score: " + score } ); + Overlays.editOverlay(text, { + text: "Score: " + score + }); } - // We will delete the bullet and target in 1/2 sec, but for now we can see them bounce! + // We will delete the bullet and target in 1/2 sec, but for now we can + // see them bounce! Script.setTimeout(deleteBulletAndTarget, 500); // Turn the target and the bullet white - Entities.editEntity(entity1, { color: { red: 255, green: 255, blue: 255 }}); - Entities.editEntity(entity2, { color: { red: 255, green: 255, blue: 255 }}); + Entities.editEntity(entity1, { + color: { + red: 255, + green: 255, + blue: 255 + } + }); + Entities.editEntity(entity2, { + color: { + red: 255, + green: 255, + blue: 255 + } + }); // play the sound near the camera so the shooter can hear it audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); @@ -188,41 +209,45 @@ function shootBullet(position, velocity, grenade) { var bVelocity = grenade ? Vec3.multiply(GRENADE_VELOCITY, Vec3.normalize(velocity)) : velocity; var bSize = grenade ? GRENADE_SIZE : BULLET_SIZE; - var bGravity = grenade ? GRENADE_GRAVITY : BULLET_GRAVITY; + var bGravity = grenade ? GRENADE_GRAVITY : BULLET_GRAVITY; + + bulletID = Entities.addEntity({ + type: "Sphere", + position: position, + dimensions: { + x: bSize, + y: bSize, + z: bSize + }, + color: { + red: 0, + green: 0, + blue: 0 + }, + velocity: bVelocity, + lifetime: BULLET_LIFETIME, + gravity: { + x: 0, + y: bGravity, + z: 0 + }, + damping: 0.01, + density: 8000, + ignoreCollisions: false, + collisionsWillMove: true + }); - bulletID = Entities.addEntity( - { type: "Sphere", - position: position, - dimensions: { x: bSize, y: bSize, z: bSize }, - color: { red: 0, green: 0, blue: 0 }, - velocity: bVelocity, - lifetime: BULLET_LIFETIME, - gravity: { x: 0, y: bGravity, z: 0 }, - damping: 0.01, - density: 8000, - ignoreCollisions: false, - collisionsWillMove: true - }); Script.addEventHandler(bulletID, "collisionWithEntity", entityCollisionWithEntity); - // Play firing sounds - audioOptions.position = position; + // Play firing sounds + audioOptions.position = position; Audio.playSound(fireSound, audioOptions); shotsFired++; if ((shotsFired % RELOAD_INTERVAL) == 0) { Audio.playSound(loadSound, audioOptions); } - - // Kickback the arm - if (elbowKickAngle > 0.0) { - MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback); - } - rotationBeforeKickback = MyAvatar.getJointRotation("LeftForeArm"); - var armRotation = MyAvatar.getJointRotation("LeftForeArm"); - armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, KICKBACK_ANGLE)); - MyAvatar.setJointData("LeftForeArm", armRotation); - elbowKickAngle = KICKBACK_ANGLE; } + function shootTarget() { var TARGET_SIZE = 0.50; var TARGET_GRAVITY = 0.0; @@ -232,95 +257,152 @@ function shootTarget() { var DISTANCE_TO_LAUNCH_FROM = 5.0; var ANGLE_RANGE_FOR_LAUNCH = 20.0; var camera = Camera.getPosition(); - - var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { x:0, y:1, z:0 }); + + var targetDirection = Quat.angleAxis(getRandomFloat(-ANGLE_RANGE_FOR_LAUNCH, ANGLE_RANGE_FOR_LAUNCH), { + x: 0, + y: 1, + z: 0 + }); targetDirection = Quat.multiply(Camera.getOrientation(), targetDirection); var forwardVector = Quat.getFront(targetDirection); - + var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_TO_LAUNCH_FROM)); var velocity = Vec3.multiply(forwardVector, TARGET_FWD_VELOCITY); velocity.y += TARGET_UP_VELOCITY; - targetID = Entities.addEntity( - { type: "Box", - position: newPosition, - dimensions: { x: TARGET_SIZE * (0.5 + Math.random()), y: TARGET_SIZE * (0.5 + Math.random()), z: TARGET_SIZE * (0.5 + Math.random()) / 4.0 }, - color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, - velocity: velocity, - gravity: { x: 0, y: TARGET_GRAVITY, z: 0 }, - lifetime: TARGET_LIFETIME, - rotation: Camera.getOrientation(), - damping: 0.1, - density: 100.0, - collisionsWillMove: true }); + targetID = Entities.addEntity({ + type: "Box", + position: newPosition, + dimensions: { + x: TARGET_SIZE * (0.5 + Math.random()), + y: TARGET_SIZE * (0.5 + Math.random()), + z: TARGET_SIZE * (0.5 + Math.random()) / 4.0 + }, + color: { + red: Math.random() * 255, + green: Math.random() * 255, + blue: Math.random() * 255 + }, + velocity: velocity, + gravity: { + x: 0, + y: TARGET_GRAVITY, + z: 0 + }, + lifetime: TARGET_LIFETIME, + rotation: Camera.getOrientation(), + damping: 0.1, + density: 100.0, + collisionsWillMove: true + }); - // Record start time + // Record start time shotTime = new Date(); // Play target shoot sound - audioOptions.position = newPosition; + audioOptions.position = newPosition; Audio.playSound(targetLaunchSound, audioOptions); } function makeGrid(type, scale, size) { - var separation = scale * 2; + var separation = scale * 2; var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation()))); var x, y, z; - var GRID_LIFE = 60.0; - var dimensions; + var GRID_LIFE = 60.0; + var dimensions; for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { for (z = 0; z < size; z++) { - - dimensions = { x: separation/2.0 * (0.5 + Math.random()), y: separation/2.0 * (0.5 + Math.random()), z: separation/2.0 * (0.5 + Math.random()) / 4.0 }; - Entities.addEntity( - { type: type, - position: { x: pos.x + x * separation, y: pos.y + y * separation, z: pos.z + z * separation }, - dimensions: dimensions, - color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, - velocity: { x: 0, y: 0, z: 0 }, - gravity: { x: 0, y: 0, z: 0 }, - lifetime: GRID_LIFE, - rotation: Camera.getOrientation(), - damping: 0.1, - density: 100.0, - collisionsWillMove: true }); + dimensions = { + x: separation / 2.0 * (0.5 + Math.random()), + y: separation / 2.0 * (0.5 + Math.random()), + z: separation / 2.0 * (0.5 + Math.random()) / 4.0 + }; + + Entities.addEntity({ + type: type, + position: { + x: pos.x + x * separation, + y: pos.y + y * separation, + z: pos.z + z * separation + }, + dimensions: dimensions, + color: { + red: Math.random() * 255, + green: Math.random() * 255, + blue: Math.random() * 255 + }, + velocity: { + x: 0, + y: 0, + z: 0 + }, + gravity: { + x: 0, + y: 0, + z: 0 + }, + lifetime: GRID_LIFE, + rotation: Camera.getOrientation(), + damping: 0.1, + density: 100.0, + collisionsWillMove: true + }); } } } } function makePlatform(gravity, scale, size) { - var separation = scale * 2; + var separation = scale * 2; var pos = Vec3.sum(Camera.getPosition(), Vec3.multiply(10.0 * scale * separation, Quat.getFront(Camera.getOrientation()))); pos.y -= separation * size; var x, y, z; - var TARGET_LIFE = 60.0; + var TARGET_LIFE = 60.0; var INITIAL_GAP = 0.5; - var dimensions; + var dimensions; for (x = 0; x < size; x++) { for (y = 0; y < size; y++) { for (z = 0; z < size; z++) { - dimensions = { x: separation/2.0, y: separation, z: separation/2.0 }; + dimensions = { + x: separation / 2.0, + y: separation, + z: separation / 2.0 + }; - Entities.addEntity( - { type: "Box", - position: { x: pos.x - (separation * size / 2.0) + x * separation, - y: pos.y + y * (separation + INITIAL_GAP), - z: pos.z - (separation * size / 2.0) + z * separation }, - dimensions: dimensions, - color: { red: Math.random() * 255, green: Math.random() * 255, blue: Math.random() * 255 }, - velocity: { x: 0, y: 0.05, z: 0 }, - gravity: { x: 0, y: gravity, z: 0 }, - lifetime: TARGET_LIFE, - damping: 0.1, - density: 100.0, - collisionsWillMove: true }); + Entities.addEntity({ + type: "Box", + position: { + x: pos.x - (separation * size / 2.0) + x * separation, + y: pos.y + y * (separation + INITIAL_GAP), + z: pos.z - (separation * size / 2.0) + z * separation + }, + dimensions: dimensions, + color: { + red: Math.random() * 255, + green: Math.random() * 255, + blue: Math.random() * 255 + }, + velocity: { + x: 0, + y: 0.05, + z: 0 + }, + gravity: { + x: 0, + y: gravity, + z: 0 + }, + lifetime: TARGET_LIFE, + damping: 0.1, + density: 100.0, + collisionsWillMove: true + }); } } } @@ -328,9 +410,21 @@ function makePlatform(gravity, scale, size) { // Make a floor for this stuff to fall onto Entities.addEntity({ type: "Box", - position: { x: pos.x, y: pos.y - separation / 2.0, z: pos.z }, - dimensions: { x: 2.0 * separation * size, y: separation / 2.0, z: 2.0 * separation * size }, - color: { red: 100, green: 100, blue: 100 }, + position: { + x: pos.x, + y: pos.y - separation / 2.0, + z: pos.z + }, + dimensions: { + x: 2.0 * separation * size, + y: separation / 2.0, + z: 2.0 * separation * size + }, + color: { + red: 100, + green: 100, + blue: 100 + }, lifetime: TARGET_LIFE }); @@ -340,153 +434,79 @@ function keyPressEvent(event) { // if our tools are off, then don't do anything if (event.text == "t") { var time = MIN_THROWER_DELAY + Math.random() * MAX_THROWER_DELAY; - Script.setTimeout(shootTarget, time); + Script.setTimeout(shootTarget, time); } else if ((event.text == ".") || (event.text == "SPACE")) { shootFromMouse(false); } else if (event.text == ",") { shootFromMouse(true); } else if (event.text == "r") { playLoadSound(); - } else if (event.text == "s") { - // Hit this key to dump a posture from hydra to log - Quat.print("arm = ", MyAvatar.getJointRotation("LeftArm")); - Quat.print("forearm = ", MyAvatar.getJointRotation("LeftForeArm")); - Quat.print("hand = ", MyAvatar.getJointRotation("LeftHand")); } } function playLoadSound() { - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + audioOptions.position = MyAvatar.leftHandPose.translation; Audio.playSound(loadSound, audioOptions); - // Raise arm to firing posture - takeFiringPose(); -} - -function clearPose() { - MyAvatar.clearJointData("LeftForeArm"); - MyAvatar.clearJointData("LeftArm"); - MyAvatar.clearJointData("LeftHand"); } function deleteBulletAndTarget() { Entities.deleteEntity(bulletID); Entities.deleteEntity(targetID); - bulletID = false; - targetID = false; + bulletID = false; + targetID = false; } -function takeFiringPose() { - clearPose(); - if (Controller.getNumberOfSpatialControls() == 0) { - MyAvatar.setJointData("LeftForeArm", {x: -0.251919, y: -0.0415449, z: 0.499487, w: 0.827843}); - MyAvatar.setJointData("LeftArm", { x: 0.470196, y: -0.132559, z: 0.494033, w: 0.719219}); - MyAvatar.setJointData("LeftHand", { x: -0.0104815, y: -0.110551, z: -0.352111, w: 0.929333}); - } -} - -MyAvatar.attach(gunModel, "RightHand", {x:0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, -85, 79), 0.40); -MyAvatar.attach(gunModel, "LeftHand", {x:-0.04, y: 0.22, z: 0.02}, Quat.fromPitchYawRollDegrees(-172, 85, -79), 0.40); - -// Give a bit of time to load before playing sound -Script.setTimeout(playLoadSound, 2000); - function update(deltaTime) { - if (activeControllers == 0) { - if (Controller.getNumberOfSpatialControls() > 0) { - activeControllers = Controller.getNumberOfSpatialControls(); - clearPose(); - } - } + // FIXME we should also expose MyAvatar.handPoses[2], MyAvatar.tipPoses[2] + var tipPoses = [ MyAvatar.leftHandTipPose, MyAvatar.rightHandTipPose ]; - var KICKBACK_DECAY_RATE = 0.125; - if (elbowKickAngle > 0.0) { - if (elbowKickAngle > 0.5) { - var newAngle = elbowKickAngle * KICKBACK_DECAY_RATE; - elbowKickAngle -= newAngle; - var armRotation = MyAvatar.getJointRotation("LeftForeArm"); - armRotation = Quat.multiply(armRotation, Quat.fromPitchYawRollDegrees(0.0, 0.0, -newAngle)); - MyAvatar.setJointData("LeftForeArm", armRotation); - } else { - MyAvatar.setJointData("LeftForeArm", rotationBeforeKickback); - if (Controller.getNumberOfSpatialControls() > 0) { - clearPose(); - } - elbowKickAngle = 0.0; - } - } - - - // check for trigger press - - var numberOfTriggers = 2; - var controllersPerTrigger = 2; - - if (numberOfTriggers == 2 && controllersPerTrigger == 2) { - for (var t = 0; t < 2; t++) { - var shootABullet = false; - var triggerValue = Controller.getTriggerValue(t); - if (triggerPulled[t]) { - // must release to at least 0.1 - if (triggerValue < 0.1) { - triggerPulled[t] = false; // unpulled - } - } else { - // must pull to at least - if (triggerValue > 0.5) { - triggerPulled[t] = true; // pulled - shootABullet = true; - } - } - var palmController = t * controllersPerTrigger; - var palmPosition = Controller.getSpatialControlPosition(palmController); - var fingerTipController = palmController + 1; - var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController); - var laserTip = Vec3.sum(Vec3.multiply(100.0, Vec3.subtract(fingerTipPosition, palmPosition)), palmPosition); - - // Update Lasers - Overlays.editOverlay(pointer[t], { - start: palmPosition, - end: laserTip, - alpha: 1 + for (var side = 0; side < 2; side++) { + // First check if the controller is valid + var controllerPose = Controller.getPoseValue(POSE_CONTROLS[side]); + validPoses[side] = controllerPose.valid; + if (!controllerPose.valid) { + Overlays.editOverlay(pointer[side], { + visible: false }); - - if (shootABullet) { - - var palmToFingerTipVector = - { x: (fingerTipPosition.x - palmPosition.x), - y: (fingerTipPosition.y - palmPosition.y), - z: (fingerTipPosition.z - palmPosition.z) }; - - // just off the front of the finger tip - var position = { x: fingerTipPosition.x + palmToFingerTipVector.x/2, - y: fingerTipPosition.y + palmToFingerTipVector.y/2, - z: fingerTipPosition.z + palmToFingerTipVector.z/2}; - - var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(palmToFingerTipVector)); - - shootBullet(position, velocity, false); - } + continue; } + + // Need to adjust the laser + var tipPose = tipPoses[side]; + var handRotation = tipPoses[side].rotation; + var barrelOffset = Vec3.multiplyQbyV(handRotation, BARREL_OFFSETS[side]); + barrelTips[side] = Vec3.sum(tipPose.translation, barrelOffset); + barrelVectors[side] = Vec3.multiplyQbyV(handRotation, { + x: 0, + y: 1, + z: 0 + }); + + var laserTip = Vec3.sum(Vec3.multiply(100.0, barrelVectors[side]), barrelTips[side]); + // Update Lasers + Overlays.editOverlay(pointer[side], { + start: barrelTips[side], + end: laserTip, + alpha: 1, + visible: true + }); } } -function shootFromMouse(grenade) { - var DISTANCE_FROM_CAMERA = 1.0; - var camera = Camera.getPosition(); - var forwardVector = Quat.getFront(Camera.getOrientation()); - var newPosition = Vec3.sum(camera, Vec3.multiply(forwardVector, DISTANCE_FROM_CAMERA)); - var velocity = Vec3.multiply(forwardVector, BULLET_VELOCITY); - shootBullet(newPosition, velocity, grenade); -} - -function mouseReleaseEvent(event) { - // position - isMouseDown = false; +function triggerChanged(side, value) { + var pressed = (value != 0); + if (pressed) { + var position = barrelTips[side]; + var velocity = Vec3.multiply(BULLET_VELOCITY, Vec3.normalize(barrelVectors[side])); + shootBullet(position, velocity, false); + } } function mousePressEvent(event) { - var clickedText = false; - var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); if (clickedOverlay == offButton) { Script.stop(); } else if (clickedOverlay == platformButton) { @@ -494,25 +514,37 @@ function mousePressEvent(event) { makePlatform(-9.8, 1.0, platformSize); } else if (clickedOverlay == gridButton) { makeGrid("Box", 1.0, 3); - } + } } function scriptEnding() { - Overlays.deleteOverlay(reticle); + mapping.disable(); toolBar.cleanup(); - Overlays.deleteOverlay(pointer[0]); - Overlays.deleteOverlay(pointer[1]); + for (var i = 0; i < pointer.length; ++i) { + Overlays.deleteOverlay(pointer[i]); + } Overlays.deleteOverlay(text); - MyAvatar.detachOne(gunModel); - MyAvatar.detachOne(gunModel); + MyAvatar.detachOne(GUN_MODEL); + MyAvatar.detachOne(GUN_MODEL); clearPose(); } +MyAvatar.attach(GUN_MODEL, "LeftHand", GUN_OFFSETS[0], GUN_ORIENTATIONS[0], 0.40); +MyAvatar.attach(GUN_MODEL, "RightHand", GUN_OFFSETS[1], GUN_ORIENTATIONS[1], 0.40); + +// Give a bit of time to load before playing sound +Script.setTimeout(playLoadSound, 2000); + +mapping.from(Controller.Standard.LT).hysteresis(0.1, 0.5).to(function(value) { + triggerChanged(0, value); +}); + +mapping.from(Controller.Standard.RT).hysteresis(0.1, 0.5).to(function(value) { + triggerChanged(1, value); +}); +mapping.enable(); + Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.keyPressEvent.connect(keyPressEvent); - - - diff --git a/examples/controllers/hydra/hydraMove.js b/examples/controllers/hydra/hydraMove.js deleted file mode 100644 index 46715839c3..0000000000 --- a/examples/controllers/hydra/hydraMove.js +++ /dev/null @@ -1,303 +0,0 @@ -// -// hydraMove.js -// examples -// -// Created by Brad Hefta-Gaub on February 10, 2014 -// Updated by Philip Rosedale on September 8, 2014 -// -// Copyright 2014 High Fidelity, Inc. -// -// This is an example script that demonstrates use of the Controller and MyAvatar classes to implement -// avatar flying through the hydra/controller joysticks -// -// The joysticks (on hydra) will drive the avatar much like a playstation controller. -// -// Pressing the '4' or the 'FWD' button and moving/banking the hand will allow you to move and fly. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -var damping = 0.9; -var position = { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }; -var joysticksCaptured = false; -var THRUST_CONTROLLER = 0; -var VIEW_CONTROLLER = 1; -var INITIAL_THRUST_MULTIPLIER = 1.0; -var THRUST_INCREASE_RATE = 1.05; -var MAX_THRUST_MULTIPLIER = 75.0; -var thrustMultiplier = INITIAL_THRUST_MULTIPLIER; -var grabDelta = { x: 0, y: 0, z: 0}; -var grabStartPosition = { x: 0, y: 0, z: 0}; -var grabDeltaVelocity = { x: 0, y: 0, z: 0}; -var grabStartRotation = { x: 0, y: 0, z: 0, w: 1}; -var grabCurrentRotation = { x: 0, y: 0, z: 0, w: 1}; -var grabbingWithRightHand = false; -var wasGrabbingWithRightHand = false; -var grabbingWithLeftHand = false; -var wasGrabbingWithLeftHand = false; - -var EPSILON = 0.000001; -var velocity = { x: 0, y: 0, z: 0}; -var THRUST_MAG_UP = 100.0; -var THRUST_MAG_DOWN = 100.0; -var THRUST_MAG_FWD = 150.0; -var THRUST_MAG_BACK = 100.0; -var THRUST_MAG_LATERAL = 150.0; -var THRUST_JUMP = 120.0; - -var YAW_MAG = 100.0; -var PITCH_MAG = 100.0; -var THRUST_MAG_HAND_JETS = THRUST_MAG_FWD; -var JOYSTICK_YAW_MAG = YAW_MAG; -var JOYSTICK_PITCH_MAG = PITCH_MAG * 0.5; - - -var LEFT_PALM = 0; -var LEFT_BUTTON_4 = 4; -var LEFT_BUTTON_FWD = 5; -var RIGHT_PALM = 2; -var RIGHT_BUTTON_4 = 10; -var RIGHT_BUTTON_FWD = 11; - - - -function printVector(text, v, decimals) { - print(text + " " + v.x.toFixed(decimals) + ", " + v.y.toFixed(decimals) + ", " + v.z.toFixed(decimals)); -} - -var debug = false; -var RED_COLOR = { red: 255, green: 0, blue: 0 }; -var GRAY_COLOR = { red: 25, green: 25, blue: 25 }; -var defaultPosition = { x: 0, y: 0, z: 0}; -var RADIUS = 0.05; -var greenSphere = -1; -var redSphere = -1; - -function createDebugOverlay() { - - if (greenSphere == -1) { - greenSphere = Overlays.addOverlay("sphere", { - position: defaultPosition, - size: RADIUS, - color: GRAY_COLOR, - alpha: 0.75, - visible: true, - solid: true, - anchor: "MyAvatar" - }); - redSphere = Overlays.addOverlay("sphere", { - position: defaultPosition, - size: RADIUS, - color: RED_COLOR, - alpha: 0.5, - visible: true, - solid: true, - anchor: "MyAvatar" - }); - } -} - -function destroyDebugOverlay() { - if (greenSphere != -1) { - Overlays.deleteOverlay(greenSphere); - Overlays.deleteOverlay(redSphere); - greenSphere = -1; - redSphere = -1; - } -} - -function displayDebug() { - if (!(grabbingWithRightHand || grabbingWithLeftHand)) { - if (greenSphere != -1) { - destroyDebugOverlay(); - } - } else { - // update debug indicator - if (greenSphere == -1) { - createDebugOverlay(); - } - - var displayOffset = { x:0, y:0.5, z:-0.5 }; - - Overlays.editOverlay(greenSphere, { position: Vec3.sum(grabStartPosition, displayOffset) } ); - Overlays.editOverlay(redSphere, { position: Vec3.sum(Vec3.sum(grabStartPosition, grabDelta), displayOffset), size: RADIUS + (0.25 * Vec3.length(grabDelta)) } ); - } -} - -function getJoystickPosition(palm) { - // returns CONTROLLER_ID position in avatar local frame - var invRotation = Quat.inverse(MyAvatar.orientation); - var palmWorld = Controller.getSpatialControlPosition(palm); - var palmRelative = Vec3.subtract(palmWorld, MyAvatar.position); - var palmLocal = Vec3.multiplyQbyV(invRotation, palmRelative); - return palmLocal; -} - -// Used by handleGrabBehavior() for managing the grab position changes -function getAndResetGrabDelta() { - var HAND_GRAB_SCALE_DISTANCE = 2.0; - var delta = Vec3.multiply(grabDelta, (MyAvatar.scale * HAND_GRAB_SCALE_DISTANCE)); - grabDelta = { x: 0, y: 0, z: 0}; - var avatarRotation = MyAvatar.orientation; - var result = Vec3.multiplyQbyV(avatarRotation, Vec3.multiply(delta, -1)); - return result; -} - -function getGrabRotation() { - var quatDiff = Quat.multiply(grabCurrentRotation, Quat.inverse(grabStartRotation)); - return quatDiff; -} - -// When move button is pressed, process results -function handleGrabBehavior(deltaTime) { - // check for and handle grab behaviors - grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_4); - grabbingWithLeftHand = Controller.isButtonPressed(LEFT_BUTTON_4); - stoppedGrabbingWithLeftHand = false; - stoppedGrabbingWithRightHand = false; - - if (grabbingWithRightHand && !wasGrabbingWithRightHand) { - // Just starting grab, capture starting rotation - grabStartRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM); - grabStartPosition = getJoystickPosition(RIGHT_PALM); - if (debug) printVector("start position", grabStartPosition, 3); - } - if (grabbingWithRightHand) { - grabDelta = Vec3.subtract(getJoystickPosition(RIGHT_PALM), grabStartPosition); - grabCurrentRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM); - } - if (!grabbingWithRightHand && wasGrabbingWithRightHand) { - // Just ending grab, capture velocity - grabDeltaVelocity = Controller.getSpatialControlVelocity(RIGHT_PALM); - stoppedGrabbingWithRightHand = true; - } - - if (grabbingWithLeftHand && !wasGrabbingWithLeftHand) { - // Just starting grab, capture starting rotation - grabStartRotation = Controller.getSpatialControlRawRotation(LEFT_PALM); - grabStartPosition = getJoystickPosition(LEFT_PALM); - if (debug) printVector("start position", grabStartPosition, 3); - } - - if (grabbingWithLeftHand) { - grabDelta = Vec3.subtract(getJoystickPosition(LEFT_PALM), grabStartPosition); - grabCurrentRotation = Controller.getSpatialControlRawRotation(LEFT_PALM); - } - if (!grabbingWithLeftHand && wasGrabbingWithLeftHand) { - // Just ending grab, capture velocity - grabDeltaVelocity = Controller.getSpatialControlVelocity(LEFT_PALM); - stoppedGrabbingWithLeftHand = true; - } - - grabbing = grabbingWithRightHand || grabbingWithLeftHand; - stoppedGrabbing = stoppedGrabbingWithRightHand || stoppedGrabbingWithLeftHand; - - if (grabbing) { - - var headOrientation = MyAvatar.headOrientation; - var front = Quat.getFront(headOrientation); - var right = Quat.getRight(headOrientation); - var up = Quat.getUp(headOrientation); - - if (debug) { - printVector("grabDelta: ", grabDelta, 3); - } - - var thrust = Vec3.multiply(grabDelta, Math.abs(Vec3.length(grabDelta))); - - var THRUST_GRAB_SCALING = 100000.0; - - var thrustFront = Vec3.multiply(front, MyAvatar.scale * -thrust.z * THRUST_GRAB_SCALING * deltaTime); - MyAvatar.addThrust(thrustFront); - var thrustRight = Vec3.multiply(right, MyAvatar.scale * thrust.x * THRUST_GRAB_SCALING * deltaTime); - MyAvatar.addThrust(thrustRight); - var thrustUp = Vec3.multiply(up, MyAvatar.scale * thrust.y * THRUST_GRAB_SCALING * deltaTime); - MyAvatar.addThrust(thrustUp); - - // add some rotation... - var deltaRotation = getGrabRotation(); - var PITCH_SCALING = 2.5; - var PITCH_DEAD_ZONE = 2.0; - var YAW_SCALING = 2.5; - var ROLL_SCALING = 2.0; - - var euler = Quat.safeEulerAngles(deltaRotation); - - // Adjust body yaw by roll from controller - var orientation = Quat.multiply(Quat.angleAxis(((euler.y * YAW_SCALING) + - (euler.z * ROLL_SCALING)) * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation); - MyAvatar.orientation = orientation; - - // Adjust head pitch from controller - var pitch = 0.0; - if (Math.abs(euler.x) > PITCH_DEAD_ZONE) { - pitch = (euler.x < 0.0) ? (euler.x + PITCH_DEAD_ZONE) : (euler.x - PITCH_DEAD_ZONE); - } - MyAvatar.headPitch = MyAvatar.headPitch + (pitch * PITCH_SCALING * deltaTime); - - // TODO: Add some camera roll proportional to the rate of turn (so it feels like an airplane or roller coaster) - - } - - wasGrabbingWithRightHand = grabbingWithRightHand; - wasGrabbingWithLeftHand = grabbingWithLeftHand; -} - -// Update for joysticks and move button -var THRUST_DEAD_ZONE = 0.1; -var ROTATE_DEAD_ZONE = 0.1; -function flyWithHydra(deltaTime) { - var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER); - - if (Math.abs(thrustJoystickPosition.x) > THRUST_DEAD_ZONE || Math.abs(thrustJoystickPosition.y) > THRUST_DEAD_ZONE) { - if (thrustMultiplier < MAX_THRUST_MULTIPLIER) { - thrustMultiplier *= 1 + (deltaTime * THRUST_INCREASE_RATE); - } - var headOrientation = MyAvatar.headOrientation; - - var front = Quat.getFront(headOrientation); - var right = Quat.getRight(headOrientation); - var up = Quat.getUp(headOrientation); - - var thrustFront = Vec3.multiply(front, MyAvatar.scale * THRUST_MAG_HAND_JETS * - thrustJoystickPosition.y * thrustMultiplier * deltaTime); - MyAvatar.addThrust(thrustFront); - var thrustRight = Vec3.multiply(right, MyAvatar.scale * THRUST_MAG_HAND_JETS * - thrustJoystickPosition.x * thrustMultiplier * deltaTime); - MyAvatar.addThrust(thrustRight); - } else { - thrustMultiplier = INITIAL_THRUST_MULTIPLIER; - } - - // View Controller - var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER); - if (Math.abs(viewJoystickPosition.x) > ROTATE_DEAD_ZONE || Math.abs(viewJoystickPosition.y) > ROTATE_DEAD_ZONE) { - - // change the body yaw based on our x controller - var orientation = MyAvatar.orientation; - var deltaOrientation = Quat.fromPitchYawRollDegrees(0, (-1 * viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime), 0); - MyAvatar.orientation = Quat.multiply(orientation, deltaOrientation); - - // change the headPitch based on our x controller - var newPitch = MyAvatar.headPitch + (viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime); - MyAvatar.headPitch = newPitch; - } - handleGrabBehavior(deltaTime); - displayDebug(); - -} - -Script.update.connect(flyWithHydra); -Controller.captureJoystick(THRUST_CONTROLLER); -Controller.captureJoystick(VIEW_CONTROLLER); - -// Map keyPress and mouse move events to our callbacks -function scriptEnding() { - // re-enabled the standard application for touch events - Controller.releaseJoystick(THRUST_CONTROLLER); - Controller.releaseJoystick(VIEW_CONTROLLER); -} -Script.scriptEnding.connect(scriptEnding); - - diff --git a/examples/controllers/hydra/paddleBall.js b/examples/controllers/hydra/paddleBall.js index 13c6e2eb62..d90a78c260 100644 --- a/examples/controllers/hydra/paddleBall.js +++ b/examples/controllers/hydra/paddleBall.js @@ -20,10 +20,11 @@ var BALL_SIZE = 0.08; var PADDLE_SIZE = 0.20; var PADDLE_THICKNESS = 0.06; var PADDLE_COLOR = { red: 184, green: 134, blue: 11 }; -var BALL_COLOR = { red: 255, green: 0, blue: 0 }; +var BALL_COLOR = { red: 0, green: 255, blue: 0 }; var LINE_COLOR = { red: 255, green: 255, blue: 0 }; var PADDLE_BOX_OFFSET = { x: 0.05, y: 0.0, z: 0.0 }; +//probably we need to fix these initial values (offsets and orientation) var HOLD_POSITION_LEFT_OFFSET = { x: -0.15, y: 0.05, z: -0.05 }; var HOLD_POSITION_RIGHT_OFFSET = { x: -0.15, y: 0.05, z: 0.05 }; var PADDLE_ORIENTATION = Quat.fromPitchYawRollDegrees(0,0,0); @@ -32,18 +33,7 @@ var SPRING_FORCE = 15.0; var lastSoundTime = 0; var gameOn = false; var leftHanded = true; -var controllerID; - -function setControllerID() { - if (leftHanded) { - controllerID = 1; - } else { - controllerID = 3; - } -} - -setControllerID(); Menu.addMenu("PaddleBall"); Menu.addMenuItem({ menuName: "PaddleBall", menuItemName: "Left-Handed", isCheckable: true, isChecked: true }); @@ -63,7 +53,7 @@ var ball, paddle, paddleModel, line; function createEntities() { ball = Entities.addEntity( { type: "Sphere", - position: Controller.getSpatialControlPosition(controllerID), + position: leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation, dimensions: { x: BALL_SIZE, y: BALL_SIZE, z: BALL_SIZE }, color: BALL_COLOR, gravity: { x: 0, y: GRAVITY, z: 0 }, @@ -73,28 +63,28 @@ function createEntities() { paddle = Entities.addEntity( { type: "Box", - position: Controller.getSpatialControlPosition(controllerID), + position: leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation, dimensions: { x: PADDLE_SIZE, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 0.80 }, color: PADDLE_COLOR, gravity: { x: 0, y: 0, z: 0 }, ignoreCollisions: false, damping: 0.10, visible: false, - rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), - collisionsWillMove: false }); + rotation : leftHanded ? MyAvatar.leftHandPose.rotation : MyAvatar.rightHandPose.rotation, + collisionsWillMove: false }); modelURL = "http://public.highfidelity.io/models/attachments/pong_paddle.fbx"; paddleModel = Entities.addEntity( { type: "Model", - position: Vec3.sum(Controller.getSpatialControlPosition(controllerID), PADDLE_BOX_OFFSET), + position: Vec3.sum( leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation, PADDLE_BOX_OFFSET), dimensions: { x: PADDLE_SIZE * 1.5, y: PADDLE_THICKNESS, z: PADDLE_SIZE * 1.25 }, color: PADDLE_COLOR, gravity: { x: 0, y: 0, z: 0 }, ignoreCollisions: true, modelURL: modelURL, damping: 0.10, - rotation: Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), - collisionsWillMove: false }); + rotation : leftHanded ? MyAvatar.leftHandPose.rotation : MyAvatar.rightHandPose.rotation, + collisionsWillMove: false }); line = Overlays.addOverlay("line3d", { start: { x: 0, y: 0, z: 0 }, @@ -118,7 +108,7 @@ function deleteEntities() { } function update(deltaTime) { - var palmPosition = Controller.getSpatialControlPosition(controllerID); + var palmPosition = leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation; var controllerActive = (Vec3.length(palmPosition) > 0); if (!gameOn && controllerActive) { @@ -133,8 +123,8 @@ function update(deltaTime) { } var paddleOrientation = leftHanded ? PADDLE_ORIENTATION : Quat.multiply(PADDLE_ORIENTATION, Quat.fromPitchYawRollDegrees(0, 180, 0)); - var paddleWorldOrientation = Quat.multiply(Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(controllerID)), paddleOrientation); - var holdPosition = Vec3.sum(leftHanded ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition(), + var paddleWorldOrientation = Quat.multiply(leftHanded ? MyAvatar.leftHandPose.rotation : MyAvatar.rightHandPose.rotation, paddleOrientation); + var holdPosition = Vec3.sum(leftHanded ? MyAvatar.leftHandPose.translation : MyAvatar.rightHandPose.translation, Vec3.multiplyQbyV(paddleWorldOrientation, leftHanded ? HOLD_POSITION_LEFT_OFFSET : HOLD_POSITION_RIGHT_OFFSET )); var props = Entities.getEntityProperties(ball); @@ -146,10 +136,10 @@ function update(deltaTime) { Entities.editEntity(ball, { velocity: ballVelocity }); Overlays.editOverlay(line, { start: props.position, end: holdPosition }); Entities.editEntity(paddle, { position: holdPosition, - velocity: Controller.getSpatialControlVelocity(controllerID), + velocity: leftHanded ? MyAvatar.leftHandPose.velocity : MyAvatar.rightHandPose.velocity, rotation: paddleWorldOrientation }); Entities.editEntity(paddleModel, { position: Vec3.sum(holdPosition, Vec3.multiplyQbyV(paddleWorldOrientation, PADDLE_BOX_OFFSET)), - velocity: Controller.getSpatialControlVelocity(controllerID), + velocity: leftHanded ? MyAvatar.leftHandPose.velocity : MyAvatar.rightHandPose.velocity, rotation: paddleWorldOrientation }); } @@ -182,7 +172,6 @@ function menuItemEvent(menuItem) { leftHanded = Menu.isOptionChecked("Left-Handed"); } if ((leftHanded != oldHanded) && gameOn) { - setControllerID(); deleteEntities(); createEntities(); } diff --git a/examples/controllers/hydra/toyball.js b/examples/controllers/hydra/toyball.js index 0f5db9b2c0..10f8a82ec9 100644 --- a/examples/controllers/hydra/toyball.js +++ b/examples/controllers/hydra/toyball.js @@ -19,14 +19,7 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; // maybe we should make these constants... var LEFT_PALM = 0; -var LEFT_TIP = 1; -var LEFT_BUTTON_FWD = 5; -var LEFT_BUTTON_3 = 3; - var RIGHT_PALM = 2; -var RIGHT_TIP = 3; -var RIGHT_BUTTON_FWD = 11; -var RIGHT_BUTTON_3 = 9; var BALL_RADIUS = 0.08; var GRAVITY_STRENGTH = 3.0; @@ -69,9 +62,6 @@ function getBallHoldPosition(whichSide) { } function checkControllerSide(whichSide) { - var BUTTON_FWD; - var BUTTON_3; - var TRIGGER; var palmPosition; var palmRotation; var ballAlreadyInHand; @@ -79,35 +69,35 @@ function checkControllerSide(whichSide) { var linearVelocity; var angularVelocity; var AVERAGE_FACTOR = 0.33; - + var grabButtonPressed; + if (whichSide == LEFT_PALM) { - BUTTON_FWD = LEFT_BUTTON_FWD; - BUTTON_3 = LEFT_BUTTON_3; - TRIGGER = 0; - palmPosition = Controller.getSpatialControlPosition(LEFT_PALM); - palmRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(LEFT_PALM)); + palmPosition = MyAvatar.leftHandPose.translation; + palmRotation = MyAvatar.leftHandPose.rotation; ballAlreadyInHand = leftBallAlreadyInHand; handMessage = "LEFT"; - averageLinearVelocity[0] = Vec3.sum(Vec3.multiply(AVERAGE_FACTOR, Controller.getSpatialControlVelocity(LEFT_TIP)), + averageLinearVelocity[0] = Vec3.sum(Vec3.multiply(AVERAGE_FACTOR, MyAvatar.leftHandTipPose.velocity), Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[0])); + linearVelocity = averageLinearVelocity[0]; - angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(LEFT_TIP)); + angularVelocity = MyAvatar.leftHandTipPose.angularVelocity; + grabButtonPressed = (Controller.getValue(Controller.Standard.LT) > 0.5); + } else { - BUTTON_FWD = RIGHT_BUTTON_FWD; - BUTTON_3 = RIGHT_BUTTON_3; - TRIGGER = 1; - palmPosition = Controller.getSpatialControlPosition(RIGHT_PALM); - palmRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(RIGHT_PALM)); + palmPosition = MyAvatar.rightHandPose.translation; + palmRotation = MyAvatar.rightHandPose.rotation; ballAlreadyInHand = rightBallAlreadyInHand; - averageLinearVelocity[1] = Vec3.sum(Vec3.multiply(AVERAGE_FACTOR, Controller.getSpatialControlVelocity(RIGHT_TIP)), + averageLinearVelocity[1] = Vec3.sum(Vec3.multiply(AVERAGE_FACTOR, MyAvatar.rightHandTipPose.velocity), Vec3.multiply(1.0 - AVERAGE_FACTOR, averageLinearVelocity[1])); + linearVelocity = averageLinearVelocity[1]; - angularVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, Controller.getSpatialControlRawAngularVelocity(RIGHT_TIP)); + angularVelocity = MyAvatar.rightHandTipPose.angularVelocity; handMessage = "RIGHT"; + grabButtonPressed = (Controller.getValue(Controller.Standard.RT) > 0.5); + } - var grabButtonPressed = (Controller.isButtonPressed(BUTTON_FWD) || Controller.isButtonPressed(BUTTON_3) || (Controller.getTriggerValue(TRIGGER) > 0.5)); - + // If I don't currently have a ball in my hand, then try to catch closest one if (!ballAlreadyInHand && grabButtonPressed) { var closestEntity = Entities.findClosestEntity(palmPosition, targetRadius); @@ -187,10 +177,8 @@ function checkControllerSide(whichSide) { if (ballAlreadyInHand) { if (whichSide == LEFT_PALM) { handEntity = leftHandEntity; - whichTip = LEFT_TIP; } else { handEntity = rightHandEntity; - whichTip = RIGHT_TIP; } // If holding the ball keep it in the palm @@ -231,22 +219,10 @@ function checkControllerSide(whichSide) { } } } - - function checkController(deltaTime) { - var numberOfButtons = Controller.getNumberOfButtons(); - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - // this is expected for hydras - if (!(numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2)) { - debugPrint("total buttons = " + numberOfButtons + ", Triggers = " + numberOfTriggers + ", controllers/trigger = " + controllersPerTrigger); - return; // bail if no hydra - } - - checkControllerSide(LEFT_PALM); - checkControllerSide(RIGHT_PALM); + checkControllerSide(LEFT_PALM); + checkControllerSide(RIGHT_PALM); } diff --git a/examples/example/avatarcontrol/controllerExample.js b/examples/example/avatarcontrol/controllerExample.js index 66a9e40c56..8d7996b02b 100644 --- a/examples/example/avatarcontrol/controllerExample.js +++ b/examples/example/avatarcontrol/controllerExample.js @@ -10,25 +10,19 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - // initialize our triggers var triggerPulled = new Array(); -var numberOfTriggers = Controller.getNumberOfTriggers(); -for (t = 0; t < numberOfTriggers; t++) { +var NUMBER_OF_TRIGGERS = 2; +for (t = 0; t < NUMBER_OF_TRIGGERS; t++) { triggerPulled[t] = false; } - +var triggers = new Array(); +triggers[0] = Controller.Standard.LT; +triggers[1] = Controller.Standard.RT; function checkController(deltaTime) { - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; var triggerToggled = false; - - // this is expected for hydras - if (numberOfTriggers == 2 && controllersPerTrigger == 2) { - for (var t = 0; t < numberOfTriggers; t++) { - var triggerValue = Controller.getTriggerValue(t); - + for (var t = 0; t < NUMBER_OF_TRIGGERS; t++) { + var triggerValue = Controller.getValue(triggers[t]); if (triggerPulled[t]) { // must release to at least 0.1 if (triggerValue < 0.1) { @@ -41,17 +35,14 @@ function checkController(deltaTime) { triggerToggled = true; } } - if (triggerToggled) { print("a trigger was toggled"); } } - } + } - // register the call back so it fires before each data send Script.update.connect(checkController); - function printKeyEvent(eventName, event) { print(eventName); print(" event.key=" + event.key); @@ -64,7 +55,6 @@ function printKeyEvent(eventName, event) { } function keyPressEvent(event) { printKeyEvent("keyPressEvent", event); - if (event.text == "A") { print("the A key was pressed"); } @@ -72,10 +62,8 @@ function keyPressEvent(event) { print("the key was pressed"); } } - function keyReleaseEvent(event) { printKeyEvent("keyReleaseEvent", event); - if (event.text == "A") { print("the A key was released"); } @@ -83,11 +71,9 @@ function keyReleaseEvent(event) { print("the key was pressed"); } } - // Map keyPress and mouse move events to our callbacks Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); - // prevent the A key from going through to the application Controller.captureKeyEvents({ text: "A" }); Controller.captureKeyEvents({ key: "A".charCodeAt(0) }); // same as above, just another example of how to capture the key @@ -95,8 +81,6 @@ Controller.captureKeyEvents({ text: " " }); Controller.captureKeyEvents({ text: "@", isMeta: true }); Controller.captureKeyEvents({ text: "page up" }); Controller.captureKeyEvents({ text: "page down" }); - - function printMouseEvent(eventName, event) { print(eventName); print(" event.x,y=" + event.x + ", " + event.y); @@ -109,22 +93,18 @@ function printMouseEvent(eventName, event) { print(" event.isMeta=" + event.isMeta); print(" event.isAlt=" + event.isAlt); } - function mouseMoveEvent(event) { printMouseEvent("mouseMoveEvent", event); } function mousePressEvent(event) { printMouseEvent("mousePressEvent", event); } - function mouseReleaseEvent(event) { printMouseEvent("mouseReleaseEvent", event); } - Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); - function printTouchEvent(eventName, event) { print(eventName); @@ -143,7 +123,6 @@ function printTouchEvent(eventName, event) { print(" event.radius=" + event.radius); print(" event.isPinching=" + event.isPinching); print(" event.isPinchOpening=" + event.isPinchOpening); - print(" event.angle=" + event.angle); for (var i = 0; i < event.points.length; i++) { print(" event.angles[" + i + "]:" + event.angles[i]); @@ -151,15 +130,12 @@ function printTouchEvent(eventName, event) { print(" event.isRotating=" + event.isRotating); print(" event.rotating=" + event.rotating); } - function touchBeginEvent(event) { printTouchEvent("touchBeginEvent", event); } - function touchUpdateEvent(event) { printTouchEvent("touchUpdateEvent", event); } - function touchEndEvent(event) { printTouchEvent("touchEndEvent", event); } @@ -167,8 +143,6 @@ function touchEndEvent(event) { Controller.touchBeginEvent.connect(touchBeginEvent); Controller.touchUpdateEvent.connect(touchUpdateEvent); Controller.touchEndEvent.connect(touchEndEvent); - - function wheelEvent(event) { print("wheelEvent"); print(" event.x,y=" + event.x + ", " + event.y); @@ -182,9 +156,7 @@ function wheelEvent(event) { print(" event.isMeta=" + event.isMeta); print(" event.isAlt=" + event.isAlt); } - Controller.wheelEvent.connect(wheelEvent); - function scriptEnding() { // re-enabled the standard application for touch events Controller.releaseKeyEvents({ text: "A" }); @@ -194,5 +166,4 @@ function scriptEnding() { Controller.releaseKeyEvents({ text: "page up" }); Controller.releaseKeyEvents({ text: "page down" }); } - -Script.scriptEnding.connect(scriptEnding); +Script.scriptEnding.connect(scriptEnding); \ No newline at end of file diff --git a/examples/example/games/hydraGrabHockey.js b/examples/example/games/hydraGrabHockey.js index b9f760fa08..26f9a443ab 100644 --- a/examples/example/games/hydraGrabHockey.js +++ b/examples/example/games/hydraGrabHockey.js @@ -59,9 +59,7 @@ function controller(side) { this.triggerHeld = false; this.triggerThreshold = 0.9; this.side = side; - this.palm = 2 * side; - this.tip = 2 * side + 1; - this.trigger = side; + this.trigger = side == LEFT ? Controller.Standard.LT : Controller.Standard.RT; this.originalGravity = { x: 0, y: 0, @@ -150,8 +148,8 @@ function controller(side) { this.updateControllerState = function() { - this.palmPosition = Controller.getSpatialControlPosition(this.palm); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); + this.palmPosition = this.side == RIGHT ? MyAvatar.rightHandPose.translation : MyAvatar.leftHandPose.translation; + this.tipPosition = this.side == RIGHT ? MyAvatar.rightHandTipPose.translation : MyAvatar.leftHandTipPose.translation; this.triggerValue = Controller.getTriggerValue(this.trigger); } diff --git a/examples/example/games/sword.js b/examples/example/games/sword.js index 608fc30361..abd94b5319 100644 --- a/examples/example/games/sword.js +++ b/examples/example/games/sword.js @@ -238,7 +238,7 @@ var inHand = false; function isControllerActive() { // I don't think the hydra API provides any reliable way to know whether a particular controller is active. Ask for both. - controllerActive = (Vec3.length(Controller.getSpatialControlPosition(3)) > 0) || Vec3.length(Controller.getSpatialControlPosition(4)) > 0; + controllerActive = (Vec3.length(MyAvatar.leftHandPose.translation) > 0) || Vec3.length(MyAvatar.rightHandPose.translation) > 0; return controllerActive; } @@ -312,10 +312,10 @@ function grabSword(hand) { } var handRotation; if (hand === "right") { - handRotation = MyAvatar.getRightPalmRotation(); + handRotation = MyAvatar.rightHandPose.rotation; } else if (hand === "left") { - handRotation = MyAvatar.getLeftPalmRotation(); + handRotation = MyAvatar.leftHandPose.rotation; } var swordRotation = Entities.getEntityProperties(swordID).rotation; var offsetRotation = Quat.multiply(Quat.inverse(handRotation), swordRotation); diff --git a/examples/example/lineExample.js b/examples/example/lineExample.js new file mode 100644 index 0000000000..d424d4f9f3 --- /dev/null +++ b/examples/example/lineExample.js @@ -0,0 +1,26 @@ +// +// lineExample.js +// examples/example +// +// Created by Ryan Huffman on October 27, 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 +// + +Script.include("../libraries/line.js"); + +var basePosition = MyAvatar.position; +var color = { red: 128, green: 220, blue: 190 }; +var strokeWidth = 0.01; +var line = new InfiniteLine(basePosition, color, 20); + +for (var i = 0; i < (16 * Math.PI); i += 0.05) { + var x = 0 + var y = 0.25 * Math.sin(i); + var z = i / 10; + + var position = Vec3.sum(basePosition, { x: x, y: y, z: z }); + line.enqueuePoint(position, strokeWidth); +} diff --git a/examples/example/painting/hydraPaint.js b/examples/example/painting/hydraPaint.js index 29a3323e72..36137945cc 100644 --- a/examples/example/painting/hydraPaint.js +++ b/examples/example/painting/hydraPaint.js @@ -71,10 +71,8 @@ function controller(side, cycleColorButton) { this.triggerHeld = false; this.triggerThreshold = 0.9; this.side = side; - this.palm = 2 * side; - this.tip = 2 * side + 1; - this.trigger = side; - this.cycleColorButton = cycleColorButton; + this.trigger = side == LEFT ? Controller.Stantard.LT : Controller.Standard.RT; + this.cycleColorButton = side == LEFT ? Controller.Stantard.LeftPrimaryThumb : Controller.Standard.RightPrimaryThumb; this.points = []; this.normals = []; @@ -173,11 +171,10 @@ function controller(side, cycleColorButton) { this.updateControllerState = function() { - this.cycleColorButtonPressed = Controller.isButtonPressed(this.cycleColorButton); - this.palmPosition = Controller.getSpatialControlPosition(this.palm); - this.tipPosition = Controller.getSpatialControlPosition(this.tip); - this.palmNormal = Controller.getSpatialControlNormal(this.palm); - this.triggerValue = Controller.getTriggerValue(this.trigger); + this.cycleColorButtonPressed = Controller.getValue(this.cycleColorButton); + this.palmPosition = this.side == RIGHT ? MyAvatar.rightHandPose.translation : MyAvatar.leftHandPose.translation; + this.tipPosition = this.side == RIGHT ? MyAvatar.rightHandTipPose.translation : MyAvatar.leftHandTipPose.translation; + this.triggerValue = Controller.getValue(this.trigger); if (this.prevCycleColorButtonPressed === true && this.cycleColorButtonPressed === false) { @@ -215,8 +212,8 @@ function vectorIsZero(v) { } -var rightController = new controller(RIGHT, RIGHT_BUTTON_4); -var leftController = new controller(LEFT, LEFT_BUTTON_4); +var rightController = new controller(RIGHT); +var leftController = new controller(LEFT); Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); diff --git a/examples/example/scripts/controllerScriptingExamples.js b/examples/example/scripts/controllerScriptingExamples.js index 6db7b38705..edf4dca414 100644 --- a/examples/example/scripts/controllerScriptingExamples.js +++ b/examples/example/scripts/controllerScriptingExamples.js @@ -11,6 +11,37 @@ // Assumes you only have the default keyboard connected +function findAction(name) { + var actions = Controller.getAllActions(); + for (var i = 0; i < actions.length; i++) { + if (actions[i].actionName == name) { + return i; + } + } + // If the action isn't found, it will default to the first available action + return 0; +} + + +var hydra = Controller.Hardware.Hydra; +if (hydra !== undefined) { + print("-----------------------------------"); + var mapping = Controller.newMapping("Test"); + var standard = Controller.Standard; + print("standard:" + standard); + mapping.from(function () { return Math.sin(Date.now() / 250); }).to(function (newValue, oldValue, source) { + print("function source newValue:" + newValue + ", oldValue:" + oldValue + ", source:" + source); + }); + mapping.from(hydra.L1).to(standard.A); + mapping.from(hydra.L2).to(standard.B); + mapping.from(hydra.L3).to(function (newValue, oldValue, source) { + print("hydra.L3 newValue:" + newValue + ", oldValue:" + oldValue + ", source:" + source); + }); + Controller.enableMapping("Test"); + print("-----------------------------------"); +} else { + print("couldn't find hydra"); +} Object.keys(Controller.Standard).forEach(function (input) { print("Controller.Standard." + input + ":" + Controller.Standard[input]); @@ -32,44 +63,48 @@ Controller.resetAllDeviceBindings(); // Query all actions print("All Actions: \n" + Controller.getAllActions()); +var actionId = findAction("YAW_LEFT") + +print("Yaw Left action ID: " + actionId) + // Each action stores: // action: int representation of enum -print("Action 5 int: \n" + Controller.getAllActions()[5].action); +print("Action int: \n" + Controller.getAllActions()[actionId].action); // actionName: string representation of enum -print("Action 5 name: \n" + Controller.getAllActions()[5].actionName); +print("Action name: \n" + Controller.getAllActions()[actionId].actionName); // inputChannels: list of all inputchannels that control that action -print("Action 5 input channels: \n" + Controller.getAllActions()[5].inputChannels + "\n"); +print("Action input channels: \n" + Controller.getAllActions()[actionId].inputChannels + "\n"); // Each input channel stores: // action: Action that this InputChannel maps to -print("Input channel action: \n" + Controller.getAllActions()[5].inputChannels[0].action); +print("Input channel action: \n" + Controller.getAllActions()[actionId].inputChannels[0].action); // scale: sensitivity of input -print("Input channel scale: \n" + Controller.getAllActions()[5].inputChannels[0].scale); +print("Input channel scale: \n" + Controller.getAllActions()[actionId].inputChannels[0].scale); // input and modifier: Inputs -print("Input channel input and modifier: \n" + Controller.getAllActions()[5].inputChannels[0].input + "\n" + Controller.getAllActions()[5].inputChannels[0].modifier + "\n"); +print("Input channel input and modifier: \n" + Controller.getAllActions()[actionId].inputChannels[0].input + "\n" + Controller.getAllActions()[actionId].inputChannels[0].modifier + "\n"); // Each Input stores: // device: device of input -print("Input device: \n" + Controller.getAllActions()[5].inputChannels[0].input.device); +print("Input device: \n" + Controller.getAllActions()[actionId].inputChannels[0].input.device); // channel: channel of input -print("Input channel: \n" + Controller.getAllActions()[5].inputChannels[0].input.channel); +print("Input channel: \n" + Controller.getAllActions()[actionId].inputChannels[0].input.channel); // type: type of input (Unknown, Button, Axis, Joint) -print("Input type: \n" + Controller.getAllActions()[5].inputChannels[0].input.type); +print("Input type: \n" + Controller.getAllActions()[actionId].inputChannels[0].input.type); // id: id of input -print("Input id: \n" + Controller.getAllActions()[5].inputChannels[0].input.id + "\n"); +print("Input id: \n" + Controller.getAllActions()[actionId].inputChannels[0].input.id + "\n"); // You can get the name of a device from its id -print("Device 1 name: \n" + Controller.getDeviceName(Controller.getAllActions()[5].inputChannels[0].input.id)); +print("Device 1 name: \n" + Controller.getDeviceName(Controller.getAllActions()[actionId].inputChannels[0].input.id)); // You can also get all of a devices input channels print("Device 1's input channels: \n" + Controller.getAllInputsForDevice(1) + "\n"); @@ -105,7 +140,7 @@ for (i = 0; i < availableInputs.length; i++) { // You can modify key bindings by using these avaiable inputs // This will replace e (up) with 6 -var e = Controller.getAllActions()[5].inputChannels[0]; +var e = Controller.getAllActions()[actionId].inputChannels[0]; Controller.removeInputChannel(e); e.input = availableInputs[6].input; Controller.addInputChannel(e); \ No newline at end of file diff --git a/examples/libraries/line.js b/examples/libraries/line.js new file mode 100644 index 0000000000..c21bf2f3ad --- /dev/null +++ b/examples/libraries/line.js @@ -0,0 +1,161 @@ +// +// line.js +// examples/libraries +// +// Created by Ryan Huffman on October 27, 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 +// + +function error(message) { + print("[ERROR] " + message); +} + +// PolyLine +var LINE_DIMENSIONS = { x: 2000, y: 2000, z: 2000 }; +var MAX_LINE_LENGTH = 40; // This must be 2 or greater; +var DEFAULT_STROKE_WIDTH = 0.1; +var DEFAULT_LIFETIME = 20; +var DEFAULT_COLOR = { red: 255, green: 255, blue: 255 }; +var PolyLine = function(position, color, lifetime) { + this.position = position; + this.color = color; + this.lifetime = lifetime === undefined ? DEFAULT_LIFETIME : lifetime; + this.points = [ + ]; + this.strokeWidths = [ + ]; + this.normals = [ + ] + this.entityID = Entities.addEntity({ + type: "PolyLine", + position: position, + linePoints: this.points, + normals: this.normals, + strokeWidths: this.strokeWidths, + dimensions: LINE_DIMENSIONS, + color: color, + lifetime: lifetime + }); +}; + +PolyLine.prototype.enqueuePoint = function(position, strokeWidth) { + if (this.isFull()) { + error("Hit max PolyLine size"); + return; + } + + position = Vec3.subtract(position, this.position); + this.points.push(position); + this.normals.push({ x: 1, y: 0, z: 0 }); + this.strokeWidths.push(strokeWidth); + Entities.editEntity(this.entityID, { + linePoints: this.points, + normals: this.normals, + strokeWidths: this.strokeWidths + }); +}; + +PolyLine.prototype.dequeuePoint = function() { + if (this.points.length == 0) { + error("Hit min PolyLine size"); + return; + } + + this.points = this.points.slice(1); + this.normals = this.normals.slice(1); + this.strokeWidths = this.strokeWidths.slice(1); + + Entities.editEntity(this.entityID, { + linePoints: this.points, + normals: this.normals, + strokeWidths: this.strokeWidths + }); +}; + +PolyLine.prototype.getFirstPoint = function() { + return Vec3.sum(this.position, this.points[0]); +}; + +PolyLine.prototype.getLastPoint = function() { + return Vec3.sum(this.position, this.points[this.points.length - 1]); +}; + +PolyLine.prototype.getSize = function() { + return this.points.length; +} + +PolyLine.prototype.isFull = function() { + return this.points.length >= MAX_LINE_LENGTH; +}; + +PolyLine.prototype.destroy = function() { + Entities.deleteEntity(this.entityID); + this.points = []; +}; + + +// InfiniteLine +InfiniteLine = function(position, color, lifetime) { + this.position = position; + this.color = color; + this.lifetime = lifetime === undefined ? DEFAULT_LIFETIME : lifetime; + this.lines = []; + this.size = 0; +}; + +InfiniteLine.prototype.enqueuePoint = function(position, strokeWidth) { + var currentLine; + + if (this.lines.length == 0) { + currentLine = new PolyLine(position, this.color, this.lifetime); + this.lines.push(currentLine); + } else { + currentLine = this.lines[this.lines.length - 1]; + } + + if (currentLine.isFull()) { + var newLine = new PolyLine(currentLine.getLastPoint(), this.color, this.lifetime); + newLine.enqueuePoint(currentLine.getLastPoint(), strokeWidth); + this.lines.push(newLine); + currentLine = newLine; + } + + currentLine.enqueuePoint(position, strokeWidth); + + ++this.size; +}; + +InfiniteLine.prototype.dequeuePoint = function() { + if (this.lines.length == 0) { + error("Trying to dequeue from InfiniteLine when no points are left"); + return; + } + + var lastLine = this.lines[0]; + lastLine.dequeuePoint(); + + if (lastLine.getSize() <= 1) { + this.lines = this.lines.slice(1); + } + + --this.size; +}; + +InfiniteLine.prototype.getFirstPoint = function() { + return this.lines.length > 0 ? this.lines[0].getFirstPoint() : null; +}; + +InfiniteLine.prototype.getLastPoint = function() { + return this.lines.length > 0 ? this.lines[lines.length - 1].getLastPoint() : null; +}; + +InfiniteLine.prototype.destroy = function() { + for (var i = 0; i < this.lines.length; ++i) { + this.lines[i].destroy(); + } + + this.size = 0; +}; diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js index 26c299cdfb..4c995d6528 100644 --- a/examples/libraries/omniTool.js +++ b/examples/libraries/omniTool.js @@ -15,16 +15,18 @@ Script.include("omniTool/models/invisibleWand.js"); OmniToolModules = {}; OmniToolModuleType = null; +LOG_DEBUG = 1; -OmniTool = function(side) { +OmniTool = function(left) { this.OMNI_KEY = "OmniTool"; this.MAX_FRAMERATE = 60; this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE - this.SIDE = side; - this.PALM = 2 * side; - this.ACTION = findAction(side ? "ACTION2" : "ACTION1"); - this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2"); - + this.left = left; + this.triggered = false; + var actions = Controller.Actions; + var standard = Controller.Standard; + this.palmControl = left ? actions.LeftHand : actions.RightHand; + logDebug("Init OmniTool " + (left ? "left" : "right")); this.highlighter = new Highlighter(); this.ignoreEntities = {}; this.nearestOmniEntity = { @@ -47,22 +49,25 @@ OmniTool = function(side) { this.showWand(false); // Connect to desired events - var _this = this; - Controller.actionEvent.connect(function(action, state) { - _this.onActionEvent(action, state); - }); + var that = this; Script.update.connect(function(deltaTime) { - _this.lastUpdateInterval += deltaTime; - if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) { - _this.onUpdate(_this.lastUpdateInterval); - _this.lastUpdateInterval = 0; + that.lastUpdateInterval += deltaTime; + if (that.lastUpdateInterval >= that.UPDATE_INTERVAL) { + that.onUpdate(that.lastUpdateInterval); + that.lastUpdateInterval = 0; } }); Script.scriptEnding.connect(function() { - _this.onCleanup(); + that.onCleanup(); }); + + this.mapping = Controller.newMapping(); + this.mapping.from(left ? standard.LeftPrimaryThumb : standard.RightPrimaryThumb).to(function(value){ + that.onUpdateTrigger(value); + }) + this.mapping.enable(); } OmniTool.prototype.showWand = function(show) { @@ -81,30 +86,23 @@ OmniTool.prototype.showWand = function(show) { } } - OmniTool.prototype.onCleanup = function(action) { + this.mapping.disable(); this.unloadModule(); } -OmniTool.prototype.onActionEvent = function(action, state) { - // FIXME figure out the issues when only one spatial controller is active - // logDebug("Action: " + action + " " + state); - if (this.module && this.module.onActionEvent) { - this.module.onActionEvent(action, state); - } - - if (action == this.ACTION) { - if (state) { +OmniTool.prototype.onUpdateTrigger = function (value) { + //logDebug("Trigger update value " + value); + var triggered = value != 0; + if (triggered != this.triggered) { + this.triggered = triggered; + if (this.triggered) { this.onClick(); } else { this.onRelease(); } } - - // FIXME Does not work - //// with only one controller active (listed as 2 here because 'tip' + 'palm') - //// then treat the alt action button as the action button } OmniTool.prototype.getOmniToolData = function(entityId) { @@ -127,7 +125,7 @@ OmniTool.prototype.setActive = function(active) { if (active === this.active) { return; } - logDebug("OmniTool changing active state: " + active); + logDebug("OmniTool " + this.left + " changing active state: " + active); this.active = active; this.model.setVisible(this.active); if (this.module && this.module.onActiveChanged) { @@ -138,17 +136,17 @@ OmniTool.prototype.setActive = function(active) { OmniTool.prototype.onUpdate = function(deltaTime) { // FIXME this returns data if either the left or right controller is not on the base - this.position = Controller.getSpatialControlPosition(this.PALM); + this.pose = Controller.getPoseValue(this.palmControl); + this.position = this.left ? MyAvatar.leftHandTipPosition : MyAvatar.rightHandTipPosition; // When on the base, hydras report a position of 0 this.setActive(Vec3.length(this.position) > 0.001); if (!this.active) { return; } - if (this.model) { // Update the wand - var rawRotation = Controller.getSpatialControlRawRotation(this.PALM); + var rawRotation = this.pose.rotation; this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation); this.model.setTransform({ rotation: this.rotation, @@ -306,6 +304,7 @@ OmniTool.prototype.scan = function() { } OmniTool.prototype.unloadModule = function() { + logDebug("Unloading omniTool module") if (this.module && this.module.onUnload) { this.module.onUnload(); } @@ -348,4 +347,4 @@ OmniTool.prototype.activateNewOmniModule = function() { } // FIXME find a good way to sync the two omni tools -OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ]; +OMNI_TOOLS = [ new OmniTool(true), new OmniTool(false) ]; diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index ab86007e4b..25900471c1 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -31,13 +31,7 @@ scaleLine = function (start, end, scale) { } findAction = function(name) { - var actions = Controller.getAllActions(); - for (var i = 0; i < actions.length; i++) { - if (actions[i].actionName == name) { - return i; - } - } - return 0; + return Controller.findAction(name); } addLine = function(origin, vector, color) { diff --git a/examples/libraries/walkApi.js b/examples/libraries/walkApi.js index 2a4471bfbf..8935380150 100644 --- a/examples/libraries/walkApi.js +++ b/examples/libraries/walkApi.js @@ -18,24 +18,8 @@ Script.include("./libraries/walkConstants.js"); Avatar = function() { // if Hydras are connected, the only way to enable use is to never set any arm joint rotation - this.hydraCheck = function() { - // function courtesy of Thijs Wenker (frisbee.js) - var numberOfButtons = Controller.getNumberOfButtons(); - var numberOfTriggers = Controller.getNumberOfTriggers(); - var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); - const HYDRA_BUTTONS = 12; - const HYDRA_TRIGGERS = 2; - const HYDRA_CONTROLLERS_PER_TRIGGER = 2; - var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; - if (numberOfButtons == HYDRA_BUTTONS && - numberOfTriggers == HYDRA_TRIGGERS && - controllersPerTrigger == HYDRA_CONTROLLERS_PER_TRIGGER) { - print('walk.js info: Razer Hydra detected. Setting arms free (not controlled by script)'); - return true; - } else { - print('walk.js info: Razer Hydra not detected. Arms will be controlled by script.'); - return false; - } + this.hydraCheck = function () { + return Controller.Hardware.Hydra !== undefined; } // settings this.headFree = true; diff --git a/examples/mouseLook.js b/examples/mouseLook.js index 880ec138c4..81bc9d2813 100644 --- a/examples/mouseLook.js +++ b/examples/mouseLook.js @@ -38,7 +38,7 @@ var mouseLook = (function () { keyboardID = 0; function onKeyPressEvent(event) { - if (event.text == 'M') { + if (event.text == 'm') { active = !active; updateMapping(); } diff --git a/examples/stick.js b/examples/stick.js index f581591957..6683d8dcb6 100644 --- a/examples/stick.js +++ b/examples/stick.js @@ -10,7 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var hand = "left"; +var hand = "right"; var nullActionID = "00000000-0000-0000-0000-000000000000"; var controllerID; var controllerActive; @@ -32,7 +32,7 @@ function makeNewStick() { modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx", compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj", dimensions: {x: .11, y: .11, z: 1.0}, - position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close + position: MyAvatar.rightHandPosition, // initial position doesn't matter, as long as it's close rotation: MyAvatar.orientation, damping: .1, collisionSoundURL: "http://public.highfidelity.io/sounds/Collisions-hitsandslaps/67LCollision07.wav", @@ -84,23 +84,15 @@ function mouseMoveEvent(event) { } -function initControls(){ - if (hand == "right") { - controllerID = 3; // right handed - } else { - controllerID = 4; // left handed - } -} - - function update(deltaTime){ - var palmPosition = Controller.getSpatialControlPosition(controllerID); + var handPose = (hand == "right") ? MyAvatar.rightHandPose : MyAvatar.leftHandPose; + var palmPosition = handPose.translation; controllerActive = (Vec3.length(palmPosition) > 0); if(!controllerActive){ return; } - stickOrientation = Controller.getSpatialControlRawRotation(controllerID); + stickOrientation = handPose.rotation; var adjustment = Quat.fromPitchYawRollDegrees(180, 0, 0); stickOrientation = Quat.multiply(stickOrientation, adjustment); diff --git a/examples/tests/controllerInterfaceTest.js b/examples/tests/controllerInterfaceTest.js new file mode 100644 index 0000000000..97ad9bbc38 --- /dev/null +++ b/examples/tests/controllerInterfaceTest.js @@ -0,0 +1,77 @@ +ControllerTest = function() { + var standard = Controller.Standard; + var actions = Controller.Actions; + var xbox = Controller.Hardware.GamePad; + this.mappingEnabled = false; + this.mapping = Controller.newMapping(); + this.mapping.from(standard.LX).when([standard.LB, standard.RB]).to(actions.Yaw); + this.mapping.from(standard.RX).to(actions.StepYaw); + this.mapping.from(standard.RY).invert().to(actions.Pitch); + this.mapping.from(standard.RY).invert().to(actions.Pitch); + + + var testMakeAxis = false; + if (testMakeAxis) { + this.mapping.makeAxis(standard.LB, standard.RB).pulse(0.25).scale(40.0).to(actions.StepYaw); + } + + var testStepYaw = false; + if (!testMakeAxis && testStepYaw){ + this.mapping.from(standard.LB).pulse(0.10).invert().scale(40.0).to(actions.StepYaw); + this.mapping.from(standard.RB).pulse(0.10).scale(15.0).to(actions.StepYaw); + } + + var testFunctionSource = false; + if (testFunctionSource) { + this.mapping.from(function(){ + return Math.sin(Date.now() / 250); + }).to(actions.Yaw); + } + + var testFunctionDest = true; + if (testFunctionDest) { + this.mapping.from(standard.DU).pulse(1.0).to(function(value){ + if (value != 0.0) { + print(value); + } + }); + + } + + this.mapping.enable(); + this.mappingEnabled = true; + + var dumpInputs = false; + if (dumpInputs) { + print("Actions"); + for (var prop in Controller.Actions) { + print("\t" + prop); + } + print("Standard"); + for (var prop in Controller.Standard) { + print("\t" + prop); + } + print("Hardware"); + for (var prop in Controller.Hardware) { + print("\t" + prop); + for (var prop2 in Controller.Hardware[prop]) { + print("\t\t" + prop2); + } + } + print("Done"); + } + + var that = this; + Script.scriptEnding.connect(function() { + that.onCleanup(); + }); +} + +ControllerTest.prototype.onCleanup = function() { + if (this.mappingEnabled) { + this.mapping.disable(); + } +} + + +new ControllerTest(); \ No newline at end of file diff --git a/examples/toybox/ping_pong_gun/pingPongGun.js b/examples/toybox/ping_pong_gun/pingPongGun.js index 879d467293..48b82f0a36 100644 --- a/examples/toybox/ping_pong_gun/pingPongGun.js +++ b/examples/toybox/ping_pong_gun/pingPongGun.js @@ -45,6 +45,12 @@ green: 255, blue: 255 }; + + var TRIGGER_CONTROLS = [ + Controller.Standard.LT, + Controller.Standard.RT, + ]; + PingPongGun.prototype = { hand: null, @@ -53,11 +59,11 @@ canShoot: false, canShootTimeout: null, setRightHand: function() { - this.hand = 'RIGHT'; + this.hand = 1; }, setLeftHand: function() { - this.hand = 'LEFT'; + this.hand = 0; }, startNearGrab: function() { @@ -92,12 +98,7 @@ }, checkTriggerPressure: function(gunHand) { - var handClickString = gunHand + "_HAND_CLICK"; - - var handClick = Controller.findAction(handClickString); - - this.triggerValue = Controller.getActionValue(handClick); - + this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[gunHand]); if (this.triggerValue < RELOAD_THRESHOLD) { // print('RELOAD'); this.canShoot = true; diff --git a/examples/toybox/spray_paint/sprayPaintCan.js b/examples/toybox/spray_paint/sprayPaintCan.js index 4e6719af76..60fd12b975 100644 --- a/examples/toybox/spray_paint/sprayPaintCan.js +++ b/examples/toybox/spray_paint/sprayPaintCan.js @@ -33,12 +33,17 @@ var MIN_POINT_DISTANCE = 0.01; var STROKE_WIDTH = 0.02; + var TRIGGER_CONTROLS = [ + Controller.Standard.LT, + Controller.Standard.RT, + ]; + this.setRightHand = function () { - this.hand = 'RIGHT'; + this.hand = 1; } this.setLeftHand = function () { - this.hand = 'LEFT'; + this.hand = 0; } this.startNearGrab = function () { @@ -46,11 +51,7 @@ } this.toggleWithTriggerPressure = function () { - var handClickString = this.whichHand + "_HAND_CLICK"; - - var handClick = Controller.findAction(handClickString); - - this.triggerValue = Controller.getActionValue(handClick); + this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.whichHand]); if (this.triggerValue < DISABLE_SPRAY_THRESHOLD && this.spraying === true) { this.spraying = false; this.disableStream(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f04fa88910..fecdfe64fb 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -104,8 +104,8 @@ endif() link_hifi_libraries(shared octree environment gpu gl procedural model render fbx networking model-networking entities avatars audio audio-client animation script-engine physics - render-utils entities-renderer ui auto-updater - plugins display-plugins input-plugins) + render-utils entities-renderer ui auto-updater + controllers plugins display-plugins input-plugins ) #fixme find a way to express faceshift as a plugin target_bullet() diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json new file mode 100644 index 0000000000..20d954932a --- /dev/null +++ b/interface/resources/controllers/hydra.json @@ -0,0 +1,31 @@ +{ + "name": "Hydra to Standard", + "channels": [ + { "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" }, + { "from": "Hydra.LX", "to": "Standard.LX" }, + { "from": "Hydra.LT", "to": "Standard.LT" }, + { "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" }, + { "from": "Hydra.RX", "to": "Standard.RX" }, + { "from": "Hydra.RT", "to": "Standard.RT" }, + + { "from": "Hydra.LB", "to": "Standard.LB" }, + { "from": "Hydra.LS", "to": "Standard.LS" }, + { "from": "Hydra.RB", "to": "Standard.RB" }, + { "from": "Hydra.RS", "to": "Standard.RS" }, + + { "from": "Hydra.L0", "to": "Standard.Back" }, + { "from": "Hydra.L1", "to": "Standard.DL" }, + { "from": "Hydra.L2", "to": "Standard.DD" }, + { "from": "Hydra.L3", "to": "Standard.DR" }, + { "from": "Hydra.L4", "to": "Standard.DU" }, + + { "from": "Hydra.R0", "to": "Standard.Start" }, + { "from": "Hydra.R1", "to": "Standard.X" }, + { "from": "Hydra.R2", "to": "Standard.A" }, + { "from": "Hydra.R3", "to": "Standard.B" }, + { "from": "Hydra.R4", "to": "Standard.Y" }, + + { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Hydra.RightHand", "to": "Standard.RightHand" } + ] +} diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json new file mode 100644 index 0000000000..8af6b1dc98 --- /dev/null +++ b/interface/resources/controllers/keyboardMouse.json @@ -0,0 +1,86 @@ +{ + "name": "Keyboard/Mouse to Actions", + "channels": [ + + { "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.A", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.D", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.E", "when": "Keyboard.Shift", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.05 } ] }, + { "from": "Keyboard.C", "when": "Keyboard.Shift", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.05 } ] }, + { "from": "Keyboard.S", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, + { "from": "Keyboard.W", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" }, + + + { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, + "when": [ "Application.InHMD", "Application.ComfortMode", "Keyboard.RightMouseClick" ], + "to": "Actions.StepYaw", + "filters": + [ + "constrainToInteger", + { "type": "pulse", "interval": 0.5 }, + { "type": "scale", "scale": 15 } + ] + }, + + { "from": { "makeAxis" : [ + ["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"], + ["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"] + ] + }, + "when": [ "Application.InHMD", "Application.ComfortMode" ], + "to": "Actions.StepYaw", + "filters": + [ + { "type": "pulse", "interval": 0.5 }, + { "type": "scale", "scale": 15 } + ] + }, + + { "from": { "makeAxis" : [ + ["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"], + ["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"] + ] + }, + "to": "Actions.Yaw" + }, + + { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, + "when": "Keyboard.RightMouseClick", + "to": "Actions.Yaw" + }, + + { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, + { "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" }, + { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, + { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, + + { "from": "Keyboard.Left", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.Right", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, + { "from": "Keyboard.Up", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" }, + + { "from": "Keyboard.Up", "to": "Actions.LONGITUDINAL_FORWARD" }, + { "from": "Keyboard.Down", "to": "Actions.LONGITUDINAL_BACKWARD" }, + + { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" }, + { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" }, + + { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_UP" }, + { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_DOWN" }, + + { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" }, + { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" }, + + { "from": "Keyboard.MouseWheelUp", "to": "Actions.LATERAL_RIGHT" }, + { "from": "Keyboard.MouseWheelDown", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.MouseWheelLeft", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.02 } ]}, + { "from": "Keyboard.MouseWheelRight", "to": "Actions.BOOM_IN", "filters": [ { "type": "scale", "scale": 0.02 } ]}, + + { "from": "Keyboard.Space", "to": "Actions.SHIFT" }, + { "from": "Keyboard.R", "to": "Actions.ACTION1" }, + { "from": "Keyboard.T", "to": "Actions.ACTION2" } + ] +} diff --git a/interface/resources/controllers/mapping-config.json b/interface/resources/controllers/mapping-config.json new file mode 100644 index 0000000000..2ccd216c2f --- /dev/null +++ b/interface/resources/controllers/mapping-config.json @@ -0,0 +1,24 @@ +{ + "name": "Full Mapping config including the standard hydra and gamepad and one more thing", + "mappings": [ + { "src": "./mapping-hydra.json" }, + { "src": "./mapping-xbox.json" }, + { + "name": "example mapping for standard to js function", + "channels": [ { + "from": "Standard.B", + "to": { + "type":"js", + "function": "function(value){ print(\"Standard.B = \" + value );}" + } + }, { + "from": "Standard.B", + "to": { + "type":"js", + "src": "http://www.theNextBigThing.com/hifiInputSignalHandler.js" + } + } + ] + } + ] +} diff --git a/interface/resources/controllers/mapping-test0.json b/interface/resources/controllers/mapping-test0.json new file mode 100644 index 0000000000..5232c97f19 --- /dev/null +++ b/interface/resources/controllers/mapping-test0.json @@ -0,0 +1,36 @@ +{ + "name": "example mapping from Standard to actions", + "channels": [ { + "from": "Standard.LY", + "filters": [ { + "type": "clamp", + "min": 0, + "max": 1 + } + ], + "to": "Actions.Forward" + }, { + "from": "Standard.LY", + "filters": [ { + "type": "clamp", + "min": -1, + "max": 0 + }, { + "type": "invert" + } + ], + "to": "Actions.Backward" + }, { + "from": "Standard.LX", + "filters": [ { + "type": "scale", + "scale": 2.0 + } + ], + "to": "Actions.Yaw" + }, { + "from": "Standard.A", + "to": "Actions.Action0" + } + ] +} \ No newline at end of file diff --git a/interface/resources/controllers/standard-old.json b/interface/resources/controllers/standard-old.json new file mode 100644 index 0000000000..b662e5394d --- /dev/null +++ b/interface/resources/controllers/standard-old.json @@ -0,0 +1,43 @@ +{ + "name": "Standard to Action", + "channels": [ + { "from": "Standard.LY", "to": "Actions.TranslateZ" }, + { "from": "Standard.LX", "to": "Actions.TranslateX" }, + { "from": "Standard.RX", "to": "Actions.Yaw" }, + { "from": "Standard.RY", "to": "Actions.Pitch" }, + { + "from": "Standard.DU", + "to": "Actions.LONGITUDINAL_FORWARD", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { + "from": "Standard.DD", + "to": "Actions.LONGITUDINAL_BACKWARD", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { + "from": "Standard.DR", + "to": "Actions.LATERAL_RIGHT", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { + "from": "Standard.DL", + "to": "Actions.LATERAL_LEFT", + "filters": [ { "type": "scale", "scale": 0.5 } ] + }, + { "from": "Standard.Y", "to": "Actions.VERTICAL_UP" }, + { "from": "Standard.X", "to": "Actions.VERTICAL_DOWN" }, + { + "from": "Standard.RT", + "to": "Actions.BOOM_IN", + "filters": [ { "type": "scale", "scale": 0.1 } ] + }, + { + "from": "Standard.LT", + "to": "Actions.BOOM_OUT", + "filters": [ { "type": "scale", "scale": 0.1 } ] + }, + { "from": "Standard.LeftHand", "to": "Actions.LEFT_HAND" }, + { "from": "Standard.RightHand", "to": "Actions.RIGHT_HAND" } + ] +} diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json new file mode 100644 index 0000000000..871374b85b --- /dev/null +++ b/interface/resources/controllers/standard.json @@ -0,0 +1,36 @@ +{ + "name": "Standard to Action", + "channels": [ + { "from": "Standard.LY", "to": "Actions.TranslateZ" }, + { "from": "Standard.LX", "to": "Actions.TranslateX" }, + + { "from": "Standard.RX", + "when": [ "Application.InHMD", "Application.ComfortMode" ], + "to": "Actions.StepYaw", + "filters": + [ + { "type": "pulse", "interval": 0.5 }, + { "type": "scale", "scale": 15 } + ] + }, + + + { "from": "Standard.RX", "to": "Actions.Yaw" }, + { "from": "Standard.RY", "to": "Actions.Pitch" }, + + + { "from": [ "Standard.DU", "Standard.DL", "Standard.DR", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": "Standard.Back", "to": "Standard.LeftSecondaryThumb" }, + + { "from": [ "Standard.A", "Standard.B", "Standard.X", "Standard.Y" ], "to": "Standard.RightPrimaryThumb" }, + { "from": "Standard.Start", "to": "Standard.RightSecondaryThumb" }, + + { "from": "Standard.LT", "to": "Actions.LeftHandClick" }, + { "from": "Standard.RT", "to": "Actions.RightHandClick" }, + + { "from": "Standard.LeftHand", "to": "Actions.LeftHand" }, + { "from": "Standard.RightHand", "to": "Actions.RightHand" } + ] +} + + diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json new file mode 100644 index 0000000000..6a3c562e44 --- /dev/null +++ b/interface/resources/controllers/vive.json @@ -0,0 +1,26 @@ +{ + "name": "Vive to Standard", + "channels": [ + { "from": "Vive.LY", "filters": [ "invert", { "type": "deadZone", "min": 0.7 } ], "to": "Standard.LY" }, + { "from": "Vive.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, + + { "from": "Vive.LT", "to": "Standard.LT" }, + { "from": "Vive.LB", "to": "Standard.LB" }, + { "from": "Vive.LS", "to": "Standard.LS" }, + + { "from": "Vive.RY", "filters": "invert", "to": "Standard.RY" }, + { "from": "Vive.RX", "to": "Standard.RX" }, + + { "from": "Vive.RT", "to": "Standard.RT" }, + { "from": "Vive.RB", "to": "Standard.RB" }, + { "from": "Vive.RS", "to": "Standard.RS" }, + + { "from": "Vive.LeftPrimaryThumb", "to": "Standard.LeftPrimaryThumb" }, + { "from": "Vive.RightPrimaryThumb", "to": "Standard.RightPrimaryThumb" }, + { "from": "Vive.LeftSecondaryThumb", "to": "Standard.LeftSecondaryThumb" }, + { "from": "Vive.RightSecondaryThumb", "to": "Standard.RightSecondaryThumb" }, + + { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Vive.RightHand", "to": "Standard.RightHand" } + ] +} diff --git a/interface/resources/controllers/xbox.json b/interface/resources/controllers/xbox.json new file mode 100644 index 0000000000..8c341dff83 --- /dev/null +++ b/interface/resources/controllers/xbox.json @@ -0,0 +1,29 @@ +{ + "name": "XBox to Standard", + "channels": [ + { "from": "GamePad.LY", "to": "Standard.LY" }, + { "from": "GamePad.LX", "to": "Standard.LX" }, + { "from": "GamePad.LT", "to": "Standard.LT" }, + { "from": "GamePad.LB", "to": "Standard.LB" }, + { "from": "GamePad.LS", "to": "Standard.LS" }, + + { "from": "GamePad.RY", "to": "Standard.RY" }, + { "from": "GamePad.RX", "to": "Standard.RX" }, + { "from": "GamePad.RT", "to": "Standard.RT" }, + { "from": "GamePad.RB", "to": "Standard.RB" }, + { "from": "GamePad.RS", "to": "Standard.RS" }, + + { "from": "GamePad.Back", "to": "Standard.Back" }, + { "from": "GamePad.Start", "to": "Standard.Start" }, + + { "from": "GamePad.DU", "to": "Standard.DU" }, + { "from": "GamePad.DD", "to": "Standard.DD" }, + { "from": "GamePad.DL", "to": "Standard.DL" }, + { "from": "GamePad.DR", "to": "Standard.DR" }, + + { "from": "GamePad.A", "to": "Standard.A" }, + { "from": "GamePad.B", "to": "Standard.B" }, + { "from": "GamePad.X", "to": "Standard.X" }, + { "from": "GamePad.Y", "to": "Standard.Y" } + ] +} diff --git a/interface/resources/qml/ScrollingGraph.qml b/interface/resources/qml/ScrollingGraph.qml new file mode 100644 index 0000000000..55523a23f4 --- /dev/null +++ b/interface/resources/qml/ScrollingGraph.qml @@ -0,0 +1,111 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Rectangle { + id: root + property int size: 64 + width: size + height: size + color: 'black' + property int controlId: 0 + property real value: 0.5 + property int scrollWidth: 1 + property real min: 0.0 + property real max: 1.0 + property bool log: false + property real range: max - min + property color lineColor: 'yellow' + property bool bar: false + property real lastHeight: -1 + property string label: "" + + function update() { + value = Controller.getValue(controlId); + if (log) { + var log = Math.log(10) / Math.log(Math.abs(value)); + var sign = Math.sign(value); + value = log * sign; + } + canvas.requestPaint(); + } + + function drawHeight() { + if (value < min) { + return 0; + } + if (value > max) { + return height; + } + return ((value - min) / range) * height; + } + + Timer { + interval: 50; running: true; repeat: true + onTriggered: root.update() + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + Text { + anchors.top: parent.top + text: root.label + color: 'white' + } + + Text { + anchors.right: parent.right + anchors.top: parent.top + text: root.max + color: 'white' + } + + Text { + anchors.right: parent.right + anchors.bottom: parent.bottom + text: root.min + color: 'white' + } + + function scroll() { + var ctx = canvas.getContext('2d'); + var image = ctx.getImageData(0, 0, canvas.width, canvas.height); + ctx.beginPath(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(image, -root.scrollWidth, 0, canvas.width, canvas.height) + ctx.restore() + } + + onPaint: { + scroll(); + var ctx = canvas.getContext('2d'); + ctx.save(); + var currentHeight = root.drawHeight(); + if (root.lastHeight == -1) { + root.lastHeight = currentHeight + } + +// var x = canvas.width - root.drawWidth; +// var y = canvas.height - drawHeight; +// ctx.fillStyle = root.color +// ctx.fillRect(x, y, root.drawWidth, root.bar ? drawHeight : 1) +// ctx.fill(); +// ctx.restore() + + + ctx.beginPath(); + ctx.lineWidth = 1 + ctx.strokeStyle = root.lineColor + ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight) + ctx.stroke() + ctx.restore() + root.lastHeight = currentHeight + } + } +} + + diff --git a/interface/resources/qml/TestControllers.qml b/interface/resources/qml/TestControllers.qml new file mode 100644 index 0000000000..482616203f --- /dev/null +++ b/interface/resources/qml/TestControllers.qml @@ -0,0 +1,161 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "controller" +import "controls" as HifiControls +import "styles" + +HifiControls.VrDialog { + id: root + HifiConstants { id: hifi } + title: "Controller Test" + resizable: true + contentImplicitWidth: clientArea.implicitWidth + contentImplicitHeight: clientArea.implicitHeight + backgroundColor: "beige" + + property var actions: Controller.Actions + property var standard: Controller.Standard + property var hydra: null + property var testMapping: null + property bool testMappingEnabled: false + property var xbox: null + + function buildMapping() { + testMapping = Controller.newMapping(); + testMapping.fromQml(standard.RY).invert().toQml(actions.Pitch); + testMapping.fromQml(function(){ + return Math.sin(Date.now() / 250); + }).toQml(actions.Yaw); + //testMapping.makeAxis(standard.LB, standard.RB).to(actions.Yaw); + // Step yaw takes a number of degrees + testMapping.fromQml(standard.LB).pulse(0.10).invert().scale(40.0).toQml(actions.StepYaw); + testMapping.fromQml(standard.RB).pulse(0.10).scale(15.0).toQml(actions.StepYaw); + testMapping.fromQml(standard.RX).scale(15.0).toQml(actions.StepYaw); + } + + function toggleMapping() { + testMapping.enable(!testMappingEnabled); + testMappingEnabled = !testMappingEnabled; + } + + Component.onCompleted: { + enabled = true + var xboxRegex = /^GamePad/; + var hydraRegex = /^Hydra/; + for (var prop in Controller.Hardware) { + if(xboxRegex.test(prop)) { + root.xbox = Controller.Hardware[prop] + print("found xbox") + continue + } + if (hydraRegex.test(prop)) { + root.hydra = Controller.Hardware[prop] + print("found hydra") + continue + } + } + } + + Column { + id: clientArea + spacing: 12 + x: root.clientX + y: root.clientY + + Row { + spacing: 8 + + Button { + text: !root.testMapping ? "Build Mapping" : (root.testMappingEnabled ? "Disable Mapping" : "Enable Mapping") + onClicked: { + + if (!root.testMapping) { + root.buildMapping() + } else { + root.toggleMapping(); + } + } + } + } + + Row { + Standard { device: root.standard; label: "Standard"; width: 180 } + } + + Row { + spacing: 8 + Xbox { device: root.xbox; label: "XBox"; width: 180 } + Hydra { device: root.hydra; width: 180 } + } + + Row { + spacing: 4 + ScrollingGraph { + controlId: Controller.Actions.Yaw + label: "Yaw" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.YawLeft + label: "Yaw Left" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.YawRight + label: "Yaw Right" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.StepYaw + label: "StepYaw" + min: -20.0 + max: 20.0 + size: 64 + } + } + + Row { + ScrollingGraph { + controlId: Controller.Actions.TranslateZ + label: "TranslateZ" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.Forward + label: "Forward" + min: -2.0 + max: 2.0 + size: 64 + } + + ScrollingGraph { + controlId: Controller.Actions.Backward + label: "Backward" + min: -2.0 + max: 2.0 + size: 64 + } + + } + } +} // dialog + + + + + diff --git a/interface/resources/qml/controller/AnalogButton.qml b/interface/resources/qml/controller/AnalogButton.qml new file mode 100644 index 0000000000..82beb818ab --- /dev/null +++ b/interface/resources/qml/controller/AnalogButton.qml @@ -0,0 +1,45 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + property int size: 64 + width: size + height: size + property int controlId: 0 + property real value: 0 + property color color: 'black' + + function update() { + value = controlId ? Controller.getValue(controlId) : 0; + canvas.requestPaint(); + } + + Timer { + interval: 50; running: true; repeat: true + onTriggered: root.update(); + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + onPaint: { + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.beginPath(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + var fillHeight = root.value * canvas.height; + + ctx.fillStyle = 'red' + ctx.fillRect(0, canvas.height - fillHeight, canvas.width, fillHeight); + ctx.fill(); + ctx.restore() + } + } +} + + diff --git a/interface/resources/qml/controller/AnalogStick.qml b/interface/resources/qml/controller/AnalogStick.qml new file mode 100644 index 0000000000..c0d10bac59 --- /dev/null +++ b/interface/resources/qml/controller/AnalogStick.qml @@ -0,0 +1,55 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + property int size: 64 + width: size + height: size + property bool invertY: false + + + property int halfSize: size / 2 + property var controlIds: [ 0, 0 ] + property vector2d value: Qt.vector2d(0, 0) + + function update() { + value = Qt.vector2d( + controlIds[0] ? Controller.getValue(controlIds[0]) : 0, + controlIds[1] ? Controller.getValue(controlIds[1]) : 0 + ); + if (root.invertY) { + value.y = value.y * -1.0 + } + canvas.requestPaint(); + } + + Timer { + interval: 50; running: controlIds; repeat: true + onTriggered: root.update() + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + onPaint: { + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.beginPath(); + ctx.clearRect(0, 0, width, height); + ctx.fill(); + ctx.translate(root.halfSize, root.halfSize) + ctx.lineWidth = 4 + ctx.strokeStyle = Qt.rgba(Math.max(Math.abs(value.x), Math.abs(value.y)), 0, 0, 1) + ctx.moveTo(0, 0).lineTo(root.value.x * root.halfSize, root.value.y * root.halfSize) + ctx.stroke() + ctx.restore() + } + } +} + + diff --git a/interface/resources/qml/controller/Hydra.qml b/interface/resources/qml/controller/Hydra.qml new file mode 100644 index 0000000000..19f3b4c193 --- /dev/null +++ b/interface/resources/qml/controller/Hydra.qml @@ -0,0 +1,34 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "hydra" + +Item { + id: root + width: 480 + height: width * 3.0 / 4.0 + property var device + property real scale: width / 480 + property real rightOffset: (width / 2) * scale + + Image { + anchors.fill: parent + source: "hydra/hydra.png" + + HydraStick { + leftStick: true + scale: root.scale + device: root.device + } + + + HydraStick { + leftStick: false + scale: root.scale + device: root.device + } + + } +} diff --git a/interface/resources/qml/controller/Standard.qml b/interface/resources/qml/controller/Standard.qml new file mode 100644 index 0000000000..45e4febfa2 --- /dev/null +++ b/interface/resources/qml/controller/Standard.qml @@ -0,0 +1,35 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "xbox" + +Item { + id: root + property real aspect: 300.0 / 215.0 + width: 300 + height: width / aspect + property var device + property string label: "" + property real scale: width / 300.0 + + Xbox { + width: root.width; height: root.height + device: root.device + } + + // Left primary + ToggleButton { + x: 0; y: parent.height - height; + controlId: root.device.LeftPrimaryThumb + width: 16 * root.scale; height: 16 * root.scale + } + + // Left primary + ToggleButton { + x: parent.width - width; y: parent.height - height; + controlId: root.device.RightPrimaryThumb + width: 16 * root.scale; height: 16 * root.scale + } +} diff --git a/interface/resources/qml/controller/ToggleButton.qml b/interface/resources/qml/controller/ToggleButton.qml new file mode 100644 index 0000000000..ee8bd380e2 --- /dev/null +++ b/interface/resources/qml/controller/ToggleButton.qml @@ -0,0 +1,45 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + width: size + height: size + property int size: 64 + property int controlId: 0 + property real value: 0 + property color color: 'black' + + function update() { + value = controlId ? Controller.getValue(controlId) : 0; + canvas.requestPaint(); + } + + Timer { + interval: 50; running: root.controlId; repeat: true + onTriggered: root.update() + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + onPaint: { + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.beginPath(); + ctx.clearRect(0, 0, width, height); + if (root.value > 0.0) { + ctx.fillStyle = root.color + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + ctx.fill(); + ctx.restore() + } + } +} + + diff --git a/interface/resources/qml/controller/Xbox.qml b/interface/resources/qml/controller/Xbox.qml new file mode 100644 index 0000000000..4ff2959129 --- /dev/null +++ b/interface/resources/qml/controller/Xbox.qml @@ -0,0 +1,104 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "xbox" + +Item { + id: root + property real aspect: 300.0 / 215.0 + width: 300 + height: width / aspect + property var device + property string label: "" + property real scale: width / 300.0 + + Image { + Text { + anchors.left: parent.left + anchors.top: parent.top + text: root.label + visible: root.label != "" + } + anchors.fill: parent + source: "xbox/xbox360-controller-md.png" + + LeftAnalogStick { + device: root.device + x: (65 * root.scale) - width / 2; y: (42 * root.scale) - height / 2 + } + + // Left stick press + ToggleButton { + controlId: root.device.LS + width: 16 * root.scale; height: 16 * root.scale + x: (65 * root.scale) - width / 2; y: (42 * root.scale) - height / 2 + } + + + RightAnalogStick { + device: root.device + x: (193 * root.scale) - width / 2; y: (96 * root.scale) - height / 2 + } + + // Right stick press + ToggleButton { + controlId: root.device.RS + width: 16 * root.scale; height: 16 * root.scale + x: (193 * root.scale) - width / 2; y: (96 * root.scale) - height / 2 + } + + // Left trigger + AnalogButton { + controlId: root.device.LT + width: 8; height: 64 + x: (20 * root.scale); y: (7 * root.scale) + } + + // Right trigger + AnalogButton { + controlId: root.device.RT + width: 8; height: 64 + x: (272 * root.scale); y: (7 * root.scale) + } + + // Left bumper + ToggleButton { + controlId: root.device.LB + width: 32 * root.scale; height: 16 * root.scale + x: (40 * root.scale); y: (7 * root.scale) + } + + // Right bumper + ToggleButton { + controlId: root.device.RB + width: 32 * root.scale; height: 16 * root.scale + x: (root.width - width) - (40 * root.scale); y: (7 * root.scale) + } + + DPad { + device: root.device + size: 48 * root.scale + x: (80 * root.scale); y: (71 * root.scale) + } + + XboxButtons { + device: root.device + size: 65 * root.scale + x: (206 * root.scale); y: (19 * root.scale) + } + + ToggleButton { + controlId: root.device.Back + width: 16 * root.scale; height: 12 * root.scale + x: (112 * root.scale); y: (45 * root.scale) + } + + ToggleButton { + controlId: root.device.Start + width: 16 * root.scale; height: 12 * root.scale + x: (177 * root.scale); y: (45 * root.scale) + } + } +} diff --git a/interface/resources/qml/controller/hydra/HydraButtons.qml b/interface/resources/qml/controller/hydra/HydraButtons.qml new file mode 100644 index 0000000000..aa8927f5b6 --- /dev/null +++ b/interface/resources/qml/controller/hydra/HydraButtons.qml @@ -0,0 +1,18 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import ".." + +Item { + id: root + width: 72 * scale + height: 48 * scale + property var device + property real scale: 1.0 + property bool leftStick: true + +} + + diff --git a/interface/resources/qml/controller/hydra/HydraStick.qml b/interface/resources/qml/controller/hydra/HydraStick.qml new file mode 100644 index 0000000000..d082a20b10 --- /dev/null +++ b/interface/resources/qml/controller/hydra/HydraStick.qml @@ -0,0 +1,91 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import ".." + +Item { + id: root + property var device + property real scale: 1.0 + property bool leftStick: true + width: parent.width / 2; height: parent.height + x: leftStick ? 0 : parent.width / 2 + + Text { + x: parent.width / 2 - width / 2; y: parent.height / 2 - height / 2 + text: root.leftStick ? "L" : "R" + color: 'green' + } + + // Analog Stick + AnalogStick { + size: 64 * root.scale + x: 127 * root.scale - width / 2; y: 45 * root.scale - width / 2; z: 100 + invertY: true + controlIds: [ + root.leftStick ? root.device.LX : root.device.RX, + root.leftStick ? root.device.LY : root.device.RY + ] + } + + // Stick press + ToggleButton { + controlId: root.leftStick ? root.device.LS : root.device.RS + width: 16 * root.scale; height: 16 * root.scale + x: 127 * root.scale - width / 2; y: 45 * root.scale - width / 2; + color: 'yellow' + } + + // Trigger + AnalogButton { + controlId: root.leftStick ? root.device.LT : root.device.RT + width: 8 * root.scale ; height: 64 * root.scale + y: 24 * root.scale + x: root.leftStick ? (48 * root.scale) : root.width - (48 * root.scale) - width / 2 + } + + // Bumper + ToggleButton { + controlId: root.leftStick ? root.device.LB : root.device.RB + height: 16 * root.scale; width: 32 * root.scale + x: 128 * root.scale - width / 2; y: 24 * root.scale + color: 'red' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L0 : root.device.R0 + height: 16 * root.scale; width: 4 * root.scale + x: 128 * root.scale - width / 2; y: 109 * root.scale + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L1 : root.device.R1 + width: 16 * root.scale; height: 16 * root.scale + x: 103 * root.scale - width / 2; y: 100 * root.scale - height / 2 + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L2 : root.device.R2 + width: 16 * root.scale; height: 16 * root.scale + x: 148 * root.scale - width / 2; y: 100 * root.scale - height / 2 + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L3 : root.device.R3 + width: 16 * root.scale; height: 16 * root.scale + x: 97 * root.scale - width / 2; y: 76 * root.scale - height / 2 + color: 'yellow' + } + + ToggleButton { + controlId: root.leftStick ? root.device.L4 : root.device.R4 + width: 16 * root.scale; height: 16 * root.scale + x: 155 * root.scale - width / 2; y: 76 * root.scale - height / 2 + color: 'yellow' + } +} diff --git a/interface/resources/qml/controller/hydra/hydra.png b/interface/resources/qml/controller/hydra/hydra.png new file mode 100644 index 0000000000..a7549ab231 Binary files /dev/null and b/interface/resources/qml/controller/hydra/hydra.png differ diff --git a/interface/resources/qml/controller/xbox/DPad.qml b/interface/resources/qml/controller/xbox/DPad.qml new file mode 100644 index 0000000000..2cfb6412e7 --- /dev/null +++ b/interface/resources/qml/controller/xbox/DPad.qml @@ -0,0 +1,42 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import ".." + +Item { + id: root + property int size: 64 + width: size + height: size + property int spacer: size / 3 + property var device + property color color: 'black' + + ToggleButton { + controlId: device.Up + x: spacer + width: spacer; height: spacer + } + + ToggleButton { + controlId: device.Left + y: spacer + width: spacer; height: spacer + } + + ToggleButton { + controlId: device.Right + x: spacer * 2; y: spacer + width: spacer; height: spacer + } + + ToggleButton { + controlId: device.Down + x: spacer; y: spacer * 2 + width: spacer; height: spacer + } +} + + diff --git a/interface/resources/qml/controller/xbox/LeftAnalogStick.qml b/interface/resources/qml/controller/xbox/LeftAnalogStick.qml new file mode 100644 index 0000000000..8e2de1eb36 --- /dev/null +++ b/interface/resources/qml/controller/xbox/LeftAnalogStick.qml @@ -0,0 +1,21 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import ".." + +Item { + id: root + property int size: 64 + width: size + height: size + property var device + + AnalogStick { + size: size + controlIds: [ device.LX, device.LY ] + } +} + + diff --git a/interface/resources/qml/controller/xbox/RightAnalogStick.qml b/interface/resources/qml/controller/xbox/RightAnalogStick.qml new file mode 100644 index 0000000000..0cdfeda2cf --- /dev/null +++ b/interface/resources/qml/controller/xbox/RightAnalogStick.qml @@ -0,0 +1,21 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import ".." + +Item { + id: root + property int size: 64 + width: size + height: size + property var device + + AnalogStick { + size: size + controlIds: [ device.RX, device.RY ] + } +} + + diff --git a/interface/resources/qml/controller/xbox/XboxButtons.qml b/interface/resources/qml/controller/xbox/XboxButtons.qml new file mode 100644 index 0000000000..e26a4a0b98 --- /dev/null +++ b/interface/resources/qml/controller/xbox/XboxButtons.qml @@ -0,0 +1,46 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import ".." + +Item { + id: root + property int size: 64 + width: size + height: size + property int spacer: size / 3 + property var device + property color color: 'black' + + ToggleButton { + controlId: device.Y + x: spacer + width: spacer; height: spacer + color: 'yellow' + } + + ToggleButton { + controlId: device.X + y: spacer + width: spacer; height: spacer + color: 'blue' + } + + ToggleButton { + controlId: device.B + x: spacer * 2; y: spacer + width: spacer; height: spacer + color: 'red' + } + + ToggleButton { + controlId: device.A + x: spacer; y: spacer * 2 + width: spacer; height: spacer + color: 'green' + } +} + + diff --git a/interface/resources/qml/controller/xbox/xbox360-controller-md.png b/interface/resources/qml/controller/xbox/xbox360-controller-md.png new file mode 100644 index 0000000000..bdb596455f Binary files /dev/null and b/interface/resources/qml/controller/xbox/xbox360-controller-md.png differ diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index eebd26c3c3..7f4b5a3c3d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -17,27 +17,32 @@ #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include #include #include @@ -60,7 +65,8 @@ #include #include #include // this should probably be removed -#include +#include +#include #include #include #include @@ -120,6 +126,7 @@ #include "scripting/SettingsScriptingInterface.h" #include "scripting/WebWindowClass.h" #include "scripting/WindowScriptingInterface.h" +#include "scripting/ControllerScriptingInterface.h" #if defined(Q_OS_MAC) || defined(Q_OS_WIN) #include "SpeechRecognizer.h" #endif @@ -137,6 +144,7 @@ #include "ui/UpdateDialog.h" #include "Util.h" + // ON WIndows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) @@ -316,6 +324,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); #endif @@ -327,7 +336,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - + DependencyManager::set(); return true; } @@ -339,45 +348,47 @@ int _keyboardFocusHighlightID{ -1 }; PluginContainer* _pluginContainer; Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : - QApplication(argc, argv), - _dependencyManagerIsSetup(setupEssentials(argc, argv)), - _window(new MainWindow(desktop())), - _toolWindow(NULL), - _undoStackScriptingInterface(&_undoStack), - _frameCount(0), - _fps(60.0f), - _physicsEngine(new PhysicsEngine(Vectors::ZERO)), - _entities(true, this, this), - _entityClipboardRenderer(false, this, this), - _entityClipboard(new EntityTree()), - _lastQueriedTime(usecTimestampNow()), - _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), - _firstRun("firstRun", true), - _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION), - _scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION), - _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES), - _scaleMirror(1.0f), - _rotateMirror(0.0f), - _raiseMirror(0.0f), - _lastMouseMoveWasSimulated(false), - _enableProcessOctreeThread(true), - _runningScriptsWidget(NULL), - _runningScriptsWidgetWasVisible(false), - _lastNackTime(usecTimestampNow()), - _lastSendDownstreamAudioStats(usecTimestampNow()), - _aboutToQuit(false), - _notifiedPacketVersionMismatchThisDomain(false), - _maxOctreePPS(maxOctreePacketsPerSecond.get()), - _lastFaceTrackerUpdate(0) + QApplication(argc, argv), + _dependencyManagerIsSetup(setupEssentials(argc, argv)), + _window(new MainWindow(desktop())), + _toolWindow(NULL), + _undoStackScriptingInterface(&_undoStack), + _frameCount(0), + _fps(60.0f), + _physicsEngine(new PhysicsEngine(Vectors::ZERO)), + _entities(true, this, this), + _entityClipboardRenderer(false, this, this), + _entityClipboard(new EntityTree()), + _lastQueriedTime(usecTimestampNow()), + _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), + _firstRun("firstRun", true), + _previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION), + _scriptsLocationHandle("scriptsLocation", DESKTOP_LOCATION), + _fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES), + _scaleMirror(1.0f), + _rotateMirror(0.0f), + _raiseMirror(0.0f), + _lastMouseMoveWasSimulated(false), + _enableProcessOctreeThread(true), + _runningScriptsWidget(NULL), + _runningScriptsWidgetWasVisible(false), + _lastNackTime(usecTimestampNow()), + _lastSendDownstreamAudioStats(usecTimestampNow()), + _aboutToQuit(false), + _notifiedPacketVersionMismatchThisDomain(false), + _maxOctreePPS(maxOctreePacketsPerSecond.get()), + _lastFaceTrackerUpdate(0) { thread()->setObjectName("Main Thread"); - + setInstance(this); - + + auto controllerScriptingInterface = DependencyManager::get().data(); + _controllerScriptingInterface = dynamic_cast(controllerScriptingInterface); // to work around the Qt constant wireless scanning, set the env for polling interval very high const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT_MAX).toLocal8Bit(); qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT); - + _entityClipboard->createRootElement(); _pluginContainer = new PluginContainerProxy(); @@ -440,7 +451,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : audioIO->moveToThread(audioThread); auto& audioScriptingInterface = AudioScriptingInterface::getInstance(); - + connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start); connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit); connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater); @@ -479,7 +490,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived); connect(&domainHandler, &DomainHandler::hostnameChanged, - DependencyManager::get().data(), &AddressManager::storeCurrentAddress); + DependencyManager::get().data(), &AddressManager::storeCurrentAddress); // update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000; @@ -490,7 +501,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // if we get a domain change, immediately attempt update location in metaverse server connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, - discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); + discoverabilityManager.data(), &DiscoverabilityManager::updateLocation); connect(nodeList.data(), &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList.data(), &NodeList::nodeKilled, this, &Application::nodeKilled); @@ -529,14 +540,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); - #ifdef _WIN32 +#ifdef _WIN32 WSADATA WsaData; - int wsaresult = WSAStartup(MAKEWORD(2,2), &WsaData); - #endif + int wsaresult = WSAStartup(MAKEWORD(2, 2), &WsaData); +#endif // tell the NodeList instance who to tell the domain server we care about nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer - << NodeType::EntityServer << NodeType::AssetServer); + << NodeType::EntityServer << NodeType::AssetServer); // connect to the packet sent signal of the _entityEditSender connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); @@ -609,29 +620,38 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // hook up bandwidth estimator QSharedPointer bandwidthRecorder = DependencyManager::get(); connect(nodeList.data(), &LimitedNodeList::dataSent, - bandwidthRecorder.data(), &BandwidthRecorder::updateOutboundData); + bandwidthRecorder.data(), &BandwidthRecorder::updateOutboundData); connect(&nodeList->getPacketReceiver(), &PacketReceiver::dataReceived, - bandwidthRecorder.data(), &BandwidthRecorder::updateInboundData); + bandwidthRecorder.data(), &BandwidthRecorder::updateInboundData); connect(&getMyAvatar()->getSkeletonModel(), &SkeletonModel::skeletonLoaded, - this, &Application::checkSkeleton, Qt::QueuedConnection); + this, &Application::checkSkeleton, Qt::QueuedConnection); // Setup the userInputMapper with the actions auto userInputMapper = DependencyManager::get(); - connect(userInputMapper.data(), &UserInputMapper::actionEvent, &_controllerScriptingInterface, &AbstractControllerScriptingInterface::actionEvent); connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { - if (state) { - switch (action) { - case UserInputMapper::Action::TOGGLE_MUTE: - DependencyManager::get()->toggleMute(); - break; - } + if (state && action == toInt(controller::Action::TOGGLE_MUTE)) { + DependencyManager::get()->toggleMute(); } }); + // A new controllerInput device used to reflect current values from the application state + _applicationStateDevice = std::make_shared(); + + _applicationStateDevice->addInputVariant(QString("InHMD"), controller::StateController::ReadLambda([]() -> float { + return (float)qApp->getAvatarUpdater()->isHMDMode(); + })); + _applicationStateDevice->addInputVariant(QString("ComfortMode"), controller::StateController::ReadLambda([]() -> float { + return (float)Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode); + })); + + userInputMapper->registerDevice(_applicationStateDevice); + // Setup the keyboardMouseDevice and the user input mapper with the default bindings - _keyboardMouseDevice->registerToUserInputMapper(*userInputMapper); - _keyboardMouseDevice->assignDefaultInputMapping(*userInputMapper); + userInputMapper->registerDevice(_keyboardMouseDevice); + + + userInputMapper->loadDefaultMapping(userInputMapper->getStandardDeviceID()); // check first run... if (_firstRun.get()) { @@ -704,8 +724,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Now that menu is initalized we can sync myAvatar with it's state. getMyAvatar()->updateMotionBehaviorFromMenu(); +#if 0 // the 3Dconnexion device wants to be initiliazed after a window is displayed. ConnexionClient::getInstance().init(); +#endif auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket"); @@ -792,6 +814,9 @@ void Application::cleanupBeforeQuit() { AnimDebugDraw::getInstance().shutdown(); + // FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete: + _applicationStateDevice.reset(); + if (_keyboardFocusHighlightID > 0) { getOverlays().deleteOverlay(_keyboardFocusHighlightID); _keyboardFocusHighlightID = -1; @@ -902,7 +927,10 @@ Application::~Application() { Leapmotion::destroy(); RealSense::destroy(); + +#if 0 ConnexionClient::getInstance().destroy(); +#endif qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages } @@ -975,6 +1003,8 @@ void Application::initializeUi() { offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); offscreenUi->load("RootMenu.qml"); + auto scriptingInterface = DependencyManager::get(); + offscreenUi->getRootContext()->setContextProperty("Controller", scriptingInterface.data()); _glWidget->installEventFilter(offscreenUi.data()); VrMenu::load(); VrMenu::executeQueuedLambdas(); @@ -1003,7 +1033,10 @@ void Application::initializeUi() { foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { QString name = inputPlugin->getName(); if (name == KeyboardMouseDevice::NAME) { - _keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky + auto kbm = static_cast(inputPlugin.data()); + // FIXME incredibly evil.... _keyboardMouseDevice is now owned by + // both a QSharedPointer and a std::shared_ptr + _keyboardMouseDevice = std::shared_ptr(kbm); } } updateInputModes(); @@ -1456,7 +1489,7 @@ bool Application::event(QEvent* event) { } if (HFActionEvent::types().contains(event->type())) { - _controllerScriptingInterface.handleMetaEvent(static_cast(event)); + _controllerScriptingInterface->handleMetaEvent(static_cast(event)); } return QApplication::event(event); @@ -1470,7 +1503,7 @@ bool Application::eventFilter(QObject* object, QEvent* event) { } // Filter out captured keys before they're used for shortcut actions. - if (_controllerScriptingInterface.isKeyCaptured(static_cast(event))) { + if (_controllerScriptingInterface->isKeyCaptured(static_cast(event))) { event->accept(); return true; } @@ -1485,10 +1518,10 @@ void Application::keyPressEvent(QKeyEvent* event) { _altPressed = event->key() == Qt::Key_Alt; _keysPressed.insert(event->key()); - _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts + _controllerScriptingInterface->emitKeyPressEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isKeyCaptured(event)) { + if (_controllerScriptingInterface->isKeyCaptured(event)) { return; } @@ -1518,6 +1551,13 @@ void Application::keyPressEvent(QKeyEvent* event) { if (isMeta) { auto offscreenUi = DependencyManager::get(); offscreenUi->load("Browser.qml"); + } + break; + + case Qt::Key_X: + if (isMeta && isShifted) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->load("TestControllers.qml"); } break; @@ -1752,10 +1792,10 @@ void Application::keyReleaseEvent(QKeyEvent* event) { _keysPressed.remove(event->key()); - _controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts + _controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isKeyCaptured(event)) { + if (_controllerScriptingInterface->isKeyCaptured(event)) { return; } @@ -1796,7 +1836,9 @@ void Application::focusOutEvent(QFocusEvent* event) { inputPlugin->pluginFocusOutEvent(); } } +#if 0 ConnexionData::getInstance().focusOutEvent(); +#endif // synthesize events for keys currently pressed, since we may not get their release events foreach (int key, _keysPressed) { @@ -1844,10 +1886,10 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { _entities.mouseMoveEvent(&mappedEvent, deviceID); - _controllerScriptingInterface.emitMouseMoveEvent(&mappedEvent, deviceID); // send events to any registered scripts + _controllerScriptingInterface->emitMouseMoveEvent(&mappedEvent, deviceID); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isMouseCaptured()) { + if (_controllerScriptingInterface->isMouseCaptured()) { return; } @@ -1872,10 +1914,10 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { _entities.mousePressEvent(&mappedEvent, deviceID); } - _controllerScriptingInterface.emitMousePressEvent(&mappedEvent); // send events to any registered scripts + _controllerScriptingInterface->emitMousePressEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isMouseCaptured()) { + if (_controllerScriptingInterface->isMouseCaptured()) { return; } @@ -1897,11 +1939,11 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) { void Application::mouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID) { // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isMouseCaptured()) { + if (_controllerScriptingInterface->isMouseCaptured()) { return; } - _controllerScriptingInterface.emitMouseDoublePressEvent(event); + _controllerScriptingInterface->emitMouseDoublePressEvent(event); } void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { @@ -1917,10 +1959,10 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { _entities.mouseReleaseEvent(&mappedEvent, deviceID); } - _controllerScriptingInterface.emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts + _controllerScriptingInterface->emitMouseReleaseEvent(&mappedEvent); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isMouseCaptured()) { + if (_controllerScriptingInterface->isMouseCaptured()) { return; } @@ -1943,12 +1985,12 @@ void Application::touchUpdateEvent(QTouchEvent* event) { if (event->type() == QEvent::TouchUpdate) { TouchEvent thisEvent(*event, _lastTouchEvent); - _controllerScriptingInterface.emitTouchUpdateEvent(thisEvent); // send events to any registered scripts + _controllerScriptingInterface->emitTouchUpdateEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; } // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isTouchCaptured()) { + if (_controllerScriptingInterface->isTouchCaptured()) { return; } @@ -1960,13 +2002,13 @@ void Application::touchUpdateEvent(QTouchEvent* event) { void Application::touchBeginEvent(QTouchEvent* event) { _altPressed = false; TouchEvent thisEvent(*event); // on touch begin, we don't compare to last event - _controllerScriptingInterface.emitTouchBeginEvent(thisEvent); // send events to any registered scripts + _controllerScriptingInterface->emitTouchBeginEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; // and we reset our last event to this event before we call our update touchUpdateEvent(event); // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isTouchCaptured()) { + if (_controllerScriptingInterface->isTouchCaptured()) { return; } @@ -1979,11 +2021,11 @@ void Application::touchBeginEvent(QTouchEvent* event) { void Application::touchEndEvent(QTouchEvent* event) { _altPressed = false; TouchEvent thisEvent(*event, _lastTouchEvent); - _controllerScriptingInterface.emitTouchEndEvent(thisEvent); // send events to any registered scripts + _controllerScriptingInterface->emitTouchEndEvent(thisEvent); // send events to any registered scripts _lastTouchEvent = thisEvent; // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isTouchCaptured()) { + if (_controllerScriptingInterface->isTouchCaptured()) { return; } @@ -1996,10 +2038,10 @@ void Application::touchEndEvent(QTouchEvent* event) { void Application::wheelEvent(QWheelEvent* event) { _altPressed = false; - _controllerScriptingInterface.emitWheelEvent(event); // send events to any registered scripts + _controllerScriptingInterface->emitWheelEvent(event); // send events to any registered scripts // if one of our scripts have asked to capture this event, then stop processing it - if (_controllerScriptingInterface.isWheelCaptured()) { + if (_controllerScriptingInterface->isWheelCaptured()) { return; } @@ -2164,7 +2206,7 @@ float Application::getAvatarSimrate() { } void Application::setLowVelocityFilter(bool lowVelocityFilter) { - InputDevice::setLowVelocityFilter(lowVelocityFilter); + controller::InputDevice::setLowVelocityFilter(lowVelocityFilter); } ivec2 Application::getMouse() const { @@ -2694,13 +2736,9 @@ void Application::update(float deltaTime) { 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()) { + for (auto inputPlugin : PluginManager::getInstance()->getInputPlugins()) { + if (inputPlugin->isActive()) { inputPlugin->pluginUpdate(deltaTime, jointsCaptured); if (inputPlugin->isJointController()) { jointsCaptured = true; @@ -2708,19 +2746,14 @@ void Application::update(float deltaTime) { } } - // Dispatch input events - _controllerScriptingInterface.updateInputControllers(); - // Transfer the user inputs to the driveKeys + // FIXME can we drop drive keys and just have the avatar read the action states directly? 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)); + if (!_controllerScriptingInterface->areActionsCaptured()) { + myAvatar->setDriveKeys(TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z)); + myAvatar->setDriveKeys(TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y)); + myAvatar->setDriveKeys(TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X)); 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. @@ -2728,25 +2761,24 @@ 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(PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH) / timeFactor); + myAvatar->setDriveKeys(YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW) / timeFactor); + myAvatar->setDriveKeys(STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW) / timeFactor); } } - myAvatar->setDriveKeys(BOOM_IN, userInputMapper->getActionState(UserInputMapper::BOOM_IN)); - myAvatar->setDriveKeys(BOOM_OUT, userInputMapper->getActionState(UserInputMapper::BOOM_OUT)); + myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z)); } - UserInputMapper::PoseValue leftHand = userInputMapper->getPoseState(UserInputMapper::LEFT_HAND); - UserInputMapper::PoseValue rightHand = userInputMapper->getPoseState(UserInputMapper::RIGHT_HAND); + + controller::Pose leftHand = userInputMapper->getPoseState(controller::Action::LEFT_HAND); + controller::Pose rightHand = userInputMapper->getPoseState(controller::Action::RIGHT_HAND); Hand* hand = DependencyManager::get()->getMyAvatar()->getHand(); - setPalmData(hand, leftHand, deltaTime, LEFT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::LEFT_HAND_CLICK)); - setPalmData(hand, rightHand, deltaTime, RIGHT_HAND_INDEX, userInputMapper->getActionState(UserInputMapper::RIGHT_HAND_CLICK)); + setPalmData(hand, leftHand, deltaTime, HandData::LeftHand, userInputMapper->getActionState(controller::Action::LEFT_HAND_CLICK)); + setPalmData(hand, rightHand, deltaTime, HandData::RightHand, userInputMapper->getActionState(controller::Action::RIGHT_HAND_CLICK)); if (Menu::getInstance()->isOptionChecked(MenuOption::EnableHandMouseInput)) { - 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); + emulateMouse(hand, userInputMapper->getActionState(controller::Action::LEFT_HAND_CLICK), + userInputMapper->getActionState(controller::Action::SHIFT), HandData::LeftHand); + emulateMouse(hand, userInputMapper->getActionState(controller::Action::RIGHT_HAND_CLICK), + userInputMapper->getActionState(controller::Action::SHIFT), HandData::RightHand); } updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... @@ -4807,86 +4839,80 @@ mat4 Application::getHMDSensorPose() const { return mat4(); } -void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index, float triggerValue) { - 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; +void Application::setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue) { + + // NOTE: the Hand::modifyPalm() will allow the lambda to modify the palm data while ensuring some other user isn't + // reading or writing to the Palms. This is definitely not the best way of handling this, and I'd like to see more + // of this palm manipulation in the Hand class itself. But unfortunately the Hand and Palm don't knbow about + // controller::Pose. More work is needed to clean this up. + hand->modifyPalm(whichHand, [&](PalmData& palm) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + 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); } - } - if (!foundHand) { - PalmData newPalm(hand); - hand->getPalms().push_back(newPalm); - palm = &(hand->getPalms()[hand->getNumPalms() - 1]); - palm->setSixenseID(index); - } + palm.setRawVelocity(rawVelocity); // meters/sec - palm->setActive(pose.isValid()); + // 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)); + } - // transform from sensor space, to world space, to avatar model space. - glm::mat4 poseMat = createMatFromQuatAndPos(pose.getRotation(), pose.getTranslation()); - glm::mat4 sensorToWorldMat = getMyAvatar()->getSensorToWorldMatrix(); - glm::mat4 modelMat = createMatFromQuatAndPos(getMyAvatar()->getOrientation(), getMyAvatar()->getPosition()); - glm::mat4 objectPose = glm::inverse(modelMat) * sensorToWorldMat * poseMat; + if (controller::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); - 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, FINGER_LENGTH, 0.0f); - 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); - palm->setTrigger(triggerValue); + // 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, FINGER_LENGTH, 0.0f); + 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); + palm.setTrigger(triggerValue); // FIXME - we want to get rid of this idea of PalmData having a trigger + }); } -void Application::emulateMouse(Hand* hand, float click, float shift, int index) { +void Application::emulateMouse(Hand* hand, float click, float shift, HandData::Hand whichHand) { + auto palms = hand->getCopyOfPalms(); + // 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]); + for (size_t j = 0; j < palms.size(); j++) { + if (palms[j].whichHand() == whichHand) { + palm = &(palms[j]); foundHand = true; break; } @@ -4898,12 +4924,14 @@ void Application::emulateMouse(Hand* hand, float click, float shift, int index) // Process the mouse events QPoint pos; - unsigned int deviceID = index == 0 ? CONTROLLER_0_EVENT : CONTROLLER_1_EVENT; + + // FIXME - this mouse emulation stuff needs to be reworked for new controller input plugins + unsigned int deviceID = whichHand == HandData::LeftHand ? CONTROLLER_0_EVENT : CONTROLLER_1_EVENT; + int index = (int)whichHand; // FIXME - hack attack if (isHMDMode()) { pos = getApplicationCompositor().getPalmClickLocation(palm); - } - else { + } else { // Get directon relative to avatar orientation glm::vec3 direction = glm::inverse(getMyAvatar()->getOrientation()) * palm->getFingerDirection(); @@ -4912,7 +4940,7 @@ void Application::emulateMouse(Hand* hand, float click, float shift, int index) float yAngle = 0.5f - ((atan2f(direction.z, direction.y) + (float)M_PI_2)); auto canvasSize = getCanvasSize(); // Get the pixel range over which the xAngle and yAngle are scaled - float cursorRange = canvasSize.x * InputDevice::getCursorPixelRangeMult(); + float cursorRange = canvasSize.x * controller::InputDevice::getCursorPixelRangeMult(); pos.setX(canvasSize.x / 2.0f + cursorRange * xAngle); pos.setY(canvasSize.y / 2.0f + cursorRange * yAngle); diff --git a/interface/src/Application.h b/interface/src/Application.h index dc714ad82a..bf45a0c6ec 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -14,13 +14,15 @@ #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include + +#include + +#include +#include #include #include @@ -69,6 +71,10 @@ class FaceTracker; class MainWindow; class AssetUpload; +namespace controller { + class StateController; +} + #ifdef Q_OS_WIN static const UINT UWM_IDENTIFY_INSTANCES = RegisterWindowMessage("UWM_IDENTIFY_INSTANCES_{8AB82783-B74A-4258-955B-8188C22AA0D6}_" + qgetenv("USERNAME")); @@ -161,7 +167,7 @@ public: ToolWindow* getToolWindow() { return _toolWindow ; } - virtual AbstractControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; } + virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine); QImage renderAvatarBillboard(RenderArgs* renderArgs); @@ -350,8 +356,8 @@ private: void update(float deltaTime); - void setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float deltaTime, int index, float triggerValue); - void emulateMouse(Hand* hand, float click, float shift, int index); + void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue); + void emulateMouse(Hand* hand, float click, float shift, HandData::Hand whichHand); // Various helper functions called during update() void updateLOD(); @@ -440,7 +446,8 @@ private: OctreeQuery _octreeQuery; // NodeData derived class for querying octee cells from octree servers - KeyboardMouseDevice* _keyboardMouseDevice{ nullptr }; // Default input device, the good old keyboard mouse and maybe touchpad + std::shared_ptr _applicationStateDevice; // Default ApplicationDevice reflecting the state of different properties of the session + std::shared_ptr _keyboardMouseDevice; // Default input device, the good old keyboard mouse and maybe touchpad AvatarUpdate* _avatarUpdate {nullptr}; SimpleMovingAverage _avatarSimsPerSecond {10}; int _avatarSimsPerSecondReport {0}; @@ -474,8 +481,7 @@ private: NodeToJurisdictionMap _entityServerJurisdictions; NodeToOctreeSceneStats _octreeServerSceneStats; - - ControllerScriptingInterface _controllerScriptingInterface; + ControllerScriptingInterface* _controllerScriptingInterface{ nullptr }; QPointer _logDialog; QPointer _snapshotShareDialog; @@ -521,10 +527,13 @@ private: ApplicationCompositor _compositor; OverlayConductor _overlayConductor; - int _oldHandMouseX[2]; - int _oldHandMouseY[2]; - bool _oldHandLeftClick[2]; - bool _oldHandRightClick[2]; + + // FIXME - Hand Controller to mouse emulation helpers. This is crufty and should be moved + // into the input plugins or something. + int _oldHandMouseX[(int)HandData::NUMBER_OF_HANDS]; + int _oldHandMouseY[(int)HandData::NUMBER_OF_HANDS]; + bool _oldHandLeftClick[(int)HandData::NUMBER_OF_HANDS]; + bool _oldHandRightClick[(int)HandData::NUMBER_OF_HANDS]; DialogsManagerScriptingInterface* _dialogsManagerScriptingInterface = new DialogsManagerScriptingInterface(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a393ca5316..1565db2905 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -464,11 +464,13 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true, avatar, SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); +#if 0 addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::Connexion, 0, false, &ConnexionClient::getInstance(), SLOT(toggleConnexion(bool))); +#endif addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 00bcf1d271..ae3aec3572 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1177,22 +1177,6 @@ glm::vec3 Avatar::getLeftPalmPosition() { return leftHandPosition; } -glm::vec3 Avatar::getLeftPalmVelocity() { - const PalmData* palm = getHand()->getPalm(LEFT_HAND_INDEX); - if (palm != NULL) { - return palm->getVelocity(); - } - return glm::vec3(0.0f); -} - -glm::vec3 Avatar::getLeftPalmAngularVelocity() { - const PalmData* palm = getHand()->getPalm(LEFT_HAND_INDEX); - if (palm != NULL) { - return palm->getRawAngularVelocity(); - } - return glm::vec3(0.0f); -} - glm::quat Avatar::getLeftPalmRotation() { glm::quat leftRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); @@ -1208,22 +1192,6 @@ glm::vec3 Avatar::getRightPalmPosition() { return rightHandPosition; } -glm::vec3 Avatar::getRightPalmVelocity() { - const PalmData* palm = getHand()->getPalm(RIGHT_HAND_INDEX); - if (palm != NULL) { - return palm->getVelocity(); - } - return glm::vec3(0.0f); -} - -glm::vec3 Avatar::getRightPalmAngularVelocity() { - const PalmData* palm = getHand()->getPalm(RIGHT_HAND_INDEX); - if (palm != NULL) { - return palm->getRawAngularVelocity(); - } - return glm::vec3(0.0f); -} - glm::quat Avatar::getRightPalmRotation() { glm::quat rightRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 9a46a145c2..44b5d91015 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -43,21 +43,6 @@ static const float BILLBOARD_DISTANCE = 5.56f; // meters extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; -enum DriveKeys { - FWD = 0, - BACK, - LEFT, - RIGHT, - UP, - DOWN, - ROT_LEFT, - ROT_RIGHT, - ROT_UP, - ROT_DOWN, - BOOM_IN, - BOOM_OUT, - MAX_DRIVE_KEYS -}; enum ScreenTintLayer { SCREEN_TINT_BEFORE_LANDSCAPE = 0, @@ -175,13 +160,11 @@ public: AvatarMotionState* getMotionState() { return _motionState; } public slots: + + // FIXME - these should be migrated to use Pose data instead glm::vec3 getLeftPalmPosition(); - glm::vec3 getLeftPalmVelocity(); - glm::vec3 getLeftPalmAngularVelocity(); glm::quat getLeftPalmRotation(); glm::vec3 getRightPalmPosition(); - glm::vec3 getRightPalmVelocity(); - glm::vec3 getRightPalmAngularVelocity(); glm::quat getRightPalmRotation(); protected: diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9783590b05..b0da8faeca 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -248,6 +248,16 @@ QVector AvatarManager::getLocalLights() const { return _localLights; } +QVector AvatarManager::getAvatarIdentifiers() { + QReadLocker locker(&_hashLock); + return _avatarHash.keys().toVector(); +} +AvatarData* AvatarManager::getAvatar(QUuid avatarID) { + QReadLocker locker(&_hashLock); + return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar. +} + + void AvatarManager::getObjectsToDelete(VectorOfMotionStates& result) { result.clear(); result.swap(_motionStatesToDelete); @@ -356,5 +366,10 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) return std::static_pointer_cast(_myAvatar); } QReadLocker locker(&_hashLock); - return _avatarHash[sessionID]; + auto iter = _avatarHash.find(sessionID); + if (iter != _avatarHash.end()) { + return iter.value(); + } else { + return AvatarSharedPointer(); + } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 35c18dff0b..fa0593368b 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -52,6 +52,10 @@ public: Q_INVOKABLE void setLocalLights(const QVector& localLights); Q_INVOKABLE QVector getLocalLights() const; + // Currently, your own avatar will be included as the null avatar id. + Q_INVOKABLE QVector getAvatarIdentifiers(); + Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); + void getObjectsToDelete(VectorOfMotionStates& motionStates); void getObjectsToAdd(VectorOfMotionStates& motionStates); diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index 0eeb7222b6..15a3163998 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -31,13 +31,7 @@ Hand::Hand(Avatar* owningAvatar) : } void Hand::simulate(float deltaTime, bool isMine) { - if (isMine) { - // Iterate hand controllers, take actions as needed - for (size_t i = 0; i < getNumPalms(); ++i) { - PalmData& palm = getPalms()[i]; - palm.setLastControllerButtons(palm.getControllerButtons()); - } - } + // nothing to do here } void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) { @@ -53,10 +47,11 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) { const glm::vec3 grayColor(0.5f); const float SPHERE_RADIUS = 0.03f * avatarScale; + auto palms = getCopyOfPalms(); + gpu::Batch& batch = *renderArgs->_batch; if (isMine) { - for (size_t i = 0; i < getNumPalms(); i++) { - PalmData& palm = getPalms()[i]; + for (const auto& palm : palms) { if (!palm.isActive()) { continue; } @@ -82,8 +77,7 @@ void Hand::renderHandTargets(RenderArgs* renderArgs, bool isMine) { const float AXIS_LENGTH = 10.0f * SPHERE_RADIUS; // Draw the coordinate frames of the hand targets - for (size_t i = 0; i < getNumPalms(); ++i) { - PalmData& palm = getPalms()[i]; + for (const auto& palm : palms) { if (palm.isActive()) { glm::vec3 root = palm.getPosition(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b567b62747..9dcd77c689 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -53,6 +53,7 @@ using namespace std; +static quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f); const float YAW_SPEED = 150.0f; // degrees/sec const float PITCH_SPEED = 100.0f; // degrees/sec @@ -246,8 +247,31 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("transform"); + bool stepAction = false; + // When there are no step values, we zero out the last step pulse. + // This allows a user to do faster snapping by tapping a control + for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) { + if (_driveKeys[i] != 0.0f) { + stepAction = true; + } + } + quint64 now = usecTimestampNow(); + quint64 pulseDeltaTime = now - _lastStepPulse; + if (!stepAction) { + _lastStepPulse = 0; + } + + if (stepAction && pulseDeltaTime > COMFORT_MODE_PULSE_TIMING) { + _pulseUpdate = true; + } + updateOrientation(deltaTime); updatePosition(deltaTime); + + if (_pulseUpdate) { + _lastStepPulse = now; + _pulseUpdate = false; + } } { @@ -531,6 +555,50 @@ void MyAvatar::updateFromTrackers(float deltaTime) { } +glm::vec3 MyAvatar::getLeftHandPosition() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand); + return palmData.isValid() ? palmData.getPosition() : glm::vec3(0.0f); +} + +glm::vec3 MyAvatar::getRightHandPosition() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand); + return palmData.isValid() ? palmData.getPosition() : glm::vec3(0.0f); +} + +glm::vec3 MyAvatar::getLeftHandTipPosition() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand); + return palmData.isValid() ? palmData.getTipPosition() : glm::vec3(0.0f); +} + +glm::vec3 MyAvatar::getRightHandTipPosition() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand); + return palmData.isValid() ? palmData.getTipPosition() : glm::vec3(0.0f); +} + +controller::Pose MyAvatar::getLeftHandPose() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand); + return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(), + palmData.getVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); +} + +controller::Pose MyAvatar::getRightHandPose() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand); + return palmData.isValid() ? controller::Pose(palmData.getPosition(), palmData.getRotation(), + palmData.getVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); +} + +controller::Pose MyAvatar::getLeftHandTipPose() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::LeftHand); + return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(), + palmData.getTipVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); +} + +controller::Pose MyAvatar::getRightHandTipPose() const { + auto palmData = getHandData()->getCopyOfPalmData(HandData::RightHand); + return palmData.isValid() ? controller::Pose(palmData.getTipPosition(), palmData.getRotation(), + palmData.getTipVelocity(), palmData.getRawAngularVelocityAsQuat()) : controller::Pose(); +} + // virtual void MyAvatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { // don't render if we've been asked to disable local rendering @@ -1533,69 +1601,44 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { void MyAvatar::updateOrientation(float deltaTime) { // Smoothly rotate body with arrow keys - float targetSpeed = 0.0f; - - // FIXME - this comfort mode code is a total hack, remove it when we have new input mapping - bool isComfortMode = Menu::getInstance()->isOptionChecked(MenuOption::ComfortMode); - bool isHMDMode = qApp->getAvatarUpdater()->isHMDMode(); - - if (!isHMDMode || !isComfortMode) { - targetSpeed = (_driveKeys[ROT_LEFT] - _driveKeys[ROT_RIGHT]) * YAW_SPEED; - - if (targetSpeed != 0.0f) { - const float ROTATION_RAMP_TIMESCALE = 0.1f; - float blend = deltaTime / ROTATION_RAMP_TIMESCALE; - if (blend > 1.0f) { - blend = 1.0f; - } - _bodyYawDelta = (1.0f - blend) * _bodyYawDelta + blend * targetSpeed; - } else if (_bodyYawDelta != 0.0f) { - // attenuate body rotation speed - const float ROTATION_DECAY_TIMESCALE = 0.05f; - float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE; - if (attenuation < 0.0f) { - attenuation = 0.0f; - } - _bodyYawDelta *= attenuation; - - float MINIMUM_ROTATION_RATE = 2.0f; - if (fabsf(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { - _bodyYawDelta = 0.0f; - } + float targetSpeed = _driveKeys[YAW] * YAW_SPEED; + if (targetSpeed != 0.0f) { + const float ROTATION_RAMP_TIMESCALE = 0.1f; + float blend = deltaTime / ROTATION_RAMP_TIMESCALE; + if (blend > 1.0f) { + blend = 1.0f; } + _bodyYawDelta = (1.0f - blend) * _bodyYawDelta + blend * targetSpeed; + } else if (_bodyYawDelta != 0.0f) { + // attenuate body rotation speed + const float ROTATION_DECAY_TIMESCALE = 0.05f; + float attenuation = 1.0f - deltaTime / ROTATION_DECAY_TIMESCALE; + if (attenuation < 0.0f) { + attenuation = 0.0f; + } + _bodyYawDelta *= attenuation; - // update body orientation by movement inputs - setOrientation(getOrientation() * - glm::quat(glm::radians(glm::vec3(0.0f, _bodyYawDelta * deltaTime, 0.0f)))); - - } else { - // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll - // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another - // snap turn every half second. - _bodyYawDelta = 0.0f; - - static quint64 lastPulse = 0; - quint64 now = usecTimestampNow(); - quint64 COMFORT_MODE_PULSE_TIMING = USECS_PER_SECOND / 2; // turn once per half second - - float driveLeft = _driveKeys[ROT_LEFT]; - float driveRight= _driveKeys[ROT_RIGHT]; - - if ((driveLeft != 0.0f || driveRight != 0.0f) && (now - lastPulse > COMFORT_MODE_PULSE_TIMING)) { - lastPulse = now; - - const float SNAP_TURN_DELTA = 15.0f; // degrees - float direction = (driveLeft - driveRight) < 0.0f ? -1.0f : 1.0f; - float turnAmount = direction * SNAP_TURN_DELTA; - - // update body orientation by movement inputs - setOrientation(getOrientation() * - glm::quat(glm::radians(glm::vec3(0.0f, turnAmount, 0.0f)))); - + float MINIMUM_ROTATION_RATE = 2.0f; + if (fabsf(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { + _bodyYawDelta = 0.0f; } } - getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime); + float totalBodyYaw = _bodyYawDelta * deltaTime; + + + // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll + // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another + // snap turn every half second. + quint64 now = usecTimestampNow(); + if (_driveKeys[STEP_YAW] != 0.0f && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) { + totalBodyYaw += _driveKeys[STEP_YAW]; + } + + // update body orientation by movement inputs + setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)))); + + getHead()->setBasePitch(getHead()->getBasePitch() + _driveKeys[PITCH] * PITCH_SPEED * deltaTime); if (qApp->getAvatarUpdater()->isHMDMode()) { glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation(); @@ -1655,14 +1698,18 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); glm::vec3 newLocalVelocity = localVelocity; - float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + - (fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) + - fabsf(_driveKeys[UP] - _driveKeys[DOWN]); + float stepControllerInput = fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]) + fabsf(_driveKeys[STEP_TRANSLATE_Z]); + quint64 now = usecTimestampNow(); + // FIXME how do I implement step translation as well? + if (stepControllerInput && now - _lastStepPulse > COMFORT_MODE_PULSE_TIMING) { + } + + float keyboardInput = fabsf(_driveKeys[TRANSLATE_Z]) + fabsf(_driveKeys[TRANSLATE_X]) + fabsf(_driveKeys[TRANSLATE_Y]); if (keyboardInput) { // Compute keyboard input - glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT; - glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT; - glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP; + glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT; + glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT; + glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP; glm::vec3 direction = front + right + up; float directionLength = glm::length(direction); @@ -1713,7 +1760,7 @@ glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVe } } - float boomChange = _driveKeys[BOOM_OUT] - _driveKeys[BOOM_IN]; + float boomChange = _driveKeys[ZOOM]; _boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange; _boomLength = glm::clamp(_boomLength, ZOOM_MIN, ZOOM_MAX); @@ -1915,28 +1962,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() { _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); } -//Renders sixense laser pointers for UI selection with controllers -void MyAvatar::renderLaserPointers(gpu::Batch& batch) { - const float PALM_TIP_ROD_RADIUS = 0.002f; - - //If the Oculus is enabled, we will draw a blue cursor ray - - for (size_t i = 0; i < getHand()->getNumPalms(); ++i) { - PalmData& palm = getHand()->getPalms()[i]; - if (palm.isActive()) { - glm::vec3 tip = getLaserPointerTipPosition(&palm); - glm::vec3 root = palm.getPosition(); - - //Scale the root vector with the avatar scale - scaleVectorRelativeToPosition(root); - Transform transform = Transform(); - transform.setTranslation(glm::vec3()); - batch.setModelTransform(transform); - Avatar::renderJointConnectingCone(batch, root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS, glm::vec4(0, 1, 1, 1)); - } - } -} - //Gets the tip position for the laser pointer glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) { glm::vec3 direction = glm::normalize(palm->getTipPosition() - palm->getPosition()); @@ -1962,7 +1987,7 @@ void MyAvatar::clearDriveKeys() { } void MyAvatar::relayDriveKeysToCharacterController() { - if (_driveKeys[UP] > 0.0f) { + if (_driveKeys[TRANSLATE_Y] > 0.0f) { _characterController.jump(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 5f75b7cf14..38c2c00382 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -17,6 +17,8 @@ #include #include +#include + #include "Avatar.h" #include "AtRestDetector.h" #include "MyCharacterController.h" @@ -24,6 +26,20 @@ class ModelItemID; +enum DriveKeys { + TRANSLATE_X = 0, + TRANSLATE_Y, + TRANSLATE_Z, + YAW, + STEP_TRANSLATE_X, + STEP_TRANSLATE_Y, + STEP_TRANSLATE_Z, + STEP_YAW, + PITCH, + ZOOM, + MAX_DRIVE_KEYS +}; + enum eyeContactTarget { LEFT_EYE, RIGHT_EYE, @@ -37,7 +53,6 @@ enum AudioListenerMode { }; Q_DECLARE_METATYPE(AudioListenerMode); - class MyAvatar : public Avatar { Q_OBJECT Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) @@ -53,6 +68,17 @@ class MyAvatar : public Avatar { Q_PROPERTY(AudioListenerMode CUSTOM READ getAudioListenerModeCustom) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) + + Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) + Q_PROPERTY(glm::vec3 rightHandPosition READ getRightHandPosition) + Q_PROPERTY(glm::vec3 leftHandTipPosition READ getLeftHandTipPosition) + Q_PROPERTY(glm::vec3 rightHandTipPosition READ getRightHandTipPosition) + + Q_PROPERTY(controller::Pose leftHandPose READ getLeftHandPose) + Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose) + Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose) + Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose) + public: MyAvatar(RigPointer rig); ~MyAvatar(); @@ -111,6 +137,18 @@ public: Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role); Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url); void clearJointAnimationPriorities(); + // Adds handler(animStateDictionaryIn) => animStateDictionaryOut, which will be invoked just before each animGraph state update. + // The handler will be called with an animStateDictionaryIn that has all those properties specified by the (possibly empty) + // propertiesList argument. However for debugging, if the properties argument is null, all internal animGraph state is provided. + // The animStateDictionaryOut can be a different object than animStateDictionaryIn. Any properties set in animStateDictionaryOut + // will override those of the internal animation machinery. + // The animStateDictionaryIn may be shared among multiple handlers, and thus may contain additional properties specified when + // adding one of the other handlers. While any handler may change a value in animStateDictionaryIn (or supply different values in animStateDictionaryOut) + // a handler must not remove properties from animStateDictionaryIn, nor change property values that it does not intend to change. + // It is not specified in what order multiple handlers are called. + Q_INVOKABLE QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { return _rig->addAnimationStateHandler(handler, propertiesList); } + // Removes a handler previously added by addAnimationStateHandler. + Q_INVOKABLE void removeAnimationStateHandler(QScriptValue handler) { _rig->removeAnimationStateHandler(handler); } // get/set avatar data void saveData(); @@ -141,6 +179,16 @@ public: Q_INVOKABLE glm::vec3 getTargetAvatarPosition() const { return _targetAvatarPosition; } + Q_INVOKABLE glm::vec3 getLeftHandPosition() const; + Q_INVOKABLE glm::vec3 getRightHandPosition() const; + Q_INVOKABLE glm::vec3 getLeftHandTipPosition() const; + Q_INVOKABLE glm::vec3 getRightHandTipPosition() const; + + Q_INVOKABLE controller::Pose getLeftHandPose() const; + Q_INVOKABLE controller::Pose getRightHandPose() const; + Q_INVOKABLE controller::Pose getLeftHandTipPose() const; + Q_INVOKABLE controller::Pose getRightHandTipPose() const; + AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); @@ -265,7 +313,6 @@ private: const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool allowDuplicates = false, bool useSaved = true) override; - void renderLaserPointers(gpu::Batch& batch); const RecorderPointer getRecorder() const { return _recorder; } const PlayerPointer getPlayer() const { return _player; } @@ -281,6 +328,8 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); + PalmData getActivePalmData(int palmIndex) const; + // derive avatar body position and orientation from the current HMD Sensor location. // results are in sensor space glm::mat4 deriveBodyFromHMDSensor() const; @@ -370,6 +419,8 @@ private: AtRestDetector _hmdAtRestDetector; glm::vec3 _lastPosition; bool _lastIsMoving { false }; + quint64 _lastStepPulse { 0 }; + bool _pulseUpdate { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 71422f6780..1347c69d61 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -97,23 +97,9 @@ void SkeletonModel::initJointStates(QVector states) { emit skeletonLoaded(); } -static const PalmData* getPalmWithIndex(Hand* hand, int index) { - const PalmData* palm = nullptr; - for (size_t j = 0; j < hand->getNumPalms(); j++) { - if (hand->getPalms()[j].getSixenseID() == index) { - palm = &(hand->getPalms()[j]); - break; - } - } - return palm; -} - const float PALM_PRIORITY = DEFAULT_PRIORITY; // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { - if (_owningAvatar->isMyAvatar()) { - _rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation()); - } Head* head = _owningAvatar->getHead(); if (_owningAvatar->isMyAvatar()) { MyAvatar* myAvatar = static_cast(_owningAvatar); @@ -160,28 +146,30 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->updateFromHeadParameters(headParams, deltaTime); Rig::HandParameters handParams; - const PalmData* leftPalm = getPalmWithIndex(myAvatar->getHand(), LEFT_HAND_INDEX); - if (leftPalm && leftPalm->isActive()) { + + auto leftPalm = myAvatar->getHand()->getCopyOfPalmData(HandData::LeftHand); + if (leftPalm.isValid() && leftPalm.isActive()) { handParams.isLeftEnabled = true; - handParams.leftPosition = leftPalm->getRawPosition(); - handParams.leftOrientation = leftPalm->getRawRotation(); - handParams.leftTrigger = leftPalm->getTrigger(); + handParams.leftPosition = leftPalm.getRawPosition(); + handParams.leftOrientation = leftPalm.getRawRotation(); + handParams.leftTrigger = leftPalm.getTrigger(); } else { handParams.isLeftEnabled = false; } - const PalmData* rightPalm = getPalmWithIndex(myAvatar->getHand(), RIGHT_HAND_INDEX); - if (rightPalm && rightPalm->isActive()) { + auto rightPalm = myAvatar->getHand()->getCopyOfPalmData(HandData::RightHand); + if (rightPalm.isValid() && rightPalm.isActive()) { handParams.isRightEnabled = true; - handParams.rightPosition = rightPalm->getRawPosition(); - handParams.rightOrientation = rightPalm->getRawRotation(); - handParams.rightTrigger = rightPalm->getTrigger(); + handParams.rightPosition = rightPalm.getRawPosition(); + handParams.rightOrientation = rightPalm.getRawRotation(); + handParams.rightTrigger = rightPalm.getTrigger(); } else { handParams.isRightEnabled = false; } _rig->updateFromHandParameters(handParams, deltaTime); + _rig->computeMotionAnimationState(deltaTime, _owningAvatar->getPosition(), _owningAvatar->getVelocity(), _owningAvatar->getOrientation()); // evaluate AnimGraph animation and update jointStates. Model::updateRig(deltaTime, parentTransform); @@ -196,7 +184,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { _rig->updateFromEyeParameters(eyeParams); - // rebuild the jointState transform for the eyes only + // rebuild the jointState transform for the eyes only. Must be after updateRig. _rig->updateJointState(eyeParams.leftEyeJointIndex, parentTransform); _rig->updateJointState(eyeParams.rightEyeJointIndex, parentTransform); @@ -266,15 +254,15 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); - // find the left and rightmost active palms - int leftPalmIndex, rightPalmIndex; - Hand* hand = _owningAvatar->getHand(); - hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); - // Don't Relax toward hand positions when in animGraph mode. if (!_rig->getEnableAnimGraph()) { + + Hand* hand = _owningAvatar->getHand(); + auto leftPalm = hand->getCopyOfPalmData(HandData::LeftHand); + auto rightPalm = hand->getCopyOfPalmData(HandData::RightHand); + const float HAND_RESTORATION_RATE = 0.25f; - if (leftPalmIndex == -1 && rightPalmIndex == -1) { + if (!leftPalm.isActive() && !rightPalm.isActive()) { // palms are not yet set, use mouse if (_owningAvatar->getHandState() == HAND_STATE_NULL) { restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); @@ -284,20 +272,14 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { applyHandPosition(geometry.rightHandJointIndex, handPosition); } restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); - - } else if (leftPalmIndex == rightPalmIndex) { - // right hand only - applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[leftPalmIndex]); - restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); - } else { - if (leftPalmIndex != -1) { - applyPalmData(geometry.leftHandJointIndex, hand->getPalms()[leftPalmIndex]); + if (leftPalm.isActive()) { + applyPalmData(geometry.leftHandJointIndex, leftPalm); } else { restoreLeftHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); } - if (rightPalmIndex != -1) { - applyPalmData(geometry.rightHandJointIndex, hand->getPalms()[rightPalmIndex]); + if (rightPalm.isActive()) { + applyPalmData(geometry.rightHandJointIndex, rightPalm); } else { restoreRightHandPosition(HAND_RESTORATION_RATE, PALM_PRIORITY); } @@ -348,7 +330,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) PALM_PRIORITY); } -void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { +void SkeletonModel::applyPalmData(int jointIndex, const PalmData& palm) { if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) { return; } diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index d655d6e01f..dc08168a8c 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -118,7 +118,7 @@ protected: /// \param position position of joint in model-frame void applyHandPosition(int jointIndex, const glm::vec3& position); - void applyPalmData(int jointIndex, PalmData& palm); + void applyPalmData(int jointIndex, const PalmData& palm); private: void renderJointConstraints(gpu::Batch& batch, int jointIndex); diff --git a/interface/src/devices/3DConnexionClient.cpp b/interface/src/devices/3DConnexionClient.cpp index 722fedcc3a..38a9b4cb29 100755 --- a/interface/src/devices/3DConnexionClient.cpp +++ b/interface/src/devices/3DConnexionClient.cpp @@ -9,9 +9,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + #include "3DConnexionClient.h" + +#if 0 +#include +#include + #include "Menu.h" -#include "UserActivityLogger.h" const float MAX_AXIS = 75.0f; // max forward = 2x speed @@ -25,22 +30,19 @@ ConnexionData& ConnexionData::getInstance() { return sharedInstance; } -ConnexionData::ConnexionData() { -} + +ConnexionData::ConnexionData() : InputDevice("ConnexionClient") {} + 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; + auto rotation = cc_rotation / MAX_AXIS; + _axisStateMap[ROTATE_X] = rotation.x; + _axisStateMap[ROTATE_Y] = rotation.y; + _axisStateMap[ROTATE_Z] = rotation.z; + auto position = cc_rotation / MAX_AXIS; + _axisStateMap[TRANSLATE_X] = position.x; + _axisStateMap[TRANSLATE_Y] = position.y; + _axisStateMap[TRANSLATE_Z] = position.z; } void ConnexionData::setButton(int lastButtonState) { @@ -48,76 +50,65 @@ void ConnexionData::setButton(int lastButtonState) { _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")); - +void ConnexionData::buildDeviceProxy(controller::DeviceProxy::Pointer proxy) { + proxy->_name = _name = "ConnexionClient"; + proxy->getButton = [this](const controller::Input& input, int timestamp) -> bool { return this->getButton(input.getChannel()); }; + proxy->getAxis = [this](const controller::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; + proxy->getAvailabeInputs = [this]() -> QVector { + using namespace controller; + static QVector availableInputs { + Input::NamedPair(makeInput(BUTTON_1), "LeftButton"), + Input::NamedPair(makeInput(BUTTON_2), "RightButton"), + Input::NamedPair(makeInput(BUTTON_3), "BothButtons"), + Input::NamedPair(makeInput(TRANSLATE_X), "TranslateX"), + Input::NamedPair(makeInput(TRANSLATE_Y), "TranslateY"), + Input::NamedPair(makeInput(TRANSLATE_Z), "TranslateZ"), + Input::NamedPair(makeInput(ROTATE_X), "RotateX"), + Input::NamedPair(makeInput(ROTATE_Y), "RotateY"), + Input::NamedPair(makeInput(ROTATE_Z), "RotateZ"), + }; 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); - +QString ConnexionData::getDefaultMappingConfig() { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/vive.json"; + return MAPPING_JSON; } +//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()) { @@ -138,15 +129,15 @@ float ConnexionData::getAxis(int channel) const { } } -UserInputMapper::Input ConnexionData::makeInput(ConnexionData::ButtonChannel button) { - return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); +controller::Input ConnexionData::makeInput(ConnexionData::ButtonChannel button) { + return controller::Input(_deviceID, button, controller::ChannelType::BUTTON); } -UserInputMapper::Input ConnexionData::makeInput(ConnexionData::PositionChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +controller::Input ConnexionData::makeInput(ConnexionData::PositionChannel axis) { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); } -void ConnexionData::update() { +void ConnexionData::update(float deltaTime, bool jointsCaptured) { // 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 @@ -187,7 +178,6 @@ void ConnexionClient::destroy() { QAbstractEventDispatcher::instance()->removeNativeEventFilter(this); ConnexionData& connexiondata = ConnexionData::getInstance(); int deviceid = connexiondata.getDeviceID(); - connexiondata.setDeviceID(0); auto userInputMapper = DependencyManager::get(); userInputMapper->removeDevice(deviceid); } @@ -295,13 +285,10 @@ bool ConnexionClient::RawInputEventFilter(void* msg, long* result) { ConnexionData& connexiondata = ConnexionData::getInstance(); auto userInputMapper = DependencyManager::get(); if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { - connexiondata.registerToUserInputMapper(*userInputMapper); - connexiondata.assignDefaultInputMapping(*userInputMapper); + userInputMapper->registerDevice(&connexiondata); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); } else if (!Is3dmouseAttached() && connexiondata.getDeviceID() != 0) { - int deviceid = connexiondata.getDeviceID(); - connexiondata.setDeviceID(0); - userInputMapper->removeDevice(deviceid); + userInputMapper->removeDevice(connexiondata.getDeviceID()); } if (!Is3dmouseAttached()) { @@ -894,8 +881,7 @@ void ConnexionClient::init() { if (Is3dmouseAttached() && connexiondata.getDeviceID() == 0) { auto userInputMapper = DependencyManager::get(); - connexiondata.registerToUserInputMapper(*userInputMapper); - connexiondata.assignDefaultInputMapping(*userInputMapper); + userInputMapper->registerDevice(&connexiondata); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); } //let one axis be dominant @@ -926,8 +912,7 @@ void DeviceAddedHandler(unsigned int connection) { if (connexiondata.getDeviceID() == 0) { qCWarning(interfaceapp) << "3Dconnexion device added "; auto userInputMapper = DependencyManager::get(); - connexiondata.registerToUserInputMapper(*userInputMapper); - connexiondata.assignDefaultInputMapping(*userInputMapper); + userInputMapper->registerDevice(&connexiondata); UserActivityLogger::getInstance().connectedDevice("controller", "3Dconnexion"); } } @@ -984,3 +969,4 @@ void MessageHandler(unsigned int connection, unsigned int messageType, void *mes #endif // __APPLE__ #endif // HAVE_3DCONNEXIONCLIENT +#endif \ No newline at end of file diff --git a/interface/src/devices/3DConnexionClient.h b/interface/src/devices/3DConnexionClient.h index cdf8e1e2a1..03a43d4c64 100755 --- a/interface/src/devices/3DConnexionClient.h +++ b/interface/src/devices/3DConnexionClient.h @@ -11,9 +11,10 @@ #ifndef hifi_3DConnexionClient_h #define hifi_3DConnexionClient_h +#if 0 #include #include -#include +#include #include "InterfaceLogging.h" @@ -175,26 +176,19 @@ public slots: // connnects to the userinputmapper -class ConnexionData : public QObject { +class ConnexionData : public QObject, public controller::InputDevice { Q_OBJECT public: static ConnexionData& getInstance(); ConnexionData(); - enum PositionChannel { - POSITION_AXIS_X_POS = 1, - POSITION_AXIS_X_NEG = 2, - POSITION_AXIS_Y_POS = 3, - POSITION_AXIS_Y_NEG = 4, - POSITION_AXIS_Z_POS = 5, - POSITION_AXIS_Z_NEG = 6, - ROTATION_AXIS_X_POS = 7, - ROTATION_AXIS_X_NEG = 8, - ROTATION_AXIS_Y_POS = 9, - ROTATION_AXIS_Y_NEG = 10, - ROTATION_AXIS_Z_POS = 11, - ROTATION_AXIS_Z_NEG = 12 + TRANSLATE_X, + TRANSLATE_Y, + TRANSLATE_Z, + ROTATE_X, + ROTATE_Y, + ROTATE_Z, }; enum ButtonChannel { @@ -209,19 +203,12 @@ public: float getButton(int channel) const; float getAxis(int channel) const; - UserInputMapper::Input makeInput(ConnexionData::PositionChannel axis); - UserInputMapper::Input makeInput(ConnexionData::ButtonChannel button); - - void registerToUserInputMapper(UserInputMapper& mapper); - void assignDefaultInputMapping(UserInputMapper& mapper); - - void update(); - void focusOutEvent(); - - int getDeviceID() { return _deviceID; } - void setDeviceID(int deviceID) { _deviceID = deviceID; } - - QString _name; + controller::Input makeInput(ConnexionData::PositionChannel axis); + controller::Input makeInput(ConnexionData::ButtonChannel button); + virtual void buildDeviceProxy(controller::DeviceProxy::Pointer proxy) override; + virtual QString getDefaultMappingConfig() override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; glm::vec3 cc_position; glm::vec3 cc_rotation; @@ -229,12 +216,8 @@ public: void setButton(int lastButtonState); void handleAxisEvent(); - -protected: - int _deviceID = 0; - - ButtonPressedMap _buttonPressedMap; - AxisStateMap _axisStateMap; }; +#endif + #endif // defined(hifi_3DConnexionClient_h) diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 9bdf8d1a4a..547f16ea8b 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -9,120 +9,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "ControllerScriptingInterface.h" + #include #include #include #include +#include #include "Application.h" #include "devices/MotionTracker.h" -#include "ControllerScriptingInterface.h" // TODO: this needs to be removed, as well as any related controller-specific information #include - -ControllerScriptingInterface::ControllerScriptingInterface() : - _mouseCaptured(false), - _touchCaptured(false), - _wheelCaptured(false), - _actionsCaptured(false) -{ - -} - -static int actionMetaTypeId = qRegisterMetaType(); -static int inputChannelMetaTypeId = qRegisterMetaType(); -static int inputMetaTypeId = qRegisterMetaType(); -static int inputPairMetaTypeId = qRegisterMetaType(); - -QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::Input& input); -void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input); -QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel); -void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel); -QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action); -void actionFromScriptValue(const QScriptValue& object, UserInputMapper::Action& action); -QScriptValue inputPairToScriptValue(QScriptEngine* engine, const UserInputMapper::InputPair& inputPair); -void inputPairFromScriptValue(const QScriptValue& object, UserInputMapper::InputPair& inputPair); - -QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::Input& input) { - QScriptValue obj = engine->newObject(); - obj.setProperty("device", input.getDevice()); - obj.setProperty("channel", input.getChannel()); - obj.setProperty("type", (unsigned short) input.getType()); - obj.setProperty("id", input.getID()); - return obj; -} - -void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input) { - input.setDevice(object.property("device").toUInt16()); - input.setChannel(object.property("channel").toUInt16()); - input.setType(object.property("type").toUInt16()); - input.setID(object.property("id").toInt32()); -} - -QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel) { - QScriptValue obj = engine->newObject(); - obj.setProperty("input", inputToScriptValue(engine, inputChannel.getInput())); - obj.setProperty("modifier", inputToScriptValue(engine, inputChannel.getModifier())); - obj.setProperty("action", inputChannel.getAction()); - obj.setProperty("scale", inputChannel.getScale()); - return obj; -} - -void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel) { - UserInputMapper::Input input; - UserInputMapper::Input modifier; - inputFromScriptValue(object.property("input"), input); - inputChannel.setInput(input); - inputFromScriptValue(object.property("modifier"), modifier); - inputChannel.setModifier(modifier); - inputChannel.setAction(UserInputMapper::Action(object.property("action").toVariant().toInt())); - inputChannel.setScale(object.property("scale").toVariant().toFloat()); -} - -QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action) { - QScriptValue obj = engine->newObject(); - 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", userInputMapper->getActionName(action)); - obj.setProperty("inputChannels", _inputChannels); - return obj; -} - -void actionFromScriptValue(const QScriptValue& object, UserInputMapper::Action& action) { - action = UserInputMapper::Action(object.property("action").toVariant().toInt()); -} - -QScriptValue inputPairToScriptValue(QScriptEngine* engine, const UserInputMapper::InputPair& inputPair) { - QScriptValue obj = engine->newObject(); - obj.setProperty("input", inputToScriptValue(engine, inputPair.first)); - obj.setProperty("inputName", inputPair.second); - return obj; -} - -void inputPairFromScriptValue(const QScriptValue& object, UserInputMapper::InputPair& inputPair) { - inputFromScriptValue(object.property("input"), inputPair.first); - inputPair.second = QString(object.property("inputName").toVariant().toString()); -} - -void ControllerScriptingInterface::registerControllerTypes(ScriptEngine* engine) { - qScriptRegisterSequenceMetaType >(engine); - qScriptRegisterSequenceMetaType >(engine); - qScriptRegisterSequenceMetaType >(engine); - qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); - qScriptRegisterMetaType(engine, inputChannelToScriptValue, inputChannelFromScriptValue); - qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); - qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); - - wireUpControllers(engine); -} - void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) { if (event->type() == HFActionEvent::startType()) { emit actionStartEvent(static_cast(*event)); @@ -135,205 +35,6 @@ void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) { } } -const PalmData* ControllerScriptingInterface::getPrimaryPalm() const { - int leftPalmIndex, rightPalmIndex; - - const HandData* handData = DependencyManager::get()->getMyAvatar()->getHandData(); - handData->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex); - - if (rightPalmIndex != -1) { - return &handData->getPalms()[rightPalmIndex]; - } - - return NULL; -} - -int ControllerScriptingInterface::getNumberOfActivePalms() const { - const HandData* handData = DependencyManager::get()->getMyAvatar()->getHandData(); - int numberOfPalms = handData->getNumPalms(); - int numberOfActivePalms = 0; - for (int i = 0; i < numberOfPalms; i++) { - if (getPalm(i)->isActive()) { - numberOfActivePalms++; - } - } - return numberOfActivePalms; -} - -const PalmData* ControllerScriptingInterface::getPalm(int palmIndex) const { - const HandData* handData = DependencyManager::get()->getMyAvatar()->getHandData(); - return &handData->getPalms()[palmIndex]; -} - -const PalmData* ControllerScriptingInterface::getActivePalm(int palmIndex) const { - const HandData* handData = DependencyManager::get()->getMyAvatar()->getHandData(); - int numberOfPalms = handData->getNumPalms(); - int numberOfActivePalms = 0; - for (int i = 0; i < numberOfPalms; i++) { - if (getPalm(i)->isActive()) { - if (numberOfActivePalms == palmIndex) { - return &handData->getPalms()[i]; - } - numberOfActivePalms++; - } - } - return NULL; -} - -bool ControllerScriptingInterface::isPrimaryButtonPressed() const { - const PalmData* primaryPalm = getPrimaryPalm(); - if (primaryPalm) { - if (primaryPalm->getControllerButtons() & BUTTON_FWD) { - return true; - } - } - - return false; -} - -glm::vec2 ControllerScriptingInterface::getPrimaryJoystickPosition() const { - const PalmData* primaryPalm = getPrimaryPalm(); - if (primaryPalm) { - return glm::vec2(primaryPalm->getJoystickX(), primaryPalm->getJoystickY()); - } - - return glm::vec2(0); -} - -int ControllerScriptingInterface::getNumberOfButtons() const { - return getNumberOfActivePalms() * NUMBER_OF_BUTTONS_PER_PALM; -} - -bool ControllerScriptingInterface::isButtonPressed(int buttonIndex) const { - int palmIndex = buttonIndex / NUMBER_OF_BUTTONS_PER_PALM; - int buttonOnPalm = buttonIndex % NUMBER_OF_BUTTONS_PER_PALM; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - switch (buttonOnPalm) { - case 0: - return palmData->getControllerButtons() & BUTTON_0; - case 1: - return palmData->getControllerButtons() & BUTTON_1; - case 2: - return palmData->getControllerButtons() & BUTTON_2; - case 3: - return palmData->getControllerButtons() & BUTTON_3; - case 4: - return palmData->getControllerButtons() & BUTTON_4; - case 5: - return palmData->getControllerButtons() & BUTTON_FWD; - } - } - return false; -} - -int ControllerScriptingInterface::getNumberOfTriggers() const { - return getNumberOfActivePalms() * NUMBER_OF_TRIGGERS_PER_PALM; -} - -float ControllerScriptingInterface::getTriggerValue(int triggerIndex) const { - // we know there's one trigger per palm, so the triggerIndex is the palm Index - int palmIndex = triggerIndex; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - return palmData->getTrigger(); - } - return 0.0f; -} - -int ControllerScriptingInterface::getNumberOfJoysticks() const { - return getNumberOfActivePalms() * NUMBER_OF_JOYSTICKS_PER_PALM; -} - -glm::vec2 ControllerScriptingInterface::getJoystickPosition(int joystickIndex) const { - // we know there's one joystick per palm, so the joystickIndex is the palm Index - int palmIndex = joystickIndex; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - return glm::vec2(palmData->getJoystickX(), palmData->getJoystickY()); - } - return glm::vec2(0); -} - -int ControllerScriptingInterface::getNumberOfSpatialControls() const { - return getNumberOfActivePalms() * NUMBER_OF_SPATIALCONTROLS_PER_PALM; -} - -glm::vec3 ControllerScriptingInterface::getSpatialControlPosition(int controlIndex) const { - int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM; - int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - switch (controlOfPalm) { - case PALM_SPATIALCONTROL: - return palmData->getPosition(); - case TIP_SPATIALCONTROL: - return palmData->getTipPosition(); - } - } - return glm::vec3(0); // bad index -} - -glm::vec3 ControllerScriptingInterface::getSpatialControlVelocity(int controlIndex) const { - int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM; - int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - switch (controlOfPalm) { - case PALM_SPATIALCONTROL: - return palmData->getVelocity(); - case TIP_SPATIALCONTROL: - return palmData->getTipVelocity(); - } - } - return glm::vec3(0); // bad index -} - -glm::quat ControllerScriptingInterface::getSpatialControlRawRotation(int controlIndex) const { - int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM; - int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - switch (controlOfPalm) { - case PALM_SPATIALCONTROL: - return palmData->getRawRotation(); - case TIP_SPATIALCONTROL: - return palmData->getRawRotation(); // currently the tip doesn't have a unique rotation, use the palm rotation - } - } - return glm::quat(); // bad index -} - -glm::vec3 ControllerScriptingInterface::getSpatialControlRawAngularVelocity(int controlIndex) const { - int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM; - int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - switch (controlOfPalm) { - case PALM_SPATIALCONTROL: - return palmData->getRawAngularVelocity(); - case TIP_SPATIALCONTROL: - return palmData->getRawAngularVelocity(); // Tip = palm angular velocity - } - } - return glm::vec3(0); // bad index -} - -glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex) const { - int palmIndex = controlIndex / NUMBER_OF_SPATIALCONTROLS_PER_PALM; - int controlOfPalm = controlIndex % NUMBER_OF_SPATIALCONTROLS_PER_PALM; - const PalmData* palmData = getActivePalm(palmIndex); - if (palmData) { - switch (controlOfPalm) { - case PALM_SPATIALCONTROL: - return palmData->getNormal(); - case TIP_SPATIALCONTROL: - return palmData->getFingerDirection(); - } - } - return glm::vec3(0); // bad index -} - bool ControllerScriptingInterface::isKeyCaptured(QKeyEvent* event) const { return isKeyCaptured(KeyEvent(*event)); } @@ -383,157 +84,45 @@ glm::vec2 ControllerScriptingInterface::getViewportDimensions() const { return qApp->getUiSize(); } -QString ControllerScriptingInterface::sanatizeName(const QString& name) { - QString cleanName { name }; - cleanName.remove(QRegularExpression{"[\\(\\)\\.\\s]"}); - return cleanName; -} - -void ControllerScriptingInterface::wireUpControllers(ScriptEngine* engine) { - - // Controller.Standard.* - auto standardDevice = DependencyManager::get()->getStandardDevice(); - if (standardDevice) { - auto deviceName = sanatizeName(standardDevice->getName()); - auto deviceInputs = standardDevice->getAvailabeInputs(); - for (const auto& inputMapping : deviceInputs) { - auto input = inputMapping.first; - auto inputName = sanatizeName(inputMapping.second); - QString deviceInputName{ "Controller." + deviceName + "." + inputName }; - engine->registerValue(deviceInputName, input.getID()); - } - } - - // Controller.Hardware.* - auto devices = DependencyManager::get()->getDevices(); - for(const auto& deviceMapping : devices) { - auto device = deviceMapping.second.get(); - auto deviceName = sanatizeName(device->getName()); - auto deviceInputs = device->getAvailabeInputs(); - for (const auto& inputMapping : deviceInputs) { - auto input = inputMapping.first; - auto inputName = sanatizeName(inputMapping.second); - QString deviceInputName { "Controller.Hardware." + deviceName + "." + inputName }; - engine->registerValue(deviceInputName, input.getID()); - } - } - - // Controller.Actions.* - auto actionNames = DependencyManager::get()->getActionNames(); - int actionNumber = 0; - for (const auto& actionName : actionNames) { - QString safeActionName { "Controller.Actions." + sanatizeName(actionName) }; - engine->registerValue(safeActionName, actionNumber); - actionNumber++; - } -} - -AbstractInputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) { +controller::InputController::Pointer ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) { // This is where we retreive the Device Tracker category and then the sub tracker within it - //TODO C++11 auto icIt = _inputControllers.find(0); - InputControllerMap::iterator icIt = _inputControllers.find(0); - + auto icIt = _inputControllers.find(0); if (icIt != _inputControllers.end()) { return (*icIt).second; - } else { + } - // Look for device - DeviceTracker::ID deviceID = DeviceTracker::getDeviceID(deviceName.toStdString()); - if (deviceID < 0) { - deviceID = 0; - } - // TODO in this current implementation, we just pick the device assuming there is one (normally the Leapmotion) - // in the near future we need to change that to a real mapping between the devices and the deviceName - // ALso we need to expand the spec so we can fall back on the "default" controller per categories - if (deviceID >= 0) { - // TODO here again the assumption it's the LeapMotion and so it's a MOtionTracker, this would need to be changed to support different types of devices - MotionTracker* motionTracker = dynamic_cast< MotionTracker* > (DeviceTracker::getDevice(deviceID)); - if (motionTracker) { - MotionTracker::Index trackerID = motionTracker->findJointIndex(tracker.toStdString()); - if (trackerID >= 0) { - AbstractInputController* inputController = new InputController(deviceID, trackerID, this); + // Look for device + DeviceTracker::ID deviceID = DeviceTracker::getDeviceID(deviceName.toStdString()); + if (deviceID < 0) { + deviceID = 0; + } + // TODO in this current implementation, we just pick the device assuming there is one (normally the Leapmotion) + // in the near future we need to change that to a real mapping between the devices and the deviceName + // ALso we need to expand the spec so we can fall back on the "default" controller per categories - _inputControllers.insert(InputControllerMap::value_type(inputController->getKey(), inputController)); - - return inputController; - } + if (deviceID >= 0) { + // TODO here again the assumption it's the LeapMotion and so it's a MOtionTracker, this would need to be changed to support different types of devices + MotionTracker* motionTracker = dynamic_cast< MotionTracker* > (DeviceTracker::getDevice(deviceID)); + if (motionTracker) { + MotionTracker::Index trackerID = motionTracker->findJointIndex(tracker.toStdString()); + if (trackerID >= 0) { + controller::InputController::Pointer inputController = std::make_shared(deviceID, trackerID, this); + controller::InputController::Key key = inputController->getKey(); + _inputControllers.insert(InputControllerMap::value_type(key, inputController)); + return inputController; } } - - return 0; } + + return controller::InputController::Pointer(); } -void ControllerScriptingInterface::releaseInputController(AbstractInputController* input) { +void ControllerScriptingInterface::releaseInputController(controller::InputController::Pointer input) { _inputControllers.erase(input->getKey()); } -void ControllerScriptingInterface::updateInputControllers() { - //TODO C++11 for (auto it = _inputControllers.begin(); it != _inputControllers.end(); it++) { - for (InputControllerMap::iterator it = _inputControllers.begin(); it != _inputControllers.end(); it++) { - (*it).second->update(); - } -} - -QVector ControllerScriptingInterface::getAllActions() { - return DependencyManager::get()->getAllActions(); -} - -QVector ControllerScriptingInterface::getInputChannelsForAction(UserInputMapper::Action action) { - return DependencyManager::get()->getInputChannelsForAction(action); -} - -QString ControllerScriptingInterface::getDeviceName(unsigned int device) { - return DependencyManager::get()->getDeviceName((unsigned short)device); -} - -QVector ControllerScriptingInterface::getAllInputsForDevice(unsigned int device) { - return DependencyManager::get()->getAllInputsForDevice(device); -} - -bool ControllerScriptingInterface::addInputChannel(UserInputMapper::InputChannel inputChannel) { - return DependencyManager::get()->addInputChannel(inputChannel._action, inputChannel._input, inputChannel._modifier, inputChannel._scale); -} - -bool ControllerScriptingInterface::removeInputChannel(UserInputMapper::InputChannel inputChannel) { - return DependencyManager::get()->removeInputChannel(inputChannel); -} - -QVector ControllerScriptingInterface::getAvailableInputs(unsigned int device) { - return DependencyManager::get()->getAvailableInputs((unsigned short)device); -} - -void ControllerScriptingInterface::resetAllDeviceBindings() { - DependencyManager::get()->resetAllDeviceBindings(); -} - -void ControllerScriptingInterface::resetDevice(unsigned int device) { - DependencyManager::get()->resetDevice(device); -} - -int ControllerScriptingInterface::findDevice(QString name) { - return DependencyManager::get()->findDevice(name); -} - -QVector ControllerScriptingInterface::getDeviceNames() { - return DependencyManager::get()->getDeviceNames(); -} - -float ControllerScriptingInterface::getActionValue(int action) { - return DependencyManager::get()->getActionState(UserInputMapper::Action(action)); -} - -int ControllerScriptingInterface::findAction(QString actionName) { - return DependencyManager::get()->findAction(actionName); -} - -QVector ControllerScriptingInterface::getActionNames() const { - return DependencyManager::get()->getActionNames(); -} - InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) : - AbstractInputController(), _deviceTrackerId(deviceTrackerId), _subTrackerId(subTrackerId), _isActive(false) @@ -556,7 +145,7 @@ void InputController::update() { joint->getLocFrame().getRotation(_eventCache.locRotation); _isActive = true; - emit spatialEvent(_eventCache); + //emit spatialEvent(_eventCache); } } } @@ -568,3 +157,19 @@ const unsigned int INPUTCONTROLLER_KEY_DEVICE_MASK = 16; InputController::Key InputController::getKey() const { return (((_deviceTrackerId & INPUTCONTROLLER_KEY_DEVICE_MASK) << INPUTCONTROLLER_KEY_DEVICE_OFFSET) | _subTrackerId); } + + +void ControllerScriptingInterface::emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); } +void ControllerScriptingInterface::emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); } + +void ControllerScriptingInterface::emitMouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { emit mouseMoveEvent(MouseEvent(*event, deviceID)); } +void ControllerScriptingInterface::emitMousePressEvent(QMouseEvent* event, unsigned int deviceID) { emit mousePressEvent(MouseEvent(*event, deviceID)); } +void ControllerScriptingInterface::emitMouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID) { emit mouseDoublePressEvent(MouseEvent(*event, deviceID)); } +void ControllerScriptingInterface::emitMouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { emit mouseReleaseEvent(MouseEvent(*event, deviceID)); } + +void ControllerScriptingInterface::emitTouchBeginEvent(const TouchEvent& event) { emit touchBeginEvent(event); } +void ControllerScriptingInterface::emitTouchEndEvent(const TouchEvent& event) { emit touchEndEvent(event); } +void ControllerScriptingInterface::emitTouchUpdateEvent(const TouchEvent& event) { emit touchUpdateEvent(event); } + +void ControllerScriptingInterface::emitWheelEvent(QWheelEvent* event) { emit wheelEvent(*event); } + diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index aa0526accb..4c69551dd2 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -14,12 +14,20 @@ #include -#include +#include +#include + +#include +#include +#include +#include +#include +#include +class ScriptEngine; -#include class PalmData; -class InputController : public AbstractInputController { +class InputController : public controller::InputController { Q_OBJECT public: @@ -50,125 +58,77 @@ signals: /// handles scripting of input controller commands from JS -class ControllerScriptingInterface : public AbstractControllerScriptingInterface { +class ControllerScriptingInterface : public controller::ScriptingInterface { Q_OBJECT + public: - ControllerScriptingInterface(); - - virtual void registerControllerTypes(ScriptEngine* engine); - - void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); } - void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); } + virtual ~ControllerScriptingInterface() {} + + void emitKeyPressEvent(QKeyEvent* event); + void emitKeyReleaseEvent(QKeyEvent* event); void handleMetaEvent(HFMetaEvent* event); - void emitMouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0) { emit mouseMoveEvent(MouseEvent(*event, deviceID)); } - void emitMousePressEvent(QMouseEvent* event, unsigned int deviceID = 0) { emit mousePressEvent(MouseEvent(*event, deviceID)); } - void emitMouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID = 0) { emit mouseDoublePressEvent(MouseEvent(*event, deviceID)); } - void emitMouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0) { emit mouseReleaseEvent(MouseEvent(*event, deviceID)); } + void emitMouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0); + void emitMousePressEvent(QMouseEvent* event, unsigned int deviceID = 0); + void emitMouseDoublePressEvent(QMouseEvent* event, unsigned int deviceID = 0); + void emitMouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0); - void emitTouchBeginEvent(const TouchEvent& event) { emit touchBeginEvent(event); } - void emitTouchEndEvent(const TouchEvent& event) { emit touchEndEvent(event); } - void emitTouchUpdateEvent(const TouchEvent& event) { emit touchUpdateEvent(event); } + void emitTouchBeginEvent(const TouchEvent& event); + void emitTouchEndEvent(const TouchEvent& event); + void emitTouchUpdateEvent(const TouchEvent& event); - void emitWheelEvent(QWheelEvent* event) { emit wheelEvent(*event); } + void emitWheelEvent(QWheelEvent* event); bool isKeyCaptured(QKeyEvent* event) const; bool isKeyCaptured(const KeyEvent& event) const; - bool isMouseCaptured() const { return _mouseCaptured; } - bool isTouchCaptured() const { return _touchCaptured; } - bool isWheelCaptured() const { return _wheelCaptured; } - bool areActionsCaptured() const { return _actionsCaptured; } bool isJoystickCaptured(int joystickIndex) const; - void updateInputControllers(); - public slots: - Q_INVOKABLE virtual QVector getAllActions(); - - 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 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 QVector getDeviceNames(); - - Q_INVOKABLE virtual int findAction(QString actionName); - Q_INVOKABLE virtual QVector getActionNames() const; - - virtual bool isPrimaryButtonPressed() const; - virtual glm::vec2 getPrimaryJoystickPosition() const; - - virtual int getNumberOfButtons() const; - virtual bool isButtonPressed(int buttonIndex) const; - - virtual int getNumberOfTriggers() const; - virtual float getTriggerValue(int triggerIndex) const; - - virtual int getNumberOfJoysticks() const; - virtual glm::vec2 getJoystickPosition(int joystickIndex) const; - - virtual int getNumberOfSpatialControls() const; - virtual glm::vec3 getSpatialControlPosition(int controlIndex) const; - virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const; - virtual glm::vec3 getSpatialControlNormal(int controlIndex) const; - virtual glm::quat getSpatialControlRawRotation(int controlIndex) const; - virtual glm::vec3 getSpatialControlRawAngularVelocity(int controlIndex) const; virtual void captureKeyEvents(const KeyEvent& event); virtual void releaseKeyEvents(const KeyEvent& event); - virtual void captureMouseEvents() { _mouseCaptured = true; } - virtual void releaseMouseEvents() { _mouseCaptured = false; } - - virtual void captureTouchEvents() { _touchCaptured = true; } - virtual void releaseTouchEvents() { _touchCaptured = false; } - - virtual void captureWheelEvents() { _wheelCaptured = true; } - virtual void releaseWheelEvents() { _wheelCaptured = false; } - - virtual void captureActionEvents() { _actionsCaptured = true; } - virtual void releaseActionEvents() { _actionsCaptured = false; } - virtual void captureJoystick(int joystickIndex); virtual void releaseJoystick(int joystickIndex); virtual glm::vec2 getViewportDimensions() const; /// Factory to create an InputController - virtual AbstractInputController* createInputController(const QString& deviceName, const QString& tracker); + virtual controller::InputController::Pointer createInputController(const QString& deviceName, const QString& tracker); + virtual void releaseInputController(controller::InputController::Pointer input); - virtual void releaseInputController(AbstractInputController* input); +signals: + void keyPressEvent(const KeyEvent& event); + void keyReleaseEvent(const KeyEvent& event); + + void actionStartEvent(const HFActionEvent& event); + void actionEndEvent(const HFActionEvent& event); + + void backStartEvent(); + void backEndEvent(); + + void mouseMoveEvent(const MouseEvent& event, unsigned int deviceID = 0); + void mousePressEvent(const MouseEvent& event, unsigned int deviceID = 0); + void mouseDoublePressEvent(const MouseEvent& event, unsigned int deviceID = 0); + void mouseReleaseEvent(const MouseEvent& event, unsigned int deviceID = 0); + + void touchBeginEvent(const TouchEvent& event); + void touchEndEvent(const TouchEvent& event); + void touchUpdateEvent(const TouchEvent& event); + + void wheelEvent(const WheelEvent& event); private: QString sanatizeName(const QString& name); /// makes a name clean for inclusing in JavaScript - const PalmData* getPrimaryPalm() const; - const PalmData* getPalm(int palmIndex) const; - int getNumberOfActivePalms() const; - const PalmData* getActivePalm(int palmIndex) const; - - bool _mouseCaptured; - bool _touchCaptured; - bool _wheelCaptured; - bool _actionsCaptured; QMultiMap _capturedKeys; QSet _capturedJoysticks; - typedef std::map< AbstractInputController::Key, AbstractInputController* > InputControllerMap; + using InputKey = controller::InputController::Key; + using InputControllerMap = std::map; InputControllerMap _inputControllers; - - void wireUpControllers(ScriptEngine* engine); - }; const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 4e5dd0da0c..2a2a45b67b 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -27,7 +27,7 @@ #include "Application.h" #include // TODO: any references to sixense should be removed here -#include +#include // Used to animate the magnification windows @@ -320,8 +320,8 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int // Only render the hand pointers if the EnableHandMouseInput is enabled if (Menu::getInstance()->isOptionChecked(MenuOption::EnableHandMouseInput)) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { - PalmData& palm = myAvatar->getHand()->getPalms()[i]; + auto palms = myAvatar->getHand()->getCopyOfPalms(); + for (const auto& palm : palms) { if (palm.isActive()) { glm::vec2 polar = getPolarCoordinates(palm); // Convert to quaternion @@ -446,6 +446,7 @@ void ApplicationCompositor::renderPointers(gpu::Batch& batch) { } +// FIXME - this is old code that likely needs to be removed and/or reworked to support the new input control model void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); @@ -455,23 +456,24 @@ void ApplicationCompositor::renderControllerPointers(gpu::Batch& batch) { static bool stateWhenPressed[NUMBER_OF_RETICLES] = { false, false, false }; const HandData* handData = DependencyManager::get()->getMyAvatar()->getHandData(); + auto palms = handData->getCopyOfPalms(); for (unsigned int palmIndex = 2; palmIndex < 4; palmIndex++) { const int index = palmIndex - 1; const PalmData* palmData = NULL; - if (palmIndex >= handData->getPalms().size()) { + if (palmIndex >= palms.size()) { return; } - if (handData->getPalms()[palmIndex].isActive()) { - palmData = &handData->getPalms()[palmIndex]; + if (palms[palmIndex].isActive()) { + palmData = &palms[palmIndex]; } else { continue; } - int controllerButtons = palmData->getControllerButtons(); + int controllerButtons = 0; //Check for if we should toggle or drag the magnification window if (controllerButtons & BUTTON_3) { @@ -521,7 +523,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 * InputDevice::getCursorPixelRangeMult(); + float cursorRange = canvasSize.x * controller::InputDevice::getCursorPixelRangeMult(); mouseX = (canvasSize.x / 2.0f + cursorRange * xAngle); mouseY = (canvasSize.y / 2.0f + cursorRange * yAngle); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 4ba248c76c..d4bab86126 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -198,7 +198,7 @@ void PreferencesDialog::loadPreferences() { ui.oculusUIAngularSizeSpin->setValue(qApp->getApplicationCompositor().getHmdUIAngularSize()); #endif - ui.sixenseReticleMoveSpeedSpin->setValue(InputDevice::getReticleMoveSpeed()); + ui.sixenseReticleMoveSpeedSpin->setValue(controller::InputDevice::getReticleMoveSpeed()); // LOD items auto lodManager = DependencyManager::get(); @@ -273,7 +273,7 @@ void PreferencesDialog::savePreferences() { qApp->getApplicationCompositor().setHmdUIAngularSize(ui.oculusUIAngularSizeSpin->value()); - InputDevice::setReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); + controller::InputDevice::setReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); auto audio = DependencyManager::get(); MixedProcessedAudioStream& stream = audio->getReceivedAudioStream(); diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp new file mode 100644 index 0000000000..8d320195dd --- /dev/null +++ b/libraries/animation/src/AnimVariant.cpp @@ -0,0 +1,121 @@ +// +// AnimVariantMap.cpp +// library/animation +// +// Created by Howard Stearns on 10/15/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// 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 +#include +#include +#include "AnimVariant.h" // which has AnimVariant/AnimVariantMap + +QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const { + if (QThread::currentThread() != engine->thread()) { + qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread(); + Q_ASSERT(false); + return QScriptValue(); + } + QScriptValue target = engine->newObject(); + auto setOne = [&] (const QString& name, const AnimVariant& value) { + switch (value.getType()) { + case AnimVariant::Type::Bool: + target.setProperty(name, value.getBool()); + break; + case AnimVariant::Type::Int: + target.setProperty(name, value.getInt()); + break; + case AnimVariant::Type::Float: + target.setProperty(name, value.getFloat()); + break; + case AnimVariant::Type::String: + target.setProperty(name, value.getString()); + break; + case AnimVariant::Type::Vec3: + target.setProperty(name, vec3toScriptValue(engine, value.getVec3())); + break; + case AnimVariant::Type::Quat: + target.setProperty(name, quatToScriptValue(engine, value.getQuat())); + break; + default: + // Note that we don't do mat4 in Javascript currently, and there's not yet a reason to start now. + assert("AnimVariant::Type" == "valid"); + } + }; + if (useNames) { // copy only the requested names + for (const QString& name : names) { + auto search = _map.find(name); + if (search != _map.end()) { // scripts are allowed to request names that do not exist + setOne(name, search->second); + } + } + + } else { // copy all of them + for (auto& pair : _map) { + setOne(pair.first, pair.second); + } + } + return target; +} +void AnimVariantMap::copyVariantsFrom(const AnimVariantMap& other) { + for (auto& pair : other._map) { + _map[pair.first] = pair.second; + } +} + +void AnimVariantMap::animVariantMapFromScriptValue(const QScriptValue& source) { + if (QThread::currentThread() != source.engine()->thread()) { + qCWarning(animation) << "Cannot examine Javacript object from non-script thread" << QThread::currentThread(); + Q_ASSERT(false); + return; + } + // POTENTIAL OPTIMIZATION: cache the types we've seen. I.e, keep a dictionary mapping property names to an enumeration of types. + // Whenever we identify a new outbound type in animVariantMapToScriptValue above, or a new inbound type in the code that follows here, + // we would enter it into the dictionary. Then switch on that type here, with the code that follow being executed only if + // the type is not known. One problem with that is that there is no checking that two different script use the same name differently. + QScriptValueIterator property(source); + // Note: QScriptValueIterator iterates only over source's own properties. It does not follow the prototype chain. + while (property.hasNext()) { + property.next(); + QScriptValue value = property.value(); + if (value.isBool()) { + set(property.name(), value.toBool()); + } else if (value.isString()) { + set(property.name(), value.toString()); + } else if (value.isNumber()) { + int asInteger = value.toInt32(); + float asFloat = value.toNumber(); + if (asInteger == asFloat) { + set(property.name(), asInteger); + } else { + set(property.name(), asFloat); + } + } else { // Try to get x,y,z and possibly w + if (value.isObject()) { + QScriptValue x = value.property("x"); + if (x.isNumber()) { + QScriptValue y = value.property("y"); + if (y.isNumber()) { + QScriptValue z = value.property("z"); + if (z.isNumber()) { + QScriptValue w = value.property("w"); + if (w.isNumber()) { + set(property.name(), glm::quat(x.toNumber(), y.toNumber(), z.toNumber(), w.toNumber())); + } else { + set(property.name(), glm::vec3(x.toNumber(), y.toNumber(), z.toNumber())); + } + continue; // we got either a vector or quaternion object, so don't fall through to warning + } + } + } + } + qCWarning(animation) << "Ignoring unrecognized data" << value.toString() << "for animation property" << property.name(); + Q_ASSERT(false); + } + } +} diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index cb886cd369..0d7c657058 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -12,11 +12,14 @@ #define hifi_AnimVariant_h #include +#include #include #include #include #include +#include #include "AnimationLogging.h" +#include "StreamUtils.h" class AnimVariant { public: @@ -58,8 +61,9 @@ public: void setString(const QString& value) { assert(_type == Type::String); _stringVal = value; } bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; } - int getInt() const { assert(_type == Type::Int); return _val.intVal; } - float getFloat() const { assert(_type == Type::Float); return _val.floats[0]; } + int getInt() const { assert(_type == Type::Int || _type == Type::Float); return _type == Type::Float ? (int)_val.floats[0] : _val.intVal; } + float getFloat() const { assert(_type == Type::Float || _type == Type::Int); return _type == Type::Int ? (float)_val.intVal : _val.floats[0]; } + const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast(&_val); } const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast(&_val); } const glm::mat4& getMat4() const { assert(_type == Type::Mat4); return *reinterpret_cast(&_val); } @@ -156,8 +160,15 @@ public: void setTrigger(const QString& key) { _triggers.insert(key); } void clearTriggers() { _triggers.clear(); } + void clearMap() { _map.clear(); } bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); } + // Answer a Plain Old Javascript Object (for the given engine) all of our values set as properties. + QScriptValue animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const; + // Side-effect us with the value of object's own properties. (No inherited properties.) + void animVariantMapFromScriptValue(const QScriptValue& object); + void copyVariantsFrom(const AnimVariantMap& other); + #ifdef NDEBUG void dump() const { qCDebug(animation) << "AnimVariantMap ="; @@ -196,4 +207,8 @@ protected: std::set _triggers; }; +typedef std::function AnimVariantResultHandler; +Q_DECLARE_METATYPE(AnimVariantResultHandler); +Q_DECLARE_METATYPE(AnimVariantMap) + #endif // hifi_AnimVariant_h diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 3fe4c2e83e..4ddae07375 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -576,6 +577,71 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos _lastPosition = worldPosition; } +// Allow script to add/remove handlers and report results, from within their thread. +QScriptValue Rig::addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList) { // called in script thread + QMutexLocker locker(&_stateMutex); + // Find a safe id, even if there are lots of many scripts add and remove handlers repeatedly. + while (!_nextStateHandlerId || _stateHandlers.contains(_nextStateHandlerId)) { // 0 is unused, and don't reuse existing after wrap. + _nextStateHandlerId++; + } + StateHandler& data = _stateHandlers[_nextStateHandlerId]; + data.function = handler; + data.useNames = propertiesList.isArray(); + if (data.useNames) { + data.propertyNames = propertiesList.toVariant().toStringList(); + } + return QScriptValue(_nextStateHandlerId); // suitable for giving to removeAnimationStateHandler +} +void Rig::removeAnimationStateHandler(QScriptValue identifier) { // called in script thread + QMutexLocker locker(&_stateMutex); + _stateHandlers.remove(identifier.isNumber() ? identifier.toInt32() : 0); // silently continues if handler not present. 0 is unused +} +void Rig::animationStateHandlerResult(int identifier, QScriptValue result) { // called synchronously from script + QMutexLocker locker(&_stateMutex); + auto found = _stateHandlers.find(identifier); + if (found == _stateHandlers.end()) { + return; // Don't use late-breaking results that got reported after the handler was removed. + } + found.value().results.animVariantMapFromScriptValue(result); // Into our own copy. +} + +void Rig::updateAnimationStateHandlers() { // called on avatar update thread (which may be main thread) + QMutexLocker locker(&_stateMutex); + // It might pay to produce just one AnimVariantMap copy here, with a union of all the requested propertyNames, + // rather than having each callAnimationStateHandler invocation make its own copy. + // However, that copying is done on the script's own time rather than ours, so even if it's less cpu, it would be more + // work on the avatar update thread (which is possibly the main thread). + for (auto data = _stateHandlers.begin(); data != _stateHandlers.end(); data++) { + // call out: + int identifier = data.key(); + StateHandler& value = data.value(); + QScriptValue& function = value.function; + auto handleResult = [this, identifier](QScriptValue result) { // called in script thread to get the result back to us. + animationStateHandlerResult(identifier, result); + }; + // invokeMethod makes a copy of the args, and copies of AnimVariantMap do copy the underlying map, so this will correctly capture + // the state of _animVars and allow continued changes to _animVars in this thread without conflict. + QMetaObject::invokeMethod(function.engine(), "callAnimationStateHandler", Qt::QueuedConnection, + Q_ARG(QScriptValue, function), + Q_ARG(AnimVariantMap, _animVars), + Q_ARG(QStringList, value.propertyNames), + Q_ARG(bool, value.useNames), + Q_ARG(AnimVariantResultHandler, handleResult)); + // It turns out that, for thread-safety reasons, ScriptEngine::callAnimationStateHandler will invoke itself if called from other + // than the script thread. Thus the above _could_ be replaced with an ordinary call, which will then trigger the same + // invokeMethod as is done explicitly above. However, the script-engine library depends on this animation library, not vice versa. + // We could create an AnimVariantCallingMixin class in shared, with an abstract virtual slot + // AnimVariantCallingMixin::callAnimationStateHandler (and move AnimVariantMap/AnimVaraintResultHandler to shared), but the + // call site here would look like this instead of the above: + // dynamic_cast(function.engine())->callAnimationStateHandler(function, ..., handleResult); + // This works (I tried it), but the result would be that we would still have same runtime type checks as the invokeMethod above + // (occuring within the ScriptEngine::callAnimationStateHandler invokeMethod trampoline), _plus_ another runtime check for the dynamic_cast. + + // gather results in (likely from an earlier update): + _animVars.copyVariantsFrom(value.results); // If multiple handlers write the same anim var, the last registgered wins. (_map preserves order). + } +} + void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { if (_enableAnimGraph) { @@ -583,6 +649,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) { return; } + updateAnimationStateHandlers(); // evaluate the animation AnimNode::Triggers triggersOut; AnimPoseVec poses = _animNode->evaluate(_animVars, deltaTime, triggersOut); diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 6e0a88d768..c23ab3d506 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -37,6 +37,8 @@ #define __hifi__Rig__ #include +#include +#include #include "JointState.h" // We might want to change this (later) to something that doesn't depend on gpu, fbx and model. -HRS @@ -51,6 +53,12 @@ typedef std::shared_ptr RigPointer; class Rig : public QObject, public std::enable_shared_from_this { public: + struct StateHandler { + AnimVariantMap results; + QStringList propertyNames; + QScriptValue function; + bool useNames; + }; struct HeadParameters { float leanSideways = 0.0f; // degrees @@ -199,10 +207,14 @@ public: AnimNode::ConstPointer getAnimNode() const { return _animNode; } AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; } bool disableHands {false}; // should go away with rig animation (and Rig::inverseKinematics) + QScriptValue addAnimationStateHandler(QScriptValue handler, QScriptValue propertiesList); + void removeAnimationStateHandler(QScriptValue handler); + void animationStateHandlerResult(int identifier, QScriptValue result); bool getModelOffset(glm::vec3& modelOffsetOut) const; protected: + void updateAnimationStateHandlers(); void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); void updateNeckJoint(int index, const HeadParameters& params); @@ -241,6 +253,11 @@ public: float _desiredStateAge = 0.0f; float _leftHandOverlayAlpha = 0.0f; float _rightHandOverlayAlpha = 0.0f; + +private: + QMap _stateHandlers; + int _nextStateHandlerId {0}; + QMutex _stateMutex; }; #endif /* defined(__hifi__Rig__) */ diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 2457bda74a..2ce2c47fef 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -59,39 +59,30 @@ Sound::Sound(const QUrl& url, bool isStereo) : void Sound::downloadFinished(const QByteArray& data) { // replace our byte array with the downloaded data QByteArray rawAudioByteArray = QByteArray(data); - QString fileName = getURL().fileName(); - - const QString WAV_EXTENSION = ".wav"; + QString fileName = getURL().fileName().toLower(); + static const QString WAV_EXTENSION = ".wav"; + static const QString RAW_EXTENSION = ".raw"; if (fileName.endsWith(WAV_EXTENSION)) { - QString headerContentType = "audio/x-wav"; - //QByteArray headerContentType = reply->rawHeader("Content-Type"); + QByteArray outputAudioByteArray; - // WAV audio file encountered - if (headerContentType == "audio/x-wav" - || headerContentType == "audio/wav" - || headerContentType == "audio/wave" - || fileName.endsWith(WAV_EXTENSION)) { - - QByteArray outputAudioByteArray; - - interpretAsWav(rawAudioByteArray, outputAudioByteArray); - downSample(outputAudioByteArray); - } else { - // check if this was a stereo raw file - // since it's raw the only way for us to know that is if the file was called .stereo.raw - if (fileName.toLower().endsWith("stereo.raw")) { - _isStereo = true; - qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file."; - } - - // Process as RAW file - downSample(rawAudioByteArray); + interpretAsWav(rawAudioByteArray, outputAudioByteArray); + downSample(outputAudioByteArray); + trimFrames(); + } else if (fileName.endsWith(RAW_EXTENSION)) { + // check if this was a stereo raw file + // since it's raw the only way for us to know that is if the file was called .stereo.raw + if (fileName.toLower().endsWith("stereo.raw")) { + _isStereo = true; + qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file."; } + + // Process as RAW file + downSample(rawAudioByteArray); trimFrames(); } else { - qCDebug(audio) << "Network reply without 'Content-Type'."; + qCDebug(audio) << "Unknown sound file type"; } _isReady = true; diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index 1a9b6775d3..5d783a671e 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -21,62 +21,43 @@ HandData::HandData(AvatarData* owningAvatar) : _owningAvatarData(owningAvatar) { - // Start with two palms - addNewPalm(); - addNewPalm(); + addNewPalm(LeftHand); + addNewPalm(RightHand); } glm::vec3 HandData::worldToLocalVector(const glm::vec3& worldVector) const { return glm::inverse(getBaseOrientation()) * worldVector / getBaseScale(); } -PalmData& HandData::addNewPalm() { - _palms.push_back(PalmData(this)); +PalmData& HandData::addNewPalm(Hand whichHand) { + QWriteLocker locker(&_palmsLock); + _palms.push_back(PalmData(this, whichHand)); return _palms.back(); } -const PalmData* HandData::getPalm(int sixSenseID) const { +PalmData HandData::getCopyOfPalmData(Hand hand) const { + QReadLocker locker(&_palmsLock); + // the palms are not necessarily added in left-right order, - // so we have to search for the right SixSenseID - for (unsigned int i = 0; i < _palms.size(); i++) { - const PalmData* palm = &(_palms[i]); - if (palm->getSixenseID() == sixSenseID) { - return palm->isActive() ? palm : NULL; + // so we have to search for the correct hand + for (const auto& palm : _palms) { + if (palm.whichHand() == hand && palm.isActive()) { + return palm; } } - return NULL; + return PalmData(); // invalid hand } -void HandData::getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex) const { - leftPalmIndex = -1; - rightPalmIndex = -1; - for (size_t i = 0; i < _palms.size(); i++) { - const PalmData& palm = _palms[i]; - if (palm.isActive()) { - if (palm.getSixenseID() == LEFT_HAND_INDEX) { - leftPalmIndex = i; - } - if (palm.getSixenseID() == RIGHT_HAND_INDEX) { - rightPalmIndex = i; - } - } - } -} - -PalmData::PalmData(HandData* owningHandData) : +PalmData::PalmData(HandData* owningHandData, HandData::Hand hand) : _rawRotation(0.0f, 0.0f, 0.0f, 1.0f), _rawPosition(0.0f), _rawVelocity(0.0f), _rawAngularVelocity(0.0f), _totalPenetration(0.0f), -_controllerButtons(0), _isActive(false), -_sixenseID(SIXENSEID_INVALID), _numFramesWithoutData(0), _owningHandData(owningHandData), -_isCollidingWithVoxel(false), -_isCollidingWithPalm(false), -_collisionlessPaddleExpiry(0) { +_hand(hand) { } void PalmData::addToPosition(const glm::vec3& delta) { @@ -85,9 +66,9 @@ void PalmData::addToPosition(const glm::vec3& delta) { bool HandData::findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration, const PalmData*& collidingPalm) const { - - for (size_t i = 0; i < _palms.size(); ++i) { - const PalmData& palm = _palms[i]; + QReadLocker locker(&_palmsLock); + + for (const auto& palm : _palms) { if (!palm.isActive()) { continue; } diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 7514e38055..d782f240ee 100644 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -12,26 +12,30 @@ #ifndef hifi_HandData_h #define hifi_HandData_h +#include #include #include #include #include +#include + #include #include class AvatarData; class PalmData; -const int LEFT_HAND_INDEX = 0; -const int RIGHT_HAND_INDEX = 1; -const int NUM_HANDS = 2; - -const int SIXENSEID_INVALID = -1; - class HandData { public: + enum Hand { + LeftHand, + RightHand, + UnknownHand, + NUMBER_OF_HANDS + }; + HandData(AvatarData* owningAvatar); virtual ~HandData() {} @@ -46,15 +50,9 @@ public: glm::vec3 worldToLocalVector(const glm::vec3& worldVector) const; - std::vector& getPalms() { return _palms; } - const std::vector& getPalms() const { return _palms; } - const PalmData* getPalm(int sixSenseID) const; - size_t getNumPalms() const { return _palms.size(); } - PalmData& addNewPalm(); + PalmData getCopyOfPalmData(Hand hand) const; - /// Finds the indices of the left and right palms according to their locations, or -1 if either or - /// both is not found. - void getLeftRightPalmIndices(int& leftPalmIndex, int& rightPalmIndex) const; + std::vector getCopyOfPalms() const { QReadLocker locker(&_palmsLock); return _palms; } /// Checks for penetration between the described sphere and the hand. /// \param penetratorCenter the center of the penetration test sphere @@ -67,14 +65,22 @@ public: glm::quat getBaseOrientation() const; + /// Allows a lamda function write access to the specific palm for this Hand, this might + /// modify the _palms vector + template void modifyPalm(Hand whichHand, PalmModifierFunction callback); + friend class AvatarData; protected: AvatarData* _owningAvatarData; std::vector _palms; + mutable QReadWriteLock _palmsLock{ QReadWriteLock::Recursive }; glm::vec3 getBasePosition() const; float getBaseScale() const; - + + PalmData& addNewPalm(Hand whichHand); + PalmData& getPalmData(Hand hand); + private: // privatize copy ctor and assignment operator so copies of this object cannot be made HandData(const HandData&); @@ -84,25 +90,30 @@ private: class PalmData { public: - PalmData(HandData* owningHandData); + PalmData(HandData* owningHandData = nullptr, HandData::Hand hand = HandData::UnknownHand); glm::vec3 getPosition() const { return _owningHandData->localToWorldPosition(_rawPosition); } glm::vec3 getVelocity() const { return _owningHandData->localToWorldDirection(_rawVelocity); } const glm::vec3& getRawPosition() const { return _rawPosition; } bool isActive() const { return _isActive; } - int getSixenseID() const { return _sixenseID; } + bool isValid() const { return _owningHandData; } void setActive(bool active) { _isActive = active; } - void setSixenseID(int id) { _sixenseID = id; } - void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; }; + HandData::Hand whichHand() const { return _hand; } + void setHand(HandData::Hand hand) { _hand = hand; } + + void setRawRotation(const glm::quat& rawRotation) { _rawRotation = rawRotation; }; glm::quat getRawRotation() const { return _rawRotation; } glm::quat getRotation() const { return _owningHandData->getBaseOrientation() * _rawRotation; } void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; } void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; } const glm::vec3& getRawVelocity() const { return _rawVelocity; } + void setRawAngularVelocity(const glm::vec3& angularVelocity) { _rawAngularVelocity = angularVelocity; } const glm::vec3& getRawAngularVelocity() const { return _rawAngularVelocity; } + glm::quat getRawAngularVelocityAsQuat() const { return glm::quat(_rawAngularVelocity); } + void addToPosition(const glm::vec3& delta); void addToPenetration(const glm::vec3& penetration) { _totalPenetration += penetration; } @@ -120,31 +131,11 @@ public: void resetFramesWithoutData() { _numFramesWithoutData = 0; } int getFramesWithoutData() const { return _numFramesWithoutData; } - // Controller buttons - void setControllerButtons(unsigned int controllerButtons) { _controllerButtons = controllerButtons; } - void setLastControllerButtons(unsigned int controllerButtons) { _lastControllerButtons = controllerButtons; } - - unsigned int getControllerButtons() const { return _controllerButtons; } - unsigned int getLastControllerButtons() const { return _lastControllerButtons; } - + // FIXME - these are used in SkeletonModel::updateRig() the skeleton/rig should probably get this information + // from an action and/or the UserInputMapper instead of piping it through here. void setTrigger(float trigger) { _trigger = trigger; } float getTrigger() const { return _trigger; } - void setJoystick(float joystickX, float joystickY) { _joystickX = joystickX; _joystickY = joystickY; } - float getJoystickX() const { return _joystickX; } - float getJoystickY() const { return _joystickY; } - bool getIsCollidingWithVoxel() const { return _isCollidingWithVoxel; } - void setIsCollidingWithVoxel(bool isCollidingWithVoxel) { _isCollidingWithVoxel = isCollidingWithVoxel; } - - bool getIsCollidingWithPalm() const { return _isCollidingWithPalm; } - void setIsCollidingWithPalm(bool isCollidingWithPalm) { _isCollidingWithPalm = isCollidingWithPalm; } - - bool hasPaddle() const { return _collisionlessPaddleExpiry < usecTimestampNow(); } - void updateCollisionlessPaddleExpiry() { _collisionlessPaddleExpiry = usecTimestampNow() + USECS_PER_SECOND; } - - /// Store position where the palm holds the ball. - void getBallHoldPosition(glm::vec3& position) const; - // return world-frame: glm::vec3 getFingerDirection() const; glm::vec3 getNormal() const; @@ -155,25 +146,29 @@ private: glm::vec3 _rawPosition; glm::vec3 _rawVelocity; glm::vec3 _rawAngularVelocity; + glm::quat _rawDeltaRotation; glm::quat _lastRotation; glm::vec3 _tipPosition; glm::vec3 _tipVelocity; - glm::vec3 _totalPenetration; // accumulator for per-frame penetrations + glm::vec3 _totalPenetration; /// accumulator for per-frame penetrations - unsigned int _controllerButtons; - unsigned int _lastControllerButtons; float _trigger; - float _joystickX, _joystickY; - bool _isActive; // This has current valid data - int _sixenseID; // Sixense controller ID for this palm - int _numFramesWithoutData; // after too many frames without data, this tracked object assumed lost. + bool _isActive; /// This has current valid data + int _numFramesWithoutData; /// after too many frames without data, this tracked object assumed lost. HandData* _owningHandData; - - bool _isCollidingWithVoxel; /// Whether the finger of this palm is inside a leaf voxel - bool _isCollidingWithPalm; - quint64 _collisionlessPaddleExpiry; /// Timestamp after which paddle starts colliding + HandData::Hand _hand; }; +template void HandData::modifyPalm(Hand whichHand, PalmModifierFunction callback) { + QReadLocker locker(&_palmsLock); + for (auto& palm : _palms) { + if (palm.whichHand() == whichHand && palm.isValid()) { + callback(palm); + return; + } + } +} + #endif // hifi_HandData_h diff --git a/libraries/controllers/CMakeLists.txt b/libraries/controllers/CMakeLists.txt new file mode 100644 index 0000000000..5beffce461 --- /dev/null +++ b/libraries/controllers/CMakeLists.txt @@ -0,0 +1,14 @@ +set(TARGET_NAME controllers) + +# set a default root dir for each of our optional externals if it was not passed +setup_hifi_library(Script) + +# use setup_hifi_library macro to setup our project and link appropriate Qt modules +link_hifi_libraries(shared) + +GroupSources("src/controllers") + +add_dependency_external_projects(glm) +find_package(GLM REQUIRED) +target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) + diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp new file mode 100644 index 0000000000..a4a8656fb7 --- /dev/null +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -0,0 +1,120 @@ +// +// Created by Bradley Austin Davis on 2015/10/19 +// 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 "Actions.h" + +#include "impl/endpoints/ActionEndpoint.h" + +namespace controller { + + Input::NamedPair makePair(ChannelType type, Action action, const QString& name) { + auto input = Input(UserInputMapper::ACTIONS_DEVICE, toInt(action), type); + return Input::NamedPair(input, name); + } + + Input::NamedPair makeAxisPair(Action action, const QString& name) { + return makePair(ChannelType::AXIS, action, name); + } + + Input::NamedPair makeButtonPair(Action action, const QString& name) { + return makePair(ChannelType::BUTTON, action, name); + } + + Input::NamedPair makePosePair(Action action, const QString& name) { + return makePair(ChannelType::POSE, action, name); + } + + EndpointPointer ActionsDevice::createEndpoint(const Input& input) const { + return std::make_shared(input); + } + + // Device functions + Input::NamedVector ActionsDevice::getAvailableInputs() const { + static Input::NamedVector availableInputs { + makeAxisPair(Action::TRANSLATE_X, "TranslateX"), + makeAxisPair(Action::TRANSLATE_Y, "TranslateY"), + makeAxisPair(Action::TRANSLATE_Z, "TranslateZ"), + makeAxisPair(Action::ROLL, "Roll"), + makeAxisPair(Action::PITCH, "Pitch"), + makeAxisPair(Action::YAW, "Yaw"), + makeAxisPair(Action::STEP_YAW, "StepYaw"), + makeAxisPair(Action::STEP_PITCH, "StepPitch"), + makeAxisPair(Action::STEP_ROLL, "StepRoll"), + makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateX"), + makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateY"), + makeAxisPair(Action::STEP_TRANSLATE_X, "StepTranslateZ"), + + makePosePair(Action::LEFT_HAND, "LeftHand"), + makePosePair(Action::RIGHT_HAND, "RightHand"), + + makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"), + makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"), + + makeButtonPair(Action::SHIFT, "Shift"), + makeButtonPair(Action::ACTION1, "PrimaryAction"), + makeButtonPair(Action::ACTION2, "SecondaryAction"), + makeButtonPair(Action::CONTEXT_MENU, "ContextMenu"), + makeButtonPair(Action::TOGGLE_MUTE, "ToggleMute"), + + // Aliases and bisected versions + makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"), + makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"), + makeAxisPair(Action::LATERAL_LEFT, "StrafeLeft"), + makeAxisPair(Action::LATERAL_RIGHT, "StrafeRight"), + makeAxisPair(Action::VERTICAL_DOWN, "Down"), + makeAxisPair(Action::VERTICAL_UP, "Up"), + makeAxisPair(Action::YAW_LEFT, "YawLeft"), + makeAxisPair(Action::YAW_RIGHT, "YawRight"), + makeAxisPair(Action::PITCH_DOWN, "PitchDown"), + makeAxisPair(Action::PITCH_UP, "PitchUp"), + makeAxisPair(Action::BOOM_IN, "BoomIn"), + makeAxisPair(Action::BOOM_OUT, "BoomOut"), + + // Deprecated aliases + // FIXME remove after we port all scripts + makeAxisPair(Action::LONGITUDINAL_BACKWARD, "LONGITUDINAL_BACKWARD"), + makeAxisPair(Action::LONGITUDINAL_FORWARD, "LONGITUDINAL_FORWARD"), + makeAxisPair(Action::LATERAL_LEFT, "LATERAL_LEFT"), + makeAxisPair(Action::LATERAL_RIGHT, "LATERAL_RIGHT"), + makeAxisPair(Action::VERTICAL_DOWN, "VERTICAL_DOWN"), + makeAxisPair(Action::VERTICAL_UP, "VERTICAL_UP"), + makeAxisPair(Action::YAW_LEFT, "YAW_LEFT"), + makeAxisPair(Action::YAW_RIGHT, "YAW_RIGHT"), + makeAxisPair(Action::PITCH_DOWN, "PITCH_DOWN"), + makeAxisPair(Action::PITCH_UP, "PITCH_UP"), + makeAxisPair(Action::BOOM_IN, "BOOM_IN"), + makeAxisPair(Action::BOOM_OUT, "BOOM_OUT"), + + makePosePair(Action::LEFT_HAND, "LEFT_HAND"), + makePosePair(Action::RIGHT_HAND, "RIGHT_HAND"), + + makeButtonPair(Action::LEFT_HAND_CLICK, "LEFT_HAND_CLICK"), + makeButtonPair(Action::RIGHT_HAND_CLICK, "RIGHT_HAND_CLICK"), + + makeButtonPair(Action::SHIFT, "SHIFT"), + makeButtonPair(Action::ACTION1, "ACTION1"), + makeButtonPair(Action::ACTION2, "ACTION2"), + makeButtonPair(Action::CONTEXT_MENU, "CONTEXT_MENU"), + makeButtonPair(Action::TOGGLE_MUTE, "TOGGLE_MUTE"), + }; + return availableInputs; + } + + void ActionsDevice::update(float deltaTime, bool jointsCaptured) { + } + + void ActionsDevice::focusOutEvent() { + } + + ActionsDevice::ActionsDevice() : InputDevice("Actions") { + _deviceID = UserInputMapper::ACTIONS_DEVICE; + } + + ActionsDevice::~ActionsDevice() {} + +} diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h new file mode 100644 index 0000000000..36df695032 --- /dev/null +++ b/libraries/controllers/src/controllers/Actions.h @@ -0,0 +1,103 @@ +// +// Created by Bradley Austin Davis on 2015/10/19 +// 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 +#ifndef hifi_controller_Actions_h +#define hifi_controller_Actions_h + +#include +#include + +#include "InputDevice.h" + +namespace controller { + +// Actions are the output channels of the Mapper, that's what the InputChannel map to +// For now the Actions are hardcoded, this is bad, but we will fix that in the near future +enum class Action { + TRANSLATE_X = 0, + TRANSLATE_Y, + TRANSLATE_Z, + ROTATE_X, PITCH = ROTATE_X, + ROTATE_Y, YAW = ROTATE_Y, + ROTATE_Z, ROLL = ROTATE_Z, + + STEP_YAW, + // FIXME does this have a use case? + STEP_PITCH, + // FIXME does this have a use case? + STEP_ROLL, + + STEP_TRANSLATE_X, + STEP_TRANSLATE_Y, + STEP_TRANSLATE_Z, + + TRANSLATE_CAMERA_Z, + NUM_COMBINED_AXES, + + LEFT_HAND = NUM_COMBINED_AXES, + RIGHT_HAND, + + LEFT_HAND_CLICK, + RIGHT_HAND_CLICK, + + ACTION1, + ACTION2, + + CONTEXT_MENU, + TOGGLE_MUTE, + + SHIFT, + + // Biseced aliases for TRANSLATE_Z + LONGITUDINAL_BACKWARD, + LONGITUDINAL_FORWARD, + + // Biseced aliases for TRANSLATE_X + LATERAL_LEFT, + LATERAL_RIGHT, + + // Biseced aliases for TRANSLATE_Y + VERTICAL_DOWN, + VERTICAL_UP, + + // Biseced aliases for ROTATE_Y + YAW_LEFT, + YAW_RIGHT, + + // Biseced aliases for ROTATE_X + PITCH_DOWN, + PITCH_UP, + + // Biseced aliases for TRANSLATE_CAMERA_Z + BOOM_IN, + BOOM_OUT, + + NUM_ACTIONS, +}; + +template +int toInt(T enumValue) { return static_cast(enumValue); } + +class ActionsDevice : public QObject, public InputDevice { + Q_OBJECT + Q_PROPERTY(QString name READ getName) + +public: + virtual EndpointPointer createEndpoint(const Input& input) const override; + virtual Input::NamedVector getAvailableInputs() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + ActionsDevice(); + virtual ~ActionsDevice(); +}; + +} + +#endif // hifi_StandardController_h diff --git a/libraries/controllers/src/controllers/DeviceProxy.cpp b/libraries/controllers/src/controllers/DeviceProxy.cpp new file mode 100644 index 0000000000..f3e9526080 --- /dev/null +++ b/libraries/controllers/src/controllers/DeviceProxy.cpp @@ -0,0 +1,14 @@ +// +// Created by Bradley Austin Davis on 2015/10/18 +// (based on UserInputMapper inner class created by Sam Gateau on 4/27/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 "DeviceProxy.h" + +namespace controller { +} + diff --git a/libraries/controllers/src/controllers/DeviceProxy.h b/libraries/controllers/src/controllers/DeviceProxy.h new file mode 100644 index 0000000000..83a813d5d5 --- /dev/null +++ b/libraries/controllers/src/controllers/DeviceProxy.h @@ -0,0 +1,47 @@ +// +// Created by Bradley Austin Davis on 2015/10/18 +// (based on UserInputMapper inner class created by Sam Gateau on 4/27/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 +// + +#pragma once +#ifndef hifi_controllers_DeviceProxy_h +#define hifi_controllers_DeviceProxy_h + +#include + +#include +#include + +#include "Input.h" +#include "Pose.h" + +namespace controller { + /* + using Modifiers = std::vector; + typedef QPair InputPair; + class Endpoint; + using EndpointPtr = std::shared_ptr; + + template + using InputGetter = std::function; + using ButtonGetter = InputGetter; + using AxisGetter = InputGetter; + using PoseGetter = InputGetter; + using ResetBindings = std::function; + using AvailableInputGetter = std::function; + using EndpointCreator = std::function; + + class DeviceProxy { + public: + using Pointer = std::shared_ptr; + + QString _name; + }; + */ +} + +#endif diff --git a/libraries/controllers/src/controllers/Forward.h b/libraries/controllers/src/controllers/Forward.h new file mode 100644 index 0000000000..e1a62556d4 --- /dev/null +++ b/libraries/controllers/src/controllers/Forward.h @@ -0,0 +1,37 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 +#ifndef hifi_Controllers_Forward_h +#define hifi_Controllers_Forward_h + +namespace controller { + +class Endpoint; +using EndpointPointer = std::shared_ptr; +using EndpointList = std::list; + +class Filter; +using FilterPointer = std::shared_ptr; +using FilterList = std::list; + +class Route; +using RoutePointer = std::shared_ptr; +using RouteList = std::list; + +class Conditional; +using ConditionalPointer = std::shared_ptr; +using ConditionalList = std::list; + +class Mapping; +using MappingPointer = std::shared_ptr; +using MappingList = std::list; + +} + +#endif diff --git a/libraries/controllers/src/controllers/Input.cpp b/libraries/controllers/src/controllers/Input.cpp new file mode 100644 index 0000000000..6f8bd547a2 --- /dev/null +++ b/libraries/controllers/src/controllers/Input.cpp @@ -0,0 +1,17 @@ +// +// Created by Bradley Austin Davis on 2015/10/18 +// 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 "Input.h" + +namespace controller { + const Input Input::INVALID_INPUT = Input(0x7fffffff); + const uint16_t Input::INVALID_DEVICE = Input::INVALID_INPUT.device; + const uint16_t Input::INVALID_CHANNEL = Input::INVALID_INPUT.channel; + const uint16_t Input::INVALID_TYPE = Input::INVALID_INPUT.type; +} + diff --git a/libraries/controllers/src/controllers/Input.h b/libraries/controllers/src/controllers/Input.h new file mode 100644 index 0000000000..db99e820da --- /dev/null +++ b/libraries/controllers/src/controllers/Input.h @@ -0,0 +1,74 @@ +// +// Created by Bradley Austin Davis on 2015/10/18 +// (based on UserInputMapper inner class created by Sam Gateau on 4/27/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 +// + +#pragma once +#ifndef hifi_controllers_Input_h +#define hifi_controllers_Input_h + +#include + +namespace controller { + +enum class ChannelType { + UNKNOWN = 0, + BUTTON, + AXIS, + POSE, + RUMBLE, + INVALID = 0x7 +}; + +// Input is the unique identifier to find a n input channel of a particular device +// Devices are responsible for registering to the UseInputMapper so their input channels can be sued and mapped +// to the Action channels +struct Input { + union { + uint32_t id{ 0 }; // by default Input is 0 meaning invalid + struct { + uint16_t device; // Up to 64K possible devices + uint16_t channel : 12 ; // 2^12 possible channel per Device + uint16_t type : 3; // 2 bits to store the Type directly in the ID + uint16_t padding : 1; // 2 bits to store the Type directly in the ID + }; + }; + + bool isValid() const { return (id != INVALID_INPUT.id); } + + uint16_t getDevice() const { return device; } + uint16_t getChannel() const { return channel; } + uint32_t getID() const { return id; } + ChannelType getType() const { return (ChannelType) type; } + + bool isButton() const { return getType() == ChannelType::BUTTON; } + bool isAxis() const { return getType() == ChannelType::AXIS; } + 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. + explicit Input() {} + explicit Input(uint32_t id) : id(id) {} + explicit Input(uint16_t device, uint16_t channel, ChannelType type) : device(device), channel(channel), type(uint16_t(type)), padding(0) {} + Input(const Input& src) : id(src.id) {} + Input& operator = (const Input& src) { id = src.id; return (*this); } + bool operator ==(const Input& right) const { return INVALID_INPUT.id != id && INVALID_INPUT.id != right.id && id == right.id; } + bool operator !=(const Input& right) const { return !(*this == right); } + bool operator < (const Input& src) const { return id < src.id; } + + static const Input INVALID_INPUT; + static const uint16_t INVALID_DEVICE; + static const uint16_t INVALID_CHANNEL; + static const uint16_t INVALID_TYPE; + + using NamedPair = QPair; + using NamedVector = QVector; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/InputDevice.cpp b/libraries/controllers/src/controllers/InputDevice.cpp new file mode 100644 index 0000000000..d5044a219f --- /dev/null +++ b/libraries/controllers/src/controllers/InputDevice.cpp @@ -0,0 +1,114 @@ +// +// 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" + +#include "Input.h" +#include "impl/endpoints/InputEndpoint.h" + +namespace controller { + + 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; + } + } + + Pose InputDevice::getPose(int channel) const { + auto pose = _poseStateMap.find(channel); + if (pose != _poseStateMap.end()) { + return (*pose).second; + } else { + return Pose(); + } + } + + Input InputDevice::makeInput(controller::StandardButtonChannel button) const { + return Input(_deviceID, button, ChannelType::BUTTON); + } + + Input InputDevice::makeInput(controller::StandardAxisChannel axis) const { + return Input(_deviceID, axis, ChannelType::AXIS); + } + + Input InputDevice::makeInput(controller::StandardPoseChannel pose) const { + return Input(_deviceID, pose, ChannelType::POSE); + } + + Input::NamedPair InputDevice::makePair(controller::StandardButtonChannel button, const QString& name) const { + return Input::NamedPair(makeInput(button), name); + } + + Input::NamedPair InputDevice::makePair(controller::StandardAxisChannel axis, const QString& name) const { + return Input::NamedPair(makeInput(axis), name); + } + + Input::NamedPair InputDevice::makePair(controller::StandardPoseChannel pose, const QString& name) const { + return Input::NamedPair(makeInput(pose), name); + } + + float InputDevice::getValue(ChannelType channelType, uint16_t channel) const { + switch (channelType) { + case ChannelType::AXIS: + return getAxis(channel); + + case ChannelType::BUTTON: + return getButton(channel); + + case ChannelType::POSE: + return getPose(channel).valid ? 1.0f : 0.0f; + + default: + break; + } + + return 0.0f; + } + + + float InputDevice::getValue(const Input& input) const { + return getValue(input.getType(), input.channel); + } + + EndpointPointer InputDevice::createEndpoint(const Input& input) const { + return std::make_shared(input); + } + +} diff --git a/libraries/input-plugins/src/input-plugins/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h similarity index 50% rename from libraries/input-plugins/src/input-plugins/InputDevice.h rename to libraries/controllers/src/controllers/InputDevice.h index 4dbb141832..fc3477b41a 100644 --- a/libraries/input-plugins/src/input-plugins/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -10,12 +10,27 @@ // #pragma once -#include "UserInputMapper.h" +#include +#include +#include + +#include + +#include "Pose.h" +#include "Input.h" +#include "StandardControls.h" +#include "DeviceProxy.h" + // Event types for each controller const unsigned int CONTROLLER_0_EVENT = 1500U; const unsigned int CONTROLLER_1_EVENT = 1501U; +namespace controller { + +class Endpoint; +using EndpointPointer = std::shared_ptr; + // 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 @@ -23,17 +38,22 @@ class InputDevice { public: InputDevice(const QString& name) : _name(name) {} + using Pointer = std::shared_ptr; + typedef std::unordered_set ButtonPressedMap; typedef std::map AxisStateMap; - typedef std::map PoseStateMap; + 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; + Pose getPose(int channel) const; - virtual void registerToUserInputMapper(UserInputMapper& mapper) = 0; - virtual void assignDefaultInputMapping(UserInputMapper& mapper) = 0; + float getValue(const Input& input) const; + float getValue(ChannelType channelType, uint16_t channel) const; + Pose getPoseValue(uint16_t channel) const; + + const QString& getName() const { return _name; } // Update call MUST be called once per simulation loop // It takes care of updating the action states and deltas @@ -42,20 +62,33 @@ public: virtual void focusOutEvent() = 0; int getDeviceID() { return _deviceID; } + void setDeviceID(int deviceID) { _deviceID = deviceID; } static float getCursorPixelRangeMult(); - static float getReticleMoveSpeed() { return reticleMoveSpeed; } - static void setReticleMoveSpeed(float sixenseReticleMoveSpeed) { reticleMoveSpeed = sixenseReticleMoveSpeed; } + static float getReticleMoveSpeed() { return _reticleMoveSpeed; } + static void setReticleMoveSpeed(float reticleMoveSpeed) { _reticleMoveSpeed = reticleMoveSpeed; } static bool getLowVelocityFilter() { return _lowVelocityFilter; }; + Input makeInput(StandardButtonChannel button) const; + Input makeInput(StandardAxisChannel axis) const; + Input makeInput(StandardPoseChannel pose) const; + Input::NamedPair makePair(StandardButtonChannel button, const QString& name) const; + Input::NamedPair makePair(StandardAxisChannel button, const QString& name) const; + Input::NamedPair makePair(StandardPoseChannel button, const QString& name) const; public slots: static void setLowVelocityFilter(bool newLowVelocityFilter) { _lowVelocityFilter = newLowVelocityFilter; }; protected: - int _deviceID = 0; + friend class UserInputMapper; - QString _name; + virtual Input::NamedVector getAvailableInputs() const = 0; + virtual QString getDefaultMappingConfig() const { return QString(); } + virtual EndpointPointer createEndpoint(const Input& input) const; + + uint16_t _deviceID { Input::INVALID_DEVICE }; + + const QString _name; ButtonPressedMap _buttonPressedMap; AxisStateMap _axisStateMap; @@ -64,5 +97,7 @@ protected: static bool _lowVelocityFilter; private: - static float reticleMoveSpeed; -}; \ No newline at end of file + static float _reticleMoveSpeed; +}; + +} \ No newline at end of file diff --git a/libraries/controllers/src/controllers/Logging.cpp b/libraries/controllers/src/controllers/Logging.cpp new file mode 100644 index 0000000000..ae6b523a45 --- /dev/null +++ b/libraries/controllers/src/controllers/Logging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// 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 "Logging.h" + +Q_LOGGING_CATEGORY(controllers, "hifi.controllers") diff --git a/libraries/controllers/src/controllers/Logging.h b/libraries/controllers/src/controllers/Logging.h new file mode 100644 index 0000000000..d74ddae59f --- /dev/null +++ b/libraries/controllers/src/controllers/Logging.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// 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_Controllers_Logging_h +#define hifi_Controllers_Logging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(controllers) + +#endif diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp new file mode 100644 index 0000000000..2281fc98ff --- /dev/null +++ b/libraries/controllers/src/controllers/Pose.cpp @@ -0,0 +1,49 @@ +// +// Created by Bradley Austin Davis on 2015/10/18 +// 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 +#include + +#include + +#include "Pose.h" + +namespace controller { + + Pose::Pose(const vec3& translation, const quat& rotation, + const vec3& velocity, const quat& angularVelocity) : + translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity), valid (true) { } + + bool Pose::operator==(const Pose& right) const { + // invalid poses return false for comparison, even against identical invalid poses, like NaN + if (!valid || !right.valid) { + return false; + } + + // FIXME add margin of error? Or add an additional withinEpsilon function? + return translation == right.getTranslation() && rotation == right.getRotation() && + velocity == right.getVelocity() && angularVelocity == right.getAngularVelocity(); + } + + QScriptValue Pose::toScriptValue(QScriptEngine* engine, const Pose& pose) { + QScriptValue obj = engine->newObject(); + obj.setProperty("translation", vec3toScriptValue(engine, pose.translation)); + obj.setProperty("rotation", quatToScriptValue(engine, pose.rotation)); + obj.setProperty("velocity", vec3toScriptValue(engine, pose.velocity)); + obj.setProperty("angularVelocity", quatToScriptValue(engine, pose.angularVelocity)); + obj.setProperty("valid", pose.valid); + + return obj; + } + + void Pose::fromScriptValue(const QScriptValue& object, Pose& pose) { + // nothing for now... + } + +} + diff --git a/libraries/controllers/src/controllers/Pose.h b/libraries/controllers/src/controllers/Pose.h new file mode 100644 index 0000000000..b8d27824f3 --- /dev/null +++ b/libraries/controllers/src/controllers/Pose.h @@ -0,0 +1,50 @@ +// +// Created by Bradley Austin Davis on 2015/10/18 +// (based on UserInputMapper inner class created by Sam Gateau on 4/27/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 +// + +#pragma once +#ifndef hifi_controllers_Pose_h +#define hifi_controllers_Pose_h + +class QScriptEngine; +class QScriptValue; + +#include + +namespace controller { + + struct Pose { + public: + vec3 translation; + quat rotation; + vec3 velocity; + quat angularVelocity; + bool valid{ false }; + + Pose() {} + Pose(const vec3& translation, const quat& rotation, + const vec3& velocity = vec3(), const quat& angularVelocity = quat()); + + Pose(const Pose&) = default; + Pose& operator = (const Pose&) = default; + bool operator ==(const Pose& right) const; + bool operator !=(const Pose& right) const { return !(*this == right); } + bool isValid() const { return valid; } + vec3 getTranslation() const { return translation; } + quat getRotation() const { return rotation; } + vec3 getVelocity() const { return velocity; } + quat getAngularVelocity() const { return angularVelocity; } + + static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event); + static void fromScriptValue(const QScriptValue& object, Pose& event); + }; +} + +//Q_DECLARE_METATYPE(controller::Pose); + +#endif diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp new file mode 100644 index 0000000000..a62172a730 --- /dev/null +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -0,0 +1,241 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "ScriptingInterface.h" + +#include +#include + +#include + +#include +#include + +#include +#include + +#include + +#include "impl/MappingBuilderProxy.h" +#include "Logging.h" +#include "InputDevice.h" + + +static QRegularExpression SANITIZE_NAME_EXPRESSION{ "[\\(\\)\\.\\s]" }; + +static QVariantMap createDeviceMap(const controller::InputDevice::Pointer device) { + auto userInputMapper = DependencyManager::get(); + QVariantMap deviceMap; + for (const auto& inputMapping : userInputMapper->getAvailableInputs(device->getDeviceID())) { + const auto& input = inputMapping.first; + const auto inputName = QString(inputMapping.second).remove(SANITIZE_NAME_EXPRESSION); + qCDebug(controllers) << "\tInput " << input.getChannel() << (int)input.getType() + << QString::number(input.getID(), 16) << ": " << inputName; + deviceMap.insert(inputName, input.getID()); + } + return deviceMap; +} + +// FIXME this throws a hissy fit on MSVC if I put it in the main controller namespace block +controller::ScriptingInterface::ScriptingInterface() { + auto userInputMapper = DependencyManager::get(); + + connect(userInputMapper.data(), &UserInputMapper::actionEvent, this, &controller::ScriptingInterface::actionEvent); + connect(userInputMapper.data(), &UserInputMapper::inputEvent, this, &controller::ScriptingInterface::inputEvent); + + // FIXME make this thread safe + connect(userInputMapper.data(), &UserInputMapper::hardwareChanged, this, [=] { + updateMaps(); + emit hardwareChanged(); + }); + + qCDebug(controllers) << "Setting up standard controller abstraction"; + _standard = createDeviceMap(userInputMapper->getStandardDevice()); + + // FIXME allow custom user actions? + auto actionNames = userInputMapper->getActionNames(); + qCDebug(controllers) << "Setting up standard actions"; + for (const auto& namedInput : userInputMapper->getActionInputs()) { + const QString& actionName = namedInput.second; + const Input& actionInput = namedInput.first; + qCDebug(controllers) << "\tAction: " << actionName << " " << actionInput.getChannel(); + + // Expose the IDs to JS + QString cleanActionName = QString(actionName).remove(SANITIZE_NAME_EXPRESSION); + _actions.insert(cleanActionName, actionInput.getID()); + } + + updateMaps(); +} + +namespace controller { + + QObject* ScriptingInterface::newMapping(const QString& mappingName) { + auto userInputMapper = DependencyManager::get(); + return new MappingBuilderProxy(*userInputMapper, userInputMapper->newMapping(mappingName)); + } + + void ScriptingInterface::enableMapping(const QString& mappingName, bool enable) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->enableMapping(mappingName, enable); + } + + float ScriptingInterface::getValue(const int& source) const { + auto userInputMapper = DependencyManager::get(); + return userInputMapper->getValue(Input((uint32_t)source)); + } + + float ScriptingInterface::getButtonValue(StandardButtonChannel source, uint16_t device) const { + return getValue(Input(device, source, ChannelType::BUTTON).getID()); + } + + float ScriptingInterface::getAxisValue(StandardAxisChannel source, uint16_t device) const { + return getValue(Input(device, source, ChannelType::AXIS).getID()); + } + + Pose ScriptingInterface::getPoseValue(const int& source) const { + auto userInputMapper = DependencyManager::get(); + return userInputMapper->getPose(Input((uint32_t)source)); + } + + Pose ScriptingInterface::getPoseValue(StandardPoseChannel source, uint16_t device) const { + return getPoseValue(Input(device, source, ChannelType::POSE).getID()); + } + + //bool ScriptingInterface::isPrimaryButtonPressed() const { + // return isButtonPressed(StandardButtonChannel::A); + //} + // + //glm::vec2 ScriptingInterface::getPrimaryJoystickPosition() const { + // return getJoystickPosition(0); + //} + + //int ScriptingInterface::getNumberOfButtons() const { + // return StandardButtonChannel::NUM_STANDARD_BUTTONS; + //} + + //bool ScriptingInterface::isButtonPressed(int buttonIndex) const { + // return getButtonValue((StandardButtonChannel)buttonIndex) == 0.0 ? false : true; + //} + + //int ScriptingInterface::getNumberOfTriggers() const { + // return StandardCounts::TRIGGERS; + //} + + //float ScriptingInterface::getTriggerValue(int triggerIndex) const { + // return getAxisValue(triggerIndex == 0 ? StandardAxisChannel::LT : StandardAxisChannel::RT); + //} + + //int ScriptingInterface::getNumberOfJoysticks() const { + // return StandardCounts::ANALOG_STICKS; + //} + + //glm::vec2 ScriptingInterface::getJoystickPosition(int joystickIndex) const { + // StandardAxisChannel xid = StandardAxisChannel::LX; + // StandardAxisChannel yid = StandardAxisChannel::LY; + // if (joystickIndex != 0) { + // xid = StandardAxisChannel::RX; + // yid = StandardAxisChannel::RY; + // } + // vec2 result; + // result.x = getAxisValue(xid); + // result.y = getAxisValue(yid); + // return result; + //} + + //int ScriptingInterface::getNumberOfSpatialControls() const { + // return StandardCounts::POSES; + //} + + //glm::vec3 ScriptingInterface::getSpatialControlPosition(int controlIndex) const { + // // FIXME extract the position from the standard pose + // return vec3(); + //} + + //glm::vec3 ScriptingInterface::getSpatialControlVelocity(int controlIndex) const { + // // FIXME extract the velocity from the standard pose + // return vec3(); + //} + + //glm::vec3 ScriptingInterface::getSpatialControlNormal(int controlIndex) const { + // // FIXME extract the normal from the standard pose + // return vec3(); + //} + // + //glm::quat ScriptingInterface::getSpatialControlRawRotation(int controlIndex) const { + // // FIXME extract the rotation from the standard pose + // return quat(); + //} + + QVector ScriptingInterface::getAllActions() { + return DependencyManager::get()->getAllActions(); + } + + QString ScriptingInterface::getDeviceName(unsigned int device) { + return DependencyManager::get()->getDeviceName((unsigned short)device); + } + + QVector ScriptingInterface::getAvailableInputs(unsigned int device) { + return DependencyManager::get()->getAvailableInputs((unsigned short)device); + } + + int ScriptingInterface::findDevice(QString name) { + return DependencyManager::get()->findDevice(name); + } + + QVector ScriptingInterface::getDeviceNames() { + return DependencyManager::get()->getDeviceNames(); + } + + float ScriptingInterface::getActionValue(int action) { + return DependencyManager::get()->getActionState(Action(action)); + } + + int ScriptingInterface::findAction(QString actionName) { + return DependencyManager::get()->findAction(actionName); + } + + QVector ScriptingInterface::getActionNames() const { + return DependencyManager::get()->getActionNames(); + } + + void ScriptingInterface::updateMaps() { + QVariantMap newHardware; + auto userInputMapper = DependencyManager::get(); + auto devices = userInputMapper->getDevices(); + for (const auto& deviceMapping : devices) { + auto deviceID = deviceMapping.first; + if (deviceID != userInputMapper->getStandardDeviceID()) { + auto device = deviceMapping.second; + auto deviceName = QString(device->getName()).remove(SANITIZE_NAME_EXPRESSION); + qCDebug(controllers) << "Device" << deviceMapping.first << ":" << deviceName; + if (newHardware.contains(deviceName)) { + continue; + } + + // Expose the IDs to JS + newHardware.insert(deviceName, createDeviceMap(device)); + } + } + _hardware = newHardware; + } + + + QObject* ScriptingInterface::parseMapping(const QString& json) { + auto userInputMapper = DependencyManager::get(); + auto mapping = userInputMapper->parseMapping(json); + return new MappingBuilderProxy(*userInputMapper, mapping); + } + + QObject* ScriptingInterface::loadMapping(const QString& jsonUrl) { + return nullptr; + } + + +} // namespace controllers + + diff --git a/libraries/controllers/src/controllers/ScriptingInterface.h b/libraries/controllers/src/controllers/ScriptingInterface.h new file mode 100644 index 0000000000..9af478e709 --- /dev/null +++ b/libraries/controllers/src/controllers/ScriptingInterface.h @@ -0,0 +1,155 @@ +// +// AbstractControllerScriptingInterface.h +// libraries/script-engine/src +// +// Created by Brad Hefta-Gaub on 12/17/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 +// + +#pragma once +#ifndef hifi_AbstractControllerScriptingInterface_h +#define hifi_AbstractControllerScriptingInterface_h + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include "UserInputMapper.h" +#include "StandardControls.h" + +namespace controller { + class InputController : public QObject { + Q_OBJECT + + public: + using Key = unsigned int; + using Pointer = std::shared_ptr; + + virtual void update() = 0; + virtual Key getKey() const = 0; + + public slots: + virtual bool isActive() const = 0; + virtual glm::vec3 getAbsTranslation() const = 0; + virtual glm::quat getAbsRotation() const = 0; + virtual glm::vec3 getLocTranslation() const = 0; + virtual glm::quat getLocRotation() const = 0; + + signals: + //void spatialEvent(const SpatialEvent& event); + }; + + /// handles scripting of input controller commands from JS + class ScriptingInterface : public QObject, public Dependency { + Q_OBJECT + Q_PROPERTY(QVariantMap Hardware READ getHardware CONSTANT FINAL) + Q_PROPERTY(QVariantMap Actions READ getActions CONSTANT FINAL) + Q_PROPERTY(QVariantMap Standard READ getStandard CONSTANT FINAL) + + public: + ScriptingInterface(); + virtual ~ScriptingInterface() {}; + + Q_INVOKABLE QVector getAllActions(); + Q_INVOKABLE QVector getAvailableInputs(unsigned int device); + Q_INVOKABLE QString getDeviceName(unsigned int device); + Q_INVOKABLE float getActionValue(int action); + Q_INVOKABLE int findDevice(QString name); + Q_INVOKABLE QVector getDeviceNames(); + Q_INVOKABLE int findAction(QString actionName); + Q_INVOKABLE QVector getActionNames() const; + + Q_INVOKABLE float getValue(const int& source) const; + Q_INVOKABLE float getButtonValue(StandardButtonChannel source, uint16_t device = 0) const; + Q_INVOKABLE float getAxisValue(StandardAxisChannel source, uint16_t device = 0) const; + Q_INVOKABLE Pose getPoseValue(const int& source) const; + Q_INVOKABLE Pose getPoseValue(StandardPoseChannel source, uint16_t device = 0) const; + + Q_INVOKABLE QObject* newMapping(const QString& mappingName = QUuid::createUuid().toString()); + Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); + Q_INVOKABLE void disableMapping(const QString& mappingName) { enableMapping(mappingName, false); } + Q_INVOKABLE QObject* parseMapping(const QString& json); + Q_INVOKABLE QObject* loadMapping(const QString& jsonUrl); + + + //Q_INVOKABLE bool isPrimaryButtonPressed() const; + //Q_INVOKABLE glm::vec2 getPrimaryJoystickPosition() const; + + //Q_INVOKABLE int getNumberOfButtons() const; + //Q_INVOKABLE bool isButtonPressed(int buttonIndex) const; + + //Q_INVOKABLE int getNumberOfTriggers() const; + //Q_INVOKABLE float getTriggerValue(int triggerIndex) const; + + //Q_INVOKABLE int getNumberOfJoysticks() const; + //Q_INVOKABLE glm::vec2 getJoystickPosition(int joystickIndex) const; + + //Q_INVOKABLE int getNumberOfSpatialControls() const; + //Q_INVOKABLE glm::vec3 getSpatialControlPosition(int controlIndex) const; + //Q_INVOKABLE glm::vec3 getSpatialControlVelocity(int controlIndex) const; + //Q_INVOKABLE glm::vec3 getSpatialControlNormal(int controlIndex) const; + //Q_INVOKABLE glm::quat getSpatialControlRawRotation(int controlIndex) const; + + Q_INVOKABLE const QVariantMap& getHardware() { return _hardware; } + Q_INVOKABLE const QVariantMap& getActions() { return _actions; } + Q_INVOKABLE const QVariantMap& getStandard() { return _standard; } + + bool isMouseCaptured() const { return _mouseCaptured; } + bool isTouchCaptured() const { return _touchCaptured; } + bool isWheelCaptured() const { return _wheelCaptured; } + bool areActionsCaptured() const { return _actionsCaptured; } + + public slots: + + virtual void captureMouseEvents() { _mouseCaptured = true; } + virtual void releaseMouseEvents() { _mouseCaptured = false; } + + virtual void captureTouchEvents() { _touchCaptured = true; } + virtual void releaseTouchEvents() { _touchCaptured = false; } + + virtual void captureWheelEvents() { _wheelCaptured = true; } + virtual void releaseWheelEvents() { _wheelCaptured = false; } + + virtual void captureActionEvents() { _actionsCaptured = true; } + virtual void releaseActionEvents() { _actionsCaptured = false; } + + signals: + void actionEvent(int action, float state); + void inputEvent(int action, float state); + void hardwareChanged(); + + private: + // Update the exposed variant maps reporting active hardware + void updateMaps(); + + QVariantMap _hardware; + QVariantMap _actions; + QVariantMap _standard; + + bool _mouseCaptured{ false }; + bool _touchCaptured{ false }; + bool _wheelCaptured{ false }; + bool _actionsCaptured{ false }; + }; + + +} + + +#endif // hifi_AbstractControllerScriptingInterface_h diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp new file mode 100644 index 0000000000..44f1bff1ae --- /dev/null +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -0,0 +1,158 @@ +// +// StandardController.cpp +// input-plugins/src/input-plugins +// +// Created by Brad Hefta-Gaub on 2015-10-11. +// 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 "StandardController.h" + +#include + +#include "UserInputMapper.h" +#include "impl/endpoints/StandardEndpoint.h" + +namespace controller { + +StandardController::StandardController() : InputDevice("Standard") { + _deviceID = UserInputMapper::STANDARD_DEVICE; +} + +StandardController::~StandardController() { +} + +void StandardController::update(float deltaTime, bool jointsCaptured) { +} + +void StandardController::focusOutEvent() { + _axisStateMap.clear(); + _buttonPressedMap.clear(); +}; + +Input::NamedVector StandardController::getAvailableInputs() const { + static Input::NamedVector availableInputs { + // Buttons + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + + // DPad + makePair(DU, "DU"), + makePair(DD, "DD"), + makePair(DL, "DL"), + makePair(DR, "DR"), + + // Bumpers + makePair(LB, "LB"), + makePair(RB, "RB"), + + // Stick press + makePair(LS, "LS"), + makePair(RS, "RS"), + + // Center buttons + makePair(START, "Start"), + makePair(BACK, "Back"), + + // Analog sticks + makePair(LY, "LY"), + makePair(LX, "LX"), + makePair(RY, "RY"), + makePair(RX, "RX"), + + // Triggers + makePair(LT, "LT"), + makePair(RT, "RT"), + + + // Finger abstractions + makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), + makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), + makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), + makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), + + makePair(LEFT_PRIMARY_INDEX, "LeftPrimaryIndex"), + makePair(LEFT_SECONDARY_INDEX, "LeftSecondaryIndex"), + makePair(RIGHT_PRIMARY_INDEX, "RightPrimaryIndex"), + makePair(RIGHT_SECONDARY_INDEX, "RightSecondaryIndex"), + + makePair(LEFT_GRIP, "LeftGrip"), + makePair(RIGHT_GRIP, "RightGrip"), + + // Poses + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), + + + // Aliases, PlayStation style names + makePair(LB, "L1"), + makePair(RB, "R1"), + makePair(LT, "L2"), + makePair(RT, "R2"), + makePair(LS, "L3"), + makePair(RS, "R3"), + makePair(BACK, "Select"), + makePair(A, "Cross"), + makePair(B, "Circle"), + makePair(X, "Square"), + makePair(Y, "Triangle"), + makePair(DU, "Up"), + makePair(DD, "Down"), + makePair(DL, "Left"), + makePair(DR, "Right"), + }; + return availableInputs; +} + +EndpointPointer StandardController::createEndpoint(const Input& input) const { + return std::make_shared(input); +} + +QString StandardController::getDefaultMappingConfig() const { + static const QString DEFAULT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard.json"; + return DEFAULT_MAPPING_JSON; +} + +// FIXME figure out how to move the shifted version to JSON +//void StandardController::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; +// +// // Hold front right shoulder button for precision controls +// // Left Joystick: Movement, strafing +// mapper.addInputChannel(UserInputMapper::TRANSLATE_Z, makeInput(controller::LY), makeInput(controller::RB), JOYSTICK_MOVE_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::TRANSLATE_X, makeInput(controller::LY), makeInput(controller::RB), JOYSTICK_MOVE_SPEED / 2.0f); +// +// // Right Joystick: Camera orientation +// mapper.addInputChannel(UserInputMapper::YAW, makeInput(controller::RX), makeInput(controller::RB), JOYSTICK_YAW_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::PITCH, makeInput(controller::RY), makeInput(controller::RB), JOYSTICK_PITCH_SPEED / 2.0f); +// +// // Dpad movement +// mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(controller::DU), makeInput(controller::RB), DPAD_MOVE_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(controller::DD), makeInput(controller::RB), DPAD_MOVE_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(controller::DR), makeInput(controller::RB), DPAD_MOVE_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(controller::DL), makeInput(controller::RB), DPAD_MOVE_SPEED / 2.0f); +// +// // Button controls +// mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(controller::Y), makeInput(controller::RB), DPAD_MOVE_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(controller::X), makeInput(controller::RB), DPAD_MOVE_SPEED / 2.0f); +// +// // Zoom +// mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(controller::RT), makeInput(controller::RB), BOOM_SPEED / 2.0f); +// mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(controller::LT), makeInput(controller::RB), BOOM_SPEED / 2.0f); +// +// mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(controller::RB)); +// +// mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(controller::B)); +// mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(controller::A)); +//} + +} diff --git a/libraries/controllers/src/controllers/StandardController.h b/libraries/controllers/src/controllers/StandardController.h new file mode 100644 index 0000000000..6c18c76371 --- /dev/null +++ b/libraries/controllers/src/controllers/StandardController.h @@ -0,0 +1,40 @@ +// +// StandardController.h +// input-plugins/src/input-plugins +// +// Created by Brad Hefta-Gaub on 2015-10-11. +// 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_StandardController_h +#define hifi_StandardController_h + +#include +#include + +#include "InputDevice.h" +#include "StandardControls.h" + +namespace controller { + +class StandardController : public QObject, public InputDevice { + Q_OBJECT + Q_PROPERTY(QString name READ getName) + +public: + virtual EndpointPointer createEndpoint(const Input& input) const override; + virtual Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + StandardController(); + virtual ~StandardController(); +}; + +} + +#endif // hifi_StandardController_h diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h new file mode 100644 index 0000000000..9c6defb865 --- /dev/null +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -0,0 +1,85 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 + +namespace controller { + + // Needs to match order and values of SDL_GameControllerButton + enum StandardButtonChannel { + // Button quad + A = 0, + B, + X, + Y, + + // Center buttons + BACK, + GUIDE, + START, + + // Stick press + LS, + RS, + + // Bumper press + LB, + RB, + + // DPad + DU, + DD, + DL, + DR, + + // These don't map to SDL types + LEFT_PRIMARY_THUMB, + LEFT_SECONDARY_THUMB, + RIGHT_PRIMARY_THUMB, + RIGHT_SECONDARY_THUMB, + + LEFT_PRIMARY_INDEX, + LEFT_SECONDARY_INDEX, + RIGHT_PRIMARY_INDEX, + RIGHT_SECONDARY_INDEX, + + LEFT_GRIP, + RIGHT_GRIP, + + NUM_STANDARD_BUTTONS + }; + + // Needs to match order and values of SDL_GameControllerAxis + enum StandardAxisChannel { + // Left Analog stick + LX = 0, + LY, + LZ, + // Right Analog stick + RX, + RY, + RZ, + // Triggers + LT, + RT, + NUM_STANDARD_AXES + }; + + // No correlation to SDL + enum StandardPoseChannel { + LEFT_HAND = 0, + RIGHT_HAND, + HEAD, + NUM_STANDARD_POSES + }; + + enum StandardCounts { + TRIGGERS = 2, + ANALOG_STICKS = 2, + POSES = 2, // FIXME 3? if we want to expose the head? + }; +} diff --git a/libraries/controllers/src/controllers/StateController.cpp b/libraries/controllers/src/controllers/StateController.cpp new file mode 100644 index 0000000000..6f89c6365c --- /dev/null +++ b/libraries/controllers/src/controllers/StateController.cpp @@ -0,0 +1,50 @@ +// +// StateController.cpp +// controllers/src/controllers +// +// Created by Sam Gateau on 2015-10-27. +// 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 "StateController.h" + +#include + +#include "DeviceProxy.h" +#include "UserInputMapper.h" +#include "impl/Endpoint.h" + +namespace controller { + +StateController::StateController() : InputDevice("Application") { +} + +StateController::~StateController() { +} + +void StateController::update(float deltaTime, bool jointsCaptured) {} + +void StateController::focusOutEvent() {} + +void StateController::addInputVariant(QString name, ReadLambda lambda) { + _namedReadLambdas.push_back(NamedReadLambda(name, lambda)); +} + +Input::NamedVector StateController::getAvailableInputs() const { + Input::NamedVector availableInputs; + int i = 0; + for (auto& pair : _namedReadLambdas) { + availableInputs.push_back(Input::NamedPair(Input(_deviceID, i, ChannelType::BUTTON), pair.first)); + i++; + } + return availableInputs; +} + +EndpointPointer StateController::createEndpoint(const Input& input) const { + return std::make_shared(_namedReadLambdas[input.getChannel()].second); +} + +} \ No newline at end of file diff --git a/libraries/controllers/src/controllers/StateController.h b/libraries/controllers/src/controllers/StateController.h new file mode 100644 index 0000000000..12f3e8b2f1 --- /dev/null +++ b/libraries/controllers/src/controllers/StateController.h @@ -0,0 +1,51 @@ +// +// StateController.h +// controllers/src/controllers +// +// Created by Sam Gateau on 2015-10-27. +// 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_StateController_h +#define hifi_StateController_h + +#include +#include + +#include "InputDevice.h" + +namespace controller { + +class StateController : public QObject, public InputDevice { + Q_OBJECT + Q_PROPERTY(QString name READ getName) + +public: + const QString& getName() const { return _name; } + + // Device functions + virtual Input::NamedVector getAvailableInputs() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; + virtual void focusOutEvent() override; + + StateController(); + virtual ~StateController(); + + using ReadLambda = std::function; + using NamedReadLambda = QPair; + + void addInputVariant(QString name, ReadLambda lambda); + + virtual EndpointPointer createEndpoint(const Input& input) const override; + + +protected: + QVector _namedReadLambdas; +}; + +} + +#endif // hifi_StateController_h \ No newline at end of file diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp new file mode 100755 index 0000000000..d33e215797 --- /dev/null +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -0,0 +1,1086 @@ +// +// Created by Sam Gateau on 4/27/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 "UserInputMapper.h" + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include "StandardController.h" +#include "StateController.h" + +#include "Logging.h" + +#include "impl/conditionals/AndConditional.h" +#include "impl/conditionals/EndpointConditional.h" +#include "impl/conditionals/ScriptConditional.h" + +#include "impl/endpoints/ActionEndpoint.h" +#include "impl/endpoints/AnyEndpoint.h" +#include "impl/endpoints/ArrayEndpoint.h" +#include "impl/endpoints/CompositeEndpoint.h" +#include "impl/endpoints/InputEndpoint.h" +#include "impl/endpoints/JSEndpoint.h" +#include "impl/endpoints/ScriptEndpoint.h" +#include "impl/endpoints/StandardEndpoint.h" + +#include "impl/Route.h" +#include "impl/Mapping.h" + + +namespace controller { + const uint16_t UserInputMapper::ACTIONS_DEVICE = Input::INVALID_DEVICE - 0xFF; + const uint16_t UserInputMapper::STANDARD_DEVICE = 0; +} + +// Default contruct allocate the poutput size with the current hardcoded action channels +controller::UserInputMapper::UserInputMapper() { + registerDevice(std::make_shared()); + registerDevice(std::make_shared()); +} + +namespace controller { + + +UserInputMapper::~UserInputMapper() { +} + +int UserInputMapper::recordDeviceOfType(const QString& deviceName) { + if (!_deviceCounts.contains(deviceName)) { + _deviceCounts[deviceName] = 0; + } + _deviceCounts[deviceName] += 1; + return _deviceCounts[deviceName]; +} + +void UserInputMapper::registerDevice(InputDevice::Pointer device) { + Locker locker(_lock); + if (device->_deviceID == Input::INVALID_DEVICE) { + device->_deviceID = getFreeDeviceID(); + } + const auto& deviceID = device->_deviceID; + + int numberOfType = recordDeviceOfType(device->getName()); + + qCDebug(controllers) << "Registered input device <" << device->getName() << "> deviceID = " << deviceID; + for (const auto& inputMapping : device->getAvailableInputs()) { + const auto& input = inputMapping.first; + // Ignore aliases + if (_endpointsByInput.count(input)) { + continue; + } + + Endpoint::Pointer endpoint = device->createEndpoint(input); + if (!endpoint) { + if (input.device == STANDARD_DEVICE) { + endpoint = std::make_shared(input); + } else if (input.device == ACTIONS_DEVICE) { + endpoint = std::make_shared(input); + } else { + endpoint = std::make_shared(input); + } + } + _inputsByEndpoint[endpoint] = input; + _endpointsByInput[input] = endpoint; + } + + _registeredDevices[deviceID] = device; + auto mapping = loadMapping(device->getDefaultMappingConfig()); + if (mapping) { + _mappingsByDevice[deviceID] = mapping; + enableMapping(mapping); + } + + emit hardwareChanged(); +} + +// FIXME remove the associated device mappings +void UserInputMapper::removeDevice(int deviceID) { + Locker locker(_lock); + auto proxyEntry = _registeredDevices.find(deviceID); + if (_registeredDevices.end() == proxyEntry) { + qCWarning(controllers) << "Attempted to remove unknown device " << deviceID; + return; + } + auto proxy = proxyEntry->second; + auto mappingsEntry = _mappingsByDevice.find(deviceID); + if (_mappingsByDevice.end() != mappingsEntry) { + disableMapping(mappingsEntry->second); + _mappingsByDevice.erase(mappingsEntry); + } + + _registeredDevices.erase(proxyEntry); + + emit hardwareChanged(); +} + + +void UserInputMapper::loadDefaultMapping(uint16 deviceID) { + Locker locker(_lock); + auto proxyEntry = _registeredDevices.find(deviceID); + if (_registeredDevices.end() == proxyEntry) { + qCWarning(controllers) << "Unknown deviceID " << deviceID; + return; + } + + + auto mapping = loadMapping(proxyEntry->second->getDefaultMappingConfig()); + if (mapping) { + auto prevMapping = _mappingsByDevice[deviceID]; + disableMapping(prevMapping); + + _mappingsByDevice[deviceID] = mapping; + enableMapping(mapping); + } + + emit hardwareChanged(); +} + +InputDevice::Pointer UserInputMapper::getDevice(const Input& input) { + Locker locker(_lock); + auto device = _registeredDevices.find(input.getDevice()); + if (device != _registeredDevices.end()) { + return (device->second); + } else { + return InputDevice::Pointer(); + } +} + +QString UserInputMapper::getDeviceName(uint16 deviceID) { + Locker locker(_lock); + if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { + return _registeredDevices[deviceID]->_name; + } + return QString("unknown"); +} + +int UserInputMapper::findDevice(QString name) const { + Locker locker(_lock); + for (auto device : _registeredDevices) { + if (device.second->_name == name) { + return device.first; + } + } + return Input::INVALID_DEVICE; +} + +QVector UserInputMapper::getDeviceNames() { + Locker locker(_lock); + QVector result; + for (auto device : _registeredDevices) { + QString deviceName = device.second->_name.split(" (")[0]; + result << deviceName; + } + return result; +} + +int UserInputMapper::findAction(const QString& actionName) const { + return findDeviceInput("Actions." + actionName).getChannel(); +} + +Input UserInputMapper::findDeviceInput(const QString& inputName) const { + Locker locker(_lock); + // Split the full input name as such: deviceName.inputName + auto names = inputName.split('.'); + + if (names.size() >= 2) { + // Get the device name: + auto deviceName = names[0]; + auto inputName = names[1]; + + int deviceID = findDevice(deviceName); + if (deviceID != Input::INVALID_DEVICE) { + const auto& device = _registeredDevices.at(deviceID); + auto deviceInputs = device->getAvailableInputs(); + + for (auto input : deviceInputs) { + if (input.second == inputName) { + return input.first; + } + } + + qCDebug(controllers) << "Couldn\'t find InputChannel named <" << inputName << "> for device <" << deviceName << ">"; + + } else { + qCDebug(controllers) << "Couldn\'t find InputDevice named <" << deviceName << ">"; + findDevice(deviceName); + } + } else { + qCDebug(controllers) << "Couldn\'t understand <" << inputName << "> as a valid inputDevice.inputName"; + } + + return Input::INVALID_INPUT; +} + +void fixBisectedAxis(float& full, float& negative, float& positive) { + full = full + (negative * -1.0f) + positive; + negative = full >= 0.0f ? 0.0f : full * -1.0f; + positive = full <= 0.0f ? 0.0f : full; +} + +void UserInputMapper::update(float deltaTime) { + Locker locker(_lock); + // Reset the axis state for next loop + for (auto& channel : _actionStates) { + channel = 0.0f; + } + + for (auto& channel : _poseStates) { + channel = Pose(); + } + + // Run the mappings code + runMappings(); + + // merge the bisected and non-bisected axes for now + fixBisectedAxis(_actionStates[toInt(Action::TRANSLATE_X)], _actionStates[toInt(Action::LATERAL_LEFT)], _actionStates[toInt(Action::LATERAL_RIGHT)]); + fixBisectedAxis(_actionStates[toInt(Action::TRANSLATE_Y)], _actionStates[toInt(Action::VERTICAL_DOWN)], _actionStates[toInt(Action::VERTICAL_UP)]); + fixBisectedAxis(_actionStates[toInt(Action::TRANSLATE_Z)], _actionStates[toInt(Action::LONGITUDINAL_FORWARD)], _actionStates[toInt(Action::LONGITUDINAL_BACKWARD)]); + fixBisectedAxis(_actionStates[toInt(Action::TRANSLATE_CAMERA_Z)], _actionStates[toInt(Action::BOOM_IN)], _actionStates[toInt(Action::BOOM_OUT)]); + fixBisectedAxis(_actionStates[toInt(Action::ROTATE_Y)], _actionStates[toInt(Action::YAW_LEFT)], _actionStates[toInt(Action::YAW_RIGHT)]); + fixBisectedAxis(_actionStates[toInt(Action::ROTATE_X)], _actionStates[toInt(Action::PITCH_UP)], _actionStates[toInt(Action::PITCH_DOWN)]); + + static const float EPSILON = 0.01f; + for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) { + _actionStates[i] *= _actionScales[i]; + // Emit only on change, and emit when moving back to 0 + if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) { + _lastActionStates[i] = _actionStates[i]; + emit actionEvent(i, _actionStates[i]); + } + // TODO: emit signal for pose changes + } + + auto standardInputs = getStandardInputs(); + if (_lastStandardStates.size() != standardInputs.size()) { + _lastStandardStates.resize(standardInputs.size()); + for (auto& lastValue : _lastStandardStates) { + lastValue = 0; + } + } + + for (int i = 0; i < standardInputs.size(); ++i) { + const auto& input = standardInputs[i].first; + float value = getValue(input); + float& oldValue = _lastStandardStates[i]; + if (value != oldValue) { + oldValue = value; + emit inputEvent(input.id, value); + } + } +} + +Input::NamedVector UserInputMapper::getAvailableInputs(uint16 deviceID) const { + Locker locker(_lock); + auto iterator = _registeredDevices.find(deviceID); + return iterator->second->getAvailableInputs(); +} + +QVector UserInputMapper::getAllActions() const { + Locker locker(_lock); + QVector actions; + for (auto i = 0; i < toInt(Action::NUM_ACTIONS); i++) { + actions.append(Action(i)); + } + return actions; +} + +QString UserInputMapper::getActionName(Action action) const { + Locker locker(_lock); + for (auto actionPair : getActionInputs()) { + if (actionPair.first.channel == toInt(action)) { + return actionPair.second; + } + } + return QString(); +} + + +QVector UserInputMapper::getActionNames() const { + Locker locker(_lock); + QVector result; + for (auto actionPair : getActionInputs()) { + result << actionPair.second; + } + return result; +} +/* +void UserInputMapper::assignDefaulActionScales() { + _actionScales[toInt(Action::LONGITUDINAL_BACKWARD)] = 1.0f; // 1m per unit + _actionScales[toInt(Action::LONGITUDINAL_FORWARD)] = 1.0f; // 1m per unit + _actionScales[toInt(Action::LATERAL_LEFT)] = 1.0f; // 1m per unit + _actionScales[toInt(Action::LATERAL_RIGHT)] = 1.0f; // 1m per unit + _actionScales[toInt(Action::VERTICAL_DOWN)] = 1.0f; // 1m per unit + _actionScales[toInt(Action::VERTICAL_UP)] = 1.0f; // 1m per unit + _actionScales[toInt(Action::YAW_LEFT)] = 1.0f; // 1 degree per unit + _actionScales[toInt(Action::YAW_RIGHT)] = 1.0f; // 1 degree per unit + _actionScales[toInt(Action::PITCH_DOWN)] = 1.0f; // 1 degree per unit + _actionScales[toInt(Action::PITCH_UP)] = 1.0f; // 1 degree per unit + _actionScales[toInt(Action::BOOM_IN)] = 0.5f; // .5m per unit + _actionScales[toInt(Action::BOOM_OUT)] = 0.5f; // .5m per unit + _actionScales[toInt(Action::LEFT_HAND)] = 1.0f; // default + _actionScales[toInt(Action::RIGHT_HAND)] = 1.0f; // default + _actionScales[toInt(Action::LEFT_HAND_CLICK)] = 1.0f; // on + _actionScales[toInt(Action::RIGHT_HAND_CLICK)] = 1.0f; // on + _actionScales[toInt(Action::SHIFT)] = 1.0f; // on + _actionScales[toInt(Action::ACTION1)] = 1.0f; // default + _actionScales[toInt(Action::ACTION2)] = 1.0f; // default + _actionScales[toInt(Action::TRANSLATE_X)] = 1.0f; // default + _actionScales[toInt(Action::TRANSLATE_Y)] = 1.0f; // default + _actionScales[toInt(Action::TRANSLATE_Z)] = 1.0f; // default + _actionScales[toInt(Action::ROLL)] = 1.0f; // default + _actionScales[toInt(Action::PITCH)] = 1.0f; // default + _actionScales[toInt(Action::YAW)] = 1.0f; // default +} +*/ + +static int actionMetaTypeId = qRegisterMetaType(); +static int inputMetaTypeId = qRegisterMetaType(); +static int inputPairMetaTypeId = qRegisterMetaType(); +static int poseMetaTypeId = qRegisterMetaType("Pose"); + + +QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input); +void inputFromScriptValue(const QScriptValue& object, Input& input); +QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action); +void actionFromScriptValue(const QScriptValue& object, Action& action); +QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair); +void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair); + +QScriptValue inputToScriptValue(QScriptEngine* engine, const Input& input) { + QScriptValue obj = engine->newObject(); + obj.setProperty("device", input.getDevice()); + obj.setProperty("channel", input.getChannel()); + obj.setProperty("type", (unsigned short)input.getType()); + obj.setProperty("id", input.getID()); + return obj; +} + +void inputFromScriptValue(const QScriptValue& object, Input& input) { + input.id = object.property("id").toInt32(); +} + +QScriptValue actionToScriptValue(QScriptEngine* engine, const Action& action) { + QScriptValue obj = engine->newObject(); + auto userInputMapper = DependencyManager::get(); + obj.setProperty("action", (int)action); + obj.setProperty("actionName", userInputMapper->getActionName(action)); + return obj; +} + +void actionFromScriptValue(const QScriptValue& object, Action& action) { + action = Action(object.property("action").toVariant().toInt()); +} + +QScriptValue inputPairToScriptValue(QScriptEngine* engine, const Input::NamedPair& inputPair) { + QScriptValue obj = engine->newObject(); + obj.setProperty("input", inputToScriptValue(engine, inputPair.first)); + obj.setProperty("inputName", inputPair.second); + return obj; +} + +void inputPairFromScriptValue(const QScriptValue& object, Input::NamedPair& inputPair) { + inputFromScriptValue(object.property("input"), inputPair.first); + inputPair.second = QString(object.property("inputName").toVariant().toString()); +} + +void UserInputMapper::registerControllerTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType >(engine); + qScriptRegisterSequenceMetaType(engine); + qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); + qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); + qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); + + qScriptRegisterMetaType(engine, Pose::toScriptValue, Pose::fromScriptValue); +} + +Input UserInputMapper::makeStandardInput(controller::StandardButtonChannel button) { + return Input(STANDARD_DEVICE, button, ChannelType::BUTTON); +} + +Input UserInputMapper::makeStandardInput(controller::StandardAxisChannel axis) { + return Input(STANDARD_DEVICE, axis, ChannelType::AXIS); +} + +Input UserInputMapper::makeStandardInput(controller::StandardPoseChannel pose) { + return Input(STANDARD_DEVICE, pose, ChannelType::POSE); +} + +static auto lastDebugTime = usecTimestampNow(); +static auto debugRoutes = false; +static auto debuggableRoutes = false; +static const auto DEBUG_INTERVAL = USECS_PER_SECOND; + +void UserInputMapper::runMappings() { + auto now = usecTimestampNow(); + if (debuggableRoutes && now - lastDebugTime > DEBUG_INTERVAL) { + lastDebugTime = now; + debugRoutes = true; + } + + if (debugRoutes) { + qCDebug(controllers) << "Beginning mapping frame"; + } + for (auto endpointEntry : this->_endpointsByInput) { + endpointEntry.second->reset(); + } + + if (debugRoutes) { + qCDebug(controllers) << "Processing device routes"; + } + // Now process the current values for each level of the stack + applyRoutes(_deviceRoutes); + + if (debugRoutes) { + qCDebug(controllers) << "Processing standard routes"; + } + applyRoutes(_standardRoutes); + + if (debugRoutes) { + qCDebug(controllers) << "Done with mappings"; + } + debugRoutes = false; +} + +// Encapsulate the logic that routes should not be read before they are written +void UserInputMapper::applyRoutes(const Route::List& routes) { + Route::List deferredRoutes; + + for (const auto& route : routes) { + if (!route) { + continue; + } + + // Try all the deferred routes + deferredRoutes.remove_if([](Route::Pointer route) { + return UserInputMapper::applyRoute(route); + }); + + if (!applyRoute(route)) { + deferredRoutes.push_back(route); + } + } + + bool force = true; + for (const auto& route : deferredRoutes) { + UserInputMapper::applyRoute(route, force); + } +} + + +bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Applying route " << route->json; + } + + // If the source hasn't been written yet, defer processing of this route + auto source = route->source; + auto sourceInput = source->getInput(); + if (sourceInput.device == STANDARD_DEVICE && !force && source->writeable()) { + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Source not yet written, deferring"; + } + return false; + } + + if (route->conditional) { + // FIXME for endpoint conditionals we need to check if they've been written + if (!route->conditional->satisfied()) { + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Conditional failed"; + } + return true; + } + } + + + // Most endpoints can only be read once (though a given mapping can route them to + // multiple places). Consider... If the default is to wire the A button to JUMP + // and someone else wires it to CONTEXT_MENU, I don't want both to occur when + // I press the button. The exception is if I'm wiring a control back to itself + // in order to adjust my interface, like inverting the Y axis on an analog stick + if (!source->readable()) { + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Source unreadable"; + } + return true; + } + + auto destination = route->destination; + // THis could happen if the route destination failed to create + // FIXME: Maybe do not create the route if the destination failed and avoid this case ? + if (!destination) { + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Bad Destination"; + } + return true; + } + + if (!destination->writeable()) { + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Destination unwritable"; + } + return true; + } + + // Fetch the value, may have been overriden by previous loopback routes + if (source->isPose()) { + Pose value = getPose(source); + static const Pose IDENTITY_POSE { vec3(), quat() }; + if (debugRoutes && route->debug) { + if (!value.valid) { + qCDebug(controllers) << "Applying invalid pose"; + } else if (value == IDENTITY_POSE) { + qCDebug(controllers) << "Applying identity pose"; + } else { + qCDebug(controllers) << "Applying valid pose"; + } + } + // no filters yet for pose + destination->apply(value, source); + } else { + // Fetch the value, may have been overriden by previous loopback routes + float value = getValue(source); + + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Value was " << value; + } + // Apply each of the filters. + for (const auto& filter : route->filters) { + value = filter->apply(value); + } + + if (debugRoutes && route->debug) { + qCDebug(controllers) << "Filtered value was " << value; + } + + destination->apply(value, source); + } + return true; +} + +Endpoint::Pointer UserInputMapper::endpointFor(const QJSValue& endpoint) { + if (endpoint.isNumber()) { + return endpointFor(Input(endpoint.toInt())); + } + + if (endpoint.isCallable()) { + auto result = std::make_shared(endpoint); + return result; + } + + qWarning() << "Unsupported input type " << endpoint.toString(); + return Endpoint::Pointer(); +} + +Endpoint::Pointer UserInputMapper::endpointFor(const QScriptValue& endpoint) { + if (endpoint.isNumber()) { + return endpointFor(Input(endpoint.toInt32())); + } + + if (endpoint.isFunction()) { + auto result = std::make_shared(endpoint); + return result; + } + + if (endpoint.isArray()) { + int length = endpoint.property("length").toInteger(); + Endpoint::List children; + for (int i = 0; i < length; i++) { + QScriptValue arrayItem = endpoint.property(i); + Endpoint::Pointer destination = endpointFor(arrayItem); + if (!destination) { + return Endpoint::Pointer(); + } + children.push_back(destination); + } + return std::make_shared(children); + } + + + qWarning() << "Unsupported input type " << endpoint.toString(); + return Endpoint::Pointer(); +} + +Endpoint::Pointer UserInputMapper::endpointFor(const Input& inputId) const { + Locker locker(_lock); + auto iterator = _endpointsByInput.find(inputId); + if (_endpointsByInput.end() == iterator) { + qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16); + return Endpoint::Pointer(); + } + return iterator->second; +} + +Endpoint::Pointer UserInputMapper::compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second) { + EndpointPair pair(first, second); + Endpoint::Pointer result; + auto iterator = _compositeEndpoints.find(pair); + if (_compositeEndpoints.end() == iterator) { + result = std::make_shared(first, second); + _compositeEndpoints[pair] = result; + } else { + result = iterator->second; + } + return result; +} + + +Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) { + Locker locker(_lock); + if (_mappingsByName.count(mappingName)) { + qCWarning(controllers) << "Refusing to recreate mapping named " << mappingName; + } + qDebug() << "Creating new Mapping " << mappingName; + auto mapping = std::make_shared(mappingName); + _mappingsByName[mappingName] = mapping; + return mapping; +} + +// FIXME handle asynchronous loading in the UserInputMapper +//QObject* ScriptingInterface::loadMapping(const QString& jsonUrl) { +// QObject* result = nullptr; +// auto request = ResourceManager::createResourceRequest(nullptr, QUrl(jsonUrl)); +// if (request) { +// QEventLoop eventLoop; +// request->setCacheEnabled(false); +// connect(request, &ResourceRequest::finished, &eventLoop, &QEventLoop::quit); +// request->send(); +// if (request->getState() != ResourceRequest::Finished) { +// eventLoop.exec(); +// } +// +// if (request->getResult() == ResourceRequest::Success) { +// result = parseMapping(QString(request->getData())); +// } else { +// qCWarning(controllers) << "Failed to load mapping url <" << jsonUrl << ">" << endl; +// } +// request->deleteLater(); +// } +// return result; +//} + +void UserInputMapper::enableMapping(const QString& mappingName, bool enable) { + Locker locker(_lock); + qCDebug(controllers) << "Attempting to enable mapping " << mappingName; + auto iterator = _mappingsByName.find(mappingName); + if (_mappingsByName.end() == iterator) { + qCWarning(controllers) << "Request to enable / disable unknown mapping " << mappingName; + return; + } + + auto mapping = iterator->second; + if (enable) { + enableMapping(mapping); + } else { + disableMapping(mapping); + } +} + +float UserInputMapper::getValue(const Endpoint::Pointer& endpoint) { + return endpoint->value(); +} + +float UserInputMapper::getValue(const Input& input) const { + Locker locker(_lock); + auto endpoint = endpointFor(input); + if (!endpoint) { + return 0; + } + return endpoint->value(); +} + +Pose UserInputMapper::getPose(const Endpoint::Pointer& endpoint) { + if (!endpoint->isPose()) { + return Pose(); + } + return endpoint->pose(); +} + +Pose UserInputMapper::getPose(const Input& input) const { + Locker locker(_lock); + auto endpoint = endpointFor(input); + if (!endpoint) { + return Pose(); + } + return getPose(endpoint); +} + +Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { + Locker locker(_lock); + if (jsonFile.isEmpty()) { + return Mapping::Pointer(); + } + QString json; + { + QFile file(jsonFile); + if (file.open(QFile::ReadOnly | QFile::Text)) { + json = QTextStream(&file).readAll(); + } + file.close(); + } + return parseMapping(json); +} + + + +static const QString JSON_NAME = QStringLiteral("name"); +static const QString JSON_CHANNELS = QStringLiteral("channels"); +static const QString JSON_CHANNEL_FROM = QStringLiteral("from"); +static const QString JSON_CHANNEL_DEBUG = QStringLiteral("debug"); +static const QString JSON_CHANNEL_WHEN = QStringLiteral("when"); +static const QString JSON_CHANNEL_TO = QStringLiteral("to"); +static const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters"); + +Endpoint::Pointer UserInputMapper::parseEndpoint(const QJsonValue& value) { + Endpoint::Pointer result; + if (value.isString()) { + auto input = findDeviceInput(value.toString()); + result = endpointFor(input); + } else if (value.isArray()) { + return parseAny(value); + } else if (value.isObject()) { + auto axisEndpoint = parseAxis(value); + if (axisEndpoint) { + return axisEndpoint; + } + // if we have other types of endpoints that are objects, follow the axisEndpoint example, and place them here + + // Endpoint is defined as an object, we expect a js function then + return Endpoint::Pointer(); + } + + if (!result) { + qWarning() << "Invalid endpoint definition " << value; + } + return result; +} + + +Conditional::Pointer UserInputMapper::conditionalFor(const QJSValue& condition) { + return Conditional::Pointer(); +} + +Conditional::Pointer UserInputMapper::conditionalFor(const QScriptValue& condition) { + if (condition.isArray()) { + int length = condition.property("length").toInteger(); + Conditional::List children; + for (int i = 0; i < length; i++) { + Conditional::Pointer destination = conditionalFor(condition.property(i)); + if (!destination) { + return Conditional::Pointer(); + } + children.push_back(destination); + } + return std::make_shared(children); + } + + if (condition.isNumber()) { + return conditionalFor(Input(condition.toInt32())); + } + + if (condition.isFunction()) { + return std::make_shared(condition); + } + + qWarning() << "Unsupported conditional type " << condition.toString(); + return Conditional::Pointer(); +} + +Conditional::Pointer UserInputMapper::conditionalFor(const Input& inputId) const { + Locker locker(_lock); + auto iterator = _endpointsByInput.find(inputId); + if (_endpointsByInput.end() == iterator) { + qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16); + return Conditional::Pointer(); + } + return std::make_shared(iterator->second); +} + +Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) { + if (value.isArray()) { + // Support "when" : [ "GamePad.RB", "GamePad.LB" ] + Conditional::List children; + for (auto arrayItem : value.toArray()) { + Conditional::Pointer childConditional = parseConditional(arrayItem); + if (!childConditional) { + return Conditional::Pointer(); + } + children.push_back(childConditional); + } + return std::make_shared(children); + } else if (value.isString()) { + // Support "when" : "GamePad.RB" + auto input = findDeviceInput(value.toString()); + auto endpoint = endpointFor(input); + if (!endpoint) { + return Conditional::Pointer(); + } + + return std::make_shared(endpoint); + } + + return Conditional::parse(value); +} + +Filter::Pointer UserInputMapper::parseFilter(const QJsonValue& value) { + Filter::Pointer result; + if (value.isString()) { + result = Filter::getFactory().create(value.toString()); + } else if (value.isObject()) { + result = Filter::parse(value.toObject()); + } + + if (!result) { + qWarning() << "Invalid filter definition " << value; + } + + return result; +} + +Filter::List UserInputMapper::parseFilters(const QJsonValue& value) { + if (value.isNull()) { + return Filter::List(); + } + + if (value.isArray()) { + Filter::List result; + auto filtersArray = value.toArray(); + for (auto filterValue : filtersArray) { + Filter::Pointer filter = parseFilter(filterValue); + if (!filter) { + return Filter::List(); + } + result.push_back(filter); + } + return result; + } + + Filter::Pointer filter = parseFilter(value); + if (!filter) { + return Filter::List(); + } + return Filter::List({ filter }); +} + +Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { + if (value.isArray()) { + ArrayEndpoint::Pointer result = std::make_shared(); + for (auto arrayItem : value.toArray()) { + Endpoint::Pointer destination = parseEndpoint(arrayItem); + if (!destination) { + return Endpoint::Pointer(); + } + result->_children.push_back(destination); + } + return result; + } + + return parseEndpoint(value); +} + +Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { + if (value.isObject()) { + auto object = value.toObject(); + if (object.contains("makeAxis")) { + auto axisValue = object.value("makeAxis"); + if (axisValue.isArray()) { + auto axisArray = axisValue.toArray(); + static const int AXIS_ARRAY_SIZE = 2; // axis can only have 2 children + if (axisArray.size() == AXIS_ARRAY_SIZE) { + Endpoint::Pointer first = parseEndpoint(axisArray.first()); + Endpoint::Pointer second = parseEndpoint(axisArray.last()); + if (first && second) { + return std::make_shared(first, second); + } + } + } + } + } + return Endpoint::Pointer(); +} + +Endpoint::Pointer UserInputMapper::parseAny(const QJsonValue& value) { + if (value.isArray()) { + Endpoint::List children; + for (auto arrayItem : value.toArray()) { + Endpoint::Pointer destination = parseEndpoint(arrayItem); + if (!destination) { + return Endpoint::Pointer(); + } + children.push_back(destination); + } + return std::make_shared(children); + } + return Endpoint::Pointer(); +} + +Endpoint::Pointer UserInputMapper::parseSource(const QJsonValue& value) { + if (value.isObject()) { + auto axisEndpoint = parseAxis(value); + if (axisEndpoint) { + return axisEndpoint; + } + // if we have other types of endpoints that are objects, follow the axisEndpoint example, and place them here + } else if (value.isArray()) { + return parseAny(value); + } + return parseEndpoint(value); +} + +Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { + if (!value.isObject()) { + return Route::Pointer(); + } + + auto obj = value.toObject(); + Route::Pointer result = std::make_shared(); + + result->json = QString(QJsonDocument(obj).toJson()); + result->source = parseSource(obj[JSON_CHANNEL_FROM]); + result->debug = obj[JSON_CHANNEL_DEBUG].toBool(); + if (!result->source) { + qWarning() << "Invalid route source " << obj[JSON_CHANNEL_FROM]; + return Route::Pointer(); + } + + + result->destination = parseDestination(obj[JSON_CHANNEL_TO]); + if (!result->destination) { + qWarning() << "Invalid route destination " << obj[JSON_CHANNEL_TO]; + return Route::Pointer(); + } + + if (result->source == result->destination) { + qWarning() << "Loopback routes not supported " << obj; + return Route::Pointer(); + } + + if (obj.contains(JSON_CHANNEL_WHEN)) { + auto conditionalsValue = obj[JSON_CHANNEL_WHEN]; + result->conditional = parseConditional(conditionalsValue); + if (!result->conditional) { + qWarning() << "Invalid route conditionals " << conditionalsValue; + return Route::Pointer(); + } + } + + if (obj.contains(JSON_CHANNEL_FILTERS)) { + auto filtersValue = obj[JSON_CHANNEL_FILTERS]; + result->filters = parseFilters(filtersValue); + if (result->filters.empty()) { + qWarning() << "Invalid route filters " << filtersValue; + return Route::Pointer(); + } + } + + return result; +} + +Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { + if (!json.isObject()) { + return Mapping::Pointer(); + } + + auto obj = json.toObject(); + auto mapping = std::make_shared("default"); + mapping->name = obj[JSON_NAME].toString(); + const auto& jsonChannels = obj[JSON_CHANNELS].toArray(); + for (const auto& channelIt : jsonChannels) { + Route::Pointer route = parseRoute(channelIt); + if (!route) { + qWarning() << "Couldn't parse route"; + continue; + } + mapping->routes.push_back(route); + } + return mapping; +} + +Mapping::Pointer UserInputMapper::parseMapping(const QString& json) { + Mapping::Pointer result; + QJsonObject obj; + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8(), &error); + // check validity of the document + if (doc.isNull()) { + qDebug() << "Invalid JSON...\n"; + qDebug() << error.errorString(); + qDebug() << "JSON was:\n" << json << endl; + return Mapping::Pointer(); + } + + if (!doc.isObject()) { + qWarning() << "Mapping json Document is not an object" << endl; + qDebug() << "JSON was:\n" << json << endl; + return Mapping::Pointer(); + } + return parseMapping(doc.object()); +} + +template +bool hasDebuggableRoute(const T& routes) { + for (auto route : routes) { + if (route->debug) { + return true; + } + } + return false; +} + + +void UserInputMapper::enableMapping(const Mapping::Pointer& mapping) { + Locker locker(_lock); + // New routes for a device get injected IN FRONT of existing routes. Routes + // are processed in order so this ensures that the standard -> action processing + // takes place after all of the hardware -> standard or hardware -> action processing + // because standard -> action is the first set of routes added. + Route::List standardRoutes = mapping->routes; + standardRoutes.remove_if([](const Route::Pointer& value) { + return (value->source->getInput().device != STANDARD_DEVICE); + }); + _standardRoutes.insert(_standardRoutes.begin(), standardRoutes.begin(), standardRoutes.end()); + + Route::List deviceRoutes = mapping->routes; + deviceRoutes.remove_if([](const Route::Pointer& value) { + return (value->source->getInput().device == STANDARD_DEVICE); + }); + _deviceRoutes.insert(_deviceRoutes.begin(), deviceRoutes.begin(), deviceRoutes.end()); + + if (!debuggableRoutes) { + debuggableRoutes = hasDebuggableRoute(_deviceRoutes) || hasDebuggableRoute(_standardRoutes); + } +} + +void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) { + Locker locker(_lock); + const auto& deviceRoutes = mapping->routes; + std::set routeSet(deviceRoutes.begin(), deviceRoutes.end()); + _deviceRoutes.remove_if([&](const Route::Pointer& value){ + return routeSet.count(value) != 0; + }); + _standardRoutes.remove_if([&](const Route::Pointer& value) { + return routeSet.count(value) != 0; + }); + + if (debuggableRoutes) { + debuggableRoutes = hasDebuggableRoute(_deviceRoutes) || hasDebuggableRoute(_standardRoutes); + } +} + +} + diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h new file mode 100644 index 0000000000..7684ecb7c5 --- /dev/null +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -0,0 +1,197 @@ +// +// Created by Sam Gateau on 4/27/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 +// + +#pragma once +#ifndef hifi_UserInputMapper_h +#define hifi_UserInputMapper_h + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "Forward.h" +#include "Pose.h" +#include "Input.h" +#include "InputDevice.h" +#include "DeviceProxy.h" +#include "StandardControls.h" +#include "Actions.h" + +namespace controller { + + class RouteBuilderProxy; + class MappingBuilderProxy; + + class UserInputMapper : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + Q_ENUMS(Action) + + public: + // FIXME move to unordered set / map + using EndpointToInputMap = std::map; + using MappingNameMap = std::map; + using MappingDeviceMap = std::map; + using MappingStack = std::list; + using InputToEndpointMap = std::map; + using EndpointSet = std::unordered_set; + using EndpointPair = std::pair; + using EndpointPairMap = std::map; + using DevicesMap = std::map; + using uint16 = uint16_t; + using uint32 = uint32_t; + + static const uint16_t ACTIONS_DEVICE; + static const uint16_t STANDARD_DEVICE; + + UserInputMapper(); + virtual ~UserInputMapper(); + + + static void registerControllerTypes(QScriptEngine* engine); + + void registerDevice(InputDevice::Pointer device); + InputDevice::Pointer getDevice(const Input& input); + QString getDeviceName(uint16 deviceID); + + Input::NamedVector getAvailableInputs(uint16 deviceID) const; + Input::NamedVector getActionInputs() const { return getAvailableInputs(ACTIONS_DEVICE); } + Input::NamedVector getStandardInputs() const { return getAvailableInputs(STANDARD_DEVICE); } + + int findDevice(QString name) const; + QVector getDeviceNames(); + Input findDeviceInput(const QString& inputName) const; + + QVector getAllActions() const; + QString getActionName(Action action) const; + float getActionState(Action action) const { return _actionStates[toInt(action)]; } + Pose getPoseState(Action action) const { return _poseStates[toInt(action)]; } + int findAction(const QString& actionName) const; + QVector getActionNames() const; + Input inputFromAction(Action action) const { return getActionInputs()[toInt(action)].first; } + + void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; } + void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; } + void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; } + + static Input makeStandardInput(controller::StandardButtonChannel button); + static Input makeStandardInput(controller::StandardAxisChannel axis); + static Input makeStandardInput(controller::StandardPoseChannel pose); + + void removeDevice(int device); + + // 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; } + + DevicesMap getDevices() { return _registeredDevices; } + uint16 getStandardDeviceID() const { return STANDARD_DEVICE; } + InputDevice::Pointer getStandardDevice() { return _registeredDevices[getStandardDeviceID()]; } + + MappingPointer newMapping(const QString& mappingName); + MappingPointer parseMapping(const QString& json); + MappingPointer loadMapping(const QString& jsonFile); + + void loadDefaultMapping(uint16 deviceID); + void enableMapping(const QString& mappingName, bool enable = true); + float getValue(const Input& input) const; + Pose getPose(const Input& input) const; + + signals: + void actionEvent(int action, float state); + void inputEvent(int input, float state); + void hardwareChanged(); + + protected: + // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device. + uint16 getFreeDeviceID() { return _nextFreeDeviceID++; } + DevicesMap _registeredDevices; + uint16 _nextFreeDeviceID = STANDARD_DEVICE + 1; + + std::vector _actionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); + std::vector _actionScales = std::vector(toInt(Action::NUM_ACTIONS), 1.0f); + std::vector _lastActionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); + std::vector _poseStates = std::vector(toInt(Action::NUM_ACTIONS)); + std::vector _lastStandardStates = std::vector(); + + glm::mat4 _sensorToWorldMat; + + int recordDeviceOfType(const QString& deviceName); + QHash _deviceCounts; + + static float getValue(const EndpointPointer& endpoint); + static Pose getPose(const EndpointPointer& endpoint); + + friend class RouteBuilderProxy; + friend class MappingBuilderProxy; + + void runMappings(); + + static void applyRoutes(const RouteList& route); + static bool applyRoute(const RoutePointer& route, bool force = false); + void enableMapping(const MappingPointer& mapping); + void disableMapping(const MappingPointer& mapping); + EndpointPointer endpointFor(const QJSValue& endpoint); + EndpointPointer endpointFor(const QScriptValue& endpoint); + EndpointPointer endpointFor(const Input& endpoint) const; + EndpointPointer compositeEndpointFor(EndpointPointer first, EndpointPointer second); + ConditionalPointer conditionalFor(const QJSValue& endpoint); + ConditionalPointer conditionalFor(const QScriptValue& endpoint); + ConditionalPointer conditionalFor(const Input& endpoint) const; + + MappingPointer parseMapping(const QJsonValue& json); + RoutePointer parseRoute(const QJsonValue& value); + EndpointPointer parseDestination(const QJsonValue& value); + EndpointPointer parseSource(const QJsonValue& value); + EndpointPointer parseAxis(const QJsonValue& value); + EndpointPointer parseAny(const QJsonValue& value); + EndpointPointer parseEndpoint(const QJsonValue& value); + ConditionalPointer parseConditional(const QJsonValue& value); + + static FilterPointer parseFilter(const QJsonValue& value); + static FilterList parseFilters(const QJsonValue& value); + + InputToEndpointMap _endpointsByInput; + EndpointToInputMap _inputsByEndpoint; + EndpointPairMap _compositeEndpoints; + + MappingNameMap _mappingsByName; + MappingDeviceMap _mappingsByDevice; + + RouteList _deviceRoutes; + RouteList _standardRoutes; + + using Locker = std::unique_lock; + + mutable std::recursive_mutex _lock; + }; + +} + +Q_DECLARE_METATYPE(controller::Input::NamedPair) +Q_DECLARE_METATYPE(controller::Pose) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(controller::Input) +Q_DECLARE_METATYPE(controller::Action) +Q_DECLARE_METATYPE(QVector) + +// Cheating. +using UserInputMapper = controller::UserInputMapper; + +#endif // hifi_UserInputMapper_h diff --git a/libraries/controllers/src/controllers/impl/Conditional.cpp b/libraries/controllers/src/controllers/impl/Conditional.cpp new file mode 100644 index 0000000000..00e42870e4 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Conditional.cpp @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 "Conditional.h" + +#include + +#include "Endpoint.h" + +namespace controller { + + Conditional::Pointer Conditional::parse(const QJsonValue& json) { + return Conditional::Pointer(); + } + +} diff --git a/libraries/controllers/src/controllers/impl/Conditional.h b/libraries/controllers/src/controllers/impl/Conditional.h new file mode 100644 index 0000000000..a216c8789f --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Conditional.h @@ -0,0 +1,54 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 +#ifndef hifi_Controllers_Conditional_h +#define hifi_Controllers_Conditional_h + +#include +#include + +#include + +#include + +class QJsonValue; + +namespace controller { + /* + * encapsulates a source, destination and filters to apply + */ + class Conditional { + public: + using Pointer = std::shared_ptr; + using List = std::list; + using Factory = hifi::SimpleFactory; + using Lambda = std::function; + + virtual bool satisfied() = 0; + virtual bool parseParameters(const QJsonValue& parameters) { return true; } + + static Pointer parse(const QJsonValue& json); + static void registerBuilder(const QString& name, Factory::Builder builder); + static Factory& getFactory() { return _factory; } + protected: + static Factory _factory; + }; + +} + +#define REGISTER_CONDITIONAL_CLASS(classEntry) \ + private: \ + using Registrar = Conditional::Factory::Registrar; \ + static Registrar _registrar; + +#define REGISTER_CONDITIONAL_CLASS_INSTANCE(classEntry, className) \ + classEntry::Registrar classEntry::_registrar(className, Conditional::getFactory()); + + +#endif diff --git a/libraries/controllers/src/controllers/impl/Endpoint.cpp b/libraries/controllers/src/controllers/impl/Endpoint.cpp new file mode 100644 index 0000000000..9e9b13f8ea --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Endpoint.cpp @@ -0,0 +1,14 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "Endpoint.h" + +namespace controller { + +} + diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h new file mode 100644 index 0000000000..5dd3f6adb4 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -0,0 +1,89 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Controllers_Endpoint_h +#define hifi_Controllers_Endpoint_h + +#include +#include +#include + +#include + +#include "../Input.h" +#include "../Pose.h" + +class QScriptValue; + +namespace controller { + /* + * Encapsulates a particular input / output, + * i.e. Hydra.Button0, Standard.X, Action.Yaw + */ + class Endpoint : public QObject { + Q_OBJECT; + public: + using Pointer = std::shared_ptr; + using List = std::list; + using Pair = std::pair; + using ReadLambda = std::function; + using WriteLambda = std::function; + + Endpoint(const Input& input) : _input(input) {} + virtual float value() = 0; + virtual void apply(float value, const Pointer& source) = 0; + virtual Pose pose() { return Pose(); } + virtual void apply(const Pose& value, const Pointer& source) {} + virtual const bool isPose() { return _input.isPose(); } + + virtual bool writeable() const { return true; } + virtual bool readable() const { return true; } + virtual void reset() { } + + const Input& getInput() { return _input; } + + protected: + Input _input; + }; + + class LambdaEndpoint : public Endpoint { + public: + LambdaEndpoint(ReadLambda readLambda, WriteLambda writeLambda = [](float) {}) + : Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { } + + virtual float value() override { return _readLambda(); } + virtual void apply(float value, const Pointer& source) override { _writeLambda(value); } + + private: + ReadLambda _readLambda; + WriteLambda _writeLambda; + }; + + + class VirtualEndpoint : public Endpoint { + public: + VirtualEndpoint(const Input& id = Input::INVALID_INPUT) + : Endpoint(id) { + } + + virtual float value() override { return _currentValue; } + virtual void apply(float value, const Pointer& source) override { _currentValue = value; } + + virtual Pose pose() override { return _currentPose; } + virtual void apply(const Pose& value, const Pointer& source) override { + _currentPose = value; + } + protected: + float _currentValue { 0.0f }; + Pose _currentPose {}; + }; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/Filter.cpp b/libraries/controllers/src/controllers/impl/Filter.cpp new file mode 100644 index 0000000000..09188318eb --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Filter.cpp @@ -0,0 +1,196 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "Filter.h" + +#include +#include + +#include +#include + +#include + +#include "filters/ClampFilter.h" +#include "filters/ConstrainToIntegerFilter.h" +#include "filters/ConstrainToPositiveIntegerFilter.h" +#include "filters/DeadZoneFilter.h" +#include "filters/HysteresisFilter.h" +#include "filters/InvertFilter.h" +#include "filters/PulseFilter.h" +#include "filters/ScaleFilter.h" + +using namespace controller; + +Filter::Factory Filter::_factory; + +REGISTER_FILTER_CLASS_INSTANCE(ClampFilter, "clamp") +REGISTER_FILTER_CLASS_INSTANCE(ConstrainToIntegerFilter, "constrainToInteger") +REGISTER_FILTER_CLASS_INSTANCE(ConstrainToPositiveIntegerFilter, "constrainToPositiveInteger") +REGISTER_FILTER_CLASS_INSTANCE(DeadZoneFilter, "deadZone") +REGISTER_FILTER_CLASS_INSTANCE(HysteresisFilter, "hysteresis") +REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert") +REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale") +REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse") + +const QString JSON_FILTER_TYPE = QStringLiteral("type"); +const QString JSON_FILTER_PARAMS = QStringLiteral("params"); + + +Filter::Pointer Filter::parse(const QJsonValue& json) { + Filter::Pointer filter; + if (json.isString()) { + filter = Filter::getFactory().create(json.toString()); + } else if (json.isObject()) { + QJsonObject jsonObj = json.toObject(); + // The filter is an object, now let s check for type and potential arguments + auto filterType = jsonObj[JSON_FILTER_TYPE]; + filter = Filter::getFactory().create(filterType.toString()); + if (filter) { + QJsonValue params = jsonObj; + if (jsonObj.contains(JSON_FILTER_PARAMS)) { + params = jsonObj[JSON_FILTER_PARAMS]; + } + if (!filter->parseParameters(params)) { + qWarning() << "Unable to parse filter parameters " << params; + return Filter::Pointer(); + } + } + } + return filter; +} + +bool Filter::parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output) { + if (parameters.isDouble()) { + output = parameters.toDouble(); + return true; + } else if (parameters.isArray()) { + auto arrayParameters = parameters.toArray(); + if (arrayParameters.size() > 1) { + output = arrayParameters[0].toDouble(); + return true; + } + } else if (parameters.isObject()) { + static const QString JSON_MIN = QStringLiteral("interval"); + auto objectParameters = parameters.toObject(); + if (objectParameters.contains(name)) { + output = objectParameters[name].toDouble(); + return true; + } + } + return false; +} + + + +#if 0 + +namespace controller { + + class LambdaFilter : public Filter { + public: + // LambdaFilter() {}12 + LambdaFilter(Lambda f) : _function(f) {}; + + virtual float apply(float value) const { + return _function(value); + } + + virtual bool parseParameters(const QJsonArray& parameters) { return true; } + + // REGISTER_FILTER_CLASS(LambdaFilter); + private: + Lambda _function; + }; + + class ScriptFilter : public Filter { + public: + + }; + + + + //class EasingFilter : public Filter { + //public: + // virtual float apply(float value) const override; + + //private: + // QEasingCurve _curve; + //}; + + //// GLSL style filters + //class StepFilter : public Filter { + //public: + // StepFilter(float edge) : _edge(edge) {}; + // virtual float apply(float value) const override; + + //private: + // const float _edge; + //}; + + //class PowFilter : public Filter { + //public: + // PowFilter(float exponent) : _exponent(exponent) {}; + // virtual float apply(float value) const override; + + //private: + // const float _exponent; + //}; + + //class AbsFilter : public Filter { + //public: + // virtual float apply(float value) const override; + //}; + + //class SignFilter : public Filter { + //public: + // virtual float apply(float value) const override; + //}; + + //class FloorFilter : public Filter { + //public: + // virtual float apply(float value) const override { + // return floor(newValue); + // } + //}; + + //class CeilFilter : public Filter { + //public: + // virtual float apply(float value) const override { + // return ceil(newValue); + // } + //}; + + //class FractFilter : public Filter { + //public: + // virtual float apply(float value) const override { + // return fract(newValue); + // } + //}; + + //class MinFilter : public Filter { + //public: + // MinFilter(float mine) : _min(min) {}; + + // virtual float apply(float value) const override { + // return glm::min(_min, newValue); + // } + + //private: + // const float _min; + //}; + + //class MaxFilter : public Filter { + //public: + // MaxFilter(float max) : _max(max) {}; + // virtual float apply(float newValue, float oldValue) override; + //private: + // const float _max; + //}; +} +#endif \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h new file mode 100644 index 0000000000..77585c8ebb --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Filter.h @@ -0,0 +1,59 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Controllers_Filter_h +#define hifi_Controllers_Filter_h + +#include +#include +#include +#include + +#include + +#include + +#include + +class QJsonValue; + +namespace controller { + + // Encapsulates part of a filter chain + class Filter { + public: + using Pointer = std::shared_ptr; + using List = std::list; + using Lambda = std::function; + using Factory = hifi::SimpleFactory; + + virtual float apply(float value) const = 0; + // Factory features + virtual bool parseParameters(const QJsonValue& parameters) { return true; } + + static Pointer parse(const QJsonValue& json); + static void registerBuilder(const QString& name, Factory::Builder builder); + static Factory& getFactory() { return _factory; } + + static bool parseSingleFloatParameter(const QJsonValue& parameters, const QString& name, float& output); + protected: + static Factory _factory; + }; +} + +#define REGISTER_FILTER_CLASS(classEntry) \ + private: \ + using Registrar = Filter::Factory::Registrar; \ + static Registrar _registrar; + +#define REGISTER_FILTER_CLASS_INSTANCE(classEntry, className) \ + classEntry::Registrar classEntry::_registrar(className, Filter::getFactory()); + + +#endif diff --git a/libraries/controllers/src/controllers/impl/Mapping.cpp b/libraries/controllers/src/controllers/impl/Mapping.cpp new file mode 100644 index 0000000000..68c43da393 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Mapping.cpp @@ -0,0 +1,8 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "Mapping.h" diff --git a/libraries/controllers/src/controllers/impl/Mapping.h b/libraries/controllers/src/controllers/impl/Mapping.h new file mode 100644 index 0000000000..99328f310b --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Mapping.h @@ -0,0 +1,37 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Controllers_Mapping_h +#define hifi_Controllers_Mapping_h + +#include +#include + +#include + +#include "Endpoint.h" +#include "Filter.h" +#include "Route.h" + +namespace controller { + + class Mapping { + public: + using Pointer = std::shared_ptr; + using List = Route::List; + + Mapping(const QString& name) : name(name) {} + + List routes; + QString name; + }; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp new file mode 100644 index 0000000000..1af3f271be --- /dev/null +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp @@ -0,0 +1,63 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "MappingBuilderProxy.h" + +#include +#include + +#include +#include + +#include "RouteBuilderProxy.h" +#include "../ScriptingInterface.h" +#include "../Logging.h" + +using namespace controller; + +QObject* MappingBuilderProxy::fromQml(const QJSValue& source) { + qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString(); + auto sourceEndpoint = _parent.endpointFor(source); + return from(sourceEndpoint); +} + +QObject* MappingBuilderProxy::from(const QScriptValue& source) { + qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString(); + auto sourceEndpoint = _parent.endpointFor(source); + return from(sourceEndpoint); +} + +QObject* MappingBuilderProxy::from(const Endpoint::Pointer& source) { + if (source) { + auto route = Route::Pointer(new Route()); + route->source = source; + return new RouteBuilderProxy(_parent, _mapping, route); + } else { + qCDebug(controllers) << "MappingBuilderProxy::from : source is null so no route created"; + return nullptr; + } +} + +QObject* MappingBuilderProxy::makeAxisQml(const QJSValue& source1, const QJSValue& source2) { + auto source1Endpoint = _parent.endpointFor(source1); + auto source2Endpoint = _parent.endpointFor(source2); + return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint)); +} + +QObject* MappingBuilderProxy::makeAxis(const QScriptValue& source1, const QScriptValue& source2) { + auto source1Endpoint = _parent.endpointFor(source1); + auto source2Endpoint = _parent.endpointFor(source2); + return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint)); +} + +QObject* MappingBuilderProxy::enable(bool enable) { + _parent.enableMapping(_mapping->name, enable); + return this; +} + + diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h new file mode 100644 index 0000000000..ac9a5a300d --- /dev/null +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -0,0 +1,58 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Controllers_Impl_MappingBuilderProxy_h +#define hifi_Controllers_Impl_MappingBuilderProxy_h + +#include +#include + +#include "Mapping.h" +#include "Endpoint.h" + +class QJSValue; +class QScriptValue; +class QJsonValue; + +namespace controller { + +class ScriptingInterface; +class UserInputMapper; + +// TODO migrate functionality to a MappingBuilder class and make the proxy defer to that +// (for easier use in both C++ and JS) +class MappingBuilderProxy : public QObject { + Q_OBJECT +public: + MappingBuilderProxy(UserInputMapper& parent, Mapping::Pointer mapping) + : _parent(parent), _mapping(mapping) { } + + Q_INVOKABLE QObject* fromQml(const QJSValue& source); + Q_INVOKABLE QObject* makeAxisQml(const QJSValue& source1, const QJSValue& source2); + + Q_INVOKABLE QObject* from(const QScriptValue& source); + Q_INVOKABLE QObject* makeAxis(const QScriptValue& source1, const QScriptValue& source2); + + Q_INVOKABLE QObject* enable(bool enable = true); + Q_INVOKABLE QObject* disable() { return enable(false); } + +protected: + QObject* from(const Endpoint::Pointer& source); + + friend class RouteBuilderProxy; + UserInputMapper& _parent; + Mapping::Pointer _mapping; + + + void parseRoute(const QJsonValue& json); + +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/Route.cpp b/libraries/controllers/src/controllers/impl/Route.cpp new file mode 100644 index 0000000000..56590e564a --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Route.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "Route.h" diff --git a/libraries/controllers/src/controllers/impl/Route.h b/libraries/controllers/src/controllers/impl/Route.h new file mode 100644 index 0000000000..5ad3d36628 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/Route.h @@ -0,0 +1,35 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Controllers_Route_h +#define hifi_Controllers_Route_h + +#include "Endpoint.h" +#include "Filter.h" +#include "Conditional.h" + +namespace controller { + /* + * encapsulates a source, destination and filters to apply + */ + class Route { + public: + Endpoint::Pointer source; + Endpoint::Pointer destination; + Conditional::Pointer conditional; + Filter::List filters; + QString json; + bool debug { false }; + + using Pointer = std::shared_ptr; + using List = std::list; + }; +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp new file mode 100644 index 0000000000..49e615439d --- /dev/null +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -0,0 +1,108 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 "RouteBuilderProxy.h" + +#include + +#include +#include + +#include + +#include "MappingBuilderProxy.h" +#include "../ScriptingInterface.h" +#include "../Logging.h" + +#include "filters/ClampFilter.h" +#include "filters/ConstrainToIntegerFilter.h" +#include "filters/ConstrainToPositiveIntegerFilter.h" +#include "filters/DeadZoneFilter.h" +#include "filters/HysteresisFilter.h" +#include "filters/InvertFilter.h" +#include "filters/PulseFilter.h" +#include "filters/ScaleFilter.h" + +using namespace controller; + +void RouteBuilderProxy::toQml(const QJSValue& destination) { + qCDebug(controllers) << "Completing route " << destination.toString(); + auto destinationEndpoint = _parent.endpointFor(destination); + return to(destinationEndpoint); +} + +void RouteBuilderProxy::to(const QScriptValue& destination) { + qCDebug(controllers) << "Completing route " << destination.toString(); + auto destinationEndpoint = _parent.endpointFor(destination); + return to(destinationEndpoint); +} + +void RouteBuilderProxy::to(const Endpoint::Pointer& destination) { + _route->destination = destination; + _mapping->routes.push_back(_route); + deleteLater(); +} + +QObject* RouteBuilderProxy::debug(bool enable) { + _route->debug = enable; + return this; +} + +QObject* RouteBuilderProxy::when(const QScriptValue& expression) { + _route->conditional = _parent.conditionalFor(expression); + return this; +} + +QObject* RouteBuilderProxy::whenQml(const QJSValue& expression) { + _route->conditional = _parent.conditionalFor(expression); + return this; +} + +QObject* RouteBuilderProxy::clamp(float min, float max) { + addFilter(std::make_shared(min, max)); + return this; +} + +QObject* RouteBuilderProxy::scale(float multiplier) { + addFilter(std::make_shared(multiplier)); + return this; +} + +QObject* RouteBuilderProxy::invert() { + addFilter(std::make_shared()); + return this; +} + +QObject* RouteBuilderProxy::hysteresis(float min, float max) { + addFilter(std::make_shared(min, max)); + return this; +} + +QObject* RouteBuilderProxy::deadZone(float min) { + addFilter(std::make_shared(min)); + return this; +} + +QObject* RouteBuilderProxy::constrainToInteger() { + addFilter(std::make_shared()); + return this; +} + +QObject* RouteBuilderProxy::constrainToPositiveInteger() { + addFilter(std::make_shared()); + return this; +} + +QObject* RouteBuilderProxy::pulse(float interval) { + addFilter(std::make_shared(interval)); + return this; +} + +void RouteBuilderProxy::addFilter(Filter::Pointer filter) { + _route->filters.push_back(filter); +} + diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h new file mode 100644 index 0000000000..d55aa80f6b --- /dev/null +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -0,0 +1,61 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Controllers_Impl_RouteBuilderProxy_h +#define hifi_Controllers_Impl_RouteBuilderProxy_h + +#include + +#include "Filter.h" +#include "Route.h" +#include "Mapping.h" + +#include "../UserInputMapper.h" + +class QJSValue; +class QScriptValue; +class QJsonValue; + +namespace controller { + +class ScriptingInterface; + +// TODO migrate functionality to a RouteBuilder class and make the proxy defer to that +// (for easier use in both C++ and JS) +class RouteBuilderProxy : public QObject { + Q_OBJECT + public: + RouteBuilderProxy(UserInputMapper& parent, Mapping::Pointer mapping, Route::Pointer route) + : _parent(parent), _mapping(mapping), _route(route) { } + + Q_INVOKABLE void toQml(const QJSValue& destination); + Q_INVOKABLE QObject* whenQml(const QJSValue& expression); + + Q_INVOKABLE void to(const QScriptValue& destination); + Q_INVOKABLE QObject* debug(bool enable = true); + Q_INVOKABLE QObject* when(const QScriptValue& expression); + Q_INVOKABLE QObject* clamp(float min, float max); + Q_INVOKABLE QObject* hysteresis(float min, float max); + Q_INVOKABLE QObject* pulse(float interval); + Q_INVOKABLE QObject* scale(float multiplier); + Q_INVOKABLE QObject* invert(); + Q_INVOKABLE QObject* deadZone(float min); + Q_INVOKABLE QObject* constrainToInteger(); + Q_INVOKABLE QObject* constrainToPositiveInteger(); + +private: + void to(const Endpoint::Pointer& destination); + void conditional(const Conditional::Pointer& conditional); + void addFilter(Filter::Pointer filter); + UserInputMapper& _parent; + Mapping::Pointer _mapping; + Route::Pointer _route; + }; + +} +#endif diff --git a/libraries/controllers/src/controllers/impl/conditionals/AndConditional.cpp b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.cpp new file mode 100644 index 0000000000..772d4f1314 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.cpp @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "AndConditional.h" + +using namespace controller; + +bool AndConditional::satisfied() { + for (auto& conditional : _children) { + if (!conditional->satisfied()) { + return false; + } + } + return true; +} + diff --git a/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h new file mode 100644 index 0000000000..c60e4b15df --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h @@ -0,0 +1,31 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 +#ifndef hifi_Controllers_AndConditional_h +#define hifi_Controllers_AndConditional_h + +#include "../Conditional.h" + +namespace controller { + +class AndConditional : public Conditional { +public: + using Pointer = std::shared_ptr; + + AndConditional(Conditional::List children) : _children(children) { } + + virtual bool satisfied() override; + +private: + Conditional::List _children; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp new file mode 100644 index 0000000000..03e16b8cf9 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "EndpointConditional.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h new file mode 100644 index 0000000000..1e4205afc7 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h @@ -0,0 +1,27 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 +#ifndef hifi_Controllers_EndpointConditional_h +#define hifi_Controllers_EndpointConditional_h + +#include "../Conditional.h" +#include "../Endpoint.h" + +namespace controller { + +class EndpointConditional : public Conditional { +public: + EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {} + virtual bool satisfied() override { return _endpoint && _endpoint->value() != 0.0; } +private: + Endpoint::Pointer _endpoint; +}; + +} +#endif diff --git a/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp new file mode 100644 index 0000000000..0c8d602b9e --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "NotConditional.h" diff --git a/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h new file mode 100644 index 0000000000..3acda07106 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 +#ifndef hifi_Controllers_NotConditional_h +#define hifi_Controllers_NotConditional_h + +#include "../Conditional.h" + + +#endif diff --git a/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.cpp b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.cpp new file mode 100644 index 0000000000..277b63ed11 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.cpp @@ -0,0 +1,27 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "ScriptConditional.h" + +#include + +using namespace controller; + +bool ScriptConditional::satisfied() { + updateValue(); + return _lastValue; +} + +void ScriptConditional::updateValue() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateValue", Qt::QueuedConnection); + return; + } + + _lastValue = _callable.call().toBool(); +} diff --git a/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h new file mode 100644 index 0000000000..800692d02c --- /dev/null +++ b/libraries/controllers/src/controllers/impl/conditionals/ScriptConditional.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// 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 +#ifndef hifi_Controllers_ScriptConditional_h +#define hifi_Controllers_ScriptConditional_h + +#include + +#include + +#include "../Conditional.h" + +namespace controller { + +class ScriptConditional : public QObject, public Conditional { + Q_OBJECT; +public: + ScriptConditional(const QScriptValue& callable) : _callable(callable) { } + virtual bool satisfied() override; +protected: + Q_INVOKABLE void updateValue(); +private: + QScriptValue _callable; + bool _lastValue { false }; +}; + +} +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp new file mode 100644 index 0000000000..b671d8e93c --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "ActionEndpoint.h" + +#include + +#include "../../UserInputMapper.h" + +using namespace controller; + +void ActionEndpoint::apply(float newValue, const Pointer& source) { + _currentValue += newValue; + if (_input != Input::INVALID_INPUT) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); + } +} + +void ActionEndpoint::apply(const Pose& value, const Pointer& source) { + _currentPose = value; + if (!_currentPose.isValid()) { + return; + } + if (_input != Input::INVALID_INPUT) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->setActionState(Action(_input.getChannel()), _currentPose); + } +} + +void ActionEndpoint::reset() { + _currentValue = 0.0f; + _currentPose = Pose(); +} + diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h new file mode 100644 index 0000000000..574fdcedb5 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h @@ -0,0 +1,41 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_ActionEndpoint_h +#define hifi_Controllers_ActionEndpoint_h + +#include "../Endpoint.h" + +#include "../../Actions.h" +#include + +#include "../../UserInputMapper.h" + +namespace controller { + +class ActionEndpoint : public Endpoint { +public: + ActionEndpoint(const Input& id = Input::INVALID_INPUT) : Endpoint(id) { } + + virtual float value() override { return _currentValue; } + virtual void apply(float newValue, const Pointer& source) override; + + virtual Pose pose() override { return _currentPose; } + virtual void apply(const Pose& value, const Pointer& source) override; + + virtual void reset() override; + +private: + float _currentValue{ 0.0f }; + Pose _currentPose{}; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp new file mode 100644 index 0000000000..24f3479ea8 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp @@ -0,0 +1,63 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "AnyEndpoint.h" + +#include "../../UserInputMapper.h" + +using namespace controller; + +AnyEndpoint::AnyEndpoint(Endpoint::List children) : Endpoint(Input::INVALID_INPUT), _children(children) { + bool standard = true; + // Ensure if we're building a composite of standard devices the composite itself + // is treated as a standard device for rule processing order + for (auto endpoint : children) { + if (endpoint->getInput().device != UserInputMapper::STANDARD_DEVICE) { + standard = false; + break; + } + } + if (standard) { + this->_input.device = UserInputMapper::STANDARD_DEVICE; + } +} + +float AnyEndpoint::value() { + float result = 0; + for (auto& child : _children) { + float childResult = child->value(); + if (childResult != 0.0f) { + result = childResult; + } + } + return result; +} + +void AnyEndpoint::apply(float newValue, const Endpoint::Pointer& source) { + qFatal("AnyEndpoint is read only"); +} + +// AnyEndpoint is used for reading, so return false if any child returns false (has been written to) +bool AnyEndpoint::writeable() const { + for (auto& child : _children) { + if (!child->writeable()) { + return false; + } + } + return true; +} + +bool AnyEndpoint::readable() const { + for (auto& child : _children) { + if (!child->readable()) { + return false; + } + } + return true; +} + diff --git a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h new file mode 100644 index 0000000000..86dd057414 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_AnyEndpoint_h +#define hifi_Controllers_AnyEndpoint_h + +#include "../Endpoint.h" + +namespace controller { + +class AnyEndpoint : public Endpoint { + friend class UserInputMapper; +public: + AnyEndpoint(Endpoint::List children); + virtual float value() override; + virtual void apply(float newValue, const Endpoint::Pointer& source) override; + virtual bool writeable() const override; + virtual bool readable() const override; + +private: + Endpoint::List _children; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp new file mode 100644 index 0000000000..c083a7147d --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "ArrayEndpoint.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h new file mode 100644 index 0000000000..79bb604ec0 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h @@ -0,0 +1,43 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_ArrayEndpoint_h +#define hifi_Controllers_ArrayEndpoint_h + +#include "../Endpoint.h" + +namespace controller { + +class ArrayEndpoint : public Endpoint { + friend class UserInputMapper; +public: + using Pointer = std::shared_ptr; + ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { } + + virtual float value() override { + return 0.0; + } + + virtual void apply(float value, const Endpoint::Pointer& source) override { + for (auto& child : _children) { + if (child->writeable()) { + child->apply(value, source); + } + } + } + + virtual bool readable() const override { return false; } + +private: + Endpoint::List _children; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp new file mode 100644 index 0000000000..913bf0136b --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp @@ -0,0 +1,36 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "CompositeEndpoint.h" + +#include "../../UserInputMapper.h" + +namespace controller { + +CompositeEndpoint::CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second) + : Endpoint(Input::INVALID_INPUT), Pair(first, second) { + if (first->getInput().device == UserInputMapper::STANDARD_DEVICE && + second->getInput().device == UserInputMapper::STANDARD_DEVICE) { + this->_input.device = UserInputMapper::STANDARD_DEVICE; + } +} + +bool CompositeEndpoint::readable() const { + return first->readable() && second->readable(); +} + +float CompositeEndpoint::value() { + float result = first->value() * -1.0f + second->value(); + return result; +} + +void CompositeEndpoint::apply(float newValue, const Pointer& source) { + // Composites are read only +} + +} diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h new file mode 100644 index 0000000000..c6ec90b7c8 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h @@ -0,0 +1,28 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_CompositeEndpoint_h +#define hifi_Controllers_CompositeEndpoint_h + +#include "../Endpoint.h" + +namespace controller { + class CompositeEndpoint : public Endpoint, Endpoint::Pair { + public: + CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second); + + virtual float value() override; + virtual void apply(float newValue, const Pointer& source) override; + virtual bool readable() const override; + + }; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp new file mode 100644 index 0000000000..bb1f6df191 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp @@ -0,0 +1,41 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "InputEndpoint.h" + +#include + +#include "../../UserInputMapper.h" + +using namespace controller; +float InputEndpoint::value(){ + _read = true; + if (isPose()) { + return pose().valid ? 1.0f : 0.0f; + } + auto userInputMapper = DependencyManager::get(); + auto deviceProxy = userInputMapper->getDevice(_input); + if (!deviceProxy) { + return 0.0f; + } + return deviceProxy->getValue(_input); +} + +Pose InputEndpoint::pose() { + _read = true; + if (!isPose()) { + return Pose(); + } + auto userInputMapper = DependencyManager::get(); + auto deviceProxy = userInputMapper->getDevice(_input); + if (!deviceProxy) { + return Pose(); + } + return deviceProxy->getPose(_input.channel); +} + diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h new file mode 100644 index 0000000000..d58f0c2e73 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h @@ -0,0 +1,39 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_InputEndpoint_h +#define hifi_Controllers_InputEndpoint_h + +#include "../Endpoint.h" + +namespace controller { + +class InputEndpoint : public Endpoint { +public: + InputEndpoint(const Input& id = Input::INVALID_INPUT) + : Endpoint(id) { + } + + virtual float value() override; + // FIXME need support for writing back to vibration / force feedback effects + virtual void apply(float newValue, const Pointer& source) override {} + virtual Pose pose() override; + virtual void apply(const Pose& value, const Pointer& source) override { } + + virtual bool writeable() const { return false; } + virtual bool readable() const { return !_read; } + virtual void reset() { _read = false; } + +private: + bool _read { false }; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp new file mode 100644 index 0000000000..4560741d12 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "JSEndpoint.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h new file mode 100644 index 0000000000..27f17b5cd3 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h @@ -0,0 +1,41 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_JSEndpoint_h +#define hifi_Controllers_JSEndpoint_h + +#include "../Endpoint.h" + +#include +#include + +namespace controller { + +class JSEndpoint : public Endpoint { +public: + JSEndpoint(const QJSValue& callable) + : Endpoint(Input::INVALID_INPUT), _callable(callable) { + } + + virtual float value() { + float result = (float)_callable.call().toNumber(); + return result; + } + + virtual void apply(float newValue, const Pointer& source) { + _callable.call(QJSValueList({ QJSValue(newValue) })); + } + +private: + QJSValue _callable; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp new file mode 100644 index 0000000000..d9b4a5fc59 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -0,0 +1,46 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "ScriptEndpoint.h" + +#include + +using namespace controller; + +float ScriptEndpoint::value() { + updateValue(); + return _lastValueRead; +} + +void ScriptEndpoint::updateValue() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateValue", Qt::QueuedConnection); + return; + } + + _lastValueRead = (float)_callable.call().toNumber(); +} + +void ScriptEndpoint::apply(float value, const Pointer& source) { + if (value == _lastValueWritten) { + return; + } + internalApply(value, source->getInput().getID()); +} + +void ScriptEndpoint::internalApply(float value, int sourceID) { + _lastValueWritten = value; + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection, + Q_ARG(float, value), + Q_ARG(int, sourceID)); + return; + } + _callable.call(QScriptValue(), + QScriptValueList({ QScriptValue(value), QScriptValue(sourceID) })); +} diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h new file mode 100644 index 0000000000..23f77892c6 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h @@ -0,0 +1,40 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_ScriptEndpoint_h +#define hifi_Controllers_ScriptEndpoint_h + +#include + +#include "../Endpoint.h" + +namespace controller { + +class ScriptEndpoint : public Endpoint { + Q_OBJECT; +public: + ScriptEndpoint(const QScriptValue& callable) + : Endpoint(Input::INVALID_INPUT), _callable(callable) { + } + + virtual float value(); + virtual void apply(float newValue, const Pointer& source); + +protected: + Q_INVOKABLE void updateValue(); + Q_INVOKABLE virtual void internalApply(float newValue, int sourceID); +private: + QScriptValue _callable; + float _lastValueRead { 0.0f }; + float _lastValueWritten { 0.0f }; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp new file mode 100644 index 0000000000..09920d249c --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 "StandardEndpoint.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h new file mode 100644 index 0000000000..74adaf825d --- /dev/null +++ b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h @@ -0,0 +1,60 @@ +// +// Created by Bradley Austin Davis 2015/10/23 +// 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 +#ifndef hifi_Controllers_StandardEndpoint_h +#define hifi_Controllers_StandardEndpoint_h + +#include "../Endpoint.h" + +namespace controller { + +class StandardEndpoint : public VirtualEndpoint { +public: + StandardEndpoint(const Input& input) : VirtualEndpoint(input) {} + virtual bool writeable() const override { return !_written; } + virtual bool readable() const override { return !_read; } + virtual void reset() override { + apply(0.0f, Endpoint::Pointer()); + apply(Pose(), Endpoint::Pointer()); + _written = _read = false; + } + + virtual float value() override { + _read = true; + return VirtualEndpoint::value(); + } + + virtual void apply(float value, const Pointer& source) override { + // For standard endpoints, the first NON-ZERO write counts. + if (value != 0.0) { + _written = true; + } + VirtualEndpoint::apply(value, source); + } + + virtual Pose pose() override { + _read = true; + return VirtualEndpoint::pose(); + } + + virtual void apply(const Pose& value, const Pointer& source) override { + if (value != Pose()) { + _written = true; + } + VirtualEndpoint::apply(value, source); + } + +private: + bool _written { false }; + bool _read { false }; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ClampFilter.cpp new file mode 100644 index 0000000000..ec22981ef3 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.cpp @@ -0,0 +1,38 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "ClampFilter.h" + +#include +#include + +using namespace controller; + +bool ClampFilter::parseParameters(const QJsonValue& parameters) { + if (parameters.isArray()) { + auto arrayParameters = parameters.toArray(); + if (arrayParameters.size() > 1) { + _min = arrayParameters[0].toDouble(); + } + if (arrayParameters.size() > 2) { + _max = arrayParameters[1].toDouble(); + } + } else if (parameters.isObject()) { + static const QString JSON_MAX = QStringLiteral("max"); + static const QString JSON_MIN = QStringLiteral("min"); + + auto objectParameters = parameters.toObject(); + if (objectParameters.contains(JSON_MIN)) { + _min = objectParameters[JSON_MIN].toDouble(); + } + if (objectParameters.contains(JSON_MAX)) { + _max = objectParameters[JSON_MAX].toDouble(); + } + } + return true; +} diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h new file mode 100644 index 0000000000..fd82821b3e --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_Clamp_h +#define hifi_Controllers_Filters_Clamp_h + +#include "../Filter.h" + +namespace controller { + +class ClampFilter : public Filter { + REGISTER_FILTER_CLASS(ClampFilter); +public: + ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {}; + virtual float apply(float value) const override { + return glm::clamp(value, _min, _max); + } + virtual bool parseParameters(const QJsonValue& parameters) override; +protected: + float _min = 0.0f; + float _max = 1.0f; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp new file mode 100644 index 0000000000..78ffb47693 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "ConstrainToIntegerFilter.h" diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h new file mode 100644 index 0000000000..580dc2a856 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_ConstrainToIntegerFilter_h +#define hifi_Controllers_Filters_ConstrainToIntegerFilter_h + +#include "../Filter.h" + +namespace controller { + +class ConstrainToIntegerFilter : public Filter { + REGISTER_FILTER_CLASS(ConstrainToIntegerFilter); +public: + ConstrainToIntegerFilter() {}; + + virtual float apply(float value) const override { + return glm::sign(value); + } +protected: +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp new file mode 100644 index 0000000000..d78942b18f --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "ConstrainToPositiveIntegerFilter.h" diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h new file mode 100644 index 0000000000..27395cde24 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_ConstrainToPositiveInteger_h +#define hifi_Controllers_Filters_ConstrainToPositiveInteger_h + +#include "../Filter.h" + +namespace controller { + +class ConstrainToPositiveIntegerFilter : public Filter { + REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter); +public: + ConstrainToPositiveIntegerFilter() {}; + + virtual float apply(float value) const override { + return (value <= 0.0f) ? 0.0f : 1.0f; + } +protected: +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp new file mode 100644 index 0000000000..809308eeab --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp @@ -0,0 +1,26 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "DeadZoneFilter.h" + +#include +#include + +using namespace controller; +float DeadZoneFilter::apply(float value) const { + float scale = 1.0f / (1.0f - _min); + if (std::abs(value) < _min) { + return 0.0f; + } + return (value - _min) * scale; +} + +bool DeadZoneFilter::parseParameters(const QJsonValue& parameters) { + static const QString JSON_MIN = QStringLiteral("min"); + return parseSingleFloatParameter(parameters, JSON_MIN, _min); +} diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h new file mode 100644 index 0000000000..70ac657415 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h @@ -0,0 +1,31 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_DeadZoneFilter_h +#define hifi_Controllers_Filters_DeadZoneFilter_h + +#include "../Filter.h" + +namespace controller { + +class DeadZoneFilter : public Filter { + REGISTER_FILTER_CLASS(DeadZoneFilter); +public: + DeadZoneFilter(float min = 0.0) : _min(min) {}; + + virtual float apply(float value) const override; + virtual bool parseParameters(const QJsonValue& parameters) override; +protected: + float _min = 0.0f; +}; + + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp new file mode 100644 index 0000000000..a7f22e1de4 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp @@ -0,0 +1,64 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "HysteresisFilter.h" + +#include +#include + +using namespace controller; + +HysteresisFilter::HysteresisFilter(float min, float max) : _min(min), _max(max) { + if (_min > _max) { + std::swap(_min, _max); + } +}; + + +float HysteresisFilter::apply(float value) const { + if (_signaled) { + if (value <= _min) { + _signaled = false; + } + } else { + if (value >= _max) { + _signaled = true; + } + } + return _signaled ? 1.0f : 0.0f; +} + +bool HysteresisFilter::parseParameters(const QJsonValue& parameters) { + if (parameters.isArray()) { + auto arrayParameters = parameters.toArray(); + if (arrayParameters.size() > 1) { + _min = arrayParameters[0].toDouble(); + } + if (arrayParameters.size() > 2) { + _max = arrayParameters[1].toDouble(); + } + } else if (parameters.isObject()) { + static const QString JSON_MAX = QStringLiteral("max"); + static const QString JSON_MIN = QStringLiteral("min"); + + auto objectParameters = parameters.toObject(); + if (objectParameters.contains(JSON_MIN)) { + _min = objectParameters[JSON_MIN].toDouble(); + } + if (objectParameters.contains(JSON_MAX)) { + _max = objectParameters[JSON_MAX].toDouble(); + } + } else { + return false; + } + + if (_min > _max) { + std::swap(_min, _max); + } + return true; +} diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h new file mode 100644 index 0000000000..4f7e07928d --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h @@ -0,0 +1,31 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_Hysteresis_h +#define hifi_Controllers_Filters_Hysteresis_h + +#include "../Filter.h" + +namespace controller { + +class HysteresisFilter : public Filter { + REGISTER_FILTER_CLASS(HysteresisFilter); +public: + HysteresisFilter(float min = 0.25, float max = 0.75); + virtual float apply(float value) const override; + virtual bool parseParameters(const QJsonValue& parameters) override; +protected: + float _min; + float _max; + mutable bool _signaled { false }; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp b/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp new file mode 100644 index 0000000000..db582b84cc --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp @@ -0,0 +1,9 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "InvertFilter.h" diff --git a/libraries/controllers/src/controllers/impl/filters/InvertFilter.h b/libraries/controllers/src/controllers/impl/filters/InvertFilter.h new file mode 100644 index 0000000000..889cd0140c --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/InvertFilter.h @@ -0,0 +1,29 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_InvertFilter_h +#define hifi_Controllers_Filters_InvertFilter_h + +#include "ScaleFilter.h" + +namespace controller { + +class InvertFilter : public ScaleFilter { + REGISTER_FILTER_CLASS(InvertFilter); +public: + InvertFilter() : ScaleFilter(-1.0f) {} + + virtual bool parseParameters(const QJsonArray& parameters) { return true; } + +private: +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp new file mode 100644 index 0000000000..f4e1f04791 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp @@ -0,0 +1,37 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "PulseFilter.h" + +#include +#include + +using namespace controller; + + + +float PulseFilter::apply(float value) const { + float result = 0.0f; + + if (0.0f != value) { + float now = secTimestampNow(); + float delta = now - _lastEmitTime; + if (delta >= _interval) { + _lastEmitTime = now; + result = value; + } + } + + return result; +} + +bool PulseFilter::parseParameters(const QJsonValue& parameters) { + static const QString JSON_MIN = QStringLiteral("interval"); + return parseSingleFloatParameter(parameters, JSON_MIN, _interval); +} + diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h new file mode 100644 index 0000000000..2512b479cf --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h @@ -0,0 +1,36 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_Pulse_h +#define hifi_Controllers_Filters_Pulse_h + +#include "../Filter.h" + +namespace controller { + + +class PulseFilter : public Filter { + REGISTER_FILTER_CLASS(PulseFilter); +public: + PulseFilter() {} + PulseFilter(float interval) : _interval(interval) {} + + + virtual float apply(float value) const override; + + virtual bool parseParameters(const QJsonValue& parameters); + +private: + mutable float _lastEmitTime { -::std::numeric_limits::max() }; + float _interval = 1.0f; +}; + +} + +#endif diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.cpp new file mode 100644 index 0000000000..4a310e3a04 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.cpp @@ -0,0 +1,19 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 "ScaleFilter.h" + +#include +#include + +using namespace controller; + +bool ScaleFilter::parseParameters(const QJsonValue& parameters) { + static const QString JSON_SCALE = QStringLiteral("scale"); + return parseSingleFloatParameter(parameters, JSON_SCALE, _scale); +} diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h new file mode 100644 index 0000000000..39c5edd4e5 --- /dev/null +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h @@ -0,0 +1,34 @@ +// +// Created by Bradley Austin Davis 2015/10/25 +// 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 +#ifndef hifi_Controllers_Filters_Scale_h +#define hifi_Controllers_Filters_Scale_h + +#include "../Filter.h" + +namespace controller { + +class ScaleFilter : public Filter { + REGISTER_FILTER_CLASS(ScaleFilter); +public: + ScaleFilter() {} + ScaleFilter(float scale) : _scale(scale) {} + + virtual float apply(float value) const override { + return value * _scale; + } + virtual bool parseParameters(const QJsonValue& parameters); + +private: + float _scale = 1.0f; +}; + +} + +#endif diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index 1f8242f081..914f80d983 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -27,7 +27,4 @@ void NullDisplayPlugin::preRender() {} void NullDisplayPlugin::preDisplay() {} void NullDisplayPlugin::display(GLuint sceneTexture, const glm::uvec2& sceneSize) {} void NullDisplayPlugin::finishFrame() {} - -void NullDisplayPlugin::activate() {} -void NullDisplayPlugin::deactivate() {} void NullDisplayPlugin::stop() {} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index bb1ab2d97f..4f2cc77b8f 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -15,8 +15,6 @@ public: virtual ~NullDisplayPlugin() final {} virtual const QString & getName() const override; - void activate() override; - void deactivate() override; void stop() override; virtual glm::uvec2 getRecommendedRenderSize() const override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 6f6117229a..3791375c9e 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -57,12 +57,12 @@ void OpenGLDisplayPlugin::customizeContext() { } void OpenGLDisplayPlugin::activate() { - _active = true; + DisplayPlugin::activate(); _timer.start(1); } void OpenGLDisplayPlugin::stop() { - _active = false; + DisplayPlugin::activate(); _timer.stop(); } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 44ab656f31..43d8e5af6b 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -44,7 +44,6 @@ protected: mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; - bool _active{ false }; bool _vsyncSupported{ false }; }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 259a82e4eb..8c81ceaeb8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -107,8 +107,7 @@ void EntityTreeRenderer::init() { entityTree->setFBXService(this); if (_wantScripts) { - _entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities", - _scriptingServices->getControllerScriptingInterface()); + _entitiesScriptEngine = new ScriptEngine(NO_SCRIPT, "Entities"); _scriptingServices->registerScriptEngineWithApplicationServices(_entitiesScriptEngine); _entitiesScriptEngine->runInThread(); DependencyManager::get()->setEntitiesScriptEngine(_entitiesScriptEngine); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 51a0c34c76..65060c8d45 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -189,7 +189,7 @@ enum EntityPropertyList { PROP_BACKGROUND_MODE = PROP_MODEL_URL, PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, - PROP_KEYLIGHT_AMBIENT_URL = PROP_ANIMATION_FRAME_INDEX, + PROP_KEYLIGHT_AMBIENT_URL = PROP_ANIMATION_PLAYING, // Aliases/Piggyback properties for Web. These properties intentionally reuse the enum values for // other properties which will never overlap with each other. diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 3afd0c84a8..b6603deb62 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include "GLEscrow.h" @@ -611,3 +612,8 @@ QQuickWindow* OffscreenQmlSurface::getWindow() { QSize OffscreenQmlSurface::size() const { return _renderer->_quickWindow->geometry().size(); } + +QQmlContext* OffscreenQmlSurface::getRootContext() { + return _qmlEngine->rootContext(); +} + diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 01dd2b88f9..67315b0783 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -60,6 +60,7 @@ public: QQuickItem* getRootItem(); QQuickWindow* getWindow(); QObject* getEventHandler(); + QQmlContext* getRootContext(); QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget); virtual bool eventFilter(QObject* originalDestination, QEvent* event); diff --git a/libraries/input-plugins/CMakeLists.txt b/libraries/input-plugins/CMakeLists.txt index c26e14e756..0e21d4a40c 100644 --- a/libraries/input-plugins/CMakeLists.txt +++ b/libraries/input-plugins/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME input-plugins) setup_hifi_library() -link_hifi_libraries(shared plugins gpu render-utils) +link_hifi_libraries(shared plugins controllers script-engine render-utils) GroupSources("src/input-plugins") diff --git a/libraries/input-plugins/src/input-plugins/InputDevice.cpp b/libraries/input-plugins/src/input-plugins/InputDevice.cpp deleted file mode 100644 index 351d5b6d1d..0000000000 --- a/libraries/input-plugins/src/input-plugins/InputDevice.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// -// 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/Joystick.cpp b/libraries/input-plugins/src/input-plugins/Joystick.cpp index d0e2705e98..b7d69b9406 100644 --- a/libraries/input-plugins/src/input-plugins/Joystick.cpp +++ b/libraries/input-plugins/src/input-plugins/Joystick.cpp @@ -9,19 +9,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include - -#include - #include "Joystick.h" +#include + const float CONTROLLER_THRESHOLD = 0.3f; #ifdef HAVE_SDL2 const float MAX_AXIS = 32768.0f; -Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) : - InputDevice(name), +Joystick::Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameController) : + InputDevice("GamePad"), _sdlGameController(sdlGameController), _sdlJoystick(SDL_GameControllerGetJoystick(_sdlGameController)), _instanceId(instanceId) @@ -55,39 +53,14 @@ void Joystick::focusOutEvent() { }; #ifdef HAVE_SDL2 + void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis) event.axis; - - switch (axis) { - case SDL_CONTROLLER_AXIS_LEFTX: - _axisStateMap[makeInput(LEFT_AXIS_X_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(LEFT_AXIS_X_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_LEFTY: - _axisStateMap[makeInput(LEFT_AXIS_Y_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(LEFT_AXIS_Y_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_RIGHTX: - _axisStateMap[makeInput(RIGHT_AXIS_X_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(RIGHT_AXIS_X_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_RIGHTY: - _axisStateMap[makeInput(RIGHT_AXIS_Y_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(RIGHT_AXIS_Y_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - _axisStateMap[makeInput(RIGHT_SHOULDER).getChannel()] = event.value / MAX_AXIS; - break; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - _axisStateMap[makeInput(LEFT_SHOULDER).getChannel()] = event.value / MAX_AXIS; - break; - default: - break; - } + _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()] = (float)event.value / MAX_AXIS; } void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { - auto input = makeInput((SDL_GameControllerButton) event.button); + auto input = makeInput((controller::StandardButtonChannel)event.button); bool newValue = event.state == SDL_PRESSED; if (newValue) { _buttonPressedMap.insert(input.getChannel()); @@ -95,131 +68,61 @@ void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { _buttonPressedMap.erase(input.getChannel()); } } -#endif - - -void Joystick::registerToUserInputMapper(UserInputMapper& mapper) { - // Grab the current free device ID - _deviceID = mapper.getFreeDeviceID(); - - 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 { - QVector availableInputs; -#ifdef HAVE_SDL2 - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_A), "Bottom Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_B), "Right Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_X), "Left Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_Y), "Top Button")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_UP), "DPad Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_DOWN), "DPad Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_LEFT), "DPad Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_RIGHT), "DPad Right")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_LEFTSHOULDER), "L1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), "R1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_SHOULDER), "L2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_SHOULDER), "R2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_NEG), "Left Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_POS), "Left Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_POS), "Left Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_NEG), "Left Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_NEG), "Right Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_POS), "Right Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_POS), "Right Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_NEG), "Right Stick Left")); #endif - return availableInputs; + +controller::Input::NamedVector Joystick::getAvailableInputs() const { + using namespace controller; + static const Input::NamedVector availableInputs{ + makePair(A, "A"), + makePair(B, "B"), + makePair(X, "X"), + makePair(Y, "Y"), + // DPad + makePair(DU, "DU"), + makePair(DD, "DD"), + makePair(DL, "DL"), + makePair(DR, "DR"), + // Bumpers + makePair(LB, "LB"), + makePair(RB, "RB"), + // Stick press + makePair(LS, "LS"), + makePair(RS, "RS"), + // Center buttons + makePair(START, "Start"), + makePair(BACK, "Back"), + // Analog sticks + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), + + // Triggers + makePair(LT, "LT"), + makePair(RT, "RT"), + + // Aliases, PlayStation style names + makePair(LB, "L1"), + makePair(RB, "R1"), + makePair(LT, "L2"), + makePair(RT, "R2"), + makePair(LS, "L3"), + makePair(RS, "R3"), + makePair(BACK, "Select"), + makePair(A, "Cross"), + makePair(B, "Circle"), + makePair(X, "Square"), + makePair(Y, "Triangle"), + makePair(DU, "Up"), + makePair(DD, "Down"), + makePair(DL, "Left"), + makePair(DR, "Right"), }; - proxy->resetDeviceBindings = [this, &mapper] () -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; - mapper.registerDevice(_deviceID, proxy); + return availableInputs; } -void Joystick::assignDefaultInputMapping(UserInputMapper& mapper) { -#ifdef HAVE_SDL2 - 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) - // Left Joystick: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), JOYSTICK_MOVE_SPEED); - - // Right Joystick: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), JOYSTICK_PITCH_SPEED); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_UP), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_DOWN), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_RIGHT), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_LEFT), DPAD_MOVE_SPEED); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(SDL_CONTROLLER_BUTTON_Y), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(SDL_CONTROLLER_BUTTON_X), DPAD_MOVE_SPEED); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), BOOM_SPEED); - - - // Hold front right shoulder button for precision controls - // Left Joystick: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - - // Right Joystick: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_UP), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_DOWN), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_RIGHT), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_LEFT), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(SDL_CONTROLLER_BUTTON_Y), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(SDL_CONTROLLER_BUTTON_X), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(SDL_CONTROLLER_BUTTON_LEFTSHOULDER)); - - mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(SDL_CONTROLLER_BUTTON_B)); - mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(SDL_CONTROLLER_BUTTON_A)); -#endif +QString Joystick::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/xbox.json"; + return MAPPING_JSON; } - -#ifdef HAVE_SDL2 -UserInputMapper::Input Joystick::makeInput(SDL_GameControllerButton button) { - return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); -} -#endif - -UserInputMapper::Input Joystick::makeInput(Joystick::JoystickAxisChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); -} - diff --git a/libraries/input-plugins/src/input-plugins/Joystick.h b/libraries/input-plugins/src/input-plugins/Joystick.h index 2ba89da052..fa50f8eab6 100644 --- a/libraries/input-plugins/src/input-plugins/Joystick.h +++ b/libraries/input-plugins/src/input-plugins/Joystick.h @@ -20,9 +20,10 @@ #undef main #endif -#include "InputDevice.h" +#include +#include -class Joystick : public QObject, public InputDevice { +class Joystick : public QObject, public controller::InputDevice { Q_OBJECT Q_PROPERTY(QString name READ getName) @@ -31,37 +32,21 @@ class Joystick : public QObject, public InputDevice { #endif public: - enum JoystickAxisChannel { - LEFT_AXIS_X_POS = 0, - LEFT_AXIS_X_NEG, - LEFT_AXIS_Y_POS, - LEFT_AXIS_Y_NEG, - RIGHT_AXIS_X_POS, - RIGHT_AXIS_X_NEG, - RIGHT_AXIS_Y_POS, - RIGHT_AXIS_Y_NEG, - RIGHT_SHOULDER, - LEFT_SHOULDER, - }; + using Pointer = std::shared_ptr; const QString& getName() const { return _name; } // Device functions - virtual void registerToUserInputMapper(UserInputMapper& mapper) override; - virtual void assignDefaultInputMapping(UserInputMapper& mapper) override; + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; - Joystick() : InputDevice("Joystick") {} + Joystick() : InputDevice("GamePad") {} ~Joystick(); #ifdef HAVE_SDL2 - UserInputMapper::Input makeInput(SDL_GameControllerButton button); -#endif - UserInputMapper::Input makeInput(Joystick::JoystickAxisChannel axis); - -#ifdef HAVE_SDL2 - Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController); + Joystick(SDL_JoystickID instanceId, SDL_GameController* sdlGameController); #endif void closeJoystick(); diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 36ae643a8e..a0481dfaa0 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -10,6 +10,13 @@ // #include "KeyboardMouseDevice.h" +#include +#include +#include + +#include +#include + const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; void KeyboardMouseDevice::update(float deltaTime, bool jointsCaptured) { @@ -81,7 +88,7 @@ void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); } -glm::vec2 KeyboardMouseDevice::evalAverageTouchPoints(const QList& points) const { +glm::vec2 evalAverageTouchPoints(const QList& points) { glm::vec2 averagePoint(0.0f); if (points.count() > 0) { for (auto& point : points) { @@ -123,163 +130,151 @@ void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { _lastTouch = currentPos; } -UserInputMapper::Input KeyboardMouseDevice::makeInput(Qt::Key code) { - auto shortCode = (UserInputMapper::uint16)(code & KEYBOARD_MASK); +controller::Input KeyboardMouseDevice::makeInput(Qt::Key code) const { + auto shortCode = (uint16_t)(code & KEYBOARD_MASK); if (shortCode != code) { shortCode |= 0x0800; // add this bit instead of the way Qt::Key add a bit on the 3rd byte for some keys } - return UserInputMapper::Input(_deviceID, shortCode, UserInputMapper::ChannelType::BUTTON); + return controller::Input(_deviceID, shortCode, controller::ChannelType::BUTTON); } -UserInputMapper::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code) { +controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code) const { switch (code) { case Qt::LeftButton: - return UserInputMapper::Input(_deviceID, MOUSE_BUTTON_LEFT, UserInputMapper::ChannelType::BUTTON); + return controller::Input(_deviceID, MOUSE_BUTTON_LEFT, controller::ChannelType::BUTTON); case Qt::RightButton: - return UserInputMapper::Input(_deviceID, MOUSE_BUTTON_RIGHT, UserInputMapper::ChannelType::BUTTON); + return controller::Input(_deviceID, MOUSE_BUTTON_RIGHT, controller::ChannelType::BUTTON); case Qt::MiddleButton: - return UserInputMapper::Input(_deviceID, MOUSE_BUTTON_MIDDLE, UserInputMapper::ChannelType::BUTTON); + return controller::Input(_deviceID, MOUSE_BUTTON_MIDDLE, controller::ChannelType::BUTTON); default: - return UserInputMapper::Input(); + return controller::Input(); }; } -UserInputMapper::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::MouseAxisChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +controller::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::MouseAxisChannel axis) const { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); } -UserInputMapper::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchAxisChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); +controller::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchAxisChannel axis) const { + return controller::Input(_deviceID, axis, controller::ChannelType::AXIS); } -UserInputMapper::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchButtonChannel button) { - return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); +controller::Input KeyboardMouseDevice::makeInput(KeyboardMouseDevice::TouchButtonChannel button) const { + return controller::Input(_deviceID, button, controller::ChannelType::BUTTON); } -void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) { - // Grab the current free device ID - _deviceID = mapper.getFreeDeviceID(); - - 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 { - QVector availableInputs; - for (int i = (int) Qt::Key_0; i <= (int) Qt::Key_9; i++) { - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); +controller::Input::NamedVector KeyboardMouseDevice::getAvailableInputs() const { + using namespace controller; + static QVector availableInputs; + static std::once_flag once; + std::call_once(once, [&] { + for (int i = (int)Qt::Key_0; i <= (int)Qt::Key_9; i++) { + availableInputs.append(Input::NamedPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); } - for (int i = (int) Qt::Key_A; i <= (int) Qt::Key_Z; i++) { - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); + for (int i = (int)Qt::Key_A; i <= (int)Qt::Key_Z; i++) { + availableInputs.append(Input::NamedPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); } - for (int i = (int) Qt::Key_Left; i <= (int) Qt::Key_Down; i++) { - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); + for (int i = (int)Qt::Key_Left; i <= (int)Qt::Key_Down; i++) { + availableInputs.append(Input::NamedPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); } - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_Space), QKeySequence(Qt::Key_Space).toString())); - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_Shift), "Shift")); - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString())); - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Space), QKeySequence(Qt::Key_Space).toString())); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Shift), "Shift")); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString())); + availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::LeftButton), "Left Mouse Click")); - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::MiddleButton), "Middle Mouse Click")); - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::RightButton), "Right Mouse Click")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_X_POS), "Mouse Move Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_X_NEG), "Mouse Move Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_Y_POS), "Mouse Move Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_Y_NEG), "Mouse Move Down")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_WHEEL_Y_POS), "Mouse Wheel Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_WHEEL_Y_NEG), "Mouse Wheel Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_WHEEL_X_POS), "Mouse Wheel Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(MOUSE_AXIS_WHEEL_X_NEG), "Mouse Wheel Down")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(TOUCH_AXIS_X_POS), "Touchpad Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(TOUCH_AXIS_X_NEG), "Touchpad Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(TOUCH_AXIS_Y_POS), "Touchpad Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(TOUCH_AXIS_Y_NEG), "Touchpad Down")); + availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseClick")); + availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseClick")); + availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseClick")); - return availableInputs; - }; - proxy->resetDeviceBindings = [this, &mapper] () -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; - mapper.registerDevice(_deviceID, proxy); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_POS), "MouseMoveRight")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "MouseMoveLeft")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_Y_POS), "MouseMoveUp")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_Y_NEG), "MouseMoveDown")); + + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_Y_POS), "MouseWheelRight")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_Y_NEG), "MouseWheelLeft")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_X_POS), "MouseWheelUp")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_X_NEG), "MouseWheelDown")); + + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_POS), "TouchpadRight")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_NEG), "TouchpadLeft")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_POS), "TouchpadUp")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_NEG), "TouchpadDown")); + }); + return availableInputs; } -void KeyboardMouseDevice::assignDefaultInputMapping(UserInputMapper& mapper) { - const float BUTTON_MOVE_SPEED = 1.0f; - const float BUTTON_YAW_SPEED = 0.75f; - const float BUTTON_PITCH_SPEED = 0.5f; - const float MOUSE_YAW_SPEED = 0.5f; - const float MOUSE_PITCH_SPEED = 0.25f; - const float TOUCH_YAW_SPEED = 0.5f; - const float TOUCH_PITCH_SPEED = 0.25f; - const float BUTTON_BOOM_SPEED = 0.1f; - - // AWSD keys mapping - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(Qt::Key_S), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(Qt::Key_W), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(Qt::Key_A), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(Qt::Key_D), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(Qt::Key_C), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(Qt::Key_E), BUTTON_MOVE_SPEED); - - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(Qt::Key_E), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(Qt::Key_C), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(Qt::Key_A), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(Qt::Key_D), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(Qt::Key_A), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(Qt::Key_D), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(Qt::Key_S), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(Qt::Key_W), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); - - // Arrow keys mapping - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(Qt::Key_Down), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(Qt::Key_Up), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(Qt::Key_Left), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(Qt::Key_Right), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(Qt::Key_PageDown), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(Qt::Key_PageUp), BUTTON_MOVE_SPEED); - - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(Qt::Key_Left), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(Qt::Key_Right), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(Qt::Key_Left), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(Qt::Key_Right), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(Qt::Key_Down), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(Qt::Key_Up), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); - - // Mouse move - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(MOUSE_AXIS_Y_NEG), makeInput(Qt::RightButton), MOUSE_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(MOUSE_AXIS_Y_POS), makeInput(Qt::RightButton), MOUSE_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(MOUSE_AXIS_X_NEG), makeInput(Qt::RightButton), MOUSE_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(MOUSE_AXIS_X_POS), makeInput(Qt::RightButton), MOUSE_YAW_SPEED); - - -#ifdef Q_OS_MAC - // wheel event modifier on Mac collide with the touchpad scroll event - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(TOUCH_AXIS_Y_NEG), TOUCH_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(TOUCH_AXIS_Y_POS), TOUCH_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(TOUCH_AXIS_X_NEG), TOUCH_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(TOUCH_AXIS_X_POS), TOUCH_YAW_SPEED); -#else - // Touch pad yaw pitch - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(TOUCH_AXIS_Y_NEG), TOUCH_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(TOUCH_AXIS_Y_POS), TOUCH_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(TOUCH_AXIS_X_NEG), TOUCH_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(TOUCH_AXIS_X_POS), TOUCH_YAW_SPEED); - - // Wheel move - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(MOUSE_AXIS_WHEEL_Y_POS), BUTTON_BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(MOUSE_AXIS_WHEEL_Y_NEG), BUTTON_BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(MOUSE_AXIS_WHEEL_X_NEG), BUTTON_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(MOUSE_AXIS_WHEEL_X_POS), BUTTON_YAW_SPEED); - -#endif - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(Qt::Key_Space)); - mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(Qt::Key_R)); - mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(Qt::Key_T)); +QString KeyboardMouseDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/keyboardMouse.json"; + return MAPPING_JSON; } +//void KeyboardMouseDevice::assignDefaultInputMapping(UserInputMapper& mapper) { +// const float BUTTON_MOVE_SPEED = 1.0f; +// const float BUTTON_YAW_SPEED = 0.75f; +// const float BUTTON_PITCH_SPEED = 0.5f; +// const float MOUSE_YAW_SPEED = 0.5f; +// const float MOUSE_PITCH_SPEED = 0.25f; +// const float TOUCH_YAW_SPEED = 0.5f; +// const float TOUCH_PITCH_SPEED = 0.25f; +// const float BUTTON_BOOM_SPEED = 0.1f; +// +// // AWSD keys mapping +// +// mapper.addInputChannel(controller::BOOM_IN, makeInput(Qt::Key_E), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); +// mapper.addInputChannel(controller::BOOM_OUT, makeInput(Qt::Key_C), makeInput(Qt::Key_Shift), BUTTON_BOOM_SPEED); +// mapper.addInputChannel(controller::LATERAL_LEFT, makeInput(Qt::Key_A), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_RIGHT, makeInput(Qt::Key_D), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_LEFT, makeInput(Qt::Key_A), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_RIGHT, makeInput(Qt::Key_D), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::PITCH_DOWN, makeInput(Qt::Key_S), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); +// mapper.addInputChannel(controller::PITCH_UP, makeInput(Qt::Key_W), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); +// +// // Arrow keys mapping +// mapper.addInputChannel(controller::LONGITUDINAL_BACKWARD, makeInput(Qt::Key_Down), BUTTON_MOVE_SPEED); +// mapper.addInputChannel(controller::LONGITUDINAL_FORWARD, makeInput(Qt::Key_Up), BUTTON_MOVE_SPEED); +// mapper.addInputChannel(controller::YAW_LEFT, makeInput(Qt::Key_Left), BUTTON_MOVE_SPEED); +// mapper.addInputChannel(controller::YAW_RIGHT, makeInput(Qt::Key_Right), BUTTON_MOVE_SPEED); +// mapper.addInputChannel(controller::VERTICAL_DOWN, makeInput(Qt::Key_PageDown), BUTTON_MOVE_SPEED); +// mapper.addInputChannel(controller::VERTICAL_UP, makeInput(Qt::Key_PageUp), BUTTON_MOVE_SPEED); +// +// mapper.addInputChannel(controller::LATERAL_LEFT, makeInput(Qt::Key_Left), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_RIGHT, makeInput(Qt::Key_Right), makeInput(Qt::RightButton), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_LEFT, makeInput(Qt::Key_Left), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_RIGHT, makeInput(Qt::Key_Right), makeInput(Qt::Key_Shift), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::PITCH_DOWN, makeInput(Qt::Key_Down), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); +// mapper.addInputChannel(controller::PITCH_UP, makeInput(Qt::Key_Up), makeInput(Qt::Key_Shift), BUTTON_PITCH_SPEED); +// +// // Mouse move +// mapper.addInputChannel(controller::PITCH_DOWN, makeInput(MOUSE_AXIS_Y_NEG), makeInput(Qt::RightButton), MOUSE_PITCH_SPEED); +// mapper.addInputChannel(controller::PITCH_UP, makeInput(MOUSE_AXIS_Y_POS), makeInput(Qt::RightButton), MOUSE_PITCH_SPEED); +// mapper.addInputChannel(controller::YAW_LEFT, makeInput(MOUSE_AXIS_X_NEG), makeInput(Qt::RightButton), MOUSE_YAW_SPEED); +// mapper.addInputChannel(controller::YAW_RIGHT, makeInput(MOUSE_AXIS_X_POS), makeInput(Qt::RightButton), MOUSE_YAW_SPEED); +// +// +//#ifdef Q_OS_MAC +// // wheel event modifier on Mac collide with the touchpad scroll event +// mapper.addInputChannel(controller::PITCH_DOWN, makeInput(TOUCH_AXIS_Y_NEG), TOUCH_PITCH_SPEED); +// mapper.addInputChannel(controller::PITCH_UP, makeInput(TOUCH_AXIS_Y_POS), TOUCH_PITCH_SPEED); +// mapper.addInputChannel(controller::YAW_LEFT, makeInput(TOUCH_AXIS_X_NEG), TOUCH_YAW_SPEED); +// mapper.addInputChannel(controller::YAW_RIGHT, makeInput(TOUCH_AXIS_X_POS), TOUCH_YAW_SPEED); +//#else +// // Touch pad yaw pitch +// mapper.addInputChannel(controller::PITCH_DOWN, makeInput(TOUCH_AXIS_Y_NEG), TOUCH_PITCH_SPEED); +// mapper.addInputChannel(controller::PITCH_UP, makeInput(TOUCH_AXIS_Y_POS), TOUCH_PITCH_SPEED); +// mapper.addInputChannel(controller::YAW_LEFT, makeInput(TOUCH_AXIS_X_NEG), TOUCH_YAW_SPEED); +// mapper.addInputChannel(controller::YAW_RIGHT, makeInput(TOUCH_AXIS_X_POS), TOUCH_YAW_SPEED); +// +// // Wheel move +// mapper.addInputChannel(controller::BOOM_IN, makeInput(MOUSE_AXIS_WHEEL_Y_POS), BUTTON_BOOM_SPEED); +// mapper.addInputChannel(controller::BOOM_OUT, makeInput(MOUSE_AXIS_WHEEL_Y_NEG), BUTTON_BOOM_SPEED); +// mapper.addInputChannel(controller::LATERAL_LEFT, makeInput(MOUSE_AXIS_WHEEL_X_NEG), BUTTON_YAW_SPEED); +// mapper.addInputChannel(controller::LATERAL_RIGHT, makeInput(MOUSE_AXIS_WHEEL_X_POS), BUTTON_YAW_SPEED); +// +//#endif +// +// mapper.addInputChannel(controller::SHIFT, makeInput(Qt::Key_Space)); +// mapper.addInputChannel(controller::ACTION1, makeInput(Qt::Key_R)); +// mapper.addInputChannel(controller::ACTION2, makeInput(Qt::Key_T)); +//} + diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 6f703bc6f9..1ff77d2dce 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -12,12 +12,19 @@ #ifndef hifi_KeyboardMouseDevice_h #define hifi_KeyboardMouseDevice_h -#include #include -#include "InputDevice.h" + +#include + +#include #include "InputPlugin.h" -class KeyboardMouseDevice : public InputPlugin, public InputDevice { +class QTouchEvent; +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +class KeyboardMouseDevice : public InputPlugin, public controller::InputDevice { Q_OBJECT public: enum KeyboardChannel { @@ -61,15 +68,12 @@ public: virtual bool isJointController() const override { return false; } const QString& getName() const override { 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 controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; @@ -87,11 +91,11 @@ public: void wheelEvent(QWheelEvent* event); // 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); + controller::Input makeInput(Qt::Key code) const; + controller::Input makeInput(Qt::MouseButton code) const; + controller::Input makeInput(MouseAxisChannel axis) const; + controller::Input makeInput(TouchAxisChannel axis) const; + controller::Input makeInput(TouchButtonChannel button) const; static const QString NAME; @@ -100,7 +104,6 @@ protected: glm::vec2 _lastTouch; bool _isTouching = false; - glm::vec2 evalAverageTouchPoints(const QList& points) const; std::chrono::high_resolution_clock _clock; std::chrono::high_resolution_clock::time_point _lastTouchTime; }; diff --git a/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp b/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp index 28874efdb0..600dc5c56f 100644 --- a/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp +++ b/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "SDL2Manager.h" @@ -47,12 +48,12 @@ void SDL2Manager::init() { if (controller) { SDL_JoystickID id = getInstanceId(controller); if (!_openJoysticks.contains(id)) { - Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + //Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + Joystick::Pointer joystick = std::make_shared(id, controller); _openJoysticks[id] = joystick; - auto userInputMapper = DependencyManager::get(); - joystick->registerToUserInputMapper(*userInputMapper); - joystick->assignDefaultInputMapping(*userInputMapper); - emit joystickAdded(joystick); + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(joystick); + emit joystickAdded(joystick.get()); } } } @@ -67,12 +68,33 @@ void SDL2Manager::init() { void SDL2Manager::deinit() { #ifdef HAVE_SDL2 - qDeleteAll(_openJoysticks); + _openJoysticks.clear(); SDL_Quit(); #endif } +void SDL2Manager::activate() { +#ifdef HAVE_SDL2 + auto userInputMapper = DependencyManager::get(); + for (auto joystick : _openJoysticks) { + userInputMapper->registerDevice(joystick); + emit joystickAdded(joystick.get()); + } +#endif +} + +void SDL2Manager::deactivate() { +#ifdef HAVE_SDL2 + auto userInputMapper = DependencyManager::get(); + for (auto joystick : _openJoysticks) { + userInputMapper->removeDevice(joystick->getDeviceID()); + emit joystickRemoved(joystick.get()); + } +#endif +} + + bool SDL2Manager::isSupported() const { #ifdef HAVE_SDL2 return true; @@ -92,7 +114,7 @@ void SDL2Manager::pluginFocusOutEvent() { void SDL2Manager::pluginUpdate(float deltaTime, bool jointsCaptured) { #ifdef HAVE_SDL2 if (_isInitialized) { - auto userInputMapper = DependencyManager::get(); + auto userInputMapper = DependencyManager::get(); for (auto joystick : _openJoysticks) { joystick->update(deltaTime, jointsCaptured); } @@ -102,12 +124,12 @@ void SDL2Manager::pluginUpdate(float deltaTime, bool jointsCaptured) { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_CONTROLLERAXISMOTION) { - Joystick* joystick = _openJoysticks[event.caxis.which]; + Joystick::Pointer joystick = _openJoysticks[event.caxis.which]; if (joystick) { joystick->handleAxisEvent(event.caxis); } } else if (event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP) { - Joystick* joystick = _openJoysticks[event.cbutton.which]; + Joystick::Pointer joystick = _openJoysticks[event.cbutton.which]; if (joystick) { joystick->handleButtonEvent(event.cbutton); } @@ -124,20 +146,21 @@ void SDL2Manager::pluginUpdate(float deltaTime, bool jointsCaptured) { } else if (event.type == SDL_CONTROLLERDEVICEADDED) { SDL_GameController* controller = SDL_GameControllerOpen(event.cdevice.which); - SDL_JoystickID id = getInstanceId(controller); if (!_openJoysticks.contains(id)) { - Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + // Joystick* joystick = new Joystick(id, SDL_GameControllerName(controller), controller); + Joystick::Pointer joystick = std::make_shared(id, controller); _openJoysticks[id] = joystick; - joystick->registerToUserInputMapper(*userInputMapper); - joystick->assignDefaultInputMapping(*userInputMapper); - emit joystickAdded(joystick); + userInputMapper->registerDevice(joystick); + emit joystickAdded(joystick.get()); } } else if (event.type == SDL_CONTROLLERDEVICEREMOVED) { - Joystick* joystick = _openJoysticks[event.cdevice.which]; - _openJoysticks.remove(event.cdevice.which); - userInputMapper->removeDevice(joystick->getDeviceID()); - emit joystickRemoved(joystick); + if (_openJoysticks.contains(event.cdevice.which)) { + Joystick::Pointer joystick = _openJoysticks[event.cdevice.which]; + _openJoysticks.remove(event.cdevice.which); + userInputMapper->removeDevice(joystick->getDeviceID()); + emit joystickRemoved(joystick.get()); + } } } } diff --git a/libraries/input-plugins/src/input-plugins/SDL2Manager.h b/libraries/input-plugins/src/input-plugins/SDL2Manager.h index 52d39597ef..d6b6de24b3 100644 --- a/libraries/input-plugins/src/input-plugins/SDL2Manager.h +++ b/libraries/input-plugins/src/input-plugins/SDL2Manager.h @@ -16,9 +16,9 @@ #include #endif -#include "InputPlugin.h" -#include "UserInputMapper.h" +#include +#include "InputPlugin.h" #include "Joystick.h" class SDL2Manager : public InputPlugin { @@ -34,9 +34,12 @@ public: virtual void init() override; virtual void deinit() override; - virtual void activate() override {}; - virtual void deactivate() override {}; - + + /// Called when a plugin is being activated for use. May be called multiple times. + virtual void activate() override; + /// Called when a plugin is no longer being used. May be called multiple times. + virtual void deactivate() override; + virtual void pluginFocusOutEvent() override; virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override; @@ -78,7 +81,7 @@ private: int buttonPressed() const { return SDL_PRESSED; } int buttonRelease() const { return SDL_RELEASED; } - QMap _openJoysticks; + QMap _openJoysticks; #endif bool _isInitialized; static const QString NAME; diff --git a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp index 8ea05fc300..2527da9e03 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.cpp +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.cpp @@ -18,10 +18,13 @@ #include #include #include +#include +#include +#include +#include -#include "NumericalConstants.h" #include "SixenseManager.h" -#include "UserActivityLogger.h" + #ifdef HAVE_SIXENSE #include "sixense.h" @@ -32,10 +35,6 @@ 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; -const unsigned int RIGHT_MASK = 1U << 1; - #ifdef HAVE_SIXENSE const int CALIBRATION_STATE_IDLE = 0; @@ -65,15 +64,12 @@ const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; const float DEFAULT_REACH_LENGTH = 1.5f; - +static std::shared_ptr instance; SixenseManager::SixenseManager() : InputDevice("Hydra"), - _reachLength(DEFAULT_REACH_LENGTH), -#ifdef __APPLE__ - _sixenseLibrary(nullptr), -#endif - _hydrasConnected(false) + _reachLength(DEFAULT_REACH_LENGTH) { + instance = std::shared_ptr(this); } bool SixenseManager::isSupported() const { @@ -85,6 +81,7 @@ bool SixenseManager::isSupported() const { } void SixenseManager::activate() { + InputPlugin::activate(); #ifdef HAVE_SIXENSE _calibrationState = CALIBRATION_STATE_IDLE; _avatarPosition = DEFAULT_AVATAR_POSITION; @@ -94,6 +91,9 @@ void SixenseManager::activate() { [this] (bool clicked) { this->setSixenseFilter(clicked); }, true, true); + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(instance); + #ifdef __APPLE__ if (!_sixenseLibrary) { @@ -125,11 +125,19 @@ void SixenseManager::activate() { } void SixenseManager::deactivate() { + InputPlugin::deactivate(); + #ifdef HAVE_SIXENSE CONTAINER->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH); CONTAINER->removeMenu(MENU_PATH); _poseStateMap.clear(); + _collectedSamples.clear(); + + if (_deviceID != controller::Input::INVALID_DEVICE) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(_deviceID); + } #ifdef __APPLE__ SixenseBaseFunction sixenseExit = (SixenseBaseFunction)_sixenseLibrary->resolve("sixenseExit"); @@ -155,6 +163,12 @@ void SixenseManager::setSixenseFilter(bool filter) { } void SixenseManager::update(float deltaTime, bool jointsCaptured) { + // FIXME - Some of the code in update() will crash if you haven't actually activated the + // plugin. But we want register with the UserInputMapper if we don't call this. + // We need to clean this up. + //if (!_activated) { + // return; + //} #ifdef HAVE_SIXENSE _buttonPressedMap.clear(); @@ -163,33 +177,34 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetNumActiveControllers"); #endif - auto userInputMapper = DependencyManager::get(); + auto userInputMapper = DependencyManager::get(); + static const float MAX_DISCONNECTED_TIME = 2.0f; + static bool disconnected { false }; + static float disconnectedInterval { 0.0f }; if (sixenseGetNumActiveControllers() == 0) { - if (_hydrasConnected) { - qCDebug(inputplugins) << "hydra disconnected" << _badDataCount; - if (_badDataCount++ < _allowedBadDataCount) { // gotta get some no-active in a row before we shut things down - return; - } + if (!disconnected) { + disconnectedInterval += deltaTime; } - _hydrasConnected = false; - if (_deviceID != 0) { - userInputMapper->removeDevice(_deviceID); - _deviceID = 0; + if (disconnectedInterval > MAX_DISCONNECTED_TIME) { + disconnected = true; + _axisStateMap.clear(); + _buttonPressedMap.clear(); _poseStateMap.clear(); + _collectedSamples.clear(); } return; } - PerformanceTimer perfTimer("sixense"); - if (!_hydrasConnected) { - _hydrasConnected = true; - _badDataCount = 0; - registerToUserInputMapper(*userInputMapper); - assignDefaultInputMapping(*userInputMapper); - UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); + if (disconnected) { + disconnected = 0; + disconnectedInterval = 0.0f; } + PerformanceTimer perfTimer("sixense"); + // FIXME send this message once when we've positively identified hydra hardware + //UserActivityLogger::getInstance().connectedDevice("spatial_controller", "hydra"); + #ifdef __APPLE__ SixenseBaseFunction sixenseGetMaxControllers = (SixenseBaseFunction) _sixenseLibrary->resolve("sixenseGetMaxControllers"); @@ -220,28 +235,31 @@ void SixenseManager::update(float deltaTime, bool jointsCaptured) { // 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; - + bool left = i == 0; + using namespace controller; // 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); + handleButtonEvent(data->buttons, left); + _axisStateMap[left ? LX : RX] = data->joystick_x; + _axisStateMap[left ? LY : RY] = data->joystick_y; + _axisStateMap[left ? LT : RT] = data->trigger; if (!jointsCaptured) { // Rotation of Palm glm::quat rotation(data->rot_quat[3], data->rot_quat[0], data->rot_quat[1], data->rot_quat[2]); - handlePoseEvent(position, rotation, numActiveControllers - 1); + handlePoseEvent(deltaTime, position, rotation, left); + } else { _poseStateMap.clear(); + _collectedSamples.clear(); } } else { - _poseStateMap[(numActiveControllers - 1) == 0 ? LEFT_HAND : RIGHT_HAND] = UserInputMapper::PoseValue(); + auto hand = left ? controller::StandardPoseChannel::LEFT_HAND : controller::StandardPoseChannel::RIGHT_HAND; + _poseStateMap[hand] = controller::Pose(); + _collectedSamples[hand].first.clear(); + _collectedSamples[hand].second.clear(); } - -// // 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) { @@ -361,40 +379,38 @@ void SixenseManager::focusOutEvent() { _buttonPressedMap.clear(); }; -void SixenseManager::handleAxisEvent(float stickX, float stickY, float trigger, int index) { - _axisStateMap[makeInput(AXIS_Y_POS, index).getChannel()] = (stickY > 0.0f) ? stickY : 0.0f; - _axisStateMap[makeInput(AXIS_Y_NEG, index).getChannel()] = (stickY < 0.0f) ? -stickY : 0.0f; - _axisStateMap[makeInput(AXIS_X_POS, index).getChannel()] = (stickX > 0.0f) ? stickX : 0.0f; - _axisStateMap[makeInput(AXIS_X_NEG, index).getChannel()] = (stickX < 0.0f) ? -stickX : 0.0f; - _axisStateMap[makeInput(BACK_TRIGGER, index).getChannel()] = trigger; +void SixenseManager::handleAxisEvent(float stickX, float stickY, float trigger, bool left) { } -void SixenseManager::handleButtonEvent(unsigned int buttons, int index) { +void SixenseManager::handleButtonEvent(unsigned int buttons, bool left) { + using namespace controller; if (buttons & BUTTON_0) { - _buttonPressedMap.insert(makeInput(BUTTON_0, index).getChannel()); + _buttonPressedMap.insert(left ? BACK : START); } if (buttons & BUTTON_1) { - _buttonPressedMap.insert(makeInput(BUTTON_1, index).getChannel()); + _buttonPressedMap.insert(left ? DL : X); } if (buttons & BUTTON_2) { - _buttonPressedMap.insert(makeInput(BUTTON_2, index).getChannel()); + _buttonPressedMap.insert(left ? DD : A); } if (buttons & BUTTON_3) { - _buttonPressedMap.insert(makeInput(BUTTON_3, index).getChannel()); + _buttonPressedMap.insert(left ? DR : B); } if (buttons & BUTTON_4) { - _buttonPressedMap.insert(makeInput(BUTTON_4, index).getChannel()); + _buttonPressedMap.insert(left ? DU : Y); } if (buttons & BUTTON_FWD) { - _buttonPressedMap.insert(makeInput(BUTTON_FWD, index).getChannel()); + _buttonPressedMap.insert(left ? LB : RB); } if (buttons & BUTTON_TRIGGER) { - _buttonPressedMap.insert(makeInput(BUTTON_TRIGGER, index).getChannel()); + _buttonPressedMap.insert(left ? LS : RS); } } -void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int index) { +void SixenseManager::handlePoseEvent(float deltaTime, glm::vec3 position, glm::quat rotation, bool left) { #ifdef HAVE_SIXENSE + auto hand = left ? controller::StandardPoseChannel::LEFT_HAND : controller::StandardPoseChannel::RIGHT_HAND; + // From ABOVE the sixense coordinate frame looks like this: // // | @@ -409,6 +425,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // | // | // z + auto prevPose = _poseStateMap[hand]; // Transform the measured position into body frame. position = _avatarRotation * (position + _avatarPosition); @@ -437,7 +454,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // In addition to Qsh each hand has pre-offset introduced by the shape of the sixense controllers // and how they fit into the hand in their relaxed state. This offset is a quarter turn about // the sixense's z-axis, with its direction different for the two hands: - float sign = (index == 0) ? 1.0f : -1.0f; + float sign = left ? 1.0f : -1.0f; const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, Vectors::UNIT_Z); // Finally, there is a post-offset (same for both hands) to get the hand's rest orientation @@ -452,103 +469,127 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int // TODO: find a shortcut with fewer rotations. rotation = _avatarRotation * postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; - _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); + glm::vec3 velocity(0.0f); + glm::quat angularVelocity; + + + + if (prevPose.isValid() && deltaTime > std::numeric_limits::epsilon()) { + + velocity = (position - prevPose.getTranslation()) / deltaTime; + + auto deltaRot = rotation * glm::conjugate(prevPose.getRotation()); + auto axis = glm::axis(deltaRot); + auto angle = glm::angle(deltaRot); + angularVelocity = glm::angleAxis(angle / deltaTime, axis); + + // Average + auto& samples = _collectedSamples[hand]; + samples.first.addSample(velocity); + velocity = samples.first.average; + + // FIXME: // Not using quaternion average yet for angular velocity because it s probably wrong but keep the MovingAverage in place + //samples.second.addSample(glm::vec4(angularVelocity.x, angularVelocity.y, angularVelocity.z, angularVelocity.w)); + //angularVelocity = glm::quat(samples.second.average.w, samples.second.average.x, samples.second.average.y, samples.second.average.z); + } else if (!prevPose.isValid()) { + _collectedSamples[hand].first.clear(); + _collectedSamples[hand].second.clear(); + } + + _poseStateMap[hand] = controller::Pose(position, rotation, velocity, angularVelocity); #endif // HAVE_SIXENSE } -void SixenseManager::registerToUserInputMapper(UserInputMapper& mapper) { - // Grab the current free device ID - _deviceID = mapper.getFreeDeviceID(); +static const auto L0 = controller::BACK; +static const auto L1 = controller::DL; +static const auto L2 = controller::DD; +static const auto L3 = controller::DR; +static const auto L4 = controller::DU; +static const auto R0 = controller::START; +static const auto R1 = controller::X; +static const auto R2 = controller::A; +static const auto R3 = controller::B; +static const auto R4 = controller::Y; - 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")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1, 0), "Left Button 1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 0), "Left Button 2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 0), "Left Button 3")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 0), "Left Button 4")); +using namespace controller; - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 0), "L1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 0), "L2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 0), "Left Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 0), "Left Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 0), "Left Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 0), "Left Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_TRIGGER, 0), "Left Trigger Press")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_0, 1), "Right Start")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_1, 1), "Right Button 1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_2, 1), "Right Button 2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_3, 1), "Right Button 3")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_4, 1), "Right Button 4")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_FWD, 1), "R1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BACK_TRIGGER, 1), "R2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_POS, 1), "Right Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_Y_NEG, 1), "Right Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_POS, 1), "Right Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(AXIS_X_NEG, 1), "Right Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(BUTTON_TRIGGER, 1), "Right Trigger Press")); - - return availableInputs; +controller::Input::NamedVector SixenseManager::getAvailableInputs() const { + using namespace controller; + static const Input::NamedVector availableInputs { + makePair(L0, "L0"), + makePair(L1, "L1"), + makePair(L2, "L2"), + makePair(L3, "L3"), + makePair(L4, "L4"), + makePair(LB, "LB"), + makePair(LS, "LS"), + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(LT, "LT"), + makePair(R0, "R0"), + makePair(R1, "R1"), + makePair(R2, "R2"), + makePair(R3, "R3"), + makePair(R4, "R4"), + makePair(RB, "RB"), + makePair(RS, "RS"), + makePair(RX, "RX"), + makePair(RY, "RY"), + makePair(RT, "RT"), + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), }; - proxy->resetDeviceBindings = [this, &mapper] () -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; - mapper.registerDevice(_deviceID, proxy); + return availableInputs; +}; + + +QString SixenseManager::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/hydra.json"; + return MAPPING_JSON; } -void SixenseManager::assignDefaultInputMapping(UserInputMapper& mapper) { - const float JOYSTICK_MOVE_SPEED = 1.0f; - const float JOYSTICK_YAW_SPEED = 0.5f; - const float JOYSTICK_PITCH_SPEED = 0.25f; - const float BUTTON_MOVE_SPEED = 1.0f; - const float BOOM_SPEED = 0.1f; - - // Left Joystick: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(AXIS_Y_POS, 0), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(AXIS_Y_NEG, 0), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(AXIS_X_POS, 0), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(AXIS_X_NEG, 0), JOYSTICK_MOVE_SPEED); - - // Right Joystick: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(AXIS_X_POS, 1), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(AXIS_X_NEG, 1), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(AXIS_Y_POS, 1), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(AXIS_Y_NEG, 1), JOYSTICK_PITCH_SPEED); - - // Buttons - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(BUTTON_3, 0), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(BUTTON_1, 0), BOOM_SPEED); - - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(BUTTON_3, 1), BUTTON_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(BUTTON_1, 1), BUTTON_MOVE_SPEED); - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 0)); - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(BUTTON_2, 1)); - - 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(BACK_TRIGGER, 0)); - mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BACK_TRIGGER, 1)); - - // TODO find a mechanism to allow users to navigate the context menu via - mapper.addInputChannel(UserInputMapper::CONTEXT_MENU, makeInput(BUTTON_0, 0)); - mapper.addInputChannel(UserInputMapper::TOGGLE_MUTE, makeInput(BUTTON_0, 1)); - -} +// +//void SixenseManager::assignDefaultInputMapping(UserInputMapper& mapper) { +// const float JOYSTICK_MOVE_SPEED = 1.0f; +// const float JOYSTICK_YAW_SPEED = 0.5f; +// const float JOYSTICK_PITCH_SPEED = 0.25f; +// const float BUTTON_MOVE_SPEED = 1.0f; +// const float BOOM_SPEED = 0.1f; +// using namespace controller; +// +// // Left Joystick: Movement, strafing +// mapper.addInputChannel(UserInputMapper::TRANSLATE_Z, makeInput(LY), JOYSTICK_MOVE_SPEED); +// mapper.addInputChannel(UserInputMapper::TRANSLATE_X, makeInput(LX), JOYSTICK_MOVE_SPEED); +// +// // Right Joystick: Camera orientation +// mapper.addInputChannel(UserInputMapper::YAW, makeInput(RX), JOYSTICK_YAW_SPEED); +// mapper.addInputChannel(UserInputMapper::PITCH, makeInput(RY), JOYSTICK_PITCH_SPEED); +// +// // Buttons +// mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(L3), BOOM_SPEED); +// mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(L1), BOOM_SPEED); +// +// mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(R3), BUTTON_MOVE_SPEED); +// mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(R1), BUTTON_MOVE_SPEED); +// +// mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(L2)); +// mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(R2)); +// +// mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(L4)); +// mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(R4)); +// +// // FIXME +//// mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND)); +//// mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND)); +// +// mapper.addInputChannel(UserInputMapper::LEFT_HAND_CLICK, makeInput(LT)); +// mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(RT)); +// +// // TODO find a mechanism to allow users to navigate the context menu via +// mapper.addInputChannel(UserInputMapper::CONTEXT_MENU, makeInput(L0)); +// mapper.addInputChannel(UserInputMapper::TOGGLE_MUTE, makeInput(R0)); +// +//} // virtual void SixenseManager::saveSettings() const { @@ -559,7 +600,6 @@ void SixenseManager::saveSettings() const { settings.setVec3Value(QString("avatarPosition"), _avatarPosition); settings.setQuatValue(QString("avatarRotation"), _avatarRotation); settings.setValue(QString("reachLength"), QVariant(_reachLength)); - settings.setValue(QString("allowedHydraFailures"), 120); } settings.endGroup(); } @@ -572,19 +612,6 @@ void SixenseManager::loadSettings() { settings.getVec3ValueIfValid(QString("avatarPosition"), _avatarPosition); settings.getQuatValueIfValid(QString("avatarRotation"), _avatarRotation); settings.getFloatValueIfValid(QString("reachLength"), _reachLength); - _allowedBadDataCount = settings.value(QString("allowedHydraFailures"), 120).toInt(); } settings.endGroup(); } - -UserInputMapper::Input SixenseManager::makeInput(unsigned int button, int index) { - return UserInputMapper::Input(_deviceID, button | (index == 0 ? LEFT_MASK : RIGHT_MASK), UserInputMapper::ChannelType::BUTTON); -} - -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/libraries/input-plugins/src/input-plugins/SixenseManager.h b/libraries/input-plugins/src/input-plugins/SixenseManager.h index 90bd830650..5b5cb7ccfa 100644 --- a/libraries/input-plugins/src/input-plugins/SixenseManager.h +++ b/libraries/input-plugins/src/input-plugins/SixenseManager.h @@ -24,8 +24,12 @@ #endif +#include + +#include +#include + #include "InputPlugin.h" -#include "InputDevice.h" class QLibrary; @@ -40,22 +44,9 @@ const unsigned int BUTTON_TRIGGER = 1U << 8; const bool DEFAULT_INVERT_SIXENSE_MOUSE_BUTTONS = false; // Handles interaction with the Sixense SDK (e.g., Razer Hydra). -class SixenseManager : public InputPlugin, public InputDevice { +class SixenseManager : public InputPlugin, public controller::InputDevice { Q_OBJECT public: - enum JoystickAxisChannel { - AXIS_Y_POS = 1U << 0, - AXIS_Y_NEG = 1U << 3, - AXIS_X_POS = 1U << 4, - AXIS_X_NEG = 1U << 5, - BACK_TRIGGER = 1U << 6, - }; - - enum JointChannel { - LEFT_HAND = 0, - RIGHT_HAND, - }; - SixenseManager(); // Plugin functions @@ -71,15 +62,12 @@ public: 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 controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; - UserInputMapper::Input makeInput(unsigned int button, int index); - UserInputMapper::Input makeInput(JoystickAxisChannel axis, int index); - UserInputMapper::Input makeInput(JointChannel joint); - virtual void saveSettings() const override; virtual void loadSettings() override; @@ -87,9 +75,9 @@ public slots: void setSixenseFilter(bool filter); private: - void handleButtonEvent(unsigned int buttons, int index); - void handleAxisEvent(float x, float y, float trigger, int index); - void handlePoseEvent(glm::vec3 position, glm::quat rotation, int index); + void handleButtonEvent(unsigned int buttons, bool left); + void handleAxisEvent(float x, float y, float trigger, bool left); + void handlePoseEvent(float deltaTime, glm::vec3 position, glm::quat rotation, bool left); void updateCalibration(void* controllers); @@ -108,17 +96,20 @@ private: glm::vec3 _reachRight; float _lastDistance; bool _useSixenseFilter = true; + + + static const int MAX_NUM_AVERAGING_SAMPLES = 50; // At ~100 updates per seconds this means averaging over ~.5s + using Samples = std::pair< MovingAverage< glm::vec3, MAX_NUM_AVERAGING_SAMPLES>, MovingAverage< glm::vec4, MAX_NUM_AVERAGING_SAMPLES> >; + using MovingAverageMap = std::map< int, Samples >; + MovingAverageMap _collectedSamples; #ifdef __APPLE__ - QLibrary* _sixenseLibrary; + QLibrary* _sixenseLibrary { nullptr }; #endif - bool _hydrasConnected; - int _badDataCount; - int _allowedBadDataCount; - static const QString NAME; static const QString HYDRA_ID_STRING; }; #endif // hifi_SixenseManager_h + diff --git a/libraries/input-plugins/src/input-plugins/StandardController.cpp b/libraries/input-plugins/src/input-plugins/StandardController.cpp deleted file mode 100644 index 9b89c081b3..0000000000 --- a/libraries/input-plugins/src/input-plugins/StandardController.cpp +++ /dev/null @@ -1,160 +0,0 @@ -// -// StandardController.cpp -// input-plugins/src/input-plugins -// -// Created by Brad Hefta-Gaub on 2015-10-11. -// 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 - -#include - -#include "StandardController.h" - -const float CONTROLLER_THRESHOLD = 0.3f; - -StandardController::~StandardController() { -} - -void StandardController::update(float deltaTime, bool jointsCaptured) { - for (auto axisState : _axisStateMap) { - if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { - _axisStateMap[axisState.first] = 0.0f; - } - } -} - -void StandardController::focusOutEvent() { - _axisStateMap.clear(); - _buttonPressedMap.clear(); -}; - -void StandardController::registerToUserInputMapper(UserInputMapper& mapper) { - // Grab the current free device ID - _deviceID = mapper.getStandardDeviceID(); - - 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 { - QVector availableInputs; - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_A), "Bottom Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_B), "Right Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_X), "Left Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_Y), "Top Button")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_UP), "DPad Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_DOWN), "DPad Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_LEFT), "DPad Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT), "DPad Right")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_LEFTSHOULDER), "L1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), "R1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_SHOULDER), "L2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_SHOULDER), "R2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_NEG), "Left Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_POS), "Left Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_POS), "Left Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_NEG), "Left Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_NEG), "Right Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_POS), "Right Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_POS), "Right Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_NEG), "Right Stick Left")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_HAND), "Left Hand")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_HAND), "Right Hand")); - - return availableInputs; - }; - proxy->resetDeviceBindings = [this, &mapper] () -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; - mapper.registerStandardDevice(proxy); -} - -void StandardController::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) - // Left StandardController: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), JOYSTICK_MOVE_SPEED); - - // Right StandardController: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), JOYSTICK_PITCH_SPEED); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_UP), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_DOWN), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_LEFT), DPAD_MOVE_SPEED); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(STANDARD_CONTROLLER_BUTTON_Y), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(STANDARD_CONTROLLER_BUTTON_X), DPAD_MOVE_SPEED); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), BOOM_SPEED); - - - // Hold front right shoulder button for precision controls - // Left StandardController: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - - // Right StandardController: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_UP), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_DOWN), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_LEFT), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(STANDARD_CONTROLLER_BUTTON_Y), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(STANDARD_CONTROLLER_BUTTON_X), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(STANDARD_CONTROLLER_BUTTON_LEFTSHOULDER)); - - mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(STANDARD_CONTROLLER_BUTTON_B)); - mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(STANDARD_CONTROLLER_BUTTON_A)); -} - -UserInputMapper::Input StandardController::makeInput(StandardController::StandardControllerButtonChannel button) { - return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); -} - -UserInputMapper::Input StandardController::makeInput(StandardController::StandardControllerAxisChannel axis) { - return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); -} - -UserInputMapper::Input StandardController::makeInput(StandardController::StandardControllerPoseChannel pose) { - return UserInputMapper::Input(_deviceID, pose, UserInputMapper::ChannelType::POSE); -} diff --git a/libraries/input-plugins/src/input-plugins/StandardController.h b/libraries/input-plugins/src/input-plugins/StandardController.h deleted file mode 100644 index f7a4215242..0000000000 --- a/libraries/input-plugins/src/input-plugins/StandardController.h +++ /dev/null @@ -1,77 +0,0 @@ -// -// StandardController.h -// input-plugins/src/input-plugins -// -// Created by Brad Hefta-Gaub on 2015-10-11. -// 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_StandardController_h -#define hifi_StandardController_h - -#include -#include - -#include "InputDevice.h" - -typedef std::shared_ptr StandardControllerPointer; - -class StandardController : public QObject, public InputDevice { - Q_OBJECT - Q_PROPERTY(QString name READ getName) - -public: - enum StandardControllerAxisChannel { - LEFT_AXIS_X_POS = 0, - LEFT_AXIS_X_NEG, - LEFT_AXIS_Y_POS, - LEFT_AXIS_Y_NEG, - RIGHT_AXIS_X_POS, - RIGHT_AXIS_X_NEG, - RIGHT_AXIS_Y_POS, - RIGHT_AXIS_Y_NEG, - RIGHT_SHOULDER, - LEFT_SHOULDER, - }; - enum StandardControllerButtonChannel { - STANDARD_CONTROLLER_BUTTON_A = 0, - STANDARD_CONTROLLER_BUTTON_B, - STANDARD_CONTROLLER_BUTTON_X, - STANDARD_CONTROLLER_BUTTON_Y, - - STANDARD_CONTROLLER_BUTTON_DPAD_UP, - STANDARD_CONTROLLER_BUTTON_DPAD_DOWN, - STANDARD_CONTROLLER_BUTTON_DPAD_LEFT, - STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT, - - STANDARD_CONTROLLER_BUTTON_LEFTSHOULDER, - STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER, - }; - - enum StandardControllerPoseChannel { - LEFT_HAND = 0, - RIGHT_HAND, - }; - - 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; - - StandardController() : InputDevice("Standard") {} - ~StandardController(); - - UserInputMapper::Input makeInput(StandardController::StandardControllerButtonChannel button); - UserInputMapper::Input makeInput(StandardController::StandardControllerAxisChannel axis); - UserInputMapper::Input makeInput(StandardController::StandardControllerPoseChannel pose); - -private: -}; - -#endif // hifi_StandardController_h diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp deleted file mode 100755 index fad962345c..0000000000 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp +++ /dev/null @@ -1,335 +0,0 @@ -// -// UserInputMapper.cpp -// input-plugins/src/input-plugins -// -// Created by Sam Gateau on 4/27/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 "UserInputMapper.h" -#include "StandardController.h" - -// Default contruct allocate the poutput size with the current hardcoded action channels -UserInputMapper::UserInputMapper() { - registerStandardDevice(); - assignDefaulActionScales(); - createActionNames(); -} - -UserInputMapper::~UserInputMapper() { -} - - -bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){ - proxy->_name += " (" + QString::number(deviceID) + ")"; - _registeredDevices[deviceID] = proxy; - return true; -} - -UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) { - auto device = _registeredDevices.find(input.getDevice()); - if (device != _registeredDevices.end()) { - return (device->second); - } else { - return DeviceProxy::Pointer(); - } -} - -QString UserInputMapper::getDeviceName(uint16 deviceID) { - if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { - return _registeredDevices[deviceID]->_name; - } - return QString("unknown"); -} - - -void UserInputMapper::resetAllDeviceBindings() { - for (auto device : _registeredDevices) { - device.second->resetDeviceBindings(); - } -} - -void UserInputMapper::resetDevice(uint16 deviceID) { - auto device = _registeredDevices.find(deviceID); - if (device != _registeredDevices.end()) { - device->second->resetDeviceBindings(); - } -} - -int UserInputMapper::findDevice(QString name) { - for (auto device : _registeredDevices) { - if (device.second->_name.split(" (")[0] == name) { - return device.first; - } - } - return 0; -} - -QVector UserInputMapper::getDeviceNames() { - QVector result; - for (auto device : _registeredDevices) { - QString deviceName = device.second->_name.split(" (")[0]; - result << deviceName; - } - return result; -} - - -bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) { - return addInputChannel(action, input, Input(), scale); -} - -bool UserInputMapper::addInputChannel(Action action, const Input& input, const Input& modifier, float scale) { - // Check that the device is registered - if (!getDeviceProxy(input)) { - qDebug() << "UserInputMapper::addInputChannel: The input comes from a device #" << input.getDevice() << "is unknown. no inputChannel mapped."; - return false; - } - - auto inputChannel = InputChannel(input, modifier, action, scale); - - // Insert or replace the input to modifiers - if (inputChannel.hasModifier()) { - auto& modifiers = _inputToModifiersMap[input.getID()]; - modifiers.push_back(inputChannel._modifier); - std::sort(modifiers.begin(), modifiers.end()); - } - - // Now update the action To Inputs side of things - _actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel)); - - return true; -} - -int UserInputMapper::addInputChannels(const InputChannels& channels) { - int nbAdded = 0; - for (auto& channel : channels) { - nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale); - } - return nbAdded; -} - -bool UserInputMapper::removeInputChannel(InputChannel inputChannel) { - // Remove from Input to Modifiers map - if (inputChannel.hasModifier()) { - _inputToModifiersMap.erase(inputChannel._input.getID()); - } - - // Remove from Action to Inputs map - std::pair ret; - ret = _actionToInputsMap.equal_range(inputChannel._action); - for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { - if (it->second == inputChannel) { - _actionToInputsMap.erase(it); - return true; - } - } - - return false; -} - -void UserInputMapper::removeAllInputChannels() { - _inputToModifiersMap.clear(); - _actionToInputsMap.clear(); -} - -void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) { - QVector channels = getAllInputsForDevice(device); - for (auto& channel : channels) { - removeInputChannel(channel); - } -} - -void UserInputMapper::removeDevice(int device) { - removeAllInputChannelsForDevice((uint16) device); - _registeredDevices.erase(device); -} - -int UserInputMapper::getInputChannels(InputChannels& channels) const { - for (auto& channel : _actionToInputsMap) { - channels.push_back(channel.second); - } - - return _actionToInputsMap.size(); -} - -QVector UserInputMapper::getAllInputsForDevice(uint16 device) { - InputChannels allChannels; - getInputChannels(allChannels); - - QVector channels; - for (InputChannel inputChannel : allChannels) { - if (inputChannel._input._device == device) { - channels.push_back(inputChannel); - } - } - - return channels; -} - -void UserInputMapper::update(float deltaTime) { - - // Reset the axis state for next loop - for (auto& channel : _actionStates) { - channel = 0.0f; - } - - for (auto& channel : _poseStates) { - channel = PoseValue(); - } - - int currentTimestamp = 0; - - for (auto& channelInput : _actionToInputsMap) { - auto& inputMapping = channelInput.second; - auto& inputID = inputMapping._input; - bool enabled = true; - - // Check if this input channel has modifiers and collect the possibilities - auto modifiersIt = _inputToModifiersMap.find(inputID.getID()); - if (modifiersIt != _inputToModifiersMap.end()) { - Modifiers validModifiers; - bool isActiveModifier = false; - for (auto& modifier : modifiersIt->second) { - auto deviceProxy = getDeviceProxy(modifier); - if (deviceProxy->getButton(modifier, currentTimestamp)) { - validModifiers.push_back(modifier); - isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID()); - } - } - enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier; - } - - // if enabled: default input or all modifiers on - if (enabled) { - auto deviceProxy = getDeviceProxy(inputID); - switch (inputMapping._input.getType()) { - case ChannelType::BUTTON: { - _actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime - break; - } - case ChannelType::AXIS: { - _actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp); - break; - } - case ChannelType::POSE: { - if (!_poseStates[channelInput.first].isValid()) { - _poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp); - } - break; - } - default: { - break; //silence please - } - } - } else{ - // Channel input not enabled - enabled = false; - } - } - - // Scale all the channel step with the scale - static const float EPSILON = 0.01f; - for (auto i = 0; i < NUM_ACTIONS; i++) { - _actionStates[i] *= _actionScales[i]; - // Emit only on change, and emit when moving back to 0 - if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) { - _lastActionStates[i] = _actionStates[i]; - emit actionEvent(i, _actionStates[i]); - } - // TODO: emit signal for pose changes - } -} - -QVector UserInputMapper::getAllActions() const { - QVector actions; - for (auto i = 0; i < NUM_ACTIONS; i++) { - actions.append(Action(i)); - } - return actions; -} - -QVector UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) { - QVector inputChannels; - std::pair ret; - ret = _actionToInputsMap.equal_range(action); - for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { - inputChannels.append(it->second); - } - return inputChannels; -} - -int UserInputMapper::findAction(const QString& actionName) const { - auto actions = getAllActions(); - for (auto action : actions) { - if (getActionName(action) == actionName) { - return action; - } - } - // If the action isn't found, return -1 - return -1; -} - -QVector UserInputMapper::getActionNames() const { - QVector result; - for (auto i = 0; i < NUM_ACTIONS; i++) { - result << _actionNames[i]; - } - return result; -} - -void UserInputMapper::assignDefaulActionScales() { - _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit - _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit - _actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit - _actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit - _actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit - _actionScales[VERTICAL_UP] = 1.0f; // 1m per unit - _actionScales[YAW_LEFT] = 1.0f; // 1 degree per unit - _actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit - _actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit - _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 -} - -// This is only necessary as long as the actions are hardcoded -// Eventually you can just add the string when you add the action -void UserInputMapper::createActionNames() { - _actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD"; - _actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD"; - _actionNames[LATERAL_LEFT] = "LATERAL_LEFT"; - _actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT"; - _actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN"; - _actionNames[VERTICAL_UP] = "VERTICAL_UP"; - _actionNames[YAW_LEFT] = "YAW_LEFT"; - _actionNames[YAW_RIGHT] = "YAW_RIGHT"; - _actionNames[PITCH_DOWN] = "PITCH_DOWN"; - _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"; - _actionNames[CONTEXT_MENU] = "CONTEXT_MENU"; - _actionNames[TOGGLE_MUTE] = "TOGGLE_MUTE"; -} - -void UserInputMapper::registerStandardDevice() { - _standardController = std::make_shared(); - _standardController->registerToUserInputMapper(*this); -} \ No newline at end of file diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.h b/libraries/input-plugins/src/input-plugins/UserInputMapper.h deleted file mode 100755 index 1d64638ee1..0000000000 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.h +++ /dev/null @@ -1,284 +0,0 @@ -// -// UserInputMapper.h -// input-plugins/src/input-plugins -// -// Created by Sam Gateau on 4/27/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 -// - -#ifndef hifi_UserInputMapper_h -#define hifi_UserInputMapper_h - -#include - -#include -#include -#include -#include -#include - -class StandardController; -typedef std::shared_ptr StandardControllerPointer; - -class UserInputMapper : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - Q_ENUMS(Action) -public: - ~UserInputMapper(); - - typedef unsigned short uint16; - typedef unsigned int uint32; - - enum class ChannelType { - UNKNOWN = 0, - BUTTON = 1, - AXIS, - POSE, - }; - - // Input is the unique identifier to find a n input channel of a particular device - // Devices are responsible for registering to the UseInputMapper so their input channels can be sued and mapped - // to the Action channels - class Input { - public: - union { - struct { - uint16 _device; // Up to 64K possible devices - uint16 _channel : 14; // 2^14 possible channel per Device - uint16 _type : 2; // 2 bits to store the Type directly in the ID - }; - uint32 _id = 0; // by default Input is 0 meaning invalid - }; - - bool isValid() const { return (_id != 0); } - - uint16 getDevice() const { return _device; } - uint16 getChannel() const { return _channel; } - uint32 getID() const { return _id; } - ChannelType getType() const { return (ChannelType) _type; } - - void setDevice(uint16 device) { _device = device; } - void setChannel(uint16 channel) { _channel = channel; } - void setType(uint16 type) { _type = type; } - void setID(uint32 ID) { _id = ID; } - - bool isButton() const { return getType() == ChannelType::BUTTON; } - bool isAxis() const { return getType() == ChannelType::AXIS; } - 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. - explicit Input() : _id(0) {} - explicit Input(uint32 id) : _id(id) {} - explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {} - Input(const Input& src) : _id(src._id) {} - Input& operator = (const Input& src) { _id = src._id; return (*this); } - bool operator ==(const Input& right) const { return _id == right._id; } - bool operator < (const Input& src) const { return _id < src._id; } - }; - - // Modifiers are just button inputID - typedef std::vector< Input > Modifiers; - - class PoseValue { - public: - glm::vec3 _translation{ 0.0f }; - glm::quat _rotation; - bool _valid; - - 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 PoseGetter; - typedef QPair InputPair; - typedef std::function ()> AvailableInputGetter; - typedef std::function ResetBindings; - - typedef QVector AvailableInput; - - class DeviceProxy { - public: - DeviceProxy(QString name) { _name = name; } - const QString& getName() const { return _name; } - - QString _name; - ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; - 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; }; - - typedef std::shared_ptr Pointer; - }; - // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device. - uint16 getFreeDeviceID() { return _nextFreeDeviceID++; } - bool registerDevice(uint16 deviceID, const DeviceProxy::Pointer& device); - bool registerStandardDevice(const DeviceProxy::Pointer& device) { _standardDevice = device; return true; } - DeviceProxy::Pointer getDeviceProxy(const Input& input); - QString getDeviceName(uint16 deviceID); - QVector getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); } - void resetAllDeviceBindings(); - void resetDevice(uint16 deviceID); - int findDevice(QString name); - QVector getDeviceNames(); - - // Actions are the output channels of the Mapper, that's what the InputChannel map to - // For now the Actions are hardcoded, this is bad, but we will fix that in the near future - enum Action { - LONGITUDINAL_BACKWARD = 0, - LONGITUDINAL_FORWARD, - - LATERAL_LEFT, - LATERAL_RIGHT, - - VERTICAL_DOWN, - VERTICAL_UP, - - YAW_LEFT, - YAW_RIGHT, - - PITCH_DOWN, - PITCH_UP, - - BOOM_IN, - BOOM_OUT, - - LEFT_HAND, - RIGHT_HAND, - - LEFT_HAND_CLICK, - RIGHT_HAND_CLICK, - - SHIFT, - - ACTION1, - ACTION2, - - CONTEXT_MENU, - TOGGLE_MUTE, - - NUM_ACTIONS, - }; - - std::vector _actionNames = std::vector(NUM_ACTIONS); - void createActionNames(); - - QVector getAllActions() const; - QString getActionName(Action action) const { return UserInputMapper::_actionNames[(int) action]; } - float getActionState(Action action) const { return _actionStates[action]; } - PoseValue getPoseState(Action action) const { return _poseStates[action]; } - int findAction(const QString& actionName) const; - QVector getActionNames() const; - void assignDefaulActionScales(); - - // Add input channel to the mapper and check that all the used channels are registered. - // Return true if theinput channel is created correctly, false either - bool addInputChannel(Action action, const Input& input, float scale = 1.0f); - bool addInputChannel(Action action, const Input& input, const Input& modifer, float scale = 1.0f); - - // Under the hood, the input channels are organized in map sorted on the _output - // The InputChannel class is just the full values describing the input channel in one object - class InputChannel { - public: - Input _input; - Input _modifier = Input(); // make it invalid by default, meaning no modifier - Action _action = LONGITUDINAL_BACKWARD; - float _scale = 0.0f; - - Input getInput() const { return _input; } - Input getModifier() const { return _modifier; } - Action getAction() const { return _action; } - float getScale() const { return _scale; } - - void setInput(Input input) { _input = input; } - void setModifier(Input modifier) { _modifier = modifier; } - void setAction(Action action) { _action = action; } - void setScale(float scale) { _scale = scale; } - - InputChannel() {} - InputChannel(const Input& input, const Input& modifier, Action action, float scale = 1.0f) : - _input(input), _modifier(modifier), _action(action), _scale(scale) {} - InputChannel(const InputChannel& src) : InputChannel(src._input, src._modifier, src._action, src._scale) {} - InputChannel& operator = (const InputChannel& src) { _input = src._input; _modifier = src._modifier; _action = src._action; _scale = src._scale; return (*this); } - bool operator ==(const InputChannel& right) const { return _input == right._input && _modifier == right._modifier && _action == right._action && _scale == right._scale; } - bool hasModifier() { return _modifier.isValid(); } - }; - typedef std::vector< InputChannel > InputChannels; - - // Add a bunch of input channels, return the true number of channels that successfully were added - int addInputChannels(const InputChannels& channels); - // Remove the first found instance of the input channel from the input mapper, true if found - bool removeInputChannel(InputChannel channel); - void removeAllInputChannels(); - void removeAllInputChannelsForDevice(uint16 device); - void removeDevice(int device); - //Grab all the input channels currently in use, return the number - int getInputChannels(InputChannels& channels) const; - QVector getAllInputsForDevice(uint16 device); - QVector getInputChannelsForAction(UserInputMapper::Action action); - std::multimap getActionToInputsMap() { return _actionToInputsMap; } - - // 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(); - - typedef std::map DevicesMap; - DevicesMap getDevices() { return _registeredDevices; } - - uint16 getStandardDeviceID() const { return _standardDeviceID; } - DeviceProxy::Pointer getStandardDevice() { return _standardDevice; } - -signals: - void actionEvent(int action, float state); - - -protected: - void registerStandardDevice(); - uint16 _standardDeviceID = 0; - DeviceProxy::Pointer _standardDevice; - StandardControllerPointer _standardController; - - DevicesMap _registeredDevices; - uint16 _nextFreeDeviceID = 1; - - typedef std::map InputToMoModifiersMap; - InputToMoModifiersMap _inputToModifiersMap; - - typedef std::multimap ActionToInputsMap; - ActionToInputsMap _actionToInputsMap; - - std::vector _actionStates = std::vector(NUM_ACTIONS, 0.0f); - std::vector _actionScales = std::vector(NUM_ACTIONS, 1.0f); - std::vector _lastActionStates = std::vector(NUM_ACTIONS, 0.0f); - std::vector _poseStates = std::vector(NUM_ACTIONS); - - glm::mat4 _sensorToWorldMat; -}; - -Q_DECLARE_METATYPE(UserInputMapper::InputPair) -Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(UserInputMapper::Input) -Q_DECLARE_METATYPE(UserInputMapper::InputChannel) -Q_DECLARE_METATYPE(QVector) -Q_DECLARE_METATYPE(UserInputMapper::Action) -Q_DECLARE_METATYPE(QVector) - -#endif // hifi_UserInputMapper_h diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp index bb8267b616..e90006e014 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.cpp @@ -12,15 +12,19 @@ #include "ViveControllerManager.h" #include - -#include "GeometryCache.h" +#include +#include #include #include #include #include -#include "NumericalConstants.h" +#include #include -#include "UserActivityLogger.h" +#include + +#include + +#include #ifdef Q_OS_WIN extern vr::IVRSystem* _hmd; @@ -29,18 +33,6 @@ 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.0762f; // three inches const QString CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; @@ -52,15 +44,17 @@ const QString MENU_NAME = "Vive Controllers"; const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; const QString RENDER_CONTROLLERS = "Render Hand Controllers"; +static std::shared_ptr instance; + ViveControllerManager::ViveControllerManager() : - InputDevice("SteamVR Controller"), + InputDevice("Vive"), _trackedControllers(0), _modelLoaded(false), _leftHandRenderID(0), _rightHandRenderID(0), _renderControllers(false) { - + instance = std::shared_ptr(this); } bool ViveControllerManager::isSupported() const { @@ -79,6 +73,7 @@ bool ViveControllerManager::isSupported() const { } void ViveControllerManager::activate() { + InputPlugin::activate(); #ifdef Q_OS_WIN CONTAINER->addMenu(MENU_PATH); CONTAINER->addMenuItem(MENU_PATH, RENDER_CONTROLLERS, @@ -140,9 +135,16 @@ void ViveControllerManager::activate() { _renderControllers = true; } #endif + + // unregister with UserInputMapper + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(instance); + _registeredWithInputMapper = true; } void ViveControllerManager::deactivate() { + InputPlugin::deactivate(); + #ifdef Q_OS_WIN CONTAINER->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); CONTAINER->removeMenu(MENU_PATH); @@ -155,6 +157,11 @@ void ViveControllerManager::deactivate() { } _poseStateMap.clear(); #endif + + // unregister with UserInputMapper + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(_deviceID); + _registeredWithInputMapper = false; } void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePointer scene, render::PendingChanges pendingChanges) { @@ -170,8 +177,8 @@ void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePoint //pendingChanges.updateItem(_leftHandRenderID, ); - UserInputMapper::PoseValue leftHand = _poseStateMap[makeInput(JointChannel::LEFT_HAND).getChannel()]; - UserInputMapper::PoseValue rightHand = _poseStateMap[makeInput(JointChannel::RIGHT_HAND).getChannel()]; + controller::Pose leftHand = _poseStateMap[controller::StandardPoseChannel::LEFT_HAND]; + controller::Pose rightHand = _poseStateMap[controller::StandardPoseChannel::RIGHT_HAND]; gpu::doInBatch(args->_context, [=](gpu::Batch& batch) { auto geometryCache = DependencyManager::get(); @@ -183,21 +190,20 @@ void ViveControllerManager::updateRendering(RenderArgs* args, render::ScenePoint //batch._glBindTexture(GL_TEXTURE_2D, _uexture); if (leftHand.isValid()) { - renderHand(leftHand, batch, LEFT_HAND); + renderHand(leftHand, batch, 1); } if (rightHand.isValid()) { - renderHand(rightHand, batch, RIGHT_HAND); + renderHand(rightHand, batch, -1); } }); } } -void ViveControllerManager::renderHand(UserInputMapper::PoseValue pose, gpu::Batch& batch, int index) { - auto userInputMapper = DependencyManager::get(); +void ViveControllerManager::renderHand(const controller::Pose& pose, gpu::Batch& batch, int sign) { + 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 : -1; 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); @@ -248,6 +254,7 @@ void ViveControllerManager::update(float deltaTime, bool jointsCaptured) { } numTrackedControllers++; + bool left = numTrackedControllers == 2; const mat4& mat = _trackedDevicePoseMat4[device]; @@ -261,26 +268,30 @@ void ViveControllerManager::update(float deltaTime, bool jointsCaptured) { //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); + for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { + auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); + bool pressed = 0 != (controllerState.ulButtonPressed & mask); + handleButtonEvent(i, pressed, left); + } + for (uint32_t i = 0; i < vr::k_unControllerStateAxisCount; i++) { + handleAxisEvent(i, controllerState.rAxis[i].x, controllerState.rAxis[i].y, left); } } } - auto userInputMapper = DependencyManager::get(); + auto userInputMapper = DependencyManager::get(); if (numTrackedControllers == 0) { - if (_deviceID != 0) { + if (_registeredWithInputMapper) { userInputMapper->removeDevice(_deviceID); - _deviceID = 0; + _registeredWithInputMapper = false; _poseStateMap.clear(); } } if (_trackedControllers == 0 && numTrackedControllers > 0) { - registerToUserInputMapper(*userInputMapper); - assignDefaultInputMapping(*userInputMapper); + userInputMapper->registerDevice(instance); + _registeredWithInputMapper = true; UserActivityLogger::getInstance().connectedDevice("spatial_controller", "steamVR"); } @@ -293,33 +304,45 @@ void ViveControllerManager::focusOutEvent() { _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; +// These functions do translation from the Steam IDs to the standard controller IDs +void ViveControllerManager::handleAxisEvent(uint32_t axis, float x, float y, bool left) { +#ifdef Q_OS_WIN + //FIX ME? It enters here every frame: probably we want to enter only if an event occurs + axis += vr::k_EButton_Axis0; + using namespace controller; + if (axis == vr::k_EButton_SteamVR_Touchpad) { + _axisStateMap[left ? LX : RX] = x; + _axisStateMap[left ? LY : RY] = y; + } else if (axis == vr::k_EButton_SteamVR_Trigger) { + _axisStateMap[left ? LT : RT] = x; } +#endif } -void ViveControllerManager::handleButtonEvent(uint64_t buttons, int index) { - if (buttons & VR_BUTTON_A) { - _buttonPressedMap.insert(makeInput(BUTTON_A, index).getChannel()); +// These functions do translation from the Steam IDs to the standard controller IDs +void ViveControllerManager::handleButtonEvent(uint32_t button, bool pressed, bool left) { +#ifdef Q_OS_WIN + if (!pressed) { + return; } - 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()); + + if (button == vr::k_EButton_ApplicationMenu) { + _buttonPressedMap.insert(left ? controller::LEFT_PRIMARY_THUMB : controller::RIGHT_PRIMARY_THUMB); + } else if (button == vr::k_EButton_Grip) { + // Tony says these are harder to reach, so make them the meta buttons + _buttonPressedMap.insert(left ? controller::LB : controller::RB); + } else if (button == vr::k_EButton_SteamVR_Trigger) { + _buttonPressedMap.insert(left ? controller::LT : controller::RT); + } else if (button == vr::k_EButton_SteamVR_Touchpad) { + _buttonPressedMap.insert(left ? controller::LS : controller::RS); + } else if (button == vr::k_EButton_System) { + //FIX ME: not able to ovrewrite the behaviour of this button + _buttonPressedMap.insert(left ? controller::LEFT_SECONDARY_THUMB : controller::RIGHT_SECONDARY_THUMB); } +#endif } -void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) { +void ViveControllerManager::handlePoseEvent(const mat4& mat, bool left) { glm::vec3 position = extractTranslation(mat); glm::quat rotation = glm::quat_cast(mat); @@ -375,7 +398,7 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) { const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); - float sign = (index == LEFT_HAND) ? -1.0f : 1.0f; + float sign = left ? -1.0f : 1.0f; const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f)); const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f)); const glm::quat offset = glm::inverse(signedQuaterZ * eighthX); @@ -383,92 +406,79 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) { position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET); - _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); + _poseStateMap[left ? controller::LEFT_HAND : controller::RIGHT_HAND] = controller::Pose(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")); +controller::Input::NamedVector ViveControllerManager::getAvailableInputs() const { + using namespace controller; + QVector availableInputs{ + // Trackpad analogs + makePair(LX, "LX"), + makePair(LY, "LY"), + makePair(RX, "RX"), + makePair(RY, "RY"), + // trigger analogs + makePair(LT, "LT"), + makePair(RT, "RT"), - 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")); + makePair(LB, "LB"), + makePair(RB, "RB"), - - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_HAND), "Right Hand")); + makePair(LS, "LS"), + makePair(RS, "RS"), + makePair(LEFT_HAND, "LeftHand"), + makePair(RIGHT_HAND, "RightHand"), - 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; + makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), + makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), + makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), + makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), }; - proxy->resetDeviceBindings = [this, &mapper] () -> bool { - mapper.removeAllInputChannelsForDevice(_deviceID); - this->assignDefaultInputMapping(mapper); - return true; - }; - mapper.registerDevice(_deviceID, proxy); + + //availableInputs.append(Input::NamedPair(makeInput(BUTTON_A, 0), "Left Button A")); + //availableInputs.append(Input::NamedPair(makeInput(GRIP_BUTTON, 0), "Left Grip Button")); + //availableInputs.append(Input::NamedPair(makeInput(TRACKPAD_BUTTON, 0), "Left Trackpad Button")); + //availableInputs.append(Input::NamedPair(makeInput(TRIGGER_BUTTON, 0), "Left Trigger Button")); + //availableInputs.append(Input::NamedPair(makeInput(BACK_TRIGGER, 0), "Left Back Trigger")); + //availableInputs.append(Input::NamedPair(makeInput(RIGHT_HAND), "Right Hand")); + //availableInputs.append(Input::NamedPair(makeInput(BUTTON_A, 1), "Right Button A")); + //availableInputs.append(Input::NamedPair(makeInput(GRIP_BUTTON, 1), "Right Grip Button")); + //availableInputs.append(Input::NamedPair(makeInput(TRACKPAD_BUTTON, 1), "Right Trackpad Button")); + //availableInputs.append(Input::NamedPair(makeInput(TRIGGER_BUTTON, 1), "Right Trigger Button")); + //availableInputs.append(Input::NamedPair(makeInput(BACK_TRIGGER, 1), "Right Back Trigger")); + + return availableInputs; } -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(BACK_TRIGGER, 0)); - mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BACK_TRIGGER, 1)); - - // Hands - mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND)); - mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND)); +QString ViveControllerManager::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/vive.json"; + return MAPPING_JSON; } -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); -} +//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(BACK_TRIGGER, 0)); +// mapper.addInputChannel(UserInputMapper::RIGHT_HAND_CLICK, makeInput(BACK_TRIGGER, 1)); +// +// // Hands +// mapper.addInputChannel(UserInputMapper::LEFT_HAND, makeInput(LEFT_HAND)); +// mapper.addInputChannel(UserInputMapper::RIGHT_HAND, makeInput(RIGHT_HAND)); +//} diff --git a/libraries/input-plugins/src/input-plugins/ViveControllerManager.h b/libraries/input-plugins/src/input-plugins/ViveControllerManager.h index 5cae8daaf4..938f2f9ba9 100644 --- a/libraries/input-plugins/src/input-plugins/ViveControllerManager.h +++ b/libraries/input-plugins/src/input-plugins/ViveControllerManager.h @@ -19,35 +19,14 @@ #include #include -#include "InputDevice.h" +#include #include "InputPlugin.h" #include #include -class ViveControllerManager : public InputPlugin, public InputDevice { +class ViveControllerManager : public InputPlugin, public controller::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 @@ -62,8 +41,8 @@ public: 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 controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; @@ -71,16 +50,12 @@ public: 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 renderHand(const controller::Pose& pose, gpu::Batch& batch, int sign); - void handleButtonEvent(uint64_t buttons, int index); - void handleAxisEvent(Axis axis, float x, float y, int index); - void handlePoseEvent(const mat4& mat, int index); + void handleButtonEvent(uint32_t button, bool pressed, bool left); + void handleAxisEvent(uint32_t axis, float x, float y, bool left); + void handlePoseEvent(const mat4& mat, bool left); int _trackedControllers; @@ -94,6 +69,9 @@ private: bool _renderControllers; static const QString NAME; + + bool _registeredWithInputMapper { false }; + }; #endif // hifi__ViveControllerManager diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 7062942c51..a46a9693ac 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,7 +38,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP; + return VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP_BIS; case PacketType::AvatarData: case PacketType::BulkAvatarData: default: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 64e0a9d8e4..553c12f8e3 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -144,5 +144,6 @@ const PacketVersion VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER = 44; const PacketVersion VERSION_ENTITIES_PROTOCOL_CHANNELS = 45; const PacketVersion VERSION_ENTITIES_ANIMATION_PROPERTIES_GROUP = 46; const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP = 47; +const PacketVersion VERSION_ENTITIES_KEYLIGHT_PROPERTIES_GROUP_BIS = 48; #endif // hifi_PacketHeaders_h diff --git a/libraries/plugins/src/plugins/Plugin.h b/libraries/plugins/src/plugins/Plugin.h index 68e012b8e1..f53d309e97 100644 --- a/libraries/plugins/src/plugins/Plugin.h +++ b/libraries/plugins/src/plugins/Plugin.h @@ -33,9 +33,18 @@ public: virtual void deinit(); /// Called when a plugin is being activated for use. May be called multiple times. - virtual void activate() = 0; + virtual void activate() { + _active = true; + } + /// Called when a plugin is no longer being used. May be called multiple times. - virtual void deactivate() = 0; + virtual void deactivate() { + _active = false; + } + + virtual bool isActive() { + return _active; + } /** * Called by the application during it's idle phase. If the plugin needs to do @@ -48,6 +57,7 @@ public: virtual void loadSettings() {} protected: + bool _active{ false }; static PluginContainer* CONTAINER; static QString UNKNOWN_PLUGIN_ID; diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index cfe0afd220..5e3d135034 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -1,3 +1,3 @@ set(TARGET_NAME script-engine) setup_hifi_library(Gui Network Script WebSockets Widgets) -link_hifi_libraries(shared networking octree gpu procedural model model-networking fbx entities animation audio physics) +link_hifi_libraries(shared networking octree gpu procedural model model-networking fbx entities controllers animation audio physics) diff --git a/libraries/script-engine/src/AbstractControllerScriptingInterface.h b/libraries/script-engine/src/AbstractControllerScriptingInterface.h deleted file mode 100644 index d6a6b51b62..0000000000 --- a/libraries/script-engine/src/AbstractControllerScriptingInterface.h +++ /dev/null @@ -1,125 +0,0 @@ -// -// AbstractControllerScriptingInterface.h -// libraries/script-engine/src -// -// Created by Brad Hefta-Gaub on 12/17/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 -// - -#ifndef hifi_AbstractControllerScriptingInterface_h -#define hifi_AbstractControllerScriptingInterface_h - -#include - -#include -#include - -#include "HFActionEvent.h" -#include "KeyEvent.h" -#include "MouseEvent.h" -#include "SpatialEvent.h" -#include "TouchEvent.h" -#include "WheelEvent.h" - -class ScriptEngine; - -class AbstractInputController : public QObject { - Q_OBJECT - -public: - typedef unsigned int Key; - - virtual void update() = 0; - - virtual Key getKey() const = 0; - -public slots: - - virtual bool isActive() const = 0; - virtual glm::vec3 getAbsTranslation() const = 0; - virtual glm::quat getAbsRotation() const = 0; - virtual glm::vec3 getLocTranslation() const = 0; - virtual glm::quat getLocRotation() const = 0; - -signals: - void spatialEvent(const SpatialEvent& event); - -}; - -/// handles scripting of input controller commands from JS -class AbstractControllerScriptingInterface : public QObject { - Q_OBJECT - -public slots: - virtual void registerControllerTypes(ScriptEngine* engine) = 0; - - virtual bool isPrimaryButtonPressed() const = 0; - virtual glm::vec2 getPrimaryJoystickPosition() const = 0; - - virtual int getNumberOfButtons() const = 0; - virtual bool isButtonPressed(int buttonIndex) const = 0; - - virtual int getNumberOfTriggers() const = 0; - virtual float getTriggerValue(int triggerIndex) const = 0; - - virtual int getNumberOfJoysticks() const = 0; - virtual glm::vec2 getJoystickPosition(int joystickIndex) const = 0; - - virtual int getNumberOfSpatialControls() const = 0; - virtual glm::vec3 getSpatialControlPosition(int controlIndex) const = 0; - virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const = 0; - virtual glm::vec3 getSpatialControlNormal(int controlIndex) const = 0; - virtual glm::quat getSpatialControlRawRotation(int controlIndex) const = 0; - - virtual void captureKeyEvents(const KeyEvent& event) = 0; - virtual void releaseKeyEvents(const KeyEvent& event) = 0; - - virtual void captureMouseEvents() = 0; - virtual void releaseMouseEvents() = 0; - - virtual void captureTouchEvents() = 0; - virtual void releaseTouchEvents() = 0; - - virtual void captureWheelEvents() = 0; - virtual void releaseWheelEvents() = 0; - - virtual void captureActionEvents() = 0; - virtual void releaseActionEvents() = 0; - - virtual void captureJoystick(int joystickIndex) = 0; - virtual void releaseJoystick(int joystickIndex) = 0; - - virtual glm::vec2 getViewportDimensions() const = 0; - - - virtual AbstractInputController* createInputController( const QString& category, const QString& tracker ) = 0; - -signals: - void keyPressEvent(const KeyEvent& event); - void keyReleaseEvent(const KeyEvent& event); - - void actionStartEvent(const HFActionEvent& event); - void actionEndEvent(const HFActionEvent& event); - - void backStartEvent(); - void backEndEvent(); - - void mouseMoveEvent(const MouseEvent& event, unsigned int deviceID = 0); - void mousePressEvent(const MouseEvent& event, unsigned int deviceID = 0); - void mouseDoublePressEvent(const MouseEvent& event, unsigned int deviceID = 0); - void mouseReleaseEvent(const MouseEvent& event, unsigned int deviceID = 0); - - void touchBeginEvent(const TouchEvent& event); - void touchEndEvent(const TouchEvent& event); - void touchUpdateEvent(const TouchEvent& event); - - void wheelEvent(const WheelEvent& event); - - void actionEvent(int action, float state); - -}; - -#endif // hifi_AbstractControllerScriptingInterface_h diff --git a/libraries/script-engine/src/AbstractScriptingServicesInterface.h b/libraries/script-engine/src/AbstractScriptingServicesInterface.h index 9d24531b60..de18338fca 100644 --- a/libraries/script-engine/src/AbstractScriptingServicesInterface.h +++ b/libraries/script-engine/src/AbstractScriptingServicesInterface.h @@ -12,21 +12,13 @@ #ifndef hifi_AbstractScriptingServicesInterface_h #define hifi_AbstractScriptingServicesInterface_h -class AbstractControllerScriptingInterface; -class Transform; class ScriptEngine; -class QThread; /// Interface provided by Application to other objects that need access to scripting services of the application class AbstractScriptingServicesInterface { public: - - /// Returns the controller interface for the application - virtual AbstractControllerScriptingInterface* getControllerScriptingInterface() = 0; - /// Registers application specific services with a script engine. virtual void registerScriptEngineWithApplicationServices(ScriptEngine* scriptEngine) = 0; - }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b416b58910..c6ac6495b1 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -28,7 +28,9 @@ #include #include -#include "AnimationObject.h" +#include +#include + #include "ArrayBufferViewClass.h" #include "BatchLoader.h" #include "DataViewClass.h" @@ -76,13 +78,16 @@ void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) { out = qobject_cast(object.toQObject()); } -QScriptValue inputControllerToScriptValue(QScriptEngine *engine, AbstractInputController* const &in) { +QScriptValue inputControllerToScriptValue(QScriptEngine *engine, controller::InputController* const &in) { return engine->newQObject(in); } -void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { - out = qobject_cast(object.toQObject()); + +void inputControllerFromScriptValue(const QScriptValue &object, controller::InputController* &out) { + out = qobject_cast(object.toQObject()); } + + static bool hasCorrectSyntax(const QScriptProgram& program) { const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { @@ -114,16 +119,13 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName return false; } -ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, - AbstractControllerScriptingInterface* controllerScriptingInterface, bool wantSignals) : - +ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) : _scriptContents(scriptContents), _isFinished(false), _isRunning(false), _isInitialized(false), _timerFunctionMap(), _wantSignals(wantSignals), - _controllerScriptingInterface(controllerScriptingInterface), _fileNameString(fileNameString), _isUserLoaded(false), _isReloading(false), @@ -285,6 +287,27 @@ void ScriptEngine::errorInLoadingScript(const QUrl& url) { } } +// Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of +// callAnimationStateHandler requires that the type be registered. +// These two are meaningful, if we ever do want to use them... +static QScriptValue animVarMapToScriptValue(QScriptEngine* engine, const AnimVariantMap& parameters) { + QStringList unused; + return parameters.animVariantMapToScriptValue(engine, unused, false); +} +static void animVarMapFromScriptValue(const QScriptValue& value, AnimVariantMap& parameters) { + parameters.animVariantMapFromScriptValue(value); +} +// ... while these two are not. But none of the four are ever used. +static QScriptValue resultHandlerToScriptValue(QScriptEngine* engine, const AnimVariantResultHandler& resultHandler) { + qCCritical(scriptengine) << "Attempt to marshall result handler to javascript"; + assert(false); + return QScriptValue(); +} +static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantResultHandler& resultHandler) { + qCCritical(scriptengine) << "Attempt to marshall result handler from javascript"; + assert(false); +} + void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once @@ -337,27 +360,26 @@ void ScriptEngine::init() { registerGlobalObject("Script", this); registerGlobalObject("Audio", &AudioScriptingInterface::getInstance()); - registerGlobalObject("Controller", _controllerScriptingInterface); registerGlobalObject("Entities", entityScriptingInterface.data()); registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Uuid", &_uuidLibrary); registerGlobalObject("AnimationCache", DependencyManager::get().data()); + qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue); + qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue); // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); - if (_controllerScriptingInterface) { - _controllerScriptingInterface->registerControllerTypes(this); - } - - + auto scriptingInterface = DependencyManager::get(); + registerGlobalObject("Controller", scriptingInterface.data()); + UserInputMapper::registerControllerTypes(this); } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING - qDebug() << "*** WARNING *** ScriptEngine::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; + qDebug() << "*** WARNING *** ScriptEngine::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; #endif QMetaObject::invokeMethod(this, "registerValue", Q_ARG(const QString&, valueName), @@ -743,6 +765,27 @@ void ScriptEngine::stop() { } } +// Other threads can invoke this through invokeMethod, which causes the callback to be asynchronously executed in this script's thread. +void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler) { + if (QThread::currentThread() != thread()) { +#ifdef THREAD_DEBUGGING + qDebug() << "*** WARNING *** ScriptEngine::callAnimationStateHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name; +#endif + QMetaObject::invokeMethod(this, "callAnimationStateHandler", + Q_ARG(QScriptValue, callback), + Q_ARG(AnimVariantMap, parameters), + Q_ARG(QStringList, names), + Q_ARG(bool, useNames), + Q_ARG(AnimVariantResultHandler, resultHandler)); + return; + } + QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames); + QScriptValueList callingArguments; + callingArguments << javascriptParameters; + QScriptValue result = callback.call(QScriptValue(), callingArguments); + resultHandler(result); +} + void ScriptEngine::timerFired() { QTimer* callingTimer = reinterpret_cast(sender()); QScriptValue timerFunction = _timerFunctionMap.value(callingTimer); @@ -928,9 +971,8 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin if (QThread::currentThread() != thread()) { qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; assert(false); - return; + return ; } - if (!_registeredHandlers.contains(entityID)) { return; } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 89d651930b..43fbdc1b0e 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -21,13 +21,14 @@ #include #include +#include #include #include #include #include #include -#include "AbstractControllerScriptingInterface.h" +#include "MouseEvent.h" #include "ArrayBufferClass.h" #include "AudioScriptingInterface.h" #include "Quat.h" @@ -53,7 +54,6 @@ class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScr public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, const QString& fileNameString = QString(""), - AbstractControllerScriptingInterface* controllerScriptingInterface = NULL, bool wantSignals = true); ~ScriptEngine(); @@ -142,6 +142,9 @@ public: // NOTE - this is used by the TypedArray implemetation. we need to review this for thread safety ArrayBufferClass* getArrayBufferClass() { return _arrayBufferClass; } +public slots: + void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); + signals: void scriptLoaded(const QString& scriptFilename); void errorLoadingScript(const QString& scriptFilename); @@ -181,8 +184,7 @@ private: QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); - - AbstractControllerScriptingInterface* _controllerScriptingInterface; + QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; diff --git a/libraries/shared/src/DependencyManager.h b/libraries/shared/src/DependencyManager.h index 01b755fdd0..db41e72e1e 100644 --- a/libraries/shared/src/DependencyManager.h +++ b/libraries/shared/src/DependencyManager.h @@ -51,7 +51,10 @@ public: template static QSharedPointer set(Args&&... args); - + + template + static QSharedPointer set(Args&&... args); + template static void destroy(); @@ -89,13 +92,26 @@ QSharedPointer DependencyManager::get() { template QSharedPointer DependencyManager::set(Args&&... args) { static size_t hashCode = _manager.getHashCode(); - + QSharedPointer& instance = _manager.safeGet(hashCode); instance.clear(); // Clear instance before creation of new one to avoid edge cases QSharedPointer newInstance(new T(args...), &T::customDeleter); QSharedPointer storedInstance = qSharedPointerCast(newInstance); instance.swap(storedInstance); - + + return newInstance; +} + +template +QSharedPointer DependencyManager::set(Args&&... args) { + static size_t hashCode = _manager.getHashCode(); + + QSharedPointer& instance = _manager.safeGet(hashCode); + instance.clear(); // Clear instance before creation of new one to avoid edge cases + QSharedPointer newInstance(new I(args...), &I::customDeleter); + QSharedPointer storedInstance = qSharedPointerCast(newInstance); + instance.swap(storedInstance); + return newInstance; } diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index cc3519e43e..7dd1aceb3d 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -12,9 +12,12 @@ #include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "LogHandler.h" @@ -24,13 +27,14 @@ LogHandler& LogHandler::getInstance() { } LogHandler::LogHandler() : - _shouldOutputPID(false) + _shouldOutputProcessID(false), + _shouldOutputThreadID(false) { // setup our timer to flush the verbose logs every 5 seconds QTimer* logFlushTimer = new QTimer(this); connect(logFlushTimer, &QTimer::timeout, this, &LogHandler::flushRepeatedMessages); logFlushTimer->start(VERBOSE_LOG_INTERVAL_SECONDS * 1000); - + // when the log handler is first setup we should print our timezone QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t"); printf("%s\n", qPrintable(timezoneString)); @@ -57,51 +61,55 @@ const char* stringForLogType(LogMsgType msgType) { const QString DATE_STRING_FORMAT = "MM/dd hh:mm:ss"; void LogHandler::flushRepeatedMessages() { + QMutexLocker locker(&_repeatedMessageLock); QHash::iterator message = _repeatMessageCountHash.begin(); while (message != _repeatMessageCountHash.end()) { - + if (message.value() > 0) { QString repeatMessage = QString("%1 repeated log entries matching \"%2\" - Last entry: \"%3\"") .arg(message.value()).arg(message.key()).arg(_lastRepeatedMessage.value(message.key())); - + QMessageLogContext emptyContext; printMessage(LogSuppressed, emptyContext, repeatMessage); } - + _lastRepeatedMessage.remove(message.key()); message = _repeatMessageCountHash.erase(message); } } QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& context, const QString& message) { - + if (message.isEmpty()) { return QString(); } - + if (type == LogDebug) { // for debug messages, check if this matches any of our regexes for repeated log messages + QMutexLocker locker(&_repeatedMessageLock); foreach(const QString& regexString, getInstance()._repeatedMessageRegexes) { QRegExp repeatRegex(regexString); if (repeatRegex.indexIn(message) != -1) { - + if (!_repeatMessageCountHash.contains(regexString)) { // we have a match but didn't have this yet - output the first one _repeatMessageCountHash[regexString] = 0; - + // break the foreach so we output the first match break; } else { // we have a match - add 1 to the count of repeats for this message and set this as the last repeated message _repeatMessageCountHash[regexString] += 1; _lastRepeatedMessage[regexString] = message; - + // return out, we're not printing this one return QString(); } } } - + } + if (type == LogDebug) { + QMutexLocker locker(&_onlyOnceMessageLock); // see if this message is one we should only print once foreach(const QString& regexString, getInstance()._onlyOnceMessageRegexes) { QRegExp onlyOnceRegex(regexString); @@ -118,23 +126,27 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont } } } - + // log prefix is in the following format - // [TIMESTAMP] [DEBUG] [PID] [TARGET] logged string - + // [TIMESTAMP] [DEBUG] [PID] [TID] [TARGET] logged string + QString prefixString = QString("[%1]").arg(QDateTime::currentDateTime().toString(DATE_STRING_FORMAT)); - + prefixString.append(QString(" [%1]").arg(stringForLogType(type))); - - if (_shouldOutputPID) { + + if (_shouldOutputProcessID) { prefixString.append(QString(" [%1]").arg(QCoreApplication::instance()->applicationPid())); - } - + + if (_shouldOutputThreadID) { + size_t threadID = (size_t)QThread::currentThreadId(); + prefixString.append(QString(" [%1]").arg(threadID)); + } + if (!_targetName.isEmpty()) { prefixString.append(QString(" [%1]").arg(_targetName)); } - + QString logMessage = QString("%1 %2").arg(prefixString, message.split("\n").join("\n" + prefixString + " ")); fprintf(stdout, "%s\n", qPrintable(logMessage)); return logMessage; @@ -143,3 +155,13 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont void LogHandler::verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { getInstance().printMessage((LogMsgType) type, context, message); } + +const QString& LogHandler::addRepeatedMessageRegex(const QString& regexString) { + QMutexLocker locker(&_repeatedMessageLock); + return *_repeatedMessageRegexes.insert(regexString); +} + +const QString& LogHandler::addOnlyOnceMessageRegex(const QString& regexString) { + QMutexLocker locker(&_onlyOnceMessageLock); + return *_onlyOnceMessageRegexes.insert(regexString); +} diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index 6af721f96c..a74a6287d7 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -13,11 +13,11 @@ #ifndef hifi_LogHandler_h #define hifi_LogHandler_h -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include const int VERBOSE_LOG_INTERVAL_SECONDS = 5; @@ -34,34 +34,38 @@ class LogHandler : public QObject { Q_OBJECT public: static LogHandler& getInstance(); - + /// sets the target name to output via the verboseMessageHandler, called once before logging begins /// \param targetName the desired target name to output in logs void setTargetName(const QString& targetName) { _targetName = targetName; } - - void setShouldOutputPID(bool shouldOutputPID) { _shouldOutputPID = shouldOutputPID; } - + + void setShouldOutputProcessID(bool shouldOutputProcessID) { _shouldOutputProcessID = shouldOutputProcessID; } + void setShouldOutputThreadID(bool shouldOutputThreadID) { _shouldOutputThreadID = shouldOutputThreadID; } + QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message); - + /// a qtMessageHandler that can be hooked up to a target that links to Qt /// prints various process, message type, and time information static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); - - const QString& addRepeatedMessageRegex(const QString& regexString) { return *_repeatedMessageRegexes.insert(regexString); } - const QString& addOnlyOnceMessageRegex(const QString& regexString) { return *_onlyOnceMessageRegexes.insert(regexString); } + + const QString& addRepeatedMessageRegex(const QString& regexString); + const QString& addOnlyOnceMessageRegex(const QString& regexString); private: LogHandler(); - + void flushRepeatedMessages(); - + QString _targetName; - bool _shouldOutputPID; + bool _shouldOutputProcessID; + bool _shouldOutputThreadID; QSet _repeatedMessageRegexes; QHash _repeatMessageCountHash; QHash _lastRepeatedMessage; + QMutex _repeatedMessageLock; QSet _onlyOnceMessageRegexes; QHash _onlyOnceMessageCountHash; + QMutex _onlyOnceMessageLock; }; #endif // hifi_LogHandler_h diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index f78c8c47e0..145ec4ec37 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -39,30 +40,29 @@ void usecTimestampNowForceClockSkew(int clockSkew) { ::usecTimestampNowAdjust = clockSkew; } +static qint64 TIME_REFERENCE = 0; // in usec +static std::once_flag usecTimestampNowIsInitialized; +static QElapsedTimer timestampTimer; + quint64 usecTimestampNow(bool wantDebug) { - static bool usecTimestampNowIsInitialized = false; - static qint64 TIME_REFERENCE = 0; // in usec - static QElapsedTimer timestampTimer; - - if (!usecTimestampNowIsInitialized) { - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * 1000; // ms to usec + std::call_once(usecTimestampNowIsInitialized, [&] { + TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec timestampTimer.start(); - usecTimestampNowIsInitialized = true; - } + }); quint64 now; quint64 nsecsElapsed = timestampTimer.nsecsElapsed(); - quint64 usecsElapsed = nsecsElapsed / 1000; // nsec to usec + quint64 usecsElapsed = nsecsElapsed / NSECS_PER_USEC; // nsec to usec // QElapsedTimer may not advance if the CPU has gone to sleep. In which case it // will begin to deviate from real time. We detect that here, and reset if necessary quint64 msecsCurrentTime = QDateTime::currentMSecsSinceEpoch(); - quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / 1000; // usecs to msecs + quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / USECS_PER_MSEC; // usecs to msecs int possibleSkew = msecsEstimate - msecsCurrentTime; - const int TOLERANCE = 10000; // up to 10 seconds of skew is tolerated + const int TOLERANCE = 10 * MSECS_PER_SECOND; // up to 10 seconds of skew is tolerated if (abs(possibleSkew) > TOLERANCE) { // reset our TIME_REFERENCE and timer - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * 1000; // ms to usec + TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec timestampTimer.restart(); now = TIME_REFERENCE + ::usecTimestampNowAdjust; @@ -118,6 +118,13 @@ quint64 usecTimestampNow(bool wantDebug) { return now; } +float secTimestampNow() { + static const auto START_TIME = usecTimestampNow(); + const auto nowUsecs = usecTimestampNow() - START_TIME; + const auto nowMsecs = nowUsecs / USECS_PER_MSEC; + return (float)nowMsecs / MSECS_PER_SECOND; +} + float randFloat() { return (rand() % 10000)/10000.0f; } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 4967778cb4..cd4f734d40 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -65,9 +65,14 @@ inline bool operator!=(const xColor& lhs, const xColor& rhs) // Use a custom User-Agent to avoid ModSecurity filtering, e.g. by hosting providers. const QByteArray HIGH_FIDELITY_USER_AGENT = "Mozilla/5.0 (HighFidelityInterface)"; +// Equivalent to time_t but in usecs instead of secs quint64 usecTimestampNow(bool wantDebug = false); void usecTimestampNowForceClockSkew(int clockSkew); +// Number of seconds expressed since the first call to this function, expressed as a float +// Maximum accuracy in msecs +float secTimestampNow(); + float randFloat(); int randIntInRange (int min, int max); float randFloatInRange (float min,float max); diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index 194a078194..53754ae241 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -40,4 +40,28 @@ private: float ONE_MINUS_WEIGHTING; }; + +template class MovingAverage { +public: + const float WEIGHTING = 1.0f / (float)MAX_NUM_SAMPLES; + const float ONE_MINUS_WEIGHTING = 1.0f - WEIGHTING; + int numSamples{ 0 }; + T average; + + void clear() { + numSamples = 0; + } + + bool isAverageValid() const { return (numSamples > 0); } + + void addSample(T sample) { + if (numSamples > 0) { + average = (sample * WEIGHTING) + (average * ONE_MINUS_WEIGHTING); + } else { + average = sample; + } + numSamples++; + } +}; + #endif // hifi_SimpleMovingAverage_h diff --git a/libraries/shared/src/shared/Factory.h b/libraries/shared/src/shared/Factory.h new file mode 100644 index 0000000000..6f1da6644b --- /dev/null +++ b/libraries/shared/src/shared/Factory.h @@ -0,0 +1,51 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// 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 +#ifndef hifi_Shared_Factory_h +#define hifi_Shared_Factory_h + +#include +#include +#include + +namespace hifi { + + template + class SimpleFactory { + public: + using Pointer = std::shared_ptr; + using Builder = std::function; + using BuilderMap = std::map; + + void registerBuilder(const Key& name, Builder builder) { + // FIXME don't allow name collisions + _builders[name] = builder; + } + + Pointer create(const Key& name) const { + const auto& entryIt = _builders.find(name); + if (entryIt != _builders.end()) { + return (*entryIt).second(); + } + return Pointer(); + } + + template + class Registrar { + public: + Registrar(const Key& name, SimpleFactory& factory) { + factory.registerBuilder(name, [] { return std::make_shared(); }); + } + }; + protected: + BuilderMap _builders; + }; +} + +#endif diff --git a/tests/controllers/CMakeLists.txt b/tests/controllers/CMakeLists.txt new file mode 100644 index 0000000000..d1c8464dd5 --- /dev/null +++ b/tests/controllers/CMakeLists.txt @@ -0,0 +1,19 @@ + +set(TARGET_NAME controllers-test) + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Script Qml) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared gl script-engine plugins render-utils input-plugins display-plugins controllers) + + +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() + +copy_dlls_beside_windows_executable() \ No newline at end of file diff --git a/tests/controllers/qml/main.qml b/tests/controllers/qml/main.qml new file mode 100644 index 0000000000..66060399a6 --- /dev/null +++ b/tests/controllers/qml/main.qml @@ -0,0 +1,22 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +ApplicationWindow { + id: window + visible: true + + Timer { + interval: 50; running: true; repeat: true + onTriggered: { + Controller.update(); + } + } + + + Loader { + id: pageLoader + source: ResourcePath + "TestControllers.qml" + } +} diff --git a/tests/controllers/src/main.cpp b/tests/controllers/src/main.cpp new file mode 100644 index 0000000000..a7b1be15ca --- /dev/null +++ b/tests/controllers/src/main.cpp @@ -0,0 +1,158 @@ +// +// main.cpp +// tests/gpu-test/src +// +// 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +const QString& getResourcesDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return dir; +} + +const QString& getQmlDir() { + static QString dir; + if (dir.isEmpty()) { + dir = getResourcesDir() + "qml/"; + qDebug() << "Qml Path: " << dir; + } + return dir; +} + +const QString& getTestQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/qml/"; + qDebug() << "Qml Test Path: " << dir; + } + return dir; +} + +using namespace controller; + + +class PluginContainerProxy : public QObject, PluginContainer { + Q_OBJECT +public: + PluginContainerProxy() { + Plugin::setContainer(this); + } + virtual ~PluginContainerProxy() {} + virtual void addMenu(const QString& menuName) override {} + virtual void removeMenu(const QString& menuName) override {} + virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; } + virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override {} + virtual bool isOptionChecked(const QString& name) override { return false; } + virtual void setIsOptionChecked(const QString& path, bool checked) override {} + virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = true) override {} + virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override {} + virtual void showDisplayPluginsTools() override {} + virtual void requestReset() override {} + virtual QGLWidget* getPrimarySurface() override { return nullptr; } + virtual bool isForeground() override { return true; } + virtual const DisplayPlugin* getActiveDisplayPlugin() const override { return nullptr; } +}; + +class MyControllerScriptingInterface : public controller::ScriptingInterface { +public: + virtual void registerControllerTypes(QScriptEngine* engine) {}; +}; + + +int main(int argc, char** argv) { + QGuiApplication app(argc, argv); + QQmlApplicationEngine engine; + auto rootContext = engine.rootContext(); + new PluginContainerProxy(); + + // Simulate our application idle loop + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [] { + static float last = secTimestampNow(); + float now = secTimestampNow(); + float delta = now - last; + last = now; + + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { + inputPlugin->pluginUpdate(delta, false); + } + + auto userInputMapper = DependencyManager::get(); + userInputMapper->update(delta); + }); + timer.start(50); + + { + DependencyManager::set(); + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { + QString name = inputPlugin->getName(); + inputPlugin->activate(); + auto userInputMapper = DependencyManager::get(); + if (name == KeyboardMouseDevice::NAME) { + auto keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky + userInputMapper->registerDevice(std::shared_ptr(keyboardMouseDevice)); + } + inputPlugin->pluginUpdate(0, false); + } + rootContext->setContextProperty("Controllers", new MyControllerScriptingInterface()); + } + qDebug() << getQmlDir(); + rootContext->setContextProperty("ResourcePath", getQmlDir()); + engine.setBaseUrl(QUrl::fromLocalFile(getQmlDir())); + engine.addImportPath(getQmlDir()); + engine.load(getTestQmlDir() + "main.qml"); + for (auto pathItem : engine.importPathList()) { + qDebug() << pathItem; + } + app.exec(); + return 0; +} + +#include "main.moc" +