I want someting relatively simple. When i click on my mouse i want the location on the terrain that i clicked on. So i implemented picking in my program. I used TrianglePicking because i needed the location of the triangle that i click on, not the bounding box of the terrain. My code is simple enough, i think.
class ClickPickResults extends TrianglePickResults{
public void processPick(){
System.out.println(getNumber());
}
}
and
class GameMouseListener implements MouseInputListener{
private Camera cam = GameWorld.getDisplay().getRenderer().getCamera();
private HoverResults hoverresults = new HoverResults();
private ClickPickResults clickresults = new ClickPickResults();
private Node node = GameWorld.getNode();
private Vector2f screenpos = new Vector2f();
private Vector3f worldcoords;
private Ray mouseray = new Ray();
public void onButton(int button, boolean pressed, int x, int y) {
if (button == 0){
if (!pressed){
recalcHighLightedUnit(x,y);
if (LocalPlayer.getLocalPlayer().getHighlightedUnit()==null){
System.out.println("click");
mouseray.setOrigin(cam.getLocation());
mouseray.setDirection(worldcoords.subtractLocal(mouseray.getOrigin()));
clickresults.clear();
node.calculatePick(mouseray, clickresults);
}
}
}
}
when i try this i get the following exception
java.lang.OutOfMemoryError: Java heap space
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Exception in thread "Thread-1" java.lang.OutOfMemoryError: Java heap space
I am guessing that this because the terrain has too many triangles. What would be a better way of getting what i want?
If your terrain is from blocks, you could make smaller blocks, and clear out (i think its called) the collision tree from blocks unlike the player will click on.
An idea for picking terrain:
Get the nearest block by picking only the bounding box
Calculate where the ray penetrates the bounding box
From there go on in the direction of the ray, and find the first point where the terrain height is bigger than the rays Y coordinate at that position
If no such point is found, get the next nearest block and go to step 2
I think this picking method would be faster, and use no extra memory.
I use triangle pick results. I haven't tested this on large terrains, right now I'm only using a single terrainblock.
Some of this code is mine, some of it is grabbed from elsewhere.
My code was/is messy. I cut a lot out of this method. I hope left enough so it works/makes sense:
I'm sure there are better ways to do this.
Steps:
1. Make a ray.
2. Call findPick or findTrianglePick on the terrain, use the ray, and store the results
3. Go through the results. Grab the mesh from the result. Grab the triangle from the mesh.
4. Find the spot on the triangle that the ray intersects with
Vector2f screenPos = new Vector2f();
// Get the position that the mouse is pointing to
screenPos.set(am.getHotSpotPosition().x, am.getHotSpotPosition().y);
// Get the world location of that X,Y value
Vector3f worldCoords = client.getDisplay().getWorldCoordinates(screenPos, 1f);
// Create a ray starting from the camera, and going in the direction
// of the mouse's location
Ray mouseRay = new Ray(client.cam.getLocation(), worldCoords
.subtractLocal(client.cam.getLocation()));
pick.clear();
terrain.findPick(mouseRay, pick);
for (int i = 0; i < pick.getNumber(); i++) {
PickData pData = pick.getPickData(i);
ArrayList tris = pData.getTargetTris();
TriMesh mesh = (TriMesh) pData.getTargetMesh();
int[] indices = new int[3];
Vector3f [] verticies = new Vector3f[3];
if(tris.size() < 1)
continue;
//check where the ray hits the triangle? get xy from that?
Vector3f target = new Vector3f();
mouseRay.intersectWhere(verticies[0], verticies[1], verticies[2], target);
x = target.x;
y = target.z;
}
// System.out.println("Picked: (" + x + ", " + y + ")");
if ( x == 0 && y == 0)
{
//probably was a bad pick so ignore
return;
}
//here is where i would tell my character to walk to x y
The problem with out of memory comes from terrain mesh’s collision tree being generated takes up memory. I only have it after 20 or so blocks. So you might have some other issue, or an absolutely huge block.
ok i can't help but think that i am using statistics incorrectly, the number of meshes, vertices and triangles keep on skyrocketing, even though i don't think im doing anything to the scene graph. All i need to do is enable statistics then use getStatistics… right?
256x256 could be too big unless you have some newer graphics card. Try using TerrainPage with 257x257 heightmap, and 129x129 (or smaller) blocksize. In terms of performance bigger blocks are better, but graf card is limited on how big can handle. Out of memory error is most probably because the single big block too. Try making the blocksize a configuration parameter, if the user has modern graf card and lots of RAM, he can choose bigger blocksize, if not, he can use smaller blocks and have the CPU more work to do.
Hi!
If your terrain is from blocks, you could make smaller blocks, and clear out (i think its called) the collision tree from blocks unlike the player will click on.
An idea for picking terrain:
1. Get the nearest block by picking only the bounding box
2. Calculate where the ray penetrates the bounding box
3. From there go on in the direction of the ray, and find the first point where the terrain height is bigger than the rays Y coordinate at that position
4. If no such point is found, get the next nearest block and go to step 2
I think this picking method would be faster, and use no extra memory.
Hi,
I tried your algorithm and it works perfectly (I'm using a simple dichotomy search for the collision point along the ray), at least on a single block. Very fast too, often less than 10 iterations for 0.01 (absolute delta, not percent) precision in height.
Thanks for the idea ;)
(If anyone is interested in the code, please let me know)
ok i can't help but think that i am using statistics incorrectly, the number of meshes, vertices and triangles keep on skyrocketing, even though i don't think im doing anything to the scene graph. All i need to do is enable statistics then use getStatistics... right?
You need to call clearStatistics once per game loop too I believe (look in simplegame example).
...
BoundingPickResults pr = new BoundingPickResults();
pr.clear();
pr.setCheckDistance(true);
rootNode.findPick(mouseRay, pr);
...
// Check if there are picking results, then iterate over them
...
Ray r = pr.getPickData(i).getRay();
// Compute the distance between the camera and the collision point on the top of the terrain bounding volume
float minHeight = camera.z - ((BoundingBox) tb.getModelBound()).zExtent * 2;
float minDist = FastMath.abs(minHeight / r.getDirection().normalize().z);
// Compute the distance between the camera and the collision point on the bottom of the terrain bb
float maxHeight = camera.z;
float maxDist = FastMath.abs(maxHeight / r.getDirection().normalize().z);
// We now perform a Newton search along the ray, between minDist and maxDist
float delta = maxDist - minDist;
float dist = minDist + delta / 2;
boolean found = false;
Vector3f target;
float x, y, z;
// Counter to avoid infinite loop or heap errors
int count = 0;
do {
// get the point along the ray at distance dist from camera
target = r.getDirection().normalize().mult(dist);
// change to world coordinates
x = target.x + camera.x;
y = target.y + camera.y;
z = target.z + camera.z;
// compute the height of the point right below target
float realHeight = tb.getHeight(new Vector2f(x + tb.getSize()/2, tb.getSize()/2 - y));
// if difference in height is lower than 0.01 (arbitrary), stop here
if (FastMath.abs(z - realHeight) < 0.01) found = true;
If someone is interested in this, I updated my code to make it work with current CVS and with a terrain page.
As a side note, I put this in a pick() method in a class which extends TerrainPage. Do you think it's the right thing to do, i.e. how would jME do it if it had this feature? By the way, if you want to include this in jME I'd be flattered