Home] [About] [Posts] [Resources]
dir: Home /
Posts /
How I Built A Simple (Horrible) 3D Graphics …
published-date: 20 Jan 2023 18:02 +0700
categories: [dumber-day-by-day] [python-hijinks]
tags: [python] [matplotlib]
Now that we have the basic on plotting a line in plt, in this post I want to expand more on creating cube primitive.
But first how would you store vertex coordinate in python? The easiest method is to store everything as 2D list buffer (or array if you prefer).
# vertices = [
# [x1, y1, z1, w1], Vertex, or point coordinate 1
# [x2, y2, z2, w2], Vertex 2
# [x3, y3, z3, w3], Vertex 3
# ...
# ]
But wait, what’s up with w
? The most obvious answer lays on the matrix calculation I used in the entire project, which uses 4x4 matrix templates because 3x3 matrix transformation does not support perspective projection. Honestly I don’t bother with projection matrixes and can’t really explain for sure. But to quote a more advanced users:
“by dividing x, y and z by w you actually project this 4d point into 3d space.”
For now ignore its existence, and we assign w1 = w2 = w3 = wn = 1; n ∈ real positive integers
Now say, you want to make cube with width
, length
, and height
as parameters. Center of mass lies in the center of the cube (duh). To add these into our vertices list, you can do one of the following.
# cube dimensions
width = 10
length = 10
height = 10
# empty vertex list as buffer
vertices = []
for i in (width, -width):
for j in (length, -length):
for k in (height, -height):
vertices.append((i/2, j/2, k/2, 1))
# or
vertices.append(
( width/2, length/2, height/2, 1),
( width/2, length/2, -height/2, 1),
( width/2, -length/2, height/2, 1),
( width/2, -length/2, -height/2, 1),
(-width/2, length/2, height/2, 1),
(-width/2, length/2, -height/2, 1),
(-width/2, -length/2, height/2, 1),
(-width/2, -length/2, -height/2, 1)
)
# either one is fine. Also notice the vertex order by the index in the list/array.
# 6 - - - - 4
# | \ | \
# | \ | \
# | 2 - - - - 0 \ Width
# | | c | | - Length
# 7 - - | - 5 | | Height
# \ | \ | c center(origin)
# \ | \ |
# 3 - - - - 1
Cool!
To plot the cube in plt, we’d have to cast this into np.array
for splicing each of the axis coordinate.
# assuming you haven't already
import numpy as np
# casts the entire list buffer as np.array
vertices = np.array(vertices)
x_values = vertices[:,0]
y_values = vertices[:,1]
z_values = vertices[:,2]
fig, ax = ConstructSubplot3D(limit=((min(x_values), max(x_values)), (min(y_values), max(y_values)), (min(z_values), max(z_values))))
ax.plot3D(x_values, y_values, z_values)
fig.show()
Whoa, shits fucked yo.
Looking back at the previous cube vertices generation code, the ordering of the vertex coordinate does not represent on how the edges should be rendered. In order to render edges or the wireframe of the cube, the order of the points should be reconstructed in a way that every line goes only at the edges. That’s where magic index comes to help reordering the vertices list buffer.
def verts_to_wireframe(vertices):
# this, does in fact, very inefficient memory-wise. Since we're basically cloning the list.
n_b = len(vertices) // 8 # if there are more than one cube in the vertex buffer, floor-division with the amount of vertex in cube (8)
magic_index = [
(0, 1, 3, 2, 0),
(0, 1, 5, 4, 0),
(0, 2, 6, 4, 0),
(0, 2, 3, 7),
(7, 3, 2, 6, 7),
(7, 5, 4, 6, 7),
(7, 3, 1, 5, 7),
]
temp = []
for n in range(n_b):
for r in magic_index:
prev = None
for i in r:
if i == prev:
continue
else:
temp.append(vertices[(8*n) + i])
prev = i
return np.array(temp)
This magic index reordering follows the rule at previous cube generation. Different pattern may differ at how you’d reorder the vertices. Basically the order draws one faces at each pass. Here’s the sequence of the passes:
0 -> 1 -> 3 -> 2 -> 0
0 -> 1 -> 5 -> 4 -> 0
0 -> 2 -> 6 -> 4 -> 0
0 -> 2 -> 3 -> 7
7 -> 3 -> 2 -> 6 -> 7
7 -> 5 -> 4 -> 6 -> 7
7 -> 3 -> 1 -> 5 -> 7
Also unhelpful flow diagram showing all of index jumps.
1import numpy as np
2import matplotlib.pyplot as plt
3
4def ConstructSubplot3D(x=1, y=1, figsize=(5,5), limit=((-2,2),(-2,2),(-2,2)), elev=60, azim=30, dpi=150):
5 return plt.subplots(
6 x, y,
7 figsize = figsize,
8 dpi = dpi,
9 subplot_kw = {
10 "projection": "3d",
11 "proj_type":'ortho',
12 "elev": elev,
13 "azim": azim,
14 "xlim": limit[0],
15 "ylim": limit[1],
16 "zlim": limit[2],
17 }
18 )
19
20def verts_to_wireframe(vertices):
21
22 n_b = len(vertices) // 8
23
24 magic_index = [
25 (0, 1, 3, 2, 0),
26 (0, 1, 5, 4, 0),
27 (0, 2, 6, 4, 0),
28 (0, 2, 3, 7),
29 (7, 3, 2, 6, 7),
30 (7, 5, 4, 6, 7),
31 (7, 3, 1, 5, 7),
32 ]
33
34 temp = []
35 for n in range(n_b):
36 for r in magic_index:
37 prev = None
38 for i in r:
39 if i == prev:
40 continue
41 else:
42 temp.append(vertices[(8*n) + i])
43 prev = i
44
45 return np.array(temp)
46
47if __name__ == "__main__":
48 width = 10
49 length = 10
50 height = 10
51
52 vertices = []
53
54 for i in (width, -width):
55 for j in (length, -length):
56 for k in (height, -height):
57 vertices.append((i/2, j/2, k/2, 1))
58
59 vertices = np.array(vertices)
60 vertices = verts_to_wireframe(vertices)
61
62 x_values = vertices[:,0]
63 y_values = vertices[:,1]
64 z_values = vertices[:,2]
65
66 fig, ax = ConstructSubplot3D(limit=((min(x_values), max(x_values)), (min(y_values), max(y_values)), (min(z_values), max(z_values))))
67 ax.plot3D(x_values, y_values, z_values)
68 fig.show() # if the image does not show up from your interpreter, use colab instead
Golly! Now in the next post I’ll cover linear transformation to the vertices we have made.
Built with Hugo | previoip (c) 2024