My current game project involved the use of selection of object (picking) in 3d world.
So I check out the XNA Creator Club Forum for such resource and found one, Picking Example.
But what I will implement right now is not the same as the sample, but I will extend it a little by only show the closest object to us. In this case, it means that we apply the ‘One selection mode‘ not ‘Multiple selection mode’ like in the xna sample.
Note that also, I will rewrite all the source in my own way which I found it’s very neat :)
Ok, you now know what I’m trying to say. So let’s implement it right away.
But first I want to show you some idea on this demo program.
Looking at the image below you will see that only one object’s name will be show.
Implemenation
Let’s turn to our implementation.
I will cover only the most important part, you should go along by your own after reading this.
In order to select the object in 3d space with mouse cursor, we need to transform our cursor’s position which is 2d space into 3d space. I use ray-casting, which is just the unit vector of direction begin from z-depth equal to 0 and end at the farthest point (but remember it’s only unit vector so the farthest point is 1).
By create the ray, we can calculate the intersection between object’s boundingsphere and our ray with ease. Thus the result is ‘picking’.
To tramsform the position in 2d space into 3d space, we use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //Create ray vector private Ray CreateRay() { //Convert the cursor's 2d-position to 3d position Vector2 originalCursor2D = new Vector2(mouseManager.X, mouseManager.Y); //Create cursor3d for closest and farthest //0 means closest //1 means farthest Vector3 cursor3D_near = new Vector3(originalCursor2D, 0f); Vector3 cursor3D_far = new Vector3(originalCursor2D, 1f); //transform each cursor3d-vector Vector3 transformed_cursor3D_near = device.Viewport.Unproject(cursor3D_near, projection, view, Matrix.Identity); Vector3 transformed_cursor3D_far = device.Viewport.Unproject(cursor3D_far, projection, view, Matrix.Identity); //create the direction of the ray Vector3 direction = transformed_cursor3D_far - transformed_cursor3D_near; direction.Normalize(); //return new ray by using the near point as the source return new Ray(transformed_cursor3D_near, direction); } |
Specify the identity matrix for both point.
Now we get our ray.
Before we go down talk about intersection test with object, I want to tell you about another idea to help us out.
Another to help us out is this, I will use 2 lists.
|_ First, it contains all objects in the world.
|_ Second, it contains only those that passed the intersect test with the ray.
You can found the declaration at very top of the code.
1 2 | List objectsInWorldList; List inSightList; |
Both lists will reference to the objects in our world. For the second list, when it add any new GameObject, it will perform loop check first for the position to insert the new data. This reason is that when you want to draw only the one of the selected object’s name, you will specify the index = 0, and the our job is a lot easier.
It’s nothing special about it, just contain enough information for object to be update, draw into the screen.
And as you can see, this class doesn’t perform any error-checking, it just play as simple as possible to ease the process of coding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | class GameObject { public Model Model; public Matrix[] AbsoluteBoneTransforms; public Vector3 Position; public float Scale; public float RotationX; public float RotationY; public float RotationZ; public List Textures; public Matrix World; public String Name; public Object Tag; //Whether this game object is textured or not public bool Textured { get { if (Textures.Count == 0) return false; else return true; } } public GameObject(String name) { Position = new Vector3(0f); Textures = new List(); Scale = 1f; RotationX = 0f; RotationY = 0f; RotationZ = 0f; Name = name; } // Copy Absolute transforms into array public void CopyAbsoluteTransformsToArray() { AbsoluteBoneTransforms = new Matrix[Model.Bones.Count]; Model.CopyAbsoluteBoneTransformsTo(AbsoluteBoneTransforms); } public void Update(GameTime gameTime) { //calculate the world matrix World = Matrix.CreateScale(Scale) * Matrix.CreateRotationX(RotationX) * Matrix.CreateRotationY(RotationY) * Matrix.CreateRotationZ(RotationZ) * Matrix.CreateTranslation(Position); } public void Draw(GameTime gameTime, Matrix view, Matrix projection, bool isTextured) { if (isTextured) XNAUtil.DrawModel_defaultLight(ref Model, view, projection, World, ref Textures); else XNAUtil.DrawModel_defaultLight(ref Model, view, projection, World); } } Now only stuff you left is the checking of intersection between object and ray. See below. <pre lang="csharp" line="1"> //Coarse collision checking between object and ray public static bool CoarseCollisionObject_with_Ray(Model model, Matrix world, Ray ray) { //get boundingsphere and transform it BoundingSphere sphere = (BoundingSphere)model.Tag; sphere = XNAUtil.TransformBoundingSphere(sphere, world); //Finer collision checking between object and ray public static bool FinerCollisionObject_with_Ray(Model model, Matrix world, Ray ray, ref float? distance) { if (!CoarseCollisionObject_with_Ray(model, world, ray)) return false; Matrix[] modelTransforms = new Matrix[model.Bones.Count]; model.CopyAbsoluteBoneTransformsTo(modelTransforms); foreach (ModelMesh mesh in model.Meshes) { //transfrom the relative sphere from the big coverred the model BoundingSphere sphere = (BoundingSphere)mesh.BoundingSphere; Matrix transform = modelTransforms[mesh.ParentBone.Index] * world; sphere = XNAUtil.TransformBoundingSphere(sphere, transform); //check collision with ray if ( (distance = sphere.Intersects(ray)) != null) return true; } return false; } |
The answer to this is ‘we just want to lower the amount of calculation’, so we roughly check first then it closes enough we then perform the finer check. We have no need to perform finer check all the time.And that’s it, you are finished now :)





