Last active
February 20, 2023 19:14
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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