top of page
Search
  • cedarcantab

Understanding Box2D-Lite in Javascript, Part 5: Friction

Updated: 4 hours ago






If you ran the CodePen from the previous post, you would have noticed that the when the pyramid crumbles, the boxes, once they land on the ground just sort of all press away and drift off the edge of the screen. This is due to the fact that I had not ported the friction part of Box2D-Lite.


This post is about implementing friction.


Friction can be a tricky subject


In real life, friction is complicated. In order to model friction in a physics engine, a simplified model must be employed.


Intuitively you can imagine that we can simulate friction by applying some impulse in the direction opposite to the current velocity of the body and orthogonal to the contact normal. But what of the magnitude of this impulse? For that, we can follow the Coulomb's model of friction, which basically says that the friction force magnitude is a fraction of the normal force magnitude, and the "fraction" is defined by the coefficient of friction, denoted by mu. Mathematically, this is expressed as:



where,

  • F is the Frictional Force,

  • μ is the coefficient of friction,

  • η is the Normal force (= mg),


Further, Coulomb's law actually distinguishes between two types of friction: (1) Static Friction, and (2) Dynamic Friction. Box2D-Lite does not distinguish between these two types of frictions, but I have set out a brief description of the two, below.


Static Friction


Static friction is defined as the frictional force which acts between the two surfaces when they are in the rest position with respect to each other. Mathematically, static friction is defined as,


Fs = μsη

where,

  • Fs is the Static Frictional Force,

  • μs is the coefficient of static friction,

  • η is the Normal force (= mg),


The magnitude is greater than dynamic friction because it has a greater value of the coefficient. It has maximum magnitude as long as the applied force does not exceed its maximum and the surfaces will not move. It depends on the magnitude of the applied force.


Dynamic Friction


Dynamic friction is defined as the frictional force which is created between any two surfaces when they are in a moving position. It is also called Kinetic Friction. Mathematically, kinetic friction is defined as,


Fk = μkη

where,

  • Fk is the kinetic Frictional Force,

  • μk is the coefficient of kinetic friction,

  • η is the Normal force (= mg),


The magnitude is less than static friction because it has a lesser value of the coefficient. It has a constant magnitude regardless of the speed at which the two objects are moving. It is independent of the magnitude of the applied force.


Implementation


In the previous post I explained that in order to make the bodies stop penetrating, we applied an impulse along the collision normal such as to negate the relative velocity along the normal at the point of contact. In simulating friction we can apply the same logic as the contact constraint but with the contact normals replaced by contact tangents.


Applying impulses along the collision tangent


The logic is similar to the collision response calculation:


  1. calculate the relative velocity at the point of contact

  2. project the above onto the collision tangent to get the relative velocity along the tangent via an appropriate Jacobian

  3. divide the above by the effective mass to calculate the appropriate impulse. If the impulse is applied in full, you can kill off the tangential relative velocity to make the bodies stop completely. If a fraction (ie friction coefficient) is applied, then you "reduce" the relative velocity along the tangent.


The following section of the preStep method of the Arbiter class (from the original Box2D code) is calculating the collision tangent and the effective normal (note this is not the same as the effective mass applicable to the collision response impulse)


	Vec2 tangent = Cross(c->normal, 1.0f);
		float rt1 = Dot(r1, tangent);
		float rt2 = Dot(r2, tangent);
		float kTangent = body1->invMass + body2->invMass;
		kTangent += body1->invI * (Dot(r1, r1) - rt1 * rt1) + body2->invI * (Dot(r2, r2) - rt2 * rt2);
		c->massTangent = 1.0f /  kTangent;

The red highlighted code is slightly confusing as it appears to be taking a cross product of the normal against 1, a scalar. Looking at the Box2D implementation of Cross product reveals the following.


inline float Cross(const Vec2& a, const Vec2& b)
{
	return a.x * b.y - a.y * b.x;
}

inline Vec2 Cross(const Vec2& a, float s)
{
	return Vec2(s * a.y, -s * a.x);
}

inline Vec2 Cross(float s, const Vec2& a)
{
	return Vec2(-s * a.y, s * a.x);
}

You can see that there are two versions of the cross product that take a vector and scalar. The one that we want in this particular situation is the version highlighted in red (the one that takes the vector as the first parameter and the scalar as the second parameter). Both versions return the vector rotated by 90 degrees, then scaled by the scalar. However, the two version rotate the vector in different directions.


By passing 1 as the scalar parameter, you rotate the normal vector by 90 degrees without changing its magnitude (it is a unit vector), thereby giving you the collision tangent.


Be careful about the direction and signs!!!

As touched upon in my posts relating to collision responses, it is critically important to be aware of:

  • direction of the collision normal

  • direction of the collision tangent (ie the direction in which the normal is rotated)

  • the "sign" of the penetration (some engines use positive to denote penetration, whereas some engines use negative to denote penetration)

The above will (obviously) affect the direction of the impulse, and hence dictate whether you should add or subtract the impulse to the respective bodies.


Calculate the effective mass

The blue highlighted code calculates the effective mass - this is the same as the collision normal effective mass, except the collision normal has been replaced by the collision tangent.





Coefficient of friction

The coefficient of friction is defined for each body, when a body is first instantiated (default is 0.2). However, what is the coefficient of friction of two bodies, if the two bodies exhibit different coefficient of friction? This depends on the material of the pair of objects and apparently there is no one formula that accurately simulates what happens in the real world. There are various ways to "estimate" the friction of two bodies, including:


  • Simple Average: the average of the two coefficients.

  • Minimum: the minimum among the two coefficients.

  • Multiply: the product of the two coefficients.

  • Maximum: the maximum among the two coefficients.


Box2D-Lite takes the square root of the product of the respective friction coefficients, which is seen to give good results. This is calculated when the Arbiter object is instantiated.


friction = sqrtf(body1->friction * body2->friction);

Accumulation of impulses

As with the collision response impulses explored in the previous post, the impulses along the tangent is also accumulated and clamped.




So we can calculate the friction impulse as follows.




The relevant part of the Arbiter class, ApplyImpuse method is reproduced below. This should be relatively easy to follow, as it is very similar to the collision normal impulse code.


	// Relative velocity at contact
		dv = b2->velocity + Cross(b2->angularVelocity, c->r2) - b1->velocity - Cross(b1->angularVelocity, c->r1);

		Vec2 tangent = Cross(c->normal, 1.0f);
		float vt = Dot(dv, tangent);
		float dPt = c->massTangent * (-vt);

		if (World::accumulateImpulses)
		{
			// Compute friction impulse
			float maxPt = friction * c->Pn;

			// Clamp friction
			float oldTangentImpulse = c->Pt;
			c->Pt = Clamp(oldTangentImpulse + dPt, -maxPt, maxPt);
			dPt = c->Pt - oldTangentImpulse;
		}
		else
		{
			float maxPt = friction * dPn;
			dPt = Clamp(dPt, -maxPt, maxPt);
		}

		// Apply contact impulse
		Vec2 Pt = dPt * tangent;

		b1->velocity -= b1->invMass * Pt;
		b1->angularVelocity -= b1->invI * Cross(c->r1, Pt);

		b2->velocity += b2->invMass * Pt;
		b2->angularVelocity += b2->invI * Cross(c->r2, Pt);

CodePen




Useful References







29 views0 comments
記事: Blog2_Post
bottom of page