diff options
| author | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
|---|---|---|
| committer | tylermurphy534 <tylermurphy534@gmail.com> | 2022-11-06 15:12:42 -0500 |
| commit | eb84bb298d2b95aec7b2ae12cbf25ac64f25379a (patch) | |
| tree | efd616a157df06ab661c6d56651853431ac6b08b /VRCSDK3Worlds/Assets/MeshBaker/scripts | |
| download | unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.gz unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.tar.bz2 unityprojects-eb84bb298d2b95aec7b2ae12cbf25ac64f25379a.zip | |
move to self host
Diffstat (limited to 'VRCSDK3Worlds/Assets/MeshBaker/scripts')
134 files changed, 23262 insertions, 0 deletions
diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssemblyInfo.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssemblyInfo.cs new file mode 100644 index 00000000..27c38700 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.CompilerServices; +using System; + +[assembly: InternalsVisibleTo("MeshBakerTests")] + +public class AssemblyInfo +{ + +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssemblyInfo.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssemblyInfo.cs.meta new file mode 100644 index 00000000..35daa9bb --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssemblyInfo.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 128f1b6366765e846853f8e143ca26cd +timeCreated: 1543357217 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers.meta new file mode 100644 index 00000000..0e4c07a6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: af0ffdc1743897c43b4d05b89351abb4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers/CustomizerPutSliceIndexInUV0_z.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers/CustomizerPutSliceIndexInUV0_z.cs new file mode 100644 index 00000000..6a2fb77f --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers/CustomizerPutSliceIndexInUV0_z.cs @@ -0,0 +1,44 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using DigitalOpus.MB.Core; + +namespace DigitalOpus.MB.Core +{ + /// <summary> + /// This MeshAssignCustomizer alters the UV data as it is being assigned to the mesh. + /// It appends the Texture Array slice index in the UV.z channel. + /// + /// Shaders must be modified to read the slice index from the UV.z channel to use this. + /// </summary> + [CreateAssetMenu(fileName = "MeshAssignCustomizerPutSliceIdxInUV0_z", menuName = "Mesh Baker/Assign To Mesh Customizer/Put Slice Index In UV0.z", order = 1)] + public class CustomizerPutSliceIndexInUV0_z : MB_DefaultMeshAssignCustomizer + { + public override void meshAssign_UV0(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { + if (textureBakeResults.resultType == MB2_TextureBakeResults.ResultType.atlas) + { + mesh.uv = uvs; + } + else + { + { + if (uvs.Length == sliceIndexes.Length) + { + List<Vector3> nuvs = new List<Vector3>(); + for (int i = 0; i < uvs.Length; i++) + { + nuvs.Add(new Vector3(uvs[i].x, uvs[i].y, sliceIndexes[i])); + } + + mesh.SetUVs(0, nuvs); + } + else + { + Debug.LogError("UV slice buffer was not the same size as the uv buffer"); + } + } + } + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers/CustomizerPutSliceIndexInUV0_z.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers/CustomizerPutSliceIndexInUV0_z.cs.meta new file mode 100644 index 00000000..d0eb1f29 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/AssignToMeshCustomizers/CustomizerPutSliceIndexInUV0_z.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e5361140dc65f264fbaef9318fb07254 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_TextureBakeResults.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_TextureBakeResults.cs new file mode 100644 index 00000000..5a39562d --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_TextureBakeResults.cs @@ -0,0 +1,635 @@ +using UnityEngine; +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using DigitalOpus.MB.Core; +using UnityEngine.Serialization; + + +/// <summary> +/// Used internally during the material baking process +/// </summary> +[Serializable] +public class MB_AtlasesAndRects{ + /// <summary> + /// One atlas per texture property. + /// </summary> + public Texture2D[] atlases; + [NonSerialized] + public List<MB_MaterialAndUVRect> 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<Material> sourceMaterials = new List<Material>(); +} + +[System.Serializable] +public class MB_TexArraySliceRendererMatPair +{ + public Material sourceMaterial; + public GameObject renderer; +} + +[System.Serializable] +public class MB_TexArraySlice +{ + public bool considerMeshUVs; + public List<MB_TexArraySliceRendererMatPair> sourceMaterials = new List<MB_TexArraySliceRendererMatPair>(); + public bool ContainsMaterial(Material mat) + { + for (int i = 0; i < sourceMaterials.Count; i++) + { + if (sourceMaterials[i].sourceMaterial == mat) return true; + } + + return false; + } + + public HashSet<Material> GetDistinctMaterials() + { + HashSet<Material> distinctMats = new HashSet<Material>(); + 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<Material> GetAllUsedMaterials(List<Material> usedMats) + { + usedMats.Clear(); + for (int i = 0; i < sourceMaterials.Count; i++) + { + usedMats.Add(sourceMaterials[i].sourceMaterial); + } + return usedMats; + } + + public List<GameObject> GetAllUsedRenderers(List<GameObject> allObjsFromTextureBaker) + { + if (considerMeshUVs) + { + List<GameObject> usedRendererGOs = new List<GameObject>(); + 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<MB_TexArraySlice> slices = new List<MB_TexArraySlice>(); + public List<MB_TexArrayForProperty> textureProperties = new List<MB_TexArrayForProperty>(); +} + +[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 +{ + /// <summary> + /// The source material that was baked into the atlas. + /// </summary> + public Material material; + + /// <summary> + /// The rectangle in the atlas where the texture (including all tiling) was copied to. + /// </summary> + public Rect atlasRect; + + /// <summary> + /// For debugging. The name of the first srcObj that uses this MaterialAndUVRect. + /// </summary> + public string srcObjName; + + public int textureArraySliceIdx = -1; + + public bool allPropsUseSameTiling = true; + + /// <summary> + /// Only valid if allPropsUseSameTiling = true. Else should be 0,0,0,0 + /// The material tiling on the source material + /// </summary> + [FormerlySerializedAs("sourceMaterialTiling")] + public Rect allPropsUseSameTiling_sourceMaterialTiling; + + /// <summary> + /// 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 + /// </summary> + [FormerlySerializedAs("samplingEncapsulatinRect")] + public Rect allPropsUseSameTiling_samplingEncapsulatinRect; + + /// <summary> + /// 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 + /// </summary> + public Rect propsUseDifferntTiling_srcUVsamplingRect; + + [NonSerialized] + public List<GameObject> objectsThatUse; + + /// <summary> + /// The tilling type for this rectangle in the atlas. + /// </summary> + public MB_TextureTilingTreatment tilingTreatment = MB_TextureTilingTreatment.unknown; + + /// <param name="mat">The Material</param> + /// <param name="destRect">The rect in the atlas this material maps to</param> + /// <param name="allPropsUseSameTiling">If true then use sourceMaterialTiling and samplingEncapsulatingRect. + /// if false then use srcUVsamplingRect. None used values should be 0,0,0,0.</param> + /// <param name="sourceMaterialTiling">allPropsUseSameTiling_sourceMaterialTiling</param> + /// <param name="samplingEncapsulatingRect">allPropsUseSameTiling_samplingEncapsulatinRect</param> + /// <param name="srcUVsamplingRect">propsUseDifferntTiling_srcUVsamplingRect</param> + 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); + } + } +} + +/// <summary> +/// 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. +/// </summary> +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; + + /// <summary> + /// 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. + /// </summary> + public MB_MaterialAndUVRect[] materialsAndUVRects; + + /// <summary> + /// 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. + /// </summary> + public MB_MultiMaterial[] resultMaterials; + + + /// <summary> + /// 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. + /// </summary> + 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<MB_TexArraySlice> 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<Material> GetSourceMaterialsUsedByResultMaterial(int resultMatIdx) + { + if (resultType == ResultType.atlas) + { + return resultMaterials[resultMatIdx].sourceMaterials; + } else + { + // TODO do this once and cache. + HashSet<Material> output = new HashSet<Material>(); + List<MB_TexArraySlice> slices = resultMaterialsTexArray[resultMatIdx].slices; + for (int i = 0; i < slices.Count; i++) + { + List<Material> usedMaterials = new List<Material>(); + slices[i].GetAllUsedMaterials(usedMaterials); + for (int j = 0; j < usedMaterials.Count; j++) + { + output.Add(usedMaterials[j]); + } + + } + + List<Material> outMats = new List<Material>(output); + return outMats; + } + } + + /// <summary> + /// Creates for materials on renderer. + /// </summary> + /// <returns>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.</returns> + public static MB2_TextureBakeResults CreateForMaterialsOnRenderer(GameObject[] gos, List<Material> matsOnTargetRenderer) + { + HashSet<Material> fullMaterialList = new HashSet<Material>(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<MB_MaterialAndUVRect> mss = new List<MB_MaterialAndUVRect>(); + 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<Material> sourceMats = new List<Material>(); + 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<Shader> shaders = new HashSet<Shader>(); + 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; + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_TextureBakeResults.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_TextureBakeResults.cs.meta new file mode 100644 index 00000000..65f6d2e5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_TextureBakeResults.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d41887b1546f5c44ab54e7e65aad3bc +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBones.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBones.cs new file mode 100644 index 00000000..8802ca6f --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBones.cs @@ -0,0 +1,26 @@ +using UnityEngine; +using System.Collections; +using DigitalOpus.MB.Core; + +public class MB2_UpdateSkinnedMeshBoundsFromBones : MonoBehaviour { + SkinnedMeshRenderer smr; + Transform[] bones; + + void Start () { + smr = GetComponent<SkinnedMeshRenderer>(); + if (smr == null){ + Debug.LogError("Need to attach MB2_UpdateSkinnedMeshBoundsFromBones script to an object with a SkinnedMeshRenderer component attached."); + return; + } + bones = smr.bones; + bool origVal = smr.updateWhenOffscreen; + smr.updateWhenOffscreen = true; + smr.updateWhenOffscreen = origVal; + } + + void Update () { + if (smr != null){ + MB3_MeshCombiner.UpdateSkinnedMeshApproximateBoundsFromBonesStatic(bones,smr); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBones.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBones.cs.meta new file mode 100644 index 00000000..41185b84 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBones.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dfe3957da2b5efe438f7ffe6a2cfa712 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBounds.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBounds.cs new file mode 100644 index 00000000..e48e98af --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBounds.cs @@ -0,0 +1,38 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using DigitalOpus.MB.Core; + +public class MB2_UpdateSkinnedMeshBoundsFromBounds : MonoBehaviour { + public List<GameObject> objects; + SkinnedMeshRenderer smr; + + void Start () { + smr = GetComponent<SkinnedMeshRenderer>(); + if (smr == null){ + Debug.LogError("Need to attach MB2_UpdateSkinnedMeshBoundsFromBounds script to an object with a SkinnedMeshRenderer component attached."); + return; + } + if (objects == null || objects.Count == 0){ + Debug.LogWarning("The MB2_UpdateSkinnedMeshBoundsFromBounds had no Game Objects. It should have the same list of game objects that the MeshBaker does."); + smr = null; + return; + } + for (int i = 0; i < objects.Count; i++){ + if (objects[i] == null || objects[i].GetComponent<Renderer>() == null){ + Debug.LogError("The list of objects had nulls or game objects without a renderer attached at position " + i); + smr = null; + return; + } + } + bool origVal = smr.updateWhenOffscreen; + smr.updateWhenOffscreen = true; + smr.updateWhenOffscreen = origVal; + } + + void Update () { + if (smr != null && objects != null){ + MB3_MeshCombiner.UpdateSkinnedMeshApproximateBoundsFromBoundsStatic(objects,smr); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBounds.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBounds.cs.meta new file mode 100644 index 00000000..098979f6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB2_UpdateSkinnedMeshBoundsFromBounds.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f4510fdae919c84a82b04658438580e +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BatchPrefabBaker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BatchPrefabBaker.cs new file mode 100644 index 00000000..1815ec66 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BatchPrefabBaker.cs @@ -0,0 +1,70 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using DigitalOpus.MB.Core; + +public class MB3_BatchPrefabBaker : MonoBehaviour { + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + + [System.Serializable] + public class MB3_PrefabBakerRow{ + public GameObject sourcePrefab; + public GameObject resultPrefab; + } + + public MB3_PrefabBakerRow[] prefabRows = new MB3_PrefabBakerRow[0]; + + public string outputPrefabFolder = ""; + + [ContextMenu("Create Instances For Prefab Rows")] + public void CreateSourceAndResultPrefabInstances() + { +#if UNITY_EDITOR + // instantiate the prefabs + List<GameObject> srcPrefabs = new List<GameObject>(); + List<GameObject> resultPrefabs = new List<GameObject>(); + for (int i = 0; i < prefabRows.Length; i++) + { + if (prefabRows[i].sourcePrefab != null && prefabRows[i].resultPrefab != null) + { + GameObject src = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefabRows[i].sourcePrefab); + GameObject result = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefabRows[i].resultPrefab); + srcPrefabs.Add(src); + resultPrefabs.Add(result); + } + } + + Vector3 offsetX = new Vector3(2, 0, 0); + + // layout the prefabs + GameObject srcRoot = new GameObject("SourcePrefabInstances"); + GameObject resultRoot = new GameObject("ResultPrefabInstance"); + + Vector3 srcPos = Vector3.zero - offsetX; + Vector3 resultPos = Vector3.zero + offsetX; + for (int i = 0; i < srcPrefabs.Count; i++) + { + Renderer[] rs = srcPrefabs[i].GetComponentsInChildren<Renderer>(true); + Bounds b = new Bounds(Vector3.zero, Vector3.one); + if (rs.Length > 0) + { + b = rs[0].bounds; + for (int bndsIdx = 1; bndsIdx < rs.Length; bndsIdx++) + { + b.Encapsulate(rs[bndsIdx].bounds); + } + } + + srcPrefabs[i].transform.parent = srcRoot.transform; + resultPrefabs[i].transform.parent = resultRoot.transform; + srcPrefabs[i].transform.localPosition = srcPos + new Vector3(-b.extents.x, 0, b.extents.z + b.extents.z * .3f); + resultPrefabs[i].transform.localPosition = resultPos + new Vector3(b.extents.x, 0, b.extents.z + b.extents.z * .3f); + srcPos += new Vector3(0,0,b.size.z + 1f); + resultPos += new Vector3(0, 0, b.size.z + 1f); + } +#else + Debug.LogError("Cannot be used outside the editor"); +#endif + } + +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BatchPrefabBaker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BatchPrefabBaker.cs.meta new file mode 100644 index 00000000..6f4393e8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BatchPrefabBaker.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c0409865d977e644d97db08d14bfa7f8 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BoneWeightCopier.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BoneWeightCopier.cs new file mode 100644 index 00000000..7d4305d2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BoneWeightCopier.cs @@ -0,0 +1,11 @@ +using UnityEngine; +using System; +using System.Collections; + +public class MB3_BoneWeightCopier : MonoBehaviour { + public GameObject inputGameObject; + public GameObject outputPrefab; + public float radius = .01f; + public SkinnedMeshRenderer seamMesh; + public string outputFolder; +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BoneWeightCopier.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BoneWeightCopier.cs.meta new file mode 100644 index 00000000..94752330 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_BoneWeightCopier.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e4f7f279433826c4db0bf060ba15c0a1 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_Comment.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_Comment.cs new file mode 100644 index 00000000..dca512cc --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_Comment.cs @@ -0,0 +1,11 @@ +using UnityEngine; +using System.Collections; + +namespace DigitalOpus.MB.Core +{ + public class MB3_Comment : MonoBehaviour + { + [Multiline] + public string comment; + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_Comment.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_Comment.cs.meta new file mode 100644 index 00000000..3f1538c8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_Comment.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4154d7f8918b2b24f8079460bf2ec3ed +timeCreated: 1536791543 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_DisableHiddenAnimations.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_DisableHiddenAnimations.cs new file mode 100644 index 00000000..72853881 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_DisableHiddenAnimations.cs @@ -0,0 +1,25 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +public class MB3_DisableHiddenAnimations : MonoBehaviour { + public List<Animation> animationsToCull = new List<Animation>(); + + void Start () { + if (GetComponent<SkinnedMeshRenderer> () == null) { + Debug.LogError ("The MB3_CullHiddenAnimations script was placed on and object " + name + " which has no SkinnedMeshRenderer attached"); + } + } + + void OnBecameVisible(){ + for (int i = 0; i < animationsToCull.Count; i++) { + if (animationsToCull[i] != null) animationsToCull[i].enabled = true; + } + } + + void OnBecameInvisible(){ + for (int i = 0; i < animationsToCull.Count; i++) { + if (animationsToCull[i] != null) animationsToCull[i].enabled = false; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_DisableHiddenAnimations.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_DisableHiddenAnimations.cs.meta new file mode 100644 index 00000000..de57fb6d --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_DisableHiddenAnimations.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1c48ae3fc24ee5b4db7320b8d0e1d35d +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MBVersionConcrete.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MBVersionConcrete.cs new file mode 100644 index 00000000..58ddc81e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MBVersionConcrete.cs @@ -0,0 +1,674 @@ +/** + * \brief Hax! DLLs cannot interpret preprocessor directives, so this class acts as a "bridge" + */ +using System; +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +#if MB_USING_HDRP && UNITY_2019_3_OR_NEWER + using UnityEngine.Rendering.HighDefinition; +#elif MB_USING_HDRP && UNITY_2018_4_OR_NEWER + using UnityEngine.Experimental.Rendering.HDPipeline; +#endif + +namespace DigitalOpus.MB.Core +{ + public class MBVersionConcrete : MBVersionInterface + { + public string version() + { + return "3.32.0"; + } + + public int GetMajorVersion() + { + /* +#if UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 + return 3; +#elif UNITY_4_0 || UNITY_4_0_1 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7 + return 4; +#else + return 5; +#endif + */ + string v = Application.unityVersion; + String[] vs = v.Split(new char[] { '.' }); + return Int32.Parse(vs[0]); + } + + public int GetMinorVersion() + { + /* +#if UNITY_3_0 || UNITY_3_0_0 + return 0; +#elif UNITY_3_1 + return 1; +#elif UNITY_3_2 + return 2; +#elif UNITY_3_3 + return 3; +#elif UNITY_3_4 + return 4; +#elif UNITY_3_5 + return 5; +#elif UNITY_4_0 || UNITY_4_0_1 + return 0; +#elif UNITY_4_1 + return 1; +#elif UNITY_4_2 + return 2; +#elif UNITY_4_3 + return 3; +#elif UNITY_4_4 + return 4; +#elif UNITY_4_5 + return 5; +#else + return 0; +#endif + */ + string v = Application.unityVersion; String[] vs = v.Split(new char[] { '.' }); + return Int32.Parse(vs[1]); + } + + public bool GetActive(GameObject go) + { +#if UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 + return go.active; +#else + return go.activeInHierarchy; +#endif + } + + public void SetActive(GameObject go, bool isActive) + { +#if UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 + go.active = isActive; +#else + go.SetActive(isActive); +#endif + } + + public void SetActiveRecursively(GameObject go, bool isActive) + { +#if UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 + go.SetActiveRecursively(isActive); +#else + go.SetActive(isActive); +#endif + } + + public UnityEngine.Object[] FindSceneObjectsOfType(Type t) + { +#if UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 + return GameObject.FindSceneObjectsOfType(t); +#else + return GameObject.FindObjectsOfType(t); +#endif + } + + public void OptimizeMesh(Mesh m) + { +#if UNITY_EDITOR +#if UNITY_5_5_OR_NEWER + UnityEditor.MeshUtility.Optimize(m); +#else + m.Optimize(); +#endif +#endif + } + + + public bool IsRunningAndMeshNotReadWriteable(Mesh m) + { + if (Application.isPlaying) + { +#if UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 + return false; +#else + return !m.isReadable; +#endif + } + else + { + return false; + } + } + + Vector2 _HALF_UV = new Vector2(.5f, .5f); + public Vector2[] GetMeshUV1s(Mesh m, MB2_LogLevel LOG_LEVEL) + { + Vector2[] uv; +#if (UNITY_4_6 || UNITY_4_7 || UNITY_4_5 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0_1 || UNITY_4_0 || UNITY_3_5) + uv = m.uv1; + +#else + if (LOG_LEVEL >= MB2_LogLevel.warn) MB2_Log.LogDebug("UV1 does not exist in Unity 5+"); + uv = m.uv; +#endif + if (uv.Length == 0) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Mesh " + m + " has no uv1s. Generating"); + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + m + " didn't have uv1s. Generating uv1s."); + uv = new Vector2[m.vertexCount]; + for (int i = 0; i < uv.Length; i++) { uv[i] = _HALF_UV; } + } + return uv; + } + + public Vector2[] GetMeshUVChannel(int channel, Mesh m, MB2_LogLevel LOG_LEVEL) + { + Vector2[] uvs = new Vector2[0]; + + switch (channel) + { + case 0: + uvs = m.uv; + break; + case 2: + uvs = m.uv2; + break; + case 3: + uvs = m.uv3; + break; + case 4: + uvs = m.uv4; + break; +#if UNITY_2018_2_OR_NEWER + case 5: + uvs = m.uv5; + break; + case 6: + uvs = m.uv6; + break; + case 7: + uvs = m.uv7; + break; + case 8: + uvs = m.uv8; + break; +#endif + default: + Debug.LogError("Mesh does not have UV channel " + channel); + break; + } + + if (uvs.Length == 0) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Mesh " + m + " has no uv" + channel + ". Generating"); + uvs = new Vector2[m.vertexCount]; + for (int i = 0; i < uvs.Length; i++) { uvs[i] = _HALF_UV; } + } + + return uvs; + } + + public void MeshClear(Mesh m, bool t) + { +#if UNITY_3_5 + m.Clear(); +#else + m.Clear(t); +#endif + } + + public void MeshAssignUVChannel(int channel, Mesh m, Vector2[] uvs) + { + switch (channel) + { + case 0: + m.uv = uvs; + break; + case 2: + m.uv2 = uvs; + break; + case 3: + m.uv3 = uvs; + break; + case 4: + m.uv4 = uvs; + break; +#if UNITY_2018_2_OR_NEWER + case 5: + m.uv5 = uvs; + break; + case 6: + m.uv6 = uvs; + break; + case 7: + m.uv7 = uvs; + break; + case 8: + m.uv8 = uvs; + break; +#endif + default: + Debug.LogError("Mesh does not have UV channel " + channel); + break; + } + } + + public Vector4 GetLightmapTilingOffset(Renderer r) + { +#if (UNITY_4_6 || UNITY_4_7 || UNITY_4_5 || UNITY_4_3 || UNITY_4_2 || UNITY_4_1 || UNITY_4_0_1 || UNITY_4_0 || UNITY_3_5) + return r.lightmapTilingOffset ; +#else + return r.lightmapScaleOffset; //r.lightmapScaleOffset ; +#endif + } +#if UNITY_5_OR_NEWER + public Transform[] GetBones(Renderer r, bool isSkinnedMeshWithBones) + { + if (r is SkinnedMeshRenderer) + { + Transform[] bone; + //check if I need to deoptimize + Animator anim = r.GetComponentInParent<Animator>(); + + if (anim != null) + { + if (anim.hasTransformHierarchy) + { + //nothing to do + } else if (anim.isOptimizable) + { + //Deoptimize + AnimatorUtility.DeoptimizeTransformHierarchy(anim.gameObject); + + } + else + { + Debug.LogError("Could not getBones. Bones optimized but could not create TransformHierarchy."); + return null; + } + bone = ((SkinnedMeshRenderer)r).bones; + //can't deoptimize here because the transforms need to exist for the combined mesh + } else + { + //no Animator component but check to see if bones were optimized on import + bone = ((SkinnedMeshRenderer)r).bones; +#if UNITY_EDITOR + if (bone.Length == 0) + { + Mesh m = ((SkinnedMeshRenderer)r).sharedMesh; + if (m.bindposes.Length != bone.Length) Debug.LogError("SkinnedMesh (" + r.gameObject + ") in the list of objects to combine has no bones. Check that 'optimize game object' is not checked in the 'Rig' tab of the asset importer. Mesh Baker cannot combine optimized skinned meshes because the bones are not available."); + } +#endif + } + return bone; + } + else if (r is MeshRenderer) + { + Transform[] bone = new Transform[1]; + bone[0] = r.transform; + return bone; + } + else { + Debug.LogError("Could not getBones. Object is not a Renderer."); + return null; + } + } +#else + public Transform[] GetBones(Renderer r, bool isSkinnedMeshWithBones) + { + if (isSkinnedMeshWithBones) + { + Transform[] bone = ((SkinnedMeshRenderer)r).bones; +#if UNITY_EDITOR + if (bone.Length == 0) + { + Mesh m = ((SkinnedMeshRenderer)r).sharedMesh; + if (m.bindposes.Length != bone.Length) Debug.LogError("SkinnedMesh (" + r.gameObject + ") in the list of objects to combine has no bones. Check that 'optimize game object' is not checked in the 'Rig' tab of the asset importer. Mesh Baker cannot combine optimized skinned meshes because the bones are not available."); + } +#endif + return bone; + } + else if (r is MeshRenderer || + (r is SkinnedMeshRenderer && !isSkinnedMeshWithBones)) + { + Transform[] bone = new Transform[1]; + bone[0] = r.transform; + return bone; + } + else + { + Debug.LogError("Could not getBones. Object does not have a renderer"); + return null; + } + } +#endif + + public int GetBlendShapeFrameCount(Mesh m, int shapeIndex) + { +#if UNITY_5_3_OR_NEWER + return m.GetBlendShapeFrameCount(shapeIndex); +#else + return 0; +#endif + } + + public float GetBlendShapeFrameWeight(Mesh m, int shapeIndex, int frameIndex) + { +#if UNITY_5_3_OR_NEWER + return m.GetBlendShapeFrameWeight(shapeIndex, frameIndex); +#else + return 0; +#endif + } + + public void GetBlendShapeFrameVertices(Mesh m, int shapeIndex, int frameIndex, Vector3[] vs, Vector3[] ns, Vector3[] ts) + { +#if UNITY_5_3_OR_NEWER + m.GetBlendShapeFrameVertices(shapeIndex, frameIndex, vs, ns, ts); +#endif + } + + public void ClearBlendShapes(Mesh m) + { +#if UNITY_5_3_OR_NEWER + m.ClearBlendShapes(); +#endif + } + + public void AddBlendShapeFrame(Mesh m, string nm, float wt, Vector3[] vs, Vector3[] ns, Vector3[] ts) + { +#if UNITY_5_3_OR_NEWER + m.AddBlendShapeFrame(nm, wt, vs, ns, ts); +#endif + } + + public int MaxMeshVertexCount() + { +#if UNITY_2017_3_OR_NEWER + return 2147483646; +#else + return 65535; +#endif + } + + public void SetMeshIndexFormatAndClearMesh(Mesh m, int numVerts, bool vertices, bool justClearTriangles) + { +#if UNITY_2017_3_OR_NEWER + if (vertices && numVerts > 65534 && m.indexFormat == UnityEngine.Rendering.IndexFormat.UInt16) + { + MBVersion.MeshClear(m, false); + m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; + return; + } + else if (vertices && numVerts <= 65534 && m.indexFormat == UnityEngine.Rendering.IndexFormat.UInt32) + { + MBVersion.MeshClear(m, false); + m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt16; + return; + } +#endif + if (justClearTriangles) + { + MBVersion.MeshClear(m, true); //clear just triangles + } + else + {//clear all the data and start with a blank mesh + MBVersion.MeshClear(m, false); + } + } + + public bool GraphicsUVStartsAtTop() + { +#if UNITY_2017_1_OR_NEWER + return SystemInfo.graphicsUVStartsAtTop; +#else + if (SystemInfo.graphicsDeviceVersion.Contains("metal")) + { + return false; + } + else + { + // "opengl es, direct3d" + return true; + } +#endif + } + + public bool IsTextureReadable(Texture2D tex) + { +#if UNITY_2018_3_OR_NEWER + return tex.isReadable; +#else + try + { + tex.GetPixel(0, 0); + return true; + } + catch + { + return false; + } +#endif + } + + public bool CollectPropertyNames(List<ShaderTextureProperty> texPropertyNames, ShaderTextureProperty[] shaderTexPropertyNames, List<ShaderTextureProperty> _customShaderPropNames, Material resultMaterial, MB2_LogLevel LOG_LEVEL) + { +#if UNITY_2019_3_OR_NEWER + // 2018.2 and up + // Collect the property names from the shader + // Check with the lists of property names to flag which ones are normal maps. + if (resultMaterial != null && resultMaterial.shader != null) + { + Shader s = resultMaterial.shader; + + for (int i = 0; i < s.GetPropertyCount(); i++) + { + if (s.GetPropertyType(i) == UnityEngine.Rendering.ShaderPropertyType.Texture) + { + string matPropName = s.GetPropertyName(i); + if (resultMaterial.GetTextureOffset(matPropName) != new Vector2(0f, 0f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material has non-zero offset for property " + matPropName + ". This is probably incorrect."); + } + + if (resultMaterial.GetTextureScale(matPropName) != new Vector2(1f, 1f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material should probably have tiling of 1,1 for propert " + matPropName); + } + + ShaderTextureProperty pn = null; + // We need to know if the property is a normal map or not. + // first check the list of default names + for (int defaultIdx = 0; defaultIdx < shaderTexPropertyNames.Length; defaultIdx++) + { + if (shaderTexPropertyNames[defaultIdx].name == matPropName) + { + pn = new ShaderTextureProperty(matPropName, shaderTexPropertyNames[defaultIdx].isNormalMap); + } + } + + // now check the list of custom property names + for (int custPropIdx = 0; custPropIdx < _customShaderPropNames.Count; custPropIdx++) + { + if (_customShaderPropNames[custPropIdx].name == matPropName) + { + pn = new ShaderTextureProperty(matPropName, _customShaderPropNames[custPropIdx].isNormalMap); + } + } + + if (pn == null) + { + pn = new ShaderTextureProperty(matPropName, false, true); + } + + texPropertyNames.Add(pn); + } + } + } + + return true; +#elif UNITY_2018_3_OR_NEWER + // 2018.2 and up + // Collect the property names from the material + // Check with the lists of property names to flag which ones are normal maps. + string[] matPropertyNames = resultMaterial.GetTexturePropertyNames(); + for (int matIdx = 0; matIdx < matPropertyNames.Length; matIdx++) + { + string matPropName = matPropertyNames[matIdx]; + if (resultMaterial.GetTextureOffset(matPropName) != new Vector2(0f, 0f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material has non-zero offset for property " + matPropName + ". This is probably incorrect."); + } + + if (resultMaterial.GetTextureScale(matPropName) != new Vector2(1f, 1f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material should probably have tiling of 1,1 for propert " + matPropName); + } + + ShaderTextureProperty pn = null; + // We need to know if the property is a normal map or not. + // first check the list of default names + for (int defaultIdx = 0; defaultIdx < shaderTexPropertyNames.Length; defaultIdx++) + { + if (shaderTexPropertyNames[defaultIdx].name == matPropName) + { + pn = new ShaderTextureProperty(matPropName, shaderTexPropertyNames[defaultIdx].isNormalMap); + } + } + + // now check the list of custom property names + for (int custPropIdx = 0; custPropIdx < _customShaderPropNames.Count; custPropIdx++) + { + if (_customShaderPropNames[custPropIdx].name == matPropName) + { + pn = new ShaderTextureProperty(matPropName, _customShaderPropNames[custPropIdx].isNormalMap); + } + } + + if (pn == null) + { + pn = new ShaderTextureProperty(matPropName, false, true); + } + + texPropertyNames.Add(pn); + } + + return true; +#else + { // Pre 2018.2, doesn't have API for querying material for property names. + //Collect the property names for the textures + string shaderPropStr = ""; + for (int i = 0; i < shaderTexPropertyNames.Length; i++) + { + if (resultMaterial.HasProperty(shaderTexPropertyNames[i].name)) + { + shaderPropStr += ", " + shaderTexPropertyNames[i].name; + if (!texPropertyNames.Contains(shaderTexPropertyNames[i])) texPropertyNames.Add(shaderTexPropertyNames[i]); + if (resultMaterial.GetTextureOffset(shaderTexPropertyNames[i].name) != new Vector2(0f, 0f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material has non-zero offset. This is may be incorrect."); + } + if (resultMaterial.GetTextureScale(shaderTexPropertyNames[i].name) != new Vector2(1f, 1f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material should have tiling of 1,1"); + } + } + } + + for (int i = 0; i < _customShaderPropNames.Count; i++) + { + if (resultMaterial.HasProperty(_customShaderPropNames[i].name)) + { + shaderPropStr += ", " + _customShaderPropNames[i].name; + texPropertyNames.Add(_customShaderPropNames[i]); + if (resultMaterial.GetTextureOffset(_customShaderPropNames[i].name) != new Vector2(0f, 0f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material has non-zero offset. This is probably incorrect."); + } + if (resultMaterial.GetTextureScale(_customShaderPropNames[i].name) != new Vector2(1f, 1f)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material should probably have tiling of 1,1."); + } + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Result material shader does not use property " + _customShaderPropNames[i].name + " in the list of custom shader property names"); + } + } + } + return true; +#endif + } + + public void DoSpecialRenderPipeline_TexturePackerFastSetup(GameObject cameraGameObject) + { + MBVersion.PipelineType pipelineType = DetectPipeline(); +#if MB_USING_HDRP && UNITY_2018_4_OR_NEWER + if (pipelineType != MBVersion.PipelineType.HDRP) + { + Debug.LogError("The 'PlayerSettings -> Other Settings -> Scripting Define Symbols' included the symbol 'MB_USING_HDRP' which should only be used " + + "if the GraphicsSettings -> Render Pipeline Asset is HDRenderPipelineAsset. The Render Pipeline Asset is NOT HDRenderPipelineAsset. Please " + + " remove the symbol MB_USING_HDRP from PlayerSettings -> Other Settings -> Scripting Define Symbols"); + } + + HDAdditionalCameraData acd = cameraGameObject.GetComponent<HDAdditionalCameraData>(); + if (acd == null) + { + acd = cameraGameObject.AddComponent<HDAdditionalCameraData>(); + } + + acd.volumeLayerMask = LayerMask.GetMask("UI"); +#endif + } + + public ColorSpace GetProjectColorSpace() + { +#if UNITY_EDITOR + if (Application.isEditor) + { + return UnityEditor.PlayerSettings.colorSpace; + } +#endif + if (QualitySettings.desiredColorSpace != QualitySettings.activeColorSpace) + { + Debug.LogError("The active color space (" + QualitySettings.activeColorSpace + ") is not the desired color space (" + QualitySettings.desiredColorSpace + "). Baked atlases may be off."); + } + + return QualitySettings.activeColorSpace; + } + + public MBVersion.PipelineType DetectPipeline() + { +#if UNITY_2019_1_OR_NEWER + if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset != null) + { + // SRP + var srpType = UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset.GetType().ToString(); + if (srpType.Contains("HDRenderPipelineAsset")) + { + return MBVersion.PipelineType.HDRP; + } + else if (srpType.Contains("UniversalRenderPipelineAsset") || srpType.Contains("LightweightRenderPipelineAsset")) + { + return MBVersion.PipelineType.URP; + } + else return MBVersion.PipelineType.Unsupported; + } +#elif UNITY_2017_1_OR_NEWER + if (UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset != null) { + // SRP not supported before 2019 + return MBVersion.PipelineType.Unsupported; + } +#endif + // no SRP + return MBVersion.PipelineType.Default; + } + + public string UnescapeURL(string url) + { +#if UNITY_2017_3_OR_NEWER + return UnityEngine.Networking.UnityWebRequest.UnEscapeURL(url); +#else + // Not correct but not going to spend a lot of effort writing custom + // code for versions of Unity soon to be depricated. + return url; +#endif + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MBVersionConcrete.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MBVersionConcrete.cs.meta new file mode 100644 index 00000000..d74cd474 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MBVersionConcrete.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f1719ea9a4347c4799756ac016a69e3 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBaker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBaker.cs new file mode 100644 index 00000000..2c4e1295 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBaker.cs @@ -0,0 +1,56 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Text; +using DigitalOpus.MB.Core; +using System.Text.RegularExpressions; + +/// <summary> +/// Component that manages a single combined mesh. +/// +/// This class is a Component. It must be added to a GameObject to use it. It is a wrapper for MB2_MeshCombiner which contains the same functionality but is not a component +/// so it can be instantiated like a normal class. +/// </summary> +public class MB3_MeshBaker : MB3_MeshBakerCommon { + + [SerializeField] protected MB3_MeshCombinerSingle _meshCombiner = new MB3_MeshCombinerSingle(); + + public override MB3_MeshCombiner meshCombiner{ + get{return _meshCombiner;} + } + + public void BuildSceneMeshObject(){ + _meshCombiner.BuildSceneMeshObject(); + } + + public virtual bool ShowHide(GameObject[] gos, GameObject[] deleteGOs){ + return _meshCombiner.ShowHideGameObjects(gos, deleteGOs); + } + + public virtual void ApplyShowHide(){ + _meshCombiner.ApplyShowHide(); + } + + public override bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource){ +// if ((_meshCombiner.outputOption == MB2_OutputOptions.bakeIntoSceneObject || (_meshCombiner.outputOption == MB2_OutputOptions.bakeIntoPrefab && _meshCombiner.renderType == MB_RenderType.skinnedMeshRenderer) )) BuildSceneMeshObject(); + _meshCombiner.name = name + "-mesh"; + return _meshCombiner.AddDeleteGameObjects(gos,deleteGOs,disableRendererInSource); + } + + public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource){ +// if ((_meshCombiner.outputOption == MB2_OutputOptions.bakeIntoSceneObject || (_meshCombiner.outputOption == MB2_OutputOptions.bakeIntoPrefab && _meshCombiner.renderType == MB_RenderType.skinnedMeshRenderer) )) BuildSceneMeshObject(); + _meshCombiner.name = name + "-mesh"; + return _meshCombiner.AddDeleteGameObjectsByID(gos,deleteGOinstanceIDs,disableRendererInSource); + } + + public void OnDestroy() + { + _meshCombiner.DisposeRuntimeCreated(); + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBaker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBaker.cs.meta new file mode 100644 index 00000000..8db993d4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBaker.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2fb2e0d8bc810bb48b63c91af80d5f17 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerCommon.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerCommon.cs new file mode 100644 index 00000000..3ee728f4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerCommon.cs @@ -0,0 +1,380 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Text; +using DigitalOpus.MB.Core; + + +/// <summary> +/// Maps a list of source materials to a combined material. Included in MB2_TextureBakeResults +/// </summary> + + +/// <summary> +/// Abstract root of the mesh combining classes +/// </summary> +public abstract class MB3_MeshBakerCommon : MB3_MeshBakerRoot { + + //todo should be list of <Renderer> + public List<GameObject> objsToMesh; + + public abstract MB3_MeshCombiner meshCombiner { + get; + } + + public bool useObjsToMeshFromTexBaker = true; + + public bool clearBuffersAfterBake = true; + + //t0do put this in the batch baker + public string bakeAssetsInPlaceFolderPath; + + [HideInInspector] public GameObject resultPrefab; + + /// <summary> + /// If checked then an instance will be left in the scene after baking. Otherwise scene instance will be deleted after prefab is baked. + /// </summary> + [HideInInspector] public bool resultPrefabLeaveInstanceInSceneAfterBake; + + /// <summary> + /// Optional, combined mesh renderers will be children of this object if it exists. + /// </summary> + [HideInInspector] public Transform parentSceneObject; + +#if UNITY_EDITOR + [ContextMenu("Create Mesh Baker Settings Asset")] + public void CreateMeshBakerSettingsAsset() + { + + string newFilePath = UnityEditor.EditorUtility.SaveFilePanelInProject("New Mesh Baker Settings", "MeshBakerSettings", "asset", "Create a new Mesh Baker Settings Asset"); + if (newFilePath != null) + { + MB3_MeshCombinerSettings asset = ScriptableObject.CreateInstance<MB3_MeshCombinerSettings>(); + UnityEditor.AssetDatabase.CreateAsset(asset, newFilePath); + } + } + + [ContextMenu("Copy settings from Shared Settings")] + public void CopyMySettingsToAssignedSettingsAsset() + { + if (meshCombiner.settingsHolder == null) + { + Debug.LogError("No Shared Settings Asset Assigned."); + return; + } + + UnityEditor.Undo.RecordObject(this, "Undo copy settings"); + _CopySettings(meshCombiner.settingsHolder.GetMeshBakerSettings(), meshCombiner); + Debug.Log("Copied settings from assigned Shared Settings to this Mesh Baker."); + UnityEditor.EditorUtility.SetDirty(this); + } + + [ContextMenu("Copy settings to Shared Settings")] + public void CopyAssignedSettingsAssetToMySettings() + { + if (meshCombiner.settingsHolder == null) + { + Debug.LogError("No Shared Settings Asset Assigned."); + return; + } + + if (meshCombiner.settingsHolder is UnityEngine.Object) UnityEditor.Undo.RecordObject((UnityEngine.Object)meshCombiner.settingsHolder, "Undo copy settings"); + _CopySettings(meshCombiner, meshCombiner.settingsHolder.GetMeshBakerSettings()); + Debug.Log("Copied settings from this Mesh Baker to the assigned Shared Settings asset."); + if (meshCombiner.settingsHolder is UnityEngine.Object) UnityEditor.EditorUtility.SetDirty((UnityEngine.Object)meshCombiner.settingsHolder); + } + + void _CopySettings(MB_IMeshBakerSettings src, MB_IMeshBakerSettings targ) + { + targ.clearBuffersAfterBake = src.clearBuffersAfterBake; + targ.doBlendShapes = src.doBlendShapes; + targ.doCol = src.doCol; + targ.doNorm = src.doNorm; + targ.doTan = src.doTan; + targ.doUV = src.doUV; + targ.doUV3 = src.doUV3; + targ.doUV4 = src.doUV4; + targ.doUV5 = src.doUV5; + targ.doUV6 = src.doUV6; + targ.doUV7 = src.doUV7; + targ.doUV8 = src.doUV8; + targ.optimizeAfterBake = src.optimizeAfterBake; + targ.pivotLocationType = src.pivotLocationType; + targ.lightmapOption = src.lightmapOption; + targ.renderType = src.renderType; + targ.uv2UnwrappingParamsHardAngle = src.uv2UnwrappingParamsHardAngle; + targ.uv2UnwrappingParamsPackMargin = src.uv2UnwrappingParamsPackMargin; + } +#endif + + public override MB2_TextureBakeResults textureBakeResults { + get { return meshCombiner.textureBakeResults; } + set { meshCombiner.textureBakeResults = value; } + } + + public override List<GameObject> GetObjectsToCombine() { + if (useObjsToMeshFromTexBaker) { + MB3_TextureBaker tb = gameObject.GetComponent<MB3_TextureBaker>(); + if (tb == null) tb = gameObject.transform.parent.GetComponent<MB3_TextureBaker>(); + if (tb != null) { + return tb.GetObjectsToCombine(); + } else { + Debug.LogWarning("Use Objects To Mesh From Texture Baker was checked but no texture baker"); + return new List<GameObject>(); + } + } else { + if (objsToMesh == null) objsToMesh = new List<GameObject>(); + return objsToMesh; + } + } + + public void EnableDisableSourceObjectRenderers(bool show) { + for (int i = 0; i < GetObjectsToCombine().Count; i++) { + GameObject go = GetObjectsToCombine()[i]; + if (go != null) { + Renderer mr = MB_Utility.GetRenderer(go); + if (mr != null) { + mr.enabled = show; + } + + LODGroup lodG = mr.GetComponentInParent<LODGroup>(); + if (lodG != null) + { + bool isOnlyInGroup = true; + LOD[] lods = lodG.GetLODs(); + for (int j = 0; j < lods.Length; j++) + { + for (int k = 0; k < lods[j].renderers.Length; k++) + { + if (lods[j].renderers[k] != mr) + { + isOnlyInGroup = false; + break; + } + } + } + + if (isOnlyInGroup) + { + lodG.enabled = show; + } + } + } + } + } + + /// <summary> + /// Clears the meshs and mesh related data but does not destroy it. + /// </summary> + public virtual void ClearMesh() + { + meshCombiner.ClearMesh(); + } + + public virtual void ClearMesh(MB2_EditorMethodsInterface editorMethods) + { + meshCombiner.ClearMesh(editorMethods); + } + + /// <summary> + /// Clears and desroys the mesh. Clears mesh related data. + /// </summary> + public virtual void DestroyMesh(){ + meshCombiner.DestroyMesh(); + } + + public virtual void DestroyMeshEditor(MB2_EditorMethodsInterface editorMethods){ + meshCombiner.DestroyMeshEditor(editorMethods); + } + + public virtual int GetNumObjectsInCombined(){ + return meshCombiner.GetNumObjectsInCombined(); + } + + public virtual int GetNumVerticesFor(GameObject go){ + return meshCombiner.GetNumVerticesFor(go); + } + + /// <summary> + /// Gets the texture baker on this component or its parent if it exists + /// </summary> + /// <returns>The texture baker.</returns> + public MB3_TextureBaker GetTextureBaker(){ + MB3_TextureBaker tb = GetComponent<MB3_TextureBaker>(); + if (tb != null) return tb; + if (transform.parent != null) return transform.parent.GetComponent<MB3_TextureBaker>(); + return null; + } + +/// <summary> +/// Adds and deletes objects from the combined mesh. gos and deleteGOs can be null. +/// You need to call Apply or ApplyAll to see the changes. +/// objects in gos must not include objects already in the combined mesh. +/// objects in gos and deleteGOs must be the game objects with a Renderer component +/// This method is slow, so should be called as infrequently as possible. +/// </summary> +/// <returns> +/// The first generated combined mesh +/// </returns> +/// <param name='gos'> +/// gos. Array of objects to add to the combined mesh. Array can be null. Must not include objects +/// already in the combined mesh. Array must contain game objects with a render component. +/// </param> +/// <param name='deleteGOs'> +/// deleteGOs. Array of objects to delete from the combined mesh. Array can be null. +/// </param> +/// <param name='disableRendererInSource'> +/// Disable renderer component on objects in gos after they have been added to the combined mesh. +/// </param> +/// <param name='fixOutOfBoundUVs'> +/// Whether to fix out of bounds UVs in meshes as they are being added. This paramater should be set to the same as the combined material. +/// </param> +/// </summary> + public abstract bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource = true); + + /// <summary> + /// This is the best version to use for deleting game objects since the source GameObjects may have been destroyed + /// Internaly Mesh Baker only stores the instanceID for Game Objects, so objects can be removed after they have been destroyed + /// </summary> + public abstract bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true); + +/// <summary> +/// Apply changes to the mesh. All channels set in this instance will be set in the combined mesh. +/// </summary> + public virtual void Apply(MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod=null){ + meshCombiner.name = name + "-mesh"; + meshCombiner.Apply(uv2GenerationMethod); + } + +/// <summary> +/// Applys the changes to flagged properties of the mesh. This method is slow, and should only be called once per frame. The speed is directly proportional to the number of flags that are true. Only apply necessary properties. +/// </summary> + public virtual void Apply(bool triangles, + bool vertices, + bool normals, + bool tangents, + bool uvs, + bool uv2, + bool uv3, + bool uv4, + bool colors, + bool bones=false, + bool blendShapesFlag=false, + MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod=null){ + meshCombiner.name = name + "-mesh"; + meshCombiner.Apply(triangles,vertices,normals,tangents,uvs,uv2,uv3,uv4,colors,bones, blendShapesFlag,uv2GenerationMethod); + } + + public virtual bool CombinedMeshContains(GameObject go){ + return meshCombiner.CombinedMeshContains(go); + } + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public virtual void UpdateGameObjects(GameObject[] gos) + { + meshCombiner.name = name + "-mesh"; + meshCombiner.UpdateGameObjects(gos, true, true, true, true, true, + false,false,false,false,false,false,false,false,false); + } + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public virtual void UpdateGameObjects(GameObject[] gos, bool updateBounds) + { + meshCombiner.name = name + "-mesh"; + meshCombiner.UpdateGameObjects(gos, true, true, true, true, true, + false, false, false, false, false, false, false, false, false); + } + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public virtual void UpdateGameObjects(GameObject[] gos, bool recalcBounds, bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV1, bool updateUV2, + bool updateColors, bool updateSkinningInfo){ + meshCombiner.name = name + "-mesh"; + meshCombiner.UpdateGameObjects(gos,recalcBounds, updateVertices, updateNormals, updateTangents, updateUV, updateUV2, false, false, updateColors, updateSkinningInfo); + } + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public virtual bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8, + bool updateColors, bool updateSkinningInfo) + { + meshCombiner.name = name + "-mesh"; + return meshCombiner.UpdateGameObjects(gos, recalcBounds, updateVertices, updateNormals, updateTangents, updateUV, updateUV2, updateUV3, updateUV4, updateUV5, updateUV6, updateUV7, updateUV8, updateColors, updateSkinningInfo); + } + + + public virtual void UpdateSkinnedMeshApproximateBounds(){ + if (_ValidateForUpdateSkinnedMeshBounds()){ + meshCombiner.UpdateSkinnedMeshApproximateBounds(); + } + } + + public virtual void UpdateSkinnedMeshApproximateBoundsFromBones(){ + if (_ValidateForUpdateSkinnedMeshBounds()){ + meshCombiner.UpdateSkinnedMeshApproximateBoundsFromBones(); + } + } + + public virtual void UpdateSkinnedMeshApproximateBoundsFromBounds(){ + if (_ValidateForUpdateSkinnedMeshBounds()){ + meshCombiner.UpdateSkinnedMeshApproximateBoundsFromBounds(); + } + } + + protected virtual bool _ValidateForUpdateSkinnedMeshBounds(){ + if (meshCombiner.outputOption == MB2_OutputOptions.bakeMeshAssetsInPlace){ + Debug.LogWarning("Can't UpdateSkinnedMeshApproximateBounds when output type is bakeMeshAssetsInPlace"); + return false; + } + if (meshCombiner.resultSceneObject == null){ + Debug.LogWarning("Result Scene Object does not exist. No point in calling UpdateSkinnedMeshApproximateBounds."); + return false; + } + SkinnedMeshRenderer smr = meshCombiner.resultSceneObject.GetComponentInChildren<SkinnedMeshRenderer>(); + if (smr == null){ + Debug.LogWarning("No SkinnedMeshRenderer on result scene object."); + return false; + } + return true; + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerCommon.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerCommon.cs.meta new file mode 100644 index 00000000..0b8ef5d8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerCommon.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bb955c20e5d3cce44b756cc24d501cb7 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerGrouper.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerGrouper.cs new file mode 100644 index 00000000..040a3941 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerGrouper.cs @@ -0,0 +1,839 @@ +using UnityEngine; +using System; +using System.Collections; +using System.Collections.Generic; +using DigitalOpus.MB.Core; + +#if UNITY_EDITOR + using UnityEditor; +#endif + +public class MB3_MeshBakerGrouper : MonoBehaviour, MB_IMeshBakerSettingsHolder +{ + public enum ClusterType + { + none, + grid, + pie, + agglomerative, + } + + public static readonly Color WHITE_TRANSP = new Color(1f,1f,1f,.1f); + + public MB3_MeshBakerGrouperCore grouper; + public ClusterType clusterType = ClusterType.none; + + /// <summary> + /// Baked meshes will be added as a child of this scene object. + /// </summary> + public Transform parentSceneObject; + public GrouperData data = new GrouperData(); + + //these are for getting a resonable bounds in which to draw gizmos. + [HideInInspector] public Bounds sourceObjectBounds = new Bounds(Vector3.zero, Vector3.one); + + public string prefabOptions_outputFolder = ""; + public bool prefabOptions_autoGeneratePrefabs; + public bool prefabOptions_mergeOutputIntoSinglePrefab; + + public MB3_MeshCombinerSettings meshBakerSettingsAsset; + public MB3_MeshCombinerSettingsData meshBakerSettings; + + public MB_IMeshBakerSettings GetMeshBakerSettings() + { + if (meshBakerSettingsAsset == null) + { + if (meshBakerSettings == null) meshBakerSettings = new MB3_MeshCombinerSettingsData(); + return meshBakerSettings; + } + else + { + return meshBakerSettingsAsset.GetMeshBakerSettings(); + } + } + + public void GetMeshBakerSettingsAsSerializedProperty(out string propertyName, out UnityEngine.Object targetObj) + { + if (meshBakerSettingsAsset == null) + { + targetObj = this; + propertyName = "meshBakerSettings"; + } + else + { + targetObj = meshBakerSettingsAsset; + propertyName = "data"; + } + } + + + void OnDrawGizmosSelected() + { + if (grouper == null) + { + grouper = CreateGrouper(clusterType, data); + } + if (grouper.d == null) + { + grouper.d = data; + } + grouper.DrawGizmos(sourceObjectBounds); + } + + public MB3_MeshBakerGrouperCore CreateGrouper(ClusterType t, GrouperData data) + { + if (t == ClusterType.grid) grouper = new MB3_MeshBakerGrouperGrid(data); + if (t == ClusterType.pie) grouper = new MB3_MeshBakerGrouperPie(data); + if (t == ClusterType.agglomerative) + { + MB3_TextureBaker tb = GetComponent<MB3_TextureBaker>(); + List<GameObject> gos; + if (tb != null) + { + gos = tb.GetObjectsToCombine(); + } + else + { + gos = new List<GameObject>(); + } + grouper = new MB3_MeshBakerGrouperCluster(data, gos); + } + if (t == ClusterType.none) grouper = new MB3_MeshBakerGrouperNone(data); + return grouper; + } + + public void DeleteAllChildMeshBakers() + { + MB3_MeshBakerCommon[] mBakers = GetComponentsInChildren<MB3_MeshBakerCommon>(); + for (int i = 0; i < mBakers.Length; i++) + { + MB3_MeshBakerCommon mb = mBakers[i]; + GameObject resultGameObject = mb.meshCombiner.resultSceneObject; + MB_Utility.Destroy(resultGameObject); + MB_Utility.Destroy(mb.gameObject); + } + } +} + +namespace DigitalOpus.MB.Core +{ + /// all properties go here so that settings are remembered as user switches between cluster types + [Serializable] + public class GrouperData + { + public bool clusterOnLMIndex; + public bool clusterByLODLevel; + public Vector3 origin; + + //Normally these properties would be in the subclasses but putting them here makes writing the inspector much easier + //for grid + public Vector3 cellSize; + + //for pie + public int pieNumSegments = 4; + public Vector3 pieAxis = Vector3.up; + public float ringSpacing = 100f; + public bool combineSegmentsInInnermostRing = false; + + //for clustering + public int height = 1; + public float maxDistBetweenClusters = 1f; + public bool includeCellsWithOnlyOneRenderer = true; + } + + [Serializable] + public abstract class MB3_MeshBakerGrouperCore + { + + public GrouperData d; + public abstract Dictionary<string, List<Renderer>> FilterIntoGroups(List<GameObject> selection); + public abstract void DrawGizmos(Bounds sourceObjectBounds); + public List<MB3_MeshBakerCommon> DoClustering(MB3_TextureBaker tb, MB3_MeshBakerGrouper grouper) + { + List<MB3_MeshBakerCommon> outBakers = new List<MB3_MeshBakerCommon>(); + if (grouper.prefabOptions_autoGeneratePrefabs || grouper.prefabOptions_mergeOutputIntoSinglePrefab) + { + if (Application.isPlaying) + { + Debug.LogError("Cannot generate prefabs while playing. Prefabs can only be generated in the editor and not in play mode."); + return outBakers; + } + } + + //todo warn for no objects and no Texture Bake Result + Dictionary<string, List<Renderer>> cell2objs = FilterIntoGroups(tb.GetObjectsToCombine()); + + if (d.clusterOnLMIndex) + { + Dictionary<string, List<Renderer>> cell2objsNew = new Dictionary<string, List<Renderer>>(); + foreach (string key in cell2objs.Keys) + { + List<Renderer> gaws = cell2objs[key]; + Dictionary<int, List<Renderer>> idx2objs = GroupByLightmapIndex(gaws); + foreach (int keyIdx in idx2objs.Keys) + { + string keyNew = key + "-LM-" + keyIdx; + cell2objsNew.Add(keyNew, idx2objs[keyIdx]); + } + } + cell2objs = cell2objsNew; + } + if (d.clusterByLODLevel) + { + //visit each cell + //visit each renderer + //check if that renderer is a child of an LOD group + // visit each LOD level check if this renderer is in that list. + // if not add it to LOD0 for that cell + // otherwise add it to LODX for that cell creating LODs as necessary + Dictionary<string, List<Renderer>> cell2objsNew = new Dictionary<string, List<Renderer>>(); + foreach (string key in cell2objs.Keys) + { + List<Renderer> gaws = cell2objs[key]; + foreach (Renderer r in gaws) + { + if (r == null) continue; + bool foundInLOD = false; + LODGroup lodg = r.GetComponentInParent<LODGroup>(); + if (lodg != null) + { + LOD[] lods = lodg.GetLODs(); + for (int i = 0; i < lods.Length; i++) + { + LOD lod = lods[i]; + if (Array.Find<Renderer>(lod.renderers, x => x == r) != null) + { + foundInLOD = true; + List<Renderer> rs; + string newKey = String.Format("{0}_LOD{1}", key, i); + if (!cell2objsNew.TryGetValue(newKey, out rs)) + { + rs = new List<Renderer>(); + cell2objsNew.Add(newKey, rs); + } + if (!rs.Contains(r)) rs.Add(r); + } + } + } + if (!foundInLOD) + { + List<Renderer> rs; + string newKey = String.Format("{0}_LOD0", key); + if (!cell2objsNew.TryGetValue(newKey, out rs)) + { + rs = new List<Renderer>(); + cell2objsNew.Add(newKey, rs); + } + if (!rs.Contains(r)) rs.Add(r); + } + } + } + cell2objs = cell2objsNew; + } + + int clustersWithOnlyOneRenderer = 0; + foreach (string key in cell2objs.Keys) + { + List<Renderer> gaws = cell2objs[key]; + if (gaws.Count > 1 || grouper.data.includeCellsWithOnlyOneRenderer) + { + outBakers.Add(AddMeshBaker(grouper, tb, key, gaws)); + } + else + { + clustersWithOnlyOneRenderer++; + } + } + + Debug.Log(String.Format("Found {0} cells with Renderers. Not creating bakers for {1} because there is only one mesh in the cell. Creating {2} bakers.", cell2objs.Count, clustersWithOnlyOneRenderer, cell2objs.Count - clustersWithOnlyOneRenderer)); + return outBakers; + } + + Dictionary<int, List<Renderer>> GroupByLightmapIndex(List<Renderer> gaws) + { + Dictionary<int, List<Renderer>> idx2objs = new Dictionary<int, List<Renderer>>(); + for (int i = 0; i < gaws.Count; i++) + { + List<Renderer> objs = null; + if (idx2objs.ContainsKey(gaws[i].lightmapIndex)) + { + objs = idx2objs[gaws[i].lightmapIndex]; + } + else + { + objs = new List<Renderer>(); + idx2objs.Add(gaws[i].lightmapIndex, objs); + } + objs.Add(gaws[i]); + } + return idx2objs; + } + + MB3_MeshBakerCommon AddMeshBaker(MB3_MeshBakerGrouper grouper, MB3_TextureBaker tb, string key, List<Renderer> gaws) + { + int numVerts = 0; + for (int i = 0; i < gaws.Count; i++) + { + Mesh m = MB_Utility.GetMesh(gaws[i].gameObject); + if (m != null) + numVerts += m.vertexCount; + } + + GameObject nmb = new GameObject("MeshBaker-" + key); + nmb.transform.position = Vector3.zero; + MB3_MeshBakerCommon newMeshBaker; + if (numVerts >= 65535) + { + newMeshBaker = nmb.AddComponent<MB3_MultiMeshBaker>(); + newMeshBaker.useObjsToMeshFromTexBaker = false; + } + else + { + newMeshBaker = nmb.AddComponent<MB3_MeshBaker>(); + newMeshBaker.useObjsToMeshFromTexBaker = false; + } + + newMeshBaker.textureBakeResults = tb.textureBakeResults; + newMeshBaker.transform.parent = tb.transform; + newMeshBaker.meshCombiner.settingsHolder = grouper; + for (int i = 0; i < gaws.Count; i++) + { + newMeshBaker.GetObjectsToCombine().Add(gaws[i].gameObject); + } + + return newMeshBaker; + } + } + + [Serializable] + public class MB3_MeshBakerGrouperNone : MB3_MeshBakerGrouperCore + { + public MB3_MeshBakerGrouperNone(GrouperData d) + { + this.d = d; + } + + public override Dictionary<string, List<Renderer>> FilterIntoGroups(List<GameObject> selection) + { + Debug.Log("Filtering into groups none"); + + Dictionary<string, List<Renderer>> cell2objs = new Dictionary<string, List<Renderer>>(); + + List<Renderer> rs = new List<Renderer>(); + for (int i = 0; i < selection.Count; i++) + { + if (selection[i] != null) + { + rs.Add(selection[i].GetComponent<Renderer>()); + } + } + + cell2objs.Add("MeshBaker", rs); + return cell2objs; + } + + public override void DrawGizmos(Bounds sourceObjectBounds) + { + + } + } + + [Serializable] + public class MB3_MeshBakerGrouperGrid : MB3_MeshBakerGrouperCore + { + public MB3_MeshBakerGrouperGrid(GrouperData d) + { + this.d = d; + } + + public override Dictionary<string, List<Renderer>> FilterIntoGroups(List<GameObject> selection) + { + Dictionary<string, List<Renderer>> cell2objs = new Dictionary<string, List<Renderer>>(); + if (d.cellSize.x <= 0f || d.cellSize.y <= 0f || d.cellSize.z <= 0f) + { + Debug.LogError("cellSize x,y,z must all be greater than zero."); + return cell2objs; + } + + Debug.Log("Collecting renderers in each cell"); + foreach (GameObject t in selection) + { + if (t == null) + { + continue; + } + + GameObject go = t; + Renderer mr = go.GetComponent<Renderer>(); + if (mr is MeshRenderer || mr is SkinnedMeshRenderer) + { + //get the cell this gameObject is in + Vector3 gridVector = mr.bounds.center; + gridVector.x = Mathf.Floor((gridVector.x - d.origin.x) / d.cellSize.x) * d.cellSize.x; + gridVector.y = Mathf.Floor((gridVector.y - d.origin.y) / d.cellSize.y) * d.cellSize.y; + gridVector.z = Mathf.Floor((gridVector.z - d.origin.z) / d.cellSize.z) * d.cellSize.z; + List<Renderer> objs = null; + string gridVectorStr = gridVector.ToString(); + if (cell2objs.ContainsKey(gridVectorStr)) + { + objs = cell2objs[gridVectorStr]; + } + else + { + objs = new List<Renderer>(); + cell2objs.Add(gridVectorStr, objs); + } + + if (!objs.Contains(mr)) + { + //Debug.Log("Adding " + mr + " todo " + gridVectorStr); + objs.Add(mr); + } + } + } + return cell2objs; + } + + public override void DrawGizmos(Bounds sourceObjectBounds) + { + Vector3 cs = d.cellSize; + if (cs.x <= .00001f || cs.y <= .00001f || cs.z <= .00001f) return; + Gizmos.color = MB3_MeshBakerGrouper.WHITE_TRANSP; + Vector3 p = sourceObjectBounds.center - sourceObjectBounds.extents; + Vector3 offset = d.origin; + offset.x = offset.x % cs.x; + offset.y = offset.y % cs.y; + offset.z = offset.z % cs.z; + //snap p to closest cell center + Vector3 start; + p.x = Mathf.Round((p.x) / cs.x) * cs.x + offset.x; + p.y = Mathf.Round((p.y) / cs.y) * cs.y + offset.y; + p.z = Mathf.Round((p.z) / cs.z) * cs.z + offset.z; + if (p.x > sourceObjectBounds.center.x - sourceObjectBounds.extents.x) p.x = p.x - cs.x; + if (p.y > sourceObjectBounds.center.y - sourceObjectBounds.extents.y) p.y = p.y - cs.y; + if (p.z > sourceObjectBounds.center.z - sourceObjectBounds.extents.z) p.z = p.z - cs.z; + start = p; + int numcells = Mathf.CeilToInt(sourceObjectBounds.size.x / cs.x + sourceObjectBounds.size.y / cs.y + sourceObjectBounds.size.z / cs.z); + if (numcells > 200) + { + Gizmos.DrawWireCube(d.origin + cs / 2f, cs); + } + else + { + for (; p.x < sourceObjectBounds.center.x + sourceObjectBounds.extents.x; p.x += cs.x) + { + p.y = start.y; + for (; p.y < sourceObjectBounds.center.y + sourceObjectBounds.extents.y; p.y += cs.y) + { + p.z = start.z; + for (; p.z < sourceObjectBounds.center.z + sourceObjectBounds.extents.z; p.z += cs.z) + { + Gizmos.DrawWireCube(p + cs / 2f, cs); + } + } + } + } + } + } + + [Serializable] + public class MB3_MeshBakerGrouperPie : MB3_MeshBakerGrouperCore + { + public MB3_MeshBakerGrouperPie(GrouperData data) + { + d = data; + } + + public override Dictionary<string, List<Renderer>> FilterIntoGroups(List<GameObject> selection) + { + Dictionary<string, List<Renderer>> cell2objs = new Dictionary<string, List<Renderer>>(); + if (d.pieNumSegments == 0) + { + Debug.LogError("pieNumSegments must be greater than zero."); + return cell2objs; + } + + if (d.pieAxis.magnitude <= .000001f) + { + Debug.LogError("Pie axis vector is too short."); + return cell2objs; + } + + if (d.ringSpacing <= .000001f) + { + Debug.LogError("Ring spacing is too small."); + return cell2objs; + } + + d.pieAxis.Normalize(); + Quaternion pieAxis2yIsUp = Quaternion.FromToRotation(d.pieAxis, Vector3.up); + + Debug.Log("Collecting renderers in each cell"); + foreach (GameObject t in selection) + { + if (t == null) + { + continue; + } + + GameObject go = t; + Renderer mr = go.GetComponent<Renderer>(); + if (mr is MeshRenderer || mr is SkinnedMeshRenderer) + { + //get the cell this gameObject is in + Vector3 origin2obj = mr.bounds.center - d.origin; + origin2obj = pieAxis2yIsUp * origin2obj; + Vector2 origin2Obj2D = new Vector2(origin2obj.x, origin2obj.z); + float radius = origin2Obj2D.magnitude; + origin2obj.Normalize(); + + float deg_aboutY = 0f; + if (Mathf.Abs(origin2obj.x) < 10e-5f && Mathf.Abs(origin2obj.z) < 10e-5f) + { + deg_aboutY = 0f; + } + else + { + deg_aboutY = Mathf.Atan2(origin2obj.x, origin2obj.z) * Mathf.Rad2Deg; + if (deg_aboutY < 0f) deg_aboutY = 360f + deg_aboutY; + } + + // Debug.Log ("Obj " + mr + " angle " + d_aboutY); + int segment = Mathf.FloorToInt(deg_aboutY / 360f * d.pieNumSegments); + int ring = Mathf.FloorToInt(radius / d.ringSpacing); + if (ring == 0 && d.combineSegmentsInInnermostRing) + { + segment = 0; + } + + List<Renderer> objs = null; + string segStr = "seg_" + segment + "_ring_" + ring; + if (cell2objs.ContainsKey(segStr)) + { + objs = cell2objs[segStr]; + } + else + { + objs = new List<Renderer>(); + cell2objs.Add(segStr, objs); + } + + if (!objs.Contains(mr)) + { + objs.Add(mr); + } + } + } + + return cell2objs; + } + + public override void DrawGizmos(Bounds sourceObjectBounds) + { + + if (d.pieAxis.magnitude < .1f) return; + if (d.pieNumSegments < 1) return; + + Gizmos.color = MB3_MeshBakerGrouper.WHITE_TRANSP; + float rad = sourceObjectBounds.extents.magnitude; + + int numRings = Mathf.CeilToInt(rad / d.ringSpacing); + numRings = Mathf.Max(1, numRings); + for (int i = 0; i < numRings; i++) + { + DrawCircle(d.pieAxis.normalized, d.origin, d.ringSpacing * (i + 1), 24); + } + + Quaternion yIsUp2PieAxis = Quaternion.FromToRotation(Vector3.up, d.pieAxis); + Quaternion rStep = Quaternion.AngleAxis(180f / d.pieNumSegments, Vector3.up); + Vector3 r = Vector3.forward; + for (int i = 0; i < d.pieNumSegments; i++) + { + Vector3 rr = yIsUp2PieAxis * r; + Vector3 origin = d.origin; + int nr = numRings; + if (d.combineSegmentsInInnermostRing) + { + origin = d.origin + rr.normalized * d.ringSpacing; + nr = numRings - 1; + } + + if (nr == 0) break; + + Gizmos.DrawLine(origin, origin + nr * d.ringSpacing * rr.normalized); + r = rStep * r; + r = rStep * r; + } + } + + static int MaxIndexInVector3(Vector3 v) + { + int idx = 0; + float val = v.x; + if (v.y > val) + { + idx = 1; + val = v.y; + } + if (v.z > val) + { + idx = 2; + val = v.z; + } + return idx; + } + + public static void DrawCircle(Vector3 axis, Vector3 center, float radius, int subdiv) + { + Quaternion q = Quaternion.AngleAxis(360 / subdiv, axis); + int maxIdx = MaxIndexInVector3(axis); + int otherIdx = maxIdx == 0 ? maxIdx + 1 : maxIdx - 1; + Vector3 r = axis; //r construct a vector perpendicular to axis + float temp = r[maxIdx]; + r[maxIdx] = r[otherIdx]; + r[otherIdx] = -temp; + r = Vector3.ProjectOnPlane(r, axis); + r.Normalize(); + r *= radius; + for (int i = 0; i < subdiv + 1; i++) + { + Vector3 r2 = q * r; + Gizmos.color = MB3_MeshBakerGrouper.WHITE_TRANSP; + Gizmos.DrawLine(center + r, center + r2); + r = r2; + } + } + } + + + [Serializable] + public class MB3_MeshBakerGrouperKMeans : MB3_MeshBakerGrouperCore + { + public int numClusters = 4; + public Vector3[] clusterCenters = new Vector3[0]; + public float[] clusterSizes = new float[0]; + + public MB3_MeshBakerGrouperKMeans(GrouperData data) + { + d = data; + } + + public override Dictionary<string, List<Renderer>> FilterIntoGroups(List<GameObject> selection) + { + Dictionary<string, List<Renderer>> cell2objs = new Dictionary<string, List<Renderer>>(); + List<GameObject> validObjs = new List<GameObject>(); + int numClusters = 20; + foreach (GameObject t in selection) + { + if (t == null) + { + continue; + } + GameObject go = t; + Renderer mr = go.GetComponent<Renderer>(); + if (mr is MeshRenderer || mr is SkinnedMeshRenderer) + { + //get the cell this gameObject is in + validObjs.Add(go); + } + } + if (validObjs.Count > 0 && numClusters > 0 && numClusters < validObjs.Count) + { + MB3_KMeansClustering kmc = new MB3_KMeansClustering(validObjs, numClusters); + kmc.Cluster(); + clusterCenters = new Vector3[numClusters]; + clusterSizes = new float[numClusters]; + for (int i = 0; i < numClusters; i++) + { + List<Renderer> lr = kmc.GetCluster(i, out clusterCenters[i], out clusterSizes[i]); + if (lr.Count > 0) + { + cell2objs.Add("Cluster_" + i, lr); + } + } + } + else + { + //todo error messages + } + return cell2objs; + } + + public override void DrawGizmos(Bounds sceneObjectBounds) + { + Gizmos.color = MB3_MeshBakerGrouper.WHITE_TRANSP; + if (clusterCenters != null && clusterSizes != null && clusterCenters.Length == clusterSizes.Length) + { + for (int i = 0; i < clusterSizes.Length; i++) + { + Gizmos.DrawWireSphere(clusterCenters[i], clusterSizes[i]); + } + } + } + } + + [Serializable] + public class MB3_MeshBakerGrouperCluster : MB3_MeshBakerGrouperCore + { + + public MB3_AgglomerativeClustering cluster; + float _lastMaxDistBetweenClusters; + public float _ObjsExtents = 10f; + public float _minDistBetweenClusters = .001f; + List<MB3_AgglomerativeClustering.ClusterNode> _clustersToDraw = new List<MB3_AgglomerativeClustering.ClusterNode>(); + float[] _radii; + + public MB3_MeshBakerGrouperCluster(GrouperData data, List<GameObject> gos) + { + d = data; + } + + public override Dictionary<string, List<Renderer>> FilterIntoGroups(List<GameObject> selection) + { + Dictionary<string, List<Renderer>> cell2objs = new Dictionary<string, List<Renderer>>(); + for (int i = 0; i < _clustersToDraw.Count; i++) + { + MB3_AgglomerativeClustering.ClusterNode node = _clustersToDraw[i]; + List<Renderer> rrs = new List<Renderer>(); + for (int j = 0; j < node.leafs.Length; j++) + { + Renderer r = cluster.clusters[node.leafs[j]].leaf.go.GetComponent<Renderer>(); + if (r is MeshRenderer || r is SkinnedMeshRenderer) + { + rrs.Add(r); + } + } + cell2objs.Add("Cluster_" + i, rrs); + } + return cell2objs; + } + + public void BuildClusters(List<GameObject> gos, ProgressUpdateCancelableDelegate progFunc) + { + if (gos.Count == 0) + { + Debug.LogWarning("No objects to cluster. Add some objects to the list of Objects To Combine."); + return; + } + if (cluster == null) cluster = new MB3_AgglomerativeClustering(); + List<MB3_AgglomerativeClustering.item_s> its = new List<MB3_AgglomerativeClustering.item_s>(); + for (int i = 0; i < gos.Count; i++) + { + if (gos[i] != null && its.Find(x => x.go == gos[i]) == null) + { + Renderer mr = gos[i].GetComponent<Renderer>(); + if (mr != null && (mr is MeshRenderer || mr is SkinnedMeshRenderer)) + { + MB3_AgglomerativeClustering.item_s ii = new MB3_AgglomerativeClustering.item_s(); + ii.go = gos[i]; + ii.coord = mr.bounds.center; + its.Add(ii); + } + } + } + cluster.items = its; + //yield return cluster.agglomerate(); + cluster.agglomerate(progFunc); + if (!cluster.wasCanceled) + { + float smallest, largest; + _BuildListOfClustersToDraw(progFunc, out smallest, out largest); + d.maxDistBetweenClusters = Mathf.Lerp(smallest, largest, .9f); + } + } + + void _BuildListOfClustersToDraw(ProgressUpdateCancelableDelegate progFunc, out float smallest, out float largest) + { + _clustersToDraw.Clear(); + if (cluster.clusters == null) + { + smallest = 1f; + largest = 10f; + return; + } + if (progFunc != null) progFunc("Building Clusters To Draw A:", 0); + List<MB3_AgglomerativeClustering.ClusterNode> removeMe = new List<MB3_AgglomerativeClustering.ClusterNode>(); + largest = 1f; + smallest = 10e6f; + for (int i = 0; i < cluster.clusters.Length; i++) + { + MB3_AgglomerativeClustering.ClusterNode node = cluster.clusters[i]; + //don't draw clusters that were merged too far apart and only want leaf nodes + if (node.distToMergedCentroid <= d.maxDistBetweenClusters /*&& node.leaf == null*/) + { + if (d.includeCellsWithOnlyOneRenderer) + { + _clustersToDraw.Add(node); + } + else if (node.leaf == null) + { + _clustersToDraw.Add(node); + } + } + if (node.distToMergedCentroid > largest) + { + largest = node.distToMergedCentroid; + } + if (node.height > 0 && node.distToMergedCentroid < smallest) + { + smallest = node.distToMergedCentroid; + } + } + if (progFunc != null) progFunc("Building Clusters To Draw B:", 0); + for (int i = 0; i < _clustersToDraw.Count; i++) + { + removeMe.Add(_clustersToDraw[i].cha); + removeMe.Add(_clustersToDraw[i].chb); + } + + for (int i = 0; i < removeMe.Count; i++) + { + _clustersToDraw.Remove(removeMe[i]); + } + _radii = new float[_clustersToDraw.Count]; + if (progFunc != null) progFunc("Building Clusters To Draw C:", 0); + for (int i = 0; i < _radii.Length; i++) + { + MB3_AgglomerativeClustering.ClusterNode n = _clustersToDraw[i]; + Bounds b = new Bounds(n.centroid, Vector3.one); + for (int j = 0; j < n.leafs.Length; j++) + { + Renderer r = cluster.clusters[n.leafs[j]].leaf.go.GetComponent<Renderer>(); + if (r != null) + { + b.Encapsulate(r.bounds); + } + } + _radii[i] = b.extents.magnitude; + } + if (progFunc != null) progFunc("Building Clusters To Draw D:", 0); + _ObjsExtents = largest + 1f; + _minDistBetweenClusters = Mathf.Lerp(smallest, 0f, .9f); + + if (_ObjsExtents < 2f) _ObjsExtents = 2f; + } + + public override void DrawGizmos(Bounds sceneObjectBounds) + { + if (cluster == null || cluster.clusters == null) + { + return; + } + if (_lastMaxDistBetweenClusters != d.maxDistBetweenClusters) + { + float s, l; + _BuildListOfClustersToDraw(null, out s, out l); + _lastMaxDistBetweenClusters = d.maxDistBetweenClusters; + } + + Gizmos.color = MB3_MeshBakerGrouper.WHITE_TRANSP; + for (int i = 0; i < _clustersToDraw.Count; i++) + { + Gizmos.color = MB3_MeshBakerGrouper.WHITE_TRANSP; + MB3_AgglomerativeClustering.ClusterNode node = _clustersToDraw[i]; + Gizmos.DrawWireSphere(node.centroid, _radii[i]); + } + } + } +} + diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerGrouper.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerGrouper.cs.meta new file mode 100644 index 00000000..0eaf6f2c --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerGrouper.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a97974931c5043247b11cd77898c1702 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerRoot.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerRoot.cs new file mode 100644 index 00000000..5f271f08 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerRoot.cs @@ -0,0 +1,162 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Text; +using DigitalOpus.MB.Core; + +/// <summary> +/// Root class of all the baking Components +/// </summary> +public abstract class MB3_MeshBakerRoot : MonoBehaviour { + + /** + * Transparent shaders often require objects to be sorted along + */ + public class ZSortObjects + { + public Vector3 sortAxis; + public class Item + { + public GameObject go; + public Vector3 point; + } + + public class ItemComparer : IComparer<Item> + { + public int Compare(Item a, Item b) + { + return (int)Mathf.Sign(b.point.z - a.point.z); + } + } + + public void SortByDistanceAlongAxis(List<GameObject> gos) + { + if (sortAxis == Vector3.zero) + { + Debug.LogError("The sort axis cannot be the zero vector."); + return; + } + Debug.Log("Z sorting meshes along axis numObjs=" + gos.Count); + List<Item> items = new List<Item>(); + Quaternion q = Quaternion.FromToRotation(sortAxis, Vector3.forward); + for (int i = 0; i < gos.Count; i++) + { + if (gos[i] != null) + { + Item item = new Item(); + item.point = gos[i].transform.position; + item.go = gos[i]; + item.point = q * item.point; + items.Add(item); + } + } + items.Sort(new ItemComparer()); + + for (int i = 0; i < gos.Count; i++) + { + gos[i] = items[i].go; + } + } + } + + public Vector3 sortAxis; + + [HideInInspector] public abstract MB2_TextureBakeResults textureBakeResults{ + get; + set; + } + + //todo switch this to List<Renderer> + public virtual List<GameObject> GetObjectsToCombine(){ + return null; + } + + public static bool DoCombinedValidate(MB3_MeshBakerRoot mom, MB_ObjsToCombineTypes objToCombineType, MB2_EditorMethodsInterface editorMethods, MB2_ValidationLevel validationLevel){ + if (mom.textureBakeResults == null){ + Debug.LogError("Need to set Texture Bake Result on " + mom); + return false; + } + if (mom is MB3_MeshBakerCommon){ + MB3_MeshBakerCommon momMB = (MB3_MeshBakerCommon) mom; + MB3_TextureBaker tb = momMB.GetTextureBaker(); + if (tb != null && tb.textureBakeResults != mom.textureBakeResults){ + Debug.LogWarning("Texture Bake Result on this component is not the same as the Texture Bake Result on the MB3_TextureBaker."); + } + } + + Dictionary<int,MB_Utility.MeshAnalysisResult> meshAnalysisResultCache = null; + if (validationLevel == MB2_ValidationLevel.robust){ + meshAnalysisResultCache = new Dictionary<int, MB_Utility.MeshAnalysisResult>(); + } + List<GameObject> objsToMesh = mom.GetObjectsToCombine(); + for (int i = 0; i < objsToMesh.Count; i++){ + GameObject go = objsToMesh[i]; + if (go == null){ + Debug.LogError("The list of objects to combine contains a null at position." + i + " Select and use [shift] delete to remove"); + return false; + } + for (int j = i + 1; j < objsToMesh.Count; j++){ + if (objsToMesh[i] == objsToMesh[j]){ + Debug.LogError("The list of objects to combine contains duplicates at " + i + " and " + j); + return false; + } + } + if (MB_Utility.GetGOMaterials(go).Length == 0){ + Debug.LogError("Object " + go + " in the list of objects to be combined does not have a material"); + return false; + } + Mesh m = MB_Utility.GetMesh(go); + if (m == null){ + Debug.LogError("Object " + go + " in the list of objects to be combined does not have a mesh"); + return false; + } + if (m != null){ //This check can be very expensive and it only warns so only do this if we are in the editor. + if (!Application.isEditor && + Application.isPlaying && + mom.textureBakeResults.doMultiMaterial && + validationLevel >= MB2_ValidationLevel.robust){ + MB_Utility.MeshAnalysisResult mar; + if (!meshAnalysisResultCache.TryGetValue(m.GetInstanceID(),out mar)){ + MB_Utility.doSubmeshesShareVertsOrTris(m,ref mar); + meshAnalysisResultCache.Add (m.GetInstanceID(),mar); + } + if (mar.hasOverlappingSubmeshVerts){ + Debug.LogWarning("Object " + objsToMesh[i] + " in the list of objects to combine has overlapping submeshes (submeshes share vertices). If the UVs associated with the shared vertices are important then this bake may not work. If you are using multiple materials then this object can only be combined with objects that use the exact same set of textures (each atlas contains one texture). There may be other undesirable side affects as well. Mesh Master, available in the asset store can fix overlapping submeshes."); + } + } + } + } + + + List<GameObject> objs = objsToMesh; + + if (mom is MB3_MeshBaker) + { + objs = mom.GetObjectsToCombine(); + //if (((MB3_MeshBaker)mom).useObjsToMeshFromTexBaker && tb != null) objs = tb.GetObjectsToCombine(); + if (objs == null || objs.Count == 0) + { + Debug.LogError("No meshes to combine. Please assign some meshes to combine."); + return false; + } + if (mom is MB3_MeshBaker && ((MB3_MeshBaker)mom).meshCombiner.settings.renderType == MB_RenderType.skinnedMeshRenderer){ + if (!editorMethods.ValidateSkinnedMeshes(objs)) + { + return false; + } + } + } + + if (editorMethods != null){ + editorMethods.CheckPrefabTypes(objToCombineType, objsToMesh); + } + return true; + } +} + diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerRoot.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerRoot.cs.meta new file mode 100644 index 00000000..6ee7a8b1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MeshBakerRoot.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aceaf5bd3ec59834bbd2a309fb7b0967 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MultiMeshBaker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MultiMeshBaker.cs new file mode 100644 index 00000000..798785c1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MultiMeshBaker.cs @@ -0,0 +1,52 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Text; +using DigitalOpus.MB.Core; +using System.Text.RegularExpressions; + +/// <summary> +/// Component that is an endless mesh. You don't need to worry about the 65k limit when adding meshes. It is like a List of combined meshes. Internally it manages +/// a collection of CombinedMeshes that are added and deleted as necessary. +/// +/// Note that this implementation does +/// not attempt to split meshes. Each mesh is added to one of the internal meshes as an atomic unit. +/// +/// This class is a Component. It must be added to a GameObject to use it. It is a wrapper for MB2_Multi_meshCombiner which contains the same functionality but is not a component +/// so it can be instantiated like a normal class. +/// </summary> +public class MB3_MultiMeshBaker : MB3_MeshBakerCommon { + + [SerializeField] protected MB3_MultiMeshCombiner _meshCombiner = new MB3_MultiMeshCombiner(); + + public override MB3_MeshCombiner meshCombiner{ + get {return _meshCombiner;} + } + + public override bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource){ + if (_meshCombiner.resultSceneObject == null){ + _meshCombiner.resultSceneObject = new GameObject("CombinedMesh-" + name); + } + meshCombiner.name = name + "-mesh"; + return _meshCombiner.AddDeleteGameObjects(gos,deleteGOs,disableRendererInSource); + } + + public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOs, bool disableRendererInSource){ + if (_meshCombiner.resultSceneObject == null){ + _meshCombiner.resultSceneObject = new GameObject("CombinedMesh-" + name); + } + meshCombiner.name = name + "-mesh"; + return _meshCombiner.AddDeleteGameObjectsByID(gos,deleteGOs,disableRendererInSource); + } + + public void OnDestroy() + { + _meshCombiner.DisposeRuntimeCreated(); + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MultiMeshBaker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MultiMeshBaker.cs.meta new file mode 100644 index 00000000..74115174 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_MultiMeshBaker.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 79177c6ffa4ff3846a904f185a7591cf +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_TextureBaker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_TextureBaker.cs new file mode 100644 index 00000000..17c986c4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_TextureBaker.cs @@ -0,0 +1,742 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using DigitalOpus.MB.Core; + + + +/// <summary> +/// Component that handles baking materials into a combined material. +/// +/// The result of the material baking process is a MB2_TextureBakeResults object, which +/// becomes the input for the mesh baking. +/// +/// This class uses the MB_TextureCombiner to do the combining. +/// +/// This class is a Component (MonoBehavior) so it is serialized and found using GetComponent. If +/// you want to access the texture baking functionality without creating a Component then use MB_TextureCombiner +/// directly. +/// </summary> +public class MB3_TextureBaker : MB3_MeshBakerRoot +{ + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + + [SerializeField] + protected MB2_TextureBakeResults _textureBakeResults; + public override MB2_TextureBakeResults textureBakeResults + { + get { return _textureBakeResults; } + set { _textureBakeResults = value; } + } + + [SerializeField] + protected int _atlasPadding = 1; + public virtual int atlasPadding + { + get { return _atlasPadding; } + set { _atlasPadding = value; } + } + + [SerializeField] + protected int _maxAtlasSize = 4096; + public virtual int maxAtlasSize + { + get { return _maxAtlasSize; } + set { _maxAtlasSize = value; } + } + + [SerializeField] + protected bool _useMaxAtlasWidthOverride = false; + public virtual bool useMaxAtlasWidthOverride + { + get { return _useMaxAtlasWidthOverride; } + set { _useMaxAtlasWidthOverride = value; } + } + + [SerializeField] + protected int _maxAtlasWidthOverride = 4096; + public virtual int maxAtlasWidthOverride + { + get { return _maxAtlasWidthOverride; } + set { _maxAtlasWidthOverride = value; } + } + + [SerializeField] + protected bool _useMaxAtlasHeightOverride = false; + public virtual bool useMaxAtlasHeightOverride + { + get { return _useMaxAtlasHeightOverride; } + set { _useMaxAtlasHeightOverride = value; } + } + + [SerializeField] + protected int _maxAtlasHeightOverride = 4096; + public virtual int maxAtlasHeightOverride + { + get { return _maxAtlasHeightOverride; } + set { _maxAtlasHeightOverride = value; } + } + + [SerializeField] + protected bool _resizePowerOfTwoTextures = false; + public virtual bool resizePowerOfTwoTextures + { + get { return _resizePowerOfTwoTextures; } + set { _resizePowerOfTwoTextures = value; } + } + + [SerializeField] + protected bool _fixOutOfBoundsUVs = false; //is considerMeshUVs but can't change because it would break all existing bakers + public virtual bool fixOutOfBoundsUVs + { + get { return _fixOutOfBoundsUVs; } + set { _fixOutOfBoundsUVs = value; } + } + + [SerializeField] + protected int _maxTilingBakeSize = 1024; + public virtual int maxTilingBakeSize + { + get { return _maxTilingBakeSize; } + set { _maxTilingBakeSize = value; } + } + + [SerializeField] + protected MB2_PackingAlgorithmEnum _packingAlgorithm = MB2_PackingAlgorithmEnum.MeshBakerTexturePacker; + public virtual MB2_PackingAlgorithmEnum packingAlgorithm + { + get { return _packingAlgorithm; } + set { _packingAlgorithm = value; } + } + + [SerializeField] + protected int _layerTexturePackerFastMesh = -1; + public virtual int layerForTexturePackerFastMesh + { + get { return _layerTexturePackerFastMesh; } + set { _layerTexturePackerFastMesh = value; } + } + + [SerializeField] + protected bool _meshBakerTexturePackerForcePowerOfTwo = true; + public bool meshBakerTexturePackerForcePowerOfTwo + { + get { return _meshBakerTexturePackerForcePowerOfTwo; } + set { _meshBakerTexturePackerForcePowerOfTwo = value; } + } + + [SerializeField] + protected List<ShaderTextureProperty> _customShaderProperties = new List<ShaderTextureProperty>(); + public virtual List<ShaderTextureProperty> customShaderProperties + { + get { return _customShaderProperties; } + set { _customShaderProperties = value; } + } + + //this is depricated + [SerializeField] + protected List<string> _customShaderPropNames_Depricated = new List<string>(); + public virtual List<string> customShaderPropNames + { + get { return _customShaderPropNames_Depricated; } + set { _customShaderPropNames_Depricated = value; } + } + + [SerializeField] + protected MB2_TextureBakeResults.ResultType _resultType; + public virtual MB2_TextureBakeResults.ResultType resultType + { + get { return _resultType; } + set { _resultType = value; } + } + + [SerializeField] + protected bool _doMultiMaterial; + public virtual bool doMultiMaterial + { + get { return _doMultiMaterial; } + set { _doMultiMaterial = value; } + } + + [SerializeField] + protected bool _doMultiMaterialSplitAtlasesIfTooBig = true; + public virtual bool doMultiMaterialSplitAtlasesIfTooBig + { + get { return _doMultiMaterialSplitAtlasesIfTooBig; } + set { _doMultiMaterialSplitAtlasesIfTooBig = value; } + } + + [SerializeField] + protected bool _doMultiMaterialSplitAtlasesIfOBUVs = true; + public virtual bool doMultiMaterialSplitAtlasesIfOBUVs + { + get { return _doMultiMaterialSplitAtlasesIfOBUVs; } + set { _doMultiMaterialSplitAtlasesIfOBUVs = value; } + } + + [SerializeField] + protected Material _resultMaterial; + public virtual Material resultMaterial + { + get { return _resultMaterial; } + set { _resultMaterial = value; } + } + + [SerializeField] + protected bool _considerNonTextureProperties = false; + public bool considerNonTextureProperties + { + get { return _considerNonTextureProperties; } + set { _considerNonTextureProperties = value; } + } + + [SerializeField] + protected bool _doSuggestTreatment = true; + public bool doSuggestTreatment + { + get { return _doSuggestTreatment; } + set { _doSuggestTreatment = value; } + } + + private MB3_TextureCombiner.CreateAtlasesCoroutineResult _coroutineResult; + public MB3_TextureCombiner.CreateAtlasesCoroutineResult CoroutineResult + { + get + { + return _coroutineResult; + } + } + + public MB_MultiMaterial[] resultMaterials = new MB_MultiMaterial[0]; + + public MB_MultiMaterialTexArray[] resultMaterialsTexArray = new MB_MultiMaterialTexArray[0]; + + public MB_TextureArrayFormatSet[] textureArrayOutputFormats; + + public List<GameObject> objsToMesh; //todo make this Renderer + + public override List<GameObject> GetObjectsToCombine() + { + if (objsToMesh == null) objsToMesh = new List<GameObject>(); + return objsToMesh; + } + + public MB_AtlasesAndRects[] CreateAtlases() + { + return CreateAtlases(null, false, null); + } + + public delegate void OnCombinedTexturesCoroutineSuccess(); + public delegate void OnCombinedTexturesCoroutineFail(); + public OnCombinedTexturesCoroutineSuccess onBuiltAtlasesSuccess; + public OnCombinedTexturesCoroutineFail onBuiltAtlasesFail; + public MB_AtlasesAndRects[] OnCombinedTexturesCoroutineAtlasesAndRects; + + public IEnumerator CreateAtlasesCoroutine(ProgressUpdateDelegate progressInfo, MB3_TextureCombiner.CreateAtlasesCoroutineResult coroutineResult, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null, float maxTimePerFrame = .01f) + { + yield return _CreateAtlasesCoroutine(progressInfo, coroutineResult, saveAtlasesAsAssets, editorMethods, maxTimePerFrame); + + if (coroutineResult.success && onBuiltAtlasesSuccess != null) + { + onBuiltAtlasesSuccess(); + } + + if (!coroutineResult.success && onBuiltAtlasesFail != null) + { + onBuiltAtlasesFail(); + } + } + + private IEnumerator _CreateAtlasesCoroutineAtlases(MB3_TextureCombiner combiner, ProgressUpdateDelegate progressInfo, MB3_TextureCombiner.CreateAtlasesCoroutineResult coroutineResult, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null, float maxTimePerFrame = .01f) + { + int numResults = 1; + if (_doMultiMaterial) + { + numResults = resultMaterials.Length; + } + + OnCombinedTexturesCoroutineAtlasesAndRects = new MB_AtlasesAndRects[numResults]; + for (int i = 0; i < OnCombinedTexturesCoroutineAtlasesAndRects.Length; i++) + { + OnCombinedTexturesCoroutineAtlasesAndRects[i] = new MB_AtlasesAndRects(); + } + + //Do the material combining. + for (int i = 0; i < OnCombinedTexturesCoroutineAtlasesAndRects.Length; i++) + { + Material resMatToPass = null; + List<Material> sourceMats = null; + if (_doMultiMaterial) + { + sourceMats = resultMaterials[i].sourceMaterials; + resMatToPass = resultMaterials[i].combinedMaterial; + combiner.fixOutOfBoundsUVs = resultMaterials[i].considerMeshUVs; + } + else + { + resMatToPass = _resultMaterial; + } + + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult coroutineResult2 = new MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult(); + yield return combiner.CombineTexturesIntoAtlasesCoroutine(progressInfo, OnCombinedTexturesCoroutineAtlasesAndRects[i], resMatToPass, objsToMesh, sourceMats, editorMethods, coroutineResult2, maxTimePerFrame, + onlyPackRects: false, splitAtlasWhenPackingIfTooBig: false); + coroutineResult.success = coroutineResult2.success; + if (!coroutineResult.success) + { + coroutineResult.isFinished = true; + yield break; + } + } + + unpackMat2RectMap(OnCombinedTexturesCoroutineAtlasesAndRects); + //Save the results + textureBakeResults.resultType = MB2_TextureBakeResults.ResultType.atlas; + textureBakeResults.resultMaterialsTexArray = new MB_MultiMaterialTexArray[0]; + textureBakeResults.doMultiMaterial = _doMultiMaterial; + if (_doMultiMaterial) + { + textureBakeResults.resultMaterials = resultMaterials; + } + else + { + MB_MultiMaterial[] resMats = new MB_MultiMaterial[1]; + resMats[0] = new MB_MultiMaterial(); + resMats[0].combinedMaterial = _resultMaterial; + resMats[0].considerMeshUVs = _fixOutOfBoundsUVs; + resMats[0].sourceMaterials = new List<Material>(); + for (int i = 0; i < textureBakeResults.materialsAndUVRects.Length; i++) + { + resMats[0].sourceMaterials.Add(textureBakeResults.materialsAndUVRects[i].material); + } + textureBakeResults.resultMaterials = resMats; + } + + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("Created Atlases"); + } + + internal IEnumerator _CreateAtlasesCoroutineTextureArray(MB3_TextureCombiner combiner, ProgressUpdateDelegate progressInfo, MB3_TextureCombiner.CreateAtlasesCoroutineResult coroutineResult, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null, float maxTimePerFrame = .01f) + { + MB_TextureArrayResultMaterial[] bakedMatsAndSlices = null; + + // Validate the formats + if (textureArrayOutputFormats == null || textureArrayOutputFormats.Length == 0) + { + Debug.LogError("No Texture Array Output Formats. There must be at least one entry."); + coroutineResult.isFinished = true; + yield break; + } + + for (int i = 0; i < textureArrayOutputFormats.Length; i++) + { + if (!textureArrayOutputFormats[i].ValidateTextureImporterFormatsExistsForTextureFormats(editorMethods, i)) + { + Debug.LogError("Could not map the selected texture format to a Texture Importer Format. Safest options are ARGB32, or RGB24."); + coroutineResult.isFinished = true; + yield break; + } + } + + for (int resMatIdx = 0; resMatIdx < resultMaterialsTexArray.Length; resMatIdx++) + { + MB_MultiMaterialTexArray textureArraySliceConfig = resultMaterialsTexArray[resMatIdx]; + if (textureArraySliceConfig.combinedMaterial == null) + { + Debug.LogError("Material is null for Texture Array Slice Configuration: " + resMatIdx + "."); + coroutineResult.isFinished = true; + yield break; + } + + + List<MB_TexArraySlice> slices = textureArraySliceConfig.slices; + for (int sliceIdx = 0; sliceIdx < slices.Count; sliceIdx++) + { + for (int srcMatIdx = 0; srcMatIdx < slices[sliceIdx].sourceMaterials.Count; srcMatIdx++) + { + MB_TexArraySliceRendererMatPair sourceMat = slices[sliceIdx].sourceMaterials[srcMatIdx]; + if (sourceMat.sourceMaterial == null) + { + Debug.LogError("Source material is null for Texture Array Slice Configuration: " + resMatIdx + " slice: " + sliceIdx); + coroutineResult.isFinished = true; + yield break; + } + + if (slices[sliceIdx].considerMeshUVs) + { + if (sourceMat.renderer == null) + { + Debug.LogError("Renderer is null for Texture Array Slice Configuration: " + resMatIdx + " slice: " + sliceIdx + ". If considerUVs is enabled then a renderer must be supplied for each source material. The same source material can be used multiple times."); + coroutineResult.isFinished = true; + yield break; + } + } + else + { + // TODO check for duplicate source mats. + } + } + } + } + + // initialize structure to store results. For texture arrays the structure is two layers deep. + // First layer is resultMaterial / submesh (each result material can use a different shader) + // Second layer is a set of TextureArrays for the TextureProperties on that result material. + int numResultMats = resultMaterialsTexArray.Length; + bakedMatsAndSlices = new MB_TextureArrayResultMaterial[numResultMats]; + for (int resMatIdx = 0; resMatIdx < bakedMatsAndSlices.Length; resMatIdx++) + { + bakedMatsAndSlices[resMatIdx] = new MB_TextureArrayResultMaterial(); + int numSlices = resultMaterialsTexArray[resMatIdx].slices.Count; + MB_AtlasesAndRects[] slices = bakedMatsAndSlices[resMatIdx].slices = new MB_AtlasesAndRects[numSlices]; + for (int j = 0; j < numSlices; j++) slices[j] = new MB_AtlasesAndRects(); + } + + // Some of the slices will be atlases (more than one atlas per slice). + // Do the material combining for these. First loop over the result materials (1 per submeshes). + for (int resMatIdx = 0; resMatIdx < bakedMatsAndSlices.Length; resMatIdx++) + { + yield return MB_TextureArrays._CreateAtlasesCoroutineSingleResultMaterial(resMatIdx, bakedMatsAndSlices[resMatIdx], resultMaterialsTexArray[resMatIdx], + objsToMesh, + combiner, + textureArrayOutputFormats, + resultMaterialsTexArray, + customShaderProperties, + progressInfo, coroutineResult, saveAtlasesAsAssets, editorMethods, maxTimePerFrame); + if (!coroutineResult.success) yield break; + } + + if (coroutineResult.success) + { + // Save the results into the TextureBakeResults. + unpackMat2RectMap(bakedMatsAndSlices); + textureBakeResults.resultType = MB2_TextureBakeResults.ResultType.textureArray; + textureBakeResults.resultMaterials = new MB_MultiMaterial[0]; + textureBakeResults.resultMaterialsTexArray = resultMaterialsTexArray; + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("Created Texture2DArrays"); + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("Failed to create Texture2DArrays"); + } + } + + private IEnumerator _CreateAtlasesCoroutine(ProgressUpdateDelegate progressInfo, MB3_TextureCombiner.CreateAtlasesCoroutineResult coroutineResult, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null, float maxTimePerFrame = .01f) + { + MBVersionConcrete mbv = new MBVersionConcrete(); + if (!MB3_TextureCombiner._RunCorutineWithoutPauseIsRunning && (mbv.GetMajorVersion() < 5 || (mbv.GetMajorVersion() == 5 && mbv.GetMinorVersion() < 3))) + { + Debug.LogError("Running the texture combiner as a coroutine only works in Unity 5.3 and higher"); + coroutineResult.success = false; + yield break; + } + this.OnCombinedTexturesCoroutineAtlasesAndRects = null; + + if (maxTimePerFrame <= 0f) + { + Debug.LogError("maxTimePerFrame must be a value greater than zero"); + coroutineResult.isFinished = true; + yield break; + } + MB2_ValidationLevel vl = Application.isPlaying ? MB2_ValidationLevel.quick : MB2_ValidationLevel.robust; + if (!DoCombinedValidate(this, MB_ObjsToCombineTypes.dontCare, null, vl)) + { + coroutineResult.isFinished = true; + yield break; + } + if (_doMultiMaterial && !_ValidateResultMaterials()) + { + coroutineResult.isFinished = true; + yield break; + } + else if (resultType == MB2_TextureBakeResults.ResultType.textureArray) + { + //TODO validate texture arrays. + } + else if (!_doMultiMaterial) + { + if (_resultMaterial == null) + { + Debug.LogError("Combined Material is null please create and assign a result material."); + coroutineResult.isFinished = true; + yield break; + } + Shader targShader = _resultMaterial.shader; + for (int i = 0; i < objsToMesh.Count; i++) + { + Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); + for (int j = 0; j < ms.Length; j++) + { + Material m = ms[j]; + if (m != null && m.shader != targShader) + { + Debug.LogWarning("Game object " + objsToMesh[i] + " does not use shader " + targShader + " it may not have the required textures. If not small solid color textures will be generated."); + } + + } + } + } + + MB3_TextureCombiner combiner = CreateAndConfigureTextureCombiner(); + combiner.saveAtlasesAsAssets = saveAtlasesAsAssets; + + OnCombinedTexturesCoroutineAtlasesAndRects = null; + if (resultType == MB2_TextureBakeResults.ResultType.textureArray) + { + yield return _CreateAtlasesCoroutineTextureArray(combiner, progressInfo, coroutineResult, saveAtlasesAsAssets, editorMethods, maxTimePerFrame); + if (!coroutineResult.success) yield break; + } + else + { + yield return _CreateAtlasesCoroutineAtlases(combiner, progressInfo, coroutineResult, saveAtlasesAsAssets, editorMethods, maxTimePerFrame); + if (!coroutineResult.success) yield break; + } + + //set the texture bake resultAtlasesAndRects on the Mesh Baker component if it exists + MB3_MeshBakerCommon[] mb = GetComponentsInChildren<MB3_MeshBakerCommon>(); + for (int i = 0; i < mb.Length; i++) + { + mb[i].textureBakeResults = textureBakeResults; + } + + coroutineResult.isFinished = true; + } + + /// <summary> + /// Creates the atlases. + /// </summary> + /// <returns> + /// The atlases. + /// </returns> + /// <param name='progressInfo'> + /// Progress info is a delegate function that displays a progress dialog. Can be null + /// </param> + /// <param name='saveAtlasesAsAssets'> + /// if true atlases are saved as assets in the project folder. Othersise they are instances in memory + /// </param> + /// <param name='editorMethods'> + /// Texture format tracker. Contains editor functionality such as save assets. Can be null. + /// </param> + public MB_AtlasesAndRects[] CreateAtlases(ProgressUpdateDelegate progressInfo, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods = null) + { + MB_AtlasesAndRects[] mAndAs = null; + + try + { + _coroutineResult = new MB3_TextureCombiner.CreateAtlasesCoroutineResult(); + MB3_TextureCombiner.RunCorutineWithoutPause(CreateAtlasesCoroutine(progressInfo, _coroutineResult, saveAtlasesAsAssets, editorMethods, 1000f), 0); + if (_coroutineResult.success && textureBakeResults != null) + { + mAndAs = this.OnCombinedTexturesCoroutineAtlasesAndRects; + } + } + catch (Exception ex) + { + Debug.LogError(ex.Message + "\n" + ex.StackTrace.ToString()); + } + finally + { + if (saveAtlasesAsAssets) + { //Atlases were saved to project so we don't need these ones + if (mAndAs != null) + { + for (int j = 0; j < mAndAs.Length; j++) + { + MB_AtlasesAndRects mAndA = mAndAs[j]; + if (mAndA != null && mAndA.atlases != null) + { + for (int i = 0; i < mAndA.atlases.Length; i++) + { + if (mAndA.atlases[i] != null) + { + if (editorMethods != null) editorMethods.Destroy(mAndA.atlases[i]); + else MB_Utility.Destroy(mAndA.atlases[i]); + } + } + } + } + } + } + } + + return mAndAs; + } + + void unpackMat2RectMap(MB_AtlasesAndRects[] rawResults) + { + List<MB_MaterialAndUVRect> mss = new List<MB_MaterialAndUVRect>(); + for (int i = 0; i < rawResults.Length; i++) + { + List<MB_MaterialAndUVRect> map = rawResults[i].mat2rect_map; + if (map != null) + { + for (int j = 0; j < map.Count; j++) + { + map[j].textureArraySliceIdx = -1; + mss.Add(map[j]); + } + } + } + + textureBakeResults.version = MB2_TextureBakeResults.VERSION; + textureBakeResults.materialsAndUVRects = mss.ToArray(); + } + + internal void unpackMat2RectMap(MB_TextureArrayResultMaterial[] rawResults) + { + List<MB_MaterialAndUVRect> mss = new List<MB_MaterialAndUVRect>(); + for (int resMatIdx = 0; resMatIdx < rawResults.Length; resMatIdx++) + { + MB_AtlasesAndRects[] slices = rawResults[resMatIdx].slices; + for (int sliceIdx = 0; sliceIdx < slices.Length; sliceIdx++) + { + List<MB_MaterialAndUVRect> map = slices[sliceIdx].mat2rect_map; + if (map != null) + { + for (int rectIdx = 0; rectIdx < map.Count; rectIdx++) + { + map[rectIdx].textureArraySliceIdx = sliceIdx; + mss.Add(map[rectIdx]); + } + } + } + } + + textureBakeResults.version = MB2_TextureBakeResults.VERSION; + textureBakeResults.materialsAndUVRects = mss.ToArray(); + } + + public MB3_TextureCombiner CreateAndConfigureTextureCombiner() + { + MB3_TextureCombiner combiner = new MB3_TextureCombiner(); + combiner.LOG_LEVEL = LOG_LEVEL; + combiner.atlasPadding = _atlasPadding; + combiner.maxAtlasSize = _maxAtlasSize; + combiner.maxAtlasHeightOverride = _maxAtlasHeightOverride; + combiner.maxAtlasWidthOverride = _maxAtlasWidthOverride; + combiner.useMaxAtlasHeightOverride = _useMaxAtlasHeightOverride; + combiner.useMaxAtlasWidthOverride = _useMaxAtlasWidthOverride; + combiner.customShaderPropNames = _customShaderProperties; + combiner.fixOutOfBoundsUVs = _fixOutOfBoundsUVs; + combiner.maxTilingBakeSize = _maxTilingBakeSize; + combiner.packingAlgorithm = _packingAlgorithm; + combiner.layerTexturePackerFastMesh = _layerTexturePackerFastMesh; + combiner.resultType = _resultType; + combiner.meshBakerTexturePackerForcePowerOfTwo = _meshBakerTexturePackerForcePowerOfTwo; + combiner.resizePowerOfTwoTextures = _resizePowerOfTwoTextures; + combiner.considerNonTextureProperties = _considerNonTextureProperties; + return combiner; + } + + public static void ConfigureNewMaterialToMatchOld(Material newMat, Material original) + { + if (original == null) + { + Debug.LogWarning("Original material is null, could not copy properties to " + newMat + ". Setting shader to " + newMat.shader); + return; + } + newMat.shader = original.shader; + newMat.CopyPropertiesFromMaterial(original); + ShaderTextureProperty[] texPropertyNames = MB3_TextureCombinerPipeline.shaderTexPropertyNames; + for (int j = 0; j < texPropertyNames.Length; j++) + { + Vector2 scale = Vector2.one; + Vector2 offset = Vector2.zero; + if (newMat.HasProperty(texPropertyNames[j].name)) + { + newMat.SetTextureOffset(texPropertyNames[j].name, offset); + newMat.SetTextureScale(texPropertyNames[j].name, scale); + } + } + } + + string PrintSet(HashSet<Material> s) + { + StringBuilder sb = new StringBuilder(); + foreach (Material m in s) + { + sb.Append(m + ","); + } + return sb.ToString(); + } + + bool _ValidateResultMaterials() + { + HashSet<Material> allMatsOnObjs = new HashSet<Material>(); + for (int i = 0; i < objsToMesh.Count; i++) + { + if (objsToMesh[i] != null) + { + Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); + for (int j = 0; j < ms.Length; j++) + { + if (ms[j] != null) allMatsOnObjs.Add(ms[j]); + } + } + } + + if (resultMaterials.Length < 1) + { + Debug.LogError("Using multiple materials but there are no 'Source Material To Combined Mappings'. You need at least one."); + } + + HashSet<Material> allMatsInMapping = new HashSet<Material>(); + for (int i = 0; i < resultMaterials.Length; i++) + { + for (int j = i + 1; j < resultMaterials.Length; j++) + { + if (resultMaterials[i].combinedMaterial == resultMaterials[j].combinedMaterial) + { + Debug.LogError(String.Format("Source To Combined Mapping: Submesh {0} and Submesh {1} use the same combined material. These should be different", i, j)); + return false; + } + } + + MB_MultiMaterial mm = resultMaterials[i]; + if (mm.combinedMaterial == null) + { + Debug.LogError("Combined Material is null please create and assign a result material."); + return false; + } + Shader targShader = mm.combinedMaterial.shader; + for (int j = 0; j < mm.sourceMaterials.Count; j++) + { + if (mm.sourceMaterials[j] == null) + { + Debug.LogError("There are null entries in the list of Source Materials"); + return false; + } + if (targShader != mm.sourceMaterials[j].shader) + { + Debug.LogWarning("Source material " + mm.sourceMaterials[j] + " does not use shader " + targShader + " it may not have the required textures. If not empty textures will be generated."); + } + if (allMatsInMapping.Contains(mm.sourceMaterials[j])) + { + Debug.LogError("A Material " + mm.sourceMaterials[j] + " appears more than once in the list of source materials in the source material to combined mapping. Each source material must be unique."); + return false; + } + allMatsInMapping.Add(mm.sourceMaterials[j]); + } + } + + if (allMatsOnObjs.IsProperSubsetOf(allMatsInMapping)) + { + allMatsInMapping.ExceptWith(allMatsOnObjs); + Debug.LogWarning("There are materials in the mapping that are not used on your source objects: " + PrintSet(allMatsInMapping)); + } + if (resultMaterials != null && resultMaterials.Length > 0 && allMatsInMapping.IsProperSubsetOf(allMatsOnObjs)) + { + allMatsOnObjs.ExceptWith(allMatsInMapping); + Debug.LogError("There are materials on the objects to combine that are not in the mapping: " + PrintSet(allMatsOnObjs)); + return false; + } + return true; + } +} + diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_TextureBaker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_TextureBaker.cs.meta new file mode 100644 index 00000000..10037be2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MB3_TextureBaker.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 077f80fe3940dc7468ecec90989abe66 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MeshBakerCore.asmdef b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MeshBakerCore.asmdef new file mode 100644 index 00000000..eafee068 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MeshBakerCore.asmdef @@ -0,0 +1,12 @@ +{ + "name": "MeshBakerCore", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/MeshBakerCore.asmdef.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MeshBakerCore.asmdef.meta new file mode 100644 index 00000000..c7ae09c5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/MeshBakerCore.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0c06a9c96253b2c4e94d65ecc349b306 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders.meta new file mode 100644 index 00000000..637b367b --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 0e962d8c7bd021a469f42e79cfac1b2c +folderAsset: yes +DefaultImporter: + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlender.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlender.cs new file mode 100644 index 00000000..36b9a01e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlender.cs @@ -0,0 +1,61 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + /// <summary> + /// A TextureBlender will attempt to blend non-texture properties with textures so that the result material looks the same as source material. + /// </summary> + public interface TextureBlender + { + /// <summary> + /// The shader name that must be matched on the result material in order for this TextureBlender to be used. This should return something like "Legacy/Bumped Difuse" + /// </summary> + bool DoesShaderNameMatch(string shaderName); + + /// <summary> + /// This is called to prepare the TextureBlender before any calls to OnBlendTexturePixel + /// Use this to grab the non-texture property values from the material that will be used to alter the Pixel color in the texture. + /// Note that the sourceMat may not use a shader matching ShaderName. It may not have expected properties. Check that properties exist + /// before grabing them. + /// </summary> + void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName); + + /// <summary> + /// Called once for each pixel in the texture to alter the pixel color. For efficiency don't check shaderPropertyName every call. Instead use OnBeforeTintTexture + /// to prepare this textrure blender for a batch of OnBlendTexturePixel calls. + /// </summary> + Color OnBlendTexturePixel(string shaderPropertyName, Color pixelColor); + + /// <summary> + /// Material a & b may have the same set of textures but different non-texture properties (colorTint etc...) + /// If so then they need to be put into separate rectangels in the atlas. This method should check the non-texture properties + /// and return false if they are different. Note that material a and b may use a different shader than GetShaderName so your code + /// should handle the case where properties do not exist. + /// </summary> + bool NonTexturePropertiesAreEqual(Material a, Material b); + + /// <summary> + /// Sets the non texture properties on the result materail after textures have been baked. If for example _Color has been blended with + /// the _Albedo textures then the _Color property on the result material should probably be set to white. + /// </summary> + void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial); + + /// <summary> + /// Some textures may not be assigned for a material. This method should return a color that will used to create a small solid color texture + /// to be used in these cases. Note that this small solid color texture will later be blended using OnBlendTexturePixel. If the texturePropertyname is _mainTex + /// then the the returned color should probably be white so it looks correct when OnBlendTexturePixel blends the _Color. + /// + /// This is also used to determine if an atlas needs to be generated for a texture property. If all the source materials are missing the texture for + /// texPropertyName property (eg. _MainTex), but some of the source materials return different value for: + /// OnBlendTexturePixel(texturePropertyName, GetColorIfNoTexture(sourceMat, texturePropertyName)) + /// Then an atlas will be generated with the different colors. + /// + /// This method can also be used to collect the value of non texture properties and cache them for each source material. This information can be useful + /// for setting values on the result material. + /// </summary> + Color GetColorIfNoTexture(Material m, ShaderTextureProperty texPropertyName); + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlender.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlender.cs.meta new file mode 100644 index 00000000..d2e044be --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlender.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 646e6367ec395784ba07a11709c5dbc9 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderFallback.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderFallback.cs new file mode 100644 index 00000000..efeda5be --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderFallback.cs @@ -0,0 +1,243 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderFallback : TextureBlender + { + bool m_doTintColor = false; + Color m_tintColor; + + Color m_defaultColor = Color.white; + + public bool DoesShaderNameMatch(string shaderName) + { + return true; //matches everything + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (shaderTexturePropertyName.Equals("_MainTex")) + { + m_doTintColor = true; + m_tintColor = Color.white; + if (sourceMat.HasProperty("_Color")) + { + m_tintColor = sourceMat.GetColor("_Color"); + } + else if (sourceMat.HasProperty("_TintColor")) + { + m_tintColor = sourceMat.GetColor("_TintColor"); + } + } else + { + m_doTintColor = false; + } + } + + public Color OnBlendTexturePixel(string shaderPropertyName, Color pixelColor) + { + if (m_doTintColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + if (a.HasProperty("_Color")) + { + if (_compareColor(a, b, m_defaultColor, "_Color")) + { + return true; + } + //return false; + } + else if (a.HasProperty("_TintColor")) + { + if (_compareColor(a, b, m_defaultColor, "_TintColor")) + { + return true; + } + //return false; + } + return false; + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + if (resultMaterial.HasProperty("_Color")) + { + resultMaterial.SetColor("_Color", m_defaultColor); + } + else if (resultMaterial.HasProperty("_TintColor")) + { + resultMaterial.SetColor("_TintColor", m_defaultColor); + } + } + + public Color GetColorIfNoTexture(Material mat, ShaderTextureProperty texProperty) + { + if (texProperty.isNormalMap) + { + return new Color(.5f, .5f, 1f); + } + else if (texProperty.name.Equals("_MainTex")) + { + if (mat != null && mat.HasProperty("_Color")) + { + try + { //need try because can't garantee _Color is a color + return mat.GetColor("_Color"); + } + catch (Exception) { } + } + else if (mat != null && mat.HasProperty("_TintColor")) + { + try + { //need try because can't garantee _TintColor is a color + return mat.GetColor("_TintColor"); + } + catch (Exception) { } + } + } + else if (texProperty.name.Equals("_SpecGlossMap")) + { + if (mat != null && mat.HasProperty("_SpecColor")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_SpecColor"); + if (mat.HasProperty("_Glossiness")) + { + try + { + c.a = mat.GetFloat("_Glossiness"); + } + catch (Exception) { } + } + Debug.LogWarning(c); + return c; + } + catch (Exception) { } + } + } + else if (texProperty.name.Equals("_MetallicGlossMap")) + { + if (mat != null && mat.HasProperty("_Metallic")) + { + try + { //need try because can't garantee _Metallic is a float + float v = mat.GetFloat("_Metallic"); + Color c = new Color(v, v, v); + if (mat.HasProperty("_Glossiness")) + { + try + { + c.a = mat.GetFloat("_Glossiness"); + } + catch (Exception) { } + } + return c; + } + catch (Exception) { } + } + } + else if (texProperty.name.Equals("_ParallaxMap")) + { + return new Color(0f, 0f, 0f, 0f); + } + else if (texProperty.name.Equals("_OcclusionMap")) + { + return new Color(1f, 1f, 1f, 1f); + } + else if (texProperty.name.Equals("_EmissionMap")) + { + if (mat != null) + { + if (mat.HasProperty("_EmissionScaleUI")) + { + //Standard shader has weird behavior if EmissionMap has never + //been set then no EmissionColorUI color picker. If has ever + //been set then is EmissionColorUI color picker. + if (mat.HasProperty("_EmissionColor") && + mat.HasProperty("_EmissionColorUI")) + { + try + { + Color c1 = mat.GetColor("_EmissionColor"); + Color c2 = mat.GetColor("_EmissionColorUI"); + float f = mat.GetFloat("_EmissionScaleUI"); + if (c1 == new Color(0f, 0f, 0f, 0f) && + c2 == new Color(1f, 1f, 1f, 1f)) + { + //is virgin Emission values + return new Color(f, f, f, f); + } + else { //non virgin Emission values + return c2; + } + } + catch (Exception) { } + + } + else { + try + { //need try because can't garantee _Color is a color + float f = mat.GetFloat("_EmissionScaleUI"); + return new Color(f, f, f, f); + } + catch (Exception) { } + } + } + } + } + else if (texProperty.name.Equals("_DetailMask")) + { + return new Color(0f, 0f, 0f, 0f); + } + return new Color(1f, 1f, 1f, 0f); + } + + public static bool _compareColor(Material a, Material b, Color defaultVal, string propertyName) + { + Color aColor = defaultVal; + Color bColor = defaultVal; + if (a.HasProperty(propertyName)) + { + aColor = a.GetColor(propertyName); + } + if (b.HasProperty(propertyName)) + { + bColor = b.GetColor(propertyName); + } + if (aColor != bColor) + { + return false; + } + return true; + } + + public static bool _compareFloat(Material a, Material b, float defaultVal, string propertyName) + { + float aFloat = defaultVal; + float bFloat = defaultVal; + if (a.HasProperty(propertyName)) + { + aFloat = a.GetFloat(propertyName); + } + if (b.HasProperty(propertyName)) + { + bFloat = b.GetFloat(propertyName); + } + if (aFloat != bFloat) + { + return false; + } + return true; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderFallback.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderFallback.cs.meta new file mode 100644 index 00000000..db6067c9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderFallback.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 71f9f92825289a14d83f3c273044b8e6 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyBumpDiffuse.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyBumpDiffuse.cs new file mode 100644 index 00000000..fb47947c --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyBumpDiffuse.cs @@ -0,0 +1,75 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderLegacyBumpDiffuse : TextureBlender + { + bool doColor; + Color m_tintColor; + Color m_defaultTintColor = Color.white; + + public bool DoesShaderNameMatch(string shaderName) + { + if (shaderName.Equals ("Legacy Shaders/Bumped Diffuse")) { + return true; + } else if (shaderName.Equals ("Bumped Diffuse")) { + return true; + } + return false; + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (shaderTexturePropertyName.EndsWith("_MainTex")) + { + doColor = true; + m_tintColor = sourceMat.GetColor("_Color"); + } else + { + doColor = false; + } + } + + public Color OnBlendTexturePixel(string propertyToDoshaderPropertyName, Color pixelColor) + { + if (doColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + return TextureBlenderFallback._compareColor(a, b, m_defaultTintColor, "_Color"); + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + resultMaterial.SetColor("_Color", Color.white); + } + + public Color GetColorIfNoTexture(Material m, ShaderTextureProperty texPropertyName) + { + if (texPropertyName.name.Equals("_BumpMap")) + { + return new Color(.5f, .5f, 1f); + } + if (texPropertyName.name.Equals("_MainTex")) + { + if (m != null && m.HasProperty("_Color")) + { + try + { //need try because can't garantee _Color is a color + return m.GetColor("_Color"); + } + catch (Exception) { } + } + } + return new Color(1,1,1,0); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyBumpDiffuse.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyBumpDiffuse.cs.meta new file mode 100644 index 00000000..91f648f2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyBumpDiffuse.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4157773ac1b12a94a83d67cdbc18b534 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyDiffuse.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyDiffuse.cs new file mode 100644 index 00000000..b1c3305c --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyDiffuse.cs @@ -0,0 +1,71 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderLegacyDiffuse : TextureBlender + { + bool doColor; + Color m_tintColor; + Color m_defaultTintColor = Color.white; + + public bool DoesShaderNameMatch(string shaderName) + { + if (shaderName.Equals ("Legacy Shaders/Diffuse")) { + return true; + } else if (shaderName.Equals ("Diffuse")) { + return true; + } + return false; + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (shaderTexturePropertyName.EndsWith("_MainTex")) + { + doColor = true; + m_tintColor = sourceMat.GetColor("_Color"); + } else + { + doColor = false; + } + } + + public Color OnBlendTexturePixel(string propertyToDoshaderPropertyName, Color pixelColor) + { + if (doColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + return TextureBlenderFallback._compareColor(a, b, m_defaultTintColor, "_Color"); + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + resultMaterial.SetColor("_Color", Color.white); + } + + public Color GetColorIfNoTexture(Material m, ShaderTextureProperty texPropertyName) + { + if (texPropertyName.name.Equals("_MainTex")) + { + if (m != null && m.HasProperty("_Color")) + { + try + { //need try because can't garantee _Color is a color + return m.GetColor("_Color"); + } + catch (Exception) { } + } + } + return new Color(1,1,1,0); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyDiffuse.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyDiffuse.cs.meta new file mode 100644 index 00000000..1bbd92f9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderLegacyDiffuse.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c86cc13385933e94a915ee9b0520efcb +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderMaterialPropertyCacheHelper.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderMaterialPropertyCacheHelper.cs new file mode 100644 index 00000000..ebaf3cba --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderMaterialPropertyCacheHelper.cs @@ -0,0 +1,84 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderMaterialPropertyCacheHelper + { + private struct MaterialPropertyPair + { + public Material material; + public string property; + + public MaterialPropertyPair(Material m, string prop) + { + material = m; + property = prop; + } + + public override bool Equals(object obj) + { + if (!(obj is MaterialPropertyPair)) return false; + MaterialPropertyPair b = (MaterialPropertyPair)obj; + if (!material.Equals(b.material)) return false; + if (property != b.property) return false; + return true; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + private Dictionary<MaterialPropertyPair, object> nonTexturePropertyValuesForSourceMaterials = new Dictionary<MaterialPropertyPair, object>(); + + private bool AllNonTexturePropertyValuesAreEqual(string prop) + { + bool foundFirst = false; + object firstVal = null; + foreach (MaterialPropertyPair k in nonTexturePropertyValuesForSourceMaterials.Keys) + { + if (k.property.Equals(prop)) + { + if (!foundFirst) + { + firstVal = nonTexturePropertyValuesForSourceMaterials[k]; + foundFirst = true; + } + else + { + if (!firstVal.Equals(nonTexturePropertyValuesForSourceMaterials[k])) + { + return false; + } + } + } + } + return true; + } + + public void CacheMaterialProperty(Material m, string property, object value) + { + nonTexturePropertyValuesForSourceMaterials[new MaterialPropertyPair(m, property)] = value; + } + + public object GetValueIfAllSourceAreTheSameOrDefault(string property, object defaultValue) + { + if (AllNonTexturePropertyValuesAreEqual(property)) + { + foreach (MaterialPropertyPair k in nonTexturePropertyValuesForSourceMaterials.Keys) + { + if (k.property.Equals(property)) + { + return nonTexturePropertyValuesForSourceMaterials[k]; + } + } + } + + return defaultValue; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderMaterialPropertyCacheHelper.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderMaterialPropertyCacheHelper.cs.meta new file mode 100644 index 00000000..7a2239a6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderMaterialPropertyCacheHelper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6ee4be839d395884797ef7560cd1d58c +timeCreated: 1524701272 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallic.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallic.cs new file mode 100644 index 00000000..d3eadb8a --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallic.cs @@ -0,0 +1,343 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderStandardMetallic : TextureBlender + { + static Color NeutralNormalMap = new Color(.5f, .5f, 1f); + + private enum Prop{ + doColor, + doMetallic, + doEmission, + doBump, + doNone, + } + + // This is used to cache the non texture property values. If all non-texutre property values are the same for a property for all source textures + // then the source value will be re-used + TextureBlenderMaterialPropertyCacheHelper sourceMaterialPropertyCache = new TextureBlenderMaterialPropertyCacheHelper(); + + // These are cached values read in OnBeforeTintTexture and used when blending pixels. + Color m_tintColor; + float m_glossiness; + float m_glossMapScale; + float m_metallic; + bool m_hasMetallicGlossMap; + float m_bumpScale; + bool m_shaderDoesEmission; + Color m_emissionColor; + + // This just makes things more efficient so we arn't doing a string comparison for each pixel. + Prop propertyToDo = Prop.doNone; + + // These are the property values that will be assigned to the result material if + // generating an atlas for those properties. + Color m_generatingTintedAtlasColor = Color.white; + float m_generatingTintedAtlasMetallic = 0f; + float m_generatingTintedAtlasGlossiness = 1f; + float m_generatingTintedAtlasGlossMapScale = 1f; + float m_generatingTintedAtlasBumpScale = 1f; + Color m_generatingTintedAtlasEmission = Color.white; + + // These are the default property values that will be assigned to the result materials if + // none of the source materials have a value for these properties. + Color m_notGeneratingAtlasDefaultColor = Color.white; + float m_notGeneratingAtlasDefaultMetallic = 0; + float m_notGeneratingAtlasDefaultGlossiness = .5f; + Color m_notGeneratingAtlasDefaultEmisionColor = Color.black; + + public bool DoesShaderNameMatch(string shaderName) + { + return shaderName.Equals("Standard") || shaderName.EndsWith("StandardTextureArray"); + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (shaderTexturePropertyName.Equals("_MainTex")) + { + propertyToDo = Prop.doColor; + if (sourceMat.HasProperty("_Color")) + { + m_tintColor = sourceMat.GetColor("_Color"); + } else + { + m_tintColor = m_generatingTintedAtlasColor; + } + } else if (shaderTexturePropertyName.Equals("_MetallicGlossMap")) + { + propertyToDo = Prop.doMetallic; + m_metallic = m_generatingTintedAtlasMetallic; + if (sourceMat.GetTexture("_MetallicGlossMap") != null) + { + m_hasMetallicGlossMap = true; + } else + { + m_hasMetallicGlossMap = false; + } + + if (sourceMat.HasProperty("_Metallic")) + { + m_metallic = sourceMat.GetFloat("_Metallic"); + } else + { + m_metallic = 0f; + } + + if (sourceMat.HasProperty("_GlossMapScale")) + { + m_glossMapScale = sourceMat.GetFloat("_GlossMapScale"); + } else + { + m_glossMapScale = 1f; + } + + if (sourceMat.HasProperty("_Glossiness")) + { + m_glossiness = sourceMat.GetFloat("_Glossiness"); + } else + { + m_glossiness = 0f; + } + + } else if (shaderTexturePropertyName.Equals("_BumpMap")) + { + propertyToDo = Prop.doBump; + if (sourceMat.HasProperty(shaderTexturePropertyName)) + { + if (sourceMat.HasProperty("_BumpScale")) + m_bumpScale = sourceMat.GetFloat("_BumpScale"); + } + else + { + m_bumpScale = m_generatingTintedAtlasBumpScale; + } + + } else if (shaderTexturePropertyName.Equals("_EmissionMap")) + { + propertyToDo = Prop.doEmission; + m_shaderDoesEmission = sourceMat.IsKeywordEnabled("_EMISSION"); + if (sourceMat.HasProperty("_EmissionColor")) { + m_emissionColor = sourceMat.GetColor("_EmissionColor"); + } else + { + m_emissionColor = m_notGeneratingAtlasDefaultEmisionColor; + } + + } else + { + propertyToDo = Prop.doNone; + } + } + + public Color OnBlendTexturePixel(string propertyToDoshaderPropertyName, Color pixelColor) + { + if (propertyToDo == Prop.doColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } + else if (propertyToDo == Prop.doMetallic) + { + if (m_hasMetallicGlossMap) + { + return pixelColor = new Color(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a * m_glossMapScale); + } + else + { + return new Color(m_metallic, 0, 0, m_glossiness); + } + } + else if (propertyToDo == Prop.doBump) + { + return Color.Lerp(NeutralNormalMap, pixelColor, m_bumpScale); + } + else if (propertyToDo == Prop.doEmission) + { + if (m_shaderDoesEmission) + { + return new Color(pixelColor.r * m_emissionColor.r, pixelColor.g * m_emissionColor.g, pixelColor.b * m_emissionColor.b, pixelColor.a * m_emissionColor.a); + } + else + { + return Color.black; + } + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + if (!TextureBlenderFallback._compareColor(a, b, m_notGeneratingAtlasDefaultColor, "_Color")) + { + return false; + } + + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultGlossiness, "_Glossiness")) + { + return false; + } + + bool aHasMetallicTex = a.HasProperty("_MetallicGlossMap") && a.GetTexture("_MetallicGlossMap") != null; + bool bHasMetallicTex = b.HasProperty("_MetallicGlossMap") && b.GetTexture("_MetallicGlossMap") != null; + if (aHasMetallicTex && bHasMetallicTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultMetallic, "_GlossMapScale")) + { + return false; + } + } else if (!aHasMetallicTex && !bHasMetallicTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultMetallic, "_Metallic")) + { + return false; + } + } else + { + return false; + } + + if (a.IsKeywordEnabled("_EMISSION") != b.IsKeywordEnabled("_EMISSION")) + { + return false; + } + if (a.IsKeywordEnabled("_EMISSION")) + { + if (!TextureBlenderFallback._compareColor(a, b, m_notGeneratingAtlasDefaultEmisionColor, "_EmissionColor")) + { + return false; + } + } + return true; + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + if (resultMaterial.GetTexture("_MainTex") != null) + { + resultMaterial.SetColor("_Color", m_generatingTintedAtlasColor); + } else { + resultMaterial.SetColor("_Color", (Color) sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Color", m_notGeneratingAtlasDefaultColor)); + } + + if (resultMaterial.GetTexture("_MetallicGlossMap") != null) + { + resultMaterial.SetFloat("_Metallic", m_generatingTintedAtlasMetallic); + resultMaterial.SetFloat("_GlossMapScale", m_generatingTintedAtlasGlossMapScale); + resultMaterial.SetFloat("_Glossiness", m_generatingTintedAtlasGlossiness); + } else + { + resultMaterial.SetFloat("_Metallic", (float) sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Metallic", m_notGeneratingAtlasDefaultMetallic)); + resultMaterial.SetFloat("_Glossiness", (float) sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Glossiness", m_notGeneratingAtlasDefaultGlossiness)); + } + + if (resultMaterial.GetTexture("_BumpMap") != null) + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlasBumpScale); + } + + if (resultMaterial.GetTexture("_EmissionMap") != null) + { + resultMaterial.EnableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", m_generatingTintedAtlasEmission); + } + else { + resultMaterial.DisableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", (Color) sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_EmissionColor", m_notGeneratingAtlasDefaultEmisionColor)); + } + } + + + public Color GetColorIfNoTexture(Material mat, ShaderTextureProperty texPropertyName) + { + if (texPropertyName.name.Equals("_BumpMap")) + { + return new Color(.5f, .5f, 1f); + } + else if (texPropertyName.name.Equals("_MainTex")) + { + if (mat != null && mat.HasProperty("_Color")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_Color"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Color", c); + return c; + } + catch (Exception) { } + return Color.white; + } + } + else if (texPropertyName.name.Equals("_MetallicGlossMap")) + { + if (mat != null && mat.HasProperty("_Metallic")) + { + try + { //need try because can't garantee _Metallic is a float + float v = mat.GetFloat("_Metallic"); + Color c = new Color(v, v, v); + if (mat.HasProperty("_Glossiness")) + { + try + { + c.a = mat.GetFloat("_Glossiness"); + } + + catch (Exception) { } + } + + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Metallic", v); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Glossiness", c.a); + return c; + } + catch (Exception) { } + return new Color(0f, 0f, 0f, .5f); + } else + { + return new Color(0f,0f,0f,.5f); + } + } + else if (texPropertyName.name.Equals("_ParallaxMap")) + { + return new Color(0f, 0f, 0f, 0f); + } + else if (texPropertyName.name.Equals("_OcclusionMap")) + { + return new Color(1f, 1f, 1f, 1f); + } + else if (texPropertyName.name.Equals("_EmissionMap")) + { + if (mat != null) + { + if (mat.IsKeywordEnabled("_EMISSION")) + { + if (mat.HasProperty("_EmissionColor")) + { + try + { + Color c = mat.GetColor("_EmissionColor"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_EmissionColor", c); + return c; + } + catch (Exception) { } + } + else + { + return Color.black; + } + } else + { + return Color.black; + } + } + } + else if (texPropertyName.name.Equals("_DetailMask")) + { + return new Color(0f, 0f, 0f, 0f); + } + return new Color(1f, 1f, 1f, 0f); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallic.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallic.cs.meta new file mode 100644 index 00000000..967edfc5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallic.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ad6e327911774fb4d9cf5c3b4c233d15 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallicRoughness.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallicRoughness.cs new file mode 100644 index 00000000..3fae60ad --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallicRoughness.cs @@ -0,0 +1,397 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderStandardMetallicRoughness : TextureBlender + { + static Color NeutralNormalMap = new Color(.5f, .5f, 1f); + + private enum Prop{ + doColor, + doMetallic, + doRoughness, + doEmission, + doBump, + doNone, + } + + // This is used to cache the non texture property values. If all non-texutre property values are the same for a property for all source textures + // then the source value will be re-used + TextureBlenderMaterialPropertyCacheHelper sourceMaterialPropertyCache = new TextureBlenderMaterialPropertyCacheHelper(); + + // These are cached values read in OnBeforeTintTexture and used when blending pixels. + Color m_tintColor; + float m_roughness; + float m_metallic; + bool m_hasMetallicGlossMap; + bool m_hasSpecGlossMap; + float m_bumpScale; + bool m_shaderDoesEmission; + Color m_emissionColor; + + // This just makes things more efficient so we arn't doing a string comparison for each pixel. + Prop propertyToDo = Prop.doNone; + + // These are the property values that will be assigned to the result material if + // generating an atlas for those properties. + Color m_generatingTintedAtlasColor = Color.white; + float m_generatingTintedAtlasMetallic = 0f; + float m_generatingTintedAtlasRoughness = .5f; + float m_generatingTintedAtlasBumpScale = 1f; + Color m_generatingTintedAtlasEmission = Color.white; + + // These are the default property values that will be assigned to the result materials if + // none of the source materials have a value for these properties. + Color m_notGeneratingAtlasDefaultColor = Color.white; + float m_notGeneratingAtlasDefaultMetallic = 0; + float m_notGeneratingAtlasDefaultGlossiness = .5f; + Color m_notGeneratingAtlasDefaultEmisionColor = Color.black; + + public bool DoesShaderNameMatch(string shaderName) + { + return shaderName.Equals("Standard (Roughness setup)"); + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (shaderTexturePropertyName.Equals("_MainTex")) + { + propertyToDo = Prop.doColor; + if (sourceMat.HasProperty("_Color")) + { + m_tintColor = sourceMat.GetColor("_Color"); + } else + { + m_tintColor = m_generatingTintedAtlasColor; + } + } else if (shaderTexturePropertyName.Equals("_MetallicGlossMap")) + { + propertyToDo = Prop.doMetallic; + m_metallic = m_generatingTintedAtlasMetallic; + if (sourceMat.GetTexture("_MetallicGlossMap") != null) + { + m_hasMetallicGlossMap = true; + } else + { + m_hasMetallicGlossMap = false; + } + + if (sourceMat.HasProperty("_Metallic")) + { + m_metallic = sourceMat.GetFloat("_Metallic"); + } else + { + m_metallic = 0f; + } + } + else if (shaderTexturePropertyName.Equals("_SpecGlossMap")) + { + propertyToDo = Prop.doRoughness; + m_roughness = m_generatingTintedAtlasRoughness; + if (sourceMat.GetTexture("_SpecGlossMap") != null) + { + m_hasSpecGlossMap = true; + } + else + { + m_hasSpecGlossMap = false; + } + + if (sourceMat.HasProperty("_Glossiness")) + { + m_roughness = sourceMat.GetFloat("_Glossiness"); + } + else + { + m_roughness = 1f; + } + } + else if (shaderTexturePropertyName.Equals("_BumpMap")) + { + propertyToDo = Prop.doBump; + if (sourceMat.HasProperty(shaderTexturePropertyName)) + { + if (sourceMat.HasProperty("_BumpScale")) + m_bumpScale = sourceMat.GetFloat("_BumpScale"); + } + else + { + m_bumpScale = m_generatingTintedAtlasBumpScale; + } + + } else if (shaderTexturePropertyName.Equals("_EmissionMap")) + { + propertyToDo = Prop.doEmission; + m_shaderDoesEmission = sourceMat.IsKeywordEnabled("_EMISSION"); + if (sourceMat.HasProperty("_EmissionColor")) + { + m_emissionColor = sourceMat.GetColor("_EmissionColor"); + } + else + { + m_emissionColor = m_notGeneratingAtlasDefaultEmisionColor; + } + + } else + { + propertyToDo = Prop.doNone; + } + } + + public Color OnBlendTexturePixel(string propertyToDoshaderPropertyName, Color pixelColor) + { + if (propertyToDo == Prop.doColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } else if (propertyToDo == Prop.doMetallic) + { + if (m_hasMetallicGlossMap) + { + return pixelColor; + } + else + { + return new Color(m_metallic, 0, 0, m_roughness); + } + } else if (propertyToDo == Prop.doRoughness) + { + if (m_hasSpecGlossMap) + { + return pixelColor; + } else + { + return new Color(m_roughness, 0, 0, 0); + } + } else if (propertyToDo == Prop.doBump) + { + return Color.Lerp(NeutralNormalMap, pixelColor, m_bumpScale); + } else if (propertyToDo == Prop.doEmission) + { + if (m_shaderDoesEmission) + { + return new Color(pixelColor.r * m_emissionColor.r, pixelColor.g * m_emissionColor.g, pixelColor.b * m_emissionColor.b, pixelColor.a * m_emissionColor.a); + } + else + { + return Color.black; + } + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + if (!TextureBlenderFallback._compareColor(a, b, m_notGeneratingAtlasDefaultColor, "_Color")) + { + return false; + } + + bool aHasMetallicTex = a.HasProperty("_MetallicGlossMap") && a.GetTexture("_MetallicGlossMap") != null; + bool bHasMetallicTex = b.HasProperty("_MetallicGlossMap") && b.GetTexture("_MetallicGlossMap") != null; + + if (!aHasMetallicTex && !bHasMetallicTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultMetallic, "_Metallic")) + { + return false; + } + } + else + { + return false; + } + + bool aHasSpecTex = a.HasProperty("_SpecGlossMap") && a.GetTexture("_SpecGlossMap") != null; + bool bHasSpecTex = b.HasProperty("_SpecGlossMap") && b.GetTexture("_SpecGlossMap") != null; + if (!aHasSpecTex && !bHasSpecTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlasRoughness, "_Glossiness")) + { + return false; + } + } + else + { + return false; + } + + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlasBumpScale, "_bumpScale")) + { + return false; + } + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlasRoughness, "_Glossiness")) + { + return false; + } + if (a.IsKeywordEnabled("_EMISSION") != b.IsKeywordEnabled("_EMISSION")) + { + return false; + } + if (a.IsKeywordEnabled("_EMISSION")) + { + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlasEmission, "_EmissionColor")) + { + return false; + } + } + return true; + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + if (resultMaterial.GetTexture("_MainTex") != null) + { + resultMaterial.SetColor("_Color", m_generatingTintedAtlasColor); + } + else + { + resultMaterial.SetColor("_Color", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Color", m_notGeneratingAtlasDefaultColor)); + } + + if (resultMaterial.GetTexture("_MetallicGlossMap") != null) + { + resultMaterial.SetFloat("_Metallic", m_generatingTintedAtlasMetallic); + } + else + { + resultMaterial.SetFloat("_Metallic", (float)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Metallic", m_notGeneratingAtlasDefaultMetallic)); + } + + + if (resultMaterial.GetTexture("_SpecGlossMap") != null) + { + + } + else + { + resultMaterial.SetFloat("_Glossiness", (float)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Glossiness", m_notGeneratingAtlasDefaultGlossiness)); + } + + if (resultMaterial.GetTexture("_BumpMap") != null) + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlasBumpScale); + } + else + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlasBumpScale); + } + + if (resultMaterial.GetTexture("_EmissionMap") != null) + { + resultMaterial.EnableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", Color.white); + } + else + { + resultMaterial.DisableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_EmissionColor", m_notGeneratingAtlasDefaultEmisionColor)); + } + } + + public Color GetColorIfNoTexture(Material mat, ShaderTextureProperty texPropertyName) + { + if (texPropertyName.name.Equals("_BumpMap")) + { + return new Color(.5f, .5f, 1f); + } + else if (texPropertyName.name.Equals("_MainTex")) + { + if (mat != null && mat.HasProperty("_Color")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_Color"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Color", c); + return c; + } + catch (Exception) { } + return Color.white; + } + } + else if (texPropertyName.name.Equals("_MetallicGlossMap")) + { + if (mat != null && mat.HasProperty("_Metallic")) + { + try + { //need try because can't garantee _Metallic is a float + float v = mat.GetFloat("_Metallic"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Metallic", v); + } + catch (Exception) { } + return new Color(0f, 0f, 0f, .5f); + } else + { + return new Color(0f,0f,0f,.5f); + } + } + else if (texPropertyName.name.Equals("_SpecGlossMap")) + { + bool success = false; + + try + { //need try because can't garantee _Color is a color + Color c = new Color(0f, 0f, 0f, .5f); + if (mat.HasProperty("_Glossiness")) + { + try + { + success = true; + c.a = mat.GetFloat("_Glossiness"); + } + catch (Exception) { } + } + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Glossiness", c.a); + return new Color(0f, 0f, 0f, .5f); + } + catch (Exception) { } + if (!success) + { + return new Color(0f, 0f, 0f, .5f); + } + } + else if (texPropertyName.name.Equals("_ParallaxMap")) + { + return new Color(0f, 0f, 0f, 0f); + } + else if (texPropertyName.name.Equals("_OcclusionMap")) + { + return new Color(1f, 1f, 1f, 1f); + } + else if (texPropertyName.name.Equals("_EmissionMap")) + { + if (mat != null) + { + if (mat.IsKeywordEnabled("_EMISSION")) + { + if (mat.HasProperty("_EmissionColor")) + { + try + { + Color c = mat.GetColor("_EmissionColor"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_EmissionColor", c); + return c; + } + catch (Exception) { } + } + else + { + return Color.black; + } + } + else + { + return Color.black; + } + } + } + else if (texPropertyName.name.Equals("_DetailMask")) + { + return new Color(0f, 0f, 0f, 0f); + } + return new Color(1f, 1f, 1f, 0f); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallicRoughness.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallicRoughness.cs.meta new file mode 100644 index 00000000..0afcdc18 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardMetallicRoughness.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 93f246464bc2f0e468fdc4761cc80c6c +timeCreated: 1524695231 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardSpecular.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardSpecular.cs new file mode 100644 index 00000000..dd91a79f --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardSpecular.cs @@ -0,0 +1,359 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderStandardSpecular : TextureBlender + { + static Color NeutralNormalMap = new Color(.5f, .5f, 1f); + + private enum Prop + { + doColor, + doSpecular, + doEmission, + doBump, + doNone, + } + + // This is used to cache the non texture property values. If all non-texutre property values are the same for a property for all source textures + // then the source value will be re-used + TextureBlenderMaterialPropertyCacheHelper sourceMaterialPropertyCache = new TextureBlenderMaterialPropertyCacheHelper(); + + // These are cached values read in OnBeforeTintTexture and used when blending pixels. + Color m_tintColor; + float m_glossiness; + float m_SpecGlossMapScale; + Color m_specColor; + bool m_hasSpecGlossMap; + float m_bumpScale; + bool m_shaderDoesEmission; + Color m_emissionColor; + + // This just makes things more efficient so we arn't doing a string comparison for each pixel. + Prop propertyToDo = Prop.doNone; + + // These are the property values that will be assigned to the result material if + // generating an atlas for those properties. + Color m_generatingTintedAtlaColor = Color.white; + Color m_generatingTintedAtlaSpecular = Color.black; + float m_generatingTintedAtlaGlossiness = 1f; + float m_generatingTintedAtlaSpecGlossMapScale = 1f; + float m_generatingTintedAtlaBumpScale = 1f; + Color m_generatingTintedAtlaEmission = Color.white; + + // These are the default property values that will be assigned to the result materials if + // none of the source materials have a value for these properties. + Color m_notGeneratingAtlasDefaultColor = Color.white; + Color m_notGeneratingAtlasDefaultSpecularColor = new Color(0f,0f,0f,1f); + float m_notGeneratingAtlasDefaultGlossiness = .5f; + Color m_notGeneratingAtlasDefaultEmisionColor = Color.black; + + public bool DoesShaderNameMatch(string shaderName) + { + return shaderName.Equals("Standard (Specular setup)"); + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (shaderTexturePropertyName.Equals("_MainTex")) + { + propertyToDo = Prop.doColor; + if (sourceMat.HasProperty("_Color")) + { + m_tintColor = sourceMat.GetColor("_Color"); + } + else + { + m_tintColor = m_generatingTintedAtlaColor; + } + } + else if (shaderTexturePropertyName.Equals("_SpecGlossMap")) + { + propertyToDo = Prop.doSpecular; + m_specColor = m_generatingTintedAtlaSpecular; + if (sourceMat.GetTexture("_SpecGlossMap") != null) + { + m_hasSpecGlossMap = true; + } + else + { + m_hasSpecGlossMap = false; + } + + if (sourceMat.HasProperty("_SpecColor")) + { + m_specColor = sourceMat.GetColor("_SpecColor"); + } else + { + m_specColor = new Color(0f, 0f, 0f, 1f); + } + + if (sourceMat.HasProperty("_GlossMapScale")) + { + m_SpecGlossMapScale = sourceMat.GetFloat("_GlossMapScale"); + } + else + { + m_SpecGlossMapScale = 1f; + } + + if (sourceMat.HasProperty("_Glossiness")) + { + m_glossiness = sourceMat.GetFloat("_Glossiness"); + } + else + { + m_glossiness = 0f; + } + } else if (shaderTexturePropertyName.Equals("_BumpMap")) + { + propertyToDo = Prop.doBump; + if (sourceMat.HasProperty(shaderTexturePropertyName)) + { + if (sourceMat.HasProperty("_BumpScale")) + m_bumpScale = sourceMat.GetFloat("_BumpScale"); + } + else + { + m_bumpScale = m_generatingTintedAtlaBumpScale; + } + } else if (shaderTexturePropertyName.Equals("_EmissionMap")) + { + propertyToDo = Prop.doEmission; + m_shaderDoesEmission = sourceMat.IsKeywordEnabled("_EMISSION"); + if (sourceMat.HasProperty("_EmissionColor")) { + m_emissionColor = sourceMat.GetColor("_EmissionColor"); + } else + { + m_generatingTintedAtlaColor = m_notGeneratingAtlasDefaultEmisionColor; + } + } else + { + propertyToDo = Prop.doNone; + } + } + + + public Color OnBlendTexturePixel(string propertyToDoshaderPropertyName, Color pixelColor) + { + if (propertyToDo == Prop.doColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } + else if (propertyToDo == Prop.doSpecular) + { + if (m_hasSpecGlossMap) + { + return pixelColor = new Color(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a * m_SpecGlossMapScale); + } + else + { + Color c = m_specColor; + c.a = m_glossiness; + return c; + } + } + else if (propertyToDo == Prop.doBump) + { + return Color.Lerp(NeutralNormalMap, pixelColor, m_bumpScale); + } + else if (propertyToDo == Prop.doEmission) + { + if (m_shaderDoesEmission) + { + return new Color(pixelColor.r * m_emissionColor.r, pixelColor.g * m_emissionColor.g, pixelColor.b * m_emissionColor.b, pixelColor.a * m_emissionColor.a); + } + else + { + return Color.black; + } + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlaColor, "_Color")) + { + return false; + } + + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlaSpecular, "_SpecColor")) + { + return false; + } + + bool aHasSpecTex = a.HasProperty("_SpecGlossMap") && a.GetTexture("_SpecGlossMap") != null; + bool bHasSpecTex = b.HasProperty("_SpecGlossMap") && b.GetTexture("_SpecGlossMap") != null; + + if (aHasSpecTex && bHasSpecTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlaSpecGlossMapScale, "_GlossMapScale")) + { + return false; + } + } + else if (!aHasSpecTex && !bHasSpecTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlaGlossiness, "_Glossiness")) + { + return false; + } + } + else + { + return false; + } + + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlaBumpScale, "_BumpScale")) + { + return false; + } + + if (a.IsKeywordEnabled("_EMISSION") != b.IsKeywordEnabled("_EMISSION")) + { + return false; + } + if (a.IsKeywordEnabled("_EMISSION")) + { + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlaEmission, "_EmissionColor")) + { + return false; + } + } + + return true; + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + if (resultMaterial.GetTexture("_MainTex") != null) + { + resultMaterial.SetColor("_Color", m_generatingTintedAtlaColor); + } + else + { + resultMaterial.SetColor("_Color", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Color", m_notGeneratingAtlasDefaultColor)); + } + + if (resultMaterial.GetTexture("_SpecGlossMap") != null) { + resultMaterial.SetColor("_SpecColor", m_generatingTintedAtlaSpecular); + resultMaterial.SetFloat("_GlossMapScale", m_generatingTintedAtlaSpecGlossMapScale); + resultMaterial.SetFloat("_Glossiness", m_generatingTintedAtlaGlossiness); + } else { + resultMaterial.SetColor("_SpecColor", (Color) sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_SpecColor", m_notGeneratingAtlasDefaultSpecularColor)); + resultMaterial.SetFloat("_Glossiness", (float) sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Glossiness", m_notGeneratingAtlasDefaultGlossiness)); + } + + if (resultMaterial.GetTexture("_BumpMap") != null) + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlaBumpScale); + } + else + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlaBumpScale); + } + + if (resultMaterial.GetTexture("_EmissionMap") != null) + { + resultMaterial.EnableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", Color.white); + } + else + { + resultMaterial.DisableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_EmissionColor", m_notGeneratingAtlasDefaultEmisionColor)); + } + } + + + public Color GetColorIfNoTexture(Material mat, ShaderTextureProperty texPropertyName) + { + if (texPropertyName.name.Equals("_BumpMap")) + { + return new Color(.5f, .5f, 1f); + } + else if (texPropertyName.name.Equals("_MainTex")) + { + if (mat != null && mat.HasProperty("_Color")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_Color"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Color", c); + return c; + } + catch (Exception) { } + return Color.white; + } + } + else if (texPropertyName.name.Equals("_SpecGlossMap")) + { + if (mat != null && mat.HasProperty("_SpecColor")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_SpecColor"); + if (mat.HasProperty("_Glossiness")) + { + try + { + c.a = mat.GetFloat("_Glossiness"); + } + catch (Exception) { } + } + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_SpecColor", c); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Glossiness", c.a); + return c; + } + catch (Exception) { } + } + return new Color(0f, 0f, 0f, .5f); + } + else if (texPropertyName.name.Equals("_ParallaxMap")) + { + return new Color(0f, 0f, 0f, 0f); + } + else if (texPropertyName.name.Equals("_OcclusionMap")) + { + return new Color(1f, 1f, 1f, 1f); + } + else if (texPropertyName.name.Equals("_EmissionMap")) + { + if (mat != null) + { + if (mat.IsKeywordEnabled("_EMISSION")) + { + if (mat.HasProperty("_EmissionColor")) + { + try + { + Color c = mat.GetColor("_EmissionColor"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_EmissionColor", c); + return c; + } + catch (Exception) { } + } + else + { + return Color.black; + } + } + else + { + return Color.black; + } + } + } + else if (texPropertyName.name.Equals("_DetailMask")) + { + return new Color(0f, 0f, 0f, 0f); + } + return new Color(1f, 1f, 1f, 0f); + } + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardSpecular.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardSpecular.cs.meta new file mode 100644 index 00000000..a58af881 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderStandardSpecular.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3a3737d1f36bf3b4c889c6f805d700bd +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderURPLit.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderURPLit.cs new file mode 100644 index 00000000..943d3109 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderURPLit.cs @@ -0,0 +1,588 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; + +namespace DigitalOpus.MB.Core +{ + public class TextureBlenderURPLit : TextureBlender + { + static Color NeutralNormalMap = new Color(.5f, .5f, 1f); + + private enum Prop + { + doColor, + doSpecular, + doMetallic, + doEmission, + doBump, + doNone, + } + + private enum WorkflowMode + { + unknown, + metallic, + specular, + } + + private enum SmoothnessTextureChannel + { + unknown, + albedo, + metallicSpecular, + } + + // This is used to cache the non texture property values. If all non-texutre property values are the same for a property for all source textures + // then the source value will be re-used + TextureBlenderMaterialPropertyCacheHelper sourceMaterialPropertyCache = new TextureBlenderMaterialPropertyCacheHelper(); + + WorkflowMode m_workflowMode = WorkflowMode.unknown; + + SmoothnessTextureChannel m_smoothnessTextureChannel = SmoothnessTextureChannel.unknown; + + // These are cached values read in OnBeforeTintTexture and used when blending pixels. + Color m_tintColor; + + float m_smoothness; // shared by both metallic maps and spec maps + + Color m_specColor; // Used if no spec map + bool m_hasSpecGlossMap; + + float m_metallic; // Used if no metallic map + bool m_hasMetallicGlossMap; + + float m_bumpScale; + + bool m_shaderDoesEmission; + Color m_emissionColor; + + // This just makes things more efficient so we arn't doing a string comparison for each pixel. + Prop propertyToDo = Prop.doNone; + + // These are the property values that will be assigned to the result material if + // generating an atlas for those properties. + Color m_generatingTintedAtlaColor = Color.white; + float m_generatingTintedAtlasMetallic = 0f; + Color m_generatingTintedAtlaSpecular = Color.black; + float m_generatingTintedAtlasMetallic_smoothness = 1f; + float m_generatingTintedAtlasSpecular_somoothness = 1f; + float m_generatingTintedAtlaBumpScale = 1f; + Color m_generatingTintedAtlaEmission = Color.white; + + // These are the default property values that will be assigned to the result materials if + // none of the source materials have a value for these properties. + Color m_notGeneratingAtlasDefaultColor = Color.white; + float m_notGeneratingAtlasDefaultMetallic = 0; + float m_notGeneratingAtlasDefaultSmoothness_MetallicWorkflow = 0f; + float m_notGeneratingAtlasDefaultSmoothness_SpecularWorkflow = 1f; + Color m_notGeneratingAtlasDefaultSpecularColor = new Color(.2f,.2f,.2f,1f); + Color m_notGeneratingAtlasDefaultEmisionColor = Color.black; + + public bool DoesShaderNameMatch(string shaderName) + { + return shaderName.Equals("Universal Render Pipeline/Lit") || + shaderName.Equals("Universal Render Pipeline/Simple Lit") || + shaderName.Equals("Universal Render Pipeline/Baked Lit"); + } + + private WorkflowMode _MapFloatToWorkflowMode(float workflowMode) + { + if (workflowMode == 0f) + { + return WorkflowMode.specular; + } + else + { + return WorkflowMode.metallic; + } + } + + private float _MapWorkflowModeToFloat(WorkflowMode workflowMode) + { + if (workflowMode == WorkflowMode.specular) + { + return 0f; + } else + { + return 1f; + } + } + + private SmoothnessTextureChannel _MapFloatToTextureChannel(float texChannel) + { + if (texChannel == 0f) + { + return SmoothnessTextureChannel.metallicSpecular; + } + else + { + return SmoothnessTextureChannel.albedo; + } + } + + private float _MapTextureChannelToFloat(SmoothnessTextureChannel workflowMode) + { + if (workflowMode == SmoothnessTextureChannel.metallicSpecular) + { + return 0f; + } + else + { + return 1f; + } + } + + public void OnBeforeTintTexture(Material sourceMat, string shaderTexturePropertyName) + { + if (m_workflowMode == WorkflowMode.unknown) + { + if (sourceMat.HasProperty("_WorkflowMode")) + { + m_workflowMode = _MapFloatToWorkflowMode(sourceMat.GetFloat("_WorkflowMode")); + } + } else + { + if (sourceMat.HasProperty("_WorkflowMode") && _MapFloatToWorkflowMode(sourceMat.GetFloat("_WorkflowMode")) != m_workflowMode) + { + Debug.LogError("Using the Universal Render Pipeline TextureBlender to blend non-texture-propertyes. Some of the source materials used different 'WorkflowModes'. These "+ + " cannot be blended properly. Results will be unpredictable."); + } + } + + if (m_smoothnessTextureChannel == SmoothnessTextureChannel.unknown) + { + if (sourceMat.HasProperty("_SmoothnessTextureChannel")) + { + m_smoothnessTextureChannel = _MapFloatToTextureChannel(sourceMat.GetFloat("_SmoothnessTextureChannel")); + } + } else + { + if (sourceMat.HasProperty("_SmoothnessTextureChannel") && _MapFloatToTextureChannel(sourceMat.GetFloat("_SmoothnessTextureChannel")) != m_smoothnessTextureChannel) + { + Debug.LogError("Using the Universal Render Pipeline TextureBlender to blend non-texture-properties. Some of the source materials store smoothness in the Albedo texture alpha" + + " and some source materials store smoothness in the Metallic/Specular texture alpha channel. The result material can only read smoothness from one or the other. Results will be unpredictable."); + } + } + + if (shaderTexturePropertyName.Equals("_BaseMap")) + { + propertyToDo = Prop.doColor; + if (sourceMat.HasProperty("_BaseColor")) + { + m_tintColor = sourceMat.GetColor("_BaseColor"); + } + else + { + m_tintColor = m_generatingTintedAtlaColor; + } + } + else if (shaderTexturePropertyName.Equals("_SpecGlossMap")) + { + propertyToDo = Prop.doSpecular; + m_specColor = m_generatingTintedAtlaSpecular; + if (sourceMat.GetTexture("_SpecGlossMap") != null) + { + m_hasSpecGlossMap = true; + } + else + { + m_hasSpecGlossMap = false; + } + + if (sourceMat.HasProperty("_SpecColor")) + { + m_specColor = sourceMat.GetColor("_SpecColor"); + } else + { + m_specColor = new Color(0f, 0f, 0f, 1f); + } + + if (sourceMat.HasProperty("_Smoothness") && m_workflowMode == WorkflowMode.specular) + { + m_smoothness = sourceMat.GetFloat("_Smoothness"); + Debug.LogError("TODO smooth " + sourceMat + " " + m_smoothness); + } + else if (m_workflowMode == WorkflowMode.specular) + { + m_smoothness = 1f; + } + } + else if (shaderTexturePropertyName.Equals("_MetallicGlossMap")) + { + propertyToDo = Prop.doMetallic; + if (sourceMat.GetTexture("_MetallicGlossMap") != null) + { + m_hasMetallicGlossMap = true; + } + else + { + m_hasMetallicGlossMap = false; + } + + if (sourceMat.HasProperty("_Metallic")) + { + m_metallic = sourceMat.GetFloat("_Metallic"); + } + else + { + m_metallic = 0f; + } + + if (sourceMat.HasProperty("_Smoothness") && m_workflowMode == WorkflowMode.metallic) + { + m_smoothness = sourceMat.GetFloat("_Smoothness"); + } + else if (m_workflowMode == WorkflowMode.metallic) + { + m_smoothness = 0f; + } + + + } + else if (shaderTexturePropertyName.Equals("_BumpMap")) + { + propertyToDo = Prop.doBump; + if (sourceMat.HasProperty(shaderTexturePropertyName)) + { + if (sourceMat.HasProperty("_BumpScale")) + m_bumpScale = sourceMat.GetFloat("_BumpScale"); + } + else + { + m_bumpScale = m_generatingTintedAtlaBumpScale; + } + } else if (shaderTexturePropertyName.Equals("_EmissionMap")) + { + propertyToDo = Prop.doEmission; + m_shaderDoesEmission = sourceMat.IsKeywordEnabled("_EMISSION"); + if (sourceMat.HasProperty("_EmissionColor")) { + m_emissionColor = sourceMat.GetColor("_EmissionColor"); + } else + { + m_generatingTintedAtlaColor = m_notGeneratingAtlasDefaultEmisionColor; + } + } else + { + propertyToDo = Prop.doNone; + } + Debug.LogError("TODO end " + m_smoothness + " " + sourceMat + " " + shaderTexturePropertyName); + } + + public Color OnBlendTexturePixel(string propertyToDoshaderPropertyName, Color pixelColor) + { + if (propertyToDo == Prop.doColor) + { + return new Color(pixelColor.r * m_tintColor.r, pixelColor.g * m_tintColor.g, pixelColor.b * m_tintColor.b, pixelColor.a * m_tintColor.a); + } + else if (propertyToDo == Prop.doMetallic) + { + if (m_hasMetallicGlossMap) + { + return pixelColor = new Color(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a * m_smoothness); + } + else + { + return new Color(m_metallic, 0, 0, m_smoothness); + } + } + else if (propertyToDo == Prop.doSpecular) + { + if (m_hasSpecGlossMap) + { + return pixelColor = new Color(pixelColor.r, pixelColor.g, pixelColor.b, pixelColor.a * m_smoothness); + } + else + { + Color c = m_specColor; + c.a = m_smoothness; + return c; + } + } + else if (propertyToDo == Prop.doBump) + { + return Color.Lerp(NeutralNormalMap, pixelColor, m_bumpScale); + } + else if (propertyToDo == Prop.doEmission) + { + if (m_shaderDoesEmission) + { + return new Color(pixelColor.r * m_emissionColor.r, pixelColor.g * m_emissionColor.g, pixelColor.b * m_emissionColor.b, pixelColor.a * m_emissionColor.a); + } + else + { + return Color.black; + } + } + return pixelColor; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlaColor, "_BaseColor")) + { + return false; + } + + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlaSpecular, "_SpecColor")) + { + return false; + } + + if (m_workflowMode == WorkflowMode.specular){ + bool aHasSpecTex = a.HasProperty("_SpecGlossMap") && a.GetTexture("_SpecGlossMap") != null; + bool bHasSpecTex = b.HasProperty("_SpecGlossMap") && b.GetTexture("_SpecGlossMap") != null; + + if (aHasSpecTex && bHasSpecTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultSmoothness_SpecularWorkflow, "_Smoothness")) + { + Debug.LogError("Are equal A"); + return false; + } + } + else if (!aHasSpecTex && !bHasSpecTex) + { + if (!TextureBlenderFallback._compareColor(a, b, m_notGeneratingAtlasDefaultSpecularColor, "_SpecColor") && + !TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultSmoothness_SpecularWorkflow, "_Smoothness")) + { + Debug.LogError("Are equal B"); + return false; + } + } + else + { + Debug.LogError("Are equal C"); + return false; + } + } + + if (m_workflowMode == WorkflowMode.metallic) { + bool aHasMetallicTex = a.HasProperty("_MetallicGlossMap") && a.GetTexture("_MetallicGlossMap") != null; + bool bHasMetallicTex = b.HasProperty("_MetallicGlossMap") && b.GetTexture("_MetallicGlossMap") != null; + if (aHasMetallicTex && bHasMetallicTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultSmoothness_MetallicWorkflow, "_Smoothness")) + { + return false; + } + } + else if (!aHasMetallicTex && !bHasMetallicTex) + { + if (!TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultMetallic, "_Metallic") && + !TextureBlenderFallback._compareFloat(a, b, m_notGeneratingAtlasDefaultSmoothness_MetallicWorkflow, "_Smoothness")) + { + return false; + } + } + else + { + return false; + } + } + + if (!TextureBlenderFallback._compareFloat(a, b, m_generatingTintedAtlaBumpScale, "_BumpScale")) + { + return false; + } + + if (a.IsKeywordEnabled("_EMISSION") != b.IsKeywordEnabled("_EMISSION")) + { + return false; + } + if (a.IsKeywordEnabled("_EMISSION")) + { + if (!TextureBlenderFallback._compareColor(a, b, m_generatingTintedAtlaEmission, "_EmissionColor")) + { + return false; + } + } + + return true; + } + + public void SetNonTexturePropertyValuesOnResultMaterial(Material resultMaterial) + { + if (m_workflowMode != WorkflowMode.unknown) + { + resultMaterial.SetFloat("_WorkflowMode", _MapWorkflowModeToFloat(m_workflowMode)); + } + + if (m_smoothnessTextureChannel != SmoothnessTextureChannel.unknown) + { + resultMaterial.SetFloat("_SmoothnessTextureChannel", _MapTextureChannelToFloat(m_smoothnessTextureChannel)); + } + + if (resultMaterial.GetTexture("_BaseMap") != null) + { + resultMaterial.SetColor("_BaseColor", m_generatingTintedAtlaColor); + } + else + { + resultMaterial.SetColor("_BaseColor", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_BaseColor", m_notGeneratingAtlasDefaultColor)); + } + + if (m_workflowMode == WorkflowMode.specular) + { + if (resultMaterial.GetTexture("_SpecGlossMap") != null) + { + Debug.LogError("Setting A " + m_smoothness); + resultMaterial.SetColor("_SpecColor", m_generatingTintedAtlaSpecular); + resultMaterial.SetFloat("_Smoothness", m_generatingTintedAtlasSpecular_somoothness); + } + else + { + Debug.LogError("Setting B " + m_smoothness); + resultMaterial.SetColor("_SpecColor", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_SpecColor", m_notGeneratingAtlasDefaultSpecularColor)); + resultMaterial.SetFloat("_Smoothness", m_smoothness); + } + } + + if (m_workflowMode == WorkflowMode.metallic) + { + if (resultMaterial.GetTexture("_MetallicGlossMap") != null) + { + resultMaterial.SetFloat("_Metallic", m_generatingTintedAtlasMetallic); + resultMaterial.SetFloat("_Smoothness", m_generatingTintedAtlasMetallic_smoothness); + } + else + { + resultMaterial.SetFloat("_Metallic", (float)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_Metallic", m_notGeneratingAtlasDefaultMetallic)); + resultMaterial.SetFloat("_Smoothness", m_smoothness); + } + } + + if (resultMaterial.GetTexture("_BumpMap") != null) + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlaBumpScale); + } + else + { + resultMaterial.SetFloat("_BumpScale", m_generatingTintedAtlaBumpScale); + } + + if (resultMaterial.GetTexture("_EmissionMap") != null) + { + resultMaterial.EnableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", Color.white); + } + else + { + resultMaterial.DisableKeyword("_EMISSION"); + resultMaterial.SetColor("_EmissionColor", (Color)sourceMaterialPropertyCache.GetValueIfAllSourceAreTheSameOrDefault("_EmissionColor", m_notGeneratingAtlasDefaultEmisionColor)); + } + } + + + public Color GetColorIfNoTexture(Material mat, ShaderTextureProperty texPropertyName) + { + if (texPropertyName.name.Equals("_BumpMap")) + { + return new Color(.5f, .5f, 1f); + } + else if (texPropertyName.name.Equals("_BaseMap")) + { + if (mat != null && mat.HasProperty("_BaseColor")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_BaseColor"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_BaseColor", c); + } + catch (Exception) { } + return Color.white; + } + } + else if (texPropertyName.name.Equals("_SpecGlossMap")) + { + if (mat != null && mat.HasProperty("_SpecColor")) + { + try + { //need try because can't garantee _Color is a color + Color c = mat.GetColor("_SpecColor"); + /* + if (mat.HasProperty("_Glossiness")) + { + try + { + c.a = mat.GetFloat("_Glossiness"); + } + catch (Exception) { } + } + */ + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_SpecColor", c); + //sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Glossiness", c.a); + } + catch (Exception) { } + } + return new Color(0f, 0f, 0f, .5f); + } + else if (texPropertyName.name.Equals("_MetallicGlossMap")) + { + if (mat != null && mat.HasProperty("_Metallic")) + { + try + { //need try because can't garantee _Metallic is a float + float v = mat.GetFloat("_Metallic"); + Color c = new Color(v, v, v); + + if (mat.HasProperty("_Smoothness")) + { + try + { + c.a = mat.GetFloat("_Smoothness"); + } + + catch (Exception) { } + } + + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Metallic", v); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_Smoothness", c.a); + } + catch (Exception) { } + return new Color(0f, 0f, 0f, .5f); + } + else + { + return new Color(0f, 0f, 0f, .5f); + } + } + else if (texPropertyName.name.Equals("_OcclusionMap")) + { + return new Color(1f, 1f, 1f, 1f); + } + else if (texPropertyName.name.Equals("_EmissionMap")) + { + if (mat != null) + { + if (mat.IsKeywordEnabled("_EMISSION")) + { + if (mat.HasProperty("_EmissionColor")) + { + try + { + Color c = mat.GetColor("_EmissionColor"); + sourceMaterialPropertyCache.CacheMaterialProperty(mat, "_EmissionColor", c); + } + catch (Exception) { } + } + else + { + return Color.black; + } + } + else + { + return Color.black; + } + } + } + else if (texPropertyName.name.Equals("_DetailMask")) + { + return new Color(0f, 0f, 0f, 0f); + } + return new Color(1f, 1f, 1f, 0f); + } + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderURPLit.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderURPLit.cs.meta new file mode 100644 index 00000000..e793edaf --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/TextureBlenders/TextureBlenderURPLit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 727a1b5619dc36f43a2d00164cd6c964 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core.meta new file mode 100644 index 00000000..1e0eb3cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 2 +guid: 6b9bcd20adc8f914f9e37a0895259b47 +folderAsset: yes +DefaultImporter: + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Core.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Core.cs new file mode 100644 index 00000000..0ca3d6ce --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Core.cs @@ -0,0 +1,102 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core{ + + public delegate void ProgressUpdateDelegate(string msg, float progress); + public delegate bool ProgressUpdateCancelableDelegate(string msg, float progress); + + public enum MB_ObjsToCombineTypes{ + prefabOnly, + sceneObjOnly, + dontCare + } + + public enum MB_OutputOptions{ + bakeIntoPrefab, + bakeMeshsInPlace, + bakeTextureAtlasesOnly, + bakeIntoSceneObject + } + + public enum MB_RenderType{ + meshRenderer, + skinnedMeshRenderer + } + + public enum MB2_OutputOptions{ + bakeIntoSceneObject, + bakeMeshAssetsInPlace, + bakeIntoPrefab + } + + public enum MB2_LightmapOptions{ + preserve_current_lightmapping, + ignore_UV2, + copy_UV2_unchanged, + generate_new_UV2_layout, + copy_UV2_unchanged_to_separate_rects, + } + + public enum MB2_PackingAlgorithmEnum{ + UnitysPackTextures, + MeshBakerTexturePacker, + MeshBakerTexturePacker_Fast, + MeshBakerTexturePacker_Horizontal, //special packing packs all horizontal. makes it possible to use an atlas with tiling textures + MeshBakerTexturePacker_Vertical, //special packing packs all Vertical. makes it possible to use an atlas with tiling textures + MeshBakerTexturePaker_Fast_V2_Beta, // new version of MeshBakerTexterPackerFast that uses a mesh. Is compatible with URP and HDRP + } + + public enum MB_TextureTilingTreatment{ + none, + considerUVs, + edgeToEdgeX, + edgeToEdgeY, + edgeToEdgeXY, // One image in atlas. + unknown, + } + + public enum MB2_ValidationLevel{ + none, + quick, + robust + } + + /// <summary> + /// M b2_ texture combiner editor methods. + /// Contains functionality such as changeing texture formats + /// Which is only available in the editor. These methods have all been put in a + /// class so that the UnityEditor namespace does not need to be included in any of the + /// the runtime classes. + /// </summary> + public interface MB2_EditorMethodsInterface{ + void Clear(); + void RestoreReadFlagsAndFormats(ProgressUpdateDelegate progressInfo); + void SetReadWriteFlag(Texture2D tx, bool isReadable, bool addToList); + void ConvertTextureFormat_DefaultPlatform(Texture2D tx, TextureFormat targetFormat, bool isNormalMap); + void ConvertTextureFormat_PlatformOverride(Texture2D tx, TextureFormat targetFormat, bool isNormalMap); + void SaveTextureArrayToAssetDatabase(Texture2DArray atlas, TextureFormat foramt, string texPropertyName, int atlasNum, Material resMat); + void SaveAtlasToAssetDatabase(Texture2D atlas, ShaderTextureProperty texPropertyName, int atlasNum, bool doAnySrcMatsHaveProperty, Material resMat); + bool IsNormalMap(Texture2D tx); + string GetPlatformString(); + void SetTextureSize(Texture2D tx, int size); + bool IsCompressed(Texture2D tx); + void CheckBuildSettings(long estimatedAtlasSize); + bool CheckPrefabTypes(MB_ObjsToCombineTypes prefabType, List<GameObject> gos); + bool ValidateSkinnedMeshes(List<GameObject> mom); + void CommitChangesToAssets(); + void OnPreTextureBake(); + void OnPostTextureBake(); + + /// <summary> + /// Safe Destroy. Won't destroy assets. + /// </summary> + void Destroy(UnityEngine.Object o); + void DestroyAsset(UnityEngine.Object o); + bool IsAnAsset(UnityEngine.Object o); + Texture2D CreateTemporaryAssetCopy(ShaderTextureProperty prop, Texture2D sliceTex, int w, int h, TextureFormat format, MB2_LogLevel logLevel); + bool TextureImporterFormatExistsForTextureFormat(TextureFormat texFormat); + bool ConvertTexture2DArray(Texture2DArray inArray, Texture2DArray outArray, TextureFormat outFormat); + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Core.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Core.cs.meta new file mode 100644 index 00000000..965dff7f --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Core.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c498c4e67e0be344cb0d92b0c9935f47 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Log.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Log.cs new file mode 100644 index 00000000..a9d069a9 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Log.cs @@ -0,0 +1,121 @@ +using System; +using UnityEngine; +using System.Collections; +using System.Text; + +namespace DigitalOpus.MB.Core{ + + public enum MB2_LogLevel{ + none, + error, + warn, + info, + debug, + trace + } + + public class MB2_Log { + + public static void Log(MB2_LogLevel l, String msg, MB2_LogLevel currentThreshold){ + if (l <= currentThreshold) { + if (l == MB2_LogLevel.error) Debug.LogError(msg); + if (l == MB2_LogLevel.warn) Debug.LogWarning(String.Format("frm={0} WARN {1}",Time.frameCount,msg)); + if (l == MB2_LogLevel.info) Debug.Log(String.Format("frm={0} INFO {1}",Time.frameCount,msg)); + if (l == MB2_LogLevel.debug) Debug.Log(String.Format("frm={0} DEBUG {1}",Time.frameCount,msg)); + if (l == MB2_LogLevel.trace) Debug.Log(String.Format("frm={0} TRACE {1}",Time.frameCount,msg)); + } + } + + public static string Error(string msg, params object[] args){ + string s = String.Format(msg, args); + string s2 = String.Format("f={0} ERROR {1}", Time.frameCount,s); + Debug.LogError(s2); + return s2; + } + + public static string Warn(string msg, params object[] args){ + string s = String.Format(msg, args); + string s2 = String.Format("f={0} WARN {1}", Time.frameCount,s); + Debug.LogWarning(s2); + return s2; + } + + public static string Info(string msg, params object[] args){ + string s = String.Format(msg, args); + string s2 = String.Format("f={0} INFO {1}", Time.frameCount,s); + Debug.Log(s2); + return s2; + } + + public static string LogDebug(string msg, params object[] args){ + string s = String.Format(msg, args); + string s2 = String.Format("f={0} DEBUG {1}", Time.frameCount,s); + Debug.Log(s2); + return s2; + } + + public static string Trace(string msg, params object[] args){ + string s = String.Format(msg, args); + string s2 = String.Format("f={0} TRACE {1}", Time.frameCount,s); + Debug.Log(s2); + return s2; + } + } + + /// <summary> + /// LOD stores a buffer of log messages specific to an object. These log messages are also written out to + /// the console. + /// </summary> + public class ObjectLog{ + int pos = 0; + string[] logMessages; + + void _CacheLogMessage(string msg){ + if (logMessages.Length == 0) return; + logMessages[pos] = msg; + pos++; + if (pos >= logMessages.Length) pos = 0; + } + + public ObjectLog(short bufferSize){ + logMessages = new string[bufferSize]; + } + + public void Log(MB2_LogLevel l, String msg, MB2_LogLevel currentThreshold){ + MB2_Log.Log(l,msg, currentThreshold); + _CacheLogMessage(msg); + } + + public void Error(string msg, params object[] args){ + _CacheLogMessage(MB2_Log.Error(msg,args)); + } + + public void Warn(string msg, params object[] args){ + _CacheLogMessage(MB2_Log.Warn(msg,args)); + } + + public void Info(string msg, params object[] args){ + _CacheLogMessage(MB2_Log.Info(msg,args)); + } + + public void LogDebug(string msg, params object[] args){ + _CacheLogMessage(MB2_Log.LogDebug(msg,args)); + } + + public void Trace(string msg, params object[] args){ + _CacheLogMessage(MB2_Log.Trace(msg,args)); + } + + public string Dump(){ + StringBuilder sb = new StringBuilder(); + int startPos = 0; + if (logMessages[logMessages.Length - 1] != null) startPos = pos; + for (int i = 0; i < logMessages.Length; i++){ + int ii = (startPos + i) % logMessages.Length; + if (logMessages[ii] == null) break; + sb.AppendLine(logMessages[ii]); + } + return sb.ToString(); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Log.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Log.cs.meta new file mode 100644 index 00000000..625be3e8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_Log.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e269d2f71ce6fea4c9661e2a613d27c6 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_MBVersion.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_MBVersion.cs new file mode 100644 index 00000000..5d72f802 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_MBVersion.cs @@ -0,0 +1,229 @@ +/** + * \brief Hax! DLLs cannot interpret preprocessor directives, so this class acts as a "bridge" + */ +using System; +using UnityEngine; +using System.Collections; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core{ + + public interface MBVersionInterface{ + string version(); + int GetMajorVersion(); + int GetMinorVersion(); + bool GetActive(GameObject go); + void SetActive(GameObject go, bool isActive); + void SetActiveRecursively(GameObject go, bool isActive); + UnityEngine.Object[] FindSceneObjectsOfType(Type t); + bool IsRunningAndMeshNotReadWriteable(Mesh m); + Vector2[] GetMeshUVChannel(int channel, Mesh m, MB2_LogLevel LOG_LEVEL); + void MeshClear(Mesh m, bool t); + void MeshAssignUVChannel(int channel, Mesh m, Vector2[] uvs); + Vector4 GetLightmapTilingOffset(Renderer r); + Transform[] GetBones(Renderer r, bool isSkinnedMeshWithBones); + void OptimizeMesh(Mesh m); + int GetBlendShapeFrameCount(Mesh m, int shapeIndex); + float GetBlendShapeFrameWeight(Mesh m, int shapeIndex, int frameIndex); + void GetBlendShapeFrameVertices(Mesh m, int shapeIndex, int frameIndex, Vector3[] vs, Vector3[] ns, Vector3[] ts); + void ClearBlendShapes(Mesh m); + void AddBlendShapeFrame(Mesh m, string nm, float wt, Vector3[] vs, Vector3[] ns, Vector3[] ts); + int MaxMeshVertexCount(); + void SetMeshIndexFormatAndClearMesh(Mesh m, int numVerts, bool vertices, bool justClearTriangles); + bool GraphicsUVStartsAtTop(); + + bool IsTextureReadable(Texture2D tex); + bool CollectPropertyNames(List<ShaderTextureProperty> texPropertyNames, ShaderTextureProperty[] shaderTexPropertyNames, List<ShaderTextureProperty> _customShaderPropNames, Material resultMaterial, MB2_LogLevel LOG_LEVEL); + + void DoSpecialRenderPipeline_TexturePackerFastSetup(GameObject cameraGameObject); + + ColorSpace GetProjectColorSpace(); + + MBVersion.PipelineType DetectPipeline(); + + string UnescapeURL(string url); + } + + public class MBVersion + { + + public const string MB_USING_HDRP = "MB_USING_HDRP"; + + public enum PipelineType + { + Unsupported, + Default, + URP, + HDRP + } + + private static MBVersionInterface _MBVersion; + + private static MBVersionInterface _CreateMBVersionConcrete(){ + Type vit = null; +#if EVAL_VERSION + vit = Type.GetType("DigitalOpus.MB.Core.MBVersionConcrete,Assembly-CSharp"); +#else + vit = typeof(MBVersionConcrete); +#endif + return (MBVersionInterface) System.Activator.CreateInstance(vit); + } + + public static string version(){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.version(); + } + + public static int GetMajorVersion(){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetMajorVersion(); + } + + public static int GetMinorVersion(){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetMinorVersion(); + } + + public static bool GetActive(GameObject go){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetActive(go); + } + + public static void SetActive(GameObject go, bool isActive){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.SetActive(go,isActive); + } + + public static void SetActiveRecursively(GameObject go, bool isActive){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.SetActiveRecursively(go,isActive); + } + + public static UnityEngine.Object[] FindSceneObjectsOfType(Type t){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.FindSceneObjectsOfType(t); + } + + public static bool IsRunningAndMeshNotReadWriteable(Mesh m){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.IsRunningAndMeshNotReadWriteable(m); + } + + public static Vector2[] GetMeshChannel(int channel, Mesh m, MB2_LogLevel LOG_LEVEL) { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetMeshUVChannel(channel, m,LOG_LEVEL); + } + + public static void MeshClear(Mesh m, bool t){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.MeshClear(m,t); + } + + public static void MeshAssignUVChannel(int channel, Mesh m, Vector2[] uvs) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.MeshAssignUVChannel(channel, m, uvs); + } + + public static Vector4 GetLightmapTilingOffset(Renderer r){ + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetLightmapTilingOffset(r); + } + + public static Transform[] GetBones(Renderer r, bool isSkinnedMeshWithBones) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetBones(r, isSkinnedMeshWithBones); + } + + public static void OptimizeMesh(Mesh m) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.OptimizeMesh(m); + } + + public static int GetBlendShapeFrameCount(Mesh m, int shapeIndex) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetBlendShapeFrameCount(m, shapeIndex); + } + + public static float GetBlendShapeFrameWeight(Mesh m, int shapeIndex, int frameIndex) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetBlendShapeFrameWeight(m, shapeIndex, frameIndex); + } + + public static void GetBlendShapeFrameVertices(Mesh m, int shapeIndex, int frameIndex, Vector3[] vs, Vector3[] ns, Vector3[] ts) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.GetBlendShapeFrameVertices(m, shapeIndex, frameIndex, vs, ns, ts); + } + + public static void ClearBlendShapes(Mesh m) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.ClearBlendShapes(m); + } + + public static void AddBlendShapeFrame(Mesh m, string nm, float wt, Vector3[] vs, Vector3[] ns, Vector3[] ts) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.AddBlendShapeFrame(m, nm, wt, vs, ns, ts); + } + + public static int MaxMeshVertexCount() + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.MaxMeshVertexCount(); + } + + public static void SetMeshIndexFormatAndClearMesh(Mesh m, int numVerts, bool vertices, bool justClearTriangles) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.SetMeshIndexFormatAndClearMesh(m, numVerts, vertices, justClearTriangles); + } + + public static bool GraphicsUVStartsAtTop() + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GraphicsUVStartsAtTop(); + } + + public static bool IsTextureReadable(Texture2D tex) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.IsTextureReadable(tex); + } + + public static void CollectPropertyNames(List<ShaderTextureProperty> texPropertyNames, ShaderTextureProperty[] shaderTexPropertyNames, List<ShaderTextureProperty> _customShaderPropNames, Material resultMaterial, MB2_LogLevel LOG_LEVEL) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.CollectPropertyNames(texPropertyNames, shaderTexPropertyNames, _customShaderPropNames, resultMaterial, LOG_LEVEL); + } + + internal static void DoSpecialRenderPipeline_TexturePackerFastSetup(GameObject cameraGameObject) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + _MBVersion.DoSpecialRenderPipeline_TexturePackerFastSetup(cameraGameObject); + } + + internal static ColorSpace GetProjectColorSpace() + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.GetProjectColorSpace(); + } + + public static PipelineType DetectPipeline() + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.DetectPipeline(); + } + + public static string UnescapeURL(string url) + { + if (_MBVersion == null) _MBVersion = _CreateMBVersionConcrete(); + return _MBVersion.UnescapeURL(url); + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_MBVersion.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_MBVersion.cs.meta new file mode 100644 index 00000000..8a50e750 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB2_MBVersion.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e2bb7c59e3400ba40a42672ace02bab2 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_AgglomerativeClustering.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_AgglomerativeClustering.cs new file mode 100644 index 00000000..e31a1e76 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_AgglomerativeClustering.cs @@ -0,0 +1,387 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System; + +namespace DigitalOpus.MB.Core +{ + [Serializable] + public class MB3_AgglomerativeClustering + { + + public List<item_s> items = new List<item_s>(); + + public ClusterNode[] clusters; + + public bool wasCanceled; + + [Serializable] + public class ClusterNode + { + public item_s leaf; + public ClusterNode cha; + public ClusterNode chb; + public int height; /* height of node from the bottom */ + public float distToMergedCentroid; + public Vector3 centroid; /* centroid of this cluster */ + public int[] leafs; /* indexes of root clusters merged */ + public int idx; //index in clusters list + public bool isUnclustered = true; + + public ClusterNode(item_s ii, int index) + { + leaf = ii; + idx = index; + leafs = new int[1]; + leafs[0] = index; + centroid = ii.coord; + height = 0; + } + + public ClusterNode(ClusterNode a, ClusterNode b, int index, int h, float dist, ClusterNode[] clusters) + { + cha = a; + chb = b; + idx = index; + leafs = new int[a.leafs.Length + b.leafs.Length]; + Array.Copy(a.leafs, leafs, a.leafs.Length); + Array.Copy(b.leafs, 0, leafs, a.leafs.Length, b.leafs.Length); + Vector3 c = Vector3.zero; + for (int i = 0; i < leafs.Length; i++) + { + c += clusters[leafs[i]].centroid; + } + centroid = c / leafs.Length; + height = h; + distToMergedCentroid = dist; + } + }; + + + [Serializable] + public class item_s + { + public GameObject go; + public Vector3 coord; /* coordinate of the input data point */ + }; + + float euclidean_distance(Vector3 a, Vector3 b) + { + return Vector3.Distance(a, b); + } + + public bool agglomerate(ProgressUpdateCancelableDelegate progFunc) + { + wasCanceled = true; + if (progFunc != null) wasCanceled = progFunc("Filling Priority Queue:", 0); + if (items.Count <= 1) + { + clusters = new ClusterNode[0]; + return false; + //yield break; + } + clusters = new ClusterNode[items.Count * 2 - 1]; + for (int i = 0; i < items.Count; i++) + { + clusters[i] = new ClusterNode(items[i], i); + } + + int numClussters = items.Count; + List<ClusterNode> unclustered = new List<ClusterNode>(); + for (int i = 0; i < numClussters; i++) + { + clusters[i].isUnclustered = true; + unclustered.Add(clusters[i]); + } + + int height = 0; + System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); + timer.Start(); + + float largestDistInQ = 0; + long usedMemory = GC.GetTotalMemory(false) / 1000000; + PriorityQueue < float, ClusterDistance > pq = new PriorityQueue<float, ClusterDistance>(); + //largestDistInQ = _RefillPriorityQWithSome(pq, unclustered, clusters /*,null,null*/); + int numRefills = 0; + while (unclustered.Count > 1) + { + + int numToFindClosetPair = 0; + height++; + //find closest pair + + if (pq.Count == 0) + { + numRefills++; + usedMemory = GC.GetTotalMemory(false) / 1000000; + if (progFunc != null) wasCanceled = progFunc("Refilling Q:" + ((float)(items.Count - unclustered.Count) * 100) / items.Count + " unclustered:" + unclustered.Count + " inQ:" + pq.Count + " usedMem:" + usedMemory, + ((float)(items.Count - unclustered.Count)) / items.Count); + largestDistInQ = _RefillPriorityQWithSome(pq, unclustered, clusters, progFunc); + if (pq.Count == 0) break; + } + KeyValuePair<float, ClusterDistance> closestPair = pq.Dequeue(); + // should only consider unclustered pairs. It is more effecient to discard nodes that have already been clustered as they are popped off the Q + // than to try to remove them from the Q when they have been clustered. + while (!closestPair.Value.a.isUnclustered || !closestPair.Value.b.isUnclustered) { + if (pq.Count == 0) + { + numRefills++; + usedMemory = GC.GetTotalMemory(false) / 1000000; + if (progFunc != null) wasCanceled = progFunc("Creating clusters:" + ((float)(items.Count - unclustered.Count) * 100) / items.Count + " unclustered:" + unclustered.Count + " inQ:" + pq.Count + " usedMem:" + usedMemory, + ((float)(items.Count - unclustered.Count)) / items.Count); + largestDistInQ = _RefillPriorityQWithSome(pq, unclustered, clusters, progFunc); + if (pq.Count == 0) break; + } + closestPair = pq.Dequeue(); + numToFindClosetPair++; + } + + //make a new cluster with pair as children set merge height + numClussters++; + ClusterNode cn = new ClusterNode(closestPair.Value.a, closestPair.Value.b, numClussters - 1, height, closestPair.Key, clusters); + //remove children from unclustered + unclustered.Remove(closestPair.Value.a); + unclustered.Remove(closestPair.Value.b); + + + //We NEED TO REMOVE ALL DISTANCE PAIRS THAT INVOLVE A AND B FROM PRIORITY Q. However searching for all these pairs and removing is very slow. + // Instead we will leave them in the Queue and flag the clusters as isUnclustered = false and discard them as they are popped from the Q which is O(1) operation. + closestPair.Value.a.isUnclustered = false; + closestPair.Value.b.isUnclustered = false; + + //add new cluster to unclustered + int newIdx = numClussters - 1; + if (newIdx == clusters.Length) + { + Debug.LogError("how did this happen"); + } + clusters[newIdx] = cn; + unclustered.Add(cn); + cn.isUnclustered = true; + //update new clusteres distance + for (int i = 0; i < unclustered.Count - 1; i++) + { + + float dist = euclidean_distance(cn.centroid, unclustered[i].centroid); + if (dist < largestDistInQ) //avoid cluttering Qwith + { + pq.Add(new KeyValuePair<float, ClusterDistance>(dist, new ClusterDistance(cn, unclustered[i]))); + } + } + //if (timer.Interval > .2f) + //{ + // yield return null; + // timer.Start(); + //} + if (wasCanceled) break; + usedMemory = GC.GetTotalMemory(false) / 1000000; + if (progFunc != null) wasCanceled = progFunc("Creating clusters:" + ((float)(items.Count - unclustered.Count)*100) / items.Count + " unclustered:" + unclustered.Count + " inQ:" + pq.Count + " usedMem:" + usedMemory, + ((float)(items.Count - unclustered.Count)) / items.Count); + } + if (progFunc != null) wasCanceled = progFunc("Finished clustering:", 100); + //Debug.Log("Time " + timer.Elapsed); + if (wasCanceled) + { + return false; + } + else + { + return true; + } + } + + const int MAX_PRIORITY_Q_SIZE = 2048; + float _RefillPriorityQWithSome(PriorityQueue<float, ClusterDistance> pq, List<ClusterNode> unclustered, ClusterNode[] clusters, ProgressUpdateCancelableDelegate progFunc) + { + //find nthSmallest point of distances between pairs + List<float> allDist = new List<float>(2048); + for (int i = 0; i < unclustered.Count; i++) + { + for (int j = i+1; j < unclustered.Count; j++) + { + + // if (unclustered[i] == omitA || unclustered[i] == omitB || + // unclustered[j] == omitA || unclustered[j] == omitB) + // { + + // } else + // { + + allDist.Add(euclidean_distance(unclustered[i].centroid, unclustered[j].centroid)); + // } + } + wasCanceled = progFunc("Refilling Queue Part A:", i / (unclustered.Count * 2f)); + if (wasCanceled) return 10f; + } + + if (allDist.Count == 0) + { + return 10e10f; + } + float nthSmallest = NthSmallestElement(allDist, MAX_PRIORITY_Q_SIZE); + + //load up Q with up to nthSmallest distance pairs + for (int i = 0; i < unclustered.Count; i++) + { + for (int j = i + 1; j < unclustered.Count; j++) + { + int idxa = unclustered[i].idx; + int idxb = unclustered[j].idx; + float newDist = euclidean_distance(unclustered[i].centroid, unclustered[j].centroid); + if (newDist <= nthSmallest) + { + pq.Add(new KeyValuePair<float, ClusterDistance>(newDist, new ClusterDistance(clusters[idxa], clusters[idxb]))); + } + } + wasCanceled = progFunc("Refilling Queue Part B:", (unclustered.Count + i) / (unclustered.Count * 2f)); + if (wasCanceled) return 10f; + } + return nthSmallest; + } + + public int TestRun(List<GameObject> gos) + { + List<item_s> its = new List<item_s>(); + for (int i = 0; i < gos.Count; i++) + { + item_s ii = new item_s(); + ii.go = gos[i]; + ii.coord = gos[i].transform.position; + its.Add(ii); + } + items = its; + if (items.Count > 0) + { + agglomerate(null); + } + return 0; + } + + + //------ + // Unclustered + //need to be able to find the smallest distance between unclustered pairs quickly + //Do this by maintaining a fixed length PriorityQueue (len = 1000) + // Q stores min distances between cluster pairs + // unlclustered stores list of unclustered + //GetMin + // if Q is empty + // build Q from unclustered O(n2) + // track the largestDistanceInQ + // if unclustered is empty we are done + // else + // q.DeQueue O(1) + // + // when creating new merged cluster, calc dist to all other unclustered add these distances to priority Q if less than largestDistanceInQ O(N) + // + + public class ClusterDistance + { + public ClusterNode a; + public ClusterNode b; + public ClusterDistance(ClusterNode aa, ClusterNode bb) + { + a = aa; + b = bb; + } + } + + + + public static void Main() + { + + List<float> inputArray = new List<float>(); + inputArray.AddRange(new float[] { 19, 18, 17, 16, 15, 10, 11, 12, 13, 14 }); + // Loop 10 times + Debug.Log("Loop quick select 10 times."); + + Debug.Log(NthSmallestElement(inputArray, 0)); + + } + + // n is 0 indexed + public static T NthSmallestElement<T>(List<T> array, int n) where T : IComparable<T> + { + if (n < 0) + n = 0; + + if (n > array.Count - 1) + n = array.Count - 1; + if (array.Count == 0) + throw new ArgumentException("Array is empty.", "array"); + if (array.Count == 1) + return array[0]; + + return QuickSelectSmallest(array, n)[n]; + } + + private static List<T> QuickSelectSmallest<T>(List<T> input, int n) where T : IComparable<T> + { + // Let's not mess up with our input array + // For very large arrays - we should optimize this somehow - or just mess up with our input + var partiallySortedArray = input; + + // Initially we are going to execute quick select to entire array + var startIndex = 0; + var endIndex = input.Count - 1; + + // Selecting initial pivot + // Maybe we are lucky and array is sorted initially? + var pivotIndex = n; + + // Loop until there is nothing to loop (this actually shouldn't happen - we should find our value before we run out of values) + var r = new System.Random(); + while (endIndex > startIndex) + { + pivotIndex = QuickSelectPartition(partiallySortedArray, startIndex, endIndex, pivotIndex); + if (pivotIndex == n) + // We found our n:th smallest value - it is stored to pivot index + break; + if (pivotIndex > n) + // Array before our pivot index have more elements that we are looking for + endIndex = pivotIndex - 1; + else + // Array before our pivot index has less elements that we are looking for + startIndex = pivotIndex + 1; + + // Omnipotent beings don't need to roll dices - but we do... + // Randomly select a new pivot index between end and start indexes (there are other methods, this is just most brutal and simplest) + pivotIndex = r.Next(startIndex, endIndex); + } + return partiallySortedArray; + } + + private static int QuickSelectPartition<T>(List<T> array, int startIndex, int endIndex, int pivotIndex) where T : IComparable<T> + { + var pivotValue = array[pivotIndex]; + // Initially we just assume that value in pivot index is largest - so we move it to end (makes also for loop more straight forward) + Swap(array, pivotIndex, endIndex); + for (var i = startIndex; i < endIndex; i++) + { + if (array[i].CompareTo(pivotValue) > 0) + continue; + + // Value stored to i was smaller than or equal with pivot value - let's move it to start + Swap(array, i, startIndex); + // Move start one index forward + startIndex++; + } + // Start index is now pointing to index where we should store our pivot value from end of array + Swap(array, endIndex, startIndex); + return startIndex; + } + + private static void Swap<T>(List<T> array, int index1, int index2) + { + if (index1 == index2) + return; + + var temp = array[index1]; + array[index1] = array[index2]; + array[index2] = temp; + } + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_AgglomerativeClustering.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_AgglomerativeClustering.cs.meta new file mode 100644 index 00000000..d2581df3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_AgglomerativeClustering.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 829bd7c001a1f75479e06594f30f7555 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_CopyBoneWeights.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_CopyBoneWeights.cs new file mode 100644 index 00000000..319920fe --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_CopyBoneWeights.cs @@ -0,0 +1,93 @@ +using UnityEngine; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core { + public class MB3_CopyBoneWeights { + public static void CopyBoneWeightsFromSeamMeshToOtherMeshes(float radius, Mesh seamMesh, Mesh[] targetMeshes) { + //Todo make sure meshes are assets + List<int> seamVerts = new List<int> (); + if (seamMesh == null) { + Debug.LogError(string.Format("The SeamMesh cannot be null")); + return; + } + if (seamMesh.vertexCount == 0){ + Debug.LogError("The seam mesh has no vertices. Check that the Asset Importer for the seam mesh does not have 'Optimize Mesh' checked."); + return; + } + Vector3[] srcVerts = seamMesh.vertices; + BoneWeight[] srcBW = seamMesh.boneWeights; + Vector3[] srcNormal = seamMesh.normals; + Vector4[] srcTangent = seamMesh.tangents; + Vector2[] srcUVs = seamMesh.uv; + + if (srcUVs.Length != srcVerts.Length) { + Debug.LogError("The seam mesh needs uvs to identify which vertices are part of the seam. Vertices with UV > .5 are part of the seam. Vertices with UV < .5 are not part of the seam."); + return; + } + + for (int i = 0; i < srcUVs.Length; i++) { + if (srcUVs[i].x > .5f && srcUVs[i].y > .5f){ + seamVerts.Add (i); + } + } + Debug.Log (string.Format("The seam mesh has {0} vertices of which {1} are seam vertices.", seamMesh.vertices.Length, seamVerts.Count)); + if (seamVerts.Count == 0) { + Debug.LogError("None of the vertices in the Seam Mesh were marked as seam vertices. To mark a vertex as a seam vertex the UV" + + " must be greater than (.5,.5). Vertices with UV less than (.5,.5) are excluded."); + return; + } + + //validate + bool failed = false; + for (int meshIdx = 0; meshIdx < targetMeshes.Length; meshIdx++) { + if (targetMeshes[meshIdx] == null) { + Debug.LogError(string.Format("Mesh {0} was null", meshIdx)); + failed = true; + } + + if (radius < 0f) { + Debug.LogError("radius must be zero or positive."); + } + } + if (failed) { + return; + } + for (int meshIdx = 0; meshIdx < targetMeshes.Length; meshIdx++) { + Mesh tm = targetMeshes[meshIdx]; + Vector3[] otherVerts = tm.vertices; + BoneWeight[] otherBWs = tm.boneWeights; + Vector3[] otherNormals = tm.normals; + Vector4[] otherTangents = tm.tangents; + + int numMatches = 0; + for (int i = 0; i < otherVerts.Length; i++) { + for (int sIdx = 0; sIdx < seamVerts.Count; sIdx++) { + int j = seamVerts[sIdx]; + if (Vector3.Distance(otherVerts[i], srcVerts[j]) <= radius) { + numMatches++; + otherBWs[i] = srcBW[j]; + otherVerts[i] = srcVerts[j]; + if (otherNormals.Length == otherVerts.Length && srcNormal.Length == srcNormal.Length) + { + otherNormals[i] = srcNormal[j]; + } + if (otherTangents.Length == otherVerts.Length && srcTangent.Length == srcVerts.Length) + { + otherTangents[i] = srcTangent[j]; + } + } + } + } + if (numMatches > 0) { + targetMeshes[meshIdx].vertices = otherVerts; + targetMeshes[meshIdx].boneWeights = otherBWs; + targetMeshes[meshIdx].normals = otherNormals; + targetMeshes[meshIdx].tangents = otherTangents; + } + Debug.Log(string.Format("Copied boneweights for {1} vertices in mesh {0} that matched positions in the seam mesh.", targetMeshes[meshIdx].name, numMatches)); + } + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_CopyBoneWeights.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_CopyBoneWeights.cs.meta new file mode 100644 index 00000000..5ff28f31 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_CopyBoneWeights.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ef04fd02a398bb9498a0952a32980f2f +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_GrouperCluster.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_GrouperCluster.cs new file mode 100644 index 00000000..39858ffd --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_GrouperCluster.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +public class MB3_KMeansClustering { + + class DataPoint + { + public Vector3 center; + public GameObject gameObject; + public int Cluster; + + public DataPoint(GameObject go) + { + gameObject = go; + center = go.transform.position; + if (go.GetComponent<Renderer>() == null) Debug.LogError("Object does not have a renderer " + go); + } + } + + List<DataPoint> _normalizedDataToCluster = new List<DataPoint>(); + Vector3[] _clusters = new Vector3[0]; + private int _numberOfClusters = 0; + + public MB3_KMeansClustering(List<GameObject> gos, int numClusters) + { + for (int i = 0; i < gos.Count; i++) + { + if (gos[i] != null) + { + DataPoint dp = new DataPoint(gos[i]); + _normalizedDataToCluster.Add(dp); + } else + { + Debug.LogWarning(String.Format("Object {0} in list of objects to cluster was null.", i)); + } + } + if (numClusters <= 0) + { + Debug.LogError("Number of clusters must be posititve."); + numClusters = 1; + } + if (_normalizedDataToCluster.Count <= numClusters) + { + Debug.LogError("There must be fewer clusters than objects to cluster"); + numClusters = _normalizedDataToCluster.Count - 1; + } + _numberOfClusters = numClusters; + if (_numberOfClusters <= 0) _numberOfClusters = 1; + + _clusters = new Vector3[_numberOfClusters]; + } + + private void InitializeCentroids() + { + //todo error if more clusters than objs + for (int i = 0; i < _numberOfClusters; ++i) + { + _normalizedDataToCluster[i].Cluster = i; + } + for (int i = _numberOfClusters; i < _normalizedDataToCluster.Count; i++) + { + _normalizedDataToCluster[i].Cluster = UnityEngine.Random.Range(0, _numberOfClusters); + } + } + + private bool UpdateDataPointMeans(bool force) + { + if (AnyAreEmpty(_normalizedDataToCluster) && !force) return false; + Vector3[] means = new Vector3[_numberOfClusters]; + int[] numInCluster = new int[_numberOfClusters]; + + for (int i = 0; i < _normalizedDataToCluster.Count; i++) + { + int idx = _normalizedDataToCluster[i].Cluster; + means[idx] += _normalizedDataToCluster[i].center; + numInCluster[idx]++; + } + for (int i = 0; i < _numberOfClusters; i++) + { + _clusters[i] = means[i] / numInCluster[i]; + } + return true; + } + + private bool AnyAreEmpty(List<DataPoint> data) + { + int[] numInCluster = new int[_numberOfClusters]; + for (int i = 0; i < _normalizedDataToCluster.Count; i++) + { + numInCluster[_normalizedDataToCluster[i].Cluster]++; + } + + for (int i = 0; i < numInCluster.Length; i++) + { + if (numInCluster[i] == 0) + { + return true; + } + } + return false; + } + + private bool UpdateClusterMembership() + { + bool changed = false; + + float[] distances = new float[_numberOfClusters]; + + for (int i = 0; i < _normalizedDataToCluster.Count; ++i) + { + + for (int k = 0; k < _numberOfClusters; ++k) + { + distances[k] = ElucidanDistance(_normalizedDataToCluster[i], _clusters[k]); + } + int newClusterId = MinIndex(distances); + if (newClusterId != _normalizedDataToCluster[i].Cluster) + { + changed = true; + _normalizedDataToCluster[i].Cluster = newClusterId; + + } + else + { + + } + + } + if (changed == false) return false; + //if (AnyAreEmpty(_normalizedDataToCluster)) return false; + return true; + } + + private float ElucidanDistance(DataPoint dataPoint, Vector3 mean) + { + return Vector3.Distance(dataPoint.center, mean); + } + + private int MinIndex(float[] distances) + { + int _indexOfMin = 0; + double _smallDist = distances[0]; + for (int k = 0; k < distances.Length; ++k) + { + if (distances[k] < _smallDist) + { + _smallDist = distances[k]; + _indexOfMin = k; + } + } + return _indexOfMin; + } + + public List<Renderer> GetCluster(int idx, out Vector3 mean, out float size) + { + if (idx < 0 || idx >= _numberOfClusters) + { + Debug.LogError("idx is out of bounds"); + mean = Vector3.zero; + size = 1; + return new List<Renderer>(); + } + UpdateDataPointMeans(true); + List<Renderer> gos = new List<Renderer>(); + mean = _clusters[idx]; + float longestDist = 0; + for (int i = 0; i < _normalizedDataToCluster.Count; i++) + { + if (_normalizedDataToCluster[i].Cluster == idx) + { + float dist = Vector3.Distance(mean, _normalizedDataToCluster[i].center); + if (dist > longestDist) longestDist = dist; + gos.Add(_normalizedDataToCluster[i].gameObject.GetComponent<Renderer>()); + } + } + mean = _clusters[idx]; + size = longestDist; //todo should be greatest distance to mean + return gos; + } + + public void Cluster() + { + bool _changed = true; + bool _success = true; + InitializeCentroids(); + + int maxIteration = _normalizedDataToCluster.Count * 1000; + int _threshold = 0; + while (_success == true && _changed == true && _threshold < maxIteration) + { + ++_threshold; + _success = UpdateDataPointMeans(false); + _changed = UpdateClusterMembership(); + } + } +} + diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_GrouperCluster.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_GrouperCluster.cs.meta new file mode 100644 index 00000000..796d44cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_GrouperCluster.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b20907f4e291cab4eb59a6f9f6ae5d0c +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombiner.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombiner.cs new file mode 100644 index 00000000..c73d3fab --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombiner.cs @@ -0,0 +1,694 @@ +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 { + + //TODO bug with triangles if using showHide with AddDelete reproduce by using the AddDeleteParts script and changeing some of it to show hide + [System.Serializable] + public abstract class MB3_MeshCombiner : MB_IMeshBakerSettings + { + public delegate void GenerateUV2Delegate(Mesh m, float hardAngle, float packMargin); + + public class MBBlendShapeKey + { + public GameObject gameObject; + public int blendShapeIndexInSrc; + + public MBBlendShapeKey(GameObject srcSkinnedMeshRenderGameObject, int blendShapeIndexInSource) + { + gameObject = srcSkinnedMeshRenderGameObject; + blendShapeIndexInSrc = blendShapeIndexInSource; + } + + public override bool Equals(object obj) + { + if (!(obj is MBBlendShapeKey) || obj == null) + { + return false; + } + MBBlendShapeKey other = (MBBlendShapeKey)obj; + return (gameObject == other.gameObject && blendShapeIndexInSrc == other.blendShapeIndexInSrc); + } + + public override int GetHashCode() + { + int hash = 23; + unchecked + { + hash = hash * 31 + gameObject.GetInstanceID(); + hash = hash * 31 + blendShapeIndexInSrc; + } + return hash; + } + } + + public class MBBlendShapeValue + { + public GameObject combinedMeshGameObject; + public int blendShapeIndex; + } + + public static bool EVAL_VERSION { + get { return false; } + } + + [SerializeField] protected MB2_ValidationLevel _validationLevel = MB2_ValidationLevel.robust; + public virtual MB2_ValidationLevel validationLevel + { + get { return _validationLevel; } + set { _validationLevel = value; } + } + + [SerializeField] protected string _name; + public string name + { + get { return _name; } + set { _name = value; } + } + + [SerializeField] protected MB2_TextureBakeResults _textureBakeResults; + public virtual MB2_TextureBakeResults textureBakeResults + { + get { return _textureBakeResults; } + set { _textureBakeResults = value; } + } + + [SerializeField] protected GameObject _resultSceneObject; + public virtual GameObject resultSceneObject + { + get { return _resultSceneObject; } + set { _resultSceneObject = value; } + } + + [SerializeField] protected UnityEngine.Renderer _targetRenderer; + public virtual Renderer targetRenderer + { + get { return _targetRenderer; } + set + { + if (_targetRenderer != null && _targetRenderer != value) + { + Debug.LogWarning("Previous targetRenderer was not null. Combined mesh may be shared by more than one Renderer"); + } + + _targetRenderer = value; + + if (value != null && MB_Utility.IsSceneInstance(value.gameObject) && value.transform.parent != null) + { + _resultSceneObject = value.transform.parent.gameObject; + } + } + } + + [SerializeField] protected MB2_LogLevel _LOG_LEVEL = MB2_LogLevel.info; + public virtual MB2_LogLevel LOG_LEVEL + { + get { return _LOG_LEVEL; } + set { _LOG_LEVEL = value; } + } + + public MB_IMeshBakerSettings settings + { + get + { + if (_settingsHolder != null) + { + return settingsHolder.GetMeshBakerSettings(); + } + else + { + return this; + } + } + } + + /// <summary> + /// This needs to be an Object so it gets serialized and works with SerializedProperty. + /// Would like this to be of type MB_IMeshBakerSettingsHolder + /// </summary> + [SerializeField] protected UnityEngine.Object _settingsHolder; + public virtual MB_IMeshBakerSettingsHolder settingsHolder + { + get { + if (_settingsHolder != null) + { + if (_settingsHolder is MB_IMeshBakerSettingsHolder) + { + return (MB_IMeshBakerSettingsHolder)_settingsHolder; + } else { + _settingsHolder = null; + } + } + return null; + } + set + { + if (value is UnityEngine.Object) + { + _settingsHolder = (UnityEngine.Object)value; + } else + { + Debug.LogError("The settings holder must be a UnityEngine.Object"); + } + } + } + + //----------------------- + [SerializeField] protected MB2_OutputOptions _outputOption; + + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.outputOption NOT this.outputOption THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual MB2_OutputOptions outputOption + { + get { return _outputOption; } + set { _outputOption = value; } + } + + [SerializeField] protected MB_RenderType _renderType; + + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.outputOption NOT this.outputOption THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual MB_RenderType renderType { + get { return _renderType; } + set { _renderType = value; } + } + + [SerializeField] protected MB2_LightmapOptions _lightmapOption = MB2_LightmapOptions.ignore_UV2; + + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.lightmapOption NOT this.lightmapOption THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual MB2_LightmapOptions lightmapOption { + get { return _lightmapOption; } + set { + _lightmapOption = value; + } + } + + [SerializeField] protected bool _doNorm = true; + + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doNorm NOT this.doNorm THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doNorm { + get { return _doNorm; } + set { _doNorm = value; } + } + + + [SerializeField] protected bool _doTan = true; + + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doTan NOT this.doTan THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doTan { + get { return _doTan; } + set { _doTan = value; } + } + + [SerializeField] protected bool _doCol; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doCol NOT this.doCol THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doCol { + get { return _doCol; } + set { _doCol = value; } + } + + [SerializeField] protected bool _doUV = true; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV NOT this.doUV THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV { + get { return _doUV; } + set { _doUV = value; } + } + + /// <summary> + /// only included for backward compatibility. Does nothing + /// </summary> + public virtual bool doUV1 { + get { return false; } + set { } + } + + public virtual bool doUV2() { + bool result = settings.lightmapOption == MB2_LightmapOptions.copy_UV2_unchanged || settings.lightmapOption == MB2_LightmapOptions.preserve_current_lightmapping || settings.lightmapOption == MB2_LightmapOptions.copy_UV2_unchanged_to_separate_rects; + return result; + } + + + [SerializeField] protected bool _doUV3; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV3 NOT this.doUV3 THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV3 { + get { return _doUV3; } + set { _doUV3 = value; } + } + + [SerializeField] protected bool _doUV4; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV4 NOT this.doUV4 THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV4 { + get { return _doUV4; } + set { _doUV4 = value; } + } + + [SerializeField] protected bool _doUV5; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV5 NOT this.doUV5 THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV5 + { + get { return _doUV5; } + set { _doUV5 = value; } + } + + [SerializeField] protected bool _doUV6; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV6 NOT this.doUV6 THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV6 + { + get { return _doUV6; } + set { _doUV6 = value; } + } + + [SerializeField] protected bool _doUV7; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV7 NOT this.doUV7 THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV7 + { + get { return _doUV7; } + set { _doUV7 = value; } + } + + [SerializeField] protected bool _doUV8; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doUV8 NOT this.doUV8 THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doUV8 + { + get { return _doUV8; } + set { _doUV8 = value; } + } + + [SerializeField] + protected bool _doBlendShapes; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.doBlendShapes NOT this.doBlendShapes THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool doBlendShapes + { + get { return _doBlendShapes; } + set { _doBlendShapes = value; } + } + + [UnityEngine.Serialization.FormerlySerializedAs("_recenterVertsToBoundsCenter")] + [SerializeField] + protected MB_MeshPivotLocation _pivotLocationType; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.pivotLocationType NOT this.pivotLocationType THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual MB_MeshPivotLocation pivotLocationType + { + get { return _pivotLocationType; } + set { _pivotLocationType = value; } + } + + [SerializeField] + protected Vector3 _pivotLocation; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.pivotLocation NOT this.pivotLocation THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual Vector3 pivotLocation + { + get { return _pivotLocation; } + set { _pivotLocation = value; } + } + + [SerializeField] + protected bool _clearBuffersAfterBake = false; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.clearBuffersAfterBake NOT this.clearBuffersAfterBake THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public virtual bool clearBuffersAfterBake + { + get { return _clearBuffersAfterBake; } + set { + Debug.LogError("Not implemented."); + _clearBuffersAfterBake = value; + } + } + + [SerializeField] + public bool _optimizeAfterBake = true; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.optimizeAfterBake NOT this.optimizeAfterBake THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public bool optimizeAfterBake + { + get { return _optimizeAfterBake; } + set { _optimizeAfterBake = value; } + } + + [SerializeField] + [UnityEngine.Serialization.FormerlySerializedAs("uv2UnwrappingParamsHardAngle")] + protected float _uv2UnwrappingParamsHardAngle = 60f; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.uv2UnwrappingParamsHardAngle NOT this.uv2UnwrappingParamsHardAngle THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public float uv2UnwrappingParamsHardAngle + { + get { return _uv2UnwrappingParamsHardAngle; } + set { _uv2UnwrappingParamsHardAngle = value; } + } + + [SerializeField] + [UnityEngine.Serialization.FormerlySerializedAs("uv2UnwrappingParamsPackMargin")] + protected float _uv2UnwrappingParamsPackMargin = .005f; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.uv2UnwrappingParamsPackMargin NOT this.uv2UnwrappingParamsPackMargin THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public float uv2UnwrappingParamsPackMargin + { + get { return _uv2UnwrappingParamsPackMargin; } + set { _uv2UnwrappingParamsPackMargin = value; } + } + + [SerializeField] + protected bool _smrNoExtraBonesWhenCombiningMeshRenderers; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.smrNoExtraBonesWhenCombiningMeshRenderers NOT this.smrNoExtraBonesWhenCombiningMeshRenderers THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public bool smrNoExtraBonesWhenCombiningMeshRenderers + { + get { return _smrNoExtraBonesWhenCombiningMeshRenderers; } + set { _smrNoExtraBonesWhenCombiningMeshRenderers = value; } + } + + [SerializeField] + protected bool _smrMergeBlendShapesWithSameNames = false; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.smrMergeBlendShapesWithSameNames NOT this.smrMergeBlendShapesWithSameNames THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public bool smrMergeBlendShapesWithSameNames + { + get { return _smrMergeBlendShapesWithSameNames; } + set { _smrMergeBlendShapesWithSameNames = value; } + } + + [SerializeField] + protected UnityEngine.Object _assignToMeshCustomizer; + /// <summary> + /// ALWAYS ACCESS THROUGH this.settings.assignToMeshCustomizer NOT this.assignToMeshCustomizer THERE MAY BE A SETTINGS HOLDER ASSIGNED. + /// </summary> + public IAssignToMeshCustomizer assignToMeshCustomizer + { + get + { + if (_assignToMeshCustomizer is IAssignToMeshCustomizer) + { + return (IAssignToMeshCustomizer)_assignToMeshCustomizer; + } + else + { + _assignToMeshCustomizer = null; + return null; + } + } + set + { + _assignToMeshCustomizer = (UnityEngine.Object) value; + } + } + + protected bool _usingTemporaryTextureBakeResult; + public abstract int GetLightmapIndex(); + public abstract void ClearBuffers(); + public abstract void ClearMesh(); + public abstract void ClearMesh(MB2_EditorMethodsInterface editorMethods); + public abstract void DisposeRuntimeCreated(); + public abstract void DestroyMesh(); + public abstract void DestroyMeshEditor(MB2_EditorMethodsInterface editorMethods); + public abstract List<GameObject> GetObjectsInCombined(); + public abstract int GetNumObjectsInCombined(); + public abstract int GetNumVerticesFor(GameObject go); + public abstract int GetNumVerticesFor(int instanceID); + + /// <summary> + /// Builds a map for mapping blend shapes in the source SkinnedMeshRenderers to blend shapes in the + /// combined skinned meshes. If you need to serialize the map then use: BuildSourceBlendShapeToCombinedSerializableIndexMap. + /// </summary> + [System.Obsolete("BuildSourceBlendShapeToCombinedIndexMap is deprecated. The map will be attached to the combined SkinnedMeshRenderer object as the MB_BlendShape2CombinedMap Component.")] + public abstract Dictionary<MBBlendShapeKey, MBBlendShapeValue> BuildSourceBlendShapeToCombinedIndexMap(); + + /// <summary> + /// Copies Mesh Baker internal data to the mesh. + /// </summary> + public virtual void Apply(){ + Apply(null); + } + + /// <summary> + /// Copies Mesh Baker internal data to the mesh. + /// </summary> + /// <param name='uv2GenerationMethod'> + /// Uv2 generation method. This is normally editor class method Unwrapping.GenerateSecondaryUVSet + /// </param> + public abstract void Apply(GenerateUV2Delegate uv2GenerationMethod); + + /// <summary> + /// Apply the specified triangles, vertices, normals, tangents, uvs, colors, uv1, uv2, bones and uv2GenerationMethod. + /// </summary> + /// <param name='triangles'> + /// Triangles. + /// </param> + /// <param name='vertices'> + /// Vertices. + /// </param> + /// <param name='normals'> + /// Normals. + /// </param> + /// <param name='tangents'> + /// Tangents. + /// </param> + /// <param name='uvs'> + /// Uvs. + /// </param> + /// <param name='colors'> + /// Colors. + /// </param> + /// <param name='uv3'> + /// Uv3. + /// </param> + /// <param name='uv4'> + /// Uv4. + /// </param> + /// <param name='uv2'> + /// Uv2. + /// </param> + /// <param name='bones'> + /// Bones. + /// </param> + /// <param name='uv2GenerationMethod'> + /// Uv2 generation method. This is normally method Unwrapping.GenerateSecondaryUVSet. This should be null when calling Apply at runtime. + /// </param> + public abstract void Apply(bool triangles, + bool vertices, + bool normals, + bool tangents, + bool uvs, + bool uv2, + bool uv3, + bool uv4, + bool uv5, + bool uv6, + bool uv7, + bool uv8, + bool colors, + bool bones = false, + bool blendShapeFlag = false, + GenerateUV2Delegate uv2GenerationMethod = null); + + /// <summary> + /// Apply the specified triangles, vertices, normals, tangents, uvs, colors, uv1, uv2, bones and uv2GenerationMethod. + /// This is the pre 2018.2 version that does not suport eight UV channels. + /// </summary> + /// <param name='triangles'> + /// Triangles. + /// </param> + /// <param name='vertices'> + /// Vertices. + /// </param> + /// <param name='normals'> + /// Normals. + /// </param> + /// <param name='tangents'> + /// Tangents. + /// </param> + /// <param name='uvs'> + /// Uvs. + /// </param> + /// <param name='colors'> + /// Colors. + /// </param> + /// <param name='uv3'> + /// Uv3. + /// </param> + /// <param name='uv4'> + /// Uv4. + /// </param> + /// <param name='uv2'> + /// Uv2. + /// </param> + /// <param name='bones'> + /// Bones. + /// </param> + /// <param name='uv2GenerationMethod'> + /// Uv2 generation method. This is normally method Unwrapping.GenerateSecondaryUVSet. This should be null when calling Apply at runtime. + /// </param> + public abstract void Apply(bool triangles, + bool vertices, + bool normals, + bool tangents, + bool uvs, + bool uv2, + bool uv3, + bool uv4, + bool colors, + bool bones=false, + bool blendShapeFlag=false, + GenerateUV2Delegate uv2GenerationMethod = null); + + + public virtual bool UpdateGameObjects(GameObject[] gos) + { + return UpdateGameObjects(gos, true, true, true, true, true, false, false, false, + false, false, false, false, false, false); + } + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public virtual bool UpdateGameObjects(GameObject[] gos, bool updateBounds) + { + return UpdateGameObjects(gos, updateBounds, true, true, true, true, false, false, false, false, false, false, false, false, false); + } + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public abstract bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateColors, bool updateSkinningInfo); + + /// <summary> + /// Updates the data in the combined mesh for meshes that are already in the combined mesh. + /// This is faster than adding and removing a mesh and has a much lower memory footprint. + /// This method can only be used if the meshes being updated have the same layout(number of + /// vertices, triangles, submeshes). + /// This is faster than removing and re-adding + /// For efficiency update as few channels as possible. + /// Apply must be called to apply the changes to the combined mesh + /// </summary> + public abstract bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8, + bool updateColors, bool updateSkinningInfo); + + public abstract bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource=true); + + public abstract bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource); + public abstract bool CombinedMeshContains(GameObject go); + public abstract void UpdateSkinnedMeshApproximateBounds(); + public abstract void UpdateSkinnedMeshApproximateBoundsFromBones(); + public abstract void CheckIntegrity(); + + /// <summary> + /// Updates the skinned mesh approximate bounds from the bounds of the source objects. + /// </summary> + public abstract void UpdateSkinnedMeshApproximateBoundsFromBounds(); + + /// <summary> + /// Updates the skinned mesh bounds by creating a bounding box that contains the bones (skeleton) of the source objects. + /// </summary> + public static void UpdateSkinnedMeshApproximateBoundsFromBonesStatic(Transform[] bs, SkinnedMeshRenderer smr){ + Vector3 max, min; + max = bs[0].position; + min = bs[0].position; + for (int i = 1; i < bs.Length; i++){ + Vector3 v = bs[i].position; + if (v.x < min.x) min.x = v.x; + if (v.y < min.y) min.y = v.y; + if (v.z < min.z) min.z = v.z; + if (v.x > max.x) max.x = v.x; + if (v.y > max.y) max.y = v.y; + if (v.z > max.z) max.z = v.z; + } + Vector3 center = (max + min)/2f; + Vector3 size = max - min; + Matrix4x4 w2l = smr.worldToLocalMatrix; + Bounds b = new Bounds(w2l * center, w2l * size); + smr.localBounds = b; + } + + public static void UpdateSkinnedMeshApproximateBoundsFromBoundsStatic(List<GameObject> objectsInCombined,SkinnedMeshRenderer smr){ + Bounds b = new Bounds(); + Bounds bigB = new Bounds(); + if (MB_Utility.GetBounds(objectsInCombined[0],out b)){ + bigB = b; + } else { + Debug.LogError("Could not get bounds. Not updating skinned mesh bounds"); + return; + } + for (int i = 1; i < objectsInCombined.Count; i++){ + if (MB_Utility.GetBounds(objectsInCombined[i],out b)){ + bigB.Encapsulate(b); + } else { + Debug.LogError("Could not get bounds. Not updating skinned mesh bounds"); + return; + } + } + smr.localBounds = bigB; + } + + protected virtual bool _CreateTemporaryTextrueBakeResult(GameObject[] gos, List<Material> matsOnTargetRenderer){ + if (GetNumObjectsInCombined() > 0) + { + Debug.LogError("Can't add objects if there are already objects in combined mesh when 'Texture Bake Result' is not set. Perhaps enable 'Clear Buffers After Bake'"); + return false; + } + _usingTemporaryTextureBakeResult = true; + _textureBakeResults = MB2_TextureBakeResults.CreateForMaterialsOnRenderer(gos, matsOnTargetRenderer); + return true; + } + + public abstract List<Material> GetMaterialsOnTargetRenderer(); + + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombiner.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombiner.cs.meta new file mode 100644 index 00000000..634f68c6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombiner.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 12884bd09c80e3145bee1a2fa1baf185 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSettings.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSettings.cs new file mode 100644 index 00000000..a9d15158 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSettings.cs @@ -0,0 +1,219 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace DigitalOpus.MB.Core +{ + public enum MB_MeshPivotLocation + { + worldOrigin, + boundsCenter, + customLocation, + } + + [System.Serializable] + public class MB3_MeshCombinerSettingsData : MB_IMeshBakerSettings + { + [SerializeField] protected MB_RenderType _renderType; + public virtual MB_RenderType renderType + { + get { return _renderType; } + set { _renderType = value; } + } + + [SerializeField] protected MB2_OutputOptions _outputOption; + public virtual MB2_OutputOptions outputOption + { + get { return _outputOption; } + set { _outputOption = value; } + } + + [SerializeField] protected MB2_LightmapOptions _lightmapOption = MB2_LightmapOptions.ignore_UV2; + public virtual MB2_LightmapOptions lightmapOption + { + get { return _lightmapOption; } + set { _lightmapOption = value; } + } + + [SerializeField] protected bool _doNorm = true; + public virtual bool doNorm + { + get { return _doNorm; } + set { _doNorm = value; } + } + + [SerializeField] protected bool _doTan = true; + public virtual bool doTan + { + get { return _doTan; } + set { _doTan = value; } + } + + [SerializeField] protected bool _doCol; + public virtual bool doCol + { + get { return _doCol; } + set { _doCol = value; } + } + + [SerializeField] protected bool _doUV = true; + public virtual bool doUV + { + get { return _doUV; } + set { _doUV = value; } + } + + [SerializeField] protected bool _doUV3; + public virtual bool doUV3 + { + get { return _doUV3; } + set { _doUV3 = value; } + } + + [SerializeField] protected bool _doUV4; + public virtual bool doUV4 + { + get { return _doUV4; } + set { _doUV4 = value; } + } + + [SerializeField] protected bool _doUV5; + public virtual bool doUV5 + { + get { return _doUV5; } + set { _doUV5 = value; } + } + + [SerializeField] protected bool _doUV6; + public virtual bool doUV6 + { + get { return _doUV6; } + set { _doUV6 = value; } + } + + [SerializeField] protected bool _doUV7; + public virtual bool doUV7 + { + get { return _doUV7; } + set { _doUV7 = value; } + } + + [SerializeField] protected bool _doUV8; + public virtual bool doUV8 + { + get { return _doUV8; } + set { _doUV8 = value; } + } + + [SerializeField] + protected bool _doBlendShapes; + public virtual bool doBlendShapes + { + get { return _doBlendShapes; } + set { _doBlendShapes = value; } + } + + [UnityEngine.Serialization.FormerlySerializedAs("_recenterVertsToBoundsCenter")] + [SerializeField] + protected MB_MeshPivotLocation _pivotLocationType; + public virtual MB_MeshPivotLocation pivotLocationType + { + get { return _pivotLocationType; } + set{ _pivotLocationType = value; } + } + + [SerializeField] + protected Vector3 _pivotLocation; + public virtual Vector3 pivotLocation + { + get { return _pivotLocation; } + set { _pivotLocation = value; } + } + + [SerializeField] + protected bool _clearBuffersAfterBake = false; + public bool clearBuffersAfterBake + { + get { return _clearBuffersAfterBake; } + set { _clearBuffersAfterBake = value; } + } + + [SerializeField] + public bool _optimizeAfterBake = true; + public bool optimizeAfterBake + { + get { return _optimizeAfterBake; } + set { _optimizeAfterBake = value; } + } + + [SerializeField] + protected float _uv2UnwrappingParamsHardAngle = 60f; + public float uv2UnwrappingParamsHardAngle + { + get { return _uv2UnwrappingParamsHardAngle; } + set { _uv2UnwrappingParamsHardAngle = value; } + } + + [SerializeField] + protected float _uv2UnwrappingParamsPackMargin = .005f; + public float uv2UnwrappingParamsPackMargin + { + get { return _uv2UnwrappingParamsPackMargin; } + set { _uv2UnwrappingParamsPackMargin = value; } + } + + [SerializeField] + protected bool _smrNoExtraBonesWhenCombiningMeshRenderers; + public bool smrNoExtraBonesWhenCombiningMeshRenderers + { + get { return _smrNoExtraBonesWhenCombiningMeshRenderers; } + set { _smrNoExtraBonesWhenCombiningMeshRenderers = value; } + } + + [SerializeField] + protected bool _smrMergeBlendShapesWithSameNames = false; + public bool smrMergeBlendShapesWithSameNames + { + get { return _smrMergeBlendShapesWithSameNames; } + set { _smrMergeBlendShapesWithSameNames = value; } + } + + [SerializeField] + protected UnityEngine.Object _assignToMeshCustomizer; + public IAssignToMeshCustomizer assignToMeshCustomizer + { + get + { + if (_assignToMeshCustomizer is IAssignToMeshCustomizer) + { + return (IAssignToMeshCustomizer) _assignToMeshCustomizer; + } + else + { + _assignToMeshCustomizer = null; + return null; + } + } + set + { + _assignToMeshCustomizer = (UnityEngine.Object)value; + } + } + } + + [CreateAssetMenu(fileName = "MeshBakerSettings", menuName = "Mesh Baker/Mesh Baker Settings")] + public class MB3_MeshCombinerSettings : ScriptableObject, MB_IMeshBakerSettingsHolder + { + public MB3_MeshCombinerSettingsData data; + + public MB_IMeshBakerSettings GetMeshBakerSettings() + { + return data; + } + public void GetMeshBakerSettingsAsSerializedProperty(out string propertyName, out UnityEngine.Object targetObj) + { + targetObj = this; + propertyName = "data"; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSettings.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSettings.cs.meta new file mode 100644 index 00000000..45ee5b5d --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSettings.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 6f9a961bf2247e448a9cf290130254df +timeCreated: 1555969770 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimple.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimple.cs new file mode 100644 index 00000000..dcf4609b --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimple.cs @@ -0,0 +1,2500 @@ +using UnityEngine; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core +{ + /// <summary> + /// 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. + /// </summary> + [System.Serializable] + public partial class MB3_MeshCombinerSingle : MB3_MeshCombiner + { + public override MB2_TextureBakeResults textureBakeResults + { + set + { + if (mbDynamicObjectsInCombinedMesh.Count > 0 && _textureBakeResults != value && _textureBakeResults != null) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("If Texture Bake Result is changed then objects currently in combined mesh may be invalid."); + } + _textureBakeResults = value; + } + } + + public override MB_RenderType renderType + { + set + { + if (value == MB_RenderType.skinnedMeshRenderer && _renderType == MB_RenderType.meshRenderer) + { + if (boneWeights.Length != verts.Length) Debug.LogError("Can't set the render type to SkinnedMeshRenderer without clearing the mesh first. Try deleteing the CombinedMesh scene object."); + } + _renderType = value; + } + } + + public override GameObject resultSceneObject + { + set + { + if (_resultSceneObject != value && _resultSceneObject != null) + { + _targetRenderer = null; + if (_mesh != null && LOG_LEVEL >= MB2_LogLevel.warn) + { + Debug.LogWarning("Result Scene Object was changed when this mesh baker component had a reference to a mesh. If mesh is being used by another object make sure to reset the mesh to none before baking to avoid overwriting the other mesh."); + } + } + _resultSceneObject = value; + } + } + + //this contains object instances that have been added to the combined mesh through AddDelete + [SerializeField] + protected List<GameObject> objectsInCombinedMesh = new List<GameObject>(); + + [SerializeField] + int lightmapIndex = -1; + + [SerializeField] + List<MB_DynamicGameObject> mbDynamicObjectsInCombinedMesh = new List<MB_DynamicGameObject>(); + Dictionary<GameObject, MB_DynamicGameObject> _instance2combined_map = new Dictionary<GameObject, MB_DynamicGameObject>(); + + [SerializeField] + Vector3[] verts = new Vector3[0]; + [SerializeField] + Vector3[] normals = new Vector3[0]; + [SerializeField] + Vector4[] tangents = new Vector4[0]; + [SerializeField] + Vector2[] uvs = new Vector2[0]; + [SerializeField] + float[] uvsSliceIdx = new float[0]; + [SerializeField] + Vector2[] uv2s = new Vector2[0]; + [SerializeField] + Vector2[] uv3s = new Vector2[0]; + [SerializeField] + Vector2[] uv4s = new Vector2[0]; + + [SerializeField] + Vector2[] uv5s = new Vector2[0]; + [SerializeField] + Vector2[] uv6s = new Vector2[0]; + [SerializeField] + Vector2[] uv7s = new Vector2[0]; + [SerializeField] + Vector2[] uv8s = new Vector2[0]; + + [SerializeField] + Color[] colors = new Color[0]; + [SerializeField] + Matrix4x4[] bindPoses = new Matrix4x4[0]; + [SerializeField] + Transform[] bones = new Transform[0]; + [SerializeField] + internal MBBlendShape[] blendShapes = new MBBlendShape[0]; + [SerializeField] + //these blend shapes are not cleared they are used to build the src to combined blend shape map + internal MBBlendShape[] blendShapesInCombined = new MBBlendShape[0]; + + [SerializeField] + SerializableIntArray[] submeshTris = new SerializableIntArray[0]; + + [SerializeField] + MeshCreationConditions _meshBirth = MeshCreationConditions.NoMesh; + + [SerializeField] + Mesh _mesh; + + //unity won't serialize these + BoneWeight[] boneWeights = new BoneWeight[0]; + + //used if user passes null in as parameter to AddOrDelete + GameObject[] empty = new GameObject[0]; + int[] emptyIDs = new int[0]; + + MB_DynamicGameObject instance2Combined_MapGet(GameObject gameObjectID) + { + return _instance2combined_map[gameObjectID]; + } + + void instance2Combined_MapAdd(GameObject gameObjectID, MB_DynamicGameObject dgo) + { + _instance2combined_map.Add(gameObjectID, dgo); + } + + void instance2Combined_MapRemove(GameObject gameObjectID) + { + _instance2combined_map.Remove(gameObjectID); + } + + bool instance2Combined_MapTryGetValue(GameObject gameObjectID, out MB_DynamicGameObject dgo) + { + return _instance2combined_map.TryGetValue(gameObjectID, out dgo); + } + + int instance2Combined_MapCount() + { + return _instance2combined_map.Count; + } + + void instance2Combined_MapClear() + { + _instance2combined_map.Clear(); + } + + bool instance2Combined_MapContainsKey(GameObject gameObjectID) + { + return _instance2combined_map.ContainsKey(gameObjectID); + } + + bool InstanceID2DGO(int instanceID, out MB_DynamicGameObject dgoGameObject) + { + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + if (mbDynamicObjectsInCombinedMesh[i].instanceID == instanceID) + { + dgoGameObject = mbDynamicObjectsInCombinedMesh[i]; + return true; + } + } + + dgoGameObject = null; + return false; + } + + public override int GetNumObjectsInCombined() + { + return mbDynamicObjectsInCombinedMesh.Count; + } + + public override List<GameObject> GetObjectsInCombined() + { + List<GameObject> outObs = new List<GameObject>(); + outObs.AddRange(objectsInCombinedMesh); + return outObs; + } + + public Mesh GetMesh() + { + if (_mesh == null) + { + _mesh = NewMesh(); + } + return _mesh; + } + + public void SetMesh(Mesh m) + { + if (m == null) + { + _meshBirth = MeshCreationConditions.AssignedByUser; + } + else + { + _meshBirth = MeshCreationConditions.NoMesh; + } + + _mesh = m; + } + + public Transform[] GetBones() + { + return bones; + } + + public override int GetLightmapIndex() + { + if (settings.lightmapOption == MB2_LightmapOptions.generate_new_UV2_layout || settings.lightmapOption == MB2_LightmapOptions.preserve_current_lightmapping) + { + return lightmapIndex; + } + else { + return -1; + } + } + + public override int GetNumVerticesFor(GameObject go) + { + return GetNumVerticesFor(go.GetInstanceID()); + } + + public override int GetNumVerticesFor(int instanceID) + { + MB_DynamicGameObject dgo = null; + InstanceID2DGO(instanceID, out dgo); + if (dgo != null) + { + return dgo.numVerts; + } + else { + return -1; + } + } + + bool _Initialize(int numResultMats) + { + if (mbDynamicObjectsInCombinedMesh.Count == 0) + { + lightmapIndex = -1; + } + + if (_mesh == null) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("_initialize Creating new Mesh"); + _mesh = GetMesh(); + } + + if (instance2Combined_MapCount() != mbDynamicObjectsInCombinedMesh.Count) + { + //build the instance2Combined map + instance2Combined_MapClear(); + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + if (mbDynamicObjectsInCombinedMesh[i] != null) + { + if (mbDynamicObjectsInCombinedMesh[i].gameObject == null) + { + Debug.LogError("This MeshBaker contains information from a previous bake that is incomlete. It may have been baked by a previous version of Mesh Baker. If you are trying to update/modify a previously baked combined mesh. Try doing the original bake."); + return false; + } + + instance2Combined_MapAdd(mbDynamicObjectsInCombinedMesh[i].gameObject, mbDynamicObjectsInCombinedMesh[i]); + } + } + //BoneWeights are not serialized get from combined mesh + boneWeights = _mesh.boneWeights; + } + + if (objectsInCombinedMesh.Count == 0) + { + if (submeshTris.Length != numResultMats) + { + submeshTris = new SerializableIntArray[numResultMats]; + for (int i = 0; i < submeshTris.Length; i++) submeshTris[i] = new SerializableIntArray(0); + } + } + + //MeshBaker was baked using old system that had duplicated bones. Upgrade to new system + //need to build indexesOfBonesUsed maps for dgos + if (mbDynamicObjectsInCombinedMesh.Count > 0 && + mbDynamicObjectsInCombinedMesh[0].indexesOfBonesUsed.Length == 0 && + settings.renderType == MB_RenderType.skinnedMeshRenderer && + boneWeights.Length > 0) + { + + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = mbDynamicObjectsInCombinedMesh[i]; + HashSet<int> idxsOfBonesUsed = new HashSet<int>(); + for (int j = dgo.vertIdx; j < dgo.vertIdx + dgo.numVerts; j++) + { + if (boneWeights[j].weight0 > 0f) idxsOfBonesUsed.Add(boneWeights[j].boneIndex0); + if (boneWeights[j].weight1 > 0f) idxsOfBonesUsed.Add(boneWeights[j].boneIndex1); + if (boneWeights[j].weight2 > 0f) idxsOfBonesUsed.Add(boneWeights[j].boneIndex2); + if (boneWeights[j].weight3 > 0f) idxsOfBonesUsed.Add(boneWeights[j].boneIndex3); + } + dgo.indexesOfBonesUsed = new int[idxsOfBonesUsed.Count]; + idxsOfBonesUsed.CopyTo(dgo.indexesOfBonesUsed); + } + if (LOG_LEVEL >= MB2_LogLevel.debug) + Debug.Log("Baker used old systems that duplicated bones. Upgrading to new system by building indexesOfBonesUsed"); + } + if (LOG_LEVEL >= MB2_LogLevel.trace) { + Debug.Log (String.Format ("_initialize numObjsInCombined={0}", mbDynamicObjectsInCombinedMesh.Count)); + } + + return true; + } + + bool _collectMaterialTriangles(Mesh m, MB_DynamicGameObject dgo, Material[] sharedMaterials, OrderedDictionary sourceMats2submeshIdx_map) + { + //everything here applies to the source object being added + int numTriMeshes = m.subMeshCount; + if (sharedMaterials.Length < numTriMeshes) numTriMeshes = sharedMaterials.Length; + dgo._tmpSubmeshTris = new SerializableIntArray[numTriMeshes]; + dgo.targetSubmeshIdxs = new int[numTriMeshes]; + for (int i = 0; i < numTriMeshes; i++) + { + if (_textureBakeResults.doMultiMaterial || _textureBakeResults.resultType == MB2_TextureBakeResults.ResultType.textureArray) + { + if (!sourceMats2submeshIdx_map.Contains(sharedMaterials[i])) + { + Debug.LogError("Object " + dgo.name + " has a material that was not found in the result materials maping. " + sharedMaterials[i]); + return false; + } + dgo.targetSubmeshIdxs[i] = (int)sourceMats2submeshIdx_map[sharedMaterials[i]]; + } + else { + dgo.targetSubmeshIdxs[i] = 0; + } + dgo._tmpSubmeshTris[i] = new SerializableIntArray(); + dgo._tmpSubmeshTris[i].data = m.GetTriangles(i); + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Collecting triangles for: " + dgo.name + " submesh:" + i + " maps to submesh:" + dgo.targetSubmeshIdxs[i] + " added:" + dgo._tmpSubmeshTris[i].data.Length, LOG_LEVEL); + } + return true; + } + + // if adding many copies of the same mesh want to cache obUVsResults + bool _collectOutOfBoundsUVRects2(Mesh m, MB_DynamicGameObject dgo, Material[] sharedMaterials, OrderedDictionary sourceMats2submeshIdx_map, Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResults, MeshChannelsCache meshChannelCache) + { + if (_textureBakeResults == null) + { + Debug.LogError("Need to bake textures into combined material"); + return false; + } + + MB_Utility.MeshAnalysisResult[] res; + if (!meshAnalysisResults.TryGetValue(m.GetInstanceID(), out res)) + { + // Process the mesh and cache the result. + int numSrcSubMeshes = m.subMeshCount; + res = new MB_Utility.MeshAnalysisResult[numSrcSubMeshes]; + Vector2[] uvs = meshChannelCache.GetUv0Raw(m); + for (int submeshIdx = 0; submeshIdx < numSrcSubMeshes; submeshIdx++) + { + MB_Utility.hasOutOfBoundsUVs(uvs, m, ref res[submeshIdx], submeshIdx); + } + + meshAnalysisResults.Add(m.GetInstanceID(), res); + } + + int numUsedSrcSubMeshes = sharedMaterials.Length; + if (numUsedSrcSubMeshes > m.subMeshCount) numUsedSrcSubMeshes = m.subMeshCount; + dgo.obUVRects = new Rect[numUsedSrcSubMeshes]; + + // We might have fewer sharedMaterials than submeshes in the mesh. + for (int submeshIdx = 0; submeshIdx < numUsedSrcSubMeshes; submeshIdx++) + { + int idxInResultMats = dgo.targetSubmeshIdxs[submeshIdx]; + if (_textureBakeResults.GetConsiderMeshUVs(idxInResultMats, sharedMaterials[submeshIdx])) + { + dgo.obUVRects[submeshIdx] = res[submeshIdx].uvRect; + } + } + + return true; + } + + bool _validateTextureBakeResults() + { + if (_textureBakeResults == null) + { + Debug.LogError("Texture Bake Results is null. Can't combine meshes."); + return false; + } + if (_textureBakeResults.materialsAndUVRects == null || _textureBakeResults.materialsAndUVRects.Length == 0) + { + Debug.LogError("Texture Bake Results has no materials in material to sourceUVRect map. Try baking materials. Can't combine meshes. " + + "If you are trying to combine meshes without combining materials, try removing the Texture Bake Result."); + return false; + } + + if (_textureBakeResults.NumResultMaterials() == 0) + { + Debug.LogError("Texture Bake Results has no result materials. Try baking materials. Can't combine meshes."); + return false; + } + + if (settings.doUV && textureBakeResults.resultType == MB2_TextureBakeResults.ResultType.textureArray) + { + if (uvs.Length != uvsSliceIdx.Length) + { + Debug.LogError("uvs buffer and sliceIdx buffer are different sizes. Did you switch texture bake result from atlas to texture array result?"); + return false; + } + } + + return true; + } + + /* + bool _validateMeshFlags() + { + if (mbDynamicObjectsInCombinedMesh.Count > 0) + { + if (settings.doNorm == false && doNorm == true || + settings.doTan == false && doTan == true || + settings.doCol == false && doCol == true || + settings.doUV == false && doUV == true || + settings.doUV3 == false && doUV3 == true || + settings.doUV4 == false && doUV4 == true) + { + Debug.LogError("The channels have changed. There are already objects in the combined mesh that were added with a different set of channels."); + return false; + } + } + settings.doNorm = doNorm; + settings.doTan = doTan; + settings.doCol = doCol; + settings.doUV = doUV; + settings.doUV3 = doUV3; + settings.doUV4 = doUV4; + return true; + } + */ + + bool _showHide(GameObject[] goToShow, GameObject[] goToHide) + { + if (goToShow == null) goToShow = empty; + if (goToHide == null) goToHide = empty; + //calculate amount to hide + int numResultMats = _textureBakeResults.NumResultMaterials(); + if (!_Initialize(numResultMats)) + { + return false; + } + + for (int i = 0; i < goToHide.Length; i++) + { + if (!instance2Combined_MapContainsKey(goToHide[i])) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Trying to hide an object " + goToHide[i] + " that is not in combined mesh. Did you initially bake with 'clear buffers after bake' enabled?"); + return false; + } + } + + //now to show + for (int i = 0; i < goToShow.Length; i++) + { + if (!instance2Combined_MapContainsKey(goToShow[i])) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Trying to show an object " + goToShow[i] + " that is not in combined mesh. Did you initially bake with 'clear buffers after bake' enabled?"); + return false; + } + } + + //set flags + for (int i = 0; i < goToHide.Length; i++) _instance2combined_map[goToHide[i]].show = false; + for (int i = 0; i < goToShow.Length; i++) _instance2combined_map[goToShow[i]].show = true; + + return true; + } + + bool _addToCombined(GameObject[] goToAdd, int[] goToDelete, bool disableRendererInSource) + { + System.Diagnostics.Stopwatch sw = null; + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + } + GameObject[] _goToAdd; + int[] _goToDelete; + if (!_validateTextureBakeResults()) return false; + if (!ValidateTargRendererAndMeshAndResultSceneObj()) return false; + + if (outputOption != MB2_OutputOptions.bakeMeshAssetsInPlace && + settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + if (_targetRenderer == null || !(_targetRenderer is SkinnedMeshRenderer)) + { + Debug.LogError("Target renderer must be set and must be a SkinnedMeshRenderer"); + return false; + } + } + if (settings.doBlendShapes && settings.renderType != MB_RenderType.skinnedMeshRenderer) + { + Debug.LogError("If doBlendShapes is set then RenderType must be skinnedMeshRenderer."); + return false; + } + if (goToAdd == null) _goToAdd = empty; + else _goToAdd = (GameObject[])goToAdd.Clone(); + if (goToDelete == null) _goToDelete = emptyIDs; + else _goToDelete = (int[])goToDelete.Clone(); + if (_mesh == null) DestroyMesh(); //cleanup maps and arrays + + //MB2_TextureBakeResults.Material2AtlasRectangleMapper mat2rect_map = new MB2_TextureBakeResults.Material2AtlasRectangleMapper(textureBakeResults); + UVAdjuster_Atlas uvAdjuster = new UVAdjuster_Atlas(textureBakeResults, LOG_LEVEL); + + int numResultMats = _textureBakeResults.NumResultMaterials(); + if (!_Initialize(numResultMats)) + { + return false; + } + + if (submeshTris.Length != numResultMats) + { + Debug.LogError("The number of submeshes " + submeshTris.Length + " in the combined mesh was not equal to the number of result materials " + numResultMats + " in the Texture Bake Result"); + return false; + } + + if (_mesh.vertexCount > 0 && _instance2combined_map.Count == 0) + { + Debug.LogWarning("There were vertices in the combined mesh but nothing in the MeshBaker buffers. If you are trying to bake in the editor and modify at runtime, make sure 'Clear Buffers After Bake' is unchecked."); + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("==== Calling _addToCombined objs adding:" + _goToAdd.Length + " objs deleting:" + _goToDelete.Length + " fixOutOfBounds:" + textureBakeResults.DoAnyResultMatsUseConsiderMeshUVs() + " doMultiMaterial:" + textureBakeResults.doMultiMaterial + " disableRenderersInSource:" + disableRendererInSource, LOG_LEVEL); + + //backward compatibility set up resultMaterials if it is blank + if (_textureBakeResults.NumResultMaterials() == 0) + { + Debug.LogError("No resultMaterials in this TextureBakeResults. Try baking textures."); + return false; + } + + OrderedDictionary sourceMats2submeshIdx_map = BuildSourceMatsToSubmeshIdxMap(numResultMats); + if (sourceMats2submeshIdx_map == null) + { + return false; + } + + //STEP 1 update our internal description of objects being added and deleted keep track of changes to buffer sizes as we do. + //calculate amount to delete + int totalDeleteVerts = 0; + int[] totalDeleteSubmeshTris = new int[numResultMats]; + int totalDeleteBlendShapes = 0; + + //in order to decide if a bone can be deleted need to know which dgos use it so build a map + MB3_MeshCombinerSimpleBones boneProcessor = new MB3_MeshCombinerSimpleBones(this); + boneProcessor.BuildBoneIdx2DGOMapIfNecessary(_goToDelete); + for (int i = 0; i < _goToDelete.Length; i++) + { + MB_DynamicGameObject dgo = null; + InstanceID2DGO(_goToDelete[i], out dgo); + if (dgo != null) + { + totalDeleteVerts += dgo.numVerts; + totalDeleteBlendShapes += dgo.numBlendShapes; + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + boneProcessor.FindBonesToDelete(dgo); + } + for (int j = 0; j < dgo.submeshNumTris.Length; j++) + { + totalDeleteSubmeshTris[j] += dgo.submeshNumTris[j]; + } + } + else { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Trying to delete an object that is not in combined mesh"); + } + } + + //now add + List<MB_DynamicGameObject> toAddDGOs = new List<MB_DynamicGameObject>(); + Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache = new Dictionary<int, MB_Utility.MeshAnalysisResult[]>(); //cache results + + //we are often adding the same sharedMesh many times. Only want to grab the results once and cache them + MeshChannelsCache meshChannelCache = new MeshChannelsCache(LOG_LEVEL, settings.lightmapOption); + + int totalAddVerts = 0; + int[] totalAddSubmeshTris = new int[numResultMats]; + int totalAddBlendShapes = 0; + + for (int i = 0; i < _goToAdd.Length; i++) + { + // if not already in mesh or we are deleting and re-adding in same operation + if (!instance2Combined_MapContainsKey(_goToAdd[i]) || Array.FindIndex<int>(_goToDelete, o => o == _goToAdd[i].GetInstanceID()) != -1) + { + MB_DynamicGameObject dgo = new MB_DynamicGameObject(); + + GameObject go = _goToAdd[i]; + + Material[] sharedMaterials = MB_Utility.GetGOMaterials(go); + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(String.Format("Getting {0} shared materials for {1}",sharedMaterials.Length, go)); + if (sharedMaterials == null) + { + Debug.LogError("Object " + go.name + " does not have a Renderer"); + _goToAdd[i] = null; + return false; + } + + Mesh m = MB_Utility.GetMesh(go); + if (sharedMaterials.Length > m.subMeshCount) + { + // The extra materials do nothing but could cause bugs. + Array.Resize(ref sharedMaterials, m.subMeshCount); + } + + if (m == null) + { + Debug.LogError("Object " + go.name + " MeshFilter or SkinedMeshRenderer had no mesh"); + _goToAdd[i] = null; + return false; + } + else if (MBVersion.IsRunningAndMeshNotReadWriteable(m)) + { + Debug.LogError("Object " + go.name + " Mesh Importer has read/write flag set to 'false'. This needs to be set to 'true' in order to read data from this mesh."); + _goToAdd[i] = null; + return false; + } + + if (!uvAdjuster.MapSharedMaterialsToAtlasRects(sharedMaterials, false, m, meshChannelCache, meshAnalysisResultsCache, sourceMats2submeshIdx_map, go, dgo)) + { + _goToAdd[i] = null; + return false; + } + + if (_goToAdd[i] != null) + { + toAddDGOs.Add(dgo); + dgo.name = String.Format("{0} {1}", _goToAdd[i].ToString(), _goToAdd[i].GetInstanceID()); + dgo.instanceID = _goToAdd[i].GetInstanceID(); + dgo.gameObject = _goToAdd[i]; + dgo.numVerts = m.vertexCount; + + if (settings.doBlendShapes) + { + dgo.numBlendShapes = m.blendShapeCount; + } + Renderer r = MB_Utility.GetRenderer(go); + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + if (!boneProcessor.CollectBonesToAddForDGO(dgo, r, settings.smrNoExtraBonesWhenCombiningMeshRenderers, meshChannelCache)) + { + Debug.LogError("Object " + go.name + " could not collect bones."); + _goToAdd[i] = null; + return false; + } + } + if (lightmapIndex == -1) + { + lightmapIndex = r.lightmapIndex; //initialize + } + if (settings.lightmapOption == MB2_LightmapOptions.preserve_current_lightmapping) + { + if (lightmapIndex != r.lightmapIndex) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Object " + go.name + " has a different lightmap index. Lightmapping will not work."); + } + if (!MBVersion.GetActive(go)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Object " + go.name + " is inactive. Can only get lightmap index of active objects."); + } + if (r.lightmapIndex == -1) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Object " + go.name + " does not have an index to a lightmap."); + } + } + dgo.lightmapIndex = r.lightmapIndex; + dgo.lightmapTilingOffset = MBVersion.GetLightmapTilingOffset(r); + if (!_collectMaterialTriangles(m, dgo, sharedMaterials, sourceMats2submeshIdx_map)) + { + return false; + } + dgo.meshSize = r.bounds.size; + dgo.submeshNumTris = new int[numResultMats]; + dgo.submeshTriIdxs = new int[numResultMats]; + dgo.sourceSharedMaterials = sharedMaterials; + + bool doAnyResultsUseConsiderMeshUVs = textureBakeResults.DoAnyResultMatsUseConsiderMeshUVs(); + if (doAnyResultsUseConsiderMeshUVs) + { + if (!_collectOutOfBoundsUVRects2(m, dgo, sharedMaterials, sourceMats2submeshIdx_map, meshAnalysisResultsCache, meshChannelCache)) + { + return false; + } + } + + totalAddVerts += dgo.numVerts; + totalAddBlendShapes += dgo.numBlendShapes; + for (int j = 0; j < dgo._tmpSubmeshTris.Length; j++) + { + totalAddSubmeshTris[dgo.targetSubmeshIdxs[j]] += dgo._tmpSubmeshTris[j].data.Length; + } + + dgo.invertTriangles = IsMirrored(go.transform.localToWorldMatrix); + + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.uvRects.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.sourceSharedMaterials.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.encapsulatingRect.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.sourceMaterialTiling.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + if (doAnyResultsUseConsiderMeshUVs) Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.obUVRects.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + } + } + else { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Object " + _goToAdd[i].name + " has already been added"); + _goToAdd[i] = null; + } + } + + for (int i = 0; i < _goToAdd.Length; i++) + { + if (_goToAdd[i] != null && disableRendererInSource) + { + MB_Utility.DisableRendererInSource(_goToAdd[i]); + if (LOG_LEVEL == MB2_LogLevel.trace) Debug.Log("Disabling renderer on " + _goToAdd[i].name + " id=" + _goToAdd[i].GetInstanceID()); + } + } + + //STEP 2 to allocate new buffers and copy everything over + int newVertSize = verts.Length + totalAddVerts - totalDeleteVerts; + int newBonesSize = boneProcessor.GetNewBonesLength(); + int[] newSubmeshTrisSize = new int[numResultMats]; + int newBlendShapeSize = 0; + if (settings.doBlendShapes) newBlendShapeSize = blendShapes.Length + totalAddBlendShapes - totalDeleteBlendShapes; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Verts adding:" + totalAddVerts + " deleting:" + totalDeleteVerts + " submeshes:" + newSubmeshTrisSize.Length + " bones:" + newBonesSize + " blendShapes:" + newBlendShapeSize); + + for (int i = 0; i < newSubmeshTrisSize.Length; i++) + { + newSubmeshTrisSize[i] = submeshTris[i].data.Length + totalAddSubmeshTris[i] - totalDeleteSubmeshTris[i]; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(" submesh :" + i + " already contains:" + submeshTris[i].data.Length + " tris to be Added:" + totalAddSubmeshTris[i] + " tris to be Deleted:" + totalDeleteSubmeshTris[i]); + } + + if (newVertSize >= MBVersion.MaxMeshVertexCount()) + { + Debug.LogError("Cannot add objects. Resulting mesh will have more than " + MBVersion.MaxMeshVertexCount() + " vertices. Try using a Multi-MeshBaker component. This will split the combined mesh into several meshes. You don't have to re-configure the MB2_TextureBaker. Just remove the MB2_MeshBaker component and add a MB2_MultiMeshBaker component."); + return false; + } + + Vector3[] nnormals = null; + Vector4[] ntangents = null; + Vector2[] nuvs = null, nuv2s = null, nuv3s = null, nuv4s = null, nuv5s = null, nuv6s = null, nuv7s = null, nuv8s = null; + float[] nuvsSliceIdx = null; + Color[] ncolors = null; + MBBlendShape[] nblendShapes = null; + Vector3[] nverts = new Vector3[newVertSize]; + + if (settings.doNorm) nnormals = new Vector3[newVertSize]; + if (settings.doTan) ntangents = new Vector4[newVertSize]; + if (settings.doUV) nuvs = new Vector2[newVertSize]; + if (settings.doUV && textureBakeResults.resultType == MB2_TextureBakeResults.ResultType.textureArray) nuvsSliceIdx = new float[newVertSize]; + if (settings.doUV3) nuv3s = new Vector2[newVertSize]; + if (settings.doUV4) nuv4s = new Vector2[newVertSize]; + + if (settings.doUV5) nuv5s = new Vector2[newVertSize]; + if (settings.doUV6) nuv6s = new Vector2[newVertSize]; + if (settings.doUV7) nuv7s = new Vector2[newVertSize]; + if (settings.doUV8) nuv8s = new Vector2[newVertSize]; + + if (doUV2()) + { + nuv2s = new Vector2[newVertSize]; + } + if (settings.doCol) ncolors = new Color[newVertSize]; + if (settings.doBlendShapes) nblendShapes = new MBBlendShape[newBlendShapeSize]; + + BoneWeight[] nboneWeights = new BoneWeight[newVertSize]; + Matrix4x4[] nbindPoses = new Matrix4x4[newBonesSize]; + Transform[] nbones = new Transform[newBonesSize]; + SerializableIntArray[] nsubmeshTris = new SerializableIntArray[numResultMats]; + + for (int i = 0; i < nsubmeshTris.Length; i++) + { + nsubmeshTris[i] = new SerializableIntArray(newSubmeshTrisSize[i]); + } + + for (int i = 0; i < _goToDelete.Length; i++) + { + MB_DynamicGameObject dgo = null; + InstanceID2DGO(_goToDelete[i], out dgo); + if (dgo != null) + { + dgo._beingDeleted = true; + } + } + + mbDynamicObjectsInCombinedMesh.Sort(); + + //copy existing arrays to narrays gameobj by gameobj omitting deleted ones + int targVidx = 0; + int targBlendShapeIdx = 0; + int[] targSubmeshTidx = new int[numResultMats]; + int triangleIdxAdjustment = 0; + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = mbDynamicObjectsInCombinedMesh[i]; + if (!dgo._beingDeleted) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Copying obj in combined arrays idx:" + i, LOG_LEVEL); + Array.Copy(verts, dgo.vertIdx, nverts, targVidx, dgo.numVerts); + if (settings.doNorm) { Array.Copy(normals, dgo.vertIdx, nnormals, targVidx, dgo.numVerts); } + if (settings.doTan) { Array.Copy(tangents, dgo.vertIdx, ntangents, targVidx, dgo.numVerts); } + if (settings.doUV) { Array.Copy(uvs, dgo.vertIdx, nuvs, targVidx, dgo.numVerts); } + if (settings.doUV && textureBakeResults.resultType == MB2_TextureBakeResults.ResultType.textureArray) { Array.Copy(uvsSliceIdx, dgo.vertIdx, nuvsSliceIdx, targVidx, dgo.numVerts); } + if (settings.doUV3) { Array.Copy(uv3s, dgo.vertIdx, nuv3s, targVidx, dgo.numVerts); } + if (settings.doUV4) { Array.Copy(uv4s, dgo.vertIdx, nuv4s, targVidx, dgo.numVerts); } + + if (settings.doUV5) { Array.Copy(uv5s, dgo.vertIdx, nuv5s, targVidx, dgo.numVerts); } + if (settings.doUV6) { Array.Copy(uv6s, dgo.vertIdx, nuv6s, targVidx, dgo.numVerts); } + if (settings.doUV7) { Array.Copy(uv7s, dgo.vertIdx, nuv7s, targVidx, dgo.numVerts); } + if (settings.doUV8) { Array.Copy(uv8s, dgo.vertIdx, nuv8s, targVidx, dgo.numVerts); } + + if (doUV2()) { Array.Copy(uv2s, dgo.vertIdx, nuv2s, targVidx, dgo.numVerts); } + if (settings.doCol) { Array.Copy(colors, dgo.vertIdx, ncolors, targVidx, dgo.numVerts); } + if (settings.doBlendShapes) { Array.Copy(blendShapes, dgo.blendShapeIdx, nblendShapes, targBlendShapeIdx, dgo.numBlendShapes); } + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) { Array.Copy(boneWeights, dgo.vertIdx, nboneWeights, targVidx, dgo.numVerts); } + + //adjust triangles, then copy them over + for (int subIdx = 0; subIdx < numResultMats; subIdx++) + { + int[] sTris = submeshTris[subIdx].data; + int sTriIdx = dgo.submeshTriIdxs[subIdx]; + int sNumTris = dgo.submeshNumTris[subIdx]; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(" Adjusting submesh triangles submesh:" + subIdx + " startIdx:" + sTriIdx + " num:" + sNumTris + " nsubmeshTris:" + nsubmeshTris.Length + " targSubmeshTidx:" + targSubmeshTidx.Length, LOG_LEVEL); + for (int j = sTriIdx; j < sTriIdx + sNumTris; j++) + { + sTris[j] = sTris[j] - triangleIdxAdjustment; + } + Array.Copy(sTris, sTriIdx, nsubmeshTris[subIdx].data, targSubmeshTidx[subIdx], sNumTris); + } + + dgo.vertIdx = targVidx; + dgo.blendShapeIdx = targBlendShapeIdx; + + for (int j = 0; j < targSubmeshTidx.Length; j++) + { + dgo.submeshTriIdxs[j] = targSubmeshTidx[j]; + targSubmeshTidx[j] += dgo.submeshNumTris[j]; + } + targBlendShapeIdx += dgo.numBlendShapes; + targVidx += dgo.numVerts; + } + else { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Not copying obj: " + i, LOG_LEVEL); + triangleIdxAdjustment += dgo.numVerts; + } + } + + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + boneProcessor.CopyBonesWeAreKeepingToNewBonesArrayAndAdjustBWIndexes(nbones, nbindPoses, nboneWeights, totalDeleteVerts); + } + + //remove objects we are deleting + for (int i = mbDynamicObjectsInCombinedMesh.Count - 1; i >= 0; i--) + { + if (mbDynamicObjectsInCombinedMesh[i]._beingDeleted) + { + instance2Combined_MapRemove(mbDynamicObjectsInCombinedMesh[i].gameObject); + objectsInCombinedMesh.RemoveAt(i); + mbDynamicObjectsInCombinedMesh.RemoveAt(i); + } + } + + verts = nverts; + if (settings.doNorm) normals = nnormals; + if (settings.doTan) tangents = ntangents; + if (settings.doUV) uvs = nuvs; + if (settings.doUV && textureBakeResults.resultType == MB2_TextureBakeResults.ResultType.textureArray) uvsSliceIdx = nuvsSliceIdx; + if (settings.doUV3) uv3s = nuv3s; + if (settings.doUV4) uv4s = nuv4s; + + if (settings.doUV5) uv5s = nuv5s; + if (settings.doUV6) uv6s = nuv6s; + if (settings.doUV7) uv7s = nuv7s; + if (settings.doUV8) uv8s = nuv8s; + + if (doUV2()) uv2s = nuv2s; + if (settings.doCol) colors = ncolors; + if (settings.doBlendShapes) blendShapes = nblendShapes; + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) boneWeights = nboneWeights; + int newBonesStartAtIdx = bones.Length - boneProcessor.GetNumBonesToDelete(); + bindPoses = nbindPoses; + bones = nbones; + submeshTris = nsubmeshTris; + + //insert the new bones into the bones array + int bidx = 0; + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + foreach (BoneAndBindpose t in boneProcessor.GetBonesToAdd()) + { + nbones[newBonesStartAtIdx + bidx] = t.bone; + nbindPoses[newBonesStartAtIdx + bidx] = t.bindPose; + bidx++; + } + } + + //add new + for (int i = 0; i < toAddDGOs.Count; i++) + { + MB_DynamicGameObject dgo = toAddDGOs[i]; + GameObject go = _goToAdd[i]; + int vertsIdx = targVidx; + int blendShapeIdx = targBlendShapeIdx; + // Profile.StartProfile("TestNewNorm"); + Mesh mesh = MB_Utility.GetMesh(go); + Matrix4x4 l2wMat = go.transform.localToWorldMatrix; + + // Similar to local2world but with translation removed and we are using the inverse transpose. + // We use this for normals and tangents because it handles scaling correctly. + Matrix4x4 l2wRotScale = l2wMat; + l2wRotScale[0, 3] = l2wRotScale[1, 3] = l2wRotScale[2, 3] = 0f; + l2wRotScale = l2wRotScale.inverse.transpose; + + //can't modify the arrays we get from the cache because they will be modified multiple times if the same mesh is being added multiple times. + nverts = meshChannelCache.GetVertices(mesh); + Vector3[] nnorms = null; + Vector4[] ntangs = null; + if (settings.doNorm) nnorms = meshChannelCache.GetNormals(mesh); + if (settings.doTan) ntangs = meshChannelCache.GetTangents(mesh); + if (settings.renderType != MB_RenderType.skinnedMeshRenderer) + { + for (int j = 0; j < nverts.Length; j++) + { + int vIdx = vertsIdx + j; + verts[vertsIdx + j] = l2wMat.MultiplyPoint3x4(nverts[j]); + if (settings.doNorm) + { + normals[vIdx] = l2wRotScale.MultiplyPoint3x4(nnorms[j]).normalized; + } + if (settings.doTan) + { + float w = ntangs[j].w; //need to preserve the w value + tangents[vIdx] = l2wRotScale.MultiplyPoint3x4(((Vector3)ntangs[j])).normalized; + tangents[vIdx].w = w; + } + } + } + else { + //for skinned meshes leave in bind pose + boneProcessor.CopyVertsNormsTansToBuffers(dgo, settings, vertsIdx, nnorms, ntangs, nverts, normals, tangents, verts); + } + + int numTriSets = mesh.subMeshCount; + if (dgo.uvRects.Length < numTriSets) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Mesh " + dgo.name + " has more submeshes than materials"); + numTriSets = dgo.uvRects.Length; + } + else if (dgo.uvRects.Length > numTriSets) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Mesh " + dgo.name + " has fewer submeshes than materials"); + } + + if (settings.doUV) + { + uvAdjuster._copyAndAdjustUVsFromMesh(textureBakeResults, dgo, mesh, 0, vertsIdx, uvs, uvsSliceIdx, meshChannelCache); + } + + if (doUV2()) + { + _copyAndAdjustUV2FromMesh(dgo, mesh, vertsIdx, meshChannelCache); + } + + if (settings.doUV3) + { + nuv3s = meshChannelCache.GetUVChannel(3, mesh); + nuv3s.CopyTo(uv3s, vertsIdx); + } + + if (settings.doUV4) + { + nuv4s = meshChannelCache.GetUVChannel(4, mesh); + nuv4s.CopyTo(uv4s, vertsIdx); + } + + if (settings.doUV5) + { + nuv5s = meshChannelCache.GetUVChannel(5, mesh); + nuv5s.CopyTo(uv5s, vertsIdx); + } + + if (settings.doUV6) + { + nuv6s = meshChannelCache.GetUVChannel(6, mesh); + nuv6s.CopyTo(uv6s, vertsIdx); + } + + if (settings.doUV7) + { + nuv7s = meshChannelCache.GetUVChannel(7, mesh); + nuv7s.CopyTo(uv7s, vertsIdx); + } + + if (settings.doUV8) + { + nuv8s = meshChannelCache.GetUVChannel(8, mesh); + nuv8s.CopyTo(uv8s, vertsIdx); + } + + if (settings.doCol) + { + ncolors = meshChannelCache.GetColors(mesh); + ncolors.CopyTo(colors, vertsIdx); + } + + if (settings.doBlendShapes) + { + nblendShapes = meshChannelCache.GetBlendShapes(mesh, dgo.instanceID, dgo.gameObject); + nblendShapes.CopyTo(blendShapes, blendShapeIdx); + } + + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + Renderer r = MB_Utility.GetRenderer(go); + MB3_MeshCombinerSimpleBones.AddBonesToNewBonesArrayAndAdjustBWIndexes(this, dgo, r, vertsIdx, nbones, nboneWeights, meshChannelCache); + } + + for (int combinedMeshIdx = 0; combinedMeshIdx < targSubmeshTidx.Length; combinedMeshIdx++) + { + dgo.submeshTriIdxs[combinedMeshIdx] = targSubmeshTidx[combinedMeshIdx]; + } + for (int j = 0; j < dgo._tmpSubmeshTris.Length; j++) + { + int[] sts = dgo._tmpSubmeshTris[j].data; + for (int k = 0; k < sts.Length; k++) + { + sts[k] = sts[k] + vertsIdx; + } + if (dgo.invertTriangles) + { + //need to reverse winding order + for (int k = 0; k < sts.Length; k += 3) + { + int tmp = sts[k]; + sts[k] = sts[k + 1]; + sts[k + 1] = tmp; + } + } + int combinedMeshIdx = dgo.targetSubmeshIdxs[j]; + sts.CopyTo(submeshTris[combinedMeshIdx].data, targSubmeshTidx[combinedMeshIdx]); + dgo.submeshNumTris[combinedMeshIdx] += sts.Length; + targSubmeshTidx[combinedMeshIdx] += sts.Length; + } + + dgo.vertIdx = targVidx; + dgo.blendShapeIdx = targBlendShapeIdx; + + instance2Combined_MapAdd(go, dgo); + objectsInCombinedMesh.Add(go); + mbDynamicObjectsInCombinedMesh.Add(dgo); + + targVidx += nverts.Length; + if (settings.doBlendShapes) + { + targBlendShapeIdx += nblendShapes.Length; + } + for (int j = 0; j < dgo._tmpSubmeshTris.Length; j++) dgo._tmpSubmeshTris[j] = null; + dgo._tmpSubmeshTris = null; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Added to combined:" + dgo.name + " verts:" + nverts.Length + " bindPoses:" + nbindPoses.Length, LOG_LEVEL); + } + if (settings.lightmapOption == MB2_LightmapOptions.copy_UV2_unchanged_to_separate_rects) + { + _copyUV2unchangedToSeparateRects(); + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("===== _addToCombined completed. Verts in buffer: " + verts.Length + " time(ms): " + sw.ElapsedMilliseconds, LOG_LEVEL); + return true; + } + + void _copyAndAdjustUV2FromMesh(MB_DynamicGameObject dgo, Mesh mesh, int vertsIdx, MeshChannelsCache meshChannelsCache) + { + Vector2[] nuv2s = meshChannelsCache.GetUVChannel(2,mesh); + if (settings.lightmapOption == MB2_LightmapOptions.preserve_current_lightmapping) + { //has a lightmap + //this does not work in Unity 5. the lightmapTilingOffset is always 1,1,0,0 for all objects + //lightMap index is always 1 + Vector2 uvscale2; + Vector4 lightmapTilingOffset = dgo.lightmapTilingOffset; + Vector2 uvscale = new Vector2(lightmapTilingOffset.x, lightmapTilingOffset.y); + Vector2 uvoffset = new Vector2(lightmapTilingOffset.z, lightmapTilingOffset.w); + for (int j = 0; j < nuv2s.Length; j++) + { + uvscale2.x = uvscale.x * nuv2s[j].x; + uvscale2.y = uvscale.y * nuv2s[j].y; + uv2s[vertsIdx + j] = uvoffset + uvscale2; + } + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("_copyAndAdjustUV2FromMesh copied and modify for preserve current lightmapping " + nuv2s.Length); + } + else + { + nuv2s.CopyTo(uv2s, vertsIdx); + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + Debug.Log("_copyAndAdjustUV2FromMesh copied without modifying " + nuv2s.Length); + } + } + } + + Transform[] _getBones(Renderer r, bool isSkinnedMeshWithBones) + { + return MBVersion.GetBones(r, isSkinnedMeshWithBones); + } + + public override void Apply(GenerateUV2Delegate uv2GenerationMethod) + { + bool doBones = false; + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) doBones = true; + Apply(true, true, settings.doNorm, settings.doTan, + settings.doUV, doUV2(), settings.doUV3, settings.doUV4, settings.doUV5, settings.doUV6, settings.doUV7, settings.doUV8, + settings.doCol, doBones, settings.doBlendShapes, uv2GenerationMethod); + } + + public virtual void ApplyShowHide() + { + if (_validationLevel >= MB2_ValidationLevel.quick && !ValidateTargRendererAndMeshAndResultSceneObj()) return; + if (_mesh != null) + { + if (settings.renderType == MB_RenderType.meshRenderer) + { + //for MeshRenderer meshes this is needed for adding. It breaks skinnedMeshRenderers + MBVersion.MeshClear(_mesh, true); + _mesh.vertices = verts; + } + SerializableIntArray[] submeshTrisToUse = GetSubmeshTrisWithShowHideApplied(); + if (textureBakeResults.doMultiMaterial) + { + //submeshes with zero length tris cause error messages. must exclude these + int numNonZero = _mesh.subMeshCount = _numNonZeroLengthSubmeshTris(submeshTrisToUse);// submeshTrisToUse.Length; + int submeshIdx = 0; + for (int i = 0; i < submeshTrisToUse.Length; i++) + { + if (submeshTrisToUse[i].data.Length != 0) + { + _mesh.SetTriangles(submeshTrisToUse[i].data, submeshIdx); + submeshIdx++; + } + } + _updateMaterialsOnTargetRenderer(submeshTrisToUse, numNonZero); + } + else { + _mesh.triangles = submeshTrisToUse[0].data; + } + + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + if (verts.Length == 0) + { + //disable mesh renderer to avoid skinning warning + targetRenderer.enabled = false; + } + else + { + targetRenderer.enabled = true; + } + //needed so that updating local bounds will take affect + bool uwos = ((SkinnedMeshRenderer)targetRenderer).updateWhenOffscreen; + ((SkinnedMeshRenderer)targetRenderer).updateWhenOffscreen = true; + ((SkinnedMeshRenderer)targetRenderer).updateWhenOffscreen = uwos; + } + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("ApplyShowHide"); + } + else { + Debug.LogError("Need to add objects to this meshbaker before calling ApplyShowHide"); + } + } + + public override void Apply(bool triangles, + bool vertices, + bool normals, + bool tangents, + bool uvs, + bool uv2, + bool uv3, + bool uv4, + bool colors, + bool bones = false, + bool blendShapesFlag = false, + GenerateUV2Delegate uv2GenerationMethod = null) + { + Apply(triangles, vertices, normals, tangents, + uvs, uv2, uv3, uv4, + false, false, false, false, + colors, bones, blendShapesFlag, uv2GenerationMethod); + } + + public override void Apply(bool triangles, + bool vertices, + bool normals, + bool tangents, + bool uvs, + bool uv2, + bool uv3, + bool uv4, + bool uv5, + bool uv6, + bool uv7, + bool uv8, + bool colors, + bool bones = false, + bool blendShapesFlag = false, + GenerateUV2Delegate uv2GenerationMethod = null) + { + System.Diagnostics.Stopwatch sw = null; + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + } + if (_validationLevel >= MB2_ValidationLevel.quick && !ValidateTargRendererAndMeshAndResultSceneObj()) return; + if (_mesh != null) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + Debug.Log(String.Format("Apply called tri={0} vert={1} norm={2} tan={3} uv={4} col={5} uv3={6} uv4={7} uv2={8} bone={9} blendShape{10} meshID={11}", + triangles, vertices, normals, tangents, uvs, colors, uv3, uv4, uv2, bones, blendShapesFlag, _mesh.GetInstanceID())); + } + if (triangles || _mesh.vertexCount != verts.Length) + { + bool justClearTriangles = triangles && !vertices && !normals && !tangents && !uvs && !colors && !uv3 && !uv4 && !uv2 && !bones; + MBVersion.SetMeshIndexFormatAndClearMesh(_mesh, verts.Length, vertices, justClearTriangles); + } + + if (vertices) + { + Vector3[] verts2Write = verts; + if (verts.Length > 0) { + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + targetRenderer.transform.position = Vector3.zero; + } else if (settings.pivotLocationType == MB_MeshPivotLocation.worldOrigin) + { + targetRenderer.transform.position = Vector3.zero; + } + else if(settings.pivotLocationType == MB_MeshPivotLocation.boundsCenter) + { + + Vector3 max = verts[0], min = verts[0]; + for (int i = 1; i < verts.Length; i++) + { + Vector3 v = verts[i]; + if (max.x < v.x) max.x = v.x; + if (max.y < v.y) max.y = v.y; + if (max.z < v.z) max.z = v.z; + if (min.x > v.x) min.x = v.x; + if (min.y > v.y) min.y = v.y; + if (min.z > v.z) min.z = v.z; + } + + Vector3 center = (max + min) / 2f; + + verts2Write = new Vector3[verts.Length]; + for (int i = 0; i < verts.Length; i++) + { + verts2Write[i] = verts[i] - center; + } + + targetRenderer.transform.position = center; + } else if (settings.pivotLocationType == MB_MeshPivotLocation.customLocation) + { + Vector3 center = settings.pivotLocation; + for (int i = 0; i < verts.Length; i++) + { + verts2Write[i] = verts[i] - center; + } + + targetRenderer.transform.position = center; + } + } + + _mesh.vertices = verts2Write; + } + if (triangles && _textureBakeResults) + { + if (_textureBakeResults == null) + { + Debug.LogError("Texture Bake Result was not set."); + } + else { + SerializableIntArray[] submeshTrisToUse = GetSubmeshTrisWithShowHideApplied(); + + //submeshes with zero length tris cause error messages. must exclude these + int numNonZero = _mesh.subMeshCount = _numNonZeroLengthSubmeshTris(submeshTrisToUse);// submeshTrisToUse.Length; + int submeshIdx = 0; + for (int i = 0; i < submeshTrisToUse.Length; i++) + { + if (submeshTrisToUse[i].data.Length != 0) + { + _mesh.SetTriangles(submeshTrisToUse[i].data, submeshIdx); + submeshIdx++; + } + } + + _updateMaterialsOnTargetRenderer(submeshTrisToUse, numNonZero); + } + } + if (normals) + { + if (settings.doNorm) { + _mesh.normals = this.normals; } + else { Debug.LogError("normal flag was set in Apply but MeshBaker didn't generate normals"); } + } + + if (tangents) + { + if (settings.doTan) { _mesh.tangents = this.tangents; } + else { Debug.LogError("tangent flag was set in Apply but MeshBaker didn't generate tangents"); } + } + if (colors) + { + if (settings.doCol) + { + if (settings.assignToMeshCustomizer == null) + { + _mesh.colors = this.colors; + } + else + { + settings.assignToMeshCustomizer.meshAssign_colors(settings, textureBakeResults, _mesh, this.colors, this.uvsSliceIdx); + } + } + else { Debug.LogError("color flag was set in Apply but MeshBaker didn't generate colors"); } + } + if (uvs) + { + if (settings.doUV) + { + if (settings.assignToMeshCustomizer == null) + { + _mesh.uv = this.uvs; + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV0(0, settings, textureBakeResults, _mesh, this.uvs, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv flag was set in Apply but MeshBaker didn't generate uvs"); } + } + if (uv2) + { + if (doUV2()) + { + if (settings.assignToMeshCustomizer == null) + { + _mesh.uv2 = this.uv2s; + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV2(2, settings, textureBakeResults, _mesh, this.uv2s, this.uvsSliceIdx); + } + + } + else { Debug.LogError("uv2 flag was set in Apply but lightmapping option was set to " + settings.lightmapOption); } + } + if (uv3) + { + if (settings.doUV3) + { + if (settings.assignToMeshCustomizer == null) + { + MBVersion.MeshAssignUVChannel(3, _mesh, this.uv3s); + } else + { + settings.assignToMeshCustomizer.meshAssign_UV3(3, settings, textureBakeResults, _mesh, this.uv3s, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv3 flag was set in Apply but MeshBaker didn't generate uv3s"); } + } + + if (uv4) + { + if (settings.doUV4) + { + if (settings.assignToMeshCustomizer == null) + { + MBVersion.MeshAssignUVChannel(4, _mesh, this.uv4s); + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV4(4, settings, textureBakeResults, _mesh, this.uv4s, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv4 flag was set in Apply but MeshBaker didn't generate uv4s"); } + } + + if (uv5) + { + if (settings.doUV5) + { + if (settings.assignToMeshCustomizer == null) + { + MBVersion.MeshAssignUVChannel(5, _mesh, this.uv5s); + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV5(5, settings, textureBakeResults, _mesh, this.uv5s, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv5 flag was set in Apply but MeshBaker didn't generate uv5s"); } + } + + if (uv6) + { + if (settings.doUV6) + { + if (settings.assignToMeshCustomizer == null) + { + MBVersion.MeshAssignUVChannel(6, _mesh, this.uv6s); + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV6(6, settings, textureBakeResults, _mesh, this.uv6s, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv6 flag was set in Apply but MeshBaker didn't generate uv6s"); } + } + + if (uv7) + { + if (settings.doUV7) + { + if (settings.assignToMeshCustomizer == null) + { + MBVersion.MeshAssignUVChannel(7, _mesh, this.uv7s); + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV7(7, settings, textureBakeResults, _mesh, this.uv7s, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv7 flag was set in Apply but MeshBaker didn't generate uv7s"); } + } + + if (uv8) + { + if (settings.doUV8) + { + if (settings.assignToMeshCustomizer == null) + { + MBVersion.MeshAssignUVChannel(8, _mesh, this.uv8s); + } + else + { + settings.assignToMeshCustomizer.meshAssign_UV8(8, settings, textureBakeResults, _mesh, this.uv8s, this.uvsSliceIdx); + } + } + else { Debug.LogError("uv8 flag was set in Apply but MeshBaker didn't generate uv8s"); } + } + + bool do_generate_new_UV2_layout = false; + if (settings.renderType != MB_RenderType.skinnedMeshRenderer && settings.lightmapOption == MB2_LightmapOptions.generate_new_UV2_layout) + { + if (uv2GenerationMethod != null) + { + uv2GenerationMethod(_mesh, settings.uv2UnwrappingParamsHardAngle, settings.uv2UnwrappingParamsPackMargin); + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("generating new UV2 layout for the combined mesh "); + } + else { + Debug.LogError("No GenerateUV2Delegate method was supplied. UV2 cannot be generated."); + } + do_generate_new_UV2_layout = true; + } + else if (settings.renderType == MB_RenderType.skinnedMeshRenderer && settings.lightmapOption == MB2_LightmapOptions.generate_new_UV2_layout) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("UV2 cannot be generated for SkinnedMeshRenderer objects."); + } + if (settings.renderType != MB_RenderType.skinnedMeshRenderer && settings.lightmapOption == MB2_LightmapOptions.generate_new_UV2_layout && do_generate_new_UV2_layout == false) + { + Debug.LogError("Failed to generate new UV2 layout. Only works in editor."); + } + + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + if (verts.Length == 0) + { + //disable mesh renderer to avoid skinning warning + targetRenderer.enabled = false; + } + else { + targetRenderer.enabled = true; + } + //needed so that updating local bounds will take affect + bool uwos = ((SkinnedMeshRenderer)targetRenderer).updateWhenOffscreen; + ((SkinnedMeshRenderer)targetRenderer).updateWhenOffscreen = true; + ((SkinnedMeshRenderer)targetRenderer).updateWhenOffscreen = uwos; + } + + if (bones) + { + _mesh.bindposes = this.bindPoses; + _mesh.boneWeights = this.boneWeights; + } + if (blendShapesFlag) + { + if (settings.smrMergeBlendShapesWithSameNames) + { + ApplyBlendShapeFramesToMeshAndBuildMap_MergeBlendShapesWithTheSameName(); + } + else + { + ApplyBlendShapeFramesToMeshAndBuildMap(); + } + } + if (triangles || vertices) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("recalculating bounds on mesh."); + _mesh.RecalculateBounds(); + } if (settings.optimizeAfterBake && !Application.isPlaying) + { + MBVersion.OptimizeMesh(_mesh); + } + } + else { + Debug.LogError("Need to add objects to this meshbaker before calling Apply or ApplyAll"); + } + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log("Apply Complete time: " + sw.ElapsedMilliseconds + " vertices: " + _mesh.vertexCount); + } + } + + int _numNonZeroLengthSubmeshTris(SerializableIntArray[] subTris) + { + int num = 0; + for (int i = 0; i < subTris.Length; i++) { if (subTris[i].data.Length > 0) num++;} + return num; + } + + private void _updateMaterialsOnTargetRenderer(SerializableIntArray[] subTris, int numNonZeroLengthSubmeshTris) + { + //zero length triangle arrays in mesh cause errors. have excluded these sumbeshes so must exclude these materials + if (subTris.Length != textureBakeResults.NumResultMaterials()) Debug.LogError("Mismatch between number of submeshes and number of result materials"); + Material[] resMats = new Material[numNonZeroLengthSubmeshTris]; + int submeshIdx = 0; + for (int i = 0; i < subTris.Length; i++) + { + if (subTris[i].data.Length > 0) { + resMats[submeshIdx] = _textureBakeResults.GetCombinedMaterialForSubmesh(i); + submeshIdx++; + } + } + targetRenderer.materials = resMats; + } + + public SerializableIntArray[] GetSubmeshTrisWithShowHideApplied() + { + bool containsHiddenObjects = false; + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + if (mbDynamicObjectsInCombinedMesh[i].show == false) + { + containsHiddenObjects = true; + break; + } + } + if (containsHiddenObjects) + { + int[] newLengths = new int[submeshTris.Length]; + SerializableIntArray[] newSubmeshTris = new SerializableIntArray[submeshTris.Length]; + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = mbDynamicObjectsInCombinedMesh[i]; + if (dgo.show) + { + for (int j = 0; j < dgo.submeshNumTris.Length; j++) + { + newLengths[j] += dgo.submeshNumTris[j]; + } + } + } + for (int i = 0; i < newSubmeshTris.Length; i++) + { + newSubmeshTris[i] = new SerializableIntArray(newLengths[i]); + } + int[] idx = new int[newSubmeshTris.Length]; + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = mbDynamicObjectsInCombinedMesh[i]; + if (dgo.show) + { + for (int j = 0; j < submeshTris.Length; j++) + { //for each submesh + int[] triIdxs = submeshTris[j].data; + int startIdx = dgo.submeshTriIdxs[j]; + int endIdx = startIdx + dgo.submeshNumTris[j]; + for (int k = startIdx; k < endIdx; k++) + { + newSubmeshTris[j].data[idx[j]] = triIdxs[k]; + idx[j] = idx[j] + 1; + } + } + } + } + return newSubmeshTris; + } + else { + return submeshTris; + } + } + + public override bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateColors, bool updateSkinningInfo) + { + return _updateGameObjects(gos, recalcBounds, updateVertices, updateNormals, updateTangents, updateUV, updateUV2, updateUV3, updateUV4, + false, false, false, false, updateColors, updateSkinningInfo); + } + + public override bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8, + bool updateColors, bool updateSkinningInfo) + { + return _updateGameObjects(gos, recalcBounds, updateVertices, updateNormals, updateTangents, updateUV, updateUV2, updateUV3, updateUV4, + updateUV5, updateUV6, updateUV7, updateUV8, updateColors, updateSkinningInfo); + } + + bool _updateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8, + bool updateColors, bool updateSkinningInfo) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("UpdateGameObjects called on " + gos.Length + " objects."); + int numResultMats = 1; + if (textureBakeResults.doMultiMaterial) numResultMats = textureBakeResults.NumResultMaterials(); + + if (!_Initialize(numResultMats)) + { + return false; + } + + if (_mesh.vertexCount > 0 && _instance2combined_map.Count == 0) + { + Debug.LogWarning("There were vertices in the combined mesh but nothing in the MeshBaker buffers. If you are trying to bake in the editor and modify at runtime, make sure 'Clear Buffers After Bake' is unchecked."); + } + bool success = true; + MeshChannelsCache meshChannelCache = new MeshChannelsCache(LOG_LEVEL, settings.lightmapOption); + UVAdjuster_Atlas uvAdjuster = null; + OrderedDictionary sourceMats2submeshIdx_map = null; + Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache = null; + if (updateUV){ + sourceMats2submeshIdx_map = BuildSourceMatsToSubmeshIdxMap(numResultMats); + if (sourceMats2submeshIdx_map == null) + { + return false; + } + + uvAdjuster = new UVAdjuster_Atlas(textureBakeResults, LOG_LEVEL); + meshAnalysisResultsCache = new Dictionary<int, MB_Utility.MeshAnalysisResult[]>(); + } + + for (int i = 0; i < gos.Length; i++) + { + success = success && _updateGameObject(gos[i], updateVertices, updateNormals, updateTangents, updateUV, updateUV2, updateUV3, updateUV4, updateUV5, updateUV6, updateUV7, updateUV8, updateColors, updateSkinningInfo, + meshChannelCache, meshAnalysisResultsCache, sourceMats2submeshIdx_map, uvAdjuster); + } + if (recalcBounds) + _mesh.RecalculateBounds(); + return success; + } + + bool _updateGameObject(GameObject go, bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8, + bool updateColors, bool updateSkinningInfo, + MeshChannelsCache meshChannelCache, Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache, + OrderedDictionary sourceMats2submeshIdx_map, UVAdjuster_Atlas uVAdjuster) + { + MB_DynamicGameObject dgo = null; + if (!instance2Combined_MapTryGetValue(go, out dgo)) + { + Debug.LogError("Object " + go.name + " has not been added"); + return false; + } + Mesh mesh = MB_Utility.GetMesh(go); + if (dgo.numVerts != mesh.vertexCount) + { + Debug.LogError("Object " + go.name + " source mesh has been modified since being added. To update it must have the same number of verts"); + return false; + } + + if (settings.doUV && updateUV) + { + // updating UVs is a bit more complicated because most likely the user has changed + // the material on the source mesh which is why they are calling update. We need to + // find the UV rect for this. + + Material[] sharedMaterials = MB_Utility.GetGOMaterials(go); + if (!uVAdjuster.MapSharedMaterialsToAtlasRects(sharedMaterials, true, mesh, meshChannelCache, meshAnalysisResultsCache, sourceMats2submeshIdx_map, go, dgo)) + { + return false; + } + + uVAdjuster._copyAndAdjustUVsFromMesh(textureBakeResults, dgo, mesh, 0, dgo.vertIdx, uvs, uvsSliceIdx, meshChannelCache); + } + if (doUV2() && updateUV2) _copyAndAdjustUV2FromMesh(dgo, mesh, dgo.vertIdx, meshChannelCache); + if (settings.renderType == MB_RenderType.skinnedMeshRenderer && updateSkinningInfo) + { + //only does BoneWeights. Used to do Bones and BindPoses but it doesn't make sence. + //if updating Bones and Bindposes should remove and re-add + Renderer r = MB_Utility.GetRenderer(go); + BoneWeight[] bws = meshChannelCache.GetBoneWeights(r, dgo.numVerts, dgo.isSkinnedMeshWithBones); + Transform[] bs = _getBones(r, dgo.isSkinnedMeshWithBones); + //assumes that the bones and boneweights have not been reeordered + int bwIdx = dgo.vertIdx; //the index in the verts array + bool switchedBonesDetected = false; + for (int i = 0; i < bws.Length; i++) + { + if (bs[bws[i].boneIndex0] != bones[boneWeights[bwIdx].boneIndex0]) + { + switchedBonesDetected = true; + break; + } + boneWeights[bwIdx].weight0 = bws[i].weight0; + boneWeights[bwIdx].weight1 = bws[i].weight1; + boneWeights[bwIdx].weight2 = bws[i].weight2; + boneWeights[bwIdx].weight3 = bws[i].weight3; + bwIdx++; + } + if (switchedBonesDetected) + { + Debug.LogError("Detected that some of the boneweights reference different bones than when initial added. Boneweights must reference the same bones " + dgo.name); + } + } + + //now do verts, norms, tangents, colors and uv1 + Matrix4x4 l2wMat = go.transform.localToWorldMatrix; + + // We use the inverse transpose for normals and tangents because it handles scaling of normals the same way that + // The shaders do. + Matrix4x4 l2wRotScale = l2wMat; + l2wRotScale[0, 3] = l2wRotScale[1, 3] = l2wRotScale[2, 3] = 0f; + l2wRotScale = l2wRotScale.inverse.transpose; + if (updateVertices) + { + Vector3[] nverts = meshChannelCache.GetVertices(mesh); + for (int j = 0; j < nverts.Length; j++) + { + verts[dgo.vertIdx + j] = l2wMat.MultiplyPoint3x4(nverts[j]); + } + } + l2wMat[0, 3] = l2wMat[1, 3] = l2wMat[2, 3] = 0f; + if (settings.doNorm && updateNormals) + { + Vector3[] nnorms = meshChannelCache.GetNormals(mesh); + for (int j = 0; j < nnorms.Length; j++) + { + int vIdx = dgo.vertIdx + j; + normals[vIdx] = l2wRotScale.MultiplyPoint3x4(nnorms[j]).normalized; + } + } + if (settings.doTan && updateTangents) + { + Vector4[] ntangs = meshChannelCache.GetTangents(mesh); + for (int j = 0; j < ntangs.Length; j++) + { + int vIdx = dgo.vertIdx + j; + float w = ntangs[j].w; //need to preserve the w value + tangents[vIdx] = l2wRotScale.MultiplyPoint3x4(((Vector3)ntangs[j])).normalized; + tangents[vIdx].w = w; + } + } + + if (settings.doCol && updateColors) + { + Color[] ncolors = meshChannelCache.GetColors(mesh); + for (int j = 0; j < ncolors.Length; j++) colors[dgo.vertIdx + j] = ncolors[j]; + } + + if (settings.doUV3 && updateUV3) + { + Vector2[] nuv3 = meshChannelCache.GetUVChannel(3, mesh); + for (int j = 0; j < nuv3.Length; j++) uv3s[dgo.vertIdx + j] = nuv3[j]; + } + + if (settings.doUV4 && updateUV4) + { + Vector2[] nuv4 = meshChannelCache.GetUVChannel(4, mesh); + for (int j = 0; j < nuv4.Length; j++) uv4s[dgo.vertIdx + j] = nuv4[j]; + } + + if (settings.doUV5 && updateUV5) + { + Vector2[] nuv5 = meshChannelCache.GetUVChannel(5, mesh); + for (int j = 0; j < nuv5.Length; j++) uv5s[dgo.vertIdx + j] = nuv5[j]; + } + + if (settings.doUV6 && updateUV6) + { + Vector2[] nuv6 = meshChannelCache.GetUVChannel(6, mesh); + for (int j = 0; j < nuv6.Length; j++) uv6s[dgo.vertIdx + j] = nuv6[j]; + } + + if (settings.doUV7 && updateUV7) + { + Vector2[] nuv7 = meshChannelCache.GetUVChannel(7, mesh); + for (int j = 0; j < nuv7.Length; j++) uv7s[dgo.vertIdx + j] = nuv7[j]; + } + + if (settings.doUV8 && updateUV8) + { + Vector2[] nuv8 = meshChannelCache.GetUVChannel(8, mesh); + for (int j = 0; j < nuv8.Length; j++) uv8s[dgo.vertIdx + j] = nuv8[j]; + } + + return true; + } + + public bool ShowHideGameObjects(GameObject[] toShow, GameObject[] toHide) + { + if (textureBakeResults == null) + { + Debug.LogError("TextureBakeResults must be set."); + return false; + } + return _showHide(toShow, toHide); + } + + public override bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource = true) + { + int[] delInstanceIDs = null; + if (deleteGOs != null) + { + delInstanceIDs = new int[deleteGOs.Length]; + for (int i = 0; i < deleteGOs.Length; i++) + { + if (deleteGOs[i] == null) + { + Debug.LogError("The " + i + "th object on the list of objects to delete is 'Null'"); + } + else { + delInstanceIDs[i] = deleteGOs[i].GetInstanceID(); + } + } + } + return AddDeleteGameObjectsByID(gos, delInstanceIDs, disableRendererInSource); + } + + public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource) + { + // Profile.StartProfile("AddDeleteGameObjectsByID"); + if (validationLevel > MB2_ValidationLevel.none) + { + //check for duplicates + if (gos != null) + { + for (int i = 0; i < gos.Length; i++) + { + if (gos[i] == null) + { + Debug.LogError("The " + i + "th object on the list of objects to combine is 'None'. Use Command-Delete on Mac OS X; Delete or Shift-Delete on Windows to remove this one element."); + return false; + } + if (validationLevel >= MB2_ValidationLevel.robust) + { + for (int j = i + 1; j < gos.Length; j++) + { + if (gos[i] == gos[j]) + { + Debug.LogError("GameObject " + gos[i] + " appears twice in list of game objects to add"); + return false; + } + } + } + } + } + if (deleteGOinstanceIDs != null && validationLevel >= MB2_ValidationLevel.robust) + { + for (int i = 0; i < deleteGOinstanceIDs.Length; i++) + { + for (int j = i + 1; j < deleteGOinstanceIDs.Length; j++) + { + if (deleteGOinstanceIDs[i] == deleteGOinstanceIDs[j]) + { + Debug.LogError("GameObject " + deleteGOinstanceIDs[i] + "appears twice in list of game objects to delete"); + return false; + } + } + } + } + } + + if (_usingTemporaryTextureBakeResult && gos != null && gos.Length > 0) + { + MB_Utility.Destroy(_textureBakeResults); + _textureBakeResults = null; + _usingTemporaryTextureBakeResult = false; + } + + //create a temporary _textureBakeResults if needed + if (_textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null) + { + if (!_CreateTemporaryTextrueBakeResult(gos, GetMaterialsOnTargetRenderer())) + { + return false; + } + } + + BuildSceneMeshObject(gos); + + + if (!_addToCombined(gos, deleteGOinstanceIDs, disableRendererInSource)) + { + Debug.LogError("Failed to add/delete objects to combined mesh"); + return false; + } + if (targetRenderer != null) + { + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + SkinnedMeshRenderer smr = (SkinnedMeshRenderer)targetRenderer; + smr.sharedMesh = _mesh; + smr.bones = bones; + UpdateSkinnedMeshApproximateBoundsFromBounds(); + } + targetRenderer.lightmapIndex = GetLightmapIndex(); + } + // Profile.EndProfile("AddDeleteGameObjectsByID"); + // Profile.PrintResults(); + return true; + } + + public override bool CombinedMeshContains(GameObject go) + { + return objectsInCombinedMesh.Contains(go); + } + + public override void ClearBuffers() + { + verts = new Vector3[0]; + normals = new Vector3[0]; + tangents = new Vector4[0]; + uvs = new Vector2[0]; + uvsSliceIdx = new float[0]; + uv2s = new Vector2[0]; + uv3s = new Vector2[0]; + uv4s = new Vector2[0]; + uv5s = new Vector2[0]; + uv6s = new Vector2[0]; + uv7s = new Vector2[0]; + uv8s = new Vector2[0]; + colors = new Color[0]; + bones = new Transform[0]; + bindPoses = new Matrix4x4[0]; + boneWeights = new BoneWeight[0]; + submeshTris = new SerializableIntArray[0]; + blendShapes = new MBBlendShape[0]; + blendShapesInCombined = new MBBlendShape[0]; + mbDynamicObjectsInCombinedMesh.Clear(); + objectsInCombinedMesh.Clear(); + instance2Combined_MapClear(); + if (_usingTemporaryTextureBakeResult) + { + MB_Utility.Destroy(_textureBakeResults); + _textureBakeResults = null; + _usingTemporaryTextureBakeResult = false; + } + if (LOG_LEVEL >= MB2_LogLevel.trace) MB2_Log.LogDebug("ClearBuffers called"); + } + + private Mesh NewMesh() + { + if (Application.isPlaying) + { + _meshBirth = MeshCreationConditions.CreatedAtRuntime; + } else { + _meshBirth = MeshCreationConditions.CreatedInEditor; + } + Mesh m = new Mesh(); + + return m; + } + + /* + * Empties all channels and clears the mesh + */ + public override void ClearMesh() + { + if (_mesh != null) + { + MBVersion.MeshClear(_mesh, false); + } + else { + _mesh = NewMesh(); + } + ClearBuffers(); + } + + public override void ClearMesh(MB2_EditorMethodsInterface editorMethods) + { + ClearMesh(); + } + + public override void DisposeRuntimeCreated() + { + if (Application.isPlaying) + { + if (_meshBirth == MeshCreationConditions.CreatedAtRuntime) + { + GameObject.Destroy(_mesh); + } + else if (_meshBirth == MeshCreationConditions.AssignedByUser) + { + _mesh = null; + } + + ClearBuffers(); + } + } + + /// <summary> + /// Empties all channels, destroys the mesh and replaces it with a new mesh + /// </summary> + public override void DestroyMesh() + { + if (_mesh != null) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Destroying Mesh"); + MB_Utility.Destroy(_mesh); + _meshBirth = MeshCreationConditions.NoMesh; + } + + ClearBuffers(); + } + + public override void DestroyMeshEditor(MB2_EditorMethodsInterface editorMethods) + { + if (_mesh != null && editorMethods != null && !Application.isPlaying) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Destroying Mesh"); + editorMethods.Destroy(_mesh); + } + + ClearBuffers(); + } + + public bool ValidateTargRendererAndMeshAndResultSceneObj() + { + if (_resultSceneObject == null) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Result Scene Object was not set."); + return false; + } + else { + if (_targetRenderer == null) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Target Renderer was not set."); + return false; + } + else { + if (_targetRenderer.transform.parent != _resultSceneObject.transform) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Target Renderer game object is not a child of Result Scene Object was not set."); + return false; + } + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + if (!(_targetRenderer is SkinnedMeshRenderer)) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Render Type is skinned mesh renderer but Target Renderer is not."); + return false; + } + /* + if (((SkinnedMeshRenderer)_targetRenderer).sharedMesh != _mesh) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Target renderer mesh is not equal to mesh."); + return false; + } + */ + } + if (settings.renderType == MB_RenderType.meshRenderer) + { + if (!(_targetRenderer is MeshRenderer)) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Render Type is mesh renderer but Target Renderer is not."); + return false; + } + MeshFilter mf = _targetRenderer.GetComponent<MeshFilter>(); + if (_mesh != mf.sharedMesh) + { + if (_LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Target renderer mesh is not equal to mesh."); + return false; + } + } + } + } + return true; + } + + OrderedDictionary BuildSourceMatsToSubmeshIdxMap(int numResultMats) + { + OrderedDictionary sourceMats2submeshIdx_map = new OrderedDictionary(); + //build the sourceMats to submesh index map + for (int resultMatIdx = 0; resultMatIdx < numResultMats; resultMatIdx++) + { + List<Material> sourceMats = _textureBakeResults.GetSourceMaterialsUsedByResultMaterial(resultMatIdx); + for (int j = 0; j < sourceMats.Count; j++) + { + if (sourceMats[j] == null) + { + Debug.LogError("Found null material in source materials for combined mesh materials " + resultMatIdx); + return null; + } + + if (!sourceMats2submeshIdx_map.Contains(sourceMats[j])) + { + sourceMats2submeshIdx_map.Add(sourceMats[j], resultMatIdx); + } + } + } + + return sourceMats2submeshIdx_map; + } + + internal Renderer BuildSceneHierarchPreBake(MB3_MeshCombinerSingle mom, GameObject root, Mesh m, bool createNewChild = false, GameObject[] objsToBeAdded = null) + { + if (mom._LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Building Scene Hierarchy createNewChild=" + createNewChild); + GameObject meshGO; + MeshFilter mf = null; + MeshRenderer mr = null; + SkinnedMeshRenderer smr = null; + Transform mt = null; + if (root == null) + { + Debug.LogError("root was null."); + return null; + } + if (mom.textureBakeResults == null) + { + Debug.LogError("textureBakeResults must be set."); + return null; + } + if (root.GetComponent<Renderer>() != null) + { + Debug.LogError("root game object cannot have a renderer component"); + return null; + } + if (!createNewChild) + { + //try to find an existing child + if (mom.targetRenderer != null && mom.targetRenderer.transform.parent == root.transform) + { + mt = mom.targetRenderer.transform; //good setup + } + else + { + Renderer[] rs = (Renderer[])root.GetComponentsInChildren<Renderer>(true); + if (rs.Length == 1) + { + if (rs[0].transform.parent != root.transform) + { + Debug.LogError("Target Renderer is not an immediate child of Result Scene Object. Try using a game object with no children as the Result Scene Object.."); + } + mt = rs[0].transform; + } + } + } + if (mt != null && mt.parent != root.transform) + { //target renderer must be a child of root + mt = null; + } + if (mt == null) + { + meshGO = new GameObject(mom.name + "-mesh"); + meshGO.transform.parent = root.transform; + mt = meshGO.transform; + } + mt.parent = root.transform; + meshGO = mt.gameObject; + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + MeshRenderer r = meshGO.GetComponent<MeshRenderer>(); + if (r != null) MB_Utility.Destroy(r); + MeshFilter f = meshGO.GetComponent<MeshFilter>(); + if (f != null) MB_Utility.Destroy(f); + smr = meshGO.GetComponent<SkinnedMeshRenderer>(); + if (smr == null) smr = meshGO.AddComponent<SkinnedMeshRenderer>(); + } + else + { + SkinnedMeshRenderer r = meshGO.GetComponent<SkinnedMeshRenderer>(); + if (r != null) MB_Utility.Destroy(r); + mf = meshGO.GetComponent<MeshFilter>(); + if (mf == null) mf = meshGO.AddComponent<MeshFilter>(); + mr = meshGO.GetComponent<MeshRenderer>(); + if (mr == null) mr = meshGO.AddComponent<MeshRenderer>(); + } + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + smr.bones = mom.GetBones(); + bool origVal = smr.updateWhenOffscreen; + smr.updateWhenOffscreen = true; + smr.updateWhenOffscreen = origVal; + } + + _ConfigureSceneHierarch(mom, root, mr, mf, smr, m, objsToBeAdded); + + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + return smr; + } + else + { + return mr; + } + } + + /* + could be building for a multiMeshBaker or a singleMeshBaker, targetRenderer will be a scene object. + */ + public static void BuildPrefabHierarchy(MB3_MeshCombinerSingle mom, GameObject instantiatedPrefabRoot, Mesh m, bool createNewChild = false, GameObject[] objsToBeAdded = null) + { + SkinnedMeshRenderer smr = null; + MeshRenderer mr = null; + MeshFilter mf = null; + GameObject meshGO = new GameObject(mom.name + "-mesh"); + meshGO.transform.parent = instantiatedPrefabRoot.transform; + Transform mt = meshGO.transform; + + mt.parent = instantiatedPrefabRoot.transform; + meshGO = mt.gameObject; + if (mom.settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + MeshRenderer r = meshGO.GetComponent<MeshRenderer>(); + if (r != null) MB_Utility.Destroy(r); + MeshFilter f = meshGO.GetComponent<MeshFilter>(); + if (f != null) MB_Utility.Destroy(f); + smr = meshGO.GetComponent<SkinnedMeshRenderer>(); + if (smr == null) smr = meshGO.AddComponent<SkinnedMeshRenderer>(); + } + else + { + SkinnedMeshRenderer r = meshGO.GetComponent<SkinnedMeshRenderer>(); + if (r != null) MB_Utility.Destroy(r); + mf = meshGO.GetComponent<MeshFilter>(); + if (mf == null) mf = meshGO.AddComponent<MeshFilter>(); + mr = meshGO.GetComponent<MeshRenderer>(); + if (mr == null) mr = meshGO.AddComponent<MeshRenderer>(); + } + if (mom.settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + smr.bones = mom.GetBones(); + bool origVal = smr.updateWhenOffscreen; + smr.updateWhenOffscreen = true; + smr.updateWhenOffscreen = origVal; + smr.sharedMesh = m; + + MB_BlendShape2CombinedMap srcMap = mom._targetRenderer.GetComponent<MB_BlendShape2CombinedMap>(); + if (srcMap != null) + { + MB_BlendShape2CombinedMap targMap = meshGO.GetComponent<MB_BlendShape2CombinedMap>(); + if (targMap == null) targMap = meshGO.AddComponent<MB_BlendShape2CombinedMap>(); + targMap.srcToCombinedMap = srcMap.srcToCombinedMap; + for (int i = 0; i < targMap.srcToCombinedMap.combinedMeshTargetGameObject.Length; i++) + { + targMap.srcToCombinedMap.combinedMeshTargetGameObject[i] = meshGO; + } + } + + } + + _ConfigureSceneHierarch(mom, instantiatedPrefabRoot, mr, mf, smr, m, objsToBeAdded); + + //First try to get the materials from the target renderer. This is because the mesh may have fewer submeshes than number of result materials if some of the submeshes had zero length tris. + //If we have just baked then materials on the target renderer will be correct wheras materials on the textureBakeResult may not be correct. + if (mom.targetRenderer != null) + { + Material[] sharedMats = new Material[mom.targetRenderer.sharedMaterials.Length]; + for (int i = 0; i < sharedMats.Length; i++) + { + sharedMats[i] = mom.targetRenderer.sharedMaterials[i]; + } + if (mom.settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + smr.sharedMaterial = null; + smr.sharedMaterials = sharedMats; + } + else + { + mr.sharedMaterial = null; + mr.sharedMaterials = sharedMats; + } + } + } + + private static void _ConfigureSceneHierarch(MB3_MeshCombinerSingle mom, GameObject root, MeshRenderer mr, MeshFilter mf, SkinnedMeshRenderer smr, Mesh m, GameObject[] objsToBeAdded = null) + { + //assumes everything is set up correctly + GameObject meshGO; + if (mom.settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + meshGO = smr.gameObject; + //smr.sharedMesh = m; can't assign mesh for skinned mesh until it has skinning information + smr.lightmapIndex = mom.GetLightmapIndex(); + } + else { + meshGO = mr.gameObject; + mf.sharedMesh = m; + mr.lightmapIndex = mom.GetLightmapIndex(); + } + if (mom.settings.lightmapOption == MB2_LightmapOptions.preserve_current_lightmapping || mom.settings.lightmapOption == MB2_LightmapOptions.generate_new_UV2_layout) + { + meshGO.isStatic = true; + } + + //set layer and tag of combined object if all source objs have same layer + if (objsToBeAdded != null && objsToBeAdded.Length > 0 && objsToBeAdded[0] != null) + { + bool tagsAreSame = true; + bool layersAreSame = true; + string tag = objsToBeAdded[0].tag; + int layer = objsToBeAdded[0].layer; + for (int i = 0; i < objsToBeAdded.Length; i++) + { + if (objsToBeAdded[i] != null) + { + if (!objsToBeAdded[i].tag.Equals(tag)) tagsAreSame = false; + if (objsToBeAdded[i].layer != layer) layersAreSame = false; + } + } + if (tagsAreSame) + { + root.tag = tag; + meshGO.tag = tag; + } + if (layersAreSame) + { + root.layer = layer; + meshGO.layer = layer; + } + } + } + + public void BuildSceneMeshObject(GameObject[] gos = null, bool createNewChild = false) + { + if (_resultSceneObject == null) + { + _resultSceneObject = new GameObject("CombinedMesh-" + name); + } + + _targetRenderer = BuildSceneHierarchPreBake(this, _resultSceneObject, GetMesh(), createNewChild, gos); + + } + + //tests if a matrix has been mirrored + bool IsMirrored(Matrix4x4 tm) + { + Vector3 x = tm.GetRow(0); + Vector3 y = tm.GetRow(1); + Vector3 z = tm.GetRow(2); + x.Normalize(); y.Normalize(); z.Normalize(); + float an = Vector3.Dot(Vector3.Cross(x, y), z); + return an >= 0 ? false : true; + } + + public override void CheckIntegrity() + { + if (!MB_Utility.DO_INTEGRITY_CHECKS) return; + //check bones. + if (settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = mbDynamicObjectsInCombinedMesh[i]; + HashSet<int> usedBonesWeights = new HashSet<int>(); + HashSet<int> usedBonesIndexes = new HashSet<int>(); + for (int j = dgo.vertIdx; j < dgo.vertIdx + dgo.numVerts; j++) + { + usedBonesWeights.Add(boneWeights[j].boneIndex0); + usedBonesWeights.Add(boneWeights[j].boneIndex1); + usedBonesWeights.Add(boneWeights[j].boneIndex2); + usedBonesWeights.Add(boneWeights[j].boneIndex3); + } + for (int j = 0; j < dgo.indexesOfBonesUsed.Length; j++) + { + usedBonesIndexes.Add(dgo.indexesOfBonesUsed[j]); + } + + usedBonesIndexes.ExceptWith(usedBonesWeights); + if (usedBonesIndexes.Count > 0) + { + Debug.LogError("The bone indexes were not the same. " + usedBonesWeights.Count + " " + usedBonesIndexes.Count); + } + for (int j = 0; j < dgo.indexesOfBonesUsed.Length; j++) + { + if (j < 0 || j > bones.Length) + Debug.LogError("Bone index was out of bounds."); + } + if (settings.renderType == MB_RenderType.skinnedMeshRenderer && dgo.indexesOfBonesUsed.Length < 1) + Debug.Log("DGO had no bones"); + + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.uvRects.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.sourceSharedMaterials.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.encapsulatingRect.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.sourceMaterialTiling.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + Debug.Assert(dgo.targetSubmeshIdxs.Length == dgo.obUVRects.Length, "Array length mismatch targetSubmeshIdxs, uvRects"); + } + + } + + //check blend shapes + if (settings.doBlendShapes) + { + if (settings.renderType != MB_RenderType.skinnedMeshRenderer) + { + Debug.LogError("Blend shapes can only be used with skinned meshes."); + } + } + } + + void _copyUV2unchangedToSeparateRects() + { + int uv2Padding = 16; //todo + //todo meshSize + List<Vector2> uv2AtlasSizes = new List<Vector2>(); + float minSize = 10e10f; + float maxSize = 0f; + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + float zz = mbDynamicObjectsInCombinedMesh[i].meshSize.magnitude; + if (zz > maxSize) maxSize = zz; + if (zz < minSize) minSize = zz; + } + + //normalize size so all values lie between these two values + float MAX_UV_VAL = 1000f; + float MIN_UV_VAL = 10f; + float offset = 0; + float scale = 1; + if (maxSize - minSize > MAX_UV_VAL - MIN_UV_VAL) + { + //need to compress the range. Scale until is MAX_UV_VAL - MIN_UV_VAL in size and shift + scale = (MAX_UV_VAL - MIN_UV_VAL) / (maxSize - minSize); + offset = MIN_UV_VAL - minSize * scale; + } else + { + scale = MAX_UV_VAL / maxSize; + } + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + + float zz = mbDynamicObjectsInCombinedMesh[i].meshSize.magnitude; + zz = zz * scale + offset; + Vector2 sz = Vector2.one * zz; + uv2AtlasSizes.Add(sz); + } + + //run texture packer on these rects + MB2_TexturePacker tp = new MB2_TexturePackerRegular(); + tp.atlasMustBePowerOfTwo = false; + AtlasPackingResult[] uv2Rects = tp.GetRects(uv2AtlasSizes, 8192, 8192, uv2Padding); + //Debug.Assert(uv2Rects.Length == 1); + //adjust UV2s + for (int i = 0; i < mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = mbDynamicObjectsInCombinedMesh[i]; + float minx, maxx, miny, maxy; + minx = maxx = uv2s[dgo.vertIdx].x; + miny = maxy = uv2s[dgo.vertIdx].y; + int endIdx = dgo.vertIdx + dgo.numVerts; + for (int j = dgo.vertIdx; j < endIdx; j++) + { + if (uv2s[j].x < minx) minx = uv2s[j].x; + if (uv2s[j].x > maxx) maxx = uv2s[j].x; + if (uv2s[j].y < miny) miny = uv2s[j].y; + if (uv2s[j].y > maxy) maxy = uv2s[j].y; + } + // scale it to fit the rect + Rect r = uv2Rects[0].rects[i]; + for (int j = dgo.vertIdx; j < endIdx; j++) + { + float width = maxx - minx; + float height = maxy - miny; + if (width == 0f) width = 1f; + if (height == 0f) height = 1f; + uv2s[j].x = ((uv2s[j].x - minx) / width) * r.width + r.x; + uv2s[j].y = ((uv2s[j].y - miny) / height) * r.height + r.y; + } + } + } + + public override List<Material> GetMaterialsOnTargetRenderer() + { + List<Material> outMats = new List<Material>(); + if (_targetRenderer != null) + { + outMats.AddRange(_targetRenderer.sharedMaterials); + } + return outMats; + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimple.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimple.cs.meta new file mode 100644 index 00000000..07a44f61 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimple.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eca6351d40e77704d860ee379a554daf +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBlendShapes.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBlendShapes.cs new file mode 100644 index 00000000..c6e012cb --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBlendShapes.cs @@ -0,0 +1,279 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + public partial class MB3_MeshCombinerSingle : MB3_MeshCombiner + { + /// <summary> + /// Get the blend shapes from the source mesh + /// </summary> + public static MBBlendShape[] GetBlendShapes(Mesh m, int gameObjectID, GameObject gameObject, Dictionary<int, MeshChannels> meshID2MeshChannels) + { + 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 shapeIdx = 0; shapeIdx < shapes.Length; shapeIdx++) + { + MBBlendShape shape = shapes[shapeIdx] = new MBBlendShape(); + shape.frames = new MBBlendShapeFrame[MBVersion.GetBlendShapeFrameCount(m, shapeIdx)]; + shape.name = m.GetBlendShapeName(shapeIdx); + shape.indexInSource = shapeIdx; + shape.gameObjectID = gameObjectID; + shape.gameObject = gameObject; + for (int frameIdx = 0; frameIdx < shape.frames.Length; frameIdx++) + { + MBBlendShapeFrame frame = shape.frames[frameIdx] = new MBBlendShapeFrame(); + frame.frameWeight = MBVersion.GetBlendShapeFrameWeight(m, shapeIdx, frameIdx); + frame.vertices = new Vector3[arrayLen]; + frame.normals = new Vector3[arrayLen]; + frame.tangents = new Vector3[arrayLen]; + MBVersion.GetBlendShapeFrameVertices(m, shapeIdx, frameIdx, frame.vertices, frame.normals, frame.tangents); + } + } + mc.blendShapes = shapes; + return mc.blendShapes; + } + else + { //copy cached blend shapes from same mesh assiged to 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]; + } + } + + void ApplyBlendShapeFramesToMeshAndBuildMap() + { + if (MBVersion.GetMajorVersion() > 5 || + (MBVersion.GetMajorVersion() == 5 && MBVersion.GetMinorVersion() >= 3)) + { + if (blendShapesInCombined.Length != blendShapes.Length) blendShapesInCombined = new MBBlendShape[blendShapes.Length]; + Vector3[] targVerts = new UnityEngine.Vector3[verts.Length]; + Vector3[] targNorms = new UnityEngine.Vector3[verts.Length]; + Vector3[] targTans = new UnityEngine.Vector3[verts.Length]; + + ((SkinnedMeshRenderer)_targetRenderer).sharedMesh = null; + + MBVersion.ClearBlendShapes(_mesh); + for (int bsIdx = 0; bsIdx < blendShapes.Length; bsIdx++) + { + MBBlendShape blendShape = blendShapes[bsIdx]; + MB_DynamicGameObject dgo = instance2Combined_MapGet(blendShape.gameObject); + if (dgo != null) + { + int destIdx = dgo.vertIdx; + for (int frmIdx = 0; frmIdx < blendShape.frames.Length; frmIdx++) + { + MBBlendShapeFrame frame = blendShape.frames[frmIdx]; + Array.Copy(frame.vertices, 0, targVerts, destIdx, frame.vertices.Length); + Array.Copy(frame.normals, 0, targNorms, destIdx, frame.normals.Length); + Array.Copy(frame.tangents, 0, targTans, destIdx, frame.tangents.Length); + MBVersion.AddBlendShapeFrame(_mesh, ConvertBlendShapeNameToOutputName(blendShape.name) + blendShape.gameObjectID, frame.frameWeight, targVerts, targNorms, targTans); + // We re-use these arrays restore them to zero + _ZeroArray(targVerts, destIdx, frame.vertices.Length); + _ZeroArray(targNorms, destIdx, frame.normals.Length); + _ZeroArray(targTans, destIdx, frame.tangents.Length); + } + } + else + { + Debug.LogError("InstanceID in blend shape that was not in instance2combinedMap"); + } + blendShapesInCombined[bsIdx] = blendShape; + } + + //this is necessary to get the renderer to refresh its data about the blendshapes. + ((SkinnedMeshRenderer)_targetRenderer).sharedMesh = null; + ((SkinnedMeshRenderer)_targetRenderer).sharedMesh = _mesh; + + // Add the map to the target renderer. + if (settings.doBlendShapes) + { + MB_BlendShape2CombinedMap mapComponent = _targetRenderer.GetComponent<MB_BlendShape2CombinedMap>(); + if (mapComponent == null) mapComponent = _targetRenderer.gameObject.AddComponent<MB_BlendShape2CombinedMap>(); + SerializableSourceBlendShape2Combined map = mapComponent.GetMap(); + BuildSrcShape2CombinedMap(map, blendShapes); + } + } + } + + /// <summary> + /// The source blend shape may have parts that should be stripped away. + /// Use this method to strip away the unused parts. + /// </summary> + string ConvertBlendShapeNameToOutputName(string bs) + { + // remove everything before the final '.' + string[] nameParts = bs.Split('.'); + string lastPart = nameParts[nameParts.Length - 1]; + + return lastPart; + } + + void ApplyBlendShapeFramesToMeshAndBuildMap_MergeBlendShapesWithTheSameName() + { + if (MBVersion.GetMajorVersion() > 5 || + (MBVersion.GetMajorVersion() == 5 && MBVersion.GetMinorVersion() >= 3)) + { + Vector3[] targVerts = new UnityEngine.Vector3[verts.Length]; + Vector3[] targNorms = new UnityEngine.Vector3[verts.Length]; + Vector3[] targTans = new UnityEngine.Vector3[verts.Length]; + + MBVersion.ClearBlendShapes(_mesh); + + // Group source that share the same blendShapeName + bool numFramesError = false; + Dictionary<string, List<MBBlendShape>> shapeName2objs = new Dictionary<string, List<MBBlendShape>>(); + { + for (int i = 0; i < blendShapes.Length; i++) + { + MBBlendShape blendShape = blendShapes[i]; + string blendShapeName = ConvertBlendShapeNameToOutputName(blendShape.name); + List<MBBlendShape> dgosUsingBlendShape; + if (!shapeName2objs.TryGetValue(blendShapeName, out dgosUsingBlendShape)) + { + dgosUsingBlendShape = new List<MBBlendShape>(); + shapeName2objs.Add(blendShapeName, dgosUsingBlendShape); + } + + dgosUsingBlendShape.Add(blendShape); + if (dgosUsingBlendShape.Count > 1) + { + if (dgosUsingBlendShape[0].frames.Length != blendShape.frames.Length) + { + Debug.LogError("BlendShapes with the same name must have the same number of frames."); + numFramesError = true; + } + } + } + } + + if (numFramesError) return; + + if (blendShapesInCombined.Length != blendShapes.Length) blendShapesInCombined = new MBBlendShape[shapeName2objs.Keys.Count]; + + int bsInCombinedIdx = 0; + foreach (string shapeName in shapeName2objs.Keys) + { + List<MBBlendShape> groupOfSrcObjs = shapeName2objs[shapeName]; + MBBlendShape firstBlendShape = groupOfSrcObjs[0]; + int numFrames = firstBlendShape.frames.Length; + int db_numVertsAdded = 0; + int db_numObjsAdded = 0; + string db_vIdx = ""; + + for (int frmIdx = 0; frmIdx < numFrames; frmIdx++) + { + float firstFrameWeight = firstBlendShape.frames[frmIdx].frameWeight; + + for (int dgoIdx = 0; dgoIdx < groupOfSrcObjs.Count; dgoIdx++) + { + MBBlendShape blendShape = groupOfSrcObjs[dgoIdx]; + MB_DynamicGameObject dgo = instance2Combined_MapGet(blendShape.gameObject); + int destIdx = dgo.vertIdx; + Debug.Assert(blendShape.frames.Length == numFrames); + MBBlendShapeFrame frame = blendShape.frames[frmIdx]; + Debug.Assert(frame.frameWeight == firstFrameWeight); + Array.Copy(frame.vertices, 0, targVerts, destIdx, frame.vertices.Length); + Array.Copy(frame.normals, 0, targNorms, destIdx, frame.normals.Length); + Array.Copy(frame.tangents, 0, targTans, destIdx, frame.tangents.Length); + if (frmIdx == 0) + { + db_numVertsAdded += frame.vertices.Length; + db_vIdx += blendShape.gameObject.name + " " + destIdx + ":" +(destIdx + frame.vertices.Length) + ", "; + } + } + + db_numObjsAdded += groupOfSrcObjs.Count; + MBVersion.AddBlendShapeFrame(_mesh, shapeName, firstFrameWeight, targVerts, targNorms, targTans); + + // We re-use these arrays restore them to zero + _ZeroArray(targVerts, 0, targVerts.Length); + _ZeroArray(targNorms, 0, targNorms.Length); + _ZeroArray(targTans, 0, targTans.Length); + } + + blendShapesInCombined[bsInCombinedIdx] = firstBlendShape; + bsInCombinedIdx++; + } + + + + //this is necessary to get the renderer to refresh its data about the blendshapes. + ((SkinnedMeshRenderer)_targetRenderer).sharedMesh = null; + ((SkinnedMeshRenderer)_targetRenderer).sharedMesh = _mesh; + + // Add the map to the target renderer. + if (settings.doBlendShapes) + { + MB_BlendShape2CombinedMap mapComponent = _targetRenderer.GetComponent<MB_BlendShape2CombinedMap>(); + if (mapComponent == null) mapComponent = _targetRenderer.gameObject.AddComponent<MB_BlendShape2CombinedMap>(); + SerializableSourceBlendShape2Combined map = mapComponent.GetMap(); + BuildSrcShape2CombinedMap(map, blendShapesInCombined); + } + } + } + + void BuildSrcShape2CombinedMap(SerializableSourceBlendShape2Combined map, MBBlendShape[] bs) + { + Debug.Assert(_targetRenderer.gameObject != null, "Target Renderer was null."); + GameObject[] srcGameObjects = new GameObject[bs.Length]; + int[] srcBlendShapeIdxs = new int[bs.Length]; + GameObject[] targGameObjects = new GameObject[bs.Length]; + int[] targBlendShapeIdxs = new int[bs.Length]; + for (int i = 0; i < blendShapesInCombined.Length; i++) + { + srcGameObjects[i] = blendShapesInCombined[i].gameObject; + srcBlendShapeIdxs[i] = blendShapesInCombined[i].indexInSource; + targGameObjects[i] = _targetRenderer.gameObject; + targBlendShapeIdxs[i] = i; + } + + map.SetBuffers(srcGameObjects, srcBlendShapeIdxs, targGameObjects, targBlendShapeIdxs); + } + + [System.Obsolete("BuildSourceBlendShapeToCombinedIndexMap is deprecated. The map will be now be attached to the combined SkinnedMeshRenderer object as the MB_BlendShape2CombinedMap Component.")] + public override Dictionary<MBBlendShapeKey, MBBlendShapeValue> BuildSourceBlendShapeToCombinedIndexMap() + { + if (_targetRenderer == null) return new Dictionary<MBBlendShapeKey, MBBlendShapeValue>(); + MB_BlendShape2CombinedMap mapComponent = _targetRenderer.GetComponent<MB_BlendShape2CombinedMap>(); + if (mapComponent == null) return new Dictionary<MBBlendShapeKey, MBBlendShapeValue>(); + return mapComponent.srcToCombinedMap.GenerateMapFromSerializedData(); + } + + void _ZeroArray(Vector3[] arr, int idx, int length) + { + int bound = idx + length; + for (int i = idx; i < bound; i++) + { + arr[i] = Vector3.zero; + } + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBlendShapes.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBlendShapes.cs.meta new file mode 100644 index 00000000..2ff306c3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBlendShapes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3950e3481903b545a30db75d3460556 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBones.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBones.cs new file mode 100644 index 00000000..554e44af --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBones.cs @@ -0,0 +1,484 @@ +using System.Collections; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace DigitalOpus.MB.Core +{ + + public partial class MB3_MeshCombinerSingle : MB3_MeshCombiner + { + public class MB3_MeshCombinerSimpleBones + { + MB3_MeshCombinerSingle combiner; + List<MB3_MeshCombinerSingle.MB_DynamicGameObject>[] boneIdx2dgoMap = null; + HashSet<int> boneIdxsToDelete = new HashSet<int>(); + HashSet<MB3_MeshCombinerSingle.BoneAndBindpose> bonesToAdd = new HashSet<MB3_MeshCombinerSingle.BoneAndBindpose>(); + Dictionary<BoneAndBindpose, int> boneAndBindPose2idx = new Dictionary<BoneAndBindpose, int>(); + + public MB3_MeshCombinerSimpleBones(MB3_MeshCombinerSingle cm) + { + combiner = cm; + } + + public HashSet<MB3_MeshCombinerSingle.BoneAndBindpose> GetBonesToAdd() + { + return bonesToAdd; + } + + public int GetNumBonesToDelete() + { + return boneIdxsToDelete.Count; + } + + private bool _didSetup = false; + public void BuildBoneIdx2DGOMapIfNecessary(int[] _goToDelete) + { + _didSetup = false; + if (combiner.settings.renderType == MB_RenderType.skinnedMeshRenderer) + { + if (_goToDelete.Length > 0) + { + boneIdx2dgoMap = _buildBoneIdx2dgoMap(); + } + + for (int i = 0; i < combiner.bones.Length; i++) + { + BoneAndBindpose bn = new BoneAndBindpose(combiner.bones[i], combiner.bindPoses[i]); + boneAndBindPose2idx.Add(bn, i); + //myBone2idx.Add(combiner.bones[i], i); + } + + _didSetup = true; + } + } + + public void FindBonesToDelete(MB_DynamicGameObject dgo) + { + Debug.Assert(_didSetup); + Debug.Assert(combiner.settings.renderType == MB_RenderType.skinnedMeshRenderer); + // We could be working with adding and deleting smr body parts from the same rig. Different smrs will share + // the same bones. Track if we need to delete a bone or not. + for (int j = 0; j < dgo.indexesOfBonesUsed.Length; j++) + { + int idxOfUsedBone = dgo.indexesOfBonesUsed[j]; + List<MB_DynamicGameObject> dgosThatUseBone = boneIdx2dgoMap[idxOfUsedBone]; + if (dgosThatUseBone.Contains(dgo)) + { + dgosThatUseBone.Remove(dgo); + if (dgosThatUseBone.Count == 0) + { + boneIdxsToDelete.Add(idxOfUsedBone); + } + } + } + } + + public int GetNewBonesLength() + { + return combiner.bindPoses.Length + bonesToAdd.Count - boneIdxsToDelete.Count; + } + + public bool CollectBonesToAddForDGO(MB_DynamicGameObject dgo, Renderer r, bool noExtraBonesForMeshRenderers, MeshChannelsCache meshChannelCache) + { + bool success = true; + Debug.Assert(_didSetup, "Need to setup first."); + Debug.Assert(combiner.settings.renderType == MB_RenderType.skinnedMeshRenderer); + // We could be working with adding and deleting smr body parts from the same rig. Different smrs will share + // the same bones. + + //cache the bone data that we will be adding. + Matrix4x4[] dgoBindPoses = dgo._tmpSMR_CachedBindposes = meshChannelCache.GetBindposes(r, out dgo.isSkinnedMeshWithBones); + BoneWeight[] dgoBoneWeights = dgo._tmpSMR_CachedBoneWeights = meshChannelCache.GetBoneWeights(r, dgo.numVerts, dgo.isSkinnedMeshWithBones); + Transform[] dgoBones = dgo._tmpSMR_CachedBones = combiner._getBones(r, dgo.isSkinnedMeshWithBones); + + + for (int i = 0; i < dgoBones.Length; i++) + { + if (dgoBones[i] == null) + { + Debug.LogError("Source mesh r had a 'null' bone. Bones must not be null: " + r); + success = false; + } + } + + if (!success) return success; + + if (noExtraBonesForMeshRenderers) + { + if (MB_Utility.GetRenderer(dgo.gameObject) is MeshRenderer) + { + // We are visiting a single dgo which is a MeshRenderer. + // It may be the child decendant of a bone in another skinned mesh that is being baked or is already in the combined mesh. We need to find that bone if it exists. + // We need to check our parent ancestors and search the bone lists of the other dgos being added or previously baked looking for bones that may have been added + Debug.Assert(dgoBones.Length == 1 && dgoBindPoses.Length == 1); + // find and cache the parent bone for this MeshRenderer (it may not be the transform.parent) + bool foundBoneParent = false; + BoneAndBindpose boneParent = new BoneAndBindpose(); + { + Transform t = dgo.gameObject.transform.parent; + while (t != null) + { + // Look for parent peviously baked in the combined mesh. + foreach (BoneAndBindpose b in boneAndBindPose2idx.Keys) + { + if (b.bone == t) + { + boneParent = b; + foundBoneParent = true; + break; + } + } + + // Look for parent in something we are adding. + foreach (BoneAndBindpose b in bonesToAdd) + { + if (b.bone == t) + { + boneParent = b; + foundBoneParent = true; + break; + } + } + + if (foundBoneParent) + { + break; + } + else + { + t = t.parent; + } + } + } + + if (foundBoneParent) + { + dgoBones[0] = boneParent.bone; + dgoBindPoses[0] = boneParent.bindPose; + } + } + } + + // The mesh being added may not use all bones on the rig. Find the bones actually used. + int[] usedBoneIdx2srcMeshBoneIdx; + { + /* + HashSet<int> usedBones = new HashSet<int>(); + for (int j = 0; j < dgoBoneWeights.Length; j++) + { + usedBones.Add(dgoBoneWeights[j].boneIndex0); + usedBones.Add(dgoBoneWeights[j].boneIndex1); + usedBones.Add(dgoBoneWeights[j].boneIndex2); + usedBones.Add(dgoBoneWeights[j].boneIndex3); + } + + usedBoneIdx2srcMeshBoneIdx = new int[usedBones.Count]; + usedBones.CopyTo(usedBoneIdx2srcMeshBoneIdx); + */ + } + + { + usedBoneIdx2srcMeshBoneIdx = new int[dgoBones.Length]; + for (int i = 0; i < usedBoneIdx2srcMeshBoneIdx.Length; i++) usedBoneIdx2srcMeshBoneIdx[i] = i; + } + + // For each bone see if it exists in the bones array (with the same bindpose.). + // We might be baking several skinned meshes on the same rig. We don't want duplicate bones in the bones array. + for (int i = 0; i < dgoBones.Length; i++) + { + bool foundInBonesList = false; + int bidx; + int dgoBoneIdx = usedBoneIdx2srcMeshBoneIdx[i]; + BoneAndBindpose bb = new BoneAndBindpose(dgoBones[dgoBoneIdx], dgoBindPoses[dgoBoneIdx]); + if (boneAndBindPose2idx.TryGetValue(bb, out bidx)) + { + if (dgoBones[dgoBoneIdx] == combiner.bones[bidx] && + !boneIdxsToDelete.Contains(bidx) && + dgoBindPoses[dgoBoneIdx] == combiner.bindPoses[bidx]) + { + foundInBonesList = true; + } + } + + if (!foundInBonesList) + { + if (!bonesToAdd.Contains(bb)) + { + bonesToAdd.Add(bb); + } + } + } + + dgo._tmpSMRIndexesOfSourceBonesUsed = usedBoneIdx2srcMeshBoneIdx; + return success; + } + + private List<MB3_MeshCombinerSingle.MB_DynamicGameObject>[] _buildBoneIdx2dgoMap() + { + List<MB3_MeshCombinerSingle.MB_DynamicGameObject>[] boneIdx2dgoMap = new List<MB3_MeshCombinerSingle.MB_DynamicGameObject>[combiner.bones.Length]; + for (int i = 0; i < boneIdx2dgoMap.Length; i++) boneIdx2dgoMap[i] = new List<MB3_MeshCombinerSingle.MB_DynamicGameObject>(); + // build the map of bone indexes to objects that use them + for (int i = 0; i < combiner.mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB3_MeshCombinerSingle.MB_DynamicGameObject dgo = combiner.mbDynamicObjectsInCombinedMesh[i]; + for (int j = 0; j < dgo.indexesOfBonesUsed.Length; j++) + { + boneIdx2dgoMap[dgo.indexesOfBonesUsed[j]].Add(dgo); + } + } + + return boneIdx2dgoMap; + } + + public void CopyBonesWeAreKeepingToNewBonesArrayAndAdjustBWIndexes(Transform[] nbones, Matrix4x4[] nbindPoses, BoneWeight[] nboneWeights, int totalDeleteVerts) + { + // bones are copied separately because some dgos share bones + if (boneIdxsToDelete.Count > 0) + { + int[] boneIdxsToDel = new int[boneIdxsToDelete.Count]; + boneIdxsToDelete.CopyTo(boneIdxsToDel); + Array.Sort(boneIdxsToDel); + //bones are being moved in bones array so need to do some remapping + int[] oldBonesIndex2newBonesIndexMap = new int[combiner.bones.Length]; + int newIdx = 0; + int indexInDeleteList = 0; + + //bones were deleted so we need to rebuild bones and bind poses + //and build a map of old bone indexes to new bone indexes + //do this by copying old to new skipping ones we are deleting + for (int i = 0; i < combiner.bones.Length; i++) + { + if (indexInDeleteList < boneIdxsToDel.Length && + boneIdxsToDel[indexInDeleteList] == i) + { + //we are deleting this bone so skip its index + indexInDeleteList++; + oldBonesIndex2newBonesIndexMap[i] = -1; + } + else + { + oldBonesIndex2newBonesIndexMap[i] = newIdx; + nbones[newIdx] = combiner.bones[i]; + nbindPoses[newIdx] = combiner.bindPoses[i]; + newIdx++; + } + } + //adjust the indexes on the boneWeights + int numVertKeeping = combiner.boneWeights.Length - totalDeleteVerts; + { + for (int i = 0; i < numVertKeeping; i++) + { + BoneWeight bw = nboneWeights[i]; + bw.boneIndex0 = oldBonesIndex2newBonesIndexMap[bw.boneIndex0]; + bw.boneIndex1 = oldBonesIndex2newBonesIndexMap[bw.boneIndex1]; + bw.boneIndex2 = oldBonesIndex2newBonesIndexMap[bw.boneIndex2]; + bw.boneIndex3 = oldBonesIndex2newBonesIndexMap[bw.boneIndex3]; + nboneWeights[i] = bw; + } + } + + /* + unsafe + { + fixed (BoneWeight* boneWeightFirstPtr = &nboneWeights[0]) + { + BoneWeight* boneWeightPtr = boneWeightFirstPtr; + for (int i = 0; i < numVertKeeping; i++) + { + boneWeightPtr->boneIndex0 = oldBonesIndex2newBonesIndexMap[boneWeightPtr->boneIndex0]; + boneWeightPtr->boneIndex1 = oldBonesIndex2newBonesIndexMap[boneWeightPtr->boneIndex1]; + boneWeightPtr->boneIndex2 = oldBonesIndex2newBonesIndexMap[boneWeightPtr->boneIndex2]; + boneWeightPtr->boneIndex3 = oldBonesIndex2newBonesIndexMap[boneWeightPtr->boneIndex3]; + boneWeightPtr++; + } + } + } + */ + + //adjust the bone indexes on the dgos from old to new + for (int i = 0; i < combiner.mbDynamicObjectsInCombinedMesh.Count; i++) + { + MB_DynamicGameObject dgo = combiner.mbDynamicObjectsInCombinedMesh[i]; + for (int j = 0; j < dgo.indexesOfBonesUsed.Length; j++) + { + dgo.indexesOfBonesUsed[j] = oldBonesIndex2newBonesIndexMap[dgo.indexesOfBonesUsed[j]]; + } + } + } + else + { //no bones are moving so can simply copy bones from old to new + Array.Copy(combiner.bones, nbones, combiner.bones.Length); + Array.Copy(combiner.bindPoses, nbindPoses, combiner.bindPoses.Length); + } + } + + public static void AddBonesToNewBonesArrayAndAdjustBWIndexes(MB3_MeshCombinerSingle combiner, MB_DynamicGameObject dgo, Renderer r, int vertsIdx, + Transform[] nbones, BoneWeight[] nboneWeights, MeshChannelsCache meshChannelCache) + { + Transform[] dgoBones = dgo._tmpSMR_CachedBones; + Matrix4x4[] dgoBindPoses = dgo._tmpSMR_CachedBindposes; + BoneWeight[] dgoBoneWeights = dgo._tmpSMR_CachedBoneWeights; + int[] srcIndex2combinedIndexMap = new int[dgoBones.Length]; + for (int j = 0; j < dgo._tmpSMRIndexesOfSourceBonesUsed.Length; j++) + { + int dgoBoneIdx = dgo._tmpSMRIndexesOfSourceBonesUsed[j]; + + for (int k = 0; k < nbones.Length; k++) + { + if (dgoBones[dgoBoneIdx] == nbones[k]) + { + if (dgoBindPoses[dgoBoneIdx] == combiner.bindPoses[k]) + { + srcIndex2combinedIndexMap[dgoBoneIdx] = k; + break; + } + } + } + } + + //remap the bone weights for this dgo + //build a list of usedBones, can't trust dgoBones because it contains all bones in the rig + for (int j = 0; j < dgoBoneWeights.Length; j++) + { + int newVertIdx = vertsIdx + j; + nboneWeights[newVertIdx].boneIndex0 = srcIndex2combinedIndexMap[dgoBoneWeights[j].boneIndex0]; + nboneWeights[newVertIdx].boneIndex1 = srcIndex2combinedIndexMap[dgoBoneWeights[j].boneIndex1]; + nboneWeights[newVertIdx].boneIndex2 = srcIndex2combinedIndexMap[dgoBoneWeights[j].boneIndex2]; + nboneWeights[newVertIdx].boneIndex3 = srcIndex2combinedIndexMap[dgoBoneWeights[j].boneIndex3]; + nboneWeights[newVertIdx].weight0 = dgoBoneWeights[j].weight0; + nboneWeights[newVertIdx].weight1 = dgoBoneWeights[j].weight1; + nboneWeights[newVertIdx].weight2 = dgoBoneWeights[j].weight2; + nboneWeights[newVertIdx].weight3 = dgoBoneWeights[j].weight3; + } + + // repurposing the _tmpIndexesOfSourceBonesUsed since + //we don't need it anymore and this saves a memory allocation . remap the indexes that point to source bones to combined bones. + for (int j = 0; j < dgo._tmpSMRIndexesOfSourceBonesUsed.Length; j++) + { + dgo._tmpSMRIndexesOfSourceBonesUsed[j] = srcIndex2combinedIndexMap[dgo._tmpSMRIndexesOfSourceBonesUsed[j]]; + } + dgo.indexesOfBonesUsed = dgo._tmpSMRIndexesOfSourceBonesUsed; + dgo._tmpSMRIndexesOfSourceBonesUsed = null; + dgo._tmpSMR_CachedBones = null; + dgo._tmpSMR_CachedBindposes = null; + dgo._tmpSMR_CachedBoneWeights = null; + + //check original bones and bindPoses + /* + for (int j = 0; j < dgo.indexesOfBonesUsed.Length; j++) { + Transform bone = bones[dgo.indexesOfBonesUsed[j]]; + Matrix4x4 bindpose = bindPoses[dgo.indexesOfBonesUsed[j]]; + bool found = false; + for (int k = 0; k < dgo._originalBones.Length; k++) { + if (dgo._originalBones[k] == bone && dgo._originalBindPoses[k] == bindpose) { + found = true; + } + } + if (!found) Debug.LogError("A Mismatch between original bones and bones array. " + dgo.name); + } + */ + } + + internal void CopyVertsNormsTansToBuffers(MB_DynamicGameObject dgo, MB_IMeshBakerSettings settings, int vertsIdx, Vector3[] nnorms, Vector4[] ntangs, Vector3[] nverts, Vector3[] normals, Vector4[] tangents, Vector3[] verts) + { + bool isMeshRenderer = dgo.gameObject.GetComponent<Renderer>() is MeshRenderer; + if (settings.smrNoExtraBonesWhenCombiningMeshRenderers && + isMeshRenderer && + dgo._tmpSMR_CachedBones[0] != dgo.gameObject.transform // bone may not have a parent ancestor that is a bone + ) + { + // transform all the verticies, norms and tangents into the parent bone's local space (adjusted by the parent bone's bind pose). + // there should be only one bone and bind pose for a mesh renderer dgo. + // The bone and bind pose should be the parent-bone's NOT the MeshRenderers. + Matrix4x4 l2parentMat = dgo._tmpSMR_CachedBindposes[0].inverse * dgo._tmpSMR_CachedBones[0].worldToLocalMatrix * dgo.gameObject.transform.localToWorldMatrix; + + // Similar to local2world but with translation removed and we are using the inverse transpose. + // We use this for normals and tangents because it handles scaling correctly. + Matrix4x4 l2parentRotScale = l2parentMat; + l2parentRotScale[0, 3] = l2parentRotScale[1, 3] = l2parentRotScale[2, 3] = 0f; + l2parentRotScale = l2parentRotScale.inverse.transpose; + + //can't modify the arrays we get from the cache because they will be modified multiple times if the same mesh is being added multiple times. + for (int j = 0; j < nverts.Length; j++) + { + int vIdx = vertsIdx + j; + verts[vertsIdx + j] = l2parentMat.MultiplyPoint3x4(nverts[j]); + if (settings.doNorm) + { + normals[vIdx] = l2parentRotScale.MultiplyPoint3x4(nnorms[j]).normalized; + } + if (settings.doTan) + { + float w = ntangs[j].w; //need to preserve the w value + tangents[vIdx] = l2parentRotScale.MultiplyPoint3x4(((Vector3)ntangs[j])).normalized; + tangents[vIdx].w = w; + } + } + } + else + { + if (settings.doNorm) nnorms.CopyTo(normals, vertsIdx); + if (settings.doTan) ntangs.CopyTo(tangents, vertsIdx); + nverts.CopyTo(verts, vertsIdx); + } + } + } + + public override void UpdateSkinnedMeshApproximateBounds() + { + UpdateSkinnedMeshApproximateBoundsFromBounds(); + } + + public override void UpdateSkinnedMeshApproximateBoundsFromBones() + { + if (outputOption == MB2_OutputOptions.bakeMeshAssetsInPlace) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Can't UpdateSkinnedMeshApproximateBounds when output type is bakeMeshAssetsInPlace"); + return; + } + if (bones.Length == 0) + { + if (verts.Length > 0) if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("No bones in SkinnedMeshRenderer. Could not UpdateSkinnedMeshApproximateBounds."); + return; + } + if (_targetRenderer == null) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Target Renderer is not set. No point in calling UpdateSkinnedMeshApproximateBounds."); + return; + } + if (!_targetRenderer.GetType().Equals(typeof(SkinnedMeshRenderer))) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Target Renderer is not a SkinnedMeshRenderer. No point in calling UpdateSkinnedMeshApproximateBounds."); + return; + } + UpdateSkinnedMeshApproximateBoundsFromBonesStatic(bones, (SkinnedMeshRenderer)targetRenderer); + } + + public override void UpdateSkinnedMeshApproximateBoundsFromBounds() + { + if (outputOption == MB2_OutputOptions.bakeMeshAssetsInPlace) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Can't UpdateSkinnedMeshApproximateBoundsFromBounds when output type is bakeMeshAssetsInPlace"); + return; + } + if (verts.Length == 0 || mbDynamicObjectsInCombinedMesh.Count == 0) + { + if (verts.Length > 0) if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Nothing in SkinnedMeshRenderer. CoulddoBlendShapes not UpdateSkinnedMeshApproximateBoundsFromBounds."); + return; + } + if (_targetRenderer == null) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Target Renderer is not set. No point in calling UpdateSkinnedMeshApproximateBoundsFromBounds."); + return; + } + if (!_targetRenderer.GetType().Equals(typeof(SkinnedMeshRenderer))) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Target Renderer is not a SkinnedMeshRenderer. No point in calling UpdateSkinnedMeshApproximateBoundsFromBounds."); + return; + } + + UpdateSkinnedMeshApproximateBoundsFromBoundsStatic(objectsInCombinedMesh, (SkinnedMeshRenderer)targetRenderer); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBones.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBones.cs.meta new file mode 100644 index 00000000..5d2c1edb --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleBones.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f3ed6449bd7407a4caaec8467fcd21e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleData.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleData.cs new file mode 100644 index 00000000..33ad434f --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleData.cs @@ -0,0 +1,657 @@ +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 +{ + /// <summary> + /// 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. + /// </summary> + 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<MB_DynamicGameObject> + { + public int instanceID; + public GameObject gameObject; + public string name; + public int vertIdx; + public int blendShapeIdx; + public int numVerts; + public int numBlendShapes; + + public bool isSkinnedMeshWithBones = false; // it is possible for a skinned mesh to have blend shapes but no bones. + + //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; + + /// <summary> + /// 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 + /// </summary> + // These are result mesh submeshCount comine these into a class. + public int[] submeshTriIdxs; + public int[] submeshNumTris; + + /// <summary> + /// These are source go mesh submeshCount todo combined these into a class. + /// Maps each submesh in source mesh to a submesh in combined mesh. + /// </summary> + public int[] targetSubmeshIdxs; + + /// <summary> + /// The UVRects in the combinedMaterial atlas. + /// </summary> + public Rect[] uvRects; + + /// <summary> + /// 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. + /// </summary> + public Rect[] encapsulatingRect; + + /// <summary> + /// If AllPropsUseSameMatTiling is the source texture material tiling. + /// else is 0,0,1,1. We don't need to care which. + /// </summary> + public Rect[] sourceMaterialTiling; + + /// <summary> + /// The obUVRect for each source mesh submesh; + /// </summary> + public Rect[] obUVRects; + + /// <summary> + /// The index of the texture array slice. + /// </summary> + public int[] textureArraySliceIdx; + + public Material[] sourceSharedMaterials; + + public bool _beingDeleted = false; + public int _triangleIdxAdjustment = 0; + + // temporary buffers used within a single bake. Not cached between bakes + // used so we don't have to call GetBones and GetBindposes multiple Times + [NonSerialized] + public SerializableIntArray[] _tmpSubmeshTris; + + // temporary buffers for bone baking + [NonSerialized] + public Transform[] _tmpSMR_CachedBones; + [NonSerialized] + public Matrix4x4[] _tmpSMR_CachedBindposes; + [NonSerialized] + public BoneWeight[] _tmpSMR_CachedBoneWeights; + [NonSerialized] + public int[] _tmpSMRIndexesOfSourceBonesUsed; + + 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<int, MeshChannels> meshID2MeshChannels = new Dictionary<int, MeshChannels>(); + + 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, out bool isSkinnedMeshWithBones) + { + 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, out isSkinnedMeshWithBones); + } else + { + if (r is SkinnedMeshRenderer && + mc.bindPoses.Length > 0) + { + isSkinnedMeshWithBones = true; + } else + { + isSkinnedMeshWithBones = false; + if (r is SkinnedMeshRenderer) Debug.Assert(m.blendShapeCount > 0, "Skinned Mesh Renderer " + r + " had no bones and no blend shapes"); + } + } + + return mc.bindPoses; + } + + internal BoneWeight[] GetBoneWeights(Renderer r, int numVertsInMeshBeingAdded, bool isSkinnedMeshWithBones) + { + 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, isSkinnedMeshWithBones); + } + 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) + { + return MB3_MeshCombinerSingle.GetBlendShapes(m, gameObjectID, gameObject, meshID2MeshChannels); + } + + 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, out bool isSkinnedMeshWithBones) + { + + Matrix4x4[] poses = null; + isSkinnedMeshWithBones = r is SkinnedMeshRenderer; + if (r is SkinnedMeshRenderer) + { + poses = ((SkinnedMeshRenderer) r).sharedMesh.bindposes; + if (poses.Length == 0) + { + Mesh m = MB_Utility.GetMesh(r.gameObject); + if (m.blendShapeCount > 0) + { + isSkinnedMeshWithBones = false; + } else + { + Debug.LogError("Skinned mesh " + r + " had no bindposes AND no blend shapes"); + } + } + } + + if (r is MeshRenderer || + (r is SkinnedMeshRenderer && !isSkinnedMeshWithBones)) // It is possible for a skinned mesh to have blend shapes but no bones. These need to be treated like MeshRenderer meshes. + { + Matrix4x4 bindPose = Matrix4x4.identity; + poses = new Matrix4x4[1]; + poses[0] = bindPose; + } + + if (poses == null) { + Debug.LogError("Could not _getBindPoses. Object does not have a renderer"); + return null; + } + + return poses; + } + + public static BoneWeight[] _getBoneWeights(Renderer r, int numVertsInMeshBeingAdded, bool isSkinnedMeshWithBones) + { + if (isSkinnedMeshWithBones) + { + return ((SkinnedMeshRenderer)r).sharedMesh.boneWeights; + } + else if (r is MeshRenderer || + (r is SkinnedMeshRenderer && !isSkinnedMeshWithBones)) // It is possible for a skinned mesh to have blend shapes but no bones. These need to be treated like MeshRenderer meshes + { + 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]; + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleData.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleData.cs.meta new file mode 100644 index 00000000..c6330609 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleData.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a48d33b6bcc4b4645b87cab80608ca2e +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleUVAdjusterAtlas.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleUVAdjusterAtlas.cs new file mode 100644 index 00000000..e958a8d8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleUVAdjusterAtlas.cs @@ -0,0 +1,367 @@ +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 +{ + public partial class MB3_MeshCombinerSingle : MB3_MeshCombiner + { + public class UVAdjuster_Atlas + { + MB2_TextureBakeResults textureBakeResults; + MB2_LogLevel LOG_LEVEL; + int[] numTimesMatAppearsInAtlas; + MB_MaterialAndUVRect[] matsAndSrcUVRect; + + public UVAdjuster_Atlas(MB2_TextureBakeResults tbr, MB2_LogLevel ll) + { + textureBakeResults = tbr; + LOG_LEVEL = ll; + matsAndSrcUVRect = tbr.materialsAndUVRects; + + //count the number of times a material appears in the atlas. used for fast lookup + numTimesMatAppearsInAtlas = new int[matsAndSrcUVRect.Length]; + for (int i = 0; i < matsAndSrcUVRect.Length; i++) + { + if (numTimesMatAppearsInAtlas[i] > 1) + { + continue; + } + int count = 1; + for (int j = i + 1; j < matsAndSrcUVRect.Length; j++) + { + if (matsAndSrcUVRect[i].material == matsAndSrcUVRect[j].material) + { + count++; + } + } + numTimesMatAppearsInAtlas[i] = count; + if (count > 1) + { + //allMatsAreUnique = false; + for (int j = i + 1; j < matsAndSrcUVRect.Length; j++) + { + if (matsAndSrcUVRect[i].material == matsAndSrcUVRect[j].material) + { + numTimesMatAppearsInAtlas[j] = count; + } + } + } + } + } + + public bool MapSharedMaterialsToAtlasRects(Material[] sharedMaterials, + bool checkTargetSubmeshIdxsFromPreviousBake, + Mesh m, MeshChannelsCache meshChannelsCache, + Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache, + OrderedDictionary sourceMats2submeshIdx_map, GameObject go, MB_DynamicGameObject dgoOut) + { + MB_TextureTilingTreatment[] tilingTreatment = new MB_TextureTilingTreatment[sharedMaterials.Length]; + Rect[] uvRectsInAtlas = new Rect[sharedMaterials.Length]; + Rect[] encapsulatingRect = new Rect[sharedMaterials.Length]; + Rect[] sourceMaterialTiling = new Rect[sharedMaterials.Length]; + int[] sliceIdx = new int[sharedMaterials.Length]; + String errorMsg = ""; + for (int srcSubmeshIdx = 0; srcSubmeshIdx<sharedMaterials.Length; srcSubmeshIdx++) + { + System.Object subIdx = sourceMats2submeshIdx_map[sharedMaterials[srcSubmeshIdx]]; + int resMatIdx; + if (subIdx == null) + { + Debug.LogError("Source object " + go.name + " used a material " + sharedMaterials[srcSubmeshIdx] + " that was not in the baked materials."); + return false; + } + else + { + resMatIdx = (int) subIdx; + if (checkTargetSubmeshIdxsFromPreviousBake) + { + /* + Possibilities: + Consider a mesh with three submeshes with materials A, B, C that map to + different submeshes in the combined mesh, AA,BB,CC. The user is updating the UVs on a + MeshRenderer so that object 'one' now uses material C => CC instead of A => AA. This will mean that the + triangle buffers will need to be resized. This is not allowed in UpdateGameObjects. + Must map to the same submesh that the old one mapped to. + */ + + if (resMatIdx != dgoOut.targetSubmeshIdxs[srcSubmeshIdx]) + { + Debug.LogError(String.Format("Update failed for object {0}. Material {1} is mapped to a different submesh in the combined mesh than the previous material. This is not supported. Try using AddDelete.", go.name, sharedMaterials[srcSubmeshIdx])); + return false; + } + } + } + + if (!TryMapMaterialToUVRect(sharedMaterials[srcSubmeshIdx], m, srcSubmeshIdx, resMatIdx, meshChannelsCache, meshAnalysisResultsCache, + out tilingTreatment[srcSubmeshIdx], + out uvRectsInAtlas[srcSubmeshIdx], + out encapsulatingRect[srcSubmeshIdx], + out sourceMaterialTiling[srcSubmeshIdx], + out sliceIdx[srcSubmeshIdx], + ref errorMsg, LOG_LEVEL)) + { + Debug.LogError(errorMsg); + return false; + } + } + + dgoOut.uvRects = uvRectsInAtlas; + dgoOut.encapsulatingRect = encapsulatingRect; + dgoOut.sourceMaterialTiling = sourceMaterialTiling; + dgoOut.textureArraySliceIdx = sliceIdx; + return true; + } + + public void _copyAndAdjustUVsFromMesh(MB2_TextureBakeResults tbr, MB_DynamicGameObject dgo, Mesh mesh, int uvChannel, int vertsIdx, Vector2[] uvsOut, float[] uvsSliceIdx, MeshChannelsCache meshChannelsCache) + { + Debug.Assert(dgo.sourceSharedMaterials != null && dgo.sourceSharedMaterials.Length == dgo.targetSubmeshIdxs.Length, + "sourceSharedMaterials array was a different size than the targetSubmeshIdxs. Was this old data that is being updated? " + dgo.sourceSharedMaterials.Length); + Vector2[] nuvs = meshChannelsCache.GetUVChannel(uvChannel, mesh); + + int[] done = new int[nuvs.Length]; //use this to track uvs that have already been adjusted don't adjust twice + for (int l = 0; l < done.Length; l++) done[l] = -1; + bool triangleArraysOverlap = false; + + //Rect uvRectInSrc = new Rect (0f,0f,1f,1f); + //need to address the UVs through the submesh indexes because + //each submesh has a different UV index + bool doTextureArray = tbr.resultType == MB2_TextureBakeResults.ResultType.textureArray; + for (int srcSubmeshIdx = 0; srcSubmeshIdx < dgo.targetSubmeshIdxs.Length; srcSubmeshIdx++) + { + + int[] srcSubTris; + if (dgo._tmpSubmeshTris != null) + { + srcSubTris = dgo._tmpSubmeshTris[srcSubmeshIdx].data; + } + else + { + srcSubTris = mesh.GetTriangles(srcSubmeshIdx); + } + + float slice = dgo.textureArraySliceIdx[srcSubmeshIdx]; + int resultSubmeshIdx = dgo.targetSubmeshIdxs[srcSubmeshIdx]; + + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(String.Format("Build UV transform for mesh {0} submesh {1} encapsulatingRect {2}", + dgo.name, srcSubmeshIdx, dgo.encapsulatingRect[srcSubmeshIdx])); + + bool considerUVs = textureBakeResults.GetConsiderMeshUVs(resultSubmeshIdx, dgo.sourceSharedMaterials[srcSubmeshIdx]); + Rect rr = MB3_TextureCombinerMerging.BuildTransformMeshUV2AtlasRect( + considerUVs, + dgo.uvRects[srcSubmeshIdx], + dgo.obUVRects == null || dgo.obUVRects.Length == 0 ? new Rect(0, 0, 1, 1) : dgo.obUVRects[srcSubmeshIdx], + dgo.sourceMaterialTiling[srcSubmeshIdx], + dgo.encapsulatingRect[srcSubmeshIdx]); + + for (int srcSubTriIdx = 0; srcSubTriIdx < srcSubTris.Length; srcSubTriIdx++) + { + int srcVertIdx = srcSubTris[srcSubTriIdx]; + if (done[srcVertIdx] == -1) + { + done[srcVertIdx] = srcSubmeshIdx; //prevents a uv from being adjusted twice. Same vert can be on more than one submesh. + Vector2 nuv = nuvs[srcVertIdx]; //don't modify nuvs directly because it is cached and we might be re-using + //if (textureBakeResults.fixOutOfBoundsUVs) { + //uvRectInSrc can be larger than (out of bounds uvs) or smaller than 0..1 + //this transforms the uvs so they fit inside the uvRectInSrc sample box + + // scale, shift to fit in atlas rect + nuv.x = rr.x + nuv.x * rr.width; + nuv.y = rr.y + nuv.y * rr.height; + int idx = vertsIdx + srcVertIdx; + uvsOut[idx] = nuv; + if (doTextureArray) + { + uvsSliceIdx[idx] = slice; + } + } + if (done[srcVertIdx] != srcSubmeshIdx) + { + triangleArraysOverlap = true; + } + } + } + if (triangleArraysOverlap) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) + Debug.LogWarning(dgo.name + "has submeshes which share verticies. Adjusted uvs may not map correctly in combined atlas."); + } + + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("_copyAndAdjustUVsFromMesh copied {0} verts", nuvs.Length)); + } + + /// <summary> + /// A material can appear more than once in an atlas if using fixOutOfBoundsUVs. + /// in this case you need to use the UV rect of the mesh to find the correct rectangle. + /// If the all properties on the mat use the same tiling then + /// encapsulatingRect can be larger and will include baked UV and material tiling + /// If mat uses different tiling for different maps then encapsulatingRect is the uvs of + /// source mesh used to bake atlas and sourceMaterialTilingOut is 0,0,1,1. This works because + /// material tiling was baked into the atlas. + /// </summary> + public bool TryMapMaterialToUVRect(Material mat, Mesh m, int submeshIdx, int idxInResultMats, + MB3_MeshCombinerSingle.MeshChannelsCache meshChannelCache, + Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisCache, + out MB_TextureTilingTreatment tilingTreatment, + out Rect rectInAtlas, + out Rect encapsulatingRectOut, + out Rect sourceMaterialTilingOut, + out int sliceIdx, + ref String errorMsg, + MB2_LogLevel logLevel) + { + if (textureBakeResults.version < MB2_TextureBakeResults.VERSION) + { + textureBakeResults.UpgradeToCurrentVersion(textureBakeResults); + } + tilingTreatment = MB_TextureTilingTreatment.unknown; + if (textureBakeResults.materialsAndUVRects.Length == 0) + { + errorMsg = "The 'Texture Bake Result' needs to be re-baked to be compatible with this version of Mesh Baker. Please re-bake using the MB3_TextureBaker."; + rectInAtlas = new Rect(); + encapsulatingRectOut = new Rect(); + sourceMaterialTilingOut = new Rect(); + sliceIdx = -1; + return false; + } + if (mat == null) + { + rectInAtlas = new Rect(); + encapsulatingRectOut = new Rect(); + sourceMaterialTilingOut = new Rect(); + sliceIdx = -1; + errorMsg = String.Format("Mesh {0} Had no material on submesh {1} cannot map to a material in the atlas", m.name, submeshIdx); + return false; + } + if (submeshIdx >= m.subMeshCount) + { + errorMsg = "Submesh index is greater than the number of submeshes"; + rectInAtlas = new Rect(); + encapsulatingRectOut = new Rect(); + sourceMaterialTilingOut = new Rect(); + sliceIdx = -1; + return false; + } + + //find the first index of this material + int idx = -1; + for (int i = 0; i < matsAndSrcUVRect.Length; i++) + { + if (mat == matsAndSrcUVRect[i].material) + { + idx = i; + break; + } + } + // if couldn't find material + if (idx == -1) + { + rectInAtlas = new Rect(); + encapsulatingRectOut = new Rect(); + sourceMaterialTilingOut = new Rect(); + sliceIdx = -1; + errorMsg = String.Format("Material {0} could not be found in the Texture Bake Result", mat.name); + return false; + } + + bool considerUVs = textureBakeResults.GetConsiderMeshUVs(idxInResultMats, mat); + if (!considerUVs) + { + if (numTimesMatAppearsInAtlas[idx] != 1) + { + Debug.LogError("There is a problem with this TextureBakeResults. FixOutOfBoundsUVs is false and a material appears more than once: " + matsAndSrcUVRect[idx].material + " appears: " + numTimesMatAppearsInAtlas[idx]); + } + MB_MaterialAndUVRect mr = matsAndSrcUVRect[idx]; + rectInAtlas = mr.atlasRect; + tilingTreatment = mr.tilingTreatment; + encapsulatingRectOut = mr.GetEncapsulatingRect(); + sourceMaterialTilingOut = mr.GetMaterialTilingRect(); + sliceIdx = mr.textureArraySliceIdx; + return true; + } + else + { + //todo what if no UVs + //Find UV rect in source mesh + MB_Utility.MeshAnalysisResult[] mar; + if (!meshAnalysisCache.TryGetValue(m.GetInstanceID(), out mar)) + { + mar = new MB_Utility.MeshAnalysisResult[m.subMeshCount]; + for (int j = 0; j < m.subMeshCount; j++) + { + Vector2[] uvss = meshChannelCache.GetUv0Raw(m); + MB_Utility.hasOutOfBoundsUVs(uvss, m, ref mar[j], j); + } + meshAnalysisCache.Add(m.GetInstanceID(), mar); + } + + //this could be a mesh that was not used in the texture baking that has huge UV tiling too big for the rect that was baked + //find a record that has an atlas uvRect capable of containing this + bool found = false; + Rect encapsulatingRect = new Rect(0, 0, 0, 0); + Rect sourceMaterialTiling = new Rect(0, 0, 0, 0); + if (logLevel >= MB2_LogLevel.trace) + { + Debug.Log(String.Format("Trying to find a rectangle in atlas capable of holding tiled sampling rect for mesh {0} using material {1} meshUVrect={2}", m, mat, mar[submeshIdx].uvRect.ToString("f5"))); + } + for (int i = idx; i < matsAndSrcUVRect.Length; i++) + { + MB_MaterialAndUVRect matAndUVrect = matsAndSrcUVRect[i]; + if (matAndUVrect.material == mat) + { + if (matAndUVrect.allPropsUseSameTiling) + { + encapsulatingRect = matAndUVrect.allPropsUseSameTiling_samplingEncapsulatinRect; + sourceMaterialTiling = matAndUVrect.allPropsUseSameTiling_sourceMaterialTiling; + } + else + { + encapsulatingRect = matAndUVrect.propsUseDifferntTiling_srcUVsamplingRect; + sourceMaterialTiling = new Rect(0, 0, 1, 1); + } + + if (MB2_TextureBakeResults.IsMeshAndMaterialRectEnclosedByAtlasRect( + matAndUVrect.tilingTreatment, + mar[submeshIdx].uvRect, + sourceMaterialTiling, + encapsulatingRect, + logLevel)) + { + if (logLevel >= MB2_LogLevel.trace) + { + Debug.Log("Found rect in atlas capable of containing tiled sampling rect for mesh " + m + " at idx=" + i); + } + idx = i; + found = true; + break; + } + } + } + if (found) + { + MB_MaterialAndUVRect mr = matsAndSrcUVRect[idx]; + rectInAtlas = mr.atlasRect; + tilingTreatment = mr.tilingTreatment; + encapsulatingRectOut = mr.GetEncapsulatingRect(); + sourceMaterialTilingOut = mr.GetMaterialTilingRect(); + sliceIdx = mr.textureArraySliceIdx; + return true; + } + else + { + rectInAtlas = new Rect(); + encapsulatingRectOut = new Rect(); + sourceMaterialTilingOut = new Rect(); + sliceIdx = -1; + errorMsg = String.Format("Could not find a tiled rectangle in the atlas capable of containing the uv and material tiling on mesh {0} for material {1}. Was this mesh included when atlases were baked?", m.name, mat); + return false; + } + } + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleUVAdjusterAtlas.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleUVAdjusterAtlas.cs.meta new file mode 100644 index 00000000..bee0cb1c --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MeshCombinerSimpleUVAdjusterAtlas.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b54b8ddc48171c44bc567ad36d474e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MultiMeshCombiner.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MultiMeshCombiner.cs new file mode 100644 index 00000000..66a91db8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MultiMeshCombiner.cs @@ -0,0 +1,738 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Specialized; +using System; +using System.Collections.Generic; +using System.Text; +using DigitalOpus.MB.Core; + +namespace DigitalOpus.MB.Core +{ + + /// <summary> + /// This class is an endless mesh. You don't need to worry about the 65k limit when adding meshes. It is like a List of combined meshes. Internally it manages + /// a collection of MB2_MeshComber objects to which meshes added and deleted as necessary. + /// + /// Note that this implementation does + /// not attempt to split meshes. Each mesh is added to one of the internal meshes as an atomic unit. + /// + /// This class is not a Component so it can be instantiated and used like a regular C Sharp class. + /// </summary> + [System.Serializable] + public class MB3_MultiMeshCombiner : MB3_MeshCombiner + { + + [System.Serializable] + public class CombinedMesh + { + public MB3_MeshCombinerSingle combinedMesh; + public int extraSpace = -1; + public int numVertsInListToDelete = 0; + public int numVertsInListToAdd = 0; + public List<GameObject> gosToAdd; + public List<int> gosToDelete; + public List<GameObject> gosToUpdate; + public bool isDirty = false; //needs apply + + public CombinedMesh(int maxNumVertsInMesh, GameObject resultSceneObject, MB2_LogLevel ll) + { + combinedMesh = new MB3_MeshCombinerSingle(); + combinedMesh.resultSceneObject = resultSceneObject; + combinedMesh.LOG_LEVEL = ll; + extraSpace = maxNumVertsInMesh; + numVertsInListToDelete = 0; + numVertsInListToAdd = 0; + gosToAdd = new List<GameObject>(); + gosToDelete = new List<int>(); + gosToUpdate = new List<GameObject>(); + } + + public bool isEmpty() + { + List<GameObject> obsIn = new List<GameObject>(); + obsIn.AddRange(combinedMesh.GetObjectsInCombined()); + for (int i = 0; i < gosToDelete.Count; i++) + { + for (int j = 0; j < obsIn.Count; j++) + { + if (obsIn[j].GetInstanceID() == gosToDelete[i]) + { + obsIn.RemoveAt(j); + break; + } + } + + } + if (obsIn.Count == 0) return true; + return false; + } + } + + static GameObject[] empty = new GameObject[0]; + static int[] emptyIDs = new int[0]; + + public override MB2_LogLevel LOG_LEVEL + { + get { return _LOG_LEVEL; } + set + { + _LOG_LEVEL = value; + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.LOG_LEVEL = value; + } + } + } + + public override MB2_ValidationLevel validationLevel + { + set + { + _validationLevel = value; + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.validationLevel = _validationLevel; + } + } + get { return _validationLevel; } + } + + public Dictionary<int, CombinedMesh> obj2MeshCombinerMap = new Dictionary<int, CombinedMesh>(); + [SerializeField] + public List<CombinedMesh> meshCombiners = new List<CombinedMesh>(); + + [SerializeField] + int _maxVertsInMesh = 65535; + public int maxVertsInMesh + { + get { return _maxVertsInMesh; } + set + { + if (obj2MeshCombinerMap.Count > 0) + { + //todo how to warn with gui + //Debug.LogError("Can't set the max verts in meshes once there are objects in the mesh."); + return; + } + else if (value < 3) + { + Debug.LogError("Max verts in mesh must be greater than three."); + } + else if (value > MBVersion.MaxMeshVertexCount()) + { + Debug.LogError("MultiMeshCombiner error in maxVertsInMesh. Meshes in unity cannot have more than " + MBVersion.MaxMeshVertexCount() + " vertices. " + value); + } + else + { + _maxVertsInMesh = value; + } + } + } + + public override int GetNumObjectsInCombined() + { + return obj2MeshCombinerMap.Count; + } + + public override int GetNumVerticesFor(GameObject go) + { + CombinedMesh c = null; + if (obj2MeshCombinerMap.TryGetValue(go.GetInstanceID(), out c)) + { + return c.combinedMesh.GetNumVerticesFor(go); + } + else + { + return -1; + } + } + + public override int GetNumVerticesFor(int gameObjectID) + { + CombinedMesh c = null; + if (obj2MeshCombinerMap.TryGetValue(gameObjectID, out c)) + { + return c.combinedMesh.GetNumVerticesFor(gameObjectID); + } + else + { + return -1; + } + } + + public override List<GameObject> GetObjectsInCombined() + { //todo look at getting from keys + List<GameObject> allObjs = new List<GameObject>(); + for (int i = 0; i < meshCombiners.Count; i++) + { + allObjs.AddRange(meshCombiners[i].combinedMesh.GetObjectsInCombined()); + } + return allObjs; + } + + public override int GetLightmapIndex() + { //todo check that all meshcombiners use same lightmap index + if (meshCombiners.Count > 0) return meshCombiners[0].combinedMesh.GetLightmapIndex(); + return -1; + } + + public override bool CombinedMeshContains(GameObject go) + { + return obj2MeshCombinerMap.ContainsKey(go.GetInstanceID()); + } + + bool _validateTextureBakeResults() + { + if (_textureBakeResults == null) + { + Debug.LogError("Texture Bake Results is null. Can't combine meshes."); + return false; + } + if ((_textureBakeResults.materialsAndUVRects == null || _textureBakeResults.materialsAndUVRects.Length == 0)) + { + Debug.LogError("Texture Bake Results has no materials in material to sourceUVRect map. Try baking materials. Can't combine meshes. " + + "If you are trying to combine meshes without combining materials, try removing the Texture Bake Result."); + return false; + } + + + if (_textureBakeResults.NumResultMaterials() == 0) + { + Debug.LogError("Texture Bake Results has no result materials. Try baking materials. Can't combine meshes."); + return false; + } + + return true; + } + + public override void Apply(MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod) + { + for (int i = 0; i < meshCombiners.Count; i++) + { + if (meshCombiners[i].isDirty) + { + meshCombiners[i].combinedMesh.Apply(uv2GenerationMethod); + meshCombiners[i].isDirty = false; + } + } + } + + public override void Apply(bool triangles, bool vertices, bool normals, bool tangents, bool uvs, bool uv2, bool uv3, bool uv4, bool colors, bool bones = false, bool blendShapeFlag = false, GenerateUV2Delegate uv2GenerationMethod = null) + { + Apply(triangles, vertices, normals, tangents, + uvs, uv2, uv3, uv4, + false, false, false, false, + colors, bones, blendShapeFlag, uv2GenerationMethod); + } + + public override void Apply(bool triangles, + bool vertices, + bool normals, + bool tangents, + bool uvs, + bool uv2, + bool uv3, + bool uv4, + bool uv5, + bool uv6, + bool uv7, + bool uv8, + bool colors, + bool bones = false, + bool blendShapesFlag = false, + MB3_MeshCombiner.GenerateUV2Delegate uv2GenerationMethod = null) + { + for (int i = 0; i < meshCombiners.Count; i++) + { + if (meshCombiners[i].isDirty) + { + meshCombiners[i].combinedMesh.Apply(triangles, vertices, normals, tangents, uvs, uv2, uv3, uv4, colors, bones, blendShapesFlag, uv2GenerationMethod); + meshCombiners[i].isDirty = false; + } + } + } + + public override void UpdateSkinnedMeshApproximateBounds() + { + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBounds(); + } + } + + public override void UpdateSkinnedMeshApproximateBoundsFromBones() + { + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBoundsFromBones(); + } + } + + public override void UpdateSkinnedMeshApproximateBoundsFromBounds() + { + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.UpdateSkinnedMeshApproximateBoundsFromBounds(); + } + } + + public override bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateColors, bool updateSkinningInfo) + { + return UpdateGameObjects(gos, recalcBounds, updateVertices, updateNormals, updateTangents, + updateUV, updateUV2, updateUV3, updateUV4, false, false, false, false, + updateColors, updateSkinningInfo); + } + + public override bool UpdateGameObjects(GameObject[] gos, bool recalcBounds, + bool updateVertices, bool updateNormals, bool updateTangents, + bool updateUV, bool updateUV2, bool updateUV3, bool updateUV4, + bool updateUV5, bool updateUV6, bool updateUV7, bool updateUV8, + bool updateColors, bool updateSkinningInfo) + { + if (gos == null) + { + Debug.LogError("list of game objects cannot be null"); + return false; + } + + //build gos lists + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].gosToUpdate.Clear(); + } + + for (int i = 0; i < gos.Length; i++) + { + CombinedMesh cm = null; + obj2MeshCombinerMap.TryGetValue(gos[i].GetInstanceID(), out cm); + if (cm != null) + { + cm.gosToUpdate.Add(gos[i]); + } + else + { + Debug.LogWarning("Object " + gos[i] + " is not in the combined mesh."); + } + } + + bool success = true; + for (int i = 0; i < meshCombiners.Count; i++) + { + if (meshCombiners[i].gosToUpdate.Count > 0) + { + meshCombiners[i].isDirty = true; + GameObject[] gosToUpdate = meshCombiners[i].gosToUpdate.ToArray(); + success = success && meshCombiners[i].combinedMesh.UpdateGameObjects(gosToUpdate, recalcBounds, updateVertices, updateNormals, updateTangents, + updateUV, updateUV2, updateUV3, updateUV4, updateUV5, updateUV6, updateUV7, updateUV8, + updateColors, updateSkinningInfo); + } + } + + return success; + } + + public override bool AddDeleteGameObjects(GameObject[] gos, GameObject[] deleteGOs, bool disableRendererInSource = true) + { + int[] delInstanceIDs = null; + if (deleteGOs != null) + { + delInstanceIDs = new int[deleteGOs.Length]; + for (int i = 0; i < deleteGOs.Length; i++) + { + if (deleteGOs[i] == null) + { + Debug.LogError("The " + i + "th object on the list of objects to delete is 'Null'"); + } + else + { + delInstanceIDs[i] = deleteGOs[i].GetInstanceID(); + } + } + } + return AddDeleteGameObjectsByID(gos, delInstanceIDs, disableRendererInSource); + } + + public override bool AddDeleteGameObjectsByID(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource = true) + { + //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); + //PART 1 ==== Validate + if (deleteGOinstanceIDs == null) deleteGOinstanceIDs = emptyIDs; + if (_usingTemporaryTextureBakeResult && gos != null && gos.Length > 0) + { + MB_Utility.Destroy(_textureBakeResults); + _textureBakeResults = null; + _usingTemporaryTextureBakeResult = false; + } + + //if all objects use the same material we can create a temporary _textureBakeResults + if (_textureBakeResults == null && gos != null && gos.Length > 0 && gos[0] != null) + { + if (!_CreateTemporaryTextrueBakeResult(gos, GetMaterialsOnTargetRenderer())) + { + return false; + } + } + + if (!_validate(gos, deleteGOinstanceIDs)) + { + return false; + } + _distributeAmongBakers(gos, deleteGOinstanceIDs); + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.AddDeleteGameObjects numCombinedMeshes: " + meshCombiners.Count + " added:" + gos.Length + " deleted:" + deleteGOinstanceIDs.Length + " disableRendererInSource:" + disableRendererInSource + " maxVertsPerCombined:" + _maxVertsInMesh); + return _bakeStep1(gos, deleteGOinstanceIDs, disableRendererInSource); + } + + bool _validate(GameObject[] gos, int[] deleteGOinstanceIDs) + { + if (_validationLevel == MB2_ValidationLevel.none) return true; + if (_maxVertsInMesh < 3) Debug.LogError("Invalid value for maxVertsInMesh=" + _maxVertsInMesh); + _validateTextureBakeResults(); + + if (gos != null) + { + for (int i = 0; i < gos.Length; i++) + { + if (gos[i] == null) + { + Debug.LogError("The " + i + "th object on the list of objects to combine is 'None'. Use Command-Delete on Mac OS X; Delete or Shift-Delete on Windows to remove this one element."); + return false; + } + if (_validationLevel >= MB2_ValidationLevel.robust) + { + for (int j = i + 1; j < gos.Length; j++) + { + if (gos[i] == gos[j]) + { + Debug.LogError("GameObject " + gos[i] + "appears twice in list of game objects to add"); + return false; + } + } + if (obj2MeshCombinerMap.ContainsKey(gos[i].GetInstanceID())) + { + bool isInDeleteList = false; + if (deleteGOinstanceIDs != null) + { + for (int k = 0; k < deleteGOinstanceIDs.Length; k++) + { + if (deleteGOinstanceIDs[k] == gos[i].GetInstanceID()) isInDeleteList = true; + } + } + if (!isInDeleteList) + { + Debug.LogError("GameObject " + gos[i] + " is already in the combined mesh " + gos[i].GetInstanceID()); + return false; + } + } + } + } + } + if (deleteGOinstanceIDs != null) + { + if (_validationLevel >= MB2_ValidationLevel.robust) + { + for (int i = 0; i < deleteGOinstanceIDs.Length; i++) + { + for (int j = i + 1; j < deleteGOinstanceIDs.Length; j++) + { + if (deleteGOinstanceIDs[i] == deleteGOinstanceIDs[j]) + { + Debug.LogError("GameObject " + deleteGOinstanceIDs[i] + "appears twice in list of game objects to delete"); + return false; + } + } + if (!obj2MeshCombinerMap.ContainsKey(deleteGOinstanceIDs[i])) + { + Debug.LogWarning("GameObject with instance ID " + deleteGOinstanceIDs[i] + " on the list of objects to delete is not in the combined mesh."); + } + } + } + } + return true; + } + + void _distributeAmongBakers(GameObject[] gos, int[] deleteGOinstanceIDs) + { + if (gos == null) gos = empty; + if (deleteGOinstanceIDs == null) deleteGOinstanceIDs = emptyIDs; + + if (resultSceneObject == null) resultSceneObject = new GameObject("CombinedMesh-" + name); + + //PART 2 ==== calculate which bakers to add objects to + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].extraSpace = _maxVertsInMesh - meshCombiners[i].combinedMesh.GetMesh().vertexCount; + } + //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects1"); + + //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.1"); + //first delete game objects from the existing combinedMeshes keep track of free space + for (int i = 0; i < deleteGOinstanceIDs.Length; i++) + { + CombinedMesh c = null; + if (obj2MeshCombinerMap.TryGetValue(deleteGOinstanceIDs[i], out c)) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Removing " + deleteGOinstanceIDs[i] + " from meshCombiner " + meshCombiners.IndexOf(c)); + c.numVertsInListToDelete += c.combinedMesh.GetNumVerticesFor(deleteGOinstanceIDs[i]); //m.vertexCount; + c.gosToDelete.Add(deleteGOinstanceIDs[i]); + } + else + { + Debug.LogWarning("Object " + deleteGOinstanceIDs[i] + " in the list of objects to delete is not in the combined mesh."); + } + } + for (int i = 0; i < gos.Length; i++) + { + GameObject go = gos[i]; + int numVerts = MB_Utility.GetMesh(go).vertexCount; + CombinedMesh cm = null; + for (int j = 0; j < meshCombiners.Count; j++) + { + if (meshCombiners[j].extraSpace + meshCombiners[j].numVertsInListToDelete - meshCombiners[j].numVertsInListToAdd > numVerts) + { + cm = meshCombiners[j]; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Added " + gos[i] + " to combinedMesh " + j, LOG_LEVEL); + break; + } + } + if (cm == null) + { + cm = new CombinedMesh(maxVertsInMesh, _resultSceneObject, _LOG_LEVEL); + _setMBValues(cm.combinedMesh); + meshCombiners.Add(cm); + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("MB2_MultiMeshCombiner.Created new combinedMesh"); + } + cm.gosToAdd.Add(go); + cm.numVertsInListToAdd += numVerts; + // obj2MeshCombinerMap.Add(go,cm); + } + } + + bool _bakeStep1(GameObject[] gos, int[] deleteGOinstanceIDs, bool disableRendererInSource) + { + //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects2.2"); + //Profile.Start//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); + //PART 3 ==== Add delete meshes from combined + for (int i = 0; i < meshCombiners.Count; i++) + { + CombinedMesh cm = meshCombiners[i]; + if (cm.combinedMesh.targetRenderer == null) + { + cm.combinedMesh.resultSceneObject = _resultSceneObject; + cm.combinedMesh.BuildSceneMeshObject(gos, true); + if (_LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("BuildSO combiner {0} goID {1} targetRenID {2} meshID {3}", i, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); + + } + else + { + if (cm.combinedMesh.targetRenderer.transform.parent != resultSceneObject.transform) + { + Debug.LogError("targetRender objects must be children of resultSceneObject"); + return false; + } + } + if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) + { + cm.combinedMesh.AddDeleteGameObjectsByID(cm.gosToAdd.ToArray(), cm.gosToDelete.ToArray(), disableRendererInSource); + if (_LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Baked combiner {0} obsAdded {1} objsRemoved {2} goID {3} targetRenID {4} meshID {5}", i, cm.gosToAdd.Count, cm.gosToDelete.Count, cm.combinedMesh.targetRenderer.gameObject.GetInstanceID(), cm.combinedMesh.targetRenderer.GetInstanceID(), cm.combinedMesh.GetMesh().GetInstanceID()); + } + Renderer r = cm.combinedMesh.targetRenderer; + Mesh m = cm.combinedMesh.GetMesh(); + if (r is MeshRenderer) + { + MeshFilter mf = r.gameObject.GetComponent<MeshFilter>(); + mf.sharedMesh = m; + } + else + { + SkinnedMeshRenderer smr = (SkinnedMeshRenderer)r; + smr.sharedMesh = m; + } + } + for (int i = 0; i < meshCombiners.Count; i++) + { + CombinedMesh cm = meshCombiners[i]; + for (int j = 0; j < cm.gosToDelete.Count; j++) + { + obj2MeshCombinerMap.Remove(cm.gosToDelete[j]); + } + } + for (int i = 0; i < meshCombiners.Count; i++) + { + CombinedMesh cm = meshCombiners[i]; + for (int j = 0; j < cm.gosToAdd.Count; j++) + { + obj2MeshCombinerMap.Add(cm.gosToAdd[j].GetInstanceID(), cm); + } + if (cm.gosToAdd.Count > 0 || cm.gosToDelete.Count > 0) + { + cm.gosToDelete.Clear(); + cm.gosToAdd.Clear(); + cm.numVertsInListToDelete = 0; + cm.numVertsInListToAdd = 0; + cm.isDirty = true; + } + } + //Profile.End//Profile("MB2_MultiMeshCombiner.AddDeleteGameObjects3"); + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + string s = "Meshes in combined:"; + for (int i = 0; i < meshCombiners.Count; i++) + { + s += " mesh" + i + "(" + meshCombiners[i].combinedMesh.GetObjectsInCombined().Count + ")\n"; + } + s += "children in result: " + resultSceneObject.transform.childCount; + MB2_Log.LogDebug(s, LOG_LEVEL); + } + if (meshCombiners.Count > 0) + { + return true; + } + else + { + return false; + } + } + + [System.Obsolete("BuildSourceBlendShapeToCombinedIndexMap is deprecated. The map will be attached to the combined SkinnedMeshRenderer objects as the MB_BlendShape2CombinedMap Component.")] + public override Dictionary<MBBlendShapeKey, MBBlendShapeValue> BuildSourceBlendShapeToCombinedIndexMap() + { + Dictionary<MBBlendShapeKey, MBBlendShapeValue> map = new Dictionary<MBBlendShapeKey, MBBlendShapeValue>(); + for (int combinerIdx = 0; combinerIdx < meshCombiners.Count; combinerIdx++) + { + + if (meshCombiners[combinerIdx].combinedMesh.targetRenderer == null) continue; + MB_BlendShape2CombinedMap mapComponent = meshCombiners[combinerIdx].combinedMesh.targetRenderer.GetComponent<MB_BlendShape2CombinedMap>(); + if (mapComponent == null) continue; + foreach (KeyValuePair<MBBlendShapeKey, MBBlendShapeValue> entry in mapComponent.srcToCombinedMap.GenerateMapFromSerializedData()) + { + map.Add(entry.Key, entry.Value); + } + } + + return map; + } + + public override void ClearBuffers() + { + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.ClearBuffers(); + } + obj2MeshCombinerMap.Clear(); + } + + public override void ClearMesh() + { + // For the MultiMeshCombiner we want to destroy because that is what an "empty" multi mesh combiner looks like. + DestroyMesh(); + } + + public override void ClearMesh(MB2_EditorMethodsInterface editorMethods) + { + DestroyMeshEditor(editorMethods); + } + + public override void DisposeRuntimeCreated() + { + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.DisposeRuntimeCreated(); + } + } + + public override void DestroyMesh() + { + for (int i = 0; i < meshCombiners.Count; i++) + { + if (meshCombiners[i].combinedMesh.targetRenderer != null) + { + MB_Utility.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject); + } + + meshCombiners[i].combinedMesh.DestroyMesh(); + } + + obj2MeshCombinerMap.Clear(); + meshCombiners.Clear(); + } + + public override void DestroyMeshEditor(MB2_EditorMethodsInterface editorMethods) + { + editorMethods.Destroy(resultSceneObject); + for (int i = 0; i < meshCombiners.Count; i++) + { + //if (meshCombiners[i].combinedMesh.targetRenderer != null) + //{ + // editorMethods.Destroy(meshCombiners[i].combinedMesh.targetRenderer.gameObject); + //} + + meshCombiners[i].combinedMesh.ClearMesh(); + } + + obj2MeshCombinerMap.Clear(); + meshCombiners.Clear(); + } + + void _setMBValues(MB3_MeshCombinerSingle targ) + { + targ.validationLevel = _validationLevel; + targ.textureBakeResults = textureBakeResults; + + // Even though the MultiMeshBaker supports baking into prefabs, the sub-combiners don't do bake into prefab when + // this is happening. They do bake into sceneObject, then the MultiMeshBaker takes their output and combines it + // into a prefab. + targ.outputOption = MB2_OutputOptions.bakeIntoSceneObject; + if (settingsHolder != null) + { + targ.settingsHolder = settingsHolder; + } else + { + targ.renderType = renderType; + targ.lightmapOption = lightmapOption; + targ.doNorm = doNorm; + targ.doTan = doTan; + targ.doCol = doCol; + targ.doUV = doUV; + targ.doUV3 = doUV3; + targ.doUV4 = doUV4; + targ.doUV5 = doUV5; + targ.doUV6 = doUV6; + targ.doUV7 = doUV7; + targ.doUV8 = doUV8; + targ.doBlendShapes = doBlendShapes; + targ.optimizeAfterBake = optimizeAfterBake; + targ.pivotLocationType = pivotLocationType; + targ.uv2UnwrappingParamsHardAngle = uv2UnwrappingParamsHardAngle; + targ.uv2UnwrappingParamsPackMargin = uv2UnwrappingParamsPackMargin; + targ.assignToMeshCustomizer = assignToMeshCustomizer; + } + } + + public override List<Material> GetMaterialsOnTargetRenderer() + { + HashSet<Material> hs = new HashSet<Material>(); + for (int i = 0; i < meshCombiners.Count; i++) + { + hs.UnionWith(meshCombiners[i].combinedMesh.GetMaterialsOnTargetRenderer()); + } + List<Material> outMats = new List<Material>(hs); + return outMats; + } + + public override void CheckIntegrity() + { + if (!MB_Utility.DO_INTEGRITY_CHECKS) return; + for (int i = 0; i < meshCombiners.Count; i++) + { + meshCombiners[i].combinedMesh.CheckIntegrity(); + } + } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MultiMeshCombiner.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MultiMeshCombiner.cs.meta new file mode 100644 index 00000000..8799275e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_MultiMeshCombiner.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3fc29a00029fca443911ec457963b267 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_PriorityQueue.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_PriorityQueue.cs new file mode 100644 index 00000000..74bae502 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_PriorityQueue.cs @@ -0,0 +1,422 @@ +using UnityEngine; +using System; +using System.Collections; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core +{ + /// From https://www.codeproject.com/Articles/126751/Priority-queue-in-Csharp-with-help-of-heap-data-st.aspx + /// <summary> + /// Priority queue based on binary heap, + /// Elements with minimum priority dequeued first + /// </summary> + /// <typeparam name="TPriority">Type of priorities</typeparam> + /// <typeparam name="TValue">Type of values</typeparam> + public class PriorityQueue<TPriority, TValue> : ICollection<KeyValuePair<TPriority, TValue>> + { + public List<KeyValuePair<TPriority, TValue>> _baseHeap; + private IComparer<TPriority> _comparer; + + #region Constructors + + /// <summary> + /// Initializes a new instance of priority queue with default initial capacity and default priority comparer + /// </summary> + public PriorityQueue() + : this(Comparer<TPriority>.Default) + { + } + + /// <summary> + /// Initializes a new instance of priority queue with specified initial capacity and default priority comparer + /// </summary> + /// <param name="capacity">initial capacity</param> + public PriorityQueue(int capacity) + : this(capacity, Comparer<TPriority>.Default) + { + } + + /// <summary> + /// Initializes a new instance of priority queue with specified initial capacity and specified priority comparer + /// </summary> + /// <param name="capacity">initial capacity</param> + /// <param name="comparer">priority comparer</param> + public PriorityQueue(int capacity, IComparer<TPriority> comparer) + { + if (comparer == null) + throw new ArgumentNullException(); + + _baseHeap = new List<KeyValuePair<TPriority, TValue>>(capacity); + _comparer = comparer; + } + + /// <summary> + /// Initializes a new instance of priority queue with default initial capacity and specified priority comparer + /// </summary> + /// <param name="comparer">priority comparer</param> + public PriorityQueue(IComparer<TPriority> comparer) + { + if (comparer == null) + throw new ArgumentNullException(); + + _baseHeap = new List<KeyValuePair<TPriority, TValue>>(); + _comparer = comparer; + } + + /// <summary> + /// Initializes a new instance of priority queue with specified data and default priority comparer + /// </summary> + /// <param name="data">data to be inserted into priority queue</param> + public PriorityQueue(IEnumerable<KeyValuePair<TPriority, TValue>> data) + : this(data, Comparer<TPriority>.Default) + { + } + + /// <summary> + /// Initializes a new instance of priority queue with specified data and specified priority comparer + /// </summary> + /// <param name="data">data to be inserted into priority queue</param> + /// <param name="comparer">priority comparer</param> + public PriorityQueue(IEnumerable<KeyValuePair<TPriority, TValue>> data, IComparer<TPriority> comparer) + { + if (data == null || comparer == null) + throw new ArgumentNullException(); + + _comparer = comparer; + _baseHeap = new List<KeyValuePair<TPriority, TValue>>(data); + // heapify data + for (int pos = _baseHeap.Count / 2 - 1; pos >= 0; pos--) + HeapifyFromBeginningToEnd(pos); + } + + #endregion + + #region Merging + + /// <summary> + /// Merges two priority queues + /// </summary> + /// <param name="pq1">first priority queue</param> + /// <param name="pq2">second priority queue</param> + /// <returns>resultant priority queue</returns> + /// <remarks> + /// source priority queues must have equal comparers, + /// otherwise <see cref="InvalidOperationException"/> will be thrown + /// </remarks> + public static PriorityQueue<TPriority, TValue> MergeQueues(PriorityQueue<TPriority, TValue> pq1, PriorityQueue<TPriority, TValue> pq2) + { + if (pq1 == null || pq2 == null) + throw new ArgumentNullException(); + if (pq1._comparer != pq2._comparer) + throw new InvalidOperationException("Priority queues to be merged must have equal comparers"); + return MergeQueues(pq1, pq2, pq1._comparer); + } + + /// <summary> + /// Merges two priority queues and sets specified comparer for resultant priority queue + /// </summary> + /// <param name="pq1">first priority queue</param> + /// <param name="pq2">second priority queue</param> + /// <param name="comparer">comparer for resultant priority queue</param> + /// <returns>resultant priority queue</returns> + public static PriorityQueue<TPriority, TValue> MergeQueues(PriorityQueue<TPriority, TValue> pq1, PriorityQueue<TPriority, TValue> pq2, IComparer<TPriority> comparer) + { + if (pq1 == null || pq2 == null || comparer == null) + throw new ArgumentNullException(); + // merge data + PriorityQueue<TPriority, TValue> result = new PriorityQueue<TPriority, TValue>(pq1.Count + pq2.Count, pq1._comparer); + result._baseHeap.AddRange(pq1._baseHeap); + result._baseHeap.AddRange(pq2._baseHeap); + // heapify data + for (int pos = result._baseHeap.Count / 2 - 1; pos >= 0; pos--) + result.HeapifyFromBeginningToEnd(pos); + + return result; + } + + #endregion + + #region Priority queue operations + + /// <summary> + /// Enqueues element into priority queue + /// </summary> + /// <param name="priority">element priority</param> + /// <param name="value">element value</param> + public void Enqueue(TPriority priority, TValue value) + { + Insert(priority, value); + } + + /// <summary> + /// Dequeues element with minimum priority and return its priority and value as <see cref="KeyValuePair{TPriority,TValue}"/> + /// </summary> + /// <returns>priority and value of the dequeued element</returns> + /// <remarks> + /// Method throws <see cref="InvalidOperationException"/> if priority queue is empty + /// </remarks> + public KeyValuePair<TPriority, TValue> Dequeue() + { + if (!IsEmpty) + { + KeyValuePair<TPriority, TValue> result = _baseHeap[0]; + DeleteRoot(); + return result; + } + else + throw new InvalidOperationException("Priority queue is empty"); + } + + /// <summary> + /// Dequeues element with minimum priority and return its value + /// </summary> + /// <returns>value of the dequeued element</returns> + /// <remarks> + /// Method throws <see cref="InvalidOperationException"/> if priority queue is empty + /// </remarks> + public TValue DequeueValue() + { + return Dequeue().Value; + } + + /// <summary> + /// Returns priority and value of the element with minimun priority, without removing it from the queue + /// </summary> + /// <returns>priority and value of the element with minimum priority</returns> + /// <remarks> + /// Method throws <see cref="InvalidOperationException"/> if priority queue is empty + /// </remarks> + public KeyValuePair<TPriority, TValue> Peek() + { + if (!IsEmpty) + return _baseHeap[0]; + else + throw new InvalidOperationException("Priority queue is empty"); + } + + /// <summary> + /// Returns value of the element with minimun priority, without removing it from the queue + /// </summary> + /// <returns>value of the element with minimum priority</returns> + /// <remarks> + /// Method throws <see cref="InvalidOperationException"/> if priority queue is empty + /// </remarks> + public TValue PeekValue() + { + return Peek().Value; + } + + /// <summary> + /// Gets whether priority queue is empty + /// </summary> + public bool IsEmpty + { + get { return _baseHeap.Count == 0; } + } + + #endregion + + #region Heap operations + + private void ExchangeElements(int pos1, int pos2) + { + KeyValuePair<TPriority, TValue> val = _baseHeap[pos1]; + _baseHeap[pos1] = _baseHeap[pos2]; + _baseHeap[pos2] = val; + } + + private void Insert(TPriority priority, TValue value) + { + KeyValuePair<TPriority, TValue> val = new KeyValuePair<TPriority, TValue>(priority, value); + _baseHeap.Add(val); + + // heap[i] have children heap[2*i + 1] and heap[2*i + 2] and parent heap[(i-1)/ 2]; + + // heapify after insert, from end to beginning + HeapifyFromEndToBeginning(_baseHeap.Count - 1); + } + + + private int HeapifyFromEndToBeginning(int pos) + { + if (pos >= _baseHeap.Count) return -1; + + while (pos > 0) + { + int parentPos = (pos - 1) / 2; + if (_comparer.Compare(_baseHeap[parentPos].Key, _baseHeap[pos].Key) > 0) + { + ExchangeElements(parentPos, pos); + pos = parentPos; + } + else break; + } + return pos; + } + + + private void DeleteRoot() + { + if (_baseHeap.Count <= 1) + { + _baseHeap.Clear(); + return; + } + + _baseHeap[0] = _baseHeap[_baseHeap.Count - 1]; + _baseHeap.RemoveAt(_baseHeap.Count - 1); + + // heapify + HeapifyFromBeginningToEnd(0); + } + + private void HeapifyFromBeginningToEnd(int pos) + { + if (pos >= _baseHeap.Count) return; + + // heap[i] have children heap[2*i + 1] and heap[2*i + 2] and parent heap[(i-1)/ 2]; + + while (true) + { + // on each iteration exchange element with its smallest child + int smallest = pos; + int left = 2 * pos + 1; + int right = 2 * pos + 2; + if (left < _baseHeap.Count && _comparer.Compare(_baseHeap[smallest].Key, _baseHeap[left].Key) > 0) + smallest = left; + if (right < _baseHeap.Count && _comparer.Compare(_baseHeap[smallest].Key, _baseHeap[right].Key) > 0) + smallest = right; + + if (smallest != pos) + { + ExchangeElements(smallest, pos); + pos = smallest; + } + else break; + } + } + + #endregion + + #region ICollection<KeyValuePair<TPriority, TValue>> implementation + + /// <summary> + /// Enqueus element into priority queue + /// </summary> + /// <param name="item">element to add</param> + public void Add(KeyValuePair<TPriority, TValue> item) + { + Enqueue(item.Key, item.Value); + } + + /// <summary> + /// Clears the collection + /// </summary> + public void Clear() + { + _baseHeap.Clear(); + } + + /// <summary> + /// Determines whether the priority queue contains a specific element + /// </summary> + /// <param name="item">The object to locate in the priority queue</param> + /// <returns><c>true</c> if item is found in the priority queue; otherwise, <c>false.</c> </returns> + public bool Contains(KeyValuePair<TPriority, TValue> item) + { + return _baseHeap.Contains(item); + } + + public bool TryFindValue(TPriority item,out TValue foundVersion){ + for (int i = 0; i < _baseHeap.Count; i++) { + if (_comparer.Compare(item,_baseHeap[i].Key) == 0){ + foundVersion = _baseHeap[i].Value; + return true; + } + } + foundVersion = default(TValue); + return false; + } + + /// <summary> + /// Gets number of elements in the priority queue + /// </summary> + public int Count + { + get { return _baseHeap.Count; } + } + + /// <summary> + /// Copies the elements of the priority queue to an Array, starting at a particular Array index. + /// </summary> + /// <param name="array">The one-dimensional Array that is the destination of the elements copied from the priority queue. The Array must have zero-based indexing. </param> + /// <param name="arrayIndex">The zero-based index in array at which copying begins.</param> + /// <remarks> + /// It is not guaranteed that items will be copied in the sorted order. + /// </remarks> + public void CopyTo(KeyValuePair<TPriority, TValue>[] array, int arrayIndex) + { + _baseHeap.CopyTo(array, arrayIndex); + } + + /// <summary> + /// Gets a value indicating whether the collection is read-only. + /// </summary> + /// <remarks> + /// For priority queue this property returns <c>false</c>. + /// </remarks> + public bool IsReadOnly + { + get { return false; } + } + + /// <summary> + /// Removes the first occurrence of a specific object from the priority queue. + /// </summary> + /// <param name="item">The object to remove from the ICollection <(Of <(T >)>). </param> + /// <returns><c>true</c> if item was successfully removed from the priority queue. + /// This method returns false if item is not found in the collection. </returns> + public bool Remove(KeyValuePair<TPriority, TValue> item) + { + // find element in the collection and remove it + int elementIdx = _baseHeap.IndexOf(item); + if (elementIdx < 0) return false; + + //remove element + _baseHeap[elementIdx] = _baseHeap[_baseHeap.Count - 1]; + _baseHeap.RemoveAt(_baseHeap.Count - 1); + + // heapify + int newPos = HeapifyFromEndToBeginning(elementIdx); + if (newPos == elementIdx) + HeapifyFromBeginningToEnd(elementIdx); + + return true; + } + + /// <summary> + /// Returns an enumerator that iterates through the collection. + /// </summary> + /// <returns>Enumerator</returns> + /// <remarks> + /// Returned enumerator does not iterate elements in sorted order.</remarks> + public IEnumerator<KeyValuePair<TPriority, TValue>> GetEnumerator() + { + return _baseHeap.GetEnumerator(); + } + + /// <summary> + /// Returns an enumerator that iterates through the collection. + /// </summary> + /// <returns>Enumerator</returns> + /// <remarks> + /// Returned enumerator does not iterate elements in sorted order.</remarks> + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + #endregion + } +} + diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_PriorityQueue.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_PriorityQueue.cs.meta new file mode 100644 index 00000000..4df18e2e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_PriorityQueue.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 878f9710d4ae4cb408f056108c0b4a73 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_UVTransform.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_UVTransform.cs new file mode 100644 index 00000000..01650add --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_UVTransform.cs @@ -0,0 +1,541 @@ +using UnityEngine; +using System.Collections; +using System; + +// A UV Transform is a transform considering only scale and offset it is used to represent the scaling and offset +// from UVs outside the 0,0..1,1 box and material tiling +// Rect objects are used to store the transform + +namespace DigitalOpus.MB.Core +{ + public struct DVector2 + { + static double epsilon = 10e-6; + + public double x; + public double y; + + public static DVector2 Subtract(DVector2 a, DVector2 b) + { + return new DVector2(a.x - b.x, a.y - b.y); + } + + public DVector2(double xx, double yy) + { + x = xx; + y = yy; + } + + public DVector2(DVector2 r) + { + x = r.x; + y = r.y; + } + + public Vector2 GetVector2() + { + return new Vector2((float)x, (float)y); + } + + public bool IsContainedIn(DRect r) + { + if (x >= r.x && y >= r.y && x <= r.x + r.width && y <= r.y + r.height) + { + return true; + } else + { + return false; + } + } + + public bool IsContainedInWithMargin(DRect r) + { + if (x >= r.x - epsilon && y >= r.y - epsilon && x <= r.x + r.width + epsilon && y <= r.y + r.height + epsilon) + { + return true; + } + else + { + return false; + } + } + + public override string ToString() + { + return string.Format("({0},{1})", x, y); + } + + public string ToString(string formatS) + { + return string.Format("({0},{1})", x.ToString(formatS), y.ToString(formatS)); + } + + public static double Distance(DVector2 a, DVector2 b) + { + double dx = b.x - a.x; + double dy = b.y - a.y; + return System.Math.Sqrt(dx * dx + dy * dy); + } + } + + public struct DRect + { + public double x; + public double y; + public double width; + public double height; + + public DRect(Rect r) + { + x = r.x; + y = r.y; + width = r.width; + height = r.height; + } + + public DRect(Vector2 o, Vector2 s) + { + x = o.x; + y = o.y; + width = s.x; + height = s.y; + } + + public DRect(DRect r) + { + x = r.x; + y = r.y; + width = r.width; + height = r.height; + } + + public DRect(float xx, float yy, float w, float h) + { + x = xx; + y = yy; + width = w; + height = h; + } + + public DRect(double xx, double yy, double w, double h) + { + x = xx; + y = yy; + width = w; + height = h; + } + + public Rect GetRect() + { + return new Rect((float)x, (float)y, (float)width, (float)height); + } + + public DVector2 minD + { + get + { + return new DVector2(x, y); + } + } + + public DVector2 maxD + { + get + { + return new DVector2((x + width), (y + height)); + } + } + + public Vector2 min + { + get + { + return new Vector2((float)x, (float)y); + } + } + + public Vector2 max + { + get + { + return new Vector2((float)(x + width), (float)(y + height)); + } + } + + public Vector2 size { + get { + return new Vector2((float)(width), (float)(height)); + } + } + + public DVector2 center + { + get + { + return new DVector2(x + width / 2.0, y + height / 2.0); + } + } + + public override bool Equals(object obj) + { + DRect dr = (DRect) obj; + if (dr.x == x && dr.y == y && dr.width == width && dr.height == height) + { + return true; + } + return false; + } + + public static bool operator ==(DRect a, DRect b) + { + return a.Equals(b); + } + + public static bool operator !=(DRect a, DRect b) + { + return !a.Equals(b); + } + + public override string ToString() + { + return String.Format("(x={0},y={1},w={2},h={3})", x.ToString("F5"), y.ToString("F5"), width.ToString("F5"), height.ToString("F5")); + } + + public void Expand(float amt) + { + x -= amt; + y -= amt; + width += amt * 2; + height += amt * 2; + } + + public bool Encloses(DRect smallToTestIfFits) + { + double smnx = smallToTestIfFits.x; + double smny = smallToTestIfFits.y; + double smxx = smallToTestIfFits.x + smallToTestIfFits.width; + double smxy = smallToTestIfFits.y + smallToTestIfFits.height; + //expand slightly to deal with rounding errors + double bmnx = this.x; + double bmny = this.y; + double bmxx = this.x + this.width; + double bmxy = this.y + this.height; + return bmnx <= smnx && smnx <= bmxx && + bmnx <= smxx && smxx <= bmxx && + bmny <= smny && smny <= bmxy && + bmny <= smxy && smxy <= bmxy; + } + + public override int GetHashCode () + { + return x.GetHashCode() ^ y.GetHashCode() ^ width.GetHashCode() ^ height.GetHashCode(); + } + } + + public class MB3_UVTransformUtility + { + public static void Test() + { + /* + Debug.Log("Running test"); + DRect rawUV = new DRect(.25, .25, 1.5, 1.5); + DRect rawMat = new DRect(.5, .5, 2, 2); + DRect rawCombined = CombineTransforms(ref rawUV, ref rawMat); + DRect matInvHierarchy = new DRect(rawMat.GetRect()); + InvertHierarchy(ref rawUV, ref matInvHierarchy); + DRect invertHierarchyCombined = CombineTransforms(ref matInvHierarchy, ref rawUV); + + //These transforms should be the same + Debug.Log("These should be same " + rawCombined + " " + invertHierarchyCombined); + + //New transform that should fit in combined + DRect otherUV = new DRect(1,1,1.5,1.5); + DRect otherMat = new DRect(0, 0, 1, 1); + DRect otherCombined = CombineTransforms(ref otherUV, ref otherMat); + Debug.Log("Other : " + otherCombined); + Debug.Log("Other fits = " + RectContains(ref rawCombined, ref otherCombined)); + + DRect invOtherCombined = InverseTransform(ref otherCombined); + Debug.Log(TransformPoint(ref otherCombined, new Vector2(0, 0)) + " " + TransformPoint(ref otherCombined, new Vector2(1, 1))); + Debug.Log(TransformPoint(ref invOtherCombined, new Vector2(0,0)) + " " + TransformPoint(ref invOtherCombined, new Vector2(1,1)).ToString("f5") + + " " + TransformPoint(ref invOtherCombined, new Vector2(2, 2)).ToString("f5") + + " " + TransformPoint(ref invOtherCombined, new Vector2(3, 3)).ToString("f5")); + + DRect src2combined = CombineTransforms(ref invOtherCombined, ref rawCombined); + + Debug.Log(TransformPoint(ref src2combined, new Vector2(0, 0)) + " " + TransformPoint(ref src2combined, new Vector2(1, 1)).ToString("f5") + + " " + TransformPoint(ref src2combined, new Vector2(2, 2)).ToString("f5") + + " " + TransformPoint(ref src2combined, new Vector2(3, 3)).ToString("f5")); + */ + //DRect rawUV = new DRect(0, 0, 1, 1); + DRect rawMat = new DRect(.5, .5, 2, 2); + DRect fullSample = new DRect(.25,.25,3,3); + //DRect altasRect = new DRect(0, 0, 1, 1); + + DRect invRawMat = InverseTransform(ref rawMat); + DRect invFullSample = InverseTransform(ref fullSample); + DRect relativeTransform = CombineTransforms(ref rawMat, ref invFullSample); + Debug.Log(invRawMat); + Debug.Log(relativeTransform); + Debug.Log("one mat trans " + TransformPoint(ref rawMat, new Vector2(1, 1))); + Debug.Log("one inv mat trans " + TransformPoint(ref invRawMat, new Vector2(1, 1)).ToString("f4")); + Debug.Log("zero " + TransformPoint(ref relativeTransform, new Vector2(0, 0)).ToString("f4")); + Debug.Log("one " + TransformPoint(ref relativeTransform, new Vector2(1, 1)).ToString("f4")); + } + + + public static float TransformX(DRect r, double x) + { + return (float) (r.width * x + r.x); + } + + public static DRect CombineTransforms(ref DRect r1, ref DRect r2) + { + DRect rCombined = new DRect(r1.x * r2.width + r2.x, + r1.y * r2.height + r2.y, + r1.width * r2.width, + r1.height * r2.height); + //rCombined.x = rCombined.x - Mathf.FloorToInt(rCombined.x); + //rCombined.y = rCombined.y - Mathf.FloorToInt(rCombined.y); + return rCombined; + } + + public static Rect CombineTransforms(ref Rect r1, ref Rect r2) + { + Rect rCombined = new Rect(r1.x * r2.width + r2.x, + r1.y * r2.height + r2.y, + r1.width * r2.width, + r1.height * r2.height); + //rCombined.x = rCombined.x - Mathf.FloorToInt(rCombined.x); + //rCombined.y = rCombined.y - Mathf.FloorToInt(rCombined.y); + return rCombined; + } + + //since the 0,0..1,1 box is tiled the offset should always be between 0 and 1 + /* + public static void Canonicalize(ref DRect r, double minX, double minY) + { + r.x = r.x - Mathf.FloorToInt((float) r.x); + if (r.x < minX) { r.x += Mathf.CeilToInt((float)minX); } + r.y = r.y - Mathf.FloorToInt((float) r.y); + if (r.y < minY) { r.y += Mathf.CeilToInt((float)minY); } + } + + public static void Canonicalize(ref Rect r, float minX, float minY) + { + r.x = r.x - Mathf.FloorToInt(r.x); + if (r.x < minX) { r.x += Mathf.CeilToInt(minX); } + r.y = r.y - Mathf.FloorToInt(r.y); + if (r.y < minY) { r.y += Mathf.CeilToInt(minY); } + } + */ + + public static DRect InverseTransform(ref DRect t) + { + DRect tinv = new DRect(); + tinv.x = -t.x / t.width; + tinv.y = -t.y / t.height; + tinv.width = 1f / t.width; + tinv.height = 1f / t.height; + return tinv; + } + + public static DRect GetShiftTransformToFitBinA(ref DRect A, ref DRect B) + { + DVector2 ac = A.center; + DVector2 bc = B.center; + DVector2 diff = DVector2.Subtract(ac, bc); + double dx = Convert.ToInt32(diff.x); + double dy = Convert.ToInt32(diff.y); + return new DRect(dx,dy,1.0,1.0); + } + + /// <summary> + /// shifts willBeIn so it is centered in uvRect1, then find a rect that encloses both + /// </summary> + /// <param name="uvRect1"></param> + /// <param name="willBeIn"></param> + /// <returns></returns> + public static DRect GetEncapsulatingRectShifted(ref DRect uvRect1, ref DRect willBeIn) + { + DVector2 bc = uvRect1.center; + DVector2 tfc = willBeIn.center; + DVector2 diff = DVector2.Subtract(bc, tfc); + double dx = Convert.ToInt32(diff.x); + double dy = Convert.ToInt32(diff.y); + DRect uvRect2 = new DRect(willBeIn); + uvRect2.x += dx; + uvRect2.y += dy; + double smnx = uvRect1.x; + double smny = uvRect1.y; + double smxx = uvRect1.x + uvRect1.width; + double smxy = uvRect1.y + uvRect1.height; + double bmnx = uvRect2.x; + double bmny = uvRect2.y; + double bmxx = uvRect2.x + uvRect2.width; + double bmxy = uvRect2.y + uvRect2.height; + double minx, miny, maxx, maxy; + minx = maxx = smnx; + miny = maxy = smny; + if (bmnx < minx) minx = bmnx; + if (smnx < minx) minx = smnx; + if (bmny < miny) miny = bmny; + if (smny < miny) miny = smny; + if (bmxx > maxx) maxx = bmxx; + if (smxx > maxx) maxx = smxx; + if (bmxy > maxy) maxy = bmxy; + if (smxy > maxy) maxy = smxy; + DRect uvRectCombined = new DRect(minx, miny, maxx - minx, maxy - miny); + return uvRectCombined; + } + + public static DRect GetEncapsulatingRect(ref DRect uvRect1, ref DRect uvRect2) + { + double smnx = uvRect1.x; + double smny = uvRect1.y; + double smxx = uvRect1.x + uvRect1.width; + double smxy = uvRect1.y + uvRect1.height; + double bmnx = uvRect2.x; + double bmny = uvRect2.y; + double bmxx = uvRect2.x + uvRect2.width; + double bmxy = uvRect2.y + uvRect2.height; + double minx, miny, maxx, maxy; + minx = maxx = smnx; + miny = maxy = smny; + if (bmnx < minx) minx = bmnx; + if (smnx < minx) minx = smnx; + if (bmny < miny) miny = bmny; + if (smny < miny) miny = smny; + if (bmxx > maxx) maxx = bmxx; + if (smxx > maxx) maxx = smxx; + if (bmxy > maxy) maxy = bmxy; + if (smxy > maxy) maxy = smxy; + DRect uvRectCombined = new DRect(minx, miny, maxx - minx, maxy - miny); + return uvRectCombined; + } + + /* + public static void InvertHierarchy(ref DRect uvRect, ref DRect matRect) + { + matRect.x = (uvRect.x * matRect.width + matRect.x - uvRect.x) / uvRect.width; + matRect.y = (uvRect.y * matRect.height + matRect.y - uvRect.y) / uvRect.height; + } + */ + + public static bool RectContainsShifted(ref DRect bucket, ref DRect tryFit) + { + //get the centers of bucket and tryFit + DVector2 bc = bucket.center; + DVector2 tfc = tryFit.center; + DVector2 diff = DVector2.Subtract(bc, tfc); + double dx = Convert.ToInt32(diff.x); + double dy = Convert.ToInt32(diff.y); + DRect tmp = new DRect(tryFit); + tmp.x += dx; + tmp.y += dy; + return bucket.Encloses(tmp); + } + + public static bool RectContainsShifted(ref Rect bucket, ref Rect tryFit) + { + //get the centers of bucket and tryFit + Vector2 bc = bucket.center; + Vector2 tfc = tryFit.center; + Vector2 diff = bc - tfc; + float dx = Convert.ToInt32(diff.x); + float dy = Convert.ToInt32(diff.y); + Rect tmp = new Rect(tryFit); + tmp.x += dx; + tmp.y += dy; + return RectContains(ref bucket, ref tmp); + } + + public static bool LineSegmentContainsShifted(float bucketOffset, float bucketLength, float tryFitOffset, float tryFitLength) + { + Debug.Assert(bucketLength >= 0); + Debug.Assert(tryFitLength >= 0); + float bc = bucketOffset + bucketLength / 2f; + float tfc = tryFitOffset + tryFitLength / 2f; + float diff = bc - tfc; + float delta = Convert.ToInt32(diff); + tryFitOffset += delta; + float sminx = tryFitOffset; + float smaxx = tryFitOffset + tryFitLength; + float bminx = bucketOffset - 10e-3f; + float bmaxx = bucketOffset + bucketLength + 10e-3f; + return bminx <= sminx && sminx <= bmaxx && + bminx <= smaxx && smaxx <= bmaxx; + } + + public static bool RectContains(ref DRect bigRect, ref DRect smallToTestIfFits) + { + double sminx = smallToTestIfFits.x; + double sminy = smallToTestIfFits.y; + double smaxx = smallToTestIfFits.x + smallToTestIfFits.width; + double smaxy = smallToTestIfFits.y + smallToTestIfFits.height; + //expand slightly to deal with rounding errors + double bminx = bigRect.x - 10e-3f; + double bminy = bigRect.y - 10e-3f; + double bmaxx = bigRect.x + bigRect.width + 10e-3f; + double bmaxy = bigRect.y + bigRect.height + 10e-3f; + //is smn in box + /* + string s = ""; + + s += (bmnx <= smnx); + s += (smnx <= bmxx); + s += (bmnx <= smxx); + s += String.Format("{0} {1} {2}",(smxx <= bmxx).ToString(), smxx.ToString("F5"), bmxx.ToString("F5")); + s += String.Format("{0} {1} {2}",(bmny <= smny), bmny.ToString("F5"), smny.ToString("F5")); + s += (smny <= bmxy); + s += (bmny <= smxy); + s += String.Format("{0} {1} {2}",(smxy <= bmxy).ToString(), smxy.ToString("F5"), bmxy.ToString("F5")); + Debug.Log("==== " + bigRect + " " + smallToTestIfFits + " " + s); + */ + return bminx <= sminx && sminx <= bmaxx && + bminx <= smaxx && smaxx <= bmaxx && + bminy <= sminy && sminy <= bmaxy && + bminy <= smaxy && smaxy <= bmaxy; + } + + public static bool RectContains(ref Rect bigRect, ref Rect smallToTestIfFits) + { + float smnx = smallToTestIfFits.x; + float smny = smallToTestIfFits.y; + float smxx = smallToTestIfFits.x + smallToTestIfFits.width; + float smxy = smallToTestIfFits.y + smallToTestIfFits.height; + //expand slightly to deal with rounding errors + float bmnx = bigRect.x - 10e-3f; + float bmny = bigRect.y - 10e-3f; + float bmxx = bigRect.x + bigRect.width + 10e-3f; + float bmxy = bigRect.y + bigRect.height + 10e-3f; + //is smn in box + /* + Debug.Log("==== " + bigRect + " " + smallToTestIfFits); + Debug.Log(bmnx <= smnx); + Debug.Log(smnx <= bmxx); + Debug.Log(bmnx <= smxx); + Debug.LogFormat("{0} {1} {2}", (smxx <= bmxx).ToString(), smxx.ToString("F5"), bmxx.ToString("F5")); + Debug.LogFormat("{0} {1} {2}", (bmny <= smny), bmny.ToString("F5"), smny.ToString("F5")); + Debug.Log(smny <= bmxy); + Debug.Log(bmny <= smxy); + Debug.LogFormat("{0} {1} {2}", (smxy <= bmxy).ToString(), smxy.ToString("F5"), bmxy.ToString("F5")); + Debug.Log("----------------"); + */ + return bmnx <= smnx && smnx <= bmxx && + bmnx <= smxx && smxx <= bmxx && + bmny <= smny && smny <= bmxy && + bmny <= smxy && smxy <= bmxy; + } + + public static Vector2 TransformPoint(ref DRect r, Vector2 p) + { + return new Vector2((float) (r.width * p.x + r.x),(float)(r.height * p.y + r.y)); + } + + public static DVector2 TransformPoint(ref DRect r, DVector2 p) + { + return new DVector2((r.width * p.x + r.x), (r.height * p.y + r.y)); + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_UVTransform.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_UVTransform.cs.meta new file mode 100644 index 00000000..c2f5c776 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB3_UVTransform.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 072fba73661e79c42a8b76a2fa362b26 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_BlendShape2CombinedMap.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_BlendShape2CombinedMap.cs new file mode 100644 index 00000000..eccc22d4 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_BlendShape2CombinedMap.cs @@ -0,0 +1,21 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace DigitalOpus.MB.Core +{ + public class MB_BlendShape2CombinedMap : MonoBehaviour + { + public SerializableSourceBlendShape2Combined srcToCombinedMap; + + public SerializableSourceBlendShape2Combined GetMap() + { + if (srcToCombinedMap == null) + { + srcToCombinedMap = new SerializableSourceBlendShape2Combined(); + } + + return srcToCombinedMap; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_BlendShape2CombinedMap.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_BlendShape2CombinedMap.cs.meta new file mode 100644 index 00000000..cad90953 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_BlendShape2CombinedMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f561367f2f5863f4f8b35838ecbf6120 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_DefaultMeshAssignCustomizer.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_DefaultMeshAssignCustomizer.cs new file mode 100644 index 00000000..a9fe1fe5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_DefaultMeshAssignCustomizer.cs @@ -0,0 +1,68 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core +{ + public class MB_DefaultMeshAssignCustomizer : ScriptableObject, IAssignToMeshCustomizer + { + public virtual void meshAssign_UV0(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { + mesh.uv = uvs; + } + + public virtual void meshAssign_UV2(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { + mesh.uv2 = uvs; + } + + public virtual void meshAssign_UV3(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { + mesh.uv3 = uvs; + } + + public virtual void meshAssign_UV4(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { + mesh.uv4 = uvs; + } + + + public virtual void meshAssign_UV5(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { +#if UNITY_2018_2_OR_NEWER + mesh.uv5 = uvs; +#endif + } + + public virtual void meshAssign_UV6(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { +#if UNITY_2018_2_OR_NEWER + mesh.uv6 = uvs; +#endif + } + + public virtual void meshAssign_UV7(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { +#if UNITY_2018_2_OR_NEWER + mesh.uv7 = uvs; +#endif + } + + public virtual void meshAssign_UV8(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes) + { +#if UNITY_2018_2_OR_NEWER + mesh.uv8 = uvs; +#endif + } + + public virtual void meshAssign_colors(MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Color[] colors, float[] sliceIndexes) + { + mesh.colors = colors; + } + + public static void DefaultDelegateAssignMeshColors(MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, + Mesh mesh, Color[] colors, float[] sliceIndexes) + { + mesh.colors = colors; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_DefaultMeshAssignCustomizer.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_DefaultMeshAssignCustomizer.cs.meta new file mode 100644 index 00000000..ac74aaaa --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_DefaultMeshAssignCustomizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 436c228f8ec135843af5390641358ff3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IAssignToMeshCustomizer.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IAssignToMeshCustomizer.cs new file mode 100644 index 00000000..7b354129 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IAssignToMeshCustomizer.cs @@ -0,0 +1,62 @@ +using UnityEngine; +using System.Collections.Generic; + +namespace DigitalOpus.MB.Core +{ + public interface IAssignToMeshCustomizer + { + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV0(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV2(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV3(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV4(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV5(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV6(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV7(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_UV8(int channel, MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Vector2[] uvs, float[] sliceIndexes); + + /// <summary> + /// For customizing data just before it is assigned to a mesh. If using Texture Arrays + /// this can be used to inject the slice index into a coordinate in the mesh. + /// </summary> + void meshAssign_colors(MB_IMeshBakerSettings settings, MB2_TextureBakeResults textureBakeResults, Mesh mesh, Color[] colors, float[] sliceIndexes); + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IAssignToMeshCustomizer.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IAssignToMeshCustomizer.cs.meta new file mode 100644 index 00000000..abfd11aa --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IAssignToMeshCustomizer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a3c363d98fc0b8a4dacbfb882890a560 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IMeshBakerSettings.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IMeshBakerSettings.cs new file mode 100644 index 00000000..074e5309 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IMeshBakerSettings.cs @@ -0,0 +1,44 @@ +using UnityEngine; + +namespace DigitalOpus.MB.Core +{ + public interface MB_IMeshBakerSettingsHolder + { + MB_IMeshBakerSettings GetMeshBakerSettings(); + + /// <summary> + /// A settings holder could store settings in one of several places. We can't return a + /// SerializedProperty because that would be editor only. Instead we return the parameters + /// needed to construct a serialized property. + /// </summary> + void GetMeshBakerSettingsAsSerializedProperty(out string propertyName, out UnityEngine.Object targetObj); + } + + public interface MB_IMeshBakerSettings + { + bool doBlendShapes { get; set; } + bool doCol { get; set; } + bool doNorm { get; set; } + bool doTan { get; set; } + bool doUV { get; set; } + bool doUV3 { get; set; } + bool doUV4 { get; set; } + bool doUV5 { get; set; } + bool doUV6 { get; set; } + bool doUV7 { get; set; } + bool doUV8 { get; set; } + MB2_LightmapOptions lightmapOption { get; set; } + float uv2UnwrappingParamsHardAngle { get; set; } + float uv2UnwrappingParamsPackMargin { get; set; } + bool optimizeAfterBake { get; set; } + MB_MeshPivotLocation pivotLocationType { get; set; } + Vector3 pivotLocation { get; set; } + bool clearBuffersAfterBake { get; set; } + MB_RenderType renderType { get; set; } + bool smrNoExtraBonesWhenCombiningMeshRenderers { get; set; } + + bool smrMergeBlendShapesWithSameNames { get; set; } + + IAssignToMeshCustomizer assignToMeshCustomizer { get; set; } + } +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IMeshBakerSettings.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IMeshBakerSettings.cs.meta new file mode 100644 index 00000000..42fc2736 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_IMeshBakerSettings.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: bb7e18c535284a84f877576ebe43decd +timeCreated: 1555964253 +licenseType: Store +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_SerializableSourceBlendShape2CombinedMap.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_SerializableSourceBlendShape2CombinedMap.cs new file mode 100644 index 00000000..59a361ca --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_SerializableSourceBlendShape2CombinedMap.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using DigitalOpus.MB.Core; + +namespace DigitalOpus.MB.Core +{ + /// <summary> + /// It is possible to combine multiple skinned meshes each of which may have blend shapes. + /// This builds a map for mapping source blend shapes to combined blend shapes. + /// The map can be serialized and saved in a prefab, this makes it possible to save combined + /// meshes with Blend Shapes in a prefab. + /// </summary> + [System.Serializable] + public class SerializableSourceBlendShape2Combined + { + public GameObject[] srcGameObject; + + public int[] srcBlendShapeIdx; + + public GameObject[] combinedMeshTargetGameObject; + + public int[] blendShapeIdx; + + public void SetBuffers(GameObject[] srcGameObjs, int[] srcBlendShapeIdxs, + GameObject[] targGameObjs, int[] targBlendShapeIdx) + { + Debug.Assert(srcGameObjs.Length == srcBlendShapeIdxs.Length && + srcGameObjs.Length == targGameObjs.Length && + srcGameObjs.Length == targBlendShapeIdx.Length); + srcGameObject = srcGameObjs; + srcBlendShapeIdx = srcBlendShapeIdxs; + combinedMeshTargetGameObject = targGameObjs; + blendShapeIdx = targBlendShapeIdx; + } + + public void DebugPrint() + { + if (srcGameObject == null) + { + Debug.LogError("Empty"); + return; + } + else + { + for (int i = 0; i < srcGameObject.Length; i++) + { + Debug.LogFormat("{0} {1} {2} {3}", srcGameObject[i], srcBlendShapeIdx[i], combinedMeshTargetGameObject[i], blendShapeIdx[i]); + } + } + + } + + public Dictionary<MB3_MeshCombiner.MBBlendShapeKey, MB3_MeshCombiner.MBBlendShapeValue> GenerateMapFromSerializedData() + { + if (srcGameObject == null || srcBlendShapeIdx == null || combinedMeshTargetGameObject == null || blendShapeIdx == null || + srcGameObject.Length != srcBlendShapeIdx.Length || + srcGameObject.Length != combinedMeshTargetGameObject.Length || + srcGameObject.Length != blendShapeIdx.Length) + { + Debug.LogError("Error GenerateMapFromSerializedData. Serialized data was malformed or missing."); + return null; + } + + Dictionary<MB3_MeshCombiner.MBBlendShapeKey, MB3_MeshCombiner.MBBlendShapeValue> map = new Dictionary<MB3_MeshCombiner.MBBlendShapeKey, MB3_MeshCombiner.MBBlendShapeValue>(); + for (int i = 0; i < srcGameObject.Length; i++) + { + GameObject src = srcGameObject[i]; + GameObject targ = combinedMeshTargetGameObject[i]; + if (src == null || targ == null) + { + Debug.LogError("Error GenerateMapFromSerializedData. There were null references in the serialized data to source or target game objects. This can happen " + + "if the SerializableSourceBlendShape2Combined was serialized in a prefab but the source and target SkinnedMeshRenderer GameObjects " + + " were not."); + return null; + } + + map.Add(new MB3_MeshCombiner.MBBlendShapeKey(src, srcBlendShapeIdx[i]), + new MB3_MeshCombiner.MBBlendShapeValue() + { + combinedMeshGameObject = targ, + blendShapeIndex = blendShapeIdx[i] + } + ); + } + + return map; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_SerializableSourceBlendShape2CombinedMap.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_SerializableSourceBlendShape2CombinedMap.cs.meta new file mode 100644 index 00000000..e73dd859 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_SerializableSourceBlendShape2CombinedMap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 347e5b40f4deb874bbd091afcadd872c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_TGAWriter.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_TGAWriter.cs new file mode 100644 index 00000000..0e1ebf53 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_TGAWriter.cs @@ -0,0 +1,66 @@ +using UnityEngine; +using System.Collections; +using System; +using System.IO; + +namespace DigitalOpus.MB.Core +{ +public static class MB_TGAWriter +{ + public static void Write(Color[] pixels, int width, int height, string path) + { + // Delete the file if it exists. + if (File.Exists(path)) + { + File.Delete(path); + } + + //Create the file. + FileStream fs = File.Create(path); + Write(pixels, width, height, fs); + } + + + public static void Write(Color[] pixels, int width, int height, Stream output) + { + byte[] pixelsArr = new byte[pixels.Length * 4]; + + int offsetSource = 0; + int offsetDest = 0; + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + Color value = pixels[offsetSource]; + pixelsArr[offsetDest] = (byte)(value.b * 255); // b + pixelsArr[offsetDest + 1] = (byte)(value.g * 255); // g + pixelsArr[offsetDest + 2] = (byte)(value.r * 255); // r + pixelsArr[offsetDest + 3] = (byte)(value.a * 255); // a + + offsetSource++; + offsetDest += 4; + } + } + + byte[] header = new byte[] { + 0, // ID length + 0, // no color map + 2, // uncompressed, true color + 0, 0, 0, 0, + 0, + 0, 0, 0, 0, // x and y origin + (byte)(width & 0x00FF), + (byte)((width & 0xFF00) >> 8), + (byte)(height & 0x00FF), + (byte)((height & 0xFF00) >> 8), + 32, // 32 bit bitmap + 0 }; + + using (BinaryWriter writer = new BinaryWriter(output)) + { + writer.Write(header); + writer.Write(pixelsArr); + } + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_TGAWriter.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_TGAWriter.cs.meta new file mode 100644 index 00000000..fcc008a7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_TGAWriter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8bc8132d76300ee44a64c5a4dde3d57e +timeCreated: 1505496766 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_Utility.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_Utility.cs new file mode 100644 index 00000000..1f2e4b9b --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_Utility.cs @@ -0,0 +1,424 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System; + +namespace DigitalOpus.MB.Core{ +public class MB_Utility{ + + public static bool DO_INTEGRITY_CHECKS = false; + + public struct MeshAnalysisResult{ + public Rect uvRect; + public bool hasOutOfBoundsUVs; + public bool hasOverlappingSubmeshVerts; + public bool hasOverlappingSubmeshTris; + public bool hasUVs; + public float submeshArea; + } + + public static Texture2D createTextureCopy(Texture2D source){ + Texture2D newTex = new Texture2D(source.width,source.height,TextureFormat.ARGB32,true); + newTex.SetPixels(source.GetPixels()); + return newTex; + } + + public static bool ArrayBIsSubsetOfA(System.Object[] a, System.Object[] b){ + for (int i = 0; i < b.Length; i++){ + bool foundBinA = false; + for (int j = 0; j < a.Length; j++){ + if (a[j] == b[i]){ + foundBinA = true; + break; + } + } + if (foundBinA == false) return false; + } + return true; + } + + public static Material[] GetGOMaterials(GameObject go){ + if (go == null) return new Material[0]; + Material[] sharedMaterials = null; + Mesh mesh = null; + MeshRenderer mr = go.GetComponent<MeshRenderer>(); + if (mr != null){ + sharedMaterials = mr.sharedMaterials; + MeshFilter mf = go.GetComponent<MeshFilter>(); + if (mf == null){ + throw new Exception("Object " + go + " has a MeshRenderer but no MeshFilter."); + } + mesh = mf.sharedMesh; + } + + SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>(); + if (smr != null){ + sharedMaterials = smr.sharedMaterials; + mesh = smr.sharedMesh; + } + + if (sharedMaterials == null){ + Debug.LogError("Object " + go.name + " does not have a MeshRenderer or a SkinnedMeshRenderer component"); + return new Material[0]; + } else if (mesh == null){ + Debug.LogError("Object " + go.name + " has a MeshRenderer or SkinnedMeshRenderer but no mesh."); + return new Material[0]; + } else { + if (mesh.subMeshCount < sharedMaterials.Length){ + Debug.LogWarning("Object " + go + " has only " + mesh.subMeshCount + " submeshes and has " + sharedMaterials.Length + " materials. Extra materials do nothing."); + Material[] newSharedMaterials = new Material[mesh.subMeshCount]; + Array.Copy(sharedMaterials,newSharedMaterials,newSharedMaterials.Length); + sharedMaterials = newSharedMaterials; + } + return sharedMaterials; + } + } + + public static Mesh GetMesh(GameObject go){ + if (go == null) return null; + MeshFilter mf = go.GetComponent<MeshFilter>(); + if (mf != null){ + return mf.sharedMesh; + } + + SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>(); + if (smr != null){ + return smr.sharedMesh; + } + + return null; + } + + public static void SetMesh(GameObject go, Mesh m) + { + if (go == null) return; + MeshFilter mf = go.GetComponent<MeshFilter>(); + if (mf != null) + { + mf.sharedMesh = m; + } + else + { + SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>(); + if (smr != null) + { + smr.sharedMesh = m; + } + } + } + + public static Renderer GetRenderer(GameObject go){ + if (go == null) return null; + MeshRenderer mr = go.GetComponent<MeshRenderer>(); + if (mr != null) return mr; + + + SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>(); + if (smr != null) return smr; + return null; + } + + public static void DisableRendererInSource(GameObject go){ + if (go == null) return; + MeshRenderer mf = go.GetComponent<MeshRenderer>(); + if (mf != null){ + mf.enabled = false; + return; + } + + SkinnedMeshRenderer smr = go.GetComponent<SkinnedMeshRenderer>(); + if (smr != null){ + smr.enabled = false; + return; + } + } + + public static bool hasOutOfBoundsUVs(Mesh m, ref Rect uvBounds){ + MeshAnalysisResult mar = new MeshAnalysisResult(); + bool outVal = hasOutOfBoundsUVs(m, ref mar); + uvBounds = mar.uvRect; + return outVal; + } + + public static bool hasOutOfBoundsUVs(Mesh m, ref MeshAnalysisResult putResultHere, int submeshIndex = -1, int uvChannel = 0) + { + if (m == null) + { + putResultHere.hasOutOfBoundsUVs = false; + return putResultHere.hasOutOfBoundsUVs; + } + Vector2[] uvs; + if (uvChannel == 0) { + uvs = m.uv; + } else if (uvChannel == 1) + { + uvs = m.uv2; + } else if (uvChannel == 2) + { + uvs = m.uv3; + } else { + + uvs = m.uv4; + } + return hasOutOfBoundsUVs(uvs, m, ref putResultHere, submeshIndex); + } + + public static bool hasOutOfBoundsUVs(Vector2[] uvs, Mesh m, ref MeshAnalysisResult putResultHere, int submeshIndex = -1) + { + putResultHere.hasUVs = true; + if (uvs.Length == 0) + { + putResultHere.hasUVs = false; + putResultHere.hasOutOfBoundsUVs = false; + putResultHere.uvRect = new Rect(); + return putResultHere.hasOutOfBoundsUVs; + } + float minx, miny, maxx, maxy; + if (submeshIndex >= m.subMeshCount) + { + putResultHere.hasOutOfBoundsUVs = false; + putResultHere.uvRect = new Rect(); + return putResultHere.hasOutOfBoundsUVs; + } + else if (submeshIndex >= 0) + { + //checking specific submesh + int[] tris = m.GetTriangles(submeshIndex); + if (tris.Length == 0) + { + putResultHere.hasOutOfBoundsUVs = false; + putResultHere.uvRect = new Rect(); + return putResultHere.hasOutOfBoundsUVs; + } + minx = maxx = uvs[tris[0]].x; + miny = maxy = uvs[tris[0]].y; + for (int idx = 0; idx < tris.Length; idx++) + { + int i = tris[idx]; + if (uvs[i].x < minx) minx = uvs[i].x; + if (uvs[i].x > maxx) maxx = uvs[i].x; + if (uvs[i].y < miny) miny = uvs[i].y; + if (uvs[i].y > maxy) maxy = uvs[i].y; + } + } + else { + //checking all UVs + minx = maxx = uvs[0].x; + miny = maxy = uvs[0].y; + for (int i = 0; i < uvs.Length; i++) + { + if (uvs[i].x < minx) minx = uvs[i].x; + if (uvs[i].x > maxx) maxx = uvs[i].x; + if (uvs[i].y < miny) miny = uvs[i].y; + if (uvs[i].y > maxy) maxy = uvs[i].y; + } + } + Rect uvBounds = new Rect(); + uvBounds.x = minx; + uvBounds.y = miny; + uvBounds.width = maxx - minx; + uvBounds.height = maxy - miny; + if (maxx > 1f || minx < 0f || maxy > 1f || miny < 0f) + { + putResultHere.hasOutOfBoundsUVs = true; + } + else + { + putResultHere.hasOutOfBoundsUVs = false; + } + putResultHere.uvRect = uvBounds; + return putResultHere.hasOutOfBoundsUVs; + } + + public static void setSolidColor(Texture2D t, Color c) + { + Color[] cs = t.GetPixels(); + for (int i = 0; i < cs.Length; i++) + { + cs[i] = c; + } + t.SetPixels(cs); + t.Apply(); + } + + public static Texture2D resampleTexture(Texture2D source, int newWidth, int newHeight){ + TextureFormat f = source.format; + if (f == TextureFormat.ARGB32 || + f == TextureFormat.RGBA32 || + f == TextureFormat.BGRA32 || + f == TextureFormat.RGB24 || + f == TextureFormat.Alpha8 || + f == TextureFormat.DXT1) + { + Texture2D newTex = new Texture2D(newWidth,newHeight,TextureFormat.ARGB32,true); + float w = newWidth; + float h = newHeight; + for (int i = 0; i < newWidth; i++){ + for (int j = 0; j < newHeight; j++){ + float u = i/w; + float v = j/h; + newTex.SetPixel(i,j,source.GetPixelBilinear(u,v)); + } + } + newTex.Apply(); + return newTex; + } else { + Debug.LogError("Can only resize textures in formats ARGB32, RGBA32, BGRA32, RGB24, Alpha8 or DXT. texture:" + source + " was in format: " + source.format); + return null; + } + } + + class MB_Triangle{ + int submeshIdx; + int[] vs = new int[3]; + + public bool isSame(object obj){ + MB_Triangle tobj = (MB_Triangle) obj; + if (vs[0] == tobj.vs[0] && + vs[1] == tobj.vs[1] && + vs[2] == tobj.vs[2] && + submeshIdx != tobj.submeshIdx){ + return true; + } + return false; + } + + public bool sharesVerts(MB_Triangle obj){ + if (vs[0] == obj.vs[0] || + vs[0] == obj.vs[1] || + vs[0] == obj.vs[2]){ + if (submeshIdx != obj.submeshIdx) return true; + } + if (vs[1] == obj.vs[0] || + vs[1] == obj.vs[1] || + vs[1] == obj.vs[2]){ + if (submeshIdx != obj.submeshIdx) return true; + } + if (vs[2] == obj.vs[0] || + vs[2] == obj.vs[1] || + vs[2] == obj.vs[2]){ + if (submeshIdx != obj.submeshIdx) return true; + } + return false; + } + + public void Initialize(int[] ts, int idx, int sIdx){ + vs[0] = ts[idx]; + vs[1] = ts[idx + 1]; + vs[2] = ts[idx + 2]; + submeshIdx = sIdx; + Array.Sort(vs); + } + } + + public static bool AreAllSharedMaterialsDistinct(Material[] sharedMaterials){ + for (int i = 0; i < sharedMaterials.Length; i++){ + for (int j = i + 1; j < sharedMaterials.Length; j++){ + if (sharedMaterials[i] == sharedMaterials[j]){ + return false; + } + } + } + return true; + } + + public static int doSubmeshesShareVertsOrTris(Mesh m, ref MeshAnalysisResult mar){ + MB_Triangle consider = new MB_Triangle(); + MB_Triangle other = new MB_Triangle(); + //cache all triangles + int[][] tris = new int[m.subMeshCount][]; + for (int i = 0; i < m.subMeshCount; i++){ + tris[i] = m.GetTriangles(i); + } + bool sharesVerts = false; + bool sharesTris = false; + for (int i = 0; i < m.subMeshCount; i++){ + int[] smA = tris[i]; + for (int j = i+1; j < m.subMeshCount; j++){ + int[] smB = tris[j]; + for (int k = 0; k < smA.Length; k+=3){ + consider.Initialize(smA,k,i); + for (int l = 0; l < smB.Length; l+=3){ + other.Initialize(smB,l,j); + if (consider.isSame(other)){ + sharesTris = true; + break; + } + if (consider.sharesVerts(other)){ + sharesVerts = true; + break; + } + } + } + } + } + if (sharesTris){ + mar.hasOverlappingSubmeshVerts = true; + mar.hasOverlappingSubmeshTris = true; + return 2; + } else if (sharesVerts){ + mar.hasOverlappingSubmeshVerts = true; + mar.hasOverlappingSubmeshTris = false; + return 1; + } else { + mar.hasOverlappingSubmeshTris = false; + mar.hasOverlappingSubmeshVerts = false; + return 0; + } + } + + public static bool GetBounds(GameObject go, out Bounds b){ + if (go == null){ + Debug.LogError("go paramater was null"); + b = new Bounds(Vector3.zero,Vector3.zero); + return false; + } + Renderer r = GetRenderer(go); + if (r == null){ + Debug.LogError("GetBounds must be called on an object with a Renderer"); + b = new Bounds(Vector3.zero,Vector3.zero); + return false; + } + if (r is MeshRenderer){ + b = r.bounds; + return true; + } else if (r is SkinnedMeshRenderer){ + b = r.bounds; + return true; + } + Debug.LogError("GetBounds must be called on an object with a MeshRender or a SkinnedMeshRenderer."); + b = new Bounds(Vector3.zero,Vector3.zero); + return false; + } + + public static void Destroy(UnityEngine.Object o){ + if (Application.isPlaying){ + MonoBehaviour.Destroy(o); + } else { +// string p = AssetDatabase.GetAssetPath(o); +// if (p != null && p.Equals("")) // don't try to destroy assets + MonoBehaviour.DestroyImmediate(o,false); + } + } + + public static string ConvertAssetsRelativePathToFullSystemPath(string pth) + { + string aPth = Application.dataPath.Replace("Assets", ""); + return aPth + pth; + } + + public static bool IsSceneInstance(GameObject go) + { + // go.scene.name + // - is the name of the scene if in a scene + // - is the name of the prefab if in prefab edit scene + // - is null if is a prefab assigned from the project folder + return go.scene.name != null; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_Utility.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_Utility.cs.meta new file mode 100644 index 00000000..05b661a7 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/MB_Utility.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0e6975d20fbb7db4cb282debae7dc110 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner.meta new file mode 100644 index 00000000..e5b0f521 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 454fc992558d0c849a7eacc2e764fa32 +folderAsset: yes +timeCreated: 1522041808 +licenseType: Store +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePacker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePacker.cs new file mode 100644 index 00000000..aca811db --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePacker.cs @@ -0,0 +1,952 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; +using System.IO; + +namespace DigitalOpus.MB.Core{ + // uses this algorithm http://blackpawn.com/texts/lightmaps/ + + [System.Serializable] + public struct AtlasPadding + { + public int topBottom; + public int leftRight; + + public AtlasPadding(int p) + { + topBottom = p; + leftRight = p; + } + + public AtlasPadding(int px, int py) + { + topBottom = py; + leftRight = px; + } + } + + [System.Serializable] + public class AtlasPackingResult + { + public int atlasX; + public int atlasY; + public int usedW; + public int usedH; + public Rect[] rects; + public AtlasPadding[] padding; + public int[] srcImgIdxs; + public object data; + + public AtlasPackingResult(AtlasPadding[] pds) + { + padding = pds; + } + + public void CalcUsedWidthAndHeight() + { + Debug.Assert(rects != null); + float maxW = 0; + float maxH = 0; + float paddingX = 0; + float paddingY = 0; + for (int i = 0; i < rects.Length; i++) + { + paddingX += padding[i].leftRight * 2f; + paddingY += padding[i].topBottom * 2f; + maxW = Mathf.Max(maxW, rects[i].x + rects[i].width); + maxH = Mathf.Max(maxH, rects[i].y + rects[i].height); + } + usedW = Mathf.CeilToInt(maxW * atlasX + paddingX); + usedH = Mathf.CeilToInt(maxH * atlasY + paddingY); + if (usedW > atlasX) usedW = atlasX; + if (usedH > atlasY) usedH = atlasY; + } + + public override string ToString() + { + return string.Format("numRects: {0}, atlasX: {1} atlasY: {2} usedW: {3} usedH: {4}", rects.Length, atlasX, atlasY, usedW, usedH); + } + } + + public abstract class MB2_TexturePacker + { + + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + + internal const int MAX_RECURSION_DEPTH = 10; + + internal enum NodeType + { + Container, + maxDim, + regular + } + + internal class PixRect + { + public int x; + public int y; + public int w; + public int h; + + public PixRect() { } + public PixRect(int xx, int yy, int ww, int hh) + { + x = xx; + y = yy; + w = ww; + h = hh; + } + + public override string ToString() + { + return String.Format("x={0},y={1},w={2},h={3}", x, y, w, h); + } + } + + internal class Image + { + public int imgId; + public int w; + public int h; + public int x; + public int y; + + public Image(int id, int tw, int th, AtlasPadding padding, int minImageSizeX, int minImageSizeY) + { + imgId = id; + w = Mathf.Max(tw + padding.leftRight * 2, minImageSizeX); + h = Mathf.Max(th + padding.topBottom * 2, minImageSizeY); + } + + public Image(Image im) + { + imgId = im.imgId; + w = im.w; + h = im.h; + x = im.x; + y = im.y; + } + } + + internal class ImgIDComparer : IComparer<Image> + { + public int Compare(Image x, Image y) + { + if (x.imgId > y.imgId) + return 1; + if (x.imgId == y.imgId) + return 0; + return -1; + } + } + + internal class ImageHeightComparer : IComparer<Image> + { + public int Compare(Image x, Image y) + { + if (x.h > y.h) + return -1; + if (x.h == y.h) + return 0; + return 1; + } + } + + internal class ImageWidthComparer : IComparer<Image> + { + public int Compare(Image x, Image y) + { + if (x.w > y.w) + return -1; + if (x.w == y.w) + return 0; + return 1; + } + } + + internal class ImageAreaComparer : IComparer<Image> + { + public int Compare(Image x, Image y) + { + int ax = x.w * x.h; + int ay = y.w * y.h; + if (ax > ay) + return -1; + if (ax == ay) + return 0; + return 1; + } + } + + public bool atlasMustBePowerOfTwo = true; + + public static int RoundToNearestPositivePowerOfTwo(int x) + { + int p = (int)Mathf.Pow(2, Mathf.RoundToInt(Mathf.Log(x) / Mathf.Log(2))); + if (p == 0 || p == 1) p = 2; + return p; + } + + public static int CeilToNearestPowerOfTwo(int x) + { + int p = (int)Mathf.Pow(2, Mathf.Ceil(Mathf.Log(x) / Mathf.Log(2))); + if (p == 0 || p == 1) p = 2; + return p; + } + + public abstract AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, int maxDimensionX, int maxDimensionY, int padding); + + public abstract AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, bool doMultiAtlas); + + /* + Packed rects may exceed atlas size and require scaling + When scaling want pixel perfect fit in atlas. Corners of rects should exactly align with pixel grid + Padding should be subtracted from pixel perfect rect to create pixel perfect square + TODO this doesn't handle each rectangle having different padding + */ + internal bool ScaleAtlasToFitMaxDim(Vector2 rootWH, List<Image> images, int maxDimensionX, int maxDimensionY, AtlasPadding padding, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, + ref int outW, ref int outH, out float padX, out float padY, out int newMinSizeX, out int newMinSizeY) + { + newMinSizeX = minImageSizeX; + newMinSizeY = minImageSizeY; + bool redoPacking = false; + + // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit + padX = (float)padding.leftRight / (float)outW; //padding needs to be pixel perfect in size + if (rootWH.x > maxDimensionX) + { + padX = (float)padding.leftRight / (float)maxDimensionX; + float scaleFactor = (float)maxDimensionX / (float)rootWH.x; + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Packing exceeded atlas width shrinking to " + scaleFactor); + for (int i = 0; i < images.Count; i++) + { + Image im = images[i]; + if (im.w * scaleFactor < masterImageSizeX) + { //check if small images will be rounded too small. If so need to redo packing forcing a larger min size + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeX."); + redoPacking = true; + newMinSizeX = Mathf.CeilToInt(minImageSizeX / scaleFactor); + } + int right = (int)((im.x + im.w) * scaleFactor); + im.x = (int)(scaleFactor * im.x); + im.w = right - im.x; + } + outW = maxDimensionX; + } + + padY = (float)padding.topBottom / (float)outH; + if (rootWH.y > maxDimensionY) + { + //float minSizeY = ((float)minImageSizeY + 1) / maxDimension; + padY = (float)padding.topBottom / (float)maxDimensionY; + float scaleFactor = (float)maxDimensionY / (float)rootWH.y; + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Packing exceeded atlas height shrinking to " + scaleFactor); + for (int i = 0; i < images.Count; i++) + { + Image im = images[i]; + if (im.h * scaleFactor < masterImageSizeY) + { //check if small images will be rounded too small. If so need to redo packing forcing a larger min size + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Small images are being scaled to zero. Will need to redo packing with larger minTexSizeY."); + redoPacking = true; + newMinSizeY = Mathf.CeilToInt(minImageSizeY / scaleFactor); + } + int bottom = (int)((im.y + im.h) * scaleFactor); + im.y = (int)(scaleFactor * im.y); + im.h = bottom - im.y; + } + outH = maxDimensionY; + } + return redoPacking; + } + + //normalize atlases so that that rects are 0 to 1 + public void ConvertToRectsWithoutPaddingAndNormalize01(AtlasPackingResult rr, AtlasPadding padding) + { + for (int i = 0; i < rr.rects.Length; i++) + { + rr.rects[i].x = (rr.rects[i].x + padding.leftRight) / rr.atlasX; + rr.rects[i].y = (rr.rects[i].y + padding.topBottom) / rr.atlasY; + rr.rects[i].width = (rr.rects[i].width - padding.leftRight * 2) / rr.atlasX; + rr.rects[i].height = (rr.rects[i].height - padding.topBottom * 2) / rr.atlasY; + } + } + } + + public class MB2_TexturePackerRegular : MB2_TexturePacker { + class ProbeResult{ + public int w; + public int h; + public int outW; + public int outH; + public Node root; + public bool largerOrEqualToMaxDim; + public float efficiency; + public float squareness; + + //these are for the multiAtlasPacker + public float totalAtlasArea; + public int numAtlases; + + public void Set(int ww, int hh, int outw, int outh, Node r, bool fits, float e, float sq){ + w = ww; + h = hh; + outW = outw; + outH = outh; + root = r; + largerOrEqualToMaxDim = fits; + efficiency = e; + squareness = sq; + } + + public float GetScore(bool doPowerOfTwoScore){ + float fitsScore = largerOrEqualToMaxDim ? 1f : 0f; + if (doPowerOfTwoScore){ + return fitsScore * 2f + efficiency; + } else { + return squareness + 2 * efficiency + fitsScore; + } + } + + public void PrintTree() + { + printTree(root, " "); + } + } + + internal class Node { + internal NodeType isFullAtlas; //is this node a full atlas used for scaling to fit + internal Node[] child = new Node[2]; + internal PixRect r; + internal Image img; + ProbeResult bestRoot; + internal Node(NodeType rootType) + { + isFullAtlas = rootType; + } + + private bool isLeaf(){ + if (child[0] == null || child[1] == null){ + return true; + } + return false; + } + + internal Node Insert(Image im, bool handed){ + int a,b; + if (handed){ + a = 0; + b = 1; + } else { + a = 1; + b = 0; + } + if (!isLeaf()){ + //try insert into first child + Node newNode = child[a].Insert(im,handed); + if (newNode != null) + return newNode; + //no room insert into second + return child[b].Insert(im,handed); + } else { + //(if there's already a img here, return) + if (img != null) + return null; + + //(if space too small, return) + if (r.w < im.w || r.h < im.h) + return null; + + //(if space just right, accept) + if (r.w == im.w && r.h == im.h){ + img = im; + return this; + } + + //(otherwise, gotta split this node and create some kids) + child[a] = new Node(NodeType.regular); + child[b] = new Node(NodeType.regular); + + //(decide which way to split) + int dw = r.w - im.w; + int dh = r.h - im.h; + + if (dw > dh){ + child[a].r = new PixRect(r.x, r.y, im.w, r.h); + child[b].r = new PixRect(r.x + im.w, r.y, r.w - im.w, r.h); + } else { + child[a].r = new PixRect(r.x, r.y, r.w, im.h); + child[b].r = new PixRect(r.x, r.y+ im.h, r.w, r.h - im.h); + } + return child[a].Insert(im,handed); + } + } + } + + ProbeResult bestRoot; + public int atlasY; + + static void printTree(Node r, string spc){ + Debug.Log(spc + "Nd img=" + (r.img != null) + " r=" + r.r); + if (r.child[0] != null) + printTree(r.child[0], spc + " "); + if (r.child[1] != null) + printTree(r.child[1], spc + " "); + } + + static void flattenTree(Node r, List<Image> putHere){ + if (r.img != null){ + r.img.x = r.r.x; + r.img.y = r.r.y; + putHere.Add(r.img); + } + if (r.child[0] != null) + flattenTree(r.child[0], putHere); + if (r.child[1] != null) + flattenTree(r.child[1], putHere); + } + + static void drawGizmosNode(Node r){ + Vector3 extents = new Vector3(r.r.w, r.r.h, 0); + Vector3 pos = new Vector3(r.r.x + extents.x/2, -r.r.y - extents.y/2, 0f); + Gizmos.color = Color.yellow; + Gizmos.DrawWireCube(pos,extents); + if (r.img != null){ + Gizmos.color = new Color(UnityEngine.Random.value, UnityEngine.Random.value, UnityEngine.Random.value); + extents = new Vector3(r.img.w, r.img.h, 0); + pos = new Vector3(r.r.x + extents.x / 2, -r.r.y - extents.y / 2, 0f); + Gizmos.DrawCube(pos,extents); + } + if (r.child[0] != null){ + Gizmos.color = Color.red; + drawGizmosNode(r.child[0]); + } + if (r.child[1] != null){ + Gizmos.color = Color.green; + drawGizmosNode(r.child[1]); + } + } + + static Texture2D createFilledTex(Color c, int w, int h){ + Texture2D t = new Texture2D(w,h); + for (int i = 0; i < w; i++){ + for (int j = 0; j < h; j++){ + t.SetPixel(i,j,c); + } + } + t.Apply(); + return t; + } + + public void DrawGizmos(){ + if (bestRoot != null) + { + drawGizmosNode(bestRoot.root); + Gizmos.color = Color.yellow; + Vector3 extents = new Vector3(bestRoot.outW, -bestRoot.outH, 0); + Vector3 pos = new Vector3(extents.x / 2, extents.y / 2, 0f); + Gizmos.DrawWireCube(pos, extents); + } + } + + bool ProbeSingleAtlas(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr){ + Node root = new Node(NodeType.maxDim); + root.r = new PixRect(0,0,idealAtlasW,idealAtlasH); + //Debug.Assert(maxAtlasDim >= 1); + for (int i = 0; i < imgsToAdd.Length; i++){ + Node n = root.Insert(imgsToAdd[i],false); + if (n == null){ + return false; + } else if (i == imgsToAdd.Length -1){ + int usedW = 0; + int usedH = 0; + GetExtent(root,ref usedW, ref usedH); + float efficiency,squareness; + bool fitsInMaxDim; + int atlasW = usedW; + int atlasH = usedH; + if (atlasMustBePowerOfTwo){ + atlasW = Mathf.Min (CeilToNearestPowerOfTwo(usedW), maxAtlasDimX); + atlasH = Mathf.Min (CeilToNearestPowerOfTwo(usedH), maxAtlasDimY); + if (atlasH < atlasW / 2) atlasH = atlasW / 2; + if (atlasW < atlasH / 2) atlasW = atlasH / 2; + fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY; + float scaleW = Mathf.Max (1f,((float)usedW)/ maxAtlasDimX); + float scaleH = Mathf.Max (1f,((float)usedH)/ maxAtlasDimY); + float atlasArea = atlasW * scaleW * atlasH * scaleH; //area if we scaled it up to something large enough to contain images + efficiency = 1f - (atlasArea - imgArea) / atlasArea; + squareness = 1f; //don't care about squareness in power of two case + } else { + efficiency = 1f - (usedW * usedH - imgArea) / (usedW * usedH); + if (usedW < usedH) squareness = (float) usedW / (float) usedH; + else squareness = (float) usedH / (float) usedW; + fitsInMaxDim = usedW <= maxAtlasDimX && usedH <= maxAtlasDimY; + } + pr.Set(usedW,usedH,atlasW,atlasH,root,fitsInMaxDim,efficiency,squareness); + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Probe success efficiency w=" + usedW + " h=" + usedH + " e=" + efficiency + " sq=" + squareness + " fits=" + fitsInMaxDim); + return true; + } + } + Debug.LogError("Should never get here."); + return false; + } + + bool ProbeMultiAtlas(Image[] imgsToAdd, int idealAtlasW, int idealAtlasH, float imgArea, int maxAtlasDimX, int maxAtlasDimY, ProbeResult pr) + { + int numAtlases = 0; + Node root = new Node(NodeType.maxDim); + root.r = new PixRect(0, 0, idealAtlasW, idealAtlasH); + for (int i = 0; i < imgsToAdd.Length; i++) + { + Node n = root.Insert(imgsToAdd[i], false); + if (n == null) + { + if (imgsToAdd[i].x > idealAtlasW && imgsToAdd[i].y > idealAtlasH) + { + return false; + } else + { + // create a new root node wider than previous atlas + Node newRoot = new Node(NodeType.Container); + newRoot.r = new PixRect(0, 0, root.r.w + idealAtlasW, idealAtlasH); + // create a new right child + Node newRight = new Node(NodeType.maxDim); + newRight.r = new PixRect(root.r.w, 0, idealAtlasW, idealAtlasH); + newRoot.child[1] = newRight; + // insert root as a new left child + newRoot.child[0] = root; + root = newRoot; + n = root.Insert(imgsToAdd[i], false); + numAtlases++; + } + } + } + pr.numAtlases = numAtlases; + pr.root = root; + //todo atlas may not be maxDim * maxDim. Do some checking to see what actual needed sizes are and update pr.totalArea + pr.totalAtlasArea = numAtlases * maxAtlasDimX * maxAtlasDimY; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Probe success efficiency numAtlases=" + numAtlases + " totalArea=" + pr.totalAtlasArea); + return true; + + } + + internal void GetExtent(Node r, ref int x, ref int y) + { + if (r.img != null) + { + if (r.r.x + r.img.w > x) + { + x = r.r.x + r.img.w; + } + if (r.r.y + r.img.h > y) y = r.r.y + r.img.h; + } + if (r.child[0] != null) + GetExtent(r.child[0], ref x, ref y); + if (r.child[1] != null) + GetExtent(r.child[1], ref x, ref y); + } + + int StepWidthHeight(int oldVal, int step, int maxDim){ + if (atlasMustBePowerOfTwo && oldVal < maxDim){ + return oldVal * 2; + } else { + int newVal = oldVal + step; + if (newVal > maxDim && oldVal < maxDim) newVal = maxDim; + return newVal; + } + } + + public override AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, int maxDimensionX, int maxDimensionY, int atPadding) + { + List<AtlasPadding> padding = new List<AtlasPadding>(); + for (int i = 0; i < imgWidthHeights.Count; i++) + { + AtlasPadding p = new AtlasPadding(); + p.leftRight = p.topBottom = atPadding; + padding.Add(p); + } + return GetRects(imgWidthHeights, padding, maxDimensionX, maxDimensionY, false); + } + + public override AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, bool doMultiAtlas){ + Debug.Assert(imgWidthHeights.Count == paddings.Count, imgWidthHeights.Count + " " + paddings.Count); + int maxPaddingX = 0; + int maxPaddingY = 0; + for (int i = 0; i < paddings.Count; i++) + { + maxPaddingX = Mathf.Max(maxPaddingX, paddings[i].leftRight); + maxPaddingY = Mathf.Max(maxPaddingY, paddings[i].topBottom); + } + if (doMultiAtlas) + { + return _GetRectsMultiAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2); + } + else + { + AtlasPackingResult apr = _GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 0); + if (apr == null) + { + return null; + } else + { + return new AtlasPackingResult[] { apr }; + } + } + } + + //------------------ Algorithm for fitting everything into one atlas and scaling down + // + // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. + // Sort images from big to small using either height, width or area comparer + // Explore space to find a resonably efficient packing. Grow the atlas gradually until a fit is found + // Scale atlas to fit + // + AtlasPackingResult _GetRectsSingleAtlas(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth){ + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log (String.Format("_GetRects numImages={0}, maxDimension={1}, minImageSizeX={2}, minImageSizeY={3}, masterImageSizeX={4}, masterImageSizeY={5}, recursionDepth={6}", + imgWidthHeights.Count, maxDimensionX, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, recursionDepth)); + if (recursionDepth > MAX_RECURSION_DEPTH) { + if (LOG_LEVEL >= MB2_LogLevel.error) Debug.LogError("Maximum recursion depth reached. The baked atlas is likely not very good. " + + " This happens when the packed atlases exceeds the maximum" + + " atlas size in one or both dimensions so that the atlas needs to be downscaled AND there are some very thin or very small images (only-a-few-pixels)." + + " these very thin images can 'vanish' completely when the atlas is downscaled.\n\n" + + " Try one or more of the following: using multiple atlases, increase the maximum atlas size, don't use 'force-power-of-two', remove the source materials that are are using very small/thin textures."); + //return null; + } + float area = 0; + int maxW = 0; + int maxH = 0; + Image[] imgsToAdd = new Image[imgWidthHeights.Count]; + for (int i = 0; i < imgsToAdd.Length; i++){ + int iw = (int)imgWidthHeights[i].x; + int ih = (int)imgWidthHeights[i].y; + Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); + area += im.w * im.h; + maxW = Mathf.Max(maxW, im.w); + maxH = Mathf.Max(maxH, im.h); + } + + if ((float)maxH/(float)maxW > 2){ + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Using height Comparer"); + Array.Sort(imgsToAdd,new ImageHeightComparer()); + } + else if ((float)maxH/(float)maxW < .5){ + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Using width Comparer"); + Array.Sort(imgsToAdd,new ImageWidthComparer()); + } + else{ + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Using area Comparer"); + Array.Sort(imgsToAdd,new ImageAreaComparer()); + } + + //explore the space to find a resonably efficient packing + int sqrtArea = (int) Mathf.Sqrt(area); + int idealAtlasW; + int idealAtlasH; + + if (atlasMustBePowerOfTwo) + { + idealAtlasW = idealAtlasH = RoundToNearestPositivePowerOfTwo(sqrtArea); + if (maxW > idealAtlasW) + { + idealAtlasW = CeilToNearestPowerOfTwo(idealAtlasW); + } + if (maxH > idealAtlasH) + { + idealAtlasH = CeilToNearestPowerOfTwo(idealAtlasH); + } + } + else + { + idealAtlasW = sqrtArea; + idealAtlasH = sqrtArea; + if (maxW > sqrtArea) + { + idealAtlasW = maxW; + idealAtlasH = Mathf.Max(Mathf.CeilToInt(area / maxW), maxH); + } + if (maxH > sqrtArea) + { + idealAtlasW = Mathf.Max(Mathf.CeilToInt(area / maxH), maxW); + idealAtlasH = maxH; + } + } + + if (idealAtlasW == 0) idealAtlasW = 4; + if (idealAtlasH == 0) idealAtlasH = 4; + int stepW = (int)(idealAtlasW * .15f); + int stepH = (int)(idealAtlasH * .15f); + if (stepW == 0) stepW = 1; + if (stepH == 0) stepH = 1; + int numWIterations=2; + int steppedWidth = idealAtlasW; + int steppedHeight = idealAtlasH; + + while (numWIterations >= 1 && steppedHeight < sqrtArea * 1000) + { + bool successW = false; + numWIterations = 0; + steppedWidth = idealAtlasW; + while (!successW && steppedWidth < sqrtArea * 1000) + { + ProbeResult pr = new ProbeResult(); + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Probing h=" + steppedHeight + " w=" + steppedWidth); + if (ProbeSingleAtlas (imgsToAdd, steppedWidth, steppedHeight, area, maxDimensionX, maxDimensionY, pr)) + { + successW = true; + if (bestRoot == null) bestRoot = pr; + else if (pr.GetScore(atlasMustBePowerOfTwo) > bestRoot.GetScore(atlasMustBePowerOfTwo)) bestRoot = pr; + } + else + { + numWIterations++; + steppedWidth = StepWidthHeight(steppedWidth, stepW, maxDimensionX); + if (LOG_LEVEL >= MB2_LogLevel.trace) MB2_Log.LogDebug("increasing Width h=" + steppedHeight + " w=" + steppedWidth); + } + } + steppedHeight = StepWidthHeight(steppedHeight, stepH, maxDimensionY); + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("increasing Height h=" + steppedHeight + " w=" + steppedWidth); + } + if (bestRoot == null) + { + return null; + } + + int outW = 0; + int outH = 0; + if (atlasMustBePowerOfTwo){ + outW = Mathf.Min (CeilToNearestPowerOfTwo(bestRoot.w), maxDimensionX); + outH = Mathf.Min (CeilToNearestPowerOfTwo(bestRoot.h), maxDimensionY); + if (outH < outW / 2) outH = outW / 2; //smaller dim can't be less than half larger + if (outW < outH / 2) outW = outH / 2; + } else + { + outW = Mathf.Min(bestRoot.w, maxDimensionX); + outH = Mathf.Min(bestRoot.h, maxDimensionY); + } + + bestRoot.outW = outW; + bestRoot.outH = outH; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Best fit found: atlasW=" + outW + " atlasH" + outH + " w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); + + //Debug.Assert(images.Count != imgsToAdd.Length, "Result images not the same lentgh as source")); + + //the atlas can be larger than the max dimension scale it if this is the case + //int newMinSizeX = minImageSizeX; + //int newMinSizeY = minImageSizeY; + + + List<Image> images = new List<Image>(); + flattenTree(bestRoot.root, images); + images.Sort(new ImgIDComparer()); + // the atlas may be packed larger than the maxDimension. If so then the atlas needs to be scaled down to fit + Vector2 rootWH = new Vector2(bestRoot.w, bestRoot.h); + float padX, padY; + int newMinSizeX, newMinSizeY; + if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, + ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY) || + recursionDepth > MAX_RECURSION_DEPTH) + { + AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); + res.rects = new Rect[images.Count]; + res.srcImgIdxs = new int[images.Count]; + res.atlasX = outW; + res.atlasY = outH; + res.usedW = -1; + res.usedH = -1; + for (int i = 0; i < images.Count; i++) + { + Image im = images[i]; + Rect r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, + (float)im.y / (float)outH + padY, + (float)im.w / (float)outW - padX * 2f, + (float)im.h / (float)outH - padY * 2f); + res.srcImgIdxs[i] = im.imgId; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + + " y=" + r.y * outH + " w=" + r.width * outW + + " h=" + r.height * outH + " padding=" + (paddings[i].leftRight * 2) + "x" + (paddings[i].topBottom*2)); + } + res.CalcUsedWidthAndHeight(); + return res; + + + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("==================== REDOING PACKING ================"); + //root = null; + return _GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, newMinSizeX, newMinSizeY, masterImageSizeX, masterImageSizeY, recursionDepth + 1); + } + + + //if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(String.Format("Done GetRects atlasW={0} atlasH={1}", bestRoot.w, bestRoot.h)); + + //return res; + } + + + //----------------- Algorithm for fitting everything into multiple Atlases + // + // for images being added calc area, maxW, maxH. A perfectly packed atlas will match area exactly. atlas must be at least maxH and maxW in size. + // Sort images from big to small using either height, width or area comparer + // + // If an image is bigger than maxDim, then shrink it to max size on the largest dimension + // distribute images using the new algorithm, should never have to expand the atlas instead create new atlases as needed + // should not need to scale atlases + // + AtlasPackingResult[] _GetRectsMultiAtlas(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(String.Format("_GetRects numImages={0}, maxDimensionX={1}, maxDimensionY={2} minImageSizeX={3}, minImageSizeY={4}, masterImageSizeX={5}, masterImageSizeY={6}", + imgWidthHeights.Count, maxDimensionPassedX, maxDimensionPassedY, minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY)); + float area = 0; + int maxW = 0; + int maxH = 0; + Image[] imgsToAdd = new Image[imgWidthHeights.Count]; + int maxDimensionX = maxDimensionPassedX; + int maxDimensionY = maxDimensionPassedY; + if (atlasMustBePowerOfTwo) + { + maxDimensionX = RoundToNearestPositivePowerOfTwo(maxDimensionX); + maxDimensionY = RoundToNearestPositivePowerOfTwo(maxDimensionY); + } + for (int i = 0; i < imgsToAdd.Length; i++) + { + int iw = (int)imgWidthHeights[i].x; + int ih = (int)imgWidthHeights[i].y; + + //shrink the image so that it fits in maxDimenion if it is larger than maxDimension if atlas exceeds maxDim x maxDim then new alas will be created + iw = Mathf.Min(iw, maxDimensionX - paddings[i].leftRight * 2); + ih = Mathf.Min(ih, maxDimensionY - paddings[i].topBottom * 2); + + Image im = imgsToAdd[i] = new Image(i, iw, ih, paddings[i], minImageSizeX, minImageSizeY); + area += im.w * im.h; + maxW = Mathf.Max(maxW, im.w); + maxH = Mathf.Max(maxH, im.h); + } + + //explore the space to find a resonably efficient packing + //int sqrtArea = (int)Mathf.Sqrt(area); + int idealAtlasW; + int idealAtlasH; + + if (atlasMustBePowerOfTwo) + { + idealAtlasH = RoundToNearestPositivePowerOfTwo(maxDimensionY); + idealAtlasW = RoundToNearestPositivePowerOfTwo(maxDimensionX); + } + else + { + idealAtlasH = maxDimensionY; + idealAtlasW = maxDimensionX; + } + + if (idealAtlasW == 0) idealAtlasW = 4; + if (idealAtlasH == 0) idealAtlasH = 4; + + ProbeResult pr = new ProbeResult(); + Array.Sort(imgsToAdd, new ImageHeightComparer()); + if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) + { + bestRoot = pr; + } + Array.Sort(imgsToAdd, new ImageWidthComparer()); + if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) + { + if (pr.totalAtlasArea < bestRoot.totalAtlasArea) + { + bestRoot = pr; + } + } + Array.Sort(imgsToAdd, new ImageAreaComparer()); + if (ProbeMultiAtlas(imgsToAdd, idealAtlasW, idealAtlasH, area, maxDimensionX, maxDimensionY, pr)) + { + if (pr.totalAtlasArea < bestRoot.totalAtlasArea) + { + bestRoot = pr; + } + } + + if (bestRoot == null) + { + return null; + } + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Best fit found: w=" + bestRoot.w + " h=" + bestRoot.h + " efficiency=" + bestRoot.efficiency + " squareness=" + bestRoot.squareness + " fits in max dimension=" + bestRoot.largerOrEqualToMaxDim); + + //the atlas can be larger than the max dimension scale it if this is the case + //int newMinSizeX = minImageSizeX; + //int newMinSizeY = minImageSizeY; + List<AtlasPackingResult> rs = new List<AtlasPackingResult>(); + + // find all Nodes that are an individual atlas + List<Node> atlasNodes = new List<Node>(); + Stack<Node> stack = new Stack<Node>(); + Node node = bestRoot.root; + + while (node != null) + { + stack.Push(node); + node = node.child[0]; + } + + // traverse the tree collecting atlasNodes + while (stack.Count > 0) + { + node = stack.Pop(); + if (node.isFullAtlas == NodeType.maxDim) atlasNodes.Add(node); + if (node.child[1] != null) + { + node = node.child[1]; + while (node != null) + { + stack.Push(node); + node = node.child[0]; + } + } + } + + //pack atlases so they all fit + for (int i = 0; i < atlasNodes.Count; i++) + { + List<Image> images = new List<Image>(); + flattenTree(atlasNodes[i], images); + Rect[] rss = new Rect[images.Count]; + int[] srcImgIdx = new int[images.Count]; + for (int j = 0; j < images.Count; j++) + { + rss[j] = (new Rect(images[j].x - atlasNodes[i].r.x, images[j].y, images[j].w, images[j].h)); + srcImgIdx[j] = images[j].imgId; + } + AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); + GetExtent(atlasNodes[i], ref res.usedW, ref res.usedH); + res.usedW -= atlasNodes[i].r.x; + int outW = atlasNodes[i].r.w; + int outH = atlasNodes[i].r.h; + if (atlasMustBePowerOfTwo) + { + outW = Mathf.Min(CeilToNearestPowerOfTwo(res.usedW), atlasNodes[i].r.w); + outH = Mathf.Min(CeilToNearestPowerOfTwo(res.usedH), atlasNodes[i].r.h); + if (outH < outW / 2) outH = outW / 2; //smaller dim can't be less than half larger + if (outW < outH / 2) outW = outH / 2; + } else + { + outW = res.usedW; + outH = res.usedH; + } + + res.atlasY = outH; + res.atlasX = outW; + + res.rects = rss; + res.srcImgIdxs = srcImgIdx; + res.CalcUsedWidthAndHeight(); + rs.Add(res); + ConvertToRectsWithoutPaddingAndNormalize01(res, paddings[i]); + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug(String.Format("Done GetRects ")); + } + + return rs.ToArray(); + } + } + + +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePacker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePacker.cs.meta new file mode 100644 index 00000000..9763241f --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePacker.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 96875af05a8fc9c4f8ce03e13615199f +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePackerHorizontalVert.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePackerHorizontalVert.cs new file mode 100644 index 00000000..1be3e0aa --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePackerHorizontalVert.cs @@ -0,0 +1,420 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; +using System.IO; + + +/* + TODO + vertical as well as horizontal + if images different heights should scale all of them to be the max + +Tests For All Texture Packers + can handle images larger than maxdim + +*/ + +namespace DigitalOpus.MB.Core{ + // uses this algorithm http://blackpawn.com/texts/lightmaps/ + public class MB2_TexturePackerHorizontalVert : MB2_TexturePacker { + + public enum TexturePackingOrientation + { + horizontal, + vertical + } + + public TexturePackingOrientation packingOrientation; + + public bool stretchImagesToEdges = true; + + public override AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, int maxDimensionX, int maxDimensionY, int padding) + { + List<AtlasPadding> paddings = new List<AtlasPadding>(); + for (int i = 0; i < imgWidthHeights.Count; i++) + { + AtlasPadding p = new AtlasPadding(); + if (packingOrientation == TexturePackingOrientation.horizontal) + { + p.leftRight = 0; + p.topBottom = 8; + } else + { + p.leftRight = 8; + p.topBottom = 0; + } + paddings.Add(p); + } + return GetRects(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, false); + } + + public override AtlasPackingResult[] GetRects(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, bool doMultiAtlas){ + Debug.Assert(imgWidthHeights.Count == paddings.Count); + int maxPaddingX = 0; + int maxPaddingY = 0; + for (int i = 0; i < paddings.Count; i++) + { + maxPaddingX = Mathf.Max(maxPaddingX, paddings[i].leftRight); + maxPaddingY = Mathf.Max(maxPaddingY, paddings[i].topBottom); + } + if (doMultiAtlas) + { + if (packingOrientation == TexturePackingOrientation.vertical) + { + return _GetRectsMultiAtlasVertical(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2); + } else + { + return _GetRectsMultiAtlasHorizontal(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2); + } + } + else + { + AtlasPackingResult apr = _GetRectsSingleAtlas(imgWidthHeights, paddings, maxDimensionX, maxDimensionY, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 2 + maxPaddingX * 2, 2 + maxPaddingY * 2, 0); + if (apr == null) + { + return null; + } else + { + return new AtlasPackingResult[] { apr }; + } + } + } + + AtlasPackingResult _GetRectsSingleAtlas(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionX, int maxDimensionY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY, int recursionDepth) + { + AtlasPackingResult res = new AtlasPackingResult(paddings.ToArray()); + + List<Rect> rects = new List<Rect>(); + int extent = 0; + int maxh = 0; + int maxw = 0; + List<Image> images = new List<Image>(); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Packing rects for: " + imgWidthHeights.Count); + for (int i = 0; i < imgWidthHeights.Count; i++) + { + Image im = new Image(i, (int) imgWidthHeights[i].x, (int) imgWidthHeights[i].y, paddings[i], minImageSizeX, minImageSizeY); + + // if images are stacked horizontally then there is no padding at the top or bottom + if (packingOrientation == TexturePackingOrientation.vertical) + { + im.h -= paddings[i].topBottom * 2; + im.x = extent; + im.y = 0; + rects.Add(new Rect(im.w, im.h, extent, 0)); + extent += im.w; + maxh = Mathf.Max(maxh, im.h); + } else + { + im.w -= paddings[i].leftRight * 2; + im.y = extent; + im.x = 0; + rects.Add(new Rect(im.w, im.h, 0, extent)); + extent += im.h; + maxw = Mathf.Max(maxw, im.w); + } + images.Add(im); + } + //scale atlas to fit maxDimension + Vector2 rootWH; + if (packingOrientation == TexturePackingOrientation.vertical) { rootWH = new Vector2(extent, maxh); } + else { rootWH = new Vector2(maxw,extent); } + int outW = (int) rootWH.x; + int outH = (int) rootWH.y; + if (packingOrientation == TexturePackingOrientation.vertical) { + if (atlasMustBePowerOfTwo) + { + outW = Mathf.Min(CeilToNearestPowerOfTwo(outW), maxDimensionX); + } + else + { + outW = Mathf.Min(outW, maxDimensionX); + } + } else + { + if (atlasMustBePowerOfTwo) + { + outH = Mathf.Min(CeilToNearestPowerOfTwo(outH), maxDimensionY); + } + else + { + outH = Mathf.Min(outH, maxDimensionY); + } + } + + float padX, padY; + int newMinSizeX, newMinSizeY; + if (!ScaleAtlasToFitMaxDim(rootWH, images, maxDimensionX, maxDimensionY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, + ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY)) + { + + res = new AtlasPackingResult(paddings.ToArray()); + res.rects = new Rect[images.Count]; + res.srcImgIdxs = new int[images.Count]; + res.atlasX = outW; + res.atlasY = outH; + for (int i = 0; i < images.Count; i++) + { + Image im = images[i]; + Rect r; + if (packingOrientation == TexturePackingOrientation.vertical) + { + r = res.rects[i] = new Rect((float)im.x / (float)outW + padX, + (float)im.y / (float)outH, + (float)im.w / (float)outW - padX * 2f, + stretchImagesToEdges ? 1f : (float)im.h / (float)outH); // all images are stretched to fill the height + } else + { + r = res.rects[i] = new Rect((float)im.x / (float)outW, + (float)im.y / (float)outH + padY, + (stretchImagesToEdges ? 1f : ((float)im.w / (float)outW)), + (float)im.h / (float)outH - padY * 2f); // all images are stretched to fill the height + } + res.srcImgIdxs[i] = im.imgId; + if (LOG_LEVEL >= MB2_LogLevel.debug) MB2_Log.LogDebug("Image: " + i + " imgID=" + im.imgId + " x=" + r.x * outW + + " y=" + r.y * outH + " w=" + r.width * outW + + " h=" + r.height * outH + " padding=" + paddings[i] + " outW=" + outW + " outH=" + outH); + } + res.CalcUsedWidthAndHeight(); + return res; + } + Debug.Log("Packing failed returning null atlas result"); + return null; + } + + AtlasPackingResult[] _GetRectsMultiAtlasVertical(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) + { + List<AtlasPackingResult> rs = new List<AtlasPackingResult>(); + int extent = 0; + int maxh = 0; + int maxw = 0; + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Packing rects for: " + imgWidthHeights.Count); + + List<Image> allImages = new List<Image>(); + for (int i = 0; i < imgWidthHeights.Count; i++) + { + Image im = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, paddings[i], minImageSizeX, minImageSizeY); + im.h -= paddings[i].topBottom * 2; + allImages.Add(im); + } + allImages.Sort(new ImageWidthComparer()); + List<Image> images = new List<Image>(); + List<Rect> rects = new List<Rect>(); + int spaceRemaining = maxDimensionPassedX; + while (allImages.Count > 0 || images.Count > 0) + { + Image im = PopLargestThatFits(allImages, spaceRemaining, maxDimensionPassedX, images.Count == 0); + if (im == null) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Atlas filled creating a new atlas "); + AtlasPackingResult apr = new AtlasPackingResult(paddings.ToArray()); + apr.atlasX = maxw; + apr.atlasY = maxh; + Rect[] rss = new Rect[images.Count]; + int[] srcImgIdx = new int[images.Count]; + for (int j = 0; j < images.Count; j++) + { + Rect r = new Rect(images[j].x, images[j].y, + images[j].w, + stretchImagesToEdges ? maxh : images[j].h); + rss[j] = r; + srcImgIdx[j] = images[j].imgId; + } + apr.rects = rss; + apr.srcImgIdxs = srcImgIdx; + apr.CalcUsedWidthAndHeight(); + images.Clear(); + rects.Clear(); + extent = 0; + maxh = 0; + rs.Add(apr); + spaceRemaining = maxDimensionPassedX; + } else + { + im.x = extent; + im.y = 0; + images.Add(im); + rects.Add(new Rect(extent, 0, im.w, im.h)); + extent += im.w; + maxh = Mathf.Max(maxh, im.h); + maxw = extent; + spaceRemaining = maxDimensionPassedX - extent; + } + } + + for (int i = 0; i < rs.Count; i++) + { + int outW = rs[i].atlasX; + int outH = Mathf.Min(rs[i].atlasY, maxDimensionPassedY); + if (atlasMustBePowerOfTwo) + { + outW = Mathf.Min(CeilToNearestPowerOfTwo(outW), maxDimensionPassedX); + } + else + { + outW = Mathf.Min(outW, maxDimensionPassedX); + } + rs[i].atlasX = outW; + //------------------------------- + //scale atlas to fit maxDimension + float padX, padY; + int newMinSizeX, newMinSizeY; + ScaleAtlasToFitMaxDim(new Vector2(rs[i].atlasX, rs[i].atlasY), images, maxDimensionPassedX, maxDimensionPassedY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, + ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY); + } + + + + //normalize atlases so that that rects are 0 to 1 + for (int i = 0; i < rs.Count; i++) { + ConvertToRectsWithoutPaddingAndNormalize01(rs[i], paddings[i]); + rs[i].CalcUsedWidthAndHeight(); + } + //----------------------------- + return rs.ToArray(); + } + + AtlasPackingResult[] _GetRectsMultiAtlasHorizontal(List<Vector2> imgWidthHeights, List<AtlasPadding> paddings, int maxDimensionPassedX, int maxDimensionPassedY, int minImageSizeX, int minImageSizeY, int masterImageSizeX, int masterImageSizeY) + { + List<AtlasPackingResult> rs = new List<AtlasPackingResult>(); + int extent = 0; + int maxh = 0; + int maxw = 0; + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Packing rects for: " + imgWidthHeights.Count); + + List<Image> allImages = new List<Image>(); + for (int i = 0; i < imgWidthHeights.Count; i++) + { + Image im = new Image(i, (int)imgWidthHeights[i].x, (int)imgWidthHeights[i].y, paddings[i], minImageSizeX, minImageSizeY); + im.w -= paddings[i].leftRight * 2; + allImages.Add(im); + } + allImages.Sort(new ImageHeightComparer()); + List<Image> images = new List<Image>(); + List<Rect> rects = new List<Rect>(); + int spaceRemaining = maxDimensionPassedY; + while (allImages.Count > 0 || images.Count > 0) + { + Image im = PopLargestThatFits(allImages, spaceRemaining, maxDimensionPassedY, images.Count == 0); + if (im == null) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Atlas filled creating a new atlas "); + AtlasPackingResult apr = new AtlasPackingResult(paddings.ToArray()); + apr.atlasX = maxw; + apr.atlasY = maxh; + Rect[] rss = new Rect[images.Count]; + int[] srcImgIdx = new int[images.Count]; + for (int j = 0; j < images.Count; j++) + { + Rect r = new Rect(images[j].x, images[j].y, + stretchImagesToEdges ? maxw : images[j].w, + images[j].h); + rss[j] = r; + srcImgIdx[j] = images[j].imgId; + } + apr.rects = rss; + apr.srcImgIdxs = srcImgIdx; + + images.Clear(); + rects.Clear(); + extent = 0; + maxh = 0; + rs.Add(apr); + spaceRemaining = maxDimensionPassedY; + } + else + { + im.x = 0; + im.y = extent; + images.Add(im); + rects.Add(new Rect(0, extent, im.w, im.h)); + extent += im.h; + maxw = Mathf.Max(maxw, im.w); + maxh = extent; + spaceRemaining = maxDimensionPassedY - extent; + } + } + + for (int i = 0; i < rs.Count; i++) + { + int outH = rs[i].atlasY; + int outW = Mathf.Min(rs[i].atlasX, maxDimensionPassedX); + if (atlasMustBePowerOfTwo) + { + outH = Mathf.Min(CeilToNearestPowerOfTwo(outH), maxDimensionPassedY); + } + else + { + outH = Mathf.Min(outH, maxDimensionPassedY); + } + rs[i].atlasY = outH; + //------------------------------- + //scale atlas to fit maxDimension + float padX, padY; + int newMinSizeX, newMinSizeY; + ScaleAtlasToFitMaxDim(new Vector2(rs[i].atlasX, rs[i].atlasY), images, maxDimensionPassedX, maxDimensionPassedY, paddings[0], minImageSizeX, minImageSizeY, masterImageSizeX, masterImageSizeY, + ref outW, ref outH, out padX, out padY, out newMinSizeX, out newMinSizeY); + } + + + + //normalize atlases so that that rects are 0 to 1 + for (int i = 0; i < rs.Count; i++) + { + ConvertToRectsWithoutPaddingAndNormalize01(rs[i], paddings[i]); + rs[i].CalcUsedWidthAndHeight(); + } + //----------------------------- + return rs.ToArray(); + } + + Image PopLargestThatFits(List<Image> images, int spaceRemaining, int maxDim, bool emptyAtlas) + { + //pop single images larger than maxdim into their own atlas + int imageDim; + if (images.Count == 0) + { + return null; + } + + if (packingOrientation == TexturePackingOrientation.vertical) + { + imageDim = images[0].w; + } else + { + imageDim = images[0].h; + } + if (images.Count > 0 && imageDim >= maxDim) + { + if (emptyAtlas) + { + Image im = images[0]; + images.RemoveAt(0); + return im; + } else + { + return null; + } + } + + // now look for images that will fit + int i = 0; + while (i < images.Count && imageDim >= spaceRemaining) + { + i++; + } + if (i < images.Count) + { + Image im = images[i]; + images.RemoveAt(i); + return im; + } else + { + return null; + } + } + } + +}
\ No newline at end of file diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePackerHorizontalVert.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePackerHorizontalVert.cs.meta new file mode 100644 index 00000000..b0e0f5c5 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB2_TexturePackerHorizontalVert.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2db0738246127ec4ea2157c079a85ea9 +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_AtlasPackerRenderTexture.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_AtlasPackerRenderTexture.cs new file mode 100644 index 00000000..39175159 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_AtlasPackerRenderTexture.cs @@ -0,0 +1,469 @@ +using UnityEngine; +using System.Collections; +using System; +using System.Collections.Generic; +using System.IO; +using DigitalOpus.MB.Core; + +public class MB_TextureCombinerRenderTexture{ + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + Material mat; //container for the shader that we will use to render the texture + RenderTexture _destinationTexture; + Camera myCamera; + int _padding; + bool _isNormalMap; + bool _fixOutOfBoundsUVs; + //bool _considerNonTextureProperties; + + //only want to render once, not every frame + bool _doRenderAtlas = false; + + Rect[] rs; + List<MB_TexSet> textureSets; + int indexOfTexSetToRender; + ShaderTextureProperty _texPropertyName; + MB3_TextureCombinerNonTextureProperties _resultMaterialTextureBlender; + Texture2D targTex; + + public Texture2D DoRenderAtlas(GameObject gameObject, int width, int height, int padding, Rect[] rss, List<MB_TexSet> textureSetss, int indexOfTexSetToRenders, ShaderTextureProperty texPropertyname, MB3_TextureCombinerNonTextureProperties resultMaterialTextureBlender, bool isNormalMap, bool fixOutOfBoundsUVs, bool considerNonTextureProperties, MB3_TextureCombiner texCombiner, MB2_LogLevel LOG_LEV){ + LOG_LEVEL = LOG_LEV; + textureSets = textureSetss; + indexOfTexSetToRender = indexOfTexSetToRenders; + _texPropertyName = texPropertyname; + _padding = padding; + _isNormalMap = isNormalMap; + _fixOutOfBoundsUVs = fixOutOfBoundsUVs; + //_considerNonTextureProperties = considerNonTextureProperties; + _resultMaterialTextureBlender = resultMaterialTextureBlender; + rs = rss; + Shader s; + if (_isNormalMap){ + s = Shader.Find ("MeshBaker/NormalMapShader"); + } else { + s = Shader.Find ("MeshBaker/AlbedoShader"); + } + if (s == null){ + Debug.LogError ("Could not find shader for RenderTexture. Try reimporting mesh baker"); + return null; + } + mat = new Material(s); + _destinationTexture = new RenderTexture(width,height,24,RenderTextureFormat.ARGB32); + _destinationTexture.filterMode = FilterMode.Point; + + myCamera = gameObject.GetComponent<Camera>(); + myCamera.orthographic = true; + myCamera.orthographicSize = height >> 1; + myCamera.aspect = ((float) width) / height; + myCamera.targetTexture = _destinationTexture; + myCamera.clearFlags = CameraClearFlags.Color; + + Transform camTransform = myCamera.GetComponent<Transform>(); + camTransform.localPosition = new Vector3(width/2.0f, height/2f, 3); + camTransform.localRotation = Quaternion.Euler(0, 180, 180); + + _doRenderAtlas = true; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(string.Format ("Begin Camera.Render destTex w={0} h={1} camPos={2} camSize={3} camAspect={4}", width, height, camTransform.localPosition, myCamera.orthographicSize, myCamera.aspect.ToString("f5"))); + //This triggers the OnRenderObject callback + myCamera.Render(); + _doRenderAtlas = false; + + MB_Utility.Destroy(mat); + MB_Utility.Destroy(_destinationTexture); + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log ("Finished Camera.Render "); + + Texture2D tempTex = targTex; + targTex = null; + return tempTex; + } + + public void OnRenderObject(){ + if (_doRenderAtlas){ + //assett rs must be same length as textureSets; + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start (); + bool yIsFlipped = YisFlipped(LOG_LEVEL); + for (int i = 0; i < rs.Length; i++){ + MeshBakerMaterialTexture texInfo = textureSets[i].ts[indexOfTexSetToRender]; + Texture2D tx = texInfo.GetTexture2D(); + if (LOG_LEVEL >= MB2_LogLevel.trace && tx != null) { + Debug.Log("Added " + tx + " to atlas w=" + tx.width + " h=" + tx.height + " offset=" + texInfo.matTilingRect.min + " scale=" + texInfo.matTilingRect.size + " rect=" + rs[i] + " padding=" + _padding); + //_printTexture(tx); + } + + CopyScaledAndTiledToAtlas(textureSets[i], texInfo, textureSets[i].obUVoffset, textureSets[i].obUVscale, rs[i],_texPropertyName,_resultMaterialTextureBlender, yIsFlipped); + } + sw.Stop(); + sw.Start(); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log ("Total time for Graphics.DrawTexture calls " + (sw.ElapsedMilliseconds).ToString("f5")); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log ("Copying RenderTexture to Texture2D. destW" + _destinationTexture.width + " destH" + _destinationTexture.height ); + //Convert the RenderTexture to a Texture2D + /* + Texture2D tempTexture; + tempTexture = new Texture2D(_destinationTexture.width, _destinationTexture.height, TextureFormat.ARGB32, true); + + RenderTexture oldRT = RenderTexture.active; + RenderTexture.active = _destinationTexture; + int xblocks = Mathf.CeilToInt(((float) _destinationTexture.width) / 512); + int yblocks = Mathf.CeilToInt(((float) _destinationTexture.height) / 512); + if (xblocks == 0 || yblocks == 0) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log ("Copying all in one shot"); + tempTexture.ReadPixels(new Rect(0, 0, _destinationTexture.width, _destinationTexture.height), 0, 0, true); + } else { + + if (yIsFlipped == false) + { + for (int x = 0; x < xblocks; x++) + { + for (int y = 0; y < yblocks; y++) + { + int xx = x * 512; + int yy = y * 512; + Rect r = new Rect(xx, yy, 512, 512); + tempTexture.ReadPixels(r, x * 512, y * 512, true); + } + } + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log ("Not OpenGL copying blocks"); + for (int x = 0; x < xblocks; x++) + { + for (int y = 0; y < yblocks; y++) + { + int xx = x * 512; + int yy = _destinationTexture.height - 512 - y * 512; + Rect r = new Rect(xx, yy, 512, 512); + tempTexture.ReadPixels(r, x * 512, y * 512, true); + } + } + } + } + RenderTexture.active = oldRT; + tempTexture.Apply (); + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + Debug.Log("TempTexture "); + if (tempTexture.height <= 16 && tempTexture.width <= 16) _printTexture(tempTexture); + } + myCamera.targetTexture = null; + RenderTexture.active = null; + */ + Texture2D tempTexture = new Texture2D(_destinationTexture.width, _destinationTexture.height, TextureFormat.ARGB32, true, false); + ConvertRenderTextureToTexture2D(_destinationTexture, yIsFlipped, false, LOG_LEVEL, tempTexture); + myCamera.targetTexture = null; + + targTex = tempTexture; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log ("Total time to copy RenderTexture to Texture2D " + (sw.ElapsedMilliseconds).ToString("f5")); + } + } + + public static void ConvertRenderTextureToTexture2D(RenderTexture _destinationTexture, bool yIsFlipped, bool doLinearColorSpace, MB2_LogLevel LOG_LEVEL, Texture2D tempTexture) + { + RenderTexture oldRT = RenderTexture.active; + RenderTexture.active = _destinationTexture; + int xblocks = Mathf.CeilToInt(((float)_destinationTexture.width) / 512); + int yblocks = Mathf.CeilToInt(((float)_destinationTexture.height) / 512); + if (xblocks == 0 || yblocks == 0) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Copying all in one shot"); + tempTexture.ReadPixels(new Rect(0, 0, _destinationTexture.width, _destinationTexture.height), 0, 0, true); + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("yIsFlipped copying blocks"); + if (yIsFlipped == false) + { + for (int x = 0; x < xblocks; x++) + { + for (int y = 0; y < yblocks; y++) + { + int xx = x * 512; + int yy = y * 512; + Rect r = new Rect(xx, yy, 512, 512); + tempTexture.ReadPixels(r, x * 512, y * 512, true); + } + } + } + else + { + + for (int x = 0; x < xblocks; x++) + { + for (int y = 0; y < yblocks; y++) + { + int xx = x * 512; + int yy = _destinationTexture.height - 512 - y * 512; + Rect r = new Rect(xx, yy, 512, 512); + tempTexture.ReadPixels(r, x * 512, y * 512, true); + } + } + } + } + + RenderTexture.active = oldRT; + tempTexture.Apply(); + if (LOG_LEVEL >= MB2_LogLevel.trace && tempTexture.height <= 16 && tempTexture.width <= 16) + { + _printTexture(tempTexture); + } + + // yield break; + } + + + /* + Unity uses a non-standard format for storing normals for some platforms. Imagine the standard format is English, Unity's is French + When the normal-map checkbox is ticked on the asset importer the normal map is translated into french. When we build the normal atlas + we are reading the french. When we save and click the normal map tickbox we are translating french -> french. A double transladion that + breaks the normal map. To fix this we need to "unconvert" the normal map to english when saving the atlas as a texture so that unity importer + can do its thing properly. + */ + Color32 ConvertNormalFormatFromUnity_ToStandard(Color32 c) { + Vector3 n = Vector3.zero; + n.x = c.a * 2f - 1f; + n.y = c.g * 2f - 1f; + n.z = Mathf.Sqrt(1 - n.x * n.x - n.y * n.y); + //now repack in the regular format + Color32 cc = new Color32(); + cc.a = 1; + cc.r = (byte) ((n.x + 1f) * .5f); + cc.g = (byte) ((n.y + 1f) * .5f); + cc.b = (byte) ((n.z + 1f) * .5f); + return cc; + } + + public static bool YisFlipped(MB2_LogLevel LOG_LEVEL) { + string graphicsDeviceVersion = SystemInfo.graphicsDeviceVersion.ToLower(); + bool flipY; + if (!MBVersion.GraphicsUVStartsAtTop()) + { + flipY = false; + } else { + // "opengl es, direct3d" + flipY = true; + } + + if (LOG_LEVEL == MB2_LogLevel.debug) Debug.Log("Graphics device version is: " + graphicsDeviceVersion + " flipY:" + flipY); + return flipY; + } + + private void CopyScaledAndTiledToAtlas(MB_TexSet texSet, MeshBakerMaterialTexture source, Vector2 obUVoffset, Vector2 obUVscale, Rect rec, ShaderTextureProperty texturePropertyName, MB3_TextureCombinerNonTextureProperties resultMatTexBlender, bool yIsFlipped){ + Rect r = rec; + myCamera.backgroundColor = resultMatTexBlender.GetColorForTemporaryTexture(texSet.matsAndGOs.mats[0].mat, texturePropertyName); + //yIsFlipped = true; + //if (yIsFlipped) + //{ + //} + r.y = 1f - (r.y + r.height); // DrawTexture uses topLeft 0,0, Texture2D uses bottomLeft 0,0 + r.x *= _destinationTexture.width; + r.y *= _destinationTexture.height; + r.width *= _destinationTexture.width; + r.height *= _destinationTexture.height; + + Rect rPadded = r; + rPadded.x -= _padding; + rPadded.y -= _padding; + rPadded.width += _padding * 2; + rPadded.height += _padding * 2; + + Rect targPr = new Rect(); + Rect srcPrTex = texSet.ts[indexOfTexSetToRender].GetEncapsulatingSamplingRect().GetRect(); + if (!_fixOutOfBoundsUVs) { + Debug.Assert(source.matTilingRect.GetRect() == texSet.ts[indexOfTexSetToRender].GetEncapsulatingSamplingRect().GetRect()); + } + Texture2D tex = source.GetTexture2D(); + /* + if (_considerNonTextureProperties && resultMatTexBlender != null) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("Blending texture {0} mat {1} with non-texture properties using TextureBlender {2}", tex.name, texSet.mats[0].mat, resultMatTexBlender)); + + resultMatTexBlender.OnBeforeTintTexture(texSet.mats[0].mat, texturePropertyName.name); + //combine the tintColor with the texture + tex = combiner._createTextureCopy(tex); + for (int i = 0; i < tex.height; i++) + { + Color[] cs = tex.GetPixels(0, i, tex.width, 1); + for (int j = 0; j < cs.Length; j++) + { + cs[j] = resultMatTexBlender.OnBlendTexturePixel(texturePropertyName.name, cs[j]); + } + tex.SetPixels(0, i, tex.width, 1, cs); + } + tex.Apply(); + } + */ + + + //main texture + TextureWrapMode oldTexWrapMode = tex.wrapMode; + if (srcPrTex.width == 1f && srcPrTex.height == 1f && srcPrTex.x == 0f && srcPrTex.y == 0f){ + //fixes bug where there is a dark line at the edge of the texture + tex.wrapMode = TextureWrapMode.Clamp; + } else { + tex.wrapMode = TextureWrapMode.Repeat; + } + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log ("DrawTexture tex=" + tex.name + " destRect=" + r + " srcRect=" + srcPrTex + " Mat=" + mat); + //fill the padding first + Rect srcPr = new Rect(); + + //top margin + srcPr.x = srcPrTex.x; + srcPr.y = srcPrTex.y + 1 - 1f / tex.height; + srcPr.width = srcPrTex.width; + srcPr.height = 1f / tex.height; + targPr.x = r.x; + targPr.y = rPadded.y; + targPr.width = r.width; + targPr.height = _padding; + RenderTexture oldRT = RenderTexture.active; + RenderTexture.active = _destinationTexture; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + //bot margin + srcPr.x = srcPrTex.x; + srcPr.y = srcPrTex.y; + srcPr.width = srcPrTex.width; + srcPr.height = 1f / tex.height; + targPr.x = r.x; + targPr.y = r.y + r.height; + targPr.width = r.width; + targPr.height = _padding; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + + //left margin + srcPr.x = srcPrTex.x; + srcPr.y = srcPrTex.y; + srcPr.width = 1f / tex.width; + srcPr.height = srcPrTex.height; + targPr.x = rPadded.x; + targPr.y = r.y; + targPr.width = _padding; + targPr.height = r.height; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + //right margin + srcPr.x = srcPrTex.x + 1f - 1f / tex.width; + srcPr.y = srcPrTex.y; + srcPr.width = 1f / tex.width; + srcPr.height = srcPrTex.height; + targPr.x = r.x + r.width; + targPr.y = r.y; + targPr.width = _padding; + targPr.height = r.height; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + + //top left corner + srcPr.x = srcPrTex.x; + srcPr.y = srcPrTex.y + 1 - 1f / tex.height ; + srcPr.width = 1f / tex.width; + srcPr.height = 1f / tex.height; + targPr.x = rPadded.x; + targPr.y = rPadded.y; + targPr.width = _padding; + targPr.height = _padding; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + //top right corner + srcPr.x = srcPrTex.x + 1f - 1f / tex.width; + srcPr.y = srcPrTex.y + 1 - 1f / tex.height ; + srcPr.width = 1f / tex.width; + srcPr.height = 1f / tex.height; + targPr.x = r.x + r.width; + targPr.y = rPadded.y; + targPr.width = _padding; + targPr.height = _padding; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + //bot left corner + srcPr.x = srcPrTex.x; + srcPr.y = srcPrTex.y; + srcPr.width = 1f / tex.width; + srcPr.height = 1f / tex.height; + targPr.x = rPadded.x; + targPr.y = r.y + r.height; + targPr.width = _padding; + targPr.height = _padding; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + //bot right corner + srcPr.x = srcPrTex.x + 1f - 1f / tex.width; + srcPr.y = srcPrTex.y ; + srcPr.width = 1f / tex.width; + srcPr.height = 1f / tex.height; + targPr.x = r.x + r.width; + targPr.y = r.y + r.height; + targPr.width = _padding; + targPr.height = _padding; + Graphics.DrawTexture(targPr, tex, srcPr, 0, 0, 0, 0, mat); + + //now the texture + Graphics.DrawTexture(r, tex, srcPrTex, 0, 0, 0, 0, mat); + RenderTexture.active = oldRT; + tex.wrapMode = oldTexWrapMode; + } + + static void _printTexture(Texture2D t) { + if (t.width * t.height > 100) + { + Debug.Log("Not printing texture too large."); + return; + } + try { + Color32[] cols = t.GetPixels32(); + string s = ""; + for (int i = 0; i < t.height; i++) { + for (int j = 0; j < t.width; j++) { + s += cols[i * t.width + j] + ", "; + } + s += "\n"; + } + Debug.Log(s); + } catch (Exception ex) + { + Debug.Log("Could not print texture. texture may not be readable." + ex.Message + "\n" + ex.StackTrace.ToString()); + } + } + +} + +[ExecuteInEditMode] +public class MB3_AtlasPackerRenderTexture : MonoBehaviour { + MB_TextureCombinerRenderTexture fastRenderer; + bool _doRenderAtlas = false; + + public int width; + public int height; + public int padding; + public bool isNormalMap; + public bool fixOutOfBoundsUVs; + public bool considerNonTextureProperties; + public MB3_TextureCombinerNonTextureProperties resultMaterialTextureBlender; + public Rect[] rects; + public Texture2D tex1; + public List<MB_TexSet> textureSets; + public int indexOfTexSetToRender; + public ShaderTextureProperty texPropertyName; + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + + public Texture2D testTex; + public Material testMat; + + public Texture2D OnRenderAtlas(MB3_TextureCombiner combiner){ + fastRenderer = new MB_TextureCombinerRenderTexture(); + _doRenderAtlas = true; + Texture2D atlas = fastRenderer.DoRenderAtlas(this.gameObject,width,height,padding,rects,textureSets,indexOfTexSetToRender, texPropertyName, resultMaterialTextureBlender, isNormalMap, fixOutOfBoundsUVs, considerNonTextureProperties, combiner, LOG_LEVEL); + _doRenderAtlas = false; + return atlas; + } + + void OnRenderObject(){ + if (_doRenderAtlas){ + fastRenderer.OnRenderObject(); + _doRenderAtlas = false; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_AtlasPackerRenderTexture.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_AtlasPackerRenderTexture.cs.meta new file mode 100644 index 00000000..a4737e76 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_AtlasPackerRenderTexture.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 758d759e719048f43856e7eb7d8ff71b +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_ITextureCombinerPacker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_ITextureCombinerPacker.cs new file mode 100644 index 00000000..56d5dcdd --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_ITextureCombinerPacker.cs @@ -0,0 +1,171 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + internal interface MB_ITextureCombinerPacker + { + bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data); + + IEnumerator ConvertTexturesToReadableFormats(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL); + + AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL); + + IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL); + } + + internal abstract class MB3_TextureCombinerPackerRoot : MB_ITextureCombinerPacker + { + public abstract bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data); + + internal static void CreateTemporaryTexturesForAtlas(List<MB_TexSet> distinctMaterialTextures, MB3_TextureCombiner combiner, int propIdx, MB3_TextureCombinerPipeline.TexturePipelineData data) + { + for (int texSetIdx = 0; texSetIdx < data.distinctMaterialTextures.Count; texSetIdx++) + { + MB_TexSet txs = data.distinctMaterialTextures[texSetIdx]; + MeshBakerMaterialTexture matTex = txs.ts[propIdx]; + if (matTex.isNull) + { + //create a small 16 x 16 texture to use in the atlas + Color col = data.nonTexturePropertyBlender.GetColorForTemporaryTexture(txs.matsAndGOs.mats[0].mat, data.texPropertyNames[propIdx]); + txs.CreateColoredTexToReplaceNull(data.texPropertyNames[propIdx].name, propIdx, data._fixOutOfBoundsUVs, combiner, col, MB3_TextureCombiner.ShouldTextureBeLinear(data.texPropertyNames[propIdx])); + } + } + } + + internal static void SaveAtlasAndConfigureResultMaterial(MB3_TextureCombinerPipeline.TexturePipelineData data, MB2_EditorMethodsInterface textureEditorMethods, Texture2D atlas, ShaderTextureProperty property, int propIdx) + { + bool doAnySrcMatsHaveProperty = MB3_TextureCombinerPipeline._DoAnySrcMatsHaveProperty(propIdx, data.allTexturesAreNullAndSameColor); + if (data._saveAtlasesAsAssets && textureEditorMethods != null) + { + textureEditorMethods.SaveAtlasToAssetDatabase(atlas, property, propIdx, doAnySrcMatsHaveProperty, data.resultMaterial); + } + else + { + if (doAnySrcMatsHaveProperty) + { + data.resultMaterial.SetTexture(property.name, atlas); + } + } + + if (doAnySrcMatsHaveProperty) + { + data.resultMaterial.SetTextureOffset(property.name, Vector2.zero); + data.resultMaterial.SetTextureScale(property.name, Vector2.one); + } + } + + public static AtlasPackingResult[] CalculateAtlasRectanglesStatic(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + List<Vector2> imageSizes = new List<Vector2>(); + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + imageSizes.Add(new Vector2(data.distinctMaterialTextures[i].idealWidth, data.distinctMaterialTextures[i].idealHeight)); + } + + MB2_TexturePacker tp = MB3_TextureCombinerPipeline.CreateTexturePacker(data._packingAlgorithm); + tp.atlasMustBePowerOfTwo = data._meshBakerTexturePackerForcePowerOfTwo; + List<AtlasPadding> paddings = new List<AtlasPadding>(); + for (int i = 0; i < imageSizes.Count; i++) + { + AtlasPadding padding = new AtlasPadding(); + padding.topBottom = data._atlasPadding; + padding.leftRight = data._atlasPadding; + if (data._packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Horizontal) padding.leftRight = 0; + if (data._packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Vertical) padding.topBottom = 0; + paddings.Add(padding); + } + + return tp.GetRects(imageSizes, paddings, data._maxAtlasWidth, data._maxAtlasHeight, doMultiAtlas); + } + + public static void MakeProceduralTexturesReadable(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + //Debug.LogError("TODO this should be done as close to textures being used as possible due to memory issues."); + //make procedural materials readable + /* + for (int i = 0; i < combiner._proceduralMaterials.Count; i++) + { + if (!combiner._proceduralMaterials[i].proceduralMat.isReadable) + { + combiner._proceduralMaterials[i].originalIsReadableVal = combiner._proceduralMaterials[i].proceduralMat.isReadable; + combiner._proceduralMaterials[i].proceduralMat.isReadable = true; + //textureEditorMethods.AddProceduralMaterialFormat(_proceduralMaterials[i].proceduralMat); + combiner._proceduralMaterials[i].proceduralMat.RebuildTexturesImmediately(); + } + } + //convert procedural textures to RAW format + + for (int i = 0; i < distinctMaterialTextures.Count; i++) + { + for (int j = 0; j < texPropertyNames.Count; j++) + { + if (distinctMaterialTextures[i].ts[j].IsProceduralTexture()) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Converting procedural texture to Textur2D:" + distinctMaterialTextures[i].ts[j].GetTexName() + " property:" + texPropertyNames[i]); + Texture2D txx = distinctMaterialTextures[i].ts[j].ConvertProceduralToTexture2D(_temporaryTextures); + distinctMaterialTextures[i].ts[j].t = txx; + } + } + } + */ + } + + public virtual IEnumerator ConvertTexturesToReadableFormats(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + //MakeProceduralTexturesReadable(progressInfo, result, data, combiner, textureEditorMethods, LOG_LEVEL); + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + for (int j = 0; j < data.texPropertyNames.Count; j++) + { + MeshBakerMaterialTexture ts = data.distinctMaterialTextures[i].ts[j]; + if (!ts.isNull) + { + if (textureEditorMethods != null) + { + Texture tx = ts.GetTexture2D(); + TextureFormat format = TextureFormat.RGBA32; + if (progressInfo != null) progressInfo(String.Format("Convert texture {0} to readable format ", tx), .5f); + textureEditorMethods.ConvertTextureFormat_DefaultPlatform((Texture2D)tx, format, data.texPropertyNames[j].isNormalMap); + } + } + } + } + yield break; + } + + public virtual AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + return CalculateAtlasRectanglesStatic(data, doMultiAtlas, LOG_LEVEL); + } + + public abstract IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL); + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_ITextureCombinerPacker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_ITextureCombinerPacker.cs.meta new file mode 100644 index 00000000..57d5d924 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_ITextureCombinerPacker.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: af90b245557210a4ca9c821c717faafe +timeCreated: 1522041810 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombiner.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombiner.cs new file mode 100644 index 00000000..b18c44b3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombiner.cs @@ -0,0 +1,812 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.IO; +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +/* + +Notes on Normal Maps in Unity3d + +Unity stores normal maps in a non standard format for some platforms. Think of the standard format as being english, unity's as being +french. The raw image files in the project folder are in english, the AssetImporter converts them to french. Texture2D.GetPixels returns +french. This is a problem when we build an atlas from Texture2D objects and save the result in the project folder. +Unity wants us to flag this file as a normal map but if we do it is effectively translated twice. + +Solutions: + + 1) convert the normal map to english just before saving to project. Then set the normal flag and let the Importer do translation. + This was rejected because Unity doesn't translate for all platforms. I would need to check with every version of Unity which platforms + use which format. + + 2) Uncheck "normal map" on importer before bake and re-check after bake. This is the solution I am using. + +*/ +namespace DigitalOpus.MB.Core +{ + + [System.Serializable] + public class ShaderTextureProperty + { + public string name; + public bool isNormalMap; + + /// <summary> + /// If we find a texture property in the result material we don't know if it is normal or not + /// We can try to look at how it is used in the source materials. If the majority of those are normal + /// then it is normal. + /// </summary> + [HideInInspector] + public bool isNormalDontKnow = false; + + public ShaderTextureProperty(string n, + bool norm) + { + name = n; + isNormalMap = norm; + isNormalDontKnow = false; + } + + public ShaderTextureProperty(string n, + bool norm, + bool isNormalDontKnow) + { + name = n; + isNormalMap = norm; + this.isNormalDontKnow = isNormalDontKnow; + } + + public override bool Equals(object obj) + { + if (!(obj is ShaderTextureProperty)) return false; + ShaderTextureProperty b = (ShaderTextureProperty)obj; + if (!name.Equals(b.name)) return false; + if (isNormalMap != b.isNormalMap) return false; + return true; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static string[] GetNames(List<ShaderTextureProperty> props) + { + string[] ss = new string[props.Count]; + for (int i = 0; i < ss.Length; i++) + { + ss[i] = props[i].name; + } + return ss; + } + } + + [System.Serializable] + public class MB3_TextureCombiner + { + public class CreateAtlasesCoroutineResult + { + public bool success = true; + public bool isFinished = false; + } + + internal class TemporaryTexture + { + internal string property; + internal Texture2D texture; + + public TemporaryTexture(string prop, Texture2D tex) + { + property = prop; + texture = tex; + } + } + + /** + Same as CombineTexturesIntoAtlases except this version runs as a coroutine to spread the load of baking textures at runtime across several frames + */ + + public class CombineTexturesIntoAtlasesCoroutineResult + { + public bool success = true; + public bool isFinished = false; + } + + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + + [SerializeField] + protected MB2_TextureBakeResults _textureBakeResults; + public MB2_TextureBakeResults textureBakeResults + { + get { return _textureBakeResults; } + set { _textureBakeResults = value; } + } + + [SerializeField] + protected int _atlasPadding = 1; + public int atlasPadding + { + get { return _atlasPadding; } + set { _atlasPadding = value; } + } + + [SerializeField] + protected int _maxAtlasSize = 1; + public int maxAtlasSize + { + get { return _maxAtlasSize; } + set { _maxAtlasSize = value; } + } + + [SerializeField] + protected int _maxAtlasWidthOverride = 4096; + public virtual int maxAtlasWidthOverride + { + get { return _maxAtlasWidthOverride; } + set { _maxAtlasWidthOverride = value; } + } + + [SerializeField] + protected int _maxAtlasHeightOverride = 4096; + public virtual int maxAtlasHeightOverride + { + get { return _maxAtlasHeightOverride; } + set { _maxAtlasHeightOverride = value; } + } + + [SerializeField] + protected bool _useMaxAtlasWidthOverride = false; + public virtual bool useMaxAtlasWidthOverride + { + get { return _useMaxAtlasWidthOverride; } + set { _useMaxAtlasWidthOverride = value; } + } + + [SerializeField] + protected bool _useMaxAtlasHeightOverride = false; + public virtual bool useMaxAtlasHeightOverride + { + get { return _useMaxAtlasHeightOverride; } + set { _useMaxAtlasHeightOverride = value; } + } + + [SerializeField] + protected bool _resizePowerOfTwoTextures = false; + public bool resizePowerOfTwoTextures + { + get { return _resizePowerOfTwoTextures; } + set { _resizePowerOfTwoTextures = value; } + } + + [SerializeField] + protected bool _fixOutOfBoundsUVs = false; + public bool fixOutOfBoundsUVs + { + get { return _fixOutOfBoundsUVs; } + set { _fixOutOfBoundsUVs = value; } + } + + [SerializeField] + protected int _layerTexturePackerFastMesh = -1; + public int layerTexturePackerFastMesh + { + get { return _layerTexturePackerFastMesh; } + set { _layerTexturePackerFastMesh = value; } + } + + [SerializeField] + protected int _maxTilingBakeSize = 1024; + public int maxTilingBakeSize + { + get { return _maxTilingBakeSize; } + set { _maxTilingBakeSize = value; } + } + + [SerializeField] + protected bool _saveAtlasesAsAssets = false; + public bool saveAtlasesAsAssets + { + get { return _saveAtlasesAsAssets; } + set { _saveAtlasesAsAssets = value; } + } + + [SerializeField] + protected MB2_TextureBakeResults.ResultType _resultType; + + public MB2_TextureBakeResults.ResultType resultType + { + get { return _resultType; } + set { _resultType = value; } + } + + [SerializeField] + protected MB2_PackingAlgorithmEnum _packingAlgorithm = MB2_PackingAlgorithmEnum.UnitysPackTextures; + public MB2_PackingAlgorithmEnum packingAlgorithm + { + get { return _packingAlgorithm; } + set { _packingAlgorithm = value; } + } + + [SerializeField] + protected bool _meshBakerTexturePackerForcePowerOfTwo = true; + public bool meshBakerTexturePackerForcePowerOfTwo + { + get { return _meshBakerTexturePackerForcePowerOfTwo; } + set { _meshBakerTexturePackerForcePowerOfTwo = value; } + } + + [SerializeField] + protected List<ShaderTextureProperty> _customShaderPropNames = new List<ShaderTextureProperty>(); + public List<ShaderTextureProperty> customShaderPropNames + { + get { return _customShaderPropNames; } + set { _customShaderPropNames = value; } + } + + [SerializeField] + protected bool _normalizeTexelDensity = false; + + [SerializeField] + protected bool _considerNonTextureProperties = false; + public bool considerNonTextureProperties + { + get { return _considerNonTextureProperties; } + set { _considerNonTextureProperties = value; } + } + + // Don't Serialize this. It should only be used in specific circumstances. + protected bool _doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize = false; + public bool doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize + { + get { return _doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize; } + set { _doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize = value; } + } + + //copies of textures created for the the atlas baking that should be destroyed in finalize + private List<TemporaryTexture> _temporaryTextures = new List<TemporaryTexture>(); + + //so we can undo read flag on procedural materials in finalize + //internal List<ProceduralMaterialInfo> _proceduralMaterials = new List<ProceduralMaterialInfo>(); + + //This runs a coroutine without pausing it is used to build the textures from the editor + public static bool _RunCorutineWithoutPauseIsRunning = false; + public static void RunCorutineWithoutPause(IEnumerator cor, int recursionDepth) + { + if (recursionDepth == 0) + { + _RunCorutineWithoutPauseIsRunning = true; + } + + if (recursionDepth > 20) + { + Debug.LogError("Recursion Depth Exceeded."); + return; + } + + while (cor.MoveNext()) + { + object retObj = cor.Current; + if (retObj is YieldInstruction) + { + //do nothing + } + else if (retObj == null) + { + //do nothing + } + else if (retObj is IEnumerator) + { + RunCorutineWithoutPause((IEnumerator)cor.Current, recursionDepth + 1); + } + } + if (recursionDepth == 0) + { + _RunCorutineWithoutPauseIsRunning = false; + } + } + + /**<summary>Combines meshes and generates texture atlases. NOTE running coroutines at runtime does not work in Unity 4</summary> + * <param name="progressInfo">A delegate function that will be called to report progress.</param> + * <param name="textureEditorMethods">If called from the editor should be an instance of MB2_EditorMethods. If called at runtime should be null.</param> + * <remarks>Combines meshes and generates texture atlases</remarks> */ + public bool CombineTexturesIntoAtlases(ProgressUpdateDelegate progressInfo, MB_AtlasesAndRects resultAtlasesAndRects, Material resultMaterial, List<GameObject> objsToMesh, List<Material> allowedMaterialsFilter, MB2_EditorMethodsInterface textureEditorMethods = null, List<AtlasPackingResult> packingResults = null, bool onlyPackRects = false, bool splitAtlasWhenPackingIfTooBig = false) + { + CombineTexturesIntoAtlasesCoroutineResult result = new CombineTexturesIntoAtlasesCoroutineResult(); + RunCorutineWithoutPause(_CombineTexturesIntoAtlases(progressInfo, result, resultAtlasesAndRects, resultMaterial, objsToMesh, allowedMaterialsFilter, textureEditorMethods, packingResults, onlyPackRects, splitAtlasWhenPackingIfTooBig), 0); + if (result.success == false) Debug.LogError("Failed to generate atlases."); + return result.success; + } + + //float _maxTimePerFrameForCoroutine; + public IEnumerator CombineTexturesIntoAtlasesCoroutine(ProgressUpdateDelegate progressInfo, MB_AtlasesAndRects resultAtlasesAndRects, Material resultMaterial, List<GameObject> objsToMesh, List<Material> allowedMaterialsFilter, MB2_EditorMethodsInterface textureEditorMethods = null, CombineTexturesIntoAtlasesCoroutineResult coroutineResult = null, float maxTimePerFrame = .01f, List<AtlasPackingResult> packingResults = null, bool onlyPackRects = false, bool splitAtlasWhenPackingIfTooBig = false) + { + if (!_RunCorutineWithoutPauseIsRunning && (MBVersion.GetMajorVersion() < 5 || (MBVersion.GetMajorVersion() == 5 && MBVersion.GetMinorVersion() < 3))) + { + Debug.LogError("Running the texture combiner as a coroutine only works in Unity 5.3 and higher"); + yield return null; + } + coroutineResult.success = true; + coroutineResult.isFinished = false; + if (maxTimePerFrame <= 0f) + { + Debug.LogError("maxTimePerFrame must be a value greater than zero"); + coroutineResult.isFinished = true; + yield break; + } + //_maxTimePerFrameForCoroutine = maxTimePerFrame; + yield return _CombineTexturesIntoAtlases(progressInfo, coroutineResult, resultAtlasesAndRects, resultMaterial, objsToMesh, allowedMaterialsFilter, textureEditorMethods, packingResults, onlyPackRects, splitAtlasWhenPackingIfTooBig); + coroutineResult.isFinished = true; + yield break; + } + + IEnumerator _CombineTexturesIntoAtlases(ProgressUpdateDelegate progressInfo, CombineTexturesIntoAtlasesCoroutineResult result, MB_AtlasesAndRects resultAtlasesAndRects, Material resultMaterial, List<GameObject> objsToMesh, List<Material> allowedMaterialsFilter, MB2_EditorMethodsInterface textureEditorMethods, List<AtlasPackingResult> atlasPackingResult, bool onlyPackRects, bool splitAtlasWhenPackingIfTooBig) + { + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + try + { + _temporaryTextures.Clear(); + MeshBakerMaterialTexture.readyToBuildAtlases = false; + + if (textureEditorMethods != null) + { + textureEditorMethods.Clear(); + textureEditorMethods.OnPreTextureBake(); + } + + if (splitAtlasWhenPackingIfTooBig == true && + onlyPackRects == false) + { + Debug.LogError("Can only use 'splitAtlasWhenPackingIfTooLarge' with 'onlyPackRects'"); + result.success = false; + yield break; + } + + if (objsToMesh == null || objsToMesh.Count == 0) + { + Debug.LogError("No meshes to combine. Please assign some meshes to combine."); + result.success = false; + yield break; + } + + if (_atlasPadding < 0) + { + Debug.LogError("Atlas padding must be zero or greater."); + result.success = false; + yield break; + } + + if (_maxTilingBakeSize < 2 || _maxTilingBakeSize > 4096) + { + Debug.LogError("Invalid value for max tiling bake size."); + result.success = false; + yield break; + } + + for (int i = 0; i < objsToMesh.Count; i++) + { + Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); + for (int j = 0; j < ms.Length; j++) + { + Material m = ms[j]; + if (m == null) + { + Debug.LogError("Game object " + objsToMesh[i] + " has a null material"); + result.success = false; + yield break; + } + + } + } + + if (progressInfo != null) + progressInfo("Collecting textures for " + objsToMesh.Count + " meshes.", .01f); + + MB3_TextureCombinerPipeline.TexturePipelineData data = LoadPipelineData(resultMaterial, new List<ShaderTextureProperty>(), objsToMesh, allowedMaterialsFilter, new List<MB_TexSet>()); + if (!MB3_TextureCombinerPipeline._CollectPropertyNames(data.texPropertyNames, data._customShaderPropNames, data.resultMaterial, LOG_LEVEL)) + { + result.success = false; + yield break; + } + + if (_fixOutOfBoundsUVs && (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Horizontal || + _packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Vertical)) + { + if (LOG_LEVEL >= MB2_LogLevel.info) + { + Debug.LogWarning("'Consider Mesh UVs' is enabled but packing algorithm is MeshBakerTexturePacker_Horizontal or MeshBakerTexturePacker_Vertical. It is recommended to use these packers without using 'Consider Mesh UVs'"); + } + } + + data.nonTexturePropertyBlender.LoadTextureBlendersIfNeeded(data.resultMaterial); + + if (onlyPackRects) + { + yield return __RunTexturePackerOnly(result, resultAtlasesAndRects, data, splitAtlasWhenPackingIfTooBig, textureEditorMethods, atlasPackingResult); + } + else + { + yield return __CombineTexturesIntoAtlases(progressInfo, result, resultAtlasesAndRects, data, textureEditorMethods); + } + } + /* + catch (MissingReferenceException mrex){ + Debug.LogError("Creating atlases failed a MissingReferenceException was thrown. This is normally only happens when trying to create very large atlases and Unity is running out of Memory. Try changing the 'Texture Packer' to a different option, it may work with an alternate packer. This error is sometimes intermittant. Try baking again."); + Debug.LogError(mrex); + } catch (Exception ex){ + Debug.LogError(ex); + } + */ + finally + { + + _destroyAllTemporaryTextures(); + _restoreProceduralMaterials(); + if (textureEditorMethods != null) + { + textureEditorMethods.RestoreReadFlagsAndFormats(progressInfo); + textureEditorMethods.OnPostTextureBake(); + } + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("===== Done creating atlases for " + resultMaterial + " Total time to create atlases " + sw.Elapsed.ToString()); + } + } + + MB3_TextureCombinerPipeline.TexturePipelineData LoadPipelineData(Material resultMaterial, + List<ShaderTextureProperty> texPropertyNames, + List<GameObject> objsToMesh, + List<Material> allowedMaterialsFilter, + List<MB_TexSet> distinctMaterialTextures) + { + MB3_TextureCombinerPipeline.TexturePipelineData data = new MB3_TextureCombinerPipeline.TexturePipelineData(); + data._textureBakeResults = _textureBakeResults; + data._atlasPadding = _atlasPadding; + if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Vertical && _useMaxAtlasHeightOverride) + { + data._maxAtlasHeight = _maxAtlasHeightOverride; + data._useMaxAtlasHeightOverride = true; + } + else + { + data._maxAtlasHeight = _maxAtlasSize; + } + + if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Horizontal && _useMaxAtlasWidthOverride) + { + data._maxAtlasWidth = _maxAtlasWidthOverride; + data._useMaxAtlasWidthOverride = true; + } + else + { + data._maxAtlasWidth = _maxAtlasSize; + } + + data._saveAtlasesAsAssets = _saveAtlasesAsAssets; + data.resultType = _resultType; + data._resizePowerOfTwoTextures = _resizePowerOfTwoTextures; + data._fixOutOfBoundsUVs = _fixOutOfBoundsUVs; + data._maxTilingBakeSize = _maxTilingBakeSize; + data._packingAlgorithm = _packingAlgorithm; + data._layerTexturePackerFastV2 = _layerTexturePackerFastMesh; + data._meshBakerTexturePackerForcePowerOfTwo = _meshBakerTexturePackerForcePowerOfTwo; + data._customShaderPropNames = _customShaderPropNames; + data._normalizeTexelDensity = _normalizeTexelDensity; + data._considerNonTextureProperties = _considerNonTextureProperties; + data.doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize = _doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize; + data.nonTexturePropertyBlender = new MB3_TextureCombinerNonTextureProperties(LOG_LEVEL, _considerNonTextureProperties); + data.resultMaterial = resultMaterial; + data.distinctMaterialTextures = distinctMaterialTextures; + data.allObjsToMesh = objsToMesh; + data.allowedMaterialsFilter = allowedMaterialsFilter; + data.texPropertyNames = texPropertyNames; + data.colorSpace = MBVersion.GetProjectColorSpace(); + return data; + } + + //texPropertyNames is the list of texture properties in the resultMaterial + //allowedMaterialsFilter is a list of materials. Objects without any of these materials will be ignored. + // this is used by the multiple materials filter + //textureEditorMethods encapsulates editor only functionality such as saving assets and tracking texture assets whos format was changed. Is null if using at runtime. + IEnumerator __CombineTexturesIntoAtlases(ProgressUpdateDelegate progressInfo, CombineTexturesIntoAtlasesCoroutineResult result, MB_AtlasesAndRects resultAtlasesAndRects, MB3_TextureCombinerPipeline.TexturePipelineData data, MB2_EditorMethodsInterface textureEditorMethods) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("__CombineTexturesIntoAtlases texture properties in shader:" + data.texPropertyNames.Count + " objsToMesh:" + data.allObjsToMesh.Count + " _fixOutOfBoundsUVs:" + data._fixOutOfBoundsUVs); + + if (progressInfo != null) progressInfo("Collecting textures ", .01f); + + MB3_TextureCombinerPipeline pipeline = new MB3_TextureCombinerPipeline(); + /* + each atlas (maintex, bump, spec etc...) will have distinctMaterialTextures.Count images in it. + each distinctMaterialTextures record is a set of textures, one for each atlas. And a list of materials + that use that distinct set of textures. + */ + List<GameObject> usedObjsToMesh = new List<GameObject>(); + yield return pipeline.__Step1_CollectDistinctMatTexturesAndUsedObjects(progressInfo, result, data, this, textureEditorMethods, usedObjsToMesh, LOG_LEVEL); + if (!result.success) + { + yield break; + } + + //Textures in each material (_mainTex, Bump, Spec ect...) must be same size + //Calculate the best sized to use. Takes into account tiling + //if only one texture in atlas re-uses original sizes + yield return pipeline.CalculateIdealSizesForTexturesInAtlasAndPadding(progressInfo, result, data, this, textureEditorMethods, LOG_LEVEL); + if (!result.success) + { + yield break; + } + + //buildAndSaveAtlases + StringBuilder report = pipeline.GenerateReport(data); + MB_ITextureCombinerPacker texturePaker = pipeline.CreatePacker(data.OnlyOneTextureInAtlasReuseTextures(), data._packingAlgorithm); + if (!texturePaker.Validate(data)) + { + result.success = false; + yield break; + } + + yield return texturePaker.ConvertTexturesToReadableFormats(progressInfo, result, data, this, textureEditorMethods, LOG_LEVEL); + if (!result.success) + { + yield break; + } + + AtlasPackingResult[] uvRects = texturePaker.CalculateAtlasRectangles(data, false, LOG_LEVEL); + Debug.Assert(uvRects.Length == 1, "Error, there should not be more than one packing here."); + yield return pipeline.__Step3_BuildAndSaveAtlasesAndStoreResults(result, progressInfo, data, this, texturePaker, uvRects[0], textureEditorMethods, resultAtlasesAndRects, report, LOG_LEVEL); + } + + IEnumerator __RunTexturePackerOnly(CombineTexturesIntoAtlasesCoroutineResult result, MB_AtlasesAndRects resultAtlasesAndRects, MB3_TextureCombinerPipeline.TexturePipelineData data, bool splitAtlasWhenPackingIfTooBig, MB2_EditorMethodsInterface textureEditorMethods, List<AtlasPackingResult> packingResult) + { + MB3_TextureCombinerPipeline pipeline = new MB3_TextureCombinerPipeline(); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("__RunTexturePacker texture properties in shader:" + data.texPropertyNames.Count + " objsToMesh:" + data.allObjsToMesh.Count + " _fixOutOfBoundsUVs:" + data._fixOutOfBoundsUVs); + List<GameObject> usedObjsToMesh = new List<GameObject>(); + yield return pipeline.__Step1_CollectDistinctMatTexturesAndUsedObjects(null, result, data, this, textureEditorMethods, usedObjsToMesh, LOG_LEVEL); + if (!result.success) + { + yield break; + } + + data.allTexturesAreNullAndSameColor = new MB3_TextureCombinerPipeline.CreateAtlasForProperty[data.texPropertyNames.Count]; + yield return pipeline.CalculateIdealSizesForTexturesInAtlasAndPadding(null, result, data, this, textureEditorMethods, LOG_LEVEL); + if (!result.success) + { + yield break; + } + + MB_ITextureCombinerPacker texturePaker = pipeline.CreatePacker(data.OnlyOneTextureInAtlasReuseTextures(), data._packingAlgorithm); + // run the texture packer only + AtlasPackingResult[] aprs = pipeline.RunTexturePackerOnly(data, splitAtlasWhenPackingIfTooBig, resultAtlasesAndRects, texturePaker, LOG_LEVEL); + for (int i = 0; i < aprs.Length; i++) + { + packingResult.Add(aprs[i]); + } + } + + internal int _getNumTemporaryTextures() + { + return _temporaryTextures.Count; + } + + //used to track temporary textures that were created so they can be destroyed + public Texture2D _createTemporaryTexture(string propertyName, int w, int h, TextureFormat texFormat, bool mipMaps, bool linear) + { + Texture2D t = new Texture2D(w, h, texFormat, mipMaps, linear); + t.name = string.Format("tmp{0}_{1}x{2}", _temporaryTextures.Count, w, h); + MB_Utility.setSolidColor(t, Color.clear); + TemporaryTexture txx = new TemporaryTexture(propertyName, t); + _temporaryTextures.Add(txx); + return t; + } + + internal void AddTemporaryTexture(TemporaryTexture tt) + { + _temporaryTextures.Add(tt); + } + + internal Texture2D _createTextureCopy(string propertyName, Texture2D t) + { + Texture2D tx = MB_Utility.createTextureCopy(t); + tx.name = string.Format("tmpCopy{0}_{1}x{2}", _temporaryTextures.Count, tx.width, tx.height); + TemporaryTexture txx = new TemporaryTexture(propertyName, tx); + _temporaryTextures.Add(txx); + return tx; + } + + internal Texture2D _resizeTexture(string propertyName, Texture2D t, int w, int h) + { + Texture2D tx = MB_Utility.resampleTexture(t, w, h); + tx.name = string.Format("tmpResampled{0}_{1}x{2}", _temporaryTextures.Count, w, h); + TemporaryTexture txx = new TemporaryTexture(propertyName, tx); + _temporaryTextures.Add(txx); + return tx; + } + + internal void _destroyAllTemporaryTextures() + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Destroying " + _temporaryTextures.Count + " temporary textures"); + for (int i = 0; i < _temporaryTextures.Count; i++) + { + MB_Utility.Destroy(_temporaryTextures[i].texture); + } + _temporaryTextures.Clear(); + } + + internal void _destroyTemporaryTextures(string propertyName) + { + int numDestroyed = 0; + for (int i = _temporaryTextures.Count - 1; i >= 0; i--) + { + if (_temporaryTextures[i].property.Equals(propertyName)) + { + numDestroyed++; + MB_Utility.Destroy(_temporaryTextures[i].texture); + _temporaryTextures.RemoveAt(i); + } + } + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Destroying " + numDestroyed + " temporary textures " + propertyName + " num remaining " + _temporaryTextures.Count); + } + + /* + public void _addProceduralMaterial(ProceduralMaterial pm) + { + ProceduralMaterialInfo pmi = new ProceduralMaterialInfo(); + pm.isReadable = pm.isReadable; + pmi.proceduralMat = pm; + _proceduralMaterials.Add(pmi); + } + */ + + public void _restoreProceduralMaterials() + { + /* + for (int i = 0; i < _proceduralMaterials.Count; i++) + { + ProceduralMaterialInfo pmi = _proceduralMaterials[i]; + pmi.proceduralMat.isReadable = pmi.originalIsReadableVal; + pmi.proceduralMat.RebuildTexturesImmediately(); + } + _proceduralMaterials.Clear(); + */ + } + + public void SuggestTreatment(List<GameObject> objsToMesh, Material[] resultMaterials, List<ShaderTextureProperty> _customShaderPropNames) + { + this._customShaderPropNames = _customShaderPropNames; + StringBuilder sb = new StringBuilder(); + Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache = new Dictionary<int, MB_Utility.MeshAnalysisResult[]>(); //cache results + for (int i = 0; i < objsToMesh.Count; i++) + { + GameObject obj = objsToMesh[i]; + if (obj == null) continue; + Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); + if (ms.Length > 1) + { // and each material is not mapped to its own layer + sb.AppendFormat("\nObject {0} uses {1} materials. Possible treatments:\n", objsToMesh[i].name, ms.Length); + sb.AppendFormat(" 1) Collapse the submeshes together into one submesh in the combined mesh. Each of the original submesh materials will map to a different UV rectangle in the atlas(es) used by the combined material.\n"); + sb.AppendFormat(" 2) Use the multiple materials feature to map submeshes in the source mesh to submeshes in the combined mesh.\n"); + } + Mesh m = MB_Utility.GetMesh(obj); + + MB_Utility.MeshAnalysisResult[] mar; + if (!meshAnalysisResultsCache.TryGetValue(m.GetInstanceID(), out mar)) + { + mar = new MB_Utility.MeshAnalysisResult[m.subMeshCount]; + MB_Utility.doSubmeshesShareVertsOrTris(m, ref mar[0]); + for (int j = 0; j < m.subMeshCount; j++) + { + MB_Utility.hasOutOfBoundsUVs(m, ref mar[j], j); + //DRect outOfBoundsUVRect = new DRect(mar[j].uvRect); + mar[j].hasOverlappingSubmeshTris = mar[0].hasOverlappingSubmeshTris; + mar[j].hasOverlappingSubmeshVerts = mar[0].hasOverlappingSubmeshVerts; + } + meshAnalysisResultsCache.Add(m.GetInstanceID(), mar); + } + + for (int j = 0; j < ms.Length; j++) + { + if (mar[j].hasOutOfBoundsUVs) + { + DRect r = new DRect(mar[j].uvRect); + sb.AppendFormat("\nObject {0} submesh={1} material={2} uses UVs outside the range 0,0 .. 1,1 to create tiling that tiles the box {3},{4} .. {5},{6}. This is a problem because the UVs outside the 0,0 .. 1,1 " + + "rectangle will pick up neighboring textures in the atlas. Possible Treatments:\n", obj, j, ms[j], r.x.ToString("G4"), r.y.ToString("G4"), (r.x + r.width).ToString("G4"), (r.y + r.height).ToString("G4")); + sb.AppendFormat(" 1) Ignore the problem. The tiling may not affect result significantly.\n"); + sb.AppendFormat(" 2) Use the 'fix out of bounds UVs' feature to bake the tiling and scale the UVs to fit in the 0,0 .. 1,1 rectangle.\n"); + sb.AppendFormat(" 3) Use the Multiple Materials feature to map the material on this submesh to its own submesh in the combined mesh. No other materials should map to this submesh. This will result in only one texture in the atlas(es) and the UVs should tile correctly.\n"); + sb.AppendFormat(" 4) Combine only meshes that use the same (or subset of) the set of materials on this mesh. The original material(s) can be applied to the result\n"); + } + } + if (mar[0].hasOverlappingSubmeshVerts) + { + sb.AppendFormat("\nObject {0} has submeshes that share vertices. This is a problem because each vertex can have only one UV coordinate and may be required to map to different positions in the various atlases that are generated. Possible treatments:\n", objsToMesh[i]); + sb.AppendFormat(" 1) Ignore the problem. The vertices may not affect the result.\n"); + sb.AppendFormat(" 2) Use the Multiple Materials feature to map the submeshs that overlap to their own submeshs in the combined mesh. No other materials should map to this submesh. This will result in only one texture in the atlas(es) and the UVs should tile correctly.\n"); + sb.AppendFormat(" 3) Combine only meshes that use the same (or subset of) the set of materials on this mesh. The original material(s) can be applied to the result\n"); + } + } + Dictionary<Material, List<GameObject>> m2gos = new Dictionary<Material, List<GameObject>>(); + for (int i = 0; i < objsToMesh.Count; i++) + { + if (objsToMesh[i] != null) + { + Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]); + for (int j = 0; j < ms.Length; j++) + { + if (ms[j] != null) + { + List<GameObject> lgo; + if (!m2gos.TryGetValue(ms[j], out lgo)) + { + lgo = new List<GameObject>(); + m2gos.Add(ms[j], lgo); + } + if (!lgo.Contains(objsToMesh[i])) lgo.Add(objsToMesh[i]); + } + } + } + } + + for (int i = 0; i < resultMaterials.Length; i++) + { + string resultMatShaderName = resultMaterials[i] != null ? "None" : resultMaterials[i].shader.name; + MB3_TextureCombinerPipeline.TexturePipelineData data = LoadPipelineData(resultMaterials[i], new List<ShaderTextureProperty>(), objsToMesh, new List<Material>(), new List<MB_TexSet>()); + MB3_TextureCombinerPipeline._CollectPropertyNames(data, LOG_LEVEL); + foreach (Material m in m2gos.Keys) + { + for (int j = 0; j < data.texPropertyNames.Count; j++) + { + if (m.HasProperty(data.texPropertyNames[j].name)) + { + Texture txx = MB3_TextureCombinerPipeline.GetTextureConsideringStandardShaderKeywords(resultMatShaderName, m, data.texPropertyNames[j].name); + if (txx != null) + { + Vector2 o = m.GetTextureOffset(data.texPropertyNames[j].name); + Vector3 s = m.GetTextureScale(data.texPropertyNames[j].name); + if (o.x < 0f || o.x + s.x > 1f || + o.y < 0f || o.y + s.y > 1f) + { + sb.AppendFormat("\nMaterial {0} used by objects {1} uses texture {2} that is tiled (scale={3} offset={4}). If there is more than one texture in the atlas " + + " then Mesh Baker will bake the tiling into the atlas. If the baked tiling is large then quality can be lost. Possible treatments:\n", m, PrintList(m2gos[m]), txx, s, o); + sb.AppendFormat(" 1) Use the baked tiling.\n"); + sb.AppendFormat(" 2) Use the Multiple Materials feature to map the material on this object/submesh to its own submesh in the combined mesh. No other materials should map to this submesh. The original material can be applied to this submesh.\n"); + sb.AppendFormat(" 3) Combine only meshes that use the same (or subset of) the set of textures on this mesh. The original material can be applied to the result.\n"); + } + } + } + } + } + } + string outstr = ""; + if (sb.Length == 0) + { + outstr = "====== No problems detected. These meshes should combine well ====\n If there are problems with the combined meshes please report the problem to digitalOpus.ca so we can improve Mesh Baker."; + } + else + { + outstr = "====== There are possible problems with these meshes that may prevent them from combining well. TREATMENT SUGGESTIONS (copy and paste to text editor if too big) =====\n" + sb.ToString(); + } + Debug.Log(outstr); + } + + public static bool ShouldTextureBeLinear(ShaderTextureProperty shaderTextureProperty) + { + if (shaderTextureProperty.isNormalMap) return true; + else return false; + } + + string PrintList(List<GameObject> gos) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < gos.Count; i++) + { + sb.Append(gos[i] + ","); + } + return sb.ToString(); + } + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombiner.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombiner.cs.meta new file mode 100644 index 00000000..df0f8acd --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombiner.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 81d9b2ea17fa3f843aff99bdb21ba244 +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerAtlasRect.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerAtlasRect.cs new file mode 100644 index 00000000..0e439ab6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerAtlasRect.cs @@ -0,0 +1,860 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System; +using System.Text; + +namespace DigitalOpus.MB.Core +{ + + /* + Like a material but also stores its tiling info since the same texture + with different tiling may need to be baked to a separate spot in the atlas + note that it is sometimes possible for textures with different tiling to share an atlas rectangle + To accomplish this need to store: + uvTiling per TexSet (can be set to 0,0,1,1 by pushing tiling down into material tiling) + matTiling per MeshBakerMaterialTexture (this is the total tiling baked into the atlas) + matSubrectInFullSamplingRect per material (a MeshBakerMaterialTexture can be used by multiple materials. This is the subrect in the atlas) + Normally UVTilings is applied first then material tiling after. This is difficult for us to use when baking meshes. It is better to apply material + tiling first then UV Tiling. There is a transform for modifying the material tiling to handle this. + once the material tiling is applied first then the uvTiling can be pushed down into the material tiling. + + Also note that this can wrap a procedural texture. The procedural texture is converted to a Texture2D in Step2 NOT BEFORE. This is important so that can + build packing layout quickly. + + Should always check if texture is null using 'isNull' function since Texture2D could be null but ProceduralTexture not + Should not call GetTexture2D before procedural textures are created + + there will be one of these per material texture property (maintex, bump etc...) + */ + public class MeshBakerMaterialTexture + { + //private ProceduralTexture _procT; + private Texture2D _t; + + public Texture2D t + { + set { _t = value; } + } + + public float texelDensity; //how many pixels per polygon area + internal static bool readyToBuildAtlases = false; + //if these are the same for all properties then these can be merged + /// <summary> + /// sampling rect including both material tiling and uv Tiling. Most of the time this is the + /// same for maintex, bumpmap, etc... but it does not need to be. Could have maintex with one + /// tiling and bumpmap with another. If these are the same for all properties then these can be merged + /// </summary> + private DRect encapsulatingSamplingRect; + + /// <summary> + /// IMPORTANT: There are two materialTilingRects. These ones are stored per result-material-texture-property. + /// The are used when baking atlases, NOT for mapping materials to atlas rects and transforming UVs. + /// The material tiling for a texture. These can be different for different properties: maintex, bumpmap etc.... + /// If these are the same for all properties then the MB_TexSets can be merged. + /// </summary> + public DRect matTilingRect { get; private set; } + + /// <summary> + /// Returns -1 if this texture was imported as a normal map + /// Returns 1 if this texture was not imported as a normal map + /// Returns 0 if unknown + /// </summary> + public int isImportedAsNormalMap { get; private set; } + + /* + public MeshBakerMaterialTexture() { } + public MeshBakerMaterialTexture(Texture tx) + { + if (tx is Texture2D) + { + _t = (Texture2D)tx; + } + //else if (tx is ProceduralTexture) + //{ + // _procT = (ProceduralTexture)tx; + //} + else if (tx == null) + { + //do nothing + } + else + { + Debug.LogError("An error occured. Texture must be Texture2D " + tx); + } + } + */ + + public MeshBakerMaterialTexture(Texture tx, Vector2 matTilingOffset, Vector2 matTilingScale, float texelDens, int isImportedAsNormalMap) + { + if (tx is Texture2D) + { + _t = (Texture2D)tx; + } + //else if (tx is ProceduralTexture) + //{ + // _procT = (ProceduralTexture)tx; + //} + else if (tx == null) + { + //do nothing + } + else + { + Debug.LogError("An error occured. Texture must be Texture2D " + tx); + } + matTilingRect = new DRect(matTilingOffset, matTilingScale); + texelDensity = texelDens; + this.isImportedAsNormalMap = isImportedAsNormalMap; + } + + public DRect GetEncapsulatingSamplingRect() + { + return encapsulatingSamplingRect; + } + + /* + public void SetMaterialTilingTo0011() + { + matTilingRect = new DRect(0, 0, 1, 1); + } + */ + + /// <summary> + /// The ts variable serves no functional purpose. I would like this method to ONLY be called from + /// MB_TexSet but there is no way in C# to enforce that. If you want to use this + /// outside of MB_TexSet then add a method to MB_TexSet that does the add on your + /// behalf. The new method should assert that MB_TexSet is in the correct state and should + /// do any necessary stated changes to MB_TexSet depending on the context. + /// + /// After you are done you should "FindAllReferences" for this function to ensure that it is only + /// called by MB_TexSet. + /// </summary> + /// <param name="ts"> + /// Not used, provided as an indicator to developers that all + /// access to this must go through MB_TexSet. + /// </param> + public void SetEncapsulatingSamplingRect(MB_TexSet ts, DRect r) + { + encapsulatingSamplingRect = r; + } + + // This should never be called until we are readyToBuildAtlases. The reason is that the textures + // may not exist, temporary textures may need to be created. + public Texture2D GetTexture2D() + { + if (!readyToBuildAtlases) + { + Debug.LogError("This function should not be called before Step3. For steps 1 and 2 should always call methods like isNull, width, height"); + throw new Exception("GetTexture2D called before ready to build atlases"); + } + return _t; + } + + public bool isNull + { + get { return _t == null/* && _procT == null*/; } + } + + public int width + { + get + { + if (_t != null) return _t.width; + //else if (_procT != null) return _procT.width; + throw new Exception("Texture was null. can't get width"); + } + } + + public int height + { + get + { + if (_t != null) return _t.height; + //else if (_procT != null) return _procT.height; + throw new Exception("Texture was null. can't get height"); + } + } + + public string GetTexName() + { + if (_t != null) return _t.name; + //else if (_procT != null) return _procT.name; + return "null"; + } + + public bool AreTexturesEqual(MeshBakerMaterialTexture b) + { + if (_t == b._t /*&& _procT == b._procT*/) return true; + return false; + } + + /* + public bool IsProceduralTexture() + { + return (_procT != null); + } + + public ProceduralTexture GetProceduralTexture() + { + return _procT; + } + + public Texture2D ConvertProceduralToTexture2D(List<Texture2D> temporaryTextures) + { + int w = _procT.width; + int h = _procT.height; + + bool mips = true; + bool isLinear = false; + GC.Collect(3, GCCollectionMode.Forced); + Texture2D tex = new Texture2D(w, h, TextureFormat.ARGB32, mips, isLinear); + Color32[] pixels = _procT.GetPixels32(0, 0, w, h); + tex.SetPixels32(0, 0, w, h, pixels); + tex.Apply(); + tex.name = _procT.name; + temporaryTextures.Add(tex); + return tex; + } + */ + } + + public class MatAndTransformToMerged + { + public Material mat; + + /// <summary> + /// If considerUVs = true is set to the UV rect of the source mesh + /// otherwise if considerUVs = false is set to 0,0,1,1 + /// </summary> + public DRect obUVRectIfTilingSame { get; private set; } + public DRect samplingRectMatAndUVTiling { get; private set; } + + /// <summary> + /// IMPORTANT: There are two materialTilingRects. These ones are stored per source material. + /// For mapping materials to atlas rects and transforming UVs, NOT for baking atlases. + /// Is set to materialTiling if allTexturesUseSameMatTiling otherwise + /// is set to 0,0,1,1 + /// </summary> + public DRect materialTiling { get; private set; } + public string objName; + + public MatAndTransformToMerged(DRect obUVrect, bool fixOutOfBoundsUVs) + { + _init(obUVrect, fixOutOfBoundsUVs, null); + } + + public MatAndTransformToMerged(DRect obUVrect, bool fixOutOfBoundsUVs, Material m) + { + _init(obUVrect, fixOutOfBoundsUVs, m); + } + + private void _init(DRect obUVrect, bool fixOutOfBoundsUVs, Material m) + { + if (fixOutOfBoundsUVs) + { + obUVRectIfTilingSame = obUVrect; + } + else + { + obUVRectIfTilingSame = new DRect(0, 0, 1, 1); + } + mat = m; + } + + public override bool Equals(object obj) + { + if (obj is MatAndTransformToMerged) + { + MatAndTransformToMerged o = (MatAndTransformToMerged)obj; + + + if (o.mat == mat && o.obUVRectIfTilingSame == obUVRectIfTilingSame) + { + return true; + } + } + return false; + } + + public override int GetHashCode() + { + return mat.GetHashCode() ^ obUVRectIfTilingSame.GetHashCode() ^ samplingRectMatAndUVTiling.GetHashCode(); + } + + public string GetMaterialName() + { + if (mat != null) + { + return mat.name; + } + else if (objName != null) + { + return string.Format("[matFor: {0}]", objName); + } + else + { + return "Unknown"; + } + } + + public void AssignInitialValuesForMaterialTilingAndSamplingRectMatAndUVTiling(bool allTexturesUseSameMatTiling, DRect matTiling) + { + if (allTexturesUseSameMatTiling) + { + materialTiling = matTiling; + + } + else + { + materialTiling = new DRect(0f, 0f, 1f, 1f); + } + DRect tmpMatTiling = materialTiling; + DRect obUVrect = obUVRectIfTilingSame; + samplingRectMatAndUVTiling = MB3_UVTransformUtility.CombineTransforms(ref obUVrect, ref tmpMatTiling); + } + } + + public class MatsAndGOs + { + public List<MatAndTransformToMerged> mats; + public List<GameObject> gos; + } + + /// <summary> + /// A set of textures one for each "maintex","bump" that one or more materials use. These + /// Will be baked into a rectangle in the atlas. + /// </summary> + public class MB_TexSet + { + /// <summary> + /// There is different handing of how things are baked into atlases depending on: + /// do all TexturesUseSameMaterialTiling + /// are the textures edge to edge. + /// We try to capture those differences a clearly defined way. + /// </summary> + private interface PipelineVariation{ + void GetRectsForTextureBakeResults(out Rect allPropsUseSameTiling_encapsulatingSamplingRect, + out Rect propsUseDifferntTiling_obUVRect); + + void SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(MB_TextureTilingTreatment newTilingTreatment); + + Rect GetMaterialTilingRectForTextureBakerResults(int materialIndex); + + void AdjustResultMaterialNonTextureProperties(Material resultMaterial, List<ShaderTextureProperty> props); + } + + private class PipelineVariationAllTexturesUseSameMatTiling : PipelineVariation + { + private MB_TexSet texSet; + + public PipelineVariationAllTexturesUseSameMatTiling(MB_TexSet ts) + { + texSet = ts; + Debug.Assert(texSet.allTexturesUseSameMatTiling == true); + } + + public void GetRectsForTextureBakeResults(out Rect allPropsUseSameTiling_encapsulatingSamplingRect, + out Rect propsUseDifferntTiling_obUVRect) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == true); + propsUseDifferntTiling_obUVRect = new Rect(0, 0, 0, 0); + allPropsUseSameTiling_encapsulatingSamplingRect = texSet.GetEncapsulatingSamplingRectIfTilingSame(); + //adjust for tilingTreatment + if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeX) + { + allPropsUseSameTiling_encapsulatingSamplingRect.x = 0; + allPropsUseSameTiling_encapsulatingSamplingRect.width = 1; + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeY) + { + allPropsUseSameTiling_encapsulatingSamplingRect.y = 0; + allPropsUseSameTiling_encapsulatingSamplingRect.height = 1; + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeXY) + { + allPropsUseSameTiling_encapsulatingSamplingRect = new Rect(0, 0, 1, 1); + } + } + + public void SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(MB_TextureTilingTreatment newTilingTreatment) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == true); + if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeX) + { + foreach (MeshBakerMaterialTexture t in texSet.ts) + { + DRect r = t.GetEncapsulatingSamplingRect(); + r.width = 1; + t.SetEncapsulatingSamplingRect(texSet, r); + } + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeY) + { + foreach (MeshBakerMaterialTexture t in texSet.ts) + { + DRect r = t.GetEncapsulatingSamplingRect(); + r.height = 1; + t.SetEncapsulatingSamplingRect(texSet, r); + } + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeXY) + { + foreach (MeshBakerMaterialTexture t in texSet.ts) + { + DRect r = t.GetEncapsulatingSamplingRect(); + r.height = 1; + r.width = 1; + t.SetEncapsulatingSamplingRect(texSet, r); + } + } + } + + public Rect GetMaterialTilingRectForTextureBakerResults(int materialIndex) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == true); + return texSet.matsAndGOs.mats[materialIndex].materialTiling.GetRect(); + } + + public void AdjustResultMaterialNonTextureProperties(Material resultMaterial, List<ShaderTextureProperty> props) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == true); + } + } + + private class PipelineVariationSomeTexturesUseDifferentMatTiling : PipelineVariation + { + private MB_TexSet texSet; + + public PipelineVariationSomeTexturesUseDifferentMatTiling(MB_TexSet ts) + { + texSet = ts; + Debug.Assert(texSet.allTexturesUseSameMatTiling == false); + } + + public void GetRectsForTextureBakeResults(out Rect allPropsUseSameTiling_encapsulatingSamplingRect, + out Rect propsUseDifferntTiling_obUVRect) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == false); + allPropsUseSameTiling_encapsulatingSamplingRect = new Rect(0,0,0,0); + propsUseDifferntTiling_obUVRect = texSet.obUVrect.GetRect(); + //adjust for tilingTreatment + if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeX) + { + propsUseDifferntTiling_obUVRect.x = 0; + propsUseDifferntTiling_obUVRect.width = 1; + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeY) + { + propsUseDifferntTiling_obUVRect.y = 0; + propsUseDifferntTiling_obUVRect.height = 1; + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeXY) + { + propsUseDifferntTiling_obUVRect = new Rect(0, 0, 1, 1); + } + } + + public void SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(MB_TextureTilingTreatment newTilingTreatment) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == false); + if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeX) + { + foreach (MeshBakerMaterialTexture t in texSet.ts) + { + DRect r = t.GetEncapsulatingSamplingRect(); + r.width = 1; + t.SetEncapsulatingSamplingRect(texSet, r); + } + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeY) + { + foreach (MeshBakerMaterialTexture t in texSet.ts) + { + DRect r = t.GetEncapsulatingSamplingRect(); + r.height = 1; + t.SetEncapsulatingSamplingRect(texSet, r); + } + } + else if (texSet.tilingTreatment == MB_TextureTilingTreatment.edgeToEdgeXY) + { + foreach (MeshBakerMaterialTexture t in texSet.ts) + { + DRect r = t.GetEncapsulatingSamplingRect(); + r.height = 1; + r.width = 1; + t.SetEncapsulatingSamplingRect(texSet, r); + } + } + } + + public Rect GetMaterialTilingRectForTextureBakerResults(int materialIndex) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == false); + return new Rect(0,0,0,0); + } + + public void AdjustResultMaterialNonTextureProperties(Material resultMaterial, List<ShaderTextureProperty> props) + { + Debug.Assert(texSet.allTexturesUseSameMatTiling == false); + if (texSet.thisIsOnlyTexSetInAtlas) + { + for (int i = 0; i < props.Count; i++) + { + if (resultMaterial.HasProperty(props[i].name)) + { + + resultMaterial.SetTextureOffset(props[i].name, texSet.ts[i].matTilingRect.min); + resultMaterial.SetTextureScale(props[i].name, texSet.ts[i].matTilingRect.size); + } + } + } + } + } + + /// <summary> + /// One per "maintex", "bump". + /// Stores encapsulatingSamplingRect (can be different for maintex, bump...) + /// Stores materialTiling for mapping materials to atlas rects and transforming UVs not for baking atlases + /// + /// </summary> + public MeshBakerMaterialTexture[] ts; + + public MatsAndGOs matsAndGOs; + + public bool allTexturesUseSameMatTiling { get; private set; } + + public bool thisIsOnlyTexSetInAtlas { get; private set; } + + public MB_TextureTilingTreatment tilingTreatment { get; private set; } + + public Vector2 obUVoffset { get; private set; } + public Vector2 obUVscale { get; private set; } + public int idealWidth; //all textures will be resized to this size + public int idealHeight; + + private PipelineVariation pipelineVariation; + + internal DRect obUVrect + { + get { return new DRect(obUVoffset, obUVscale); } + } + + public MB_TexSet(MeshBakerMaterialTexture[] tss, Vector2 uvOffset, Vector2 uvScale, MB_TextureTilingTreatment treatment) + { + ts = tss; + tilingTreatment = treatment; + obUVoffset = uvOffset; + obUVscale = uvScale; + allTexturesUseSameMatTiling = false; + thisIsOnlyTexSetInAtlas = false; + matsAndGOs = new MatsAndGOs(); + matsAndGOs.mats = new List<MatAndTransformToMerged>(); + matsAndGOs.gos = new List<GameObject>(); + pipelineVariation = new PipelineVariationSomeTexturesUseDifferentMatTiling(this); + } + + // The two texture sets are equal if they are using the same + // textures/color properties for each map and have the same + // tiling for each of those color properties + internal bool IsEqual(object obj, bool fixOutOfBoundsUVs, MB3_TextureCombinerNonTextureProperties resultMaterialTextureBlender) + { + if (!(obj is MB_TexSet)) + { + return false; + } + MB_TexSet other = (MB_TexSet)obj; + if (other.ts.Length != ts.Length) + { + return false; + } + else + { + for (int i = 0; i < ts.Length; i++) + { + if (ts[i].matTilingRect != other.ts[i].matTilingRect) + return false; + if (!ts[i].AreTexturesEqual(other.ts[i])) + return false; + + if (!resultMaterialTextureBlender.NonTexturePropertiesAreEqual(matsAndGOs.mats[0].mat, other.matsAndGOs.mats[0].mat)) + { + return false; + } + } + + //IMPORTANT don't use Vector2 != Vector2 because it is only acurate to about 5 decimal places + //this can lead to tiled rectangles that can't accept rectangles. + if (fixOutOfBoundsUVs && (obUVoffset.x != other.obUVoffset.x || + obUVoffset.y != other.obUVoffset.y)) + return false; + if (fixOutOfBoundsUVs && (obUVscale.x != other.obUVscale.x || + obUVscale.y != other.obUVscale.y)) + return false; + return true; + } + } + + public Vector2 GetMaxRawTextureHeightWidth() + { + Vector2 max = new Vector2(0, 0); + for (int propIdx = 0; propIdx < ts.Length; propIdx++) + { + MeshBakerMaterialTexture tx = ts[propIdx]; + if (!tx.isNull) + { + max.x = Mathf.Max(max.x, tx.width); + max.y = Mathf.Max(max.y, tx.height); + } + } + return max; + } + + private Rect GetEncapsulatingSamplingRectIfTilingSame() + { + Debug.Assert(allTexturesUseSameMatTiling, "This should never be called if different properties use different tiling. "); + if (ts.Length > 0) + { + return ts[0].GetEncapsulatingSamplingRect().GetRect(); + } + return new Rect(0, 0, 1, 1); + } + + public void SetEncapsulatingSamplingRectWhenMergingTexSets(DRect newEncapsulatingSamplingRect) + { + Debug.Assert(allTexturesUseSameMatTiling, "This should never be called if different properties use different tiling. "); + for (int propIdx = 0; propIdx < ts.Length; propIdx++) + { + ts[propIdx].SetEncapsulatingSamplingRect(this, newEncapsulatingSamplingRect); + } + } + + public void SetEncapsulatingSamplingRectForTesting(int propIdx, DRect newEncapsulatingSamplingRect) + { + ts[propIdx].SetEncapsulatingSamplingRect(this, newEncapsulatingSamplingRect); + } + + public void SetEncapsulatingRect(int propIdx, bool considerMeshUVs) + { + if (considerMeshUVs) + { + ts[propIdx].SetEncapsulatingSamplingRect(this, obUVrect); + } + else + { + ts[propIdx].SetEncapsulatingSamplingRect(this, new DRect(0, 0, 1, 1)); + } + } + + public void CreateColoredTexToReplaceNull(string propName, int propIdx, bool considerMeshUVs, MB3_TextureCombiner combiner, Color col, bool isLinear) + { + MeshBakerMaterialTexture matTex = ts[propIdx]; + Texture2D tt = combiner._createTemporaryTexture(propName, 16, 16, TextureFormat.ARGB32, true, isLinear); + matTex.t = tt; + MB_Utility.setSolidColor(matTex.GetTexture2D(), col); + } + + public void SetThisIsOnlyTexSetInAtlasTrue() + { + Debug.Assert(thisIsOnlyTexSetInAtlas == false); + thisIsOnlyTexSetInAtlas = true; + } + + public void SetAllTexturesUseSameMatTilingTrue() + { + Debug.Assert(allTexturesUseSameMatTiling == false); + allTexturesUseSameMatTiling = true; + pipelineVariation = new PipelineVariationAllTexturesUseSameMatTiling(this); + } + + public void AdjustResultMaterialNonTextureProperties(Material resultMaterial, List<ShaderTextureProperty> props) + { + pipelineVariation.AdjustResultMaterialNonTextureProperties(resultMaterial, props); + } + + public void SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(MB_TextureTilingTreatment newTilingTreatment) + { + tilingTreatment = newTilingTreatment; + pipelineVariation.SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(newTilingTreatment); + } + + + internal void GetRectsForTextureBakeResults(out Rect allPropsUseSameTiling_encapsulatingSamplingRect, + out Rect propsUseDifferntTiling_obUVRect) + { + pipelineVariation.GetRectsForTextureBakeResults(out allPropsUseSameTiling_encapsulatingSamplingRect, out propsUseDifferntTiling_obUVRect); + } + + + /// <summary> + /// + /// </summary> + /// <param name="materialIndex">Should be an index in matsAndGOs.mats List</param> + /// <returns></returns> + internal Rect GetMaterialTilingRectForTextureBakerResults(int materialIndex) + { + return pipelineVariation.GetMaterialTilingRectForTextureBakerResults(materialIndex); + } + + //assumes all materials use the same obUVrects. + internal void CalcInitialFullSamplingRects(bool fixOutOfBoundsUVs) + { + DRect validFullSamplingRect = new Core.DRect(0, 0, 1, 1); + if (fixOutOfBoundsUVs) + { + validFullSamplingRect = obUVrect; + } + + for (int propIdx = 0; propIdx < ts.Length; propIdx++) + { + if (!ts[propIdx].isNull) + { + DRect matTiling = ts[propIdx].matTilingRect; + DRect ruv; + if (fixOutOfBoundsUVs) + { + ruv = obUVrect; + } + else + { + ruv = new DRect(0.0, 0.0, 1.0, 1.0); + } + + ts[propIdx].SetEncapsulatingSamplingRect(this, MB3_UVTransformUtility.CombineTransforms(ref ruv, ref matTiling)); + validFullSamplingRect = ts[propIdx].GetEncapsulatingSamplingRect(); + } + } + + //if some of the textures were null make them match the sampling of one of the other textures + for (int propIdx = 0; propIdx < ts.Length; propIdx++) + { + if (ts[propIdx].isNull) + { + ts[propIdx].SetEncapsulatingSamplingRect(this, validFullSamplingRect); + } + } + } + + internal void CalcMatAndUVSamplingRects() + { + DRect matTiling = new DRect(0f, 0f, 1f, 1f); + if (allTexturesUseSameMatTiling) + { + + for (int propIdx = 0; propIdx < ts.Length; propIdx++) + { + if (!ts[propIdx].isNull) + { + matTiling = ts[propIdx].matTilingRect; + break; + } + } + } + + for (int matIdx = 0; matIdx < matsAndGOs.mats.Count; matIdx++) + { + matsAndGOs.mats[matIdx].AssignInitialValuesForMaterialTilingAndSamplingRectMatAndUVTiling(allTexturesUseSameMatTiling, matTiling); + } + } + + public bool AllTexturesAreSameForMerge(MB_TexSet other, bool considerNonTextureProperties, MB3_TextureCombinerNonTextureProperties resultMaterialTextureBlender) + { + if (other.ts.Length != ts.Length) + { + return false; + } + else + { + if (!other.allTexturesUseSameMatTiling || !allTexturesUseSameMatTiling) + { + return false; + } + // must use same set of textures + int idxOfFirstNoneNull = -1; + for (int i = 0; i < ts.Length; i++) + { + if (!ts[i].AreTexturesEqual(other.ts[i])) + return false; + if (idxOfFirstNoneNull == -1 && !ts[i].isNull) + { + idxOfFirstNoneNull = i; + } + if (considerNonTextureProperties) + { + if (!resultMaterialTextureBlender.NonTexturePropertiesAreEqual(matsAndGOs.mats[0].mat, other.matsAndGOs.mats[0].mat)) + { + return false; + } + } + } + if (idxOfFirstNoneNull != -1) + { + //check that all textures are the same. Have already checked all tiling is same + for (int i = 0; i < ts.Length; i++) + { + if (!ts[i].AreTexturesEqual(other.ts[i])) + { + return false; + } + } + } + return true; + } + } + + public void DrawRectsToMergeGizmos(Color encC, Color innerC) + { + DRect r = ts[0].GetEncapsulatingSamplingRect(); + r.Expand(.05f); + Gizmos.color = encC; + Gizmos.DrawWireCube(r.center.GetVector2(), r.size); + for (int i = 0; i < matsAndGOs.mats.Count; i++) + { + DRect rr = matsAndGOs.mats[i].samplingRectMatAndUVTiling; + DRect trans = MB3_UVTransformUtility.GetShiftTransformToFitBinA(ref r, ref rr); + Vector2 xy = MB3_UVTransformUtility.TransformPoint(ref trans, rr.min); + rr.x = xy.x; + rr.y = xy.y; + //Debug.Log("r " + r + " rr" + rr); + Gizmos.color = innerC; + Gizmos.DrawWireCube(rr.center.GetVector2(), rr.size); + } + } + + internal string GetDescription() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("[GAME_OBJS="); + for (int i = 0; i < matsAndGOs.gos.Count; i++) + { + sb.AppendFormat("{0},", matsAndGOs.gos[i].name); + } + sb.AppendFormat("MATS="); + for (int i = 0; i < matsAndGOs.mats.Count; i++) + { + sb.AppendFormat("{0},", matsAndGOs.mats[i].GetMaterialName()); + } + sb.Append("]"); + return sb.ToString(); + } + + internal string GetMatSubrectDescriptions() + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < matsAndGOs.mats.Count; i++) + { + sb.AppendFormat("\n {0}={1},", matsAndGOs.mats[i].GetMaterialName(), matsAndGOs.mats[i].samplingRectMatAndUVTiling); + } + return sb.ToString(); + } + } + + /* + class ProceduralMaterialInfo + { + //public ProceduralMaterial proceduralMat; + public bool originalIsReadableVal; + } + */ + +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerAtlasRect.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerAtlasRect.cs.meta new file mode 100644 index 00000000..c7e6aaa3 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerAtlasRect.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1caa0caed1da6af47b756fe7bec22da9 +timeCreated: 1522041808 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerMerging.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerMerging.cs new file mode 100644 index 00000000..8b04e1c8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerMerging.cs @@ -0,0 +1,472 @@ +using UnityEngine; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace DigitalOpus.MB.Core +{ + public class MB3_TextureCombinerMerging + { + public static bool DO_INTEGRITY_CHECKS = false; + + private bool _HasBeenInitialized = false; + + public static Rect BuildTransformMeshUV2AtlasRect( + bool considerMeshUVs, + Rect _atlasRect, + Rect _obUVRect, + Rect _sourceMaterialTiling, + Rect _encapsulatingRect) + { + DRect atlasRect = new DRect(_atlasRect); + DRect obUVRect; + if (considerMeshUVs) + { + obUVRect = new DRect(_obUVRect); //this is the uvRect in src mesh + } + else + { + obUVRect = new DRect(0.0, 0.0, 1.0, 1.0); + } + + DRect sourceMaterialTiling = new DRect(_sourceMaterialTiling); + + DRect encapsulatingRectMatAndUVTiling = new DRect(_encapsulatingRect); + + DRect encapsulatingRectMatAndUVTilingInverse = MB3_UVTransformUtility.InverseTransform(ref encapsulatingRectMatAndUVTiling); + + DRect toNormalizedUVs = MB3_UVTransformUtility.InverseTransform(ref obUVRect); + + DRect meshFullSamplingRect = MB3_UVTransformUtility.CombineTransforms(ref obUVRect, ref sourceMaterialTiling); + + DRect shiftToFitInEncapsulating = MB3_UVTransformUtility.GetShiftTransformToFitBinA(ref encapsulatingRectMatAndUVTiling, ref meshFullSamplingRect); + meshFullSamplingRect = MB3_UVTransformUtility.CombineTransforms(ref meshFullSamplingRect, ref shiftToFitInEncapsulating); + + //transform between full sample rect and encapsulating rect + DRect relativeTrans = MB3_UVTransformUtility.CombineTransforms(ref meshFullSamplingRect, ref encapsulatingRectMatAndUVTilingInverse); + + // [transform] = [toNormalizedUVs][relativeTrans][uvSubRectInAtlas] + DRect trans = MB3_UVTransformUtility.CombineTransforms(ref toNormalizedUVs, ref relativeTrans); + trans = MB3_UVTransformUtility.CombineTransforms(ref trans, ref atlasRect); + Rect rr = trans.GetRect(); + return rr; + } + + bool _considerNonTextureProperties = false; + MB3_TextureCombinerNonTextureProperties resultMaterialTextureBlender; + bool fixOutOfBoundsUVs = true; + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + private static bool LOG_LEVEL_TRACE_MERGE_MAT_SUBRECTS = false; + + public MB3_TextureCombinerMerging(bool considerNonTextureProps, MB3_TextureCombinerNonTextureProperties resultMaterialTexBlender, bool fixObUVs, MB2_LogLevel logLevel) + { + LOG_LEVEL = logLevel; + _considerNonTextureProperties = considerNonTextureProps; + resultMaterialTextureBlender = resultMaterialTexBlender; + fixOutOfBoundsUVs = fixObUVs; + } + + public void MergeOverlappingDistinctMaterialTexturesAndCalcMaterialSubrects(List<MB_TexSet> distinctMaterialTextures) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log("MergeOverlappingDistinctMaterialTexturesAndCalcMaterialSubrects num atlas rects" + distinctMaterialTextures.Count); + } + + int numMerged = 0; + + // IMPORTANT: Note that the verts stored in the mesh are NOT Normalized UV Coords. They are normalized * [UVTrans]. To get normalized UV + // coords we must multiply them by [invUVTrans]. Need to do this to the verts in the mesh before we do any transforms with them. + // Also check that all textures use same tiling. This is a prerequisite for merging. + // Mark MB3_TexSet that are mergable (allTexturesUseSameMatTiling) + for (int i = 0; i < distinctMaterialTextures.Count; i++) + { + MB_TexSet tx = distinctMaterialTextures[i]; + int idxOfFirstNotNull = -1; + bool allAreSame = true; + DRect firstRect = new DRect(); + for (int propIdx = 0; propIdx < tx.ts.Length; propIdx++) + { + if (idxOfFirstNotNull != -1) + { + if (!tx.ts[propIdx].isNull && firstRect != tx.ts[propIdx].matTilingRect) + { + allAreSame = false; + } + } + else if (!tx.ts[propIdx].isNull) + { + idxOfFirstNotNull = propIdx; + firstRect = tx.ts[propIdx].matTilingRect; + } + } + if (LOG_LEVEL >= MB2_LogLevel.debug || LOG_LEVEL_TRACE_MERGE_MAT_SUBRECTS == true) + { + if (allAreSame) + { + Debug.LogFormat("TextureSet {0} allTexturesUseSameMatTiling = {1}", i, allAreSame); + } + else + { + Debug.Log(string.Format("Textures in material(s) do not all use the same material tiling. This set of textures will not be considered for merge: {0} ", tx.GetDescription())); + } + } + if (allAreSame) + { + tx.SetAllTexturesUseSameMatTilingTrue(); + } + } + + for (int i = 0; i < distinctMaterialTextures.Count; i++) + { + MB_TexSet tx = distinctMaterialTextures[i]; + for (int matIdx = 0; matIdx < tx.matsAndGOs.mats.Count; matIdx++) + { + if (tx.matsAndGOs.gos.Count > 0) { + tx.matsAndGOs.mats[matIdx].objName = tx.matsAndGOs.gos[0].name; + } else if (tx.ts[0] != null) { + tx.matsAndGOs.mats[matIdx].objName = string.Format("[objWithTx:{0} atlasBlock:{1} matIdx{2}]",tx.ts[0].GetTexName(),i,matIdx); + } else { + tx.matsAndGOs.mats[matIdx].objName = string.Format("[objWithTx:{0} atlasBlock:{1} matIdx{2}]", "Unknown", i, matIdx); + } + } + + tx.CalcInitialFullSamplingRects(fixOutOfBoundsUVs); + tx.CalcMatAndUVSamplingRects(); + } + + _HasBeenInitialized = true; + // need to calculate the srcSampleRect for the complete tiling in the atlas + // for each material need to know what the subrect would be in the atlas if material UVRect was 0,0,1,1 and Merged uvRect was full tiling + List<int> MarkedForDeletion = new List<int>(); + for (int i = 0; i < distinctMaterialTextures.Count; i++) + { + MB_TexSet tx2 = distinctMaterialTextures[i]; + for (int j = i + 1; j < distinctMaterialTextures.Count; j++) + { + MB_TexSet tx1 = distinctMaterialTextures[j]; + if (tx1.AllTexturesAreSameForMerge(tx2, _considerNonTextureProperties, resultMaterialTextureBlender)) + { + double accumulatedAreaCombined = 0f; + double accumulatedAreaNotCombined = 0f; + DRect encapsulatingRectMerged = new DRect(); + int idxOfFirstNotNull = -1; + for (int propIdx = 0; propIdx < tx2.ts.Length; propIdx++) + { + if (!tx2.ts[propIdx].isNull) + { + if (idxOfFirstNotNull == -1) idxOfFirstNotNull = propIdx; + } + } + + DRect encapsulatingRect1 = new DRect(); + DRect encapsulatingRect2 = new DRect(); + if (idxOfFirstNotNull != -1) + { + // only in here if all properties use the same tiling so don't need to worry about which propIdx we are dealing with + //Get the rect that encapsulates all material and UV tiling for materials and meshes in tx1 + encapsulatingRect1 = tx1.matsAndGOs.mats[0].samplingRectMatAndUVTiling; + for (int matIdx = 1; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + DRect tmpSsamplingRectMatAndUVTilingTx1 = tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling; + encapsulatingRect1 = MB3_UVTransformUtility.GetEncapsulatingRectShifted(ref encapsulatingRect1, ref tmpSsamplingRectMatAndUVTilingTx1); + } + + //same for tx2 + encapsulatingRect2 = tx2.matsAndGOs.mats[0].samplingRectMatAndUVTiling; + for (int matIdx = 1; matIdx < tx2.matsAndGOs.mats.Count; matIdx++) + { + DRect tmpSsamplingRectMatAndUVTilingTx2 = tx2.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling; + encapsulatingRect2 = MB3_UVTransformUtility.GetEncapsulatingRectShifted(ref encapsulatingRect2, ref tmpSsamplingRectMatAndUVTilingTx2); + } + + encapsulatingRectMerged = MB3_UVTransformUtility.GetEncapsulatingRectShifted(ref encapsulatingRect1, ref encapsulatingRect2); + accumulatedAreaCombined += encapsulatingRectMerged.width * encapsulatingRectMerged.height; + accumulatedAreaNotCombined += encapsulatingRect1.width * encapsulatingRect1.height + encapsulatingRect2.width * encapsulatingRect2.height; + } + else + { + encapsulatingRectMerged = new DRect(0f, 0f, 1f, 1f); + } + + //the distinct material textures may overlap. + //if the area of these rectangles combined is less than the sum of these areas of these rectangles then merge these distinctMaterialTextures + if (accumulatedAreaCombined < accumulatedAreaNotCombined) + { + // merge tx2 into tx1 + numMerged++; + StringBuilder sb = null; + if (LOG_LEVEL >= MB2_LogLevel.info) + { + sb = new StringBuilder(); + sb.AppendFormat("About To Merge:\n TextureSet1 {0}\n TextureSet2 {1}\n", tx1.GetDescription(), tx2.GetDescription()); + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + for (int matIdx = 0; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + sb.AppendFormat("tx1 Mat {0} matAndMeshUVRect {1} fullSamplingRect {2}\n", + tx1.matsAndGOs.mats[matIdx].mat, tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling, tx1.ts[0].GetEncapsulatingSamplingRect()); + } + for (int matIdx = 0; matIdx < tx2.matsAndGOs.mats.Count; matIdx++) + { + sb.AppendFormat("tx2 Mat {0} matAndMeshUVRect {1} fullSamplingRect {2}\n", + tx2.matsAndGOs.mats[matIdx].mat, tx2.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling, tx2.ts[0].GetEncapsulatingSamplingRect()); + } + } + } + + //copy game objects over + for (int k = 0; k < tx2.matsAndGOs.gos.Count; k++) + { + if (!tx1.matsAndGOs.gos.Contains(tx2.matsAndGOs.gos[k])) + { + tx1.matsAndGOs.gos.Add(tx2.matsAndGOs.gos[k]); + } + } + + //copy materials over from tx2 to tx1 + for (int matIdx = 0; matIdx < tx2.matsAndGOs.mats.Count; matIdx++) + { + tx1.matsAndGOs.mats.Add(tx2.matsAndGOs.mats[matIdx]); + } + + tx1.SetEncapsulatingSamplingRectWhenMergingTexSets(encapsulatingRectMerged); + if (!MarkedForDeletion.Contains(i)) + { + MarkedForDeletion.Add(i); + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + sb.AppendFormat("=== After Merge TextureSet {0}\n", tx1.GetDescription()); + for (int matIdx = 0; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + sb.AppendFormat("tx1 Mat {0} matAndMeshUVRect {1} fullSamplingRect {2}\n", + tx1.matsAndGOs.mats[matIdx].mat, tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling, tx1.ts[0].GetEncapsulatingSamplingRect()); + } + //Integrity check that sampling rects fit into enapsulating rects + if (DO_INTEGRITY_CHECKS) + { + if (DO_INTEGRITY_CHECKS) { DoIntegrityCheckMergedEncapsulatingSamplingRects(distinctMaterialTextures); } + } + } + Debug.Log(sb.ToString()); + } + break; + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log(string.Format("Considered merging {0} and {1} but there was not enough overlap. It is more efficient to bake these to separate rectangles.", + tx1.GetDescription() + encapsulatingRect1, + tx2.GetDescription() + encapsulatingRect2)); + } + } + } + } + } + + //remove distinctMaterialTextures that were merged + for (int j = MarkedForDeletion.Count - 1; j >= 0; j--) + { + distinctMaterialTextures.RemoveAt(MarkedForDeletion[j]); + } + MarkedForDeletion.Clear(); + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log(string.Format("MergeOverlappingDistinctMaterialTexturesAndCalcMaterialSubrects complete merged {0} now have {1}", numMerged, distinctMaterialTextures.Count)); + } + if (DO_INTEGRITY_CHECKS) { DoIntegrityCheckMergedEncapsulatingSamplingRects(distinctMaterialTextures); } + } + + + // This should only be called after regular merge so that rects have been correctly setup. + public void MergeDistinctMaterialTexturesThatWouldExceedMaxAtlasSizeAndCalcMaterialSubrects(List<MB_TexSet> distinctMaterialTextures, int maxAtlasSize) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log("MergeDistinctMaterialTexturesThatWouldExceedMaxAtlasSizeAndCalcMaterialSubrects num atlas rects" + distinctMaterialTextures.Count); + } + + Debug.Assert(_HasBeenInitialized, "MergeOverlappingDistinctMaterialTexturesAndCalcMaterialSubrects must be called before MergeDistinctMaterialTexturesThatWouldExceedMaxAtlasSizeAndCalcMaterialSubrects"); + + int numMerged = 0; + + List<int> MarkedForDeletion = new List<int>(); + for (int i = 0; i < distinctMaterialTextures.Count; i++) + { + MB_TexSet tx2 = distinctMaterialTextures[i]; + for (int j = i + 1; j < distinctMaterialTextures.Count; j++) + { + MB_TexSet tx1 = distinctMaterialTextures[j]; + if (tx1.AllTexturesAreSameForMerge(tx2, _considerNonTextureProperties, resultMaterialTextureBlender)) + { + //Check if the size of the rect in the atlas would be greater than max atlas size. + + DRect encapsulatingRectMerged = new DRect(); + int idxOfFirstNotNull = -1; + for (int propIdx = 0; propIdx < tx2.ts.Length; propIdx++) + { + if (!tx2.ts[propIdx].isNull) + { + if (idxOfFirstNotNull == -1) idxOfFirstNotNull = propIdx; + } + } + + DRect encapsulatingRect1 = new DRect(); + DRect encapsulatingRect2 = new DRect(); + if (idxOfFirstNotNull != -1) + { + // only in here if all properties use the same tiling so don't need to worry about which propIdx we are dealing with + //Get the rect that encapsulates all material and UV tiling for materials and meshes in tx1 + encapsulatingRect1 = tx1.matsAndGOs.mats[0].samplingRectMatAndUVTiling; + for (int matIdx = 1; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + DRect tmpSsamplingRectMatAndUVTilingTx1 = tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling; + encapsulatingRect1 = MB3_UVTransformUtility.GetEncapsulatingRectShifted(ref encapsulatingRect1, ref tmpSsamplingRectMatAndUVTilingTx1); + } + + //same for tx2 + encapsulatingRect2 = tx2.matsAndGOs.mats[0].samplingRectMatAndUVTiling; + for (int matIdx = 1; matIdx < tx2.matsAndGOs.mats.Count; matIdx++) + { + DRect tmpSsamplingRectMatAndUVTilingTx2 = tx2.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling; + encapsulatingRect2 = MB3_UVTransformUtility.GetEncapsulatingRectShifted(ref encapsulatingRect2, ref tmpSsamplingRectMatAndUVTilingTx2); + } + + encapsulatingRectMerged = MB3_UVTransformUtility.GetEncapsulatingRectShifted(ref encapsulatingRect1, ref encapsulatingRect2); + } + else + { + encapsulatingRectMerged = new DRect(0f, 0f, 1f, 1f); + } + + Vector2 maxHeightWidth = tx1.GetMaxRawTextureHeightWidth(); + if (encapsulatingRectMerged.width * maxHeightWidth.x > maxAtlasSize || + encapsulatingRectMerged.height * maxHeightWidth.y > maxAtlasSize) + { + // merge tx2 into tx1 + numMerged++; + StringBuilder sb = null; + if (LOG_LEVEL >= MB2_LogLevel.info) + { + sb = new StringBuilder(); + sb.AppendFormat("About To Merge:\n TextureSet1 {0}\n TextureSet2 {1}\n", tx1.GetDescription(), tx2.GetDescription()); + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + for (int matIdx = 0; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + sb.AppendFormat("tx1 Mat {0} matAndMeshUVRect {1} fullSamplingRect {2}\n", + tx1.matsAndGOs.mats[matIdx].mat, tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling, tx1.ts[0].GetEncapsulatingSamplingRect()); + } + for (int matIdx = 0; matIdx < tx2.matsAndGOs.mats.Count; matIdx++) + { + sb.AppendFormat("tx2 Mat {0} matAndMeshUVRect {1} fullSamplingRect {2}\n", + tx2.matsAndGOs.mats[matIdx].mat, tx2.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling, tx2.ts[0].GetEncapsulatingSamplingRect()); + } + } + } + + //copy game objects over + for (int k = 0; k < tx2.matsAndGOs.gos.Count; k++) + { + if (!tx1.matsAndGOs.gos.Contains(tx2.matsAndGOs.gos[k])) + { + tx1.matsAndGOs.gos.Add(tx2.matsAndGOs.gos[k]); + } + } + + //copy materials over from tx2 to tx1 + for (int matIdx = 0; matIdx < tx2.matsAndGOs.mats.Count; matIdx++) + { + tx1.matsAndGOs.mats.Add(tx2.matsAndGOs.mats[matIdx]); + } + + tx1.SetEncapsulatingSamplingRectWhenMergingTexSets(encapsulatingRectMerged); + if (!MarkedForDeletion.Contains(i)) + { + MarkedForDeletion.Add(i); + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) + { + sb.AppendFormat("=== After Merge TextureSet {0}\n", tx1.GetDescription()); + for (int matIdx = 0; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + sb.AppendFormat("tx1 Mat {0} matAndMeshUVRect {1} fullSamplingRect {2}\n", + tx1.matsAndGOs.mats[matIdx].mat, tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling, tx1.ts[0].GetEncapsulatingSamplingRect()); + } + //Integrity check that sampling rects fit into enapsulating rects + if (DO_INTEGRITY_CHECKS) + { + if (DO_INTEGRITY_CHECKS) { DoIntegrityCheckMergedEncapsulatingSamplingRects(distinctMaterialTextures); } + } + } + Debug.Log(sb.ToString()); + } + break; + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log(string.Format("Considered merging {0} and {1} but there was not enough overlap. It is more efficient to bake these to separate rectangles.", + tx1.GetDescription() + encapsulatingRect1, + tx2.GetDescription() + encapsulatingRect2)); + } + } + } + } + } + + //remove distinctMaterialTextures that were merged + for (int j = MarkedForDeletion.Count - 1; j >= 0; j--) + { + distinctMaterialTextures.RemoveAt(MarkedForDeletion[j]); + } + MarkedForDeletion.Clear(); + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log(string.Format("MergeDistinctMaterialTexturesThatWouldExceedMaxAtlasSizeAndCalcMaterialSubrects complete merged {0} now have {1}", numMerged, distinctMaterialTextures.Count)); + } + if (DO_INTEGRITY_CHECKS) { DoIntegrityCheckMergedEncapsulatingSamplingRects(distinctMaterialTextures); } + } + + public void DoIntegrityCheckMergedEncapsulatingSamplingRects(List<MB_TexSet> distinctMaterialTextures) + { + if (DO_INTEGRITY_CHECKS) + { + for (int i = 0; i < distinctMaterialTextures.Count; i++) + { + MB_TexSet tx1 = distinctMaterialTextures[i]; + if (!tx1.allTexturesUseSameMatTiling) + { + continue; + } + for (int matIdx = 0; matIdx < tx1.matsAndGOs.mats.Count; matIdx++) + { + MatAndTransformToMerged mat = tx1.matsAndGOs.mats[matIdx]; + DRect uvR = mat.obUVRectIfTilingSame; + DRect matR = mat.materialTiling; + if (!MB2_TextureBakeResults.IsMeshAndMaterialRectEnclosedByAtlasRect(tx1.tilingTreatment, uvR.GetRect(), matR.GetRect(), tx1.ts[0].GetEncapsulatingSamplingRect().GetRect(),MB2_LogLevel.info)) + { + Debug.LogErrorFormat("mesh " + tx1.matsAndGOs.mats[matIdx].objName + "\n" + + " uv=" + uvR + "\n" + + " mat=" + matR.GetRect().ToString("f5") + "\n" + + " samplingRect=" + tx1.matsAndGOs.mats[matIdx].samplingRectMatAndUVTiling.GetRect().ToString("f4") + "\n" + + " encapsulatingRect " + tx1.ts[0].GetEncapsulatingSamplingRect().GetRect().ToString("f4") + "\n"); + Debug.LogErrorFormat(string.Format("Integrity check failed. " + tx1.matsAndGOs.mats[matIdx].objName + " Encapsulating sampling rect failed to contain potentialRect\n")); + MB2_TextureBakeResults.IsMeshAndMaterialRectEnclosedByAtlasRect(tx1.tilingTreatment, uvR.GetRect(), matR.GetRect(), tx1.ts[0].GetEncapsulatingSamplingRect().GetRect(), MB2_LogLevel.trace); + Debug.Assert(false); + } + } + } + } + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerMerging.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerMerging.cs.meta new file mode 100644 index 00000000..36b75548 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerMerging.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6ff1c41a1f899a44994f5e669ea7b9ec +timeCreated: 1522042024 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerNonTextureProperties.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerNonTextureProperties.cs new file mode 100644 index 00000000..927b039b --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerNonTextureProperties.cs @@ -0,0 +1,500 @@ +using UnityEngine; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace DigitalOpus.MB.Core +{ + public class MB3_TextureCombinerNonTextureProperties + { + public interface MaterialProperty + { + string PropertyName { get; set; } + MaterialPropertyValueAveraged GetAverageCalculator(); + object GetDefaultValue(); + } + + public class MaterialPropertyFloat : MaterialProperty + { + public string PropertyName { get; set; } + MaterialPropertyValueAveragedFloat _averageCalc; + float _defaultValue; + + public MaterialPropertyFloat(string name, float defValue) + { + _averageCalc = new MaterialPropertyValueAveragedFloat(); + _defaultValue = defValue; + PropertyName = name; + } + + public MaterialPropertyValueAveraged GetAverageCalculator() + { + return _averageCalc; + } + + public object GetDefaultValue() + { + return _defaultValue; + } + } + + public class MaterialPropertyColor : MaterialProperty + { + public string PropertyName { get; set; } + MaterialPropertyValueAveragedColor _averageCalc; + Color _defaultValue; + + public MaterialPropertyColor(string name, Color defaultVal) + { + _averageCalc = new MaterialPropertyValueAveragedColor(); + _defaultValue = defaultVal; + PropertyName = name; + } + + public MaterialPropertyValueAveraged GetAverageCalculator() + { + return _averageCalc; + } + + public object GetDefaultValue() + { + return _defaultValue; + } + } + + public interface MaterialPropertyValueAveraged + { + void TryGetPropValueFromMaterialAndBlendIntoAverage(Material mat, MaterialProperty property); + object GetAverage(); + int NumValues(); + void SetAverageValueOrDefaultOnMaterial(Material mat, MaterialProperty property); + } + + public class MaterialPropertyValueAveragedFloat : MaterialPropertyValueAveraged + { + public float averageVal; + public int numValues; + + public void TryGetPropValueFromMaterialAndBlendIntoAverage(Material mat, MaterialProperty property) + { + if (mat.HasProperty(property.PropertyName)) + { + float v = mat.GetFloat(property.PropertyName); + averageVal = averageVal * ((float)numValues) / (numValues + 1) + v / (numValues + 1); + numValues++; + } + } + + public object GetAverage() + { + return averageVal; + } + + public int NumValues() + { + return numValues; + } + + public void SetAverageValueOrDefaultOnMaterial(Material mat, MaterialProperty property) + { + if (mat.HasProperty(property.PropertyName)) + { + if (numValues > 0) + { + mat.SetFloat(property.PropertyName, averageVal); + } else + { + mat.SetFloat(property.PropertyName, (float)property.GetDefaultValue()); + } + } + } + } + + public class MaterialPropertyValueAveragedColor : MaterialPropertyValueAveraged + { + public Color averageVal; + public int numValues; + + public void TryGetPropValueFromMaterialAndBlendIntoAverage(Material mat, MaterialProperty property) + { + if (mat.HasProperty(property.PropertyName)) + { + Color v = mat.GetColor(property.PropertyName); + averageVal = averageVal * ((float)numValues) / (numValues + 1) + v / (numValues + 1); + numValues++; + } + } + + public object GetAverage() + { + return averageVal; + } + + public int NumValues() + { + return numValues; + } + + public void SetAverageValueOrDefaultOnMaterial(Material mat, MaterialProperty property) + { + if (mat.HasProperty(property.PropertyName)) + { + if (numValues > 0) + { + mat.SetColor(property.PropertyName, averageVal); + } + else + { + mat.SetColor(property.PropertyName, (Color) property.GetDefaultValue()); + } + } + } + } + + public struct TexPropertyNameColorPair + { + public string name; + public Color color; + + public TexPropertyNameColorPair(string nm, Color col) + { + name = nm; + color = col; + } + } + + private interface NonTextureProperties + { + bool NonTexturePropertiesAreEqual(Material a, Material b); + Texture2D TintTextureWithTextureCombiner(Texture2D t, MB_TexSet sourceMaterial, ShaderTextureProperty shaderPropertyName); + void AdjustNonTextureProperties(Material resultMat, List<ShaderTextureProperty> texPropertyNames, MB2_EditorMethodsInterface editorMethods); + Color GetColorForTemporaryTexture(Material matIfBlender, ShaderTextureProperty texProperty); + Color GetColorAsItWouldAppearInAtlasIfNoTexture(Material matIfBlender, ShaderTextureProperty texProperty); + } + + private class NonTexturePropertiesDontBlendProps : NonTextureProperties + { + MB3_TextureCombinerNonTextureProperties _textureProperties; + + public NonTexturePropertiesDontBlendProps(MB3_TextureCombinerNonTextureProperties textureProperties) + { + _textureProperties = textureProperties; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + Debug.Assert(_textureProperties._considerNonTextureProperties == false); + return true; + } + + public Texture2D TintTextureWithTextureCombiner(Texture2D t, MB_TexSet sourceMaterial, ShaderTextureProperty shaderPropertyName) + { + Debug.Assert(_textureProperties._considerNonTextureProperties == false); + Debug.LogError("TintTextureWithTextureCombiner should never be called if resultMaterialTextureBlender is null"); + return t; + } + + public void AdjustNonTextureProperties(Material resultMat, List<ShaderTextureProperty> texPropertyNames, MB2_EditorMethodsInterface editorMethods) + { + Debug.Assert(_textureProperties._considerNonTextureProperties == false); + if (resultMat == null || texPropertyNames == null) return; + for (int nonTexPropIdx = 0; nonTexPropIdx < _textureProperties._nonTextureProperties.Length; nonTexPropIdx++) + { + MaterialProperty nonTexProperty = _textureProperties._nonTextureProperties[nonTexPropIdx]; + if (resultMat.HasProperty(nonTexProperty.PropertyName)) + { + nonTexProperty.GetAverageCalculator().SetAverageValueOrDefaultOnMaterial(resultMat, nonTexProperty); + } + } + + if (editorMethods != null) + { + editorMethods.CommitChangesToAssets(); + } + } + + public Color GetColorAsItWouldAppearInAtlasIfNoTexture(Material matIfBlender, ShaderTextureProperty texProperty) + { + Debug.Assert(false, "Should never be called"); + return Color.white; + } + + public Color GetColorForTemporaryTexture(Material matIfBlender, ShaderTextureProperty texProperty) + { + Debug.Assert(_textureProperties._considerNonTextureProperties == false); + if (texProperty.isNormalMap) + { + return NEUTRAL_NORMAL_MAP_COLOR; + } + + else if (_textureProperties.textureProperty2DefaultColorMap.ContainsKey(texProperty.name)) + { + return _textureProperties.textureProperty2DefaultColorMap[texProperty.name]; + } + + return new Color(1f, 1f, 1f, 0f); + } + } + + private class NonTexturePropertiesBlendProps : NonTextureProperties + { + MB3_TextureCombinerNonTextureProperties _textureProperties; + TextureBlender resultMaterialTextureBlender; + + public NonTexturePropertiesBlendProps(MB3_TextureCombinerNonTextureProperties textureProperties, TextureBlender resultMats) + { + resultMaterialTextureBlender = resultMats; + _textureProperties = textureProperties; + } + + public bool NonTexturePropertiesAreEqual(Material a, Material b) + { + Debug.Assert(resultMaterialTextureBlender != null); + return resultMaterialTextureBlender.NonTexturePropertiesAreEqual(a, b); + } + + public Texture2D TintTextureWithTextureCombiner(Texture2D t, MB_TexSet sourceMaterial, ShaderTextureProperty shaderPropertyName) + { + Debug.Assert(resultMaterialTextureBlender != null); + resultMaterialTextureBlender.OnBeforeTintTexture(sourceMaterial.matsAndGOs.mats[0].mat, shaderPropertyName.name); + if (_textureProperties.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("Blending texture {0} mat {1} with non-texture properties using TextureBlender {2}", t.name, sourceMaterial.matsAndGOs.mats[0].mat, resultMaterialTextureBlender)); + for (int i = 0; i < t.height; i++) + { + Color[] cs = t.GetPixels(0, i, t.width, 1); + for (int j = 0; j < cs.Length; j++) + { + cs[j] = resultMaterialTextureBlender.OnBlendTexturePixel(shaderPropertyName.name, cs[j]); + } + t.SetPixels(0, i, t.width, 1, cs); + } + t.Apply(); + return t; + } + + public void AdjustNonTextureProperties(Material resultMat, List<ShaderTextureProperty> texPropertyNames, MB2_EditorMethodsInterface editorMethods) + { + if (resultMat == null || texPropertyNames == null) return; + + //try to use a texture blender if we can find one to set the non-texture property values + if (_textureProperties.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Adjusting non texture properties using TextureBlender for shader: " + resultMat.shader.name); + resultMaterialTextureBlender.SetNonTexturePropertyValuesOnResultMaterial(resultMat); + + if (editorMethods != null) + { + editorMethods.CommitChangesToAssets(); + } + } + + public Color GetColorAsItWouldAppearInAtlasIfNoTexture(Material matIfBlender, ShaderTextureProperty texProperty) + { + resultMaterialTextureBlender.OnBeforeTintTexture(matIfBlender, texProperty.name); + Color c = GetColorForTemporaryTexture(matIfBlender, texProperty); + return resultMaterialTextureBlender.OnBlendTexturePixel(texProperty.name, c); + } + + public Color GetColorForTemporaryTexture(Material matIfBlender, ShaderTextureProperty texProperty) + { + return resultMaterialTextureBlender.GetColorIfNoTexture(matIfBlender, texProperty); + } + } + + public static Color NEUTRAL_NORMAL_MAP_COLOR = new Color(.5f, .5f, 1f); + + TexPropertyNameColorPair[] defaultTextureProperty2DefaultColorMap = new TexPropertyNameColorPair[] + { + new TexPropertyNameColorPair("_MainTex", new Color(1f, 1f, 1f, 0f)), + new TexPropertyNameColorPair("_MetallicGlossMap", new Color(0f, 0f, 0f, 1f)), + new TexPropertyNameColorPair("_ParallaxMap", new Color(0f, 0f, 0f, 0f)), + new TexPropertyNameColorPair("_OcclusionMap", new Color(1f, 1f, 1f, 1f)), + new TexPropertyNameColorPair("_EmissionMap", new Color(0f, 0f, 0f, 0f)), + new TexPropertyNameColorPair("_DetailMask", new Color(0f, 0f, 0f, 0f)), + }; + + MB3_TextureCombinerNonTextureProperties.MaterialProperty[] _nonTextureProperties = new MB3_TextureCombinerNonTextureProperties.MaterialProperty[] + { + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyColor("_Color", Color.white), + //new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_Cutoff"), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_Glossiness", .5f), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_GlossMapScale", 1f), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_Metallic", 0f), + //new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_SpecularHightlights"), + //new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_GlossyReflections"), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_BumpScale", .1f), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_Parallax", .02f), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyFloat("_OcclusionStrength", 1f), + new MB3_TextureCombinerNonTextureProperties.MaterialPropertyColor("_EmissionColor", Color.black), + }; + + MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + bool _considerNonTextureProperties = false; + private TextureBlender resultMaterialTextureBlender; + private TextureBlender[] textureBlenders = new TextureBlender[0]; + private Dictionary<string, Color> textureProperty2DefaultColorMap = new Dictionary<string, Color>(); + private NonTextureProperties _nonTexturePropertiesBlender; + + public MB3_TextureCombinerNonTextureProperties(MB2_LogLevel ll, bool considerNonTextureProps) + { + _considerNonTextureProperties = considerNonTextureProps; + textureProperty2DefaultColorMap = new Dictionary<string, Color>(); + for (int i = 0; i < defaultTextureProperty2DefaultColorMap.Length; i++) + { + textureProperty2DefaultColorMap.Add(defaultTextureProperty2DefaultColorMap[i].name, + defaultTextureProperty2DefaultColorMap[i].color); + _nonTexturePropertiesBlender = new NonTexturePropertiesDontBlendProps(this); + } + } + + internal void CollectAverageValuesOfNonTextureProperties(Material resultMaterial, Material mat) + { + for (int i = 0; i < _nonTextureProperties.Length; i++) + { + MB3_TextureCombinerNonTextureProperties.MaterialProperty prop = _nonTextureProperties[i]; + if (resultMaterial.HasProperty(prop.PropertyName)) + { + prop.GetAverageCalculator().TryGetPropValueFromMaterialAndBlendIntoAverage(mat, prop); + } + } + } + + internal void LoadTextureBlendersIfNeeded(Material resultMaterial) + { + if (_considerNonTextureProperties) + { + LoadTextureBlenders(); + FindBestTextureBlender(resultMaterial); + } + } + +#if UNITY_WSA && !UNITY_EDITOR + //not defined for WSA runtime +#else + private static bool InterfaceFilter(Type typeObj, System.Object criteriaObj) + { + return typeObj.ToString() == criteriaObj.ToString(); + } +#endif + + private void FindBestTextureBlender(Material resultMaterial) + { + Debug.Assert(_considerNonTextureProperties); + resultMaterialTextureBlender = FindMatchingTextureBlender(resultMaterial.shader.name); + if (resultMaterialTextureBlender != null) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Using Consider Non-Texture Properties found a TextureBlender for result material. Using: " + resultMaterialTextureBlender); + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.error) Debug.LogWarning("Using _considerNonTextureProperties could not find a TextureBlender that matches the shader on the result material. Using the Fallback Texture Blender."); + resultMaterialTextureBlender = new TextureBlenderFallback(); + } + _nonTexturePropertiesBlender = new NonTexturePropertiesBlendProps(this, resultMaterialTextureBlender); + } + + private void LoadTextureBlenders() + { +#if UNITY_WSA && !UNITY_EDITOR + //not defined for WSA runtime +#else + Debug.Assert(_considerNonTextureProperties); + string qualifiedInterfaceName = "DigitalOpus.MB.Core.TextureBlender"; + var interfaceFilter = new TypeFilter(InterfaceFilter); + List<Type> types = new List<Type>(); + foreach (Assembly ass in AppDomain.CurrentDomain.GetAssemblies()) + { + System.Collections.IEnumerable typesIterator = null; + try + { + typesIterator = ass.GetTypes(); + } + catch (Exception e) + { + //Debug.Log("The assembly that I could not read types for was: " + ass.GetName()); + //suppress error + e.Equals(null); + } + if (typesIterator != null) + { + foreach (Type ty in ass.GetTypes()) + { + var myInterfaces = ty.FindInterfaces(interfaceFilter, qualifiedInterfaceName); + if (myInterfaces.Length > 0) + { + types.Add(ty); + } + } + } + } + + TextureBlender fallbackTB = null; + List<TextureBlender> textureBlendersList = new List<TextureBlender>(); + foreach (Type tt in types) + { + if (!tt.IsAbstract && !tt.IsInterface) + { + TextureBlender instance = (TextureBlender) System.Activator.CreateInstance(tt); + if (instance is TextureBlenderFallback) + { + fallbackTB = instance; + } + else + { + textureBlendersList.Add(instance); + } + } + } + + if (fallbackTB != null) textureBlendersList.Add(fallbackTB); // must come last in list + textureBlenders = textureBlendersList.ToArray(); + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log(string.Format("Loaded {0} TextureBlenders.", textureBlenders.Length)); + } +#endif + } + + internal bool NonTexturePropertiesAreEqual(Material a, Material b) + { + return _nonTexturePropertiesBlender.NonTexturePropertiesAreEqual(a, b); + } + + internal Texture2D TintTextureWithTextureCombiner(Texture2D t, MB_TexSet sourceMaterial, ShaderTextureProperty shaderPropertyName) + { + return _nonTexturePropertiesBlender.TintTextureWithTextureCombiner(t, sourceMaterial, shaderPropertyName); + } + + //If we are switching from a Material that uses color properties to + //using atlases don't want some properties such as _Color to be copied + //from the original material because the atlas texture will be multiplied + //by that color + internal void AdjustNonTextureProperties(Material resultMat, List<ShaderTextureProperty> texPropertyNames, MB2_EditorMethodsInterface editorMethods) + { + if (resultMat == null || texPropertyNames == null) return; + _nonTexturePropertiesBlender.AdjustNonTextureProperties(resultMat, texPropertyNames, editorMethods); + } + + internal Color GetColorAsItWouldAppearInAtlasIfNoTexture(Material matIfBlender, ShaderTextureProperty texProperty) + { + return _nonTexturePropertiesBlender.GetColorAsItWouldAppearInAtlasIfNoTexture(matIfBlender, texProperty); + } + + internal Color GetColorForTemporaryTexture(Material matIfBlender, ShaderTextureProperty texProperty) + { + return _nonTexturePropertiesBlender.GetColorForTemporaryTexture(matIfBlender, texProperty); + } + + private TextureBlender FindMatchingTextureBlender(string shaderName) + { + for (int i = 0; i < textureBlenders.Length; i++) + { + if (textureBlenders[i].DoesShaderNameMatch(shaderName)) + { + return textureBlenders[i]; + } + } + return null; + } + + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerNonTextureProperties.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerNonTextureProperties.cs.meta new file mode 100644 index 00000000..8e8859a2 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerNonTextureProperties.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 42ef8fd238db6b548b7ce213f220977c +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBaker.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBaker.cs new file mode 100644 index 00000000..d1fde711 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBaker.cs @@ -0,0 +1,183 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + internal class MB3_TextureCombinerPackerMeshBaker : MB3_TextureCombinerPackerRoot + { + public override bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data) + { + return true; + } + + public override IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Rect[] uvRects = packedAtlasRects.rects; + + int atlasSizeX = packedAtlasRects.atlasX; + int atlasSizeY = packedAtlasRects.atlasY; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Generated atlas will be " + atlasSizeX + "x" + atlasSizeY); + + for (int propIdx = 0; propIdx < data.numAtlases; propIdx++) + { + Texture2D atlas = null; + ShaderTextureProperty property = data.texPropertyNames[propIdx]; + if (!MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(propIdx, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + atlas = null; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("=== Not creating atlas for " + property.name + " because textures are null and default value parameters are the same."); + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("=== Creating atlas for " + property.name); + + GC.Collect(); + + CreateTemporaryTexturesForAtlas(data.distinctMaterialTextures, combiner, propIdx, data); + + //use a jagged array because it is much more efficient in memory + Color[][] atlasPixels = new Color[atlasSizeY][]; + for (int j = 0; j < atlasPixels.Length; j++) + { + atlasPixels[j] = new Color[atlasSizeX]; + } + + bool isNormalMap = false; + if (property.isNormalMap) isNormalMap = true; + + for (int texSetIdx = 0; texSetIdx < data.distinctMaterialTextures.Count; texSetIdx++) + { + MB_TexSet texSet = data.distinctMaterialTextures[texSetIdx]; + MeshBakerMaterialTexture matTex = texSet.ts[propIdx]; + string s = "Creating Atlas '" + property.name + "' texture " + matTex.GetTexName(); + if (progressInfo != null) progressInfo(s, .01f); + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("Adding texture {0} to atlas {1} for texSet {2} srcMat {3}", matTex.GetTexName(), property.name, texSetIdx, texSet.matsAndGOs.mats[0].GetMaterialName())); + Rect r = uvRects[texSetIdx]; + Texture2D t = texSet.ts[propIdx].GetTexture2D(); + int x = Mathf.RoundToInt(r.x * atlasSizeX); + int y = Mathf.RoundToInt(r.y * atlasSizeY); + int ww = Mathf.RoundToInt(r.width * atlasSizeX); + int hh = Mathf.RoundToInt(r.height * atlasSizeY); + if (ww == 0 || hh == 0) Debug.LogError("Image in atlas has no height or width " + r); + if (progressInfo != null) progressInfo(s + " set ReadWrite flag", .01f); + if (textureEditorMethods != null) textureEditorMethods.SetReadWriteFlag(t, true, true); + if (progressInfo != null) progressInfo(s + "Copying to atlas: '" + matTex.GetTexName() + "'", .02f); + DRect samplingRect = texSet.ts[propIdx].GetEncapsulatingSamplingRect(); + Debug.Assert(!texSet.ts[propIdx].isNull, string.Format("Adding texture {0} to atlas {1} for texSet {2} srcMat {3}", matTex.GetTexName(), property.name, texSetIdx, texSet.matsAndGOs.mats[0].GetMaterialName())); + yield return CopyScaledAndTiledToAtlas(texSet.ts[propIdx], texSet, property, samplingRect, x, y, ww, hh, packedAtlasRects.padding[texSetIdx], atlasPixels, isNormalMap, data, combiner, progressInfo, LOG_LEVEL); + } + + yield return data.numAtlases; + if (progressInfo != null) progressInfo("Applying changes to atlas: '" + property.name + "'", .03f); + atlas = new Texture2D(atlasSizeX, atlasSizeY, TextureFormat.ARGB32, true); + for (int j = 0; j < atlasPixels.Length; j++) + { + atlas.SetPixels(0, j, atlasSizeX, 1, atlasPixels[j]); + } + + atlas.Apply(); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Saving atlas " + property.name + " w=" + atlas.width + " h=" + atlas.height); + } + + atlases[propIdx] = atlas; + if (progressInfo != null) progressInfo("Saving atlas: '" + property.name + "'", .04f); + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + if (data.resultType == MB2_TextureBakeResults.ResultType.atlas) + { + SaveAtlasAndConfigureResultMaterial(data, textureEditorMethods, atlases[propIdx], data.texPropertyNames[propIdx], propIdx); + } + + combiner._destroyTemporaryTextures(data.texPropertyNames[propIdx].name); + } + + yield break; + } + + internal static IEnumerator CopyScaledAndTiledToAtlas(MeshBakerMaterialTexture source, MB_TexSet sourceMaterial, + ShaderTextureProperty shaderPropertyName, DRect srcSamplingRect, int targX, int targY, int targW, int targH, + AtlasPadding padding, + Color[][] atlasPixels, bool isNormalMap, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + ProgressUpdateDelegate progressInfo = null, + MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info) + { + //HasFinished = false; + Texture2D t = source.GetTexture2D(); + if (LOG_LEVEL >= MB2_LogLevel.debug) + { + Debug.Log(String.Format("CopyScaledAndTiledToAtlas: {0} inAtlasX={1} inAtlasY={2} inAtlasW={3} inAtlasH={4} paddX={5} paddY={6} srcSamplingRect={7}", + t, targX, targY, targW, targH, padding.leftRight, padding.topBottom, srcSamplingRect)); + } + float newWidth = targW; + float newHeight = targH; + float scx = (float)srcSamplingRect.width; + float scy = (float)srcSamplingRect.height; + float ox = (float)srcSamplingRect.x; + float oy = (float)srcSamplingRect.y; + int w = (int)newWidth; + int h = (int)newHeight; + if (data._considerNonTextureProperties) + { + t = combiner._createTextureCopy(shaderPropertyName.name, t); + t = data.nonTexturePropertyBlender.TintTextureWithTextureCombiner(t, sourceMaterial, shaderPropertyName); + } + for (int i = 0; i < w; i++) + { + + if (progressInfo != null && w > 0) progressInfo("CopyScaledAndTiledToAtlas " + (((float)i / (float)w) * 100f).ToString("F0"), .2f); + for (int j = 0; j < h; j++) + { + float u = i / newWidth * scx + ox; + float v = j / newHeight * scy + oy; + atlasPixels[targY + j][targX + i] = t.GetPixelBilinear(u, v); + } + } + + //bleed the border colors into the padding + for (int i = 0; i < w; i++) + { + for (int j = 1; j <= padding.topBottom; j++) + { + //top margin + atlasPixels[(targY - j)][targX + i] = atlasPixels[(targY)][targX + i]; + //bottom margin + atlasPixels[(targY + h - 1 + j)][targX + i] = atlasPixels[(targY + h - 1)][targX + i]; + } + } + for (int j = 0; j < h; j++) + { + for (int i = 1; i <= padding.leftRight; i++) + { + //left margin + atlasPixels[(targY + j)][targX - i] = atlasPixels[(targY + j)][targX]; + //right margin + atlasPixels[(targY + j)][targX + w + i - 1] = atlasPixels[(targY + j)][targX + w - 1]; + } + } + //corners + for (int i = 1; i <= padding.leftRight; i++) + { + for (int j = 1; j <= padding.topBottom; j++) + { + atlasPixels[(targY - j)][targX - i] = atlasPixels[targY][targX]; + atlasPixels[(targY + h - 1 + j)][targX - i] = atlasPixels[(targY + h - 1)][targX]; + atlasPixels[(targY + h - 1 + j)][targX + w + i - 1] = atlasPixels[(targY + h - 1)][targX + w - 1]; + atlasPixels[(targY - j)][targX + w + i - 1] = atlasPixels[targY][targX + w - 1]; + yield return null; + } + yield return null; + } + // Debug.Log("copyandscaledatlas finished too!"); + //HasFinished = true; + yield break; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBaker.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBaker.cs.meta new file mode 100644 index 00000000..e808a6c6 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBaker.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d8041a03bdfbc294785f7f116e695801 +timeCreated: 1522041818 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFast.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFast.cs new file mode 100644 index 00000000..3c8b245e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFast.cs @@ -0,0 +1,117 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + internal class MB3_TextureCombinerPackerMeshBakerFast : MB_ITextureCombinerPacker + { + public bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data) + { + return true; + } + + public IEnumerator ConvertTexturesToReadableFormats(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + //MB3_TextureCombinerPackerRoot.MakeProceduralTexturesReadable(progressInfo, result, data, combiner, textureEditorMethods, LOG_LEVEL); + yield break; + } + + public virtual AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + return MB3_TextureCombinerPackerRoot.CalculateAtlasRectanglesStatic(data, doMultiAtlas, LOG_LEVEL); + } + + public IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + Rect[] uvRects = packedAtlasRects.rects; + + int atlasSizeX = packedAtlasRects.atlasX; + int atlasSizeY = packedAtlasRects.atlasY; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Generated atlas will be " + atlasSizeX + "x" + atlasSizeY); + + //create a game object + GameObject renderAtlasesGO = null; + try + { + renderAtlasesGO = new GameObject("MBrenderAtlasesGO"); + MB3_AtlasPackerRenderTexture atlasRenderTexture = renderAtlasesGO.AddComponent<MB3_AtlasPackerRenderTexture>(); + renderAtlasesGO.AddComponent<Camera>(); + if (data._considerNonTextureProperties && LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogError("Blend Non-Texture Properties has limited functionality when used with Mesh Baker Texture Packer Fast. If no texture is pesent, then a small texture matching the non-texture property will be created and used in the atlas. But non-texture properties will not be blended into texture."); + + for (int propIdx = 0; propIdx < data.numAtlases; propIdx++) + { + Texture2D atlas = null; + if (!MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(propIdx, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + atlas = null; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Not creating atlas for " + data.texPropertyNames[propIdx].name + " because textures are null and default value parameters are the same."); + } + else + { + GC.Collect(); + + MB3_TextureCombinerPackerRoot.CreateTemporaryTexturesForAtlas(data.distinctMaterialTextures, combiner, propIdx, data); + + if (progressInfo != null) progressInfo("Creating Atlas '" + data.texPropertyNames[propIdx].name + "'", .01f); + // =========== + // configure it + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("About to render " + data.texPropertyNames[propIdx].name + " isNormal=" + data.texPropertyNames[propIdx].isNormalMap); + atlasRenderTexture.LOG_LEVEL = LOG_LEVEL; + atlasRenderTexture.width = atlasSizeX; + atlasRenderTexture.height = atlasSizeY; + atlasRenderTexture.padding = data._atlasPadding; + atlasRenderTexture.rects = uvRects; + atlasRenderTexture.textureSets = data.distinctMaterialTextures; + atlasRenderTexture.indexOfTexSetToRender = propIdx; + atlasRenderTexture.texPropertyName = data.texPropertyNames[propIdx]; + atlasRenderTexture.isNormalMap = data.texPropertyNames[propIdx].isNormalMap; + atlasRenderTexture.fixOutOfBoundsUVs = data._fixOutOfBoundsUVs; + atlasRenderTexture.considerNonTextureProperties = data._considerNonTextureProperties; + atlasRenderTexture.resultMaterialTextureBlender = data.nonTexturePropertyBlender; + // call render on it + atlas = atlasRenderTexture.OnRenderAtlas(combiner); + + // destroy it + // ============= + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Saving atlas " + data.texPropertyNames[propIdx].name + " w=" + atlas.width + " h=" + atlas.height + " id=" + atlas.GetInstanceID()); + } + atlases[propIdx] = atlas; + if (progressInfo != null) progressInfo("Saving atlas: '" + data.texPropertyNames[propIdx].name + "'", .04f); + if (data.resultType == MB2_TextureBakeResults.ResultType.atlas) + { + MB3_TextureCombinerPackerRoot.SaveAtlasAndConfigureResultMaterial(data, textureEditorMethods, atlases[propIdx], data.texPropertyNames[propIdx], propIdx); + } + + combiner._destroyTemporaryTextures(data.texPropertyNames[propIdx].name); // need to save atlases before doing this + } + } + catch (Exception ex) + { + //Debug.LogError(ex); + Debug.LogError(ex.Message + "\n" + ex.StackTrace.ToString()); + } + finally + { + if (renderAtlasesGO != null) + { + MB_Utility.Destroy(renderAtlasesGO); + } + } + yield break; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFast.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFast.cs.meta new file mode 100644 index 00000000..e9b42bf1 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFast.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 43ddbe0f0a808d64fbf472d35ca2b903 +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFastV2.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFastV2.cs new file mode 100644 index 00000000..063de60d --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFastV2.cs @@ -0,0 +1,520 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + //TODO + // + // What about normal maps? + // + internal class MB3_TextureCombinerPackerMeshBakerFastV2 : MB_ITextureCombinerPacker + { + Mesh mesh; + GameObject renderAtlasesGO; + GameObject cameraGameObject; + + public bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data) + { + string layerName = LayerMask.LayerToName(data._layerTexturePackerFastV2); + if (layerName == null || layerName.Length == 0) + { + Debug.LogError("The MB3_MeshBaker -> 'Atlas Render Layer' has not been set. This should be set to a layer that has no other renderers on it."); + return false; + } + + if (Application.isEditor) + { + Renderer[] rs = GameObject.FindObjectsOfType<Renderer>(); + bool isObjsOnLayer = false; + for (int i = 0; i < rs.Length; i++) + { + if (rs[i].gameObject.layer == data._layerTexturePackerFastV2) + { + isObjsOnLayer = true; + } + } + + if (isObjsOnLayer) + { + Debug.LogError("There are Renderers in the scene that are on layer '" + layerName + "'. 'Atlas Render Layer' layer should have no renderers that use it."); + return false; + } + } + + // Tried adding a check for BuildSettings.DefineSymbols but it is messy because that is editor only code. + + return true; + } + + public IEnumerator ConvertTexturesToReadableFormats(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + //MB3_TextureCombinerPackerRoot.MakeProceduralTexturesReadable(progressInfo, result, data, combiner, textureEditorMethods, LOG_LEVEL); + yield break; + } + + public virtual AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + return MB3_TextureCombinerPackerRoot.CalculateAtlasRectanglesStatic(data, doMultiAtlas, LOG_LEVEL); + } + + public IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + Rect[] uvRects = packedAtlasRects.rects; + + int atlasSizeX = packedAtlasRects.atlasX; + int atlasSizeY = packedAtlasRects.atlasY; + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Generated atlas will be " + atlasSizeX + "x" + atlasSizeY); + + int layer = data._layerTexturePackerFastV2; + Debug.Assert(layer >= 0 && layer <= 32); + + //create a game object + mesh = new Mesh(); + renderAtlasesGO = null; + cameraGameObject = null; + try + { + System.Diagnostics.Stopwatch db_time_MB3_TextureCombinerPackerMeshBakerFastV2_CreateAtlases = new System.Diagnostics.Stopwatch(); + db_time_MB3_TextureCombinerPackerMeshBakerFastV2_CreateAtlases.Start(); + renderAtlasesGO = new GameObject("MBrenderAtlasesGO"); + cameraGameObject = new GameObject("MBCameraGameObject"); + MB3_AtlasPackerRenderTextureUsingMesh atlasRenderer = new MB3_AtlasPackerRenderTextureUsingMesh(); + OneTimeSetup(atlasRenderer, renderAtlasesGO, cameraGameObject, atlasSizeX, atlasSizeY, data._atlasPadding, layer, LOG_LEVEL); + + if (data._considerNonTextureProperties && LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Blend Non-Texture Properties has limited functionality when used with Mesh Baker Texture Packer Fast."); + + List<Material> mats = new List<Material>(); + for (int propIdx = 0; propIdx < data.numAtlases; propIdx++) + { + Texture2D atlas = null; + if (!MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(propIdx, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + atlas = null; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Not creating atlas for " + data.texPropertyNames[propIdx].name + " because textures are null and default value parameters are the same."); + } + else + { + if (progressInfo != null) progressInfo("Creating Atlas '" + data.texPropertyNames[propIdx].name + "'", .01f); + + // configure it + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("About to render " + data.texPropertyNames[propIdx].name + " isNormal=" + data.texPropertyNames[propIdx].isNormalMap); + + // Create the mesh + mats.Clear(); + + MB3_AtlasPackerRenderTextureUsingMesh.MeshAtlas.BuildAtlas(packedAtlasRects, data.distinctMaterialTextures, propIdx, packedAtlasRects.atlasX, packedAtlasRects.atlasY, mesh, mats, data.texPropertyNames[propIdx], data, combiner, textureEditorMethods, LOG_LEVEL); + { + MeshFilter mf = renderAtlasesGO.GetComponent<MeshFilter>(); + mf.sharedMesh = mesh; + MeshRenderer mrr = renderAtlasesGO.GetComponent<MeshRenderer>(); + Material[] mrs = mats.ToArray(); + mrr.sharedMaterials = mrs; + } + + // Render + atlas = atlasRenderer.DoRenderAtlas(cameraGameObject, packedAtlasRects.atlasX, packedAtlasRects.atlasY, data.texPropertyNames[propIdx].isNormalMap, data.texPropertyNames[propIdx]); + + { + for (int i = 0; i < mats.Count; i++) + { + MB_Utility.Destroy(mats[i]); + } + mats.Clear(); + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Saving atlas " + data.texPropertyNames[propIdx].name + " w=" + atlas.width + " h=" + atlas.height + " id=" + atlas.GetInstanceID()); + } + atlases[propIdx] = atlas; + if (progressInfo != null) progressInfo("Saving atlas: '" + data.texPropertyNames[propIdx].name + "'", .04f); + if (data.resultType == MB2_TextureBakeResults.ResultType.atlas) + { + MB3_TextureCombinerPackerRoot.SaveAtlasAndConfigureResultMaterial(data, textureEditorMethods, atlases[propIdx], data.texPropertyNames[propIdx], propIdx); + } + + combiner._destroyTemporaryTextures(data.texPropertyNames[propIdx].name); // need to save atlases before doing this + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.LogFormat("Timing MB3_TextureCombinerPackerMeshBakerFastV2.CreateAtlases={0}", + db_time_MB3_TextureCombinerPackerMeshBakerFastV2_CreateAtlases.ElapsedMilliseconds * .001f); + } + catch (Exception ex) + { + Debug.LogError(ex.Message + "\n" + ex.StackTrace.ToString()); + } + finally + { + if (renderAtlasesGO != null) { + MB_Utility.Destroy(renderAtlasesGO); + } + if (cameraGameObject != null) { + MB_Utility.Destroy(cameraGameObject); + } + if (mesh != null) { + MB_Utility.Destroy(mesh); + } + + } + yield break; + } + + void OneTimeSetup(MB3_AtlasPackerRenderTextureUsingMesh atlasRenderer, GameObject atlasMesh, GameObject cameraGameObject, int atlasWidth, int atlasHeight, int padding, int layer, MB2_LogLevel logLevel) + { + { + // Set up game object for holding the atlas mesh + atlasMesh.AddComponent<MeshFilter>(); + atlasMesh.AddComponent<MeshRenderer>(); + atlasMesh.transform.rotation = Quaternion.Euler(0, 0, 0); + atlasMesh.transform.position = new Vector3(0, 0, .5f); + atlasMesh.gameObject.layer = layer; + } + + // set up the camera + { + atlasRenderer.Initialize(layer, + atlasWidth, + atlasHeight, + padding, + logLevel); + atlasRenderer.SetupCameraGameObject(cameraGameObject); + } + } + + } + + + public class MB3_AtlasPackerRenderTextureUsingMesh + { + public int camMaskLayer; + + public int width; + public int height; + public int padding; + public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info; + + bool _initialized = false; + bool _camSetup = false; + + public void Initialize( + int camMaskLayer, + int width, + int height, + int padding, + MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info + ) + { + this.camMaskLayer = camMaskLayer; + this.width = width; + this.height = height; + this.padding = padding; + this.LOG_LEVEL = LOG_LEVEL; + _initialized = true; + } + + internal void SetupCameraGameObject(GameObject camGameObject) + { + Debug.Assert(_initialized); + Debug.Assert(LayerMask.LayerToName(camMaskLayer) != null || LayerMask.LayerToName(camMaskLayer) != ""); + LayerMask camMask = 1 << camMaskLayer; + Camera myCamera = camGameObject.AddComponent<Camera>(); + myCamera.enabled = false; + myCamera.orthographic = true; + myCamera.orthographicSize = height / 2f; + myCamera.aspect = ((float)width) / height; + myCamera.rect = new Rect(0, 0, 1, 1); + myCamera.clearFlags = CameraClearFlags.Color; + myCamera.cullingMask = camMask; + Transform camTransform = myCamera.GetComponent<Transform>(); + camTransform.localPosition = new Vector3(width / 2.0f, height / 2f, 0); + camTransform.localRotation = Quaternion.Euler(0, 0, 0); + + MBVersion.DoSpecialRenderPipeline_TexturePackerFastSetup(camGameObject); + + _camSetup = true; + } + + internal Texture2D DoRenderAtlas(GameObject go, int width, int height, bool isNormalMap, ShaderTextureProperty propertyName) + { + System.Diagnostics.Stopwatch db_time_DoRenderAtlas = new System.Diagnostics.Stopwatch(); + db_time_DoRenderAtlas.Start(); + Debug.Assert(_initialized && _camSetup); + RenderTexture _destinationTexture; + if (isNormalMap) + { + _destinationTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear); + } + else + { + _destinationTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); + } + + _destinationTexture.filterMode = FilterMode.Point; + Camera myCamera = go.GetComponent<Camera>(); + myCamera.targetTexture = _destinationTexture; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(string.Format("Begin Camera.Render destTex w={0} h={1} camPos={2} camSize={3} camAspect={4}", width, height, go.transform.localPosition, myCamera.orthographicSize, myCamera.aspect.ToString("f5"))); + myCamera.Render(); + + System.Diagnostics.Stopwatch db_ConvertRenderTextureToTexture2D = new System.Diagnostics.Stopwatch(); + db_ConvertRenderTextureToTexture2D.Start(); + Texture2D tempTexture = new Texture2D(_destinationTexture.width, _destinationTexture.height, TextureFormat.ARGB32, true, false); + MB_TextureCombinerRenderTexture.ConvertRenderTextureToTexture2D(_destinationTexture, MB_TextureCombinerRenderTexture.YisFlipped(LOG_LEVEL), isNormalMap, LOG_LEVEL, tempTexture); + + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Finished rendering atlas " + propertyName.name + " db_time_DoRenderAtlas:" + (db_time_DoRenderAtlas.ElapsedMilliseconds * .001f) + " db_ConvertRenderTextureToTexture2D:" + (db_ConvertRenderTextureToTexture2D.ElapsedMilliseconds * .001f)); + MB_Utility.Destroy(_destinationTexture); + return tempTexture; + } + + public class MeshRectInfo + { + public int vertIdx; + public int triIdx; + public int atlasIdx; + } + + public class MeshAtlas + { + internal static void BuildAtlas( + AtlasPackingResult packedAtlasRects, + List<MB_TexSet> distinctMaterialTextures, + int propIdx, + int atlasSizeX, int atlasSizeY, + Mesh m, + List<Material> generatedMats, + ShaderTextureProperty property, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + // Collect vertices and quads for mesh that we will use for the atlas. + Debug.Assert(generatedMats.Count == 0, "Previous mats should have been destroyed"); + generatedMats.Clear(); + List<Vector3> vs = new List<Vector3>(); + List<Vector2> uvs = new List<Vector2>(); + + // One submesh and material per texture that we are packing + List<int>[] ts = new List<int>[distinctMaterialTextures.Count]; + for (int i = 0; i < ts.Length; i++) + { + ts[i] = new List<int>(); + } + + MeshBakerMaterialTexture.readyToBuildAtlases = true; + GC.Collect(); + MB3_TextureCombinerPackerRoot.CreateTemporaryTexturesForAtlas(data.distinctMaterialTextures, combiner, propIdx, data); + + Rect[] uvRects = packedAtlasRects.rects; + for (int texSetIdx = 0; texSetIdx < distinctMaterialTextures.Count; texSetIdx++) + { + MB_TexSet texSet = distinctMaterialTextures[texSetIdx]; + MeshBakerMaterialTexture matTex = texSet.ts[propIdx]; + + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("Adding texture {0} to atlas {1} for texSet {2} srcMat {3}", matTex.GetTexName(), property.name, texSetIdx, texSet.matsAndGOs.mats[0].GetMaterialName())); + Rect r = uvRects[texSetIdx]; + Texture2D t = matTex.GetTexture2D(); + int x = Mathf.RoundToInt(r.x * atlasSizeX); + int y = Mathf.RoundToInt(r.y * atlasSizeY); + int ww = Mathf.RoundToInt(r.width * atlasSizeX); + int hh = Mathf.RoundToInt(r.height * atlasSizeY); + r = new Rect(x, y, ww, hh); + if (ww == 0 || hh == 0) Debug.LogError("Image in atlas has no height or width " + r); + DRect samplingRect = texSet.ts[propIdx].GetEncapsulatingSamplingRect(); + Debug.Assert(!texSet.ts[propIdx].isNull, string.Format("Adding texture {0} to atlas {1} for texSet {2} srcMat {3}", matTex.GetTexName(), property.name, texSetIdx, texSet.matsAndGOs.mats[0].GetMaterialName())); + + AtlasPadding padding = packedAtlasRects.padding[texSetIdx]; + AddNineSlicedRect(r, padding.leftRight, padding.topBottom, samplingRect.GetRect(), vs, uvs, ts[texSetIdx], t.width, t.height, t.name); + + Material mt = new Material(Shader.Find("MeshBaker/Unlit/UnlitWithAlpha")); + + bool isSavingAsANormalMapAssetThatWillBeImported = property.isNormalMap && data._saveAtlasesAsAssets; + MBVersion.PipelineType pipelineType = MBVersion.DetectPipeline(); + if (pipelineType == MBVersion.PipelineType.URP) + { + ConfigureMaterial_DefaultPipeline(mt, t, isSavingAsANormalMapAssetThatWillBeImported, LOG_LEVEL); + //ConfigureMaterial_URP(mt, t, isSavingAsANormalMapAssetThatWillBeImported, LOG_LEVEL); + } + else if (pipelineType == MBVersion.PipelineType.HDRP) + { + ConfigureMaterial_DefaultPipeline(mt, t, isSavingAsANormalMapAssetThatWillBeImported, LOG_LEVEL); + } + else + { + ConfigureMaterial_DefaultPipeline(mt, t, isSavingAsANormalMapAssetThatWillBeImported, LOG_LEVEL); + } + + generatedMats.Add(mt); + } + + // Apply to the mesh + m.Clear(); + m.vertices = vs.ToArray(); + m.uv = uvs.ToArray(); + m.subMeshCount = ts.Length; + for (int i = 0; i < m.subMeshCount; i++) + { + m.SetIndices(ts[i].ToArray(), MeshTopology.Triangles, i); + } + MeshBakerMaterialTexture.readyToBuildAtlases = false; + } + + static void ConfigureMaterial_DefaultPipeline(Material mt, Texture2D t, bool isSavingAsANormalMapAssetThatWillBeImported, MB2_LogLevel LOG_LEVEL) + { + Shader shad = null; + shad = Shader.Find("MeshBaker/Unlit/UnlitWithAlpha"); + Debug.Assert(shad != null, "Could not find shader MeshBaker/Unlit/UnlitWithAlpha"); + mt.shader = shad; + mt.SetTexture("_MainTex", t); + if (isSavingAsANormalMapAssetThatWillBeImported) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Unswizling normal map channels NM"); + mt.SetFloat("_SwizzleNormalMapChannelsNM", 1f); + mt.EnableKeyword("_SWIZZLE_NORMAL_CHANNELS_NM"); + } + else + { + //if (property.isNormalMap && LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("NOT Unswizling normal map channel savingAtlases=" + isSavingAsANormalMapAssetThatWillBeImported + " platformSwizzlesNormalMap=" + platformSwizzlesNormalMap); + mt.SetFloat("_SwizzleNormalMapChannelsNM", 0f); + mt.DisableKeyword("_SWIZZLE_NORMAL_CHANNELS_NM"); + } + } + + public static MeshRectInfo AddQuad(Rect wldRect, Rect uvRect, List<Vector3> verts, List<Vector2> uvs, List<int> tris) + { + MeshRectInfo mri = new MeshRectInfo(); + int rootIdx = mri.vertIdx = verts.Count; + mri.triIdx = tris.Count; + + verts.Add(new Vector3(wldRect.x, wldRect.y, 0)); + verts.Add(new Vector3(wldRect.x + wldRect.width, wldRect.y, 0)); + verts.Add(new Vector3(wldRect.x, wldRect.y + wldRect.height, 0)); + verts.Add(new Vector3(wldRect.x + wldRect.width, wldRect.y + wldRect.height, 0)); + + uvs.Add(new Vector2(uvRect.x, uvRect.y)); + uvs.Add(new Vector2(uvRect.x + uvRect.width, uvRect.y)); + uvs.Add(new Vector2(uvRect.x, uvRect.y + uvRect.height)); + uvs.Add(new Vector2(uvRect.x + uvRect.width, uvRect.y + uvRect.height)); + + tris.Add(rootIdx + 0); tris.Add(rootIdx + 2); tris.Add(rootIdx + 1); + tris.Add(rootIdx + 2); tris.Add(rootIdx + 3); tris.Add(rootIdx + 1); + return mri; + } + + public static void AddNineSlicedRect(Rect atlasRectRaw, float paddingX, float paddingY, Rect srcUVRectt, List<Vector3> verts, List<Vector2> uvs, List<int> tris, float srcTexWidth, float srcTexHeight, string texName) + { + float singlePixelHalfWidth = .5f / srcTexWidth; + float singlePixelHalfHeight = .5f / srcTexHeight; + + float smallWidth = 0f; + float smallHeight = 0f; + + Rect srcUVRecttt = srcUVRectt; + + Rect srcRectMinusHalfPix = srcUVRectt; + { + srcRectMinusHalfPix.x += singlePixelHalfWidth; + srcRectMinusHalfPix.y += singlePixelHalfHeight; + srcRectMinusHalfPix.width -= singlePixelHalfWidth * 2f; + srcRectMinusHalfPix.height -= singlePixelHalfHeight * 2f; + } + + Rect atlasRect = atlasRectRaw; + + AddQuad(atlasRectRaw, srcUVRecttt, verts, uvs, tris); + + bool addTopBottom = paddingY > 0f; + bool addLeftRight = paddingX > 0f; + Rect uvRectPix; + + //Top + + if (addTopBottom) + { + uvRectPix = new Rect(srcUVRecttt.x, + srcUVRecttt.y + srcUVRecttt.height - singlePixelHalfHeight - smallHeight, + srcUVRecttt.width, + smallHeight); + AddQuad(new Rect(atlasRect.x, atlasRect.y + atlasRect.height, atlasRect.width, paddingY), uvRectPix, verts, uvs, tris); + } + + //Bottom + if (addTopBottom) + { + uvRectPix = new Rect(srcUVRecttt.x, + srcUVRecttt.y + singlePixelHalfHeight - smallHeight, + srcUVRecttt.width, + smallHeight); + AddQuad(new Rect(atlasRect.x, atlasRect.y - paddingY, atlasRect.width, paddingY), uvRectPix, verts, uvs, tris); + } + + //Left + if (addLeftRight) + { + uvRectPix = new Rect(srcUVRecttt.x + singlePixelHalfWidth, srcUVRecttt.y, smallWidth, srcUVRecttt.height); + AddQuad(new Rect(atlasRect.x - paddingX, atlasRect.y, paddingX, atlasRect.height), uvRectPix, verts, uvs, tris); + } + + //Right + if (addLeftRight) + { + uvRectPix = new Rect(srcUVRecttt.x + srcUVRecttt.width - singlePixelHalfWidth - smallWidth, + srcUVRecttt.y, + smallWidth, + srcUVRecttt.height); + AddQuad(new Rect(atlasRect.x + atlasRect.width, atlasRect.y, paddingX, atlasRect.height), uvRectPix, verts, uvs, tris); + } + + + // Bottom Left + if (addTopBottom && addLeftRight) + { + uvRectPix = new Rect(srcUVRecttt.x + singlePixelHalfWidth, srcUVRecttt.y + singlePixelHalfHeight, smallWidth, smallHeight); + AddQuad(new Rect(atlasRect.x - paddingX, atlasRect.y - paddingY, paddingX, paddingY), uvRectPix, verts, uvs, tris); + } + + // Top Left + if (addTopBottom && addLeftRight) + { + uvRectPix = new Rect( + srcUVRecttt.x + singlePixelHalfWidth, + srcUVRecttt.y + srcUVRecttt.height - singlePixelHalfHeight - smallHeight, + smallWidth, smallHeight); + AddQuad(new Rect(atlasRect.x - paddingX, atlasRect.y + atlasRect.height, paddingX, paddingY), uvRectPix, verts, uvs, tris); + } + + + // Top Right + if (addTopBottom && addLeftRight) + { + uvRectPix = new Rect(srcUVRecttt.x + srcUVRecttt.width - singlePixelHalfWidth - smallWidth, + srcUVRecttt.y + srcUVRecttt.height - singlePixelHalfHeight - smallHeight, + smallWidth, smallHeight); + AddQuad(new Rect(atlasRect.x + atlasRect.width, atlasRect.y + atlasRect.height, paddingX, paddingY), uvRectPix, verts, uvs, tris); + } + + // Bot Right + if (addTopBottom && addLeftRight) + { + uvRectPix = new Rect(srcUVRecttt.x + srcUVRecttt.width - singlePixelHalfWidth - smallWidth, + srcUVRecttt.y + singlePixelHalfHeight - smallHeight, + smallWidth, smallHeight); + AddQuad(new Rect(atlasRect.x + atlasRect.width, atlasRect.y - paddingY, paddingX, paddingY), uvRectPix, verts, uvs, tris); + } + } + } + } + +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFastV2.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFastV2.cs.meta new file mode 100644 index 00000000..c7797d73 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerFastV2.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 71cace7ff8777d1418a56468a3ba0001 +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerHorizontalVertical.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerHorizontalVertical.cs new file mode 100644 index 00000000..95ce8f99 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerHorizontalVertical.cs @@ -0,0 +1,447 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + /* + TODO test + */ + internal class MB3_TextureCombinerPackerMeshBakerHorizontalVertical : MB3_TextureCombinerPackerMeshBaker + { + private interface IPipeline + { + MB2_PackingAlgorithmEnum GetPackingAlg(); + void SortTexSetIntoBins(MB_TexSet texSet, List<MB_TexSet> horizontalVert, List<MB_TexSet> regular, int maxAtlasWidth, int maxAtlasHeight); + MB_TextureTilingTreatment GetEdge2EdgeTreatment(); + void InitializeAtlasPadding(ref AtlasPadding padding, int paddingValue); + void MergeAtlasPackingResultStackBonAInternal(AtlasPackingResult a, AtlasPackingResult b, out Rect AatlasToFinal, out Rect BatlasToFinal, bool stretchBToAtlasWidth, int maxWidthDim, int maxHeightDim, out int atlasX, out int atlasY); + void GetExtraRoomForRegularAtlas(int usedHorizontalVertWidth, int usedHorizontalVertHeight, int maxAtlasWidth, int maxAtlasHeight, out int atlasRegularMaxWidth, out int atlasRegularMaxHeight); + } + + private class VerticalPipeline : IPipeline + { + public MB2_PackingAlgorithmEnum GetPackingAlg() + { + return MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Vertical; + } + + public void SortTexSetIntoBins(MB_TexSet texSet, List<MB_TexSet> horizontalVert, List<MB_TexSet> regular, int maxAtlasWidth, int maxAtlasHeight) + { + if (texSet.idealHeight >= maxAtlasHeight && + texSet.ts[0].GetEncapsulatingSamplingRect().height >= 1f) + { + horizontalVert.Add(texSet); + } + else + { + regular.Add(texSet); + } + } + + public MB_TextureTilingTreatment GetEdge2EdgeTreatment() + { + return MB_TextureTilingTreatment.edgeToEdgeY; + } + + public void InitializeAtlasPadding(ref AtlasPadding padding, int paddingValue) + { + padding.topBottom = 0; + padding.leftRight = paddingValue; + } + + public void MergeAtlasPackingResultStackBonAInternal(AtlasPackingResult a, AtlasPackingResult b, out Rect AatlasToFinal, out Rect BatlasToFinal, bool stretchBToAtlasWidth, int maxWidthDim, int maxHeightDim, out int atlasX, out int atlasY) + { + // first calc width scale and offset + float finalW = a.usedW + b.usedW; + float scaleXa, scaleXb; + if (finalW > maxWidthDim) + { + scaleXa = maxWidthDim / finalW; //0,1 + float offsetBx = ((float)Mathf.FloorToInt(a.usedW * scaleXa)) / maxWidthDim;//0,1 + scaleXa = offsetBx; + scaleXb = (1f - offsetBx); + AatlasToFinal = new Rect(0, 0, scaleXa, 1); + BatlasToFinal = new Rect(offsetBx, 0, scaleXb, 1); + } + else + { + float offsetBx = a.usedW / finalW; + AatlasToFinal = new Rect(0, 0, offsetBx, 1); + BatlasToFinal = new Rect(offsetBx, 0, b.usedW / finalW, 1); + } + + //next calc width scale and offset + if (a.atlasX > b.atlasX) + { + if (!stretchBToAtlasWidth) + { + // b rects will be placed in a larger atlas which will make them smaller + BatlasToFinal.width = ((float)b.atlasX) / a.atlasX; + } + } + else if (b.atlasX > a.atlasX) + { + // a rects will be placed in a larger atlas which will make them smaller + AatlasToFinal.width = ((float)a.atlasX) / b.atlasX; + } + + atlasX = a.usedW + b.usedW; + atlasY = Mathf.Max(a.usedH, b.usedH); + } + + public void GetExtraRoomForRegularAtlas(int usedHorizontalVertWidth, int usedHorizontalVertHeight, int maxAtlasWidth, int maxAtlasHeight, out int atlasRegularMaxWidth, out int atlasRegularMaxHeight) + { + atlasRegularMaxWidth = maxAtlasWidth - usedHorizontalVertWidth; + atlasRegularMaxHeight = maxAtlasHeight; + } + } + + private class HorizontalPipeline : IPipeline + { + public MB2_PackingAlgorithmEnum GetPackingAlg() + { + return MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Horizontal; + } + + public void SortTexSetIntoBins(MB_TexSet texSet, List<MB_TexSet> horizontalVert, List<MB_TexSet> regular, int maxAtlasWidth, int maxAtlasHeight) + { + if (texSet.idealWidth >= maxAtlasWidth && + texSet.ts[0].GetEncapsulatingSamplingRect().width >= 1f) + { + horizontalVert.Add(texSet); + } + else + { + regular.Add(texSet); + } + } + + public MB_TextureTilingTreatment GetEdge2EdgeTreatment() + { + return MB_TextureTilingTreatment.edgeToEdgeX; + } + + public void InitializeAtlasPadding(ref AtlasPadding padding, int paddingValue) + { + padding.topBottom = paddingValue; + padding.leftRight = 0; + } + + public void MergeAtlasPackingResultStackBonAInternal(AtlasPackingResult a, AtlasPackingResult b, out Rect AatlasToFinal, out Rect BatlasToFinal, bool stretchBToAtlasWidth, int maxWidthDim, int maxHeightDim, out int atlasX, out int atlasY) + { + float finalH = a.usedH + b.usedH; + float scaleYa, scaleYb; + if (finalH > maxHeightDim) + { + scaleYa = maxHeightDim / finalH; //0,1 + float offsetBy = ((float)Mathf.FloorToInt(a.usedH * scaleYa)) / maxHeightDim;//0,1 + scaleYa = offsetBy; + scaleYb = (1f - offsetBy); + AatlasToFinal = new Rect(0, 0, 1, scaleYa); + BatlasToFinal = new Rect(0, offsetBy, 1, scaleYb); + } + else + { + float offsetBy = a.usedH / finalH; + AatlasToFinal = new Rect(0, 0, 1, offsetBy); + BatlasToFinal = new Rect(0, offsetBy, 1, b.usedH / finalH); + } + + //next calc width scale and offset + if (a.atlasX > b.atlasX) + { + if (!stretchBToAtlasWidth) + { + // b rects will be placed in a larger atlas which will make them smaller + BatlasToFinal.width = ((float)b.atlasX) / a.atlasX; + } + } + else if (b.atlasX > a.atlasX) + { + // a rects will be placed in a larger atlas which will make them smaller + AatlasToFinal.width = ((float)a.atlasX) / b.atlasX; + } + + atlasX = Mathf.Max(a.usedW, b.usedW); + atlasY = a.usedH + b.usedH; + } + + public void GetExtraRoomForRegularAtlas(int usedHorizontalVertWidth, int usedHorizontalVertHeight, int maxAtlasWidth, int maxAtlasHeight, out int atlasRegularMaxWidth, out int atlasRegularMaxHeight) + { + atlasRegularMaxWidth = maxAtlasWidth; + atlasRegularMaxHeight = maxAtlasHeight - usedHorizontalVertHeight; + } + } + + public enum AtlasDirection + { + horizontal, + vertical + } + + private AtlasDirection _atlasDirection = AtlasDirection.horizontal; + + public MB3_TextureCombinerPackerMeshBakerHorizontalVertical(AtlasDirection ad) + { + _atlasDirection = ad; + } + + public override AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + Debug.Assert(data._packingAlgorithm != MB2_PackingAlgorithmEnum.UnitysPackTextures, "Unity texture packer cannot be used"); + + IPipeline pipeline; + if (_atlasDirection == AtlasDirection.horizontal) + { + pipeline = new HorizontalPipeline(); + } else + { + pipeline = new VerticalPipeline(); + } + + //int maxAtlasWidth = data._maxAtlasWidth; + //int maxAtlasHeight = data._maxAtlasHeight; + if (_atlasDirection == AtlasDirection.horizontal) + { + if (!data._useMaxAtlasWidthOverride) + { + // need to get the width of the atlas without mesh uvs considered + int maxWidth = 2; + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + MB_TexSet ts = data.distinctMaterialTextures[i]; + int w; + if (data._fixOutOfBoundsUVs) + { + Vector2 rawHeightWidth = ts.GetMaxRawTextureHeightWidth(); + w = (int)rawHeightWidth.x; + } + else + { + w = ts.idealWidth; + } + if (ts.idealWidth > maxWidth) maxWidth = w; + } + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Calculated max atlas width: " + maxWidth); + data._maxAtlasWidth = maxWidth; + } + } else + { + if (!data._useMaxAtlasHeightOverride) + { + int maxHeight = 2; + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + MB_TexSet ts = data.distinctMaterialTextures[i]; + int h; + if (data._fixOutOfBoundsUVs) + { + Vector2 rawHeightWidth = ts.GetMaxRawTextureHeightWidth(); + h = (int) rawHeightWidth.y; + } else + { + h = ts.idealHeight; + } + if (ts.idealHeight > maxHeight) maxHeight = h; + } + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Calculated max atlas height: " + maxHeight); + data._maxAtlasHeight = maxHeight; + } + } + + //split the list of distinctMaterialTextures into two bins + List<MB_TexSet> horizontalVerticalDistinctMaterialTextures = new List<MB_TexSet>(); + List<MB_TexSet> regularTextures = new List<MB_TexSet>(); + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + pipeline.SortTexSetIntoBins(data.distinctMaterialTextures[i], horizontalVerticalDistinctMaterialTextures, regularTextures, data._maxAtlasWidth, data._maxAtlasHeight); + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(String.Format("Splitting list of distinctMaterialTextures numHorizontalVertical={0} numRegular={1} maxAtlasWidth={2} maxAtlasHeight={3}", horizontalVerticalDistinctMaterialTextures.Count, regularTextures.Count, data._maxAtlasWidth, data._maxAtlasHeight)); + + //pack one bin with the horizontal vertical texture packer. + MB2_TexturePacker tp; + MB2_PackingAlgorithmEnum packingAlgorithm; + AtlasPackingResult[] packerRectsHorizontalVertical; + if (horizontalVerticalDistinctMaterialTextures.Count > 0) + { + packingAlgorithm = pipeline.GetPackingAlg(); + List<Vector2> imageSizesHorizontalVertical = new List<Vector2>(); + for (int i = 0; i < horizontalVerticalDistinctMaterialTextures.Count; i++) + { + horizontalVerticalDistinctMaterialTextures[i].SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(pipeline.GetEdge2EdgeTreatment()); + imageSizesHorizontalVertical.Add(new Vector2(horizontalVerticalDistinctMaterialTextures[i].idealWidth, horizontalVerticalDistinctMaterialTextures[i].idealHeight)); + } + + tp = MB3_TextureCombinerPipeline.CreateTexturePacker(packingAlgorithm); + tp.atlasMustBePowerOfTwo = false; + List<AtlasPadding> paddingsHorizontalVertical = new List<AtlasPadding>(); + for (int i = 0; i < imageSizesHorizontalVertical.Count; i++) + { + AtlasPadding padding = new AtlasPadding(); + pipeline.InitializeAtlasPadding(ref padding, data._atlasPadding); + paddingsHorizontalVertical.Add(padding); + } + + tp.LOG_LEVEL = MB2_LogLevel.trace; + packerRectsHorizontalVertical = tp.GetRects(imageSizesHorizontalVertical, paddingsHorizontalVertical, data._maxAtlasWidth, data._maxAtlasHeight, false); + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(String.Format("Packed {0} textures with edgeToEdge tiling into an atlas of size {1} by {2} usedW {3} usedH {4}", horizontalVerticalDistinctMaterialTextures.Count, packerRectsHorizontalVertical[0].atlasX, packerRectsHorizontalVertical[0].atlasY, packerRectsHorizontalVertical[0].usedW, packerRectsHorizontalVertical[0].usedH)); + } + else + { + packerRectsHorizontalVertical = new AtlasPackingResult[0]; + } + + //pack other bin with regular texture packer + AtlasPackingResult[] packerRectsRegular; + if (regularTextures.Count > 0) + { + packingAlgorithm = MB2_PackingAlgorithmEnum.MeshBakerTexturePacker; + List<Vector2> imageSizesRegular = new List<Vector2>(); + for (int i = 0; i < regularTextures.Count; i++) + { + imageSizesRegular.Add(new Vector2(regularTextures[i].idealWidth, regularTextures[i].idealHeight)); + } + + tp = MB3_TextureCombinerPipeline.CreateTexturePacker(MB2_PackingAlgorithmEnum.MeshBakerTexturePacker); + tp.atlasMustBePowerOfTwo = false; + List<AtlasPadding> paddingsRegular = new List<AtlasPadding>(); + for (int i = 0; i < imageSizesRegular.Count; i++) + { + AtlasPadding padding = new AtlasPadding(); + padding.topBottom = data._atlasPadding; + padding.leftRight = data._atlasPadding; + paddingsRegular.Add(padding); + } + + int atlasRegularMaxWidth, atlasRegularMaxHeight; + int usedHorizontalVertWidth = 0, usedHorizontalVertHeight = 0; + if (packerRectsHorizontalVertical.Length > 0) + { + usedHorizontalVertHeight = packerRectsHorizontalVertical[0].atlasY; + usedHorizontalVertWidth = packerRectsHorizontalVertical[0].atlasX; + } + pipeline.GetExtraRoomForRegularAtlas(usedHorizontalVertWidth, usedHorizontalVertHeight, data._maxAtlasWidth, data._maxAtlasHeight, out atlasRegularMaxWidth, out atlasRegularMaxHeight); + packerRectsRegular = tp.GetRects(imageSizesRegular, paddingsRegular, atlasRegularMaxWidth, atlasRegularMaxHeight, false); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(String.Format("Packed {0} textures without edgeToEdge tiling into an atlas of size {1} by {2} usedW {3} usedH {4}", regularTextures.Count, packerRectsRegular[0].atlasX, packerRectsRegular[0].atlasY, packerRectsRegular[0].usedW, packerRectsRegular[0].usedH)); + } + else + { + packerRectsRegular = new AtlasPackingResult[0]; + } + + + AtlasPackingResult result = null; + if (packerRectsHorizontalVertical.Length == 0 && packerRectsRegular.Length == 0) + { + Debug.Assert(false, "Should never have reached this."); + return null; + } + else if (packerRectsHorizontalVertical.Length > 0 && packerRectsRegular.Length > 0) + { + result = MergeAtlasPackingResultStackBonA(packerRectsHorizontalVertical[0], packerRectsRegular[0], data._maxAtlasWidth, data._maxAtlasHeight, true, pipeline); + } + else if (packerRectsHorizontalVertical.Length > 0) + { + result = packerRectsHorizontalVertical[0]; + } + else if (packerRectsRegular.Length > 0) + { + result = packerRectsRegular[0]; + } + + Debug.Assert(data.distinctMaterialTextures.Count == result.rects.Length); + + //We re-ordered the distinctMaterial textures so replace the list with the new reordered one + horizontalVerticalDistinctMaterialTextures.AddRange(regularTextures); + data.distinctMaterialTextures = horizontalVerticalDistinctMaterialTextures; + AtlasPackingResult[] results; + if (result != null) results = new AtlasPackingResult[] { result }; + else results = new AtlasPackingResult[0]; + return results; + } + + public static AtlasPackingResult TestStackRectanglesHorizontal(AtlasPackingResult a, + AtlasPackingResult b, int maxHeightDim, int maxWidthDim, bool stretchBToAtlasWidth) + { + return MergeAtlasPackingResultStackBonA(a, b, maxWidthDim, maxHeightDim, stretchBToAtlasWidth, new HorizontalPipeline()); + } + + public static AtlasPackingResult TestStackRectanglesVertical(AtlasPackingResult a, + AtlasPackingResult b, int maxHeightDim, int maxWidthDim, bool stretchBToAtlasWidth) + { + return MergeAtlasPackingResultStackBonA(a, b, maxWidthDim, maxHeightDim, stretchBToAtlasWidth, new VerticalPipeline()); + } + + private static AtlasPackingResult MergeAtlasPackingResultStackBonA(AtlasPackingResult a, + AtlasPackingResult b, int maxWidthDim, int maxHeightDim, bool stretchBToAtlasWidth, IPipeline pipeline) + { + Debug.Assert(a.usedW == a.atlasX); + Debug.Assert(a.usedH == a.atlasY); + Debug.Assert(b.usedW == b.atlasX); + Debug.Assert(b.usedH == b.atlasY); + Debug.Assert(a.usedW <= maxWidthDim, a.usedW + " " + maxWidthDim); + Debug.Assert(a.usedH <= maxHeightDim, a.usedH + " " + maxHeightDim); + Debug.Assert(b.usedH <= maxHeightDim); + Debug.Assert(b.usedW <= maxWidthDim, b.usedW + " " + maxWidthDim); + + Rect AatlasToFinal; + Rect BatlasToFinal; + + // first calc height scale and offset + int atlasX; + int atlasY; + pipeline.MergeAtlasPackingResultStackBonAInternal(a, b, out AatlasToFinal, out BatlasToFinal, stretchBToAtlasWidth, maxWidthDim, maxHeightDim, out atlasX, out atlasY); + + Rect[] newRects = new Rect[a.rects.Length + b.rects.Length]; + AtlasPadding[] paddings = new AtlasPadding[a.rects.Length + b.rects.Length]; + int[] srcImgIdxs = new int[a.rects.Length + b.rects.Length]; + Array.Copy(a.padding, paddings, a.padding.Length); + Array.Copy(b.padding, 0, paddings, a.padding.Length, b.padding.Length); + Array.Copy(a.srcImgIdxs, srcImgIdxs, a.srcImgIdxs.Length); + Array.Copy(b.srcImgIdxs, 0, srcImgIdxs, a.srcImgIdxs.Length, b.srcImgIdxs.Length); + Array.Copy(a.rects, newRects, a.rects.Length); + for (int i = 0; i < a.rects.Length; i++) + { + Rect r = a.rects[i]; + r.x = AatlasToFinal.x + r.x * AatlasToFinal.width; + r.y = AatlasToFinal.y + r.y * AatlasToFinal.height; + r.width *= AatlasToFinal.width; + r.height *= AatlasToFinal.height; + Debug.Assert(r.max.x <= 1f); + Debug.Assert(r.max.y <= 1f); + Debug.Assert(r.min.x >= 0f); + Debug.Assert(r.min.y >= 0f); + newRects[i] = r; + srcImgIdxs[i] = a.srcImgIdxs[i]; + } + + for (int i = 0; i < b.rects.Length; i++) + { + Rect r = b.rects[i]; + r.x = BatlasToFinal.x + r.x * BatlasToFinal.width; + r.y = BatlasToFinal.y + r.y * BatlasToFinal.height; + r.width *= BatlasToFinal.width; + r.height *= BatlasToFinal.height; + Debug.Assert(r.max.x <= 1f); + Debug.Assert(r.max.y <= 1f); + Debug.Assert(r.min.x >= 0f); + Debug.Assert(r.min.y >= 0f); + newRects[a.rects.Length + i] = r; + srcImgIdxs[a.rects.Length + i] = b.srcImgIdxs[i]; + } + + AtlasPackingResult res = new AtlasPackingResult(paddings); + res.atlasX = atlasX; + res.atlasY = atlasY; + res.padding = paddings; + res.rects = newRects; + res.srcImgIdxs = srcImgIdxs; + res.CalcUsedWidthAndHeight(); + return res; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerHorizontalVertical.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerHorizontalVertical.cs.meta new file mode 100644 index 00000000..ad43f5e8 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerMeshBakerHorizontalVertical.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b09d683d2eeac64489ae12a44c9c00d8 +timeCreated: 1524695231 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerOneTextureInAtlas.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerOneTextureInAtlas.cs new file mode 100644 index 00000000..9425a45d --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerOneTextureInAtlas.cs @@ -0,0 +1,89 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + internal class MB3_TextureCombinerPackerOneTextureInAtlas : MB_ITextureCombinerPacker + { + public virtual bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data) + { + if (!data.OnlyOneTextureInAtlasReuseTextures()) + { + Debug.LogError("There must be only one texture in the atlas to use MB3_TextureCombinerPackerOneTextureInAtlas"); + return false; + } + + return true; + } + + public IEnumerator ConvertTexturesToReadableFormats(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(data.OnlyOneTextureInAtlasReuseTextures()); + yield break; + } + + public AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(data.OnlyOneTextureInAtlasReuseTextures()); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Only one image per atlas. Will re-use original texture"); + AtlasPackingResult[] packerRects = new AtlasPackingResult[1]; + AtlasPadding[] paddings = new AtlasPadding[] { new AtlasPadding(data._atlasPadding) }; + packerRects[0] = new AtlasPackingResult(paddings); + packerRects[0].rects = new Rect[1]; + packerRects[0].srcImgIdxs = new int[] { 0 }; + packerRects[0].rects[0] = new Rect(0f, 0f, 1f, 1f); + + MeshBakerMaterialTexture dmt = null; + if (data.distinctMaterialTextures[0].ts.Length > 0) + { + dmt = data.distinctMaterialTextures[0].ts[0]; + + } + if (dmt == null || dmt.isNull) + { + packerRects[0].atlasX = 16; + packerRects[0].atlasY = 16; + packerRects[0].usedW = 16; + packerRects[0].usedH = 16; + } + else + { + packerRects[0].atlasX = dmt.width; + packerRects[0].atlasY = dmt.height; + packerRects[0].usedW = dmt.width; + packerRects[0].usedH = dmt.height; + } + return packerRects; + } + + public IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(data.OnlyOneTextureInAtlasReuseTextures()); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Only one image per atlas. Will re-use original texture"); + for (int i = 0; i < data.numAtlases; i++) + { + MeshBakerMaterialTexture dmt = data.distinctMaterialTextures[0].ts[i]; + atlases[i] = dmt.GetTexture2D(); + if (data.resultType == MB2_TextureBakeResults.ResultType.atlas) + { + data.resultMaterial.SetTexture(data.texPropertyNames[i].name, atlases[i]); + data.resultMaterial.SetTextureScale(data.texPropertyNames[i].name, Vector2.one); + data.resultMaterial.SetTextureOffset(data.texPropertyNames[i].name, Vector2.zero); + } + } + + yield break; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerOneTextureInAtlas.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerOneTextureInAtlas.cs.meta new file mode 100644 index 00000000..1a40b308 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerOneTextureInAtlas.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 79f6c18547087b2409adc04a3e6dc6e6 +timeCreated: 1524695231 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerUnity.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerUnity.cs new file mode 100644 index 00000000..99f2733e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerUnity.cs @@ -0,0 +1,200 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using System; + +namespace DigitalOpus.MB.Core +{ + internal class MB3_TextureCombinerPackerUnity : MB3_TextureCombinerPackerRoot + { + public override bool Validate(MB3_TextureCombinerPipeline.TexturePipelineData data) + { + return true; + } + + public override AtlasPackingResult[] CalculateAtlasRectangles(MB3_TextureCombinerPipeline.TexturePipelineData data, bool doMultiAtlas, MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + //with Unity texture packer we don't find the rectangles, Unity does. When packer is run + return new AtlasPackingResult[] { new AtlasPackingResult(new AtlasPadding[0]) }; + } + + public override IEnumerator CreateAtlases(ProgressUpdateDelegate progressInfo, + MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, + AtlasPackingResult packedAtlasRects, + Texture2D[] atlases, MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(!data.OnlyOneTextureInAtlasReuseTextures()); + long estArea = 0; + int atlasSizeX = 1; + int atlasSizeY = 1; + Rect[] uvRects = null; + for (int propIdx = 0; propIdx < data.numAtlases; propIdx++) + { + //----------------------- + ShaderTextureProperty prop = data.texPropertyNames[propIdx]; + Texture2D atlas = null; + if (!MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(propIdx, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + atlas = null; + } + else + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.LogWarning("Beginning loop " + propIdx + " num temporary textures " + combiner._getNumTemporaryTextures()); + MB3_TextureCombinerPackerRoot.CreateTemporaryTexturesForAtlas(data.distinctMaterialTextures, combiner, propIdx, data); + Texture2D[] texToPack = new Texture2D[data.distinctMaterialTextures.Count]; + for (int texSetIdx = 0; texSetIdx < data.distinctMaterialTextures.Count; texSetIdx++) + { + MB_TexSet txs = data.distinctMaterialTextures[texSetIdx]; + int tWidth = txs.idealWidth; + int tHeight = txs.idealHeight; + Texture2D tx = txs.ts[propIdx].GetTexture2D(); + if (progressInfo != null) + { + progressInfo("Adjusting for scale and offset " + tx, .01f); + } + + if (textureEditorMethods != null) + { + textureEditorMethods.SetReadWriteFlag(tx, true, true); + } + + tx = GetAdjustedForScaleAndOffset2(prop, txs.ts[propIdx], txs.obUVoffset, txs.obUVscale, data, combiner, LOG_LEVEL); + //create a resized copy if necessary + if (tx.width != tWidth || tx.height != tHeight) + { + if (progressInfo != null) progressInfo("Resizing texture '" + tx + "'", .01f); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.LogWarning("Copying and resizing texture " + prop.name + " from " + tx.width + "x" + tx.height + " to " + tWidth + "x" + tHeight); + tx = combiner._resizeTexture(prop.name, (Texture2D)tx, tWidth, tHeight); + } + + estArea += tx.width * tx.height; + if (data._considerNonTextureProperties) + { + //combine the tintColor with the texture + tx = combiner._createTextureCopy(prop.name, tx); + data.nonTexturePropertyBlender.TintTextureWithTextureCombiner(tx, data.distinctMaterialTextures[texSetIdx], prop); + } + + texToPack[texSetIdx] = tx; + } + + if (textureEditorMethods != null) textureEditorMethods.CheckBuildSettings(estArea); + + if (Math.Sqrt(estArea) > 3500f) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("The maximum possible atlas size is 4096. Textures may be shrunk"); + } + + atlas = new Texture2D(1, 1, TextureFormat.ARGB32, true); + if (progressInfo != null) progressInfo("Packing texture atlas " + prop.name, .25f); + if (propIdx == 0) + { + if (progressInfo != null) progressInfo("Estimated min size of atlases: " + Math.Sqrt(estArea).ToString("F0"), .1f); + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("Estimated atlas minimum size:" + Math.Sqrt(estArea).ToString("F0")); + int maxAtlasSize = 4096; + uvRects = atlas.PackTextures(texToPack, data._atlasPadding, maxAtlasSize, false); + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("After pack textures atlas numTextures " + texToPack.Length + " size " + atlas.width + " " + atlas.height); + atlasSizeX = atlas.width; + atlasSizeY = atlas.height; + atlas.Apply(); + } + else + { + if (progressInfo != null) progressInfo("Copying Textures Into: " + prop.name, .1f); + atlas = _copyTexturesIntoAtlas(texToPack, data._atlasPadding, uvRects, atlasSizeX, atlasSizeY, combiner); + } + } + + atlases[propIdx] = atlas; + //---------------------- + + if (data._saveAtlasesAsAssets && textureEditorMethods != null) + { + SaveAtlasAndConfigureResultMaterial(data, textureEditorMethods, atlases[propIdx], data.texPropertyNames[propIdx], propIdx); + } + + data.resultMaterial.SetTextureOffset(prop.name, Vector2.zero); + data.resultMaterial.SetTextureScale(prop.name, Vector2.one); + combiner._destroyTemporaryTextures(prop.name); + GC.Collect(); + } + packedAtlasRects.rects = uvRects; + yield break; + } + + internal static Texture2D _copyTexturesIntoAtlas(Texture2D[] texToPack, int padding, Rect[] rs, int w, int h, MB3_TextureCombiner combiner) + { + Texture2D ta = new Texture2D(w, h, TextureFormat.ARGB32, true); + MB_Utility.setSolidColor(ta, Color.clear); + for (int i = 0; i < rs.Length; i++) + { + Rect r = rs[i]; + Texture2D t = texToPack[i]; + Texture2D tmpTex = null; + int x = Mathf.RoundToInt(r.x * w); + int y = Mathf.RoundToInt(r.y * h); + int ww = Mathf.RoundToInt(r.width * w); + int hh = Mathf.RoundToInt(r.height * h); + if (t.width != ww && t.height != hh) + { + tmpTex = t = MB_Utility.resampleTexture(t, ww, hh); + } + ta.SetPixels(x, y, ww, hh, t.GetPixels()); + if (tmpTex != null) MB_Utility.Destroy(tmpTex); + } + ta.Apply(); + return ta; + } + + // used by Unity texture packer to handle tiled textures. + // may create a new texture that has the correct tiling to handle fix out of bounds UVs + internal static Texture2D GetAdjustedForScaleAndOffset2(ShaderTextureProperty propertyName, MeshBakerMaterialTexture source, Vector2 obUVoffset, Vector2 obUVscale, MB3_TextureCombinerPipeline.TexturePipelineData data, MB3_TextureCombiner combiner, MB2_LogLevel LOG_LEVEL) + { + Texture2D sourceTex = source.GetTexture2D(); + if (source.matTilingRect.x == 0f && source.matTilingRect.y == 0f && source.matTilingRect.width == 1f && source.matTilingRect.height == 1f) + { + if (data._fixOutOfBoundsUVs) + { + if (obUVoffset.x == 0f && obUVoffset.y == 0f && obUVscale.x == 1f && obUVscale.y == 1f) + { + return sourceTex; //no adjustment necessary + } + } + else + { + return sourceTex; //no adjustment necessary + } + } + Vector2 dim = MB3_TextureCombinerPipeline.GetAdjustedForScaleAndOffset2Dimensions(source, obUVoffset, obUVscale, data, LOG_LEVEL); + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.LogWarning("GetAdjustedForScaleAndOffset2: " + sourceTex + " " + obUVoffset + " " + obUVscale); + float newWidth = dim.x; + float newHeight = dim.y; + float scx = (float)source.matTilingRect.width; + float scy = (float)source.matTilingRect.height; + float ox = (float)source.matTilingRect.x; + float oy = (float)source.matTilingRect.y; + if (data._fixOutOfBoundsUVs) + { + scx *= obUVscale.x; + scy *= obUVscale.y; + ox = (float)(source.matTilingRect.x * obUVscale.x + obUVoffset.x); + oy = (float)(source.matTilingRect.y * obUVscale.y + obUVoffset.y); + } + Texture2D newTex = combiner._createTemporaryTexture(propertyName.name, (int)newWidth, (int)newHeight, TextureFormat.ARGB32, true, MB3_TextureCombiner.ShouldTextureBeLinear(propertyName)); + for (int i = 0; i < newTex.width; i++) + { + for (int j = 0; j < newTex.height; j++) + { + float u = i / newWidth * scx + ox; + float v = j / newHeight * scy + oy; + newTex.SetPixel(i, j, sourceTex.GetPixelBilinear(u, v)); + } + } + newTex.Apply(); + return newTex; + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerUnity.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerUnity.cs.meta new file mode 100644 index 00000000..479f2894 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPackerUnity.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5726b8dd6256181429b45797bfd21b81 +timeCreated: 1522041809 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPipeline.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPipeline.cs new file mode 100644 index 00000000..c42b1809 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPipeline.cs @@ -0,0 +1,1133 @@ +//---------------------------------------------- +// MeshBaker +// Copyright © 2011-2012 Ian Deane +//---------------------------------------------- +using UnityEngine; +using System.Collections; +using System.IO; +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +/* + +Notes on Normal Maps in Unity3d + +Unity stores normal maps in a non standard format for some platforms. Think of the standard format as being english, unity's as being +french. The raw image files in the project folder are in english, the AssetImporter converts them to french. Texture2D.GetPixels returns +french. This is a problem when we build an atlas from Texture2D objects and save the result in the project folder. +Unity wants us to flag this file as a normal map but if we do it is effectively translated twice. + +Solutions: + + 1) convert the normal map to english just before saving to project. Then set the normal flag and let the Importer do translation. + This was rejected because Unity doesn't translate for all platforms. I would need to check with every version of Unity which platforms + use which format. + + 2) Uncheck "normal map" on importer before bake and re-check after bake. This is the solution I am using. + +*/ +namespace DigitalOpus.MB.Core +{ + public class MB3_TextureCombinerPipeline + { + public static bool USE_EXPERIMENTAL_HOIZONTALVERTICAL = true; + + public struct CreateAtlasForProperty + { + public bool allTexturesAreNull; + public bool allTexturesAreSame; + public bool allNonTexturePropsAreSame; + public bool allSrcMatsOmittedTextureProperty; + + public override string ToString() + { + return String.Format("AllTexturesNull={0} areSame={1} nonTexPropsAreSame={2} allSrcMatsOmittedTextureProperty={3}", allTexturesAreNull, allTexturesAreSame, allNonTexturePropsAreSame, allSrcMatsOmittedTextureProperty); + } + } + + public static ShaderTextureProperty[] shaderTexPropertyNames = new ShaderTextureProperty[] { + new ShaderTextureProperty("_MainTex",false), + new ShaderTextureProperty("_BaseMap",false), + new ShaderTextureProperty("_BaseColorMap",false), + new ShaderTextureProperty("_BumpMap",true), + new ShaderTextureProperty("_Normal",true), + new ShaderTextureProperty("_BumpSpecMap",false), + new ShaderTextureProperty("_DecalTex",false), + new ShaderTextureProperty("_MaskMap",false), + new ShaderTextureProperty("_BentNormalMap",false), + new ShaderTextureProperty("_TangentMap",false), + new ShaderTextureProperty("_AnisotropyMap",false), + new ShaderTextureProperty("_SubsurfaceMaskMap",false), + new ShaderTextureProperty("_ThicknessMap",false), + new ShaderTextureProperty("_IridescenceThicknessMap",false), + new ShaderTextureProperty("_IridescenceMaskMap",false), + new ShaderTextureProperty("_SpecularColorMap",false), + new ShaderTextureProperty("_EmissiveColorMap",false), + new ShaderTextureProperty("_DistortionVectorMap",false), + new ShaderTextureProperty("_TransmittanceColorMap",false), + new ShaderTextureProperty("_Detail",false), + new ShaderTextureProperty("_GlossMap",false), + new ShaderTextureProperty("_Illum",false), + new ShaderTextureProperty("_LightTextureB0",false), + new ShaderTextureProperty("_ParallaxMap",false), + new ShaderTextureProperty("_ShadowOffset",false), + new ShaderTextureProperty("_TranslucencyMap",false), + new ShaderTextureProperty("_SpecMap",false), + new ShaderTextureProperty("_SpecGlossMap",false), + new ShaderTextureProperty("_TranspMap",false), + new ShaderTextureProperty("_MetallicGlossMap",false), + new ShaderTextureProperty("_OcclusionMap",false), + new ShaderTextureProperty("_EmissionMap",false), + new ShaderTextureProperty("_DetailMask",false), +// new ShaderTextureProperty("_DetailAlbedoMap",false), +// new ShaderTextureProperty("_DetailNormalMap",true), + }; + + internal class TexturePipelineData + { + internal MB2_TextureBakeResults _textureBakeResults; + internal int _atlasPadding = 1; + internal int _maxAtlasWidth = 1; + internal int _maxAtlasHeight = 1; + internal bool _useMaxAtlasHeightOverride = false; + internal bool _useMaxAtlasWidthOverride = false; + internal bool _resizePowerOfTwoTextures = false; + internal bool _fixOutOfBoundsUVs = false; + internal int _maxTilingBakeSize = 1024; + internal bool _saveAtlasesAsAssets = false; + internal MB2_PackingAlgorithmEnum _packingAlgorithm = MB2_PackingAlgorithmEnum.UnitysPackTextures; + internal int _layerTexturePackerFastV2 = -1; + internal bool _meshBakerTexturePackerForcePowerOfTwo = true; + internal List<ShaderTextureProperty> _customShaderPropNames = new List<ShaderTextureProperty>(); + internal bool _normalizeTexelDensity = false; + internal bool _considerNonTextureProperties = false; + internal bool doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize = false; + internal ColorSpace colorSpace = ColorSpace.Gamma; + internal MB3_TextureCombinerNonTextureProperties nonTexturePropertyBlender; + internal List<MB_TexSet> distinctMaterialTextures; + internal List<GameObject> allObjsToMesh; + internal List<Material> allowedMaterialsFilter; + internal List<ShaderTextureProperty> texPropertyNames; + internal CreateAtlasForProperty[] allTexturesAreNullAndSameColor; + internal MB2_TextureBakeResults.ResultType resultType; + + internal int numAtlases { get + { + if (texPropertyNames != null) return texPropertyNames.Count; + else return 0; + } + } + internal Material resultMaterial; + + internal bool OnlyOneTextureInAtlasReuseTextures() + { + if (distinctMaterialTextures != null && + distinctMaterialTextures.Count == 1 && + distinctMaterialTextures[0].thisIsOnlyTexSetInAtlas == true && + !_fixOutOfBoundsUVs && + !_considerNonTextureProperties) + { + return true; + } + return false; + } + } + + internal static bool _ShouldWeCreateAtlasForThisProperty(int propertyIndex, bool considerNonTextureProperties, CreateAtlasForProperty[] allTexturesAreNullAndSameColor) + { + CreateAtlasForProperty v = allTexturesAreNullAndSameColor[propertyIndex]; + if (considerNonTextureProperties) + { + if (!v.allNonTexturePropsAreSame || !v.allTexturesAreNull) + { + return true; + } + else + { + return false; + } + } + else + { + if (!v.allTexturesAreNull) + { + return true; + } + else + { + return false; + } + } + } + + internal static bool _DoAnySrcMatsHaveProperty(int propertyIndex, CreateAtlasForProperty[] allTexturesAreNullAndSameColor) + { + return !allTexturesAreNullAndSameColor[propertyIndex].allSrcMatsOmittedTextureProperty; + } + + internal static bool _CollectPropertyNames(MB3_TextureCombinerPipeline.TexturePipelineData data, MB2_LogLevel LOG_LEVEL) + { + return _CollectPropertyNames(data.texPropertyNames, data._customShaderPropNames, + data.resultMaterial, LOG_LEVEL); + } + + internal static bool _CollectPropertyNames(List<ShaderTextureProperty> texPropertyNames, List<ShaderTextureProperty> _customShaderPropNames, + Material resultMaterial, MB2_LogLevel LOG_LEVEL) + { + //try custom properties remove duplicates + for (int i = 0; i < texPropertyNames.Count; i++) + { + ShaderTextureProperty s = _customShaderPropNames.Find(x => x.name.Equals(texPropertyNames[i].name)); + if (s != null) + { + _customShaderPropNames.Remove(s); + } + } + + if (resultMaterial == null) + { + Debug.LogError("Please assign a result material. The combined mesh will use this material."); + return false; + } + + MBVersion.CollectPropertyNames(texPropertyNames, shaderTexPropertyNames, _customShaderPropNames, resultMaterial, LOG_LEVEL); + return true; + } + + /// <summary> + /// Some shaders like the Standard shader have texture properties like Emission which can be set on the material + /// but are disabled using keywords. In these cases the textures should not be returned. + /// </summary> + public static Texture GetTextureConsideringStandardShaderKeywords(string shaderName, Material mat, string propertyName) + { + if (shaderName.Equals("Standard") || shaderName.Equals("Standard (Specular setup)") || shaderName.Equals("Standard (Roughness setup")) + { + if (propertyName.Equals("_EmissionMap")) + { + if (mat.IsKeywordEnabled("_EMISSION")) + { + return mat.GetTexture(propertyName); + } else + { + return null; + } + } + } + return mat.GetTexture(propertyName); + } + + /// <summary> + /// Fills distinctMaterialTextures (a list of TexSets) and usedObjsToMesh. Each TexSet is a rectangle in the set of atlases. + /// If allowedMaterialsFilter is empty then all materials on allObjsToMesh will be collected and usedObjsToMesh will be same as allObjsToMesh + /// else only materials in allowedMaterialsFilter will be included and usedObjsToMesh will be objs that use those materials. + /// bool __step1_CollectDistinctMatTexturesAndUsedObjects; + /// </summary> + internal virtual IEnumerator __Step1_CollectDistinctMatTexturesAndUsedObjects(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + List<GameObject> usedObjsToMesh, + MB2_LogLevel LOG_LEVEL + ) + { + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + // Collect distinct list of textures to combine from the materials on objsToCombine + bool outOfBoundsUVs = false; + Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache = new Dictionary<int, MB_Utility.MeshAnalysisResult[]>(); //cache results + for (int i = 0; i < data.allObjsToMesh.Count; i++) + { + GameObject obj = data.allObjsToMesh[i]; + if (progressInfo != null) progressInfo("Collecting textures for " + obj, ((float)i) / data.allObjsToMesh.Count / 2f); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Collecting textures for object " + obj); + + if (obj == null) + { + Debug.LogError("The list of objects to mesh contained nulls."); + result.success = false; + yield break; + } + + Mesh sharedMesh = MB_Utility.GetMesh(obj); + if (sharedMesh == null) + { + Debug.LogError("Object " + obj.name + " in the list of objects to mesh has no mesh."); + result.success = false; + yield break; + } + + Material[] sharedMaterials = MB_Utility.GetGOMaterials(obj); + if (sharedMaterials.Length == 0) + { + Debug.LogError("Object " + obj.name + " in the list of objects has no materials."); + result.success = false; + yield break; + } + + //analyze mesh or grab cached result of previous analysis, stores one result for each submesh + MB_Utility.MeshAnalysisResult[] mar; + if (!meshAnalysisResultsCache.TryGetValue(sharedMesh.GetInstanceID(), out mar)) + { + mar = new MB_Utility.MeshAnalysisResult[sharedMesh.subMeshCount]; + for (int j = 0; j < sharedMesh.subMeshCount; j++) + { + MB_Utility.hasOutOfBoundsUVs(sharedMesh, ref mar[j], j); + if (data._normalizeTexelDensity) + { + mar[j].submeshArea = GetSubmeshArea(sharedMesh, j); + } + + if (data._fixOutOfBoundsUVs && !mar[j].hasUVs) + { + //assume UVs will be generated if this feature is being used and generated UVs will be 0,0,1,1 + mar[j].uvRect = new Rect(0, 0, 1, 1); + Debug.LogWarning("Mesh for object " + obj + " has no UV channel but 'consider UVs' is enabled. Assuming UVs will be generated filling 0,0,1,1 rectangle."); + } + } + meshAnalysisResultsCache.Add(sharedMesh.GetInstanceID(), mar); + } + + if (data._fixOutOfBoundsUVs && LOG_LEVEL >= MB2_LogLevel.trace) + { + Debug.Log("Mesh Analysis for object " + obj + " numSubmesh=" + mar.Length + " HasOBUV=" + mar[0].hasOutOfBoundsUVs + " UVrectSubmesh0=" + mar[0].uvRect); + } + + for (int matIdx = 0; matIdx < sharedMaterials.Length; matIdx++) + { //for each submesh + if (progressInfo != null) progressInfo(String.Format("Collecting textures for {0} submesh {1}", obj, matIdx), ((float)i) / data.allObjsToMesh.Count / 2f); + Material mat = sharedMaterials[matIdx]; + + //check if this material is in the list of source materaials + if (data.allowedMaterialsFilter != null && !data.allowedMaterialsFilter.Contains(mat)) + { + continue; + } + + //Rect uvBounds = mar[matIdx].sourceUVRect; + outOfBoundsUVs = outOfBoundsUVs || mar[matIdx].hasOutOfBoundsUVs; + + if (mat.name.Contains("(Instance)")) + { + Debug.LogError("The sharedMaterial on object " + obj.name + " has been 'Instanced'. This was probably caused by a script accessing the meshRender.material property in the editor. " + + " The material to UV Rectangle mapping will be incorrect. To fix this recreate the object from its prefab or re-assign its material from the correct asset."); + result.success = false; + yield break; + } + + if (data._fixOutOfBoundsUVs) + { + if (!MB_Utility.AreAllSharedMaterialsDistinct(sharedMaterials)) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Object " + obj.name + " uses the same material on multiple submeshes. This may generate strange resultAtlasesAndRects especially when used with fix out of bounds uvs. Try duplicating the material."); + } + } + + //need to set up procedural material before converting its texs to texture2D + /* + if (mat is ProceduralMaterial) + { + combiner._addProceduralMaterial((ProceduralMaterial)mat); + } + */ + + + //collect textures scale and offset for each texture in objects material + MeshBakerMaterialTexture[] mts = new MeshBakerMaterialTexture[data.texPropertyNames.Count]; + for (int propIdx = 0; propIdx < data.texPropertyNames.Count; propIdx++) + { + Texture tx = null; + Vector2 scale = Vector2.one; + Vector2 offset = Vector2.zero; + float texelDensity = 0f; + int isImportedAsNormalMap = 0; + if (mat.HasProperty(data.texPropertyNames[propIdx].name)) + { + Texture txx = GetTextureConsideringStandardShaderKeywords(data.resultMaterial.shader.name, mat, data.texPropertyNames[propIdx].name); + if (txx != null) + { + if (txx is Texture2D) + { + tx = txx; + TextureFormat f = ((Texture2D)tx).format; + bool isNormalMap = false; + if (!Application.isPlaying && textureEditorMethods != null) + { + isNormalMap = textureEditorMethods.IsNormalMap((Texture2D)tx); + isImportedAsNormalMap = isNormalMap == true ? -1 : 1; + } + if ((f == TextureFormat.ARGB32 || + f == TextureFormat.RGBA32 || + f == TextureFormat.BGRA32 || + f == TextureFormat.RGB24 || + f == TextureFormat.Alpha8) && !isNormalMap) //DXT5 does not work + { + //good + } + else + { + //TRIED to copy texture using tex2.SetPixels(tex1.GetPixels()) but bug in 3.5 means DTX1 and 5 compressed textures come out skewe + if (Application.isPlaying && + data._packingAlgorithm != MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Fast && + data._packingAlgorithm != MB2_PackingAlgorithmEnum.MeshBakerTexturePaker_Fast_V2_Beta) + { + Debug.LogError("Object " + obj.name + " in the list of objects to mesh uses Texture " + tx.name + " uses format " + f + " that is not in: ARGB32, RGBA32, BGRA32, RGB24, Alpha8 or DXT. These textures cannot be resized at runtime. Try changing texture format. If format says 'compressed' try changing it to 'truecolor'"); + result.success = false; + yield break; + } + else + { + tx = (Texture2D)mat.GetTexture(data.texPropertyNames[propIdx].name); + } + } + } + /* + else if (txx is ProceduralTexture) + { + //if (!MBVersion.IsTextureFormatRaw(((ProceduralTexture)txx).format)) + //{ + // Debug.LogError("Object " + obj.name + " in the list of objects to mesh uses a ProceduarlTexture that is not in a RAW format. Convert textures to RAW."); + // result.success = false; + // yield break; + //} + tx = txx; + } + */ + else + { + Debug.LogError("Object '" + obj.name + "' in the list of objects to mesh uses a Texture that is not a Texture2D. Cannot build atlases with this object."); + result.success = false; + yield break; + } + + } + + if (tx != null && data._normalizeTexelDensity) + { + //todo this doesn't take into account tiling and out of bounds UV sampling + if (mar[propIdx].submeshArea == 0) + { + texelDensity = 0f; + } + else + { + texelDensity = (tx.width * tx.height) / (mar[propIdx].submeshArea); + } + } + + GetMaterialScaleAndOffset(mat, data.texPropertyNames[propIdx].name, out offset, out scale); + } + + mts[propIdx] = new MeshBakerMaterialTexture(tx, offset, scale, texelDensity, isImportedAsNormalMap); + } + + data.nonTexturePropertyBlender.CollectAverageValuesOfNonTextureProperties(data.resultMaterial, mat); + + Vector2 obUVscale = new Vector2(mar[matIdx].uvRect.width, mar[matIdx].uvRect.height); + Vector2 obUVoffset = new Vector2(mar[matIdx].uvRect.x, mar[matIdx].uvRect.y); + + //Add to distinct set of textures if not already there + MB_TextureTilingTreatment tilingTreatment = MB_TextureTilingTreatment.none; + if (data._fixOutOfBoundsUVs) + { + tilingTreatment = MB_TextureTilingTreatment.considerUVs; + } + + MB_TexSet setOfTexs = new MB_TexSet(mts, obUVoffset, obUVscale, tilingTreatment); //one of these per submesh + MatAndTransformToMerged matt = new MatAndTransformToMerged(new DRect(obUVoffset, obUVscale), data._fixOutOfBoundsUVs, mat); + setOfTexs.matsAndGOs.mats.Add(matt); + MB_TexSet setOfTexs2 = data.distinctMaterialTextures.Find(x => x.IsEqual(setOfTexs, data._fixOutOfBoundsUVs, data.nonTexturePropertyBlender)); + if (setOfTexs2 != null) + { + setOfTexs = setOfTexs2; + } + else + { + data.distinctMaterialTextures.Add(setOfTexs); + } + + if (!setOfTexs.matsAndGOs.mats.Contains(matt)) + { + setOfTexs.matsAndGOs.mats.Add(matt); + } + + if (!setOfTexs.matsAndGOs.gos.Contains(obj)) + { + setOfTexs.matsAndGOs.gos.Add(obj); + if (!usedObjsToMesh.Contains(obj)) usedObjsToMesh.Add(obj); + } + } + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log(String.Format("Step1_CollectDistinctTextures collected {0} sets of textures fixOutOfBoundsUV={1} considerNonTextureProperties={2}", data.distinctMaterialTextures.Count, data._fixOutOfBoundsUVs, data._considerNonTextureProperties)); + + if (data.distinctMaterialTextures.Count == 0) + { + string[] filterStrings = new string[data.allowedMaterialsFilter.Count]; + for (int i = 0; i < filterStrings.Length; i++) filterStrings[i] = data.allowedMaterialsFilter[i].name; + string allowedMaterialsString = string.Join(", ", filterStrings); + Debug.LogError("None of the materials on the objects to combine matched any of the allowed materials for submesh with result material: " + data.resultMaterial + " allowedMaterials: " + allowedMaterialsString + ". Do any of the source objects use the allowed materials?"); + result.success = false; + yield break; + } + + MB3_TextureCombinerMerging merger = new MB3_TextureCombinerMerging(data._considerNonTextureProperties, data.nonTexturePropertyBlender, data._fixOutOfBoundsUVs, LOG_LEVEL); + merger.MergeOverlappingDistinctMaterialTexturesAndCalcMaterialSubrects(data.distinctMaterialTextures); + + if (data.doMergeDistinctMaterialTexturesThatWouldExceedAtlasSize) + { + merger.MergeDistinctMaterialTexturesThatWouldExceedMaxAtlasSizeAndCalcMaterialSubrects(data.distinctMaterialTextures, Mathf.Max(data._maxAtlasHeight, data._maxAtlasWidth)); + } + + // Try to guess the isNormalMap if for textureProperties if necessary. + { + for (int propIdx = 0; propIdx < data.texPropertyNames.Count; propIdx++) + { + ShaderTextureProperty texProp = data.texPropertyNames[propIdx]; + if (texProp.isNormalDontKnow) + { + int isNormalVote = 0; + for (int rectIdx = 0; rectIdx < data.distinctMaterialTextures.Count; rectIdx++) + { + MeshBakerMaterialTexture matTex = data.distinctMaterialTextures[rectIdx].ts[propIdx]; + isNormalVote += matTex.isImportedAsNormalMap; + } + + texProp.isNormalMap = isNormalVote >= 0 ? false : true; + texProp.isNormalDontKnow = false; + } + } + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Total time Step1_CollectDistinctTextures " + (sw.ElapsedMilliseconds).ToString("f5")); + yield break; + } + + private static CreateAtlasForProperty[] CalculateAllTexturesAreNullAndSameColor(MB3_TextureCombinerPipeline.TexturePipelineData data, MB2_LogLevel LOG_LEVEL) + { + // check if all textures are null and use same color for each atlas + // will not generate an atlas if so + CreateAtlasForProperty[] shouldWeCreateAtlasForProp = new CreateAtlasForProperty[data.texPropertyNames.Count]; + for (int propIdx = 0; propIdx < data.texPropertyNames.Count; propIdx++) + { + MeshBakerMaterialTexture firstTexture = data.distinctMaterialTextures[0].ts[propIdx]; + Color firstColor = Color.black; + if (data._considerNonTextureProperties) + { + firstColor = data.nonTexturePropertyBlender.GetColorAsItWouldAppearInAtlasIfNoTexture(data.distinctMaterialTextures[0].matsAndGOs.mats[0].mat, data.texPropertyNames[propIdx]); + } + int numTexturesExisting = 0; + int numTexturesMatchinFirst = 0; + int numNonTexturePropertiesMatchingFirst = 0; + bool allSrcMatsOmittedTexProp = true; + for (int j = 0; j < data.distinctMaterialTextures.Count; j++) + { + MB_TexSet matTex = data.distinctMaterialTextures[j]; + if (!matTex.ts[propIdx].isNull) + { + numTexturesExisting++; + } + if (firstTexture.AreTexturesEqual(matTex.ts[propIdx])) + { + numTexturesMatchinFirst++; + } + if (data._considerNonTextureProperties) + { + Color colJ = data.nonTexturePropertyBlender.GetColorAsItWouldAppearInAtlasIfNoTexture(matTex.matsAndGOs.mats[0].mat, data.texPropertyNames[propIdx]); + if (colJ == firstColor) + { + numNonTexturePropertiesMatchingFirst++; + } + } + + for (int srcMatIdx = 0; srcMatIdx < matTex.matsAndGOs.mats.Count; srcMatIdx++) + { + allSrcMatsOmittedTexProp = !matTex.matsAndGOs.mats[srcMatIdx].mat.HasProperty(data.texPropertyNames[propIdx].name); + } + } + + shouldWeCreateAtlasForProp[propIdx].allTexturesAreNull = numTexturesExisting == 0; + shouldWeCreateAtlasForProp[propIdx].allTexturesAreSame = numTexturesMatchinFirst == data.distinctMaterialTextures.Count; + shouldWeCreateAtlasForProp[propIdx].allNonTexturePropsAreSame = numNonTexturePropertiesMatchingFirst == data.distinctMaterialTextures.Count; + shouldWeCreateAtlasForProp[propIdx].allSrcMatsOmittedTextureProperty |= allSrcMatsOmittedTexProp; + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(String.Format("AllTexturesAreNullAndSameColor prop: {0} createAtlas:{1} val:{2}", data.texPropertyNames[propIdx].name, MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(propIdx, data._considerNonTextureProperties, shouldWeCreateAtlasForProp), shouldWeCreateAtlasForProp[propIdx])); + } + return shouldWeCreateAtlasForProp; + } + + //Textures in each material (_mainTex, Bump, Spec ect...) must be same size + //Calculate the best sized to use. Takes into account tiling + //if only one texture in atlas re-uses original sizes + internal virtual IEnumerator CalculateIdealSizesForTexturesInAtlasAndPadding(ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + MB3_TextureCombinerPipeline.TexturePipelineData data, + MB3_TextureCombiner combiner, + MB2_EditorMethodsInterface textureEditorMethods, + MB2_LogLevel LOG_LEVEL) + { + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + + MeshBakerMaterialTexture.readyToBuildAtlases = true; + data.allTexturesAreNullAndSameColor = CalculateAllTexturesAreNullAndSameColor(data, LOG_LEVEL); + + if (MB3_MeshCombiner.EVAL_VERSION) + { + List<int> propIdxsGeneratingAtlasesFor = new List<int>(); + // Prioritize albedo and bump if those props are used. + for (int i = 0; i < data.allTexturesAreNullAndSameColor.Length; i++) + { + if (_ShouldWeCreateAtlasForThisProperty(i, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + if (data.texPropertyNames[i].name.Equals("_Albedo") || + data.texPropertyNames[i].name.Equals("_MainTex") || + data.texPropertyNames[i].name.Equals("_BaseMap") || + data.texPropertyNames[i].name.Equals("_BaseColorMap")) + { + if (propIdxsGeneratingAtlasesFor.Count < 2) propIdxsGeneratingAtlasesFor.Add(i); + } + + if (data.texPropertyNames[i].name.Equals("_BumpMap") || + data.texPropertyNames[i].name.Equals("_Normal") || + data.texPropertyNames[i].name.Equals("_NormalMap") || + data.texPropertyNames[i].name.Equals("_BentNormalMap")) + { + if (propIdxsGeneratingAtlasesFor.Count < 2) propIdxsGeneratingAtlasesFor.Add(i); + } + } + } + + List<string> namesTruncated = new List<string>(); + List<int> propIdxsTruncated = new List<int>(); + for (int i = 0; i < data.allTexturesAreNullAndSameColor.Length; i++) + { + if (_ShouldWeCreateAtlasForThisProperty(i, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + if (propIdxsGeneratingAtlasesFor.Count >= 2 && !propIdxsGeneratingAtlasesFor.Contains(i)) + { + namesTruncated.Add(data.texPropertyNames[i].name); + propIdxsTruncated.Add(i); + } + } + } + + for (int i = 0; i < propIdxsTruncated.Count; i++) + { + data.allTexturesAreNullAndSameColor[propIdxsTruncated[i]].allTexturesAreNull = true; + data.allTexturesAreNullAndSameColor[propIdxsTruncated[i]].allTexturesAreSame = true; + data.allTexturesAreNullAndSameColor[propIdxsTruncated[i]].allNonTexturePropsAreSame = true; + } + + if (namesTruncated.Count > 0) + { + Debug.LogError("The free version of Mesh Baker will generate a maximum of two atlases per combined material. The source materials had more than two properties with textures. " + + "Atlases will not be generated for: " + string.Join(",", namesTruncated.ToArray())); + } + } + + //calculate size of rectangles in atlas + int _padding = data._atlasPadding; + if (data.distinctMaterialTextures.Count == 1 && data._fixOutOfBoundsUVs == false && data._considerNonTextureProperties == false) + { + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("All objects use the same textures in this set of atlases. Original textures will be reused instead of creating atlases."); + _padding = 0; + data.distinctMaterialTextures[0].SetThisIsOnlyTexSetInAtlasTrue(); + data.distinctMaterialTextures[0].SetTilingTreatmentAndAdjustEncapsulatingSamplingRect(MB_TextureTilingTreatment.edgeToEdgeXY); + } + + Debug.Assert(data.allTexturesAreNullAndSameColor.Length == data.texPropertyNames.Count, "allTexturesAreNullAndSameColor array must be the same length of texPropertyNames."); + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Calculating ideal sizes for texSet TexSet " + i + " of " + data.distinctMaterialTextures.Count); + MB_TexSet txs = data.distinctMaterialTextures[i]; + txs.idealWidth = 1; + txs.idealHeight = 1; + int tWidth = 1; + int tHeight = 1; + Debug.Assert(txs.ts.Length == data.texPropertyNames.Count, "length of arrays in each element of distinctMaterialTextures must be texPropertyNames.Count"); + + //get the best size all textures in a TexSet must be the same size. + for (int propIdx = 0; propIdx < data.texPropertyNames.Count; propIdx++) + { + if (MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(propIdx, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + MeshBakerMaterialTexture matTex = txs.ts[propIdx]; + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("Calculating ideal size for texSet {0} property {1}", i, data.texPropertyNames[propIdx].name)); + if (!matTex.matTilingRect.size.Equals(Vector2.one) && data.distinctMaterialTextures.Count > 1) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Texture " + matTex.GetTexName() + "is tiled by " + matTex.matTilingRect.size + " tiling will be baked into a texture with maxSize:" + data._maxTilingBakeSize); + } + + if (!txs.obUVscale.Equals(Vector2.one) && data.distinctMaterialTextures.Count > 1 && data._fixOutOfBoundsUVs) + { + if (LOG_LEVEL >= MB2_LogLevel.warn) Debug.LogWarning("Texture " + matTex.GetTexName() + " has out of bounds UVs that effectively tile by " + txs.obUVscale + " tiling will be baked into a texture with maxSize:" + data._maxTilingBakeSize); + } + + if (matTex.isNull) + { + txs.SetEncapsulatingRect(propIdx, data._fixOutOfBoundsUVs); + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(String.Format("No source texture creating a 16x16 texture for {0} texSet {1} srcMat {2}", data.texPropertyNames[propIdx].name, i, txs.matsAndGOs.mats[0].GetMaterialName())); + } + + if (!matTex.isNull) + { + Vector2 dim = MB3_TextureCombinerPipeline.GetAdjustedForScaleAndOffset2Dimensions(matTex, txs.obUVoffset, txs.obUVscale, data, LOG_LEVEL); + if ((int)(dim.x * dim.y) > tWidth * tHeight) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" matTex " + matTex.GetTexName() + " " + dim + " has a bigger size than " + tWidth + " " + tHeight); + tWidth = (int)dim.x; + tHeight = (int)dim.y; + } + } + } + } + + if (data._resizePowerOfTwoTextures) + { + if (tWidth <= _padding * 5) + { + Debug.LogWarning(String.Format("Some of the textures have widths close to the size of the padding. It is not recommended to use _resizePowerOfTwoTextures with widths this small.", txs.ToString())); + } + if (tHeight <= _padding * 5) + { + Debug.LogWarning(String.Format("Some of the textures have heights close to the size of the padding. It is not recommended to use _resizePowerOfTwoTextures with heights this small.", txs.ToString())); + } + if (IsPowerOfTwo(tWidth)) + { + tWidth -= _padding * 2; + } + if (IsPowerOfTwo(tHeight)) + { + tHeight -= _padding * 2; + } + if (tWidth < 1) tWidth = 1; + if (tHeight < 1) tHeight = 1; + } + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" Ideal size is " + tWidth + " " + tHeight); + txs.idealWidth = tWidth; + txs.idealHeight = tHeight; + } + data._atlasPadding = _padding; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Total time Step2 Calculate Ideal Sizes part1: " + sw.Elapsed.ToString()); + yield break; + } + + internal virtual AtlasPackingResult[] RunTexturePackerOnly(TexturePipelineData data, bool doSplitIntoMultiAtlasIfTooBig, MB_AtlasesAndRects resultAtlasesAndRects, MB_ITextureCombinerPacker texturePacker, MB2_LogLevel LOG_LEVEL) + { + AtlasPackingResult[] apr = texturePacker.CalculateAtlasRectangles(data, doSplitIntoMultiAtlasIfTooBig, LOG_LEVEL); // __RuntTexturePackerOnly(data, texturePacker, LOG_LEVEL); + + FillAtlasPackingResultAuxillaryData(data, apr); + + Texture2D[] atlases = new Texture2D[data.texPropertyNames.Count]; + if (!doSplitIntoMultiAtlasIfTooBig) + { + FillResultAtlasesAndRects(data, apr[0], resultAtlasesAndRects, atlases); + } + + return apr; + } + + internal virtual MB_ITextureCombinerPacker CreatePacker(bool onlyOneTextureInAtlasReuseTextures, MB2_PackingAlgorithmEnum packingAlgorithm) + { + if (onlyOneTextureInAtlasReuseTextures) + { + return new MB3_TextureCombinerPackerOneTextureInAtlas(); + } + else if (packingAlgorithm == MB2_PackingAlgorithmEnum.UnitysPackTextures) + { + return new MB3_TextureCombinerPackerUnity(); + } + else if (packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Horizontal) + { + if (USE_EXPERIMENTAL_HOIZONTALVERTICAL) + { + return new MB3_TextureCombinerPackerMeshBakerHorizontalVertical(MB3_TextureCombinerPackerMeshBakerHorizontalVertical.AtlasDirection.horizontal); + } else + { + return new MB3_TextureCombinerPackerMeshBaker(); + } + + } + else if (packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Vertical) + { + if (USE_EXPERIMENTAL_HOIZONTALVERTICAL) + { + return new MB3_TextureCombinerPackerMeshBakerHorizontalVertical(MB3_TextureCombinerPackerMeshBakerHorizontalVertical.AtlasDirection.vertical); + } else + { + return new MB3_TextureCombinerPackerMeshBaker(); + } + } + else if (packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker) + { + return new MB3_TextureCombinerPackerMeshBaker(); + } + else if (packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePaker_Fast_V2_Beta) + { + return new MB3_TextureCombinerPackerMeshBakerFastV2(); + } + else if (packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Fast) + { + return new MB3_TextureCombinerPackerMeshBakerFast(); + } else + { + Debug.LogError("Unknown texture packer type. " + packingAlgorithm + " This should never happen."); + return null; + } + } + + internal virtual IEnumerator __Step3_BuildAndSaveAtlasesAndStoreResults(MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult result, + ProgressUpdateDelegate progressInfo, + TexturePipelineData data, + MB3_TextureCombiner combiner, + MB_ITextureCombinerPacker packer, + AtlasPackingResult atlasPackingResult, + MB2_EditorMethodsInterface textureEditorMethods, MB_AtlasesAndRects resultAtlasesAndRects, + StringBuilder report, + MB2_LogLevel LOG_LEVEL) + { + System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + + //run the garbage collector to free up as much memory as possible before bake to reduce MissingReferenceException problems + GC.Collect(); + Texture2D[] atlases = new Texture2D[data.numAtlases]; + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("time Step 3 Create And Save Atlases part 1 " + sw.Elapsed.ToString()); + + yield return packer.CreateAtlases(progressInfo, data, combiner, atlasPackingResult, atlases, textureEditorMethods, LOG_LEVEL); + float t3 = sw.ElapsedMilliseconds; + + data.nonTexturePropertyBlender.AdjustNonTextureProperties(data.resultMaterial, data.texPropertyNames, textureEditorMethods); + + if (data.distinctMaterialTextures.Count > 0) data.distinctMaterialTextures[0].AdjustResultMaterialNonTextureProperties(data.resultMaterial, data.texPropertyNames); + + if (progressInfo != null) progressInfo("Building Report", .7f); + + //report on atlases created + StringBuilder atlasMessage = new StringBuilder(); + atlasMessage.AppendLine("---- Atlases ------"); + for (int i = 0; i < data.numAtlases; i++) + { + if (atlases[i] != null) + { + atlasMessage.AppendLine("Created Atlas For: " + data.texPropertyNames[i].name + " h=" + atlases[i].height + " w=" + atlases[i].width); + } + else if (!_ShouldWeCreateAtlasForThisProperty(i, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + atlasMessage.AppendLine("Did not create atlas for " + data.texPropertyNames[i].name + " because all source textures were null."); + } + } + report.Append(atlasMessage.ToString()); + + FillResultAtlasesAndRects(data, atlasPackingResult, resultAtlasesAndRects, atlases); + + if (progressInfo != null) progressInfo("Restoring Texture Formats & Read Flags", .8f); + combiner._destroyAllTemporaryTextures(); + if (textureEditorMethods != null) textureEditorMethods.RestoreReadFlagsAndFormats(progressInfo); + if (report != null && LOG_LEVEL >= MB2_LogLevel.info) Debug.Log(report.ToString()); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Time Step 3 Create And Save Atlases part 3 " + (sw.ElapsedMilliseconds - t3).ToString("f5")); + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Total time Step 3 Create And Save Atlases " + sw.Elapsed.ToString()); + yield break; + } + + private void FillAtlasPackingResultAuxillaryData(TexturePipelineData data, AtlasPackingResult[] atlasPackingResults) + { + for (int packingResultIdx = 0; packingResultIdx < atlasPackingResults.Length; packingResultIdx++) + { + List<MatsAndGOs> matsList = new List<MatsAndGOs>(); + AtlasPackingResult packingResult = atlasPackingResults[packingResultIdx]; + List<MB_MaterialAndUVRect> auxData = new List<MB_MaterialAndUVRect>(); + for (int aprTexIdx = 0; aprTexIdx < packingResult.srcImgIdxs.Length; aprTexIdx++) + { + int srcTexIdx = packingResult.srcImgIdxs[aprTexIdx]; + MB_TexSet srcTexSet = data.distinctMaterialTextures[srcTexIdx]; + List<MatAndTransformToMerged> mergedMats = srcTexSet.matsAndGOs.mats; + Rect allPropsUseSameTiling_encapsulatingSamplingRect, propsUseDifferntTiling_obUVRect; + srcTexSet.GetRectsForTextureBakeResults(out allPropsUseSameTiling_encapsulatingSamplingRect, out propsUseDifferntTiling_obUVRect); + // A single rectangle in the atlas can be shared by multiple source materials + for (int matIdx = 0; matIdx < mergedMats.Count; matIdx++) + { + Rect allPropsUseSameTiling_sourceMaterialTiling = srcTexSet.GetMaterialTilingRectForTextureBakerResults(matIdx); + MB_MaterialAndUVRect srcMatData = new MB_MaterialAndUVRect( + mergedMats[matIdx].mat, + packingResult.rects[aprTexIdx], + srcTexSet.allTexturesUseSameMatTiling, + allPropsUseSameTiling_sourceMaterialTiling, + allPropsUseSameTiling_encapsulatingSamplingRect, + propsUseDifferntTiling_obUVRect, + srcTexSet.tilingTreatment, + mergedMats[matIdx].objName); + srcMatData.objectsThatUse = new List<GameObject>(srcTexSet.matsAndGOs.gos); + auxData.Add(srcMatData); + } + } + + packingResult.data = auxData; + } + } + + private void FillResultAtlasesAndRects(TexturePipelineData data, AtlasPackingResult atlasPackingResult, MB_AtlasesAndRects resultAtlasesAndRects, Texture2D[] atlases) + { + List<MB_MaterialAndUVRect> mat2rect_map = new List<MB_MaterialAndUVRect>(); + Debug.Assert(atlasPackingResult.rects.Length == data.distinctMaterialTextures.Count, "Number of rects should equal the number of distinct matarial textures." + atlasPackingResult.rects.Length + " " + data.distinctMaterialTextures.Count); + for (int rectInAtlasIdx = 0; rectInAtlasIdx < data.distinctMaterialTextures.Count; rectInAtlasIdx++) + { + MB_TexSet texSet = data.distinctMaterialTextures[rectInAtlasIdx]; + List<MatAndTransformToMerged> mergedMats = texSet.matsAndGOs.mats; + Rect allPropsUseSameTiling_encapsulatingSamplingRect, propsUseDifferntTiling_obUVRect; + texSet.GetRectsForTextureBakeResults(out allPropsUseSameTiling_encapsulatingSamplingRect, out propsUseDifferntTiling_obUVRect); + // A single rectangle in the atlas can be shared by multiple source materials + for (int matIdx = 0; matIdx < mergedMats.Count; matIdx++) + { + Rect allPropsUseSameTiling_sourceMaterialTiling = texSet.GetMaterialTilingRectForTextureBakerResults(matIdx); + MB_MaterialAndUVRect key = new MB_MaterialAndUVRect( + mergedMats[matIdx].mat, + atlasPackingResult.rects[rectInAtlasIdx], + texSet.allTexturesUseSameMatTiling, + allPropsUseSameTiling_sourceMaterialTiling, + allPropsUseSameTiling_encapsulatingSamplingRect, + propsUseDifferntTiling_obUVRect, + texSet.tilingTreatment, + mergedMats[matIdx].objName); + if (!mat2rect_map.Contains(key)) + { + mat2rect_map.Add(key); + } + } + } + + resultAtlasesAndRects.atlases = atlases; // one per texture on result shader + resultAtlasesAndRects.texPropertyNames = ShaderTextureProperty.GetNames(data.texPropertyNames); // one per texture on source shader + resultAtlasesAndRects.mat2rect_map = mat2rect_map; + } + + internal virtual StringBuilder GenerateReport(MB3_TextureCombinerPipeline.TexturePipelineData data) + { + //generate report want to do this before + StringBuilder report = new StringBuilder(); + if (data.numAtlases > 0) + { + report = new StringBuilder(); + report.AppendLine("Report"); + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + MB_TexSet txs = data.distinctMaterialTextures[i]; + report.AppendLine("----------"); + report.Append("This set of textures will be resized to:" + txs.idealWidth + "x" + txs.idealHeight + "\n"); + for (int j = 0; j < txs.ts.Length; j++) + { + if (!txs.ts[j].isNull) + { + report.Append(" [" + data.texPropertyNames[j].name + " " + txs.ts[j].GetTexName() + " " + txs.ts[j].width + "x" + txs.ts[j].height + "]"); + if (txs.ts[j].matTilingRect.size != Vector2.one || txs.ts[j].matTilingRect.min != Vector2.zero) report.AppendFormat(" material scale {0} offset{1} ", txs.ts[j].matTilingRect.size.ToString("G4"), txs.ts[j].matTilingRect.min.ToString("G4")); + if (txs.obUVscale != Vector2.one || txs.obUVoffset != Vector2.zero) report.AppendFormat(" obUV scale {0} offset{1} ", txs.obUVscale.ToString("G4"), txs.obUVoffset.ToString("G4")); + report.AppendLine(""); + } + else + { + report.Append(" [" + data.texPropertyNames[j].name + " null "); + if (!MB3_TextureCombinerPipeline._ShouldWeCreateAtlasForThisProperty(j, data._considerNonTextureProperties, data.allTexturesAreNullAndSameColor)) + { + report.Append("no atlas will be created all textures null]\n"); + } + else + { + report.AppendFormat("a 16x16 texture will be created]\n"); + } + } + } + report.AppendLine(""); + report.Append("Materials using:"); + for (int j = 0; j < txs.matsAndGOs.mats.Count; j++) + { + report.Append(txs.matsAndGOs.mats[j].mat.name + ", "); + } + report.AppendLine(""); + } + } + return report; + } + + /* + internal static AtlasPackingResult[] __RuntTexturePackerOnly(TexturePipelineData data, MB_ITextureCombinerPacker texturePacker, MB2_LogLevel LOG_LEVEL) + { + AtlasPackingResult[] packerRects; + if (data.OnlyOneTextureInAtlasReuseTextures()) + { + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Only one image per atlas. Will re-use original texture"); + packerRects = new AtlasPackingResult[1]; + AtlasPadding[] paddings = new AtlasPadding[] { new AtlasPadding(data._atlasPadding) }; + packerRects[0] = new AtlasPackingResult(paddings); + packerRects[0].rects = new Rect[1]; + packerRects[0].srcImgIdxs = new int[] { 0 }; + packerRects[0].rects[0] = new Rect(0f, 0f, 1f, 1f); + + MeshBakerMaterialTexture dmt = null; + if (data.distinctMaterialTextures[0].ts.Length > 0) + { + dmt = data.distinctMaterialTextures[0].ts[0]; + + } + packerRects[0].atlasX = dmt.isNull ? 16 : dmt.width; + packerRects[0].atlasY = dmt.isNull ? 16 : dmt.height; + packerRects[0].usedW = dmt.isNull ? 16 : dmt.width; + packerRects[0].usedH = dmt.isNull ? 16 : dmt.height; + } + else + { + List<Vector2> imageSizes = new List<Vector2>(); + List<AtlasPadding> paddings = new List<AtlasPadding>(); + for (int i = 0; i < data.distinctMaterialTextures.Count; i++) + { + imageSizes.Add(new Vector2(data.distinctMaterialTextures[i].idealWidth, data.distinctMaterialTextures[i].idealHeight)); + paddings.Add(new AtlasPadding(data._atlasPadding)); + } + MB2_TexturePacker tp = CreateTexturePacker(data._packingAlgorithm); + tp.atlasMustBePowerOfTwo = data._meshBakerTexturePackerForcePowerOfTwo; + packerRects = tp.GetRects(imageSizes, paddings, data._maxAtlasSize, data._maxAtlasSize, true); + //Debug.Assert(packerRects.Length != 0); + } + return packerRects; + } + */ + + internal static MB2_TexturePacker CreateTexturePacker(MB2_PackingAlgorithmEnum _packingAlgorithm) + { + if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker) + { + return new MB2_TexturePackerRegular(); + } + else if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Fast) + { + return new MB2_TexturePackerRegular(); + } + else if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePaker_Fast_V2_Beta) + { + return new MB2_TexturePackerRegular(); + } + else if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Horizontal) + { + MB2_TexturePackerHorizontalVert tp = new MB2_TexturePackerHorizontalVert(); + tp.packingOrientation = MB2_TexturePackerHorizontalVert.TexturePackingOrientation.horizontal; + return tp; + } + else if (_packingAlgorithm == MB2_PackingAlgorithmEnum.MeshBakerTexturePacker_Vertical) + { + MB2_TexturePackerHorizontalVert tp = new MB2_TexturePackerHorizontalVert(); + tp.packingOrientation = MB2_TexturePackerHorizontalVert.TexturePackingOrientation.vertical; + return tp; + } + else + { + Debug.LogError("packing algorithm must be one of the MeshBaker options to create a Texture Packer"); + } + return null; + } + + internal static Vector2 GetAdjustedForScaleAndOffset2Dimensions(MeshBakerMaterialTexture source, Vector2 obUVoffset, Vector2 obUVscale, TexturePipelineData data, MB2_LogLevel LOG_LEVEL) + { + if (source.matTilingRect.x == 0f && source.matTilingRect.y == 0f && source.matTilingRect.width == 1f && source.matTilingRect.height == 1f) + { + if (data._fixOutOfBoundsUVs) + { + if (obUVoffset.x == 0f && obUVoffset.y == 0f && obUVscale.x == 1f && obUVscale.y == 1f) + { + return new Vector2(source.width, source.height); //no adjustment necessary + } + } + else + { + return new Vector2(source.width, source.height); //no adjustment necessary + } + } + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("GetAdjustedForScaleAndOffset2Dimensions: " + source.GetTexName() + " " + obUVoffset + " " + obUVscale); + Rect encapsulatingSamplingRect = source.GetEncapsulatingSamplingRect().GetRect(); + float newWidth = encapsulatingSamplingRect.width * source.width; + float newHeight = encapsulatingSamplingRect.height * source.height; + + if (newWidth > data._maxTilingBakeSize) newWidth = data._maxTilingBakeSize; + if (newHeight > data._maxTilingBakeSize) newHeight = data._maxTilingBakeSize; + if (newWidth < 1f) newWidth = 1f; + if (newHeight < 1f) newHeight = 1f; + return new Vector2(newWidth, newHeight); + } + + /* + Unity uses a non-standard format for storing normals for some platforms. Imagine the standard format is English, Unity's is French + When the normal-map checkbox is ticked on the asset importer the normal map is translated into french. When we build the normal atlas + we are reading the french. When we save and click the normal map tickbox we are translating french -> french. A double transladion that + breaks the normal map. To fix this we need to "unconvert" the normal map to english when saving the atlas as a texture so that unity importer + can do its thing properly. + */ + internal static Color32 ConvertNormalFormatFromUnity_ToStandard(Color32 c) + { + Vector3 n = Vector3.zero; + n.x = c.a * 2f - 1f; + n.y = c.g * 2f - 1f; + n.z = Mathf.Sqrt(1 - n.x * n.x - n.y * n.y); + //now repack in the regular format + Color32 cc = new Color32(); + cc.a = 1; + cc.r = (byte)((n.x + 1f) * .5f); + cc.g = (byte)((n.y + 1f) * .5f); + cc.b = (byte)((n.z + 1f) * .5f); + return cc; + } + + /// <summary> + /// Returns the tiling scale and offset for a given material. + /// + /// The only reason that this method is necessary is the Standard shader. Each texture in a material has a scale and offset stored with it. + /// Most shaders use the scale and offset accociated with each texture map. The Standard shader does not do this. It uses the scale and offset + /// associated with _MainTex for most of the maps. + /// </summary> + internal static void GetMaterialScaleAndOffset(Material mat, string propertyName, out Vector2 offset, out Vector2 scale) + { + if (mat == null) + { + Debug.LogError("Material was null. Should never happen."); + offset = Vector2.zero; + scale = Vector2.one; + } + + if ((mat.shader.name.Equals("Standard") || mat.shader.name.Equals("Standard (Specular setup)")) && mat.HasProperty("_MainTex")) + { + offset = mat.GetTextureOffset("_MainTex"); + scale = mat.GetTextureScale("_MainTex"); + } else + { + offset = mat.GetTextureOffset(propertyName); + scale = mat.GetTextureScale(propertyName); + } + } + + internal static float GetSubmeshArea(Mesh m, int submeshIdx) + { + if (submeshIdx >= m.subMeshCount || submeshIdx < 0) + { + return 0f; + } + Vector3[] vs = m.vertices; + int[] tris = m.GetIndices(submeshIdx); + float area = 0f; + for (int i = 0; i < tris.Length; i += 3) + { + Vector3 v0 = vs[tris[i]]; + Vector3 v1 = vs[tris[i + 1]]; + Vector3 v2 = vs[tris[i + 2]]; + Vector3 cross = Vector3.Cross(v1 - v0, v2 - v0); + area += cross.magnitude / 2f; + } + return area; + } + + internal static bool IsPowerOfTwo(int x) + { + return (x & (x - 1)) == 0; + } + + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPipeline.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPipeline.cs.meta new file mode 100644 index 00000000..96e6261e --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB3_TextureCombinerPipeline.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 035d1b5b42714c340a93439644c39417 +timeCreated: 1522041808 +licenseType: Store +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB_TextureArrays.cs b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB_TextureArrays.cs new file mode 100644 index 00000000..5a3749af --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB_TextureArrays.cs @@ -0,0 +1,437 @@ +using System.Collections; +using System.Collections.Generic; +using System; +using UnityEngine; + +namespace DigitalOpus.MB.Core +{ + public class MB_TextureArrays + { + internal class TexturePropertyData + { + public bool[] doMips; + public int[] numMipMaps; + public TextureFormat[] formats; + public Vector2[] sizes; + } + + internal static bool[] DetermineWhichPropertiesHaveTextures(MB_AtlasesAndRects[] resultAtlasesAndRectSlices) + { + bool[] hasTexForProperty = new bool[resultAtlasesAndRectSlices[0].texPropertyNames.Length]; + for (int i = 0; i < hasTexForProperty.Length; i++) + { + hasTexForProperty[i] = false; + } + + int numSlices = resultAtlasesAndRectSlices.Length; + for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++) + { + MB_AtlasesAndRects slice = resultAtlasesAndRectSlices[sliceIdx]; + Debug.Assert(slice.texPropertyNames.Length == hasTexForProperty.Length); + for (int propIdx = 0; propIdx < hasTexForProperty.Length; propIdx++) + { + if (slice.atlases[propIdx] != null) + { + hasTexForProperty[propIdx] = true; + } + } + } + + return hasTexForProperty; + } + + static bool IsLinearProperty(List<ShaderTextureProperty> shaderPropertyNames, string shaderProperty) + { + for (int i = 0; i < shaderPropertyNames.Count; i++) + { + if (shaderPropertyNames[i].name == shaderProperty && shaderPropertyNames[i].isNormalMap) return true; + } + + return false; + } + + /// <summary> + /// Creates one texture array per texture property. + /// </summary> + /// <returns></returns> + internal static Texture2DArray[] CreateTextureArraysForResultMaterial(TexturePropertyData texPropertyData, List<ShaderTextureProperty> masterListOfTexProperties, MB_AtlasesAndRects[] resultAtlasesAndRectSlices, + bool[] hasTexForProperty, MB3_TextureCombiner combiner, MB2_LogLevel LOG_LEVEL) + { + Debug.Assert(texPropertyData.sizes.Length == hasTexForProperty.Length); + + // ASSUMPTION all slices in the same format and the same size, alpha channel and mipMapCount + string[] texPropertyNames = resultAtlasesAndRectSlices[0].texPropertyNames; + Debug.Assert(texPropertyNames.Length == hasTexForProperty.Length); + Texture2DArray[] texArrays = new Texture2DArray[texPropertyNames.Length]; + + // Each texture property (_MainTex, _Bump, ...) becomes a Texture2DArray + for (int propIdx = 0; propIdx < texPropertyNames.Length; propIdx++) + { + if (!hasTexForProperty[propIdx]) continue; + string propName = texPropertyNames[propIdx]; + int numSlices = resultAtlasesAndRectSlices.Length; + int w = (int)texPropertyData.sizes[propIdx].x; + int h = (int)texPropertyData.sizes[propIdx].y; + int numMips = (int)texPropertyData.numMipMaps[propIdx]; + TextureFormat format = texPropertyData.formats[propIdx]; + bool doMipMaps = texPropertyData.doMips[propIdx]; + + Debug.Assert(QualitySettings.desiredColorSpace == QualitySettings.activeColorSpace, "Wanted to use color space " + QualitySettings.desiredColorSpace + " but the activeColorSpace was " + QualitySettings.activeColorSpace + " hardware may not support the desired color space."); + + bool isLinear = MBVersion.GetProjectColorSpace() == ColorSpace.Linear; + { + if (IsLinearProperty(masterListOfTexProperties, propName)) + { + isLinear = true; + } else + { + isLinear = false; + } + } + + Texture2DArray texArray = new Texture2DArray(w, h, numSlices, format, doMipMaps, isLinear); + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.LogFormat("Creating Texture2DArray for property: {0} w: {1} h: {2} format: {3} doMips: {4} isLinear: {5}", propName, w, h, format, doMipMaps, isLinear); + for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++) + { + Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].atlases.Length == texPropertyNames.Length); + Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].texPropertyNames[propIdx] == propName); + Texture2D srcTex = resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx]; + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.LogFormat("Slice: {0} texture: {1}", sliceIdx, srcTex); + bool isCopy = false; + if (srcTex == null) + { + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.LogFormat("Texture is null for slice: {0} creating temporary texture", sliceIdx); + // Slices might not have all textures create a dummy if needed. + srcTex = combiner._createTemporaryTexture(propName, w, h, format, doMipMaps, isLinear); + } + + Debug.Assert(srcTex.width == texArray.width, "Source texture is not the same width as the texture array '" + srcTex + " srcWidth:" + srcTex.width + " texArrayWidth:" + texArray.width); + Debug.Assert(srcTex.height == texArray.height, "Source texture is not the same height as the texture array " + srcTex + " srcWidth:" + srcTex.height + " texArrayWidth:" + texArray.height); + Debug.Assert(srcTex.mipmapCount == numMips, "Source texture does have not the same number of mips as the texture array: " + srcTex + " numMipsTex: " + srcTex.mipmapCount + " numMipsTexArray: " + numMips + " texDims: " + srcTex.width + "x" + srcTex.height); + Debug.Assert(srcTex.format == format, "Formats should have been converted before this. Texture: " + srcTex + "Source: " + srcTex.format + " Targ: " + format); + + for (int mipIdx = 0; mipIdx < numMips; mipIdx++) + { + Graphics.CopyTexture(srcTex, 0, mipIdx, texArray, sliceIdx, mipIdx); + } + + if (isCopy) MB_Utility.Destroy(srcTex); + } + + texArray.Apply(); + texArrays[propIdx] = texArray; + } + + return texArrays; + } + + internal static bool ConvertTexturesToReadableFormat(TexturePropertyData texturePropertyData, + MB_AtlasesAndRects[] resultAtlasesAndRectSlices, + bool[] hasTexForProperty, List<ShaderTextureProperty> textureShaderProperties, + MB3_TextureCombiner combiner, + MB2_LogLevel logLevel, + List<Texture2D> createdTemporaryTextureAssets, + MB2_EditorMethodsInterface textureEditorMethods) + { + for (int propIdx = 0; propIdx < hasTexForProperty.Length; propIdx++) + { + if (!hasTexForProperty[propIdx]) continue; + + TextureFormat format = texturePropertyData.formats[propIdx]; + + if (!textureEditorMethods.TextureImporterFormatExistsForTextureFormat(format)) + { + Debug.LogError("Could not find target importer format matching " + format); + return false; + } + + int numSlices = resultAtlasesAndRectSlices.Length; + int targetWidth = (int)texturePropertyData.sizes[propIdx].x; + int targetHeight = (int)texturePropertyData.sizes[propIdx].y; + for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++) + { + Texture2D sliceTex = resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx]; + + Debug.Assert(sliceTex != null, "sliceIdx " + sliceIdx + " " + propIdx); + if (sliceTex != null) + { + if (!MBVersion.IsTextureReadable(sliceTex)) + { + textureEditorMethods.SetReadWriteFlag(sliceTex, true, true); + } + + bool isAsset = textureEditorMethods.IsAnAsset(sliceTex); + if (logLevel >= MB2_LogLevel.trace) Debug.Log("Considering format of texture: " + sliceTex + " format:" + sliceTex.format); + if ((sliceTex.width != targetWidth || sliceTex.height != targetHeight) || + (!isAsset && sliceTex.format != format)) + { + // Do this the horrible hard way. It is only possible to resize textures in TrueColor formats, + // And only possible to switch formats using the Texture importer. + // Create a resized temporary texture asset in ARGB32 format. Then set its texture format and reimport + resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx] = textureEditorMethods.CreateTemporaryAssetCopy(textureShaderProperties[propIdx], sliceTex, targetWidth, targetHeight, format, logLevel); + createdTemporaryTextureAssets.Add(resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx]); + } + else if (sliceTex.format != format) + { + textureEditorMethods.ConvertTextureFormat_PlatformOverride(sliceTex, format, textureShaderProperties[propIdx].isNormalMap); + } + } + else + { + + } + + if (resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx].format != format) + { + Debug.LogError("Could not convert texture to format " + format + + ". This can happen if the target build platform in build settings does not support textures in this format." + + " It may be necessary to switch the build platform in order to build texture arrays in this format."); + return false; + } + } + } + + return true; + } + + /// <summary> + /// Third coord is num mipmaps. + /// </summary> + internal static void FindBestSizeAndMipCountAndFormatForTextureArrays(List<ShaderTextureProperty> texPropertyNames, int maxAtlasSize, MB_TextureArrayFormatSet targetFormatSet, MB_AtlasesAndRects[] resultAtlasesAndRectSlices, + TexturePropertyData texturePropertyData) + { + texturePropertyData.sizes = new Vector2[texPropertyNames.Count]; + texturePropertyData.doMips = new bool[texPropertyNames.Count]; + texturePropertyData.numMipMaps = new int[texPropertyNames.Count]; + texturePropertyData.formats = new TextureFormat[texPropertyNames.Count]; + for (int propIdx = 0; propIdx < texPropertyNames.Count; propIdx++) + { + int numSlices = resultAtlasesAndRectSlices.Length; + texturePropertyData.sizes[propIdx] = new Vector3(16, 16, 1); + bool hasMips = false; + int mipCount = 1; + for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++) + { + Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].atlases.Length == texPropertyNames.Count); + Debug.Assert(resultAtlasesAndRectSlices[sliceIdx].texPropertyNames[propIdx] == texPropertyNames[propIdx].name); + Texture2D sliceTex = resultAtlasesAndRectSlices[sliceIdx].atlases[propIdx]; + if (sliceTex != null) + { + if (sliceTex.mipmapCount > 1) hasMips = true; + mipCount = Mathf.Max(mipCount, sliceTex.mipmapCount); + texturePropertyData.sizes[propIdx].x = Mathf.Min(Mathf.Max(texturePropertyData.sizes[propIdx].x, sliceTex.width), maxAtlasSize); + texturePropertyData.sizes[propIdx].y = Mathf.Min(Mathf.Max(texturePropertyData.sizes[propIdx].y, sliceTex.height), maxAtlasSize); + //texturePropertyData.sizes[propIdx].z = Mathf.Max(texturePropertyData.sizes[propIdx].z, sliceTex.mipmapCount); + texturePropertyData.formats[propIdx] = targetFormatSet.GetFormatForProperty(texPropertyNames[propIdx].name); + } + } + + int numberMipsForMaxAtlasSize = Mathf.CeilToInt(Mathf.Log(maxAtlasSize, 2)) + 1; + texturePropertyData.numMipMaps[propIdx] = Mathf.Min(numberMipsForMaxAtlasSize, mipCount); + texturePropertyData.doMips[propIdx] = hasMips; + } + } + + public static IEnumerator _CreateAtlasesCoroutineSingleResultMaterial(int resMatIdx, + MB_TextureArrayResultMaterial bakedMatsAndSlicesResMat, + MB_MultiMaterialTexArray resMatConfig, + List<GameObject> objsToMesh, + MB3_TextureCombiner combiner, + MB_TextureArrayFormatSet[] textureArrayOutputFormats, + MB_MultiMaterialTexArray[] resultMaterialsTexArray, + List<ShaderTextureProperty> customShaderProperties, + ProgressUpdateDelegate progressInfo, + MB3_TextureCombiner.CreateAtlasesCoroutineResult coroutineResult, + bool saveAtlasesAsAssets = false, + MB2_EditorMethodsInterface editorMethods = null, + float maxTimePerFrame = .01f) + { + MB2_LogLevel LOG_LEVEL = combiner.LOG_LEVEL; + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Baking atlases for result material " + resMatIdx + " num slices:" + resMatConfig.slices.Count); + // Each result material can be one set of slices per textureProperty. Each slice can be an atlas. + // Create atlases for each slice. + List<MB3_TextureCombiner.TemporaryTexture> generatedTemporaryAtlases = new List<MB3_TextureCombiner.TemporaryTexture>(); + { + combiner.saveAtlasesAsAssets = false; // Don't want generated atlas slices to be assets + List<MB_TexArraySlice> slicesConfig = resMatConfig.slices; + for (int sliceIdx = 0; sliceIdx < slicesConfig.Count; sliceIdx++) + { + Material resMatToPass = null; + List<MB_TexArraySliceRendererMatPair> srcMatAndObjPairs = slicesConfig[sliceIdx].sourceMaterials; + + if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" Baking atlases for result material:" + resMatIdx + " slice:" + sliceIdx); + resMatToPass = resMatConfig.combinedMaterial; + combiner.fixOutOfBoundsUVs = slicesConfig[sliceIdx].considerMeshUVs; + MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult coroutineResult2 = new MB3_TextureCombiner.CombineTexturesIntoAtlasesCoroutineResult(); + MB_AtlasesAndRects sliceAtlasesAndRectOutput = bakedMatsAndSlicesResMat.slices[sliceIdx]; + List<Material> usedMats = new List<Material>(); + slicesConfig[sliceIdx].GetAllUsedMaterials(usedMats); + + yield return combiner.CombineTexturesIntoAtlasesCoroutine(progressInfo, sliceAtlasesAndRectOutput, resMatToPass, slicesConfig[sliceIdx].GetAllUsedRenderers(objsToMesh), usedMats, editorMethods, coroutineResult2, maxTimePerFrame, + onlyPackRects: false, splitAtlasWhenPackingIfTooBig: false); + coroutineResult.success = coroutineResult2.success; + if (!coroutineResult.success) + { + coroutineResult.isFinished = true; + yield break; + } + + // Track which slices are new generated texture instances. Atlases could be original texture assets (one tex per atlas) or temporary texture instances in memory that will need to be destroyed. + { + for (int texPropIdx = 0; texPropIdx < sliceAtlasesAndRectOutput.atlases.Length; texPropIdx++) + { + Texture2D atlas = sliceAtlasesAndRectOutput.atlases[texPropIdx]; + if (atlas != null) + { + bool atlasWasASourceTexture = false; + for (int srcMatIdx = 0; srcMatIdx < srcMatAndObjPairs.Count; srcMatIdx++) + { + Material srcMat = srcMatAndObjPairs[srcMatIdx].sourceMaterial; + if (srcMat.HasProperty(sliceAtlasesAndRectOutput.texPropertyNames[texPropIdx]) && + srcMat.GetTexture(sliceAtlasesAndRectOutput.texPropertyNames[texPropIdx]) == atlas) + { + atlasWasASourceTexture = true; + break; + } + } + + if (!atlasWasASourceTexture) + { + generatedTemporaryAtlases.Add(new MB3_TextureCombiner.TemporaryTexture(sliceAtlasesAndRectOutput.texPropertyNames[texPropIdx], atlas)); + } + } + } + } // end visit slices + + Debug.Assert(combiner._getNumTemporaryTextures() == 0, "Combiner should have no temporary textures."); + } + + combiner.saveAtlasesAsAssets = saveAtlasesAsAssets; // Restore original setting. + } + + // Generated atlas textures are temporary for texture arrays. They exist only in memory. Need to be cleaned up after we create slices. + for (int i = 0; i < generatedTemporaryAtlases.Count; i++) + { + combiner.AddTemporaryTexture(generatedTemporaryAtlases[i]); + } + + List<ShaderTextureProperty> texPropertyNames = new List<ShaderTextureProperty>(); + MB3_TextureCombinerPipeline._CollectPropertyNames(texPropertyNames, customShaderProperties, resMatConfig.combinedMaterial, LOG_LEVEL); + + // The slices are built from different source-material-lists. Each slice can have different sets of texture properties missing (nulls). + // Build a master list of texture properties. + bool[] hasTexForProperty = MB_TextureArrays.DetermineWhichPropertiesHaveTextures(bakedMatsAndSlicesResMat.slices); + + List<Texture2D> temporaryTextureAssets = new List<Texture2D>(); + try + { + MB_MultiMaterialTexArray resMaterial = resMatConfig; + Dictionary<string, MB_TexArrayForProperty> resTexArraysByProperty = new Dictionary<string, MB_TexArrayForProperty>(); + { + // Initialize so I don't need to check if properties exist later. + for (int propIdx = 0; propIdx < texPropertyNames.Count; propIdx++) + { + if (hasTexForProperty[propIdx]) resTexArraysByProperty[texPropertyNames[propIdx].name] = + new MB_TexArrayForProperty(texPropertyNames[propIdx].name, new MB_TextureArrayReference[textureArrayOutputFormats.Length]); + } + } + + + MB3_TextureCombinerNonTextureProperties textureBlender = null; + textureBlender = new MB3_TextureCombinerNonTextureProperties(LOG_LEVEL, combiner.considerNonTextureProperties); + textureBlender.LoadTextureBlendersIfNeeded(resMatConfig.combinedMaterial); + textureBlender.AdjustNonTextureProperties(resMatConfig.combinedMaterial, texPropertyNames, editorMethods); + + // Vist each TextureFormatSet + for (int texFormatSetIdx = 0; texFormatSetIdx < textureArrayOutputFormats.Length; texFormatSetIdx++) + { + MB_TextureArrayFormatSet textureArrayFormatSet = textureArrayOutputFormats[texFormatSetIdx]; + editorMethods.Clear(); + + MB_TextureArrays.TexturePropertyData texPropertyData = new MB_TextureArrays.TexturePropertyData(); + MB_TextureArrays.FindBestSizeAndMipCountAndFormatForTextureArrays(texPropertyNames, combiner.maxAtlasSize, textureArrayFormatSet, bakedMatsAndSlicesResMat.slices, texPropertyData); + + // Create textures we might need to create if they don't exist. + { + for (int propIdx = 0; propIdx < hasTexForProperty.Length; propIdx++) + { + if (hasTexForProperty[propIdx]) + { + TextureFormat format = texPropertyData.formats[propIdx]; + int numSlices = bakedMatsAndSlicesResMat.slices.Length; + int targetWidth = (int)texPropertyData.sizes[propIdx].x; + int targetHeight = (int)texPropertyData.sizes[propIdx].y; + for (int sliceIdx = 0; sliceIdx < numSlices; sliceIdx++) + { + if (bakedMatsAndSlicesResMat.slices[sliceIdx].atlases[propIdx] == null) + { + // Can only setSolidColor on truecolor textures. First create a texture in trucolor format + Texture2D sliceTex = new Texture2D(targetWidth, targetHeight, TextureFormat.ARGB32, texPropertyData.doMips[propIdx]); + Color col = textureBlender.GetColorForTemporaryTexture(resMatConfig.slices[sliceIdx].sourceMaterials[0].sourceMaterial, texPropertyNames[propIdx]); + MB_Utility.setSolidColor(sliceTex, col); + // Now create a copy of this texture in target format. + bakedMatsAndSlicesResMat.slices[sliceIdx].atlases[propIdx] = editorMethods.CreateTemporaryAssetCopy(texPropertyNames[propIdx], sliceTex, targetWidth, targetHeight, format, LOG_LEVEL); + temporaryTextureAssets.Add(bakedMatsAndSlicesResMat.slices[sliceIdx].atlases[propIdx]); + MB_Utility.Destroy(sliceTex); + } + } + } + } + } + + + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Converting source textures to readable formats."); + if (MB_TextureArrays.ConvertTexturesToReadableFormat(texPropertyData, bakedMatsAndSlicesResMat.slices, hasTexForProperty, texPropertyNames, combiner, LOG_LEVEL, temporaryTextureAssets, editorMethods)) + { + // We now have a set of slices (one per textureProperty). Build these into Texture2DArray's. + if (LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Creating texture arrays"); + if (LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("THERE MAY BE ERRORS IN THE CONSOLE ABOUT 'Rebuilding mipmaps ... not supported'. THESE ARE PROBABLY FALSE POSITIVES AND CAN BE IGNORED."); + Texture2DArray[] textureArrays = MB_TextureArrays.CreateTextureArraysForResultMaterial(texPropertyData, texPropertyNames, bakedMatsAndSlicesResMat.slices, hasTexForProperty, combiner, LOG_LEVEL); + + + // Now have texture arrays for a result material, for all props. Save it. + for (int propIdx = 0; propIdx < textureArrays.Length; propIdx++) + { + if (hasTexForProperty[propIdx]) + { + MB_TextureArrayReference texRef = new MB_TextureArrayReference(textureArrayFormatSet.name, textureArrays[propIdx]); + resTexArraysByProperty[texPropertyNames[propIdx].name].formats[texFormatSetIdx] = texRef; + if (saveAtlasesAsAssets) + { + editorMethods.SaveTextureArrayToAssetDatabase(textureArrays[propIdx], + textureArrayFormatSet.GetFormatForProperty(texPropertyNames[propIdx].name), + bakedMatsAndSlicesResMat.slices[0].texPropertyNames[propIdx], + propIdx, resMaterial.combinedMaterial); + } + } + } + } + } // end vist format set + + resMaterial.textureProperties = new List<MB_TexArrayForProperty>(); + foreach (MB_TexArrayForProperty val in resTexArraysByProperty.Values) + { + resMaterial.textureProperties.Add(val); + } + } + catch (Exception e) + { + Debug.LogError(e.Message + "\n" + e.StackTrace.ToString()); + coroutineResult.isFinished = true; + coroutineResult.success = false; + } + finally + { + editorMethods.RestoreReadFlagsAndFormats(progressInfo); + combiner._destroyAllTemporaryTextures(); + for (int i = 0; i < temporaryTextureAssets.Count; i++) + { + editorMethods.DestroyAsset(temporaryTextureAssets[i]); + } + temporaryTextureAssets.Clear(); + } + } + } +} diff --git a/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB_TextureArrays.cs.meta b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB_TextureArrays.cs.meta new file mode 100644 index 00000000..f9f155d0 --- /dev/null +++ b/VRCSDK3Worlds/Assets/MeshBaker/scripts/core/TextureCombiner/MB_TextureArrays.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c42ec825239a48f409a96b012d8b6362 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: |