The Newtonian Object-oriented Physics Engine -- NOOPE

1. Introduction

NOOPE is an open source physics engine written in Java. A program that allows you to run physical simulations. Physical laws are not part of the core of the engine, so you can define your own laws as Java classes and these can be used by NOOPE so long as they follow a certain format.

NOOPE is based on the following assumptions:

At the time of writing, NOOPE can simulate the motion of particles, and also spheres to some extent, with elastic collisions, no spin, however.

2. How NOOPE works

This is a short overview of the internal working of NOOPE. For a reference, refer to the easy-to-browse HTML API documentation generated by javadoc.

A. Basic object hierarchy

NOOPE is built up around two fundamental classes: noope.entities.Entity: This is the base class for all physical objects simulated. noope.laws.Law: This is the abstract base class for all physical laws simulated.

The toplevel class in the NOOPE hierarchy is noope.core.Physics. An instance of Physics contains a list of entities present in the simulation (instance of noope.core.Entities) and a list laws (an instance of noope.core.Laws). [Both Entities and Laws are subclasses of java.util.Vector - an object-oriented array.]

An Entity has got a set of fundamental properties: position, velocity, inertialMass.

What other properties are needed, depends, however, on which laws are being simulated. For this reason, each Entity holds an array of instances of subclasses of noope.laws.LawPropertyRecord, one for each Law; where the indeces of a LawPropertyRecord in the Entity and the corresponding Law in Laws are the same.

Diagram of the basic NOOPE object hierarchy
The basic NOOPE object hierarchy

B. Running of the engine

A physical simulation is run as a sequence of time-slices. A time-slice can be simulated by calling Physics.step(dt), which makes a single discrete timestep of length dt.

Then, the forces by all laws on all entities, the corresponding accelerations, and the resulting new velocities and positions of all entities, are calculated.

For this, Entity provides the methods

Physics calls the act() method of every Law, which causes the Law to calculate the resulting force on each Entity and call Entity.addForce(this_law_s_force). Then, Physics calls the step(dt) method of every Entity to advance it in time by dt. N.B.: Entity.step(dt) also calls the getActiveForce() method of Entity. The result returned is added to the total force, before doing the calculations. This returns 0 for Entity, but subclasses may override it, in order to make simulations, which would otherwise be hard to describe in terms of physical laws, e.g. the motion of a Rocket. Note that this violates Newton's 3rd law (action - reaction principle).

C. Construction - noope.input.BlockReader

The constructor of Physics takes an instance of noope.input.BlockReader.

A BlockReader is a generic data source that is used to construct all NOOPE objects. It is essentially a directory structure with String keys and values, that has to be accessed sequentially.

In more detail, a BlockReader contains

In addition to this, a BlockReader provides a way of identifying errors in the input data, and giving the user the position of the error.

BlockReader.getEntryLocation() and BlockReader.getBlockLocation() ask the BlockReader to return a noope.input.BlockReaderLocation, that contains an identification of the position at which the error has occurred (not necessarily human-readable).

BlockReader.getContext(BlockReaderLocation loc) returns a noope.input.BlockReaderContext that should be a human-readable representation of the error position stored in loc. A BlockReaderContext can provide various useful data and methods for presenting the error to the user, these depend on the particular BlockReader implementation, as do the contents of BlockReaderLocation.

The BlockReader from which Physics will be constructed has the format explained in the noope.input.BlockReader source file.

Physics uses the BlockReader it is constructed with to dynamically load classes with their names given in the source file. This means that noope can work with laws implemented later than it was compiled.

D. Output

There are no specialized output objects in NOOPE, instead these are implemented as laws. At the time of writing, the most advanced output law is noope.output.OutputProjection, which draws to the screen (or to animated gif files or a collection of static gif files, 1 per frame) the projection of the entities onto a plane.

3. How to write your own law

To write your own subclass of law, you need to do three things: The following explains each of these points in more detail:

A. Writing your law constructor

Every law has to have a constructor of the following form for the dynamic loading mechanism to be able to load it:

public MyLaw(BlockReader br, int lawnumber, Physics phys) throws BRLoadingException
where br is the BlockReader this law should be constructed from, lawnumber is the number of this law in the collection of laws and phys is a reference to the Physics object this law belongs to.
A minimal implementation of the law constructor that does nothing but calling the inherited constructor would be:
public MyLaw(BlockReader br, int lawnumber, Physics phys) throws BRLoadingException
{
	super(br, lawnumber, phys);
}

B. Writing the act() method

The act() method should calculate the force on each Entity and call Entity.addForce(force) with it.

The abstract class noope.laws.ParticlePairLaw that is used for laws where the effect on each Entity is the sum of the effects of all other Entitys on that Entity has the following act() method:

    public void act()
    // for all i != j in e: actOnPair(i, j);
    {
	Entities e = physics.getEntities();
	if (e.size() <= 1) return; // nothing to do

	Entity ent, other;

	int i, j;
	for (i = 0; i < e.size(); i++)
	    {
		ent = (Entity)e.elementAt(i);
		for (j = i+1; j < e.size(); j++)
		    {
			other = (Entity)e.elementAt(j);
			actOnPair(ent, other);
		    }
	    }
    }


    /** Applies the law to the two parameters. This should be overridden in subclasses. */
    abstract public void actOnPair(Entity ent1, Entity ent2);

In an output law, it outputs some sort of data, instead of exerting forces on the Entitys.

C. Storing data for each Entity

If each Entity has a property that it should store for a particular law, it can be held in an instance of a subclass of LawPropertyRecord.

For this, you need to:

For an example, I recommend you have a look at the source for noope.laws.Gravity and noope.laws.GravityPropertyRecord.