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.
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.
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
World editor is an application that is used to create or compose every visual element in the other application. It is usually used to create game worlds (In this article, I assume ‘game’ will refer to ‘3D game’ unless it is specified). World editor is an important aspect of game development as it allows the game artists to set up the worlds as what they want them to be appeared in game. World editor itself is not a modeling tool, as it isn’t used to create the 3D models (though, it is used to create the terrain). In this article I will cover about how to combine java’s swing user interface with jMonkeyEngine (jME). I will assume you know the basic of java language, swing and jME.
This first step is quite easy. You only have to create the user interface with swing. You can design your own user interface. You just have to make sure that you have some place to put your editing tools and a canvas for your 3D viewport. For the canvas, we will use LWJGLCanvas which you can find at com.jmex.awt.lwjgl. It is inherited from java’s Canvas, so we can put it in our swing user interface.
It is the initial design of my UI. As you can see on the left side, I put JtabbedPane to place my editing tools, and on the right side I put LWJGLCanvas. For now, the canvas is still empty.
You will also need an implementor class to implement your jME process on the canvas. We will create a simple one. Actually, this class behaves like SimplePassGame class which you should have already familiar with (If you don’t know, you can assume it like BaseGame with pass rendering capability added). We use pass rendering because we will need it to render our terrain which will use TerrainPass.
Here is the code snippet. You should know what the statements in the simpleSetup() do.
For this article, we will create a simple scene to check if our canvas is correctly set. You can add jME code insde the implementor class that you just created. You may override some methods which are inherited from SimplePassCanvasImpl such as simpleSetup(), simpleUpdate(), and simpleRender(). I will create a simple box.
You can change the scene with anything you like. Actually, we will change it with the terrain later.
Adding Camera Movement
Now we have to add control to the scene. We have to make the user can control what they want see in their editor. We will add some movements such as panning, rotating, and zooming the scene.
The first thing to do is creating a new mouse handler for the canvas. We can use MouseAdapter to do this. It has every mouse action we need.
I will explain from the simplest one, zooming. First we have to know how much we want out camera to move forward or backward. Then we have to get the camera from our implementor class. Then get the distance from the camera to the focus position. Initially, the focus will be at the center of the world. This focus position will be used again when we do rotation and its position will change as we pan the camera. For this reason, we have to store the focus position to a variable. The distance is only needed if we want to restrict our zooming capability, in this case we don’t want the scene is zoomed too close or surpassing the focus position. If you want to restrict your zooming, recalculate the zooming amount so it becamo zero after the camera reaches certain position, otherwise you can use your initial amount. After that, we only have to scale the normalized camera direction with the zooming amount and store the result as the new camera position. Here is the code to do that.
Maybe you wonder how we get the zooming amount. The answer is very simple, from your mouse input. The truth is you can also use keyboard to do that, but I prefer to use mouse because it is easier to calculate and combined with the other camera transformation. I will use mouse wheel scroll amount which we can get from MouseAdapter method as presented below. I also add the condition if the wheel is scrolled when left shift is pressed down, the zooming amount will be only half from normal. You can experiment on your own the value that suits you. You will notice that I use GameTaskQueueManager in this method. We need that to ensure the thread in our jME application and the awt thread run correctly.
Next action is panning the camera. Actually, it’s not too different with zooming. The differences are panning needs two direction to move the camera and when we pan the camera, we also have to pan the focus position. Moving in two directions is just as easy as moving in one direction in zooming. We only have to get the moving amount for each direction and also camera’s up and left direction. Then scale each camera’s direction with the corresponding moving amount and add them together. This way, we get the required Vector3f to move the camera and the focus position. Just add the vector to camera to move the camera and add it to focus to move the focus. Here is the code
Now we arrive at the hardest part of the camera transformation, rotation. It requires you to understand the operation of quaternion, process to rotate object about any point in 3D world, and if you want to restrict the rotation elevation, you also have to know some trigonometry. In rotation we have to use two axes as a pivot, world’s up axis to rotate right and left and camera’s left axis to rotate up and down. But unlike panning, we can’t directly calculate two axes rotation, so we have to rotate it twice. First, we have to get the amount of rotation that we want to occur. Because we only want to restrict rotation to up and down direction, we only need to recalculate the amount when we rotate it about left axis of the camera. Here we use some trigonometry to find the elevation (I won’t explain it here, learn some trigonometry if you want to know). For the rotation, we have to get the vector to move the camera to the focus position. Then rotate the vector about the axis by the amount you have calculated before. Finally, move the vector back with the rotated vector. You can now move the camera by the resulting vector. And also don’t forget to make your camera to look at the focus again (moving it with the vector doesn’t rotate the camera direction). Here is the code snippet.
The next part is about how to call the two previous methods we wrote. I present them together because actually they get similar input from the mouse. The actions happen when the user drags the mouse. The difference is the mouse button which is clicked. For panning I use middle button, and for rotation I use right button.
This is what we got so far. We will start to create the terrain in the next article.
© William Salim – WS08-1 – Software Laboratory Center