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); }