# How the Maze Obstacle System Works in Peng: Implementation and Physics

> Explore the Maze obstacle system implementation in Peng. Learn how this 3D simulation component generates and renders spherical obstacles in Rust.

- Repository: [Yang Zhou/peng](https://github.com/makeecat/peng)
- Tags: internals
- Published: 2026-03-06

---

**The Maze obstacle system in Peng is a deterministic 3D simulation component that generates, updates, and renders moving spherical obstacles within bounded volumes, implemented in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) using the `Obstacle` and `Maze` structs.**

The Peng repository provides a quadrotor simulation environment with dynamic obstacle generation for testing collision avoidance algorithms. The **Maze obstacle system** creates a configurable 3D space filled with moving spherical objects that bounce off virtual walls, providing reproducible scenarios for drone navigation testing according to the `makeecat/peng` source code.

## Core Data Structures

The implementation centers on two primary types defined in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs): the `Obstacle` struct representing individual spheres, and the `Maze` struct managing the collection and boundary conditions.

### The Obstacle Struct

Defined at lines 2671–2779 in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs), the `Obstacle` struct holds the state of a single moving sphere:

```rust
pub struct Obstacle {
    pub position: Vector3<f32>,
    pub velocity: Vector3<f32>,
    pub radius: f32,
}

```

Each obstacle maintains its **position** as a 3D vector, **velocity** for movement per simulation step, and **radius** defining its collision size.

### The Maze Struct

The `Maze` struct (lines 2718–2732 in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs)) serves as the container and simulation controller:

```rust
pub struct Maze {
    pub lower_bounds: [f32; 3],
    pub upper_bounds: [f32; 3],
    pub obstacles: Vec<Obstacle>,
    pub obstacles_velocity_bounds: [f32; 3],
    pub obstacles_radius_bounds: [f32; 2],
    pub rng: ChaCha8Rng,
}

```

Key fields include:
- **Bounds arrays** defining the axis-aligned bounding box
- **Obstacles vector** storing all active spheres
- **Velocity bounds** specifying maximum speed per axis (±value)
- **Radius bounds** constraining obstacle sizes
- **ChaCha8Rng** providing deterministic random number generation

## Initialization and Construction

The `Maze::new` method (lines 2746–2763 in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs)) handles construction:

```rust
pub fn new(
    lower_bounds: [f32; 3],
    upper_bounds: [f32; 3],
    num_obstacles: usize,
    obstacles_velocity_bounds: [f32; 3],
    obstacles_radius_bounds: [f32; 2],
) -> Self {
    let mut maze = Maze {
        lower_bounds,
        upper_bounds,
        obstacles: Vec::new(),
        obstacles_velocity_bounds,
        obstacles_radius_bounds,
        rng: ChaCha8Rng::from_rng(&mut rand::rng()),
    };
    maze.generate_obstacles(num_obstacles);
    maze
}

```

The constructor stores the configuration and immediately calls `generate_obstacles` to fill the `obstacles` vector.

## Random Obstacle Generation

The `generate_obstacles` method (lines 2734–2755 in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs)) creates the initial obstacle distribution:

```rust
pub fn generate_obstacles(&mut self, num_obstacles: usize) {
    self.obstacles = (0..num_obstacles)
        .map(|_| {
            // Random position inside the maze volume
            let position = Vector3::new(
                self.rng.random_range(self.lower_bounds[0]..self.upper_bounds[0]),
                self.rng.random_range(self.lower_bounds[1]..self.upper_bounds[1]),
                self.rng.random_range(self.lower_bounds[2]..self.upper_bounds[2]),
            );
            // Random symmetric velocity per axis
            let v_bounds = self.obstacles_velocity_bounds;
            let velocity = Vector3::new(
                self.rng.random_range(-v_bounds[0]..v_bounds[0]),
                self.rng.random_range(-v_bounds[1]..v_bounds[1]),
                self.rng.random_range(-v_bounds[2]..v_bounds[2]),
            );
            // Random radius within the configured range
            let r_bounds = self.obstacles_radius_bounds;
            let radius = self.rng.random_range(r_bounds[0]..r_bounds[1]);

            Obstacle::new(position, velocity, radius)
        })
        .collect();
}

```

This method samples **position**, **velocity**, and **radius** uniformly from their respective bounds, ensuring each obstacle starts with valid parameters.

## Physics Update and Collision Detection

The `update_obstacles` method (lines 2796–2816 in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs)) advances the simulation:

```rust
pub fn update_obstacles(&mut self, dt: f32) {
    self.obstacles.iter_mut().for_each(|obstacle| {
        // Simple Euler integration
        obstacle.position += obstacle.velocity * dt;

        // Bounce off each wall when the sphere would cross the bounds.
        for i in 0..3 {
            if obstacle.position[i] - obstacle.radius < self.lower_bounds[i]
                || obstacle.position[i] + obstacle.radius > self.upper_bounds[i]
            {
                obstacle.velocity[i] *= -1.0; // invert the component on that axis
            }
        }
    });
}

```

The update loop performs **Euler integration** to move obstacles linearly, then checks each axis for boundary violations. When a sphere’s surface would go outside the maze box, the corresponding velocity component is flipped, creating perfectly elastic bounces.

## Integration with the Simulation Loop

In **[`src/main.rs`](https://github.com/makeecat/peng/blob/main/src/main.rs)**, the maze is instantiated from the YAML configuration and updated every simulation step:

```rust
let mut maze = Maze::new(
    config.maze.lower_bounds,
    config.maze.upper_bounds,
    config.maze.num_obstacles,
    config.maze.obstacles_velocity_bounds,
    config.maze.obstacles_radius_bounds,
);

...

loop {
    // Advance obstacle motion
    maze.update_obstacles(quad.time_step);
    // … other simulation updates …
}

```

The same `maze` instance is passed to the depth‑camera renderer and to the ray‑casting routine, so every rendered depth image reflects the current positions of the moving obstacles.

## Visualization and Logging

Peng can log the maze geometry (the enclosing tube) and the current obstacle states to a Rerun recording stream:

- **`log_maze_tube`** writes the bounding box as a visual tube.
- **`log_maze_obstacles`** emits each obstacle’s position and radius as a 3‑D point cloud.

Both functions read directly from `maze.lower_bounds`, `maze.upper_bounds`, and `maze.obstacles` (see **[`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs)** around lines 3100‑3170).

## Practical Code Examples

Below are short snippets that demonstrate how a user could create and manipulate a maze outside the main binary (e.g. in a test or a REPL).

```rust
use peng_quad::{Maze, Obstacle};
use nalgebra::Vector3;

// 1️⃣ Create a maze with a 2 m³ volume and 10 random obstacles.
let mut maze = Maze::new(
    [-1.0, -1.0, -1.0],   // lower bounds
    [1.0, 1.0, 1.0],      // upper bounds
    10,                    // number of obstacles
    [0.2, 0.2, 0.2],       // max velocity per axis
    [0.05, 0.15],          // radius range
);

// 2️⃣ Inspect a randomly generated obstacle.
let first = &maze.obstacles[0];
println!(
    "Obstacle #0 – pos: ({:.2}, {:.2}, {:.2}), vel: ({:.2}, {:.2}, {:.2}), r: {:.2}",
    first.position.x, first.position.y, first.position.z,
    first.velocity.x, first.velocity.y, first.velocity.z,
    first.radius,
);

// 3️⃣ Step the simulation forward by 0.02 s.
maze.update_obstacles(0.02);

// 4️⃣ Manually add a deterministic obstacle.
let fixed = Obstacle::new(
    Vector3::new(0.0, 0.0, 0.0), // stationary at the centre
    Vector3::zeros(),
    0.1,
);
maze.obstacles.push(fixed);

```

## Summary

- The **Maze obstacle system** in Peng provides a deterministic, reproducible environment for testing quadrotor collision avoidance.
- Core types **`Obstacle`** and **`Maze`** defined in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) manage spherical obstacle state and boundary conditions.
- **Random generation** uses `ChaCha8Rng` for reproducible initial conditions, sampling position, velocity, and radius within configured bounds.
- **Physics updates** employ Euler integration with perfectly elastic bouncing against axis-aligned bounding walls.
- **Integration** in [`src/main.rs`](https://github.com/makeecat/peng/blob/main/src/main.rs) connects the maze to the rendering pipeline and depth-camera simulation.
- **Visualization** via Rerun functions in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs) enables real-time debugging of obstacle positions.

## Frequently Asked Questions

### How does Peng ensure reproducible maze configurations?

Peng uses a **ChaCha8Rng** random number generator seeded from the global RNG during `Maze::new`. Because the generator is deterministic, providing the same seed produces identical obstacle positions, velocities, and radii across simulation runs, essential for reproducible robotics experiments.

### What collision detection method does the Maze obstacle system use?

The system implements **sphere-to-wall collision detection** in `Maze::update_obstacles`. After each Euler integration step, the code checks if `obstacle.position[i] - obstacle.radius` falls below `lower_bounds[i]` or if `obstacle.position[i] + obstacle.radius` exceeds `upper_bounds[i]` for each axis. When violated, the corresponding velocity component flips, creating perfectly elastic bounces.

### Can obstacles be added manually after maze initialization?

Yes. While `Maze::generate_obstacles` creates random obstacles during construction, the `obstacles` field is a public `Vec<Obstacle>`. Users can push manually constructed `Obstacle` instances using `Obstacle::new(position, velocity, radius)`, allowing deterministic test scenarios alongside randomly generated environments.

### How is the maze visualized during simulation?

Peng integrates with the Rerun visualization framework through functions defined around lines 3100–3170 in [`src/lib.rs`](https://github.com/makeecat/peng/blob/main/src/lib.rs). The `log_maze_tube` function renders the bounding box as a tube structure, while `log_maze_obstacles` emits each obstacle’s position and radius as a 3D point cloud, enabling real-time debugging of obstacle motion and collision boundaries.