diciembre 1, 2022

ROBOPONG – Programa de 3D sin Pygame

Written by

Seguimos trabajando con el modelo de 3D. El problema con el sistema que hemos probado antes, basado en la librería Pygame es que no es compatible con TKinter. Ambos son sistemas que manejan el bucle principal de mensajes del S.O. y no pueden funcionar juntos en un mismo programa. Lo más que hemos conseguido es crear dos ventanas distintas y que no se estorben, pero no es un resultado muy estético.

Decidimos probar con el sistema TKinter, que nos permite mostrar el vídeo y otros elementos en el programa y así aplicar Opencv. Lo que queremos es reproducir lo que el otro sistema hace con Pygame en un Canvas. La parte negativa es que el bucle de animación lo tenemos que hacer por nuestra cuenta.

Cogemos el vídeo de Stanislav, que explica poco a poco cómo hacer el motor de 3D y lo vamos a intentar aplicar, cambiando las instrucciones necesarias para trabajar con un Canvas de TKinter.

Lo primero es agrupar las clases que define en el sistema a un programa sencillo, para que luego sea más fácil su traducción.

Programa de 3D mínimo

Lo siguiente es transformar las sentencias de Pygame a otras sin la librería, que pueda dibujar líneas y puntos en el Canvas. Cambiando las instrucciones de dibujo y alguna otra, conseguimos un primer dibujo.

Prueba de 3D sin Pygame

Finalmente añadimos las sentencias para conseguir mover el objeto y la cámara para conseguir la animación.

El código que hemos empleado es el de abajo. Todo está en un sólo fichero salvo las operaciones de matrices, que no las hemos cambiado nada.

# Prueba de sistema 3D con TKinter
# Dibuja un cubo en perspectiva en Canvas
# Mueve el cubo y cámara con teclas
# Animación con movimiento de cámara automático

import tkinter as tk
import time
from matrix_functions import *


class Projection:
    def __init__(self, render):
        NEAR = render.camera.near_plane
        FAR = render.camera.far_plane
        RIGHT = math.tan(render.camera.h_fov / 2)
        LEFT = -RIGHT
        TOP = math.tan(render.camera.v_fov / 2)
        BOTTOM = -TOP

        m00 = 2 / (RIGHT - LEFT)
        m11 = 2 / (TOP - BOTTOM)
        m22 = (FAR + NEAR) / (FAR - NEAR)
        m32 = -2 * NEAR * FAR / (FAR - NEAR)
        self.projection_matrix = np.array([
            [m00, 0, 0, 0],
            [0, m11, 0, 0],
            [0, 0, m22, 1],
            [0, 0, m32, 0]
        ])

        HW, HH = render.H_WIDTH, render.H_HEIGHT
        self.to_screen_matrix = np.array([
            [HW, 0, 0, 0],
            [0, -HH, 0, 0],
            [0, 0, 1, 0],
            [HW, HH, 0, 1]
        ])

        
class Camera:
    def __init__(self, render, position):
        self.render = render
        self.position = np.array([*position, 1.0])
        self.forward = np.array([0, 0, 1, 1])
        self.up = np.array([0, 1, 0, 1])
        self.right = np.array([1, 0, 0, 1])
        self.h_fov = math.pi / 3
        self.v_fov = self.h_fov * (render.HEIGHT / render.WIDTH)
        self.near_plane = 0.1
        self.far_plane = 100
        self.moving_speed = 0.3
        self.rotation_speed = 0.015

    def control(self,codigo):
        if codigo==65:
            self.position -= self.right * self.moving_speed
        if codigo==68:
            self.position += self.right * self.moving_speed
        if codigo==87:
            self.position += self.forward * self.moving_speed
        if codigo==83:
            self.position -= self.forward * self.moving_speed
        if codigo==81:
            self.position += self.up * self.moving_speed
        if codigo==69:
            self.position -= self.up * self.moving_speed

        if codigo==37:
            self.camera_yaw(-self.rotation_speed)
        if codigo==39:
            self.camera_yaw(self.rotation_speed)
        if codigo==38:
            self.camera_pitch(-self.rotation_speed)
        if codigo==40:
            self.camera_pitch(self.rotation_speed)

    def camera_yaw(self, angle):
        rotate=rotate_y(angle)
        self.forward=self.forward @ rotate
        self.right=self.right @ rotate
        self.up=self.up @ rotate

    def camera_pitch(self, angle):
        rotate=rotate_x(angle)
        self.forward=self.forward @ rotate
        self.right=self.right @ rotate
        self.up=self.up @ rotate

    def translate_matrix(self):
        x, y, z, w = self.position
        return np.array([
            [1, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 0, 1, 0],
            [-x, -y, -z, 1]
        ])

    def rotate_matrix(self):
        rx, ry, rz, w = self.right
        fx, fy, fz, w = self.forward
        ux, uy, uz, w = self.up
        return np.array([
            [rx, ux, fx, 0],
            [ry, uy, fy, 0],
            [rz, uz, fz, 0],
            [0, 0, 0, 1]
        ])

    def camera_matrix(self):
        return self.translate_matrix() @ self.rotate_matrix()

    
class Object3D:
    def __init__(self, render):
        self.render = render
        self.vertices=np.array([(0,0,0,1),(0,1,0,1),(1,1,0,1),(1,0,0,1),
                               (0,0,1,1),(0,1,1,1),(1,1,1,1),(1,0,1,1)])
        self.faces=np.array([(0,1,2,3),(4,5,6,7),(0,4,5,1),(2,3,7,6),(1,2,6,5),(0,3,7,4)])

    def draw(self):
        self.screen_projection()
        self.movement()

    def movement(self):
        self.rotate_y(-0.1)
            
    def screen_projection(self):
        vertices = self.vertices @ self.render.camera.camera_matrix()
        vertices = vertices @ self.render.projection.projection_matrix
        vertices /= vertices[:, -1].reshape(-1, 1)
        vertices[(vertices > 2) | (vertices < -2)] = 0
        vertices = vertices @ self.render.projection.to_screen_matrix
        vertices = vertices[:, :2]

        for face in self.faces:
            polygon=vertices[face]
            if not np.any((polygon==self.render.H_WIDTH) | (polygon==self.render.H_WIDTH)):
                canvas.create_polygon(list(polygon.flatten()),fill='',outline='yellow',width=2)

        for vertex in vertices:
            if not np.any((vertex==self.render.H_WIDTH) | (vertex==self.render.H_WIDTH)):
                self.create_point(vertex,3,"white")

    def create_point(self,point,radius,color):
        x1, y1 = point[0]-radius, point[1]-radius
        x2, y2 = point[0]+radius, point[1]+radius
        canvas.create_oval(x1, y1, x2, y2, fill=color)
    
    def translate(self, pos):
        self.vertices = self.vertices @ translate(pos)

    def scale(self, scale_to):
        self.vertices = self.vertices @ scale(scale_to)

    def rotate_x(self, angle):
        self.vertices = self.vertices @ rotate_x(angle)

    def rotate_y(self, angle):
        self.vertices = self.vertices @ rotate_y(angle)

    def rotate_z(self, angle):
        self.vertices = self.vertices @ rotate_z(angle)


class SoftwareRender:
    def __init__(self):
        self.RES = self.WIDTH, self.HEIGHT = 800, 600
        self.H_WIDTH, self.H_HEIGHT = self.WIDTH // 2, self.HEIGHT // 2
        self.FPS = 60
        self.create_objects()
        self.running=True
        
    def create_objects(self):
        self.camera = Camera(self, [0.5, 1, -4])
        self.projection = Projection(self)
        self.object = Object3D(self)
        self.object.translate([0.2,0.4,0.2])
        self.object.rotate_y(-math.pi / 6)

    def draw(self):
        canvas.delete('all')
        self.object.draw()
        
    def run(self):
        self.draw()

    def key_pressed(self,event):
        self.camera.control(event.keycode)
        self.draw()


def anima():
    app3d.draw()
    window.after(100, anima)
        
if __name__ == '__main__':
    window = tk.Tk()
    greeting = tk.Label(text="Prueba con TKinter")
    label = tk.Label(
    text="Sistema 3D animado",
    foreground="white",  # Set the text color to white
    background="black",  # Set the background color to black
    width=20,
    height=2
    )
    canvas=tk.Canvas(width=800,height=600,bg="black")
    greeting.pack()
    label.pack()
    canvas.pack()
    app3d=SoftwareRender()
    window.bind('<Key>',app3d.key_pressed)
    app3d.draw()
    window.after(100, anima)
    window.mainloop()

De momento es todo un éxito. Ahora hay que mezclarlo con el programa de visión.

Category : ROBOT PING PONG

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Proudly powered by WordPress and Sweet Tech Theme