Skip to main content

Overview

Colliders define the shape used for collision detection and physics simulation. Atlas provides four primary collider types through the Bezel physics abstraction layer.
All collider types are defined in bezel/bezel.h and implement the base Collider interface.

Collider Types

Atlas supports four collider types (from bezel.h:71-157):
  1. BoxCollider - Axis-aligned box
  2. SphereCollider - Sphere
  3. CapsuleCollider - Capsule (cylinder with hemisphere ends)
  4. MeshCollider - Triangle mesh for complex shapes

BoxCollider

Box colliders are defined by half-extents (distance from center to face).

Definition (bezel.h:85-98)

class BoxCollider : public Collider {
  public:
    Position3d halfExtents;
    
    BoxCollider(const Position3d &halfExtents) 
        : halfExtents(halfExtents) {}
    
    float getMinExtent() const override {
        return std::min({halfExtents.x, halfExtents.y, halfExtents.z});
    }
};

Usage

// Create a 1x1x1 box (half-extents of 0.5)
auto rb = std::make_shared<Rigidbody>();
rb->addBoxCollider({0.5f, 0.5f, 0.5f});

// Create a 2x1x3 box
rb->addBoxCollider({1.0f, 0.5f, 1.5f});

// Flat platform (10x0.2x10)
rb->addBoxCollider({5.0f, 0.1f, 5.0f});
Box colliders are the most efficient collider type and should be used whenever possible.

Example: Ground Plane

auto ground = std::make_shared<Rigidbody>();
ground->setMotionType(MotionType::Static);
ground->addBoxCollider({10.0f, 0.1f, 10.0f});  // 20x0.2x20 platform
ground->setFriction(0.5f);

SphereCollider

Sphere colliders are defined by a radius.

Definition (bezel.h:122-132)

class SphereCollider : public Collider {
  public:
    float radius;
    
    float getMinExtent() const override { 
        return radius * 2.0f; 
    }
    
    explicit SphereCollider(float radius) : radius(radius) {}
};

Usage

// Create a sphere with radius 0.5
auto rb = std::make_shared<Rigidbody>();
rb->addSphereCollider(0.5f);

// Small ball (radius 0.25)
rb->addSphereCollider(0.25f);

// Large sphere (radius 2.0)
rb->addSphereCollider(2.0f);

Example: Bouncing Ball

auto ball = std::make_shared<Rigidbody>();
ball->setMotionType(MotionType::Dynamic);
ball->setMass(1.0f);
ball->addSphereCollider(0.5f);
ball->setRestitution(0.8f);  // Bouncy!
ball->applyImpulse({0.0f, 10.0f, 0.0f});
Sphere colliders are very efficient and provide smooth rolling physics.

CapsuleCollider

Capsule colliders are defined by a radius and height. They’re ideal for characters.

Definition (bezel.h:103-117)

class CapsuleCollider : public Collider {
  public:
    float radius;
    float height;
    
    float getMinExtent() const override {
        return std::min(radius * 2.0f, height);
    }
    
    CapsuleCollider(float radius, float height)
        : radius(radius), height(height) {}
};

Usage

// Create a capsule (radius 0.5, height 2.0)
auto rb = std::make_shared<Rigidbody>();
rb->addCapsuleCollider(0.5f, 2.0f);

// Character controller (radius 0.3, height 1.8)
rb->addCapsuleCollider(0.3f, 1.8f);

// Pill shape (radius 0.4, height 1.0)
rb->addCapsuleCollider(0.4f, 1.0f);

Example: Character Controller

auto character = std::make_shared<Rigidbody>();
character->setMotionType(MotionType::Dynamic);
character->setMass(70.0f);  // 70kg human
character->addCapsuleCollider(0.3f, 1.8f);
character->setFriction(0.8f);
character->addTag("Player");
Capsule colliders are perfect for characters because they don’t catch on edges like boxes do.

MeshCollider

Mesh colliders use triangle meshes for complex shapes. They’re more expensive than primitive colliders.

Definition (bezel.h:139-157)

class MeshCollider : public Collider {
  public:
    std::vector<Position3d> vertices;
    std::vector<uint32_t> indices;
    
    float getMinExtent() const override {
        float minExtent = std::numeric_limits<float>::max();
        for (const auto &vert : vertices) {
            minExtent = std::min({minExtent, vert.x, vert.y, vert.z});
        }
        return minExtent;
    }
    
    MeshCollider(const std::vector<Position3d> &vertices,
                 const std::vector<uint32_t> &indices);
};
Mesh colliders are typically more expensive than primitives. Use them only when necessary for complex static geometry.

Usage

// Add mesh collider from GameObject's mesh
auto rb = std::make_shared<Rigidbody>();
rb->addMeshCollider();

// For complex terrain or architecture
auto terrain = std::make_shared<Rigidbody>();
terrain->setMotionType(MotionType::Static);
terrain->addMeshCollider();

Example: Static Terrain

auto terrain = std::make_shared<Rigidbody>();
terrain->setMotionType(MotionType::Static);
terrain->addMeshCollider();  // Uses GameObject's mesh
terrain->setFriction(0.7f);

Changing Colliders at Runtime

You can replace a rigidbody’s collider:
// Start with sphere
rigidbody->addSphereCollider(0.5f);

// Later, switch to box
auto newCollider = std::make_shared<bezel::BoxCollider>(Position3d{0.5f, 0.5f, 0.5f});
rigidbody->body->setCollider(newCollider);
From bezel.h:541:
void setCollider(std::shared_ptr<Collider> collider);

Collider Interface

All colliders implement the base Collider interface (bezel.h:71-80):
class Collider {
  public:
    virtual ~Collider() = default;
    
    // Returns the smallest extent for broad-phase heuristics
    virtual float getMinExtent() const = 0;
    
    // Internal Jolt Physics shape conversion
    virtual JPH::RefConst<JPH::Shape> getJoltShape() const = 0;
};

Collision Detection

Colliders are used for:
  1. Continuous collision detection - Physics simulation
  2. Raycasts - Line-of-sight checks
  3. Overlap queries - Trigger detection
  4. Sweep tests - Movement prediction

Raycast Example

// Cast ray downward to check for ground
rigidbody->raycast({0.0f, -1.0f, 0.0f}, 2.0f);

// Handle result in component
void onQueryReceive(QueryResult& result) override {
    if (result.raycastResult.hit.didHit) {
        std::cout << "Ground at: " 
                  << result.raycastResult.hit.distance 
                  << "m below" << std::endl;
    }
}

Overlap Example

// Check for nearby enemies using sphere
rigidbody->overlapSphere(5.0f);

// Handle overlap results
void onQueryReceive(QueryResult& result) override {
    if (result.overlapResult.hitAny) {
        std::cout << "Found " 
                  << result.overlapResult.hits.size() 
                  << " nearby objects" << std::endl;
    }
}

Performance Considerations

Collider Complexity

From most to least efficient:
  1. Sphere - Fastest, simplest
  2. Box - Very fast, most versatile
  3. Capsule - Fast, great for characters
  4. Mesh - Slowest, use for static geometry only
For dynamic objects, always prefer primitive colliders (box, sphere, capsule) over mesh colliders.

Compound Colliders

For complex dynamic objects, use multiple primitive colliders instead of a mesh:
// Instead of mesh collider, use multiple boxes
auto body = std::make_shared<Rigidbody>();
body->addBoxCollider({1.0f, 0.5f, 2.0f});  // Main body
// Create child objects with additional colliders

Static vs Dynamic

  • Static mesh colliders - Fine for terrain and buildings
  • Dynamic mesh colliders - Very expensive, avoid if possible
// Good: Static terrain with mesh collider
auto terrain = std::make_shared<Rigidbody>();
terrain->setMotionType(MotionType::Static);
terrain->addMeshCollider();

// Bad: Dynamic mesh collider (use primitives instead)
auto vehicle = std::make_shared<Rigidbody>();
vehicle->setMotionType(MotionType::Dynamic);
vehicle->addBoxCollider({1.0f, 0.5f, 2.0f});  // Use box, not mesh!

Collider Scaling

Collider extents should match the visual representation:
// For a 2x2x2 visual cube, use half-extents of 1x1x1
rb->addBoxCollider({1.0f, 1.0f, 1.0f});

// For a sphere with diameter 3, use radius 1.5
rb->addSphereCollider(1.5f);

// For a 2m tall character, use appropriate capsule
rb->addCapsuleCollider(0.3f, 1.8f);

Query Colliders

You can create temporary colliders for queries without attaching them to rigidbodies:
// Overlap test with temporary sphere
auto sphereCollider = std::make_shared<bezel::SphereCollider>(2.0f);
world->overlap(world, sphereCollider, position, rotation);

// Sweep test with temporary box
auto boxCollider = std::make_shared<bezel::BoxCollider>(Position3d{0.5f, 0.5f, 0.5f});
world->sweep(world, boxCollider, startPos, startRot, direction, endPos);

Complete Example

Here’s a complete scene with multiple collider types:
// Create physics world
auto world = std::make_shared<bezel::PhysicsWorld>();
world->init();
world->setGravity({0.0f, -9.81f, 0.0f});

// Ground (static box)
auto ground = std::make_shared<Rigidbody>();
ground->setMotionType(MotionType::Static);
ground->addBoxCollider({10.0f, 0.1f, 10.0f});
ground->setFriction(0.5f);

// Ball (dynamic sphere)
auto ball = std::make_shared<Rigidbody>();
ball->setMotionType(MotionType::Dynamic);
ball->setMass(1.0f);
ball->addSphereCollider(0.5f);
ball->setRestitution(0.8f);

// Character (dynamic capsule)
auto character = std::make_shared<Rigidbody>();
character->setMotionType(MotionType::Dynamic);
character->setMass(70.0f);
character->addCapsuleCollider(0.3f, 1.8f);
character->setFriction(0.8f);

// Terrain (static mesh)
auto terrain = std::make_shared<Rigidbody>();
terrain->setMotionType(MotionType::Static);
terrain->addMeshCollider();
terrain->setFriction(0.7f);

API Reference

Rigidbody Collider Methods (atlas/physics.h:365-372)

MethodDescription
addBoxCollider(Position3d)Add box collider with half-extents
addSphereCollider(float)Add sphere collider with radius
addCapsuleCollider(float, float)Add capsule with radius and height
addMeshCollider()Add mesh collider from object mesh

See Also

Build docs developers (and LLMs) love