Introducción a la API Java3D

En este artículo se ofrece una introducción a la API Java3D a través de tres ejemplos de código. En el primero, veremos como utilizar Java 3D para poner y posicionar un objeto en la pantalla. En el segundo ejemplo, veremos cómo utilizar la API para ver un objeto en movimiento. Y el tercer y último ejemplo, muestra cómo la API permite aplicar luminosidad a una escena.

La Java 3D API es un paquete opcional de la J2SE que actualmente está soportada en Solaris y en Windows, así como en otras plataformas. Podemos descargar una implementación para nuestra propia computadora desde el sitio de la Java 3D API. Esa página también provee enlaces a tutoriales, demostraciones, y código de ejemplo — también incluye enlaces a otras plataformas sobre las cuales la Java 3D API está siendo soportada.

Usando los constructores de la Java 3D API, creamos un mundo virtual 3D en donde podemos construir y manipular estructuras tri-dimensionales. Una anaología familiar podría ser un documento XML. Cuando vemos un documento XML siendo renderizado en un navegador, nos focalizamos en el contenido y podemos no ser concientes de la estructura arbórea subyacente. Similarmente, la información de una escena 3D que vemos en una aplicación Java 3D como objetos en el espacio también son almacenados en una jerarquía de nodos conocida como el grafo de una escena. Estos nodos representan objetos, información sobre posición y movimiento, e información sobre apariencia y luminosidad.

En la raíz de esta estructura arbórea existe un objeto BranchGroup. Necesitaremos asociar este objeto con el objeto Canvas3D que es utilizado para renderizar la escena. Para una mayor consistencia con los ejemplos distribuídos con Java 3D, esta raíz del grafo la escena, en nuestros ejemplos, será llamada objRoot.

Hay tres pasos fundamentales en la creación de un mundo 3D:

  • Crear un objeto Canvas3D.
  • Crear un grafo de escena.
  • Conectar el objeto Canvas3D con un objeto BranchGroup que apunta a la raíz del grafo de la escena.

Estos tres pasos constituyen el cuerpo del constructor en los tres ejemplos incluídos en este artículo.

En el siguiente primer ejemplo, Static3DWorld, el método createCanvas3D() ajusta el tamaño de un JFrame. El método entonces crea un objeto canvas3D y lo agrega al centro del JFrame. La tarea específica de la Java 3D API es llamar al método estático getPreferredConfiguration(), y pasar el resultado al constructor de Canvas3D.

La principal acción en este ejemplo está contenida en el método createSceneGraph(). El objeto BranchGroup, objRoot, apunta a la raíz del grafo de la escena. El objeto rotator es un objeto TransformGroup que rota un objeto Pi/4 en el eje de las x, y Pi/4 en el eje de las y. Este TransformGroup es agregado como hijo de objRoot.

Luego, se crea un cubo unitario con cada uno de sus seis lados emitiendo un color diferente. Este ColorCube es una de las primitivas de la Java 3D API. El ColorCube es agregado como hijo del objeto rotator. El método createSceneGraph() retorna una manija a su raíz.

He aquí el código para Static3DWorld:

    import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Canvas3D;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
public class Static3DWorld extends JFrame {
private Transform3D rotate1 = new Transform3D();
private Transform3D rotate2 = new Transform3D();
public Static3DWorld() {
super("Static3DWorld");
Canvas3D canvas3D = createCanvas3D();
BranchGroup scene = createSceneGraph();
connect(canvas3D, scene);
}
private Canvas3D createCanvas3D() {
setSize(300, 300);
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
setSize(300, 300);
getContentPane().add(canvas3D);
return canvas3D;
}
private BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup rotator = new TransformGroup(
rotateCube());
objRoot.addChild(rotator);
rotator.addChild(new ColorCube(0.3));
objRoot.compile();
return objRoot;
}
private Transform3D rotateCube() {
rotate1.rotX(Math.PI / 4.0d);
rotate2.rotY(Math.PI / 4.0d);
rotate1.mul(rotate2);
return rotate1;
}
private void connect(Canvas3D canvas3D,
BranchGroup scene) {
SimpleUniverse simpleU =
new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().
setNominalViewingTransform();
simpleU.addBranchGraph(scene);
}
public static void main(String[] args) {
new Static3DWorld().setVisible(true);
}
}

Luego de compilar y ejecutar Static3DWorld, deberíamos obtener lo siguiente:

staticube

He aquí un segundo ejemplo que utiliza la Java 3D API. El programa de ejemplo a continuación, Spinning3DWorld, le agrega movimiento al cubo. Nuevamente, el lugar desde donde comenzar el estudio del programa es el método createSceneGraph().

     private BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup spinner = new TransformGroup();
spinner.setCapability(
TransformGroup.ALLOW_TRANSFORM_WRITE);
objRoot.addChild(spinner);
spinner.addChild(new ColorCube(0.3));
spinner.addChild(makeSpin(spinner));
return objRoot;
}

Nótese que el grafo de escena es algo más complejo que en el primer ejemplo. El spinner es un TransformGroup que tiene permitido especificar la información de su transformación. Sin esta línea la rotación sería calculada, pero no se vería representada en la pantalla. Como antes, el spinner es agregado como hijo de objRoot, y un ColorCube es creado y agregado como hijo de spinner. Sin embargo, esta vez el spinner tiene un segundo hijo que se encarga de la rotación. El objeto RotationInterpolator es retornado desde el método makeSpin().

   private RotationInterpolator makeSpin(TransformGroup
spinner) {
RotationInterpolator rotator =
new RotationInterpolator(new Alpha(-1, 3000),
spinner);
rotator.setAxisOfRotation(rotateCube());
BoundingSphere bounds = new BoundingSphere();
rotator.setSchedulingBounds(bounds);
return rotator;
}

Un nuevo RotationInterpolator es construído para el spinner recibido como parámetro. El -1 indica que se repetirá hasta que el programa sea terminado. Disminuyendo el segundo número desde 3000 acelera la rotación, y aumentándolo se lentifica la misma. El método rotateCube() del ejemplo anterior se utiliza aquí para setear el eje de rotación.

Here’s the code for Spinning3DWorld:

    import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Alpha;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.BoundingSphere;
import javax.swing.JFrame;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
public class Spinning3DWorld extends JFrame {
private Transform3D rotate1 = new Transform3D();
private Transform3D rotate2 = new Transform3D();
public Spinning3DWorld() {
super("Spinning3DWorld");
Canvas3D canvas3D = createCanvas3D();
BranchGroup scene = createSceneGraph();
connect(canvas3D, scene);
}
private Canvas3D createCanvas3D() {
setSize(300, 300);
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
setSize(300, 300);
getContentPane().add(canvas3D);
return canvas3D;
}
private BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup spinner = new TransformGroup();
spinner.setCapability(
TransformGroup.ALLOW_TRANSFORM_WRITE);
objRoot.addChild(spinner);
spinner.addChild(new ColorCube(0.3));
spinner.addChild(makeSpin(spinner));
return objRoot;
}
private RotationInterpolator makeSpin(
TransformGroup spinner) {
RotationInterpolator rotator =
new RotationInterpolator(new Alpha(-1, 3000),
spinner);
rotator.setTransformAxis(rotateCube());
BoundingSphere bounds = new BoundingSphere();
rotator.setSchedulingBounds(bounds);
return rotator;
}
private Transform3D rotateCube() {
rotate1.rotX(Math.PI / 4.0d);
rotate2.rotY(Math.PI / 3.0d);
rotate1.mul(rotate2);
return rotate1;
}
private void connect(Canvas3D canvas3D,
BranchGroup scene) {
SimpleUniverse simpleU =
new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().
setNominalViewingTransform();
simpleU.addBranchGraph(scene);
}
public static void main(String[] args) {
new Spinning3DWorld().setVisible(true);
}
}

Luego de compilar y ejecutar Spinning3DWorld, deberíamos ver el mismo ColorCube desplegado en el programa Static3DWorld, pero esta vez el cubo debería rotar. He aquí una pantalla capturada en un instante de tiempo:

cuberotate

Ahora veamos el último ejemplo. En este ejemplo, Text3DWorld, reemplacemos el ColorCube rotador con texto tri-dimensional. Esto agrega un poco de complejidad al asunto. La construcción de un objeto Text3D requiere algunos detalles más que el que se solía utilizar al trabajar con Fonts. Tenemos más opciones y podemos customizar la forma de varias formas al alterar la textura o el material del objeto Shape3D. He aquí un caso sencillo que utiliza principalmente valores por defecto.

   private Shape3D createTextShape() {
Appearance textAppear = new Appearance();
textAppear.setMaterial(new Material());
Font3D font3D = new Font3D(new Font("Helvetica",
Font.PLAIN, 1),
new FontExtrusion());
Text3D textGeom = new Text3D(font3D,
new String("Text3DWorld"));
textGeom.setAlignment(Text3D.ALIGN_CENTER);
Shape3D textShape = new Shape3D();
textShape.setGeometry(textGeom);
textShape.setAppearance(textAppear);
return textShape;
}

La principal diferencia en este ejemplo, comparado con el anterior, es que debemos iluminar el objeto. Si no le proveemos luminicencia, el texto rotador no sería visible. Tenemos tres elecciones básicas para la luz: AmbientLight, DirectionalLight, y PointLight. Este ejemplo utiliza DirectionalLight. Especificamos la dirección de la luz como un Vector de floats. Similarmente, debemos especificar el color utilizando una tripleta de floats representando los valores rojo, verde, azul (en ese orden) como valores entre 0 y 1. Si proyectamos más de una fuente de luz en el objeto los valores de luminicencia son sumados pero cada color se setea con un valor máximo de 1.

     private void setLighting(TransformGroup objMove) {
DirectionalLight light =
new DirectionalLight();
light.setInfluencingBounds(
new BoundingSphere());
light.setDirection(
new Vector3f(0.0f,0.0f,-1.0f));
light.setColor(
new Color3f(0.0f, 1.0f, 1.0f));
objMove.addChild(light);
}

He aquí el código para Text3DWorld:

    import com.sun.j3d.utils.universe.SimpleUniverse;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Alpha;
import javax.media.j3d.RotationInterpolator;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Appearance;
import javax.media.j3d.Material;
import javax.media.j3d.Font3D;
import javax.media.j3d.FontExtrusion;
import javax.media.j3d.Text3D;
import javax.media.j3d.Shape3D;
import javax.media.j3d.DirectionalLight;
import javax.swing.JFrame;
import javax.vecmath.Vector3f;
import javax.vecmath.Color3f;
import java.awt.BorderLayout;
import java.awt.GraphicsConfiguration;
import java.awt.Font;
public class Text3DWorld extends JFrame {
private Transform3D rotate1 = new Transform3D();
private  Transform3D rotate2 = new Transform3D();
public Text3DWorld() {
super("Text3DWorld");
Canvas3D canvas3D = createCanvas3D();
BranchGroup scene = createSceneGraph();
connect(canvas3D, scene);
}
private Canvas3D createCanvas3D() {
setSize(300, 300);
getContentPane().setLayout(new BorderLayout());
GraphicsConfiguration config =
SimpleUniverse.getPreferredConfiguration();
Canvas3D canvas3D = new Canvas3D(config);
setSize(300, 300);
getContentPane().add(canvas3D);
return canvas3D;
}
public BranchGroup createSceneGraph() {
BranchGroup objRoot = new BranchGroup();
TransformGroup mover = moveTextBack();
TransformGroup spinner = createSpinner();
objRoot.addChild(mover);
mover.addChild(spinner);
spinner.addChild( createTextShape());
spinner.addChild(makeSpin(spinner));
setLighting(mover);
return objRoot;
}
private TransformGroup createSpinner() {
TransformGroup spinner = new TransformGroup();
spinner.setCapability(TransformGroup.
ALLOW_TRANSFORM_WRITE);
return spinner;
}
private TransformGroup moveTextBack() {
Transform3D transform3D = new Transform3D();
transform3D.setTranslation(
new Vector3f(0.0f, 0.0f, -5.0f));
return new TransformGroup(transform3D);
}
private Shape3D createTextShape() {
Appearance textAppear = new Appearance();
textAppear.setMaterial(new Material());
Font3D font3D = new Font3D(
new Font("Helvetica", Font.PLAIN, 1),
new FontExtrusion());
Text3D textGeom = new Text3D(font3D,
new String("Text3DWorld"));
textGeom.setAlignment(Text3D.ALIGN_CENTER);
Shape3D textShape = new Shape3D();
textShape.setGeometry(textGeom);
textShape.setAppearance(textAppear);
return textShape;
}
private void setLighting(
TransformGroup objMove) {
DirectionalLight light =
new DirectionalLight();
light.setInfluencingBounds(
new BoundingSphere());
light.setDirection(
new Vector3f(0.0f,0.0f,-1.0f));
light.setColor(new Color3f(
0.0f, 1.0f, 1.0f));
objMove.addChild(light);
}
private RotationInterpolator makeSpin(
TransformGroup spinner) {
RotationInterpolator rotator =
new RotationInterpolator(
new Alpha(-1, 3000), spinner);
rotator.setTransformAxis(rotateCube());
BoundingSphere bounds =
new BoundingSphere();
rotator.setSchedulingBounds(bounds);
return rotator;
}
private Transform3D rotateCube() {
rotate1.rotX(Math.PI / 4.0d);
rotate2.rotY(Math.PI / 3.0d);
rotate1.mul(rotate2);
return rotate1;
}
private void connect(Canvas3D canvas3D,
BranchGroup scene) {
SimpleUniverse simpleU =
new SimpleUniverse(canvas3D);
simpleU.getViewingPlatform().
setNominalViewingTransform();
simpleU.addBranchGraph(scene);
}
public static void main(String[] args) {
new Text3DWorld().setVisible(true);
}
}

Luego de compilar y ejecutar Text3DWorld, deberíamos ver el texto rotando Text3DWorld. He aquí una pantalla capturada en un instante de tiempo:

textrotate

Conclusión

El objetivo de este artículo era agregar complejidad, un paso a la vez. En el primer ejemplo creamos un objeto visible tri-dimensional. Luego las cosas se tornaron algo más complejas en el segundo ejemplo, donde hicimos rotar al objeto. El tercer ejemplo agregó aún más complejidad. Allí, hicimos rotar un objeto que requería luminosidad. Al utilizar métodos modulares podemos facilmente construir ejemplos mucho más complejos.

Sun Microsystems
http://cricava.com/java/detalleArticulos.php?id=226

Leave A Comment?