top of page
Search
  • cedarcantab

Box2D-Lite Walkthrough in Javascript: Preparations - Basic Classes

Updated: Apr 6

Box2D-Lite




In this post, I list out the core classes of Box2D-Lite, which should make the following posts easier to comprehend.


Basic structure of a physics engine


Before delving into the details, it is useful to understand the basic functions of a physics engine, and how it might sit within the main loop of your code. Following is a diagram of a typical game loop, borrowed from here.



In essence, the physics engine is responsible for looking after the positions of simulated bodies moving around the screen, detecting the collisions between the bodies, and resolving the collisions (eg make the collided bodies bounce away from each other) when they are detected.


The basic pieces of the physics engine may be illustrated by the diagram below.




I will delve into how each of these functions are implemented in Box2D-Lite in subsequent posts, but for this post I will list out the key classes / objects relevant to the individual pieces, with brief description of their properties.



Body


The body is probably the simplest to understand. It is the class that represent each element moving around the simulation world. The instantiated body object will hold the data that define the state of each body. This class will be described in detail in the next post.


Arbiter Class


This is effectively the collision manifold - the class that holds the collision information. The key properties are as below. As well as holding the reference to the two colliding bodies, the friction for the pair is calculated when this class in instantiated, and most importantly, this class holds the details of the actual points, held in class called Contact.



Contact class


The granular contact information is stored in the separate object called Contact.


struct Contact
{
	Contact() : Pn(0.0f), Pt(0.0f), Pnb(0.0f) {}

	Vec2 position;
	Vec2 normal;
	Vec2 r1, r2;
	float separation;
	float Pn;	// accumulated normal impulse
	float Pt;	// accumulated tangent impulse
	float Pnb;	// accumulated normal impulse for position bias
	float massNormal, massTangent;
	float bias;
	FeaturePair feature;
};

The name of the properties together with the comments provide plenty of clues as to the information that needs to be collected in this object, except perhaps the property called feature, which is another separate object called FeaturePair, which as the name suggests holds a pair of features for each of the bodies (in 2D, it would be pair of edges for each box). We'll see why such a class is necessary when we come to contact point generation.


FeaturePair Class


FeaturePair is a C++ specific data-type called a union, which, as far as I am aware, does not exist in Javascript. It has two members:

  • Edges, which is another class called Edges, and

  • value, which is an integer.

(C++ code)

union FeaturePair
{
	struct Edges
	{
		char inEdge1;
		char outEdge1;
		char inEdge2;
		char outEdge2;
	} e;
	int value;
};

The Edge class comprises 4 properties; information relating to 2 edges x 2 bodies, by reference to the constants below:


(C++ code)

enum EdgeNumbers
{
	NO_EDGE = 0,
	EDGE1,
	EDGE2,
	EDGE3,
	EDGE4
};

The "union" class is a special data type available in C++ that allows to store different data types in the same memory location. This is a fancy way of saying that Edges and value will both point to the same location in memory. Hence if Edges is changed, so does value, except since value is defined as an integer, it will return an integer; ie FeaturePair.value will always return an integer that reflects the content of the Edges class. As we will see later, this property is used to compare one Featurepair object against another.


There is also a method to "flip" the feature pair.


voidFlip(FeaturePair& fp){Swap(fp.e.inEdge1, fp.e.inEdge2);Swap(fp.e.outEdge1, fp.e.outEdge2);}

JavaScript implementation

How can we replicate this type of object in Javascript? The flip method is easy enough. The difficult part is the value property. I ended up creating a method that returns an integer that reflects the Edges object, together with a method to compare two feature pairs, based on that value. It's much less efficient than the C++ code but it does the job.


class FeaturePair {
    constructor() {
        this.e = new Edges();
        this.value = 0;
    }

    flip() {
        const edges = this.e;

        const tempIn = edges.inEdge1;
        const tempOut = edges.outEdge1;

        edges.inEdge1 = edges.inEdge2;
        edges.inEdge2 = tempIn;

        edges.outEdge1 = edges.outEdge2;
        edges.outEdge2 = tempOut;
    }

    getKey(){
		return this.e.inEdge1 + (this.e.outEdge1 << 8) + (this.e.inEdge2 << 16) + (this.e.outEdge2 << 24);
	};

    equals(fp) {
		return this.getKey() === fp.getKey();
	}
    
}


World


There is a world object to hold all the physics bodies, joints, and the arbiters.



The tricky part is the ArbiterKey. This appears to be a object consisting of pair of bodies which is used to "identify" a collision pair in a C++ specific structure sorted associative container. This is a class that contains key-value pairs with unique keys.


std:: map<ArbiterKey, Arbiter> arbiters;

I will explain how I ended up implementing this in Javascript, in a later post.


Step method


The world class has a number of methods. Of these methods, the step method is where all the action happens. At a very high level, there are 5 steps within this method:


  1. broadphase collision detection - the first step is to call a method called broadPhase - which is actually not much of a broad phase collision method, but actually a brute force collision detection that loops through all combination of bodies, assessing whether there is collision

  2. integrate forces - loop through all the bodies, adding "force" to the body (including gravity, any external force and torque)

  3. arbiter pre-step - this is the meat of the physics engine where the impulse required to separate any penetrating bodies will be calculated

  4. apply impulse

  5. update position



Rotation Matrix class


Box2D uses a lot of matrix maths.


Although Phaser 3 does provide a custom transformation matrix object as well as some methods to manipulate arrays like matrices, I have pretty much replicated the Mat22 class in the original C++ implementation.


The structure of the Mat22 class is basically a object with two vector2's called this.col1, and this.col2, representing the left column and right column respectively.





C++ allows you to "overload" methods. As far as I am aware there is not a particularly elegant way of doing the same in Javascript and where possible I have avoided it. However, in the case constructor function, I have "hacked" an overloading type of feature as follows, as not doing this would have made my "port" much more different from the original.


class Mat22 {

	constructor(angle) {

		if (arguments.length === 1 && typeof arguments[0] === 'number') {
		
			const c = Math.cos(angle);
			const s = Math.sin(angle);
				
			this.col1 = new Vector2(c, s);
			this.col2 = new Vector2(-s, c);

		}
		
		else if (arguments.length === 2 && arguments[0] instanceof Vector2 && arguments[1] instanceof Vector2)
		{
			this.col1 = arguments[0];
			this.col2 = arguments[1];
		}
		else
	
		{
			this.col1 = new Vector2();
			this.col2 = new Vector2();
		}
	}


And with that, we are ready to start the journey of extending Phaser's Geom.Rectangle class into Box2D-Lite Box!


36 views0 comments
記事: Blog2_Post
bottom of page