Building a Wave Viewer
- By Wilf LaLonde
Recently, I played with programs that simulated waves on water and found them much more complex than I had expected. In particular, when objects were dropped on water, the programs simulated a ring that was propagated in 3D to provide a convincing spherical wave expanding out from the drop point. It seemed to me that this type of wave wasn't all that different from a simple sine wave. Just to be sure, I built a wave viewer using the same user interface framework discussed in my "Stretch Them Springs"1
and "Twirl Them Particles"2
columns that appeared in previous issues.
Figure 1. A sine wave in the x-direction (function 1).
Figure 1 illustrates the result of using a sine wave that moves in the x-direction. My goal was to create a number of simple functions based on sine waves to see what they actually looked like. Although you can't tell from Figure 1, the sine wave was a dynamic activity; i.e., the wave motion was occurring in real-time on my Pentium 450.
DESIGNING A WAVE OBJECT
To provide this wave-viewing facility, I designed a SurfaceWave class to provide a 3D surface. More specifically, the SurfaceWave class maintained a 2D array (that I called grid) containing 3D points. The x- and z-values of the points were set up to be constant, but the y-values were designed to be modulated by a function, such as a sine wave. The convention I followed is a standard one—x goes right, y goes up, and z goes back.
I specified the size of the surface using set-method extent: (the 2D point is reinterpreted to represent the x- and z-dimensions). All points were created assuming the surface dimensions were unit length; i.e., the x- and z-dimensions were set up to range from -0.5 through to +0.5.
To access the 3D points on the grid, I provided at: and at:put: methods, where the index is a 2D point representing x- and z-coordinates (just like I used for extent:).
To try out alternative sine-like functions, I added an instance variable functionIndex that was used to pick one of a number of class methods such as function1ForPoint:time: (the function itself was intended to compute a new y-value for any one of the 3D points on the surface). This function was provided with a 3D point and a time value (a number in milliseconds representing the time elapsed since the wave viewer started running). These functions might be best kept in the viewer class, but because I'm not showing this code, I thought it might be more convenient to keep it in the SurfaceWave class.
The viewer can perform a number of steps including:
- creating an instance of the surface wave,
- determining the elapsed time since starting,
- telling the surface wave to tick (via message tick:, which is also provided with the elapsed time), and
- telling it to draw.
It can also repeat the last three steps so that we can see real-time dynamic changes to the surface wave. The user has the ability to control the viewer via four keys: r=>reset, s=>stop, g=>go, and n=>next. The next key causes the surface wave to get the message advance
that increments the value of functionIndex
. If functionIndex
is such that the associated function name (which is computed via method functionName
no less) is not defined, it is reset to 1 providing wraparound capability.
The surface wave's tick: method (with a time parameter) simply executes the function selected by the current function index on each 3D point via a perform:with:with: message (the 3D point and the time is provided to the function). The draw method draws a line drawing of the surface by connecting the 3D points. The complete code is provided in Listing 1.
The point of this experiment was to see if we needed complicated functions to provide interesting wave-like behavior. Functions 1 (see Fig. 1) and 2 were designed to display a moving sine wave in the x- and z-directions, respectively. Function 3 (see Fig. 2) changed the wave to move in the diagonal direction. Function 4 (see Fig. 3) modified the sine wave of function 3 by using the absolute value of the x- and z-locations, rather than their signed values. The end result was a circular wave centered at the origin. Function 5 (not shown) accentuated the height differences by squaring the x- and z-values. Function 6 (see Fig. 4) merely moved the start of the circular expanding wavefront to a location other than the origin.
Figure 2. A sine wave marching diagonally (function 3).
Figure 3. A sine wave expanding outward from the origin (function 4).
Figure 4. A sine wave expanding outward from an arbitrary location (function 6).
This simple implementation made it quite clear that waves coming in from far away with no apparent radial motion, or from an expanding wavefront due, for example, to a rock thrown into the water, were simply very minor variants of each other.
- LaLonde, W. "Stretch Them Springs," JOOP, 12(7): 44–52, Nov./Dec. 1999.
- LaLonde, W. "Twirl Them Particles," JOOP, 13(2): 38–43, May 2000.
Wilf LaLonde is director of Learning Dimensions, Inc., a company focused on 3D gaming development.