From 34aa09eafc234428523c9ff7252993c62d1f9047 Mon Sep 17 00:00:00 2001 From: Peter Verswyvelen Date: Wed, 18 Nov 2020 18:31:55 +0100 Subject: [PATCH] NEW: Animation curve related changes: - Quaternions are now flipped to give continuous curves - added zero animation threshold arguments - when animation channels are forced, all TRS and weight channels are created now --- .clang-format | 2 +- src/Arguments.cpp | 21 ++++++ src/Arguments.h | 18 ++++- src/ExportableClip.cpp | 29 +++----- src/ExportableClip.h | 3 +- src/ExportableItem.cpp | 3 +- src/ExportableItem.h | 6 +- src/ExportableNode.cpp | 60 ++++++---------- src/ExportableNode.h | 24 ++----- src/NodeAnimation.cpp | 158 +++++++++++++++++++++-------------------- src/NodeAnimation.h | 43 +++++------ src/PropAnimation.h | 53 ++++++++++---- 12 files changed, 219 insertions(+), 201 deletions(-) diff --git a/.clang-format b/.clang-format index 7d6f025..0f068c9 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,4 @@ # We'll use defaults from the LLVM style, but with 4 columns indentation. BasedOnStyle: LLVM IndentWidth: 4 -ColumnLimit: 120 \ No newline at end of file +ColumnLimit: 150 \ No newline at end of file diff --git a/src/Arguments.cpp b/src/Arguments.cpp index ad5b7ef..a30509a 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -69,6 +69,8 @@ const auto forceRootNode = "frn"; const auto forceAnimationChannels = "fac"; +const auto forceAnimationSampling = "fas"; + const auto hashBufferURIs = "hbu"; const auto dumpAccessorComponents = "dac"; @@ -82,6 +84,12 @@ const auto reportSkewedInverseBindMatrices = "rsb"; const auto clearOutputWindow = "cow"; const auto cameras = "cam"; + +const auto constantTranslationThreshold = "ctt"; +const auto constantRotationThreshold = "crt"; +const auto constantScalingThreshold = "cst"; +const auto constantWeightsThreshold = "cwt"; + } // namespace flag inline const char *getArgTypeName(const MSyntax::MArgType argType) { @@ -173,6 +181,8 @@ SyntaxFactory::SyntaxFactory() { registerFlag(ss, flag::bakeScalingFactor, "bakeScalingFactor", kNoArg); registerFlag(ss, flag::forceRootNode, "forceRootNode", kNoArg); registerFlag(ss, flag::forceAnimationChannels, "forceAnimationChannels", kNoArg); + registerFlag(ss, flag::forceAnimationSampling, "forceAnimationSampling", kNoArg); + registerFlag(ss, flag::hashBufferURIs, "hashBufferUri", kNoArg); registerFlag(ss, flag::niceBufferURIs, "niceBufferNames", kNoArg); @@ -182,6 +192,11 @@ SyntaxFactory::SyntaxFactory() { registerFlag(ss, flag::cameras, "cameras", true, kString); + registerFlag(ss, flag::constantTranslationThreshold, "constantTranslationThreshold", kDouble); + registerFlag(ss, flag::constantRotationThreshold, "constantRotationThreshold", kDouble); + registerFlag(ss, flag::constantScalingThreshold, "constantScalingThreshold", kDouble); + registerFlag(ss, flag::constantWeightsThreshold, "constantWeightsThreshold", kDouble); + m_usage = ss.str(); } @@ -473,6 +488,7 @@ Arguments::Arguments(const MArgList &args, const MSyntax &syntax) { bakeScalingFactor = adb.isFlagSet(flag::bakeScalingFactor); forceRootNode = adb.isFlagSet(flag::forceRootNode); forceAnimationChannels = adb.isFlagSet(flag::forceAnimationChannels); + forceAnimationSampling = adb.isFlagSet(flag::forceAnimationSampling); hashBufferURIs = adb.isFlagSet(flag::hashBufferURIs); niceBufferURIs = adb.isFlagSet(flag::niceBufferURIs); convertUnsupportedImages = adb.isFlagSet(flag::convertUnsupportedImages); @@ -481,6 +497,11 @@ Arguments::Arguments(const MArgList &args, const MSyntax &syntax) { adb.optional(flag::globalOpacityFactor, opacityFactor); + adb.optional(flag::constantTranslationThreshold, constantTranslationThreshold); + adb.optional(flag::constantRotationThreshold, constantRotationThreshold); + adb.optional(flag::constantScalingThreshold, constantScalingThreshold); + adb.optional(flag::constantWeightsThreshold, constantWeightsThreshold); + if (!adb.optional(flag::sceneName, sceneName)) { // Use filename without extension of current scene file. MFileIO fileIO; diff --git a/src/Arguments.h b/src/Arguments.h index 46b13d3..9676d5f 100644 --- a/src/Arguments.h +++ b/src/Arguments.h @@ -199,10 +199,12 @@ class Arguments { * which case an extra root node is not always needed? */ bool forceRootNode = false; - /** Force the creation of an animation channel for each node, even if the - * node doesn't contain any animation? */ + /** Force the creation of an animation channel for each node, even if the node doesn't contain any animation? */ bool forceAnimationChannels = false; + /** Force the sampling of an animation channel for each node, even if the node doesn't contain any animation? */ + bool forceAnimationSampling = false; + /** Use a hash of the buffer for its URI? Useful when exporting the same * mesh buffer per animation scene */ bool hashBufferURIs = false; @@ -232,6 +234,18 @@ class Arguments { * too. */ bool visibleNodesOnly = false; + /** Consider a translation animation path as constant if all values are below this threshold */ + double constantTranslationThreshold = 1e-9; + + /** Consider a rotation animation path as constant if all values are below this threshold */ + double constantRotationThreshold = 1e-9; + + /** Consider a scaling animation path as constant if all values are below this threshold */ + double constantScalingThreshold = 1e-9; + + /** Consider a blend shape weight animation path as constant if all values are below this threshold */ + double constantWeightsThreshold = 1e-9; + std::vector animationClips; /** Copyright text of the exported file */ diff --git a/src/ExportableClip.cpp b/src/ExportableClip.cpp index 7bacba1..e0fc953 100644 --- a/src/ExportableClip.cpp +++ b/src/ExportableClip.cpp @@ -5,11 +5,8 @@ #include "progress.h" #include "timeControl.h" -ExportableClip::ExportableClip(const Arguments &args, - const AnimClipArg &clipArg, - const ExportableScene &scene) - : m_frames(args.makeName(clipArg.name + "/anim/frames"), - clipArg.frameCount(), clipArg.framesPerSecond) { +ExportableClip::ExportableClip(const Arguments &args, const AnimClipArg &clipArg, const ExportableScene &scene) + : m_frames(args.makeName(clipArg.name + "/anim/frames"), clipArg.frameCount(), clipArg.framesPerSecond) { glAnimation.name = clipArg.name; const auto frameCount = clipArg.frameCount(); @@ -21,30 +18,25 @@ ExportableClip::ExportableClip(const Arguments &args, for (auto &pair : items) { auto &node = pair.second; - auto nodeAnimation = node->createAnimation(m_frames, scaleFactor); + auto nodeAnimation = node->createAnimation(args, m_frames, scaleFactor); if (nodeAnimation) { m_nodeAnimations.emplace_back(std::move(nodeAnimation)); } } - for (auto relativeFrameIndex = 0; relativeFrameIndex < frameCount; - ++relativeFrameIndex) { + for (auto relativeFrameIndex = 0; relativeFrameIndex < frameCount; ++relativeFrameIndex) { const double relativeFrameTime = m_frames.times.at(relativeFrameIndex); - const MTime absoluteFrameTime = - clipArg.startTime + MTime(relativeFrameTime, MTime::kSeconds); + const MTime absoluteFrameTime = clipArg.startTime + MTime(relativeFrameTime, MTime::kSeconds); setCurrentTime(absoluteFrameTime, args.redrawViewport); NodeTransformCache transformCache; for (auto &nodeAnimation : m_nodeAnimations) { - nodeAnimation->sampleAt(absoluteFrameTime, relativeFrameIndex, - transformCache); + nodeAnimation->sampleAt(absoluteFrameTime, relativeFrameIndex, transformCache); } - if (relativeFrameIndex % checkProgressFrameInterval == - checkProgressFrameInterval - 1) { - uiAdvanceProgress( - "exporting clip '" + clipArg.name + - formatted("' %d%%", relativeFrameIndex * 100 / frameCount)); + if (relativeFrameIndex % checkProgressFrameInterval == checkProgressFrameInterval - 1) { + uiAdvanceProgress("exporting clip '" + clipArg.name + + formatted("' %d%%", relativeFrameIndex * 100 / frameCount)); } } @@ -55,8 +47,7 @@ ExportableClip::ExportableClip(const Arguments &args, ExportableClip::~ExportableClip() = default; -void ExportableClip::getAllAccessors( - std::vector &accessors) const { +void ExportableClip::getAllAccessors(std::vector &accessors) const { m_frames.getAllAccessors(accessors); for (auto &&animation : m_nodeAnimations) { diff --git a/src/ExportableClip.h b/src/ExportableClip.h index a14215d..b1d77bd 100644 --- a/src/ExportableClip.h +++ b/src/ExportableClip.h @@ -6,8 +6,7 @@ class ExportableClip { public: - ExportableClip(const Arguments &args, const AnimClipArg &clipArg, - const ExportableScene &scene); + ExportableClip(const Arguments &args, const AnimClipArg &clipArg, const ExportableScene &scene); virtual ~ExportableClip(); GLTF::Animation glAnimation; diff --git a/src/ExportableItem.cpp b/src/ExportableItem.cpp index 21a67ca..9ece0ea 100644 --- a/src/ExportableItem.cpp +++ b/src/ExportableItem.cpp @@ -6,7 +6,6 @@ ExportableItem::~ExportableItem() = default; std::unique_ptr -ExportableItem::createAnimation(const ExportableFrames &frameTimes, - const double scaleFactor) { +ExportableItem::createAnimation(const Arguments &, const ExportableFrames &, double ) { return nullptr; } diff --git a/src/ExportableItem.h b/src/ExportableItem.h index 853557d..7f417bc 100644 --- a/src/ExportableItem.h +++ b/src/ExportableItem.h @@ -4,14 +4,14 @@ class ExportableFrames; class NodeAnimation; +class Arguments; class ExportableItem { public: virtual ~ExportableItem() = 0; - virtual std::unique_ptr - createAnimation(const ExportableFrames &frameTimes, - const double scaleFactor); + virtual std::unique_ptr createAnimation(const Arguments &args, const ExportableFrames &frameTimes, + double scaleFactor); protected: ExportableItem() = default; diff --git a/src/ExportableNode.cpp b/src/ExportableNode.cpp index 173723c..d79fce9 100644 --- a/src/ExportableNode.cpp +++ b/src/ExportableNode.cpp @@ -9,23 +9,17 @@ #include "NodeAnimation.h" #include "Transform.h" -ExportableNode::ExportableNode(const MDagPath &dagPath) - : ExportableObject(dagPath.node()), dagPath(dagPath) {} +ExportableNode::ExportableNode(const MDagPath &dagPath) : ExportableObject(dagPath.node()), dagPath(dagPath) {} -void ExportableNode::load(ExportableScene &scene, - NodeTransformCache &transformCache) { +void ExportableNode::load(ExportableScene &scene, NodeTransformCache &transformCache) { MStatus status; auto &resources = scene.resources(); auto &args = resources.arguments(); - m_disableNameAssignment = args.disableNameAssignment; - m_forceAnimationChannels = args.forceAnimationChannels; - // Is this a joint with segment scale compensation? (the default in Maya) bool maybeSegmentScaleCompensation = false; - DagHelper::getPlugValue(obj, "segmentScaleCompensate", - maybeSegmentScaleCompensation); + DagHelper::getPlugValue(obj, "segmentScaleCompensate", maybeSegmentScaleCompensation); // Remember scale factor scaleFactor = args.getBakeScaleFactor(); @@ -40,8 +34,7 @@ void ExportableNode::load(ExportableScene &scene, // Deal with segment scale compensation // A root joint never has segment scale compensation, since the parent is // the world. - if (maybeSegmentScaleCompensation && parentNode && - parentNode->obj.hasFn(MFn::kJoint) && + if (maybeSegmentScaleCompensation && parentNode && parentNode->obj.hasFn(MFn::kJoint) && !args.ignoreSegmentScaleCompensation) { transformKind = TransformKind::ComplexJoint; } @@ -56,12 +49,10 @@ void ExportableNode::load(ExportableScene &scene, const auto rotatePivot = fnTransform.rotatePivot(MSpace::kObject); if (scalePivot != rotatePivot) { - MayaException::printError( - formatted( - "Transform '%s' has different scaling and rotation pivots, " - "this is not supported, ignoring scaling pivot!", - dagPath.partialPathName().asChar()), - MStatus::kNotImplemented); + MayaException::printError(formatted("Transform '%s' has different scaling and rotation pivots, " + "this is not supported, ignoring scaling pivot!", + dagPath.partialPathName().asChar()), + MStatus::kNotImplemented); } pivotPoint = rotatePivot; @@ -123,8 +114,7 @@ void ExportableNode::load(ExportableScene &scene, << "' has initial transforms that are not representable by glTF! " "Skewing is not supported, use 3 nodes to simulate this. " "Deviation = " - << std::fixed << std::setprecision(2) - << initialTransformState.maxNonOrthogonality * 100 << "%" << endl; + << std::fixed << std::setprecision(2) << initialTransformState.maxNonOrthogonality * 100 << "%" << endl; } // Create mesh, if any @@ -135,8 +125,7 @@ void ExportableNode::load(ExportableScene &scene, if (status && shapeDagPath.hasFn(MFn::kMesh)) { // The shape is a mesh - m_mesh = - std::make_unique(scene, *this, shapeDagPath); + m_mesh = std::make_unique(scene, *this, shapeDagPath); m_mesh->attachToNode(pNode); } } @@ -148,8 +137,7 @@ void ExportableNode::load(ExportableScene &scene, if (status && shapeDagPath.hasFn(MFn::kCamera)) { // The shape is a camera - m_camera = - std::make_unique(scene, *this, shapeDagPath); + m_camera = std::make_unique(scene, *this, shapeDagPath); m_camera->attachToNode(pNode); } } @@ -158,11 +146,9 @@ void ExportableNode::load(ExportableScene &scene, ExportableNode::~ExportableNode() = default; std::unique_ptr -ExportableNode::createAnimation(const ExportableFrames &frameTimes, - const double scaleFactor) { - return std::make_unique(*this, frameTimes, scaleFactor, - m_disableNameAssignment, - m_forceAnimationChannels); +ExportableNode::createAnimation(const Arguments &args, const ExportableFrames &frameTimes, const double scaleFactor) { + + return std::make_unique(*this, frameTimes, scaleFactor, args); } void ExportableNode::updateNodeTransforms(NodeTransformCache &transformCache) { @@ -175,12 +161,10 @@ void ExportableNode::updateNodeTransforms(NodeTransformCache &transformCache) { // and scale matrices. const auto currentFrameTime = MAnimControl::currentTime(); - cerr << prefix << "WARNING: node '" << name() - << "' has transforms at the current frame " << currentFrameTime + cerr << prefix << "WARNING: node '" << name() << "' has transforms at the current frame " << currentFrameTime << " that are not representable by glTF! Skewing is not " "supported, use 3 nodes to simulate this. Deviation = " - << std::fixed << std::setprecision(2) - << currentTransformState.maxNonOrthogonality * 100 << "%" << endl; + << std::fixed << std::setprecision(2) << currentTransformState.maxNonOrthogonality * 100 << "%" << endl; } } @@ -211,17 +195,14 @@ bool ExportableNode::tryMergeRedundantShapeNode() { return false; auto *trs = static_cast(transform); - if (trs->translation[0] != 0 || trs->translation[1] != 0 || - trs->translation[2] != 0) + if (trs->translation[0] != 0 || trs->translation[1] != 0 || trs->translation[2] != 0) return false; - if (trs->rotation[0] != 0 || trs->rotation[1] != 0 || - trs->rotation[2] != 0 || trs->rotation[3] != 1) + if (trs->rotation[0] != 0 || trs->rotation[1] != 0 || trs->rotation[2] != 0 || trs->rotation[3] != 1) return false; if (trs->scale[0] != 1 || trs->scale[1] != 1 || trs->scale[2] != 1) return false; - cout << prefix << "Shape-only node '" << name() - << "' is redundant, moving its shapes to parent node '" + cout << prefix << "Shape-only node '" << name() << "' is redundant, moving its shapes to parent node '" << parentNode->name() << "'" << endl; glParentNode.children.clear(); @@ -242,8 +223,7 @@ bool ExportableNode::tryMergeRedundantShapeNode() { return true; } -void ExportableNode::getAllAccessors( - std::vector &accessors) const { +void ExportableNode::getAllAccessors(std::vector &accessors) const { if (m_mesh) { m_mesh->getAllAccessors(accessors); } diff --git a/src/ExportableNode.h b/src/ExportableNode.h index 1b1bc52..4fcbb98 100644 --- a/src/ExportableNode.h +++ b/src/ExportableNode.h @@ -43,30 +43,21 @@ class ExportableNode : public ExportableObject { NodeTransformState initialTransformState; NodeTransformState currentTransformState; - std::unique_ptr - createAnimation(const ExportableFrames &frameTimes, - const double scaleFactor) override; + std::unique_ptr createAnimation(const Arguments &args, const ExportableFrames &frameTimes, + double scaleFactor) override; // The primary node to represent the transform // See Transform.h for details GLTF::Node &glPrimaryNode() { return m_glNodes[0]; } - const GLTF::Node &glPrimaryNode() const { - return const_cast(this)->glPrimaryNode(); - } + const GLTF::Node &glPrimaryNode() const { return const_cast(this)->glPrimaryNode(); } // The secondary node to represent the transform // Can be the same as the primary node for simple transforms // See Transform.h for details - GLTF::Node &glSecondaryNode() { - return m_glNodes[transformKind != TransformKind::Simple]; - } - const GLTF::Node &glSecondaryNode() const { - return const_cast(this)->glSecondaryNode(); - } + GLTF::Node &glSecondaryNode() { return m_glNodes[transformKind != TransformKind::Simple]; } + const GLTF::Node &glSecondaryNode() const { return const_cast(this)->glSecondaryNode(); } - MDagPath parentDagPath() const { - return parentNode ? parentNode->dagPath : MDagPath(); - } + MDagPath parentDagPath() const { return parentNode ? parentNode->dagPath : MDagPath(); } // Update the node transforms using the values at the current frame void updateNodeTransforms(NodeTransformCache &transformCache); @@ -88,8 +79,5 @@ class ExportableNode : public ExportableObject { std::unique_ptr m_mesh; std::unique_ptr m_camera; - bool m_disableNameAssignment = false; - bool m_forceAnimationChannels = false; - DISALLOW_COPY_MOVE_ASSIGN(ExportableNode); }; diff --git a/src/NodeAnimation.cpp b/src/NodeAnimation.cpp index d1602e9..7a2e062 100644 --- a/src/NodeAnimation.cpp +++ b/src/NodeAnimation.cpp @@ -1,21 +1,13 @@ #include "externals.h" -#include "Arguments.h" #include "ExportableMesh.h" #include "ExportableNode.h" #include "NodeAnimation.h" #include "OutputStreamsPatch.h" #include "Transform.h" -NodeAnimation::NodeAnimation(const ExportableNode &node, - const ExportableFrames &frames, - const double scaleFactor, - const bool disableNameAssignment, - const bool forceConstantKey) - : node(node), mesh(node.mesh()), m_scaleFactor(scaleFactor), - m_disableNameAssignment(disableNameAssignment), - m_forceChannels(forceConstantKey), - m_blendShapeCount(mesh ? mesh->blendShapeCount() : 0) { +NodeAnimation::NodeAnimation(const ExportableNode &node, const ExportableFrames &frames, const double scaleFactor, const Arguments &arguments) + : node(node), mesh(node.mesh()), m_scaleFactor(scaleFactor), m_blendShapeCount(mesh ? mesh->blendShapeCount() : 0), m_arguments(arguments) { auto &sNode = node.glSecondaryNode(); auto &pNode = node.glPrimaryNode(); @@ -23,33 +15,34 @@ NodeAnimation::NodeAnimation(const ExportableNode &node, switch (node.transformKind) { case TransformKind::Simple: - m_positions = std::make_unique( - frames, pNode, GLTF::Animation::Path::TRANSLATION, 3, false); - m_rotations = std::make_unique( - frames, pNode, GLTF::Animation::Path::ROTATION, 4, false); - m_scales = std::make_unique( - frames, pNode, GLTF::Animation::Path::SCALE, 3, false); + m_positions = std::make_unique(frames, pNode, GLTF::Animation::Path::TRANSLATION, 3, false); + m_rotations = std::make_unique(frames, pNode, GLTF::Animation::Path::ROTATION, 4, false); + m_scales = std::make_unique(frames, pNode, GLTF::Animation::Path::SCALE, 3, false); break; case TransformKind::ComplexJoint: - m_positions = std::make_unique( - frames, sNode, GLTF::Animation::Path::TRANSLATION, 3, false); - m_rotations = std::make_unique( - frames, pNode, GLTF::Animation::Path::ROTATION, 4, false); - m_scales = std::make_unique( - frames, pNode, GLTF::Animation::Path::SCALE, 3, false); - m_correctors = std::make_unique( - frames, sNode, GLTF::Animation::Path::SCALE, 3, false); + m_positions = std::make_unique(frames, sNode, GLTF::Animation::Path::TRANSLATION, 3, false); + m_rotations = std::make_unique(frames, pNode, GLTF::Animation::Path::ROTATION, 4, false); + m_scales = std::make_unique(frames, pNode, GLTF::Animation::Path::SCALE, 3, false); + + m_correctors = std::make_unique(frames, sNode, GLTF::Animation::Path::SCALE, 3, false); + + if (m_arguments.forceAnimationChannels) { + m_dummyProps1 = std::make_unique(frames, pNode, GLTF::Animation::Path::TRANSLATION, 3, false); + m_dummyProps2 = std::make_unique(frames, sNode, GLTF::Animation::Path::ROTATION, 4, false); + } break; case TransformKind::ComplexTransform: - m_positions = std::make_unique( - frames, sNode, GLTF::Animation::Path::TRANSLATION, 3, false); - m_rotations = std::make_unique( - frames, sNode, GLTF::Animation::Path::ROTATION, 4, false); - m_scales = std::make_unique( - frames, sNode, GLTF::Animation::Path::SCALE, 3, false); - m_correctors = std::make_unique( - frames, pNode, GLTF::Animation::Path::TRANSLATION, 3, false); + m_positions = std::make_unique(frames, sNode, GLTF::Animation::Path::TRANSLATION, 3, false); + m_rotations = std::make_unique(frames, sNode, GLTF::Animation::Path::ROTATION, 4, false); + m_scales = std::make_unique(frames, sNode, GLTF::Animation::Path::SCALE, 3, false); + + m_correctors = std::make_unique(frames, pNode, GLTF::Animation::Path::TRANSLATION, 3, false); + + if (m_arguments.forceAnimationChannels) { + m_dummyProps1 = std::make_unique(frames, pNode, GLTF::Animation::Path::SCALE, 3, false); + m_dummyProps2 = std::make_unique(frames, pNode, GLTF::Animation::Path::ROTATION, 4, false); + } break; default: @@ -58,44 +51,50 @@ NodeAnimation::NodeAnimation(const ExportableNode &node, } if (m_blendShapeCount > 0) { - m_weights = std::make_unique( - frames, pNode, GLTF::Animation::Path::WEIGHTS, m_blendShapeCount, - true); + m_weights = std::make_unique(frames, pNode, GLTF::Animation::Path::WEIGHTS, m_blendShapeCount, true); } } -void NodeAnimation::sampleAt(const MTime &absoluteTime, const int frameIndex, - NodeTransformCache &transformCache) { +void NodeAnimation::sampleAt(const MTime &absoluteTime, const int frameIndex, NodeTransformCache &transformCache) { auto &transformState = transformCache.getTransform(&node, m_scaleFactor); auto &pTRS = transformState.primaryTRS(); auto &sTRS = transformState.secondaryTRS(); - if (transformState.maxNonOrthogonality > MAX_NON_ORTHOGONALITY && - m_invalidLocalTransformTimes.size() < - m_invalidLocalTransformTimes.capacity()) { - m_maxNonOrthogonality = - std::max(m_maxNonOrthogonality, transformState.maxNonOrthogonality); + if (transformState.maxNonOrthogonality > MAX_NON_ORTHOGONALITY && m_invalidLocalTransformTimes.size() < m_invalidLocalTransformTimes.capacity()) { + m_maxNonOrthogonality = std::max(m_maxNonOrthogonality, transformState.maxNonOrthogonality); m_invalidLocalTransformTimes.emplace_back(absoluteTime); } switch (node.transformKind) { case TransformKind::Simple: m_positions->append(gsl::make_span(pTRS.translation)); - m_rotations->append(gsl::make_span(pTRS.rotation)); + m_rotations->appendQuaternion(gsl::make_span(pTRS.rotation)); m_scales->append(gsl::make_span(pTRS.scale)); break; case TransformKind::ComplexJoint: m_positions->append(gsl::make_span(sTRS.translation)); - m_rotations->append(gsl::make_span(pTRS.rotation)); + m_rotations->appendQuaternion(gsl::make_span(pTRS.rotation)); m_scales->append(gsl::make_span(pTRS.scale)); + m_correctors->append(gsl::make_span(sTRS.scale)); + + if (m_arguments.forceAnimationChannels) { + m_dummyProps1->append(gsl::make_span(pTRS.translation)); + m_dummyProps2->appendQuaternion(gsl::make_span(sTRS.rotation)); + } break; case TransformKind::ComplexTransform: m_positions->append(gsl::make_span(sTRS.translation)); - m_rotations->append(gsl::make_span(sTRS.rotation)); + m_rotations->appendQuaternion(gsl::make_span(sTRS.rotation)); m_scales->append(gsl::make_span(sTRS.scale)); + m_correctors->append(gsl::make_span(pTRS.translation)); + + if (m_arguments.forceAnimationChannels) { + m_dummyProps1->append(gsl::make_span(pTRS.scale)); + m_dummyProps2->appendQuaternion(gsl::make_span(pTRS.rotation)); + } break; default: @@ -118,8 +117,7 @@ void NodeAnimation::exportTo(GLTF::Animation &glAnimation) { << "' has animated transforms that are not representable by glTF! " "Skewing is not supported, use 3 nodes to simulate this. " "Largest deviation = " - << std::fixed << std::setprecision(2) - << m_maxNonOrthogonality * 100 << "%" << endl; + << std::fixed << std::setprecision(2) << m_maxNonOrthogonality * 100 << "%" << endl; cerr << prefix << "The first invalid transforms were found at times: "; for (auto &time : m_invalidLocalTransformTimes) @@ -133,22 +131,34 @@ void NodeAnimation::exportTo(GLTF::Animation &glAnimation) { switch (node.transformKind) { case TransformKind::Simple: - finish(glAnimation, "T", m_positions, pTRS.translation); - finish(glAnimation, "R", m_rotations, pTRS.rotation); - finish(glAnimation, "S", m_scales, pTRS.scale); + finish(glAnimation, "T", m_positions, m_arguments.constantTranslationThreshold, pTRS.translation); + finish(glAnimation, "R", m_rotations, m_arguments.constantRotationThreshold, pTRS.rotation); + finish(glAnimation, "S", m_scales, m_arguments.constantScalingThreshold, pTRS.scale); break; case TransformKind::ComplexJoint: - finish(glAnimation, "T", m_positions, sTRS.translation); - finish(glAnimation, "R", m_rotations, pTRS.rotation); - finish(glAnimation, "S", m_scales, pTRS.scale); - finish(glAnimation, "C", m_correctors, sTRS.scale); + finish(glAnimation, "T", m_positions, m_arguments.constantTranslationThreshold, sTRS.translation); + finish(glAnimation, "R", m_rotations, m_arguments.constantRotationThreshold, pTRS.rotation); + finish(glAnimation, "S", m_scales, m_arguments.constantScalingThreshold, pTRS.scale); + + finish(glAnimation, "C", m_correctors, m_arguments.constantScalingThreshold, sTRS.scale); + + if (m_arguments.forceAnimationChannels) { + finish(glAnimation, "DT", m_dummyProps1, 0, pTRS.translation); + finish(glAnimation, "DR", m_dummyProps2, 0, sTRS.rotation); + } break; case TransformKind::ComplexTransform: - finish(glAnimation, "T", m_positions, sTRS.translation); - finish(glAnimation, "R", m_rotations, sTRS.rotation); - finish(glAnimation, "S", m_scales, sTRS.scale); - finish(glAnimation, "C", m_correctors, pTRS.translation); + finish(glAnimation, "T", m_positions, m_arguments.constantTranslationThreshold, sTRS.translation); + finish(glAnimation, "R", m_rotations, m_arguments.constantRotationThreshold, sTRS.rotation); + finish(glAnimation, "S", m_scales, m_arguments.constantScalingThreshold, sTRS.scale); + + finish(glAnimation, "C", m_correctors, m_arguments.constantScalingThreshold, pTRS.translation); + + if (m_arguments.forceAnimationChannels) { + finish(glAnimation, "DS", m_dummyProps1, 0, pTRS.scale); + finish(glAnimation, "DR", m_dummyProps2, 0, pTRS.rotation); + } break; default: @@ -159,21 +169,21 @@ void NodeAnimation::exportTo(GLTF::Animation &glAnimation) { if (m_blendShapeCount) { const auto initialWeights = mesh->initialWeights(); assert(initialWeights.size() == m_blendShapeCount); - finish(glAnimation, "W", m_weights, initialWeights); + finish(glAnimation, "W", m_weights, m_arguments.constantWeightsThreshold, initialWeights); } } -void NodeAnimation::getAllAccessors( - std::vector &accessors) const { +void NodeAnimation::getAllAccessors(std::vector &accessors) const { getAllAccessors(m_positions, accessors); getAllAccessors(m_rotations, accessors); getAllAccessors(m_scales, accessors); getAllAccessors(m_correctors, accessors); + getAllAccessors(m_dummyProps1, accessors); + getAllAccessors(m_dummyProps2, accessors); getAllAccessors(m_weights, accessors); } -void NodeAnimation::finish(GLTF::Animation &glAnimation, const char *propName, - std::unique_ptr &animatedProp, +void NodeAnimation::finish(GLTF::Animation &glAnimation, const char *propName, std::unique_ptr &animatedProp, double constantThreshold, const gsl::span &baseValues) const { const auto dimension = animatedProp->dimension; @@ -184,33 +194,25 @@ void NodeAnimation::finish(GLTF::Animation &glAnimation, const char *propName, bool isConstant = true; - for (size_t offset = 0; offset < componentValues.size() && isConstant; - offset += dimension) { + for (size_t offset = 0; offset < componentValues.size() && isConstant; offset += dimension) { for (size_t index = 0; index < dimension && isConstant; ++index) { - isConstant = std::abs(baseValues[index] - - componentValues[offset + index]) < 1e-9; + isConstant = std::abs(baseValues[index] - componentValues[offset + index]) < constantThreshold; } } - if (isConstant && !m_forceChannels) { - // All animation frames are the same as the scene, to need to - // animate the prop. + if (isConstant && !m_arguments.forceAnimationSampling && !m_arguments.forceAnimationChannels) { + // All animation frames are the same as the scene, to need to animate the prop. animatedProp.release(); } else { // TODO: Apply a curve simplifier. - animatedProp->finish(m_disableNameAssignment - ? "" - : node.name() + "/anim/" + - glAnimation.name + "/" + propName, - isConstant); + auto useSingleKey = isConstant && !m_arguments.forceAnimationSampling; + animatedProp->finish(m_arguments.disableNameAssignment ? "" : node.name() + "/anim/" + glAnimation.name + "/" + propName, useSingleKey); glAnimation.channels.push_back(&animatedProp->glChannel); } } } -void NodeAnimation::getAllAccessors( - const std::unique_ptr &animatedProp, - std::vector &accessors) { +void NodeAnimation::getAllAccessors(const std::unique_ptr &animatedProp, std::vector &accessors) { if (animatedProp) { animatedProp->getAllAccessors(accessors); } diff --git a/src/NodeAnimation.h b/src/NodeAnimation.h index 011af8b..fb85c1c 100644 --- a/src/NodeAnimation.h +++ b/src/NodeAnimation.h @@ -2,7 +2,7 @@ #include "ExportableNode.h" #include "PropAnimation.h" -#include "sceneTypes.h" +#include "Arguments.h" class ExportableNode; class ExportableMesh; @@ -10,15 +10,15 @@ class NodeTransformCache; class NodeAnimation { public: - NodeAnimation(const ExportableNode &node, const ExportableFrames &frames, - double scaleFactor, bool disableNameAssignment, - bool forceChannels); + NodeAnimation(const ExportableNode &node, const ExportableFrames &frames, double scaleFactor, const Arguments &args); + + /** Consider a blend shape weight animation path as constant if all values are below this threshold */ + double constantWeightsThreshold = 1e-9; virtual ~NodeAnimation() = default; // Samples values at the current time - void sampleAt(const MTime &absoluteTime, int relativeFrameIndex, - NodeTransformCache &transformCache); + void sampleAt(const MTime &absoluteTime, int relativeFrameIndex, NodeTransformCache &transformCache); void exportTo(GLTF::Animation &glAnimation); @@ -29,9 +29,8 @@ class NodeAnimation { private: const double m_scaleFactor; - const bool m_disableNameAssignment; - const bool m_forceChannels; const size_t m_blendShapeCount; + const Arguments &m_arguments; double m_maxNonOrthogonality = 0; std::vector m_invalidLocalTransformTimes; @@ -39,25 +38,27 @@ class NodeAnimation { std::unique_ptr m_positions; std::unique_ptr m_rotations; std::unique_ptr m_scales; - std::unique_ptr - m_correctors; // Either the inverse parent scales, or pivot offsets + + // Either the inverse parent scales, or pivot offsets. + std::unique_ptr m_correctors; + + // Dummy constant channels for when animation channels are forced. + std::unique_ptr m_dummyProps1; + std::unique_ptr m_dummyProps2; + std::unique_ptr m_weights; - void finish(GLTF::Animation &glAnimation, const char *propName, - std::unique_ptr &animatedProp, - const gsl::span &baseValues) const; + void finish(GLTF::Animation &glAnimation, const char *propName, std::unique_ptr &animatedProp, + double constantThreshold, const gsl::span &baseValues) const; template - void finish(GLTF::Animation &glAnimation, const char *propName, - std::unique_ptr &animatedProp, - const float (&baseValues)[N]) { - finish(glAnimation, propName, animatedProp, - gsl::make_span(&baseValues[0], N)); + void finish(GLTF::Animation &glAnimation, const char *propName, std::unique_ptr &animatedProp, + double constantThreshold, const float (&baseValues)[N]) { + finish(glAnimation, propName, animatedProp, constantThreshold, gsl::make_span(&baseValues[0], N)); } - static void - getAllAccessors(const std::unique_ptr &animatedProp, - std::vector &accessors); + static void getAllAccessors(const std::unique_ptr &animatedProp, + std::vector &accessors); DISALLOW_COPY_MOVE_ASSIGN(NodeAnimation); }; diff --git a/src/PropAnimation.h b/src/PropAnimation.h index 275576d..5de45ec 100644 --- a/src/PropAnimation.h +++ b/src/PropAnimation.h @@ -8,10 +8,8 @@ class ExportableNode; class PropAnimation { public: - PropAnimation(const ExportableFrames &frames, const GLTF::Node &node, - const GLTF::Animation::Path path, const size_t dimension, - const bool useFloatArray, - const char *interpolation = "LINEAR") + PropAnimation(const ExportableFrames &frames, const GLTF::Node &node, const GLTF::Animation::Path path, + const size_t dimension, const bool useFloatArray, const char *interpolation = "LINEAR") : dimension(dimension), useFloatArray(useFloatArray), frames(frames) { componentValuesPerFrame.reserve(frames.count * dimension); @@ -36,10 +34,41 @@ class PropAnimation { GLTF::Animation::Sampler glSampler; GLTF::Animation::Channel::Target glTarget; - template - void append(const gsl::span &components) { - std::copy(components.begin(), components.end(), - std::back_inserter(componentValuesPerFrame)); + template void append(const gsl::span &components) { + std::copy(components.begin(), components.end(), std::back_inserter(componentValuesPerFrame)); + } + + void appendQuaternion(const gsl::span& q) { + const auto index = componentValuesPerFrame.size(); + if (index == 0) { + append(q); + } else { + auto x0 = componentValuesPerFrame[index - 4]; + auto y0 = componentValuesPerFrame[index - 3]; + auto z0 = componentValuesPerFrame[index - 2]; + auto w0 = componentValuesPerFrame[index - 1]; + + auto x1 = q[0]; + auto y1 = q[1]; + auto z1 = q[2]; + auto w1 = q[3]; + + // Check if the negative quaternion is a closer. + auto dp = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) + (z0 - z1) * (z0 - z1) + (w0 - w1) * (w0 - w1); + auto dn = (x0 + x1) * (x0 + x1) + (y0 + y1) * (y0 + y1) + (z0 + z1) * (z0 + z1) + (w0 + w1) * (w0 + w1); + + if (dn < dp) { + x1 = -x1; + y1 = -y1; + z1 = -z1; + w1 = -w1; + } + + componentValuesPerFrame.push_back(x1); + componentValuesPerFrame.push_back(y1); + componentValuesPerFrame.push_back(z1); + componentValuesPerFrame.push_back(w1); + } } void finish(const std::string &name, const bool useSingleKey) { @@ -51,13 +80,7 @@ class PropAnimation { glSampler.input = frames.glInputs(); } - m_outputs = - useFloatArray - ? contiguousChannelAccessor( - name, - reinterpret_span(componentValuesPerFrame), 1) - : contiguousChannelAccessor( - name, span(componentValuesPerFrame), dimension); + m_outputs = contiguousChannelAccessor(name, span(componentValuesPerFrame), useFloatArray ? 1 : dimension); glSampler.output = m_outputs.get();