From 7a454edc469849048e890a5f2faa8d950f48fd32 Mon Sep 17 00:00:00 2001 From: Quinn Date: Tue, 27 Aug 2024 18:17:27 -0400 Subject: [PATCH] Organized modules into individual files --- tools/animation-tools/.gitignore | 3 +- tools/animation-tools/CustomWidgets.py | 18 +++ tools/animation-tools/MatrixMath.py | 17 +++ tools/animation-tools/Scene.py | 70 +++++++++++ tools/animation-tools/Shapes.py | 42 +++++++ tools/animation-tools/animation-tool.py | 148 +----------------------- 6 files changed, 154 insertions(+), 144 deletions(-) create mode 100644 tools/animation-tools/CustomWidgets.py create mode 100644 tools/animation-tools/MatrixMath.py create mode 100644 tools/animation-tools/Scene.py create mode 100644 tools/animation-tools/Shapes.py diff --git a/tools/animation-tools/.gitignore b/tools/animation-tools/.gitignore index eba74f4..4ea05a1 100644 --- a/tools/animation-tools/.gitignore +++ b/tools/animation-tools/.gitignore @@ -1 +1,2 @@ -venv/ \ No newline at end of file +venv/ +__pycache__/ \ No newline at end of file diff --git a/tools/animation-tools/CustomWidgets.py b/tools/animation-tools/CustomWidgets.py new file mode 100644 index 0000000..395baa8 --- /dev/null +++ b/tools/animation-tools/CustomWidgets.py @@ -0,0 +1,18 @@ +from pygame_widgets.slider import Slider + +class ColorPicker: + def __init__(self, screen, x_pos: int, y_pos: int, width: int, height: int): + width = max(50, width) + self.sliders: list[Slider] = [ + Slider(screen, int(x_pos + i*(width/3)), y_pos, int((width-50)/3), height, min=0, max=255, step=1, vertical=True) for i in range(3) + ] + for slider in self.sliders: + slider.enable() + + def get_color(self) -> tuple[int]: + # return (0,0,0) + return tuple([slider.getValue() for slider in self.sliders]) + + def set_color(self, color: tuple[int]): + for i, slider in enumerate(self.sliders): + slider.setValue(color[i]) \ No newline at end of file diff --git a/tools/animation-tools/MatrixMath.py b/tools/animation-tools/MatrixMath.py new file mode 100644 index 0000000..cd0d371 --- /dev/null +++ b/tools/animation-tools/MatrixMath.py @@ -0,0 +1,17 @@ +import math +from pygame.math import Vector3 + +def project(vector, w, h, fov, distance): + factor = math.atan(fov / 2 * math.pi / 180) / (distance + vector.z) + x = vector.x * factor * w + w / 2 + y = -vector.y * factor * w + h / 2 + return Vector3(x, y, vector.z) + +def rotate_vertices(vertices, angle, axis): + return [v.rotate(angle, axis) for v in vertices] +def scale_vertices(vertices, s): + return [Vector3(v[0]*s[0], v[1]*s[1], v[2]*s[2]) for v in vertices] +def translate_vertices(vertices, t): + return [v + Vector3(t) for v in vertices] +def project_vertices(vertices, w, h, fov, distance): + return [project(v, w, h, fov, distance) for v in vertices] \ No newline at end of file diff --git a/tools/animation-tools/Scene.py b/tools/animation-tools/Scene.py new file mode 100644 index 0000000..a5fe9db --- /dev/null +++ b/tools/animation-tools/Scene.py @@ -0,0 +1,70 @@ +from Shapes import Mesh +from MatrixMath import * +import pygame + +class Scene: + def __init__(self, meshes, fov, distance): + self.meshes: list[Mesh] = meshes + self.fov = fov + self.distance = distance + self.euler_angles = [0, 0, 0] + + def transform_vertices(self, vertices, width, height): + transformed_vertices = vertices + axis_list = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + for angle, axis in reversed(list(zip(list(self.euler_angles), axis_list))): + transformed_vertices = rotate_vertices(transformed_vertices, angle, axis) + transformed_vertices = project_vertices(transformed_vertices, width, height, self.fov, self.distance) + return transformed_vertices + + def point_in_polygon(self, point, polygon): + """ Determine if the point (x, y) is inside the polygon """ + x, y = point + n = len(polygon) + inside = False + p1x, p1y = polygon[0] + for i in range(n + 1): + p2x, p2y = polygon[i % n] + if y > min(p1y, p2y): + if y <= max(p1y, p2y): + if x <= max(p1x, p2x): + if p1y != p2y: + xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x + if p1x == p2x or x <= xints: + inside = not inside + p1x, p1y = p2x, p2y + return inside + + def get_mesh_from_xy(self, pos: tuple[int]) -> Mesh: + x, y = pos + closest_mesh = None + closest_z = float('inf') + + for mesh in self.meshes: + transformed_vertices = self.transform_vertices(mesh.get_vertices(), *pygame.display.get_surface().get_size()) + avg_z = mesh.calculate_average_z(transformed_vertices) + for i, avg_z_value in avg_z: + polygon = mesh.create_polygon(mesh.get_face(i), transformed_vertices) + if self.point_in_polygon((x, y), polygon): + if avg_z_value < closest_z: + closest_z = avg_z_value + closest_mesh = mesh + + return closest_mesh + + def draw(self, surface): + + polygons = [] + for mesh in self.meshes: + transformed_vertices = self.transform_vertices(mesh.get_vertices(), *surface.get_size()) + avg_z = mesh.calculate_average_z(transformed_vertices) + for z in avg_z: + #for z in sorted(avg_z, key=lambda x: x[1], reverse=True): + pointlist = mesh.create_polygon(mesh.get_face(z[0]), transformed_vertices) + polygons.append((pointlist, z[1], mesh.face_color, mesh.edge_color)) + #pygame.draw.polygon(surface, (128, 128, 192), pointlist) + #pygame.draw.polygon(surface, (0, 0, 0), pointlist, 3) + + for poly in sorted(polygons, key=lambda x: x[1], reverse=True): + pygame.draw.polygon(surface, poly[2], poly[0]) + pygame.draw.polygon(surface, poly[3], poly[0], 3) \ No newline at end of file diff --git a/tools/animation-tools/Shapes.py b/tools/animation-tools/Shapes.py new file mode 100644 index 0000000..5280177 --- /dev/null +++ b/tools/animation-tools/Shapes.py @@ -0,0 +1,42 @@ +from MatrixMath import * +from pygame.math import Vector3 + +class Mesh(): + def __init__(self, vertices, faces): + self.__vertices = [Vector3(v) for v in vertices] + self.__faces = faces + self.face_color = (0, 0, 0) + self.edge_color = (0,0,0) + + def rotate(self, angle, axis): + self.__vertices = rotate_vertices(self.__vertices, angle, axis) + def scale(self, s): + self.__vertices = scale_vertices(self.__vertices, s) + def translate(self, t): + self.__vertices = translate_vertices(self.__vertices, t) + + def calculate_average_z(self, vertices): + return [(i, sum([vertices[j].z for j in f]) / len(f)) for i, f in enumerate(self.__faces)] + + def get_face(self, index): + return self.__faces[index] + def get_vertices(self): + return self.__vertices + + def create_polygon(self, face, vertices): + return [(vertices[i].x, vertices[i].y) for i in [*face, face[0]]] + + def set_face_color(self, color : tuple[int]): + self.face_color = color + + def set_edge_color(self, color : tuple[int]): + self.edge_color = color + +class Cube(Mesh): + def __init__(self): + vertices = [(-1,-1,1), (1,-1,1), (1,1,1), (-1,1,1), (-1,-1,-1), (1,-1,-1), (1,1,-1), (-1,1,-1)] + faces = [(0,1,2,3), (1,5,6,2), (5,4,7,6), (4,0,3,7), (3,2,6,7), (1,0,4,5)] + super().__init__(vertices, faces) + + def set_position(self, position : tuple): + super().translate(position) \ No newline at end of file diff --git a/tools/animation-tools/animation-tool.py b/tools/animation-tools/animation-tool.py index 4158a54..4aad042 100644 --- a/tools/animation-tools/animation-tool.py +++ b/tools/animation-tools/animation-tool.py @@ -1,151 +1,13 @@ -import math import pygame from itertools import product -from pygame_widgets.slider import Slider import pygame_widgets - -def project(vector, w, h, fov, distance): - factor = math.atan(fov / 2 * math.pi / 180) / (distance + vector.z) - x = vector.x * factor * w + w / 2 - y = -vector.y * factor * w + h / 2 - return pygame.math.Vector3(x, y, vector.z) - -def rotate_vertices(vertices, angle, axis): - return [v.rotate(angle, axis) for v in vertices] -def scale_vertices(vertices, s): - return [pygame.math.Vector3(v[0]*s[0], v[1]*s[1], v[2]*s[2]) for v in vertices] -def translate_vertices(vertices, t): - return [v + pygame.math.Vector3(t) for v in vertices] -def project_vertices(vertices, w, h, fov, distance): - return [project(v, w, h, fov, distance) for v in vertices] - -class Mesh(): - def __init__(self, vertices, faces): - self.__vertices = [pygame.math.Vector3(v) for v in vertices] - self.__faces = faces - self.face_color = (0, 0, 0) - self.edge_color = (0,0,0) - - def rotate(self, angle, axis): - self.__vertices = rotate_vertices(self.__vertices, angle, axis) - def scale(self, s): - self.__vertices = scale_vertices(self.__vertices, s) - def translate(self, t): - self.__vertices = translate_vertices(self.__vertices, t) - - def calculate_average_z(self, vertices): - return [(i, sum([vertices[j].z for j in f]) / len(f)) for i, f in enumerate(self.__faces)] - - def get_face(self, index): - return self.__faces[index] - def get_vertices(self): - return self.__vertices - - def create_polygon(self, face, vertices): - return [(vertices[i].x, vertices[i].y) for i in [*face, face[0]]] - - def set_face_color(self, color : tuple[int]): - self.face_color = color - - def set_edge_color(self, color : tuple[int]): - self.edge_color = color - -class Scene: - def __init__(self, meshes, fov, distance): - self.meshes: list[Mesh] = meshes - self.fov = fov - self.distance = distance - self.euler_angles = [0, 0, 0] - - def transform_vertices(self, vertices, width, height): - transformed_vertices = vertices - axis_list = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] - for angle, axis in reversed(list(zip(list(self.euler_angles), axis_list))): - transformed_vertices = rotate_vertices(transformed_vertices, angle, axis) - transformed_vertices = project_vertices(transformed_vertices, width, height, self.fov, self.distance) - return transformed_vertices - - def point_in_polygon(self, point, polygon): - """ Determine if the point (x, y) is inside the polygon """ - x, y = point - n = len(polygon) - inside = False - p1x, p1y = polygon[0] - for i in range(n + 1): - p2x, p2y = polygon[i % n] - if y > min(p1y, p2y): - if y <= max(p1y, p2y): - if x <= max(p1x, p2x): - if p1y != p2y: - xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x - if p1x == p2x or x <= xints: - inside = not inside - p1x, p1y = p2x, p2y - return inside - - def get_mesh_from_xy(self, pos: tuple[int]) -> Mesh: - x, y = pos - closest_mesh = None - closest_z = float('inf') - - for mesh in self.meshes: - transformed_vertices = self.transform_vertices(mesh.get_vertices(), *pygame.display.get_surface().get_size()) - avg_z = mesh.calculate_average_z(transformed_vertices) - for i, avg_z_value in avg_z: - polygon = mesh.create_polygon(mesh.get_face(i), transformed_vertices) - if self.point_in_polygon((x, y), polygon): - if avg_z_value < closest_z: - closest_z = avg_z_value - closest_mesh = mesh - - return closest_mesh - - def draw(self, surface): - - polygons = [] - for mesh in self.meshes: - transformed_vertices = self.transform_vertices(mesh.get_vertices(), *surface.get_size()) - avg_z = mesh.calculate_average_z(transformed_vertices) - for z in avg_z: - #for z in sorted(avg_z, key=lambda x: x[1], reverse=True): - pointlist = mesh.create_polygon(mesh.get_face(z[0]), transformed_vertices) - polygons.append((pointlist, z[1], mesh.face_color, mesh.edge_color)) - #pygame.draw.polygon(surface, (128, 128, 192), pointlist) - #pygame.draw.polygon(surface, (0, 0, 0), pointlist, 3) - - for poly in sorted(polygons, key=lambda x: x[1], reverse=True): - pygame.draw.polygon(surface, poly[2], poly[0]) - pygame.draw.polygon(surface, poly[3], poly[0], 3) +from Shapes import * +from Scene import Scene +from CustomWidgets import ColorPicker - -class Cube(Mesh): - def __init__(self): - vertices = [(-1,-1,1), (1,-1,1), (1,1,1), (-1,1,1), (-1,-1,-1), (1,-1,-1), (1,1,-1), (-1,1,-1)] - faces = [(0,1,2,3), (1,5,6,2), (5,4,7,6), (4,0,3,7), (3,2,6,7), (1,0,4,5)] - super().__init__(vertices, faces) - - def set_position(self, position : tuple): - super().translate(position) - -class ColorPicker: - def __init__(self, screen, x_pos: int, y_pos: int, width: int, height: int): - width = max(50, width) - self.sliders: list[Slider] = [ - Slider(screen, int(x_pos + i*(width/3)), y_pos, int((width-50)/3), height, min=0, max=255, step=1, vertical=True) for i in range(3) - ] - for slider in self.sliders: - slider.enable() - - def get_color(self) -> tuple[int]: - # return (0,0,0) - return tuple([slider.getValue() for slider in self.sliders]) - - def set_color(self, color: tuple[int]): - for i, slider in enumerate(self.sliders): - slider.setValue(color[i]) - -meshes: list[Cube] = [] +# generate a list of cubes +meshes: list[Mesh] = [] for origin in product([-1, 0, 1],[-1, 0, 1],[-1, 0, 1]): cube = Cube() cube.scale((0.35, 0.35, 0.35))