OSDN Git Service

a06100147eb427bb7870eb188d8ebcf690a3e0f6
[mikumikustudio/MikuMikuStudio.git] / src / jmetest / flagrushtut / lesson8 / Lesson8.java
1 /*
2  * Copyright (c) 2003-2009 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
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.
15  *
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.
19  *
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.
31  */
32
33 package jmetest.flagrushtut.lesson8;
34
35 import java.io.IOException;
36 import java.net.URL;
37 import java.util.HashMap;
38 import java.util.logging.Level;
39 import java.util.logging.Logger;
40
41 import javax.swing.ImageIcon;
42
43 import jmetest.renderer.TestSkybox;
44 import jmetest.terrain.TestTerrain;
45
46 import com.jme.app.BaseGame;
47 import com.jme.bounding.BoundingBox;
48 import com.jme.image.Texture;
49 import com.jme.input.ChaseCamera;
50 import com.jme.input.InputHandler;
51 import com.jme.input.KeyBindingManager;
52 import com.jme.input.KeyInput;
53 import com.jme.input.thirdperson.ThirdPersonMouseLook;
54 import com.jme.light.DirectionalLight;
55 import com.jme.math.FastMath;
56 import com.jme.math.Vector3f;
57 import com.jme.renderer.Camera;
58 import com.jme.renderer.ColorRGBA;
59 import com.jme.renderer.Renderer;
60 import com.jme.scene.Node;
61 import com.jme.scene.Skybox;
62 import com.jme.scene.Spatial;
63 import com.jme.scene.state.CullState;
64 import com.jme.scene.state.LightState;
65 import com.jme.scene.state.TextureState;
66 import com.jme.scene.state.ZBufferState;
67 import com.jme.system.DisplaySystem;
68 import com.jme.system.JmeException;
69 import com.jme.util.TextureManager;
70 import com.jme.util.Timer;
71 import com.jme.util.export.binary.BinaryImporter;
72 import com.jmex.terrain.TerrainBlock;
73 import com.jmex.terrain.util.MidPointHeightMap;
74 import com.jmex.terrain.util.ProceduralTextureGenerator;
75
76 /**
77  * Lesson 8 introduces a few visual enhancements and the goal of the game. The flag to grab.
78  * Tilting of the bike to simulate leaning into turns is added, the wheels now rotate with the
79  * speed of the bike and a flag is in making use of the jME cloth system.
80  * @author Mark Powell
81  */
82 public class Lesson8 extends BaseGame {
83     private static final Logger logger = Logger.getLogger(Lesson8.class
84             .getName());
85     
86     // the terrain we will drive over.
87     private TerrainBlock tb;
88     // fence that will keep us in.
89     private ForceFieldFence fence;
90     //Sky box (we update it each frame)
91     private Skybox skybox;
92     //the new player object
93     private Vehicle player;
94     //the flag to grab
95     private Flag flag;
96     //private ChaseCamera chaser;
97     protected InputHandler input;
98     //the timer
99     protected Timer timer;
100     // Our camera object for viewing the scene
101     private Camera cam;
102     //The chase camera, this will follow our player as he zooms around the level
103     private ChaseCamera chaser;
104     // the root node of the scene graph
105     private Node scene;
106
107     // display attributes for the window. We will keep these values
108     // to allow the user to change them
109     private int width, height, depth, freq;
110     private boolean fullscreen;
111     
112     //store the normal of the terrain
113     private Vector3f normal = new Vector3f();
114     
115     //height above ground level
116     private float agl;
117     
118     /**
119      * Main entry point of the application
120      */
121     public static void main(String[] args) {
122         Lesson8 app = new Lesson8();
123         // We will load our own "fantastic" Flag Rush logo. Yes, I'm an artist.
124         app.setConfigShowMode(ConfigShowMode.AlwaysShow, Lesson8.class
125                 .getClassLoader().getResource(
126                         "jmetest/data/images/FlagRush.png"));
127         app.start();
128     }
129
130     /**
131      * During an update we look for the escape button and update the timer
132      * to get the framerate. Things are now starting to happen, so we will 
133      * update 
134      * 
135      * @see com.jme.app.BaseGame#update(float)
136      */
137     protected void update(float interpolation) {
138         // update the time to get the framerate
139         timer.update();
140         interpolation = timer.getTimePerFrame();
141         //update the keyboard input (move the player around)
142         input.update(interpolation);
143         //update the chase camera to handle the player moving around.
144         chaser.update(interpolation);
145         //update the fence to animate the force field texture
146         fence.update(interpolation);
147         //update the flag to make it flap in the wind
148         flag.update(interpolation);
149         
150         //we want to keep the skybox around our eyes, so move it with
151         //the camera
152         skybox.setLocalTranslation(cam.getLocation());
153         
154         // if escape was pressed, we exit
155         if (KeyBindingManager.getKeyBindingManager().isValidCommand("exit")) {
156             finished = true;
157         }
158         
159         //We don't want the chase camera to go below the world, so always keep 
160         //it 2 units above the level.
161         if(cam.getLocation().y < (tb.getHeight(cam.getLocation())+2)) {
162             cam.getLocation().y = tb.getHeight(cam.getLocation()) + 2;
163             cam.update();
164         }
165         
166         //make sure that if the player left the level we don't crash. When we add collisions,
167         //the fence will do its job and keep the player inside.
168         float characterMinHeight = tb.getHeight(player
169                 .getLocalTranslation())+agl;
170         if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
171             player.getLocalTranslation().y = characterMinHeight;
172         }
173         
174         //get the normal of the terrain at our current location. We then apply it to the up vector
175         //of the player.
176         tb.getSurfaceNormal(player.getLocalTranslation(), normal);
177         if(normal != null) {
178             player.rotateUpTo(normal);
179         }
180         
181         //Because we are changing the scene (moving the skybox and player) we need to update
182         //the graph.
183         scene.updateGeometricState(interpolation, true);
184     }
185
186     /**
187      * draws the scene graph
188      * 
189      * @see com.jme.app.BaseGame#render(float)
190      */
191     protected void render(float interpolation) {
192         // Clear the screen
193         display.getRenderer().clearBuffers();
194         display.getRenderer().draw(scene);
195     }
196
197     /**
198      * initializes the display and camera.
199      * 
200      * @see com.jme.app.BaseGame#initSystem()
201      */
202     protected void initSystem() {
203         // store the settings information
204         width = settings.getWidth();
205         height = settings.getHeight();
206         depth = settings.getDepth();
207         freq = settings.getFrequency();
208         fullscreen = settings.isFullscreen();
209         
210         try {
211             display = DisplaySystem.getDisplaySystem(settings.getRenderer());
212             display.createWindow(width, height, depth, freq, fullscreen);
213
214             cam = display.getRenderer().createCamera(width, height);
215         } catch (JmeException e) {
216             logger.log(Level.SEVERE, "Could not create displaySystem", e);
217             System.exit(1);
218         }
219
220         // set the background to black
221         display.getRenderer().setBackgroundColor(ColorRGBA.black.clone());
222
223         // initialize the camera
224         cam.setFrustumPerspective(45.0f, (float) width / (float) height, 1,
225                 5000);
226         cam.setLocation(new Vector3f(200,1000,200));
227         
228         /** Signal that we've changed our camera's location/frustum. */
229         cam.update();
230
231         /** Get a high resolution timer for FPS updates. */
232         timer = Timer.getTimer();
233
234         display.getRenderer().setCamera(cam);
235
236         KeyBindingManager.getKeyBindingManager().set("exit",
237                 KeyInput.KEY_ESCAPE);
238     }
239
240     /**
241      * initializes the scene
242      * 
243      * @see com.jme.app.BaseGame#initGame()
244      */
245     protected void initGame() {
246         display.setTitle("Flag Rush");
247         
248         scene = new Node("Scene graph node");
249         /** Create a ZBuffer to display pixels closest to the camera above farther ones.  */
250         ZBufferState buf = display.getRenderer().createZBufferState();
251         buf.setEnabled(true);
252         buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
253         scene.setRenderState(buf);
254         
255         //Time for a little optimization. We don't need to render back face triangles, so lets
256         //not. This will give us a performance boost for very little effort.
257         CullState cs = display.getRenderer().createCullState();
258         cs.setCullFace(CullState.Face.Back);
259         scene.setRenderState(cs);
260         
261         //Add terrain to the scene
262         buildTerrain();
263         //Add a flag randomly to the terrain
264         buildFlag();
265         //Light the world
266         buildLighting();
267         //add the force field fence
268         buildEnvironment();
269         //Add the skybox
270         buildSkyBox();
271         //Build the player
272         buildPlayer();
273         //build the chase cam
274         buildChaseCamera();
275         //build the player input
276         buildInput();
277         
278         // update the scene graph for rendering
279         scene.updateGeometricState(0.0f, true);
280         scene.updateRenderState();
281     }
282
283     /**
284      * we created a new Flag class, so we'll use it to add the flag to the world.
285      * This is the flag that we desire, the one to get.
286      *
287      */
288     private void buildFlag() {
289         //create the flag and place it
290         flag = new Flag(tb);
291         scene.attachChild(flag);
292         flag.placeFlag();
293     }
294     
295     /**
296      * we are going to build the player object here. Now, we will load a .3ds model and convert it
297      * to .jme in realtime. The next lesson will show how to store as .jme so this conversion doesn't
298      * have to take place every time. 
299      * 
300      * We now have a Vehicle object that represents our player. The vehicle object will allow
301      * us to have multiple vehicle types with different capabilities.
302      *
303      */
304     private void buildPlayer() {
305         Spatial model = null;
306         try {
307             //This should be updated to the latest BinaryImporter code once the model
308             //is converted.
309             URL bikeFile = Lesson8.class.getClassLoader().getResource("jmetest/data/model/bike.jme");
310             BinaryImporter importer = new BinaryImporter();
311             model = (Spatial)importer.load(bikeFile.openStream());
312             model.setModelBound(new BoundingBox());
313             model.updateModelBound();
314             //scale it to be MUCH smaller than it is originally
315             model.setLocalScale(.0025f);
316         } catch (IOException e) {
317             logger
318                     .throwing(this.getClass().toString(), "buildPlayer()",
319                             e);
320         }
321         
322         //set the vehicles attributes (these numbers can be thought
323         //of as Unit/Second).
324         player = new Vehicle("Player Node", model);
325         player.setAcceleration(15);
326         player.setBraking(15);
327         player.setTurnSpeed(2.5f);
328         player.setWeight(25);
329         player.setMaxSpeed(25);
330         player.setMinSpeed(15);
331         
332         player.setLocalTranslation(new Vector3f(100,0, 100));
333         scene.attachChild(player);
334         scene.updateGeometricState(0, true);
335         //we now store this initial value, because we are rotating the wheels the bounding box will
336         //change each frame.
337         agl = ((BoundingBox)model.getWorldBound()).yExtent;
338         player.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
339     }
340     
341     /**
342      * buildEnvironment will create a fence. 
343      */
344     private void buildEnvironment() {
345         //This is the main node of our fence
346         fence = new ForceFieldFence("fence");
347         
348         //we will do a little 'tweaking' by hand to make it fit in the terrain a bit better.
349         //first we'll scale the entire "model" by a factor of 5
350         fence.setLocalScale(5);
351         //now let's move the fence to to the height of the terrain and in a little bit.
352         fence.setLocalTranslation(new Vector3f(25, tb.getHeight(25,25)+10, 25));
353         
354         scene.attachChild(fence);
355     }
356
357     /**
358      * creates a light for the terrain.
359      */
360     private void buildLighting() {
361         /** Set up a basic, default light. */
362         DirectionalLight light = new DirectionalLight();
363         light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
364         light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
365         light.setDirection(new Vector3f(1,-1,0));
366         light.setEnabled(true);
367
368           /** Attach the light to a lightState and the lightState to rootNode. */
369         LightState lightState = display.getRenderer().createLightState();
370         lightState.setEnabled(true);
371         lightState.attach(light);
372         scene.setRenderState(lightState);
373     }
374
375     /**
376      * build the height map and terrain block.
377      */
378     private void buildTerrain() {
379         
380         
381         MidPointHeightMap heightMap = new MidPointHeightMap(64, 1f);
382         // Scale the data
383         Vector3f terrainScale = new Vector3f(4, 0.0575f, 4);
384         // create a terrainblock
385          tb = new TerrainBlock("Terrain", heightMap.getSize(), terrainScale,
386                 heightMap.getHeightMap(), new Vector3f(0, 0, 0));
387
388         tb.setModelBound(new BoundingBox());
389         tb.updateModelBound();
390
391         // generate a terrain texture with 2 textures
392         ProceduralTextureGenerator pt = new ProceduralTextureGenerator(
393                 heightMap);
394         pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
395                 .getResource("jmetest/data/texture/grassb.png")), -128, 0, 128);
396         pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
397                 .getResource("jmetest/data/texture/dirt.jpg")), 0, 128, 255);
398         pt.addTexture(new ImageIcon(TestTerrain.class.getClassLoader()
399                 .getResource("jmetest/data/texture/highest.jpg")), 128, 255,
400                 384);
401         pt.createTexture(32);
402         
403         // assign the texture to the terrain
404         TextureState ts = display.getRenderer().createTextureState();
405         Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
406                 Texture.MinificationFilter.Trilinear, Texture.MagnificationFilter.Bilinear, true);
407         ts.setTexture(t1, 0);
408         
409         //load a detail texture and set the combine modes for the two terrain textures.
410         Texture t2 = TextureManager.loadTexture(
411                 TestTerrain.class.getClassLoader().getResource(
412                 "jmetest/data/texture/Detail.jpg"),
413                 Texture.MinificationFilter.Trilinear,
414                 Texture.MagnificationFilter.Bilinear);
415
416         ts.setTexture(t2, 1);
417         t2.setWrap(Texture.WrapMode.Repeat);
418
419         t1.setApply(Texture.ApplyMode.Combine);
420         t1.setCombineFuncRGB(Texture.CombinerFunctionRGB.Modulate);
421         t1.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
422         t1.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
423         t1.setCombineSrc1RGB(Texture.CombinerSource.PrimaryColor);
424         t1.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
425
426         t2.setApply(Texture.ApplyMode.Combine);
427         t2.setCombineFuncRGB(Texture.CombinerFunctionRGB.AddSigned);
428         t2.setCombineSrc0RGB(Texture.CombinerSource.CurrentTexture);
429         t2.setCombineOp0RGB(Texture.CombinerOperandRGB.SourceColor);
430         t2.setCombineSrc1RGB(Texture.CombinerSource.Previous);
431         t2.setCombineOp1RGB(Texture.CombinerOperandRGB.SourceColor);
432
433         tb.setRenderState(ts);
434         //set the detail parameters.
435         tb.setDetailTexture(1, 16);
436         tb.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
437         scene.attachChild(tb);
438         
439         
440     }
441     
442     /**
443      * buildSkyBox creates a new skybox object with all the proper textures. The
444      * textures used are the standard skybox textures from all the tests.
445      *
446      */
447     private void buildSkyBox() {
448         skybox = new Skybox("skybox", 10, 10, 10);
449
450         Texture north = TextureManager.loadTexture(
451             TestSkybox.class.getClassLoader().getResource(
452             "jmetest/data/texture/north.jpg"),
453             Texture.MinificationFilter.BilinearNearestMipMap,
454             Texture.MagnificationFilter.Bilinear);
455         Texture south = TextureManager.loadTexture(
456             TestSkybox.class.getClassLoader().getResource(
457             "jmetest/data/texture/south.jpg"),
458             Texture.MinificationFilter.BilinearNearestMipMap,
459             Texture.MagnificationFilter.Bilinear);
460         Texture east = TextureManager.loadTexture(
461             TestSkybox.class.getClassLoader().getResource(
462             "jmetest/data/texture/east.jpg"),
463             Texture.MinificationFilter.BilinearNearestMipMap,
464             Texture.MagnificationFilter.Bilinear);
465         Texture west = TextureManager.loadTexture(
466             TestSkybox.class.getClassLoader().getResource(
467             "jmetest/data/texture/west.jpg"),
468             Texture.MinificationFilter.BilinearNearestMipMap,
469             Texture.MagnificationFilter.Bilinear);
470         Texture up = TextureManager.loadTexture(
471             TestSkybox.class.getClassLoader().getResource(
472             "jmetest/data/texture/top.jpg"),
473             Texture.MinificationFilter.BilinearNearestMipMap,
474             Texture.MagnificationFilter.Bilinear);
475         Texture down = TextureManager.loadTexture(
476             TestSkybox.class.getClassLoader().getResource(
477             "jmetest/data/texture/bottom.jpg"),
478             Texture.MinificationFilter.BilinearNearestMipMap,
479             Texture.MagnificationFilter.Bilinear);
480
481         skybox.setTexture(Skybox.Face.North, north);
482         skybox.setTexture(Skybox.Face.West, west);
483         skybox.setTexture(Skybox.Face.South, south);
484         skybox.setTexture(Skybox.Face.East, east);
485         skybox.setTexture(Skybox.Face.Up, up);
486         skybox.setTexture(Skybox.Face.Down, down);
487         skybox.preloadTextures();
488         scene.attachChild(skybox);
489     }
490     
491     /**
492      * set the basic parameters of the chase camera. This includes the offset. We want
493      * to be behind the vehicle and a little above it. So we will the offset as 0 for
494      * x and z, but be 1.5 times higher than the node.
495      * 
496      * We then set the roll out parameters (2 units is the closest the camera can get, and
497      * 5 is the furthest).
498      *
499      */
500     private void buildChaseCamera() {
501         HashMap<String, Object> props = new HashMap<String, Object>();
502         props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "6");
503         props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "3");
504         props.put(ThirdPersonMouseLook.PROP_MAXASCENT, ""+45 * FastMath.DEG_TO_RAD);
505         props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(5, 0, 30 * FastMath.DEG_TO_RAD));
506         props.put(ChaseCamera.PROP_DAMPINGK, "4");
507         props.put(ChaseCamera.PROP_SPRINGK, "9");
508         chaser = new ChaseCamera(cam, player, props);
509         chaser.setMaxDistance(8);
510         chaser.setMinDistance(2);
511     }
512
513     /**
514      * create our custom input handler.
515      *
516      */
517     private void buildInput() {
518         input = new FlagRushHandler(player, settings.getRenderer());
519     }
520     
521
522
523     /**
524      * will be called if the resolution changes
525      * 
526      * @see com.jme.app.BaseGame#reinit()
527      */
528     protected void reinit() {
529         display.recreateWindow(width, height, depth, freq, fullscreen);
530     }
531     
532     /**
533      * close the window and also exit the program.
534      */
535     protected void quit() {
536         super.quit();
537         System.exit(0);
538     }
539
540     /**
541      * clean up the textures.
542      * 
543      * @see com.jme.app.BaseGame#cleanup()
544      */
545     protected void cleanup() {
546
547     }
548 }