diff --git a/cmake/externals/sixense/CMakeLists.txt b/cmake/externals/sixense/CMakeLists.txt index 07cf1c1163..0cbca087a5 100644 --- a/cmake/externals/sixense/CMakeLists.txt +++ b/cmake/externals/sixense/CMakeLists.txt @@ -4,10 +4,22 @@ set(EXTERNAL_NAME sixense) string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) +#set(SIXENSE_URL "https://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_062612.zip") +#set(SIXENSE_URL_MD5 "10cc8dc470d2ac1244a88cf04bc549cc") +#set(SIXENSE_NEW_LAYOUT 0) + +#set(SIXENSE_URL "https://public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip") +#set(SIXENSE_URL_MD5 "752a3901f334124e9cffc2ba4136ef7d") +#set(SIXENSE_NEW_LAYOUT 1) + +set(SIXENSE_URL "https://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_102215.zip") +set(SIXENSE_URL_MD5 "93c3a6795cce777a0f472b09532935f1") +set(SIXENSE_NEW_LAYOUT 1) + ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip - URL_MD5 752a3901f334124e9cffc2ba4136ef7d + URL ${SIXENSE_URL} + URL_MD5 ${SIXENSE_URL_MD5} CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" @@ -30,8 +42,18 @@ if (WIN32) set(ARCH_SUFFIX "") endif() - set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${SOURCE_DIR}/lib/${ARCH_DIR}/VS2013/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) - add_paths_to_fixup_libs("${SOURCE_DIR}/bin/${ARCH_DIR}/VS2013/release_dll") + if (${SIXENSE_NEW_LAYOUT}) + # for 2015 SDKs (using the 2013 versions may be causing the crash) + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/${ARCH_DIR}/VS2010/release_dll") + set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/lib/${ARCH_DIR}/VS2010/release_dll") + else() + # for the 2012 SDK + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/${ARCH_DIR}/release_dll") + set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/lib/${ARCH_DIR}/release_dll") + endif() + + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) + add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_DLL_PATH}") elseif(APPLE) diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index 7d98e9796c..4afe4de403 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -10,5 +10,35 @@ # include(BundleUtilities) + + +# replace copy_resolved_item_into_bundle +# +# The official version of copy_resolved_item_into_bundle will print out a "warning:" when +# the resolved item matches the resolved embedded item. This not not really an issue that +# should rise to the level of a "warning" so we replace this message with a "status:" +# +function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) + if(WIN32) + # ignore case on Windows + string(TOLOWER "${resolved_item}" resolved_item_compare) + string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) + else() + set(resolved_item_compare "${resolved_item}") + set(resolved_embedded_item_compare "${resolved_embedded_item}") + endif() + + if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") + # this is our only change from the original version + message(STATUS "status: resolved_item == resolved_embedded_item - not copying...") + else() + #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") + execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") + if(UNIX AND NOT APPLE) + file(RPATH_REMOVE FILE "${resolved_embedded_item}") + endif() + endif() +endfunction() + message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@") fixup_bundle("${BUNDLE_EXECUTABLE}" "" "@FIXUP_LIBS@") \ No newline at end of file diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 9f7d579f10..edb9fe05bd 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -352,14 +352,14 @@ function MyController(hand) { var intersection = Entities.findRayIntersection(pickRayBacked, true); - if (intersection.intersects && intersection.properties.locked === 0) { + if (intersection.intersects && !intersection.properties.locked) { // the ray is intersecting something we can move. var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); this.grabbedEntity = intersection.entityID; //this code will disabled the beam for the opposite hand of the one that grabbed it if the entity says so var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); - if (grabbableData["turnOffOppositeBeam"] === true) { + if (grabbableData["turnOffOppositeBeam"]) { if (this.hand === RIGHT_HAND) { disabledHand = LEFT_HAND; } else { @@ -369,7 +369,7 @@ function MyController(hand) { disabledHand = 'none'; } - if (grabbableData.grabbable === false) { + if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { this.grabbedEntity = null; continue; } @@ -379,7 +379,6 @@ function MyController(hand) { } if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { // the hand is very close to the intersected object. go into close-grabbing mode. - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); if (grabbableData.wantsTrigger) { this.setState(STATE_NEAR_GRABBING_NON_COLLIDING); } else { @@ -391,7 +390,7 @@ function MyController(hand) { this.grabbedEntity = null; } else { // the hand is far from the intersected object. go into distance-holding mode - if (intersection.properties.collisionsWillMove === 1) { + if (intersection.properties.collisionsWillMove) { this.setState(STATE_DISTANCE_HOLDING); } else { this.setState(STATE_FAR_GRABBING_NON_COLLIDING); @@ -409,7 +408,7 @@ function MyController(hand) { for (i = 0; i < nearbyEntities.length; i++) { var grabbableDataForCandidate = getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); - if (grabbableDataForCandidate.grabbable === false) { + if (!grabbableDataForCandidate.grabbable) { continue; } var propsForCandidate = @@ -427,7 +426,7 @@ function MyController(hand) { } if (grabbableData.wantsTrigger) { this.setState(STATE_NEAR_GRABBING_NON_COLLIDING); - } else if (props.locked === 0) { + } else if (!props.locked) { this.setState(STATE_NEAR_GRABBING); } } @@ -446,7 +445,7 @@ function MyController(hand) { this.currentObjectPosition = grabbedProperties.position; this.currentObjectRotation = grabbedProperties.rotation; this.currentObjectTime = now; - this.handPreviousPosition = handControllerPosition; + this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position); this.handPreviousRotation = handRotation; this.actionID = NULL_ACTION_ID; @@ -523,11 +522,10 @@ function MyController(hand) { this.currentAvatarOrientation = currentOrientation; // how far did hand move this timestep? - var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition); - this.handPreviousPosition = handControllerPosition; + var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition); + this.handRelativePreviousPosition = handToAvatar; // magnify the hand movement but not the change from avatar movement & rotation - handMoved = Vec3.subtract(handMoved, avatarDeltaPosition); handMoved = Vec3.subtract(handMoved, handMovementFromTurning); var superHandMoved = Vec3.multiply(handMoved, radius); @@ -570,7 +568,7 @@ function MyController(hand) { var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); var turnOffOtherHand = grabbableData["turnOffOtherHand"]; - if (turnOffOtherHand === true) { + if (turnOffOtherHand) { //don't activate the second hand grab because the script is handling the second hand logic return; } @@ -783,11 +781,11 @@ function MyController(hand) { // we haven't been touched before, but either right or left is touching us now _this.allTouchedIDs[id] = true; _this.startTouch(id); - } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id] === true) { + } else if ((leftIsTouching || rightIsTouching) && _this.allTouchedIDs[id]) { // we have been touched before and are still being touched // continue touch _this.continueTouch(id); - } else if (_this.allTouchedIDs[id] === true) { + } else if (_this.allTouchedIDs[id]) { delete _this.allTouchedIDs[id]; _this.stopTouch(id); diff --git a/examples/controllers/rightClickExample.js b/examples/controllers/rightClickExample.js new file mode 100644 index 0000000000..c3e6ea8f3d --- /dev/null +++ b/examples/controllers/rightClickExample.js @@ -0,0 +1,10 @@ +var MAPPING_NAME = "com.highfidelity.rightClickExample"; +var mapping = Controller.newMapping(MAPPING_NAME); +mapping.from(Controller.Hardware.Keyboard.RightMouseClicked).to(function (value) { + print("Keyboard.RightMouseClicked"); +}); +Controller.enableMapping(MAPPING_NAME); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); +}); \ No newline at end of file diff --git a/examples/libraries/fjs.js b/examples/libraries/fjs.js new file mode 100644 index 0000000000..f2e6c48f59 --- /dev/null +++ b/examples/libraries/fjs.js @@ -0,0 +1,341 @@ +/* + +http://functionaljs.com/ + +https://github.com/leecrossley/functional-js/ + +The MIT License (MIT) +Copyright © 2015 Lee Crossley + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + + loadFJS = function(){ + return fjs(); +} + +var fjs = function() { + "use strict"; + + var fjs = {}, + hardReturn = "hardReturn;"; + + var lambda = function(exp) { + if (!fjs.isString(exp)) { + return; + } + + var parts = exp.match(/(.*)\s*[=-]>\s*(.*)/); + parts.shift(); + + var params = parts.shift() + .replace(/^\s*|\s(?=\s)|\s*$|,/g, "").split(" "); + var body = parts.shift(); + + parts = ((!/\s*return\s+/.test(body)) ? "return " : "") + body; + params.push(parts); + + return Function.apply({}, params); + }; + + var sliceArgs = function(args) { + return args.length > 0 ? [].slice.call(args, 0) : []; + }; + + fjs.isFunction = function(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }; + + fjs.isObject = function(obj) { + return fjs.isFunction(obj) || (!!obj && typeof(obj) === "object"); + }; + + fjs.isArray = function(obj) { + return Object.prototype.toString.call(obj) === "[object Array]"; + }; + + var checkFunction = function(func) { + if (!fjs.isFunction(func)) { + func = lambda(func); + if (!fjs.isFunction(func)) { + throw "fjs Error: Invalid function"; + } + } + return func; + }; + + fjs.curry = function(func) { + func = checkFunction(func); + return function inner() { + var _args = sliceArgs(arguments); + if (_args.length === func.length) { + return func.apply(null, _args); + } else if (_args.length > func.length) { + var initial = func.apply(null, _args); + return fjs.fold(func, initial, _args.slice(func.length)); + } else { + return function() { + var args = sliceArgs(arguments); + return inner.apply(null, _args.concat(args)); + }; + } + }; + }; + + fjs.each = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + if (!fjs.exists(items) || !fjs.isArray(items)) { + return; + } + for (var i = 0; i < items.length; i += 1) { + if (iterator.call(null, items[i], i) === hardReturn) { + return; + } + } + }); + + fjs.map = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var mapped = []; + fjs.each(function() { + mapped.push(iterator.apply(null, arguments)); + }, items); + return mapped; + }); + + fjs.fold = fjs.foldl = fjs.curry(function(iterator, cumulate, items) { + iterator = checkFunction(iterator); + fjs.each(function(item, i) { + cumulate = iterator.call(null, cumulate, item, i); + }, items); + return cumulate; + }); + + fjs.reduce = fjs.reducel = fjs.foldll = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var cumulate = items[0]; + items.shift(); + return fjs.fold(iterator, cumulate, items); + }); + + fjs.clone = function(items) { + var clone = []; + fjs.each(function(item) { + clone.push(item); + }, items); + return clone; + }; + + fjs.first = fjs.head = fjs.take = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var first; + fjs.each(function(item) { + if (iterator.call(null, item)) { + first = item; + return hardReturn; + } + }, items); + return first; + }); + + fjs.rest = fjs.tail = fjs.drop = fjs.curry(function(iterator, items) { + var result = fjs.select(iterator, items); + result.shift(); + return result; + }); + + fjs.last = fjs.curry(function(iterator, items) { + var itemsClone = fjs.clone(items); + return fjs.first(iterator, itemsClone.reverse()); + }); + + fjs.every = fjs.all = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var isEvery = true; + fjs.each(function(item) { + if (!iterator.call(null, item)) { + isEvery = false; + return hardReturn; + } + }, items); + return isEvery; + }); + + fjs.any = fjs.contains = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var isAny = false; + fjs.each(function(item) { + if (iterator.call(null, item)) { + isAny = true; + return hardReturn; + } + }, items); + return isAny; + }); + + fjs.select = fjs.filter = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var filtered = []; + fjs.each(function(item) { + if (iterator.call(null, item)) { + filtered.push(item); + } + }, items); + return filtered; + }); + + fjs.best = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var compare = function(arg1, arg2) { + return iterator.call(this, arg1, arg2) ? + arg1 : arg2; + }; + return fjs.reduce(compare, items); + }); + + fjs._while = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var result = []; + fjs.each(function(item) { + if (iterator.call(null, item)) { + result.push(item); + } else { + return hardReturn; + } + }, items); + return result; + }); + + fjs.compose = function(funcs) { + var anyInvalid = fjs.any(function(func) { + return !fjs.isFunction(func); + }); + funcs = sliceArgs(arguments).reverse(); + if (anyInvalid(funcs)) { + throw "fjs Error: Invalid function to compose"; + } + return function() { + var args = arguments; + var applyEach = fjs.each(function(func) { + args = [func.apply(null, args)]; + }); + applyEach(funcs); + return args[0]; + }; + }; + + fjs.partition = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var truthy = [], + falsy = []; + fjs.each(function(item) { + (iterator.call(null, item) ? truthy : falsy).push(item); + }, items); + return [truthy, falsy]; + }); + + fjs.group = fjs.curry(function(iterator, items) { + iterator = checkFunction(iterator); + var result = {}; + var group; + fjs.each(function(item) { + group = iterator.call(null, item); + result[group] = result[group] || []; + result[group].push(item); + }, items); + return result; + }); + + fjs.shuffle = function(items) { + var j, t; + fjs.each(function(item, i) { + j = Math.floor(Math.random() * (i + 1)); + t = items[i]; + items[i] = items[j]; + items[j] = t; + }, items); + return items; + }; + + fjs.toArray = function(obj) { + return fjs.map(function(key) { + return [key, obj[key]]; + }, Object.keys(obj)); + }; + + fjs.apply = fjs.curry(function(func, items) { + var args = []; + if (fjs.isArray(func)) { + args = [].slice.call(func, 1); + func = func[0]; + } + return fjs.map(function(item) { + return item[func].apply(item, args); + }, items); + }); + + fjs.assign = fjs.extend = fjs.curry(function(obj1, obj2) { + fjs.each(function(key) { + obj2[key] = obj1[key]; + }, Object.keys(obj1)); + return obj2; + }); + + fjs.prop = function(prop) { + return function(obj) { + return obj[prop]; + }; + }; + + fjs.pluck = fjs.curry(function(prop, items) { + return fjs.map(fjs.prop(prop), items); + }); + + fjs.nub = fjs.unique = fjs.distinct = fjs.curry(function(comparator, items) { + var unique = items.length > 0 ? [items[0]] : []; + + fjs.each(function(item) { + if (!fjs.any(fjs.curry(comparator)(item), unique)) { + unique[unique.length] = item; + } + }, items); + + return unique; + }); + + fjs.exists = function(obj) { + return obj != null; // jshint ignore:line + }; + + fjs.truthy = function(obj) { + return fjs.exists(obj) && obj !== false; + }; + + fjs.falsy = function(obj) { + return !fjs.truthy(obj); + }; + + fjs.each(function(type) { + fjs["is" + type] = function(obj) { + return Object.prototype.toString.call(obj) === "[object " + type + "]"; + }; + }, ["Arguments", "Date", "Number", "RegExp", "String"]); + + return fjs; +} \ No newline at end of file diff --git a/examples/libraries/fjsExample.js b/examples/libraries/fjsExample.js new file mode 100644 index 0000000000..78960826fd --- /dev/null +++ b/examples/libraries/fjsExample.js @@ -0,0 +1,9 @@ +Script.include('fjs.js'); +var fjs = loadFJS(); + +var concatenate = fjs.curry(function(word1, word2) { + return word1 + " " + word2; +}); +var concatenateHello = concatenate("Hello"); +var hi = concatenateHello("World"); +print('anyone listening? ' + hi) \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index fecdfe64fb..930bdbd7ce 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME interface) project(${TARGET_NAME}) # set a default root dir for each of our optional externals if it was not passed -set(OPTIONAL_EXTERNALS "LeapMotion" "RtMidi" "RSSDK" "iViewHMD") +set(OPTIONAL_EXTERNALS "LeapMotion" "RtMidi" "RSSDK") if(WIN32) list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient") diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index 20d954932a..b517d1bad5 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -13,17 +13,12 @@ { "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.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" }, + { "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftSecondaryThumb" }, + + { "from": [ "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" }, + { "from": [ "Hydra.R1", "Hydra.R2" ], "to": "Standard.RightSecondaryThumb" }, - { "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 index 9c3ba79d76..f99472b0e9 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -4,8 +4,8 @@ { "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.A", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.D", "when": "Keyboard.RightMouseButton", "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" }, @@ -13,7 +13,7 @@ { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, - "when": [ "Application.InHMD", "Application.ComfortMode", "Keyboard.RightMouseClick" ], + "when": [ "Application.InHMD", "Application.ComfortMode", "Keyboard.RightMouseButton" ], "to": "Actions.StepYaw", "filters": [ @@ -46,7 +46,7 @@ }, { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, - "when": "Keyboard.RightMouseClick", + "when": "Keyboard.RightMouseButton", "to": "Actions.Yaw" }, @@ -55,8 +55,8 @@ { "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.RightMouseButton", "to": "Actions.LATERAL_LEFT" }, + { "from": "Keyboard.Right", "when": "Keyboard.RightMouseButton", "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" }, @@ -68,8 +68,8 @@ { "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.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP" }, + { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" }, @@ -82,6 +82,6 @@ { "from": "Keyboard.Space", "to": "Actions.SHIFT" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" }, { "from": "Keyboard.T", "to": "Actions.ACTION2" }, - { "from": "Keyboard.RightMouseClick", "to": "Actions.ContextMenu" } + { "from": "Keyboard.RightMouseClicked", "to": "Actions.ContextMenu" } ] } diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index b08abcbaad..d312913a23 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -2,9 +2,8 @@ "name": "Standard to Action", "channels": [ { "from": "Standard.LY", "to": "Actions.TranslateZ" }, - { "from": "Standard.LX", "to": "Actions.TranslateX" }, - { "from": "Standard.RX", + { "from": "Standard.LX", "when": [ "Application.InHMD", "Application.ComfortMode" ], "to": "Actions.StepYaw", "filters": @@ -14,8 +13,9 @@ ] }, + { "from": "Standard.LX", "to": "Actions.Yaw" }, - { "from": "Standard.RX", "to": "Actions.Yaw" }, + { "from": "Standard.RX", "to": "Actions.TranslateX" }, { "from": "Standard.RY", "filters": "invert", "to": "Actions.TranslateY" }, diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 6a3c562e44..c46bcd9402 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -1,15 +1,15 @@ { "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.LY", "when": "Vive.LS", "filters": "invert", "to": "Standard.LY" }, + { "from": "Vive.LX", "when": "Vive.LS", "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.RY", "when": "Vive.RS", "filters": "invert", "to": "Standard.RY" }, + { "from": "Vive.RX", "when": "Vive.RS", "to": "Standard.RX" }, { "from": "Vive.RT", "to": "Standard.RT" }, { "from": "Vive.RB", "to": "Standard.RB" }, diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index 515f6e96fe..fd1099a85b 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -523,29 +523,89 @@ }, { "id": "walkFwd", - "type": "clip", + "type": "blendLinearMove", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", - "startFrame": 0.0, - "endFrame": 35.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.5, 1.4, 4.5], + "alphaVar": "moveForwardAlpha", + "desiredSpeedVar": "moveForwardSpeed" }, - "children": [] + "children": [ + { + "id": "walkFwdShort", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_short_fwd.fbx", + "startFrame": 0.0, + "endFrame": 39.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdNormal", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", + "startFrame": 0.0, + "endFrame": 35.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkFwdRun", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/run_fwd.fbx", + "startFrame": 0.0, + "endFrame": 21.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { "id": "walkBwd", - "type": "clip", + "type": "blendLinearMove", "data": { - "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", - "startFrame": 0.0, - "endFrame": 37.0, - "timeScale": 1.0, - "loopFlag": true, - "timeScaleVar": "walkTimeScale" + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.6, 1.45], + "alphaVar": "moveBackwardAlpha", + "desiredSpeedVar": "moveBackwardSpeed" }, - "children": [] + "children": [ + { + "id": "walkBwdShort", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_short_bwd.fbx", + "startFrame": 0.0, + "endFrame": 38.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "walkBwdNormal", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", + "startFrame": 0.0, + "endFrame": 36.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { "id": "turnLeft", @@ -573,27 +633,77 @@ }, { "id": "strafeLeft", - "type": "clip", + "type": "blendLinearMove", "data": { - "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.2, 0.65], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" }, - "children": [] + "children": [ + { + "id": "strafeLeftShort", + "type": "clip", + "data": { + "url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_short_left.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeLeftNormal", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] }, { "id": "strafeRight", - "type": "clip", + "type": "blendLinearMove", "data": { - "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx", - "startFrame": 0.0, - "endFrame": 31.0, - "timeScale": 1.0, - "loopFlag": true + "alpha": 0.0, + "desiredSpeed": 1.4, + "characteristicSpeeds": [0.2, 0.65], + "alphaVar": "moveLateralAlpha", + "desiredSpeedVar": "moveLateralSpeed" }, - "children": [] + "children": [ + { + "id": "strafeRightShort", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_short_right.fbx", + "startFrame": 0.0, + "endFrame": 28.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + }, + { + "id": "strafeRightNormal", + "type": "clip", + "data": { + "url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx", + "startFrame": 0.0, + "endFrame": 30.0, + "timeScale": 1.0, + "loopFlag": true + }, + "children": [] + } + ] } ] } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index b65341a4b2..4ffa1ffc7c 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -72,6 +72,7 @@ namespace render { avatarPtr->setDisplayingLookatTarget(renderLookAtTarget); if (avatarPtr->isInitialized() && args) { + PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload"); avatarPtr->render(args, qApp->getCamera()->getPosition()); } } @@ -304,6 +305,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr_batch; + PROFILE_RANGE_BATCH(batch, __FUNCTION__); if (glm::distance(DependencyManager::get()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) { auto geometryCache = DependencyManager::get(); @@ -330,6 +332,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } if (havePosition && haveRotation) { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":leftHandPointer"); Transform pointerTransform; pointerTransform.setTranslation(position); pointerTransform.setRotation(rotation); @@ -353,6 +356,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { } if (havePosition && haveRotation) { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":rightHandPointer"); Transform pointerTransform; pointerTransform.setTranslation(position); pointerTransform.setRotation(rotation); @@ -424,6 +428,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes"); _skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f); } @@ -432,7 +437,9 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { static const float INDICATOR_OFFSET = 0.22f; static const float INDICATOR_RADIUS = 0.03f; static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; - glm::vec3 position = glm::vec3(getPosition().x, getDisplayNamePosition().y + INDICATOR_OFFSET, getPosition().z); + glm::vec3 avatarPosition = getPosition(); + glm::vec3 position = glm::vec3(avatarPosition.x, getDisplayNamePosition().y + INDICATOR_OFFSET, avatarPosition.z); + PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderFocusIndicator"); Transform transform; transform.setTranslation(position); transform.postScale(INDICATOR_RADIUS); @@ -441,6 +448,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { // If the avatar is looking at me, indicate that they are if (getHead()->isLookingAtMe() && Menu::getInstance()->isOptionChecked(MenuOption::ShowWhosLookingAtMe)) { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderLookingAtMe"); const glm::vec3 LOOKING_AT_ME_COLOR = { 1.0f, 1.0f, 1.0f }; const float LOOKING_AT_ME_ALPHA_START = 0.8f; const float LOOKING_AT_ME_DURATION = 0.5f; // seconds @@ -486,6 +494,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { const float MIN_VOICE_SPHERE_DISTANCE = 12.0f; if (Menu::getInstance()->isOptionChecked(MenuOption::BlueSpeechSphere) && distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderVoiceSphere"); // render voice intensity sphere for avatars that are farther away const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; @@ -621,6 +630,9 @@ void Avatar::updateJointMappings() { } void Avatar::renderBillboard(RenderArgs* renderArgs) { + // FIXME disabling the billboard because it doesn't appear to work reliably + // the billboard is ending up with a random texture and position. + return; if (_billboard.isEmpty()) { return; } @@ -652,6 +664,7 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) { glm::vec2 texCoordBottomRight(1.0f, 1.0f); gpu::Batch& batch = *renderArgs->_batch; + PROFILE_RANGE_BATCH(batch, __FUNCTION__); batch.setResourceTexture(0, _billboardTexture->getGPUTexture()); DependencyManager::get()->bindSimpleProgram(batch, true); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, @@ -734,6 +747,8 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, cons } void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const { + PROFILE_RANGE_BATCH(batch, __FUNCTION__); + bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats() && !isMyAvatar(); // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return @@ -784,17 +799,24 @@ void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, co // Test on extent above insures abs(height) > 0.0f textTransform.postScale(1.0f / height); batch.setModelTransform(textTransform); - - DependencyManager::get()->bindSimpleProgram(batch, false, true, true, true); - DependencyManager::get()->renderBevelCornersRect(batch, left, bottom, width, height, - bevelDistance, backgroundColor); + + { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderBevelCornersRect"); + DependencyManager::get()->bindSimpleProgram(batch, false, true, true, true); + DependencyManager::get()->renderBevelCornersRect(batch, left, bottom, width, height, + bevelDistance, backgroundColor); + } + // Render actual name QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit(); // Render text slightly in front to avoid z-fighting textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize())); batch.setModelTransform(textTransform); - renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor); + { + PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderText"); + renderer->draw(batch, text_x, -text_y, nameUTF8.data(), textColor); + } } } @@ -1059,6 +1081,7 @@ void Avatar::renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, g points << p1a << p1b << p2a << p1b << p2a << p2b; } + PROFILE_RANGE_BATCH(batch, __FUNCTION__); // TODO: this is really inefficient constantly recreating these vertices buffers. It would be // better if the avatars cached these buffers for each of the joints they are rendering geometryCache->updateVertices(_jointConesID, points, color); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 548d66bed4..4ba684ea66 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -156,6 +156,8 @@ void MyAvatar::reset(bool andReload) { // Reset dynamic state. _wasPushing = _isPushing = _isBraking = _billboardValid = false; _isFollowingHMD = false; + _hmdFollowVelocity = Vectors::ZERO; + _hmdFollowSpeed = 0.0f; _skeletonModel.reset(); getHead()->reset(); _targetVelocity = glm::vec3(0.0f); @@ -243,7 +245,7 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("transform"); bool stepAction = false; - // When there are no step values, we zero out the last step pulse. + // 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) { @@ -327,25 +329,6 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const { return _sensorToWorldMatrix; } -// returns true if pos is OUTSIDE of the vertical capsule -// where the middle cylinder length is defined by capsuleLen and the radius by capsuleRad. -static bool pointIsOutsideCapsule(const glm::vec3& pos, float capsuleLen, float capsuleRad) { - const float halfCapsuleLen = capsuleLen / 2.0f; - if (fabs(pos.y) <= halfCapsuleLen) { - // cylinder check for middle capsule - glm::vec2 horizPos(pos.x, pos.z); - return glm::length(horizPos) > capsuleRad; - } else if (pos.y > halfCapsuleLen) { - glm::vec3 center(0.0f, halfCapsuleLen, 0.0f); - return glm::length(center - pos) > capsuleRad; - } else if (pos.y < halfCapsuleLen) { - glm::vec3 center(0.0f, -halfCapsuleLen, 0.0f); - return glm::length(center - pos) > capsuleRad; - } else { - return false; - } -} - // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD // as it moves through the world. @@ -354,102 +337,59 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorMatrix = hmdSensorMatrix; _hmdSensorPosition = extractTranslation(hmdSensorMatrix); _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); +} - // calc deltaTime - auto now = usecTimestampNow(); - auto deltaUsecs = now - _lastUpdateFromHMDTime; - _lastUpdateFromHMDTime = now; - double actualDeltaTime = (double)deltaUsecs / (double)USECS_PER_SECOND; - const float BIGGEST_DELTA_TIME_SECS = 0.25f; - float deltaTime = glm::clamp((float)actualDeltaTime, 0.0f, BIGGEST_DELTA_TIME_SECS); - - bool hmdIsAtRest = _hmdAtRestDetector.update(deltaTime, _hmdSensorPosition, _hmdSensorOrientation); - - // It can be more accurate/smooth to use velocity rather than position, - // but some modes (e.g., hmd standing) update position without updating velocity. - // So, let's create our own workingVelocity from the worldPosition... - glm::vec3 positionDelta = getPosition() - _lastPosition; - glm::vec3 workingVelocity = positionDelta / deltaTime; - _lastPosition = getPosition(); - - const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec - const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec +void MyAvatar::updateHMDFollowVelocity() { bool isMoving; if (_lastIsMoving) { - isMoving = glm::length(workingVelocity) >= MOVE_EXIT_SPEED_THRESHOLD; + const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec + isMoving = glm::length(_velocity) >= MOVE_EXIT_SPEED_THRESHOLD; } else { - isMoving = glm::length(workingVelocity) > MOVE_ENTER_SPEED_THRESHOLD; + const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec + isMoving = glm::length(_velocity) > MOVE_ENTER_SPEED_THRESHOLD; } bool justStartedMoving = (_lastIsMoving != isMoving) && isMoving; _lastIsMoving = isMoving; - if (shouldFollowHMD() || hmdIsAtRest || justStartedMoving) { - beginFollowingHMD(); - } - - followHMD(deltaTime); -} - -glm::vec3 MyAvatar::getHMDCorrectionVelocity() const { - // TODO: impelement this - return Vectors::ZERO; -} - -void MyAvatar::beginFollowingHMD() { - // begin homing toward derived body position. - if (!_isFollowingHMD) { + bool hmdIsAtRest = _hmdAtRestDetector.update(_hmdSensorPosition, _hmdSensorOrientation); + if (hmdIsAtRest || justStartedMoving) { _isFollowingHMD = true; - _followHMDAlpha = 0.0f; } -} -bool MyAvatar::shouldFollowHMD() const { - if (!_isFollowingHMD) { - // define a vertical capsule - const float FOLLOW_HMD_CAPSULE_RADIUS = 0.2f; // meters - const float FOLLOW_HMD_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters. - - // detect if the derived body position is outside of a capsule around the _bodySensorMatrix - auto newBodySensorMatrix = deriveBodyFromHMDSensor(); - glm::vec3 localPoint = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix); - return pointIsOutsideCapsule(localPoint, FOLLOW_HMD_CAPSULE_LENGTH, FOLLOW_HMD_CAPSULE_RADIUS); + // compute offset to body's target position (in sensor-frame) + auto sensorBodyMatrix = deriveBodyFromHMDSensor(); + _hmdFollowOffset = extractTranslation(sensorBodyMatrix) - extractTranslation(_bodySensorMatrix); + glm::vec3 truncatedOffset = _hmdFollowOffset; + if (truncatedOffset.y < 0.0f) { + // don't pull the body DOWN to match the target (allow animation system to squat) + truncatedOffset.y = 0.0f; } - return false; -} -void MyAvatar::followHMD(float deltaTime) { - if (_isFollowingHMD) { - - const float FOLLOW_HMD_DURATION = 0.5f; // seconds - - auto newBodySensorMatrix = deriveBodyFromHMDSensor(); - auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; - glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix); - glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix)); - - _followHMDAlpha += (1.0f / FOLLOW_HMD_DURATION) * deltaTime; - - if (_followHMDAlpha >= 1.0f) { - _isFollowingHMD = false; - nextAttitude(worldBodyPos, worldBodyRot); - _bodySensorMatrix = newBodySensorMatrix; - } else { - // interp position toward the desired pos - glm::vec3 pos = lerp(getPosition(), worldBodyPos, _followHMDAlpha); - glm::quat rot = glm::normalize(safeMix(getOrientation(), worldBodyRot, _followHMDAlpha)); - nextAttitude(pos, rot); - - // interp sensor matrix toward desired - glm::vec3 nextBodyPos = extractTranslation(newBodySensorMatrix); - glm::quat nextBodyRot = glm::normalize(glm::quat_cast(newBodySensorMatrix)); - glm::vec3 prevBodyPos = extractTranslation(_bodySensorMatrix); - glm::quat prevBodyRot = glm::normalize(glm::quat_cast(_bodySensorMatrix)); - pos = lerp(prevBodyPos, nextBodyPos, _followHMDAlpha); - rot = glm::normalize(safeMix(prevBodyRot, nextBodyRot, _followHMDAlpha)); - _bodySensorMatrix = createMatFromQuatAndPos(rot, pos); + bool needNewFollowSpeed = (_isFollowingHMD && _hmdFollowSpeed == 0.0f); + if (!needNewFollowSpeed) { + // check to see if offset has exceeded its threshold + float distance = glm::length(truncatedOffset); + const float MAX_HMD_HIP_SHIFT = 0.2f; + if (distance > MAX_HMD_HIP_SHIFT) { + _isFollowingHMD = true; + needNewFollowSpeed = true; } } + if (_isFollowingHMD) { + // only bother to rotate into world frame if we're following + glm::quat sensorToWorldRotation = extractRotation(_sensorToWorldMatrix); + _hmdFollowOffset = sensorToWorldRotation * _hmdFollowOffset; + } + if (needNewFollowSpeed) { + // compute new velocity that will be used to resolve offset of hips from body + const float FOLLOW_HMD_DURATION = 0.5f; // seconds + _hmdFollowVelocity = (_hmdFollowOffset / FOLLOW_HMD_DURATION); + _hmdFollowSpeed = glm::length(_hmdFollowVelocity); + } else if (_isFollowingHMD) { + // compute new velocity (but not new speed) + _hmdFollowVelocity = _hmdFollowSpeed * glm::normalize(_hmdFollowOffset); + } } // best called at end of main loop, just before rendering. @@ -1326,17 +1266,68 @@ void MyAvatar::prepareForPhysicsSimulation() { relayDriveKeysToCharacterController(); _characterController.setTargetVelocity(getTargetVelocity()); _characterController.setAvatarPositionAndOrientation(getPosition(), getOrientation()); - _characterController.setHMDVelocity(getHMDCorrectionVelocity()); -} + if (qApp->isHMDMode()) { + updateHMDFollowVelocity(); + _characterController.setHMDVelocity(_hmdFollowVelocity); + } else { + _characterController.setHMDVelocity(Vectors::ZERO); + _isFollowingHMD = false; + } +} void MyAvatar::harvestResultsFromPhysicsSimulation() { glm::vec3 position = getPosition(); glm::quat orientation = getOrientation(); _characterController.getAvatarPositionAndOrientation(position, orientation); nextAttitude(position, orientation); - setVelocity(_characterController.getLinearVelocity()); - // TODO: harvest HMD shift here - //glm::vec3 hmdShift = _characterController.getHMDShift(); + if (_isFollowingHMD) { + setVelocity(_characterController.getLinearVelocity() + _hmdFollowVelocity); + glm::vec3 hmdShift = _characterController.getHMDShift(); + adjustSensorTransform(hmdShift); + } else { + setVelocity(_characterController.getLinearVelocity()); + } +} + +void MyAvatar::adjustSensorTransform(glm::vec3 hmdShift) { + // compute blendFactor of latest hmdShift + // which we'll use to blend the rotation part + float blendFactor = 1.0f; + float shiftLength = glm::length(hmdShift); + if (shiftLength > 1.0e-5f) { + float offsetLength = glm::length(_hmdFollowOffset); + if (offsetLength > shiftLength) { + blendFactor = shiftLength / offsetLength; + } + } + + auto newBodySensorMatrix = deriveBodyFromHMDSensor(); + auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; + glm::quat finalBodyRotation = glm::normalize(glm::quat_cast(worldBodyMatrix)); + if (blendFactor >= 0.99f) { + // the "adjustment" is more or less complete so stop following + _isFollowingHMD = false; + _hmdFollowSpeed = 0.0f; + // and slam the body's transform anyway to eliminate any slight errors + glm::vec3 finalBodyPosition = extractTranslation(worldBodyMatrix); + nextAttitude(finalBodyPosition, finalBodyRotation); + _bodySensorMatrix = newBodySensorMatrix; + } else { + // physics already did the positional blending for us + glm::vec3 newBodyPosition = getPosition(); + // but the rotational part must be done manually + glm::quat newBodyRotation = glm::normalize(safeMix(getOrientation(), finalBodyRotation, blendFactor)); + nextAttitude(newBodyPosition, newBodyRotation); + + // interp sensor matrix toward the desired + glm::vec3 prevPosition = extractTranslation(_bodySensorMatrix); + glm::quat prevRotation = glm::normalize(glm::quat_cast(_bodySensorMatrix)); + glm::vec3 nextPosition = extractTranslation(newBodySensorMatrix); + glm::quat nextRotation = glm::normalize(glm::quat_cast(newBodySensorMatrix)); + _bodySensorMatrix = createMatFromQuatAndPos( + glm::normalize(safeMix(prevRotation, nextRotation, blendFactor)), + lerp(prevPosition, nextPosition, blendFactor)); + } } QString MyAvatar::getScriptedMotorFrame() const { @@ -1438,7 +1429,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl getHead()->renderLookAts(renderArgs); } - if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE && + if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) { getHand()->renderHandTargets(renderArgs, true); } @@ -1917,7 +1908,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() { QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu"); return; } - + Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b93358be9e..deac9acdfe 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -101,8 +101,6 @@ public: // as it moves through the world. void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); - glm::vec3 getHMDCorrectionVelocity() const; - // best called at end of main loop, just before rendering. // update sensor to world matrix from current body position and hmd sensor. // This is so the correct camera can be used for rendering. @@ -211,6 +209,7 @@ public: void prepareForPhysicsSimulation(); void harvestResultsFromPhysicsSimulation(); + void adjustSensorTransform(glm::vec3 hmdShift); const QString& getCollisionSoundURL() { return _collisionSoundURL; } void setCollisionSoundURL(const QString& url); @@ -312,9 +311,10 @@ private: const RecorderPointer getRecorder() const { return _recorder; } const PlayerPointer getPlayer() const { return _player; } - void beginFollowingHMD(); - bool shouldFollowHMD() const; - void followHMD(float deltaTime); + //void beginFollowingHMD(); + //bool shouldFollowHMD() const; + //void followHMD(float deltaTime); + void updateHMDFollowVelocity(); bool cameraInsideHead() const; @@ -391,6 +391,9 @@ private: // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. glm::mat4 _sensorToWorldMatrix; + glm::vec3 _hmdFollowOffset { Vectors::ZERO }; + glm::vec3 _hmdFollowVelocity { Vectors::ZERO }; + float _hmdFollowSpeed { 0.0f }; bool _goToPending; glm::vec3 _goToPosition; @@ -411,9 +414,7 @@ private: bool _isFollowingHMD { false }; float _followHMDAlpha { 0.0f }; - quint64 _lastUpdateFromHMDTime { usecTimestampNow() }; AtRestDetector _hmdAtRestDetector; - glm::vec3 _lastPosition; bool _lastIsMoving { false }; quint64 _lastStepPulse { 0 }; bool _pulseUpdate { false }; diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index b7a0a8c299..2cfc2ecd75 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -118,18 +118,6 @@ void EyeTracker::init() { qCWarning(interfaceapp) << "Eye Tracker: Already initialized"; return; } - -#ifdef HAVE_IVIEWHMD - int result = smi_setCallback(eyeTrackerCallback); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result); - QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); - } else { - _isInitialized = true; - } - - connect(&_startStreamingWatcher, SIGNAL(finished()), this, SLOT(onStreamStarted())); -#endif } #ifdef HAVE_IVIEWHMD @@ -140,6 +128,10 @@ int EyeTracker::startStreaming(bool simulate) { #ifdef HAVE_IVIEWHMD void EyeTracker::onStreamStarted() { + if (!_isInitialized) { + return; + } + int result = _startStreamingWatcher.result(); _isStreaming = (result == SMI_RET_SUCCESS); @@ -171,6 +163,20 @@ void EyeTracker::onStreamStarted() { #endif void EyeTracker::setEnabled(bool enabled, bool simulate) { + if (enabled && !_isInitialized) { +#ifdef HAVE_IVIEWHMD + int result = smi_setCallback(eyeTrackerCallback); + if (result != SMI_RET_SUCCESS) { + qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result); + QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); + } else { + _isInitialized = true; + } + + connect(&_startStreamingWatcher, SIGNAL(finished()), this, SLOT(onStreamStarted())); +#endif + } + if (!_isInitialized) { return; } diff --git a/libraries/animation/src/AnimBlendLinear.cpp b/libraries/animation/src/AnimBlendLinear.cpp index bc95565f6f..52c440a14e 100644 --- a/libraries/animation/src/AnimBlendLinear.cpp +++ b/libraries/animation/src/AnimBlendLinear.cpp @@ -12,6 +12,7 @@ #include "GLMHelpers.h" #include "AnimationLogging.h" #include "AnimUtil.h" +#include "AnimClip.h" AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha) : AnimNode(AnimNode::Type::BlendLinear, id), @@ -34,24 +35,13 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo } else if (_children.size() == 1) { _poses = _children[0]->evaluate(animVars, dt, triggersOut); } else { + float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); size_t prevPoseIndex = glm::floor(clampedAlpha); size_t nextPoseIndex = glm::ceil(clampedAlpha); float alpha = glm::fract(clampedAlpha); - if (prevPoseIndex == nextPoseIndex) { - // this can happen if alpha is on an integer boundary - _poses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); - } else { - // need to eval and blend between two children. - auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); - auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt, triggersOut); - if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { - _poses.resize(prevPoses.size()); - - ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); - } - } + evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt); } return _poses; } @@ -60,3 +50,21 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo const AnimPoseVec& AnimBlendLinear::getPosesInternal() const { return _poses; } + +void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, float dt) { + if (prevPoseIndex == nextPoseIndex) { + // this can happen if alpha is on an integer boundary + _poses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); + } else { + // need to eval and blend between two children. + auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut); + auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt, triggersOut); + + if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { + _poses.resize(prevPoses.size()); + + ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); + } + } +} diff --git a/libraries/animation/src/AnimBlendLinear.h b/libraries/animation/src/AnimBlendLinear.h index 56acd5c2f7..2478f9b473 100644 --- a/libraries/animation/src/AnimBlendLinear.h +++ b/libraries/animation/src/AnimBlendLinear.h @@ -38,6 +38,9 @@ protected: // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; + void evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, float dt); + AnimPoseVec _poses; float _alpha; diff --git a/libraries/animation/src/AnimBlendLinearMove.cpp b/libraries/animation/src/AnimBlendLinearMove.cpp new file mode 100644 index 0000000000..d8985f8b72 --- /dev/null +++ b/libraries/animation/src/AnimBlendLinearMove.cpp @@ -0,0 +1,126 @@ +// +// AnimBlendLinearMove.cpp +// +// Created by Anthony J. Thibault on 10/22/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 "AnimBlendLinearMove.h" +#include +#include "AnimationLogging.h" +#include "AnimUtil.h" +#include "AnimClip.h" + +AnimBlendLinearMove::AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector& characteristicSpeeds) : + AnimNode(AnimNode::Type::BlendLinearMove, id), + _alpha(alpha), + _desiredSpeed(desiredSpeed), + _characteristicSpeeds(characteristicSpeeds) { + +} + +AnimBlendLinearMove::~AnimBlendLinearMove() { + +} + +const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) { + + assert(_children.size() == _characteristicSpeeds.size()); + + _alpha = animVars.lookup(_alphaVar, _alpha); + _desiredSpeed = animVars.lookup(_desiredSpeedVar, _desiredSpeed); + + if (_children.size() == 0) { + for (auto&& pose : _poses) { + pose = AnimPose::identity; + } + } else if (_children.size() == 1) { + const float alpha = 0.0f; + const int prevPoseIndex = 0; + const int nextPoseIndex = 0; + float prevDeltaTime, nextDeltaTime; + setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); + evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); + } else { + + float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); + size_t prevPoseIndex = glm::floor(clampedAlpha); + size_t nextPoseIndex = glm::ceil(clampedAlpha); + float alpha = glm::fract(clampedAlpha); + float prevDeltaTime, nextDeltaTime; + setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut); + evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime); + } + return _poses; +} + +// for AnimDebugDraw rendering +const AnimPoseVec& AnimBlendLinearMove::getPosesInternal() const { + return _poses; +} + +void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, + float prevDeltaTime, float nextDeltaTime) { + if (prevPoseIndex == nextPoseIndex) { + // this can happen if alpha is on an integer boundary + _poses = _children[prevPoseIndex]->evaluate(animVars, prevDeltaTime, triggersOut); + } else { + // need to eval and blend between two children. + auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, prevDeltaTime, triggersOut); + auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, nextDeltaTime, triggersOut); + + if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) { + _poses.resize(prevPoses.size()); + + ::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]); + } + } +} + +void AnimBlendLinearMove::setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, + float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut) { + + const float FRAMES_PER_SECOND = 30.0f; + auto prevClipNode = std::dynamic_pointer_cast(_children[prevPoseIndex]); + assert(prevClipNode); + auto nextClipNode = std::dynamic_pointer_cast(_children[nextPoseIndex]); + assert(nextClipNode); + + float v0 = _characteristicSpeeds[prevPoseIndex]; + float n0 = (prevClipNode->getEndFrame() - prevClipNode->getStartFrame()) + 1.0f; + float v1 = _characteristicSpeeds[nextPoseIndex]; + float n1 = (nextClipNode->getEndFrame() - nextClipNode->getStartFrame()) + 1.0f; + + // rate of change in phase space, necessary to achive desired speed. + float omega = (_desiredSpeed * FRAMES_PER_SECOND) / ((1.0f - alpha) * v0 * n0 + alpha * v1 * n1); + + float f0 = prevClipNode->getStartFrame() + _phase * n0; + prevClipNode->setCurrentFrame(f0); + + float f1 = nextClipNode->getStartFrame() + _phase * n1; + nextClipNode->setCurrentFrame(f1); + + // integrate phase forward in time. + _phase += omega * dt; + + // detect loop trigger events + if (_phase >= 1.0f) { + triggersOut.push_back(_id + "Loop"); + _phase = glm::fract(_phase); + } + + *prevDeltaTimeOut = omega * dt * (n0 / FRAMES_PER_SECOND); + *nextDeltaTimeOut = omega * dt * (n1 / FRAMES_PER_SECOND); +} + +void AnimBlendLinearMove::setCurrentFrameInternal(float frame) { + assert(_children.size() > 0); + auto clipNode = std::dynamic_pointer_cast(_children.front()); + assert(clipNode); + const float NUM_FRAMES = (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f; + _phase = fmodf(frame, NUM_FRAMES); +} diff --git a/libraries/animation/src/AnimBlendLinearMove.h b/libraries/animation/src/AnimBlendLinearMove.h new file mode 100644 index 0000000000..4e04ce29cb --- /dev/null +++ b/libraries/animation/src/AnimBlendLinearMove.h @@ -0,0 +1,77 @@ +// +// AnimBlendLinearMove.h +// +// Created by Anthony J. Thibault on 10/22/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 +// + +#ifndef hifi_AnimBlendLinearMove_h +#define hifi_AnimBlendLinearMove_h + +#include "AnimNode.h" + +// Synced linear blend between two AnimNodes, where the playback speed of +// the animation is timeScaled to match movement speed. +// +// Each child animation is associated with a chracteristic speed. +// This defines the speed of that animation when played at the normal playback rate, 30 frames per second. +// +// The user also specifies a desired speed. This desired speed is used to timescale +// the animation to achive the desired movement velocity. +// +// Blending is determined by the alpha parameter. +// If the number of children is 2, then the alpha parameters should be between +// 0 and 1. The first animation will have a (1 - alpha) factor, and the second +// will have factor of alpha. +// +// This node supports more then 2 children. In this case the alpha should be +// between 0 and n - 1. This alpha can be used to linearly interpolate between +// the closest two children poses. This can be used to sweep through a series +// of animation poses. + +class AnimBlendLinearMove : public AnimNode { +public: + friend class AnimTests; + + AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector& characteristicSpeeds); + virtual ~AnimBlendLinearMove() override; + + virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; + + void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; } + void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; } + +protected: + // for AnimDebugDraw rendering + virtual const AnimPoseVec& getPosesInternal() const override; + + void evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha, + size_t prevPoseIndex, size_t nextPoseIndex, + float prevDeltaTime, float nextDeltaTime); + + void setFrameAndPhase(float dt, float alpha, int prevPoseIndex, int nextPoseIndex, + float* prevDeltaTimeOut, float* nextDeltaTimeOut, Triggers& triggersOut); + + virtual void setCurrentFrameInternal(float frame) override; + + AnimPoseVec _poses; + + float _alpha; + float _desiredSpeed; + + float _phase = 0.0f; + + QString _alphaVar; + QString _desiredSpeedVar; + + std::vector _characteristicSpeeds; + + // no copies + AnimBlendLinearMove(const AnimBlendLinearMove&) = delete; + AnimBlendLinearMove& operator=(const AnimBlendLinearMove&) = delete; +}; + +#endif // hifi_AnimBlendLinearMove_h diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 251cb0047a..8f50874ed3 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -35,7 +35,9 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, _endFrame = animVars.lookup(_endFrameVar, _endFrame); _timeScale = animVars.lookup(_timeScaleVar, _timeScale); _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); - _frame = accumulateTime(animVars.lookup(_frameVar, _frame), dt, triggersOut); + float frame = animVars.lookup(_frameVar, _frame); + + _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut); // poll network anim to see if it's finished loading yet. if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { @@ -45,16 +47,17 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, } if (_anim.size()) { - int frameCount = _anim.size(); - int prevIndex = (int)glm::floor(_frame); - int nextIndex = (int)glm::ceil(_frame); - if (_loopFlag && nextIndex >= frameCount) { - nextIndex = 0; + int nextIndex; + if (_loopFlag && _frame >= _endFrame) { + nextIndex = (int)glm::ceil(_startFrame); + } else { + nextIndex = (int)glm::ceil(_frame); } // It can be quite possible for the user to set _startFrame and _endFrame to // values before or past valid ranges. We clamp the frames here. + int frameCount = _anim.size(); prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); @@ -78,39 +81,7 @@ void AnimClip::setCurrentFrameInternal(float frame) { // because dt is 0, we should not encounter any triggers const float dt = 0.0f; Triggers triggers; - _frame = accumulateTime(frame * _timeScale, dt, triggers); -} - -float AnimClip::accumulateTime(float frame, float dt, Triggers& triggersOut) const { - const float startFrame = std::min(_startFrame, _endFrame); - if (startFrame == _endFrame) { - // when startFrame >= endFrame - frame = _endFrame; - } else if (_timeScale > 0.0f) { - // accumulate time, keeping track of loops and end of animation events. - const float FRAMES_PER_SECOND = 30.0f; - float framesRemaining = (dt * _timeScale) * FRAMES_PER_SECOND; - while (framesRemaining > 0.0f) { - float framesTillEnd = _endFrame - _frame; - if (framesRemaining >= framesTillEnd) { - if (_loopFlag) { - // anim loop - triggersOut.push_back(_id + "OnLoop"); - framesRemaining -= framesTillEnd; - frame = startFrame; - } else { - // anim end - triggersOut.push_back(_id + "OnDone"); - frame = _endFrame; - framesRemaining = 0.0f; - } - } else { - frame += framesRemaining; - framesRemaining = 0.0f; - } - } - } - return frame; + _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggers); } void AnimClip::copyFromNetworkAnim() { diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 3a76870c98..36867e622b 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -36,12 +36,17 @@ public: void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; } void setFrameVar(const QString& frameVar) { _frameVar = frameVar; } + float getStartFrame() const { return _startFrame; } + float getEndFrame() const { return _endFrame; } + + void setTimeScale(float timeScale) { _timeScale = timeScale; } + float getTimeScale() const { return _timeScale; } + protected: void loadURL(const QString& url); virtual void setCurrentFrameInternal(float frame) override; - float accumulateTime(float frame, float dt, Triggers& triggersOut) const; void copyFromNetworkAnim(); // for AnimDebugDraw rendering diff --git a/libraries/animation/src/AnimNode.h b/libraries/animation/src/AnimNode.h index 9a21526409..ac3365c272 100644 --- a/libraries/animation/src/AnimNode.h +++ b/libraries/animation/src/AnimNode.h @@ -38,6 +38,7 @@ public: enum class Type { Clip = 0, BlendLinear, + BlendLinearMove, Overlay, StateMachine, Manipulator, @@ -75,10 +76,10 @@ public: return evaluate(animVars, dt, triggersOut); } -protected: - void setCurrentFrame(float frame); +protected: + virtual void setCurrentFrameInternal(float frame) {} virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 147025f1cf..2a52e04e1d 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -16,6 +16,7 @@ #include "AnimNode.h" #include "AnimClip.h" #include "AnimBlendLinear.h" +#include "AnimBlendLinearMove.h" #include "AnimationLogging.h" #include "AnimOverlay.h" #include "AnimNodeLoader.h" @@ -29,6 +30,7 @@ using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& json // factory functions static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); +static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); @@ -36,17 +38,14 @@ static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, c // called after children have been loaded // returns node on success, nullptr on failure. -static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } +static bool processDoNothing(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); -static bool processManipulatorNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } -static bool processInverseKinematicsNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } static const char* animNodeTypeToString(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return "clip"; case AnimNode::Type::BlendLinear: return "blendLinear"; + case AnimNode::Type::BlendLinearMove: return "blendLinearMove"; case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::Manipulator: return "manipulator"; @@ -60,6 +59,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { switch (type) { case AnimNode::Type::Clip: return loadClipNode; case AnimNode::Type::BlendLinear: return loadBlendLinearNode; + case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode; case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::Manipulator: return loadManipulatorNode; @@ -71,12 +71,13 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) { static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { switch (type) { - case AnimNode::Type::Clip: return processClipNode; - case AnimNode::Type::BlendLinear: return processBlendLinearNode; - case AnimNode::Type::Overlay: return processOverlayNode; + case AnimNode::Type::Clip: return processDoNothing; + case AnimNode::Type::BlendLinear: return processDoNothing; + case AnimNode::Type::BlendLinearMove: return processDoNothing; + case AnimNode::Type::Overlay: return processDoNothing; case AnimNode::Type::StateMachine: return processStateMachineNode; - case AnimNode::Type::Manipulator: return processManipulatorNode; - case AnimNode::Type::InverseKinematics: return processInverseKinematicsNode; + case AnimNode::Type::Manipulator: return processDoNothing; + case AnimNode::Type::InverseKinematics: return processDoNothing; case AnimNode::Type::NumTypes: return nullptr; }; return nullptr; @@ -160,6 +161,9 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr assert((int)type >= 0 && type < AnimNode::Type::NumTypes); auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); + if (!node) { + return nullptr; + } auto childrenValue = jsonObj.value("children"); if (!childrenValue.isArray()) { @@ -233,6 +237,45 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q return node; } +static AnimNode::Pointer loadBlendLinearMoveNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { + + READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr); + READ_FLOAT(desiredSpeed, jsonObj, id, jsonUrl, nullptr); + + std::vector characteristicSpeeds; + auto speedsValue = jsonObj.value("characteristicSpeeds"); + if (!speedsValue.isArray()) { + qCCritical(animation) << "AnimNodeLoader, bad array \"characteristicSpeeds\" in blendLinearMove node, id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + + auto speedsArray = speedsValue.toArray(); + for (const auto& speedValue : speedsArray) { + if (!speedValue.isDouble()) { + qCCritical(animation) << "AnimNodeLoader, bad number in \"characteristicSpeeds\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + return nullptr; + } + float speedVal = (float)speedValue.toDouble(); + characteristicSpeeds.push_back(speedVal); + }; + + READ_OPTIONAL_STRING(alphaVar, jsonObj); + READ_OPTIONAL_STRING(desiredSpeedVar, jsonObj); + + auto node = std::make_shared(id, alpha, desiredSpeed, characteristicSpeeds); + + if (!alphaVar.isEmpty()) { + node->setAlphaVar(alphaVar); + } + + if (!desiredSpeedVar.isEmpty()) { + node->setDesiredSpeedVar(desiredSpeedVar); + } + + return node; +} + + static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "fullBody", "upperBody", diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp new file mode 100644 index 0000000000..bae34509ae --- /dev/null +++ b/libraries/animation/src/AnimPose.cpp @@ -0,0 +1,56 @@ +// +// AnimPose.cpp +// +// Created by Anthony J. Thibault on 10/14/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 "AnimPose.h" +#include "GLMHelpers.h" + +const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), + glm::quat(), + glm::vec3(0.0f)); + +AnimPose::AnimPose(const glm::mat4& mat) { + scale = extractScale(mat); + rot = glm::normalize(glm::quat_cast(mat)); + trans = extractTranslation(mat); +} + +glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const { + return trans + (rot * (scale * rhs)); +} + +glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const { + return *this * rhs; +} + +// really slow +glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { + glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); + glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); + glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z); + glm::mat3 mat(xAxis, yAxis, zAxis); + glm::mat3 transInvMat = glm::inverse(glm::transpose(mat)); + return transInvMat * rhs; +} + +AnimPose AnimPose::operator*(const AnimPose& rhs) const { + return AnimPose(static_cast(*this) * static_cast(rhs)); +} + +AnimPose AnimPose::inverse() const { + return AnimPose(glm::inverse(static_cast(*this))); +} + +AnimPose::operator glm::mat4() const { + glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); + glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); + glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z); + return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), + glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); +} diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h new file mode 100644 index 0000000000..852d84ec1b --- /dev/null +++ b/libraries/animation/src/AnimPose.h @@ -0,0 +1,47 @@ +// +// AnimPose.h +// +// Created by Anthony J. Thibault on 10/14/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 +// + +#ifndef hifi_AnimPose +#define hifi_AnimPose + +#include +#include +#include +#include +#include + +struct AnimPose { + AnimPose() {} + explicit AnimPose(const glm::mat4& mat); + AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} + static const AnimPose identity; + + glm::vec3 xformPoint(const glm::vec3& rhs) const; + glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow + + glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint + AnimPose operator*(const AnimPose& rhs) const; + + AnimPose inverse() const; + operator glm::mat4() const; + + glm::vec3 scale; + glm::quat rot; + glm::vec3 trans; +}; + +inline QDebug operator<<(QDebug debug, const AnimPose& pose) { + debug << "AnimPose, trans = (" << pose.trans.x << pose.trans.y << pose.trans.z << "), rot = (" << pose.rot.x << pose.rot.y << pose.rot.z << pose.rot.w << "), scale = (" << pose.scale.x << pose.scale.y << pose.scale.z << ")"; + return debug; +} + +using AnimPoseVec = std::vector; + +#endif diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index e43a55150c..0db7473c9c 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -16,50 +16,6 @@ #include "AnimationLogging.h" -const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), - glm::quat(), - glm::vec3(0.0f)); - -AnimPose::AnimPose(const glm::mat4& mat) { - scale = extractScale(mat); - rot = glm::normalize(glm::quat_cast(mat)); - trans = extractTranslation(mat); -} - -glm::vec3 AnimPose::operator*(const glm::vec3& rhs) const { - return trans + (rot * (scale * rhs)); -} - -glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const { - return *this * rhs; -} - -// really slow -glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { - glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); - glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); - glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z); - glm::mat3 mat(xAxis, yAxis, zAxis); - glm::mat3 transInvMat = glm::inverse(glm::transpose(mat)); - return transInvMat * rhs; -} - -AnimPose AnimPose::operator*(const AnimPose& rhs) const { - return AnimPose(static_cast(*this) * static_cast(rhs)); -} - -AnimPose AnimPose::inverse() const { - return AnimPose(glm::inverse(static_cast(*this))); -} - -AnimPose::operator glm::mat4() const { - glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); - glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); - glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z); - return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f), - glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f)); -} - AnimSkeleton::AnimSkeleton(const FBXGeometry& fbxGeometry) { // convert to std::vector of joints std::vector joints; diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index d59719df73..9dda313528 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -16,33 +16,7 @@ #include #include - -struct AnimPose { - AnimPose() {} - explicit AnimPose(const glm::mat4& mat); - AnimPose(const glm::vec3& scaleIn, const glm::quat& rotIn, const glm::vec3& transIn) : scale(scaleIn), rot(rotIn), trans(transIn) {} - static const AnimPose identity; - - glm::vec3 xformPoint(const glm::vec3& rhs) const; - glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow - - glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint - AnimPose operator*(const AnimPose& rhs) const; - - AnimPose inverse() const; - operator glm::mat4() const; - - glm::vec3 scale; - glm::quat rot; - glm::vec3 trans; -}; - -inline QDebug operator<<(QDebug debug, const AnimPose& pose) { - debug << "AnimPose, trans = (" << pose.trans.x << pose.trans.y << pose.trans.z << "), rot = (" << pose.rot.x << pose.rot.y << pose.rot.z << pose.rot.w << "), scale = (" << pose.scale.x << pose.scale.y << pose.scale.z << ")"; - return debug; -} - -using AnimPoseVec = std::vector; +#include "AnimPose.h" class AnimSkeleton { public: diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 81b294e66c..e9e5ea95de 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -11,6 +11,9 @@ #include "AnimUtil.h" #include "GLMHelpers.h" +// TODO: use restrict keyword +// TODO: excellent candidate for simd vectorization. + void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) { for (size_t i = 0; i < numPoses; i++) { const AnimPose& aPose = a[i]; @@ -20,3 +23,42 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A result[i].trans = lerp(aPose.trans, bPose.trans, alpha); } } + +float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, + const QString& id, AnimNode::Triggers& triggersOut) { + + float frame = currentFrame; + const float clampedStartFrame = std::min(startFrame, endFrame); + if (fabsf(clampedStartFrame - endFrame) < 1.0f) { + frame = endFrame; + } else if (timeScale > 0.0f) { + // accumulate time, keeping track of loops and end of animation events. + const float FRAMES_PER_SECOND = 30.0f; + float framesRemaining = (dt * timeScale) * FRAMES_PER_SECOND; + while (framesRemaining > 0.0f) { + float framesTillEnd = endFrame - frame; + // when looping, add one frame between start and end. + if (loopFlag) { + framesTillEnd += 1.0f; + } + if (framesRemaining >= framesTillEnd) { + if (loopFlag) { + // anim loop + triggersOut.push_back(id + "OnLoop"); + framesRemaining -= framesTillEnd; + frame = clampedStartFrame; + } else { + // anim end + triggersOut.push_back(id + "OnDone"); + frame = endFrame; + framesRemaining = 0.0f; + } + } else { + frame += framesRemaining; + framesRemaining = 0.0f; + } + } + } + return frame; +} + diff --git a/libraries/animation/src/AnimUtil.h b/libraries/animation/src/AnimUtil.h index 23c02b6183..6d394be882 100644 --- a/libraries/animation/src/AnimUtil.h +++ b/libraries/animation/src/AnimUtil.h @@ -13,12 +13,12 @@ #include "AnimNode.h" -// TODO: use restrict keyword -// TODO: excellent candidate for simd vectorization. - // this is where the magic happens void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result); +float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag, + const QString& id, AnimNode::Triggers& triggersOut); + #endif diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 4ddae07375..4d66097265 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -381,6 +381,33 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const { return _jointStates[jointIndex].getTransform(); } +void Rig::calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const { + + assert(referenceSpeeds.size() > 0); + + // calculate alpha from linear combination of referenceSpeeds. + float alpha = 0.0f; + if (speed <= referenceSpeeds.front()) { + alpha = 0.0f; + } else if (speed > referenceSpeeds.back()) { + alpha = (float)(referenceSpeeds.size() - 1); + } else { + for (size_t i = 0; i < referenceSpeeds.size() - 1; i++) { + if (referenceSpeeds[i] < speed && speed < referenceSpeeds[i + 1]) { + alpha = (float)i + ((speed - referenceSpeeds[i]) / (referenceSpeeds[i + 1] - referenceSpeeds[i])); + break; + } + } + } + + *alphaOut = alpha; +} + +// animation reference speeds. +static const std::vector FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s +static const std::vector BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s +static const std::vector LATERAL_SPEEDS = { 0.2f, 0.65f }; // m/s + void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) { glm::vec3 front = worldRotation * IDENTITY_FRONT; @@ -389,8 +416,16 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos // but some modes (e.g., hmd standing) update position without updating velocity. // It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...) // So, let's create our own workingVelocity from the worldPosition... + glm::vec3 workingVelocity = _lastVelocity; glm::vec3 positionDelta = worldPosition - _lastPosition; - glm::vec3 workingVelocity = positionDelta / deltaTime; + + // Don't trust position delta if deltaTime is 'small'. + // NOTE: This is mostly just a work around for an issue in oculus 0.7 runtime, where + // Application::idle() is being called more frequently and with smaller dt's then expected. + const float SMALL_DELTA_TIME = 0.006f; // 6 ms + if (deltaTime > SMALL_DELTA_TIME) { + workingVelocity = positionDelta / deltaTime; + } #if !WANT_DEBUG // But for smoothest (non-hmd standing) results, go ahead and use velocity: @@ -399,19 +434,43 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos } #endif + if (deltaTime > SMALL_DELTA_TIME) { + _lastVelocity = workingVelocity; + } + if (_enableAnimGraph) { glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity; + float forwardSpeed = glm::dot(localVel, IDENTITY_FRONT); float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT); float turningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; + // filter speeds using a simple moving average. + _averageForwardSpeed.updateAverage(forwardSpeed); + _averageLateralSpeed.updateAverage(lateralSpeed); + // sine wave LFO var for testing. static float t = 0.0f; - _animVars.set("sine", static_cast(0.5 * sin(t) + 0.5)); + _animVars.set("sine", 2.0f * static_cast(0.5 * sin(t) + 0.5)); - const float ANIM_WALK_SPEED = 1.4f; // m/s - _animVars.set("walkTimeScale", glm::clamp(0.5f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED)); + float moveForwardAlpha = 0.0f; + float moveBackwardAlpha = 0.0f; + float moveLateralAlpha = 0.0f; + + // calcuate the animation alpha and timeScale values based on current speeds and animation reference speeds. + calcAnimAlpha(_averageForwardSpeed.getAverage(), FORWARD_SPEEDS, &moveForwardAlpha); + calcAnimAlpha(-_averageForwardSpeed.getAverage(), BACKWARD_SPEEDS, &moveBackwardAlpha); + calcAnimAlpha(fabsf(_averageLateralSpeed.getAverage()), LATERAL_SPEEDS, &moveLateralAlpha); + + _animVars.set("moveForwardSpeed", _averageForwardSpeed.getAverage()); + _animVars.set("moveForwardAlpha", moveForwardAlpha); + + _animVars.set("moveBackwardSpeed", -_averageForwardSpeed.getAverage()); + _animVars.set("moveBackwardAlpha", moveBackwardAlpha); + + _animVars.set("moveLateralSpeed", fabsf(_averageLateralSpeed.getAverage())); + _animVars.set("moveLateralAlpha", moveLateralAlpha); const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index c23ab3d506..98847b9915 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -44,6 +44,7 @@ #include "AnimNode.h" #include "AnimNodeLoader.h" +#include "SimpleMovingAverage.h" class AnimationHandle; typedef std::shared_ptr AnimationHandlePointer; @@ -219,6 +220,7 @@ public: void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); void updateNeckJoint(int index, const HeadParameters& params); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::quat& worldHeadOrientation, const glm::vec3& lookAt, const glm::vec3& saccade); + void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; QVector _jointStates; int _rootJointIndex = -1; @@ -238,6 +240,7 @@ public: bool _enableAnimGraph = false; glm::vec3 _lastFront; glm::vec3 _lastPosition; + glm::vec3 _lastVelocity; std::shared_ptr _animNode; std::shared_ptr _animSkeleton; @@ -254,6 +257,9 @@ public: float _leftHandOverlayAlpha = 0.0f; float _rightHandOverlayAlpha = 0.0f; + SimpleMovingAverage _averageForwardSpeed{ 10 }; + SimpleMovingAverage _averageLateralSpeed{ 10 }; + private: QMap _stateHandlers; int _nextStateHandlerId {0}; diff --git a/libraries/controllers/src/controllers/DeviceProxy.cpp b/libraries/controllers/src/controllers/DeviceProxy.cpp index f3e9526080..f03354c52d 100644 --- a/libraries/controllers/src/controllers/DeviceProxy.cpp +++ b/libraries/controllers/src/controllers/DeviceProxy.cpp @@ -7,8 +7,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "DeviceProxy.h" - -namespace controller { -} +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "DeviceProxy.h" diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index 31c023b1ea..fadbeee326 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -55,6 +55,9 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(LS, "LS"), makePair(RS, "RS"), + makePair(LS_TOUCH, "LSTouch"), + makePair(RS_TOUCH, "RSTouch"), + // Center buttons makePair(START, "Start"), makePair(BACK, "Back"), @@ -69,26 +72,41 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(LT, "LT"), makePair(RT, "RT"), - // Finger abstractions makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), + makePair(LEFT_THUMB_UP, "LeftThumbUp"), makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), + makePair(RIGHT_THUMB_UP, "RightThumbUp"), + + makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"), + makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"), + makePair(RIGHT_PRIMARY_THUMB_TOUCH, "RightPrimaryThumbTouch"), + makePair(RIGHT_SECONDARY_THUMB_TOUCH, "RightSecondaryThumbTouch"), + + makePair(LEFT_INDEX_POINT, "LeftIndexPoint"), + makePair(RIGHT_INDEX_POINT, "RightIndexPoint"), makePair(LEFT_PRIMARY_INDEX, "LeftPrimaryIndex"), makePair(LEFT_SECONDARY_INDEX, "LeftSecondaryIndex"), makePair(RIGHT_PRIMARY_INDEX, "RightPrimaryIndex"), makePair(RIGHT_SECONDARY_INDEX, "RightSecondaryIndex"), + makePair(LEFT_PRIMARY_INDEX_TOUCH, "LeftPrimaryIndexTouch"), + makePair(LEFT_SECONDARY_INDEX_TOUCH, "LeftSecondaryIndexTouch"), + makePair(RIGHT_PRIMARY_INDEX_TOUCH, "RightPrimaryIndexTouch"), + makePair(RIGHT_SECONDARY_INDEX_TOUCH, "RightSecondaryIndexTouch"), + makePair(LEFT_GRIP, "LeftGrip"), + makePair(LEFT_GRIP_TOUCH, "LeftGripTouch"), makePair(RIGHT_GRIP, "RightGrip"), + makePair(RIGHT_GRIP_TOUCH, "RightGripTouch"), // Poses makePair(LEFT_HAND, "LeftHand"), makePair(RIGHT_HAND, "RightHand"), - // Aliases, PlayStation style names makePair(LB, "L1"), makePair(RB, "R1"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index d5063f6034..bbd33c5cb3 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -39,16 +39,33 @@ namespace controller { // These don't map to SDL types LEFT_PRIMARY_THUMB, LEFT_SECONDARY_THUMB, + LEFT_PRIMARY_THUMB_TOUCH, + LEFT_SECONDARY_THUMB_TOUCH, + LS_TOUCH, + LEFT_THUMB_UP, + RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, + RIGHT_PRIMARY_THUMB_TOUCH, + RIGHT_SECONDARY_THUMB_TOUCH, + RS_TOUCH, + RIGHT_THUMB_UP, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, + LEFT_PRIMARY_INDEX_TOUCH, + LEFT_SECONDARY_INDEX_TOUCH, + LEFT_INDEX_POINT, RIGHT_PRIMARY_INDEX, RIGHT_SECONDARY_INDEX, + RIGHT_PRIMARY_INDEX_TOUCH, + RIGHT_SECONDARY_INDEX_TOUCH, + RIGHT_INDEX_POINT, LEFT_GRIP, + LEFT_GRIP_TOUCH, RIGHT_GRIP, + RIGHT_GRIP_TOUCH, NUM_STANDARD_BUTTONS }; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 1a0fae8158..ec32024b91 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -26,6 +26,7 @@ #include "Logging.h" #include "impl/conditionals/AndConditional.h" +#include "impl/conditionals/NotConditional.h" #include "impl/conditionals/EndpointConditional.h" #include "impl/conditionals/ScriptConditional.h" @@ -676,7 +677,7 @@ Mapping::Pointer UserInputMapper::newMapping(const QString& mappingName) { void UserInputMapper::enableMapping(const QString& mappingName, bool enable) { Locker locker(_lock); - qCDebug(controllers) << "Attempting to enable mapping " << mappingName; + qCDebug(controllers) << "Attempting to " << (enable ? "enable" : "disable") << " mapping " << mappingName; auto iterator = _mappingsByName.find(mappingName); if (_mappingsByName.end() == iterator) { qCWarning(controllers) << "Request to enable / disable unknown mapping " << mappingName; @@ -826,13 +827,31 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) return std::make_shared(children); } else if (value.isString()) { // Support "when" : "GamePad.RB" - auto input = findDeviceInput(value.toString()); + auto conditionalToken = value.toString(); + + // Detect for modifier case (Not...) + QString conditionalModifier; + const QString JSON_CONDITIONAL_MODIFIER_NOT("!"); + if (conditionalToken.startsWith(JSON_CONDITIONAL_MODIFIER_NOT)) { + conditionalModifier = JSON_CONDITIONAL_MODIFIER_NOT; + conditionalToken = conditionalToken.right(conditionalToken.size() - conditionalModifier.size()); + } + + auto input = findDeviceInput(conditionalToken); auto endpoint = endpointFor(input); if (!endpoint) { return Conditional::Pointer(); } + auto conditional = std::make_shared(endpoint); - return std::make_shared(endpoint); + if (!conditionalModifier.isEmpty()) { + if (conditionalModifier == JSON_CONDITIONAL_MODIFIER_NOT) { + return std::make_shared(conditional); + } + } + + // Default and conditional behavior + return conditional; } return Conditional::parse(value); diff --git a/libraries/controllers/src/controllers/impl/Endpoint.cpp b/libraries/controllers/src/controllers/impl/Endpoint.cpp index 9e9b13f8ea..e771b1916f 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.cpp +++ b/libraries/controllers/src/controllers/impl/Endpoint.cpp @@ -6,9 +6,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Endpoint.h" - -namespace controller { - -} +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "Endpoint.h" diff --git a/libraries/controllers/src/controllers/impl/Mapping.cpp b/libraries/controllers/src/controllers/impl/Mapping.cpp index 68c43da393..dd8e1c1d48 100644 --- a/libraries/controllers/src/controllers/impl/Mapping.cpp +++ b/libraries/controllers/src/controllers/impl/Mapping.cpp @@ -5,4 +5,11 @@ // 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" + +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "Mapping.h" diff --git a/libraries/controllers/src/controllers/impl/Route.cpp b/libraries/controllers/src/controllers/impl/Route.cpp index 56590e564a..c74f809195 100644 --- a/libraries/controllers/src/controllers/impl/Route.cpp +++ b/libraries/controllers/src/controllers/impl/Route.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Route.h" +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "Route.h" diff --git a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp index 03e16b8cf9..f833eedb60 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp +++ b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "EndpointConditional.h" \ No newline at end of file +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "EndpointConditional.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp index 0c8d602b9e..813c8ebfad 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp +++ b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.cpp @@ -6,4 +6,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + #include "NotConditional.h" + +using namespace controller; + +bool NotConditional::satisfied() { + if (_operand) { + return (!_operand->satisfied()); + } else { + return false; + } +} + diff --git a/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h index 3acda07106..6b19cf9505 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/NotConditional.h @@ -12,5 +12,19 @@ #include "../Conditional.h" +namespace controller { + + class NotConditional : public Conditional { + public: + using Pointer = std::shared_ptr; + + NotConditional(Conditional::Pointer operand) : _operand(operand) { } + + virtual bool satisfied() override; + + private: + Conditional::Pointer _operand; + }; +} #endif diff --git a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp index c083a7147d..5dea1de34e 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "ArrayEndpoint.h" \ No newline at end of file +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "ArrayEndpoint.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp index 4560741d12..7f0e80cbae 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "JSEndpoint.h" \ No newline at end of file +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "JSEndpoint.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp index 09920d249c..89bbe5d777 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "StandardEndpoint.h" \ No newline at end of file +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "StandardEndpoint.h" \ No newline at end of file diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp index 78ffb47693..8bd3d2db89 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "ConstrainToIntegerFilter.h" +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "ConstrainToIntegerFilter.h" diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp index d78942b18f..f1abc8cecd 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "ConstrainToPositiveIntegerFilter.h" +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "ConstrainToPositiveIntegerFilter.h" diff --git a/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp b/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp index db582b84cc..5407c6dd1d 100644 --- a/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/InvertFilter.cpp @@ -6,4 +6,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "InvertFilter.h" +// NOTE: we don't need to include this header unless/until we add additional symbols. +// By removing this header we prevent these warnings on windows: +// +// warning LNK4221: This object file does not define any previously undefined public symbols, +// so it will not be used by any link operation that consumes this library +// +//#include "InvertFilter.h" diff --git a/libraries/gl/src/gl/GlWindow.h b/libraries/gl/src/gl/GlWindow.h index d62d891c12..4956177725 100644 --- a/libraries/gl/src/gl/GlWindow.h +++ b/libraries/gl/src/gl/GlWindow.h @@ -11,7 +11,7 @@ #define hifi_GlWindow_h #include -#include +#include class QOpenGLContext; class QOpenGLDebugLogger; diff --git a/libraries/gpu/src/gpu/Batch.cpp b/libraries/gpu/src/gpu/Batch.cpp index c3e186a630..2e3ea7fc36 100644 --- a/libraries/gpu/src/gpu/Batch.cpp +++ b/libraries/gpu/src/gpu/Batch.cpp @@ -22,6 +22,14 @@ ProfileRange::ProfileRange(const char *name) { ProfileRange::~ProfileRange() { nvtxRangePop(); } + +ProfileRangeBatch::ProfileRangeBatch(gpu::Batch& batch, const char *name) : _batch(batch) { + _batch.pushProfileRange(name); +} + +ProfileRangeBatch::~ProfileRangeBatch() { + _batch.popProfileRange(); +} #endif #define ADD_COMMAND(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size()); @@ -391,3 +399,17 @@ QDebug& operator<<(QDebug& debug, const Batch::CacheState& cacheState) { return debug; } + +// Debugging +void Batch::pushProfileRange(const char* name) { +#if defined(NSIGHT_FOUND) + ADD_COMMAND(pushProfileRange); + _params.push_back(_profileRanges.cache(name)); +#endif +} + +void Batch::popProfileRange() { +#if defined(NSIGHT_FOUND) + ADD_COMMAND(popProfileRange); +#endif +} diff --git a/libraries/gpu/src/gpu/Batch.h b/libraries/gpu/src/gpu/Batch.h index deb70f7a68..8397f92da6 100644 --- a/libraries/gpu/src/gpu/Batch.h +++ b/libraries/gpu/src/gpu/Batch.h @@ -229,6 +229,10 @@ public: // Reset the stage caches and states void resetStages(); + // Debugging + void pushProfileRange(const char* name); + void popProfileRange(); + // TODO: As long as we have gl calls explicitely issued from interface // code, we need to be able to record and batch these calls. THe long // term strategy is to get rid of any GL calls in favor of the HIFI GPU API @@ -324,6 +328,9 @@ public: COMMAND_glColor4f, + COMMAND_pushProfileRange, + COMMAND_popProfileRange, + NUM_COMMANDS, }; typedef std::vector Commands; @@ -389,6 +396,7 @@ public: typedef Cache::Vector PipelineCaches; typedef Cache::Vector FramebufferCaches; typedef Cache::Vector QueryCaches; + typedef Cache::Vector ProfileRangeCaches; typedef Cache>::Vector LambdaCache; // Cache Data in a byte array if too big to fit in Param @@ -416,6 +424,7 @@ public: FramebufferCaches _framebuffers; QueryCaches _queries; LambdaCache _lambdas; + ProfileRangeCaches _profileRanges; NamedBatchDataMap _namedData; @@ -429,6 +438,25 @@ protected: } +#if defined(NSIGHT_FOUND) + +class ProfileRangeBatch { +public: + ProfileRangeBatch(gpu::Batch& batch, const char *name); + ~ProfileRangeBatch(); + +private: + gpu::Batch& _batch; +}; + +#define PROFILE_RANGE_BATCH(batch, name) ProfileRangeBatch profileRangeThis(batch, name); + +#else + +#define PROFILE_RANGE_BATCH(batch, name) + +#endif + QDebug& operator<<(QDebug& debug, const gpu::Batch::CacheState& cacheState); #endif diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 19edbaee5f..e49a3ba6c0 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -15,6 +15,11 @@ #include #include +#if defined(NSIGHT_FOUND) +#include "nvToolsExt.h" +#endif + + using namespace gpu; GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = @@ -69,6 +74,9 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = (&::gpu::GLBackend::do_glUniformMatrix4fv), (&::gpu::GLBackend::do_glColor4f), + + (&::gpu::GLBackend::do_pushProfileRange), + (&::gpu::GLBackend::do_popProfileRange), }; void GLBackend::init() { @@ -710,3 +718,17 @@ void GLBackend::do_glColor4f(Batch& batch, uint32 paramOffset) { } (void) CHECK_GL_ERROR(); } + + +void GLBackend::do_pushProfileRange(Batch& batch, uint32 paramOffset) { +#if defined(NSIGHT_FOUND) + auto name = batch._profileRanges.get(batch._params[paramOffset]._uint); + nvtxRangePush(name.c_str()); +#endif +} + +void GLBackend::do_popProfileRange(Batch& batch, uint32 paramOffset) { +#if defined(NSIGHT_FOUND) + nvtxRangePop(); +#endif +} diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 6c359d81f1..9f1e17205c 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -479,6 +479,9 @@ protected: void do_glColor4f(Batch& batch, uint32 paramOffset); + void do_pushProfileRange(Batch& batch, uint32 paramOffset); + void do_popProfileRange(Batch& batch, uint32 paramOffset); + typedef void (GLBackend::*CommandCall)(Batch&, uint32); static CommandCall _commandCalls[Batch::NUM_COMMANDS]; }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 9cd510c521..2157a3a010 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -59,11 +59,27 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event, unsigned int devic // key pressed again ? without catching the release event ? } _lastCursor = event->pos(); + _mousePressAt = event->pos(); + + eraseMouseClicked(); } void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { auto input = makeInput((Qt::MouseButton) event->button()); _buttonPressedMap.erase(input.getChannel()); + + // if we pressed and released at the same location, then create a "_CLICKED" input for this button + // we might want to add some small tolerance to this so if you do a small drag it still counts as + // a clicked. + if (_mousePressAt == event->pos()) { + _buttonPressedMap.insert(makeInput((Qt::MouseButton) event->button(), true).getChannel()); + } +} + +void KeyboardMouseDevice::eraseMouseClicked() { + _buttonPressedMap.erase(makeInput(Qt::LeftButton, true).getChannel()); + _buttonPressedMap.erase(makeInput(Qt::MiddleButton, true).getChannel()); + _buttonPressedMap.erase(makeInput(Qt::RightButton, true).getChannel()); } void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) { @@ -77,6 +93,8 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int device _axisStateMap[makeInput(MOUSE_AXIS_Y_NEG).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); _lastCursor = currentPos; + + eraseMouseClicked(); } void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { @@ -138,14 +156,17 @@ controller::Input KeyboardMouseDevice::makeInput(Qt::Key code) const { return controller::Input(_deviceID, shortCode, controller::ChannelType::BUTTON); } -controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code) const { +controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code, bool clicked) const { switch (code) { case Qt::LeftButton: - return controller::Input(_deviceID, MOUSE_BUTTON_LEFT, controller::ChannelType::BUTTON); + return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_LEFT_CLICKED : + MOUSE_BUTTON_LEFT, controller::ChannelType::BUTTON); case Qt::RightButton: - return controller::Input(_deviceID, MOUSE_BUTTON_RIGHT, controller::ChannelType::BUTTON); + return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_RIGHT_CLICKED : + MOUSE_BUTTON_RIGHT, controller::ChannelType::BUTTON); case Qt::MiddleButton: - return controller::Input(_deviceID, MOUSE_BUTTON_MIDDLE, controller::ChannelType::BUTTON); + return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_MIDDLE_CLICKED : + MOUSE_BUTTON_MIDDLE, controller::ChannelType::BUTTON); default: return controller::Input(); }; @@ -182,9 +203,13 @@ controller::Input::NamedVector KeyboardMouseDevice::getAvailableInputs() const { 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(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseClick")); - availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseClick")); - availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseClick")); + availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton")); + availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton")); + availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseButton")); + + availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton, true), "LeftMouseClicked")); + availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton, true), "MiddleMouseClicked")); + availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton, true), "RightMouseClicked")); availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_POS), "MouseMoveRight")); availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "MouseMoveLeft")); diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 1ff77d2dce..5d86821db1 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -37,6 +37,9 @@ public: MOUSE_BUTTON_LEFT = KEYBOARD_LAST + 1, MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_MIDDLE, + MOUSE_BUTTON_LEFT_CLICKED, + MOUSE_BUTTON_RIGHT_CLICKED, + MOUSE_BUTTON_MIDDLE_CLICKED, }; enum MouseAxisChannel { @@ -83,6 +86,7 @@ public: void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0); void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0); void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0); + void eraseMouseClicked(); void touchBeginEvent(const QTouchEvent* event); void touchEndEvent(const QTouchEvent* event); @@ -92,7 +96,7 @@ public: // Let's make it easy for Qt because we assume we love Qt forever controller::Input makeInput(Qt::Key code) const; - controller::Input makeInput(Qt::MouseButton code) const; + controller::Input makeInput(Qt::MouseButton code, bool clicked = false) const; controller::Input makeInput(MouseAxisChannel axis) const; controller::Input makeInput(TouchAxisChannel axis) const; controller::Input makeInput(TouchButtonChannel button) const; @@ -101,6 +105,7 @@ public: protected: QPoint _lastCursor; + QPoint _mousePressAt; glm::vec2 _lastTouch; bool _isTouching = false; diff --git a/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp b/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp index ec2fa2ed07..c6f5491a5a 100644 --- a/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp +++ b/libraries/input-plugins/src/input-plugins/SDL2Manager.cpp @@ -20,27 +20,27 @@ #ifdef HAVE_SDL2 static_assert( - controller::A == SDL_CONTROLLER_BUTTON_A && - controller::B == SDL_CONTROLLER_BUTTON_B && - controller::X == SDL_CONTROLLER_BUTTON_X && - controller::Y == SDL_CONTROLLER_BUTTON_Y && - controller::BACK == SDL_CONTROLLER_BUTTON_BACK && - controller::GUIDE == SDL_CONTROLLER_BUTTON_GUIDE && - controller::START == SDL_CONTROLLER_BUTTON_START && - controller::LS == SDL_CONTROLLER_BUTTON_LEFTSTICK && - controller::RS == SDL_CONTROLLER_BUTTON_RIGHTSTICK && - controller::LB == SDL_CONTROLLER_BUTTON_LEFTSHOULDER && - controller::RB == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER && - controller::DU == SDL_CONTROLLER_BUTTON_DPAD_UP && - controller::DD == SDL_CONTROLLER_BUTTON_DPAD_DOWN && - controller::DL == SDL_CONTROLLER_BUTTON_DPAD_LEFT && - controller::DR == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && - controller::LX == SDL_CONTROLLER_AXIS_LEFTX && - controller::LY == SDL_CONTROLLER_AXIS_LEFTY && - controller::RX == SDL_CONTROLLER_AXIS_RIGHTX && - controller::RY == SDL_CONTROLLER_AXIS_RIGHTY && - controller::LT == SDL_CONTROLLER_AXIS_TRIGGERLEFT && - controller::RT == SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + (int)controller::A == (int)SDL_CONTROLLER_BUTTON_A && + (int)controller::B == (int)SDL_CONTROLLER_BUTTON_B && + (int)controller::X == (int)SDL_CONTROLLER_BUTTON_X && + (int)controller::Y == (int)SDL_CONTROLLER_BUTTON_Y && + (int)controller::BACK == (int)SDL_CONTROLLER_BUTTON_BACK && + (int)controller::GUIDE == (int)SDL_CONTROLLER_BUTTON_GUIDE && + (int)controller::START == (int)SDL_CONTROLLER_BUTTON_START && + (int)controller::LS == (int)SDL_CONTROLLER_BUTTON_LEFTSTICK && + (int)controller::RS == (int)SDL_CONTROLLER_BUTTON_RIGHTSTICK && + (int)controller::LB == (int)SDL_CONTROLLER_BUTTON_LEFTSHOULDER && + (int)controller::RB == (int)SDL_CONTROLLER_BUTTON_RIGHTSHOULDER && + (int)controller::DU == (int)SDL_CONTROLLER_BUTTON_DPAD_UP && + (int)controller::DD == (int)SDL_CONTROLLER_BUTTON_DPAD_DOWN && + (int)controller::DL == (int)SDL_CONTROLLER_BUTTON_DPAD_LEFT && + (int)controller::DR == (int)SDL_CONTROLLER_BUTTON_DPAD_RIGHT && + (int)controller::LX == (int)SDL_CONTROLLER_AXIS_LEFTX && + (int)controller::LY == (int)SDL_CONTROLLER_AXIS_LEFTY && + (int)controller::RX == (int)SDL_CONTROLLER_AXIS_RIGHTX && + (int)controller::RY == (int)SDL_CONTROLLER_AXIS_RIGHTY && + (int)controller::LT == (int)SDL_CONTROLLER_AXIS_TRIGGERLEFT && + (int)controller::RT == (int)SDL_CONTROLLER_AXIS_TRIGGERRIGHT, "SDL2 equvalence: Enums and values from StandardControls.h are assumed to match enums from SDL_gamecontroller.h"); #endif diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 17a5afd09a..71b6cf3872 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -84,7 +84,7 @@ private: const glm::vec3& TWO() { return Vectors::TWO; } const glm::vec3& HALF() { return Vectors::HALF; } const glm::vec3& RIGHT() { return Vectors::RIGHT; } - const glm::vec3& UP() { return Vectors::UNIT_X; } + const glm::vec3& UP() { return Vectors::UP; } const glm::vec3& FRONT() { return Vectors::FRONT; } }; diff --git a/libraries/shared/src/AtRestDetector.cpp b/libraries/shared/src/AtRestDetector.cpp index 5e623a005c..d790e2b066 100644 --- a/libraries/shared/src/AtRestDetector.cpp +++ b/libraries/shared/src/AtRestDetector.cpp @@ -1,5 +1,19 @@ +// +// AtRestDetector.cpp +// libraries/shared/src +// +// Created by Anthony Thibault on 10/6/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 "AtRestDetector.h" + +#include "NumericalConstants.h" #include "SharedLogging.h" +#include "SharedUtil.h" AtRestDetector::AtRestDetector(const glm::vec3& startPosition, const glm::quat& startRotation) { reset(startPosition, startRotation); @@ -12,9 +26,14 @@ void AtRestDetector::reset(const glm::vec3& startPosition, const glm::quat& star glm::quat ql = glm::log(startRotation); _quatLogAverage = glm::vec3(ql.x, ql.y, ql.z); _quatLogVariance = 0.0f; + _lastUpdateTime = usecTimestampNow(); + _isAtRest = false; } -bool AtRestDetector::update(float dt, const glm::vec3& position, const glm::quat& rotation) { +bool AtRestDetector::update(const glm::vec3& position, const glm::quat& rotation) { + uint64_t now = usecTimestampNow(); + float dt = (float)(_lastUpdateTime - now) / (float)USECS_PER_SECOND; + _lastUpdateTime = now; const float TAU = 1.0f; float delta = glm::min(dt / TAU, 1.0f); @@ -37,5 +56,6 @@ bool AtRestDetector::update(float dt, const glm::vec3& position, const glm::quat const float POSITION_VARIANCE_THRESHOLD = 0.001f; const float QUAT_LOG_VARIANCE_THRESHOLD = 0.00002f; - return _positionVariance < POSITION_VARIANCE_THRESHOLD && _quatLogVariance < QUAT_LOG_VARIANCE_THRESHOLD; + _isAtRest = _positionVariance < POSITION_VARIANCE_THRESHOLD && _quatLogVariance < QUAT_LOG_VARIANCE_THRESHOLD; + return _isAtRest; } diff --git a/libraries/shared/src/AtRestDetector.h b/libraries/shared/src/AtRestDetector.h index d82e54a692..525156de65 100644 --- a/libraries/shared/src/AtRestDetector.h +++ b/libraries/shared/src/AtRestDetector.h @@ -21,14 +21,17 @@ public: void reset(const glm::vec3& startPosition, const glm::quat& startRotation); // returns true if object is at rest, dt in assumed to be seconds. - bool update(float dt, const glm::vec3& position, const glm::quat& startRotation); + bool update(const glm::vec3& position, const glm::quat& startRotation); + + bool isAtRest() const { return _isAtRest; } protected: glm::vec3 _positionAverage; - float _positionVariance; - glm::vec3 _quatLogAverage; - float _quatLogVariance; + uint64_t _lastUpdateTime { 0 }; + float _positionVariance { 0.0f }; + float _quatLogVariance { 0.0f }; + bool _isAtRest { false }; }; #endif diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 7bdd3bf2de..9c1bbe23a4 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -183,6 +183,11 @@ T toNormalizedDeviceScale(const T& value, const T& size) { #define PITCH(euler) euler.x #define ROLL(euler) euler.z +// float - linear interpolate +inline float lerp(float x, float y, float a) { + return x * (1.0f - a) + (y * a); +} + // vec2 lerp - linear interpolate template glm::detail::tvec2 lerp(const glm::detail::tvec2& x, const glm::detail::tvec2& y, T a) { diff --git a/plugins/oculus/CMakeLists.txt b/plugins/oculus/CMakeLists.txt index 62999cbb7e..fe1a87d6b6 100644 --- a/plugins/oculus/CMakeLists.txt +++ b/plugins/oculus/CMakeLists.txt @@ -8,6 +8,9 @@ if (WIN32) + # we're using static GLEW, so define GLEW_STATIC + add_definitions(-DGLEW_STATIC) + set(TARGET_NAME oculus) setup_hifi_plugin() link_hifi_libraries(shared gl plugins display-plugins) diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 0120bcf2aa..3898d586ad 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -9,15 +9,15 @@ #include -#include -#include +#include +#include #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include @@ -193,7 +193,7 @@ void OculusLegacyDisplayPlugin::deactivate() { // DLL based display plugins MUST initialize GLEW inside the DLL code. void OculusLegacyDisplayPlugin::customizeContext() { glewExperimental = true; - GLenum err = glewInit(); + glewInit(); glGetError(); WindowOpenGLDisplayPlugin::customizeContext(); } diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index a08288000e..1b5bb4739a 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -13,6 +13,7 @@ #include "AnimBlendLinear.h" #include "AnimationLogging.h" #include "AnimVariant.h" +#include "AnimUtil.h" #include <../QTestExtensions.h> @@ -30,8 +31,8 @@ void AnimTests::cleanupTestCase() { } void AnimTests::testClipInternalState() { - std::string id = "my anim clip"; - std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; + QString id = "my anim clip"; + QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 20.0f; float timeScale = 1.1f; @@ -55,8 +56,8 @@ static float framesToSec(float secs) { } void AnimTests::testClipEvaulate() { - std::string id = "myClipNode"; - std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; + QString id = "myClipNode"; + QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 22.0f; float timeScale = 1.0f; @@ -73,8 +74,8 @@ void AnimTests::testClipEvaulate() { // does it loop? triggers.clear(); - clip.evaluate(vars, framesToSec(11.0f), triggers); - QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); + clip.evaluate(vars, framesToSec(12.0f), triggers); + QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); // Note: frame 3 and not 4, because extra frame between start and end. // did we receive a loop trigger? QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end()); @@ -90,8 +91,8 @@ void AnimTests::testClipEvaulate() { } void AnimTests::testClipEvaulateWithVars() { - std::string id = "myClipNode"; - std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; + QString id = "myClipNode"; + QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; float startFrame = 2.0f; float endFrame = 22.0f; float timeScale = 1.0f; @@ -126,9 +127,9 @@ void AnimTests::testClipEvaulateWithVars() { } void AnimTests::testLoader() { - auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/8ba57a8f0a76f88b39a11f77f8d9df04af9cec95/test.json"); + auto url = QUrl("https://gist.githubusercontent.com/hyperlogic/857129fe04567cbe670f/raw/0c54500f480fd7314a5aeb147c45a8a707edcc2e/test.json"); // NOTE: This will warn about missing "test01.fbx", "test02.fbx", etc. if the resource loading code doesn't handle relative pathnames! - // However, the test will proceed. + // However, the test will proceed. AnimNodeLoader loader(url); const int timeout = 1000; @@ -238,3 +239,87 @@ void AnimTests::testVariant() { QVERIFY(m[1].z == -7.0f); QVERIFY(m[3].w == 16.0f); } + +void AnimTests::testAccumulateTime() { + + float startFrame = 0.0f; + float endFrame = 10.0f; + float timeScale = 1.0f; + testAccumulateTimeWithParameters(startFrame, endFrame, timeScale); + + startFrame = 5.0f; + endFrame = 15.0f; + timeScale = 1.0f; + testAccumulateTimeWithParameters(startFrame, endFrame, timeScale); + + startFrame = 0.0f; + endFrame = 10.0f; + timeScale = 0.5f; + testAccumulateTimeWithParameters(startFrame, endFrame, timeScale); + + startFrame = 5.0f; + endFrame = 15.0f; + timeScale = 2.0f; + testAccumulateTimeWithParameters(startFrame, endFrame, timeScale); +} + +void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const { + + float dt = (1.0f / 30.0f) / timeScale; // sec + QString id = "testNode"; + AnimNode::Triggers triggers; + bool loopFlag = false; + + float resultFrame = accumulateTime(startFrame, endFrame, timeScale, startFrame, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == startFrame + 1.0f); + QVERIFY(triggers.empty()); + triggers.clear(); + + resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == startFrame + 2.0f); + QVERIFY(triggers.empty()); + triggers.clear(); + + resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == startFrame + 3.0f); + QVERIFY(triggers.empty()); + triggers.clear(); + + // test onDone trigger and frame clamping. + resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == endFrame); + QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone"); + triggers.clear(); + + resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == endFrame); + QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnDone"); + triggers.clear(); + + // test onLoop trigger and looping frame logic + loopFlag = true; + + // should NOT trigger loop even though we stop at last frame, because there is an extra frame between end and start frames. + resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 1.0f, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == endFrame); + QVERIFY(triggers.empty()); + triggers.clear(); + + // now we should hit loop trigger + resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == startFrame); + QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); + triggers.clear(); + + // should NOT trigger loop, even though we move past the end frame, because of extra frame between end and start. + resultFrame = accumulateTime(startFrame, endFrame, timeScale, endFrame - 0.5f, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == endFrame + 0.5f); + QVERIFY(triggers.empty()); + triggers.clear(); + + // now we should hit loop trigger + resultFrame = accumulateTime(startFrame, endFrame, timeScale, resultFrame, dt, loopFlag, id, triggers); + QVERIFY(resultFrame == startFrame + 0.5f); + QVERIFY(!triggers.empty() && triggers[0] == "testNodeOnLoop"); + triggers.clear(); +} diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index e667444657..7bd05369c7 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -15,6 +15,8 @@ class AnimTests : public QObject { Q_OBJECT +public: + void testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const; private slots: void initTestCase(); void cleanupTestCase(); @@ -23,6 +25,7 @@ private slots: void testClipEvaulateWithVars(); void testLoader(); void testVariant(); + void testAccumulateTime(); }; #endif // hifi_AnimTests_h diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 43d23e4b3c..f56c705ce4 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -114,7 +114,6 @@ createTargets(); createTargetResetter(); - createBasketballHoop(); createBasketballRack(); createBasketballResetter(); @@ -130,14 +129,11 @@ z: 503.49 }); - createSprayCan({ x: 549.7, y: 495.6, z: 503.91 }); - - } function deleteAllToys() { @@ -930,45 +926,6 @@ }); } - function createBasketballHoop() { - var position = { - x: 539.23, - y: 496.13, - z: 475.89 - }; - var rotation = Quat.fromPitchYawRollDegrees(0, 58.49, 0); - - var hoopURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop.fbx"; - var hoopCollisionHullURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop_collision_hull.obj"; - - var hoop = Entities.addEntity({ - type: "Model", - modelURL: hoopURL, - position: position, - rotation: rotation, - shapeType: 'compound', - gravity: { - x: 0, - y: -9.8, - z: 0 - }, - dimensions: { - x: 1.89, - y: 3.99, - z: 3.79 - }, - compoundShapeURL: hoopCollisionHullURL, - userData: JSON.stringify({ - resetMe: { - resetMe: true - }, - grabbableKey: { - grabbable: false - } - }) - }); - } - function createWand(position) { var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj'; diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 74eb3c85ac..b138db163d 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -10,7 +10,7 @@ //per script -/*global deleteAllToys, createAllToys, createGates, createPingPongBallGun, createFire, createPottedPlant, createCombinedArmChair, createBasketballHoop, createBasketBall, createSprayCan, createDoll, createWand, createDice, createCat, deleteAllToys, createFlashlight, createBlocks, createMagballs, createLights */ +/*global deleteAllToys, createAllToys, createGates, createPingPongBallGun, createFire, createPottedPlant, createCombinedArmChair, createBasketBall, createSprayCan, createDoll, createWand, createDice, createCat, deleteAllToys, createFlashlight, createBlocks, createMagballs, createLights */ var utilitiesScript = Script.resolvePath("../examples/libraries/utils.js"); Script.include(utilitiesScript); @@ -87,7 +87,6 @@ MasterReset = function() { createTargets(); createTargetResetter(); - createBasketballHoop(); createBasketballRack(); createBasketballResetter(); @@ -908,45 +907,6 @@ MasterReset = function() { }); } - function createBasketballHoop() { - var position = { - x: 539.23, - y: 496.13, - z: 475.89 - }; - var rotation = Quat.fromPitchYawRollDegrees(0, 58.49, 0); - - var hoopURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop.fbx"; - var hoopCollisionHullURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop_collision_hull.obj"; - - var hoop = Entities.addEntity({ - type: "Model", - modelURL: hoopURL, - position: position, - rotation: rotation, - shapeType: 'compound', - gravity: { - x: 0, - y: -9.8, - z: 0 - }, - dimensions: { - x: 1.89, - y: 3.99, - z: 3.79 - }, - compoundShapeURL: hoopCollisionHullURL, - userData: JSON.stringify({ - resetMe: { - resetMe: true - }, - grabbableKey: { - grabbable: false - } - }) - }); - } - function createWand(position) { var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj';