diff --git a/OpenSim/Simulation/Model/AbstractGeometryPath.cpp b/OpenSim/Simulation/Model/AbstractGeometryPath.cpp index 9aead5eaed..39b9bd0614 100644 --- a/OpenSim/Simulation/Model/AbstractGeometryPath.cpp +++ b/OpenSim/Simulation/Model/AbstractGeometryPath.cpp @@ -26,6 +26,8 @@ #include #include +#include + using namespace OpenSim; //============================================================================= @@ -91,3 +93,64 @@ void AbstractGeometryPath::setPreScaleLength(const SimTK::State&, { _preScaleLength = preScaleLength; } + +void AbstractGeometryPath::forEachDecorativePathPoint( + const SimTK::State& state, + const std::function& callback) const +{ + implForEachDecorativePathPoint(state, callback); +} + +std::vector +AbstractGeometryPath::getDecorativePathPoints(const SimTK::State& state) const +{ + std::vector rv; + forEachDecorativePathPoint(state, + [&rv](const DecorativePathPoint& dp) { rv.push_back(dp); }); + return rv; +} + +void AbstractGeometryPath::generateDecorations(bool fixed, + const ModelDisplayHints& hints, const SimTK::State& s, + SimTK::Array_& geoms) const +{ + if (fixed) { + return; + } + if (not get_Appearance().get_visible()) { + // Don't render a path that's hidden + // (ComputationalBiomechanicsLab/opensim-creator#1166) + return; + } + + const bool showPathPoints = hints.get_show_path_points(); + const SimTK::Vec3 color = getColor(s); + const double opacity = get_Appearance().get_opacity(); + const auto representation = get_Appearance().get_representation(); + + int index = 0; + std::optional previous; + forEachDecorativePathPoint(s, [&](const DecorativePathPoint& dpp) { + ++index; + if (showPathPoints) { + geoms.push_back(SimTK::DecorativeSphere(0.005) + .setTransform(dpp.getLocationInGround()) + .setColor(color) + .setOpacity(0.8) + .setBodyId(0)); + } + + if (previous) { + // Emit line between points. + const SimTK::Vec3& p1 = previous->getLocationInGround(); + const SimTK::Vec3& p2 = dpp.getLocationInGround(); + geoms.push_back(SimTK::DecorativeLine(p1, p2) + .setLineThickness(4) + .setColor(color) + .setBodyId(0) + .setIndexOnBody(index-1)); + } + + previous = dpp; + }); +} diff --git a/OpenSim/Simulation/Model/AbstractGeometryPath.h b/OpenSim/Simulation/Model/AbstractGeometryPath.h index dc6274fb93..62e4e7704a 100644 --- a/OpenSim/Simulation/Model/AbstractGeometryPath.h +++ b/OpenSim/Simulation/Model/AbstractGeometryPath.h @@ -27,6 +27,9 @@ #include #include +#include +#include + #ifdef SWIG #ifdef OSIMSIMULATION_API #undef OSIMSIMULATION_API @@ -229,8 +232,86 @@ OpenSim_DECLARE_ABSTRACT_OBJECT(AbstractGeometryPath, ModelComponent); */ double getPreScaleLength(const SimTK::State& s) const; void setPreScaleLength(const SimTK::State& s, double preScaleLength); - + + /** + * Represents a decorative (i.e. visualization-only) point of an + * `AbstractGeometryPath`. Can be used by UI implementations to + * display the path. + */ + class DecorativePathPoint final { + public: + explicit DecorativePathPoint( + const SimTK::Vec3& locationInGround, + const Component* associatedComponent = nullptr) : + m_locationInGround{locationInGround}, + m_maybeAssociatedComponent{associatedComponent} + {} + + SimTK::Vec3 getLocationInGround() const { return m_locationInGround; } + void setLocationInGround(const SimTK::Vec3& p) { + m_locationInGround = p; + } + const Component* getAssociatedComponent() const { + return m_maybeAssociatedComponent; + } + private: + SimTK::Vec3 m_locationInGround; + const Component* m_maybeAssociatedComponent; + }; + + /** + * Calls `callback` with each point of a decorative representation of the + * path, if such a representation is available. + * + * @param s Current simulation state, used to read the state of this + * path. + * @param callback A callback that should be called with each point of the + * decorative path. + */ + void forEachDecorativePathPoint(const SimTK::State& state, + const std::function& callback) const; + + /** + * Get a vector of decorative path points. + * + * @param s Current simulation state, used to read the state of this path. + */ + std::vector getDecorativePathPoints( + const SimTK::State& s) const; + +protected: + /** + * Generates default decorations for this `AbstractGeometryPath`, based on + * the decorative points emitted by `implForEachDecorativePathPoint`. + * + * Derived classes may override this to provide different display behavior + * for the path. + */ + void generateDecorations(bool fixed, const ModelDisplayHints&, + const SimTK::State&, + SimTK::Array_&) const override; + private: + /** + * Implementors should call `callback` with each point of a decorative + * representation of the path. + * + * The decorative representation does not necessarily need to be the same + * as the computational representation: it only needs to be a "suitable" + * visual representation of the path (if any). + * + * @param s Current simulation state, used to read the state of this + * path. + * @param callback A callback that should be called with each point of the + * decorative path. + */ + virtual void implForEachDecorativePathPoint(const SimTK::State& s, + const std::function& callback) const + { + // No-op: implementations are also permitted to provide no decorative + // point representation at all. + } + // Used by `(get|set)PreLengthScale`. Used during `extend(Pre|Post)Scale` by // downstream users of AbstractGeometryPath to cache the length of the path // before scaling. diff --git a/OpenSim/Simulation/Model/GeometryPath.cpp b/OpenSim/Simulation/Model/GeometryPath.cpp index 2172e09515..5426b52258 100644 --- a/OpenSim/Simulation/Model/GeometryPath.cpp +++ b/OpenSim/Simulation/Model/GeometryPath.cpp @@ -177,6 +177,35 @@ void GeometryPath::extendFinalizeFromProperties() } } +void GeometryPath::implForEachDecorativePathPoint( + const SimTK::State& state, + const std::function& callback) const +{ + const Array& pathPoints = getCurrentPath(state); + + for (int i = 0; i < pathPoints.size(); ++i) { + const AbstractPathPoint& p = *pathPoints[i]; + + if (auto* pwp = dynamic_cast(&p)) { + // A `PathWrapPoint`'s surface points are expressed w.r.t. the wrap + // surface's body frame. Ensure they're transformed to ground. + const SimTK::Transform& X_BG = + pwp->getParentFrame().getTransformInGround(state); + + // A `PathWrapPoint`'s surface points should be emitted, but not + // associated to a component in the model (they are synthetic). + const Array& surfacePoints = pwp->getWrapPath(state); + + for (int j = 0; j < surfacePoints.getSize(); ++j) { + callback(DecorativePathPoint{X_BG * surfacePoints[j]}); + } + } + else { // Otherwise, it's a regular `PathPoint`, so just emit it. + callback(DecorativePathPoint{p.getLocationInGround(state), &p}); + } + } +} + void GeometryPath::extendConnectToModel(Model& aModel) { Super::extendConnectToModel(aModel); @@ -220,82 +249,6 @@ void GeometryPath::extendConnectToModel(Model& aModel) markCacheVariableValid(s, _colorCV); // it is OK at its default value } -//------------------------------------------------------------------------------ -// GENERATE DECORATIONS -//------------------------------------------------------------------------------ -// The GeometryPath takes care of drawing itself here, using information it -// can extract from the supplied state, including position information and -// color information that may have been calculated as late as Stage::Dynamics. -// For example, muscles may want the color to reflect activation level and -// other path-using components might want to use forces (tension). We will -// ensure that the state has been realized to Stage::Dynamics before looking -// at it. (It is only guaranteed to be at Stage::Position here.) -void GeometryPath:: -generateDecorations(bool fixed, const ModelDisplayHints& hints, - const SimTK::State& state, - SimTK::Array_& appendToThis) const -{ - // There is no fixed geometry to generate here. - if (fixed) { return; } - - const Array& pathPoints = getCurrentPath(state); - - OPENSIM_ASSERT_FRMOBJ(pathPoints.size() > 1); - - const AbstractPathPoint* lastPoint = pathPoints[0]; - SimTK::MobilizedBodyIndex mbix(0); - - Vec3 lastPos = lastPoint->getLocationInGround(state); - if (hints.get_show_path_points()) - SimTK::DefaultGeometry::drawPathPoint( - mbix, lastPos, getColor(state), appendToThis); - - Vec3 pos; - - for (int i = 1; i < pathPoints.getSize(); ++i) { - AbstractPathPoint* point = pathPoints[i]; - PathWrapPoint* pwp = dynamic_cast(point); - - if (pwp) { - // A PathWrapPoint provides points on the wrapping surface as Vec3s - const Array& surfacePoints = pwp->getWrapPath(state); - // The surface points are expressed w.r.t. the wrap surface's body frame. - // Transform the surface points into the ground reference frame to draw - // the surface point as the wrapping portion of the GeometryPath - const SimTK::Transform& X_BG = - pwp->getParentFrame().getTransformInGround(state); - // Cycle through each surface point and draw it the Ground frame - for (int j = 0; jgetLocationInGround(state); - if (hints.get_show_path_points()) - SimTK::DefaultGeometry::drawPathPoint( - mbix, pos, getColor(state), appendToThis); - // Line segments will be in ground frame - appendToThis.push_back(SimTK::DecorativeLine(lastPos, pos) - .setLineThickness(4) - .setColor(getColor(state)) - .setBodyId(0) - .setIndexOnBody(i)); - lastPos = pos; - } - } -} - //_____________________________________________________________________________ /* * Connect properties to local pointers. diff --git a/OpenSim/Simulation/Model/GeometryPath.h b/OpenSim/Simulation/Model/GeometryPath.h index 05bbc029e1..7d64693990 100644 --- a/OpenSim/Simulation/Model/GeometryPath.h +++ b/OpenSim/Simulation/Model/GeometryPath.h @@ -193,17 +193,11 @@ OpenSim_DECLARE_CONCRETE_OBJECT(GeometryPath, AbstractGeometryPath); void extendInitStateFromProperties(SimTK::State& s) const override; void extendAddToSystem(SimTK::MultibodySystem& system) const override; - // Visual support GeometryPath drawing in SimTK visualizer. - void generateDecorations( - bool fixed, - const ModelDisplayHints& hints, - const SimTK::State& state, - SimTK::Array_& appendToThis) const - override; - void extendFinalizeFromProperties() override; private: + void implForEachDecorativePathPoint(const SimTK::State&, + const std::function&) const override; void computePath(const SimTK::State& s ) const; void computeLengtheningSpeed(const SimTK::State& s) const; diff --git a/OpenSim/Simulation/Model/Scholz2015GeometryPath.cpp b/OpenSim/Simulation/Model/Scholz2015GeometryPath.cpp index d3c7d84a6b..fd4c9491c5 100644 --- a/OpenSim/Simulation/Model/Scholz2015GeometryPath.cpp +++ b/OpenSim/Simulation/Model/Scholz2015GeometryPath.cpp @@ -28,8 +28,6 @@ #include #include -#include - using namespace OpenSim; //============================================================================= @@ -189,6 +187,15 @@ int Scholz2015GeometryPath::getNumPathElements() const { return getProperty_path_elements().size(); } +void Scholz2015GeometryPath::setNumDecorativeCurvePoints( + int numDecorativeCurvePoints) { + set_num_decorative_curve_points(numDecorativeCurvePoints); +} + +int Scholz2015GeometryPath::getNumDecorativeCurvePoints() const { + return get_num_decorative_curve_points(); +} + //============================================================================= // ABSTRACT PATH INTERFACE //============================================================================= @@ -212,6 +219,16 @@ double Scholz2015GeometryPath::computeMomentArm(const SimTK::State& s, return _maSolver->solve(s, coord, *this); } +void Scholz2015GeometryPath::implForEachDecorativePathPoint( + const SimTK::State& state, + const std::function& callback) const +{ + getCableSpan().calcResampledDecorativePathPoints( + state, get_num_decorative_curve_points(), + [&callback](SimTK::Vec3 p) { callback(DecorativePathPoint{p}); + }); +} + void Scholz2015GeometryPath::produceForces(const SimTK::State& state, double tension, ForceConsumer& forceConsumer) const { @@ -329,47 +346,12 @@ void Scholz2015GeometryPath::extendAddToSystem( _index = cable.getIndex(); } -void Scholz2015GeometryPath::generateDecorations( - bool fixed, - const ModelDisplayHints& hints, - const SimTK::State& s, - SimTK::Array_& geoms) const { - - if (fixed) { return; } - const bool showPathPoints = hints.get_show_path_points(); - const SimTK::Vec3 color = getColor(s); - int index = 0; - std::optional previous; - getCableSpan().calcDecorativePathPoints(s, [&](SimTK::Vec3 x_G) { - if (previous) { - // Emit line between points - geoms.push_back(SimTK::DecorativeLine(*previous, x_G) - .setLineThickness(4) - .setScaleFactors(SimTK::Vec3{1.0}) - .setColor(color) - .setBodyId(0) - .setIndexOnBody(index++) - ); - } - if (showPathPoints) { - geoms.push_back(SimTK::DecorativeSphere(0.005) - .setTransform(x_G) - .setScaleFactors(SimTK::Vec3{1.0}) - .setColor(color) - .setBodyId(0) - .setIndexOnBody(index++) - ); - } - - previous = x_G; - }); -} - //============================================================================= // CONVENIENCE METHODS //============================================================================= void Scholz2015GeometryPath::constructProperties() { constructProperty_path_elements(); + constructProperty_num_decorative_curve_points(5); } const Scholz2015GeometryPathObstacle* Scholz2015GeometryPath::getObstacle( diff --git a/OpenSim/Simulation/Model/Scholz2015GeometryPath.h b/OpenSim/Simulation/Model/Scholz2015GeometryPath.h index 8eda015c90..dae5886b74 100644 --- a/OpenSim/Simulation/Model/Scholz2015GeometryPath.h +++ b/OpenSim/Simulation/Model/Scholz2015GeometryPath.h @@ -421,6 +421,18 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Scholz2015GeometryPath, AbstractGeometryPath); */ int getNumPathElements() const; + /** + * Set the number of decorative path points per curve segment used when + * generating path decorations. + */ + void setNumDecorativeCurvePoints(int numDecorativeCurvePoints); + + /** + * Get the number of decorative path points per curve segment used when + * generating path decorations (default: 5). + */ + int getNumDecorativeCurvePoints() const; + // @} //** @name `AbstractGeometryPath` interface */ @@ -430,6 +442,8 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Scholz2015GeometryPath, AbstractGeometryPath); double computeMomentArm(const SimTK::State& s, const Coordinate& coord) const override; bool isVisualPath() const override { return true; } + void implForEachDecorativePathPoint(const SimTK::State&, + const std::function&) const override; // @} //** @name `ForceProducer` interface */ @@ -442,13 +456,13 @@ OpenSim_DECLARE_CONCRETE_OBJECT(Scholz2015GeometryPath, AbstractGeometryPath); // PROPERTIES OpenSim_DECLARE_LIST_PROPERTY(path_elements, Scholz2015GeometryPathElement, "The list of elements (path points or obstacles) defining the path."); + OpenSim_DECLARE_PROPERTY(num_decorative_curve_points, int, + "The number of decorative path points per curve segment used when " + "generating path decorations (default: 5)."); // MODEL COMPONENT INTERFACE void extendConnectToModel(Model& model) override; void extendAddToSystem(SimTK::MultibodySystem& system) const override; - void generateDecorations(bool fixed, const ModelDisplayHints& hints, - const SimTK::State& s, - SimTK::Array_& geoms) const override; // CONVENIENCE METHODS void constructProperties(); diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 255c845e13..9656c03644 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -185,7 +185,7 @@ AddDependency(NAME ezc3d AddDependency(NAME simbody DEFAULT ON GIT_URL https://github.com/simbody/simbody.git - GIT_TAG 7f35622b3c5daac919fc39a2865498c03c553e53 + GIT_TAG 8a62e27882838708ae98e72c6902704f836aacb9 CMAKE_ARGS -DBUILD_EXAMPLES:BOOL=OFF -DBUILD_TESTING:BOOL=OFF ${SIMBODY_EXTRA_CMAKE_ARGS})