Google
 
Showing posts with label Picking Objects. Show all posts
Showing posts with label Picking Objects. Show all posts

Thursday, June 07, 2007

Engine Design - Terrain

Well this is my version of a terrain class for the Engine, it is a very simple class. I took the initial idea and I guess framework from Riemers Series 4 tutorial and got it to fit my engine. I added a few methods to it to make it a bit more user friendly, for instance there is a method to tell if an object has collided with the terrain object, there is a method that will return the given height of the terrain at a given Vector3 and another method that I use to raise and lower the terrain with a mouse click. This terrain picker is derived from my picking method found in the last post, it was also inspired (again) by Riemers post on his forum concerning terrain picking, what he was doing was very similar to my object picker and so gave me the inspiration to come up with my method. I also have to add that if it was not for Chr0n1x I would not have gone looking for examples to do terrain picking.

I also created my own shader for the terrain as I found that Riemers, although very good as it had great detail, slowed my engine a little. The shader I came up with, while not being as pretty gives a faster render, I will put that up here to.

So on to the code. What I will do is break it down into chunks again and then offer the whole class at the end. I am not going to go over the basic terrain code as this is all covered in Riemers tutorial, for this please go to his site and read though the tutorial to get a better understanding, saves me repeating his tutorial. Also there are some methods I have commented out as they are unfinished, for example I am working on a random map generator, this is not complete so has been removed, also there is the color map generator for the terrain, this I have left as it is completed, but it was more of a folly rather than for any real purpose, I have left it in as a matter of completeness.

Terrain Position

       private Vector3 basePosition;

       public new Vector3 Position
       {
           get { return basePosition; }
           set { basePosition = value; }
       }



This field and property were added so that I could have my terrain centered on the position rather than the position vector be the bottom left hand corner of the terrain mesh.

GetHeightAt

       public Vector3 GetHeightAt(Vector3 objPosition)
       {
           Vector3 retVal = new Vector3();
           int x, y;

           y = (int)objPosition.X + (myWidth / 2);
           x = (int)objPosition.Z + (myHeight / 2);

           if ((x >= 0 && y >= 0) && (x < myWidth && y < myHeight))
               retVal = new Vector3(x, y, myPosition.Y + myHeightData[x, y]);
           
           return retVal;
       }


This method basicaly returns the height value of the terrain at a given position. I used this for debugging my collision method using it to display the height of the terrain as I drove my camera over it.

Collision


       public bool Collision(Vector3 objPosition,float heightBuffer)
       {
           bool retVal = false;
           int x, y, objHeight;

           y = (int)objPosition.X + (myWidth / 2);
           x = (int)objPosition.Z + (myHeight / 2);

           objHeight = (int)(objPosition.Y - heightBuffer);

           if ((x >= 0 && y >= 0) && (x < myWidth && y < myHeight))
           {
               if (objHeight < myPosition.Y + myHeightData[x, y])
                   retVal = true;
           }
           return retVal;
       }


This method returns true if the object is colliding with the terrain and false if it is not, simple enough. The heightBuffer is there as your models position may well be taked from it's center and so you need to specify the distance from the center to the base of the model. I guess you could use the radius of the objects native BoundingSphere, I did not do this as this as just occured to me as I write this.

Here is an example of this in action, this code placed in the Update of your Game will stop your camera from going through the terrain and give it the impression of being solid.

   // Terrain collision.
   RCTerrain terrain = (RCTerrain)(((RCObjectNode)game.Scene.GetObject("terrain")).Object);
   game.WriteText(game.Font, 10, 100, terrain.GetHeightAt(RCCameraManager.ActiveCamera.Position).ToString(), Color.White);
   if (terrain.Collision(RCCameraManager.ActiveCamera.Position, 2.5f))
       RCCameraManager.ActiveCamera.Position = new Vector3(RCCameraManager.ActiveCamera.Position.X, RCCameraManager.ActiveCamera.Position.Y + .2f, RCCameraManager.ActiveCamera.Position.Z);



PickTerrain

Before Mouse Clicks


After wafting the mouse, clicking left and right buttons



       public void PickTerrain(GraphicsDevice device, Point mousecoords,bool Up)
       {
           RCCameras.RCCamera camera = RCCameras.RCCameraManager.ActiveCamera;

           Vector3 nearSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MinDepth), camera.Projection, camera.View, Matrix.Identity);
           Vector3 farSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MaxDepth), camera.Projection, camera.View, Matrix.Identity);
           Vector3 direction = farSource - nearSource;

           float zFactor = nearSource.Y / direction.Y;
           Vector3 zeroWorldPoint = nearSource + direction * zFactor;

           Ray ray = new Ray(nearSource, direction);

           for (int x = 0; x < myWidth; x++)
               for (int y = 0; y < myHeight; y++)
               {
                   BoundingBox tmp = new BoundingBox(myVertices[x + y * myWidth].Position + myPosition, (myVertices[x + y * myWidth].Position + myPosition) + new Vector3(1f, 1f, 1f));

                   if (ray.Intersects(tmp) != null)
                   {
                       float val = 0;
                       if (Up)
                           val += .5f;
                       else
                           val -= .5f;

                       if (x > 0)
                           myHeightData[x - 1, y] += val;

                       myHeightData[x, y] += val;

                       if (x < myWidth)
                           myHeightData[x + 1, y] += val;

                       if (x > 0 && y > 0)
                           myHeightData[x - 1, y - 1] += val;

                       if (y > 0)
                           myHeightData[x, y - 1] += val;
                       if (x < myWidth && y > 0)
                           myHeightData[x + 1, y - 1] += val;

                       if (x > 0 && y < myHeight)
                           myHeightData[x - 1, y + 1] += val;
                       if (y < myHeight)
                           myHeightData[x, y + 1] += val;
                       if (x < myWidth && y < myHeight)
                           myHeightData[x + 1, y + 1] += val;

                       break;
                   }
               }            

           BuildTerrain(device);
       }


This is basicaly my Picking code, but what I am doing here is checking to see if my ray intersects a vertex of the terrain. I do this by moving through each of the terrains verts and using their possition Vector3 build a bounding box around them to check if my ray intersects it, if it does then I do what I want to do, in this case raise or lower the trrain depending on which mouse button was clicked, a bit like POPULOUS, a very old game I used to love. What I need to add to this method is the check to see if the intersecting vertex is the nearest to the camera like in my scene object picking method. Oh well at least this gives you an idea of how it can be done.

Here is the complete class.

RCTerrain:

   public class RCTerrain : RCObject, IRCRenderable, IRCLoadable
   {
       protected struct VertexMultitextured
       {
           public Vector3 Position;
           public Vector3 Normal;
           public Vector4 TextureCoordinate;
           public Vector4 TexWeights;

           public static int SizeInBytes = (3 + 3 + 4 + 4) * 4;
           public static VertexElement[] VertexElements = new VertexElement[]
            {
                new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
                new VertexElement( 0, sizeof(float) * 10, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ),
            };
       }

       private VertexBuffer vb;
       private IndexBuffer ib;
       VertexMultitextured[] myVertices;
       private int myHeight;
       private int myWidth;
       private float[,] myHeightData;
       private string myHeightAsset;

       private bool randomMap;

       public bool GenerateMap
       {
           get { return randomMap; }
           set { randomMap = value; }
       }

       Texture2D ColorMap;

       private Vector3 basePosition;

       public new Vector3 Position
       {
           get { return basePosition; }
           set { basePosition = value; }
       }

       public int Height
       {
           get { return myHeight; }
           set { myHeight = value; }
       }

       public int Width
       {
           get { return myWidth; }
           set { myWidth = value; }
       }
       
       private string[] myAssets;
       private Texture2D myHeightMap;
       private Texture2D[] myTextures;

       private Vector3 myLightPosition = new Vector3(0.5f, 0.5f, -1);

       public Vector3 LightPosition
       {
           get { return myLightPosition; }
           set { myLightPosition = value; }
       }

       public RCTerrain(string[] textureAssets,string heightAsset,string name):base(name)
       {          
           myHeightAsset = heightAsset;

           myAssets = new string[textureAssets.Length];
           for(int ass=0;ass<textureAssets.Length;ass++)
               myAssets[ass] = textureAssets[ass];
       }

       public void PickTerrain(GraphicsDevice device, Point mousecoords,bool Up)
       {
           RCCameras.RCCamera camera = RCCameras.RCCameraManager.ActiveCamera;

           Vector3 nearSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MinDepth), camera.Projection, camera.View, Matrix.Identity);
           Vector3 farSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MaxDepth), camera.Projection, camera.View, Matrix.Identity);
           Vector3 direction = farSource - nearSource;


           float zFactor = nearSource.Y / direction.Y;
           Vector3 zeroWorldPoint = nearSource + direction * zFactor;

           Ray ray = new Ray(nearSource, direction);

           for (int x = 0; x < myWidth; x++)
               for (int y = 0; y < myHeight; y++)
               {
                   BoundingBox tmp = new BoundingBox(myVertices[x + y * myWidth].Position + myPosition, (myVertices[x + y * myWidth].Position + myPosition) + new Vector3(1f, 1f, 1f));

                   if (ray.Intersects(tmp) != null)
                   {
                       float val = 0;
                       if (Up)
                           val += .5f;
                       else
                           val -= .5f;

                       if (x > 0)
                           myHeightData[x - 1, y] += val;

                       myHeightData[x, y] += val;

                       if (x < myWidth)
                           myHeightData[x + 1, y] += val;

                       if (x > 0 && y > 0)
                           myHeightData[x - 1, y - 1] += val;

                       if (y > 0)
                           myHeightData[x, y - 1] += val;
                       if (x < myWidth && y > 0)
                           myHeightData[x + 1, y - 1] += val;

                       if (x > 0 && y < myHeight)
                           myHeightData[x - 1, y + 1] += val;
                       if (y < myHeight)
                           myHeightData[x, y + 1] += val;
                       if (x < myWidth && y < myHeight)
                           myHeightData[x + 1, y + 1] += val;

                       break;
                   }
               }            

           BuildTerrain(device);
       }

       public bool Collision(Vector3 objPosition,float heightBuffer)
       {
           bool retVal = false;
           int x, y, objHeight;

           y = (int)objPosition.X + (myWidth / 2);
           x = (int)objPosition.Z + (myHeight / 2);

           objHeight = (int)(objPosition.Y - heightBuffer);

           if ((x >= 0 && y >= 0) && (x < myWidth && y < myHeight))
           {
               if (objHeight < myPosition.Y + myHeightData[x, y])
                   retVal = true;
           }
           return retVal;
       }

       public Vector3 GetHeightAt(Vector3 objPosition)
       {
           Vector3 retVal = new Vector3();
           int x, y;

           y = (int)objPosition.X + (myWidth / 2);
           x = (int)objPosition.Z + (myHeight / 2);

           if ((x >= 0 && y >= 0) && (x < myWidth && y < myHeight))
               retVal = new Vector3(x, y, myPosition.Y + myHeightData[x, y]);
           
           return retVal;
       }

       public void LoadGraphicsContent(GraphicsDevice myDevice, ContentManager myLoader)
       {
           // Textures
           myTextures = new Texture2D[myAssets.Length];
           for (int ass = 0; ass < myAssets.Length; ass++)
               myTextures[ass] = myLoader.Load<Texture2D>(myAssets[ass]);
           
           // Height Data
           if (!randomMap)
           {
               if (myHeightAsset.IndexOf(".raw") == -1)
               {
                   myHeightMap = myLoader.Load<Texture2D>(myHeightAsset);
                   LoadHeightData();

                   ColorMap = new Texture2D(myDevice, myWidth, myHeight, 1, ResourceUsage.None, SurfaceFormat.Color);
                   BuildColorMap();
               }
               else
                   LoadRawHeightData();
           }
           else
               GenerateHeightMap();

           // Update position
           myPosition = new Vector3(basePosition.X - (myWidth / 2), basePosition.Y, basePosition.Z - (myHeight / 2));
           
           //Build Terrain
           BuildTerrain(myDevice);
       }
       private void BuildTerrain(GraphicsDevice myDevice)
       {
           // Vertices
           myVertices = new VertexMultitextured[myWidth * myHeight];

           for (int x = 0; x < myWidth; x++)
               for (int y = 0; y < myHeight; y++)
               {
                   myVertices[x + y * myWidth].Position = new Vector3(y, myHeightData[x, y], x);
                   myVertices[x + y * myWidth].Normal = new Vector3(0, 1, 0);
                   myVertices[x + y * myWidth].TextureCoordinate.X = (float)x / 30.0f;
                   myVertices[x + y * myWidth].TextureCoordinate.Y = (float)y / 30.0f;

                   myVertices[x + y * myWidth].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(myHeightData[x, y] - 0) / 8.0f, 0, 1);
                   myVertices[x + y * myWidth].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(myHeightData[x, y] - 12) / 6.0f, 0, 1);
                   myVertices[x + y * myWidth].TexWeights.Z = MathHelper.Clamp(1.0f - Math.Abs(myHeightData[x, y] - 20) / 6.0f, 0, 1);
                   myVertices[x + y * myWidth].TexWeights.W = MathHelper.Clamp(1.0f - Math.Abs(myHeightData[x, y] - 30) / 6.0f, 0, 1);

                   float totalWeight = myVertices[x + y * myWidth].TexWeights.X;
                   totalWeight += myVertices[x + y * myWidth].TexWeights.Y;
                   totalWeight += myVertices[x + y * myWidth].TexWeights.Z;
                   totalWeight += myVertices[x + y * myWidth].TexWeights.W;
                   myVertices[x + y * myWidth].TexWeights.X /= totalWeight;
                   myVertices[x + y * myWidth].TexWeights.Y /= totalWeight;
                   myVertices[x + y * myWidth].TexWeights.Z /= totalWeight;
                   myVertices[x + y * myWidth].TexWeights.W /= totalWeight;

               }

           for (int x = 1; x < myWidth - 1; x++)
           {
               for (int y = 1; y < myHeight - 1; y++)
               {
                   Vector3 normX = new Vector3((myVertices[x - 1 + y * myWidth].Position.Y - myVertices[x + 1 + y * myWidth].Position.Y) / 2, 0, 1);
                   Vector3 normY = new Vector3(0, 1, (myVertices[x + (y - 1) * myWidth].Position.Y - myVertices[x + (y + 1) * myWidth].Position.Y) / 2);
                   myVertices[x + y * myWidth].Normal = normX + normY;
                   myVertices[x + y * myWidth].Normal.Normalize();
               }
           }

           vb = new VertexBuffer(myDevice, VertexMultitextured.SizeInBytes * myWidth * myHeight, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
           vb.SetData(myVertices);

           //Index
           short[] terrainIndices = new short[(myWidth - 1) * (myHeight - 1) * 6];
           for (short x = 0; x < myWidth - 1; x++)
           {
               for (short y = 0; y < myHeight - 1; y++)
               {
                   terrainIndices[(x + y * (myWidth - 1)) * 6] = (short)((x + 1) + (y + 1) * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 1] = (short)((x + 1) + y * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 2] = (short)(x + y * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 3] = (short)((x + 1) + (y + 1) * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 4] = (short)(x + y * myWidth);
                   terrainIndices[(x + y * (myWidth - 1)) * 6 + 5] = (short)(x + (y + 1) * myWidth);
               }
           }


           ib = new IndexBuffer(myDevice, typeof(short), (myWidth - 1) * (myHeight - 1) * 6, ResourceUsage.WriteOnly, ResourceManagementMode.Automatic);
           ib.SetData(terrainIndices);

       }
       private void GenerateHeightMap()
       {
           // Under Construction
       }

       private void LoadRawHeightData()
       {
           myHeightData = new float[myHeight, myWidth];

           FileStream fs = new FileStream(myHeightAsset, FileMode.Open, FileAccess.Read);
           BinaryReader r = new BinaryReader(fs);

           for (int i = 0; i < myHeight; i++)
           {

               for (int y = 0; y < myWidth; y++)
               {
                   int height = (int)(r.ReadByte() / 10);
                   myHeightData[y, myHeight - 1 - i] = height;
               }
           }
           r.Close();
       }
       private void LoadHeightData()
       {
           float minimumHeight = 255;
           float maximumHeight = 0;

           myWidth = myHeightMap.Width;
           myHeight = myHeightMap.Height;

           Color[] heightMapColors = new Color[myWidth * myHeight];
           myHeightMap.GetData(heightMapColors);

           myHeightData = new float[myWidth, myHeight];
           for (int x = 0; x < myWidth; x++)
               for (int y = 0; y < myHeight; y++)
               {
                   myHeightData[x, y] = heightMapColors[x + y * myWidth].R;
                   if (myHeightData[x, y] < minimumHeight)
                       minimumHeight = myHeightData[x, y];
                   if (myHeightData[x, y] > maximumHeight)
                       maximumHeight = myHeightData[x, y];
               }

           for (int x = 0; x < myWidth; x++)
               for (int y = 0; y < myHeight; y++)
                   myHeightData[x, y] = (myHeightData[x, y] - minimumHeight) / (maximumHeight - minimumHeight) * 30;
           
       }

       private void BuildColorMap()
       {
           Color[] colorMap = new Color[myWidth * myHeight];
           Color[] sand = new Color[myTextures[0].Width * myTextures[0].Height];
           Color[] grass = new Color[myTextures[1].Width * myTextures[1].Height];
           Color[] stone = new Color[myTextures[2].Width * myTextures[2].Height];
           Color[] snow = new Color[myTextures[3].Width * myTextures[3].Height];

           myTextures[0].GetData(sand);
           myTextures[1].GetData(grass);
           myTextures[2].GetData(stone);
           myTextures[3].GetData(snow);

           int cnt = 0;
           for (int x = 0; x < myWidth; x++)
           {                
               for (int y = 0; y < myHeight; y++)
               {
                   cnt++;
                   Color col = Color.Black;
                   float height = myHeightData[x, y];
                   if (height <= 5)
                   {
                       col = sand[sand.Length-1];
                   }
                   else
                       if (height > 5 && height <= 15)
                       {
                           col = grass[grass.Length-1];
                       }
                       else
                           if (height > 15 && height < 25)
                           {
                             col = stone[stone.Length-1];
                           }
                           else
                           {
                               col = snow[snow.Length-1];
                           }
                   colorMap[x + y] = col;
                   ColorMap.SetData(0, new Rectangle(x, y, 1, 1), colorMap, x+y, 1, SetDataOptions.Discard);                    
               }
               
           }            
           //ColorMap.Save("TerrainMap.jpg", ImageFileFormat.Jpg);
       }

       public void Render(GraphicsDevice myDevice)
       {
           Effect effect = RCShaderManager.GetShader(myShader).Effect;            
           if (effect.Parameters["LightPosition"] != null)
               effect.Parameters["LightPosition"].SetValue(myLightPosition);
           if (effect.Parameters["SandTexture"] != null)
               effect.Parameters["SandTexture"].SetValue(myTextures[0]);
           if (effect.Parameters["GrassTexture"] != null)
               effect.Parameters["GrassTexture"].SetValue(myTextures[1]);
           if (effect.Parameters["RockTexture"] != null)
               effect.Parameters["RockTexture"].SetValue(myTextures[2]);
           if (effect.Parameters["SnowTexture"] != null)
               effect.Parameters["SnowTexture"].SetValue(myTextures[3]);

           if (effect.Parameters["LayerMap0"] != null)
               effect.Parameters["LayerMap0"].SetValue(myTextures[0]);
           if (effect.Parameters["LayerMap1"] != null)
               effect.Parameters["LayerMap1"].SetValue(myTextures[1]);
           if (effect.Parameters["LayerMap2"] != null)
               effect.Parameters["LayerMap2"].SetValue(myTextures[2]);
           if (effect.Parameters["LayerMap3"] != null)
               effect.Parameters["LayerMap3"].SetValue(myTextures[3]);

           if (effect.Parameters["full"] != null)
               effect.Parameters["full"].SetValue(ColorMap);

           if (effect.Parameters["UseFullMap"] != null)
               effect.Parameters["UseFullMap"].SetValue(false);
                       
           myDevice.Vertices[0].SetSource(vb, 0, VertexMultitextured.SizeInBytes);
           myDevice.Indices = ib;
           myDevice.VertexDeclaration = new VertexDeclaration(myDevice, VertexMultitextured.VertexElements);
           myDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, myWidth * myHeight, 0, (myWidth - 1) * (myHeight - 1) * 2);
       }
   }


Almost forgot to post my shader:

//////////////////////////////////////////////////////////////////////////////
//                                                                            //
//    NemoKradTerrain.fx Terrain shader by C.Humphrey 02/05/2007              //
//                                                                            //
//    This shader is based on terrain shaders by Riemer and Frank Luna.       //
//  Riemer: http://www.riemers.net                                            //
//  Frank Luna: http://www.moon-labs.com                                    //
//                                                                            //
//    http://randomchaos.co.uk                                                //
//    http://randomchaosuk.blogspot.com                                        //
//                                                                            //
//////////////////////////////////////////////////////////////////////////////

float4x4 wvp : WorldViewProjection;
float3   LightPosition : LightDirection;

texture  LayerMap0;
texture  LayerMap1;
texture  LayerMap2;
texture  LayerMap3;
texture  full;

bool UseFullMap;

sampler Full = sampler_state
{
   Texture   = <full>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};

sampler LayerMap0Sampler = sampler_state
{
   Texture   = <LayerMap0>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};

sampler LayerMap1Sampler = sampler_state
{
   Texture   = <LayerMap1>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};

sampler LayerMap2Sampler = sampler_state
{
   Texture   = <LayerMap2>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};
sampler LayerMap3Sampler = sampler_state
{
   Texture   = <LayerMap3>;
   MinFilter = LINEAR;
   MagFilter = LINEAR;
   MipFilter = LINEAR;
   AddressU  = WRAP;
   AddressV  = WRAP;
};


struct OutputVS
{
   float4 posH         : POSITION0;
   float  shade        : TEXCOORD0;
   float4 tiledTexC    : TEXCOORD1;  
   float4 TextureWeights : TEXCOORD2;  
};

OutputVS Terrain_MultiTexVS(float4 posL         : POSITION0,
                           float3 normalL      : NORMAL0,
                           float4 tiledTexC    : TEXCOORD0,
                           float4 TextureWeights : TEXCOORD1)
{
   OutputVS outVS = (OutputVS)0;
   
   outVS.posH  = mul(posL, wvp);        
   outVS.shade = saturate(saturate(dot(normalL, normalize(LightPosition))));    
   
   if(UseFullMap)        
       outVS.tiledTexC = tiledTexC;
   else
       outVS.tiledTexC = tiledTexC * .6;
       
   outVS.TextureWeights = TextureWeights;    
   return outVS;
}

float4 Terrain_MultiTexPS(float  shade        : TEXCOORD0,
                         float4 tiledTexC    : TEXCOORD1,
                         float4 TextureWeights : TEXCOORD2) : COLOR
{    

   float3 c0 = tex2D(LayerMap0Sampler, tiledTexC);
   float3 c1 = tex2D(LayerMap1Sampler, tiledTexC);
   float3 c2 = tex2D(LayerMap2Sampler, tiledTexC);
   float3 c3 = tex2D(LayerMap3Sampler, tiledTexC);    
   
   c0 *= TextureWeights.x;
   c1 *= TextureWeights.y;
   c2 *= TextureWeights.z;
   c3 *= TextureWeights.w;        
   
   float3 final;

   if(UseFullMap)
   {
       float3 ful = tex2D(Full, tiledTexC * .117);
       final = ful;
   }
   else
       final = (c0 + c1 + c2 + c3);
       
   final *= shade;
   
   return float4(final, 1.0f);        
}

technique Terrain_MultiTex
{
   pass P0
   {
       vertexShader = compile vs_2_0 Terrain_MultiTexVS();
       pixelShader  = compile ps_2_0 Terrain_MultiTexPS();
   }
}


Well I guess my Water class is next, here is an image of it in action:

First some nice calm water, waves at an amplitude of .1


Now at amplitude 1


"lets go surfing now, everybody's learning how..."

Tuesday, May 29, 2007

Engine Design - Ray Picking


Here I will show how I manage to pick 3D objects from the scene using the mouse. Now as you can imagine this can lead to a few issues, one of which is converting a mouses 2D coordinates to 3D coordinates that can be used in the scene.

Before I give you the code (again) I will first try and explain how this method works. I use a very similar thing in my terrain object to pick vert's from the terrain in my PickTerrain method which you will see in my next post.

My Scene picking class has just one public method "GetClickedModel" it is passed the mouse 2D coords and the Scene object we are trying to pick from. First thing we need to do is convert the mouse coordinates to some kind of 3D representation, I do this with a Ray object, this Ray object is given a starting point and a direction. So basically where your ray starts and where you are pointing it. To get the ray's starting point I use the following code.


Vector3 nearSource = camera.Viewport.Unproject(new Vector3(mousecoords.X,mousecoords.Y, camera.Viewport.MinDepth), camera.Projection, camera.View, Matrix.Identity);


This gives us the 3D position of the 2D mouse in the scene. We now need to get the direction the ray is pointing in, to do this we first need to know the furthest point our ray will point at this is archived with this line of code.


Vector3 farSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MaxDepth), camera.Projection, camera.View, Matrix.Identity);



And now to get the direction of the ray we simply do this


Vector3 direction = farSource - nearSource;



We can now construct our ray using the nearSource and the direction Vector3 variables like this.


myRay = new Ray(nearSource, direction);



Now we have the ray constructed we need to find out what objects it hits on it's way from the nearSource to the farSource points in the scene. To do this I wrote a method called RayIntersects that returns the object that is hit by the ray. This method simply goes through the objects in the scene and checks if the ray intersects them. This is not enough as you may end up picking a object that is behind your intended target, to manage this the intersected objects are placed in an array list along with there distance from the nearSource I then iterate through the array finding the object with the shortest distance and return that as the selected object.

Here is the picking class in full:

   public class RCScenePicker
   {
       private static Ray myRay;
       private static ArrayList RayHitList;

       public static Ray RayPicker
       {
           get { return myRay; }
           set { myRay = value; }
       }
       private RCScenePicker() { }

       public static RCObject GetClickedModel(Point mousecoords,RCSceneGraph Scene)
       {
           RCCamera camera = RCCameraManager.ActiveCamera;

           Vector3 nearSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MinDepth), camera.Projection, camera.View, Matrix.Identity);
           Vector3 farSource = camera.Viewport.Unproject(new Vector3(mousecoords.X, mousecoords.Y, camera.Viewport.MaxDepth), camera.Projection, camera.View, Matrix.Identity);
           Vector3 direction = farSource - nearSource;

           direction.Normalize();

           myRay = new Ray(nearSource, direction);
           return RayIntersects(Scene);
                       
       }

       private static RCObject RayIntersects(RCSceneGraph Scene)
       {
           RCObject retVal = null;
           RayHitList = new ArrayList();
           
           for (int n = 0; n < Scene.SceneRoot.Nodes.Count; n++)
           {
               RCObject obj = (RCObject)(((RCObjectNode)Scene.SceneRoot.Nodes[n]).Object);
               BoundingBox bb = obj.ObjectsBoundingBox;
               Nullable<float> distance;
               myRay.Intersects(ref bb,out distance);                
               if (distance != null)
               {
                   object[] thisObj = new object[2];
                   thisObj[0] = (int)distance;
                   thisObj[1] = obj;

                   RayHitList.Add(thisObj);
               }
           }

           // Now get the object nearest the camera.
           object[] lastDist = new object[] {(int)RCCameraManager.ActiveCamera.Viewport.MaxDepth,new RCObject("tmp")};

           for (int o = 0; o < RayHitList.Count; o++)
           {
               if((int)((object[])RayHitList[o])[0] < (int)lastDist[0])
                   lastDist = ((object[])RayHitList[o]);
           }

           if(RayHitList.Count > 0)
               retVal = (RCObject)lastDist[1];

           
           return retVal;
       }        
   }



Once an object is picked I draw it's bounding box to show it has been picked.

Before Pick



After Pick




As I have a picker in my terrain object that will be the focus of my next post.

Sunday, April 08, 2007

Engine Design - Interfaces and RCScene

The interfaces are pretty much as the tutorial has them, I have removed the set methods from the IRCHasMaterial interface as I didn't think they where of much use as I prefer setting fields in my objects with properties rather than using methods. I don't think there is any performance issue with it, it is just a personal preference.

Interfaces
interface IRCObjectComponent
interface IRCHasMaterial : IRCObjectComponent
interface IRCChildRenderer : IRCObjectComponent
interface IRCLoadable : IRCObjectComponent
interface IRCRenderable : IRCObjectComponent

IRCObjectComponent has no methods associated with it, yet.

IRCHasMaterial has a single method:


void SetMaterialProperties();



The method is used to invoke the setting up of shader parameters with the objects material fields. Things like material ambient color and material diffuse color should be set using this method.

IRCChildRenderer has a single method:


void RenderChildren(GraphicsDevice myDevice);



The method should be used to call an objects child render methods. for example one of my particle emitters has an array of RCBilboard objects, this class inherits from IRCChildRenderer as it needs to render each billboard in the array.

IRCLoadable has a single method:


void LoadGraphicsContent(GraphicsDevice myDevice,     ContentManager myLoader);



This method is used to load any graphical content ready to be drawn. For example an object that has a shader that uses an environment cube map would use this to load the cube map asset into memory ready to use.

IRCRenderable has a single method:


void Render(GraphicsDevice myDevice);



Objects that need to be rendered should use this method, so that is probably 99% of all objects in the engine!

The Scene Objects
I have added a new class called RCSceneLoader which can load/save scenes to XML. I implemented this class ages ago and it is now totally out of date, so I will leave this for another post once I have brought it up to date with the engine as it stands now.

The only real change to the RCSceneGraph class is the addition of one method, GetObject. This method is passed the objects name that you want to retrieve from the scene graph and returns the RCObjectNode containing this object which you can then cast to it's expected type and use.


public RCObjectNode GetObject(string Name)
{
    for(int on=0;on < myRoot.Nodes.Count;on++)
    {
        if (((RCObjectNode)myRoot.Nodes[on]).Name == Name)
        return (RCObjectNode)myRoot.Nodes[on];
    }
    return null;
}



Here is an example of it being used:

RCTerrain terrain = (RCTerrain)game.Scene.GetObject("terrain").Object;



RCScenePicker
This class uses ray picking to select an object from the scene with the mouse.


public class RCScenePicker
{
    private static Ray myRay;
    private static ArrayList RayHitList;

    public static Ray RayPicker
    {
        get { return myRay; }
        set { myRay = value; }
    }

    private RCScenePicker() { }

    public static RCObject GetClickedModel(Point mousecoords,
                RCSceneGraph Scene)
    {
        RCCamera camera = RCCameraManager.ActiveCamera;

        Vector3 nearSource = camera.Viewport.Unproject(
            new Vector3(mousecoords.X, mousecoords.Y,
            camera.Viewport.MinDepth), camera.Projection, camera.View,
            Matrix.Identity);

        Vector3 farSource = camera.Viewport.Unproject(
            new Vector3(mousecoords.X, mousecoords.Y,
            camera.Viewport.MaxDepth), camera.Projection, camera.View,
            Matrix.Identity);

        Vector3 direction = farSource - nearSource;

        direction.Normalize();

        myRay = new Ray(nearSource, direction);
        return RayIntersects(Scene);
    }

    private static RCObject RayIntersects(RCSceneGraph Scene)
    {
        RCObject retVal = null;
        RayHitList = new ArrayList();

        for (int n = 0; n < Scene.SceneRoot.Nodes.Count; n++)
        {
            RCObject obj =
                (RCObject)(((RCObjectNode)
                    Scene.SceneRoot.Nodes[n]).Object);
            BoundingBox bb = obj.ObjectsBoundingBox;
            Nullable<float> distance;
            myRay.Intersects(ref bb,out distance);
            if (distance != null)
            {
                object[] thisObj = new object[2];
                thisObj[0] = (int)distance;
                thisObj[1] = obj;

                RayHitList.Add(thisObj);
            }
        }

        // Now get the object nearest the camera.
        object[] lastDist = new object[] {(int)
            RCCameraManager.ActiveCamera.Viewport.MaxDepth,
            new RCObject("tmp")};

        for (int o = 0; o < RayHitList.Count; o++)
        {
            if((int)((object[])RayHitList[o])[0] < (int)lastDist[0])
                lastDist = ((object[])RayHitList[o]);
        }

        if(RayHitList.Count > 0)
            retVal = (RCObject)lastDist[1];

        return retVal;
    }
}



In my next post I will be documenting the changes I have made to the objects in Tutorial 3, the Shader and ShaderManager class, the TexturedQuad, the Camera and CameraManager class.