FCL vs Bullet vs ODE Collision Backends in DART: Architecture and Usage Guide
DART implements three distinct collision backends—FCL (default), Bullet, and ODE—each wrapping different physics libraries with varying trade-offs in speed, dependencies, and feature support, selectable at runtime via the CollisionDetector factory pattern.
DART (Dynamic Animation and Robotics Toolkit) abstracts collision detection through a Strategy Pattern defined in dart::collision::CollisionDetector, enabling developers to swap between FCL, Bullet, and ODE backends without changing application logic. This article examines the architectural differences, API capabilities, and configuration options for each backend based on the dartsim/dart source code.
Architecture and Feature Comparison
DART selects collision backends by instantiating concrete implementations of the abstract CollisionDetector base class. Each backend provides its own CollisionGroup implementation and shapes are converted into backend-specific representations.
FCL (Flexible Collision Library)
FCL serves as the default collision backend in DART, implemented in dart/collision/fcl/fcl_collision_detector.hpp. It utilizes FCL’s AABB tree for fast broad-phase pruning and supports both analytic primitive tests and mesh-based collision detection.
The FCL backend offers unique configurability through two enum-controlled settings. First, PrimitiveShape (lines 55-58) determines whether shapes are treated as analytic primitives or converted to meshes:
enum PrimitiveShape { PRIMITIVE = 0, MESH };
void setPrimitiveShapeType(PrimitiveShape type);
In PRIMITIVE mode (default), the detector performs analytic tests for spheres, boxes, capsules, and cylinders. In MESH mode, primitives are approximated as thin meshes to work around known FCL bugs in certain shape-pair interactions.
Second, the ContactPointComputationMethod (lines 59-64) controls contact generation:
enum ContactPointComputationMethod { FCL = 0, DART };
void setContactPointComputationMethod(ContactPointComputationMethod method);
The default DART method recomputes contact points internally because FCL’s native algorithm has documented inaccuracies (see FCL issue #106). The FCL method delegates directly to the library for users who prefer native behavior.
FCL provides full support for distance queries and ray-casting, forwarding these calls directly to FCL’s underlying APIs.
Bullet Physics Backend
The Bullet backend, defined in dart/collision/bullet/bullet_collision_detector.hpp, wraps Bullet’s btCollisionWorld and provides a robust, battle-tested narrow-phase collision pipeline. Unlike FCL, Bullet does not offer a primitive/mesh mode switch—shapes are always represented as native Bullet collision primitives (btCollisionShape), with automatic mesh fallback handled internally by the Bullet engine.
Contact point computation is fully delegated to Bullet; DART does not intervene in the generation or filtering of contact manifolds. This backend implements distance queries and ray-casting via btCollisionWorld::contactTest and btCollisionWorld::rayTest respectively.
Bullet involves a heavier external dependency and typically incurs higher memory overhead than FCL, but provides superior robustness for complex mesh interactions.
ODE (Open Dynamics Engine) Backend
The ODE backend, implemented in dart/collision/ode/ode_collision_detector.hpp, offers a lightweight alternative with minimal dependencies. It wraps ODE’s dGeomID objects directly and maintains a contact-history cache per shape pair (lines 70-74) using a ContactHistoryItem struct to stabilize resting contacts—a feature similar to Bullet’s manifold persistence.
However, the ODE backend has significant limitations. Distance queries are not implemented (the distance() method is a stub marked with @warning Not implemented yet). Ray-casting exists in ODE but is not exposed through DART’s API. Shape support is restricted to spheres, boxes, capsules, cylinders, planes, and triangle meshes—soft-body and height-field shapes are unavailable.
Performance and Dependency Considerations
| Backend | Build Dependency | Runtime Characteristics | Ideal Use Case |
|---|---|---|---|
| FCL | libfcl (lightweight) |
Low-to-moderate overhead; fast AABB broad-phase; fast analytic narrow-phase | Default choice requiring configurable primitive handling and full feature support |
| Bullet | Full Bullet Physics (heavyweight) | Higher memory and CPU usage; robust narrow-phase for complex geometry | Projects already depending on Bullet or requiring robust mesh-mesh collision |
| ODE | ODE (medium) |
Lowest memory footprint; limited API surface | Lightweight deployments or projects integrating ODE dynamics with simple collision needs |
Configuring Collision Backends in Code
Select a backend by invoking the static create() factory method and installing the detector on the world:
#include <dart/dart.hpp>
int main()
{
auto world = dart::simulation::World::create();
// FCL (default) with custom configuration
auto fclDetector = dart::collision::FCLCollisionDetector::create();
fclDetector->setPrimitiveShapeType(
dart::collision::FCLCollisionDetector::MESH);
fclDetector->setContactPointComputationMethod(
dart::collision::FCLCollisionDetector::DART);
world->setCollisionDetector(fclDetector);
// Alternative: Bullet backend
// auto bulletDetector = dart::collision::BulletCollisionDetector::create();
// world->setCollisionDetector(bulletDetector);
// Alternative: ODE backend
// auto odeDetector = dart::collision::OdeCollisionDetector::create();
// world->setCollisionDetector(odeDetector);
}
The detector can be switched at any time by calling world->setCollisionDetector() with a new instance.
Executing Backend-Agnostic Collision Queries
Once configured, collision detection operates identically regardless of the active backend:
dart::collision::CollisionOption option(/*enableContact=*/true);
dart::collision::CollisionResult result;
// Works with FCL, Bullet, or ODE
world->getConstraintSolver()->collide(option, &result);
for (std::size_t i = 0; i < result.getNumContacts(); ++i) {
const auto& contact = result.getContact(i);
std::cout << "Contact: " << contact.bodyNode1->getName()
<< " vs " << contact.bodyNode2->getName()
<< " at " << contact.point.transpose() << "\n";
}
This abstraction allows you to compare backend performance or work around specific bugs without modifying query logic.
Summary
- FCL provides the best balance of speed and configurability, offering primitive/mesh modes and switchable contact computation, making it the default for most DART applications.
- Bullet delivers the most robust narrow-phase collision detection for complex meshes at the cost of heavier dependencies and resource usage.
- ODE minimizes external dependencies and memory overhead but lacks distance queries and full shape support, suitable for lightweight simulations or ODE-specific pipelines.
Choose the backend that aligns with your project’s dependency constraints, geometric complexity, and required query types (collision, distance, or ray-casting).
Frequently Asked Questions
Which collision backend is fastest in DART?
FCL generally offers the lowest overhead for typical robotics simulations due to its fast AABB tree broad-phase and efficient analytic primitive tests. However, for scenes with many complex concave meshes, Bullet’s optimized narrow-phase may outperform FCL despite higher memory usage. Benchmarking with your specific geometry is recommended.
Can I switch between FCL, Bullet, and ODE at runtime?
Yes. DART’s Strategy Pattern allows runtime backend switching by calling World::setCollisionDetector() with a new detector instance (e.g., BulletCollisionDetector::create()). Existing collision groups are invalidated and must be recreated when switching.
Why does the ODE backend lack distance queries?
The ODE backend in DART currently implements the distance() method as an empty stub returning zero (see dart/collision/ode/ode_collision_detector.hpp). While ODE itself supports distance calculations, the DART wrapper has not implemented the bridge code to expose this functionality, limiting ODE to binary collision detection only.
How do I fix inaccurate contact points with the FCL backend?
Set the contact computation method to DART. The default FCL contact algorithm has known precision issues (documented in FCL issue #106). Configure the detector to use DART’s internal contact point recomputation:
detector->setContactPointComputationMethod(
dart::collision::FCLCollisionDetector::DART);
This resolves penetration depth inaccuracies by computing contact points independently of FCL’s native algorithms.
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 →