Dalton has posted 1 posts at DZone. View Full User Profile

3D Model Interaction with Java 3D

01.26.2008
| 48348 views |
  • submit to reddit

Hierarchical Model

Now that you have access to all components separately, you can build your custom hierarchical graph.

If you have been using Swing or AWT, you are already familiar with the hierarchical model. For instance, you can have a JFrame, which then adds a JPanel, which then adds a JLabel and so forth. Many properties applied on the root are propagated to children, like the isVisible() property. With a 3D model, all transforms and texturizations will be applied to all children (subgraphs). Imagine if you had to apply the same transform over and over to many model parts just to make one movement?

Java 3D has a class called Group, which is basically an N-Tree: every children has only one parent and an arbitrary number of children. You will use subclasses of Group to create your scenes. Java 3D has also the Leaf class, which is used to construct objects on the tree which wouldn't make sense with children, like background, camera, behaviour, etc.

Scene graph

Hierarchical model of the scene

I will use the TransformGroup class as the default node for building the graph. You may use other subclasses of Group if you have other needs. You may want to keep a reference of every TransformGroup you create if you are going to do some interaction (like making a cockroach walk).

Note that the code above suffers from the same flaws of programatic GUI construction. You can define the graph in XML and create a custom parser if you need reusability. If possible, you can also edit the model graph in a model editor to avoid having to perform these steps on your program.

Hierarchical construction of the graph

            TransformGroup getCockroach(Scene scene) {

/* Obtain the scene's BranchGroup, from which components are removed */
BranchGroup root = scene.getSceneGroup();

Map<String, Shape3D> nameMap = scene.getNamedObjects();

/* Remove all children (you don't want a MultiParentException) */
root.removeAllChildren();

/* Construct the groups */
TransformGroup leftLegs = new TransformGroup();
TransformGroup rightLegs = new TransformGroup();
TransformGroup body = new TransformGroup();
TransformGroup roach = new TransformGroup();

/* Build the graph --> LEFT LEGS */
leftLegs.addChild(nameMap.get("luplegf"));
leftLegs.addChild(nameMap.get("luplegm"));
leftLegs.addChild(nameMap.get("luplegr"));
leftLegs.addChild(nameMap.get("lmidlegf"));
leftLegs.addChild(nameMap.get("lmidlegm"));
leftLegs.addChild(nameMap.get("lmidlegr"));
leftLegs.addChild(nameMap.get("llowlegf"));
leftLegs.addChild(nameMap.get("llowlegm"));
leftLegs.addChild(nameMap.get("llowlegr"));
leftLegs.addChild(nameMap.get("lfootf"));
leftLegs.addChild(nameMap.get("lfootm"));
leftLegs.addChild(nameMap.get("lfootr"));

/* Build the graph --> RIGHT LEGS */
rightLegs.addChild(nameMap.get("ruplegf"));
rightLegs.addChild(nameMap.get("ruplegm"));
rightLegs.addChild(nameMap.get("ruplegr"));
rightLegs.addChild(nameMap.get("rmidlegf"));
rightLegs.addChild(nameMap.get("rmidlegm"));
rightLegs.addChild(nameMap.get("rmidlegr"));
rightLegs.addChild(nameMap.get("rlowlegf"));
rightLegs.addChild(nameMap.get("rlowlegm"));
rightLegs.addChild(nameMap.get("rlowlegr"));
rightLegs.addChild(nameMap.get("rfootf"));
rightLegs.addChild(nameMap.get("rfootm"));
rightLegs.addChild(nameMap.get("rfootr"));

/* Build the graph --> REMAINING BODY */
body.addChild(nameMap.get("antena"));
body.addChild(nameMap.get("antenar"));
body.addChild(nameMap.get("wing"));
body.addChild(nameMap.get("abdomen"));
body.addChild(nameMap.get("head"));
body.addChild(nameMap.get("prothorx"));
body.addChild(nameMap.get("eyes"));
body.addChild(nameMap.get("lpalp"));
body.addChild(nameMap.get("rpalp"));

/* Build the graph --> ROACH */
roach.addChild(leftLegs);
roach.addChild(rightLegs);
roach.addChild(body);

/* Enable transform capability (it is not enabled by default) */
enableTransformCapability(leftLegs, rightLegs, body, roach);

return roach;
}

void enableTransformCapability(TransformGroup... parts) {
for (TransformGroup part : parts) {
part.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
}
}

Note that I have declared the transform groups locally, but on your program you will have to declare them globally or keep a reference to them somewhere if you plan to add interaction to your model. We will configure the camera (actually a view) and add lights later.

I did a fairly simple hierarchy because the movement this cockroach will do is just as simple. In my assignment I had to do an interaction in which the legs would articulate, which implied in a different (i.e. more complex) setup for the hierarchy of the legs.

Appearance

The loaded cockroach is quite pale since no material descriptors were associated with it, but this is not a problem, as you can define your textures for each component of your graph. You must read the Material javadoc to understand what is being done here.

To save some effort, I will declare some constants for ambient, emissive and specular light colors. The user may choose the diffuse color - the light which is emitted when the object is under the influence of some light.

            import javax.vecmath.Color3f; 

private static final Color3f SPECULAR_LIGHT_COLOR = new Color3f(Color.WHITE);
private static final Color3f AMBIENT_LIGHT_COLOR = new Color3f(Color.LIGHT_GRAY);
private static final Color3f EMISSIVE_LIGHT_COLOR = new Color3f(Color.BLACK);

Now you can create a method that returns an Apperance based on a given Color:

            import javax.media.j3d.Material;
import javax.media.j3d.Appearance;

Appearance getAppearance(Color color) {
Appearance app = new Appearance();
app.setMaterial(getMaterial(color));
return app;
}

Material getMaterial(Color color) {
return new Material(AMBIENT_LIGHT_COLOR,
EMISSIVE_LIGHT_COLOR,
new Color3f(color),
SPECULAR_LIGHT_COLOR,
100F);
}

It's possible to use an image as a texture, but there are some constraints: the image must be equal in width and height and must be a power of 2. If you have ever used Swing, you know you have to pass an instance of Component to MediaTracker if you want to track the loading of an image. Loading a texture uses a similar process:

            import javax.media.j3d.Texture2D;
import com.sun.j3d.utils.image.TextureLoader;

Appearance getAppearance(String path, Component canvas, int dimension) {
Appearance appearance = new Appearance();
appearance.setTexture(getTexture(path, canvas, dimension));
return appearance;
}

Texture getTexture(String path, Component canvas, int dimension) {
TextureLoader textureLoader = new TextureLoader(path, canvas);

Texture2D texture = new Texture2D(Texture2D.BASE_LEVEL,
Texture2D.RGB,
dimension,
dimension);

texture.setImage(0, textureLoader.getImage());

return texture;
}

Applying the material:

            Scene cockroach = getSceneFromFile("ROACH_mod.obj");
Map<String, Shape3D> nameMap = cockroach.getNamedObjects();

Color brown = new Color(165, 42, 42);
Appearance brownAppearance = getAppearance(brown);

nameMap.get("wing").setAppearance(brownAppearance);
A material responds to different light positions

As far as I've tested, if you assign a texture instead of a material, the object will not respond to different light configurations, instead it will look like being constantly illuminated.

Properly textured roach

Roach with a texture

Lights

As you have seen here, we still need to add two lights and one camera (a view). If you have read the basic setup, you have seen a directional light being added to the root of the scene. It's interesting to make the light go with the roach wherever it goes if you don't want it to get completely black after walking out of the reach of the light - on this case you will need to add your lights as leafs on the same node which contains the object you want to illuminate. On the other hand, if you want your object to become shadowed as it moves, you should add the lights to a node other than the one you used to add the model.

Except from finding the right vector to point the light to your object, creating and configuring lights is mostly simple. The following figure demonstrates how to construct an ambient light and a directional light:

            import javax.media.j3d.DirectionalLight;
import javax.media.j3d.AmbientLight;


Color3f directionalLightColor = new Color3f(Color.BLUE);
Color3f ambientLightColor = new Color3f(Color.WHITE);
Vector3f lightDirection = new Vector3f(-1F, -1F, -1F);

AmbientLight ambientLight = new AmbientLight(ambientLightColor);
DirectionalLight directionalLight = new DirectionalLight(directionalLightColor, lightDirection);

Bounds influenceRegion = new BoundingSphere();

ambientLight.setInfluencingBounds(influenceRegion);
directionalLight.setInfluencingBounds(influenceRegion);

Why do you need an influence region? For the same reason you need clipping: to avoid doing useless calculations. See the Light javadoc for more information.

References
Published at DZone with permission of its author, Dalton Filho. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Rick Ross replied on Sat, 2008/01/26 - 2:39pm

Wow, Dalton! I am truly impressed, and you are a rock star! This is super neat, so thank you for being part of the DZone/Javalobby community!

Rick

Steven Devijver replied on Sat, 2008/01/26 - 2:44pm

I can't stop thinking "Groovy Builder" ... :-)

Andres Almiray replied on Sat, 2008/01/26 - 3:15pm in response to: Steven Devijver

hehe me too =) I hope some takes on the the task of creating one.

Bob Spuckwood replied on Sun, 2008/01/27 - 12:37pm

Uh, so where, exactly is the viewable demo?

Rick Ross replied on Sun, 2008/01/27 - 5:07pm in response to: Bob Spuckwood

[quote=jonjonz]Uh, so where, exactly is the viewable demo?[/quote]

The rhetorical value of starting with "Uh" is lost on me. Be kind to this man, please. If you'd like to see a demo, then I imagine he'd be happy to oblige if you just ask nicely. Your comment seemed to carry more an overtone of criticism than of request. Dalton worked hard and put much effort into this article, so let's give him some acknowledgement.

"This looks interesting, Dalton. Would it be possible to provide a link to a viewable demo? Thanks."

Cheers,
Rick

Dalton Filho replied on Mon, 2008/01/28 - 11:31am

jonjonz,

I have now included a binary download that includes the 3D model. Note that you must have Java3D installed for it to work. Check https://java3d.dev.java.net/binary-builds.html

 

Dalton

Trent Creekmore replied on Thu, 2009/04/02 - 7:16pm

So where is the example code?

Trent Creekmore replied on Tue, 2009/04/07 - 6:51pm

s

deepak singh replied on Wed, 2009/06/03 - 2:03am


I was  testing 3D Model Interaction with Java 3D throught this example .
 During testing following error is coming


Exception in thread "main" java.lang.UnsatisfiedLinkError: no j3dcore-ogl in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1709)
        at java.lang.Runtime.loadLibrary0(Runtime.java:823)
        at java.lang.System.loadLibrary(System.java:1030)
        at javax.media.j3d.MasterControl$6.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.media.j3d.MasterControl.loadLibraries(Unknown Source)
        at javax.media.j3d.VirtualUniverse.<clinit>(Unknown Source)
        at javax.media.j3d.Canvas3D.<clinit>(Unknown Source)

        at com.daltonfilho.tutorials.java3d.SceneControl.initCanvas(SceneControl.java:68)
        at com.daltonfilho.tutorials.java3d.SceneControl.<init>(SceneControl.java:54)
        at com.daltonfilho.tutorials.java3d.Program.main(Program.java:31)



can you  help me i am using netbean +j2re1.4.1+jdk1.6.0_10+Java3D.1.4

I will very thank full for you help

deepak

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.