Can’t paint Trees, etc onto your Unity Terrain?

I was scratching my head today as I suddenly wasn’t able to use the either the Terrain tools or GeNa to paint/stamp trees onto my Terrain. Turns out that Vegetation Studio Pro turns off the ability here;capture

 

Auto Convert Roads Types from Real World Terrain

Real World Terrain is a great Unity Asset to kick start a Unity Terrain based upon a real life location. One of the helpful features is that it will can also be used in conjunction with EasyRoads3D to generate a road network based on actual road data. However, you might find yourself in a situation were you’ve requested that all roads types (highways to dirt paths) are included but you end up with everything looking like 6 metre asphalt roads. If you’re in this situation then you can use the following script (or tweaked to your needs) to convert them to something more useful. NB It also sets the width of the road to that specified in the meta data.

Instructions:

  1. Make sure you have a road type you want to use in EasyRoads3D, e.g. Track
  2. Add the script to your ‘Road Objects’ game object
  3. Set the ‘Not From Road Surface’ to surface that you DO NOT want to be effected by the change, e.g. asphalt, i.e. please don’t change anything that has a surface of asphalt
  4. Set the ”To Road Type’ to the name of EasyRoads3D type your want, e.g. Track
  5. Check the ‘Should Run’ field
using System;
using System.Linq;
using EasyRoads3Dv3;
using InfinityCode.RealWorldTerrain.OSM;
using UnityEngine;

namespace CBC
{
    [ExecuteInEditMode]
    public class EasyRoads3DTypeMapper : MonoBehaviour
    {
        [SerializeField]
        string fromNotRoadSurface;
        
        [SerializeField]
        string toRoadType;

        [SerializeField]
        bool shouldRun = false;

        private void OnValidate()
        {
            if (shouldRun)
            {
                int roadsChanged = 0;
                try
                {
                    var roadNetwork = new ERRoadNetwork();
                    if (roadNetwork == null)
                    {
                        Debug.LogError("Unknown road network");
                        shouldRun = false;
                        return;
                    }

                    var erModularBase = GetComponentInParent();
                    var roadTypes = erModularBase.GetRoadTypes();
                    // DisplayRoadTypes(roadTypes);

                    var requestedRoadType = roadTypes.FirstOrDefault(rt => rt.roadTypeName.Equals(toRoadType, System.StringComparison.InvariantCultureIgnoreCase));
                    if (requestedRoadType == null)
                    {
                        Debug.LogError("Unknown road type " + toRoadType);
                        shouldRun = false;
                        return;
                    }

                    var realWorldTerrainOSMMetas = GetComponentsInChildren();
                    foreach (var rwtRoad in realWorldTerrainOSMMetas)
                    {
                        var erRoad = rwtRoad.GetComponent();
                        RealWorldTerrainOSMMetaTag surface = GetMetaInfo(rwtRoad, "surface");

                        var isTargetSurfaceForChange = surface == null || (surface != null && !surface.info.Equals(fromNotRoadSurface, System.StringComparison.InvariantCultureIgnoreCase));

                        var roadType = erRoad.GetRoadType(roadTypes);
                        if (isTargetSurfaceForChange && roadType != requestedRoadType)
                        {
                            var estWidthInfo = GetMetaInfo(rwtRoad, "est_width");
                            var estWidth = estWidthInfo?.info ?? "0";
                            var requestedWidth = (float)Convert.ToDouble(estWidth);

                            print(roadType?.roadTypeName ?? "No road type set");
                            print($"{surface?.info ?? "(surface not set)"} != {fromNotRoadSurface}");



                            print($"Changing {erRoad.name} {roadType?.roadTypeName ?? "not set"} to {requestedRoadType.roadTypeName}");
                            print($"Current width: {erRoad.GetRoadWidth()} Requested Width {estWidth} ");

                            var road = erRoad.road;
                            if (road == null)
                            {
                                road = roadNetwork.GetRoadByName(erRoad.name);
                                if (road == null)
                                {
                                    Debug.LogError("Road name not found in network");
                                }
                            }

                            if (road == null)
                            {
                                Debug.LogError("Road is missing, cannot change any details");
                            }
                            else
                            {
                                road.SetRoadType(requestedRoadType);
                                if (requestedWidth > 0f)
                                {
                                    road.SetWidth(requestedWidth);
                                }
                                roadsChanged++;
                            }
                        }
                    }
                }
                finally
                {
                    print($"Changed {roadsChanged} roads in this run");
                    shouldRun = false;
                }
            }
        }

        private void DisplayRoadTypes(ERRoadType[] roadTypes)
        {
            for (int x = 0; x  m.title == key);
        }

        private static void DisplayMetaInfo(RealWorldTerrainOSMMeta rwtRoad)
        {
            print($"metaInfo: {rwtRoad.metaInfo.Length}");
            for (int x = 0; x < rwtRoad.metaInfo.Length; x++)
            {
                print($"{rwtRoad.metaInfo[x].title} {rwtRoad.metaInfo[x].info}");
            }
        }
    }
}

Convert Vegetation Studio Pro’s Vegetation Mask to Biome Mask

Vegetation Masks (VM) can be a convenient way to allocate different vegetation types to an area. However, they don’t really play nicely with other Vegetation Studio features such as Vegetation Mask Lines (ML). The problem is that whatever vegetation you include in a VM will always override any attempts to use an ML to exclude vegetation. What does that all really mean? If you add a road to a scene then naturally you don’t want a huge tree in the middle of it. So you assign the road an ML and exclude all trees, simple. But if the trees are there because the road overlaps a VM then you’ll still have a forest in the middle of your road.

The answer to this is to use Biome Masks (BM). A BM fulfills a similar role, you can assign it different vegetation (along with lots of other options). A ML will correctly remove any vegetation from a BM and therefore you can have tree-free roads.

“Oh no, but I have created lots of VMs”
You may have a scene that you’ve painstakingly created using VMs, or perhaps you’ve used the Real World Terrain asset which generates VMs from the underlying texture (nice). If you have, then you can use the following script to convert VMs to BMs.

Instructions:

  1. Create your Biome in Vegetation Studio (Pro), remember the Biome type
  2. Create an empty game object in your scene for every type of Vegetation Mask; e.g. Grasses, Trees. This will be the Biome’s parent object
  3. Assign the script to the parent game object containing your VMs
  4. Select the Biome type from (1)
  5. Enter the “Starts With Mask” to find all the VMs that start with that name. So if you have VMs called “Grass 12344”, “Grass 54323”, etc, you would enter “Grass”
  6. Assign the associated empty game object from (2), i.e. drag that node into “Biome Parent”
  7. Repeat 3-6 for each type of conversation you want. E.g. another one with a “Starts With Mask” of “Tree”
  8. To run a conversation select the “Should Run” checkbox

Once run you should find the converted Biome Masks under the appropriate parent objects. The original Vegetation Masks are still there but have been disabled.

using System.Collections;
using System.Collections.Generic;
using AwesomeTechnologies.VegetationSystem;
using AwesomeTechnologies.VegetationSystem.Biomes;
using UnityEngine;

namespace CBC
{
    [ExecuteInEditMode]
    public class VegToBiomeMask : MonoBehaviour
    {
        [SerializeField]
        Transform biomeParent;

        [SerializeField]
        BiomeType biomeType;

        [SerializeField]
        string startWithMask;

        [SerializeField]
        bool shouldRun = false;
               
        void Update()
        {
            if (shouldRun)
            {
                var defaultGameObject = new GameObject();
                shouldRun = false;
                var childMasks = gameObject.GetComponentsInChildren();
                foreach(var childMask in childMasks)
                {
                    if (childMask.enabled && childMask.name.StartsWith(startWithMask))
                    {
                        var nodes = childMask.Nodes;
                        var biomeMaskGameObject = Instantiate(defaultGameObject, biomeParent.transform);
                        biomeMaskGameObject.transform.position = new Vector3(childMask.transform.position.x, childMask.transform.position.y, childMask.transform.position.z);
                        biomeMaskGameObject.name = "B" + childMask.name;
                        var biomeMaskArea = biomeMaskGameObject.AddComponent();
                        biomeMaskArea.ClearNodes();
                        foreach (var node in nodes)
                        {
                            biomeMaskArea.AddNode(new Vector3(node.Position.x, node.Position.y, node.Position.z));
                        }

                        // for some strange reason you have to reassign the positions again otherwise they all have an incorrect offset??
                        for (int x = 0; x < nodes.Count; x++)
                        {
                            var node = nodes[x];
                            var bNode = biomeMaskArea.Nodes[x];
                            bNode.Position = new Vector3(node.Position.x, node.Position.y, node.Position.z);
                        }

                        biomeMaskArea.BiomeType = biomeType;
                        childMask.enabled = false;
                    }
                }
            }
        }
    }
}