Extending SunBurn with physics
What do your physics system and rendering system have in common? At first consideration you might think not much, but you quickly remember that the physics system sets up the position and orientation of every object and your rendering system needs that information to draw things. So how do we utilize a physics system with SunBurn? Let’s take a look at one solution to integrating JigLibX with SunBurn.
First we’re going to create a subclass of the SunBurn RenderManager class. We’ll call our’s JigLibXRenderManager. We’ll add a private struct and a list which will hold mappings between our JigLibX Body and Model, along with an ObjectVisibility:
public class JigLibXRenderManager : RenderManager
{
// a simple struct to hold the data for submitted physics objects
struct PhysicsObject
{
public Model Model;
public Body Body;
public ObjectVisibility Visibility;
}
// a list of physics objects in the manager
private readonly List<PhysicsObject> physicsObjects = new List<PhysicsObject>();
public JigLibXRenderManager(IGraphicsDeviceService graphics) : base(graphics)
{
}
}
Next we’re going to add an overload to the SubmitRenderableObject method that will let us easily submit a model with a body to the system:
public void SubmitRenderableObject(Model model, Body body, ObjectVisibility visibility)
{
physicsObjects.Add(new PhysicsObject
{
Model = model,
Body = body,
Visibility = visibility
});
}
Last for this class, we’ll override the BeginFrameRendering method to take care of submitting all the physics objects with a frame lifespan:
public override void BeginFrameRendering(ISceneState scenestate)
{
// before we begin rendering we need to add all the physics objects
// into our graphics system with Frame lifespan
foreach (var o in physicsObjects)
{
// calculate the body's world matrix
Vector3 pos = o.Body.Position;
Matrix orientation = o.Body.Orientation;
Matrix world;
Matrix.CreateTranslation(ref pos, out world);
Matrix.Multiply(ref orientation, ref world, out world);
// submit the model with the computed world matrix
SubmitRenderableObject(o.Model, world, o.Visibility, ObjectLifeSpan.Frame);
}
base.BeginFrameRendering(scenestate);
}
And that’s it for this class.
Now you’ll want to grab this Camera class, which I’ve taken from the JigLibX sample game. The camera works with standard WASD movement controls but requires you to hold down the right mouse button to look around. It seems weird, but it’s quite handy when working with test games, especially windowed games.
Now let’s set up the basics of the main game class. It should look quite a bit similar to the game set up in the first basic SunBurn project with a few slight tweaks. We’re using our JigLibXRenderManager instead of the RenderManager and we’ve disabled fog in our SceneEnvironment class. We’ve also added the Camera object instead of our old view and projection matrices.
public class Game1 : Game
{
private readonly GraphicsDeviceManager graphics;
private readonly LightingSystemManager lightingSystemManager;
private readonly JigLibXRenderManager renderManager;
private readonly SceneState sceneState;
private readonly SceneEnvironment environment;
private readonly Camera camera;
public Game1()
{
graphics = new GraphicsDeviceManager(this)
{
// Minimum requirements for dynamic shadows
// (if disabling shadows these can be dropped to PS_2_0/VS_2_0).
MinimumPixelShaderProfile = ShaderProfile.PS_3_0,
MinimumVertexShaderProfile = ShaderProfile.VS_3_0,
// Used for advanced edge cleanup.
PreferredDepthStencilFormat = DepthFormat.Depth24Stencil8
};
Content.RootDirectory = "Content";
// Required for lighting system.
Components.Add(new SplashScreenGameComponent(this, graphics));
// Create the lighting system.
lightingSystemManager = new LightingSystemManager(Services);
renderManager = new JigLibXRenderManager(graphics);
sceneState = new SceneState();
// we are disabling fog for this demo
environment = new SceneEnvironment { FogEnabled = false };
// add a camera (stolen from the JigLibX demo) to the game
Components.Add(camera = new Camera(this)
{
Position = new Vector3(0f, 10f, 20f),
Target = new Vector3(0f, 5f, 0f)
});
}
protected override void LoadContent()
{
}
protected override void UnloadContent()
{
renderManager.Unload();
lightingSystemManager.Unload();
}
protected override void Draw(GameTime gameTime)
{
// Check to see if the splash screen is finished.
if (!SplashScreenGameComponent.DisplayComplete)
{
base.Draw(gameTime);
return;
}
sceneState.BeginFrameRendering(camera.View, camera.Projection, gameTime, environment);
renderManager.BeginFrameRendering(sceneState);
renderManager.Render();
renderManager.EndFrameRendering();
sceneState.EndFrameRendering();
base.Draw(gameTime);
}
}
Next let’s add a couple pieces of content. You’ll want to add box.x and se_free_road_texture.jpg (from the Studio Evil texture pack) to your Content project. Remember to switch the content processor of box.x to the Sunburn Model Processor.
Now let’s start working on this game. First we’ll add a few fields to our game class. We’re adding the JigLibX PhysicsSystem, along with two values for handling our physics timing and a MouseState for some input we’re going to be handling.
We want to make sure we only ever update the PhysicsSystem with a fixed value, so we’ll use an accumulator and constant time step. This also let’s you tweak your physics time step separate from your game. For instance you could change that to 1f / 120f if you wanted to do 120 physics updates per second. It’s up to you and your CPU.
private const float physicsTimeStep = 1f / 60f;
private float physicsAccumulator;
private readonly PhysicsSystem physicsSystem = new PhysicsSystem
{
CollisionSystem = new CollisionSystemSAP { UseSweepTests = true },
EnableFreezing = true,
SolverType = PhysicsSystem.Solver.Normal,
NumCollisionIterations = 10,
NumContactIterations = 10,
NumPenetrationRelaxtionTimesteps = 15,
};
private MouseState lastMouseState;
Next we’re going to add a couple of helper methods. First a CreateBox method that will handle creating the necessary JigLibX classes along with submitting the box to our JigLibXRenderManager:
private Body CreateBox(Vector3 position, Vector3 rotation)
{
// create the body
Body body = new Body { AllowFreezing = true };
body.EnableBody();
// create the collision
CollisionSkin skin = new CollisionSkin(body);
body.CollisionSkin = skin;
skin.AddPrimitive(
new Box(new Vector3(-.5f), Matrix.Identity, new Vector3(1f)),
new MaterialProperties(0.8f, 0.8f, 0.7f));
// move the body
Matrix orientation = Matrix.CreateFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z);
body.MoveTo(position, orientation);
// add the body and skin to the physics system
physicsSystem.AddBody(body);
physicsSystem.CollisionSystem.AddCollisionSkin(skin);
// submit a model for rendering attached to this body
renderManager.SubmitRenderableObject(
Content.Load<Model>("box"),
body,
ObjectVisibility.Rendered | ObjectVisibility.CastShadows);
return body;
}
I’m not going to explain JigLibX here, but the important part for this post is where we submit the object. You’ll see that we just load in the box model and pass that, the body, and a visibility setting to the manager. This really makes it simple because we don’t have to do anything special each frame to keep these objects drawing.
Next we’ll add a CreateGroundPlane method that takes care of adding the ground plane for our test world. It’s largely the same as in the basic SunBurn part two except we’re making a larger plane and creating some collision data:
private void CreateGroundPlane()
{
// sets the width/depth of the ground plane
const float size = 250f;
// sets the amount of texture tiling for the ground plane
const float tiling = 250f;
// create a vertex buffer for our four points
VertexBuffer vertBuffer = new VertexBuffer(
GraphicsDevice,
VertexPositionNormalTexture.SizeInBytes * 4,
BufferUsage.WriteOnly);
vertBuffer.SetData(new[]
{
new VertexPositionNormalTexture(
new Vector3(-size, 0f, -size), Vector3.Up, new Vector2(0f, 0f)),
new VertexPositionNormalTexture(
new Vector3(size, 0f, -size), Vector3.Up, new Vector2(tiling, 0f)),
new VertexPositionNormalTexture(
new Vector3(-size, 0f, size), Vector3.Up,new Vector2(0f, tiling)),
new VertexPositionNormalTexture(
new Vector3(size, 0f, size), Vector3.Up, new Vector2(tiling, tiling)),
});
// create an index buffer
IndexBuffer indexBuffer = new IndexBuffer(
GraphicsDevice,
sizeof(short) * 6,
BufferUsage.WriteOnly,
IndexElementSize.SixteenBits);
indexBuffer.SetData(new short[] { 0, 1, 2, 2, 1, 3 });
// create a LightingEffect and load a texture in for the diffuse map
LightingEffect effect = new LightingEffect(GraphicsDevice)
{
DiffuseMapTexture = Content.Load<Texture2D>("se_free_road_texture")
};
// submit the object to be rendered as a scene object
renderManager.SubmitRenderableObject(
new BoundingSphere(Vector3.Zero, new Vector3(size, 0f, size).Length()),
effect,
indexBuffer,
vertBuffer,
new VertexDeclaration(GraphicsDevice, VertexPositionNormalTexture.VertexElements),
0,
PrimitiveType.TriangleList,
2,
0,
4,
0,
VertexPositionNormalTexture.SizeInBytes,
Matrix.Identity,
null,
ObjectVisibility.Rendered,
ObjectLifeSpan.Scene);
// create the collision for the ground plane. since this is static, we don't have
// a Body nor are we using our overload of SubmitRenderableObject.
CollisionSkin groundSkin = new CollisionSkin(null);
groundSkin.AddPrimitive(
new JigLibX.Geometry.Plane(Vector3.Up, 0f),
new MaterialProperties(0.2f, 0.8f, 0.7f));
physicsSystem.CollisionSystem.AddCollisionSkin(groundSkin);
}
Next let’s set up our scene in LoadContent. First I load in the model (which is used for all blocks) and set the DiffuseColor to a blue so the boxes are easier to see against the background. Then I create the ground plane, a few box stacks, and then a couple of lights for the scene:
protected override void LoadContent()
{
// since every box uses the same model, we'll set up the effect once here
Model box = Content.Load<Model>("box");
foreach (var mesh in box.Meshes)
{
foreach (LightingEffect effect in mesh.Effects)
{
// just set a basic diffuse color so the boxes
// are easier to see against the background
effect.DiffuseColor = new Vector3(.1f, .5f, .7f);
}
}
// create the ground plane
CreateGroundPlane();
// add some little box towers
for (int y = 0; y < 5; y++)
{
for (int x = 0; x < 5; x++)
{
CreateBox(new Vector3(x * 2f, y * 1.01f + .5f, 0), Vector3.Zero);
}
}
// add an ambient light and directional light
renderManager.LightManager.SubmitAmbientLight(new Vector3(.3f), 1f, ObjectLifeSpan.Scene);
renderManager.LightManager.SubmitDirectionalLight(
new Vector3(.8f),
new Vector3(-1f), 1f,
ShadowType.AllObjects,
1f, 1f, .1f,
ObjectLifeSpan.Scene);
}
And finally we’ll implement our Update to handle updating the physics as well as some basic input that lets us throw boxes into the world:
protected override void Update(GameTime gameTime)
{
// make sure the game is active
if (IsActive)
{
physicsAccumulator += (float)gameTime.ElapsedGameTime.TotalSeconds;
while (physicsAccumulator >= physicsTimeStep)
{
physicsSystem.Integrate(physicsTimeStep);
physicsAccumulator -= physicsTimeStep;
}
MouseState mouseState = Mouse.GetState();
if (mouseState.LeftButton == ButtonState.Pressed &&
lastMouseState.LeftButton == ButtonState.Released)
{
Body body = CreateBox(camera.Position, Vector3.Zero);
body.Velocity = Vector3.Normalize(camera.Target - camera.Position) * 40f;
}
lastMouseState = mouseState;
base.Update(gameTime);
}
}
Now run the game and you should see something like this:
Of course after a few seconds of throwing boxes, you’ll wind up with something much more like this:
And that’s how it can be done. Anyone else out there have a way of using a physics system with SunBurn they think works better than this? Or maybe just an enhanced version of this concept that works better?
Download: SunBurnPhysics.zip
Possibly Related Posts
(Automatically Generated)Getting started with SunBurn – part 1
Getting started with SunBurn – part 1
Split screen in SunBurn
Getting started with SunBurn – part 3
Getting started with SunBurn – part 2



Great work. I have done similar work for spheres, capsules(ie a character player and terrina meshes. Alos working on BSP object.
Have you done any work for 3d meshes ??
GGB
Great work. I have done similar work for spheres, capsules(ie a character player and terrina meshes. Alos working on BSP object.
Have you done any work for 3d meshes ??
GGB