summaryrefslogtreecommitdiff
path: root/tb/models/gfx_pineda.py
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2023-11-22 08:33:55 -0600
committerAlejandro Soto <alejandro@34project.org>2023-11-22 08:33:55 -0600
commitc8b09a0f7716e2b26b1ae8456752c025a14bc18b (patch)
tree5e7efbb6d15c89b06418e4d462250c92de21fe09 /tb/models/gfx_pineda.py
parent7c2700699b6ef4ba6dc413540d23dcc18f437eeb (diff)
tb/models: add gfx_pineda
Diffstat (limited to 'tb/models/gfx_pineda.py')
-rw-r--r--tb/models/gfx_pineda.py164
1 files changed, 164 insertions, 0 deletions
diff --git a/tb/models/gfx_pineda.py b/tb/models/gfx_pineda.py
new file mode 100644
index 0000000..0e673d7
--- /dev/null
+++ b/tb/models/gfx_pineda.py
@@ -0,0 +1,164 @@
+import math, sys
+from PIL import Image
+
+W, H = 640, 480
+FILL = (0, 0, 0)
+
+fb = None
+
+def fixed_color(color):
+ for c in color[:3]:
+ c = abs(int(c * (1 << 8)))
+ yield (c & 255) | (255 if (c & (1 << 8)) else 0)
+
+def fixed(p):
+ pos, color = p
+ w = pos
+ pos = tuple(int(c / pos[3] * (1 << 13)) for c in pos[:3])
+ return (pos, tuple(fixed_color(color)))
+
+def bounding(p0, p1, p2):
+ minx = max(min(p0[0][0], p1[0][0], p2[0][0]), (-W // 2) << 4)
+ maxx = min(max(p0[0][0], p1[0][0], p2[0][0]), (W // 2) << 4)
+ miny = max(min(p0[0][1], p1[0][1], p2[0][1]), (-H // 2) << 4)
+ maxy = min(max(p0[0][1], p1[0][1], p2[0][1]), (H // 2) << 4)
+ return (minx, miny, maxx, maxy)
+
+def edge_fn(p, q, sx, sy):
+ p, q = p[0], q[0]
+
+ # https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/rasterization-stage.html
+ a, b = p[1] - q[1], -(p[0] - q[0])
+ r = (sx - q[0]) * a + (sy - q[1]) * b
+ return r, r, a, b
+
+def raster(p0, p1, p2, msaa=True):
+ global fb
+
+ minx, miny, maxx, maxy = bounding(p0, p1, p2)
+
+ sx, sy = minx // 16 * 16, miny // 16 * 16
+
+ base_x_a, base_y_a, add_x_a, add_y_a = edge_fn(p0, p1, sx, sy)
+ base_x_b, base_y_b, add_x_b, add_y_b = edge_fn(p1, p2, sx, sy)
+ base_x_c, base_y_c, add_x_c, add_y_c = edge_fn(p2, p0, sx, sy)
+
+ int_x_a = add_x_a << 4
+ int_x_b = add_x_b << 4
+ int_x_c = add_x_c << 4
+ int_y_a = add_y_a << 4
+ int_y_b = add_y_b << 4
+ int_y_c = add_y_c << 4
+
+ if msaa:
+ samples = ((-5, 10), (11, 2), (-7, -4))
+ else:
+ samples = ((0, 0),)
+
+ samples_a = tuple(add_x_a * sx + add_y_a * sy for sx, sy in samples)
+ samples_b = tuple(add_x_b * sx + add_y_b * sy for sx, sy in samples)
+ samples_c = tuple(add_x_c * sx + add_y_c * sy for sx, sy in samples)
+
+ for x in range(minx // 16 * 16, maxx + 16, 16):
+ for y in range(miny // 16 * 16, maxy + 16, 16):
+ count = 0
+
+ for a, b, c in zip(samples_a, samples_b, samples_c):
+ if base_y_a + a >= 0 and base_y_b + b >= 0 and base_y_c + c >= 0:
+ count += 1
+
+ if count > 0:
+ yield (x >> 4, y >> 4, count if msaa else 3)
+
+ base_y_a += int_y_a
+ base_y_b += int_y_b
+ base_y_c += int_y_c
+
+ base_x_a += int_x_a
+ base_x_b += int_x_b
+ base_x_c += int_x_c
+
+ base_y_a = base_x_a
+ base_y_b = base_x_b
+ base_y_c = base_x_c
+
+def translate(x, y, z):
+ return ((1, 0, 0, x),
+ (0, 1, 0, y),
+ (0, 0, 1, z),
+ (0, 0, 0, 1))
+
+def scale(x, y, z):
+ return ((x, 0, 0, 0),
+ (0, y, 0, 0),
+ (0, 0, z, 0),
+ (0, 0, 0, 1))
+
+def rotate(x, y, z, angle):
+ mag = math.hypot(x, y, z)
+ x /= mag
+ y /= mag
+ z /= mag
+
+ angle = math.radians(angle)
+ c, s = math.cos(angle), math.sin(angle)
+
+ return ((x * x * (1 - c) + c, x * y * (1 - c) - z * s, x * z * (1 - c) + y * s, 0),
+ (y * x * (1 - c) + z * s, y * y * (1 - c) + c, y * z * (1 - c) - x * s, 0),
+ (x * z * (1 - c) - y * s, y * z * (1 - c) + x * s, z * z * (1 - c) + c, 0),
+ (0, 0, 0, 1))
+
+def frustum(left, right, bottom, top, near, far):
+ # https://docs.gl/gl3/glFrustum
+
+ l, r, b, t, n, f = left, right, bottom, top, near, far
+ return ((2 * n / (r - l), 0, (r + l) / (r - l), 0),
+ (0, 2 * n / (t - b), (t + b) / (t - b), 0),
+ (0, 0, -(f + n) / (f - n), -2 * f * n / (f - n)),
+ (0, 0, -1 , 0))
+
+def mat_mat(a, b):
+ n = lambda: range(len(a))
+ return tuple(tuple(sum(a[i][k] * b[k][j] for k in n()) for j in n()) for i in n())
+
+def mat_vec(mat, vec):
+ return tuple(sum(a * b for a, b in zip(r, vec)) for r in mat)
+
+def backend(a, f, w):
+ tri = (((-1.0, -1.0, 1, 1.0), (1.0, 0.0, 0.0, 0.0)),
+ (( 1.0, -1.0, 1, 1.0), (0.0, 1.0, 0.0, 0.0)),
+ (( 0.0, 1.0, 3, 1.0), (0.0, 0.0, 1.0, 0.0)))
+
+ m = frustum(-w, w, -w, w, -5, 5)
+ m = mat_mat(m, rotate(1, 0, 0, a))
+ m = mat_mat(m, scale(f, f, f))
+
+ tri = tuple((mat_vec(m, p), c) for p, c in tri)
+
+ tri = tuple(fixed(p) for p in tri)
+ for x, y, frac in raster(*tri):
+ if -W // 2 <= x < W // 2 and -H // 2 <= y < H // 2:
+ x += W // 2
+ y = H // 2 - y - 1
+ fb[y * W + x] = (255 * frac // 3, 255 * frac // 3, 255 * frac // 3)
+
+imgs = []
+
+def do_frame(a, f, w):
+ global fb, imgs
+ fb = [FILL] * W * H
+ backend(a, f, w)
+
+ image = Image.new('RGB', (W, H))
+ image.putdata(fb)
+ imgs.append(image)
+
+a, f, w = int(sys.argv[1]), float(sys.argv[2]), float(sys.argv[3])
+for n in range(100):
+ do_frame(n * a, f, w)
+
+imgs[0].save('out.gif',
+ save_all=True,
+ append_images=imgs[1:],
+ duration=100,
+ loop=0)