/ *
* Copyright ( c ) 2009 - 2020 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.asset.TextureKey ;
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.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.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 ;
/ * *
* Uses the terrain ' s lighting texture with normal maps and lights .
*
* @author bowens
* /
public class TerrainTestAdvanced extends SimpleApplication {
private TerrainQuad terrain ;
Material matTerrain ;
Material matWire ;
boolean wireframe = false ;
boolean triPlanar = false ;
boolean wardiso = false ;
boolean minnaert = false ;
protected BitmapText hintText ;
PointLight pl ;
Geometry lightMdl ;
private float dirtScale = 16 ;
private float darkRockScale = 32 ;
private float pinkRockScale = 32 ;
private float riverRockScale = 80 ;
private float grassScale = 32 ;
private float brickScale = 128 ;
private float roadScale = 200 ;
public static void main ( String [ ] args ) {
TerrainTestAdvanced app = new TerrainTestAdvanced ( ) ;
app . start ( ) ;
}
@Override
public void initialize ( ) {
super . initialize ( ) ;
loadHintText ( ) ;
}
@Override
public void simpleInitApp ( ) {
setupKeys ( ) ;
// First, we load up our textures and the heightmap texture for the terrain
// 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)
TextureKey hmKey = new TextureKey ( "Textures/Terrain/splat/mountains512.png" , false ) ;
Texture heightMapImage = assetManager . loadTexture ( hmKey ) ;
// 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 ) ;
// DARK ROCK texture
Texture darkRock = assetManager . loadTexture ( "Textures/Terrain/Rock2/rock.jpg" ) ;
darkRock . setWrap ( WrapMode . Repeat ) ;
matTerrain . setTexture ( "DiffuseMap_1" , darkRock ) ;
matTerrain . setFloat ( "DiffuseMap_1_scale" , darkRockScale ) ;
// PINK ROCK texture
Texture pinkRock = assetManager . loadTexture ( "Textures/Terrain/Rock/Rock.PNG" ) ;
pinkRock . setWrap ( WrapMode . Repeat ) ;
matTerrain . setTexture ( "DiffuseMap_2" , pinkRock ) ;
matTerrain . setFloat ( "DiffuseMap_2_scale" , pinkRockScale ) ;
// 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_3" , riverRock ) ;
matTerrain . setFloat ( "DiffuseMap_3_scale" , riverRockScale ) ;
// GRASS texture
Texture grass = assetManager . loadTexture ( "Textures/Terrain/splat/grass.jpg" ) ;
grass . setWrap ( WrapMode . Repeat ) ;
matTerrain . setTexture ( "DiffuseMap_4" , grass ) ;
matTerrain . setFloat ( "DiffuseMap_4_scale" , grassScale ) ;
// BRICK texture
Texture brick = assetManager . loadTexture ( "Textures/Terrain/BrickWall/BrickWall.jpg" ) ;
brick . setWrap ( WrapMode . Repeat ) ;
matTerrain . setTexture ( "DiffuseMap_5" , brick ) ;
matTerrain . setFloat ( "DiffuseMap_5_scale" , brickScale ) ;
// ROAD texture
Texture road = assetManager . loadTexture ( "Textures/Terrain/splat/road.jpg" ) ;
road . setWrap ( WrapMode . Repeat ) ;
matTerrain . setTexture ( "DiffuseMap_6" , road ) ;
matTerrain . setFloat ( "DiffuseMap_6_scale" , roadScale ) ;
// diffuse textures 0 to 3 use AlphaMap
// diffuse textures 4 to 7 use AlphaMap_1
// diffuse textures 8 to 11 use AlphaMap_2
// NORMAL MAPS
Texture normalMapDirt = assetManager . loadTexture ( "Textures/Terrain/splat/dirt_normal.png" ) ;
normalMapDirt . setWrap ( WrapMode . Repeat ) ;
Texture normalMapPinkRock = assetManager . loadTexture ( "Textures/Terrain/Rock/Rock_normal.png" ) ;
normalMapPinkRock . setWrap ( WrapMode . Repeat ) ;
Texture normalMapGrass = assetManager . loadTexture ( "Textures/Terrain/splat/grass_normal.jpg" ) ;
normalMapGrass . setWrap ( WrapMode . Repeat ) ;
Texture normalMapRoad = assetManager . loadTexture ( "Textures/Terrain/splat/road_normal.png" ) ;
normalMapRoad . setWrap ( WrapMode . Repeat ) ;
matTerrain . setTexture ( "NormalMap" , normalMapDirt ) ;
matTerrain . setTexture ( "NormalMap_1" , normalMapPinkRock ) ;
matTerrain . setTexture ( "NormalMap_2" , normalMapPinkRock ) ;
matTerrain . setTexture ( "NormalMap_4" , normalMapGrass ) ;
matTerrain . setTexture ( "NormalMap_6" , normalMapRoad ) ;
// 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 ( heightMapImage . getImage ( ) , 0 . 3f ) ;
heightmap . load ( ) ;
heightmap . smooth ( 0 . 9f , 1 ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
}
/ *
* Here we create the actual terrain . The tiles will be 65x65 , and the total size of the
* terrain will be 513x513 . It uses the heightmap we created to generate the height values .
* /
/ * *
* Optimal terrain patch size is 65 ( 64x64 ) .
* The total size is up to you . At 1025 it ran fine for me ( 200 + FPS ) , however at
* size = 2049 it got really slow . But that is a jump from 2 million to 8 million triangles . . .
* /
terrain = new TerrainQuad ( "terrain" , 65 , 513 , heightmap . getHeightMap ( ) ) ; //, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations
TerrainLodControl control = new TerrainLodControl ( terrain , getCamera ( ) ) ;
control . setLodCalculator ( new DistanceLodCalculator ( 65 , 2 . 7f ) ) ; // patch size, and a multiplier
terrain . addControl ( control ) ;
terrain . setMaterial ( matTerrain ) ;
terrain . setModelBound ( new BoundingBox ( ) ) ;
terrain . updateModelBound ( ) ;
terrain . setLocalTranslation ( 0 , - 100 , 0 ) ;
terrain . setLocalScale ( 1f , 1f , 1f ) ;
rootNode . attachChild ( terrain ) ;
//Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m");
//terrain.generateDebugTangents(debugMat);
DirectionalLight light = new DirectionalLight ( ) ;
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 ( ) {
hintText = new BitmapText ( guiFont , false ) ;
hintText . setSize ( guiFont . getCharSet ( ) . getRenderedSize ( ) ) ;
hintText . setLocalTranslation ( 0 , getCamera ( ) . getHeight ( ) , 0 ) ;
hintText . setText ( "Hit T to switch to wireframe, P to switch to tri-planar texturing" ) ;
guiNode . attachChild ( hintText ) ;
}
private void setupKeys ( ) {
flyCam . setMoveSpeed ( 50 ) ;
inputManager . addMapping ( "wireframe" , new KeyTrigger ( KeyInput . KEY_T ) ) ;
inputManager . addListener ( actionListener , "wireframe" ) ;
inputManager . addMapping ( "triPlanar" , new KeyTrigger ( KeyInput . KEY_P ) ) ;
inputManager . addListener ( actionListener , "triPlanar" ) ;
inputManager . addMapping ( "WardIso" , new KeyTrigger ( KeyInput . KEY_9 ) ) ;
inputManager . addListener ( actionListener , "WardIso" ) ;
inputManager . addMapping ( "DetachControl" , new KeyTrigger ( KeyInput . KEY_0 ) ) ;
inputManager . addListener ( actionListener , "DetachControl" ) ;
}
private ActionListener actionListener = new ActionListener ( ) {
@Override
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 ) ;
}
} else if ( name . equals ( "triPlanar" ) & & ! pressed ) {
triPlanar = ! triPlanar ;
if ( triPlanar ) {
matTerrain . setBoolean ( "useTriPlanarMapping" , true ) ;
// planar textures don't use the mesh's texture coordinates but real world coordinates,
// so we need to convert these texture coordinate scales into real world scales so it looks
// the same when we switch to/from tr-planar mode (1024f is the alphamap size)
matTerrain . setFloat ( "DiffuseMap_0_scale" , 1f / ( 1024f / dirtScale ) ) ;
matTerrain . setFloat ( "DiffuseMap_1_scale" , 1f / ( 1024f / darkRockScale ) ) ;
matTerrain . setFloat ( "DiffuseMap_2_scale" , 1f / ( 1024f / pinkRockScale ) ) ;
matTerrain . setFloat ( "DiffuseMap_3_scale" , 1f / ( 1024f / riverRockScale ) ) ;
matTerrain . setFloat ( "DiffuseMap_4_scale" , 1f / ( 1024f / grassScale ) ) ;
matTerrain . setFloat ( "DiffuseMap_5_scale" , 1f / ( 1024f / brickScale ) ) ;
matTerrain . setFloat ( "DiffuseMap_6_scale" , 1f / ( 1024f / roadScale ) ) ;
} else {
matTerrain . setBoolean ( "useTriPlanarMapping" , false ) ;
matTerrain . setFloat ( "DiffuseMap_0_scale" , dirtScale ) ;
matTerrain . setFloat ( "DiffuseMap_1_scale" , darkRockScale ) ;
matTerrain . setFloat ( "DiffuseMap_2_scale" , pinkRockScale ) ;
matTerrain . setFloat ( "DiffuseMap_3_scale" , riverRockScale ) ;
matTerrain . setFloat ( "DiffuseMap_4_scale" , grassScale ) ;
matTerrain . setFloat ( "DiffuseMap_5_scale" , brickScale ) ;
matTerrain . setFloat ( "DiffuseMap_6_scale" , roadScale ) ;
}
} 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 ) ;
}
}
}
} ;
private void createSky ( ) {
Texture west = assetManager . loadTexture ( "Textures/Sky/Lagoon/lagoon_west.jpg" ) ;
Texture east = assetManager . loadTexture ( "Textures/Sky/Lagoon/lagoon_east.jpg" ) ;
Texture north = assetManager . loadTexture ( "Textures/Sky/Lagoon/lagoon_north.jpg" ) ;
Texture south = assetManager . loadTexture ( "Textures/Sky/Lagoon/lagoon_south.jpg" ) ;
Texture up = assetManager . loadTexture ( "Textures/Sky/Lagoon/lagoon_up.jpg" ) ;
Texture down = assetManager . loadTexture ( "Textures/Sky/Lagoon/lagoon_down.jpg" ) ;
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 ;
}
}