1330 lines
54 KiB
C#
1330 lines
54 KiB
C#
/* SimpleLOD 1.5a */
|
|
/* By Orbcreation BV */
|
|
/* Richard Knol */
|
|
/* March 18, 2015 */
|
|
|
|
/* Note: if you also use other packages by Orbcreation, */
|
|
/* you may end up with multiple copies of this file. */
|
|
/* In that case, better delete/merge those files into 1. */
|
|
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
#if UNITY_WP8
|
|
#elif UNITY_WP_8_1
|
|
#elif UNITY_WSA
|
|
#elif UNITY_WSA_8_0
|
|
#elif UNITY_WSA_8_1
|
|
#elif UNITY_WINRT
|
|
#elif UNITY_WINRT_8_0
|
|
#elif UNITY_WINRT_8_1
|
|
#elif NETFX_CORE
|
|
#else
|
|
using System.Threading;
|
|
#endif
|
|
|
|
using LOD = UnityEngine.LOD; // This is needed when SimpleLOD is used in combination with SmartLOD
|
|
|
|
namespace OrbCreationExtensions
|
|
{
|
|
public static class GameObjectExtensions
|
|
{
|
|
|
|
public static Bounds GetWorldBounds(this GameObject go) {
|
|
if(go.transform == null) return new Bounds();
|
|
Bounds goBounds = new Bounds(go.transform.position, Vector3.zero);
|
|
Renderer[] renderers = go.GetComponentsInChildren<Renderer>(true);
|
|
if(renderers != null && renderers.Length > 0) goBounds = renderers[0].bounds;
|
|
foreach (Renderer r in renderers) {
|
|
Bounds bounds = r.bounds;
|
|
goBounds.Encapsulate(bounds);
|
|
}
|
|
return goBounds;
|
|
}
|
|
// Bounds are always in world coordinates
|
|
// Bounds always gives a rectangle that is straight with global x,y,z axis.
|
|
// So when you rotate a cude with 45 degrees, its bounds will get bigger
|
|
// localScale is incorporated, so when you increase the scale, the bounds will increase too
|
|
// But watch out:
|
|
// When you move or alter a GameObject that is set to inactive (activeSelf == false), the bounds are not updated!!!
|
|
|
|
|
|
public static Vector3[] GetBoundsCorners(this Bounds bounds) {
|
|
Vector3[] corners = new Vector3[8];
|
|
for(int i=0;i<8;i++) {
|
|
corners[i] = bounds.min;
|
|
if((i & 1) > 0) corners[i].x += bounds.size.x;
|
|
if((i & 2) > 0) corners[i].y += bounds.size.y;
|
|
if((i & 4) > 0) corners[i].z += bounds.size.z;
|
|
}
|
|
return corners;
|
|
}
|
|
public static Vector3[] GetBoundsCenterAndCorners(this Bounds bounds) {
|
|
Vector3[] corners = new Vector3[9];
|
|
corners[0] = bounds.center;
|
|
for(int i=1;i<9;i++) {
|
|
corners[i] = bounds.min;
|
|
if((i & 1) > 0) corners[i].x += bounds.size.x;
|
|
if((i & 2) > 0) corners[i].y += bounds.size.y;
|
|
if((i & 4) > 0) corners[i].z += bounds.size.z;
|
|
}
|
|
return corners;
|
|
}
|
|
|
|
public static Vector3[] GetWorldBoundsCorners(this GameObject go) {
|
|
return GetBoundsCorners(GetWorldBounds(go));
|
|
}
|
|
// These are in world coordinates
|
|
|
|
public static Vector3[] GetWorldBoundsCenterAndCorners(this GameObject go) {
|
|
return GetBoundsCenterAndCorners(GetWorldBounds(go));
|
|
}
|
|
|
|
public static float GetModelComplexity(this GameObject go) {
|
|
float complexity = 0f;
|
|
MeshFilter[] meshFilters = go.GetComponentsInChildren<MeshFilter>(true);
|
|
foreach (MeshFilter mf in meshFilters) {
|
|
Mesh mesh = mf.sharedMesh;
|
|
float multiplier = 1f;
|
|
for(int i=0;i<mesh.subMeshCount;i++) {
|
|
complexity += multiplier * mesh.GetTriangles(i).Length / 3f / 65536f;
|
|
// nog iets doen met de shader
|
|
multiplier *= 1.1f; // more materials, higher weight
|
|
}
|
|
}
|
|
return complexity; // 1 model with 1 submesh of 64K vertices = 1.0f
|
|
}
|
|
|
|
public static string GetModelInfoString(this GameObject go) {
|
|
string infoString = "";
|
|
int meshCount = 0;
|
|
int subMeshCount = 0;
|
|
int vertexCount = 0;
|
|
int triangleCount = 0;
|
|
Bounds bounds = GetWorldBounds(go);
|
|
MeshFilter[] meshFilters = go.GetComponentsInChildren<MeshFilter>(true);
|
|
foreach (MeshFilter mf in meshFilters) {
|
|
Mesh mesh = mf.sharedMesh;
|
|
meshCount++;
|
|
subMeshCount += mesh.subMeshCount;
|
|
vertexCount += mesh.vertices.Length;
|
|
triangleCount += mesh.triangles.Length / 3;
|
|
}
|
|
infoString = infoString + meshCount + " meshes\n";
|
|
infoString = infoString + subMeshCount + " submeshes\n";
|
|
infoString = infoString + vertexCount + " vertices\n";
|
|
infoString = infoString + triangleCount + " triangles\n";
|
|
infoString = infoString + bounds.size + " meters";
|
|
return infoString;
|
|
}
|
|
|
|
public static GameObject TopParent(this GameObject go) {
|
|
Transform parentTransform = go.transform.parent;
|
|
if(parentTransform == null) return go;
|
|
return TopParent(parentTransform.gameObject);
|
|
}
|
|
public static GameObject FindParentWithName(this GameObject go, string parentName) {
|
|
if(go.name == parentName) return go;
|
|
Transform parentTransform = go.transform.parent;
|
|
if(parentTransform == null) return null;
|
|
return FindParentWithName(parentTransform.gameObject, parentName);
|
|
}
|
|
public static GameObject FindMutualParent(this GameObject go1, GameObject go2) {
|
|
if(go2 == null || go1 == go2) return null;
|
|
Transform checkTrans = go2.transform;
|
|
while(checkTrans != null) {
|
|
if(go1 == checkTrans.gameObject) return go1;
|
|
checkTrans = checkTrans.parent;
|
|
}
|
|
Transform parent1 = go1.transform.parent;
|
|
if(parent1 == null) return null;
|
|
return FindMutualParent(parent1.gameObject, go2);
|
|
}
|
|
|
|
public static GameObject FindFirstChildWithName(this GameObject go, string childName) {
|
|
if(go.name == childName) return go;
|
|
foreach(Transform child in go.transform) {
|
|
GameObject childWithName = FindFirstChildWithName(child.gameObject, childName);
|
|
if(childWithName != null) return childWithName;
|
|
}
|
|
return null;
|
|
}
|
|
public static bool IsChildWithNameUnique(this GameObject go, string childName) {
|
|
int total = 0;
|
|
CountChildrenWithName(go, childName, ref total);
|
|
return (total <= 1);
|
|
}
|
|
public static void CountChildrenWithName(this GameObject go, string childName, ref int total) {
|
|
if(go.name == childName) total++;
|
|
foreach(Transform child in go.transform) {
|
|
CountChildrenWithName(child.gameObject, childName, ref total);
|
|
}
|
|
}
|
|
|
|
public static void DestroyChildren(this GameObject go, bool disabledOnly) {
|
|
List<Transform> children = new List<Transform>();
|
|
foreach(Transform child in go.transform) {
|
|
if((!child.gameObject.activeSelf) || (!disabledOnly)) children.Add(child);
|
|
}
|
|
// we do it this way because you cant alter the array in a foreach loop
|
|
for(int i=children.Count-1;i>=0;i--) {
|
|
#if UNITY_4_3
|
|
children[i].parent = null;
|
|
#elif UNITY_4_4
|
|
children[i].parent = null;
|
|
#elif UNITY_4_5
|
|
children[i].parent = null;
|
|
#else
|
|
children[i].SetParent(null);
|
|
#endif
|
|
GameObject.Destroy(children[i].gameObject);
|
|
}
|
|
}
|
|
|
|
public static T GetFirstComponentInParents<T>(this GameObject go) where T : Component {
|
|
T component = go.GetComponent<T>();
|
|
if(component != null) return component;
|
|
if(go.transform.parent != null && go.transform.parent.gameObject != go) return GetFirstComponentInParents<T>(go.transform.parent.gameObject);
|
|
return null;
|
|
}
|
|
|
|
public static T GetFirstComponentInChildren<T>(this GameObject go) where T : Component {
|
|
T[] components = go.GetComponentsInChildren<T>();
|
|
if(components != null && components.Length>0) return components[0];
|
|
return null;
|
|
}
|
|
|
|
public static Mesh[] GetMeshes(this GameObject aGo) {
|
|
return GetMeshes(aGo, true);
|
|
}
|
|
public static Mesh[] GetMeshes(this GameObject aGo, bool includeDisabled) {
|
|
MeshFilter[] mfs = aGo.GetComponentsInChildren<MeshFilter>(includeDisabled);
|
|
SkinnedMeshRenderer[] smr = aGo.GetComponentsInChildren<SkinnedMeshRenderer>(includeDisabled);
|
|
int newLen = 0;
|
|
if(mfs != null) newLen += mfs.Length;
|
|
if(smr != null) newLen += smr.Length;
|
|
if(newLen == 0) return null;
|
|
Mesh[] meshes = new Mesh[newLen];
|
|
int i = 0;
|
|
for(;mfs != null && i<mfs.Length;i++) {
|
|
meshes[i] = mfs[i].sharedMesh;
|
|
}
|
|
int offset = i;
|
|
for(i=0;smr != null && i<smr.Length;i++) {
|
|
meshes[i+offset] = smr[i].sharedMesh;
|
|
}
|
|
return meshes;
|
|
}
|
|
|
|
public static int GetTotalVertexCount(this GameObject aGo) {
|
|
MeshFilter[] mfs = aGo.GetComponentsInChildren<MeshFilter>(false);
|
|
SkinnedMeshRenderer[] smr = aGo.GetComponentsInChildren<SkinnedMeshRenderer>(false);
|
|
int totalVertexCount = 0;
|
|
for(int i=0;mfs != null && i<mfs.Length;i++) {
|
|
Mesh mesh = mfs[i].sharedMesh;
|
|
if(mesh != null) totalVertexCount += mesh.vertexCount;
|
|
}
|
|
for(int i=0;smr != null && i<smr.Length;i++) {
|
|
Mesh mesh = smr[i].sharedMesh;
|
|
if(mesh != null) totalVertexCount += mesh.vertexCount;
|
|
}
|
|
return totalVertexCount;
|
|
}
|
|
|
|
public static Mesh Get1stSharedMesh(this GameObject aGo) {
|
|
MeshFilter[] mfs = aGo.GetComponentsInChildren<MeshFilter>(false);
|
|
for(int i=0;mfs != null && i<mfs.Length;i++) {
|
|
if(mfs[i].sharedMesh != null) return mfs[i].sharedMesh;
|
|
}
|
|
SkinnedMeshRenderer[] smrs = aGo.GetComponentsInChildren<SkinnedMeshRenderer>(false);
|
|
for(int i=0;smrs != null && i<smrs.Length;i++) {
|
|
if(smrs[i].sharedMesh != null) return smrs[i].sharedMesh;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static void SetMeshes(this GameObject aGo, Mesh[] meshes) {
|
|
SetMeshes(aGo, meshes, true, 0);
|
|
}
|
|
public static void SetMeshes(this GameObject aGo, Mesh[] meshes, int lodLevel) {
|
|
SetMeshes(aGo, meshes, true, lodLevel);
|
|
}
|
|
public static void SetMeshes(this GameObject aGo, Mesh[] meshes, bool includeDisabled, int lodLevel) {
|
|
MeshFilter[] mfs = aGo.GetComponentsInChildren<MeshFilter>(includeDisabled);
|
|
SkinnedMeshRenderer[] smr = aGo.GetComponentsInChildren<SkinnedMeshRenderer>(includeDisabled);
|
|
int meshLen = 0;
|
|
if(mfs != null) meshLen += mfs.Length;
|
|
if(smr != null) meshLen += smr.Length;
|
|
if(meshLen == 0) return;
|
|
int i = 0;
|
|
for(;mfs != null && i<mfs.Length;i++) {
|
|
LODSwitcher lodSwitcher = mfs[i].gameObject.GetComponent<LODSwitcher>();
|
|
if(meshes != null && meshes.Length>i) {
|
|
if(lodLevel == 0) mfs[i].sharedMesh = meshes[i];
|
|
if(lodSwitcher == null && lodLevel > 0) {
|
|
lodSwitcher = mfs[i].gameObject.AddComponent<LODSwitcher>();
|
|
lodSwitcher.SetMesh(mfs[i].sharedMesh, 0);
|
|
}
|
|
if(lodSwitcher != null) lodSwitcher.SetMesh(meshes[i], lodLevel);
|
|
} else {
|
|
if(lodSwitcher != null) lodSwitcher.SetMesh(null, lodLevel);
|
|
if(lodLevel == 0) mfs[i].sharedMesh = null;
|
|
}
|
|
}
|
|
int offset = i;
|
|
for(i=0;smr != null && i<smr.Length;i++) {
|
|
LODSwitcher lodSwitcher = smr[i].gameObject.GetComponent<LODSwitcher>();
|
|
if(meshes != null && meshes.Length>i+offset) {
|
|
if(lodLevel == 0) smr[i].sharedMesh = meshes[i + offset];
|
|
if(lodSwitcher == null && lodLevel > 0) {
|
|
lodSwitcher = smr[i].gameObject.AddComponent<LODSwitcher>();
|
|
lodSwitcher.SetMesh(smr[i].sharedMesh, 0);
|
|
}
|
|
if(lodSwitcher != null) lodSwitcher.SetMesh(meshes[i + offset], lodLevel);
|
|
} else {
|
|
if(lodSwitcher != null) lodSwitcher.SetMesh(null, lodLevel);
|
|
if(lodLevel == 0) smr[i].sharedMesh = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static Material[] GetMaterials(this GameObject aGo, bool includeDisabled) {
|
|
List<Material> materials = new List<Material>();
|
|
MeshRenderer[] mrs = aGo.GetComponentsInChildren<MeshRenderer>(includeDisabled);
|
|
for(int i=0;i<mrs.Length;i++) {
|
|
materials.AddRange(mrs[i].sharedMaterials);
|
|
}
|
|
SkinnedMeshRenderer[] smr = aGo.GetComponentsInChildren<SkinnedMeshRenderer>(includeDisabled);
|
|
for(int i=0;i<smr.Length;i++) {
|
|
materials.AddRange(smr[i].sharedMaterials);
|
|
}
|
|
return materials.ToArray();
|
|
}
|
|
|
|
// Unity Mesh.CombineMeshes function sucks. It makes 1 submesh for each mesh
|
|
// but if the meshes already have submeshes, this info gets lost
|
|
// My function combines all submeshes of all child gameObjects and merges them per unique material
|
|
public static Mesh[] CombineMeshes(this GameObject aGO) {
|
|
return CombineMeshes(aGO, new string[0] {});
|
|
}
|
|
public static Mesh[] CombineMeshes(this GameObject aGO, string[] skipSubmeshNames, bool makeNewGameObjectWhenRendererPresent = true) {
|
|
List<Mesh> meshes = new List<Mesh>();
|
|
MeshRenderer[] meshRenderers = aGO.GetComponentsInChildren<MeshRenderer>(false);
|
|
SkinnedMeshRenderer[] skinnedMeshRenderers = aGO.GetComponentsInChildren<SkinnedMeshRenderer>(false);
|
|
int totalVertexCount = 0;
|
|
int totalMeshCount = 0;
|
|
int totalMeshCountAnyLightmapIdx = 0;
|
|
int lightmapIndex = -999;
|
|
bool makeNewGameObject = false;
|
|
if(aGO.GetComponent<SkinnedMeshRenderer>() != null || aGO.GetComponent<MeshRenderer>() != null) makeNewGameObject = makeNewGameObjectWhenRendererPresent;
|
|
|
|
if(skinnedMeshRenderers != null && skinnedMeshRenderers.Length > 0) {
|
|
foreach(SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) {
|
|
if(skinnedMeshRenderer.sharedMesh != null) {
|
|
totalVertexCount += skinnedMeshRenderer.sharedMesh.vertexCount;
|
|
totalMeshCount++;
|
|
totalMeshCountAnyLightmapIdx++;
|
|
}
|
|
}
|
|
}
|
|
if(meshRenderers != null && meshRenderers.Length > 0) {
|
|
foreach(MeshRenderer meshRenderer in meshRenderers) {
|
|
MeshFilter filter = meshRenderer.gameObject.GetComponent<MeshFilter>();
|
|
if(filter != null && filter.sharedMesh != null) {
|
|
if(lightmapIndex == -999 && meshRenderer.lightmapIndex >= 0 && meshRenderer.lightmapIndex <= 253) {
|
|
lightmapIndex = meshRenderer.lightmapIndex;
|
|
}
|
|
if(lightmapIndex<0 || meshRenderer.lightmapIndex<0 || meshRenderer.lightmapIndex>253 || lightmapIndex == meshRenderer.lightmapIndex) {
|
|
totalVertexCount += filter.sharedMesh.vertexCount;
|
|
totalMeshCount++;
|
|
}
|
|
}
|
|
totalMeshCountAnyLightmapIdx++;
|
|
}
|
|
}
|
|
|
|
if(totalMeshCount == 0) {
|
|
throw new ApplicationException("No meshes found in children. There's nothing to combine.");
|
|
// Debug.LogError("No meshes found in children. There's nothing to combine.");
|
|
// return null;
|
|
}
|
|
|
|
if(makeNewGameObject) {
|
|
GameObject newGO = new GameObject();
|
|
string newName = aGO.name + "_Merged";
|
|
string newUniqueName = newName;
|
|
int uniqueCounter = 0;
|
|
while(GameObject.Find(newUniqueName) != null) {
|
|
newUniqueName = newName + "_" + uniqueCounter;
|
|
uniqueCounter++;
|
|
}
|
|
newGO.name = newUniqueName;
|
|
#if UNITY_4_3
|
|
newGO.transform.parent = aGO.transform.parent;
|
|
#elif UNITY_4_4
|
|
newGO.transform.parent = aGO.transform.parent;
|
|
#elif UNITY_4_5
|
|
newGO.transform.parent = aGO.transform.parent;
|
|
#else
|
|
newGO.transform.SetParent(aGO.transform.parent);
|
|
#endif
|
|
newGO.transform.localPosition = aGO.transform.localPosition;
|
|
newGO.transform.localRotation = aGO.transform.localRotation;
|
|
newGO.transform.localScale = aGO.transform.localScale;
|
|
aGO = newGO;
|
|
}
|
|
|
|
int partNr = 1;
|
|
int prevMeshOffset = -1;
|
|
int meshOffset = 0;
|
|
while(meshOffset < totalMeshCount) {
|
|
if(prevMeshOffset == meshOffset) break;
|
|
prevMeshOffset = meshOffset;
|
|
GameObject topGO = aGO;
|
|
if(totalVertexCount > 65534) {
|
|
topGO = new GameObject();
|
|
topGO.name = "Merged part "+(partNr++);
|
|
#if UNITY_4_3
|
|
topGO.transform.parent = aGO.transform;
|
|
#elif UNITY_4_4
|
|
topGO.transform.parent = aGO.transform;
|
|
#elif UNITY_4_5
|
|
topGO.transform.parent = aGO.transform;
|
|
#else
|
|
topGO.transform.SetParent(aGO.transform);
|
|
#endif
|
|
topGO.transform.localPosition = Vector3.zero;
|
|
topGO.transform.localRotation = Quaternion.identity;
|
|
topGO.transform.localScale = Vector3.one;
|
|
}
|
|
|
|
Mesh mesh = null;
|
|
List<Vector3> vertices = new List<Vector3>();
|
|
List<Vector3> normals = new List<Vector3>();
|
|
List<Vector2> uv1s = new List<Vector2>();
|
|
List<Vector2> uv2s = new List<Vector2>();
|
|
List<Vector2> uv3s = new List<Vector2>();
|
|
List<Vector2> uv4s = new List<Vector2>();
|
|
List<Color32> colors32 = new List<Color32>();
|
|
List<Transform> bones = new List<Transform>();
|
|
List<Matrix4x4> bindPoses = new List<Matrix4x4>();
|
|
List<BoneWeight> boneWeights = new List<BoneWeight>();
|
|
Dictionary<Material, List<int>> subMeshes = new Dictionary<Material, List<int>>();
|
|
|
|
if(skinnedMeshRenderers != null && skinnedMeshRenderers.Length > 0) {
|
|
bool meshLimitReached = false;
|
|
bool hasSkinnedMesh = false;
|
|
int counter = 0;
|
|
int nrOfMeshWithBlendShapes = -1;
|
|
// First we process the first mesh that has blendshapes
|
|
int meshNr = 0;
|
|
foreach(SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) {
|
|
if(skinnedMeshRenderer.sharedMesh != null) {
|
|
if(vertices.Count + skinnedMeshRenderer.sharedMesh.vertexCount > 65534) meshLimitReached = true;
|
|
if(mesh == null && skinnedMeshRenderer.sharedMesh.blendShapeCount > 0) {
|
|
if(meshOffset <= counter && (!meshLimitReached)) {
|
|
// make copy to preserve blendshapes
|
|
mesh = (Mesh)Mesh.Instantiate(skinnedMeshRenderer.sharedMesh);
|
|
#if UNITY_4_3
|
|
#elif UNITY_4_4
|
|
#elif UNITY_4_5
|
|
#elif UNITY_4_6
|
|
#else
|
|
mesh.uv4 = null;
|
|
mesh.uv3 = null;
|
|
#endif
|
|
mesh.uv2 = null;
|
|
mesh.uv2 = null;
|
|
mesh.boneWeights = null;
|
|
mesh.colors32 = null;
|
|
mesh.triangles = null;
|
|
mesh.tangents = null;
|
|
mesh.normals = null;
|
|
mesh.vertices = null;
|
|
|
|
bool allSubmeshesMerged = MergeMeshInto(skinnedMeshRenderer.sharedMesh, skinnedMeshRenderer.bones, skinnedMeshRenderer.sharedMaterials, vertices, normals, uv1s, uv2s, uv3s, uv4s, colors32, boneWeights, bones, bindPoses, subMeshes, ((skinnedMeshRenderer.transform.localScale.x * skinnedMeshRenderer.transform.localScale.y * skinnedMeshRenderer.transform.localScale.z) < 0f), new Vector4(1,1,0,0), skinnedMeshRenderer.transform, topGO.transform, skinnedMeshRenderer.gameObject.name + "_" + skinnedMeshRenderer.sharedMesh.name, skipSubmeshNames);
|
|
if(allSubmeshesMerged && skinnedMeshRenderer.gameObject != topGO) {
|
|
skinnedMeshRenderer.gameObject.SetActive(false);
|
|
}
|
|
nrOfMeshWithBlendShapes = meshNr;
|
|
}
|
|
}
|
|
meshNr++;
|
|
}
|
|
}
|
|
|
|
// then we process all the other skinned meshes except the blendshaped one
|
|
foreach(SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers) {
|
|
if(skinnedMeshRenderer.sharedMesh != null) {
|
|
if(vertices.Count + skinnedMeshRenderer.sharedMesh.vertexCount > 65534) meshLimitReached = true;
|
|
if(meshOffset <= counter && (!meshLimitReached)) {
|
|
if(counter != nrOfMeshWithBlendShapes) {
|
|
bool allSubmeshesMerged = MergeMeshInto(skinnedMeshRenderer.sharedMesh, skinnedMeshRenderer.bones, skinnedMeshRenderer.sharedMaterials, vertices, normals, uv1s, uv2s, uv3s, uv4s, colors32, boneWeights, bones, bindPoses, subMeshes, ((skinnedMeshRenderer.transform.localScale.x * skinnedMeshRenderer.transform.localScale.y * skinnedMeshRenderer.transform.localScale.z) < 0f), new Vector4(1,1,0,0), skinnedMeshRenderer.transform, topGO.transform, skinnedMeshRenderer.gameObject.name + "_" + skinnedMeshRenderer.sharedMesh.name, skipSubmeshNames);
|
|
if(allSubmeshesMerged && skinnedMeshRenderer.gameObject != topGO) {
|
|
skinnedMeshRenderer.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
hasSkinnedMesh = true;
|
|
meshOffset++;
|
|
}
|
|
counter++;
|
|
}
|
|
}
|
|
|
|
// and then the non-skinned meshes that were attached to some bone
|
|
if(meshRenderers != null && meshRenderers.Length > 0 && hasSkinnedMesh) {
|
|
foreach(MeshRenderer meshRenderer in meshRenderers) {
|
|
MeshFilter filter = meshRenderer.gameObject.GetComponent<MeshFilter>();
|
|
if(filter != null && filter.sharedMesh != null && filter.gameObject != topGO) {
|
|
if(vertices.Count + filter.sharedMesh.vertexCount > 65534) meshLimitReached = true;
|
|
if(meshOffset <= counter && (!meshLimitReached)) {
|
|
bool allSubmeshesMerged = MergeMeshInto(filter.sharedMesh, null, meshRenderer.sharedMaterials, vertices, normals, uv1s, uv2s, uv3s, uv4s, colors32, boneWeights, bones, bindPoses, subMeshes, ((filter.transform.localScale.x * filter.transform.localScale.y * filter.transform.localScale.z) < 0f), meshRenderer.lightmapScaleOffset, filter.transform, topGO.transform, filter.gameObject.name + "_" + filter.sharedMesh.name, skipSubmeshNames);
|
|
if(allSubmeshesMerged) {
|
|
// the gameObject has now become a bone, so I wll not disable it, but disable the renderer instead;
|
|
meshRenderer.enabled = false;
|
|
}
|
|
meshOffset++;
|
|
}
|
|
counter++;
|
|
}
|
|
}
|
|
}
|
|
} else if(meshRenderers != null && meshRenderers.Length > 0) {
|
|
int counter = 0;
|
|
foreach(MeshRenderer meshRenderer in meshRenderers) {
|
|
if(lightmapIndex<0 || meshRenderer.lightmapIndex<0 || meshRenderer.lightmapIndex>253 || lightmapIndex == meshRenderer.lightmapIndex) { // only merge objects with the same lightmapIndex
|
|
MeshFilter filter = meshRenderer.gameObject.GetComponent<MeshFilter>();
|
|
if(filter != null && filter.sharedMesh != null) {
|
|
if(meshOffset <= counter && vertices.Count + filter.sharedMesh.vertexCount <= 65534) {
|
|
bool allSubmeshesMerged = MergeMeshInto(filter.sharedMesh, null, meshRenderer.sharedMaterials, vertices, normals, uv1s, uv2s, uv3s, uv4s, colors32, boneWeights, bones, bindPoses, subMeshes, ((filter.transform.localScale.x * filter.transform.localScale.y * filter.transform.localScale.z) < 0f), meshRenderer.lightmapScaleOffset, filter.transform, topGO.transform, filter.gameObject.name + "_" + filter.sharedMesh.name, skipSubmeshNames);
|
|
if(allSubmeshesMerged && filter.gameObject != topGO) {
|
|
filter.gameObject.SetActive(false);
|
|
|
|
// Also disable parent (go's with meshes often have empty parent transforms that we dont need anymore)
|
|
Transform parentTransform = filter.gameObject.transform.parent;
|
|
if(parentTransform != null && parentTransform.gameObject != topGO) {
|
|
parentTransform.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
meshOffset++;
|
|
}
|
|
counter++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove unused vertices
|
|
LODMaker.RemoveUnusedVertices(vertices, normals, uv1s, uv2s, uv3s, uv4s, colors32, boneWeights, subMeshes);
|
|
|
|
if(mesh == null) mesh = new Mesh();
|
|
mesh.vertices = vertices.ToArray();
|
|
bool setValues = false;
|
|
if(normals.Count>0) mesh.normals = normals.ToArray();
|
|
|
|
setValues = false;
|
|
for(int i=0;i<uv1s.Count;i++) {
|
|
if(uv1s[i].x != 0f || uv1s[i].y != 0f) {
|
|
setValues = true;
|
|
break;
|
|
}
|
|
}
|
|
if(setValues) mesh.uv = uv1s.ToArray();
|
|
|
|
setValues = false;
|
|
for(int i=0;i<uv2s.Count;i++) {
|
|
if(uv2s[i].x != 0f || uv2s[i].y != 0f) {
|
|
setValues = true;
|
|
break;
|
|
}
|
|
}
|
|
if(setValues) mesh.uv2 = uv2s.ToArray();
|
|
|
|
#if UNITY_4_3
|
|
#elif UNITY_4_4
|
|
#elif UNITY_4_5
|
|
#elif UNITY_4_6
|
|
#else
|
|
setValues = false;
|
|
for(int i=0;i<uv3s.Count;i++) {
|
|
if(uv3s[i].x != 0f || uv3s[i].y != 0f) {
|
|
setValues = true;
|
|
break;
|
|
}
|
|
}
|
|
if(setValues) mesh.uv3 = uv3s.ToArray();
|
|
|
|
setValues = false;
|
|
for(int i=0;i<uv4s.Count;i++) {
|
|
if(uv4s[i].x != 0f || uv4s[i].y != 0f) {
|
|
setValues = true;
|
|
break;
|
|
}
|
|
}
|
|
if(setValues) mesh.uv4 = uv4s.ToArray();
|
|
#endif
|
|
|
|
setValues = false;
|
|
for(int i=0;i<colors32.Count;i++) {
|
|
if(colors32[i].r > 0 || colors32[i].g > 0 || colors32[i].b > 0) {
|
|
setValues = true;
|
|
break;
|
|
}
|
|
}
|
|
if(setValues) mesh.colors32 = colors32.ToArray();
|
|
|
|
if(bindPoses.Count>0) mesh.bindposes = bindPoses.ToArray();
|
|
if(boneWeights.Count>0) {
|
|
if(boneWeights.Count == vertices.Count) mesh.boneWeights = boneWeights.ToArray();
|
|
else Debug.LogWarning("Nr of bone weights not equal to nr of vertices.");
|
|
}
|
|
mesh.subMeshCount = subMeshes.Keys.Count;
|
|
Material[] materials = new Material[subMeshes.Keys.Count];
|
|
int mIdx = 0;
|
|
foreach(Material m in subMeshes.Keys) {
|
|
materials[mIdx] = m;
|
|
mesh.SetTriangles(subMeshes[m].ToArray(), mIdx++);
|
|
}
|
|
|
|
if(normals == null || normals.Count <= 0) mesh.RecalculateNormals();
|
|
mesh.RecalculateTangents();
|
|
mesh.RecalculateBounds();
|
|
if(skinnedMeshRenderers != null && skinnedMeshRenderers.Length > 0) {
|
|
SkinnedMeshRenderer meshRend = topGO.GetComponent<SkinnedMeshRenderer>();
|
|
|
|
if(meshRend == null) {
|
|
meshRend = topGO.AddComponent<SkinnedMeshRenderer>();
|
|
}
|
|
meshRend.quality = skinnedMeshRenderers[0].quality;
|
|
meshRend.sharedMesh = mesh;
|
|
meshRend.sharedMaterials = materials;
|
|
meshRend.bones = bones.ToArray();
|
|
} else if(meshRenderers != null && meshRenderers.Length > 0) {
|
|
MeshRenderer meshRend = topGO.GetComponent<MeshRenderer>();
|
|
if(meshRend == null) {
|
|
meshRend = topGO.AddComponent<MeshRenderer>();
|
|
}
|
|
if(lightmapIndex >=0 && lightmapIndex <= 253) meshRend.lightmapIndex = lightmapIndex;
|
|
meshRend.sharedMaterials = materials;
|
|
MeshFilter meshFilter = topGO.GetComponent<MeshFilter>();
|
|
if(meshFilter == null) {
|
|
meshFilter = topGO.AddComponent<MeshFilter>();
|
|
}
|
|
meshFilter.sharedMesh = mesh;
|
|
}
|
|
meshes.Add(mesh);
|
|
}
|
|
|
|
return meshes.ToArray();
|
|
}
|
|
|
|
private static int GiveUniqueNameIfNeeded(GameObject aGo, GameObject topGO, int uniqueId) {
|
|
if(IsChildWithNameUnique(topGO, aGo.name)) return uniqueId;
|
|
aGo.name = aGo.name + "_simpleLod" + (++uniqueId);
|
|
return uniqueId;
|
|
}
|
|
|
|
public static void SetUpLODLevels(this GameObject go) {
|
|
SetUpLODLevels(go, 1f);
|
|
}
|
|
|
|
public static void SetUpLODLevels(this GameObject go, float maxWeight) {
|
|
SetUpLODLevels(go, new float[3] {0.6f, 0.3f, 0.15f}, new float[3] {maxWeight * 0.65f, maxWeight, maxWeight * 1.5f});
|
|
}
|
|
|
|
public static void SetUpLODLevels(this GameObject go, float[] lodScreenSizes, float[] maxWeights) {
|
|
MeshFilter[] mfs = go.GetComponentsInChildren<MeshFilter>(false);
|
|
if(mfs == null || mfs.Length == 0) return;
|
|
for(int i=0;i<mfs.Length;i++) {
|
|
SetUpLODLevelsWithLODSwitcher(mfs[i].gameObject, lodScreenSizes, maxWeights, true);
|
|
}
|
|
}
|
|
|
|
public static Mesh[] SetUpLODLevelsWithLODSwitcher(this GameObject go, float[] lodScreenSizes, float[] maxWeights, bool recalcNormals, float removeSmallParts = 1f, float protectNormals = 1f, float protectUvs = 1f, float protectSubMeshesAndSharpEdges = 1f, float smallTrianglesFirst = 1f, int nrOfSteps = 1) {
|
|
Mesh mesh = null;
|
|
LODSwitcher lodSwitcher = go.GetComponent<LODSwitcher>();
|
|
if(lodSwitcher != null) {
|
|
lodSwitcher.ReleaseFixedLODLevel();
|
|
lodSwitcher.SetLODLevel(0);
|
|
}
|
|
|
|
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
|
|
if(smr != null) mesh = smr.sharedMesh;
|
|
else {
|
|
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
|
|
if(meshFilter != null) mesh = meshFilter.sharedMesh;
|
|
}
|
|
if(mesh == null) {
|
|
throw new ApplicationException("No mesh found in " + go.name+". Maybe you need to select a child object?");
|
|
}
|
|
for(int i=0;i<maxWeights.Length;i++) {
|
|
if(maxWeights[i] <= 0f) throw new ApplicationException("MaxWeight should be more that 0 or else this operation will have no effect");
|
|
}
|
|
|
|
Mesh[] lodMeshes = mesh.MakeLODMeshes(maxWeights, recalcNormals, removeSmallParts, protectNormals, protectUvs, protectSubMeshesAndSharpEdges, smallTrianglesFirst, nrOfSteps);
|
|
if(lodMeshes == null) return null;
|
|
|
|
if(lodSwitcher == null) {
|
|
// add LODSwitcher component if needed
|
|
lodSwitcher = go.AddComponent<LODSwitcher>();
|
|
}
|
|
Array.Resize(ref lodMeshes, maxWeights.Length+1);
|
|
for(int i=maxWeights.Length;i>0;i--) lodMeshes[i] = lodMeshes[i-1];
|
|
lodMeshes[0] = mesh;
|
|
|
|
lodSwitcher.lodMeshes = lodMeshes;
|
|
lodSwitcher.lodScreenSizes = lodScreenSizes;
|
|
lodSwitcher.ComputeDimensions();
|
|
lodSwitcher.enabled = true;
|
|
return lodMeshes;
|
|
}
|
|
|
|
#if UNITY_WP8
|
|
#elif UNITY_WP_8_1
|
|
#elif UNITY_WSA
|
|
#elif UNITY_WSA_8_0
|
|
#elif UNITY_WSA_8_1
|
|
#elif UNITY_WINRT
|
|
#elif UNITY_WINRT_8_0
|
|
#elif UNITY_WINRT_8_1
|
|
#elif NETFX_CORE
|
|
#elif UNITY_WEBGL
|
|
#else
|
|
public static IEnumerator SetUpLODLevelsWithLODSwitcherInBackground(this GameObject go, float[] lodScreenSizes, float[] maxWeights, bool recalcNormals, float removeSmallParts = 1f, float protectNormals = 1f, float protectUvs = 1f, float protectSubMeshesAndSharpEdges = 1f, float smallTrianglesFirst = 1f) {
|
|
yield return null;
|
|
Mesh mesh = null;
|
|
LODSwitcher lodSwitcher = go.GetComponent<LODSwitcher>();
|
|
if(lodSwitcher != null) {
|
|
lodSwitcher.ReleaseFixedLODLevel();
|
|
lodSwitcher.SetLODLevel(0);
|
|
}
|
|
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
|
|
if(smr != null) mesh = smr.sharedMesh;
|
|
else {
|
|
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
|
|
if(meshFilter != null) mesh = meshFilter.sharedMesh;
|
|
}
|
|
|
|
if(mesh == null) {
|
|
throw new ApplicationException("No mesh found in " + go.name+". Maybe you need to select a child object?");
|
|
}
|
|
for(int i=0;i<maxWeights.Length;i++) {
|
|
if(maxWeights[i] <= 0f) throw new ApplicationException("MaxWeight should be more that 0 or else this operation will have no effect");
|
|
}
|
|
|
|
Mesh mesh0 = mesh;
|
|
Mesh[] lodMeshes = new Mesh[maxWeights.Length];
|
|
for(int i=0;i<maxWeights.Length;i++) {
|
|
yield return null;
|
|
Hashtable lodInfo = new Hashtable();
|
|
lodInfo["maxWeight"] = maxWeights[i];
|
|
lodInfo["removeSmallParts"] = removeSmallParts;
|
|
Vector3[] vs = mesh.vertices;
|
|
if(vs.Length <= 0) throw new ApplicationException("Mesh was empty");
|
|
Vector3[] ns = mesh.normals;
|
|
if(ns.Length == 0) { // mesh has no normals
|
|
mesh.RecalculateNormals();
|
|
ns = mesh.normals;
|
|
}
|
|
Vector2[] uv1s = mesh.uv;
|
|
Vector2[] uv2s = mesh.uv2;
|
|
#if UNITY_4_3
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#elif UNITY_4_4
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#elif UNITY_4_5
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#elif UNITY_4_6
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#else
|
|
Vector2[] uv3s = mesh.uv3;
|
|
Vector2[] uv4s = mesh.uv4;
|
|
#endif
|
|
Color32[] colors32 = mesh.colors32;
|
|
int[] ts = mesh.triangles;
|
|
Matrix4x4[] bindposes = mesh.bindposes;
|
|
BoneWeight[] bws = mesh.boneWeights;
|
|
int[] subMeshOffsets = new int[mesh.subMeshCount];
|
|
if(mesh.subMeshCount > 1) { // read triangles of submeshes 1 by 1 because I dont know the internal order of the mesh
|
|
for(int s=0;s<mesh.subMeshCount;s++) {
|
|
int[] subTs = mesh.GetTriangles(s);
|
|
int t=0;
|
|
for(;t<subTs.Length;t++) ts[subMeshOffsets[s] + t] = subTs[t];
|
|
if(s+1 < mesh.subMeshCount) subMeshOffsets[s+1] = subMeshOffsets[s] + t;
|
|
}
|
|
}
|
|
Bounds meshBounds = mesh.bounds;
|
|
lodInfo["vertices"] = vs;
|
|
lodInfo["normals"] = ns;
|
|
lodInfo["uv1s"] = uv1s;
|
|
lodInfo["uv2s"] = uv2s;
|
|
lodInfo["uv3s"] = uv3s;
|
|
lodInfo["uv4s"] = uv4s;
|
|
lodInfo["colors32"] = colors32;
|
|
lodInfo["triangles"] = ts;
|
|
lodInfo["bindposes"] = bindposes;
|
|
lodInfo["boneWeights"] = bws;
|
|
lodInfo["subMeshOffsets"] = subMeshOffsets;
|
|
lodInfo["meshBounds"] = meshBounds;
|
|
|
|
Thread thread = new Thread(LODMaker.MakeLODMeshInBackground);
|
|
thread.Start(lodInfo);
|
|
while(!lodInfo.ContainsKey("ready")) {
|
|
yield return new WaitForSeconds(0.2f);
|
|
}
|
|
|
|
lodMeshes[i] = LODMaker.CreateNewMesh((Vector3[])lodInfo["vertices"], (Vector3[])lodInfo["normals"], (Vector2[])lodInfo["uv1s"], (Vector2[])lodInfo["uv2s"], (Vector2[])lodInfo["uv3s"], (Vector2[])lodInfo["uv4s"], (Color32[])lodInfo["colors32"], (int[])lodInfo["triangles"], (BoneWeight[])lodInfo["boneWeights"], (Matrix4x4[])lodInfo["bindposes"], (int[])lodInfo["subMeshOffsets"], recalcNormals);
|
|
mesh = lodMeshes[i];
|
|
go.transform.parent.gameObject.BroadcastMessage("LOD"+(i+1)+"IsReady", go, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
|
|
yield return null;
|
|
if(lodMeshes != null) {
|
|
if(lodSwitcher == null) {
|
|
// add LODSwitcher component if needed
|
|
lodSwitcher = go.AddComponent<LODSwitcher>();
|
|
}
|
|
Array.Resize(ref lodMeshes, maxWeights.Length+1);
|
|
for(int i=maxWeights.Length;i>0;i--) lodMeshes[i] = lodMeshes[i-1];
|
|
lodMeshes[0] = mesh0;
|
|
|
|
lodSwitcher.lodMeshes = lodMeshes;
|
|
lodSwitcher.lodScreenSizes = lodScreenSizes;
|
|
lodSwitcher.ComputeDimensions();
|
|
lodSwitcher.enabled = true;
|
|
}
|
|
go.transform.parent.gameObject.BroadcastMessage("LODsAreReady", go, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
#endif
|
|
|
|
public static Mesh[] SetUpLODLevelsAndChildrenWithLODSwitcher(this GameObject go, float[] lodScreenSizes, float[] maxWeights, bool recalcNormals, float removeSmallParts, float protectNormals = 1f, float protectUvs = 1f, float protectSubMeshesAndSharpEdges = 1f, float smallTrianglesFirst = 1f, int nrOfSteps = 1) {
|
|
Mesh mesh = null;
|
|
Material[] mats = null;
|
|
LODSwitcher lodSwitcher = go.GetComponent<LODSwitcher>();
|
|
if(lodSwitcher != null) {
|
|
lodSwitcher.ReleaseFixedLODLevel();
|
|
lodSwitcher.SetLODLevel(0);
|
|
}
|
|
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
|
|
if(smr != null) {
|
|
mesh = smr.sharedMesh;
|
|
mats = smr.sharedMaterials;
|
|
smr.enabled = false;
|
|
} else {
|
|
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
|
|
if(meshFilter != null) mesh = meshFilter.sharedMesh;
|
|
MeshRenderer rend = go.GetComponent<MeshRenderer>();
|
|
if(rend == null) {
|
|
throw new ApplicationException("No MeshRenderer found");
|
|
}
|
|
mats = rend.sharedMaterials;
|
|
rend.enabled = false;
|
|
}
|
|
if(mesh == null) {
|
|
throw new ApplicationException("No mesh found in " + go.name+". Maybe you need to select a child object?");
|
|
}
|
|
for(int i=0;i<maxWeights.Length;i++) {
|
|
if(maxWeights[i] <= 0f) throw new ApplicationException("MaxWeight should be more that 0 or else this operation will have no effect");
|
|
}
|
|
|
|
Mesh[] lod1Meshes = mesh.MakeLODMeshes(maxWeights, recalcNormals, removeSmallParts, protectNormals, protectUvs, protectSubMeshesAndSharpEdges, smallTrianglesFirst, nrOfSteps);
|
|
if(lod1Meshes == null) return null;
|
|
Mesh[] lodMeshes = new Mesh[lod1Meshes.Length+1];
|
|
lodMeshes[0] = mesh;
|
|
for(int i=0;i<lod1Meshes.Length;i++) lodMeshes[i+1] = lod1Meshes[i];
|
|
|
|
if(lodSwitcher == null) lodSwitcher = go.AddComponent<LODSwitcher>();
|
|
lodSwitcher.lodScreenSizes = lodScreenSizes;
|
|
|
|
GameObject[] children = new GameObject[lodMeshes.Length];
|
|
for(int i=0;i<lodMeshes.Length;i++) {
|
|
Transform subTrans = go.transform.FindFirstChildWithName(go.name+"_LOD"+i);
|
|
if(subTrans != null) {
|
|
children[i] = subTrans.gameObject;
|
|
children[i].SetActive(true);
|
|
}
|
|
if(children[i] == null) {
|
|
children[i] = new GameObject(go.name+"_LOD"+i);
|
|
#if UNITY_4_3
|
|
children[i].transform.parent = go.transform;
|
|
#elif UNITY_4_4
|
|
children[i].transform.parent = go.transform;
|
|
#elif UNITY_4_5
|
|
children[i].transform.parent = go.transform;
|
|
#else
|
|
children[i].transform.SetParent(go.transform);
|
|
#endif
|
|
children[i].transform.localPosition = Vector3.zero;
|
|
children[i].transform.localRotation = Quaternion.identity;
|
|
children[i].transform.localScale = Vector3.one;
|
|
}
|
|
if(smr != null) {
|
|
SkinnedMeshRenderer sRend = children[i].GetComponent<SkinnedMeshRenderer>();
|
|
if(sRend == null) sRend = children[i].AddComponent<SkinnedMeshRenderer>();
|
|
sRend.sharedMesh = lodMeshes[i];
|
|
sRend.sharedMaterials = mats;
|
|
} else {
|
|
MeshFilter filter = children[i].GetComponent<MeshFilter>();
|
|
if(filter == null) filter = children[i].AddComponent<MeshFilter>();
|
|
filter.sharedMesh = lodMeshes[i];
|
|
MeshRenderer rend = children[i].GetComponent<MeshRenderer>();
|
|
if(rend == null) rend = children[i].AddComponent<MeshRenderer>();
|
|
rend.sharedMaterials = mats;
|
|
}
|
|
children[i].SetActive(i==0);
|
|
}
|
|
lodSwitcher.lodGameObjects = children;
|
|
lodSwitcher.ComputeDimensions();
|
|
lodSwitcher.enabled = true;
|
|
return lodMeshes;
|
|
}
|
|
|
|
public static Mesh[] SetUpLODLevelsAndChildrenWithLODGroup(this GameObject go, float[] relativeTransitionHeights, float[] maxWeights, bool recalcNormals, float removeSmallParts, float protectNormals = 1f, float protectUvs = 1f, float protectSubMeshesAndSharpEdges = 1f, float smallTrianglesFirst = 1f, int nrOfSteps = 1) {
|
|
GameObject parentGO = null;
|
|
LODGroup lodGroup = null;
|
|
if(relativeTransitionHeights.Length < 0 || relativeTransitionHeights.Length != maxWeights.Length) {
|
|
throw new ApplicationException("relativeTransitionHeights and maxWeights arrays need to have equal length and be longer than 0. Example: SetUpLODLevelsWithLODGroup(go, new float[2] {0.6f, 0.4f}, new float[2] {1f, 1.75f})");
|
|
}
|
|
for(int i=0;i<maxWeights.Length;i++) {
|
|
if(maxWeights[i] <= 0f) throw new ApplicationException("MaxWeight should be more that 0 or else this operation will have no effect");
|
|
}
|
|
|
|
parentGO = new GameObject(go.name+"_$LodGrp");
|
|
parentGO.transform.position = go.transform.position;
|
|
parentGO.transform.rotation = go.transform.rotation;
|
|
parentGO.transform.localScale = go.transform.localScale;
|
|
#if UNITY_4_3
|
|
go.transform.parent = parentGO.transform;
|
|
#elif UNITY_4_4
|
|
go.transform.parent = parentGO.transform;
|
|
#elif UNITY_4_5
|
|
go.transform.parent = parentGO.transform;
|
|
#else
|
|
go.transform.SetParent(parentGO.transform);
|
|
#endif
|
|
|
|
if(lodGroup == null) {
|
|
// add LODGroup component if needed
|
|
lodGroup = parentGO.AddComponent<LODGroup>() ;
|
|
} else {
|
|
// remove existing LOD gameObjects, because we will make new ones
|
|
// let's hope no one will call their sub gameobjects <parent name>_$Lod:*
|
|
Transform subObject = parentGO.transform.FindFirstChildWhereNameContains(go.name+"_$Lod:");
|
|
int safetyCounter = 0;
|
|
while(subObject != null && safetyCounter++ < 10) {
|
|
#if UNITY_4_3
|
|
subObject.parent = null;
|
|
#elif UNITY_4_4
|
|
subObject.parent = null;
|
|
#elif UNITY_4_5
|
|
subObject.parent = null;
|
|
#else
|
|
subObject.SetParent(null);
|
|
#endif
|
|
UnityEngine.Object.Destroy(subObject.gameObject);
|
|
subObject = parentGO.transform.FindFirstChildWhereNameContains(go.name+"_$Lod:");
|
|
}
|
|
}
|
|
|
|
// Create LODs
|
|
LOD[] lods = new LOD[maxWeights.Length + 1];
|
|
lods[0] = new LOD(relativeTransitionHeights[0], go.GetComponentsInChildren<MeshRenderer>(false));
|
|
List<Mesh> lodMeshes = new List<Mesh>();
|
|
Mesh[] meshesOrig = GetMeshes(go, false);
|
|
float prevWeight = 0f;
|
|
for(int l=1;l<lods.Length;l++) {
|
|
Mesh[] meshesLOD = new Mesh[meshesOrig.Length];
|
|
for(int i=0;i<meshesOrig.Length;i++) {
|
|
Mesh lodMesh = meshesOrig[i];
|
|
if(nrOfSteps < 1) nrOfSteps = 1;
|
|
for(int s=0;s<nrOfSteps;s++) {
|
|
float weight = (maxWeights[l-1] - prevWeight);
|
|
lodMesh = lodMesh.MakeLODMesh(((float)(s+1) * (weight / nrOfSteps)) + prevWeight, recalcNormals, removeSmallParts, protectNormals, protectUvs, protectSubMeshesAndSharpEdges, smallTrianglesFirst);
|
|
}
|
|
prevWeight = maxWeights[l-1];
|
|
meshesLOD[i] = lodMesh;
|
|
lodMesh.name = go.name+"_"+i+"_LOD"+l;
|
|
lodMeshes.Add(lodMesh);
|
|
}
|
|
GameObject goLOD = (GameObject)GameObject.Instantiate(go);
|
|
goLOD.name = go.name+"_$Lod:"+l;
|
|
#if UNITY_4_3
|
|
goLOD.transform.parent = parentGO.transform;
|
|
#elif UNITY_4_4
|
|
goLOD.transform.parent = parentGO.transform;
|
|
#elif UNITY_4_5
|
|
goLOD.transform.parent = parentGO.transform;
|
|
#else
|
|
goLOD.transform.SetParent(parentGO.transform);
|
|
#endif
|
|
goLOD.transform.localPosition = go.transform.localPosition;
|
|
goLOD.transform.localRotation = go.transform.localRotation;
|
|
goLOD.transform.localScale = go.transform.localScale;
|
|
SetMeshes(goLOD, meshesLOD);
|
|
float transitionHeight = l < relativeTransitionHeights.Length ? relativeTransitionHeights[l] : 0f;
|
|
lods[l] = new LOD(transitionHeight, goLOD.GetComponentsInChildren<MeshRenderer>(false));
|
|
meshesOrig = meshesLOD;
|
|
}
|
|
|
|
lodGroup.SetLODs(lods);
|
|
lodGroup.RecalculateBounds();
|
|
lodGroup.ForceLOD(-1);
|
|
return lodMeshes.ToArray();
|
|
}
|
|
|
|
public static Mesh GetSimplifiedMesh(this GameObject go, float maxWeight, bool recalcNormals, float removeSmallParts, float protectNormals = 1f, float protectUvs = 1f, float protectSubMeshesAndSharpEdges = 1f, float smallTrianglesFirst = 1f, int nrOfSteps = 1) {
|
|
Mesh mesh = null;
|
|
LODSwitcher lodSwitcher = go.GetComponent<LODSwitcher>();
|
|
if(lodSwitcher != null) {
|
|
lodSwitcher.ReleaseFixedLODLevel();
|
|
lodSwitcher.SetLODLevel(0);
|
|
}
|
|
MeshFilter mf = null;
|
|
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
|
|
if(maxWeight <= 0f) throw new ApplicationException("MaxWeight should be more that 0 or else this operation will have no effect");
|
|
if(smr != null) mesh = smr.sharedMesh;
|
|
else {
|
|
mf = go.GetComponent<MeshFilter>();
|
|
if(mf != null) mesh = mf.sharedMesh;
|
|
}
|
|
if(mesh == null) {
|
|
throw new ApplicationException("No mesh found. Maybe you need to select a child object?");
|
|
}
|
|
|
|
Mesh decimatedMesh = mesh;
|
|
if(nrOfSteps < 1) nrOfSteps = 1;
|
|
for(int i=0;i<nrOfSteps;i++) {
|
|
decimatedMesh = decimatedMesh.MakeLODMesh((float)(i+1) * (maxWeight / nrOfSteps), recalcNormals, removeSmallParts, protectNormals, protectUvs, protectSubMeshesAndSharpEdges, smallTrianglesFirst);
|
|
}
|
|
|
|
if(smr != null) {
|
|
smr.sharedMesh = decimatedMesh;
|
|
} else if(mf != null) {
|
|
mf.sharedMesh = decimatedMesh;
|
|
}
|
|
return decimatedMesh;
|
|
}
|
|
|
|
#if UNITY_WP8
|
|
#elif UNITY_WP_8_1
|
|
#elif UNITY_WSA
|
|
#elif UNITY_WSA_8_0
|
|
#elif UNITY_WSA_8_1
|
|
#elif UNITY_WINRT
|
|
#elif UNITY_WINRT_8_0
|
|
#elif UNITY_WINRT_8_1
|
|
#elif NETFX_CORE
|
|
#elif UNITY_WEBGL
|
|
#else
|
|
public static IEnumerator GetSimplifiedMeshInBackground(this GameObject go, float maxWeight, bool recalcNormals, float removeSmallParts, System.Action<Mesh> result) {
|
|
Mesh mesh = null;
|
|
LODSwitcher lodSwitcher = go.GetComponent<LODSwitcher>();
|
|
if(lodSwitcher != null) {
|
|
lodSwitcher.ReleaseFixedLODLevel();
|
|
lodSwitcher.SetLODLevel(0);
|
|
}
|
|
SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>();
|
|
if(maxWeight <= 0f) throw new ApplicationException("MaxWeight should be more that 0 or else this operation will have no effect");
|
|
if(smr != null) mesh = smr.sharedMesh;
|
|
else {
|
|
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
|
|
if(meshFilter != null) mesh = meshFilter.sharedMesh;
|
|
}
|
|
if(mesh == null) {
|
|
throw new ApplicationException("No mesh found. Maybe you need to select a child object?");
|
|
}
|
|
|
|
Hashtable lodInfo = new Hashtable();
|
|
lodInfo["maxWeight"] = maxWeight;
|
|
lodInfo["removeSmallParts"] = removeSmallParts;
|
|
Vector3[] vs = mesh.vertices;
|
|
if(vs.Length <= 0) throw new ApplicationException("Mesh was empty");
|
|
Vector3[] ns = mesh.normals;
|
|
if(ns.Length == 0) { // mesh has no normals
|
|
mesh.RecalculateNormals();
|
|
ns = mesh.normals;
|
|
}
|
|
Vector2[] uv1s = mesh.uv;
|
|
Vector2[] uv2s = mesh.uv2;
|
|
#if UNITY_4_3
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#elif UNITY_4_4
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#elif UNITY_4_5
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#elif UNITY_4_6
|
|
Vector2[] uv3s = new Vector2[0];
|
|
Vector2[] uv4s = new Vector2[0];
|
|
#else
|
|
Vector2[] uv3s = mesh.uv3;
|
|
Vector2[] uv4s = mesh.uv4;
|
|
#endif
|
|
Color32[] colors32 = mesh.colors32;
|
|
int[] ts = mesh.triangles;
|
|
Matrix4x4[] bindposes = mesh.bindposes;
|
|
BoneWeight[] bws = mesh.boneWeights;
|
|
int[] subMeshOffsets = new int[mesh.subMeshCount];
|
|
if(mesh.subMeshCount > 1) { // read triangles of submeshes 1 by 1 because I dont know the internal order of the mesh
|
|
for(int s=0;s<mesh.subMeshCount;s++) {
|
|
int[] subTs = mesh.GetTriangles(s);
|
|
int t=0;
|
|
for(;t<subTs.Length;t++) ts[subMeshOffsets[s] + t] = subTs[t];
|
|
if(s+1 < mesh.subMeshCount) subMeshOffsets[s+1] = subMeshOffsets[s] + t;
|
|
}
|
|
}
|
|
Bounds meshBounds = mesh.bounds;
|
|
lodInfo["vertices"] = vs;
|
|
lodInfo["normals"] = ns;
|
|
lodInfo["uv1s"] = uv1s;
|
|
lodInfo["uv2s"] = uv2s;
|
|
lodInfo["uv3s"] = uv3s;
|
|
lodInfo["uv4s"] = uv4s;
|
|
lodInfo["colors32"] = colors32;
|
|
lodInfo["triangles"] = ts;
|
|
lodInfo["bindposes"] = bindposes;
|
|
lodInfo["boneWeights"] = bws;
|
|
lodInfo["subMeshOffsets"] = subMeshOffsets;
|
|
lodInfo["meshBounds"] = meshBounds;
|
|
|
|
Thread thread = new Thread(LODMaker.MakeLODMeshInBackground);
|
|
thread.Start(lodInfo);
|
|
while(!lodInfo.ContainsKey("ready")) {
|
|
yield return new WaitForSeconds(0.2f);
|
|
}
|
|
result(LODMaker.CreateNewMesh((Vector3[])lodInfo["vertices"], (Vector3[])lodInfo["normals"], (Vector2[])lodInfo["uv1s"], (Vector2[])lodInfo["uv2s"], (Vector2[])lodInfo["uv3s"], (Vector2[])lodInfo["uv4s"], (Color32[])lodInfo["colors32"], (int[])lodInfo["triangles"], (BoneWeight[])lodInfo["boneWeights"], (Matrix4x4[])lodInfo["bindposes"], (int[])lodInfo["subMeshOffsets"], recalcNormals));
|
|
}
|
|
#endif
|
|
|
|
private static bool MergeMeshInto(
|
|
Mesh fromMesh,
|
|
Transform[] fromBones,
|
|
Material[] fromMaterials,
|
|
List<Vector3> vertices,
|
|
List<Vector3> normals,
|
|
List<Vector2> uv1s,
|
|
List<Vector2> uv2s,
|
|
List<Vector2> uv3s,
|
|
List<Vector2> uv4s,
|
|
List<Color32> colors32,
|
|
List<BoneWeight> boneWeights,
|
|
List<Transform> bones,
|
|
List<Matrix4x4> bindposes,
|
|
Dictionary<Material, List<int>> subMeshes,
|
|
bool usesNegativeScale,
|
|
Vector4 lightmapScaleOffset,
|
|
Transform fromTransform,
|
|
Transform topTransform,
|
|
string submeshName,
|
|
string[] skipSubmeshNames) {
|
|
if(fromMesh == null) return false;
|
|
bool allSubmeshesMerged = true;
|
|
int vertexOffset = vertices.Count;
|
|
Vector3[] fromVertices = fromMesh.vertices;
|
|
Vector3[] fromNormals = fromMesh.normals;
|
|
Vector2[] fromUv1s = fromMesh.uv;
|
|
Vector2[] fromUv2s = fromMesh.uv2;
|
|
#if UNITY_4_3
|
|
#elif UNITY_4_4
|
|
#elif UNITY_4_5
|
|
#elif UNITY_4_6
|
|
#else
|
|
Vector2[] fromUv3s = fromMesh.uv3;
|
|
Vector2[] fromUv4s = fromMesh.uv4;
|
|
#endif
|
|
Color32[] fromColors32 = fromMesh.colors32;
|
|
BoneWeight[] fromBoneWeights = fromMesh.boneWeights;
|
|
Matrix4x4[] fromBindposes = fromMesh.bindposes;
|
|
List<int> old2NewBoneIndex = new List<int>();
|
|
|
|
Vector3 oldPos = fromTransform.localPosition;
|
|
Quaternion oldRot = fromTransform.localRotation;
|
|
Vector3 oldScale = fromTransform.localScale;
|
|
bool remapVertices = false;
|
|
|
|
if(fromBones != null) {
|
|
fromTransform.localPosition = Vector3.zero;
|
|
fromTransform.localRotation = Quaternion.identity;
|
|
fromTransform.localScale = Vector3.one;
|
|
}
|
|
|
|
if(fromBones == null || fromBones.Length == 0) remapVertices = true;
|
|
if((fromBones == null || fromBones.Length == 0) && bones != null && bones.Count > 0) { // merging a non-skinned mesh into a skinned mesh
|
|
fromBones = new Transform[] {fromTransform};
|
|
Matrix4x4 newBindPose = fromTransform.worldToLocalMatrix * topTransform.localToWorldMatrix;
|
|
fromBindposes = new Matrix4x4[] {newBindPose};
|
|
fromBoneWeights = new BoneWeight[fromVertices.Length];
|
|
for(int i=0;i<fromVertices.Length;i++) {
|
|
fromBoneWeights[i].boneIndex0 = 0;
|
|
fromBoneWeights[i].weight0 = 1;
|
|
}
|
|
}
|
|
|
|
if(fromBones != null) {
|
|
bool hasDifferentBindposes = false;
|
|
for(int from=0;from<fromBones.Length;from++) {
|
|
int to = 0;
|
|
old2NewBoneIndex.Add(from);
|
|
for(;to<bones.Count;to++) {
|
|
if(fromBones[from] == bones[to]) {
|
|
old2NewBoneIndex[from] = to;
|
|
if(fromBindposes[from] != bindposes[to]) {
|
|
hasDifferentBindposes = true;
|
|
if(fromBones[from] != null) {
|
|
Debug.Log(fromTransform.gameObject.name + ": The bindpose of " + fromBones[from].gameObject.name + " is different, vertices will be moved to match the bindpose of the merged mesh");
|
|
} else {
|
|
Debug.LogError(fromTransform.gameObject.name + ": There is an error in the bonestructure. A bone could not be found.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(to >= bones.Count) { // bone not found
|
|
old2NewBoneIndex[from] = bones.Count;
|
|
bones.Add(fromBones[from]);
|
|
bindposes.Add(fromBindposes[from]);
|
|
}
|
|
}
|
|
|
|
if(hasDifferentBindposes) {
|
|
for(int i=0;i<fromVertices.Length;i++) {
|
|
Vector3 v = fromVertices[i];
|
|
BoneWeight bw = fromBoneWeights[i];
|
|
if(fromBones[bw.boneIndex0] != null) {
|
|
// apply the old bindpose and inverse apply the new bindpose to put the vertex in the right position
|
|
v = ApplyBindPose(fromVertices[i], fromBones[bw.boneIndex0], fromBindposes[bw.boneIndex0], bw.weight0);
|
|
if(bw.weight1 > 0f) v = v + ApplyBindPose(fromVertices[i], fromBones[bw.boneIndex1], fromBindposes[bw.boneIndex1], bw.weight1);
|
|
if(bw.weight2 > 0f) v = v + ApplyBindPose(fromVertices[i], fromBones[bw.boneIndex2], fromBindposes[bw.boneIndex2], bw.weight2);
|
|
if(bw.weight3 > 0f) v = v + ApplyBindPose(fromVertices[i], fromBones[bw.boneIndex3], fromBindposes[bw.boneIndex3], bw.weight3);
|
|
|
|
Vector3 v0 = v;
|
|
v = UnApplyBindPose(v0, bones[old2NewBoneIndex[bw.boneIndex0]], bindposes[old2NewBoneIndex[bw.boneIndex0]], bw.weight0);
|
|
if(bw.weight1 > 0f) v = v + UnApplyBindPose(v0, bones[old2NewBoneIndex[bw.boneIndex1]], bindposes[old2NewBoneIndex[bw.boneIndex1]], bw.weight1);
|
|
if(bw.weight2 > 0f) v = v + UnApplyBindPose(v0, bones[old2NewBoneIndex[bw.boneIndex2]], bindposes[old2NewBoneIndex[bw.boneIndex2]], bw.weight2);
|
|
if(bw.weight3 > 0f) v = v + UnApplyBindPose(v0, bones[old2NewBoneIndex[bw.boneIndex3]], bindposes[old2NewBoneIndex[bw.boneIndex3]], bw.weight3);
|
|
fromVertices[i] = v;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
if(boneWeights != null && fromBoneWeights!=null && fromBoneWeights.Length>0) {
|
|
for(int i=0;i<fromBoneWeights.Length;i++) {
|
|
BoneWeight bw = new BoneWeight();
|
|
bw.boneIndex0 = old2NewBoneIndex[fromBoneWeights[i].boneIndex0];
|
|
bw.boneIndex1 = old2NewBoneIndex[fromBoneWeights[i].boneIndex1];
|
|
bw.boneIndex2 = old2NewBoneIndex[fromBoneWeights[i].boneIndex2];
|
|
bw.boneIndex3 = old2NewBoneIndex[fromBoneWeights[i].boneIndex3];
|
|
bw.weight0 = fromBoneWeights[i].weight0;
|
|
bw.weight1 = fromBoneWeights[i].weight1;
|
|
bw.weight2 = fromBoneWeights[i].weight2;
|
|
bw.weight3 = fromBoneWeights[i].weight3;
|
|
boneWeights.Add(bw);
|
|
}
|
|
}
|
|
|
|
Matrix4x4 transformMatrix = topTransform.worldToLocalMatrix * fromTransform.localToWorldMatrix;
|
|
if(remapVertices) {
|
|
for(int i=0;i<fromVertices.Length;i++) {
|
|
Vector3 v = fromVertices[i];
|
|
fromVertices[i] = transformMatrix.MultiplyPoint3x4(v);
|
|
}
|
|
}
|
|
vertices.AddRange(fromVertices);
|
|
|
|
Quaternion rotation = Quaternion.LookRotation(transformMatrix.GetColumn(2), transformMatrix.GetColumn(1));
|
|
if(fromNormals!=null && fromNormals.Length>0) {
|
|
for(int i=0;i<fromNormals.Length;i++) fromNormals[i] = rotation * fromNormals[i];
|
|
normals.AddRange(fromNormals);
|
|
}
|
|
|
|
if(fromUv1s==null || fromUv1s.Length != fromVertices.Length) fromUv1s = new Vector2[fromVertices.Length];
|
|
if(fromUv1s!=null && fromUv1s.Length>0) uv1s.AddRange(fromUv1s);
|
|
|
|
if(fromUv2s==null || fromUv2s.Length != fromVertices.Length) fromUv2s = new Vector2[fromVertices.Length];
|
|
for(int i=0;fromUv2s!=null && i<fromUv2s.Length;i++) {
|
|
uv2s.Add(new Vector2(lightmapScaleOffset.z + (fromUv2s[i].x * lightmapScaleOffset.x), lightmapScaleOffset.w + (fromUv2s[i].y * lightmapScaleOffset.y)));
|
|
}
|
|
#if UNITY_4_3
|
|
#elif UNITY_4_4
|
|
#elif UNITY_4_5
|
|
#elif UNITY_4_6
|
|
#else
|
|
if(fromUv3s==null || fromUv3s.Length != fromVertices.Length) fromUv3s = new Vector2[fromVertices.Length];
|
|
if(fromUv3s!=null && fromUv3s.Length>0) uv3s.AddRange(fromUv3s);
|
|
if(fromUv4s==null || fromUv4s.Length != fromVertices.Length) fromUv4s = new Vector2[fromVertices.Length];
|
|
if(fromUv4s!=null && fromUv4s.Length>0) uv4s.AddRange(fromUv3s);
|
|
#endif
|
|
if(fromColors32==null || fromColors32.Length != fromVertices.Length) fromColors32 = new Color32[fromVertices.Length];
|
|
if(fromColors32!=null && fromColors32.Length>0) colors32.AddRange(fromColors32);
|
|
|
|
int d=0;
|
|
for(int i=0;i<fromMaterials.Length;i++) {
|
|
if(i<fromMesh.subMeshCount) {
|
|
string thisSubmeshName = submeshName + "_" + i;
|
|
int j = 0;
|
|
for(;j<skipSubmeshNames.Length;j++) {
|
|
if(thisSubmeshName == skipSubmeshNames[j]) break;
|
|
}
|
|
if(j>=skipSubmeshNames.Length) {
|
|
int[] ts = fromMesh.GetTriangles(i);
|
|
if(ts.Length>0) {
|
|
if(fromMaterials[i]!=null && !subMeshes.ContainsKey(fromMaterials[i])) {
|
|
subMeshes.Add(fromMaterials[i], new List<int>());
|
|
}
|
|
List<int> subMesh = subMeshes[fromMaterials[i]];
|
|
for(int t=0;t<ts.Length;t+=3) {
|
|
if(usesNegativeScale) { // flip triangle
|
|
int t1 = ts[t+1];
|
|
int t2 = ts[t+2];
|
|
ts[t+1] = t2;
|
|
ts[t+2] = t1;
|
|
d++;
|
|
}
|
|
ts[t] += vertexOffset;
|
|
ts[t+1] += vertexOffset;
|
|
ts[t+2] += vertexOffset;
|
|
}
|
|
subMesh.AddRange(ts);
|
|
}
|
|
} else {
|
|
allSubmeshesMerged = false;
|
|
}
|
|
}
|
|
}
|
|
if(fromBones != null) {
|
|
fromTransform.localPosition = oldPos;
|
|
fromTransform.localRotation = oldRot;
|
|
fromTransform.localScale = oldScale;
|
|
}
|
|
return allSubmeshesMerged;
|
|
}
|
|
|
|
private static Vector3 ApplyBindPose(Vector3 vertex, Transform bone, Matrix4x4 bindpose, float boneWeight) {
|
|
Matrix4x4 newMatrix = (bone.localToWorldMatrix * bindpose);
|
|
for(int r=0;r<4;r++) {
|
|
for(int c=0;c<4;c++) {
|
|
newMatrix[r,c] *= boneWeight;
|
|
}
|
|
}
|
|
return newMatrix.MultiplyPoint3x4(vertex);
|
|
}
|
|
private static Vector3 UnApplyBindPose(Vector3 vertex, Transform bone, Matrix4x4 bindpose, float boneWeight) {
|
|
Matrix4x4 newMatrix = (bone.localToWorldMatrix * bindpose).inverse;
|
|
for(int r=0;r<4;r++) {
|
|
for(int c=0;c<4;c++) {
|
|
newMatrix[r,c] *= boneWeight;
|
|
}
|
|
}
|
|
return newMatrix.MultiplyPoint3x4(vertex);
|
|
}
|
|
}
|
|
}
|