# FCL vs Bullet vs ODE Collision Backends in DART: Architecture and Usage Guide

> Explore FCL, Bullet, and ODE collision backends in DART. Understand their architectures, speed, dependencies, and features to choose the best fit for your project.

- Repository: [DART: Dynamic Animation and Robotics Toolkit/dart](https://github.com/dartsim/dart)
- Tags: architecture
- Published: 2026-02-28

---

**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`](https://github.com/dartsim/dart/blob/main/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:

```cpp
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:

```cpp
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`](https://github.com/dartsim/dart/blob/main/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`](https://github.com/dartsim/dart/blob/main/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:

```cpp
#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:

```cpp
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`](https://github.com/dartsim/dart/blob/main/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:

```cpp
detector->setContactPointComputationMethod(
    dart::collision::FCLCollisionDetector::DART);

```

This resolves penetration depth inaccuracies by computing contact points independently of FCL’s native algorithms.