Desenvolvimento 3D em Java - Java Magazine 84
Esta é a segunda parte de uma série de três artigos que tratam do desenvolvimento de animações e jogos 3D em Java. Neste artigo, serão abordados classes e recursos que permitem a criação de interfaces mais realísticas do que aquelas desenvolvidas no artigo anterior, e será iniciado o desenvolvimento de um jogo 3D.
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();
}
}
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.
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();
}
}"
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo