Mostrando entradas con la etiqueta XBox360. Mostrar todas las entradas
Mostrando entradas con la etiqueta XBox360. Mostrar todas las entradas

HLSL code editing in Notepad ++

There are several HLSL syntax highlight add-ins for Visual Studio out there, but if you prefer to use the great NotePad++ to author or edit your shaders, my fellow DirectX MVP Matt Pettineo has written a Notepad++ add-in to allow doing this.

You just need to download the HLSL.xml file from his GoogleDrive account, and in Notepad ++ click Language->Define Your Language->Import and then select the downloaded file. After restarting notepad++, you´ll find a new entry in the Language menu item like this:


By clicking in that new item when you load an HLSL file, you´ll get the following result:


According to him, it supports even SM 5.0 profiles.

Great job Matt !!! :)

New XNA 4 book by Kurt Jaegers [Packt Publishing]

Kurt Jaegers has a new book on XNA 4 Game Development. I´ll review it in a few days, by now, I paste here some word from the author itself:

“This book follows the same style as my previous books on 2D game development with XNA, bringing three different 3D games to life. I cover items such as:
- The basic concepts behind 3D graphics and game design
- Generating geometry with triangles
- Converting height map images into terrain
- An introduction to HLSL, including writing shaders that handle lighting and multi-texturing
- Building a 2D button-based interface to overlay on your 3D action
- Implementing skyboxes for full 3D backgrounds”

More info here and here.

Properly calculating the diffuse contribution of lights in HLSL Shaders

It’s been many years since Vertex and Pixel Shaders came out, and several years too since the Fixed Pipeline is deprecated, but there are still many questions in the forums out there asking about how to properly calculate the diffuse contribution of Lights. This paper has a great tutorial about the issue, and includes a whole Shader that mimics the Fixed Pipeline behavior. However, we will see here how to perform just the basic calculations, just in case you don’t need to emulate the full pipeline.
First thing is to write some D3D9 code that allows you to switch from the old Fixed Pipeline and your own Shaders, using the same parameters. Doing so, you will easily find any behavior differences in light calculations. You can read more about how D3D9 Fixed Pipeline calculates lighting in this page.
When writing shaders, people tend to calculate the diffuse contribution like:
Out.Color = (materialAmbient * lightAmbient) + (materialDiffuse * lightDiffuse * dot(Normal, L));
Where L is the vector from the vertex position (in world coordinates) to the light.
Apart from not doing any specular or emissive calculations (which could not be necessary in many cases, depending on your scenario), there are several mistakes in that approach:
1.- You don’t want the dot to return negative values, because it will black out colors wrongly. So, you need to clamp it to the 0..1 range, using the saturate operator: saturate(dot(Normal, L))
2.- In order to get the same results as the Fixed Pipeline, you should include Attenuation calculations, because they modify the intensity of light with the distance between the point being lit and the light source. Attenuation (as opposed to what its name suggests), not only attenuates light, but also can increase intensity in some circumstances. (See below how to properly calculate attenuation factors)
3.- Once you are calculating attenuation, you should remove the materialDiffuse factor from the previous equation, as you don’t want it to be attenuated too. You will apply it later, when the entire lighting contribution is properly calculated and attenuated.
Keeping those 3 things in mind, the final calculation in a vertex shader would be:
    float4 LightContrib = (0.f, 0.f, 0.f, 0.f);
    float fAtten = 1.f;

    // 1.- First, we store the total ambient light in the scene (multiplication of material_ambient, light_ambient, and any other global ambient component)
    Out.Color = mMaterialAmbient * mLightAmbient;

    // 2.- Calculate vector from point to Light (both normalized and not-normalized versions, as we might need to calculate its length later)
    float pointToLightDif = mLightPos - P;
    float3 pointToLightNormalized = normalize(pointToLightDif);
    
    // 3.- Calculate dot product between world_normal and pointToLightNormalized
    float NDotL = dot(Nw, pointToLightNormalized);        
    if(NDotL > 0)
    {
        LightContrib = mLightDiffuse * NDotL * mLightDivider;     
            
        float LD = length(pointToLightDif);        
        if(LD > mLightRange)
            fAtten = 0.f;
        else
            fAtten = 1.f/(mLightAtt0 + mLightAtt1*LD + mLightAtt2*LD*LD);
        
        LightContrib *= fAtten;
    }
    Out.Color += LightContrib * mMaterialColor;
    Out.Color = saturate(Out.Color);

 

Comparison

First image is the Programmable version. You can slightly tell it by the reflections on the windows.
image
Second image is the Fixed Pipeline version (no real time reflections on windows):
image

Developing a MatrixStack in pure managed C# code (ready for XNA)

Some time ago, we already talked about the possibility of creating your own Math library directly in C#, with no native code. If you take enough care, it can be as fast as performing interop with a native one.
Today, we are showing an additional example on this matter, and we are going to develop our own fast MatrixStack class, all in safe C# code, with no COM interop.

Why?

I never understood well why the MatrixStack class remains to be an iDisposable COM object. Don´t know what kind of optimizations it has internally that justify having disposable resources, but it’s annoying to have the iDisposable overhead with no need for it.
Besides that, MatrixStacks are used in most cases as simple matrix helpers, to traverse object hierarchies. So, replacing the API MatrixStack with your own one should be a piece of cake, and will definitely help you if trying to port your code to some other platform.
Last, but not least, XNA does not have a MatrixStack class. So this C# implementation fits perfectly on it for all that want to use it.
I this example, I will be comparing my own class with the SlimDX MatrixStack, which is nothing more than a wrapper over the D3DX Matrix Stack.

The interface

In order to make the SlimDX stack replacement painless, I will keep the exact same interface in my class (except the COM-related stuff, which is no longer necessary). So, it will have to be something like this:
image

How it works

A MatrixStack, basically supplies a mechanism to enable matrices to be pushed onto and popped off of a matrix stack. Implementing a matrix stack is an efficient way to track matrices while traversing a transform hierarchy.
So, we can clear the stack to the Identity or to any other matrix, we can operate with the top of the stack, and we can add (push) or remove (pop) nodes (or levels, if you want) to the stack.
Example: for a robot arm hierarchy, we would go like this:

1.- Initialize the stack, and load the matrix of the first node in the hierarchy (the upper arm, for example). Now you can use the Top matrix to draw the upper arm.
2.- Create another level on the stack (Push) for the lower arm, and multiply the lower arm matrix. Use the Top matrix to draw the lower arm.
3.- Create another level on the stack (Push) for the hand, and multiply the hand matrix. Use the Top matrix to draw the hand.
The stack itself does nothing you cannot do with regular Matrix multiplications, except that it keeps track of the previous levels you have been creating. So you can go back to the upper node whenever you want. After the previous operations, for instance, if we perform a Pop, we would remove the top node of the stack, and go back to the previous. This way, the new Top node would represent the lower arm matrix, instead of the hand matrix.

The code

Here is my implementation of the MatrixStack. Please keep in mind that it has not been intensively tested, and might contain errors. Use it at your own risk:
    public class MatrixStack
    {
        /// <summary>
        /// Retrieves the Top node matrix of the stack
        /// </summary>
        public Matrix Top = Matrix.Identity;        
        public object Tag = null;
        private List<Matrix> mStack = new List<Matrix>();
        
        /// <summary>
        ///
        /// </summary>
        public MatrixStack()
        {
            LoadIdentity();
        }
        /// <summary>
        /// Clears the stack and loads the Identity Matrix in the top of the stack
        /// </summary>
        public void LoadIdentity()
        {
            mStack.Clear();
            Top = Matrix.Identity;
        }
        /// <summary>
        /// Clears the Stack, and loads the matrix in the top of the stack
        /// </summary>
        /// <param name="pMat"></param>
        public void LoadMatrix(Matrix pMat)
        {
            mStack.Clear();
            Top = pMat;
        }
        /// <summary>
        /// Adds a new level to the stack, cloning the current TOP matrix of the stack
        /// </summary>
        public void Push()
        {
            mStack.Add(Top);
        }
        /// <summary>
        /// Removes the current TOP matrix of the stacks, returning back to the previous one
        /// </summary>
        public void Pop()
        {
            if (mStack.Count > 0)
            {
                Top = mStack[mStack.Count - 1];
                mStack.RemoveAt(mStack.Count - 1);                
            }
        }
        /// <summary>
        /// This method right-multiplies the given matrix to the current matrix (transformation is about the current world origin).
        /// This method does not add an item to the stack, it replaces the current matrix with the product of the current matrix and the given matrix.
        /// </summary>
        /// <param name="pMat"></param>
        public void MultiplyMatrix(Matrix pMat)
        {
            Matrix.Multiply(ref Top, ref pMat, out Top);
        }
        /// <summary>
        /// This method left-multiplies the given matrix to the current matrix (transformation is about the local origin of the object).
        /// This method does not add an item to the stack, it replaces the current matrix with the product of the given matrix and the current matrix.
        /// </summary>
        /// <param name="pMat"></param>
        public void MultiplyMatrixLocal(Matrix pMat)
        {
            Matrix.Multiply(ref pMat, ref Top, out Top);            
        }      
        /// <summary>
        /// Rotates (relative to world coordinate space) around an arbitrary axis.
        /// </summary>
        public void RotateAxis(Vector3 pAxis, float pAngle)
        {
            Matrix tmp;
            Matrix.RotationAxisAngle(ref pAxis, pAngle, out tmp);
            Matrix.Multiply(ref Top, ref tmp, out Top);           
        }
        /// <summary>
        /// Rotates (relative to world coordinate space) around an arbitrary axis.
        /// </summary>
        public void RotateAxisLocal(Vector3 pAxis, float pAngle)
        {
            Matrix tmp;
            Matrix.RotationAxisAngle(ref pAxis, pAngle, out tmp);
            Matrix.Multiply(ref tmp, ref Top, out Top);           
        }
        /// <summary>
        /// Rotates (relative to world coordinate space) the specified Euler Angles
        /// </summary>
        public void RotateYawPitchRoll(float pYaw, float pPitch, float pRoll)
        {
            Matrix tmp;
            Matrix.CreateFromYawPitchRoll(pYaw, pPitch, pRoll, out tmp);
            Matrix.Multiply(ref Top, ref tmp, out Top);            
        }
        /// <summary>
        /// Rotates (relative to world coordinate space) the specified Euler Angles
        /// </summary>
        public void RotateYawPitchRollLocal(float pYaw, float pPitch, float pRoll)
        {
            Matrix tmp;
            Matrix.CreateFromYawPitchRoll(pYaw, pPitch, pRoll, out tmp);
            Matrix.Multiply(ref tmp, ref Top, out Top);           
        }
        /// <summary>
        /// Scale the current matrix about the world coordinate origin
        /// </summary>
        public void Scale(float pX, float pY, float pZ)
        {
            Matrix tmp;
            Matrix.CreateScale(pX, pY, pZ, out tmp);
            Matrix.Multiply(ref Top, ref tmp, out Top);
        }
        /// <summary>
        /// Scale the current matrix about the world coordinate origin
        /// </summary>
        public void ScaleLocal(float pX, float pY, float pZ)
        {
            Matrix tmp;
            Matrix.CreateScale(pX, pY, pZ, out tmp);
            Matrix.Multiply(ref tmp, ref Top, out Top);           
        }
        /// <summary>
        /// Determines the product of the current matrix and the computed translation matrix determined by the given factors (x, y, and z).
        /// </summary>
        public void Translate(float pX, float pY, float pZ)
        {
            Matrix tmp;
            Matrix.CreateTranslation(pX, pY, pZ, out tmp);
            Matrix.Multiply(ref Top, ref tmp, out Top);           
        }
        /// <summary>
        /// Determines the product of the current matrix and the computed translation matrix determined by the given factors (x, y, and z).
        /// </summary>
        public void TranslateLocal(float pX, float pY, float pZ)
        {
            Matrix tmp;
            Matrix.CreateTranslation(pX, pY, pZ, out tmp);
            Matrix.Multiply(ref tmp, ref Top, out Top);
        }
    }

It has to be fast

When you start coding your own MatrixStack, you will soon realize that .Net includes a Generic Collection called Stack. You can use it, although I didn’t. Why?
Because I have separated the management of the Top Matrix of the stack to a member variable, and for the rest I just preferred to use a simple list to keep track of the previous nodes.
The Top Matrix is stored as a member variable to be able to pass it By Reference to the Matrix Multiplication methods. The speed increase avoiding to pass a whole matrix by value is significant. In the example below, it was around a 40% faster.

Test 1 – Reliability

I just made several random operations with the matrix stack, trying to test some of its features by comparing the end Top Matrix, both with a SlimDX MatrixStack and my own. The test operations are:
matrixStack.LoadIdentity();
matrixStack.MultiplyMatrix(Matrix.PerspectiveFovLH(0.8f, 1.6f, 0.1f, 999f));
matrixStack.Translate(10, 10, 10);
matrixStack.Scale(2, 2, 2);
matrixStack.RotateYawPitchRoll(1f, 0f, 0f);
matrixStack.RotateAxis(Vector3.UnitY, 0.75f);
matrixStack.Push();
matrixStack.TranslateLocal(-5, -5, -5);
matrixStack.ScaleLocal(0.1f, 0.1f, 0.1f);
matrixStack.Pop();
matrixStack.MultiplyMatrixLocal(Matrix.RotationZ(1.45f));
The resulting top matrix is:
SlimDX MatrixStack:
  1. [M11:-0.06350367 M12:4.695973 M13:-0.3505643 M14:0]
  2. [M21:0.5231493 M22:0.5700315 M23:2.887983 M24:0]
  3. [M31:18.08297 M32:20 M33:-23.60117 M34:1]
  4. [M41:-0.1968169 M42:0 M43:0.03565279 M44:0]
MyMatrixStack:
  1. {M11:-0.06350368 M12:4.695973 M13:-0.3505643 M14:0}
  2. {M21:0.5231493 M22:0.5700315 M23:2.887982 M24:0}
  3. {M31:18.08297 M32:20 M33:-23.60117 M34:1}
  4. {M41:-0.1968169 M42:0 M43:0.0356528 M44:0}
As you can see, the result is exactly the same.

Test 2 - Speed

Speed is important, so I decided to run the above mentioned operation 10 million times, to se how long it takes to complete both using SlimDX and my own code.
Obviously, if we run in Debug mode (disabling optimizations), there will be a huge performance difference, as the SlimDX dll is already compiled with optimizations. But what happens if we turn all optimizations on when compiling our code?
Here is the result of a small test application:
image
As you can see, the .Net Framework alone is faster than SlimDX, thanks to its optimizations and to the absence of the interop layer.
What happens if we increase the number of iterations to 60 million? The difference is obviously bigger (1.36 seconds faster):
image
Note: This test has been done on an intel i7 CPU at 3.8 Ghz, running on Windows 7 x64 with .Net Framework 4.0.
Note2: SlimDX MatrixStack uses its own Matrix class and operations. My implementation uses my own Matrix implementation, also written in pure C# code.
Conclusion: .Net Rocks. A purely native C++ code would be even faster of course, but if you put in the equation the huge amount of benefits .Net will give you, I really think it’s worth it. Don’t you think?
Cheers !

New XNA 4.0 book by Packt Publishing

Packt has released a new book on XNA 4.0 development: XNA 4.0 Game Development by Example: Beginner's Guide – Visual Basic Edition.

2403EXP_XNA%204_0%20Game%20Developement%20by%20Example

I think I will have the chance to review the book, so I’ll tell you more when I’ve read it, but it looks promising. Seems to be a must for anyone that is facing XNA 4.0 development in Visual Basic.

Cheers !

Ramblings about the excellent Windows Media Center

Today, I was trying to setup a Windows Media Center Extender, to be able to see my movies (stored in my PC) through my XBox in the living room. In theory, it´s easy, but you can get into some troubles I´d like to point, just in case that might help you…

You can get some basic knowledge about Media Center extenders here.

First, try to connect your XBox with the PC, through Settings->Network->Connection to computer. If the connection is established successfully, you probably won´t have any problem configuring the Media Center Extender. But if you have problems, check:

1.- That your router supports Multicast Filtering, as some routers, like the Cisco EPC3825 (the one I was trying first), seem to have problems with that. In fact, in the settings dialogs of that router, you won´t find any option about Multicast Filtering. I can tell you that I tried every single possibility for a couple of hours with that router, and no luck. I switched to a different one (from Linksys), and everything worked like a charm…

2.- If your router is supposed to work with this feature, make sure you have enabled the mentioned “Filter Multicast” option (probably available in the Security settings tab of the router), and also that you have enabled the uPnP (probably in the Management settings tab of the router).

3.- If still have problems, you can check your Firewall settings to search for the needed open ports and so on… You can read more here.

One you have properly linked your PC and XBox 360, you can start the Windows Media Center on your PC, to choose what folders you will be sharing.

Some tips about folder structures, in order to see the covers of the movies, and additional info:

1.- Put each movie in a separate folder, as WMC will look this way for additional info for each movie

2.- If you want to manually download a cover for a movie or video, you just need to put the picture in the movie folder, with the name “folder.jpg”. WMC will load it automatically.

3.- If you want to put additional information, like movie specs, genre, etc, you should add some DVDID XML files with a certain format that will help WMC identifying the movie. You can download those files from http://dvdxml.com or even better, use one of the available metadata managers out there. I have tried YAMMM and works pretty well.

4.- If your XBox is downloading the movie covers and info again and again, each time you enter the Windows Media Center, or if it takes long to recover the covers, etc, that´s probably because you don´t have indexed the shared folders on your PC. Just make sure that the Indexing Service is installed and enabled (Start->Control Panel->Programs and Features->Turn on/off Windows features->Indexing Service), and also make sure that the shared folders for the movies are added to the index (Rightclick->properties->advanced->allow to add this folder to the index).

Some tips about YAMMM

1.- It´s a Windows Service, so don´t expect any User Interface, except for setting up the application. It’s run in background, monitoring the folders you tell it to for changes, and downloading automatically the info and covers.

2.- It expects movies to be in separate folders, and users folder’s name (not file’s name) to identify what movie you are talking about. If it doesn´t identify what movie it is, it won´t download anything. If it does, it can automatically rename the folder and movie files (with a more standard name, if you indicate it to do so), and will start downloading.

3.- YAMMM won´t find the correct movie if you don´t use the original movie name for the folder name. So forget any any translated version.

4.- To help YAMMM finding it, you can include the year of the movie, like this: “American Gangster (2007)”. That will help, a lot…

5.- If your movie is divided into several files (part 1, part 2, etc), YAMMM will automatically create a playlist for them, so WMC will identify them as a single movie. By default, after doing this you will see that WMC adds multiple entries for your movie: one for the playlist, and one for each part your AVI or DVD is divided into. In order to hide the parts, and live only the playlist, you can rename the AVI parts like: “video 1.avi” to “video 1.avi2”. This way, WMC won´t identify that part as a movie, and the reference file (playlist) will still work. (You should make sure that the playlist has modified the reference names too, by simply opening it with the WordPad).

Some technology behind Kinect

I cannot wait to have this thing -tomorrow is the day here in Europe-. I must admit that I was a bit skeptical at first. I could barely test the device at the Microsoft Spain&Portugal MVP Open Day, several weeks ago, but the more videos I see, the more convinced I am about the potential of the device. For instance, check this XBox dashboard demo:

The smoothness and precision of hand movements are amazing. It could even be an alternative to multi-touch technology. It seems that Microsoft is almost out of stock in the U.S. what means that it’s been a very successful launch. Congrats!

Adaptability and calibration

The Kinect includes a small electric motor that allows the device to tilt the camera up and down (up to 30 degrees), and also operates the zooming functionalities. This allows the device to properly adapt to any space. If you want to know more about the motor, you can check this site.

Sound Processing

The Kinect includes an array of 4 microphones, which provide:

  • Acoustic source localization: 3D localization or positioning of sound, to help detecting which player is talking, for example.
  • Ambient noise suppression: To make speech recognition more efficient, it filters sounds to remove any ambient noise that could distort voices and sounds to be processed.

Both functionalities allow to control the XBox with your voice (see above video for a demo), and probably will enable headset-free chat on XBox Live (though I haven’t seen this in action yet).

More about Voice Recognition on Kinect

Facial and skeletal Recognition

The video camera included on Kinect allows to automatically recognize players just by looking at them. So, you just need to jump in the game, and it will automatically detect you, keeping your scores, etc. This is awesome.

Gesture and movement recognition

Of course, the most important feature of Kinect is gesture and movement reco. It can handle up to 6 players, but making motion recognition to two of them at a time. Regarding this, both hardware and software are extremely important.

Hardware was firstly developed by the company –later acquired by Microsoft- PrimeSense. The diagram of the setup is:

As you can see, the righter part of the diagram includes a normal color camera, to detect the human visual spectrum with a resolution that seems to be: 640x480, according to some non-official sources. It is used for facial recognition and to include images of players in games, among other things.

The other part is regarding depth, space or 3D detection, whatever you want to call it.

How can a 2D camera extract 3D information?

The secret is the IR setup it includes, with both a projector and a camera. It first projects thousands of IR dots to you and your living room, and then read that information back with the IR camera (Depth Image CMOS). Depending on the size and distance between the projected dots, it calculates the depth map. The following is a representation of the result of this depth map:

This IR dots that Kinect projects use a similar technology to the one used in those video-recording cameras which include the so-called Night-Shot technology.

Those cameras project IR light in front of you with a small IR beamer, and then switch to a sensor which is sensible to IR light. This way, they project light ahead (what makes seeing possible), but in an invisible way to the human eye.

Knowing that, anyone else is thinking the same thing than me? I guess so…

This is what happens if you record your Kinect with a Night-Shot camera (I recommend you to see it in FullHD):

Et Voila! There are the dots. The IR projector seems to be projecting a matrix of 320x240 dots at 30 frames per second (again, according to non-official sources). Another example:

So, starting with info like this:

image

Kinect takes the distance between dots, and calculates a depth-map of your living room:

Some personal conclusions

I’m sure that the hardware included on Kinect is awesome. It’s been launched with a few titles, but I can guarantee you that best is yet to come. Now it’s time for software. New titles will come, and new ways to explore and use this device. And I’m 100% sure they will be incredible.

Go Microsoft !!

Want to know more?

http://www.ifixit.com/Teardown/Microsoft-Kinect-Teardown/4066/1

http://en.wikipedia.org/wiki/Kinect

http://www.t3.com/feature/exclusive-how-does-microsoft-xbox-kinect-work?

http://consolepress.com/main/2010/08/23/the-tech-that-drives-kinect/

Comment, I mean… Connect, I mean… KINECT !!!!!

It’s closer. Closer….. I cannot wait to have fun at home with this thing.

The dancing game promises to be epic, with a bunch of guys and a couple of beers…. ufff… just check out the guy with glasses in the video…

image

EPIC !!!!!

Final de Microsoft Imagine Cup España 2009

El próximo 7 de Mayo, seré uno de los ponentes en la final del concurso Imagine Cup 09 en su edición Española, que tendrá lugar si no me equivoco en la Universidad Complutense de Madrid.

Se trata de un concurso internacional que promueve Microsoft para apoyar a los nuevos talentos desde la Universidad. Los premios van desde un smartphone o una XBox360 hasta un viaje a El Cairo con todos los gastos pagados para participar en la final mundial de Imagine Cup. Según la propia web:

“En la edición de Imagine Cup en España participan estudiantes universitarios españoles con inquietud por la tecnología y en particular por el desarrollo de aplicaciones innovadoras y creativas. La séptima edición internacional de Imagine Cup tiene como objetivo encontrar soluciones a los problemas reales de nuestros días.”

Más información haciendo click en la foto.

Collision detection in XNA

[This article continues the prelude about collision detection published here. It refreshes and completes the older Collision detection in XNA posts –parts I, II and III-, written a long time ago and which were demanded to be completed many times. Finally, here it is]

Simple Collision Detection

XNA includes simple intersection tests for shapes like: Bounding Spheres, AABB (Axis Aligned Bound Box), Planes, Rays, Rectangles, Frustums, etc, and any combination of them. And what is even more useful, 3D models already have bounding spheres for their parts.
Using those tests, almost any kind of game-like intersection can be achieved. You must remember that a Mesh-Whatever intersection is expensive (depending in the number of polygons, of course), and should be left for special cases in which a very high intersection accuracy is needed. So, it’s usually preferred to approximate a complex geometry by a bunch of spheres or boxes, than using the real triangles (see Part 1).
There´s a very good post at Sharky´s blog about XNA collisions, specially focused in approximating generic shapes with bounding spheres. You can find it here.

Accurate collision detectionDespacho_2_Low


As commented earlier, sometimes a more accurate intersection method is needed. For example, for lighting calculations (where tracing rays are the best and more usual approach to modeling lights –see pic on your right-), the Ray-Mesh intersection test seems to be the best option. In D3D, there´s a Mesh.Intersect method ready for you, that performs the desired intersection test, but in XNA, there´s not such a method, and we will have to do it on our own.
To do it, we will need a system-memory copy of the geometry. Unfortunately, meshes are usually created with the ReadOnly flag in XNA (to make their management fast), what won´t allow us to access their geometry at runtime. To do so, we´ll have to deal with Custom Content Processing.
Note: Here you will find and introduction to Custom Content Processing.

Implementing a Custom Content Processor for collision detection

The solution to the previous problem is to make a custom content processor that stores a copy of the geometry information at build time, where there´s still access to it. All the information needed will be stored in a new class we will name MeshData.
 public class MeshData
{
     public VertexPositionNormalTexture[] Vertices;
     public int[] Indices;
     public Vector3[] FaceNormals;
 
     public MeshData(VertexPositionNormalTexture[] Vertices, int[] Indices, Vector3[] pFaceNormals)
     {
         this.Vertices = Vertices;
         this.Indices = Indices;
         this.FaceNormals = pFaceNormals;
     }
}
 
You can put in here all the information you need. By the moment, it will be enough to store the vertices, indices and face normals.
When VisualStudio processes each model with our ContentProcessor, it will write the model´s data to an XNB file. When it finds a MeshData object, will search for a writer that is able to serialize it, so we have to write our custom ContentTypeWriter for the MeshData class:
[ContentTypeWriter]
public class ModelVertexDataWriter : ContentTypeWriter<MeshData>
{
    protected override void Write(ContentWriter output, MeshData value)
    {
        output.Write((int)value.Vertices.Length);
        for (int x = 0; x < value.Vertices.Length; x++)
        {
            output.Write(value.Vertices[x].Position);
            output.Write(value.Vertices[x].Normal);
            output.Write(value.Vertices[x].TextureCoordinate);
        }
 
        output.Write(value.Indices.Length);
        for (int x = 0; x < value.Indices.Length; x++)
            output.Write(value.Indices[x]);
 
        output.Write(value.FaceNormals.Length);
        for (int x = 0; x < value.FaceNormals.Length; x++)
            output.Write(value.FaceNormals[x]);
    }
 
    public override string GetRuntimeType(TargetPlatform targetPlatform)
    {
        return typeof(MeshData).AssemblyQualifiedName;
    }
    public override string GetRuntimeReader(TargetPlatform targetPlatform)
    {
        return "ContentProcessors.ModelVertexDataReader, ContentProcessors, Version=1.0.0.0, Culture=neutral";
    }
}
 
In a similar way, when the ContentPipeline tries to read back the XNB file, it will search for a deserializer for the type MeshData, so we have to write our own ContentTypeReader:
 public class ModelVertexDataReader : ContentTypeReader<MeshData>
{
     protected override MeshData Read(ContentReader input, MeshData existingInstance)
     {
         int i = input.ReadInt32();
         VertexPositionNormalTexture[] vb = new VertexPositionNormalTexture[i];
         for (int x = 0; x < i; x++)
         {
             vb[x].Position = input.ReadVector3();
             vb[x].Normal = input.ReadVector3();
             vb[x].TextureCoordinate = input.ReadVector2();
         }
 
         i = input.ReadInt32();
         int[] ib = new int[i];
         for (int x = 0; x < i; x++)
             ib[x] = input.ReadInt32();
 
         i = input.ReadInt32();
         Vector3[] normals = new Vector3[i];
         for (int x = 0; x < i; x++)
             normals[x] = input.ReadVector3();
 
         return new MeshData(vb, ib, normals);
     }
}
Finally, our Custom Content Processor that fills up the MeshData objects for each model that goes through it. Note: some parts taken from ZiggyWare
    [ContentProcessor(DisplayName = "Custom Mesh Processor")]
    public class PositionNormalTexture : ModelProcessor
    {
        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            ModelContent model = base.Process(input, context);
            foreach (ModelMeshContent mesh in model.Meshes)
            {
                // Put the data in the tag.
                VertexPositionNormalTexture[] vb;
                MemoryStream ms = new MemoryStream(mesh.VertexBuffer.VertexData);
                BinaryReader reader = new BinaryReader(ms);
 
                VertexElement[] elems = mesh.MeshParts[0].GetVertexDeclaration();
                int num = mesh.VertexBuffer.VertexData.Length / VertexDeclaration.GetVertexStrideSize(elems, 0);
 
                vb = new VertexPositionNormalTexture[num];
                for (int i = 0; i < num; i++)
                {
                    foreach (VertexElement e in elems)
                    {
                        switch (e.VertexElementUsage)
                        {
                            case VertexElementUsage.Position:
                                vb[i].Position.X = reader.ReadSingle();
                                vb[i].Position.Y = reader.ReadSingle();
                                vb[i].Position.Z = reader.ReadSingle();
                                break;
                            case VertexElementUsage.Normal:
                                vb[i].Normal.X = reader.ReadSingle();
                                vb[i].Normal.Y = reader.ReadSingle();
                                vb[i].Normal.Z = reader.ReadSingle();
                                break;
                            case VertexElementUsage.TextureCoordinate:
                                if (e.UsageIndex != 0)
                                    continue;
                                vb[i].TextureCoordinate.X = reader.ReadSingle();
                                vb[i].TextureCoordinate.Y = reader.ReadSingle();
                                break;
                            default:
                                Console.WriteLine(e.VertexElementFormat.ToString());
                                switch (e.VertexElementFormat)
                                {
                                    case VertexElementFormat.Color:
                                        reader.ReadUInt32();
                                        break;
                                    case VertexElementFormat.Vector3:
                                        reader.ReadSingle();
                                        reader.ReadSingle();
                                        reader.ReadSingle();
                                        break;
                                    case VertexElementFormat.Vector2:
                                        reader.ReadSingle();
                                        reader.ReadSingle();
                                        break;
 
                                }
                                break;
                        }
                    }
                } // for i < num
 
                reader.Close();
 
                int[] ib = new int[mesh.IndexBuffer.Count];
                mesh.IndexBuffer.CopyTo(ib, 0);
                Vector3[] normals = new Vector3[mesh.IndexBuffer.Count / 3];
                for (int i = 0, conta = 0; i < mesh.IndexBuffer.Count; i += 3, conta++)
                {
                    Vector3 v0 = vb[mesh.IndexBuffer[i]].Position;
                    Vector3 v1 = vb[mesh.IndexBuffer[i + 1]].Position;
                    Vector3 v2 = vb[mesh.IndexBuffer[i + 2]].Position;
                    Vector3 edge1 = v1 - v0;
                    Vector3 edge2 = v2 - v0;
                    Vector3 normal = Vector3.Cross(edge1, edge2);
                    normal.Normalize();
                    normals[conta] = normal;
                }
 
                mesh.Tag = new MeshData(vb, ib, normals);
 
            } // foreach mesh
            return model;
        }
    }
Now that we have all the information needed, we will focus in the Collision Detection implementation itself.

Implementing the Ray-Mesh test using the MeshData

Many people thinks that the D3D Mesh.Intersect method does some kind of optimized "magic" to test for intersection, but in fact it just loops through all the triangles of the mesh doing a triangle-ray intersection test, and keeping track of the closest collision point (or all of them, depending on the overloaded version you use). Of course it applies some well known optimizations, like quick discarding polygons, back faces, and so on. That is exactly what we have to do now with the info generated at the Content Processor.
The following method performs a Ray-Mesh test getting as parameter a MeshData object generated by the previous content processor. Note that a lot of optimization can be done here, quick discarding triangles. Just google a bit for it.
public static bool RayMesh(Vector3 orig, Vector3 dir, MeshData pMesh, ref Vector3 pContactPoint, ref float pDist, ref int pFaceIdx)
{
       Vector3 maxContactPoint = Vector3.Zero;
       int maxFaceIdx = -1;
       float minT = float.MaxValue;
       for (int i = 0, countFace = 0; i < pMesh.Indices.Length; i += 3, countFace++)
       {
           int ia = pMesh.Indices[i];
           int ib = pMesh.Indices[i + 1];
           int ic = pMesh.Indices[i + 2];
           Vector3 v0 = pMesh.Vertices[ia].Position;
           Vector3 v1 = pMesh.Vertices[ib].Position;
           Vector3 v2 = pMesh.Vertices[ic].Position;
 
           double t = 0f;
           double u = 0f;
           double v = 0f;
           if (RayTriangle(orig, dir, v0, v1, v2, ref t, ref u, ref v))
           {
               Vector3 appPoint = orig + (dir * (float)t);
               if (t < minT)
               {
                   minT = (float)t;
                   maxFaceIdx = countFace;
                   maxContactPoint = appPoint;
               }
           }
       }
       pContactPoint = maxContactPoint;
       pFaceIdx = maxFaceIdx;
       pDist = minT;
       return (minT < float.MaxValue);
}
The only part left is the Ray-Triangle intersection test, but there is so much information around the net about this issue that I´ll just leave it for you. However, you can check the following links:
http://www.devmaster.net/wiki/Ray-triangle_intersection
http://www.graphics.cornell.edu/pubs/1997/MT97.html
http://www.graphics.cornell.edu/pubs/1997/MT97.pdf
http://www.acm.org/pubs/tog/editors/erich/ptinpoly/
Hope with 4 references is enough, and that you liked the post.
Enjoy!