private static void ExtractFromRootObject(FoliagePainterEditTime painter, GameObject root, bool disable, bool delete)
        {
            FoliageLog.Assert(root.transform.parent == null, "Must have root object!");

            List <GameObject> foliageInstances = new List <GameObject>();

            RecursivelyExtract(root, foliageInstances);

            int extracted = 0;

            // Process them and add them
            for (int i = foliageInstances.Count - 1; i >= 0; i--)
            {
                GameObject proto = foliageInstances[i];

                GameObject protoPrefab = PrefabUtility.GetPrefabParent(proto) as GameObject;

                if (painter.HasFoliageType(protoPrefab) == false)
                {
                    continue;
                }

                int             hash     = painter.GetFoliageTypeHash(protoPrefab);
                FoliageInstance instance = new FoliageInstance();

                // Populate the data

                // Get the world data
                Vector3    worldPosition = proto.transform.position;
                Vector3    worldScale    = proto.transform.localScale;
                Quaternion worldRotation = proto.transform.rotation;

                instance.m_Position = worldPosition;
                instance.m_Scale    = worldScale;
                instance.m_Rotation = worldRotation;

                // Add the foliage instance
                painter.AddFoliageInstance(hash, instance, root.name);
                extracted++;

                // Auto disable
                if (disable)
                {
                    proto.SetActive(false);
                }

                // Auto delete
                if (delete)
                {
                    GameObject.DestroyImmediate(proto);
                }
            }

            FoliageLog.i("Extracted objects: " + extracted + " from: " + root.name);
        }
 private static void WriteFoliageInstance(BinaryWriter a, FoliageInstance i, bool tree)
 {
     if (tree)
     {
         WriteBounds(a, i.m_Bounds);
         WriteVector3(a, i.m_Position);
         WriteQuaternion(a, i.m_Rotation);
         WriteVector3(a, i.m_Scale);
         WriteGuid(a, i.m_UniqueId);
     }
     else
     {
         WriteVector3(a, i.m_Position);
         WriteQuaternion(a, i.m_Rotation);
         WriteVector3(a, i.m_Scale);
     }
 }
    /**
     * Runs basic code to add a tree at runtime. You should implement your own logic and not call this each frame
     * since it will cause a severe FPS drop.
     */
    void Update()
    {
        if (Input.GetMouseButtonDown(0) == false)
        {
            return;
        }

        CritiasFoliage.FoliagePainterRuntime runtime = FindObjectOfType <CritiasFoliage.FoliagePainter>().GetRuntime;

        var types = runtime.GetFoliageTypes();

        CritiasFoliage.FoliageTypeRuntime treeType = default(CritiasFoliage.FoliageTypeRuntime);
        bool foundTree = false;

        for (int i = 0; i < types.Count; i++)
        {
            if (types[i].m_IsGrassType == false)
            {
                treeType  = types[i];
                foundTree = true;
            }
        }

        if (foundTree == false)
        {
            Debug.LogError("Could not find a tree type! Please add it in the inspector!");
            return;
        }

        RaycastHit hit;

        if (Physics.Raycast(transform.position, transform.forward, out hit, 100, ~0))
        {
            if (hit.collider)
            {
                CritiasFoliage.FoliageInstance inst = new CritiasFoliage.FoliageInstance();

                // All the data that we need to set
                inst.m_Position = hit.point;
                inst.m_Scale    = Vector3.one;
                inst.m_Rotation = Quaternion.Euler(0, Random.Range(0, 360), 0);

                runtime.AddFoliageInstance(treeType.m_Hash, inst);
            }
        }
    }
Esempio n. 4
0
        /** Add foliage instance. Only works for trees at runtime.  */
        public void AddFoliageInstance(int typeHash, FoliageInstance instance)
        {
            int keyHash = FoliageCell.MakeHash(instance.m_Position);

            FoliageCellDataRuntime cell;

            if (m_FoliageData.ContainsKey(keyHash) == false)
            {
                cell = new FoliageCellDataRuntime();

                FoliageCell fCell = new FoliageCell(instance.m_Position, false);

                // Set the standard data
                cell.m_Bounds   = fCell.GetBounds();
                cell.m_Position = fCell;

                // Empty subdivided data
                cell.m_FoliageDataSubdivided = new FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime> [0];

                // Empty type data
                cell.m_TypeHashLocationsRuntime = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> > [0];

                // Add the data
                m_FoliageData.Add(keyHash, cell);
            }

            cell = m_FoliageData[keyHash];

            // Check if we have all the data
            int idx = System.Array.FindIndex(cell.m_TypeHashLocationsRuntime, (x) => x.Key == keyHash);

            if (idx < 0)
            {
                System.Array.Resize(ref cell.m_TypeHashLocationsRuntime, cell.m_TypeHashLocationsRuntime.Length + 1);
                idx = cell.m_TypeHashLocationsRuntime.Length - 1;

                // Add the type
                cell.m_TypeHashLocationsRuntime[idx] = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> >(typeHash, new FoliageTuple <FoliageInstance[]>(new FoliageInstance[0]));
            }

            // We have the stuff
            System.Array.Resize(ref cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime, cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime.Length + 1);
            cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime[cell.m_TypeHashLocationsRuntime[idx].Value.m_EditTime.Length - 1] = instance;
        }
        private static FoliageInstance ReadFoliageInstance(BinaryReader a, bool tree)
        {
            FoliageInstance instance = new FoliageInstance();

            if (tree)
            {
                instance.m_Bounds   = ReadBounds(a);
                instance.m_Position = ReadVector3(a);
                instance.m_Rotation = ReadQuaternion(a);
                instance.m_Scale    = ReadVector3(a);
                instance.m_UniqueId = ReadGuid(a);
            }
            else
            {
                instance.m_Position = ReadVector3(a);
                instance.m_Rotation = ReadQuaternion(a);
                instance.m_Scale    = ReadVector3(a);
            }

            return(instance);
        }
Esempio n. 6
0
    /**
     * Runs basic code to remove a tree at runtime. You should implement your own logic and not call this each frame
     * since it will cause a severe FPS drop.
     *
     * NOTE: Even if the maximum distance is set to '1000' MAKE SURE that you also set the maximum distance of the collision data in the
     * 'Foliage Colliders' script to something larger than the default 7! Else colliders will only have 7m of colliders and therefore you
     * can remove only trees at 7m distance.
     */
    void Update()
    {
        if (Input.GetMouseButtonDown(0) == false)
        {
            return;
        }

        CritiasFoliage.FoliagePainterRuntime runtime = FindObjectOfType <CritiasFoliage.FoliagePainter>().GetRuntime;

        // Change this to whatever layer you use for trees
        const string queryLayer = "Default";

        RaycastHit hit;

        if (Physics.Raycast(transform.position, transform.forward, out hit, 1000, LayerMask.GetMask(queryLayer)))
        {
            if (hit.collider)
            {
                // Try and get the value if the collider is sticked here
                var data = hit.collider.gameObject.GetComponent <CritiasFoliage.FoliageColliderData>();

                if (data)
                {
                    CritiasFoliage.FoliageInstance instance = data.m_FoliageInstance;
                    runtime.RemoveFoliageInstance(data.m_FoliageType, instance.m_UniqueId, instance.m_Position);
                }

                // Try and get the value if maybe it is in the owner in case we are an owned collider
                data = hit.collider.gameObject.GetComponentInParent <CritiasFoliage.FoliageColliderData>();

                if (data)
                {
                    CritiasFoliage.FoliageInstance instance = data.m_FoliageInstance;
                    runtime.RemoveFoliageInstance(data.m_FoliageType, instance.m_UniqueId, instance.m_Position);
                }
            }
        }
    }
Esempio n. 7
0
        /**
         * Add a new foliage instance to the underlaying data.
         *
         * The painter should decide if our data is added to the subdivisions or if it is a tree type and must not be added to the subdivision.
         */
        public void AddInstance(int typeHash, FoliageInstance instance, bool subdivision, string label = FoliageGlobals.LABEL_PAINTED)
        {
            int hash = FoliageCell.MakeHash(instance.m_Position);

            if (m_FoliageData.ContainsKey(hash) == false)
            {
                FoliageCellData data = new FoliageCellData();
                data.m_Position = new FoliageCell();
                data.m_Position.Set(instance.m_Position);

                data.m_Bounds         = data.m_Position.GetBounds();
                data.m_BoundsExtended = data.m_Bounds;

                // Add the foliage cell
                m_FoliageData.Add(hash, data);
            }

            FoliageCellData cellData = m_FoliageData[hash];

            if (subdivision == false)
            {
                if (cellData.m_TypeHashLocationsEditor.ContainsKey(typeHash) == false)
                {
                    cellData.m_TypeHashLocationsEditor.Add(typeHash, new Dictionary <string, List <FoliageInstance> >());
                }

                var labeled = cellData.m_TypeHashLocationsEditor[typeHash];

                if (labeled.ContainsKey(label) == false)
                {
                    labeled.Add(label, new List <FoliageInstance>());
                }

                // Add the foliage data
                labeled[label].Add(instance);

                // Make the extended bounds larger. Encapsulate anything that might make the bounds larger. Only applied to trees
                m_FoliageData[hash].m_BoundsExtended.Encapsulate(instance.m_Bounds);
            }
            else
            {
                var foliageSubdividedData = cellData.m_FoliageDataSubdivided;

                Vector3 localPosition = GetLocalInCell(instance.m_Position, cellData);

                int hashSubdivided = FoliageCell.MakeHashSubdivided(localPosition);

                if (foliageSubdividedData.ContainsKey(hashSubdivided) == false)
                {
                    FoliageCellSubdividedData data = new FoliageCellSubdividedData();
                    data.m_Position = new FoliageCell();
                    data.m_Position.SetSubdivided(localPosition);

                    // Get bounds in world space
                    data.m_Bounds        = data.m_Position.GetBoundsSubdivided();
                    data.m_Bounds.center = GetWorldInCell(data.m_Bounds.center, cellData);

                    foliageSubdividedData.Add(hashSubdivided, data);
                }

                FoliageCellSubdividedData cellDataSubdivided = foliageSubdividedData[hashSubdivided];

                if (cellDataSubdivided.m_TypeHashLocationsEditor.ContainsKey(typeHash) == false)
                {
                    cellDataSubdivided.m_TypeHashLocationsEditor.Add(typeHash, new Dictionary <string, List <FoliageInstance> >());
                }

                var labeled = cellDataSubdivided.m_TypeHashLocationsEditor[typeHash];

                if (labeled.ContainsKey(label) == false)
                {
                    labeled.Add(label, new List <FoliageInstance>());
                }

                labeled[label].Add(instance);
            }
        }
        /**
         * Load from file the runtime version of the data
         */
        public static FoliageDataRuntime LoadFromFileRuntime(string filename)
        {
            FoliageData data = LoadFromFileEditTime(filename);

            // Build the runtime data from the edit time data
            FoliageDataRuntime runtime = new FoliageDataRuntime();

            foreach (var hashedCell in data.m_FoliageData)
            {
                FoliageCellData        editCell    = hashedCell.Value;
                FoliageCellDataRuntime runtimeCell = new FoliageCellDataRuntime();

                // Set the data. Note that we only need the extended bounds
                runtimeCell.m_Bounds   = editCell.m_BoundsExtended;
                runtimeCell.m_Position = editCell.m_Position;

                // Build the tree instance data
                int idx = -1;
                runtimeCell.m_TypeHashLocationsRuntime = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> > [editCell.m_TypeHashLocationsEditor.Count];
                foreach (var instances in editCell.m_TypeHashLocationsEditor)
                {
                    idx++;

                    List <FoliageInstance> allTreeInstances = new List <FoliageInstance>();
                    var labeledInstances = instances.Value;

                    // Build all the data from the labeled data
                    foreach (List <FoliageInstance> inst in labeledInstances.Values)
                    {
                        allTreeInstances.AddRange(inst);
                    }

                    // We will build the world matrix for trees
                    for (int i = 0; i < allTreeInstances.Count; i++)
                    {
                        FoliageInstance inst = allTreeInstances[i];
                        inst.BuildWorldMatrix();
                        allTreeInstances[i] = inst;
                    }

                    // Don't forget to trim all excess instances!
                    allTreeInstances.TrimExcess();

                    #if UNITY_EDITOR
                    if (allTreeInstances.Count == 0)
                    {
                        Debug.Assert(false, "Count 0!");
                    }
                    #endif

                    runtimeCell.m_TypeHashLocationsRuntime[idx] = new FoliageKeyValuePair <int, FoliageTuple <FoliageInstance[]> >(instances.Key, new FoliageTuple <FoliageInstance[]>(allTreeInstances.ToArray()));
                }

                // Build the grass instance data from the subdivided cells
                List <FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime> > foliageCellDataSubdivided = new List <FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime> >(editCell.m_FoliageDataSubdivided.Count);

                foreach (var hashedSubdividedCell in editCell.m_FoliageDataSubdivided)
                {
                    FoliageCellSubdividedData        editSubdividedCell    = hashedSubdividedCell.Value;
                    FoliageCellSubdividedDataRuntime runtimeSubdividedCell = new FoliageCellSubdividedDataRuntime();

                    // Set the data
                    runtimeSubdividedCell.m_Bounds   = editSubdividedCell.m_Bounds;
                    runtimeSubdividedCell.m_Position = editSubdividedCell.m_Position;

                    idx = -1;
                    runtimeSubdividedCell.m_TypeHashLocationsRuntime = new FoliageKeyValuePair <int, FoliageTuple <Matrix4x4[][]> > [editSubdividedCell.m_TypeHashLocationsEditor.Count];
                    foreach (var instances in editSubdividedCell.m_TypeHashLocationsEditor)
                    {
                        idx++;

                        List <FoliageInstance> allGrassInstances = new List <FoliageInstance>();
                        var labeledInstances = instances.Value;

                        foreach (List <FoliageInstance> inst in labeledInstances.Values)
                        {
                            allGrassInstances.AddRange(inst);
                        }

                        #if UNITY_EDITOR
                        if (allGrassInstances.Count == 0)
                        {
                            Debug.Assert(false, "Count 0!");
                        }
                        #endif

                        // Build the multi-array data
                        int           ranges  = Mathf.CeilToInt(allGrassInstances.Count / (float)FoliageGlobals.RENDER_BATCH_SIZE);
                        Matrix4x4[][] batches = new Matrix4x4[ranges][];

                        for (int i = 0; i < ranges; i++)
                        {
                            List <FoliageInstance> range = allGrassInstances.GetRange(i * FoliageGlobals.RENDER_BATCH_SIZE,
                                                                                      i * FoliageGlobals.RENDER_BATCH_SIZE + FoliageGlobals.RENDER_BATCH_SIZE > allGrassInstances.Count
                                    ? allGrassInstances.Count - i * FoliageGlobals.RENDER_BATCH_SIZE
                                    : FoliageGlobals.RENDER_BATCH_SIZE);

                            batches[i] = range.ConvertAll <Matrix4x4>((x) => x.GetWorldTransform()).ToArray();
                        }

                        // Set the data
                        runtimeSubdividedCell.m_TypeHashLocationsRuntime[idx] = new FoliageKeyValuePair <int, FoliageTuple <Matrix4x4[][]> >(instances.Key, new FoliageTuple <Matrix4x4[][]>(batches));
                    }

                    // Add the subdivided runtime cell
                    foliageCellDataSubdivided.Add(new FoliageKeyValuePair <int, FoliageCellSubdividedDataRuntime>(hashedSubdividedCell.Key, runtimeSubdividedCell));
                }

                // Build the subdivided data
                runtimeCell.m_FoliageDataSubdivided = foliageCellDataSubdivided.ToArray();


                // Add the runtime cell
                runtime.m_FoliageData.Add(hashedCell.Key, runtimeCell);
            }

            // Good for GC
            data = null;

            return(runtime);
        }
Esempio n. 9
0
 /**
  * Adds a new instance to the runtime data. Adding a foliage instance requires a 'typeHash'. One can be
  * retrieved either by 'AddFoliageType' at runtime, or by querying the type hash from the existing list
  * of added types at edit time, using 'GetFoliageTypes'.
  *
  * @param typeHash
  *          Foliage type hash that will be used for adding the foliage. Must exist in order to take effect
  * @param instance
  *          Foliage data. All the data this has to have set is the position, rotation and scale, the other is auto-set
  */
 public void AddFoliageInstance(int typeHash, FoliageInstance instance)
 {
     m_Painter.AddFoliageInstanceRuntime(typeHash, instance);
 }
        private static void ExtractDetailsFromTerrain(FoliagePainter painter, Terrain terrain, List <FoliageDetailExtracterMapping> mappings, bool disable, bool delete)
        {
            string label = FoliageGlobals.LABEL_TERRAIN_DETAILS_EXTRACTED + terrain.name;

            FoliagePainterEditTime edit = painter.GetEditTime;

            int detailMapSizeW = terrain.terrainData.detailWidth;
            int detailMapSizeH = terrain.terrainData.detailHeight;

            float patchSizeW = terrain.terrainData.size.x / detailMapSizeW;
            float patchSizeH = terrain.terrainData.size.z / detailMapSizeH;

            int extracted = 0;

            // Extract the types for all the mapping
            for (int m = 0; m < mappings.Count; m++)
            {
                int   layer   = mappings[m].m_DetailLayer;
                int   mapping = mappings[m].m_FoliageTypeHash;
                float density = mappings[m].m_ExtractedDensity;

                FoliageLog.Assert(painter.HasFoliageType(mapping), "Must have foliage hash!!");

                // Get the foliage type
                FoliageType     type  = painter.GetFoliageTypeByHash(mapping);
                DetailPrototype proto = terrain.terrainData.detailPrototypes[layer];

                // If we should align to the surface
                bool    followTerrainNormal = type.m_PaintInfo.m_SurfaceAlign;
                Vector2 alignPercentage     = type.m_PaintInfo.m_SurfaceAlignInfluence;

                // Get the terrain data
                int[,] data = terrain.terrainData.GetDetailLayer(0, 0, detailMapSizeW, detailMapSizeH, layer);

                // Iterate data
                for (int i = 0; i < detailMapSizeH; i++)
                {
                    for (int j = 0; j < detailMapSizeW; j++)
                    {
                        // j,i not i,j
                        int count = data[j, i];

                        if (count > 0)
                        {
                            // Minimum 1 never 0
                            count = Mathf.Clamp(Mathf.CeilToInt(count * density), 1, count + 1);

                            // Map from local space cell space to local terrain space
                            Vector2 cellMin = new Vector2(patchSizeW * i, patchSizeH * j);
                            Vector2 cellMax = cellMin + new Vector2(patchSizeW, patchSizeH);

                            for (int d = 0; d < count; d++)
                            {
                                Vector3 randomInCell;
                                randomInCell.x = Random.Range(cellMin.x, cellMax.x);
                                randomInCell.z = Random.Range(cellMin.y, cellMax.y);
                                randomInCell.y = 0;

                                randomInCell = FoliageTerrainUtilities.TerrainLocalToTerrainNormalizedPos(randomInCell, terrain);
                                float y = FoliageTerrainUtilities.TerrainHeight(randomInCell, terrain);

                                // Build the rotation
                                Quaternion rotation = Quaternion.identity;

                                if (followTerrainNormal)
                                {
                                    Quaternion slopeOrientation = Quaternion.LookRotation(FoliageTerrainUtilities.TerrainNormal(randomInCell, terrain)) * Quaternion.Euler(90, 0, 0);

                                    // How much we orient towards the slope
                                    rotation = Quaternion.Slerp(rotation, slopeOrientation,
                                                                Random.Range(alignPercentage.x, alignPercentage.y));
                                }

                                // Rotate around the Y axis
                                rotation *= Quaternion.Euler(0, Random.Range(0, 360), 0);

                                // Random in cell in world position
                                randomInCell   = FoliageTerrainUtilities.TerrainNormalizedToWorldPos(randomInCell, terrain);
                                randomInCell.y = y;

                                Vector3 scale;
                                float   x = Random.Range(proto.minWidth, proto.maxWidth);
                                scale.x = x;
                                scale.z = x;
                                scale.y = Random.Range(proto.minHeight, proto.maxHeight);

                                // Construct a foliage instance based on the foliage type data
                                FoliageInstance instance = new FoliageInstance();

                                // Build the foliage data based on the type
                                instance.m_Position = randomInCell;
                                instance.m_Rotation = rotation;
                                instance.m_Scale    = scale;

                                // Instantiate at a random pos in that cell
                                edit.AddFoliageInstance(mapping, instance, label);
                                extracted++;
                            }
                        }

                        // If we should delete
                        if (delete)
                        {
                            data[j, i] = 0;
                        }
                    }
                } // End detail array iteration

                // If we deleted set the new detail layer data
                if (delete)
                {
                    terrain.terrainData.SetDetailLayer(0, 0, layer, data);
                }
            } // End types iteration

            // If we should disable the trees and foliage draw
            if (disable)
            {
                terrain.drawTreesAndFoliage = false;
            }

            // If we deleted anything we need to save
            if (delete)
            {
                UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(terrain.gameObject.scene);
            }

            FoliageLog.i("Extracted details: " + extracted + " from: " + terrain.name);
        }
        private static void ExtractFromTerrain(FoliagePainter painterRaw, FoliagePainterEditTime painter, Terrain terrain, bool autoExtract, bool disable, bool delete)
        {
            string label = FoliageGlobals.LABEL_TERRAIN_EXTRACTED + terrain.name;

            // Ensure that we have all the required foliage types
            List <TreeInstance> terrainTreeInstances = new List <TreeInstance>(terrain.terrainData.treeInstances);

            TreePrototype[] terrainTreePrototypes = terrain.terrainData.treePrototypes;

            // Attempt to build the prefab's that we don't have
            if (autoExtract)
            {
                AutoExtractTypes(painter, terrain);
            }

            int extracted = 0;

            for (int i = terrainTreeInstances.Count - 1; i >= 0; i--)
            {
                GameObject proto = terrainTreePrototypes[terrainTreeInstances[i].prototypeIndex].prefab;

                if (painter.HasFoliageType(proto) == false)
                {
                    continue;
                }

                int         hash = painter.GetFoliageTypeHash(proto);
                FoliageType type = painterRaw.GetFoliageTypeByHash(hash);

                FoliageInstance instance = new FoliageInstance();

                // Populate the data

                // Get the world data
                float   YOffset       = Random.Range(type.m_PaintInfo.m_YOffset.x, type.m_PaintInfo.m_YOffset.y);
                Vector3 worldPosition = FoliageTerrainUtilities.TerrainNormalizedToWorldPos(terrainTreeInstances[i].position, terrain) + new Vector3(0, YOffset, 0); // YOffset too
                Vector3 worldScale    = new Vector3(terrainTreeInstances[i].widthScale, terrainTreeInstances[i].heightScale, terrainTreeInstances[i].widthScale);
                Vector3 worldRotation = new Vector3(0, terrainTreeInstances[i].rotation * Mathf.Rad2Deg, 0);

                instance.m_Position = worldPosition;
                instance.m_Scale    = worldScale;
                instance.m_Rotation = Quaternion.Euler(worldRotation.x, worldRotation.y, worldRotation.z);

                // Add the foliage instance
                painter.AddFoliageInstance(hash, instance, label);
                extracted++;

                // Delete the instance from the terrain if we have to
                if (delete)
                {
                    terrainTreeInstances.RemoveAt(i);
                }
            }

            if (disable)
            {
                terrain.drawTreesAndFoliage = false;
            }

            // If we should delete then delete the instance
            if (delete)
            {
                terrain.terrainData.treeInstances = terrainTreeInstances.ToArray();
                UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(terrain.gameObject.scene);
            }

            FoliageLog.i("Extracted objects: " + extracted + " from: " + terrain.name);
        }
 /**
  * Add a new foliage instance to the system. For the foliage instance
  * we only need to populate it's world position, rotation and scale
  * there's no need to populate the bounds matrix and GUID.
  *
  * @param typeHash
  *          Type hash of the foliage that we want to add
  * @param instance
  *          Foliage instance data containing world position, scale and rotation
  * @param label
  *          Optional label in case we want to add it to a different than 'hand painted' label. Usefull when
  *          we want to quickly remove all the foliage with a specified label from the list of foliages
  */
 public void AddFoliageInstance(int typeHash, FoliageInstance instance, string label = FoliageGlobals.LABEL_PAINTED)
 {
     m_Painter.AddFoliageInstance(typeHash, instance, label);
 }