XNA UK User Group

A helping hand for bedroom coders throughout the land.
in

RandomChaos

  • Volumetric Clouds - Source

     

    Now, I said I wanted to get the editor written up before I posted the basic system, but I have had so many requests for it I thought I may as well get the base up and I can build on it as the system evolves.

    Before I get into the code, I'll explain a little of it's evolution. I first started out with a point sprite cloud system. This had a point sprite base cloud and a point sprite cloud manager, but found (on my Graphics card) that you get a kind of parting of the waves effect. I have recently seen this code ran on a much more superior laptop (Paul Foster from Microsoft) and he does not get the issue, anyway I digress. So with this issue I thought I would go with a billboard system, this then gave rise to a billboard cloud and respective manager as well as the point sprite version and there associated shaders. Now I am under the impression that a point sprite system is more cost effective than billboards, so what I have done in the final system in merge the two. The initial idea was to have the close up cloud sprites to be billboards and the ones in the distance to be point sprite . I decided to go a little further and you can have that and specify the distance where the two cross over or have just billboards or have just point sprites, or have none (switch the lot off)

    Now you will notice in the early clips of the cloud system, the cloud sprites look a bit odd, now that's because in my shader I was using the sprites as colour sprites and not alpha sprites, this was pointed out to me by my good friend chr0n1x. Also you will see the cloud sprites are, well not too good. This is because I hacked the sprites out individually from the sprite sheet in Niniane Wangs white paper. I now pass the whole sheet to the shader and get it to sort out what image is required.Which reminds me, as it is part of this source zip, if you use this code for any commercial gain DO NOT USE THIS SPRITE SHEET, it is released under a similar license as the XNA assets so fine for educational use. To be specific, see this license.

    Where to start, well I was going to start with the particle structures, but they are very similar to the ones I have posted before on both point sprite and billboard particle systems, so won't bother repeating myself.

    Ill begin with the basic class that these volumous objects are derived from, VolumusObject

    /// <summary>
    /// Basic volumous object.
    /// </summary>
    public class VolumusObject
    {
        // Boundingbox variables.
        BasicEffect shader;
        protected VertexPositionColor[] points;
        protected short[] index;
        public Color boxColor = Color.Red;
    
        // World pos, scale and rotation.
        public Vector3 myPosition;
        public Vector3 myScale;
        public Quaternion myRotation;
    
        // Volume
        public BoundingBox volume;
    
        // Drawable bounds (AABB)
        protected BoundingBox myDrawBounds;
    
        protected Game game;
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="game">Calling game class</param>
        /// <param name="volume">Size of volume.</param>
        public VolumusObject(Game game,BoundingBox volume)
        {
            this.game = game;
            this.volume = volume;
        }
    
        /// <summary>
        /// Method to draw bounding box (volume)
        /// </summary>
        /// <param name="offestPos">Offset, normaly the center of a cloud formation or CloudManager</param>
        public void DrawBounds(Vector3 offestPos)
        {
            if (shader == null)
                shader = new BasicEffect(game.GraphicsDevice, null);
    
            myDrawBounds = new BoundingBox(volume.Min + offestPos, volume.Max + offestPos);
            BuildBoxCorners();
            game.GraphicsDevice.VertexDeclaration = new VertexDeclaration(game.GraphicsDevice, VertexPositionColor.VertexElements);
    
            shader.World = Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition+offestPos);
            shader.View = Camera.myView;
            shader.Projection = Camera.myProjection;
            shader.DiffuseColor = volume.Min;
            shader.DiffuseColor = boxColor.ToVector3();
    
            shader.Begin(SaveStateMode.SaveState);
            for (int pass = 0; pass < shader.CurrentTechnique.Passes.Count; pass++)
            {
                shader.CurrentTechnique.Passes[pass].Begin();
                game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, points, 0, 8, index, 0, 12);
                shader.CurrentTechnique.Passes[pass].End();
            }
            shader.End();
        }
        /// <summary>
        /// Method to get BB corners.
        /// </summary>
        protected void BuildBoxCorners()
        {
            points = new VertexPositionColor[8];
    
            Vector3[] corners = myDrawBounds.GetCorners();
    
            points[0] = new VertexPositionColor(corners[1], Color.Green);
            points[1] = new VertexPositionColor(corners[0], Color.Green);
            points[2] = new VertexPositionColor(corners[2], Color.Green);
            points[3] = new VertexPositionColor(corners[3], Color.Green);
            points[4] = new VertexPositionColor(corners[5], Color.Green);
            points[5] = new VertexPositionColor(corners[4], Color.Green);
            points[6] = new VertexPositionColor(corners[6], Color.Green);
            points[7] = new VertexPositionColor(corners[7], Color.Green);
    
            short[] inds = {
                0, 1, 0, 2, 1, 3, 2, 3,
                4, 5, 4, 6, 5, 7, 6, 7,
                0, 4, 1, 5, 2, 6, 3, 7
                };
    
            index = inds;
        }
    }

    So a pretty strait forward class, the only special feature really is the BoundingBox parameter, this is used to define the size of the volume.

    Now into the first bit of nitty-gritty, the base cloud class that is derived from VolumetricObject, BaseCloud.

    /// <summary>
    /// Base cloud class, derived from VolumousObject
    /// </summary>    
    public class BaseCloud : VolumusObject 
    {
        // static object instance, not used and I will probably remove it.
        protected static int instance = 0;
        protected int myID = 0;
        
        // list of verticies to draw, name left over from origional point sprite system
        public ParticleVertex[] m_sprites;
    
        // Base color of particles/sprites
        public Color particleColor;
        
        // Number of particles/sprites in this cloud volume
        public int partCount;
    
        // Legacy from point sprites.
        public float particleScale = 0;
    
        // Used for creating and disolving clouds.
        protected float disipation = 1f;
    
        // Used for psudo random numeber generation
        Random rnd;
    
        // Set to tru if you want the volume's AABB to be drawn.
        public bool DrawBoundingBox = false;
    
        /// <summary>
        /// ctor.
        /// </summary>
        /// <param name="game">Calling game class</param>
        /// <param name="particleCount">Number of partilces/sprites in this volume</param>
        /// <param name="volume">Volume</param>
        public BaseCloud(Game game, int particleCount,BoundingBox volume): base(game,volume)
        {
            instance++;
            myID = instance;
    
            myPosition = Vector3.Zero;
            myScale = Vector3.One;
            myRotation = new Quaternion(0, 0, 0, 1);
    
            partCount = particleCount;
    
            particleColor = Color.White;
    
        }
    
        public virtual void UnloadContent()
        {
            
        }
    
        public virtual void LoadContent()
        {       
            rnd = new Random((int)DateTime.Now.Ticks);
            Thread.Sleep(15);
    
            BuildCloud();
        }
    
        public Color CloudColor = Color.White;        
    
        /// <summary>
        /// Method to randomly fill the volume with random particles/sprites off the sprite sheet.
        /// </summary>
        protected virtual void BuildCloud()
        {
            m_sprites = new ParticleVertex[partCount];
    
            float maxY = volume.Max.Y;
    
            for (int i = 0; i < m_sprites.Length; i++)
            {
                float x, y, z;
    
                // Randomly position particle in the volume.                
                x = MathHelper.Lerp(volume.Min.X, volume.Max.X, (float)rnd.NextDouble());
                y = MathHelper.Lerp(volume.Min.Y, volume.Max.Y, (float)rnd.NextDouble());
                z = MathHelper.Lerp(volume.Min.Z, volume.Max.Z, (float)rnd.NextDouble());
                               
                m_sprites[i].Position = new Vector3(x, y, z);// * (displacement *= -1));                
                m_sprites[i].Color = CloudColor;
    
                // Setup particles data.                
                // x describes the sprite type 0 - .15 are valid values.
                // y describes sprite width
                // z describes the sprite height.
                // w alpha blend, so clouds can be disolved and re formed.
    
                if (particleScale == 0)
                    particleScale = Vector3.Distance(volume.Min, volume.Max) / 4.5f;
    
                // Want the fuzzy stuff near the bottom of the cloud
                int img = (int)((16 / volume.Max.Y) * y);
                m_sprites[i].Data = new Vector4((((float)img) / 100), particleScale, particleScale, disipation);
    
                m_sprites[i].Data = new Vector4((((float)rnd.Next(0, 16)) / 100), particleScale, particleScale, disipation);
            }
    
        }
        public virtual void Update(GameTime gameTime)
        { }
    
    }

     

    So, we have the regular particle system type of stuff, particle colour, scale and count, but we have another, disipation (miss spelt it, how like me that is..); it simply regulates the alpha level of the cloud so giving the impression of forming and dissipating. The BuildCloud() method is where all the actions at. What this method does it place the cloud sprites randomly in the volume and scales them to take up the volume space. It then randomly sets the sprite image to be used for each cloud sprite.

    We now have a basic, randomly generated cloud, and this was great in the early days, but I wanted to control what image sprites are used in the cloud, so I created the Cloud class which inherits from BaseCloud.

    /// <summary>
    /// This class creates a cloud volume using a single given sprite index.
    /// </summary>
    public class Cloud : BaseCloud 
    {
        int[] spriteIndex;       
    
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="game">Calling game class</param>
        /// <param name="particleCount">Number of sprites in this cloud</param>
        /// <param name="volume">Volume of cloud</param>
        /// <param name="spriteIndex">Sprite index to use (0-15)</param>
        public Cloud(Game game, int particleCount, BoundingBox volume, params int[] spriteIndex) : base(game, particleCount, volume)
        {
            this.spriteIndex = spriteIndex;
        }
    
        protected override void BuildCloud()
        {
            base.BuildCloud();
    
            for (int p = 0,i=0; p < m_sprites.Length; p++,i++)
            {
                if (i >= spriteIndex.Length)
                    i = 0;
    
                m_sprites[p].Data = new Vector4((float)(spriteIndex[i]/100f), m_sprites[p].Data.Y, m_sprites[p].Data.Z, m_sprites[p].Data.W);                
            }
        }
    }

    This is even simpler, all we do is add an array of integers to describe the cloud images to use off the sprite sheet.

    So far, pretty simple eh? Well then lets take a look at the cloud manger and see if it gets complicated....

        /// <summary>
        /// Class used to managed clouds.
        /// </summary>
        public class CloudManager : DrawableGameComponent, IVolumetric
        {
            public enum SpriteType
            {
                BillboardOnly,
                PointSpriteOnly,
                MixedBBAndPS,
                None
            }
    
            private DynamicVertexBuffer vb;
            private DynamicIndexBuffer ib;  
            VertexDeclaration m_bbvDec;
            VertexDeclaration m_psvDec;
    
            // Last position in vertex stream
            int lastPos = 0;
            // Vertex data to be drawn
            ParticleVertex[] masterParticleList;
            // Close up billboard particles.
            ParticleVertex[] m_billboards;
            // Faraway pint sprite particles..
            VertexParticle[] m_sprites;
            Effect bbEffect;
            Effect psEffect;
    
            // List of clouds in the manager.
            public List<BaseCloud> CloudList = new List<BaseCloud>();
    
            // Last camera position.
            private Vector3 lastCamPos = Vector3.Zero;
    
            public Vector3 myPosition;
            public Vector3 myScale;
            public Quaternion myRotation;
    
            // Total number of particles 
            int particleCount = 0;
    
            /// <summary>
            /// Ambiant light color.
            /// </summary>
            public Color SunlightColor = Color.White;
    
            // Draw cloud bounding box's
            bool DrawBoundingBox = false;
    
            // Cloud disipation;
            float disipation = 0;
    
            // Speed clouds are formed.
            public float formSpeed = .00025f;
            public bool disipate = false;
    
            // switch to form or evaporate clouds.
            bool formOrEvap = false;
            
            // current frame
            short frame = 0;
    
            /// <summary>
            /// Time Of Day.
            /// </summary>
            public float tod = 0;
    
            /// <summary>
            /// Base Cloud color.
            /// </summary>
            public Color BaseCloudColor = Color.DarkGray;
    
            /// <summary>
            /// Cameras viewport object
            /// </summary>
            Viewport ViewPort;
    
            /// <summary>
            /// Distance required to turn a point sprite into a billboard.
            /// </summary>
            public float switchingDistance = 250;
    
            public SpriteType spriteType = SpriteType.MixedBBAndPS;
    
            /// <summary>
            /// ctor
            /// </summary>
            /// <param name="game">Calling game class</param>
            public CloudManager(Game game) : base(game)
            {
                myPosition = Vector3.Zero;
                myScale = Vector3.One;
                myRotation = new Quaternion(0, 0, 0, 1);
            }
    
            /// <summary>
            /// Method to add a single cloud to the manager
            /// </summary>
            /// <param name="cloud">Cloud derived from BaseCloud</param>
            public void AddCloud(BaseCloud cloud)
            {
                CloudList.Add(cloud);
                particleCount += cloud.partCount;
            }
    
            /// <summary>
            /// Method to add clouds to the manager.
            /// </summary>
            /// <param name="cloud">Cloud derived from BaseCloud</param>
            public void AddClouds(params BaseCloud[] clouds)
            {
                for (int c = 0; c < clouds.Length; c++)
                    AddCloud(clouds[c]);
            }
            /// <summary>
            /// Method to build cloud vertex data and add to main stream.
            /// </summary>
            /// <param name="cloud">Cloud derived from BaseCloud</param>
            protected void AddToCloudSprites(BaseCloud cloud)
            {
                for (int e = 0; e < cloud.m_sprites.Length; e++, lastPos += 4)
                {
                    for (int thisP = 0; thisP < 4; thisP++)
                    {
                        masterParticleList[lastPos + thisP] = new ParticleVertex();
                        masterParticleList[lastPos + thisP].Position = cloud.m_sprites[e].Position + cloud.myPosition + myPosition;
                        masterParticleList[lastPos + thisP].Color = cloud.m_sprites[e].Color;
                        masterParticleList[lastPos + thisP].Data = cloud.m_sprites[e].Data;
                        masterParticleList[lastPos + thisP].Data2 = cloud.m_sprites[e].Data2;
                        switch (thisP)
                        {
                            case 0:
                                masterParticleList[lastPos + thisP].TextureCoordinate = Vector2.Zero;
                                break;
                            case 1:
                                masterParticleList[lastPos + thisP].TextureCoordinate = new Vector2(1, 0);
                                break;
                            case 2:
                                masterParticleList[lastPos + thisP].TextureCoordinate = new Vector2(0, 1);
                                break;
                            case 3:
                                masterParticleList[lastPos + thisP].TextureCoordinate = Vector2.One;
                                break;
                        }
                    }
                }
            }
            protected override void LoadContent()
            {
                m_bbvDec = new VertexDeclaration(Game.GraphicsDevice, ParticleVertex.VertexElements);
                m_psvDec = new VertexDeclaration(Game.GraphicsDevice, VertexParticle.VertexElements);
    
                Texture2D cloudSheet = Game.Content.Load<Texture2D>("Textures/Clouds/CloudSpriteSheetOrg");
                bbEffect = Game.Content.Load<Effect>("Shaders/Clouds/BillboardCloudShader");
                bbEffect.Parameters["partTexture"].SetValue(cloudSheet);
    
                psEffect = Game.Content.Load<Effect>("Shaders/Clouds/PointSpriteCloudShader");
                psEffect.Parameters["particleTexture"].SetValue(cloudSheet);
    
                masterParticleList = new ParticleVertex[particleCount * 4];
                lastPos = 0;
                for (int c = 0; c < CloudList.Count; c++)
                {
                    CloudList[c].LoadContent();
                    // Add to master list.
                    AddToCloudSprites(CloudList[c]);
                }
    
                short[] indices = new short[6 * particleCount];
    
                for (int part = 0; part < particleCount; part++)
                {
                    int off = part * 6;
                    int offVal = part * 4;
    
                    indices[off + 0] = (short)(0 + offVal);
                    indices[off + 1] = (short)(1 + offVal);
                    indices[off + 2] = (short)(2 + offVal);
    
                    indices[off + 3] = (short)(1 + offVal);
                    indices[off + 4] = (short)(3 + offVal);
                    indices[off + 5] = (short)(2 + offVal);
                }
    
                if (particleCount > 0)
                {
                    ib = new DynamicIndexBuffer(Game.GraphicsDevice, typeof(short), 6 * particleCount, BufferUsage.WriteOnly);
                    ib.SetData(indices);
                }
                SortClouds();
                
                base.LoadContent();
            }
    
            protected override void UnloadContent()
            {
                for (int c = 0; c < CloudList.Count; c++)
                    CloudList[c].UnloadContent();
    
                base.UnloadContent();
            }                
            public override void Update(GameTime gameTime)
            {            
    
                //if (lastCamPos != Camera.myPosition)
                if (Visible)
                {
    #if !XBOX
                    // Dont sort EVERY time the camera moves position.
                    if (frame > 20)
                    {
                        frame = 0;
                        lastCamPos = Camera.myPosition;
                        SortClouds();
                    }
                    frame++;
    
    #else
                    SortClouds();            
    #endif
                }
                    base.Update(gameTime);
            }
            
            /// <summary>
            /// Method to sort draw order of cloud vertices.
            /// </summary>
            private void SortClouds()
            {
                List<distData> bbDists = new List<distData>();
                List<distData> psDists = new List<distData>();
    
                for (int p = 0; p < masterParticleList.Length; p += 4)
                {
                    float dist = (new distData()).Distance(masterParticleList[p].Position, Camera.myPosition);
                    
                    switch (spriteType)
                    {
                        case SpriteType.MixedBBAndPS:
                            if (Math.Sqrt(dist) <= switchingDistance)
                                bbDists.Add(new distData(p, dist));
                            else
                                psDists.Add(new distData(p, dist));
                            break;
                        case SpriteType.BillboardOnly:
                            bbDists.Add(new distData(p, dist));
                            break;
                        case SpriteType.PointSpriteOnly:
                            psDists.Add(new distData(p, dist));
                            break;
                    }
                }
    
                bbDists.Sort(new distData());
                psDists.Sort(new distData());
    
                ParticleVertex[] newBBSet = new ParticleVertex[bbDists.Count * 4];
                VertexParticle[] newPSSet = new VertexParticle[psDists.Count];
                
                for (int p = 0; p < bbDists.Count * 4; p += 4)
                {
                    for (int ip = 0; ip < 4; ip++)
                    {
                        newBBSet[p + ip] = masterParticleList[bbDists[p / 4].idx + ip];
                        if(disipate)
                            newBBSet[p + ip].AlphaValue = disipation;
                    }
                }
                m_billboards = newBBSet;
    
                for (int p = 0; p < psDists.Count; p++)
                {
                    for (int ip = 0; ip < 4; ip++)
                    {
                        VertexParticle vp = new VertexParticle();
                        vp.Data = masterParticleList[psDists[p].idx + ip].Data;
                        vp.Position = masterParticleList[psDists[p].idx + ip].Position;
                        vp.Color = masterParticleList[psDists[p].idx + ip].Color;
                        if (disipate)
                            vp.AlphaValue = disipation;
                        newPSSet[p] = vp;
                    }
                }
                m_sprites = newPSSet;
    
                // As the array size changes then this needs to be re created, will alter this so
                // it knows the last size and only recreates if different.
                vb = new DynamicVertexBuffer(Game.GraphicsDevice, typeof(ParticleVertex), m_billboards.Length, BufferUsage.WriteOnly);
                vb.ContentLost += new EventHandler(vbLost);
                vb.SetData(m_billboards);                        
            }
    
            protected void vbLost(object sender,EventArgs args)
            {
                try
                {
                    if(!vb.IsDisposed)
                        vb.SetData(m_billboards);
                }
                catch { }
            }
    
            // Simple evaperation, will alter this to evaperate from the outside in.
            private void EvaporateCloud(int cloudIDX)
            {
                if (disipation > 0)
                    disipation -= formSpeed;
                else
                {
                    disipation = 0;
                    formOrEvap = false;
                }
            }
            // opposite of the evaperate method.
            private void FormCloud(int cloudIDX)
            {
                if (disipation <= 1)
                    disipation += formSpeed;
                else
                {
                    disipation = 1;
                    formOrEvap = true;
                }
            }
    
            public override void Draw(GameTime gameTime)
            {
                if (Visible)
                {
                    if (DrawBoundingBox)
                        for (int c = 0; c < CloudList.Count; c++)
                            CloudList[c].DrawBounds(myPosition);
    
                    // Set blend mode
                    bool AlphaBlendEnable = Game.GraphicsDevice.RenderState.AlphaBlendEnable;
                    Blend DestinationBlend = Game.GraphicsDevice.RenderState.DestinationBlend;
                    Blend SourceBlend = Game.GraphicsDevice.RenderState.SourceBlend;
    
                    Game.GraphicsDevice.RenderState.AlphaBlendEnable = true;
    
                    Game.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                    Game.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                    
                    Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
    
                    // Draw Point Sprite Clouds
                    if (m_sprites != null && m_sprites.Length > 0)
                        DrawPSClouds(gameTime);
    
                    // Draw Billboard Clouds
                    if (m_billboards != null && m_billboards.Length > 0)
                        DrawBBClouds(gameTime);
    
                    // Set the states back.
                    Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
    
                    Game.GraphicsDevice.RenderState.AlphaBlendEnable = AlphaBlendEnable;
    
                    Game.GraphicsDevice.RenderState.DestinationBlend = DestinationBlend;
                    Game.GraphicsDevice.RenderState.SourceBlend = SourceBlend;
                }
                base.Draw(gameTime);
            }
            /// <summary>
            /// World View Projection of manager, for external processes (i.e DoF)
            /// </summary>
            public Matrix WVP;
            private void DrawPSClouds(GameTime gameTime)
            {
                Game.GraphicsDevice.VertexDeclaration = m_psvDec;
    
                bool PointSpriteEnable = Game.GraphicsDevice.RenderState.PointSpriteEnable;
    
                Game.GraphicsDevice.RenderState.PointSpriteEnable = true;
                Game.GraphicsDevice.RenderState.PointSizeMax = float.MaxValue;
                Game.GraphicsDevice.RenderState.PointSizeMin = float.MinValue;
    
                WVP = (Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition)) * Camera.myView * Camera.myProjection;
    
                psEffect.Parameters["Projection"].SetValue(Camera.myProjection);
                psEffect.Parameters["ViewportHeight"].SetValue(Camera.myViewport.Height);
                psEffect.Parameters["WorldViewProj"].SetValue(WVP);
    
                psEffect.Parameters["lightColor"].SetValue(SunlightColor.ToVector4());
                psEffect.Parameters["lightDir"].SetValue(new Vector3(0, 0, -10));
    
                psEffect.Parameters["EyePosition"].SetValue(Camera.myPosition);
                psEffect.Parameters["timeOfDay"].SetValue(tod);
    
                psEffect.Begin();
                for (int ps = 0; ps < psEffect.CurrentTechnique.Passes.Count; ps++)
                {
                    psEffect.CurrentTechnique.Passes[ps].Begin();
                    Game.GraphicsDevice.DrawUserPrimitives<VertexParticle>(PrimitiveType.PointList, m_sprites, 0, m_sprites.Length);
                    psEffect.CurrentTechnique.Passes[ps].End();
                }
                psEffect.End();
    
                Game.GraphicsDevice.RenderState.PointSpriteEnable = PointSpriteEnable;
            }
            private void DrawBBClouds(GameTime gameTime)
            {
                Game.GraphicsDevice.RenderState.CullMode = CullMode.None;
    
                Game.GraphicsDevice.VertexDeclaration = m_bbvDec;
                Game.GraphicsDevice.Vertices[0].SetSource(vb, 0, ParticleVertex.SizeInBytes);
                Game.GraphicsDevice.Indices = ib;
    
                Matrix World = Matrix.CreateScale(myScale) * Matrix.CreateFromQuaternion(myRotation) * Matrix.CreateTranslation(myPosition);
                bbEffect.Parameters["world"].SetValue(World);
                Matrix vp = Camera.myView * Camera.myProjection;
                bbEffect.Parameters["vp"].SetValue(vp);
    
                bbEffect.Parameters["EyePosition"].SetValue(Camera.myPosition);
    
                bbEffect.Parameters["lightColor"].SetValue(SunlightColor.ToVector4());
                bbEffect.Parameters["lightDir"].SetValue(new Vector3(-1, 0, -100));
    
                bbEffect.Parameters["floor"].SetValue(BaseCloudColor.ToVector4());
                bbEffect.Parameters["basePos"].SetValue(myPosition);
    
                bbEffect.Parameters["timeOfDay"].SetValue(tod);
    
                bbEffect.Begin(SaveStateMode.SaveState);
                for (int ps = 0; ps < bbEffect.CurrentTechnique.Passes.Count; ps++)
                {
                    bbEffect.CurrentTechnique.Passes[ps].Begin();
                    Game.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, m_billboards.Length, 0, m_billboards.Length / 2);
                    bbEffect.CurrentTechnique.Passes[ps].End();
                }
                bbEffect.End();
    
                Game.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            }
            /// <summary>
            /// Method to switch the drawing of bounding box's (volumes) on/off
            /// </summary>
            /// <param name="onOff">true is on, false is off</param>
            public void SetDrawBounds(bool onOff)
            {
                DrawBoundingBox = onOff;
            }
            public void Rotate(Vector3 axis, float angle)
            {
                axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
                myRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * myRotation);
            }
            public void Translate(Vector3 distance)
            {
                myPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(myRotation));
            }
            public void Revolve(Vector3 target, Vector3 axis, float angle)
            {
                Rotate(axis, angle);
                Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
                Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);
                myPosition = Vector3.Transform(target - myPosition, Matrix.CreateFromQuaternion(rotate));
            }
        }

    OK, a fair bit in there, but it looks complex because of the splitting of billboard and pint sprite particles, other than that is it a pretty strait forward particle emitter. There are some areas of note here though...

    AddCloud() & AddClouds()

    This adds a cloud to the manager. All it does is increment the overall particle count and adds the cloud to the cloud list.

    AddToCloudSprites()

    This method populates the master particle list with the clouds that have been added.

    SortClouds()

    Now, this bit might be interesting, this is where the draw order is sorted. I started off with a bubble sort to do this, but guess what? It was very, very slow. Now I lack the education to implement great sorting algorithms (some suggested by Leaf) so I used my ingenuity and decided to use the sort method of the List object to do my dirty work. You will see there is a reference to a class called distData, this is a class that I derived from IComparer and holds my sorting logic. You will find it in the Utility.cs source file. So using that I just get the List.Sort method to do all the work for me, great!

    And that is about it, pretty simple stuff I know, looks good though don't it. Wait....I am missing the vital bit that brings all this stuff together, the shader.

    I am just going to put up the billboard shader as it and the point sprite shader are very similar.

    half4x4 world : World;
    half4x4 vp : ViewProjection;
    half3 EyePosition : CAMERAPOSITION;
    
    half3 worldUp = half3(0,1,0);
    half4 floor = float4(0,0,0,1);
    
    half4 lightColor = half4(1,1,1,1);
    half3 lightDir;
    half3 basePos;
    
    half timeOfDay;
    
    half MistingDistance = 25;
    
    const half2 imgageUV[16] = {
            half2(0,0),half2(.25,0),half2(.50,0),half2(.75,0),
            half2(0,.25),half2(.25,.25),half2(.50,.25),half2(.75,.25),
            half2(0,.50),half2(.25,.50),half2(.50,.50),half2(.75,.50),
            half2(0,.75),half2(.25,.75),half2(.50,.75),half2(.75,.75)};
    
    texture partTexture;
    sampler partTextureSampler = sampler_state 
    { 
        Texture = <partTexture>; 
        MinFilter = Linear;
        MagFilter = Linear;
        MipFilter = Linear;
    };
    
    struct VertexIn
    {
        half4 Position       : POSITION0;             
        half2 TextureCoords: TEXCOORD0;    
        half4 Color        : COLOR0;
        half4 Extras : POSITION1;
        half4 Extras2 : POSITION2;
    };
    struct VertexOut
    {
        half4 Position       : POSITION0;      
        half2 TextureCoords: TEXCOORD0;
        half4  Color        : COLOR0;
        half image : COLOR1;
        half lightLerp : TEXCOORD2;
    };
    
    struct PixelToFrame
    {
        half4 Color : COLOR0;
    };
    
    half4 GetTexture(half2 texCoord,half img)
    {
        texCoord = (texCoord * .25) +  imgageUV[round(img * 100.0f)];
        return tex2D(partTextureSampler, texCoord);
    }
    VertexOut VS(VertexIn input)
    {
        VertexOut Out = (VertexOut)0;
        
        half3 center = mul(input.Position,world);    
        half3 eyeVector = center - EyePosition;
        
        half3 finalPos = center;
        half3 sideVector;
        half3 upVector;    
        
        sideVector = normalize(cross(eyeVector,worldUp));            
        upVector = normalize(cross(sideVector,eyeVector));    
        
        finalPos += (input.TextureCoords.x - 0.5) * sideVector * input.Extras.y;
        finalPos += (0.5 - input.TextureCoords.y) * upVector * (input.Extras.z);    
        
        half4 finalPos4 = half4(finalPos,1);    
        
        Out.Position = mul(finalPos4,vp);
        Out.TextureCoords = input.TextureCoords;
        
        Out.Color = input.Color;    
        
        // Which sprite to draw...
        Out.image = input.Extras.x;
        
        // Alpha
        Out.Color.a = input.Extras.w;
        
        // Misting    
        half3 dist = abs(finalPos4 - EyePosition);
        half3 dist2 = abs(center - EyePosition);
        half distVal = dist.x + dist.y + dist.z;    
        half distVal2 = dist2.x + dist2.y + dist2.z;    
        
        if(distVal <= MistingDistance)
            Out.Color.a *= distVal / MistingDistance;        
            
        if(distVal2 <= MistingDistance)
            Out.Color.a *= distVal2 / MistingDistance;    
            
        return Out;
    }
    
    PixelToFrame PS(VertexOut input)
    {
        PixelToFrame Out = (PixelToFrame)0;
        
        half color = GetTexture(input.TextureCoords,input.image).rgb;    
        
        if(timeOfDay <= 12)
            lightColor *= timeOfDay / 12;            
        else
            lightColor *= (timeOfDay - 24) / -12;                        
        
        lightColor += .5;
        
        Out.Color = input.Color * lightColor;    
        
        //Out.Color = input.Color;
        
        Out.Color.a *= color;
        
        // Draw lighter as we go down the texture.
        Out.Color.a *= 1-input.TextureCoords.y;
    
        return Out;
    }
    
    technique Go
    {
        pass P0 
        {
            VertexShader = compile vs_2_0 VS();
            PixelShader  = compile ps_2_0 PS();
        }
    }

     

    I am quite chuffed with this shader as it is all my own work, I must be learning something :)

    What is going on here? Well, in the Vertex shader, as before we are orientating the board to face the camera and scaling it, then we ready the tex coords and the color, then set the image index, this is the sprite on the sprite sheet to be used, when you look at the sprite sheet the index works like this:

    0   1   2   3 

    4   5   6   7 

    8   9   10 11

    12 13 14 15

    How am I getting a single sprite off the one sheet of 16 sprites? This is done in the GetTexture function, what I am doing here is taking the texcoord and manipulating it to give me the required uv value on the sprite sheet for the given index. This manipulation is done by quartering the texcoord value (the sprite sheet is 4x4 sprites) then adding the uv value I have set up in my imageUV array for the given image index. You can see this is getting multiplied by 100, and in the code the value gets divided by 100, I had tried to pass this as an int but it never worked out so, I force it to be a float/half to 2 decimal places. Once I have this modified texccord I can get the image from the sprite sheet.

    Then depending on the distance of the camera from the sprite we fade it, this is done so as you approach a mass of cloud it thins out giving a misting effect.

    Then in the Pixel shader I get the rgb values from the sprite sheet for the given sprite index (remember they are alpha sprites), then, as I had integrated it with my SkySphere it calcs the shade of the cloud based on time of day and sets the color of the cloud based on base cloud color and the ambient light color then the alpha is set using the rgb from the sprite sheet. I then thin the cloud out from top to bottom giving the sprite a wispy effect. Also, you will notice in the sprite sheet assets pipeline properties I have set it so it is resized to the power of two, this speeds it up a little. You may notice on your PC or laptop that as you get close to a cloud or view a lot of clouds your FPS will drop (if you have a regular card like me), this is due to your card reaching it's fill rate, on the Xxbox 360 you don't seem to get this issue.

    In the Game class you will see I have set up 3 skyTypes, this is just to show how you can have varying sky's with the system, you can do SO much more than I have in this sample, but I guess I will show you that in later posts.

    And so that is about it....that is my basic volumetric cloud system. I hope you have as much fun using it as I have had creating and posting about it. Hope it lives up to your expectations too. This is not the end of it though, I will keep posting on it's evolution and giving code updates. There are a few of us working on this system now and I have set up an SVN so we can improve it, Kyle Hayward aka GraphicsRunner is helping out with the lighting and other design elements as is Michael Hansen over at EvoFX Studio we are hoping to get it integrated into his XNA games engine as well as Sora.

    As ever, don't see this as a finished entity, it is a WIP, also if you do use it in your game then please give me a shout, would love to see it used. And do remember if you use this for a commercial venture DO NOT USE THE SPRITE SHEET, CREATE YOUR OWN! Again, read the license for this asset here.

    Download the cloud solution here.

  • ReMix 08 - Demo Source Code

    Some of you that have come to this blog after going to the ReMix 08 conference are probably wondering, "Where is that source code Paul said would be here!?" Well, most of it is here, it's just smashed across a lot of posts.

    But, it's not all under one project ready for you to run and play a game with, so that's what I am putting up here in this post. In the following zip is my GSM (Game State Manager) which I have posted before, this version has a few changes in it that I think improve on it a little. Also, and this is a first for my blog, a cut down version of my physics engine, I have removed a lot from it as it is just not used in the demo, but you will see the basic principles of what I am working on. I wont even start to explain the physics engine here. Also, you will remember in the demo my volumetric cloud system, I have removed it from the demo as I want to post this in it's own right again, as I want to explain what I am doing with it.

    Well I hope you liked the demo and get something from this source code. I am also including *.ccgame files that can be used to install (I use the word very loosely) on your PC or XBox 360. To be able to use any of the source you will need XNA 2.0 as well as the latest DirectX9c, to install the *.ccgame files on your XBox 360 or deploy the source to your XBox 360 you will need a Creators Club account as well as an XBox Live account (Silver will do). Don't forget to build the GSM and custom content pipeline before the ReMix08 project the first time you build.

    Downloads:

    ReMix08 Source, with demos (includes PC & XBox 360 projects)

    ReMix08-Windows.ccgame

    ReMix08-Xbox360.ccgame

    For both the ccgame files I had to give them a .zip extension so they could be downloaded, please remove the .zip extension befoer executing thr ccgame files.

  • Sora World - A Dream Build Play Entry for 2008

    Before I start this post here is a description of what we intend Sora to be:

    Ideally a two player networked game where you choose to be either a Pilot or a Navigator. The game will be story driven but have a complex progress tree and so resulting in different endings. As a Pilot you will need to master the controls of the craft(s) and there weaponry so you can make your way through the world safely, avoiding or flying through storms, fighting off attackers. The Navigator has a more RTS roll to play, Navigators having the knowledge of where places are in Sora. A good navigator will have a large map (each time a navigator visits a new pace it's added to his map). Also some craft will come with strategic weapons that only a Navigator can use. In flight the navigator will be able to direct the pilot to there destination and away from hazards. So A good pilot, with lots of kills, or fast ship will be sort after by Navigators in the network lobby and a Navigator with lots of destinations on his/her map will be sought after by Pilots in the lobby.

    Here are some excerpts from one of the design documents (rough draft):

    "Everything in Sora originates from the Source, the brilliant cloud of immeasurable energy that anybody foolish to venture to has never returned from. Truth be told, the people of Sora don’t really know exactly how their world works, but it is clear the Source and Sora are intertwined, entangled and inseparable."

    "The south is home to the floating city of Mina and considered the industrial end of Sora. With a more plentiful cloud supply it is the natural home of the water reservoir. Raincrafts hover around rain crowds, collecting and returning water to the reservoir. Cloud Scrapers weave between cumulus clouds extracting their energy, and capturing the essence of the cloud itself. Surrounding Mina are several scattered villages and even single island houses, a reminder of the old nomadic days of Sora.

    The north has clouds but no Raincrafts. The rain is allowed to naturally fall under the farming islands, the primary source of food for Sora. Kita is the residential and agricultural region, heavily protected by the nearby Union Air Force (UAF) base."

    "In that age, the Source shone brightly and the people of Sora lived in harmony with it, scattered among villages throughout the skies, taking only what they needed."

    There is still more to it than this, but this gives you an idea...


    OK, I got contacted about 2 months ago by a team (Zenna Tavares) building a game for the DBP competition as they wanted to use my clouds in their game and wondered if I would be interested in helping them out. So I replied saying that I was willing to help out, but was not sure how much help I could be other than the cloud system as I am busy at work, writing my blog posts and spending time with my family (not in that order) and that it only gave us 2 months  to get an entry in (not enough time in my book!). Zenna was fine with this and so the journey began....

    So we went about building an engine for Sora (Japanese for Heavens), we got a concept artist on board, then a 3D artist and it was all looking good. We took a few other components from my blog here as well as my (still fledgling) physics engine and started putting a few technical demos together. Then I got asked to do the ReMix08 demo, which kind of took a little time from it, but there was another dev who was also working on components so was no real issue, but then we lost the 3D artist, time was drawing near for the DBP deadline, we had no assets, no consolidated engine, just bits and pieces of demos and some great ideas...  

    Thankfully Zenna got another two 3D artists on board and they started work on converting the concept art to models. We also were joined by a Musician and it was all looking good....until I looked at the calendar....

    We had literally a week and a bit to get an entry in, I was working on getting my GSM into the entry sorting out some kind of flight physics, the other developer (Ken Lau) was sorting out the networking side of the game and some asset management bits to. Then came a bomb shell, Ken, our other developer had to drop out at this late stage with about a week and a bit before the dead line (Thankfully we still have Ken, he just had too much on at the time).

    So, I guess it fell on me to put the entry together :( So I have been stealing time from the land of sleep where and when I can to get what we have now. This meant dropping 90% of what we wanted the entry to be, the networking, weapon systems, plot and story...lol

    Even with the extension I can't see what I/we could add in the next 2 days (that would = 6 hours development for me).

    Well these are all just excuses really, we have done what we can and at least we have an entry.

    The team is:

    Zenna Tavares - Design and Production

    Michal Cho - Concept Art

    Damien Senior - 3D Art

    Trung Tran - 3D Art

    Martin Ahn - Music

    Ken Lau - Developer

    Me - Developer

    You can take a look at the concept work for Sora here. Here are some in game pics:

     

    And here is the now obligatory film clip that comes with almost every post of mine now :P

    http://www.youtube.com/v/_b5z9KlWVkc <p><a href="http://www.youtube.com/v/_b5z9KlWVkc">http://www.youtube.com/v/_b5z9KlWVkc</a></p>

    There is also a clip on soapbox.

    So, there is more to Sora World than the entry for DBP 08, this is just a taste of what Sora will be I guess, fingers crossed we might actually finish it, yet another long road I have started down...lol

  • ReMix 08 - In The Beginning There Was CornFlowerBlue

     

    The remit I got in mid August from Paul on this was "A simple flying game into which we could weave the important elements of the XNA game infrastructure". So my initial thoughts were some kind of Jet Pack type game where you can fly about shooting stuff. So I cranked up Express and created a new project. Now, I thought to my self, where do I start, and the GSM came to mind, so I started to create a GSM for the ReMix project, after a few minutes of me messing about and grabbing code from my existing GSM I thought, "What on earth are you doing, you have done all this before, use the GSM off the blog!!" and that is exactly what I have done.

    This was the start of a trend. At first I wanted to write the whole thing from the bottom up just for ReMix to show how quick you can get going with XNA, but I think this way also shows what can be done with the community components that are out there. I know it's mostly my stuff I am using here, elements of my blog, but my point is, that we have such a rich community you can get game components from all over the place. My post processing code for example is a modification of Kyle Hayward's (GraphicsRunner) post processing framework.  I also found a few bugs in some of my elements, my terrain class for example was creating the normals incorrectly, so I revisited Riemers site to see where I went wrong with it and found he has a new method of calculating them and so I have used this to fix my issue in the class.

    So, this was the first update I sent Paul, about 2 days later, remember I only get a few hours a day to work on my XNA stuff, my lunch break (if I get one) and in the evenings. So using my GSM, Kyle's modified post processing framework and my terrain class I had three screens, Initial screen, Main menu and a game screen.

    http://www.youtube.com/v/jGxv-fD69rA <p><a href="http://www.youtube.com/v/jGxv-fD69rA">http://www.youtube.com/v/jGxv-fD69rA</a></p>

     

    Next came the clouds, Paul actually mentioned in his remit the clouds, I have not just put them in to show them off, though I probably would have put them in any way :P So, this was about a day later (about an hours work) this next clip shows the dynamic sky sphere and clouds in the project.

    You will see in the clip the clouds are moving, I did this quite simply by just rotating the whole cloud manager so each cloud moves relative to it.

    http://www.youtube.com/v/Z-GqG-AmQRo <p><a href="http://www.youtube.com/v/Z-GqG-AmQRo">http://www.youtube.com/v/Z-GqG-AmQRo</a></p>

     

    In the same day, I got my physics engine in there. I stripped out a load of it as it's not finished and just left what was needed, it's still at a simple stage, but what the hell I have my own physics engine in XNA!! I also put in basic terrain collision, not yet taking the collision normal from the terrain just using Vector3.Up. No idea why I kept showing him the menu system...

    http://www.youtube.com/v/KesfhA6K_hI <p><a href="http://www.youtube.com/v/KesfhA6K_hI">http://www.youtube.com/v/KesfhA6K_hI</a></p>

     

    So, now came the particle system, about four days later, as you can see I still don't have a model for my "Jet Pack" so kept using the cube. I think at this point I was also getting the normal from the terrain for the collision normal. I have put three trails on the player, two engine trails, a smoke and a fire to give the impression of, well and jet engine and a third to give the sense of motion, with out the third it just felt a little odd to me.

    http://www.youtube.com/v/hYJ2_aSa_sQ <p><a href="http://www.youtube.com/v/hYJ2_aSa_sQ">http://www.youtube.com/v/hYJ2_aSa_sQ</a></p>

     

    Lacking a "Jet Pack" model a friend of mine (Chr0n1x) suggested using a model from the space war starter kit, so, got that in here, got the bullet collision written and put in a few bad guys (the blocks :P). In this clip, you will see how poor a shot I am, I have not improved.. The clip also shows the physics system working as the blocks are colliding and bouncing off each other, also the explosion code as the bullets collide with game objects. Oh, this is a day later...

    http://www.youtube.com/v/tWnbplNFZIA <p><a href="http://www.youtube.com/v/tWnbplNFZIA">http://www.youtube.com/v/tWnbplNFZIA</a></p>

     

    And this is where I was about a week ago (23rd of August to be exact), not bad eh. Bit of a HUD added, used better models (again space war) for the baddies, lives, score, energy and health...and a game has been written!! Only difference really between this clip and the one in my last post is a few bugs got ironed out and I added specular lighting to the ships. If my laptop had a decent sound card, I would record the in game sounds too, as ever Audio gives it that extra touch.

    http://www.youtube.com/v/PAiI6S2Ai7Y <p><a href="http://www.youtube.com/v/PAiI6S2Ai7Y">http://www.youtube.com/v/PAiI6S2Ai7Y</a></p>

     

    I do have a few issues, well not with the PC version, as I have not written a great deal for the 360 I guess I am quite a sloppy 360 developer. Just got to iron those out, create the demo projects that show the various elements that make up the game and all should be good on the day. Fingers crossed eh :P

    My next post will be on the particle system used in this demo, it's about the only thing that it not on this blog, well almost, the principles are the same as in my particle tutorials, but this will include a particle manager.

    Before I end this post, I would like to thank Paul for asking me to do this, it is a great honor in deed for me to be asked, and it's been fun too

  • ReMix08 - The Initial Shock...

    Well, would you believe I have been asked by Microsoft to give a talk at their ReMix 08 conference on XNA. The conference it's self is primarily concerning Web development but they are having an 'after hours' section on XNA.

    Now I know what you are thinking, "WOW! that's great!" and that was my initial reaction, but as I read on through the email from Paul Foster, a Microsoft Evangelist, a cold chill fell over my body, my palms began to sweat and I almost started to cry. They wanted me to talk for an hour in front of around 300 people. Any one who knows me, knows I am not the worlds greatest public speaker. In fact, that and wasps are the only things on earth that I fear, I will happily enter a pit of tigers, bears and mothers-in-law and wrestle them all to the death, but public speaking and wasps and I turn to jelly. I actually don't advocate the wresting of bears and tigers, they are endangered animals and this should not be a past time anyone should take part in. Mother-in-laws on the other hand are open sport but should only be wrestled when appropriate. I am gutted really, I would love to be able to do the talk, I am almost over my phobia of wasps, I have to be, I don't want my children to have the same silly fear I have of them so I just let them fly by, also I think it would look bad if my young daughters saw there dad screaming and running like an idiot just because an insect entered the room. Alas my other phobia is long off being cured, oh well one day eh..

    Thankfully Paul was very understanding when I said that I have an issue with speaking and has agreed to let me write the demo for him to present. I have actually finished the final game demo it's self and I am currently putting together the demo projects to show various elements of XNA and how the game comes together. I was surprised at how fast it all came together, I just took the components off my blog and put them together to create the demo game. I guess it took me about two weeks to write in all.

    I am going to retrospectively blog the progress of this demo game, and put up some code snippets from it, for example the particle system (much the same as I have posted here before). I guess to show how easy it was to come up with a game in XNA in such a short time. Now don't get me wrong, it's not a AAA game, but what do you expect from a hobbyist, and two weeks development, I think it's pretty cool, but then I would, I wrote it :)  In fact, I enjoyed writing it so much I am thinking of evolving it into a full game, adding networking and all sorts.

    Here is how it looks now, in my later blogs I will post clips of how it came together, hope you like the look of it.

    http://www.youtube.com/v/V8J0CsLhstw <p><a href="http://www.youtube.com/v/V8J0CsLhstw">http://www.youtube.com/v/V8J0CsLhstw</a></p>
  • Generic XNA - Threading for Windows & XBox 360

    OK, I went back to my old threading code to port it to the 360 so I could start playing about with processor affinity (as promised last time) and found the compact framework for the 360 was a bit thin on the ground for parameterized threading, so I have re written the whole thing and it will now play on both the PC and the 360 and also included processor affinity for your 360 projects and there is very little interface change so if you were using the code for your Windows games you can just replace the threading code wit this and you wont have to alter any of your calls to it.

    So, what did I do??

    Pretty much ripped the lot out and started again. The ThreadManagers innards have been totally re done, now instead of having a shed load of Dictionaries holding mutex's and the like we now just have three, one for the thread starters, one for the threaded code and one for the threads them selves.

        /// <summary>
        /// This is the Thread manager.
        /// </summary>
        public class ThreadManager : GameComponent
        {
    
            /// <summary>
            /// List of ThreadStart used to start the treads.
            /// </summary>
            private Dictionary<int, ThreadStart> threadStarters = new Dictionary<int, ThreadStart>();
            private Dictionary<int, ThreadCodeObj> threadedCodeList = new Dictionary<int, ThreadCodeObj>();
            /// <summary>
            /// List of runnign threads.
            /// </summary>
            private Dictionary<int, Thread> threads = new Dictionary<int, Thread>();
    
            /// <summary>
            /// Managers GameTime to be passed onto the threads.
            /// </summary>
            static GameTime gameTime;
            /// <summary>
            /// ctor
            /// </summary>
            /// <param name="game">Calling game class</param>
            public ThreadManager(Game game) : base(game)
            { }
    
            /// <summary>
            /// Overiden Update call, loads manager gameTime varaible.
            /// </summary>
            /// <param name="gameTime"></param>
            public override void Update(GameTime gameTime)
            {
                ThreadManager.gameTime = gameTime;
    
                for (int t = 0; t < threadedCodeList.Count; t++)
                    threadedCodeList[t].Update(gameTime);
    
                base.Update(gameTime);
            }
    
            /// <summary>
            /// Method to add a thread to the maanger.
            /// </summary>
            /// <param name="threadCode">Code to be executed in the thread.</param>
            /// <param name="threadInterval">Time period between each call in miliseconds</param>
            /// <returns>Index of thread, first one added will be 0 next 1 etc..</returns>
    #if XBOX
            public int AddThread(ThreadCode threadCode, int threadInterval,int affinityIndex)
    #else
            public int AddThread(ThreadCode threadCode, int threadInterval)
    #endif
            {
                int retVal = threads.Count;
    
    #if XBOX
                ThreadCodeObj thisThread = new ThreadCodeObj(threadCode, threadInterval,affinityIndex);
    #else
                ThreadCodeObj thisThread = new ThreadCodeObj(threadCode, threadInterval);
    #endif
    
                threadedCodeList.Add(threadedCodeList.Count, thisThread);
                threadStarters.Add(threadStarters.Count, new ThreadStart(thisThread.Worker));
                threads.Add(threads.Count, new Thread(threadStarters[threads.Count]));
    
                threads[threads.Count - 1].Start();
    
                return retVal;
            }
    
            /// <summary>
            /// Method to kill a single thread.
            /// </summary>
            /// <param name="index"></param>
            public void KillThread(int index)
            {
                threadedCodeList[index].KillThread(threads[index]);
            }
    
            /// <summary>
            /// Method to start a thread
            /// </summary>
            /// <param name="threadCode"></param>
            /// <param name="threadInterval"></param>
            /// <param name="index"></param>
            public void StartThread(ThreadCode threadCode, int threadInterval, int index)
            {
                if (threadedCodeList[index].stopThread)
                {
                    threads[index] = new Thread(threadStarters[index]);
                    threadedCodeList[index].stopThread = false;
                    threads[index].Start();
                }
            }
    
            /// <summary>
            /// Method to tidy up unfinished threads.
            /// </summary>
            /// <param name="disposing"></param>
            protected override void Dispose(bool disposing)
            {
                for (int t = 0; t < threads.Count; t++)
                    KillThread(t);
    
                base.Dispose(disposing);
            }
        }

    The real change has been in the ThreadCodeObj, this now holds most of your threading information.

    /// <summary>
        /// This is the delegate to be used for passing the code to be called in the tread.
        /// </summary>
        /// <param name="gameTime">GameTime</param>
        public delegate void ThreadCode(GameTime gameTime);
    
        /// <summary>
        /// This class holds the required data for the code to be called in the thread.
        /// </summary>
        internal class ThreadCodeObj
        {
            public ThreadCode CodeToCall = null;
    
            /// <summary>
            /// Mutex to stop thread clashes.
            /// </summary>
            private static Mutex mutex = new Mutex();
            /// <summary>
            /// Used to make the thread wait.
            /// </summary>
            private ManualResetEvent threadStopEvent = new ManualResetEvent(false);
            /// <summary>
            /// Bool to control imediate stopping of thread loop.
            /// </summary>        
            public bool stopThread = false;
            /// <summary>
            /// Interval thread will wait befoer next cycle.
            /// </summary>        
            private int threadIntervals = 1;
    
    #if XBOX 
            int processorAffinity;
    #endif
    
            public GameTime gameTime;
    
    #if XBOX
            public ThreadCodeObj(ThreadCode code,int interval,int affinity)
            {
                CodeToCall = code;
                threadIntervals = interval;
                processorAffinity = aff