-
Notifications
You must be signed in to change notification settings - Fork 1
/
canvas.py
129 lines (103 loc) · 4.4 KB
/
canvas.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from math import radians
from numpy import ndarray
import numpy as np
from matrix import Matrix
from mesh import Mesh
from texture import Texture
from vector import Vector
from color import Color
from vertex import Vertex
from common import *
class Canvas(object):
def __init__(self, width: int, height: int, pixels: ndarray):
self.width = width
self.height = height
self.pixels = pixels
self.buffer_size = width * height
self.depth_buffer = np.empty(self.width * self.height)
def clear(self):
self.pixels.fill(0)
self.depth_buffer.fill(float("inf"))
def put_pixel(self, x: int, y: int, z: float, color: Color):
index = int(y) * self.width + int(x)
depth = self.depth_buffer[index]
if depth < z:
return
self.depth_buffer[index] = z
self.pixels[index] = color.uint32()
def draw_point(self, point: Vector, color: Color = Color.white()):
if 0 <= point.x < self.width and 0 <= point.y < self.height:
self.put_pixel(point.x, point.y, point.z, color)
def draw_line(self, p1: Vector, p2: Vector):
x1, y1, x2, y2 = [int(i) for i in [p1.x, p1.y, p2.x, p2.y]]
dx = x2 - x1
dy = y2 - y1
if abs(dx) > abs(dy):
xmin, xmax = sorted([x1, x2])
ratio = dy / dx
for x in range(xmin, xmax):
y = y1 + (x - xmin) * ratio
self.draw_point(Vector(x, y))
else:
ymin, ymax = sorted([y1, y2])
ratio = 0 if dy == 0 else dx / dy
for y in range(ymin, ymax):
x = x1 + (y - ymin) * ratio
self.draw_point(Vector(x, y))
def draw_scanline(self, va: Vertex, vb: Vertex, y: int, texture: Texture):
x1 = int(va.position.x)
x2 = int(vb.position.x)
sign = 1 if x2 > x1 else -1
factor = 0
for x in range(x1, x2 + sign * 1, sign):
if x1 != x2:
factor = (x - x1) / (x2 - x1)
# color = interpolate(v1.color, v2.color, factor)
v = interpolate(va, vb, factor)
color = texture.sample(v.u, v.v)
self.draw_point(Vector(x, y), color)
def draw_triangle(self, v1: Vertex, v2: Vertex, v3: Vertex, texture: Texture):
a, b, c = sorted([v1, v2, v3], key=lambda k: k.position.y)
middle_factor = 0
if c.position.y - a.position.y != 0:
middle_factor = (b.position.y - a.position.y) / (c.position.y - a.position.y)
middle = interpolate(a, c, middle_factor)
start_y = int(a.position.y)
end_y = int(b.position.y)
for y in range(start_y, end_y + 1):
factor = (y - start_y) / (end_y - start_y) if end_y != start_y else 0
va = interpolate(a, b, factor)
vb = interpolate(a, middle, factor)
self.draw_scanline(va, vb, y, texture)
start_y = int(b.position.y)
end_y = int(c.position.y)
for y in range(start_y, end_y + 1):
factor = (y - start_y) / (end_y - start_y) if end_y != start_y else 0
va = interpolate(b, c, factor)
vb = interpolate(middle, c, factor)
self.draw_scanline(va, vb, y, texture)
def project(self, v: Vertex, transform: Matrix):
# the function for vertex shader
p = transform.transform(v.position)
p.x = p.x * self.width + self.width / 2
p.y *= self.height
return Vertex(p, v.normal, v.u, v.v, v.color)
def draw_mesh(self, mesh: Mesh):
camera_position = Vector(0, 0, -10)
camera_target = Vector(0, 0, 0)
camera_up = Vector(0, 1, 0)
view = Matrix.lookAtLH(camera_position, camera_target, camera_up)
projection = Matrix.perspectiveFovLH(radians(45), self.width / self.height, 0.1, 1)
rotation = Matrix.rotation(mesh.rotation)
translation = Matrix.translation(mesh.position)
scale = Matrix.scale(mesh.scale)
world = scale * rotation * translation
transform = world * view * projection
for i in range(0, len(mesh.indices), 3):
a = mesh.vertices[mesh.indices[i]]
b = mesh.vertices[mesh.indices[i + 1]]
c = mesh.vertices[mesh.indices[i + 2]]
v1 = self.project(a, transform)
v2 = self.project(b, transform)
v3 = self.project(c, transform)
self.draw_triangle(v1, v2, v3, mesh.texture)