commit fd7b0ec647359ecb8b55337c0c75799fd9a14a42 Author: Alexander Bass Date: Sun Apr 14 10:38:11 2024 -0400 donut diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b132ede --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +progress +__pycache__ \ No newline at end of file diff --git a/dirt.ppm b/dirt.ppm new file mode 100644 index 0000000..f6811bc --- /dev/null +++ b/dirt.ppm @@ -0,0 +1,19 @@ +P3 +16 16 +255 +185 133 92 150 108 74 150 108 74 121 85 58 121 85 58 185 133 92 150 108 74 150 108 74 121 85 58 121 85 58 89 61 41 121 85 58 121 85 58 185 133 92 121 85 58 185 133 92 +121 85 58 150 108 74 89 61 41 121 85 58 121 85 58 150 108 74 135 135 135 89 61 41 121 85 58 185 133 92 150 108 74 121 85 58 185 133 92 150 108 74 89 61 41 89 61 41 +185 133 92 121 85 58 121 85 58 89 61 41 185 133 92 121 85 58 121 85 58 121 85 58 185 133 92 121 85 58 121 85 58 121 85 58 89 61 41 89 61 41 185 133 92 121 85 58 +150 108 74 108 108 108 185 133 92 121 85 58 150 108 74 89 61 41 121 85 58 185 133 92 150 108 74 150 108 74 121 85 58 150 108 74 121 85 58 185 133 92 150 108 74 121 85 58 +150 108 74 121 85 58 150 108 74 185 133 92 89 61 41 150 108 74 121 85 58 121 85 58 150 108 74 89 61 41 121 85 58 108 108 108 121 85 58 150 108 74 89 61 41 121 85 58 +121 85 58 89 61 41 150 108 74 150 108 74 121 85 58 150 108 74 89 61 41 89 61 41 89 61 41 121 85 58 121 85 58 89 61 41 121 85 58 121 85 58 121 85 58 185 133 92 +185 133 92 121 85 58 121 85 58 121 85 58 135 135 135 121 85 58 121 85 58 185 133 92 185 133 92 121 85 58 185 133 92 185 133 92 121 85 58 150 108 74 121 85 58 150 108 74 +121 85 58 121 85 58 185 133 92 185 133 92 150 108 74 150 108 74 121 85 58 121 85 58 150 108 74 89 61 41 150 108 74 150 108 74 121 85 58 121 85 58 150 108 74 150 108 74 +150 108 74 121 85 58 121 85 58 150 108 74 121 85 58 150 108 74 121 85 58 89 61 41 121 85 58 150 108 74 150 108 74 121 85 58 121 85 58 121 85 58 89 61 41 121 85 58 +121 85 58 150 108 74 89 61 41 121 85 58 121 85 58 89 61 41 89 61 41 121 85 58 121 85 58 121 85 58 121 85 58 121 85 58 185 133 92 185 133 92 121 85 58 150 108 74 +121 85 58 150 108 74 121 85 58 185 133 92 185 133 92 121 85 58 185 133 92 150 108 74 89 61 41 185 133 92 185 133 92 89 61 41 150 108 74 150 108 74 135 135 135 121 85 58 +150 108 74 121 85 58 121 85 58 150 108 74 150 108 74 185 133 92 121 85 58 150 108 74 108 108 108 150 108 74 150 108 74 121 85 58 89 61 41 150 108 74 121 85 58 89 61 41 +121 85 58 89 61 41 150 108 74 121 85 58 150 108 74 150 108 74 185 133 92 121 85 58 121 85 58 121 85 58 121 85 58 121 85 58 121 85 58 121 85 58 185 133 92 185 133 92 +121 85 58 150 108 74 121 85 58 121 85 58 116 88 68 121 85 58 150 108 74 150 108 74 121 85 58 89 61 41 185 133 92 89 61 41 121 85 58 185 133 92 150 108 74 150 108 74 +150 108 74 121 85 58 89 61 41 185 133 92 121 85 58 89 61 41 121 85 58 89 61 41 185 133 92 185 133 92 121 85 58 150 108 74 121 85 58 121 85 58 150 108 74 150 108 74 +150 108 74 121 85 58 185 133 92 150 108 74 150 108 74 121 85 58 135 135 135 121 85 58 150 108 74 150 108 74 121 85 58 121 85 58 150 108 74 150 108 74 121 85 58 89 61 41 diff --git a/entity.py b/entity.py new file mode 100644 index 0000000..4b816c1 --- /dev/null +++ b/entity.py @@ -0,0 +1,25 @@ +# Alexander Bass +# Created 4/13/24 +# Last Edited 4/13/24 +class Entity: + def update(self, t): + pass + + +class PointBlobEntity(Entity): + def __init__(self): + super().__init__() + self.points = [] + + def get_points(self): + return self.points + + def add_point(self, point): + self.points.append(point) + + def rotate(self, transform): + for p in self.points: + p.rotate(transform) + + def set_points(self, points): + self.points = points diff --git a/grass_donut.py b/grass_donut.py new file mode 100644 index 0000000..8a6fa7c --- /dev/null +++ b/grass_donut.py @@ -0,0 +1,107 @@ +# Alexander Bass +# Created 4/13/24 +# Last Edited 4/13/24 +from point import Point, Rotator +from util import to_rad +import ppm +from itertools import product +from entity import PointBlobEntity +import math + + +DIRT = ppm.load_ppm(open("dirt.ppm", "r")) +GRASS_TOP = ppm.load_ppm(open("grass_side.ppm", "r")) +GRASS_SIDE = ppm.load_ppm(open("grass_top.ppm", "r")) + + +def mix_color_transparency(left, right): + for a in left: + if a > 0: + return left + return right + + +def mul_color(a, b): + return (a[0] * b[0], a[1] * b[1], a[2] * b[2]) + + +def mul_color_scalar(a, b): + return (a[0] * b, a[1] * b, a[2] * b) + + +def cube(dirt, g_sides, g_top, face_x, face_y): + GRASS_COLOR_MUL = (0.4, 1, 0.4) + out = [] + image_size = 16 + point_count = 20 + world_width = 16 + for x, y in product(range(point_count), range(point_count)): + # Scale x and y to be from 0 to 1 to be later scaled back for any respective coordinate system + x /= point_count + y /= point_count + + dirt_color = dirt[round(y * image_size)][round(x * image_size)] + top_col = g_top[round(y * image_size)][round(x * image_size)] + grass_side_color = g_sides[int(y * image_size)][int(x * image_size)] + grass_side_color = mul_color(grass_side_color, GRASS_COLOR_MUL) + top_col = mul_color(top_col, GRASS_COLOR_MUL) + + side_color = mix_color_transparency(grass_side_color, dirt_color) + + b_red, b_green, b_blue = dirt_color + + t_red, t_green, t_blue = top_col + + s_red, s_green, s_blue = side_color + + x_w = x * world_width + y_w = y * world_width + w_w = world_width + + if face_x == -1 or face_x == 1 and face_y == 0: + out.append(Point().set_xyz(0, x_w, y_w).set_color(s_red, s_green, s_blue)) + if face_x == 1 or face_x == -1 and face_y == 0: + out.append(Point().set_xyz(w_w, x_w, y_w).set_color(s_red, s_green, s_blue)) + + if face_y == -1 or face_y == 1 and face_x == 0: + out.append(Point().set_xyz(x_w, 0, y_w).set_color(s_red, s_green, s_blue)) + if face_y == 1 or face_y == -1 and face_x == 0: + out.append(Point().set_xyz(x_w, w_w, y_w).set_color(s_red, s_green, s_blue)) + + # Top and bottom faces + out.append(Point().set_xyz(x_w, y_w, w_w).set_color(b_red, b_green, b_blue)) + out.append(Point().set_xyz(x_w, y_w, 0).set_color(t_red, t_green, t_blue)) + + for p in out: + p.translate_x(face_x * world_width - world_width / 2) + p.translate_y(face_y * world_width - world_width / 2) + p.translate_z(-world_width / 2) + p.rotate_z(to_rad(90)) + p.rotate_y(45 * 3) + pass + return out + + +class DonutEntity(PointBlobEntity): + def __init__(self): + super().__init__() + points = [] + for x, y in product((-1, 0, 1), (-1, 0, 1)): + if x == y == 0: + continue + points.extend(cube(DIRT, GRASS_TOP, GRASS_SIDE, x, y)) + self.set_points(points) + + def update(self, t): + tf = Rotator() + tf.rotate_y(math.cos(t / 25) * 0.5) + # tf.rotate_z(0.02) + tf.rotate_x(math.sin(t / 20) * 0.5 + 0.02) + tf.build() + for p in self.points: + p.rotate(tf) + pass + + def shift(self, x, y, z): + for p in self.points: + p.translate(x, y, z) diff --git a/grass_side.ppm b/grass_side.ppm new file mode 100644 index 0000000..78ddfe6 --- /dev/null +++ b/grass_side.ppm @@ -0,0 +1,19 @@ +P3 +16 16 +255 +169 169 169 144 144 144 150 150 150 140 140 140 169 169 169 144 144 144 169 169 169 150 150 150 150 150 150 159 159 159 169 169 169 159 159 159 178 178 178 129 129 129 159 159 159 150 150 150 +169 169 169 140 140 140 169 169 169 144 144 144 178 178 178 0 0 0 169 169 169 130 130 130 178 178 178 150 150 150 169 169 169 147 147 147 169 169 169 140 140 140 169 169 169 139 139 139 +150 150 150 0 0 0 178 178 178 132 132 132 169 169 169 0 0 0 144 144 144 0 0 0 169 169 169 159 159 159 150 150 150 173 173 173 0 0 0 119 119 119 150 150 150 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 145 145 145 0 0 0 0 0 0 0 0 0 127 127 127 0 0 0 141 141 141 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 diff --git a/grass_top.ppm b/grass_top.ppm new file mode 100644 index 0000000..90eb983 --- /dev/null +++ b/grass_top.ppm @@ -0,0 +1,19 @@ +P3 +16 16 +255 +148 148 148 195 195 195 147 147 147 134 134 134 134 134 134 143 143 143 127 127 127 140 140 140 158 158 158 150 150 150 138 138 138 135 135 135 137 137 137 167 167 167 112 112 112 141 141 141 +149 149 149 140 140 140 160 160 160 151 151 151 153 153 153 128 128 128 153 153 153 130 130 130 127 127 127 169 169 169 166 166 166 147 147 147 195 195 195 140 140 140 135 135 135 139 139 139 +163 163 163 141 141 141 125 125 125 132 132 132 137 137 137 136 136 136 144 144 144 144 144 144 148 148 148 159 159 159 168 168 168 173 173 173 135 135 135 119 119 119 128 128 128 182 182 182 +118 118 118 133 133 133 138 138 138 151 151 151 145 145 145 153 153 153 125 125 125 146 146 146 127 127 127 131 131 131 141 141 141 151 151 151 143 143 143 139 139 139 167 167 167 125 125 125 +162 162 162 131 131 131 173 173 173 131 131 131 172 172 172 174 174 174 136 136 136 172 172 172 186 186 186 117 117 117 128 128 128 167 167 167 171 171 171 126 126 126 136 136 136 141 141 141 +153 153 153 152 152 152 120 120 120 166 166 166 129 129 129 150 150 150 149 149 149 155 155 155 135 135 135 149 149 149 136 136 136 131 131 131 120 120 120 136 136 136 142 142 142 148 148 148 +126 126 126 156 156 156 165 165 165 144 144 144 146 146 146 131 131 131 138 138 138 127 127 127 132 132 132 135 135 135 181 181 181 172 172 172 145 145 145 167 167 167 151 151 151 143 143 143 +143 143 143 135 135 135 157 157 157 139 139 139 144 144 144 124 124 124 164 164 164 132 132 132 151 151 151 173 173 173 135 135 135 129 129 129 143 143 143 160 160 160 127 127 127 169 169 169 +167 167 167 155 155 155 182 182 182 170 170 170 172 172 172 129 129 129 173 173 173 156 156 156 179 179 179 170 170 170 130 130 130 146 146 146 133 133 133 147 147 147 158 158 158 126 126 126 +151 151 151 130 130 130 183 183 183 141 141 141 140 140 140 152 152 152 170 170 170 128 128 128 135 135 135 145 145 145 146 146 146 169 169 169 142 142 142 179 179 179 143 143 143 129 129 129 +155 155 155 158 158 158 151 151 151 165 165 165 130 130 130 182 182 182 142 142 142 147 147 147 192 192 192 121 121 121 167 167 167 131 131 131 143 143 143 136 136 136 151 151 151 143 143 143 +132 132 132 173 173 173 134 134 134 163 163 163 151 151 151 132 132 132 146 146 146 137 137 137 151 151 151 146 146 146 140 140 140 123 123 123 179 179 179 127 127 127 125 125 125 138 138 138 +186 186 186 151 151 151 129 129 129 138 138 138 153 153 153 145 145 145 133 133 133 122 122 122 138 138 138 145 145 145 138 138 138 125 125 125 159 159 159 165 165 165 121 121 121 151 151 151 +129 129 129 168 168 168 118 118 118 146 146 146 125 125 125 153 153 153 129 129 129 195 195 195 161 161 161 147 147 147 168 168 168 155 155 155 160 160 160 136 136 136 168 168 168 174 174 174 +163 163 163 161 161 161 192 192 192 123 123 123 179 179 179 171 171 171 148 148 148 147 147 147 126 126 126 149 149 149 149 149 149 135 135 135 169 169 169 157 157 157 139 139 139 121 121 121 +133 133 133 150 150 150 136 136 136 137 137 137 164 164 164 152 152 152 148 148 148 147 147 147 142 142 142 191 191 191 124 124 124 169 169 169 132 132 132 144 144 144 136 136 136 158 158 158 diff --git a/point.py b/point.py new file mode 100644 index 0000000..201f018 --- /dev/null +++ b/point.py @@ -0,0 +1,129 @@ +# Alexander Bass +# Created 4/13/24 +# Last Edited 4/13/24 +from math import cos, sin + + +class Point: + def __init__(self): + self.__color = (1, 1, 1) + self.__x, self.__y, self.__z = 0, 0, 0 + + def set_xyz(self, x, y, z): + self.__x, self.__y, self.__z = x, y, z + return self + + def rotate_x(self, angle): + cos_a = cos(angle) + sin_a = sin(angle) + y_1 = self.__y * cos_a - self.__z * sin_a + z_1 = self.__y * sin_a + self.__z * cos_a + self.set_xyz(self.__x, y_1, z_1) + return self + + def rotate_z(self, angle): + cos_a = cos(angle) + sin_a = sin(angle) + + x_1 = self.__x * cos_a - self.__y * sin_a + y_1 = self.__x * sin_a + self.__y * cos_a + self.set_xyz(x_1, y_1, self.__z) + return self + + def rotate_y(self, angle): + cos_a = cos(angle) + sin_a = sin(angle) + + x_1 = self.__x * cos_a + self.__z * sin_a + z_1 = self.__z * cos_a - self.__x * sin_a + self.set_xyz(x_1, self.__y, z_1) + return self + + def translate(self, x, y, z): + self.__x += x + self.__y += y + self.__z += z + + def translate_x(self, x): + self.__x += x + + def translate_y(self, y): + self.__y += y + + def translate_z(self, z): + self.__z += z + + def get_xyz(self): + return (self.__x, self.__y, self.__z) + + def rotate(self, transform): + x_n, y_n, z_n = transform.run(*self.get_xyz()) + self.set_xyz(x_n, y_n, z_n) + + def get_rgb(self): + return self.__color + + def set_color(self, r, g, b): + if r > 255 or g > 255 or b > 255 or r < 0 or g < 0 or b < 0: + raise ValueError("Color is greater than 255 or less than 0") + self.__color = (r, g, b) + return self + + +class Rotator: + def __init__(self): + self.__alpha = 0.0 + self.__beta = 0.0 + self.__gamma = 0.0 + + self.__sin_alpha = 0.0 + self.__cos_alpha = 0.0 + self.__sin_beta = 0.0 + self.__cos_beta = 0.0 + self.__sin_gamma = 0.0 + self.__cos_gamma = 0.0 + + def rotate_x(self, angle): + self.__alpha += angle + return self + + def rotate_y(self, angle): + self.__beta += angle + return self + + def rotate_z(self, angle): + self.__gamma += angle + return self + + def build(self): + self.__sin_alpha = sin(self.__alpha) + self.__cos_alpha = cos(self.__alpha) + self.__sin_beta = sin(self.__beta) + self.__cos_beta = cos(self.__beta) + self.__sin_gamma = sin(self.__gamma) + self.__cos_gamma = cos(self.__gamma) + return self + + def run(self, xI, yI, zI): + x_0 = xI + y_0 = yI + z_0 = zI + + x_1, y_1 = 0, 0 + + # if self.__alpha != 0.0: + y_1 = y_0 * self.__cos_alpha - z_0 * self.__sin_alpha + z_0 = y_0 * self.__sin_alpha + z_0 * self.__cos_alpha + y_0 = y_1 + + # if self.__beta != 0.0: + x_1 = x_0 * self.__cos_beta + z_0 * self.__sin_beta + z_0 = z_0 * self.__cos_beta - x_0 * self.__sin_beta + x_0 = x_1 + + # if self.__gamma != 0.0: + x_1 = x_0 * self.__cos_gamma - y_0 * self.__sin_gamma + y_0 = x_0 * self.__sin_gamma + y_0 * self.__cos_gamma + x_0 = x_1 + + return x_0, y_0, z_0 diff --git a/ppm.py b/ppm.py new file mode 100644 index 0000000..6e96da9 --- /dev/null +++ b/ppm.py @@ -0,0 +1,46 @@ +# Alexander Bass +# Created 4/12/24 +# Last Edited 4/13/24 +def load_ppm(file): + header = next(file).strip().upper() + assert header == "P3" + dimensions = next(file).strip() + split_dimensions = dimensions.split() + assert len(split_dimensions) == 2 + width = split_dimensions[0] + height = split_dimensions[-1] + width = int(width) + height = int(height) + + # not really doing anything with this + color_depth = next(file).strip() + assert color_depth == "255" + + image = [] + for y, line in enumerate(file): + assert y < height + t = [int(value) for value in line.strip().split()] + + pixels = [] + + assert len(t) % 3 == 0 + + assert len(t) // 3 <= width + + highest = 0 + for x in range(0, len(t), 3): + red = t[0 + x] + green = t[1 + x] + blue = t[2 + x] + highest = x // 3 + pixels.append((red, green, blue)) + assert highest == width - 1 + image.append(pixels) + + assert len(image) == height + file.close() + return image + + +if __name__ == "__main__": + print(load_ppm(open("dirt.ppm"))) diff --git a/real_donut.py b/real_donut.py new file mode 100644 index 0000000..65634c2 --- /dev/null +++ b/real_donut.py @@ -0,0 +1,51 @@ +# Alexander Bass +# Created 4/13/24 +# Last Edited 4/13/24 +from point import Point, Rotator +from util import to_rad +from entity import PointBlobEntity +from math import sin, cos + +RADIUS = 10 +DISTANCE = 15 +POINTS_PER_CIRCLE = 70 +NUMBER_CIRCLES = 360 + + +class RealDonutEntity(PointBlobEntity): + def __init__(self): + super().__init__() + points = [] + rot = Rotator().rotate_x(to_rad(-30)) + rot.build() + for phi_t in range(NUMBER_CIRCLES): + phi_percent = phi_t / NUMBER_CIRCLES + phi = phi_percent * 360 + temp_points = [] + for theta in range(POINTS_PER_CIRCLE): + percent_theta = theta / POINTS_PER_CIRCLE + theta = percent_theta * 360 + x = DISTANCE + RADIUS * cos(to_rad(theta)) + y = RADIUS * sin(to_rad(theta)) + new_point = Point().set_xyz(x, y, 0).set_color(255, 255, 0) + # temp_points + new_point.rotate_y(to_rad(phi)) + temp_points.append(new_point) + points.extend(temp_points) + temp_points.clear() + for p in points: + p.rotate(rot) + self.set_points(points) + + def update(self, t): + tf = Rotator() + tf.rotate_y(0.03) + tf.rotate_x(0.02 * cos(t / 120)) + tf.build() + for p in self.points: + p.rotate(tf) + # pass + + def shift(self, x, y, z): + for p in self.points: + p.translate(x, y, z) diff --git a/spiral.py b/spiral.py new file mode 100644 index 0000000..bde8c56 --- /dev/null +++ b/spiral.py @@ -0,0 +1,136 @@ +# Alexander Bass +# Created 4/10/24 +# Last Edited 4/13/24 +import time +from math import pi, cos, sin, sqrt # noqa: F401 +from point import Point, Rotator +from grass_donut import DonutEntity +from real_donut import RealDonutEntity +from entity import Entity, PointBlobEntity +from util import to_rad + +WIDTH = 150 +HEIGHT = 100 +DEPTH = WIDTH +HALF_WIDTH = WIDTH / 2 +HALF_HEIGHT = HEIGHT // 2 +HALF_DEPTH = DEPTH // 2 + +NEGATIVE_DEPTH = -DEPTH + + +def world_to_screen(p_x, p_y, p_z): + s_x = round(p_x + HALF_WIDTH) + s_y = round(p_y + HALF_HEIGHT) + s_z = (p_z + HALF_DEPTH) / DEPTH + return s_x, s_y, s_z + + +def format_grid(entities, screen): + for x in range(WIDTH): + for y in range(HEIGHT): + screen[y][x] = None + + for entity in entities: + for p in entity.get_points(): + p_x, p_y, p_z = p.get_xyz() + s_x, s_y, s_z = world_to_screen(p_x, p_y, p_z) + if s_x in range(WIDTH) and s_y in range(HEIGHT): + val = screen[s_y][s_x] + if val is None: + red, green, blue = p.get_rgb() + screen[s_y][s_x] = (red, green, blue, s_z) + elif val[3] < s_z: + red, green, blue = p.get_rgb() + screen[s_y][s_x] = (red, green, blue, s_z) + + s = "" + last_color = (0, 0, 0) + for y in range(HEIGHT): + for x in range(WIDTH): + cell = screen[y][x] + if cell is None or 0 > cell[3] > 255: + s += " " + continue + + red, green, blue, depth = cell + scale = depth + red *= scale + green *= scale + blue *= scale + if (red, green, blue) != last_color: + s += f"\033[38;2;{round(red)};{round(green)};{round(blue)}m" + last_color = (red, green, blue) + s += "██" + s += "\n" + return s + + +def update_entities(entities, t): + for e in entities: + e.update(t) + + +class AxisEntity(PointBlobEntity): + def __init__(self): + self.points = [] + for x in range(WIDTH // 2 + 1): + tx = x - WIDTH / 4 + for y in range(WIDTH // 2 + 1): + ty = y - WIDTH / 4 + self.add_point(Point().set_xyz(0, tx, ty).set_color(1, 1, 0)) + + r = ( + Rotator() + .rotate_z(to_rad(80)) + .rotate_y(to_rad(30)) + .rotate_x(to_rad(30)) + .build() + ) + + self.rotate(r) + + self.rotor = Rotator().rotate_z(to_rad(0.6)).rotate_y(to_rad(0.06)).build() + + def update(self, t): + t = t + 6 * 30 + + self.rotate(self.rotor) + for p in self.points: + x, y, z = p.get_xyz() + x, y = abs(x), abs(y) + p.set_color(255 * abs(sin(x * t / 99)), 255 * abs(sin(y * t / 100)), 0) + + +class Origin(PointBlobEntity): + def __init__(self): + super().__init__() + self.points = [Point().set_xyz(0, 0, 0).set_color(255, 0, 0)] + + +def main(): + entities = [] + # entities.append(AxisEntity()) + # entities.append(Origin()) + entities.append(RealDonutEntity()) + entities.append(DonutEntity()) + t = 1 + last_time = time.time() + dt = 0 + + grid = [[None for x in range(WIDTH)] for y in range(HEIGHT)] + while True: + print("\033c", end="") + print(format_grid(entities, grid)) + + update_entities(entities, t) + current_time = time.time() + dt = current_time - last_time + wait = 1 / 45 - dt + last_time = current_time + time.sleep(max(wait, 0)) + + t += 1 + 1 / 45 - dt + + +main() diff --git a/util.py b/util.py new file mode 100644 index 0000000..a6952f5 --- /dev/null +++ b/util.py @@ -0,0 +1,31 @@ +# Alexander Bass +# Created 4/13/24 +# Last Edited 4/13/24 +from math import sin, cos, atan2, acos, sqrt +import math + + +def to_rad(deg): + return math.pi * deg / 180.0 + + +def sign(f): + if f == 0: + return f + + return f / math.fabs(f) + + +def to_cart(r, theta, phi): + y = r * sin(theta) * cos(phi) + x = r * cos(theta) + z = r * sin(theta) * sin(phi) + return x, y, z + + +def to_spherical(x, y, z): + r = sqrt(x**2 + y**2 + z**2) + theta = acos(z / r) + x = x if x != 0 else 1 + phi = math.atan(y / x) + return r, theta, phi