Merge branch 'master' of https://github.com/highfidelity/hifi into punk

This commit is contained in:
Sam Gateau 2015-09-10 15:55:42 -07:00
commit ae03d32a84
28 changed files with 1716 additions and 315 deletions

View file

@ -9,71 +9,80 @@
// 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
// //
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var HIFI_OZAN_BUCKET = "http://hifi-public.s3.amazonaws.com/ozan/";
var rightHandAnimation = HIFI_PUBLIC_BUCKET + "animations/RightHandAnimPhilip.fbx"; var rightHandAnimation = HIFI_OZAN_BUCKET + "anim/squeeze_hands/right_hand_anim.fbx";
var leftHandAnimation = HIFI_PUBLIC_BUCKET + "animations/LeftHandAnimPhilip.fbx"; var leftHandAnimation = HIFI_OZAN_BUCKET + "anim/squeeze_hands/left_hand_anim.fbx";
var LEFT = 0; var lastLeftTrigger = 0;
var RIGHT = 1; var lastRightTrigger = 0;
var lastLeftFrame = 0; var leftIsClosing = true;
var lastRightFrame = 0; var rightIsClosing = true;
var leftDirection = true;
var rightDirection = true;
var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation? var LAST_FRAME = 15.0; // What is the number of the last frame we want to use in the animation?
var SMOOTH_FACTOR = 0.0; var SMOOTH_FACTOR = 0.75;
var MAX_FRAMES = 30.0; var MAX_FRAMES = 30.0;
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
var CONTROLLER_DEAD_SPOT = 0.25;
function clamp(val, min, max) {
if (val < min) {
return min;
} else if (val > max) {
return max;
} else {
return val;
}
}
function normalizeControllerValue(val) {
return clamp((val - CONTROLLER_DEAD_SPOT) / (1.0 - CONTROLLER_DEAD_SPOT), 0.0, 1.0);
}
Script.update.connect(function(deltaTime) { Script.update.connect(function(deltaTime) {
var leftTriggerValue = Controller.getActionValue(LEFT_HAND_CLICK); var leftTrigger = normalizeControllerValue(Controller.getActionValue(LEFT_HAND_CLICK));
var rightTriggerValue = Controller.getActionValue(RIGHT_HAND_CLICK); var rightTrigger = normalizeControllerValue(Controller.getActionValue(RIGHT_HAND_CLICK));
var leftFrame, rightFrame; // Average last few trigger values together for a bit of smoothing
var smoothLeftTrigger = leftTrigger * (1.0 - SMOOTH_FACTOR) + lastLeftTrigger * SMOOTH_FACTOR;
var smoothRightTrigger = rightTrigger * (1.0 - SMOOTH_FACTOR) + lastRightTrigger * SMOOTH_FACTOR;
// Average last few trigger frames together for a bit of smoothing if (leftTrigger == 0.0) {
leftFrame = (leftTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastLeftFrame * SMOOTH_FACTOR; leftIsClosing = true;
rightFrame = (rightTriggerValue * LAST_FRAME) * (1.0 - SMOOTH_FACTOR) + lastRightFrame * SMOOTH_FACTOR; } else if (leftTrigger == 1.0) {
leftIsClosing = false;
if (!leftDirection) {
leftFrame = MAX_FRAMES - leftFrame;
}
if (!rightDirection) {
rightFrame = MAX_FRAMES - rightFrame;
} }
if ((leftTriggerValue == 1.0) && (leftDirection == true)) { if (rightTrigger == 0.0) {
leftDirection = false; rightIsClosing = true;
lastLeftFrame = MAX_FRAMES - leftFrame; } else if (rightTrigger == 1.0) {
} else if ((leftTriggerValue == 0.0) && (leftDirection == false)) { rightIsClosing = false;
leftDirection = true;
lastLeftFrame = leftFrame;
}
if ((rightTriggerValue == 1.0) && (rightDirection == true)) {
rightDirection = false;
lastRightFrame = MAX_FRAMES - rightFrame;
} else if ((rightTriggerValue == 0.0) && (rightDirection == false)) {
rightDirection = true;
lastRightFrame = rightFrame;
} }
if ((leftFrame != lastLeftFrame) && leftHandAnimation.length){ lastLeftTrigger = smoothLeftTrigger;
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, leftFrame, leftFrame); lastRightTrigger = smoothRightTrigger;
}
if ((rightFrame != lastRightFrame) && rightHandAnimation.length) { // 0..15
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, rightFrame, rightFrame); var leftFrame = smoothLeftTrigger * LAST_FRAME;
var rightFrame = smoothRightTrigger * LAST_FRAME;
var adjustedLeftFrame = (leftIsClosing) ? leftFrame : (MAX_FRAMES - leftFrame);
if (leftHandAnimation.length) {
MyAvatar.startAnimation(leftHandAnimation, 30.0, 1.0, false, true, adjustedLeftFrame, adjustedLeftFrame);
} }
lastLeftFrame = leftFrame; var adjustedRightFrame = (rightIsClosing) ? rightFrame : (MAX_FRAMES - rightFrame);
lastRightFrame = rightFrame; if (rightHandAnimation.length) {
MyAvatar.startAnimation(rightHandAnimation, 30.0, 1.0, false, true, adjustedRightFrame, adjustedRightFrame);
}
}); });
Script.scriptEnding.connect(function() { Script.scriptEnding.connect(function() {
MyAvatar.stopAnimation(leftHandAnimation); MyAvatar.stopAnimation(leftHandAnimation);
MyAvatar.stopAnimation(rightHandAnimation); MyAvatar.stopAnimation(rightHandAnimation);
}); });

View file

@ -260,7 +260,6 @@ var toolBar = (function () {
cameraManager.disable(); cameraManager.disable();
} else { } else {
hasShownPropertiesTool = false; hasShownPropertiesTool = false;
cameraManager.enable();
entityListTool.setVisible(true); entityListTool.setVisible(true);
gridTool.setVisible(true); gridTool.setVisible(true);
grid.setEnabled(true); grid.setEnabled(true);
@ -670,15 +669,11 @@ function mouseMove(event) {
lastMousePosition = { x: event.x, y: event.y }; lastMousePosition = { x: event.x, y: event.y };
highlightEntityUnderCursor(lastMousePosition, false);
idleMouseTimerId = Script.setTimeout(handleIdleMouse, IDLE_MOUSE_TIMEOUT); idleMouseTimerId = Script.setTimeout(handleIdleMouse, IDLE_MOUSE_TIMEOUT);
} }
function handleIdleMouse() { function handleIdleMouse() {
idleMouseTimerId = null; idleMouseTimerId = null;
if (isActive) {
highlightEntityUnderCursor(lastMousePosition, true);
}
} }
function highlightEntityUnderCursor(position, accurateRay) { function highlightEntityUnderCursor(position, accurateRay) {
@ -802,6 +797,7 @@ function mouseClickEvent(event) {
selectionDisplay.select(selectedEntityID, event); selectionDisplay.select(selectedEntityID, event);
if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition, cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions, selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
@ -1142,6 +1138,7 @@ Controller.keyReleaseEvent.connect(function (event) {
} else if (event.text == "f") { } else if (event.text == "f") {
if (isActive) { if (isActive) {
if (selectionManager.hasSelection()) { if (selectionManager.hasSelection()) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition, cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions, selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); Menu.isOptionChecked(MENU_EASE_ON_FOCUS));

View file

@ -49,7 +49,7 @@ EntityListTool = function(opts) {
var selectedIDs = []; var selectedIDs = [];
for (var i = 0; i < selectionManager.selections.length; i++) { for (var i = 0; i < selectionManager.selections.length; i++) {
selectedIDs.push(selectionManager.selections[i].id); // ? selectedIDs.push(selectionManager.selections[i].id);
} }
var data = { var data = {
@ -70,6 +70,7 @@ EntityListTool = function(opts) {
} }
selectionManager.setSelections(entityIDs); selectionManager.setSelections(entityIDs);
if (data.focus) { if (data.focus) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition, cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions, selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); Menu.isOptionChecked(MENU_EASE_ON_FOCUS));

View file

@ -786,12 +786,12 @@ void Application::aboutToQuit() {
} }
void Application::cleanupBeforeQuit() { void Application::cleanupBeforeQuit() {
// Terminate third party processes so that they're not left running in the event of a subsequent shutdown crash // Stop third party processes so that they're not left running in the event of a subsequent shutdown crash.
#ifdef HAVE_DDE #ifdef HAVE_DDE
DependencyManager::destroy<DdeFaceTracker>(); DependencyManager::get<DdeFaceTracker>()->setEnabled(false);
#endif #endif
#ifdef HAVE_IVIEWHMD #ifdef HAVE_IVIEWHMD
DependencyManager::destroy<EyeTracker>(); DependencyManager::get<EyeTracker>()->setEnabled(false, true);
#endif #endif
if (_keyboardFocusHighlightID > 0) { if (_keyboardFocusHighlightID > 0) {
@ -842,6 +842,14 @@ void Application::cleanupBeforeQuit() {
// destroy the AudioClient so it and its thread have a chance to go down safely // destroy the AudioClient so it and its thread have a chance to go down safely
DependencyManager::destroy<AudioClient>(); DependencyManager::destroy<AudioClient>();
// Destroy third party processes after scripts have finished using them.
#ifdef HAVE_DDE
DependencyManager::destroy<DdeFaceTracker>();
#endif
#ifdef HAVE_IVIEWHMD
DependencyManager::destroy<EyeTracker>();
#endif
} }
void Application::emptyLocalCache() { void Application::emptyLocalCache() {

View file

@ -1268,10 +1268,15 @@ void MyAvatar::initHeadBones() {
} }
void MyAvatar::initAnimGraph() { void MyAvatar::initAnimGraph() {
// avatar.json
// https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9 // https://gist.github.com/hyperlogic/7d6a0892a7319c69e2b9
// python2 -m SimpleHTTPServer& //
//auto graphUrl = QUrl("http://localhost:8000/avatar.json"); // ik-avatar.json
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/7d6a0892a7319c69e2b9/raw/e2cb37aee601b6fba31d60eac3f6ae3ef72d4a66/avatar.json"); // https://gist.github.com/hyperlogic/e58e0a24cc341ad5d060
//
// or run a local web-server
// python -m SimpleHTTPServer&
auto graphUrl = QUrl("https://gist.githubusercontent.com/hyperlogic/e58e0a24cc341ad5d060/raw/2a994bef7726ce8e9efcee7622b8b1a1b6b67490/ik-avatar.json");
_rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry()); _rig->initAnimGraph(graphUrl, _skeletonModel.getGeometry()->getFBXGeometry());
} }

View file

@ -0,0 +1,106 @@
//
// AnimController.cpp
//
// Created by Anthony J. Thibault on 9/8/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 "AnimController.h"
#include "AnimationLogging.h"
AnimController::AnimController(const std::string& id, float alpha) :
AnimNode(AnimNode::Type::Controller, id),
_alpha(alpha) {
}
AnimController::~AnimController() {
}
const AnimPoseVec& AnimController::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
return overlay(animVars, dt, triggersOut, _skeleton->getRelativeBindPoses());
}
const AnimPoseVec& AnimController::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
_alpha = animVars.lookup(_alphaVar, _alpha);
for (auto& jointVar : _jointVars) {
if (!jointVar.hasPerformedJointLookup) {
QString qJointName = QString::fromStdString(jointVar.jointName);
jointVar.jointIndex = _skeleton->nameToJointIndex(qJointName);
if (jointVar.jointIndex < 0) {
qCWarning(animation) << "AnimController could not find jointName" << qJointName << "in skeleton";
}
jointVar.hasPerformedJointLookup = true;
}
if (jointVar.jointIndex >= 0) {
AnimPose defaultPose;
glm::quat absRot;
glm::quat parentAbsRot;
if (jointVar.jointIndex <= (int)underPoses.size()) {
// jointVar is an absolute rotation, if it is not set we will use the underPose as our default value
defaultPose = _skeleton->getAbsolutePose(jointVar.jointIndex, underPoses);
absRot = animVars.lookup(jointVar.var, defaultPose.rot);
// because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose.
int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex);
if (parentIndex >= 0) {
parentAbsRot = _skeleton->getAbsolutePose(parentIndex, underPoses).rot;
}
} else {
// jointVar is an absolute rotation, if it is not set we will use the bindPose as our default value
defaultPose = _skeleton->getAbsoluteBindPose(jointVar.jointIndex);
absRot = animVars.lookup(jointVar.var, defaultPose.rot);
// because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose
// here we use the bind pose
int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex);
if (parentIndex >= 0) {
parentAbsRot = _skeleton->getAbsoluteBindPose(parentIndex).rot;
}
}
// convert from absolute to relative
glm::quat relRot = glm::inverse(parentAbsRot) * absRot;
_poses[jointVar.jointIndex] = AnimPose(defaultPose.scale, relRot, defaultPose.trans);
}
}
return _poses;
}
void AnimController::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
AnimNode::setSkeletonInternal(skeleton);
// invalidate all jointVar indices
for (auto& jointVar : _jointVars) {
jointVar.jointIndex = -1;
jointVar.hasPerformedJointLookup = false;
}
// potentially allocate new joints.
_poses.resize(skeleton->getNumJoints());
// set all joints to identity
for (auto& pose : _poses) {
pose = AnimPose::identity;
}
}
// for AnimDebugDraw rendering
const AnimPoseVec& AnimController::getPosesInternal() const {
return _poses;
}
void AnimController::addJointVar(const JointVar& jointVar) {
_jointVars.push_back(jointVar);
}

View file

@ -0,0 +1,58 @@
//
// AnimController.h
//
// Created by Anthony J. Thibault on 9/8/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_AnimController_h
#define hifi_AnimController_h
#include "AnimNode.h"
// Allows procedural control over a set of joints.
class AnimController : public AnimNode {
public:
friend class AnimTests;
AnimController(const std::string& id, float alpha);
virtual ~AnimController() override;
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
void setAlphaVar(const std::string& alphaVar) { _alphaVar = alphaVar; }
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
struct JointVar {
JointVar(const std::string& varIn, const std::string& jointNameIn) : var(varIn), jointName(jointNameIn), jointIndex(-1), hasPerformedJointLookup(false) {}
std::string var = "";
std::string jointName = "";
int jointIndex = -1;
bool hasPerformedJointLookup = false;
};
void addJointVar(const JointVar& jointVar);
protected:
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override;
AnimPoseVec _poses;
float _alpha;
std::string _alphaVar;
std::vector<JointVar> _jointVars;
// no copies
AnimController(const AnimController&) = delete;
AnimController& operator=(const AnimController&) = delete;
};
#endif // hifi_AnimController_h

View file

@ -0,0 +1,584 @@
//
// AnimInverseKinematics.cpp
//
// 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 "AnimInverseKinematics.h"
#include <NumericalConstants.h>
#include <SharedUtil.h>
#include "ElbowConstraint.h"
#include "SwingTwistConstraint.h"
#include "AnimationLogging.h"
AnimInverseKinematics::AnimInverseKinematics(const std::string& id) : AnimNode(AnimNode::Type::InverseKinematics, id) {
}
AnimInverseKinematics::~AnimInverseKinematics() {
clearConstraints();
}
void AnimInverseKinematics::loadDefaultPoses(const AnimPoseVec& poses) {
_defaultRelativePoses = poses;
assert(_skeleton && _skeleton->getNumJoints() == (int)poses.size());
}
void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
assert(_skeleton && _skeleton->getNumJoints() == (int)poses.size());
if (_skeleton->getNumJoints() == (int)poses.size()) {
_relativePoses = poses;
} else {
_relativePoses.clear();
}
}
void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) const {
int numJoints = (int)_relativePoses.size();
absolutePoses.clear();
absolutePoses.reserve(numJoints);
assert(numJoints <= _skeleton->getNumJoints());
for (int i = 0; i < numJoints; ++i) {
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex < 0) {
absolutePoses[i] = _relativePoses[i];
} else {
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
}
}
}
void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar) {
// if there are dups, last one wins.
_targetVarVec.push_back(IKTargetVar(jointName, positionVar.toStdString(), rotationVar.toStdString()));
}
static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int index) {
// walk down the skeleton hierarchy to find the joint's root
int rootIndex = -1;
int parentIndex = skeleton->getParentIndex(index);
while (parentIndex != -1) {
rootIndex = parentIndex;
parentIndex = skeleton->getParentIndex(parentIndex);
}
return rootIndex;
}
//virtual
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) {
// NOTE: we assume that _relativePoses are up to date (e.g. loadPoses() was just called)
if (_relativePoses.empty()) {
return _relativePoses;
}
// evaluate target vars
for (auto& targetVar : _targetVarVec) {
// lazy look up of jointIndices and insertion into _absoluteTargets map
if (!targetVar.hasPerformedJointLookup) {
targetVar.jointIndex = _skeleton->nameToJointIndex(targetVar.jointName);
if (targetVar.jointIndex >= 0) {
// insert into _absoluteTargets map
IKTarget target;
target.pose = AnimPose::identity;
target.rootIndex = findRootJointInSkeleton(_skeleton, targetVar.jointIndex);
_absoluteTargets[targetVar.jointIndex] = target;
if (targetVar.jointIndex > _maxTargetIndex) {
_maxTargetIndex = targetVar.jointIndex;
}
} else {
qCWarning(animation) << "AnimInverseKinematics could not find jointName" << targetVar.jointName << "in skeleton";
}
targetVar.hasPerformedJointLookup = true;
}
if (targetVar.jointIndex >= 0) {
// update pose in _absoluteTargets map
auto iter = _absoluteTargets.find(targetVar.jointIndex);
if (iter != _absoluteTargets.end()) {
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses);
iter->second.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans);
iter->second.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot);
}
}
}
// RELAX! Don't do it.
// relaxTowardDefaults(dt);
if (_absoluteTargets.empty()) {
// no IK targets but still need to enforce constraints
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
while (constraintItr != _constraints.end()) {
int index = constraintItr->first;
glm::quat rotation = _relativePoses[index].rot;
constraintItr->second->apply(rotation);
_relativePoses[index].rot = rotation;
++constraintItr;
}
} else {
// compute absolute poses that correspond to relative target poses
AnimPoseVec absolutePoses;
computeAbsolutePoses(absolutePoses);
float largestError = 0.0f;
const float ACCEPTABLE_RELATIVE_ERROR = 1.0e-3f;
int numLoops = 0;
const int MAX_IK_LOOPS = 16;
const quint64 MAX_IK_TIME = 10 * USECS_PER_MSEC;
quint64 expiry = usecTimestampNow() + MAX_IK_TIME;
do {
largestError = 0.0f;
for (auto& targetPair: _absoluteTargets) {
int lowestMovedIndex = _relativePoses.size() - 1;
int tipIndex = targetPair.first;
AnimPose targetPose = targetPair.second.pose;
int rootIndex = targetPair.second.rootIndex;
if (rootIndex != -1) {
// transform targetPose into skeleton's absolute frame
AnimPose& rootPose = _relativePoses[rootIndex];
targetPose.trans = rootPose.trans + rootPose.rot * targetPose.trans;
targetPose.rot = rootPose.rot * targetPose.rot;
}
glm::vec3 tip = absolutePoses[tipIndex].trans;
float error = glm::length(targetPose.trans - tip);
if (error < ACCEPTABLE_RELATIVE_ERROR) {
if (largestError < error) {
largestError = error;
}
// this targetPose has been met
// finally set the relative rotation of the tip to agree with absolute target rotation
int parentIndex = _skeleton->getParentIndex(tipIndex);
if (parentIndex != -1) {
// compute tip's new parent-relative rotation
// Q = Qp * q --> q' = Qp^ * Q
glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot;
RotationConstraint* constraint = getConstraint(tipIndex);
if (constraint) {
constraint->apply(newRelativeRotation);
// TODO: ATM the final rotation target may fails but we need to provide
// feedback to the IK system so that it can adjust the bones up the skeleton
// to help this rotation target get met.
}
_relativePoses[tipIndex].rot = newRelativeRotation;
}
break;
}
// descend toward root, rotating each joint to get tip closer to target
int index = _skeleton->getParentIndex(tipIndex);
while (index != -1 && error > ACCEPTABLE_RELATIVE_ERROR) {
// compute the two lines that should be aligned
glm::vec3 jointPosition = absolutePoses[index].trans;
glm::vec3 leverArm = tip - jointPosition;
glm::vec3 pivotLine = targetPose.trans - jointPosition;
// compute the axis of the rotation that would align them
glm::vec3 axis = glm::cross(leverArm, pivotLine);
float axisLength = glm::length(axis);
if (axisLength > EPSILON) {
// compute deltaRotation for alignment (brings tip closer to target)
axis /= axisLength;
float angle = acosf(glm::dot(leverArm, pivotLine) / (glm::length(leverArm) * glm::length(pivotLine)));
// NOTE: even when axisLength is not zero (e.g. lever-arm and pivot-arm are not quite aligned) it is
// still possible for the angle to be zero so we also check that to avoid unnecessary calculations.
if (angle > EPSILON) {
glm::quat deltaRotation = glm::angleAxis(angle, axis);
int parentIndex = _skeleton->getParentIndex(index);
if (parentIndex == -1) {
// TODO? apply constraints to root?
// TODO? harvest the root's transform as movement of entire skeleton?
} else {
// compute joint's new parent-relative rotation
// Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q
glm::quat newRot = glm::normalize(glm::inverse(absolutePoses[parentIndex].rot) * deltaRotation * absolutePoses[index].rot);
RotationConstraint* constraint = getConstraint(index);
if (constraint) {
constraint->apply(newRot);
}
_relativePoses[index].rot = newRot;
}
// this joint has been changed so we check to see if it has the lowest index
if (index < lowestMovedIndex) {
lowestMovedIndex = index;
}
// keep track of tip's new position as we descend towards root
tip = jointPosition + deltaRotation * leverArm;
error = glm::length(targetPose.trans - tip);
}
}
index = _skeleton->getParentIndex(index);
}
if (largestError < error) {
largestError = error;
}
if (lowestMovedIndex <= _maxTargetIndex && lowestMovedIndex < tipIndex) {
// only update the absolutePoses that matter: those between lowestMovedIndex and _maxTargetIndex
for (int i = lowestMovedIndex; i <= _maxTargetIndex; ++i) {
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex != -1) {
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
}
}
}
// finally set the relative rotation of the tip to agree with absolute target rotation
int parentIndex = _skeleton->getParentIndex(tipIndex);
if (parentIndex != -1) {
// compute tip's new parent-relative rotation
// Q = Qp * q --> q' = Qp^ * Q
glm::quat newRelativeRotation = glm::inverse(absolutePoses[parentIndex].rot) * targetPose.rot;
RotationConstraint* constraint = getConstraint(tipIndex);
if (constraint) {
constraint->apply(newRelativeRotation);
// TODO: ATM the final rotation target just fails but we need to provide
// feedback to the IK system so that it can adjust the bones up the skeleton
// to help this rotation target get met.
}
_relativePoses[tipIndex].rot = newRelativeRotation;
absolutePoses[tipIndex].rot = targetPose.rot;
}
}
++numLoops;
} while (largestError > ACCEPTABLE_RELATIVE_ERROR && numLoops < MAX_IK_LOOPS && usecTimestampNow() < expiry);
}
return _relativePoses;
}
//virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
loadPoses(underPoses);
return evaluate(animVars, dt, triggersOut);
}
RotationConstraint* AnimInverseKinematics::getConstraint(int index) {
RotationConstraint* constraint = nullptr;
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.find(index);
if (constraintItr != _constraints.end()) {
constraint = constraintItr->second;
}
return constraint;
}
void AnimInverseKinematics::clearConstraints() {
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
while (constraintItr != _constraints.end()) {
delete constraintItr->second;
++constraintItr;
}
_constraints.clear();
}
const glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
const glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
void AnimInverseKinematics::initConstraints() {
if (!_skeleton) {
return;
}
// We create constraints for the joints shown here
// (and their Left counterparts if applicable).
//
// O RightHand
// /
// O /
// | O RightForeArm
// Neck O /
// | /
// O-------O-------O----O----O RightArm
// Spine2|
// |
// O Spine1
// |
// |
// O Spine
// |
// |
// O---O---O RightUpLeg
// | |
// | |
// | |
// O O RightLeg
// | |
// y | |
// | | |
// | O O RightFoot
// z | / /
// \ | O--O O--O
// \|
// x -----+
loadDefaultPoses(_skeleton->getRelativeBindPoses());
// compute corresponding absolute poses
int numJoints = (int)_defaultRelativePoses.size();
AnimPoseVec absolutePoses;
absolutePoses.reserve(numJoints);
for (int i = 0; i < numJoints; ++i) {
int parentIndex = _skeleton->getParentIndex(i);
if (parentIndex < 0) {
absolutePoses[i] = _defaultRelativePoses[i];
} else {
absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i];
}
}
_constraints.clear();
for (int i = 0; i < numJoints; ++i) {
QString name = _skeleton->getJointName(i);
bool isLeft = name.startsWith("Left", Qt::CaseInsensitive);
float mirror = isLeft ? -1.0f : 1.0f;
if (isLeft) {
//name.remove(0, 4);
} else if (name.startsWith("Right", Qt::CaseInsensitive)) {
//name.remove(0, 5);
}
RotationConstraint* constraint = nullptr;
if (0 == name.compare("Arm", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
stConstraint->setTwistLimits(-PI / 2.0f, PI / 2.0f);
// these directions are approximate swing limits in root-frame
// NOTE: they don't need to be normalized
std::vector<glm::vec3> swungDirections;
swungDirections.push_back(glm::vec3(mirror * 1.0f, 1.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * 1.0f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * 1.0f, -1.0f, 0.5f));
swungDirections.push_back(glm::vec3(mirror * 0.0f, -1.0f, 0.0f));
swungDirections.push_back(glm::vec3(mirror * 0.0f, -1.0f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * -0.5f, 0.0f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * 0.0f, 1.0f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * 0.0f, 1.0f, 0.0f));
// rotate directions into joint-frame
glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot);
int numDirections = (int)swungDirections.size();
for (int j = 0; j < numDirections; ++j) {
swungDirections[j] = invAbsoluteRotation * swungDirections[j];
}
stConstraint->setSwingLimits(swungDirections);
constraint = static_cast<RotationConstraint*>(stConstraint);
} else if (0 == name.compare("UpLeg", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f);
// these directions are approximate swing limits in root-frame
// NOTE: they don't need to be normalized
std::vector<glm::vec3> swungDirections;
swungDirections.push_back(glm::vec3(mirror * 0.25f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * -0.5f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 1.0f));
swungDirections.push_back(glm::vec3(mirror * -1.0f, 0.0f, 0.0f));
swungDirections.push_back(glm::vec3(mirror * -0.5f, -0.5f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * 0.0f, -0.75f, -1.0f));
swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 0.0f));
swungDirections.push_back(glm::vec3(mirror * 0.25f, -1.0f, 1.0f));
// rotate directions into joint-frame
glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot);
int numDirections = (int)swungDirections.size();
for (int j = 0; j < numDirections; ++j) {
swungDirections[j] = invAbsoluteRotation * swungDirections[j];
}
stConstraint->setSwingLimits(swungDirections);
constraint = static_cast<RotationConstraint*>(stConstraint);
} else if (0 == name.compare("RightHand", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
const float MAX_HAND_TWIST = PI / 2.0f;
stConstraint->setTwistLimits(-MAX_HAND_TWIST, MAX_HAND_TWIST);
// these directions are approximate swing limits in parent-frame
// NOTE: they don't need to be normalized
std::vector<glm::vec3> swungDirections;
swungDirections.push_back(glm::vec3(1.0f, 1.0f, 0.0f));
swungDirections.push_back(glm::vec3(0.75f, 1.0f, -1.0f));
swungDirections.push_back(glm::vec3(-0.75f, 1.0f, -1.0f));
swungDirections.push_back(glm::vec3(-1.0f, 1.0f, 0.0f));
swungDirections.push_back(glm::vec3(-0.75f, 1.0f, 1.0f));
swungDirections.push_back(glm::vec3(0.75f, 1.0f, 1.0f));
// rotate directions into joint-frame
glm::quat invRelativeRotation = glm::inverse(_defaultRelativePoses[i].rot);
int numDirections = (int)swungDirections.size();
for (int j = 0; j < numDirections; ++j) {
swungDirections[j] = invRelativeRotation * swungDirections[j];
}
stConstraint->setSwingLimits(swungDirections);
/*
std::vector<float> minDots;
const float MAX_HAND_SWING = PI / 3.0f;
minDots.push_back(cosf(MAX_HAND_SWING));
stConstraint->setSwingLimits(minDots);
*/
constraint = static_cast<RotationConstraint*>(stConstraint);
} else if (name.startsWith("SpineXXX", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
const float MAX_SPINE_TWIST = PI / 8.0f;
stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST);
std::vector<float> minDots;
const float MAX_SPINE_SWING = PI / 14.0f;
minDots.push_back(cosf(MAX_SPINE_SWING));
stConstraint->setSwingLimits(minDots);
constraint = static_cast<RotationConstraint*>(stConstraint);
} else if (0 == name.compare("NeckXXX", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
const float MAX_NECK_TWIST = PI / 2.0f;
stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST);
std::vector<float> minDots;
const float MAX_NECK_SWING = PI / 3.0f;
minDots.push_back(cosf(MAX_NECK_SWING));
stConstraint->setSwingLimits(minDots);
constraint = static_cast<RotationConstraint*>(stConstraint);
} else if (0 == name.compare("ForeArm", Qt::CaseInsensitive)) {
// The elbow joint rotates about the parent-frame's zAxis (-zAxis) for the Right (Left) arm.
ElbowConstraint* eConstraint = new ElbowConstraint();
glm::quat referenceRotation = _defaultRelativePoses[i].rot;
eConstraint->setReferenceRotation(referenceRotation);
// we determine the max/min angles by rotating the swing limit lines from parent- to child-frame
// then measure the angles to swing the yAxis into alignment
glm::vec3 hingeAxis = - mirror * zAxis;
const float MIN_ELBOW_ANGLE = 0.0f;
const float MAX_ELBOW_ANGLE = 7.0f * PI / 8.0f;
glm::quat invReferenceRotation = glm::inverse(referenceRotation);
glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_ELBOW_ANGLE, hingeAxis) * yAxis;
glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_ELBOW_ANGLE, hingeAxis) * yAxis;
// for the rest of the math we rotate hingeAxis into the child frame
hingeAxis = referenceRotation * hingeAxis;
eConstraint->setHingeAxis(hingeAxis);
glm::vec3 projectedYAxis = glm::normalize(yAxis - glm::dot(yAxis, hingeAxis) * hingeAxis);
float minAngle = acosf(glm::dot(projectedYAxis, minSwingAxis));
if (glm::dot(hingeAxis, glm::cross(projectedYAxis, minSwingAxis)) < 0.0f) {
minAngle = - minAngle;
}
float maxAngle = acosf(glm::dot(projectedYAxis, maxSwingAxis));
if (glm::dot(hingeAxis, glm::cross(projectedYAxis, maxSwingAxis)) < 0.0f) {
maxAngle = - maxAngle;
}
eConstraint->setAngleLimits(minAngle, maxAngle);
constraint = static_cast<RotationConstraint*>(eConstraint);
} else if (0 == name.compare("Leg", Qt::CaseInsensitive)) {
// The knee joint rotates about the parent-frame's -xAxis.
ElbowConstraint* eConstraint = new ElbowConstraint();
glm::quat referenceRotation = _defaultRelativePoses[i].rot;
eConstraint->setReferenceRotation(referenceRotation);
glm::vec3 hingeAxis = -1.0f * xAxis;
// we determine the max/min angles by rotating the swing limit lines from parent- to child-frame
// then measure the angles to swing the yAxis into alignment
const float MIN_KNEE_ANGLE = 0.0f;
const float MAX_KNEE_ANGLE = 3.0f * PI / 4.0f;
glm::quat invReferenceRotation = glm::inverse(referenceRotation);
glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * yAxis;
glm::vec3 maxSwingAxis = invReferenceRotation * glm::angleAxis(MAX_KNEE_ANGLE, hingeAxis) * yAxis;
// for the rest of the math we rotate hingeAxis into the child frame
hingeAxis = referenceRotation * hingeAxis;
eConstraint->setHingeAxis(hingeAxis);
glm::vec3 projectedYAxis = glm::normalize(yAxis - glm::dot(yAxis, hingeAxis) * hingeAxis);
float minAngle = acosf(glm::dot(projectedYAxis, minSwingAxis));
if (glm::dot(hingeAxis, glm::cross(projectedYAxis, minSwingAxis)) < 0.0f) {
minAngle = - minAngle;
}
float maxAngle = acosf(glm::dot(projectedYAxis, maxSwingAxis));
if (glm::dot(hingeAxis, glm::cross(projectedYAxis, maxSwingAxis)) < 0.0f) {
maxAngle = - maxAngle;
}
eConstraint->setAngleLimits(minAngle, maxAngle);
constraint = static_cast<RotationConstraint*>(eConstraint);
} else if (0 == name.compare("Foot", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
stConstraint->setTwistLimits(-PI / 4.0f, PI / 4.0f);
// these directions are approximate swing limits in parent-frame
// NOTE: they don't need to be normalized
std::vector<glm::vec3> swungDirections;
swungDirections.push_back(yAxis);
swungDirections.push_back(xAxis);
swungDirections.push_back(glm::vec3(1.0f, 1.0f, 1.0f));
swungDirections.push_back(glm::vec3(1.0f, 1.0f, -1.0f));
// rotate directions into joint-frame
glm::quat invRelativeRotation = glm::inverse(_defaultRelativePoses[i].rot);
int numDirections = (int)swungDirections.size();
for (int j = 0; j < numDirections; ++j) {
swungDirections[j] = invRelativeRotation * swungDirections[j];
}
stConstraint->setSwingLimits(swungDirections);
constraint = static_cast<RotationConstraint*>(stConstraint);
}
if (constraint) {
_constraints[i] = constraint;
}
}
}
void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
AnimNode::setSkeletonInternal(skeleton);
// invalidate all targetVars
for (auto& targetVar : _targetVarVec) {
targetVar.hasPerformedJointLookup = false;
}
// invalidate all targets
_absoluteTargets.clear();
_maxTargetIndex = 0;
// No constraints!
/*
if (skeleton) {
initConstraints();
} else {
clearConstraints();
}
*/
}
void AnimInverseKinematics::relaxTowardDefaults(float dt) {
// NOTE: for now we just use a single relaxation timescale for all joints, but in the future
// we could vary the timescale on a per-joint basis or do other fancy things.
// for each joint: lerp towards the default pose
const float RELAXATION_TIMESCALE = 0.25f;
const float alpha = glm::clamp(dt / RELAXATION_TIMESCALE, 0.0f, 1.0f);
int numJoints = (int)_relativePoses.size();
for (int i = 0; i < numJoints; ++i) {
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot, _defaultRelativePoses[i].rot));
_relativePoses[i].rot = glm::normalize(glm::lerp(_relativePoses[i].rot, dotSign * _defaultRelativePoses[i].rot, alpha));
}
}

View file

@ -0,0 +1,82 @@
//
// AnimInverseKinematics.h
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AnimInverseKinematics_h
#define hifi_AnimInverseKinematics_h
#include <string>
#include "AnimNode.h"
class RotationConstraint;
class AnimInverseKinematics : public AnimNode {
public:
AnimInverseKinematics(const std::string& id);
virtual ~AnimInverseKinematics() override;
void loadDefaultPoses(const AnimPoseVec& poses);
void loadPoses(const AnimPoseVec& poses);
const AnimPoseVec& getRelativePoses() const { return _relativePoses; }
void computeAbsolutePoses(AnimPoseVec& absolutePoses) const;
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar);
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) override;
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
protected:
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton);
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
void relaxTowardDefaults(float dt);
RotationConstraint* getConstraint(int index);
void clearConstraints();
void initConstraints();
struct IKTargetVar {
IKTargetVar(const QString& jointNameIn, const std::string& positionVarIn, const std::string& rotationVarIn) :
jointName(jointNameIn),
positionVar(positionVarIn),
rotationVar(rotationVarIn),
jointIndex(-1),
hasPerformedJointLookup(false) {}
std::string positionVar;
std::string rotationVar;
QString jointName;
int jointIndex; // cached joint index
bool hasPerformedJointLookup = false;
};
struct IKTarget {
AnimPose pose;
int rootIndex;
};
std::map<int, RotationConstraint*> _constraints;
std::vector<IKTargetVar> _targetVarVec;
std::map<int, IKTarget> _absoluteTargets; // IK targets of end-points
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
AnimPoseVec _relativePoses; // current relative poses
// no copies
AnimInverseKinematics(const AnimInverseKinematics&) = delete;
AnimInverseKinematics& operator=(const AnimInverseKinematics&) = delete;
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
// during the the cyclic coordinate descent algorithm
int _maxTargetIndex = 0;
};
#endif // hifi_AnimInverseKinematics_h

View file

@ -40,6 +40,8 @@ public:
BlendLinear, BlendLinear,
Overlay, Overlay,
StateMachine, StateMachine,
Controller,
InverseKinematics,
NumTypes NumTypes
}; };
using Pointer = std::shared_ptr<AnimNode>; using Pointer = std::shared_ptr<AnimNode>;

View file

@ -20,6 +20,8 @@
#include "AnimOverlay.h" #include "AnimOverlay.h"
#include "AnimNodeLoader.h" #include "AnimNodeLoader.h"
#include "AnimStateMachine.h" #include "AnimStateMachine.h"
#include "AnimController.h"
#include "AnimInverseKinematics.h"
using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeLoaderFunc = AnimNode::Pointer (*)(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl); using NodeProcessFunc = bool (*)(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
@ -29,12 +31,17 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
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 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 loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
static AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl);
// called after children have been loaded // called after children have been loaded
// returns node on success, nullptr on failure.
static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } static bool processClipNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } static bool processBlendLinearNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; }
static bool processOverlayNode(AnimNode::Pointer node, const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) { return true; } static bool 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 processControllerNode(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) {
@ -42,6 +49,8 @@ static const char* animNodeTypeToString(AnimNode::Type type) {
case AnimNode::Type::BlendLinear: return "blendLinear"; case AnimNode::Type::BlendLinear: return "blendLinear";
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::Controller: return "controller";
case AnimNode::Type::InverseKinematics: return "inverseKinematics";
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -53,6 +62,8 @@ static NodeLoaderFunc animNodeTypeToLoaderFunc(AnimNode::Type type) {
case AnimNode::Type::BlendLinear: return loadBlendLinearNode; case AnimNode::Type::BlendLinear: return loadBlendLinearNode;
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::Controller: return loadControllerNode;
case AnimNode::Type::InverseKinematics: return loadInverseKinematicsNode;
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
@ -64,18 +75,20 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
case AnimNode::Type::BlendLinear: return processBlendLinearNode; case AnimNode::Type::BlendLinear: return processBlendLinearNode;
case AnimNode::Type::Overlay: return processOverlayNode; case AnimNode::Type::Overlay: return processOverlayNode;
case AnimNode::Type::StateMachine: return processStateMachineNode; case AnimNode::Type::StateMachine: return processStateMachineNode;
case AnimNode::Type::Controller: return processControllerNode;
case AnimNode::Type::InverseKinematics: return processInverseKinematicsNode;
case AnimNode::Type::NumTypes: return nullptr; case AnimNode::Type::NumTypes: return nullptr;
}; };
return nullptr; return nullptr;
} }
#define READ_STRING(NAME, JSON_OBJ, ID, URL) \ #define READ_STRING(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \
auto NAME##_VAL = JSON_OBJ.value(#NAME); \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \
if (!NAME##_VAL.isString()) { \ if (!NAME##_VAL.isString()) { \
qCCritical(animation) << "AnimNodeLoader, error reading string" \ qCCritical(animation) << "AnimNodeLoader, error reading string" \
<< #NAME << ", id =" << ID \ << #NAME << ", id =" << ID \
<< ", url =" << URL.toDisplayString(); \ << ", url =" << URL.toDisplayString(); \
return nullptr; \ return ERROR_RETURN; \
} \ } \
QString NAME = NAME##_VAL.toString() QString NAME = NAME##_VAL.toString()
@ -86,23 +99,23 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
NAME = NAME##_VAL.toString(); \ NAME = NAME##_VAL.toString(); \
} }
#define READ_BOOL(NAME, JSON_OBJ, ID, URL) \ #define READ_BOOL(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \
auto NAME##_VAL = JSON_OBJ.value(#NAME); \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \
if (!NAME##_VAL.isBool()) { \ if (!NAME##_VAL.isBool()) { \
qCCritical(animation) << "AnimNodeLoader, error reading bool" \ qCCritical(animation) << "AnimNodeLoader, error reading bool" \
<< #NAME << ", id =" << ID \ << #NAME << ", id =" << ID \
<< ", url =" << URL.toDisplayString(); \ << ", url =" << URL.toDisplayString(); \
return nullptr; \ return ERROR_RETURN; \
} \ } \
bool NAME = NAME##_VAL.toBool() bool NAME = NAME##_VAL.toBool()
#define READ_FLOAT(NAME, JSON_OBJ, ID, URL) \ #define READ_FLOAT(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \
auto NAME##_VAL = JSON_OBJ.value(#NAME); \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \
if (!NAME##_VAL.isDouble()) { \ if (!NAME##_VAL.isDouble()) { \
qCCritical(animation) << "AnimNodeLoader, error reading double" \ qCCritical(animation) << "AnimNodeLoader, error reading double" \
<< #NAME << "id =" << ID \ << #NAME << "id =" << ID \
<< ", url =" << URL.toDisplayString(); \ << ", url =" << URL.toDisplayString(); \
return nullptr; \ return ERROR_RETURN; \
} \ } \
float NAME = (float)NAME##_VAL.toDouble() float NAME = (float)NAME##_VAL.toDouble()
@ -171,11 +184,11 @@ static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUr
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) {
READ_STRING(url, jsonObj, id, jsonUrl); READ_STRING(url, jsonObj, id, jsonUrl, nullptr);
READ_FLOAT(startFrame, jsonObj, id, jsonUrl); READ_FLOAT(startFrame, jsonObj, id, jsonUrl, nullptr);
READ_FLOAT(endFrame, jsonObj, id, jsonUrl); READ_FLOAT(endFrame, jsonObj, id, jsonUrl, nullptr);
READ_FLOAT(timeScale, jsonObj, id, jsonUrl); READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr);
READ_BOOL(loopFlag, jsonObj, id, jsonUrl); READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr);
READ_OPTIONAL_STRING(startFrameVar, jsonObj); READ_OPTIONAL_STRING(startFrameVar, jsonObj);
READ_OPTIONAL_STRING(endFrameVar, jsonObj); READ_OPTIONAL_STRING(endFrameVar, jsonObj);
@ -202,7 +215,7 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
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) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl); READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
READ_OPTIONAL_STRING(alphaVar, jsonObj); READ_OPTIONAL_STRING(alphaVar, jsonObj);
@ -239,8 +252,8 @@ static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
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) {
READ_STRING(boneSet, jsonObj, id, jsonUrl); READ_STRING(boneSet, jsonObj, id, jsonUrl, nullptr);
READ_FLOAT(alpha, jsonObj, id, jsonUrl); READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
auto boneSetEnum = stringToBoneSetEnum(boneSet); auto boneSetEnum = stringToBoneSetEnum(boneSet);
if (boneSetEnum == AnimOverlay::NumBoneSets) { if (boneSetEnum == AnimOverlay::NumBoneSets) {
@ -268,6 +281,67 @@ static AnimNode::Pointer loadStateMachineNode(const QJsonObject& jsonObj, const
return node; return node;
} }
static AnimNode::Pointer loadControllerNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
READ_FLOAT(alpha, jsonObj, id, jsonUrl, nullptr);
auto node = std::make_shared<AnimController>(id.toStdString(), alpha);
READ_OPTIONAL_STRING(alphaVar, jsonObj);
if (!alphaVar.isEmpty()) {
node->setAlphaVar(alphaVar.toStdString());
}
auto jointsValue = jsonObj.value("joints");
if (!jointsValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"joints\" in controller node, id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto jointsArray = jointsValue.toArray();
for (const auto& jointValue : jointsArray) {
if (!jointValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad state object in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto jointObj = jointValue.toObject();
READ_STRING(var, jointObj, id, jsonUrl, nullptr);
READ_STRING(jointName, jointObj, id, jsonUrl, nullptr);
AnimController::JointVar jointVar(var.toStdString(), jointName.toStdString());
node->addJointVar(jointVar);
};
return node;
}
AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
auto node = std::make_shared<AnimInverseKinematics>(id.toStdString());
auto targetsValue = jsonObj.value("targets");
if (!targetsValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"targets\" in inverseKinematics node, id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto targetsArray = targetsValue.toArray();
for (const auto& targetValue : targetsArray) {
if (!targetValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad state object in \"targets\", id =" << id << ", url =" << jsonUrl.toDisplayString();
return nullptr;
}
auto targetObj = targetValue.toObject();
READ_STRING(jointName, targetObj, id, jsonUrl, nullptr);
READ_STRING(positionVar, targetObj, id, jsonUrl, nullptr);
READ_STRING(rotationVar, targetObj, id, jsonUrl, nullptr);
node->setTargetVars(jointName, positionVar, rotationVar);
};
return node;
}
void buildChildMap(std::map<std::string, AnimNode::Pointer>& map, AnimNode::Pointer node) { void buildChildMap(std::map<std::string, AnimNode::Pointer>& map, AnimNode::Pointer node) {
for ( auto child : node->_children ) { for ( auto child : node->_children ) {
map.insert(std::pair<std::string, AnimNode::Pointer>(child->_id, child)); map.insert(std::pair<std::string, AnimNode::Pointer>(child->_id, child));
@ -278,12 +352,12 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
auto smNode = std::static_pointer_cast<AnimStateMachine>(node); auto smNode = std::static_pointer_cast<AnimStateMachine>(node);
assert(smNode); assert(smNode);
READ_STRING(currentState, jsonObj, nodeId, jsonUrl); READ_STRING(currentState, jsonObj, nodeId, jsonUrl, false);
auto statesValue = jsonObj.value("states"); auto statesValue = jsonObj.value("states");
if (!statesValue.isArray()) { if (!statesValue.isArray()) {
qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); qCCritical(animation) << "AnimNodeLoader, bad array \"states\" in stateMachine node, id =" << nodeId << ", url =" << jsonUrl.toDisplayString();
return nullptr; return false;
} }
// build a map for all children by name. // build a map for all children by name.
@ -302,13 +376,13 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
for (const auto& stateValue : statesArray) { for (const auto& stateValue : statesArray) {
if (!stateValue.isObject()) { if (!stateValue.isObject()) {
qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString(); qCCritical(animation) << "AnimNodeLoader, bad state object in \"states\", id =" << nodeId << ", url =" << jsonUrl.toDisplayString();
return nullptr; return false;
} }
auto stateObj = stateValue.toObject(); auto stateObj = stateValue.toObject();
READ_STRING(id, stateObj, nodeId, jsonUrl); READ_STRING(id, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl); READ_FLOAT(interpTarget, stateObj, nodeId, jsonUrl, false);
READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl); READ_FLOAT(interpDuration, stateObj, nodeId, jsonUrl, false);
READ_OPTIONAL_STRING(interpTargetVar, stateObj); READ_OPTIONAL_STRING(interpTargetVar, stateObj);
READ_OPTIONAL_STRING(interpDurationVar, stateObj); READ_OPTIONAL_STRING(interpDurationVar, stateObj);
@ -318,7 +392,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
auto iter = childMap.find(stdId); auto iter = childMap.find(stdId);
if (iter == childMap.end()) { if (iter == childMap.end()) {
qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString(); qCCritical(animation) << "AnimNodeLoader, could not find stateMachine child (state) with nodeId =" << nodeId << "stateId =" << id << "url =" << jsonUrl.toDisplayString();
return nullptr; return false;
} }
auto statePtr = std::make_shared<AnimStateMachine::State>(stdId, iter->second, interpTarget, interpDuration); auto statePtr = std::make_shared<AnimStateMachine::State>(stdId, iter->second, interpTarget, interpDuration);
@ -337,19 +411,19 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
auto transitionsValue = stateObj.value("transitions"); auto transitionsValue = stateObj.value("transitions");
if (!transitionsValue.isArray()) { if (!transitionsValue.isArray()) {
qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in stateMachine node, stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString(); qCritical(animation) << "AnimNodeLoader, bad array \"transitions\" in stateMachine node, stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString();
return nullptr; return false;
} }
auto transitionsArray = transitionsValue.toArray(); auto transitionsArray = transitionsValue.toArray();
for (const auto& transitionValue : transitionsArray) { for (const auto& transitionValue : transitionsArray) {
if (!transitionValue.isObject()) { if (!transitionValue.isObject()) {
qCritical(animation) << "AnimNodeLoader, bad transition object in \"transtions\", stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString(); qCritical(animation) << "AnimNodeLoader, bad transition object in \"transtions\", stateId =" << id << "nodeId =" << nodeId << "url =" << jsonUrl.toDisplayString();
return nullptr; return false;
} }
auto transitionObj = transitionValue.toObject(); auto transitionObj = transitionValue.toObject();
READ_STRING(var, transitionObj, nodeId, jsonUrl); READ_STRING(var, transitionObj, nodeId, jsonUrl, false);
READ_STRING(state, transitionObj, nodeId, jsonUrl); READ_STRING(state, transitionObj, nodeId, jsonUrl, false);
transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString()))); transitionMap.insert(TransitionMap::value_type(statePtr, StringPair(var.toStdString(), state.toStdString())));
} }
@ -363,7 +437,7 @@ bool processStateMachineNode(AnimNode::Pointer node, const QJsonObject& jsonObj,
srcState->addTransition(AnimStateMachine::State::Transition(transition.second.first, iter->second)); srcState->addTransition(AnimStateMachine::State::Transition(transition.second.first, iter->second));
} else { } else {
qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id.c_str() << "dstState =" << transition.second.second.c_str() << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString(); qCCritical(animation) << "AnimNodeLoader, bad state machine transtion from srcState =" << srcState->_id.c_str() << "dstState =" << transition.second.second.c_str() << "nodeId =" << nodeId << "url = " << jsonUrl.toDisplayString();
return nullptr; return false;
} }
} }

View file

@ -15,7 +15,7 @@
#include <QString> #include <QString>
#include <QUrl> #include <QUrl>
#include <QNetworkReply> #include <QtNetwork/QNetworkReply>
#include "AnimNode.h" #include "AnimNode.h"

View file

@ -9,10 +9,12 @@
// //
#include "AnimSkeleton.h" #include "AnimSkeleton.h"
#include "AnimationLogging.h"
#include "GLMHelpers.h"
#include <glm/gtx/transform.hpp> #include <glm/gtx/transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <GLMHelpers.h>
#include "AnimationLogging.h"
const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f), const AnimPose AnimPose::identity = AnimPose(glm::vec3(1.0f),
glm::quat(), glm::quat(),
@ -103,6 +105,14 @@ const QString& AnimSkeleton::getJointName(int jointIndex) const {
return _joints[jointIndex].name; return _joints[jointIndex].name;
} }
AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const {
if (jointIndex < 0) {
return AnimPose::identity;
} else {
return getAbsolutePose(_joints[jointIndex].parentIndex, poses) * poses[jointIndex];
}
}
void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints, const AnimPose& geometryOffset) { void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints, const AnimPose& geometryOffset) {
_joints = joints; _joints = joints;

View file

@ -12,8 +12,10 @@
#define hifi_AnimSkeleton #define hifi_AnimSkeleton
#include <vector> #include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "FBXReader.h" #include <FBXReader.h>
struct AnimPose { struct AnimPose {
AnimPose() {} AnimPose() {}
@ -58,9 +60,12 @@ public:
// relative to parent pose // relative to parent pose
const AnimPose& getRelativeBindPose(int jointIndex) const; const AnimPose& getRelativeBindPose(int jointIndex) const;
const AnimPoseVec& getRelativeBindPoses() const { return _relativeBindPoses; }
int getParentIndex(int jointIndex) const; int getParentIndex(int jointIndex) const;
AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const;
#ifndef NDEBUG #ifndef NDEBUG
void dump() const; void dump() const;
void dump(const AnimPoseVec& poses) const; void dump(const AnimPoseVec& poses) const;

View file

@ -12,9 +12,9 @@
#ifndef hifi_AnimationCache_h #ifndef hifi_AnimationCache_h
#define hifi_AnimationCache_h #define hifi_AnimationCache_h
#include <QRunnable> #include <QtCore/QRunnable>
#include <QScriptEngine> #include <QtScript/QScriptEngine>
#include <QScriptValue> #include <QtScript/QScriptValue>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <FBXReader.h> #include <FBXReader.h>

View file

@ -15,7 +15,6 @@
#include <NumericalConstants.h> #include <NumericalConstants.h>
ElbowConstraint::ElbowConstraint() : ElbowConstraint::ElbowConstraint() :
_referenceRotation(),
_minAngle(-PI), _minAngle(-PI),
_maxAngle(PI) _maxAngle(PI)
{ {

View file

@ -15,12 +15,10 @@
class ElbowConstraint : public RotationConstraint { class ElbowConstraint : public RotationConstraint {
public: public:
ElbowConstraint(); ElbowConstraint();
virtual void setReferenceRotation(const glm::quat& rotation) override { _referenceRotation = rotation; }
void setHingeAxis(const glm::vec3& axis); void setHingeAxis(const glm::vec3& axis);
void setAngleLimits(float minAngle, float maxAngle); void setAngleLimits(float minAngle, float maxAngle);
virtual bool apply(glm::quat& rotation) const override; virtual bool apply(glm::quat& rotation) const override;
protected: protected:
glm::quat _referenceRotation;
glm::vec3 _axis; glm::vec3 _axis;
glm::vec3 _perpAxis; glm::vec3 _perpAxis;
float _minAngle; float _minAngle;

View file

@ -737,6 +737,19 @@ void Rig::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::q
} }
int numFree = freeLineage.size(); int numFree = freeLineage.size();
if (_enableAnimGraph && _animSkeleton) {
if (endIndex == _leftHandJointIndex) {
auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans;
_animVars.set("leftHandPosition", targetPosition + rootTrans);
_animVars.set("leftHandRotation", targetRotation);
} else if (endIndex == _rightHandJointIndex) {
auto rootTrans = _animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans;
_animVars.set("rightHandPosition", targetPosition + rootTrans);
_animVars.set("rightHandRotation", targetRotation);
}
return;
}
// store and remember topmost parent transform // store and remember topmost parent transform
glm::mat4 topParentTransform; glm::mat4 topParentTransform;
{ {
@ -947,18 +960,28 @@ void Rig::updateFromHeadParameters(const HeadParameters& params) {
void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) { void Rig::updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist) {
if (index >= 0 && _jointStates[index].getParentIndex() >= 0) { if (index >= 0 && _jointStates[index].getParentIndex() >= 0) {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()]; if (_enableAnimGraph && _animSkeleton) {
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat absRot = (glm::angleAxis(-RADIANS_PER_DEGREE * leanSideways, zAxis) *
glm::angleAxis(-RADIANS_PER_DEGREE * leanForward, xAxis) *
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, yAxis));
_animVars.set("lean", absRot);
} else if (!_enableAnimGraph) {
auto& parentState = _jointStates[_jointStates[index].getParentIndex()];
// get the rotation axes in joint space and use them to adjust the rotation // get the rotation axes in joint space and use them to adjust the rotation
glm::vec3 xAxis(1.0f, 0.0f, 0.0f); glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 yAxis(0.0f, 1.0f, 0.0f); glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 zAxis(0.0f, 0.0f, 1.0f); glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index)); glm::quat inverse = glm::inverse(parentState.getRotation() * getJointDefaultRotationInParentFrame(index));
setJointRotationInConstrainedFrame(index, setJointRotationInConstrainedFrame(index,
glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) * glm::angleAxis(- RADIANS_PER_DEGREE * leanSideways, inverse * zAxis) *
glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) * glm::angleAxis(- RADIANS_PER_DEGREE * leanForward, inverse * xAxis) *
glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) * glm::angleAxis(RADIANS_PER_DEGREE * torsoTwist, inverse * yAxis) *
getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY); getJointState(index).getDefaultRotation(), DEFAULT_PRIORITY);
}
} }
} }
@ -1021,7 +1044,7 @@ void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
_animNode = nodeIn; _animNode = nodeIn;
_animNode->setSkeleton(_animSkeleton); _animNode->setSkeleton(_animSkeleton);
}); });
connect(_animLoader.get(), &AnimNodeLoader::error, [this, url](int error, QString str) { connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) {
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str; qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
}); });
} }

View file

@ -15,15 +15,21 @@
class RotationConstraint { class RotationConstraint {
public: public:
RotationConstraint() {} RotationConstraint() : _referenceRotation() {}
virtual ~RotationConstraint() {} virtual ~RotationConstraint() {}
/// \param rotation the default rotation that represents /// \param referenceRotation the rotation from which rotation changes are measured.
virtual void setReferenceRotation(const glm::quat& rotation) = 0; virtual void setReferenceRotation(const glm::quat& rotation) { _referenceRotation = rotation; }
/// \return the rotation from which rotation changes are measured.
const glm::quat& getReferenceRotation() const { return _referenceRotation; }
/// \param rotation rotation to clamp /// \param rotation rotation to clamp
/// \return true if rotation is clamped /// \return true if rotation is clamped
virtual bool apply(glm::quat& rotation) const = 0; virtual bool apply(glm::quat& rotation) const = 0;
protected:
glm::quat _referenceRotation = glm::quat();
}; };
#endif // hifi_RotationConstraint_h #endif // hifi_RotationConstraint_h

View file

@ -32,14 +32,20 @@ void SwingTwistConstraint::SwingLimitFunction::setCone(float maxAngle) {
} }
void SwingTwistConstraint::SwingLimitFunction::setMinDots(const std::vector<float>& minDots) { void SwingTwistConstraint::SwingLimitFunction::setMinDots(const std::vector<float>& minDots) {
int numDots = minDots.size(); uint32_t numDots = minDots.size();
_minDots.clear(); _minDots.clear();
_minDots.reserve(numDots); if (numDots == 0) {
for (int i = 0; i < numDots; ++i) { // push two copies of MIN_MINDOT
_minDots.push_back(glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT)); _minDots.push_back(MIN_MINDOT);
_minDots.push_back(MIN_MINDOT);
} else {
_minDots.reserve(numDots);
for (uint32_t i = 0; i < numDots; ++i) {
_minDots.push_back(glm::clamp(minDots[i], MIN_MINDOT, MAX_MINDOT));
}
// push the first value to the back to establish cyclic boundary conditions
_minDots.push_back(_minDots[0]);
} }
// push the first value to the back to establish cyclic boundary conditions
_minDots.push_back(_minDots[0]);
} }
float SwingTwistConstraint::SwingLimitFunction::getMinDot(float theta) const { float SwingTwistConstraint::SwingLimitFunction::getMinDot(float theta) const {
@ -58,8 +64,8 @@ float SwingTwistConstraint::SwingLimitFunction::getMinDot(float theta) const {
} }
SwingTwistConstraint::SwingTwistConstraint() : SwingTwistConstraint::SwingTwistConstraint() :
RotationConstraint(),
_swingLimitFunction(), _swingLimitFunction(),
_referenceRotation(),
_minTwist(-PI), _minTwist(-PI),
_maxTwist(PI) _maxTwist(PI)
{ {
@ -69,6 +75,85 @@ void SwingTwistConstraint::setSwingLimits(std::vector<float> minDots) {
_swingLimitFunction.setMinDots(minDots); _swingLimitFunction.setMinDots(minDots);
} }
void SwingTwistConstraint::setSwingLimits(const std::vector<glm::vec3>& swungDirections) {
struct SwingLimitData {
SwingLimitData() : _theta(0.0f), _minDot(1.0f) {}
SwingLimitData(float theta, float minDot) : _theta(theta), _minDot(minDot) {}
float _theta;
float _minDot;
bool operator<(const SwingLimitData& other) const { return _theta < other._theta; }
};
std::vector<SwingLimitData> limits;
uint32_t numLimits = swungDirections.size();
limits.reserve(numLimits);
// compute the limit pairs: <theta, minDot>
const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
for (uint32_t i = 0; i < numLimits; ++i) {
float directionLength = glm::length(swungDirections[i]);
if (directionLength > EPSILON) {
glm::vec3 swingAxis = glm::cross(yAxis, swungDirections[i]);
float theta = atan2f(-swingAxis.z, swingAxis.x);
if (theta < 0.0f) {
theta += TWO_PI;
}
limits.push_back(SwingLimitData(theta, swungDirections[i].y / directionLength));
}
}
std::vector<float> minDots;
numLimits = limits.size();
if (numLimits == 0) {
// trivial case: nearly free constraint
std::vector<float> minDots;
_swingLimitFunction.setMinDots(minDots);
} else if (numLimits == 1) {
// trivial case: uniform conical constraint
std::vector<float> minDots;
minDots.push_back(limits[0]._minDot);
_swingLimitFunction.setMinDots(minDots);
} else {
// interesting case: potentially non-uniform constraints
// sort limits by theta
std::sort(limits.begin(), limits.end());
// extrapolate evenly distributed limits for fast lookup table
float deltaTheta = TWO_PI / (float)(numLimits);
uint32_t rightIndex = 0;
for (uint32_t i = 0; i < numLimits; ++i) {
float theta = (float)i * deltaTheta;
uint32_t leftIndex = (rightIndex - 1) % numLimits;
while (rightIndex < numLimits && theta > limits[rightIndex]._theta) {
leftIndex = rightIndex++;
}
if (leftIndex == numLimits - 1) {
// we straddle the boundary
rightIndex = 0;
}
float rightTheta = limits[rightIndex]._theta;
float leftTheta = limits[leftIndex]._theta;
if (leftTheta > rightTheta) {
// we straddle the boundary, but we need to figure out which way to stride
// in order to keep theta between left and right
if (leftTheta > theta) {
leftTheta -= TWO_PI;
} else {
rightTheta += TWO_PI;
}
}
// blend between the left and right minDots to get the value that corresponds to this theta
float rightWeight = (theta - leftTheta) / (rightTheta - leftTheta);
minDots.push_back((1.0f - rightWeight) * limits[leftIndex]._minDot + rightWeight * limits[rightIndex]._minDot);
}
}
_swingLimitFunction.setMinDots(minDots);
}
void SwingTwistConstraint::setTwistLimits(float minTwist, float maxTwist) { void SwingTwistConstraint::setTwistLimits(float minTwist, float maxTwist) {
// NOTE: min/maxTwist angles should be in the range [-PI, PI] // NOTE: min/maxTwist angles should be in the range [-PI, PI]
_minTwist = glm::min(minTwist, maxTwist); _minTwist = glm::min(minTwist, maxTwist);
@ -107,13 +192,10 @@ bool SwingTwistConstraint::apply(glm::quat& rotation) const {
float theta = atan2f(-swingAxis.z, swingAxis.x); float theta = atan2f(-swingAxis.z, swingAxis.x);
float minDot = _swingLimitFunction.getMinDot(theta); float minDot = _swingLimitFunction.getMinDot(theta);
if (glm::dot(swungY, yAxis) < minDot) { if (glm::dot(swungY, yAxis) < minDot) {
// The swing limits are violated so we use the maxAngle to supply a new rotation. // The swing limits are violated so we extract the angle from midDot and
float maxAngle = acosf(minDot); // use it to supply a new rotation.
if (minDot < 0.0f) {
maxAngle = PI - maxAngle;
}
swingAxis /= axisLength; swingAxis /= axisLength;
swingRotation = glm::angleAxis(maxAngle, swingAxis); swingRotation = glm::angleAxis(acosf(minDot), swingAxis);
swingWasClamped = true; swingWasClamped = true;
} }
} }

View file

@ -16,7 +16,7 @@
#include <math.h> #include <math.h>
class SwingTwistConstraint : RotationConstraint { class SwingTwistConstraint : public RotationConstraint {
public: public:
// The SwingTwistConstraint starts in the "referenceRotation" and then measures an initial twist // The SwingTwistConstraint starts in the "referenceRotation" and then measures an initial twist
// about the yAxis followed by a swing about some axis that lies in the XZ plane, such that the twist // about the yAxis followed by a swing about some axis that lies in the XZ plane, such that the twist
@ -25,10 +25,7 @@ public:
SwingTwistConstraint(); SwingTwistConstraint();
/// \param referenceRotation the rotation from which rotation changes are measured. /// \param minDots vector of minimum dot products between the twist and swung axes
virtual void setReferenceRotation(const glm::quat& referenceRotation) override { _referenceRotation = referenceRotation; }
/// \param minDots vector of minimum dot products between the twist and swung axes.
/// \brief The values are minimum dot-products between the twist axis and the swung axes /// \brief The values are minimum dot-products between the twist axis and the swung axes
/// that correspond to swing axes equally spaced around the XZ plane. Another way to /// that correspond to swing axes equally spaced around the XZ plane. Another way to
/// think about it is that the dot-products correspond to correspond to angles (theta) /// think about it is that the dot-products correspond to correspond to angles (theta)
@ -38,6 +35,13 @@ public:
/// description of how this works. /// description of how this works.
void setSwingLimits(std::vector<float> minDots); void setSwingLimits(std::vector<float> minDots);
/// \param swungDirections vector of directions that lie on the swing limit boundary
/// \brief For each swungDirection we compute the corresponding [theta, minDot] pair.
/// We expect the values of theta to NOT be uniformly spaced around the range [0, TWO_PI]
/// so we'll use the input set to extrapolate a lookup function of evenly spaced values.
void setSwingLimits(const std::vector<glm::vec3>& swungDirections);
/// \param minTwist the minimum angle of rotation about the twist axis /// \param minTwist the minimum angle of rotation about the twist axis
/// \param maxTwist the maximum angle of rotation about the twist axis /// \param maxTwist the maximum angle of rotation about the twist axis
void setTwistLimits(float minTwist, float maxTwist); void setTwistLimits(float minTwist, float maxTwist);
@ -71,7 +75,6 @@ public:
protected: protected:
SwingLimitFunction _swingLimitFunction; SwingLimitFunction _swingLimitFunction;
glm::quat _referenceRotation;
float _minTwist; float _minTwist;
float _maxTwist; float _maxTwist;
}; };

View file

@ -12,17 +12,17 @@
#ifndef hifi_ResourceCache_h #ifndef hifi_ResourceCache_h
#define hifi_ResourceCache_h #define hifi_ResourceCache_h
#include <QHash> #include <QtCore/QHash>
#include <QList> #include <QtCore/QList>
#include <QNetworkReply> #include <QtCore/QObject>
#include <QNetworkRequest> #include <QtCore/QPointer>
#include <QObject> #include <QtCore/QSharedPointer>
#include <QPointer> #include <QtCore/QUrl>
#include <QSharedPointer> #include <QtCore/QWeakPointer>
#include <QUrl> #include <QtCore/QReadWriteLock>
#include <QWeakPointer> #include <QtCore/QQueue>
#include <QReadWriteLock> #include <QtNetwork/QNetworkReply>
#include <QQueue> #include <QtNetwork/QNetworkRequest>
#include <DependencyManager.h> #include <DependencyManager.h>

View file

@ -216,7 +216,10 @@ void OctreeHeadlessViewer::queryOctree() {
// setup the query packet // setup the query packet
auto queryPacket = NLPacket::create(packetType); auto queryPacket = NLPacket::create(packetType);
_octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
// read the data to our packet and set the payload size to fit the query
int querySize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
queryPacket->setPayloadSize(querySize);
// ask the NodeList to send it // ask the NodeList to send it
nodeList->sendPacket(std::move(queryPacket), *node); nodeList->sendPacket(std::move(queryPacket), *node);

View file

@ -162,7 +162,7 @@ void Procedural::prepare(gpu::Batch& batch, const glm::vec3& size) {
if (replaceIndex != std::string::npos) { if (replaceIndex != std::string::npos) {
fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data()); fragmentShaderSource.replace(replaceIndex, PROCEDURAL_BLOCK.size(), _shaderSource.toLocal8Bit().data());
} }
qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str(); //qDebug() << "FragmentShader:\n" << fragmentShaderSource.c_str();
_fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSource)); _fragmentShader = gpu::ShaderPointer(gpu::Shader::createPixel(fragmentShaderSource));
_shader = gpu::ShaderPointer(gpu::Shader::createProgram(_vertexShader, _fragmentShader)); _shader = gpu::ShaderPointer(gpu::Shader::createProgram(_vertexShader, _fragmentShader));
gpu::Shader::makeProgram(*_shader); gpu::Shader::makeProgram(*_shader);

View file

@ -0,0 +1,269 @@
//
// AnimInverseKinematicsTests.cpp
//
// 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 "AnimInverseKinematicsTests.h"
#include <glm/gtx/transform.hpp>
#include <AnimNodeLoader.h>
#include <AnimInverseKinematics.h>
#include <AnimBlendLinear.h>
#include <AnimationLogging.h>
#include <NumericalConstants.h>
#include "../QTestExtensions.h"
QTEST_MAIN(AnimInverseKinematicsTests)
const glm::vec3 origin(0.0f);
const glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
const glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
const glm::vec3 zAxis(0.0f, 0.0f, 1.0f);
const glm::quat identity = glm::quat();
const glm::quat quaterTurnAroundZ = glm::angleAxis(0.5f * PI, zAxis);
void makeTestFBXJoints(std::vector<FBXJoint>& fbxJoints) {
FBXJoint joint;
joint.isFree = false;
joint.freeLineage.clear();
joint.parentIndex = -1;
joint.distanceToParent = 1.0f;
joint.boneRadius = 1.0f;
joint.translation = origin; // T
joint.preTransform = glm::mat4(); // Roff * Rp
joint.preRotation = identity; // Rpre
joint.rotation = identity; // R
joint.postRotation = identity; // Rpost
joint.postTransform = glm::mat4(); // Rp-1 * Soff * Sp * S * Sp-1
// World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1)
joint.transform = glm::mat4();
joint.rotationMin = glm::vec3(-PI);
joint.rotationMax = glm::vec3(PI);
joint.inverseDefaultRotation = identity;
joint.inverseBindRotation = identity;
joint.bindTransform = glm::mat4();
joint.name = "";
joint.isSkeletonJoint = false;
// we make a list of joints that look like this:
//
// A------>B------>C------>D
joint.name = "A";
joint.parentIndex = -1;
joint.translation = origin;
fbxJoints.push_back(joint);
joint.name = "B";
joint.parentIndex = 0;
joint.translation = xAxis;
fbxJoints.push_back(joint);
joint.name = "C";
joint.parentIndex = 1;
joint.translation = xAxis;
fbxJoints.push_back(joint);
joint.name = "D";
joint.parentIndex = 2;
joint.translation = xAxis;
fbxJoints.push_back(joint);
// compute each joint's transform
for (int i = 1; i < (int)fbxJoints.size(); ++i) {
FBXJoint& j = fbxJoints[i];
int parentIndex = j.parentIndex;
// World = ParentWorld * T * (Roff * Rp) * Rpre * R * Rpost * (Rp-1 * Soff * Sp * S * Sp-1)
j.transform = fbxJoints[parentIndex].transform *
glm::translate(j.translation) *
j.preTransform *
glm::mat4_cast(j.preRotation * j.rotation * j.postRotation) *
j.postTransform;
j.inverseBindRotation = identity;
j.bindTransform = j.transform;
}
}
void AnimInverseKinematicsTests::testSingleChain() {
std::vector<FBXJoint> fbxJoints;
AnimPose offset;
makeTestFBXJoints(fbxJoints, offset);
// create a skeleton and doll
AnimSkeleton* skeleton = new AnimSkeleton(fbxJoints);
AnimSkeleton::Pointer skeletonPtr(skeleton);
AnimInverseKinematics ikDoll("doll");
ikDoll.setSkeleton(skeletonPtr);
{ // easy test IK of joint C
// load intial poses that look like this:
//
// A------>B------>C------>D
AnimPose pose;
pose.scale = glm::vec3(1.0f);
pose.rot = identity;
pose.trans = origin;
std::vector<AnimPose> poses;
poses.push_back(pose);
pose.trans = xAxis;
for (int i = 1; i < (int)fbxJoints.size(); ++i) {
poses.push_back(pose);
}
ikDoll.loadPoses(poses);
// we'll put a target t on D for <2, 1, 0> like this:
//
// t
//
//
// A------>B------>C------>D
//
int indexD = 3;
glm::vec3 targetPosition(2.0f, 1.0f, 0.0f);
glm::quat targetRotation = glm::angleAxis(PI / 2.0f, zAxis);
ikDoll.updateTarget(indexD, targetPosition, targetRotation);
// the IK solution should be:
//
// D
// |
// |
// A------>B------>C
//
float dt = 1.0f;
ikDoll.evaluate(dt);
// verify absolute results
// NOTE: since we expect this solution to converge very quickly (one loop)
// we can impose very tight error thresholds.
std::vector<AnimPose> absolutePoses;
ikDoll.computeAbsolutePoses(absolutePoses);
float acceptableAngle = 0.0001f;
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[2].rot, quaterTurnAroundZ, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[3].rot, quaterTurnAroundZ, acceptableAngle);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, targetPosition, EPSILON);
// verify relative results
const std::vector<AnimPose>& relativePoses = ikDoll.getRelativePoses();
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[2].rot, quaterTurnAroundZ, acceptableAngle);
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle);
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, EPSILON);
}
{ // hard test IK of joint C
// load intial poses that look like this:
//
// D<------C
// |
// |
// A------>B
//
AnimPose pose;
pose.scale = glm::vec3(1.0f);
pose.rot = identity;
pose.trans = origin;
std::vector<AnimPose> poses;
poses.push_back(pose);
pose.trans = xAxis;
pose.rot = quaterTurnAroundZ;
poses.push_back(pose);
poses.push_back(pose);
poses.push_back(pose);
ikDoll.loadPoses(poses);
// we'll put a target t on D for <3, 0, 0> like this:
//
// D<------C
// |
// |
// A------>B t
//
int indexD = 3;
glm::vec3 targetPosition(3.0f, 0.0f, 0.0f);
glm::quat targetRotation = identity;
ikDoll.updateTarget(indexD, targetPosition, targetRotation);
// the IK solution should be:
//
// A------>B------>C------>D
//
float dt = 1.0f;
ikDoll.evaluate(dt);
// verify absolute results
// NOTE: the IK algorithm doesn't converge very fast for full-reach targets,
// so we'll consider the poses as good if they are closer than they started.
//
// NOTE: constraints may help speed up convergence since some joints may get clamped
// to maximum extension. TODO: experiment with tightening the error thresholds when
// constraints are working.
std::vector<AnimPose> absolutePoses;
ikDoll.computeAbsolutePoses(absolutePoses);
float acceptableAngle = 0.1f; // radians
QCOMPARE_QUATS(absolutePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[2].rot, identity, acceptableAngle);
QCOMPARE_QUATS(absolutePoses[3].rot, identity, acceptableAngle);
float acceptableDistance = 0.4f;
QCOMPARE_WITH_ABS_ERROR(absolutePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[1].trans, xAxis, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[2].trans, 2.0f * xAxis, acceptableDistance);
QCOMPARE_WITH_ABS_ERROR(absolutePoses[3].trans, 3.0f * xAxis, acceptableDistance);
// verify relative results
const std::vector<AnimPose>& relativePoses = ikDoll.getRelativePoses();
QCOMPARE_QUATS(relativePoses[0].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[1].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[2].rot, identity, acceptableAngle);
QCOMPARE_QUATS(relativePoses[3].rot, identity, acceptableAngle);
QCOMPARE_WITH_ABS_ERROR(relativePoses[0].trans, origin, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[1].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[2].trans, xAxis, EPSILON);
QCOMPARE_WITH_ABS_ERROR(relativePoses[3].trans, xAxis, EPSILON);
}
}
void AnimInverseKinematicsTests::testBar() {
// test AnimPose math
// TODO: move this to other test file
glm::vec3 transA = glm::vec3(1.0f, 2.0f, 3.0f);
glm::vec3 transB = glm::vec3(5.0f, 7.0f, 9.0f);
glm::quat rot = identity;
glm::vec3 scale = glm::vec3(1.0f);
AnimPose poseA(scale, rot, transA);
AnimPose poseB(scale, rot, transB);
AnimPose poseC = poseA * poseB;
glm::vec3 expectedTransC = transA + transB;
QCOMPARE_WITH_ABS_ERROR(expectedTransC, poseC.trans, EPSILON);
}

View file

@ -0,0 +1,27 @@
//
// AnimInverseKinematicsTests.h
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AnimInverseKinematicsTests_h
#define hifi_AnimInverseKinematicsTests_h
#include <QtTest/QtTest>
#include <glm/glm.hpp>
inline float getErrorDifference(float a, float b) {
return fabs(a - b);
}
class AnimInverseKinematicsTests : public QObject {
Q_OBJECT
private slots:
void testSingleChain();
void testBar();
};
#endif // hifi_AnimInverseKinematicsTests_h

View file

@ -1,190 +1,240 @@
{ {
"version": "1.0", "version": "1.0",
"root": { "root": {
"id": "root", "id": "ikOverlay",
"type": "stateMachine", "type": "overlay",
"data": { "data": {
"currentState": "idle", "alpha": 1.0,
"states": [ "boneSet": "fullBody"
{
"id": "idle",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "walkFwd",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "walkBwd",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" }
]
}
]
}, },
"children": [ "children": [
{ {
"id": "idle", "id": "ik",
"type": "clip", "type": "inverseKinematics",
"data": { "data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx", "targets": [
"startFrame": 0.0, {
"endFrame": 90.0, "jointName": "RightHand",
"timeScale": 1.0, "positionVar": "rightHandPosition",
"loopFlag": true "rotationVar": "rightHandRotation"
},
{
"jointName": "LeftHand",
"positionVar": "leftHandPosition",
"rotationVar": "leftHandRotation"
}
]
}, },
"children": [] "children": []
}, },
{ {
"id": "walkFwd", "id": "controllerOverlay",
"type": "clip", "type": "overlay",
"data": { "data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_fwd.fbx", "alpha": 1.0,
"startFrame": 0.0, "boneSet": "spineOnly"
"endFrame": 35.0,
"timeScale": 1.0,
"loopFlag": true,
"timeScaleVar": "walkTimeScale"
}, },
"children": [] "children": [
}, {
{ "id": "spineLean",
"id": "walkBwd", "type": "controller",
"type": "clip", "data": {
"data": { "alpha": 1.0,
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx", "joints": [
"startFrame": 0.0, { "var": "lean", "jointName": "Spine" }
"endFrame": 37.0, ]
"timeScale": 1.0, },
"loopFlag": true, "children": []
"timeScaleVar": "walkTimeScale" },
}, {
"children": [] "id": "mainStateMachine",
}, "type": "stateMachine",
{ "data": {
"id": "turnLeft", "currentState": "idle",
"type": "clip", "states": [
"data": { {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx", "id": "idle",
"startFrame": 0.0, "interpTarget": 6,
"endFrame": 28.0, "interpDuration": 6,
"timeScale": 1.0, "transitions": [
"loopFlag": true { "var": "isMovingForward", "state": "walkFwd" },
}, { "var": "isMovingBackward", "state": "walkBwd" },
"children": [] { "var": "isMovingRight", "state": "strafeRight" },
}, { "var": "isMovingLeft", "state": "strafeLeft" },
{ { "var": "isTurningRight", "state": "turnRight" },
"id": "turnRight", { "var": "isTurningLeft", "state": "turnLeft" }
"type": "clip", ]
"data": { },
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx", {
"startFrame": 0.0, "id": "walkFwd",
"endFrame": 30.0, "interpTarget": 6,
"timeScale": 1.0, "interpDuration": 6,
"loopFlag": true "transitions": [
}, { "var": "isNotMoving", "state": "idle" },
"children": [] { "var": "isMovingBackward", "state": "walkBwd" },
}, { "var": "isMovingRight", "state": "strafeRight" },
{ { "var": "isMovingLeft", "state": "strafeLeft" },
"id": "strafeLeft", { "var": "isTurningRight", "state": "turnRight" },
"type": "clip", { "var": "isTurningLeft", "state": "turnLeft" }
"data": { ]
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx", },
"startFrame": 0.0, {
"endFrame": 31.0, "id": "walkBwd",
"timeScale": 1.0, "interpTarget": 6,
"loopFlag": true "interpDuration": 6,
}, "transitions": [
"children": [] { "var": "isNotMoving", "state": "idle" },
}, { "var": "isMovingForward", "state": "walkFwd" },
{ { "var": "isMovingRight", "state": "strafeRight" },
"id": "strafeRight", { "var": "isMovingLeft", "state": "strafeLeft" },
"type": "clip", { "var": "isTurningRight", "state": "turnRight" },
"data": { { "var": "isTurningLeft", "state": "turnLeft" }
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx", ]
"startFrame": 0.0, },
"endFrame": 31.0, {
"timeScale": 1.0, "id": "strafeRight",
"loopFlag": true "interpTarget": 6,
}, "interpDuration": 6,
"children": [] "transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "strafeLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotMoving", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isTurningRight", "state": "turnRight" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnRight",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningLeft", "state": "turnLeft" }
]
},
{
"id": "turnLeft",
"interpTarget": 6,
"interpDuration": 6,
"transitions": [
{ "var": "isNotTurning", "state": "idle" },
{ "var": "isMovingForward", "state": "walkFwd" },
{ "var": "isMovingBackward", "state": "walkBwd" },
{ "var": "isMovingRight", "state": "strafeRight" },
{ "var": "isMovingLeft", "state": "strafeLeft" },
{ "var": "isTurningRight", "state": "turnRight" }
]
}
]
},
"children": [
{
"id": "idle",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/idle.fbx",
"startFrame": 0.0,
"endFrame": 90.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "walkFwd",
"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,
"timeScaleVar": "walkTimeScale"
},
"children": []
},
{
"id": "walkBwd",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/walk_bwd.fbx",
"startFrame": 0.0,
"endFrame": 37.0,
"timeScale": 1.0,
"loopFlag": true,
"timeScaleVar": "walkTimeScale"
},
"children": []
},
{
"id": "turnLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_left.fbx",
"startFrame": 0.0,
"endFrame": 28.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "turnRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/turn_right.fbx",
"startFrame": 0.0,
"endFrame": 30.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
},
{
"id": "strafeRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
"loopFlag": true
},
"children": []
}
]
}
]
} }
] ]
} }

View file

@ -84,8 +84,8 @@ void testByteCountCodedStable(const T& value) {
Q_ASSERT(decoder.data == coder.data); Q_ASSERT(decoder.data == coder.data);
#ifndef QT_NO_DEBUG #ifndef QT_NO_DEBUG
auto consumed = decoder.decode(encoded.data(), encoded.size()); auto consumed = decoder.decode(encoded.data(), encoded.size());
#endif
Q_ASSERT(consumed == (unsigned int)originalEncodedSize); Q_ASSERT(consumed == (unsigned int)originalEncodedSize);
#endif
} }
@ -124,9 +124,9 @@ void testPropertyFlags(uint32_t value) {
{ {
#ifndef QT_NO_DEBUG #ifndef QT_NO_DEBUG
int decodeSize = decodeNew.decode((const uint8_t*)encoded.data(), encoded.size()); int decodeSize = decodeNew.decode((const uint8_t*)encoded.data(), encoded.size());
#endif
Q_ASSERT(originalSize == decodeSize); Q_ASSERT(originalSize == decodeSize);
Q_ASSERT(decodeNew == original); Q_ASSERT(decodeNew == original);
#endif
} }
} }