{"id":101594,"date":"2019-10-10T19:07:00","date_gmt":"2019-10-10T19:07:00","guid":{"rendered":"http:\/\/www.gamasutra.com\/view\/news\/352018"},"modified":"2019-10-10T19:07:00","modified_gmt":"2019-10-10T19:07:00","slug":"dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate","status":"publish","type":"post","link":"https:\/\/sickgaming.net\/blog\/2019\/10\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate\/","title":{"rendered":"Don&#8217;t Miss: An in-depth look at the physics of trains in Assassin&#8217;s Creed Syndicate"},"content":{"rendered":"<p><strong><i><small> The following blog post, unless otherwise noted, was written by a member of Gamasutra\u0092s community.<br \/>The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company. <\/small><\/i><\/strong> <\/p>\n<hr>\n<p>In this article, I would like to present our custom simulator which we created to model the physics of trains in Assassin&#8217;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.<\/p>\n<h2><strong>Introduction<\/strong><\/h2>\n<p>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<sup>th<\/sup>-century London.<\/p>\n<p>The standard coupling system for trains in Europe is presented in figure 1 on the left. The same system was used in 19<sup>th<\/sup>-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.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate.png\"><\/p>\n<p><strong>Figure 1<\/strong>. Chain coupler details on the left (source: Wikipedia [1]). The coupling system in Assassin\u2019s Creed Syndicate on the right.<\/p>\n<p>There are a couple of advantages for our own physics solution in this case:<\/p>\n<ul>\n<li>A curved railway track is easier to manage with the 1D simulator. Having to force the 3D physics middleware to use constraints to limit the movement into the one-dimensional space is rather a risky solution. It could be very prone to every possible instability causing wagons to fly in the air. However, we still wanted to detect collisions between wagons in a full 3D space.<\/li>\n<li>Movable coupling chain gives more freedom in gameplay design. In comparison to the real world, we need much more distance between wagons. This is to have more space for the player and the camera to perform different actions (like climbing to the top of the wagon). Also, our coupling chain is much less tightly connected than in the real world, so we have more free relative movement between wagons. It allows us to handle sharp curves of the railway lines more easily, while collision detection between wagons prevents from interpenetration.<\/li>\n<li>With our system we can easily support wagon\u2019s decoupling (with special handling of friction) and collisions between decoupled wagons and the rest of the train (for example when the train stops suddenly and decoupled wagons are still rolling finally hitting the train).<\/li>\n<\/ul>\n<p>Here is the video with our physics of trains in action:<\/p>\n<p>[embedded content]<\/p>\n<p>We will start with the section explaining first how we control our trains.<\/p>\n<p><u>Note<\/u>:<\/p>\n<p>To simplify our discussion, we use the word \u201ctractor\u201d to describe a wagon closer to the locomotive and the word \u201ctrailer\u201d to describe a wagon closer to the end of the train.<\/p>\n<h2><strong>Controlling locomotive<\/strong><\/h2>\n<p>We have a very simple interface to control the locomotive \u2013 which consists of requesting a desired speed:<\/p>\n<pre><code class=\"language-cpp\">Locomotive::SetDesiredSpeed(float DesiredSpeed, float TimeToReachDesiredSpeed)<\/code><\/pre>\n<p>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\u2019s second law):<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate.gif\"><\/p>\n<p>where <em>F<\/em> is computed force, <em>m<\/em> is the locomotive\u2019s mass, <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-1.gif\">, and <em>t = TimeToReachDesiredSpeed<\/em>.<\/p>\n<p>Once the force is calculated, we send it to <em>WagonPhysicsState<\/em> as an \u201cengine force\u201d to drive the locomotive (more information about it in the next section).<\/p>\n<p>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 <em>TimeToReachDesiredSpeed<\/em> exactly. However, small deviations in time were acceptable in our game.<\/p>\n<p>Also, to keep the speed of the locomotive as given by <em>SetDesiredSpeed<\/em> 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 \u2013 more about it in the section \u201cthe start-up of the train\u201d. Finally, we do not allow collision response to modify the speed of the locomotive except when the train decelerates to a zero speed.<\/p>\n<p>In the next section, we describe our basic level of the physical simulation.<\/p>\n<h2><strong>Basic simulation step<\/strong><\/h2>\n<p>This is a structure used to keep physical information about every wagon (and locomotive):<\/p>\n<pre><code class=\"language-cpp\">struct WagonPhysicsState { \/\/ Values advanced during integration: \/\/ distance along the track and momentum. RailwayTrack m_Track; float m_LinearMomentum; \/\/ Speed is calculated from momentum. float m_LinearSpeed; \/\/ Current value of forces. float m_EngineForce; float m_FrictionForce; \/\/ World position and rotation obtained directly from the railway track. Vector m_WorldPosition; Quaternion m_WorldRotation; \/\/ Constant during the simulation: float m_Mass; }<\/code><\/pre>\n<p>As we can see there is no angular velocity. Even if we check&nbsp;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).<\/p>\n<p>For every wagon we use Euler method [2] as a basic simulation step (<em>dt<\/em> is the time for one simulation step):<\/p>\n<pre><code class=\"language-cpp\">void WagonPhysicsState::BasicSimulationStep(float dt) { \/\/ Calculate derivatives. float dPosition = m_LinearSpeed; float dLinearMomentum = m_EngineForce + m_FrictionForce; \/\/ Update momentum. m_LinearMomentum += dLinearMomentum*dt; m_LinearSpeed = m_LinearMomentum \/ m_Mass; \/\/ Update position. float DistanceToTravelDuringThisStep = dPosition*dt; m_Track.MoveAlongSpline( DistanceToTravelDuringThisStep ); \/\/ Obtain new position and rotation from the railway line. m_WorldPosition = m_Track.GetCurrentWorldPosition(); m_WorldRotation = m_Track.AlignToSpline(); }<\/code><\/pre>\n<p>We use three main equations to implement our <em>BasicSimulationStep<\/em>. 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 &#8211; 4]:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-2.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-3.gif\"><\/p>\n<p>The third equation defines momentum <em>P<\/em>, which is a multiplication of mass and velocity:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-4.gif\"><\/p>\n<p>In our implementation, applying an impulse to the wagon is just an addition operation to the current momentum:<\/p>\n<pre><code class=\"language-cpp\">void WagonPhysicsState::ApplyImpulse(float AmountOfImpulse) { m_LinearMomentum += AmountOfImpulse; m_LinearSpeed = m_LinearMomentum \/ m_Mass; }<\/code><\/pre>\n<p>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].<\/p>\n<p>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.<\/p>\n<h2><strong>High-level steps of the simulation for one train<\/strong><\/h2>\n<p>Here is the pseudo code for the full simulation step for one train:<\/p>\n<pre><code class=\"language-cpp\">\/\/ Part A Update train start-up velocities \/\/ Part B For all wagons in train ApplyDeferredImpulses \/\/ Part C For all wagons in train UpdateCouplingChainConstraint \/\/ Part D For all wagons in train UpdateEngineAndFrictionForces SimulationStepWithFindCollision CollisionResponse<\/code><\/pre>\n<p>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 \u2013 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.<\/p>\n<p>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 \u2013 to consecutively simulate every wagon\u2019s movement and check collisions only with one other wagon.<\/p>\n<p>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 <em>SimulationStepWithFindCollision<\/em>.<\/p>\n<h2><strong>Simulation with collisions<\/strong><\/h2>\n<p>Here is the code for our function <em>SimulationStepWithFindCollision<\/em>:<\/p>\n<pre><code class=\"language-cpp\">WagonPhysicsState SimulationStepWithFindCollision(WagonPhysicsState InitialState, float dt) { WagonPhysicsState NewState = InitialState; NewState.BasicSimulationStep( dt ); bool IsCollision = IsCollisionWithWagonAheadOrBehind( NewState ); if (!IsCollision) { return NewState; } return FindCollision(InitialState, dt); }<\/code><\/pre>\n<p>First, we perform tentative simulation step using the full delta time by calling<\/p>\n<pre><code class=\"language-cpp\">NewState.BasicSimulationStep( dt );<\/code><\/pre>\n<p>and checking if in a new state we have any collisions:<\/p>\n<pre><code class=\"language-cpp\">bool IsCollision = IsCollisionWithWagonAheadOrBehind( NewState );<\/code><\/pre>\n<p>If this method returns false, we can use this newly computed state directly. But if we have a collision, we execute <em>FindCollision<\/em> 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].<\/p>\n<p>This is our loop to find the more precise time of collision and physics state:<\/p>\n<pre><code class=\"language-cpp\">WagonPhysicsState FindCollision(WagonPhysicsState CurrentPhysicsState, float TimeToSimulate) { WagonPhysicsState Result = CurrentPhysicsState; float MinTime = 0.0f; float MaxTime = TimeToSimulate; for (int step = 0 ; step&lt;MAX_STEPS ; ++step) { float TestedTime = (MinTime + MaxTime) * 0.5f; WagonPhysicsState TestedPhysicsState = CurrentPhysicsState; TestedPhysicsState.BasicSimulationStep(TestedTime); if (IsCollisionWithWagonAheadOrBehind(TestedPhysicsState)) { MaxTime = TestedTime; } else { MinTime = TestedTime; Result = TestedPhysicsState; } } return Result; }<\/code><\/pre>\n<p>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 <em>IsCollisionWithWagonAheadOrBehind<\/em> uses collision test between two oriented bounding boxes (OBB) to provide the result. We are checking collisions in a full 3D space using <em>m_WorldPosition<\/em> and <em>m_WorldRotation<\/em> from <em>WagonPhysicsState<\/em>.<\/p>\n<h2><strong>Collision response<\/strong><\/h2>\n<p>Once we have found the state of physics just before the collision event, we need to calculate appropriate reaction impulse <em>j<\/em> to apply it to both tractor and trailer wagons. First, we start with a calculation for current relative velocity between wagons before the collision:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-5.gif\"><\/p>\n<p>A similar value of relative velocity <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-6.gif\">&nbsp;but after the collision event:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-7.gif\"><\/p>\n<p>where <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-8.gif\">&nbsp;and <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-9.gif\">&nbsp;are velocities after the collision response impulse <em>j<\/em> is applied. These velocities can be calculated using velocities from before the collision and our impulse <em>j<\/em> as follows (<img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-10.gif\">&nbsp;and <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-11.gif\">&nbsp;are wagon\u2019s masses):<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-12.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-13.gif\"><\/p>\n<p>We are ready now to define the coefficient of restitution <em>r<\/em>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-14.gif\"><\/p>\n<p>The coefficient of restitution describes how \u201cbouncy\u201d the collision response is. Value <em>r = 0<\/em> means a total loss of energy, value <em>r = 1<\/em> means no loss of energy (perfect bounce). Substituting into this equation our previous formulas we get<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-15.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-16.gif\"><\/p>\n<p>Organizing this equation to get our impulse <em>j<\/em>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-17.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-18.gif\"><\/p>\n<p>Finally, we can calculate our impulse <em>j<\/em>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-19.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-20.gif\"><\/p>\n<p>In our game, we use <em>r = 0.35<\/em> as the coefficient of restitution.<\/p>\n<p>We apply impulse <em>+j<\/em> to the tractor and impulse <em>-j<\/em> to the trailer. However, we use \u201cdeferred\u201d 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 \u201cdeferred\u201d impulse is collected for the wagon and applied during part B in the next simulation frame.<\/p>\n<p>A video showcasing the stop of the train:<\/p>\n<p>[embedded content]<\/p>\n<h2><strong>Coupling chain<\/strong><\/h2>\n<p>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.<\/p>\n<p>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):<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-21.gif\"><\/p>\n<p>where <em>x<\/em> is our distance to travel, <em>V<\/em> is current velocity and <em>t<\/em> is the simulation step time.<\/p>\n<p>Then, we calculate the formula:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-22.gif\"><\/p>\n<p>where:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-23.gif\">&nbsp;= distance the tractor will travel during upcoming simulation step.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-24.gif\">&nbsp;= distance the trailer will travel during upcoming simulation step.<\/p>\n<p>If <em>FutureChainLength<\/em> 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<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-25.gif\"><\/p>\n<p>If distance constraint is broken, <em>d<\/em> value will be positive. In such case, to satisfy our distance constraint we need to apply such impulses that <em>d = 0<\/em>. We will use the wagon\u2019s mass to scale appropriate impulses. We want the lighter wagon to move farther and the heavier wagon to move less. Let us define coefficients <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-26.gif\">&nbsp;and <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-27.gif\">&nbsp;as follows<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-28.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-29.gif\"><\/p>\n<p>Please notice that <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-30.gif\">. We want the trailer to move with the additional distance <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-31.gif\">&nbsp;and the tractor with the distance <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-32.gif\">&nbsp;during the next simulation step. To accomplish it by applying an impulse we need to multiply the given distance by mass divided by the simulation step time:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-33.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-34.gif\"><\/p>\n<p>If we will use additional symbol <em>C<\/em> defined as follows<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-35.gif\"><\/p>\n<p>we can simplify these impulses to<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-36.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-37.gif\"><\/p>\n<p>We can see that they have equal magnitude but the opposite sign.<\/p>\n<p>After applying both impulses, wagons connected with this coupling chain will not break the distance constraint during the next simulation step. These impulses modify velocities in such way that integration formulas will end up at positions satisfying the maximum distance for the chain.<\/p>\n<p>Still, after computing these impulses for one coupling chain, we can possibly break the maximum chain distance for other wagons in a train. We would need to rerun this method several times to converge to the final solution. However, in practice, we run this loop just once. It is enough to achieve good global results.<\/p>\n<p>We execute these calculations consecutively for every coupling chain in a train starting from the locomotive. We always apply impulses to both wagons connected with the chain. But there is one exception to this rule: we never apply an impulse to the locomotive. We want the locomotive to keep its speed, so we apply impulse only to the first wagon after the locomotive. This impulse applied only to the trailer needs to compensate for the whole required distance <em>d <\/em>(in such case we have <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-38.gif\">, <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-39.gif\">&nbsp;and <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-40.gif\">).<\/p>\n<h2><strong>Correction during sharp curves<\/strong><\/h2>\n<p>Because our simulation runs along the 1D line we have problems with a perfect fit for the coupling chain on the hook when wagons are running on a sharp curve. This is the situation when our 1D world meets 3D game world. Our coupling chain is finally placed in the 3D world, but our impulses (to compensate for the distance constraint) are applied only in our simplified 1D world. To correct the final placement of the chain on the hook we slightly modify <em>MaximumLengthOfCouplingChain<\/em> depending on the relative angle between directions of the tractor and the chain. Bigger the angle, smaller the maximum available length of the chain. First, we compute dot product between two normalized vectors:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-41.gif\"><\/p>\n<p>where <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-42.gif\">&nbsp;is the normalized direction of the coupling chain and <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-43.gif\">&nbsp;is the forward direction of the tractor. Then, we use the following formula to finally compute the distance we want to subtract from the physical length of the coupling chain:<\/p>\n<pre><code class=\"language-cpp\">float DistanceConvertedFromCosAngle = 2.0f*clamp( (1.0f-s)-0.001f, 0.0f, 1.0f ); float DistanceSubtract = clamp( DistanceConvertedFromCosAngle, 0.0f, 0.9f );<\/code><\/pre>\n<p>As you can see we do not calculate the exact value of the angle, as we use cosine angle directly. It saves us some processing time and is sufficient for our needs. We also use some additional numbers, based on empirical tests \u2013 to limit values within reasonable thresholds. Finally, we use <em>DistanceSubtract<\/em> value before starting to satisfy the distance constraint for the coupling chain:<\/p>\n<pre><code class=\"language-cpp\">MaximumLengthOfCouplingChain = ChainPhysicalLength - DistanceSubtract;<\/code><\/pre>\n<p>It turns out that these formulas work very well in practice. It makes our coupling chain hanging correctly on the hook even on sharp turnings along the railway curves.<\/p>\n<p>Now, we will describe specific case of the start-up of the train.<\/p>\n<h2><strong>The start-up of the train<\/strong><\/h2>\n<p>As mentioned before, we are not allowing the coupling chain impulses to change the speed of the locomotive. However, we still need a way to simulate effects of a dragging force &#8211; especially during the start-up of the train. When locomotive starts it drags other wagons, but also the locomotive itself should slow down according to the dragging mass of wagons. To achieve this, we modify velocities when the train accelerates from a zero speed. We start with calculations based on the law of conservation of momentum. This law states that \u201cthe momentum of a system is constant unless external forces act on that system\u201d [3]. It means that in our case the momentum <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-44.gif\">&nbsp;before dragging another wagon, should be equal to the momentum <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-45.gif\">&nbsp;just after the coupling chain is pulling another wagon:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-46.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-47.gif\"><\/p>\n<p>In our case, we can expand it to the following formula:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-48.gif\"><\/p>\n<p>where <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-49.gif\">&nbsp;is the mass of the i-th wagon (<img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-50.gif\">&nbsp;is the mass of the locomotive), <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-51.gif\">&nbsp;is the current speed of the locomotive (we assume that all already moving wagons have the same speed as the locomotive), <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-52.gif\">&nbsp;is the speed of the system after the dragging (we assume that all dragged wagons will have the same speed). If we use additional symbol <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-53.gif\">&nbsp;defined as follows<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-54.gif\"><\/p>\n<p>we can simplify our formula in this way<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-55.gif\"><\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-52.gif\">&nbsp;is the value we are looking for:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-56.gif\"><\/p>\n<p>Using this formula, we simply set the new velocity <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-52.gif\">&nbsp;for the locomotive and for all wagons (from 2 to n) currently being dragged by the coupling chain.<br \/> In figure 2 below we can see the schematic description when the locomotive <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-50.gif\">&nbsp;and two wagons start to drag the third wagon <img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-57.gif\">:<\/p>\n<p align=\"center\"><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-1.png\"><\/p>\n<p align=\"center\"><strong>Figure 2<\/strong>. The start-up of the train.<\/p>\n<p>Here is the video with the start-up of the train:<\/p>\n<p>[embedded content]<\/p>\n<h2><strong>Friction<\/strong><\/h2>\n<p>To compute friction force (variable <em>m_FrictionForce<\/em> in <em>WagonPhysicsState<\/em>) we are using formulas and values chosen after a series of experiments to better support our gameplay. We have constant friction force value, but additionally, we are scaling it according to the current speed (when the speed is below 4). Here is the graph of our standard friction force for wagons:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-2.png\"><\/p>\n<p align=\"center\"><strong>Figure 3<\/strong>. The standard friction force for wagons.<\/p>\n<p>We use different friction values for detached wagons:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-3.png\"><\/p>\n<p align=\"center\"><strong>Figure 4<\/strong>. The friction force for detached wagons.<\/p>\n<p>Additionally, we want to allow the player to easily jump between wagons during a short amount of time after the detaching. So, we use a smaller value of the friction and we scale it with the time passing from the detaching event. The final value of the friction for detached wagons is given by:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.sickgaming.net\/blog\/wp-content\/uploads\/2019\/10\/dont-miss-an-in-depth-look-at-the-physics-of-trains-in-assassins-creed-syndicate-58.gif\"><\/p>\n<p>where <em>t<\/em> is time passed from the detaching event (measured in seconds).<\/p>\n<p>As we can see, we use no friction during the first 3 seconds and then gradually increase it.<\/p>\n<h2><strong>Final remarks<\/strong><\/h2>\n<p>In our trains, we also have movable bumpers at the front and the back of every wagon. These bumpers do not generate any physical forces. We implemented their behavior as an additional visual element. They move according to the detected displacement of a neighbor bumper in another wagon.<\/p>\n<p>Also, as you can notice, we are not checking collisions between different trains in our simulator. It is the responsibility of the railway system manager to adjust trains speed to prevent collisions. In our simulation, we check collisions between wagons only within one train.<\/p>\n<p>It is important to mention that for the high-quality perception of trains in the game sounds and special effects play a very important role. We are calculating different quantities derived from the physical behaviors to control sounds and FXs (like sounds for the tension of the coupling chain, bumpers hit, deceleration, etc.).&nbsp;<\/p>\n<h2><strong>Summary<\/strong><\/h2>\n<p>We presented our custom physically-based simulator for trains created for Assassin\u2019s Creed Syndicate. It was a great pleasure and a big challenge to work on this part of the game. In the open-world experience, there are a lot of gameplay opportunities and different interactions. It creates even more challenges to deliver stable and robust systems. But in the end, it is very rewarding to observe trains running in the game and contributing to the final quality of the player\u2019s experience.<\/p>\n<h2><strong>Thanks<\/strong><\/h2>\n<p>I would like to thank James Carnahan from Ubisoft Quebec City and Nobuyuki Miura from Ubisoft Singapore for reviewing this article and useful advice.<\/p>\n<p>I would like to thank my colleagues at Ubisoft Quebec City studio: Pierre Fortin &#8211; who let me start with the physics of trains and inspired to push it forward; Dave Tremblay for his technical advice; James Carnahan for every talk about physics we did together; Matthieu Pierrot for his inspiring attitude; Maxime Begin who was always happy to start a talk about programming with me; Vincent Martineau for every help I have received from him. I would like also to thank Martin Bedard, Marc Parenteau, Jonathan Gendron, Carl Dumont, Patrick Charland, Emil Uddestrand, Damien Bastian, Eric Martel, Steve Blezy, Patrick Legare, Guillaume Lupien, Eric Girard and every other person who worked&nbsp;on Assassin\u2019s Creed Syndicate for making such incredible game!<\/p>\n<h2><strong>References<\/strong><\/h2>\n<p>[1] \u201cBuffers and chain coupler\u201d, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Buffers_and_chain_coupler\">https:\/\/en.wikipedia.org\/wiki\/Buffers_and_chain_coupler<\/a><\/p>\n<p>[2] Andrew Witkin, David Baraff and Michael Kass, \u201cAn Introduction to Physically Based Modeling\u201d,&nbsp;<a href=\"http:\/\/www.cs.cmu.edu\/~baraff\/pbm\/\">http:\/\/www.cs.cmu.edu\/~baraff\/pbm\/<\/a><\/p>\n<p>[3] Fletcher Dunn, Ian Parberry, \u201c3D Math Primer for Graphics and Game Development, Second Edition\u201d, CRC Press, Taylor &amp; Francis Group, 2011.<\/p>\n<p>[4] David H. Eberly, \u201cGame Physics. Second Edition\u201d, Morgan Kaufmann, Elsevier, 2010.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The following blog post, unless otherwise noted, was written by a member of Gamasutra\u0092s community.The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company. In this article, I would like to present our custom simulator which we created to model the physics of trains in Assassin&#8217;s Creed Syndicate. [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":101595,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[],"class_list":["post-101594","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-news"],"_links":{"self":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/101594","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/comments?post=101594"}],"version-history":[{"count":0,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/posts\/101594\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media\/101595"}],"wp:attachment":[{"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/media?parent=101594"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/categories?post=101594"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sickgaming.net\/blog\/wp-json\/wp\/v2\/tags?post=101594"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}