fix: use shortest-path rotation interpolation in playback
Switch viewer playback from Magnum Math::slerp() to Math::slerpShortestPath() when interpolating adjacent OpenSim frame orientations. Why: - Adjacent OpenSim quaternions can cross sign while representing nearly identical orientations. - Non-shortest-path interpolation can create artificial long-arc spins between valid sampled poses. - That makes playback exaggerate or invent visible bone flips that are not present in the sampled frame states. What changed: - Updated playback interpolation in src/ViewerApp.cpp to use shortest-path quaternion slerp. - Added docs/motion-troubleshooting.md documenting the distinction between viewer interpolation artifacts and upstream IK discontinuities. - Added a README pointer to the troubleshooting note. Investigation log: - Verified the viewer loads .mot through OpenSim state storage and renders PhysicalFrame transforms directly. - Reproduced the target Sports2D/Pose2Sim/OpenSim clip and confirmed the .mot already contains large coordinate discontinuities and limit clamping, indicating upstream IK failure. - Confirmed the viewer also had a separate interpolation issue due to non-shortest-path quaternion slerp. Validation: - Rebuilt with: cmake --build build -j - Relaunched the viewer successfully against the problematic .osim/.mot pair after the fix.
This commit is contained in:
@@ -31,3 +31,7 @@ Sample run:
|
|||||||
/home/crosstyan/Code/opensim-core/OpenSim/Moco/Test/walk_gait1018_subject01.osim \
|
/home/crosstyan/Code/opensim-core/OpenSim/Moco/Test/walk_gait1018_subject01.osim \
|
||||||
/home/crosstyan/Code/opensim-core/OpenSim/Moco/Test/walk_gait1018_state_reference.mot
|
/home/crosstyan/Code/opensim-core/OpenSim/Moco/Test/walk_gait1018_state_reference.mot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Motion troubleshooting notes: [`docs/motion-troubleshooting.md`](docs/motion-troubleshooting.md)
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Motion Troubleshooting
|
||||||
|
|
||||||
|
## Bone Flips During Playback
|
||||||
|
|
||||||
|
The viewer loads the `.mot` file through OpenSim, converts it to model states,
|
||||||
|
and renders each body using `PhysicalFrame::getTransformInGround()`. It does
|
||||||
|
not solve IK, rebuild bones from markers, or apply any custom joint logic.
|
||||||
|
|
||||||
|
Because of that, a visible flip can come from two different places:
|
||||||
|
|
||||||
|
1. The upstream motion data already contains a discontinuity.
|
||||||
|
2. Playback interpolation between two valid sampled poses introduces an
|
||||||
|
artificial long-arc rotation.
|
||||||
|
|
||||||
|
## Viewer Behavior
|
||||||
|
|
||||||
|
Playback now uses shortest-path quaternion interpolation between sampled OpenSim
|
||||||
|
poses. This avoids false in-between spins when adjacent quaternions have
|
||||||
|
opposite signs but represent nearly the same orientation.
|
||||||
|
|
||||||
|
If a flip is still visible after this fix, the sampled OpenSim motion itself is
|
||||||
|
already discontinuous.
|
||||||
|
|
||||||
|
## Upstream IK Failures
|
||||||
|
|
||||||
|
For Sports2D / Pose2Sim / OpenSim pipelines, broken IK usually shows up as one
|
||||||
|
or more of the following:
|
||||||
|
|
||||||
|
- sudden large frame-to-frame jumps in joint coordinates
|
||||||
|
- joint values snapping to model limits
|
||||||
|
- marker RMS staying low while anatomically implausible body orientations appear
|
||||||
|
- left/right ambiguity or bad depth reconstruction in the source TRC
|
||||||
|
|
||||||
|
Sports2D ultimately delegates IK generation to Pose2Sim, which then calls
|
||||||
|
`opensim.InverseKinematicsTool(...)`. If the generated `_ik.mot` already
|
||||||
|
contains large discontinuities, the viewer is only displaying that failure.
|
||||||
|
|
||||||
|
## Practical Check
|
||||||
|
|
||||||
|
To separate viewer issues from upstream IK issues:
|
||||||
|
|
||||||
|
1. Load the `.osim` and `.mot` in the viewer.
|
||||||
|
2. Scrub near the suspicious frame while paused.
|
||||||
|
3. If the sampled pose itself is wrong, the IK output is broken upstream.
|
||||||
|
4. If only the in-between motion looks wrong, interpolation is the likely cause.
|
||||||
+4
-1
@@ -343,7 +343,10 @@ void ViewerApp::updateSceneAtCurrentTime() {
|
|||||||
|
|
||||||
for(std::size_t i = 0; i != _frames.size(); ++i) {
|
for(std::size_t i = 0; i != _frames.size(); ++i) {
|
||||||
const Vector3 translation = Math::lerp(_sceneData.frames[i].translations[sample], _sceneData.frames[i].translations[next], alpha);
|
const Vector3 translation = Math::lerp(_sceneData.frames[i].translations[sample], _sceneData.frames[i].translations[next], alpha);
|
||||||
const Quaternion rotation = Math::slerp(_sceneData.frames[i].rotations[sample], _sceneData.frames[i].rotations[next], alpha).normalized();
|
const Quaternion rotation = Math::slerpShortestPath(
|
||||||
|
_sceneData.frames[i].rotations[sample],
|
||||||
|
_sceneData.frames[i].rotations[next],
|
||||||
|
alpha).normalized();
|
||||||
_frames[i].object->setTransformation(makeFrameMatrix(translation, rotation));
|
_frames[i].object->setTransformation(makeFrameMatrix(translation, rotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user