summaryrefslogtreecommitdiff
path: root/tb/models
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2023-11-22 08:51:21 -0600
committerAlejandro Soto <alejandro@34project.org>2023-11-22 08:51:21 -0600
commitad488cb54fa7cba274901cf7fab2060624204b1b (patch)
tree1f3e1824c11aeac11c1bac801370aabd7116bd71 /tb/models
parentc8b09a0f7716e2b26b1ae8456752c025a14bc18b (diff)
tb/models: add gfx model
Diffstat (limited to 'tb/models')
-rw-r--r--tb/models/gfx.py128
1 files changed, 128 insertions, 0 deletions
diff --git a/tb/models/gfx.py b/tb/models/gfx.py
new file mode 100644
index 0000000..c43a315
--- /dev/null
+++ b/tb/models/gfx.py
@@ -0,0 +1,128 @@
+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=False):
+ 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):
+ b0 = base_y_a + a
+ b1 = base_y_b + b
+ b2 = base_y_c + c
+ if b0 >= 0 and b1 >= 0 and b2 >= 0:
+ count += 1
+
+ if count > 0:
+ s = b0 + b1 + b2
+ b0 /= s
+ b1 /= s
+ b2 /= s
+ yield (x >> 4, y >> 4, b0, b1, b2)
+
+ 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 backend():
+ tri = (((-5 / 320, -5 / 240, 1.0, 1.0), (0.0, 0.0, 0.0, 1.0)),
+ ((-50/320, -5/240, 1.0, 1.0), (0.0, 1.0, 0.0, 0.0)),
+ ((-20/320, -70/240, 1.0, 1.0), (0.0, 0.0, 1.0, 0.0)))
+
+ t = tri
+
+ tri = tuple(fixed(p) for p in tri)
+ for x, y, b0, b1, b2 in raster(*tri):
+ if -W // 2 <= x < W // 2 and -H // 2 <= y < H // 2:
+ r = b0 * t[0][1][1] + b1 * t[1][1][1] + b2 * t[2][1][1]
+ g = b0 * t[0][1][2] + b1 * t[1][1][2] + b2 * t[2][1][2]
+ b = b0 * t[0][1][3] + b1 * t[1][1][3] + b2 * t[2][1][3]
+
+ x += W // 2
+ y = H // 2 - y - 1
+ fb[y * W + x] = (int(r * 255), int(g * 255), int(b * 255))
+
+imgs = []
+
+def do_frame():
+ global fb, imgs
+ fb = [FILL] * W * H
+ backend()
+
+ image = Image.new('RGB', (W, H))
+ image.putdata(fb)
+ imgs.append(image)
+
+for n in range(1):
+ do_frame()
+
+imgs[0].save('out.gif',
+ save_all=True,
+ append_images=imgs[1:],
+ duration=100,
+ loop=0)