//---------------------------------------------- // MeshBaker // Copyright © 2011-2012 Ian Deane //---------------------------------------------- using UnityEngine; using System.Collections; using System.Collections.Specialized; using System; using System.Collections.Generic; using System.Text; using DigitalOpus.MB.Core; namespace DigitalOpus.MB.Core { /// /// This class is an endless mesh. You don't need to worry about the 65k limit when adding meshes. It is like a List of combined meshes. Internally it manages /// a collection of MB2_MeshComber objects to which meshes added and deleted as necessary. /// /// Note that this implementation does /// not attempt to split meshes. Each mesh is added to one of the internal meshes as an atomic unit. /// /// This class is not a Component so it can be instantiated and used like a regular C Sharp class. /// [System.Serializable] public class MB3_MultiMeshCombiner : MB3_MeshCombiner { [System.Serializable] public class CombinedMesh { public MB3_MeshCombinerSingle combinedMesh; public int extraSpace = -1; public int numVertsInListToDelete = 0; public int numVertsInListToAdd = 0; public List gosToAdd; public List gosToDelete; public List gosToUpdate; public bool isDirty = false; //needs apply public CombinedMesh(int maxNumVertsInMesh, GameObject resultSceneObject, MB2_LogLevel ll) { combinedMesh = new MB3_MeshCombinerSingle(); combinedMesh.resultSceneObject = resultSceneObject; combinedMesh.LOG_LEVEL = ll; extraSpace = maxNumVertsInMesh; numVertsInListToDelete = 0; numVertsInListToAdd = 0; gosToAdd = new List(); gosToDelete = new List(); gosToUpdate = new List(); } public bool isEmpty() { List obsIn = new List(); obsIn.AddRange(combinedMesh.GetObjectsInCombined()); for (int i = 0; i < gosToDelete.Count; i++) { for (int j = 0; j < obsIn.Count; j++) { if (obsIn[j].GetInstanceID() == gosToDelete[i]) { obsIn.RemoveAt(j); break; } } } if (obsIn.Count == 0) return true; return false; } } static GameObject[] empty = new GameObject[0]; static int[] emptyIDs = new int[0]; public override MB2_LogLevel LOG_LEVEL { get { return _LOG_LEVEL; } set { _LOG_LEVEL = value; for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.LOG_LEVEL = value; } } } public override MB2_ValidationLevel validationLevel { set { _validationLevel = value; for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.validationLevel = _validationLevel; } } get { return _validationLevel; } } public Dictionary obj2MeshCombinerMap = new Dictionary(); [SerializeField] public List meshCombiners = new List(); [SerializeField] int _maxVertsInMesh = 65535; public int maxVertsInMesh { get { return _maxVertsInMesh; } set { if (obj2MeshCombinerMap.Count > 0) { //todo how to warn with gui //Debug.LogError("Can't set the max verts in meshes once there are objects in the mesh."); return; } else if (value < 3) { Debug.LogError("Max verts in mesh must be greater than three."); } else if (value > MBVersion.MaxMeshVertexCount()) { Debug.LogError("Meshes in unity cannot have more than " + MBVersion.MaxMeshVertexCount() + " vertices."); } else { _maxVertsInMesh = value; } } } public override int GetNumObjectsInCombined() { return obj2MeshCombinerMap.Count; } public override int GetNumVerticesFor(GameObject go) { CombinedMesh c = null; if (obj2MeshCombinerMap.TryGetValue(go.GetInstanceID(), out c)) { return c.combinedMesh.GetNumVerticesFor(go); } else { return -1; } } public override int GetNumVerticesFor(int gameObjectID) { CombinedMesh c = null; if (obj2MeshCombinerMap.TryGetValue(gameObjectID, out c)) { return c.combinedMesh.GetNumVerticesFor(gameObjectID); } else { return -1; } } public override List GetObjectsInCombined() { //todo look at getting from keys List allObjs = new List(); for (int i = 0; i < meshCombiners.Count; i++) { allObjs.AddRange(meshCombiners[i].combinedMesh.GetObjectsInCombined()); } return allObjs; } public override int GetLightmapIndex() { //todo check that all meshcombiners use same lightmap index if (meshCombiners.Count > 0) return meshCombiners[0].combinedMesh.GetLightmapIndex(); return -1; } public override bool CombinedMeshContains(GameObject go) { return obj2MeshCombinerMap.ContainsKey(go.GetInstanceID()); } bool _validateTextureBakeResults() { if (_textureBakeResults == null) { Debug.LogError("Texture Bake Results is null. Can't combine meshes."); return false; } if ((_textureBakeResults.materialsAndUVRects == null || _textureBakeResults.materialsAndUVRects.Length == 0)) { Debug.LogError("Texture Bake Results has no materials in material to sourceUVRect map. Try baking materials. Can't combine meshes."); return false; } if (_textureBakeResults.resultMaterials == null || _textureBakeResults.resultMaterials.Length == 0) { Debug.LogError("Texture Bake Results has no result materials. Try baking materials. Can't combine meshes."); return false; } return true; } public override void Apply(MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod) { for (int i = 0; i < meshCombiners.Count; i++) { if (meshCombiners[i].isDirty) { meshCombiners[i].combinedMesh.Apply(uv2GenerationMethod); meshCombiners[i].isDirty = false; } } } public override void Apply(bool triangles, bool vertices, bool normals, bool tangents, bool uvs, bool uv2, bool uv3, bool uv4, bool colors, bool bones = false, bool blendShapesFlag = false, MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod = null) { for (int i = 0; i < meshCombiners.Count; i++) { if (meshCombiners[i].isDirty) { meshCombiners[i].combinedMesh.Apply(triangles, vertices, normals, tangents, uvs, uv2, uv3, uv4, colors, bones, blendShapesFlag, uv2GenerationMethod); meshCombiners[i].isDirty = false; } } } public override void UpdateSkinnedMeshApproximateBounds() { for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBounds(); } } public override void UpdateSkinnedMeshApproximateBoundsFromBones() { for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBoundsFromBones(); } } public override void UpdateSkinnedMeshApproximateBoundsFromBounds() { for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBoundsFromBounds(); } } public override void UpdateGameObjects(GameObject[] gos, bool recalcBounds = true, bool updateVertices = true, bool updateNormals = true, bool updateTangents = true, bool updateUV = false, bool updateUV2 = false, bool updateUV3 = false, bool updateUV4 = false, bool updateColors = false, bool updateSkinningInfo = false) { if (gos == null) { Debug.LogError("list of game objects cannot be null"); return; } //build gos lists for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].gosToUpdate.Clear(); } for (int i = 0; i < gos.Length; i++) { CombinedMesh cm = null; obj2MeshCombinerMap.TryGetValue(gos[i].GetInstanceID(), out cm); if (cm != null) { cm.gosToUpdate.Add(gos[i]); } else { Debug.LogWarning("Object " + gos[i] + " is not in the combined mesh."); } } for (int i = 0; i < meshCombiners.Count; i++) { if (meshCombiners[i].gosToUpdate.Count > 0) { meshCombiners[i].isDirty = true; GameObject[] gosToUpdate = meshCombiners[i].gosToUpdate.ToArray(); meshCombiners[i].combinedMesh.UpdateGameObjects(gosToUpdate, recalcBounds, updateVertices, updateNormals, updateTangents, updateUV, updateUV2, updateUV3, updateUV4, updateColors, updateSkinningInfo); } } } public override bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource = true) { int[] delInstanceIDs = null; if (deleteGOs != null) { delInstanceIDs = new int[deleteGOs.Length]; for (int i = 0; i < deleteGOs.Length; i++) { if (deleteGOs[i] == null) { Debug.LogError("The " + i + "th object on the list of objects to delete is 'Null'"); } else { delInstanceIDs[i] = deleteGOs[i].GetInstanceID(); } } } return AddDeleteGameObjectsByID(gos, delInstanceIDs, disableRendererInSource); } public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true) { //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //PART 1 ==== Validate if (_usingTemporaryTextureBakeResult && gos != null && gos.Length > 0) { MB_Utility.Destroy(_textureBakeResults); _textureBakeResults = null; _usingTemporaryTextureBakeResult = false; } //if all objects use the same material we can create a temporary _textureBakeResults if (_textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null) { if (!_CreateTemporaryTextrueBakeResult(gos, GetMaterialsOnTargetRenderer())) { return false; } } if (!_validate(gos, deleteGOinstanceIDs)) { return false; } _distributeAmongBakers(gos, deleteGOinstanceIDs); if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: " + meshCombiners.Count + " added:" + gos + " deleted:" + deleteGOinstanceIDs + " disableRendererInSource:" + disableRendererInSource + " maxVertsPerCombined:" + _maxVertsInMesh); return _bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource); } bool _validate(GameObject[] gos, int[] deleteGOinstanceIDs) { if (_validationLevel == MB2_ValidationLevel.none) return true; if (_maxVertsInMesh < 3) Debug.LogError("Invalid value for maxVertsInMesh=" + _maxVertsInMesh); _validateTextureBakeResults(); if (gos != null) { for (int i = 0; i < gos.Length; i++) { if (gos[i] == null) { Debug.LogError("The " + i + "th object on the list of objects to combine is 'None'. Use Command-Delete on Mac OS X; Delete or Shift-Delete on Windows to remove this one element."); return false; } if (_validationLevel >= MB2_ValidationLevel.robust) { for (int j = i + 1; j < gos.Length; j++) { if (gos[i] == gos[j]) { Debug.LogError("GameObject " + gos[i] + "appears twice in list of game objects to add"); return false; } } if (obj2MeshCombinerMap.ContainsKey(gos[i].GetInstanceID())) { bool isInDeleteList = false; if (deleteGOinstanceIDs != null) { for (int k = 0; k < deleteGOinstanceIDs.Length; k++) { if (deleteGOinstanceIDs[k] == gos[i].GetInstanceID()) isInDeleteList = true; } } if (!isInDeleteList) { Debug.LogError("GameObject " + gos[i] + " is already in the combined mesh " + gos[i].GetInstanceID()); return false; } } } } } if (deleteGOinstanceIDs != null) { if (_validationLevel >= MB2_ValidationLevel.robust) { for (int i = 0; i < deleteGOinstanceIDs.Length; i++) { for (int j = i + 1; j < deleteGOinstanceIDs.Length; j++) { if (deleteGOinstanceIDs[i] == deleteGOinstanceIDs[j]) { Debug.LogError("GameObject " + deleteGOinstanceIDs[i] + "appears twice in list of game objects to delete"); return false; } } if (!obj2MeshCombinerMap.ContainsKey(deleteGOinstanceIDs[i])) { Debug.LogWarning("GameObject with instance ID " + deleteGOinstanceIDs[i] + " on the list of objects to delete is not in the combined mesh."); } } } } return true; } void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs) { if (gos == null) gos = empty; if (deleteGOinstanceIDs == null) deleteGOinstanceIDs = emptyIDs; if (resultSceneObject == null) resultSceneObject = new GameObject("CombinedMesh-" + name); //PART 2 ==== calculate which bakers to add objects to for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].extraSpace = _maxVertsInMesh - meshCombiners[i].combinedMesh.GetMesh().vertexCount; } //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.1"); //first delete game objects from the existing combinedMeshes keep track of free space for (int i = 0; i < deleteGOinstanceIDs.Length; i++) { CombinedMesh c = null; if (obj2MeshCombinerMap.TryGetValue(deleteGOinstanceIDs[i], out c)) { if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Removing " + deleteGOinstanceIDs[i] + " from meshCombiner " + meshCombiners.IndexOf(c)); c.numVertsInListToDelete += c.combinedMesh.GetNumVerticesFor(deleteGOinstanceIDs[i]); //m.vertexCount; c.gosToDelete.Add(deleteGOinstanceIDs[i]); } else { Debug.LogWarning("Object " + deleteGOinstanceIDs[i] + " in the list of objects to delete is not in the combined mesh."); } } for (int i = 0; i < gos.Length; i++) { GameObject go = gos[i]; int numVerts = MB_Utility.GetMesh(go).vertexCount; CombinedMesh cm = null; for (int j = 0; j < meshCombiners.Count; j++) { if (meshCombiners[j].extraSpace + meshCombiners[j].numVertsInListToDelete - meshCombiners[j].numVertsInListToAdd > numVerts) { cm = meshCombiners[j]; if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Added " + gos[i] + " to combinedMesh " + j, LOG_LEVEL); break; } } if (cm == null) { cm = new CombinedMesh(maxVertsInMesh, _resultSceneObject, _LOG_LEVEL); _setMBValues(cm.combinedMesh); meshCombiners.Add(cm); if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Created new combinedMesh"); } cm.gosToAdd.Add(go); cm.numVertsInListToAdd += numVerts; // obj2MeshCombinerMap.Add(go,cm); } } bool _bakeStep1(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource) { //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.2"); //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); //PART 3 ==== Add delete meshes from combined for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; if (cm.combinedMesh.targetRenderer == null) { cm.combinedMesh.resultSceneObject = _resultSceneObject; cm.combinedMesh.BuildSceneMeshObject(gos, true); if (_LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("BuildSO combiner {0} goID {1} targetRenID {2} meshID {3}", i, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); } else { if (cm.combinedMesh.targetRenderer.transform.parent != resultSceneObject.transform) { Debug.LogError("targetRender objects must be children of resultSceneObject"); return false; } } if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) { cm.combinedMesh.AddDeleteGameObjectsByID(cm.gosToAdd.ToArray(), cm.gosToDelete.ToArray(), disableRendererInSource); if (_LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Baked combiner {0} obsAdded {1} objsRemoved {2} goID {3} targetRenID {4} meshID {5}", i, cm.gosToAdd.Count, cm.gosToDelete.Count, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); } Renderer r = cm.combinedMesh.targetRenderer; Mesh m = cm.combinedMesh.GetMesh(); if (r is MeshRenderer) { MeshFilter mf = r.gameObject.GetComponent(); mf.sharedMesh = m; } else { SkinnedMeshRenderer smr = (SkinnedMeshRenderer)r; smr.sharedMesh = m; } } for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; for (int j = 0; j < cm.gosToDelete.Count; j++) { obj2MeshCombinerMap.Remove(cm.gosToDelete[j]); } } for (int i = 0; i < meshCombiners.Count; i++) { CombinedMesh cm = meshCombiners[i]; for (int j = 0; j < cm.gosToAdd.Count; j++) { obj2MeshCombinerMap.Add(cm.gosToAdd[j].GetInstanceID(), cm); } if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) { cm.gosToDelete.Clear(); cm.gosToAdd.Clear(); cm.numVertsInListToDelete = 0; cm.numVertsInListToAdd = 0; cm.isDirty = true; } } //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); if (LOG_LEVEL >= MB2_LogLevel.debug) { string s = "Meshes in combined:"; for (int i = 0; i < meshCombiners.Count; i++) { s += " mesh" + i + "(" + meshCombiners[i].combinedMesh.GetObjectsInCombined().Count + ")\n"; } s += "children in result: " + resultSceneObject.transform.childCount; MB2_Log.LogDebug(s, LOG_LEVEL); } if (meshCombiners.Count > 0) { return true; } else { return false; } } public override Dictionary BuildSourceBlendShapeToCombinedIndexMap() { Dictionary map = new Dictionary(); for (int combinerIdx = 0; combinerIdx < meshCombiners.Count; combinerIdx++) { for (int i = 0; i < meshCombiners[combinerIdx].combinedMesh.blendShapes.Length; i++) { MB3_MeshCombinerSingle.MBBlendShape bs = meshCombiners[combinerIdx].combinedMesh.blendShapes[i]; MBBlendShapeValue bsv = new MBBlendShapeValue(); bsv.combinedMeshGameObject = meshCombiners[combinerIdx].combinedMesh.targetRenderer.gameObject; bsv.blendShapeIndex = i; map.Add(new MBBlendShapeKey(bs.gameObjectID, bs.indexInSource), bsv); } } return map; } public override void ClearBuffers() { for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.ClearBuffers(); } obj2MeshCombinerMap.Clear(); } public override void ClearMesh() { DestroyMesh(); } public override void DestroyMesh() { for (int i = 0; i < meshCombiners.Count; i++) { if (meshCombiners[i].combinedMesh.targetRenderer != null) { MB_Utility.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject); } meshCombiners[i].combinedMesh.ClearMesh(); } obj2MeshCombinerMap.Clear(); meshCombiners.Clear(); } public override void DestroyMeshEditor(MB2_EditorMethodsInterface editorMethods) { for (int i = 0; i < meshCombiners.Count; i++) { if (meshCombiners[i].combinedMesh.targetRenderer != null) { editorMethods.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject); } meshCombiners[i].combinedMesh.ClearMesh(); } obj2MeshCombinerMap.Clear(); meshCombiners.Clear(); } void _setMBValues(MB3_MeshCombinerSingle targ) { targ.validationLevel = _validationLevel; targ.renderType = renderType; targ.outputOption = MB2_OutputOptions.bakeIntoSceneObject; targ.lightmapOption = lightmapOption; targ.textureBakeResults = textureBakeResults; targ.doNorm = doNorm; targ.doTan = doTan; targ.doCol = doCol; targ.doUV = doUV; targ.doUV3 = doUV3; targ.doUV4 = doUV4; targ.doBlendShapes = doBlendShapes; targ.optimizeAfterBake = optimizeAfterBake; targ.recenterVertsToBoundsCenter = recenterVertsToBoundsCenter; targ.uv2UnwrappingParamsHardAngle = uv2UnwrappingParamsHardAngle; targ.uv2UnwrappingParamsPackMargin = uv2UnwrappingParamsPackMargin; } public override List GetMaterialsOnTargetRenderer() { HashSet hs = new HashSet(); for (int i = 0; i < meshCombiners.Count; i++) { hs.UnionWith(meshCombiners[i].combinedMesh.GetMaterialsOnTargetRenderer()); } List outMats = new List(hs); return outMats; } public override void CheckIntegrity() { if (!MB_Utility.DO_INTEGRITY_CHECKS) return; for (int i = 0; i < meshCombiners.Count; i++) { meshCombiners[i].combinedMesh.CheckIntegrity(); } } } }