Source code for simvx.graphics.debug_draw

"""Immediate-mode debug line drawing.

Usage:
    from simvx.graphics.debug_draw import DebugDraw

    # In your process() callback:
    DebugDraw.line((0,0,0), (1,1,1), colour=(1,0,0,1))
    DebugDraw.box((0,0,0), (1,1,1), colour=(0,1,0,1))
    DebugDraw.axes((0,0,0), size=2.0)

Lines are rendered after the main 3D pass and cleared each frame.
"""


from __future__ import annotations

import logging
import math

import numpy as np

log = logging.getLogger(__name__)

__all__ = ["DebugDraw"]

# Vertex dtype: position (vec3) + colour (vec4) = 28 bytes, matching line.vert
DEBUG_VERTEX_DTYPE = np.dtype(
    [
        ("position", np.float32, 3),
        ("colour", np.float32, 4),
    ]
)


[docs] class DebugDraw: """Singleton immediate-mode debug line renderer.""" _vertices: list[tuple] = [] _enabled: bool = True
[docs] @classmethod def line(cls, start, end, colour=(1.0, 1.0, 1.0, 1.0)): """Draw a line from start to end.""" if not cls._enabled: return c = _rgba(colour) cls._vertices.append((start[0], start[1], start[2], *c)) cls._vertices.append((end[0], end[1], end[2], *c))
[docs] @classmethod def box(cls, center, half_extents, colour=(1.0, 1.0, 1.0, 1.0)): """Draw a wireframe axis-aligned box.""" if not cls._enabled: return cx, cy, cz = center[0], center[1], center[2] hx, hy, hz = half_extents[0], half_extents[1], half_extents[2] # 8 corners corners = [ (cx - hx, cy - hy, cz - hz), (cx + hx, cy - hy, cz - hz), (cx + hx, cy + hy, cz - hz), (cx - hx, cy + hy, cz - hz), (cx - hx, cy - hy, cz + hz), (cx + hx, cy - hy, cz + hz), (cx + hx, cy + hy, cz + hz), (cx - hx, cy + hy, cz + hz), ] # 12 edges edges = [ (0, 1), (1, 2), (2, 3), (3, 0), # front (4, 5), (5, 6), (6, 7), (7, 4), # back (0, 4), (1, 5), (2, 6), (3, 7), # connecting ] for a, b in edges: cls.line(corners[a], corners[b], colour)
[docs] @classmethod def sphere(cls, center, radius, colour=(1.0, 1.0, 1.0, 1.0), segments=16): """Draw a wireframe sphere (3 circles: XY, XZ, YZ planes).""" if not cls._enabled: return cx, cy, cz = center[0], center[1], center[2] step = 2.0 * math.pi / segments for plane in range(3): for i in range(segments): a0 = i * step a1 = (i + 1) * step if plane == 0: # XY p0 = (cx + radius * math.cos(a0), cy + radius * math.sin(a0), cz) p1 = (cx + radius * math.cos(a1), cy + radius * math.sin(a1), cz) elif plane == 1: # XZ p0 = (cx + radius * math.cos(a0), cy, cz + radius * math.sin(a0)) p1 = (cx + radius * math.cos(a1), cy, cz + radius * math.sin(a1)) else: # YZ p0 = (cx, cy + radius * math.cos(a0), cz + radius * math.sin(a0)) p1 = (cx, cy + radius * math.cos(a1), cz + radius * math.sin(a1)) cls.line(p0, p1, colour)
[docs] @classmethod def ray(cls, origin, direction, length=10.0, colour=(1.0, 1.0, 0.0, 1.0)): """Draw a ray from origin along direction.""" if not cls._enabled: return end = ( origin[0] + direction[0] * length, origin[1] + direction[1] * length, origin[2] + direction[2] * length, ) cls.line(origin, end, colour)
[docs] @classmethod def axes(cls, position=(0, 0, 0), size=1.0): """Draw RGB XYZ axes gizmo at position.""" if not cls._enabled: return px, py, pz = position[0], position[1], position[2] cls.line((px, py, pz), (px + size, py, pz), (1, 0, 0, 1)) # X = Red cls.line((px, py, pz), (px, py + size, pz), (0, 1, 0, 1)) # Y = Green cls.line((px, py, pz), (px, py, pz + size), (0, 0, 1, 1)) # Z = Blue
[docs] @classmethod def get_vertex_data(cls) -> np.ndarray | None: """Return vertex data as structured numpy array, or None if empty.""" if not cls._vertices: return None data = np.zeros(len(cls._vertices), dtype=DEBUG_VERTEX_DTYPE) for i, v in enumerate(cls._vertices): data[i]["position"] = (v[0], v[1], v[2]) data[i]["colour"] = (v[3], v[4], v[5], v[6]) return data
[docs] @classmethod def vertex_count(cls) -> int: return len(cls._vertices)
@classmethod def _clear(cls): """Clear all debug lines (called after rendering).""" cls._vertices.clear()
def _rgba(colour) -> tuple: """Normalize colour to RGBA tuple.""" if len(colour) == 3: return (colour[0], colour[1], colour[2], 1.0) return (colour[0], colour[1], colour[2], colour[3])