Vaibhav Shende Vaibhav Shende

Motion Planning Algorithms Part 3: Reactive Methods and Hybrid Approaches

Reactive path planning (Bug algorithms, potential fields) and hybrid methods that combine strengths of multiple algorithms.

Motion Planning Robotics

Motion Planning Algorithms Part 3: Reactive Methods and Hybrid Approaches

When Should You Use Reactive Planning?

Reactive methods are online algorithms. The robot doesn’t precompute a full path—it reacts to immediate surroundings.

Suitable for:

  • Unknown environments (discovery during navigation)
  • Real-time constraints (no time to compute global path)
  • Simple robots (limited computational power)
  • Dynamic obstacles (moving targets)

Not suitable for:

  • Complex navigation (many obstacles)
  • Optimal paths (reactive ≠ optimal)
  • Guaranteed convergence (local methods can get stuck)
Planning Method Comparison

                Time  Optimality  Memory  Guarantees  Adaptivity
Grid-Based      Good  Optimal     High    Complete    Low (static)
RRT/RRT*        Fair  Fair/Good   Low     Probabilistic Low
Reactive        Fast  Suboptimal  Very Low Partial     High
Hybrid          Good  Good        Medium   High        High

Part 1: Bug Algorithms

Bug algorithms are simple: move toward goal, when you hit an obstacle, follow its boundary.

Figure 1: Bug Algorithm Navigation Strategy

BUG ALGORITHM: Three Phases

         Goal (G)
           *
          /│
         / │ Direct approach
        /  │ blocked
       /   │
      /    ╱════════╲
   ╱───────          ╲
  │  Obstacle        │
  │    (hit point→) H │
  │╱─────────────────╲│
       │             │ ← Follow boundary
       │             │    with left-hand rule
       │             │
     Start (S)       │
          
Phase 1: Move directly toward G
Phase 2: Hit obstacle at H, start boundary following
Phase 3: Exit boundary when closer to G than H

Three Variants:

  • Bug0: Simple, fast, can get stuck in U-shapes
  • Bug1: Slower, guarantees convergence, complete
  • Bug2: Uses M-line trick, usually fastest, mostly works

Bug0 (Simplest)

Algorithm:

1. Move directly toward goal
2. If no obstacle in path:
   - Move straight to goal
3. If obstacle encountered:
   a. Enter boundary-following (left-hand rule)
   b. Exit when:
      - Can move toward goal AND
      - Have made progress past hit point

Left-Hand Rule: Keep your left hand on the wall, follow it.

       Goal
        *
        |
     ___|(hit obstacle)
    /   \
   | ← follow boundary with left hand
   \____/
        |
      Start

Bug0 Code

class Bug0:
    def __init__(self, robot, goal):
        self.robot = robot
        self.goal = goal
        self.hit_point = None
        self.following_boundary = False
    
    def next_move(self):
        current_pos = self.robot.position
        
        # Try direct path to goal
        if not self.following_boundary:
            direction = self.goal - current_pos
            
            # Check if direct path is free
            if self.is_path_clear(current_pos, self.goal):
                return direction.normalize()
            
            # Hit obstacle, enter boundary following
            self.hit_point = current_pos
            self.following_boundary = True
        
        # Boundary following (left-hand rule)
        if self.following_boundary:
            boundary_move = self.follow_left_wall()
            
            # Check if we can exit
            if self.can_reach_goal_from_here():
                if self.is_path_clear(current_pos, self.goal):
                    self.following_boundary = False
                    return (self.goal - current_pos).normalize()
            
            return boundary_move
    
    def follow_left_wall(self):
        # Simplified: try moving forward
        # If blocked, turn left and try again
        current_pos = self.robot.position
        
        # Left-hand rule: keep obstacle on right
        for angle in [0, -45, -90, -135, 180]:
            direction = self.get_direction_relative_to_heading(angle)
            if self.is_path_clear(current_pos, current_pos + direction):
                return direction
        
        return (0, 0)  # Stuck

Bug0 Pros & Cons

✅ Pros:

  • Very simple (easy to implement)
  • Works with unknown environments
  • Fast (no global planning)

❌ Cons:

  • Gets stuck in U-shaped obstacles
  • Not optimal (long paths around obstacles)
  • Only works with point robots

Bug1 (Better)

Improves Bug0 by fully circling obstacles.

Algorithm:

1. Move directly toward goal
2. If hit obstacle:
   a. Fully circumnavigate obstacle (complete circle)
   b. Record closest point to goal (closest_point)
   c. Return to closest_point
   d. Resume direct motion

Key improvement: Doesn’t exit too early. Guarantees finding goal if reachable.

Hit point → Full circle → Closest point → Direct to goal
    *───────────────────────*
   /╱╲                      ╲
  / │ │  (full boundary      │
  │ │ │   traversal)         │
  │ │ │                      │
  ╱ │ │                     ╱
  ╲_│_│_________________╱

Bug1 Code

class Bug1:
    def __init__(self, robot, goal):
        self.robot = robot
        self.goal = goal
        self.hit_point = None
        self.closest_point = None
        self.closest_distance = float('inf')
        self.circumnavigating = False
    
    def next_move(self):
        current_pos = self.robot.position
        
        if not self.circumnavigating:
            # Try direct approach
            if self.is_path_clear(current_pos, self.goal):
                return (self.goal - current_pos).normalize()
            
            # Hit obstacle, start circumnavigation
            self.hit_point = current_pos
            self.closest_point = current_pos
            self.closest_distance = distance(current_pos, self.goal)
            self.circumnavigating = True
        
        # Circumnavigation phase
        boundary_move = self.follow_left_wall()
        
        # Update closest point
        dist_to_goal = distance(current_pos, self.goal)
        if dist_to_goal < self.closest_distance:
            self.closest_distance = dist_to_goal
            self.closest_point = current_pos
        
        # Exit condition: returned to hit point after circumnavigation
        if distance(current_pos, self.hit_point) < 0.1 and self.circumnavigating:
            # Move to closest point
            if distance(current_pos, self.closest_point) < 0.1:
                # Now try direct approach again
                self.circumnavigating = False
                return (self.goal - current_pos).normalize()
            else:
                return (self.closest_point - current_pos).normalize()
        
        return boundary_move

Bug1 Guarantee

Bug1 GUARANTEES finding goal if reachable (complete).

Cost: May traverse boundaries multiple times.


Bug2 (Optimal)

Uses M-line strategy—usually finds path faster than Bug1.

Algorithm:

1. Define M-line: straight line from start to goal
2. Move toward goal
3. If hit obstacle:
   a. Follow boundary
   b. Exit when:
      - Cross M-line AND
      - Closer to goal than hit point
4. If return to hit point without crossing M-line:
   - Goal unreachable
Start ─ M-line ─ Goal
  \      │      /
   \  (obstacle) /
    \    │     /
     ╲   │   ╱
      ╲  │ ╱ (boundary follow)
       ╲│╱
         ● (exit when cross M-line closer to goal)

Bug2 Code

class Bug2:
    def __init__(self, robot, goal):
        self.robot = robot
        self.goal = goal
        self.start = robot.position.copy()
        self.hit_point = None
        self.on_boundary = False
    
    def next_move(self):
        current_pos = self.robot.position
        
        if not self.on_boundary:
            # Try direct approach
            if self.is_path_clear(current_pos, self.goal):
                return (self.goal - current_pos).normalize()
            
            # Hit obstacle
            self.hit_point = current_pos
            self.on_boundary = True
        
        # Boundary following
        if self.on_boundary:
            boundary_move = self.follow_left_wall()
            
            # Check exit condition
            if self.is_on_m_line(current_pos) and \
               distance(current_pos, self.goal) < distance(self.hit_point, self.goal):
                # Exit boundary following
                self.on_boundary = False
                return (self.goal - current_pos).normalize()
            
            # Check if unreachable
            if distance(current_pos, self.hit_point) < 0.01 and self.on_boundary:
                return None  # Goal unreachable
            
            return boundary_move
    
    def is_on_m_line(self, pos):
        # Check if position is on line from start to goal
        # Simplified check using cross product
        v1 = self.goal - self.start
        v2 = pos - self.start
        cross = v1[0]*v2[1] - v1[1]*v2[0]
        return abs(cross) < 0.1

Bug Algorithm Comparison

Obstacle Type       Bug0        Bug1        Bug2
Simple (open)       ✓ Works     ✓ Works     ✓ Works
U-shaped            ✗ Stuck     ✓ Works     ✓ Works
Spiral              ✗ Stuck     ✓ Works     ✗ Issues
Steps explored      Least       Most        Medium
Guaranteed?         No          Yes         Mostly

Practical use:
- Unknown environment, simple geometry → Bug0
- Guaranteed convergence needed → Bug1
- Balanced approach → Bug2

Part 2: Potential Fields

Potential fields treat obstacles as repelling forces and goal as attracting force.

Figure 2: Potential Field Force Visualization

ATTRACTIVE FORCE (Goal)          REPULSIVE FORCE (Obstacle)
  ↑ ↑ ↑                             ← ← ← ←
  ↑   ↑                          ←      ↓      ←
  ↑ G ↑        +       ←   O   ←        =
  ↑   ↑                          ←      ↑      ←
  ↑ ↑ ↑                             ← ← ← ←
  
Goal attracts               Obstacles repel
(like magnet)              (like opposing magnets)

COMBINED FIELD:
  ↑ ↓ ↓ ↓
  ↑   ↓ ↓  (robot moves here,
  ↑ R →   around obstacle)
  ↑   ↑    
  ↑ ↑ ↑ ← ← ←

Robot follows resultant force vector

Method:

Total Force = Attractive Force + Repulsive Forces
Robot Velocity = K_p * Total_Force (proportional control)

Pros: Very fast, works with dynamic obstacles
Cons: Can get stuck in local minima (concave obstacles)

The Concept

Total force = Attractive (toward goal) + Repulsive (away from obstacles)

Robot moves in direction of total force

Attractive Potential

Goal attracts robot:

F_attractive = -k_a * (robot_pos - goal_pos)

More distance → Stronger force.

Repulsive Potential

Obstacles repel robot:

F_repulsive = 0 if distance > d_0
F_repulsive = k_r * (1/distance - 1/d_0) * (robot_pos - obstacle_pos) / distance
              if distance < d_0

Where d_0 is influence radius.

Visualization

Goal (attractive)         Obstacles (repulsive)
   ↑ ↑ ↑                      ← ← ←
   ↑   ↑                    ←   ↓   ←
   ↑ R ↑        vs.       ←   O   ←
                             ← ↑ ←
   
Robot sees combined field:
   ↑ ↓ ↓
   ↑   ↓  (attraction weaker than repulsion)
   ↑ R →  (move around obstacle)

Potential Field Code

import numpy as np
 
class PotentialField:
    def __init__(self, k_attractive=1.0, k_repulsive=1.0, influence_radius=2.0):
        self.k_a = k_attractive
        self.k_r = k_repulsive
        self.d_0 = influence_radius
    
    def attractive_force(self, robot_pos, goal):
        return -self.k_a * (robot_pos - goal)
    
    def repulsive_force(self, robot_pos, obstacles):
        force = np.array([0.0, 0.0])
        
        for obs_pos, obs_radius in obstacles:
            distance = np.linalg.norm(robot_pos - obs_pos)
            
            if distance < self.d_0:
                repulsion = (self.k_r * 
                           (1.0/distance - 1.0/self.d_0) / (distance**2) *
                           (robot_pos - obs_pos))
                force += repulsion
        
        return force
    
    def total_force(self, robot_pos, goal, obstacles):
        f_attr = self.attractive_force(robot_pos, goal)
        f_rep = self.repulsive_force(robot_pos, obstacles)
        return f_attr + f_rep
    
    def next_move(self, robot_pos, goal, obstacles):
        force = self.total_force(robot_pos, goal, obstacles)
        
        if np.linalg.norm(force) < 0.01:
            return np.array([0.0, 0.0])  # Stuck
        
        direction = force / np.linalg.norm(force)
        step_size = 0.1
        
        new_pos = robot_pos + step_size * direction
        return new_pos
 
# Usage
pf = PotentialField(k_attractive=1.0, k_repulsive=2.0)
robot_pos = np.array([0.0, 0.0])
goal = np.array([10.0, 10.0])
obstacles = [
    (np.array([5.0, 5.0]), 0.5),  # (center, radius)
    (np.array([7.0, 3.0]), 0.3)
]
 
for _ in range(100):
    new_pos = pf.next_move(robot_pos, goal, obstacles)
    robot_pos = new_pos
    
    if np.linalg.norm(robot_pos - goal) < 0.1:
        print("Goal reached!")
        break

Potential Field Pros & Cons

✅ Pros:

  • Very fast (one force calculation per step)
  • Works with dynamic obstacles
  • Smooth trajectories
  • Simple to implement

❌ Cons:

  • Gets stuck in local minima (concave obstacles)
  • No global optimality
  • Parameter tuning critical (k_a, k_r)
  • Jittery at obstacle boundaries

Local Minimum Problem

Potential field stuck in local minima:

Goal
  G
  |
  ├─ ○ (obstacle creates local minimum)
  |
  R (robot stuck here)
  
Robot can't escape—forces balanced.

Part 3: Hybrid Approaches

Combine strengths of different methods.

RRT-Connect (Bidirectional RRT)

Grows trees from both start AND goal simultaneously.

Figure 4: RRT-Connect: Bidirectional Growth

UNIDIRECTIONAL RRT          BIDIRECTIONAL RRT-CONNECT
(Traditional)               (Faster - trees meet)

        G                           G
        *                           *
      / │ \                       / ║ \
    /   │   \                   /   T   \
   /    │    \                /     2    \
  /     │     \              /            \
 │      │      │           │              │
 │  Tree│grows │           │   T2 T1      │
 │   from│start │           │     |│      │
 │      │      │           │     ││       │
 └──┬───┴──┬───┘           └──┬──┴┴──┬───┘
    │      │                   │Meeting│
    S      1000+ iters         S  point
    
Growth expands                Both trees
from start toward             grow toward
goal (slow)                   each other
                              (fast!)

TIME COMPARISON:
Unidirectional: Need 1000+ iterations to reach goal
Bidirectional:  Trees meet after ~50-100 iterations (10× faster!)
Start                           Goal
  S ←RRT1                    RRT2→ G
  
  Tree1 expands from start
  Tree2 expands from goal
  
  Trees meet → Path found!

Why faster:

  • Trees grow toward each other (smaller growth radius)
  • Meets in middle (half the exploration space)

Time improvement: Often 10-100x faster than unidirectional RRT.

Pseudocode

RRTConnect(q_start, q_goal):
  T1.add_node(q_start)
  T2.add_node(q_goal)
  
  for i = 1 to max_iterations:
    // Grow T1 toward random sample
    q_rand = random_config()
    q_new1 = extend(T1, q_rand)
    
    if collision_free(q_new1):
      T1.add_node(q_new1)
      
      // Try connecting to T2
      q_nearest2 = T2.nearest(q_new1)
      q_new2 = extend(T2, q_new1)
      
      if collision_free(q_new2):
        T2.add_node(q_new2)
        
        // Try direct connection
        if collision_free(q_new1, q_new2):
          return Path(T1, T2, q_new1, q_new2)
    
    // Swap trees to balance growth
    swap(T1, T2)
  
  return NULL

RRT-Connect Performance

Time (seconds)
     |
     | RRT (unidirectional)
   5 ├─────
     |    /
   2 ├   /
     |  / RRT-Connect (bidirectional)
   0.5 ├
     |/
     └──────→ Workspace Complexity

Informed RRT*

Combines RRT* with informed sampling.

Idea: After finding first path, only sample configurations that could improve it.

Phase 1: RRT* finds path with cost C_best
Phase 2: Sample only from region where:
         g_cost + h_cost < C_best
         (could improve best solution)

Dramatically reduces unnecessary samples.

When to Use Each

Problem Characteristics         Use This Algorithm
────────────────────────────────────────────────────
Known static environment        Grid-based / RRT*
Unknown/partially known         Bug algorithms
Real-time, low-power device     Potential fields
High-DOF manipulator            RRT*
Need reusable path database     PRM
Dynamic obstacles               Potential fields + reactive
Optimal path critical           RRT* or informed RRT*
Bidirectional growth needed     RRT-Connect

Hybrid Strategy: Multilevel Planning

Combine multiple methods.

Figure 5: Hierarchical Planning Architecture

┌──────────────────────────────────────────────────┐
│         LEVEL 1: GLOBAL PLANNER                  │
│  (5-10 Hz, RRT* / Grid-based)                    │
│  Output: Rough waypoint path                     │
│  Time budget: 1-10 seconds                       │
└────────────────────┬─────────────────────────────┘
                     │
                     ↓
┌──────────────────────────────────────────────────┐
│         LEVEL 2: LOCAL PLANNER                   │
│  (20-50 Hz, Potential fields / MPC)              │
│  Output: Smooth collision-free trajectory        │
│  Time budget: 20-50ms per step                   │
└────────────────────┬─────────────────────────────┘
                     │
                     ↓
┌──────────────────────────────────────────────────┐
│         LEVEL 3: REACTIVE LAYER                  │
│  (100+ Hz, Bug algo / Obstacle avoidance)        │
│  Output: Immediate velocity commands             │
│  Time budget: 10ms per cycle                     │
└────────────────────┬─────────────────────────────┘
                     │
                     ↓
┌──────────────────────────────────────────────────┐
│              ROBOT EXECUTION                      │
│  Motor commands, wheel speeds, joint angles      │
└──────────────────────────────────────────────────┘

Advantages of Layered Approach:
├─ Global: Optimizes overall mission
├─ Local: Handles local obstacles/constraints
└─ Reactive: Ensures real-time safety
Level 1: Global Path (RRT* or grid-based)
         └─ Rough waypoints

Level 2: Local Refinement (potential fields)
         └─ Smooth trajectories

Level 3: Reactive Correction (Bug algorithms)
         └─ Handle unexpected obstacles

Workflow:

  1. Compute global path with RRT*
  2. Smooth with potential fields
  3. If obstacle encountered, apply Bug algorithm locally
  4. Resume global path

Real-World Example: Navigation Stack

Navigation Pipeline
─────────────────────────────────────

Start ──→ [Global Planner] ──→ Global Path
             (RRT* or grid)

          ↓

   [Local Planner] ──→ Local Trajectory
   (potential fields)

          ↓

   [Reactive Layer] ──→ Final Velocity Command
   (Bug algorithm)

          ↓

  [Motor Controller] ──→ Robot Motion

Each layer handles different time scales:

  • Global: seconds (rough path)
  • Local: 100ms (smooth trajectory)
  • Reactive: 10ms (obstacle avoidance)

Key Takeaways

  1. Reactive methods for online planning

    • Bug algorithms, potential fields
    • Fast but not optimal
  2. Bug algorithms guarantee convergence

    • Bug0: Simplest
    • Bug1: Complete
    • Bug2: Usually fastest
  3. Potential fields smooth but local minima

    • Adjust k_a, k_r for balance
    • Works with dynamic obstacles
  4. Hybrid approaches combine strengths

    • RRT-Connect faster than RRT
    • Multilevel planning for robustness
    • Practical real-world systems use combinations
  5. Choose algorithm by requirements

    • Time constraint?
    • Optimality needed?
    • Environment known?

Next: Part 4

In Part 4, we’ll explore advanced topics: trajectory optimization, MPC (Model Predictive Control), and practical deployment strategies.


References

  1. Khatib, O. (1986). “Real-time obstacle avoidance for manipulators and mobile robots.” IJRR, 5(1), 90-98.

  2. Lumelsky, V. J., Skewis, S. E. (1990). “Incorporating range sensing in the robot navigation function.” IEEE Trans. Systems, Man, and Cybernetics, 20(5), 1058-1068.

  3. Kuffner, J. J., & LaValle, S. M. (2000). “RRT-Connect: An efficient approach to single-query path planning.” ICRA, 995-1001.

  4. Gammell, J. D., Srinivasa, S. S., & Barfoot, T. D. (2015). “Informed RRT*: Optimal sampling-based path planning focused via direct sampling of an admissible ellipsoidal heuristic.” IROS.


Last updated: June 2026 | Tested on ground robots and manipulators