Beast Helper
From Unify Community Wiki
Author: Rubén Vallejo
Description
This wizard allows to save the state of your Beast Lightmaps (Tiling/Offset, GameObjects lightmap index etc), and restore it later.
You can merge several lightmapping sessions automatically with this.
Dual lightmaps are not supported for now.
Official unity forums thread: [1]
Usage
To install, just place the script in Assets/Editor folder. To use, just Go to Window > Beast Helper.
C# - BeastHelper.cs
using UnityEngine; using UnityEditor; using System; using System.IO; using System.Text; using System.Runtime.InteropServices; public class BeastHelper : EditorWindow { int ArraySize = 10; Vector2 scrollpos1; static GameObject[] staticobjects; // Static Gameobjects Array static object[,,] arrayobjects = new object[255,50,3]; // array relative to baketex array, contains gamobject(s) and tiling info for each lightmap Texture2D[] baketex = new Texture2D[255]; //array of lightmaps //--------------------------------------------- // Add menu named "My Window" to the Window menu [MenuItem("Window/Beast Helper")] static void Init() { // Get existing open window or if none, make a new one: BeastHelper window = (BeastHelper)EditorWindow.GetWindow(typeof(BeastHelper)); window.autoRepaintOnSceneChange = true; window.title = "Beast Helper"; window.Show(); //-------------- Get Lightmapped geometry... ---------------- GameObject[] gameobjects = (GameObject[])Resources.FindObjectsOfTypeAll(typeof(GameObject)); int i1 = 0; foreach (GameObject g in gameobjects) { if (g.isStatic) { i1++; } } staticobjects = new GameObject[i1]; int i2 = 0; foreach (GameObject g in gameobjects) { if (g.isStatic) { staticobjects.SetValue((GameObject)g, i2); i2++; } } Debug.Log("Static GameObjects count = " + i1); for (int i = 0; i < arrayobjects.GetLength(0); i++) { for (int i3 = 0; i3 < arrayobjects.GetLength(1); i3++) { arrayobjects[i, i3, 0] = null; arrayobjects[i, i3, 1] = (Vector4)new Vector4(); arrayobjects[i, i3, 2] = (bool)false; } } //---------------------------------------------------------- } void OnGUI() { GUILayout.Label("Stored Lightmaps", EditorStyles.boldLabel); ArraySize = EditorGUILayout.IntField("Array Size(" + ArraySize.ToString() + ")", ArraySize); GUILayoutOption[] layout1 = new GUILayoutOption[2]; layout1[0] = (GUILayoutOption)GUILayout.Height(75); layout1[1] = (GUILayoutOption)GUILayout.Width(75); scrollpos1 = EditorGUILayout.BeginScrollView(scrollpos1); for (int i = 0; i < ArraySize; i++) { // Debug.Log("Loop1 i = " + i); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(i.ToString(),"", GUILayout.Width(20)); baketex[i] = (Texture2D)EditorGUILayout.ObjectField((Texture2D)baketex.GetValue(i), typeof(Texture2D), layout1); //----------- Each GameObject whit [i] lightmap index go here ----------- for (int i2 = 0; i2 < arrayobjects.GetLength(1); i2++) { GUILayout.BeginVertical(GUILayout.Width(300)); EditorGUILayout.LabelField("GameObject: " + i2.ToString(), "", GUILayout.Width(300)); arrayobjects[i, i2, 0] = (GameObject)EditorGUILayout.ObjectField((GameObject)arrayobjects[i, i2, 0], typeof(GameObject), GUILayout.Width(300)); arrayobjects[i, i2, 1] = (Vector4)EditorGUILayout.Vector4Field("Tiling (X-Y) / Offset (X-Y)", (Vector4)arrayobjects[i, i2, 1], GUILayout.Width(300)); arrayobjects[i, i2, 2] = (bool)EditorGUILayout.Toggle("Use: ",(bool)arrayobjects[i, i2, 2], GUILayout.Width(300)); GUILayout.EndVertical(); } //------------------------------------------------------------------------- EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); Repaint(); EditorGUILayout.BeginHorizontal(); //-------------- Buttons ------------------- if(GUILayout.Button("Read current maps")) { if (LightmapSettings.lightmaps.Length > ArraySize) ArraySize = LightmapSettings.lightmaps.Length; Repaint(); for (int i = 0; i < LightmapSettings.lightmaps.Length; i++) { // Debug.Log("Loop2 i = " + i); baketex.SetValue(LightmapSettings.lightmaps[i].lightmapFar,i); //------------- Get objects with same lightmap index --------- if (baketex.GetValue(i) != null) { var i2 = 0; foreach (GameObject r in staticobjects) { if (r.renderer) { if (r.renderer.lightmapIndex == i) { arrayobjects[i, i2, 0] = (GameObject)r; // The real GameObject arrayobjects[i, i2, 1] = (Vector4)r.renderer.lightmapTilingOffset; // Tiling Lightmap properties arrayobjects[i, i2, 2] = (bool)true; // use it?? i2++; } } } } //------------------------------------------------------------ } Repaint(); } if (GUILayout.Button("Load Maps info from Disk")) { string path = EditorUtility.OpenFilePanel("Select beast maps info file", Application.dataPath, "ini"); if (path.Length > 0) { //-------------- Loading .ini file ------------- cIni inifile = new cIni(path); string mapscountstr = inifile.ReadValue("MAPSCOUNT", "COUNT"); int mapscount = 0; if (mapscountstr.Length > 0) { mapscount = int.Parse(mapscountstr); } if (ArraySize < mapscount) { ArraySize = mapscount; Repaint(); } int arrayofsset = 0; bool cancel = false; switch (EditorUtility.DisplayDialogComplex("Merge with current maps?", "If you choose merge, the loaded maps will be added after the current ones", "Merge", "Replace", "Cancel")) { case 0: { for (int i = 0; i < ArraySize; i++) { if (baketex[i] != null) arrayofsset = i; } if (arrayofsset > 0) arrayofsset++; if ((arrayofsset + mapscount) > ArraySize) { ArraySize = arrayofsset + mapscount; } } break; case 1: { for (int i = 0; i < ArraySize; i++) { // Debug.Log("Loop2 i = " + i); baketex.SetValue(null, i); for (int i2 = 0; i2 < arrayobjects.GetLength(1); i2++) { arrayobjects[i, i2, 0] = null; arrayobjects[i, i2, 1] = (Vector4)new Vector4(); arrayobjects[i, i2, 2] = (bool)false; } } Repaint(); } break; case 2: { cancel = true; } break; } if (cancel == false) { int counter1 = 0; for (int i = arrayofsset; i < ArraySize; i++) { string texpath = inifile.ReadValue("MAPS", counter1.ToString()); if (texpath.Length > 0) { baketex.SetValue((Texture2D)AssetDatabase.LoadAssetAtPath(texpath, typeof(Texture2D)), i); } counter1++; } for (int i2 = arrayofsset; i2 < ArraySize; i2++) { int counter2 = 0; for (int i3 = 0; i3 < arrayobjects.GetLength(1); i3++) { string gpath = inifile.ReadValue("MAP" + counter2.ToString(), "GAMEOBJECT" + i3.ToString()); string gtiling = inifile.ReadValue("MAP" + counter2.ToString(), "TILING" + i3.ToString()); string guse = inifile.ReadValue("MAP" + counter2.ToString(), "USE" + i3.ToString()); if (gpath.Length > 0) { arrayobjects[i2, i3, 0] = (GameObject)GameObject.Find(gpath); arrayobjects[i2, i3, 1] = (Vector4)vector4fromstring(gtiling); arrayobjects[i2, i3, 2] = (bool)Convert.ToBoolean(guse); } counter2++; } } } //----------------------------------------------- } } if(GUILayout.Button("Store Maps info to Disk")) { EditorUtility.DisplayDialog("Important!", "If you want to start a new bake operation whitout lose current maps, move them to another folder in the project view! Beast will remove old maps otherwise!", "Ok"); string path = EditorUtility.SaveFilePanel("Save beast maps info to file", Application.dataPath, "beast_info", "ini"); if (path.Length > 0) { //-------------- Creating .ini file ------------- if (File.Exists(path)) File.Delete(path); // Delete old ini file, otherwise files content will be merged. cIni inifile = new cIni(path); inifile.WriteValue("MAPSCOUNT", "COUNT", ArraySize.ToString()); for (int i = 0; i < ArraySize; i++) { inifile.WriteValue("MAPS",i.ToString(), AssetDatabase.GetAssetPath(baketex[i])); } for (int i2 = 0; i2 < ArraySize; i2++) { for (int i3 = 0; i3 < arrayobjects.GetLength(1); i3++) { inifile.WriteValue("MAP" + i2.ToString(), "GAMEOBJECT" + i3.ToString(), GetHierarchy((GameObject)arrayobjects[i2,i3,0])); Vector4 tiling = (Vector4)arrayobjects[i2, i3, 1]; inifile.WriteValue("MAP" + i2.ToString(), "TILING" + i3.ToString(), "(" + tiling.x.ToString() + "," + tiling.y.ToString() + "," + tiling.w.ToString() + "," + tiling.z.ToString() + ")"); inifile.WriteValue("MAP" + i2.ToString(), "USE" + i3.ToString(), arrayobjects[i2, i3, 2].ToString().ToLower()); } } //----------------------------------------------- } } if(GUILayout.Button("Set actual Maps in Beast")) { bool cancel = false; if(!cancel) { if (EditorUtility.DisplayDialog("Do you want to apply these maps??", "If you have unsaved maps in beast, you will lose them", "Apply", "Cancel")) { LightmapData[] maps = LightmapSettings.lightmaps; Array.Resize(ref maps, ArraySize); for (int c2 = 0; c2 < maps.Length; c2++) { maps.SetValue(new LightmapData(), c2); } for (int c1 = 0; c1 < ArraySize; c1++) { maps[c1].lightmapFar = baketex[c1]; } LightmapSettings.lightmaps = maps; for (int i2 = 0; i2 < ArraySize; i2++) { for (int i3 = 0; i3 < arrayobjects.GetLength(1); i3++) { bool use = (bool)arrayobjects[i2, i3, 2]; if (use == true) { GameObject g = (GameObject)arrayobjects[i2, i3, 0]; g.renderer.lightmapIndex = i3; g.renderer.lightmapTilingOffset = (Vector4)arrayobjects[i2, i3, 1]; } } } } } } //------------------------------------------ EditorGUILayout.EndHorizontal(); //--------------- Down Buttons ------------- EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Clear All")) { for (int i = 0; i < ArraySize; i++) { // Debug.Log("Loop2 i = " + i); baketex.SetValue(null, i); for (int i2 = 0; i2 < arrayobjects.GetLength(1); i2++) { arrayobjects[i, i2, 0] = null; arrayobjects[i, i2, 1] = (Vector4)new Vector4(); arrayobjects[i, i2, 2] = (bool)false; } } Repaint(); } //if (GUILayout.Button("Reload Gameobject List")) //{ //} if (GUILayout.Button("Close")) { Close(); } EditorGUILayout.EndHorizontal(); if (GUI.changed) Repaint(); } // Class for handling .ini files... public class cIni { [DllImport("kernel32", SetLastError = true)] private static extern int WritePrivateProfileString(string pSection, string pKey, string pValue, string pFile); [DllImport("kernel32", SetLastError = true)] private static extern int WritePrivateProfileStruct(string pSection, string pKey, string pValue, int pValueLen, string pFile); [DllImport("kernel32", SetLastError = true)] private static extern int GetPrivateProfileString(string pSection, string pKey, string pDefault, byte[] prReturn, int pBufferLen, string pFile); [DllImport("kernel32", SetLastError = true)] private static extern int GetPrivateProfileStruct(string pSection, string pKey, byte[] prReturn, int pBufferLen, string pFile); private string ls_IniFilename; private int li_BufferLen = 256; /// <summary> /// cINI Constructor /// </summary> public cIni(string pIniFilename) { ls_IniFilename = pIniFilename; } /// <summary> /// INI filename (If no path is specifyed the function will look with-in the windows directory for the file) /// </summary> public string IniFile { get { return (ls_IniFilename); } set { ls_IniFilename = value; } } /// <summary> /// Max return length when reading data (Max: 32767) /// </summary> public int BufferLen { get { return li_BufferLen; } set { if (value > 32767) { li_BufferLen = 32767; } else if (value < 1) { li_BufferLen = 1; } else { li_BufferLen = value; } } } /// <summary> /// Read value from INI File /// </summary> public string ReadValue(string pSection, string pKey, string pDefault) { return (z_GetString(pSection, pKey, pDefault)); } /// <summary> /// Read value from INI File, default = "" /// </summary> public string ReadValue(string pSection, string pKey) { return (z_GetString(pSection, pKey, "")); } /// <summary> /// Write value to INI File /// </summary> public void WriteValue(string pSection, string pKey, string pValue) { WritePrivateProfileString(pSection, pKey, pValue, this.ls_IniFilename); } /// <summary> /// Remove value from INI File /// </summary> public void RemoveValue(string pSection, string pKey) { WritePrivateProfileString(pSection, pKey, null, this.ls_IniFilename); } /// <summary> /// Read values in a section from INI File /// </summary> public void ReadValues(string pSection, ref Array pValues) { pValues = z_GetString(pSection, null, null).Split((char)0); } /// <summary> /// Read sections from INI File /// </summary> public void ReadSections(ref Array pSections) { pSections = z_GetString(null, null, null).Split((char)0); } /// <summary> /// Remove section from INI File /// </summary> public void RemoveSection(string pSection) { WritePrivateProfileString(pSection, null, null, this.ls_IniFilename); } /// <summary> /// Call GetPrivateProfileString / GetPrivateProfileStruct API /// </summary> private string z_GetString(string pSection, string pKey, string pDefault) { string sRet = pDefault; byte[] bRet = new byte[li_BufferLen]; int i = GetPrivateProfileString(pSection, pKey, pDefault, bRet, li_BufferLen, ls_IniFilename); sRet = System.Text.Encoding.GetEncoding(1252).GetString(bRet, 0, i).TrimEnd((char)0); return (sRet); } } //Return the full hierarchy string of a gameObject, ready for use in GameObject.Find method later... string GetHierarchy(GameObject g) { if (!g || g == null) { return ""; } string hierarchy = null; string gname = g.name; Transform parent = g.transform.parent; while (parent) { hierarchy += "/" + parent.gameObject.name; parent = parent.parent; } string inverse = gname + hierarchy; string[] arrayh = (string[])inverse.Split(new string[] { "/" }, StringSplitOptions.None); Array.Reverse(arrayh); return "/" + string.Join("/", arrayh); } Vector4 vector4fromstring(string vectorstring) { if (vectorstring == null || vectorstring.Length == 0) return new Vector4(); vectorstring = vectorstring.Substring(1, vectorstring.Length - 2); string[] arrayvector = (string[])vectorstring.Split(new string[] { "," }, StringSplitOptions.None); Vector4 vectorfinal = new Vector4(float.Parse(arrayvector[0]), float.Parse(arrayvector[1]), float.Parse(arrayvector[2]), float.Parse(arrayvector[3])); return vectorfinal; } }