aboutsummaryrefslogtreecommitdiff
path: root/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
diff options
context:
space:
mode:
authorScott Barta <sbarta@google.com>2012-03-01 12:35:35 -0800
committerScott Barta <sbarta@google.com>2012-03-01 12:40:08 -0800
commit59b2e6871c65f58fdad78cd7229c292f6a177578 (patch)
tree2d4e7bfc05b93f40b34675d77e403dd1c25efafd /engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
parentf9b30489e75ac1eabc365064959804e99534f7ab (diff)
downloadjmonkeyengine-59b2e6871c65f58fdad78cd7229c292f6a177578.tar.gz
Adds the jMonkeyEngine library to the build.
Adds the jMonkeyEngine open source 3D game engine to the build. This is built as a static library and is only used by the Finsky client. Change-Id: I06a3f054df7b8a67757267d884854f70c5a16ca0
Diffstat (limited to 'engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java')
-rw-r--r--engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java983
1 files changed, 983 insertions, 0 deletions
diff --git a/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
new file mode 100644
index 0000000..40cf190
--- /dev/null
+++ b/engine/src/terrain/com/jme3/terrain/geomipmap/TerrainPatch.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.terrain.geomipmap;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.bounding.BoundingSphere;
+import com.jme3.bounding.BoundingVolume;
+import com.jme3.collision.Collidable;
+import com.jme3.collision.CollisionResults;
+import com.jme3.collision.UnsupportedCollisionException;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.*;
+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.util.EntropyComputeUtil;
+import com.jme3.util.BufferUtils;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.HashMap;
+import java.util.List;
+
+
+/**
+ * A terrain patch is a leaf in the terrain quad tree. It has a mesh that can change levels of detail (LOD)
+ * whenever the view point, or camera, changes. The actual terrain mesh is created by the LODGeomap class.
+ * 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,
+ * then the LOD changes every 130 units away.
+ *
+ * @author Brent Owens
+ */
+public class TerrainPatch extends Geometry {
+
+ protected LODGeomap geomap;
+ protected int lod = -1; // this terrain patch's LOD
+ private int maxLod = -1;
+ protected int previousLod = -1;
+ protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs
+
+ protected int size;
+
+ protected int totalSize;
+
+ protected short quadrant = 1;
+
+ // x/z step
+ protected Vector3f stepScale;
+
+ // center of the patch in relation to (0,0,0)
+ protected Vector2f offset;
+
+ // amount the patch has been shifted.
+ protected float offsetAmount;
+
+ //protected LodCalculator lodCalculator;
+ //protected LodCalculatorFactory lodCalculatorFactory;
+
+ protected TerrainPatch leftNeighbour, topNeighbour, rightNeighbour, bottomNeighbour;
+ protected boolean searchedForNeighboursAlready = false;
+
+
+ protected float[] lodEntropy;
+
+ public TerrainPatch() {
+ super("TerrainPatch");
+ }
+
+ public TerrainPatch(String name) {
+ super(name);
+ }
+
+ public TerrainPatch(String name, int size) {
+ this(name, size, new Vector3f(1,1,1), null, new Vector3f(0,0,0));
+ }
+
+ /**
+ * Constructor instantiates a new <code>TerrainPatch</code> object. The
+ * parameters and heightmap data are then processed to generate a
+ * <code>TriMesh</code> object for rendering.
+ *
+ * @param name
+ * the name of the terrain patch.
+ * @param size
+ * the size of the heightmap.
+ * @param stepScale
+ * the scale for the axes.
+ * @param heightMap
+ * the height data.
+ * @param origin
+ * the origin offset of the patch.
+ */
+ public TerrainPatch(String name, int size, Vector3f stepScale,
+ float[] heightMap, Vector3f origin) {
+ this(name, size, stepScale, heightMap, origin, size, new Vector2f(), 0);
+ }
+
+ /**
+ * Constructor instantiates a new <code>TerrainPatch</code> object. The
+ * parameters and heightmap data are then processed to generate a
+ * <code>TriMesh</code> object for renderering.
+ *
+ * @param name
+ * the name of the terrain patch.
+ * @param size
+ * the size of the patch.
+ * @param stepScale
+ * the scale for the axes.
+ * @param heightMap
+ * the height data.
+ * @param origin
+ * the origin offset of the patch.
+ * @param totalSize
+ * the total size of the terrain. (Higher if the patch is part of
+ * a <code>TerrainQuad</code> tree.
+ * @param offset
+ * the offset for texture coordinates.
+ * @param offsetAmount
+ * the total offset amount. Used for texture coordinates.
+ */
+ public TerrainPatch(String name, int size, Vector3f stepScale,
+ float[] heightMap, Vector3f origin, int totalSize,
+ Vector2f offset, float offsetAmount) {
+ super(name);
+ this.size = size;
+ this.stepScale = stepScale;
+ this.totalSize = totalSize;
+ this.offsetAmount = offsetAmount;
+ this.offset = offset;
+
+ setLocalTranslation(origin);
+
+ geomap = new LODGeomap(size, heightMap);
+ Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false);
+ setMesh(m);
+
+ }
+
+ /**
+ * This calculation is slow, so don't use it often.
+ */
+ public void generateLodEntropies() {
+ float[] entropies = new float[getMaxLod()+1];
+ for (int i = 0; i <= getMaxLod(); i++){
+ int curLod = (int) Math.pow(2, i);
+ IntBuffer buf = geomap.writeIndexArrayLodDiff(null, curLod, false, false, false, false);
+ entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, buf);
+ }
+
+ lodEntropy = entropies;
+ }
+
+ public float[] getLodEntropies(){
+ if (lodEntropy == null){
+ generateLodEntropies();
+ }
+ return lodEntropy;
+ }
+
+ @Deprecated
+ public FloatBuffer getHeightmap() {
+ return BufferUtils.createFloatBuffer(geomap.getHeightArray());
+ }
+
+ public float[] getHeightMap() {
+ return geomap.getHeightArray();
+ }
+
+ /**
+ * The maximum lod supported by this terrain patch.
+ * If the patch size is 32 then the returned value would be log2(32)-2 = 3
+ * You can then use that value, 3, to see how many times you can divide 32 by 2
+ * before the terrain gets too un-detailed (can't stitch it any further).
+ * @return
+ */
+ 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
+
+ return maxLod;
+ }
+
+ protected void reIndexGeometry(HashMap<String,UpdatedTerrainPatch> updated, boolean useVariableLod) {
+
+ UpdatedTerrainPatch utp = updated.get(getName());
+
+ if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) {
+ int pow = (int) Math.pow(2, utp.getNewLod());
+ boolean left = utp.getLeftLod() > utp.getNewLod();
+ boolean top = utp.getTopLod() > utp.getNewLod();
+ boolean right = utp.getRightLod() > utp.getNewLod();
+ boolean bottom = utp.getBottomLod() > utp.getNewLod();
+
+ IntBuffer ib = null;
+ if (useVariableLod)
+ ib = geomap.writeIndexArrayLodVariable(null, 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()));
+ else
+ ib = geomap.writeIndexArrayLodDiff(null, pow, right, top, left, bottom);
+ utp.setNewIndexBuffer(ib);
+ }
+
+ }
+
+
+ public Vector2f getTex(float x, float z, Vector2f store) {
+ if (x < 0 || z < 0 || x >= size || z >= size) {
+ store.set(Vector2f.ZERO);
+ return store;
+ }
+ int idx = (int) (z * size + x);
+ 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
+ * @param z local to the terrain patch
+ * @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis
+ */
+ public Triangle getTriangle(float x, float z) {
+ return geomap.getTriangleAtPoint(x, z, getWorldScale() , getWorldTranslation());
+ }
+
+ /**
+ * Get the triangles at the specified grid point. Probably only 2 triangles
+ * @param x local to the terrain patch
+ * @param z local to the terrain patch
+ * @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis
+ */
+ public Triangle[] getGridTriangles(float x, float z) {
+ return geomap.getGridTrianglesAtPoint(x, z, getWorldScale() , getWorldTranslation());
+ }
+
+ protected void setHeight(List<LocationHeight> 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.getHeightArray()[idx] = lh.h;
+ } else {
+ float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1);
+ geomap.getHeightArray()[idx] = h+lh.h;
+ }
+
+ }
+
+ FloatBuffer newVertexBuffer = geomap.writeVertexArray(null, stepScale, false);
+ getMesh().clearBuffer(Type.Position);
+ getMesh().setBuffer(Type.Position, 3, newVertexBuffer);
+ }
+
+ /**
+ * recalculate all of the normal vectors in this terrain patch
+ */
+ protected void updateNormals() {
+ FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale());
+ getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer);
+ FloatBuffer newTangentBuffer = null;
+ FloatBuffer newBinormalBuffer = null;
+ FloatBuffer[] tb = geomap.writeTangentArray(newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale());
+ newTangentBuffer = tb[0];
+ newBinormalBuffer = tb[1];
+ getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer);
+ getMesh().getBuffer(Type.Binormal).updateData(newBinormalBuffer);
+ }
+
+ /**
+ * 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
+ * patch, and saves those normals in the neighbour's edges too.
+ *
+ * Takes 4 points (if has neighbour on that side) for each
+ * point on the edge of the patch:
+ * *
+ * |
+ * *---x---*
+ * |
+ * *
+ * 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.
+ */
+ protected void fixNormalEdges(TerrainPatch right,
+ TerrainPatch bottom,
+ TerrainPatch top,
+ TerrainPatch left,
+ TerrainPatch bottomRight,
+ TerrainPatch bottomLeft,
+ TerrainPatch topRight,
+ TerrainPatch topLeft)
+ {
+ Vector3f rootPoint = new Vector3f();
+ Vector3f rightPoint = new Vector3f();
+ Vector3f leftPoint = new Vector3f();
+ Vector3f topPoint = new Vector3f();
+
+ Vector3f bottomPoint = new Vector3f();
+
+ Vector3f tangent = new Vector3f();
+ 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<s+1; i++) {
+ rootPoint.set(s, this.getHeightmapHeight(s,i), i);
+ leftPoint.set(s-1, this.getHeightmapHeight(s-1,i), i);
+ rightPoint.set(s+1, right.getHeightmapHeight(1,i), i);
+
+ if (i == 0) { // top point
+ if (top == null) {
+ bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);
+
+ averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), s);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), 0);
+ } else {
+ topPoint.set(s, top.getHeightmapHeight(s,s-1), i-1);
+ bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);
+
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), s);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), 0);
+
+ if (topRight != null) {
+ VertexBuffer topRightNB = topRight.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)topRightNB.getData(), (s+1)*s);
+ topRightNB.setUpdateNeeded();
+ }
+ }
+ } else if (i == s) { // bottom point
+ if (bottom == null) {
+ topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);
+
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i+1)-1);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*(s));
+ } else {
+ topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);
+ bottomPoint.set(s, bottom.getHeightmapHeight(s,1), i+1);
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);
+ VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);
+
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s+1)-1);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*s);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), s);
+
+ if (bottomRight != null) {
+ VertexBuffer bottomRightNB = bottomRight.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)bottomRightNB.getData(), 0);
+ bottomRightNB.setUpdateNeeded();
+ }
+ downNB.setUpdateNeeded();
+ }
+ } else { // all in the middle
+ topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);
+ bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i+1)-1);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*(i));
+ }
+ }
+ right.getMesh().getBuffer(Type.Normal).setUpdateNeeded();
+ }
+
+ if (left != null) { // left side, works its way down
+ for (int i=0; i<s+1; i++) {
+ rootPoint.set(0, this.getHeightmapHeight(0,i), i);
+ leftPoint.set(-1, left.getHeightmapHeight(s-1,i), i);
+ rightPoint.set(1, this.getHeightmapHeight(1,i), i);
+
+ if (i == 0) { // top point
+ if (top == null) {
+ bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);
+ averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), 0);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), s);
+ } else {
+ topPoint.set(0, top.getHeightmapHeight(0,s-1), i-1);
+ bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);
+
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), 0);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), s);
+
+ if (topLeft != null) {
+ VertexBuffer topLeftNB = topLeft.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)topLeftNB.getData(), (s+1)*(s+1)-1);
+ topLeftNB.setUpdateNeeded();
+ }
+ }
+ } else if (i == s) { // bottom point
+ if (bottom == null) {
+ topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);
+
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s));
+ BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);
+ } else {
+ topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);
+ bottomPoint.set(0, bottom.getHeightmapHeight(0,1), i+1);
+
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);
+ VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);
+
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s));
+ BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), 0);
+
+ if (bottomLeft != null) {
+ VertexBuffer bottomLeftNB = bottomLeft.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)bottomLeftNB.getData(), s);
+ bottomLeftNB.setUpdateNeeded();
+ }
+ downNB.setUpdateNeeded();
+ }
+ } else { // all in the middle
+ topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);
+ bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);
+
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i));
+ BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);
+ }
+ }
+ left.getMesh().getBuffer(Type.Normal).setUpdateNeeded();
+ }
+
+ if (top != null) { // top side, works its way right
+ for (int i=0; i<s+1; i++) {
+ rootPoint.set(i, this.getHeightmapHeight(i,0), 0);
+ topPoint.set(i, top.getHeightmapHeight(i,s-1), -1);
+ bottomPoint.set(i, this.getHeightmapHeight(i,1), 1);
+
+ if (i == 0) { // left corner
+ // handled by left side pass
+
+ } else if (i == s) { // right corner
+
+ // handled by this patch when it does its right side
+
+ } else { // all in the middle
+ leftPoint.set(i-1, this.getHeightmapHeight(i-1,0), 0);
+ rightPoint.set(i+1, this.getHeightmapHeight(i+1,0), 0);
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), i);
+ VertexBuffer topNB = top.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)topNB.getData(), (s+1)*(s)+i);
+ }
+ }
+ top.getMesh().getBuffer(Type.Normal).setUpdateNeeded();
+
+ }
+
+ if (bottom != null) { // bottom side, works its way right
+ for (int i=0; i<s+1; i++) {
+ rootPoint.set(i, this.getHeightmapHeight(i,s), s);
+ topPoint.set(i, this.getHeightmapHeight(i,s-1), s-1);
+ bottomPoint.set(i, bottom.getHeightmapHeight(i,1), s+1);
+
+ if (i == 0) { // left
+ // handled by the left side pass
+
+ } else if (i == s) { // right
+
+ // handled by this patch when it does its right side
+
+ } else { // all in the middle
+ leftPoint.set(i-1, this.getHeightmapHeight(i-1,s), s);
+ rightPoint.set(i+1, this.getHeightmapHeight(i+1,s), s);
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, null, null, null, null, null, normal, tangent, binormal);
+ VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s)+i);
+ VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);
+ BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), i);
+ }
+ }
+ bottom.getMesh().getBuffer(Type.Normal).setUpdateNeeded();
+
+ }
+
+ this.getMesh().getBuffer(Type.Normal).setUpdateNeeded();
+ this.getMesh().getBuffer(Type.Tangent).setUpdateNeeded();
+ this.getMesh().getBuffer(Type.Binormal).setUpdateNeeded();
+ }
+
+ protected void averageNormalsTangents(
+ Vector3f topPoint,
+ Vector3f rootPoint,
+ Vector3f leftPoint,
+ Vector3f bottomPoint,
+ Vector3f rightPoint,
+ Vector2f topTex,
+ Vector2f rootTex,
+ Vector2f leftTex,
+ Vector2f bottomTex,
+ Vector2f rightTex,
+ Vector3f normal,
+ Vector3f tangent,
+ Vector3f binormal)
+ {
+ Vector3f scale = getWorldScale();
+
+ Vector3f n1 = Vector3f.ZERO;
+ if (topPoint != null && leftPoint != null) {
+ n1 = calculateNormal(topPoint.mult(scale), rootPoint.mult(scale), leftPoint.mult(scale));
+ }
+ Vector3f n2 = Vector3f.ZERO;
+ if (leftPoint != null && bottomPoint != null) {
+ n2 = calculateNormal(leftPoint.mult(scale), rootPoint.mult(scale), bottomPoint.mult(scale));
+ }
+ Vector3f n3 = Vector3f.ZERO;
+ if (rightPoint != null && bottomPoint != null) {
+ n3 = calculateNormal(bottomPoint.mult(scale), rootPoint.mult(scale), rightPoint.mult(scale));
+ }
+ Vector3f n4 = Vector3f.ZERO;
+ if (rightPoint != null && topPoint != null) {
+ n4 = calculateNormal(rightPoint.mult(scale), rootPoint.mult(scale), topPoint.mult(scale));
+ }
+
+ if (bottomPoint != null && rightPoint != null && rootTex != null && rightTex != null && bottomTex != null)
+ LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),bottomPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,bottomTex}, tangent, binormal);
+
+ normal.set(n1.add(n2).add(n3).add(n4).normalizeLocal());
+ }
+
+ private Vector3f calculateNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint) {
+ Vector3f normal = new Vector3f();
+ normal.set(firstPoint).subtractLocal(rootPoint)
+ .crossLocal(secondPoint.subtract(rootPoint)).normalizeLocal();
+ return normal;
+ }
+
+ protected Vector3f getMeshNormal(int x, int z) {
+ if (x >= 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();
+ normal.x = nb.get(index);
+ normal.y = nb.get(index+1);
+ normal.z = nb.get(index+2);
+ return normal;
+ }
+
+ /**
+ * Locks the mesh (sets it static) to improve performance.
+ * But it it not editable then. Set unlock to make it editable.
+ */
+ public void lockMesh() {
+ getMesh().setStatic();
+ }
+
+ /**
+ * Unlocks the mesh (sets it dynamic) to make it editable.
+ * It will be editable but performance will be reduced.
+ * Call lockMesh to improve performance.
+ */
+ public void unlockMesh() {
+ getMesh().setDynamic();
+ }
+
+ /**
+ * Returns the offset amount this terrain patch uses for textures.
+ *
+ * @return The current offset amount.
+ */
+ public float getOffsetAmount() {
+ return offsetAmount;
+ }
+
+ /**
+ * Returns the step scale that stretches the height map.
+ *
+ * @return The current step scale.
+ */
+ public Vector3f getStepScale() {
+ return stepScale;
+ }
+
+ /**
+ * Returns the total size of the terrain.
+ *
+ * @return The terrain's total size.
+ */
+ public int getTotalSize() {
+ return totalSize;
+ }
+
+ /**
+ * Returns the size of this terrain patch.
+ *
+ * @return The current patch size.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /**
+ * Returns the current offset amount. This is used when building texture
+ * coordinates.
+ *
+ * @return The current offset amount.
+ */
+ public Vector2f getOffset() {
+ return offset;
+ }
+
+ /**
+ * Sets the value for the current offset amount to use when building texture
+ * coordinates. Note that this does <b>NOT </b> rebuild the terrain at all.
+ * This is mostly used for outside constructors of terrain patches.
+ *
+ * @param offset
+ * The new texture offset.
+ */
+ public void setOffset(Vector2f offset) {
+ this.offset = offset;
+ }
+
+ /**
+ * Sets the size of this terrain patch. Note that this does <b>NOT </b>
+ * rebuild the terrain at all. This is mostly used for outside constructors
+ * of terrain patches.
+ *
+ * @param size
+ * The new size.
+ */
+ public void setSize(int size) {
+ this.size = size;
+
+ maxLod = -1; // reset it
+ }
+
+ /**
+ * Sets the total size of the terrain . Note that this does <b>NOT </b>
+ * rebuild the terrain at all. This is mostly used for outside constructors
+ * of terrain patches.
+ *
+ * @param totalSize
+ * The new total size.
+ */
+ public void setTotalSize(int totalSize) {
+ this.totalSize = totalSize;
+ }
+
+ /**
+ * Sets the step scale of this terrain patch's height map. Note that this
+ * does <b>NOT </b> rebuild the terrain at all. This is mostly used for
+ * outside constructors of terrain patches.
+ *
+ * @param stepScale
+ * The new step scale.
+ */
+ public void setStepScale(Vector3f stepScale) {
+ this.stepScale = stepScale;
+ }
+
+ /**
+ * Sets the offset of this terrain texture map. Note that this does <b>NOT
+ * </b> rebuild the terrain at all. This is mostly used for outside
+ * constructors of terrain patches.
+ *
+ * @param offsetAmount
+ * The new texture offset.
+ */
+ public void setOffsetAmount(float offsetAmount) {
+ this.offsetAmount = offsetAmount;
+ }
+
+ /**
+ * @return Returns the quadrant.
+ */
+ public short getQuadrant() {
+ return quadrant;
+ }
+
+ /**
+ * @param quadrant
+ * The quadrant to set.
+ */
+ public void setQuadrant(short quadrant) {
+ this.quadrant = quadrant;
+ }
+
+ public int getLod() {
+ return lod;
+ }
+
+ public void setLod(int lod) {
+ this.lod = lod;
+ }
+
+ public int getPreviousLod() {
+ return previousLod;
+ }
+
+ public void setPreviousLod(int previousLod) {
+ this.previousLod = previousLod;
+ }
+
+ protected int getLodLeft() {
+ return lodLeft;
+ }
+
+ protected void setLodLeft(int lodLeft) {
+ this.lodLeft = lodLeft;
+ }
+
+ protected int getLodTop() {
+ return lodTop;
+ }
+
+ protected void setLodTop(int lodTop) {
+ this.lodTop = lodTop;
+ }
+
+ protected int getLodRight() {
+ return lodRight;
+ }
+
+ protected void setLodRight(int lodRight) {
+ this.lodRight = lodRight;
+ }
+
+ protected int getLodBottom() {
+ return lodBottom;
+ }
+
+ protected void setLodBottom(int lodBottom) {
+ this.lodBottom = lodBottom;
+ }
+
+ /*public void setLodCalculator(LodCalculatorFactory lodCalculatorFactory) {
+ this.lodCalculatorFactory = lodCalculatorFactory;
+ setLodCalculator(lodCalculatorFactory.createCalculator(this));
+ }*/
+
+ @Override
+ public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
+ if (refreshFlags != 0)
+ throw new IllegalStateException("Scene graph must be updated" +
+ " before checking collision");
+
+ 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)
+ return collideWithBoundingVolume((BoundingVolume)other, results);
+ else {
+ throw new UnsupportedCollisionException("TerrainPatch cannnot collide with "+other.getClass().getName());
+ }
+ }
+
+
+ private int collideWithRay(Ray ray, CollisionResults results) {
+ // This should be handled in the root terrain quad
+ return 0;
+ }
+
+ private int collideWithBoundingVolume(BoundingVolume boundingVolume, CollisionResults results) {
+ if (boundingVolume instanceof BoundingBox)
+ return collideWithBoundingBox((BoundingBox)boundingVolume, results);
+ else if(boundingVolume instanceof BoundingSphere) {
+ BoundingSphere sphere = (BoundingSphere) boundingVolume;
+ BoundingBox bbox = new BoundingBox(boundingVolume.getCenter().clone(), sphere.getRadius(),
+ sphere.getRadius(),
+ sphere.getRadius());
+ return collideWithBoundingBox(bbox, results);
+ }
+ return 0;
+ }
+
+ protected Vector3f worldCoordinateToLocal(Vector3f loc) {
+ Vector3f translated = new Vector3f();
+ translated.x = loc.x/getWorldScale().x - getWorldTranslation().x;
+ translated.y = loc.y/getWorldScale().y - getWorldTranslation().y;
+ translated.z = loc.z/getWorldScale().z - getWorldTranslation().z;
+ return translated;
+ }
+
+ /**
+ * 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()));
+ Vector3f bottomLeft = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x-bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent()));
+ Vector3f bottomRight = worldCoordinateToLocal(new Vector3f(bbox.getCenter().x+bbox.getXExtent(), 0, bbox.getCenter().z+bbox.getZExtent()));
+
+ Triangle t = getTriangle(topLeft.x, topLeft.z);
+ if (t != null && bbox.collideWith(t, results) > 0)
+ return 1;
+ t = getTriangle(topRight.x, topRight.z);
+ if (t != null && bbox.collideWith(t, results) > 0)
+ return 1;
+ t = getTriangle(bottomLeft.x, bottomLeft.z);
+ if (t != null && bbox.collideWith(t, results) > 0)
+ return 1;
+ 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<bottomLeft.z; z+=1) {
+ for (float x=topLeft.x; x<topRight.x; x+=1) {
+
+ if (x < 0 || z < 0 || x >= size || z >= size)
+ continue;
+ t = getTriangle(x,z);
+ if (t != null && bbox.collideWith(t, results) > 0)
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ // the mesh is removed, and reloaded when read() is called
+ // 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);
+ oc.write(totalSize, "totalSize", 16);
+ oc.write(quadrant, "quadrant", (short)0);
+ oc.write(stepScale, "stepScale", Vector3f.UNIT_XYZ);
+ oc.write(offset, "offset", Vector3f.UNIT_XYZ);
+ oc.write(offsetAmount, "offsetAmount", 0);
+ //oc.write(lodCalculator, "lodCalculator", null);
+ //oc.write(lodCalculatorFactory, "lodCalculatorFactory", null);
+ oc.write(lodEntropy, "lodEntropy", null);
+ oc.write(geomap, "geomap", null);
+
+ setMesh(temp);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ size = ic.readInt("size", 16);
+ totalSize = ic.readInt("totalSize", 16);
+ quadrant = ic.readShort("quadrant", (short)0);
+ stepScale = (Vector3f) ic.readSavable("stepScale", Vector3f.UNIT_XYZ);
+ offset = (Vector2f) ic.readSavable("offset", Vector3f.UNIT_XYZ);
+ offsetAmount = ic.readFloat("offsetAmount", 0);
+ //lodCalculator = (LodCalculator) ic.readSavable("lodCalculator", new DistanceLodCalculator());
+ //lodCalculator.setTerrainPatch(this);
+ //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
+ ensurePositiveVolumeBBox();
+ }
+
+ @Override
+ public TerrainPatch clone() {
+ TerrainPatch clone = new TerrainPatch();
+ clone.name = name.toString();
+ clone.size = size;
+ clone.totalSize = totalSize;
+ clone.quadrant = quadrant;
+ clone.stepScale = stepScale.clone();
+ clone.offset = offset.clone();
+ clone.offsetAmount = offsetAmount;
+ //clone.lodCalculator = lodCalculator.clone();
+ //clone.lodCalculator.setTerrainPatch(clone);
+ //clone.setLodCalculator(lodCalculatorFactory.clone());
+ clone.geomap = new LODGeomap(size, geomap.getHeightArray());
+ clone.setLocalTranslation(getLocalTranslation().clone());
+ Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false);
+ clone.setMesh(m);
+ clone.setMaterial(material.clone());
+ return clone;
+ }
+
+ protected void ensurePositiveVolumeBBox() {
+ if (getModelBound() instanceof BoundingBox) {
+ if (((BoundingBox)getModelBound()).getYExtent() < 0.001f) {
+ // a correction so the box always has a volume
+ ((BoundingBox)getModelBound()).setYExtent(0.001f);
+ updateWorldBound();
+ }
+ }
+ }
+
+
+
+}