Rotación de un cubo 3D usando Java
Resumen
Se muestra una manera sencilla de graficar y rotar un Cubo.
Se comienza con la teoría de transformaciones lineales y proyecciones en el plano, se describen las matrices de rotación y al final se muestra un código muy sencillo de una aplicación hecha en Java que ejemplifica y muestra las transfomaciones descritas.
Introducción
Se puede rotar una figura en tres dimensiones, sobre el eje X, sobre el eje Y o sobre el eje Z.
En el caso de una computadora, la pantalla es un espacio de dos dimensiones, por lo tanto hay que hacer una proyección sobre el plano XY cada vez que se manipulen figuras tridimensionales.
Existe un vector
en el plano R2 con coordenadas (x, y) y un ángulo
con respecto al eje X,
se desea rotar el vector en un ángulo
en sentido contrario de las manecillas del reloj con respecto al eje X. El nuevo vector
‘ forma un ángulo
+
respecto al vector
y existe la misma longitud r en ambos vectores como se muestra en la figura:
Utilizando las definiciones de seno y coseno obtenemos que para el vector ![]()

Para el vector
‘, de forma similar, se obtiene que:

La suma de dos ángulos esta dada por:

Si se considera r = 1 y se sustituyen los valores correspondientes de x e y de la figura 1 tenemos.


Escribiendo las mismas ecuaciones en forma de matrices se obtiene:

La matriz (1) es mejor conocida como matriz de rotación, la cual determina las nuevas coordenadas (x’, y’) obtenidas por rotar un ángulo
un punto (x, y) en sentido contrario de las manecillas del reloj.
Si las rotaciones se quieren hacer en el espacio R3, se necesita considerar el valor del eje de rotación (en el cual se desea girar) igual a uno y poner el resto de los valores igual a cero. En el caso de la matriz (1) se considera que rota con respecto al eje z.
Las matrices de rotación correspondientes a cada uno de los ejes están dadas por:

Para poder mostrar la rotación en tres dimensiones en un plano de dos dimensiones hay que hacer una proyección. Una proyección es una trasnformación lineal que toma un punto en el espacio de tres dimensiones y lo proyecta en un plano, que en este caso sera el plano xy. La matriz de transfomación que nos brinda la proyección de un punto en el plano xy es:

Ahora vamos a usar las matrices anteriores para tratar de rotar un cubo.
Para construir un cubo se pueden tomar una lista de vértices, aristas o caras, para ejemplos ilustrativos vamos a tomar una lista de ocho vértices y vamos a enumerarlos como sigue:

Si trazamos lineas que unan los vértices de forma tal que formen las aristas del cubo, se obtiene la siguiente figura:

La figura 3 muestra un cubo centrado en el origen pero a pesar de que la figura mostrada aparenta ser un cuadrado (dos dimensiones) se sabe que tiene una tercera dimensión dado que tiene las tres coordenadas en cada uno de sus ocho vértices, pero al proyectar sus puntos en el plano xy todos los valores z de los puntos son "planchados" al plano xy. Para darse cuenta de la forma tridimensional de la figura basta con rotar los puntos del cubo sobre el eje x o sobre el eje y.

El cubo aparenta estar representado en el espacio de tres dimensiones, pero carece de volumen, propiedad típica de los cuerpos tridimensionales. Para lograr una apariencia de volumen se necesitan crear diferentes polígonos que representen cada una de las caras del cubo, los vértices de cada cara del cubo serán los vértices de cada polígono y se puede asignar un color diferente a cada uno de ellos como se muestra.

Para ilustrar mejor lo antes mencionado, se contruyó un applet que muestra un cubo en 3D el cual puede rotar utlizando las matrices descritas.
Da click en el siguiente enlace para abrir el applet:
Rotación 3D de un cubo usando Java
Debes mover el ratón mientras mantienes presionado el botón izquierdo sobre el cubo para observar su rotación.
A continuación se muestra el código que muestra la utilización de matrices de rotación y proyecciones en el plano.
/** this software is under GNU GPL @author: Jorge Alejandro Gutiérrez Orozco compilation javac Cube.java Ejecution appletviewer Hola.html */ import java.applet.*; import java.awt.*; import java.awt.event.*; import java.lang.Math; import java.awt.Polygon; /* Represents a point in space */ class Point{ public double x; public double y; public double z; Point(){ this.x=0; this.y=0; this.z=0; } Point(double x,double y, double z){ this.x=x; this.y=y; this.z=z; } /* for move it at origin */ int getXCoordinate(){return Cube.origin + (int)this.x;} int getYCoordinate(){return Cube.origin - (int)this.y;} int getZCoordinate(){return 0;} /* projection on XY plane*/ } class Face{ Point p1,p2,p3,p4; Polygon side= new Polygon(); Face(){} Face(Point p1,Point p2,Point p3,Point p4){ this.p1=p1;this.p2=p2;this.p3=p3;this.p4=p4; this.side.addPoint(p1.getXCoordinate(),p1.getYCoordinate()); this.side.addPoint(p2.getXCoordinate(),p2.getYCoordinate()); this.side.addPoint(p3.getXCoordinate(),p3.getYCoordinate()); this.side.addPoint(p4.getXCoordinate(),p4.getYCoordinate()); } public void drawFace(Graphics g){ g.fillPolygon(side); } public boolean isVisible(){ Point aux1, aux2; aux1 = new Point(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z); // p1->p2 aux2 = new Point(p4.x-p1.x, p4.y-p1.y, p4.z-p1.z); // p1->p4 if((aux1.x*aux2.y - aux1.y*aux2.x) > 0) return true; return false; } } public class Cube extends Applet implements Runnable, MouseMotionListener{ Thread luxury; static Point[] vertex = new Point[8]; // Cube vertexes static int maxSize =300; // screen size static int origin = maxSize/2; // (0,0) static int xMouseP=0, yMouseP =0; // X and Y mouse's positions int xAux , yAux ; // old X and Y mouse's positions int module = 50; // length of lines of cube. Image canvasAux; Graphics backBuffer; public void init(){ setSize(maxSize,maxSize); setBackground( new Color(0.2f,0.6f,0.1f,1.0f) ); addMouseMotionListener(this); /*Initial coordinates of eight vertex module is half the length of the cube*/ int[] coordX = new int[]{-module,module,module,-module,-module,module,module,-module}; int[] coordY = new int[]{-module,-module,module,module,-module,-module,module,module}; int[] coordZ = new int[]{module,module,module,module,-module,-module,-module,-module}; canvasAux = createImage(500,500); backBuffer = canvasAux.getGraphics(); for(int i =0; i<vertex.length; i++){ vertex[i] = new Point(coordX[i],coordY[i],coordZ[i]); } } public void start(){ try { luxury = new Thread(this); luxury.start(); } catch (Exception e){} } public void run() {} public void stop(){} public void paint(Graphics g){ /* Cube's points. Requires four points to create a face, requires six faces to create a cube.*/ int[] pts1 = new int[]{0,1,5,0,0,3}; int[] pts2 = new int[]{1,5,4,3,4,2}; int[] pts3 = new int[]{2,6,7,7,5,6}; int[] pts4 = new int[]{3,2,6,4,1,7}; Color[] colorRGB = new Color[]{ Color.black, Color.blue,Color.orange, Color.pink,Color.red, Color.yellow}; Face[] faces = new Face[6]; backBuffer.clearRect(0,0,500,500); for(int i=0; i<6; i++){ backBuffer.setColor(colorRGB[i]); faces[i] = new Face(vertex[pts1[i]],vertex[pts2[i]],vertex[pts3[i]],vertex[pts4[i]]); if(faces[i].isVisible()){ faces[i].drawFace(backBuffer); } } g.drawImage(canvasAux,0,0,this); g.drawString(" Touche ", 30,30); g.drawString(" X " + xMouseP, 30,50); g.drawString(" Y " + yMouseP, 30,65); return; } public void update(Graphics g){ paint(g); } public void destroy(){ try {Thread.sleep(1500);} catch (InterruptedException e) { System.out.println("Exception in sleep"); } } /* Rotate each vertex of cube over X, Y and Z axis */ void rota(double angleTeta, double anglePhi, double anglePsi) { double teta= Math.toRadians(angleTeta); double phi= Math.toRadians(anglePhi); double psi= Math.toRadians(anglePsi); Point pAux = new Point(); Point pAux1 = new Point(); Point pAux2 = new Point(); for(int i =0; i<8; i++){ /* Rotating over x */ pAux1.x = vertex[i].x; pAux1.y= vertex[i].y * Math.cos(teta) + vertex[i].z * (-Math.sin(teta)); pAux1.z = vertex[i].y * Math.sin(teta) + vertex[i].z * Math.cos(teta); /* Rotating over y */ pAux2.x = pAux1.x * Math.cos(phi) + pAux1.z * Math.sin(phi); pAux2.y = pAux1.y; pAux2.z = pAux1.x * (-Math.sin(phi)) + pAux1.z * Math.cos(phi); /* Rotating over z */ pAux.x= pAux2.x * Math.cos(psi) + pAux2.y * (-Math.sin(psi)); pAux.y = pAux2.x * Math.sin(psi) + pAux2.y * Math.cos(psi); pAux.z= pAux2.z; /* new position */ vertex[i].x = pAux.x; vertex[i].y = pAux.y; vertex[i].z = pAux.z; } } /* For move the cube over X and Y axis */ public void mouseDragged( MouseEvent e ){ /* old coordinates */ xAux = xMouseP; yAux = yMouseP; /* new coordinates */ xMouseP = e.getX(); yMouseP = e.getY(); if(yMouseP > yAux){ rota(2,0,0); } if(yMouseP < yAux){ rota(-2,0,0); } if(xMouseP > xAux){ rota(0,2,0); } if(xMouseP < xAux){ rota(0,-2,0); } repaint(); e.consume(); } public void mouseMoved( MouseEvent e ) { } }
El código anterior pertenece a un archivo llamado Cube.java, para poder visualizar el applet generado se necesita crear una archivo html.
El siguiente código solo es un html para desplegar el applet.
<!-- hola.html --> <HTML> <BODY> Cubo <APPLET width="500" height="500" CODE="Cube.class"><br /></APPLET> </BODY> </HTML>
En caso de contar con un IDE (Eclipse, jGrasp, etc.) no existe necesidad de crear dicho html para visualizar el cubo.
Quizá te interese :
Programa para realizar suma de polinomios con listas enlazadas. package com.ubicuos.main; im ...
Leer datos con Java Read from Console Standard Input with Java Si alguna vez, necesitaste leer ...
Árbol fractal en BorlandC para DOS /*arbol3*/ /*Fernando Galindo Soria*/ #include #incl ...









Podrías incrustar el Applet para verlo en funcionamiento?
Añadiendo el coloreado de sintaxis es más fácil seguir el código :)
La rotación en el eje y esta invertido.
Efectivamente hay mucha confusión con respecto a la matríz de rotación en la web, la mayoría de las pags tienen una matríz donde el seno negativo está en la segunda fila, pero ello representa una rotación en sentido de las manecillas del reloj:
http://en.wikipedia.org/wiki/Rotation_matrix
Puedes revisar el applet en el siguiente enlace para comprobar que el sentido de la rotación va acorde con el movimiento del ratón:
http://www.ubicuos.com/wp-content/uploads/2010/03/Hola.html
Muchísimas gracias por la observación.
Yeah, es muy bueno, puedo visualizarlo creando el archivo html, pero no en netbeans, de todos modos revisare el codigo para ver la lógica, genial.
Jorge Alejandro dijo:
“Efectivamente hay mucha confusión con respecto a la matríz de rotación en la web, la mayoría de las pags tienen una matríz donde el seno negativo está en la segunda fila, pero ello representa una rotación en sentido de las manecillas del reloj:”
Esa es la desventaja del internet, cualquier persona puede escribir lo que quiera, sea cierto o no. Lo cierto es que realmenete no encontrarían tal “confusion” si utilizarán referencias confiables, o si entendieran como se deduce las matrices de transformación.
El análisis no es díficil, lo único que necesitas es conocer las identidades de suma de ángulos.
Analicé el código pero no le veo la utilidad del Thread luxury. Se lo quité al código y sigue funcionando el código, para que sirve entonces ? el método run() de todos modos no hace nada.
Buen programa.
Pues yo le he quitado el hilo y se alentó un poco, yo creo que este hilo es para manejar procesos adicionales ( tal vez por eso le llama “lujo”) sin necesidad de que la eficiencia de la rotación se viese afectada, no sé, se me imagina que podríais agregar iluminación al cubo o tal vez otros cálculos de manera independiente a la graficación. Excelente programa!
@Jorge_Alejandro: porqué no agregaís la iluminación que te digo? Tomaré tu código para fines educativos jejeje. Buen trabajo.
eee genial programa
en vdd