Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 16, Skydome)

Introduction

Welcome to Part16 of the XNA Framework GameEngine Development series.  In this article I will get back on track toward my original plan.  I am sorry if any readers felt misled by my deviation from the series plan.  From now on I will treat Physics updates and WorldBuilder updates as their own sub-series.

Now that we have created a reasonably decent rendering wrapper, we should start creating some standard objects that will be used most often in games.  I normally start my engine development off with a good skydome, mostly because the math is simple and well known (more on this silly statement soon).  Essentially, we are going to draw half a sphere on the screen, but with criteria.

  • Would prefer to use TriangleStrips over TriangleLists.
  • Would like to have control over the Skydome vertex/index complexity.
  • Would like to have the skydome centered on the camera.

part16.jpg

Release

Here is the source code, as a side note thanks to feedback from readers I implemented the JigLibX physics library into this latest release.  This library has a cleaner feel than BulletX and will be the physics engine for this game engine… well until next month when I change my mind again.

Where to begin?

At this point, I grabbed my old geometry book and found… well nothing helpful to me.  I wish I was able to say, “yup I used my highschool geometry books and wrote a skydome tutorial”, but I did not.  At this point, I did what any developer would do, I googled until I found something helpful. 

After reading a few dozen articles I realized, there are literally thousands of people making skydomes, skyplanes, skyboxes, and skyspheres.  As a bi-product, there are thousands of ways to create a skydome and by no means am I saying the way I chose is best.  In fact, while writing this article, I looked up at least 3 other ways to create a skydome and will probably write up tutorials on those subjects as well.

Skydome

A skydome is a simple half sphere that will allow us to surround the camera with a beautifully shaded sky.

First, we create a scene object that is loadable.  This skydome is constructed based on four parameters, the radius of the circle, the height (this implies we can make the sphere more elliptical in the Y direction), the number of rings (latitude), and the number of segments (longitude).

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using RoeEngine2.Managers;

namespace RoeEngine2.SceneObject.StandardObjects
{
    public class Skydome : RoeSceneObject, IRoeLoadable
    {
        int _numberOfVertices;
        int _numberOfIndices;
        float _radius;
        float _height;
        int _rings;
        int _segments;
        VertexBuffer _vb;
        IndexBuffer _ib;

        private bool _attachToCamera = true;
        public bool AttachToCamera
        {
            get { return _attachToCamera; }
            set { _attachToCamera = value; }
        }

        public Skydome(float radius, float height, float rings, float segments)
        {
            _radius = (float)Math.Max(radius, 1.0e-05);
            _height = (float)Math.Max(height, 1.0e-05);

            _rings = (int)Math.Max(rings, 1) + 1;
            _segments = (int)Math.Max(segments, 4) + 1;

            _numberOfVertices = _rings * (_segments + 1);
            _numberOfIndices = _segments * _rings * 2;
        }  

        public virtual void LoadContent()
        {
            CreateVB();
            CreateIB();
            ReadyToRender = true;
        }

        public virtual void UnloadContent()
        {
           
        }

        BasicEffect effect = new BasicEffect(EngineManager.Device, null);

        public override void Draw(GameTime gameTime)
        {
            if (_attachToCamera)
            {
                Position = new Vector3(CameraManager.ActiveCamera.Position.X, Position.Y, CameraManager.ActiveCamera.Position.Z);
            }

            effect.EnableDefaultLighting();
            effect.PreferPerPixelLighting = true;

            effect.World = World;
            effect.View = CameraManager.ActiveCamera.View;
            effect.Projection = CameraManager.ActiveCamera.Projection;

            effect.DiffuseColor = new Vector3(255, 0, 0);
            effect.DirectionalLight0.Enabled = true;

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                using (VertexDeclaration declaration = new VertexDeclaration(EngineManager.Device, VertexPositionTexture.VertexElements))
                {
                    EngineManager.Device.VertexDeclaration = declaration;
                    EngineManager.Device.Vertices[0].SetSource(_vb, 0, VertexPositionTexture.SizeInBytes);
                    EngineManager.Device.Indices = _ib;
                    EngineManager.Device.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, _numberOfVertices, 0, _numberOfIndices - 2);
                }
                pass.End();
            }
            effect.End();
        }

        public override string ToString()
        {
            return "Skydome";
        }

        private void CreateIB()
        {
            _ib = new IndexBuffer(
                EngineManager.Device,
                typeof(int),
                _numberOfIndices,
                BufferUsage.WriteOnly);

            int[] indices = new int[_numberOfIndices];

            bool leftToRight = false;
            int n = 0;
            for (int y = -0; y < _segments; y++)
            {
                if (leftToRight == true)
                {
                    for (int x = 0; x = 0; x--)
                    {
                        indices[n++] = y * _rings + x;
                        indices[n++] = (y + 1) * _rings + x;
                    }
                }
            }
            _ib.SetData(indices);
        }

        private void CreateVB()
        {

            _vb = new VertexBuffer(
                EngineManager.Device,
                typeof(VertexPositionTexture),
                _numberOfVertices,
                BufferUsage.WriteOnly);

            VertexPositionTexture[] vertices = new VertexPositionTexture[_numberOfVertices];
            int n = 0;
            for (int y = 0; y = 0; x--)
                {
                    float phi = (float)x / _rings * MathHelper.ToRadians(90.0f);

                    VertexPositionTexture vert = new VertexPositionTexture();
                    vert.Position.X = (float)(Math.Sin(phi) * Math.Cos(theta) * _radius);
                    vert.Position.Z = (float)(Math.Sin(phi) * Math.Sin(theta) * _radius);
                    vert.Position.Y = (float)(Math.Cos(phi) * _height);
                    vertices[n++] = vert;
                }
            }
            _vb.SetData(vertices);
        }
    }
}

I would like to point out a few items that might interest readers.  I am using a BasicEffect at this point, this will be replaced by a more sophisticated shader in the next article.  Also, I am using a user defined vertex and index buffer rather than a model.  Hopefully, readers will realize the flexibility of not always using a model from the content pipeline for rendering.Adding a Skydome to the SceneGraph

Lines of code like the following are the reason why we write game engines.  We want to make game developers lives easier so they can focus on gameplay.  Personally, this is the reason why I write this series, to make it possible for game developers to worry less about graphics and more about gameplay.

            Skydome dome = new Skydome(20, 20, 32, 32);
            SceneGraphManager.AddObject(dome);

Conclusion

In this article, I introduced the first user defined SceneObject in the engine.  A simple and useful Skydome will be the backdrop of any outdoor game.  In the next article, I will show how to paint this dome with a true to life lightscattering shader.

Please feel free to leave comments or suggestions, I thoroughly enjoy the feedback.

March 5, 2008 Posted by | C#, XBOX360, XNA | 15 Comments

   

Follow

Get every new post delivered to your Inbox.