Tutorials‎ > ‎

Creating Your Own 3D World Editor with jMonkeyEngine 2.0 (Part 2) – Get On the Terrain

posted Dec 29, 2011, 12:02 AM by William Salim   [ updated Aug 15, 2016, 11:28 PM by Surya Wang ]

Introduction

From the last article, we have created a user interface for our world editor. It can handle simple camera movements such as pan, rotate, dan zoom. In this article, I will cover about how we set up the terrain and prepare some tools that will be used to edit the terrain. I won’t give a lot of code snippet here because the code in this article will depend on your own code style and design. I assume you have already done with the previous article.

TerrainPass

You may wonder why I write an article for just creating a terrain while it’s already covered in the jME flagrush tutorial. It’s because we will use a slightly different method to create our terrain. Unlike the terrain explained in the flagrush tutorial, our terrain need more flexibility. It has to be able to be edited while we run the world editor. The texture should be able to be edited as well. This flexibility can’t be achieved with the method explained in flagrush tutorial because it can only create an uneditable terrain.

To get the editable terrain, we will use TerrainPass. Actually, it’s only used for the texture. So, what is TerrainPass? jMonkeyEngine can use several kind of pass rendering to achieve some kinds of rendering effects such as bloom, shadow, motion blur, sketch, etc. TerrainPass is a pass renderer which allow us to create texture splatting effect on the terrain. With TerrainPass, we can give our terrain some layers of detail texture and specify which part of the layer will be displayed, while with ordinary renderer, we can only have one layer and it will cover entire terrain. So TerrainPass has everything we need to make our terrain.

First of all, you should get your hands on the source code of TerrainPass. It isn’t a part of jME stable release. But you can get it from http://mfkarpg.110mb.com/jme/TerrainPass-jME2.zip. It comes with a test application, so you can see the example code from that. After you get the source code, you can copy all the files into your project directory. You may get some errors in the file CXTerrainPass.java (I do), if you do, you can just delete the file as we won’t use that class. Unfortunately, the existing TerrainPass doesn’t has all the methods we need. So you have two options, you can extend TerrainPass into your own class then add the new methods in your class or you can just directly modify the existing TerrainPass. In my case, since I have the source code, I prefer to modify the the source code. You can choose either that suits your style. But in this article, I won’t add anything to the TerrainPass as we don’t need it yet.

Now we go back to our previous code. If you see our implementor class, it is extended from SimplePassCanvasImpl. As I have explained before, it behaves like SimplePassGame which manages the renderer to draw from some passes.

I will now explain how to setup the terrain by using TerrainPass. But before I explain further, I would suggest you to encapsulate all your scene data into a class, especially everything that you can change dynamically from the world editor. They include but not limited to terrain, textures, static objects, animated objects, lights, etc. For the terrain mesh, you can make it like what you usually do. You can use any type of height map provided by jME to create you initial terrain. I provide some type of height map for user to use. I also let the user to choose the size of the terrain. I suggest you to use TerrainPage because we may be dealing with a large terrain. TerrainPage  also allows us to change the individual height map value at a certain position (In TerrainBlock, we need to reset the entire height map to change it).

Because we want our terrain to be rendered by TerrainPass, we don’t need to attach the terrain we’ve just created to the root node. Adding it to root node will make the default renderer renders it. Instead, you have to create an instance of TerrainPass, and add the terrain to it by using addTerrain() method from TerrainPass. Then by using getManager() method from you implementor class, you will get the pass manager which contains the list of passes that will be used to draw the scene (these include the default render pass which draws the root node). You have to add your TerrainPass to this manager to let the implementor class draws it.

Before you can run your application, you have to give at least one texture to the terrain. But assigning the texture state to the terrain won’t work because we don’t use general rendering method. Instead of that, we have to assign the texture to the TerrainPass to make it work. The texture on TerrainPass is divided into two types. The first type is the detail texture. It repeats itself across the terrain surface just like the detail texture of the terrain. The other type is the alpha texture. This texture defines which area of the terrain that will have the detail texture drawn onto it based on alpha value of the texture at certain point. These two kinds of texture have to be added in pair to the TerrainPass by using addDetail() method from TerrainPass. If the given alpha texture is null, it will set the given detail texture to be the base texture. This base texture is what the TerrainPass needs to render the terrain. Later, we will need to edit the textures, so I think you should consider to store the textures to another place because we have no way to obtain the texture data from the TerrainPass.

This is what we get so far. On the left we have a flat all zero height map terrain, while on the right we have hill based height map terrain. It’s not very pretty, is it? On the hill based terrain we can’t see the difference in height because we haven’t added the light yet. To add light or other rendering states, you can use setPassState() method from TerrainPass. Alternatively, you can use lightmap (texture that acts like the light) to produce light, but that method can only be used if we have the lightmap and the terrain shape is fixed, so we will ignore it for now.

This is what you will get after you add some render states to the TerrainPass. It’s nicer than before. In my application, I add LightState, CullState, and ZBufferState.

Adding Tools

Now let’s add some user interface for tools that will be used to modify the terrain. I use JToogleButton to create the tools. For the time being, I don’t have time to create the icon for the tools by myself, so I will use the ones found from Radakan’s world editor (http://code.google.com/p/radakan/).

This is what I have for now.

From left to right and top to bottom, they are view, raise, lower, flatten, smooth, noise, and set height. I can’t find a proper icon for set height, so I just put the text for now. The idea is to do something according to the active tool if we left click the terrain. Actually, the view button won’t do anything, it will only deactivate the other tool. I will cover about how each tool works in the next article.

If we activate the other tools, for example raise tool, and then click the terrain, we want the effect applies to a certain area, not just at the clicked point. In other world editor, we usually find they use some kind of brush to show the affected area on the terrain. The world editor user usually want to be able to control how big the affected area. I will add some controls using JSlider. The first is brush radius. This parameter will show the radius of the fully affected area. The second will control brush falloff. This parameter will show the radius of the farthest area that are still affected but with gradually reduced effect. The last one will be brush strength which will show how much it will affect the area.

Now we have to draw the brush. There are several ways to do this for example we can create a 3D plane and give it a texture or we can create a 3D lines as the brush. But we have to remember that our terrain won’t stay flat forever, it will change dynamically. So the brush should also can adapt to the terrain’s shape. Because of that, choosing to use lines would be a wise choice. Lines are more inexpensive to draw than textured plane. They are also easier to construct and may contain less visible artifact because of its intersection with the terrain. Here is the code to construct two circled lines on the terrain.

I make it so it creates new brush every time it want to move the brush to another place. It is easier to do than trying to move every points in the brush to the new location. So we need a method to remove the old brush.

Next step is to call those two methods above. We can create another method which will be called each time the mouse moves over the canvas. I call the method from mouseDragged(), mouseMoved(), and mouseWheelMoved() methods from the mouse handler. The method should do mouse picking to find the coordinate of the terrain which is pointed by the mouse. The old brush should be removed first and then we can draw a new brush. It’s pretty simple.

This is what your result should show

I will explain in depth about how we actually deform the terrain shape on the next article.

© William Salim – WS08-1 – Software Laboratory Center