Use particle systems to simulate explosion rings, fireworks explosions, vapor trails, and more Computer graphics (CG) is important to the games we play, the movies we watch, the design of our vehicles, and more. CG uses traditional polygon-based techniques to model and render classical geometry (cones, spheres, cubes, etc.). In contrast, the geometry of natural phenomena—such as fire and flowing water—needs a different modeling, rendering, and animation technique: particle systems.After introducing you to the particle system concept and presenting particle systems in a historical context, this Java Fun and Games installment takes you on a tour of Java-based software that I created to build and play with particle systems. The article then reveals a demo applet that uses this software to simulate explosion rings, fireworks explosions, and vapor trails.Particle system basicsA particle system is a CG technique that combines modeling, rendering, and animation to simulate fuzzy phenomena (also known as fuzzy objects); examples are various kinds of explosions, flowing water, clouds, fire, meteor trails, snow, sparks, fog, and falling leaves. These phenomena are called fuzzy because, due to the absence of straight edges, they appear blurred when rendered: they are composed of particles. Particle systems manipulate collections of particles. Each particle has various attributes that affect the particle’s behavior, along with where and how (as a point composed of one or more pixels, a line, an image, and so on) the particle is rendered. Common attributes include position, velocity (speed and direction), color, lifetime, age, shape, size, and transparency. Most of these attributes vary their values during the particle’s existence:Birth: Particles are generated somewhere in the fuzzy object’s vicinity. Each of the particle’s attributes is given an initial value. This value may be fixed or determined by some stochastic process.Life: Various particle attributes (like position and age) tend to vary over time. For an explosion, each particle’s color may darken as the particle moves further away from the explosion’s center. This signifies an energy decrease (the particle is dying).Death: Particles typically have two attributes that determine the length of the particle’s life: age and lifetime. Age determines the amount of time the particle has already lived. It typically is initialized to 0 and is measured in animation frames (iterations of an animation loop). Lifetime is the maximum amount of time the particle can live and also is measured in frames. The particle is destroyed when its age reaches its lifetime. Various factors may result in a premature death, including a particle moving out of the viewing area and not returning to that area, and a particle’s color fading to black prior to its age reaching its lifetime. In this case, we could assume that a black particle indicates zero energy (think explosion particles).Particle systems have been in use for several decades. Perhaps their earliest use dates back to 1961-1962. According to “Welcome to PONG-Story,” in 1961, three MIT students created a video game called Spacewar for the PDP-1 minicomputer (released by Digital Equipment Corporation in November 1960). This game featured a particle system to simulate explosions.Spacewar involves a pair of spaceships battling around a star whose gravity pulls them in. While trying to avoid the star, these spaceships fire torpedoes at each other. As Figure 1 reveals, the particle system rips apart a spaceship when the spaceship is hit by a torpedo. Figure 1. The lower-right spaceship’s breakup is generated by a particle systemThe Spacewar particle system also generates explosions whenever both spaceships collide. Despite a low resolution and the absence of color, Figure 2 indicates a degree of realism that the particle system achieves when simulating explosions.Note If you would like to play the original Spacewar game (which is in the public domain), point your Web browser to the Spacewar instructions page and follow the instructions. After you download the Perl and Java source files, you will need to install a copy of Perl on your system (if you don’t already have Perl installed) and execute the following commands to create spacewar.bin:perl macro.pl spacewar.mac >out expands macros in spacewar.mac‘s PDP-1 assembly language and stores the expanded assembly language in a file named outperl pass12.pl out >out1 performs a two-pass assembly of out‘s expanded assembly language and stores a binary listing in a file named out1perl tape.pl out1 extracts the object code from out1‘s binary listing and stores it in spacewar.binAfter you create spacewar.bin, compile pdp1b.java, which also compiles all of the associated Java source files. Execute java pdp1b and click the resulting GUI’s Run button to play. The pdp1b program loads spacewar.bin and starts a PDP-1 emulator to interpret this file’s contents. Subsequent to Spacewar, particle systems were used in other games (such as Asteroids). But their potential was not realized until 1983. In that year, Lucasfilm’s William T. Reeves published his paper “Particle Systems: A Technique for Modeling a Class of Fuzzy Objects.” This paper formalized the particle system concept. It was well received because it described the particle system that generated the Genesis Effect’s planetary wall of fire in the movie Star Trek II: The Wrath of Kahn. If you have an account with the Association for Computing Machinery, you can read Reeves’s paper, a link to which appears in Resources.Particle system softwareParticle system software lets you simulate fuzzy phenomena by supporting the modeling, rendering, and animation of particles. My Java-based particle system software provides the PS class to support modeling and facilitate animation. It also provides the Display class to support rendering. I first discuss Display because PS depends on this class. The rendererThe Display class is a java.awt.Canvas component that renders particles. It renders particles as individual pixels, although there is no reason why Display could not render them as groups of pixels, images, and so on.When you create a Display object, you identify a window and a viewport. CG courses typically define window as the portion of a 2D world that you want to see. The window’s coordinates are specified as floating-point values and are known as world coordinates. CG courses also typically define viewport as the portion of the screen where you want to display the window’s contents. The viewport’s coordinates are specified as integer values and are known as screen coordinates. A transformation known as the windowing transformation maps world coordinates to screen coordinates. Figure 3 illustrates this mapping in terms of world point (xw, yw) and equivalent screen point (xs, ys).The window’s edges are specified at x = wl, x = wr, y = wb, and y = wt. The corresponding viewport edges are located at x = vl, x = vr, y = vb, and y = vt. The following equations use this information to transform world point (xw, yw) to screen point (xs, ys): xs = (vr – vl) / (wr – wl) * (xw – wl) + vlys = (vt – vb) / (wt – wb) * (yw – wb) + vbThese equations determine the position of point (xw, yw) within the window as a fraction of full displacement from the window’s lower-left corner, and interpret this fraction as a fraction of full displacement across the viewport. The addition of the viewport’s lower-left corner provides the (xs, ys) position on the screen. Create a Display object by invoking the Display(double wl, double wb, double wr, double wt, int width, int height) constructor. The wl and wb values identify the lower-left corner of a window, whereas the wr and wt values identify the window’s upper-right corner. These values are specified in world coordinates. The width and height values identify the extents of a viewport associated with the window. All of these values should be chosen so that the window and viewport have the same shape. Otherwise, as Figure 3 reveals, you end up with distortion. Because I want the viewport to occupy the entire component’s drawing surface, there is no point in specifying the upper-left corner, which is (0, 0).The following code fragment creates a window with a lower-left corner at (-100.0, -100.0) and upper-left corner at (100.0, 100.0). The extents are 50 pixels less than the width and height of the applet, to prevent distortion: display = new Display (-100.0, -100.0, 100.0, 100.0, getWidth ()-50, getHeight ()-50); After creating the Display object, your animation logic can call any of the following Display methods: void clear() clears the display to black. Call this method at the beginning of each animation frame to erase previously drawn particles. You normally do this before updating the particle system.void drawLine(double x1, double y1, double x2, double y2, Color c) draws a line on the display from (x1, y1) to (x2, y2) and in the color specified by c. Values passed to x1, y1, x2, and y2 are specified in world coordinates. Call this method to draw any shapes (like a rocket ship) after updating the particle system for the current animation frame.void plot(double x, double y, Color c) draws a pixel on the display at (x, y) and in the color specified by c. Values passed to x and y are specified in world coordinates. Although the PS class calls this method to plot particles, you can also call this method to assist in drawing any shapes after updating the particle system for the current animation frame.Because the aforementioned methods produce output in a buffer (to eliminate one source of flicker), you need to call repaint() on the Display object to make the display’s buffer visible.You’re probably wondering why I went to the trouble of creating a window and a viewport. In addition to extra code not necessary if integers were used instead of floating-point values, these floating-pointing values yield simulations that are slower than their integer-based counterparts. I had two reasons for doing what I did:Accuracy: When you simulate with integer values, you’ll lose some information due to truncation. My original integer-based vapor trail simulation resulted in unsightly empty streaks appearing in the trail. This was caused by the discrete nature of integers as opposed to the continuous nature of floating-point values.Simplify transition to 3D: At some point, I’ll want to transition this particle system software to 3D. A 3D graphics package often provides the capability to plot points in world coordinates using floating-point values.The engineThe PS class is the engine of my particle system software. It models and generates particles, updates the particle system during an animation loop, and kills off those particles that die naturally or prematurely. After creating a Display object, your application or applet creates a PS object by invoking the PS(Display display) constructor to initialize the engine. Initialization saves the Display object for later access by the particle system update logic, creates four color tables describing shades of green, red, white, and yellow, and creates an array of particle objects that each describe an individual particle. After creating the PS object, your animation logic can call the following PS methods:void generateParticle(int type, int color_type, int lifetime, int age, double x, double y, double xv, double yv) attempts to generate a new particle for the particle system. Because PS has an upper limit (identified by constant PS.MAX_PARTICLES) on the maximum number of particles that can be modeled and manipulated, this method silently returns if there is no more room for a new particle.Each particle has a type that determines what happens to the particle’s color (which can be thought of as signifying particle energy in simulations where it makes sense to think about energy) as the particle ages. Pass to type one of the constants PS.PARTICLE_TYPE_CONSTANT (particle’s color remains the same throughout its life), PS.PARTICLE_TYPE_FADE (particle’s color fades to black as the particle ages), or PS.PARTICLE_TYPE_FLICKERFADE (particle’s color flickers while it fades to black as the particle ages).The range of colors that a particle can take on during its life (PS.PARTICLE_TYPE_FADE and PS.PARTICLE_TYPE_FLICKERFADE) or the brightest of these colors (PS.PARTICLE_TYPE_CONSTANT) is determined by the value passed to color_type. Pass to color_type one of the constants PS.PARTICLE_COLOR_TYPE_GREEN, PS.PARTICLE_COLOR_TYPE_RED, PS.PARTICLE_COLOR_TYPE_WHITE, or PS.PARTICLE_COLOR_TYPE_YELLOW. In addition to specifying values for type and color_type, you must specify values for six other parameters when generating a new particle:lifetime determines the maximum number of animation frames that the particle will live. Pass a value greater than zero, otherwise an IllegalArgumentException results.age determines the number of animation frames that the particle has already lived. Pass a value greater than or equal to zero and less than lifetime‘s value, otherwise an IllegalArgumentException results.x and y determine the particle’s initial position in world coordinates.xv and yv determine the particle’s horizontal and vertical velocities (and directions), respectively.boolean isFinished() returns true if all particles are dead (the particle system is finished). Use this method to determine when to stop the animation loop.void reset() resets the engine. All particles are marked dead (in case any are still alive from the premature termination of a prior simulation), and the gravity and wind are reset to their zero defaults. Call this method before entering the animation loop.static int rnd(int min, int max) returns a random integer ranging from min inclusive to max inclusive. Use this method to randomly choose an initial particle age, a particle’s departure angle, and so on.void setGravity(double g) establishes the global gravity for all particles. Specify a negative g value to pull particles toward the bottom of the window.void setWind(double w) establishes the global wind for all particles. Specify a positive w value to blow particles toward the window’s right.void update() updates the particle system by transitioning all particles to their next state. Call this method after clearing the display. The update() method determines if a particle has left the window’s bounds by comparing the particle’s position with Display‘s wl, wr, wb, and wt variables. If the particle’s position exceeds these bounds, the particle is killed off. A particle is also killed off when it fades or flicker-fades to black, or when its age reaches its lifetime (whichever comes first). This method calls plot() to plot particles.Let’s find out how this engine is used in a simulation. The following code fragment creates a particle system that simulates a single white particle moving in an upper-left direction. It assumes the Display object was created by the earlier code fragment: // Initialize the engine at the start of the program.ps = new PS (display);// In some other method ...// Reset the engine in case some other simulation has ended before // all particles have died.ps.reset ();// Generate a single white particle in the lower-right quadrant of // the window. This particle will move towards the upper-left // quadrant.// At least one particle must be created before the animation loop // because isFinished() will return true if no alive particles are // present, and there are no alive particles following a reset() // method call.ps.generateParticle (PS.PARTICLE_TYPE_CONSTANT, PS.PARTICLE_COLOR_TYPE_WHITE, 300, 0, 75.0, -75.0, -1.0, 1.0);// Establish the animation.Runnable r = new Runnable () { public void run () { Thread thdCurrent = Thread.currentThread (); while (thdCurrent == thd && !ps.isFinished ()) { // Erase the contents of the background // buffer to remove the particle from its // previous position. display.clear (); // Transition the particle system to a new // state. This might involve the death of // the particle. ps.update (); // Display's plot() method, which ps.update() // calls, renders the particle in the // background buffer in its new position. A // repaint() call is needed to display the // buffer on the surface of the Display // component. display.repaint (); // Every animation needs to sleep for a length // of time before moving on to the next frame. try { Thread.sleep (DELAY); } catch (InterruptedException e) { } } } };// Prepare new simulation thread and begin the simulation.thd = new Thread (r); thd.start (); Example particle systemsMy particle system software lets you simulate a wide variety of fuzzy phenomena. To give you some idea of what is possible, I’ve created a PSDemo applet that demonstrates explosion rings, fireworks explosions, and vapor trails. Explosion ringsThe movies Star Trek VI: The Undiscovered Country and Star Wars: A New Hope feature a ring of energy/matter moving away from an exploded moon, planet, and deathstar. I like this explosion ring effect and have created a particle system that simulates explosion rings. Because of 2D graphics limitations, however, you don’t see the effect in all of its glory. Instead, you observe the effect as if you were looking down on it (or looking up at it). The result, shown in Figure 4, looks more like rings of plasma ejected from a star going nova.To simplify the explosion ring simulation, I wrote a startExplosionRing() method that generates a specific number of particles. Each particle is given the same starting location, a random departure angle (to achieve a circular ring effect), and a velocity constrained to a bounded range of randomly-generated velocities. An acceleration factor is applied to the velocity so that the outer explosion ring pulls away from the inner explosion ring (which I think looks cool). This method, excerpted from PSDemo, appears below: void startExplosionRing (int type, int color_type, int lifetime, int age, double x, double y, double xv, double yv, double accel, int num_particles) { while (--num_particles >= 0) { // Calculate particle's departure angle in terms of degrees. int ang = PS.rnd (0, 359); // All particles must leave the explosion's center within a limited // range of velocities. double vel = 1.5+rnd (0.0, 0.5); // Accelerate or decelerate the explosion ring if accel is not 1.0. vel *= accel; // Generate the particle so that it leaves the explosion ring in the // appropriate direction. ps.generateParticle (type, color_type, lifetime, age, x, y, xv+Math.cos (Math.toRadians (ang))*vel, yv+Math.sin (Math.toRadians (ang))*vel); } } The explosion ring simulation resets the engine, calls startExplosionRing() to start an explosion ring, and creates/starts a thread to handle the animation. The animation thread loop iteration clears the display, updates the particle system, paints the display on the component surface, increments the counter, and then sleeps. When the counter reaches 8, another explosion ring starts. These tasks are carried out by PSDemo‘s explosionRing() method: void explosionRing () { btnFireworksExplosion.setEnabled (false); btnVaporTrail.setEnabled (false); btnExplosionRing.setEnabled (false); // Reset the particle system. ps.reset (); // Generate 1000 particles that are positioned at the center of the // outer explosion ring and share the same range of velocities. Give the // outer explosion ring a higher acceleration than the inner explosion // ring so that the outer explosion ring pulls away from the inner // explosion ring. final double x; final double y; startExplosionRing (PS.PARTICLE_TYPE_FLICKERFADE, PS.PARTICLE_COLOR_TYPE_GREEN, 300, 0, x = rnd (-75.0, 75.0), y = rnd (-75.0, 75.0), 0, 0, 1.6, 1000); Runnable r = new Runnable () { public void run () { int counter = 0; Thread thdCurrent = Thread.currentThread (); while (thdCurrent == thd && !ps.isFinished ()) { display.clear (); ps.update (); display.repaint (); try { Thread.sleep (DELAY_EXPLOSION_RING); } catch (InterruptedException e) { } // After a small delay, generate a secondary // explosion ring consisting of 500 particles. // These particles are positioned at the center // of the explosion ring and share the same range // of velocities. if (++counter == 8) startExplosionRing (PS.PARTICLE_TYPE_FADE, PS.PARTICLE_COLOR_TYPE_YELLOW, 300, 0, x, y, 0, 0, 1.0, 500); } btnFireworksExplosion.setEnabled (true); btnVaporTrail.setEnabled (true); btnExplosionRing.setEnabled (true); } }; thd = new Thread (r); thd.start (); } Fireworks explosionsI love watching fireworks displays, especially the exploding balls of energy/matter. I’ve created a particle system to simulate this effect. As Figure 5 reveals, the particle system uses multiple colors to enhance the explosion.To simplify the fireworks explosion simulation, I wrote a startFireworksExplosion() method that generates a specific number of particles. Each particle is given the same starting location, a random departure angle (to achieve a circular effect), and a unique velocity. This method, excerpted from PSDemo, appears below: void startFireworksExplosion (int type, int color_type, int lifetime, int age, double x, double y, double xv, double yv, int num_particles) { // Each particle must be given a unique velocity for the fireworks // explosion to look realistic. This is accomplished first by calculating // a velocity increment based on the number of particles, and then by // specifying num_particles velocities, where each velocity is a unique // multiple of the increment. double incr = 1.0/num_particles; double vel = 0.0; while (--num_particles >= 0) { // Calculate particle's departure angle in terms of degrees. int ang = PS.rnd (0, 359); // Establish appropriate departure velocity. vel += incr; // Generate the particle so that it leaves the fireworks explosion's // center in the appropriate direction and at the appropriate // velocity. ps.generateParticle (type, color_type, lifetime, age, x, y, xv+Math.cos (Math.toRadians (ang))*vel, yv+Math.sin (Math.toRadians (ang))*vel); } } The fireworks explosion simulation resets the engine, calls startFireworksExplosion() three times to start three fireworks explosions (each with a different color for its particles; all three beginning at the same window location), establishes some gravity to pull the particles toward the bottom of the window, and creates/starts a thread to take care of the animation. The animation thread loop iteration clears the display, updates the particle system, paints the display on the component surface, and then sleeps. These tasks are carried out by PSDemo‘s fireworksExplosion() method: void fireworksExplosion () { btnVaporTrail.setEnabled (false); btnExplosionRing.setEnabled (false); btnFireworksExplosion.setEnabled (false); ps.reset (); // Generate 4000 particles that are positioned at the center of the // fireworks explosion and have unique velocities. double x; double y; startFireworksExplosion (PS.PARTICLE_TYPE_FLICKERFADE, PS.PARTICLE_COLOR_TYPE_YELLOW, 800, PS.rnd (50, 100), x = rnd (-75.0, 75.0), y = rnd (-75.0, 75.0), 0, 0, 4000); // Generate 500 particles of a different color that are positioned at // the center of the fireworks explosion and have unique velocities among // themselves. This adds a more colorful effect to the explosion. startFireworksExplosion (PS.PARTICLE_TYPE_FADE, PS.PARTICLE_COLOR_TYPE_RED, 800, PS.rnd (50, 100), x, y, 0, 0, 500); // Generate 250 particles of a different color that are positioned at // the center of the fireworks explosion and have unique velocities among // themselves. This adds an even more colorful effect to the explosion. startFireworksExplosion (PS.PARTICLE_TYPE_FADE, PS.PARTICLE_COLOR_TYPE_WHITE, 800, PS.rnd (50, 100), x, y, 0, 0, 250); // Because the fireworks explosion happens in the atmosphere of a planet, // particles will be pulled toward the ground via the planet's // gravitational force. Because of the window transform, a negative // value must be used to pull particles toward the bottom of the window. ps.setGravity (-0.01); Runnable r = new Runnable () { public void run () { Thread thdCurrent = Thread.currentThread (); while (thdCurrent == thd && !ps.isFinished ()) { display.clear (); ps.update (); display.repaint (); try { Thread.sleep (DELAY_FIREWORKS_EXPLOSION); } catch (InterruptedException e) { } } btnVaporTrail.setEnabled (true); btnExplosionRing.setEnabled (true); btnFireworksExplosion.setEnabled (true); } }; thd = new Thread (r); thd.start (); } Vapor trailsThe final effect that I’ve simulated with a particle system is the vapor trail—a streak of condensed water vapor created in the atmosphere by an airplane or a rocket at a high altitude. I’ve fudged this definition somewhat by assuming that a vapor trail can alternatively consist of plasma fired from a rocket engine in space. Figure 6 reveals this assumption by showing one frame of an animation where an arrow-shaped rocket travels through space and ejects plasma from its engine nozzle—an emitter of particles.The vapor trail simulation resets the engine and creates/starts a thread to handle the animation. The animation thread establishes an emitter location, generates an initial particle to ensure at least one live particle exists before entering the animation loop, and enters the loop. Each loop iteration generates a random number of particles with random velocities constrained to a specific range of directions, clears the display, updates the particle system, draws an arrow shape signifying a rocket and its direction, calls repaint(), sleeps, and moves the emitter in an upper-left direction so that the trail flows in a lower-right direction. These tasks are carried out by PSDemo‘s vaporTrail() method: void vaporTrail () { btnExplosionRing.setEnabled (false); btnFireworksExplosion.setEnabled (false); btnVaporTrail.setEnabled (false); ps.reset (); Runnable r; r = new Runnable () { public void run () { // Vapor trail particles emerge from an emitter -- the nozzle // of a rocket ship, for example. Establish the emitter's // initial location in the lower-right quadrant of the window. double emit_x = 75.0; double emit_y = -75.0; // Generate an initial particle. At least one particle must // exist. Otherwise, ps.isFinished() returns false (because // there are no alive particles) and the while loop is never // entered. ps.generateParticle (PS.PARTICLE_TYPE_FADE, PS.PARTICLE_COLOR_TYPE_WHITE, 300, PS.rnd (90, 150), emit_x, emit_y, 1.0, -1.0); Thread thdCurrent = Thread.currentThread (); while (thdCurrent == thd && !ps.isFinished ()) { // Create a group of particles that blast forth in various // directions from the emitter. Directions are constrained // so that the vapor trail moves in the opposite direction // to the emitter. int limit = PS.rnd (30, 130); for (int i = 0; i < limit; i++) { double xv = rnd (1.0, 7.0); double yv = rnd (-1.0, -7.0); ps.generateParticle (PS.PARTICLE_TYPE_FADE, PS.PARTICLE_COLOR_TYPE_WHITE, 300, PS.rnd (90, 150), emit_x, emit_y, xv, yv); } display.clear (); ps.update (); // Draw a rocket. (As an exercise, make the rocket look // more realistic than its current arrow shape.) display.drawLine (emit_x-8, emit_y+8, emit_x-8, emit_y-2, Color.magenta); display.drawLine (emit_x-8, emit_y+8, emit_x+2, emit_y+8, Color.magenta); display.drawLine (emit_x-8, emit_y+8, emit_x+2, emit_y-2, Color.magenta); display.repaint (); try { Thread.sleep (DELAY_VAPOR_TRAIL); } catch (InterruptedException e) { } // Move the emitter (and rocket) in a diagonal direction // towards the upper-left corner of the window. emit_x -= 1.0; emit_y += 1.0; } btnExplosionRing.setEnabled (true); btnFireworksExplosion.setEnabled (true); btnVaporTrail.setEnabled (true); } }; thd = new Thread (r); thd.start (); } Within each of the startExplosionRing(), startFireworksExplosion(), and startVaporTrail() methods, I disable GUI buttons to prevent another simulation from being selected while a simulation is running. I re-enable those buttons when the simulation finishes. You might be wondering why I change the button order from method to method when disabling buttons. I do this because the focus shifts to the last disabled button and I don’t want the focus to shift away from the currently focused button. ConclusionIt’s fun to simulate different kinds of fuzzy phenomena with particle systems. After playing with the applet’s explosion ring, fireworks explosion, and vapor trail simulations, you might consider improving the existing simulations and introducing your own simulations. I have some ideas that can help get you started:Modify PSDemo‘s GUI to include configuration items. Perhaps you might want to introduce a java.awt.Choice component for modifying the color of the vapor trail, or for determining if a particle type should be constant, fade, or flicker-fade.Introduce new particle types with behavior similar to fade and flicker-fade, but with a slower fade—by having PS‘s update() method decrement the current color every other time (rather than every time) this method is called. Modify PSDemo to use these new constants.Combine the fireworks explosion and vapor trail simulations into a simulation where the rocket explodes when it reaches a certain position on its diagonal trajectory. The explosion should occur at the tip of the rocket. Remove the rocket prior to the explosion.Create a fountain-based simulation. Water particles shoot straight up from an emitter, curve around, and fall into a pool—the source of the water particles. After completing this simulation, think about simulating fire.Although this article’s 2D-based particle system software lets you create interesting particle systems, you’ll probably want to build 3D-based particle system software to achieve more impressive particle systems. To learn how to build this software, I recommend reading Mike Jacobs’s “Star Trek Technology for Java3D” (Java Developers Journal, July 2005).Jeff Friesen is a freelance software developer and educator specializing in C, C++, and Java technology. JavaComputers and PeripheralsTechnology Industry