« Previous: Starter Tutorial 4 - Hello States
Next: Starter Tutorial 6 - Hello ModelLoading »
(Tip: Up-to-date source files for the tutorials are always in the repository)
This program introduces KeyBindingManager, texture wrapping, and how to change TriMesh data after it is assigned.
import java.net.URL; import java.nio.FloatBuffer; import com.jme.app.SimpleGame; import com.jme.image.Texture; import com.jme.input.KeyBindingManager; import com.jme.input.KeyInput; import com.jme.math.Vector2f; import com.jme.math.Vector3f; import com.jme.scene.TexCoords; import com.jme.scene.TriMesh; import com.jme.scene.state.TextureState; import com.jme.util.TextureManager; import com.jme.util.geom.BufferUtils; /** * Started Date: Jul 21, 2004<br> * <br> * * This program demonstrates using key inputs to change things. * * @author Jack Lindamood */ public class HelloKeyInput extends SimpleGame { // The TriMesh that I will change TriMesh square; // A scale of my current texture values float coordDelta; public static void main(String[] args) { HelloKeyInput app = new HelloKeyInput(); app.setConfigShowMode(ConfigShowMode.AlwaysShow); app.start(); } protected void simpleInitGame() { lightState.setEnabled(false); // Vertex positions for the mesh Vector3f[] vertexes = { new Vector3f(0, 0, 0), new Vector3f(1, 0, 0), new Vector3f(0, 1, 0), new Vector3f(1, 1, 0) }; // Texture Coordinates for each position coordDelta = 1; Vector2f[] texCoords = { new Vector2f(0, 0), new Vector2f(coordDelta, 0), new Vector2f(0, coordDelta), new Vector2f(coordDelta, coordDelta) }; // The indexes of Vertex/Normal/Color/TexCoord sets. Every 3 makes a // triangle. int[] indexes = { 0, 1, 2, 1, 2, 3 }; // Create the square square = new TriMesh("My Mesh", BufferUtils.createFloatBuffer(vertexes), null, null, TexCoords.makeNew(texCoords), BufferUtils.createIntBuffer(indexes)); // Point to the monkey image URL monkeyLoc = HelloKeyInput.class.getClassLoader().getResource( "jmetest/data/images/Monkey.jpg"); // Get my TextureState TextureState ts = display.getRenderer().createTextureState(); // Get my Texture Texture t = TextureManager.loadTexture(monkeyLoc, Texture.MinificationFilter.BilinearNearestMipMap, Texture.MagnificationFilter.Bilinear); // Set a wrap for my texture so it repeats t.setWrap(Texture.WrapMode.Repeat); // Set the texture to the TextureState ts.setTexture(t); // Assign the TextureState to the square square.setRenderState(ts); // Scale my square 10x larger square.setLocalScale(10); // Attach my square to my rootNode rootNode.attachChild(square); // Assign the "+" key on the keypad to the command "coordsUp" KeyBindingManager.getKeyBindingManager().set("coordsUp", KeyInput.KEY_ADD); // Adds the "u" key to the command "coordsUp" KeyBindingManager.getKeyBindingManager().add("coordsUp", KeyInput.KEY_U); // Assign the "-" key on the keypad to the command "coordsDown" KeyBindingManager.getKeyBindingManager().set("coordsDown", KeyInput.KEY_SUBTRACT); } // Called every frame update protected void simpleUpdate() { boolean updateTex = false; // If the coordsDown command was activated if (KeyBindingManager.getKeyBindingManager().isValidCommand( "coordsDown", true)) { // Scale my texture down coordDelta -= .01f; updateTex = true; } // if the coordsUp command was activated if (KeyBindingManager.getKeyBindingManager().isValidCommand("coordsUp", true)) { // Scale my texture up coordDelta += .01f; updateTex = true; } if (updateTex) { // Get my square's texture array FloatBuffer texBuf = square.getTextureCoords(0).coords; texBuf.rewind().position(2); // start after the 1st texcoord (2 // floats wide) // Change the values of the texture coords in the buffer texBuf.put(coordDelta).put(0); texBuf.put(0).put(coordDelta); texBuf.put(coordDelta).put(coordDelta); } } }
The first new thing you see here is how I create my square:
// Create the square square = new TriMesh("My Mesh", BufferUtils.createFloatBuffer(vertexes), null, null, TexCoords.makeNew(texCoords), BufferUtils.createIntBuffer(indexes));
It’s just like the Hello Trimesh tutorial, but I put all arguments into one constructor. Not too difficult, right?
Next, I set a texture wrap attribute:
// Set a wrap for my texture so it repeats t.setWrap(Texture.WrapMode.Repeat);
Without this line, my texture picture would just get smaller. It wouldn’t show up 20 million times like it does in the picture above. As an example, comment this line out and run the program. You’ll see something like the following:
After attaching the TextureState and nodes correctly, I begin to bind commands to keys:
// Assign the "+" key on the keypad to the command "coordsUp" KeyBindingManager.getKeyBindingManager().set("coordsUp", KeyInput.KEY_ADD);
Key input is abstracted away just like the rendering, so I have to use display.getRenderer() in the same way I also have to use KeyBindingManager.getKeyBindingManager().
This function sets the command coordsUp to the key KEY_ADD (which is the + key). Later, we won’t check “Did they press the + key?”. Instead we will check “Did the coordsUp action happen?”. This lets me assign multiple keys to the same command, and lets users customize their keys very easily.
Speaking of this, next I assign the U key to coordsUp as well:
// Adds the "u" key to the command "coordsUp" KeyBindingManager.getKeyBindingManager().add("coordsUp", KeyInput.KEY_U);
Notice the add function call, not the set function call. This “adds” key U to the coordsUp command.
So either key U or + will trigger coordsUp.
Next I add a key for coordsDown:
// Assign the "-" key on the keypad to the command "coordsDown" KeyBindingManager.getKeyBindingManager().set("coordsDown", KeyInput.KEY_SUBTRACT);
It’s just like the one I assigned for coordsUp.
Next you see a new function we haven’t used before:
// Called every frame update protected void simpleUpdate() {
Here I am overriding the simpleUpdate() method in SimpleGame. This function is called every frame. By
putting queries here, I can poll for a key every frame. Which is exactly what I do right off the bat:
// If the coordsUp command was activated if (KeyBindingManager.getKeyBindingManager().isValidCommand( "coordsUp",false)) {
Notice I don’t ask “Did they press the + key or the U key?”. I don’t need to know which key is coordsUp. I just ask “Did a coordsUp key get pressed?”. The Boolean at the end signals if I allow users to hold the button down and execute the command multiple times. Change the false to true, and you’ll notice you only get more changes per button press.
On a down command, I shrink my texture cords for each point of my square:
// Scale my texture up coordDelta += .01f;
Since we don't want to write code twice, we add a boolean flag updateTex, indicating wether the texture should be updated.
updateTex = true;
Next I check for the boolean flag updateTex and then I get my texture array for the square and change the values in the array:
if (updateTex) { // Get my square's texture array FloatBuffer texBuf = square.getTextureCoords(0).coords; // Start after the 1st texcoord (2 floats wide) texBuf.rewind().position(2); // Change the values of the texture coords in the buffer texBuf.put(coordDelta).put(0); texBuf.put(0).put(coordDelta); texBuf.put(coordDelta).put(coordDelta); }
Texture coordinate numbers are a value between 0 and 1 that signals which part of my picture is put in the rectangle. If they go from 0 to 0.5 then the lower half is shown. If they go from 0.5 to 1 then the upper half is shown. Because I set the command wrap, the texture will wrap, so that a value of 1.5 means it will show my entire picture plus the first half. That is why, as coordDelta increases, you see many monkeys.
First we call:
texBuf.rewind().position(2); // start after the 1st texcoord (2 floats wide)
Start putting values after the 1st texcoord. We don't want to change the first Texcoord, cause it should always stay [0,0]. Then we put in the new values:
// Change the values of the texture coords in the buffer texBuf.put(coordDelta).put(0); texBuf.put(0).put(coordDelta); texBuf.put(coordDelta).put(coordDelta)
The put(float) method is a bit like the println() method, you put a value and then iterate to the next.
So the first call to put(float) will edit the third float in the Buffer (remember we did position(2)) and the next will edit the fourth and so on.
Now our TexCoords Buffer look like the following:
[0 0 coordDelta 0 0 coordDelta coordDelta coordDelta]
which corresponds directly to our texture coordinates
texCoords[0] = 0, 0 texCoords[1] = coordDelta, 0 texCoords[2] = 0, coordDelta texCoords[3] = coordDelta, coordDelta
That’s it. Simple!