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 { /// /// Manages a single combined mesh.This class is the core of the mesh combining API. /// /// It is not a component so it can be can be instantiated and used like a normal c sharp class. /// public partial class MB3_MeshCombinerSingle : MB3_MeshCombiner { public enum MeshCreationConditions { NoMesh, CreatedInEditor, CreatedAtRuntime, AssignedByUser, } //2D arrays are not serializable but arrays of arrays are. [System.Serializable] public class SerializableIntArray { public int[] data; public SerializableIntArray() { } public SerializableIntArray(int len) { data = new int[len]; } } /* Stores information about one source game object that has been added to the combined mesh. */ [System.Serializable] public class MB_DynamicGameObject : IComparable { public int instanceID; public GameObject gameObject; public string name; public int vertIdx; public int blendShapeIdx; public int numVerts; public int numBlendShapes; //distinct list of bones in the bones array public int[] indexesOfBonesUsed = new int[0]; //public Transform[] _originalBones; //used only for integrity checking //public Matrix4x4[] _originalBindPoses; //used only for integrity checking public int lightmapIndex = -1; public Vector4 lightmapTilingOffset = new Vector4(1f, 1f, 0f, 0f); public Vector3 meshSize = Vector3.one; // in world coordinates public bool show = true; public bool invertTriangles = false; /// /// combined mesh will have one submesh per result material /// source meshes can have any number of submeshes.They are mapped to a result submesh based on their material /// if two different submeshes have the same material they are merged in the same result submesh /// // These are result mesh submeshCount comine these into a class. public int[] submeshTriIdxs; public int[] submeshNumTris; /// /// These are source go mesh submeshCount todo combined these into a class. /// Maps each submesh in source mesh to a submesh in combined mesh. /// public int[] targetSubmeshIdxs; /// /// The UVRects in the combinedMaterial atlas. /// public Rect[] uvRects; /// /// If AllPropsUseSameMatTiling is the rect that was used for sampling the atlas texture from the source texture including both mesh uvTiling and material tiling. /// else is the source mesh obUVrect. We don't need to care which. /// public Rect[] encapsulatingRect; /// /// If AllPropsUseSameMatTiling is the source texture material tiling. /// else is 0,0,1,1. We don't need to care which. /// public Rect[] sourceMaterialTiling; /// /// The obUVRect for each source mesh submesh; /// public Rect[] obUVRects; /// /// The index of the texture array slice. /// public int[] textureArraySliceIdx; public Material[] sourceSharedMaterials; public bool _beingDeleted = false; public int _triangleIdxAdjustment = 0; //used so we don't have to call GetBones and GetBindposes twice [NonSerialized] public SerializableIntArray[] _tmpSubmeshTris; [NonSerialized] public Transform[] _tmpCachedBones; [NonSerialized] public Matrix4x4[] _tmpCachedBindposes; [NonSerialized] public BoneWeight[] _tmpCachedBoneWeights; [NonSerialized] public int[] _tmpIndexesOfSourceBonesUsed; public int CompareTo(MB_DynamicGameObject b) { return this.vertIdx - b.vertIdx; } } //if baking many instances of the same sharedMesh, want to cache these results rather than grab them multiple times from the mesh public class MeshChannels { public Vector3[] vertices; public Vector3[] normals; public Vector4[] tangents; public Vector2[] uv0raw; public Vector2[] uv0modified; public Vector2[] uv2; public Vector2[] uv3; public Vector2[] uv4; public Vector2[] uv5; public Vector2[] uv6; public Vector2[] uv7; public Vector2[] uv8; public Color[] colors; public BoneWeight[] boneWeights; public Matrix4x4[] bindPoses; public int[] triangles; public MBBlendShape[] blendShapes; } [Serializable] public class MBBlendShapeFrame { public float frameWeight; public Vector3[] vertices; public Vector3[] normals; public Vector3[] tangents; } [Serializable] public class MBBlendShape { public int gameObjectID; public GameObject gameObject; public string name; public int indexInSource; public MBBlendShapeFrame[] frames; } public class MeshChannelsCache { MB2_LogLevel LOG_LEVEL; MB2_LightmapOptions lightmapOption; protected Dictionary meshID2MeshChannels = new Dictionary(); public MeshChannelsCache(MB2_LogLevel ll, MB2_LightmapOptions lo) { LOG_LEVEL = ll; lightmapOption = lo; } internal Vector3[] GetVertices(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.vertices == null) { mc.vertices = m.vertices; } return mc.vertices; } internal Vector3[] GetNormals(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.normals == null) { mc.normals = _getMeshNormals(m); } return mc.normals; } internal Vector4[] GetTangents(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.tangents == null) { mc.tangents = _getMeshTangents(m); } return mc.tangents; } internal Vector2[] GetUv0Raw(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.uv0raw == null) { mc.uv0raw = _getMeshUVs(m); } return mc.uv0raw; } internal Vector2[] GetUv0Modified(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.uv0modified == null) { //todo mc.uv0modified = null; } return mc.uv0modified; } internal Vector2[] GetUVChannel(int channel, Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } switch(channel) { case 0: if (mc.uv0raw == null) { mc.uv0raw = GetUv0Raw(m); } return mc.uv0raw; case 2: if (mc.uv2 == null) { mc.uv2 = _getMeshUV2s(m); } return mc.uv2; case 3: if (mc.uv3 == null) { mc.uv3 = MBVersion.GetMeshChannel(channel, m, LOG_LEVEL); } return mc.uv3; case 4: if (mc.uv4 == null) { mc.uv4 = MBVersion.GetMeshChannel(channel, m, LOG_LEVEL); } return mc.uv4; case 5: if (mc.uv5 == null) { mc.uv5 = MBVersion.GetMeshChannel(channel, m, LOG_LEVEL); } return mc.uv5; case 6: if (mc.uv6 == null) { mc.uv6 = MBVersion.GetMeshChannel(channel, m, LOG_LEVEL); } return mc.uv6; case 7: if (mc.uv7 == null) { mc.uv7 = MBVersion.GetMeshChannel(channel, m, LOG_LEVEL); } return mc.uv7; case 8: if (mc.uv8 == null) { mc.uv8 = MBVersion.GetMeshChannel(channel, m, LOG_LEVEL); } return mc.uv8; default: Debug.LogError("Error mesh channel " + channel + " not supported"); break; } return null; } internal Color[] GetColors(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.colors == null) { mc.colors = _getMeshColors(m); } return mc.colors; } internal Matrix4x4[] GetBindposes(Renderer r) { MeshChannels mc; Mesh m = MB_Utility.GetMesh(r.gameObject); if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.bindPoses == null) { mc.bindPoses = _getBindPoses(r); } return mc.bindPoses; } internal BoneWeight[] GetBoneWeights(Renderer r, int numVertsInMeshBeingAdded) { MeshChannels mc; Mesh m = MB_Utility.GetMesh(r.gameObject); if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.boneWeights == null) { mc.boneWeights = _getBoneWeights(r, numVertsInMeshBeingAdded); } return mc.boneWeights; } internal int[] GetTriangles(Mesh m) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.triangles == null) { mc.triangles = m.triangles; } return mc.triangles; } internal MBBlendShape[] GetBlendShapes(Mesh m, int gameObjectID, GameObject gameObject) { if (MBVersion.GetMajorVersion() > 5 || ( MBVersion.GetMajorVersion() == 5 && MBVersion.GetMinorVersion() >= 3)) { MeshChannels mc; if (!meshID2MeshChannels.TryGetValue(m.GetInstanceID(), out mc)) { mc = new MeshChannels(); meshID2MeshChannels.Add(m.GetInstanceID(), mc); } if (mc.blendShapes == null) { MBBlendShape[] shapes = new MBBlendShape[m.blendShapeCount]; int arrayLen = m.vertexCount; for (int i = 0; i < shapes.Length; i++) { MBBlendShape shape = shapes[i] = new MBBlendShape(); shape.frames = new MBBlendShapeFrame[MBVersion.GetBlendShapeFrameCount(m, i)]; shape.name = m.GetBlendShapeName(i); shape.indexInSource = i; shape.gameObjectID = gameObjectID; shape.gameObject = gameObject; for (int j = 0; j < shape.frames.Length; j++) { MBBlendShapeFrame frame = shape.frames[j] = new MBBlendShapeFrame(); frame.frameWeight = MBVersion.GetBlendShapeFrameWeight(m, i, j); frame.vertices = new Vector3[arrayLen]; frame.normals = new Vector3[arrayLen]; frame.tangents = new Vector3[arrayLen]; MBVersion.GetBlendShapeFrameVertices(m, i, j, frame.vertices, frame.normals, frame.tangents); } } mc.blendShapes = shapes; return mc.blendShapes; } else { //copy cached blend shapes assigning a different gameObjectID MBBlendShape[] shapes = new MBBlendShape[mc.blendShapes.Length]; for (int i = 0; i < shapes.Length; i++) { shapes[i] = new MBBlendShape(); shapes[i].name = mc.blendShapes[i].name; shapes[i].indexInSource = mc.blendShapes[i].indexInSource; shapes[i].frames = mc.blendShapes[i].frames; shapes[i].gameObjectID = gameObjectID; shapes[i].gameObject = gameObject; } return shapes; } } else { return new MBBlendShape[0]; } } Color[] _getMeshColors(Mesh m) { Color[] cs = m.colors; if (cs.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Mesh " + m + " has no colors. Generating"); if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + m + " didn't have colors. Generating an array of white colors"); cs = new Color[m.vertexCount]; for (int i = 0; i < cs.Length; i++) { cs[i] = Color.white; } } return cs; } Vector3[] _getMeshNormals(Mesh m) { Vector3[] ns = m.normals; if (ns.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Mesh " + m + " has no normals. Generating"); if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + m + " didn't have normals. Generating normals."); Mesh tempMesh = (Mesh)GameObject.Instantiate(m); tempMesh.RecalculateNormals(); ns = tempMesh.normals; MB_Utility.Destroy(tempMesh); } return ns; } Vector4[] _getMeshTangents(Mesh m) { Vector4[] ts = m.tangents; if (ts.Length == 0) { if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Mesh " + m + " has no tangents. Generating"); if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + m + " didn't have tangents. Generating tangents."); Vector3[] verts = m.vertices; Vector2[] uvs = GetUv0Raw(m); Vector3[] norms = _getMeshNormals(m); ts = new Vector4[m.vertexCount]; for (int i = 0; i < m.subMeshCount; i++) { int[] tris = m.GetTriangles(i); _generateTangents(tris, verts, uvs, norms, ts); } } return ts; } Vector2 _HALF_UV = new Vector2(.5f, .5f); Vector2[] _getMeshUVs(Mesh m) { Vector2[] uv = m.uv; if (uv.Length == 0) { #if UNITY_EDITOR Debug.LogError("Mesh " + m + " has no uvs. Generating garbage uvs. Every UV = .5, .5"); #endif if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + m + " didn't have uvs. Generating uvs."); uv = new Vector2[m.vertexCount]; for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } } return uv; } Vector2[] _getMeshUV2s(Mesh m) { Vector2[] uv = m.uv2; if (uv.Length == 0) { #if UNITY_EDITOR Debug.LogError("Mesh " + m + " has no uv2s. Generating garbage UVs. Every UV = .5, .5"); #endif if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + m + " didn't have uv2s. Generating uv2s."); if (lightmapOption == MB2_LightmapOptions.copy_UV2_unchanged_to_separate_rects) Debug.LogError("Mesh " + m + " did not have a UV2 channel. Nothing to copy when trying to copy UV2 to separate rects. The combined mesh will not lightmap properly. Try using generate new uv2 layout."); uv = new Vector2[m.vertexCount]; for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } } return uv; } public static Matrix4x4[] _getBindPoses(Renderer r) { if (r is SkinnedMeshRenderer) { return ((SkinnedMeshRenderer)r).sharedMesh.bindposes; } else if (r is MeshRenderer) { Matrix4x4 bindPose = Matrix4x4.identity; Matrix4x4[] poses = new Matrix4x4[1]; poses[0] = bindPose; return poses; } else { Debug.LogError("Could not _getBindPoses. Object does not have a renderer"); return null; } } public static BoneWeight[] _getBoneWeights(Renderer r, int numVertsInMeshBeingAdded) { if (r is SkinnedMeshRenderer) { return ((SkinnedMeshRenderer)r).sharedMesh.boneWeights; } else if (r is MeshRenderer) { BoneWeight bw = new BoneWeight(); bw.boneIndex0 = bw.boneIndex1 = bw.boneIndex2 = bw.boneIndex3 = 0; bw.weight0 = 1f; bw.weight1 = bw.weight2 = bw.weight3 = 0f; BoneWeight[] bws = new BoneWeight[numVertsInMeshBeingAdded]; for (int i = 0; i < bws.Length; i++) bws[i] = bw; return bws; } else { Debug.LogError("Could not _getBoneWeights. Object does not have a renderer"); return null; } } void _generateTangents(int[] triangles, Vector3[] verts, Vector2[] uvs, Vector3[] normals, Vector4[] outTangents) { int triangleCount = triangles.Length; int vertexCount = verts.Length; Vector3[] tan1 = new Vector3[vertexCount]; Vector3[] tan2 = new Vector3[vertexCount]; for (int a = 0; a < triangleCount; a += 3) { int i1 = triangles[a + 0]; int i2 = triangles[a + 1]; int i3 = triangles[a + 2]; Vector3 v1 = verts[i1]; Vector3 v2 = verts[i2]; Vector3 v3 = verts[i3]; Vector2 w1 = uvs[i1]; Vector2 w2 = uvs[i2]; Vector2 w3 = uvs[i3]; float x1 = v2.x - v1.x; float x2 = v3.x - v1.x; float y1 = v2.y - v1.y; float y2 = v3.y - v1.y; float z1 = v2.z - v1.z; float z2 = v3.z - v1.z; float s1 = w2.x - w1.x; float s2 = w3.x - w1.x; float t1 = w2.y - w1.y; float t2 = w3.y - w1.y; float rBot = (s1 * t2 - s2 * t1); if (rBot == 0f) { Debug.LogError("Could not compute tangents. All UVs need to form a valid triangles in UV space. If any UV triangles are collapsed, tangents cannot be generated."); return; } float r = 1.0f / rBot; Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); tan1[i1] += sdir; tan1[i2] += sdir; tan1[i3] += sdir; tan2[i1] += tdir; tan2[i2] += tdir; tan2[i3] += tdir; } for (int a = 0; a < vertexCount; ++a) { Vector3 n = normals[a]; Vector3 t = tan1[a]; Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized; outTangents[a] = new Vector4(tmp.x, tmp.y, tmp.z); outTangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f; } } } //Used for comparing if skinned meshes use the same bone and bindpose. //Skinned meshes must be bound with the same TRS to share a bone. public struct BoneAndBindpose { public Transform bone; public Matrix4x4 bindPose; public BoneAndBindpose(Transform t, Matrix4x4 bp) { bone = t; bindPose = bp; } public override bool Equals(object obj) { if (obj is BoneAndBindpose) { if (bone == ((BoneAndBindpose)obj).bone && bindPose == ((BoneAndBindpose)obj).bindPose) { return true; } } return false; } public override int GetHashCode() { //OK if don't check bindPose well because bp should be the same return (bone.GetInstanceID() % 2147483647) ^ (int)bindPose[0, 0]; } } } }