Skip to content

Commit

Permalink
Refactor ModelGraph helper functions into seperate ModelGraphHelpers …
Browse files Browse the repository at this point in the history
…hpp/cpp files (#802)
  • Loading branch information
adamkewley committed Nov 16, 2023
1 parent 573bbb5 commit 2c16d83
Show file tree
Hide file tree
Showing 4 changed files with 447 additions and 349 deletions.
4 changes: 3 additions & 1 deletion src/OpenSimCreator/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ add_library(OpenSimCreator STATIC
ModelGraph/MeshEl.cpp
ModelGraph/MeshEl.hpp
ModelGraph/ModelGraph.hpp
ModelGraph/ModelGraphHelpers.cpp
ModelGraph/ModelGraphHelpers.hpp
ModelGraph/ModelGraphIDs.cpp
ModelGraph/ModelGraphIDs.hpp
ModelGraph/ModelGraphStrings.hpp
Expand Down Expand Up @@ -252,7 +254,7 @@ add_library(OpenSimCreator STATIC
Utils/TPS3D.hpp
Utils/UndoableModelActions.cpp
Utils/UndoableModelActions.hpp
)
"ModelGraph/ModelGraphHelpers.cpp")

# OpenSimCreatorConfig.hpp
#
Expand Down
329 changes: 329 additions & 0 deletions src/OpenSimCreator/ModelGraph/ModelGraphHelpers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
#include "ModelGraphHelpers.hpp"

#include <OpenSimCreator/ModelGraph/BodyEl.hpp>
#include <OpenSimCreator/ModelGraph/EdgeEl.hpp>
#include <OpenSimCreator/ModelGraph/JointEl.hpp>
#include <OpenSimCreator/ModelGraph/MeshEl.hpp>
#include <OpenSimCreator/ModelGraph/ModelGraph.hpp>
#include <OpenSimCreator/ModelGraph/ModelGraphIDs.hpp>
#include <OpenSimCreator/ModelGraph/SceneEl.hpp>
#include <OpenSimCreator/ModelGraph/StationEl.hpp>

#include <oscar/Maths/MathHelpers.hpp>
#include <oscar/Maths/Transform.hpp>
#include <oscar/Maths/Vec3.hpp>
#include <oscar/Scene/SceneDecorationFlags.hpp>
#include <oscar/Utils/Assertions.hpp>
#include <oscar/Utils/CStringView.hpp>
#include <oscar/Utils/UID.hpp>
#include <oscar/Utils/VariantHelpers.hpp>

#include <algorithm>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <variant>

void osc::SelectOnly(ModelGraph& mg, SceneEl const& e)
{
mg.deSelectAll();
mg.select(e);
}

bool osc::HasSelection(ModelGraph const& mg)
{
return !mg.getSelected().empty();
}

void osc::DeleteSelected(ModelGraph& mg)
{
// copy deletion set to ensure iterator can't be invalidated by deletion
std::unordered_set<UID> selected = mg.getSelected();

for (UID id : selected)
{
mg.deleteElByID(id);
}

mg.deSelectAll();
}

osc::CStringView osc::getLabel(ModelGraph const& mg, UID id)
{
return mg.getElByID(id).getLabel();
}

osc::Transform osc::GetTransform(ModelGraph const& mg, UID id)
{
return mg.getElByID(id).getXForm(mg);
}

osc::Vec3 osc::GetPosition(ModelGraph const& mg, UID id)
{
return mg.getElByID(id).getPos(mg);
}

// returns `true` if `body` participates in any joint in the model graph
bool osc::IsAChildAttachmentInAnyJoint(ModelGraph const& mg, SceneEl const& el)
{
auto const iterable = mg.iter<JointEl>();
return std::any_of(iterable.begin(), iterable.end(), [id = el.getID()](JointEl const& j)
{
return j.getChildID() == id;
});
}

// returns `true` if a Joint is complete b.s.
bool osc::IsGarbageJoint(ModelGraph const& modelGraph, JointEl const& jointEl)
{
if (jointEl.getChildID() == ModelGraphIDs::Ground())
{
return true; // ground cannot be a child in a joint
}

if (jointEl.getParentID() == jointEl.getChildID())
{
return true; // is directly attached to itself
}

if (jointEl.getParentID() != ModelGraphIDs::Ground() && !modelGraph.containsEl<BodyEl>(jointEl.getParentID()))
{
return true; // has a parent ID that's invalid for this model graph
}

if (!modelGraph.containsEl<BodyEl>(jointEl.getChildID()))
{
return true; // has a child ID that's invalid for this model graph
}

return false;
}

// returns `true` if `joint` is indirectly or directly attached to ground via its parent
bool osc::IsJointAttachedToGround(
ModelGraph const& modelGraph,
JointEl const& joint,
std::unordered_set<UID>& previousVisits)
{
OSC_ASSERT_ALWAYS(!IsGarbageJoint(modelGraph, joint));

if (joint.getParentID() == ModelGraphIDs::Ground())
{
return true; // it's directly attached to ground
}

auto const* const parent = modelGraph.tryGetElByID<BodyEl>(joint.getParentID());
if (!parent)
{
return false; // joint's parent is garbage
}

// else: recurse to parent
return IsBodyAttachedToGround(modelGraph, *parent, previousVisits);
}

// returns `true` if `body` is attached to ground
bool osc::IsBodyAttachedToGround(
ModelGraph const& modelGraph,
BodyEl const& body,
std::unordered_set<UID>& previouslyVisitedJoints)
{
bool childInAtLeastOneJoint = false;

for (JointEl const& jointEl : modelGraph.iter<JointEl>())
{
OSC_ASSERT(!IsGarbageJoint(modelGraph, jointEl));

if (jointEl.getChildID() == body.getID())
{
childInAtLeastOneJoint = true;

bool const alreadyVisited = !previouslyVisitedJoints.emplace(jointEl.getID()).second;
if (alreadyVisited)
{
continue; // skip this joint: was previously visited
}

if (IsJointAttachedToGround(modelGraph, jointEl, previouslyVisitedJoints))
{
return true; // recurse
}
}
}

return !childInAtLeastOneJoint;
}

bool osc::GetModelGraphIssues(
ModelGraph const& modelGraph,
std::vector<std::string>& issuesOut)
{
issuesOut.clear();

for (JointEl const& joint : modelGraph.iter<JointEl>())
{
if (IsGarbageJoint(modelGraph, joint))
{
std::stringstream ss;
ss << joint.getLabel() << ": joint is garbage (this is an implementation error)";
throw std::runtime_error{std::move(ss).str()};
}
}

for (BodyEl const& body : modelGraph.iter<BodyEl>())
{
std::unordered_set<UID> previouslyVisitedJoints;
if (!IsBodyAttachedToGround(modelGraph, body, previouslyVisitedJoints))
{
std::stringstream ss;
ss << body.getLabel() << ": body is not attached to ground: it is connected by a joint that, itself, does not connect to ground";
issuesOut.push_back(std::move(ss).str());
}
}

return !issuesOut.empty();
}

// returns a string representing the subheader of a scene element
std::string osc::GetContextMenuSubHeaderText(
ModelGraph const& mg,
SceneEl const& e)
{
std::stringstream ss;
std::visit(Overload
{
[&ss](GroundEl const&)
{
ss << "(scene origin)";
},
[&ss, &mg](MeshEl const& m)
{
ss << '(' << m.getClass().getName() << ", " << m.getPath().filename().string() << ", attached to " << getLabel(mg, m.getParentID()) << ')';
},
[&ss](BodyEl const& b)
{
ss << '(' << b.getClass().getName() << ')';
},
[&ss, &mg](JointEl const& j)
{
ss << '(' << j.getSpecificTypeName() << ", " << getLabel(mg, j.getChildID()) << " --> " << getLabel(mg, j.getParentID()) << ')';
},
[&ss, &mg](StationEl const& s)
{
ss << '(' << s.getClass().getName() << ", attached to " << getLabel(mg, s.getParentID()) << ')';
},
[&ss, &mg](EdgeEl const& e)
{
ss << '(' << e.getClass().getName() << ", " << getLabel(mg, e.getFirstAttachmentID()) << " --> " << getLabel(mg, e.getSecondAttachmentID()) << ')';
}
}, e.toVariant());
return std::move(ss).str();
}

// returns true if the given element (ID) is in the "selection group" of
bool osc::IsInSelectionGroupOf(
ModelGraph const& mg,
UID parent,
UID id)
{
if (id == ModelGraphIDs::Empty() || parent == ModelGraphIDs::Empty())
{
return false;
}

if (id == parent)
{
return true;
}

BodyEl const* bodyEl = nullptr;

if (auto const* be = mg.tryGetElByID<BodyEl>(parent))
{
bodyEl = be;
}
else if (auto const* me = mg.tryGetElByID<MeshEl>(parent))
{
bodyEl = mg.tryGetElByID<BodyEl>(me->getParentID());
}

if (!bodyEl)
{
return false; // parent isn't attached to any body (or isn't a body)
}

if (auto const* be = mg.tryGetElByID<BodyEl>(id))
{
return be->getID() == bodyEl->getID();
}
else if (auto const* me = mg.tryGetElByID<MeshEl>(id))
{
return me->getParentID() == bodyEl->getID();
}
else
{
return false;
}
}

void osc::SelectAnythingGroupedWith(ModelGraph& mg, UID el)
{
ForEachIDInSelectionGroup(mg, el, [&mg](UID other)
{
mg.select(other);
});
}

osc::UID osc::GetStationAttachmentParent(ModelGraph const& mg, SceneEl const& el)
{
return std::visit(Overload
{
[](GroundEl const&) { return ModelGraphIDs::Ground(); },
[&mg](MeshEl const& meshEl) { return mg.containsEl<BodyEl>(meshEl.getParentID()) ? meshEl.getParentID() : ModelGraphIDs::Ground(); },
[](BodyEl const& bodyEl) { return bodyEl.getID(); },
[](JointEl const&) { return ModelGraphIDs::Ground(); },
[](StationEl const&) { return ModelGraphIDs::Ground(); },
[](EdgeEl const&) { return ModelGraphIDs::Ground(); },
}, el.toVariant());
}

void osc::PointAxisTowards(
ModelGraph& mg,
UID id,
int axis,
UID other)
{
Vec3 const choicePos = GetPosition(mg, other);
Transform const sourceXform = Transform{.position = GetPosition(mg, id)};

mg.updElByID(id).setXform(mg, PointAxisTowards(sourceXform, axis, choicePos));
}

// returns recommended rim intensity for an element in the model graph
osc::SceneDecorationFlags osc::computeFlags(
ModelGraph const& mg,
UID id,
UID hoverID)
{
if (id == ModelGraphIDs::Empty())
{
return SceneDecorationFlags::None;
}
else if (mg.isSelected(id))
{
return SceneDecorationFlags::IsSelected;
}
else if (id == hoverID)
{
return SceneDecorationFlags::IsHovered | SceneDecorationFlags::IsChildOfHovered;
}
else if (IsInSelectionGroupOf(mg, hoverID, id))
{
return SceneDecorationFlags::IsChildOfHovered;
}
else
{
return SceneDecorationFlags::None;
}
}
Loading

0 comments on commit 2c16d83

Please sign in to comment.