Home United States USA — software The physics of trains in Assassin's Creed Syndicate

The physics of trains in Assassin's Creed Syndicate

293
0
SHARE

Bartlomiej Waszak is a gameplay engineer at Ubisoft Quebec. In this article, he presents details of the simulator for the physics of trains in Assassi
In this article, I would like to present our custom simulator which we created to model the physics of trains in Assassin’s Creed Syndicate. The game is set in the year 1868 in London during the times of the industrial revolution when steam and steel marked the progress of the society. It was a great pleasure to work on this unique opportunity of bringing to life the world of Victorian Era London. Attention to the historical and the real-world details led us to the creation of this physically-based simulation.
It is not common these days to write your own physics engine. However, there are situations when it is very useful to create your own physical simulator from the ground up. Such situations might occur when there are specific conditions or needs for a new gameplay feature or a part of the simulated game world. This is the situation which we came across when developing railway system and the whole management of trains running in the 19 th -century London.
The standard coupling system for trains in Europe is presented in figure 1 on the left. The same system was used in 19 th -century trains in London [1]. When we started our work on trains we quickly realized that we can create interesting interactions and behaviors when physically simulating the chain. So instead of having rigidly connected wagons, we have them connected with the movable coupling chain which drives the movement for all wagons in the train.
Figure 1. Chain coupler details on the left (source: Wikipedia [1]). The coupling system in Assassin’s Creed Syndicate on the right.
There are a couple of advantages for our own physics solution in this case:
Here is the video with our physics of trains in action:
We will start with the section explaining first how we control our trains.
Note:
To simplify our discussion, we use the word “tractor” to describe a wagon closer to the locomotive and the word “trailer” to describe a wagon closer to the end of the train.
We have a very simple interface to control the locomotive – which consists of requesting a desired speed:
Railway system manager submits such requests for every train running in the game. To execute the request, we calculate a force needed to generate desired acceleration. We use the following formula (Newton’s second law):
where F is computed force, m is the locomotive’s mass,, and t = TimeToReachDesiredSpeed.
Once the force is calculated, we send it to WagonPhysicsState as an “engine force” to drive the locomotive (more information about it in the next section).
Because the physical behavior of the train can depend for example on the number of wagons (wagons colliding with each other creating a chain reaction and pushing the train forward), we need a way to ensure that our desired speed request once submitted is fully executed. To achieve this, we re-evaluate the force needed to reach desired speed every 2 seconds. This way we are sure that the request once submitted is finally reached. But as a result, we are not able to always satisfy TimeToReachDesiredSpeed exactly. However, small deviations in time were acceptable in our game.
Also, to keep the speed of the locomotive as given by SetDesiredSpeed request, we do not allow the coupling chain constraint to change the speed of the locomotive. To compensate the lack of such constraint impulses, we created a special method to model the dragging force – more about it in the section “the start-up of the train”. Finally, we do not allow collision response to modify the speed of the locomotive except when the train decelerates to a zero speed.
In the next section, we describe our basic level of the physical simulation.
This is a structure used to keep physical information about every wagon (and locomotive):
As we can see there is no angular velocity. Even if we check collisions between wagons using 3D boxes (with rotation always aligned to the railway line) trains are moving in the 1D world along the railway line. So there is no need to keep any information about the angular movement for the physics. Also, because of the 1D nature of our simulation, it is enough to use floats to store physical quantities (forces, momentum and speed).
For every wagon we use Euler method [2] as a basic simulation step ( dt is the time for one simulation step):
We use three main equations to implement our BasicSimulationStep. These equations state that velocity is a derivative of position and force is a derivative of momentum (dot above the symbol indicate derivative with respect to time) [2 – 4]:
The third equation defines momentum P, which is a multiplication of mass and velocity:
In our implementation, applying an impulse to the wagon is just an addition operation to the current momentum:
As we can see, immediately after changing momentum we are recalculating our speed for an easier access to this value. This is done in the same way as in [2].
Now, when we have the basic method to advance the time in our simulation, we can move forward to the other parts of our algorithm.
Here is the pseudo code for the full simulation step for one train:
It is important to mention that, as it is written in the pseudo-code, every part is executed consecutively for all wagons in one train. Part A implements specific behavior related to the start-up of the train. Part B applies deferred impulses that come from collisions. Part C is our coupling chain solver – to be sure that we do not exceed maximum distance for the chain. Part D is responsible for engine and friction forces, the basic simulation step (integration) and handling collisions.
In our simulation algorithm, we always keep the same order of updates for wagons in the train. We start from the locomotive and proceed consecutively along every wagon from the first one to the last one in the train. Because we are able to use this specific property in our simulator, it makes our calculations easier to formulate. We use this characteristic especially for collision contact – to consecutively simulate every wagon’s movement and check collisions only with one other wagon.
Every part of this high-level simulation loop is explained in details in the following sections. However, because of its importance, we start with part D and SimulationStepWithFindCollision.
Here is the code for our function SimulationStepWithFindCollision:
First, we perform tentative simulation step using the full delta time by calling
and checking if in a new state we have any collisions:
If this method returns false, we can use this newly computed state directly. But if we have a collision, we execute FindCollision to find a more precise time and physics state just before the collision event. To perform this task we are using binary search in a similar manner as in [2].
This is our loop to find the more precise time of collision and physics state:
Every iteration gets us closer to the precise time of the collision. We also know that we need to check our collisions only with one wagon directly ahead of us (or behind us in a case of backward movement). Method IsCollisionWithWagonAheadOrBehind uses collision test between two oriented bounding boxes (OBB) to provide the result. We are checking collisions in a full 3D space using m_WorldPosition and m_WorldRotation from WagonPhysicsState.
Once we have found the state of physics just before the collision event, we need to calculate appropriate reaction impulse j to apply it to both tractor and trailer wagons. First, we start with a calculation for current relative velocity between wagons before the collision:
A similar value of relative velocity but after the collision event:
where and are velocities after the collision response impulse j is applied. These velocities can be calculated using velocities from before the collision and our impulse j as follows ( and are wagon’s masses):
We are ready now to define the coefficient of restitution r:
The coefficient of restitution describes how “bouncy” the collision response is. Value r = 0 means a total loss of energy, value r = 1 means no loss of energy (perfect bounce). Substituting into this equation our previous formulas we get
Organizing this equation to get our impulse j:
Finally, we can calculate our impulse j:
In our game, we use r = 0.35 as the coefficient of restitution.
We apply impulse +j to the tractor and impulse -j to the trailer. However, we use “deferred” impulses for the tractor. Because we already processed integration for our tractor and we do not want to change its current velocity, we defer our impulse to the next simulation frame. It does not create any significant change in visual behavior as one frame difference is very hard to notice. This “deferred” impulse is collected for the wagon and applied during part B in the next simulation frame.
A video showcasing the stop of the train:
We can think about the coupling chain as a distance constraint between wagons. To keep this distance constraint satisfied we compute and apply appropriate impulses to change velocities.
We start our calculations with a distance evaluation for the next simulation step. For every two wagons connected by a coupling chain, we calculate distances they will travel during the upcoming simulation step. We can compute such distance very easily using current velocity (and inspecting our integration equations):
where x is our distance to travel, V is current velocity and t is the simulation step time.
Then, we calculate the formula:
where:
= distance the tractor will travel during upcoming simulation step.
= distance the trailer will travel during upcoming simulation step.
If FutureChainLength is bigger than the maximum length of the coupling chain, then our distance constraint will be broken after the next simulation step. Let assume that
If distance constraint is broken, d value will be positive. In such case, to satisfy our distance constraint we need to apply such impulses that d = 0. We will use the wagon’s mass to scale appropriate impulses. We want the lighter wagon to move farther and the heavier wagon to move less. Let us define coefficients and as follows
Please notice that.

Continue reading...