Merge branch 'master' into tony/anim-sync-blend

This commit is contained in:
Anthony J. Thibault 2015-11-02 12:18:11 -08:00
commit 2236573a9d
12 changed files with 542 additions and 146 deletions

View file

@ -6,8 +6,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_071615.zip
URL_MD5 752a3901f334124e9cffc2ba4136ef7d
URL https://hifi-public.s3.amazonaws.com/dependencies/SixenseSDK_102215.zip
URL_MD5 93c3a6795cce777a0f472b09532935f1
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

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

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

View file

@ -157,6 +157,8 @@ void MyAvatar::reset(bool andReload) {
// Reset dynamic state.
_wasPushing = _isPushing = _isBraking = _billboardValid = false;
_isFollowingHMD = false;
_hmdFollowVelocity = Vectors::ZERO;
_hmdFollowSpeed = 0.0f;
_skeletonModel.reset();
getHead()->reset();
_targetVelocity = glm::vec3(0.0f);
@ -248,7 +250,7 @@ void MyAvatar::simulate(float deltaTime) {
{
PerformanceTimer perfTimer("transform");
bool stepAction = false;
// When there are no step values, we zero out the last step pulse.
// When there are no step values, we zero out the last step pulse.
// This allows a user to do faster snapping by tapping a control
for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) {
if (_driveKeys[i] != 0.0f) {
@ -332,25 +334,6 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
return _sensorToWorldMatrix;
}
// returns true if pos is OUTSIDE of the vertical capsule
// where the middle cylinder length is defined by capsuleLen and the radius by capsuleRad.
static bool pointIsOutsideCapsule(const glm::vec3& pos, float capsuleLen, float capsuleRad) {
const float halfCapsuleLen = capsuleLen / 2.0f;
if (fabs(pos.y) <= halfCapsuleLen) {
// cylinder check for middle capsule
glm::vec2 horizPos(pos.x, pos.z);
return glm::length(horizPos) > capsuleRad;
} else if (pos.y > halfCapsuleLen) {
glm::vec3 center(0.0f, halfCapsuleLen, 0.0f);
return glm::length(center - pos) > capsuleRad;
} else if (pos.y < halfCapsuleLen) {
glm::vec3 center(0.0f, -halfCapsuleLen, 0.0f);
return glm::length(center - pos) > capsuleRad;
} else {
return false;
}
}
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
// as it moves through the world.
@ -359,102 +342,59 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
_hmdSensorMatrix = hmdSensorMatrix;
_hmdSensorPosition = extractTranslation(hmdSensorMatrix);
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
}
// calc deltaTime
auto now = usecTimestampNow();
auto deltaUsecs = now - _lastUpdateFromHMDTime;
_lastUpdateFromHMDTime = now;
double actualDeltaTime = (double)deltaUsecs / (double)USECS_PER_SECOND;
const float BIGGEST_DELTA_TIME_SECS = 0.25f;
float deltaTime = glm::clamp((float)actualDeltaTime, 0.0f, BIGGEST_DELTA_TIME_SECS);
bool hmdIsAtRest = _hmdAtRestDetector.update(deltaTime, _hmdSensorPosition, _hmdSensorOrientation);
// It can be more accurate/smooth to use velocity rather than position,
// but some modes (e.g., hmd standing) update position without updating velocity.
// So, let's create our own workingVelocity from the worldPosition...
glm::vec3 positionDelta = getPosition() - _lastPosition;
glm::vec3 workingVelocity = positionDelta / deltaTime;
_lastPosition = getPosition();
const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec
void MyAvatar::updateHMDFollowVelocity() {
bool isMoving;
if (_lastIsMoving) {
isMoving = glm::length(workingVelocity) >= MOVE_EXIT_SPEED_THRESHOLD;
const float MOVE_EXIT_SPEED_THRESHOLD = 0.07f; // m/sec
isMoving = glm::length(_velocity) >= MOVE_EXIT_SPEED_THRESHOLD;
} else {
isMoving = glm::length(workingVelocity) > MOVE_ENTER_SPEED_THRESHOLD;
const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
isMoving = glm::length(_velocity) > MOVE_ENTER_SPEED_THRESHOLD;
}
bool justStartedMoving = (_lastIsMoving != isMoving) && isMoving;
_lastIsMoving = isMoving;
if (shouldFollowHMD() || hmdIsAtRest || justStartedMoving) {
beginFollowingHMD();
}
followHMD(deltaTime);
}
glm::vec3 MyAvatar::getHMDCorrectionVelocity() const {
// TODO: impelement this
return Vectors::ZERO;
}
void MyAvatar::beginFollowingHMD() {
// begin homing toward derived body position.
if (!_isFollowingHMD) {
bool hmdIsAtRest = _hmdAtRestDetector.update(_hmdSensorPosition, _hmdSensorOrientation);
if (hmdIsAtRest || justStartedMoving) {
_isFollowingHMD = true;
_followHMDAlpha = 0.0f;
}
}
bool MyAvatar::shouldFollowHMD() const {
if (!_isFollowingHMD) {
// define a vertical capsule
const float FOLLOW_HMD_CAPSULE_RADIUS = 0.2f; // meters
const float FOLLOW_HMD_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters.
// detect if the derived body position is outside of a capsule around the _bodySensorMatrix
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
glm::vec3 localPoint = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix);
return pointIsOutsideCapsule(localPoint, FOLLOW_HMD_CAPSULE_LENGTH, FOLLOW_HMD_CAPSULE_RADIUS);
// compute offset to body's target position (in sensor-frame)
auto sensorBodyMatrix = deriveBodyFromHMDSensor();
_hmdFollowOffset = extractTranslation(sensorBodyMatrix) - extractTranslation(_bodySensorMatrix);
glm::vec3 truncatedOffset = _hmdFollowOffset;
if (truncatedOffset.y < 0.0f) {
// don't pull the body DOWN to match the target (allow animation system to squat)
truncatedOffset.y = 0.0f;
}
return false;
}
void MyAvatar::followHMD(float deltaTime) {
if (_isFollowingHMD) {
const float FOLLOW_HMD_DURATION = 0.5f; // seconds
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix;
glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix);
glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix));
_followHMDAlpha += (1.0f / FOLLOW_HMD_DURATION) * deltaTime;
if (_followHMDAlpha >= 1.0f) {
_isFollowingHMD = false;
nextAttitude(worldBodyPos, worldBodyRot);
_bodySensorMatrix = newBodySensorMatrix;
} else {
// interp position toward the desired pos
glm::vec3 pos = lerp(getPosition(), worldBodyPos, _followHMDAlpha);
glm::quat rot = glm::normalize(safeMix(getOrientation(), worldBodyRot, _followHMDAlpha));
nextAttitude(pos, rot);
// interp sensor matrix toward desired
glm::vec3 nextBodyPos = extractTranslation(newBodySensorMatrix);
glm::quat nextBodyRot = glm::normalize(glm::quat_cast(newBodySensorMatrix));
glm::vec3 prevBodyPos = extractTranslation(_bodySensorMatrix);
glm::quat prevBodyRot = glm::normalize(glm::quat_cast(_bodySensorMatrix));
pos = lerp(prevBodyPos, nextBodyPos, _followHMDAlpha);
rot = glm::normalize(safeMix(prevBodyRot, nextBodyRot, _followHMDAlpha));
_bodySensorMatrix = createMatFromQuatAndPos(rot, pos);
bool needNewFollowSpeed = (_isFollowingHMD && _hmdFollowSpeed == 0.0f);
if (!needNewFollowSpeed) {
// check to see if offset has exceeded its threshold
float distance = glm::length(truncatedOffset);
const float MAX_HMD_HIP_SHIFT = 0.2f;
if (distance > MAX_HMD_HIP_SHIFT) {
_isFollowingHMD = true;
needNewFollowSpeed = true;
}
}
if (_isFollowingHMD) {
// only bother to rotate into world frame if we're following
glm::quat sensorToWorldRotation = extractRotation(_sensorToWorldMatrix);
_hmdFollowOffset = sensorToWorldRotation * _hmdFollowOffset;
}
if (needNewFollowSpeed) {
// compute new velocity that will be used to resolve offset of hips from body
const float FOLLOW_HMD_DURATION = 0.5f; // seconds
_hmdFollowVelocity = (_hmdFollowOffset / FOLLOW_HMD_DURATION);
_hmdFollowSpeed = glm::length(_hmdFollowVelocity);
} else if (_isFollowingHMD) {
// compute new velocity (but not new speed)
_hmdFollowVelocity = _hmdFollowSpeed * glm::normalize(_hmdFollowOffset);
}
}
// best called at end of main loop, just before rendering.
@ -1356,17 +1296,68 @@ void MyAvatar::prepareForPhysicsSimulation() {
relayDriveKeysToCharacterController();
_characterController.setTargetVelocity(getTargetVelocity());
_characterController.setAvatarPositionAndOrientation(getPosition(), getOrientation());
_characterController.setHMDVelocity(getHMDCorrectionVelocity());
}
if (qApp->isHMDMode()) {
updateHMDFollowVelocity();
_characterController.setHMDVelocity(_hmdFollowVelocity);
} else {
_characterController.setHMDVelocity(Vectors::ZERO);
_isFollowingHMD = false;
}
}
void MyAvatar::harvestResultsFromPhysicsSimulation() {
glm::vec3 position = getPosition();
glm::quat orientation = getOrientation();
_characterController.getAvatarPositionAndOrientation(position, orientation);
nextAttitude(position, orientation);
setVelocity(_characterController.getLinearVelocity());
// TODO: harvest HMD shift here
//glm::vec3 hmdShift = _characterController.getHMDShift();
if (_isFollowingHMD) {
setVelocity(_characterController.getLinearVelocity() + _hmdFollowVelocity);
glm::vec3 hmdShift = _characterController.getHMDShift();
adjustSensorTransform(hmdShift);
} else {
setVelocity(_characterController.getLinearVelocity());
}
}
void MyAvatar::adjustSensorTransform(glm::vec3 hmdShift) {
// compute blendFactor of latest hmdShift
// which we'll use to blend the rotation part
float blendFactor = 1.0f;
float shiftLength = glm::length(hmdShift);
if (shiftLength > 1.0e-5f) {
float offsetLength = glm::length(_hmdFollowOffset);
if (offsetLength > shiftLength) {
blendFactor = shiftLength / offsetLength;
}
}
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix;
glm::quat finalBodyRotation = glm::normalize(glm::quat_cast(worldBodyMatrix));
if (blendFactor >= 0.99f) {
// the "adjustment" is more or less complete so stop following
_isFollowingHMD = false;
_hmdFollowSpeed = 0.0f;
// and slam the body's transform anyway to eliminate any slight errors
glm::vec3 finalBodyPosition = extractTranslation(worldBodyMatrix);
nextAttitude(finalBodyPosition, finalBodyRotation);
_bodySensorMatrix = newBodySensorMatrix;
} else {
// physics already did the positional blending for us
glm::vec3 newBodyPosition = getPosition();
// but the rotational part must be done manually
glm::quat newBodyRotation = glm::normalize(safeMix(getOrientation(), finalBodyRotation, blendFactor));
nextAttitude(newBodyPosition, newBodyRotation);
// interp sensor matrix toward the desired
glm::vec3 prevPosition = extractTranslation(_bodySensorMatrix);
glm::quat prevRotation = glm::normalize(glm::quat_cast(_bodySensorMatrix));
glm::vec3 nextPosition = extractTranslation(newBodySensorMatrix);
glm::quat nextRotation = glm::normalize(glm::quat_cast(newBodySensorMatrix));
_bodySensorMatrix = createMatFromQuatAndPos(
glm::normalize(safeMix(prevRotation, nextRotation, blendFactor)),
lerp(prevPosition, nextPosition, blendFactor));
}
}
QString MyAvatar::getScriptedMotorFrame() const {
@ -1468,7 +1459,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, fl
getHead()->renderLookAts(renderArgs);
}
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
if (renderArgs->_renderMode != RenderArgs::SHADOW_RENDER_MODE &&
Menu::getInstance()->isOptionChecked(MenuOption::DisplayHandTargets)) {
getHand()->renderHandTargets(renderArgs, true);
}
@ -1947,7 +1938,7 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu");
return;
}
Menu* menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) {
_motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;

View file

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

View file

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

View file

@ -59,11 +59,27 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event, unsigned int devic
// key pressed again ? without catching the release event ?
}
_lastCursor = event->pos();
_mousePressAt = event->pos();
eraseMouseClicked();
}
void KeyboardMouseDevice::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
auto input = makeInput((Qt::MouseButton) event->button());
_buttonPressedMap.erase(input.getChannel());
// if we pressed and released at the same location, then create a "_CLICKED" input for this button
// we might want to add some small tolerance to this so if you do a small drag it still counts as
// a clicked.
if (_mousePressAt == event->pos()) {
_buttonPressedMap.insert(makeInput((Qt::MouseButton) event->button(), true).getChannel());
}
}
void KeyboardMouseDevice::eraseMouseClicked() {
_buttonPressedMap.erase(makeInput(Qt::LeftButton, true).getChannel());
_buttonPressedMap.erase(makeInput(Qt::MiddleButton, true).getChannel());
_buttonPressedMap.erase(makeInput(Qt::RightButton, true).getChannel());
}
void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
@ -77,6 +93,8 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event, unsigned int device
_axisStateMap[makeInput(MOUSE_AXIS_Y_NEG).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f);
_lastCursor = currentPos;
eraseMouseClicked();
}
void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) {
@ -138,14 +156,17 @@ controller::Input KeyboardMouseDevice::makeInput(Qt::Key code) const {
return controller::Input(_deviceID, shortCode, controller::ChannelType::BUTTON);
}
controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code) const {
controller::Input KeyboardMouseDevice::makeInput(Qt::MouseButton code, bool clicked) const {
switch (code) {
case Qt::LeftButton:
return controller::Input(_deviceID, MOUSE_BUTTON_LEFT, controller::ChannelType::BUTTON);
return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_LEFT_CLICKED :
MOUSE_BUTTON_LEFT, controller::ChannelType::BUTTON);
case Qt::RightButton:
return controller::Input(_deviceID, MOUSE_BUTTON_RIGHT, controller::ChannelType::BUTTON);
return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_RIGHT_CLICKED :
MOUSE_BUTTON_RIGHT, controller::ChannelType::BUTTON);
case Qt::MiddleButton:
return controller::Input(_deviceID, MOUSE_BUTTON_MIDDLE, controller::ChannelType::BUTTON);
return controller::Input(_deviceID, clicked ? MOUSE_BUTTON_MIDDLE_CLICKED :
MOUSE_BUTTON_MIDDLE, controller::ChannelType::BUTTON);
default:
return controller::Input();
};
@ -182,9 +203,13 @@ controller::Input::NamedVector KeyboardMouseDevice::getAvailableInputs() const {
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString()));
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseClick"));
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseClick"));
availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseClick"));
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));
availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseButton"));
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton, true), "LeftMouseClicked"));
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton, true), "MiddleMouseClicked"));
availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton, true), "RightMouseClicked"));
availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_POS), "MouseMoveRight"));
availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "MouseMoveLeft"));

View file

@ -37,6 +37,9 @@ public:
MOUSE_BUTTON_LEFT = KEYBOARD_LAST + 1,
MOUSE_BUTTON_RIGHT,
MOUSE_BUTTON_MIDDLE,
MOUSE_BUTTON_LEFT_CLICKED,
MOUSE_BUTTON_RIGHT_CLICKED,
MOUSE_BUTTON_MIDDLE_CLICKED,
};
enum MouseAxisChannel {
@ -83,6 +86,7 @@ public:
void mouseMoveEvent(QMouseEvent* event, unsigned int deviceID = 0);
void mousePressEvent(QMouseEvent* event, unsigned int deviceID = 0);
void mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID = 0);
void eraseMouseClicked();
void touchBeginEvent(const QTouchEvent* event);
void touchEndEvent(const QTouchEvent* event);
@ -92,7 +96,7 @@ public:
// Let's make it easy for Qt because we assume we love Qt forever
controller::Input makeInput(Qt::Key code) const;
controller::Input makeInput(Qt::MouseButton code) const;
controller::Input makeInput(Qt::MouseButton code, bool clicked = false) const;
controller::Input makeInput(MouseAxisChannel axis) const;
controller::Input makeInput(TouchAxisChannel axis) const;
controller::Input makeInput(TouchButtonChannel button) const;
@ -101,6 +105,7 @@ public:
protected:
QPoint _lastCursor;
QPoint _mousePressAt;
glm::vec2 _lastTouch;
bool _isTouching = false;

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 "NumericalConstants.h"
#include "SharedLogging.h"
#include "SharedUtil.h"
AtRestDetector::AtRestDetector(const glm::vec3& startPosition, const glm::quat& startRotation) {
reset(startPosition, startRotation);
@ -12,9 +26,14 @@ void AtRestDetector::reset(const glm::vec3& startPosition, const glm::quat& star
glm::quat ql = glm::log(startRotation);
_quatLogAverage = glm::vec3(ql.x, ql.y, ql.z);
_quatLogVariance = 0.0f;
_lastUpdateTime = usecTimestampNow();
_isAtRest = false;
}
bool AtRestDetector::update(float dt, const glm::vec3& position, const glm::quat& rotation) {
bool AtRestDetector::update(const glm::vec3& position, const glm::quat& rotation) {
uint64_t now = usecTimestampNow();
float dt = (float)(_lastUpdateTime - now) / (float)USECS_PER_SECOND;
_lastUpdateTime = now;
const float TAU = 1.0f;
float delta = glm::min(dt / TAU, 1.0f);
@ -37,5 +56,6 @@ bool AtRestDetector::update(float dt, const glm::vec3& position, const glm::quat
const float POSITION_VARIANCE_THRESHOLD = 0.001f;
const float QUAT_LOG_VARIANCE_THRESHOLD = 0.00002f;
return _positionVariance < POSITION_VARIANCE_THRESHOLD && _quatLogVariance < QUAT_LOG_VARIANCE_THRESHOLD;
_isAtRest = _positionVariance < POSITION_VARIANCE_THRESHOLD && _quatLogVariance < QUAT_LOG_VARIANCE_THRESHOLD;
return _isAtRest;
}

View file

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

View file

@ -9,15 +9,15 @@
#include <memory>
#include <QMainWindow>
#include <QGLWidget>
#include <QtWidgets/QMainWindow>
#include <QtOpenGL/QGLWidget>
#include <GLMHelpers.h>
#include <gl/GlWindow.h>
#include <QEvent>
#include <QResizeEvent>
#include <QOpenGLContext>
#include <QGuiApplication>
#include <QScreen>
#include <QtGui/QResizeEvent>
#include <QtGui/QOpenGLContext>
#include <QtGui/QGuiApplication>
#include <QtGui/QScreen>
#include <PerfStat.h>
#include <gl/OglplusHelpers.h>