MB3_MeshCombinerSimpleUVAdjusterAtlas.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Specialized;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Text;
  7. using DigitalOpus.MB.Core;
  8. namespace DigitalOpus.MB.Core
  9. {
  10. public partial class MB3_MeshCombinerSingle : MB3_MeshCombiner
  11. {
  12. public class UVAdjuster_Atlas
  13. {
  14. MB2_TextureBakeResults textureBakeResults;
  15. MB2_LogLevel LOG_LEVEL;
  16. int[] numTimesMatAppearsInAtlas;
  17. MB_MaterialAndUVRect[] matsAndSrcUVRect;
  18. public UVAdjuster_Atlas(MB2_TextureBakeResults tbr, MB2_LogLevel ll)
  19. {
  20. textureBakeResults = tbr;
  21. LOG_LEVEL = ll;
  22. matsAndSrcUVRect = tbr.materialsAndUVRects;
  23. //count the number of times a material appears in the atlas. used for fast lookup
  24. numTimesMatAppearsInAtlas = new int[matsAndSrcUVRect.Length];
  25. for (int i = 0; i < matsAndSrcUVRect.Length; i++)
  26. {
  27. if (numTimesMatAppearsInAtlas[i] > 1)
  28. {
  29. continue;
  30. }
  31. int count = 1;
  32. for (int j = i + 1; j < matsAndSrcUVRect.Length; j++)
  33. {
  34. if (matsAndSrcUVRect[i].material == matsAndSrcUVRect[j].material)
  35. {
  36. count++;
  37. }
  38. }
  39. numTimesMatAppearsInAtlas[i] = count;
  40. if (count > 1)
  41. {
  42. //allMatsAreUnique = false;
  43. for (int j = i + 1; j < matsAndSrcUVRect.Length; j++)
  44. {
  45. if (matsAndSrcUVRect[i].material == matsAndSrcUVRect[j].material)
  46. {
  47. numTimesMatAppearsInAtlas[j] = count;
  48. }
  49. }
  50. }
  51. }
  52. }
  53. public bool MapSharedMaterialsToAtlasRects(Material[] sharedMaterials,
  54. bool checkTargetSubmeshIdxsFromPreviousBake,
  55. Mesh m, MeshChannelsCache meshChannelsCache,
  56. Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisResultsCache,
  57. OrderedDictionary sourceMats2submeshIdx_map, GameObject go, MB_DynamicGameObject dgoOut)
  58. {
  59. MB_TextureTilingTreatment[] tilingTreatment = new MB_TextureTilingTreatment[sharedMaterials.Length];
  60. Rect[] uvRectsInAtlas = new Rect[sharedMaterials.Length];
  61. Rect[] encapsulatingRect = new Rect[sharedMaterials.Length];
  62. Rect[] sourceMaterialTiling = new Rect[sharedMaterials.Length];
  63. int[] sliceIdx = new int[sharedMaterials.Length];
  64. String errorMsg = "";
  65. for (int srcSubmeshIdx = 0; srcSubmeshIdx<sharedMaterials.Length; srcSubmeshIdx++)
  66. {
  67. System.Object subIdx = sourceMats2submeshIdx_map[sharedMaterials[srcSubmeshIdx]];
  68. int resMatIdx;
  69. if (subIdx == null)
  70. {
  71. Debug.LogError("Source object " + go.name + " used a material " + sharedMaterials[srcSubmeshIdx] + " that was not in the baked materials.");
  72. return false;
  73. }
  74. else
  75. {
  76. resMatIdx = (int) subIdx;
  77. if (checkTargetSubmeshIdxsFromPreviousBake)
  78. {
  79. /*
  80. Possibilities:
  81. Consider a mesh with three submeshes with materials A, B, C that map to
  82. different submeshes in the combined mesh, AA,BB,CC. The user is updating the UVs on a
  83. MeshRenderer so that object 'one' now uses material C => CC instead of A => AA. This will mean that the
  84. triangle buffers will need to be resized. This is not allowed in UpdateGameObjects.
  85. Must map to the same submesh that the old one mapped to.
  86. */
  87. if (resMatIdx != dgoOut.targetSubmeshIdxs[srcSubmeshIdx])
  88. {
  89. 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]));
  90. return false;
  91. }
  92. }
  93. }
  94. if (!TryMapMaterialToUVRect(sharedMaterials[srcSubmeshIdx], m, srcSubmeshIdx, resMatIdx, meshChannelsCache, meshAnalysisResultsCache,
  95. out tilingTreatment[srcSubmeshIdx],
  96. out uvRectsInAtlas[srcSubmeshIdx],
  97. out encapsulatingRect[srcSubmeshIdx],
  98. out sourceMaterialTiling[srcSubmeshIdx],
  99. out sliceIdx[srcSubmeshIdx],
  100. ref errorMsg, LOG_LEVEL))
  101. {
  102. Debug.LogError(errorMsg);
  103. return false;
  104. }
  105. }
  106. dgoOut.uvRects = uvRectsInAtlas;
  107. dgoOut.encapsulatingRect = encapsulatingRect;
  108. dgoOut.sourceMaterialTiling = sourceMaterialTiling;
  109. dgoOut.textureArraySliceIdx = sliceIdx;
  110. return true;
  111. }
  112. public void _copyAndAdjustUVsFromMesh(MB2_TextureBakeResults tbr, MB_DynamicGameObject dgo, Mesh mesh, int uvChannel, int vertsIdx, Vector2[] uvsOut, float[] uvsSliceIdx, MeshChannelsCache meshChannelsCache)
  113. {
  114. Debug.Assert(dgo.sourceSharedMaterials != null && dgo.sourceSharedMaterials.Length == dgo.targetSubmeshIdxs.Length,
  115. "sourceSharedMaterials array was a different size than the targetSubmeshIdxs. Was this old data that is being updated? " + dgo.sourceSharedMaterials.Length);
  116. Vector2[] nuvs = meshChannelsCache.GetUVChannel(uvChannel, mesh);
  117. int[] done = new int[nuvs.Length]; //use this to track uvs that have already been adjusted don't adjust twice
  118. for (int l = 0; l < done.Length; l++) done[l] = -1;
  119. bool triangleArraysOverlap = false;
  120. //Rect uvRectInSrc = new Rect (0f,0f,1f,1f);
  121. //need to address the UVs through the submesh indexes because
  122. //each submesh has a different UV index
  123. bool doTextureArray = tbr.resultType == MB2_TextureBakeResults.ResultType.textureArray;
  124. for (int srcSubmeshIdx = 0; srcSubmeshIdx < dgo.targetSubmeshIdxs.Length; srcSubmeshIdx++)
  125. {
  126. int[] srcSubTris;
  127. if (dgo._tmpSubmeshTris != null)
  128. {
  129. srcSubTris = dgo._tmpSubmeshTris[srcSubmeshIdx].data;
  130. }
  131. else
  132. {
  133. srcSubTris = mesh.GetTriangles(srcSubmeshIdx);
  134. }
  135. float slice = dgo.textureArraySliceIdx[srcSubmeshIdx];
  136. int resultSubmeshIdx = dgo.targetSubmeshIdxs[srcSubmeshIdx];
  137. if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(String.Format("Build UV transform for mesh {0} submesh {1} encapsulatingRect {2}",
  138. dgo.name, srcSubmeshIdx, dgo.encapsulatingRect[srcSubmeshIdx]));
  139. bool considerUVs = textureBakeResults.GetConsiderMeshUVs(resultSubmeshIdx, dgo.sourceSharedMaterials[srcSubmeshIdx]);
  140. Rect rr = MB3_TextureCombinerMerging.BuildTransformMeshUV2AtlasRect(
  141. considerUVs,
  142. dgo.uvRects[srcSubmeshIdx],
  143. dgo.obUVRects == null || dgo.obUVRects.Length == 0 ? new Rect(0, 0, 1, 1) : dgo.obUVRects[srcSubmeshIdx],
  144. dgo.sourceMaterialTiling[srcSubmeshIdx],
  145. dgo.encapsulatingRect[srcSubmeshIdx]);
  146. for (int srcSubTriIdx = 0; srcSubTriIdx < srcSubTris.Length; srcSubTriIdx++)
  147. {
  148. int srcVertIdx = srcSubTris[srcSubTriIdx];
  149. if (done[srcVertIdx] == -1)
  150. {
  151. done[srcVertIdx] = srcSubmeshIdx; //prevents a uv from being adjusted twice. Same vert can be on more than one submesh.
  152. Vector2 nuv = nuvs[srcVertIdx]; //don't modify nuvs directly because it is cached and we might be re-using
  153. //if (textureBakeResults.fixOutOfBoundsUVs) {
  154. //uvRectInSrc can be larger than (out of bounds uvs) or smaller than 0..1
  155. //this transforms the uvs so they fit inside the uvRectInSrc sample box
  156. // scale, shift to fit in atlas rect
  157. nuv.x = rr.x + nuv.x * rr.width;
  158. nuv.y = rr.y + nuv.y * rr.height;
  159. int idx = vertsIdx + srcVertIdx;
  160. uvsOut[idx] = nuv;
  161. if (doTextureArray)
  162. {
  163. uvsSliceIdx[idx] = slice;
  164. }
  165. }
  166. if (done[srcVertIdx] != srcSubmeshIdx)
  167. {
  168. triangleArraysOverlap = true;
  169. }
  170. }
  171. }
  172. if (triangleArraysOverlap)
  173. {
  174. if (LOG_LEVEL >= MB2_LogLevel.warn)
  175. Debug.LogWarning(dgo.name + "has submeshes which share verticies. Adjusted uvs may not map correctly in combined atlas.");
  176. }
  177. if (LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(string.Format("_copyAndAdjustUVsFromMesh copied {0} verts", nuvs.Length));
  178. }
  179. /// <summary>
  180. /// A material can appear more than once in an atlas if using fixOutOfBoundsUVs.
  181. /// in this case you need to use the UV rect of the mesh to find the correct rectangle.
  182. /// If the all properties on the mat use the same tiling then
  183. /// encapsulatingRect can be larger and will include baked UV and material tiling
  184. /// If mat uses different tiling for different maps then encapsulatingRect is the uvs of
  185. /// source mesh used to bake atlas and sourceMaterialTilingOut is 0,0,1,1. This works because
  186. /// material tiling was baked into the atlas.
  187. /// </summary>
  188. public bool TryMapMaterialToUVRect(Material mat, Mesh m, int submeshIdx, int idxInResultMats,
  189. MB3_MeshCombinerSingle.MeshChannelsCache meshChannelCache,
  190. Dictionary<int, MB_Utility.MeshAnalysisResult[]> meshAnalysisCache,
  191. out MB_TextureTilingTreatment tilingTreatment,
  192. out Rect rectInAtlas,
  193. out Rect encapsulatingRectOut,
  194. out Rect sourceMaterialTilingOut,
  195. out int sliceIdx,
  196. ref String errorMsg,
  197. MB2_LogLevel logLevel)
  198. {
  199. if (textureBakeResults.version < MB2_TextureBakeResults.VERSION)
  200. {
  201. textureBakeResults.UpgradeToCurrentVersion(textureBakeResults);
  202. }
  203. tilingTreatment = MB_TextureTilingTreatment.unknown;
  204. if (textureBakeResults.materialsAndUVRects.Length == 0)
  205. {
  206. 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.";
  207. rectInAtlas = new Rect();
  208. encapsulatingRectOut = new Rect();
  209. sourceMaterialTilingOut = new Rect();
  210. sliceIdx = -1;
  211. return false;
  212. }
  213. if (mat == null)
  214. {
  215. rectInAtlas = new Rect();
  216. encapsulatingRectOut = new Rect();
  217. sourceMaterialTilingOut = new Rect();
  218. sliceIdx = -1;
  219. errorMsg = String.Format("Mesh {0} Had no material on submesh {1} cannot map to a material in the atlas", m.name, submeshIdx);
  220. return false;
  221. }
  222. if (submeshIdx >= m.subMeshCount)
  223. {
  224. errorMsg = "Submesh index is greater than the number of submeshes";
  225. rectInAtlas = new Rect();
  226. encapsulatingRectOut = new Rect();
  227. sourceMaterialTilingOut = new Rect();
  228. sliceIdx = -1;
  229. return false;
  230. }
  231. //find the first index of this material
  232. int idx = -1;
  233. for (int i = 0; i < matsAndSrcUVRect.Length; i++)
  234. {
  235. if (mat == matsAndSrcUVRect[i].material)
  236. {
  237. idx = i;
  238. break;
  239. }
  240. }
  241. // if couldn't find material
  242. if (idx == -1)
  243. {
  244. rectInAtlas = new Rect();
  245. encapsulatingRectOut = new Rect();
  246. sourceMaterialTilingOut = new Rect();
  247. sliceIdx = -1;
  248. errorMsg = String.Format("Material {0} could not be found in the Texture Bake Result", mat.name);
  249. return false;
  250. }
  251. bool considerUVs = textureBakeResults.GetConsiderMeshUVs(idxInResultMats, mat);
  252. if (!considerUVs)
  253. {
  254. if (numTimesMatAppearsInAtlas[idx] != 1)
  255. {
  256. 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]);
  257. }
  258. MB_MaterialAndUVRect mr = matsAndSrcUVRect[idx];
  259. rectInAtlas = mr.atlasRect;
  260. tilingTreatment = mr.tilingTreatment;
  261. encapsulatingRectOut = mr.GetEncapsulatingRect();
  262. sourceMaterialTilingOut = mr.GetMaterialTilingRect();
  263. sliceIdx = mr.textureArraySliceIdx;
  264. return true;
  265. }
  266. else
  267. {
  268. //todo what if no UVs
  269. //Find UV rect in source mesh
  270. MB_Utility.MeshAnalysisResult[] mar;
  271. if (!meshAnalysisCache.TryGetValue(m.GetInstanceID(), out mar))
  272. {
  273. mar = new MB_Utility.MeshAnalysisResult[m.subMeshCount];
  274. for (int j = 0; j < m.subMeshCount; j++)
  275. {
  276. Vector2[] uvss = meshChannelCache.GetUv0Raw(m);
  277. MB_Utility.hasOutOfBoundsUVs(uvss, m, ref mar[j], j);
  278. }
  279. meshAnalysisCache.Add(m.GetInstanceID(), mar);
  280. }
  281. //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
  282. //find a record that has an atlas uvRect capable of containing this
  283. bool found = false;
  284. Rect encapsulatingRect = new Rect(0, 0, 0, 0);
  285. Rect sourceMaterialTiling = new Rect(0, 0, 0, 0);
  286. if (logLevel >= MB2_LogLevel.trace)
  287. {
  288. 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")));
  289. }
  290. for (int i = idx; i < matsAndSrcUVRect.Length; i++)
  291. {
  292. MB_MaterialAndUVRect matAndUVrect = matsAndSrcUVRect[i];
  293. if (matAndUVrect.material == mat)
  294. {
  295. if (matAndUVrect.allPropsUseSameTiling)
  296. {
  297. encapsulatingRect = matAndUVrect.allPropsUseSameTiling_samplingEncapsulatinRect;
  298. sourceMaterialTiling = matAndUVrect.allPropsUseSameTiling_sourceMaterialTiling;
  299. }
  300. else
  301. {
  302. encapsulatingRect = matAndUVrect.propsUseDifferntTiling_srcUVsamplingRect;
  303. sourceMaterialTiling = new Rect(0, 0, 1, 1);
  304. }
  305. if (MB2_TextureBakeResults.IsMeshAndMaterialRectEnclosedByAtlasRect(
  306. matAndUVrect.tilingTreatment,
  307. mar[submeshIdx].uvRect,
  308. sourceMaterialTiling,
  309. encapsulatingRect,
  310. logLevel))
  311. {
  312. if (logLevel >= MB2_LogLevel.trace)
  313. {
  314. Debug.Log("Found rect in atlas capable of containing tiled sampling rect for mesh " + m + " at idx=" + i);
  315. }
  316. idx = i;
  317. found = true;
  318. break;
  319. }
  320. }
  321. }
  322. if (found)
  323. {
  324. MB_MaterialAndUVRect mr = matsAndSrcUVRect[idx];
  325. rectInAtlas = mr.atlasRect;
  326. tilingTreatment = mr.tilingTreatment;
  327. encapsulatingRectOut = mr.GetEncapsulatingRect();
  328. sourceMaterialTilingOut = mr.GetMaterialTilingRect();
  329. sliceIdx = mr.textureArraySliceIdx;
  330. return true;
  331. }
  332. else
  333. {
  334. rectInAtlas = new Rect();
  335. encapsulatingRectOut = new Rect();
  336. sourceMaterialTilingOut = new Rect();
  337. sliceIdx = -1;
  338. 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);
  339. return false;
  340. }
  341. }
  342. }
  343. }
  344. }
  345. }