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.
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.
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.
Deja una respuesta