Jump to content
  • Advertisement
Sign in to follow this  
TimCool

[XNA] Picking

This topic is 2451 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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! :)

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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! :)

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites
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! :)

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
With some little adjustments, i got your code to work, Darg.

It seems to work 100% smoothly...


Thank you so much!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!