Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bfcfe5901 | |||
| ab728c7e9a |
@@ -45,3 +45,7 @@ compile_commands.json
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
Testing/
|
||||
|
||||
# runtime files
|
||||
imgui.ini
|
||||
opensim.log
|
||||
|
||||
@@ -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_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.
|
||||
+27
-18
@@ -44,9 +44,8 @@ Color4 toColor(const OpenSim::Appearance& appearance) {
|
||||
}
|
||||
|
||||
Range3D scaledBounds(const Range3D& bounds, const Vector3& scale) {
|
||||
bool hasBounds = false;
|
||||
Range3D out;
|
||||
out.min() = Vector3{std::numeric_limits<Float>::max()};
|
||||
out.max() = Vector3{-std::numeric_limits<Float>::max()};
|
||||
for(const Vector3& corner: {
|
||||
Vector3{bounds.min().x(), bounds.min().y(), bounds.min().z()},
|
||||
Vector3{bounds.min().x(), bounds.min().y(), bounds.max().z()},
|
||||
@@ -57,15 +56,17 @@ Range3D scaledBounds(const Range3D& bounds, const Vector3& scale) {
|
||||
Vector3{bounds.max().x(), bounds.max().y(), bounds.min().z()},
|
||||
Vector3{bounds.max().x(), bounds.max().y(), bounds.max().z()},
|
||||
}) {
|
||||
out = Math::join(out, Range3D::fromSize(corner*scale, {}));
|
||||
const Range3D point = Range3D::fromSize(corner*scale, {});
|
||||
if(hasBounds) out = Math::join(out, point);
|
||||
else out = point;
|
||||
hasBounds = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Range3D transformBounds(const Range3D& bounds, const Matrix4& transform) {
|
||||
bool hasBounds = false;
|
||||
Range3D out;
|
||||
out.min() = Vector3{std::numeric_limits<Float>::max()};
|
||||
out.max() = Vector3{-std::numeric_limits<Float>::max()};
|
||||
for(const Vector3& corner: {
|
||||
Vector3{bounds.min().x(), bounds.min().y(), bounds.min().z()},
|
||||
Vector3{bounds.min().x(), bounds.min().y(), bounds.max().z()},
|
||||
@@ -76,7 +77,10 @@ Range3D transformBounds(const Range3D& bounds, const Matrix4& transform) {
|
||||
Vector3{bounds.max().x(), bounds.max().y(), bounds.min().z()},
|
||||
Vector3{bounds.max().x(), bounds.max().y(), bounds.max().z()},
|
||||
}) {
|
||||
out = Math::join(out, Range3D::fromSize(transform.transformPoint(corner), {}));
|
||||
const Range3D point = Range3D::fromSize(transform.transformPoint(corner), {});
|
||||
if(hasBounds) out = Math::join(out, point);
|
||||
else out = point;
|
||||
hasBounds = true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -114,15 +118,17 @@ std::shared_ptr<CpuMesh> buildCpuMesh(const SimTK::PolygonalMesh& mesh) {
|
||||
}
|
||||
}
|
||||
|
||||
bool hasBounds = false;
|
||||
Range3D bounds;
|
||||
bounds.min() = Vector3{std::numeric_limits<Float>::max()};
|
||||
bounds.max() = Vector3{-std::numeric_limits<Float>::max()};
|
||||
|
||||
cpu->vertices.reserve(positions.size());
|
||||
for(std::size_t i = 0; i != positions.size(); ++i) {
|
||||
const Vector3 normal = normals[i].isZero() ? Vector3::yAxis() : normals[i].normalized();
|
||||
cpu->vertices.push_back({positions[i], normal});
|
||||
bounds = Math::join(bounds, Range3D::fromSize(positions[i], {}));
|
||||
const Range3D point = Range3D::fromSize(positions[i], {});
|
||||
if(hasBounds) bounds = Math::join(bounds, point);
|
||||
else bounds = point;
|
||||
hasBounds = true;
|
||||
}
|
||||
cpu->bounds = bounds;
|
||||
return cpu;
|
||||
@@ -302,8 +308,7 @@ LoadedScene loadScene(const std::string& modelPath,
|
||||
}
|
||||
for(MarkerTrack& marker: scene.markers) marker.positions.reserve(trajectory.getSize());
|
||||
|
||||
scene.initialBounds.min() = Vector3{std::numeric_limits<Float>::max()};
|
||||
scene.initialBounds.max() = Vector3{-std::numeric_limits<Float>::max()};
|
||||
bool hasSceneBounds = false;
|
||||
|
||||
for(std::size_t sample = 0; sample != trajectory.getSize(); ++sample) {
|
||||
const SimTK::State& state = trajectory[sample];
|
||||
@@ -317,19 +322,23 @@ LoadedScene loadScene(const std::string& modelPath,
|
||||
scene.frames[i].translations.push_back(translation);
|
||||
scene.frames[i].rotations.push_back(rotation);
|
||||
|
||||
if(sample == 0) {
|
||||
const Matrix4 world = frameMatrix(translation, rotation);
|
||||
for(const GeometrySpec& geometry: scene.frames[i].geometries) {
|
||||
const Range3D scaled = scaledBounds(geometry.mesh->bounds, geometry.scale);
|
||||
scene.initialBounds = Math::join(scene.initialBounds, transformBounds(scaled, world));
|
||||
}
|
||||
const Matrix4 world = frameMatrix(translation, rotation);
|
||||
for(const GeometrySpec& geometry: scene.frames[i].geometries) {
|
||||
const Range3D scaled = scaledBounds(geometry.mesh->bounds, geometry.scale);
|
||||
const Range3D worldBounds = transformBounds(scaled, world);
|
||||
if(hasSceneBounds) scene.initialBounds = Math::join(scene.initialBounds, worldBounds);
|
||||
else scene.initialBounds = worldBounds;
|
||||
hasSceneBounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
for(std::size_t i = 0; i != markerPtrs.size(); ++i) {
|
||||
const Vector3 position = toVector3(markerPtrs[i]->getLocationInGround(state));
|
||||
scene.markers[i].positions.push_back(position);
|
||||
if(sample == 0) scene.initialBounds = Math::join(scene.initialBounds, Range3D::fromSize(position, {}));
|
||||
const Range3D point = Range3D::fromSize(position, {});
|
||||
if(hasSceneBounds) scene.initialBounds = Math::join(scene.initialBounds, point);
|
||||
else scene.initialBounds = point;
|
||||
hasSceneBounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -343,7 +343,10 @@ void ViewerApp::updateSceneAtCurrentTime() {
|
||||
|
||||
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 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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user