merge from upstream

This commit is contained in:
Seth Alves 2015-11-03 13:35:17 -08:00
commit dba9eb7046
66 changed files with 1702 additions and 511 deletions

View file

@ -4,10 +4,22 @@ set(EXTERNAL_NAME sixense)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) 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( ExternalProject_Add(
${EXTERNAL_NAME} ${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip URL ${SIXENSE_URL}
URL_MD5 752a3901f334124e9cffc2ba4136ef7d URL_MD5 ${SIXENSE_URL_MD5}
CONFIGURE_COMMAND "" CONFIGURE_COMMAND ""
BUILD_COMMAND "" BUILD_COMMAND ""
INSTALL_COMMAND "" INSTALL_COMMAND ""
@ -30,8 +42,18 @@ if (WIN32)
set(ARCH_SUFFIX "") set(ARCH_SUFFIX "")
endif() endif()
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${SOURCE_DIR}/lib/${ARCH_DIR}/VS2013/release_dll/sixense${ARCH_SUFFIX}.lib" CACHE TYPE INTERNAL) if (${SIXENSE_NEW_LAYOUT})
add_paths_to_fixup_libs("${SOURCE_DIR}/bin/${ARCH_DIR}/VS2013/release_dll") # 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) elseif(APPLE)

View file

@ -10,5 +10,35 @@
# #
include(BundleUtilities) 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@") message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@")
fixup_bundle("${BUNDLE_EXECUTABLE}" "" "@FIXUP_LIBS@") fixup_bundle("${BUNDLE_EXECUTABLE}" "" "@FIXUP_LIBS@")

View file

@ -352,14 +352,14 @@ function MyController(hand) {
var intersection = Entities.findRayIntersection(pickRayBacked, true); 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. // the ray is intersecting something we can move.
var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection);
this.grabbedEntity = intersection.entityID; 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 //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); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA);
if (grabbableData["turnOffOppositeBeam"] === true) { if (grabbableData["turnOffOppositeBeam"]) {
if (this.hand === RIGHT_HAND) { if (this.hand === RIGHT_HAND) {
disabledHand = LEFT_HAND; disabledHand = LEFT_HAND;
} else { } else {
@ -369,7 +369,7 @@ function MyController(hand) {
disabledHand = 'none'; disabledHand = 'none';
} }
if (grabbableData.grabbable === false) { if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) {
this.grabbedEntity = null; this.grabbedEntity = null;
continue; continue;
} }
@ -379,7 +379,6 @@ function MyController(hand) {
} }
if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) {
// the hand is very close to the intersected object. go into close-grabbing mode. // 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) { if (grabbableData.wantsTrigger) {
this.setState(STATE_NEAR_GRABBING_NON_COLLIDING); this.setState(STATE_NEAR_GRABBING_NON_COLLIDING);
} else { } else {
@ -391,7 +390,7 @@ function MyController(hand) {
this.grabbedEntity = null; this.grabbedEntity = null;
} else { } else {
// the hand is far from the intersected object. go into distance-holding mode // 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); this.setState(STATE_DISTANCE_HOLDING);
} else { } else {
this.setState(STATE_FAR_GRABBING_NON_COLLIDING); this.setState(STATE_FAR_GRABBING_NON_COLLIDING);
@ -409,7 +408,7 @@ function MyController(hand) {
for (i = 0; i < nearbyEntities.length; i++) { for (i = 0; i < nearbyEntities.length; i++) {
var grabbableDataForCandidate = var grabbableDataForCandidate =
getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA); getEntityCustomData(GRABBABLE_DATA_KEY, nearbyEntities[i], DEFAULT_GRABBABLE_DATA);
if (grabbableDataForCandidate.grabbable === false) { if (!grabbableDataForCandidate.grabbable) {
continue; continue;
} }
var propsForCandidate = var propsForCandidate =
@ -427,7 +426,7 @@ function MyController(hand) {
} }
if (grabbableData.wantsTrigger) { if (grabbableData.wantsTrigger) {
this.setState(STATE_NEAR_GRABBING_NON_COLLIDING); this.setState(STATE_NEAR_GRABBING_NON_COLLIDING);
} else if (props.locked === 0) { } else if (!props.locked) {
this.setState(STATE_NEAR_GRABBING); this.setState(STATE_NEAR_GRABBING);
} }
} }
@ -446,7 +445,7 @@ function MyController(hand) {
this.currentObjectPosition = grabbedProperties.position; this.currentObjectPosition = grabbedProperties.position;
this.currentObjectRotation = grabbedProperties.rotation; this.currentObjectRotation = grabbedProperties.rotation;
this.currentObjectTime = now; this.currentObjectTime = now;
this.handPreviousPosition = handControllerPosition; this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position);
this.handPreviousRotation = handRotation; this.handPreviousRotation = handRotation;
this.actionID = NULL_ACTION_ID; this.actionID = NULL_ACTION_ID;
@ -523,11 +522,10 @@ function MyController(hand) {
this.currentAvatarOrientation = currentOrientation; this.currentAvatarOrientation = currentOrientation;
// how far did hand move this timestep? // how far did hand move this timestep?
var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition); var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition);
this.handPreviousPosition = handControllerPosition; this.handRelativePreviousPosition = handToAvatar;
// magnify the hand movement but not the change from avatar movement & rotation // magnify the hand movement but not the change from avatar movement & rotation
handMoved = Vec3.subtract(handMoved, avatarDeltaPosition);
handMoved = Vec3.subtract(handMoved, handMovementFromTurning); handMoved = Vec3.subtract(handMoved, handMovementFromTurning);
var superHandMoved = Vec3.multiply(handMoved, radius); 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 grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
var turnOffOtherHand = grabbableData["turnOffOtherHand"]; 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 //don't activate the second hand grab because the script is handling the second hand logic
return; return;
} }
@ -783,11 +781,11 @@ function MyController(hand) {
// we haven't been touched before, but either right or left is touching us now // we haven't been touched before, but either right or left is touching us now
_this.allTouchedIDs[id] = true; _this.allTouchedIDs[id] = true;
_this.startTouch(id); _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 // we have been touched before and are still being touched
// continue touch // continue touch
_this.continueTouch(id); _this.continueTouch(id);
} else if (_this.allTouchedIDs[id] === true) { } else if (_this.allTouchedIDs[id]) {
delete _this.allTouchedIDs[id]; delete _this.allTouchedIDs[id];
_this.stopTouch(id); _this.stopTouch(id);

View file

@ -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);
});

341
examples/libraries/fjs.js Normal file
View file

@ -0,0 +1,341 @@
/*
http://functionaljs.com/
https://github.com/leecrossley/functional-js/
The MIT License (MIT)
Copyright © 2015 Lee Crossley <leee@hotmail.co.uk>
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;
}

View file

@ -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)

View file

@ -2,7 +2,7 @@ set(TARGET_NAME interface)
project(${TARGET_NAME}) project(${TARGET_NAME})
# set a default root dir for each of our optional externals if it was not passed # 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) if(WIN32)
list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient") list(APPEND OPTIONAL_EXTERNALS "3DConnexionClient")

View file

@ -13,17 +13,12 @@
{ "from": "Hydra.RB", "to": "Standard.RB" }, { "from": "Hydra.RB", "to": "Standard.RB" },
{ "from": "Hydra.RS", "to": "Standard.RS" }, { "from": "Hydra.RS", "to": "Standard.RS" },
{ "from": "Hydra.L0", "to": "Standard.Back" }, { "from": [ "Hydra.L3", "Hydra.L4" ], "to": "Standard.LeftPrimaryThumb" },
{ "from": "Hydra.L1", "to": "Standard.DL" }, { "from": [ "Hydra.L1", "Hydra.L2" ], "to": "Standard.LeftSecondaryThumb" },
{ "from": "Hydra.L2", "to": "Standard.DD" },
{ "from": "Hydra.L3", "to": "Standard.DR" }, { "from": [ "Hydra.R3", "Hydra.R4" ], "to": "Standard.RightPrimaryThumb" },
{ "from": "Hydra.L4", "to": "Standard.DU" }, { "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.LeftHand", "to": "Standard.LeftHand" },
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" } { "from": "Hydra.RightHand", "to": "Standard.RightHand" }

View file

@ -4,8 +4,8 @@
{ "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.A", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.A", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" }, { "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.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.C", "when": "Keyboard.Shift", "to": "Actions.BOOM_OUT", "filters": [ { "type": "scale", "scale": 0.05 } ] },
{ "from": "Keyboard.S", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.S", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" },
@ -13,7 +13,7 @@
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": [ "Application.InHMD", "Application.ComfortMode", "Keyboard.RightMouseClick" ], "when": [ "Application.InHMD", "Application.ComfortMode", "Keyboard.RightMouseButton" ],
"to": "Actions.StepYaw", "to": "Actions.StepYaw",
"filters": "filters":
[ [
@ -46,7 +46,7 @@
}, },
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": "Keyboard.RightMouseClick", "when": "Keyboard.RightMouseButton",
"to": "Actions.Yaw" "to": "Actions.Yaw"
}, },
@ -55,8 +55,8 @@
{ "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" },
{ "from": "Keyboard.Left", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.Left", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Right", "when": "Keyboard.RightMouseButton", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" },
@ -68,8 +68,8 @@
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" }, { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
{ "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_UP" }, { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP" },
{ "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN" },
{ "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" },
{ "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" }, { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" },
@ -82,6 +82,6 @@
{ "from": "Keyboard.Space", "to": "Actions.SHIFT" }, { "from": "Keyboard.Space", "to": "Actions.SHIFT" },
{ "from": "Keyboard.R", "to": "Actions.ACTION1" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" },
{ "from": "Keyboard.T", "to": "Actions.ACTION2" }, { "from": "Keyboard.T", "to": "Actions.ACTION2" },
{ "from": "Keyboard.RightMouseClick", "to": "Actions.ContextMenu" } { "from": "Keyboard.RightMouseClicked", "to": "Actions.ContextMenu" }
] ]
} }

View file

@ -2,9 +2,8 @@
"name": "Standard to Action", "name": "Standard to Action",
"channels": [ "channels": [
{ "from": "Standard.LY", "to": "Actions.TranslateZ" }, { "from": "Standard.LY", "to": "Actions.TranslateZ" },
{ "from": "Standard.LX", "to": "Actions.TranslateX" },
{ "from": "Standard.RX", { "from": "Standard.LX",
"when": [ "Application.InHMD", "Application.ComfortMode" ], "when": [ "Application.InHMD", "Application.ComfortMode" ],
"to": "Actions.StepYaw", "to": "Actions.StepYaw",
"filters": "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" }, { "from": "Standard.RY", "filters": "invert", "to": "Actions.TranslateY" },

View file

@ -1,15 +1,15 @@
{ {
"name": "Vive to Standard", "name": "Vive to Standard",
"channels": [ "channels": [
{ "from": "Vive.LY", "filters": [ "invert", { "type": "deadZone", "min": 0.7 } ], "to": "Standard.LY" }, { "from": "Vive.LY", "when": "Vive.LS", "filters": "invert", "to": "Standard.LY" },
{ "from": "Vive.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, { "from": "Vive.LX", "when": "Vive.LS", "to": "Standard.LX" },
{ "from": "Vive.LT", "to": "Standard.LT" }, { "from": "Vive.LT", "to": "Standard.LT" },
{ "from": "Vive.LB", "to": "Standard.LB" }, { "from": "Vive.LB", "to": "Standard.LB" },
{ "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LS", "to": "Standard.LS" },
{ "from": "Vive.RY", "filters": "invert", "to": "Standard.RY" }, { "from": "Vive.RY", "when": "Vive.RS", "filters": "invert", "to": "Standard.RY" },
{ "from": "Vive.RX", "to": "Standard.RX" }, { "from": "Vive.RX", "when": "Vive.RS", "to": "Standard.RX" },
{ "from": "Vive.RT", "to": "Standard.RT" }, { "from": "Vive.RT", "to": "Standard.RT" },
{ "from": "Vive.RB", "to": "Standard.RB" }, { "from": "Vive.RB", "to": "Standard.RB" },

View file

@ -523,29 +523,89 @@
}, },
{ {
"id": "walkFwd", "id": "walkFwd",
"type": "clip", "type": "blendLinearMove",
"data": { "data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", "alpha": 0.0,
"startFrame": 0.0, "desiredSpeed": 1.4,
"endFrame": 35.0, "characteristicSpeeds": [0.5, 1.4, 4.5],
"timeScale": 1.0, "alphaVar": "moveForwardAlpha",
"loopFlag": true, "desiredSpeedVar": "moveForwardSpeed"
"timeScaleVar": "walkTimeScale"
}, },
"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", "id": "walkBwd",
"type": "clip", "type": "blendLinearMove",
"data": { "data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", "alpha": 0.0,
"startFrame": 0.0, "desiredSpeed": 1.4,
"endFrame": 37.0, "characteristicSpeeds": [0.6, 1.45],
"timeScale": 1.0, "alphaVar": "moveBackwardAlpha",
"loopFlag": true, "desiredSpeedVar": "moveBackwardSpeed"
"timeScaleVar": "walkTimeScale"
}, },
"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", "id": "turnLeft",
@ -573,27 +633,77 @@
}, },
{ {
"id": "strafeLeft", "id": "strafeLeft",
"type": "clip", "type": "blendLinearMove",
"data": { "data": {
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx", "alpha": 0.0,
"startFrame": 0.0, "desiredSpeed": 1.4,
"endFrame": 31.0, "characteristicSpeeds": [0.2, 0.65],
"timeScale": 1.0, "alphaVar": "moveLateralAlpha",
"loopFlag": true "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", "id": "strafeRight",
"type": "clip", "type": "blendLinearMove",
"data": { "data": {
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx", "alpha": 0.0,
"startFrame": 0.0, "desiredSpeed": 1.4,
"endFrame": 31.0, "characteristicSpeeds": [0.2, 0.65],
"timeScale": 1.0, "alphaVar": "moveLateralAlpha",
"loopFlag": true "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": []
}
]
} }
] ]
} }

View file

@ -72,6 +72,7 @@ namespace render {
avatarPtr->setDisplayingLookatTarget(renderLookAtTarget); avatarPtr->setDisplayingLookatTarget(renderLookAtTarget);
if (avatarPtr->isInitialized() && args) { if (avatarPtr->isInitialized() && args) {
PROFILE_RANGE_BATCH(*args->_batch, "renderAvatarPayload");
avatarPtr->render(args, qApp->getCamera()->getPosition()); avatarPtr->render(args, qApp->getCamera()->getPosition());
} }
} }
@ -304,6 +305,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::S
void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) { void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
auto& batch = *renderArgs->_batch; auto& batch = *renderArgs->_batch;
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
if (glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) { if (glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), getPosition()) < 10.0f) {
auto geometryCache = DependencyManager::get<GeometryCache>(); auto geometryCache = DependencyManager::get<GeometryCache>();
@ -330,6 +332,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
} }
if (havePosition && haveRotation) { if (havePosition && haveRotation) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":leftHandPointer");
Transform pointerTransform; Transform pointerTransform;
pointerTransform.setTranslation(position); pointerTransform.setTranslation(position);
pointerTransform.setRotation(rotation); pointerTransform.setRotation(rotation);
@ -353,6 +356,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
} }
if (havePosition && haveRotation) { if (havePosition && haveRotation) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":rightHandPointer");
Transform pointerTransform; Transform pointerTransform;
pointerTransform.setTranslation(position); pointerTransform.setTranslation(position);
pointerTransform.setRotation(rotation); pointerTransform.setRotation(rotation);
@ -424,6 +428,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes); bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) { if (renderBounding && shouldRenderHead(renderArgs) && _skeletonModel.isRenderable()) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":skeletonBoundingCollisionShapes");
_skeletonModel.renderBoundingCollisionShapes(*renderArgs->_batch, 0.7f); _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_OFFSET = 0.22f;
static const float INDICATOR_RADIUS = 0.03f; static const float INDICATOR_RADIUS = 0.03f;
static const glm::vec4 LOOK_AT_INDICATOR_COLOR = { 0.8f, 0.0f, 0.0f, 0.75f }; 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 transform;
transform.setTranslation(position); transform.setTranslation(position);
transform.postScale(INDICATOR_RADIUS); 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 the avatar is looking at me, indicate that they are
if (getHead()->isLookingAtMe() && Menu::getInstance()->isOptionChecked(MenuOption::ShowWhosLookingAtMe)) { 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 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_ALPHA_START = 0.8f;
const float LOOKING_AT_ME_DURATION = 0.5f; // seconds 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; const float MIN_VOICE_SPHERE_DISTANCE = 12.0f;
if (Menu::getInstance()->isOptionChecked(MenuOption::BlueSpeechSphere) if (Menu::getInstance()->isOptionChecked(MenuOption::BlueSpeechSphere)
&& distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { && distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) {
PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderVoiceSphere");
// render voice intensity sphere for avatars that are farther away // render voice intensity sphere for avatars that are farther away
const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE;
@ -621,6 +630,9 @@ void Avatar::updateJointMappings() {
} }
void Avatar::renderBillboard(RenderArgs* renderArgs) { 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()) { if (_billboard.isEmpty()) {
return; return;
} }
@ -652,6 +664,7 @@ void Avatar::renderBillboard(RenderArgs* renderArgs) {
glm::vec2 texCoordBottomRight(1.0f, 1.0f); glm::vec2 texCoordBottomRight(1.0f, 1.0f);
gpu::Batch& batch = *renderArgs->_batch; gpu::Batch& batch = *renderArgs->_batch;
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
batch.setResourceTexture(0, _billboardTexture->getGPUTexture()); batch.setResourceTexture(0, _billboardTexture->getGPUTexture());
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true); DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true);
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, DependencyManager::get<GeometryCache>()->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 { void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::vec3& textPosition) const {
PROFILE_RANGE_BATCH(batch, __FUNCTION__);
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar(); bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
// If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return // 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 // Test on extent above insures abs(height) > 0.0f
textTransform.postScale(1.0f / height); textTransform.postScale(1.0f / height);
batch.setModelTransform(textTransform); batch.setModelTransform(textTransform);
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, false, true, true, true); {
DependencyManager::get<GeometryCache>()->renderBevelCornersRect(batch, left, bottom, width, height, PROFILE_RANGE_BATCH(batch, __FUNCTION__":renderBevelCornersRect");
bevelDistance, backgroundColor); DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, false, true, true, true);
DependencyManager::get<GeometryCache>()->renderBevelCornersRect(batch, left, bottom, width, height,
bevelDistance, backgroundColor);
}
// Render actual name // Render actual name
QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit(); QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit();
// Render text slightly in front to avoid z-fighting // Render text slightly in front to avoid z-fighting
textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize())); textTransform.postTranslate(glm::vec3(0.0f, 0.0f, SLIGHTLY_IN_FRONT * renderer->getFontSize()));
batch.setModelTransform(textTransform); 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; 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 // 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 // better if the avatars cached these buffers for each of the joints they are rendering
geometryCache->updateVertices(_jointConesID, points, color); geometryCache->updateVertices(_jointConesID, points, color);

View file

@ -156,6 +156,8 @@ void MyAvatar::reset(bool andReload) {
// Reset dynamic state. // Reset dynamic state.
_wasPushing = _isPushing = _isBraking = _billboardValid = false; _wasPushing = _isPushing = _isBraking = _billboardValid = false;
_isFollowingHMD = false; _isFollowingHMD = false;
_hmdFollowVelocity = Vectors::ZERO;
_hmdFollowSpeed = 0.0f;
_skeletonModel.reset(); _skeletonModel.reset();
getHead()->reset(); getHead()->reset();
_targetVelocity = glm::vec3(0.0f); _targetVelocity = glm::vec3(0.0f);
@ -243,7 +245,7 @@ void MyAvatar::simulate(float deltaTime) {
{ {
PerformanceTimer perfTimer("transform"); PerformanceTimer perfTimer("transform");
bool stepAction = false; 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 // This allows a user to do faster snapping by tapping a control
for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) { for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) {
if (_driveKeys[i] != 0.0f) { if (_driveKeys[i] != 0.0f) {
@ -327,25 +329,6 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
return _sensorToWorldMatrix; 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. // Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD // This can also update the avatar's position to follow the HMD
// as it moves through the world. // as it moves through the world.
@ -354,102 +337,59 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorMatrix = hmdSensorMatrix; _hmdSensorMatrix = hmdSensorMatrix;
_hmdSensorPosition = extractTranslation(hmdSensorMatrix); _hmdSensorPosition = extractTranslation(hmdSensorMatrix);
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
}
// calc deltaTime void MyAvatar::updateHMDFollowVelocity() {
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
bool isMoving; bool isMoving;
if (_lastIsMoving) { 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 { } 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; bool justStartedMoving = (_lastIsMoving != isMoving) && isMoving;
_lastIsMoving = isMoving; _lastIsMoving = isMoving;
if (shouldFollowHMD() || hmdIsAtRest || justStartedMoving) { bool hmdIsAtRest = _hmdAtRestDetector.update(_hmdSensorPosition, _hmdSensorOrientation);
beginFollowingHMD(); if (hmdIsAtRest || justStartedMoving) {
}
followHMD(deltaTime);
}
glm::vec3 MyAvatar::getHMDCorrectionVelocity() const {
// TODO: impelement this
return Vectors::ZERO;
}
void MyAvatar::beginFollowingHMD() {
// begin homing toward derived body position.
if (!_isFollowingHMD) {
_isFollowingHMD = true; _isFollowingHMD = true;
_followHMDAlpha = 0.0f;
} }
}
bool MyAvatar::shouldFollowHMD() const { // compute offset to body's target position (in sensor-frame)
if (!_isFollowingHMD) { auto sensorBodyMatrix = deriveBodyFromHMDSensor();
// define a vertical capsule _hmdFollowOffset = extractTranslation(sensorBodyMatrix) - extractTranslation(_bodySensorMatrix);
const float FOLLOW_HMD_CAPSULE_RADIUS = 0.2f; // meters glm::vec3 truncatedOffset = _hmdFollowOffset;
const float FOLLOW_HMD_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters. if (truncatedOffset.y < 0.0f) {
// don't pull the body DOWN to match the target (allow animation system to squat)
// detect if the derived body position is outside of a capsule around the _bodySensorMatrix truncatedOffset.y = 0.0f;
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
glm::vec3 localPoint = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix);
return pointIsOutsideCapsule(localPoint, FOLLOW_HMD_CAPSULE_LENGTH, FOLLOW_HMD_CAPSULE_RADIUS);
} }
return false;
}
void MyAvatar::followHMD(float deltaTime) { bool needNewFollowSpeed = (_isFollowingHMD && _hmdFollowSpeed == 0.0f);
if (_isFollowingHMD) { if (!needNewFollowSpeed) {
// check to see if offset has exceeded its threshold
const float FOLLOW_HMD_DURATION = 0.5f; // seconds float distance = glm::length(truncatedOffset);
const float MAX_HMD_HIP_SHIFT = 0.2f;
auto newBodySensorMatrix = deriveBodyFromHMDSensor(); if (distance > MAX_HMD_HIP_SHIFT) {
auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix; _isFollowingHMD = true;
glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix); needNewFollowSpeed = true;
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);
} }
} }
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. // best called at end of main loop, just before rendering.
@ -1326,17 +1266,68 @@ void MyAvatar::prepareForPhysicsSimulation() {
relayDriveKeysToCharacterController(); relayDriveKeysToCharacterController();
_characterController.setTargetVelocity(getTargetVelocity()); _characterController.setTargetVelocity(getTargetVelocity());
_characterController.setAvatarPositionAndOrientation(getPosition(), getOrientation()); _characterController.setAvatarPositionAndOrientation(getPosition(), getOrientation());
_characterController.setHMDVelocity(getHMDCorrectionVelocity()); if (qApp->isHMDMode()) {
} updateHMDFollowVelocity();
_characterController.setHMDVelocity(_hmdFollowVelocity);
} else {
_characterController.setHMDVelocity(Vectors::ZERO);
_isFollowingHMD = false;
}
}
void MyAvatar::harvestResultsFromPhysicsSimulation() { void MyAvatar::harvestResultsFromPhysicsSimulation() {
glm::vec3 position = getPosition(); glm::vec3 position = getPosition();
glm::quat orientation = getOrientation(); glm::quat orientation = getOrientation();
_characterController.getAvatarPositionAndOrientation(position, orientation); _characterController.getAvatarPositionAndOrientation(position, orientation);
nextAttitude(position, orientation); nextAttitude(position, orientation);
setVelocity(_characterController.getLinearVelocity()); if (_isFollowingHMD) {
// TODO: harvest HMD shift here setVelocity(_characterController.getLinearVelocity() + _hmdFollowVelocity);
//glm::vec3 hmdShift = _characterController.getHMDShift(); 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 { QString MyAvatar::getScriptedMotorFrame() const {
@ -1438,7 +1429,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
getHead()->renderLookAts(renderArgs); getHead()->renderLookAts(renderArgs);
} }
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE && if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) { Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) {
getHand()->renderHandTargets(renderArgs, true); getHand()->renderHandTargets(renderArgs, true);
} }
@ -1917,7 +1908,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu"); QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu");
return; return;
} }
Menu* menu = Menu::getInstance(); Menu* menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;

View file

@ -101,8 +101,6 @@ public:
// as it moves through the world. // as it moves through the world.
void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix); void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix);
glm::vec3 getHMDCorrectionVelocity() const;
// best called at end of main loop, just before rendering. // best called at end of main loop, just before rendering.
// update sensor to world matrix from current body position and hmd sensor. // update sensor to world matrix from current body position and hmd sensor.
// This is so the correct camera can be used for rendering. // This is so the correct camera can be used for rendering.
@ -211,6 +209,7 @@ public:
void prepareForPhysicsSimulation(); void prepareForPhysicsSimulation();
void harvestResultsFromPhysicsSimulation(); void harvestResultsFromPhysicsSimulation();
void adjustSensorTransform(glm::vec3 hmdShift);
const QString& getCollisionSoundURL() { return _collisionSoundURL; } const QString& getCollisionSoundURL() { return _collisionSoundURL; }
void setCollisionSoundURL(const QString& url); void setCollisionSoundURL(const QString& url);
@ -312,9 +311,10 @@ private:
const RecorderPointer getRecorder() const { return _recorder; } const RecorderPointer getRecorder() const { return _recorder; }
const PlayerPointer getPlayer() const { return _player; } const PlayerPointer getPlayer() const { return _player; }
void beginFollowingHMD(); //void beginFollowingHMD();
bool shouldFollowHMD() const; //bool shouldFollowHMD() const;
void followHMD(float deltaTime); //void followHMD(float deltaTime);
void updateHMDFollowVelocity();
bool cameraInsideHead() const; bool cameraInsideHead() const;
@ -391,6 +391,9 @@ private:
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix; glm::mat4 _sensorToWorldMatrix;
glm::vec3 _hmdFollowOffset { Vectors::ZERO };
glm::vec3 _hmdFollowVelocity { Vectors::ZERO };
float _hmdFollowSpeed { 0.0f };
bool _goToPending; bool _goToPending;
glm::vec3 _goToPosition; glm::vec3 _goToPosition;
@ -411,9 +414,7 @@ private:
bool _isFollowingHMD { false }; bool _isFollowingHMD { false };
float _followHMDAlpha { 0.0f }; float _followHMDAlpha { 0.0f };
quint64 _lastUpdateFromHMDTime { usecTimestampNow() };
AtRestDetector _hmdAtRestDetector; AtRestDetector _hmdAtRestDetector;
glm::vec3 _lastPosition;
bool _lastIsMoving { false }; bool _lastIsMoving { false };
quint64 _lastStepPulse { 0 }; quint64 _lastStepPulse { 0 };
bool _pulseUpdate { false }; bool _pulseUpdate { false };

View file

@ -118,18 +118,6 @@ void EyeTracker::init() {
qCWarning(interfaceapp) << "Eye Tracker: Already initialized"; qCWarning(interfaceapp) << "Eye Tracker: Already initialized";
return; 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 #ifdef HAVE_IVIEWHMD
@ -140,6 +128,10 @@ int EyeTracker::startStreaming(bool simulate) {
#ifdef HAVE_IVIEWHMD #ifdef HAVE_IVIEWHMD
void EyeTracker::onStreamStarted() { void EyeTracker::onStreamStarted() {
if (!_isInitialized) {
return;
}
int result = _startStreamingWatcher.result(); int result = _startStreamingWatcher.result();
_isStreaming = (result == SMI_RET_SUCCESS); _isStreaming = (result == SMI_RET_SUCCESS);
@ -171,6 +163,20 @@ void EyeTracker::onStreamStarted() {
#endif #endif
void EyeTracker::setEnabled(bool enabled, bool simulate) { 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) { if (!_isInitialized) {
return; return;
} }

View file

@ -12,6 +12,7 @@
#include "GLMHelpers.h" #include "GLMHelpers.h"
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimUtil.h" #include "AnimUtil.h"
#include "AnimClip.h"
AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha) : AnimBlendLinear::AnimBlendLinear(const QString& id, float alpha) :
AnimNode(AnimNode::Type::BlendLinear, id), AnimNode(AnimNode::Type::BlendLinear, id),
@ -34,24 +35,13 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo
} else if (_children.size() == 1) { } else if (_children.size() == 1) {
_poses = _children[0]->evaluate(animVars, dt, triggersOut); _poses = _children[0]->evaluate(animVars, dt, triggersOut);
} else { } else {
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1)); float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
size_t prevPoseIndex = glm::floor(clampedAlpha); size_t prevPoseIndex = glm::floor(clampedAlpha);
size_t nextPoseIndex = glm::ceil(clampedAlpha); size_t nextPoseIndex = glm::ceil(clampedAlpha);
float alpha = glm::fract(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()) { evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
_poses.resize(prevPoses.size());
::blend(_poses.size(), &prevPoses[0], &nextPoses[0], alpha, &_poses[0]);
}
}
} }
return _poses; return _poses;
} }
@ -60,3 +50,21 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, flo
const AnimPoseVec& AnimBlendLinear::getPosesInternal() const { const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
return _poses; 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]);
}
}
}

View file

@ -38,6 +38,9 @@ protected:
// for AnimDebugDraw rendering // for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override; 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; AnimPoseVec _poses;
float _alpha; float _alpha;

View file

@ -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 <GLMHelpers.h>
#include "AnimationLogging.h"
#include "AnimUtil.h"
#include "AnimClip.h"
AnimBlendLinearMove::AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector<float>& 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<AnimClip>(_children[prevPoseIndex]);
assert(prevClipNode);
auto nextClipNode = std::dynamic_pointer_cast<AnimClip>(_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<AnimClip>(_children.front());
assert(clipNode);
const float NUM_FRAMES = (clipNode->getEndFrame() - clipNode->getStartFrame()) + 1.0f;
_phase = fmodf(frame, NUM_FRAMES);
}

View file

@ -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<float>& 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<float> _characteristicSpeeds;
// no copies
AnimBlendLinearMove(const AnimBlendLinearMove&) = delete;
AnimBlendLinearMove& operator=(const AnimBlendLinearMove&) = delete;
};
#endif // hifi_AnimBlendLinearMove_h

View file

@ -35,7 +35,9 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt,
_endFrame = animVars.lookup(_endFrameVar, _endFrame); _endFrame = animVars.lookup(_endFrameVar, _endFrame);
_timeScale = animVars.lookup(_timeScaleVar, _timeScale); _timeScale = animVars.lookup(_timeScaleVar, _timeScale);
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); _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. // poll network anim to see if it's finished loading yet.
if (_networkAnim && _networkAnim->isLoaded() && _skeleton) { if (_networkAnim && _networkAnim->isLoaded() && _skeleton) {
@ -45,16 +47,17 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt,
} }
if (_anim.size()) { if (_anim.size()) {
int frameCount = _anim.size();
int prevIndex = (int)glm::floor(_frame); int prevIndex = (int)glm::floor(_frame);
int nextIndex = (int)glm::ceil(_frame); int nextIndex;
if (_loopFlag && nextIndex >= frameCount) { if (_loopFlag && _frame >= _endFrame) {
nextIndex = 0; 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 // 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. // values before or past valid ranges. We clamp the frames here.
int frameCount = _anim.size();
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
nextIndex = std::min(std::max(0, nextIndex), 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 // because dt is 0, we should not encounter any triggers
const float dt = 0.0f; const float dt = 0.0f;
Triggers triggers; Triggers triggers;
_frame = accumulateTime(frame * _timeScale, dt, triggers); _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, 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;
} }
void AnimClip::copyFromNetworkAnim() { void AnimClip::copyFromNetworkAnim() {

View file

@ -36,12 +36,17 @@ public:
void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; } void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; }
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; } 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: protected:
void loadURL(const QString& url); void loadURL(const QString& url);
virtual void setCurrentFrameInternal(float frame) override; virtual void setCurrentFrameInternal(float frame) override;
float accumulateTime(float frame, float dt, Triggers& triggersOut) const;
void copyFromNetworkAnim(); void copyFromNetworkAnim();
// for AnimDebugDraw rendering // for AnimDebugDraw rendering

View file

@ -38,6 +38,7 @@ public:
enum class Type { enum class Type {
Clip = 0, Clip = 0,
BlendLinear, BlendLinear,
BlendLinearMove,
Overlay, Overlay,
StateMachine, StateMachine,
Manipulator, Manipulator,
@ -75,10 +76,10 @@ public:
return evaluate(animVars, dt, triggersOut); return evaluate(animVars, dt, triggersOut);
} }
protected:
void setCurrentFrame(float frame); void setCurrentFrame(float frame);
protected:
virtual void setCurrentFrameInternal(float frame) {} virtual void setCurrentFrameInternal(float frame) {}
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; } virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) { _skeleton = skeleton; }

View file

@ -16,6 +16,7 @@
#include "AnimNode.h" #include "AnimNode.h"
#include "AnimClip.h" #include "AnimClip.h"
#include "AnimBlendLinear.h" #include "AnimBlendLinear.h"
#include "AnimBlendLinearMove.h"
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimOverlay.h" #include "AnimOverlay.h"
#include "AnimNodeLoader.h" #include "AnimNodeLoader.h"
@ -29,6 +30,7 @@ using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& json
// factory functions // factory functions
static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); 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 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 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 loadStateMachineNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadManipulatorNode(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 // called after children have been loaded
// returns node on success, nullptr on failure. // 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 processDoNothing(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; }
bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); 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) { static const char* animNodeTypeToString(AnimNode::Type type) {
switch (type) { switch (type) {
case AnimNode::Type::Clip: return "clip"; case AnimNode::Type::Clip: return "clip";
case AnimNode::Type::BlendLinear: return "blendLinear"; case AnimNode::Type::BlendLinear: return "blendLinear";
case AnimNode::Type::BlendLinearMove: return "blendLinearMove";
case AnimNode::Type::Overlay: return "overlay"; case AnimNode::Type::Overlay: return "overlay";
case AnimNode::Type::StateMachine: return "stateMachine"; case AnimNode::Type::StateMachine: return "stateMachine";
case AnimNode::Type::Manipulator: return "manipulator"; case AnimNode::Type::Manipulator: return "manipulator";
@ -60,6 +59,7 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
switch (type) { switch (type) {
case AnimNode::Type::Clip: return loadClipNode; case AnimNode::Type::Clip: return loadClipNode;
case AnimNode::Type::BlendLinear: return loadBlendLinearNode; case AnimNode::Type::BlendLinear: return loadBlendLinearNode;
case AnimNode::Type::BlendLinearMove: return loadBlendLinearMoveNode;
case AnimNode::Type::Overlay: return loadOverlayNode; case AnimNode::Type::Overlay: return loadOverlayNode;
case AnimNode::Type::StateMachine: return loadStateMachineNode; case AnimNode::Type::StateMachine: return loadStateMachineNode;
case AnimNode::Type::Manipulator: return loadManipulatorNode; case AnimNode::Type::Manipulator: return loadManipulatorNode;
@ -71,12 +71,13 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
switch (type) { switch (type) {
case AnimNode::Type::Clip: return processClipNode; case AnimNode::Type::Clip: return processDoNothing;
case AnimNode::Type::BlendLinear: return processBlendLinearNode; case AnimNode::Type::BlendLinear: return processDoNothing;
case AnimNode::Type::Overlay: return processOverlayNode; case AnimNode::Type::BlendLinearMove: return processDoNothing;
case AnimNode::Type::Overlay: return processDoNothing;
case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::StateMachine: return processStateMachineNode;
case AnimNode::Type::Manipulator: return processManipulatorNode; case AnimNode::Type::Manipulator: return processDoNothing;
case AnimNode::Type::InverseKinematics: return processInverseKinematicsNode; case AnimNode::Type::InverseKinematics: return processDoNothing;
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
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); assert((int)type >= 0 && type < AnimNode::Type::NumTypes);
auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl); auto node = (animNodeTypeToLoaderFunc(type))(dataObj, id, jsonUrl);
if (!node) {
return nullptr;
}
auto childrenValue = jsonObj.value("children"); auto childrenValue = jsonObj.value("children");
if (!childrenValue.isArray()) { if (!childrenValue.isArray()) {
@ -233,6 +237,45 @@ static AnimNode::Pointer loadBlendLinearNode(const QJsonObject& jsonObj, const Q
return node; 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<float> 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<AnimBlendLinearMove>(id, alpha, desiredSpeed, characteristicSpeeds);
if (!alphaVar.isEmpty()) {
node->setAlphaVar(alphaVar);
}
if (!desiredSpeedVar.isEmpty()) {
node->setDesiredSpeedVar(desiredSpeedVar);
}
return node;
}
static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { static const char* boneSetStrings[AnimOverlay::NumBoneSets] = {
"fullBody", "fullBody",
"upperBody", "upperBody",

View file

@ -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<glm::mat4>(*this) * static_cast<glm::mat4>(rhs));
}
AnimPose AnimPose::inverse() const {
return AnimPose(glm::inverse(static_cast<glm::mat4>(*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));
}

View file

@ -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 <QtGlobal>
#include <QDebug>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
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<AnimPose>;
#endif

View file

@ -16,50 +16,6 @@
#include "AnimationLogging.h" #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<glm::mat4>(*this) * static_cast<glm::mat4>(rhs));
}
AnimPose AnimPose::inverse() const {
return AnimPose(glm::inverse(static_cast<glm::mat4>(*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) { AnimSkeleton::AnimSkeleton(const FBXGeometry& fbxGeometry) {
// convert to std::vector of joints // convert to std::vector of joints
std::vector<FBXJoint> joints; std::vector<FBXJoint> joints;

View file

@ -16,33 +16,7 @@
#include <glm/gtc/quaternion.hpp> #include <glm/gtc/quaternion.hpp>
#include <FBXReader.h> #include <FBXReader.h>
#include "AnimPose.h"
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<AnimPose>;
class AnimSkeleton { class AnimSkeleton {
public: public:

View file

@ -11,6 +11,9 @@
#include "AnimUtil.h" #include "AnimUtil.h"
#include "GLMHelpers.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) { void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) {
for (size_t i = 0; i < numPoses; i++) { for (size_t i = 0; i < numPoses; i++) {
const AnimPose& aPose = a[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); 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;
}

View file

@ -13,12 +13,12 @@
#include "AnimNode.h" #include "AnimNode.h"
// TODO: use restrict keyword
// TODO: excellent candidate for simd vectorization.
// this is where the magic happens // this is where the magic happens
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result); 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 #endif

View file

@ -381,6 +381,33 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const {
return _jointStates[jointIndex].getTransform(); return _jointStates[jointIndex].getTransform();
} }
void Rig::calcAnimAlpha(float speed, const std::vector<float>& 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<float> FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s
static const std::vector<float> BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s
static const std::vector<float> 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) { void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation) {
glm::vec3 front = worldRotation * IDENTITY_FRONT; 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. // 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...) // 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... // So, let's create our own workingVelocity from the worldPosition...
glm::vec3 workingVelocity = _lastVelocity;
glm::vec3 positionDelta = worldPosition - _lastPosition; 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 #if !WANT_DEBUG
// But for smoothest (non-hmd standing) results, go ahead and use velocity: // 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 #endif
if (deltaTime > SMALL_DELTA_TIME) {
_lastVelocity = workingVelocity;
}
if (_enableAnimGraph) { if (_enableAnimGraph) {
glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity; glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity;
float forwardSpeed = glm::dot(localVel, IDENTITY_FRONT); float forwardSpeed = glm::dot(localVel, IDENTITY_FRONT);
float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT); float lateralSpeed = glm::dot(localVel, IDENTITY_RIGHT);
float turningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; 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. // sine wave LFO var for testing.
static float t = 0.0f; static float t = 0.0f;
_animVars.set("sine", static_cast<float>(0.5 * sin(t) + 0.5)); _animVars.set("sine", 2.0f * static_cast<float>(0.5 * sin(t) + 0.5));
const float ANIM_WALK_SPEED = 1.4f; // m/s float moveForwardAlpha = 0.0f;
_animVars.set("walkTimeScale", glm::clamp(0.5f, 2.0f, glm::length(localVel) / ANIM_WALK_SPEED)); 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_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec

View file

@ -44,6 +44,7 @@
#include "AnimNode.h" #include "AnimNode.h"
#include "AnimNodeLoader.h" #include "AnimNodeLoader.h"
#include "SimpleMovingAverage.h"
class AnimationHandle; class AnimationHandle;
typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer; typedef std::shared_ptr<AnimationHandle> AnimationHandlePointer;
@ -219,6 +220,7 @@ public:
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist); void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);
void updateNeckJoint(int index, const HeadParameters& params); 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 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<float>& referenceSpeeds, float* alphaOut) const;
QVector<JointState> _jointStates; QVector<JointState> _jointStates;
int _rootJointIndex = -1; int _rootJointIndex = -1;
@ -238,6 +240,7 @@ public:
bool _enableAnimGraph = false; bool _enableAnimGraph = false;
glm::vec3 _lastFront; glm::vec3 _lastFront;
glm::vec3 _lastPosition; glm::vec3 _lastPosition;
glm::vec3 _lastVelocity;
std::shared_ptr<AnimNode> _animNode; std::shared_ptr<AnimNode> _animNode;
std::shared_ptr<AnimSkeleton> _animSkeleton; std::shared_ptr<AnimSkeleton> _animSkeleton;
@ -254,6 +257,9 @@ public:
float _leftHandOverlayAlpha = 0.0f; float _leftHandOverlayAlpha = 0.0f;
float _rightHandOverlayAlpha = 0.0f; float _rightHandOverlayAlpha = 0.0f;
SimpleMovingAverage _averageForwardSpeed{ 10 };
SimpleMovingAverage _averageLateralSpeed{ 10 };
private: private:
QMap<int, StateHandler> _stateHandlers; QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId {0}; int _nextStateHandlerId {0};

View file

@ -7,8 +7,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "DeviceProxy.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:
namespace controller { //
} // 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"

View file

@ -55,6 +55,9 @@ Input::NamedVector StandardController::getAvailableInputs() const {
makePair(LS, "LS"), makePair(LS, "LS"),
makePair(RS, "RS"), makePair(RS, "RS"),
makePair(LS_TOUCH, "LSTouch"),
makePair(RS_TOUCH, "RSTouch"),
// Center buttons // Center buttons
makePair(START, "Start"), makePair(START, "Start"),
makePair(BACK, "Back"), makePair(BACK, "Back"),
@ -69,26 +72,41 @@ Input::NamedVector StandardController::getAvailableInputs() const {
makePair(LT, "LT"), makePair(LT, "LT"),
makePair(RT, "RT"), makePair(RT, "RT"),
// Finger abstractions // Finger abstractions
makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"),
makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"), makePair(LEFT_SECONDARY_THUMB, "LeftSecondaryThumb"),
makePair(LEFT_THUMB_UP, "LeftThumbUp"),
makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"), makePair(RIGHT_PRIMARY_THUMB, "RightPrimaryThumb"),
makePair(RIGHT_SECONDARY_THUMB, "RightSecondaryThumb"), 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_PRIMARY_INDEX, "LeftPrimaryIndex"),
makePair(LEFT_SECONDARY_INDEX, "LeftSecondaryIndex"), makePair(LEFT_SECONDARY_INDEX, "LeftSecondaryIndex"),
makePair(RIGHT_PRIMARY_INDEX, "RightPrimaryIndex"), makePair(RIGHT_PRIMARY_INDEX, "RightPrimaryIndex"),
makePair(RIGHT_SECONDARY_INDEX, "RightSecondaryIndex"), 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, "LeftGrip"),
makePair(LEFT_GRIP_TOUCH, "LeftGripTouch"),
makePair(RIGHT_GRIP, "RightGrip"), makePair(RIGHT_GRIP, "RightGrip"),
makePair(RIGHT_GRIP_TOUCH, "RightGripTouch"),
// Poses // Poses
makePair(LEFT_HAND, "LeftHand"), makePair(LEFT_HAND, "LeftHand"),
makePair(RIGHT_HAND, "RightHand"), makePair(RIGHT_HAND, "RightHand"),
// Aliases, PlayStation style names // Aliases, PlayStation style names
makePair(LB, "L1"), makePair(LB, "L1"),
makePair(RB, "R1"), makePair(RB, "R1"),

View file

@ -39,16 +39,33 @@ namespace controller {
// These don't map to SDL types // These don't map to SDL types
LEFT_PRIMARY_THUMB, LEFT_PRIMARY_THUMB,
LEFT_SECONDARY_THUMB, LEFT_SECONDARY_THUMB,
LEFT_PRIMARY_THUMB_TOUCH,
LEFT_SECONDARY_THUMB_TOUCH,
LS_TOUCH,
LEFT_THUMB_UP,
RIGHT_PRIMARY_THUMB, RIGHT_PRIMARY_THUMB,
RIGHT_SECONDARY_THUMB, RIGHT_SECONDARY_THUMB,
RIGHT_PRIMARY_THUMB_TOUCH,
RIGHT_SECONDARY_THUMB_TOUCH,
RS_TOUCH,
RIGHT_THUMB_UP,
LEFT_PRIMARY_INDEX, LEFT_PRIMARY_INDEX,
LEFT_SECONDARY_INDEX, LEFT_SECONDARY_INDEX,
LEFT_PRIMARY_INDEX_TOUCH,
LEFT_SECONDARY_INDEX_TOUCH,
LEFT_INDEX_POINT,
RIGHT_PRIMARY_INDEX, RIGHT_PRIMARY_INDEX,
RIGHT_SECONDARY_INDEX, RIGHT_SECONDARY_INDEX,
RIGHT_PRIMARY_INDEX_TOUCH,
RIGHT_SECONDARY_INDEX_TOUCH,
RIGHT_INDEX_POINT,
LEFT_GRIP, LEFT_GRIP,
LEFT_GRIP_TOUCH,
RIGHT_GRIP, RIGHT_GRIP,
RIGHT_GRIP_TOUCH,
NUM_STANDARD_BUTTONS NUM_STANDARD_BUTTONS
}; };

View file

@ -26,6 +26,7 @@
#include "Logging.h" #include "Logging.h"
#include "impl/conditionals/AndConditional.h" #include "impl/conditionals/AndConditional.h"
#include "impl/conditionals/NotConditional.h"
#include "impl/conditionals/EndpointConditional.h" #include "impl/conditionals/EndpointConditional.h"
#include "impl/conditionals/ScriptConditional.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) { void UserInputMapper::enableMapping(const QString& mappingName, bool enable) {
Locker locker(_lock); Locker locker(_lock);
qCDebug(controllers) << "Attempting to enable mapping " << mappingName; qCDebug(controllers) << "Attempting to " << (enable ? "enable" : "disable") << " mapping " << mappingName;
auto iterator = _mappingsByName.find(mappingName); auto iterator = _mappingsByName.find(mappingName);
if (_mappingsByName.end() == iterator) { if (_mappingsByName.end() == iterator) {
qCWarning(controllers) << "Request to enable / disable unknown mapping " << mappingName; qCWarning(controllers) << "Request to enable / disable unknown mapping " << mappingName;
@ -826,13 +827,31 @@ Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value)
return std::make_shared<AndConditional>(children); return std::make_shared<AndConditional>(children);
} else if (value.isString()) { } else if (value.isString()) {
// Support "when" : "GamePad.RB" // 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); auto endpoint = endpointFor(input);
if (!endpoint) { if (!endpoint) {
return Conditional::Pointer(); return Conditional::Pointer();
} }
auto conditional = std::make_shared<EndpointConditional>(endpoint);
return std::make_shared<EndpointConditional>(endpoint); if (!conditionalModifier.isEmpty()) {
if (conditionalModifier == JSON_CONDITIONAL_MODIFIER_NOT) {
return std::make_shared<NotConditional>(conditional);
}
}
// Default and conditional behavior
return conditional;
} }
return Conditional::parse(value); return Conditional::parse(value);

View file

@ -6,9 +6,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "Endpoint.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:
namespace controller { //
// 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"

View file

@ -5,4 +5,11 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "EndpointConditional.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 "EndpointConditional.h"

View file

@ -6,4 +6,16 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "NotConditional.h" #include "NotConditional.h"
using namespace controller;
bool NotConditional::satisfied() {
if (_operand) {
return (!_operand->satisfied());
} else {
return false;
}
}

View file

@ -12,5 +12,19 @@
#include "../Conditional.h" #include "../Conditional.h"
namespace controller {
class NotConditional : public Conditional {
public:
using Pointer = std::shared_ptr<NotConditional>;
NotConditional(Conditional::Pointer operand) : _operand(operand) { }
virtual bool satisfied() override;
private:
Conditional::Pointer _operand;
};
}
#endif #endif

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "ArrayEndpoint.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 "ArrayEndpoint.h"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "JSEndpoint.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 "JSEndpoint.h"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "StandardEndpoint.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 "StandardEndpoint.h"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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"

View file

@ -6,4 +6,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // 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"

View file

@ -11,7 +11,7 @@
#define hifi_GlWindow_h #define hifi_GlWindow_h
#include <mutex> #include <mutex>
#include <QWindow> #include <QtGui/QWindow>
class QOpenGLContext; class QOpenGLContext;
class QOpenGLDebugLogger; class QOpenGLDebugLogger;

View file

@ -22,6 +22,14 @@ ProfileRange::ProfileRange(const char *name) {
ProfileRange::~ProfileRange() { ProfileRange::~ProfileRange() {
nvtxRangePop(); nvtxRangePop();
} }
ProfileRangeBatch::ProfileRangeBatch(gpu::Batch& batch, const char *name) : _batch(batch) {
_batch.pushProfileRange(name);
}
ProfileRangeBatch::~ProfileRangeBatch() {
_batch.popProfileRange();
}
#endif #endif
#define ADD_COMMAND(call) _commands.push_back(COMMAND_##call); _commandOffsets.push_back(_params.size()); #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; 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
}

View file

@ -229,6 +229,10 @@ public:
// Reset the stage caches and states // Reset the stage caches and states
void resetStages(); void resetStages();
// Debugging
void pushProfileRange(const char* name);
void popProfileRange();
// TODO: As long as we have gl calls explicitely issued from interface // 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 // 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 // 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_glColor4f,
COMMAND_pushProfileRange,
COMMAND_popProfileRange,
NUM_COMMANDS, NUM_COMMANDS,
}; };
typedef std::vector<Command> Commands; typedef std::vector<Command> Commands;
@ -389,6 +396,7 @@ public:
typedef Cache<PipelinePointer>::Vector PipelineCaches; typedef Cache<PipelinePointer>::Vector PipelineCaches;
typedef Cache<FramebufferPointer>::Vector FramebufferCaches; typedef Cache<FramebufferPointer>::Vector FramebufferCaches;
typedef Cache<QueryPointer>::Vector QueryCaches; typedef Cache<QueryPointer>::Vector QueryCaches;
typedef Cache<std::string>::Vector ProfileRangeCaches;
typedef Cache<std::function<void()>>::Vector LambdaCache; typedef Cache<std::function<void()>>::Vector LambdaCache;
// Cache Data in a byte array if too big to fit in Param // Cache Data in a byte array if too big to fit in Param
@ -416,6 +424,7 @@ public:
FramebufferCaches _framebuffers; FramebufferCaches _framebuffers;
QueryCaches _queries; QueryCaches _queries;
LambdaCache _lambdas; LambdaCache _lambdas;
ProfileRangeCaches _profileRanges;
NamedBatchDataMap _namedData; 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); QDebug& operator<<(QDebug& debug, const gpu::Batch::CacheState& cacheState);
#endif #endif

View file

@ -15,6 +15,11 @@
#include <list> #include <list>
#include <glm/gtc/type_ptr.hpp> #include <glm/gtc/type_ptr.hpp>
#if defined(NSIGHT_FOUND)
#include "nvToolsExt.h"
#endif
using namespace gpu; using namespace gpu;
GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] = 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_glUniformMatrix4fv),
(&::gpu::GLBackend::do_glColor4f), (&::gpu::GLBackend::do_glColor4f),
(&::gpu::GLBackend::do_pushProfileRange),
(&::gpu::GLBackend::do_popProfileRange),
}; };
void GLBackend::init() { void GLBackend::init() {
@ -710,3 +718,17 @@ void GLBackend::do_glColor4f(Batch& batch, uint32 paramOffset) {
} }
(void) CHECK_GL_ERROR(); (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
}

View file

@ -479,6 +479,9 @@ protected:
void do_glColor4f(Batch& batch, uint32 paramOffset); 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); typedef void (GLBackend::*CommandCall)(Batch&, uint32);
static CommandCall _commandCalls[Batch::NUM_COMMANDS]; static CommandCall _commandCalls[Batch::NUM_COMMANDS];
}; };

View file

@ -59,11 +59,27 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event, unsigned int devic
// key pressed again ? without catching the release event ? // key pressed again ? without catching the release event ?
} }
_lastCursor = event->pos(); _lastCursor = event->pos();
_mousePressAt = event->pos();
eraseMouseClicked();
} }
void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) { void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
auto input = makeInput((Qt::MouseButton) event->button()); auto input = makeInput((Qt::MouseButton) event->button());
_buttonPressedMap.erase(input.getChannel()); _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) { 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); _axisStateMap[makeInput(MOUSE_AXIS_Y_NEG).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f);
_lastCursor = currentPos; _lastCursor = currentPos;
eraseMouseClicked();
} }
void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { 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); 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) { switch (code) {
case Qt::LeftButton: 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: 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: 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: default:
return controller::Input(); 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_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::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseClick")); availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseClick")); availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseClick")); 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_POS), "MouseMoveRight"));
availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "MouseMoveLeft")); availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "MouseMoveLeft"));

View file

@ -37,6 +37,9 @@ public:
MOUSE_BUTTON_LEFT = KEYBOARD_LAST + 1, MOUSE_BUTTON_LEFT = KEYBOARD_LAST + 1,
MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_MIDDLE, MOUSE_BUTTON_MIDDLE,
MOUSE_BUTTON_LEFT_CLICKED,
MOUSE_BUTTON_RIGHT_CLICKED,
MOUSE_BUTTON_MIDDLE_CLICKED,
}; };
enum MouseAxisChannel { enum MouseAxisChannel {
@ -83,6 +86,7 @@ public:
void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0); void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0);
void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0); void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0);
void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0); void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0);
void eraseMouseClicked();
void touchBeginEvent(const QTouchEvent* event); void touchBeginEvent(const QTouchEvent* event);
void touchEndEvent(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 // 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::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(MouseAxisChannel axis) const;
controller::Input makeInput(TouchAxisChannel axis) const; controller::Input makeInput(TouchAxisChannel axis) const;
controller::Input makeInput(TouchButtonChannel button) const; controller::Input makeInput(TouchButtonChannel button) const;
@ -101,6 +105,7 @@ public:
protected: protected:
QPoint _lastCursor; QPoint _lastCursor;
QPoint _mousePressAt;
glm::vec2 _lastTouch; glm::vec2 _lastTouch;
bool _isTouching = false; bool _isTouching = false;

View file

@ -20,27 +20,27 @@
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
static_assert( static_assert(
controller::A == SDL_CONTROLLER_BUTTON_A && (int)controller::A == (int)SDL_CONTROLLER_BUTTON_A &&
controller::B == SDL_CONTROLLER_BUTTON_B && (int)controller::B == (int)SDL_CONTROLLER_BUTTON_B &&
controller::X == SDL_CONTROLLER_BUTTON_X && (int)controller::X == (int)SDL_CONTROLLER_BUTTON_X &&
controller::Y == SDL_CONTROLLER_BUTTON_Y && (int)controller::Y == (int)SDL_CONTROLLER_BUTTON_Y &&
controller::BACK == SDL_CONTROLLER_BUTTON_BACK && (int)controller::BACK == (int)SDL_CONTROLLER_BUTTON_BACK &&
controller::GUIDE == SDL_CONTROLLER_BUTTON_GUIDE && (int)controller::GUIDE == (int)SDL_CONTROLLER_BUTTON_GUIDE &&
controller::START == SDL_CONTROLLER_BUTTON_START && (int)controller::START == (int)SDL_CONTROLLER_BUTTON_START &&
controller::LS == SDL_CONTROLLER_BUTTON_LEFTSTICK && (int)controller::LS == (int)SDL_CONTROLLER_BUTTON_LEFTSTICK &&
controller::RS == SDL_CONTROLLER_BUTTON_RIGHTSTICK && (int)controller::RS == (int)SDL_CONTROLLER_BUTTON_RIGHTSTICK &&
controller::LB == SDL_CONTROLLER_BUTTON_LEFTSHOULDER && (int)controller::LB == (int)SDL_CONTROLLER_BUTTON_LEFTSHOULDER &&
controller::RB == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER && (int)controller::RB == (int)SDL_CONTROLLER_BUTTON_RIGHTSHOULDER &&
controller::DU == SDL_CONTROLLER_BUTTON_DPAD_UP && (int)controller::DU == (int)SDL_CONTROLLER_BUTTON_DPAD_UP &&
controller::DD == SDL_CONTROLLER_BUTTON_DPAD_DOWN && (int)controller::DD == (int)SDL_CONTROLLER_BUTTON_DPAD_DOWN &&
controller::DL == SDL_CONTROLLER_BUTTON_DPAD_LEFT && (int)controller::DL == (int)SDL_CONTROLLER_BUTTON_DPAD_LEFT &&
controller::DR == SDL_CONTROLLER_BUTTON_DPAD_RIGHT && (int)controller::DR == (int)SDL_CONTROLLER_BUTTON_DPAD_RIGHT &&
controller::LX == SDL_CONTROLLER_AXIS_LEFTX && (int)controller::LX == (int)SDL_CONTROLLER_AXIS_LEFTX &&
controller::LY == SDL_CONTROLLER_AXIS_LEFTY && (int)controller::LY == (int)SDL_CONTROLLER_AXIS_LEFTY &&
controller::RX == SDL_CONTROLLER_AXIS_RIGHTX && (int)controller::RX == (int)SDL_CONTROLLER_AXIS_RIGHTX &&
controller::RY == SDL_CONTROLLER_AXIS_RIGHTY && (int)controller::RY == (int)SDL_CONTROLLER_AXIS_RIGHTY &&
controller::LT == SDL_CONTROLLER_AXIS_TRIGGERLEFT && (int)controller::LT == (int)SDL_CONTROLLER_AXIS_TRIGGERLEFT &&
controller::RT == SDL_CONTROLLER_AXIS_TRIGGERRIGHT, (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"); "SDL2 equvalence: Enums and values from StandardControls.h are assumed to match enums from SDL_gamecontroller.h");
#endif #endif

View file

@ -84,7 +84,7 @@ private:
const glm::vec3& TWO() { return Vectors::TWO; } const glm::vec3& TWO() { return Vectors::TWO; }
const glm::vec3& HALF() { return Vectors::HALF; } const glm::vec3& HALF() { return Vectors::HALF; }
const glm::vec3& RIGHT() { return Vectors::RIGHT; } 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; } const glm::vec3& FRONT() { return Vectors::FRONT; }
}; };

View file

@ -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 "AtRestDetector.h"
#include "NumericalConstants.h"
#include "SharedLogging.h" #include "SharedLogging.h"
#include "SharedUtil.h"
AtRestDetector::AtRestDetector(const glm::vec3& startPosition, const glm::quat& startRotation) { AtRestDetector::AtRestDetector(const glm::vec3& startPosition, const glm::quat& startRotation) {
reset(startPosition, 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); glm::quat ql = glm::log(startRotation);
_quatLogAverage = glm::vec3(ql.x, ql.y, ql.z); _quatLogAverage = glm::vec3(ql.x, ql.y, ql.z);
_quatLogVariance = 0.0f; _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; const float TAU = 1.0f;
float delta = glm::min(dt / 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 POSITION_VARIANCE_THRESHOLD = 0.001f;
const float QUAT_LOG_VARIANCE_THRESHOLD = 0.00002f; 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;
} }

View file

@ -21,14 +21,17 @@ public:
void reset(const glm::vec3& startPosition, const glm::quat& startRotation); void reset(const glm::vec3& startPosition, const glm::quat& startRotation);
// returns true if object is at rest, dt in assumed to be seconds. // 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: protected:
glm::vec3 _positionAverage; glm::vec3 _positionAverage;
float _positionVariance;
glm::vec3 _quatLogAverage; glm::vec3 _quatLogAverage;
float _quatLogVariance; uint64_t _lastUpdateTime { 0 };
float _positionVariance { 0.0f };
float _quatLogVariance { 0.0f };
bool _isAtRest { false };
}; };
#endif #endif

View file

@ -183,6 +183,11 @@ T toNormalizedDeviceScale(const T& value, const T& size) {
#define PITCH(euler) euler.x #define PITCH(euler) euler.x
#define ROLL(euler) euler.z #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 // vec2 lerp - linear interpolate
template<typename T, glm::precision P> template<typename T, glm::precision P>
glm::detail::tvec2<T, P> lerp(const glm::detail::tvec2<T, P>& x, const glm::detail::tvec2<T, P>& y, T a) { glm::detail::tvec2<T, P> lerp(const glm::detail::tvec2<T, P>& x, const glm::detail::tvec2<T, P>& y, T a) {

View file

@ -8,6 +8,9 @@
if (WIN32) if (WIN32)
# we're using static GLEW, so define GLEW_STATIC
add_definitions(-DGLEW_STATIC)
set(TARGET_NAME oculus) set(TARGET_NAME oculus)
setup_hifi_plugin() setup_hifi_plugin()
link_hifi_libraries(shared gl plugins display-plugins) link_hifi_libraries(shared gl plugins display-plugins)

View file

@ -9,15 +9,15 @@
#include <memory> #include <memory>
#include <QMainWindow> #include <QtWidgets/QMainWindow>
#include <QGLWidget> #include <QtOpenGL/QGLWidget>
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <gl/GlWindow.h> #include <gl/GlWindow.h>
#include <QEvent> #include <QEvent>
#include <QResizeEvent> #include <QtGui/QResizeEvent>
#include <QOpenGLContext> #include <QtGui/QOpenGLContext>
#include <QGuiApplication> #include <QtGui/QGuiApplication>
#include <QScreen> #include <QtGui/QScreen>
#include <PerfStat.h> #include <PerfStat.h>
#include <gl/OglplusHelpers.h> #include <gl/OglplusHelpers.h>
@ -193,7 +193,7 @@ void OculusLegacyDisplayPlugin::deactivate() {
// DLL based display plugins MUST initialize GLEW inside the DLL code. // DLL based display plugins MUST initialize GLEW inside the DLL code.
void OculusLegacyDisplayPlugin::customizeContext() { void OculusLegacyDisplayPlugin::customizeContext() {
glewExperimental = true; glewExperimental = true;
GLenum err = glewInit(); glewInit();
glGetError(); glGetError();
WindowOpenGLDisplayPlugin::customizeContext(); WindowOpenGLDisplayPlugin::customizeContext();
} }

View file

@ -13,6 +13,7 @@
#include "AnimBlendLinear.h" #include "AnimBlendLinear.h"
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "AnimVariant.h" #include "AnimVariant.h"
#include "AnimUtil.h"
#include <../QTestExtensions.h> #include <../QTestExtensions.h>
@ -30,8 +31,8 @@ void AnimTests::cleanupTestCase() {
} }
void AnimTests::testClipInternalState() { void AnimTests::testClipInternalState() {
std::string id = "my anim clip"; QString id = "my anim clip";
std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
float startFrame = 2.0f; float startFrame = 2.0f;
float endFrame = 20.0f; float endFrame = 20.0f;
float timeScale = 1.1f; float timeScale = 1.1f;
@ -55,8 +56,8 @@ static float framesToSec(float secs) {
} }
void AnimTests::testClipEvaulate() { void AnimTests::testClipEvaulate() {
std::string id = "myClipNode"; QString id = "myClipNode";
std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
float startFrame = 2.0f; float startFrame = 2.0f;
float endFrame = 22.0f; float endFrame = 22.0f;
float timeScale = 1.0f; float timeScale = 1.0f;
@ -73,8 +74,8 @@ void AnimTests::testClipEvaulate() {
// does it loop? // does it loop?
triggers.clear(); triggers.clear();
clip.evaluate(vars, framesToSec(11.0f), triggers); clip.evaluate(vars, framesToSec(12.0f), triggers);
QCOMPARE_WITH_ABS_ERROR(clip._frame, 3.0f, EPSILON); 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? // did we receive a loop trigger?
QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end()); QVERIFY(std::find(triggers.begin(), triggers.end(), "myClipNodeOnLoop") != triggers.end());
@ -90,8 +91,8 @@ void AnimTests::testClipEvaulate() {
} }
void AnimTests::testClipEvaulateWithVars() { void AnimTests::testClipEvaulateWithVars() {
std::string id = "myClipNode"; QString id = "myClipNode";
std::string url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx"; QString url = "https://hifi-public.s3.amazonaws.com/ozan/support/FightClubBotTest1/Animations/standard_idle.fbx";
float startFrame = 2.0f; float startFrame = 2.0f;
float endFrame = 22.0f; float endFrame = 22.0f;
float timeScale = 1.0f; float timeScale = 1.0f;
@ -126,9 +127,9 @@ void AnimTests::testClipEvaulateWithVars() {
} }
void AnimTests::testLoader() { 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! // 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); AnimNodeLoader loader(url);
const int timeout = 1000; const int timeout = 1000;
@ -238,3 +239,87 @@ void AnimTests::testVariant() {
QVERIFY(m[1].z == -7.0f); QVERIFY(m[1].z == -7.0f);
QVERIFY(m[3].w == 16.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();
}

View file

@ -15,6 +15,8 @@
class AnimTests : public QObject { class AnimTests : public QObject {
Q_OBJECT Q_OBJECT
public:
void testAccumulateTimeWithParameters(float startFrame, float endFrame, float timeScale) const;
private slots: private slots:
void initTestCase(); void initTestCase();
void cleanupTestCase(); void cleanupTestCase();
@ -23,6 +25,7 @@ private slots:
void testClipEvaulateWithVars(); void testClipEvaulateWithVars();
void testLoader(); void testLoader();
void testVariant(); void testVariant();
void testAccumulateTime();
}; };
#endif // hifi_AnimTests_h #endif // hifi_AnimTests_h

View file

@ -114,7 +114,6 @@
createTargets(); createTargets();
createTargetResetter(); createTargetResetter();
createBasketballHoop();
createBasketballRack(); createBasketballRack();
createBasketballResetter(); createBasketballResetter();
@ -130,14 +129,11 @@
z: 503.49 z: 503.49
}); });
createSprayCan({ createSprayCan({
x: 549.7, x: 549.7,
y: 495.6, y: 495.6,
z: 503.91 z: 503.91
}); });
} }
function deleteAllToys() { 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) { function createWand(position) {
var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; 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'; var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj';

View file

@ -10,7 +10,7 @@
//per script //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"); var utilitiesScript = Script.resolvePath("../examples/libraries/utils.js");
Script.include(utilitiesScript); Script.include(utilitiesScript);
@ -87,7 +87,6 @@ MasterReset = function() {
createTargets(); createTargets();
createTargetResetter(); createTargetResetter();
createBasketballHoop();
createBasketballRack(); createBasketballRack();
createBasketballResetter(); 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) { function createWand(position) {
var WAND_MODEL = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/wand.fbx'; 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'; var WAND_COLLISION_SHAPE = 'http://hifi-public.s3.amazonaws.com/james/bubblewand/models/wand/actual_no_top_collision_hull.obj';