using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using DigitalOpus.MB.Core; #if UNITY_EDITOR using UnityEditor; #endif public class MB3_MeshBakerGrouper : MonoBehaviour, MB_IMeshBakerSettingsHolder { public enum ClusterType { none, grid, pie, agglomerative, } public MB3_MeshBakerGrouperCore grouper; public ClusterType clusterType = ClusterType.none; public GrouperData data = new GrouperData(); //these are for getting a resonable bounds in which to draw gizmos. [HideInInspector] public Bounds sourceObjectBounds = new Bounds(Vector3.zero, Vector3.one); public MB3_MeshCombinerSettings meshBakerSettingsAsset; public MB3_MeshCombinerSettingsData meshBakerSettings; public MB_IMeshBakerSettings GetMeshBakerSettings() { if (meshBakerSettingsAsset == null) { return meshBakerSettings; } else { return meshBakerSettingsAsset.GetMeshBakerSettings(); } } #if UNITY_EDITOR public SerializedProperty GetMeshBakerSettingsAsSerializedProperty() { if (meshBakerSettingsAsset == null) { UnityEditor.SerializedObject so = new UnityEditor.SerializedObject(this); return so.FindProperty("meshBakerSettings"); } else { UnityEditor.SerializedObject so = new UnityEditor.SerializedObject(meshBakerSettingsAsset); return so.FindProperty("data"); } } #endif void OnDrawGizmosSelected() { if (grouper == null) { grouper = CreateGrouper(clusterType, data); } if (grouper.d == null) { grouper.d = data; } grouper.DrawGizmos(sourceObjectBounds); } public MB3_MeshBakerGrouperCore CreateGrouper(ClusterType t, GrouperData data) { if (t == ClusterType.grid) grouper = new MB3_MeshBakerGrouperGrid(data); if (t == ClusterType.pie) grouper = new MB3_MeshBakerGrouperPie(data); if (t == ClusterType.agglomerative) { MB3_TextureBaker tb = GetComponent(); List gos; if (tb != null) { gos = tb.GetObjectsToCombine(); } else { gos = new List(); } grouper = new MB3_MeshBakerGrouperCluster(data, gos); } if (t == ClusterType.none) grouper = new MB3_MeshBakerGrouperNone(data); return grouper; } public void DeleteAllChildMeshBakers() { MB3_MeshBakerCommon[] mBakers = GetComponentsInChildren(); for (int i = 0; i < mBakers.Length; i++) { MB3_MeshBakerCommon mb = mBakers[i]; GameObject resultGameObject = mb.meshCombiner.resultSceneObject; MB_Utility.Destroy(resultGameObject); MB_Utility.Destroy(mb.gameObject); } } } namespace DigitalOpus.MB.Core { /// all properties go here so that settings are remembered as user switches between cluster types [Serializable] public class GrouperData { public bool clusterOnLMIndex; public bool clusterByLODLevel; public Vector3 origin; //Normally these properties would be in the subclasses but putting them here makes writing the inspector much easier //for grid public Vector3 cellSize; //for pie public int pieNumSegments = 4; public Vector3 pieAxis = Vector3.up; public float ringSpacing = 100f; public bool combineSegmentsInInnermostRing = false; //for clustering public int height = 1; public float maxDistBetweenClusters = 1f; public bool includeCellsWithOnlyOneRenderer = true; } [Serializable] public abstract class MB3_MeshBakerGrouperCore { public GrouperData d; public abstract Dictionary> FilterIntoGroups(List selection); public abstract void DrawGizmos(Bounds sourceObjectBounds); public void DoClustering(MB3_TextureBaker tb, MB3_MeshBakerGrouper grouper) { //todo warn for no objects and no Texture Bake Result Dictionary> cell2objs = FilterIntoGroups(tb.GetObjectsToCombine()); if (d.clusterOnLMIndex) { Dictionary> cell2objsNew = new Dictionary>(); foreach (string key in cell2objs.Keys) { List gaws = cell2objs[key]; Dictionary> idx2objs = GroupByLightmapIndex(gaws); foreach (int keyIdx in idx2objs.Keys) { string keyNew = key + "-LM-" + keyIdx; cell2objsNew.Add(keyNew, idx2objs[keyIdx]); } } cell2objs = cell2objsNew; } if (d.clusterByLODLevel) { //visit each cell //visit each renderer //check if that renderer is a child of an LOD group // visit each LOD level check if this renderer is in that list. // if not add it to LOD0 for that cell // otherwise add it to LODX for that cell creating LODs as necessary Dictionary> cell2objsNew = new Dictionary>(); foreach (string key in cell2objs.Keys) { List gaws = cell2objs[key]; foreach (Renderer r in gaws) { if (r == null) continue; bool foundInLOD = false; LODGroup lodg = r.GetComponentInParent(); if (lodg != null) { LOD[] lods = lodg.GetLODs(); for (int i = 0; i < lods.Length; i++) { LOD lod = lods[i]; if (Array.Find(lod.renderers, x => x == r) != null) { foundInLOD = true; List rs; string newKey = String.Format("{0}_LOD{1}", key, i); if (!cell2objsNew.TryGetValue(newKey, out rs)) { rs = new List(); cell2objsNew.Add(newKey, rs); } if (!rs.Contains(r)) rs.Add(r); } } } if (!foundInLOD) { List rs; string newKey = String.Format("{0}_LOD0", key); if (!cell2objsNew.TryGetValue(newKey, out rs)) { rs = new List(); cell2objsNew.Add(newKey, rs); } if (!rs.Contains(r)) rs.Add(r); } } } cell2objs = cell2objsNew; } int clustersWithOnlyOneRenderer = 0; foreach (string key in cell2objs.Keys) { List gaws = cell2objs[key]; if (gaws.Count > 1 || grouper.data.includeCellsWithOnlyOneRenderer) { AddMeshBaker(grouper, tb, key, gaws); } else { clustersWithOnlyOneRenderer++; } } Debug.Log(String.Format("Found {0} cells with Renderers. Not creating bakers for {1} because there is only one mesh in the cell. Creating {2} bakers.", cell2objs.Count, clustersWithOnlyOneRenderer, cell2objs.Count - clustersWithOnlyOneRenderer)); } Dictionary> GroupByLightmapIndex(List gaws) { Dictionary> idx2objs = new Dictionary>(); for (int i = 0; i < gaws.Count; i++) { List objs = null; if (idx2objs.ContainsKey(gaws[i].lightmapIndex)) { objs = idx2objs[gaws[i].lightmapIndex]; } else { objs = new List(); idx2objs.Add(gaws[i].lightmapIndex, objs); } objs.Add(gaws[i]); } return idx2objs; } void AddMeshBaker(MB3_MeshBakerGrouper grouper, MB3_TextureBaker tb, string key, List gaws) { int numVerts = 0; for (int i = 0; i < gaws.Count; i++) { Mesh m = MB_Utility.GetMesh(gaws[i].gameObject); if (m != null) numVerts += m.vertexCount; } GameObject nmb = new GameObject("MeshBaker-" + key); nmb.transform.position = Vector3.zero; MB3_MeshBakerCommon newMeshBaker; if (numVerts >= 65535) { newMeshBaker = nmb.AddComponent(); newMeshBaker.useObjsToMeshFromTexBaker = false; } else { newMeshBaker = nmb.AddComponent(); newMeshBaker.useObjsToMeshFromTexBaker = false; } newMeshBaker.textureBakeResults = tb.textureBakeResults; newMeshBaker.transform.parent = tb.transform; newMeshBaker.meshCombiner.settingsHolder = grouper; for (int i = 0; i < gaws.Count; i++) { newMeshBaker.GetObjectsToCombine().Add(gaws[i].gameObject); } } } [Serializable] public class MB3_MeshBakerGrouperNone : MB3_MeshBakerGrouperCore { public MB3_MeshBakerGrouperNone(GrouperData d) { this.d = d; } public override Dictionary> FilterIntoGroups(List selection) { Debug.Log("Filtering into groups none"); Dictionary> cell2objs = new Dictionary>(); List rs = new List(); for (int i = 0; i < selection.Count; i++) { if (selection[i] != null) { rs.Add(selection[i].GetComponent()); } } cell2objs.Add("MeshBaker", rs); return cell2objs; } public override void DrawGizmos(Bounds sourceObjectBounds) { } } [Serializable] public class MB3_MeshBakerGrouperGrid : MB3_MeshBakerGrouperCore { public MB3_MeshBakerGrouperGrid(GrouperData d) { this.d = d; } public override Dictionary> FilterIntoGroups(List selection) { Dictionary> cell2objs = new Dictionary>(); if (d.cellSize.x <= 0f || d.cellSize.y <= 0f || d.cellSize.z <= 0f) { Debug.LogError("cellSize x,y,z must all be greater than zero."); return cell2objs; } Debug.Log("Collecting renderers in each cell"); foreach (GameObject t in selection) { if (t == null) { continue; } GameObject go = t; Renderer mr = go.GetComponent(); if (mr is MeshRenderer || mr is SkinnedMeshRenderer) { //get the cell this gameObject is in Vector3 gridVector = mr.bounds.center; gridVector.x = Mathf.Floor((gridVector.x - d.origin.x) / d.cellSize.x) * d.cellSize.x; gridVector.y = Mathf.Floor((gridVector.y - d.origin.y) / d.cellSize.y) * d.cellSize.y; gridVector.z = Mathf.Floor((gridVector.z - d.origin.z) / d.cellSize.z) * d.cellSize.z; List objs = null; string gridVectorStr = gridVector.ToString(); if (cell2objs.ContainsKey(gridVectorStr)) { objs = cell2objs[gridVectorStr]; } else { objs = new List(); cell2objs.Add(gridVectorStr, objs); } if (!objs.Contains(mr)) { //Debug.Log("Adding " + mr + " todo " + gridVectorStr); objs.Add(mr); } } } return cell2objs; } public override void DrawGizmos(Bounds sourceObjectBounds) { Vector3 cs = d.cellSize; if (cs.x <= .00001f || cs.y <= .00001f || cs.z <= .00001f) return; Vector3 p = sourceObjectBounds.center - sourceObjectBounds.extents; Vector3 offset = d.origin; offset.x = offset.x % cs.x; offset.y = offset.y % cs.y; offset.z = offset.z % cs.z; //snap p to closest cell center Vector3 start; p.x = Mathf.Round((p.x) / cs.x) * cs.x + offset.x; p.y = Mathf.Round((p.y) / cs.y) * cs.y + offset.y; p.z = Mathf.Round((p.z) / cs.z) * cs.z + offset.z; if (p.x > sourceObjectBounds.center.x - sourceObjectBounds.extents.x) p.x = p.x - cs.x; if (p.y > sourceObjectBounds.center.y - sourceObjectBounds.extents.y) p.y = p.y - cs.y; if (p.z > sourceObjectBounds.center.z - sourceObjectBounds.extents.z) p.z = p.z - cs.z; start = p; int numcells = Mathf.CeilToInt(sourceObjectBounds.size.x / cs.x + sourceObjectBounds.size.y / cs.y + sourceObjectBounds.size.z / cs.z); if (numcells > 200) { Gizmos.DrawWireCube(d.origin + cs / 2f, cs); } else { for (; p.x < sourceObjectBounds.center.x + sourceObjectBounds.extents.x; p.x += cs.x) { p.y = start.y; for (; p.y < sourceObjectBounds.center.y + sourceObjectBounds.extents.y; p.y += cs.y) { p.z = start.z; for (; p.z < sourceObjectBounds.center.z + sourceObjectBounds.extents.z; p.z += cs.z) { Gizmos.DrawWireCube(p + cs / 2f, cs); } } } } } } [Serializable] public class MB3_MeshBakerGrouperPie : MB3_MeshBakerGrouperCore { public MB3_MeshBakerGrouperPie(GrouperData data) { d = data; } public override Dictionary> FilterIntoGroups(List selection) { Dictionary> cell2objs = new Dictionary>(); if (d.pieNumSegments == 0) { Debug.LogError("pieNumSegments must be greater than zero."); return cell2objs; } if (d.pieAxis.magnitude <= .000001f) { Debug.LogError("Pie axis vector is too short."); return cell2objs; } if (d.ringSpacing <= .000001f) { Debug.LogError("Ring spacing is too small."); return cell2objs; } d.pieAxis.Normalize(); Quaternion pieAxis2yIsUp = Quaternion.FromToRotation(d.pieAxis, Vector3.up); Debug.Log("Collecting renderers in each cell"); foreach (GameObject t in selection) { if (t == null) { continue; } GameObject go = t; Renderer mr = go.GetComponent(); if (mr is MeshRenderer || mr is SkinnedMeshRenderer) { //get the cell this gameObject is in Vector3 origin2obj = mr.bounds.center - d.origin; origin2obj = pieAxis2yIsUp * origin2obj; Vector2 origin2Obj2D = new Vector2(origin2obj.x, origin2obj.z); float radius = origin2Obj2D.magnitude; origin2obj.Normalize(); float deg_aboutY = 0f; if (Mathf.Abs(origin2obj.x) < 10e-5f && Mathf.Abs(origin2obj.z) < 10e-5f) { deg_aboutY = 0f; } else { deg_aboutY = Mathf.Atan2(origin2obj.x, origin2obj.z) * Mathf.Rad2Deg; if (deg_aboutY < 0f) deg_aboutY = 360f + deg_aboutY; } // Debug.Log ("Obj " + mr + " angle " + d_aboutY); int segment = Mathf.FloorToInt(deg_aboutY / 360f * d.pieNumSegments); int ring = Mathf.FloorToInt(radius / d.ringSpacing); if (ring == 0 && d.combineSegmentsInInnermostRing) { segment = 0; } List objs = null; string segStr = "seg_" + segment + "_ring_" + ring; if (cell2objs.ContainsKey(segStr)) { objs = cell2objs[segStr]; } else { objs = new List(); cell2objs.Add(segStr, objs); } if (!objs.Contains(mr)) { objs.Add(mr); } } } return cell2objs; } public override void DrawGizmos(Bounds sourceObjectBounds) { if (d.pieAxis.magnitude < .1f) return; if (d.pieNumSegments < 1) return; float rad = sourceObjectBounds.extents.magnitude; int numRings = Mathf.CeilToInt(rad / d.ringSpacing); numRings = Mathf.Max(1, numRings); for (int i = 0; i < numRings; i++) { DrawCircle(d.pieAxis.normalized, d.origin, d.ringSpacing * (i + 1), 24); } Gizmos.color = Color.white; Quaternion yIsUp2PieAxis = Quaternion.FromToRotation(Vector3.up, d.pieAxis); Quaternion rStep = Quaternion.AngleAxis(180f / d.pieNumSegments, Vector3.up); Vector3 r = Vector3.forward; for (int i = 0; i < d.pieNumSegments; i++) { Vector3 rr = yIsUp2PieAxis * r; Vector3 origin = d.origin; int nr = numRings; if (d.combineSegmentsInInnermostRing) { origin = d.origin + rr.normalized * d.ringSpacing; nr = numRings - 1; } if (nr == 0) break; Gizmos.DrawLine(origin, origin + nr * d.ringSpacing * rr.normalized); r = rStep * r; r = rStep * r; } } static int MaxIndexInVector3(Vector3 v) { int idx = 0; float val = v.x; if (v.y > val) { idx = 1; val = v.y; } if (v.z > val) { idx = 2; val = v.z; } return idx; } public static void DrawCircle(Vector3 axis, Vector3 center, float radius, int subdiv) { Quaternion q = Quaternion.AngleAxis(360 / subdiv, axis); int maxIdx = MaxIndexInVector3(axis); int otherIdx = maxIdx == 0 ? maxIdx + 1 : maxIdx - 1; Vector3 r = axis; //r construct a vector perpendicular to axis float temp = r[maxIdx]; r[maxIdx] = r[otherIdx]; r[otherIdx] = -temp; r = Vector3.ProjectOnPlane(r, axis); r.Normalize(); r *= radius; for (int i = 0; i < subdiv + 1; i++) { Vector3 r2 = q * r; Gizmos.color = Color.white; Gizmos.DrawLine(center + r, center + r2); r = r2; } } } [Serializable] public class MB3_MeshBakerGrouperKMeans : MB3_MeshBakerGrouperCore { public int numClusters = 4; public Vector3[] clusterCenters = new Vector3[0]; public float[] clusterSizes = new float[0]; public MB3_MeshBakerGrouperKMeans(GrouperData data) { d = data; } public override Dictionary> FilterIntoGroups(List selection) { Dictionary> cell2objs = new Dictionary>(); List validObjs = new List(); int numClusters = 20; foreach (GameObject t in selection) { if (t == null) { continue; } GameObject go = t; Renderer mr = go.GetComponent(); if (mr is MeshRenderer || mr is SkinnedMeshRenderer) { //get the cell this gameObject is in validObjs.Add(go); } } if (validObjs.Count > 0 && numClusters > 0 && numClusters < validObjs.Count) { MB3_KMeansClustering kmc = new MB3_KMeansClustering(validObjs, numClusters); kmc.Cluster(); clusterCenters = new Vector3[numClusters]; clusterSizes = new float[numClusters]; for (int i = 0; i < numClusters; i++) { List lr = kmc.GetCluster(i, out clusterCenters[i], out clusterSizes[i]); if (lr.Count > 0) { cell2objs.Add("Cluster_" + i, lr); } } } else { //todo error messages } return cell2objs; } public override void DrawGizmos(Bounds sceneObjectBounds) { if (clusterCenters != null && clusterSizes != null && clusterCenters.Length == clusterSizes.Length) { for (int i = 0; i < clusterSizes.Length; i++) { Gizmos.DrawWireSphere(clusterCenters[i], clusterSizes[i]); } } } } [Serializable] public class MB3_MeshBakerGrouperCluster : MB3_MeshBakerGrouperCore { public MB3_AgglomerativeClustering cluster; float _lastMaxDistBetweenClusters; public float _ObjsExtents = 10f; public float _minDistBetweenClusters = .001f; List _clustersToDraw = new List(); float[] _radii; public MB3_MeshBakerGrouperCluster(GrouperData data, List gos) { d = data; } public override Dictionary> FilterIntoGroups(List selection) { Dictionary> cell2objs = new Dictionary>(); for (int i = 0; i < _clustersToDraw.Count; i++) { MB3_AgglomerativeClustering.ClusterNode node = _clustersToDraw[i]; List rrs = new List(); for (int j = 0; j < node.leafs.Length; j++) { Renderer r = cluster.clusters[node.leafs[j]].leaf.go.GetComponent(); if (r is MeshRenderer || r is SkinnedMeshRenderer) { rrs.Add(r); } } cell2objs.Add("Cluster_" + i, rrs); } return cell2objs; } public void BuildClusters(List gos, ProgressUpdateCancelableDelegate progFunc) { if (gos.Count == 0) { Debug.LogWarning("No objects to cluster. Add some objects to the list of Objects To Combine."); return; } if (cluster == null) cluster = new MB3_AgglomerativeClustering(); List its = new List(); for (int i = 0; i < gos.Count; i++) { if (gos[i] != null && its.Find(x => x.go == gos[i]) == null) { Renderer mr = gos[i].GetComponent(); if (mr != null && (mr is MeshRenderer || mr is SkinnedMeshRenderer)) { MB3_AgglomerativeClustering.item_s ii = new MB3_AgglomerativeClustering.item_s(); ii.go = gos[i]; ii.coord = mr.bounds.center; its.Add(ii); } } } cluster.items = its; //yield return cluster.agglomerate(); cluster.agglomerate(progFunc); if (!cluster.wasCanceled) { float smallest, largest; _BuildListOfClustersToDraw(progFunc, out smallest, out largest); d.maxDistBetweenClusters = Mathf.Lerp(smallest, largest, .9f); } } void _BuildListOfClustersToDraw(ProgressUpdateCancelableDelegate progFunc, out float smallest, out float largest) { _clustersToDraw.Clear(); if (cluster.clusters == null) { smallest = 1f; largest = 10f; return; } if (progFunc != null) progFunc("Building Clusters To Draw A:", 0); List removeMe = new List(); largest = 1f; smallest = 10e6f; for (int i = 0; i < cluster.clusters.Length; i++) { MB3_AgglomerativeClustering.ClusterNode node = cluster.clusters[i]; //don't draw clusters that were merged too far apart and only want leaf nodes if (node.distToMergedCentroid <= d.maxDistBetweenClusters /*&& node.leaf == null*/) { if (d.includeCellsWithOnlyOneRenderer) { _clustersToDraw.Add(node); } else if (node.leaf == null) { _clustersToDraw.Add(node); } } if (node.distToMergedCentroid > largest) { largest = node.distToMergedCentroid; } if (node.height > 0 && node.distToMergedCentroid < smallest) { smallest = node.distToMergedCentroid; } } if (progFunc != null) progFunc("Building Clusters To Draw B:", 0); for (int i = 0; i < _clustersToDraw.Count; i++) { removeMe.Add(_clustersToDraw[i].cha); removeMe.Add(_clustersToDraw[i].chb); } for (int i = 0; i < removeMe.Count; i++) { _clustersToDraw.Remove(removeMe[i]); } _radii = new float[_clustersToDraw.Count]; if (progFunc != null) progFunc("Building Clusters To Draw C:", 0); for (int i = 0; i < _radii.Length; i++) { MB3_AgglomerativeClustering.ClusterNode n = _clustersToDraw[i]; Bounds b = new Bounds(n.centroid, Vector3.one); for (int j = 0; j < n.leafs.Length; j++) { Renderer r = cluster.clusters[n.leafs[j]].leaf.go.GetComponent(); if (r != null) { b.Encapsulate(r.bounds); } } _radii[i] = b.extents.magnitude; } if (progFunc != null) progFunc("Building Clusters To Draw D:", 0); _ObjsExtents = largest + 1f; _minDistBetweenClusters = Mathf.Lerp(smallest, 0f, .9f); if (_ObjsExtents < 2f) _ObjsExtents = 2f; } public override void DrawGizmos(Bounds sceneObjectBounds) { if (cluster == null || cluster.clusters == null) { return; } if (_lastMaxDistBetweenClusters != d.maxDistBetweenClusters) { float s, l; _BuildListOfClustersToDraw(null, out s, out l); _lastMaxDistBetweenClusters = d.maxDistBetweenClusters; } for (int i = 0; i < _clustersToDraw.Count; i++) { Gizmos.color = Color.white; MB3_AgglomerativeClustering.ClusterNode node = _clustersToDraw[i]; Gizmos.DrawWireSphere(node.centroid, _radii[i]); } } } }