Bounce Documentation

Contents


Introduction

The Bounce project demonstrates a software framework for creating simulations of biological processes. The framework is implemented in Java and partitions the implementation into 4 distinct modules:

This modularity helps to manage the complexity of the software implementation by isolating the high-level tasks from each other. However, in doing so, the code makes fairly extensive use of certain object-oriented design features such as classes, objects, inheritance, composition and interfaces. For a nice introduction to object-oriented concepts in Java, you can download the free electronic book, Thinking In Java, 3rd Ed., by Bruce Eckel.

Another criterion that motivates the object-oriented design of this application is avoidance of cyclic dependencies between modules. This refers to modules that mutually reference each other. For example, if we have two modules A and B, a cyclic dependency exists if module A references classes in module B and module B references classes in module A. While this is sometimes unavoidable, in general it is a bad thing and should be avoided if possible. Among other reasons, such circular dependencies makes it difficult to re-use modules independently in other applications.

In the Bounce code, cyclic dependencies are avoided through the use of base classes and interfaces. The numerics module doesn't reference any other modules, the model module only references the numerics module and the view module references the model and numerics modules.


Program Initialization

A top-level class, called 'Bounce', contains the single main() entry point for the application. After initialization is done, the graphical part of the application will consist of a single JFrame window that contains all of the user-interface components and graphical output.

However, because a controlled framerate is needed for CPU-speed independence, the application design is complicated somewhat by the need for a separate thread, which drives the actual processing and rendering of the simulation. The following UML diagram outlines the top-level class and its relationship to its main components:

The Bounce program begins in the usual way, in the main() function of the Bounce class. This simply constructs a Bounce object and then calls an initialization function, init(). Program execution then exits the main() function, but the process does not terminate because a separate thread is created that remains running.

Inside the Bounce.init() function, the three main components BounceModel, BounceView and SimEngine are created. SimEngine is the component that creates and starts the separate thread, which is a Java Thread object. The SimEngine also provides an implementation of the Thread.run() method, which is called periodically by the operating system.


The SimEngine Driver

The SimEngine defines a common functionality for driving simulations, so it is designed as a component that can be reused with multiple applications. It does this by interacting with the application through an interface, 'GraphicSim'. The engine periodically calls two functions, 'update()' and 'draw()', which are implemented by the Bounce class.

The draw() function is pretty self-explanatory - it causes the graphical display to be updated. The Bounce class in turn just calls the draw() method of its BounceView component, which handles the actual drawing operations.

The purpose of the update() call is a little less obvious. It causes the state of the simulation to be updated or advanced forward in time by a certain amount. The BounceModel component provides a method to do this, by updating the state of the simulation, which consists of a dynamical system of a falling ball and a fixed floor. Note that this simulation processing is completely independent of any graphical representation.

However, the graphical BounceView component also has an update() method that must be called, because it allows the graphical interface to remain in sync with the state of the model. For example, when the ball hits the floor, the view needs to know about this so it can play a 'bounce' sound. This 'view state' makes use of a view Finite State Machine, which is described in more detail below.


BounceView

Because the physical model in this demo application is so simple, the view module is by far the most complicated part of the program. However, much of the functionality it provides is common to other simulations, so the effort of learning and working with it should decrease over time.

Most of the view functionality can be divided into the following areas:

The following UML diagram shows the BounceView class and its main components:

The BounceView class derives from JFrame, a Swing component that displays as a top-level window in the operating system. It registers itself as a MouseListener to receive mouse events from the operating system. It then delivers these mouse events to objects in its scene hierarchy.

Scene Graph

A scene hierarchy is a common design pattern in 3D computer games. Also called a scene graph, it is a tree structure built using SceneNode objects. The scene graph is contained in the BounceScene component of the view, which contains a single root node object that is the ancestor of all other nodes. All SceneNode objects in the graph contain a draw() function. These are called through a traversal of the tree, which begins in the top-level draw() method of BounceScene, which overrides this method of its JPanel base class. All drawing is done in an off-screen image buffer (double-buffering) to avoid flicker of moving objects.

GUI Events

Input events such as mouse clicks are delivered from the BounceView to the BounceScene, which in turn sends them to the root SceneNode, which traverses the tree in a recursive function getNodeFromPoint(). A SceneNode object which intersects the mouse click location can then choose to handle the mouse event. For example, a Button object will respond by calling ButtonListener.itemClicked(). The BounceView object itself implements the ButtonListener and RadioListener interfaces and will receive these events, and can either process them directly or let them be handled by the Finite State Machine (BounceStateMachine). This provides for a simple GUI event-handling capability that bypasses the vastly more complex and potentially problematic Swing-based event-handling system.

Note that the scene graph components are divided into two groups: GUI-type objects (such as Button and Radio) and Simulation objects (HappyFunBall and GcuFloor). Including the GUI objects in the scene graph is perhaps atypical; many 3D game designs will separate the GUI framework from the scene graph of 3D objects. The two were combined in this demo app as a simplification.

Media Handlers

Photographic images and sound effects can greatly enhance the appeal of educational simulations. The view framework includes two classes for providing convenient use of these resources. The ImageMan class loads .png image files from the 'images' subdirectory and makes them available to callers through the getImage() method. In the Bounce app, only a single image is currently used: a steel texture drawn by the GcuFloor object, which produces the graphical representation of the Floor object in the model module.

Sound effects are provided by the SoundMan class, which loads .wav files in the 'sounds' subdirectory and makes them available to callers through the playSound() method.

Finite State Machine

The final main component of BounceView is BounceStateMachine. This is a class that implements a Finite State Machine, which is another common design strategy in certain computer games and simulations. A finite state machine (FSM) attempts to solve the following problem: how do we produce the desired behavior of a complex simulation for all potential combinations of internal states and external inputs?

For example, a mouse click on a graphical object in some simulation might be expected to bring up a property box for that object in one application mode, allow it to be dragged in another mode and do nothing if the application is paused or in a 'help' mode.

One way of solving this problem is to scatter conditional logic all throughout the code. While this can work for small and simple cases, for more complicated situations it can produce code that is bug-prone and difficult to maintain and understand.

An alternative is to use an FSM for controlling the behavior of a game or simulation. An FSM involves the explicit definition of distinct application states, along with an associated set of transition rules that control how the application moves from one state to another. Once the states and transition rules are defined, the behavior is trivially determined by the following algorithm of the FSM, which takes place in the tick() method of the StateMachine class:

This function is called periodically in the BounceView update() method. The following UML diagram shows some of the classes that are used to implement the FSM in the Bounce application:

The diagram shows the BounceStateMachine containing a single component, an IdleState object. This represents the initial state of the application. In this state, the application sits there doing nothing. The IdleState contains 3 StateTransition objects: IdleToFallingTransition, IdleToRisingTransition and IdleToShowHelpTransition. For each tick() of the FSM, the state machine will examine each of these transitions, in order, and evaluate its conditions.

For the IdleToFallingTransition, the condition is met if the 'Run' button is clicked and the ball velocity is 0 or negative. If this condition is met, a click sound is played and then the FallingState is set as the new current state. When the FallingState (or the RisingState) is the current FSM state, then numerical integration becomes enabled and the dynamic state of the ball will start to change.

The application then remains in the FallingState until its transition conditions are met, and so on. The following table defines each of the states and their associated transitions:

StateTransitionConditionsActionsNext State
IdleState IdleToFallingTransition 'Run' button clicked, ball velocity <= 0 play 'click' sound FallingState
IdleToRisingTransition 'Run' button clicked, ball velocity > 0 play 'click' sound RisingState
IdleToShowHelpTransition 'Help' button clicked play 'click' sound ShowHelpState
FallingState FallingToIdleTransition 'Stop' or 'Reset' button clicked play 'click' sound, reset sim if 'Reset' clicked IdleState
FallingToRisingTransition ball velocity > 0 play 'bounce' sound RisingState
FallingToShowHelpTransition 'Help' button clicked play 'click' sound ShowHelpState
RisingState RisingToIdleTransition 'Stop' or 'Reset' button clicked play 'click' sound, reset sim if 'Reset' clicked IdleState
RisingToFallingTransition ball velocity <= 0   FallingState
RisingToShowHelpTransition 'Help' button clicked play 'click' sound ShowHelpState
ShowHelpState ShowHelpToIdleTransition 'Ok' button clicked and active state == "Idle" play 'click' sound, hide help IdleState
ShowHelpToFallingTransition 'Ok' button clicked and active state == "Falling" play 'click' sound, hide help FallingState
ShowHelpToRisingTransition 'Ok' button clicked and active state == "Rising" play 'click' sound, hide help RisingState


BounceModel

The model component of the application is much simpler than the view. The model and associated numerics have the most important task of computing the dynamical behavior of the simulation. In this case, the behavior consists of the position and velocity of a bouncing ball, resulting from the gravitaional and collision forces that act upon it.

A UML diagram shows the main class, BounceModel, and related classes:

The BounceModel object has two component objects Floor and Ball, which represent physical objects. The two additional model classes are BallVelFunction and BallAccelFunction, which are derived from the base class Function in the numerics module. The final component is an instance of the Numerics class, which contains the integration methods called by the BounceModel in its solveSystem() method.

The details of how these components are used in the solveSystem() method will be described below, after first showing the additional classes in the numerics module.


Numerics

The numerics module contains some utility classes for numerical operations and data, and classes to support input, calculation and return of numerical integration data. The numerical classes used in the Bounce application are shown in the following UML diagram:

The Vector class is a simple data class for containing 2D values such as position, velocity, force, etc. The Bounce simulation actually only involves changes in a single (y) dimension, but a 2D representation of the objects was chosen to enable generality and extension of the code to other applications.

The next class in the module is Function. Objects of this class provide function derivative values to the integration method. The actual values are model-specific, and provided by derived classes such as BallAccelFunction, but use of a base Function class allows them to be passed to the numerics module without it having to know anything about the specifics of the other module.