Por que eu devo ler este artigo: Para o estudo dos recursos da API Java 3D relacionados ao carregamento de modelos tridimensionais e a configuração da aparência de objetos, e como guia para empregá-la na criação de animações e jogos 3D em Java.

O tema é relevante tanto para desenvolvedores que desejam ampliar e tornar mais interessantes os cenários de interação de suas aplicações, quanto para aqueles que desejam desenvolver animações e jogos com interfaces gráficas tridimensionais.

Em aplicações que fazem uso de recursos tridimensionais, é muitas vezes desejável que os objetos apresentem aparência semelhante a aquela apresentada na realidade. Neste artigo, serão abordados recursos e classes da API Java 3D que permitem o carregamento de modelos tridimensionais, a definição de fontes de luz e a configuração da aparência de objetos.

Neste artigo, serão estudados recursos relacionados ao carregamento de modelos tridimensionais e a aparência de geometrias, bem como será iniciado o desenvolvimento de um jogo.

Carregamento de Modelos Tridimensionais

O primeiro artigo apresentou duas formas de definição de objetos geométricos: através do uso de classes utilitárias, que definem primitivas geométricas – cilindro, esfera, cubo, entre outras –, e a partir da definição de vértices e polígonos.

A definição de objetos complexos através de qualquer uma destas duas formas pode ser difícil e trabalhosa. Por esse motivo, o uso de ferramentas de modelagem 3D, como o 3ds Max ou o Blender, é de grande valor para a produção de modelos complexos.

Em Java 3D, modelos tridimensionais podem ser utilizados através de carregadores (loaders), classes responsáveis por importar objetos criados em ferramentas de modelagem para o universo virtual, tais como Wavefront (.obj), 3ds Max (.3ds) e VRML (.vrml).

Para permitir que, com o surgimento de novos tipos de arquivos de modelos, possam ser criados carregadores apropriados, a API define interfaces (pacote com.sun.j3d.loaders) que especificam o comportamento comum a carregadores. Este pacote define duas interfaces: Loader e Scene.

A interface Loader especifica os métodos responsáveis pela leitura, análise e criação do grafo de conteúdo associado ao objeto tridimensional, enquanto a interface Scene armazena os dados lidos pelo loader. Para facilitar a criação de carregadores, é fornecida, neste pacote, uma classe abstrata que implementa alguns métodos da interface Loader.

A seguir, será apresentado um exemplo de carregamento de arquivos Wavefront (.obj) através da classe ObjectFile, que é distribuída no pacote com.sun.j3d.loaders.objectfile.

O método load(), especificado pela interface Loader e implementado por esta classe, é sobrecarregado e possui três versões. Caso seja utilizada a versão que recebe como parâmetro um Reader e a leitura seja feita a partir de um arquivo localizado em uma pasta diferente daquela onde se encontra a classe, o arquivo .mtl – de aparência do objeto Wavefront, que acompanha o .obj – não será encontrado.

Isto ocorre porque, no código-fonte da classe ObjectFile, existem atributos que armazenam a pasta base onde estão os arquivos .obj e .mtl. Entretanto, esse atributo só é definido para os construtores que recebem URL ou String, não para o construtor que recebe um Reader. Assim sendo, no uso deste último, deve-se chamar o método setBasePath() para definir a pasta onde se encontra o arquivo .mtl e suas texturas.

O código apresentado na Listagem 1 carrega e exibe um modelo do satélite TDRS da NASA, copiado de http://www.nasa.gov/multimedia/3d_resources/models.html. O arquivo copiado está no formato .3ds e foi convertido para .obj, através do Blender, para que o carregador do Wavefront pudesse ser utilizado.

package quadrans.java3d;

// imports…

public class CarregarModelo1 {
 public static void main(String[] args) {
   SimpleUniverse universo = new SimpleUniverse(); // Cria um universo virtual.
   // Cria um subgrafo de conteúdo.
   BranchGroup subgrafoConteudo = new BranchGroup(); 
  try { // Carrega o satélite e o adiciona ao universo.
    ObjectFile carregador = new ObjectFile(ObjectFile.RESIZE);
    carregador.setBasePath("c://Modelo3D");
    Scene cena = carregador.load(new FileReader("c://Modelo3D//TDRS.obj"));
    subgrafoConteudo.addChild(cena.getSceneGroup());
    universo.addBranchGraph(subgrafoConteudo);
  } catch(Exception erro) { erro.printStackTrace(); }        

  // Define o comportamento do observador.
  OrbitBehavior comportamentoOrbita = new OrbitBehavior(universo.getCanvas(), 
  OrbitBehavior.REVERSE_ALL);
    comportamentoOrbita.setSchedulingBounds(new BoundingSphere());
    universo.getViewingPlatform().setViewPlatformBehavior(comportamentoOrbita);
    universo.getViewingPlatform().setNominalViewingTransform();
  }
}
Listagem 1. Código que carrega um modelo 3D de satélite e o exibe

As linhas de código em negrito são responsáveis por carregar o modelo e o adicionar ao universo virtual. Além dessas linhas, é adicionado um comportamento de órbita para que o usuário possa utilizar o mouse para a navegação no universo, conforme estudado no primeiro artigo. A Figura 1 exibe o resultado da execução desse código.

Satélite exibido como resultado da execução do código
Figura 1. Satélite exibido como resultado da execução do código da Listagem 1

Os dados carregados por um loader podem conter tanto nós com objetos Shape3D, quanto comportamentos. Para acessá-los, pode-se utilizar o nome dos nós presentes na cena. A classe Scene possui um método chamado getNamedObjects(), que retorna uma lista de todos os nomes de objetos presentes na cena e os objetos associados no grafo de cena.

Para ilustrar o uso desse método, a Listagem 2 apresenta um segundo exemplo de carregamento de modelo, um carro, cuja roda é recuperada e rotacionada, criando a ideia de movimento (Figura 2). Para este exemplo, foi necessário definir fontes de luz, uma vez que o modelo não possui texturas e, sem elas, não seria possível visualizá-lo. Mais adiante, neste artigo, conceitos e técnicas sobre iluminação serão abordados.


package quadrans.java3d;

// imports...

public class CarregarModelo2 {
 public static void main(String[] args) {
   SimpleUniverse universo = new SimpleUniverse(); // Cria um universo virtual.        
   // Cria um subgrafo de conteúdo.
   BranchGroup subgrafoConteudo = new BranchGroup();
  // Define uma região de influência que será utilizada por recursos de iluminação.
  BoundingSphere regiao = new BoundingSphere(new Point3d(0, 0, 0), 100);

  // Carrega o satélite e o adiciona ao universo.
  try {
    ObjectFile carregador = new ObjectFile(ObjectFile.RESIZE);
    carregador.setBasePath("c://Modelo3D");
    Scene cena = carregador.load(new FileReader("c://Modelo3D//Nissan.obj"));
    BranchGroup grupoCena = cena.getSceneGroup();

    // Recupera uma referência para o Shape3D que representa a roda do carro.
    String nomeRoda = "entity124";
    grupoCena.removeChild(((Shape3D)cena.getNamedObjects().get(nomeRoda)));
    // Cria um grupo de transformação para rotacioná-la.
    TransformGroup grupoTransformacao = new TransformGroup();
    grupoTransformacao.addChild(((Shape3D)cena.getNamedObjects().get(nomeRoda)));
    grupoTransformacao.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    subgrafoConteudo.addChild(grupoTransformacao);
    // Recupera os dois limites da roda para descobrir
    // seu centro e, consequentemente, o eixo de rotação.
    BoundingBox fronteiras = (BoundingBox) 
    ((Shape3D)cena.getNamedObjects().get(nomeRoda)).getBounds();
      Point3d superior = new Point3d();
      fronteiras.getUpper(superior);
      Point3d inferior = new Point3d();
      fronteiras.getLower(inferior);
      // Define o eixo de rotação
      Transform3D eixoRotacao = new Transform3D();
      eixoRotacao.rotZ(Math.toRadians(-90));
      eixoRotacao.setTranslation(new Vector3d(0, 
      inferior.getY()+(superior.getY()-inferior.getY())/2, 
      inferior.getZ()+(superior.getZ()-inferior.getZ())/2));

      // Cria um interpolador para rotacionar a roda.
      RotationInterpolator interpoladorRotacao = new 
      RotationInterpolator(new Alpha(-1, 3000), grupoTransformacao);
      interpoladorRotacao.setSchedulingBounds(regiao);
      interpoladorRotacao.setTransformAxis(eixoRotacao);
      subgrafoConteudo.addChild(interpoladorRotacao);

      subgrafoConteudo.addChild(grupoCena);
    } catch(Exception erro) { erro.printStackTrace(); }

    // Define duas fontes de iluminação no universo virtual.       
    AmbientLight luzAmbiente = new AmbientLight(new Color3f(0.25f, 0.25f, 0.25f));
    luzAmbiente.setInfluencingBounds(regiao);
    subgrafoConteudo.addChild(luzAmbiente);
    DirectionalLight luzDirecional1 = 
    new DirectionalLight(new Color3f(.8f, .8f, .8f), new Vector3f(-1f, 1f, -1f));
  luzDirecional1.setInfluencingBounds(regiao);
  subgrafoConteudo.addChild(luzDirecional1);

  universo.addBranchGraph(subgrafoConteudo);
  // Define o comportamento do observador.
  OrbitBehavior comportamentoOrbita = new OrbitBehavior(universo.getCanvas(),
  OrbitBehavior.REVERSE_ALL);
    comportamentoOrbita.setSchedulingBounds(new BoundingSphere());
    universo.getViewingPlatform().setViewPlatformBehavior(comportamentoOrbita);
    universo.getViewingPlatform().setNominalViewingTransform();
  }
} ... 

Quer ler esse conteúdo completo? Tenha acesso completo