TerrainGrid getheight fix (when one of the initial 4 terrainquads are replaced)

With sweat, tears, blood and swearing (like the guy with the great boat game:) ) for a couple of nights in a row, I believe I have conjured the 5 lines of code necessary to fix the problem.

i think it’s a good idea that @sploreg or @anthyon have a look at the code.

At the top of getHeightmapHeight(int x, int z) in TerrainQuad I added these lines:

[java]

protected float getHeightmapHeight(int x, int z) {

if(getClass().equals(TerrainGrid.class)){ // EDIT: previous test assumed terraingrid was in rootnode

//x+=(x>size/2)?-((x-(size/4))/(size/2))(size/2):((x-3size/4)/(size/2))(size/2);

//z+=(z>size/2)?-((z-(size/4))/(size/2))
(size/2):((z-3size/4)/(size/2))(size/2);

int sizeMinOne = size-1;

int sizeDiv2 = size >> 1;

x+=(x>sizeDiv2)?((sizeDiv2-2*x)/sizeMinOne)sizeDiv2:((sizeMinOne+sizeDiv2-2x)/sizeMinOne)sizeDiv2;

z+=(z>sizeDiv2)?((sizeDiv2-2
z)/sizeMinOne)sizeDiv2:((sizeMinOne+sizeDiv2-2z)/sizeMinOne)*sizeDiv2;

}

[/java]

Edit: Since adjustHeight is dependant on this function, it also solves that (tested), i believe someone mentioned it didn’t work.

A more correct approach is to override getheightmapheight in terraingrid instead of altering terrainquad.

No reason TerrainQuad needs to know about TerrainGrid.

Ok, so in TerrainGrid i propose adding:

[java]

@Override

protected float getHeightmapHeight(int x, int z) {

int sizeMinOne = size-1;

int sizeDiv2 = size >> 1;

x+=(x>sizeDiv2)?((sizeDiv2-2*x)/sizeMinOne)sizeDiv2:((sizeMinOne+sizeDiv2-2x)/sizeMinOne)sizeDiv2;

z+=(z>sizeDiv2)?((sizeDiv2-2
z)/sizeMinOne)sizeDiv2:((sizeMinOne+sizeDiv2-2z)/sizeMinOne)*sizeDiv2;

return super.getHeightmapHeight(x, z);

}

[/java]

Edit: There are issues while new quads are being loaded, it then checks height on wrong quad it seems. Maybe it’s a threading issue.

Edit2: the process can be moved to somewhere it is only done after the grid updates.This would also solve the issue in the edit above I think. I’ll look into it when I get home from work.

Edit3: Scratch edit2, it will not work if the height request is located outside the current terrainquads.

Thanks makeshift, I will be taking a look at your fix this weekend.

The terrain grid class will no double have to block the getHeightmapHeight method call if tiles are being loaded in.

Nice that your having a look at it, hope it’s a viable solution.

Sploreg said:

The terrain grid class will no double have to block the getHeightmapHeight method call if tiles are being loaded in.


Could you please elaborate a little?

simply, the getHeightmapHeight method will have to wait on a synchronized block when a new tile is being loaded in. So if a tile is loading then the getHeightmapHeight method will have a slight delay.

Won’t a few heightvalues “disappear”, I meen while the terrain is loading and the logic loop continues (excuse my possible lack of correct naming). Not that most users will care, I’m just taking all possible problems into consideration. I don’t think I know how to do something like this in a correct manner.

I was thinking about something related, but I thought to freeze the whole logic loop until new terrainquads where loaded; that’s probably like shooting a sparrow with a canon, and will probably affect the user experience.

The synchronized blocks would block each other it so you would always get the proper height value. The tile loading and the getHeightmapHeight calls would synchronize on the same variable enabling this.So during that time, collision might pause. If it is a long delay, then something else might have to be worked out, possibly keeping a reference to the previous grid indexes and then just swapping the references once the new tiles are loaded.

A lot of changing my mind as my knowledge of how terraingrid and quad works, but I believe this is a viable and final solution. It’s tested vigorously. Synchronizing seems not necessary because the old offset can be used until cells are updated.

[java]

private int cellsLoaded = 0;

private int[] gridOffset;

// ALTERED

@Override

public void update(List<Vector3f> locations) {

// for now, only the first camera is handled.

// to accept more, there are two ways:

// 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed

// 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore

Vector3f cam = locations.get(0);

Vector3f camCell = this.getCell(cam);

if(cellsLoaded>1){ // Check if cells are updated before updating gridoffset.

gridOffset[0] = Math.round(camCell.x*(size/2));

gridOffset[1] = Math.round(camCell.z*(size/2));

cellsLoaded=0;

}

if (camCell.x != this.currentCell.x || camCell.z != currentCell.z) {

this.updateChildrens(camCell);

for (TerrainGridListener l : this.listeners.values()) {

l.gridMoved(camCell);

}

}

super.update(locations);

}

// ALTERED

@Override

protected float getHeightmapHeight(int x, int z) {

return super.getHeightmapHeight(x-gridOffset[0], z-gridOffset[1]);

}

// NOT ALTERED

public Vector3f getCell(Vector3f location) {

final Vector3f v = location.clone().divideLocal(this.getLocalScale().mult(this.quadSize - 1)).add(0.5f, 0, 0.5f);

return new Vector3f(FastMath.floor(v.x), 0, FastMath.floor(v.z));

}

// ALTERED

protected void removeQuad(int idx) {

if (this.getQuad(idx) != null) {

if (quadControls != null) {

this.getQuad(idx).removeControl(RigidBodyControl.class);

}

for (TerrainGridListener l : listeners.values()) {

l.tileDetached(getCell(this.getQuad(idx).getWorldTranslation()), this.getQuad(idx));

}

this.detachChild(this.getQuad(idx));

cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this.

}

}

[/java]

I committed the fix and updated TerrainTestModifyHeight to test getHeight() from terrainGrid. It seems to be working quite well so far.

1 Like