![Free-eBooks.net](/resources/img/logo-nfe.png)
![All New Design](/resources/img/allnew.png)
Example code
C.1 Box objects and GeomBuffer
C.2 Primitives and the geometry cache
C.1 Box objects and GeomBuffer
Arguably there is a bug in the Box class because the faces of the cube are defined using two triangles. TriangleArrays are quicker to render than QuadArrays, but when the Box is rendered as a wireframe (i.e., only the edges of the Box are drawn), an extra diagonal line is drawn that separates the two triangles that define a face. This bug was not present in Java 3D 1.1 and was introduced in Java 3D 1.1.1. With luck, the bug will be rectified in subsequent releases.
If you require that your Box objects be rendered as wireframes, the following class can be used instead of Box to ensure the faces are rendered correctly. The Box class must be simply modified to create an OldGeomBuffer object instead of a GeomBuffer.
From CuboidTest\Cuboid.java
/*
* Based on Sun's Box.java 1.13 98/11/23 10:23:02
* Work around for the Box bug when rendered in Wireframe mode.
* override this method
*/
public Cuboid( float xdim, float ydim, float zdim, int primflags, Appearance ap)
{
int i;
double sign;
xDim = xdim;
yDim = ydim;
zDim = zdim;
flags = primflags;
//Depends on whether normal inward bit is set.
if ((flags GENERATE_NORMALS_INWARD) != 0)
sign = -1.0;
else
sign = 1.0;
TransformGroup objTrans = new TransformGroup();
objTrans.setCapability(ALLOW_CHILDREN_READ);
this.addChild(objTrans);
Shape3D shape[] = new Shape3D[6];
for (i = FRONT; i <= BOTTOM; i++)
{
OldGeomBuffer gbuf = new OldGeomBuffer(4);
gbuf.begin(OldGeomBuffer.QUAD_STRIP);
for (int j = 0; j < 2; j++)
{
gbuf.normal3d( (double) normals[i].x*sign,
(double) normals[i].y*sign,
(double) normals[i].z*sign);
gbuf.texCoord2d(tcoords[i*8 + j*2], tcoords[i*8 + j*2 + 1]);
gbuf.vertex3d( (double) verts[i*12 + j*3]*xdim,
(double) verts[i*12+ j*3 + 1]*ydim,
(double) verts[i*12+ j*3 + 2]*zdim );
}
for (int j = 3; j > 1; j--)
{
gbuf.normal3d( (double) normals[i].x*sign,
(double) normals[i].y*sign,
(double) normals[i].z*sign);
gbuf.texCoord2d(tcoords[i*8 + j*2], tcoords[i*8 + j*2 + 1]);
gbuf.vertex3d( (double) verts[i*12 + j*3]*xdim,
(double) verts[i*12+ j*3 + 1]*ydim,
(double) verts[i*12+ j*3 + 2]*zdim );
}
gbuf.end();
shape[i] = new Shape3D(gbuf.getGeom(flags));
numVerts = gbuf.getNumVerts();
numTris = gbuf.getNumTris();
if ((flags ENABLE_APPEARANCE_MODIFY) != 0)
{
(shape[i]).setCapability(Shape3D.ALLOW_APPEARANCE_READ);
(shape[i]).setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
}
objTrans.addChild(shape[i]);
}
if (ap == null)
{
setAppearance();
}
else
setAppearance(ap);
}
_________________________________________________________________
GeometryBuffer must also be simply modified (in fact, the original 1.1 version can be used), to create a QuadArray inside processQuadStrips—newer versions create a TriangleStripArray. Copy the GeomBuffer file (defined in the com.sun.j3d.utils.geometry package, for which there is source code). Save the file as OldGeomBuffer and replace the processQuadStrips method from GeomBuffer with the method which follows.
From CuboidTest\OldGeomBuffer.java
/*
* OldGeometryBuffer.java - based on Sun's GeomBuffer.java.
* Work around for the Box bug when rendered in Wireframe mode.
* This version actually returns Quadstrips for a Quadstrip
array,
* unlike the newer version that returns TriangleStrips....
* override this method
*/
private GeometryArray processQuadStrips()
{
GeometryArray obj = null;
int i;
int totalVerts = 0;
for (i = 0; i < currPrimCnt; i++)
{
int numQuadStripVerts;
numQuadStripVerts = currPrimEndVertex[I]
- currPrimStartVertex[i];
totalVerts += (numQuadStripVerts/2 - 1) * 4;
}
if (debug >= 1) System.out.println
("totalVerts " + totalVerts);
if (((flags GENERATE_NORMALS) != 0) ((flags
GENERATE_TEXTURE_COORDS) != 0))
{
obj = new QuadArray(totalVerts,
QuadArray.COORDINATES | QuadArray.NORMALS |
QuadArray.TEXTURE_COORDINATE_2);
}
else if (((flags GENERATE_NORMALS) == 0) ((flags
GENERATE_TEXTURE_COORDS) != 0))
{
obj = new QuadArray(totalVerts,
QuadArray.COORDINATES |
QuadArray.TEXTURE_COORDINATE_2);
}
else if (((flags GENERATE_NORMALS) != 0) ((flags
GENERATE_TEXTURE_COORDS) == 0))
{
obj = new QuadArray(totalVerts,
QuadArray.COORDINATES |
QuadArray.NORMALS);
}
else
{
obj = new QuadArray(totalVerts,
QuadArray.COORDINATES);
}
Point3f[] newpts = new Point3f[totalVerts];
Vector3f[] newnormals = new Vector3f[totalVerts];
Point2f[] newtcoords = new Point2f[totalVerts];
int currVert = 0;
for (i = 0; i < currPrimCnt; i++)
{
for (int j = currPrimStartVertex[i] + 2;
j < currPrimEndVertex[i];j+=2)
{
outVertex(newpts, newnormals, newtcoords, currVert++,
pts, normals, tcoords, j - 2);
outVertex(newpts, newnormals, newtcoords, currVert++,
pts, normals, tcoords, j - 1);
outVertex(newpts, newnormals, newtcoords, currVert++,
pts, normals, tcoords, j + 1);
outVertex(newpts, newnormals, newtcoords, currVert++,
pts, normals, tcoords, j);
numTris += 2;
}
}
numVerts = currVert;
obj.setCoordinates(0, newpts);
if ((flags GENERATE_NORMALS) != 0)
obj.setNormals(0, newnormals);
if ((flags GENERATE_TEXTURE_COORDS) != 0)
obj.setTextureCoordinates(0, newtcoords);
geometry = obj;
return obj;
}
_________________________________________________________________
C.2 Primitives and the geometry cache
A feature of the Primitive-derived classes is that they support the geometry cache (or some of them do). The geometry cache is intended to save CPU time when building Primitive-derived objects by caching GeomBuffer objects and returning them as appropriate. For example, if your application requires 100 Spheres with radius 50, the geometry cache will create the geometry for the first sphere and return this geometry for the remaining 99. Mysteriously, only the Cone, Cylinder, and Sphere Primitives use the geometry cache.
The source code to implement the geometry cache is useful because it presents an object lesson in how not to design such a facility. The geometry cache is implemented using a static hashtable of String keys that are used to retrieve an Object instance (in this case, GeomBuffer). The Strings that are used as keys are built from four int and three float parameters. Problems with this crude, inefficient, and simplistic design are:
From Primitive.java
//The data structure used to cache GeomBuffer objects
static Hashtable geomCache = new Hashtable();
String strfloat(float x)
{
return (new Float(x)).toString();
}
// Add a GeomBuffer to the cache
protected void cacheGeometry( int kind, float a, float b,
float c, int d, int e, int flags,
GeomBuffer geo)
{
String key = new String(kind+strfloat(a)+strfloat(b)+
strfloat(c)+d+e+flags);
geomCache.put(key, geo);
}
// Retrieve a GeomBuffer object
protected GeomBuffer getCachedGeometry( int kind, float a,
float b, float c, int d,
int e, int flags)
{
String key = new String(kind+strfloat(a)+strfloat(b)+
strfloat(c) +d+e+flags);
Object cache = geomCache.get(key);
return((GeomBuffer) cache);
}
_________________________________________________________________
From Cylinder.java
//The Geometry Cache in use
GeomBuffer cache =
getCachedGeometry( Primitive.CYLINDER, radius, radius height,
xdivision, ydivision, primflags);
if (cache != null)
{
shape[BODY] = new Shape3D(cache.getComputedGeometry());
numVerts += cache.getNumVerts();
numTris += cache.getNumTris();
}
_________________________________________________________________
Java 3D programmers coming from an OpenGL background will recognize much of the code used to define the vertices and normal vectors of the Box primitive, defined in com.sun.j3d.utils.geometry.Box.
GeomBuffer gbuf = new GeomBuffer(4);
//extract of code to generate the geometry of a Box
gbuf.begin(GeomBuffer.QUAD_STRIP);
gbuf.normal3d( (double) normals[i].x*sign, (double)
normals[i].y*sign, (double) normals[i].z*sign);
gbuf.texCoord2d(tcoords[i*8 + j*2], tcoords[i*8 + j*2 + 1]);
gbuf.vertex3d( (double) verts[i*12 + j*3]*xdim, (double)
verts[i*12+ j*3 + 1]*ydim,
(double) verts[i*12+ j*3 + 2]*zdim );
gbuf.end();
//create a Shape3D object to wrap the GeomBuffer
Shape3D shape =
new Shape3D( gbuf.getGeom( GeomBuffer.GENERATE_NORMALS ) );
The GeomBuffer class has been designed to allow OpenGL programmers to quickly and easily generate Java 3D geometry in a manner similar to defining an OpenGL display list (for example). In the preceding example a GeomBuffer is created to hold four vertices defined as a quad strip which draws a connected group of quadrilaterals. One quadrilateral is defined for each pair of vertices presented after the first pair. Vertices 2n - 1, 2n, 2n + 2, and 2n + 1 define quadrilateral n, and n quadrilaterals are drawn.
The GeomBuffer class is used in many of the classes derived from Primitive since, I suspect, this code has been ported from an OpenGL-based implementation and the GeomBuffer was created to simplify porting.
int QUAD_STRIP = 0x01;
int TRIANGLES = 0x02;
int QUADS = 0x04;
At present, an instance of a GeomBuffer can contain only a single primitive type; that is, one cannot mix quad strips and Triangles (for example) in a single GeomBuffer.
Except for a bug that causes the GeomBuffer to generate a TriangleStripArray for a QUAD_STRIP instead of a QuadStripArray, the class is easy to use and allows OpenGL code to be quickly inserted into a Java 3D application.