Mecanum Wheel Kinematics
Forward and inverse kinematics for mecanum wheel robots, enabling omnidirectional motion including sideways movement.
Overview
Mecanum wheels have rollers at 45° to the wheel axis, allowing omnidirectional movement without changing orientation.
Advantages:
- Omnidirectional motion (can move in any direction)
- Can translate while rotating
- No need to reorient before moving
- Excellent maneuverability in tight spaces
Disadvantages:
- More complex mechanically
- Lower efficiency than standard wheels
- Sensitive to floor conditions
- More expensive
Wheel Configuration
1 Front
2 ↑
3 1 2
4 \ • / • = Robot center
5 / \
6 4 3
7
8Wheel 1 (FL): Rollers at +45°
9Wheel 2 (FR): Rollers at -45°
10Wheel 3 (RR): Rollers at +45°
11Wheel 4 (RL): Rollers at -45°
Parameters:
- $l_x$: Half of wheelbase (front-back distance / 2)
- $l_y$: Half of track width (left-right distance / 2)
- $r$: Wheel radius
- $(v_x, v_y, \omega)$: Robot velocity (forward, sideways, rotation)
Forward Kinematics
Interactive Mecanum Drive Simulator
From Wheel Velocities to Robot Velocity
Given wheel angular velocities $[\omega_1, \omega_2, \omega_3, \omega_4]$:
$$ \begin{bmatrix} v_x \\ v_y \\ \omega \end{bmatrix} = \frac{r}{4} \begin{bmatrix} 1 & 1 & 1 & 1 \\ -1 & 1 & 1 & -1 \\ -\frac{1}{l_x + l_y} & \frac{1}{l_x + l_y} & -\frac{1}{l_x + l_y} & \frac{1}{l_x + l_y} \end{bmatrix} \begin{bmatrix} \omega_1 \\ \omega_2 \\ \omega_3 \\ \omega_4 \end{bmatrix} $$
Python Implementation
1import numpy as np
2
3class MecanumRobot:
4 def __init__(self, lx=0.2, ly=0.2, wheel_radius=0.05):
5 """
6 Args:
7 lx: Half of wheelbase (m)
8 ly: Half of track width (m)
9 wheel_radius: Radius of wheels (m)
10 """
11 self.lx = lx
12 self.ly = ly
13 self.r = wheel_radius
14
15 # State: [x, y, theta]
16 self.state = np.array([0.0, 0.0, 0.0])
17
18 # Forward kinematics matrix
19 self.FK_matrix = (self.r / 4) * np.array([
20 [1, 1, 1, 1],
21 [-1, 1, 1, -1],
22 [-1/(lx+ly), 1/(lx+ly), -1/(lx+ly), 1/(lx+ly)]
23 ])
24
25 def forward_kinematics(self, wheel_velocities, dt):
26 """
27 Compute robot velocity from wheel velocities
28
29 Args:
30 wheel_velocities: [ω1, ω2, ω3, ω4] (rad/s)
31 dt: Time step (s)
32
33 Returns:
34 Robot velocity [vx, vy, ω] in robot frame
35 """
36 wheel_velocities = np.array(wheel_velocities)
37
38 # Robot velocity in robot frame
39 robot_vel = self.FK_matrix @ wheel_velocities
40 vx_robot, vy_robot, omega = robot_vel
41
42 # Transform to world frame
43 x, y, theta = self.state
44
45 vx_world = vx_robot * np.cos(theta) - vy_robot * np.sin(theta)
46 vy_world = vx_robot * np.sin(theta) + vy_robot * np.cos(theta)
47
48 # Update pose
49 self.state[0] += vx_world * dt
50 self.state[1] += vy_world * dt
51 self.state[2] += omega * dt
52
53 # Normalize angle
54 self.state[2] = np.arctan2(np.sin(self.state[2]),
55 np.cos(self.state[2]))
56
57 return self.state.copy()
58
59 def get_robot_velocity(self, wheel_velocities):
60 """Get robot velocity without updating state"""
61 wheel_velocities = np.array(wheel_velocities)
62 return self.FK_matrix @ wheel_velocities
63
64# Example: Forward motion
65robot = MecanumRobot(lx=0.2, ly=0.2, wheel_radius=0.05)
66
67# All wheels same speed → forward motion
68for _ in range(100):
69 robot.forward_kinematics([10, 10, 10, 10], dt=0.01)
70
71print(f"Forward motion: {robot.state}")
72
73# Reset and move sideways
74robot.state = np.array([0.0, 0.0, 0.0])
75
76# Alternating wheel pattern → sideways motion
77for _ in range(100):
78 robot.forward_kinematics([-10, 10, 10, -10], dt=0.01)
79
80print(f"Sideways motion: {robot.state}")
Inverse Kinematics
From Robot Velocity to Wheel Velocities
Given desired robot velocity $(v_x, v_y, \omega)$ in robot frame:
$$ \begin{bmatrix} \omega_1 \\ \omega_2 \\ \omega_3 \\ \omega_4 \end{bmatrix} = \frac{1}{r} \begin{bmatrix} 1 & -1 & -(l_x + l_y) \\ 1 & 1 & (l_x + l_y) \\ 1 & 1 & -(l_x + l_y) \\ 1 & -1 & (l_x + l_y) \end{bmatrix} \begin{bmatrix} v_x \\ v_y \\ \omega \end{bmatrix} $$
Implementation
1def inverse_kinematics(self, vx, vy, omega):
2 """
3 Compute wheel velocities for desired robot motion
4
5 Args:
6 vx: Desired forward velocity (m/s) in robot frame
7 vy: Desired sideways velocity (m/s) in robot frame
8 omega: Desired angular velocity (rad/s)
9
10 Returns:
11 [ω1, ω2, ω3, ω4] wheel velocities (rad/s)
12 """
13 # Inverse kinematics matrix
14 IK_matrix = (1 / self.r) * np.array([
15 [1, -1, -(self.lx + self.ly)],
16 [1, 1, (self.lx + self.ly)],
17 [1, 1, -(self.lx + self.ly)],
18 [1, -1, (self.lx + self.ly)]
19 ])
20
21 robot_vel = np.array([vx, vy, omega])
22 wheel_velocities = IK_matrix @ robot_vel
23
24 return wheel_velocities
25
26# Add to MecanumRobot class
27MecanumRobot.inverse_kinematics = inverse_kinematics
28
29# Example usage
30robot = MecanumRobot()
31
32# Forward motion
33wheels = robot.inverse_kinematics(vx=0.5, vy=0, omega=0)
34print(f"Forward: {wheels}")
35
36# Sideways motion
37wheels = robot.inverse_kinematics(vx=0, vy=0.5, omega=0)
38print(f"Sideways: {wheels}")
39
40# Diagonal motion
41wheels = robot.inverse_kinematics(vx=0.5, vy=0.5, omega=0)
42print(f"Diagonal: {wheels}")
43
44# Rotation in place
45wheels = robot.inverse_kinematics(vx=0, vy=0, omega=1.0)
46print(f"Rotation: {wheels}")
47
48# Combined motion
49wheels = robot.inverse_kinematics(vx=0.3, vy=0.2, omega=0.5)
50print(f"Combined: {wheels}")
Velocity Constraints
Apply Wheel Velocity Limits
1def apply_velocity_limits(self, wheel_velocities, omega_max=20.0):
2 """
3 Scale wheel velocities to respect limits while preserving motion direction
4
5 Args:
6 wheel_velocities: Desired wheel velocities [ω1, ω2, ω3, ω4]
7 omega_max: Maximum wheel velocity (rad/s)
8
9 Returns:
10 Limited wheel velocities
11 """
12 wheel_velocities = np.array(wheel_velocities)
13
14 # Find maximum absolute velocity
15 max_vel = np.max(np.abs(wheel_velocities))
16
17 if max_vel > omega_max:
18 # Scale all velocities proportionally
19 scale = omega_max / max_vel
20 wheel_velocities *= scale
21
22 return wheel_velocities
23
24# Add to class
25MecanumRobot.apply_velocity_limits = apply_velocity_limits
26
27# Example
28robot = MecanumRobot()
29wheels = robot.inverse_kinematics(vx=2.0, vy=2.0, omega=5.0)
30print(f"Unlimited: {wheels}")
31
32wheels_limited = robot.apply_velocity_limits(wheels, omega_max=15.0)
33print(f"Limited: {wheels_limited}")
Motion Control
Velocity Controller
1def velocity_control(self, vx_desired, vy_desired, omega_desired,
2 world_frame=False):
3 """
4 Control robot to achieve desired velocity
5
6 Args:
7 vx_desired: Desired x velocity (m/s)
8 vy_desired: Desired y velocity (m/s)
9 omega_desired: Desired angular velocity (rad/s)
10 world_frame: If True, velocities are in world frame
11
12 Returns:
13 Wheel velocities [ω1, ω2, ω3, ω4]
14 """
15 if world_frame:
16 # Transform to robot frame
17 theta = self.state[2]
18 vx_robot = vx_desired * np.cos(theta) + vy_desired * np.sin(theta)
19 vy_robot = -vx_desired * np.sin(theta) + vy_desired * np.cos(theta)
20 else:
21 vx_robot = vx_desired
22 vy_robot = vy_desired
23
24 # Inverse kinematics
25 wheel_velocities = self.inverse_kinematics(vx_robot, vy_robot,
26 omega_desired)
27
28 # Apply limits
29 wheel_velocities = self.apply_velocity_limits(wheel_velocities)
30
31 return wheel_velocities
32
33# Add to class
34MecanumRobot.velocity_control = velocity_control
Position Controller
1def navigate_to_point(self, target_x, target_y, target_theta=None,
2 kp_linear=1.0, kp_angular=2.0):
3 """
4 Navigate to target position
5
6 Args:
7 target_x, target_y: Target position (world frame)
8 target_theta: Target orientation (None = don't care)
9 kp_linear: Linear velocity gain
10 kp_angular: Angular velocity gain
11
12 Returns:
13 Wheel velocities and distance to target
14 """
15 x, y, theta = self.state
16
17 # Position error (world frame)
18 dx_world = target_x - x
19 dy_world = target_y - y
20 distance = np.sqrt(dx_world**2 + dy_world**2)
21
22 # Transform to robot frame
23 dx_robot = dx_world * np.cos(theta) + dy_world * np.sin(theta)
24 dy_robot = -dx_world * np.sin(theta) + dy_world * np.cos(theta)
25
26 # Desired velocities (robot frame)
27 vx_desired = kp_linear * dx_robot
28 vy_desired = kp_linear * dy_robot
29
30 # Orientation control
31 if target_theta is not None:
32 theta_error = np.arctan2(np.sin(target_theta - theta),
33 np.cos(target_theta - theta))
34 omega_desired = kp_angular * theta_error
35 else:
36 omega_desired = 0
37
38 # Velocity limits
39 v_max = 1.0 # m/s
40 omega_max_robot = 2.0 # rad/s
41
42 vx_desired = np.clip(vx_desired, -v_max, v_max)
43 vy_desired = np.clip(vy_desired, -v_max, v_max)
44 omega_desired = np.clip(omega_desired, -omega_max_robot, omega_max_robot)
45
46 # Inverse kinematics
47 wheel_velocities = self.inverse_kinematics(vx_desired, vy_desired,
48 omega_desired)
49 wheel_velocities = self.apply_velocity_limits(wheel_velocities)
50
51 return wheel_velocities, distance
52
53# Add to class
54MecanumRobot.navigate_to_point = navigate_to_point
55
56# Example: Navigate to point
57robot = MecanumRobot()
58target = (2.0, 1.5, np.pi/4)
59
60dt = 0.05
61trajectory = [robot.state.copy()]
62
63for _ in range(500):
64 wheels, dist = robot.navigate_to_point(*target)
65
66 if dist < 0.05:
67 break
68
69 robot.forward_kinematics(wheels, dt)
70 trajectory.append(robot.state.copy())
71
72print(f"Final position: {robot.state}")
73print(f"Distance to target: {dist:.4f} m")
Motion Primitives
Common Maneuvers
1class MecanumMotionPrimitives:
2 @staticmethod
3 def forward(distance, speed=0.5):
4 """Move forward"""
5 return {
6 'vx': speed,
7 'vy': 0,
8 'omega': 0,
9 'duration': distance / speed
10 }
11
12 @staticmethod
13 def sideways(distance, speed=0.5):
14 """Move sideways (positive = right)"""
15 direction = 1 if distance > 0 else -1
16 return {
17 'vx': 0,
18 'vy': direction * speed,
19 'omega': 0,
20 'duration': abs(distance) / speed
21 }
22
23 @staticmethod
24 def diagonal(distance_x, distance_y, speed=0.5):
25 """Move diagonally"""
26 distance = np.sqrt(distance_x**2 + distance_y**2)
27 duration = distance / speed
28
29 vx = distance_x / duration
30 vy = distance_y / duration
31
32 return {
33 'vx': vx,
34 'vy': vy,
35 'omega': 0,
36 'duration': duration
37 }
38
39 @staticmethod
40 def rotate(angle, angular_speed=1.0):
41 """Rotate in place"""
42 duration = abs(angle) / angular_speed
43 omega = angular_speed if angle > 0 else -angular_speed
44
45 return {
46 'vx': 0,
47 'vy': 0,
48 'omega': omega,
49 'duration': duration
50 }
51
52 @staticmethod
53 def strafe_arc(radius, angle, speed=0.5):
54 """Move in arc while maintaining orientation"""
55 arc_length = radius * abs(angle)
56 duration = arc_length / speed
57
58 # Velocity perpendicular to radius
59 vx = speed * np.cos(angle / 2)
60 vy = speed * np.sin(angle / 2) * (1 if angle > 0 else -1)
61
62 return {
63 'vx': vx,
64 'vy': vy,
65 'omega': 0,
66 'duration': duration
67 }
68
69# Example: Execute sequence
70robot = MecanumRobot()
71
72sequence = [
73 MecanumMotionPrimitives.forward(1.0, 0.5),
74 MecanumMotionPrimitives.sideways(0.5, 0.3),
75 MecanumMotionPrimitives.diagonal(0.5, 0.5, 0.4),
76 MecanumMotionPrimitives.rotate(np.pi/2, 1.0)
77]
78
79dt = 0.01
80for primitive in sequence:
81 t = 0
82 while t < primitive['duration']:
83 wheels = robot.inverse_kinematics(
84 primitive['vx'], primitive['vy'], primitive['omega']
85 )
86 robot.forward_kinematics(wheels, dt)
87 t += dt
88
89print(f"Final pose: {robot.state}")
Slippage Compensation
Mecanum wheels are prone to slippage, especially on smooth surfaces.
1class MecanumRobotWithSlippage(MecanumRobot):
2 def __init__(self, lx=0.2, ly=0.2, wheel_radius=0.05,
3 slip_factor_forward=0.95,
4 slip_factor_sideways=0.85):
5 """
6 Args:
7 slip_factor_forward: Efficiency in forward direction (0-1)
8 slip_factor_sideways: Efficiency in sideways direction (0-1)
9 """
10 super().__init__(lx, ly, wheel_radius)
11 self.slip_forward = slip_factor_forward
12 self.slip_sideways = slip_factor_sideways
13
14 def compensate_slippage(self, vx, vy, omega):
15 """Compensate for expected slippage"""
16 vx_compensated = vx / self.slip_forward
17 vy_compensated = vy / self.slip_sideways
18
19 return vx_compensated, vy_compensated, omega
20
21 def inverse_kinematics_compensated(self, vx, vy, omega):
22 """IK with slippage compensation"""
23 vx_comp, vy_comp, omega_comp = self.compensate_slippage(vx, vy, omega)
24 return self.inverse_kinematics(vx_comp, vy_comp, omega_comp)
25
26# Example
27robot = MecanumRobotWithSlippage(slip_factor_sideways=0.8)
28wheels = robot.inverse_kinematics_compensated(vx=0.5, vy=0.5, omega=0)
29print(f"Compensated wheel velocities: {wheels}")
Visualization
1def simulate_and_plot(robot, control_sequence, dt=0.01):
2 """Simulate and visualize mecanum robot motion"""
3 import matplotlib.pyplot as plt
4
5 trajectory = [robot.state.copy()]
6
7 for cmd in control_sequence:
8 t = 0
9 while t < cmd['duration']:
10 wheels = robot.inverse_kinematics(cmd['vx'], cmd['vy'], cmd['omega'])
11 robot.forward_kinematics(wheels, dt)
12 trajectory.append(robot.state.copy())
13 t += dt
14
15 trajectory = np.array(trajectory)
16
17 # Plot
18 fig, ax = plt.subplots(figsize=(10, 10))
19
20 # Trajectory
21 ax.plot(trajectory[:, 0], trajectory[:, 1], 'b-', linewidth=2, label='Path')
22 ax.plot(trajectory[0, 0], trajectory[0, 1], 'go', markersize=12, label='Start')
23 ax.plot(trajectory[-1, 0], trajectory[-1, 1], 'ro', markersize=12, label='End')
24
25 # Robot orientation at intervals
26 interval = len(trajectory) // 15
27 for i in range(0, len(trajectory), interval):
28 x, y, theta = trajectory[i]
29
30 # Draw robot body
31 size = 0.15
32 corners = np.array([
33 [size, size],
34 [-size, size],
35 [-size, -size],
36 [size, -size],
37 [size, size]
38 ])
39
40 # Rotate corners
41 rot = np.array([
42 [np.cos(theta), -np.sin(theta)],
43 [np.sin(theta), np.cos(theta)]
44 ])
45 corners_rot = (rot @ corners.T).T
46 corners_rot[:, 0] += x
47 corners_rot[:, 1] += y
48
49 ax.plot(corners_rot[:, 0], corners_rot[:, 1], 'k-', linewidth=1)
50
51 # Direction arrow
52 dx = 0.1 * np.cos(theta)
53 dy = 0.1 * np.sin(theta)
54 ax.arrow(x, y, dx, dy, head_width=0.05, head_length=0.05,
55 fc='red', ec='red')
56
57 ax.grid(True)
58 ax.axis('equal')
59 ax.set_xlabel('X (m)')
60 ax.set_ylabel('Y (m)')
61 ax.legend()
62 ax.set_title('Mecanum Robot Trajectory')
63 plt.show()
64
65# Example: Complex maneuver
66robot = MecanumRobot()
67
68maneuver = [
69 {'vx': 0.5, 'vy': 0, 'omega': 0, 'duration': 2.0}, # Forward
70 {'vx': 0, 'vy': 0.5, 'omega': 0, 'duration': 2.0}, # Right
71 {'vx': 0.5, 'vy': 0.5, 'omega': 0, 'duration': 2.0}, # Diagonal
72 {'vx': 0, 'vy': 0, 'omega': 1.0, 'duration': np.pi}, # Rotate 180°
73 {'vx': 0.5, 'vy': -0.5, 'omega': 0.5, 'duration': 3.0} # Combined
74]
75
76simulate_and_plot(robot, maneuver)
Further Reading
Related Snippets
- Denavit-Hartenberg (DH) Parameters
Denavit-Hartenberg convention for systematically describing robot manipulator … - DH Parameter Examples and Common Configurations
Practical examples of DH parameter configurations for common robot manipulators … - Four-Wheel Independent Drive Kinematics
Forward and inverse kinematics for four-wheel independent drive robots with … - Front-Wheel Drive (FWD) Kinematics
Forward and inverse kinematics for front-wheel drive robots with Ackermann … - Jacobian Matrices for Forward and Inverse Kinematics
Jacobian matrices relate joint velocities to end-effector velocities, essential … - Rear-Wheel Drive (RWD) Kinematics
Forward and inverse kinematics for rear-wheel drive robots with front-wheel … - Two-Wheel Balancing Robot Kinematics and Control
Kinematics and control for two-wheel self-balancing robots (inverted pendulum on … - Two-Wheel Differential Drive Robot Kinematics
Forward and inverse kinematics for two-wheel differential drive robots, the most …