🔄 Transformation Foundations
🎯 What are Transformations?
Transformations are mathematical operations that map points from one coordinate system to another. They are fundamental to computer graphics, robotics, computer vision, and geometric modeling.
Core Concepts
Linear transformations: Preserve lines and origin. Affine transformations: Preserve lines but not necessarily origin. Projective: Preserve lines but not parallelism.
import numpy as np
# Basic transformation operations
def translate(point, tx, ty):
return point + np.array([tx, ty])
def rotate_2d(point, angle):
theta = np.radians(angle)
rot = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
return rot @ point
def scale(point, sx, sy):
return point * np.array([sx, sy])📐 Translation & Vectors
🎯 Vector Translation
Translation moves every point by a constant vector. It's the simplest transformation and fundamental to all graphics operations.
Translation Properties
Vector addition: p' = p + t. Commutative: Translations commute (t1 + t2 = t2 + t1). Inverse: Inverse translation is -t.
import numpy as np
class Translation:
def __init__(self, tx, ty, tz=0):
self.t = np.array([tx, ty, tz])
def apply(self, points):
return points + self.t
def inverse(self):
return Translation(-self.t[0], -self.t[1], -self.t[2])
def compose(self, other):
return Translation(*(self.t + other.t))
# Example
t1 = Translation(2, 3)
point = np.array([1, 1, 0])
result = t1.apply(point)
print(f"Translated: {result}")🔄 Rotation Matrices
🎯 Rotations in 2D and 3D
Rotation matrices are orthogonal matrices with determinant 1. They represent rotations around axes in 2D and 3D space.
Rotation Properties
Orthogonal: R^T R = I. Determinant: det(R) = 1. Group: Rotations form SO(2) and SO(3) groups.
import numpy as np
def rotation_2d(theta):
"""2D rotation matrix"""
c, s = np.cos(theta), np.sin(theta)
return np.array([[c, -s], [s, c]])
def rotation_3d_x(angle):
"""Rotation around X axis"""
c, s = np.cos(angle), np.sin(angle)
return np.array([
[1, 0, 0],
[0, c, -s],
[0, s, c]
])
def rotation_3d_y(angle):
"""Rotation around Y axis"""
c, s = np.cos(angle), np.sin(angle)
return np.array([
[c, 0, s],
[0, 1, 0],
[-s, 0, c]
])
def rotation_3d_z(angle):
"""Rotation around Z axis"""
c, s = np.cos(angle), np.sin(angle)
return np.array([
[c, -s, 0],
[s, c, 0],
[0, 0, 1]
])📏 Scaling & Reflection
🎯 Scale and Mirror Operations
Scaling changes the size of objects. Reflection flips objects across axes. Both are linear transformations.
Scaling Properties
Uniform scaling: sx = sy (preserves aspect ratio). Non-uniform: sx ≠ sy (stretches). Reflection: Scaling with negative factors.
def scale_matrix(sx, sy, sz=1):
return np.diag([sx, sy, sz])
def reflect_x():
return np.diag([-1, 1, 1])
def reflect_y():
return np.diag([1, -1, 1])
def reflect_z():
return np.diag([1, 1, -1])
# Example: Uniform scaling by 2
S = scale_matrix(2, 2, 2)
point = np.array([1, 1, 1])
result = S @ point
print(f"Scaled: {result}")🧊 Homogeneous Coordinates
🎯 Unified Representation
Homogeneous coordinates add a fourth coordinate (w) to represent points. This allows translations and linear transformations in the same matrix.
Why Homogeneous?
Advantages: Unified transformation matrices, representation of points at infinity, easy composition of transformations.
def translation_matrix(tx, ty, tz=0):
return np.array([
[1, 0, 0, tx],
[0, 1, 0, ty],
[0, 0, 1, tz],
[0, 0, 0, 1]
])
def transform_point(mat, point):
# Convert to homogeneous, transform, convert back
h_point = np.append(point, 1)
result = mat @ h_point
return result[:3] / result[3]
# Compose transformations
T = translation_matrix(2, 3, 0)
point = np.array([1, 1, 0])
result = transform_point(T, point)
print(f"Transformed: {result}")📊 Affine Transformations
🎯 Linear + Translation
Affine transformations combine linear transformations (rotation, scaling, shear) with translation. They preserve lines and ratios.
Affine Properties
Preserves: Lines, parallelism, collinearity, ratios of distances on lines. Does not preserve: Angles, lengths (unless isometric).
class AffineTransform:
def __init__(self, matrix, translation):
self.A = matrix # 2x2 for 2D, 3x3 for 3D
self.t = translation
def apply(self, points):
return points @ self.A.T + self.t
def compose(self, other):
return AffineTransform(
self.A @ other.A,
self.A @ other.t + self.t
)
def inverse(self):
inv_A = np.linalg.inv(self.A)
return AffineTransform(inv_A, -inv_A @ self.t)
# 2D affine: rotation + translation
theta = np.radians(30)
R = np.array([[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]])
t = np.array([2, 3])
affine = AffineTransform(R, t)🎯 Projection Transforms
🎯 Orthographic & Perspective
Projection transforms map 3D points to 2D image planes. Orthographic preserves parallelism. Perspective mimics human vision.
Projection Types
Orthographic: No perspective distortion, used in engineering drawings. Perspective: Objects farther away appear smaller, realistic rendering.
def orthographic_projection():
return np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 1]
])
def perspective_projection(fov, aspect, near, far):
"""Perspective projection matrix"""
f = 1 / np.tan(np.radians(fov) / 2)
return np.array([
[f/aspect, 0, 0, 0],
[0, f, 0, 0],
[0, 0, (far+near)/(near-far), 2*far*near/(near-far)],
[0, 0, -1, 0]
])🌀 Quaternions & Rotations
🎯 3D Rotation Representation
Quaternions provide a compact, singularity-free representation of 3D rotations. They are essential for smooth interpolation (SLERP).
Quaternion Properties
Unit quaternion: ||q|| = 1 represents rotation. SLERP: Spherical linear interpolation for smooth animation. No gimbal lock: Unlike Euler angles.
import numpy as np
class Quaternion:
def __init__(self, w, x, y, z):
self.w = w
self.v = np.array([x, y, z])
def normalize(self):
norm = np.sqrt(self.w**2 + np.sum(self.v**2))
return Quaternion(self.w/norm, *(self.v/norm))
def to_rotation_matrix(self):
w, x, y, z = self.w, *self.v
return np.array([
[1-2*y*y-2*z*z, 2*x*y-2*z*w, 2*x*z+2*y*w],
[2*x*y+2*z*w, 1-2*x*x-2*z*z, 2*y*z-2*x*w],
[2*x*z-2*y*w, 2*y*z+2*x*w, 1-2*x*x-2*y*y]
])
def slerp(self, other, t):
# Spherical linear interpolation
dot = self.w*other.w + np.dot(self.v, other.v)
if dot < 0:
other = Quaternion(-other.w, *(-other.v))
dot = -dot
if dot > 0.9995:
# Linear interpolation for small angles
result = Quaternion(
self.w + t*(other.w - self.w),
*(self.v + t*(other.v - self.v))
)
return result.normalize()
theta = np.arccos(np.clip(dot, -1, 1))
return Quaternion(
np.sin((1-t)*theta)/np.sin(theta) * self.w + np.sin(t*theta)/np.sin(theta) * other.w,
*(np.sin((1-t)*theta)/np.sin(theta) * self.v + np.sin(t*theta)/np.sin(theta) * other.v)
).normalize()📐 Euler Angles
🎯 Yaw, Pitch, Roll
Euler angles decompose 3D rotation into three sequential rotations around coordinate axes. Common in aerospace, robotics, and gaming.
Euler Angle Issues
Gimbal lock: Loss of one degree of freedom at certain angles. Ambiguity: Multiple Euler angle representations for same rotation.
def euler_to_rotation(yaw, pitch, roll):
"""Convert Euler angles to rotation matrix (XYZ order)"""
Rz = rotation_3d_z(yaw)
Ry = rotation_3d_y(pitch)
Rx = rotation_3d_x(roll)
return Rz @ Ry @ Rx
def rotation_to_euler(R):
"""Extract Euler angles from rotation matrix"""
# Simplified for ZYX order
sy = np.sqrt(R[0,0]**2 + R[1,0]**2)
singular = sy < 1e-6
if not singular:
yaw = np.arctan2(R[1,0], R[0,0])
pitch = np.arctan2(-R[2,0], sy)
roll = np.arctan2(R[2,1], R[2,2])
else:
yaw = np.arctan2(-R[1,2], R[1,1])
pitch = np.arctan2(-R[2,0], sy)
roll = 0
return yaw, pitch, roll🔧 Rigid Body Transformations
🎯 Rotation + Translation Only
Rigid body transformations preserve distances and angles. They are the foundation of robotics, animation, and physics simulation.
Rigid Motion Properties
SE(3): Special Euclidean group — rigid transformations in 3D. DOF: 6 degrees of freedom (3 rotation + 3 translation).
class RigidTransform:
def __init__(self, R, t):
self.R = R # Rotation matrix (3x3)
self.t = t # Translation vector (3)
def apply(self, points):
return points @ self.R.T + self.t
def inverse(self):
return RigidTransform(self.R.T, -self.R.T @ self.t)
def compose(self, other):
return RigidTransform(self.R @ other.R, self.R @ other.t + self.t)
def to_homogeneous(self):
H = np.eye(4)
H[:3, :3] = self.R
H[:3, 3] = self.t
return H📷 Camera Transformations
🎯 World → Camera → Image
Camera transformations convert 3D world coordinates to 2D image coordinates through extrinsic (pose) and intrinsic (camera) parameters.
Camera Model
Extrinsic: World-to-camera transformation (R, t). Intrinsic: Focal length, principal point, distortion. Projection: 3D → 2D using perspective projection.
class Camera:
def __init__(self, fx, fy, cx, cy):
self.K = np.array([
[fx, 0, cx],
[0, fy, cy],
[0, 0, 1]
])
def project(self, points_3d, R, t):
# Transform to camera coordinates
points_cam = points_3d @ R.T + t
# Project to image plane
points_img = points_cam[:, :2] / points_cam[:, 2:]
# Apply intrinsic matrix
points_img = points_img @ self.K[:2, :2].T + self.K[:2, 2]
return points_img
def unproject(self, points_2d, depth, R, t):
# Inverse projection
points_hom = np.column_stack([points_2d, np.ones(len(points_2d))])
points_cam = points_hom @ np.linalg.inv(self.K).T
points_cam *= depth[:, np.newaxis]
points_3d = points_cam @ R - t
return points_3d🌍 Coordinate Systems
🎯 World, Local, Object, View
Different coordinate systems in graphics and robotics: world space, object space, camera space, screen space, and model space.
Coordinate Spaces
Object space: Local to object. World space: Global reference frame. View space: Camera-centric. Screen space: Final 2D pixel coordinates.
class CoordinateSystem:
def __init__(self, origin, axes):
self.origin = origin
self.axes = axes # Matrix of basis vectors
def world_to_local(self, point):
return self.axes.T @ (point - self.origin)
def local_to_world(self, point):
return self.origin + self.axes @ point
# Example: Create a local coordinate system
origin = np.array([1, 2, 3])
axes = np.eye(3) # Aligned with world axes
local = CoordinateSystem(origin, axes)
world_point = np.array([2, 3, 4])
local_point = local.world_to_local(world_point)
print(f"Local point: {local_point}")🎨 Graphics Pipeline
🎯 Transformation Pipeline
Modern graphics pipelines use a series of transformations: model → world → view → projection → viewport transformations.
Pipeline Stages
Model-View-Projection (MVP): Combines model, view, and projection matrices. Viewport: Maps NDC to screen coordinates. Vertex shader: Executes per-vertex transformations.
def create_mvp(model, view, projection):
"""Create Model-View-Projection matrix"""
return projection @ view @ model
class GraphicsPipeline:
def __init__(self, viewport_width, viewport_height):
self.viewport_width = viewport_width
self.viewport_height = viewport_height
def transform_vertex(self, vertex, model, view, projection):
"""Apply full transformation pipeline to a vertex"""
# Model -> World
world_pos = model @ vertex
# World -> View
view_pos = view @ world_pos
# View -> Clip
clip_pos = projection @ view_pos
# Perspective divide
ndc_pos = clip_pos[:3] / clip_pos[3]
# Viewport mapping
screen_x = (ndc_pos[0] + 1) * self.viewport_width / 2
screen_y = (1 - ndc_pos[1]) * self.viewport_height / 2
return np.array([screen_x, screen_y, ndc_pos[2]])🚀 Applications & Tools
🎯 Real-World Applications
Transformations are everywhere: 3D gaming, robotics, computer vision, CAD, animation, AR/VR, and more.
Application Domains
Robotics: Forward/inverse kinematics, motion planning. AR/VR: Head tracking, object placement. Animation: Character rigging, skinning. Computer Vision: Image registration, structure from motion.
# Example: Robot manipulator kinematics
class RobotArm:
def __init__(self, lengths):
self.lengths = lengths
self.joint_angles = np.zeros(len(lengths))
def forward_kinematics(self):
"""Compute end-effector position"""
position = np.zeros(3)
current = np.eye(4)
for i, (length, angle) in enumerate(zip(self.lengths, self.joint_angles)):
# Build transformation matrix for joint i
T = np.eye(4)
T[:3, 3] = [length, 0, 0]
R = rotation_3d_z(angle)
T[:3, :3] = R
current = current @ T
return current[:3, 3]
def set_joints(self, angles):
self.joint_angles = angles
def inverse_kinematics(self, target):
# Simplified inverse kinematics (2D case)
import numpy as np
x, y = target[:2]
L1, L2 = self.lengths[:2]
cos_theta2 = (x**2 + y**2 - L1**2 - L2**2) / (2 * L1 * L2)
theta2 = np.arccos(np.clip(cos_theta2, -1, 1))
theta1 = np.arctan2(y, x) - np.arctan2(L2 * np.sin(theta2), L1 + L2 * np.cos(theta2))
return np.array([theta1, theta2])