Thought I’d post this here in case any other “newbies” could use some examples of doing stuff combined together from the tutorials. I’ve got some interesting things going on with respect to driving things in the update loop and filters. Also demonstrates some custom input handling, camera auto-orbiting, etc. Here’s a screen-shot:
And here is the code (NOTE: This isn’t production/good code - organizationally, this should all be broken up into AppStates and Controllers, but, I’m just trying to get a feel for what can be done in JME3 and how):
package com.butlersoftwarellc.jme3test.helloworld;
import com.jme3.app.SimpleApplication;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
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.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.DepthOfFieldFilter;
import com.jme3.post.filters.FogFilter;
import com.jme3.post.filters.LightScatteringFilter;
import com.jme3.post.filters.TranslucentBucketFilter;
import com.jme3.post.ssao.SSAOFilter;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Torus;
import com.jme3.shadow.DirectionalLightShadowFilter;
import com.jme3.shadow.EdgeFilteringMode;
import com.jme3.util.TangentBinormalGenerator;
/**
*
* @author gbutler
* <p>
* Sample 4 - how to trigger repeating actions from the main event loop.
* In this example, you use the loop to make the player character
* rotate continuously. */
public class HelloKitchenSink
extends SimpleApplication
{
public static void main ( String[] args )
{
HelloKitchenSink app = new HelloKitchenSink();
app.start();
}
private Geometry player;
private Geometry npc;
private Geometry npcTorus;
private float npc_scale_factor = 0f;
private float third_npc_scale_factor = 0f;
private boolean isOrbiting = true;
private float orbitRadius = 20.0f;
private boolean isLookingAtPlayer = true;
private boolean isOscillatingNpc = true;
private ParticleEmitter fireEffect;
private boolean isFoggy = true;
private FogFilter fog;
private FilterPostProcessor fpp;
private final ActionListener actionListener = new ActionListener()
{
@Override
public void onAction ( String name,
boolean isPressed,
float tpf )
{
if ( name.equals( "Orbit" ) && !isPressed ) {
isOrbiting = !isOrbiting;
} else if ( name.equals( "LookAtPlayer" ) && !isPressed ) {
isLookingAtPlayer = !isLookingAtPlayer;
} else if ( name.equals( "OrbitRadiusPlus" ) && !isPressed ) {
orbitRadius = Math.min( orbitRadius + 1.0f, 40.0f );
} else if ( name.equals( "OrbitRadiusMinus" ) && !isPressed ) {
orbitRadius = Math.max( orbitRadius - 1.0f, 2.0f );
} else if ( name.equals( "OscillateNPC" ) && !isPressed ) {
isOscillatingNpc = !isOscillatingNpc;
} else if ( name.equals( "Fog" ) && !isPressed ) {
if ( isFoggy ) {
fpp.removeFilter( fog );
} else {
fpp.addFilter( fog );
}
isFoggy = !isFoggy;
}
}
};
@Override
public void simpleInitApp ()
{
// Load the town scene as a background
Spatial gameLevel = assetManager.loadModel( "Scenes/town/main.j3o" );
gameLevel.setLocalTranslation( 0, -1f, 0 );
gameLevel.setLocalScale( 2 );
rootNode.attachChild( gameLevel );
// You must add a light to make the model visible
Vector3f sunDirection = new Vector3f( -0.1f, -0.75f, -1.0f );
DirectionalLight sun = new DirectionalLight();
sun.setColor( new ColorRGBA( 0.5f, 0.3f, 0.3f, 0.8f ) );
sun.setDirection( sunDirection );
rootNode.addLight( sun );
/** Uses Texture from jme3-test-data library! */
fireEffect = new ParticleEmitter( "Emitter", ParticleMesh.Type.Triangle, 250 );
Material fireMat = new Material( assetManager, "Common/MatDefs/Misc/Particle.j3md" );
fireMat.setTexture( "Texture", assetManager.loadTexture( "Effects/Explosion/flame.png" ) );
fireEffect.setMaterial( fireMat );
fireEffect.setImagesX( 2 );
fireEffect.setImagesY( 2 ); // 2x2 texture animation
fireEffect.setStartColor( new ColorRGBA( 1f, .55f, 0.1f, 0.9f ) ); // yellow
fireEffect.setEndColor( new ColorRGBA( 1f, 0f, 0f, 1f ) ); // red
fireEffect.getParticleInfluencer()
.setInitialVelocity( new Vector3f( 0f, 2.5f, 0f ) );
fireEffect.getParticleInfluencer()
.setVelocityVariation( 0.15f );
fireEffect.setStartSize( 1.5f );
fireEffect.setEndSize( 0.01f );
fireEffect.setGravity( -2.0f, 0.25f, 0f );
fireEffect.setLowLife( 0.75f );
fireEffect.setHighLife( 3f );
fireEffect.setRotateSpeed( 0.37f );
fireEffect.setLocalTranslation( new Vector3f( 0f, 0f, -4f ) );
//fireEffect.setShape( new EmitterPointShape( Vector3f.ZERO ) );
rootNode.attachChild( fireEffect );
// Add Point Light Source of Fire
PointLight fireLight = new PointLight( new Vector3f( 0f, 0.1f, -4f ), ColorRGBA.Orange, 10.0f );
rootNode.addLight( fireLight );
/** this textured sphere is our player character */
Sphere sphere = new Sphere( 16, 16, 2f );
sphere.setTextureMode( Sphere.TextureMode.Polar ); // better quality on spheres
TangentBinormalGenerator.generate( sphere );
player = new Geometry( "Player", sphere );
player.setLocalTranslation( new Vector3f( 0f, 1f, 0f ) );
Material mat = new Material( assetManager,
"Common/MatDefs/Light/Lighting.j3md" );
mat.setTexture( "DiffuseMap", assetManager.loadTexture( "Textures/Terrain/BrickWall/BrickWall.jpg" ) );
mat.setTexture( "NormalMap", assetManager.loadTexture( "Textures/Terrain/BrickWall/BrickWall_normal.jpg" ) );
mat.setBoolean( "UseMaterialColors", true );
mat.setColor( "Ambient", ColorRGBA.Green );
mat.setColor( "Diffuse", ColorRGBA.Orange );
mat.setColor( "Specular", ColorRGBA.Brown );
mat.setFloat( "Shininess", 127f );
player.setMaterial( mat );
rootNode.attachChild( player );
// this is a similar textured sphere for comparison with the player (Dead Player)
Sphere sphere2 = new Sphere( 16, 16, 2f );
sphere2.setTextureMode( Sphere.TextureMode.Polar ); // better quality on spheres
TangentBinormalGenerator.generate( sphere2 );
Geometry p2 = new Geometry( "Dead Player", sphere2 );
p2.setLocalTranslation( new Vector3f( -4.0f, 1f, 1f ) );
mat = new Material( assetManager,
"Common/MatDefs/Light/Lighting.j3md" );
mat.setTexture( "DiffuseMap", assetManager.loadTexture( "Textures/Terrain/BrickWall/BrickWall.jpg" ) );
mat.setTexture( "NormalMap", assetManager.loadTexture( "Textures/Terrain/BrickWall/BrickWall_normal.jpg" ) );
mat.setBoolean( "UseMaterialColors", true );
mat.setColor( "Ambient", ColorRGBA.Green );
mat.setColor( "Diffuse", ColorRGBA.Orange );
mat.setColor( "Specular", ColorRGBA.Brown );
mat.setFloat( "Shininess", 127f );
p2.setMaterial( mat );
rootNode.attachChild( p2 );
/** this alpha-blended box is our NPC character */
Box b = new Box( 1, 1, 1 );
npc = new Geometry( "NPC", b );
mat = new Material( assetManager,
"Common/MatDefs/Misc/Unshaded.j3md" );
mat.setColor( "Color", ColorRGBA.Red ); // This color is mostly irrelevant, as we'll be pulsing it in the update loop
mat.setTransparent( true ); // we want to the npc to be slightly transparent - see the update loop
mat.getAdditionalRenderState()
.setBlendMode( BlendMode.Alpha );
npc.setMaterial( mat );
npc.setLocalTranslation( new Vector3f( 6f, 2f, -8f ) );
npc.setQueueBucket( Bucket.Transparent ); // transparent items need to be in the transparent queue bucket
rootNode.attachChild( npc );
// strip box for comparison
Torus torus = new Torus( 64, 16, 0.5f, 1.5f );
npcTorus = new Geometry( "NPC StripBox Comparison", torus );
mat = new Material( assetManager,
"Common/MatDefs/Misc/Unshaded.j3md" );
mat.setColor( "Color", new ColorRGBA( 1.0f, 0.0f, 0.0f, 0.5f ) );
mat.setTransparent( true ); // we want to the npc to be slightly transparent
mat.getAdditionalRenderState()
.setBlendMode( BlendMode.Alpha );
npcTorus.setMaterial( mat );
npcTorus.setLocalTranslation( new Vector3f( 6f, 2f, 4f ) );
npcTorus.setQueueBucket( Bucket.Transparent ); // transparent items need to be in the transparent queue bucket
rootNode.attachChild( npcTorus );
/** Add fog to a scene */
fpp = new FilterPostProcessor( assetManager );
fog = new FogFilter();
fog.setFogColor( new ColorRGBA( 0.9f, 0.9f, 0.9f, 0.96f ) );
fog.setFogDistance( 155 );
fog.setFogDensity( 0.98f );
fpp.addFilter( fog );
viewPort.addProcessor( fpp );
// Ambient Shadows
SSAOFilter ssaoFilter = new SSAOFilter( 12.94f, 43.93f, 0.33f, 0.60f );
fpp.addFilter( ssaoFilter );
// Scatter Sunlight
LightScatteringFilter sunlightScatteringFilter = new LightScatteringFilter( sunDirection.multLocal( -3000 ) );
sunlightScatteringFilter.setEnabled( true );
sunlightScatteringFilter.setLightDensity( 4.0f );
fpp.addFilter( sunlightScatteringFilter );
/* this shadow needs a directional light */
DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter( assetManager, 1024, 4 );
dlsf.setLight( sun );
dlsf.setEnabled( true );
dlsf.setEdgeFilteringMode( EdgeFilteringMode.Bilinear );
fpp.addFilter( dlsf );
// Add a couple of additional post-processing filters that makes things "nicer"
fpp.addFilter( new TranslucentBucketFilter( false ) );
DepthOfFieldFilter depthOfFieldFilter = new DepthOfFieldFilter();
depthOfFieldFilter.setFocusDistance( 10.0f );
depthOfFieldFilter.setFocusRange( 5.0f );
fpp.addFilter( depthOfFieldFilter );
// Initialize the Key Map
initKeys();
}
/* Use the main event loop to trigger repeating actions. */
@Override
public void simpleUpdate ( float tpf )
{
// make the player rotate:
player.rotate( 0, 2 * tpf, 0 );
// make the npc roll erattically on all axes
if ( isOscillatingNpc ) {
npc.rotate( 1 * tpf, 1.25f * tpf, 2.5f * tpf );
// compute a scale factor from 0 to TAU (2PI) and get the sin so we can have harmonic motion
npc_scale_factor = ( npc_scale_factor + FastMath.TWO_PI * tpf * 0.25f );
while ( npc_scale_factor >= FastMath.TWO_PI ) {
npc_scale_factor = ( npc_scale_factor - FastMath.TWO_PI );
}
float sin_npc_scale_factor = FastMath.sin( npc_scale_factor );
// maeke the npc grow-and-shrink and move back and forth through the donut harmonically while pulsating it's color
npc.setLocalScale( 0.1f + 1.9f * FastMath.abs( sin_npc_scale_factor ) );
npc.getMaterial()
.setColor( "Color", new ColorRGBA( 1f, 0.25f, 0.5f * FastMath.abs( sin_npc_scale_factor ) + 0.25f, 0.25f ) );
npc.setLocalTranslation( new Vector3f( 6f,
2f + FastMath.abs( sin_npc_scale_factor ),
4f + 8f * sin_npc_scale_factor ) );
// Let's make the donut rotate such that it synchronizes with the cube passing through it
npcTorus.setLocalRotation( new Quaternion().fromAngleAxis( npc_scale_factor, Vector3f.UNIT_Y ) );
}
// make the camera orbit the scene
third_npc_scale_factor = ( third_npc_scale_factor + FastMath.TWO_PI * tpf * 0.5f * 0.025f );
while ( third_npc_scale_factor >= FastMath.TWO_PI ) {
third_npc_scale_factor = ( third_npc_scale_factor - FastMath.TWO_PI );
}
float sin_third_npc_scale_factor = FastMath.sin( third_npc_scale_factor );
float cos_third_npc_scale_factor = FastMath.cos( third_npc_scale_factor );
if ( isOrbiting ) {
Camera cam = getCamera();
cam.setLocation( new Vector3f( orbitRadius * sin_third_npc_scale_factor,
12f * ( orbitRadius / 39.0f ),
orbitRadius * cos_third_npc_scale_factor ) );
if ( isLookingAtPlayer ) {
cam.lookAt( Vector3f.ZERO, Vector3f.UNIT_Y );
}
}
// adjust wind for fire
Vector3f feg = fireEffect.getGravity();
float feg_x = feg.getX();
float feg_z = feg.getZ();
feg_x = ( feg_x + ( FastMath.rand.nextFloat() * 0.25f - 0.125f ) );
feg_z = ( feg_z + ( FastMath.rand.nextFloat() * 0.25f - 0.125f ) );
feg_x = Math.max( Math.min( feg_x, 3.5f ), -3.5f ); // don't let the "wind" get too fast in any direction
feg_z = Math.max( Math.min( feg_z, 3.5f ), -3.5f );
feg.setX( feg_x );
feg.setZ( feg_z );
fireEffect.setGravity( feg );
}
private void initKeys ()
{
inputManager.addMapping( "Orbit", new KeyTrigger( KeyInput.KEY_O ) );
inputManager.addMapping( "LookAtPlayer", new KeyTrigger( KeyInput.KEY_L ) );
inputManager.addMapping( "OrbitRadiusPlus", new KeyTrigger( KeyInput.KEY_ADD ) );
inputManager.addMapping( "OrbitRadiusMinus", new KeyTrigger( KeyInput.KEY_SUBTRACT ) );
inputManager.addMapping( "OscillateNPC", new KeyTrigger( KeyInput.KEY_H ) );
inputManager.addMapping( "Fog", new KeyTrigger( KeyInput.KEY_F ) );
inputManager.addListener( actionListener, "Orbit" );
inputManager.addListener( actionListener, "LookAtPlayer" );
inputManager.addListener( actionListener, "OrbitRadiusPlus" );
inputManager.addListener( actionListener, "OrbitRadiusMinus" );
inputManager.addListener( actionListener, "OscillateNPC" );
inputManager.addListener( actionListener, "Fog" );
}
}