From 77ddc5c7fd2ae5dd9bc4f1400e8c374d2e113cea Mon Sep 17 00:00:00 2001 From: Quinn Date: Tue, 27 Aug 2024 18:01:26 -0400 Subject: [PATCH] starting to build the naimation tool --- tools/animation-tools/animation-tool.py | 207 ++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 tools/animation-tools/animation-tool.py diff --git a/tools/animation-tools/animation-tool.py b/tools/animation-tools/animation-tool.py new file mode 100644 index 0000000..4158a54 --- /dev/null +++ b/tools/animation-tools/animation-tool.py @@ -0,0 +1,207 @@ +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) + + + +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] = [] +for origin in product([-1, 0, 1],[-1, 0, 1],[-1, 0, 1]): + cube = Cube() + cube.scale((0.35, 0.35, 0.35)) + cube.set_position(origin) + cube.set_face_color((0, 0, 0)) + meshes.append(cube) + +scene = Scene(meshes, 90, 5) + + +pygame.init() +window = pygame.display.set_mode((500, 500)) +colorPicker = ColorPicker(window, 20, 20, 100, 300) +clock = pygame.time.Clock() +selected_meshes = [] +run = True +trackMouseMotion = False + +while run: + clock.tick(60) + window.fill((255, 255, 255)) + events = pygame.event.get() + for event in events: + if event.type == pygame.QUIT: + run = False + elif event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 2: # middle mouse click + trackMouseMotion = True + elif event.button == 1: # left click + mousePosition = pygame.mouse.get_pos() + mesh = scene.get_mesh_from_xy(mousePosition) + if mesh != None: + if mesh in selected_meshes: + mesh.set_edge_color((0,0,0)) + selected_meshes.remove(mesh) + else: + mesh.set_edge_color((255,0,0)) + selected_meshes.append(mesh) + + pass + elif event.type == pygame.MOUSEBUTTONUP: + if event.button == 2: # middle mouse release + trackMouseMotion = False + elif trackMouseMotion and event.type == pygame.MOUSEMOTION: + mouseMovement = pygame.mouse.get_rel() + scene.euler_angles[0] -= mouseMovement[1] + scene.euler_angles[1] -= mouseMovement[0] + pygame_widgets.update(events) + + + if not trackMouseMotion: + pygame.mouse.get_rel() + + for mesh in selected_meshes: + mesh.set_face_color(colorPicker.get_color()) + scene.draw(window) + pygame.display.flip() + +pygame.quit() \ No newline at end of file