Skip to content

Commit

Permalink
Add more high-level tests for new ModelWarperConfiguration (#894 #891 #…
Browse files Browse the repository at this point in the history
  • Loading branch information
adamkewley committed Jul 2, 2024
1 parent 3cde6f1 commit 68eda8e
Show file tree
Hide file tree
Showing 13 changed files with 346 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,50 @@
#include "ModelWarperConfiguration.h"

#include <OpenSim/Common/Component.h>
#include <oscar/Utils/HashHelpers.h>

#include <filesystem>
#include <sstream>
#include <string_view>
#include <unordered_set>
#include <utility>

osc::mow::ModelWarperConfiguration::ModelWarperConfiguration()
{
constructProperties();
constructProperties();
}

osc::mow::ModelWarperConfiguration::ModelWarperConfiguration(const std::filesystem::path& filePath) :
OpenSim::Component{filePath.string()}
OpenSim::Component{filePath.string()}
{
constructProperties();
updateFromXMLDocument();
constructProperties();
updateFromXMLDocument();
}

void osc::mow::ModelWarperConfiguration::constructProperties()
{}

void osc::mow::ModelWarperConfiguration::extendFinalizeFromProperties()
{
// TODO
if (getProperty_components().empty()) {
return; // BODGE: the OpenSim API throws if you call `getComponentList` on an
}

// note: it's ok to have the same `StrategyTarget` if the `ComponentStrategy` applies
// to a different type of component
//
// (e.g. if a station warper targets "*", that will capture different components from
// a offset frame warper that targets "*")
using SetElement = std::pair<const std::type_info*, std::string_view>;
std::unordered_set<SetElement, Hasher<SetElement>> allStrategyTargets;
for (const auto& warpingStrategy : getComponentList<ComponentWarpingStrategy>()) {
const std::type_info& targetType = warpingStrategy.getTargetComponentTypeInfo();
for (int i = 0; i < warpingStrategy.getProperty_StrategyTargets().size(); ++i) {
if (not allStrategyTargets.emplace(&targetType, warpingStrategy.get_StrategyTargets(i)).second) {
std::stringstream ss;
ss << warpingStrategy.get_StrategyTargets(i) << ": duplicate strategy target detected in '" << warpingStrategy.getName() << "': this will confuse the engine and should be resolved";
OPENSIM_THROW_FRMOBJ(OpenSim::Exception, std::move(ss).str());
}
}
}
}
81 changes: 62 additions & 19 deletions src/OpenSimCreator/Documents/ModelWarper/ModelWarperConfiguration.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,87 @@
#pragma once

#include <OpenSim/Common/Component.h>
#include <OpenSim/Simulation/Model/PhysicalOffsetFrame.h>
#include <OpenSim/Simulation/Model/Station.h>

#include <concepts>
#include <filesystem>
#include <sstream>
#include <string_view>
#include <typeinfo>
#include <unordered_set>
#include <utility>

namespace osc::mow
{
// abstract interface to a component that is capable of warping `n` other
// components (`StrategyTargets`) during a model warp
class ComponentWarpingStrategy : public OpenSim::Component {
OpenSim_DECLARE_ABSTRACT_OBJECT(ComponentWarpingStrategy, OpenSim::Component);
public:
OpenSim_DECLARE_LIST_PROPERTY(StrategyTargets, std::string, "a sequence of strategy target strings that this strategy applies to");
protected:
ComponentWarpingStrategy() = default;
ComponentWarpingStrategy(const std::type_info& targetComponentType) :
_targetComponentType{&targetComponentType}
{
constructProperty_StrategyTargets();
}
ComponentWarpingStrategy(const ComponentWarpingStrategy&) = default;
ComponentWarpingStrategy(ComponentWarpingStrategy&&) noexcept = default;
ComponentWarpingStrategy& operator=(const ComponentWarpingStrategy&) = default;
ComponentWarpingStrategy& operator=(ComponentWarpingStrategy&&) noexcept = default;
public:
~ComponentWarpingStrategy() noexcept override = default;

// StrategyTargets
const std::type_info& getTargetComponentTypeInfo() const
{
return *_targetComponentType;
}
private:
void extendFinalizeFromProperties() override
{
assertStrategyTargetsNotEmpty();
assertStrategyTargetsAreUnique();
}

void assertStrategyTargetsNotEmpty()
{
if (getProperty_StrategyTargets().empty()) {
OPENSIM_THROW_FRMOBJ(OpenSim::Exception, "The <StrategyTargets> property of this component must be populated with at least one entry");
}
}

void assertStrategyTargetsAreUnique()
{
const int numStrategyTargets = getProperty_StrategyTargets().size();
std::unordered_set<std::string_view> uniqueStrategyTargets;
uniqueStrategyTargets.reserve(numStrategyTargets);
for (int i = 0; i < numStrategyTargets; ++i) {
const std::string& strategyTarget = get_StrategyTargets(i);
const auto [_, inserted] = uniqueStrategyTargets.emplace(strategyTarget);
if (not inserted) {
std::stringstream ss;
ss << strategyTarget << ": duplicate strategy target detected: all strategy targets must be unique";
OPENSIM_THROW_FRMOBJ(OpenSim::Exception, std::move(ss).str());
}
}
}

const std::type_info* _targetComponentType;
};

// abstract interface to a component that is capable of warping `n` other
// components (`StrategyTargets`) of type `T` during a model warp
template<std::derived_from<OpenSim::Component> T>
class ComponentWarpingStrategyFor : public ComponentWarpingStrategy {
public:
ComponentWarpingStrategyFor() : ComponentWarpingStrategy{typeid(T)} {}
};

// abstract interface to a component that is capable of warping `n`
// `OpenSim::PhysicalOffsetFrame`s during a model warp
class OffsetFrameWarpingStrategy : public ComponentWarpingStrategy {
OpenSim_DECLARE_ABSTRACT_OBJECT(OffsetFrameWarpingStrategy, ComponentWarpingStrategy);
class OffsetFrameWarpingStrategy : public ComponentWarpingStrategyFor<OpenSim::PhysicalOffsetFrame> {
OpenSim_DECLARE_ABSTRACT_OBJECT(OffsetFrameWarpingStrategy, ComponentWarpingStrategyFor<OpenSim::PhysicalOffsetFrame>);
};

// concrete implementation of an `OffsetFrameWarpingStrategy` in which
Expand Down Expand Up @@ -55,7 +110,7 @@ namespace osc::mow

// abstract interface to a component that is capable of warping `n`
// `OpenSim::Station`s during a model warp
class StationWarpingStrategy : public ComponentWarpingStrategy {
class StationWarpingStrategy : public ComponentWarpingStrategyFor<OpenSim::Station> {
OpenSim_DECLARE_ABSTRACT_OBJECT(StationWarpingStrategy, ComponentWarpingStrategy);
};

Expand All @@ -78,19 +133,6 @@ namespace osc::mow
OpenSim_DECLARE_CONCRETE_OBJECT(IdentityStationWarpingStrategy, StationWarpingStrategy);
};

// TODO:
// MuscleParameterWarpingStrategies
// MuscleParameterWarpingStrategy
// some_scaling_param
// IdentityMuscleParameterWarpingStrategy
//
// MeshWarpingStrategies
// ThinPlateSplineMeshWarpingStrategy
//
// WrapSurfaceWarpingStrategies
// WrapSurfaceWarpingStrategy
// LeastSquaresProjectionWrapSurfaceWarpingStrategy?

// top-level model warping configuration file
class ModelWarperConfiguration final : public OpenSim::Component {
OpenSim_DECLARE_CONCRETE_OBJECT(ModelWarperConfiguration, OpenSim::Component);
Expand All @@ -103,5 +145,6 @@ namespace osc::mow
explicit ModelWarperConfiguration(const std::filesystem::path& filePath);
private:
void constructProperties();
void extendFinalizeFromProperties() override;
};
}
}
17 changes: 17 additions & 0 deletions src/oscar/Utils/HashHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cstddef>
#include <functional>
#include <ranges>
#include <utility>

namespace osc
{
Expand Down Expand Up @@ -36,4 +37,20 @@ namespace osc
}
return rv;
}

// an osc-specific hashing object
//
// think of it as a `std::hash` that's used specifically in situations where
// specializing `std::hash` might be a bad idea (e.g. on `std` library types
// templated on other `std` library types, where there's a nonzero chance the
template<typename Key>
struct Hasher;

template<typename T1, typename T2>
struct Hasher<std::pair<T1, T2>> final {
size_t operator()(const std::pair<T1, T2>& p) const
{
return hash_of(p.first, p.second);
}
};
}
Loading

0 comments on commit 68eda8e

Please sign in to comment.