CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pygame

Cross-platform library for developing multimedia applications and video games in Python built on top of SDL

Overview
Eval results
Files

math-utils.mddocs/

Mathematical Utilities

Mathematical functions and vector operations for game calculations. Pygame's math module provides essential mathematical tools including 2D and 3D vectors, utility functions, and geometric calculations commonly needed in game development.

Capabilities

Utility Functions

General mathematical functions for common game calculations.

def clamp(value: float, min_value: float, max_value: float) -> float:
    """
    Constrain value to range [min_value, max_value].

    Args:
        value (float): Value to clamp
        min_value (float): Minimum allowed value
        max_value (float): Maximum allowed value

    Returns:
        float: Clamped value
    """

def lerp(a: float, b: float, weight: float) -> float:
    """
    Linear interpolation between two values.

    Args:
        a (float): Start value
        b (float): End value
        weight (float): Interpolation factor (0.0 to 1.0)

    Returns:
        float: Interpolated value
    """

Vector2 Class

2D vector class for position, velocity, direction calculations.

class Vector2:
    def __init__(self, x: float = 0, y: float = 0):
        """
        Create 2D vector.

        Args:
            x (float): X component
            y (float): Y component
        """

    # Properties
    x: float  # X component
    y: float  # Y component

    # Vector Operations
    def dot(self, vector: 'Vector2') -> float:
        """
        Calculate dot product with another vector.

        Args:
            vector (Vector2): Other vector

        Returns:
            float: Dot product (v1.x * v2.x + v1.y * v2.y)
        """

    def cross(self, vector: 'Vector2') -> float:
        """
        Calculate 2D cross product (scalar).

        Args:
            vector (Vector2): Other vector

        Returns:
            float: Cross product magnitude
        """

    def magnitude(self) -> float:
        """
        Get vector length/magnitude.

        Returns:
            float: Vector magnitude
        """

    def magnitude_squared(self) -> float:
        """
        Get squared magnitude (faster than magnitude()).

        Returns:
            float: Squared magnitude
        """

    def length(self) -> float:
        """
        Alias for magnitude().

        Returns:
            float: Vector length
        """

    def length_squared(self) -> float:
        """
        Alias for magnitude_squared().

        Returns:
            float: Squared length
        """

    def normalize(self) -> 'Vector2':
        """
        Get normalized vector (length = 1).

        Returns:
            Vector2: Unit vector in same direction
        """

    def normalize_ip(self) -> None:
        """Normalize this vector in place."""

    def is_normalized(self) -> bool:
        """
        Check if vector is normalized.

        Returns:
            bool: True if length is approximately 1
        """

    def safe_normalize(self) -> 'Vector2':
        """
        Normalize vector, returns zero vector if length is zero.

        Returns:
            Vector2: Normalized vector or zero vector
        """

    def scale_to_length(self, length: float) -> None:
        """
        Scale vector to specific length.

        Args:
            length (float): Desired length
        """

    # Distance Functions
    def distance_to(self, vector: 'Vector2') -> float:
        """
        Calculate distance to another vector.

        Args:
            vector (Vector2): Target vector

        Returns:
            float: Distance between vectors
        """

    def distance_squared_to(self, vector: 'Vector2') -> float:
        """
        Calculate squared distance (faster than distance_to()).

        Args:
            vector (Vector2): Target vector

        Returns:
            float: Squared distance
        """

    # Interpolation
    def lerp(self, vector: 'Vector2', t: float) -> 'Vector2':
        """
        Linear interpolation to another vector.

        Args:
            vector (Vector2): Target vector
            t (float): Interpolation factor (0.0 to 1.0)

        Returns:
            Vector2: Interpolated vector
        """

    def slerp(self, vector: 'Vector2', t: float) -> 'Vector2':
        """
        Spherical linear interpolation (smoother rotation).

        Args:
            vector (Vector2): Target vector
            t (float): Interpolation factor (0.0 to 1.0)

        Returns:
            Vector2: Interpolated vector
        """

    def move_towards(self, vector: 'Vector2', max_distance: float) -> 'Vector2':
        """
        Move towards target by maximum distance.

        Args:
            vector (Vector2): Target vector
            max_distance (float): Maximum distance to move

        Returns:
            Vector2: New position
        """

    # Rotation and Reflection
    def rotate(self, angle: float) -> 'Vector2':
        """
        Rotate vector by angle (degrees).

        Args:
            angle (float): Rotation angle in degrees

        Returns:
            Vector2: Rotated vector
        """

    def rotate_ip(self, angle: float) -> None:
        """
        Rotate vector in place.

        Args:
            angle (float): Rotation angle in degrees
        """

    def rotate_rad(self, angle: float) -> 'Vector2':
        """
        Rotate vector by angle (radians).

        Args:
            angle (float): Rotation angle in radians

        Returns:
            Vector2: Rotated vector
        """

    def rotate_rad_ip(self, angle: float) -> None:
        """
        Rotate vector in place (radians).

        Args:
            angle (float): Rotation angle in radians
        """

    def reflect(self, normal: 'Vector2') -> 'Vector2':
        """
        Reflect vector across surface normal.

        Args:
            normal (Vector2): Surface normal vector

        Returns:
            Vector2: Reflected vector
        """

    def reflect_ip(self, normal: 'Vector2') -> None:
        """
        Reflect vector in place.

        Args:
            normal (Vector2): Surface normal vector
        """

    # Polar Coordinates
    def as_polar(self) -> tuple[float, float]:
        """
        Convert to polar coordinates.

        Returns:
            tuple[float, float]: (radius, angle_in_degrees)
        """

    @classmethod
    def from_polar(cls, polar: tuple[float, float]) -> 'Vector2':
        """
        Create vector from polar coordinates.

        Args:
            polar (tuple[float, float]): (radius, angle_in_degrees)

        Returns:
            Vector2: New vector from polar coordinates
        """

    # Angles
    def angle_to(self, vector: 'Vector2') -> float:
        """
        Calculate angle to another vector.

        Args:
            vector (Vector2): Target vector

        Returns:
            float: Angle in degrees (-180 to 180)
        """

    # Utility
    def copy(self) -> 'Vector2':
        """
        Create copy of vector.

        Returns:
            Vector2: Copy of this vector
        """

    def update(self, x: float, y: float) -> None:
        """
        Update vector components.

        Args:
            x (float): New X component
            y (float): New Y component
        """

    def clamp_magnitude(self, min_length: float, max_length: float) -> 'Vector2':
        """
        Clamp vector magnitude to range.

        Args:
            min_length (float): Minimum magnitude
            max_length (float): Maximum magnitude

        Returns:
            Vector2: Vector with clamped magnitude
        """

    def clamp_magnitude_ip(self, min_length: float, max_length: float) -> None:
        """
        Clamp magnitude in place.

        Args:
            min_length (float): Minimum magnitude
            max_length (float): Maximum magnitude
        """

    # Arithmetic Operations (supports +, -, *, /, //, %, **)
    def __add__(self, other) -> 'Vector2': ...
    def __sub__(self, other) -> 'Vector2': ...
    def __mul__(self, scalar: float) -> 'Vector2': ...
    def __truediv__(self, scalar: float) -> 'Vector2': ...
    def __floordiv__(self, scalar: float) -> 'Vector2': ...
    def __mod__(self, scalar: float) -> 'Vector2': ...
    def __pow__(self, exponent: float) -> 'Vector2': ...

    # Comparison Operations
    def __eq__(self, other) -> bool: ...
    def __ne__(self, other) -> bool: ...

    # Sequence Operations
    def __getitem__(self, index: int) -> float: ...
    def __setitem__(self, index: int, value: float) -> None: ...
    def __len__(self) -> int: ...  # Always returns 2
    def __iter__(self): ...

Vector3 Class

3D vector class extending Vector2 functionality with Z component.

class Vector3:
    def __init__(self, x: float = 0, y: float = 0, z: float = 0):
        """
        Create 3D vector.

        Args:
            x (float): X component
            y (float): Y component
            z (float): Z component
        """

    # Properties
    x: float  # X component
    y: float  # Y component
    z: float  # Z component

    # All Vector2 methods plus 3D-specific operations:

    def cross(self, vector: 'Vector3') -> 'Vector3':
        """
        Calculate 3D cross product.

        Args:
            vector (Vector3): Other vector

        Returns:
            Vector3: Cross product vector (perpendicular to both inputs)
        """

    def dot(self, vector: 'Vector3') -> float:
        """
        Calculate dot product.

        Args:
            vector (Vector3): Other vector

        Returns:
            float: Dot product
        """

    # 3D Rotation
    def rotate_x(self, angle: float) -> 'Vector3':
        """
        Rotate around X axis.

        Args:
            angle (float): Rotation angle in degrees

        Returns:
            Vector3: Rotated vector
        """

    def rotate_x_ip(self, angle: float) -> None:
        """Rotate around X axis in place."""

    def rotate_y(self, angle: float) -> 'Vector3':
        """
        Rotate around Y axis.

        Args:
            angle (float): Rotation angle in degrees

        Returns:
            Vector3: Rotated vector
        """

    def rotate_y_ip(self, angle: float) -> None:
        """Rotate around Y axis in place."""

    def rotate_z(self, angle: float) -> 'Vector3':
        """
        Rotate around Z axis.

        Args:
            angle (float): Rotation angle in degrees

        Returns:
            Vector3: Rotated vector
        """

    def rotate_z_ip(self, angle: float) -> None:
        """Rotate around Z axis in place."""

    def rotate(self, axis: 'Vector3', angle: float) -> 'Vector3':
        """
        Rotate around arbitrary axis.

        Args:
            axis (Vector3): Rotation axis (should be normalized)
            angle (float): Rotation angle in degrees

        Returns:
            Vector3: Rotated vector
        """

    def rotate_ip(self, axis: 'Vector3', angle: float) -> None:
        """
        Rotate around arbitrary axis in place.

        Args:
            axis (Vector3): Rotation axis (should be normalized)
            angle (float): Rotation angle in degrees
        """

    # Spherical Coordinates
    def as_spherical(self) -> tuple[float, float, float]:
        """
        Convert to spherical coordinates.

        Returns:
            tuple[float, float, float]: (radius, theta, phi) in degrees
        """

    @classmethod
    def from_spherical(cls, spherical: tuple[float, float, float]) -> 'Vector3':
        """
        Create vector from spherical coordinates.

        Args:
            spherical (tuple[float, float, float]): (radius, theta, phi) in degrees

        Returns:
            Vector3: New vector from spherical coordinates
        """

    def update(self, x: float, y: float, z: float) -> None:
        """
        Update vector components.

        Args:
            x (float): New X component
            y (float): New Y component
            z (float): New Z component
        """

    # All arithmetic and comparison operations from Vector2
    def __len__(self) -> int: ...  # Always returns 3

Usage Examples

Basic Vector Operations

import pygame
import pygame.math

# Create vectors
pos = pygame.math.Vector2(100, 50)
velocity = pygame.math.Vector2(5, -3)
target = pygame.math.Vector2(400, 300)

print(f"Position: {pos}")
print(f"Velocity: {velocity}")

# Vector arithmetic
new_pos = pos + velocity
print(f"New position: {new_pos}")

# Vector magnitude
speed = velocity.magnitude()
print(f"Speed: {speed}")

# Distance calculation
distance = pos.distance_to(target)
print(f"Distance to target: {distance}")

# Normalize vector
direction = (target - pos).normalize()
print(f"Direction to target: {direction}")

Movement and Steering

import pygame
import pygame.math
import random

class Entity:
    def __init__(self, x, y):
        self.position = pygame.math.Vector2(x, y)
        self.velocity = pygame.math.Vector2(0, 0)
        self.max_speed = 3.0
        self.acceleration = pygame.math.Vector2(0, 0)

    def seek(self, target):
        """Steer towards target"""
        desired = (target - self.position).normalize() * self.max_speed
        steer = desired - self.velocity
        return steer

    def flee(self, target):
        """Steer away from target"""
        return -self.seek(target)

    def wander(self):
        """Random wandering behavior"""
        random_angle = random.uniform(-30, 30)
        forward = self.velocity.normalize() if self.velocity.magnitude() > 0 else pygame.math.Vector2(1, 0)
        wander_direction = forward.rotate(random_angle)
        return wander_direction * 0.5

    def update(self):
        # Apply acceleration
        self.velocity += self.acceleration

        # Limit speed
        if self.velocity.magnitude() > self.max_speed:
            self.velocity.scale_to_length(self.max_speed)

        # Update position
        self.position += self.velocity

        # Reset acceleration
        self.acceleration = pygame.math.Vector2(0, 0)

    def apply_force(self, force):
        self.acceleration += force

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Create entities
entities = [Entity(random.randint(50, 750), random.randint(50, 550)) for _ in range(10)]
mouse_pos = pygame.math.Vector2(400, 300)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEMOTION:
            mouse_pos = pygame.math.Vector2(event.pos)

    screen.fill((0, 0, 0))

    for entity in entities:
        # Calculate steering forces
        seek_force = entity.seek(mouse_pos) * 0.1
        wander_force = entity.wander() * 0.5

        # Combine forces
        total_force = seek_force + wander_force
        entity.apply_force(total_force)
        entity.update()

        # Wrap around screen edges
        if entity.position.x < 0:
            entity.position.x = 800
        elif entity.position.x > 800:
            entity.position.x = 0
        if entity.position.y < 0:
            entity.position.y = 600
        elif entity.position.y > 600:
            entity.position.y = 0

        # Draw entity
        pygame.draw.circle(screen, (255, 255, 255), (int(entity.position.x), int(entity.position.y)), 5)

        # Draw velocity vector
        end_pos = entity.position + entity.velocity * 10
        pygame.draw.line(screen, (255, 0, 0), entity.position, end_pos, 2)

    # Draw mouse cursor
    pygame.draw.circle(screen, (0, 255, 0), (int(mouse_pos.x), int(mouse_pos.y)), 8)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

3D Vector Math

import pygame.math
import math

# Create 3D vectors
position = pygame.math.Vector3(1, 2, 3)
forward = pygame.math.Vector3(0, 0, -1)  # Looking down negative Z
up = pygame.math.Vector3(0, 1, 0)

print(f"Position: {position}")
print(f"Forward: {forward}")
print(f"Up: {up}")

# Calculate right vector using cross product
right = forward.cross(up)
print(f"Right: {right}")

# Rotate around Y axis (yaw)
rotated_forward = forward.rotate_y(45)
print(f"Rotated 45° around Y: {rotated_forward}")

# Convert to spherical coordinates
spherical = position.as_spherical()
print(f"Spherical (r, theta, phi): {spherical}")

# Create vector from spherical coordinates
from_spherical = pygame.math.Vector3.from_spherical((5, 45, 30))
print(f"From spherical: {from_spherical}")

# 3D distance and interpolation
target = pygame.math.Vector3(5, 8, 2)
distance_3d = position.distance_to(target)
print(f"3D distance: {distance_3d}")

# Interpolate between positions
interpolated = position.lerp(target, 0.5)
print(f"Halfway point: {interpolated}")

Collision Detection with Vectors

import pygame
import pygame.math

def circle_collision(pos1, radius1, pos2, radius2):
    """Check collision between two circles using vectors"""
    distance = pos1.distance_to(pos2)
    return distance <= (radius1 + radius2)

def line_intersection(line1_start, line1_end, line2_start, line2_end):
    """Find intersection point of two lines"""
    # Convert to vector form
    d1 = line1_end - line1_start
    d2 = line2_end - line2_start
    d3 = line1_start - line2_start

    # Calculate cross products
    cross_d2_d3 = d2.cross(d3)
    cross_d1_d2 = d1.cross(d2)

    if abs(cross_d1_d2) < 1e-10:  # Lines are parallel
        return None

    t1 = cross_d2_d3 / cross_d1_d2
    if 0 <= t1 <= 1:
        intersection = line1_start + d1 * t1
        return intersection
    return None

def point_in_triangle(point, a, b, c):
    """Check if point is inside triangle using barycentric coordinates"""
    v0 = c - a
    v1 = b - a
    v2 = point - a

    dot00 = v0.dot(v0)
    dot01 = v0.dot(v1)
    dot02 = v0.dot(v2)
    dot11 = v1.dot(v1)
    dot12 = v1.dot(v2)

    inv_denom = 1 / (dot00 * dot11 - dot01 * dot01)
    u = (dot11 * dot02 - dot01 * dot12) * inv_denom
    v = (dot00 * dot12 - dot01 * dot02) * inv_denom

    return (u >= 0) and (v >= 0) and (u + v <= 1)

# Example usage
pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Test objects
circle1 = {"pos": pygame.math.Vector2(200, 200), "radius": 30}
circle2 = {"pos": pygame.math.Vector2(400, 300), "radius": 40}

triangle_points = [
    pygame.math.Vector2(500, 100),
    pygame.math.Vector2(600, 250),
    pygame.math.Vector2(450, 200)
]

mouse_pos = pygame.math.Vector2(0, 0)

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.MOUSEMOTION:
            mouse_pos = pygame.math.Vector2(event.pos)

    screen.fill((0, 0, 0))

    # Check circle collision
    circles_colliding = circle_collision(
        circle1["pos"], circle1["radius"],
        circle2["pos"], circle2["radius"]
    )

    # Draw circles
    color1 = (255, 100, 100) if circles_colliding else (100, 100, 255)
    color2 = (255, 100, 100) if circles_colliding else (100, 255, 100)

    pygame.draw.circle(screen, color1, (int(circle1["pos"].x), int(circle1["pos"].y)), circle1["radius"])
    pygame.draw.circle(screen, color2, (int(circle2["pos"].x), int(circle2["pos"].y)), circle2["radius"])

    # Check point in triangle
    mouse_in_triangle = point_in_triangle(mouse_pos, *triangle_points)
    triangle_color = (255, 255, 100) if mouse_in_triangle else (100, 100, 100)

    # Draw triangle
    triangle_coords = [(int(p.x), int(p.y)) for p in triangle_points]
    pygame.draw.polygon(screen, triangle_color, triangle_coords)

    # Draw mouse position
    pygame.draw.circle(screen, (255, 255, 255), (int(mouse_pos.x), int(mouse_pos.y)), 5)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Vector Field Visualization

import pygame
import pygame.math
import math

def vector_field(pos):
    """Generate vector field - spiral pattern"""
    center = pygame.math.Vector2(400, 300)
    to_center = center - pos

    if to_center.magnitude() > 0:
        # Spiral field
        perpendicular = pygame.math.Vector2(-to_center.y, to_center.x).normalize()
        radial = to_center.normalize()

        spiral_strength = 0.3
        radial_strength = 0.1

        field_vector = perpendicular * spiral_strength + radial * radial_strength
        return field_vector
    else:
        return pygame.math.Vector2(0, 0)

pygame.init()
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

# Create grid of vectors
grid_size = 40
vectors = []

for x in range(0, 800, grid_size):
    for y in range(0, 600, grid_size):
        pos = pygame.math.Vector2(x, y)
        field_vec = vector_field(pos)
        vectors.append((pos, field_vec))

# Particles
particles = []
for _ in range(50):
    particle = {
        'pos': pygame.math.Vector2(
            pygame.math.Vector2(400, 300).x + (random.uniform(-100, 100)),
            pygame.math.Vector2(400, 300).y + (random.uniform(-100, 100))
        ),
        'vel': pygame.math.Vector2(0, 0),
        'trail': []
    }
    particles.append(particle)

import random

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    screen.fill((0, 0, 0))

    # Draw vector field
    for pos, field_vec in vectors:
        if field_vec.magnitude() > 0.01:
            end_pos = pos + field_vec * 30
            pygame.draw.line(screen, (50, 50, 50), pos, end_pos)
            # Draw arrowhead
            arrow_size = 5
            if field_vec.magnitude() > 0:
                arrow_dir = field_vec.normalize()
                left_arrow = end_pos - arrow_dir.rotate(150) * arrow_size
                right_arrow = end_pos - arrow_dir.rotate(-150) * arrow_size
                pygame.draw.polygon(screen, (50, 50, 50), [end_pos, left_arrow, right_arrow])

    # Update and draw particles
    for particle in particles:
        # Apply field force
        field_force = vector_field(particle['pos'])
        particle['vel'] += field_force * 0.5

        # Apply damping
        particle['vel'] *= 0.98

        # Update position
        particle['pos'] += particle['vel']

        # Add to trail
        particle['trail'].append(particle['pos'].copy())
        if len(particle['trail']) > 20:
            particle['trail'].pop(0)

        # Wrap around screen
        if particle['pos'].x < 0: particle['pos'].x = 800
        if particle['pos'].x > 800: particle['pos'].x = 0
        if particle['pos'].y < 0: particle['pos'].y = 600
        if particle['pos'].y > 600: particle['pos'].y = 0

        # Draw trail
        for i, trail_pos in enumerate(particle['trail']):
            alpha = i / len(particle['trail'])
            color_intensity = int(255 * alpha)
            pygame.draw.circle(screen, (color_intensity, color_intensity, 255),
                             (int(trail_pos.x), int(trail_pos.y)), 2)

    pygame.display.flip()
    clock.tick(60)

pygame.quit()

Install with Tessl CLI

npx tessl i tessl/pypi-pygame

docs

advanced-drawing.md

audio-sound.md

core-system.md

drawing-shapes.md

event-input.md

game-objects.md

graphics-display.md

index.md

input-devices.md

joystick-gamepad.md

math-utils.md

surface-image.md

text-font.md

time-animation.md

transform-image.md

tile.json