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.
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".
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.
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.
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);
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.
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
).
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.
something.mesh.xml
goes with something.material
, plus (optionally) something.skeleton.xml
and some JPG files.assets/Models/
directory. E.g. assets/Models/something/
.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.
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); |
As an exercise, let's try different ways of loading a scene. You can load a scene directly, or from a zip file:
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.)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:
town.zip
is in the project directory.town.zip/main.scene
or similar.)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);
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:
town/
directory into the assets/Scenes/
directory of your project.simpleInitApp() {
Spatial gameLevel = assetManager.loadModel("Scenes/town/main.scene"); gameLevel.setLocalTranslation(0, -5.2f, 0); gameLevel.setLocalScale(2); rootNode.attachChild(gameLevel);
assets/…
directory.Here is a third method you must know. Loading a scene/model from a .j3o file:
assets/Scenes/town
directory.main.scene
and convert the scene to binary: The jMoneyPlatform generates a main.j3o file.simpleInitApp() {
Spatial gameLevel = assetManager.loadModel("Scenes/town/main.j3o"); gameLevel.setLocalTranslation(0, -5.2f, 0); gameLevel.setLocalScale(2); rootNode.attachChild(gameLevel);
assets/…
directory. What is the difference between loading Scenes/town/main.j3o
and Scenes/town/main.scene
?
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: