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;


           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;


           // 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.

Saturday, May 19, 2007

Engine Design - Fog

I thought I would post this class first as it is relatively simple to implement and can be put into any XNA engine and is not specific to the HM Engine.

Right, the effect of this class can be seen in the earlier post I gave for fog. So without further a do, here is the class:

   public sealed class Fog
       public static Color FogColor;
       public static bool FogEnable;
       public static float FogStart;
       public static float FogEnd;
       public static FogMode FogTableMode;

       public static void Draw(GraphicsDevice myDevice,GameTime gameTime)
           if (myDevice.RenderState.FogEnable != FogEnable)
               myDevice.RenderState.FogEnable = FogEnable;

           if (FogEnable)
               if(myDevice.RenderState.FogColor != FogColor)
                   myDevice.RenderState.FogColor = FogColor;
               if(myDevice.RenderState.FogStart  != FogStart)
                   myDevice.RenderState.FogStart = FogStart;
               if(myDevice.RenderState.FogEnd != FogEnd)
                   myDevice.RenderState.FogEnd = FogEnd;
               if(myDevice.RenderState.FogTableMode != FogTableMode)
                   myDevice.RenderState.FogTableMode = FogTableMode;

So all you have to do is populate the Fog class static properties with the required values and in your assemblies Draw method call the static Draw method, like this:

       protected override void Draw(GameTime gameTime)
           Fog.FogEnable = true;
           Fog.FogColor = Color.WhiteSmoke;
           Fog.FogStart = 95f;
           Fog.FogEnd = .999f;            
           Fog.FogTableMode = FogMode.Linear;

           // Your drawing stuff here...

           Fog.Draw(graphics.GraphicsDevice, gameTime);

           // Possibly more code here...


And with that you can have a nice fog effect in your engine.

Thursday, May 10, 2007

Engine Design

Well I guess thats all the modifications I have made to the core elements. What I intend to document next are my additional objects and methods like how I am currently doing Fog, Ray picking, Terrain, Water, Fire, Sound and the particle system.