The following blog post, unless otherwise noted, was written by a member of Gamasutras community.

The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

Jacques Kerner is a senior software engineer at Avalanche Studios.

As if it wasn't crazy enough before



Introduction

The Just Cause series and Avalanche Studios are known for their open world technology and offer one of the most varied and engaging open world experience in video games. The latest iteration, Just Cause 4, adds wind and extreme weather as notable new comers in the array of technologies that deepens the gameplay experience. But extreme weather, from its original design, was more than just a way to simulate a more believable world. It is the fury of nature controlled by the forces of evil cast against Rico Rodriguez. The design intent was also to make wind more present throughout the world so extreme weather wouldn't feel like an abrupt event not belonging to that world. This article presents the techniques we developed to realize wind in all its manifestations from a physical standpoint and how all objects were made to react to it.

JC4 Tropical storm - early concept art (Volta)

An offer we couldn't refuse

As Just Cause 3 was coming to an end, a big part of the development team rolled off to the pre-production of Just Cause 4, while a small nucleus remained to work on patching JC3 and working on downloadable content ("DLC"). Hamish Young and myself, lead vehicle designer and lead vehicle programmer at the time, focused on the Mech Land Assault DLC. We were to become the lead physics (and player mechanics) designer and lead physics programmer of JC4, respectively, but were completely engrossed in our DLC, while an entirely new iteration of the franchise was being designed, its unique selling points and back of the box features determined. At the forefront: extreme weather, in all its manifestations, playing a central part in the game. By this time, extensive prototyping had been done, proving out new core mechanics and how Rico would react to wind. Part of the back story was drafted, the early design direction approved by the publisher, Square Enix. The only thing left to do was to embrace the concept. But how to do it? Especially in a way that wouldn't jeopardize performance. As soon as we joined the project, we attacked the problem on two fronts: 1. institute some broad restrictions right away to avoid the worst case scenarios (spoiler, we failed) 2. understand the different manifestations of extreme weather, and extreme wind in particular, to design a system giving a realistic behavior but scaling well with the desired number and density of objects.

JC4 Sand storm - early concept art (Volta)

Damage control

A performance bottle neck for real time simulation, and open world games in particular, is the number of collidable physical bodies. The main cost comes from computing collision of the many moving bodies among themselves and between them and the static scenery (terrain, buildings). This is why physics engines such as Havok distinguish between active and inactive bodies. Active bodies are checked for collision against other bodies and incur a full cost. When an active body hasn't moved for a few frames the physics engine marks it as inactive and from then on it can be completely ignored until it gets woken up by an active body coming into proximity. Those inactive bodies are usually resting on ground and the collision check between them and the ground is no longer calculated. The presence of wind everywhere in the open world was an obvious threat to this and it was important to make it very clear that the wind needed to be either moderate and purely cosmetic in which case it could occur in large areas, or strong and physically activating, in which case it needed to be constrained to smaller volumes and found in locations where the number of potentially active objects was not too large. I communicated these restrictions to our design team early on to be sure we wouldn't put ourselves in a tricky situation. At first the design team seemed to be listening and decided to restrain the trajectories of extreme weather events to predefined paths of destruction within which more restrictive building rules would be followed, and some of these restrictions were indeed respected. But the temptation was so great, the peer pressure to blow everything up in a Just Cause game too irresistible. What was I thinking? The very next thing to happen was the design of a kilometers high tornado passing right through the capital city, the densest populated part of the island. This tornado sucked in not only half of that capital city but also a few unsuspecting programmers not firmly committed to another core feature.

JC4 Blizzard - early concept art (Volta)



The overall approach

Ultimately, the physics of extreme weather of JC4 required the following ingredients:

An aerodynamic drag model applying to all dynamic objects in the world taking into account their shape and dimension

Sources of wind, matching the desired shape and wind distribution of extreme weather events (a storm, a tornado) but also for authoring wind patterns around the world

Optimization of the above, to remain within budget

These three problems needed to be solved almost simultaneously, and as early as possible. A lot of the design decisions depended on being able to deliver on all three, and in the first months you could feel the tension from designers patiently waiting to be able to play with any of it on a large scale.

The aerodynamic drag model came first. After its initial submission, every object suddenly would react to wind in a somewhat realistic manner. For instance, when dropped from a cliff or falling back down from an explosion, all objects slowed down and rotated in air in a more realistic manner, which was worth it in itself. But with no source of wind to speak of it was hard to tell how well it was going to work in extreme wind. While we prided ourselves that experience and intuition helped quickly adopt an approach for the first two problems that would satisfy the performance requirements, we estimated ourselves pretty lucky when the first version of the tornado turned out to work exactly as expected, picking up all dynamic objects, swirling them around in a way that seemed realistic enough, without completely bringing the CPU to its knees. Francesco Antollini, Game Director of JC4, who had been asking at increasingly close intervals about the tornado, came by my desk almost immediately after its submission to express his deep relief: this was going to work - I guess I must have missed an opportunity to make him feel bad for ever doubting me or something along those lines; in reality I felt exactly the same.

JC4 had to push the envelope compared to JC3 in terms of physics: a denser, fuller, more lively and beautiful world, with more interesting destruction. All of this on exactly the same target platforms (XBox One and PlayStation 4). Add to this extreme weather and you had ... the perfect storm. It is no wonder that optimization work took a great part of our time. One advantage that Dave Barrett, our technical director, leveraged immediately was that we had a base line for performance with JC3 and Daniele Pieroni, lead of the engine team, was put in charge of determining what the performance and memory budgets should be for each technical discipline. Our budget for physics was a generous 8.5 ms on 4 threads out of 33 ms of CPU time available to simulate a frame, to reach 30 frames per second. The greatest part of this budget is reserved to Havok to detect possible collision between objects, compute contacts, solve constraints and "integrate" the motion of all active bodies to determine their position after 33 ms of simulated time. I estimated that our budget for all aerodynamic and wind related computation was therefore about 1 ms on 4 threads. We mostly kept within budget, although it's probably a good idea to not ask Daniele for his opinion on the matter. The truth is that the solution presented here is fast enough for real time that involves several hundreds of objects on modern CPUs. It could certainly be optimized much further by being adapted to run on GPUs, as many things are.

In the rest of this article I outline how we approached each problem, and confine the details or mathematical developments in appendices, for those interested.

JC4 Wind - early concept art (Ironklad Studios)



Drag model

Our goal for the drag model was to estimate the resistive forces and torques that vaguely resemble those that would be applied on a body of arbitrary shape in reality. Note how unambitious we are here, there is no claim to great realism. We did however have some requirements. First, the resistive forces should oppose the motion of the body. Second, the force should stem from the basic aerodynamic drag equation, i.e. scale with the surface area of the shape in contact with air and scale with the square of speed of motion through air. Finally, the shape and size of the body should come into play somehow, so that objects of the same surface area but very different shape behave differently. I was very tempted to just approximate every body's shape by its bounding box but I thought I needed to do better than that, as it was certain that the shape of a lot of objects would vary considerably from a box or even a small number of boxes. Previous experience with systems crudely approximating the volume of 3D objects with voxels or spheres pushed me to find a better solution. Some suggested we author coefficients of drag for each objects manually but I wanted to prevent adding yet another manual step to our already complicated pipeline. So I kept looking for an automated method. I'm glad I persevered.

The simplest method was to estimate a wind pressure on each face of the collision shape of our objects, due to incoming air, ignoring any sort of air circulation around the object or viscosity of the air surrounding it. As if the air drag simply consisted of pushing forward the mass of air in front of the object, or sucking it in on the receding side. An early prototype showed that this method was good enough for our purposes.

The "only" issue was that summing the contributions of thousands of triangles for every single shape at runtime was an order of magnitude too costly. We could easily reach more than 100,000 faces in heavy scenes. It would be great if we could precompute the forces beforehand. A first brute force method is to precompute the aerodynamic forces and torques due to the body translating at different speeds in stationary air in several directions and rotating at different angular speeds around different directions to build a look up table to be sampled from at runtime instead. Or you can imagine a virtual wind tunnel, operating at compilation time of our content, where we place each object in turn, and subject them to different translating or rotating air speeds, positioning each object at various angles and measuring the forces and torques each time.

What would this lookup table look like? At runtime, the known quantities are the linear and angular velocities of the body, so \(S=5\), only about 300 kB per object. It is still at least an order of magnitude over the memory consumption I could afford.

To reduce drastically the number of samples I used two techniques. Firstly, I linearized the computation into a sum of contributions based on precomputing the forces and torques on the purely translating body on one hand and the purely rotating body on the other. Secondly, I approximated it into an analytical formula for extrapolating the value at arbitrary speeds based on the precomputed response at a translating speed of 1 m/s and a rotating speed of 1 rad/s only. This reduced the precomputed table to a size of just 7 kB per object. Unfortunately this required making a few approximations. The issue stems from the fact that even with a simple model of drag we are facing the fact that the forces applied to a body that is both rotating and translating is not just the sum of the forces applied to it when just rotating and just translating. Even though I had to make some approximation, I managed to keep some of the terms due to the combination of rotating while translating motion, akin to a Coriolis term. You can refer to Appendix 1 for the derivation of the formulation. The end result is that instead of directly storing the forces and torques for each sample, we store a vector of terms \(\mathcal{A}(\hat{u})\) that appear in the linearized formula. Each sample corresponds to a unit linear velocity relative to air of 1 m/s or a unit angular velocity relative to air of 1 rad/s, in a certain direction. The components of this vector are used in the formula that also takes the actual linear and angular velocities of the object relative to air to compute the forces and torques to apply on that object. Appendix 1 explains exactly how.

To store the look up table we used a cube map, i.e. a cube which faces are divided into grids of, say, 3x3 cells, like a Rubik's cube. We store values at the corners of the cells so each face stores 4x4 values. At each cell corner we calculate the vector going from the center of the cube to the corner, and we normalize that vector to get a unit direction. We use this unit direction vector as \(\mathcal{A}(\hat{u})\) for the cell corner. We end up with 6 tables (one per face) of 16 entries with 19 floats each, just a little over 7 kB, very manageable. There are ways to reduce the number of samples even further, but this way makes it very easy to interpolate an arbitrary velocity direction: since it always falls on a cell, it is always possible to use the values at the four corners of that cell to do a bilinear interpolation for that particular direction. The cube map technique and bilinear interpolation are very widely used in rendering so there are ample resources and code available.





Figure 1 - Cube mapping of unit velocities relative to air. On this figure I have isolated in red a particular cell of the cube map to illustrate the principle. Given the linear velocity \(\hat{\omega}\) relative to air, we sample the same cube map again to find the blue cell and use the 4 coefficients stored at its corners. We then mix all the values into the formula of Appendix 1.

While this worked well in most cases, we encountered a few complications regarding changing the center of mass or scale of objects at runtime. Our solutions to these problems are found in Appendices 2 and 3.

While developing this model, I wasn't certain about the amount of sampling needed to get a good behavior of objects moving and rotating in air, so I wasn't sure I would have enough memory on disk or at runtime. We also started building more Havok destruction assets which have hundreds of fracture debris of various sizes, sometimes several hundred pieces for a single asset, and it dawned on me that a few of these assets would require as much memory as all other assets combined if every fragment was to come with its own cube map. The general model was good to capture some of the unique characteristics of complex objects but overkill for smaller debris. I also wasn't sure about the cost at runtime and having the option to fall back on a cheaper model was a good way to limit the risk ahead of me. So I found a very compact formulation (making generous approximations) solely based on the bounding box of objects, which you can find in Appendix 4.

Wind volumes

We introduced the concept of wind volume to cement the idea that strong, physically activating wind was only occurring in finite spaces, and we started classifying them by shape and wind distribution. We distinguished between the following main types of wind volumes:

Cylindrical wind volume used for the blizzard, sandstorm and tropical storms

The tornado wind volume, a vertical stack of cylinders centered around a vertical spline deforming over time

Wind tunnels composed of a series of connected capsuloids (capsules with different radii on either end) forming a sort of wormhole

This restriction to a finite volume came under attack a couple of times, and it came almost undone at the eleventh hour with "topographical wind". This wind, present everywhere, would correspond to the drift of clouds in the upper atmosphere and would provide the player with an updraft wind whenever it pushed against a rising incline of terrain. We did implement it but the lack of clear graphical representation of that wind forced us to cut the feature. The rendering team was already over tasked with implementing clouds, rivers, waterfalls, upgrading to DirectX 12 and so many other things.

In retrospect the storms were the simplest wind volumes, in terms of shape (cylindrical) and wind distribution (mostly unidirectional for the sandstorm, or rotating around a center axis for the blizzard). The tornado and wind tunnels on the other hand were more complicated and deserve a bit more explanation.

The tornado

We all had in our mind a very clear and simple idea of how objects should evolve around the tornado but we didn't necessarily agree about the distribution of wind speeds around it to get to that result. Instead of discussing endlessly, we made different components of the wind field tunable at runtime so Hamish could experiment and quickly find what was working. The tornado is really a stack of horizontal cylinders which centers are located on the center spline, which deforms over time. Within those horizontal cylinders, we decomposed the tornado wind field in cylindrical coordinates, with a tangential component, a radial component and a vertical component. Each component was influenced by tunable curves.

Figure 2 - Our gorgeous tornado - Shader work by Gabriel Sassone, Engin Cilasun, Stephen Yuen, Eddie Gong and Hamish Young who all contributed at different stages of development. Time is accelerated non uniformly here to showcase some nice looking times of day and skip faster over night time.

As in many other features in the past and as is common in the industry, Hamish Young and I went back and forth on the tuning and the code until the result was satisfactory. Hamish tuned the curves to his liking, dropping dozens of 40 ft (~12m) shipping container around the tornado to see how they behaved, and using the highest recorded wind speed (~230 mph) as the max speed around the tornado. After a while, Hamish found that his tuning was made difficult by the need to make the wind everywhere consistent with a simple rotating motion, but pull towards the center at the same time. It was already the case that we had angular velocity as part of the field, to spin objects in the tornado in addition to pushing them. This would guarantee for instance that objects stuck in the center would rotate in place, as we wanted. So Hamish asked to start with a wind everywhere consistent with this angular velocity at the center, meaning to start with a velocity field of a perfectly rotating field, which the tunable wind field would add itself to. With this last adjustment, the curves ended up being:

\(\mathcal{R}(d)\) - radial component multiplier (radial progression)

\(\mathcal{T}(d)\) - tangential component multiplier (radial progression)

\(\mathcal{V}(d)\) - vertical component multiplier (radial progression)

\(\mathcal{H}(h)\) - horizontal component multiplier (vertical progression)

\(\Phi(h)\) - vertical component multiplier (vertical progression)

Where \(h\) is the height above the bottom of the tornado and \(d\) the distance from the center. The tornado wind velocity is given by: $$\vec{V}(d, h) = [V_t \mathcal{T}(d) \mathcal{H}(h) + \Omega d]\hat{t} + V_r \mathcal{R}(d) \mathcal{H}(h) \hat{r} + V_u \mathcal{V}(d) \Phi(h) \hat{up}$$

When objects have accelerated to the tangential speed of the tornado at a given distance, the tangential component of the air drag forces becomes essentially zero since both air and object are tangentially moving in unison. The remaining part of the wind velocity pulls the object in, just as a planet would pull a satellite, to bend its trajectory so it remains an orbit, and the objects are pushed up, to reach higher altitudes. Pushing objects up is a crucial performance optimization, preventing expensive computation of contacts between hundreds of objects and the ground. That the tornado expands in radius with altitude also helps, spreading out objects spatially so they are less likely to touch and lead to contact computations. A real tornado is formed by an exchange of air when a layer of cold air sits on top of a layer of warm air. The warm air ascends in a swirling fashion around the center of the tornado which is the cold air descending. So in principle, at the center of the funnel the wind speed should be strongly directed downwards. As you can see we have no such thing to avoid the chance of objects being pushed down to the ground and being dragged along with the tornado, incurring an expensive collision detection cost.

Figure 3 - Tornado wind field in horizontal plane cuts (above, zoomed in), then in vertical plane cuts. The 2 cuts on the left leave out the \(\Omega d\) term of the purely rotating wind field from which we start, to see better the additional field necessary to make the tornado work. The version on the right includes the rotating field. At distance we have a very fast wind at distance that is roughly at 45 degrees from the radius to any point. The vertical cut on the left shows how much the tornado has to pull to the center. Then there is a transition range within which there is very little wind, followed by a strongly rotating field in the center. The intensity of the wind field is the color, the redder the stronger.

Figure 4 - Path lines around the tornado wind field - center spline straight.

A straight tornado is a boring tornado, that's what I always say. To deform it we used 2 cubic splines connected end to end, with 3 of the control points in orbit at different speeds around an imaginary vertical line. As a result the profile of the tornado changes over time so it slowly shifts from an S shape to a C shape and back. Appendix 5 explains exactly how we constructed the splines. Interestingly the tornado didn't work as well in deforming mode. Objects that had orbited in an orderly fashion around a straight tornado were now sling shot as soon as the center spline of the tornado was receding from them, and would sadly rain down to the ground. To fix this problem we included yet another term to the velocity, due to the deformation of the tornado itself. After all, that deformation must have originated from air motion in the first place and it seemed natural to include that component. This fixed the problem nicely, giving path lines very similar to the figure below.

Figure 5 - Path lines around the tornado wind field - center spline bending over time

Figure 6 - The tornado in game, destroying and sucking up everything on its path. This is Havok's visual debugger (VDB), an extremely useful tool that allows us to see the simulated elements of our simulation.

Wind Tunnels

Wind tunnels were developed to give control over the physical wind in certain sections of the world, such as canyons and caves but also over smoke stacks, in front of giant industrial fans or wind cannons. It can be used as a way to guide the player in certain spaces or missions. Our first prototypes used a traditional cylinder with a uniform vector field of constant velocity parallel to the cylinder's axis of revolution. Placing all those cylinders in the scene was cumbersome so we instead used a cubic Bézier spline around which a circle of varying radius is extruded. This shape is approximated by splitting it along the length of the central spline into capsuloids, i.e. capsules which radii at either end are different. The velocity field within it also went through several evolutions. At first, the wind in the tunnel was tangential to the central spline. We authored wind speed at each control point of the spline, and the speed was interpolated from one control point to the next along a spline segment. Soon, however, Joshua Espinosa, designer in charge of wind tunnels and their traversal by the player, found that he needed two more components. One component was an updraft, giving an additional artificial lift to the player, always pointed upwards. The other component we called "pull-in", which pushed the player towards the central spline, but was strongest out the outskirt of the tunnel, ramping down to zero at the center. Finally, a fall-off margin in the outer part of the tunnel ensured the transition from inside to outside the tunnel wasn't too abrupt. That margin thickness was authored as a percentage of radius, effective across the whole wind tunnel.

Figure 7 - Wind tunnel as split into a sequence of overlapping capsuloids. The splitting occurs when the direction of the spline changes, or when it is no longer possible to interpolate linearly the longitudinal change in radius (A) or wind speed (B). We computed the second derivative of those variables along the spline to determine when to cut.

Figure 8 - Wind tunnels as edited in the Apex Engine. The wind tunnel is broken into capsules as needed, depending on the spline, and the variations of radius and wind speed variables along it. As discussed in the next section you can also see the spatial partitioning which sorts the capsuloids into Morton ordered cells. This is all fast enough to update in real time and the wind tunnel can quickly be interacted with for testing. This authoring tool was scrapped together by re-purposing some parts of the river authoring tool under development and debug draws.

Figure 9 - An example of a long wind tunnel used to help the player travel upstream the river to the mountain. The river current pushes Rico towards the sea and the wind can bring him back quickly inland atop the same river. The player mechanics team found that rotating Rico by side wind was too jarring, so the main influence of wind is to accelerate him (with tail wind) or decelerate him (with head wind) and give him additional lift (updraft component).

Figure 10 - Rico wing suiting in that same wind tunnel. No direction changing input was given here. At first Rico loses altitude until he reaches the outer part of the wind tunnel which slows his descent gradually. Then the center part provides enough updraft to overcome gravity and accelerates him quickly upstream the canyon. The wingsuit code from JC3 was improved by Joshua Espinosa, Hamish Young and Rickard Granfeldt to interact nicely with wind.





Optimization of wind queries

With potentially hundreds of active bodies, calculating the local wind at their position needed to be fast. We started optimizing the query by calculating the AABB (axis aligned bounding box) of each wind volume and storing them in a tightly packed array. This constitutes a brute force early out as it is very fast to test a position against each box consecutively without cache miss. Each frame, the AABB of wind volumes is updated in the array to prepare for query.

For the storm volumes once a position is determined to be inside, we directly calculate the wind contribution using the cylindrical shape. For the tornado we needed to do more because it is much wider at the top than at the bottom, while the biggest density of objects is found at the bottom. An AABB approach is not very good at culling objects in its vicinity, but a stack of a few cylinders approached the shape much better and was a good second early out before actually calculating the wind. One last optimization we did with the tornado was to avoid projecting the query position on its center spline. This was possible because even though the tornado spline is interpolating points in 3D and is therefore a parametric 3D curve, in practice the control points it is interpolating are evenly and consecutively spread out along the vertical dimension and result in a fairly simple curve. In the general case, projecting a point onto a 3D spline or finding the point on that spline with same height, requires a search along that spline. But instead we decided to modify the way the spline was sampled by using the height above the bottom point as sampling parameter of the spline itself. This changes the shape of the spline slightly but doesn't change the fact that the spline still passes through the interpolation points, and then finding the point on the spline of same height becomes a matter of feeding it the query position's height above the bottom point, then fixing the height of the point to the height of the query. For control points not evenly spaced along the vertical axis the difference is noticeable but when they are evenly spaced it is barely so. We could also have changed the way the central curve was built by using a cubic function of height instead of a parametric curve but, like many optimizations, this one arrived late and was the least amount of work at very low risk, which is always good when confronted with many competing priorities.

The wind tunnels also needed optimizing, as projecting each query point onto each wind tunnel's center spline was out of the question. Luckily we were able to leverage a piece of technology that was being implemented simultaneously for rivers by Engin Cilasun: the sparse spatial database. We divide space in 32 \(O(\log(n))\). If that cell is not in the spatial database, there is no wind due to wind tunnels at the query point. If the cell is found, then a more expensive test needs to be performed. That test also needed to be somewhat quick, which is why we chopped up wind tunnels into wind capsuloids as explained previously. In general capsules are used because it is cheap to compute distance to their center segment. Capsuloids are marginally more expensive but still a faster method than finding the shortest distance to a cubic spline. Another property of the spatially coherent organization of wind tunnels was that we could remember the index of the cell a certain object had been in the previous frame, and use it as a hint to speed up the query on subsequent frame, since it was likely to be in that cell already or a neighboring cell.

Figure 11 - Wind tunnel capsuloids are entered in the sparse spatial database according to which cell they overlap. This result in an array of 9 cells, each of which have only a few capsuloids.

Closing thoughts

The combination of techniques we used proved highly effective. The techniques themselves are widely used in programming. A lot of the work consists in selecting them, making them work well together to solve the problem at hand. This is typical of video game programming: fast paced and rich in little problems which require simple yet sturdy solutions.

The air drag model is extremely simplified and does not capture some important features of real fluid flow. For instance a plate angled at 45 degrees angle would generate more lift in reality than with our crude model. The good thing is that the forces and torques are not computed at runtime and as such, the cube map approach doesn't preclude a more sophisticated simulation. In fact, we used an open source CFD software (OpenFOAM) to compute the forces and torques generated on the whole cube map on a test object, one of the vintage cars of JC4. The comparison revealed we weren't too far off but the "shape" of the cube maps were different, and I expect they could be even more so for objects shaped like wings. It however took several hours of computation, so this approach was clearly not ready for use on all objects. In comparison our method took barely a second per object. But I think it would be really interesting for a physics based game to have fast but realistic aerodynamic coefficients for just about any object. Maybe Rico could tie up an assemblage of objects into gigantic boomerangs. I toyed with this idea briefly after seeing samaras (the helicopter seeds of maple trees) fall and spin rapidly, created a model of one in Maya to see what would happen. The result was better than expected and the object did spin slowly on its fall but I believe we would need to improve the air drag model significantly for it to spin as fast as a real one. Like lots of things, we ran out of time.

Figure 12 - Left: all the orientations of Just Cause 4's vintage muscle car when placed in a virtual wind tunnel to cover a cube map, with a 5x5 grid on each face. Right: results visualized in Paraview for two different orientations - at an angle from the top (top) or from the rear (bottom). Red for high pressure, blue for low, with streamlines.

The tornado and storms worked very effectively. The deformation of the tornado could be improved further. If we were to spend more time on it, it would more probably be for making it a waterspout (with sharks instead of cows, of course), whirlpool, or turn it into a fire tornado. Another idea is to develop a mini tornado gun.

The wind tunnels were used here and there to control the wind on the island, for instance in canyons, in missions, or around certain military bases and help the player traverse the huge space of our open world by providing ascending winds at strategic locations. But I think we would have benefited from the topographical wind idea if we had implemented it sooner. It would have been more rational to implement it first, which would have given us a systemic omnipresent wind requiring very little authoring, the wind tunnels providing local control of custom deviations from it. We could have limited the extent of its object activation potential to a large sphere around the player, so as to limit its performance implications. This could have worked, in hindsight, but it's easy to say now. It is very hard however to know what the implications would have been on the rendering team, who would have had to solve much earlier the problem of communicating wind to the player in a way that didn't detract from the beauty of the world.

Finally a fairly natural idea is to use wind occlusion as a gameplay mechanic. We implemented a playable prototype of wind occlusion that was shown internally but we had to scrap it for lack of time to realize it properly. The physics of it is only a portion of the overall cost of a feature, which must be supported holistically by a large portion of the development team.

None of these thoughts in hindsight detract from how proud I am of what we accomplished on JC4. Our rendition of extreme weather events in general and the tornado in particular, combined with Rico's ability to traverse space and participate in the mayhem as in no other game, make for an absolutely unique and outstanding gameplay experience. We hope you have as much fun playing JC4 as we had creating it.

Appendix 1: Air drag model

We consider a body \(\mathcal{B}\) by a mesh of \(T_i, i \in [0, N-1]\).

Figure 13 - Body \(i \in [0, N-1]\).

We now focus on triangle \(\vec{V}_i\): $$ \vec{V}_i = \vec{V} + \vec{\Omega}\times\vec{OC_i} = V_i \ \hat{v_i}$$

Figure 14 - Triangle and velocities at its center

Now we have to write the elementary force \(\vec{F}_i\) acting on this triangle. This is where we need to respect the requirements we set for ourselves: $$ \vec{F}_i = - A_i \ V_i \ (\vec{V}_i \cdot \hat{n}_i)\ \hat{n}_i $$ This fits the bill nicely because it is bound to always somewhat oppose the direction of motion, is proportional to the surface area of the triangle, and is proportional to the square of the speed of the triangle relative to air. To get the force on the whole body, all that is needed is to sum those forces: $$ \vec{F} = \sum{\vec{F}_i} = \sum{- A_i \ V_i \ (\vec{V}_i \cdot \hat{n}_i)\ \hat{n}_i} $$ Since we are trying to reduce everything to 1 m/s and 1 rad/s let's rewrite with \(\hat{\omega}\): $$ \vec{F} = \sum{- A_i \ \|V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}\| (V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i\ \hat{n}_i} $$ Now for the first big approximation: $$\|V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}\| \approx V + \Omega\| \hat{\omega}\times\vec{OC_i} \| $$ This is equivalent to approximating the length of the hypotenuse of a triangle by the sum of the lengths of its two other edges. This approximation is also known as the Manhattan distance, which is where I happened to be located when working this out, so it would have been silly not to use it. So now we have: $$ \vec{F} = \sum{- A_i \ (V + \Omega \| \hat{\omega}\times\vec{OC_i} \|) (V\hat{v} + \Omega\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i\ \hat{n}_i} $$ Expanding: $$ \vec{F} = V^2 \sum{ -A_i \hat{v} \cdot \hat{n}_i \hat{n}_i } +\Omega^2 \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{\omega}\times\vec{OC_i})\cdot \hat{n}_i\hat{n}_i } \\ +V\Omega \sum{ -A_i (\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i \hat{n}_i } +\Omega V \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{v}\cdot\hat{n}_i)\hat{n}_i } $$ This may look a little bit daunting at first. Until you notice that this is almost what we were looking for. Notice that the sums either involve only \(\|\hat{\omega}\times\vec{OC_i}\|\) with its average across all triangles: $$\|\hat{\omega}\times\vec{OC_i}\| \approx Avg(\|\hat{\omega}\times\vec{OC_i}\|)_{i\in[0,N-1]}$$ Let's call it \(\hat{\omega}\). Now we can finally write: $$ \vec{F} = V(V+\Omega\ \overline{v_r}(\hat{\omega}))\vec{F}_t(\hat{v}) +\Omega^2 \vec{F}_r(\hat{\omega}) +V\Omega \vec{F}_c(\hat{\omega}) $$ where: $$\begin{align*} \vec{F}_t(\hat{v}) &= \sum{ -A_i \hat{v} \cdot \hat{n}_i \ \hat{n}_i }\\ \vec{F}_r(\hat{\omega}) &= \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{\omega}\times\vec{OC_i})\cdot \hat{n}_i \ \hat{n}_i }\\ \vec{F}_c(\hat{\omega}) &= \sum{ -A_i (\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i \ \hat{n}_i }\\ \overline{v_r}(\hat{\omega}) &= {1\over N}\sum{ \|\hat{\omega}\times\vec{OC_i}\| }\end{align*}$$ The moment of those forces around point \(O\) can be obtained in a similar fashion and we end up with: $$\left.\vec{M}\right|_O(\hat{v}, \hat{w}) = V(V+\Omega\ \overline{v_r}(\hat{\omega}))\vec{M}_t(\hat{v}) +\Omega^2 \vec{M}_r(\hat{\omega}) +V\Omega \vec{M}_c(\hat{\omega})$$ where: $$\begin{align*} \vec{M}_t(\hat{v}) &= \sum{ -A_i \hat{v} \cdot \hat{n}_i \ (\vec{OC_i}\times\hat{n}_i) }\\ \vec{M}_r(\hat{\omega}) &= \sum{ -A_i \|\hat{\omega}\times\vec{OC_i}\|(\hat{\omega}\times\vec{OC_i})\cdot \hat{n}_i \ (\vec{OC_i}\times\hat{n}_i) }\\ \vec{M}_c(\hat{\omega}) &= \sum{ -A_i (\hat{\omega}\times\vec{OC_i}) \cdot \hat{n}_i \ (\vec{OC_i}\times\hat{n}_i) }\end{align*}$$

With these definitions above, we can now form a vector \(\mathcal{A}(\hat{u})\): $$\mathcal{A}(\hat{u}) = \begin{bmatrix} \vec{F}_t(\hat{u}) \\ \vec{F}_r(\hat{u}) \\ \vec{F}_c(\hat{u}) \\ \vec{M}_t(\hat{u}) \\ \vec{M}_r(\hat{u}) \\ \vec{M}_c(\hat{u}) \\ \overline{v_r}(\hat{u}) \end{bmatrix} $$ This is a vector of 19 values and we refer to the table of samples of \(\hat{w}\) but we can find values that are close enough, and either use them directly, or use them to interpolate the output. This is where the cube map comes in, as explained in the article.

Appendix 2: Center of mass

You may have noted that the quantities above are computed around an arbitrary point \(P_i\). The total force on the body is simply the sum of forces: $$\vec{F} = \sum{\vec{f}_i}$$ The moments \(O\) is defined as: $$\left.\vec{m}_i\right|_O = \vec{OP_i} \times \vec{f}_i$$ and the total moment of those forces around \(O\) is: $$\left.\vec{M}\right|_O = \sum{\left.\vec{m}_i\right|_O} = \sum{\vec{OP_i} \times \vec{f}_i}$$ Now this is just a mathematical definition, a formula that is not directly usable to modify the motion of the body since the center of gravity \(G\) in that formula by applying Chasles' theorem: $$\left.\vec{M}\right|_O = \sum{(\vec{OG} + \vec{GP_i}) \times \vec{f}_i} = \sum{\vec{OG} \times \vec{f}_i} + \sum{\vec{GP_i} \times \vec{f}_i} $$ We recognize the moment of forces around \(\vec{F}\), so: $$\left.\vec{M}\right|_O = \vec{OG} \times \vec{F} + \left.\vec{M}\right|_G $$ Which means that, very usefully for us, if we have precomputed the total force \(\vec{F}\) unmodified and apply the following torque around the center of mass: $$\left.\vec{M}\right|_G = \left.\vec{M}\right|_O + \vec{GO} \times \vec{F} $$ At first I chose to simply take the origin of the body as point \(O\) at the center of the bounding box of the body, which was bound to be close to the center of mass anyways and localized the wind sampling in the most sensible place relative to the body.

Appendix 3: Scaling

Another wrinkle in the problem presented itself when inflatable balloons were introduced. These balloons are spherical bodies which size can change by a factor 5 or 10. Just as for changes in the center of mass, it is too costly to recompute the aerodynamic properties at runtime. Can we derive an analytical formula?

Figure 15 - Body \(\vec{O_sC_{si}} = s\ \vec{OC_i}\)

Consider again body \(\vec{F}\) but for the scaled object, we will have: $$ \vec{F_s} = V^2 \sum{ -A_i \hat{v} \cdot \hat{n}_i \hat{n}_i } +\Omega^2 \sum{ -A_i \|\hat{\omega}\times\vec{OC_{si}}\|(\hat{\omega}\times\vec{OC_{si}})\cdot \hat{n}_i\hat{n}_i } \\ +V\Omega \sum{ -A_i (\hat{\omega}\times\vec{OC_{si}}) \cdot \hat{n}_i \hat{n}_i } +\Omega V \sum{ -A_i \|\hat{\omega}\times\vec{OC_{si}}\|(\hat{v}\cdot\hat{n}_i)\hat{n}_i } $$ Using the same approximations than the first time and replacing \(s\ \vec{OC_i}\) we arrive at: $$ \vec{F_s} = V(V s^2+\Omega s^3\ \overline{v_r}(\hat{\omega}))\vec{F}_t(\hat{v}) +\Omega^2 s^4 \vec{F}_r(\hat{\omega}) +V\Omega s^3 \vec{F}_c(\hat{\omega}) $$ For \(G_s\) the center of mass of the scaled body.

Appendix 4: Simpler box drag model

There are many ways to approximate the forces and torques on a box and get various formulas. One that is fairly compact and easy to code and calculate at runtime can be derived by ignoring the contribution of angular motion on the linear force and of the linear velocity on the torque. Considering a bounding box of dimensions \(e_z\) we can arrive at the following compact formulas: $$\vec{F} = - \rho V^2 \left< e_y e_z, e_x e_z, e_x e_y \right> \cdot \hat{v}$$ $$\vec{M} = - {{\rho}\over{6}} \Omega^2 e_x e_y e_z \hat{w} \cdot \left< e_y^2+e_z^2, e_x^2+e_z^2, e_x^2+e_y^2 \right>$$

Appendix 5: Tornado center spline

The tornado is centered around a central curve which shape changes slowly over time. We realized it with 2 consecutive cubic Bézier splines \(\mathcal{C}_0\) for the lower part and \(\mathcal{C}_1\) for the higher part. The spline \(\mathcal{C}_0\) has control points \(P_0\), \(P_1\), \(P_2\) and \(P_3\) and \(\mathcal{C}_1\) is controlled by \(P_4\), \(P_5\), \(P_6\) and \(P_7\). The position of the tornado itself is \(P_0\) on ground and the top point \(P_7\) starts right above at the level of the cloud layer, but lags in time slowly following the ground position. Because the curves are connected by one end, \(P_3\) and \(P_4\) are at the same position. We also used 3 horizontal control orbits for points \(P_1\), \(P_2\) and \(P_5\). Finally \(P_3\) was placed exactly at the midpoint between \(P_2\) and \(P_5\) and \(P_6\) at the midpoint of \(P_5\) and \(P_7\).

Construction of the tornado center spline Evolution of the tornado center spline in time

References