BITFALL/Assets/GSpawn - Level Designer/Scripts/Optimization/MeshCombiner.cs

503 lines
23 KiB
C#

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;
namespace GSpawn
{
public static class MeshCombiner
{
private class MaterialInstance
{
public Material material;
public List<MeshInstance> meshInstances = new List<MeshInstance>();
}
private class MeshInstance
{
public Mesh mesh;
public int subMeshIndex;
public Transform transform;
}
private class CombinedMeshData
{
public List<Vector3> positions;
public List<Color> colors;
public List<Vector4> tangents;
public List<Vector3> normals;
public List<Vector2> uv1;
public List<Vector2> uv2;
public List<Vector2> uv3;
public List<Vector2> uv4;
public List<int> indices;
public int currentVertexIndex;
public CombinedMeshData()
{
tangents = new List<Vector4>();
positions = new List<Vector3>();
normals = new List<Vector3>();
uv1 = new List<Vector2>();
uv2 = new List<Vector2>();
uv3 = new List<Vector2>();
uv4 = new List<Vector2>();
colors = new List<Color>();
indices = new List<int>();
}
public CombinedMeshData(int combinedNumVertsGuess)
{
tangents = new List<Vector4>(combinedNumVertsGuess);
positions = new List<Vector3>(combinedNumVertsGuess);
normals = new List<Vector3>(combinedNumVertsGuess);
uv1 = new List<Vector2>(combinedNumVertsGuess);
uv2 = new List<Vector2>(combinedNumVertsGuess);
uv3 = new List<Vector2>(combinedNumVertsGuess);
uv4 = new List<Vector2>(combinedNumVertsGuess);
colors = new List<Color>(combinedNumVertsGuess);
indices = new List<int>(combinedNumVertsGuess / 3);
}
public void reset()
{
tangents.Clear();
positions.Clear();
normals.Clear();
uv1.Clear();
uv2.Clear();
uv3.Clear();
uv4.Clear();
colors.Clear();
indices.Clear();
currentVertexIndex = 0;
}
public void addCurrentVertIndex()
{
// Note: Assumes that vertices are stored in the combined mesh buffers in the same
// way as they are encountered when reading the vertex data using indices
// from the source mesh.
indices.Add(currentVertexIndex++);
}
public void reverseWindingOrderForLastTriangle()
{
int lastIndexPtr = indices.Count - 1;
int tempIndex = indices[lastIndexPtr];
indices[lastIndexPtr] = indices[lastIndexPtr - 2];
indices[lastIndexPtr - 2] = tempIndex;
}
}
private static List<Mesh> _combinedMeshes = new List<Mesh>();
private static MeshCombineSettings _settings;
private static List<GameObject> _parentsBuffer = new List<GameObject>();
public static void combine(List<GameObject> sourceObjects, GameObject destinationParent, MeshCombineSettings settings)
{
if (sourceObjects.Count == 0)
{
EditorUtility.DisplayDialog("Missing Data", "No source objects available.", "Ok");
return;
}
if (destinationParent == null)
{
EditorUtility.DisplayDialog("Missing Data", "You must specify a destination parent that will hold all meshes that result from the mesh combine process.", "Ok");
return;
}
if (!settings.combineStaticMeshes && !settings.combineDynamicMeshes)
{
EditorUtility.DisplayDialog("Invalid Data", "You have specified that neither static nor dynamic meshes should be combined. " +
"At least one of these (i.e. static or dynamic) should be allowed.", "Ok");
return;
}
_combinedMeshes.Clear();
_settings = settings;
GameObjectEx.getParents(sourceObjects, _parentsBuffer);
List<GameObject> meshObjects = new List<GameObject>();
collectMeshObjects(_parentsBuffer, meshObjects);
if (meshObjects.Count == 0)
{
EditorUtility.DisplayDialog("Nothing Combined", "There were no meshes combined.", "Ok");
return;
}
combineMeshObjects(meshObjects, destinationParent);
}
public static void combineChildren(GameObject sourceParent, GameObject destinationParent, MeshCombineSettings settings)
{
if (sourceParent == null)
{
EditorUtility.DisplayDialog("Missing Data", "You must specify a source parent whose child meshes must be combined.", "Ok");
return;
}
if (destinationParent == null)
{
EditorUtility.DisplayDialog("Missing Data", "You must specify a destination parent that will hold all meshes that result from the mesh combine process.", "Ok");
return;
}
if (!settings.combineStaticMeshes && !settings.combineDynamicMeshes)
{
EditorUtility.DisplayDialog("Invalid Data", "You have specified that neither static nor dynamic meshes should be combined. " +
"At least one of these (i.e. static or dynamic) should be allowed.", "Ok");
return;
}
_combinedMeshes.Clear();
_settings = settings;
List<GameObject> meshObjects = new List<GameObject>();
collectMeshObjects(sourceParent, meshObjects, false);
if (meshObjects.Count == 0)
{
EditorUtility.DisplayDialog("Nothing Combined", "There were no meshes combined.", "Ok");
return;
}
combineMeshObjects(meshObjects, destinationParent);
}
private static void combineMeshObjects(List<GameObject> meshObjects, GameObject destinationParent)
{
List<MaterialInstance> materialInstances = new List<MaterialInstance>();
collectMaterialInstances(meshObjects, materialInstances);
// Note: Create the combined mesh folder if not present.
if (!FileSystem.folderExists(_settings.combinedMeshFolder)) AssetDbEx.createAssetFolder(_settings.combinedMeshFolder);
UndoEx.saveEnabledState();
UndoEx.enabled = false;
int numMaterialInstances = materialInstances.Count;
for (int i = 0; i < numMaterialInstances; ++i)
combine(materialInstances[i], destinationParent);
int numMeshObjects = meshObjects.Count;
PluginProgressDialog.begin("Post-Processing Mesh Objects");
if (_settings.disableSourceRenderers)
{
for (int i = 0; i < numMeshObjects; ++i)
{
PluginProgressDialog.updateProgress(meshObjects[i].name, (i + 1) / (float)numMeshObjects);
MeshRenderer r = meshObjects[i].getMeshRenderer();
if (r != null) r.enabled = false;
}
}
PluginProgressDialog.end();
int numCombinedMeshes = _combinedMeshes.Count;
if (numCombinedMeshes != 0)
{
PluginProgressDialog.begin("Saving Mesh Assets");
for (int i = 0; i < numCombinedMeshes; ++i)
{
Mesh mesh = _combinedMeshes[i];
PluginProgressDialog.updateProgress(mesh.name, (i + 1) / (float)numCombinedMeshes);
AssetDatabase.CreateAsset(mesh, _settings.combinedMeshFolder + "/" + mesh.name + ".asset");
}
AssetDatabase.SaveAssets();
PluginProgressDialog.end();
}
UndoEx.restoreEnabledState();
}
private static void collectMeshObjects(List<GameObject> parentObjects, List<GameObject> meshObjects)
{
meshObjects.Clear();
PluginProgressDialog.begin("Collecting Mesh Objects");
int numParentObjects = parentObjects.Count;
for (int i = 0; i < numParentObjects; ++i)
{
var parent = parentObjects[i];
collectMeshObjects(parent, meshObjects, true);
PluginProgressDialog.updateProgress(parent.name, (i + 1) / (float)numParentObjects);
}
PluginProgressDialog.end();
}
private static void collectMeshObjects(GameObject sourceParent, List<GameObject> meshObjects, bool append)
{
if (!append)
{
meshObjects.Clear();
PluginProgressDialog.begin("Collecting Mesh Objects");
var meshRenderers = sourceParent.GetComponentsInChildren<MeshRenderer>(false);
int numRenderers = meshRenderers.Length;
for (int i = 0; i < numRenderers; ++i)
{
var r = meshRenderers[i];
PluginProgressDialog.updateProgress(r.gameObject.name, (i + 1) / (float)numRenderers);
if (canMeshObjectBeCombined(r, sourceParent))
meshObjects.Add(r.gameObject);
}
PluginProgressDialog.end();
}
else
{
var meshRenderers = sourceParent.GetComponentsInChildren<MeshRenderer>(false);
int numRenderers = meshRenderers.Length;
for (int i = 0; i < numRenderers; ++i)
{
var r = meshRenderers[i];
if (canMeshObjectBeCombined(r, sourceParent))
meshObjects.Add(r.gameObject);
}
}
}
private static void collectMaterialInstances(List<GameObject> meshObjects, List<MaterialInstance> materialInstances)
{
materialInstances.Clear();
var materialInstanceMap = new Dictionary<Material, MaterialInstance>();
PluginProgressDialog.begin("Collecting Material Instances");
int numMeshObjects = meshObjects.Count;
for (int i = 0; i < numMeshObjects; ++i)
{
var meshObject = meshObjects[i];
PluginProgressDialog.updateProgress(meshObject.name, (i + 1) / (float)numMeshObjects);
MeshFilter meshFilter = meshObject.getMeshFilter();
MeshRenderer meshRenderer;
if (_settings.combineLODs)
{
int lodIndex = meshObject.findLODIndexAndMeshRenderer(out meshRenderer);
if (lodIndex < 0) meshRenderer = meshObject.getMeshRenderer();
else if (lodIndex != _settings.lodIndex) continue;
}
else meshRenderer = meshObject.getMeshRenderer();
Mesh sharedMesh = meshFilter.sharedMesh;
int numSharedMaterials = meshRenderer.sharedMaterials.Length;
for (int subMeshIndex = 0; subMeshIndex < sharedMesh.subMeshCount; ++subMeshIndex)
{
// Note: How can this happen?
if (subMeshIndex >= numSharedMaterials) break;
// Note: Only accepts triangle meshes.
if (sharedMesh.GetTopology(subMeshIndex) != MeshTopology.Triangles) continue;
Material material = meshRenderer.sharedMaterials[subMeshIndex];
MaterialInstance materialInstance;
if (!materialInstanceMap.ContainsKey(material))
{
materialInstance = new MaterialInstance();
materialInstance.material = material;
materialInstanceMap.Add(material, materialInstance);
materialInstances.Add(materialInstance);
}
else materialInstance = materialInstanceMap[material];
var meshInstance = new MeshInstance();
meshInstance.mesh = sharedMesh;
meshInstance.subMeshIndex = subMeshIndex;
meshInstance.transform = meshObject.transform;
materialInstance.meshInstances.Add(meshInstance);
}
}
PluginProgressDialog.end();
}
private static bool canMeshObjectBeCombined(Renderer renderer, GameObject sourceParent)
{
if (!renderer.enabled) return false;
GameObject gameObject = renderer.gameObject;
if (!gameObject.activeInHierarchy) return false;
if (!_settings.combineStaticMeshes && gameObject.isStatic) return false;
if (!_settings.combineDynamicMeshes && !gameObject.isStatic) return false;
MeshFilter meshFilter = gameObject.getMeshFilter();
if (meshFilter == null || meshFilter.sharedMesh == null) return false;
if (_settings.ignoreMultiLevelHierarchies)
{
if (sourceParent != null)
{
if ((gameObject.transform.parent != null && gameObject.transform.parent.gameObject != sourceParent) ||
gameObject.transform.childCount != 0) return false;
}
else if (gameObject.transform.parent != null || gameObject.transform.childCount != 0) return false;
}
if (!_settings.combineLODs)
{
if (gameObject.isPartOfLODGroup()) return false;
}
return true;
}
private static int getMaxNumberOfMeshVerts()
{
if (_settings.combinedIndexFormat == MeshCombineIndexFormat.UInt16)
{
if (_settings.generateLightmapUVs) return 32000;
return 65000;
}
else
{
if (_settings.generateLightmapUVs) return 4000000;
return 8000000;
// Note: These values seem to be too large and cause Unity to crash
// when saving the meshes as assets.
/*
if (_settings.generateLightmapUVs) return 1000000000;
return 2000000000;*/
}
}
private static void combine(MaterialInstance materialInstance, GameObject destinationParent)
{
List<MeshInstance> meshInstances = materialInstance.meshInstances;
if (meshInstances.Count == 0) return;
int maxNumMeshVerts = getMaxNumberOfMeshVerts();
var combinedMeshData = new CombinedMeshData();
PluginProgressDialog.begin("Combining Meshes for Material: " + materialInstance.material.name);
List<GameObject> combinedMeshObjects = new List<GameObject>();
for (int meshInstanceIndex = 0; meshInstanceIndex < meshInstances.Count; ++meshInstanceIndex)
{
MeshInstance meshInstance = meshInstances[meshInstanceIndex];
Mesh mesh = meshInstance.mesh;
if (mesh.vertexCount == 0) continue;
PluginProgressDialog.updateProgress("Mesh: " + meshInstance.mesh.name, (meshInstanceIndex + 1) / (float)meshInstances.Count);
Matrix4x4 worldMatrix = meshInstance.transform.localToWorldMatrix;
Matrix4x4 worldInverseTranspose = worldMatrix.inverse.transpose;
Vector3 worldScale = meshInstance.transform.lossyScale;
bool reverseVertexWindingOrder = (worldScale.countNegative() % 2 != 0);
int[] subMeshVertIndices = mesh.GetTriangles(meshInstance.subMeshIndex);
if (subMeshVertIndices.Length == 0) continue;
Vector3[] positions = mesh.vertices;
Color[] colors = mesh.colors;
Vector4[] tangents = mesh.tangents;
Vector3[] normals = mesh.normals;
Vector2[] uv1 = mesh.uv;
Vector2[] uv2 = mesh.uv2;
Vector2[] uv3 = mesh.uv3;
Vector2[] uv4 = mesh.uv4;
foreach (var vertIndex in subMeshVertIndices)
{
if (tangents.Length != 0)
{
Vector3 transformedTangent = new Vector3(tangents[vertIndex].x, tangents[vertIndex].y, tangents[vertIndex].z);
transformedTangent = worldInverseTranspose.MultiplyVector(transformedTangent);
transformedTangent.Normalize();
combinedMeshData.tangents.Add(new Vector4(transformedTangent.x, transformedTangent.y, transformedTangent.z, tangents[vertIndex].w));
}
if (normals.Length != 0)
{
Vector3 transformedNormal = worldInverseTranspose.MultiplyVector(normals[vertIndex]);
transformedNormal.Normalize();
combinedMeshData.normals.Add(transformedNormal);
}
if (positions.Length != 0) combinedMeshData.positions.Add(worldMatrix.MultiplyPoint(positions[vertIndex]));
if (colors.Length != 0) combinedMeshData.colors.Add(colors[vertIndex]);
if (uv1.Length != 0) combinedMeshData.uv1.Add(uv1[vertIndex]);
if (uv3.Length != 0) combinedMeshData.uv2.Add(uv3[vertIndex]);
if (uv4.Length != 0) combinedMeshData.uv3.Add(uv4[vertIndex]);
if (uv2.Length != 0 && !_settings.generateLightmapUVs) combinedMeshData.uv2.Add(uv2[vertIndex]);
combinedMeshData.addCurrentVertIndex();
int numIndices = combinedMeshData.indices.Count;
if (reverseVertexWindingOrder && numIndices % 3 == 0) combinedMeshData.reverseWindingOrderForLastTriangle();
int numMeshVerts = combinedMeshData.positions.Count;
if (combinedMeshData.indices.Count % 3 == 0 && (maxNumMeshVerts - numMeshVerts) < 3)
{
combinedMeshObjects.Add(createCombinedMeshObject(combinedMeshData, materialInstance, destinationParent));
combinedMeshData.reset();
}
}
}
PluginProgressDialog.end();
combinedMeshObjects.Add(createCombinedMeshObject(combinedMeshData, materialInstance, destinationParent));
}
private static GameObject createCombinedMeshObject(CombinedMeshData combinedMeshData, MaterialInstance materialInstance, GameObject destinationParent)
{
Mesh combinedMesh = createCombinedMesh(combinedMeshData, materialInstance);
string baseName = _settings.combinedMeshObjectBaseName;
if (baseName == null) baseName = string.Empty;
GameObject combinedMeshObject = new GameObject(baseName + materialInstance.material.name);
combinedMeshObject.transform.parent = destinationParent.transform;
combinedMeshObject.isStatic = _settings.combineAsStatic ? true : false;
MeshFilter meshFilter = combinedMeshObject.AddComponent<MeshFilter>();
meshFilter.sharedMesh = combinedMesh;
MeshRenderer meshRenderer = combinedMeshObject.AddComponent<MeshRenderer>();
meshRenderer.sharedMaterial = materialInstance.material;
var boundsQConfig = ObjectBounds.QueryConfig.defaultConfig;
boundsQConfig.objectTypes = GameObjectType.Mesh;
AABB combinedAABB = ObjectBounds.calcWorldAABB(combinedMeshObject, boundsQConfig);
Vector3 meshPivotPt;
if (_settings.combinedMeshPivot == MeshCombinePivot.Center) meshPivotPt = combinedAABB.center;
else if (_settings.combinedMeshPivot == MeshCombinePivot.BackCenter) meshPivotPt = Box3D.calcFaceCenter(combinedAABB.center, combinedAABB.size, Box3DFace.Back);
else if (_settings.combinedMeshPivot == MeshCombinePivot.FrontCenter) meshPivotPt = Box3D.calcFaceCenter(combinedAABB.center, combinedAABB.size, Box3DFace.Front);
else if (_settings.combinedMeshPivot == MeshCombinePivot.BottomCenter) meshPivotPt = Box3D.calcFaceCenter(combinedAABB.center, combinedAABB.size, Box3DFace.Bottom);
else if (_settings.combinedMeshPivot == MeshCombinePivot.TopCenter) meshPivotPt = Box3D.calcFaceCenter(combinedAABB.center, combinedAABB.size, Box3DFace.Top);
else if (_settings.combinedMeshPivot == MeshCombinePivot.LeftCenter) meshPivotPt = Box3D.calcFaceCenter(combinedAABB.center, combinedAABB.size, Box3DFace.Left);
else meshPivotPt = Box3D.calcFaceCenter(combinedAABB.center, combinedAABB.size, Box3DFace.Right);
combinedMeshObject.setMeshPivotPoint(combinedMesh, meshPivotPt);
return combinedMeshObject;
}
private static Mesh createCombinedMesh(CombinedMeshData combinedMeshData, MaterialInstance materialInstance)
{
Mesh combinedMesh = new Mesh();
combinedMesh.name = _settings.combinedMeshBaseName + materialInstance.material.name + "_" + combinedMesh.GetHashCode();
combinedMesh.indexFormat = _settings.combinedIndexFormat == MeshCombineIndexFormat.UInt32 ? IndexFormat.UInt32 : IndexFormat.UInt16;
combinedMesh.vertices = combinedMeshData.positions.ToArray();
if (combinedMeshData.tangents.Count != 0) combinedMesh.tangents = combinedMeshData.tangents.ToArray();
if (combinedMeshData.normals.Count != 0) combinedMesh.normals = combinedMeshData.normals.ToArray();
if (combinedMeshData.uv1.Count != 0) combinedMesh.uv = combinedMeshData.uv1.ToArray();
if (combinedMeshData.uv3.Count != 0) combinedMesh.uv3 = combinedMeshData.uv3.ToArray();
if (combinedMeshData.uv4.Count != 0) combinedMesh.uv4 = combinedMeshData.uv4.ToArray();
combinedMesh.SetIndices(combinedMeshData.indices.ToArray(), MeshTopology.Triangles, 0);
if (_settings.generateLightmapUVs) Unwrapping.GenerateSecondaryUVSet(combinedMesh);
else if (combinedMeshData.uv2.Count != 0) combinedMesh.uv2 = combinedMeshData.uv2.ToArray();
combinedMesh.UploadMeshData(!_settings.combinedMeshesAreReadable);
_combinedMeshes.Add(combinedMesh);
return combinedMesh;
}
}
}
#endif