using UnityEngine; using System; using System.Text; using System.Collections; using System.Collections.Generic; using DigitalOpus.MB.Core; using UnityEngine.Serialization; /// /// Used internally during the material baking process /// [Serializable] public class MB_AtlasesAndRects{ /// /// One atlas per texture property. /// public Texture2D[] atlases; [NonSerialized] public List mat2rect_map; public string[] texPropertyNames; } public class MB_TextureArrayResultMaterial { public MB_AtlasesAndRects[] slices; } [System.Serializable] public class MB_MultiMaterial{ public Material combinedMaterial; public bool considerMeshUVs; public List sourceMaterials = new List(); } [System.Serializable] public class MB_TexArraySliceRendererMatPair { public Material sourceMaterial; public GameObject renderer; } [System.Serializable] public class MB_TexArraySlice { public bool considerMeshUVs; public List sourceMaterials = new List(); public bool ContainsMaterial(Material mat) { for (int i = 0; i < sourceMaterials.Count; i++) { if (sourceMaterials[i].sourceMaterial == mat) return true; } return false; } public HashSet GetDistinctMaterials() { HashSet distinctMats = new HashSet(); if (sourceMaterials == null) return distinctMats; for (int i = 0; i < sourceMaterials.Count; i++) { distinctMats.Add(sourceMaterials[i].sourceMaterial); } return distinctMats; } public bool ContainsMaterialAndMesh(Material mat, Mesh mesh) { for (int i = 0; i < sourceMaterials.Count; i++) { if (sourceMaterials[i].sourceMaterial == mat && MB_Utility.GetMesh(sourceMaterials[i].renderer) == mesh) return true; } return false; } public List GetAllUsedMaterials(List usedMats) { usedMats.Clear(); for (int i = 0; i < sourceMaterials.Count; i++) { usedMats.Add(sourceMaterials[i].sourceMaterial); } return usedMats; } public List GetAllUsedRenderers(List allObjsFromTextureBaker) { if (considerMeshUVs) { List usedRendererGOs = new List(); for (int i = 0; i < sourceMaterials.Count; i++) { usedRendererGOs.Add(sourceMaterials[i].renderer); } return usedRendererGOs; } else { return allObjsFromTextureBaker; } } } [System.Serializable] public class MB_TextureArrayReference { public string texFromatSetName; public Texture2DArray texArray; public MB_TextureArrayReference(string formatSetName, Texture2DArray ta) { texFromatSetName = formatSetName; texArray = ta; } } [System.Serializable] public class MB_TexArrayForProperty { public string texPropertyName; public MB_TextureArrayReference[] formats = new MB_TextureArrayReference[0]; public MB_TexArrayForProperty(string name, MB_TextureArrayReference[] texRefs) { texPropertyName = name; formats = texRefs; } } [System.Serializable] public class MB_MultiMaterialTexArray { public Material combinedMaterial; public List slices = new List(); public List textureProperties = new List(); } [System.Serializable] public class MB_TextureArrayFormat { public string propertyName; public TextureFormat format; } [System.Serializable] public class MB_TextureArrayFormatSet { public string name; public TextureFormat defaultFormat; public MB_TextureArrayFormat[] formatOverrides; public bool ValidateTextureImporterFormatsExistsForTextureFormats(MB2_EditorMethodsInterface editorMethods, int idx) { if (editorMethods == null) return true; if (!editorMethods.TextureImporterFormatExistsForTextureFormat(defaultFormat)) { Debug.LogError("TextureImporter format does not exist for Texture Array Output Formats: " + idx + " Defaut Format " + defaultFormat); return false; } for (int i = 0; i < formatOverrides.Length; i++) { if (!editorMethods.TextureImporterFormatExistsForTextureFormat(formatOverrides[i].format)) { Debug.LogError("TextureImporter format does not exist for Texture Array Output Formats: " + idx + " Format Overrides: " + i + " (" + formatOverrides[i].format + ")"); return false; } } return true; } public TextureFormat GetFormatForProperty(string propName) { for (int i = 0; i < formatOverrides.Length; i++) { if (formatOverrides.Equals(formatOverrides[i].propertyName)) { return formatOverrides[i].format; } } return defaultFormat; } } [System.Serializable] public class MB_MaterialAndUVRect { /// /// The source material that was baked into the atlas. /// public Material material; /// /// The rectangle in the atlas where the texture (including all tiling) was copied to. /// public Rect atlasRect; /// /// For debugging. The name of the first srcObj that uses this MaterialAndUVRect. /// public string srcObjName; public int textureArraySliceIdx = -1; public bool allPropsUseSameTiling = true; /// /// Only valid if allPropsUseSameTiling = true. Else should be 0,0,0,0 /// The material tiling on the source material /// [FormerlySerializedAs("sourceMaterialTiling")] public Rect allPropsUseSameTiling_sourceMaterialTiling; /// /// Only valid if allPropsUseSameTiling = true. Else should be 0,0,0,0 /// The encapsulating sampling rect that was used to sample for the atlas. Note that the case /// of dont-considerMeshUVs is the same as do-considerMeshUVs where the uv rect is 0,0,1,1 /// [FormerlySerializedAs("samplingEncapsulatinRect")] public Rect allPropsUseSameTiling_samplingEncapsulatinRect; /// /// Only valid if allPropsUseSameTiling = false. /// The UVrect of the source mesh that was baked. We are using a trick here. /// Instead of storing the material tiling for each /// texture property here, we instead bake all those tilings into the atlases and here we pretend /// that all those tilings were 0,0,1,1. Then all we need is to store is the /// srcUVsamplingRect /// public Rect propsUseDifferntTiling_srcUVsamplingRect; [NonSerialized] public List objectsThatUse; /// /// The tilling type for this rectangle in the atlas. /// public MB_TextureTilingTreatment tilingTreatment = MB_TextureTilingTreatment.unknown; /// The Material /// The rect in the atlas this material maps to /// If true then use sourceMaterialTiling and samplingEncapsulatingRect. /// if false then use srcUVsamplingRect. None used values should be 0,0,0,0. /// allPropsUseSameTiling_sourceMaterialTiling /// allPropsUseSameTiling_samplingEncapsulatinRect /// propsUseDifferntTiling_srcUVsamplingRect public MB_MaterialAndUVRect(Material mat, Rect destRect, bool allPropsUseSameTiling, Rect sourceMaterialTiling, Rect samplingEncapsulatingRect, Rect srcUVsamplingRect, MB_TextureTilingTreatment treatment, string objName) { if (allPropsUseSameTiling) { Debug.Assert(srcUVsamplingRect == new Rect(0, 0, 0, 0)); } if (!allPropsUseSameTiling) { Debug.Assert(samplingEncapsulatingRect == new Rect(0, 0, 0, 0)); Debug.Assert(sourceMaterialTiling == new Rect(0, 0, 0, 0)); } material = mat; atlasRect = destRect; tilingTreatment = treatment; this.allPropsUseSameTiling = allPropsUseSameTiling; allPropsUseSameTiling_sourceMaterialTiling = sourceMaterialTiling; allPropsUseSameTiling_samplingEncapsulatinRect = samplingEncapsulatingRect; propsUseDifferntTiling_srcUVsamplingRect = srcUVsamplingRect; srcObjName = objName; } public override int GetHashCode() { return material.GetInstanceID() ^ allPropsUseSameTiling_samplingEncapsulatinRect.GetHashCode() ^ propsUseDifferntTiling_srcUVsamplingRect.GetHashCode(); } public override bool Equals(object obj) { if (!(obj is MB_MaterialAndUVRect)) return false; MB_MaterialAndUVRect b = (MB_MaterialAndUVRect)obj; return material == b.material && allPropsUseSameTiling_samplingEncapsulatinRect == b.allPropsUseSameTiling_samplingEncapsulatinRect && allPropsUseSameTiling_sourceMaterialTiling == b.allPropsUseSameTiling_sourceMaterialTiling && allPropsUseSameTiling == b.allPropsUseSameTiling && propsUseDifferntTiling_srcUVsamplingRect == b.propsUseDifferntTiling_srcUVsamplingRect; } public Rect GetEncapsulatingRect() { if (allPropsUseSameTiling) { return allPropsUseSameTiling_samplingEncapsulatinRect; } else { return propsUseDifferntTiling_srcUVsamplingRect; } } public Rect GetMaterialTilingRect() { if (allPropsUseSameTiling) { return allPropsUseSameTiling_sourceMaterialTiling; } else { return new Rect(0, 0, 1, 1); } } } /// /// This class stores the results from an MB2_TextureBaker when materials are combined into atlases. It stores /// a list of materials and the corresponding UV rectangles in the atlases. It also stores the configuration /// options that were used to generate the combined material. /// /// It can be saved as an asset in the project so that textures can be baked in one scene and used in another. /// public class MB2_TextureBakeResults : ScriptableObject { public enum ResultType { atlas, textureArray, } public static int VERSION { get { return 3252; } } public int version; public ResultType resultType = ResultType.atlas; /// /// Information about the atlas UV rects. /// IMPORTANT a source material can appear more than once in this list. If we are using considerUVs, /// then different parts of a source texture could be extracted to different atlas rects. /// public MB_MaterialAndUVRect[] materialsAndUVRects; /// /// This is like the combinedMeshRenderer.sharedMaterials. Some materials may be omitted if they have zero submesh triangles. /// There is one of these per MultiMaterial mapping. /// Lists source materials that were combined into each result material, and whether considerUVs was used. /// This is the result materials if building for atlases. /// public MB_MultiMaterial[] resultMaterials; /// /// This is like combinedMeshRenderer.sharedMaterials. Each result mat likely uses a different shader. /// There is one of these per MultiMaterialTexArray mapping. /// Each of these lists the slices and each slice has list of source mats that are in that slice. /// This is the result materials if building for texArrays. /// public MB_MultiMaterialTexArray[] resultMaterialsTexArray; public bool doMultiMaterial; public MB2_TextureBakeResults() { version = VERSION; } private void OnEnable() { // backward compatibility copy depricated fixOutOfBounds values to resultMaterials if (version < 3251) { for (int i = 0; i < materialsAndUVRects.Length; i++) { materialsAndUVRects[i].allPropsUseSameTiling = true; } } version = VERSION; } public int NumResultMaterials() { if (resultType == ResultType.atlas) return resultMaterials.Length; else return resultMaterialsTexArray.Length; } public Material GetCombinedMaterialForSubmesh(int idx) { if (resultType == ResultType.atlas) { return resultMaterials[idx].combinedMaterial; } else { return resultMaterialsTexArray[idx].combinedMaterial; } } public bool GetConsiderMeshUVs(int idxInSrcMats, Material srcMaterial) { if (resultType == ResultType.atlas) { return resultMaterials[idxInSrcMats].considerMeshUVs; } else { // TODO do this once and cache. List slices = resultMaterialsTexArray[idxInSrcMats].slices; for (int i = 0; i < slices.Count; i++) { if (slices[i].ContainsMaterial(srcMaterial)) return slices[i].considerMeshUVs; } Debug.LogError("There were no source materials for any slice in this result material."); return false; } } public List GetSourceMaterialsUsedByResultMaterial(int resultMatIdx) { if (resultType == ResultType.atlas) { return resultMaterials[resultMatIdx].sourceMaterials; } else { // TODO do this once and cache. HashSet output = new HashSet(); List slices = resultMaterialsTexArray[resultMatIdx].slices; for (int i = 0; i < slices.Count; i++) { List usedMaterials = new List(); slices[i].GetAllUsedMaterials(usedMaterials); for (int j = 0; j < usedMaterials.Count; j++) { output.Add(usedMaterials[j]); } } List outMats = new List(output); return outMats; } } /// /// Creates for materials on renderer. /// /// Generates an MB2_TextureBakeResult that can be used if all objects to be combined use the same material. /// Returns a MB2_TextureBakeResults that will map all materials used by renderer r to /// the rectangle 0,0..1,1 in the atlas. public static MB2_TextureBakeResults CreateForMaterialsOnRenderer(GameObject[] gos, List matsOnTargetRenderer) { HashSet fullMaterialList = new HashSet(matsOnTargetRenderer); for (int i = 0; i < gos.Length; i++) { if (gos[i] == null) { Debug.LogError(string.Format("Game object {0} in list of objects to add was null", i)); return null; } Material[] oMats = MB_Utility.GetGOMaterials(gos[i]); if (oMats.Length == 0) { Debug.LogError(string.Format("Game object {0} in list of objects to add no renderer", i)); return null; } for (int j = 0; j < oMats.Length; j++) { if (!fullMaterialList.Contains(oMats[j])) { fullMaterialList.Add(oMats[j]); } } } Material[] rms = new Material[fullMaterialList.Count]; fullMaterialList.CopyTo(rms); MB2_TextureBakeResults tbr = (MB2_TextureBakeResults) ScriptableObject.CreateInstance( typeof(MB2_TextureBakeResults) ); List mss = new List(); for (int i = 0; i < rms.Length; i++) { if (rms[i] != null) { MB_MaterialAndUVRect matAndUVRect = new MB_MaterialAndUVRect(rms[i], new Rect(0f, 0f, 1f, 1f), true, new Rect(0f,0f,1f,1f), new Rect(0f,0f,1f,1f), new Rect(0,0,0,0), MB_TextureTilingTreatment.none, ""); if (!mss.Contains(matAndUVRect)) { mss.Add(matAndUVRect); } } } tbr.resultMaterials = new MB_MultiMaterial[mss.Count]; for (int i = 0; i < mss.Count; i++){ tbr.resultMaterials[i] = new MB_MultiMaterial(); List sourceMats = new List(); sourceMats.Add (mss[i].material); tbr.resultMaterials[i].sourceMaterials = sourceMats; tbr.resultMaterials[i].combinedMaterial = mss[i].material; tbr.resultMaterials[i].considerMeshUVs = false; } if (rms.Length == 1) { tbr.doMultiMaterial = false; } else { tbr.doMultiMaterial = true; } tbr.materialsAndUVRects = mss.ToArray(); return tbr; } public bool DoAnyResultMatsUseConsiderMeshUVs() { if (resultType == ResultType.atlas) { if (resultMaterials == null) return false; for (int i = 0; i < resultMaterials.Length; i++) { if (resultMaterials[i].considerMeshUVs) return true; } return false; } else { if (resultMaterialsTexArray == null) return false; for (int i = 0; i < resultMaterialsTexArray.Length; i++) { MB_MultiMaterialTexArray resMat = resultMaterialsTexArray[i]; for (int j = 0; j < resMat.slices.Count; j++) { if (resMat.slices[j].considerMeshUVs) return true; } } return false; } } public bool ContainsMaterial(Material m) { for (int i = 0; i < materialsAndUVRects.Length; i++) { if (materialsAndUVRects[i].material == m){ return true; } } return false; } public string GetDescription(){ StringBuilder sb = new StringBuilder(); sb.Append("Shaders:\n"); HashSet shaders = new HashSet(); if (materialsAndUVRects != null){ for (int i = 0; i < materialsAndUVRects.Length; i++){ if (materialsAndUVRects[i].material != null) { shaders.Add(materialsAndUVRects[i].material.shader); } } } foreach(Shader m in shaders){ sb.Append(" ").Append(m.name).AppendLine(); } sb.Append("Materials:\n"); if (materialsAndUVRects != null){ for (int i = 0; i < materialsAndUVRects.Length; i++){ if (materialsAndUVRects[i].material != null) { sb.Append(" ").Append(materialsAndUVRects[i].material.name).AppendLine(); } } } return sb.ToString(); } public void UpgradeToCurrentVersion(MB2_TextureBakeResults tbr) { if (tbr.version < 3252) { for (int i = 0; i < tbr.materialsAndUVRects.Length; i++) { tbr.materialsAndUVRects[i].allPropsUseSameTiling = true; } } } public static bool IsMeshAndMaterialRectEnclosedByAtlasRect(MB_TextureTilingTreatment tilingTreatment, Rect uvR, Rect sourceMaterialTiling, Rect samplingEncapsulatinRect, MB2_LogLevel logLevel) { Rect potentialRect = new Rect(); // test to see if this would fit in what was baked in the atlas potentialRect = MB3_UVTransformUtility.CombineTransforms(ref uvR, ref sourceMaterialTiling); if (logLevel >= MB2_LogLevel.trace) { if (logLevel >= MB2_LogLevel.trace) Debug.Log("IsMeshAndMaterialRectEnclosedByAtlasRect Rect in atlas uvR=" + uvR.ToString("f5") + " sourceMaterialTiling=" + sourceMaterialTiling.ToString("f5") + "Potential Rect (must fit in encapsulating) " + potentialRect.ToString("f5") + " encapsulating=" + samplingEncapsulatinRect.ToString("f5") + " tilingTreatment=" + tilingTreatment); } if (tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeX) { if (MB3_UVTransformUtility.LineSegmentContainsShifted(samplingEncapsulatinRect.y, samplingEncapsulatinRect.height, potentialRect.y, potentialRect.height)) { return true; } } else if (tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeY) { if (MB3_UVTransformUtility.LineSegmentContainsShifted(samplingEncapsulatinRect.x, samplingEncapsulatinRect.width, potentialRect.x, potentialRect.width)) { return true; } } else if (tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeXY) { //only one rect in atlas and is edge to edge in both X and Y directions. return true; } else { if (MB3_UVTransformUtility.RectContainsShifted(ref samplingEncapsulatinRect, ref potentialRect)) { return true; } } return false; } }