private bool IsCellEmpty(FoliageCellData cell)
        {
            // Iterate subdivisions
            foreach (FoliageCellSubdividedData cellSubdiv in cell.m_FoliageDataSubdivided.Values)
            {
                if (IsSubCellEmpty(cellSubdiv) == false)
                {
                    return(false);
                }
            }

            // Iterate divisions
            foreach (var data in cell.m_TypeHashLocationsEditor.Values)
            {
                foreach (var instances in data.Values)
                {
                    // If we have a count return that we are not empty
                    if (instances.Count > 0)
                    {
                        return(false);
                    }
                }
            }

            return(true);
        }
        private void RemoveEmptyTypeDataCell(FoliageCellData data)
        {
            if (data.m_TypeHashLocationsEditor.Count > 0)
            {
                HashSet <int> removeTypes = null;

                // Remove labeled data
                foreach (var pair in data.m_TypeHashLocationsEditor)
                {
                    Dictionary <string, List <FoliageInstance> > labeled = pair.Value;
                    HashSet <string> removeLabels = null;

                    // Iterate through the label data
                    foreach (var pairLabeled in labeled)
                    {
                        if (pairLabeled.Value.Count <= 0)
                        {
                            if (removeLabels == null)
                            {
                                removeLabels = new HashSet <string>();
                            }

                            removeLabels.Add(pairLabeled.Key);
                        }
                    }

                    // Labels to remove
                    if (removeLabels != null)
                    {
                        foreach (string label in removeLabels)
                        {
                            labeled.Remove(label);
                        }
                    }

                    // If we ended up with an empty list of data
                    if (labeled.Count <= 0)
                    {
                        if (removeTypes == null)
                        {
                            removeTypes = new HashSet <int>();
                        }

                        removeTypes.Add(pair.Key);
                    }
                }

                if (removeTypes != null)
                {
                    foreach (int type in removeTypes)
                    {
                        data.m_TypeHashLocationsEditor.Remove(type);
                    }
                }
            }
        }
        private static void ReadFoliageCellData(BinaryReader a, out int key, out FoliageCellData data)
        {
            // Read key
            key = a.ReadInt32();

            // Read foliage cell data
            data = new FoliageCellData();

            // Read bounds and position
            data.m_Bounds         = ReadBounds(a);
            data.m_BoundsExtended = ReadBounds(a);
            data.m_Position       = ReadFoliageCell(a);

            {
                // Read inner cell data
                int entries = a.ReadInt32();
                for (int i = 0; i < entries; i++)
                {
                    int dataKey = a.ReadInt32();

                    // Add the type
                    data.m_TypeHashLocationsEditor.Add(dataKey, new Dictionary <string, List <FoliageInstance> >());

                    // Read the foliage instances
                    int entriesLabeled = a.ReadInt32();
                    for (int j = 0; j < entriesLabeled; j++)
                    {
                        string labeledKey = a.ReadString();
                        List <FoliageInstance> instances = ReadListFoliageInstance(a, true);

                        data.m_TypeHashLocationsEditor[dataKey].Add(labeledKey, instances);
                    }
                }
            }

            {
                // Read the subdivided data
                int subdivided = a.ReadInt32();
                for (int j = 0; j < subdivided; j++)
                {
                    int subdivKey;
                    FoliageCellSubdividedData subdivData;

                    ReadFoliageCellDataSubdivided(a, out subdivKey, out subdivData);
                    data.m_FoliageDataSubdivided.Add(subdivKey, subdivData);
                }
            }
        }
        private static void WriteFoliageCellData(BinaryWriter a, int key, FoliageCellData data)
        {
            // Write key
            a.Write(key);

            // Write foliage cell data

            // Write bounds and position
            WriteBounds(a, data.m_Bounds);
            WriteBounds(a, data.m_BoundsExtended);
            WriteFoliageCell(a, data.m_Position);

            {
                // Write inner cell data
                a.Write(data.m_TypeHashLocationsEditor.Count);
                foreach (int dataKey in data.m_TypeHashLocationsEditor.Keys)
                {
                    // Write data
                    a.Write(dataKey);
                    var labeled = data.m_TypeHashLocationsEditor[dataKey];

                    // Write foliage instances
                    a.Write(labeled.Count);
                    foreach (string dataKeyLabel in labeled.Keys)
                    {
                        a.Write(dataKeyLabel);
                        WriteListFoliageInstance(a, labeled[dataKeyLabel], true, true);
                    }
                }
            }

            {
                // Write subdivided data
                a.Write(data.m_FoliageDataSubdivided.Count);
                foreach (int subdivKey in data.m_FoliageDataSubdivided.Keys)
                {
                    WriteFoliageCellDataSubdivided(a, subdivKey, data.m_FoliageDataSubdivided[subdivKey]);
                }
            }
        }
        /**
         * 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);
            }
        }
        /**
         * Removes foliage instances.
         *
         * @return True if we removed anything
         */
        public bool RemoveInstances(int typeHash, Vector3 position, float radius = 0.3f /*30cm default position delta */)
        {
            Vector3 min = position - new Vector3(radius, radius, radius);
            Vector3 max = position + new Vector3(radius, radius, radius);

            bool anyRemoved      = false;
            bool anyGrassRemoved = false;

            float x, y, z;
            float distanceDelta = radius * radius;

            // Remove all the foliage that overlaps the sphere
            FoliageCell.IterateMinMax(min, max, false, (int hash) =>
            {
                if (m_FoliageData.ContainsKey(hash) == false)
                {
                    return;
                }

                FoliageCellData cell = m_FoliageData[hash];

                // Remove the types from the cell
                if (cell.m_TypeHashLocationsEditor.ContainsKey(typeHash))
                {
                    var labeledData = cell.m_TypeHashLocationsEditor[typeHash];

                    foreach (var instances in labeledData.Values)
                    {
                        for (int i = instances.Count - 1; i >= 0; i--)
                        {
                            x = instances[i].m_Position.x - position.x;
                            y = instances[i].m_Position.y - position.y;
                            z = instances[i].m_Position.z - position.z;

                            // If we are at a distance smaller than the threshold then remove the instance
                            if ((x * x + y * y + z * z) < distanceDelta)
                            {
                                instances.RemoveAt(i);

                                // We removed from the cell, we have to recalculate the extended bounds
                                anyRemoved = true;
                            }
                        }
                    }
                }

                // Remove the types from the subdivided cells

                // Iterate subdivisions
                Vector3 minLocal = GetLocalInCell(min, cell);
                Vector3 maxLocal = GetLocalInCell(max, cell);

                FoliageCell.IterateMinMax(minLocal, maxLocal, true, (int hashLocal) =>
                {
                    FoliageCellSubdividedData cellSubdivided;

                    if (cell.m_FoliageDataSubdivided.TryGetValue(hashLocal, out cellSubdivided) == false)
                    {
                        return;
                    }

                    // Count all the grass foliage that overlaps the sphere
                    if (cellSubdivided.m_TypeHashLocationsEditor.ContainsKey(typeHash))
                    {
                        var data = cellSubdivided.m_TypeHashLocationsEditor[typeHash];

                        foreach (var instances in data.Values)
                        {
                            for (int i = instances.Count - 1; i >= 0; i--)
                            {
                                x = instances[i].m_Position.x - position.x;
                                y = instances[i].m_Position.y - position.y;
                                z = instances[i].m_Position.z - position.z;

                                // If we are at a distance smaller than the threshold then remove the instance
                                if ((x * x + y * y + z * z) < distanceDelta)
                                {
                                    instances.RemoveAt(i);

                                    // Just for grass removal...
                                    anyGrassRemoved = true;
                                }
                            }
                        }
                    }

                    RemoveEmptyTypeDataCellSubdivided(cellSubdivided);
                    if (IsSubCellEmpty(cellSubdivided))
                    {
                        cell.m_FoliageDataSubdivided.Remove(hashLocal);
                    }
                });

                // If we removed everything from a cell then clear it from the list completely
                RemoveEmptyTypeDataCell(cell);
                if (IsCellEmpty(cell))
                {
                    m_FoliageData.Remove(hash);
                }
            });

            if (anyRemoved)
            {
                RecalculateBoundsAfterRemove();
            }

            return(anyRemoved || anyGrassRemoved);
        }
 /** Get a position in world space that is inside a cell. */
 private Vector3 GetWorldInCell(Vector3 localPosition, FoliageCellData cell)
 {
     return(localPosition + cell.m_Bounds.min);
 }
 /** Get a position in local space inside a cell. */
 private Vector3 GetLocalInCell(Vector3 worldPosition, FoliageCellData cell)
 {
     return(worldPosition - cell.m_Bounds.min);
 }
        /**
         * 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);
        }