More Particle Systems


A Fountain of Quads

Though many particle systems can be modeled with points and lines, quadrilaterals (quads) combined with textures allow more interesting effects. The texture can contain extra surface detail and can be partially transparent to break up the regularity of the quad shape. A quad can be assigned a normal and a Material node component to allow it to be affected by lighting in the scene.

The only danger with these additional features is that they may decelerate rendering. The example here only utilizes a single Texture2D object and stores all the quads in a single QuadArray, thereby reducing the overheads of texture and shape creation considerably.

The effect I'm after with this example is suitably gory: a fountain of blood corpuscles gushing up from the origin. Each "blood" particle is roughly spherical and oscillates slightly as it travels through the air.

Figure 21-3 shows the QuadParticles system in action. If the user viewpoint is rotated around the fountain, the particles seem to be rounded on all sides (see Figure 21-6).

Figure 21-6. A fountain of blood from the back


It's a mistake to represent the particles using GeometryArray meshes; the number of vertices required for a reasonable blood cell would severely restrict the total number of particles that could be created. Instead the effect is achieved by trickery: the particle system is placed inside an OrientedShape3D node, rather than a Shape3D.

OrientedShape3D nodes automatically face toward the viewer and can be set to rotate about a particular point or axis. Since the particle system is rooted at the origin, it makes the most sense to rotate the system about the y-axis.

QuadParticles is made a subclass of OrientedShape3D, rather than Shape3D, and its constructor specifies the rotation axis:

     // rotate about the y-axis to follow the viewer     setAlignmentAxis( 0.0f, 1.0f, 0.0f); 

This means that the illusion of blood globules starts to breaks down if the viewpoint is looking down toward the XZ plane, as in Figure 21-7.

Figure 21-7. The fountain of blood from above


There is a drawback with the y-axis rotation setting in setAlignmentAxis( ): the entire particle system rotates as the viewpoint moves so each particle retains its position relative to the others. This is noticeable with the fountain of blood since each particle is large and moving relatively slowly. It's interesting to experiment with different alignment values, such as rotations around the x-or z-axes, or axis combinations.

Specifying the Geometry

The QuadArray requires information about coordinates, textures, and normals:

     // BY_REFERENCE QuadArray     quadParts = new QuadArray(numPoints,GeometryArray.COORDINATES |                      GeometryArray.TEXTURE_COORDINATE_2 |                      GeometryArray.NORMALS  |                                 GeometryArray.BY_REFERENCE );         // the referenced data can be read and written     quadParts.setCapability(GeometryArray.ALLOW_REF_DATA_READ);     quadParts.setCapability(GeometryArray.ALLOW_REF_DATA_WRITE); 

Using BY_REFERENCE means there must be float arrays for the coordinates, velocities, and accelerations (as before), and for normals and texture coordinates:

     private float[] cs, vels, accs, norms;     private float[] tcoords;         cs = new float[numPoints*3];   // to store each (x,y,z)     vels = new float[numPoints*3];     accs = new float[numPoints*3];     norms = new float[numPoints*3];     tcoords = new float[numPoints*2]; 

Each vertex in the QuadArray (there are numPoints of them) requires a texture coordinate (s, t), where s and t have float values in the range 0 to 1 (see Chapter 16 for the first use of Texture2D).

As of Java 3D 1.3, the use of a TexCoord2f array to store the texture coordinates of a BY_REFERENCE geometry is no longer encouraged; a float array should be employed. Instead of storing numPoints TexCoord2f objects, numPoints*2 floats are added to a tcoords[] array.


Initializing Particle Movement

A particle is a single quad, made up of four vertices (12 floats). The consequence is that much of the particle initialization and updating code utilizes loops which make steps of 12 through the float arrays. The creation/updating of one quad involves 12 floats at a time.

createGeometry( ) calls initQuadParticle( ) to initialize each quad:

     for(int i=0; i < numPoints*3; i=i+12)       initQuadParticle(i);     // refer to the coordinates in the QuadArray     quadParts.setCoordRefFloat(cs); 

initQuadParticles( ) has a similar style to the initialization methods in PointParticles and LineParticles. The idea is to use the same initial velocity and acceleration for all the vertices and to make the parts of the quad move in the same manner.

The position of a quad is determined by setting its four points to have the values stored in the globals p1, p2, p3, and p4:

     private float[] p1 = {-QUAD_LEN/2, 0.0f, 0.0f};     private float[] p2 = {QUAD_LEN/2, 0.0f, 0.0f};     private float[] p3 = {QUAD_LEN/2, QUAD_LEN, 0.0f};     private float[] p4 = {-QUAD_LEN/2, QUAD_LEN, 0.0f}; 

The order of these starting points is important: they specify the quad in a counterclockwise order starting from the bottom-left point. Collectively, the points define a quad of sides QUAD_LEN, facing along the positive z-axis, resting on the XZ plane, and centered at the origin. This is illustrated by Figure 21-8.

The QuadArray will contain quads which all begin at this position and orientation.

Initializing Particle Texture Coordinates

The aim is to add a texture to each particle (each quad) in the QuadArray and to use only one Texture2D object. This is possible by mapping the four vertices of each quad

Figure 21-8. Initial quad position


to the same (s, t) texels. Later, when the Texture2D is set in the Appearance node component, it will be applied to all the quads in the QuadArray individually. The mapping of quad coords to texels is represented in Figure 21-9.

Figure 21-9. Mapping quads to the same texels


The texels should be specified in an counterclockwise order starting at the bottom left so they possess the same ordering as the quad vertices; otherwise, the texture image will appear distorted.

The mapping is done in createGeometry( ):

     for(int i=0; i < numPoints*2; i=i+8) {       tcoords[i]   = 0.0f; tcoords[i+1] = 0.0f;  // for one vertex       tcoords[i+2] = 1.0f; tcoords[i+3] = 0.0f;       tcoords[i+4] = 1.0f; tcoords[i+5] = 1.0f;       tcoords[i+6] = 0.0f; tcoords[i+7] = 1.0f;     }     quadParts.setTexCoordRefFloat(0, tcoords);   // use BY_REFERENCE 

tcoords is filled with a repeating sequence of (s, t) texels (in float format). When this is applied to the QuadArray, Java 3D will map the vertices to the texels.

The code is difficult to understand because the cs[] array (holding the vertices) is bigger than tcoords[] (holding the texels). The reason for the size difference is that a vertex is stored as three floats, but a texel only needs two.

Initializing Particle Normals

For lighting and Material node components to work, normals must be set for the points of each quad. The desired effect is a fountain of globules pumping out in different directions, which can be enhanced by assigning random normals to the quads. This causes each one to reflect light in a different way, giving the impression of surface variations. In fact, the normals won't be completely random: the z-direction should be positive so light is bounced back toward the viewer, but the x-and y-components can be positive or negative. A further restriction is that the normal vector must be unit length (i.e., be normalized).

Here's the code in createGeometry( ) that does this:

     Vector3f norm = new Vector3f(  );     for(int i=0; i < numPoints*3; i=i+3) {       randomNormal(norm);       norms[i]=norm.x; norms[i+1]=norm.y; norms[i+2]=norm.z;     }     quadParts.setNormalRefFloat(norms);   // use BY_REFERENCE 

and:

     private void randomNormal(Vector3f v)     { float z = (float) Math.random(  );     // between 0-1       float x = (float)(Math.random(  )*2.0 - 1.0);   // -1 to 1       float y = (float)(Math.random(  )*2.0 - 1.0);   // -1 to 1       v.set(x,y,z);       v.normalize(  );     } 

Particle Appearance

createAppearance( ) carries out four tasks:

  • It switches on transparency blending so the transparent parts of a texture will be invisible inside Java 3D.

  • It turns on texture modulation so the texture and material colors will be displayed together.

  • It loads the texture.

  • It sets the material to a blood red.

Here's the code:

     private void createAppearance(  )     { Appearance app = new Appearance(  );           // blended transparency so texture can be irregular       TransparencyAttributes tra = new TransparencyAttributes(  );       tra.setTransparencyMode( TransparencyAttributes.BLENDED );       app.setTransparencyAttributes( tra );           // mix the texture and the material color       TextureAttributes ta = new TextureAttributes(  );       ta.setTextureMode(TextureAttributes.MODULATE);       app.setTextureAttributes(ta);           // load and set the texture       System.out.println("Loading textures from " + TEX_FNM);       TextureLoader loader = new TextureLoader(TEX_FNM, null);       Texture2D texture = (Texture2D) loader.getTexture(  );       app.setTexture(texture);           // set the material: bloody       Material mat = new Material(darkRed, black, red, white, 20.f);       mat.setLightingEnable(true);       app.setMaterial(mat);           setAppearance(app);     } 

TEX_FNM is the file smoke.gif, shown in Figure 21-10. Its background is transparent (i.e., its alpha is 0.0).

Figure 21-10. The smoke.gif texture


createAppearance( ) doesn't switch off polygon culling, which means that the back face of each quad will be invisible (the default action). Will this spoil the illusion when the viewer moves round the back of the particle system? No, because the system is inside an OrientedShape3D node and will rotate to keep its front pointing at the user.

Permitting back face culling improves overall speed since Java 3D only has to render half of the quad.


Updating the Particles

QuadsUpdater is similar to previous GeometryUpdater implementations but works on groups of four points (12 floats) at a time. When a quad has completely dropped below the XZ plane then it's reinitialized: the blood never stops flowing.

Here is updateDate( )'s code:

     public void updateData(Geometry geo)     {  // step in 12's == 4 (x,y,z) coords == one quad       for(int i=0; i < numPoints*3; i=i+12)         updateQuadParticle(i);     }         private void updateQuadParticle(int i)     { if ((cs[i+1] < 0.0f) && (cs[i+4] < 0.0f) &&           (cs[i+7] < 0.0f) && (cs[i+10] < 0.0f))           // all of the quad has dropped below the y-axis         initQuadParticle(i);       else {         updateParticle(i);  // all points in a quad change the same         updateParticle(i+3);         updateParticle(i+6);         updateParticle(i+9);       }     } 

updateParticle( ) uses the same position and velocity equations as the point and line-based particle systems but with a slight modification to the position calculation. After the new coordinates for a point have been stored in cs[i], cs[i+1], and cs[i+2], they are adjusted:

     cs[i] = perturbate(cs[i], DELTA);     cs[i+1] = perturbate(cs[i+1], DELTA);     cs[i+1] = perturbate(cs[i+1], DELTA); 

perturbate( ) adds a random number in the range -DELTA to DELTA, to the coordinate (DELTA is set to be 0.05f). This has the effect of slightly distorting the quad as it moves through the scene, which causes the texture to twist.

Figure 21-11 shows the particle system with the perturbate( ) calls commented out: each quad looks like a perfect sphere, which is unconvincing. This should be compared to the irregular shapes in Figures 21-3 and 21-6.

Figure 21-11. Particles without perturbation




Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net