Este artigo apresenta o Java3D API em três exemplos de codificação. No primeiro exemplo, você verá como usar a Java 3D API, pôr um objeto na tela e posiciona-lo. O segundo exemplo mostra como usar a Java 3D API e pôr um objeto em movimento. Já o terceiro exemplo mostra como usar o API para aplicar iluminação a uma cena.

A Java 3D API é um pacote opcional do J2SE que está atualmente disponível em Solaris e Windows como também outras plataformas. Você pode baixar a implementação para seu computador do site Java 3D API. Este site também oferece links para tutoriais, demonstrações, e códigos de exemplo--também inclui um link às outras plataformas em qual a Java 3D API está disponível.

Utilizando construtores Java 3D API, você cria um mundo virtual 3D no qual você pode construir e manipular estruturas 3D. Uma analogia familiar poderia ser um documento XML. Quando você vê um documento XML feito em um browser você foca no conteúdo e pode não estar atento à estrutura de árvore subjacente. Semelhantemente com a Java 3D API, também são armazenadas as informações de cena 3D que você vê em uma aplicação, como objetos em espaço em uma hierarquia de nós conhecida como um gráfico de cena. Os nós representam objetos, informação sobre posição ou movimento, e informação sobre aparência e iluminação.

À raiz da estrutura treelike está um objeto de BranchGroup. Você precisará associar este objeto com o objeto de Canvas3D que é usado para fazer a cena. Para consistência com os exemplos Java 3D, esta raiz do gráfico de cena é nomeada objRoot em cada um dos exemplos neste artigo.

Há três passos fundamentais para criação de um 3D world:

  • Crie um objeto de Canvas3D;
  • Crie um gráfico de cena;
  • Conecte o objeto Canvas3D a um objeto BranchGroup que aponta para a raiz do gráfico de cena.

Estes três passos compõem o corpo do construtor em cada dos exemplos neste artigo.

No primeiro programa de exemplo abaixo, Static3DWorld, o método createCanvas3D() ajusta o tamanho de um JFrame. O método cria um objeto de canvas3D e acrescenta isto ao centro do JFrame. A específica tarefa da Java 3D API é chamar o método estático getPreferredConfiguration(), e passar o resultado no construtor de Canvas3D

A ação principal do exemplo está contida no método createSceneGraph(). O objeto BranchGroup, objRoot, aponta para a raiz do gráfico de cena. O objeto rotator é um TransformGroup que gira um objeto Pi/4 em cima do eixo de x e Pi/4 em cima do eixo de y. Este TransformGroup é adicionado como um filho do objeto objRoot.

Em seguida, um cubo com cada um de seus seis lados emitindo uma cor diferente é criado. Este ColorCube é um exemplo primitivo da Java 3D API. O ColorCube é inserido como um filho do objeto rotator. O método createSceneGraph() retorna um manipulador para sua raiz.

Abaixo está o código para o 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);
   }
}

Depois que você compilar e rodar o Static3DWorld, você verá a seguinte imagem:

java3d1.JPG

Este é o segundo exemplo que usa a Java 3D API. O programa de exemplo que segue, Spinning3DWorld, põe o cubo em moviemtno. Novamente, o lugar para começar sua programação é no 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;
}

Note que o novo gráfico é um pouco mais complexo que no primeiro exemplo. O objeto spinner é um TransformGroup que permite recuperar a informação da transformação. Sem esta linha seria calculada a rotação, mas não seria representado na tela. Como fizemos anteriormente, o objeto spinner é adicionado como um filho de objRoot, e um ColorCube é criado e adicionado como um filho do objeto spinner. Entretanto, neste momento, o objeto spinner possui um segundo “filho” que é responsável pela rotação. O objeto RotationInterpolator é devolvido através do 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;
}

Um novo RotationInterpolator é construído para o spinner que é passado. O -1 indica que a repetição acontecerá até que o programa seja terminado. Diminuindo o segundo número de parâmetro do método RotationInterpolator, a velocidade da rotação irá aumentar, enquanto que aumentar este número implica em reduzir a velocidade a rotação. O método rotateCube() do exemplo prévio é usado aqui para fixar o eixo de rotação.

Abaixo está o código de 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);
   }
}

Depois que você compila e roda Spinning3DWorld, você deve ver o mesmo ColorCube que você viu com o programa de Static3DWorld, mas este exemplo, o cubo fica girando.

java3d2.JPG

Neste último exemplo, Text3DWorld, vamos substituir o objeto ColorCube pelo texto tridimensional. A construção do objeto Text3D requer mais alguns detalhes que os que você precisaria ter para trabalhar com Fontes. Você tem mais opções e poderia personalizar a forma alterando sua textura ou material do objeto Shape3D. Neste exemplo temos o uso das opções básicas.


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;
}

A principal diferença neste exemplo comparando-o ao exemplo anterior é que você precisa iluminar o objeto. Se você não ilumina-lo, o texto não será visível. Você tem três escolhas básicas para luzes: AmbientLight, DirectionalLight, e PointLight. Este exemplo usa DirectionalLight. Você especifica a direção da luz como um vetor de floats. De forma semelhante, você tem que especificar a cor que usa uma combinação de vermelho, verde, azul.


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);
}

Abaixo está o 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);
   }
}

Depois de compilar e rodar, você verá o texto Text3DWorld em movimento. Esta é uma imagem do programa em execução.

java3d3.JPG

O objetivo deste artigo foi de evoluir aos poucos o nosso primeiro exemplo. No primeiro caso, você criou e exibiu um objeto tridimensional. A partir daí, as coisas se tornavam um pouco mais complexas no segundo exemplo onde você fixou o objeto e o fez girar. O terceiro exemplo foi adicionado um pouco mais de complexidade. Neste exemplo, você fez o objeto girar e também aprendeu a mexer com a iluminação deste.