/** Fastest removal */ public void RemoveFoliageInstance(int typeHash, System.Guid guid, Vector3 position) { FoliageCellDataRuntime cell; if (m_FoliageData.TryGetValue(FoliageCell.MakeHash(position), out cell)) { RemoveFoliageInstanceCell(typeHash, guid, cell); } }
public static void IterateMinMax(Vector3 min, Vector3 max, bool subdivided, FoliageIterationAction action) { FoliageCell cMin = new FoliageCell(min, subdivided); FoliageCell cMax = new FoliageCell(max, subdivided); for (int x = cMin.x; x <= cMax.x; x++) { for (int y = cMin.y; y <= cMax.y; y++) { for (int z = cMin.z; z <= cMax.z; z++) { action(MakeHash(x, y, z)); } } } }
/** * Remove the foliage with the marked GUID. Not supported for grass since * we are not having that data for it. */ public void RemoveInstanceGuid(int typeHash, Vector3 position, System.Guid guid) { FoliageCellData cell; if (m_FoliageData.TryGetValue(FoliageCell.MakeHash(position), out cell) == false) { return; } bool anyRemoved = false; // Look through the cell if (cell.m_TypeHashLocationsEditor.ContainsKey(typeHash)) { var labeled = cell.m_TypeHashLocationsEditor[typeHash]; foreach (var instances in labeled.Values) { for (int i = 0; i < instances.Count; i++) { if (instances[i].m_UniqueId == guid) { instances.RemoveAt(i); anyRemoved = true; // Yea, I know. Don't use goto... However we want to break all the iterations goto finished; } } } // Foreach labeled finished finished :; } // If we removed everything from a cell then clear it from the list completely RemoveEmptyTypeDataCell(cell); if (IsCellEmpty(cell)) { m_FoliageData.Remove(cell.GetHashCode()); } if (anyRemoved) { RecalculateBoundsAfterRemove(); } }
/** 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; }
public override bool Equals(object obj) { FoliageCell other = (FoliageCell)obj; if (other.x != x) { return(false); } if (other.y != y) { return(false); } if (other.z != z) { return(false); } return(true); }
/** * The depth should be around 1 or maximum 2 at runtime. */ public static void IterateNeighboring(FoliageCell cell, int depth, FoliageIterationAction action) { int minX = cell.x - depth; int maxX = cell.x + depth; int minY = cell.y - depth; int maxY = cell.y + depth; int minZ = cell.z - depth; int maxZ = cell.z + depth; for (int x = minX; x <= maxX; x++) { for (int y = minY; y <= maxY; y++) { for (int z = minZ; z <= maxZ; z++) { action(MakeHash(x, y, z)); } } } }
public static void GenerateBillboards(Bounds bounds, FoliageCell cell, GameObject owner, List <FoliageInstance> trees, FoliageType type, bool addLodGroup, float screenFadeSize, bool animatedCrossFade) { int[] originalTriangles = m_SystemQuadTriangles; GameObject meshObj = new GameObject(); // Mark object as static #if UNITY_EDITOR GameObjectUtility.SetStaticEditorFlags(meshObj, StaticEditorFlags.OccludeeStatic | StaticEditorFlags.ReflectionProbeStatic); #endif string name = string.Format("MeshCell[{0}_{1}]", cell.GetHashCode(), type.m_Prefab.name); Transform existing = owner.transform.Find(name); if (existing != null) { #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlaying == false) { Object.DestroyImmediate(existing.gameObject); } else { Object.Destroy(existing.gameObject); } #else Object.Destroy(existing.gameObject); #endif existing = null; } meshObj.transform.SetParent(owner.transform); meshObj.name = name; var data = type.m_RuntimeData.m_SpeedTreeData; Vector3 worldScale = new Vector3(data.m_Size.x, data.m_Size.y, data.m_Size.x); // Set material MeshRenderer rend = meshObj.AddComponent <MeshRenderer>(); rend.sharedMaterial = GenerateBillboardMaterial(type.m_RuntimeData.m_SpeedTreeData); MeshFilter filter = meshObj.AddComponent <MeshFilter>(); Mesh treeMesh = new Mesh(); treeMesh.name = meshObj.name; List <Vector4> m_TempWorldPositions = new List <Vector4>(); List <Vector3> m_TempWorldScales = new List <Vector3>(); List <Vector3> m_TempQuadVertices = new List <Vector3>(); List <Vector4> m_TempQuadTangents = new List <Vector4>(); List <Vector3> m_TempQuadNormals = new List <Vector3>(); List <int> m_TempQuadIndices = new List <int>(); for (int treeIndex = 0; treeIndex < trees.Count; treeIndex++) { Vector3 position = trees[treeIndex].m_Position; Vector3 scale = trees[treeIndex].m_Scale; float rot = trees[treeIndex].m_Rotation.eulerAngles.y * Mathf.Deg2Rad; // Offset world position, by the grounding factor Vector3 instancePos = position; // Don't use this, but offset in shader, so that we can have that correct hue // instancePos.y += data.m_Size.z; // Scale by the world scale too so that we don't have to do an extra multip Vector3 instanceScale = scale; instanceScale.Scale(worldScale); // Add the world and scale data for (int index = 0; index < 4; index++) { Vector4 posAndRot = instancePos; posAndRot.w = rot; m_TempWorldPositions.Add(posAndRot); m_TempWorldScales.Add(instanceScale); } // Add stanard quad data m_TempQuadVertices.AddRange(m_SystemQuadVertices); m_TempQuadTangents.AddRange(m_SystemQuadTangents); m_TempQuadNormals.AddRange(m_SystemQuadNormals); // Calculate triangle indixes m_TempQuadIndices.AddRange(originalTriangles); for (int triIndex = 0; triIndex < 6; triIndex++) { // Just add to the triangles the existing triangles + the new indices m_TempQuadIndices[triIndex + 6 * treeIndex] = originalTriangles[triIndex] + 4 * treeIndex; } } treeMesh.Clear(); // Set standard data treeMesh.SetVertices(m_TempQuadVertices); treeMesh.SetNormals(m_TempQuadNormals); treeMesh.SetTangents(m_TempQuadTangents); // Set the custom data treeMesh.SetUVs(1, m_TempWorldPositions); treeMesh.SetUVs(2, m_TempWorldScales); // Set triangles and do not calculate bounds treeMesh.SetTriangles(m_TempQuadIndices, 0, false); // Set the manually calculated bounds treeMesh.bounds = bounds; treeMesh.UploadMeshData(true); // Set the mesh filter.mesh = treeMesh; if (addLodGroup) { // Add the mesh' lod group LODGroup group = meshObj.AddComponent <LODGroup>(); group.animateCrossFading = false; if (animatedCrossFade) { group.fadeMode = LODFadeMode.CrossFade; group.animateCrossFading = true; } else { group.fadeMode = LODFadeMode.None; group.animateCrossFading = false; } group.SetLODs(new LOD[] { new LOD(screenFadeSize, new Renderer[] { rend }) }); group.RecalculateBounds(); } #if UNITY_EDITOR UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(meshObj.scene); #endif }
void Update() { #if UNITY_EDITOR FoliageLog.Assert(m_FoliageData != null); #endif // Camera used for culling m_CurrentFrameCameraCull = m_Settings.m_UsedCameraCulling; // Camera used for drawing m_CurrentFrameCameraDraw = m_Settings.m_UsedCameraDrawing; m_CurrentFrameLayer = LayerMask.NameToLayer(m_Settings.m_UsedLayer); // Extract the planes ExtractPlanes(m_CameraPlanes, m_CurrentFrameCameraCull.projectionMatrix * m_CurrentFrameCameraCull.worldToCameraMatrix); // Set the position m_CurrentFrameCameraPosition = m_CurrentFrameCameraCull.transform.position; // Set the bend position m_CurrentFrameBendPosition = m_Settings.m_BendTransform != null ? m_Settings.m_BendTransform.position : m_CurrentFrameCameraPosition; // Current cell position currentCell.Set(m_CurrentFrameCameraPosition); m_CurrentFrameAllowIndirect = m_Settings.m_AllowDrawInstancedIndirect; m_DrawStats.Reset(); // Copy the wind for SpeedTree types for (int i = 0; i < m_FoliageTypesArray.Length; i++) { if (m_FoliageTypesArray[i].IsSpeedTreeType) { m_FoliageTypesArray[i].CopyBlock(); } } bool applyShadowCorrection = m_Settings.m_ApplyShadowPoppingCorrection; float shadowCorrectionDistanceSqr = m_Settings.m_ShadowPoppingCorrection * m_Settings.m_ShadowPoppingCorrection; // We iterate only as many cells as we need FoliageCell.IterateNeighboring(currentCell, m_CellNeighborCount, (int hash) => { FoliageCellDataRuntime data; if (m_FoliageData.m_FoliageData.TryGetValue(hash, out data)) { // If it is within distance and in the frustum float distanceSqr = data.m_Bounds.SqrDistance(m_CurrentFrameCameraPosition); // Check for the maximum distance if (distanceSqr <= m_MaxDistanceAllSqr && GeometryUtility.TestPlanesAABB(m_CameraPlanes, data.m_Bounds)) { // Process the big cells if we are withing the tree distance if (distanceSqr <= m_MaxDistanceTreeSqr) { ProcessCellTree(data, distanceSqr, applyShadowCorrection, shadowCorrectionDistanceSqr, false); } // Process subdivided cells only if we have instancing enabled and only if it is within the distance proximity for grass if (distanceSqr <= m_MaxDistanceGrassSqr && m_Settings.m_DrawInstanced) { ProcessCellGrass(data); } m_DrawStats.m_ProcessedCells++; } else if (distanceSqr <= shadowCorrectionDistanceSqr) { ProcessCellTree(data, distanceSqr, applyShadowCorrection, shadowCorrectionDistanceSqr, true); } } }); #if UNITY_EDITOR if (Time.frameCount % 300 == 0) { FoliageLog.i(string.Format("Proc cells:{0} Proc subdiv cells: {1} Proc tree instances: {2} Proc draw calls: {3}", m_DrawStats.m_ProcessedCells, m_DrawStats.m_ProcessedCellsSubdiv, m_DrawStats.m_ProcessedInstances, m_DrawStats.m_ProcessedDrawCalls)); } #endif }
/** * Get all the instances withing the given radius of the provided location. * * @param subdivision * If we should get the instance count for subdivisions (grass) or for big cells. */ public int GetInstanceCountLocation(Vector3 position, float radius, bool subdivision) { int count = 0; Vector3 min = position - new Vector3(radius, radius, radius); Vector3 max = position + new Vector3(radius, radius, radius); float distanceDelta = radius * radius; float x, y, z; // Iterate divisions FoliageCell.IterateMinMax(min, max, false, (int hash) => { FoliageCellData cell; if (m_FoliageData.TryGetValue(hash, out cell) == false) { return; } if (subdivision == false) { // Count all the tree foliage that overlaps the sphere foreach (var data in cell.m_TypeHashLocationsEditor.Values) { foreach (var instances in data.Values) { for (int i = 0; i < instances.Count; 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 ((x * x + y * y + z * z) < distanceDelta) { count++; } } } } } else { // 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 foreach (var data in cellSubdivided.m_TypeHashLocationsEditor.Values) { foreach (var instances in data.Values) { for (int i = 0; i < instances.Count; 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 ((x * x + y * y + z * z) < distanceDelta) { count++; } } } } }); } }); return(count); }
/** * 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); }
private static void ReadFoliageCell(BinaryReader a, out FoliageCell fc) { fc.x = a.ReadInt32(); fc.y = a.ReadInt32(); fc.z = a.ReadInt32(); }
private static void WriteFoliageCell(BinaryWriter a, FoliageCell fc) { a.Write(fc.x); a.Write(fc.y); a.Write(fc.z); }
private void Update() { if (!m_Settings.m_WatchedTransform) { return; } m_CameraPosTemp = m_Settings.m_WatchedTransform.position; float x = m_CameraPosTemp.x - m_LastPosition.x; float y = m_CameraPosTemp.y - m_LastPosition.y; float z = m_CameraPosTemp.z - m_LastPosition.z; float distWalked = x * x + y * y + z * z; // If we didn't walked enough, return if (distWalked > m_Settings.m_CollisionRefreshDistance * m_Settings.m_CollisionRefreshDistance) { // Update last position m_LastPosition = m_CameraPosTemp; m_Layer = LayerMask.NameToLayer(m_Settings.m_UsedLayer); // Reset counter m_DataIssuedActiveColliders = 0; // Reset all the cache's data foreach (CollisionCache cache in m_Cache.Values) { if (cache != null) { cache.Reset(); } } // Set the current cell currentCell.Set(m_LastPosition); // Refresh eveything float collDistSqr = m_Settings.m_CollisionDistance * m_Settings.m_CollisionDistance; // Iterate cells FoliageCell.IterateNeighboring(currentCell, 1, (int hash) => { FoliageCellDataRuntime data; if (m_FoliageData.m_FoliageData.TryGetValue(hash, out data)) { // If it is within distance and in the frustum float distanceSqr = data.m_Bounds.SqrDistance(m_LastPosition); if (distanceSqr <= collDistSqr) { ProcessCell(data, collDistSqr); } } }); } #if UNITY_EDITOR if (Time.frameCount % 300 == 0) { FoliageLog.i("Issued colliders: " + m_DataIssuedActiveColliders); } #endif }