Areas
  • J3D FAQ
  • Tutorials
  • Utilities
  • Books
  • News
  • Other Sites
  •  Chapters
  • TOC
  • Getting Started
  • Lighting
  • Textures
  • Behaviours
  • User Feedback
  • Navigation
  • Audio
  • Input Devices
  • No Monitors
  •  Sections
  • Chapter TOC
  • Example Code
  • Geometry Types
  • The Primitives
  • Rasters
  • 3D Text
  • Compressed
  •   

    Geometry Array Backgrounder

    © Justin Couch 1999-2000

    The majority of the time that you are creating a 3D application, you will be concerned with the use of the GeometryArray class and its derivatives. The reason for this is that this is the only class that can present information about polygons to the internal rendering engine. The other classes derived from the Geometry base class cannot do this.

    At first, and even second glance, GeometryArray documentation is confusing to say the least. To start with, the constructor has all these different numbers that need to be passed in as parameters - yet the documentation seems to suggest that you are specifying the same value twice. Although you will probably never directly instantiate an instance of this class, the parameters and definitions effect every single derived class. This introduction should smooth many of those problems out before you get started.

      

    Defining Geometry

    As previously described, the GeometryArray class forms the basis of all geometry. What it describes is:
    • A collection of points in 3D space.
    • A colour value for each of these points
    • A normal for each of these points
    Note that at this stage, there is nothing there that describes relationships between points or any other surface information. Each vertex (point) that has been added to the class is standalone in value. If you were to render this, you would end up with a series of coloured points, one pixel in size, on your screen. There is nothing to say that this should be a solid surface or even a collection of lines - that comes from the higher level derived classes.

    So why do we start with just a collection of points? When you boil down geometry, all you really have are a collection of points in 3D space that are interconnected. Even with most modern video cards, everything is rendered as a series of flat surfaces defined by verticies - even complex curves of objects like cars or aircraft.

       Note
      Tom Barbelett from the Nervana Project has apparently come up with a non-polygonal based method of representing surfaces within a rendering engine (not to be confused with spline based representations of surfaces like NURBS and B-Splines). Supposedly this is faster than traditional systems by a considerable amount. Unfortunately, nothing is really available in concrete code or demos at this point in time to prove or disprove it.
    After you have defined a 3D point in space, the other information becomes useful. For each vertex you can give it a separate colour value as well as a normal - or direction that it is pointing. While defining a normal for a single point is not particularly useful, once you start connecting vertices together into surfaces, then they become useful. Because they are common to almost every type of geometry representation, it makes sense to define those values here in the base class rather than replicating the same piece of code through numerous derived classes. Those that don't need the information (e.g. PointArrays) can safely ignore it without adding any extra overhead. We'll explain more what each of these extra pieces do later on in the chapter.

    Geometry Internals

    As you pass through this chapter, you will notice that every class has a constructor that requires arguments - there is no capability for creating default instances of that class. There's a very good reason for this.

    The underlying rendering engine is written in native code - simply to keep the speed as high as possible. When dealing with Java code, a collection of corresponding native data structures need to be created. If you have done any serious programming, in any language, you will be aware that memory allocation is an extremely expensive operation. For the sake of performance, you cannot just go around and create new arrays every time that a piece of geometry is about to be rendered (which could be 50 or more times a second with the latest consumer cards). Instead, a set of fixed size arrays are created when you first create the object. To do this accurately, the information must be passed in through the constructor - hence the requirement for arguments every single time. This leads to the rather cumbersome burden of having to know exactly how many vertices each piece of geometry is going to have before you even create it. This is especially annoying when you want to create custom derived geometry.

    Apart from speed reasons, internal native code is used to represent all geometry. The underlying rendering API requires data structures to be passed through. The traditional units of these are triangles that define surfaces. Once we know the size of the data, updates through the Java side API calls then just assign values into this array - there does not need to be any array bounds checking/resizing etc as we update the geometry.

    Version 1.2 of J3D has introduced the ability to take user referenced data rather than making its own internal copy. Just as statically creating internal arrays saves on memory allocation, this capability will contribute more. It is often the case that a file loader will be creating a new array instance for each piece of geometry information created, so there is no need to create yet further instances (and the instances created by the loading process are usually quickly discarded anyway shortly after use).

      

    GeometryArray Constructor

    There are two constructor arguments: vertexCount and vertextFormat. As you will see through the chapter, these will always be required.

    The first argument is relatively simple: It is the number of vertices in this piece of geometry. That is, the total number of points in 3D space that will be represented in one object. Depending on the type of geometry you are creating, this may need to be a multiple of 2,3 or 4, but as a minimum, it must be a non-zero, positive number. At such a low level object like this, it doesn't matter how many points you have. Later, when you come to set values for each vertex, there cannot be any more values than this number.

    The vertex format is a little more complex. This describes the type of data that will be supplied with this piece of geometry. It states whether there is vertex information, colour information and normals availble. It also defines the format that information comes in. Let's have a look at each value:

    • COORDINATES Vertex values will be supplied. This is not an optional value so it should always be included and the constructor will barf if it isn't.
    • NORMALS Normals for each vertex are supplied. It also implies that three is one normal per vertex, rather than one per face that might be implied by higher level primitives.
    • COLOR_3 Colour information that has three components (RGB) is supplied. Like normals, this indicates that there is one colour per vertex supplied.
    • COLOR_4 Colour information that has four components (ARGB) is supplied. Follows the same rules as 3 component colour.
    • TEXTURE_COORDINATE_2 2D texture information is supplied. Texture coordinates in a 2D space (flat images) can be used. Per vertex information is implied as for colour and normals.
    • TEXTURE_COORDINATE_3 3D texture information is supplied. Texture coordinates are taken from a 3D volume of images (this is how volume rendering is done in J3D). Per vertex information is implied.
    • BY_REFERENCE Data passed in will be used as the reference array rather than information being copied to internal structures.
    • INTERLEAVED Indicates that data that is supplied is interleaved into a single array rather than using multiple arrays. This should only be set is BY_REFERENCE is also set.
    All of these values are bitwise OR'd together to form a single value that is passed to the constructor. Once this has been set, it cannot change. For example, if you don't elect to use texture information, any texture coordinates that you supply will be ignored.

    For example, say you wanted to create an single square that had standard colour information and 2D texturing, the constructor call would be like this:

      int format = GeometryArray.COORDINATE |
                   GeometryArray.COLOR_3 |
                   GeometryArray.TEXTURE_COORDINATE_2;
    
      Geometry obj = new GeometryArray(4, format);
    
    An alternative constructor is supplied that allows you to specify extra texture mapping information. This provides coordination between multiple texture maps for a single object (typically called Multi-texturing) and sets of texture coordinates. This is a fairly complex subject, so it will be covered in a separate section in Chapter 4, Textures.

    Variety is the Spice of Life

    No, this is not some obscure reference to an all girl pommie pop group! Having constructed the basic object, you now need to fill it with useful data. A quick scroll through the javadoc and you'll see how bewildering the options are. For every piece of information that you could provide, there's at least six different methods that you could call.

    Much of the following information will be modified in some form by the derived classes, but this should give you a few ideas about where to start.

    Every type of information may be supplied either as single values with a corresponding vertex index or everything may be set at once. In variations of this, each type may be supplied in a format that is "native" to it's data type (more on this shortly).

    When bulk setting values, you don't need to supply every single value for vertexCount vertices. These methods provide two parameters start and length that describe the first vertex index to set and then the number of vertices to set from the passed in array.

       Note
      Vertex indices start at zero. Therefore, to set the four corners of a square, you would need to call:
        setCoordinate(0, {x1, y1, z1});
        setCoordinate(1, {x2, y2, z2});
        setCoordinate(2, {x3, y3, z3});
        setCoordinate(3, {x4, y4, z4});
      

    Coordinates

    Coordinate values may be supplied as either an array of floats or doubles. For setting an individual value, the array must contain at least 3 values. Setting a collection of values at once requires that the array is three times the length of the number of vertices being set (x,y and z coordinates for each vertex).

    If you are obtaining coordinate data from an external source, it might be easier to use the versions that take Point3f or Point3d values. The values are copied from this class into the internal array allowing you to reuse the one class instance multiple times to save on memory allocation/GC costs.

       Note
      Should you use floats or doubles to describe values? Common sense would say that for more accurate representations that you should use the highest precision representation - doubles. However, as an email from the J3D team states, everything is actually converted to floats. If you want the highest speed, then supply everything in floats and there will be no conversion costs for your application.

    Normals

    Setting normal values follows the same system as coordinates. You can specify values in a single array or using Vector3f instances. You can pass in values individually or a large array of values.

    Normal values are required to present a unit length vector. That is the length of all components must be equal to one. If you can't remember your Pythagoras Theorem this means:

      x^2 + y^2 + z^2 == 1.0
    
    (note that normally you take the square root of the left side, but because the right has a value of 1.0, there is no point as they will have the same value).

    Colour

    Colour values may be supplied in one of two forms:
    • one byte per component. The range of values are 0 (no contribution) - 255 (full intensity).
    • one float per component. The range of values are 0 (no contribution) - 1.0 (full intensity).
    Which version you use really depends on the source that you are getting information from. Some file formats, like VRML, use float ranges, while many UI components will return byte values (eg javax.swing.JColorChooser)

    Once you have decided on the format of the information, there are the typical numerous ways of providing it. There is the array form (either 3 or 4 values in length depending on what you chose in the constructor flay), Color3f or Color4f.

    Texture Coordinates

    Texture coordinates are provided in either array form or using TexCoord2f/TexCoord3f, again depending on what you have supplied in the construct flags. (J3D 1.1 and earlier use Point2f/Point3f to provide the same values).

    For texture coordinates there is an extra argument to supply. This is the texture coordinate set that you are applying the new value to. As it is possible to have more than one texture to an object, you may not want to have the same texture coordinates on every texture, so this allows you to selectively supply values for each image.

    Updating Values

    Geometry information follows the same rules as all other elements in the scene graph. In order to update values on a live or compiled piece of geometry, you need to make sure the appropriate capability bits have been set. How you update values in the geometry depends on whether you are copying the data internally or using user supplied references.

    Using Copies

    Changing values for internal copied data is a simple matter of calling the exact same methods that you used to set the initial values. Of course, you also need to make sure that the capability bit for that area is set. For example, to change colour values, you need to have ALLOW_COLOR_WRITE set.

    Using References

    If you have declared that geometry information is being supplied by reference, then this is a bit more work to be done. The first capability you must enable is Geometry.ALLOW_REF_DATA_WRITE. Next, you need to implement the GeometryUpdater interface in your code. The updater has a single method void updateData(Geometry).

    Making updates with referenced datasets is an asynchronous affair.

    1. Collect the values that you want to change and store them somewhere (usually in the class that implements the GeometryUpdater interface). Do not set these values in the target geometry object.
    2. Call the updateData() method passing in your GeometryUpdater instance.
    3. At some time later, when the rendering engine decides that now is a safe time to make updates, it will call the updateData() of your GeometryUpdater instance.
    4. Call the appropriate setter methods on the geometry object with the data that you stored in step 1.
    5. Exit the updateData() method and all is completed
    Note that this requires a fair amount of overhead in creating new classes and storing the information between when it arrives and the time it is needed for an update. This could be tricky if you are providing information from external devices that provide realtime data (eg HMD, 3D mouse etc).

      

    [ TOC ] [ Home ] [ FAQ ] [ Books ] [ Tutorials ] [ Utilities ] [ Contact Us ]