Professional Documents
Culture Documents
The Joy of Java 3D: by Greg Hopkins
The Joy of Java 3D: by Greg Hopkins
by Greg Hopkins
Copyright 2001-2007
Introduction
Java 3D is an addition to Java for displaying three-dimensional graphics. Programs written in Java 3D can
be run on several different types of computer and over the internet.
The Java 3D class library provides a simpler interface than most other graphics libraries, but has enough
capabilities to produce good games and animation. Java 3D builds on existing technology such as DirectX
and OpenGL so the programs do not run as slowly as you might expect. Also, Java 3D can incorporate
objects created by 3D modeling packages like TrueSpace and VRML models.
This tutorial is an introduction to Java 3D. Examples lead you through the basic methods for producing 3D
images and animation. You do not need to have any knowledge of 3D graphics or Java 3D to learn from
this tutorial, but it will help if you have a basic understanding of the Java programming language.
Programming in three dimensions can seem complicated because of the amount of jargon and the
mathematics involved, this tutorial will keep things as simple as possible.
Please help to improve this tutorial for future readers by reporting any mistakes you find or suggesting
improvements to editor@java3d.org.
Look at the Hello3d() constructor and you will see the five lines that perform each of these steps. The
program displays a glowing cube, the viewer is looking directly at the red face of the cube, so what you
actually see is a red square on a black background
importcom.sun.j3d.utils.universe.SimpleUniverse;
importcom.sun.j3d.utils.geometry.ColorCube;
importcom.sun.j3d.utils.geometry.Sphere;
importjavax.media.j3d.BranchGroup;
publicclassHello3d{
publicHello3d()
{
SimpleUniverseuniverse=newSimpleUniverse();
BranchGroupgroup=newBranchGroup();
group.addChild(newColorCube(0.3));
universe.getViewingPlatform().setNominalViewingTransform();
universe.addBranchGraph(group);
}
publicstaticvoidmain(String[]args){
newHello3d();
}
}//endofclassHello3d
The import statements at the beginning of this program use various parts of Java 3D, so compiling and
running this program is a good test that you have installed Java 3D correctly.
//looktowardstheball
universe.getViewingPlatform().setNominalViewingTransform();
//addthegroupofobjectstotheUniverse
universe.addBranchGraph(group);
}
publicstaticvoidmain(String[]args){newBall();}
}
The sphere we created is white (the default), it appears red because of the colored light. Since it is a
DirectionalLight, we also have to specify how far the light shines and in what direction. In the example, the
light shines for 100 meters from the origin and the direction is to the right, down and into the screen (this is
defined by the vector: 4.0 right, -7.0 down, and -12.0 into the screen).
You can also create an AmbientLight which will produce a directionless light, or a SpotLight if you want to
focus on a particular part of your scene. A combination of a strong directional light and a weaker ambient
light gives a natural-looking appearance to your scene. Java 3D lights do not produce shadows.
Example
Transform transform= new Transform3D();
TransformGroup tg = new TransformGroup();
Cone cone = new Cone(0.5f, 0.5f);
Vector3f vector = new Vector3f(-.2f,.1f , -.4f);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(cone);
This may seem complicated, but the transform groups enable you to collect objects together and move them
as one unit. For example, a table could be made up of cylinders for legs and a box for the top. If you add all
the parts of the table to a single transform group, you can move the whole table with one translation.
The Transform3D class can do much more than specifying the co-ordinates of the object. The functions
include setScale to change the size of an object and rotX, rotY and rotZ for rotating an object around each
axis (counter clockwise).
This example displays the different objects on each axis.
importcom.sun.j3d.utils.geometry.*;
importcom.sun.j3d.utils.universe.*;
importjavax.media.j3d.*;
importjavax.vecmath.*;
publicclassPosition{
publicPosition(){
SimpleUniverseuniverse=newSimpleUniverse();
BranchGroupgroup=newBranchGroup();
//Xaxismadeofspheres
for(floatx=1.0f;x<=1.0f;x=x+0.1f)
{
Spheresphere=newSphere(0.05f);
TransformGrouptg=newTransformGroup();
Transform3Dtransform=newTransform3D();
Vector3fvector=newVector3f(x,.0f,.0f);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(sphere);
group.addChild(tg);
}
//Yaxismadeofcones
for(floaty=1.0f;y<=1.0f;y=y+0.1f)
{
TransformGrouptg=newTransformGroup();
Transform3Dtransform=newTransform3D();
Conecone=newCone(0.05f,0.1f);
Vector3fvector=newVector3f(.0f,y,.0f);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(cone);
group.addChild(tg);
}
//Zaxismadeofcylinders
for(floatz=1.0f;z<=1.0f;z=z+0.1f)
{
TransformGrouptg=newTransformGroup();
Transform3Dtransform=newTransform3D();
Cylindercylinder=newCylinder(0.05f,0.1f);
Vector3fvector=newVector3f(.0f,.0f,z);
transform.setTranslation(vector);
tg.setTransform(transform);
tg.addChild(cylinder);
group.addChild(tg);
}
Color3flight1Color=newColor3f(.1f,1.4f,.1f);//greenlight
BoundingSpherebounds=
newBoundingSphere(newPoint3d(0.0,0.0,0.0),100.0);
Vector3flight1Direction=newVector3f(4.0f,7.0f,12.0f);
DirectionalLightlight1
=newDirectionalLight(light1Color,light1Direction);
light1.setInfluencingBounds(bounds);
group.addChild(light1);
universe.getViewingPlatform().setNominalViewingTransform();
//addthegroupofobjectstotheUniverse
universe.addBranchGraph(group);
}
publicstaticvoidmain(String[]args){
newPosition();
}
}
Appearance Is Everything
There are many ways to change the way that objects in your scene look.
You can change their color, how much light they reflect. You can paint
them with two-dimensional images, or add rough textures to their
surfaces. The Appearance class contains the functions for making these
changes. This section shows you how to use these functions.
The simplest way of setting the appearance is by specifying only the
color and the shading method. This works for setting an object to being
a simple color, but to make an object look realistic, you need to specify
how an object appears under lights. You do this by creating a Material.
Step
1. Create an object
2. Create an appearance
3. Create a color
4. Create the coloring attributes
5.
6.
Example
Sphere sphere = new Sphere();
Appearance ap = new Appearance();
Color3f col = new Color3f(0.0f, 0.0f, 1.0f);
ColoringAttributes ca = new ColoringAttributes
(col, ColoringAttributes.NICEST);
ap.setColoringAttributes(ca);
sphere.setAppearance(ap);
Materials
Materials have five properties that enable you to specify how the object appears. There are four colors:
Ambient, Emissive, Diffuse, and Specular. The fifth property is shininess, that you specify with a number.
Each color specifies what light is given off in a certain situation.
Ambient color reflects light that been scattered so much by the environment that the direction is
impossible to determine. This is created by an AmbientLight in Java 3D.
Emissive color is given off even in darkness. You could use this for a neon sign or a glow-in-the-dark
object
Diffuse color reflects light that comes from one direction, so it's brighter if it comes squarely down on
a surface that if it barely glances off the surface. This is used with a DirectionalLight.
Specular light comes from a particular direction, and it tends to bounce off the surface in a preferred
direction. Shiny metal or plastic have a high specular component. The amount of specular light that
reaches the viewer depends on the location of the viewer and the angle of the light bouncing off the
object.
Changing the shininess factor affects not just how shiny the object is, but whether it shines with a small
glint in one area, or a larger area with less of a gleaming look.
For most objects you can use one color for both Ambient and Diffuse components, and black for Emissive
(most things dont glow in the dark). If its a shiny object, you would use a lighter color for Specular
reflections. For example, the material for a red billiard ball might be:
//billiardball
//ambientemissivediffusespecularshininess
//Materialmat=newMaterial(red,black,red,white,70f);
For a rubber ball, you could use a black or red specular light instead of white which would make the ball
appear less shiny. Reducing the shininess factor from 70 to 0 would not work the way you might expect, it
would spread the white reflection across the whole object instead of it being concentrated in one spot.
Texture
Materials make change the appearance of a whole shape, but sometimes even the shiniest objects can seem
dull. By adding texture you can produce more interesting effects like marbling or wrapping a twodimensional image around your object.
The TextureLoader class enables you to load an image to use as a texture. The dimensions of your image
must be powers of two, for example 128 pixels by 256. When you load the texture you can also specify
how you want to use the image. For example, RGB to use the color of the image or LUMINANCE to see
the image in black and white.
After the texture is loaded, you can change the TextureAttributes to say whether you want the image to
replace the object underneath or modulate the underlying color. You can also apply it as a decal or blend the
image with the color of your choice.
If you are using a simple object like a sphere then you will also have to enable texturing by setting the
primitive flags. These can be set to Primitive.GENERATE_NORMALS+
Primitive.GENERATE_TEXTURE_COORDS when you create the object.
In case this is starting to sound a bit complicated, here is an example. You can experiment with the texture
settings in this example and compare the results. You can download the picture I used from
http://www.java3d.org/Arizona.jpg or you can substitute a picture of your own.
importcom.sun.j3d.utils.geometry.*;
importcom.sun.j3d.utils.universe.*;
importcom.sun.j3d.utils.image.*;
importjavax.media.j3d.*;
importjavax.vecmath.*;
importjava.awt.Container;
publicclassPictureBall{
publicPictureBall(){
//Createtheuniverse
SimpleUniverseuniverse=newSimpleUniverse();
//Createastructuretocontainobjects
BranchGroupgroup=newBranchGroup();
//Setupcolors
Color3fblack=newColor3f(0.0f,0.0f,0.0f);
Color3fwhite=newColor3f(1.0f,1.0f,1.0f);
Color3fred=newColor3f(0.7f,.15f,.15f);
//Setupthetexturemap
TextureLoaderloader=newTextureLoader("K:\\3d\\Arizona.jpg",
"LUMINANCE",newContainer());
Texturetexture=loader.getTexture();
texture.setBoundaryModeS(Texture.WRAP);
texture.setBoundaryModeT(Texture.WRAP);
texture.setBoundaryColor(newColor4f(0.0f,1.0f,0.0f,0.0f));
//Setupthetextureattributes
//couldbeREPLACE,BLENDorDECALinsteadofMODULATE
TextureAttributestexAttr=newTextureAttributes();
texAttr.setTextureMode(TextureAttributes.MODULATE);
Appearanceap=newAppearance();
ap.setTexture(texture);
ap.setTextureAttributes(texAttr);
//setupthematerial
ap.setMaterial(newMaterial(red,black,red,black,1.0f));
//Createaballtodemonstratetextures
intprimflags=Primitive.GENERATE_NORMALS+
Primitive.GENERATE_TEXTURE_COORDS;
Spheresphere=newSphere(0.5f,primflags,ap);
group.addChild(sphere);
//Createlights
Color3flight1Color=newColor3f(1f,1f,1f);
BoundingSpherebounds=
newBoundingSphere(newPoint3d(0.0,0.0,0.0),100.0);
Vector3flight1Direction=newVector3f(4.0f,7.0f,12.0f);
DirectionalLightlight1
=newDirectionalLight(light1Color,light1Direction);
light1.setInfluencingBounds(bounds);
group.addChild(light1);
AmbientLightambientLight=
newAmbientLight(newColor3f(.5f,.5f,.5f));
ambientLight.setInfluencingBounds(bounds);
group.addChild(ambientLight);
//looktowardstheball
universe.getViewingPlatform().setNominalViewingTransform();
//addthegroupofobjectstotheUniverse
universe.addBranchGraph(group);
}
publicstaticvoidmain(String[]args){
newPictureBall();
}
}
You can also set up three-dimensional textures, using shapes instead of a flat image. Unfortunately, these do
not currently work very well across different platforms.
Special Effects
Look at the AppearanceTest example that comes with Java 3D for more effects you can use. For example
you can display objects as wire-frames, display only the corners of an object and so on. You can even make
objects transparent, with the following settings:
TransparencyAttributest_attr=
newTransparencyAttributes(TransparencyAttributes.BLENDED,0.5f,
TransparencyAttributes.BLEND_SRC_ALPHA,
TransparencyAttributes.BLEND_ONE);
ap.setTransparencyAttributes(t_attr);
Canvas3D
Each area where three-dimensional graphics can be painted is called a Canvas3D. This is a rectangle that
contains a view of the objects in your universe. You place the canvas inside a frame, then you create a
universe to be displayed in the canvas.
The following example shows how to create a canvas in a frame with labels at the top and bottom. The
program can be run as either an applet or an application.
importcom.sun.j3d.utils.universe.SimpleUniverse;
importcom.sun.j3d.utils.geometry.ColorCube;
importjavax.media.j3d.BranchGroup;
importjavax.media.j3d.Canvas3D;
importjava.awt.GraphicsConfiguration;
importjava.awt.BorderLayout;
importjava.awt.Label;
importjava.applet.Applet;
importcom.sun.j3d.utils.applet.MainFrame;
publicclassCanvasDemoextendsApplet{
publicCanvasDemo(){
setLayout(newBorderLayout());
GraphicsConfigurationconfig=
SimpleUniverse.getPreferredConfiguration();
Canvas3Dcanvas=newCanvas3D(config);
add("North",newLabel("Thisisthetop"));
add("Center",canvas);
add("South",newLabel("Thisisthebottom"));
BranchGroupcontents=newBranchGroup();
contents.addChild(newColorCube(0.3));
SimpleUniverseuniverse=newSimpleUniverse(canvas);
universe.getViewingPlatform().setNominalViewingTransform();
universe.addBranchGraph(contents);
}
publicstaticvoidmain(String[]args){
CanvasDemodemo=newCanvasDemo();
newMainFrame(demo,400,400);
}
}
The Canvas3D takes advantage of your computers graphics card to increase performance. Unfortunately,
this means that it does not mix very well with Suns swing user interface components. These components
are called lightweight Lightweight components can be hidden by a Canvas3D even if they are supposed
to be at the front.
There are several solutions to this problem:
You can mix lightweight and heavyweight components on the same screen if you keep them in separate
containers.
If you use Popup menus, a static function on JPopupMenu fixes the problem:
setDefaultLightWeightPopupEnabled(false);
10
11
newBoundingSphere(newPoint3d(0.0,0.0,0.0),100.0);
Color3flight1Color=newColor3f(1.0f,0.0f,0.2f);
Vector3flight1Direction=newVector3f(4.0f,7.0f,12.0f);
DirectionalLightlight1
=newDirectionalLight(light1Color,light1Direction);
light1.setInfluencingBounds(bounds);
objRoot.addChild(light1);
//Setuptheambientlight
Color3fambientColor=newColor3f(1.0f,1.0f,1.0f);
AmbientLightambientLightNode=newAmbientLight(ambientColor);
ambientLightNode.setInfluencingBounds(bounds);
objRoot.addChild(ambientLightNode);
returnobjRoot;
}
publicBouncingBall(){
setLayout(newBorderLayout());
GraphicsConfigurationconfig=
SimpleUniverse.getPreferredConfiguration();
Canvas3Dc=newCanvas3D(config);
add("Center",c);
c.addKeyListener(this);
timer=newTimer(100,this);
//timer.start();
Panelp=newPanel();
p.add(go);
add("North",p);
go.addActionListener(this);
go.addKeyListener(this);
//Createasimplesceneandattachittothevirtualuniverse
BranchGroupscene=createSceneGraph();
SimpleUniverseu=newSimpleUniverse(c);
u.getViewingPlatform().setNominalViewingTransform();
u.addBranchGraph(scene);
}
publicvoidkeyPressed(KeyEvente){
//Invokedwhenakeyhasbeenpressed.
if(e.getKeyChar()=='s'){xloc=xloc+.1f;}
if(e.getKeyChar()=='a'){xloc=xloc.1f;}
}
publicvoidkeyReleased(KeyEvente){
//Invokedwhenakeyhasbeenreleased.
}
publicvoidkeyTyped(KeyEvente){
//Invokedwhenakeyhasbeentyped.
}
publicvoidactionPerformed(ActionEvente){
//starttimerwhenbuttonispressed
12
if(e.getSource()==go){
if(!timer.isRunning()){
timer.start();
}
}
else{
height+=.1*sign;
if(Math.abs(height*2)>=1)sign=1.0f*sign;
if(height<0.4f){
trans.setScale(newVector3d(1.0,.8,1.0));
}
else{
trans.setScale(newVector3d(1.0,1.0,1.0));
}
trans.setTranslation(newVector3f(xloc,height,0.0f));
objTrans.setTransform(trans);
}
}
publicstaticvoidmain(String[]args){
System.out.println("ProgramStarted");
BouncingBallbb=newBouncingBall();
bb.addKeyListener(bb);
MainFramemf=newMainFrame(bb,256,256);
}
}
13
Natural Selection
Once you have created a 3D scene you may want to interact with the objects
within it. A first step is to select an object with the mouse. The Java 3D
picking classes help you to do this, you can use them in the following way:
1.
CreateaPickCanvasfromtheCanvas3DandtheBranchGroupyouwanttopickfrom.
PickCanvaspickCanvas=newPickCanvas(canvas,group);
2.
SetthepickCanvastousetheboundsoftheobjectforpicking.
pickCanvas.setMode(PickCanvas.BOUNDS);
3.
HandlethemouseeventbyextendingMouseAdapterandlisteningformouseeventsonthe
Canvas3Dusingcanvas.addMouseListener(this).
4.
DefinethemouseClickedfunctionsothatthePickCanvascallsthePickClosest
functiontoreturnthePickResult.
5.
CallgetNodeonPickResulttofindoutmoreabouttheobjectthathasbeenpicked.
Hereisacompleteexamplethatdisplaystwoobjects(acubeandasphere).Whenyouclickthemouseit
printsouttheclassnameoftheselectedobject.
importcom.sun.j3d.utils.picking.*;
importcom.sun.j3d.utils.universe.SimpleUniverse;
importcom.sun.j3d.utils.geometry.*;
importjavax.media.j3d.*;
importjavax.vecmath.*;
importjava.awt.event.*;
importjava.awt.*;
publicclassPickextendsMouseAdapter{
privatePickCanvaspickCanvas;
publicPick()
{
Frameframe=newFrame("BoxandSphere");
GraphicsConfigurationconfig=
SimpleUniverse.getPreferredConfiguration();
Canvas3Dcanvas=newCanvas3D(config);
canvas.setSize(400,400);
SimpleUniverseuniverse=newSimpleUniverse(canvas);
BranchGroupgroup=newBranchGroup();
//createacolorcube
Vector3fvector=newVector3f(0.3f,0.0f,0.0f);
Transform3Dtransform=newTransform3D();
transform.setTranslation(vector);
TransformGrouptransformGroup=newTransformGroup(transform);
ColorCubecube=newColorCube(0.3);
transformGroup.addChild(cube);
group.addChild(transformGroup);
//createasphere
Vector3fvector2=newVector3f(+0.3f,0.0f,0.0f);
14
Transform3Dtransform2=newTransform3D();
transform2.setTranslation(vector2);
TransformGrouptransformGroup2=newTransformGroup(transform2);
Appearanceappearance=newAppearance();
appearance.setPolygonAttributes(
newPolygonAttributes(PolygonAttributes.POLYGON_LINE,
PolygonAttributes.CULL_BACK,0.0f));
Spheresphere=newSphere(0.3f,appearance);
transformGroup2.addChild(sphere);
group.addChild(transformGroup2);
universe.getViewingPlatform().setNominalViewingTransform();
universe.addBranchGraph(group);
frame.addWindowListener(newWindowAdapter(){
publicvoidwindowClosing(WindowEventwinEvent){
System.exit(0);
}
});
frame.add(canvas);
pickCanvas=newPickCanvas(canvas,group);
pickCanvas.setMode(PickCanvas.BOUNDS);
canvas.addMouseListener(this);
frame.pack();
frame.show();
}
publicstaticvoidmain(String[]args){
newPick();
}
publicvoidmouseClicked(MouseEvente)
{
pickCanvas.setShapeLocation(e);
PickResultresult=pickCanvas.pickClosest();
if(result==null){
System.out.println("Nothingpicked");
}else{
Primitivep=(Primitive)result.getNode(PickResult.PRIMITIVE);
Shape3Ds=(Shape3D)result.getNode(PickResult.SHAPE3D);
if(p!=null){
System.out.println(p.getClass().getName());
}elseif(s!=null){
System.out.println(s.getClass().getName());
}else{
System.out.println("null");
}
}
}
}//endofclassPick
If you need to be more accurate you need to use geometry picking. Change the line:
15
pickCanvas.setMode(PickCanvas.BOUNDS);
to
pickCanvas.setMode(PickCanvas.GEOMETRY);
Unfortunately, this is not the whole story. For geometry picking to work you need to set
various capabilities on the objects in your scene. These capabilities are set to off by
default so that applications that do not use advanced picking are not slowed down
unnecessarily. To turn them on, use:
node.setCapability(Node.ENABLE_PICK_REPORTING);
PickTool.setCapabilities(node,PickTool.INTERSECT_FULL);
If you set the capabilities on all the objects you have added to the scene, you may find
that detailed picking still does not work. This is because objects may be made up of other
objects. For example instead of setting them once for a cube, you may need to set the
capabilities six times, once for each face. The easiest way to make sure that all parts of
each object are pickable is to call the following function on BranchGroup of your scene:
publicvoidenablePicking(Nodenode){
node.setPickable(true);
node.setCapability(Node.ENABLE_PICK_REPORTING);
try{
Groupgroup=(Group)node;
for(Enumeratione=group.getAllChildren();
e.hasMoreElements();){
enablePicking((Node)e.nextElement());
}
}
catch(ClassCastExceptione){
//ifnotagroupnode,therearenochildrensoignore
exception
}
try{
Shape3Dshape=(Shape3D)node;
PickTool.setCapabilities(node,PickTool.INTERSECT_FULL);
for(Enumeratione=shape.getAllGeometries();
e.hasMoreElements();){
Geometryg=(Geometry)e.nextElement();
g.setCapability(g.ALLOW_INTERSECT);
}
}
catch(ClassCastExceptione){
//notaShape3Dnodeignoreexception
}
}
After all the capabilities are set you can use System.out.println on your pick result to find the full
range of information available. You can also use PickAllSorted instead of PickClosest to pick
more than one object at the same time.
16
Pick Shapes
So far the examples have picked objects using the default shape: an imaginary ray going from the viewer's
eye position through the mouse pointer into infinity. Instead of a thin ray you can change the shape used for
picking. For example, you might use a cylinder instead of a narrow ray to make picking points easier. A
cone shaped area is an easy way to pick anything that a viewer can see.
The setShape function on PickCanvas (and PickTool) can set the shape to a Cone, Cylinder, Point
or Ray. Each of these shapes can either go on forever (a ray) or stop after a fixed distance (a segment), you
can also pick based on a Bounds object.
As well as setting the shape you can set the tolerance for the picking operation. This is the distance (in
pixels) that your PickShape can miss the target object by and still select the object.
Advanced Picking
Picking can be used for more than just mouse selection. The pick shapes can be located anywhere and can
be moved around within your scene. They can be used for collision detection or avoidance. For example,
you could use a pick shape in front of a person to detect potential obstacles and stop them from bumping
into things. If you were writing a pool game you could use a PickRaySegment to represent the pool cue and
you could pick with BoundingSphere to detecting collisions between the balls.
17
18
/**
* Returns the point where a line crosses a plane
*/
Point3d getIntersection(Point3d line1, Point3d line2,
Point3d plane1, Point3d plane2, Point3d plane3) {
Vector3d p1 = new Vector3d(plane1);
Vector3d p2 = new Vector3d(plane2);
Vector3d p3 = new Vector3d(plane3);
Vector3d p2minusp1 = new Vector3d(p2);
p2minusp1.sub(p1);
Vector3d p3minusp1 = new Vector3d(p3);
p3minusp1.sub(p1);
Vector3d normal = new Vector3d();
normal.cross(p2minusp1, p3minusp1);
// The plane can be defined by p1, n + d = 0
double d = -p1.dot(normal);
Vector3d i1 = new Vector3d(line1);
Vector3d direction = new Vector3d(line1);
direction.sub(line2);
double dot = direction.dot(normal);
if (dot == 0) return null;
double t = (-d - i1.dot(normal)) / (dot);
Vector3d intersection = new Vector3d(line1);
Vector3d scaledDirection = new Vector3d(direction);
scaledDirection.scale(t);
intersection.add(scaledDirection);
Point3d intersectionPoint = new Point3d(intersection);
return intersectionPoint;
}
Now we know where the intersection point is, it is easy to draw on an image that we can use as a texture on
the cube. Remember that the y coordinate for the mouse gets higher as you move the mouse pointer down
the screen. This has to be reversed to get to the y coordinate you are used to seeing on a graph.
public void mouseDragged(MouseEvent event) {
Point3d intersectionPoint = getPosition(event);
if (Math.abs(intersectionPoint.x) < 0.5 &&
Math.abs(intersectionPoint.y) < 0.5) {
double x = (0.5 + intersectionPoint.x) * imageWidth;
double y = (0.5 - intersectionPoint.y) * imageHeight;
Graphics2D g = (Graphics2D) frontImage.getGraphics();
g.setColor( Color.BLACK);
g.setStroke(new BasicStroke(3));
int iX = (int)(x + .5);
int iY = (int)(y + .5);
if (lastX < 0) {
lastX = iX;
lastY = iY;
}
g.drawLine(lastX, lastY, iX, iY);
lastX = iX;
lastY = iY;
changeTexture(texture, frontImage, frontShape);
}
19
Of course, the method Ive explained so far assumes that the cube always stays in the same place. In a real
application you are likely to have objects in different places and different orientations. Fortunately, it is
easy to allow for this by transforming the points of the plane from the local to virtual world coordinates and
then transforming the intersection point back to local coordinates at the end.
Transform3D currentTransform = new Transform3D();
box.getLocalToVworld(currentTransform);
currentTransform.transform(p1);
currentTransform.transform(p2);
currentTransform.transform(p3);
Point3d intersection = getIntersection(eyePos, mousePos, p1, p2, p3);
currentTransform.invert();
currentTransform.transform(intersection);
Heres an example that puts all these techniques together. It shows a cube with different colored faces. You
can rotate the cube with the left mouse button, and draw on the front (blue) face with the right mouse
button.
import
import
import
import
import
import
import
import
import
import
import
import
import
java.applet.Applet;
java.awt.*;
java.awt.event.*;
java.awt.image.BufferedImage;
javax.media.j3d.*;
javax.vecmath.*;
com.sun.j3d.utils.applet.MainFrame;
com.sun.j3d.utils.behaviors.mouse.MouseRotate;
com.sun.j3d.utils.geometry.*;
com.sun.j3d.utils.image.TextureLoader;
com.sun.j3d.utils.pickfast.PickCanvas;
com.sun.j3d.utils.universe.SimpleUniverse;
com.sun.j3d.utils.universe.ViewingPlatform;
20
21
box.getShape(Box.TOP).setAppearance(getAppearance(Color.magenta));
box.getShape(Box.BOTTOM).setAppearance(
getAppearance(Color.orange)); ;
box.getShape(Box.RIGHT).setAppearance(
getAppearance(Color.red));
box.getShape(Box.LEFT).setAppearance(
getAppearance(Color.green));
box.getShape(Box.BACK).setAppearance(
getAppearance(Color.yellow));
frontImage = new BufferedImage(imageWidth, imageHeight,
BufferedImage.TYPE_INT_RGB);
22
Graphics2D g = (Graphics2D)frontImage.getGraphics();
g.setColor(new Color(70,70,140));
g.fillRect(0, 0, imageWidth, imageHeight);
addTexture(frontImage, frontShape);
MouseRotate behavior = new MouseRotate();
BoundingSphere bounds =
new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);
boxTransformGroup = new TransformGroup();
boxTransformGroup
.setCapability(TransformGroup.ALLOW_TRANSFORM_RE
AD);
boxTransformGroup
.setCapability(TransformGroup.ALLOW_TRANSFORM_WR
ITE);
behavior.setTransformGroup(boxTransformGroup);
boxTransformGroup.addChild(behavior);
behavior.setSchedulingBounds(bounds);
boxTransformGroup.addChild(box);
group.addChild(boxTransformGroup);
}
public void addTexture(BufferedImage image, Shape3D shape) {
frontShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
appearance = shape.getAppearance();
appearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE);
appearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
appearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
changeTexture( texture, image, shape);
Color3f col = new Color3f(0.0f, 0.0f, 1.0f);
ColoringAttributes ca = new ColoringAttributes(col,
ColoringAttributes.NICEST);
appearance.setColoringAttributes(ca);
}
public void changeTexture(Texture texture, BufferedImage image,
Shape3D shape) {
loader = new TextureLoader(image, "RGB",
TextureLoader.ALLOW_NON_POWER_OF_TWO);
texture = loader.getTexture();
texture.setBoundaryModeS(Texture.CLAMP_TO_BOUNDARY);
texture.setBoundaryModeT(Texture.CLAMP_TO_BOUNDARY);
texture.setBoundaryColor(new Color4f(0.0f, 1.0f, 0.5f, 0f));
// Set up the texture attributes
// could be REPLACE, BLEND or DECAL instead of MODULATE
1.0f));
23
24
0.0f));
70f);
}
@Override
public void mouseClicked(MouseEvent arg0) {
}
@Override
public void mouseEntered(MouseEvent arg0) {
}
@Override
public void mouseExited(MouseEvent arg0) {
}
@Override
public void mousePressed(MouseEvent event) {
lastX=-1;
lastY=-1;
mouseButton = event.getButton();
}
@Override
public void mouseReleased(MouseEvent arg0) {
}
@Override
public void mouseDragged(MouseEvent event) {
if (mouseButton==MouseEvent.BUTTON1) return;
Point3d intersectionPoint = getPosition(event);
if (Math.abs(intersectionPoint.x) < 0.5 &&
Math.abs(intersectionPoint.y) < 0.5) {
double x = (0.5 + intersectionPoint.x) * imageWidth;
double y = (0.5 - intersectionPoint.y) * imageHeight;
Graphics2D g = (Graphics2D) frontImage.getGraphics();
g.setColor( Color.BLACK);
g.setStroke(new BasicStroke(3));
int iX = (int)(x + .5);
int iY = (int)(y + .5);
if (lastX < 0) {
lastX = iX;
lastY = iY;
}
g.drawLine(lastX, lastY, iX, iY);
lastX = iX;
lastY = iY;
changeTexture(texture, frontImage, frontShape);
25
@Override
public void mouseMoved(MouseEvent arg0) {
}
}
26
Further Information
I hope this tutorial has got you interested in programming with Java 3D. You should now know enough to
program simple scenes, or games like the Pyramid game at http://www.fungames.org.
There are several sources of information to help you learn more.
Online Information
Java 3D comes with several useful examples, these are described at:
http://www.java.sun.com/products/javamedia/3D/forDevelopers/J3D_1_2_API/j3dguide/AppendixExamples.html
My site is at:
http://www.java3d.org
Books
There the following books on Java 3D::
The Java 3D API Specification (With CD-ROM), by Henry Sowizral, Kevin Rushforth, Michael
Deering
27