En este ejemplo vamos a cargar un modelo 3D en formato OBJ con textura JPG (imagen que “lo envolverá”) gracias a Processing. Le dotaremos de un par de funcionalidades. Por una parte, rotará alrededor de su eje de simetría. Además, podremos hacer clic con el ratón alternando la funcionalidad de vista de los triángulos que conforman el modelo. Podemos observarlo en la figura 1.
Para ello utilizaremos la librería SaitoObjLoader que es sencilla de utilizar. Esta librería se compone fundamentalmente de un fichero JAVA (OBJLoader.jar) que contiene todas las funciones Processing que accederemos desde nuestro código.
Antes de empezar es importante que dentro del directorio de nuestro sketch Processing, creemos dos subdirectorios para almacenar tanto el modelo y la textura asociada como la librería. Sugiero que lo hagáis de esta forma:
- Subdirectorio “data”. Almacena el fichero 3D (en nuestro caso los ficheros example.mtl y example.obj) así como la textura (example.jpg). Todos pueden generarse fácilmente desde cualquier programa de edición 2D (textura) y 3D (modelo). De hecho tanto el fichero .MTL como el .OBJ pueden editarse con un editor de texto.
- Subdirectorio “code”. Almacena la librería OBJLoader.jar que habremos descargado previamente desde la página de los desarrolladores.
En la raíz de nuestro sketch, haciendo compañía a ambos subdirectorios, tendremos nuestro fichero Processing (.PDE). Pasemos a analizar la primera porción de código pues (ver código 1).
import saito.objloader.*;
// We create an OBJModel object
OBJModel my3DModel;
// We will rotate our 3D model via this variable
float rotationFactor = 0.0;
// The showTriangles variable controls if triangles are being rendered or not (strokes)
boolean showTriangles = false;
// The Setup Function, called only once
void setup(){
// We establish a 800 x 600 pixels window
// with P3D (Processing 3D) support
size(800, 600, P3D);
// We force 30 frames per second
frameRate(30);
// By calling the constructor of our 3D container
// we select a file to be loaded (example.obj)
my3DModel = new OBJModel(this, "example.obj", "absolute", TRIANGLES);
// Our 3D model is scaled by a factor of 30 (it gets bigger)
my3DModel.scale(30);
// Textures are ON in our example
my3DModel.enableTexture();
// Triangles will be rendered in 5 pixels wide lines, red color
stroke(255,0,0);
strokeWeight(5);
}
Código 1. Objetos, variables e inicialización de nuestro programa de carga de modelo 3D.
Empezamos importando la librería OBJLoader para acto y seguido crear un objeto al cual traspasar nuestro modelo 3D (my3DModel). Pensemos que éste se encuentra en disco duro y lo necesitamos en memoria para poder operar con él a una velocidad adecuada. Creamos dos variables (rotationFactor, showTriangles) que permitirán que controlemos la rotación del modelo así como la activación, o no, de la visualización de sus triángulos.
En nuestra función de inicialización (setup() en Processing) creamos una ventana de 800 x 600 píxeles y además activamos la funcionalidad 3D de Processing (P3D). Por otra parte, forzamos que se pinten 30 cuadros por segundo. Inicializamos nuestro objeto my3Dmodel llamando al constructor de éste. Observemos que estamos determinando que deseamos pintar en base a triángulos y que vamos a cargar el modelo “example.obj”, al cual nos referíamos antes. Llamamos a un par de métodos de nuestro objeto example.obj, concretamente scale(30) y enableTexture(). Gracias a ellos escalamos uniformemente el modelo 3D en sus 3 direcciones XYZ (lo hacemos 30 veces más grande) y después activamos las texturas para éste. El escalado nos permite darle el tamaño que deseemos. Por último definimos color rojo y de 5 píxeles de ancho, gracias a las funcionalidades Stroke de Processing, para los triángulos que también pintaremos al hacer clic con el ratón.
En la siguiente porción de código (código 2), procederemos a pintar el modelo tal y como deseemos así como a rotarlo automáticamente según su eje de simetría. Todo dentro de la función draw()de Processing, imprescindible en cualquier porción de código dinámico para ese lenguaje.
// Our infinite loop
void draw(){
// A yellow background
background(255,255,0);
// We save that Transformation Matrix state
// to "return" in a few moments
pushMatrix();
// We translate to the center of our window
translate(width/2, height/2, 200);
// We scale our 3D model by 1/10
scale(0.1);
// We rotate our 3D model around the Y axis
// by the rotationFactor amount
rotateY(rotationFactor);
// Our rotationFactor gets smoothly incremented
// for a nice animation to be shown
rotationFactor+=0.01;
// We render the 3D model to the window
my3DModel.draw();
// We get back to the last pushMatrix Transformation state
popMatrix();
}
Código 2. Función draw() de Processing para mostrar y rotar el modelo 3D.
Observemos como decidimos que para cada nueva iteración (recordemos que la función draw() es un bucle infinito) tiene que repintarse el fondo en amarillo. A continuación utilizamos las funciones pushMatrix() y popMatrix() , comunes en librerías de programación gráfica 3D como OpenGL por ejemplo, para “salvar el estado”. Se trata de guardar un punto de retorno, a posición 3D, gracias a pushMatrix()y recuperarlo con popMatrix(). En nuestro ejemplo, salvamos el estado dado que vamos a aplicar transformaciones homogéneas a nuestro modelo 3D (lo vamos a trasladar, escalar y rotar) y no queremos que éstas “se acumulen” iteración tras iteración, dado que los efectos no serían los deseados (lo cierto es que para el caso concreto de la función draw() funcionaría igualmente pero quiero aprovechar la ocasión para que utilicéis estas funciones).
Tras salvar el estado, procedemos a trasladar (al centro de nuestra ventana), escalar (10 veces menos) y rotar (ésta última según nuestra variable-contador rotationFactor) el modelo 3D. Todas las funciones están disponibles en Processing nativo y además las empleamos con mayor suavidad y eficiencia gracias a la activación del modo “P3D” que realizamos en nuestro setup(). Cuidado! Que el orden de las transformaciones es importante en geometría! Y por lo tanto es posible que llamadas a las mismas traslaciones, escalados y rotaciones en órdenes diferentes nos den como resultado operaciones distintas que se traducirán de forma diferente en pantalla. Ya estamos preparados para pintar nuestro modelo 3D, que se trasladará-escalará-rotará como hemos definido en las líneas anteriores. Lo hacemos llamando al método draw() (no confundir con la función de Processing!) de nuestro objeto my3Dmodel (recordemos que éste contiene el fichero y la textura asociada). Después recuperamos el estado.
Quizás os preguntéis como “sabe” Processing que la textura del modelo “example.obj” es “example.jpg”. Es bien sencillo y de hecho no se debe al lenguaje de programación en sí sino a la forma como se genera un modelo 3D en formato “.OBJ”. Si editáis el fichero Con un editor de texto plano como el bloc de notas de windows o el textEdit de OSX. “example.obj” observaréis que “llama” al fichero “example.mtl”. Editando el fichero “example.obj” veréis como éste “apunta” a “example.jpg”.[JM1]
Sólo nos queda habilitar la funcionalidad gracias a la cual pintemos, o no, los triángulos que componen el modelo 3D. Lo podemos observar en el código 3.
// We toggle triangles ON and OFF
// everytime the user clicks the mouse
void mousePressed(){
// Activate triangles
if (showTriangles){
strokeWeight(5);
showTriangles = false;
}
// Deactivate triangles
else{
strokeWeight(0);
showTriangles = true;
}
}
Código 3. Evento de ratón que activa/desactiva la funcionalidad de pintar los triángulos que componen a nuestro modelo 3D.
Gracias al evento de ratón mousePressed() con el que nos recompensa Processing, podemos incluir código a ejecutarse cada vez que se haga clic. Observemos como gracias a una variable de tipo Boolean (showTriangles) procedemos a variar el grueso de línea con el que se pintaran los triángulos. De hecho se pintan siempre! Pero dependiendo del valor de showTriangles lo hacen con un grueso de 5 píxeles o de 0 (no están). Alternamos el valor de esta variable para realizar el control adecuadamente a cada clic de ratón.
A continuación podéis observar el código al completo (código 4). Espero que hayáis disfrutado del ejemplo y que lo modifiquéis a gusto para adaptarlo a vuestras necesidades!.
// In order to load a 3D model
// we will use the saitoobjloader library
// available at http://code.google.com/p/saitoobjloader/
// Therefore we import it
import saito.objloader.*;
// We create an OBJModel object
OBJModel my3DModel;
// We will rotate our 3D model via this variable
float rotationFactor = 0.0;
// The showTriangles variable controls if triangles are being rendered or not (strokes)
boolean showTriangles = false;
// The Setup Function, called only once
void setup(){
// We establish a 800 x 600 pixels window
// with P3D (Processing 3D) support
size(800, 600, P3D);
// We force 30 frames per second
frameRate(30);
// By calling the constructor of our 3D container
// we select a file to be loaded (example.obj)
my3DModel = new OBJModel(this, "example.obj", "absolute", TRIANGLES);
// Our 3D model is scaled by a factor of 30 (it gets bigger)
my3DModel.scale(30);
// Textures are ON in our example
my3DModel.enableTexture();
// Triangles will be rendered in 5 pixels wide lines, red color
stroke(255,0,0);
strokeWeight(5);
}
// Our infinite loop
void draw(){
// A yellow background
background(255,255,0);
// We save that Transformation Matrix state
// to "return" in a few moments
//pushMatrix();
// We translate to the center of our window
translate(width/2, height/2, 200);
// We scale our 3D model by 1/10
scale(0.1);
// We rotate our 3D model around the Y axis
// by the rotationFactor amount
rotateY(rotationFactor);
// Our rotationFactor gets smoothly incremented
// for a nice animation to be shown
rotationFactor+=0.01;
// We render the 3D model to the window
my3DModel.draw();
// We get back to the last pushMatrix Transformation state
//popMatrix();
}
// We toggle triangles ON and OFF
// everytime the user clicks the mouse
void mousePressed(){
// Activate triangles
if (showTriangles){
strokeWeight(5);
showTriangles = false;
}
// Deactivate triangles
else{
strokeWeight(0);
showTriangles = true;
}
}
Código 4. Programa al completo de carga de un modelo 3D con textura y rotación en Processing.
Excelente artículo y muy bien explicado!