-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UfbxImporter: Support for animation and skinning #136
Conversation
Codecov ReportBase: 96.97% // Head: 96.78% // Decreases project coverage by
Additional details and impacted files@@ Coverage Diff @@
## master #136 +/- ##
==========================================
- Coverage 96.97% 96.78% -0.19%
==========================================
Files 133 133
Lines 14682 14950 +268
==========================================
+ Hits 14238 14470 +232
- Misses 444 480 +36
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. ☔ View full report at Codecov. |
Managed to implement skinning: demo mp4 It was very straightforward with the new API for vertex joint/weight data. I did run into a couple of issues though: I didn't find a way to report begin/end time for Skinning was broken in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, thank you! Again extremely sorry for a delayed review...
I made some comments but still want to go through the animation keyframe code more thoroughly to fully understand what's going on.
the
Visibility
support is there as kind of a test for a non-transformation property
That's good to have, and it nicely matches the Visibility
field in the scene. I just need to improve the APIs for custom animation targets so they're not that annoying to use, can be named etc.
(Going off on a tangent -- I was thinking about how to make the Visibility
field builtin in a way that it's "visible" (so, true
) by default, and realized that having the inverse, i.e. Hidden
might make more sense from that perspective. But that's probably wildly inconsistent with how it's named everywhere else, right? I also saw glTF has a KHR_nodes_disable
extension proposal, which was originally named KHR_visibility_nodes
, so I suppose they're facing the same issue, heh.)
I didn't find a way to report begin/end time for
AnimationData
I had a feeling that "it should definitely be there" so I checked and apparently it's only in Animation::Player
, but not in the tracks themselves. Strange. It definitely makes sense to have that in the tracks directly, I'll add it (that's I guess a third TODO for me, in addition to the two below).
Skinning was broken in
magnum-player
in some files
I never said it was working correctly there :P A bug like that is completely possible, I was in a time pressure when implementing skinning support in the player and so tested with just a few glTF files and they all looked alright.
But then at some point after I loaded this skinned FBX model with Assimp and was greeted by the following nightmare trigger:
I shook it off as yet another Assimp weirdness and didn't investigate further, but now that's probably the same bug you were experiencing? :D Can you send me the patch you did to make it working? I'll need to go through the glTF spec again to see what exactly I was misunderstanding there, and why none of the models I tried exposed that behavior (fourth TODO for me).
constexpr Animation::Extrapolation extrapolationBefore = Animation::Extrapolation::Constant; | ||
constexpr Animation::Extrapolation extrapolationAfter = Animation::Extrapolation::Constant; | ||
|
||
/* No exposed way to do this parametrically */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I should probably fix this now. I have a TODO from when I was doing skinning support elsewhere and was hitting similar pain points -- the AnimationData
APIs pre-date the MeshData
/ SceneData
by several years and are unnecessarily verbose and hard to use in comparison.
std::size_t valuesSize; | ||
}; | ||
|
||
constexpr AnimationTrackTargetType CustomAnimationTrackTargetVisibility = AnimationTrackTargetType(UnsignedByte(AnimationTrackTargetType::Custom) + 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh, and this I have to fix as well, to have similar utilities as with sceneFieldCustom()
etc.
Thanks for the review and no worries about the delay!
I'm not sure how it's done in other file formats (and actually looking at
I just did a super nasty hack to make skinning look correct in - if(_jointMatrices) _shader
- .setJointMatrices(_jointMatrices)
- .setPerVertexJointCount(_perVertexJointCount, _secondaryPerVertexJointCount);
+ if(_jointMatrices) {
+ _shader
+ .setJointMatrices(_jointMatrices)
+ .setPerVertexJointCount(_perVertexJointCount, _secondaryPerVertexJointCount)
+ .setTransformationMatrix(camera.cameraMatrix())
+ .setNormalMatrix(camera.cameraMatrix().normalMatrix());
+ } |
Thanks for the patch! Hmm, I need to look into this more, then. Still not sure what exactly should I be doing. Just to be sure -- if you load the model I linked above with ufbx, does it behave the same broken way with the unpatched |
f2fae90
to
f955895
Compare
Oh hey that's the same issue I had with the glTF Cesium Man, it also had the actual objects 90° rotated from where the mesh got transformed. (And I "fixed" it by patching the file to add another rotation to everything, heh.) This is starting to make sense to me, just not fully yet. The Assimp behavior is then probably mainly just an Assimp bug, this additional rotation is a minuscule issue compared to what it does with the legs and eyes and aaargh. |
Very possible if the node containing the mesh itself is transformed (not the skeleton), which would cause the same issue.
Yea I'm curious how it got to look so off :D Now that I think about it, as the issue with skinning is that for correct skinning the matrices should plug together like (using Sebastian Sylvan's neat matrix naming scheme, albeit with the standard non-D3D "right-to-left" multiplication order): // (bone xform) (inverse bind) (mesh vertex)
worldVertex = boneToWorld * geometryToBone * geometryVertex But what the player is doing was something like this, where we end up transforming a point already in world space by the // (node xform) (bone xform) (inverse bind) (mesh vertex)
worldVertex = nodeToWorld * boneToWorld * geometryToBone * geometryVertex Anyways long way to say that this bug can in the worst case only transform the whole skinned mesh (as it did with your glTF and the ufbx unpatched cases) as the erroneous matrix is composed last. In other news got the test coverage back to 100%, still need to go over with a cleanup pass and crunch through my dataset when I got time, so aside from some latent bugs and stylistic issues should be ready for reviewing! I can incorporate some fixes still if you wanted to refactor |
Hi, extremely sorry for a massive delay again. I finally managed to resolve the TODOs on my side which were blocking this PR:
And finally,
there actually is a way, the The PR looks fine to me in its current state, so what's left is just adapting to the Magnum-side changes listed above, and possibly upstreaming the ufbx changes in case you don't have them in your repo yet. The only remaining thing I don't exactly know where to put (and how to test) would be the custom track duration. I completely understand if you don't have time for this PR anymore -- I'm happy to take over in that case. Let me know, thank you and my apologies! |
No worries at all about the delay, I figured that you wanted to have this done right instead of hacked together with how the animation representation was at the time! I can adapt to the Magnum changes and clean up this PR in probably a bit over a week or so, and I'll probably include some new changes from ufbx as well (currently writing documentation and changing the public API whenever I find myself having to explain why something is weird).
Nice, and it seems to even be a range so I can have the start time there as well, perfect! Also seems like my GitHub notifications are bugged as I didn't get any info about your reply (and looks like I've got a couple of issues in repos I wasn't aware of) so I might not reply at my usual speed until I get that sorted out :D |
ufbx did not handle exact keyframe times correctly for constant values, hacked a temporary fix to the embedded ufbx.c
Previously animation layer weight was not considered for keyframe placement, now it is.
This makes ufbx ignore negative and zero weights
|
||
Containers::ArrayView<Float> times = timeData.sliceSize(animTrack.timeOffset, animTrack.keyCount); | ||
Containers::ArrayView<char> valueBytes = valueData.sliceSize(animTrack.valueOffset, animTrack.valuesSize); | ||
Containers::StridedArrayView1D<const void> stridedValues{valueBytes, times.size(), animTrack.valueElementSize}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Containers::StridedArrayView1D<const void> stridedValues{valueBytes, times.size(), animTrack.valueElementSize}; | |
Containers::StridedArrayView1D<const void> stridedValues{valueBytes, times.size(), std::ptrdiff_t(animTrack.valueElementSize)}; |
There's a single signed-unsigned conversion warning on GCC, this should fix it.
|
||
/* @todo: Could detect tracks that have constant interpolation for every keyframe? */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Such operation would belong in some processing tool, I don't think the importer plugin should deal with that. It's also easier to test that way, and reuse it outside of FBX files.
Gets rid of a whole bunch of manual alignment and fiddling
Containers::ArrayView<Float> times = timeData.sliceSize(animTrack.timeOffset, animTrack.keyCount); | ||
Containers::ArrayView<char> valueBytes = valueData.sliceSize(animTrack.valueOffset, animTrack.valuesSize); | ||
Containers::StridedArrayView1D<const void> stridedValues{valueBytes, times.size(), animTrack.valueElementSize}; | ||
Containers::StridedArrayView1D<const void> values{animTrack.values.asContiguous(), animTrack.values.size()[0], animTrack.values.stride()[0]}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's probably a nicer way to do this but this is the best I could come up with
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting from type-erased StridedArrayView2D<char>
to Containers::StridedArrayView1D<const void>
that is
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be animTrack.values.transposed<0, 1>()[0]
(where the [0]
doesn't get the first element bytes anymore, but rather first bytes of all elements), and this is a StridedArrayView1D<char>
which should be implicitly convertible to the void variant.
But yes, this part is a bit shitty, unfortunately given how unconventional the API is I don't really have from where to steal (and I refuse to look at std::mdspan
for inspiration).
What could probably solve this better is a AnimationTrackData
constructor that takes a StridedArrayView2D<const char>
. Other *Data
APIs have this and it's useful, not sure why I didn't add that here as well in the original round of updates. Will do so now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be StridedArrayView2D<const char>
? 1D would be individually spaced out bytes without any idea about element size or am I confused? And yes the API is really novel and I like it :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, 2D, updated the message, sorry.
I'm answering 15 threads at once and can't keep my thoughts straight 🙈
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed my mind after seeing how many constructors there already is -- don't really want to basically double their count again 😅 So transposed<0, 1>()[0]
it is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to work flawlessly, nice!
Ouch, can't repro the LeakSanitizer check locally on Linux, so gonna do a few cursed commits.. |
I was checking it in the CI and have no idea what could it be. That thing usually reports what went wrong, but this time it seems to just crash itself. It's building on an ancient system, so it's possibly you hit some nasty bug in LSan itself. Feel free to ignore that problem, I can look into that myself after. CircleCI has a nice option to rerun with SSH which is much better for iteration times -- not sure if you can do that from your end, but I can. |
Alright great, I'll stop my desperate attempts at bisecting then :D |
Managed to repro the LSan crash -- it happens only if running through This seems to be related and the suggested Maybe you can also reproduce this locally now -- just need to run through I'm investigating further through SSH... |
I have no idea what's going on. This is the change that makes the test pass without LSan crashing: diff --git a/src/MagnumPlugins/UfbxImporter/Test/UfbxImporterTest.cpp b/src/MagnumPlugins/UfbxImporter/Test/UfbxImporterTest.cpp
index 3ca685b4..7476a51c 100644
--- a/src/MagnumPlugins/UfbxImporter/Test/UfbxImporterTest.cpp
+++ b/src/MagnumPlugins/UfbxImporter/Test/UfbxImporterTest.cpp
@@ -307,7 +307,9 @@ UfbxImporterTest::UfbxImporterTest() {
&UfbxImporterTest::textureTransform,
&UfbxImporterTest::textureWrapModes,
+#if 0
&UfbxImporterTest::imageEmbedded,
+#endif
&UfbxImporterTest::imageExternal,
&UfbxImporterTest::imageExternalFromData,
&UfbxImporterTest::imageFileCallback, As far as I see there's nothing in this PR that would even remotely touch image-related code, and a build that I made few hours ago off TLS is used for thread-safe access to plugin managers and debug output redirection, but seriously I've never had a problem with that interacting with LSan until now. I guess I'd have to read the whole thread on the sanitizers repo to get an idea what's happening. If we don't figure this out soon, to avoid wasting too much time the solution here would be to do a special case on the CI and run UfbxImporter tests separately with |
resampled by default to preserve the original motion, this can be disabled | ||
via the | ||
@cb{.ini} resampleRotation @ce @ref Trade-UfbxImporter-configuration "configuration option" | ||
- Morph targets are not supported |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume morph targets are not supported on Magnum yet? I at least couldn't find any way of representing them from UfbxImporter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, not supported, the interfaces are not ready for that yet. It's on my near-term TODO list tho.
Hey, sorry for the accidental silence here -- seeing the "documentation draft" commit I assumed you'd be pushing some additional commits after but you didn't, and looking at it it doesn't really seem "drafty" at all :) Which means this PR is (mostly) ready, right? I definitely don't want to delay it any further by any additional requests, I already caused enough of that. As I see it, there's this left:
Again, absolutely no pressure here, just want to make sure you weren't waiting for some action from my side. |
Ah yes no worries, I still wanted to do a quick pass over everything. I was busy wrapping up some other things at work, gonna clean this up for merge now! |
Ah and just to clarify, I've now gone over it so feel free to review/cleanup/merge, only the lingering CI issue remains :D |
I squashed the commit series a bit and merged as e40a9ca...70f5644. The only bit I didn't include was f8571a5, I have a bunch of these "weird uncovered lines" in various other places as well and simply refuse to work around gcov / lcov bugs :D They'll be gone eventually, either after a lcov update or after a compiler update. I did just very minor whitespace shuffling on top -- your code was mostly perfect to begin with. The FBX model I attempted to load in Assimp isn't cursed with ufbx anymore, and everything is great ✨ Cheers, and thanks a lot for your work! |
Hi, I'm back at the grind!
This PR currently adds support for animations, I'll add skinning in the same PR if that's fine by you as it's quite related.
Some background information on FBX animation to explain what's going on:
resampleRotation
constantInterpolationDuration
for step duration/epsilonanimationLayers
to retain layers but all layer metadata (such as blend modes/weights) is lostminimumSampleRate
prevents additional resampling when the keyframes are dense enough* (one of the rotation orders is called
SPHERIC
which might imply some sort of quaternion interpolation, but I haven't been able to produce a file using that)Classic disclaimer about everything being very much work-in-progress but I wanted to open the PR in case you have differing ideas to the solutions I described above. Also, don't worry about the embedded ufbx diverging here, I'm going to merge that to the main repository after feeling out the changes (or remove it in case it turns out to be a bad idea, though I've been wanting to add those defines for ages).
Edit: Oh and the
Visibility
support is there as kind of a test for a non-transformation property, it does exist in some files and is sometimes used but I don't mind removing that either.