Hi all,
I’m trying to run my game on my Android phone (Samsung Galaxy Ace 2 - Mali 400 MP single core), but I get the following error:
09-17 22:01:33.420: E/cargame.core.CarGame(23171): GameState attached.
09-17 22:01:33.420: E/cargame.core.CarGame(23171): GameState-update: super updated.
09-17 22:01:33.430: E/cargame.core.CarGame(23171): GameState-update: begin update nodes.
09-17 22:01:33.690: D/dalvikvm(23171): GC_CONCURRENT freed 1145K, 70% free 7392K/24391K, external 10411K/12437K, paused 3ms+17ms
09-17 22:01:33.940: D/dalvikvm(23171): GC_CONCURRENT freed 1752K, 69% free 7688K/24391K, external 10411K/12437K, paused 2ms+8ms
09-17 22:01:34.210: D/dalvikvm(23171): GC_CONCURRENT freed 517K, 63% free 9218K/24391K, external 10411K/12437K, paused 3ms+10ms
09-17 22:01:34.450: D/dalvikvm(23171): GC_FOR_MALLOC freed <1K, 58% free 10338K/24391K, external 10411K/12437K, paused 93ms
09-17 22:01:34.700: D/dalvikvm(23171): GC_CONCURRENT freed 775K, 53% free 11610K/24391K, external 10411K/12437K, paused 5ms+13ms
09-17 22:01:35.000: D/dalvikvm(23171): GC_FOR_MALLOC freed <1K, 47% free 13053K/24391K, external 10411K/12437K, paused 109ms
09-17 22:01:35.200: D/dalvikvm(23171): GC_CONCURRENT freed 1163K, 43% free 13937K/24391K, external 10411K/12437K, paused 3ms+16ms
09-17 22:01:35.650: D/dalvikvm(23171): GC_CONCURRENT freed <1K, 35% free 15870K/24391K, external 10411K/12437K, paused 3ms+16ms
09-17 22:01:35.830: D/dalvikvm(23171): GC_EXTERNAL_ALLOC freed <1K, 35% free 15870K/24391K, external 10411K/12437K, paused 178ms
09-17 22:01:38.330: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (null) attached to this node (DebugShapeNode)
09-17 22:01:38.330: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (null) attached to this node (DebugShapeNode)
09-17 22:01:38.340: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelLocationDebugShape0) attached to this node (DebugShapeNode)
09-17 22:01:38.340: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelDirectionDebugShape0) attached to this node (DebugShapeNode)
09-17 22:01:38.340: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelAxleDebugShape0) attached to this node (DebugShapeNode)
09-17 22:01:38.350: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelRadiusDebugShape0) attached to this node (DebugShapeNode)
09-17 22:01:38.350: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelLocationDebugShape1) attached to this node (DebugShapeNode)
09-17 22:01:38.350: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelDirectionDebugShape1) attached to this node (DebugShapeNode)
09-17 22:01:38.350: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelAxleDebugShape1) attached to this node (DebugShapeNode)
09-17 22:01:38.360: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelRadiusDebugShape1) attached to this node (DebugShapeNode)
09-17 22:01:38.360: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelLocationDebugShape2) attached to this node (DebugShapeNode)
09-17 22:01:38.360: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelDirectionDebugShape2) attached to this node (DebugShapeNode)
09-17 22:01:38.360: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelAxleDebugShape2) attached to this node (DebugShapeNode)
09-17 22:01:38.360: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelRadiusDebugShape2) attached to this node (DebugShapeNode)
09-17 22:01:38.370: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelLocationDebugShape3) attached to this node (DebugShapeNode)
09-17 22:01:38.370: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelDirectionDebugShape3) attached to this node (DebugShapeNode)
09-17 22:01:38.370: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelAxleDebugShape3) attached to this node (DebugShapeNode)
09-17 22:01:38.370: I/com.jme3.scene.Node(23171): INFO Node 22:01:38 Child (WheelRadiusDebugShape3) attached to this node (DebugShapeNode)
09-17 22:01:38.630: D/dalvikvm(23171): GC_CONCURRENT freed 12163K, 77% free 5754K/24391K, external 15021K/17067K, paused 2ms+9ms
09-17 22:01:38.660: I/AndroidImageInfo(23171): Bitmap was deleted.
09-17 22:01:38.660: I/AndroidImageInfo(23171): Bitmap was deleted.
09-17 22:01:38.660: I/AndroidImageInfo(23171): Bitmap was deleted.
09-17 22:01:40.260: I/AndroidImageInfo(23171): Bitmap was deleted.
09-17 22:01:40.430: D/dalvikvm(23171): GC_CONCURRENT freed 1783K, 76% free 6006K/24391K, external 13936K/15960K, paused 3ms+11ms
09-17 22:01:40.620: D/dalvikvm(23171): GC_CONCURRENT freed 1971K, 76% free 6082K/24391K, external 13766K/15814K, paused 3ms+5ms
09-17 22:01:40.900: D/dalvikvm(23171): GC_CONCURRENT freed 1310K, 73% free 6805K/24391K, external 13761K/15790K, paused 3ms+20ms
09-17 22:01:41.110: W/EglHelper(23171): destroySurface() tid=11
09-17 22:01:41.140: W/EglHelper(23171): finish() tid=11
09-17 22:01:41.200: I/GLThread(23171): exiting tid=11
09-17 22:01:41.200: W/dalvikvm(23171): threadid=9: thread exiting with uncaught exception (group=0x4001e578)
09-17 22:01:41.200: E/AndroidHarness(23171): Exception thrown in Thread[GLThread 11,5,main]
09-17 22:01:41.200: E/AndroidHarness(23171): com.jme3.renderer.RendererException: OpenGL Error 1280. Enable error checking for more info.
09-17 22:01:41.200: E/AndroidHarness(23171): at com.jme3.renderer.android.OGLESShaderRenderer.onFrame(OGLESShaderRenderer.java:566)
09-17 22:01:41.200: E/AndroidHarness(23171): at com.jme3.system.android.OGLESContext.onDrawFrame(OGLESContext.java:334)
09-17 22:01:41.200: E/AndroidHarness(23171): at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1401)
09-17 22:01:41.200: E/AndroidHarness(23171): at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1146)
I allready have read some post about this error, but I’m running the latest nightly build from jME.
What is the best way to solve this problem ?
Thanks in advance.
Regards,
Vortex
I have a similar error running a program in Galaxy S3. It ran well on Galaxy Nexus, Nexus S, and Galaxy tab. But when I ran it in Galaxy S3, I got a similar exception. After some debugging effort it turns out in my case this error is generated after a GLES11.glDrawElements() was called with the following parameter: 4, 834, 5125, 0. Among these parameter, 2 of them supposed to be GLEnum:
- 4 means GL_TRIANGLES
- 5215 means GL_UNSIGNED_INT
Since error 1280 means invalid enum, I’m wondering if the error was caused by Samsung Galaxy S3 not supporting one of those enum?
BTW I am running jmonkey engine revision 9175, a couple of months old. The method call that cased the error was in OGLESShaderRenderer.java, line 2560. It looks this way:
GLES11.glDrawElements(
convertElementMode(mesh.getMode()),
indexBuf.getData().capacity(),
convertFormat(indexBuf.getFormat()),
0);
It looks a little bit odd for me that the call is invoked on GLES11 instead of GLES20. It might be a stupid question, but can anybody tell me why not GLES20? Was this a mistake? - Thanks
It seems to have been fixed in the latest jME3. Can you try it and see if it helps?
It doesn’t work on my device with the latest SVN. The debugging with my device doesn’t work so it’s a little hard to see what’s going wrong.
I updated the sdk to the latest version, but it still doesn’t work. I will try the latest SVN next.
There are no more calls to GLES11 in latest jME3. Do you still get GL_INVALID_ENUM in glDrawElements?
The answer is yes, Momoko_Fan.
I downloaded revision 9796 and run against it. I still have the same error. Now it got generated from line 1926, OGLESShaderRenderer.java, which is:
[java]
GLES20.glDrawElements(
convertElementMode(mesh.getMode()),
indexBuf.getData().capacity(),
convertFormat(indexBuf.getFormat()),
0);
[/java]
The way I check it is I set the debug point to that line. When the program reach it, I use Eclipse inspect to run the method checkGLError(), and no error was reported. Then I executed the line 1926, run checkGLError() again and see error 1280. Again, my phone is Galaxy S3:
Model number: GT-I9300
Android version: 4.0.4
BTW, I run some other program using another model, and it ran OK. When I check that line 1926, the parameter is:
(GL_TRIANGLE, XXX, GL_UNSIGNED_SHORT, YYY)
In the program that crash with open gl error 1280, the parameter is:
(GL_TRIANGLE, ZZZ, GL_UNSIGNED_INT, WWW)
OK, I found a temporary solution for me. It looks like that as long as the index buffer is of the type GL_UNSIGHED_SHORT, we should be OK. GL_UNSIGNED_INT is indeed a problem with Samsung Galaxy S 3 (and might be S2 also).
For some of my projects, all of my geometries are hard coded, or built in code right now. So I just manually force jmonkey to create short buffer instead of int buffer when creating the index buffer. Something like that:
short[] indexes = new short[size];
…
mesh.setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(indexes));
I dont know what to do if my models are imported, says, from Blender. Looks like sometimes it will result in short buffer (this was a while ago, so it could be because of an older version of jmonkey) and sometimes it will result in int buffer (which will definately crash Galaxy S3. Is there something we can do to make sure that our imported models use short buffer instead of int buffer, whenever possible?
When I check the OpenGL ES 2.0 reference page at (http://www.khronos.org/opengles/sdk/docs/man/), they say the following about glDrawElements:
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid * indices);
Parameters
mode
Specifies what kind of primitives to render. Symbolic constants GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, and GL_TRIANGLES are accepted.
count
Specifies the number of elements to be rendered.
type
Specifies the type of the values in indices. Must be GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT.
indices
Specifies a pointer to the location where the indices are stored.
So it looks to me that using GL_UNSINGED_INT is indeed a bug in jmonkey and the fact that some android phones actually support that enum is just luck (or may be bad luck in case of testing ).
Thanks for this thorough analysis!
I added that to my todo
I am experiencing this issue as well. I take it that it has not been patched yet? Or do I need to re-import all my models?
I still have this error with the terrain engine. I have a Samsung Galaxy Ace 2.
The terrain engine was the component that caused the crash from the stack trace in the first post. I’m sure that it is this component, because when I use the terrain engine in an isolated testcase it also crashes.
Good news!!! I just got it fixed by checking out the latest SVN version, and replacing all IntBuffers with ShortBuffers in the terrain classes. Code is below.
UpdatedTerrainPatch.java
[java]
/*
- Copyright © 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.scene.VertexBuffer.Type;
import java.nio.ShortBuffer;
/**
- Stores a terrain patch’s details so the LOD background thread can update
- the actual terrain patch back on the ogl thread.
- @author Brent Owens
*/
public class UpdatedTerrainPatch {
private TerrainPatch updatedPatch;
private int newLod;
private int previousLod;
private int rightLod,topLod,leftLod,bottomLod;
private ShortBuffer newIndexBuffer;
//private boolean reIndexNeeded = false;
private boolean fixEdges = false;
public UpdatedTerrainPatch(TerrainPatch updatedPatch) {
this.updatedPatch = updatedPatch;
}
public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {
this.updatedPatch = updatedPatch;
this.newLod = newLod;
}
public String getName() {
return updatedPatch.getName();
}
protected boolean lodChanged() {
if ( previousLod != newLod)
return true;
else
return false;
}
protected TerrainPatch getUpdatedPatch() {
return updatedPatch;
}
protected void setUpdatedPatch(TerrainPatch updatedPatch) {
this.updatedPatch = updatedPatch;
}
protected int getNewLod() {
return newLod;
}
public void setNewLod(int newLod) {
this.newLod = newLod;
if (this.newLod < 0)
throw new IllegalArgumentException("newLod cannot be less than zero, was: "+newLod);
}
/*protected IntBuffer getNewIndexBuffer() {
return newIndexBuffer;
}*/
protected void setNewIndexBuffer(ShortBuffer newIndexBuffer) {
this.newIndexBuffer = newIndexBuffer;
}
protected int getRightLod() {
return rightLod;
}
protected void setRightLod(int rightLod) {
this.rightLod = rightLod;
}
protected int getTopLod() {
return topLod;
}
protected void setTopLod(int topLod) {
this.topLod = topLod;
}
protected int getLeftLod() {
return leftLod;
}
protected void setLeftLod(int leftLod) {
this.leftLod = leftLod;
}
protected int getBottomLod() {
return bottomLod;
}
protected void setBottomLod(int bottomLod) {
this.bottomLod = bottomLod;
}
public boolean isReIndexNeeded() {
if (lodChanged() || isFixEdges())
return true;
//if (leftLod != newLod || rightLod != newLod || bottomLod != newLod || topLod != newLod)
// return true;
return false;
}
/*public void setReIndexNeeded(boolean reIndexNeeded) {
this.reIndexNeeded = reIndexNeeded;
}*/
public boolean isFixEdges() {
return fixEdges;
}
public void setFixEdges(boolean fixEdges) {
this.fixEdges = fixEdges;
}
/*public int getPreviousLod() {
return previousLod;
}*/
public void setPreviousLod(int previousLod) {
this.previousLod = previousLod;
}
public void updateAll() {
updatedPatch.setLod(newLod);
updatedPatch.setLodRight(rightLod);
updatedPatch.setLodTop(topLod);
updatedPatch.setLodLeft(leftLod);
updatedPatch.setLodBottom(bottomLod);
if (newIndexBuffer != null && isReIndexNeeded()) {
updatedPatch.setPreviousLod(previousLod);
updatedPatch.getMesh().clearBuffer(Type.Index);
updatedPatch.getMesh().setBuffer(Type.Index, 3, newIndexBuffer);
}
}
}
[/java]
LodGeomap.java
[java]
/*
- Copyright © 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.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.FastMath;
import com.jme3.math.Triangle;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.Mesh.Mode;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.terrain.GeoMap;
import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import com.jme3.scene.mesh.IndexBuffer;
import java.nio.ShortBuffer;
/**
-
Produces the mesh for the TerrainPatch.
-
This LOD algorithm generates a single triangle strip by first building the center of the
-
mesh, minus one outer edge around it. Then it builds the edges in counter-clockwise order,
-
starting at the bottom right and working up, then left across the top, then down across the
-
left, then right across the bottom.
-
It needs to know what its neighbour's LOD's are so it can stitch the edges.
-
It creates degenerate polygons in order to keep the winding order of the polygons and to move
-
the strip to a new position while still maintaining the continuity of the overall mesh. These
-
degenerates are removed quickly by the video card.
-
@author Brent Owens
*/
public class LODGeomap extends GeoMap {public LODGeomap() {
}@Deprecated
public LODGeomap(int size, FloatBuffer heightMap) {
super(heightMap, size, size, 1);
}public LODGeomap(int size, float[] heightMap) {
super(heightMap, size, size, 1);
}public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center) {
return this.createMesh(scale, tcScale, tcOffset, offsetAmount, totalSize, center, 1, false, false, false, false);
}public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {
FloatBuffer pb = writeVertexArray(null, scale, center);
FloatBuffer texb = writeTexCoordArray(null, tcOffset, tcScale, offsetAmount, totalSize);
FloatBuffer nb = writeNormalArray(null, scale);
ShortBuffer ib = writeIndexArrayLodDiff(null, lod, rightLod, topLod, leftLod, bottomLod);
FloatBuffer bb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);
FloatBuffer tanb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);
writeTangentArray(nb, tanb, bb, texb, scale);
Mesh m = new Mesh();
m.setMode(Mode.TriangleStrip);
m.setBuffer(Type.Position, 3, pb);
m.setBuffer(Type.Normal, 3, nb);
m.setBuffer(Type.Tangent, 3, tanb);
m.setBuffer(Type.Binormal, 3, bb);
m.setBuffer(Type.TexCoord, 2, texb);
m.setBuffer(Type.Index, 3, ib);
m.setStatic();
m.updateBound();
return m;
}public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale, float offsetAmount, int totalSize) {
if (store != null) {
if (store.remaining() = 0; y–) {
for (int x = 0; x < getWidth(); x++) {
getUV(x, y, tcStore, offset, offsetAmount, totalSize);
float tx = tcStore.x * scale.x;
float ty = tcStore.y * scale.y;
store.put(tx);
store.put(ty);
}
}return store;
}
public Vector2f getUV(int x, int y, Vector2f store, Vector2f offset, float offsetAmount, int totalSize) {
float offsetX = offset.x + (offsetAmount * 1.0f);
float offsetY = -offset.y + (offsetAmount * 1.0f);//note the -, we flip the tex coordsstore.set((((float) x) + offsetX) / (float) (totalSize - 1), // calculates percentage of texture here (((float) y) + offsetY) / (float) (totalSize - 1)); return store;
}
/**
-
Create the LOD index array that will seam its edges with its neighbour's LOD.
-
This is a scary method!!! It will break your mind.
-
@param store to store the index buffer
-
@param lod level of detail of the mesh
-
@param rightLod LOD of the right neighbour
-
@param topLod LOD of the top neighbour
-
@param leftLod LOD of the left neighbour
-
@param bottomLod LOD of the bottom neighbour
-
@return the LOD-ified index buffer
*/
public ShortBuffer writeIndexArrayLodDiff(IndexBuffer store, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {//if (true)
//return writeIndexArrayLodVariable(store, lod, height, lod, lod, lod);int putBuffer = 0;
IndexBuffer buffer = store;
int numIndexes = calculateNumIndexesLodDiff(lod);
if (store == null) {
buffer = IndexBuffer.createIndexBuffer(numIndexes,numIndexes);
}// generate center squares minus the edges
//System.out.println("for (x="+lod+"; x<"+(getWidth()-(2lod))+"; x+="+lod+")");
//System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1lod))+"; z+="+lod+")");
for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row
int rowIdx = r * getWidth();
int nextRowIdx = (r + 1 * lod) * getWidth();
for (int c = lod; c lod + 1) { //if not the last one
idx = (row - lod) * getWidth() - 1 - lod;
buffer.put(putBuffer++, idx);
idx = (row - lod) * getWidth() - 1;
buffer.put(putBuffer++, idx);
} else {
}
}
} else {
buffer.put(putBuffer++, corner);//br+1);//degenerate to flip winding order
for (int row = getWidth() - lod; row > lod; row -= lod) {
int idx = row * getWidth() - 1; // mult to get row
buffer.put(putBuffer++, idx);
buffer.put(putBuffer++, idx - lod);
}}
buffer.put(putBuffer++, getWidth() - 1);
//System.out.println("\nbuffer right: "+(buffer.getCount()-runningBufferCount));
//runningBufferCount = buffer.getCount();//System.out.println("\ntop:");
// top (the order gets reversed here so the diagonals line up)
if (topLod) { // if lower LOD
if (rightLod) {
buffer.put(putBuffer++, getWidth() - 1);
}
for (int col = getWidth() - 1; col >= lod; col -= 2 * lod) {
int idx = (lod * getWidth()) + col - lod; // next row
buffer.put(putBuffer++, idx);
idx = col - 2 * lod;
buffer.put(putBuffer++, idx);
if (col > lod * 2) { //if not the last one
idx = (lod * getWidth()) + col - 2 * lod;
buffer.put(putBuffer++, idx);
idx = col - 2 * lod;
buffer.put(putBuffer++, idx);
} else {
}
}
} else {
if (rightLod) {
buffer.put(putBuffer++, getWidth() - 1);
}
for (int col = getWidth() - 1 - lod; col > 0; col -= lod) {
int idx = col + (lod * getWidth());
buffer.put(putBuffer++, idx);
idx = col;
buffer.put(putBuffer++, idx);
}
buffer.put(putBuffer++, 0);
}
buffer.put(putBuffer++, 0);//System.out.println("\nbuffer top: "+(buffer.getCount()-runningBufferCount));
//runningBufferCount = buffer.getCount();//System.out.println("\nleft:");
// left
if (leftLod) { // if lower LOD
if (topLod) {
buffer.put(putBuffer++, 0);
}
for (int row = 0; row < getWidth() - lod; row += 2 * lod) {
int idx = (row + lod) * getWidth() + lod;
buffer.put(putBuffer++, idx);
idx = (row + 2 * lod) * getWidth();
buffer.put(putBuffer++, idx);
if (row < getWidth() - 1 - 2 * lod) { //if not the last one
idx = (row + 2 * lod) * getWidth() + lod;
buffer.put(putBuffer++, idx);
idx = (row + 2 * lod) * getWidth();
buffer.put(putBuffer++, idx);
} else {
}
}
} else {
if (!topLod) {
buffer.put(putBuffer++, 0);
}
//buffer.put(getWidth()+1); // degenerate
//buffer.put(0); // degenerate winding-flip
for (int row = lod; row < getWidth() - lod; row += lod) {
int idx = row * getWidth();
buffer.put(putBuffer++, idx);
idx = row * getWidth() + lod;
buffer.put(putBuffer++, idx);
}}
buffer.put(putBuffer++, getWidth() * (getWidth() - 1));//System.out.println("\nbuffer left: "+(buffer.getCount()-runningBufferCount));
//runningBufferCount = buffer.getCount();//if (true) return buffer.delegate;
//System.out.println("\nbottom");// bottom
if (bottomLod) { // if lower LOD
if (leftLod) {
buffer.put(putBuffer++, getWidth() * (getWidth() - 1));
}
// there was a slight bug here when really high LOD near maxLod
// far right has extra index one row up and all the way to the right, need to skip last index entered
// seemed to be fixed by making "getWidth()-1-2-lod" this: "getWidth()-1-2*lod", which seems more correct
for (int col = 0; col < getWidth() - lod; col += 2 * lod) {
int idx = getWidth() * (getWidth() - 1 - lod) + col + lod;
buffer.put(putBuffer++, idx);
idx = getWidth() * (getWidth() - 1) + col + 2 * lod;
buffer.put(putBuffer++, idx);
if (col < getWidth() - 1 - 2 * lod) { //if not the last one
idx = getWidth() * (getWidth() - 1 - lod) + col + 2 * lod;
buffer.put(putBuffer++, idx);
idx = getWidth() * (getWidth() - 1) + col + 2 * lod;
buffer.put(putBuffer++, idx);
} else {
}
}
} else {
if (leftLod) {
buffer.put(putBuffer++, getWidth() * (getWidth() - 1));
}
for (int col = lod; col < getWidth() - lod; col += lod) {
int idx = getWidth() * (getWidth() - 1 - lod) + col; // up
buffer.put(putBuffer++, idx);
idx = getWidth() * (getWidth() - 1) + col; // down
buffer.put(putBuffer++, idx);
}
//buffer.put(getWidth()*getWidth()-1-lod); // <-- THIS caused holes at the end!
}buffer.put(putBuffer++, getWidth() * getWidth() - 1);
//System.out.println("\nbuffer bottom: "+(buffer.getCount()-runningBufferCount));
//runningBufferCount = buffer.getCount();//System.out.println("\nBuffer size: "+buffer.getCount());
// fill in the rest of the buffer with degenerates, there should only be a couple
for (int i = buffer.size(); i < numIndexes; i++) {
buffer.put(putBuffer++, getWidth() * getWidth() - 1);
}return (ShortBuffer)buffer.getBuffer();
}
public ShortBuffer writeIndexArrayLodVariable(IndexBuffer store, int lod, int rightLod, int topLod, int leftLod, int bottomLod) {
IndexBuffer buffer = store; int numIndexes = calculateNumIndexesLodDiff(lod); if (store == null) { buffer = IndexBuffer.createIndexBuffer(numIndexes, numIndexes); } int putIndex = 0; // generate center squares minus the edges //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")"); //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row int rowIdx = r * getWidth(); int nextRowIdx = (r + 1 * lod) * getWidth(); for (int c = lod; c 0; i--) { // for each lod level of the neighbour idx = getWidth() * (i * rightLod + 1) - 1; for (int j = 1; j lod; row -= lod) { int idx = row * getWidth() - 1; // mult to get row buffer.put(putIndex++, idx); buffer.put(putIndex++, idx - lod); } buffer.put(putIndex++, getWidth() - 1); } //System.out.println("\nbuffer right: "+(buffer.getCount()-runningBufferCount)); //runningBufferCount = buffer.getCount(); //System.out.println("\ntop:"); // top (the order gets reversed here so the diagonals line up) if (topLod > lod) { // if lower LOD if (rightLod > lod) { // need to flip winding order buffer.put(putIndex++, getWidth() - 1); buffer.put(putIndex++, getWidth() * lod - 1); buffer.put(putIndex++, getWidth() - 1); } int idx = getWidth() - 1; int it = (getWidth() - 1) / topLod; // iterations int lodDiff = topLod / lod; for (int i = it; i > 0; i--) { // for each lod level of the neighbour idx = (i * topLod); for (int j = 1; j lod) { buffer.put(putIndex++, getWidth() - 1); } for (int col = getWidth() - 1 - lod; col > 0; col -= lod) { int idx = col + (lod * getWidth()); buffer.put(putIndex++, idx); idx = col; buffer.put(putIndex++, idx); } buffer.put(putIndex++, 0); } buffer.put(putIndex++, 0); //System.out.println("\nbuffer top: "+(buffer.getCount()-runningBufferCount)); //runningBufferCount = buffer.getCount(); //System.out.println("\nleft:"); // left if (leftLod > lod) { // if lower LOD int idx = 0; int it = (getWidth() - 1) / leftLod; // iterations int lodDiff = leftLod / lod; for (int i = 0; i < it; i++) { // for each lod level of the neighbour idx = getWidth() * (i * leftLod); for (int j = 1; j lod) { buffer.put(putIndex++, getWidth() * (getWidth() - 1)); buffer.put(putIndex++, getWidth() * (getWidth() - lod)); buffer.put(putIndex++, getWidth() * (getWidth() - 1)); } int idx = getWidth() * getWidth() - getWidth(); int it = (getWidth() - 1) / bottomLod; // iterations int lodDiff = bottomLod / lod; for (int i = 0; i < it; i++) { // for each lod level of the neighbour idx = getWidth() * getWidth() - getWidth() + (i * bottomLod); for (int j = 1; j lod) { buffer.put(putIndex++, getWidth() * (getWidth() - 1)); buffer.put(putIndex++, getWidth() * getWidth() - (getWidth() * lod) + lod); buffer.put(putIndex++, getWidth() * (getWidth() - 1)); } for (int col = lod; col < getWidth() - lod; col += lod) { int idx = getWidth() * (getWidth() - 1 - lod) + col; // up buffer.put(putIndex++, idx); idx = getWidth() * (getWidth() - 1) + col; // down buffer.put(putIndex++, idx); } //buffer.put(getWidth()*getWidth()-1-lod); // <-- THIS caused holes at the end! } buffer.put(putIndex++, getWidth() * getWidth() - 1); //System.out.println("\nbuffer bottom: "+(buffer.getCount()-runningBufferCount)); //runningBufferCount = buffer.getCount(); //System.out.println("\nBuffer size: "+buffer.getCount()); // fill in the rest of the buffer with degenerates, there should only be a couple for (int i = buffer.size(); i < numIndexes; i++) { buffer.put(putIndex++, getWidth() * getWidth() - 1); } return (ShortBuffer)buffer.getBuffer();
}
/private int calculateNumIndexesNormal(int lod) {
int length = getWidth()-1;
int num = ((length/lod)+1)((length/lod)+1)2;
System.out.println("num: "+num);
num -= 2((length/lod)+1);
System.out.println("num2: "+num);
// now get the degenerate indexes that exist between strip rows
num += 2*(((length/lod)+1)-2); // every row except the first and last
System.out.println("Index buffer size: "+num);
return num;
}*/
/**-
calculate how many indexes there will be.
-
This isn't that precise and there might be a couple extra.
*/
private int calculateNumIndexesLodDiff(int lod) {
if (lod == 0) {
lod = 1;
}
int length = getWidth() - 1; // make it even for lod calc
int side = (length / lod) + 1 - (2);
//System.out.println("side: "+side);
int num = side * side * 2;
//System.out.println("num: "+num);
num -= 2 * side; // remove one first row and one last row (they are only hit once each)
//System.out.println("num2: "+num);
// now get the degenerate indexes that exist between strip rows
int degenerates = 2 * (side - (2)); // every row except the first and last
num += degenerates;
//System.out.println("degenerates: "+degenerates);//System.out.println("center, before edges: "+num);
num += (getWidth() / lod) * 2 * 4;
num++;num += 10;// TODO remove me: extra
//System.out.println("Index buffer size: "+num);
return num;
}
public FloatBuffer[] writeTangentArray(FloatBuffer normalBuffer, FloatBuffer tangentStore, FloatBuffer binormalStore, FloatBuffer textureBuffer, Vector3f scale) {
if (!isLoaded()) {
throw new NullPointerException();
}if (tangentStore != null) { if (tangentStore.remaining() < getWidth() * getHeight() * 3) { throw new BufferUnderflowException(); } } else { tangentStore = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); } tangentStore.rewind(); if (binormalStore != null) { if (binormalStore.remaining() < getWidth() * getHeight() * 3) { throw new BufferUnderflowException(); } } else { binormalStore = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3); } binormalStore.rewind(); Vector3f normal = new Vector3f(); Vector3f tangent = new Vector3f(); Vector3f binormal = new Vector3f(); /*Vector3f v1 = new Vector3f(); Vector3f v2 = new Vector3f(); Vector3f v3 = new Vector3f(); Vector2f t1 = new Vector2f(); Vector2f t2 = new Vector2f(); Vector2f t3 = new Vector2f();*/ for (int r = 0; r < getHeight(); r++) { for (int c = 0; c < getWidth(); c++) { int idx = (r * getWidth() + c) * 3; normal.set(normalBuffer.get(idx), normalBuffer.get(idx+1), normalBuffer.get(idx+2)); tangent.set(normal.cross(new Vector3f(0,0,1))); binormal.set(new Vector3f(1,0,0).cross(normal)); BufferUtils.setInBuffer(tangent.normalizeLocal(), tangentStore, (r * getWidth() + c)); // save the tangent BufferUtils.setInBuffer(binormal.normalizeLocal(), binormalStore, (r * getWidth() + c)); // save the binormal } }
-
/* for (int r = 0; r < getHeight(); r++) {
for (int c = 0; c < getWidth(); c++) {
int texIdx = ((getHeight() - 1 - r) * getWidth() + c) * 2; // pull from the end
int texIdxAbove = ((getHeight() - 1 - (r - 1)) * getWidth() + c) * 2; // pull from the end
int texIdxNext = ((getHeight() - 1 - (r + 1)) * getWidth() + c) * 2; // pull from the end
v1.set(c, getValue(c, r), r);
t1.set(textureBuffer.get(texIdx), textureBuffer.get(texIdx + 1));
// below
if (r == getHeight()-1) { // last row
v3.set(c, getValue(c, r), r + 1);
float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdxAbove);
u += textureBuffer.get(texIdx);
float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdxAbove + 1);
v += textureBuffer.get(texIdx + 1);
t3.set(u, v);
} else {
v3.set(c, getValue(c, r + 1), r + 1);
t3.set(textureBuffer.get(texIdxNext), textureBuffer.get(texIdxNext + 1));
}
//right
if (c == getWidth()-1) { // last column
v2.set(c + 1, getValue(c, r), r);
float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdx - 2);
u += textureBuffer.get(texIdx);
float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdx - 1);
v += textureBuffer.get(texIdx - 1);
t2.set(u, v);
} else {
v2.set(c + 1, getValue(c + 1, r), r); // one to the right
t2.set(textureBuffer.get(texIdx + 2), textureBuffer.get(texIdx + 3));
}
calculateTangent(new Vector3f[]{v1.mult(scale), v2.mult(scale), v3.mult(scale)}, new Vector2f[]{t1, t2, t3}, tangent, binormal);
BufferUtils.setInBuffer(tangent, tangentStore, (r * getWidth() + c)); // save the tangent
BufferUtils.setInBuffer(binormal, binormalStore, (r * getWidth() + c)); // save the binormal
}
}
*/
return new FloatBuffer[]{tangentStore, binormalStore};
}
/**
*
* @param v Takes 3 vertices: root, right, bottom
* @param t Takes 3 tex coords: root, right, bottom
* @param tangent that will store the result
* @return the tangent store
*/
public static Vector3f calculateTangent(Vector3f[] v, Vector2f[] t, Vector3f tangent, Vector3f binormal) {
Vector3f edge1 = new Vector3f(); // y=0
Vector3f edge2 = new Vector3f(); // x=0
Vector2f edge1uv = new Vector2f(); // y=0
Vector2f edge2uv = new Vector2f(); // x=0
t[2].subtract(t[0], edge2uv);
t[1].subtract(t[0], edge1uv);
float det = edge1uv.x * edge2uv.y;// - edge1uv.y*edge2uv.x; = 0
boolean normalize = true;
if (Math.abs(det) < 0.0000001f) {
det = 1;
normalize = true;
}
v[1].subtract(v[0], edge1);
v[2].subtract(v[0], edge2);
tangent.set(edge1);
tangent.normalizeLocal();
binormal.set(edge2);
binormal.normalizeLocal();
float factor = 1 / det;
tangent.x = (edge2uv.y * edge1.x) * factor;
tangent.y = 0;
tangent.z = (edge2uv.y * edge1.z) * factor;
if (normalize) {
tangent.normalizeLocal();
}
binormal.x = 0;
binormal.y = (edge1uv.x * edge2.y) * factor;
binormal.z = (edge1uv.x * edge2.z) * factor;
if (normalize) {
binormal.normalizeLocal();
}
return tangent;
}
@Override
public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) {
if (!isLoaded()) {
throw new NullPointerException();
}
if (store != null) {
if (store.remaining() < getWidth() * getHeight() * 3) {
throw new BufferUnderflowException();
}
} else {
store = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);
}
store.rewind();
TempVars vars = TempVars.get();
Vector3f rootPoint = vars.vect1;
Vector3f rightPoint = vars.vect2;
Vector3f leftPoint = vars.vect3;
Vector3f topPoint = vars.vect4;
Vector3f bottomPoint = vars.vect5;
Vector3f tmp1 = vars.vect6;
// calculate normals for each polygon
for (int r = 0; r < getHeight(); r++) {
for (int c = 0; c delegate.limit())
throw new BufferOverflowException();
delegate.put(value);
} catch (BufferOverflowException e) {
System.out.println("err buffer size: "+delegate.capacity());
}
}
public int getCount() {
return count;
}
}
/**
* Get the two triangles that make up the grid section at the specified point.
*
* For every grid space there are two triangles oriented like this:
* *----*
* |a / |
* | / b|
* *----*
* The corners of the mesh have differently oriented triangles. The two
* corners that we have to special-case are the top left and bottom right
* corners. They are oriented inversely:
* *----*
* | \ b|
* |a \ |
* *----*
*/
protected float getHeight(int x, int z, float xm, float zm) {
int index = findClosestHeightIndex(x, z);
if (index < 0) {
return Float.NaN;
}
float h1 = hdata[index]; // top left
float h2 = hdata[index + 1]; // top right
float h3 = hdata[index + width]; // bottom left
float h4 = hdata[index + width + 1]; // bottom right
//float dix = (x % 1f) ;
//float diz = (z % 1f) ;
if ((x == 0 && z == 0) || (x == width - 2 && z == width - 2)) {
// top left or bottom right grid point
/* 1----2
* | \ b|
* |a \ |
* 3----4 */
if (xm<zm)
return h1 + xm*(h4-h3) + zm*(h3-h1);
else
return h1 + xm*(h2-h1) + zm*(h4-h2);
} else {
// all other grid points
/* 1----2
* |a / |
* | / b|
* 3----4 */
if (xm<(1-zm))
return h3 + (xm)*(h2-h1) + (1f-zm)*(h1-h3);
else
return h3 + (xm)*(h4-h3) + (1f-zm)*(h2-h4);
}
}
/**
* Get a representation of the underlying triangle at the given point,
* translated to world coordinates.
*
* @param x local x coordinate
* @param z local z coordinate
* @return a triangle in world space not local space
*/
protected Triangle getTriangleAtPoint(float x, float z, Vector3f scale, Vector3f translation) {
Triangle tri = getTriangleAtPoint(x, z);
if (tri != null) {
tri.get1().multLocal(scale).addLocal(translation);
tri.get2().multLocal(scale).addLocal(translation);
tri.get3().multLocal(scale).addLocal(translation);
}
return tri;
}
/**
* Get the two triangles that make up the grid section at the specified point,
* translated to world coordinates.
*
* @param x local x coordinate
* @param z local z coordinate
* @param scale
* @param translation
* @return two triangles in world space not local space
*/
protected Triangle[] getGridTrianglesAtPoint(float x, float z, Vector3f scale, Vector3f translation) {
Triangle[] tris = getGridTrianglesAtPoint(x, z);
if (tris != null) {
tris[0].get1().multLocal(scale).addLocal(translation);
tris[0].get2().multLocal(scale).addLocal(translation);
tris[0].get3().multLocal(scale).addLocal(translation);
tris[1].get1().multLocal(scale).addLocal(translation);
tris[1].get2().multLocal(scale).addLocal(translation);
tris[1].get3().multLocal(scale).addLocal(translation);
}
return tris;
}
/**
* Get the two triangles that make up the grid section at the specified point.
*
* For every grid space there are two triangles oriented like this:
* *----*
* |a / |
* | / b|
* *----*
* The corners of the mesh have differently oriented triangles. The two
* corners that we have to special-case are the top left and bottom right
* corners. They are oriented inversely:
* *----*
* | \ b|
* |a \ |
* *----*
*
* @param x local x coordinate
* @param z local z coordinate
* @return
*/
protected Triangle[] getGridTrianglesAtPoint(float x, float z) {
int gridX = (int) x;
int gridY = (int) z;
int index = findClosestHeightIndex(gridX, gridY);
if (index < 0) {
return null;
}
Triangle t = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());
Triangle t2 = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());
float h1 = hdata[index]; // top left
float h2 = hdata[index + 1]; // top right
float h3 = hdata[index + width]; // bottom left
float h4 = hdata[index + width + 1]; // bottom right
if ((gridX == 0 && gridY == 0) || (gridX == width - 2 && gridY == width - 2)) {
// top left or bottom right grid point
t.get(0).x = (gridX);
t.get(0).y = (h1);
t.get(0).z = (gridY);
t.get(1).x = (gridX);
t.get(1).y = (h3);
t.get(1).z = (gridY + 1);
t.get(2).x = (gridX + 1);
t.get(2).y = (h4);
t.get(2).z = (gridY + 1);
t2.get(0).x = (gridX);
t2.get(0).y = (h1);
t2.get(0).z = (gridY);
t2.get(1).x = (gridX + 1);
t2.get(1).y = (h4);
t2.get(1).z = (gridY + 1);
t2.get(2).x = (gridX + 1);
t2.get(2).y = (h2);
t2.get(2).z = (gridY);
} else {
// all other grid points
t.get(0).x = (gridX);
t.get(0).y = (h1);
t.get(0).z = (gridY);
t.get(1).x = (gridX);
t.get(1).y = (h3);
t.get(1).z = (gridY + 1);
t.get(2).x = (gridX + 1);
t.get(2).y = (h2);
t.get(2).z = (gridY);
t2.get(0).x = (gridX + 1);
t2.get(0).y = (h2);
t2.get(0).z = (gridY);
t2.get(1).x = (gridX);
t2.get(1).y = (h3);
t2.get(1).z = (gridY + 1);
t2.get(2).x = (gridX + 1);
t2.get(2).y = (h4);
t2.get(2).z = (gridY + 1);
}
return new Triangle[]{t, t2};
}
/**
* Get the triangle that the point is on.
*
* @param x coordinate in local space to the geomap
* @param z coordinate in local space to the geomap
* @return triangle in local space to the geomap
*/
protected Triangle getTriangleAtPoint(float x, float z) {
Triangle[] triangles = getGridTrianglesAtPoint(x, z);
if (triangles == null) {
//System.out.println("x,z: " + x + "," + z);
return null;
}
Vector2f point = new Vector2f(x, z);
Vector2f t1 = new Vector2f(triangles[0].get1().x, triangles[0].get1().z);
Vector2f t2 = new Vector2f(triangles[0].get2().x, triangles[0].get2().z);
Vector2f t3 = new Vector2f(triangles[0].get3().x, triangles[0].get3().z);
if (0 != FastMath.pointInsideTriangle(t1, t2, t3, point)) {
return triangles[0];
}
t1.set(triangles[1].get1().x, triangles[1].get1().z);
t1.set(triangles[1].get2().x, triangles[1].get2().z);
t1.set(triangles[1].get3().x, triangles[1].get3().z);
if (0 != FastMath.pointInsideTriangle(t1, t2, t3, point)) {
return triangles[1];
}
return null;
}
protected int findClosestHeightIndex(int x, int z) {
if (x = width - 1) {
return -1;
}
if (z = width - 1) {
return -1;
}
return z * width + x;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
}
@Override
public void read(JmeImporter im) throws IOException {
super.read(im);
}
}
[/java]
TerrainPatch.java
[java]
/*
- Copyright © 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.ShortBuffer;
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 = 0; // this terrain patch's LOD
private int maxLod = -1;
protected int previousLod = -1;
protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODsprotected 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;// these two vectors are calculated on the GL thread, but used in the outside LOD thread
protected Vector3f worldTranslationCached;
protected Vector3f worldScaleCached;protected float[] lodEntropy;
public TerrainPatch() {
super("TerrainPatch");
setBatchHint(BatchHint.Never);
}public TerrainPatch(String name) {
super(name);
setBatchHint(BatchHint.Never);
}public TerrainPatch(String name, int size) {
this(name, size, new Vector3f(1,1,1), null, new Vector3f(0,0,0));
}/**
- Constructor instantiates a new
TerrainPatch
object. The - parameters and heightmap data are then processed to generate a
-
TriMesh
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
TerrainPatch
object. The - parameters and heightmap data are then processed to generate a
-
TriMesh
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);
setBatchHint(BatchHint.Never);
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);
ShortBuffer 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 the maximum LOD
*/
public int getMaxLod() {
if (maxLod utp.getNewLod();
boolean top = utp.getTopLod() > utp.getNewLod();
boolean right = utp.getRightLod() > utp.getNewLod();
boolean bottom = utp.getBottomLod() > utp.getNewLod();ShortBuffer 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 = size) {
store.set(Vector2f.ZERO);
return store;
}
int idx = (int) (z * size + x);
return store.set(getMesh().getFloatBuffer(Type.TexCoord).get(idx2),
getMesh().getFloatBuffer(Type.TexCoord).get(idx2+1) );
}public float getHeightmapHeight(float x, float z) {
if (x = 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 locationHeights, boolean overrideHeight) {
for (LocationHeight lh : locationHeights) { if (lh.x = 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(newNormalBuffer, 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);
}
private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent, Vector3f binormal) {
VertexBuffer NB = mesh.getBuffer(Type.Normal);
VertexBuffer TB = mesh.getBuffer(Type.Tangent);
VertexBuffer BB = mesh.getBuffer(Type.Binormal);
BufferUtils.setInBuffer(normal, (FloatBuffer)NB.getData(), index);
BufferUtils.setInBuffer(tangent, (FloatBuffer)TB.getData(), index);
BufferUtils.setInBuffer(binormal, (FloatBuffer)BB.getData(), index);
NB.setUpdateNeeded();
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
-
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(0, this.getHeightmapHeight(s,i), 0);
leftPoint.set(-1, this.getHeightmapHeight(s-1,i), 0);
rightPoint.set(1, right.getHeightmapHeight(1,i), 0);if (i == 0) { // top point bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1); if (top == null) { averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), s, normal, tangent, binormal); setInBuffer(right.getMesh(), 0, normal, tangent, binormal); } else { topPoint.set(0, top.getHeightmapHeight(s,s-1), -1); averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint,normal, tangent, binormal); setInBuffer(this.getMesh(), s, normal, tangent, binormal); setInBuffer(right.getMesh(), 0, normal, tangent, binormal); setInBuffer(top.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); if (topRight != null) { // setInBuffer(topRight.getMesh(), (s+1)*s, normal, tangent, binormal); } } } else if (i == s) { // bottom point topPoint.set(0, this.getHeightmapHeight(s,s-1), -1); if (bottom == null) { averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); setInBuffer(right.getMesh(), (s+1)*(s), normal, tangent, binormal); } else { bottomPoint.set(0, bottom.getHeightmapHeight(s,1), 1); averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); setInBuffer(right.getMesh(), (s+1)*s, normal, tangent, binormal); setInBuffer(bottom.getMesh(), s, normal, tangent, binormal); if (bottomRight != null) { // setInBuffer(bottomRight.getMesh(), 0, normal, tangent, binormal); } } } else { // all in the middle topPoint.set(0, this.getHeightmapHeight(s,i-1), -1); bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1); averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal); setInBuffer(right.getMesh(), (s+1)*(i), normal, tangent, binormal); } }
}
if (left != null) { // left side, works its way down
for (int i=0; i<s+1; i++) {
rootPoint.set(0, this.getHeightmapHeight(0,i), 0);
leftPoint.set(-1, left.getHeightmapHeight(s-1,i), 0);
rightPoint.set(1, this.getHeightmapHeight(1,i), 0);if (i == 0) { // top point bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1); if (top == null) { averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), 0, normal, tangent, binormal); setInBuffer(left.getMesh(), s, normal, tangent, binormal); } else { topPoint.set(0, top.getHeightmapHeight(0,s-1), -1); averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), 0, normal, tangent, binormal); setInBuffer(left.getMesh(), s, normal, tangent, binormal); setInBuffer(top.getMesh(), (s+1)*s, normal, tangent, binormal); if (topLeft != null) { // setInBuffer(topLeft.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); } } } else if (i == s) { // bottom point topPoint.set(0, this.getHeightmapHeight(0,i-1), -1); if (bottom == null) { averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal); setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); } else { bottomPoint.set(0, bottom.getHeightmapHeight(0,1), 1); averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal); setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal); setInBuffer(bottom.getMesh(), 0, normal, tangent, binormal); if (bottomLeft != null) { // setInBuffer(bottomLeft.getMesh(), s, normal, tangent, binormal); } } } else { // all in the middle topPoint.set(0, this.getHeightmapHeight(0,i-1), -1); bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1); averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal); setInBuffer(this.getMesh(), (s+1)*(i), normal, tangent, binormal); setInBuffer(left.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal); } }
}
if (top != null) { // top side, works its way right
for (int i=0; i= size)
return null; // out of rangeint 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;
}
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.
*/
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 NOT 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 NOT
- 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 NOT
- 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 NOT 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 NOT
- 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 = 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();
}
}
}/**
- Caches the transforms (except rotation) so the LOD calculator,
- which runs on a separate thread, can access them safely.
*/
protected void cacheTerrainTransforms() {
this.worldScaleCached = getWorldScale().clone();
this.worldTranslationCached = getWorldTranslation().clone();
}
public Vector3f getWorldScaleCached() {
return worldScaleCached;
}public Vector3f getWorldTranslationCached() {
return worldTranslationCached;
} - Constructor instantiates a new
}
[/java]
EntropyComputeUtil.java
[java]
/*
- Copyright © 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.lodcalc.util;
import java.nio.ShortBuffer;
import com.jme3.bounding.BoundingBox;
import com.jme3.collision.CollisionResults;
import com.jme3.math.Matrix4f;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/**
-
Computes the entropy value δ (delta) for a given terrain block and
-
LOD level.
-
See the geomipmapping paper section
-
"2.3.1 Choosing the appropriate GeoMipMap level"
-
@author Kirill Vainer
*/
public class EntropyComputeUtil {public static float computeLodEntropy(Mesh terrainBlock, ShortBuffer lodIndices){
// Bounding box for the terrain block
BoundingBox bbox = (BoundingBox) terrainBlock.getBound();// Vertex positions for the block FloatBuffer positions = terrainBlock.getFloatBuffer(Type.Position); // Prepare to cast rays Vector3f pos = new Vector3f(); Vector3f dir = new Vector3f(0, -1, 0); Ray ray = new Ray(pos, dir); // Prepare collision results CollisionResults results = new CollisionResults(); // Set the LOD indices on the block VertexBuffer originalIndices = terrainBlock.getBuffer(Type.Index); terrainBlock.clearBuffer(Type.Index); terrainBlock.setBuffer(Type.Index, 3, lodIndices); // Recalculate collision mesh terrainBlock.createCollisionData(); float entropy = 0; for (int i = 0; i 0){ Vector3f contactPoint = results.getClosestCollision().getContactPoint(); float delta = Math.abs(realHeight - contactPoint.y); entropy = Math.max(delta, entropy); } } // Restore original indices terrainBlock.clearBuffer(Type.Index); terrainBlock.setBuffer(originalIndices); return entropy;
}
}
[/java]
No. That will limit the terrain size to 256x256.
An ‘android’ state of these classes will have to be created and switch between short and int index buffers. Not pretty but doable.
@Sploreg said: No. That will limit the terrain size to 256x256. An 'android' state of these classes will have to be created and switch between short and int index buffers. Not pretty but doable.
Maybe the terrain could use a shortBuffer if the size is below 256x256 and else an IntBuffer. Then just explain that android cannot support more than 256 x 256 tiles in the javadoc?
Would be a reasonable limitation IMO.
Only problem would be to correctly report the error…
Yea something like that can work. I will play with it a bit. A full Android Terrain wiki page will have to be made to describe the limitations, and as you suggest in the javadocs as well.
I think you also should make a notice in the “Add terrain” tool in the SDK - Terrain Editor.
The number of vertexes of my terrain is 67600. Is it correct that my heightmap is 256x256?