2 * Copyright (c) 2003-2009 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 package jmetest.flagrushtut;
35 import java.util.logging.Level;
36 import java.util.logging.Logger;
38 import javax.swing.ImageIcon;
40 import jmetest.renderer.TestSkybox;
41 import jmetest.terrain.TestTerrain;
43 import com.jme.app.BaseGame;
44 import com.jme.bounding.BoundingBox;
45 import com.jme.image.Texture;
46 import com.jme.input.KeyBindingManager;
47 import com.jme.input.KeyInput;
48 import com.jme.light.DirectionalLight;
49 import com.jme.math.FastMath;
50 import com.jme.math.Quaternion;
51 import com.jme.math.Vector3f;
52 import com.jme.renderer.Camera;
53 import com.jme.renderer.ColorRGBA;
54 import com.jme.renderer.Renderer;
55 import com.jme.scene.Node;
56 import com.jme.scene.SharedMesh;
57 import com.jme.scene.Skybox;
58 import com.jme.scene.shape.Box;
59 import com.jme.scene.shape.Cylinder;
60 import com.jme.scene.state.BlendState;
61 import com.jme.scene.state.LightState;
62 import com.jme.scene.state.TextureState;
63 import com.jme.scene.state.ZBufferState;
64 import com.jme.system.DisplaySystem;
65 import com.jme.system.JmeException;
66 import com.jme.util.TextureManager;
67 import com.jme.util.Timer;
68 import com.jme.util.geom.Debugger;
69 import com.jmex.terrain.TerrainBlock;
70 import com.jmex.terrain.util.MidPointHeightMap;
71 import com.jmex.terrain.util.ProceduralTextureGenerator;
74 * <code>Tutorial 4</code> Builds the environment (surrounding structure and sky).
75 * framework for Flag Rush. For Flag Rush Tutorial Series.
79 public class Lesson4 extends BaseGame {
80 private static final Logger logger = Logger.getLogger(Lesson4.class
83 // the terrain we will drive over.
84 private TerrainBlock tb;
85 // The texture that makes up the "force field", we will keep a reference to it
86 // here to allow us to animate it.
88 //Sky box (we update it each frame)
89 private Skybox skybox;
92 protected Timer timer;
94 // Our camera object for viewing the scene
97 // the root node of the scene graph
100 // display attributes for the window. We will keep these values
101 // to allow the user to change them
102 private int width, height, depth, freq;
103 private boolean fullscreen;
106 * Main entry point of the application
108 public static void main(String[] args) {
109 Lesson4 app = new Lesson4();
110 // We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
111 app.setConfigShowMode(ConfigShowMode.AlwaysShow, Lesson4.class
112 .getClassLoader().getResource(
113 "jmetest/data/images/FlagRush.png"));
118 * During an update we look for the escape button and update the timer
119 * to get the framerate. Things are now starting to happen, so we will
122 * @see com.jme.app.BaseGame#update(float)
124 protected void update(float interpolation) {
125 // update the time to get the framerate
127 interpolation = timer.getTimePerFrame();
129 //We will use the interpolation value to keep the speed
130 //of the forcefield consistent between computers.
131 //we update the Y have of the texture matrix to give
132 //the appearance the forcefield is moving.
133 t.getTranslation().y += 0.3f * interpolation;
134 //if the translation is over 1, it's wrapped, so go ahead
135 //and check for this (to keep the vector's y value from getting
137 if(t.getTranslation().y > 1) {
138 t.getTranslation().y = 0;
141 //we want to keep the skybox around our eyes, so move it with
143 skybox.setLocalTranslation(cam.getLocation());
145 // if escape was pressed, we exit
146 if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
150 //Because we are changing the scene (moving the skybox) we need to update
152 scene.updateGeometricState(interpolation, true);
156 * draws the scene graph
158 * @see com.jme.app.BaseGame#render(float)
160 protected void render(float interpolation) {
162 display.getRenderer().clearBuffers();
163 display.getRenderer().draw(scene);
164 Debugger.drawBounds(scene, display.getRenderer());
168 * initializes the display and camera.
170 * @see com.jme.app.BaseGame#initSystem()
172 protected void initSystem() {
173 // store the settings information
174 width = settings.getWidth();
175 height = settings.getHeight();
176 depth = settings.getDepth();
177 freq = settings.getFrequency();
178 fullscreen = settings.isFullscreen();
181 display = DisplaySystem.getDisplaySystem(settings.getRenderer());
182 display.createWindow(width, height, depth, freq, fullscreen);
184 cam = display.getRenderer().createCamera(width, height);
185 } catch (JmeException e) {
186 logger.log(Level.SEVERE, "Could not create displaySystem", e);
190 // set the background to black
191 display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());
193 // initialize the camera
194 cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
196 Vector3f loc = new Vector3f(250.0f, 100.0f, 250.0f);
197 Vector3f left = new Vector3f(-0.5f, 0.0f, 0.5f);
198 Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
199 Vector3f dir = new Vector3f(-0.5f, 0.0f, -0.5f);
200 // Move our camera to a correct place and orientation.
201 cam.setFrame(loc, left, up, dir);
202 /** Signal that we've changed our camera's location/frustum. */
205 /** Get a high resolution timer for FPS updates. */
206 timer = Timer.getTimer();
208 display.getRenderer().setCamera(cam);
210 KeyBindingManager.getKeyBindingManager().set("exit",
211 KeyInput.KEY_ESCAPE);
215 * initializes the scene
217 * @see com.jme.app.BaseGame#initGame()
219 protected void initGame() {
220 display.setTitle("Flag Rush");
222 scene = new Node("Scene graph node");
223 /** Create a ZBuffer to display pixels closest to the camera above farther ones. */
224 ZBufferState buf = display.getRenderer().createZBufferState();
225 buf.setEnabled(true);
226 buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
227 scene.setRenderState(buf);
228 //Add terrain to the scene
232 //add the force field fence
237 // update the scene graph for rendering
238 scene.updateGeometricState(0.0f, true);
239 scene.updateRenderState();
243 * buildEnvironment will create a fence. This is done by hand
244 * to show how to create geometry and shared this geometry.
245 * Normally, you wouldn't build your models by hand as it is
246 * too much of a trial and error process.
248 private void buildEnvironment() {
249 //This is the main node of our fence
250 Node forceFieldFence = new Node("fence");
252 //This cylinder will act as the four main posts at each corner
253 Cylinder postGeometry = new Cylinder("post", 10, 10, 1, 10);
254 Quaternion q = new Quaternion();
255 //rotate the cylinder to be vertical
256 q.fromAngleAxis(FastMath.PI/2, new Vector3f(1,0,0));
257 postGeometry.setLocalRotation(q);
258 postGeometry.setModelBound(new BoundingBox());
259 postGeometry.updateModelBound();
261 //We will share the post 4 times (one for each post)
262 //It is *not* a good idea to add the original geometry
263 //as the sharedmeshes will alter its local values.
264 //We then translate the posts into position.
265 //Magic numbers are bad, but help illustrate the point.:)
266 SharedMesh post1 = new SharedMesh("post1", postGeometry);
267 post1.setLocalTranslation(new Vector3f(0,0.5f,0));
268 SharedMesh post2 = new SharedMesh("post2", postGeometry);
269 post2.setLocalTranslation(new Vector3f(32,0.5f,0));
270 SharedMesh post3 = new SharedMesh("post3", postGeometry);
271 post3.setLocalTranslation(new Vector3f(0,0.5f,32));
272 SharedMesh post4 = new SharedMesh("post4", postGeometry);
273 post4.setLocalTranslation(new Vector3f(32,0.5f,32));
275 //This cylinder will be the horizontal struts that hold
276 //the field in place.
277 Cylinder strutGeometry = new Cylinder("strut", 10,10, 0.125f, 32);
278 strutGeometry.setModelBound(new BoundingBox());
279 strutGeometry.updateModelBound();
281 //again, we'll share this mesh.
282 //Some we need to rotate to connect various posts.
283 SharedMesh strut1 = new SharedMesh("strut1", strutGeometry);
284 Quaternion rotate90 = new Quaternion();
285 rotate90.fromAngleAxis(FastMath.PI/2, new Vector3f(0,1,0));
286 strut1.setLocalRotation(rotate90);
287 strut1.setLocalTranslation(new Vector3f(16,3f,0));
288 SharedMesh strut2 = new SharedMesh("strut2", strutGeometry);
289 strut2.setLocalTranslation(new Vector3f(0,3f,16));
290 SharedMesh strut3 = new SharedMesh("strut3", strutGeometry);
291 strut3.setLocalTranslation(new Vector3f(32,3f,16));
292 SharedMesh strut4 = new SharedMesh("strut4", strutGeometry);
293 strut4.setLocalRotation(rotate90);
294 strut4.setLocalTranslation(new Vector3f(16,3f,32));
296 //Create the actual forcefield
297 //The first box handles the X-axis, the second handles the z-axis.
298 //We don't rotate the box as a demonstration on how boxes can be
299 //created differently.
300 Box forceFieldX = new Box("forceFieldX", new Vector3f(-16, -3f, -0.1f), new Vector3f(16f, 3f, 0.1f));
301 forceFieldX.setModelBound(new BoundingBox());
302 forceFieldX.updateModelBound();
303 //We are going to share these boxes as well
304 SharedMesh forceFieldX1 = new SharedMesh("forceFieldX1",forceFieldX);
305 forceFieldX1.setLocalTranslation(new Vector3f(16,0,0));
306 SharedMesh forceFieldX2 = new SharedMesh("forceFieldX2",forceFieldX);
307 forceFieldX2.setLocalTranslation(new Vector3f(16,0,32));
309 //The other box for the Z axis
310 Box forceFieldZ = new Box("forceFieldZ", new Vector3f(-0.1f, -3f, -16), new Vector3f(0.1f, 3f, 16));
311 forceFieldZ.setModelBound(new BoundingBox());
312 forceFieldZ.updateModelBound();
313 //and again we will share it
314 SharedMesh forceFieldZ1 = new SharedMesh("forceFieldZ1",forceFieldZ);
315 forceFieldZ1.setLocalTranslation(new Vector3f(0,0,16));
316 SharedMesh forceFieldZ2 = new SharedMesh("forceFieldZ2",forceFieldZ);
317 forceFieldZ2.setLocalTranslation(new Vector3f(32,0,16));
319 //add all the force fields to a single node and make this node part of
320 //the transparent queue.
321 Node forceFieldNode = new Node("forceFieldNode");
322 forceFieldNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
323 forceFieldNode.attachChild(forceFieldX1);
324 forceFieldNode.attachChild(forceFieldX2);
325 forceFieldNode.attachChild(forceFieldZ1);
326 forceFieldNode.attachChild(forceFieldZ2);
328 //Add the alpha values for the transparent node
329 BlendState as1 = display.getRenderer().createBlendState();
330 as1.setBlendEnabled(true);
331 as1.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
332 as1.setDestinationFunction(BlendState.DestinationFunction.One);
333 as1.setTestEnabled(true);
334 as1.setTestFunction(BlendState.TestFunction.GreaterThan);
335 as1.setEnabled(true);
337 forceFieldNode.setRenderState(as1);
339 //load a texture for the force field elements
340 TextureState ts = display.getRenderer().createTextureState();
341 t = TextureManager.loadTexture(Lesson2.class.getClassLoader()
342 .getResource("jmetest/data/texture/reflector.jpg"),
343 Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
345 t.setWrap(Texture.WrapMode.Repeat);
346 t.setTranslation(new Vector3f());
349 forceFieldNode.setRenderState(ts);
352 //put all the posts into a tower node
353 Node towerNode = new Node("tower");
354 towerNode.attachChild(post1);
355 towerNode.attachChild(post2);
356 towerNode.attachChild(post3);
357 towerNode.attachChild(post4);
359 //add the tower to the opaque queue (we don't want to be able to see through them)
360 //and we do want to see them through the forcefield.
361 towerNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
363 //load a texture for the towers
364 TextureState ts2 = display.getRenderer().createTextureState();
365 Texture t2 = TextureManager.loadTexture(Lesson2.class.getClassLoader()
366 .getResource("jmetest/data/texture/post.jpg"),
367 Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
371 towerNode.setRenderState(ts2);
373 //put all the struts into a single node.
374 Node strutNode = new Node("strutNode");
375 strutNode.attachChild(strut1);
376 strutNode.attachChild(strut2);
377 strutNode.attachChild(strut3);
378 strutNode.attachChild(strut4);
379 //this too is in the opaque queue.
380 strutNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
382 //load a texture for the struts
383 TextureState ts3 = display.getRenderer().createTextureState();
384 Texture t3 = TextureManager.loadTexture(Lesson2.class.getClassLoader()
385 .getResource("jmetest/data/texture/rust.jpg"),
386 Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear);
390 strutNode.setRenderState(ts3);
392 //we will do a little 'tweaking' by hand to make it fit in the terrain a bit better.
393 //first we'll scale the entire "model" by a factor of 5
394 forceFieldFence.setLocalScale(new Vector3f(5,4,4));
395 //now let's move the fence to to the height of the terrain and in a little bit.
396 forceFieldFence.setLocalTranslation(new Vector3f(25, tb.getHeight(25,25) + 15, 25));
398 //Attach all the pieces to the main fence node
399 forceFieldFence.attachChild(forceFieldNode);
400 forceFieldFence.attachChild(towerNode);
401 forceFieldFence.attachChild(strutNode);
403 scene.attachChild(forceFieldFence);
407 * creates a light for the terrain.
409 private void buildLighting() {
410 /** Set up a basic, default light. */
411 DirectionalLight light = new DirectionalLight();
412 light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
413 light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
414 light.setDirection(new Vector3f(1,-1,0));
415 light.setEnabled(true);
417 /** Attach the light to a lightState and the lightState to rootNode. */
418 LightState lightState = display.getRenderer().createLightState();
419 lightState.setEnabled(true);
420 lightState.attach(light);
421 scene.setRenderState(lightState);
425 * build the height map and terrain block.
427 private void buildTerrain() {
430 MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
432 Vector3f terrainScale = new Vector3f(4, 0.0575f, 4);
433 // create a terrainblock
434 tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
435 heightMap.getHeightMap(), new Vector3f(0, 0, 0));
437 tb.setModelBound(new BoundingBox());
438 tb.updateModelBound();
440 // generate a terrain texture with 2 textures
441 ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
443 pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
444 .getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
445 pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
446 .getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
447 pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
448 .getResource("jmetest/data/texture/highest.jpg")), 128, 255,
450 pt.createTexture(32);
452 // assign the texture to the terrain
453 TextureState ts = display.getRenderer().createTextureState();
454 Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
455 Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true);
456 ts.setTexture(t1, 0);
458 tb.setRenderState(ts);
459 tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
460 scene.attachChild(tb);
466 * buildSkyBox creates a new skybox object with all the proper textures. The
467 * textures used are the standard skybox textures from all the tests.
470 private void buildSkyBox() {
471 skybox = new Skybox("skybox", 10, 10, 10);
473 Texture north = TextureManager.loadTexture(
474 TestSkybox.class.getClassLoader().getResource(
475 "jmetest/data/texture/north.jpg"),
476 Texture.MinificationFilter.BilinearNearestMipMap,
477 Texture.MagnificationFilter.Bilinear);
478 Texture south = TextureManager.loadTexture(
479 TestSkybox.class.getClassLoader().getResource(
480 "jmetest/data/texture/south.jpg"),
481 Texture.MinificationFilter.BilinearNearestMipMap,
482 Texture.MagnificationFilter.Bilinear);
483 Texture east = TextureManager.loadTexture(
484 TestSkybox.class.getClassLoader().getResource(
485 "jmetest/data/texture/east.jpg"),
486 Texture.MinificationFilter.BilinearNearestMipMap,
487 Texture.MagnificationFilter.Bilinear);
488 Texture west = TextureManager.loadTexture(
489 TestSkybox.class.getClassLoader().getResource(
490 "jmetest/data/texture/west.jpg"),
491 Texture.MinificationFilter.BilinearNearestMipMap,
492 Texture.MagnificationFilter.Bilinear);
493 Texture up = TextureManager.loadTexture(
494 TestSkybox.class.getClassLoader().getResource(
495 "jmetest/data/texture/top.jpg"),
496 Texture.MinificationFilter.BilinearNearestMipMap,
497 Texture.MagnificationFilter.Bilinear);
498 Texture down = TextureManager.loadTexture(
499 TestSkybox.class.getClassLoader().getResource(
500 "jmetest/data/texture/bottom.jpg"),
501 Texture.MinificationFilter.BilinearNearestMipMap,
502 Texture.MagnificationFilter.Bilinear);
504 skybox.setTexture(Skybox.Face.North, north);
505 skybox.setTexture(Skybox.Face.West, west);
506 skybox.setTexture(Skybox.Face.South, south);
507 skybox.setTexture(Skybox.Face.East, east);
508 skybox.setTexture(Skybox.Face.Up, up);
509 skybox.setTexture(Skybox.Face.Down, down);
510 skybox.preloadTextures();
511 scene.attachChild(skybox);
515 * will be called if the resolution changes
517 * @see com.jme.app.BaseGame#reinit()
519 protected void reinit() {
520 display.recreateWindow(width, height, depth, freq, fullscreen);
524 * close the window and also exit the program.
526 protected void quit() {
532 * clean up the textures.
534 * @see com.jme.app.BaseGame#cleanup()
536 protected void cleanup() {