Adjusting a Model's Shape AttributesAfter reporting on the model's scene graph, WrapLoaderInfo3D's last task is to modify the model's appearance according to the user's supplied adaptation number. Many aspects of a model can be easily changed once its individual Shape3D nodes are accessible. This Figure 16-9. The dolphins modeldDcan be done with a variant of the examineNode( ) pseudocode, concentrating only on Leaf nodes that are Shape3Ds: visitNode(node) { if the node is a Group { for each child of the node visitNode(child); // recursive call } else if the node is a Shape3D adjust the node's attributes; } This pseudocode is the basis of visitNode( ) in WrapLoaderInfo3D. The manipulation of the shape's attributes is initiated in adjustShape3D( ), which uses the adaptation number entered by the user to choose between six possibilities: 0 Makes the shape blue with makeBlue( ) 1 Draws the shape in outline with drawOutline( ) 2 Renders the shape almost transparent with makeAlmostTransparent( ) 3 Lays a texture over the shape with addTexture( ) 4 Makes the shape blue and adds a texture by calling makeBlue( ) and addTexture( )
Turning the Shape BlueFigure 16-10 shows the rendering of the dolphins model after being turned blue. Figure 16-10. Blue dolphinsThe Material node used in makeBlue( ) is: Material blueMat = new Material(black, black, blue, white, 20.0f); The use of black as the ambient color (Color3f(0.0f, 0.0f, 0.0f)) means unlit parts of the shape are rendered in black, which looks like shadow on the model. However, the model doesn't cast shadows onto other surfaces, such as the floor: private void makeBlue(Shape3D shape) { Appearance app = shape.getAppearance( ); Material blueMat = new Material(black, black, blue, white, 20.0f); blueMat.setLightingEnable(true); app.setMaterial(blueMat); shape.setAppearance(app); } The appearance is obtained from the shape, its material attribute changed, and then the appearance component assigned back to the shape; only the attribute of interest is modified. Drawing a Shape in OutlineFigure 16-11 shows a VRML model of a box, cone, and sphere rendered in outline: Figure 16-11. Shapes in outline
The effect is achieved by setting the POLYGON_LINE mode in PolygonAttribute in drawOutline( ): private void drawOutline(Shape3D shape) { Appearance app = shape.getAppearance( ); PolygonAttributes pa = new PolygonAttributes( ); pa.setCullFace( PolygonAttributes.CULL_NONE ); pa.setPolygonMode( PolygonAttributes.POLYGON_LINE ); app.setPolygonAttributes( pa ); shape.setAppearance(app); }
Making a Shape Almost TransparentFigure 16-12 shows a model of the gun rendered almost transparent. This is done in makeAlmostTransparent( ) by setting the transparencyAttributes of the shape's Appearance: private void makeAlmostTransparent(Shape3D shape) { Appearance app = shape.getAppearance( ); Figure 16-12. Semi-transparent gunTransparencyAttributes ta = new TransparencyAttributes( ); ta.setTransparencyMode( TransparencyAttributes.BLENDED ); ta.setTransparency(0.8f); // 1.0f is totally transparent app.setTransparencyAttributes( ta ); shape.setAppearance(app); } Various transparency mode settings affect how the original color of the shape is mixed with the background pixels. For example, blended transparency (used here) mixes the color of the transparent shape with the color of the background pixels. Screen door transparency (transparencyAttributes.SCREEN_DOOR) renders a mesh-like pattern of pixels with the color of the transparent shape, leaving gaps where the background pixels show through. More details can be found in the documentation for the transparencyAttributes class. A comparison of the last three examples shows the general strategy for manipulating a shape: create an attribute setting, and add it to the existing Appearance component of the shape. Adding a Texture to a ShapeFigure 16-13 shows a castle with a rock texture wrapped over it. A quick look at Figure 16-13 reveals some of the texturing is unrealistic: clear stripes of textures run down the walls. Once you understand how the texture is mapped, the reasons for this striping will be clear. Figure 16-13. Castle rockA texture is made in two stages. First, a TextureLoader object is created for the file holding the texture image, then the texture is extracted from the object: private Texture2D texture = null; // global private void loadTexture(String fn) { TextureLoader texLoader = new TextureLoader(fn, null); texture = (Texture2D) texLoader.getTexture( ); if (texture == null) System.out.println("Cannot load texture from " + fn); else { System.out.println("Loaded texture from " + fn); texture.setEnable(true); } } The call to setEnable( ) switches on texture mapping, which allows the texture to be wrapped around a shape. TextureLoader can handle JPEGs and GIFs (which are useful if transparency is required), and it can be employed in conjunction with Java Advanced Imaging (JAI) to load other formats, such as BMP, PNG, and TIFF files. The loader can include various flags, such as one for creating textures at various levels of resolution for rendering onto small areas. Aside from Textures, the loader can return ImageComponent2D objects, the Java 3D format for images used in backgrounds and rasters. Textures can be 2D (as shown here) or 3D: Texture3D objects are employed for volumetric textures, typically in scientific applications and visualization. The if test in loadTexture( ) checks if the texture was successfully created. A common reason for the texture being null is that the source image's dimensions are invalid. The image must be square, with its dimensions a power of two. Keeping this in mind, I made the rock image's size 256 x 256 pixels. For a texture to be applied to a shape, three conditions must be met:
Texture coordinatesTexture2D coordinates are measured with (s, t) values that range between 0 and 1. Texture mapping is the art of mapping (s, t) values (sometimes called texels) onto geometry coordinates (x, y, z) to create a realistic looking effect. One mapping approach is to tile the texture in one-by-one patches over the geometry's surface. However, tiling may create excessive repetition of the pattern, and after the geometry has been scaled down for the Java 3D world, the texture's details may be too small to see. A more flexible mapping approach is to utilize a TexCoordGeneration object, which lets the programmer define equations stating how geometry coordinates (x, y, z) values are converted into texels (s, t). The simplest equations are linear, of these forms:
planeS and planeT are vectors that contain the xc, yc, zc, and w constants, which define the equations. Specifying these equations can be tricky, so I'll use a simple technique based on the bounding box for the shape. Figure 16-14 shows a shape's bounding box, with its upper and lower points highlighted. The upper point contains the maximum x, y, and z values, and the lower point has the minima. Texture mapping becomes a matter of mapping the (x, y, z) coordinates of the box to the (s, t) coordinates of the texture. The height and width of the bounding box is easily calculated:
Two simple equations for s and t are then given:
This is expressed in vector form:
Figure 16-14. From bounding box to texture
This bounding box algorithm is implemented in stampTexCoords( ): private TexCoordGeneration stampTexCoords(Shape3D shape) { BoundingBox boundBox = new BoundingBox( shape.getBounds( ) ); Point3d lower = new Point3d( ); Point3d upper = new Point3d( ); boundBox.getLower(lower); boundBox.getUpper(upper); double width = upper.x - lower.x; double height = upper.y - lower.y; Vector4f planeS = new Vector4f( (float)(1.0/width), 0.0f, 0.0f, (float)(-lower.x/width)); Vector4f planeT = new Vector4f( 0.0f, (float)(1.0/height), 0.0f, (float)(-lower.y/height)); // generate texture coordinates TexCoordGeneration texGen = new TexCoordGeneration( ); texGen.setPlaneS(planeS); texGen.setPlaneT(planeT); return texGen; } // end of stampTexCoords( ) The (s, t) equations are encoded as two Java 3D Vector4f objects: planeS and planeT. They're used to initialize a TexCoordGeneration object, which becomes the return result of the method. A tendency to stripFigure 16-13 shows stripes of textures running down the castle walls. However, this orientation is due to the model being rotated 90 degrees clockwise around the x-axis. In fact, the stripes are running along the z-axis of the model. This z-striping is the visible consequence of not using the z-coordinate in the (s, t) equations. It means that (x, y, z) coordinates with the same (x, y) value but different z-values will all map to the same (s, t) texel. Applying the texture to the shapeThe texture is applied to the shape by the addTextureGA( ) method, which has four main duties:
Here is the code: private void addTextureGA(Shape3D shape) { Appearance app = shape.getAppearance( ); // make shape two-sided, so texture appears on both sides PolygonAttributes pa = new PolygonAttributes( ); pa.setCullFace( PolygonAttributes.CULL_NONE ); app.setPolygonAttributes( pa ); // generate texture coords app.setTexCoordGeneration( stampTexCoords(shape) ); // modulate texture with color and lighting of underlying surface TextureAttributes ta = new TextureAttributes( ); ta.setTextureMode( TextureAttributes.MODULATE ); app.setTextureAttributes( ta ); // apply texture to shape if (texture != null) { // loaded at start, from adjustShapes( ) app.setTexture(texture); shape.setAppearance(app); } } // end of addTextureGA( ) The modulation task utilizes a Java 3D TextureAttributes object to control how the texture is combined with the surface colors of the shape. There are four texture modes:
The MODULATE mode is often used to combine an underlying Material with a texture, which allows lighting and shading effects to be seen alongside the texture. Figure 16-15 shows the dolphins models turned blue and with a texture. Figure 16-15. Textured, blue dolphins
|