[XNA] Picking

Started by
7 comments, last by TimCool 12 years ago
Hello everyone,

i got stuck developing a game in XNA so i decided i'd ask a community for some help, and i choose this one.



So i been working on a game, it's still very basic and was hoping to work it out to a rpg game. I've followed riemers technieque to implement a heightmap combined with microsoft's example of collision detection. all of that works but now i wanna be able to move my character with my mouse instead of with my mouse. I been looking for a way to do that and found that picking is the solution. After searching for a VERY long time i found (also on riemers) a way to do it. Everything works untill the characters position increases in height, then the picking starts to get inaccurate. I'm using the following code for picking:



public Vector3 Pick()
{
MouseState currentMouseState = Mouse.GetState();
if (currentMouseState.LeftButton == ButtonState.Pressed)
{
Vector3 nearScreenPoint = new Vector3(currentMouseState.X, currentMouseState.Y, 0);
Vector3 farScreenPoint = new Vector3(currentMouseState.X, currentMouseState.Y, 1);
Vector3 nearWorldPoint = GraphicsDevice.Viewport.Unproject(nearScreenPoint, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);
Vector3 farWorldPoint = GraphicsDevice.Viewport.Unproject(farScreenPoint, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);

Vector3 direction = farWorldPoint - nearWorldPoint;

if (direction.Y < 0)
{
float zFactor = -nearWorldPoint.Y / direction.Y;
Vector3 zeroWorldPoint = nearWorldPoint + direction * zFactor;
return zeroWorldPoint;
}
}
return Vector3.Zero;
}

Does anyone know what i'm doing wrong and can give me some hints on how to solve it?



Thanks in advance! :)

Advertisement
Is it because that picks the point on the x-y plane ( or x-z plane if you prefer ) . Do you have terrain or is your world flat ? It won't work for terrain that is not flat or that is at a different height. When your character rises (jumps or climbs), the picking is then not picking beneath him but beyond him, because he is now above the ground or higher than z=0.
My terrain has heights indeed, is there a chance you know how i can get it to work?


Any suggestions wud be appreciated ALOT...



thanks in advance! :)
Hi there, this here is what you need to do follow this link

Hi there, this here is what you need to do follow this link


If you can't make XNA code out of that, let us know. I have picking code for XNA and SlimDX available. But the basic principle is to step along the mouse-ray and test for height and when your height is negative, you know that somewhere between the last test position and the current test position lies the terrain. So it's an approximation method, but it works really well. You'll also need a function that tests for the height at a given x,y coordinate, by first finding what tile quad is there, then determining which of the two triangles you're over, then determining the finalHeight. search for Reimer's terrain picking code for an example.
I tried getting it to work, but i didnt get it to work.

If it's possible to help me out here converting that method to xna, wud be much appreciated!



Thanks in advance! :)
So after being on a break for a while, i decided to pick up where i left on my XNA project...
I been working on "mouse picking" where i was strungling with.
Now i got some working code, but it not accurate when you click on a hill the heightmap and theres ground behind the hill...

This is my code so far...



public Vector3 Pick()
{
Vector3 nearScreenPoint = new Vector3(currentMouseState.X, currentMouseState.Y, 0);
Vector3 farScreenPoint = new Vector3(currentMouseState.X, currentMouseState.Y, 1);
Vector3 nearWorldPoint = GraphicsDevice.Viewport.Unproject(nearScreenPoint, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.CreateTranslation(0, 0, 0));
Vector3 farWorldPoint = GraphicsDevice.Viewport.Unproject(farScreenPoint, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.CreateTranslation(0, 0, 0));
direction = farWorldPoint - nearWorldPoint;
direction.Normalize();


Ray destinationRay = new Ray(nearWorldPoint, direction);
BoundingBox tmp;
for (int x = 0; x < terrainWidth; x++)
{
for (int z = 0; z < terrainLength; z++)
{
var position = new Vector3();
position.X = x;
position.Y = heightData[x, z];
position.Z = z;
tmp = BoundingBox.CreateFromSphere(new BoundingSphere(position, 1F));
if (destinationRay.Intersects(tmp) != null)
{
return position;
}
}
}
return Vector3.Zero;
}


Also, my source is not rly performant, if anyone can help me with that aswell, that wud be much appreciated!

Thanks in advance,
Tim
You can have a look at my XNA code that does this. You start off with having a GetHeightAtLoc(float x, float y) method like this:


public float GetHeightAtLoc(float x, float y)
{
if (x >= 0 && x < size && y >= 0 && y < size)
{
if (x >= size - 1)
{
if (y >= size - 1)
{
return heights[(int)x + ((int)y * size)];
}
float modY = y % 1;
int cX = (int)x;
int cY = (int)y;
return MathHelper.Lerp(heights[cX + (cY * size)], heights[cX + ((cY + 1) * size)], modY);
}
else if (y >= size - 1)
{
float modX = x % 1;
int cX = (int)x;
int cY = (int)y;
return MathHelper.Lerp(heights[cX + (cY * size)], heights[(cX + 1) + (cY * size)], modX);
}
else
{
float modX = x % 1;
float modY = y % 1;
int cX = (int)x;
int cY = (int)y;
float TopLin = MathHelper.Lerp(heights[cX + (cY * size)], heights[(cX + 1) + (cY * size)], modX);
float BotLin = MathHelper.Lerp(heights[cX + ((cY + 1) * size)], heights[(cX + 1) + ((cY + 1) * size)], modX);
return MathHelper.Lerp(TopLin, BotLin, modY);
}
}
else
{
return 0;
}
}


Then you have a method that takes a ray and intersects it with the terrain return a Vector3 point:


public Vector3 GetRayCollisionPoint(Ray ray)
{
ray = ClipRay(ray, boundingBox.Max.Y, boundingBox.Min.Y);
ray = LinearSearch(ray);
Vector3 collisionPoint = BinarySearch(ray);
if (Calc.Contains(collisionPoint, boundingBox))
{
return collisionPoint;
}
else
{
Calc.MapBounds(ref collisionPoint, boundingBox);
collisionPoint.Y = GetHeightAtLoc(collisionPoint.X, collisionPoint.Z);
return collisionPoint;
}
}


That method uses a Linear search along the ray until the ray goes under the terrain, it then performs a Binary search backwards until it reaches a close enough approximation, in this instance within 0.01f of the terrain height. This results in fast accurate results that are hard to break. A Linear only approach would not be accurate enough due to the large stepping size, decreasing the stepping size would drastically increase computation time. A Binary only approach is prone to breaking if for instance you are looking through one hill, out the other side of it above the terrain again and then into another hill, you might well get the intersection point with the second hill. If no intersection point is found then an intersection with the map bounds is performed and the height at that point used, so if you're looking at the map wall or roof it will give you the point just below it on the terrain.

Here are the Linear and Binary search methods:


private Ray LinearSearch(Ray ray)
{
ray.Direction /= 50.0f;
Vector3 nextPoint = ray.Position + ray.Direction;
float heightAtNextPoint = GetHeightAtLoc(nextPoint.X, nextPoint.Z);
while (heightAtNextPoint < nextPoint.Y)
{
ray.Position = nextPoint;
nextPoint = ray.Position + ray.Direction;
heightAtNextPoint = GetHeightAtLoc(nextPoint.X, nextPoint.Z);
}
return ray;
}
private Vector3 BinarySearch(Ray ray)
{
float accuracy = 0.01f;
float heightAtStartingPoint = GetHeightAtLoc(ray.Position.X, ray.Position.Z);
float currentError = ray.Position.Y - heightAtStartingPoint;
int counter = 0;
while (currentError > accuracy)
{
ray.Direction /= 2.0f;
Vector3 nextPoint = ray.Position + ray.Direction;
float heightAtNextPoint = GetHeightAtLoc(nextPoint.X, nextPoint.Z);
if (nextPoint.Y > heightAtNextPoint)
{
ray.Position = nextPoint;
currentError = ray.Position.Y - heightAtNextPoint;
}
if (counter++ == 1000) break;
}
return ray.Position;
}


Let me know if you have any questions!
Portfolio & Blog:http://scgamedev.tumblr.com/
With some little adjustments, i got your code to work, Darg.

It seems to work 100% smoothly...


Thank you so much!

This topic is closed to new replies.

Advertisement