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