Time for action – simulating fluids with movement
Your project manager is amazed with the shower of dozens of meteors in the background. However, he wants to add a more realistic background.
He shows you a water simulation sample using Farseer Physics Engine. He wants you to use the wave simulation capabilities offered by this powerful physics simulator to create an asteroids belt.
First, we are going to create a new class to define a fluid model capable of setting the initial parameters and updating a wave controller provided by the physics simulator.
We will use Farseer Physics Engine’s wave controller to add real-time fluids with movement for our games. The following code is based on the Silverlight water sample offered with the physics simulator. However, in this case, we are not interested in collision detection capabilities because we are going to create an asteroid belt in the background.
- Stay in the 3DInvadersSilverlight project.
- Create a new class—FluidModel.
- Replace the default using declarations with the following lines of code (we are going to use many classes and interfaces from Farseer Physics Engine):
using System;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Controllers;
using FarseerGames.FarseerPhysics.Mathematics; - Add the following public property to hold the WaveController instance:
public WaveController WaveController { get; private set; }
- Add the following public properties to define the wave generator parameters:
public float WaveGeneratorMax { get; set; }
public float WaveGeneratorMin { get; set; }
public float WaveGeneratorStep { get; set; } - Add the following constructor without parameters:
public FluidModel()
{
// Assign the initial values for the wave generator parameters
WaveGeneratorMax = 0.20f;
WaveGeneratorMin = -0.15f;
WaveGeneratorStep = 0.025f;
} - Add the Initialize method to create and configure the WaveController instance using the PhysicsSimulator instance received as a parameter:
public void Initialize(PhysicsSimulator physicsSimulator)
{
// The wave controller controls how the waves move
// It defines how big and how fast is the wave
// It is represented as set of points equally spaced
horizontally along the width of the wave.
WaveController = new WaveController();
WaveController.Position = ConvertUnits.ToSimUnits(-20, 5);
WaveController.Width = ConvertUnits.ToSimUnits(30);
WaveController.Height = ConvertUnits.ToSimUnits(3);
// The number of vertices that make up the surface of the wave
WaveController.NodeCount = 40;
// Determines how quickly the wave will dissipate
WaveController.DampingCoefficient = .95f;
// Establishes how fast the wave algorithm runs (in seconds)
WaveController.Frequency = .16f;
//The wave generator parameters simply move an end-point of the
WaveController.WaveGeneratorMax = WaveGeneratorMax;
WaveController.WaveGeneratorMin = WaveGeneratorMin;
WaveController.WaveGeneratorStep = WaveGeneratorStep;
WaveController.Initialize();
} - Add the Update method to update the wave controller and update the points that draw the waves shapes:
public void Update(TimeSpan elapsedTime)
{
WaveController.Update((float) elapsedTime.TotalSeconds);
}
What just happened?
We now have a FluidModel class that creates, configures, and updates a WaveController instance according to an associated physics simulator. As we are going to work with different gravitational forces, we are going to use another independent physics simulator to work with the FluidModel instance in our game.
Simulating waves
The wave controller offers many parameters to represent a set of points equally spaced horizontally along the width of one or many waves. The waves can be:
- Big or small
- Fast or slow
- Tall or short
The wave controller’s parameters allow us to determine the number of vertices that make up the surface of the wave assigning a value to its NodeCount property. In this case, we are going to create waves with 40 nodes and each point is going to be represented by an asteroid:
WaveController.NodeCount = 40;
The Initialize method defines the position, width, height and other parameters for the wave controller. We have to convert our position values to the simulator values. Thus, we use the ConvertUnits.ToSimUnits method. For example, this line defines the 2D Vector for the wave’s upper left corner (X = -20 and Y = 5):
WaveController.Position = ConvertUnits.ToSimUnits(-20, 5);
The best way to understand each parameter is changing its values and running the example using these new values. Using a wave controller we can create amazing fluids with movement.
Time for action – creating a subclass for a complex asteroid belt
Now, we are going to create a specialized subclass of Actor (Balder.Core.Runtime. Actor) to load, create an update a fluid with waves. This class will enable us to encapsulate an independent asteroid belt and add it to the game. In this case, it is a 3D character composed of many models (many instances of Mesh).
- Stay in the 3DInvadersSilverlight project.
- Create a new class, FluidWithWaves (a subclass of Actor) using the following declaration:
public class FluidWithWaves : Actor
- Replace the default using declarations with the following lines of code (we are going to use many classes and interfaces from Balder, Farseer Physics Engine and lists):
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
// BALDER
using Balder.Core;
using Balder.Core.Geometries;
using Balder.Core.Math;
using Balder.Core.Runtime;
// FARSEER PHYSICS
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics.Mathematics;
// LISTS
using System.Collections.Generic; - Add the following protected variables to hold references for the RealTimeGame and the Scene instances:
protected RealTimeGame _game;
protected Scene _scene; - Add the following private variables to hold the associated FluidModel instance, the collection of points that define the wave and the list of meshes (asteroids):
private FluidModel _fluidModel;
private PointCollection _points;
private List<Mesh> _meshList; - Add the following constructor with three parameters—the RealTimeGame, the Scene, and the PhysicsSimulator instances:
public FluidWithWaves(RealTimeGame game, Scene scene,
PhysicsSimulator physicsSimulator)
{
_game = game;
_scene = scene;
_fluidModel = new FluidModel();
_fluidModel.Initialize(physicsSimulator);
int count = _fluidModel.WaveController.NodeCount;
_points = new PointCollection();
for (int i = 0; i < count; i++)
{
_points.Add(new Point(ConvertUnits.ToDisplayUnits
(_fluidModel.WaveController.XPosition[i]),
ConvertUnits.ToDisplayUnits
(_fluidModel.WaveController.CurrentWave[i])));
}
} - Override the LoadContent method to load the meteors’ meshes and set their initial positions according to the points that define the wave:
public override void LoadContent()
{
base.LoadContent();
_meshList = new List<Mesh>(_points.Count);
for (int i = 0; i < _points.Count; i++)
{
Mesh mesh = _game.ContentManager.Load<Mesh>("meteor.ase");
_meshList.Add(mesh);
_scene.AddNode(mesh);
mesh.Position.X = (float) _points[i].X;
mesh.Position.Y = (float) _points[i].Y;
mesh.Position.Z = 0;
}
} - Override the Update method to update the fluid model and then change the meteors’ positions taking into account the points that define the wave according to the elapsed time:
public override void Update()
{
base.Update();
// Update the fluid model with the real-time game elapsed time
_fluidModel.Update(_game.ElapsedTime);
_points.Clear();
for (int i = 0; i < _fluidModel.WaveController.NodeCount; i++)
{
Point p = new Point(ConvertUnits.ToDisplayUnits
(_fluidModel.WaveController.XPosition[i]),
ConvertUnits.ToDisplayUnits
(_fluidModel.WaveController.CurrentWave[i])
+ConvertUnits.ToDisplayUnits
(_fluidModel.WaveController.Position.Y));
_points.Add(p);
}
// Update the positions for the meshes that define the wave's points
for (int i = 0; i < _points.Count; i++)
{
_meshList[i].Position.X = (float)_points[i].X;
_meshList[i].Position.Y = (float)_points[i].Y;
}
}