The shaking objects at very far distance from origin problem (revisited)

Hi,

I read a couple threads around here concerning the shaking geometries at a very far away distance from the origin. I totally understand it’s due to the floating point (lack of) precision when GLSL is projecting at big numbers translations.

From my point of view, I can see distracting enough shakiness from around X:32000/Z:32000 which in my little project is only chunk 80,80. I mean, even tough it’s very far away, it’s really not that far away in an “infinite open world” type of application like what I’m after. I understand even Minecraft has a maximum XZ and I would be OK if those values in my app were in the magnitude of say, 1000,1000 but 80,80 is really not that much to play with for a sandbox.

TL;DR’ing?

inb4: BUT BEN! What the F*** are you thinking having the camera at such far distances from the origin!? Move everything and let the camera stay at the origin instead!

Well, it’s PRECISELY the point of this post. That’s what I just tried today and it’s exactly the same thing. I thought this would be the case too, because I mean, logically, even if the camera stays at 0,0 I still have to translate the rootNode to -32000,-32000 so the calculations will still suffer from the lack of float point precision no?

Anyhow, I’m turning to you guys. I’m sure somebody already made an open world type of application and has a pocket full of epicness to enlighten my poor soul. For the record, what I’m exactly doing at the moment (and it doesn’t work) is:

[java]
// Anti-shaking fix (float precision problem when very very far from spawn point)
Vector3f tmp_offset_chunk = world.toTerrainLocationScaled(new Vector3f(player.getPhysicsLocation().x, 0, player.getPhysicsLocation().z));
if(!tmp_offset_chunk.equals(Vector3f.ZERO)){
offset_camera_chunk.addLocal(tmp_offset_chunk); // Accumulate chunk displacements
rootNode.setLocalTranslation(world.fromTerrainLocationScaled(offset_camera_chunk).negate()); // Compensate by displacing everything back in the opposite direction
player.setPhysicsLocation(player.getPhysicsLocation().add(world.fromTerrainLocationScaled(tmp_offset_chunk).negate())); // Compensate by displacing everything back in the opposite direction
cam_node.setLocalTranslation(cam.getLocation().add(world.fromTerrainLocationScaled(tmp_offset_chunk).negate()));
cam.setLocation(cam.getLocation().add(world.fromTerrainLocationScaled(tmp_offset_chunk).negate()));
cam_to_world_old_pos = Vector3f.ZERO;
cam_to_world_pos = Vector3f.ZERO;
}
[/java]

I do appreciate all inputs, even if you’re not sure, it’s always worth a try in my book. Thx for your interest.

@.Ben. said: Well, it's PRECISELY the point of this post. That's what I just tried today and it's exactly the same thing. I thought this would be the case too, because I mean, logically, even if the camera stays at 0,0 I still have to translate the rootNode to -32000,-32000 so the calculations will still suffer from the lack of float point precision no?

No. Because the “move the world, keep the camera at 0,0,0” thing means that you construct the world relative to the camera, too. Tile 80, 80 is at scene graph 0,0 when the camera is in tile 80, 80. You only load the tiles around the camera and you give them local coordinates.

This is precisely what the PagedGrid is doing in the IsoSurfaceDemo, by the way. Each tile is constructed relative to its own origin and the paged grid manages it’s camera relative location.
https://code.google.com/p/simsilica-tools/source/browse/trunk/Pager/src/main/java/com/simsilica/pager/PagedGrid.java

It calls tiles Zones and it’s got some internal complexity to deal with child zones and stuff… but maybe the idea is clear enough. It might be hard to follow but you can see how it’s used here:
https://code.google.com/p/simsilica-tools/source/browse/trunk/IsoSurfaceDemo/src/main/java/com/simsilica/iso/demo/TerrainState.java#267

OK so basically, if I get you right, my mistake right now is that I attach a TerrainQuad at 32000 then move the rootNode back -32000, which LOOKS LIKE it’s at 0,0 while its wrapper node is very far away. But aren’t all the setLocalTranslation() we do on each node and leaf-nodes all the way to the last leaves all merged down and computed to a final value before being passed to the shader? In that case, why would it make any difference? The GLSL shader would still see 0,0 not +32000-32000 if you will?

@.Ben. said: OK so basically, if I get you right, my mistake right now is that I attach a TerrainQuad at 32000 then move the rootNode back -32000, which LOOKS LIKE it's at 0,0 while its wrapper node is very far away. But aren't all the setLocalTranslation() we do on each node and leaf-nodes all the way to the last leaves all merged down and computed to a final value before being passed to the shader? In that case, why would it make any difference? The GLSL shader would still see 0,0 not +32000-32000 if you will?

But all of that math is done in… floating point with… floats. In some cases it can still work but only I guess if your ‘tiles’ are really internally 0-based and simply have the main node at 32,000. Otherwise, every objects in there gets the floating point error.

And actually, because the world matrix and the view matrix are different, your approach won’t really work. It might work if the root node was at 0,0,0 and you had another sub-root node under that that you move to -32000 or whatever. Then at least the world transform would resolve back to local… but you’d still have to take matrix accumulation into account and have the local tiles 0,0,0 based internally.

Jeez… that’s one interesting problematic that I never thought about 9 months ago when I first began trying for a 3D project! :smiley: I’m loving it tough, it’s quite a challenge to put all the puzzle pieces together actually! Much harder than one could think. As I read already here a couple of times, making a game is not just about an idea or a vision, it’s also about all kinds of over the course cheating to actually making it work!

So basically, my TerrainQuads are never at 0,0 except for TerrainQuad_0_0 because all others are displaced to match the borders of the adjacent ones. So even if I’m only drawing - let’s say 9 “tiles” - only the center one is exactly at 0,0. The other ones are displaced 512 units on each XZ axis (in a grid shape of course).

TL;DR:

OK, so in order not to reinvent the wheel and only fix what’s currently working (aside from the shaking problem at very far distance) here’s the summary, please correct me if I have not understood you right:

  1. Never touch rootNode at all and leave it at 0,0,0
  2. Never touch cam at all and leave it at 0,0,0
  3. Add up all player movement in a variable to know we’re actually not at {0,0,0} anymore (e.g.: we’re now at {9000,0,7500} therefore on TerrainQuad_18_15) but the cam always stays at {0,0,0}
  4. Use that global variable to attach the TerrainQuads and everything else along so that for instance if virtual_cam_pos = {9000,0,7500}, then instead of attaching TerrainQuad_18_15 at {8960,0,7424}, it will be attached at: {8960 - virtual_cam_pos.x, 0, 7424 - virtual_cam_pos.z} so at {-40, 0, -76} which looks like it makes sense.

But that raises one more question tough: if the cam always stays at 0,0 then it means EVERY SINGLE geometry, filter, everything will have to constantly be translated every frame… that’s got to have a big impact on performance will it not? [I have another idea about this actually]

You could still move the camera within a tile and just move the world when you cross a tile boundary if you are concerned about all of those objects being moved. It does make a difference though from experience only a slight one depending on how many objects are in your scene.

What I meant by tiles being 0 base is that the geometry within the tile should see itself local to the tile’s origin. So if you have a rock or a tree or whatever, it’s location is relative to the tile’s 0,0 and the tile is the thing moved to world coordinates.

Your life will be easier with this simple approach if you keep rootNode as it is and make your own rootNode child that you offset. Everything in your scene then would be attached to this child root node.

Also, if all of your movement is jerky then it may just be that you are also calculating world position in float… which as you’ve discovered works out pretty badly. If you want to support movement on worlds this big then you have to do world position in double. If you are using bullet from your movement then these simply approaches won’t work anymore because you’ll need to keep bullet in the low-float coordinate range. In that case, bullet would work in local space, world position would be updated from bullet + some offset, and this would determine where you actually are in the world. But everything in the scene graph is 100% local with no sub root node projection tricks.

…or you could redo bullet using doubles. (Not for the faint of heart.)

1 Like

Thanks for replying, yes that’s exactly what my idea was after writing my last post: Let the player physically move between -256 and +256 and upon chunk change (arriving on another TerrainQuad) apply negated XZ axis * terrain size * terrain scale. So for instance if the player changes from chunk 0,0 to chunk -1,0 then upon arriving on -257, set the player position back to (-(-1) * 256 * 2) which means +512 so: -257 + 512 = 255, then do the same with everything (rocks, vegetation, etc…) like move all TerrainQuads and stuff +512 too so effectively everything will be shifted +512 and it will LOOK LIKE the player has moved but it will always be contained in a -256 to +256 space. That means that since absolutely nothing will EVER be like 1024 units far away from 0,0 and that rootNode is ALWAYS at 0,0 and cam too always at 0,0, then I don’t see how it could ever fail to precisely draw this. Every god damn numbers will always be of a relatively very small magnitude. That sounds like a perfect solution.

@pspeed said: Your life will be easier with this simple approach if you keep rootNode as it is and make your own rootNode child that you offset. Everything in your scene then would be attached to this child root node.

Oh yeah, I know. I totally understand because I’ve already set it up like that 6 months ago as it was required to boost performance for some stuff that is not traceable/solid so I totally understand the point of leaving the rootNode untouched and only moving nodes under it:

[java]
// Prepare nodes
Node n = new Node(“Terrain”);
rayTraceablesRootNode.attachChild(n);
n = new Node(“Terrain For Server Physics”);
n.setCullHint(Spatial.CullHint.Always);
rayTraceablesRootNode.attachChild(n);
n = new Node(“Terrain Water Springs”);
rayTraceablesRootNode.attachChild(n);
n = new Node(“Vegetation”);
rayTraceablesRootNode.attachChild(n);
n = new Node(“PickupItems”);
rayTraceablesRootNode.attachChild(n);
n = new Node(“Props”);
rayTraceablesRootNode.attachChild(n);
[/java]

Well… proof of concept works quite nicely. The only things I need to fix are details like why .getMesh() crashes on very far away chunk noise height generation step, but I’ll figure it out, it’s a detail I guess, maybe it’s just that the noise map can’t handle a number as big as X:5170688.0/Z:5179392.0… lol… +1’ed you for the discussion, it helped, thank you. I was close but had the rootNode moving part wrong :stuck_out_tongue: So for newbies coming here for a quick fix, the rootNode should not be moved at all and the camera should stay within a very limited range, for instance I use 1 TerrainQuad width as a length for the camera, it can never go beyond that limit, else I move everything back the length of a TerrainQuad to compensate. It looks exactly like if the camera is progressing indefinitely very far away from the origin while in fact, it ALWAYS STAYS right at the origin! Very clever. No more shaking AT ALL. It’s working perfectly!