Merge pull request #5839 from sethalves/transmit-joint-translation

WARNING protocol change: Transmit joint translation
This commit is contained in:
Howard Stearns 2015-10-02 13:37:56 -07:00
commit f6665a4ac6
32 changed files with 610 additions and 93 deletions

View file

@ -77,7 +77,19 @@ void ScriptableAvatar::update(float deltatime) {
int mapping = animationJoints.indexOf(modelJoints[i]);
if (mapping != -1 && !_maskedJoints.contains(modelJoints[i])) {
JointData& data = _jointData[i];
data.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
auto newRotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
auto newTranslation = floorFrame.translations.at(i) * (1.0f - frameFraction) +
ceilFrame.translations.at(i) * frameFraction;
if (data.rotation != newRotation) {
data.rotation = newRotation;
data.rotationSet = true;
}
if (data.translation != newTranslation) {
data.translation = newTranslation;
data.translationSet = true;
}
}
}
} else {

View file

@ -202,11 +202,14 @@ void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("skeleton");
for (int i = 0; i < _jointData.size(); i++) {
const JointData& data = _jointData.at(i);
_skeletonModel.setJointState(i, true, data.rotation);
_skeletonModel.setJointRotation(i, data.rotationSet, data.rotation, 1.0f);
_skeletonModel.setJointTranslation(i, data.translationSet, data.translation, 1.0f);
}
_skeletonModel.simulate(deltaTime, _hasNewJointRotations);
_skeletonModel.simulate(deltaTime, _hasNewJointRotations || _hasNewJointTranslations);
simulateAttachments(deltaTime);
_hasNewJointRotations = false;
_hasNewJointTranslations = false;
}
{
PerformanceTimer perfTimer("head");
@ -879,6 +882,16 @@ glm::quat Avatar::getJointRotation(int index) const {
return rotation;
}
glm::vec3 Avatar::getJointTranslation(int index) const {
if (QThread::currentThread() != thread()) {
return AvatarData::getJointTranslation(index);
}
glm::vec3 translation;
_skeletonModel.getJointTranslation(index, translation);
return translation;
}
int Avatar::getJointIndex(const QString& name) const {
if (QThread::currentThread() != thread()) {
int result;

View file

@ -115,6 +115,7 @@ public:
virtual QVector<glm::quat> getJointRotations() const;
virtual glm::quat getJointRotation(int index) const;
virtual glm::vec3 getJointTranslation(int index) const;
virtual int getJointIndex(const QString& name) const;
virtual QStringList getJointNames() const;

View file

@ -240,9 +240,11 @@ void MyAvatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("joints");
// copy out the skeleton joints from the model
_jointData.resize(_rig->getJointStateCount());
for (int i = 0; i < _jointData.size(); i++) {
JointData& data = _jointData[i];
_rig->getJointStateRotation(i, data.rotation);
data.rotationSet |= _rig->getJointStateRotation(i, data.rotation);
data.translationSet |= _rig->getJointStateTranslation(i, data.translation);
}
}
@ -1115,21 +1117,43 @@ void MyAvatar::setJointRotations(QVector<glm::quat> jointRotations) {
int numStates = glm::min(_skeletonModel.getJointStateCount(), jointRotations.size());
for (int i = 0; i < numStates; ++i) {
// HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here
_skeletonModel.setJointState(i, true, jointRotations[i], RECORDER_PRIORITY);
_skeletonModel.setJointRotation(i, true, jointRotations[i], RECORDER_PRIORITY);
}
}
void MyAvatar::setJointData(int index, const glm::quat& rotation) {
void MyAvatar::setJointTranslations(QVector<glm::vec3> jointTranslations) {
int numStates = glm::min(_skeletonModel.getJointStateCount(), jointTranslations.size());
for (int i = 0; i < numStates; ++i) {
// HACK: ATM only Recorder calls setJointTranslations() so we hardcode its priority here
_skeletonModel.setJointTranslation(i, true, jointTranslations[i], RECORDER_PRIORITY);
}
}
void MyAvatar::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_rig->setJointState(index, true, rotation, SCRIPT_PRIORITY);
_rig->setJointState(index, true, rotation, translation, SCRIPT_PRIORITY);
}
}
void MyAvatar::setJointRotation(int index, const glm::quat& rotation) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_rig->setJointRotation(index, true, rotation, SCRIPT_PRIORITY);
}
}
void MyAvatar::setJointTranslation(int index, const glm::vec3& translation) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority
_rig->setJointTranslation(index, true, translation, SCRIPT_PRIORITY);
}
}
void MyAvatar::clearJointData(int index) {
if (QThread::currentThread() == thread()) {
// HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority
_rig->setJointState(index, false, glm::quat(), 0.0f);
_rig->setJointState(index, false, glm::quat(), glm::vec3(), 0.0f);
_rig->clearJointAnimationPriority(index);
}
}
@ -1428,7 +1452,10 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
AnimPose pose = _debugDrawSkeleton->getRelativeBindPose(i);
glm::quat jointRot;
_rig->getJointRotationInConstrainedFrame(i, jointRot);
glm::vec3 jointTrans;
_rig->getJointTranslation(i, jointTrans);
pose.rot = pose.rot * jointRot;
pose.trans = jointTrans;
poses.push_back(pose);
}

View file

@ -140,7 +140,10 @@ public:
void clearLookAtTargetAvatar();
virtual void setJointRotations(QVector<glm::quat> jointRotations);
virtual void setJointData(int index, const glm::quat& rotation);
virtual void setJointTranslations(QVector<glm::vec3> jointTranslations);
virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation);
virtual void setJointRotation(int index, const glm::quat& rotation);
virtual void setJointTranslation(int index, const glm::vec3& translation);
virtual void clearJointData(int index);
virtual void clearJointsData();

View file

@ -246,6 +246,12 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
Hand* hand = _owningAvatar->getHand();
hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
// let rig compute the model offset
glm::vec3 modelOffset;
if (_rig->getModelOffset(modelOffset)) {
setOffset(modelOffset);
}
// Don't Relax toward hand positions when in animGraph mode.
if (!_rig->getEnableAnimGraph()) {
const float HAND_RESTORATION_RATE = 0.25f;
@ -651,6 +657,7 @@ void SkeletonModel::computeBoundingShape() {
// RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose
for (int i = 0; i < numStates; i++) {
_rig->restoreJointRotation(i, 1.0f, 1.0f);
_rig->restoreJointTranslation(i, 1.0f, 1.0f);
}
}

View file

@ -135,23 +135,46 @@ void AnimClip::copyFromNetworkAnim() {
const int frameCount = geom.animationFrames.size();
const int skeletonJointCount = _skeleton->getNumJoints();
_anim.resize(frameCount);
for (int i = 0; i < frameCount; i++) {
const glm::vec3 offsetScale = extractScale(geom.offset);
for (int frame = 0; frame < frameCount; frame++) {
// init all joints in animation to bind pose
_anim[i].reserve(skeletonJointCount);
for (int j = 0; j < skeletonJointCount; j++) {
_anim[i].push_back(_skeleton->getRelativeBindPose(j));
// this will give us a resonable result for bones in the skeleton but not in the animation.
_anim[frame].reserve(skeletonJointCount);
for (int skeletonJoint = 0; skeletonJoint < skeletonJointCount; skeletonJoint++) {
_anim[frame].push_back(_skeleton->getRelativeBindPose(skeletonJoint));
}
// init over all joint animations
for (int j = 0; j < animJointCount; j++) {
int k = jointMap[j];
if (k >= 0 && k < skeletonJointCount) {
// currently FBX animations only have rotation.
_anim[i][k].rot = _skeleton->getRelativeBindPose(k).rot * geom.animationFrames[i].rotations[j];
for (int animJoint = 0; animJoint < animJointCount; animJoint++) {
int skeletonJoint = jointMap[animJoint];
// skip joints that are in the animation but not in the skeleton.
if (skeletonJoint >= 0 && skeletonJoint < skeletonJointCount) {
const glm::vec3& fbxZeroTrans = geom.animationFrames[0].translations[animJoint] * offsetScale;
const AnimPose& relBindPose = _skeleton->getRelativeBindPose(skeletonJoint);
// used to adjust translation offsets, so large translation animatons on the reference skeleton
// will be adjusted when played on a skeleton with short limbs.
float limbLengthScale = fabs(glm::length(fbxZeroTrans)) <= 0.0001f ? 1.0f : (glm::length(relBindPose.trans) / glm::length(fbxZeroTrans));
AnimPose& pose = _anim[frame][skeletonJoint];
const FBXAnimationFrame& fbxAnimFrame = geom.animationFrames[frame];
// rotation in fbxAnimationFrame is a delta from a reference skeleton bind pose.
pose.rot = relBindPose.rot * fbxAnimFrame.rotations[animJoint];
// translation in fbxAnimationFrame is not a delta.
// convert it into a delta by subtracting from the first frame.
const glm::vec3& fbxTrans = fbxAnimFrame.translations[animJoint] * offsetScale;
pose.trans = relBindPose.trans + limbLengthScale * (fbxTrans - fbxZeroTrans);
}
}
}
_poses.resize(skeletonJointCount);
}

View file

@ -348,6 +348,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
} else {
_relativePoses[i].rot = underPoses[i].rot;
}
_relativePoses[i].trans = underPoses[i].trans;
}
}

View file

@ -173,6 +173,7 @@ void AnimationHandle::applyFrame(float frameIndex) {
const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount);
const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount);
float frameFraction = glm::fract(frameIndex);
for (int i = 0; i < _jointMappings.size(); i++) {
int mapping = _jointMappings.at(i);
if (mapping != -1) { // allow missing bones
@ -182,6 +183,15 @@ void AnimationHandle::applyFrame(float frameIndex) {
frameFraction),
_priority,
_mix);
// This isn't working.
// glm::vec3 floorTranslationPart = floorFrame.translations.at(i) * (1.0f - frameFraction);
// glm::vec3 ceilTranslationPart = ceilFrame.translations.at(i) * frameFraction;
// glm::vec3 floorCeilFraction = floorTranslationPart + ceilTranslationPart;
// glm::vec3 defaultTrans = _rig->getJointDefaultTranslationInConstrainedFrame(i);
// glm::vec3 mixedTranslation = floorCeilFraction * (1.0f - _mix) + defaultTrans * _mix;
// _rig->setJointTranslation(mapping, true, mixedTranslation, _priority);
}
}
}
@ -202,6 +212,7 @@ void AnimationHandle::restoreJoints() {
int mapping = _jointMappings.at(i);
if (mapping != -1) {
_rig->restoreJointRotation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping));
_rig->restoreJointTranslation(mapping, 1.0f, _rig->getJointAnimatinoPriority(mapping));
}
}
}

View file

@ -20,9 +20,7 @@ void AvatarRig::updateJointState(int index, glm::mat4 rootTransform) {
// compute model transforms
if (index == _rootJointIndex) {
// we always zero-out the translation part of an avatar's root join-transform.
state.computeTransform(rootTransform);
clearJointTransformTranslation(index);
} else {
// guard against out-of-bounds access to _jointStates
int parentIndex = state.getParentIndex();
@ -97,3 +95,28 @@ void AvatarRig::setHandPosition(int jointIndex,
shoulderRotation, priority);
setJointRotationInBindFrame(jointIndex, rotation, priority);
}
void AvatarRig::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
if (valid) {
state.setTranslation(translation, priority);
} else {
state.restoreTranslation(1.0f, priority);
}
}
}
void AvatarRig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
if (valid) {
state.setRotationInConstrainedFrame(rotation, priority);
state.setTranslation(translation, priority);
} else {
state.restoreRotation(1.0f, priority);
state.restoreTranslation(1.0f, priority);
}
}
}

View file

@ -24,6 +24,10 @@ class AvatarRig : public Rig {
virtual void updateJointState(int index, glm::mat4 rootTransform);
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
float scale, float priority);
virtual void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority);
};
#endif // hifi_AvatarRig_h

View file

@ -27,3 +27,17 @@ void EntityRig::updateJointState(int index, glm::mat4 rootTransform) {
}
}
}
void EntityRig::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
if (valid) {
state.setRotationInConstrainedFrame(rotation, priority);
// state.setTranslation(translation, priority);
} else {
state.restoreRotation(1.0f, priority);
// state.restoreTranslation(1.0f, priority);
}
}
}

View file

@ -24,6 +24,7 @@ class EntityRig : public Rig {
virtual void updateJointState(int index, glm::mat4 rootTransform);
virtual void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation,
float scale, float priority) {}
virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority);
};
#endif // hifi_EntityRig_h

View file

@ -34,6 +34,7 @@ void JointState::copyState(const JointState& other) {
_isFree = other._isFree;
_parentIndex = other._parentIndex;
_defaultRotation = other._defaultRotation;
_defaultTranslation = other._defaultTranslation;
_inverseDefaultRotation = other._inverseDefaultRotation;
_translation = other._translation;
_preRotation = other._preRotation;
@ -49,6 +50,7 @@ JointState::JointState(const FBXJoint& joint) {
_parentIndex = joint.parentIndex;
_translation = joint.translation;
_defaultRotation = joint.rotation;
_defaultTranslation = _translation;
_inverseDefaultRotation = joint.inverseDefaultRotation;
_preRotation = joint.preRotation;
_postRotation = joint.postRotation;
@ -70,6 +72,9 @@ glm::quat JointState::getRotation() const {
}
void JointState::initTransform(const glm::mat4& parentTransform) {
_unitsScale = extractScale(parentTransform);
computeTransform(parentTransform);
_positionInParentFrame = glm::inverse(extractRotation(parentTransform)) * (extractTranslation(_transform) - extractTranslation(parentTransform));
_distanceToParent = glm::length(_positionInParentFrame);
@ -79,11 +84,11 @@ void JointState::computeTransform(const glm::mat4& parentTransform, bool parentT
if (!parentTransformChanged && !_transformChanged) {
return;
}
glm::quat rotationInParentFrame = _preRotation * _rotationInConstrainedFrame * _postRotation;
glm::mat4 transformInParentFrame = _preTransform * glm::mat4_cast(rotationInParentFrame) * _postTransform;
glm::mat4 newTransform = parentTransform * glm::translate(_translation) * transformInParentFrame;
if (newTransform != _transform) {
_transform = newTransform;
_transformChanged = true;
@ -106,6 +111,13 @@ void JointState::restoreRotation(float fraction, float priority) {
}
}
void JointState::restoreTranslation(float fraction, float priority) {
if (priority == _animationPriority || _animationPriority == 0.0f) {
_translation = _translation * (1.0f - fraction) + _defaultTranslation * fraction;
_animationPriority = 0.0f;
}
}
void JointState::setRotationInBindFrame(const glm::quat& rotation, float priority) {
// rotation is from bind- to model-frame
if (priority >= _animationPriority) {
@ -176,6 +188,14 @@ void JointState::setRotationInConstrainedFrame(glm::quat targetRotation, float p
}
}
void JointState::setTranslation(const glm::vec3& translation, float priority) {
if (priority >= _animationPriority || _animationPriority == 0.0f) {
_translation = translation / _unitsScale;
_transformChanged = true;
_animationPriority = priority;
}
}
void JointState::setRotationInConstrainedFrameInternal(const glm::quat& targetRotation) {
if (_rotationInConstrainedFrame != targetRotation) {
glm::quat parentRotation = computeParentRotation();
@ -194,11 +214,15 @@ bool JointState::rotationIsDefault(const glm::quat& rotation, float tolerance) c
glm::abs(rotation.w - defaultRotation.w) < tolerance;
}
bool JointState::translationIsDefault(const glm::vec3& translation, float tolerance) const {
return glm::distance(_defaultTranslation * _unitsScale, translation * _unitsScale) < tolerance;
}
glm::quat JointState::getDefaultRotationInParentFrame() const {
// NOTE: the result is constant and could be cached
return _preRotation * _defaultRotation * _postRotation;
}
const glm::vec3& JointState::getDefaultTranslationInConstrainedFrame() const {
return _translation;
glm::vec3 JointState::getDefaultTranslationInConstrainedFrame() const {
return _defaultTranslation * _unitsScale;
}

View file

@ -68,6 +68,8 @@ public:
/// \param priority priority level of this animation blend
void restoreRotation(float fraction, float priority);
void restoreTranslation(float fraction, float priority);
/// \param rotation is from bind- to model-frame
/// computes and sets new _rotationInConstrainedFrame
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
@ -78,13 +80,17 @@ public:
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
void setRotationInModelFrame(const glm::quat& rotationInModelFrame, float priority);
void setTranslation(const glm::vec3& translation, float priority);
void setRotationInConstrainedFrame(glm::quat targetRotation, float priority, float mix = 1.0f);
const glm::quat& getRotationInConstrainedFrame() const { return _rotationInConstrainedFrame; }
bool rotationIsDefault(const glm::quat& rotation, float tolerance = EPSILON) const;
bool translationIsDefault(const glm::vec3& translation, float tolerance = EPSILON) const;
glm::quat getDefaultRotationInParentFrame() const;
const glm::vec3& getDefaultTranslationInConstrainedFrame() const;
glm::vec3 getDefaultTranslationInConstrainedFrame() const;
void clearTransformTranslation();
@ -95,12 +101,13 @@ public:
void setTransform(const glm::mat4& transform) { _transform = transform; }
const glm::vec3& getTranslation() const { return _translation; }
glm::vec3 getTranslation() const { return _translation * _unitsScale; }
const glm::mat4& getPreTransform() const { return _preTransform; }
const glm::mat4& getPostTransform() const { return _postTransform; }
const glm::quat& getPreRotation() const { return _preRotation; }
const glm::quat& getPostRotation() const { return _postRotation; }
const glm::quat& getDefaultRotation() const { return _defaultRotation; }
glm::vec3 getDefaultTranslation() const { return _defaultTranslation * _unitsScale; }
const glm::quat& getInverseDefaultRotation() const { return _inverseDefaultRotation; }
const QString& getName() const { return _name; }
bool getIsFree() const { return _isFree; }
@ -124,6 +131,7 @@ private:
glm::quat _defaultRotation; // Not necessarilly bind rotation. See FBXJoint transform/bindTransform
glm::quat _inverseDefaultRotation;
glm::vec3 _defaultTranslation;
glm::vec3 _translation;
QString _name;
int _parentIndex;
@ -133,6 +141,8 @@ private:
glm::mat4 _preTransform;
glm::mat4 _postTransform;
glm::quat _inverseBindRotation;
glm::vec3 _unitsScale{1.0f, 1.0f, 1.0f};
};
#endif // hifi_JointState_h

View file

@ -266,6 +266,7 @@ void Rig::reset(const QVector<FBXJoint>& fbxJoints) {
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].setRotationInConstrainedFrame(fbxJoints.at(i).rotation, 0.0f);
_jointStates[i].setTranslation(fbxJoints.at(i).translation, 0.0f);
}
}
@ -285,10 +286,20 @@ bool Rig::getJointStateRotation(int index, glm::quat& rotation) const {
return !state.rotationIsDefault(rotation);
}
bool Rig::getJointStateTranslation(int index, glm::vec3& translation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
}
const JointState& state = _jointStates.at(index);
translation = state.getTranslation();
return !state.translationIsDefault(translation);
}
void Rig::clearJointState(int index) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
state.setRotationInConstrainedFrame(glm::quat(), 0.0f);
state.setTranslation(state.getDefaultTranslationInConstrainedFrame(), 0.0f);
}
}
@ -315,7 +326,7 @@ void Rig::setJointAnimatinoPriority(int index, float newPriority) {
}
}
void Rig::setJointState(int index, bool valid, const glm::quat& rotation, float priority) {
void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) {
if (index != -1 && index < _jointStates.size()) {
JointState& state = _jointStates[index];
if (valid) {
@ -332,6 +343,12 @@ void Rig::restoreJointRotation(int index, float fraction, float priority) {
}
}
void Rig::restoreJointTranslation(int index, float fraction, float priority) {
if (index != -1 && index < _jointStates.size()) {
_jointStates[index].restoreTranslation(fraction, priority);
}
}
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
@ -367,6 +384,14 @@ bool Rig::getJointRotation(int jointIndex, glm::quat& rotation) const {
return true;
}
bool Rig::getJointTranslation(int jointIndex, glm::vec3& translation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
}
translation = _jointStates[jointIndex].getTranslation();
return true;
}
bool Rig::getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const {
if (jointIndex == -1 || jointIndex >= _jointStates.size()) {
return false;
@ -554,6 +579,7 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
const float PRIORITY = 1.0f;
for (size_t i = 0; i < poses.size(); i++) {
setJointRotationInConstrainedFrame((int)i, glm::inverse(_animSkeleton->getRelativeBindPose(i).rot) * poses[i].rot, PRIORITY, 1.0f);
setJointTranslation((int)i, true, poses[i].trans, PRIORITY);
}
} else {
@ -827,6 +853,7 @@ bool Rig::restoreJointPosition(int jointIndex, float fraction, float priority, c
foreach (int index, freeLineage) {
JointState& state = _jointStates[index];
state.restoreRotation(fraction, priority);
state.restoreTranslation(fraction, priority);
}
return true;
}
@ -1175,3 +1202,12 @@ void Rig::initAnimGraph(const QUrl& url, const FBXGeometry& fbxGeometry) {
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
});
}
bool Rig::getModelOffset(glm::vec3& modelOffsetOut) const {
if (_animSkeleton && _rootJointIndex >= 0) {
modelOffsetOut = -_animSkeleton->getAbsoluteBindPose(_rootJointIndex).trans;
return true;
} else {
return false;
}
}

View file

@ -123,12 +123,13 @@ public:
int rightShoulderJointIndex);
bool jointStatesEmpty() { return _jointStates.isEmpty(); };
int getJointStateCount() const { return _jointStates.size(); }
int indexOfJoint(const QString& jointName) ;
int indexOfJoint(const QString& jointName);
void initJointTransforms(glm::mat4 rootTransform);
void clearJointTransformTranslation(int jointIndex);
void reset(const QVector<FBXJoint>& fbxJoints);
bool getJointStateRotation(int index, glm::quat& rotation) const;
bool getJointStateTranslation(int index, glm::vec3& translation) const;
void applyJointRotationDelta(int jointIndex, const glm::quat& delta, float priority);
JointState getJointState(int jointIndex) const; // XXX
bool getVisibleJointState(int index, glm::quat& rotation) const;
@ -137,14 +138,21 @@ public:
void clearJointAnimationPriority(int index);
float getJointAnimatinoPriority(int index);
void setJointAnimatinoPriority(int index, float newPriority);
void setJointState(int index, bool valid, const glm::quat& rotation, float priority);
virtual void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation,
float priority) = 0;
virtual void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) {}
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
void restoreJointRotation(int index, float fraction, float priority);
void restoreJointTranslation(int index, float fraction, float priority);
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const;
bool getJointPosition(int jointIndex, glm::vec3& position) const;
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& result, const glm::quat& rotation) const;
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
bool getJointTranslation(int jointIndex, glm::vec3& translation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& result, const glm::quat& rotation) const;
bool getVisibleJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const;
@ -196,6 +204,8 @@ public:
AnimSkeleton::ConstPointer getAnimSkeleton() const { return _animSkeleton; }
bool disableHands {false}; // should go away with rig animation (and Rig::inverseKinematics)
bool getModelOffset(glm::vec3& modelOffsetOut) const;
protected:
void updateLeanJoint(int index, float leanSideways, float leanForward, float torsoTwist);

View file

@ -49,6 +49,7 @@ AvatarData::AvatarData() :
_keyState(NO_KEY_DOWN),
_forceFaceTrackerConnected(false),
_hasNewJointRotations(true),
_hasNewJointTranslations(true),
_headData(NULL),
_handData(NULL),
_faceModelURL("http://invalid.com"),
@ -278,12 +279,16 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
// pupil dilation
destinationBuffer += packFloatToByte(destinationBuffer, _headData->_pupilDilation, 1.0f);
// joint data
// joint rotation data
*destinationBuffer++ = _jointData.size();
unsigned char* validityPosition = destinationBuffer;
unsigned char validity = 0;
int validityBit = 0;
#ifdef WANT_DEBUG
int rotationSentCount = 0;
#endif
_lastSentJointData.resize(_jointData.size());
for (int i=0; i < _jointData.size(); i++) {
@ -292,7 +297,12 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
if (sendAll ||
!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
validity |= (1 << validityBit);
if (data.rotationSet) {
validity |= (1 << validityBit);
#ifdef WANT_DEBUG
rotationSentCount++;
#endif
}
}
}
if (++validityBit == BITS_IN_BYTE) {
@ -317,6 +327,73 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) {
}
}
// joint translation data
validityPosition = destinationBuffer;
validity = 0;
validityBit = 0;
#ifdef WANT_DEBUG
int translationSentCount = 0;
#endif
float maxTranslationDimension = 0.0;
for (int i=0; i < _jointData.size(); i++) {
const JointData& data = _jointData.at(i);
if (sendAll || _lastSentJointData[i].translation != data.translation) {
if (sendAll ||
!cullSmallChanges ||
glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) {
if (data.translationSet) {
validity |= (1 << validityBit);
#ifdef WANT_DEBUG
translationSentCount++;
#endif
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
}
}
}
if (++validityBit == BITS_IN_BYTE) {
*destinationBuffer++ = validity;
validityBit = validity = 0;
}
}
if (validityBit != 0) {
*destinationBuffer++ = validity;
}
// TODO -- automatically pick translationCompressionRadix
int translationCompressionRadix = 12;
*destinationBuffer++ = translationCompressionRadix;
validityBit = 0;
validity = *validityPosition++;
for (int i = 0; i < _jointData.size(); i ++) {
const JointData& data = _jointData[ i ];
if (validity & (1 << validityBit)) {
destinationBuffer +=
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, translationCompressionRadix);
}
if (++validityBit == BITS_IN_BYTE) {
validityBit = 0;
validity = *validityPosition++;
}
}
#ifdef WANT_DEBUG
if (sendAll) {
qDebug() << "SENDING -- rotations:" << rotationSentCount << "translations:" << translationSentCount
<< "largest:" << maxTranslationDimension
<< "radix:" << translationCompressionRadix
<< "size:" << (int)(destinationBuffer - startPosition);
}
#endif
return avatarDataByteArray.left(destinationBuffer - startPosition);
}
@ -328,7 +405,16 @@ void AvatarData::doneEncoding(bool cullSmallChanges) {
if (_lastSentJointData[i].rotation != data.rotation) {
if (!cullSmallChanges ||
fabsf(glm::dot(data.rotation, _lastSentJointData[i].rotation)) <= AVATAR_MIN_ROTATION_DOT) {
_lastSentJointData[i].rotation = data.rotation;
if (data.rotationSet) {
_lastSentJointData[i].rotation = data.rotation;
}
}
if (!cullSmallChanges ||
glm::distance(data.translation, _lastSentJointData[i].translation) > AVATAR_MIN_TRANSLATION) {
if (data.translationSet) {
_lastSentJointData[i].translation = data.translation;
}
}
}
}
@ -374,7 +460,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
// + 1 byte for numJoints (0)
// = 39 bytes
int minPossibleSize = 39;
int maxAvailableSize = buffer.size();
if (minPossibleSize > maxAvailableSize) {
if (shouldLogError(now)) {
@ -556,7 +642,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f);
} // 1 byte
// joint data
// joint rotations
int numJoints = *sourceBuffer++;
int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE);
minPossibleSize += bytesOfValidity;
@ -569,13 +655,13 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
return maxAvailableSize;
}
int numValidJoints = 0;
int numValidJointRotations = 0;
_jointData.resize(numJoints);
QVector<bool> valids;
valids.resize(numJoints);
QVector<bool> validRotations;
validRotations.resize(numJoints);
{ // validity bits
{ // rotation validity bits
unsigned char validity = 0;
int validityBit = 0;
for (int i = 0; i < numJoints; i++) {
@ -584,20 +670,19 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
}
bool valid = (bool)(validity & (1 << validityBit));
if (valid) {
++numValidJoints;
++numValidJointRotations;
}
valids[i] = valid;
validRotations[i] = valid;
validityBit = (validityBit + 1) % BITS_IN_BYTE;
}
}
// 1 + bytesOfValidity bytes
} // 1 + bytesOfValidity bytes
// each joint rotation component is stored in two bytes (sizeof(uint16_t))
int COMPONENTS_PER_QUATERNION = 4;
minPossibleSize += numValidJoints * COMPONENTS_PER_QUATERNION * sizeof(uint16_t);
minPossibleSize += numValidJointRotations * COMPONENTS_PER_QUATERNION * sizeof(uint16_t);
if (minPossibleSize > maxAvailableSize) {
if (shouldLogError(now)) {
qCDebug(avatars) << "Malformed AvatarData packet after JointData;"
qCDebug(avatars) << "Malformed AvatarData packet after JointData rotation validity;"
<< " displayName = '" << _displayName << "'"
<< " minPossibleSize = " << minPossibleSize
<< " maxAvailableSize = " << maxAvailableSize;
@ -608,13 +693,71 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
{ // joint data
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
if (valids[i]) {
if (validRotations[i]) {
_hasNewJointRotations = true;
data.rotationSet = true;
sourceBuffer += unpackOrientationQuatFromBytes(sourceBuffer, data.rotation);
}
}
} // numJoints * 8 bytes
// joint translations
// get translation validity bits -- these indicate which translations were packed
int numValidJointTranslations = 0;
QVector<bool> validTranslations;
validTranslations.resize(numJoints);
{ // translation validity bits
unsigned char validity = 0;
int validityBit = 0;
for (int i = 0; i < numJoints; i++) {
if (validityBit == 0) {
validity = *sourceBuffer++;
}
bool valid = (bool)(validity & (1 << validityBit));
if (valid) {
++numValidJointTranslations;
}
validTranslations[i] = valid;
validityBit = (validityBit + 1) % BITS_IN_BYTE;
}
} // 1 + bytesOfValidity bytes
// each joint translation component is stored in 6 bytes. 1 byte for translationCompressionRadix
minPossibleSize += numValidJointTranslations * 6 + 1;
if (minPossibleSize > maxAvailableSize) {
if (shouldLogError(now)) {
qCDebug(avatars) << "Malformed AvatarData packet after JointData translation validity;"
<< " displayName = '" << _displayName << "'"
<< " minPossibleSize = " << minPossibleSize
<< " maxAvailableSize = " << maxAvailableSize;
}
return maxAvailableSize;
}
int translationCompressionRadix = *sourceBuffer++;
{ // joint data
for (int i = 0; i < numJoints; i++) {
JointData& data = _jointData[i];
if (validTranslations[i]) {
sourceBuffer +=
unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, translationCompressionRadix);
_hasNewJointTranslations = true;
data.translationSet = true;
}
}
} // numJoints * 12 bytes
#ifdef WANT_DEBUG
if (numValidJointRotations > 15) {
qDebug() << "RECEIVING -- rotations:" << numValidJointRotations
<< "translations:" << numValidJointTranslations
<< "radix:" << translationCompressionRadix
<< "size:" << (int)(sourceBuffer - startPosition);
}
#endif
int numBytesRead = sourceBuffer - startPosition;
_averageBytesReceived.updateAverage(numBytesRead);
return numBytesRead;
@ -800,7 +943,7 @@ void AvatarData::changeReferential(Referential* ref) {
_referential = ref;
}
void AvatarData::setJointData(int index, const glm::quat& rotation) {
void AvatarData::setJointData(int index, const glm::quat& rotation, const glm::vec3& translation) {
if (index == -1) {
return;
}
@ -813,6 +956,7 @@ void AvatarData::setJointData(int index, const glm::quat& rotation) {
}
JointData& data = _jointData[index];
data.rotation = rotation;
data.translation = translation;
}
void AvatarData::clearJointData(int index) {
@ -854,13 +998,67 @@ glm::quat AvatarData::getJointRotation(int index) const {
return index < _jointData.size() ? _jointData.at(index).rotation : glm::quat();
}
void AvatarData::setJointData(const QString& name, const glm::quat& rotation) {
glm::vec3 AvatarData::getJointTranslation(int index) const {
if (index == -1) {
return glm::vec3();
}
if (QThread::currentThread() != thread()) {
glm::vec3 result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getJointTranslation", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(glm::vec3, result), Q_ARG(int, index));
return result;
}
return index < _jointData.size() ? _jointData.at(index).translation : glm::vec3();
}
glm::vec3 AvatarData::getJointTranslation(const QString& name) const {
if (QThread::currentThread() != thread()) {
glm::vec3 result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this), "getJointTranslation", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(glm::vec3, result), Q_ARG(const QString&, name));
return result;
}
return getJointTranslation(getJointIndex(name));
}
void AvatarData::setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointData", Q_ARG(const QString&, name),
Q_ARG(const glm::quat&, rotation));
return;
}
setJointData(getJointIndex(name), rotation);
setJointData(getJointIndex(name), rotation, translation);
}
void AvatarData::setJointRotation(int index, const glm::quat& rotation) {
if (index == -1) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointRotation", Q_ARG(int, index), Q_ARG(const glm::quat&, rotation));
return;
}
if (_jointData.size() <= index) {
_jointData.resize(index + 1);
}
JointData& data = _jointData[index];
data.rotation = rotation;
}
void AvatarData::setJointTranslation(int index, const glm::vec3& translation) {
if (index == -1) {
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setJointTranslation", Q_ARG(int, index), Q_ARG(const glm::vec3&, translation));
return;
}
if (_jointData.size() <= index) {
_jointData.resize(index + 1);
}
JointData& data = _jointData[index];
data.translation = translation;
}
void AvatarData::clearJointData(const QString& name) {
@ -918,7 +1116,25 @@ void AvatarData::setJointRotations(QVector<glm::quat> jointRotations) {
}
for (int i = 0; i < jointRotations.size(); ++i) {
if (i < _jointData.size()) {
setJointData(i, jointRotations[i]);
setJointRotation(i, jointRotations[i]);
}
}
}
void AvatarData::setJointTranslations(QVector<glm::vec3> jointTranslations) {
if (QThread::currentThread() != thread()) {
QVector<glm::quat> result;
QMetaObject::invokeMethod(const_cast<AvatarData*>(this),
"setJointTranslations", Qt::BlockingQueuedConnection,
Q_ARG(QVector<glm::vec3>, jointTranslations));
}
if (_jointData.size() < jointTranslations.size()) {
_jointData.resize(jointTranslations.size());
}
for (int i = 0; i < jointTranslations.size(); ++i) {
if (i < _jointData.size()) {
setJointTranslation(i, jointTranslations[i]);
}
}
}

View file

@ -117,6 +117,7 @@ const QString DEFAULT_FULL_AVATAR_MODEL_NAME = QString("Default");
const float AVATAR_SEND_FULL_UPDATE_RATIO = 0.02f;
// this controls how large a change in joint-rotation must be before the interface sends it to the avatar mixer
const float AVATAR_MIN_ROTATION_DOT = 0.9999999f;
const float AVATAR_MIN_TRANSLATION = 0.0001f;
// Where one's own Avatar begins in the world (will be overwritten if avatar data file is found).
@ -240,20 +241,24 @@ public:
Q_INVOKABLE char getHandState() const { return _handState; }
const QVector<JointData>& getJointData() const { return _jointData; }
void setJointData(const QVector<JointData>& jointData) { _jointData = jointData; }
Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation);
Q_INVOKABLE virtual void setJointData(int index, const glm::quat& rotation, const glm::vec3& translation);
Q_INVOKABLE virtual void setJointRotation(int index, const glm::quat& rotation);
Q_INVOKABLE virtual void setJointTranslation(int index, const glm::vec3& translation);
Q_INVOKABLE virtual void clearJointData(int index);
Q_INVOKABLE bool isJointDataValid(int index) const;
Q_INVOKABLE virtual glm::quat getJointRotation(int index) const;
Q_INVOKABLE virtual glm::vec3 getJointTranslation(int index) const;
Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation);
Q_INVOKABLE void setJointData(const QString& name, const glm::quat& rotation, const glm::vec3& translation);
Q_INVOKABLE void clearJointData(const QString& name);
Q_INVOKABLE bool isJointDataValid(const QString& name) const;
Q_INVOKABLE glm::quat getJointRotation(const QString& name) const;
Q_INVOKABLE glm::vec3 getJointTranslation(const QString& name) const;
Q_INVOKABLE virtual QVector<glm::quat> getJointRotations() const;
Q_INVOKABLE virtual void setJointRotations(QVector<glm::quat> jointRotations);
Q_INVOKABLE virtual void setJointTranslations(QVector<glm::vec3> jointTranslations);
Q_INVOKABLE virtual void clearJointsData();
@ -387,6 +392,7 @@ protected:
bool _forceFaceTrackerConnected;
bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar
bool _hasNewJointTranslations; // set in AvatarData, cleared in Avatar
HeadData* _headData;
HandData* _handData;
@ -435,6 +441,9 @@ Q_DECLARE_METATYPE(AvatarData*)
class JointData {
public:
glm::quat rotation;
bool rotationSet = false;
glm::vec3 translation;
bool translationSet = false;
};
class AttachmentData {

View file

@ -254,8 +254,17 @@ void Player::play() {
nextFrame.getJointRotations()[i],
_frameInterpolationFactor);
}
QVector<glm::vec3> jointTranslations(currentFrame.getJointTranslations().size());
for (int i = 0; i < currentFrame.getJointTranslations().size(); ++i) {
jointTranslations[i] =
currentFrame.getJointTranslations()[i] * (1.0f - _frameInterpolationFactor) +
nextFrame.getJointTranslations()[i] * _frameInterpolationFactor;
}
_avatar->setJointRotations(jointRotations);
_avatar->setJointTranslations(jointTranslations);
HeadData* head = const_cast<HeadData*>(_avatar->getHeadData());
if (head) {
// Make sure fake face tracker connection doesn't get turned off

View file

@ -239,6 +239,7 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename) {
if (i == 0 ||
frame._jointRotations[j] != previousFrame._jointRotations[j]) {
writeQuat(stream, frame._jointRotations[j]);
// TODO -- handle translations
mask.setBit(maskIndex);
}
maskIndex++;
@ -561,7 +562,9 @@ RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString
frame._jointRotations[j] = previousFrame._jointRotations[j];
}
}
// TODO -- handle translations
if (!mask[maskIndex++] || !readVec3(stream, frame._translation)) {
frame._translation = previousFrame._translation;
}
@ -670,7 +673,9 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr
for (int i = 0; i < jointRotationSize; ++i) {
fileStream >> baseFrame._jointRotations[i].x >> baseFrame._jointRotations[i].y >> baseFrame._jointRotations[i].z >> baseFrame._jointRotations[i].w;
}
// TODO -- handle translations
fileStream >> baseFrame._translation.x >> baseFrame._translation.y >> baseFrame._translation.z;
fileStream >> baseFrame._rotation.x >> baseFrame._rotation.y >> baseFrame._rotation.z >> baseFrame._rotation.w;
fileStream >> baseFrame._scale;
@ -736,7 +741,9 @@ RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QStr
frame._jointRotations[i] = previousFrame._jointRotations[i];
}
}
// TODO -- handle translations
if (mask[maskIndex++]) {
stream >> frame._translation.x >> frame._translation.y >> frame._translation.z;
frame._translation = context.orientationInv * frame._translation;

View file

@ -83,6 +83,7 @@ class RecordingFrame {
public:
QVector<float> getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
QVector<glm::quat> getJointRotations() const { return _jointRotations; }
QVector<glm::vec3> getJointTranslations() const { return _jointTranslations; }
glm::vec3 getTranslation() const { return _translation; }
glm::quat getRotation() const { return _rotation; }
float getScale() const { return _scale; }
@ -94,6 +95,7 @@ public:
protected:
void setBlendshapeCoefficients(QVector<float> blendshapeCoefficients);
void setJointRotations(QVector<glm::quat> jointRotations) { _jointRotations = jointRotations; }
void setJointTranslations(QVector<glm::vec3> jointTranslations) { _jointTranslations = jointTranslations; }
void setTranslation(const glm::vec3& translation) { _translation = translation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
void setScale(float scale) { _scale = scale; }
@ -105,6 +107,7 @@ protected:
private:
QVector<float> _blendshapeCoefficients;
QVector<glm::quat> _jointRotations;
QVector<glm::vec3> _jointTranslations;
glm::vec3 _translation;
glm::quat _rotation;
float _scale;
@ -124,4 +127,4 @@ void writeRecordingToFile(RecordingPointer recording, const QString& filename);
RecordingPointer readRecordingFromFile(RecordingPointer recording, const QString& filename);
RecordingPointer readRecordingFromRecFile(RecordingPointer recording, const QString& filename, const QByteArray& byteArray);
#endif // hifi_Recording_h
#endif // hifi_Recording_h

View file

@ -276,10 +276,13 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
if (jointsMapped()) {
bool newFrame;
auto frameData = getAnimationFrame(newFrame);
QVector<glm::quat> frameDataRotations;
QVector<glm::vec3> frameDataTranslations;
getAnimationFrame(newFrame, frameDataRotations, frameDataTranslations);
assert(frameDataRotations.size() == frameDataTranslations.size());
if (newFrame) {
for (int i = 0; i < frameData.size(); i++) {
_model->setJointState(i, true, frameData[i]);
for (int i = 0; i < frameDataRotations.size(); i++) {
_model->setJointState(i, true, frameDataRotations[i], frameDataTranslations[i], 1.0f);
}
}
}

View file

@ -218,18 +218,20 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) {
}
}
const QVector<glm::quat>& ModelEntityItem::getAnimationFrame(bool& newFrame) {
void ModelEntityItem::getAnimationFrame(bool& newFrame,
QVector<glm::quat>& rotationsResult, QVector<glm::vec3>& translationsResult) {
newFrame = false;
if (!hasAnimation() || !_jointMappingCompleted) {
return _lastKnownFrameData;
rotationsResult = _lastKnownFrameDataRotations;
translationsResult = _lastKnownFrameDataTranslations;
}
AnimationPointer myAnimation = getAnimation(_animationURL); // FIXME: this could be optimized
if (myAnimation && myAnimation->isLoaded()) {
const QVector<FBXAnimationFrame>& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy
int frameCount = frames.size();
if (frameCount > 0) {
int animationFrameIndex = (int)(glm::floor(getAnimationFrameIndex())) % frameCount;
@ -240,20 +242,27 @@ const QVector<glm::quat>& ModelEntityItem::getAnimationFrame(bool& newFrame) {
if (animationFrameIndex != _lastKnownFrameIndex) {
_lastKnownFrameIndex = animationFrameIndex;
newFrame = true;
const QVector<glm::quat>& rotations = frames[animationFrameIndex].rotations;
_lastKnownFrameData.resize(_jointMapping.size());
const QVector<glm::quat>& rotations = frames[animationFrameIndex].rotations;
const QVector<glm::vec3>& translations = frames[animationFrameIndex].translations;
_lastKnownFrameDataRotations.resize(_jointMapping.size());
_lastKnownFrameDataTranslations.resize(_jointMapping.size());
for (int j = 0; j < _jointMapping.size(); j++) {
int rotationIndex = _jointMapping[j];
if (rotationIndex != -1 && rotationIndex < rotations.size()) {
_lastKnownFrameData[j] = rotations[rotationIndex];
int index = _jointMapping[j];
if (index != -1 && index < rotations.size()) {
_lastKnownFrameDataRotations[j] = rotations[index];
}
if (index != -1 && index < translations.size()) {
_lastKnownFrameDataTranslations[j] = translations[index];
}
}
}
}
}
return _lastKnownFrameData;
rotationsResult = _lastKnownFrameDataRotations;
translationsResult = _lastKnownFrameDataTranslations;
}
bool ModelEntityItem::isAnimatingSomething() const {

View file

@ -106,7 +106,7 @@ public:
float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); }
void mapJoints(const QStringList& modelJointNames);
const QVector<glm::quat>& getAnimationFrame(bool& newFrame);
void getAnimationFrame(bool& newFrame, QVector<glm::quat>& rotationsResult, QVector<glm::vec3>& translationsResult);
bool jointsMapped() const { return _jointMappingCompleted; }
bool getAnimationIsPlaying() const { return _animationLoop.isRunning(); }
@ -123,7 +123,8 @@ public:
static void cleanupLoadedAnimations();
protected:
QVector<glm::quat> _lastKnownFrameData;
QVector<glm::quat> _lastKnownFrameDataRotations;
QVector<glm::vec3> _lastKnownFrameDataTranslations;
int _lastKnownFrameIndex;

View file

@ -530,6 +530,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
QHash<QString, QString> typeFlags;
QHash<QString, QString> localRotations;
QHash<QString, QString> localTranslations;
QHash<QString, QString> xComponents;
QHash<QString, QString> yComponents;
QHash<QString, QString> zComponents;
@ -1112,16 +1113,16 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
normalTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("specular") || type.contains("reflection")) {
specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "lcl rotation") {
localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "lcl translation") {
localTranslations.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "d|x") {
xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "d|y") {
yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type == "d|z") {
zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1));
@ -1232,6 +1233,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
for (int i = 0; i < frameCount; i++) {
FBXAnimationFrame frame;
frame.rotations.resize(modelIDs.size());
frame.translations.resize(modelIDs.size());
geometry.animationFrames.append(frame);
}
@ -1255,7 +1257,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
joint.freeLineage.append(index);
}
joint.freeLineage.remove(lastFreeIndex + 1, joint.freeLineage.size() - lastFreeIndex - 1);
joint.translation = model.translation;
joint.translation = model.translation; // these are usually in centimeters
joint.preTransform = model.preTransform;
joint.preRotation = model.preRotation;
joint.rotation = model.rotation;
@ -1280,7 +1282,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
}
joint.inverseBindRotation = joint.inverseDefaultRotation;
joint.name = model.name;
foreach (const QString& childID, _connectionChildMap.values(modelID)) {
QString type = typeFlags.value(childID);
if (!type.isEmpty()) {
@ -1293,17 +1295,29 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
geometry.joints.append(joint);
geometry.jointIndices.insert(model.name, geometry.joints.size());
QString rotationID = localRotations.value(modelID);
AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID));
AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID));
AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID));
glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation));
AnimationCurve xRotCurve = animationCurves.value(xComponents.value(rotationID));
AnimationCurve yRotCurve = animationCurves.value(yComponents.value(rotationID));
AnimationCurve zRotCurve = animationCurves.value(zComponents.value(rotationID));
QString translationID = localTranslations.value(modelID);
AnimationCurve xPosCurve = animationCurves.value(xComponents.value(translationID));
AnimationCurve yPosCurve = animationCurves.value(yComponents.value(translationID));
AnimationCurve zPosCurve = animationCurves.value(zComponents.value(translationID));
glm::vec3 defaultRotValues = glm::degrees(safeEulerAngles(joint.rotation));
glm::vec3 defaultPosValues = joint.translation;
for (int i = 0; i < frameCount; i++) {
geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3(
xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()),
yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()),
zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size()))));
xRotCurve.values.isEmpty() ? defaultRotValues.x : xRotCurve.values.at(i % xRotCurve.values.size()),
yRotCurve.values.isEmpty() ? defaultRotValues.y : yRotCurve.values.at(i % yRotCurve.values.size()),
zRotCurve.values.isEmpty() ? defaultRotValues.z : zRotCurve.values.at(i % zRotCurve.values.size()))));
geometry.animationFrames[i].translations[jointIndex] = glm::vec3(
xPosCurve.values.isEmpty() ? defaultPosValues.x : xPosCurve.values.at(i % xPosCurve.values.size()),
yPosCurve.values.isEmpty() ? defaultPosValues.y : yPosCurve.values.at(i % yPosCurve.values.size()),
zPosCurve.values.isEmpty() ? defaultPosValues.z : zPosCurve.values.at(i % zPosCurve.values.size()));
}
}

View file

@ -211,8 +211,8 @@ public:
/// A single animation frame extracted from an FBX document.
class FBXAnimationFrame {
public:
QVector<glm::quat> rotations;
QVector<glm::vec3> translations;
};
/// A light in an FBX document.

View file

@ -39,6 +39,9 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
return VERSION_ENTITIES_PARTICLE_ELLIPSOID_EMITTER;
case PacketType::AvatarData:
case PacketType::BulkAvatarData:
return 15;
default:
return 14;
}

View file

@ -1001,8 +1001,16 @@ void Model::clearJointState(int index) {
_rig->clearJointState(index);
}
void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) {
_rig->setJointState(index, valid, rotation, priority);
void Model::setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority) {
_rig->setJointState(index, valid, rotation, translation, priority);
}
void Model::setJointRotation(int index, bool valid, const glm::quat& rotation, float priority) {
_rig->setJointRotation(index, valid, rotation, priority);
}
void Model::setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority) {
_rig->setJointTranslation(index, valid, translation, priority);
}
int Model::getParentJointIndex(int jointIndex) const {
@ -1074,6 +1082,10 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation) const {
return _rig->getJointRotation(jointIndex, rotation);
}
bool Model::getJointTranslation(int jointIndex, glm::vec3& translation) const {
return _rig->getJointTranslation(jointIndex, translation);
}
bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const {
return _rig->getJointCombinedRotation(jointIndex, rotation, _rotation);
}

View file

@ -126,7 +126,9 @@ public:
QStringList getJointNames() const;
/// Sets the joint state at the specified index.
void setJointState(int index, bool valid, const glm::quat& rotation = glm::quat(), float priority = 1.0f);
void setJointState(int index, bool valid, const glm::quat& rotation, const glm::vec3& translation, float priority);
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
@ -161,6 +163,7 @@ public:
/// \param rotation[out] rotation of joint in model-frame
/// \return true if joint exists
bool getJointRotation(int jointIndex, glm::quat& rotation) const;
bool getJointTranslation(int jointIndex, glm::vec3& translation) const;
/// Returns the index of the parent of the indexed joint, or -1 if not found.
int getParentJointIndex(int jointIndex) const;

View file

@ -33,6 +33,8 @@ const vec3& Vectors::RIGHT = Vectors::UNIT_X;
const vec3& Vectors::UP = Vectors::UNIT_Y;
const vec3& Vectors::FRONT = Vectors::UNIT_NEG_Z;
const quat Quaternions::IDENTITY{ 1.0f, 0.0f, 0.0f, 0.0f };
// Safe version of glm::mix; based on the code in Nick Bobick's article,
// http://www.gamasutra.com/features/19980703/quaternions_01.htm (via Clyde,
// https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Quaternion.java)

View file

@ -53,6 +53,12 @@ const glm::vec3 IDENTITY_FRONT = glm::vec3( 0.0f, 0.0f,-1.0f);
glm::quat safeMix(const glm::quat& q1, const glm::quat& q2, float alpha);
class Quaternions {
public:
static const quat IDENTITY;
};
class Vectors {
public:
static const vec3 UNIT_X;