diff --git a/projects/FBX/DualQuaternion.cpp b/projects/FBX/DualQuaternion.cpp new file mode 100644 index 0000000000..8f7c731f75 --- /dev/null +++ b/projects/FBX/DualQuaternion.cpp @@ -0,0 +1,131 @@ +#include "DualQuaternion.h" +#include +#include + +DualQuaternion operator+(const DualQuaternion& l, const DualQuaternion& r) { + return DualQuaternion(l.real + r.real, l.dual + r.dual); +} + +DualQuaternion operator*(const DualQuaternion& dq, float f) { + return DualQuaternion(dq.real * f, dq.dual * f); +} + +bool operator==(const DualQuaternion& l, const DualQuaternion& r) { + return l.real == r.real && l.dual == r.dual; +} + +bool operator!=(const DualQuaternion& l, const DualQuaternion& r) { + return l.real != r.real || l.dual != r.dual; +} + +// Remember, multiplication order is left to right. +// This is the opposite of matrix and quaternion multiplication order +DualQuaternion operator*(const DualQuaternion& l, const DualQuaternion& r) { + DualQuaternion lhs = normalized(l); + DualQuaternion rhs = normalized(r); +// DualQuaternion lhs = l; +// DualQuaternion rhs = r; + + return DualQuaternion(lhs.real * rhs.real, lhs.real * rhs.dual + lhs.dual * rhs.real); +} + +float dot(const DualQuaternion& l, const DualQuaternion& r) { + return dot(l.real, r.real); +} + +DualQuaternion conjugate(const DualQuaternion& dq) { + return DualQuaternion(conjugate(dq.real), conjugate(dq.dual)); +} + +DualQuaternion normalized(const DualQuaternion& dq) { + float magSq = dot(dq.real, dq.real); + if (magSq < 0.000001f) { + return DualQuaternion(); + } + float invMag = 1.0f / sqrtf(magSq); + + return DualQuaternion(dq.real * invMag, dq.dual * invMag); +} + +void normalize(DualQuaternion& dq) { + float magSq = dot(dq.real, dq.real); + if (magSq < 0.000001f) { + return; + } + float invMag = 1.0f / sqrtf(magSq); + + dq.real = dq.real * invMag; + dq.dual = dq.dual * invMag; +} + +static void decomposeMtx(const glm::mat4& m, glm::vec3& pos, glm::quat& rot, glm::vec3& scale) +{ + pos = m[3]; + for(int i = 0; i < 3; i++) + scale[i] = glm::length(glm::vec3(m[i])); + const glm::mat3 rotMtx( + glm::vec3(m[0]) / scale[0], + glm::vec3(m[1]) / scale[1], + glm::vec3(m[2]) / scale[2]); + rot = glm::quat_cast(rotMtx); +} + +constexpr glm::quat dual_quat(const glm::quat& q,const glm::vec3& t) { + + auto qx = q.x; + auto qy = q.y; + auto qz = q.z; + auto qw = q.w; + auto tx = t[0]; + auto ty = t[1]; + auto tz = t[2]; + + glm::quat qd; + qd.w = -0.5*( tx*qx + ty*qy + tz*qz); // qd.w + qd.x = 0.5*( tx*qw + ty*qz - tz*qy); // qd.x + qd.y = 0.5*(-tx*qz + ty*qw + tz*qx); // qd.y + qd.z = 0.5*( tx*qy - ty*qx + tz*qw); // qd.z + + return qd; +} +DualQuaternion mat4ToDualQuat2(const glm::mat4& transformation) { + glm::vec3 scale; + glm::quat rotation; + glm::vec3 translation; + decomposeMtx(transformation, translation, rotation, scale); + glm::quat qr = rotation; + glm::quat qd = dual_quat(qr, translation); + return DualQuaternion(qr, qd); +} + +glm::mat4 dualQuatToMat4(const DualQuaternion& dq) { + glm::mat4 rotation = glm::toMat4(dq.real); + + glm::quat d = conjugate(dq.real) * (dq.dual * 2.0f); + glm::mat4 position = glm::translate(glm::vec3(d.x, d.y, d.z)); + + glm::mat4 result = position * rotation; + return result; +} + +glm::vec3 transformVector(const DualQuaternion& dq, const glm::vec3& v) { + return dq.real * v; +} + +glm::vec3 transformPoint2(const DualQuaternion& dq, const glm::vec3& v){ + auto d0 = glm::vec3(dq.real.x, dq.real.y, dq.real.z); + auto de = glm::vec3(dq.dual.x, dq.dual.y, dq.dual.z); + auto a0 = dq.real.w; + auto ae = dq.dual.w; + + return v + 2.0f * cross(d0,cross(d0,v) + a0*v) + 2.0f *(a0*de - ae*d0 + cross(d0,de)); +} + + +zeno::vec3f transformVector(const DualQuaternion& dq, const zeno::vec3f& v) { + return zeno::bit_cast(transformVector(dq, zeno::bit_cast(v))); +} + +zeno::vec3f transformPoint2(const DualQuaternion& dq, const zeno::vec3f& v) { + return zeno::bit_cast(transformPoint2(dq, zeno::bit_cast(v))); +} \ No newline at end of file diff --git a/projects/FBX/DualQuaternion.h b/projects/FBX/DualQuaternion.h new file mode 100644 index 0000000000..061a8f5c85 --- /dev/null +++ b/projects/FBX/DualQuaternion.h @@ -0,0 +1,40 @@ +#ifndef _H_DUALQUATERNION_ +#define _H_DUALQUATERNION_ + +#include +#include +#include +#include +#include +#include +#include + +struct DualQuaternion { + glm::quat real = {1, 0, 0, 0}; + glm::quat dual = {0, 0, 0, 0}; + inline DualQuaternion() { } + inline DualQuaternion(const glm::quat& r, const glm::quat& d) : + real(r), dual(d) { } +}; + +DualQuaternion operator+(const DualQuaternion& l, const DualQuaternion& r); +DualQuaternion operator*(const DualQuaternion& dq, float f); +// Multiplication order is left to right +// Left to right is the OPPOSITE of matrices and quaternions +DualQuaternion operator*(const DualQuaternion& l, const DualQuaternion& r); +bool operator==(const DualQuaternion& l, const DualQuaternion& r); +bool operator!=(const DualQuaternion& l, const DualQuaternion& r); + +float dot(const DualQuaternion& l, const DualQuaternion& r); +DualQuaternion conjugate(const DualQuaternion& dq); +DualQuaternion normalized(const DualQuaternion& dq); +void normalize(DualQuaternion& dq); + +DualQuaternion mat4ToDualQuat2(const glm::mat4& t); +glm::mat4 dualQuatToMat4(const DualQuaternion& dq); + +glm::vec3 transformVector(const DualQuaternion& dq, const glm::vec3& v); +zeno::vec3f transformVector(const DualQuaternion& dq, const zeno::vec3f& v); +zeno::vec3f transformPoint2(const DualQuaternion& dq, const zeno::vec3f& v); + +#endif \ No newline at end of file diff --git a/projects/FBX/FBXSDK.cpp b/projects/FBX/FBXSDK.cpp index 9c598b05b6..013118ca88 100644 --- a/projects/FBX/FBXSDK.cpp +++ b/projects/FBX/FBXSDK.cpp @@ -21,6 +21,7 @@ #include #include #include +#include "DualQuaternion.h" #ifdef ZENO_FBXSDK #include @@ -1009,6 +1010,7 @@ struct NewFBXImportAnimation : INode { ud.set2("boneName_count", int(bone_names.size())); for (auto i = 0; i < bone_names.size(); i++) { ud.set2(zeno::format("boneName_{}", i), bone_names[i]); + zeno::log_info("boneName: {}", bone_names[i]); } } @@ -1387,6 +1389,7 @@ struct NewFBXBoneDeform : INode { return mapping; } virtual void apply() override { + auto usingDualQuaternion = get_input2("SkinningMethod") == "DualQuaternion"; auto geometryToDeform = get_input2("GeometryToDeform"); auto geometryToDeformBoneNames = getBoneNames(geometryToDeform.get()); auto restPointTransformsPrim = get_input2("RestPointTransforms"); @@ -1400,6 +1403,8 @@ struct NewFBXBoneDeform : INode { std::vector matrixs; matrixs.reserve(geometryToDeformBoneNames.size()); + std::vector dqs; + dqs.reserve(geometryToDeformBoneNames.size()); for (auto i = 0; i < geometryToDeformBoneNames.size(); i++) { glm::mat4 res_inv_matrix = glm::mat4(1); glm::mat4 deform_matrix = glm::mat4(1); @@ -1409,6 +1414,7 @@ struct NewFBXBoneDeform : INode { } auto matrix = deform_matrix * res_inv_matrix; matrixs.push_back(matrix); + dqs.push_back(mat4ToDualQuat2(matrix)); } auto prim = std::dynamic_pointer_cast(geometryToDeform->clone()); @@ -1425,19 +1431,30 @@ struct NewFBXBoneDeform : INode { for (auto i = 0; i < vert_count; i++) { auto opos = prim->verts[i]; vec3f pos = {}; + DualQuaternion dq_acc({0, 0, 0, 0}, {0, 0, 0, 0}); float w = 0; for (auto j = 0; j < maxnum_boneWeight; j++) { auto index = bi[j]->operator[](i); if (index < 0) { continue; } - auto matrix = matrixs[index]; auto weight = bw[j]->operator[](i); - pos += transform_pos(matrix, opos) * weight; + if (usingDualQuaternion) { + dq_acc = dq_acc + dqs[index] * weight; + } + else { + pos += transform_pos(matrixs[index], opos) * weight; + } w += weight; } if (w > 0) { - prim->verts[i] = pos / w; + if (usingDualQuaternion) { + dq_acc = normalized(dq_acc); + prim->verts[i] = transformPoint2(dq_acc, opos); + } + else { + prim->verts[i] = pos / w; + } } } auto vectors_str = get_input2("vectors"); @@ -1450,6 +1467,7 @@ struct NewFBXBoneDeform : INode { #pragma omp parallel for for (auto i = 0; i < vert_count; i++) { glm::mat4 matrix(0); + DualQuaternion dq_acc({0, 0, 0, 0}, {0, 0, 0, 0}); float w = 0; for (auto j = 0; j < maxnum_boneWeight; j++) { auto index = bi[j]->operator[](i); @@ -1457,12 +1475,25 @@ struct NewFBXBoneDeform : INode { continue; } auto weight = bw[j]->operator[](i); - matrix += matrixs[index] * weight; + if (usingDualQuaternion) { + dq_acc = dq_acc + dqs[index] * weight; + } + else { + matrix += matrixs[index] * weight; + } w += weight; } - matrix = matrix / w; - auto nrm = transform_nrm(matrix, nrms[i]); - nrms[i] = zeno::normalize(nrm); + if (w > 0) { + if (usingDualQuaternion) { + dq_acc = normalized(dq_acc); + nrms[i] = transformVector(dq_acc, nrms[i]); + } + else { + matrix = matrix / w; + nrms[i] = transform_nrm(matrix, nrms[i]); + } + nrms[i] = zeno::normalize(nrms[i]); + } } } if (prim->loops.attr_is(vector)) { @@ -1471,6 +1502,7 @@ struct NewFBXBoneDeform : INode { for (auto i = 0; i < prim->loops.size(); i++) { auto vi = prim->loops[i]; glm::mat4 matrix(0); + DualQuaternion dq_acc({0, 0, 0, 0}, {0, 0, 0, 0}); float w = 0; for (auto j = 0; j < maxnum_boneWeight; j++) { auto index = bi[j]->operator[](vi); @@ -1478,12 +1510,25 @@ struct NewFBXBoneDeform : INode { continue; } auto weight = bw[j]->operator[](vi); - matrix += matrixs[index] * weight; + if (usingDualQuaternion) { + dq_acc = dq_acc + dqs[index] * weight; + } + else { + matrix += matrixs[index] * weight; + } w += weight; } - matrix = matrix / w; - auto nrm = transform_nrm(matrix, nrms[i]); - nrms[i] = zeno::normalize(nrm); + if (w > 0) { + if (usingDualQuaternion) { + dq_acc = normalized(dq_acc); + nrms[i] = transformVector(dq_acc, nrms[i]); + } + else { + matrix = matrix / w; + nrms[i] = transform_nrm(matrix, nrms[i]); + } + nrms[i] = zeno::normalize(nrms[i]); + } } } } @@ -1498,6 +1543,7 @@ ZENDEFNODE(NewFBXBoneDeform, { "GeometryToDeform", "RestPointTransforms", "DeformPointTransforms", + {"enum Linear DualQuaternion", "SkinningMethod", "Linear"}, {"string", "vectors", "nrm,"}, }, {