--- /dev/null
+Copyright (c) 2014, Kazuhiko Kobayashi
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
+following disclaimer.
+
+2. 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.
+
+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 HOLDER 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.
\ No newline at end of file
, baseDirectory.value / "src/ogre"
, baseDirectory.value / "src/pack"
, baseDirectory.value / "src/tools"
-// , baseDirectory.value / "src/terrain"
+ , baseDirectory.value / "src/terrain"
, baseDirectory.value / "src/xml"
)
sources in Compile ~= {
- dirs => dirs filter(file => (!file.getAbsolutePath.contains("cinematic") && !file.getAbsolutePath.contains("CollisionShapeFactory.java")))
+ dirs => dirs filter(file => (!file.getAbsolutePath.contains("cinematic") /* && !file.getAbsolutePath.contains("CollisionShapeFactory.java")*/))
}
unmanagedResourceDirectories in Compile <<= unmanagedSourceDirectories in Compile
libraryDependencies += "net.sf.sociaal" % "j-ogg-oggd" % "3.0.0.20130526"
-libraryDependencies += "net.sf.sociaal" % "j-ogg-vorbisd" % "3.0.0.20130526"
\ No newline at end of file
+libraryDependencies += "net.sf.sociaal" % "j-ogg-vorbisd" % "3.0.0.20130526"
}\r
}\r
\r
- Technique FixedFunc {\r
+ Technique {\r
}\r
-\r
}
\ No newline at end of file
}\r
}\r
\r
- Technique FixedFunc {\r
+ Technique {\r
}\r
-\r
}
\ No newline at end of file
}\r
\r
float lightComputeSpecular(in vec3 norm, in vec3 viewdir, in vec3 lightdir, in float shiny){\r
- if (shiny <= 1.0){\r
- return 0.0;\r
- }\r
#ifdef WARDISO\r
// Isotropic Ward\r
vec3 halfVec = normalize(viewdir + lightdir);\r
vec2 computeLighting(in vec3 wvPos, in vec3 wvNorm, in vec3 wvViewDir, in vec3 wvLightDir){\r
float diffuseFactor = lightComputeDiffuse(wvNorm, wvLightDir, wvViewDir);\r
float specularFactor = lightComputeSpecular(wvNorm, wvViewDir, wvLightDir, m_Shininess);\r
- specularFactor *= step(1.0, m_Shininess);\r
+\r
+ if (m_Shininess <= 1.0) {\r
+ specularFactor = 0.0; // should be one instruction on most cards ..\r
+ }\r
\r
float att = vLightDir.w;\r
\r
\r
vec3 calculateNormal(in vec2 texCoord) {\r
vec3 normal = vec3(0,0,1);\r
- vec4 normalHeight = vec4(0,0,0,0);\r
vec3 n = vec3(0,0,0);\r
\r
vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy );\r
#endif\r
\r
#ifdef NORMALMAP\r
- normalHeight = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap, texCoord * m_DiffuseMap_0_scale).xyz;\r
normal += n * alphaBlend.r;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.r;\r
#endif\r
\r
#ifdef NORMALMAP_1\r
- normalHeight = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_1, texCoord * m_DiffuseMap_1_scale).xyz;\r
normal += n * alphaBlend.g;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.g;\r
#endif\r
\r
#ifdef NORMALMAP_2\r
- normalHeight = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_2, texCoord * m_DiffuseMap_2_scale).xyz;\r
normal += n * alphaBlend.b;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.b;\r
#endif\r
\r
#ifdef NORMALMAP_3\r
- normalHeight = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_3, texCoord * m_DiffuseMap_3_scale).xyz;\r
normal += n * alphaBlend.a;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.a;\r
#endif\r
\r
#ifdef ALPHAMAP_1\r
#ifdef NORMALMAP_4\r
- normalHeight = texture2D(m_NormalMap_4, texCoord * m_DiffuseMap_4_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_4, texCoord * m_DiffuseMap_4_scale).xyz;\r
normal += n * alphaBlend1.r;\r
#endif\r
\r
#ifdef NORMALMAP_5\r
- normalHeight = texture2D(m_NormalMap_5, texCoord * m_DiffuseMap_5_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_5, texCoord * m_DiffuseMap_5_scale).xyz;\r
normal += n * alphaBlend1.g;\r
#endif\r
\r
#ifdef NORMALMAP_6\r
- normalHeight = texture2D(m_NormalMap_6, texCoord * m_DiffuseMap_6_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_6, texCoord * m_DiffuseMap_6_scale).xyz;\r
normal += n * alphaBlend1.b;\r
#endif\r
\r
#ifdef NORMALMAP_7\r
- normalHeight = texture2D(m_NormalMap_7, texCoord * m_DiffuseMap_7_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_7, texCoord * m_DiffuseMap_7_scale).xyz;\r
normal += n * alphaBlend1.a;\r
#endif\r
#endif\r
\r
#ifdef ALPHAMAP_2\r
#ifdef NORMALMAP_8\r
- normalHeight = texture2D(m_NormalMap_8, texCoord * m_DiffuseMap_8_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_8, texCoord * m_DiffuseMap_8_scale).xyz;\r
normal += n * alphaBlend2.r;\r
#endif\r
\r
#ifdef NORMALMAP_9\r
- normalHeight = texture2D(m_NormalMap_9, texCoord * m_DiffuseMap_9_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_9, texCoord * m_DiffuseMap_9_scale);\r
normal += n * alphaBlend2.g;\r
#endif\r
\r
#ifdef NORMALMAP_10\r
- normalHeight = texture2D(m_NormalMap_10, texCoord * m_DiffuseMap_10_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_10, texCoord * m_DiffuseMap_10_scale);\r
normal += n * alphaBlend2.b;\r
#endif\r
\r
#ifdef NORMALMAP_11\r
- normalHeight = texture2D(m_NormalMap_11, texCoord * m_DiffuseMap_11_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = texture2D(m_NormalMap_11, texCoord * m_DiffuseMap_11_scale);\r
normal += n * alphaBlend2.a;\r
#endif\r
#endif\r
\r
+ normal = (normal.xyz * vec3(2.0) - vec3(1.0));\r
return normalize(normal);\r
}\r
\r
vec4 coords = wVert;\r
vec4 alphaBlend = texture2D( m_AlphaMap, texCoord.xy );\r
\r
- #ifdef ALPHAMAP_1\r
- vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy );\r
- #endif\r
- #ifdef ALPHAMAP_2\r
- vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy );\r
- #endif\r
+ #ifdef ALPHAMAP_1\r
+ vec4 alphaBlend1 = texture2D( m_AlphaMap_1, texCoord.xy );\r
+ #endif\r
+ #ifdef ALPHAMAP_2\r
+ vec4 alphaBlend2 = texture2D( m_AlphaMap_2, texCoord.xy );\r
+ #endif\r
\r
vec3 normal = vec3(0,0,1);\r
vec3 n = vec3(0,0,0);\r
- vec4 normalHeight = vec4(0,0,0,0);\r
\r
#ifdef NORMALMAP\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap, m_DiffuseMap_0_scale).xyz;\r
normal += n * alphaBlend.r;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.r;\r
#endif\r
\r
#ifdef NORMALMAP_1\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_1, m_DiffuseMap_1_scale).xyz;\r
normal += n * alphaBlend.g;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.g;\r
#endif\r
\r
#ifdef NORMALMAP_2\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_2, m_DiffuseMap_2_scale).xyz;\r
normal += n * alphaBlend.b;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.b;\r
#endif\r
\r
#ifdef NORMALMAP_3\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_3, m_DiffuseMap_3_scale).xyz;\r
normal += n * alphaBlend.a;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.a;\r
#endif\r
\r
#ifdef ALPHAMAP_1\r
#ifdef NORMALMAP_4\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_4, m_DiffuseMap_4_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_4, m_DiffuseMap_4_scale).xyz;\r
normal += n * alphaBlend1.r;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.r;\r
#endif\r
\r
#ifdef NORMALMAP_5\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_5, m_DiffuseMap_5_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_5, m_DiffuseMap_5_scale).xyz;\r
normal += n * alphaBlend1.g;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.g;\r
#endif\r
\r
#ifdef NORMALMAP_6\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_6, m_DiffuseMap_6_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_6, m_DiffuseMap_6_scale).xyz;\r
normal += n * alphaBlend1.b;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.b;\r
#endif\r
\r
#ifdef NORMALMAP_7\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_7, m_DiffuseMap_7_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_7, m_DiffuseMap_7_scale).xyz;\r
normal += n * alphaBlend1.a;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.a;\r
#endif\r
#endif\r
\r
#ifdef ALPHAMAP_2\r
#ifdef NORMALMAP_8\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_8, m_DiffuseMap_8_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_8, m_DiffuseMap_8_scale).xyz;\r
normal += n * alphaBlend2.r;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.r;\r
#endif\r
\r
#ifdef NORMALMAP_9\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_9, m_DiffuseMap_9_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_9, m_DiffuseMap_9_scale).xyz;\r
normal += n * alphaBlend2.g;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.g;\r
#endif\r
\r
#ifdef NORMALMAP_10\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_10, m_DiffuseMap_10_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_10, m_DiffuseMap_10_scale).xyz;\r
normal += n * alphaBlend2.b;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.b;\r
#endif\r
\r
#ifdef NORMALMAP_11\r
- normalHeight = getTriPlanarBlend(coords, blending, m_NormalMap_11, m_DiffuseMap_11_scale);\r
- n = (normalHeight.xyz * vec3(2.0) - vec3(1.0));\r
- n.z = sqrt(1.0 - (n.x * n.x) - (n.y * n.y));\r
- n.y = -n.y;\r
+ n = getTriPlanarBlend(coords, blending, m_NormalMap_11, m_DiffuseMap_11_scale).xyz;\r
normal += n * alphaBlend2.a;\r
+ #else\r
+ normal += vec3(0.5,0.5,1) * alphaBlend.a;\r
#endif\r
#endif\r
\r
+ normal = (normal.xyz * vec3(2.0) - vec3(1.0));\r
return normalize(normal);\r
}\r
#endif\r
Color Specular\r
\r
// Specular power/shininess\r
- Float Shininess : 1\r
+ Float Shininess : 0\r
\r
// Texture map #0\r
Texture2D DiffuseMap\r
}\r
}\r
\r
- Technique FixedFunc {\r
+ Technique {\r
LightMode FixedPipeline\r
}\r
\r
Technique Glow {\r
\r
- VertexShader GLSL100: Common/MatDefs/Misc/SimpleTextured.vert\r
+ VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert\r
FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag\r
\r
WorldParameters {\r
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
*/
package com.jme3.terrain;
-import com.jme3.export.InputCapsule;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.export.OutputCapsule;
-import com.jme3.export.Savable;
+import com.jme3.export.*;
import com.jme3.math.Vector2f;
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.io.IOException;
import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
*/
public class GeoMap implements Savable {
- protected FloatBuffer hdata;
- protected ByteBuffer ndata;
+ protected float[] hdata;
protected int width, height, maxval;
public GeoMap() {}
- public GeoMap(FloatBuffer heightData, ByteBuffer normalData, int width, int height, int maxval){
- this.hdata = heightData;
- this.ndata = normalData;
+ @Deprecated
+ public GeoMap(FloatBuffer heightData, int width, int height, int maxval){
+ hdata = new float[heightData.limit()];
+ heightData.get(hdata);
this.width = width;
this.height = height;
this.maxval = maxval;
}
-
- public GeoMap(int width, int height, int maxval) {
- this(BufferUtils.createFloatBuffer(width*height), null, width,height,maxval);
+
+ public GeoMap(float[] heightData, int width, int height, int maxval){
+ this.hdata = heightData;
+ this.width = width;
+ this.height = height;
+ this.maxval = maxval;
}
+ @Deprecated
public FloatBuffer getHeightData(){
if (!isLoaded())
return null;
-
- return hdata;
+ return BufferUtils.createFloatBuffer(hdata);
}
-
- public ByteBuffer getNormalData(){
- if (!isLoaded() || !hasNormalmap())
+
+ public float[] getHeightArray(){
+ if (!isLoaded())
return null;
-
- return ndata;
+ return hdata;
}
/**
* @throws NullPointerException If isLoaded() is false
*/
public float getValue(int x, int y) {
- return hdata.get(y*width+x);
+ return hdata[y*width+x];
}
/**
* @throws NullPointerException If isLoaded() is false
*/
public float getValue(int i) {
- return hdata.get(i);
+ return hdata[i];
}
- /**
- * Returns the normal at a point
- *
- * If store is null, then a new vector is returned,
- * otherwise, the result is stored in the provided vector
- * and then returned from this method
- *
- * @param x the X coordinate
- * @param y the Y coordinate
- * @param store A preallocated vector for storing the normal data, optional
- * @returns store, or a new vector with the normal data if store is null
- *
- * @throws NullPointerException If isLoaded() or hasNormalmap() is false
- */
- public Vector3f getNormal(int x, int y, Vector3f store) {
- return getNormal(y*width+x,store);
- }
-
- /**
- * Returns the normal at an index
- *
- * If store is null, then a new vector is returned,
- * otherwise, the result is stored in the provided vector
- * and then returned from this method
- *
- * See getHeight(int) for information about index lookup
- *
- * @param i the index
- * @param store A preallocated vector for storing the normal data, optional
- * @returns store, or a new vector with the normal data if store is null
- *
- * @throws NullPointerException If isLoaded() or hasNormalmap() is false
- */
- public Vector3f getNormal(int i, Vector3f store) {
- ndata.position( i*3 );
- if (store==null) store = new Vector3f();
- store.setX( (((float)(ndata.get() & 0xFF)/255f)-0.5f)*2f );
- store.setY( (((float)(ndata.get() & 0xFF)/255f)-0.5f)*2f );
- store.setZ( (((float)(ndata.get() & 0xFF)/255f)-0.5f)*2f );
- return store;
- }
/**
* Returns the width of this Geomap
}
/**
- * Copies a section of this geomap as a new geomap
- */
- public GeoMap copySubGeomap(int x, int y, int w, int h){
- FloatBuffer nhdata = BufferUtils.createFloatBuffer(w * h);
- hdata.position(y*width+x);
- for (int cy = 0; cy < height; cy++){
- hdata.limit(hdata.position()+w);
- nhdata.put(hdata);
- hdata.limit(hdata.capacity());
- hdata.position(hdata.position()+width);
- }
- nhdata.flip();
-
- ByteBuffer nndata = null;
- if (ndata!=null){
- nndata = BufferUtils.createByteBuffer(w*h*3);
- ndata.position( (y*width+x)*3 );
- for (int cy = 0; cy < height; cy++){
- ndata.limit(ndata.position()+w*3);
- nndata.put(ndata);
- ndata.limit(ndata.capacity());
- ndata.position(ndata.position()+width*3);
- }
- nndata.flip();
- }
-
- return new GeoMap(nhdata,nndata,w,h,maxval);
- }
-
- /**
- * Returns true if this Geomap has a normalmap associated with it
- */
- public boolean hasNormalmap() {
- return ndata != null;
- }
-
- /**
* Returns true if the Geomap data is loaded in memory
* If false, then the data is unavailable- must be loaded with load()
* before the methods getHeight/getNormal can be used
* @throws NullPointerException If isLoaded() or hasNormalmap() is false
*/
public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) {
- if (!isLoaded())
- throw new NullPointerException();
if (store!=null){
if (store.remaining() < getWidth()*getHeight()*3)
store = BufferUtils.createFloatBuffer(getWidth()*getHeight()*3);
}
store.rewind();
-
- if (!hasNormalmap()){
- Vector3f oppositePoint = new Vector3f();
- Vector3f adjacentPoint = new Vector3f();
- Vector3f rootPoint = new Vector3f();
- Vector3f tempNorm = new Vector3f();
- int normalIndex = 0;
-
- for (int y = 0; y < getHeight(); y++) {
- for (int x = 0; x < getWidth(); x++) {
- rootPoint.set(x, getValue(x,y), y);
- if (y == getHeight() - 1) {
- if (x == getWidth() - 1) { // case #4 : last row, last col
- // left cross up
+
+ Vector3f oppositePoint = new Vector3f();
+ Vector3f adjacentPoint = new Vector3f();
+ Vector3f rootPoint = new Vector3f();
+ Vector3f tempNorm = new Vector3f();
+ int normalIndex = 0;
+
+ for (int y = 0; y < getHeight(); y++) {
+ for (int x = 0; x < getWidth(); x++) {
+ rootPoint.set(x, getValue(x,y), y);
+ if (y == getHeight() - 1) {
+ if (x == getWidth() - 1) { // case #4 : last row, last col
+ // left cross up
// adj = normalIndex - getWidth();
// opp = normalIndex - 1;
- adjacentPoint.set(x, getValue(x,y-1), y-1);
- oppositePoint.set(x-1, getValue(x-1, y), y);
- } else { // case #3 : last row, except for last col
- // right cross up
+ adjacentPoint.set(x, getValue(x,y-1), y-1);
+ oppositePoint.set(x-1, getValue(x-1, y), y);
+ } else { // case #3 : last row, except for last col
+ // right cross up
// adj = normalIndex + 1;
// opp = normalIndex - getWidth();
- adjacentPoint.set(x+1, getValue(x+1,y), y);
- oppositePoint.set(x, getValue(x,y-1), y-1);
- }
- } else {
- if (x == getWidth() - 1) { // case #2 : last column except for last row
- // left cross down
- adjacentPoint.set(x-1, getValue(x-1,y), y);
- oppositePoint.set(x, getValue(x,y+1), y+1);
+ adjacentPoint.set(x+1, getValue(x+1,y), y);
+ oppositePoint.set(x, getValue(x,y-1), y-1);
+ }
+ } else {
+ if (x == getWidth() - 1) { // case #2 : last column except for last row
+ // left cross down
+ adjacentPoint.set(x-1, getValue(x-1,y), y);
+ oppositePoint.set(x, getValue(x,y+1), y+1);
// adj = normalIndex - 1;
// opp = normalIndex + getWidth();
- } else { // case #1 : most cases
- // right cross down
- adjacentPoint.set(x, getValue(x,y+1), y+1);
- oppositePoint.set(x+1, getValue(x+1,y), y);
+ } else { // case #1 : most cases
+ // right cross down
+ adjacentPoint.set(x, getValue(x,y+1), y+1);
+ oppositePoint.set(x+1, getValue(x+1,y), y);
// adj = normalIndex + getWidth();
// opp = normalIndex + 1;
- }
}
+ }
- tempNorm.set(adjacentPoint).subtractLocal(rootPoint)
- .crossLocal(oppositePoint.subtractLocal(rootPoint));
- tempNorm.multLocal(scale).normalizeLocal();
+ tempNorm.set(adjacentPoint).subtractLocal(rootPoint)
+ .crossLocal(oppositePoint.subtractLocal(rootPoint));
+ tempNorm.multLocal(scale).normalizeLocal();
// store.put(tempNorm.x).put(tempNorm.y).put(tempNorm.z);
- BufferUtils.setInBuffer(tempNorm, store,
- normalIndex);
- normalIndex++;
- }
- }
- }else{
- Vector3f temp = new Vector3f();
- for (int z = 0; z < getHeight(); z++){
- for (int x = 0; x < getWidth(); x++){
- getNormal(x,z,temp);
- store.put(temp.x).put(temp.y).put(temp.z);
- }
+ BufferUtils.setInBuffer(tempNorm, store,
+ normalIndex);
+ normalIndex++;
}
}
* @throws NullPointerException If isLoaded() is false
*/
public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean center) {
- if (!isLoaded()) throw new NullPointerException();
if (store!=null){
if (store.remaining() < width*height*3)
}else{
store = BufferUtils.createFloatBuffer(width*height*3);
}
- hdata.rewind();
- assert hdata.limit() == height*width;
+ assert hdata.length == height*width;
Vector3f offset = new Vector3f(-getWidth() * scale.x * 0.5f,
0,
if (!center)
offset.zero();
+ int i = 0;
for (int z = 0; z < height; z++){
for (int x = 0; x < width; x++){
store.put( (float)x*scale.x + offset.x );
- store.put( (float)hdata.get()*scale.y );
+ store.put( (float)hdata[i++]*scale.y );
store.put( (float)z*scale.z + offset.z );
}
}
return m;
}
- /**
- * Populate the height data from the supplied mesh.
- * The mesh's dimensions should be the same as width and height
- * of this geomap
- */
- public void populateHdataFromMesh(Mesh mesh) {
- hdata = BufferUtils.createFloatBuffer(width*height);
- hdata.rewind();
- VertexBuffer pb = mesh.getBuffer(Type.Position);
- FloatBuffer fb = (FloatBuffer) pb.getData();
- for (int r=0; r<height; r++) {
- for (int c=0; c<width; c++) {
- float f = fb.get( (width*r) + c + 1);
- hdata.put( f );
- }
- }
- }
-
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
- oc.write(hdata, "hdata", null);
+ oc.write(hdata, "hdataarray", null);
oc.write(width, "width", 0);
oc.write(height, "height", 0);
oc.write(maxval, "maxval", 0);
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
- hdata = ic.readFloatBuffer("hdata", null);
+ hdata = ic.readFloatArray("hdataarray", null);
+ if (hdata == null) {
+ FloatBuffer buf = ic.readFloatBuffer("hdata", null);
+ if (buf != null) {
+ hdata = new float[buf.limit()];
+ buf.get(hdata);
+ }
+ }
width = ic.readInt("width", 0);
height = ic.readInt("height", 0);
maxval = ic.readInt("maxval", 0);
+++ /dev/null
-package com.jme3.terrain;\r
-\r
-import java.awt.image.BufferedImage;\r
-import java.io.File;\r
-import java.io.IOException;\r
-import java.nio.FloatBuffer;\r
-import java.util.logging.Level;\r
-import java.util.logging.Logger;\r
-\r
-import javax.imageio.ImageIO;\r
-\r
-import org.novyon.noise.ShaderUtils;\r
-\r
-public class MapUtils {\r
-\r
- public static FloatBuffer clip(FloatBuffer src, int origSize, int newSize, int offset) {\r
- FloatBuffer result = FloatBuffer.allocate(newSize * newSize);\r
-\r
- float[] orig = src.array();\r
- for (int i = offset; i < offset + newSize; i++) {\r
- result.put(orig, i * origSize + offset, newSize);\r
- }\r
-\r
- return result;\r
- }\r
-\r
- public static BufferedImage toGrayscale16Image(FloatBuffer buff, int size) {\r
- BufferedImage retval = new BufferedImage(size, size, BufferedImage.TYPE_USHORT_GRAY);\r
- buff.rewind();\r
- for (int y = 0; y < size; y++) {\r
- for (int x = 0; x < size; x++) {\r
- short c = (short) (ShaderUtils.clamp(buff.get(), 0, 1) * 65532);\r
- retval.getRaster().setDataElements(x, y, new short[]{c});\r
- }\r
- }\r
- return retval;\r
- }\r
-\r
- public static BufferedImage toGrayscaleRGBImage(FloatBuffer buff, int size) {\r
- BufferedImage retval = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);\r
- buff.rewind();\r
- for (int y = 0; y < size; y++) {\r
- for (int x = 0; x < size; x++) {\r
- int c = (int) (ShaderUtils.clamp(buff.get(), 0, 1) * 255);\r
- retval.setRGB(x, y, 0xFF000000 | c << 16 | c << 8 | c);\r
- }\r
- }\r
- return retval;\r
- }\r
-\r
- public static void saveImage(BufferedImage im, String file) {\r
- MapUtils.saveImage(im, new File(file));\r
- }\r
-\r
- public static void saveImage(BufferedImage im, File file) {\r
- try {\r
- ImageIO.write(im, "PNG", file);\r
- Logger.getLogger(MapUtils.class.getCanonicalName()).log(Level.INFO, "Saved image as : {0}", file.getAbsolutePath());\r
- } catch (IOException e) {\r
- e.printStackTrace();\r
- }\r
- }\r
-}\r
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
import com.jme3.material.Material;\r
import com.jme3.math.Vector2f;\r
import com.jme3.math.Vector3f;\r
-import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;\r
import java.util.List;\r
\r
/**\r
public int getMaxLod();\r
\r
/**\r
- * Called by an LodControl.\r
- * Calculates the level of detail of the terrain and adjusts its geometry.\r
- * This is where the Terrain's LOD algorithm will change the detail of\r
- * the terrain based on how far away this position is from the particular\r
- * terrain patch.\r
- * @param location: the Camera's location. A list of one camera location is normal \r
- * if you just have one camera in your scene.\r
- */\r
- public void update(List<Vector3f> location, LodCalculator lodCalculator);\r
-\r
- /**\r
* Lock or unlock the meshes of this terrain.\r
* Locked meshes are un-editable but have better performance.\r
* This should call the underlying getMesh().setStatic()/setDynamic() methods.\r
\r
/**\r
* Returns the material that this terrain uses.\r
- * This does not necessarily have to guarantee the material\r
- * return is the only material used in the whole terrain structure.\r
+ * If it uses many materials, just return the one you think is best.\r
+ * For TerrainQuads this is sufficient. For TerrainGrid you want to call\r
+ * getMaterial(Vector3f) instead.\r
*/\r
public Material getMaterial();\r
+ \r
+ /**\r
+ * Returns the material that this terrain uses.\r
+ * Terrain can have different materials in different locations.\r
+ * In general, the TerrainQuad will only have one material. But \r
+ * TerrainGrid will have a different material per tile.\r
+ * \r
+ * It could be possible to pass in null for the location, some Terrain\r
+ * implementations might just have the one material and not care where\r
+ * you are looking. So implementations must handle null being supplied.\r
+ * \r
+ * @param worldLocation the location, in world coordinates, of where \r
+ * we are interested in the underlying texture.\r
+ */\r
+ public Material getMaterial(Vector3f worldLocation);\r
\r
/**\r
* Used for painting to get the number of vertices along the edge of the\r
* 1/512 (or 0.00195) percent of the texture.\r
* This is used for converting between tri-planar texture scales and regular\r
* texture scales.\r
+ * \r
+ * not needed\r
+ */\r
+ //public float getTextureCoordinateScale();\r
+ \r
+ /**\r
+ * \r
+ * \r
*/\r
- public float getTextureCoordinateScale();\r
+ public int getNumMajorSubdivisions();\r
}\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
*/\r
package com.jme3.terrain.geomipmap;\r
\r
-import com.jme3.terrain.GeoMap;\r
import com.jme3.export.JmeExporter;\r
import com.jme3.export.JmeImporter;\r
import com.jme3.math.FastMath;\r
import com.jme3.math.Triangle;\r
-import java.nio.BufferOverflowException;\r
-import java.nio.BufferUnderflowException;\r
-import java.nio.FloatBuffer;\r
-import java.nio.IntBuffer;\r
-\r
import com.jme3.math.Vector2f;\r
import com.jme3.math.Vector3f;\r
import com.jme3.scene.Mesh;\r
import com.jme3.scene.Mesh.Mode;\r
-import com.jme3.scene.VertexBuffer;\r
import com.jme3.scene.VertexBuffer.Type;\r
+import com.jme3.scene.mesh.IndexBuffer;\r
+import com.jme3.terrain.GeoMap;\r
import com.jme3.util.BufferUtils;\r
-import com.jme3.util.TangentBinormalGenerator;\r
+import com.jme3.util.TempVars;\r
import java.io.IOException;\r
+import java.nio.Buffer;\r
+import java.nio.BufferOverflowException;\r
+import java.nio.BufferUnderflowException;\r
+import java.nio.FloatBuffer;\r
+import java.nio.IntBuffer;\r
+import java.nio.ShortBuffer;\r
\r
/**\r
* Produces the mesh for the TerrainPatch.\r
public LODGeomap() {\r
}\r
\r
+ @Deprecated\r
public LODGeomap(int size, FloatBuffer heightMap) {\r
- super(heightMap, null, size, size, 1);\r
+ super(heightMap, size, size, 1);\r
+ }\r
+ \r
+ public LODGeomap(int size, float[] heightMap) {\r
+ super(heightMap, size, size, 1);\r
}\r
\r
public Mesh createMesh(Vector3f scale, Vector2f tcScale, Vector2f tcOffset, float offsetAmount, int totalSize, boolean center) {\r
FloatBuffer pb = writeVertexArray(null, scale, center);\r
FloatBuffer texb = writeTexCoordArray(null, tcOffset, tcScale, offsetAmount, totalSize);\r
FloatBuffer nb = writeNormalArray(null, scale);\r
- IntBuffer ib = writeIndexArrayLodDiff(null, lod, rightLod, topLod, leftLod, bottomLod);\r
+ Buffer ib;\r
+ IndexBuffer idxB = writeIndexArrayLodDiff(lod, rightLod, topLod, leftLod, bottomLod, totalSize);\r
+ if (idxB.getBuffer() instanceof IntBuffer)\r
+ ib = (IntBuffer)idxB.getBuffer();\r
+ else\r
+ ib = (ShortBuffer)idxB.getBuffer();\r
FloatBuffer bb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);\r
FloatBuffer tanb = BufferUtils.createFloatBuffer(getWidth() * getHeight() * 3);\r
- writeTangentArray(tanb, bb, texb, scale);\r
+ writeTangentArray(nb, tanb, bb, texb, scale);\r
Mesh m = new Mesh();\r
m.setMode(Mode.TriangleStrip);\r
m.setBuffer(Type.Position, 3, pb);\r
m.setBuffer(Type.Tangent, 3, tanb);\r
m.setBuffer(Type.Binormal, 3, bb);\r
m.setBuffer(Type.TexCoord, 2, texb);\r
- m.setBuffer(Type.Index, 3, ib);\r
+ if (ib instanceof IntBuffer)\r
+ m.setBuffer(Type.Index, 3, (IntBuffer)ib);\r
+ else if (ib instanceof ShortBuffer)\r
+ m.setBuffer(Type.Index, 3, (ShortBuffer)ib);\r
m.setStatic();\r
m.updateBound();\r
return m;\r
}\r
\r
- protected void removeNormalBuffer() {\r
- ndata = null;\r
- }\r
-\r
public FloatBuffer writeTexCoordArray(FloatBuffer store, Vector2f offset, Vector2f scale, float offsetAmount, int totalSize) {\r
if (store != null) {\r
if (store.remaining() < getWidth() * getHeight() * 2) {\r
* @param bottomLod LOD of the bottom neighbour\r
* @return the LOD-ified index buffer\r
*/\r
- public IntBuffer writeIndexArrayLodDiff(IntBuffer store, int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod) {\r
+ public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod, int totalSize) {\r
\r
- IntBuffer buffer2 = store;\r
+ \r
int numIndexes = calculateNumIndexesLodDiff(lod);\r
- if (store == null) {\r
- buffer2 = BufferUtils.createIntBuffer(numIndexes);\r
- }\r
- VerboseIntBuffer buffer = new VerboseIntBuffer(buffer2);\r
+ \r
+ IndexBuffer ib = IndexBuffer.createIndexBuffer(numIndexes, numIndexes);\r
+ VerboseBuffer buffer = new VerboseBuffer(ib);\r
\r
\r
// generate center squares minus the edges\r
buffer.put(idx);\r
idx = (row + 2 * lod) * getWidth();\r
buffer.put(idx);\r
- if (row < getWidth() - lod - 2 - 1) { //if not the last one\r
+ if (row < getWidth() - 1 - 2 * lod) { //if not the last one\r
idx = (row + 2 * lod) * getWidth() + lod;\r
buffer.put(idx);\r
idx = (row + 2 * lod) * getWidth();\r
return buffer.delegate;\r
}\r
\r
- public IntBuffer writeIndexArrayLodVariable(IntBuffer store, int lod, int rightLod, int topLod, int leftLod, int bottomLod) {\r
+ public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, int leftLod, int bottomLod, int totalSize) {\r
\r
- IntBuffer buffer2 = store;\r
int numIndexes = calculateNumIndexesLodDiff(lod);\r
- if (store == null) {\r
- buffer2 = BufferUtils.createIntBuffer(numIndexes);\r
- }\r
- VerboseIntBuffer buffer = new VerboseIntBuffer(buffer2);\r
+ \r
+ IndexBuffer ib = IndexBuffer.createIndexBuffer(numIndexes, numIndexes);\r
+ VerboseBuffer buffer = new VerboseBuffer(ib);\r
\r
\r
// generate center squares minus the edges\r
return num;\r
}\r
\r
- public FloatBuffer[] writeTangentArray(FloatBuffer tangentStore, FloatBuffer binormalStore, FloatBuffer textureBuffer, Vector3f scale) {\r
+ public FloatBuffer[] writeTangentArray(FloatBuffer normalBuffer, FloatBuffer tangentStore, FloatBuffer binormalStore, FloatBuffer textureBuffer, Vector3f scale) {\r
if (!isLoaded()) {\r
throw new NullPointerException();\r
}\r
}\r
binormalStore.rewind();\r
\r
+ Vector3f normal = new Vector3f();\r
Vector3f tangent = new Vector3f();\r
Vector3f binormal = new Vector3f();\r
- Vector3f v1 = new Vector3f();\r
+ /*Vector3f v1 = new Vector3f();\r
Vector3f v2 = new Vector3f();\r
Vector3f v3 = new Vector3f();\r
Vector2f t1 = new Vector2f();\r
Vector2f t2 = new Vector2f();\r
- Vector2f t3 = new Vector2f();\r
-\r
- scale = Vector3f.UNIT_XYZ;\r
+ Vector2f t3 = new Vector2f();*/\r
\r
for (int r = 0; r < getHeight(); r++) {\r
for (int c = 0; c < getWidth(); c++) {\r
+ \r
+ int idx = (r * getWidth() + c) * 3;\r
+ normal.set(normalBuffer.get(idx), normalBuffer.get(idx+1), normalBuffer.get(idx+2));\r
+ tangent.set(normal.cross(new Vector3f(0,0,1)));\r
+ binormal.set(new Vector3f(1,0,0).cross(normal));\r
+ \r
+ BufferUtils.setInBuffer(tangent.normalizeLocal(), tangentStore, (r * getWidth() + c)); // save the tangent\r
+ BufferUtils.setInBuffer(binormal.normalizeLocal(), binormalStore, (r * getWidth() + c)); // save the binormal\r
+ }\r
+ }\r
+\r
+/* for (int r = 0; r < getHeight(); r++) {\r
+ for (int c = 0; c < getWidth(); c++) {\r
\r
int texIdx = ((getHeight() - 1 - r) * getWidth() + c) * 2; // pull from the end\r
- int texIdxPrev = ((getHeight() - 1 - (r - 1)) * getWidth() + c) * 2; // pull from the end\r
+ int texIdxAbove = ((getHeight() - 1 - (r - 1)) * getWidth() + c) * 2; // pull from the end\r
int texIdxNext = ((getHeight() - 1 - (r + 1)) * getWidth() + c) * 2; // pull from the end\r
\r
v1.set(c, getValue(c, r), r);\r
t1.set(textureBuffer.get(texIdx), textureBuffer.get(texIdx + 1));\r
\r
- if (r == 0) { // first row\r
- v3.set(c, getValue(c, r), r); // ???\r
- t3.set(textureBuffer.get(texIdxNext), textureBuffer.get(texIdxNext + 1)); // ???\r
+ // below\r
+ if (r == getHeight()-1) { // last row\r
+ v3.set(c, getValue(c, r), r + 1);\r
+ float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdxAbove);\r
+ u += textureBuffer.get(texIdx);\r
+ float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdxAbove + 1);\r
+ v += textureBuffer.get(texIdx + 1);\r
+ t3.set(u, v);\r
} else {\r
- v3.set(c, getValue(c, r - 1), r - 1);\r
- t3.set(textureBuffer.get(texIdxPrev), textureBuffer.get(texIdxPrev + 1));\r
+ v3.set(c, getValue(c, r + 1), r + 1);\r
+ t3.set(textureBuffer.get(texIdxNext), textureBuffer.get(texIdxNext + 1));\r
}\r
-\r
- if (c == getWidth() - 1) { // last column\r
- v2.set(c + 1, getValue(c, r), r); // use same height\r
- t2.set(textureBuffer.get(texIdx), textureBuffer.get(texIdx + 1));\r
+ \r
+ //right\r
+ if (c == getWidth()-1) { // last column\r
+ v2.set(c + 1, getValue(c, r), r);\r
+ float u = textureBuffer.get(texIdx) - textureBuffer.get(texIdx - 2);\r
+ u += textureBuffer.get(texIdx);\r
+ float v = textureBuffer.get(texIdx + 1) - textureBuffer.get(texIdx - 1);\r
+ v += textureBuffer.get(texIdx - 1);\r
+ t2.set(u, v);\r
} else {\r
v2.set(c + 1, getValue(c + 1, r), r); // one to the right\r
t2.set(textureBuffer.get(texIdx + 2), textureBuffer.get(texIdx + 3));\r
BufferUtils.setInBuffer(binormal, binormalStore, (r * getWidth() + c)); // save the binormal\r
}\r
}\r
-\r
+ */\r
return new FloatBuffer[]{tangentStore, binormalStore};\r
}\r
\r
/**\r
* \r
- * @param v Takes 3 vertexes: root, right, top\r
- * @param t Takes 3 tex coords: root, right, top\r
+ * @param v Takes 3 vertices: root, right, bottom\r
+ * @param t Takes 3 tex coords: root, right, bottom\r
* @param tangent that will store the result\r
* @return the tangent store\r
*/\r
}\r
store.rewind();\r
\r
- Vector3f rootPoint = new Vector3f();\r
- Vector3f rightPoint = new Vector3f();\r
- Vector3f leftPoint = new Vector3f();\r
- Vector3f topPoint = new Vector3f();\r
- Vector3f bottomPoint = new Vector3f();\r
+ TempVars vars = TempVars.get();\r
+ \r
+ Vector3f rootPoint = vars.vect1;\r
+ Vector3f rightPoint = vars.vect2;\r
+ Vector3f leftPoint = vars.vect3;\r
+ Vector3f topPoint = vars.vect4;\r
+ Vector3f bottomPoint = vars.vect5;\r
+ \r
+ Vector3f tmp1 = vars.vect6;\r
\r
// calculate normals for each polygon\r
for (int r = 0; r < getHeight(); r++) {\r
for (int c = 0; c < getWidth(); c++) {\r
\r
- rootPoint.set(c, getValue(c, r), r);\r
- Vector3f normal = new Vector3f();\r
+ rootPoint.set(0, getValue(c, r), 0);\r
+ Vector3f normal = vars.vect8;\r
\r
if (r == 0) { // first row\r
if (c == 0) { // first column\r
- rightPoint.set(c + 1, getValue(c + 1, r), r);\r
- bottomPoint.set(c, getValue(c, r + 1), r + 1);\r
- normal.set(getNormal(bottomPoint, rootPoint, rightPoint, scale));\r
+ rightPoint.set(1, getValue(c + 1, r), 0);\r
+ bottomPoint.set(0, getValue(c, r + 1), 1);\r
+ getNormal(bottomPoint, rootPoint, rightPoint, scale, normal);\r
} else if (c == getWidth() - 1) { // last column\r
- leftPoint.set(c - 1, getValue(c - 1, r), r);\r
- bottomPoint.set(c, getValue(c, r + 1), r + 1);\r
- normal.set(getNormal(leftPoint, rootPoint, bottomPoint, scale));\r
+ leftPoint.set(-1, getValue(c - 1, r), 0);\r
+ bottomPoint.set(0, getValue(c, r + 1), 1);\r
+ getNormal(leftPoint, rootPoint, bottomPoint, scale, normal);\r
} else { // all middle columns\r
- leftPoint.set(c - 1, getValue(c - 1, r), r);\r
- rightPoint.set(c + 1, getValue(c + 1, r), r);\r
- bottomPoint.set(c, getValue(c, r + 1), r + 1);\r
- Vector3f n1 = getNormal(leftPoint, rootPoint, bottomPoint, scale);\r
- Vector3f n2 = getNormal(bottomPoint, rootPoint, rightPoint, scale);\r
- normal.set(n1.add(n2).normalizeLocal());\r
+ leftPoint.set(-1, getValue(c - 1, r), 0);\r
+ rightPoint.set(1, getValue(c + 1, r), 0);\r
+ bottomPoint.set(0, getValue(c, r + 1), 1);\r
+ \r
+ normal.set( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) );\r
+ normal.addLocal( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) );\r
}\r
} else if (r == getHeight() - 1) { // last row\r
if (c == 0) { // first column\r
- topPoint.set(c, getValue(c, r - 1), r - 1);\r
- rightPoint.set(c + 1, getValue(c + 1, r), r);\r
- normal.set(getNormal(rightPoint, rootPoint, topPoint, scale));\r
+ topPoint.set(0, getValue(c, r - 1), -1);\r
+ rightPoint.set(1, getValue(c + 1, r), 0);\r
+ getNormal(rightPoint, rootPoint, topPoint, scale, normal);\r
} else if (c == getWidth() - 1) { // last column\r
- topPoint.set(c, getValue(c, r - 1), r - 1);\r
- leftPoint.set(c - 1, getValue(c - 1, r), r);\r
- normal.set(getNormal(topPoint, rootPoint, leftPoint, scale));\r
+ topPoint.set(0, getValue(c, r - 1), -1);\r
+ leftPoint.set(-1, getValue(c - 1, r), 0);\r
+ getNormal(topPoint, rootPoint, leftPoint, scale, normal);\r
} else { // all middle columns\r
- topPoint.set(c, getValue(c, r - 1), r - 1);\r
- leftPoint.set(c - 1, getValue(c - 1, r), r);\r
- rightPoint.set(c + 1, getValue(c + 1, r), r);\r
- Vector3f n1 = getNormal(topPoint, rootPoint, leftPoint, scale);\r
- Vector3f n2 = getNormal(rightPoint, rootPoint, topPoint, scale);\r
- normal.set(n1.add(n2).normalizeLocal());\r
+ topPoint.set(0, getValue(c, r - 1), -1);\r
+ leftPoint.set(-1, getValue(c - 1, r), 0);\r
+ rightPoint.set(1, getValue(c + 1, r), 0);\r
+ \r
+ normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1) );\r
+ normal.addLocal( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) );\r
}\r
} else { // all middle rows\r
if (c == 0) { // first column\r
- topPoint.set(c, getValue(c, r - 1), r - 1);\r
- rightPoint.set(c + 1, getValue(c + 1, r), r);\r
- bottomPoint.set(c, getValue(c, r + 1), r + 1);\r
- Vector3f n1 = getNormal(rightPoint, rootPoint, topPoint, scale);\r
- Vector3f n2 = getNormal(bottomPoint, rootPoint, rightPoint, scale);\r
- normal.set(n1.add(n2).normalizeLocal());\r
+ topPoint.set(0, getValue(c, r - 1), -1);\r
+ rightPoint.set(1, getValue(c + 1, r), 0);\r
+ bottomPoint.set(0, getValue(c, r + 1), 1);\r
+ \r
+ normal.set( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) );\r
+ normal.addLocal( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) );\r
} else if (c == getWidth() - 1) { // last column\r
- topPoint.set(c, getValue(c, r - 1), r - 1);\r
- leftPoint.set(c - 1, getValue(c - 1, r), r);\r
- bottomPoint.set(c, getValue(c, r + 1), r + 1);\r
- Vector3f n1 = getNormal(topPoint, rootPoint, leftPoint, scale);\r
- Vector3f n2 = getNormal(leftPoint, rootPoint, bottomPoint, scale);\r
- normal.set(n1.add(n2).normalizeLocal());\r
+ topPoint.set(0, getValue(c, r - 1), -1);\r
+ leftPoint.set(-1, getValue(c - 1, r), 0);\r
+ bottomPoint.set(0, getValue(c, r + 1), 1);\r
+\r
+ normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1) );\r
+ normal.addLocal( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) );\r
} else { // all middle columns\r
- topPoint.set(c, getValue(c, r - 1), r - 1);\r
- leftPoint.set(c - 1, getValue(c - 1, r), r);\r
- rightPoint.set(c + 1, getValue(c + 1, r), r);\r
- bottomPoint.set(c, getValue(c, r + 1), r + 1);\r
- Vector3f n1 = getNormal(topPoint, rootPoint, leftPoint, scale);\r
- Vector3f n2 = getNormal(leftPoint, rootPoint, bottomPoint, scale);\r
- Vector3f n3 = getNormal(bottomPoint, rootPoint, rightPoint, scale);\r
- Vector3f n4 = getNormal(rightPoint, rootPoint, topPoint, scale);\r
- normal.set(n1.add(n2).add(n3).add(n4).normalizeLocal());\r
+ topPoint.set(0, getValue(c, r - 1), -1);\r
+ leftPoint.set(-1, getValue(c - 1, r), 0);\r
+ rightPoint.set(1, getValue(c + 1, r), 0);\r
+ bottomPoint.set(0, getValue(c, r + 1), 1);\r
+ \r
+ normal.set( getNormal(topPoint, rootPoint, leftPoint, scale, tmp1 ) );\r
+ normal.addLocal( getNormal(leftPoint, rootPoint, bottomPoint, scale, tmp1) );\r
+ normal.addLocal( getNormal(bottomPoint, rootPoint, rightPoint, scale, tmp1) );\r
+ normal.addLocal( getNormal(rightPoint, rootPoint, topPoint, scale, tmp1) );\r
}\r
}\r
-\r
+ normal.normalizeLocal();\r
BufferUtils.setInBuffer(normal, store, (r * getWidth() + c)); // save the normal\r
-\r
}\r
}\r
-\r
+ vars.release();\r
+ \r
return store;\r
}\r
\r
- private Vector3f getNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint, Vector3f scale) {\r
- Vector3f normal = new Vector3f();\r
- //scale = Vector3f.UNIT_XYZ;\r
- normal.set(firstPoint.mult(scale)).subtractLocal(rootPoint.mult(scale)).crossLocal(secondPoint.mult(scale).subtract(rootPoint.mult(scale))).normalizeLocal();\r
- return normal;\r
+ private Vector3f getNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint, Vector3f scale, Vector3f store) {\r
+ float x1 = firstPoint.x - rootPoint.x;\r
+ float y1 = firstPoint.y - rootPoint.y;\r
+ float z1 = firstPoint.z - rootPoint.z;\r
+ x1 *= scale.x;\r
+ y1 *= scale.y;\r
+ z1 *= scale.z;\r
+ float x2 = secondPoint.x - rootPoint.x;\r
+ float y2 = secondPoint.y - rootPoint.y;\r
+ float z2 = secondPoint.z - rootPoint.z;\r
+ x2 *= scale.x;\r
+ y2 *= scale.y;\r
+ z2 *= scale.z;\r
+ float x3 = (y1 * z2) - (z1 * y2);\r
+ float y3 = (z1 * x2) - (x1 * z2);\r
+ float z3 = (x1 * y2) - (y1 * x2);\r
+ \r
+ float inv = 1.0f / FastMath.sqrt(x3 * x3 + y3 * y3 + z3 * z3);\r
+ store.x = x3 * inv;\r
+ store.y = y3 * inv;\r
+ store.z = z3 * inv;\r
+ \r
+ \r
+ /*firstPoint.multLocal(scale);\r
+ rootPoint.multLocal(scale);\r
+ secondPoint.multLocal(scale);\r
+ firstPoint.subtractLocal(rootPoint);\r
+ secondPoint.subtractLocal(rootPoint);\r
+ firstPoint.cross(secondPoint, store);*/\r
+ return store;\r
}\r
\r
/**\r
* Keeps a count of the number of indexes, good for debugging\r
*/\r
- public class VerboseIntBuffer {\r
+ public class VerboseBuffer {\r
\r
- private IntBuffer delegate;\r
+ private IndexBuffer delegate;\r
+ //private IntBuffer delegateInt;\r
+ //private ShortBuffer delegateShort;\r
int count = 0;\r
+ //private boolean intb = true;\r
\r
- public VerboseIntBuffer(IntBuffer d) {\r
- delegate = d;\r
+ public VerboseBuffer(IndexBuffer d) {\r
+ this.delegate = d;\r
}\r
+ \r
+ /*public VerboseBuffer(Buffer d) {\r
+ if (d instanceof IntBuffer)\r
+ delegateInt = (IntBuffer)d;\r
+ else if (d instanceof ShortBuffer) {\r
+ delegateShort = (ShortBuffer)d;\r
+ intb = false;\r
+ }\r
+ }*/\r
\r
public void put(int value) {\r
+ delegate.put(count, value);\r
+ count++;\r
+ }\r
+ /*public void put(int value) {\r
try {\r
- delegate.put(value);\r
count++;\r
+ int limit = intb? delegateInt.limit() : delegateShort.limit();\r
+ if (count > limit)\r
+ throw new BufferOverflowException();\r
+ if (intb)\r
+ delegateInt.put(value);\r
+ else {\r
+ System.out.println(Integer.toString(value)+" "+Short.toString((short)value));\r
+ delegateShort.put((short)value);\r
+ }\r
} catch (BufferOverflowException e) {\r
- //System.out.println("err buffer size: "+delegate.capacity());\r
+ Logger.getLogger(this.getClass().getName()).log(Logger.Level.ERROR, "err buffer size: "+delegateInt.capacity());\r
}\r
- }\r
+ }*/\r
\r
public int getCount() {\r
return count;\r
}\r
\r
/**\r
+ * Get the two triangles that make up the grid section at the specified point.\r
+ *\r
+ * For every grid space there are two triangles oriented like this:\r
+ * *----*\r
+ * |a / |\r
+ * | / b|\r
+ * *----*\r
+ * The corners of the mesh have differently oriented triangles. The two\r
+ * corners that we have to special-case are the top left and bottom right\r
+ * corners. They are oriented inversely:\r
+ * *----*\r
+ * | \ b|\r
+ * |a \ |\r
+ * *----*\r
+ */\r
+ protected float getHeight(int x, int z, float xm, float zm) {\r
+ \r
+ int index = findClosestHeightIndex(x, z);\r
+ if (index < 0) {\r
+ return Float.NaN;\r
+ }\r
+ \r
+ float h1 = hdata[index]; // top left\r
+ float h2 = hdata[index + 1]; // top right\r
+ float h3 = hdata[index + width]; // bottom left\r
+ float h4 = hdata[index + width + 1]; // bottom right\r
+\r
+ //float dix = (x % 1f) ;\r
+ //float diz = (z % 1f) ;\r
+ \r
+ if ((x == 0 && z == 0) || (x == width - 2 && z == width - 2)) {\r
+ // top left or bottom right grid point\r
+ /* 1----2\r
+ * | \ b|\r
+ * |a \ |\r
+ * 3----4 */\r
+ if (xm<zm)\r
+ return h1 + xm*(h4-h3) + zm*(h3-h1);\r
+ else\r
+ return h1 + xm*(h2-h1) + zm*(h4-h2);\r
+ \r
+ } else {\r
+ // all other grid points\r
+ /* 1----2\r
+ * |a / |\r
+ * | / b|\r
+ * 3----4 */\r
+ if (xm<(1-zm))\r
+ return h3 + (xm)*(h2-h1) + (1f-zm)*(h1-h3);\r
+ else\r
+ return h3 + (xm)*(h4-h3) + (1f-zm)*(h2-h4);\r
+ }\r
+ }\r
+ \r
+ /**\r
* Get a representation of the underlying triangle at the given point,\r
* translated to world coordinates.\r
* \r
*\r
* @param x local x coordinate\r
* @param z local z coordinate\r
- * @param scale\r
- * @param translation\r
* @return\r
*/\r
protected Triangle[] getGridTrianglesAtPoint(float x, float z) {\r
Triangle t = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());\r
Triangle t2 = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());\r
\r
- float h1 = hdata.get(index); // top left\r
- float h2 = hdata.get(index + 1); // top right\r
- float h3 = hdata.get(index + width); // bottom left\r
- float h4 = hdata.get(index + width + 1); // bottom right\r
+ float h1 = hdata[index]; // top left\r
+ float h2 = hdata[index + 1]; // top right\r
+ float h3 = hdata[index + width]; // bottom left\r
+ float h4 = hdata[index + width + 1]; // bottom right\r
\r
\r
- if ((gridX == 0 && gridY == 0) || (gridX == width - 1 && gridY == width - 1)) {\r
+ if ((gridX == 0 && gridY == 0) || (gridX == width - 2 && gridY == width - 2)) {\r
// top left or bottom right grid point\r
t.get(0).x = (gridX);\r
t.get(0).y = (h1);\r
protected Triangle getTriangleAtPoint(float x, float z) {\r
Triangle[] triangles = getGridTrianglesAtPoint(x, z);\r
if (triangles == null) {\r
- System.out.println("x,z: " + x + "," + z);\r
+ //System.out.println("x,z: " + x + "," + z);\r
return null;\r
}\r
Vector2f point = new Vector2f(x, z);\r
--- /dev/null
+/*
+ * 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.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * An extension of the TerrainLodControl that handles
+ * multiple terrains at once. This is to be used if you
+ * have your own tiling/paging terrain system, such as
+ * TerrainGrid.
+ *
+ * @author Brent Owens
+ */
+public class MultiTerrainLodControl extends TerrainLodControl {
+
+ List<TerrainQuad> terrains = new ArrayList<TerrainQuad>();
+ private List<TerrainQuad> addedTerrains = new ArrayList<TerrainQuad>();
+ private List<TerrainQuad> removedTerrains = new ArrayList<TerrainQuad>();
+
+ public MultiTerrainLodControl(List<Camera> cameras) {
+ this.cameras = cameras;
+ lodCalculator = new DistanceLodCalculator(65, 2.7f);
+ }
+
+ public MultiTerrainLodControl(Camera camera) {
+ List<Camera> cams = new ArrayList<Camera>();
+ cams.add(camera);
+ this.cameras = cams;
+ lodCalculator = new DistanceLodCalculator(65, 2.7f);
+ }
+
+ /**
+ * Add a terrain that will have its LOD handled by this control.
+ * It will be added next update run. You should only call this from
+ * the render thread.
+ */
+ public void addTerrain(TerrainQuad tq) {
+ addedTerrains.add(tq);
+ }
+
+ /**
+ * Add a terrain that will no longer have its LOD handled by this control.
+ * It will be removed next update run. You should only call this from
+ * the render thread.
+ */
+ public void removeTerrain(TerrainQuad tq) {
+ removedTerrains.add(tq);
+ }
+
+ @Override
+ protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {
+ return new UpdateMultiLOD(locations, lodCalculator);
+ }
+
+ @Override
+ protected void prepareTerrain() {
+ if (!addedTerrains.isEmpty()) {
+ for (TerrainQuad t : addedTerrains) {
+ if (!terrains.contains(t))
+ terrains.add(t);
+ }
+ addedTerrains.clear();
+ }
+
+ if (!removedTerrains.isEmpty()) {
+ terrains.removeAll(removedTerrains);
+ removedTerrains.clear();
+ }
+
+ for (TerrainQuad terrain : terrains)
+ terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely
+ }
+
+ /**
+ * Overrides the parent UpdateLOD runnable to process
+ * multiple terrains.
+ */
+ protected class UpdateMultiLOD extends UpdateLOD {
+
+
+ protected UpdateMultiLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {
+ super(camLocations, lodCalculator);
+ }
+
+ @Override
+ public HashMap<String, UpdatedTerrainPatch> call() throws Exception {
+
+ setLodCalcRunning(true);
+
+ HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();
+
+ for (TerrainQuad terrainQuad : terrains) {
+ // go through each patch and calculate its LOD based on camera distance
+ terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here
+ }
+
+ for (TerrainQuad terrainQuad : terrains) {
+ // then calculate the neighbour LOD values for seaming
+ terrainQuad.findNeighboursLod(updated);
+ }
+
+ for (TerrainQuad terrainQuad : terrains) {
+ // check neighbour quads that need their edges seamed
+ terrainQuad.fixEdges(updated);
+ }
+
+ for (TerrainQuad terrainQuad : terrains) {
+ // perform the edge seaming, if it requires it
+ terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod());
+ }
+
+ //setUpdateQuadLODs(updated); // set back to main ogl thread
+ setLodCalcRunning(false);
+
+ return updated;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+
+/**
+ * Used for TerrainQuad to find neighbours that are not part of the
+ * same quad tree. Normally TerrainQuads function in a quad tree and
+ * use the neighbour methods getRightQuad, getLeftQuad etc. to update
+ * LOD values of the terrain (and for some other routines).
+ *
+ * With this you can have a parent, control or spatial, that manages a group of
+ * TerrainQuads by linking them together through these four methods.
+ *
+ * The general orientation of TerrainQuads and their sub-quads is as such:
+ *
+ *
+ * +-- x+ ---->
+ * |
+ * | 1 | 3 (quadrants)
+ * z+ --+--
+ * | 2 | 4
+ * |
+ * \/
+ *
+ * Your implementation will still have to manage getHeight, getNormal, and
+ * most other Terrain.java interface methods; often by offsetting the XZ
+ * coordinate parameters.
+ *
+ * @author sploreg
+ */
+public interface NeighbourFinder {
+
+ /**
+ * Get the TerrainQuad to the right of the supplied 'center' quad.
+ */
+ public TerrainQuad getRightQuad(TerrainQuad center);
+
+ /**
+ * Get the TerrainQuad to the left of the supplied 'center' quad.
+ */
+ public TerrainQuad getLeftQuad(TerrainQuad center);
+
+ /**
+ * Get the TerrainQuad above the supplied 'center' quad.
+ */
+ public TerrainQuad getTopQuad(TerrainQuad center);
+
+ /**
+ * Get the TerrainQuad below the supplied 'center' quad.
+ */
+ public TerrainQuad getDownQuad(TerrainQuad center);
+}
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
*/
package com.jme3.terrain.geomipmap;
-import com.jme3.scene.control.UpdateControl;
-import com.jme3.bullet.PhysicsSpace;
-import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
-import com.jme3.bullet.control.RigidBodyControl;
-import com.jme3.terrain.heightmap.HeightMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
+import com.jme3.bounding.BoundingBox;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
-import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
-import com.jme3.terrain.geomipmap.lodcalc.LodCalculatorFactory;
-import com.jme3.terrain.geomipmap.lodcalc.LodDistanceCalculatorFactory;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.UpdateControl;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.heightmap.HeightMap;
import com.jme3.terrain.heightmap.HeightMapGrid;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
/**
+ * <p>
+ * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.</p>
+ * </p><p>
+ * The grid is indexed by cells. Each cell has an integer XZ coordinate originating at 0,0.
+ * TerrainGrid will piggyback on the TerrainLodControl so it can use the camera for its
+ * updates as well. It does this in the overwritten update() method.
+ * </p><p>
+ * It uses an LRU (Least Recently Used) cache of 16 terrain tiles (full TerrainQuadTrees). The
+ * center 4 are the ones that are visible. As the camera moves, it checks what camera cell it is in
+ * and will attach the now visible tiles.
+ * </p><p>
+ * The 'quadIndex' variable is a 4x4 array that represents the tiles. The center
+ * four (index numbers: 5, 6, 9, 10) are what is visible. Each quadIndex value is an
+ * offset vector. The vector contains whole numbers and represents how many tiles in offset
+ * this location is from the center of the map. So for example the index 11 [Vector3f(2, 0, 1)]
+ * is located 2*terrainSize in X axis and 1*terrainSize in Z axis.
+ * </p><p>
+ * As the camera moves, it tests what cameraCell it is in. Each camera cell covers four quad tiles
+ * and is half way inside each one.
+ * </p><pre>
+ * +-------+-------+
+ * | 1 | 3 | Four terrainQuads that make up the grid
+ * | *..|..* | with the cameraCell in the middle, covering
+ * |----|--|--|----| all four quads.
+ * | *..|..* |
+ * | 2 | 4 |
+ * +-------+-------+
+ * </pre><p>
+ * This results in the effect of when the camera gets half way across one of the sides of a quad to
+ * an empty (non-loaded) area, it will trigger the system to load in the next tiles.
+ * </p><p>
+ * The tile loading is done on a background thread, and once the tile is loaded, then it is
+ * attached to the qrid quad tree, back on the OGL thread. It will grab the terrain quad from
+ * the LRU cache if it exists. If it does not exist, it will load in the new TerrainQuad tile.
+ * </p><p>
+ * The loading of new tiles triggers events for any TerrainGridListeners. The events are:
+ * <ul>
+ * <li>tile Attached
+ * <li>tile Detached
+ * <li>grid moved.
+ * </ul>
+ * <p>
+ * These allow physics to update, and other operation (often needed for loading the terrain) to occur
+ * at the right time.
+ * </p>
* @author Anthyon
*/
public class TerrainGrid extends TerrainQuad {
-
protected static final Logger log = Logger.getLogger(TerrainGrid.class.getCanonicalName());
- protected Vector3f currentCell;
- protected int quarterSize;
+ protected Vector3f currentCamCell = Vector3f.ZERO;
+ protected int quarterSize; // half of quadSize
protected int quadSize;
protected HeightMapGrid heightMapGrid;
+ private TerrainGridTileLoader gridTileLoader;
protected Vector3f[] quadIndex;
- protected Map<String, TerrainGridListener> listeners = new HashMap<String, TerrainGridListener>();
+ protected Set<TerrainGridListener> listeners = new HashSet<TerrainGridListener>();
protected Material material;
- protected LRUCache<Vector3f, TerrainQuad> cache = new LRUCache<Vector3f, TerrainQuad>(16);
- protected RigidBodyControl[] quadControls;
- protected PhysicsSpace space;
- private int cellsLoaded = 0;
- private int[] gridOffset;
+ //cache needs to be 1 row (4 cells) larger than what we care is cached
+ protected LRUCache<Vector3f, TerrainQuad> cache = new LRUCache<Vector3f, TerrainQuad>(20);
+ protected int cellsLoaded = 0;
+ protected int[] gridOffset;
+ protected boolean runOnce = false;
+ protected ExecutorService cacheExecutor;
protected class UpdateQuadCache implements Runnable {
this.location = location;
}
+ /**
+ * This is executed if the camera has moved into a new CameraCell and will load in
+ * the new TerrainQuad tiles to be children of this TerrainGrid parent.
+ * It will first check the LRU cache to see if the terrain tile is already there,
+ * if it is not there, it will load it in and then cache that tile.
+ * The terrain tiles get added to the quad tree back on the OGL thread using the
+ * attachQuadAt() method. It also resets any cached values in TerrainQuad (such as
+ * neighbours).
+ */
public void run() {
-
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int quadIdx = i * 4 + j;
- final Vector3f temp = location.add(quadIndex[quadIdx]);
- TerrainQuad q = cache.get(temp);
+ final Vector3f quadCell = location.add(quadIndex[quadIdx]);
+ TerrainQuad q = cache.get(quadCell);
if (q == null) {
- // create the new Quad since it doesn't exist
- HeightMap heightMapAt = heightMapGrid.getHeightMapAt(temp);
- q = new TerrainQuad(getName() + "Quad" + temp, patchSize, quadSize, totalSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
- q.setMaterial(material.clone());
- log.log(Level.FINE, "Loaded TerrainQuad {0}", q.getName());
+ if (heightMapGrid != null) {
+ // create the new Quad since it doesn't exist
+ HeightMap heightMapAt = heightMapGrid.getHeightMapAt(quadCell);
+ q = new TerrainQuad(getName() + "Quad" + quadCell, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
+ q.setMaterial(material.clone());
+ log.log(Level.FINE, "Loaded TerrainQuad {0} from HeightMapGrid", q.getName());
+ } else if (gridTileLoader != null) {
+ q = gridTileLoader.getTerrainQuadAt(quadCell);
+ // only clone the material to the quad if it doesn't have a material of its own
+ if(q.getMaterial()==null) q.setMaterial(material.clone());
+ log.log(Level.FINE, "Loaded TerrainQuad {0} from TerrainQuadGrid", q.getName());
+ }
}
- cache.put(temp, q);
+ cache.put(quadCell, q);
+
+ final int quadrant = getQuadrant(quadIdx);
+ final TerrainQuad newQuad = q;
+
if (isCenter(quadIdx)) {
// if it should be attached as a child right now, attach it
- final int quadrant = getQuadrant(quadIdx);
- final TerrainQuad newQuad = q;
- // back on the OpenGL thread:
getControl(UpdateControl.class).enqueue(new Callable() {
-
+ // back on the OpenGL thread:
public Object call() throws Exception {
- attachQuadAt(newQuad, quadrant, temp);
+ if (newQuad.getParent() != null) {
+ attachQuadAt(newQuad, quadrant, quadCell, true);
+ }
+ else {
+ attachQuadAt(newQuad, quadrant, quadCell, false);
+ }
+ return null;
+ }
+ });
+ } else {
+ getControl(UpdateControl.class).enqueue(new Callable() {
+ public Object call() throws Exception {
+ removeQuad(newQuad);
return null;
}
});
}
}
+ getControl(UpdateControl.class).enqueue(new Callable() {
+ // back on the OpenGL thread:
+ public Object call() throws Exception {
+ for (Spatial s : getChildren()) {
+ if (s instanceof TerrainQuad) {
+ TerrainQuad tq = (TerrainQuad)s;
+ tq.resetCachedNeighbours();
+ }
+ }
+ System.out.println("fixed normals "+location.clone().mult(size));
+ setNeedToRecalculateNormals();
+ return null;
+ }
+ });
}
}
}
protected int getQuadrant(int quadIndex) {
- if (quadIndex == 9) {
+ if (quadIndex == 5) {
return 1;
- } else if (quadIndex == 5) {
+ } else if (quadIndex == 9) {
return 2;
- } else if (quadIndex == 10) {
- return 3;
} else if (quadIndex == 6) {
+ return 3;
+ } else if (quadIndex == 10) {
return 4;
}
return 0; // error
}
- public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, HeightMapGrid heightMapGrid,
+ public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid,
Vector2f offset, float offsetAmount) {
this.name = name;
this.patchSize = patchSize;
this.size = maxVisibleSize;
- this.quarterSize = maxVisibleSize >> 2;
- this.quadSize = (maxVisibleSize + 1) >> 1;
this.stepScale = scale;
- this.heightMapGrid = heightMapGrid;
- heightMapGrid.setSize(this.quadSize);
- this.totalSize = maxVisibleSize;
this.offset = offset;
this.offsetAmount = offsetAmount;
- //this.lodCalculatorFactory = lodCalculatorFactory;
- this.gridOffset = new int[]{0,0};
- //if (lodCalculatorFactory == null) {
- // lodCalculatorFactory = new LodDistanceCalculatorFactory();
- //}
- this.quadIndex = new Vector3f[]{
- new Vector3f(-1, 0, 2), new Vector3f(0, 0, 2), new Vector3f(1, 0, 2), new Vector3f(2, 0, 2),
- new Vector3f(-1, 0, 1), new Vector3f(0, 0, 1), new Vector3f(1, 0, 1), new Vector3f(2, 0, 1),
- new Vector3f(-1, 0, 0), new Vector3f(0, 0, 0), new Vector3f(1, 0, 0), new Vector3f(2, 0, 0),
- new Vector3f(-1, 0, -1), new Vector3f(0, 0, -1), new Vector3f(1, 0, -1), new Vector3f(-2, 0, -1)};
-
+ initData();
+ this.gridTileLoader = terrainQuadGrid;
+ terrainQuadGrid.setPatchSize(this.patchSize);
+ terrainQuadGrid.setQuadSize(this.quadSize);
addControl(new UpdateControl());
+
+ fixNormalEdges(new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2));
+ addControl(new NormalRecalcControl(this));
}
- public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, HeightMapGrid heightMapGrid) {
- this(name, patchSize, maxVisibleSize, scale, heightMapGrid, new Vector2f(), 0);
+ public TerrainGrid(String name, int patchSize, int maxVisibleSize, Vector3f scale, TerrainGridTileLoader terrainQuadGrid) {
+ this(name, patchSize, maxVisibleSize, scale, terrainQuadGrid, new Vector2f(), 0);
}
- public TerrainGrid(String name, int patchSize, int maxVisibleSize, HeightMapGrid heightMapGrid) {
- this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, heightMapGrid);
+ public TerrainGrid(String name, int patchSize, int maxVisibleSize, TerrainGridTileLoader terrainQuadGrid) {
+ this(name, patchSize, maxVisibleSize, Vector3f.UNIT_XYZ, terrainQuadGrid);
}
public TerrainGrid() {
}
- public void initialize(Vector3f location) {
- if (this.material == null) {
- throw new RuntimeException("Material must be set prior to call of initialize");
- }
- Vector3f camCell = this.getCell(location);
- this.updateChildrens(camCell);
- for (TerrainGridListener l : this.listeners.values()) {
- l.gridMoved(camCell);
- }
+ private void initData() {
+ int maxVisibleSize = size;
+ this.quarterSize = maxVisibleSize >> 2;
+ this.quadSize = (maxVisibleSize + 1) >> 1;
+ this.totalSize = maxVisibleSize;
+ this.gridOffset = new int[]{0, 0};
+
+ /*
+ * -z
+ * |
+ * 1|3
+ * -x ----+---- x
+ * 2|4
+ * |
+ * z
+ */
+ this.quadIndex = new Vector3f[]{
+ new Vector3f(-1, 0, -1), new Vector3f(0, 0, -1), new Vector3f(1, 0, -1), new Vector3f(2, 0, -1),
+ new Vector3f(-1, 0, 0), new Vector3f(0, 0, 0), new Vector3f(1, 0, 0), new Vector3f(2, 0, 0),
+ new Vector3f(-1, 0, 1), new Vector3f(0, 0, 1), new Vector3f(1, 0, 1), new Vector3f(2, 0, 1),
+ new Vector3f(-1, 0, 2), new Vector3f(0, 0, 2), new Vector3f(1, 0, 2), new Vector3f(2, 0, 2)};
+
}
- @Override
- public void update(List<Vector3f> locations, LodCalculator lodCalculator) {
- // for now, only the first camera is handled.
- // to accept more, there are two ways:
- // 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed
- // 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore
- Vector3f cam = locations.get(0);
- Vector3f camCell = this.getCell(cam);
- if(cellsLoaded>1){ // Check if cells are updated before updating gridoffset.
- gridOffset[0] = Math.round(camCell.x*(size/2));
- gridOffset[1] = Math.round(camCell.z*(size/2));
- cellsLoaded=0;
- }
- if (camCell.x != this.currentCell.x || camCell.z != currentCell.z) {
- this.updateChildrens(camCell);
- for (TerrainGridListener l : this.listeners.values()) {
- l.gridMoved(camCell);
- }
- }
- super.update(locations, lodCalculator);
+ /**
+ * Get the location in cell-coordinates of the specified location.
+ * Cell coordinates are integer corrdinates, usually with y=0, each
+ * representing a cell in the world.
+ * For example, moving right in the +X direction:
+ * (0,0,0) (1,0,0) (2,0,0), (3,0,0)
+ * and then down the -Z direction:
+ * (3,0,-1) (3,0,-2) (3,0,-3)
+ */
+ public Vector3f getCamCell(Vector3f location) {
+ Vector3f tile = getTileCell(location);
+ Vector3f offsetHalf = new Vector3f(-0.5f, 0, -0.5f);
+ Vector3f shifted = tile.subtract(offsetHalf);
+ return new Vector3f(FastMath.floor(shifted.x), 0, FastMath.floor(shifted.z));
}
- public Vector3f getCell(Vector3f location) {
- final Vector3f v = location.clone().divideLocal(this.getLocalScale().mult(this.quadSize - 1)).add(0.5f, 0, 0.5f);
- return new Vector3f(FastMath.floor(v.x), 0, FastMath.floor(v.z));
+ /**
+ * Centered at 0,0.
+ * Get the tile index location in integer form:
+ * @param location world coordinate
+ */
+ public Vector3f getTileCell(Vector3f location) {
+ Vector3f tileLoc = location.divide(this.getWorldScale().mult(this.quadSize));
+ return tileLoc;
}
- protected void removeQuad(int idx) {
- if (this.getQuad(idx) != null) {
- if (quadControls != null) {
- this.getQuad(idx).removeControl(RigidBodyControl.class);
- }
- for (TerrainGridListener l : listeners.values()) {
- l.tileDetached(getCell(this.getQuad(idx).getWorldTranslation()), this.getQuad(idx));
+ public TerrainGridTileLoader getGridTileLoader() {
+ return gridTileLoader;
+ }
+
+ /**
+ * Get the terrain tile at the specified world location, in XZ coordinates.
+ */
+ public Terrain getTerrainAt(Vector3f worldLocation) {
+ if (worldLocation == null)
+ return null;
+ Vector3f tileCell = getTileCell(worldLocation.setY(0));
+ tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z));
+ return cache.get(tileCell);
+ }
+
+ /**
+ * Get the terrain tile at the specified XZ cell coordinate (not world coordinate).
+ * @param cellCoordinate integer cell coordinates
+ * @return the terrain tile at that location
+ */
+ public Terrain getTerrainAtCell(Vector3f cellCoordinate) {
+ return cache.get(cellCoordinate);
+ }
+
+ /**
+ * Convert the world location into a cell location (integer coordinates)
+ */
+ public Vector3f toCellSpace(Vector3f worldLocation) {
+ Vector3f tileCell = getTileCell(worldLocation);
+ tileCell = new Vector3f(Math.round(tileCell.x), tileCell.y, Math.round(tileCell.z));
+ return tileCell;
+ }
+
+ /**
+ * Convert the cell coordinate (integer coordinates) into world coordinates.
+ */
+ public Vector3f toWorldSpace(Vector3f cellLocation) {
+ return cellLocation.mult(getLocalScale()).multLocal(quadSize - 1);
+ }
+
+ protected void removeQuad(TerrainQuad q) {
+ if (q != null && ( (q.getQuadrant() > 0 && q.getQuadrant()<5) || q.getParent() != null) ) {
+ for (TerrainGridListener l : listeners) {
+ l.tileDetached(getTileCell(q.getWorldTranslation()), q);
}
- this.detachChild(this.getQuad(idx));
+ q.setQuadrant((short)0);
+ this.detachChild(q);
cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this.
}
}
/**
* Runs on the rendering thread
+ * @param shifted quads are still attached to the parent and don't need to re-load
*/
- protected void attachQuadAt(TerrainQuad q, int quadrant, Vector3f cam) {
- this.removeQuad(quadrant);
- //q.setMaterial(this.material);
- //q.setLocalTranslation(quadOrigins[quadrant - 1]);
+ protected void attachQuadAt(TerrainQuad q, int quadrant, Vector3f quadCell, boolean shifted) {
+
q.setQuadrant((short) quadrant);
- this.attachChild(q);
+ if (!shifted)
+ this.attachChild(q);
- Vector3f loc = cam.mult(this.quadSize - 1).subtract(quarterSize, 0, quarterSize);// quadrant location handled TerrainQuad automatically now
+ Vector3f loc = quadCell.mult(this.quadSize - 1).subtract(quarterSize, 0, quarterSize);// quadrant location handled TerrainQuad automatically now
q.setLocalTranslation(loc);
- for (TerrainGridListener l : listeners.values()) {
- l.tileAttached(cam, q);
+ if (!shifted) {
+ for (TerrainGridListener l : listeners) {
+ l.tileAttached(quadCell, q);
+ }
}
-
updateModelBound();
+
}
- protected void updateChildrens(Vector3f cam) {
- // ---------------------------------------------------
- // LRU cache is used, so elements that need to remain
- // should be touched.
- // ---------------------------------------------------
+
+ /**
+ * Called when the camera has moved into a new cell. We need to
+ * update what quads are in the scene now.
+ *
+ * Step 1: touch cache
+ * LRU cache is used, so elements that need to remain
+ * should be touched.
+ *
+ * Step 2: load new quads in background thread
+ * if the camera has moved into a new cell, we load in new quads
+ * @param camCell the cell the camera is in
+ */
+ protected void updateChildren(Vector3f camCell) {
+
int dx = 0;
int dy = 0;
- if (currentCell != null) {
- dx = (int) (cam.x - currentCell.x);
- dy = (int) (cam.z - currentCell.z);
+ if (currentCamCell != null) {
+ dx = (int) (camCell.x - currentCamCell.x);
+ dy = (int) (camCell.z - currentCamCell.z);
}
- int kxm = 0;
- int kxM = 4;
- int kym = 0;
- int kyM = 4;
- if (dx == -1) {
- kxM = 3;
- } else if (dx == 1) {
- kxm = 1;
+ int xMin = 0;
+ int xMax = 4;
+ int yMin = 0;
+ int yMax = 4;
+ if (dx == -1) { // camera moved to -X direction
+ xMax = 3;
+ } else if (dx == 1) { // camera moved to +X direction
+ xMin = 1;
}
- if (dy == -1) {
- kyM = 3;
- } else if (dy == 1) {
- kym = 1;
+ if (dy == -1) { // camera moved to -Y direction
+ yMax = 3;
+ } else if (dy == 1) { // camera moved to +Y direction
+ yMin = 1;
}
- for (int i = kym; i < kyM; i++) {
- for (int j = kxm; j < kxM; j++) {
- cache.get(cam.add(quadIndex[i * 4 + j]));
+ // Touch the items in the cache that we are and will be interested in.
+ // We activate cells in the direction we are moving. If we didn't move
+ // either way in one of the axes (say X or Y axis) then they are all touched.
+ for (int i = yMin; i < yMax; i++) {
+ for (int j = xMin; j < xMax; j++) {
+ cache.get(camCell.add(quadIndex[i * 4 + j]));
}
}
+
// ---------------------------------------------------
// ---------------------------------------------------
- if (executor == null) {
- executor = createExecutorService();
+ if (cacheExecutor == null) {
+ // use the same executor as the LODControl
+ cacheExecutor = createExecutorService();
}
- executor.submit(new UpdateQuadCache(cam));
+ cacheExecutor.submit(new UpdateQuadCache(camCell));
- this.currentCell = cam;
+ this.currentCamCell = camCell;
}
- public void addListener(String id, TerrainGridListener listener) {
- this.listeners.put(id, listener);
+ public void addListener(TerrainGridListener listener) {
+ this.listeners.add(listener);
}
public Vector3f getCurrentCell() {
- return this.currentCell;
+ return this.currentCamCell;
}
- public void removeListener(String id) {
- this.listeners.remove(id);
+ public void removeListener(TerrainGridListener listener) {
+ this.listeners.remove(listener);
}
@Override
}
super.adjustHeight(xz, height);
}
-
+
@Override
protected float getHeightmapHeight(int x, int z) {
- return super.getHeightmapHeight(x-gridOffset[0], z-gridOffset[1]);
+ return super.getHeightmapHeight(x - gridOffset[0], z - gridOffset[1]);
}
-
+
@Override
- protected TerrainQuad findDownQuad() {
- if (quadrant == 1) {
- return cache.get(currentCell.add(quadIndex[13]));
- } else if (quadrant == 3) {
- return cache.get(currentCell.add(quadIndex[14]));
- }
- return null;
+ public int getNumMajorSubdivisions() {
+ return 2;
}
-
+
@Override
- protected TerrainQuad findLeftQuad() {
- if (quadrant == 1) {
- return cache.get(currentCell.add(quadIndex[8]));
- } else if (quadrant == 2) {
- return cache.get(currentCell.add(quadIndex[4]));
- }
- return null;
+ public Material getMaterial(Vector3f worldLocation) {
+ if (worldLocation == null)
+ return null;
+ Vector3f tileCell = getTileCell(worldLocation);
+ Terrain terrain = cache.get(tileCell);
+ if (terrain == null)
+ return null; // terrain not loaded for that cell yet!
+ return terrain.getMaterial(worldLocation);
}
+ /**
+ * This will print out any exceptions from the thread
+ */
+ protected ExecutorService createExecutorService() {
+ final ThreadFactory threadFactory = new ThreadFactory() {
+ public Thread newThread(Runnable r) {
+ Thread th = new Thread(r);
+ th.setName("jME TerrainGrid Thread");
+ th.setDaemon(true);
+ return th;
+ }
+ };
+ ThreadPoolExecutor ex = new ThreadPoolExecutor(1, 1,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(),
+ threadFactory) {
+ protected void afterExecute(Runnable r, Throwable t) {
+ super.afterExecute(r, t);
+ if (t == null && r instanceof Future<?>) {
+ try {
+ Future<?> future = (Future<?>) r;
+ if (future.isDone())
+ future.get();
+ } catch (CancellationException ce) {
+ t = ce;
+ } catch (ExecutionException ee) {
+ t = ee.getCause();
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt(); // ignore/reset
+ }
+ }
+ if (t != null)
+ t.printStackTrace();
+ }
+ };
+ return ex;
+ }
+
@Override
- protected TerrainQuad findRightQuad() {
- if (quadrant == 3) {
- return cache.get(currentCell.add(quadIndex[11]));
- } else if (quadrant == 4) {
- return cache.get(currentCell.add(quadIndex[7]));
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule c = im.getCapsule(this);
+ name = c.readString("name", null);
+ size = c.readInt("size", 0);
+ patchSize = c.readInt("patchSize", 0);
+ stepScale = (Vector3f) c.readSavable("stepScale", null);
+ offset = (Vector2f) c.readSavable("offset", null);
+ offsetAmount = c.readFloat("offsetAmount", 0);
+ gridTileLoader = (TerrainGridTileLoader) c.readSavable("terrainQuadGrid", null);
+ material = (Material) c.readSavable("material", null);
+ initData();
+ if (gridTileLoader != null) {
+ gridTileLoader.setPatchSize(this.patchSize);
+ gridTileLoader.setQuadSize(this.quadSize);
}
- return null;
}
@Override
- protected TerrainQuad findTopQuad() {
- if (quadrant == 2) {
- return cache.get(currentCell.add(quadIndex[1]));
- } else if (quadrant == 4) {
- return cache.get(currentCell.add(quadIndex[2]));
- }
- return null;
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule c = ex.getCapsule(this);
+ c.write(gridTileLoader, "terrainQuadGrid", null);
+ c.write(size, "size", 0);
+ c.write(patchSize, "patchSize", 0);
+ c.write(stepScale, "stepScale", null);
+ c.write(offset, "offset", null);
+ c.write(offsetAmount, "offsetAmount", 0);
+ c.write(material, "material", null);
}
}
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
*/
package com.jme3.terrain.geomipmap;
-import com.jme3.material.Material;
import com.jme3.math.Vector3f;
/**
- *
+ * Notifies the user of grid change events, such as moving to new grid cells.
* @author Anthyon
*/
public interface TerrainGridListener {
+ /**
+ * Called whenever the camera has moved full grid cells. This triggers new tiles to load.
+ * @param newCenter
+ */
public void gridMoved(Vector3f newCenter);
+ /**
+ * Called when a TerrainQuad is attached to the scene and is visible (attached to the root TerrainGrid)
+ * @param cell the cell that is moved into
+ * @param quad the quad that was just attached
+ */
public void tileAttached( Vector3f cell, TerrainQuad quad );
+ /**
+ * Called when a TerrainQuad is detached from its TerrainGrid parent: it is no longer on the scene graph.
+ * @param cell the cell that is moved into
+ * @param quad the quad that was just detached
+ */
public void tileDetached( Vector3f cell, TerrainQuad quad );
}
--- /dev/null
+/*
+ * 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.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
+import java.util.List;
+
+/**
+ * Updates grid offsets and cell positions.
+ * As well as updating LOD.
+ *
+ * @author sploreg
+ */
+public class TerrainGridLodControl extends TerrainLodControl {
+
+ public TerrainGridLodControl(Terrain terrain, Camera camera) {
+ super(terrain, camera);
+ }
+
+ @Override
+ protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) {
+ TerrainGrid terrainGrid = (TerrainGrid)getSpatial();
+
+ // for now, only the first camera is handled.
+ // to accept more, there are two ways:
+ // 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed
+ // 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore
+ Vector3f cam = locations.isEmpty() ? Vector3f.ZERO.clone() : locations.get(0);
+ Vector3f camCell = terrainGrid.getCamCell(cam); // get the grid index value of where the camera is (ie. 2,1)
+ if (terrainGrid.cellsLoaded > 1) { // Check if cells are updated before updating gridoffset.
+ terrainGrid.gridOffset[0] = Math.round(camCell.x * (terrainGrid.size / 2));
+ terrainGrid.gridOffset[1] = Math.round(camCell.z * (terrainGrid.size / 2));
+ terrainGrid.cellsLoaded = 0;
+ }
+ if (camCell.x != terrainGrid.currentCamCell.x || camCell.z != terrainGrid.currentCamCell.z || !terrainGrid.runOnce) {
+ // if the camera has moved into a new cell, load new terrain into the visible 4 center quads
+ terrainGrid.updateChildren(camCell);
+ for (TerrainGridListener l : terrainGrid.listeners) {
+ l.gridMoved(camCell);
+ }
+ }
+ terrainGrid.runOnce = true;
+ super.updateLOD(locations, lodCalculator);
+ }
+}
--- /dev/null
+/*
+ * 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.export.Savable;
+import com.jme3.math.Vector3f;
+
+/**
+ *
+ * @author normenhansen
+ */
+public interface TerrainGridTileLoader extends Savable {
+
+ public TerrainQuad getTerrainQuadAt(Vector3f location);
+
+ public void setPatchSize(int patchSize);
+
+ public void setQuadSize(int quadSize);
+}
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
import com.jme3.export.JmeImporter;\r
import com.jme3.export.OutputCapsule;\r
import com.jme3.math.Vector3f;\r
-import java.util.List;\r
-\r
import com.jme3.renderer.Camera;\r
import com.jme3.renderer.RenderManager;\r
import com.jme3.renderer.ViewPort;\r
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;\r
import java.io.IOException;\r
import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.concurrent.Callable;\r
+import java.util.concurrent.ExecutionException;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.Future;\r
+import java.util.concurrent.ThreadFactory;\r
+import java.util.concurrent.atomic.AtomicBoolean;\r
+import java.util.logging.Level;\r
+import java.util.logging.Logger;\r
\r
/**\r
* Tells the terrain to update its Level of Detail.\r
* in the future it will use all of them to determine what\r
* LOD to set.\r
*\r
+ * This control serializes, but it does not save the Camera reference.\r
+ * This camera reference has to be manually added in when you load the\r
+ * terrain to the scene!\r
+ * \r
+ * When the control or the terrain are removed from the scene, you should call\r
+ * TerrainLodControl.detachAndCleanUpControl() to remove any threads it created\r
+ * to handle the LOD processing. If you supply your own executor service, then\r
+ * you have to handle its thread termination yourself.\r
+ * \r
* @author Brent Owens\r
*/\r
public class TerrainLodControl extends AbstractControl {\r
\r
private Terrain terrain;\r
- private List<Camera> cameras;\r
+ protected List<Camera> cameras;\r
private List<Vector3f> cameraLocations = new ArrayList<Vector3f>();\r
- private LodCalculator lodCalculator;\r
+ protected LodCalculator lodCalculator;\r
private boolean hasResetLod = false; // used when enabled is set to false\r
\r
+ private HashMap<String,UpdatedTerrainPatch> updatedPatches;\r
+ private final Object updatePatchesLock = new Object();\r
+ \r
+ protected List<Vector3f> lastCameraLocations; // used for LOD calc\r
+ private AtomicBoolean lodCalcRunning = new AtomicBoolean(false);\r
+ private int lodOffCount = 0;\r
+ \r
+ protected ExecutorService executor;\r
+ protected Future<HashMap<String, UpdatedTerrainPatch>> indexer;\r
+ private boolean forceUpdate = true;\r
+ \r
public TerrainLodControl() {\r
}\r
\r
public TerrainLodControl(Terrain terrain, List<Camera> cameras) {\r
this.terrain = terrain;\r
this.cameras = cameras;\r
+ lodCalculator = new DistanceLodCalculator(65, 2.7f); // a default calculator\r
}\r
\r
@Override\r
protected void controlRender(RenderManager rm, ViewPort vp) {\r
}\r
\r
- @Override\r
- public void update(float tpf) {\r
- controlUpdate(tpf);\r
+ /**\r
+ * Set your own custom executor to be used. The control will use\r
+ * this instead of creating its own.\r
+ */\r
+ public void setExecutor(ExecutorService executor) {\r
+ this.executor = executor;\r
+ }\r
+ \r
+ protected ExecutorService createExecutorService() {\r
+ return Executors.newSingleThreadExecutor(new ThreadFactory() {\r
+ public Thread newThread(Runnable r) {\r
+ Thread th = new Thread(r);\r
+ th.setName("jME Terrain Thread");\r
+ th.setDaemon(true);\r
+ return th;\r
+ }\r
+ });\r
}\r
\r
@Override\r
cameraLocations.add(c.getLocation());\r
}\r
}\r
- terrain.update(cameraLocations, lodCalculator);\r
+ updateLOD(cameraLocations, lodCalculator);\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Call this when you remove the terrain or this control from the scene.\r
+ * It will clear up any threads it had.\r
+ */\r
+ public void detachAndCleanUpControl() {\r
+ if (executor != null)\r
+ executor.shutdownNow();\r
+ getSpatial().removeControl(this);\r
+ }\r
+\r
+ // do all of the LOD calculations\r
+ protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) {\r
+ if(getSpatial() == null){\r
+ return;\r
}\r
+ \r
+ // update any existing ones that need updating\r
+ updateQuadLODs();\r
+\r
+ if (lodCalculator.isLodOff()) {\r
+ // we want to calculate the base lod at least once\r
+ if (lodOffCount == 1)\r
+ return;\r
+ else\r
+ lodOffCount++;\r
+ } else \r
+ lodOffCount = 0;\r
+ \r
+ if (lastCameraLocations != null) {\r
+ if (!forceUpdate && lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff())\r
+ return; // don't update if in same spot\r
+ else\r
+ lastCameraLocations = cloneVectorList(locations);\r
+ forceUpdate = false;\r
+ }\r
+ else {\r
+ lastCameraLocations = cloneVectorList(locations);\r
+ return;\r
+ }\r
+\r
+ if (isLodCalcRunning()) {\r
+ return;\r
+ }\r
+ setLodCalcRunning(true);\r
+\r
+ if (executor == null)\r
+ executor = createExecutorService();\r
+ \r
+ prepareTerrain();\r
+ \r
+ UpdateLOD updateLodThread = getLodThread(locations, lodCalculator);\r
+ indexer = executor.submit(updateLodThread);\r
+ }\r
+\r
+ /**\r
+ * Force the LOD to update instantly, does not wait for the camera to move.\r
+ * It will reset once it has updated.\r
+ */\r
+ public void forceUpdate() {\r
+ this.forceUpdate = true;\r
+ }\r
+ \r
+ protected void prepareTerrain() {\r
+ TerrainQuad terrain = (TerrainQuad)getSpatial();\r
+ terrain.cacheTerrainTransforms();// cache the terrain's world transforms so they can be accessed on the separate thread safely\r
+ }\r
+ \r
+ protected UpdateLOD getLodThread(List<Vector3f> locations, LodCalculator lodCalculator) {\r
+ return new UpdateLOD(locations, lodCalculator);\r
}\r
\r
+ /**\r
+ * Back on the ogl thread: update the terrain patch geometries\r
+ */\r
+ private void updateQuadLODs() {\r
+ if (indexer != null) {\r
+ if (indexer.isDone()) {\r
+ try {\r
+ \r
+ HashMap<String, UpdatedTerrainPatch> updated = indexer.get();\r
+ if (updated != null) {\r
+ // do the actual geometry update here\r
+ for (UpdatedTerrainPatch utp : updated.values()) {\r
+ utp.updateAll();\r
+ }\r
+ }\r
+ \r
+ } catch (InterruptedException ex) {\r
+ Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);\r
+ } catch (ExecutionException ex) {\r
+ Logger.getLogger(TerrainLodControl.class.getName()).log(Level.SEVERE, null, ex);\r
+ } finally {\r
+ indexer = null;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {\r
+ boolean theSame = true;\r
+ for (Vector3f l : locations) {\r
+ for (Vector3f v : lastCameraLocations) {\r
+ if (!v.equals(l) ) {\r
+ theSame = false;\r
+ return false;\r
+ }\r
+ }\r
+ }\r
+ return theSame;\r
+ }\r
+ \r
+ protected synchronized boolean isLodCalcRunning() {\r
+ return lodCalcRunning.get();\r
+ }\r
+\r
+ protected synchronized void setLodCalcRunning(boolean running) {\r
+ lodCalcRunning.set(running);\r
+ }\r
+\r
+ private List<Vector3f> cloneVectorList(List<Vector3f> locations) {\r
+ List<Vector3f> cloned = new ArrayList<Vector3f>();\r
+ for(Vector3f l : locations)\r
+ cloned.add(l.clone());\r
+ return cloned;\r
+ }\r
+\r
+ \r
+ \r
+ \r
+ \r
+ \r
+ \r
public Control cloneForSpatial(Spatial spatial) {\r
if (spatial instanceof Terrain) {\r
List<Camera> cameraClone = new ArrayList<Camera>();\r
lodCalculator.turnOnLod();\r
}\r
}\r
+ \r
+ \r
+ /**\r
+ * Calculates the LOD of all child terrain patches.\r
+ */\r
+ protected class UpdateLOD implements Callable<HashMap<String,UpdatedTerrainPatch>> {\r
+ protected List<Vector3f> camLocations;\r
+ protected LodCalculator lodCalculator;\r
+\r
+ protected UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {\r
+ this.camLocations = camLocations;\r
+ this.lodCalculator = lodCalculator;\r
+ }\r
+\r
+ public HashMap<String, UpdatedTerrainPatch> call() throws Exception {\r
+ //long start = System.currentTimeMillis();\r
+ //if (isLodCalcRunning()) {\r
+ // return null;\r
+ //}\r
+ setLodCalcRunning(true);\r
+\r
+ TerrainQuad terrainQuad = (TerrainQuad)getSpatial();\r
+ \r
+ // go through each patch and calculate its LOD based on camera distance\r
+ HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();\r
+ boolean lodChanged = terrainQuad.calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here\r
+\r
+ if (!lodChanged) {\r
+ // not worth updating anything else since no one's LOD changed\r
+ setLodCalcRunning(false);\r
+ return null;\r
+ }\r
+ \r
+ \r
+ // then calculate its neighbour LOD values for seaming in the shader\r
+ terrainQuad.findNeighboursLod(updated);\r
+\r
+ terrainQuad.fixEdges(updated); // 'updated' can get added to here\r
+\r
+ terrainQuad.reIndexPages(updated, lodCalculator.usesVariableLod());\r
+\r
+ //setUpdateQuadLODs(updated); // set back to main ogl thread\r
+\r
+ setLodCalcRunning(false);\r
+ \r
+ return updated;\r
+ }\r
+ }\r
\r
@Override\r
public void write(JmeExporter ex) throws IOException {\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.geomipmap;\r
\r
import com.jme3.bounding.BoundingBox;\r
import com.jme3.export.JmeExporter;\r
import com.jme3.export.JmeImporter;\r
import com.jme3.export.OutputCapsule;\r
-import java.nio.FloatBuffer;\r
-import java.nio.IntBuffer;\r
-import java.util.HashMap;\r
-\r
-import com.jme3.math.FastMath;\r
-import com.jme3.math.Ray;\r
-import com.jme3.math.Triangle;\r
-import com.jme3.math.Vector2f;\r
-import com.jme3.math.Vector3f;\r
+import com.jme3.math.*;\r
import com.jme3.scene.Geometry;\r
import com.jme3.scene.Mesh;\r
import com.jme3.scene.VertexBuffer;\r
import com.jme3.scene.VertexBuffer.Type;\r
+import com.jme3.scene.mesh.IndexBuffer;\r
import com.jme3.terrain.geomipmap.TerrainQuad.LocationHeight;\r
-import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;\r
-import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;\r
-import com.jme3.terrain.geomipmap.lodcalc.LodCalculatorFactory;\r
import com.jme3.terrain.geomipmap.lodcalc.util.EntropyComputeUtil;\r
import com.jme3.util.BufferUtils;\r
-import com.jme3.util.TangentBinormalGenerator;\r
import java.io.IOException;\r
+import java.nio.Buffer;\r
+import java.nio.FloatBuffer;\r
+import java.nio.IntBuffer;\r
+import java.nio.ShortBuffer;\r
+import java.util.HashMap;\r
import java.util.List;\r
\r
\r
public class TerrainPatch extends Geometry {\r
\r
protected LODGeomap geomap;\r
- protected int lod = -1; // this terrain patch's LOD\r
+ protected int lod = 0; // this terrain patch's LOD\r
private int maxLod = -1;\r
protected int previousLod = -1;\r
protected int lodLeft, lodTop, lodRight, lodBottom; // it's neighbour's LODs\r
protected TerrainPatch leftNeighbour, topNeighbour, rightNeighbour, bottomNeighbour;\r
protected boolean searchedForNeighboursAlready = false;\r
\r
+ // these two vectors are calculated on the GL thread, but used in the outside LOD thread\r
+ protected Vector3f worldTranslationCached;\r
+ protected Vector3f worldScaleCached;\r
\r
protected float[] lodEntropy;\r
\r
public TerrainPatch() {\r
super("TerrainPatch");\r
+ setBatchHint(BatchHint.Never);\r
}\r
\r
public TerrainPatch(String name) {\r
super(name);\r
+ setBatchHint(BatchHint.Never);\r
}\r
\r
public TerrainPatch(String name, int size) {\r
float[] heightMap, Vector3f origin, int totalSize,\r
Vector2f offset, float offsetAmount) {\r
super(name);\r
+ setBatchHint(BatchHint.Never);\r
this.size = size;\r
this.stepScale = stepScale;\r
this.totalSize = totalSize;\r
\r
setLocalTranslation(origin);\r
\r
- FloatBuffer heightBuffer = BufferUtils.createFloatBuffer(size*size);\r
- heightBuffer.put(heightMap);\r
-\r
- geomap = new LODGeomap(size, heightBuffer);\r
+ geomap = new LODGeomap(size, heightMap);\r
Mesh m = geomap.createMesh(stepScale, new Vector2f(1,1), offset, offsetAmount, totalSize, false);\r
setMesh(m);\r
\r
float[] entropies = new float[getMaxLod()+1];\r
for (int i = 0; i <= getMaxLod(); i++){\r
int curLod = (int) Math.pow(2, i);\r
- IntBuffer buf = geomap.writeIndexArrayLodDiff(null, curLod, false, false, false, false);\r
- entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, buf);\r
+ IndexBuffer idxB = geomap.writeIndexArrayLodDiff(curLod, false, false, false, false, totalSize);\r
+ Buffer ib;\r
+ if (idxB.getBuffer() instanceof IntBuffer)\r
+ ib = (IntBuffer)idxB.getBuffer();\r
+ else\r
+ ib = (ShortBuffer)idxB.getBuffer();\r
+ entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, ib);\r
}\r
\r
lodEntropy = entropies;\r
return lodEntropy;\r
}\r
\r
+ @Deprecated\r
public FloatBuffer getHeightmap() {\r
- return geomap.getHeightData();\r
+ return BufferUtils.createFloatBuffer(geomap.getHeightArray());\r
+ }\r
+ \r
+ public float[] getHeightMap() {\r
+ return geomap.getHeightArray();\r
}\r
\r
/**\r
* If the patch size is 32 then the returned value would be log2(32)-2 = 3\r
* You can then use that value, 3, to see how many times you can divide 32 by 2\r
* before the terrain gets too un-detailed (can't stitch it any further).\r
- * @return\r
+ * @return the maximum LOD\r
*/\r
public int getMaxLod() {\r
if (maxLod < 0)\r
\r
UpdatedTerrainPatch utp = updated.get(getName());\r
\r
- if (utp != null && (utp.isReIndexNeeded() || utp.isFixEdges()) ) {\r
+ if (utp != null && utp.isReIndexNeeded() ) {\r
int pow = (int) Math.pow(2, utp.getNewLod());\r
boolean left = utp.getLeftLod() > utp.getNewLod();\r
boolean top = utp.getTopLod() > utp.getNewLod();\r
boolean right = utp.getRightLod() > utp.getNewLod();\r
boolean bottom = utp.getBottomLod() > utp.getNewLod();\r
\r
- IntBuffer ib = null;\r
+ IndexBuffer idxB;\r
if (useVariableLod)\r
- 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()));\r
+ idxB = geomap.writeIndexArrayLodVariable(pow, (int) Math.pow(2, utp.getRightLod()), (int) Math.pow(2, utp.getTopLod()), (int) Math.pow(2, utp.getLeftLod()), (int) Math.pow(2, utp.getBottomLod()), totalSize);\r
else\r
- ib = geomap.writeIndexArrayLodDiff(null, pow, right, top, left, bottom);\r
- utp.setNewIndexBuffer(ib);\r
+ idxB = geomap.writeIndexArrayLodDiff(pow, right, top, left, bottom, totalSize);\r
+ \r
+ Buffer b;\r
+ if (idxB.getBuffer() instanceof IntBuffer)\r
+ b = (IntBuffer)idxB.getBuffer();\r
+ else\r
+ b = (ShortBuffer)idxB.getBuffer();\r
+ utp.setNewIndexBuffer(b);\r
}\r
\r
}\r
\r
/**\r
* Get the triangle of this geometry at the specified local coordinate.\r
- * @param gridX local to the terrain patch\r
- * @param gridY local to the terrain patch\r
+ * @param x local to the terrain patch\r
+ * @param z local to the terrain patch\r
* @return the triangle in world coordinates, or null if the point does intersect this patch on the XZ axis\r
*/\r
public Triangle getTriangle(float x, float z) {\r
\r
/**\r
* Get the triangles at the specified grid point. Probably only 2 triangles\r
- * @param gridX local to the terrain patch\r
- * @param gridY local to the terrain patch\r
+ * @param x local to the terrain patch\r
+ * @param z local to the terrain patch\r
* @return the triangles in world coordinates, or null if the point does intersect this patch on the XZ axis\r
*/\r
public Triangle[] getGridTriangles(float x, float z) {\r
continue;\r
int idx = lh.z * size + lh.x;\r
if (overrideHeight) {\r
- geomap.getHeightData().put(idx, lh.h);\r
+ geomap.getHeightArray()[idx] = lh.h;\r
} else {\r
float h = getMesh().getFloatBuffer(Type.Position).get(idx*3+1);\r
- geomap.getHeightData().put(idx, h+lh.h);\r
+ geomap.getHeightArray()[idx] = h+lh.h;\r
}\r
\r
}\r
}\r
\r
/**\r
- * recalculate all of this normal vectors in this terrain patch\r
+ * recalculate all of the normal vectors in this terrain patch\r
*/\r
protected void updateNormals() {\r
FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale());\r
getMesh().getBuffer(Type.Normal).updateData(newNormalBuffer);\r
FloatBuffer newTangentBuffer = null;\r
FloatBuffer newBinormalBuffer = null;\r
- FloatBuffer[] tb = geomap.writeTangentArray(newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale());\r
+ FloatBuffer[] tb = geomap.writeTangentArray(newNormalBuffer, newTangentBuffer, newBinormalBuffer, (FloatBuffer)getMesh().getBuffer(Type.TexCoord).getData(), getWorldScale());\r
newTangentBuffer = tb[0];\r
newBinormalBuffer = tb[1];\r
getMesh().getBuffer(Type.Tangent).updateData(newTangentBuffer);\r
getMesh().getBuffer(Type.Binormal).updateData(newBinormalBuffer);\r
}\r
\r
+ private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent, Vector3f binormal) {\r
+ VertexBuffer NB = mesh.getBuffer(Type.Normal);\r
+ VertexBuffer TB = mesh.getBuffer(Type.Tangent);\r
+ VertexBuffer BB = mesh.getBuffer(Type.Binormal);\r
+ BufferUtils.setInBuffer(normal, (FloatBuffer)NB.getData(), index);\r
+ BufferUtils.setInBuffer(tangent, (FloatBuffer)TB.getData(), index);\r
+ BufferUtils.setInBuffer(binormal, (FloatBuffer)BB.getData(), index);\r
+ NB.setUpdateNeeded();\r
+ TB.setUpdateNeeded();\r
+ BB.setUpdateNeeded();\r
+ }\r
+ \r
/**\r
* Matches the normals along the edge of the patch with the neighbours.\r
* Computes the normals for the right, bottom, left, and top edges of the\r
* *---x---*\r
* |\r
* *\r
+ * It works across the right side of the patch, from the top down to \r
+ * the bottom. Then it works on the bottom side of the patch, from the\r
+ * left to the right.\r
*/\r
protected void fixNormalEdges(TerrainPatch right,\r
TerrainPatch bottom,\r
Vector3f rightPoint = new Vector3f();\r
Vector3f leftPoint = new Vector3f();\r
Vector3f topPoint = new Vector3f();\r
+\r
Vector3f bottomPoint = new Vector3f();\r
- Vector2f rootTex = new Vector2f();\r
- Vector2f rightTex = new Vector2f();\r
- Vector2f leftTex = new Vector2f();\r
- Vector2f topTex = new Vector2f();\r
- Vector2f bottomTex = new Vector2f();\r
- Vector3f normal = new Vector3f();\r
+\r
Vector3f tangent = new Vector3f();\r
+ Vector3f binormal = new Vector3f();\r
+ Vector3f normal = new Vector3f();\r
\r
+ \r
int s = this.getSize()-1;\r
\r
- if (right != null) { // right side\r
+ if (right != null) { // right side, works its way down\r
for (int i=0; i<s+1; i++) {\r
- rootPoint.set(s, this.getHeightmapHeight(s,i), i);\r
- leftPoint.set(s-1, this.getHeightmapHeight(s-1,i), i);\r
- rightPoint.set(s+1, right.getHeightmapHeight(1,i), i);\r
- this.getTex(s,i, rootTex);\r
- this.getTex(s-1,i, leftTex);\r
- right.getTex(1,i, rightTex);\r
- if (i == 0) { // top\r
+ rootPoint.set(0, this.getHeightmapHeight(s,i), 0);\r
+ leftPoint.set(-1, this.getHeightmapHeight(s-1,i), 0);\r
+ rightPoint.set(1, right.getHeightmapHeight(1,i), 0);\r
+\r
+ if (i == 0) { // top point\r
+ bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1);\r
+ \r
if (top == null) {\r
- bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);\r
- this.getTex(s,i+1, bottomTex);\r
- averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, null, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer rightTB = right.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), s);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)rightTB.getData(), 0);\r
+ averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), s, normal, tangent, binormal);\r
+ setInBuffer(right.getMesh(), 0, normal, tangent, binormal);\r
} else {\r
- topPoint.set(s, top.getHeightmapHeight(s,s-1), i-1);\r
- bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);\r
- top.getTex(s,s-1, topTex);\r
- this.getTex(s,i+1, bottomTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer rightTB = right.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer topNB = top.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer topTB = top.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), s);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)rightTB.getData(), 0);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)topNB.getData(), (s+1)*(s+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)topTB.getData(), (s+1)*(s+1)-1);\r
- // update top right corner\r
- /*if (topRight != null) {\r
- VertexBuffer topRightNB = topRight.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer topRightTB = topRight.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)topRightNB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)topRightTB.getData(), (s+1)*s);\r
- topRightNB.setUpdateNeeded();\r
- topRightTB.setUpdateNeeded();\r
- }*/\r
+ topPoint.set(0, top.getHeightmapHeight(s,s-1), -1);\r
+ \r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint,normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), s, normal, tangent, binormal);\r
+ setInBuffer(right.getMesh(), 0, normal, tangent, binormal);\r
+ setInBuffer(top.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);\r
+ \r
+ if (topRight != null) {\r
+ // setInBuffer(topRight.getMesh(), (s+1)*s, normal, tangent, binormal);\r
+ }\r
}\r
- } else if (i == s) { // bottom\r
+ } else if (i == s) { // bottom point\r
+ topPoint.set(0, this.getHeightmapHeight(s,s-1), -1);\r
+ \r
if (bottom == null) {\r
- topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);\r
- this.getTex(s,i-1, topTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, topTex, rootTex, leftTex, null, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer rightTB = right.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*(i+1)-1);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*(i));\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)rightTB.getData(), (s+1)*(i));\r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);\r
+ setInBuffer(right.getMesh(), (s+1)*(s), normal, tangent, binormal);\r
} else {\r
- topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);\r
- bottomPoint.set(s, bottom.getHeightmapHeight(s,1), i+1);\r
- this.getTex(s,i-1, topTex);\r
- bottom.getTex(s,1, bottomTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer rightTB = right.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer downTB = bottom.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*(s+1)-1);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)rightTB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)downTB.getData(), s);\r
- /*if (bottomRight != null) {\r
- VertexBuffer bottomRightNB = bottomRight.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer bottomRightTB = bottomRight.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)bottomRightNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)bottomRightTB.getData(), 0);\r
- bottomRightNB.setUpdateNeeded();\r
- bottomRightTB.setUpdateNeeded();\r
- }*/\r
- downNB.setUpdateNeeded();\r
- downTB.setUpdateNeeded();\r
+ bottomPoint.set(0, bottom.getHeightmapHeight(s,1), 1);\r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);\r
+ setInBuffer(right.getMesh(), (s+1)*s, normal, tangent, binormal);\r
+ setInBuffer(bottom.getMesh(), s, normal, tangent, binormal);\r
+ \r
+ if (bottomRight != null) {\r
+ // setInBuffer(bottomRight.getMesh(), 0, normal, tangent, binormal);\r
+ }\r
}\r
} else { // all in the middle\r
- topPoint.set(s, this.getHeightmapHeight(s,i-1), i-1);\r
- bottomPoint.set(s, this.getHeightmapHeight(s,i+1), i+1);\r
- this.getTex(s,i-1, topTex);\r
- this.getTex(s,i+1, bottomTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer rightNB = right.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer rightTB = right.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*(i+1)-1);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)rightNB.getData(), (s+1)*(i));\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)rightTB.getData(), (s+1)*(i));\r
+ topPoint.set(0, this.getHeightmapHeight(s,i-1), -1);\r
+ bottomPoint.set(0, this.getHeightmapHeight(s,i+1), 1);\r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal);\r
+ setInBuffer(right.getMesh(), (s+1)*(i), normal, tangent, binormal);\r
}\r
}\r
- right.getMesh().getBuffer(Type.Normal).setUpdateNeeded();\r
- right.getMesh().getBuffer(Type.Tangent).setUpdateNeeded();\r
}\r
\r
- if (bottom != null) {\r
+ if (left != null) { // left side, works its way down\r
for (int i=0; i<s+1; i++) {\r
- rootPoint.set(i, this.getHeightmapHeight(i,s), s);\r
- topPoint.set(i, this.getHeightmapHeight(i,s-1), s-1);\r
- bottomPoint.set(i, bottom.getHeightmapHeight(i,1), s+1);\r
- this.getTex(i,s, rootTex);\r
- this.getTex(i,s-1, topTex);\r
- bottom.getTex(i,1, bottomTex);\r
- if (i == 0) { // left\r
- /*if (left == null) {\r
- rightPoint.set(i+1, this.getHeight(i+1,s), s);\r
- averageNormalsTangents(v, t, indexes, topPoint, rootPoint, null, bottomPoint, rightPoint, topTex, rootTex, null, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer downTB = bottom.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)downTB.getData(), 0);\r
- } else {\r
- leftPoint.set(i-1, left.getHeight(s-1,s), s);\r
- rightPoint.set(i+1, this.getHeight(i+1,s), s);\r
- left.getTex(i-1,s, leftTex);\r
- this.getTex(i+1,s, rightTex);\r
- averageNormalsTangents(v, t, indexes, topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer leftTB = left.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer downTB = bottom.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(s+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)leftTB.getData(), (s+1)*(s+1)-1);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)downTB.getData(), 0);\r
- }*/\r
- } else if (i == s) { // right\r
+ rootPoint.set(0, this.getHeightmapHeight(0,i), 0);\r
+ leftPoint.set(-1, left.getHeightmapHeight(s-1,i), 0);\r
+ rightPoint.set(1, this.getHeightmapHeight(1,i), 0);\r
+ \r
+ if (i == 0) { // top point\r
+ bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1);\r
\r
- // handled by right side\r
+ if (top == null) {\r
+ averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), 0, normal, tangent, binormal);\r
+ setInBuffer(left.getMesh(), s, normal, tangent, binormal);\r
+ } else {\r
+ topPoint.set(0, top.getHeightmapHeight(0,s-1), -1);\r
+ \r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), 0, normal, tangent, binormal);\r
+ setInBuffer(left.getMesh(), s, normal, tangent, binormal);\r
+ setInBuffer(top.getMesh(), (s+1)*s, normal, tangent, binormal);\r
+ \r
+ if (topLeft != null) {\r
+ // setInBuffer(topLeft.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);\r
+ }\r
+ }\r
+ } else if (i == s) { // bottom point\r
+ topPoint.set(0, this.getHeightmapHeight(0,i-1), -1);\r
\r
+ if (bottom == null) {\r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, null, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal);\r
+ setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);\r
+ } else {\r
+ bottomPoint.set(0, bottom.getHeightmapHeight(0,1), 1);\r
+ \r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(s), normal, tangent, binormal);\r
+ setInBuffer(left.getMesh(), (s+1)*(s+1)-1, normal, tangent, binormal);\r
+ setInBuffer(bottom.getMesh(), 0, normal, tangent, binormal);\r
+ \r
+ if (bottomLeft != null) {\r
+ // setInBuffer(bottomLeft.getMesh(), s, normal, tangent, binormal);\r
+ }\r
+ }\r
} else { // all in the middle\r
- leftPoint.set(i-1, this.getHeightmapHeight(i-1,s), s);\r
- rightPoint.set(i+1, this.getHeightmapHeight(i+1,s), s);\r
- this.getTex(i-1,s, leftTex);\r
- this.getTex(i+1,s, rightTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer downNB = bottom.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer downTB = bottom.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(s)+i);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*(s)+i);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)downNB.getData(), i);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)downTB.getData(), i);\r
+ topPoint.set(0, this.getHeightmapHeight(0,i-1), -1);\r
+ bottomPoint.set(0, this.getHeightmapHeight(0,i+1), 1);\r
+ \r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(i), normal, tangent, binormal);\r
+ setInBuffer(left.getMesh(), (s+1)*(i+1)-1, normal, tangent, binormal);\r
}\r
}\r
- bottom.getMesh().getBuffer(Type.Normal).setUpdateNeeded();\r
- bottom.getMesh().getBuffer(Type.Tangent).setUpdateNeeded();\r
}\r
\r
- if (left != null) { // left side\r
+ if (top != null) { // top side, works its way right\r
for (int i=0; i<s+1; i++) {\r
- rootPoint.set(0, this.getHeightmapHeight(0,i), i);\r
- leftPoint.set(-1, left.getHeightmapHeight(s-1,i), i);\r
- rightPoint.set(1, this.getHeightmapHeight(1,i), i);\r
- this.getTex(0,i, rootTex);\r
- left.getTex(s-1,i, leftTex);\r
- this.getTex(1,i, rightTex);\r
- if (i == 0) { // top\r
- if (top == null) {\r
- bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);\r
- this.getTex(0,i+1, bottomTex);\r
- averageNormalsTangents(null, rootPoint, leftPoint, bottomPoint, rightPoint, null, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer leftTB = left.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), 0);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), s+1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)leftTB.getData(), s+1);\r
- } else {\r
- topPoint.set(0, top.getHeightmapHeight(0,s-1), i-1);\r
- bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);\r
- top.getTex(0,i-1, topTex);\r
- this.getTex(0,i+1, bottomTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer leftTB = left.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer topNB = top.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer topTB = top.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), 0);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), 0);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)leftTB.getData(), s);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)topNB.getData(), (s+1)*s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)topTB.getData(), (s+1)*s);\r
- /*if (topLeft != null) {\r
- VertexBuffer topLeftNB = topLeft.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer topLeftTB = topLeft.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)topLeftNB.getData(), (s+1)*(s+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)topLeftTB.getData(), (s+1)*(s+1)-1);\r
- topLeftNB.setUpdateNeeded();\r
- topLeftTB.setUpdateNeeded();\r
- }*/\r
- topNB.setUpdateNeeded();\r
- }\r
- } else if (i == s) { // bottom\r
-\r
- // handled by bottom\r
-\r
- /*if (bottomLeft != null) {\r
- VertexBuffer bottomLeftNB = bottomLeft.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer bottomLeftTB = bottomLeft.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)bottomLeftNB.getData(), s);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)bottomLeftTB.getData(), s);\r
- bottomLeftNB.setUpdateNeeded();\r
- bottomLeftTB.setUpdateNeeded();\r
- }*/\r
-\r
+ rootPoint.set(0, this.getHeightmapHeight(i,0), 0);\r
+ topPoint.set(0, top.getHeightmapHeight(i,s-1), -1);\r
+ bottomPoint.set(0, this.getHeightmapHeight(i,1), 1);\r
+ \r
+ if (i == 0) { // left corner\r
+ // handled by left side pass\r
+ \r
+ } else if (i == s) { // right corner\r
+ \r
+ // handled by this patch when it does its right side\r
+ \r
} else { // all in the middle\r
- topPoint.set(0, this.getHeightmapHeight(0,i-1), i-1);\r
- bottomPoint.set(0, this.getHeightmapHeight(0,i+1), i+1);\r
- this.getTex(0,i-1, topTex);\r
- this.getTex(0,i+1, bottomTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer leftNB = left.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer leftTB = left.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), (s+1)*(i));\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), (s+1)*(i));\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)leftNB.getData(), (s+1)*(i+1)-1);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)leftTB.getData(), (s+1)*(i+1)-1);\r
+ leftPoint.set(-1, this.getHeightmapHeight(i-1,0), 0);\r
+ rightPoint.set(1, this.getHeightmapHeight(i+1,0), 0);\r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), i, normal, tangent, binormal);\r
+ setInBuffer(top.getMesh(), (s+1)*(s)+i, normal, tangent, binormal);\r
}\r
}\r
- left.getMesh().getBuffer(Type.Normal).setUpdateNeeded();\r
- left.getMesh().getBuffer(Type.Tangent).setUpdateNeeded();\r
+ \r
}\r
-\r
- if (top != null) { // top side\r
+ \r
+ if (bottom != null) { // bottom side, works its way right\r
for (int i=0; i<s+1; i++) {\r
- rootPoint.set(i, this.getHeightmapHeight(i,0), 0);\r
- topPoint.set(i, top.getHeightmapHeight(i,s-1), -1);\r
- bottomPoint.set(i, this.getHeightmapHeight(i,1), 1);\r
- this.getTex(i,s, rootTex);\r
- top.getTex(i,s-1, topTex);\r
- this.getTex(i,1, bottomTex);\r
- if (i == 0) { // left\r
-\r
- // handled by left side\r
+ rootPoint.set(0, this.getHeightmapHeight(i,s), 0);\r
+ topPoint.set(0, this.getHeightmapHeight(i,s-1), -1);\r
+ bottomPoint.set(0, bottom.getHeightmapHeight(i,1), 1);\r
\r
+ if (i == 0) { // left\r
+ // handled by the left side pass\r
+ \r
} else if (i == s) { // right\r
-\r
- // handled by right side\r
-\r
+ \r
+ // handled by the right side pass\r
+ \r
} else { // all in the middle\r
- leftPoint.set(i-1, this.getHeightmapHeight(i-1,0), 0);\r
- rightPoint.set(i+1, this.getHeightmapHeight(i+1,0), 0);\r
- this.getTex(i-1,0, leftTex);\r
- this.getTex(i+1,0, rightTex);\r
- averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, topTex, rootTex, leftTex, bottomTex, rightTex, normal, tangent);\r
- VertexBuffer tpNB = this.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer tpTB = this.getMesh().getBuffer(Type.Tangent);\r
- VertexBuffer topNB = top.getMesh().getBuffer(Type.Normal);\r
- VertexBuffer topTB = top.getMesh().getBuffer(Type.Tangent);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)tpNB.getData(), i);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)tpTB.getData(), i);\r
- BufferUtils.setInBuffer(normal, (FloatBuffer)topNB.getData(), (s+1)*(s)+i);\r
- BufferUtils.setInBuffer(tangent, (FloatBuffer)topTB.getData(), (s+1)*(s)+i);\r
+ leftPoint.set(-1, this.getHeightmapHeight(i-1,s), 0);\r
+ rightPoint.set(1, this.getHeightmapHeight(i+1,s), 0);\r
+ averageNormalsTangents(topPoint, rootPoint, leftPoint, bottomPoint, rightPoint, normal, tangent, binormal);\r
+ setInBuffer(this.getMesh(), (s+1)*(s)+i, normal, tangent, binormal);\r
+ setInBuffer(bottom.getMesh(), i, normal, tangent, binormal);\r
}\r
}\r
- top.getMesh().getBuffer(Type.Normal).setUpdateNeeded();\r
+ \r
}\r
-\r
- this.getMesh().getBuffer(Type.Normal).setUpdateNeeded();\r
- this.getMesh().getBuffer(Type.Tangent).setUpdateNeeded();\r
}\r
\r
protected void averageNormalsTangents(\r
Vector3f leftPoint, \r
Vector3f bottomPoint, \r
Vector3f rightPoint,\r
- Vector2f topTex,\r
- Vector2f rootTex,\r
- Vector2f leftTex,\r
- Vector2f bottomTex,\r
- Vector2f rightTex,\r
Vector3f normal,\r
- Vector3f tangent)\r
+ Vector3f tangent,\r
+ Vector3f binormal)\r
{\r
Vector3f scale = getWorldScale();\r
\r
- Vector3f n1 = Vector3f.ZERO;\r
+ Vector3f n1 = new Vector3f(0,0,0);\r
if (topPoint != null && leftPoint != null) {\r
- n1 = calculateNormal(topPoint.mult(scale), rootPoint.mult(scale), leftPoint.mult(scale));\r
+ n1.set(calculateNormal(topPoint.mult(scale), rootPoint.mult(scale), leftPoint.mult(scale)));\r
}\r
- Vector3f n2 = Vector3f.ZERO;\r
+ Vector3f n2 = new Vector3f(0,0,0);\r
if (leftPoint != null && bottomPoint != null) {\r
- n2 = calculateNormal(leftPoint.mult(scale), rootPoint.mult(scale), bottomPoint.mult(scale));\r
+ n2.set(calculateNormal(leftPoint.mult(scale), rootPoint.mult(scale), bottomPoint.mult(scale)));\r
}\r
- Vector3f n3 = Vector3f.ZERO;\r
+ Vector3f n3 = new Vector3f(0,0,0);\r
if (rightPoint != null && bottomPoint != null) {\r
- n3 = calculateNormal(bottomPoint.mult(scale), rootPoint.mult(scale), rightPoint.mult(scale));\r
+ n3.set(calculateNormal(bottomPoint.mult(scale), rootPoint.mult(scale), rightPoint.mult(scale)));\r
}\r
- Vector3f n4 = Vector3f.ZERO;\r
+ Vector3f n4 = new Vector3f(0,0,0);\r
if (rightPoint != null && topPoint != null) {\r
- n4 = calculateNormal(rightPoint.mult(scale), rootPoint.mult(scale), topPoint.mult(scale));\r
+ n4.set(calculateNormal(rightPoint.mult(scale), rootPoint.mult(scale), topPoint.mult(scale)));\r
}\r
\r
- Vector3f binormal = new Vector3f();\r
- if (topPoint != null && rightPoint != null)\r
- LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),topPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,topTex}, tangent, binormal);\r
+ //if (bottomPoint != null && rightPoint != null && rootTex != null && rightTex != null && bottomTex != null)\r
+ // LODGeomap.calculateTangent(new Vector3f[]{rootPoint.mult(scale),rightPoint.mult(scale),bottomPoint.mult(scale)}, new Vector2f[]{rootTex,rightTex,bottomTex}, tangent, binormal);\r
\r
- normal.set(n1.add(n2).add(n3).add(n4).normalizeLocal());\r
+ normal.set(n1.add(n2).add(n3).add(n4).normalize());\r
+ \r
+ tangent.set(normal.cross(new Vector3f(0,0,1)).normalize());\r
+ binormal.set(new Vector3f(1,0,0).cross(normal).normalize());\r
}\r
\r
private Vector3f calculateNormal(Vector3f firstPoint, Vector3f rootPoint, Vector3f secondPoint) {\r
return normal;\r
}\r
\r
+ protected float getHeight(int x, int z, float xm, float zm) {\r
+ return geomap.getHeight(x,z,xm,zm);\r
+ }\r
+ \r
/**\r
* Locks the mesh (sets it static) to improve performance.\r
* But it it not editable then. Set unlock to make it editable.\r
//clone.lodCalculator = lodCalculator.clone();\r
//clone.lodCalculator.setTerrainPatch(clone);\r
//clone.setLodCalculator(lodCalculatorFactory.clone());\r
- clone.geomap = new LODGeomap(size, geomap.getHeightData());\r
+ clone.geomap = new LODGeomap(size, geomap.getHeightArray());\r
clone.setLocalTranslation(getLocalTranslation().clone());\r
Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false);\r
clone.setMesh(m);\r
}\r
}\r
\r
+ /**\r
+ * Caches the transforms (except rotation) so the LOD calculator,\r
+ * which runs on a separate thread, can access them safely.\r
+ */\r
+ protected void cacheTerrainTransforms() {\r
+ this.worldScaleCached = getWorldScale().clone();\r
+ this.worldTranslationCached = getWorldTranslation().clone();\r
+ }\r
+\r
+ public Vector3f getWorldScaleCached() {\r
+ return worldScaleCached;\r
+ }\r
+\r
+ public Vector3f getWorldTranslationCached() {\r
+ return worldTranslationCached;\r
+ }\r
+\r
+ /**\r
+ * Removes any references when the terrain is being removed.\r
+ */\r
+ protected void clearCaches() {\r
+ if (leftNeighbour != null) {\r
+ leftNeighbour.rightNeighbour = null;\r
+ leftNeighbour = null;\r
+ }\r
+ if (rightNeighbour != null) {\r
+ rightNeighbour.leftNeighbour = null;\r
+ rightNeighbour = null;\r
+ }\r
+ if (topNeighbour != null) {\r
+ topNeighbour.bottomNeighbour = null;\r
+ topNeighbour = null;\r
+ }\r
+ if (bottomNeighbour != null) {\r
+ bottomNeighbour.topNeighbour = null;\r
+ bottomNeighbour = null;\r
+ }\r
+ }\r
\r
\r
}\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.geomipmap;\r
\r
-import com.jme3.material.Material;\r
-import java.io.IOException;\r
-import java.util.HashMap;\r
-import java.util.concurrent.ExecutorService;\r
-import java.util.concurrent.Executors;\r
-import java.util.concurrent.ThreadFactory;\r
-\r
import com.jme3.bounding.BoundingBox;\r
import com.jme3.bounding.BoundingVolume;\r
import com.jme3.collision.Collidable;\r
import com.jme3.export.JmeExporter;\r
import com.jme3.export.JmeImporter;\r
import com.jme3.export.OutputCapsule;\r
+import com.jme3.material.Material;\r
import com.jme3.math.FastMath;\r
import com.jme3.math.Ray;\r
import com.jme3.math.Vector2f;\r
import com.jme3.scene.debug.WireBox;\r
import com.jme3.terrain.ProgressMonitor;\r
import com.jme3.terrain.Terrain;\r
-import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;\r
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;\r
-import com.jme3.terrain.geomipmap.lodcalc.SimpleLodThreshold;\r
import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker;\r
import com.jme3.terrain.geomipmap.picking.TerrainPickData;\r
import com.jme3.terrain.geomipmap.picking.TerrainPicker;\r
-import com.jme3.util.BufferUtils;\r
import com.jme3.util.TangentBinormalGenerator;\r
+import java.io.IOException;\r
import java.util.ArrayList;\r
+import java.util.HashMap;\r
import java.util.List;\r
import java.util.Map;\r
import java.util.logging.Level;\r
import java.util.logging.Logger;\r
\r
/**\r
+ * <p>\r
+ * TerrainQuad is a heightfield-based terrain system. Heightfield terrain is fast and can\r
+ * render large areas, and allows for easy Level of Detail control. However it does not\r
+ * permit caves easily.\r
+ * TerrainQuad is a quad tree, meaning that the root quad has four children, and each of\r
+ * those children have four children. All the way down until you reach the bottom, the actual\r
+ * geometry, the TerrainPatches.\r
+ * If you look at a TerrainQuad in wireframe mode with the TerrainLODControl attached, you will\r
+ * see blocks that change their LOD level together; these are the TerrainPatches. The TerrainQuad\r
+ * is just an organizational structure for the TerrainPatches so patches that are not in the\r
+ * view frustum get culled quickly.\r
+ * TerrainQuads size are a power of 2, plus 1. So 513x513, or 1025x1025 etc.\r
+ * Each point in the terrain is one unit apart from its neighbour. So a 513x513 terrain\r
+ * will be 513 units wide and 513 units long.\r
+ * Patch size can be specified on the terrain. This sets how large each geometry (TerrainPatch)\r
+ * is. It also must be a power of 2 plus 1 so the terrain can be subdivided equally.\r
+ * </p>\r
+ * <p>\r
+ * The height of the terrain can be modified at runtime using setHeight()\r
+ * </p>\r
+ * <p>\r
* A terrain quad is a node in the quad tree of the terrain system.\r
* The root terrain quad will be the only one that receives the update() call every frame\r
* and it will determine if there has been any LOD change.\r
- *\r
+ * </p><p>\r
* The leaves of the terrain quad tree are Terrain Patches. These have the real geometry mesh.\r
- *\r
- * \r
+ * </p><p>\r
* Heightmap coordinates start from the bottom left of the world and work towards the\r
- * top right. This will seem upside down, but the texture coordinates compensate; and\r
- * it allows you to easily get the heightmap values at real world X,Z coordinates.\r
- * \r
+ * top right.\r
+ * </p><pre>\r
* +x\r
* ^\r
* | ......N = length of heightmap\r
* | 0.....:\r
* +---------> +z\r
* (world coordinates)\r
- * \r
+ * </pre>\r
* @author Brent Owens\r
*/\r
public class TerrainQuad extends Node implements Terrain {\r
-\r
protected Vector2f offset;\r
\r
protected int totalSize; // the size of this entire terrain tree (on one side)\r
\r
protected float offsetAmount;\r
\r
- protected int quadrant = 1; // 1=upper left, 2=lower left, 3=upper right, 4=lower right\r
-\r
- //protected LodCalculatorFactory lodCalculatorFactory;\r
- //protected LodCalculator lodCalculator;\r
- \r
- protected List<Vector3f> lastCameraLocations; // used for LOD calc\r
- private boolean lodCalcRunning = false;\r
- private int lodOffCount = 0;\r
+ protected int quadrant = 0; // 1=upper left, 2=lower left, 3=upper right, 4=lower right\r
private int maxLod = -1;\r
- private HashMap<String,UpdatedTerrainPatch> updatedPatches;\r
- private final Object updatePatchesLock = new Object();\r
private BoundingBox affectedAreaBBox; // only set in the root quad\r
\r
private TerrainPicker picker;\r
private Vector3f lastScale = Vector3f.UNIT_XYZ;\r
\r
- protected ExecutorService executor;\r
-\r
- protected ExecutorService createExecutorService() {\r
- return Executors.newSingleThreadExecutor(new ThreadFactory() {\r
- public Thread newThread(Runnable r) {\r
- Thread th = new Thread(r);\r
- th.setName("jME Terrain Thread");\r
- th.setDaemon(true);\r
- return th;\r
- }\r
- });\r
- }\r
-\r
+ protected NeighbourFinder neighbourFinder;\r
+ \r
public TerrainQuad() {\r
super("Terrain");\r
}\r
\r
+ /**\r
+ * Creates a terrain with:\r
+ * <ul>\r
+ * <li>the total, real-world, size of the terrain</li>\r
+ * <li>the patchSize, or the size of each geometry tile of the terrain</li>\r
+ * <li>the heightmap that defines the height of the terrain</li>\r
+ * </ul>\r
+ * <p>\r
+ * A TerrainQuad of totalSize 513x513 will be 513 units wide and 513 units long.\r
+ * PatchSize is just used to subdivide the terrain into tiles that can be culled.\r
+ * </p>\r
+ * @param name the name of the scene element. This is required for\r
+ * identification and comparison purposes.\r
+ * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, \r
+ * must be smaller than totalSize. (eg. 33, 65...)\r
+ * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 \r
+ * (eg. 513, 1025, 2049...)\r
+ * @param heightMap The height map to generate the terrain from (a flat\r
+ * height map will be generated if this is null). The size of one side of the heightmap \r
+ * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513.\r
+ */\r
public TerrainQuad(String name, int patchSize, int totalSize, float[] heightMap) {\r
this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap);\r
+ \r
+ affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);\r
+ fixNormalEdges(affectedAreaBBox);\r
+ addControl(new NormalRecalcControl(this));\r
}\r
\r
+ /**\r
+ * \r
+ * @param name the name of the scene element. This is required for\r
+ * identification and comparison purposes.\r
+ * @param patchSize size of the individual patches\r
+ * @param quadSize\r
+ * @param totalSize the size of this entire terrain tree (on one side)\r
+ * @param heightMap The height map to generate the terrain from (a flat\r
+ * height map will be generated if this is null)\r
+ */\r
+ @Deprecated\r
public TerrainQuad(String name, int patchSize, int quadSize, int totalSize, float[] heightMap) {\r
- this(name, patchSize, totalSize, Vector3f.UNIT_XYZ, heightMap);\r
+ this(name, patchSize, totalSize, quadSize, Vector3f.UNIT_XYZ, heightMap);\r
}\r
\r
+ /**\r
+ * \r
+ * @param name the name of the scene element. This is required for\r
+ * identification and comparison purposes.\r
+ * @param patchSize size of the individual patches\r
+ * @param size size of this quad, can be between totalSize and patchSize\r
+ * @param scale\r
+ * @param heightMap The height map to generate the terrain from (a flat\r
+ * height map will be generated if this is null)\r
+ */\r
+ @Deprecated\r
public TerrainQuad(String name, int patchSize, int size, Vector3f scale, float[] heightMap) {\r
this(name, patchSize, size, scale, heightMap, size, new Vector2f(), 0);\r
- affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);\r
- fixNormalEdges(affectedAreaBBox);\r
- addControl(new NormalRecalcControl(this));\r
+ //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size*2, Float.MAX_VALUE, size*2);\r
+ //fixNormalEdges(affectedAreaBBox);\r
+ //addControl(new NormalRecalcControl(this));\r
}\r
\r
+ /**\r
+ * \r
+ * @param name the name of the scene element. This is required for\r
+ * identification and comparison purposes.\r
+ * @param patchSize size of the individual patches\r
+ * @param totalSize the size of this entire terrain tree (on one side)\r
+ * @param quadSize\r
+ * @param scale\r
+ * @param heightMap The height map to generate the terrain from (a flat\r
+ * height map will be generated if this is null)\r
+ */\r
+ @Deprecated\r
public TerrainQuad(String name, int patchSize, int totalSize, int quadSize, Vector3f scale, float[] heightMap) {\r
this(name, patchSize, quadSize, scale, heightMap, totalSize, new Vector2f(), 0);\r
- affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);\r
- fixNormalEdges(affectedAreaBBox);\r
- addControl(new NormalRecalcControl(this));\r
+ //affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);\r
+ //fixNormalEdges(affectedAreaBBox);\r
+ //addControl(new NormalRecalcControl(this));\r
}\r
\r
protected TerrainQuad(String name, int patchSize, int quadSize,\r
this.size = quadSize;\r
this.patchSize = patchSize;\r
this.stepScale = scale;\r
- //this.lodCalculatorFactory = lodCalculatorFactory;\r
- //this.lodCalculator = lodCalculator;\r
split(patchSize, heightMap);\r
}\r
\r
- /*public void setLodCalculatorFactory(LodCalculatorFactory lodCalculatorFactory) {\r
- if (children != null) {\r
- for (int i = children.size(); --i >= 0;) {\r
- Spatial child = children.get(i);\r
- if (child instanceof TerrainQuad) {\r
- ((TerrainQuad) child).setLodCalculatorFactory(lodCalculatorFactory);\r
- } else if (child instanceof TerrainPatch) {\r
- ((TerrainPatch) child).setLodCalculator(lodCalculatorFactory.createCalculator((TerrainPatch) child));\r
- }\r
- }\r
- }\r
- }*/\r
-\r
+ public void setNeighbourFinder(NeighbourFinder neighbourFinder) {\r
+ this.neighbourFinder = neighbourFinder;\r
+ resetCachedNeighbours();\r
+ }\r
\r
/**\r
+ * Forces the recalculation of all normals on the terrain.\r
+ */\r
+ public void recalculateAllNormals() {\r
+ affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), totalSize*2, Float.MAX_VALUE, totalSize*2);\r
+ }\r
+ \r
+ /**\r
* Create just a flat heightmap\r
*/\r
private float[] generateDefaultHeightMap(int size) {\r
return heightMap;\r
}\r
\r
- /**\r
- * Call from the update() method of a terrain controller to update\r
- * the LOD values of each patch.\r
- * This will perform the geometry calculation in a background thread and\r
- * do the actual update on the opengl thread.\r
- */\r
- public void update(List<Vector3f> locations, LodCalculator lodCalculator) {\r
- updateLOD(locations, lodCalculator);\r
- }\r
-\r
/**\r
* update the normals if there were any height changes recently.\r
* Should only be called on the root quad\r
setNormalRecalcNeeded(null); // set to false\r
}\r
}\r
-\r
- // do all of the LOD calculations\r
- protected void updateLOD(List<Vector3f> locations, LodCalculator lodCalculator) {\r
- // update any existing ones that need updating\r
- updateQuadLODs();\r
-\r
- if (lodCalculator.isLodOff()) {\r
- // we want to calculate the base lod at least once\r
- if (lodOffCount == 1)\r
- return;\r
- else\r
- lodOffCount++;\r
- } else \r
- lodOffCount = 0;\r
- \r
- if (lastCameraLocations != null) {\r
- if (lastCameraLocationsTheSame(locations) && !lodCalculator.isLodOff())\r
- return; // don't update if in same spot\r
- else\r
- lastCameraLocations = cloneVectorList(locations);\r
- }\r
- else {\r
- lastCameraLocations = cloneVectorList(locations);\r
- return;\r
- }\r
-\r
- if (isLodCalcRunning()) {\r
- return;\r
- }\r
-\r
- if (getParent() instanceof TerrainQuad) {\r
- return; // we just want the root quad to perform this.\r
- }\r
-\r
- if (executor == null)\r
- executor = createExecutorService();\r
- \r
- UpdateLOD updateLodThread = new UpdateLOD(locations, lodCalculator);\r
- executor.execute(updateLodThread);\r
- }\r
-\r
- private synchronized boolean isLodCalcRunning() {\r
- return lodCalcRunning;\r
- }\r
-\r
- private synchronized void setLodCalcRunning(boolean running) {\r
- lodCalcRunning = running;\r
- }\r
-\r
- private List<Vector3f> cloneVectorList(List<Vector3f> locations) {\r
- List<Vector3f> cloned = new ArrayList<Vector3f>();\r
- for(Vector3f l : locations)\r
- cloned.add(l.clone());\r
- return cloned;\r
- }\r
-\r
- private boolean lastCameraLocationsTheSame(List<Vector3f> locations) {\r
- boolean theSame = true;\r
- for (Vector3f l : locations) {\r
- for (Vector3f v : lastCameraLocations) {\r
- if (!v.equals(l) ) {\r
- theSame = false;\r
- return false;\r
- }\r
+ \r
+ /**\r
+ * Caches the transforms (except rotation) so the LOD calculator,\r
+ * which runs on a separate thread, can access them safely.\r
+ */\r
+ protected void cacheTerrainTransforms() {\r
+ for (int i = children.size(); --i >= 0;) {\r
+ Spatial child = children.get(i);\r
+ if (child instanceof TerrainQuad) {\r
+ ((TerrainQuad) child).cacheTerrainTransforms();\r
+ } else if (child instanceof TerrainPatch) {\r
+ ((TerrainPatch) child).cacheTerrainTransforms();\r
}\r
}\r
- return theSame;\r
}\r
\r
private int collideWithRay(Ray ray, CollisionResults results) {\r
picker = new BresenhamTerrainPicker(this);\r
\r
Vector3f intersection = picker.getTerrainIntersection(ray, results);\r
- if (intersection != null)\r
- return 1;\r
- else\r
+ if (intersection != null) {\r
+ if (ray.getLimit() < Float.POSITIVE_INFINITY) {\r
+ if (results.getClosestCollision().getDistance() <= ray.getLimit())\r
+ return 1; // in range\r
+ else\r
+ return 0; // out of range\r
+ } else\r
+ return 1;\r
+ } else\r
return 0;\r
}\r
\r
+ /**\r
+ * Generate the entropy values for the terrain for the "perspective" LOD\r
+ * calculator. This routine can take a long time to run!\r
+ * @param progressMonitor optional\r
+ */\r
public void generateEntropy(ProgressMonitor progressMonitor) {\r
// only check this on the root quad\r
if (isRootQuad())\r
}\r
\r
public Material getMaterial() {\r
+ return getMaterial(null);\r
+ }\r
+ \r
+ public Material getMaterial(Vector3f worldLocation) {\r
// get the material from one of the children. They all share the same material\r
if (children != null) {\r
for (int i = children.size(); --i >= 0;) {\r
Spatial child = children.get(i);\r
if (child instanceof TerrainQuad) {\r
- return ((TerrainQuad)child).getMaterial();\r
+ return ((TerrainQuad)child).getMaterial(worldLocation);\r
} else if (child instanceof TerrainPatch) {\r
return ((TerrainPatch)child).getMaterial();\r
}\r
return null;\r
}\r
\r
- public float getTextureCoordinateScale() {\r
- return 1f/(float)totalSize;\r
- }\r
-\r
- /**\r
- * Calculates the LOD of all child terrain patches.\r
- */\r
- private class UpdateLOD implements Runnable {\r
- private List<Vector3f> camLocations;\r
- private LodCalculator lodCalculator;\r
-\r
- UpdateLOD(List<Vector3f> camLocations, LodCalculator lodCalculator) {\r
- this.camLocations = camLocations;\r
- this.lodCalculator = lodCalculator;\r
- }\r
-\r
- public void run() {\r
- long start = System.currentTimeMillis();\r
- if (isLodCalcRunning()) {\r
- //System.out.println("thread already running");\r
- return;\r
- }\r
- //System.out.println("spawned thread "+toString());\r
- setLodCalcRunning(true);\r
-\r
- // go through each patch and calculate its LOD based on camera distance\r
- HashMap<String,UpdatedTerrainPatch> updated = new HashMap<String,UpdatedTerrainPatch>();\r
- boolean lodChanged = calculateLod(camLocations, updated, lodCalculator); // 'updated' gets populated here\r
-\r
- if (!lodChanged) {\r
- // not worth updating anything else since no one's LOD changed\r
- setLodCalcRunning(false);\r
- return;\r
- }\r
- // then calculate its neighbour LOD values for seaming in the shader\r
- findNeighboursLod(updated);\r
-\r
- fixEdges(updated); // 'updated' can get added to here\r
-\r
- reIndexPages(updated, lodCalculator.usesVariableLod());\r
-\r
- setUpdateQuadLODs(updated); // set back to main ogl thread\r
-\r
- setLodCalcRunning(false);\r
- //double duration = (System.currentTimeMillis()-start);\r
- //System.out.println("terminated in "+duration);\r
- }\r
- }\r
-\r
- private void setUpdateQuadLODs(HashMap<String,UpdatedTerrainPatch> updated) {\r
- synchronized (updatePatchesLock) {\r
- updatedPatches = updated;\r
- }\r
- }\r
-\r
- /**\r
- * Back on the ogl thread: update the terrain patch geometries\r
- * @param updatedPatches to be updated\r
- */\r
- private void updateQuadLODs() {\r
- synchronized (updatePatchesLock) {\r
- \r
- if (updatedPatches == null || updatedPatches.isEmpty())\r
- return;\r
-\r
- // do the actual geometry update here\r
- for (UpdatedTerrainPatch utp : updatedPatches.values()) {\r
- utp.updateAll();\r
- }\r
-\r
- updatedPatches.clear();\r
- }\r
+ public int getNumMajorSubdivisions() {\r
+ return 1;\r
}\r
\r
- public boolean hasPatchesToUpdate() {\r
- return updatedPatches != null && !updatedPatches.isEmpty();\r
- }\r
\r
protected boolean calculateLod(List<Vector3f> location, HashMap<String,UpdatedTerrainPatch> updates, LodCalculator lodCalculator) {\r
\r
}\r
TerrainPatch right = patch.rightNeighbour;\r
TerrainPatch down = patch.bottomNeighbour;\r
+ TerrainPatch left = patch.leftNeighbour;\r
+ TerrainPatch top = patch.topNeighbour;\r
\r
UpdatedTerrainPatch utp = updated.get(patch.getName());\r
if (utp == null) {\r
if (right != null) {\r
UpdatedTerrainPatch utpR = updated.get(right.getName());\r
if (utpR == null) {\r
- utpR = new UpdatedTerrainPatch(right, right.lod);\r
+ utpR = new UpdatedTerrainPatch(right);\r
updated.put(utpR.getName(), utpR);\r
+ utpR.setNewLod(right.lod);\r
}\r
-\r
utp.setRightLod(utpR.getNewLod());\r
utpR.setLeftLod(utp.getNewLod());\r
}\r
if (down != null) {\r
UpdatedTerrainPatch utpD = updated.get(down.getName());\r
if (utpD == null) {\r
- utpD = new UpdatedTerrainPatch(down, down.lod);\r
+ utpD = new UpdatedTerrainPatch(down);\r
updated.put(utpD.getName(), utpD);\r
+ utpD.setNewLod(down.lod);\r
}\r
-\r
utp.setBottomLod(utpD.getNewLod());\r
utpD.setTopLod(utp.getNewLod());\r
}\r
-\r
+ \r
+ if (left != null) {\r
+ UpdatedTerrainPatch utpL = updated.get(left.getName());\r
+ if (utpL == null) {\r
+ utpL = new UpdatedTerrainPatch(left);\r
+ updated.put(utpL.getName(), utpL);\r
+ utpL.setNewLod(left.lod);\r
+ }\r
+ utp.setLeftLod(utpL.getNewLod());\r
+ utpL.setRightLod(utp.getNewLod());\r
+ }\r
+ if (top != null) {\r
+ UpdatedTerrainPatch utpT = updated.get(top.getName());\r
+ if (utpT == null) {\r
+ utpT = new UpdatedTerrainPatch(top);\r
+ updated.put(utpT.getName(), utpT);\r
+ utpT.setNewLod(top.lod);\r
+ }\r
+ utp.setTopLod(utpT.getNewLod());\r
+ utpT.setBottomLod(utp.getNewLod());\r
+ }\r
}\r
}\r
}\r
}\r
\r
/**\r
+ * Reset the cached references of neighbours.\r
+ * TerrainQuad caches neighbours for faster LOD checks.\r
+ * Sometimes you might want to reset this cache (for instance in TerrainGrid)\r
+ */\r
+ public void resetCachedNeighbours() {\r
+ if (children != null) {\r
+ for (int x = children.size(); --x >= 0;) {\r
+ Spatial child = children.get(x);\r
+ if (child instanceof TerrainQuad) {\r
+ ((TerrainQuad) child).resetCachedNeighbours();\r
+ } else if (child instanceof TerrainPatch) {\r
+ TerrainPatch patch = (TerrainPatch) child;\r
+ patch.searchedForNeighboursAlready = false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ \r
+ /**\r
* Find any neighbours that should have their edges seamed because another neighbour\r
* changed its LOD to a greater value (less detailed)\r
*/\r
TerrainPatch patch = (TerrainPatch) child;\r
UpdatedTerrainPatch utp = updated.get(patch.getName());\r
\r
- if(utp.lodChanged()) {\r
+ if(utp != null && utp.lodChanged()) {\r
if (!patch.searchedForNeighboursAlready) {\r
// set the references to the neighbours\r
patch.rightNeighbour = findRightPatch(patch);\r
if (right != null) {\r
UpdatedTerrainPatch utpR = updated.get(right.getName());\r
if (utpR == null) {\r
- utpR = new UpdatedTerrainPatch(right, right.lod);\r
+ utpR = new UpdatedTerrainPatch(right);\r
updated.put(utpR.getName(), utpR);\r
+ utpR.setNewLod(right.lod);\r
}\r
+ utpR.setLeftLod(utp.getNewLod());\r
utpR.setFixEdges(true);\r
}\r
if (down != null) {\r
UpdatedTerrainPatch utpD = updated.get(down.getName());\r
if (utpD == null) {\r
- utpD = new UpdatedTerrainPatch(down, down.lod);\r
+ utpD = new UpdatedTerrainPatch(down);\r
updated.put(utpD.getName(), utpD);\r
+ utpD.setNewLod(down.lod);\r
}\r
+ utpD.setTopLod(utp.getNewLod());\r
utpD.setFixEdges(true);\r
}\r
if (top != null){\r
UpdatedTerrainPatch utpT = updated.get(top.getName());\r
if (utpT == null) {\r
- utpT = new UpdatedTerrainPatch(top, top.lod);\r
+ utpT = new UpdatedTerrainPatch(top);\r
updated.put(utpT.getName(), utpT);\r
+ utpT.setNewLod(top.lod);\r
}\r
+ utpT.setBottomLod(utp.getNewLod());\r
utpT.setFixEdges(true);\r
}\r
if (left != null){\r
UpdatedTerrainPatch utpL = updated.get(left.getName());\r
if (utpL == null) {\r
- utpL = new UpdatedTerrainPatch(left, left.lod);\r
+ utpL = new UpdatedTerrainPatch(left);\r
updated.put(utpL.getName(), utpL);\r
+ utpL.setNewLod(left.lod);\r
}\r
+ utpL.setRightLod(utp.getNewLod());\r
utpL.setFixEdges(true);\r
}\r
}\r
\r
/**\r
* Quadrants, world coordinates, and heightmap coordinates (Y-up):\r
- * x\r
- * | u\r
- * 2|4 v\r
- * -z ----+---- z\r
- * -v 1|3\r
- * -u |\r
- * -x\r
+ * \r
+ * -z\r
+ * -u | \r
+ * -v 1|3 \r
+ * -x ----+---- x\r
+ * 2|4 u\r
+ * | v\r
+ * z\r
* <code>createQuad</code> generates four new quads from this quad.\r
* The heightmap's top left (0,0) coordinate is at the bottom, -x,-z\r
* coordinate of the terrain, so it grows in the positive x.z direction.\r
//if (lodCalculator == null)\r
// lodCalculator = createDefaultLodCalculator(); // set a default one\r
\r
- // 1 upper left of heightmap, lower left quad\r
+ // 1 upper left of heightmap, upper left quad\r
float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);\r
\r
Vector3f origin1 = new Vector3f(-quarterSize * stepScale.x, 0,\r
quad1.quadrant = 1;\r
this.attachChild(quad1);\r
\r
- // 2 lower left of heightmap, upper left quad\r
+ // 2 lower left of heightmap, lower left quad\r
float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,\r
split);\r
\r
quad2.quadrant = 2;\r
this.attachChild(quad2);\r
\r
- // 3 upper right of heightmap, lower right quad\r
+ // 3 upper right of heightmap, upper right quad\r
float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,\r
split);\r
\r
quad3.quadrant = 3;\r
this.attachChild(quad3);\r
\r
- // 4 lower right of heightmap, upper right quad\r
+ // 4 lower right of heightmap, lower right quad\r
float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,\r
split - 1, split);\r
\r
\r
offsetAmount += quarterSize;\r
\r
- // 1 upper left\r
+ // 1 lower left\r
float[] heightBlock1 = createHeightSubBlock(heightMap, 0, 0, split);\r
\r
Vector3f origin1 = new Vector3f(-halfSize * stepScale.x, 0, -halfSize\r
//patch1.setLodCalculator(lodCalculator);\r
//TangentBinormalGenerator.generate(patch1);\r
\r
- // 2 lower left\r
+ // 2 upper left\r
float[] heightBlock2 = createHeightSubBlock(heightMap, 0, split - 1,\r
split);\r
\r
//patch2.setLodCalculator(lodCalculator);\r
//TangentBinormalGenerator.generate(patch2);\r
\r
- // 3 upper right\r
+ // 3 lower right\r
float[] heightBlock3 = createHeightSubBlock(heightMap, split - 1, 0,\r
split);\r
\r
//patch3.setLodCalculator(lodCalculator);\r
//TangentBinormalGenerator.generate(patch3);\r
\r
- // 4 lower right\r
+ // 4 upper right\r
float[] heightBlock4 = createHeightSubBlock(heightMap, split - 1,\r
split - 1, split);\r
\r
}\r
\r
if (affectedAreaBBox == null) {\r
- affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 0.5f, Float.MAX_VALUE, 0.5f); // unit length\r
+ affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length\r
} else {\r
// adjust size of box to be larger\r
- affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 0.5f, Float.MAX_VALUE, 0.5f));\r
+ affectedAreaBBox.mergeLocal(new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f));\r
}\r
}\r
\r
if (affectedAreaBBox != null)\r
return true;\r
if (!lastScale.equals(getWorldScale())) {\r
- affectedAreaBBox = new BoundingBox(new Vector3f(0,0,0), size, Float.MAX_VALUE, size);\r
+ affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);\r
lastScale = getWorldScale();\r
return true;\r
}\r
return false;\r
}\r
+ \r
+ /**\r
+ * This will cause all normals for this terrain quad to be recalculated\r
+ */\r
+ protected void setNeedToRecalculateNormals() {\r
+ affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE);\r
+ }\r
\r
public float getHeightmapHeight(Vector2f xz) {\r
// offset\r
int x = Math.round((xz.x / getWorldScale().x) + halfSize);\r
int z = Math.round((xz.y / getWorldScale().z) + halfSize);\r
\r
+ if (!isInside(x, z))\r
+ return Float.NaN;\r
return getHeightmapHeight(x, z);\r
}\r
\r
return null;\r
}\r
\r
+ /**\r
+ * is the 2d point inside the terrain?\r
+ * @param x local coordinate\r
+ * @param z local coordinate\r
+ */\r
+ private boolean isInside(int x, int z) {\r
+ if (x < 0 || z < 0 || x > totalSize || z > totalSize)\r
+ return false;\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Used for searching for a child and keeping\r
+ * track of its quadrant\r
+ */\r
+ private class QuadrantChild {\r
+ int col;\r
+ int row;\r
+ Spatial child;\r
+ \r
+ QuadrantChild(int col, int row, Spatial child) {\r
+ this.col = col;\r
+ this.row = row;\r
+ this.child = child;\r
+ }\r
+ }\r
+ \r
+ private QuadrantChild findMatchingChild(int x, int z) {\r
+ int quad = findQuadrant(x, z);\r
+ int split = (size + 1) >> 1;\r
+ if (children != null) {\r
+ for (int i = children.size(); --i >= 0;) {\r
+ Spatial spat = children.get(i);\r
+ int col = x;\r
+ int row = z;\r
+ boolean match = false;\r
+\r
+ // get the childs quadrant\r
+ int childQuadrant = 0;\r
+ if (spat instanceof TerrainQuad) {\r
+ childQuadrant = ((TerrainQuad) spat).getQuadrant();\r
+ } else if (spat instanceof TerrainPatch) {\r
+ childQuadrant = ((TerrainPatch) spat).getQuadrant();\r
+ }\r
+\r
+ if (childQuadrant == 1 && (quad & 1) != 0) {\r
+ match = true;\r
+ } else if (childQuadrant == 2 && (quad & 2) != 0) {\r
+ row = z - split + 1;\r
+ match = true;\r
+ } else if (childQuadrant == 3 && (quad & 4) != 0) {\r
+ col = x - split + 1;\r
+ match = true;\r
+ } else if (childQuadrant == 4 && (quad & 8) != 0) {\r
+ col = x - split + 1;\r
+ row = z - split + 1;\r
+ match = true;\r
+ }\r
+ if (match)\r
+ return new QuadrantChild(col, row, spat);\r
+ }\r
+ }\r
+ return null;\r
+ }\r
+ \r
+ /**\r
+ * Get the interpolated height of the terrain at the specified point.\r
+ * @param xz the location to get the height for\r
+ * @return Float.NAN if the value does not exist, or the coordinates are outside of the terrain\r
+ */\r
public float getHeight(Vector2f xz) {\r
// offset\r
- float x = (float)(((xz.x - getLocalTranslation().x) / getWorldScale().x) + (float)totalSize / 2f);\r
- float z = (float)(((xz.y - getLocalTranslation().z) / getWorldScale().z) + (float)totalSize / 2f);\r
- float height = getHeight(x, z);\r
+ float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f);\r
+ float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f);\r
+ if (!isInside((int)x, (int)z))\r
+ return Float.NaN;\r
+ float height = getHeight((int)x, (int)z, (x%1f), (z%1f));\r
height *= getWorldScale().y;\r
return height;\r
}\r
\r
/*\r
* gets an interpolated value at the specified point\r
- * @param x coordinate translated into actual (positive) terrain grid coordinates\r
- * @param y coordinate translated into actual (positive) terrain grid coordinates\r
*/\r
- protected float getHeight(float x, float z) {\r
- x-=0.5f;\r
- z-=0.5f;\r
- float col = FastMath.floor(x);\r
- float row = FastMath.floor(z);\r
- boolean onX = false;\r
- if(1 - (x - col)-(z - row) < 0) // what triangle to interpolate on\r
- onX = true;\r
- // v1--v2 ^\r
- // | / | |\r
- // | / | |\r
- // v3--v4 | Z\r
- // |\r
- // <-------Y\r
- // X \r
- float v1 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.ceil(z));\r
- float v2 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.ceil(z));\r
- float v3 = getHeightmapHeight((int) FastMath.ceil(x), (int) FastMath.floor(z));\r
- float v4 = getHeightmapHeight((int) FastMath.floor(x), (int) FastMath.floor(z));\r
- if (onX) {\r
- return ((x - col) + (z - row) - 1f)*v1 + (1f - (x - col))*v2 + (1f - (z - row))*v3;\r
- } else {\r
- return (1f - (x - col) - (z - row))*v4 + (z - row)*v2 + (x - col)*v3;\r
+ protected float getHeight(int x, int z, float xm, float zm) {\r
+ \r
+ QuadrantChild match = findMatchingChild(x,z);\r
+ if (match != null) {\r
+ if (match.child instanceof TerrainQuad) {\r
+ return ((TerrainQuad) match.child).getHeight(match.col, match.row, xm, zm);\r
+ } else if (match.child instanceof TerrainPatch) {\r
+ return ((TerrainPatch) match.child).getHeight(match.col, match.row, xm, zm);\r
+ }\r
}\r
+ return Float.NaN;\r
}\r
\r
public Vector3f getNormal(Vector2f xz) {\r
// offset\r
- float x = (float)(((xz.x - getLocalTranslation().x) / getWorldScale().x) + (float)totalSize / 2f);\r
- float z = (float)(((xz.y - getLocalTranslation().z) / getWorldScale().z) + (float)totalSize / 2f);\r
+ float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f);\r
+ float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f);\r
Vector3f normal = getNormal(x, z, xz);\r
\r
return normal;\r
for (int i=0; i<xz.size(); i++) {\r
int x = Math.round((xz.get(i).x / getWorldScale().x) + halfSize);\r
int z = Math.round((xz.get(i).y / getWorldScale().z) + halfSize);\r
+ if (!isInside(x, z))\r
+ continue;\r
locations.add(new LocationHeight(x,z,height.get(i)));\r
}\r
\r
// distribute each locationHeight into the quadrant it intersects\r
for (LocationHeight lh : locations) {\r
int quad = findQuadrant(lh.x, lh.z);\r
-\r
int col = lh.x;\r
int row = lh.z;\r
\r
}\r
\r
protected TerrainQuad getQuad(int quad) {\r
+ if (quad == 0)\r
+ return this;\r
if (children != null)\r
for (int x = children.size(); --x >= 0;) {\r
Spatial child = children.get(x);\r
else if (tp.getQuadrant() == 4)\r
return getPatch(2);\r
else if (tp.getQuadrant() == 1) {\r
- // find the patch above and ask it for child 2.\r
+ // find the patch above and ask it for child 3.\r
TerrainQuad quad = findLeftQuad();\r
if (quad != null)\r
return quad.getPatch(3);\r
}\r
\r
protected TerrainQuad findRightQuad() {\r
- if (getParent() == null || !(getParent() instanceof TerrainQuad))\r
- return null;\r
+ boolean useFinder = false;\r
+ if (getParent() == null || !(getParent() instanceof TerrainQuad)) {\r
+ if (neighbourFinder == null)\r
+ return null;\r
+ else\r
+ useFinder = true;\r
+ }\r
\r
- TerrainQuad pQuad = (TerrainQuad) getParent();\r
+ TerrainQuad pQuad = null;\r
+ if (!useFinder)\r
+ pQuad = (TerrainQuad) getParent();\r
\r
if (quadrant == 1)\r
return pQuad.getQuad(3);\r
TerrainQuad quad = pQuad.findRightQuad();\r
if (quad != null)\r
return quad.getQuad(2);\r
+ } else if (quadrant == 0) {\r
+ // at the top quad\r
+ if (useFinder) {\r
+ TerrainQuad quad = neighbourFinder.getRightQuad(this);\r
+ return quad;\r
+ }\r
}\r
\r
return null;\r
}\r
\r
protected TerrainQuad findDownQuad() {\r
- if (getParent() == null || !(getParent() instanceof TerrainQuad))\r
- return null;\r
+ boolean useFinder = false;\r
+ if (getParent() == null || !(getParent() instanceof TerrainQuad)) {\r
+ if (neighbourFinder == null)\r
+ return null;\r
+ else\r
+ useFinder = true;\r
+ }\r
\r
- TerrainQuad pQuad = (TerrainQuad) getParent();\r
+ TerrainQuad pQuad = null;\r
+ if (!useFinder)\r
+ pQuad = (TerrainQuad) getParent();\r
\r
if (quadrant == 1)\r
return pQuad.getQuad(2);\r
TerrainQuad quad = pQuad.findDownQuad();\r
if (quad != null)\r
return quad.getQuad(3);\r
+ } else if (quadrant == 0) {\r
+ // at the top quad\r
+ if (useFinder) {\r
+ TerrainQuad quad = neighbourFinder.getDownQuad(this);\r
+ return quad;\r
+ }\r
}\r
\r
return null;\r
}\r
\r
protected TerrainQuad findTopQuad() {\r
- if (getParent() == null || !(getParent() instanceof TerrainQuad))\r
- return null;\r
+ boolean useFinder = false;\r
+ if (getParent() == null || !(getParent() instanceof TerrainQuad)) {\r
+ if (neighbourFinder == null)\r
+ return null;\r
+ else\r
+ useFinder = true;\r
+ }\r
\r
- TerrainQuad pQuad = (TerrainQuad) getParent();\r
+ TerrainQuad pQuad = null;\r
+ if (!useFinder)\r
+ pQuad = (TerrainQuad) getParent();\r
\r
if (quadrant == 2)\r
return pQuad.getQuad(1);\r
TerrainQuad quad = pQuad.findTopQuad();\r
if (quad != null)\r
return quad.getQuad(4);\r
+ } else if (quadrant == 0) {\r
+ // at the top quad\r
+ if (useFinder) {\r
+ TerrainQuad quad = neighbourFinder.getTopQuad(this);\r
+ return quad;\r
+ }\r
}\r
\r
return null;\r
}\r
\r
protected TerrainQuad findLeftQuad() {\r
- if (getParent() == null || !(getParent() instanceof TerrainQuad))\r
- return null;\r
+ boolean useFinder = false;\r
+ if (getParent() == null || !(getParent() instanceof TerrainQuad)) {\r
+ if (neighbourFinder == null)\r
+ return null;\r
+ else\r
+ useFinder = true;\r
+ }\r
\r
- TerrainQuad pQuad = (TerrainQuad) getParent();\r
+ TerrainQuad pQuad = null;\r
+ if (!useFinder)\r
+ pQuad = (TerrainQuad) getParent();\r
\r
if (quadrant == 3)\r
return pQuad.getQuad(1);\r
TerrainQuad quad = pQuad.findLeftQuad();\r
if (quad != null)\r
return quad.getQuad(4);\r
+ } else if (quadrant == 0) {\r
+ // at the top quad\r
+ if (useFinder) {\r
+ TerrainQuad quad = neighbourFinder.getLeftQuad(this);\r
+ return quad;\r
+ }\r
}\r
\r
return null;\r
}\r
}\r
}\r
- else\r
+ else if (children.get(i) instanceof TerrainQuad) {\r
((TerrainQuad) children.get(i)).findPick(toTest, results);\r
+ }\r
}\r
}\r
}\r
quadClone.name = name.toString();\r
quadClone.size = size;\r
quadClone.totalSize = totalSize;\r
- quadClone.stepScale = stepScale.clone();\r
- quadClone.offset = offset.clone();\r
+ if (stepScale != null) {\r
+ quadClone.stepScale = stepScale.clone();\r
+ }\r
+ if (offset != null) {\r
+ quadClone.offset = offset.clone();\r
+ }\r
quadClone.offsetAmount = offsetAmount;\r
quadClone.quadrant = quadrant;\r
//quadClone.lodCalculatorFactory = lodCalculatorFactory.clone();\r
\r
return quadClone;\r
}\r
+ \r
+ @Override\r
+ protected void setParent(Node parent) {\r
+ super.setParent(parent);\r
+ if (parent == null) {\r
+ // if the terrain is being detached\r
+ clearCaches();\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Removes any cached references this terrain is holding, in particular\r
+ * the TerrainPatch's neighbour references.\r
+ * This is called automatically when the root terrainQuad is detached from\r
+ * its parent or if setParent(null) is called.\r
+ */\r
+ public void clearCaches() {\r
+ if (children != null) {\r
+ for (int i = children.size(); --i >= 0;) {\r
+ Spatial child = children.get(i);\r
+ if (child instanceof TerrainQuad) {\r
+ ((TerrainQuad) child).clearCaches();\r
+ } else if (child instanceof TerrainPatch) {\r
+ ((TerrainPatch) child).clearCaches();\r
+ }\r
+ }\r
+ }\r
+ }\r
\r
public int getMaxLod() {\r
if (maxLod < 0)\r
if (getChild(0) instanceof TerrainPatch) {\r
for (Spatial s : getChildren()) {\r
if ( ((TerrainPatch)s).getQuadrant() == 1)\r
- ul = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap());\r
+ ul = ((TerrainPatch)s).getHeightMap();\r
else if(((TerrainPatch) s).getQuadrant() == 2)\r
- bl = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap());\r
+ bl = ((TerrainPatch)s).getHeightMap();\r
else if(((TerrainPatch) s).getQuadrant() == 3)\r
- ur = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap());\r
+ ur = ((TerrainPatch)s).getHeightMap();\r
else if(((TerrainPatch) s).getQuadrant() == 4)\r
- br = BufferUtils.getFloatArray(((TerrainPatch)s).getHeightmap());\r
+ br = ((TerrainPatch)s).getHeightMap();\r
}\r
}\r
else {\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.geomipmap;\r
\r
-import java.nio.IntBuffer;\r
-\r
import com.jme3.scene.VertexBuffer.Type;\r
+import java.nio.Buffer;\r
+import java.nio.IntBuffer;\r
+import java.nio.ShortBuffer;\r
\r
/**\r
* Stores a terrain patch's details so the LOD background thread can update\r
*/\r
public class UpdatedTerrainPatch {\r
\r
- private TerrainPatch updatedPatch;\r
- private int newLod;\r
- private int previousLod;\r
- private int rightLod,topLod,leftLod,bottomLod;\r
- private IntBuffer newIndexBuffer;\r
- private boolean reIndexNeeded = false;\r
- private boolean fixEdges = false;\r
-\r
- public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {\r
- this.updatedPatch = updatedPatch;\r
- this.newLod = newLod;\r
- }\r
-\r
- public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod, int prevLOD, boolean reIndexNeeded) {\r
- this.updatedPatch = updatedPatch;\r
- this.newLod = newLod;\r
- this.previousLod = prevLOD;\r
- this.reIndexNeeded = reIndexNeeded;\r
- if (this.newLod <= 0)\r
- throw new IllegalArgumentException();\r
- }\r
-\r
- public String getName() {\r
- return updatedPatch.getName();\r
- }\r
-\r
- protected boolean lodChanged() {\r
- if (reIndexNeeded && previousLod != newLod)\r
- return true;\r
- else\r
- return false;\r
- }\r
-\r
- protected TerrainPatch getUpdatedPatch() {\r
- return updatedPatch;\r
- }\r
-\r
- protected void setUpdatedPatch(TerrainPatch updatedPatch) {\r
- this.updatedPatch = updatedPatch;\r
- }\r
-\r
- protected int getNewLod() {\r
- return newLod;\r
- }\r
-\r
- public void setNewLod(int newLod) {\r
- this.newLod = newLod;\r
- if (this.newLod < 0)\r
- throw new IllegalArgumentException();\r
- }\r
-\r
- protected IntBuffer getNewIndexBuffer() {\r
- return newIndexBuffer;\r
- }\r
-\r
- protected void setNewIndexBuffer(IntBuffer newIndexBuffer) {\r
- this.newIndexBuffer = newIndexBuffer;\r
- }\r
-\r
-\r
- protected int getRightLod() {\r
- return rightLod;\r
- }\r
-\r
-\r
- protected void setRightLod(int rightLod) {\r
- this.rightLod = rightLod;\r
- }\r
-\r
-\r
- protected int getTopLod() {\r
- return topLod;\r
- }\r
-\r
-\r
- protected void setTopLod(int topLod) {\r
- this.topLod = topLod;\r
- }\r
-\r
-\r
- protected int getLeftLod() {\r
- return leftLod;\r
- }\r
-\r
-\r
- protected void setLeftLod(int leftLod) {\r
- this.leftLod = leftLod;\r
- }\r
-\r
-\r
- protected int getBottomLod() {\r
- return bottomLod;\r
- }\r
-\r
-\r
- protected void setBottomLod(int bottomLod) {\r
- this.bottomLod = bottomLod;\r
- }\r
-\r
- public boolean isReIndexNeeded() {\r
- return reIndexNeeded;\r
- }\r
-\r
- public void setReIndexNeeded(boolean reIndexNeeded) {\r
- this.reIndexNeeded = reIndexNeeded;\r
- }\r
+ private TerrainPatch updatedPatch;\r
+ private int newLod;\r
+ private int previousLod;\r
+ private int rightLod,topLod,leftLod,bottomLod;\r
+ private Buffer newIndexBuffer;\r
+ //private boolean reIndexNeeded = false;\r
+ private boolean fixEdges = false;\r
\r
- public boolean isFixEdges() {\r
- return fixEdges;\r
- }\r
+ public UpdatedTerrainPatch(TerrainPatch updatedPatch) {\r
+ this.updatedPatch = updatedPatch;\r
+ }\r
\r
- public void setFixEdges(boolean fixEdges) {\r
- this.fixEdges = fixEdges;\r
- }\r
+ public UpdatedTerrainPatch(TerrainPatch updatedPatch, int newLod) {\r
+ this.updatedPatch = updatedPatch;\r
+ this.newLod = newLod;\r
+ }\r
\r
- public int getPreviousLod() {\r
- return previousLod;\r
- }\r
+ public String getName() {\r
+ return updatedPatch.getName();\r
+ }\r
\r
- public void setPreviousLod(int previousLod) {\r
- this.previousLod = previousLod;\r
- }\r
+ protected boolean lodChanged() {\r
+ if ( previousLod != newLod)\r
+ return true;\r
+ else\r
+ return false;\r
+ }\r
\r
- public void updateAll() {\r
- updatedPatch.setLod(newLod);\r
- updatedPatch.setLodRight(rightLod);\r
- updatedPatch.setLodTop(topLod);\r
- updatedPatch.setLodLeft(leftLod);\r
- updatedPatch.setLodBottom(bottomLod);\r
- if (newIndexBuffer != null && (reIndexNeeded || fixEdges)) {\r
- updatedPatch.setPreviousLod(previousLod);\r
- updatedPatch.getMesh().clearBuffer(Type.Index);\r
- updatedPatch.getMesh().setBuffer(Type.Index, 3, newIndexBuffer);\r
- }\r
- }\r
+ protected TerrainPatch getUpdatedPatch() {\r
+ return updatedPatch;\r
+ }\r
\r
+ protected void setUpdatedPatch(TerrainPatch updatedPatch) {\r
+ this.updatedPatch = updatedPatch;\r
+ }\r
+\r
+ protected int getNewLod() {\r
+ return newLod;\r
+ }\r
+ \r
+ public void setNewLod(int newLod) {\r
+ this.newLod = newLod;\r
+ if (this.newLod < 0)\r
+ throw new IllegalArgumentException("newLod cannot be less than zero, was: "+newLod);\r
+ }\r
+\r
+ /*protected IntBuffer getNewIndexBuffer() {\r
+ return newIndexBuffer;\r
+ }*/\r
+\r
+ protected void setNewIndexBuffer(Buffer newIndexBuffer) {\r
+ this.newIndexBuffer = newIndexBuffer;\r
+ }\r
+\r
+\r
+ protected int getRightLod() {\r
+ return rightLod;\r
+ }\r
+\r
+\r
+ protected void setRightLod(int rightLod) {\r
+ this.rightLod = rightLod;\r
+ }\r
+\r
+\r
+ protected int getTopLod() {\r
+ return topLod;\r
+ }\r
+\r
+\r
+ protected void setTopLod(int topLod) {\r
+ this.topLod = topLod;\r
+ }\r
+\r
+\r
+ protected int getLeftLod() {\r
+ return leftLod;\r
+ }\r
+\r
+\r
+ protected void setLeftLod(int leftLod) {\r
+ this.leftLod = leftLod;\r
+ }\r
+\r
+\r
+ protected int getBottomLod() {\r
+ return bottomLod;\r
+ }\r
+\r
+\r
+ protected void setBottomLod(int bottomLod) {\r
+ this.bottomLod = bottomLod;\r
+ }\r
+\r
+ public boolean isReIndexNeeded() {\r
+ if (lodChanged() || isFixEdges())\r
+ return true;\r
+ //if (leftLod != newLod || rightLod != newLod || bottomLod != newLod || topLod != newLod)\r
+ // return true;\r
+ return false;\r
+ }\r
+\r
+ /*public void setReIndexNeeded(boolean reIndexNeeded) {\r
+ this.reIndexNeeded = reIndexNeeded;\r
+ }*/\r
+\r
+ public boolean isFixEdges() {\r
+ return fixEdges;\r
+ }\r
+\r
+ public void setFixEdges(boolean fixEdges) {\r
+ this.fixEdges = fixEdges;\r
+ }\r
+\r
+ /*public int getPreviousLod() {\r
+ return previousLod;\r
+ }*/\r
+\r
+ public void setPreviousLod(int previousLod) {\r
+ this.previousLod = previousLod;\r
+ }\r
+\r
+ public void updateAll() {\r
+ updatedPatch.setLod(newLod);\r
+ updatedPatch.setLodRight(rightLod);\r
+ updatedPatch.setLodTop(topLod);\r
+ updatedPatch.setLodLeft(leftLod);\r
+ updatedPatch.setLodBottom(bottomLod);\r
+ if (newIndexBuffer != null && isReIndexNeeded()) {\r
+ updatedPatch.setPreviousLod(previousLod);\r
+ updatedPatch.getMesh().clearBuffer(Type.Index);\r
+ if (newIndexBuffer instanceof IntBuffer)\r
+ updatedPatch.getMesh().setBuffer(Type.Index, 3, (IntBuffer)newIndexBuffer);\r
+ else if (newIndexBuffer instanceof ShortBuffer)\r
+ updatedPatch.getMesh().setBuffer(Type.Index, 3, (ShortBuffer)newIndexBuffer);\r
+ }\r
+ }\r
+ \r
}\r
--- /dev/null
+/*
+ * 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.grid;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class AssetTileLoader implements TerrainGridTileLoader {
+
+ private AssetManager manager;
+ private String assetPath;
+ private String name;
+ private int size;
+ private int patchSize;
+ private int quadSize;
+
+ public AssetTileLoader() {
+ }
+
+ public AssetTileLoader(AssetManager manager, String name, String assetPath) {
+ this.manager = manager;
+ this.name = name;
+ this.assetPath = assetPath;
+ }
+
+ public TerrainQuad getTerrainQuadAt(Vector3f location) {
+ String modelName = assetPath + "/" + name + "_" + Math.round(location.x) + "_" + Math.round(location.y) + "_" + Math.round(location.z) + ".j3o";
+ Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Load terrain grid tile: {0}", modelName);
+ TerrainQuad quad = null;
+ try {
+ quad = (TerrainQuad) manager.loadModel(modelName);
+ } catch (Exception e) {
+// e.printStackTrace();
+ }
+ if (quad == null) {
+ Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Could not load terrain grid tile: {0}", modelName);
+ quad = createNewQuad(location);
+ } else {
+ Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Loaded terrain grid tile: {0}", modelName);
+ }
+ return quad;
+ }
+
+ public String getAssetPath() {
+ return assetPath;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setPatchSize(int patchSize) {
+ this.patchSize = patchSize;
+ }
+
+ public void setQuadSize(int quadSize) {
+ this.quadSize = quadSize;
+ }
+
+ private TerrainQuad createNewQuad(Vector3f location) {
+ TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, null);
+ return q;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule c = ex.getCapsule(this);
+ c.write(assetPath, "assetPath", null);
+ c.write(name, "name", null);
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule c = im.getCapsule(this);
+ manager = im.getAssetManager();
+ assetPath = c.readString("assetPath", null);
+ name = c.readString("name", null);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.grid;
+
+
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.HeightMap;
+import com.jme3.terrain.noise.Basis;
+import java.io.IOException;
+import java.nio.FloatBuffer;
+
+/**
+ *
+ * @author Anthyon, normenhansen
+ */
+public class FractalTileLoader implements TerrainGridTileLoader{
+
+ public class FloatBufferHeightMap extends AbstractHeightMap {
+
+ private final FloatBuffer buffer;
+
+ public FloatBufferHeightMap(FloatBuffer buffer) {
+ this.buffer = buffer;
+ }
+
+ @Override
+ public boolean load() {
+ this.heightData = this.buffer.array();
+ return true;
+ }
+
+ }
+
+ private int patchSize;
+ private int quadSize;
+ private final Basis base;
+ private final float heightScale;
+
+ public FractalTileLoader(Basis base, float heightScale) {
+ this.base = base;
+ this.heightScale = heightScale;
+ }
+
+ private HeightMap getHeightMapAt(Vector3f location) {
+ AbstractHeightMap heightmap = null;
+
+ FloatBuffer buffer = this.base.getBuffer(location.x * (this.quadSize - 1), location.z * (this.quadSize - 1), 0, this.quadSize);
+
+ float[] arr = buffer.array();
+ for (int i = 0; i < arr.length; i++) {
+ arr[i] = arr[i] * this.heightScale;
+ }
+ heightmap = new FloatBufferHeightMap(buffer);
+ heightmap.load();
+ return heightmap;
+ }
+
+ public TerrainQuad getTerrainQuadAt(Vector3f location) {
+ HeightMap heightMapAt = getHeightMapAt(location);
+ TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
+ return q;
+ }
+
+ public void setPatchSize(int patchSize) {
+ this.patchSize = patchSize;
+ }
+
+ public void setQuadSize(int quadSize) {
+ this.quadSize = quadSize;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ //TODO: serialization
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ //TODO: serialization
+ }
+}
--- /dev/null
+/*
+ * 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.grid;
+
+import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGridTileLoader;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.heightmap.*;
+import com.jme3.texture.Texture;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Anthyon, normenhansen
+ */
+public class ImageTileLoader implements TerrainGridTileLoader{
+ private static final Logger logger = Logger.getLogger(ImageTileLoader.class.getName());
+ private final AssetManager assetManager;
+ private final Namer namer;
+ private int patchSize;
+ private int quadSize;
+ private float heightScale = 1;
+ //private int imageType = BufferedImage.TYPE_USHORT_GRAY; // 16 bit grayscale
+ //private ImageHeightmap customImageHeightmap;
+
+ public ImageTileLoader(final String textureBase, final String textureExt, AssetManager assetManager) {
+ this(assetManager, new Namer() {
+
+ public String getName(int x, int y) {
+ return textureBase + "_" + x + "_" + y + "." + textureExt;
+ }
+ });
+ }
+
+ public ImageTileLoader(AssetManager assetManager, Namer namer) {
+ this.assetManager = assetManager;
+ this.namer = namer;
+ }
+
+ /**
+ * Effects vertical scale of the height of the terrain when loaded.
+ */
+ public void setHeightScale(float heightScale) {
+ this.heightScale = heightScale;
+ }
+
+
+ /**
+ * Lets you specify the type of images that are being loaded. All images
+ * must be the same type.
+ * @param imageType eg. BufferedImage.TYPE_USHORT_GRAY
+ */
+ /*public void setImageType(int imageType) {
+ this.imageType = imageType;
+ }*/
+
+ /**
+ * The ImageHeightmap that will parse the image type that you
+ * specify with setImageType().
+ * @param customImageHeightmap must extend AbstractHeightmap
+ */
+ /*public void setCustomImageHeightmap(ImageHeightmap customImageHeightmap) {
+ if (!(customImageHeightmap instanceof AbstractHeightMap)) {
+ throw new IllegalArgumentException("customImageHeightmap must be an AbstractHeightMap!");
+ }
+ this.customImageHeightmap = customImageHeightmap;
+ }*/
+
+ private HeightMap getHeightMapAt(Vector3f location) {
+ // HEIGHTMAP image (for the terrain heightmap)
+ int x = (int) location.x;
+ int z = (int) location.z;
+
+ AbstractHeightMap heightmap = null;
+ //BufferedImage im = null;
+
+ String name = null;
+ try {
+ name = namer.getName(x, z);
+ logger.log(Level.FINE, "Loading heightmap from file: {0}", name);
+ final Texture texture = assetManager.loadTexture(new TextureKey(name));
+ heightmap = new ImageBasedHeightMap(texture.getImage());
+ /*if (assetInfo != null){
+ InputStream in = assetInfo.openStream();
+ im = ImageIO.read(in);
+ } else {
+ im = new BufferedImage(patchSize, patchSize, imageType);
+ logger.log(Level.WARNING, "File: {0} not found, loading zero heightmap instead", name);
+ }*/
+ // CREATE HEIGHTMAP
+ /*if (imageType == BufferedImage.TYPE_USHORT_GRAY) {
+ heightmap = new Grayscale16BitHeightMap(im);
+ } else if (imageType == BufferedImage.TYPE_3BYTE_BGR) {
+ heightmap = new ImageBasedHeightMap(im);
+ } else if (customImageHeightmap != null && customImageHeightmap instanceof AbstractHeightMap) {
+ // If it gets here, it means you have specified a different image type, and you must
+ // then also supply a custom image heightmap class that can parse that image into
+ // a heightmap.
+ customImageHeightmap.setImage(im);
+ heightmap = (AbstractHeightMap) customImageHeightmap;
+ } else {
+ // error, no supported image format and no custom image heightmap specified
+ if (customImageHeightmap == null)
+ logger.log(Level.SEVERE, "Custom image type specified [{0}] but no customImageHeightmap declared! Use setCustomImageHeightmap()",imageType);
+ if (!(customImageHeightmap instanceof AbstractHeightMap))
+ logger.severe("customImageHeightmap must be an AbstractHeightMap!");
+ return null;
+ }*/
+ heightmap.setHeightScale(1);
+ heightmap.load();
+ //} catch (IOException e) {
+ // e.printStackTrace();
+ } catch (AssetNotFoundException e) {
+ logger.log(Level.WARNING, "Asset {0} not found, loading zero heightmap instead", name);
+ }
+ return heightmap;
+ }
+
+ public void setSize(int size) {
+ this.patchSize = size - 1;
+ }
+
+ public TerrainQuad getTerrainQuadAt(Vector3f location) {
+ HeightMap heightMapAt = getHeightMapAt(location);
+ TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap());
+ return q;
+ }
+
+ public void setPatchSize(int patchSize) {
+ this.patchSize = patchSize;
+ }
+
+ public void setQuadSize(int quadSize) {
+ this.quadSize = quadSize;
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ //TODO: serialization
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ //TODO: serialization
+ }
+}
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
}
public boolean calculateLod(TerrainPatch terrainPatch, List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
+ if (locations == null || locations.isEmpty())
+ return false;// no camera yet
float distance = getCenterLocation(terrainPatch).distance(locations.get(0));
-
if (turnOffLod) {
// set to full detail
int prevLOD = terrainPatch.getLod();
UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
if (utp == null) {
- utp = new UpdatedTerrainPatch(terrainPatch, 0);
+ utp = new UpdatedTerrainPatch(terrainPatch);
updates.put(utp.getName(), utp);
}
utp.setNewLod(0);
utp.setPreviousLod(prevLOD);
- utp.setReIndexNeeded(true);
+ //utp.setReIndexNeeded(true);
return true;
}
// go through each lod level to find the one we are in
for (int i = 0; i <= terrainPatch.getMaxLod(); i++) {
- if (distance < getLodDistanceThreshold() * (i + 1)*terrainPatch.getWorldScale().x || i == terrainPatch.getMaxLod()) {
+ if (distance < getLodDistanceThreshold() * (i + 1)*terrainPatch.getWorldScaleCached().x || i == terrainPatch.getMaxLod()) {
boolean reIndexNeeded = false;
if (i != terrainPatch.getLod()) {
reIndexNeeded = true;
//System.out.println("lod change: "+lod+" > "+i+" dist: "+distance);
}
int prevLOD = terrainPatch.getLod();
- //previousLod = lod;
- //lod = i;
+
UpdatedTerrainPatch utp = updates.get(terrainPatch.getName());
if (utp == null) {
- utp = new UpdatedTerrainPatch(terrainPatch, i);//save in here, do not update actual variables
+ utp = new UpdatedTerrainPatch(terrainPatch);//save in here, do not update actual variables
updates.put(utp.getName(), utp);
}
+ utp.setNewLod(i);
utp.setPreviousLod(prevLOD);
- utp.setReIndexNeeded(reIndexNeeded);
+ //utp.setReIndexNeeded(reIndexNeeded);
return reIndexNeeded;
}
}
protected Vector3f getCenterLocation(TerrainPatch terrainPatch) {
- Vector3f loc = terrainPatch.getWorldTranslation().clone();
- loc.x += terrainPatch.getSize()*terrainPatch.getWorldScale().x / 2;
- loc.z += terrainPatch.getSize()*terrainPatch.getWorldScale().z / 2;
+ Vector3f loc = terrainPatch.getWorldTranslationCached();
+ loc.x += terrainPatch.getSize()*terrainPatch.getWorldScaleCached().x / 2;
+ loc.z += terrainPatch.getSize()*terrainPatch.getWorldScaleCached().z / 2;
return loc;
}
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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;
import com.jme3.export.Savable;
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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;
import com.jme3.export.Savable;
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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;
import com.jme3.export.InputCapsule;
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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;
import com.jme3.export.JmeExporter;
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.geomipmap.lodcalc;\r
\r
import com.jme3.export.Savable;\r
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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;
import com.jme3.export.JmeExporter;
public class PerspectiveLodCalculator implements LodCalculator {
- private TerrainPatch patch;
private Camera cam;
- private float[] entropyDistances;
private float pixelError;
+ private boolean turnOffLod = false;
public PerspectiveLodCalculator() {}
- public PerspectiveLodCalculator(Camera cam, float pixelError){
+ public PerspectiveLodCalculator(Camera cam, float pixelError) {
this.cam = cam;
this.pixelError = pixelError;
}
return A / T;
}
- public boolean calculateLod(List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
- return calculateLod(patch, locations, updates);
- }
-
- public boolean calculateLod(TerrainPatch terrainPatch, List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
- if (entropyDistances == null){
- // compute entropy distances
- float[] lodEntropies = patch.getLodEntropies();
- entropyDistances = new float[lodEntropies.length];
- float cameraConstant = getCameraConstant(cam, pixelError);
- for (int i = 0; i < lodEntropies.length; i++){
- entropyDistances[i] = lodEntropies[i] * cameraConstant;
+ public boolean calculateLod(TerrainPatch patch, List<Vector3f> locations, HashMap<String, UpdatedTerrainPatch> updates) {
+ if (turnOffLod) {
+ // set to full detail
+ int prevLOD = patch.getLod();
+ UpdatedTerrainPatch utp = updates.get(patch.getName());
+ if (utp == null) {
+ utp = new UpdatedTerrainPatch(patch);
+ updates.put(utp.getName(), utp);
}
+ utp.setNewLod(0);
+ utp.setPreviousLod(prevLOD);
+ //utp.setReIndexNeeded(true);
+ return true;
}
-
+
+ float[] lodEntropies = patch.getLodEntropies();
+ float cameraConstant = getCameraConstant(cam, pixelError);
+
Vector3f patchPos = getCenterLocation(patch);
// vector from camera to patch
// go through each lod level to find the one we are in
for (int i = 0; i <= patch.getMaxLod(); i++) {
- if (distance < entropyDistances[i] || i == patch.getMaxLod()){
+ if (distance < lodEntropies[i] * cameraConstant || i == patch.getMaxLod()){
boolean reIndexNeeded = false;
if (i != patch.getLod()) {
reIndexNeeded = true;
}
int prevLOD = patch.getLod();
- //previousLod = lod;
- //lod = i;
+
UpdatedTerrainPatch utp = updates.get(patch.getName());
if (utp == null) {
- utp = new UpdatedTerrainPatch(patch, i);//save in here, do not update actual variables
+ utp = new UpdatedTerrainPatch(patch);//save in here, do not update actual variables
updates.put(utp.getName(), utp);
}
+ utp.setNewLod(i);
utp.setPreviousLod(prevLOD);
- utp.setReIndexNeeded(reIndexNeeded);
+ //utp.setReIndexNeeded(reIndexNeeded);
return reIndexNeeded;
}
}
}
public void write(JmeExporter ex) throws IOException {
+
}
public void read(JmeImporter im) throws IOException {
}
public void turnOffLod() {
- //TODO
+ turnOffLod = true;
}
-
+
public boolean isLodOff() {
- return false; //TODO
+ return turnOffLod;
}
public void turnOnLod() {
- //TODO
+ turnOffLod = false;
}
}
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.geomipmap.lodcalc;\r
\r
import com.jme3.export.InputCapsule;\r
+/*
+ * 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.lodcalc.util;
import com.jme3.bounding.BoundingBox;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
+import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
/**
* Computes the entropy value δ (delta) for a given terrain block and
*/
public class EntropyComputeUtil {
- public static float computeLodEntropy(Mesh terrainBlock, IntBuffer lodIndices){
+ public static float computeLodEntropy(Mesh terrainBlock, Buffer lodIndices){
// Bounding box for the terrain block
BoundingBox bbox = (BoundingBox) terrainBlock.getBound();
VertexBuffer originalIndices = terrainBlock.getBuffer(Type.Index);
terrainBlock.clearBuffer(Type.Index);
- terrainBlock.setBuffer(Type.Index, 3, lodIndices);
+ if (lodIndices instanceof IntBuffer)
+ terrainBlock.setBuffer(Type.Index, 3, (IntBuffer)lodIndices);
+ else if (lodIndices instanceof ShortBuffer) {
+ terrainBlock.setBuffer(Type.Index, 3, (ShortBuffer) lodIndices);
+ }
// Recalculate collision mesh
terrainBlock.createCollisionData();
float entropy = 0;
- for (int i = 0; i < positions.capacity() / 3; i++){
+ for (int i = 0; i < positions.limit() / 3; i++){
BufferUtils.populateFromBuffer(pos, positions, i);
float realHeight = pos.y;
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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.picking;
import com.jme3.collision.CollisionResult;
* grid row
* @param gridY
* grid column
- * @param block
- * the TerrainBlock we are working with
+ * @param patch
+ * the TerrainPatch we are working with
* @return true if the grid point is valid for the given block, false if it
* is off the block.
*/
*
* @param position
* the position to check at
- * @param block
- * the block to get height values from
+ * @param patch
+ * the patch to get height values from
* @return an index to the height position of the given block.
*/
protected int findClosestHeightIndex(Vector3f position, TerrainPatch patch) {
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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.picking;
import com.jme3.math.Ray;
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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.picking;
import com.jme3.collision.CollisionResult;
return 0;
}
+ @Override
+ public boolean equals(Object obj) {
+ if(obj instanceof TerrainPickData){
+ return ((TerrainPickData)obj).compareTo(this) == 0;
+ }
+ return super.equals(obj);
+ }
+
}
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 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.picking;
import com.jme3.collision.CollisionResults;
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* @return the value at (x,z).\r
*/\r
public float getTrueHeightAtPoint(int x, int z) {\r
- //logger.info( heightData[x + (z*size)]);\r
+ //logger.fine( heightData[x + (z*size)]);\r
return heightData[x + (z * size)];\r
}\r
\r
throw new Exception("Filename must not be null");\r
}\r
//open the streams and send the height data to the file.\r
+ FileOutputStream fos = null;\r
+ DataOutputStream dos = null;\r
try {\r
- FileOutputStream fos = new FileOutputStream(filename);\r
- DataOutputStream dos = new DataOutputStream(fos);\r
+ fos = new FileOutputStream(filename);\r
+ dos = new DataOutputStream(fos);\r
+ \r
for (int i = 0; i < size; i++) {\r
for (int j = 0; j < size; j++) {\r
dos.write((int) heightData[j + (i * size)]);\r
} catch (IOException e) {\r
logger.log(Level.WARNING, "Error writing to file {0}", filename);\r
return false;\r
+ } finally {\r
+ if (fos != null) {\r
+ fos.close();\r
+ }\r
+ if (dos != null) {\r
+ dos.close();\r
+ }\r
}\r
\r
- logger.log(Level.INFO, "Saved terrain to {0}", filename);\r
+ logger.log(Level.FINE, "Saved terrain to {0}", filename);\r
return true;\r
}\r
\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
}\r
\r
\r
- logger.info("Created heightmap using Combiner");\r
+ logger.fine("Created heightmap using Combiner");\r
\r
\r
return true;\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* @param faultShape Shape of the fault -line or circle\r
* @param minFaultHeight Height modified on each side\r
* @param maxFaultHeight Height modified on each side\r
- * @param faultHeight Height modified on each side\r
* @param seed A seed to feed the Random generator\r
* @see setFaultRange, setMinRadius, setMaxRadius\r
*/\r
* Create an heightmap with linear step faults.\r
* @param size size of heightmap\r
* @param iterations number of iterations\r
- * @param faultHeight height to modify\r
+ * @param minFaultHeight Height modified on each side\r
+ * @param maxFaultHeight Height modified on each side\r
*/\r
public FaultHeightMap(int size, int iterations, float minFaultHeight, float maxFaultHeight) throws Exception {\r
this(size, iterations, FAULTTYPE_STEP, FAULTSHAPE_LINE, minFaultHeight, maxFaultHeight, new Random().nextLong());\r
\r
normalizeTerrain(NORMALIZE_RANGE);\r
\r
- logger.log(Level.INFO, "Fault heightmap generated");\r
+ logger.log(Level.FINE, "Fault heightmap generated");\r
return true;\r
}\r
\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
}\r
normalizeTerrain(NORMALIZE_RANGE);\r
\r
- logger.info("Created Heightmap using fluid simulation");\r
+ logger.fine("Created Heightmap using fluid simulation");\r
\r
return true;\r
}\r
+++ /dev/null
-/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
- * All rights reserved.\r
- *\r
- * Redistribution and use in source and binary forms, with or without\r
- * modification, are permitted provided that the following conditions are\r
- * met:\r
- *\r
- * * Redistributions of source code must retain the above copyright\r
- * notice, this list of conditions and the following disclaimer.\r
- *\r
- * * Redistributions in binary form must reproduce the above copyright\r
- * notice, this list of conditions and the following disclaimer in the\r
- * documentation and/or other materials provided with the distribution.\r
- *\r
- * * Neither the name of 'jMonkeyEngine' nor the names of its contributors\r
- * may be used to endorse or promote products derived from this software\r
- * without specific prior written permission.\r
- *\r
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\r
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
- */\r
-package com.jme3.terrain.heightmap;\r
-\r
-import java.awt.image.BufferedImage;\r
-import java.io.File;\r
-import java.io.IOException;\r
-import java.nio.FloatBuffer;\r
-\r
-import javax.imageio.ImageIO;\r
-\r
-import org.novyon.noise.Basis;\r
-\r
-import com.jme3.math.Vector3f;\r
-import com.jme3.terrain.MapUtils;\r
-\r
-public class FractalHeightMapGrid implements HeightMapGrid {\r
-\r
- public class FloatBufferHeightMap extends AbstractHeightMap {\r
-\r
- private final FloatBuffer buffer;\r
-\r
- public FloatBufferHeightMap(FloatBuffer buffer) {\r
- this.buffer = buffer;\r
- }\r
-\r
- @Override\r
- public boolean load() {\r
- this.heightData = this.buffer.array();\r
- return true;\r
- }\r
-\r
- }\r
-\r
- private int size;\r
- private final Basis base;\r
- private final String cacheDir;\r
- private final float heightScale;\r
-\r
- public FractalHeightMapGrid(Basis base, String cacheDir, float heightScale) {\r
- this.base = base;\r
- this.cacheDir = cacheDir;\r
- this.heightScale = heightScale;\r
- }\r
-\r
- @Override\r
- public HeightMap getHeightMapAt(Vector3f location) {\r
- AbstractHeightMap heightmap = null;\r
- if (this.cacheDir != null && new File(this.cacheDir, "terrain_" + (int) location.x + "_" + (int) location.z + ".png").exists()) {\r
- try {\r
- BufferedImage im = null;\r
- im = ImageIO.read(new File(this.cacheDir, "terrain_" + (int) location.x + "_" + (int) location.z + ".png"));\r
- heightmap = new Grayscale16BitHeightMap(im);\r
- heightmap.setHeightScale(heightScale);\r
- } catch (IOException e) {}\r
- } else {\r
- FloatBuffer buffer = this.base.getBuffer(location.x * (this.size - 1), location.z * (this.size - 1), 0, this.size);\r
- if (this.cacheDir != null) {\r
- MapUtils.saveImage(MapUtils.toGrayscale16Image(buffer, this.size), new File(this.cacheDir, "terrain_" + (int) location.x\r
- + "_" + (int) location.z + ".png"));\r
- }\r
- float[] arr = buffer.array();\r
- for (int i = 0; i < arr.length; i++) {\r
- arr[i] = arr[i] * this.heightScale;\r
- }\r
- heightmap = new FloatBufferHeightMap(buffer);\r
- }\r
- heightmap.load();\r
- return heightmap;\r
- }\r
-\r
- @Override\r
- public void setSize(int size) {\r
- this.size = size;\r
- }\r
-\r
-}\r
+++ /dev/null
-/*
- * Copyright (c) 2009-2010 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.heightmap;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.imageio.ImageIO;
-
-/**
- *
- * @author Anthyon
- */
-public class Grayscale16BitHeightMap extends AbstractHeightMap {
-
- private BufferedImage image;
-
- public Grayscale16BitHeightMap() {
- }
-
- public Grayscale16BitHeightMap(BufferedImage image) {
- this.image = image;
- }
-
- public Grayscale16BitHeightMap(String filename) {
- this(new File(filename));
- }
-
- public Grayscale16BitHeightMap(File file) {
- try {
- this.image = ImageIO.read(file);
- } catch (IOException ex) {
- Logger.getLogger(Grayscale16BitHeightMap.class.getName()).log(Level.SEVERE, null, ex);
- }
- }
-
- @Override
- public boolean load() {
- return load(false, false);
- }
-
- public boolean load(boolean flipX, boolean flipY) {
- int imageWidth = image.getWidth();
- int imageHeight = image.getHeight();
-
- if (imageWidth != imageHeight) {
- throw new RuntimeException("imageWidth: " + imageWidth
- + " != imageHeight: " + imageHeight);
- }
-
- Object out = new short[imageWidth * imageHeight];
- out = image.getData().getDataElements(0, 0, imageWidth, imageHeight, out);
- short[] values = (short[]) out;
- heightData = new float[imageWidth * imageHeight];
- int i = 0;
- for (int y = 0; y < imageHeight; y++) {
- for (int x = 0; x < imageWidth; x++, i++) {
- heightData[i] = heightScale * (values[i] & 0x0000FFFF) / 65536f;
- }
- }
-
- return true;
- }
-}
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.heightmap;\r
\r
/**\r
/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
+ * 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.heightmap;
*
* @author Anthyon
*/
+@Deprecated
+/**
+ * @Deprecated in favor of TerrainGridTileLoader
+ */
public interface HeightMapGrid {
public HeightMap getHeightMapAt(Vector3f location);
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* the minimum radius of a hill\r
* @param maxRadius\r
* the maximum radius of a hill\r
- * @param flattening\r
- * the power of flattening done, 1 means none\r
* @param seed\r
* the seed to generate the same heightmap again\r
* @throws Exception\r
+ "or minimum radius is greater than maximum radius, "\r
+ "or power of flattening is below one");\r
}\r
- logger.info("Contructing hill heightmap using seed: " + seed);\r
+ logger.fine("Contructing hill heightmap using seed: " + seed);\r
this.size = size;\r
this.seed = seed;\r
this.iterations = iterations;\r
* the minimum radius of a hill\r
* @param maxRadius\r
* the maximum radius of a hill\r
- * @param flattening\r
- * the power of flattening done, 1 means none\r
* @throws Exception\r
* @throws JmeException\r
* if size of the terrain is not greater that zero, or number of\r
\r
normalizeTerrain(NORMALIZE_RANGE);\r
\r
- logger.info("Created Heightmap using the Hill Algorithm");\r
+ logger.fine("Created Heightmap using the Hill Algorithm");\r
\r
return true;\r
}\r
* @param minRadius\r
* the minimum radius of a hill\r
* @throws Exception\r
- * @throw JmeException if the minimum radius is not greater than zero or not\r
+ * @throws JmeException if the minimum radius is not greater than zero or not\r
* lower than the maximum radius\r
*/\r
public void setMinRadius(float minRadius) throws Exception {\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
*/\r
-\r
package com.jme3.terrain.heightmap;\r
\r
-import java.awt.Graphics2D;\r
-import java.awt.Image;\r
-import java.awt.image.BufferedImage;\r
-import java.awt.image.ColorModel;\r
-import java.awt.image.PixelGrabber;\r
-import java.awt.image.WritableRaster;\r
+import com.jme3.math.ColorRGBA;\r
+import com.jme3.texture.Image;\r
+import com.jme3.texture.image.ImageRaster;\r
\r
/**\r
* <code>ImageBasedHeightMap</code> is a height map created from the grayscale\r
* @author Mike Kienenberger\r
* @version $id$\r
*/\r
-public class ImageBasedHeightMap extends AbstractHeightMap {\r
-\r
- static protected class ImageConverter {\r
-\r
- // Posted by DrLaszloJamf to Java Technology Forums\r
- //\r
- // Copyright 1994-2004 Sun Microsystems, Inc. All Rights Reserved.\r
- // Redistribution and use in source and binary forms, with or without\r
- // modification, are permitted provided that the following conditions\r
- // are met:\r
- //\r
- // Redistribution of source code must retain the above copyright notice,\r
- // this list of conditions and the following disclaimer.\r
- //\r
- // Redistribution in binary form must reproduce the above copyright\r
- // notice, this list of conditions and the following disclaimer in the\r
- // documentation and/or other materials provided with the distribution.\r
- //\r
- // Neither the name of Sun Microsystems, Inc. or the names of\r
- // contributors may be used to endorse or promote products derived from\r
- // this software without specific prior written permission.\r
- //\r
- // This software is provided "AS IS," without a warranty of any kind.\r
- // ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,\r
- // INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A\r
- // PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN\r
- // MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR\r
- // ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR\r
- // DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN\r
- // OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR\r
- // FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE\r
- // DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,\r
- // ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF\r
- // SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.\r
- //\r
- //\r
- // You acknowledge that this software is not designed, licensed or\r
- // intended for use in the design, construction, operation or\r
- // maintenance of any nuclear facility.\r
-\r
- // preserves image's colormodel. Assumes image is loaded\r
- public static BufferedImage toBufferedImage(Image image) {\r
- if (image instanceof BufferedImage) return (BufferedImage) image;\r
- ColorModel cm = getColorModel(image);\r
- int width = image.getWidth(null);\r
- int height = image.getHeight(null);\r
- return copy(createBufferedImage(cm, width, height), image);\r
- }\r
-\r
- public static BufferedImage toBufferedImage(Image image, int type) {\r
- if (image instanceof BufferedImage\r
- && ((BufferedImage) image).getType() == type)\r
- return (BufferedImage) image;\r
- int width = image.getWidth(null);\r
- int height = image.getHeight(null);\r
- return copy(new BufferedImage(width, height, type), image);\r
- }\r
-\r
- // Returns target. Assumes source is loaded\r
- public static BufferedImage copy(BufferedImage target, Image source) {\r
- Graphics2D g = target.createGraphics();\r
- g.drawImage(source, 0, 0, null);\r
- g.dispose();\r
- return target;\r
- }\r
-\r
- public static ColorModel getColorModel(Image image) {\r
- try {\r
- PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);\r
- pg.grabPixels();\r
- return pg.getColorModel();\r
- } catch (InterruptedException e) {\r
- throw new RuntimeException("Unexpected interruption", e);\r
- }\r
- }\r
-\r
- public static BufferedImage createBufferedImage(ColorModel cm, int w,\r
- int h) {\r
- WritableRaster raster = cm.createCompatibleWritableRaster(w, h);\r
- boolean isRasterPremultiplied = cm.isAlphaPremultiplied();\r
- return new BufferedImage(cm, raster, isRasterPremultiplied, null);\r
- }\r
- }\r
+public class ImageBasedHeightMap extends AbstractHeightMap implements ImageHeightmap {\r
\r
\r
protected Image colorImage;\r
- protected float dampen = 1.0f;\r
+ private float backwardsCompScale = 255f;\r
\r
\r
+ public void setImage(Image image) {\r
+ this.colorImage = image;\r
+ }\r
+ \r
/**\r
* Creates a HeightMap from an Image. The image will be converted to\r
* grayscale, and the grayscale values will be used to generate the height\r
* Image to map to the height map.\r
*/\r
public ImageBasedHeightMap(Image colorImage) {\r
- this(colorImage, 1.0f);\r
+ this.colorImage = colorImage;\r
}\r
\r
- public ImageBasedHeightMap(Image colorImage, float dampen) {\r
- super();\r
+ public ImageBasedHeightMap(Image colorImage, float heightScale) {\r
this.colorImage = colorImage;\r
- this.dampen = dampen;\r
+ this.heightScale = heightScale;\r
}\r
\r
/**\r
* Get the grayscale value, or override in your own sub-classes\r
*/\r
protected float calculateHeight(float red, float green, float blue) {\r
- return (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen);\r
+ return (float) (0.299 * red + 0.587 * green + 0.114 * blue);\r
+ }\r
+ \r
+ protected float calculateHeight(ColorRGBA color) {\r
+ return (float) (0.299 * color.r + 0.587 * color.g + 0.114 * color.b);\r
+ }\r
+ \r
+ protected ImageRaster getImageRaster() {\r
+ return ImageRaster.create(colorImage);\r
}\r
\r
public boolean load(boolean flipX, boolean flipY) {\r
\r
- // FUTURE: Rescale image if not square?\r
- BufferedImage colorBufferedImage = ImageConverter.toBufferedImage(\r
- colorImage, BufferedImage.TYPE_3BYTE_BGR);\r
-\r
- boolean hasAlpha = colorBufferedImage.getColorModel().hasAlpha();\r
-\r
- int imageWidth = colorBufferedImage.getWidth();\r
- int imageHeight = colorBufferedImage.getHeight();\r
+ int imageWidth = colorImage.getWidth();\r
+ int imageHeight = colorImage.getHeight();\r
\r
if (imageWidth != imageHeight)\r
throw new RuntimeException("imageWidth: " + imageWidth\r
+ " != imageHeight: " + imageHeight);\r
\r
size = imageWidth;\r
-\r
- byte data[] = (byte[]) colorBufferedImage.getRaster().getDataElements(\r
- 0, 0, imageWidth, imageHeight, null);\r
-\r
- int bytesPerPixel = 3;\r
- int blueBase = 0;\r
- if (hasAlpha) {\r
- bytesPerPixel = 4;\r
- blueBase = 1;\r
- }\r
+ ImageRaster raster = getImageRaster();\r
\r
heightData = new float[(imageWidth * imageHeight)];\r
\r
- int startW = 0;\r
- int endW = imageWidth-1;\r
- if (flipX) {\r
- startW = imageWidth-1;\r
- endW = 0;\r
- }\r
- int startH = imageHeight-1;\r
- int endH = 0;\r
- if (flipY) {\r
- startH = 0;\r
- endH = imageHeight-1;\r
- }\r
-\r
+ ColorRGBA colorStore = new ColorRGBA();\r
+ \r
int index = 0;\r
if (flipY) {\r
for (int h = 0; h < imageHeight; ++h) {\r
if (flipX) {\r
for (int w = imageWidth - 1; w >= 0; --w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
- heightData[index++] = calculateHeight(red,green,blue);\r
+ //int baseIndex = (h * imageWidth)+ w;\r
+ //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale;\r
+ heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale;\r
}\r
} else {\r
for (int w = 0; w < imageWidth; ++w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
- heightData[index++] = calculateHeight(red,green,blue);\r
-\r
+ //int baseIndex = (h * imageWidth)+ w;\r
+ //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale;\r
+ heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale;\r
}\r
}\r
}\r
for (int h = imageHeight - 1; h >= 0; --h) {\r
if (flipX) {\r
for (int w = imageWidth - 1; w >= 0; --w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
- heightData[index++] = calculateHeight(red,green,blue);\r
+ //int baseIndex = (h * imageWidth)+ w;\r
+ //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale;\r
+ heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale;\r
}\r
} else {\r
for (int w = 0; w < imageWidth; ++w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
- heightData[index++] = calculateHeight(red,green,blue);\r
+ //int baseIndex = (h * imageWidth)+ w;\r
+ //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale;\r
+ heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale;\r
}\r
}\r
}\r
}\r
\r
- /*int index = 0;\r
- if (flipY) {\r
- for (int h = 0; h < imageHeight; ++h) {\r
- if (flipX) {\r
- for (int w = imageWidth-1; w >= 0; --w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
-\r
- float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen);\r
-\r
- heightData[index++] = grayscale;\r
- }\r
- } else {\r
- for (int w = 0; w < imageWidth; ++w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
-\r
- float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen);\r
-\r
- heightData[index++] = grayscale;\r
- }\r
- }\r
- }\r
- } else {\r
- for (int h = imageHeight-1; h >= 0; --h) {\r
- if (flipX) {\r
- for (int w = imageWidth-1; w >= 0; --w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
-\r
- float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen);\r
-\r
- heightData[index++] = grayscale;\r
- }\r
- } else {\r
- for (int w = 0; w < imageWidth; ++w) {\r
- int baseIndex = (h * imageWidth * bytesPerPixel)\r
- + (w * bytesPerPixel) + blueBase;\r
- float blue = data[baseIndex] >= 0 ? data[baseIndex]\r
- : (256 + (data[baseIndex]));\r
- float green = data[baseIndex + 1] >= 0 ? data[baseIndex + 1]\r
- : (256 + (data[baseIndex + 1]));\r
- float red = data[baseIndex + 2] >= 0 ? data[baseIndex + 2]\r
- : (256 + (data[baseIndex + 2]));\r
-\r
- float grayscale = (float) ((0.299 * red + 0.587 * green + 0.114 * blue) * dampen);\r
-\r
- heightData[index++] = grayscale;\r
- }\r
- }\r
- }\r
- }*/\r
- \r
return true;\r
}\r
+ \r
+ /*protected float getHeightAtPostion(ImageRaster image, int position, ColorRGBA store) {\r
+ switch (image.getFormat()){\r
+ case RGBA8:\r
+ buf.position( position * 4 );\r
+ store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()));\r
+ return calculateHeight(store.r, store.g, store.b);\r
+ case ABGR8:\r
+ buf.position( position * 4 );\r
+ float a = byte2float(buf.get());\r
+ float b = byte2float(buf.get());\r
+ float g = byte2float(buf.get());\r
+ float r = byte2float(buf.get());\r
+ store.set(r,g,b,a);\r
+ return calculateHeight(store.r, store.g, store.b);\r
+ case RGB8:\r
+ buf.position( position * 3 );\r
+ store.set(byte2float(buf.get()), byte2float(buf.get()), byte2float(buf.get()), 1);\r
+ return calculateHeight(store.r, store.g, store.b);\r
+ case Luminance8:\r
+ buf.position( position );\r
+ return byte2float(buf.get())*255*heightScale;\r
+ case Luminance16:\r
+ ShortBuffer sbuf = buf.asShortBuffer();\r
+ sbuf.position( position );\r
+ return (sbuf.get() & 0xFFFF) / 65535f * 255f * heightScale;\r
+ default:\r
+ throw new UnsupportedOperationException("Image format: "+image.getFormat());\r
+ }\r
+ }\r
+ \r
+ private float byte2float(byte b){\r
+ return ((float)(b & 0xFF)) / 255f;\r
+ }*/\r
}
\ No newline at end of file
/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
+ * 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.heightmap;
-import com.jme3.asset.AssetInfo;
-import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetManager;
import com.jme3.asset.AssetNotFoundException;
+import com.jme3.asset.TextureKey;
import com.jme3.math.Vector3f;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.io.InputStream;
+import com.jme3.texture.Texture;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.imageio.ImageIO;
/**
- *
- * @author Anthyon
+ * Loads Terrain grid tiles with image heightmaps.
+ * By default it expects a 16-bit grayscale image as the heightmap, but
+ * you can also call setImageType(BufferedImage.TYPE_) to set it to be a different
+ * image type. If you do this, you must also set a custom ImageHeightmap that will
+ * understand and be able to parse the image. By default if you pass in an image of type
+ * BufferedImage.TYPE_3BYTE_BGR, it will use the ImageBasedHeightMap for you.
+ *
+ * @author Anthyon, Brent Owens
+ */
+@Deprecated
+/**
+ * @Deprecated in favor of ImageTileLoader
*/
public class ImageBasedHeightMapGrid implements HeightMapGrid {
private final AssetManager assetManager;
private final Namer namer;
private int size;
+
public ImageBasedHeightMapGrid(final String textureBase, final String textureExt, AssetManager assetManager) {
this(assetManager, new Namer() {
int z = (int) location.z;
AbstractHeightMap heightmap = null;
- BufferedImage im = null;
+ //BufferedImage im = null;
try {
String name = namer.getName(x, z);
- logger.log(Level.INFO, "Loading heightmap from file: {0}", name);
- final AssetInfo assetInfo = assetManager.locateAsset(new AssetKey(name));
- if (assetInfo != null){
- InputStream in = assetInfo.openStream();
- im = ImageIO.read(in);
- } else {
- im = new BufferedImage(size, size, BufferedImage.TYPE_USHORT_GRAY);
- logger.log(Level.WARNING, "File: {0} not found, loading zero heightmap instead", name);
- }
+ logger.log(Level.FINE, "Loading heightmap from file: {0}", name);
+ final Texture texture = assetManager.loadTexture(new TextureKey(name));
+
// CREATE HEIGHTMAP
- heightmap = new Grayscale16BitHeightMap(im);
- heightmap.setHeightScale(256);
+ heightmap = new ImageBasedHeightMap(texture.getImage());
+
+ heightmap.setHeightScale(1);
heightmap.load();
- } catch (IOException e) {
+
} catch (AssetNotFoundException e) {
+ logger.log(Level.SEVERE, "Asset Not found! ", e);
}
return heightmap;
}
--- /dev/null
+/*
+ * 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.heightmap;
+
+/**
+ * A heightmap that is built off an image.
+ * If you want to be able to supply different Image types to
+ * ImageBaseHeightMapGrid, you need to implement this interface,
+ * and have that class extend Abstract heightmap.
+ *
+ * @author bowens
+ * @deprecated
+ */
+public interface ImageHeightmap {
+
+ /**
+ * Set the image to use for this heightmap
+ */
+ //public void setImage(Image image);
+
+ /**
+ * The BufferedImage.TYPE_ that is supported
+ * by this ImageHeightmap
+ */
+ //public int getSupportedImageType();
+}
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
\r
normalizeTerrain(NORMALIZE_RANGE);\r
\r
- logger.log(Level.INFO, "Midpoint displacement heightmap generated");\r
+ logger.log(Level.FINE, "Midpoint displacement heightmap generated");\r
return true;\r
}\r
\r
/*
- * To change this template, choose Tools | Templates
- * and open the template in the editor.
+ * 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.heightmap;
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
erodeTerrain();\r
normalizeTerrain(NORMALIZE_RANGE);\r
\r
- logger.info("Created heightmap using Particle Deposition");\r
+ logger.fine("Created heightmap using Particle Deposition");\r
\r
\r
return false;\r
/*\r
- * Copyright (c) 2009-2010 jMonkeyEngine\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
* All rights reserved.\r
*\r
* Redistribution and use in source and binary forms, with or without\r
\r
import com.jme3.math.FastMath;\r
import com.jme3.util.LittleEndien;\r
-import java.io.BufferedInputStream;\r
-import java.io.DataInputStream;\r
-import java.io.FileInputStream;\r
-import java.io.FileNotFoundException;\r
-import java.io.IOException;\r
-import java.io.InputStream;\r
+import java.io.*;\r
import java.net.URL;\r
import java.util.logging.Logger;\r
\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise;\r
+\r
+import com.jme3.terrain.noise.basis.ImprovedNoise;\r
+import com.jme3.terrain.noise.modulator.Modulator;\r
+import java.nio.FloatBuffer;\r
+\r
+/**\r
+ * Interface for - basically 3D - noise generation algorithms, based on the\r
+ * book: Texturing & Modeling - A Procedural Approach\r
+ * \r
+ * The main concept is to look at noise as a basis for generating fractals.\r
+ * Basis can be anything, like a simple:\r
+ * \r
+ * <code>\r
+ * float value(float x, float y, float z) {\r
+ * return 0; // a flat noise with 0 value everywhere\r
+ * }\r
+ * </code>\r
+ * \r
+ * or a more complex perlin noise ({@link ImprovedNoise}\r
+ * \r
+ * Fractals use these functions to generate a more complex result based on some\r
+ * frequency, roughness, etc values.\r
+ * \r
+ * Fractals themselves are implementing the Basis interface as well, opening\r
+ * an infinite range of results.\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ * @since 2011\r
+ * \r
+ */\r
+public interface Basis {\r
+\r
+ public void init();\r
+\r
+ public Basis setScale(float scale);\r
+\r
+ public float getScale();\r
+\r
+ public Basis addModulator(Modulator modulator);\r
+\r
+ public float value(float x, float y, float z);\r
+\r
+ public FloatBuffer getBuffer(float sx, float sy, float base, int size);\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise;\r
+\r
+/**\r
+ * Helper class for working with colors and gradients\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ */\r
+public class Color {\r
+\r
+ private final float[] rgba = new float[4];\r
+\r
+ public Color() {}\r
+\r
+ public Color(final int r, final int g, final int b) {\r
+ this(r, g, b, 255);\r
+ }\r
+\r
+ public Color(final int r, final int g, final int b, final int a) {\r
+ this.rgba[0] = (r & 255) / 256f;\r
+ this.rgba[1] = (g & 255) / 256f;\r
+ this.rgba[2] = (b & 255) / 256f;\r
+ this.rgba[3] = (a & 255) / 256f;\r
+ }\r
+\r
+ public Color(final float r, final float g, final float b) {\r
+ this(r, g, b, 1);\r
+ }\r
+\r
+ public Color(final float r, final float g, final float b, final float a) {\r
+ this.rgba[0] = ShaderUtils.clamp(r, 0, 1);\r
+ this.rgba[1] = ShaderUtils.clamp(g, 0, 1);\r
+ this.rgba[2] = ShaderUtils.clamp(b, 0, 1);\r
+ this.rgba[3] = ShaderUtils.clamp(a, 0, 1);\r
+ }\r
+\r
+ public Color(final int h, final float s, final float b) {\r
+ this(h, s, b, 1);\r
+ }\r
+\r
+ public Color(final int h, final float s, final float b, final float a) {\r
+ this.rgba[3] = a;\r
+ if (s == 0) {\r
+ // achromatic ( grey )\r
+ this.rgba[0] = b;\r
+ this.rgba[1] = b;\r
+ this.rgba[2] = b;\r
+ return;\r
+ }\r
+\r
+ float hh = h / 60.0f;\r
+ int i = ShaderUtils.floor(hh);\r
+ float f = hh - i;\r
+ float p = b * (1 - s);\r
+ float q = b * (1 - s * f);\r
+ float t = b * (1 - s * (1 - f));\r
+\r
+ if (i == 0) {\r
+ this.rgba[0] = b;\r
+ this.rgba[1] = t;\r
+ this.rgba[2] = p;\r
+ } else if (i == 1) {\r
+ this.rgba[0] = q;\r
+ this.rgba[1] = b;\r
+ this.rgba[2] = p;\r
+ } else if (i == 2) {\r
+ this.rgba[0] = p;\r
+ this.rgba[1] = b;\r
+ this.rgba[2] = t;\r
+ } else if (i == 3) {\r
+ this.rgba[0] = p;\r
+ this.rgba[1] = q;\r
+ this.rgba[2] = b;\r
+ } else if (i == 4) {\r
+ this.rgba[0] = t;\r
+ this.rgba[1] = p;\r
+ this.rgba[2] = b;\r
+ } else {\r
+ this.rgba[0] = b;\r
+ this.rgba[1] = p;\r
+ this.rgba[2] = q;\r
+ }\r
+ }\r
+\r
+ public int toInteger() {\r
+ return 0x00000000 | (int) (this.rgba[3] * 256) << 24 | (int) (this.rgba[0] * 256) << 16 | (int) (this.rgba[1] * 256) << 8\r
+ | (int) (this.rgba[2] * 256);\r
+ }\r
+\r
+ public String toWeb() {\r
+ return Integer.toHexString(this.toInteger());\r
+ }\r
+\r
+ public Color toGrayscale() {\r
+ float v = (this.rgba[0] + this.rgba[1] + this.rgba[2]) / 3f;\r
+ return new Color(v, v, v, this.rgba[3]);\r
+ }\r
+\r
+ public Color toSepia() {\r
+ float r = ShaderUtils.clamp(this.rgba[0] * 0.393f + this.rgba[1] * 0.769f + this.rgba[2] * 0.189f, 0, 1);\r
+ float g = ShaderUtils.clamp(this.rgba[0] * 0.349f + this.rgba[1] * 0.686f + this.rgba[2] * 0.168f, 0, 1);\r
+ float b = ShaderUtils.clamp(this.rgba[0] * 0.272f + this.rgba[1] * 0.534f + this.rgba[2] * 0.131f, 0, 1);\r
+ return new Color(r, g, b, this.rgba[3]);\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise;\r
+\r
+import java.nio.FloatBuffer;\r
+\r
+public interface Filter {\r
+ public Filter addPreFilter(Filter filter);\r
+\r
+ public Filter addPostFilter(Filter filter);\r
+\r
+ public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size);\r
+\r
+ public int getMargin(int size, int margin);\r
+\r
+ public boolean isEnabled();\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise;\r
+\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.image.DataBuffer;\r
+import java.awt.image.DataBufferInt;\r
+import java.awt.image.WritableRaster;\r
+import java.nio.ByteBuffer;\r
+import java.nio.ByteOrder;\r
+\r
+/**\r
+ * Helper class containing useful functions explained in the book:\r
+ * Texturing & Modeling - A Procedural Approach\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ */\r
+public class ShaderUtils {\r
+\r
+ public static final float[] i2c(final int color) {\r
+ return new float[] { (color & 0x00ff0000) / 256f, (color & 0x0000ff00) / 256f, (color & 0x000000ff) / 256f,\r
+ (color & 0xff000000) / 256f };\r
+ }\r
+\r
+ public static final int c2i(final float[] color) {\r
+ return (color.length == 4 ? (int) (color[3] * 256) : 0xff000000) | ((int) (color[0] * 256) << 16) | ((int) (color[1] * 256) << 8)\r
+ | (int) (color[2] * 256);\r
+ }\r
+\r
+ public static final float mix(final float a, final float b, final float f) {\r
+ return (1 - f) * a + f * b;\r
+ }\r
+\r
+ public static final Color mix(final Color a, final Color b, final float f) {\r
+ return new Color((int) ShaderUtils.clamp(ShaderUtils.mix(a.getRed(), b.getRed(), f), 0, 255), (int) ShaderUtils.clamp(\r
+ ShaderUtils.mix(a.getGreen(), b.getGreen(), f), 0, 255), (int) ShaderUtils.clamp(\r
+ ShaderUtils.mix(a.getBlue(), b.getBlue(), f), 0, 255));\r
+ }\r
+\r
+ public static final int mix(final int a, final int b, final float f) {\r
+ return (int) ((1 - f) * a + f * b);\r
+ }\r
+\r
+ public static final float[] mix(final float[] c1, final float[] c2, final float f) {\r
+ return new float[] { ShaderUtils.mix(c1[0], c2[0], f), ShaderUtils.mix(c1[1], c2[1], f), ShaderUtils.mix(c1[2], c2[2], f) };\r
+ }\r
+\r
+ public static final float step(final float a, final float x) {\r
+ return x < a ? 0 : 1;\r
+ }\r
+\r
+ public static final float boxstep(final float a, final float b, final float x) {\r
+ return ShaderUtils.clamp((x - a) / (b - a), 0, 1);\r
+ }\r
+\r
+ public static final float pulse(final float a, final float b, final float x) {\r
+ return ShaderUtils.step(a, x) - ShaderUtils.step(b, x);\r
+ }\r
+\r
+ public static final float clamp(final float x, final float a, final float b) {\r
+ return x < a ? a : x > b ? b : x;\r
+ }\r
+\r
+ public static final float min(final float a, final float b) {\r
+ return a < b ? a : b;\r
+ }\r
+\r
+ public static final float max(final float a, final float b) {\r
+ return a > b ? a : b;\r
+ }\r
+\r
+ public static final float abs(final float x) {\r
+ return x < 0 ? -x : x;\r
+ }\r
+\r
+ public static final float smoothstep(final float a, final float b, final float x) {\r
+ if (x < a) {\r
+ return 0;\r
+ } else if (x > b) {\r
+ return 1;\r
+ }\r
+ float xx = (x - a) / (b - a);\r
+ return xx * xx * (3 - 2 * xx);\r
+ }\r
+\r
+ public static final float mod(final float a, final float b) {\r
+ int n = (int) (a / b);\r
+ float aa = a - n * b;\r
+ if (aa < 0) {\r
+ aa += b;\r
+ }\r
+ return aa;\r
+ }\r
+\r
+ public static final int floor(final float x) {\r
+ return x > 0 ? (int) x : (int) x - 1;\r
+ }\r
+\r
+ public static final float ceil(final float x) {\r
+ return (int) x + (x > 0 && x != (int) x ? 1 : 0);\r
+ }\r
+\r
+ public static final float spline(float x, final float[] knot) {\r
+ float CR00 = -0.5f;\r
+ float CR01 = 1.5f;\r
+ float CR02 = -1.5f;\r
+ float CR03 = 0.5f;\r
+ float CR10 = 1.0f;\r
+ float CR11 = -2.5f;\r
+ float CR12 = 2.0f;\r
+ float CR13 = -0.5f;\r
+ float CR20 = -0.5f;\r
+ float CR21 = 0.0f;\r
+ float CR22 = 0.5f;\r
+ float CR23 = 0.0f;\r
+ float CR30 = 0.0f;\r
+ float CR31 = 1.0f;\r
+ float CR32 = 0.0f;\r
+ float CR33 = 0.0f;\r
+\r
+ int span;\r
+ int nspans = knot.length - 3;\r
+ float c0, c1, c2, c3; /* coefficients of the cubic. */\r
+ if (nspans < 1) {/* illegal */\r
+ throw new RuntimeException("Spline has too few knots.");\r
+ }\r
+ /* Find the appropriate 4-point span of the spline. */\r
+ x = ShaderUtils.clamp(x, 0, 1) * nspans;\r
+ span = (int) x;\r
+ if (span >= knot.length - 3) {\r
+ span = knot.length - 3;\r
+ }\r
+ x -= span;\r
+ /* Evaluate the span cubic at x using Horner’s rule. */\r
+ c3 = CR00 * knot[span + 0] + CR01 * knot[span + 1] + CR02 * knot[span + 2] + CR03 * knot[span + 3];\r
+ c2 = CR10 * knot[span + 0] + CR11 * knot[span + 1] + CR12 * knot[span + 2] + CR13 * knot[span + 3];\r
+ c1 = CR20 * knot[span + 0] + CR21 * knot[span + 1] + CR22 * knot[span + 2] + CR23 * knot[span + 3];\r
+ c0 = CR30 * knot[span + 0] + CR31 * knot[span + 1] + CR32 * knot[span + 2] + CR33 * knot[span + 3];\r
+ return ((c3 * x + c2) * x + c1) * x + c0;\r
+ }\r
+\r
+ public static final float[] spline(final float x, final float[][] knots) {\r
+ float[] retval = new float[knots.length];\r
+ for (int i = 0; i < knots.length; i++) {\r
+ retval[i] = ShaderUtils.spline(x, knots[i]);\r
+ }\r
+ return retval;\r
+ }\r
+\r
+ public static final float gammaCorrection(final float gamma, final float x) {\r
+ return (float) Math.pow(x, 1 / gamma);\r
+ }\r
+\r
+ public static final float bias(final float b, final float x) {\r
+ return (float) Math.pow(x, Math.log(b) / Math.log(0.5));\r
+ }\r
+\r
+ public static final float gain(final float g, final float x) {\r
+ return x < 0.5 ? ShaderUtils.bias(1 - g, 2 * x) / 2 : 1 - ShaderUtils.bias(1 - g, 2 - 2 * x) / 2;\r
+ }\r
+\r
+ public static final float sinValue(final float s, final float minFreq, final float maxFreq, final float swidth) {\r
+ float value = 0;\r
+ float cutoff = ShaderUtils.clamp(0.5f / swidth, 0, maxFreq);\r
+ float f;\r
+ for (f = minFreq; f < 0.5 * cutoff; f *= 2) {\r
+ value += Math.sin(2 * Math.PI * f * s) / f;\r
+ }\r
+ float fade = ShaderUtils.clamp(2 * (cutoff - f) / cutoff, 0, 1);\r
+ value += fade * Math.sin(2 * Math.PI * f * s) / f;\r
+ return value;\r
+ }\r
+\r
+ public static final float length(final float x, final float y, final float z) {\r
+ return (float) Math.sqrt(x * x + y * y + z * z);\r
+ }\r
+\r
+ public static final float[] rotate(final float[] v, final float[][] m) {\r
+ float x = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2];\r
+ float y = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2];\r
+ float z = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2];\r
+ return new float[] { x, y, z };\r
+ }\r
+\r
+ public static final float[][] calcRotationMatrix(final float ax, final float ay, final float az) {\r
+ float[][] retval = new float[3][3];\r
+ float cax = (float) Math.cos(ax);\r
+ float sax = (float) Math.sin(ax);\r
+ float cay = (float) Math.cos(ay);\r
+ float say = (float) Math.sin(ay);\r
+ float caz = (float) Math.cos(az);\r
+ float saz = (float) Math.sin(az);\r
+\r
+ retval[0][0] = cay * caz;\r
+ retval[0][1] = -cay * saz;\r
+ retval[0][2] = say;\r
+ retval[1][0] = sax * say * caz + cax * saz;\r
+ retval[1][1] = -sax * say * saz + cax * caz;\r
+ retval[1][2] = -sax * cay;\r
+ retval[2][0] = -cax * say * caz + sax * saz;\r
+ retval[2][1] = cax * say * saz + sax * caz;\r
+ retval[2][2] = cax * cay;\r
+\r
+ return retval;\r
+ }\r
+\r
+ public static final float[] normalize(final float[] v) {\r
+ float l = ShaderUtils.length(v);\r
+ float[] r = new float[v.length];\r
+ int i = 0;\r
+ for (float vv : v) {\r
+ r[i++] = vv / l;\r
+ }\r
+ return r;\r
+ }\r
+\r
+ public static final float length(final float[] v) {\r
+ float s = 0;\r
+ for (float vv : v) {\r
+ s += vv * vv;\r
+ }\r
+ return (float) Math.sqrt(s);\r
+ }\r
+\r
+ public static final ByteBuffer getImageDataFromImage(BufferedImage bufferedImage) {\r
+ WritableRaster wr;\r
+ DataBuffer db;\r
+\r
+ BufferedImage bi = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB);\r
+ Graphics2D g = bi.createGraphics();\r
+ g.drawImage(bufferedImage, null, null);\r
+ bufferedImage = bi;\r
+ wr = bi.getRaster();\r
+ db = wr.getDataBuffer();\r
+\r
+ DataBufferInt dbi = (DataBufferInt) db;\r
+ int[] data = dbi.getData();\r
+\r
+ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4);\r
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);\r
+ byteBuffer.asIntBuffer().put(data);\r
+ byteBuffer.flip();\r
+\r
+ return byteBuffer;\r
+ }\r
+\r
+ public static float frac(float f) {\r
+ return f - ShaderUtils.floor(f);\r
+ }\r
+\r
+ public static float[] floor(float[] fs) {\r
+ float[] retval = new float[fs.length];\r
+ for (int i = 0; i < fs.length; i++) {\r
+ retval[i] = ShaderUtils.floor(fs[i]);\r
+ }\r
+ return retval;\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.basis;\r
+\r
+import com.jme3.terrain.noise.Basis;\r
+import com.jme3.terrain.noise.filter.AbstractFilter;\r
+import com.jme3.terrain.noise.modulator.Modulator;\r
+import java.nio.FloatBuffer;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+public class FilteredBasis extends AbstractFilter implements Basis {\r
+\r
+ private Basis basis;\r
+ private List<Modulator> modulators = new ArrayList<Modulator>();\r
+ private float scale;\r
+\r
+ public FilteredBasis() {}\r
+\r
+ public FilteredBasis(Basis basis) {\r
+ this.basis = basis;\r
+ }\r
+\r
+ public Basis getBasis() {\r
+ return this.basis;\r
+ }\r
+\r
+ public void setBasis(Basis basis) {\r
+ this.basis = basis;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) {\r
+ return data;\r
+ }\r
+\r
+ @Override\r
+ public void init() {\r
+ this.basis.init();\r
+ }\r
+\r
+ @Override\r
+ public Basis setScale(float scale) {\r
+ this.scale = scale;\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public float getScale() {\r
+ return this.scale;\r
+ }\r
+\r
+ @Override\r
+ public Basis addModulator(Modulator modulator) {\r
+ this.modulators.add(modulator);\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public float value(float x, float y, float z) {\r
+ throw new UnsupportedOperationException(\r
+ "Method value cannot be called on FilteredBasis and its descendants. Use getBuffer instead!");\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer getBuffer(float sx, float sy, float base, int size) {\r
+ int margin = this.getMargin(size, 0);\r
+ int workSize = size + 2 * margin;\r
+ FloatBuffer retval = this.basis.getBuffer(sx - margin, sy - margin, base, workSize);\r
+ return this.clip(this.doFilter(sx, sy, base, retval, workSize), workSize, size, margin);\r
+ }\r
+\r
+ public FloatBuffer clip(FloatBuffer buf, int origSize, int newSize, int offset) {\r
+ FloatBuffer result = FloatBuffer.allocate(newSize * newSize);\r
+\r
+ float[] orig = buf.array();\r
+ for (int i = offset; i < offset + newSize; i++) {\r
+ result.put(orig, i * origSize + offset, newSize);\r
+ }\r
+\r
+ return result;\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.basis;\r
+\r
+import com.jme3.terrain.noise.ShaderUtils;\r
+\r
+// JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN.\r
+/**\r
+ * Perlin's default implementation of Improved Perlin Noise\r
+ * designed to work with Noise base\r
+ */\r
+public final class ImprovedNoise extends Noise {\r
+\r
+ @Override\r
+ public void init() {\r
+\r
+ }\r
+\r
+ static public float noise(float x, float y, float z) {\r
+ int X = ShaderUtils.floor(x), // FIND UNIT CUBE THAT\r
+ Y = ShaderUtils.floor(y), // CONTAINS POINT.\r
+ Z = ShaderUtils.floor(z);\r
+ x -= X; // FIND RELATIVE X,Y,Z\r
+ y -= Y; // OF POINT IN CUBE.\r
+ z -= Z;\r
+ X = X & 255;\r
+ Y = Y & 255;\r
+ Z = Z & 255;\r
+ float u = ImprovedNoise.fade(x), // COMPUTE FADE CURVES\r
+ v = ImprovedNoise.fade(y), // FOR EACH OF X,Y,Z.\r
+ w = ImprovedNoise.fade(z);\r
+ int A = ImprovedNoise.p[X] + Y;\r
+ int AA = ImprovedNoise.p[A] + Z;\r
+ int AB = ImprovedNoise.p[A + 1] + Z;\r
+ int B = ImprovedNoise.p[X + 1] + Y;\r
+ int BA = ImprovedNoise.p[B] + Z;\r
+ int BB = ImprovedNoise.p[B + 1] + Z;\r
+\r
+ return ImprovedNoise.lerp(\r
+ w,\r
+ ImprovedNoise.lerp(\r
+ v,\r
+ ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA], x, y, z),\r
+ ImprovedNoise.grad3(ImprovedNoise.p[BA], x - 1, y, z)), // BLENDED\r
+ ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB], x, y - 1, z), // RESULTS\r
+ ImprovedNoise.grad3(ImprovedNoise.p[BB], x - 1, y - 1, z))),// FROM\r
+ ImprovedNoise.lerp(v,\r
+ ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA + 1], x, y, z - 1), // CORNERS\r
+ ImprovedNoise.grad3(ImprovedNoise.p[BA + 1], x - 1, y, z - 1)), // OF\r
+ ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB + 1], x, y - 1, z - 1),\r
+ ImprovedNoise.grad3(ImprovedNoise.p[BB + 1], x - 1, y - 1, z - 1))));\r
+ }\r
+\r
+ static final float fade(final float t) {\r
+ return t * t * t * (t * (t * 6 - 15) + 10);\r
+ }\r
+\r
+ static final float lerp(final float t, final float a, final float b) {\r
+ return a + t * (b - a);\r
+ }\r
+\r
+ static float grad(final int hash, final float x, final float y, final float z) {\r
+ int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE\r
+ float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.\r
+ v = h < 4 ? y : h == 12 || h == 14 ? x : z;\r
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);\r
+ }\r
+\r
+ static final float grad3(final int hash, final float x, final float y, final float z) {\r
+ int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE\r
+ return x * ImprovedNoise.GRAD3[h][0] + y * ImprovedNoise.GRAD3[h][1] + z * ImprovedNoise.GRAD3[h][2];\r
+ }\r
+\r
+ static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36,\r
+ 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11,\r
+ 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158,\r
+ 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80,\r
+ 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217,\r
+ 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183,\r
+ 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113,\r
+ 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235,\r
+ 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205,\r
+ 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 };\r
+\r
+ private static float[][] GRAD3 = new float[][] { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 },\r
+ { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, -1, 1 },\r
+ { 0, 1, 1 } };\r
+\r
+ static {\r
+ for (int i = 0; i < 256; i++) {\r
+ ImprovedNoise.p[256 + i] = ImprovedNoise.p[i] = ImprovedNoise.permutation[i];\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public float value(final float x, final float y, final float z) {\r
+ return ImprovedNoise.noise(this.scale * x, this.scale * y, this.scale * z);\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.basis;\r
+\r
+import com.jme3.terrain.noise.Basis;\r
+import com.jme3.terrain.noise.modulator.Modulator;\r
+import com.jme3.terrain.noise.modulator.NoiseModulator;\r
+import java.nio.FloatBuffer;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+/**\r
+ * Utility base class for Noise implementations\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ */\r
+public abstract class Noise implements Basis {\r
+\r
+ protected List<Modulator> modulators = new ArrayList<Modulator>();\r
+\r
+ protected float scale = 1.0f;\r
+\r
+ @Override\r
+ public String toString() {\r
+ return this.getClass().getSimpleName();\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer getBuffer(float sx, float sy, float base, int size) {\r
+ FloatBuffer retval = FloatBuffer.allocate(size * size);\r
+ for (int y = 0; y < size; y++) {\r
+ for (int x = 0; x < size; x++) {\r
+ retval.put(this.modulate((sx + x) / size, (sy + y) / size, base));\r
+ }\r
+ }\r
+ return retval;\r
+ }\r
+\r
+ public float modulate(float x, float y, float z) {\r
+ float retval = this.value(x, y, z);\r
+ for (Modulator m : this.modulators) {\r
+ if (m instanceof NoiseModulator) {\r
+ retval = m.value(retval);\r
+ }\r
+ }\r
+ return retval;\r
+ }\r
+\r
+ @Override\r
+ public Basis addModulator(Modulator modulator) {\r
+ this.modulators.add(modulator);\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public Basis setScale(float scale) {\r
+ this.scale = scale;\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public float getScale() {\r
+ return this.scale;\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.basis;\r
+\r
+import com.jme3.terrain.noise.Basis;\r
+\r
+/**\r
+ * A simple aggregator basis. Takes two basis functions and a rate and return\r
+ * some mixed values\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ */\r
+public class NoiseAggregator extends Noise {\r
+\r
+ private final float rate;\r
+ private final Basis a;\r
+ private final Basis b;\r
+\r
+ public NoiseAggregator(final Basis a, final Basis b, final float rate) {\r
+ this.a = a;\r
+ this.b = b;\r
+ this.rate = rate;\r
+ }\r
+\r
+ @Override\r
+ public void init() {\r
+ this.a.init();\r
+ this.b.init();\r
+ }\r
+\r
+ @Override\r
+ public float value(final float x, final float y, final float z) {\r
+ return this.a.value(x, y, z) * (1 - this.rate) + this.rate * this.b.value(x, y, z);\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import com.jme3.terrain.noise.Filter;\r
+import java.nio.FloatBuffer;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+public abstract class AbstractFilter implements Filter {\r
+\r
+ protected List<Filter> preFilters = new ArrayList<Filter>();\r
+ protected List<Filter> postFilters = new ArrayList<Filter>();\r
+\r
+ private boolean enabled = true;\r
+\r
+ @Override\r
+ public Filter addPreFilter(Filter filter) {\r
+ this.preFilters.add(filter);\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public Filter addPostFilter(Filter filter) {\r
+ this.postFilters.add(filter);\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size) {\r
+ if (!this.isEnabled()) {\r
+ return data;\r
+ }\r
+ FloatBuffer retval = data;\r
+ for (Filter f : this.preFilters) {\r
+ retval = f.doFilter(sx, sy, base, retval, size);\r
+ }\r
+ retval = this.filter(sx, sy, base, retval, size);\r
+ for (Filter f : this.postFilters) {\r
+ retval = f.doFilter(sx, sy, base, retval, size);\r
+ }\r
+ return retval;\r
+ }\r
+\r
+ public abstract FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size);\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ // TODO sums up all the margins from filters... maybe there's a more\r
+ // efficient algorithm\r
+ if (!this.isEnabled()) {\r
+ return margin;\r
+ }\r
+ for (Filter f : this.preFilters) {\r
+ margin = f.getMargin(size, margin);\r
+ }\r
+ for (Filter f : this.postFilters) {\r
+ margin = f.getMargin(size, margin);\r
+ }\r
+ return margin;\r
+ }\r
+\r
+ @Override\r
+ public boolean isEnabled() {\r
+ return this.enabled;\r
+ }\r
+\r
+ public void setEnabled(boolean enabled) {\r
+ this.enabled = enabled;\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import com.jme3.terrain.noise.Basis;\r
+import java.nio.FloatBuffer;\r
+\r
+public class HydraulicErodeFilter extends AbstractFilter {\r
+\r
+ private Basis waterMap;\r
+ private Basis sedimentMap;\r
+ private float Kr;\r
+ private float Ks;\r
+ private float Ke;\r
+ private float Kc;\r
+ private float T;\r
+\r
+ public void setKc(float kc) {\r
+ this.Kc = kc;\r
+ }\r
+\r
+ public void setKe(float ke) {\r
+ this.Ke = ke;\r
+ }\r
+\r
+ public void setKr(float kr) {\r
+ this.Kr = kr;\r
+ }\r
+\r
+ public void setKs(float ks) {\r
+ this.Ks = ks;\r
+ }\r
+\r
+ public void setSedimentMap(Basis sedimentMap) {\r
+ this.sedimentMap = sedimentMap;\r
+ }\r
+\r
+ public void setT(float t) {\r
+ this.T = t;\r
+ }\r
+\r
+ public void setWaterMap(Basis waterMap) {\r
+ this.waterMap = waterMap;\r
+ }\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ return super.getMargin(size, margin) + 1;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) {\r
+ float[] ga = buffer.array();\r
+ // float[] wa = this.waterMap.getBuffer(sx, sy, base, workSize).array();\r
+ // float[] sa = this.sedimentMap.getBuffer(sx, sy, base,\r
+ // workSize).array();\r
+ float[] wt = new float[workSize * workSize];\r
+ float[] st = new float[workSize * workSize];\r
+\r
+ int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 };\r
+\r
+ // step 1. water arrives and step 2. captures material\r
+ for (int y = 0; y < workSize; y++) {\r
+ for (int x = 0; x < workSize; x++) {\r
+ int idx = y * workSize + x;\r
+ float wtemp = this.Kr; // * wa[idx];\r
+ float stemp = this.Ks; // * sa[idx];\r
+ if (wtemp > 0) {\r
+ wt[idx] += wtemp;\r
+ if (stemp > 0) {\r
+ ga[idx] -= stemp * wt[idx];\r
+ st[idx] += stemp * wt[idx];\r
+ }\r
+ }\r
+\r
+ // step 3. water is transported to it's neighbours\r
+ float a = ga[idx] + wt[idx];\r
+ // float[] aj = new float[idxrel.length];\r
+ float amax = 0;\r
+ int amaxidx = -1;\r
+ float ac = 0;\r
+ float dtotal = 0;\r
+\r
+ for (int j = 0; j < idxrel.length; j++) {\r
+ if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize) {\r
+ float at = ga[idx + idxrel[j]] + wt[idx + idxrel[j]];\r
+ if (a - at > a - amax) {\r
+ dtotal += at;\r
+ amax = at;\r
+ amaxidx = j;\r
+ ac++;\r
+ }\r
+ }\r
+ }\r
+\r
+ float aa = (dtotal + a) / (ac + 1);\r
+ // for (int j = 0; j < idxrel.length; j++) {\r
+ // if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize && a -\r
+ // aj[j] > 0) {\r
+ if (amaxidx > -1) {\r
+ float dwj = Math.min(wt[idx], a - aa) * (a - amax) / dtotal;\r
+ float dsj = st[idx] * dwj / wt[idx];\r
+ wt[idx] -= dwj;\r
+ st[idx] -= dsj;\r
+ wt[idx + idxrel[amaxidx]] += dwj;\r
+ st[idx + idxrel[amaxidx]] += dsj;\r
+ }\r
+ // }\r
+\r
+ // step 4. water evaporates and deposits material\r
+ wt[idx] = wt[idx] * (1 - this.Ke);\r
+ if (wt[idx] < this.T) {\r
+ wt[idx] = 0;\r
+ }\r
+ float smax = this.Kc * wt[idx];\r
+ if (st[idx] > smax) {\r
+ ga[idx] += st[idx] - smax;\r
+ st[idx] -= st[idx] - smax;\r
+ }\r
+ }\r
+ }\r
+\r
+ return buffer;\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import com.jme3.terrain.noise.Filter;\r
+import java.nio.FloatBuffer;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+public class IterativeFilter extends AbstractFilter {\r
+\r
+ private int iterations;\r
+\r
+ private List<Filter> preIterateFilters = new ArrayList<Filter>();\r
+ private List<Filter> postIterateFilters = new ArrayList<Filter>();\r
+ private Filter filter;\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ if (!this.isEnabled()) {\r
+ return margin;\r
+ }\r
+ for (Filter f : this.preIterateFilters) {\r
+ margin = f.getMargin(size, margin);\r
+ }\r
+ margin = this.filter.getMargin(size, margin);\r
+ for (Filter f : this.postIterateFilters) {\r
+ margin = f.getMargin(size, margin);\r
+ }\r
+ return this.iterations * margin + super.getMargin(size, margin);\r
+ }\r
+\r
+ public void setIterations(int iterations) {\r
+ this.iterations = iterations;\r
+ }\r
+\r
+ public int getIterations() {\r
+ return this.iterations;\r
+ }\r
+\r
+ public IterativeFilter addPostIterateFilter(Filter filter) {\r
+ this.postIterateFilters.add(filter);\r
+ return this;\r
+ }\r
+\r
+ public IterativeFilter addPreIterateFilter(Filter filter) {\r
+ this.preIterateFilters.add(filter);\r
+ return this;\r
+ }\r
+\r
+ public void setFilter(Filter filter) {\r
+ this.filter = filter;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) {\r
+ if (!this.isEnabled()) {\r
+ return data;\r
+ }\r
+ FloatBuffer retval = data;\r
+\r
+ for (int i = 0; i < this.iterations; i++) {\r
+ for (Filter f : this.preIterateFilters) {\r
+ retval = f.doFilter(sx, sy, base, retval, size);\r
+ }\r
+ retval = this.filter.doFilter(sx, sy, base, retval, size);\r
+ for (Filter f : this.postIterateFilters) {\r
+ retval = f.doFilter(sx, sy, base, retval, size);\r
+ }\r
+ }\r
+\r
+ return retval;\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import java.nio.FloatBuffer;\r
+\r
+public class OptimizedErode extends AbstractFilter {\r
+\r
+ private float talus;\r
+ private int radius;\r
+\r
+ public OptimizedErode setRadius(int radius) {\r
+ this.radius = radius;\r
+ return this;\r
+ }\r
+\r
+ public int getRadius() {\r
+ return this.radius;\r
+ }\r
+\r
+ public OptimizedErode setTalus(float talus) {\r
+ this.talus = talus;\r
+ return this;\r
+ }\r
+\r
+ public float getTalus() {\r
+ return this.talus;\r
+ }\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ return super.getMargin(size, margin) + this.radius;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) {\r
+ float[] tmp = buffer.array();\r
+ float[] retval = new float[tmp.length];\r
+\r
+ for (int y = this.radius + 1; y < size - this.radius; y++) {\r
+ for (int x = this.radius + 1; x < size - this.radius; x++) {\r
+ int idx = y * size + x;\r
+ float h = tmp[idx];\r
+\r
+ float horizAvg = 0;\r
+ int horizCount = 0;\r
+ float vertAvg = 0;\r
+ int vertCount = 0;\r
+\r
+ boolean horizT = false;\r
+ boolean vertT = false;\r
+\r
+ for (int i = 0; i >= -this.radius; i--) {\r
+ int idxV = (y + i) * size + x;\r
+ int idxVL = (y + i - 1) * size + x;\r
+ int idxH = y * size + x + i;\r
+ int idxHL = y * size + x + i - 1;\r
+ float hV = tmp[idxV];\r
+ float hH = tmp[idxH];\r
+\r
+ if (Math.abs(h - hV) > this.talus && Math.abs(h - tmp[idxVL]) > this.talus || vertT) {\r
+ vertT = true;\r
+ } else {\r
+ if (Math.abs(h - hV) <= this.talus) {\r
+ vertAvg += hV;\r
+ vertCount++;\r
+ }\r
+ }\r
+\r
+ if (Math.abs(h - hH) > this.talus && Math.abs(h - tmp[idxHL]) > this.talus || horizT) {\r
+ horizT = true;\r
+ } else {\r
+ if (Math.abs(h - hH) <= this.talus) {\r
+ horizAvg += hH;\r
+ horizCount++;\r
+ }\r
+ }\r
+ }\r
+\r
+ retval[idx] = 0.5f * (vertAvg / (vertCount > 0 ? vertCount : 1) + horizAvg / (horizCount > 0 ? horizCount : 1));\r
+ }\r
+ }\r
+ return FloatBuffer.wrap(retval);\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import com.jme3.terrain.noise.ShaderUtils;\r
+import com.jme3.terrain.noise.fractal.FractalSum;\r
+import java.nio.FloatBuffer;\r
+import java.util.logging.Logger;\r
+\r
+public class PerturbFilter extends AbstractFilter {\r
+\r
+ private float magnitude;\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ margin = super.getMargin(size, margin);\r
+ return (int) Math.floor(this.magnitude * (margin + size) + margin);\r
+ }\r
+\r
+ public void setMagnitude(float magnitude) {\r
+ this.magnitude = magnitude;\r
+ }\r
+\r
+ public float getMagnitude() {\r
+ return this.magnitude;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int workSize) {\r
+ float[] arr = data.array();\r
+ int origSize = (int) Math.ceil(workSize / (2 * this.magnitude + 1));\r
+ int offset = (workSize - origSize) / 2;\r
+ Logger.getLogger(PerturbFilter.class.getCanonicalName()).info(\r
+ "Found origSize : " + origSize + " and offset: " + offset + " for workSize : " + workSize + " and magnitude : "\r
+ + this.magnitude);\r
+ float[] retval = new float[workSize * workSize];\r
+ float[] perturbx = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base, workSize).array();\r
+ float[] perturby = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base + 1, workSize).array();\r
+ for (int y = 0; y < workSize; y++) {\r
+ for (int x = 0; x < workSize; x++) {\r
+ // Perturb our coordinates\r
+ float noisex = perturbx[y * workSize + x];\r
+ float noisey = perturby[y * workSize + x];\r
+\r
+ int px = (int) (origSize * noisex * this.magnitude);\r
+ int py = (int) (origSize * noisey * this.magnitude);\r
+\r
+ float c00 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x - px, workSize)];\r
+ float c01 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x + px, workSize)];\r
+ float c10 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x - px, workSize)];\r
+ float c11 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x + px, workSize)];\r
+\r
+ float c0 = ShaderUtils.mix(c00, c01, noisex);\r
+ float c1 = ShaderUtils.mix(c10, c11, noisex);\r
+ retval[y * workSize + x] = ShaderUtils.mix(c0, c1, noisey);\r
+ }\r
+ }\r
+ return FloatBuffer.wrap(retval);\r
+ }\r
+\r
+ private int wrap(int v, int size) {\r
+ if (v < 0) {\r
+ return v + size - 1;\r
+ } else if (v >= size) {\r
+ return v - size;\r
+ } else {\r
+ return v;\r
+ }\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import java.nio.FloatBuffer;\r
+\r
+public class SmoothFilter extends AbstractFilter {\r
+\r
+ private int radius;\r
+ private float effect;\r
+\r
+ public void setRadius(int radius) {\r
+ this.radius = radius;\r
+ }\r
+\r
+ public int getRadius() {\r
+ return this.radius;\r
+ }\r
+\r
+ public void setEffect(float effect) {\r
+ this.effect = effect;\r
+ }\r
+\r
+ public float getEffect() {\r
+ return this.effect;\r
+ }\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ return super.getMargin(size, margin) + this.radius;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) {\r
+ float[] data = buffer.array();\r
+ float[] retval = new float[data.length];\r
+\r
+ for (int y = this.radius; y < size - this.radius; y++) {\r
+ for (int x = this.radius; x < size - this.radius; x++) {\r
+ int idx = y * size + x;\r
+ float n = 0;\r
+ for (int i = -this.radius; i < this.radius + 1; i++) {\r
+ for (int j = -this.radius; j < this.radius + 1; j++) {\r
+ n += data[(y + i) * size + x + j];\r
+ }\r
+ }\r
+ retval[idx] = this.effect * n / (4 * this.radius * (this.radius + 1) + 1) + (1 - this.effect) * data[idx];\r
+ }\r
+ }\r
+\r
+ return FloatBuffer.wrap(retval);\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.filter;\r
+\r
+import java.nio.FloatBuffer;\r
+\r
+public class ThermalErodeFilter extends AbstractFilter {\r
+\r
+ private float talus;\r
+ private float c;\r
+\r
+ public ThermalErodeFilter setC(float c) {\r
+ this.c = c;\r
+ return this;\r
+ }\r
+\r
+ public ThermalErodeFilter setTalus(float talus) {\r
+ this.talus = talus;\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public int getMargin(int size, int margin) {\r
+ return super.getMargin(size, margin) + 1;\r
+ }\r
+\r
+ @Override\r
+ public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) {\r
+ float[] ga = buffer.array();\r
+ float[] sa = new float[workSize * workSize];\r
+\r
+ int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 };\r
+\r
+ for (int y = 0; y < workSize; y++) {\r
+ for (int x = 0; x < workSize; x++) {\r
+ int idx = y * workSize + x;\r
+ ga[idx] += sa[idx];\r
+ sa[idx] = 0;\r
+\r
+ float[] deltas = new float[idxrel.length];\r
+ float deltaMax = this.talus;\r
+ float deltaTotal = 0;\r
+\r
+ for (int j = 0; j < idxrel.length; j++) {\r
+ if (idx + idxrel[j] > 0 && idx + idxrel[j] < ga.length) {\r
+ float dj = ga[idx] - ga[idx + idxrel[j]];\r
+ if (dj > this.talus) {\r
+ deltas[j] = dj;\r
+ deltaTotal += dj;\r
+ if (dj > deltaMax) {\r
+ deltaMax = dj;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ for (int j = 0; j < idxrel.length; j++) {\r
+ if (deltas[j] != 0) {\r
+ float d = this.c * (deltaMax - this.talus) * deltas[j] / deltaTotal;\r
+ if (d > ga[idx] + sa[idx]) {\r
+ d = ga[idx] + sa[idx];\r
+ }\r
+ sa[idx] -= d;\r
+ sa[idx + idxrel[j]] += d;\r
+ }\r
+ deltas[j] = 0;\r
+ }\r
+ }\r
+ }\r
+\r
+ return buffer;\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.fractal;\r
+\r
+import com.jme3.terrain.noise.Basis;\r
+\r
+/**\r
+ * Interface for a general fractal basis.\r
+ * \r
+ * Takes any number of basis funcions to work with and a few common parameters\r
+ * for noise fractals\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ */\r
+public interface Fractal extends Basis {\r
+\r
+ public Fractal setOctaves(final float octaves);\r
+\r
+ public Fractal setFrequency(final float frequency);\r
+\r
+ public Fractal setRoughness(final float roughness);\r
+\r
+ public Fractal setAmplitude(final float amplitude);\r
+\r
+ public Fractal setLacunarity(final float lacunarity);\r
+\r
+ public Fractal addBasis(Basis basis);\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.fractal;\r
+\r
+import com.jme3.terrain.noise.Basis;\r
+import com.jme3.terrain.noise.ShaderUtils;\r
+import com.jme3.terrain.noise.basis.ImprovedNoise;\r
+import com.jme3.terrain.noise.basis.Noise;\r
+\r
+/**\r
+ * FractalSum is the simplest form of fractal functions summing up a few octaves\r
+ * of the noise value with an ever decreasing (0 < roughness < 1) amplitude\r
+ * \r
+ * lacunarity = 2.0f is the classical octave distance\r
+ * \r
+ * Note: though noise basis functions are generally designed to return value\r
+ * between -1..1, there sum can easily be made to extend out of this range. To\r
+ * handle this is up to the user.\r
+ * \r
+ * @author Anthyon\r
+ * \r
+ */\r
+public class FractalSum extends Noise implements Fractal {\r
+\r
+ private Basis basis;\r
+ private float lacunarity;\r
+ private float amplitude;\r
+ private float roughness;\r
+ private float frequency;\r
+ private float octaves;\r
+ private int maxFreq;\r
+\r
+ public FractalSum() {\r
+ this.basis = new ImprovedNoise();\r
+ this.lacunarity = 2.124367f;\r
+ this.amplitude = 1.0f;\r
+ this.roughness = 0.6f;\r
+ this.frequency = 1f;\r
+ this.setOctaves(1);\r
+ }\r
+\r
+ @Override\r
+ public float value(final float x, final float y, final float z) {\r
+ float total = 0;\r
+\r
+ for (float f = this.frequency, a = this.amplitude; f < this.maxFreq; f *= this.lacunarity, a *= this.roughness) {\r
+ total += this.basis.value(this.scale * x * f, this.scale * y * f, this.scale * z * f) * a;\r
+ }\r
+\r
+ return ShaderUtils.clamp(total, -1, 1);\r
+ }\r
+\r
+ @Override\r
+ public Fractal addBasis(final Basis basis) {\r
+ this.basis = basis;\r
+ return this;\r
+ }\r
+\r
+ public float getOctaves() {\r
+ return this.octaves;\r
+ }\r
+\r
+ @Override\r
+ public Fractal setOctaves(final float octaves) {\r
+ this.octaves = octaves;\r
+ this.maxFreq = 1 << (int) octaves;\r
+ return this;\r
+ }\r
+\r
+ public float getFrequency() {\r
+ return this.frequency;\r
+ }\r
+\r
+ @Override\r
+ public Fractal setFrequency(final float frequency) {\r
+ this.frequency = frequency;\r
+ return this;\r
+ }\r
+\r
+ public float getRoughness() {\r
+ return this.roughness;\r
+ }\r
+\r
+ @Override\r
+ public Fractal setRoughness(final float roughness) {\r
+ this.roughness = roughness;\r
+ return this;\r
+ }\r
+\r
+ public float getAmplitude() {\r
+ return this.amplitude;\r
+ }\r
+\r
+ @Override\r
+ public Fractal setAmplitude(final float amplitude) {\r
+ this.amplitude = amplitude;\r
+ return this;\r
+ }\r
+\r
+ public float getLacunarity() {\r
+ return this.lacunarity;\r
+ }\r
+\r
+ @Override\r
+ public Fractal setLacunarity(final float lacunarity) {\r
+ this.lacunarity = lacunarity;\r
+ return this;\r
+ }\r
+\r
+ @Override\r
+ public void init() {\r
+\r
+ }\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.modulator;\r
+\r
+import com.jme3.terrain.noise.ShaderUtils;\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+public class CatRom2 implements Modulator {\r
+\r
+ private int sampleRate = 100;\r
+\r
+ private final float[] table;\r
+\r
+ private static Map<Integer, CatRom2> instances = new HashMap<Integer, CatRom2>();\r
+\r
+ public CatRom2(final int sampleRate) {\r
+ this.sampleRate = sampleRate;\r
+ this.table = new float[4 * sampleRate + 1];\r
+ for (int i = 0; i < 4 * sampleRate + 1; i++) {\r
+ float x = i / (float) sampleRate;\r
+ x = (float) Math.sqrt(x);\r
+ if (x < 1) {\r
+ this.table[i] = 0.5f * (2 + x * x * (-5 + x * 3));\r
+ } else {\r
+ this.table[i] = 0.5f * (4 + x * (-8 + x * (5 - x)));\r
+ }\r
+ }\r
+ }\r
+\r
+ public static CatRom2 getInstance(final int sampleRate) {\r
+ if (!CatRom2.instances.containsKey(sampleRate)) {\r
+ CatRom2.instances.put(sampleRate, new CatRom2(sampleRate));\r
+ }\r
+ return CatRom2.instances.get(sampleRate);\r
+ }\r
+\r
+ @Override\r
+ public float value(final float... in) {\r
+ if (in[0] >= 4) {\r
+ return 0;\r
+ }\r
+ in[0] = in[0] * this.sampleRate + 0.5f;\r
+ int i = ShaderUtils.floor(in[0]);\r
+ if (i >= 4 * this.sampleRate + 1) {\r
+ return 0;\r
+ }\r
+ return this.table[i];\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.modulator;\r
+\r
+public interface Modulator {\r
+\r
+ public float value(float... in);\r
+\r
+}\r
--- /dev/null
+/**\r
+ * Copyright (c) 2011, Novyon Events\r
+ * \r
+ * All rights reserved.\r
+ * \r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are met:\r
+ * \r
+ * - Redistributions of source code must retain the above copyright notice, this\r
+ * list of conditions and the following disclaimer.\r
+ * \r
+ * - Redistributions in binary form must reproduce the above copyright notice,\r
+ * this list of conditions and the following disclaimer in the documentation\r
+ * and/or other materials provided with the distribution.\r
+ * \r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\r
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\r
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\r
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\r
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\r
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\r
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR\r
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\r
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ * \r
+ * @author Anthyon\r
+ */\r
+package com.jme3.terrain.noise.modulator;\r
+\r
+public interface NoiseModulator extends Modulator {\r
+\r
+}\r
package jme3test.terrain;
-import com.jme3.terrain.geomipmap.TerrainQuad;
-import java.util.ArrayList;
-import java.util.List;
-
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.ScreenshotAppState;
-import com.jme3.bullet.BulletAppState;
-import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
-import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
import com.jme3.bullet.control.CharacterControl;
-import com.jme3.bullet.control.RigidBodyControl;
-import com.jme3.input.KeyInput;
-import com.jme3.input.controls.ActionListener;
-import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
-import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
-import com.jme3.renderer.Camera;
import com.jme3.terrain.geomipmap.TerrainGrid;
-import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainGridLodControl;
import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
-import com.jme3.terrain.heightmap.FractalHeightMapGrid;
+import com.jme3.terrain.noise.ShaderUtils;
+import com.jme3.terrain.noise.basis.FilteredBasis;
+import com.jme3.terrain.noise.filter.IterativeFilter;
+import com.jme3.terrain.noise.filter.OptimizedErode;
+import com.jme3.terrain.noise.filter.PerturbFilter;
+import com.jme3.terrain.noise.filter.SmoothFilter;
+import com.jme3.terrain.noise.fractal.FractalSum;
+import com.jme3.terrain.noise.modulator.NoiseModulator;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
-import org.novyon.noise.ShaderUtils;
-import org.novyon.noise.basis.FilteredBasis;
-import org.novyon.noise.filter.IterativeFilter;
-import org.novyon.noise.filter.OptimizedErode;
-import org.novyon.noise.filter.PerturbFilter;
-import org.novyon.noise.filter.SmoothFilter;
-import org.novyon.noise.fractal.FractalSum;
-import org.novyon.noise.modulator.NoiseModulator;
public class TerrainFractalGridTest extends SimpleApplication {
private float grassScale = 64;
private float dirtScale = 16;
private float rockScale = 128;
- private boolean usePhysics = false;
public static void main(final String[] args) {
TerrainFractalGridTest app = new TerrainFractalGridTest();
ground.addPreFilter(this.iterate);
- this.terrain = new TerrainGrid("terrain", 33, 129, new FractalHeightMapGrid(ground, null, 256f));
+ this.terrain = new TerrainGrid("terrain", 33, 129, new FractalTileLoader(ground, 256f));
this.terrain.setMaterial(this.mat_terrain);
this.terrain.setLocalTranslation(0, 0, 0);
this.terrain.setLocalScale(2f, 1f, 2f);
this.rootNode.attachChild(this.terrain);
- List<Camera> cameras = new ArrayList<Camera>();
- cameras.add(this.getCamera());
- TerrainLodControl control = new TerrainLodControl(this.terrain, cameras);
- control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier
+ TerrainLodControl control = new TerrainGridLodControl(this.terrain, this.getCamera());
+ control.setLodCalculator(new DistanceLodCalculator(33, 2.7f)); // patch size, and a multiplier
this.terrain.addControl(control);
- final BulletAppState bulletAppState = new BulletAppState();
- stateManager.attach(bulletAppState);
- this.getCamera().setLocation(new Vector3f(0, 0, 0));
+ this.getCamera().setLocation(new Vector3f(0, 300, 0));
this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
- if (usePhysics) {
- CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
- player3 = new CharacterControl(capsuleShape, 0.5f);
- player3.setJumpSpeed(20);
- player3.setFallSpeed(10);
- player3.setGravity(10);
-
- player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 512, cam.getLocation().z));
-
- bulletAppState.getPhysicsSpace().add(player3);
-
- terrain.addListener("physicsStartListener", new TerrainGridListener() {
-
- public void gridMoved(Vector3f newCenter) {
- }
-
- public Material tileLoaded(Material material, Vector3f cell) {
- return material;
- }
-
- public void tileAttached(Vector3f cell, TerrainQuad quad) {
- quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
- bulletAppState.getPhysicsSpace().add(quad);
- }
-
- public void tileDetached(Vector3f cell, TerrainQuad quad) {
- bulletAppState.getPhysicsSpace().remove(quad);
- quad.removeControl(RigidBodyControl.class);
- }
-
- });
- }
- this.terrain.initialize(cam.getLocation());
- this.initKeys();
+
}
- private void initKeys() {
- // You can map one or several inputs to one named action
- this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
- this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
- this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
- this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
- this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
- this.inputManager.addListener(this.actionListener, "Lefts");
- this.inputManager.addListener(this.actionListener, "Rights");
- this.inputManager.addListener(this.actionListener, "Ups");
- this.inputManager.addListener(this.actionListener, "Downs");
- this.inputManager.addListener(this.actionListener, "Jumps");
- }
- private boolean left;
- private boolean right;
- private boolean up;
- private boolean down;
- private final ActionListener actionListener = new ActionListener() {
-
- @Override
- public void onAction(final String name, final boolean keyPressed, final float tpf) {
- if (name.equals("Lefts")) {
- if (keyPressed) {
- TerrainFractalGridTest.this.left = true;
- } else {
- TerrainFractalGridTest.this.left = false;
- }
- } else if (name.equals("Rights")) {
- if (keyPressed) {
- TerrainFractalGridTest.this.right = true;
- } else {
- TerrainFractalGridTest.this.right = false;
- }
- } else if (name.equals("Ups")) {
- if (keyPressed) {
- TerrainFractalGridTest.this.up = true;
- } else {
- TerrainFractalGridTest.this.up = false;
- }
- } else if (name.equals("Downs")) {
- if (keyPressed) {
- TerrainFractalGridTest.this.down = true;
- } else {
- TerrainFractalGridTest.this.down = false;
- }
- } else if (name.equals("Jumps")) {
- if (usePhysics) {
- TerrainFractalGridTest.this.player3.jump();
- }
- }
- }
- };
- private final Vector3f walkDirection = new Vector3f();
-
@Override
public void simpleUpdate(final float tpf) {
- Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
- Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
- this.walkDirection.set(0, 0, 0);
- if (this.left) {
- this.walkDirection.addLocal(camLeft);
- }
- if (this.right) {
- this.walkDirection.addLocal(camLeft.negate());
- }
- if (this.up) {
- this.walkDirection.addLocal(camDir);
- }
- if (this.down) {
- this.walkDirection.addLocal(camDir.negate());
- }
-
- if (usePhysics) {
- this.player3.setWalkDirection(this.walkDirection);
- this.cam.setLocation(this.player3.getPhysicsLocation());
- }
}
}
package jme3test.terrain;
-import java.util.ArrayList;
-import java.util.List;
-
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.ScreenshotAppState;
import com.jme3.asset.plugins.HttpZipLocator;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
-import com.jme3.renderer.Camera;
-import com.jme3.shader.VarType;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
import com.jme3.terrain.geomipmap.TerrainGrid;
import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainGridLodControl;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
-import com.jme3.terrain.heightmap.FractalHeightMapGrid;
-import com.jme3.terrain.heightmap.ImageBasedHeightMapGrid;
-import com.jme3.terrain.heightmap.Namer;
+import com.jme3.terrain.noise.ShaderUtils;
+import com.jme3.terrain.noise.basis.FilteredBasis;
+import com.jme3.terrain.noise.filter.IterativeFilter;
+import com.jme3.terrain.noise.filter.OptimizedErode;
+import com.jme3.terrain.noise.filter.PerturbFilter;
+import com.jme3.terrain.noise.filter.SmoothFilter;
+import com.jme3.terrain.noise.fractal.FractalSum;
+import com.jme3.terrain.noise.modulator.NoiseModulator;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.io.File;
-import org.novyon.noise.ShaderUtils;
-import org.novyon.noise.basis.FilteredBasis;
-import org.novyon.noise.filter.IterativeFilter;
-import org.novyon.noise.filter.OptimizedErode;
-import org.novyon.noise.filter.PerturbFilter;
-import org.novyon.noise.filter.SmoothFilter;
-import org.novyon.noise.fractal.FractalSum;
-import org.novyon.noise.modulator.NoiseModulator;
public class TerrainGridAlphaMapTest extends SimpleApplication {
private float grassScale = 64;
private float dirtScale = 16;
private float rockScale = 128;
- private boolean usePhysics = true;
+ private boolean usePhysics = false;
public static void main(final String[] args) {
TerrainGridAlphaMapTest app = new TerrainGridAlphaMapTest();
private OptimizedErode therm;
private SmoothFilter smooth;
private IterativeFilter iterate;
- private Material matRock;
+ private Material material;
private Material matWire;
@Override
al.setColor(ColorRGBA.White.mult(1.3f));
rootNode.addLight(al);
- File file = new File("mountains.zip");
+ File file = new File("TerrainGridTestData.zip");
if (!file.exists()) {
- assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/mountains.zip", HttpZipLocator.class);
+ assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
} else {
- assetManager.registerLocator("mountains.zip", ZipLocator.class);
+ assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
}
this.flyCam.setMoveSpeed(100f);
this.stateManager.attach(state);
// TERRAIN TEXTURE material
- matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
- matRock.setBoolean("useTriPlanarMapping", false);
- matRock.setBoolean("isTerrainGrid", true);
+ material = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+ material.setBoolean("useTriPlanarMapping", false);
+ //material.setBoolean("isTerrainGrid", true);
+ material.setFloat("Shininess", 0.0f);
// GRASS texture
Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
grass.setWrap(WrapMode.Repeat);
- matRock.setTexture("DiffuseMap", grass);
- matRock.setFloat("DiffuseMap_0_scale", grassScale);
+ material.setTexture("DiffuseMap", grass);
+ material.setFloat("DiffuseMap_0_scale", grassScale);
// DIRT texture
Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
dirt.setWrap(WrapMode.Repeat);
- matRock.setTexture("DiffuseMap_1", dirt);
- matRock.setFloat("DiffuseMap_1_scale", dirtScale);
+ material.setTexture("DiffuseMap_1", dirt);
+ material.setFloat("DiffuseMap_1_scale", dirtScale);
// ROCK texture
Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
rock.setWrap(WrapMode.Repeat);
- matRock.setTexture("DiffuseMap_2", rock);
- matRock.setFloat("DiffuseMap_2_scale", rockScale);
+ material.setTexture("DiffuseMap_2", rock);
+ material.setFloat("DiffuseMap_2_scale", rockScale);
// WIREFRAME material
matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
ground.addPreFilter(this.iterate);
- this.terrain = new TerrainGrid("terrain", 33, 257, new FractalHeightMapGrid(ground, null, 256));
- this.terrain.setMaterial(this.matRock);
+ this.terrain = new TerrainGrid("terrain", 33, 257, new FractalTileLoader(ground, 256));
+ this.terrain.setMaterial(this.material);
this.terrain.setLocalTranslation(0, 0, 0);
this.terrain.setLocalScale(2f, 1f, 2f);
this.rootNode.attachChild(this.terrain);
- List<Camera> cameras = new ArrayList<Camera>();
- cameras.add(this.getCamera());
- TerrainLodControl control = new TerrainLodControl(this.terrain, cameras);
+ TerrainLodControl control = new TerrainGridLodControl(this.terrain, this.getCamera());
control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier
this.terrain.addControl(control);
bulletAppState.getPhysicsSpace().add(player3);
}
- terrain.addListener("physicsStartListener", new TerrainGridListener() {
+ terrain.addListener(new TerrainGridListener() {
public void gridMoved(Vector3f newCenter) {
}
- public Material tileLoaded(Material material, Vector3f cell) {
- return material;
- }
-
public void tileAttached(Vector3f cell, TerrainQuad quad) {
- Texture alpha = assetManager.loadTexture("Scenes/TerrainAlphaTest/alphamap_" + Math.abs((int) (cell.x % 2)) * 512 + "_" + Math.abs((int) (cell.z % 2) * 512) + ".png");
+ Texture alpha = null;
+ try {
+ alpha = assetManager.loadTexture("TerrainAlphaTest/alpha_" + (int)cell.x+ "_" + (int)cell.z + ".png");
+ } catch (Exception e) {
+ alpha = assetManager.loadTexture("TerrainAlphaTest/alpha_default.png");
+ }
quad.getMaterial().setTexture("AlphaMap", alpha);
if (usePhysics) {
quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
bulletAppState.getPhysicsSpace().add(quad);
}
+ updateMarkerElevations();
}
public void tileDetached(Vector3f cell, TerrainQuad quad) {
if (usePhysics) {
- bulletAppState.getPhysicsSpace().remove(quad);
- quad.removeControl(RigidBodyControl.class);
+ if (quad.getControl(RigidBodyControl.class) != null) {
+ bulletAppState.getPhysicsSpace().remove(quad);
+ quad.removeControl(RigidBodyControl.class);
+ }
}
+ updateMarkerElevations();
}
});
- this.terrain.initialize(cam.getLocation());
+
this.initKeys();
+
+ markers = new Node();
+ rootNode.attachChild(markers);
+ createMarkerPoints(1);
}
-
+
+ Node markers;
+
+
+ private void createMarkerPoints(float count) {
+ Node center = createAxisMarker(10);
+ markers.attachChild(center);
+
+ float xS = (count-1)*terrain.getTerrainSize() - (terrain.getTerrainSize()/2);
+ float zS = (count-1)*terrain.getTerrainSize() - (terrain.getTerrainSize()/2);
+ float xSi = xS;
+ float zSi = zS;
+ for (int x=0; x<count*2; x++) {
+ for (int z=0; z<count*2; z++) {
+ Node m = createAxisMarker(5);
+ m.setLocalTranslation(xSi, 0, zSi);
+ markers.attachChild(m);
+ zSi += terrain.getTerrainSize();
+ }
+ zSi = zS;
+ xSi += terrain.getTerrainSize();
+ }
+ }
+
+ private void updateMarkerElevations() {
+ for (Spatial s : markers.getChildren()) {
+ float h = terrain.getHeight(new Vector2f(s.getLocalTranslation().x, s.getLocalTranslation().z));
+ s.setLocalTranslation(s.getLocalTranslation().x, h+1, s.getLocalTranslation().z);
+ }
+ }
+
private void initKeys() {
// You can map one or several inputs to one named action
this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
this.cam.setLocation(this.player3.getPhysicsLocation());
}
}
+
+ protected Node createAxisMarker(float arrowSize) {
+
+ Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ redMat.getAdditionalRenderState().setWireframe(true);
+ redMat.setColor("Color", ColorRGBA.Red);
+
+ Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ greenMat.getAdditionalRenderState().setWireframe(true);
+ greenMat.setColor("Color", ColorRGBA.Green);
+
+ Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ blueMat.getAdditionalRenderState().setWireframe(true);
+ blueMat.setColor("Color", ColorRGBA.Blue);
+
+ Node axis = new Node();
+
+ // create arrows
+ Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0)));
+ arrowX.setMaterial(redMat);
+ Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0)));
+ arrowY.setMaterial(greenMat);
+ Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize)));
+ arrowZ.setMaterial(blueMat);
+ axis.attachChild(arrowX);
+ axis.attachChild(arrowY);
+ axis.attachChild(arrowZ);
+
+ //axis.setModelBound(new BoundingBox());
+ return axis;
+ }
}
--- /dev/null
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainGridLodControl;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import java.io.File;
+
+public class TerrainGridSerializationTest extends SimpleApplication {
+
+ private TerrainGrid terrain;
+ private boolean usePhysics = true;
+
+ public static void main(final String[] args) {
+ TerrainGridSerializationTest app = new TerrainGridSerializationTest();
+ app.start();
+ }
+ private CharacterControl player3;
+
+ @Override
+ public void simpleInitApp() {
+ File file = new File("TerrainGridTestData.zip");
+ if (!file.exists()) {
+ assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
+ } else {
+ assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
+ }
+
+ this.flyCam.setMoveSpeed(100f);
+ ScreenshotAppState state = new ScreenshotAppState();
+ this.stateManager.attach(state);
+
+ this.terrain= (TerrainGrid) assetManager.loadModel("TerrainGrid/TerrainGrid.j3o");
+
+ this.rootNode.attachChild(this.terrain);
+
+ TerrainLodControl control = new TerrainGridLodControl(this.terrain, getCamera());
+ control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+ this.terrain.addControl(control);
+
+ final BulletAppState bulletAppState = new BulletAppState();
+ stateManager.attach(bulletAppState);
+
+ this.getCamera().setLocation(new Vector3f(0, 256, 0));
+
+ this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+ if (usePhysics) {
+ CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
+ player3 = new CharacterControl(capsuleShape, 0.5f);
+ player3.setJumpSpeed(20);
+ player3.setFallSpeed(10);
+ player3.setGravity(10);
+
+ player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));
+
+ bulletAppState.getPhysicsSpace().add(player3);
+
+ terrain.addListener(new TerrainGridListener() {
+
+ public void gridMoved(Vector3f newCenter) {
+ }
+
+ public void tileAttached(Vector3f cell, TerrainQuad quad) {
+ //workaround for bugged test j3o's
+ while(quad.getControl(RigidBodyControl.class)!=null){
+ quad.removeControl(RigidBodyControl.class);
+ }
+ quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
+ bulletAppState.getPhysicsSpace().add(quad);
+ }
+
+ public void tileDetached(Vector3f cell, TerrainQuad quad) {
+ if (quad.getControl(RigidBodyControl.class) != null) {
+ bulletAppState.getPhysicsSpace().remove(quad);
+ quad.removeControl(RigidBodyControl.class);
+ }
+ }
+
+ });
+ }
+
+ this.initKeys();
+ }
+
+ private void initKeys() {
+ // You can map one or several inputs to one named action
+ this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+ this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+ this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+ this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+ this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
+ this.inputManager.addListener(this.actionListener, "Lefts");
+ this.inputManager.addListener(this.actionListener, "Rights");
+ this.inputManager.addListener(this.actionListener, "Ups");
+ this.inputManager.addListener(this.actionListener, "Downs");
+ this.inputManager.addListener(this.actionListener, "Jumps");
+ }
+ private boolean left;
+ private boolean right;
+ private boolean up;
+ private boolean down;
+ private final ActionListener actionListener = new ActionListener() {
+
+ @Override
+ public void onAction(final String name, final boolean keyPressed, final float tpf) {
+ if (name.equals("Lefts")) {
+ if (keyPressed) {
+ TerrainGridSerializationTest.this.left = true;
+ } else {
+ TerrainGridSerializationTest.this.left = false;
+ }
+ } else if (name.equals("Rights")) {
+ if (keyPressed) {
+ TerrainGridSerializationTest.this.right = true;
+ } else {
+ TerrainGridSerializationTest.this.right = false;
+ }
+ } else if (name.equals("Ups")) {
+ if (keyPressed) {
+ TerrainGridSerializationTest.this.up = true;
+ } else {
+ TerrainGridSerializationTest.this.up = false;
+ }
+ } else if (name.equals("Downs")) {
+ if (keyPressed) {
+ TerrainGridSerializationTest.this.down = true;
+ } else {
+ TerrainGridSerializationTest.this.down = false;
+ }
+ } else if (name.equals("Jumps")) {
+ TerrainGridSerializationTest.this.player3.jump();
+ }
+ }
+ };
+ private final Vector3f walkDirection = new Vector3f();
+
+ @Override
+ public void simpleUpdate(final float tpf) {
+ Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
+ Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
+ this.walkDirection.set(0, 0, 0);
+ if (this.left) {
+ this.walkDirection.addLocal(camLeft);
+ }
+ if (this.right) {
+ this.walkDirection.addLocal(camLeft.negate());
+ }
+ if (this.up) {
+ this.walkDirection.addLocal(camDir);
+ }
+ if (this.down) {
+ this.walkDirection.addLocal(camDir.negate());
+ }
+
+ if (usePhysics) {
+ this.player3.setWalkDirection(this.walkDirection);
+ this.cam.setLocation(this.player3.getPhysicsLocation());
+ }
+ }
+}
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.terrain.geomipmap.TerrainGrid;
import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainGridLodControl;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.ImageTileLoader;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
-import com.jme3.terrain.heightmap.ImageBasedHeightMapGrid;
import com.jme3.terrain.heightmap.Namer;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
private float grassScale = 64;
private float dirtScale = 16;
private float rockScale = 128;
- private boolean usePhysics = true;
+ private boolean usePhysics = false;
private boolean physicsAdded = false;
public static void main(final String[] args) {
@Override
public void simpleInitApp() {
- File file = new File("mountains.zip");
+ File file = new File("TerrainGridTestData.zip");
if (!file.exists()) {
- assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/mountains.zip", HttpZipLocator.class);
+ assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
} else {
- assetManager.registerLocator("mountains.zip", ZipLocator.class);
+ assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
}
this.flyCam.setMoveSpeed(100f);
this.mat_terrain.setFloat("terrainSize", 129);
- this.terrain = new TerrainGrid("terrain", 65, 257, new ImageBasedHeightMapGrid(assetManager, new Namer() {
+ this.terrain = new TerrainGrid("terrain", 65, 257, new ImageTileLoader(assetManager, new Namer() {
public String getName(int x, int y) {
return "Scenes/TerrainMountains/terrain_" + x + "_" + y + ".png";
}
}));
-
- this.terrain.setMaterial(this.mat_terrain);
+
+ this.terrain.setMaterial(mat_terrain);
this.terrain.setLocalTranslation(0, 0, 0);
- this.terrain.setLocalScale(2f, 1f, 2f);
+ this.terrain.setLocalScale(1f, 1f, 1f);
this.rootNode.attachChild(this.terrain);
- TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera());
+ TerrainLodControl control = new TerrainGridLodControl(this.terrain, getCamera());
control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
this.terrain.addControl(control);
final BulletAppState bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
- this.getCamera().setLocation(new Vector3f(0, 256, 0));
-
+ this.getCamera().setLocation(new Vector3f(0, 400, 0));
+ this.getCamera().lookAt(new Vector3f(0,0,0), Vector3f.UNIT_Y);
+
this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+ DirectionalLight light = new DirectionalLight();
+ light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
+ rootNode.addLight(light);
+
if (usePhysics) {
CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
player3 = new CharacterControl(capsuleShape, 0.5f);
bulletAppState.getPhysicsSpace().add(player3);
- terrain.addListener("physicsStartListener", new TerrainGridListener() {
+ terrain.addListener(new TerrainGridListener() {
public void gridMoved(Vector3f newCenter) {
}
- public Material tileLoaded(Material material, Vector3f cell) {
- return material;
- }
-
public void tileAttached(Vector3f cell, TerrainQuad quad) {
+ while(quad.getControl(RigidBodyControl.class)!=null){
+ quad.removeControl(RigidBodyControl.class);
+ }
quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
bulletAppState.getPhysicsSpace().add(quad);
}
public void tileDetached(Vector3f cell, TerrainQuad quad) {
- bulletAppState.getPhysicsSpace().remove(quad);
- quad.removeControl(RigidBodyControl.class);
+ if (quad.getControl(RigidBodyControl.class) != null) {
+ bulletAppState.getPhysicsSpace().remove(quad);
+ quad.removeControl(RigidBodyControl.class);
+ }
}
});
}
- this.terrain.initialize(cam.getLocation());
+
this.initKeys();
}
--- /dev/null
+package jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.app.state.ScreenshotAppState;
+import com.jme3.asset.plugins.HttpZipLocator;
+import com.jme3.asset.plugins.ZipLocator;
+import com.jme3.bullet.BulletAppState;
+import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
+import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
+import com.jme3.bullet.control.CharacterControl;
+import com.jme3.bullet.control.RigidBodyControl;
+import com.jme3.input.KeyInput;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.TerrainGrid;
+import com.jme3.terrain.geomipmap.TerrainGridListener;
+import com.jme3.terrain.geomipmap.TerrainGridLodControl;
+import com.jme3.terrain.geomipmap.TerrainLodControl;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.AssetTileLoader;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.io.File;
+
+public class TerrainGridTileLoaderTest extends SimpleApplication {
+
+ private Material mat_terrain;
+ private TerrainGrid terrain;
+ private float grassScale = 64;
+ private float dirtScale = 16;
+ private float rockScale = 128;
+ private boolean usePhysics = true;
+ private boolean physicsAdded = false;
+
+ public static void main(final String[] args) {
+ TerrainGridTileLoaderTest app = new TerrainGridTileLoaderTest();
+ app.start();
+ }
+ private CharacterControl player3;
+
+ @Override
+ public void simpleInitApp() {
+ File file = new File("TerrainGridTestData.zip");
+ if (!file.exists()) {
+ assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/TerrainGridTestData.zip", HttpZipLocator.class);
+ } else {
+ assetManager.registerLocator("TerrainGridTestData.zip", ZipLocator.class);
+ }
+
+ this.flyCam.setMoveSpeed(100f);
+ ScreenshotAppState state = new ScreenshotAppState();
+ this.stateManager.attach(state);
+
+ // TERRAIN TEXTURE material
+ this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
+
+ // Parameters to material:
+ // regionXColorMap: X = 1..4 the texture that should be appliad to state X
+ // regionX: a Vector3f containing the following information:
+ // regionX.x: the start height of the region
+ // regionX.y: the end height of the region
+ // regionX.z: the texture scale for the region
+ // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
+ // slopeColorMap: the texture to be used for cliffs, and steep mountain sites
+ // slopeTileFactor: the texture scale for slopes
+ // terrainSize: the total size of the terrain (used for scaling the texture)
+ // GRASS texture
+ Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+ grass.setWrap(WrapMode.Repeat);
+ this.mat_terrain.setTexture("region1ColorMap", grass);
+ this.mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale));
+
+ // DIRT texture
+ Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+ dirt.setWrap(WrapMode.Repeat);
+ this.mat_terrain.setTexture("region2ColorMap", dirt);
+ this.mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale));
+
+ // ROCK texture
+ Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
+ rock.setWrap(WrapMode.Repeat);
+ this.mat_terrain.setTexture("region3ColorMap", rock);
+ this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale));
+
+ this.mat_terrain.setTexture("region4ColorMap", rock);
+ this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale));
+
+ this.mat_terrain.setTexture("slopeColorMap", rock);
+ this.mat_terrain.setFloat("slopeTileFactor", 32);
+
+ this.mat_terrain.setFloat("terrainSize", 129);
+//quad.getHeightMap(), terrain.getLocalScale()), 0
+ AssetTileLoader grid = new AssetTileLoader(assetManager, "testgrid", "TerrainGrid");
+ this.terrain = new TerrainGrid("terrain", 65, 257, grid);
+
+ this.terrain.setMaterial(this.mat_terrain);
+ this.terrain.setLocalTranslation(0, 0, 0);
+ this.terrain.setLocalScale(2f, 1f, 2f);
+// try {
+// BinaryExporter.getInstance().save(terrain, new File("/Users/normenhansen/Documents/Code/jme3/engine/src/test-data/TerrainGrid/"
+// + "TerrainGrid.j3o"));
+// } catch (IOException ex) {
+// Logger.getLogger(TerrainFractalGridTest.class.getName()).log(Level.SEVERE, null, ex);
+// }
+
+ this.rootNode.attachChild(this.terrain);
+
+ TerrainLodControl control = new TerrainGridLodControl(this.terrain, getCamera());
+ control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+ this.terrain.addControl(control);
+
+ final BulletAppState bulletAppState = new BulletAppState();
+ stateManager.attach(bulletAppState);
+
+ this.getCamera().setLocation(new Vector3f(0, 256, 0));
+
+ this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f));
+
+ if (usePhysics) {
+ CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(0.5f, 1.8f, 1);
+ player3 = new CharacterControl(capsuleShape, 0.5f);
+ player3.setJumpSpeed(20);
+ player3.setFallSpeed(10);
+ player3.setGravity(10);
+
+ player3.setPhysicsLocation(new Vector3f(cam.getLocation().x, 256, cam.getLocation().z));
+
+ bulletAppState.getPhysicsSpace().add(player3);
+
+ terrain.addListener(new TerrainGridListener() {
+
+ public void gridMoved(Vector3f newCenter) {
+ }
+
+ public void tileAttached(Vector3f cell, TerrainQuad quad) {
+ while(quad.getControl(RigidBodyControl.class)!=null){
+ quad.removeControl(RigidBodyControl.class);
+ }
+ quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrain.getLocalScale()), 0));
+ bulletAppState.getPhysicsSpace().add(quad);
+ }
+
+ public void tileDetached(Vector3f cell, TerrainQuad quad) {
+ if (quad.getControl(RigidBodyControl.class) != null) {
+ bulletAppState.getPhysicsSpace().remove(quad);
+ quad.removeControl(RigidBodyControl.class);
+ }
+ }
+
+ });
+ }
+
+ this.initKeys();
+ }
+
+ private void initKeys() {
+ // You can map one or several inputs to one named action
+ this.inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
+ this.inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));
+ this.inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
+ this.inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
+ this.inputManager.addMapping("Jumps", new KeyTrigger(KeyInput.KEY_SPACE));
+ this.inputManager.addMapping("pick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+ this.inputManager.addListener(this.actionListener, "Lefts");
+ this.inputManager.addListener(this.actionListener, "Rights");
+ this.inputManager.addListener(this.actionListener, "Ups");
+ this.inputManager.addListener(this.actionListener, "Downs");
+ this.inputManager.addListener(this.actionListener, "Jumps");
+ this.inputManager.addListener(this.actionListener, "pick");
+ }
+ private boolean left;
+ private boolean right;
+ private boolean up;
+ private boolean down;
+ private final ActionListener actionListener = new ActionListener() {
+
+ @Override
+ public void onAction(final String name, final boolean keyPressed, final float tpf) {
+ if (name.equals("Lefts")) {
+ if (keyPressed) {
+ TerrainGridTileLoaderTest.this.left = true;
+ } else {
+ TerrainGridTileLoaderTest.this.left = false;
+ }
+ } else if (name.equals("Rights")) {
+ if (keyPressed) {
+ TerrainGridTileLoaderTest.this.right = true;
+ } else {
+ TerrainGridTileLoaderTest.this.right = false;
+ }
+ } else if (name.equals("Ups")) {
+ if (keyPressed) {
+ TerrainGridTileLoaderTest.this.up = true;
+ } else {
+ TerrainGridTileLoaderTest.this.up = false;
+ }
+ } else if (name.equals("Downs")) {
+ if (keyPressed) {
+ TerrainGridTileLoaderTest.this.down = true;
+ } else {
+ TerrainGridTileLoaderTest.this.down = false;
+ }
+ } else if (name.equals("Jumps")) {
+ TerrainGridTileLoaderTest.this.player3.jump();
+ } else if (name.equals("pick") && keyPressed) {
+ //Terrain picked = terrain.getTerrainAt(player3.getPhysicsLocation());
+ Terrain picked = terrain.getTerrainAtCell(terrain.getCurrentCell());
+ System.out.println("** cell "+player3.getPhysicsLocation()+" picked terrain: "+picked);
+ }
+ }
+ };
+ private final Vector3f walkDirection = new Vector3f();
+
+ @Override
+ public void simpleUpdate(final float tpf) {
+ Vector3f camDir = this.cam.getDirection().clone().multLocal(0.6f);
+ Vector3f camLeft = this.cam.getLeft().clone().multLocal(0.4f);
+ this.walkDirection.set(0, 0, 0);
+ if (this.left) {
+ this.walkDirection.addLocal(camLeft);
+ }
+ if (this.right) {
+ this.walkDirection.addLocal(camLeft.negate());
+ }
+ if (this.up) {
+ this.walkDirection.addLocal(camDir);
+ }
+ if (this.down) {
+ this.walkDirection.addLocal(camDir.negate());
+ }
+
+ if (usePhysics) {
+ this.player3.setWalkDirection(this.walkDirection);
+ this.cam.setLocation(this.player3.getPhysicsLocation());
+ }
+ }
+}
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
*/
package jme3test.terrain;
-import jme3tools.converters.ImageToAwt;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.terrain.geomipmap.TerrainLodControl;
-import com.jme3.terrain.heightmap.AbstractHeightMap;
-import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
-import com.jme3.terrain.geomipmap.lodcalc.SimpleLodThreshold;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
try {
//heightmap = new HillHeightMap(1025, 1000, 50, 100, (byte) 3);
- heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 1f);
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
heightmap.load();
} catch (Exception e) {
terrain.addControl(control);
terrain.setMaterial(matRock);
terrain.setLocalTranslation(0, -100, 0);
- terrain.setLocalScale(2f, 1f, 2f);
+ terrain.setLocalScale(2f, 0.5f, 2f);
rootNode.attachChild(terrain);
DirectionalLight light = new DirectionalLight();
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
*/
package jme3test.terrain;
-import jme3tools.converters.ImageToAwt;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.font.BitmapText;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
-import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
import com.jme3.terrain.geomipmap.TerrainLodControl;
-import com.jme3.terrain.heightmap.AbstractHeightMap;
-import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.util.SkyFactory;
// TERRAIN TEXTURE material
matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
matTerrain.setBoolean("useTriPlanarMapping", false);
+ matTerrain.setFloat("Shininess", 0.0f);
// ALPHA map (for splat textures)
matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png"));
matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png"));
-
+ // this material also supports 'AlphaMap_2', so you can get up to 12 diffuse textures
+
// HEIGHTMAP image (for the terrain heightmap)
Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
-
+
+ // DIRT texture, Diffuse textures 0 to 3 use the first AlphaMap
+ Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
+ dirt.setWrap(WrapMode.Repeat);
+ matTerrain.setTexture("DiffuseMap", dirt);
+ matTerrain.setFloat("DiffuseMap_0_scale", dirtScale);
+
// GRASS texture
Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
grass.setWrap(WrapMode.Repeat);
matTerrain.setTexture("DiffuseMap_1", grass);
matTerrain.setFloat("DiffuseMap_1_scale", grassScale);
- // DIRT texture
- Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");
- dirt.setWrap(WrapMode.Repeat);
- matTerrain.setTexture("DiffuseMap", dirt);
- matTerrain.setFloat("DiffuseMap_0_scale", dirtScale);
-
// ROCK texture
Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");
rock.setWrap(WrapMode.Repeat);
matTerrain.setTexture("DiffuseMap_3", brick);
matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
- // RIVER ROCK texture
+ // RIVER ROCK texture, this texture will use the next alphaMap: AlphaMap_1
Texture riverRock = assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg");
riverRock.setWrap(WrapMode.Repeat);
matTerrain.setTexture("DiffuseMap_4", riverRock);
matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
-
+
+ // diffuse textures 4 to 7 use AlphaMap_1
+ // diffuse textures 8 to 11 use AlphaMap_2
Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg");
normalMap0.setWrap(WrapMode.Repeat);
normalMap1.setWrap(WrapMode.Repeat);
Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png");
normalMap2.setWrap(WrapMode.Repeat);
- matTerrain.setTexture("NormalMap", normalMap2);
+ //matTerrain.setTexture("NormalMap", normalMap0);
matTerrain.setTexture("NormalMap_1", normalMap2);
matTerrain.setTexture("NormalMap_2", normalMap2);
matTerrain.setTexture("NormalMap_4", normalMap2);
- // WIREFRAME material
+
+ // WIREFRAME material (used to debug the terrain, only useful for this test case)
matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matWire.getAdditionalRenderState().setWireframe(true);
matWire.setColor("Color", ColorRGBA.Green);
-
+
createSky();
// CREATE HEIGHTMAP
AbstractHeightMap heightmap = null;
try {
- heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.5f);
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f);
heightmap.load();
heightmap.smooth(0.9f, 1);
terrain.setLocalScale(1f, 1f, 1f);
rootNode.attachChild(terrain);
- Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
+ //Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
//terrain.generateDebugTangents(debugMat);
DirectionalLight light = new DirectionalLight();
- light.setDirection((new Vector3f(-0.25f, -1f, -0.25f)).normalize());
+ light.setDirection((new Vector3f(-0.1f, -0.1f, -0.1f)).normalize());
rootNode.addLight(light);
cam.setLocation(new Vector3f(0, 10, -10));
cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);
flyCam.setMoveSpeed(400);
+
+ rootNode.attachChild(createAxisMarker(20));
}
public void loadHintText() {
inputManager.addListener(actionListener, "triPlanar");
inputManager.addMapping("WardIso", new KeyTrigger(KeyInput.KEY_9));
inputManager.addListener(actionListener, "WardIso");
- inputManager.addMapping("Minnaert", new KeyTrigger(KeyInput.KEY_0));
- inputManager.addListener(actionListener, "Minnaert");
+ inputManager.addMapping("DetachControl", new KeyTrigger(KeyInput.KEY_0));
+ inputManager.addListener(actionListener, "DetachControl");
}
private ActionListener actionListener = new ActionListener() {
matTerrain.setFloat("DiffuseMap_3_scale", rockScale);
matTerrain.setFloat("DiffuseMap_4_scale", rockScale);
}
+ } if (name.equals("DetachControl") && !pressed) {
+ TerrainLodControl control = terrain.getControl(TerrainLodControl.class);
+ if (control != null)
+ control.detachAndCleanUpControl();
+ else {
+ control = new TerrainLodControl(terrain, cam);
+ terrain.addControl(control);
+ }
+
}
}
};
Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
rootNode.attachChild(sky);
}
+
+ protected Node createAxisMarker(float arrowSize) {
+
+ Material redMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ redMat.getAdditionalRenderState().setWireframe(true);
+ redMat.setColor("Color", ColorRGBA.Red);
+
+ Material greenMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ greenMat.getAdditionalRenderState().setWireframe(true);
+ greenMat.setColor("Color", ColorRGBA.Green);
+
+ Material blueMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ blueMat.getAdditionalRenderState().setWireframe(true);
+ blueMat.setColor("Color", ColorRGBA.Blue);
+
+ Node axis = new Node();
+
+ // create arrows
+ Geometry arrowX = new Geometry("arrowX", new Arrow(new Vector3f(arrowSize, 0, 0)));
+ arrowX.setMaterial(redMat);
+ Geometry arrowY = new Geometry("arrowY", new Arrow(new Vector3f(0, arrowSize, 0)));
+ arrowY.setMaterial(greenMat);
+ Geometry arrowZ = new Geometry("arrowZ", new Arrow(new Vector3f(0, 0, arrowSize)));
+ arrowZ.setMaterial(blueMat);
+ axis.attachChild(arrowX);
+ axis.attachChild(arrowY);
+ axis.attachChild(arrowZ);
+
+ //axis.setModelBound(new BoundingBox());
+ return axis;
+ }
}
--- /dev/null
+/*\r
+ * Copyright (c) 2009-2012 jMonkeyEngine\r
+ * All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or without\r
+ * modification, are permitted provided that the following conditions are\r
+ * met:\r
+ *\r
+ * * Redistributions of source code must retain the above copyright\r
+ * notice, this list of conditions and the following disclaimer.\r
+ *\r
+ * * Redistributions in binary form must reproduce the above copyright\r
+ * notice, this list of conditions and the following disclaimer in the\r
+ * documentation and/or other materials provided with the distribution.\r
+ *\r
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors\r
+ * may be used to endorse or promote products derived from this software\r
+ * without specific prior written permission.\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\r
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
+ */\r
+package jme3test.terrain;\r
+\r
+import com.jme3.app.SimpleApplication;\r
+import com.jme3.font.BitmapText;\r
+import com.jme3.input.KeyInput;\r
+import com.jme3.input.controls.ActionListener;\r
+import com.jme3.input.controls.KeyTrigger;\r
+import com.jme3.light.DirectionalLight;\r
+import com.jme3.light.PointLight;\r
+import com.jme3.material.Material;\r
+import com.jme3.math.ColorRGBA;\r
+import com.jme3.math.Vector3f;\r
+import com.jme3.scene.Geometry;\r
+import com.jme3.terrain.geomipmap.TerrainLodControl;\r
+import com.jme3.terrain.geomipmap.TerrainQuad;\r
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;\r
+import com.jme3.terrain.heightmap.AbstractHeightMap;\r
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;\r
+import com.jme3.texture.Texture;\r
+import com.jme3.texture.Texture.WrapMode;\r
+\r
+/**\r
+ * Demonstrates how to use terrain on Android.\r
+ * The only difference is it uses a much smaller heightmap so it won't use up\r
+ * all of the android device's memory.\r
+ *\r
+ * @author bowens\r
+ */\r
+public class TerrainTestAndroid extends SimpleApplication {\r
+\r
+ private TerrainQuad terrain;\r
+ Material matRock;\r
+ Material matWire;\r
+ boolean wireframe = false;\r
+ boolean triPlanar = false;\r
+ protected BitmapText hintText;\r
+ PointLight pl;\r
+ Geometry lightMdl;\r
+ private float grassScale = 64;\r
+ private float dirtScale = 16;\r
+ private float rockScale = 128;\r
+\r
+ public static void main(String[] args) {\r
+ TerrainTestAndroid app = new TerrainTestAndroid();\r
+ app.start();\r
+ }\r
+\r
+ @Override\r
+ public void initialize() {\r
+ super.initialize();\r
+\r
+ loadHintText();\r
+ }\r
+\r
+ @Override\r
+ public void simpleInitApp() {\r
+ setupKeys();\r
+\r
+ // First, we load up our textures and the heightmap texture for the terrain\r
+\r
+ // TERRAIN TEXTURE material\r
+ matRock = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");\r
+ matRock.setBoolean("useTriPlanarMapping", false);\r
+\r
+ // ALPHA map (for splat textures)\r
+ matRock.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));\r
+\r
+ // HEIGHTMAP image (for the terrain heightmap)\r
+ Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains128.png");\r
+\r
+ // GRASS texture\r
+ Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");\r
+ grass.setWrap(WrapMode.Repeat);\r
+ matRock.setTexture("Tex1", grass);\r
+ matRock.setFloat("Tex1Scale", grassScale);\r
+\r
+ // DIRT texture\r
+ Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");\r
+ dirt.setWrap(WrapMode.Repeat);\r
+ matRock.setTexture("Tex2", dirt);\r
+ matRock.setFloat("Tex2Scale", dirtScale);\r
+\r
+ // ROCK texture\r
+ Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");\r
+ rock.setWrap(WrapMode.Repeat);\r
+ matRock.setTexture("Tex3", rock);\r
+ matRock.setFloat("Tex3Scale", rockScale);\r
+\r
+ // WIREFRAME material\r
+ matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");\r
+ matWire.getAdditionalRenderState().setWireframe(true);\r
+ matWire.setColor("Color", ColorRGBA.Green);\r
+\r
+ // CREATE HEIGHTMAP\r
+ AbstractHeightMap heightmap = null;\r
+ try {\r
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);\r
+ heightmap.load();\r
+\r
+ } catch (Exception e) {\r
+ e.printStackTrace();\r
+ }\r
+\r
+ /*\r
+ * Here we create the actual terrain. The tiles will be 33x33, and the total size of the\r
+ * terrain will be 129x129. It uses the heightmap we created to generate the height values.\r
+ */\r
+ terrain = new TerrainQuad("terrain", 33, 129, heightmap.getHeightMap());\r
+ TerrainLodControl control = new TerrainLodControl(terrain, getCamera());\r
+ control.setLodCalculator( new DistanceLodCalculator(33, 2.7f) ); // patch size, and a multiplier\r
+ terrain.addControl(control);\r
+ terrain.setMaterial(matRock);\r
+ terrain.setLocalTranslation(0, -100, 0);\r
+ terrain.setLocalScale(8f, 0.5f, 8f);\r
+ rootNode.attachChild(terrain);\r
+\r
+ DirectionalLight light = new DirectionalLight();\r
+ light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());\r
+ rootNode.addLight(light);\r
+\r
+ cam.setLocation(new Vector3f(0, 10, -10));\r
+ cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y);\r
+ }\r
+\r
+ public void loadHintText() {\r
+ hintText = new BitmapText(guiFont, false);\r
+ hintText.setSize(guiFont.getCharSet().getRenderedSize());\r
+ hintText.setLocalTranslation(0, getCamera().getHeight(), 0);\r
+ hintText.setText("Hit T to switch to wireframe, P to switch to tri-planar texturing");\r
+ guiNode.attachChild(hintText);\r
+ }\r
+\r
+ private void setupKeys() {\r
+ flyCam.setMoveSpeed(50);\r
+ inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));\r
+ inputManager.addListener(actionListener, "wireframe");\r
+ inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P));\r
+ inputManager.addListener(actionListener, "triPlanar");\r
+ }\r
+ private ActionListener actionListener = new ActionListener() {\r
+\r
+ public void onAction(String name, boolean pressed, float tpf) {\r
+ if (name.equals("wireframe") && !pressed) {\r
+ wireframe = !wireframe;\r
+ if (!wireframe) {\r
+ terrain.setMaterial(matWire);\r
+ } else {\r
+ terrain.setMaterial(matRock);\r
+ }\r
+ } else if (name.equals("triPlanar") && !pressed) {\r
+ triPlanar = !triPlanar;\r
+ if (triPlanar) {\r
+ matRock.setBoolean("useTriPlanarMapping", true);\r
+ // planar textures don't use the mesh's texture coordinates but real world coordinates,\r
+ // so we need to convert these texture coordinate scales into real world scales so it looks\r
+ // the same when we switch to/from tr-planar mode\r
+ matRock.setFloat("Tex1Scale", 1f / (float) (512f / grassScale));\r
+ matRock.setFloat("Tex2Scale", 1f / (float) (512f / dirtScale));\r
+ matRock.setFloat("Tex3Scale", 1f / (float) (512f / rockScale));\r
+ } else {\r
+ matRock.setBoolean("useTriPlanarMapping", false);\r
+ matRock.setFloat("Tex1Scale", grassScale);\r
+ matRock.setFloat("Tex2Scale", dirtScale);\r
+ matRock.setFloat("Tex3Scale", rockScale);\r
+ }\r
+ }\r
+ }\r
+ };\r
+}\r
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
*/
package jme3test.terrain;
-import jme3tools.converters.ImageToAwt;
-import com.jme3.bullet.collision.shapes.SphereCollisionShape;
+import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.BulletAppState;
-import com.jme3.app.SimpleApplication;
+import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.terrain.geomipmap.TerrainLodControl;
-import com.jme3.terrain.heightmap.AbstractHeightMap;
-import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.terrain.heightmap.AbstractHeightMap;
+import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
matWire.setColor("Color", ColorRGBA.Green);
AbstractHeightMap heightmap = null;
try {
- heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.25f);
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f);
heightmap.load();
} catch (Exception e) {
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
import com.jme3.terrain.geomipmap.TerrainGrid;
import com.jme3.terrain.geomipmap.TerrainLodControl;
import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
import com.jme3.terrain.heightmap.AbstractHeightMap;
-import com.jme3.terrain.heightmap.FractalHeightMapGrid;
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
+import com.jme3.terrain.noise.ShaderUtils;
+import com.jme3.terrain.noise.basis.FilteredBasis;
+import com.jme3.terrain.noise.filter.IterativeFilter;
+import com.jme3.terrain.noise.filter.OptimizedErode;
+import com.jme3.terrain.noise.filter.PerturbFilter;
+import com.jme3.terrain.noise.filter.SmoothFilter;
+import com.jme3.terrain.noise.fractal.FractalSum;
+import com.jme3.terrain.noise.modulator.NoiseModulator;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import java.util.ArrayList;
import java.util.List;
-import jme3tools.converters.ImageToAwt;
-import org.novyon.noise.ShaderUtils;
-import org.novyon.noise.basis.FilteredBasis;
-import org.novyon.noise.filter.IterativeFilter;
-import org.novyon.noise.filter.OptimizedErode;
-import org.novyon.noise.filter.PerturbFilter;
-import org.novyon.noise.filter.SmoothFilter;
-import org.novyon.noise.fractal.FractalSum;
-import org.novyon.noise.modulator.NoiseModulator;
/**
*
matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
matTerrain.setBoolean("useTriPlanarMapping", false);
matTerrain.setBoolean("WardIso", true);
+ matTerrain.setFloat("Shininess", 0);
// ALPHA map (for splat textures)
matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
AbstractHeightMap heightmap = null;
try {
- heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 0.5f);
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.5f);
heightmap.load();
heightmap.smooth(0.9f, 1);
ground.addPreFilter(iterate);
- this.terrain = new TerrainGrid("terrain", 65, 257, new FractalHeightMapGrid(ground, null, 256f));
+ this.terrain = new TerrainGrid("terrain", 65, 257, new FractalTileLoader(ground, 256f));
terrain.setMaterial(matTerrain);
terrain.setLocalTranslation(0, 0, 0);
terrain.setLocalScale(2f, 1f, 2f);
- ((TerrainGrid)terrain).initialize(Vector3f.ZERO);
+
rootNode.attachChild(this.terrain);
TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera());
/*
- * Copyright (c) 2009-2010 jMonkeyEngine
+ * Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
import com.jme3.terrain.heightmap.ImageBasedHeightMap;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
-import jme3tools.converters.ImageToAwt;
/**
* Saves and loads terrain.
// CREATE HEIGHTMAP
AbstractHeightMap heightmap = null;
try {
- heightmap = new ImageBasedHeightMap(ImageToAwt.convert(heightMapImage.getImage(), false, true, 0), 1f);
+ heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 1f);
heightmap.load();
} catch (Exception e) {
--- /dev/null
+/*
+ * 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 jme3test.terrain;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.font.BitmapText;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector2f;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Sphere;
+import com.jme3.terrain.ProgressMonitor;
+import com.jme3.terrain.Terrain;
+import com.jme3.terrain.geomipmap.MultiTerrainLodControl;
+import com.jme3.terrain.geomipmap.NeighbourFinder;
+import com.jme3.terrain.geomipmap.TerrainQuad;
+import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
+import com.jme3.texture.Texture;
+import com.jme3.texture.Texture.WrapMode;
+import java.util.List;
+
+/**
+ * Demonstrates the NeighbourFinder interface for TerrainQuads,
+ * allowing you to tile terrains together without having to use
+ * TerrainGrid. It also introduces the MultiTerrainLodControl that
+ * will seam the edges of all the terrains supplied.
+ *
+ * @author sploreg
+ */
+public class TerrainTestTile extends SimpleApplication {
+
+ private TiledTerrain terrain;
+ Material matTerrain;
+ Material matWire;
+ boolean wireframe = true;
+ boolean triPlanar = false;
+ boolean wardiso = false;
+ boolean minnaert = false;
+ protected BitmapText hintText;
+ private float grassScale = 256;
+
+
+ public static void main(String[] args) {
+ TerrainTestTile app = new TerrainTestTile();
+ app.start();
+ }
+
+
+
+ @Override
+ public void simpleInitApp() {
+ loadHintText();
+ setupKeys();
+
+ // WIREFRAME material
+ matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ matWire.getAdditionalRenderState().setWireframe(true);
+ matWire.setColor("Color", ColorRGBA.Green);
+
+ terrain = new TiledTerrain();
+ rootNode.attachChild(terrain);
+
+ DirectionalLight light = new DirectionalLight();
+ light.setDirection((new Vector3f(-0.5f, -1f, -0.5f)).normalize());
+ rootNode.addLight(light);
+
+ AmbientLight ambLight = new AmbientLight();
+ ambLight.setColor(new ColorRGBA(1f, 1f, 0.8f, 0.2f));
+ rootNode.addLight(ambLight);
+
+ cam.setLocation(new Vector3f(0, 256, 0));
+ cam.lookAtDirection(new Vector3f(0, -1, -1).normalizeLocal(), Vector3f.UNIT_Y);
+
+
+ Sphere s = new Sphere(12, 12, 3);
+ Geometry g = new Geometry("marker");
+ g.setMesh(s);
+ Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
+ mat.setColor("Color", ColorRGBA.Red);
+ g.setMaterial(mat);
+ g.setLocalTranslation(0, -100, 0);
+ rootNode.attachChild(g);
+
+ Geometry g2 = new Geometry("marker");
+ g2.setMesh(s);
+ mat.setColor("Color", ColorRGBA.Red);
+ g2.setMaterial(mat);
+ g2.setLocalTranslation(10, -100, 0);
+ rootNode.attachChild(g2);
+
+ Geometry g3 = new Geometry("marker");
+ g3.setMesh(s);
+ mat.setColor("Color", ColorRGBA.Red);
+ g3.setMaterial(mat);
+ g3.setLocalTranslation(0, -100, 10);
+ rootNode.attachChild(g3);
+ }
+
+ public void loadHintText() {
+ hintText = new BitmapText(guiFont, false);
+ hintText.setLocalTranslation(0, getCamera().getHeight(), 0);
+ hintText.setText("Hit 'T' to toggle wireframe");
+ guiNode.attachChild(hintText);
+ }
+
+
+ private void setupKeys() {
+ flyCam.setMoveSpeed(100);
+ inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T));
+ inputManager.addListener(actionListener, "wireframe");
+ }
+ private ActionListener actionListener = new ActionListener() {
+
+ public void onAction(String name, boolean pressed, float tpf) {
+ if (name.equals("wireframe") && !pressed) {
+ wireframe = !wireframe;
+ if (!wireframe) {
+ terrain.setMaterial(matWire);
+ } else {
+ terrain.setMaterial(matTerrain);
+ }
+ }
+ }
+ };
+
+ /**
+ * A sample class (node in this case) that demonstrates
+ * the use of NeighbourFinder.
+ * It just links up the left,right,top,bottom TerrainQuads
+ * so LOD can work.
+ * It does not implement many of the Terrain interface's methods,
+ * you will want to do that for your own implementations.
+ */
+ private class TiledTerrain extends Node implements Terrain, NeighbourFinder {
+
+ private TerrainQuad terrain1;
+ private TerrainQuad terrain2;
+ private TerrainQuad terrain3;
+ private TerrainQuad terrain4;
+
+ TiledTerrain() {
+ // TERRAIN TEXTURE material
+ matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
+ matTerrain.setBoolean("useTriPlanarMapping", false);
+ matTerrain.setBoolean("WardIso", true);
+ matTerrain.setFloat("Shininess", 0);
+
+ // GRASS texture
+ Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
+ grass.setWrap(WrapMode.Repeat);
+ matTerrain.setTexture("DiffuseMap", grass);
+ matTerrain.setFloat("DiffuseMap_0_scale", grassScale);
+
+ // CREATE THE TERRAIN
+ terrain1 = new TerrainQuad("terrain 1", 65, 513, null);
+ terrain1.setMaterial(matTerrain);
+ terrain1.setLocalTranslation(-256, -100, -256);
+ terrain1.setLocalScale(1f, 1f, 1f);
+ this.attachChild(terrain1);
+
+ terrain2 = new TerrainQuad("terrain 2", 65, 513, null);
+ terrain2.setMaterial(matTerrain);
+ terrain2.setLocalTranslation(-256, -100, 256);
+ terrain2.setLocalScale(1f, 1f, 1f);
+ this.attachChild(terrain2);
+
+ terrain3 = new TerrainQuad("terrain 3", 65, 513, null);
+ terrain3.setMaterial(matTerrain);
+ terrain3.setLocalTranslation(256, -100, -256);
+ terrain3.setLocalScale(1f, 1f, 1f);
+ this.attachChild(terrain3);
+
+ terrain4 = new TerrainQuad("terrain 4", 65, 513, null);
+ terrain4.setMaterial(matTerrain);
+ terrain4.setLocalTranslation(256, -100, 256);
+ terrain4.setLocalScale(1f, 1f, 1f);
+ this.attachChild(terrain4);
+
+ terrain1.setNeighbourFinder(this);
+ terrain2.setNeighbourFinder(this);
+ terrain3.setNeighbourFinder(this);
+ terrain4.setNeighbourFinder(this);
+
+ MultiTerrainLodControl lodControl = new MultiTerrainLodControl(getCamera());
+ lodControl.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); // patch size, and a multiplier
+ lodControl.addTerrain(terrain1);
+ lodControl.addTerrain(terrain2);
+ lodControl.addTerrain(terrain3);// order of these seems to matter
+ lodControl.addTerrain(terrain4);
+ this.addControl(lodControl);
+
+ }
+
+ /**
+ * 1 3
+ * 2 4
+ */
+ public TerrainQuad getRightQuad(TerrainQuad center) {
+ //System.out.println("lookup neighbour");
+ if (center == terrain1)
+ return terrain3;
+ if (center == terrain2)
+ return terrain4;
+
+ return null;
+ }
+
+ /**
+ * 1 3
+ * 2 4
+ */
+ public TerrainQuad getLeftQuad(TerrainQuad center) {
+ //System.out.println("lookup neighbour");
+ if (center == terrain3)
+ return terrain1;
+ if (center == terrain4)
+ return terrain2;
+
+ return null;
+ }
+
+ /**
+ * 1 3
+ * 2 4
+ */
+ public TerrainQuad getTopQuad(TerrainQuad center) {
+ //System.out.println("lookup neighbour");
+ if (center == terrain2)
+ return terrain1;
+ if (center == terrain4)
+ return terrain3;
+
+ return null;
+ }
+
+ /**
+ * 1 3
+ * 2 4
+ */
+ public TerrainQuad getDownQuad(TerrainQuad center) {
+ //System.out.println("lookup neighbour");
+ if (center == terrain1)
+ return terrain2;
+ if (center == terrain3)
+ return terrain4;
+
+ return null;
+ }
+
+ public float getHeight(Vector2f xz) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public Vector3f getNormal(Vector2f xz) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public float getHeightmapHeight(Vector2f xz) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void setHeight(Vector2f xzCoordinate, float height) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void setHeight(List<Vector2f> xz, List<Float> height) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void adjustHeight(Vector2f xzCoordinate, float delta) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void adjustHeight(List<Vector2f> xz, List<Float> height) {
+ // you will have to offset the coordinate for each terrain, to center on it
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public float[] getHeightMap() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public int getMaxLod() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void setLocked(boolean locked) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public void generateEntropy(ProgressMonitor monitor) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public Material getMaterial() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public Material getMaterial(Vector3f worldLocation) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public int getTerrainSize() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public int getNumMajorSubdivisions() {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+
+
+ }
+}