diff --git a/engine/src/terrain/com/jme3/terrain/Terrain.java b/engine/src/terrain/com/jme3/terrain/Terrain.java index d2bce1a83..72b783aaa 100644 --- a/engine/src/terrain/com/jme3/terrain/Terrain.java +++ b/engine/src/terrain/com/jme3/terrain/Terrain.java @@ -50,12 +50,12 @@ import java.util.List; */ public interface Terrain { - /** - * Get the real-world height of the terrain at the specified X-Z coorindate. - * @param xz the X-Z world coordinate - * @return the height at the given point - */ - public float getHeight(Vector2f xz); + /** + * Get the real-world height of the terrain at the specified X-Z coorindate. + * @param xz the X-Z world coordinate + * @return the height at the given point + */ + public float getHeight(Vector2f xz); /** * Get the heightmap height at the specified X-Z coordinate. This does not @@ -65,15 +65,25 @@ public interface Terrain { */ public float getHeightmapHeight(Vector2f xz); - /** - * Set the height at the specified X-Z coordinate. + /** + * Set the height at the specified X-Z coordinate. * To set the height of the terrain and see it, you will have * to unlock the terrain meshes by calling terrain.setLocked(false) before * you call setHeight(). - * @param xzCoordinate coordinate to set the height - * @param height that will be set at the coordinate - */ - public void setHeight(Vector2f xzCoordinate, float height); + * @param xzCoordinate coordinate to set the height + * @param height that will be set at the coordinate + */ + public void setHeight(Vector2f xzCoordinate, float height); + + /** + * Set the height at many points. The two lists must be the same size. + * Each xz coordinate entry matches to a height entry, 1 for 1. So the + * first coordinate matches to the first height value, the last to the + * last etc. + * @param xz a list of coordinates where the hight will be set + * @param height the heights that match the xz coordinates + */ + public void setHeight(List xz, List height); /** * Raise/lower the height in one call (instead of getHeight then setHeight). @@ -81,44 +91,54 @@ public interface Terrain { * @param delta +- value to adjust the height by */ public void adjustHeight(Vector2f xzCoordinate, float delta); - - /** - * Get the heightmap of the entire terrain. - * This can return null if that terrain object does not store the height data. + + /** + * Raise/lower the height at many points. The two lists must be the same size. + * Each xz coordinate entry matches to a height entry, 1 for 1. So the + * first coordinate matches to the first height value, the last to the + * last etc. + * @param xz a list of coordinates where the hight will be adjusted + * @param height +- value to adjust the height by, that matches the xz coordinates + */ + public void adjustHeight(List xz, List height); + + /** + * Get the heightmap of the entire terrain. + * This can return null if that terrain object does not store the height data. * Infinite or "paged" terrains will not be able to support this, so use with caution. - */ - public float[] getHeightMap(); + */ + public float[] getHeightMap(); - /** - * Tell the terrain system to use/not use Level of Detail algorithms. + /** + * Tell the terrain system to use/not use Level of Detail algorithms. * This is allowed to be ignored if a particular implementation cannot support it. - */ - public void useLOD(boolean useLod); + */ + public void useLOD(boolean useLod); /** * Check if the terrain is using LOD techniques. * If a terrain system only supports enabled LOD, then this * should always return true. */ - public boolean isUsingLOD(); - - /** - * This is calculated by the specific LOD algorithm. - * A value of one means that the terrain is showing full detail. - * The higher the value, the more the terrain has been generalized - * and the less detailed it will be. - */ - public int getMaxLod(); + public boolean isUsingLOD(); + + /** + * This is calculated by the specific LOD algorithm. + * A value of one means that the terrain is showing full detail. + * The higher the value, the more the terrain has been generalized + * and the less detailed it will be. + */ + public int getMaxLod(); - /** - * Called in the update (pre or post, up to you) method of your game. - * Calculates the level of detail of the terrain and adjusts its geometry. - * This is where the Terrain's LOD algorithm will change the detail of - * the terrain based on how far away this position is from the particular - * terrain patch. - * @param location often the Camera's location - */ - public void update(List location); + /** + * Called in the update (pre or post, up to you) method of your game. + * Calculates the level of detail of the terrain and adjusts its geometry. + * This is where the Terrain's LOD algorithm will change the detail of + * the terrain based on how far away this position is from the particular + * terrain patch. + * @param location often the Camera's location + */ + public void update(List location); /** * Get the spatial instance of this Terrain. Right now just used in the diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java index 80796d9c9..a5c4e576c 100644 --- a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -55,6 +55,7 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Type; +import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculatorFactory; @@ -306,16 +307,24 @@ public class TerrainPatch extends Geometry { return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation()); } - public void setHeight(float x, float z, float height) { - if (x < 0 || z < 0 || x >= size || z >= size) - return; - int idx = (int) (z * size + x); - geomap.getHeightData().put(idx, height); + protected void setHeight(List locationHeights, boolean overrideHeight) { + for (LocationHeight lh : locationHeights) { + if (lh.x < 0 || lh.z < 0 || lh.x >= size || lh.z >= size) + continue; + int idx = lh.z * size + lh.x; + if (overrideHeight) { + geomap.getHeightData().put(idx, lh.h); + } else { + float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); + geomap.getHeightData().put(idx, h+lh.h); + } + + } + FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); getMesh().clearBuffer(Type.Position); - getMesh().setBuffer(Type.Position, 3, newVertexBuffer); - // normals are updated from the terrain controller on update() + getMesh().setBuffer(Type.Position, 3, newVertexBuffer); } public void adjustHeight(float x, float z, float delta) { @@ -328,7 +337,7 @@ public class TerrainPatch extends Geometry { FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); getMesh().clearBuffer(Type.Position); - getMesh().setBuffer(Type.Position, 3, newVertexBuffer); + getMesh().setBuffer(Type.Position, 3, newVertexBuffer); } /** diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java index c80645756..e9e6eb635 100644 --- a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java +++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainQuad.java @@ -806,15 +806,15 @@ public class TerrainQuad extends Node implements Terrain { */ public void attachBoundChildren(Node parent) { for (int i = 0; i < this.getQuantity(); i++) { - if (this.getChild(i) instanceof TerrainQuad) { - ((TerrainQuad) getChild(i)).attachBoundChildren(parent); - } else if (this.getChild(i) instanceof TerrainPatch) { - BoundingVolume bv = getChild(i).getWorldBound(); + if (this.getChild(i) instanceof TerrainQuad) { + ((TerrainQuad) getChild(i)).attachBoundChildren(parent); + } else if (this.getChild(i) instanceof TerrainPatch) { + BoundingVolume bv = getChild(i).getWorldBound(); if (bv instanceof BoundingBox) { attachBoundingBox((BoundingBox)bv, parent); } - } - } + } + } BoundingVolume bv = getWorldBound(); if (bv instanceof BoundingBox) { attachBoundingBox((BoundingBox)bv, parent); @@ -858,13 +858,13 @@ public class TerrainQuad extends Node implements Terrain { return affectedAreaBBox != null; } - public float getHeightmapHeight(Vector2f xz) { + public float getHeightmapHeight(Vector2f xz) { // offset - int x = Math.round((xz.x / getLocalScale().x) + totalSize / 2); - int z = Math.round((xz.y / getLocalScale().z) + totalSize / 2); + int x = Math.round((xz.x / getLocalScale().x) + (float)totalSize / 2f); + int z = Math.round((xz.y / getLocalScale().z) + (float)totalSize / 2f); return getHeightmapHeight(x, z); - } + } /** * This will just get the heightmap value at the supplied point, @@ -946,132 +946,171 @@ public class TerrainQuad extends Node implements Terrain { return 0; } - - // the coord calculations should be the same as getHeight() public void setHeight(Vector2f xz, float height) { - // offset - int x = Math.round((xz.x / getLocalScale().x) + totalSize / 2); - int z = Math.round((xz.y / getLocalScale().z) + totalSize / 2); + List coord = new ArrayList(); + coord.add(xz); + List h = new ArrayList(); + h.add(height); + + setHeight(coord, h); + } - setHeight(x, z, height); // adjust the actual mesh + public void adjustHeight(Vector2f xz, float delta) { + List coord = new ArrayList(); + coord.add(xz); + List h = new ArrayList(); + h.add(delta); - setNormalRecalcNeeded(xz); - } + adjustHeight(coord, h); + } - protected void setHeight(int x, int z, float newVal) { - int quad = findQuadrant(x, z); - int split = (size + 1) >> 1; - if (children != null) { - for (int i = children.size(); --i >= 0;) { - Spatial spat = children.get(i); - int col = x; - int row = z; - boolean match = false; + public void setHeight(List xz, List height) { + setHeight(xz, height, true); + } - // get the childs quadrant - int childQuadrant = 0; - if (spat instanceof TerrainQuad) { - childQuadrant = ((TerrainQuad) spat).getQuadrant(); - } else if (spat instanceof TerrainPatch) { - childQuadrant = ((TerrainPatch) spat).getQuadrant(); - } + public void adjustHeight(List xz, List height) { + setHeight(xz, height, false); + } - if (childQuadrant == 1 && (quad & 1) != 0) { - match = true; - } else if (childQuadrant == 2 && (quad & 2) != 0) { - row = z - split + 1; - match = true; - } else if (childQuadrant == 3 && (quad & 4) != 0) { - col = x - split + 1; - match = true; - } else if (childQuadrant == 4 && (quad & 8) != 0) { - col = x - split + 1; - row = z - split + 1; - match = true; - } + protected void setHeight(List xz, List height, boolean overrideHeight) { + if (xz.size() != height.size()) + throw new IllegalArgumentException("Both lists must be the same length!"); - if (match) { - if (spat instanceof TerrainQuad) { - ((TerrainQuad) spat).setHeight(col, row, newVal); - } else if (spat instanceof TerrainPatch) { - ((TerrainPatch) spat).setHeight(col, row, newVal); - } - } + int halfSize = totalSize / 2; - } + List locations = new ArrayList(); + + // offset + for (int i=0; i= 0 && x <= totalSize && z >= 0 && z <= totalSize); - } + setHeight(locations, overrideHeight); // adjust height of the actual mesh - public Vector2f getPointPercentagePosition(float worldX, float worldY) { - Vector2f uv = new Vector2f(worldX,worldY); - uv.subtractLocal(getLocalTranslation().x, getLocalTranslation().z); // center it on 0,0 - uv.addLocal(totalSize/2, totalSize/2); // shift the bottom left corner up to 0,0 - uv.divideLocal(totalSize); // get the location as a percentage - - return uv; + // signal that the normals need updating + for (int i=0; i locations, boolean overrideHeight) { + if (children == null) return; - adjustHeight(x, z,delta); + List quadLH1 = new ArrayList(); + List quadLH2 = new ArrayList(); + List quadLH3 = new ArrayList(); + List quadLH4 = new ArrayList(); + Spatial quad1 = null; + Spatial quad2 = null; + Spatial quad3 = null; + Spatial quad4 = null; + + // get the child quadrants + for (int i = children.size(); --i >= 0;) { + Spatial spat = children.get(i); + int childQuadrant = 0; + if (spat instanceof TerrainQuad) { + childQuadrant = ((TerrainQuad) spat).getQuadrant(); + } else if (spat instanceof TerrainPatch) { + childQuadrant = ((TerrainPatch) spat).getQuadrant(); + } - setNormalRecalcNeeded(xz); - } + if (childQuadrant == 1) + quad1 = spat; + else if (childQuadrant == 2) + quad2 = spat; + else if (childQuadrant == 3) + quad3 = spat; + else if (childQuadrant == 4) + quad4 = spat; + } - protected void adjustHeight(int x, int z, float delta) { - int quad = findQuadrant(x, z); int split = (size + 1) >> 1; - if (children != null) { - for (int i = children.size(); --i >= 0;) { - Spatial spat = children.get(i); - int col = x; - int row = z; - boolean match = false; - - // get the childs quadrant - int childQuadrant = 0; - if (spat instanceof TerrainQuad) { - childQuadrant = ((TerrainQuad) spat).getQuadrant(); - } else if (spat instanceof TerrainPatch) { - childQuadrant = ((TerrainPatch) spat).getQuadrant(); - } - if (childQuadrant == 1 && (quad & 1) != 0) { - match = true; - } else if (childQuadrant == 2 && (quad & 2) != 0) { - row = z - split + 1; - match = true; - } else if (childQuadrant == 3 && (quad & 4) != 0) { - col = x - split + 1; - match = true; - } else if (childQuadrant == 4 && (quad & 8) != 0) { - col = x - split + 1; - row = z - split + 1; - match = true; - } + // distribute each locationHeight into the quadrant it intersects + for (LocationHeight lh : locations) { + int quad = findQuadrant(lh.x, lh.z); - if (match) { - if (spat instanceof TerrainQuad) { - ((TerrainQuad) spat).adjustHeight(col, row, delta); - } else if (spat instanceof TerrainPatch) { - ((TerrainPatch) spat).adjustHeight(col, row, delta); - } - } + int col = lh.x; + int row = lh.z; + if ((quad & 1) != 0) { + quadLH1.add(lh); } + if ((quad & 2) != 0) { + row = lh.z - split + 1; + quadLH2.add(new LocationHeight(lh.x, row, lh.h)); + } + if ((quad & 4) != 0) { + col = lh.x - split + 1; + quadLH3.add(new LocationHeight(col, lh.z, lh.h)); + } + if ((quad & 8) != 0) { + col = lh.x - split + 1; + row = lh.z - split + 1; + quadLH4.add(new LocationHeight(col, row, lh.h)); + } + } + + // send the locations to the children + if (!quadLH1.isEmpty()) { + if (quad1 instanceof TerrainQuad) + ((TerrainQuad)quad1).setHeight(quadLH1, overrideHeight); + else if(quad1 instanceof TerrainPatch) + ((TerrainPatch)quad1).setHeight(quadLH1, overrideHeight); + } + + if (!quadLH2.isEmpty()) { + if (quad2 instanceof TerrainQuad) + ((TerrainQuad)quad2).setHeight(quadLH2, overrideHeight); + else if(quad2 instanceof TerrainPatch) + ((TerrainPatch)quad2).setHeight(quadLH2, overrideHeight); + } + + if (!quadLH3.isEmpty()) { + if (quad3 instanceof TerrainQuad) + ((TerrainQuad)quad3).setHeight(quadLH3, overrideHeight); + else if(quad3 instanceof TerrainPatch) + ((TerrainPatch)quad3).setHeight(quadLH3, overrideHeight); + } + + if (!quadLH4.isEmpty()) { + if (quad4 instanceof TerrainQuad) + ((TerrainQuad)quad4).setHeight(quadLH4, overrideHeight); + else if(quad4 instanceof TerrainPatch) + ((TerrainPatch)quad4).setHeight(quadLH4, overrideHeight); } } + protected boolean isPointOnTerrain(int x, int z) { + return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); + } + + public Vector2f getPointPercentagePosition(float worldX, float worldY) { + Vector2f uv = new Vector2f(worldX,worldY); + uv.subtractLocal(getLocalTranslation().x, getLocalTranslation().z); // center it on 0,0 + uv.addLocal(totalSize/2, totalSize/2); // shift the bottom left corner up to 0,0 + uv.divideLocal(totalSize); // get the location as a percentage + + return uv; + } + // a position can be in multiple quadrants, so use a bit anded value. private int findQuadrant(int x, int y) { diff --git a/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java b/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java index 94cf5c25d..057a07b87 100644 --- a/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java +++ b/engine/src/test/jme3test/terrain/TerrainTestModifyHeight.java @@ -66,7 +66,7 @@ public class TerrainTestModifyHeight extends SimpleApplication { private TerrainQuad terrain; Material matTerrain; Material matWire; - boolean wireframe = false; + boolean wireframe = true; boolean triPlanar = false; boolean wardiso = false; boolean minnaert = false; @@ -206,14 +206,14 @@ public class TerrainTestModifyHeight extends SimpleApplication { if (pressed) { Vector3f intersection = getWorldIntersection(); if (intersection != null) { - adjustHeight(intersection, 16, 1); + adjustHeight(intersection, 64, 1); } } } else if (name.equals("Lower")) { if (pressed) { Vector3f intersection = getWorldIntersection(); if (intersection != null) { - adjustHeight(intersection, 16, -1); + adjustHeight(intersection, 32, -1); } } } @@ -230,8 +230,11 @@ public class TerrainTestModifyHeight extends SimpleApplication { float xStepAmount = terrain.getLocalScale().x; float zStepAmount = terrain.getLocalScale().z; long start = System.currentTimeMillis(); + List locs = new ArrayList(); + List heights = new ArrayList(); + for (int z = -radiusStepsZ; z < radiusStepsZ; z++) { - for (int x = -radiusStepsZ; x < radiusStepsX; x++) { + for (int x = -radiusStepsX; x < radiusStepsX; x++) { float locX = loc.x + (x * xStepAmount); float locZ = loc.z + (z * zStepAmount); @@ -239,13 +242,14 @@ public class TerrainTestModifyHeight extends SimpleApplication { if (isInRadius(locX - loc.x, locZ - loc.z, radius)) { // see if it is in the radius of the tool float h = calculateHeight(radius, height, locX - loc.x, locZ - loc.z); - - // increase the height - terrain.adjustHeight(new Vector2f(locX, locZ), h); + locs.add(new Vector2f(locX, locZ)); + heights.add(h); } } } - System.out.println("took: " + (System.currentTimeMillis() - start)); + + terrain.adjustHeight(locs, heights); + //System.out.println("Modified "+locs.size()+" points, took: " + (System.currentTimeMillis() - start)+" ms"); terrain.updateModelBound(); }