ENH: Entity-Component Model
This introduces the beginnings of an ECS in iMSTK. Note: Stems of GeometryClone branch, merge that to remove some diffs.
Resolves: #455 (closed) #384 (closed) #366 (closed) #255 (closed)
Partially Resolves: https://gitlab.kitware.com/iMSTK/iMSTK/-/issues/397
Entity-Component-System
-
Entity
was introduced which is just an object with a set ofComponent
s, name, and id. -
Component
was introduced which represents functionality on an object. Also has a name. It does not implement any logic. Systems do. - A system is abstract and can be anything that has a reference to the components of an entity and does function. In this way a
Renderer
,Scene
, CollisionHandler, etc are all systems. -
Behaviour
was introduced which givesupdate
andvisualUpdate
virtual functions to a Component for an easy way to implement a single component system.
Implementation
-
Entity
is now the base class toSceneObject
. Usages around imstk were replaced with it instead (for instanceScene
takesEntity
now). The inheritance tree (PbdObject
,RbdObject
, etc) were kept to avoid breaking many parts of imstk (also its more work, these can slowly change later). - Initialization was reworked to support dependent components (also a bit cleaner now).
- A helper function to generate example default components was added.
Components Added
ObjectControllerGhost
Give it a controller and pop it on an object. It will display a transparent clone of that object at the physical location of the device. This iss done in many examples, now generalized.
Previously we did:
// Setup a debug ghost tool for virtual coupling
auto ghostToolObj = std::make_shared<SceneObject>("ghostTool");
auto toolMesh = std::dynamic_pointer_cast<SurfaceMesh>(toolObj->getVisualGeometry());
auto toolGhostMesh = std::make_shared<SurfaceMesh>();
toolGhostMesh->initialize(
std::make_shared<VecDataArray<double, 3>>(*toolMesh->getVertexPositions(Geometry::DataType::PreTransform)),
std::make_shared<VecDataArray<int, 3>>(*toolMesh->getCells()));
ghostToolObj->setVisualGeometry(toolGhostMesh);
ghostToolObj->getVisualModel(0)->getRenderMaterial()->setColor(Color::Orange);
ghostToolObj->getVisualModel(0)->getRenderMaterial()->setLineWidth(5.0);
ghostToolObj->getVisualModel(0)->getRenderMaterial()->setOpacity(0.3);
ghostToolObj->getVisualModel(0)->getRenderMaterial()->setIsDynamicMesh(false);
scene->addSceneObject(ghostToolObj);
// Update the ghost debug geometry
connect<Event>(viewer, &SceneManager::preUpdate,
[&](Event*)
{
std::shared_ptr<Geometry> toolGhostMesh = ghostToolObj->getVisualGeometry();
toolGhostMesh->setRotation(controller->getOrientation());
toolGhostMesh->setTranslation(controller->getPosition());
toolGhostMesh->updatePostTransformData();
toolGhostMesh->postModified();
});
Now all that's needed is:
auto ghostTool = toolObj->addComponent<ObjectControllerGhost>();
ghostTool->setController(myPbdControlller); // Or rigid controller
Additionally it can exist on the tool rather than as a separate object.
Needle & Puncturable
Implements the data part of needles. Eliminates all NeedleObject's in examples. Still no function done in these, just contains needle states. StraightNeedle and ArcNeedle stem from Needle for now. This would be setup like so:
auto needle = needleObj->addComponent<StraightNeedle>();
needle->setLineGeometry(...);
...
tissueObj->addComponent<Puncturable>();
These were designed to support:
- Puncturing more than one entity
- Puncturing more than 'thing' (like a face id) on an entity (via a secondary/support id).
- Inserted, touching, and removed states. Though touching isn't required in all scenarios.
Additionally puncturable is not needed in many scenarios. However, from experience, it allows the tissueObj to have a sense of state without needing the needle. Meaning if you only have tissueObj, you could tell if it was punctured. By coincidence of nice design this also allows puncturing to be used in other contexts besides needles.
ControllerForceText
Displays virtual coupling forces as text via TextVisualModel. Instead of some 100 lines it can be done re-usably like so:
auto vcText = needle->addComponent<VirtualCouplingText>();
vcText->setController(controller);
Debug & Example Defaults
A set of debug & example functionalities were moved or added as components. All of these are added in the examples like so:
// Add default mouse and keyboard controls to the viewer
std::shared_ptr<Entity> mouseAndKeyControls =
SimulationUtils::createDefaultSceneControlEntity(driver);
scene->addSceneObject(mouseAndKeyControls);
Usage of VTKTextStatusManager was eliminated and replaced with the more general TextVisualModel's.
KeyboardSceneControl & MouseSceneControl
Controls were refactored to be behaviours.
FpsTxtCounter
The FpsTxtCounter moves the implementation of the display of the counter (not the actual counting itself) to its own component. This can now be reused or even omitted entirely. The user can it if they choose, but its provided in a set of defaults provided in the imstk examples. The user can also grab it and tweak font size, etc. It reuses TextVisualModel
PerformanceGraph
Updates the performance graph defined in the VTKRenderer. Only one is ever supported in a scene due to its use of the internals of VTKRenderer. Eventually this should move to a more general graph component with RenderDelegate.
SceneControlText
As before gives a giant text that indicates the state of the simulation. But now its independent of the KeyboardSceneControl and MouseSceneControl. So you don't need these control scheme's to use this. It is provided by default in examples via: createDefaultSceneControlEntity.
AxesObject
Also used in debug. But now reuseable elsewhere such as in the SDFHaptics example which was loading its own axes.
DebugGeometryObject -> DebugGeometryModel, CollisionDataDebugObject -> CollisionDataDebugModel
Object moved to component. Renamed as model is more in line with our other VisualModel, AxesModel, TextVisualModel components.