Aug 31, 2014

40. Collisions

Collisions of 3D objects with an invisible line, whose origin and direction we can set, can be tested.




In simpleInitApp() we call makeBoxes() which sets up our scene.


        // *** 1. Start (Call function to create 64 boxes)
        makeBoxes(4,4,4);
        // *** 1. End



The Default Text is used to write status text such as number of boxes remaining.


        // *** 2. Start (Text)
        guiNode.detachAllChildren();
        BitmapFont font = assetManager.loadFont(
                "Interface/Fonts/Default.fnt");
        statusText = new BitmapText(font, false);
        statusText.setSize(font.getCharSet().getRenderedSize());
        statusText.setLocalTranslation(20, 40+statusText.getLineHeight(), 0);
        statusText.scale(3);
        statusText.setColor(ColorRGBA.Green);
        guiNode.attachChild(statusText);
        // *** 2. End



The invisible ray goes from camera location, in the direction of camera, and which is also the view direction. If any boxes are in sight and they are close enough, they are removed.


        // *** 3. Start (Collisions)
        CollisionResults results = new CollisionResults(); // empty results
        Ray ray = new Ray(cam.getLocation(), cam.getDirection()); // camera
        boxesNode.collideWith(ray, results); // get results
        if (results.size() > 0) {
          Geometry closest = results.
                  getClosestCollision().getGeometry(); // nearest box
          float dist = results.getClosestCollision().
                  getDistance(); // distance to nearest box
          String nameClosest = closest.getName(); // name of nearest box
          System.out.println("The closest box has the name " +
                  nameClosest + " and it is " + dist + " units away.");
          if (dist<10f) { // close enough will disappear
              System.out.println("---->And I am gone");
              boxesNode.detachChild(closest);
          }
          int num = boxesNode.getQuantity(); // boxes left
          if (num==0) { // no more boxes
              System.out.println("You win!!!");
              statusText.setText("You win!!!");
          }
          else { // boxes left is nonzero
              System.out.println("There are " + num + " boxes left");
              statusText.setText("There are " + num + " boxes left");
          }
        }
        // *** 3. End



In the makeBoxes, we create a number of adjoining boxes depending on the parameters.


        // *** 4. Start (create M*N*P boxes)
        int i,j,k;
        boxesNode = new Node("boxes");
        Material mat = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat.setBoolean("UseMaterialColors",true);
        mat.setColor("Diffuse",ColorRGBA.Blue);
        mat.setColor("Specular",ColorRGBA.White);
        for (i=0; i<M;i++) {
            for (j = 0; j<N; j++ ) {
                for (k = 0; k<P; k++) {
                    Box b = new Box(1f,1f,1f);
                    Geometry gb = new Geometry("b"+i+","+j+","+k,b);
                    gb.setMaterial(mat);
                    gb.setLocalTranslation(2*i,2*j,2*k-5);
                    boxesNode.attachChild(gb);
                }
            }
        }
        // *** 4. End



// JMonkey40.java

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;

public class JMonkey40 extends SimpleApplication {
    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setFrameRate(60);
        settings.setTitle("Remove the Boxes !");
        JMonkey40 app = new JMonkey40();
        app.setSettings(settings);
        app.start();
    }
    
    private Node boxesNode;
    private BitmapText statusText;

    @Override
    public void simpleInitApp() {
        viewPort.setBackgroundColor(ColorRGBA.Gray);
        setDisplayFps(false);
        setDisplayStatView(false);
        
        // *** 1. Start (Call function to create 64 boxes)
        makeBoxes(4,4,4);
        // *** 1. End
        
        // *** 2. Start (Text)
        guiNode.detachAllChildren();
        BitmapFont font = assetManager.loadFont(
                "Interface/Fonts/Default.fnt");
        statusText = new BitmapText(font, false);
        statusText.setSize(font.getCharSet().getRenderedSize());
        statusText.setLocalTranslation(20, 40+statusText.getLineHeight(), 0);
        statusText.scale(3);
        statusText.setColor(ColorRGBA.Green);
        guiNode.attachChild(statusText);
        // *** 2. End
    }

    @Override
    public void simpleUpdate(float tpf) {
        // *** 3. Start (Collisions)
        CollisionResults results = new CollisionResults(); // empty results
        Ray ray = new Ray(cam.getLocation(), cam.getDirection()); // camera
        boxesNode.collideWith(ray, results); // get results
        if (results.size() > 0) {
          Geometry closest = results.
                  getClosestCollision().getGeometry(); // nearest box
          float dist = results.getClosestCollision().
                  getDistance(); // distance to nearest box
          String nameClosest = closest.getName(); // name of nearest box
          System.out.println("The closest box has the name " +
                  nameClosest + " and it is " + dist + " units away.");
          if (dist<10f) { // close enough will disappear
              System.out.println("---->And I am gone");
              boxesNode.detachChild(closest);
          }
          int num = boxesNode.getQuantity(); // boxes left
          if (num==0) { // no more boxes
              System.out.println("You win!!!");
              statusText.setText("You win!!!");
          }
          else { // boxes left is nonzero
              System.out.println("There are " + num + " boxes left");
              statusText.setText("There are " + num + " boxes left");
          }
        }
        // *** 3. End
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
    
    private void makeBoxes(int M, int N, int P) {
        // *** 4. Start (create M*N*P boxes)
        int i,j,k;
        boxesNode = new Node("boxes");
        Material mat = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat.setBoolean("UseMaterialColors",true);
        mat.setColor("Diffuse",ColorRGBA.Blue);
        mat.setColor("Specular",ColorRGBA.White);
        for (i=0; i<M;i++) {
            for (j = 0; j<N; j++ ) {
                for (k = 0; k<P; k++) {
                    Box b = new Box(1f,1f,1f);
                    Geometry gb = new Geometry("b"+i+","+j+","+k,b);
                    gb.setMaterial(mat);
                    gb.setLocalTranslation(2*i,2*j,2*k-5);
                    boxesNode.attachChild(gb);
                }
            }
        }
        // *** 4. End
        
        // sun1 
        DirectionalLight sun1 = new DirectionalLight();
        sun1.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
        sun1.setColor(ColorRGBA.White);
        rootNode.addLight(sun1);
        // sun2 
        DirectionalLight sun2 = new DirectionalLight();
        sun2.setDirection((new Vector3f(0.5f, 0.5f, 0.5f)).normalizeLocal());
        sun2.setColor(ColorRGBA.Pink);
        rootNode.addLight(sun2);
        
        rootNode.attachChild(boxesNode);
    }
}


Output:


39. Quad

The Quad is a 2D Mesh.




The Mesh and Geometry are created.


        // *** 1. Start (Quad Mesh + Geometry)
        Quad quad = new Quad(4,6); // 4 by 6
        geoQuad = new Geometry("quad", quad);
        geoQuad.setLocalTranslation(0, -3, 0);
  // *** 1. End



Some mesh information is printed.


        // *** 2. Start (Vertices and Texture coordinates)
        VertexBuffer position = quad.
                getBuffer(VertexBuffer.Type.Position);
        Vector3f[] arrayVer = BufferUtils.
                getVector3Array((FloatBuffer)position.
                getData());
        VertexBuffer texCoord = quad.
                getBuffer(VertexBuffer.Type.TexCoord);
        Vector2f[] arrayTex = BufferUtils.
                getVector2Array((FloatBuffer)texCoord.
                getData());
        System.out.println("quad has " +
                arrayVer.length +
                " vertices. Coordinates for " +
                " Vertices (x,y,z) and Texture (u,v) are: ");
        for (int i = 0; i<arrayVer.length; i++) {
            System.out.printf("%d\t%s\t%s\n",i,
                    arrayVer[i],arrayTex[i]);
        }
        // *** 2. End



The material shows the back shadow and we also use the transparency to blend with the background color.


        // *** 3. Start (Material)
        Material mat = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat.getAdditionalRenderState().
                setFaceCullMode(FaceCullMode.Off);
        Texture quadTex = assetManager.loadTexture(
                "Interface/monkey/Jmonkeyengine-logo.png");
        mat.setTexture("DiffuseMap", quadTex);
        mat.getAdditionalRenderState().
                setBlendMode(BlendMode.Alpha);
        geoQuad.setMaterial(mat);
        // *** 3. End



// JMonkey39.java

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;

public class JMonkey39 extends SimpleApplication {
    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setFrameRate(60);
        settings.setTitle("Monkey on a Quad !");
        JMonkey39 app = new JMonkey39();
        app.setSettings(settings);
        app.start();
    }
    
    private Geometry geoQuad;

    @Override
    public void simpleInitApp() {
        viewPort.setBackgroundColor(
                new ColorRGBA(1f,.9f,.8f,1));
        setDisplayFps(false);
        setDisplayStatView(false);
        flyCam.setEnabled(false);
        
        // *** 1. Start (Quad Mesh + Geometry)
        Quad quad = new Quad(4,6); // 4 by 6
        geoQuad = new Geometry("quad", quad);
        geoQuad.setLocalTranslation(0, -3, 0);
  // *** 1. End
        
        // *** 2. Start (Vertices and Texture coordinates)
        VertexBuffer position = quad.
                getBuffer(VertexBuffer.Type.Position);
        Vector3f[] arrayVer = BufferUtils.
                getVector3Array((FloatBuffer)position.
                getData());
        VertexBuffer texCoord = quad.
                getBuffer(VertexBuffer.Type.TexCoord);
        Vector2f[] arrayTex = BufferUtils.
                getVector2Array((FloatBuffer)texCoord.
                getData());
        System.out.println("quad has " +
                arrayVer.length +
                " vertices. Coordinates for " +
                " Vertices (x,y,z) and Texture (u,v) are: ");
        for (int i = 0; i<arrayVer.length; i++) {
            System.out.printf("%d\t%s\t%s\n",i,
                    arrayVer[i],arrayTex[i]);
        }
        // *** 2. End
        // *** 3. Start (Material)
        Material mat = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat.getAdditionalRenderState().
                setFaceCullMode(FaceCullMode.Off);
        Texture quadTex = assetManager.loadTexture(
                "Interface/monkey/Jmonkeyengine-logo.png");
        mat.setTexture("DiffuseMap", quadTex);
        mat.getAdditionalRenderState().
                setBlendMode(BlendMode.Alpha);
        geoQuad.setMaterial(mat);
        // *** 3. End
        
        /** A white, directional light source */ 
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);  
        /** A cone-shaped spotlight with location, direction, range */
        SpotLight spot = new SpotLight();
        spot.setSpotRange(100);
        spot.setSpotOuterAngle(20 * FastMath.DEG_TO_RAD); 
        spot.setSpotInnerAngle(15 * FastMath.DEG_TO_RAD); 
        spot.setDirection(cam.getDirection()); 
        spot.setPosition(cam.getLocation());
        rootNode.addLight(spot);
        
        rootNode.attachChild(geoQuad);
    }

    @Override
    public void simpleUpdate(float tpf) {
        geoQuad.rotate(0,FastMath.HALF_PI*tpf,0);
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
}


Output:


38. Cylinder

A Cylinder is one of the built-in mesh shapes.




We can find number of vertices and their coordinates.


        // *** 2. Start (Vertices)
        VertexBuffer position = cylinder.
                getBuffer(Type.Position);
        Vector3f[] array = BufferUtils.
                getVector3Array((FloatBuffer)position.
                getData());
        System.out.println("cylinder has " +
                array.length +
                " vertices. Coordinates are: ");
        int i = 0;
        for (Vector3f v : array) {
            System.out.printf("%d\t%s\n",i++,v);
        }
        // *** 2. End



This materials tests if the mesh structure is correctly loaded.


        // *** 3. Start (Material)
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/ShowNormals.j3md");
        geoCylinder.setMaterial(mat);
        // *** 3. End



We have constant rotations to see the mesh from different angles.



        // *** 4. Start (Rotation per frame)
        rootNode.rotate(.5f*tpf, .1f*tpf, .2f*tpf);
        // *** 4. End



// JMonkey38.java

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.shape.Cylinder;
import com.jme3.system.AppSettings;
import com.jme3.util.BufferUtils;
import java.nio.FloatBuffer;

public class JMonkey38 extends SimpleApplication {
    public static void main(String[] args) {
        AppSettings settings = new AppSettings(true);
        settings.setFrameRate(57);
        settings.setTitle("Cylinder !");
        JMonkey38 app = new JMonkey38();
        app.setSettings(settings);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        viewPort.setBackgroundColor(
                new ColorRGBA(1f,.9f,.8f,1));
        setDisplayFps(false);
        setDisplayStatView(false);
        flyCam.setEnabled(false);
        // *** 1. Start (cylinder mesh)
        Cylinder cylinder = new Cylinder(8,8,1.5f,2.5f,true);
        Geometry geoCylinder = new Geometry("cylinder", cylinder);
        
        // *** 2. Start (Vertices)
        VertexBuffer position = cylinder.
                getBuffer(Type.Position);
        Vector3f[] array = BufferUtils.
                getVector3Array((FloatBuffer)position.
                getData());
        System.out.println("cylinder has " +
                array.length +
                " vertices. Coordinates are: ");
        int i = 0;
        for (Vector3f v : array) {
            System.out.printf("%d\t%s\n",i++,v);
        }
        // *** 2. End
        // *** 3. Start (Material)
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/ShowNormals.j3md");
        geoCylinder.setMaterial(mat);
        // *** 3. End

        rootNode.attachChild(geoCylinder);
    }

    @Override
    public void simpleUpdate(float tpf) {
        // *** 4. Start (Rotation per frame)
        rootNode.rotate(.5f*tpf, .1f*tpf, .2f*tpf);
        // *** 4. End
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
}


Output:


Aug 30, 2014

37. Sphere + Dome

Sphere and Dome are two built-in mesh shapes.




One Sphere and two Dome objects are created with constructors setting number of mesh divisions and radius. The Geometry has to be created.


        // *** 1. Start (Sphere, Dome1, Dome2)
        Sphere sphere = new Sphere(16,16,1);
        Geometry geoSphere = new Geometry("sphere", sphere);
        Dome dome1 = new Dome(new Vector3f(2,0,0),16,16,1);
        Geometry geoDome1 = new Geometry("dome1", dome1);
        Dome dome2 = new Dome(new Vector3f(-2,0,0),16,16,1);
        Geometry geoDome2 = new Geometry("dome2", dome2);
        geoDome2.rotate(FastMath.PI,0,0);
        // *** 1. End



We print the number of vertex points in each structure.


        // *** 2. Start (Number of Vertex Points)
        System.out.println("The sphere has " + sphere.getVertexCount() +
                " vertices.");
        System.out.println("The dome1 has " + dome1.getVertexCount() +
                " vertices.");
        System.out.println("The dome2 has " + dome2.getVertexCount() +
                " vertices.");
        // *** 2. End



The three Geometries are set as blue, red or green.


        // *** 3. Start (Materials - Blue, Red, Green)
        Material mat = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat.setBoolean("UseMaterialColors",true);
        mat.setColor("Diffuse",ColorRGBA.Blue);
        mat.setColor("Specular",ColorRGBA.White);
        geoSphere.setMaterial(mat);
        Material mat1 = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat1.setBoolean("UseMaterialColors",true);
        mat1.setColor("Diffuse",ColorRGBA.Red);
        mat1.setColor("Specular",ColorRGBA.White);
        geoDome1.setMaterial(mat1);
        Material mat2 = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat2.setBoolean("UseMaterialColors",true);
        mat2.setColor("Diffuse",ColorRGBA.Green);
        mat2.setColor("Specular",ColorRGBA.White);
        geoDome2.setMaterial(mat2);
        // *** 3. End



// JMonkey37.java

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Dome;
import com.jme3.scene.shape.Sphere;

public class JMonkey37 extends SimpleApplication {

    public static void main(String[] args) {
        JMonkey37 app = new JMonkey37();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        flyCam.setEnabled(false);
        viewPort.setBackgroundColor(ColorRGBA.White);
        setDisplayFps(false);
        setDisplayStatView(false);
        // *** 1. Start (Sphere, Dome1, Dome2)
        Sphere sphere = new Sphere(16,16,1);
        Geometry geoSphere = new Geometry("sphere", sphere);
        Dome dome1 = new Dome(new Vector3f(2,0,0),16,16,1);
        Geometry geoDome1 = new Geometry("dome1", dome1);
        Dome dome2 = new Dome(new Vector3f(-2,0,0),16,16,1);
        Geometry geoDome2 = new Geometry("dome2", dome2);
        geoDome2.rotate(FastMath.PI,0,0);
        // *** 1. End
        // *** 2. Start (Number of Vertex Points)
        System.out.println("The sphere has " + sphere.getVertexCount() +
                " vertices.");
        System.out.println("The dome1 has " + dome1.getVertexCount() +
                " vertices.");
        System.out.println("The dome2 has " + dome2.getVertexCount() +
                " vertices.");
        // *** 2. End

        // *** 3. Start (Materials - Blue, Red, Green)
        Material mat = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat.setBoolean("UseMaterialColors",true);
        mat.setColor("Diffuse",ColorRGBA.Blue);
        mat.setColor("Specular",ColorRGBA.White);
        geoSphere.setMaterial(mat);
        Material mat1 = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat1.setBoolean("UseMaterialColors",true);
        mat1.setColor("Diffuse",ColorRGBA.Red);
        mat1.setColor("Specular",ColorRGBA.White);
        geoDome1.setMaterial(mat1);
        Material mat2 = new Material(assetManager, 
                "Common/MatDefs/Light/Lighting.j3md");
        mat2.setBoolean("UseMaterialColors",true);
        mat2.setColor("Diffuse",ColorRGBA.Green);
        mat2.setColor("Specular",ColorRGBA.White);
        geoDome2.setMaterial(mat2);
        // *** 3. End
     
        /** A cone-shaped spotlight with location, direction, range */
        SpotLight spot = new SpotLight(); 
        spot = new SpotLight(); 
        spot.setSpotRange(100); 
        spot.setSpotOuterAngle(20 * FastMath.DEG_TO_RAD); 
        spot.setSpotInnerAngle(15 * FastMath.DEG_TO_RAD); 
        spot.setDirection(cam.getDirection()); 
        spot.setPosition(cam.getLocation()); 
        rootNode.addLight(spot); 

        rootNode.attachChild(geoSphere);
        rootNode.attachChild(geoDome1);
        rootNode.attachChild(geoDome2);
    }

    @Override
    public void simpleUpdate(float tpf) {
        rootNode.rotate(2*tpf,tpf,2*tpf);
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
}


Output:


Aug 29, 2014

36. Line

Line is one of the built-in mesh shapes.




Line requires the starting point and ending point, both being Vector3f objects. We can also set Line properties such as the line width.


        // *** 1. Start (Line objects)
        Line line = new Line(Vector3f.ZERO,new Vector3f(1,1,0));
        line.setLineWidth(5);
        Line line1 = new Line(Vector3f.ZERO,new Vector3f(1,-1f,0));
        line1.setLineWidth(5);
        Line line2 = new Line(Vector3f.ZERO,new Vector3f(1,0,3));
        line2.setLineWidth(3);
        // *** 1. End



Three Geometries are created from the line shapes. We have to use a String identifier and mesh object.


        // *** 2. Start (Geometry objects)
        Geometry gline = new Geometry("upLine",line);
        Geometry gline1 = new Geometry("downLine",line1);
        Geometry gline2 = new Geometry("horizLine",line2);
        // *** 2. End



The three lines are set to different colors.


        // *** 3. Start (3 Materials, Different Colors)
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        Material mat1 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Red);
        Material mat2 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Green);
        gline.setMaterial(mat);
        gline1.setMaterial(mat1);
        gline2.setMaterial(mat2);
        // *** 3. End



We clone the last 3 Geometries, and Scale and Translate them. If you did not scale them, it would be hard to see them.


        // *** 4. Start (clones with scale and translations)
        Geometry gline3 = new Geometry("line3");
        Geometry gline4 = new Geometry("line4");
        Geometry gline5 = new Geometry("line5");
        gline3 = gline.clone();
        gline3.setLocalScale(20);
        gline3.setLocalTranslation(x, y, 0);
        gline4 = gline1.clone();
        gline4.setLocalScale(20);
        gline4.setLocalTranslation(x, y, 0);
        gline5 = gline2.clone();
        gline5.setLocalScale(20);
        gline5.setLocalTranslation(x, y, 0);
        // *** 4. End



package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Line;

public class JMonkey36 extends SimpleApplication {
    public static void main(String[] args) {
        JMonkey36 app = new JMonkey36();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        setDisplayFps(false);
        setDisplayStatView(false);
        viewPort.setBackgroundColor(ColorRGBA.White);
        // *** 1. Start (Line objects)
        Line line = new Line(Vector3f.ZERO,new Vector3f(1,1,0));
        line.setLineWidth(5);
        Line line1 = new Line(Vector3f.ZERO,new Vector3f(1,-1f,0));
        line1.setLineWidth(5);
        Line line2 = new Line(Vector3f.ZERO,new Vector3f(1,0,3));
        line2.setLineWidth(3);
        // *** 1. End
        // *** 2. Start (Geometry objects)
        Geometry gline = new Geometry("upLine",line);
        Geometry gline1 = new Geometry("downLine",line1);
        Geometry gline2 = new Geometry("horizLine",line2);
        // *** 2. End
        // *** 3. Start (3 Materials, Different Colors)
        Material mat = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", ColorRGBA.Blue);
        Material mat1 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Red);
        Material mat2 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Green);
        gline.setMaterial(mat);
        gline1.setMaterial(mat1);
        gline2.setMaterial(mat2);
        // *** 3. End
        int x = settings.getWidth()/4; // 2dgui traslated
        int y = settings.getHeight()/4; // to (x,y)
        
        // *** 4. Start (clones with scale and translations)
        Geometry gline3 = new Geometry("line3");
        Geometry gline4 = new Geometry("line4");
        Geometry gline5 = new Geometry("line5");
        gline3 = gline.clone();
        gline3.setLocalScale(20);
        gline3.setLocalTranslation(x, y, 0);
        gline4 = gline1.clone();
        gline4.setLocalScale(20);
        gline4.setLocalTranslation(x, y, 0);
        gline5 = gline2.clone();
        gline5.setLocalScale(20);
        gline5.setLocalTranslation(x, y, 0);
        // *** 4. End
        
        rootNode.attachChild(gline);
        rootNode.attachChild(gline1);
        rootNode.attachChild(gline2);
        
        guiNode.attachChild(gline3);
        guiNode.attachChild(gline4);
        guiNode.attachChild(gline5);
    }

    @Override
    public void simpleUpdate(float tpf) {
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }
}



35. Images

Besides using images for textures, fonts, etc., they can be used as part of 2D GUI.




By searching for jMonkeyEngine, we can get this Wikipedia webpage with the logo at right. Right-click to save the file.




This is the file downloaded. In the project, create a subfolder in Interface folder, such as jmonkey_logo. The file should be saved there.




In the simpleInitApp(), create a Picture object, such as logo. You next have to point it to the Interface image file using the assetManager. The width and height should be set as well as the position it should be moved to. Finally, it should be attached to the guiNode.


        // *** 1. Start (image)
        logo = new Picture("The Monkey Engine");
        logo.setImage(assetManager, 
                "Interface/jmonkey_logo/Jmonkeyengine-logo.png",
                true); // true - use alpha
        x = settings.getWidth()/2-75;
        y = settings.getHeight()/2-60;
        logo.move(x,y,0);
        logo.setWidth(150); // image width
        logo.setHeight(120); // image height
        guiNode.attachChild(logo);
        // *** 1. End



package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.math.ColorRGBA;
import com.jme3.system.AppSettings;
import com.jme3.ui.Picture;

public class JMonkey35 extends SimpleApplication {

    public static void main(String[] args) {
        AppSettings cfg = new AppSettings(true);
        cfg.setFrameRate(60);
        JMonkey35 app = new JMonkey35();
        app.setSettings(cfg);
        app.start();
    }
    
    private Picture logo;
    private int x,y;

    @Override
    public void simpleInitApp() {
        flyCam.setEnabled(false);
        viewPort.setBackgroundColor(ColorRGBA.White);
        setDisplayStatView(false);
        setDisplayFps(false);
        // *** 1. Start (image)
        logo = new Picture("The Monkey Engine");
        logo.setImage(assetManager, 
                "Interface/jmonkey_logo/Jmonkeyengine-logo.png",
                true); // true - use alpha
        x = settings.getWidth()/2-75;
        y = settings.getHeight()/2-60;
        logo.move(x,y,0);
        logo.setWidth(150); // image width
        logo.setHeight(120); // image height
        guiNode.attachChild(logo);
        // *** 1. End
    }
    
    @Override
    public void simpleUpdate(float tps) {
        System.out.println("rate = " + 1/tps);
    }
}



Aug 28, 2014

34. Font

Bitmap Fonts can be created using a Plugin in the jMonkeyEngine SDK, and we can use them as 2D objects.




We can check plugins from the Tools menu. If the Font Creator plugin is not already installed, install it from the Available Plugins tab, and restart the SDK.




In a project, right-click on the Interface folder in the Assets, and select new Font file. Then, any system font can be installed. You can also get free fonts from some websites.




In the next step, you have to set some settings such as image size, font size and padding.




After these steps, two files be in a Fonts folder.




To use the fonts, first load the file into BitmapFont object. Then you can create BitmapText objects and attach them to guiNode.


        // *** 1. Start (Load font, initial text)
        BitmapFont myFont = assetManager.loadFont(
                "Interface/Fonts/TrebuchetMS.fnt");
        outText = new BitmapText(myFont, false);
        outText.setSize(guiFont.getCharSet().getRenderedSize());
        outText.move(settings.getWidth()/5,      // X
                        settings.getHeight()/2,  // Y
                        0);                      // Z
        outText.setText("This is test # " + count);
        outText.setColor(ColorRGBA.Blue);
        outText.setLocalScale(2);
        guiNode.attachChild(outText);
        // *** 1. End



In the simpleUpdate(), the counter is incremented and new text is written.


        // *** 2. Start (Updated text)
        outText.setText("This is a test # " + (++count));
        // *** 2. End



package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.math.ColorRGBA;
import com.jme3.system.AppSettings;

public class JMonkey34 extends SimpleApplication {

    public static void main(String[] args) {
        AppSettings cfg = new AppSettings(true);
        cfg.setFrameRate(2); // 2 frames per second
        JMonkey34 app = new JMonkey34();
        app.setSettings(cfg);
        app.start();
    }
    private int count = 0;
    private BitmapText outText = null;

    @Override
    public void simpleInitApp() {
        flyCam.setEnabled(false);
        viewPort.setBackgroundColor(ColorRGBA.LightGray);
        setDisplayStatView(false);
        setDisplayFps(false);
        // *** 1. Start (Load font, initial text)
        BitmapFont myFont = assetManager.loadFont(
                "Interface/Fonts/TrebuchetMS.fnt");
        outText = new BitmapText(myFont, false);
        outText.setSize(guiFont.getCharSet().getRenderedSize());
        outText.move(settings.getWidth()/5,      // X
                        settings.getHeight()/2,  // Y
                        0);                      // Z
        outText.setText("This is test # " + count);
        outText.setColor(ColorRGBA.Blue);
        outText.setLocalScale(2);
        guiNode.attachChild(outText);
        // *** 1. End
    }
    
    @Override
    public void simpleUpdate(float tps) {
        // *** 2. Start (Updated text)
        outText.setText("This is a test # " + (++count));
        // *** 2. End
    }
}


Output:


33. Animation

There are two parts to creating Animation for our characters and other objects, within jMonkeyEngine. The first part involves design using Blender or similar software. This might take some time. However, the end result is a few text files and images. We will cover the second part, importing the design files into jMonkeyEngine.




The design files for a few models and animations are included in jMonkeyEngine, inside a compressed jar file. We just have to add the jar file containing the models to the project Library. This is done by right-clicking on the Project, select Properties, and then Libraries. After clicking on Add Library, we find jme3-test-data.




Now, if we expand Libraries in our project, we can find the relevant Model.




We have to have animation object variables.


    // *** 1. Start (Animation variables)
    private AnimChannel channel;
    private AnimControl control;
    // *** 1. End



We also have a ArrayList of strings to hold the list of animations. We will create a Node for the model. There will be variables for current animation, as well as text that will be outputted.


    // *** 2. Start (variables)
    ArrayList<String> animNames; // list of animations
    Node sinbad; // Model
    private boolean finished = true; // Current Animation Finished
    private int animNum; // Points to random animation
    private float playTime; // Current Animation Duration
    private String stringOut; // Text that will written
    BitmapText outText; // Bitmap Text for guiNode
    // *** 2. End



Some of the initialization code will be used to create Bitmap Text object so it can be used later to print status text. The Node is loaded with the model. Now the Animation Control can be set and we can find the list of Animations, using methods on control.


        // *** 3. Start (Initialization)
        guiFont = assetManager.loadFont(
                    "Interface/Fonts/Default.fnt"); // font
        outText = new BitmapText(guiFont, false); // test Bitmap
        outText.setSize(guiFont.getCharSet().getRenderedSize());
        sinbad = (Node)assetManager.loadModel(
                "Models/Sinbad/Sinbad.mesh.xml"); // model
        sinbad.setLocalScale(0.5f); // scale
        rootNode.attachChild(sinbad); // visual
        control = sinbad.getControl(AnimControl.class); // anim control
        animNames = new ArrayList<String>(control.getAnimationNames());
        // animNames is ArrayList String array holding all Animations.
        // *** 3. End



New animation is started, inside the simpleUpdate(), provided the last animation has finished.


        // *** 4. Start (New Animation, Write Text)
        if (finished) {
            animNum = FastMath.nextRandomInt(0, animNames.size()-1);
            channel.setAnim(animNames.get(animNum));
            playTime = channel.getAnimMaxTime();
            channel.setLoopMode(LoopMode.DontLoop);
            stringOut = "Starting (" +
                    animNum + ")\t" + animNames.get(animNum) +
                    "\tplay time = " + playTime;
            System.out.println(stringOut);
            if (playTime>tpf) finished = false;
            guiNode.detachAllChildren();
            outText.setText(stringOut);
            outText.setLocalTranslation(50, 50, 0);
            guiNode.attachChild(outText);
        }
        // *** 4. End



Once any animation is finished it will call this method, since the animation listener was set, and our class implements the animation listener. This will set our finished boolean to true, so in the next simpleUpdate(), a new animation may be started.


        // *** 5. Start (Called when current Animation finishes)
        finished = true;
        // *** 5. End



package mygame;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.system.AppSettings;
import java.util.ArrayList;

public class JMonkey33 extends SimpleApplication 
        implements AnimEventListener {

    public static void main(String[] args) {
        AppSettings cfg = new AppSettings(true);
        cfg.setFrameRate(60);
        JMonkey33 app = new JMonkey33();
        app.setSettings(cfg);
        app.setDisplayFps(false);
        app.setDisplayStatView(false);
        app.start();
    }
    
    // *** 1. Start (Animation variables)
    private AnimChannel channel;
    private AnimControl control;
    // *** 1. End
    
    // *** 2. Start (variables)
    ArrayList<String> animNames; // list of animations
    Node sinbad; // Model
    private boolean finished = true; // Current Animation Finished
    private int animNum; // Points to random animation
    private float playTime; // Current Animation Duration
    private String stringOut; // Text that will written
    BitmapText outText; // Bitmap Text for guiNode
    // *** 2. End

    @Override
    public void simpleInitApp() {
        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
        flyCam.setEnabled(false);
        // *** 3. Start (Initialization)
        guiFont = assetManager.loadFont(
                    "Interface/Fonts/Default.fnt"); // font
        outText = new BitmapText(guiFont, false); // test Bitmap
        outText.setSize(guiFont.getCharSet().getRenderedSize());
        sinbad = (Node)assetManager.loadModel(
                "Models/Sinbad/Sinbad.mesh.xml"); // model
        sinbad.setLocalScale(0.5f); // scale
        rootNode.attachChild(sinbad); // visual
        control = sinbad.getControl(AnimControl.class); // anim control
        animNames = new ArrayList<String>(control.getAnimationNames());
        // animNames is ArrayList String array holding all Animations.
        // *** 3. End
        System.out.println("There are " + animNames.size()
                + " animations!");
        for (String anim : animNames) {
            System.out.printf("%20s\n",anim);
        }
        // ***
        /** A white, directional light source */
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun);
        control.addListener(this); // Listen
        channel = control.createChannel(); // Animation control
    }

    @Override
    public void simpleUpdate(float tpf) {
        // *** 4. Start (New Animation, Write Text)
        if (finished) {
            animNum = FastMath.nextRandomInt(0, animNames.size()-1);
            channel.setAnim(animNames.get(animNum));
            playTime = channel.getAnimMaxTime();
            channel.setLoopMode(LoopMode.DontLoop);
            stringOut = "Starting (" +
                    animNum + ")\t" + animNames.get(animNum) +
                    "\tplay time = " + playTime;
            System.out.println(stringOut);
            if (playTime>tpf) finished = false;
            guiNode.detachAllChildren();
            outText.setText(stringOut);
            outText.setLocalTranslation(50, 50, 0);
            guiNode.attachChild(outText);
        }
        // *** 4. End
    }

    @Override
    public void simpleRender(RenderManager rm) {
    }

    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
        // *** 5. Start (Called when current Animation finishes)
        finished = true;
        // *** 5. End
    }

    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
    }
}


Output:


Aug 26, 2014

32. Mesh Programming


A mesh can be created programmatically in jMonkeyEngine.




The cube has 24 vertex points with 8 physical vertex points, each with 3 normals.


        // *** 1. Start (24 pts in cube with normal)
        Vector3f [] vert = new Vector3f[24];
        vert[0] = new Vector3f(-1, -1, -1); // -z
        vert[1] = new Vector3f(1, -1, -1); // -z
        vert[2] = new Vector3f(1, 1, -1); // -z
        vert[3] = new Vector3f(-1, 1, -1); // -z
        vert[4] = new Vector3f(1, -1, -1); // +x
        vert[5] = new Vector3f(1, -1, 1); // +x
        vert[6] = new Vector3f(1, 1, 1); // +x
        vert[7] = new Vector3f(1, 1, -1); // +x
        vert[8] = new Vector3f(1, -1, 1); // +z
        vert[9] = new Vector3f(-1, -1, 1); // +z
        vert[10] = new Vector3f(-1, 1, 1); // +z
        vert[11] = new Vector3f(1, 1, 1); // +z
        vert[12] = new Vector3f(-1, -1, 1); // -x
        vert[13] = new Vector3f(-1, -1, -1); // -x
        vert[14] = new Vector3f(-1, 1, -1); // -x
        vert[15] = new Vector3f(-1, 1, 1); // -x
        vert[16] = new Vector3f(1, 1, -1); // +y
        vert[17] = new Vector3f(1, 1, 1); // +y
        vert[18] = new Vector3f(-1, 1, 1); // +y
        vert[19] = new Vector3f(-1, 1, -1); // +y
        vert[20] = new Vector3f(-1, -1, -1); // -y
        vert[21] = new Vector3f(-1, -1, 1); // -y
        vert[22] = new Vector3f(1, -1, 1); // -y
        vert[23] = new Vector3f(1, -1, -1); // -y
        // *** 1. End



For the 24 vertex points, we have to give the normal. With the vertex information and normal information, all the 24 entries are now unique.


        // *** 2. Start (Normals for the Verts)
        Vector3f[] norm = new Vector3f[24];
        norm[0] = new Vector3f(0, 0, -1); // -z
        norm[1] = new Vector3f(0, 0, -1); // -z
        norm[2] = new Vector3f(0, 0, -1); // -z
        norm[3] = new Vector3f(0, 0, -1); // -z
        norm[4] = new Vector3f(1, 0, 0); // +x
        norm[5] = new Vector3f(1, 0, 0); // +x
        norm[6] = new Vector3f(1, 0, 0); // +x
        norm[7] = new Vector3f(1, 0, 0); // +x
        norm[8] = new Vector3f(0, 0, 1); // +z
        norm[9] = new Vector3f(0, 0, 1); // +z
        norm[10] = new Vector3f(0, 0, 1); // +z
        norm[11] = new Vector3f(0, 0, 1); // +z
        norm[12] = new Vector3f(-1, 0, 0); // -x
        norm[13] = new Vector3f(-1, 0, 0); // -x
        norm[14] = new Vector3f(-1, 0, 0); // -x
        norm[15] = new Vector3f(-1, 0, 0); // -x
        norm[16] = new Vector3f(0, 1, 0); // +y
        norm[17] = new Vector3f(0, 1, 0); // +y
        norm[18] = new Vector3f(0, 1, 0); // +y
        norm[19] = new Vector3f(0, 1, 0); // +y
        norm[20] = new Vector3f(0, -1, 0); // -y
        norm[21] = new Vector3f(0, -1, 0); // -y
        norm[22] = new Vector3f(0, -1, 0); // -y
        norm[23] = new Vector3f(0, -1, 0); // -y
        // *** 2. End



Inside the program, we can create our UV mapping, based on the image. Here, the image was 3 by 2, and these divisions corresponds to the 6 faces.


        // *** 3. Start (Texture coordinates for the Verts)
        Vector2f [] texCoord = new Vector2f[24];
        texCoord[0] = new Vector2f(0,0);
        texCoord[1] = new Vector2f(THIRD,0);
        texCoord[2] = new Vector2f(THIRD,0.5f);
        texCoord[3] = new Vector2f(0,0.5f);
        texCoord[4] = new Vector2f(THIRD,0);
        texCoord[5] = new Vector2f(2*THIRD,0);
        texCoord[6] = new Vector2f(2*THIRD,0.5f);
        texCoord[7] = new Vector2f(THIRD,0.5f);
        texCoord[8] = new Vector2f(2*THIRD,0);
        texCoord[9] = new Vector2f(1,0);
        texCoord[10] = new Vector2f(1,0.5f);
        texCoord[11] = new Vector2f(2*THIRD,0.5f);
        texCoord[12] = new Vector2f(0,0.5f);
        texCoord[13] = new Vector2f(THIRD,0.5f);
        texCoord[14] = new Vector2f(THIRD,1);
        texCoord[15] = new Vector2f(0,1);
        texCoord[16] = new Vector2f(THIRD,0.5f);
        texCoord[17] = new Vector2f(2*THIRD,0.5f);
        texCoord[18] = new Vector2f(2*THIRD,1);
        texCoord[19] = new Vector2f(THIRD,1);
        texCoord[20] = new Vector2f(2*THIRD,0.5f);
        texCoord[21] = new Vector2f(1,0.5f);
        texCoord[22] = new Vector2f(1,1);
        texCoord[23] = new Vector2f(2*THIRD,1);
        // *** 3. End



dice.png texture




For the 6 faces, we have to give the 12 counter clockwise triangles.


        // *** 4. Start (// Indexes. 12 CCW Triangles)
        int [] indexes = {  3,2,1, 1,0,3, // -z
                            7,6,5, 5,4,7, // +x
                            11,10,9, 9,8,11, // +z
                            15,14,13, 13,12,15, // -x
                            19,18,17, 17,16,19, // +y
                            23,22,21, 21,20,23  // -y
                         };
        // *** 4. End



Now that we have the lists, we can create our mesh.


        // *** 5. Start (Setting buffers)
        m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vert));
        m.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(norm));
        m.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
        m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes));
        m.updateBound();
        // *** 5. End



We have to set the texture from external image file.


        // *** 6. Start (Geometry, Material, Texture)
        Geometry geom = new Geometry("DiceMesh", m);
        Material mat = new Material(assetManager,
            "Common/MatDefs/Light/Lighting.j3md");
        Texture matTex = assetManager.loadTexture(
            "Textures/dice.png");
        mat.setTexture("DiffuseMap", matTex);
        mat.setColor("Diffuse",ColorRGBA.Red);
        mat.setColor("Specular",ColorRGBA.White);
        mat.setFloat("Shininess", 32f);  // [0,128]
        geom.setMaterial(mat);
        rootNode.attachChild(geom);
        // *** 6. End



The lights have to illuminate the scene.


        // *** 7. Start (Lights, Drag from Palette)
        /** A white, directional light source */ 
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun); /** A white ambient light source. */ 
        /** A cone-shaped spotlight with location, direction, range */
        SpotLight spot = new SpotLight(); 
        spot = new SpotLight(); 
        spot.setSpotRange(100); 
        spot.setSpotOuterAngle(20 * FastMath.DEG_TO_RAD); 
        spot.setSpotInnerAngle(15 * FastMath.DEG_TO_RAD); 
        spot.setDirection(cam.getDirection()); 
        spot.setPosition(cam.getLocation()); 
        rootNode.addLight(spot); 
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(ColorRGBA.Red);
        rootNode.addLight(ambient);
        // *** 7. End



package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.light.SpotLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;

public class JMonkey32 extends SimpleApplication {

    public static void main(String[] args){
        AppSettings cfg = new AppSettings(true);
        cfg.setFrameRate(60);
        JMonkey32 app = new JMonkey32();
        app.setSettings(cfg);
        app.setDisplayFps(false);
        app.setDisplayStatView(false);
        app.start();
    }

    @Override
    public void simpleInitApp() {
      
        final float THIRD = 1f/3f;
        Mesh m = new Mesh();
        viewPort.setBackgroundColor(ColorRGBA.White);
        flyCam.setEnabled(false);
        
        // *** 1. Start (24 pts in cube with normal)
        Vector3f [] vert = new Vector3f[24];
        vert[0] = new Vector3f(-1, -1, -1); // -z
        vert[1] = new Vector3f(1, -1, -1); // -z
        vert[2] = new Vector3f(1, 1, -1); // -z
        vert[3] = new Vector3f(-1, 1, -1); // -z
        vert[4] = new Vector3f(1, -1, -1); // +x
        vert[5] = new Vector3f(1, -1, 1); // +x
        vert[6] = new Vector3f(1, 1, 1); // +x
        vert[7] = new Vector3f(1, 1, -1); // +x
        vert[8] = new Vector3f(1, -1, 1); // +z
        vert[9] = new Vector3f(-1, -1, 1); // +z
        vert[10] = new Vector3f(-1, 1, 1); // +z
        vert[11] = new Vector3f(1, 1, 1); // +z
        vert[12] = new Vector3f(-1, -1, 1); // -x
        vert[13] = new Vector3f(-1, -1, -1); // -x
        vert[14] = new Vector3f(-1, 1, -1); // -x
        vert[15] = new Vector3f(-1, 1, 1); // -x
        vert[16] = new Vector3f(1, 1, -1); // +y
        vert[17] = new Vector3f(1, 1, 1); // +y
        vert[18] = new Vector3f(-1, 1, 1); // +y
        vert[19] = new Vector3f(-1, 1, -1); // +y
        vert[20] = new Vector3f(-1, -1, -1); // -y
        vert[21] = new Vector3f(-1, -1, 1); // -y
        vert[22] = new Vector3f(1, -1, 1); // -y
        vert[23] = new Vector3f(1, -1, -1); // -y
        // *** 1. End
        
        // *** 2. Start (Normals for the Verts)
        Vector3f[] norm = new Vector3f[24];
        norm[0] = new Vector3f(0, 0, -1); // -z
        norm[1] = new Vector3f(0, 0, -1); // -z
        norm[2] = new Vector3f(0, 0, -1); // -z
        norm[3] = new Vector3f(0, 0, -1); // -z
        norm[4] = new Vector3f(1, 0, 0); // +x
        norm[5] = new Vector3f(1, 0, 0); // +x
        norm[6] = new Vector3f(1, 0, 0); // +x
        norm[7] = new Vector3f(1, 0, 0); // +x
        norm[8] = new Vector3f(0, 0, 1); // +z
        norm[9] = new Vector3f(0, 0, 1); // +z
        norm[10] = new Vector3f(0, 0, 1); // +z
        norm[11] = new Vector3f(0, 0, 1); // +z
        norm[12] = new Vector3f(-1, 0, 0); // -x
        norm[13] = new Vector3f(-1, 0, 0); // -x
        norm[14] = new Vector3f(-1, 0, 0); // -x
        norm[15] = new Vector3f(-1, 0, 0); // -x
        norm[16] = new Vector3f(0, 1, 0); // +y
        norm[17] = new Vector3f(0, 1, 0); // +y
        norm[18] = new Vector3f(0, 1, 0); // +y
        norm[19] = new Vector3f(0, 1, 0); // +y
        norm[20] = new Vector3f(0, -1, 0); // -y
        norm[21] = new Vector3f(0, -1, 0); // -y
        norm[22] = new Vector3f(0, -1, 0); // -y
        norm[23] = new Vector3f(0, -1, 0); // -y
        // *** 2. End
        
        // *** 3. Start (Texture coordinates for the Verts)
        Vector2f [] texCoord = new Vector2f[24];
        texCoord[0] = new Vector2f(0,0);
        texCoord[1] = new Vector2f(THIRD,0);
        texCoord[2] = new Vector2f(THIRD,0.5f);
        texCoord[3] = new Vector2f(0,0.5f);
        texCoord[4] = new Vector2f(THIRD,0);
        texCoord[5] = new Vector2f(2*THIRD,0);
        texCoord[6] = new Vector2f(2*THIRD,0.5f);
        texCoord[7] = new Vector2f(THIRD,0.5f);
        texCoord[8] = new Vector2f(2*THIRD,0);
        texCoord[9] = new Vector2f(1,0);
        texCoord[10] = new Vector2f(1,0.5f);
        texCoord[11] = new Vector2f(2*THIRD,0.5f);
        texCoord[12] = new Vector2f(0,0.5f);
        texCoord[13] = new Vector2f(THIRD,0.5f);
        texCoord[14] = new Vector2f(THIRD,1);
        texCoord[15] = new Vector2f(0,1);
        texCoord[16] = new Vector2f(THIRD,0.5f);
        texCoord[17] = new Vector2f(2*THIRD,0.5f);
        texCoord[18] = new Vector2f(2*THIRD,1);
        texCoord[19] = new Vector2f(THIRD,1);
        texCoord[20] = new Vector2f(2*THIRD,0.5f);
        texCoord[21] = new Vector2f(1,0.5f);
        texCoord[22] = new Vector2f(1,1);
        texCoord[23] = new Vector2f(2*THIRD,1);
        // *** 3. End
        
        // *** 4. Start (// Indexes. 12 CCW Triangles)
        int [] indexes = {  3,2,1, 1,0,3, // -z
                            7,6,5, 5,4,7, // +x
                            11,10,9, 9,8,11, // +z
                            15,14,13, 13,12,15, // -x
                            19,18,17, 17,16,19, // +y
                            23,22,21, 21,20,23  // -y
                         };
        // *** 4. End
        
        // *** 5. Start (Setting buffers)
        m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vert));
        m.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(norm));
        m.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord));
        m.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(indexes));
        m.updateBound();
        // *** 5. End

        // *** 6. Start (Geometry, Material, Texture)
        Geometry geom = new Geometry("DiceMesh", m);
        Material mat = new Material(assetManager,
            "Common/MatDefs/Light/Lighting.j3md");
        Texture matTex = assetManager.loadTexture(
            "Textures/dice.png");
        mat.setTexture("DiffuseMap", matTex);
        mat.setColor("Diffuse",ColorRGBA.Red);
        mat.setColor("Specular",ColorRGBA.White);
        mat.setFloat("Shininess", 32f);  // [0,128]
        geom.setMaterial(mat);
        rootNode.attachChild(geom);
        // *** 6. End
        
        // *** 7. Start (Lights, Drag from Palette)
        /** A white, directional light source */ 
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection((new Vector3f(-0.5f, -0.5f, -0.5f)).normalizeLocal());
        sun.setColor(ColorRGBA.White);
        rootNode.addLight(sun); /** A white ambient light source. */ 
        /** A cone-shaped spotlight with location, direction, range */
        SpotLight spot = new SpotLight(); 
        spot = new SpotLight(); 
        spot.setSpotRange(100); 
        spot.setSpotOuterAngle(20 * FastMath.DEG_TO_RAD); 
        spot.setSpotInnerAngle(15 * FastMath.DEG_TO_RAD); 
        spot.setDirection(cam.getDirection()); 
        spot.setPosition(cam.getLocation()); 
        rootNode.addLight(spot); 
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(ColorRGBA.Red);
        rootNode.addLight(ambient);
        // *** 7. End
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        // Rotation per frame
        rootNode.rotate(.005f,.01f,.008f);
    }
}


Output: