Skip to content

Latest commit

 

History

History
184 lines (139 loc) · 7.72 KB

terrain.md

File metadata and controls

184 lines (139 loc) · 7.72 KB

Terrain generator

Terrain generation is one of the most complex parts in utymap and it is essential to understand how it is working.

Table of content

General idea

You might expect that terrain generator contains some separate logic for roads, water, other flat areas, but the core idea is that it does NOT know anything about what types of object it generates. Instead it abstracts them by region concept which is just a 3D surface with additional properties.

Terrain Regions

As mentioned above, terrain region is represented by 2D surface:

struct Region final {
    /// Layer flag. If it is set all regions with such flag should be merged together.
    bool isLayer() const;
    /// Level value: zero for objects on terrain surface.
    int level;
    /// Area of polygon.
    double area;
    /// Context is optional: might be empty if region is part of a layer.
    /// In this case, context is read from canvas definition.
    std::shared_ptr<const RegionContext> context;
    /// Geometry of region.
    ClipperLib::Paths geometry;
}

Each region has a region specific properties such as area, geometry, etc. and shared style properties from a context:

/// Region context encapsulates information about region style.
struct RegionContext final {
  const utymap::mapcss::Style style;
  /// Prefix in mapcss.
  const std::string prefix;

  const utymap::builders::MeshBuilder::GeometryOptions geometryOptions;
  const utymap::builders::MeshBuilder::AppearanceOptions appearanceOptions;
}

All style properties are known by MeshBuilder and has to be defined in mapcss for region objects:

area|z9-16[kind=park] {
	clip: true;
    builder: terrain;
    mesh-name: terrain_park;
	max-area: 0.25%;

    color: gradient(#98fb98, #064807 10%, #035804 50%, #808080);
    color-noise-freq: 0;
    ele-noise-freq: 0;

    texture-index: 0;
    texture-type: background;
    texture-scale: 50;
}

Declaration meaning:

  • [Optional] clip: should region be clipped by tile borders? Default is false.
  • [Required] builder: builder name.
  • [Optional] terrain__layer: a name of terrain layer (see Terrain Layers). If it is defined, other properties are ignored.
  • [Optional] mesh-name: name of generated mesh. If it is not present than default name is used.
  • [Required?] max-area: triangulation setting for max triangle area relative to tile area.
  • [Optional] height_offset: height offset of any point of region. Default is 0. Note, as all points are shifted either up or down, some additional points are inflated on sides to avoid possible gaps.
  • [Required] color: fill color represented by gradient or color. Hex and name representations are supported.
  • [Optional] color-noise-freq: color noise in absolute values. Default is 0.
  • [Optional] ele-noise-freq: elevation noise in absolute values. Default is 0.
  • [Required] texture_index: texture index provided by @texture definitions. See [Textures](#Using textures) section for details.
  • [Required] texture-type: texture type inside given texture.
  • [Required] texture-scale: texture scale ratio relative to tile size.

Terrain layers

Multiple regions can be merged into one layer object to build single mesh from different objects by using terrain_layer declaration: terrain_layer: road;

In this case, declarations have to be defined in canvas rule with the corresponding prefix and additional sorting declaration:

canvas|z16 {
    road-max-area: 0.1%;
    road-color-noise-freq: 0;
    road-ele-noise-freq: 0;
    road-color: gray;
    road-sort-order: 1008;
    road-mesh-name: terrain_road;
    road-height-offset:-0.3m;
}

Z-fighting problem solution

As it is possible that different regions overlap, it is important to understand how terrain builder avoids z-fighting problem.

In general, this problem is solved with z-index usage. However, utymap supports z-index setting only for layers: sort-order definition is used for that. For other regions the algorithm sorts them according to their area in ascending order so that smaller region is processed first.

After region or layer is processed, its polygon shape is clipped from background region and all others non-processed polygons. Last step is to render the rest as terrain background mesh to avoid gaps. Background is represented by unnamed layer with sort-order 0, so you can tweak its style in canvas definition.

Using textures

You can use textures for regions as described in Using Textures

Elevation

By default, most of the scenes use Flat elevation provider which always returns the same height for the all geocoordinates. You can use real elevation data by specifying Grid (recommended) or SRTM elevation provider when tile is loaded. Under hood, utymap unity library tries to download and cache elevation data from mapzen (Grid) or NASA (SRTM) servers.

Check Elevation scene for working example.

Clipping bounds

Terrain is always rendered as square and element's bounds will be clipped if they are outside quadkey bounding box. To avoid storing extra points, there is specific clip declaration available. If it specified, element geometry will be clipped during data import procedure.

Terrain extras

For some complex objects, it might be not enough to generate just surface mesh. For example, let's say we want to generate a forest with uniformly distributed tree. Of course, it is possible to do it using extension mechanism provided by managed library. However, it might be expensive due to extra data marshaling or/and rebuilding polygon mesh. That's why utymap core library supports mesh extra mechanism by mesh-extra declaration.

Mesh extra is defined by ExtrasFuncs map inside SurfaceGenerator class:

const std::unordered_map<std::string, TerraExtras::ExtrasFunc> ExtrasFuncs =
    {
        {"forest", std::bind(&TerraExtras::addForest, _1, _2)},
        {"water", std::bind(&TerraExtras::addWater, _1, _2)},
    };
};

where map key is name of extras and value is function defined the following way:

/// Specifies Extras function signature.
typedef std::function<void(const utymap::builders::BuilderContext &, TerraExtras::Context &)> ExtrasFunc;

As you can see, there are two extras already.

Forest

Builds a forest using lsystem for tree generation. Here is example of mapcss rule:

area|z16[leisure=garden] {
    color: gradient(#98fb98, #adff2f 5%, #006400 10%, #556b2f 80%, #b8860b);
    texture-type: grass;

    mesh-extras: forest;
    mesh-name: terrain_park;
    tree-frequency: 60;
    tree-chunk-size: 30;

    lsys: tree;
    lsys-size: 1m;
    lsys-colors: brown,green;
    lsys-texture-indices: 0,0;
    lsys-texture-types: background,tree;
    lsys-texture-scales: 200,50;
}

For more details about lsystem generation, check Tree generation via L-System section.

Water

Do nothing so far as the water is emulated by height-offset setting

What's about roads, railways?

So far, there is nothing special about them: roads and railways are represented by specific layers. It is not optimal as there is no nice road connection, crossroads, signs. This can be implemented theoretically by specific mesh extra or element builder extension. It is in long-term TODO list.