Merge remote-tracking branch 'highfidelity/master'

This commit is contained in:
James B. Pollack 2015-09-26 11:10:45 -07:00
commit a49c4756dd
13 changed files with 298 additions and 84 deletions

View file

@ -0,0 +1,93 @@
//
// collectHifiStats.js
//
// Created by Thijs Wenker on 24 Sept 2015
// Additions by James B. Pollack @imgntn on 25 Sept 2015
// Copyright 2015 High Fidelity, Inc.
//
// Collects stats for analysis to a REST endpoint. Defaults to batching stats, but can be customized.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// The url where the data will be posted.
var ENDPOINT_URL = "";
var BATCH_STATS = true;
var BATCH_SIZE = 5;
var batch = [];
if (BATCH_STATS) {
var RECORD_EVERY = 1000; // 1 seconds
var batchCount = 0;
Script.setInterval(function() {
if (batchCount === BATCH_SIZE) {
sendBatchToEndpoint(batch);
batchCount = 0;
}
Stats.forceUpdateStats();
batch.push(getBatchStats());
batchCount++;
}, RECORD_EVERY);
} else {
// Send the data every:
var SEND_EVERY = 30000; // 30 seconds
Script.setInterval(function() {
Stats.forceUpdateStats();
var req = new XMLHttpRequest();
req.open("POST", ENDPOINT_URL, false);
req.send(getStats());
}, SEND_EVERY);
}
function getStats() {
return JSON.stringify({
username: GlobalServices.username,
location: Window.location.hostname,
framerate: Stats.framerate,
simrate: Stats.simrate,
ping: {
audio: Stats.audioPing,
avatar: Stats.avatarPing,
entities: Stats.entitiesPing,
asset: Stats.assetPing
},
position: Camera.position,
yaw: Stats.yaw,
rotation: Camera.orientation.x + ',' + Camera.orientation.y + ',' + Camera.orientation.z + ',' + Camera.orientation.w
})
}
function getBatchStats() {
// print('GET BATCH STATS');
return {
username: GlobalServices.username,
location: Window.location.hostname,
framerate: Stats.framerate,
simrate: Stats.simrate,
ping: {
audio: Stats.audioPing,
avatar: Stats.avatarPing,
entities: Stats.entitiesPing,
asset: Stats.assetPing
},
position: Camera.position,
yaw: Stats.yaw,
rotation: Camera.orientation.x + ',' + Camera.orientation.y + ',' + Camera.orientation.z + ',' + Camera.orientation.w
}
}
function sendBatchToEndpoint(batch) {
// print('SEND BATCH TO ENDPOINT');
var req = new XMLHttpRequest();
req.open("POST", ENDPOINT_URL, false);
req.send(JSON.stringify(batch));
}

View file

@ -0,0 +1,67 @@
//
// statsExample.js
// examples/example/misc
//
// Created by Thijs Wenker on 24 Sept 2015
// Copyright 2015 High Fidelity, Inc.
//
// Prints the stats to the console.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// The stats to be displayed
var stats = [
'serverCount',
'framerate', // a.k.a. FPS
'simrate',
'avatarSimrate',
'avatarCount',
'packetInCount',
'packetOutCount',
'mbpsIn',
'mbpsOut',
'audioPing',
'avatarPing',
'entitiesPing',
'assetPing',
'velocity',
'yaw',
'avatarMixerKbps',
'avatarMixerPps',
'audioMixerKbps',
'audioMixerPps',
'downloads',
'downloadsPending',
'triangles',
'quads',
'materialSwitches',
'meshOpaque',
'meshTranslucent',
'opaqueConsidered',
'opaqueOutOfView',
'opaqueTooSmall',
'translucentConsidered',
'translucentOutOfView',
'translucentTooSmall',
'sendingMode',
'packetStats',
'lodStatus',
'timingStats',
'serverElements',
'serverInternal',
'serverLeaves',
'localElements',
'localInternal',
'localLeaves'
];
// Force update the stats, in case the stats panel is invisible
Stats.forceUpdateStats();
// Loop through the stats and display them
for (var i in stats) {
var stat = stats[i];
print(stat + " = " + Stats[stat]);
}

View file

@ -560,7 +560,7 @@
"id": "strafeLeft",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_left.fbx",
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_left.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,
@ -572,7 +572,7 @@
"id": "strafeRight",
"type": "clip",
"data": {
"url": "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/strafe_right.fbx",
"url": "http://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/side_step_right.fbx",
"startFrame": 0.0,
"endFrame": 31.0,
"timeScale": 1.0,

View file

@ -708,7 +708,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// Now that menu is initalized we can sync myAvatar with it's state.
_myAvatar->updateMotionBehaviorFromMenu();
_myAvatar->updateStandingHMDModeFromMenu();
// the 3Dconnexion device wants to be initiliazed after a window is displayed.
ConnexionClient::getInstance().init();
@ -1055,8 +1054,6 @@ void Application::paintGL() {
auto displayPlugin = getActiveDisplayPlugin();
displayPlugin->preRender();
_offscreenContext->makeCurrent();
// update the avatar with a fresh HMD pose
_myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose());
auto lodManager = DependencyManager::get<LODManager>();
@ -2898,6 +2895,9 @@ void Application::update(float deltaTime) {
userInputMapper->getActionState(UserInputMapper::SHIFT), RIGHT_HAND_INDEX);
}
// update the avatar with a fresh HMD pose
_myAvatar->updateFromHMDSensorMatrix(getHMDSensorPose(), deltaTime);
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
updateCamera(deltaTime); // handle various camera tweaks like off axis projection
@ -4075,6 +4075,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Stats", Stats::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());

View file

@ -294,9 +294,6 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::StandingHMDSensorMode, 0, false,
avatar, SLOT(updateStandingHMDModeFromMenu()));
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::WorldAxes);
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats);

View file

@ -124,14 +124,12 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
_isEyeTrackerConnected = eyeTracker->isTracking();
}
if (!myAvatar->getStandingHMDSensorMode()) {
// Twist the upper body to follow the rotation of the head, but only do this with my avatar,
// since everyone else will see the full joint rotations for other people.
const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f;
const float BODY_FOLLOW_HEAD_FACTOR = 0.66f;
float currentTwist = getTorsoTwist();
setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE);
}
// Twist the upper body to follow the rotation of the head, but only do this with my avatar,
// since everyone else will see the full joint rotations for other people.
const float BODY_FOLLOW_HEAD_YAW_RATE = 0.1f;
const float BODY_FOLLOW_HEAD_FACTOR = 0.66f;
float currentTwist = getTorsoTwist();
setTorsoTwist(currentTwist + (getFinalYaw() * BODY_FOLLOW_HEAD_FACTOR - currentTwist) * BODY_FOLLOW_HEAD_YAW_RATE);
}
if (!(_isFaceTrackerConnected || billboard)) {
@ -392,7 +390,7 @@ glm::quat Head::getCameraOrientation() const {
// always the same.
if (qApp->getAvatarUpdater()->isHMDMode()) {
MyAvatar* myAvatar = dynamic_cast<MyAvatar*>(_owningAvatar);
if (myAvatar && myAvatar->getStandingHMDSensorMode()) {
if (myAvatar) {
return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
} else {
return getOrientation();

View file

@ -104,7 +104,6 @@ MyAvatar::MyAvatar(RigPointer rig) :
_hmdSensorPosition(),
_bodySensorMatrix(),
_sensorToWorldMatrix(),
_standingHMDSensorMode(false),
_goToPending(false),
_goToPosition(),
_goToOrientation(),
@ -268,29 +267,59 @@ void MyAvatar::simulate(float deltaTime) {
}
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
if (getStandingHMDSensorMode()) {
return _sensorToWorldMatrix;
} else {
return createMatFromQuatAndPos(getWorldAlignedOrientation(), getDefaultEyePosition());
}
return _sensorToWorldMatrix;
}
// best called at start of main loop just after we have a fresh hmd pose.
// update internal body position from new hmd pose.
void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float deltaTime) {
// update the sensorMatrices based on the new hmd pose
_hmdSensorMatrix = hmdSensorMatrix;
_hmdSensorPosition = extractTranslation(hmdSensorMatrix);
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
_bodySensorMatrix = deriveBodyFromHMDSensor();
if (getStandingHMDSensorMode()) {
// set the body position/orientation to reflect motion due to the head.
auto worldMat = _sensorToWorldMatrix * _bodySensorMatrix;
nextAttitude(extractTranslation(worldMat), glm::quat_cast(worldMat));
const float STRAIGHTING_LEAN_DURATION = 0.5f; // seconds
const float STRAIGHTING_LEAN_THRESHOLD = 0.2f; // meters
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix);
if (!_straightingLean && glm::length(diff) > STRAIGHTING_LEAN_THRESHOLD) {
// begin homing toward derived body position.
_straightingLean = true;
_straightingLeanAlpha = 0.0f;
} else if (_straightingLean) {
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
auto worldBodyMatrix = _sensorToWorldMatrix * newBodySensorMatrix;
glm::vec3 worldBodyPos = extractTranslation(worldBodyMatrix);
glm::quat worldBodyRot = glm::normalize(glm::quat_cast(worldBodyMatrix));
_straightingLeanAlpha += (1.0f / STRAIGHTING_LEAN_DURATION) * deltaTime;
if (_straightingLeanAlpha >= 1.0f) {
_straightingLean = false;
nextAttitude(worldBodyPos, worldBodyRot);
_bodySensorMatrix = newBodySensorMatrix;
} else {
// interp position toward the desired pos
glm::vec3 pos = lerp(getPosition(), worldBodyPos, _straightingLeanAlpha);
glm::quat rot = glm::normalize(safeMix(getOrientation(), worldBodyRot, _straightingLeanAlpha));
nextAttitude(pos, rot);
// interp sensor matrix toward desired
glm::vec3 nextBodyPos = extractTranslation(newBodySensorMatrix);
glm::quat nextBodyRot = glm::normalize(glm::quat_cast(newBodySensorMatrix));
glm::vec3 prevBodyPos = extractTranslation(_bodySensorMatrix);
glm::quat prevBodyRot = glm::normalize(glm::quat_cast(_bodySensorMatrix));
pos = lerp(prevBodyPos, nextBodyPos, _straightingLeanAlpha);
rot = glm::normalize(safeMix(prevBodyRot, nextBodyRot, _straightingLeanAlpha));
_bodySensorMatrix = createMatFromQuatAndPos(rot, pos);
}
}
}
//
// best called at end of main loop, just before rendering.
// update sensor to world matrix from current body position and hmd sensor.
// This is so the correct camera can be used for rendering.
@ -359,11 +388,9 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
Head* head = getHead();
if (inHmd || isPlaying()) {
if (!getStandingHMDSensorMode()) {
head->setDeltaPitch(estimatedRotation.x);
head->setDeltaYaw(estimatedRotation.y);
head->setDeltaRoll(estimatedRotation.z);
}
head->setDeltaPitch(estimatedRotation.x);
head->setDeltaYaw(estimatedRotation.y);
head->setDeltaRoll(estimatedRotation.z);
} else {
float magnifyFieldOfView = qApp->getFieldOfView() /
_realWorldFieldOfView.get();
@ -385,12 +412,10 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
relativePosition.x = -relativePosition.x;
}
if (!(inHmd && getStandingHMDSensorMode())) {
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
}
head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)),
-MAX_LEAN, MAX_LEAN));
}
@ -1744,11 +1769,6 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
_characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
}
void MyAvatar::updateStandingHMDModeFromMenu() {
Menu* menu = Menu::getInstance();
_standingHMDSensorMode = menu->isOptionChecked(MenuOption::StandingHMDSensorMode);
}
//Renders sixense laser pointers for UI selection with controllers
void MyAvatar::renderLaserPointers(gpu::Batch& batch) {
const float PALM_TIP_ROD_RADIUS = 0.002f;

View file

@ -68,7 +68,7 @@ public:
// best called at start of main loop just after we have a fresh hmd pose.
// update internal body position from new hmd pose.
void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix);
void updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix, float deltaTime);
// best called at end of main loop, just before rendering.
// update sensor to world matrix from current body position and hmd sensor.
@ -168,7 +168,6 @@ public:
static const float ZOOM_MAX;
static const float ZOOM_DEFAULT;
bool getStandingHMDSensorMode() const { return _standingHMDSensorMode; }
void doUpdateBillboard();
void destroyAnimGraph();
@ -194,7 +193,6 @@ public slots:
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void updateMotionBehaviorFromMenu();
void updateStandingHMDModeFromMenu();
glm::vec3 getLeftPalmPosition();
glm::vec3 getLeftPalmVelocity();
@ -345,8 +343,6 @@ private:
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix;
bool _standingHMDSensorMode;
bool _goToPending;
glm::vec3 _goToPosition;
glm::quat _goToOrientation;
@ -362,6 +358,9 @@ private:
AudioListenerMode _audioListenerMode;
glm::vec3 _customListenPosition;
glm::quat _customListenOrientation;
bool _straightingLean = false;
float _straightingLeanAlpha = 0.0f;
};
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -123,7 +123,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
Rig::HeadParameters headParams;
headParams.modelRotation = getRotation();
headParams.modelTranslation = getTranslation();
headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode() && !myAvatar->getStandingHMDSensorMode();
headParams.enableLean = qApp->getAvatarUpdater()->isHMDMode();
headParams.leanSideways = head->getFinalLeanSideways();
headParams.leanForward = head->getFinalLeanForward();
headParams.torsoTwist = head->getTorsoTwist();

View file

@ -68,12 +68,7 @@ void OverlayConductor::updateMode() {
Mode newMode;
if (qApp->isHMDMode()) {
MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
if (myAvatar->getStandingHMDSensorMode()) {
newMode = STANDING;
} else {
newMode = SITTING;
}
newMode = SITTING;
} else {
newMode = FLAT;
}

View file

@ -89,14 +89,14 @@ bool Stats::includeTimingRecord(const QString& name) {
}
void Stats::updateStats() {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
if (isVisible()) {
setVisible(false);
}
return;
} else {
if (!isVisible()) {
void Stats::updateStats(bool force) {
if (!force) {
if (!Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
if (isVisible()) {
setVisible(false);
}
return;
} else if (!isVisible()) {
setVisible(true);
}
}
@ -161,7 +161,7 @@ void Stats::updateStats() {
STAT_UPDATE(position, QVector3D(avatarPos.x, avatarPos.y, avatarPos.z));
STAT_UPDATE_FLOAT(velocity, glm::length(myAvatar->getVelocity()), 0.1f);
STAT_UPDATE_FLOAT(yaw, myAvatar->getBodyYaw(), 0.1f);
if (_expanded) {
if (_expanded || force) {
SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer);
if (avatarMixer) {
STAT_UPDATE(avatarMixerKbps, roundf(
@ -175,7 +175,7 @@ void Stats::updateStats() {
STAT_UPDATE(avatarMixerPps, -1);
}
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixerNode) {
if (audioMixerNode || force) {
STAT_UPDATE(audioMixerKbps, roundf(
bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) +
bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer)));
@ -230,7 +230,7 @@ void Stats::updateStats() {
totalLeaves += stats.getTotalLeaves();
}
}
if (_expanded) {
if (_expanded || force) {
if (serverCount == 0) {
sendingModeStream << "---";
}
@ -272,7 +272,7 @@ void Stats::updateStats() {
STAT_UPDATE(serverElements, (int)totalNodes);
STAT_UPDATE(localElements, (int)OctreeElement::getNodeCount());
if (_expanded) {
if (_expanded || force) {
STAT_UPDATE(serverInternal, (int)totalInternal);
STAT_UPDATE(serverLeaves, (int)totalLeaves);
// Local Voxels

View file

@ -81,7 +81,7 @@ public:
const QString& monospaceFont() {
return _monospaceFont;
}
void updateStats();
void updateStats(bool force = false);
bool isExpanded() { return _expanded; }
bool isTimingExpanded() { return _timingExpanded; }
@ -93,6 +93,9 @@ public:
}
}
public slots:
void forceUpdateStats() { updateStats(true); }
signals:
void expandedChanged();
void timingExpandedChanged();
@ -149,3 +152,4 @@ private:
};
#endif // hifi_Stats_h

View file

@ -9,6 +9,7 @@
#include "AnimInverseKinematics.h"
#include <GeometryUtil.h>
#include <GLMHelpers.h>
#include <NumericalConstants.h>
#include <SharedUtil.h>
@ -159,24 +160,34 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
continue;
}
AnimPose targetPose = target.pose;
glm::vec3 tip = absolutePoses[tipIndex].trans;
// cache tip absolute transform
glm::vec3 tipPosition = absolutePoses[tipIndex].trans;
glm::quat tipRotation = absolutePoses[tipIndex].rot;
// cache tip's parent's absolute rotation so we can recompute the tip's parent-relative
// as we proceed walking down the joint chain
int pivotIndex = _skeleton->getParentIndex(tipIndex);
glm::quat tipParentRotation;
if (pivotIndex != -1) {
tipParentRotation = absolutePoses[pivotIndex].rot;
}
// descend toward root, pivoting each joint to get tip closer to target
int pivotIndex = _skeleton->getParentIndex(tipIndex);
float fractionDenominator = 1.0f;
int ancestorCount = 1;
while (pivotIndex != -1) {
// compute the two lines that should be aligned
glm::vec3 jointPosition = absolutePoses[pivotIndex].trans;
glm::vec3 leverArm = tip - jointPosition;
glm::vec3 leverArm = tipPosition - jointPosition;
glm::vec3 targetLine = targetPose.trans - jointPosition;
// compute the axis of the rotation that would align them
// compute the swing that would get get tip closer
glm::vec3 axis = glm::cross(leverArm, targetLine);
float axisLength = glm::length(axis);
glm::quat deltaRotation;
const float MIN_AXIS_LENGTH = 1.0e-4f;
if (axisLength > MIN_AXIS_LENGTH) {
// compute deltaRotation for alignment (brings tip closer to target)
// compute deltaRotation for alignment (swings tip closer to target)
axis /= axisLength;
float angle = acosf(glm::dot(leverArm, targetLine) / (glm::length(leverArm) * glm::length(targetLine)));
@ -184,24 +195,50 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
// still possible for the angle to be zero so we also check that to avoid unnecessary calculations.
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
if (angle > MIN_ADJUSTMENT_ANGLE) {
// reduce angle by half: slows convergence but adds stability to IK solution
angle /= fractionDenominator;
// reduce angle by a fraction (reduces IK swing contribution of this joint)
angle /= (float)ancestorCount;
deltaRotation = glm::angleAxis(angle, axis);
}
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
// new rotation and its target. We compute that delta here and rotate the tipJoint accordingly.
glm::quat tipRelativeRotation = glm::inverse(deltaRotation * tipParentRotation) * targetPose.rot;
// enforce tip's constraint
RotationConstraint* constraint = getConstraint(tipIndex);
if (constraint) {
bool constrained = constraint->apply(tipRelativeRotation);
if (constrained) {
// The tip's final parent-relative rotation violates its constraint
// so we try to twist this pivot to compensate.
glm::quat constrainedTipRotation = deltaRotation * tipParentRotation * tipRelativeRotation;
glm::quat missingRotation = targetPose.rot * glm::inverse(constrainedTipRotation);
glm::quat swingPart;
glm::quat twistPart;
glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
deltaRotation = twistPart * deltaRotation;
}
// we update the tip rotation here to rotate it as close to its target orientation as possible
// before moving on to next pivot
tipRotation = tipParentRotation * tipRelativeRotation;
}
}
fractionDenominator++;
++ancestorCount;
int parentIndex = _skeleton->getParentIndex(pivotIndex);
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
// compute joint's new parent-relative rotation after swing
// Q' = dQ * Q and Q = Qp * q --> q' = Qp^ * dQ * Q
glm::quat newRot = glm::normalize(glm::inverse(
absolutePoses[parentIndex].rot) *
deltaRotation *
absolutePoses[pivotIndex].rot);
// enforce pivot's constraint
RotationConstraint* constraint = getConstraint(pivotIndex);
if (constraint) {
bool constrained = constraint->apply(newRot);
@ -214,6 +251,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
glm::inverse(absolutePoses[pivotIndex].rot);
}
}
// store the rotation change in the accumulator
_accumulators[pivotIndex].add(newRot);
}
@ -222,8 +260,10 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
lowestMovedIndex = pivotIndex;
}
// keep track of tip's new position as we descend towards root
tip = jointPosition + deltaRotation * leverArm;
// keep track of tip's new transform as we descend towards root
tipPosition = jointPosition + deltaRotation * leverArm;
tipRotation = glm::normalize(deltaRotation * tipRotation);
tipParentRotation = glm::normalize(deltaRotation * tipParentRotation);
pivotIndex = _skeleton->getParentIndex(pivotIndex);
}
@ -464,7 +504,7 @@ void AnimInverseKinematics::initConstraints() {
} else if (0 == baseName.compare("Hand", Qt::CaseInsensitive)) {
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot);
const float MAX_HAND_TWIST = PI;
const float MAX_HAND_TWIST = 3.0f * PI / 5.0f;
const float MIN_HAND_TWIST = -PI / 2.0f;
if (isLeft) {
stConstraint->setTwistLimits(-MAX_HAND_TWIST, -MIN_HAND_TWIST);