fix: track full-motion scene bounds and ignore runtime files

Accumulate scene bounds from actual geometry and marker samples across the full trajectory instead of seeding Range3D with sentinel extrema and only joining against the first sample.

Why this loader change is necessary:

- Viewer camera framing and reference sizing derive from scene.initialBounds.

- The old code only incorporated sample==0 geometry and marker positions, so motions that travel away from the initial pose could be framed incorrectly.

- The old sentinel initialization depended on joining with artificial min/max values instead of real bounds.

What changed:

- Use first-real-bound initialization in scaledBounds(), transformBounds(), and buildCpuMesh().

- Accumulate scene.initialBounds across all trajectory samples in src/OpenSimLoader.cpp.

- Ignore generated runtime files imgui.ini and opensim.log in .gitignore.

Validation:

- Rebuilt successfully with: cmake --build build -j
This commit is contained in:
2026-03-11 11:56:25 +08:00
parent ab728c7e9a
commit 0bfcfe5901
2 changed files with 31 additions and 18 deletions
+4
View File
@@ -45,3 +45,7 @@ compile_commands.json
Makefile
cmake_install.cmake
Testing/
# runtime files
imgui.ini
opensim.log
+27 -18
View File
@@ -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;
}
}