O Projeto AlphaCube é um projeto que utiliza a biblioteca Pygame
para desenhar um cubo rotativo em 3 dimensões. O cubo pode ser rotacionado em torno de seus eixos X, Y e Z.
O programa também permite que o usuário ajuste a distância focal da câmera virtual utilizada para renderizar o cubo de 3 para 2 dimensões através de um controle deslizante.
Se trata de um programa que simula o uso de ferramentas como OpenGL para simular a renderização de objetos 3D em 2D, movendo o cubo em torno de um eixo e alterando a distância focal da câmera virtual.
- Certifique-se de ter instalado Python na versão 3.9 ou superior.
- Clone o repositório do projeto através do comando no terminal:
git clone https://github.com/Maraba23/AlphaCube.git
- Acesse a pasta do projeto através do comando no terminal:
cd AlphaCube
- (Recomendado) Crie um ambiente virtual para o projeto através do comando no terminal:
python3 -m venv env
- Ative o ambiente virtual através do comando no terminal:
source env/bin/activate
(Linux)env\Scripts\Activate.ps1
(Windows) - Instale as dependências do projeto através do comando no terminal:
pip install -r requirements.txt
- Execute o programa através do comando no terminal:
python3 Main.py
- Para fechar o jogo, basta pressionar a tecla
ESC
, ou simplesmente fechar a janela. - Para desativar o ambiente virtual, basta executar o comando no terminal:
deactivate
O projeto usa como base a teoria por trás de uma câmera Pinhole, que consiste em uma câmera com um pequeno orifício e um aparato fotossensível. Quando a luz entra pelo orifício, gera uma imagem invertida no aparato. Em um plano cartesiano bidimensional, é possível pensar na câmera com o orifício sendo o ponto (0, 0), e o aparato sendo uma reta que fica a uma distância d desse orifício. Assim, diversos pontos no plano cartesiano podem ser projetados nessa reta, como mostra a figura a seguir:
Na figura, o aparato é representado pela reta y = -1, e a distância focal d é representada pela distância entre o ponto (0, 0) e a reta y = -1, que é igual a 1.
Assim, é possível projetar o ponto
Por semelhança de triângulos, no caso acima, temos que:
Ou seja,
Para realizar o cálculo matricial, precisamos que o valor projetado, multiplicado por algum número real, dependa apenas de
Com
Como sabemos que
Por fim, precisamos saber o valor
Com isso, podemos concluir que a matriz P será:
Com isso, após realizarmos a multiplicação matricial, é possível obter as coordenadas no plano cartesiano das imagens projetadas na reta y = -1.
O vetor
Após esse resultado, podemos obter o valor projetado
Para realizar a projeção de um objeto tridimensional em um espaço bidimensional, a teoria é a mesma. O que muda é que o orifício pode ser considerado como estando na origem,
Os cálculos anteriores foram feitos com o aparato estando fixo paralelamente ao eixo y e com a dimensão z fixa, sendo necessário obter o ponto
Segue, a seguir, uma imagem que representa um resumo teórico da explicação acima:
No código, a primeira coisa que é feita é definir as coordenadas dos vértices do cubo, armazenados no array vertices
. A função desenha_cubo
irá utilizar essas coordenadas e a distância focal (dist_focal
) para desenhar o cubo em um espaço bidimensional, utilizando a teoria acima explicada.
Ademais, a função também realiza as rotações do cubo nos 3 eixos utilizando trigonometria e multiplicações matriciais. Definimos os senos e cossenos dos ângulos que serão incrementados a cada tick do andamento do programa, e depois definimos as matrizes de rotação para cada eixo:
Com isso, podemos aplicar as rotações nos vértices do cubo, utilizando a multiplicação matricial. Isso está aplicado na seguinte seção do código:
# Matrizes de rotação para cada eixo
R_X = np.array([
[1, 0, 0, 0],
[0, math.cos(angulo_x), -math.sin(angulo_x), 0],
[0, math.sin(angulo_x), math.cos(angulo_x), 0],
[0, 0, 0, 1]
])
R_Y = np.array([
[math.cos(angulo_y), 0, math.sin(angulo_y), 0],
[0, 1, 0, 0],
[-math.sin(angulo_y), 0, math.cos(angulo_y), 0],
[0, 0, 0, 1]
])
R_Z = np.array([
[math.cos(angulo_z), -math.sin(angulo_z), 0, 0],
[math.sin(angulo_z), math.cos(angulo_z), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
# Matriz de rotação final
R = R_X @ R_Y @ R_Z
Assim, o cubo pode ser rotacionado nos eixos
Para realizar as transformações na matriz, é necessário realizar uma translação no eixo Z, permitindo que o usuário consiga visualizar o cubo, e depois transladar o cubo para o centro da tela, caso contrário o cubo estaria centrado no ponto (0, 0). Para isso, definimos as matrizes de translação:
# Matrizes de translação do eixo Z e para o centro da tela
T_Z = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, dist_focal],
[0, 0, 0, 1]
])
T_CENTRO = np.array([
[1, 0, 0, tamanho_tela[0] // 2],
[0, 1, 0, tamanho_tela[1] // 2],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
Por fim, definimos a matriz de projeção do Pinhole, e, por meio de multiplicações matriciais, obtemos a matriz de transformação final que, novamente por meio de multiplicações matriciais, será aplicada ao cubo:
# Matriz de projeção Pinhole
P = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, -dist_focal],
[0, 0, -1/dist_focal, 0]
])
# Matriz de transformação final
M_T = T_CENTRO @ P @ T_Z @ R
# Aplicando a transformação no cubo
C = M_T @ vertices
Após isso, realizamos as operações padrão do Pygame
para desenhar o cubo na tela, utilizando as coordenadas presentes na matriz draw_line
, chamada para desenhar cada aresta do cubo:
pygame.draw.line(tela, (255, 255, 255), (C[0, 0]/C[3, 0], C[1, 0]/C[3, 0]), (C[0, 1]/C[3, 1], C[1, 1]/C[3, 1]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 1]/C[3, 1], C[1, 1]/C[3, 1]), (C[0, 2]/C[3, 2], C[1, 2]/C[3, 2]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 2]/C[3, 2], C[1, 2]/C[3, 2]), (C[0, 3]/C[3, 3], C[1, 3]/C[3, 3]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 3]/C[3, 3], C[1, 3]/C[3, 3]), (C[0, 0]/C[3, 0], C[1, 0]/C[3, 0]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 4]/C[3, 4], C[1, 4]/C[3, 4]), (C[0, 5]/C[3, 5], C[1, 5]/C[3, 5]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 5]/C[3, 5], C[1, 5]/C[3, 5]), (C[0, 6]/C[3, 6], C[1, 6]/C[3, 6]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 6]/C[3, 6], C[1, 6]/C[3, 6]), (C[0, 7]/C[3, 7], C[1, 7]/C[3, 7]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 7]/C[3, 7], C[1, 7]/C[3, 7]), (C[0, 4]/C[3, 4], C[1, 4]/C[3, 4]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 0]/C[3, 0], C[1, 0]/C[3, 0]), (C[0, 4]/C[3, 4], C[1, 4]/C[3, 4]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 1]/C[3, 1], C[1, 1]/C[3, 1]), (C[0, 5]/C[3, 5], C[1, 5]/C[3, 5]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 2]/C[3, 2], C[1, 2]/C[3, 2]), (C[0, 6]/C[3, 6], C[1, 6]/C[3, 6]))
pygame.draw.line(tela, (255, 255, 255), (C[0, 3]/C[3, 3], C[1, 3]/C[3, 3]), (C[0, 7]/C[3, 7], C[1, 7]/C[3, 7]))
Como pode ser visto, em cada coordenada temos a divisão do valor