MB3_BatchPrefabBakerEditor.cs 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. using UnityEngine;
  2. using UnityEditor;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using DigitalOpus.MB.Core;
  8. using System.Text.RegularExpressions;
  9. [CustomEditor(typeof(MB3_BatchPrefabBaker))]
  10. public class MB3_BatchPrefabBakerEditor : Editor
  11. {
  12. public class UnityTransform
  13. {
  14. public Vector3 p;
  15. public Quaternion q;
  16. public Vector3 s;
  17. public Transform t;
  18. public UnityTransform(Transform t)
  19. {
  20. this.t = t;
  21. p = t.localPosition;
  22. q = t.localRotation;
  23. s = t.localScale;
  24. }
  25. }
  26. private enum TargetMeshTreatment
  27. {
  28. createNewMesh,
  29. replaceMesh,
  30. reuseMesh
  31. }
  32. private class ProcessedMeshInfo
  33. {
  34. public Material[] srcMaterials;
  35. public Material[] targMaterials;
  36. public Mesh targetMesh;
  37. }
  38. SerializedObject prefabBaker = null;
  39. GUIContent GUIContentLogLevelContent = new GUIContent("Log Level");
  40. GUIContent GUIContentBatchBakePrefabReplacePrefab = new GUIContent("Batch Bake Prefabs (Replace Prefab)",
  41. "This will clone the source prefab, replace the meshes in the clone with baked versions and replace the target prefab with the clone.\n\n" +
  42. "IF ANY CHANGES HAD BEEN MADE TO THE TARGET PREFAB, THOSE WILL BE LOST.");
  43. GUIContent GUIContentBatchBakePrefabOnlyMeshesAndMats = new GUIContent("Batch Bake Prefabs (Only Replace Meshes & Materials)",
  44. "This will attempt to match the meshes used by the target prefab to those used by the source prefab. For this to work" +
  45. " well, the source and target prefabs should have the same hierarchy. The meshes and materials in the target prefab will be updated to baked versions. " +
  46. " Modifications to the target prefab other than the meshes and materials will be preserved.\n\n" +
  47. "Check the console for errors after baking the prefabs.");
  48. SerializedProperty prefabRows, outputFolder, logLevel;
  49. Color buttonColor = new Color(.8f, .8f, 1f, 1f);
  50. [MenuItem("GameObject/Create Other/Mesh Baker/Batch Prefab Baker", false, 1000)]
  51. public static void CreateNewBatchPrefabBaker()
  52. {
  53. if (MB3_MeshCombiner.EVAL_VERSION)
  54. {
  55. Debug.LogError("The prefab baker is only available in the full version of MeshBaker.");
  56. return;
  57. }
  58. MB3_TextureBaker[] mbs = (MB3_TextureBaker[])Editor.FindObjectsOfType(typeof(MB3_TextureBaker));
  59. Regex regex = new Regex(@"\((\d+)\)$", RegexOptions.Compiled | RegexOptions.CultureInvariant);
  60. int largest = 0;
  61. try
  62. {
  63. for (int i = 0; i < mbs.Length; i++)
  64. {
  65. Match match = regex.Match(mbs[i].name);
  66. if (match.Success)
  67. {
  68. int val = Convert.ToInt32(match.Groups[1].Value);
  69. if (val >= largest)
  70. largest = val + 1;
  71. }
  72. }
  73. }
  74. catch (Exception e)
  75. {
  76. if (e == null) e = null; //Do nothing supress compiler warning
  77. }
  78. GameObject nmb = new GameObject("BatchPrefabBaker (" + largest + ")");
  79. nmb.transform.position = Vector3.zero;
  80. nmb.AddComponent<MB3_BatchPrefabBaker>();
  81. nmb.AddComponent<MB3_TextureBaker>();
  82. nmb.AddComponent<MB3_MeshBaker>();
  83. }
  84. void OnEnable()
  85. {
  86. prefabBaker = new SerializedObject(target);
  87. prefabRows = prefabBaker.FindProperty("prefabRows");
  88. outputFolder = prefabBaker.FindProperty("outputPrefabFolder");
  89. logLevel = prefabBaker.FindProperty("LOG_LEVEL");
  90. }
  91. void OnDisable()
  92. {
  93. prefabBaker = null;
  94. }
  95. public override void OnInspectorGUI()
  96. {
  97. prefabBaker.Update();
  98. EditorGUILayout.HelpBox(
  99. "This tool speeds up the process of preparing prefabs " +
  100. " for static and dynamic batching. It creates duplicate prefab assets and meshes " +
  101. "that share a combined material. Source assets are not touched.\n\n" +
  102. "1) bake the textures to be used by prefabs using the MB3_TextureBaker attached to this game object\n" +
  103. "2) enter the number of prefabs to bake in the 'Prefab Rows Size' field\n" +
  104. "3) drag source prefab assets to the 'Source Prefab' slots. These should be project assets not scene objects. Renderers" +
  105. " do not need to be in the root of the prefab. There can be more than one" +
  106. " renderer in each prefab.\n" +
  107. "4) choose a folder where the result prefabs will be stored and click 'Create Empty Result Prefabs'\n" +
  108. "5) click 'Batch Bake Prefabs'\n" +
  109. "6) Check the console for messages and errors", MessageType.Info);
  110. EditorGUILayout.PropertyField(logLevel, GUIContentLogLevelContent);
  111. EditorGUILayout.PropertyField(prefabRows, true);
  112. EditorGUILayout.LabelField("Output Folder", EditorStyles.boldLabel);
  113. EditorGUILayout.LabelField(outputFolder.stringValue);
  114. if (GUILayout.Button("Browse For Output Folder"))
  115. {
  116. string path = EditorUtility.OpenFolderPanel("Browse For Output Folder", "", "");
  117. outputFolder.stringValue = path;
  118. }
  119. if (GUILayout.Button("Create Empty Result Prefabs"))
  120. {
  121. CreateEmptyOutputPrefabs();
  122. }
  123. Color oldColor = GUI.backgroundColor;
  124. GUI.backgroundColor = buttonColor;
  125. if (GUILayout.Button(GUIContentBatchBakePrefabReplacePrefab))
  126. {
  127. MB3_BatchPrefabBaker pb = (MB3_BatchPrefabBaker)target;
  128. BakePrefabs(pb, true);
  129. }
  130. if (GUILayout.Button(GUIContentBatchBakePrefabOnlyMeshesAndMats))
  131. {
  132. MB3_BatchPrefabBaker pb = (MB3_BatchPrefabBaker)target;
  133. BakePrefabs(pb, false);
  134. }
  135. GUI.backgroundColor = oldColor;
  136. if (GUILayout.Button("Poplate Prefab Rows From Texture Baker"))
  137. {
  138. PopulatePrefabRowsFromTextureBaker((MB3_BatchPrefabBaker)prefabBaker.targetObject);
  139. }
  140. prefabBaker.ApplyModifiedProperties();
  141. prefabBaker.SetIsDifferentCacheDirty();
  142. }
  143. public void PopulatePrefabRowsFromTextureBaker(MB3_BatchPrefabBaker prefabBaker)
  144. {
  145. Undo.RecordObject(prefabBaker, "Populate prefab rows");
  146. MB3_TextureBaker texBaker = prefabBaker.GetComponent<MB3_TextureBaker>();
  147. List<GameObject> prefabs = new List<GameObject>();
  148. List<GameObject> gos = texBaker.GetObjectsToCombine();
  149. for (int i = 0; i < gos.Count; i++)
  150. {
  151. GameObject go = (GameObject)PrefabUtility.FindPrefabRoot(gos[i]);
  152. UnityEngine.Object obj = MBVersionEditor.PrefabUtility_GetCorrespondingObjectFromSource(go);
  153. if (obj != null && obj is GameObject)
  154. {
  155. if (!prefabs.Contains((GameObject)obj)) prefabs.Add((GameObject)obj);
  156. }
  157. else
  158. {
  159. Debug.LogWarning(String.Format("Object {0} did not have a prefab", gos[i]));
  160. }
  161. }
  162. List<MB3_BatchPrefabBaker.MB3_PrefabBakerRow> newRows = new List<MB3_BatchPrefabBaker.MB3_PrefabBakerRow>();
  163. for (int i = 0; i < prefabs.Count; i++)
  164. {
  165. MB3_BatchPrefabBaker.MB3_PrefabBakerRow row = new MB3_BatchPrefabBaker.MB3_PrefabBakerRow();
  166. row.sourcePrefab = prefabs[i];
  167. newRows.Add(row);
  168. }
  169. prefabBaker.prefabRows = newRows.ToArray();
  170. }
  171. public static void BakePrefabs(MB3_BatchPrefabBaker pb, bool doReplaceTargetPrefab)
  172. {
  173. if (pb.LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("Batch baking prefabs");
  174. if (Application.isPlaying)
  175. {
  176. Debug.LogError("The BatchPrefabBaker cannot be run in play mode.");
  177. return;
  178. }
  179. MB3_MeshBaker mb = pb.GetComponent<MB3_MeshBaker>();
  180. if (mb == null)
  181. {
  182. Debug.LogError("Prefab baker needs to be attached to a Game Object with a MB3_MeshBaker component.");
  183. return;
  184. }
  185. if (mb.textureBakeResults == null)
  186. {
  187. Debug.LogError("Texture Bake Results is not set");
  188. return;
  189. }
  190. if (mb.meshCombiner.outputOption != MB2_OutputOptions.bakeMeshAssetsInPlace)
  191. {
  192. mb.meshCombiner.outputOption = MB2_OutputOptions.bakeMeshAssetsInPlace;
  193. }
  194. MB2_TextureBakeResults tbr = mb.textureBakeResults;
  195. HashSet<Mesh> sourceMeshes = new HashSet<Mesh>();
  196. HashSet<Mesh> allResultMeshes = new HashSet<Mesh>();
  197. //validate prefabs
  198. for (int i = 0; i < pb.prefabRows.Length; i++)
  199. {
  200. if (pb.prefabRows[i] == null || pb.prefabRows[i].sourcePrefab == null)
  201. {
  202. Debug.LogError("Source Prefab on row " + i + " is not set.");
  203. return;
  204. }
  205. if (pb.prefabRows[i].resultPrefab == null)
  206. {
  207. Debug.LogError("Result Prefab on row " + i + " is not set.");
  208. return;
  209. }
  210. for (int j = i + 1; j < pb.prefabRows.Length; j++)
  211. {
  212. if (pb.prefabRows[i].sourcePrefab == pb.prefabRows[j].sourcePrefab)
  213. {
  214. Debug.LogError("Rows " + i + " and " + j + " contain the same source prefab");
  215. return;
  216. }
  217. }
  218. for (int j = 0; j < pb.prefabRows.Length; j++)
  219. {
  220. if (pb.prefabRows[i].sourcePrefab == pb.prefabRows[j].resultPrefab)
  221. {
  222. Debug.LogError("Row " + i + " source prefab is the same as row " + j + " result prefab");
  223. return;
  224. }
  225. }
  226. if (PrefabUtility.GetPrefabType(pb.prefabRows[i].sourcePrefab) != PrefabType.ModelPrefab &&
  227. PrefabUtility.GetPrefabType(pb.prefabRows[i].sourcePrefab) != PrefabType.Prefab)
  228. {
  229. Debug.LogError("Row " + i + " source prefab is not a prefab asset");
  230. return;
  231. }
  232. if (PrefabUtility.GetPrefabType(pb.prefabRows[i].resultPrefab) != PrefabType.ModelPrefab &&
  233. PrefabUtility.GetPrefabType(pb.prefabRows[i].resultPrefab) != PrefabType.Prefab)
  234. {
  235. Debug.LogError("Row " + i + " result prefab is not a prefab asset");
  236. return;
  237. }
  238. GameObject so = (GameObject)Instantiate(pb.prefabRows[i].sourcePrefab);
  239. GameObject ro = (GameObject)Instantiate(pb.prefabRows[i].resultPrefab);
  240. Renderer[] rs = (Renderer[])so.GetComponentsInChildren<Renderer>();
  241. for (int j = 0; j < rs.Length; j++)
  242. {
  243. if (IsGoodToBake(rs[j], tbr))
  244. {
  245. sourceMeshes.Add(MB_Utility.GetMesh(rs[j].gameObject));
  246. }
  247. }
  248. rs = (Renderer[])ro.GetComponentsInChildren<Renderer>();
  249. for (int j = 0; j < rs.Length; j++)
  250. {
  251. Renderer r = rs[j];
  252. if (r is MeshRenderer || r is SkinnedMeshRenderer)
  253. {
  254. Mesh m = MB_Utility.GetMesh(r.gameObject);
  255. if (m != null)
  256. {
  257. allResultMeshes.Add(m);
  258. }
  259. }
  260. }
  261. DestroyImmediate(so); //todo should cache these and have a proper cleanup at end
  262. DestroyImmediate(ro);
  263. }
  264. sourceMeshes.IntersectWith(allResultMeshes);
  265. HashSet<Mesh> sourceMeshesThatAreUsedByResult = sourceMeshes;
  266. if (sourceMeshesThatAreUsedByResult.Count > 0)
  267. {
  268. foreach (Mesh m in sourceMeshesThatAreUsedByResult)
  269. {
  270. Debug.LogWarning("Mesh " + m + " is used by both the source and result prefabs. New meshes will be created.");
  271. }
  272. //return;
  273. }
  274. List<UnityTransform> unityTransforms = new List<UnityTransform>();
  275. // Bake the meshes using the meshBaker component one prefab at a time
  276. for (int prefabIdx = 0; prefabIdx < pb.prefabRows.Length; prefabIdx++)
  277. {
  278. if (doReplaceTargetPrefab)
  279. {
  280. ProcessPrefabRowReplaceTargetPrefab(pb, pb.prefabRows[prefabIdx], tbr, unityTransforms, mb);
  281. }
  282. else
  283. {
  284. ProcessPrefabRowOnlyMeshesAndMaterials(pb, pb.prefabRows[prefabIdx], tbr, unityTransforms, mb);
  285. }
  286. }
  287. AssetDatabase.Refresh();
  288. mb.ClearMesh();
  289. }
  290. private static void ProcessPrefabRowOnlyMeshesAndMaterials(MB3_BatchPrefabBaker pb, MB3_BatchPrefabBaker.MB3_PrefabBakerRow pr, MB2_TextureBakeResults tbr, List<UnityTransform> unityTransforms, MB3_MeshBaker mb)
  291. {
  292. if (pb.LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("==== Processing Source Prefab " + pr.sourcePrefab);
  293. GameObject srcPrefab = pr.sourcePrefab;
  294. GameObject targetPrefab = pr.resultPrefab;
  295. string targetPrefabName = AssetDatabase.GetAssetPath(targetPrefab);
  296. GameObject srcPrefabInstance = GameObject.Instantiate(srcPrefab);
  297. GameObject targPrefabInstance = GameObject.Instantiate(targetPrefab);
  298. Renderer[] rs = srcPrefabInstance.GetComponentsInChildren<Renderer>();
  299. if (rs.Length < 1)
  300. {
  301. Debug.LogWarning("Prefab " + pr.sourcePrefab + " does not have a renderer");
  302. DestroyImmediate(srcPrefabInstance);
  303. DestroyImmediate(targPrefabInstance);
  304. return;
  305. }
  306. Renderer[] sourceRenderers = srcPrefabInstance.GetComponentsInChildren<Renderer>();
  307. Dictionary<Mesh, List<ProcessedMeshInfo>> processedMeshesSrcToTargetMap = new Dictionary<Mesh, List<ProcessedMeshInfo>>();
  308. for (int i = 0; i < sourceRenderers.Length; i++)
  309. {
  310. if (!IsGoodToBake(sourceRenderers[i], tbr))
  311. {
  312. continue;
  313. }
  314. Mesh sourceMesh = MB_Utility.GetMesh(sourceRenderers[i].gameObject);
  315. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("== Visiting renderer: " + sourceRenderers[i]);
  316. // Try to find an existing mesh in the target that we can re-use
  317. Mesh targetMeshAsset = null;
  318. Transform tr = FindCorrespondingTransform(srcPrefabInstance.transform, sourceRenderers[i].transform, targPrefabInstance.transform);
  319. Renderer targetRenderer = null;
  320. if (tr != null)
  321. {
  322. Mesh targMesh = MB_Utility.GetMesh(tr.gameObject);
  323. // Only replace target meshes if they are part of the target prefab.
  324. if (AssetDatabase.GetAssetPath(targMesh) == AssetDatabase.GetAssetPath(targetPrefab))
  325. {
  326. targetRenderer = tr.GetComponent<Renderer>();
  327. if (sourceRenderers[i].GetType() == targetRenderer.GetType())
  328. {
  329. targetMeshAsset = MB_Utility.GetMesh(tr.gameObject);
  330. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Found correspoinding transform in target prefab: " + tr + " mesh: " + targetMeshAsset);
  331. }
  332. else
  333. {
  334. Debug.LogError("The renderer on the target prefab matching source prefab " + pr.sourcePrefab + " was not the same kind of renderer " + sourceRenderers[i].name);
  335. continue;
  336. }
  337. }
  338. }
  339. else
  340. {
  341. Debug.LogError("There was a renderer in source prefab " + pr.sourcePrefab + " that could not be a matched to a target renderer in the target prefab: " + sourceRenderers[i].name +
  342. " This can happen if the hierarchy in the source prefab is different from the hierarchy in the target prefab.");
  343. continue;
  344. }
  345. // Check that we haven't processed this mesh already.
  346. List<ProcessedMeshInfo> lpmi;
  347. if (processedMeshesSrcToTargetMap.TryGetValue(sourceMesh, out lpmi))
  348. {
  349. Material[] srcMats = MB_Utility.GetGOMaterials(sourceRenderers[i].gameObject);
  350. for (int j = 0; j < lpmi.Count; j++)
  351. {
  352. if (ComapreMaterials(srcMats, lpmi[j].srcMaterials))
  353. {
  354. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Found already processed mesh that uses the same mats");
  355. targetMeshAsset = lpmi[j].targetMesh;
  356. break;
  357. }
  358. }
  359. }
  360. Material[] sourceMaterials = MB_Utility.GetGOMaterials(sourceRenderers[i].gameObject);
  361. TargetMeshTreatment targetMeshTreatment = TargetMeshTreatment.createNewMesh;
  362. string newMeshName = sourceMesh.name;
  363. if (targetMeshAsset != null)
  364. {
  365. // check if this mesh has already been processed
  366. processedMeshesSrcToTargetMap.TryGetValue(sourceMesh, out lpmi);
  367. if (lpmi != null)
  368. {
  369. // check if this mesh uses the same materials as one of the processed meshs
  370. bool foundMatch = false;
  371. bool targetMeshHasBeenUsed = false;
  372. Material[] foundMatchMaterials = null;
  373. for (int j = 0; j < lpmi.Count; j++)
  374. {
  375. if (lpmi[j].targetMesh == targetMeshAsset)
  376. {
  377. targetMeshHasBeenUsed = true;
  378. }
  379. if (ComapreMaterials(sourceMaterials, lpmi[j].srcMaterials))
  380. {
  381. foundMatchMaterials = lpmi[j].targMaterials;
  382. foundMatch = true;
  383. break;
  384. }
  385. }
  386. if (foundMatch)
  387. {
  388. // If materials match then we can re-use this processed mesh don't process.
  389. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" we can re-use this processed mesh don't process. " + targetMeshAsset);
  390. targetMeshTreatment = TargetMeshTreatment.reuseMesh;
  391. MB_Utility.SetMesh(tr.gameObject, targetMeshAsset);
  392. SetMaterials(foundMatchMaterials, targetRenderer);
  393. continue;
  394. }
  395. else
  396. {
  397. if (targetMeshHasBeenUsed)
  398. {
  399. // we need a new target mesh with a safe different name
  400. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" we can't re-use this processed mesh create new with different name. " + targetMeshAsset);
  401. newMeshName = GetNameForNewMesh(AssetDatabase.GetAssetPath(targetPrefab), newMeshName);
  402. targetMeshTreatment = TargetMeshTreatment.createNewMesh;
  403. targetMeshAsset = null;
  404. }
  405. else
  406. {
  407. // is it safe to reuse the target mesh
  408. // we need a new target mesh with a safe different name
  409. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" we can replace this processed mesh. " + targetMeshAsset);
  410. targetMeshTreatment = TargetMeshTreatment.replaceMesh;
  411. }
  412. }
  413. }
  414. else
  415. {
  416. // source mesh has not been processed can reuse the target mesh
  417. targetMeshTreatment = TargetMeshTreatment.replaceMesh;
  418. }
  419. }
  420. if (targetMeshTreatment == TargetMeshTreatment.replaceMesh)
  421. {
  422. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Replace mesh " + targetMeshAsset);
  423. EditorUtility.CopySerialized(sourceMesh, targetMeshAsset);
  424. AssetDatabase.SaveAssets();
  425. AssetDatabase.ImportAsset(targetPrefabName);
  426. }
  427. else if (targetMeshTreatment == TargetMeshTreatment.createNewMesh)
  428. {
  429. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Create new mesh " + newMeshName);
  430. targetMeshAsset = GameObject.Instantiate<Mesh>(sourceMesh);
  431. targetMeshAsset.name = newMeshName;
  432. AssetDatabase.AddObjectToAsset(targetMeshAsset, targetPrefab);
  433. Debug.Assert(targetMeshAsset != null);
  434. // need a new mesh
  435. }
  436. if (targetMeshTreatment == TargetMeshTreatment.createNewMesh || targetMeshTreatment == TargetMeshTreatment.replaceMesh)
  437. {
  438. if (ProcessMesh(sourceRenderers[i], targetMeshAsset, unityTransforms, mb))
  439. {
  440. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Done processing mesh " + targetMeshAsset + " verts " + targetMeshAsset.vertexCount);
  441. ProcessedMeshInfo pmi = new ProcessedMeshInfo();
  442. pmi.targetMesh = targetMeshAsset;
  443. pmi.srcMaterials = sourceMaterials;
  444. pmi.targMaterials = sourceRenderers[i].sharedMaterials;
  445. AddToDictionary(sourceMesh, pmi, processedMeshesSrcToTargetMap);
  446. }
  447. else
  448. {
  449. Debug.LogError("Error processing mesh " + targetMeshAsset);
  450. }
  451. }
  452. MB_Utility.SetMesh(tr.gameObject, targetMeshAsset);
  453. }
  454. PrefabUtility.ReplacePrefab(targPrefabInstance, targetPrefab, ReplacePrefabOptions.ReplaceNameBased);
  455. GameObject.DestroyImmediate(srcPrefabInstance);
  456. GameObject.DestroyImmediate(targPrefabInstance);
  457. // Destroy obsolete meshes
  458. UnityEngine.Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(targetPrefabName);
  459. HashSet<Mesh> usedByTarget = new HashSet<Mesh>();
  460. foreach (List<ProcessedMeshInfo> ll in processedMeshesSrcToTargetMap.Values)
  461. {
  462. for (int i = 0; i < ll.Count; i++)
  463. {
  464. usedByTarget.Add(ll[i].targetMesh);
  465. }
  466. }
  467. int numDestroyed = 0;
  468. for (int i = 0; i < allAssets.Length; i++)
  469. {
  470. if (allAssets[i] is Mesh)
  471. {
  472. if (!usedByTarget.Contains((Mesh)allAssets[i]) && AssetDatabase.GetAssetPath(allAssets[i]) == AssetDatabase.GetAssetPath(targetPrefab))
  473. {
  474. numDestroyed++;
  475. GameObject.DestroyImmediate(allAssets[i], true);
  476. }
  477. }
  478. }
  479. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Destroyed " + numDestroyed + " meshes");
  480. AssetDatabase.SaveAssets();
  481. //--------------------------
  482. }
  483. private static void ProcessPrefabRowReplaceTargetPrefab(MB3_BatchPrefabBaker pb, MB3_BatchPrefabBaker.MB3_PrefabBakerRow pr, MB2_TextureBakeResults tbr, List<UnityTransform> unityTransforms, MB3_MeshBaker mb)
  484. {
  485. if (pb.LOG_LEVEL >= MB2_LogLevel.info) Debug.Log("==== Processing Source Prefab " + pr.sourcePrefab);
  486. GameObject srcPrefab = pr.sourcePrefab;
  487. GameObject targetPrefab = pr.resultPrefab;
  488. string targetPrefabName = AssetDatabase.GetAssetPath(targetPrefab);
  489. GameObject prefabInstance = GameObject.Instantiate(srcPrefab);
  490. Renderer[] rs = prefabInstance.GetComponentsInChildren<Renderer>();
  491. if (rs.Length < 1)
  492. {
  493. Debug.LogWarning("Prefab " + pr.sourcePrefab + " does not have a renderer");
  494. DestroyImmediate(prefabInstance);
  495. return;
  496. }
  497. Renderer[] sourceRenderers = prefabInstance.GetComponentsInChildren<Renderer>();
  498. Dictionary<Mesh, List<ProcessedMeshInfo>> processedMeshesSrcToTargetMap = new Dictionary<Mesh, List<ProcessedMeshInfo>>();
  499. for (int i = 0; i < sourceRenderers.Length; i++)
  500. {
  501. if (!IsGoodToBake(sourceRenderers[i], tbr))
  502. {
  503. continue;
  504. }
  505. Mesh sourceMesh = MB_Utility.GetMesh(sourceRenderers[i].gameObject);
  506. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("== Visiting renderer: " + sourceRenderers[i]);
  507. // Try to find an existing mesh in the target that we can re-use
  508. Mesh targetMeshAsset = null;
  509. Transform tr = FindCorrespondingTransform(prefabInstance.transform, sourceRenderers[i].transform, targetPrefab.transform);
  510. if (tr != null)
  511. {
  512. Mesh targMesh = MB_Utility.GetMesh(tr.gameObject);
  513. // Only replace target meshes if they are part of the target prefab.
  514. if (AssetDatabase.GetAssetPath(targMesh) == AssetDatabase.GetAssetPath(targetPrefab))
  515. {
  516. targetMeshAsset = MB_Utility.GetMesh(tr.gameObject);
  517. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Found correspoinding transform in target prefab: " + tr + " mesh: " + targetMeshAsset);
  518. }
  519. }
  520. // Check that we haven't processed this mesh already.
  521. List<ProcessedMeshInfo> lpmi;
  522. if (processedMeshesSrcToTargetMap.TryGetValue(sourceMesh, out lpmi))
  523. {
  524. Material[] srcMats = MB_Utility.GetGOMaterials(sourceRenderers[i].gameObject);
  525. for (int j = 0; j < lpmi.Count; j++)
  526. {
  527. if (ComapreMaterials(srcMats, lpmi[j].srcMaterials))
  528. {
  529. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log("Found already processed mesh that uses the same mats");
  530. targetMeshAsset = lpmi[j].targetMesh;
  531. break;
  532. }
  533. }
  534. }
  535. Material[] sourceMaterials = MB_Utility.GetGOMaterials(sourceRenderers[i].gameObject);
  536. TargetMeshTreatment targetMeshTreatment = TargetMeshTreatment.createNewMesh;
  537. string newMeshName = sourceMesh.name;
  538. if (targetMeshAsset != null)
  539. {
  540. // check if this mesh has already been processed
  541. processedMeshesSrcToTargetMap.TryGetValue(sourceMesh, out lpmi);
  542. if (lpmi != null)
  543. {
  544. // check if this mesh uses the same materials as one of the processed meshs
  545. bool foundMatch = false;
  546. bool targetMeshHasBeenUsed = false;
  547. Material[] foundMatchMaterials = null;
  548. for (int j = 0; j < lpmi.Count; j++)
  549. {
  550. if (lpmi[j].targetMesh == targetMeshAsset)
  551. {
  552. targetMeshHasBeenUsed = true;
  553. }
  554. if (ComapreMaterials(sourceMaterials, lpmi[j].srcMaterials))
  555. {
  556. foundMatchMaterials = lpmi[j].targMaterials;
  557. foundMatch = true;
  558. break;
  559. }
  560. }
  561. if (foundMatch)
  562. {
  563. // If materials match then we can re-use this processed mesh don't process.
  564. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" we can re-use this processed mesh don't process. " + targetMeshAsset);
  565. targetMeshTreatment = TargetMeshTreatment.reuseMesh;
  566. MB_Utility.SetMesh(sourceRenderers[i].gameObject, targetMeshAsset);
  567. SetMaterials(foundMatchMaterials, sourceRenderers[i]);
  568. continue;
  569. }
  570. else
  571. {
  572. if (targetMeshHasBeenUsed)
  573. {
  574. // we need a new target mesh with a safe different name
  575. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" we can't re-use this processed mesh create new with different name. " + targetMeshAsset);
  576. newMeshName = GetNameForNewMesh(AssetDatabase.GetAssetPath(targetPrefab), newMeshName);
  577. targetMeshTreatment = TargetMeshTreatment.createNewMesh;
  578. targetMeshAsset = null;
  579. }
  580. else
  581. {
  582. // is it safe to reuse the target mesh
  583. // we need a new target mesh with a safe different name
  584. if (pb.LOG_LEVEL >= MB2_LogLevel.trace) Debug.Log(" we can replace this processed mesh. " + targetMeshAsset);
  585. targetMeshTreatment = TargetMeshTreatment.replaceMesh;
  586. }
  587. }
  588. }
  589. else
  590. {
  591. // source mesh has not been processed can reuse the target mesh
  592. targetMeshTreatment = TargetMeshTreatment.replaceMesh;
  593. }
  594. }
  595. if (targetMeshTreatment == TargetMeshTreatment.replaceMesh)
  596. {
  597. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Replace mesh " + targetMeshAsset);
  598. EditorUtility.CopySerialized(sourceMesh, targetMeshAsset);
  599. AssetDatabase.SaveAssets();
  600. AssetDatabase.ImportAsset(targetPrefabName);
  601. }
  602. else if (targetMeshTreatment == TargetMeshTreatment.createNewMesh)
  603. {
  604. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Create new mesh " + newMeshName);
  605. targetMeshAsset = GameObject.Instantiate<Mesh>(sourceMesh);
  606. targetMeshAsset.name = newMeshName;
  607. AssetDatabase.AddObjectToAsset(targetMeshAsset, targetPrefab);
  608. Debug.Assert(targetMeshAsset != null);
  609. // need a new mesh
  610. }
  611. if (targetMeshTreatment == TargetMeshTreatment.createNewMesh || targetMeshTreatment == TargetMeshTreatment.replaceMesh)
  612. {
  613. if (ProcessMesh(sourceRenderers[i], targetMeshAsset, unityTransforms, mb))
  614. {
  615. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Done processing mesh " + targetMeshAsset + " verts " + targetMeshAsset.vertexCount);
  616. ProcessedMeshInfo pmi = new ProcessedMeshInfo();
  617. pmi.targetMesh = targetMeshAsset;
  618. pmi.srcMaterials = sourceMaterials;
  619. pmi.targMaterials = sourceRenderers[i].sharedMaterials;
  620. AddToDictionary(sourceMesh, pmi, processedMeshesSrcToTargetMap);
  621. }
  622. else
  623. {
  624. Debug.LogError("Error processing mesh " + targetMeshAsset);
  625. }
  626. }
  627. MB_Utility.SetMesh(sourceRenderers[i].gameObject, targetMeshAsset);
  628. }
  629. PrefabUtility.ReplacePrefab(prefabInstance, targetPrefab, ReplacePrefabOptions.ReplaceNameBased);
  630. GameObject.DestroyImmediate(prefabInstance);
  631. // Destroy obsolete meshes
  632. UnityEngine.Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(targetPrefabName);
  633. HashSet<Mesh> usedByTarget = new HashSet<Mesh>();
  634. foreach (List<ProcessedMeshInfo> ll in processedMeshesSrcToTargetMap.Values)
  635. {
  636. for (int i = 0; i < ll.Count; i++)
  637. {
  638. usedByTarget.Add(ll[i].targetMesh);
  639. }
  640. }
  641. int numDestroyed = 0;
  642. for (int i = 0; i < allAssets.Length; i++)
  643. {
  644. if (allAssets[i] is Mesh)
  645. {
  646. if (!usedByTarget.Contains((Mesh)allAssets[i]) && AssetDatabase.GetAssetPath(allAssets[i]) == AssetDatabase.GetAssetPath(targetPrefab))
  647. {
  648. numDestroyed++;
  649. GameObject.DestroyImmediate(allAssets[i], true);
  650. }
  651. }
  652. }
  653. if (pb.LOG_LEVEL >= MB2_LogLevel.debug) Debug.Log("Destroyed " + numDestroyed + " meshes");
  654. AssetDatabase.SaveAssets();
  655. //--------------------------
  656. }
  657. private static string GetNameForNewMesh(string prefabPath, string baseName)
  658. {
  659. // get all Mesh assets in prefab
  660. UnityEngine.Object[] objs = AssetDatabase.LoadAllAssetsAtPath(prefabPath);
  661. string[] oldNames = new string[objs.Length]; // TODO get these
  662. for (int i = 0; i < oldNames.Length; i++)
  663. {
  664. oldNames[i] = objs[i].name;
  665. }
  666. bool isUnique = false;
  667. int idx = 0;
  668. string name = baseName;
  669. while (!isUnique)
  670. {
  671. bool wasAMatch = false;
  672. for (int i = 0; i < oldNames.Length; i++)
  673. {
  674. if (oldNames[i].Equals(name))
  675. {
  676. wasAMatch = true;
  677. break;
  678. }
  679. }
  680. if (wasAMatch)
  681. {
  682. idx++;
  683. name = baseName + idx;
  684. }
  685. else
  686. {
  687. isUnique = true;
  688. }
  689. }
  690. return name;
  691. }
  692. private static bool ComapreMaterials(Material[] a, Material[] b)
  693. {
  694. if (a.Length != b.Length) return false;
  695. for (int i = 0; i < a.Length; i++)
  696. {
  697. if (a[i] != b[i]) return false;
  698. }
  699. return true;
  700. }
  701. private static void AddToDictionary(Mesh sourceMesh, ProcessedMeshInfo pmi, Dictionary<Mesh, List<ProcessedMeshInfo>> dict)
  702. {
  703. List<ProcessedMeshInfo> lpmi;
  704. if (!dict.ContainsKey(sourceMesh))
  705. {
  706. lpmi = new List<ProcessedMeshInfo>();
  707. dict[sourceMesh] = lpmi;
  708. }
  709. else
  710. {
  711. lpmi = dict[sourceMesh];
  712. }
  713. lpmi.Add(pmi);
  714. }
  715. private static bool ProcessMesh(Renderer r, Mesh m, List<UnityTransform> unityTransforms, MB3_MeshBaker mb)
  716. {
  717. unityTransforms.Clear();
  718. // position rotation and scale are baked into combined mesh.
  719. // Remember all the transforms settings then
  720. // record transform values to root of hierarchy
  721. Transform t = r.transform;
  722. if (t != t.root)
  723. {
  724. do
  725. {
  726. unityTransforms.Add(new UnityTransform(t));
  727. t = t.parent;
  728. } while (t != null && t != t.root);
  729. }
  730. //add the root
  731. unityTransforms.Add(new UnityTransform(t.root));
  732. //position at identity
  733. for (int k = 0; k < unityTransforms.Count; k++)
  734. {
  735. unityTransforms[k].t.localPosition = Vector3.zero;
  736. unityTransforms[k].t.localRotation = Quaternion.identity;
  737. unityTransforms[k].t.localScale = Vector3.one;
  738. }
  739. //bake the mesh
  740. MB3_MeshCombinerSingle mc = (MB3_MeshCombinerSingle)mb.meshCombiner;
  741. if (!MB3_BakeInPlace.BakeOneMesh(mc, m, r.gameObject))
  742. {
  743. return false;
  744. }
  745. //replace the mesh
  746. if (r is MeshRenderer)
  747. {
  748. MeshFilter mf = r.gameObject.GetComponent<MeshFilter>();
  749. mf.sharedMesh = m;
  750. }
  751. else
  752. { //skinned mesh
  753. SkinnedMeshRenderer smr = r.gameObject.GetComponent<SkinnedMeshRenderer>();
  754. smr.sharedMesh = m;
  755. smr.bones = ((SkinnedMeshRenderer)mc.targetRenderer).bones;
  756. }
  757. if (mc.targetRenderer != null)
  758. {
  759. SetMaterials(mc.targetRenderer.sharedMaterials, r);
  760. }
  761. //restore the transforms
  762. for (int k = 0; k < unityTransforms.Count; k++)
  763. {
  764. unityTransforms[k].t.localPosition = unityTransforms[k].p;
  765. unityTransforms[k].t.localRotation = unityTransforms[k].q;
  766. unityTransforms[k].t.localScale = unityTransforms[k].s;
  767. }
  768. mc.SetMesh(null);
  769. return true;
  770. }
  771. private static void SetMaterials(Material[] sharedMaterials, Renderer r)
  772. {
  773. //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.
  774. //If we have just baked then materials on the target renderer will be correct wheras materials on the textureBakeResult may not be correct.
  775. Material[] sharedMats = new Material[sharedMaterials.Length];
  776. for (int i = 0; i < sharedMats.Length; i++)
  777. {
  778. sharedMats[i] = sharedMaterials[i];
  779. }
  780. if (r is SkinnedMeshRenderer)
  781. {
  782. r.sharedMaterial = null;
  783. r.sharedMaterials = sharedMats;
  784. }
  785. else
  786. {
  787. r.sharedMaterial = null;
  788. r.sharedMaterials = sharedMats;
  789. }
  790. }
  791. private static bool IsGoodToBake(Renderer r, MB2_TextureBakeResults tbr)
  792. {
  793. if (r == null) return false;
  794. if (!(r is MeshRenderer) && !(r is SkinnedMeshRenderer))
  795. {
  796. return false;
  797. }
  798. Material[] mats = r.sharedMaterials;
  799. for (int i = 0; i < mats.Length; i++)
  800. {
  801. if (!tbr.ContainsMaterial(mats[i]))
  802. {
  803. Debug.LogWarning("Mesh on " + r + " uses a material " + mats[i] + " that is not in the list of materials. This mesh will not be baked. The original mesh and material will be used in the result prefab.");
  804. return false;
  805. }
  806. }
  807. if (MB_Utility.GetMesh(r.gameObject) == null)
  808. {
  809. return false;
  810. }
  811. return true;
  812. }
  813. private static Transform FindCorrespondingTransform(Transform srcRoot, Transform srcChild,
  814. Transform targRoot)
  815. {
  816. if (srcRoot == srcChild) return targRoot;
  817. // Debug.Log ("start ============");
  818. //build the path to the root in the source prefab
  819. List<Transform> path_root2child = new List<Transform>();
  820. Transform t = srcChild;
  821. do
  822. {
  823. path_root2child.Insert(0, t);
  824. t = t.parent;
  825. } while (t != null && t != t.root && t != srcRoot);
  826. if (t == null)
  827. {
  828. Debug.LogError("scrChild was not child of srcRoot " + srcRoot + " " + srcChild);
  829. return null;
  830. }
  831. path_root2child.Insert(0, srcRoot);
  832. // Debug.Log ("path to root for " + srcChild + " " + path_root2child.Count);
  833. //try to find a matching path in the target prefab
  834. t = targRoot;
  835. for (int i = 1; i < path_root2child.Count; i++)
  836. {
  837. Transform tSrc = path_root2child[i - 1];
  838. //try to find child in same position with same name
  839. int srcIdx = TIndexOf(tSrc, path_root2child[i]);
  840. if (srcIdx < t.childCount && path_root2child[i].name.Equals(t.GetChild(srcIdx).name))
  841. {
  842. t = t.GetChild(srcIdx);
  843. // Debug.Log ("found child in same position with same name " + t);
  844. continue;
  845. }
  846. //try to find child with same name
  847. for (int j = 0; j < t.childCount; j++)
  848. {
  849. if (t.GetChild(j).name.Equals(path_root2child[i].name))
  850. {
  851. t = t.GetChild(j);
  852. // Debug.Log ("found child with same name " + t);
  853. continue;
  854. }
  855. }
  856. t = null;
  857. break;
  858. }
  859. // Debug.Log ("end =============== " + t);
  860. return t;
  861. }
  862. private static int TIndexOf(Transform p, Transform c)
  863. {
  864. for (int i = 0; i < p.childCount; i++)
  865. {
  866. if (c == p.GetChild(i))
  867. {
  868. return i;
  869. }
  870. }
  871. return -1;
  872. }
  873. public void CreateEmptyOutputPrefabs()
  874. {
  875. if (outputFolder.stringValue == null)
  876. {
  877. Debug.LogError("Output folder must be set");
  878. return;
  879. }
  880. if (outputFolder.stringValue.StartsWith(Application.dataPath))
  881. {
  882. string relativePath = "Assets" + outputFolder.stringValue.Substring(Application.dataPath.Length);
  883. string gid = AssetDatabase.AssetPathToGUID(relativePath);
  884. if (gid == null)
  885. {
  886. Debug.LogError("Output folder must be a folder in the Unity project Asset folder");
  887. return;
  888. }
  889. }
  890. else
  891. {
  892. Debug.LogError("Output folder must be a folder in the Unity project Asset folder");
  893. return;
  894. }
  895. int numCreated = 0;
  896. int numSkippedSrcNull = 0;
  897. int numSkippedAlreadyExisted = 0;
  898. MB3_BatchPrefabBaker prefabBaker = (MB3_BatchPrefabBaker)target;
  899. for (int i = 0; i < prefabBaker.prefabRows.Length; i++)
  900. {
  901. if (prefabBaker.prefabRows[i].sourcePrefab != null)
  902. {
  903. if (prefabBaker.prefabRows[i].resultPrefab == null)
  904. {
  905. string outName = outputFolder.stringValue + "/" + prefabBaker.prefabRows[i].sourcePrefab.name + ".prefab";
  906. outName = outName.Replace(Application.dataPath, "");
  907. outName = "Assets" + outName;
  908. GameObject go = new GameObject(prefabBaker.prefabRows[i].sourcePrefab.name);
  909. prefabBaker.prefabRows[i].resultPrefab = PrefabUtility.CreatePrefab(outName, go);
  910. DestroyImmediate(go);
  911. numCreated++;
  912. }
  913. else
  914. {
  915. numSkippedAlreadyExisted++;
  916. }
  917. }
  918. else
  919. {
  920. numSkippedSrcNull++;
  921. }
  922. }
  923. Debug.Log(String.Format("Created {0} prefabs. Skipped {1} because source prefab was null. Skipped {2} because the result prefab was already assigned", numCreated, numSkippedSrcNull, numSkippedAlreadyExisted));
  924. }
  925. }