U3D Terrain Editor


#1

Some images as of Jan 2018

Some recent images:

Edit to the title as well; it suddenly wants a title to be 15 characters.

Edit:
Another recent image:

Edit:
Updated with some recent images.


While waiting for Azalrion to finish an overhaul of the DetourCrowd stuff scorvi proposed (wink wink, nudge nudge) I started working a little bit on a simple terrain editor for Urho3D. It’s pretty basic right now, only a few hours into it mostly on weekends when I’m not working IRL. You can find it at this clicky right here if you are interested in running rough, unpolished code. It’ll run from unmodified Urho3DPlayer if you like. Just copy the TerrainEditorData to the Bin directory and add it as a Resource Directory. (Disclaimer: not currently tested from a vanilla Urho3DPlayer; also not tested in OpenGL. If you try it and run into problems, let me know.) To run, execute the file LuaScripts/testterrainedit.lua. (Did I mention it requires Lua support to be built in?)

Additionally, it comes with the framework code for an enhanced player app that includes built in support for the vm branch of the Accidental Noise Library for noise function support.

The program supports a basic set of brushes: edit height, smooth height, 4 detail blend layers (using an enhanced version of the default TerrainBlend shader to support a detail layer in the alpha channel of the blend texture) masking and filters. The height editing is fairly reminiscent of the old Age of Mythology editor. It’s serviceable, but the real power of a thing like this is in procedural support which is where the Filters come in, accessible via the toolbar menu.

Filters are implemented as Lua scripts held in a certain directory that is scanned at startup. They specify parameters that can be tweaked, and upon a press of the execute button will perform their scripted action.

So far, I’ve only implemented a small handful of test filters: one to Cliffify terrain (ie, set terrain to a cliff texture based upon its steepness), one to generate a fractal-based mottled pattern of dirt and grass and one to generate a generic fractal-based terrain with some tweakable parameters. As I refine this thing (and especially as I use it for other projects) that filter list is certain to grow. In order to use the included filters, the Urho3D player needs to be built with the ANL noise support.

The program is really very simple. An editor component is implemented as a script object (it’ll probably be done as a C++ component very soon) exposing some methods for painting to the various layers, and a UI component instances the UI widgets and hooks them all up. The UI is currently VERY unpolished and will certainly change as time goes by. There are a few issues (such as changing filter parameters not being persistent between sessions at the filters window) but for the most part it is functional. As of the time of this initial posting, importing and exporting from PNGs is not fully supported; you can do an export of the terrain map by pressing ‘s’ and the blend map by pressing ‘d’. ‘a’ takes a screenshot. I’m working on the New Terrain and Import/Export dialogs pretty much as we speak, though.

Anyway, if you’re interested take a look. It’ll be a project I work on occasionally as I require features, but it won’t be a main focus item (especially once Azalrion gets moving).


Ready to use terrain editor?
How to use Texture2DArray?
Triplanar texturing with multiple materials on terrain
Exterrior level design
Performance hit on procedural terrain blending
Terrain tesselation based on material?
Call lua function from angelscript
#2

I’m going follow this. It seems like its something I am trying to implement on a different level.


#3

Cool. I will definetely take a closer look. At first glance I see your include dirs in the main CMakeLists.txt are still hardwired for Lua and tolua++. The latest Urho master branch already contains the changes you requested in Urho GitHub Issues sometime ago.


#4

This is Awesome! Many thanks for working on this :smiley:


#5

Great! Why isnt it part of the official editor? Because it is written in Lua? Is there going to be texture painting?


#6

I had thought about writing it as part of the editor, but decided not to because:

a) The editor is getting pretty complex, and I have only the tiniest familiarity with AngelScript and no desire to plunge into that codebase to integrate it, and
b) I decided I preferred a standalone approach rather than further complicating the editor. The editor UI is getting quite ‘busy’ and I didn’t really want to deal with all that just for simple height editing.

I’ve made a few recent commits. A quick hack to implement the basic editing tasks in C++ rather than Lua, for better performance. Modified the test filters to use the mask layer (toggled with a check flag). For example, you can paint an area with the mask tool:

then run the genericfbm fractal filter, with Use Mask selected, to apply the filter only to the masked area:

And the result is that the filter is applied only in the selected region:

Currently in the works is the ability to select a brush that will add checkpoints, or waypoints, to a list as you click. Filters can access the checkpoint list to do tasks such as road-building, ie by interpolating the curve formed by the checkpoints and smoothing/raising/lowering the terrain along the curve. Or tasks such as interpreting the checkpoint list as a closed region and converting it to a mask. Stuff like that.


#7

THANK YOU! :smiley:

I heavily rely on terrains for my Project and this is editor is going to be a huge time saver!


#8

Aboutr the editor UI getting too crowded, I have been thinking to propose to separate editor functions like Torque does. Torque editor has different screens for different tools: one mode for UI editing, one for scene editing, one for terrain, and so on.


#9

I’ve been working on a draft of a Road Builder filter. The current waypoint system lacks a UI; you add waypoints with the W key and remove with the Q key. When I get to polish stage I’ll add a real UI for it. The Road Builder filter requires at least 4 waypoints (to feed to a cubic spline). So once you have built a terrain, you use W to add a few waypoints:

The road builder filter gives you a few options to control things such as road width, fadeout distance of the road bed and paving texture, which detail texture layer to use for the paving, and the number of steps to tessellate each segment of the spline.

After tweaking the parameters to your taste, hit execute and wait for a little while. (The procedure is currently non-optimized, requiring three rasterization passes.) And the result:

Grab the smooth brush and go to town on the rougher spots to make it nicer:

Note that you do have to consider your waypoint placement carefully. The filter gets elevation heights at the spline knots (waypoints) and interpolates between them, so it will happily bridge and carve the hell out of things:

Working on this brings back memories, as I am digging through my archives all the way back to 2005 to a nearly identical project I was working on using a custom engine. Urho3D makes it a lot easier to add new features, though.


#10

Brilliant! :smiley:

Many thanks JTippetts for working on this ( i know i said that already but this is great! ) :smiley:


#11

Hello

How are you calculating the matterials? It is something I need badly. I’m looking all over the place. I’m not sure how to modify shaders code to do it with a procedural terrain.

Vivienne

[quote=“JTippetts”]I’ve been working on a draft of a Road Builder filter. The current waypoint system lacks a UI; you add waypoints with the W key and remove with the Q key. When I get to polish stage I’ll add a real UI for it. The Road Builder filter requires at least 4 waypoints (to feed to a cubic spline). So once you have built a terrain, you use W to add a few waypoints:

The road builder filter gives you a few options to control things such as road width, fadeout distance of the road bed and paving texture, which detail texture layer to use for the paving, and the number of steps to tessellate each segment of the spline.

After tweaking the parameters to your taste, hit execute and wait for a little while. (The procedure is currently non-optimized, requiring three rasterization passes.) And the result:

Grab the smooth brush and go to town on the rougher spots to make it nicer:

Note that you do have to consider your waypoint placement carefully. The filter gets elevation heights at the spline knots (waypoints) and interpolates between them, so it will happily bridge and carve the hell out of things:

Working on this brings back memories, as I am digging through my archives all the way back to 2005 to a nearly identical project I was working on using a custom engine. Urho3D makes it a lot easier to add new features, though.[/quote]


#12

The material simply uses the TerrainBlendEdit technique which is a slight modification of the default TerrainBlend technique. The modifications I made to the shader can be seen here and include 1) Adding a 4th detail texture, mapped by the alpha channel of the blend texture and 2) Adding visualization for the mask texture. If you do not need the mask, the TerrainBlend4 shader includes only the 4th detail texture addition to the default TerrainBlend shader.


#13

Ah. I just have to figure out how to convert the terrain to a RGB weightmap or convert the BG(bw) to RGB(rgba) weight map. It looks like it doesn’t work by slope if I read it right (TerrainBlend4.). I might be wrong.

I found this thread.
truevision3d.com/forums/prin … pic=5652.0

Couldn’t something like that be used to help create a slope based height?

I don’t see anything in the HLSL shader that could be used to get height information from different points from geomotry?


#14

No, the shader doesn’t work by slope. It doesn’t work by elevation or any hard-coded rules. It doesn’t need to; you can do all of that stuff outside the shader. It doesn’t make much sense to do it inside the shader because 1) You might want to have some places where slope DOESN’T affect the detail texture and 2) Sampling the normal from the heightmap itself might result in an incorrect normal if you don’t account for the values passed to Terrain::SetSpacing().

The shader merely uses the blend texture to determine what detail textures to draw for a given fragment; the contents of the blend texture are entirely up to you. You can set a texel in the blend map with a value derived from the slope of the terrain normal, or with one derived from the elevation, or with one derived from a noise function, or with one derived from the phase of the moon. It doesn’t matter how you generate the blend values.

For example, look at how the cliffify filter works. It iterates on x and y through the image associated with the blend texture, and for each texel it calculates the World coordinates of the respective location, and uses those world coordinates to query the Terrain directly for the normal at that location. A simple dot product with the vertical vector (and some additional calculation to apply cutoff and fade values) and the result is a value that can be used to Lerp between whatever color is already in the blend texture and the color representing the detail layer for cliff texture. And just like that, you have cliffs. No monkeying with the shader itself is necessary.


#15

Hello

I kinda get what you are saying. Thanks for your information.

I find these two links online. Are they good examples of what you mean Dot and Lerp?

Vivienne

Lerp is bassically keithmaggio.wordpress.com/2011/ … and-nlerp/
Dot product concept rosettacode.org/wiki/Dot_product


#16

Sure, those look about right.

The purpose of the dot product is to determine how steep the terrain is. If you take the dot product of the terrain normal and the vector representing “UP” the result is the cosine of the angle between the vectors (assuming both vectors are of length 1, or unit length). So if the normal is parallel with UP, the dot product will equal 1 ( or cosine(0)) whereas if the normal is perpendicular to UP, the dot product will be equal to 0 (cosine(90)). Thus, the dot product gives you a good function for “steepness” of terrain. As the terrain gets steeper, the dot product gets closer to 0.

Now, with this dot product in hand you are able to use it to interpolate (lerp) between colors. The most basic operation would be to use it as-is, but that doesn’t necessarily get you a good cliff. That gets the cliff texture spread across the entire continuum of normals, lighter on the more horizontal spots and fading in gradually to full cliff at the vertical spots. So instead, I apply some math to specify a threshold cutoff. Any slope below this threshold gets no cliff terrain blended in. Anything above the cutoff gets full cliff. I also specify a fade value that lets me gradually fade from cliff to non-cliff in a narrow band centered on the cutoff threshold. This softens the cutoff as desired.

If Use Mask is enabled for the cliffify filter, then I can scale the final Cliff value with the mask value, enabling me to mask out places where I don’t want there to be cliff even if the terrain is steep enough for it.


#17

I did this code so far. I’m just not sure about if I should be doing per vertices.

float cutoff(float inputvalue, float pointmid, float range)
{

    /// Create valuables to calculate
    float midpoint=pointmid;

    float midpoint_low=midpoint-range;
    float midpoint_max=midpoint+range;

    float midpoint_range;
    float result;

    if(midpoint_low<0){ midpoint_low=0;}

    if(midpoint_max>1){midpoint_max=1;}

    midpoint_range=midpoint_max-midpoint_low;

    /// Calculate value to range

    if(inputvalue<midpoint_low)
    {
        result=0;
    }
    else if(inputvalue>midpoint_max)
    {
        result=1;
    }
    else
    {
        result=(inputvalue-midpoint_low)/midpoint_range;
    }

    return result;
}

int main()
{

    /// Get terrain size
    IntVector2 terrainSizeV2 = terrain-> GetNumVertices ();
    Vector3 terrainSize;
    
    terrainSize.x_= terrainSizeV2.x_;
    terrainSize.y_= terrainSizeV2.y_;
    
    
    /// loop vertices
    for(ix=0; ix<terrainSize.x_; ix++)
    {
        for(iy=0; iy<terrainSize.y_; iy++)
        {
            /// get steepness
            Vector3 normalvalue=terrain ->GetNormal(worldposition);
            float steep=math.abs(normalvalue.DotProduct(Vector3(0,1,0)));

                                 float percentlerp=cutuff(steep,.5,.2);
                                 
                                 
            /// Choose between material, blend, color. Not sure yet.
             }
       }

      return 1;
}

#18

There is no restriction that the blend texture be the same resolution as the heightmap texture. In fact, you probably get a better visual result (in my opinion) if you use a blend texture with a larger resolution than the heightmap. So rather than iterate on the heightmap size, you’ll want to iterate on the blend map size. If you do that, though, then you will need a way to convert the blend map coordinate to an actual real-world location in order to get the terrain normal. For this, I wrote the NormalizedToWorld() function which takes a pair of coordinates in the range 0 to 1 and converts them to a world location. The function directly rips off the way the Terrain component internally does it. To use, simply divide the blend map pixel coordinate by the image dimensions (to get the normalized coordinates) then pass to the NormalizedToWorld() function to obtain world coordinates.

(Converting between spaces like this is about the trickiest part. In the editor, the heightmap blend map and mask can all have different resolutions, so I am constantly converting. Luckily, the Image class makes sampling from other spaces easy with the GetPixelBilinear() method, which takes normalized input coords to start with.)


#19

I used your code a little then I’m going add my function… The code I put was. You can tell me if its correct so far. I put steepness = 1- so 0 can the flatter and 1 would be the highest steepness.

Does that make any sense?

    /// Testing
    float bw=2048.0f;
    float bh=2048.0f;
    float x=1024.0f;
    float y=1024.0f;

    /// Get steepness
    Vector2 nworld=Vector2(x/bw, y/bh);
    Vector3 worldvalue=NormalizedToWorld( producedHeightMapImage,terrain,nworld);
    Vector3 normalvalue=terrain ->GetNormal(Vector3(wordvalue));
    float steep=1.0f -abs(normalvalue.DotProduct(Vector3(0,1,0)));

    cout << steep << " "<< normalvalue.x_<< " " << normalvalue.y_ << " " << normalvalue.z_;

#20

I’ve gotten some performance issues worked out, so I’ve started working on the TODO list to start polishing this thing. Things on the TODO list include a UI revamp, a modal option to switch to a first person view to explore the map from the ground, a revamp of the filter options to include a slider option type, implementation of a Navigation mesh test so that you can see how navigable the heightmap is (This option will construct a navmesh and display its debug geometry as a partially transparent overlay so that you can see areas of movability and make any necessary changes), implementation of a preview mode for waypoint line lists plus a UI and editing options for waypoints (ability to drag waypoints around, etc… and a preview mode that shows the waypoint linelist/curve as a partially transparent ribbon, updated in real time as waypoints are added or moved; this should help visualize roads better as they are built), real file save/load dialogs so I can quit using hotkeys, a real New Terrain dialog with appropriate options. And a few other things.