Skip to content

Instantly share code, notes, and snippets.

@Matthias1590
Last active February 20, 2023 19:14
Show Gist options
  • Save Matthias1590/44b1d13ebe13f22a3fb79e6066f115e2 to your computer and use it in GitHub Desktop.
Save Matthias1590/44b1d13ebe13f22a3fb79e6066f115e2 to your computer and use it in GitHub Desktop.
Code to render a wireframe, inspired by: https://www.youtube.com/watch?v=hFRlnNci3Rs
# Wireframe renderer by Matthias (https://github.com/Matthias1590)
# Inspired by Mattbatwings' 3D Renderer video (https://www.youtube.com/watch?v=hFRlnNci3Rs)
from lamp_display import LampDisplay # Install via `pip install lamp_display`, source code available at https://github.com/Matthias1590/LampDisplay
from bresenham import bresenham # Install via `pip install bresenham`, if you want to use another line drawing algorithm you can replace the draw_line function
from functools import cache # Builtin
import numpy as np # Install via `pip install numpy`
# Wireframe constants
VERTICES = [
(-1, -0.5, -1),
(1, -0.5, -1),
(0, 0.75, 0),
(-1, -0.5, 1),
(1, -0.5, 1),
]
EDGES = [
(0, 1),
(1, 4),
(4, 3),
(3, 0),
(0, 2),
(1, 2),
(3, 2),
(4, 2),
]
# Rotation constants
ROTATE_X = 0.03
ROTATE_Y = 0.02
ROTATE_Z = 0.01
# Camera constants
CAMERA_Z = 7.5 # The camera is facing negative Z
FOCAL_LENGTH = 1.25
SCALE = 200
@cache
def project_vertex(
vertex: tuple[float, float, float],
focal_length: float,
) -> tuple[float, float]:
return (
focal_length * vertex[0] / (focal_length + vertex[2]),
focal_length * -vertex[1] / (focal_length + vertex[2]),
)
def draw_line(
display: LampDisplay, start: tuple[float, float], end: tuple[float, float]
) -> None:
for point in bresenham(*map(round, start + end)):
display.set_pixel(point, True)
rot_matrix = (
np.matrix(
[
[1, 0, 0],
[0, np.cos(ROTATE_X), -np.sin(ROTATE_X)],
[0, np.sin(ROTATE_X), np.cos(ROTATE_X)],
]
)
.dot(
[
[np.cos(ROTATE_Y), 0, np.sin(ROTATE_Y)],
[0, 1, 0],
[-np.sin(ROTATE_Y), 0, np.cos(ROTATE_Y)],
]
)
.dot(
[
[np.cos(ROTATE_Z), -np.sin(ROTATE_Z), 0],
[np.sin(ROTATE_Z), np.cos(ROTATE_Z), 0],
[0, 0, 1],
]
)
)
# You don't really need this copy, it's just here since I don't want to modify the constant VERTICES
vertices = VERTICES.copy()
display = LampDisplay((100, 100), 10)
while True:
display.clear()
# Rotate vertices
for i, vertex in enumerate(vertices):
rotated = rot_matrix.dot(vertex)
vertices[i] = tuple(rotated.tolist()[0])
# Project vertices
projected = []
for x, y, z in vertices:
z += CAMERA_Z
x_proj, y_proj = project_vertex((x, y, z), FOCAL_LENGTH)
x_proj *= SCALE
y_proj *= SCALE
x_proj += display.size[0] / 2
y_proj += display.size[1] / 2
projected.append((x_proj, y_proj))
# Draw edges
for start, end in EDGES:
draw_line(display, projected[start], projected[end])
if not display.update():
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment