using System.Collections.Generic; using UnityEngine; using System; namespace RayFire { [Serializable] public class RFCluster : IComparable { /// Main public int id; public Transform tm; public int depth; public Vector3 pos; public Quaternion rot; public Vector3 scl; public Bounds bound; public bool demolishable; public RayfireRigid rigid; public List shards; public bool cachedHost; /// Collapse public float areaCollapse; public float minimumArea; public float maximumArea; public float sizeCollapse; public float minimumSize; public float maximumSize; public int randomCollapse; /// Non Serialized [NonSerialized] public bool initialized; [NonSerialized] public RFCluster mainCluster; [NonSerialized] public List childClusters; [NonSerialized] public List neibClusters; [NonSerialized] public List neibArea; [NonSerialized] public List neibPerc; /// Static static List checkShards = new List(); static List newClusterShards = new List(); /// ///////////////////////////////////////////////////////// /// Constructor /// ///////////////////////////////////////////////////////// // Constructor public RFCluster() { id = -1; tm = null; depth = 0; initialized = false; demolishable = true; shards = new List(); rigid = null; // Collapse minimumArea = 0; maximumArea = 0; minimumSize = 0; maximumSize = 0; randomCollapse = 0; cachedHost = false; // Non serialized mainCluster = null; childClusters = null; } // Constructor public RFCluster (RFCluster source) { // Main id = source.id; tm = source.tm; depth = source.depth; pos = source.pos; rot = source.rot; bound = source.bound; demolishable = source.demolishable; rigid = source.rigid; // Collapse areaCollapse = source.areaCollapse; minimumArea = source.minimumArea; maximumArea = source.maximumArea; sizeCollapse = source.sizeCollapse; minimumSize = source.minimumSize; maximumSize = source.maximumSize; randomCollapse = source.randomCollapse; // Remapped shards shards = new List(source.shards.Count); for (int i = 0; i < source.shards.Count; i++) shards.Add (new RFShard (source.shards[i])); // Copy child clusters if (source.HasChildClusters == true) { childClusters = new List(source.childClusters.Count); for (int i = 0; i < source.childClusters.Count; i++) childClusters.Add (new RFCluster(source.childClusters[i])); } // Set false to reinit it in SaveBackup after all shards will be copied initialized = false; } // Compare by size public int CompareTo(RFCluster otherCluster) { float thisSize = bound.size.magnitude; float otherSize = otherCluster.bound.size.magnitude; if (thisSize > otherSize) return -1; if (thisSize < otherSize) return 1; return 0; } /// ///////////////////////////////////////////////////////// /// Init Cluster /// ///////////////////////////////////////////////////////// // Reinit non serialized fields in case of prefab use public static void InitCluster (RayfireRigid scr, RFCluster cluster) { if (cluster.initialized == false) { // Reinit connected cluster shards non serialized fields if (scr.objTp == ObjectType.ConnectedCluster) InitConnectedCluster (cluster); // Unfold nested cluster non serialized child clusters else if (scr.objTp == ObjectType.NestedCluster) InitNestedCluster (scr, cluster); cluster.initialized = true; } } // Init connected cluster static void InitConnectedCluster (RFCluster cluster) { for (int s = 0; s < cluster.shards.Count; s++) { cluster.shards[s].cluster = cluster; cluster.shards[s].neibShards = new List(cluster.shards[s].nIds.Count); for (int n = 0; n < cluster.shards[s].nIds.Count; n++) cluster.shards[s].neibShards.Add (cluster.shards[cluster.shards[s].nIds[n]]); } } // Unfold nested cluster static void InitNestedCluster (RayfireRigid scr, RFCluster cluster) { // Set shard cluster for (int s = 0; s < cluster.shards.Count; s++) cluster.shards[s].cluster = cluster; // Check all minor clusters and set main cluster for them for (int i = 0; i < scr.clsDemol.minorClusters.Count; i++) if (cluster.tm == scr.clsDemol.minorClusters[i].tm.parent) scr.clsDemol.minorClusters[i].mainCluster = scr.clsDemol.cluster; // Repeat for child clusters if (cluster.HasChildClusters == true) for (int i = 0; i < cluster.childClusters.Count; i++) InitNestedCluster (scr, cluster.childClusters[i]); } /// ///////////////////////////////////////////////////////// /// Connectivity /// ///////////////////////////////////////////////////////// // Connectivity check. Separate not connected groups to child clusters public static void ConnectivityUnyCheck (RFCluster cluster) { // Set up child cluster if (cluster.childClusters != null) cluster.childClusters.Clear(); // Not enough shards to check for connectivity if (cluster.shards.Count <= 1) return; // Check all shards and collect new clusters int shardsAmount = cluster.shards.Count; // Set unchecked state RFShard.SetUnchecked (cluster.shards); // Check all shards for connectivity but keep uny clusters in main cluster for (int s = cluster.shards.Count - 1; s >= 0; s--) { // Skip checked shards if (cluster.shards[s].check == true) continue; // Mark as checked cluster.shards[s].check = true; // New possible cluster. Create new because applied to cluster newClusterShards.Clear(); newClusterShards.Add (cluster.shards[s]); // Shards in possible connection checkShards.Clear(); checkShards.Add (cluster.shards[s]); // Collect by neibs while (checkShards.Count > 0) { // Add neibs to check If neib among current cluster shards And not already collected for (int n = 0; n < checkShards[0].neibShards.Count; n++) { if (checkShards[0].neibShards[n].check == false) { // Mark as checked checkShards[0].neibShards[n].check = true; // Collect checkShards.Add(checkShards[0].neibShards[n]); newClusterShards.Add (checkShards[0].neibShards[n]); } } // Remove checked checkShards.RemoveAt(0); } // Child cluster connected if (shardsAmount == newClusterShards.Count) break; // Start over if connected shards has uny if (RFShard.UnyieldingByShard(newClusterShards) == true) continue; // Create new cluster by shards NewClusterByShards (cluster, newClusterShards); } // Clear checkShards.Clear(); newClusterShards.Clear(); // Remove new child clusters shards from original cluster shards list before repeat while cycle if (cluster.HasChildClusters == true) for (int i = cluster.shards.Count - 1; i >= 0; i--) if (cluster.shards[i].cluster != cluster) cluster.shards.RemoveAt(i); // Set unchecked state RFShard.SetUnchecked (cluster.shards); } // Connectivity check public static void ConnectivityCheck (RFCluster cluster) { // Set up child cluster if (cluster.childClusters != null) cluster.childClusters.Clear(); // Not enough shards to check for connectivity if (cluster.shards.Count <= 1) return; // Set unchecked state RFShard.SetUnchecked (cluster.shards); // Check all shards and collect new clusters int shardsAmount = cluster.shards.Count; // Iterate through shards and check connectivity while (cluster.shards.Count > 0) { // Lists checkShards.Clear(); checkShards.Add(cluster.shards[0]); cluster.shards[0].check = true; // Collect shards for new cluster group newClusterShards.Clear(); newClusterShards.Add (cluster.shards[0]); // Collect by neibs while (checkShards.Count > 0) { // Add neibs to check If neib among current cluster shards And not already collected for (int n = 0; n < checkShards[0].neibShards.Count; n++) { if (checkShards[0].neibShards[n].check == false) { // Mark as checked checkShards[0].neibShards[n].check = true; // Collect checkShards.Add(checkShards[0].neibShards[n]); newClusterShards.Add(checkShards[0].neibShards[n]); } } // Remove checked checkShards.RemoveAt(0); } // Child cluster connected if (shardsAmount == newClusterShards.Count) break; // Create new cluster by shards NewClusterByShards (cluster, newClusterShards); // Remove new cluster shards from original cluster shards list before repeat while cycle for (int i = cluster.shards.Count - 1; i >= 0; i--) if (cluster.shards[i].cluster != cluster) cluster.shards.RemoveAt(i); } // Clear checkShards.Clear(); newClusterShards.Clear(); // Set unchecked state RFShard.SetUnchecked (cluster.shards); } // Create new cluster by shards, set id and main cluster link public static void NewClusterByShards(RFCluster mainCLuster, List shards) { // Child cluster not connected. Create new cluster and add to parent RFCluster newCluster = new RFCluster(); for (int i = 0; i < shards.Count; i++) newCluster.shards.Add (shards[i]); newCluster.demolishable = mainCLuster.demolishable; // Set main cluster if (mainCLuster.mainCluster == null) newCluster.mainCluster = mainCLuster; else newCluster.mainCluster = mainCLuster.mainCluster; // Set uniq id after main cluster defined newCluster.id = GetUniqClusterId (newCluster); // Set shards cluster to new cluster for (int i = 0; i < newCluster.shards.Count; i++) newCluster.shards[i].cluster = newCluster; // Add new child cluster mainCLuster.AddChildCluster (newCluster); } // Add new child cluster public void AddChildCluster(RFCluster cluster) { if (childClusters == null) childClusters = new List(); childClusters.Add(cluster); } // Set biggest child cluster shards to original cluster. Cant be 1 child cluster public static void ReduceChildClusters (RFCluster cluster) { // Cluster has child cluster if (cluster.childClusters != null && cluster.childClusters.Count > 0) { // Get biggest cluster int biggestInd = GetBiggestCluster (cluster.childClusters); // Set biggest cluster shards to original cluster shards to reuse it cluster.shards = cluster.childClusters[biggestInd].shards; // Set shards cluster back to original cluster for (int i = 0; i < cluster.shards.Count; i++) cluster.shards[i].cluster = cluster; // Remove biggest cluster from child clusters cluster.childClusters.RemoveAt (biggestInd); } } /// ///////////////////////////////////////////////////////// /// Bounds /// ///////////////////////////////////////////////////////// // Get all children bounds public static Bounds GetChildrenBound(Transform tm) { // Collect all children transforms Renderer[] renderers = tm.GetComponentsInChildren(); // Get list of bounds Bounds[] bounds = new Bounds[renderers.Length]; for (int i = 0; i < renderers.Length; i++) bounds[i] = renderers[i].bounds; return GetBoundsBound(bounds); } // Get all children bounds public static Bounds GetClusterBound(RFCluster cluster) { // Get list of bounds Bounds[] bounds = new Bounds[cluster.shards.Count + cluster.childClusters.Count]; for (int i = 0; i < cluster.shards.Count; i++) bounds[i] = cluster.shards[i].bnd; for (int i = 0; i < cluster.childClusters.Count; i++) bounds[i + cluster.shards.Count] = cluster.childClusters[i].bound; return GetBoundsBound(bounds); } // Get bound by list of bounds public static Bounds GetBoundsBound(Bounds[] bounds) { // new bound Bounds bound = new Bounds(); // No mesh renderers if (bounds.Length == 0) { Debug.Log("GetBoundsBound warning"); return bound; } // Basic bounds min and max values float minX = bounds[0].min.x; float minY = bounds[0].min.y; float minZ = bounds[0].min.z; float maxX = bounds[0].max.x; float maxY = bounds[0].max.y; float maxZ = bounds[0].max.z; // Compare with other bounds if (bounds.Length > 1) { for (int i = 1; i < bounds.Length; i++) { if (bounds[i].min.x < minX) minX = bounds[i].min.x; if (bounds[i].min.y < minY) minY = bounds[i].min.y; if (bounds[i].min.z < minZ) minZ = bounds[i].min.z; if (bounds[i].max.x > maxX) maxX = bounds[i].max.x; if (bounds[i].max.y > maxY) maxY = bounds[i].max.y; if (bounds[i].max.z > maxZ) maxZ = bounds[i].max.z; } } // Get center bound.center = new Vector3((maxX - minX) / 2f, (maxY - minY) / 2f, (maxZ - minZ) / 2f); // Get min and max vectors bound.min = new Vector3(minX, minY, minZ); bound.max = new Vector3(maxX, maxY, maxZ); return bound; } // Get bound by list of bounds public static Bounds GetShardsBound(List shards) { // new bound Bounds bound = new Bounds(); // No mesh renderers if (shards.Count == 0) { Debug.Log("GetShardsBound warning"); return bound; } // Basic bounds min and max values float minX = shards[0].bnd.min.x; float minY = shards[0].bnd.min.y; float minZ = shards[0].bnd.min.z; float maxX = shards[0].bnd.max.x; float maxY = shards[0].bnd.max.y; float maxZ = shards[0].bnd.max.z; // Compare with other bounds if (shards.Count > 1) { for (int i = 1; i < shards.Count; i++) { if (shards[i].bnd.min.x < minX) minX = shards[i].bnd.min.x; if (shards[i].bnd.min.y < minY) minY = shards[i].bnd.min.y; if (shards[i].bnd.min.z < minZ) minZ = shards[i].bnd.min.z; if (shards[i].bnd.max.x > maxX) maxX = shards[i].bnd.max.x; if (shards[i].bnd.max.y > maxY) maxY = shards[i].bnd.max.y; if (shards[i].bnd.max.z > maxZ) maxZ = shards[i].bnd.max.z; } } // Get center bound.center = new Vector3((maxX - minX) / 2f, (maxY - minY) / 2f, (maxZ - minZ) / 2f); // Get min and max vectors bound.min = new Vector3(minX, minY, minZ); bound.max = new Vector3(maxX, maxY, maxZ); return bound; } // Get bound by list of bounds public static Bounds GetShardsBoundByPosition(List shards) { // new bound Bounds bound = new Bounds(); // No mesh renderers if (shards.Count == 0) { Debug.Log("GetShardsBoundByPosition warning"); return bound; } // Get all position lists List posList = new List(shards.Count); for (int i = 0; i < shards.Count; i++) posList.Add (shards[i].tm.position); // Basic bounds min and max values float minX = shards[0].tm.position.x - 0.01f; float minY = shards[0].tm.position.y - 0.01f; float minZ = shards[0].tm.position.z - 0.01f; float maxX = shards[0].tm.position.x + 0.01f; float maxY = shards[0].tm.position.y + 0.01f; float maxZ = shards[0].tm.position.z + 0.01f; // Compare with other bounds if (shards.Count > 1) { for (int i = 1; i < posList.Count; i++) { if (posList[i].x < minX) minX = posList[i].x; if (posList[i].y < minY) minY = posList[i].y; if (posList[i].z < minZ) minZ = posList[i].z; if (posList[i].x > maxX) maxX = posList[i].x; if (posList[i].y > maxY) maxY = posList[i].y; if (posList[i].z > maxZ) maxZ = posList[i].z; } } // Get center bound.center = new Vector3((maxX - minX) / 2f, (maxY - minY) / 2f, (maxZ - minZ) / 2f); // Get min and max vectors bound.min = new Vector3(minX, minY, minZ); bound.max = new Vector3(maxX, maxY, maxZ); return bound; } /// ///////////////////////////////////////////////////////// /// Static /// ///////////////////////////////////////////////////////// // Get biggest cluster static int GetBiggestCluster(List clusters) { // Only one cluster if (clusters.Count == 1) return 0; int index = 0; int amount = 0; for (int i = 0; i < clusters.Count; i++) { if (clusters[i].shards.Count > amount) { amount = clusters[i].shards.Count; index = i; } } return index; } // Collect solo shards, remove from cluster, reinit cluster public static void DetachSoloShards(RFCluster cluster, List soloShards) { for (int s = cluster.shards.Count - 1; s >= 0; s--) if (cluster.shards[s].neibShards.Count == 0) { soloShards.Add (cluster.shards[s]); cluster.shards[s].cluster = null; cluster.shards.RemoveAt (s); } } // Set uniq id after main cluster defined public static int GetUniqClusterId (RFCluster cluster) { // Main cluster is if (cluster.mainCluster == null) return 1; if (cluster.mainCluster.rigid == null) return 2; cluster.mainCluster.rigid.clsDemol.clsCount++; return cluster.mainCluster.rigid.clsDemol.clsCount; } // Integrity check public static bool IntegrityCheck(RFCluster cluster) { if (cluster != null && cluster.shards.Count > 0) for (int i = 0; i < cluster.shards.Count; i++) if (cluster.shards[i].tm == null) return false; return true; } // Create Root for Cluster public static void CreateClusterRoot(RFCluster cluster, Vector3 pos, Quaternion rot, int layer, string tag, string nm) { GameObject leftRoot = new GameObject(nm); cluster.tm = leftRoot.transform; cluster.tm.position = pos; cluster.tm.rotation = rot; leftRoot.layer = layer; leftRoot.tag = tag; } // Get mass by cluster shards mass public static float GetClusterMass (RFCluster cluster) { float m = 0; for (int s = 0; s < cluster.shards.Count; s++) if (cluster.shards[s].rb != null) m += cluster.shards[s].rb.mass; return m; } /// ///////////////////////////////////////////////////////// /// Getters /// ///////////////////////////////////////////////////////// // Had child cluster public bool HasChildClusters { get { return childClusters != null && childClusters.Count > 0; } } // Check if cluster has unyielding shards public bool UnyieldingByShard { get { for (int i = 0; i < shards.Count; i++) if (shards[i].uny == true) return true; return false; } } // Check if cluster has unyielding shards public bool UnyieldingByRigid { get { for (int i = 0; i < shards.Count; i++) if (shards[i].rigid.activation.uny == true) return true; return false; } } // Check if cluster has unyielding shards and return its sim state public SimType UnyieldingSimByShard { get { for (int i = 0; i < shards.Count; i++) if (shards[i].uny == true) return shards[i].sm; return SimType.Sleeping; } } /// ///////////////////////////////////////////////////////// /// Cluster component /// ///////////////////////////////////////////////////////// // Get all shards ain all child clusters List GetNestedShards(bool OwnShards = false) { List nestedShards = new List(); List nestedClusters = new List(); nestedClusters.AddRange(childClusters); // Collect own shards if (OwnShards == true) nestedShards.AddRange(shards); while (nestedClusters.Count > 0) { nestedShards.AddRange(nestedClusters[0].shards); nestedClusters.AddRange(nestedClusters[0].childClusters); nestedClusters.RemoveAt(0); } return nestedShards; } // Get all shards ain all child clusters List GetNestedClusters() { List nestedClusters = new List(); nestedClusters.AddRange(childClusters); List checkClusters = new List(); checkClusters.AddRange(childClusters); while (checkClusters.Count > 0) { nestedClusters.AddRange(checkClusters[0].childClusters); checkClusters.RemoveAt(0); } return nestedClusters; } // Check if other cluster has shared face bool TrisNeib(RFCluster otherCluster) { // Check if cluster shards has 1 neib in other cluster shards foreach (RFShard shard in shards) for (int i = 0; i < shard.neibShards.Count; i++) if (otherCluster.shards.Contains(shard.neibShards[i]) == true) return true; List nestedShards = GetNestedShards(); List otherNestedShards = otherCluster.GetNestedShards(); foreach (RFShard shard in nestedShards) for (int i = 0; i < shard.neibShards.Count; i++) if (otherNestedShards.Contains(shard.neibShards[i]) == true) return true; //// Check if other cluster among neib clusters //if (neibClusters.Contains(otherCluster) == true) // return true; //// Check if other cluster children clusters has //foreach (RFCluster cluster in childClusters) // for (int i = 0; i < cluster.neibClusters.Count; i++) // if (otherCluster.neibClusters.Contains(cluster.neibClusters[i]) == true) // return true; return false; } // Get shared area with another cluster float NeibArea(RFCluster otherCluster) { float area = 0f; foreach (RFShard shard in shards) for (int i = 0; i < shard.neibShards.Count; i++) if (otherCluster.shards.Contains(shard.neibShards[i]) == true) area += shard.nArea[i]; List nestedShards = GetNestedShards(); List otherNestedShards = otherCluster.GetNestedShards(); foreach (RFShard shard in nestedShards) for (int i = 0; i < shard.neibShards.Count; i++) if (otherNestedShards.Contains(shard.neibShards[i]) == true) area += shard.nArea[i]; //// Check if other cluster children clusters has //foreach (RFCluster cluster in childClusters) // for (int i = 0; i < cluster.neibClusters.Count; i++) // if (otherCluster.neibClusters.Contains(cluster.neibClusters[i]) == true) // area += cluster.neibArea[i]; return area; } // Get neib index with biggest shared area public int GetNeibIndArea(List clusterList = null) { // Get neib index with biggest shared area float biggestArea = 0f; int neibInd = 0; for (int i = 0; i < neibClusters.Count; i++) { // Skip if check neib shard not in filter list if (clusterList != null) if (clusterList.Contains(neibClusters[i]) == false) continue; // Remember if bigger if (neibArea[i] > biggestArea) { biggestArea = neibArea[i]; neibInd = i; } } // Return index of neib with biggest shared area if (biggestArea > 0) return neibInd; // No neib return -1; } // Find neib clusters amount cluster list and set them with neib area public static void SetClusterNeib(List clusters, bool connectivity) { // Set list foreach (RFCluster cluster in clusters) { cluster.neibClusters = new List(); cluster.neibArea = new List(); cluster.neibPerc = new List(); } // Set neib and area info for (int i = 0; i < clusters.Count; i++) { for (int s = 0; s < clusters.Count; s++) { if (s != i) { // Check if shard was not added as neib before if (clusters[s].neibClusters.Contains(clusters[i]) == false) { // Bounding box intersection check if (clusters[i].bound.Intersects(clusters[s].bound) == true) { // No need in face check connectivity if (connectivity == false) { float size = clusters[i].bound.size.magnitude; clusters[i].neibClusters.Add(clusters[s]); clusters[i].neibArea.Add(size); clusters[s].neibClusters.Add(clusters[i]); clusters[s].neibArea.Add(size); } // Face to face connectivity check else { // Check for shared faces and collect neibs and areas if (clusters[i].TrisNeib(clusters[s]) == true) { float area = clusters[i].NeibArea(clusters[s]); clusters[i].neibClusters.Add(clusters[s]); clusters[i].neibArea.Add(area); clusters[s].neibClusters.Add(clusters[i]); clusters[s].neibArea.Add(area); } } } } } } // Set area ratio float maxArea = Mathf.Max(clusters[i].neibArea.ToArray()); foreach (float area in clusters[i].neibArea) { if (maxArea > 0) clusters[i].neibPerc.Add(area / maxArea); else clusters[i].neibPerc.Add(0f); } } } // Get neib cluster from shardList which is neib to one of the shards public static RFCluster GetNeibClusterArea(List clusters, List clusterList) { // No clusters to pick if (clusterList.Count == 0) return null; // Get all neibs for clusters, exclude neibs not from clusterList List allNeibs = new List(); // Biggest area float biggestArea = 0f; RFCluster biggestCluster = null; // Check cluster foreach (RFCluster cluster in clusters) { // Check neibs for (int i = 0; i < cluster.neibClusters.Count; i++) { // Shared are is too small relative other neibs if (cluster.neibPerc[i] < 0.5f) continue; // Neib cluster has shared area lower than already founded if (biggestArea >= cluster.neibArea[i]) continue; // Neib already in neib list if (allNeibs.Contains(cluster.neibClusters[i]) == true) continue; // Neib not among allowed clusters if (clusterList.Contains(cluster.neibClusters[i]) == false) continue; // Remember neib allNeibs.Add(cluster.neibClusters[i]); biggestArea = cluster.neibArea[i]; biggestCluster = cluster.neibClusters[i]; } } // Pick shard with biggest area return biggestCluster; } } }