JME 3 Tutorial (3) - Hello Assets

Previous: Hello Node, Next: Hello Update Loop

In this tutorial we will learn to load 3-D models and text into the scene graph, using the jME asset manager. You also learn how to arrive at the correct paths, and which file formats to use.

To use the example assets in a new jMonkeyPlatform project, right-click your project, select "Properties", go to "Libraries", press "Add Library" and add the "jme3-test-data" library.

Code Sample

package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
 
/** Sample 3 - how to load an OBJ model, and OgreXML model,
 * a material/texture, or text. */
public class HelloAssets extends SimpleApplication {
 
    public static void main(String[] args) {
        HelloAssets app = new HelloAssets();
        app.start();
    }
 
    @Override
    public void simpleInitApp() {
 
        Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
        Material mat_default = new Material(
            assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
        teapot.setMaterial(mat_default);
        rootNode.attachChild(teapot);
 
        // Create a wall with a simple texture from test_data
        Box(Vector3f.ZERO, 2.5f,2.5f,1.0f);
        Spatial wall = new Geometry("Box", box );
        Material mat_brick = new Material(
            assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_brick.setTexture("ColorMap",
            assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
        wall.setMaterial(mat_brick);
        wall.setLocalTranslation(2.0f,-2.5f,0.0f);
        rootNode.attachChild(wall);
 
        // Display a line of text with a default font
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText helloText = new BitmapText(guiFont, false);
        helloText.setSize(guiFont.getCharSet().getRenderedSize());
        helloText.setText("Hello World");
        helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
        guiNode.attachChild(helloText);
 
        // Load a model from test_data (OgreXML + material + texture)
        Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
        ninja.scale(0.05f, 0.05f, 0.05f);
        ninja.rotate(0.0f, -3.0f, 0.0f);
        ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
        rootNode.attachChild(ninja);
        // You must add a light to make the model visible
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
        rootNode.addLight(sun);
 
    }
}

Build and run the code sample. You should see a green Ninja with a colorful teapot standing behind a wall. The text on the screen should say "Hello World".

The Asset Manager

JME3 comes with a handy asset manager that helps you keep your assets structured. Project assets are media files such as models, materials, textures, scenes, shaders, sounds, and fonts. The asset manager maintains a root that contains the classpath, so it can load any file from the current classpath (the top level of your project directory).

Additionally, the assetManager can be configured to add any path to the assets root, so assets can be loaded from directories you specify. In a jMonkeyPlatform project, jME3 seaches for models in the assets directory of your project. This is our recommended directory structure for storing assets:

assets/Interface/
assets/MatDefs/
assets/Materials/
assets/Models/
assets/Scenes/
assets/Shaders/
assets/Sounds/
assets/Textures/
build.xml
src/...
dist/...

These are just the most common examples, you can name the directories inside the assets directory how you like.

Loading Textures

Place the textures in a subdirectory of assets/Textures/. Load the texture into the material before you set the Material. The following code sample is from the simpleInitApp() method:

        // Create a wall with a simple texture from test_data
        Box(Vector3f.ZERO, 2.5f,2.5f,1.0f);
        Spatial wall = new Geometry("Box", box );
        Material mat_brick = new Material(
            assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat_brick.setTexture("ColorMap",
            assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall.jpg"));
        wall.setMaterial(mat_brick);
        wall.setLocalTranslation(2.0f,-2.5f,0.0f);
        rootNode.attachChild(wall);

In most cases, you use default material descriptions such as "SimpleTextured.j3md", as we do in this example. Advanced users can create custom Materials.

Loading Text and Fonts

This example displays "Hello Text" in the default font at the bottom edge of the window. You attach text to the guiNode, a special node for flat (orthogonal) display elements. You can clear existing text in the guiNode by detaching all its children. The following code sample goes into the simpleInitApp() method.

        // Display a line of text with a default font
        guiNode.detachAllChildren();
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText helloText = new BitmapText(guiFont, false);
        helloText.setSize(guiFont.getCharSet().getRenderedSize());
        helloText.setText("Hello World");
        helloText.setLocalTranslation(300, helloText.getLineHeight(), 0);
        guiNode.attachChild(helloText);

Loading an Ogre XML Model

Export your model in OgreXML format (.mesh.xml, .scene, .material, .skeleton.xml) and place it in a subdirectory of assets/Models/. The following code sample goes into the simpleInitApp() method.

        // Load a model from test_data (OgreXML + material + texture)
        Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml");
        ninja.scale(0.05f, 0.05f, 0.05f);
        ninja.rotate(0.0f, -3.0f, 0.0f);
        ninja.setLocalTranslation(0.0f, -5.0f, -2.0f);
        rootNode.attachChild(ninja);
        // You must add a light to make the model visible
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f));
        rootNode.addLight(sun);

If you use the build script created by the jMonkeyPlatform then, by default, the original OgreXML files will not be included in your distributed game. You will get an error message when trying to load them.

com.jme3.asset.DesktopAssetManager loadAsset
WARNING: Cannot locate resource: Scenes/town/main.scene
com.jme3.app.Application handleError
SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]
java.lang.NullPointerException

For the release build, you should work with .j3o files only. Use the jMonkeyPlatform's context menu action to convert OgreXML models to .j3o format.

Loading Assets From Custom Paths

What if your game relies on user supplied model files, that will not be included in your distribution? If a file is not located in the default location, you can register a custom Locator and load it from any path.

Here is a usage example of a ZipLocator that is registered to a file town.zip in the top level of your project directory:

    assetManager.registerLocator("town.zip", ZipLocator.class.getName());
    Spatial scene = assetManager.loadModel("main.scene");
    rootNode.attachChild(scene);

Here is a HttpZipLocator that can download zipped models:

    assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/wildhouse.zip",
                                 HttpZipLocator.class.getName());
    Spatial scene = assetManager.loadModel("main.scene");
    rootNode.attachChild(scene);

JME3 offers ClasspathLocator, ZipLocator, FileLocator, HttpZipLocator, and UrlLocator (see com.jme3.asset.plugins).

Creating Models and Scenes

To create 3D models and scenes, you need a 3D Mesh Editor such as Blender, with an OgreXML Exporter plugin. Create your models with UV textures. You can use the jMonkeyPlatform to load models and create scenes from them.

Export your models as Ogre XML meshes with materials.

  1. Open the menu File > Export > OgreXML Exporter to open the exporter dialog.
  2. In the Export Materials field: Give the material the same name as the model. For example, the model something.mesh.xml goes with something.material, plus (optionally) something.skeleton.xml and some JPG files.
  3. In the Export Meshes field: Select a subdirectory of your assets/Models/ directory. E.g. assets/Models/something/.
  4. Activate the following exporter settings:
    • Copy Textures: YES
    • Rendering Materials: YES
    • Flip Axis: YES
    • Require Materials: YES
    • Skeleton name follows mesh: YES
  5. Click export.

File Format: JME 3 can load Ogre XML models, materials, and scenes, as well as Wavefront OBJ+MTL models. For the game release, you should optimize model loading by converting all models to JME3's .j3o format. We recommend creating your project in the jMonkeyPlatform, it contains an integrated .j3o converter.

  1. Open your JME3 Project in the jMonkeyplatform.
  2. Right-click a .mesh.xml file in the Projects (or Favorites) window, and choose "convert to JME3 binary".
  3. The .j3o file appears next to the .mesh.xml file with the same name.
  4. If you use the build script generated by the jMonkeyPlatform, mesh.xml files and obj files will be excluded from the executable JAR. If you get a runtime exception, make sure you have converted all models to .j3o.

Loading Models and Scenes

Task? Solution!
Load a model with materials Use the asset managers loadModel() method and attach the Spatial to the rootNode.
Spatial elephant = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml");
rootNode.attachChild(elephant);
Spatial elephant = assetManager.loadModel("Models/Elephant/Elephant.j3o");
rootNode.attachChild(elephant);
Load a model without materials If you have a model without materials, you have to add a default material to make it visible.
Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj");
Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
teapot.setMaterial(mat);
rootNode.attachChild(teapot);
Load a scene You load scenes just like you load models:
Spatial scene = assetManager.loadModel("Scenes/house/main.scene");
rootNode.attachChild(scene);

Excercise - How to Load Assets

As an exercise, let's try different ways of loading a scene. You can load a scene directly, or from a zip file:

  1. Download the town.zip sample scene.
  2. (Optional:) Unzip the town.zip to see the structure of the contained Ogre dotScene: You'll get a directory named town. It contains XML and texture files, and file called main.scene. (This is just for your information, you do not need to do anything with it.)
  3. Place the town.zip file in the top level directory of your JME3 project, like so:
    jMonkeyProjects/MyGameProject/assets/
    jMonkeyProjects/MyGameProject/build.xml
    jMonkeyProjects/MyGameProject/src/
    jMonkeyProjects/MyGameProject/town.zip
    ...

Use the following method to load models from a zip file:

  1. Make sure town.zip is in the project directory.
  2. We register a zip file locator to the project directory. The loadModel() method now searches this zip directly for the files to load.
    (That is, do not write town.zip/main.scene or similar.)
  3. Add the following code under simpleInitApp() {
        assetManager.registerLocator("town.zip", ZipLocator.class.getName());
        Spatial gameLevel = assetManager.loadModel("main.scene");
        gameLevel.setLocalTranslation(0, -5.2f, 0);
        gameLevel.setLocalScale(2);
        rootNode.attachChild(gameLevel);
  4. Clean, build and run the project. You should see the Ninja+wall+teapot standing in a town.

If you register new locators, make sure you do not get any file name conflicts: Give each scene a unique name.

Earlier in this tutorial, you loaded scenes and models from the asset directory. This is the most common way you will be loading scenes and models. Here is the typical procedure:

  1. Remove the code from the previous exercise.
  2. Move the unzipped town/ directory into the assets/Scenes/ directory of your project.
  3. Add the following code under simpleInitApp() {
        Spatial gameLevel = assetManager.loadModel("Scenes/town/main.scene");
        gameLevel.setLocalTranslation(0, -5.2f, 0);
        gameLevel.setLocalScale(2);
        rootNode.attachChild(gameLevel);
  4. Note that the path starts relative to the assets/… directory.
  5. Clean, build and run the project. Again, you should see the Ninja+wall+teapot standing in a town.

Here is a third method you must know. Loading a scene/model from a .j3o file:

  1. Remove the code from the previous exercise.
  2. If you haven't already, open the jMonkeyPlatform and open the project that contains the Hello Asset class.
  3. In the projects window, browse to the assets/Scenes/town directory.
  4. Right-click the main.scene and convert the scene to binary: The jMoneyPlatform generates a main.j3o file.
  5. Add the following code under simpleInitApp() {
        Spatial gameLevel = assetManager.loadModel("Scenes/town/main.j3o");
        gameLevel.setLocalTranslation(0, -5.2f, 0);
        gameLevel.setLocalScale(2);
        rootNode.attachChild(gameLevel);
  6. Again, note that the path starts relative to the assets/… directory.
  7. Clean, Build and run the project. Again, you should see the Ninja+wall+teapot standing in a town.

What is the difference between loading Scenes/town/main.j3o and Scenes/town/main.scene?

Conclusion

Now you know how to populate the scenegraph with static shapes and models, and how to build scenes. You have learned how to load assets using the assetManager and you have seen that the paths start relative to your project directory. Another important thing you have learned is to convert models to .j3o format for the JAR builds.

Let's add some action to the scene and continue with the Update Loop.


See also:

beginner, beginner,, intro, documentation, lightnode, material, model, node, gui, texture

view online version