mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge pull request #12092 from hyperlogic/feature/dual-quat
Fix tootsie roll wrists and shoulders with dual quaternion skinning
This commit is contained in:
commit
0a54241fe3
21 changed files with 679 additions and 65 deletions
|
@ -76,3 +76,5 @@ AnimPose::operator glm::mat4() const {
|
|||
return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
|
||||
glm::vec4(zAxis, 0.0f), glm::vec4(_trans, 1.0f));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1732,6 +1732,14 @@ glm::mat4 Rig::getJointTransform(int jointIndex) const {
|
|||
}
|
||||
}
|
||||
|
||||
AnimPose Rig::getJointPose(int jointIndex) const {
|
||||
if (isIndexValid(jointIndex)) {
|
||||
return _internalPoseSet._absolutePoses[jointIndex];
|
||||
} else {
|
||||
return AnimPose::identity;
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
|
||||
|
||||
const AnimPose geometryToRigPose(_geometryToRigTransform);
|
||||
|
|
|
@ -164,6 +164,7 @@ public:
|
|||
|
||||
// rig space
|
||||
glm::mat4 getJointTransform(int jointIndex) const;
|
||||
AnimPose getJointPose(int jointIndex) const;
|
||||
|
||||
// Start or stop animations as needed.
|
||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState);
|
||||
|
|
|
@ -116,6 +116,7 @@ public:
|
|||
|
||||
int jointIndex;
|
||||
glm::mat4 inverseBindMatrix;
|
||||
Transform inverseBindTransform;
|
||||
};
|
||||
|
||||
const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048;
|
||||
|
@ -225,7 +226,7 @@ public:
|
|||
QVector<glm::vec2> texCoords;
|
||||
QVector<glm::vec2> texCoords1;
|
||||
QVector<uint16_t> clusterIndices;
|
||||
QVector<uint8_t> clusterWeights;
|
||||
QVector<uint16_t> clusterWeights;
|
||||
QVector<int32_t> originalIndices;
|
||||
|
||||
QVector<FBXCluster> clusters;
|
||||
|
|
|
@ -1675,6 +1675,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
fbxCluster.jointIndex = 0;
|
||||
}
|
||||
fbxCluster.inverseBindMatrix = glm::inverse(cluster.transformLink) * modelTransform;
|
||||
fbxCluster.inverseBindTransform = Transform(fbxCluster.inverseBindMatrix);
|
||||
extracted.mesh.clusters.append(fbxCluster);
|
||||
|
||||
// override the bind rotation with the transform link
|
||||
|
@ -1789,9 +1790,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
|
|||
}
|
||||
if (totalWeight > 0.0f) {
|
||||
const float ALMOST_HALF = 0.499f;
|
||||
float weightScalingFactor = (float)(UINT8_MAX) / totalWeight;
|
||||
float weightScalingFactor = (float)(UINT16_MAX) / totalWeight;
|
||||
for (int k = j; k < j + WEIGHTS_PER_VERTEX; ++k) {
|
||||
extracted.mesh.clusterWeights[k] = (uint8_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF);
|
||||
extracted.mesh.clusterWeights[k] = (uint16_t)(weightScalingFactor * weightAccumulators[k] + ALMOST_HALF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -624,7 +624,8 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) {
|
|||
// we need 16 bits instead of just 8 for clusterIndices
|
||||
clusterIndicesSize *= 2;
|
||||
}
|
||||
const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint8_t);
|
||||
|
||||
const int clusterWeightsSize = fbxMesh.clusterWeights.size() * sizeof(uint16_t);
|
||||
|
||||
// Normals and tangents are interleaved
|
||||
const int normalsOffset = 0;
|
||||
|
@ -759,7 +760,7 @@ void FBXReader::buildModelMesh(FBXMesh& extractedMesh, const QString& url) {
|
|||
if (clusterWeightsSize) {
|
||||
mesh->addAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT,
|
||||
model::BufferView(attribBuffer, clusterWeightsOffset, clusterWeightsSize,
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::XYZW)));
|
||||
gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW)));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -63,12 +63,17 @@ namespace gl {
|
|||
}
|
||||
*/
|
||||
|
||||
qCWarning(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:";
|
||||
qCCritical(glLogging) << "GLShader::compileShader - failed to compile the gl shader object:";
|
||||
int lineNumber = 0;
|
||||
for (auto s : srcstr) {
|
||||
qCWarning(glLogging) << s;
|
||||
QString str(s);
|
||||
QStringList lines = str.split("\n");
|
||||
for (auto& line : lines) {
|
||||
qCCritical(glLogging).noquote() << QString("%1: %2").arg(lineNumber++, 5, 10, QChar('0')).arg(line);
|
||||
}
|
||||
}
|
||||
qCWarning(glLogging) << "GLShader::compileShader - errors:";
|
||||
qCWarning(glLogging) << temp;
|
||||
qCCritical(glLogging) << "GLShader::compileShader - errors:";
|
||||
qCCritical(glLogging) << temp;
|
||||
|
||||
error = std::string(temp);
|
||||
delete[] temp;
|
||||
|
|
|
@ -20,16 +20,16 @@ using namespace render;
|
|||
CauterizedMeshPartPayload::CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform)
|
||||
: ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {}
|
||||
|
||||
void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices, const std::vector<glm::mat4>& cauterizedClusterMatrices) {
|
||||
ModelMeshPartPayload::updateClusterBuffer(clusterMatrices);
|
||||
void CauterizedMeshPartPayload::updateClusterBuffer(const std::vector<TransformType>& clusterTransforms, const std::vector<TransformType>& cauterizedClusterTransforms) {
|
||||
ModelMeshPartPayload::updateClusterBuffer(clusterTransforms);
|
||||
|
||||
if (cauterizedClusterMatrices.size() > 1) {
|
||||
if (cauterizedClusterTransforms.size() > 1) {
|
||||
if (!_cauterizedClusterBuffer) {
|
||||
_cauterizedClusterBuffer = std::make_shared<gpu::Buffer>(cauterizedClusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) cauterizedClusterMatrices.data());
|
||||
_cauterizedClusterBuffer = std::make_shared<gpu::Buffer>(cauterizedClusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) cauterizedClusterTransforms.data());
|
||||
} else {
|
||||
_cauterizedClusterBuffer->setSubData(0, cauterizedClusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) cauterizedClusterMatrices.data());
|
||||
_cauterizedClusterBuffer->setSubData(0, cauterizedClusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) cauterizedClusterTransforms.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,13 @@ class CauterizedMeshPartPayload : public ModelMeshPartPayload {
|
|||
public:
|
||||
CauterizedMeshPartPayload(ModelPointer model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
|
||||
|
||||
void updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices, const std::vector<glm::mat4>& cauterizedClusterMatrices);
|
||||
#if defined(SKIN_DQ)
|
||||
using TransformType = Model::TransformDualQuaternion;
|
||||
#else
|
||||
using TransformType = glm::mat4;
|
||||
#endif
|
||||
|
||||
void updateClusterBuffer(const std::vector<TransformType>& clusterTransforms, const std::vector<TransformType>& cauterizedClusterTransforms);
|
||||
|
||||
void updateTransformForCauterizedMesh(const Transform& renderTransform);
|
||||
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
#include "CauterizedModel.h"
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <DualQuaternion.h>
|
||||
|
||||
#include "AbstractViewStateInterface.h"
|
||||
#include "MeshPartPayload.h"
|
||||
#include "CauterizedMeshPartPayload.h"
|
||||
#include "RenderUtilsLogging.h"
|
||||
|
||||
|
||||
CauterizedModel::CauterizedModel(QObject* parent) :
|
||||
Model(parent) {
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ bool CauterizedModel::updateGeometry() {
|
|||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
Model::MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
state.clusterTransforms.resize(mesh.clusters.size());
|
||||
_cauterizeMeshStates.append(state);
|
||||
}
|
||||
}
|
||||
|
@ -109,30 +109,52 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
#if defined(SKIN_DQ)
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
#else
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
|
||||
if (!_cauterizeBoneSet.empty()) {
|
||||
#if defined(SKIN_DQ)
|
||||
AnimPose cauterizePose = _rig.getJointPose(geometry.neckJointIndex);
|
||||
cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f);
|
||||
#else
|
||||
static const glm::mat4 zeroScale(
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0001f, 0.0f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0001f, 0.0f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0001f, 0.0f),
|
||||
glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
auto cauterizeMatrix = _rig.getJointTransform(geometry.neckJointIndex) * zeroScale;
|
||||
|
||||
#endif
|
||||
for (int i = 0; i < _cauterizeMeshStates.size(); i++) {
|
||||
Model::MeshState& state = _cauterizeMeshStates[i];
|
||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) {
|
||||
jointMatrix = cauterizeMatrix;
|
||||
|
||||
if (_cauterizeBoneSet.find(cluster.jointIndex) == _cauterizeBoneSet.end()) {
|
||||
// not cauterized so just copy the value from the non-cauterized version.
|
||||
state.clusterTransforms[j] = _meshStates[i].clusterTransforms[j];
|
||||
} else {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform jointTransform(cauterizePose.rot(), cauterizePose.scale(), cauterizePose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
#else
|
||||
glm_mat4u_mul(cauterizeMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
}
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,24 +211,38 @@ void CauterizedModel::updateRenderItems() {
|
|||
|
||||
auto itemID = self->_modelMeshRenderItemIDs[i];
|
||||
auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;
|
||||
auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices);
|
||||
auto clusterMatricesCauterized(self->getCauterizeMeshState(meshIndex).clusterMatrices);
|
||||
auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms);
|
||||
auto clusterTransformsCauterized(self->getCauterizeMeshState(meshIndex).clusterTransforms);
|
||||
|
||||
bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
|
||||
transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, clusterMatrices, clusterMatricesCauterized, invalidatePayloadShapeKey,
|
||||
transaction.updateItem<CauterizedMeshPartPayload>(itemID, [modelTransform, clusterTransforms, clusterTransformsCauterized, invalidatePayloadShapeKey,
|
||||
isWireframe, isVisible, isLayeredInFront, isLayeredInHUD, enableCauterization](CauterizedMeshPartPayload& data) {
|
||||
data.updateClusterBuffer(clusterMatrices, clusterMatricesCauterized);
|
||||
data.updateClusterBuffer(clusterTransforms, clusterTransformsCauterized);
|
||||
|
||||
Transform renderTransform = modelTransform;
|
||||
if (clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0]));
|
||||
if (clusterTransforms.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(transform);
|
||||
#else
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0]));
|
||||
#endif
|
||||
}
|
||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
|
||||
|
||||
renderTransform = modelTransform;
|
||||
if (clusterMatricesCauterized.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterMatricesCauterized[0]));
|
||||
if (clusterTransformsCauterized.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(Transform(transform));
|
||||
#else
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterTransformsCauterized[0]));
|
||||
#endif
|
||||
}
|
||||
data.updateTransformForCauterizedMesh(renderTransform);
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "MeshPartPayload.h"
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include <DualQuaternion.h>
|
||||
|
||||
#include "DeferredLightingEffect.h"
|
||||
|
||||
|
@ -325,12 +326,20 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in
|
|||
const Model::MeshState& state = model->getMeshState(_meshIndex);
|
||||
|
||||
updateMeshPart(modelMesh, partIndex);
|
||||
computeAdjustedLocalBound(state.clusterMatrices);
|
||||
computeAdjustedLocalBound(state.clusterTransforms);
|
||||
|
||||
updateTransform(transform, offsetTransform);
|
||||
Transform renderTransform = transform;
|
||||
if (state.clusterMatrices.size() == 1) {
|
||||
renderTransform = transform.worldTransform(Transform(state.clusterMatrices[0]));
|
||||
if (state.clusterTransforms.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(state.clusterTransforms[0].getRotation(),
|
||||
state.clusterTransforms[0].getScale(),
|
||||
state.clusterTransforms[0].getTranslation());
|
||||
renderTransform = transform.worldTransform(Transform(transform));
|
||||
#else
|
||||
renderTransform = transform.worldTransform(Transform(state.clusterTransforms[0]));
|
||||
#endif
|
||||
|
||||
}
|
||||
updateTransformForSkinnedMesh(renderTransform, transform);
|
||||
|
||||
|
@ -360,17 +369,16 @@ void ModelMeshPartPayload::notifyLocationChanged() {
|
|||
|
||||
}
|
||||
|
||||
|
||||
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices) {
|
||||
void ModelMeshPartPayload::updateClusterBuffer(const std::vector<TransformType>& clusterTransforms) {
|
||||
// Once computed the cluster matrices, update the buffer(s)
|
||||
if (clusterMatrices.size() > 1) {
|
||||
if (clusterTransforms.size() > 1) {
|
||||
if (!_clusterBuffer) {
|
||||
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) clusterMatrices.data());
|
||||
_clusterBuffer = std::make_shared<gpu::Buffer>(clusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) clusterTransforms.data());
|
||||
}
|
||||
else {
|
||||
_clusterBuffer->setSubData(0, clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) clusterMatrices.data());
|
||||
_clusterBuffer->setSubData(0, clusterTransforms.size() * sizeof(TransformType),
|
||||
(const gpu::Byte*) clusterTransforms.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -530,13 +538,29 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
|
|||
args->_details._trianglesRendered += _drawPart._numIndices / INDICES_PER_TRIANGLE;
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices) {
|
||||
|
||||
void ModelMeshPartPayload::computeAdjustedLocalBound(const std::vector<TransformType>& clusterTransforms) {
|
||||
_adjustedLocalBound = _localBound;
|
||||
if (clusterMatrices.size() > 0) {
|
||||
_adjustedLocalBound.transform(clusterMatrices[0]);
|
||||
for (int i = 1; i < (int)clusterMatrices.size(); ++i) {
|
||||
if (clusterTransforms.size() > 0) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform rootTransform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
_adjustedLocalBound.transform(rootTransform);
|
||||
#else
|
||||
_adjustedLocalBound.transform(clusterTransforms[0]);
|
||||
#endif
|
||||
|
||||
for (int i = 1; i < (int)clusterTransforms.size(); ++i) {
|
||||
AABox clusterBound = _localBound;
|
||||
clusterBound.transform(clusterMatrices[i]);
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[i].getRotation(),
|
||||
clusterTransforms[i].getScale(),
|
||||
clusterTransforms[i].getTranslation());
|
||||
clusterBound.transform(transform);
|
||||
#else
|
||||
clusterBound.transform(clusterTransforms[i]);
|
||||
#endif
|
||||
_adjustedLocalBound += clusterBound;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,14 @@ public:
|
|||
typedef Payload::DataPointer Pointer;
|
||||
|
||||
void notifyLocationChanged() override;
|
||||
void updateClusterBuffer(const std::vector<glm::mat4>& clusterMatrices);
|
||||
|
||||
#if defined(SKIN_DQ)
|
||||
using TransformType = Model::TransformDualQuaternion;
|
||||
#else
|
||||
using TransformType = glm::mat4;
|
||||
#endif
|
||||
|
||||
void updateClusterBuffer(const std::vector<TransformType>& clusterTransforms);
|
||||
void updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform);
|
||||
|
||||
// Render Item interface
|
||||
|
@ -104,7 +111,7 @@ public:
|
|||
void bindMesh(gpu::Batch& batch) override;
|
||||
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
|
||||
|
||||
void computeAdjustedLocalBound(const std::vector<glm::mat4>& clusterMatrices);
|
||||
void computeAdjustedLocalBound(const std::vector<TransformType>& clusterTransforms);
|
||||
|
||||
gpu::BufferPointer _clusterBuffer;
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <TBBHelpers.h>
|
||||
|
||||
#include <model-networking/SimpleMeshProxy.h>
|
||||
#include <DualQuaternion.h>
|
||||
|
||||
#include <glm/gtc/packing.hpp>
|
||||
|
||||
|
@ -269,16 +270,24 @@ void Model::updateRenderItems() {
|
|||
|
||||
auto itemID = self->_modelMeshRenderItemIDs[i];
|
||||
auto meshIndex = self->_modelMeshRenderItemShapes[i].meshIndex;
|
||||
auto clusterMatrices(self->getMeshState(meshIndex).clusterMatrices);
|
||||
auto clusterTransforms(self->getMeshState(meshIndex).clusterTransforms);
|
||||
|
||||
bool invalidatePayloadShapeKey = self->shouldInvalidatePayloadShapeKey(meshIndex);
|
||||
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterMatrices, invalidatePayloadShapeKey,
|
||||
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, clusterTransforms, invalidatePayloadShapeKey,
|
||||
isWireframe, isVisible, isLayeredInFront, isLayeredInHUD](ModelMeshPartPayload& data) {
|
||||
data.updateClusterBuffer(clusterMatrices);
|
||||
data.updateClusterBuffer(clusterTransforms);
|
||||
|
||||
Transform renderTransform = modelTransform;
|
||||
if (clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterMatrices[0]));
|
||||
if (clusterTransforms.size() == 1) {
|
||||
#if defined(SKIN_DQ)
|
||||
Transform transform(clusterTransforms[0].getRotation(),
|
||||
clusterTransforms[0].getScale(),
|
||||
clusterTransforms[0].getTranslation());
|
||||
renderTransform = modelTransform.worldTransform(Transform(transform));
|
||||
#else
|
||||
renderTransform = modelTransform.worldTransform(Transform(clusterTransforms[0]));
|
||||
#endif
|
||||
}
|
||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
|
||||
|
||||
|
@ -359,7 +368,7 @@ bool Model::updateGeometry() {
|
|||
const FBXGeometry& fbxGeometry = getFBXGeometry();
|
||||
foreach (const FBXMesh& mesh, fbxGeometry.meshes) {
|
||||
MeshState state;
|
||||
state.clusterMatrices.resize(mesh.clusters.size());
|
||||
state.clusterTransforms.resize(mesh.clusters.size());
|
||||
_meshStates.push_back(state);
|
||||
|
||||
// Note: we add empty buffers for meshes that lack blendshapes so we can access the buffers by index
|
||||
|
@ -1211,7 +1220,7 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
void Model::computeMeshPartLocalBounds() {
|
||||
for (auto& part : _modelMeshRenderItems) {
|
||||
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
||||
part->computeAdjustedLocalBound(state.clusterMatrices);
|
||||
part->computeAdjustedLocalBound(state.clusterTransforms);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1222,6 +1231,7 @@ void Model::updateClusterMatrices() {
|
|||
if (!_needsUpdateClusterMatrices || !isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_needsUpdateClusterMatrices = false;
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
for (int i = 0; i < (int) _meshStates.size(); i++) {
|
||||
|
@ -1229,8 +1239,16 @@ void Model::updateClusterMatrices() {
|
|||
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||
for (int j = 0; j < mesh.clusters.size(); j++) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(j);
|
||||
#if defined(SKIN_DQ)
|
||||
auto jointPose = _rig.getJointPose(cluster.jointIndex);
|
||||
Transform jointTransform(jointPose.rot(), jointPose.scale(), jointPose.trans());
|
||||
Transform clusterTransform;
|
||||
Transform::mult(clusterTransform, jointTransform, cluster.inverseBindTransform);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(clusterTransform);
|
||||
#else
|
||||
auto jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,11 +30,15 @@
|
|||
#include <Transform.h>
|
||||
#include <SpatiallyNestable.h>
|
||||
#include <TriangleSet.h>
|
||||
#include <DualQuaternion.h>
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "TextureCache.h"
|
||||
#include "Rig.h"
|
||||
|
||||
// Use dual quaternion skinning!
|
||||
// Must match define in Skinning.slh
|
||||
#define SKIN_DQ
|
||||
|
||||
class AbstractViewStateInterface;
|
||||
class QScriptEngine;
|
||||
|
@ -246,9 +250,46 @@ public:
|
|||
int getRenderInfoDrawCalls() const { return _renderInfoDrawCalls; }
|
||||
bool getRenderInfoHasTransparent() const { return _renderInfoHasTransparent; }
|
||||
|
||||
|
||||
#if defined(SKIN_DQ)
|
||||
class TransformDualQuaternion {
|
||||
public:
|
||||
TransformDualQuaternion() {}
|
||||
TransformDualQuaternion(const glm::mat4& m) {
|
||||
AnimPose p(m);
|
||||
_scale.x = p.scale().x;
|
||||
_scale.y = p.scale().y;
|
||||
_scale.z = p.scale().z;
|
||||
_dq = DualQuaternion(p.rot(), p.trans());
|
||||
}
|
||||
TransformDualQuaternion(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) {
|
||||
_scale.x = scale.x;
|
||||
_scale.y = scale.y;
|
||||
_scale.z = scale.z;
|
||||
_dq = DualQuaternion(rot, trans);
|
||||
}
|
||||
TransformDualQuaternion(const Transform& transform) {
|
||||
_scale = glm::vec4(transform.getScale(), 0.0f);
|
||||
_dq = DualQuaternion(transform.getRotation(), transform.getTranslation());
|
||||
}
|
||||
glm::vec3 getScale() const { return glm::vec3(_scale); }
|
||||
glm::quat getRotation() const { return _dq.getRotation(); }
|
||||
glm::vec3 getTranslation() const { return _dq.getTranslation(); }
|
||||
glm::mat4 getMatrix() const { return createMatFromScaleQuatAndPos(getScale(), getRotation(), getTranslation()); };
|
||||
protected:
|
||||
glm::vec4 _scale { 1.0f, 1.0f, 1.0f, 0.0f };
|
||||
DualQuaternion _dq;
|
||||
glm::vec4 _padding;
|
||||
};
|
||||
#endif
|
||||
|
||||
class MeshState {
|
||||
public:
|
||||
std::vector<glm::mat4> clusterMatrices;
|
||||
#if defined(SKIN_DQ)
|
||||
std::vector<TransformDualQuaternion> clusterTransforms;
|
||||
#else
|
||||
std::vector<glm::mat4> clusterTransforms;
|
||||
#endif
|
||||
};
|
||||
|
||||
const MeshState& getMeshState(int index) { return _meshStates.at(index); }
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
<@if not SKINNING_SLH@>
|
||||
<@def SKINNING_SLH@>
|
||||
|
||||
// Use dual quaternion skinning
|
||||
// Must match #define SKIN_DQ in Model.h
|
||||
<@def SKIN_DQ@>
|
||||
|
||||
const int MAX_CLUSTERS = 128;
|
||||
const int INDICES_PER_VERTEX = 4;
|
||||
|
||||
|
@ -18,6 +22,156 @@ layout(std140) uniform skinClusterBuffer {
|
|||
mat4 clusterMatrices[MAX_CLUSTERS];
|
||||
};
|
||||
|
||||
<@if SKIN_DQ@>
|
||||
|
||||
mat4 dualQuatToMat4(vec4 real, vec4 dual) {
|
||||
float twoRealXSq = 2.0 * real.x * real.x;
|
||||
float twoRealYSq = 2.0 * real.y * real.y;
|
||||
float twoRealZSq = 2.0 * real.z * real.z;
|
||||
float twoRealXY = 2.0 * real.x * real.y;
|
||||
float twoRealXZ = 2.0 * real.x * real.z;
|
||||
float twoRealXW = 2.0 * real.x * real.w;
|
||||
float twoRealZW = 2.0 * real.z * real.w;
|
||||
float twoRealYZ = 2.0 * real.y * real.z;
|
||||
float twoRealYW = 2.0 * real.y * real.w;
|
||||
vec4 col0 = vec4(1.0 - twoRealYSq - twoRealZSq,
|
||||
twoRealXY + twoRealZW,
|
||||
twoRealXZ - twoRealYW,
|
||||
0.0);
|
||||
vec4 col1 = vec4(twoRealXY - twoRealZW,
|
||||
1 - twoRealXSq - twoRealZSq,
|
||||
twoRealYZ + twoRealXW,
|
||||
0.0);
|
||||
vec4 col2 = vec4(twoRealXZ + twoRealYW,
|
||||
twoRealYZ - twoRealXW,
|
||||
1 - twoRealXSq - twoRealYSq,
|
||||
0.0);
|
||||
vec4 col3 = vec4(2.0 * (-dual.w * real.x + dual.x * real.w - dual.y * real.z + dual.z * real.y),
|
||||
2.0 * (-dual.w * real.y + dual.x * real.z + dual.y * real.w - dual.z * real.x),
|
||||
2.0 * (-dual.w * real.z - dual.x * real.y + dual.y * real.x + dual.z * real.w),
|
||||
1.0);
|
||||
|
||||
return mat4(col0, col1, col2, col3);
|
||||
}
|
||||
|
||||
// dual quaternion linear blending
|
||||
void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) {
|
||||
|
||||
// linearly blend scale and dual quaternion components
|
||||
vec3 sAccum = vec3(0.0, 0.0, 0.0);
|
||||
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
|
||||
float clusterWeight = skinClusterWeight[i];
|
||||
|
||||
vec3 scale = vec3(clusterMatrix[0]);
|
||||
vec4 real = clusterMatrix[1];
|
||||
vec4 dual = clusterMatrix[2];
|
||||
|
||||
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
|
||||
float dqClusterWeight = clusterWeight;
|
||||
if (dot(real, polarityReference) < 0) {
|
||||
dqClusterWeight = -clusterWeight;
|
||||
}
|
||||
|
||||
sAccum += scale * clusterWeight;
|
||||
rAccum += real * dqClusterWeight;
|
||||
dAccum += dual * dqClusterWeight;
|
||||
}
|
||||
|
||||
// normalize dual quaternion
|
||||
float norm = length(rAccum);
|
||||
rAccum /= norm;
|
||||
dAccum /= norm;
|
||||
|
||||
// conversion from dual quaternion to 4x4 matrix.
|
||||
mat4 m = dualQuatToMat4(rAccum, dAccum);
|
||||
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
|
||||
}
|
||||
|
||||
void skinPositionNormal(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal,
|
||||
out vec4 skinnedPosition, out vec3 skinnedNormal) {
|
||||
|
||||
// linearly blend scale and dual quaternion components
|
||||
vec3 sAccum = vec3(0.0, 0.0, 0.0);
|
||||
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
|
||||
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
|
||||
float clusterWeight = skinClusterWeight[i];
|
||||
|
||||
vec3 scale = vec3(clusterMatrix[0]);
|
||||
vec4 real = clusterMatrix[1];
|
||||
vec4 dual = clusterMatrix[2];
|
||||
|
||||
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
|
||||
float dqClusterWeight = clusterWeight;
|
||||
if (dot(real, polarityReference) < 0) {
|
||||
dqClusterWeight = -clusterWeight;
|
||||
}
|
||||
|
||||
sAccum += scale * clusterWeight;
|
||||
rAccum += real * dqClusterWeight;
|
||||
dAccum += dual * dqClusterWeight;
|
||||
}
|
||||
|
||||
// normalize dual quaternion
|
||||
float norm = length(rAccum);
|
||||
rAccum /= norm;
|
||||
dAccum /= norm;
|
||||
|
||||
// conversion from dual quaternion to 4x4 matrix.
|
||||
mat4 m = dualQuatToMat4(rAccum, dAccum);
|
||||
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
|
||||
skinnedNormal = vec3(m * vec4(inNormal, 0));
|
||||
}
|
||||
|
||||
void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, vec3 inNormal, vec3 inTangent,
|
||||
out vec4 skinnedPosition, out vec3 skinnedNormal, out vec3 skinnedTangent) {
|
||||
|
||||
// linearly blend scale and dual quaternion components
|
||||
vec3 sAccum = vec3(0.0, 0.0, 0.0);
|
||||
vec4 rAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 dAccum = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec4 polarityReference = clusterMatrices[skinClusterIndex[0]][1];
|
||||
|
||||
for (int i = 0; i < INDICES_PER_VERTEX; i++) {
|
||||
mat4 clusterMatrix = clusterMatrices[(skinClusterIndex[i])];
|
||||
float clusterWeight = skinClusterWeight[i];
|
||||
|
||||
vec3 scale = vec3(clusterMatrix[0]);
|
||||
vec4 real = clusterMatrix[1];
|
||||
vec4 dual = clusterMatrix[2];
|
||||
|
||||
// to ensure that we rotate along the shortest arc, reverse dual quaternions with negative polarity.
|
||||
float dqClusterWeight = clusterWeight;
|
||||
if (dot(real, polarityReference) < 0) {
|
||||
dqClusterWeight = -clusterWeight;
|
||||
}
|
||||
|
||||
sAccum += scale * clusterWeight;
|
||||
rAccum += real * dqClusterWeight;
|
||||
dAccum += dual * dqClusterWeight;
|
||||
}
|
||||
|
||||
// normalize dual quaternion
|
||||
float norm = length(rAccum);
|
||||
rAccum /= norm;
|
||||
dAccum /= norm;
|
||||
|
||||
// conversion from dual quaternion to 4x4 matrix.
|
||||
mat4 m = dualQuatToMat4(rAccum, dAccum);
|
||||
skinnedPosition = m * (vec4(sAccum, 1) * inPosition);
|
||||
skinnedNormal = vec3(m * vec4(inNormal, 0));
|
||||
skinnedTangent = vec3(m * vec4(inTangent, 0));
|
||||
}
|
||||
|
||||
<@else@> // SKIN_DQ
|
||||
|
||||
void skinPosition(ivec4 skinClusterIndex, vec4 skinClusterWeight, vec4 inPosition, out vec4 skinnedPosition) {
|
||||
vec4 newPosition = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
|
@ -65,5 +219,6 @@ void skinPositionNormalTangent(ivec4 skinClusterIndex, vec4 skinClusterWeight, v
|
|||
skinnedTangent = newTangent.xyz;
|
||||
}
|
||||
|
||||
<@endif@> // if SKIN_DQ
|
||||
|
||||
<@endif@>
|
||||
<@endif@> // if not SKINNING_SLH
|
||||
|
|
|
@ -52,13 +52,27 @@ void SoftAttachmentModel::updateClusterMatrices() {
|
|||
|
||||
// TODO: cache these look-ups as an optimization
|
||||
int jointIndexOverride = getJointIndexOverride(cluster.jointIndex);
|
||||
#if defined(SKIN_DQ)
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterMatrices[j]);
|
||||
|
||||
glm::mat4 m;
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, m);
|
||||
state.clusterTransforms[j] = Model::TransformDualQuaternion(m);
|
||||
#else
|
||||
glm::mat4 jointMatrix;
|
||||
if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride.getJointStateCount()) {
|
||||
jointMatrix = _rigOverride.getJointTransform(jointIndexOverride);
|
||||
} else {
|
||||
jointMatrix = _rig.getJointTransform(cluster.jointIndex);
|
||||
}
|
||||
|
||||
glm_mat4u_mul(jointMatrix, cluster.inverseBindMatrix, state.clusterTransforms[j]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
92
libraries/shared/src/DualQuaternion.cpp
Normal file
92
libraries/shared/src/DualQuaternion.cpp
Normal file
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// DualQuaternion.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on Dec 13th 2017.
|
||||
// Copyright (c) 2017 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 "DualQuaternion.h"
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
// delegating constructor
|
||||
DualQuaternion::DualQuaternion() : _real(1.0f, 0.0f, 0.0f, 0.0), _dual(0.0f, 0.0f, 0.0f, 0.0f) {
|
||||
}
|
||||
|
||||
DualQuaternion::DualQuaternion(const glm::mat4& m) : DualQuaternion(glmExtractRotation(m), extractTranslation(m)) {
|
||||
}
|
||||
|
||||
DualQuaternion::DualQuaternion(const glm::quat& real, const glm::quat& dual) : _real(real), _dual(dual) {
|
||||
}
|
||||
|
||||
DualQuaternion::DualQuaternion(const glm::vec4& real, const glm::vec4& dual) :
|
||||
_real(real.w, real.x, real.y, real.z),
|
||||
_dual(dual.w, dual.x, dual.y, dual.z) {
|
||||
}
|
||||
|
||||
DualQuaternion::DualQuaternion(const glm::quat& rotation, const glm::vec3& translation) {
|
||||
_real = rotation;
|
||||
_dual = glm::quat(0.0f, 0.5f * translation.x, 0.5f * translation.y, 0.5f * translation.z) * rotation;
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::operator*(const DualQuaternion& rhs) const {
|
||||
return DualQuaternion(_real * rhs._real, _real * rhs._dual + _dual * rhs._real);
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::operator*(float scalar) const {
|
||||
return DualQuaternion(_real * scalar, _dual * scalar);
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::operator+(const DualQuaternion& rhs) const {
|
||||
return DualQuaternion(_real + rhs._real, _dual + rhs._dual);
|
||||
}
|
||||
|
||||
glm::vec3 DualQuaternion::xformPoint(const glm::vec3& rhs) const {
|
||||
DualQuaternion v(glm::quat(), glm::quat(0.0f, rhs.x, rhs.y, rhs.z));
|
||||
DualQuaternion dualConj(glm::conjugate(_real), -glm::conjugate(_dual));
|
||||
DualQuaternion result = *this * v * dualConj;
|
||||
return vec3(result._dual.x, result._dual.y, result._dual.z);
|
||||
}
|
||||
|
||||
glm::quat DualQuaternion::getRotation() const {
|
||||
return _real;
|
||||
}
|
||||
|
||||
glm::vec3 DualQuaternion::getTranslation() const {
|
||||
glm::quat result = 2.0f * (_dual * glm::inverse(_real));
|
||||
return glm::vec3(result.x, result.y, result.z);
|
||||
}
|
||||
|
||||
glm::vec3 DualQuaternion::xformVector(const glm::vec3& rhs) const {
|
||||
return _real * rhs;
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::inverse() const {
|
||||
glm::quat invReal = glm::inverse(_real);
|
||||
return DualQuaternion(invReal, - invReal * _dual * invReal);
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::conjugate() const {
|
||||
return DualQuaternion(glm::conjugate(_real), glm::conjugate(_dual));
|
||||
}
|
||||
|
||||
float DualQuaternion::length() const {
|
||||
float dot = this->dot(*this);
|
||||
return sqrtf(dot);
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::normalize() const {
|
||||
float invLen = 1.0f / length();
|
||||
return *this * invLen;
|
||||
}
|
||||
|
||||
float DualQuaternion::dot(const DualQuaternion& rhs) const {
|
||||
DualQuaternion result = *this * conjugate();
|
||||
return result._real.w;
|
||||
}
|
||||
|
||||
DualQuaternion DualQuaternion::operator-() const {
|
||||
return DualQuaternion(-_real, -_dual);
|
||||
}
|
62
libraries/shared/src/DualQuaternion.h
Normal file
62
libraries/shared/src/DualQuaternion.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// DualQuaternion.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on Dec 13th 2017.
|
||||
// Copyright (c) 2017 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_DualQuaternion
|
||||
#define hifi_DualQuaternion
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QDebug>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
class DualQuaternion {
|
||||
public:
|
||||
DualQuaternion();
|
||||
explicit DualQuaternion(const glm::mat4& m);
|
||||
DualQuaternion(const glm::quat& real, const glm::quat& imag);
|
||||
DualQuaternion(const glm::quat& rotation, const glm::vec3& translation);
|
||||
DualQuaternion(const glm::vec4& real, const glm::vec4& imag);
|
||||
DualQuaternion operator*(const DualQuaternion& rhs) const;
|
||||
DualQuaternion operator*(float scalar) const;
|
||||
DualQuaternion operator+(const DualQuaternion& rhs) const;
|
||||
|
||||
const glm::quat& real() const { return _real; }
|
||||
glm::quat& real() { return _real; }
|
||||
|
||||
const glm::quat& dual() const { return _dual; }
|
||||
glm::quat& dual() { return _dual; }
|
||||
|
||||
glm::quat getRotation() const;
|
||||
glm::vec3 getTranslation() const;
|
||||
|
||||
glm::vec3 xformPoint(const glm::vec3& rhs) const;
|
||||
glm::vec3 xformVector(const glm::vec3& rhs) const;
|
||||
|
||||
DualQuaternion inverse() const;
|
||||
DualQuaternion conjugate() const;
|
||||
float length() const;
|
||||
DualQuaternion normalize() const;
|
||||
float dot(const DualQuaternion& rhs) const;
|
||||
DualQuaternion operator-() const;
|
||||
|
||||
protected:
|
||||
friend QDebug operator<<(QDebug debug, const DualQuaternion& pose);
|
||||
glm::quat _real;
|
||||
glm::quat _dual;
|
||||
};
|
||||
|
||||
|
||||
inline QDebug operator<<(QDebug debug, const DualQuaternion& dq) {
|
||||
debug << "AnimPose, real = (" << dq._real.x << dq._real.y << dq._real.z << dq._real.w << "), dual = (" << dq._dual.x << dq._dual.y << dq._dual.z << dq._dual.w << ")";
|
||||
return debug;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -58,7 +58,7 @@ public:
|
|||
_rotation(rotation),
|
||||
_scale(scale),
|
||||
_translation(translation),
|
||||
_flags(FLAG_CACHE_INVALID_BITSET) // invalid cache
|
||||
_flags(0xf) // FLAG_TRANSLATION | FLAG_ROTATION | FLAG_SCALING | FLAG_NON_UNIFORM
|
||||
{
|
||||
if (!isValidScale(_scale)) {
|
||||
_scale = Vec3(1.0f);
|
||||
|
|
115
tests/shared/src/DualQuaternionTests.cpp
Normal file
115
tests/shared/src/DualQuaternionTests.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// DualQuaternionTests.cpp
|
||||
// tests/shared/src
|
||||
//
|
||||
// Copyright 2017 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 <iostream>
|
||||
|
||||
#include "DualQuaternionTests.h"
|
||||
|
||||
#include <DualQuaternion.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <../GLMTestUtils.h>
|
||||
#include <../QTestExtensions.h>
|
||||
|
||||
QTEST_MAIN(DualQuaternionTests)
|
||||
|
||||
static void quatComp(const glm::quat& q1, const glm::quat& q2) {
|
||||
QCOMPARE_WITH_ABS_ERROR(q1.x, q2.x, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(q1.y, q2.y, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(q1.z, q2.z, EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(q1.w, q2.w, EPSILON);
|
||||
}
|
||||
|
||||
void DualQuaternionTests::ctor() {
|
||||
glm::quat real = angleAxis(PI / 2.0f, Vectors::UNIT_Y);
|
||||
glm::quat dual(0.0f, 1.0f, 2.0f, 3.0f);
|
||||
|
||||
DualQuaternion dq(real, dual);
|
||||
quatComp(real, dq.real());
|
||||
quatComp(dual, dq.dual());
|
||||
|
||||
glm::quat rotation = angleAxis(PI / 3.0f, Vectors::UNIT_X);
|
||||
glm::vec3 translation(1.0, 2.0f, 3.0f);
|
||||
dq = DualQuaternion(rotation, translation);
|
||||
quatComp(rotation, dq.getRotation());
|
||||
QCOMPARE_WITH_ABS_ERROR(translation, dq.getTranslation(), EPSILON);
|
||||
|
||||
rotation = angleAxis(-2.0f * PI / 7.0f, Vectors::UNIT_Z);
|
||||
translation = glm::vec3(-1.0, 12.0f, 2.0f);
|
||||
glm::mat4 m = createMatFromQuatAndPos(rotation, translation);
|
||||
dq = DualQuaternion(m);
|
||||
quatComp(rotation, dq.getRotation());
|
||||
QCOMPARE_WITH_ABS_ERROR(translation, dq.getTranslation(), EPSILON);
|
||||
}
|
||||
|
||||
void DualQuaternionTests::mult() {
|
||||
|
||||
glm::quat rotation = angleAxis(PI / 3.0f, Vectors::UNIT_X);
|
||||
glm::vec3 translation(1.0, 2.0f, 3.0f);
|
||||
glm::mat4 m1 = createMatFromQuatAndPos(rotation, translation);
|
||||
DualQuaternion dq1(m1);
|
||||
|
||||
rotation = angleAxis(-2.0f * PI / 7.0f, Vectors::UNIT_Z);
|
||||
translation = glm::vec3(-1.0, 12.0f, 2.0f);
|
||||
glm::mat4 m2 = createMatFromQuatAndPos(rotation, translation);
|
||||
DualQuaternion dq2(m2);
|
||||
|
||||
DualQuaternion dq3 = dq1 * dq2;
|
||||
glm::mat4 m3 = m1 * m2;
|
||||
|
||||
rotation = glmExtractRotation(m3);
|
||||
translation = extractTranslation(m3);
|
||||
|
||||
quatComp(rotation, dq3.getRotation());
|
||||
QCOMPARE_WITH_ABS_ERROR(translation, dq3.getTranslation(), EPSILON);
|
||||
}
|
||||
|
||||
void DualQuaternionTests::xform() {
|
||||
|
||||
glm::quat rotation = angleAxis(PI / 3.0f, Vectors::UNIT_X);
|
||||
glm::vec3 translation(1.0, 2.0f, 3.0f);
|
||||
glm::mat4 m1 = createMatFromQuatAndPos(rotation, translation);
|
||||
DualQuaternion dq1(m1);
|
||||
|
||||
rotation = angleAxis(-2.0f * PI / 7.0f, Vectors::UNIT_Z);
|
||||
translation = glm::vec3(-1.0, 12.0f, 2.0f);
|
||||
glm::mat4 m2 = createMatFromQuatAndPos(rotation, translation);
|
||||
DualQuaternion dq2(m2);
|
||||
|
||||
DualQuaternion dq3 = dq1 * dq2;
|
||||
glm::mat4 m3 = m1 * m2;
|
||||
|
||||
glm::vec3 p(1.0f, 2.0f, 3.0f);
|
||||
|
||||
glm::vec3 p1 = transformPoint(m3, p);
|
||||
glm::vec3 p2 = dq3.xformPoint(p);
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(p1, p2, 0.001f);
|
||||
|
||||
p1 = transformVectorFast(m3, p);
|
||||
p2 = dq3.xformVector(p);
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(p1, p2, 0.001f);
|
||||
}
|
||||
|
||||
void DualQuaternionTests::trans() {
|
||||
glm::vec3 t1 = glm::vec3();
|
||||
DualQuaternion dq1(Quaternions::IDENTITY, t1);
|
||||
glm::vec3 t2 = glm::vec3(1.0f, 2.0f, 3.0f);
|
||||
DualQuaternion dq2(angleAxis(PI / 3.0f, Vectors::UNIT_X), t2);
|
||||
glm::vec3 t3 = glm::vec3(3.0f, 2.0f, 1.0f);
|
||||
DualQuaternion dq3(angleAxis(PI / 5.0f, Vectors::UNIT_Y), t3);
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(t1, dq1.getTranslation(), 0.001f);
|
||||
QCOMPARE_WITH_ABS_ERROR(t2, dq2.getTranslation(), 0.001f);
|
||||
QCOMPARE_WITH_ABS_ERROR(t3, dq3.getTranslation(), 0.001f);
|
||||
}
|
25
tests/shared/src/DualQuaternionTests.h
Normal file
25
tests/shared/src/DualQuaternionTests.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// DualQuaternionTests.h
|
||||
// tests/shared/src
|
||||
//
|
||||
// Copyright 2017 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_DualQuaternionTests_h
|
||||
#define hifi_DualQuaternionTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class DualQuaternionTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void ctor();
|
||||
void mult();
|
||||
void xform();
|
||||
void trans();
|
||||
};
|
||||
|
||||
#endif // hifi_DualQuaternionTests_h
|
Loading…
Reference in a new issue