How physics simulation in games works on the example of Bullet Physics



Many modern games use physical simulation. The first acquaintance with the physics engine is usually promising. The ball jumps from the cube and rolls downhill, but in the process, when the project is almost ready, it turns out that something does not work as you would like. Here you can ditch a lot of time.

And if you can’t do anything with Nvidia Physix (hi Unity3D) except for a couple of parameters, then Bullet Physics and Box2D are available in the source codes, and you start to understand how it works and how it works.

Understanding how everything works is very useful. All game physics engines are very similar. They are all impulse base, which means that in each simulation frame all (forces) are converted into pulses, the pulses are added, divided by mass and added to the velocities (linear and rotational) of the object, but the speed determines how much the object will move in the current simulation frame.

And then the question arises, if everything is so simple, how does it turn out that you can put the cubes in a column and they calmly stand and do not fly apart?

I will show that a little piece of code is responsible for all this magic!

I assume that the reader already knows that in the physical engine, there are two main types of simulated bodies: a rigid body (an elastic body that moves and has a masa) and a static body (a motionless body that does not have a mass and is motionless). Kinematic and soft body are not worth attention.

Collision detection


All magic begins when two bodies touch each other. Bullet Physics uses the term collision.

The system must determine all the bodies that touch each other in a given simulation frame. Each touch (collision) may involve 2 or more bodies. Finding all possible touches can be very resource intensive.

Broadphase Collision Detection

The first step is to find the intersection of the AABB boxes. AABB are two points that describe the box inside which the object lies. The idea is that if two objects intersect, then their AABB boxes intersect.

One of three algorithms solves this problem in BulletPhysics. They are implemented as classes that implement btBroadphaseInterface .

1. The most obvious and ineffective is btSimpleBroadphase, it is a simple enumeration of all O (n ^ 2) pairs.

2. btAxisSweep3works in a limited space. You specify an AABB box, inside which all objects are supposed to be. This may seem inconvenient, but due to the fact that floating-point numbers lose accuracy with increasing value, you still can’t simulate anything at a distance of a million units as if you were doing it at a distance of 10 units from the center of coordinates (the unit is usually considered a meter).

3. btDbvtBroadphase organizes all objects in two tree structures, one for static objects, the other for dynamic (rigid, kinematic) bodies.

Narrowphase Collision Detection

After the groups of objects whose AABB boxes are intersected are found, a check is made to see if there is an intersection in reality.

It should be remembered that all objects are given a shape (shape), maybe a ball, cylinder, box, convex polyhedron for rigid body or just a polyhedron for static body. There is also a composite form of the forms listed in the previous sentence.

For each pair, an algorithm for finding points and perpendiculars of intersection is defined.



Decision


Well, all the intersections are found, what to do next? Since our engine is impulse based, we need to calculate the momentum by which to shove the two touched objects so that everything looks realistic.

Then I promised that there would be a small piece of code. It seems to me two years ago it was generally three lines, but now it looks like this.

// Project Gauss Seidel or the equivalent Sequential Impulse 
 void btSequentialImpulseConstraintSolver::resolveSingleConstraintRowLowerLimit(btRigidBody& body1,btRigidBody& body2,const btSolverConstraint& c) 
{ 
	btScalar deltaImpulse = c.m_rhs-btScalar(c.m_appliedImpulse)*c.m_cfm; 
	const btScalar deltaVel1Dotn	=	c.m_contactNormal.dot(body1.internalGetDeltaLinearVelocity()) 	+ c.m_relpos1CrossNormal.dot(body1.internalGetDeltaAngularVelocity()); 
	const btScalar deltaVel2Dotn	=	-c.m_contactNormal.dot(body2.internalGetDeltaLinearVelocity()) + c.m_relpos2CrossNormal.dot(body2.internalGetDeltaAngularVelocity()); 
	deltaImpulse	-=	deltaVel1Dotn*c.m_jacDiagABInv; 
	deltaImpulse	-=	deltaVel2Dotn*c.m_jacDiagABInv; 
	const btScalar sum = btScalar(c.m_appliedImpulse) + deltaImpulse; 
	if (sum < c.m_lowerLimit) 
	{ 
		deltaImpulse = c.m_lowerLimit-c.m_appliedImpulse; 
		c.m_appliedImpulse = c.m_lowerLimit; 
	} 
	else 
	{ 
		c.m_appliedImpulse = sum; 
	} 
	body1.internalApplyImpulse(c.m_contactNormal*body1.internalGetInvMass(),c.m_angularComponentA,deltaImpulse); 
	body2.internalApplyImpulse(-c.m_contactNormal*body2.internalGetInvMass(),c.m_angularComponentB,deltaImpulse); 
}

At the entrance, we have two bodies that have touched, a point of contact with a perpendicular to the touch, and as a result we have two impulses that should resolve the conflict, make the boxes rest quietly and motionless on top of each other, and the cylinder and ball roll smoothly and realistically along an inclined one.

Useful advice instead of farewell


You should not touch this code. For example, if you are developing a game with a racing car, and the car will beat against the curb or the wall, and it will behave unpredictably and not playably, you should pay attention to the perpendicular at the point of contact. It is the inconstancy and unpredictability of the perpendicular at the point of contact that often determines the unpredictability of the simulation.

You looked into the soul of a modern physics engine and now you are obliged to marry him.

Also popular now: