JME2 Starter 4 - Hello States


« Previous: Starter Tutorial 3 - Hello TriMesh
Next: Starter Tutorial 5 - Hello KeyInput »


(Tip: Up-to-date source files for the tutorials are always in the repository)


This example introduces MaterialState, TextureState, and LightState. You learn how to assign States to objects that give the surface a pattern and color. For example, I want to create a cube with a jMonkey picture on it.

Code Sample

import java.net.URL;
 
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingSphere;
import com.jme.image.Texture;
import com.jme.light.PointLight;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.util.TextureManager;
 
/**
 * Started Date: Jul 20, 2004<br>
 * <br>
 * 
 * Demonstrates using RenderStates with jME.
 * 
 * @author Jack Lindamood
 */
public class HelloStates extends SimpleGame {
	public static void main(String[] args) {
		HelloStates app = new HelloStates();
		app.setConfigShowMode(ConfigShowMode.AlwaysShow);
		app.start();
	}
 
	protected void simpleInitGame() {
		// Create our objects. Nothing new here.
		Box b = new Box("My box", new Vector3f(1, 1, 1), new Vector3f(2, 2, 2));
		b.setModelBound(new BoundingBox());
		b.updateModelBound();
		Sphere s = new Sphere("My sphere", 15, 15, 1);
		s.setModelBound(new BoundingSphere());
		s.updateModelBound();
		Node n = new Node("My root node");
 
		// Get a URL that points to the texture we're going to load
		URL monkeyLoc;
		monkeyLoc = HelloStates.class.getClassLoader().getResource(
				"jmetest/data/images/Monkey.jpg");
 
		// Get a TextureState
		TextureState ts = display.getRenderer().createTextureState();
		// Use the TextureManager to load a texture
		Texture t = TextureManager.loadTexture(monkeyLoc,
				Texture.MinificationFilter.BilinearNearestMipMap,
				Texture.MagnificationFilter.Bilinear);
		// Assign the texture to the TextureState
		ts.setTexture(t);
 
		// Get a MaterialState
		MaterialState ms = display.getRenderer().createMaterialState();
		// Give the MaterialState an emissive tint
		ms.setEmissive(new ColorRGBA(0f, .2f, 0f, 1));
 
		// Create a point light
		PointLight l = new PointLight();
		// Give it a location
		l.setLocation(new Vector3f(0, 10, 5));
		// Make it a red light
		l.setDiffuse(ColorRGBA.red.clone());
		// Enable it
		l.setEnabled(true);
 
		// Create a LightState to put my light in
		LightState ls = display.getRenderer().createLightState();
		// Attach the light
		ls.attach(l);
 
		// Signal that b should use renderstate ts
		b.setRenderState(ts);
		// Signal that n should use renderstate ms
		n.setRenderState(ms);
		// Detach all the default lights made by SimpleGame
		lightState.detachAll();
		// Make my light effect everything below node n
		n.setRenderState(ls);
 
		// Attach b and s to n, and n to rootNode.
		n.attachChild(b);
		n.attachChild(s);
		rootNode.attachChild(n);
	}
}

DisplaySystem and Renderer

Every object in jME has several states. Here we learn more about TextureState, MaterialState, and LightState: These States define how an object is rendered. This is why they are dependent on the renderer.

Did you note the object display? It always refers to whichever renderer I’m using. This variable is already defined for me in SimpleGame as a reference to DisplaySystem.getDisplaySystem(). This way your code stays the same, no matter whether your user decides to use LWJGL or JOGL. This is jME’s abstraction, the game code is independent of the rendering environment, very handy!

Using a ClassLoader to Load a Texture

First we use a ClassLoader to locate the image that we want to put on the box: The Monkey.jpg image is stored in the sources directory (src) under jmetest/data/images/.

// Get a URL that points to the texture we're going to load
URL monkeyLoc;
monkeyLoc=HelloStates.class.getClassLoader().getResource( "jmetest/data/images/Monkey.jpg" );

Second we create a Texture (jME API) object from the image in the monkeyLoc URL.

// Use the TextureManager to load a texture
Texture t=TextureManager.loadTexture(
  monkeyLoc,
  Texture.MinificationFilter.BilinearNearestMipMap,
  Texture.MagnificationFilter.Bilinear);

To create a Texture, we have used a class called Texture Manager (jME API). It manages loading of textures so that all we need to do is supply the URL and it will load correctly. The constants BilinearNearestMipMap and Bilinear just let jME know how to filter the texture, I won’t get into any details here. For now, just use these two filters all the time.

TextureState: Object Surface

To assign a texture to an object we need a TextureState (jME API). A TextureState is created by the DisplaySystem.

// Get a TextureState
TextureState ts=display.getRenderer().createTextureState();

Our TextureState is ready to accept our previously created Texture object.

// Assign the texture to the TextureState
ts.setTexture(t);

TextureState maintains a texture state for a given node and it's children. The number of states that a TextureState can maintain at one time is equal to the number of texture units available on the GPU.

MaterialState: Object Color

To assign a color to an object we need a MaterialState (jME API). A MaterialState is created by the DisplaySystem.

// Get a MaterialState
MaterialState ms=display.getRenderer().createMaterialState();

Now that I have the MaterialState, I give it a color:

// Give the MaterialState an emissive tint
ms.setEmissive(new ColorRGBA(0f,.2f,0f,1));

The Emissive Color setting makes the object look a tiny bit green, which is why you see a green tint at the bottom. Material is defined by the emissive quality of the object, the ambient color, diffuse color and specular color. The material also defines the shininess of the object and the alpha value of the object. MatrialState has methods such as setEmissive, setSpecular, setDiffuse, setAmbient, setShininess. These work just like any other modeling environment.


Did you know? The difference between MaterialStates and the previously discussed per-vertex coloring is that the MaterialState uses lighting and shadows, so it looks much better. You can use either one or the other.

LightState

The appearance of both the MaterialState and the TextureState depend on a light source. A light source is made up of a light object and a LightState (jME API).

// Create a point light
PointLight l=new PointLight();
// Give it a location
l.setLocation(new Vector3f(0,10,5));

This creates a point light object. Think of a PointLight (jME API) as a lightbulb. It’s a light, at a point. Which point, you ask? Well, that’s why I use setLocation. It moves my light to (x=0,y=10,z=5). Lights can have colors of their own, and I give my light a red color:

// Make it a red light
l.setDiffuse(ColorRGBA.red.clone());

After creating the PointLight object, I have to attach the light to a LightState. The LightState is what makes the MaterialState's color visible. A LightState (jME API) is created by the DisplaySystem.

// Create a LightState to put my light in
LightState ls=display.getRenderer().createLightState();
// Attach the light
ls.attach(l);

Assigning States to Objects

After I have created all my states, I have to assign them to the nodes that I want them to affect. Node n is the parent of the box b and the sphere s. I want everything child of Node n to have a greenish tint from the MaterialState ms. I additionally want the reddish light ls to shine on node n. I also want to apply the monkey TextureState ts on the box.

// Detach all the default lights made by SimpleGame
lightState.detachAll();
// Make my light affect everything below Node n
n.setRenderState(ls);
// Signal that Node ''n'' should use renderstate ''ms''
n.setRenderState(ms);
// Signal that the Box ''b'' should use renderstate ''ts''
b.setRenderState(ts);

Notice I have to call detachAll() on an object called lightState. That is because SimpleGame makes a light for us and puts it in lightState. I only want to use my lights (the red light), so I detach all of the default lightState lights.

Scene Graph

This is what the scene graph looks like now:


rootNode
“My root node” n red lightstate, green materialstate
“my box” b monkey texturestate “My sphere” s


Again, the scene graph inheritance comes into play. The box “my box” is affected by a red lightstate (the red light) as well as the green MaterialState (the green tint), because it is a child of n. The same with s.


Compare it with this example scene graph:


rootNode
“My root node” n red lightstate, green materialstate “Another sphere” q
“My sphere” s “my box” b monkey texturestate


If my SceneGraph had a Sphere q attached to the rootNode, Sphere q would not be affected by Node n’s light at all – no matter how close it was to the lights location! That’s simply because q is not a child of n. This allows you to control precisely what your lights are lighting. If an object appears to dark, too bright, or colorless, check the SceneGraph whether you got the inheritance of States right.

 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution 3.0 Unported