diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java index c43c491ec..f810fbf4d 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java @@ -69,12 +69,23 @@ public class NormalRecalcControl extends AbstractControl { } - @Override + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override public Object jmeClone() { NormalRecalcControl control = (NormalRecalcControl)super.jmeClone(); control.setEnabled(true); - return control; - } + return control; + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.terrain = cloner.clone(terrain); + } @Override public Control cloneForSpatial(Spatial spatial) { @@ -83,7 +94,7 @@ public class NormalRecalcControl extends AbstractControl { control.setEnabled(true); return control; } - + @Override public void setSpatial(Spatial spatial) { super.setSpatial(spatial); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java index 4ac811e9f..0f6a1bb8d 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainPatch.java @@ -50,6 +50,7 @@ import com.jme3.scene.mesh.IndexBuffer; import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight; import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil; import com.jme3.util.BufferUtils; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.nio.Buffer; import java.nio.FloatBuffer; @@ -65,18 +66,18 @@ import java.util.List; * That uses a geo-mipmapping algorithm to change the index buffer of the mesh. * The mesh is a triangle strip. In wireframe mode you might notice some strange lines, these are degenerate * triangles generated by the geoMipMap algorithm and can be ignored. The video card removes them at almost no cost. - * + * * Each patch needs to know its neighbour's LOD so it can seam its edges with them, in case the neighbour has a different * LOD. If this doesn't happen, you will see gaps. - * + * * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far. - * - * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change - * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, + * + * You can supply a LodThresholdCalculator for use in determining when the LOD should change. It's API will no doubt change + * in the near future. Right now it defaults to just changing LOD every two patch sizes. So if a patch has a size of 65, * then the LOD changes every 130 units away. - * + * * @author Brent Owens */ public class TerrainPatch extends Geometry { @@ -118,7 +119,7 @@ public class TerrainPatch extends Geometry { super("TerrainPatch"); setBatchHint(BatchHint.Never); } - + public TerrainPatch(String name) { super(name); setBatchHint(BatchHint.Never); @@ -221,7 +222,7 @@ public class TerrainPatch extends Geometry { public FloatBuffer getHeightmap() { return BufferUtils.createFloatBuffer(geomap.getHeightArray()); } - + public float[] getHeightMap() { return geomap.getHeightArray(); } @@ -256,7 +257,7 @@ public class TerrainPatch extends Geometry { idxB = geomap.writeIndexArrayLodVariable(pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod()), totalSize); else idxB = geomap.writeIndexArrayLodDiff(pow, right, top, left, bottom, totalSize); - + Buffer b; if (idxB.getBuffer() instanceof IntBuffer) b = (IntBuffer)idxB.getBuffer(); @@ -277,14 +278,14 @@ public class TerrainPatch extends Geometry { return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx*2), getMesh().getFloatBuffer(Type.TexCoord).get(idx*2+1) ); } - + public float getHeightmapHeight(float x, float z) { if (x < 0 || z < 0 || x >= size || z >= size) return 0; int idx = (int) (z * size + x); return getMesh().getFloatBuffer(Type.Position).get(idx*3+1); // 3 floats per entry (x,y,z), the +1 is to get the Y } - + /** * Get the triangle of this geometry at the specified local coordinate. * @param x local to the terrain patch @@ -306,7 +307,7 @@ public class TerrainPatch extends Geometry { } 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; @@ -317,7 +318,7 @@ public class TerrainPatch extends Geometry { float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1); geomap.getHeightArray()[idx] = h+lh.h; } - + } FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false); @@ -351,7 +352,7 @@ public class TerrainPatch extends Geometry { TB.setUpdateNeeded(); BB.setUpdateNeeded(); } - + /** * Matches the normals along the edge of the patch with the neighbours. * Computes the normals for the right, bottom, left, and top edges of the @@ -364,7 +365,7 @@ public class TerrainPatch extends Geometry { * *---x---* * | * * - * It works across the right side of the patch, from the top down to + * It works across the right side of the patch, from the top down to * the bottom. Then it works on the bottom side of the patch, from the * left to the right. */ @@ -388,9 +389,9 @@ public class TerrainPatch extends Geometry { Vector3f binormal = new Vector3f(); Vector3f normal = new Vector3f(); - + int s = this.getSize()-1; - + if (right != null) { // right side, works its way down for (int i=0; i= size || z >= size) return null; // out of range - + int index = (z*size+x)*3; FloatBuffer nb = (FloatBuffer)this.getMesh().getBuffer(Type.Normal).getData(); Vector3f normal = new Vector3f(); @@ -609,7 +610,7 @@ public class TerrainPatch extends Geometry { protected float getHeight(int x, int z, float xm, float zm) { return geomap.getHeight(x,z,xm,zm); } - + /** * Locks the mesh (sets it static) to improve performance. * But it it not editable then. Set unlock to make it editable. @@ -626,7 +627,7 @@ public class TerrainPatch extends Geometry { public void unlockMesh() { getMesh().setDynamic(); } - + /** * Returns the offset amount this terrain patch uses for textures. * @@ -797,7 +798,7 @@ public class TerrainPatch extends Geometry { protected void setLodBottom(int lodBottom) { this.lodBottom = lodBottom; } - + /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) { this.lodCalculatorFactory = lodCalculatorFactory; setLodCalculator(lodCalculatorFactory.createCalculator(this)); @@ -812,7 +813,7 @@ public class TerrainPatch extends Geometry { if (other instanceof BoundingVolume) if (!getWorldBound().intersects((BoundingVolume)other)) return 0; - + if(other instanceof Ray) return collideWithRay((Ray)other, results); else if (other instanceof BoundingVolume) @@ -853,7 +854,7 @@ public class TerrainPatch extends Geometry { * This most definitely is not optimized. */ private int collideWithBoundingBox(BoundingBox bbox, CollisionResults results) { - + // test the four corners, for cases where the bbox dimensions are less than the terrain grid size, which is probably most of the time Vector3f topLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); Vector3f topRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z-bbox.getZExtent())); @@ -872,11 +873,11 @@ public class TerrainPatch extends Geometry { t = getTriangle(bottomRight.x, bottomRight.z); if (t != null && bbox.collideWith(t, results) > 0) return 1; - + // box is larger than the points on the terrain, so test against the points for (float z=topLeft.z; z= size || z >= size) continue; t = getTriangle(x,z); @@ -895,7 +896,7 @@ public class TerrainPatch extends Geometry { // this reduces the save size to 10% by not saving the mesh Mesh temp = getMesh(); mesh = null; - + super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(size, "size", 16); @@ -908,7 +909,7 @@ public class TerrainPatch extends Geometry { //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null); oc.write(lodEntropy, "lodEntropy", null); oc.write(geomap, "geomap", null); - + setMesh(temp); } @@ -927,7 +928,7 @@ public class TerrainPatch extends Geometry { //lodCalculatorFactory = (LodCalculatorFactory) ic.readSavable("lodCalculatorFactory", null); lodEntropy = ic.readFloatArray("lodEntropy", null); geomap = (LODGeomap) ic.readSavable("geomap", null); - + Mesh regen = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false); setMesh(regen); //TangentBinormalGenerator.generate(this); // note that this will be removed @@ -955,6 +956,33 @@ public class TerrainPatch extends Geometry { return clone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + + this.stepScale = cloner.clone(stepScale); + this.offset = cloner.clone(offset); + + this.leftNeighbour = null; + this.topNeighbour = null; + this.rightNeighbour = null; + this.bottomNeighbour = null; + + // Don't feel like making geomap cloneable tonight + // so I'll copy the old logic. + this.geomap = new LODGeomap(size, geomap.getHeightArray()); + Mesh m = geomap.createMesh(stepScale, Vector2f.UNIT_XY, offset, offsetAmount, totalSize, false); + this.setMesh(m); + + // In this case, we always clone material even if the cloner is setup + // not to clone it. Terrain uses mutable textures and stuff so it's important + // to clone it. (At least that's my understanding and is evidenced by the old + // clone code specifically cloning material.) -pspeed + this.material = material.clone(); + } + protected void ensurePositiveVolumeBBox() { if (getModelBound() instanceof BoundingBox) { if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java index 8cceb85bb..2553e06a0 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java @@ -55,6 +55,7 @@ import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker; import com.jme3.terrain.geomipmap.picking.TerrainPickData; import com.jme3.terrain.geomipmap.picking.TerrainPicker; import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -126,7 +127,7 @@ public class TerrainQuad extends Node implements Terrain { private Vector3f lastScale = Vector3f.UNIT_XYZ; protected NeighbourFinder neighbourFinder; - + public TerrainQuad() { super("Terrain"); } @@ -144,24 +145,24 @@ public class TerrainQuad extends Node implements Terrain { *

* @param name the name of the scene element. This is required for * identification and comparison purposes. - * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, + * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, * must be smaller than totalSize. (eg. 33, 65...) - * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 + * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 * (eg. 513, 1025, 2049...) * @param heightMap The height map to generate the terrain from (a flat - * height map will be generated if this is null). The size of one side of the heightmap + * height map will be generated if this is null). The size of one side of the heightmap * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513. */ public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) { this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap); - + affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2); fixNormalEdges(affectedAreaBBox); addControl(new NormalRecalcControl(this)); } - + /** - * + * * @param name the name of the scene element. This is required for * identification and comparison purposes. * @param patchSize size of the individual patches @@ -176,7 +177,7 @@ public class TerrainQuad extends Node implements Terrain { } /** - * + * * @param name the name of the scene element. This is required for * identification and comparison purposes. * @param patchSize size of the individual patches @@ -192,9 +193,9 @@ public class TerrainQuad extends Node implements Terrain { //fixNormalEdges(affectedAreaBBox); //addControl(new NormalRecalcControl(this)); } - + /** - * + * * @param name the name of the scene element. This is required for * identification and comparison purposes. * @param patchSize size of the individual patches @@ -217,17 +218,17 @@ public class TerrainQuad extends Node implements Terrain { Vector2f offset, float offsetAmount) { super(name); - + if (heightMap == null) heightMap = generateDefaultHeightMap(quadSize); - + if (!FastMath.isPowerOfTwo(quadSize - 1)) { throw new RuntimeException("size given: " + quadSize + " Terrain quad sizes may only be (2^N + 1)"); } if (FastMath.sqrt(heightMap.length) > quadSize) { Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Heightmap size is larger than the terrain size. Make sure your heightmap image is the same size as the terrain!"); } - + this.offset = offset; this.offsetAmount = offsetAmount; this.totalSize = totalSize; @@ -248,7 +249,7 @@ public class TerrainQuad extends Node implements Terrain { public void recalculateAllNormals() { affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2); } - + /** * Create just a flat heightmap */ @@ -267,11 +268,11 @@ public class TerrainQuad extends Node implements Terrain { //TODO background-thread this if it ends up being expensive fixNormals(affectedAreaBBox); // the affected patches fixNormalEdges(affectedAreaBBox); // the edges between the patches - + setNormalRecalcNeeded(null); // set to false } } - + /** * Caches the transforms (except rotation) so the LOD calculator, * which runs on a separate thread, can access them safely. @@ -343,7 +344,7 @@ public class TerrainQuad extends Node implements Terrain { public Material getMaterial() { return getMaterial(null); } - + public Material getMaterial(Vector3f worldLocation) { // get the material from one of the children. They all share the same material if (children != null) { @@ -362,7 +363,7 @@ public class TerrainQuad extends Node implements Terrain { public int getNumMajorSubdivisions() { return 1; } - + protected boolean calculateLod(List location, HashMap updates, LodCalculator lodCalculator) { @@ -434,7 +435,7 @@ public class TerrainQuad extends Node implements Terrain { utp.setBottomLod(utpD.getNewLod()); utpD.setTopLod(utp.getNewLod()); } - + if (left != null) { UpdatedTerrainPatch utpL = updated.get(left.getName()); if (utpL == null) { @@ -478,7 +479,7 @@ public class TerrainQuad extends Node implements Terrain { } } } - + /** * Find any neighbours that should have their edges seamed because another neighbour * changed its LOD to a greater value (less detailed) @@ -587,10 +588,10 @@ public class TerrainQuad extends Node implements Terrain { /** * Quadrants, world coordinates, and heightmap coordinates (Y-up): - * + * * -z - * -u | - * -v 1|3 + * -u | + * -v 1|3 * -x ----+---- x * 2|4 u * | v @@ -668,7 +669,7 @@ public class TerrainQuad extends Node implements Terrain { quad3.setLocalTranslation(origin3); quad3.quadrant = 3; this.attachChild(quad3); - + // 4 lower right of heightmap, lower right quad float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1, split - 1, split); @@ -892,7 +893,7 @@ public class TerrainQuad extends Node implements Terrain { } return false; } - + /** * This will cause all normals for this terrain quad to be recalculated */ @@ -1024,14 +1025,14 @@ public class TerrainQuad extends Node implements Terrain { int col; int row; Spatial child; - + QuadrantChild(int col, int row, Spatial child) { this.col = col; this.row = row; this.child = child; } } - + private QuadrantChild findMatchingChild(int x, int z) { int quad = findQuadrant(x, z); int split = (size + 1) >> 1; @@ -1069,7 +1070,7 @@ public class TerrainQuad extends Node implements Terrain { } return null; } - + /** * Get the interpolated height of the terrain at the specified point. * @param xz the location to get the height for @@ -1090,7 +1091,7 @@ public class TerrainQuad extends Node implements Terrain { * gets an interpolated value at the specified point */ protected float getHeight(int x, int z, float xm, float zm) { - + QuadrantChild match = findMatchingChild(x,z); if (match != null) { if (match.child instanceof TerrainQuad) { @@ -1107,10 +1108,10 @@ public class TerrainQuad extends Node implements Terrain { float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); Vector3f normal = getNormal(x, z, xz); - + return normal; } - + protected Vector3f getNormal(float x, float z, Vector2f xz) { x-=0.5f; z-=0.5f; @@ -1125,15 +1126,15 @@ public class TerrainQuad extends Node implements Terrain { // v3--v4 | Z // | // <-------Y - // X + // X Vector3f n1 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.ceil(z)); Vector3f n2 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.ceil(z)); Vector3f n3 = getMeshNormal((int) FastMath.ceil(x), (int) FastMath.floor(z)); Vector3f n4 = getMeshNormal((int) FastMath.floor(x), (int) FastMath.floor(z)); - + return n1.add(n2).add(n3).add(n4).normalize(); } - + public void setHeight(Vector2f xz, float height) { List coord = new ArrayList(); coord.add(xz); @@ -1291,7 +1292,7 @@ public class TerrainQuad extends Node implements Terrain { return (x >= 0 && x <= totalSize && z >= 0 && z <= totalSize); } - + public int getTerrainSize() { return totalSize; } @@ -1750,7 +1751,7 @@ public class TerrainQuad extends Node implements Terrain { totalSize = c.readInt("totalSize", 0); //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator()); //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); - + if ( !(getParent() instanceof TerrainQuad) ) { BoundingBox all = new BoundingBox(getWorldTranslation(), totalSize, totalSize, totalSize); affectedAreaBBox = all; @@ -1793,10 +1794,10 @@ public class TerrainQuad extends Node implements Terrain { quadClone.quadrant = quadrant; //quadClone.lodCalculatorFactory = lodCalculatorFactory.clone(); //quadClone.lodCalculator = lodCalculator.clone(); - + TerrainLodControl lodControlCloned = this.getControl(TerrainLodControl.class); TerrainLodControl lodControl = quadClone.getControl(TerrainLodControl.class); - + if (lodControlCloned != null && !(getParent() instanceof TerrainQuad)) { //lodControlCloned.setLodCalculator(lodControl.getLodCalculator().clone()); } @@ -1806,7 +1807,25 @@ public class TerrainQuad extends Node implements Terrain { return quadClone; } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.stepScale = cloner.clone(stepScale); + this.offset = cloner.clone(offset); + + // This was not cloned before... I think that's a mistake. + this.affectedAreaBBox = cloner.clone(affectedAreaBBox); + + // picker is not cloneable and not cloned. This also seems like + // a mistake if you ever load the same terrain twice. + // this.picker = cloner.clone(picker); + + // neighbourFinder is also not cloned. Maybe that's ok. + } + @Override protected void setParent(Node parent) { super.setParent(parent); @@ -1815,7 +1834,7 @@ public class TerrainQuad extends Node implements Terrain { clearCaches(); } } - + /** * Removes any cached references this terrain is holding, in particular * the TerrainPatch's neighbour references. @@ -1834,7 +1853,7 @@ public class TerrainQuad extends Node implements Terrain { } } } - + public int getMaxLod() { if (maxLod < 0) maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide