OSDN Git Service

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