manipulationtrajectoryrobot-armplanningjoint-space

Trajectory Types: Joint Space vs Cartesian Space Planning

Compare Joint Space and Cartesian Space trajectory planning in detail — when to use each and how to implement from scratch.

Nguyễn Anh Tuấn12 tháng 3, 20269 phút đọc
Trajectory Types: Joint Space vs Cartesian Space Planning

After mastering kinematics and dynamics in the previous post, the next question is: How does the robot move from point A to point B? This is the trajectory planning problem — and how you plan the trajectory determines whether the robot moves fast or slow, smooth or jerky, safe or dangerous.

There are two fundamental spaces for planning: Joint Space and Cartesian Space. Each has its own strengths and weaknesses, and choosing wrong can lead to collisions, singularities, or inefficient trajectories.

Trajectory planning concept

Joint Space vs Cartesian Space — Core Differences

Joint Space Planning

In Joint Space, the trajectory is planned directly on joint angles $q(t)$. The robot interpolates from initial joint configuration $q_0$ to the goal $q_f$.

Characteristics:

  • TCP (Tool Center Point) path in Cartesian is unpredictable — can curve arbitrarily
  • No singularity — IK is not needed during motion
  • Fastest — each joint moves along the shortest path
  • Joint limits guaranteed — easy to check since we work directly with joint angles

Cartesian Space Planning

In Cartesian Space, the trajectory is planned on the end-effector position and orientation in 3D space. The robot follows a straight line (or arc) in Cartesian coordinates.

Characteristics:

  • TCP path is predictable — exact straight line or arc
  • May encounter singularities — IK must be solved at every point along the trajectory
  • Slower — constrained by the Cartesian path
  • Essential for many applications — welding, painting, assembly all require straight lines
Criterion Joint Space Cartesian Space
TCP Path Unpredictable Straight line/arc
Speed Fastest Constrained
Singularity Never Possible
Joint limits Easy to ensure May violate
Use when Free movement Path control needed

Joint Space Trajectory — Point-to-Point

Linear Interpolation

The simplest approach — linear interpolation between $q_0$ and $q_f$:

q(s) = q_0 + s × (q_f - q_0),  s ∈ [0, 1]

But linear interpolation is not smooth — velocity jumps abruptly at start and end.

Cubic Polynomial Interpolation

Add constraints for zero velocity at start and end:

q(t) = a₀ + a₁t + a₂t² + a₃t³

With 4 constraints:

  • $q(0) = q_0$, $q(T) = q_f$
  • $\dot{q}(0) = 0$, $\dot{q}(T) = 0$
import numpy as np
import matplotlib.pyplot as plt

def cubic_trajectory(q0, qf, T, dt=0.01):
    """Joint space cubic polynomial trajectory."""
    a0 = q0
    a1 = 0  # Zero initial velocity
    a2 = 3 * (qf - q0) / T**2
    a3 = -2 * (qf - q0) / T**3

    t = np.arange(0, T + dt, dt)
    q = a0 + a1*t + a2*t**2 + a3*t**3
    qd = a1 + 2*a2*t + 3*a3*t**2
    qdd = 2*a2 + 6*a3*t

    return t, q, qd, qdd

# Trajectory for 1 joint: 0° to 90° in 2 seconds
T = 2.0
t, q, qd, qdd = cubic_trajectory(0, np.pi/2, T)

fig, axes = plt.subplots(3, 1, figsize=(10, 8), sharex=True)
axes[0].plot(t, np.degrees(q), 'b-', linewidth=2)
axes[0].set_ylabel('Position (deg)')
axes[0].set_title('Cubic Polynomial Joint Trajectory')
axes[0].grid(True)

axes[1].plot(t, np.degrees(qd), 'r-', linewidth=2)
axes[1].set_ylabel('Velocity (deg/s)')
axes[1].grid(True)

axes[2].plot(t, np.degrees(qdd), 'g-', linewidth=2)
axes[2].set_ylabel('Acceleration (deg/s²)')
axes[2].set_xlabel('Time (s)')
axes[2].grid(True)

plt.tight_layout()
plt.savefig('cubic_trajectory.png', dpi=150)
plt.show()

Quintic Polynomial Interpolation

Add zero acceleration constraints at start and end — smoother than cubic:

def quintic_trajectory(q0, qf, T, dt=0.01):
    """Joint space quintic polynomial trajectory."""
    a0 = q0
    a1 = 0
    a2 = 0
    a3 = 10 * (qf - q0) / T**3
    a4 = -15 * (qf - q0) / T**4
    a5 = 6 * (qf - q0) / T**5

    t = np.arange(0, T + dt, dt)
    q = a0 + a1*t + a2*t**2 + a3*t**3 + a4*t**4 + a5*t**5
    qd = a1 + 2*a2*t + 3*a3*t**2 + 4*a4*t**3 + 5*a5*t**4
    qdd = 2*a2 + 6*a3*t + 12*a4*t**2 + 20*a5*t**3

    return t, q, qd, qdd

Cartesian Space Trajectory — Straight Lines in 3D

Position Interpolation

Linear position interpolation in Cartesian is straightforward:

p(s) = p_0 + s × (p_f - p_0),  s ∈ [0, 1]

But the complication lies in orientation — rotation matrices cannot be linearly interpolated.

SLERP — Spherical Linear Interpolation for Orientation

SLERP interpolates between two quaternions on the hypersphere surface, ensuring:

  • Shortest rotation between two orientations
  • Constant angular velocity
  • Result is always a valid unit quaternion
q(s) = q_0 × sin((1-s)Ω) / sin(Ω) + q_f × sin(sΩ) / sin(Ω)

Where $\Omega = \arccos(q_0 \cdot q_f)$.

import numpy as np
from spatialmath import SE3, UnitQuaternion
import roboticstoolbox as rtb
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def cartesian_trajectory(T_start, T_end, n_points=100):
    """Linear Cartesian trajectory with SLERP orientation."""
    positions = []
    orientations = []

    q_start = UnitQuaternion(T_start.R)
    q_end = UnitQuaternion(T_end.R)

    for i in range(n_points):
        s = i / (n_points - 1)
        pos = T_start.t + s * (T_end.t - T_start.t)
        q = q_start.interp(q_end, s)
        positions.append(pos)
        orientations.append(q)

    return np.array(positions), orientations

T_start = SE3(0.4, -0.2, 0.3) * SE3.Rz(0)
T_end = SE3(0.4, 0.3, 0.5) * SE3.Rz(np.pi/2)

positions, orientations = cartesian_trajectory(T_start, T_end)

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot(positions[:, 0], positions[:, 1], positions[:, 2],
        'b-', linewidth=2, label='Cartesian path')
ax.scatter(*T_start.t, c='green', s=100, label='Start')
ax.scatter(*T_end.t, c='red', s=100, label='End')
ax.set_xlabel('X (m)')
ax.set_ylabel('Y (m)')
ax.set_zlabel('Z (m)')
ax.set_title('Cartesian Linear Trajectory')
ax.legend()
plt.savefig('cartesian_trajectory.png', dpi=150)
plt.show()

Linear trajectory

Visual Comparison: Joint vs Cartesian TCP Path

This is the most visible difference — same start and end points, but completely different TCP paths:

import roboticstoolbox as rtb
import numpy as np
import matplotlib.pyplot as plt
from spatialmath import SE3

ur5e = rtb.models.UR5()

q_start = np.array([0, -np.pi/3, np.pi/3, -np.pi/2, np.pi/2, 0])
q_end = np.array([np.pi/2, -np.pi/4, np.pi/4, -np.pi/3, np.pi/3, np.pi/4])

n_points = 100

# === JOINT SPACE trajectory ===
joint_positions = []
for i in range(n_points):
    s = i / (n_points - 1)
    q = q_start + s * (q_end - q_start)
    T = ur5e.fkine(q)
    joint_positions.append(T.t)
joint_positions = np.array(joint_positions)

# === CARTESIAN SPACE trajectory ===
T_start = ur5e.fkine(q_start)
T_end = ur5e.fkine(q_end)

cart_positions = []
for i in range(n_points):
    s = i / (n_points - 1)
    pos = T_start.t + s * (T_end.t - T_start.t)
    cart_positions.append(pos)
cart_positions = np.array(cart_positions)

fig = plt.figure(figsize=(14, 6))

ax1 = fig.add_subplot(121, projection='3d')
ax1.plot(joint_positions[:, 0], joint_positions[:, 1], joint_positions[:, 2],
         'b-', linewidth=2)
ax1.scatter(*joint_positions[0], c='green', s=100)
ax1.scatter(*joint_positions[-1], c='red', s=100)
ax1.set_title('Joint Space — TCP Path (curved)')

ax2 = fig.add_subplot(122, projection='3d')
ax2.plot(cart_positions[:, 0], cart_positions[:, 1], cart_positions[:, 2],
         'r-', linewidth=2)
ax2.scatter(*cart_positions[0], c='green', s=100)
ax2.scatter(*cart_positions[-1], c='red', s=100)
ax2.set_title('Cartesian Space — TCP Path (straight)')

plt.tight_layout()
plt.savefig('joint_vs_cartesian.png', dpi=150)
plt.show()

Time Parameterization — Controlling Speed Along the Path

A geometric path only describes the shape of the trajectory. To execute it, you need time parameterization — mapping $s \in [0,1]$ to $t \in [0, T]$.

Common Time Parameterization Methods

Method Description Use case
Linear $s(t) = t/T$ Constant velocity
Trapezoidal Accel -> cruise -> decel Most common
S-curve Trapezoidal + bounded jerk Smooth motion
Polynomial $s(t)$ = 3rd/5th/7th order poly Zero start/end velocity
def trapezoidal_time_scaling(T, v_max, dt=0.01):
    """Trapezoidal velocity time scaling."""
    t_acc = T - 1.0 / v_max
    if t_acc < 0:
        t_acc = T / 3

    a = v_max / t_acc

    t = np.arange(0, T + dt, dt)
    s = np.zeros_like(t)
    sd = np.zeros_like(t)

    for i, ti in enumerate(t):
        if ti < t_acc:
            s[i] = 0.5 * a * ti**2
            sd[i] = a * ti
        elif ti < T - t_acc:
            s[i] = 0.5 * a * t_acc**2 + v_max * (ti - t_acc)
            sd[i] = v_max
        else:
            t_rem = T - ti
            s[i] = 1.0 - 0.5 * a * t_rem**2
            sd[i] = a * t_rem

    return t, s, sd

Waypoint Planning — Multiple Intermediate Points

In practice, robots rarely move between just 2 points. Waypoint planning plans through multiple intermediate points:

import roboticstoolbox as rtb
import numpy as np

ur5e = rtb.models.UR5()

# Define 5 waypoints (joint configurations)
waypoints = np.array([
    [0, -np.pi/3, np.pi/3, -np.pi/2, np.pi/2, 0],
    [np.pi/4, -np.pi/4, np.pi/4, -np.pi/3, np.pi/3, np.pi/6],
    [np.pi/2, -np.pi/6, np.pi/6, -np.pi/4, np.pi/4, np.pi/4],
    [np.pi/4, -np.pi/3, np.pi/2, -np.pi/2, np.pi/2, np.pi/6],
    [0, -np.pi/4, np.pi/3, -np.pi/3, np.pi/3, 0],
])

traj = rtb.mstraj(waypoints, dt=0.05, tacc=0.5, qdmax=[2.0]*6)

print(f"Trajectory duration: {traj.t[-1]:.2f}s")
print(f"Number of points: {len(traj.t)}")

tcp_positions = []
for q in traj.q:
    T = ur5e.fkine(q)
    tcp_positions.append(T.t)
tcp_positions = np.array(tcp_positions)

When to Use Which?

Scenario Choice Reason
Fast free movement between distant points Joint Space Fastest, no singularity
Straight-line welding Cartesian Need precise straight line
Surface spray painting Cartesian Need uniform distance
Obstacle avoidance Depends on planner Use motion planner (RRT, PRM)
Pick-and-place (approach) Joint Space Free movement to vicinity
Pick-and-place (grasp) Cartesian Vertical approach needed

Robot application

Handling Singularity in Cartesian Planning

When planning in Cartesian space, singularity is the biggest challenge. The most common solution:

Damped Least Squares (DLS)

Instead of $\dot{q} = J^{-1}v$, use:

q̇ = J^T (JJ^T + λ²I)^{-1} v

The damping parameter $\lambda$ prevents joint velocity blow-up near singularities.

def damped_least_squares(J, v, damping=0.01):
    """Compute joint velocities using Damped Least Squares."""
    JJT = J @ J.T
    identity = np.eye(JJT.shape[0])
    q_dot = J.T @ np.linalg.solve(JJT + damping**2 * identity, v)
    return q_dot

References

Conclusion

In this post, we analyzed the two fundamental trajectory planning approaches:

  • Joint Space — fast, safe, but no TCP path control
  • Cartesian Space — precise TCP path but faces singularity issues
  • SLERP — accurate orientation interpolation on quaternions
  • Time parameterization — converting geometric paths into executable trajectories

In the next post, we will dive into MoveJ and MoveL — the two most fundamental commands in industrial robot programming, which are the practical realization of Joint Space and Cartesian Space planning.

NT

Nguyễn Anh Tuấn

Robotics & AI Engineer. Building VnRobo — sharing knowledge about robot learning, VLA models, and automation.

Bài viết liên quan

Tutorial
Integration: ROS 2 MoveIt2, URScript và Real Robot Deploy
ros2moveit2urscriptrobot-armdeploymentPhần 8

Integration: ROS 2 MoveIt2, URScript và Real Robot Deploy

Tổng hợp toàn bộ series: tích hợp trajectory planning với MoveIt2, URScript và deploy lên robot thật qua ros2_control.

5/4/202611 phút đọc
Deep Dive
Advanced Trajectories: Spline, B-Spline và Time-Optimal Planning
splineb-splinetime-optimaltrajectoryPhần 7

Advanced Trajectories: Spline, B-Spline và Time-Optimal Planning

Kỹ thuật nâng cao: cubic spline, B-spline interpolation và TOPPRA time-optimal trajectory planning cho robot arm.

1/4/202610 phút đọc
Deep Dive
Motion Profiles: Trapezoidal, S-Curve và Polynomial Trajectories
trapezoidals-curvetrajectorymotion-profilePhần 6

Motion Profiles: Trapezoidal, S-Curve và Polynomial Trajectories

So sánh chi tiết 3 loại motion profile phổ biến — trapezoidal, S-curve và polynomial — với code Python từ đầu.

28/3/202612 phút đọc