How to Create Custom Shapes and Collision Geometries in DART: A Complete Guide
To create custom shapes and collision geometries in DART, derive a class from dart::dynamics::Shape, implement the pure virtual interface including computeInertia(), clone(), and bounding box updates, then attach it to a BodyNode via a ShapeNode with CollisionAspect for physics simulation.
DART (Dynamic Animation and Robotics Toolkit) provides a flexible geometry system through the dart::dynamics namespace that allows developers to extend the simulator with arbitrary collision geometries. Whether you need a custom convex hull for collision detection or a novel visual primitive, understanding how to create custom shapes and collision geometries in DART is essential for advanced robotics simulation. The architecture separates visual representation from collision representation, enabling you to mix and match aspects as needed.
Understanding DART's Shape Architecture
DART represents all geometry through the dart::dynamics::Shape class hierarchy defined in dart/dynamics/shape.hpp. This abstract base class declares the contract that every geometry must satisfy, including type identification, inertia calculation, and spatial bounds.
The key components work together as follows:
dart::dynamics::Shape– The abstract base inshape.hppthat defines pure virtual methods forgetType(),computeInertia(),clone(),updateBoundingBox(), andupdateVolume().- Concrete implementations – Classes like
SphereShapeandBoxShapeindart/dynamics/provide reference implementations of the interface. dart::dynamics::ShapeNode– Defined inshape_node.cpp, this class binds aShapeto aBodyNodeand manages aspects:VisualAspectfor rendering,CollisionAspectfor physics, andDynamicsAspectfor mass properties.- FCL integration – The FCL collision detector converts shapes into BVH models using utilities in
dart/collision/fcl/collision_shapes.hpp.
Implementing a Custom Shape Class
To create custom shapes and collision geometries in DART, you must subclass Shape and implement five critical virtual methods. These methods enable DART to compute physical properties, perform broad-phase collision culling, and clone skeletons.
Required Virtual Methods
Every custom shape must override:
getType()– Returns astd::string_viewidentifier for debugging and serialization.computeInertia(double mass)– Returns anEigen::Matrix3drepresenting the inertia tensor for the given mass.clone()– Returns astd::shared_ptr<Shape>deep copy for DART's skeleton cloning system.updateBoundingBox()– PopulatesmBoundingBoxwith axis-aligned bounds in the local frame.updateVolume()– Computes and caches the volume inmVolume.
Complete Capped Cylinder Example
Below is a minimal implementation of a capped cylinder, demonstrating how to satisfy the Shape contract:
// MyCappedCylinder.hpp
#pragma once
#include <dart/dynamics/Shape.hpp>
#include <Eigen/Dense>
class MyCappedCylinder : public dart::dynamics::Shape
{
public:
MyCappedCylinder(double radius, double height)
: Shape(SHAPE_TYPE::CYLINDER), // reuse existing type enum
mRadius(radius), mHeight(height) {}
std::string_view getType() const override {
return "MyCappedCylinder";
}
Eigen::Matrix3d computeInertia(double mass) const override
{
// Solid cylinder inertia (simplified, caps omitted)
double Ixx = 0.25 * mass * mRadius * mRadius
+ (1.0 / 12.0) * mass * mHeight * mHeight;
double Iyy = Ixx;
double Izz = 0.5 * mass * mRadius * mRadius;
Eigen::Matrix3d I = Eigen::Matrix3d::Zero();
I(0,0) = Ixx; I(1,1) = Iyy; I(2,2) = Izz;
return I;
}
dart::dynamics::ShapePtr clone() const override
{
return std::make_shared<MyCappedCylinder>(mRadius, mHeight);
}
protected:
void updateBoundingBox() const override
{
mBoundingBox.setMin(Eigen::Vector3d(-mRadius, -mRadius, -mHeight/2));
mBoundingBox.setMax(Eigen::Vector3d( mRadius, mRadius, mHeight/2));
mIsBoundingBoxDirty = false;
}
void updateVolume() const override
{
mVolume = M_PI * mRadius * mRadius * mHeight;
mIsVolumeDirty = false;
}
private:
double mRadius, mHeight;
};
The constructor initializes the base class with a ShapeType enum value. You can reuse an existing type like CYLINDER or add a new entry to shape.hpp if your geometry requires distinct handling.
Attaching Custom Shapes to Body Nodes
Once implemented, attach your custom shape to a skeleton using BodyNode::createShapeNodeWith<Aspects...>. This template method instantiates a ShapeNode and attaches the specified aspects in a single call.
#include <dart/dart.hpp>
#include "MyCappedCylinder.hpp"
int main()
{
// Create skeleton and body
auto skel = dart::dynamics::Skeleton::create("robot");
auto body = skel->createBodyNode();
// Instantiate custom shape
auto myShape = std::make_shared<MyCappedCylinder>(0.3, 1.2);
// Create ShapeNode with visual, collision, and dynamics aspects
auto shapeNode = body->createShapeNodeWith<
dart::dynamics::VisualAspect,
dart::dynamics::CollisionAspect,
dart::dynamics::DynamicsAspect>(myShape);
// Configure visual properties
shapeNode->getVisualAspect()->setColor({0.8, 0.2, 0.2, 1.0});
return 0;
}
By including CollisionAspect, you enable physics simulation. DART automatically converts the shape for the FCL collision detector using the generic mesh pathway defined in dart/collision/fcl/collision_shapes.hpp.
Providing Custom Collision Geometries for FCL
When your visual geometry does not map cleanly to primitives, you can supply a custom FCL BVH model directly. This is necessary for complex convex hulls or procedurally generated collision meshes.
Building FCL BVH Models
The utilities in collision_shapes.hpp demonstrate how to construct fcl::BVHModel instances programmatically. Adapt this pattern for custom convex hulls:
#include <dart/collision/fcl/collision_shapes.hpp>
#include <fcl/geometry/bvh/bvh_model.h>
std::shared_ptr<::fcl::CollisionGeometryd> createCustomConvexHull(
const std::vector<Eigen::Vector3d>& points,
const dart::math::Isometry3d& transform)
{
using BV = ::fcl::OBBRSSd; // Bounding volume type
auto model = std::make_shared<::fcl::BVHModel<BV>>();
model->beginModel();
// Convert points and add triangles
for (size_t i = 0; i + 2 < points.size(); i += 3)
{
auto fclTransform = dart::collision::fcl::toFclTransform(transform);
::fcl::Vector3d p1 = fclTransform * ::fcl::Vector3d(points[i][0], points[i][1], points[i][2]);
::fcl::Vector3d p2 = fclTransform * ::fcl::Vector3d(points[i+1][0], points[i+1][1], points[i+1][2]);
::fcl::Vector3d p3 = fclTransform * ::fcl::Vector3d(points[i+2][0], points[i+2][1], points[i+2][2]);
model->addTriangle(p1, p2, p3);
}
model->endModel();
return model;
}
This follows the same implementation pattern as createCube() and createEllipsoid() in the DART source.
Integrating with CollisionAspect
To use your custom FCL geometry, wrap it in a collision object and attach it to a body:
auto customCollision = std::make_shared<dart::collision::FCLCollisionObject>(
createCustomConvexHull(myPoints, dart::math::Isometry3d::Identity()));
body->createShapeNodeWith<dart::dynamics::CollisionAspect>(customCollision);
This approach bypasses the automatic shape-to-mesh conversion, giving you full control over the collision representation while maintaining compatibility with DART's simulation pipeline.
Summary
- Subclass
dart::dynamics::Shapeand implementgetType(),computeInertia(),clone(),updateBoundingBox(), andupdateVolume()to define custom geometry. - Use
BodyNode::createShapeNodeWith<...>to attach shapes withVisualAspect,CollisionAspect, andDynamicsAspectas needed. - Leverage
dart/collision/fcl/collision_shapes.hppfor reference implementations of FCL BVH model creation when building custom collision geometries. - Provide
clone()implementations that returnstd::shared_ptr<Shape>to ensure skeleton copying works correctly throughout the framework.
Frequently Asked Questions
Do I need to modify DART's source code to add a custom shape?
No. You can subclass dart::dynamics::Shape in your own project headers without modifying dartsim/dart. Simply include <dart/dynamics/Shape.hpp> and link against DART. The dynamic nature of the Shape hierarchy allows runtime registration of your custom types.
How does DART convert shapes for collision detection?
By default, DART uses the FCL collision detector. When you add a CollisionAspect to a ShapeNode, DART attempts to convert the shape into an FCL BVH model using converters in dart/collision/fcl/collision_shapes.hpp. For standard types like spheres and boxes, it uses optimized FCL primitives. For meshes, it builds a BVH tree from the triangle data.
Can I use a custom collision geometry without a visual representation?
Yes. Create a ShapeNode with only CollisionAspect specified in the template parameters: body->createShapeNodeWith<dart::dynamics::CollisionAspect>(myCollisionShape). This creates an invisible collision geometry that participates in physics simulation but renders nothing, useful for simplified collision proxies.
What is the difference between Shape and ShapeNode in DART?
A Shape represents the geometric data itself—vertices, inertia, and bounding volume. A ShapeNode is the scene graph attachment point that associates a Shape with a specific BodyNode and manages aspects (visual, collision, dynamics). Multiple ShapeNode instances can share the same Shape instance, allowing efficient memory use when identical geometries appear multiple times in a skeleton.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →