DeltaVR/Assets/Editor/x64/Bakery/scripts/ftBuildGraphics.cs
2020-12-03 20:09:51 +02:00

6641 lines
279 KiB
C#

#if UNITY_EDITOR
// Disable 'obsolete' warnings
#pragma warning disable 0618
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;
using System.Reflection;
public class ftBuildGraphics : ScriptableWizard
{
const int UVGBFLAG_NORMAL = 1;
const int UVGBFLAG_FACENORMAL = 2;
const int UVGBFLAG_ALBEDO = 4;
const int UVGBFLAG_EMISSIVE = 8;
const int UVGBFLAG_POS = 16;
const int UVGBFLAG_SMOOTHPOS = 32;
const int UVGBFLAG_TANGENT = 64;
const int UVGBFLAG_TERRAIN = 128;
const int UVGBFLAG_RESERVED = 256;
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void InitShaders();
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void LoadScene(string path);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
private static extern void SetAlbedos(int count, IntPtr[] tex);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
private static extern int CopyAlbedos();
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void FreeAlbedoCopies();
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
private static extern void SetAlphas(int count, IntPtr[] tex, float[] alphaRefs, int[] alphaChannels, int numLODs, bool flip);
[DllImport ("unityLib11")]
private static extern void SaveTexture(IntPtr tex, string path);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void SaveSky(IntPtr tex, float rx, float ry, float rz, float ux, float uy, float uz, float fx, float fy, float fz, string path, bool isLinear, bool doubleLDR);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void SaveCookie(IntPtr tex, string path);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern int ftRenderUVGBuffer();
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void SetUVGBFlags(int flags);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void SetFixPos(bool enabled);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern void SetCompression(bool enabled);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern int ftGenerateAlphaBuffer();
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern int SaveGBufferMap(IntPtr tex, string path, bool compressed);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern int SaveGBufferMapFromRAM(byte[] tex, int size, string path, bool compressed);
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern int GetABGErrorCode();
[DllImport ("frender", CallingConvention=CallingConvention.Cdecl)]
public static extern int GetUVGBErrorCode();
[DllImport ("uvrepack", CallingConvention=CallingConvention.Cdecl)]
public static extern int uvrLoad(float[] inputUVs, int numVerts, int[] inputIndices, int numIndices);
[DllImport ("uvrepack", CallingConvention=CallingConvention.Cdecl)]
public static extern int uvrRepack(float padding, int resolution);
[DllImport ("uvrepack", CallingConvention=CallingConvention.Cdecl)]
public static extern int uvrUnload();
static int voffset, soffset, ioffset;
static public string scenePath = "";
static BufferedBinaryWriterFloat fvbfull, fvbtrace, fvbtraceTex, fvbtraceUV0;
static BufferedBinaryWriterInt fib;
static BinaryWriter fscene, fmesh, flmid, fseamfix, fsurf, fmatid, fmatide, fmatidh, fmatideb, falphaid, fib32, fhmaps;
static BinaryWriter[] fib32lod;
static BinaryWriter[] falphaidlod;
public static ftLightmapsStorage.ImplicitLightmapData tempStorage = new ftLightmapsStorage.ImplicitLightmapData();
public static float texelsPerUnit = 20;
public static int minAutoResolution = 16;
public static int maxAutoResolution = 4096;
public static bool mustBePOT = true;
public static bool autoAtlas = true;
public static bool unwrapUVs = true;
public static bool forceDisableUnwrapUVs = false;
public static bool exportShaderColors = true;
public static int atlasPaddingPixels = 3;
public static bool atlasCountPriority = false;
public static bool splitByScene = false;
public static bool uvPaddingMax = false;
public static bool exportTerrainAsHeightmap = true;
public static bool exportTerrainTrees = false;
public static bool uvgbHeightmap = true;
public static bool texelsPerUnitPerMap = false;
public static float mainLightmapScale = 1;
public static float maskLightmapScale = 1;
public static float dirLightmapScale = 1;
const float atlasScaleUpValue = 1.01f;
const int atlasMaxTries = 100;
const float alphaInstanceThreshold = 5.0f / 255.0f;
const bool flipAlpha = true;
public static string overwriteExtensionCheck = ".hdr";
public static bool overwriteWarning = false;
public static bool overwriteWarningSelectedOnly = false;
public static bool memoryWarning = false;
public static bool modifyLightmapStorage = true;
public static bool validateLightmapStorageImmutability = true;
public static bool sceneNeedsToBeRebuilt = false;
//public static int unityVersionMajor = 0;
//public static int unityVersionMinor = 0;
static int areaLightCounter = -2;
public static int sceneLodsUsed = 0;
static GameObject lmgroupHolder;
static BakeryLightmapGroup lightProbeLMGroup = null;
static BakeryLightmapGroup volumeLMGroup = null;
static List<GameObject> terrainObjectList;
static List<Terrain> terrainObjectToActual;
static List<Texture> terrainObjectToHeightMap;
static IntPtr[] terrainObjectToHeightMapPtr;
static List<float> terrainObjectToBounds;
static List<int> terrainObjectToLMID;
static List<float> terrainObjectToBoundsUV;
static List<int> terrainObjectToFlags;
static List<List<float[]>> terrainObjectToHeightMips;
//static List<List<Vector3[]>> terrainObjectToNormalMips;
//static List<Vector3[]> terrainObjectToNormalMip0;
static List<GameObject> temporaryAreaLightMeshList;
static List<BakeryLightMesh> temporaryAreaLightMeshList2;
static List<GameObject> treeObjectList;
static Dictionary<GameObject,int> cmp_objToLodLevel;
static Dictionary<GameObject, float> cmp_holderObjArea;
public static List<float> vbtraceTexPosNormalArray; // global vbTraceTex.bin positions/normals
public static List<float> vbtraceTexUVArray; // global vbTraceTex.bin UVs
public static float[] vbtraceTexUVArrayLOD; // global vbTraceTex.bin LOD UVs
public static List<Renderer> atlasOnlyObj;
public static List<Vector4> atlasOnlyScaleOffset;
public static List<int> atlasOnlySize;
public static List<int> atlasOnlyID;
public static ftGlobalStorage.AtlasPacker atlasPacker = ftGlobalStorage.AtlasPacker.xatlas;
public static bool forceAllAreaLightsSelfshadow = false;
public static bool postPacking = true;
public static bool holeFilling = false;
static int floatOverWarnCount = 0;
const int maxFloatOverWarn = 10;
static ftGlobalStorage gstorage;
static public void DebugLogError(string text)
{
ftRenderLightmap.DebugLogError(text);
}
class AtlasNode
{
public AtlasNode child0, child1;
public Rect rc;
public GameObject obj;
bool leaf = true;
public AtlasNode Insert(GameObject o, Rect rect)
{
if (!leaf)
{
var newNode = child0.Insert(o, rect);
if (newNode != null) return newNode;
return child1.Insert(o, rect);
}
else
{
if (obj != null) return null;
var fits = (rect.width <= rc.width && rect.height <= rc.height);
if (!fits) return null;
var fitsExactly = (rect.width == rc.width && rect.height == rc.height);
if (fitsExactly)
{
obj = o;
return this;
}
child0 = new AtlasNode();
child1 = new AtlasNode();
var dw = rc.width - rect.width;
var dh = rc.height - rect.height;
if (dw > dh)
{
child0.rc = new Rect(rc.x, rc.y, rect.width, rc.height);
child1.rc = new Rect(rc.x + rect.width, rc.y, rc.width - rect.width, rc.height);
}
else
{
child0.rc = new Rect(rc.x, rc.y, rc.width, rect.height);
child1.rc = new Rect(rc.x, rc.y + rect.height, rc.width, rc.height - rect.height);
}
leaf = false;
return child0.Insert(o, rect);
}
}
public void GetMax(ref float maxx, ref float maxy)
{
if (obj != null)
{
if ((rc.x + rc.width) > maxx) maxx = rc.x + rc.width;
if ((rc.y + rc.height) > maxy) maxy = rc.y + rc.height;
}
if (child0 != null) child0.GetMax(ref maxx, ref maxy);
if (child1 != null) child1.GetMax(ref maxx, ref maxy);
}
public void Transform(float offsetx, float offsety, float scalex, float scaley)
{
rc.x *= scalex;
rc.y *= scaley;
rc.x += offsetx;
rc.y += offsety;
rc.width *= scalex;
rc.height *= scaley;
if (child0 != null) child0.Transform(offsetx, offsety, scalex, scaley);
if (child1 != null) child1.Transform(offsetx, offsety, scalex, scaley);
}
};
static ftBuildGraphics()
{
//ftRenderLightmap.PatchPath();
//var unityVer = Application.unityVersion.Split('.');
//unityVersionMajor = Int32.Parse(unityVer[0]);
//unityVersionMinor = Int32.Parse(unityVer[1]);
}
static void exportVBPos(BinaryWriter f, Transform t, Mesh m, Vector3[] vertices)
{
for(int i=0;i<vertices.Length;i++)
{
Vector3 pos = vertices[i];//t.(vertices[i]);
f.Write(pos.x);
f.Write(pos.y);
f.Write(pos.z);
}
}
static void exportVBTrace(BufferedBinaryWriterFloat f, Mesh m, Vector3[] vertices, Vector3[] normals)
{
for(int i=0;i<vertices.Length;i++)
{
Vector3 pos = vertices[i];//t.(vertices[i]);
f.Write(pos.x);
f.Write(pos.y);
f.Write(pos.z);
Vector3 normal = normals[i];//t.TransformDirection(normals[i]);
f.Write(normal.x);
f.Write(normal.y);
f.Write(normal.z);
}
}
static BakeryLightmapGroup GetLMGroupFromObjectExplicit(GameObject obj, ExportSceneData data)
{
lmgroupHolder = null;
var lmgroupSelector = obj.GetComponent<BakeryLightmapGroupSelector>(); // if object has explicit lmgroup
if (lmgroupSelector == null)
{
// if parents have explicit lmgroup
var t2 = obj.transform.parent;
while(lmgroupSelector == null && t2 != null)
{
lmgroupSelector = t2.GetComponent<BakeryLightmapGroupSelector>();
t2 = t2.parent;
}
}
BakeryLightmapGroup lmgroup = null;
if (lmgroupSelector != null)
{
lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup;
lmgroupHolder = lmgroupSelector.gameObject;
var so = new SerializedObject(obj.GetComponent<Renderer>());
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
if (scaleInLm == 0.0f) lmgroup = data.autoVertexGroup;
//null; // ignore lightmaps when scaleInLightmap == 0
}
return lmgroup;
}
static BakeryLightmapGroup GetLMGroupFromObject(GameObject obj, ExportSceneData data)
{
UnityEngine.Object lmgroupObj = null;
BakeryLightmapGroup lmgroup = null;
lmgroupHolder = null;
var lmgroupSelector = obj.GetComponent<BakeryLightmapGroupSelector>(); // if object has explicit lmgroup
tempStorage.implicitGroupMap.TryGetValue(obj, out lmgroupObj); // or implicit
lmgroup = (BakeryLightmapGroup)lmgroupObj;
lmgroupHolder = obj;
if (lmgroupSelector == null && lmgroup == null)
{
// if parents have explicit/implicit lmgroup
var t2 = obj.transform.parent;
while(lmgroupSelector == null && lmgroup == null && t2 != null)
{
lmgroupSelector = t2.GetComponent<BakeryLightmapGroupSelector>();
tempStorage.implicitGroupMap.TryGetValue(t2.gameObject, out lmgroupObj);
lmgroup = (BakeryLightmapGroup)lmgroupObj;
lmgroupHolder = t2.gameObject;
t2 = t2.parent;
}
}
if (lmgroupSelector != null)
{
lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup;
}
if (lmgroup != null)
{
var r = obj.GetComponent<Renderer>();
if (r)
{
var so = new SerializedObject(r);
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
if (scaleInLm == 0.0f) lmgroup = data.autoVertexGroup;
// null; // ignore lightmaps when scaleInLightmap == 0
}
}
else
{
lmgroupHolder = null;
}
return lmgroup;
}
// use by ftRenderLightmap
public static BakeryLightmapGroup GetLMGroupFromObject(GameObject obj)
{
UnityEngine.Object lmgroupObj = null;
BakeryLightmapGroup lmgroup = null;
lmgroupHolder = null;
var lmgroupSelector = obj.GetComponent<BakeryLightmapGroupSelector>(); // if object has explicit lmgroup
tempStorage.implicitGroupMap.TryGetValue(obj, out lmgroupObj); // or implicit
lmgroup = (BakeryLightmapGroup)lmgroupObj;
lmgroupHolder = obj;
if (lmgroupSelector == null && lmgroup == null)
{
// if parents have explicit/implicit lmgroup
var t2 = obj.transform.parent;
while(lmgroupSelector == null && lmgroup == null && t2 != null)
{
lmgroupSelector = t2.GetComponent<BakeryLightmapGroupSelector>();
tempStorage.implicitGroupMap.TryGetValue(t2.gameObject, out lmgroupObj);
lmgroup = (BakeryLightmapGroup)lmgroupObj;
lmgroupHolder = t2.gameObject;
t2 = t2.parent;
}
}
if (lmgroupSelector != null)
{
lmgroup = lmgroupSelector.lmgroupAsset as BakeryLightmapGroup;
}
if (lmgroup != null)
{
var r = obj.GetComponent<Renderer>();
if (r)
{
var so = new SerializedObject(r);
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
if (scaleInLm == 0.0f) lmgroup = null;
// null; // ignore lightmaps when scaleInLightmap == 0
}
}
else
{
lmgroupHolder = null;
}
return lmgroup;
}
public static void exportVBTraceTexAttribs(List<float> arrPosNormal, List<float> arrUV,
Vector3[] vertices, Vector3[] normals, Vector2[] uv2, int lmid, bool vertexBake, GameObject obj)
{
for(int i=0;i<vertices.Length;i++)
{
Vector3 pos = vertices[i];//t.(vertices[i]);
arrPosNormal.Add(pos.x);
arrPosNormal.Add(pos.y);
arrPosNormal.Add(pos.z);
Vector3 normal = normals[i];//t.TransformDirection(normals[i]);
arrPosNormal.Add(normal.x);
arrPosNormal.Add(normal.y);
arrPosNormal.Add(normal.z);
float u = 0;
float v = 0;
if (lmid < 0)
{
//u = lmid * 10;
if (uv2.Length>0)
{
u = Mathf.Clamp(uv2[i].x, 0, 0.99999f);
v = Mathf.Clamp(1.0f - uv2[i].y, 0, 0.99999f);
}
}
else
{
if (uv2.Length>0 && !vertexBake)
{
u = Mathf.Clamp(uv2[i].x, 0, 0.99999f);
v = Mathf.Clamp(uv2[i].y, 0, 0.99999f);
}
else if (vertexBake)
{
u = uv2[i].x;
v = uv2[i].y - 1.0f;
}
}
float origU = u;
if (lmid >= 0)
{
u += lmid * 10;
if ((int)u > lmid*10)
{
// attempt fixing float overflow
u = (lmid*10+1) - (lmid*10+1)*0.0000002f;
if ((int)u > lmid*10)
{
if (floatOverWarnCount < maxFloatOverWarn)
{
Debug.LogError("Float overflow for " + obj.name + " (U: " + origU + ", LMID: " + lmid + ")");
floatOverWarnCount++;
}
}
}
}
else
{
u = lmid * 10 - u;
if ((int)u != lmid*10)
{
u = -u;
lmid = -lmid;
u = (lmid*10+1) - (lmid*10+1)*0.0000002f;
if ((int)u > lmid*10)
{
if (floatOverWarnCount < maxFloatOverWarn)
{
Debug.LogError("Float overflow for " + obj.name + " (U: " + origU + ", LMID: " + lmid + ")");
floatOverWarnCount++;
}
}
u = -u;
lmid = -lmid;
}
}
arrUV.Add(u);
arrUV.Add(v);
}
}
static void exportVBTraceUV0(BufferedBinaryWriterFloat f, Vector2[] uvs, int vertCount)
{
if (uvs.Length == 0)
{
for(int i=0;i<vertCount;i++)
{
f.Write(0.0f);
f.Write(0.0f);
}
}
else
{
for(int i=0;i<vertCount;i++)
{
f.Write(uvs[i].x);
f.Write(uvs[i].y);
}
}
}
static void exportVBBasic(BinaryWriter f, Transform t, Mesh m, Vector3[] vertices, Vector3[] normals, Vector2[] uv2)
{
for(int i=0;i<vertices.Length;i++)
{
Vector3 pos = vertices[i];//t.(vertices[i]);
f.Write(pos.x);
f.Write(pos.y);
f.Write(pos.z);
Vector3 normal = normals[i];//t.TransformDirection(normals[i]);
f.Write(normal.x);
f.Write(normal.y);
f.Write(normal.z);
if (uv2.Length>0)
{
f.Write(uv2[i].x);
f.Write(uv2[i].y);
}
else
{
f.Write(0.0f);
f.Write(0.0f);
}
}
}
// Either I'm dumb, or it's impossible to make generics work with it (only worked in .NET 3.5)
class BufferedBinaryWriterFloat
{
[StructLayout(LayoutKind.Explicit)]
public class ReinterpretBuffer
{
[FieldOffset(0)]
public byte[] bytes;
[FieldOffset(0)]
public float[] elements;
}
BinaryWriter f;
ReinterpretBuffer buffer;
int bufferPtr;
int bufferSize;
int elementSize;
public BufferedBinaryWriterFloat(BinaryWriter b, int elemSize = 4, int buffSizeInFloats = 1024*1024)
{
f = b;
buffer = new ReinterpretBuffer();
buffer.bytes = new byte[buffSizeInFloats * elemSize];
bufferPtr = 0;
bufferSize = buffSizeInFloats;
elementSize = elemSize;
}
void Flush()
{
if (bufferPtr == 0) return;
f.Write(buffer.bytes, 0, bufferPtr * elementSize);
bufferPtr = 0;
}
public void Write(float x)
{
buffer.elements[bufferPtr] = x;
bufferPtr++;
if (bufferPtr == bufferSize) Flush();
}
public void Close()
{
Flush();
f.Close();
}
}
class BufferedBinaryWriterInt
{
[StructLayout(LayoutKind.Explicit)]
public class ReinterpretBuffer
{
[FieldOffset(0)]
public byte[] bytes;
[FieldOffset(0)]
public int[] elements;
}
BinaryWriter f;
ReinterpretBuffer buffer;
int bufferPtr;
int bufferSize;
int elementSize;
public BufferedBinaryWriterInt(BinaryWriter b, int elemSize = 4, int buffSizeInFloats = 1024*1024)
{
f = b;
buffer = new ReinterpretBuffer();
buffer.bytes = new byte[buffSizeInFloats * elemSize];
bufferPtr = 0;
bufferSize = buffSizeInFloats;
elementSize = elemSize;
}
void Flush()
{
if (bufferPtr == 0) return;
f.Write(buffer.bytes, 0, bufferPtr * elementSize);
bufferPtr = 0;
}
public void Write(int x)
{
buffer.elements[bufferPtr] = x;
bufferPtr++;
if (bufferPtr == bufferSize) Flush();
}
public void Close()
{
Flush();
f.Close();
}
}
static void exportVBFull(BufferedBinaryWriterFloat f, Vector3[] vertices, Vector3[] normals, Vector4[] tangents, Vector2[] uv, Vector2[] uv2)
{
bool hasUV = uv.Length > 0;
bool hasUV2 = uv2.Length > 0;
for(int i=0;i<vertices.Length;i++)
{
Vector3 pos = vertices[i];//t.(vertices[i]);
f.Write(pos.x);
f.Write(pos.y);
f.Write(pos.z);
Vector3 normal = normals[i];//t.TransformDirection(normals[i]);
f.Write(normal.x);
f.Write(normal.y);
f.Write(normal.z);
if (tangents == null)
{
f.Write(0.0f);
f.Write(0.0f);
f.Write(0.0f);
f.Write(0.0f);
}
else
{
Vector4 tangent = tangents[i];
f.Write(tangent.x);
f.Write(tangent.y);
f.Write(tangent.z);
f.Write(tangent.w);
}
if (hasUV)
{
f.Write(uv[i].x);
f.Write(uv[i].y);
}
else
{
f.Write(0.0f);
f.Write(0.0f);
}
if (hasUV2)
{
f.Write(uv2[i].x);
f.Write(uv2[i].y);
}
else
{
f.Write(0.0f);
f.Write(0.0f);
}
}
}
static int exportIB(BufferedBinaryWriterInt f, int[] indices, bool isFlipped, bool is32Bit, int offset, BinaryWriter falphaid, ushort alphaID)
{
//var indices = m.GetTriangles(i);
for(int j=0;j<indices.Length;j+=3)
{
if (!isFlipped)
{
if (is32Bit)
{
f.Write(indices[j] + offset);
f.Write(indices[j+1] + offset);
f.Write(indices[j+2] + offset);
}
else
{
f.Write(indices[j]);
f.Write(indices[j+1]);
f.Write(indices[j+2]);
}
}
else
{
if (is32Bit)
{
f.Write(indices[j+2] + offset);
f.Write(indices[j+1] + offset);
f.Write(indices[j] + offset);
}
else
{
f.Write(indices[j+2]);
f.Write(indices[j+1]);
f.Write(indices[j]);
}
}
if (falphaid!=null) falphaid.Write(alphaID);
}
return indices.Length;
}
// returns mesh area if requested
static void exportIB32(List<int> indicesOpaque, List<int> indicesTransparent, List<int> indicesLMID,
int[] indices, bool isFlipped, int offset, int indexOffsetLMID, BinaryWriter falphaid,
ushort alphaID)
{
//var indices = m.GetTriangles(i);
var indicesOut = alphaID == 0xFFFF ? indicesOpaque : indicesTransparent;
int indexA, indexB, indexC;
for(int j=0;j<indices.Length;j+=3)
{
if (!isFlipped)
{
indexA = indices[j];
indexB = indices[j + 1];
indexC = indices[j + 2];
}
else
{
indexA = indices[j + 2];
indexB = indices[j + 1];
indexC = indices[j];
}
indicesOut.Add(indexA + offset);
indicesOut.Add(indexB + offset);
indicesOut.Add(indexC + offset);
if (indicesLMID != null)
{
indicesLMID.Add(indexA + indexOffsetLMID);
indicesLMID.Add(indexB + indexOffsetLMID);
indicesLMID.Add(indexC + indexOffsetLMID);
}
if (alphaID != 0xFFFF) falphaid.Write(alphaID);
}
}
static void exportSurfs(BinaryWriter f, int[][] indices, int subMeshCount)// Mesh m)
{
int offset = ioffset;
for(int i=0;i<subMeshCount;i++) {
int size = indices[i].Length;//m.GetTriangles(i).Length;
f.Write(offset);
f.Write(size);
offset += size;// * 2;
}
soffset += subMeshCount;
}
static void exportMesh(BinaryWriter f, Mesh m)
{
f.Write(voffset);
f.Write(soffset);
f.Write((ushort)m.subMeshCount);
f.Write((ushort)0);
}
static int exportLMID(BinaryWriter f, GameObject obj, BakeryLightmapGroup lmgroup)
{
var areaLight = obj.GetComponent<BakeryLightMesh>();
if (areaLight == null)
{
int index = temporaryAreaLightMeshList.IndexOf(obj);
if (index >= 0)
{
areaLight = temporaryAreaLightMeshList2[index];
}
}
if (areaLight != null)
{
f.Write(areaLightCounter);
areaLight.lmid = areaLightCounter;
areaLightCounter--;
return areaLightCounter;
}
else if (lmgroup != null)
{
f.Write(lmgroup.id);
return lmgroup.id;
}
else
{
f.Write(0xFFFFFFFF);
return -1;
}
}
static Vector2[] GenerateVertexBakeUVs(int voffset, int vlength, int totalVertexCount)
{
int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)totalVertexCount));
atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize;
var uvs = new Vector2[vlength];
float mul = 1.0f / atlasTexSize;
float add = mul * 0.5f;
for(int i=0; i<vlength; i++)
{
int x = (i + voffset) % atlasTexSize;
int y = (i + voffset) / atlasTexSize;
uvs[i] = new Vector2(x * mul + add, y * mul + add);// - 1.0f);
}
return uvs;
}
public static Mesh GetSharedMesh(Renderer mr)
{
if (mr == null) return null;
var mrSkin = mr as SkinnedMeshRenderer;
var mf = mr.gameObject.GetComponent<MeshFilter>();
return mrSkin != null ? mrSkin.sharedMesh : (mf != null ? mf.sharedMesh : null);
}
public static Mesh GetSharedMeshBaked(GameObject obj)
{
var mrSkin = obj.GetComponent<SkinnedMeshRenderer>();
if (mrSkin != null)
{
var baked = new Mesh();
mrSkin.BakeMesh(baked);
return baked;
}
var mf = obj.GetComponent<MeshFilter>();
return (mf != null ? mf.sharedMesh : null);
}
public static Mesh GetSharedMesh(GameObject obj)
{
var mrSkin = obj.GetComponent<SkinnedMeshRenderer>();
var mf = obj.GetComponent<MeshFilter>();
return mrSkin != null ? mrSkin.sharedMesh : (mf != null ? mf.sharedMesh : null);
}
public static Mesh GetSharedMeshSkinned(GameObject obj, out bool isSkin)
{
var mrSkin = obj.GetComponent<SkinnedMeshRenderer>();
Mesh mesh;
if (mrSkin != null)
{
mesh = new Mesh();
mrSkin.BakeMesh(mesh);
isSkin = mrSkin.bones.Length > 0; // blendshape-only don't need scale?
}
else
{
isSkin = false;
var mf = obj.GetComponent<MeshFilter>();
if (mf == null) return null;
mesh = mf.sharedMesh;
}
return mesh;
}
static GameObject TestPackAsSingleSquare(GameObject holder)
{
var t = holder.transform;
var p = t.parent;
while(p != null)
{
if (p.GetComponent<BakeryPackAsSingleSquare>() != null) return p.gameObject;
p = p.parent;
}
return holder;
}
static bool ModelUVsOverlap(ModelImporter importer, ftGlobalStorage store)
{
if (importer.generateSecondaryUV) return true;
var path = importer.assetPath;
/*for(int i=0; i<storages.Length; i++)
{
var store = storages[i];
if (store == null) continue;
var index = store.assetList.IndexOf(path);
if (index < 0) continue;
if (store.uvOverlapAssetList[index] == 0)
{
return false;
}
else
{
return true;
}
}*/
var index = store.assetList.IndexOf(path);
if (index >= 0 && index < store.uvOverlapAssetList.Count)
{
if (store.uvOverlapAssetList[index] == 0)
{
return false;
}
else
{
return true;
}
}
var newAsset = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject;
ftModelPostProcessor.CheckUVOverlap(newAsset, path);
/*for(int i=0; i<storages.Length; i++)
{
var store = storages[i];
var index = store.assetList.IndexOf(path);
if (index < 0) continue;
if (store.uvOverlapAssetList[index] == 0)
{
return false;
}
else
{
return true;
}
}*/
index = store.assetList.IndexOf(path);
if (index >= 0)
{
if (store.uvOverlapAssetList[index] == 0)
{
return false;
}
else
{
return true;
}
}
return true;
}
static bool NeedsTangents(BakeryLightmapGroup lmgroup, bool tangentSHLights)
{
// RNM requires tangents
if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.RNM ||
(lmgroup!=null && lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.RNM)) return true;
// SH requires tangents only if there is a SH skylight
if (!tangentSHLights) return false;
if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.SH ||
(lmgroup!=null && lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.SH)) return true;
return false;
}
static long GetTime()
{
return System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond;
}
static public string progressBarText;
static public float progressBarPercent = 0;
//static bool progressBarEnabled = false;
static public bool userCanceled = false;
//static IEnumerator progressFunc;
static EditorWindow activeWindow;
static void ProgressBarInit(string startText, EditorWindow window = null)
{
progressBarText = startText;
//progressBarEnabled = true;
ftRenderLightmap.simpleProgressBarShow("Bakery", progressBarText, progressBarPercent, 0);
}
static void ProgressBarShow(string text, float percent)
{
progressBarText = text;
progressBarPercent = percent;
ftRenderLightmap.simpleProgressBarShow("Bakery", progressBarText, progressBarPercent, 0);
userCanceled = ftRenderLightmap.simpleProgressBarCancelled();
}
public static void FreeTemporaryAreaLightMeshes()
{
if (temporaryAreaLightMeshList != null)
{
for(int i=0; i<temporaryAreaLightMeshList.Count; i++)
{
if (temporaryAreaLightMeshList[i] != null)
{
//var mr = temporaryAreaLightMeshList[i].GetComponent<Renderer>();
//if (mr != null) DestroyImmediate(mr);
//var mf = temporaryAreaLightMeshList[i].GetComponent<MeshFilter>();
//if (mf != null) DestroyImmediate(mf);
DestroyImmediate(temporaryAreaLightMeshList[i]);
}
}
temporaryAreaLightMeshList = null;
}
}
public static void ProgressBarEnd(bool removeTempObjects)// bool isError = true)
{
if (removeTempObjects)
{
if (terrainObjectList != null)
{
for(int i=0; i<terrainObjectList.Count; i++)
{
if (terrainObjectList[i] != null) DestroyImmediate(terrainObjectList[i]);
}
terrainObjectList = null;
}
if (treeObjectList != null)
{
for(int i=0; i<treeObjectList.Count; i++)
{
if (treeObjectList[i] != null) DestroyImmediate(treeObjectList[i]);
}
treeObjectList = null;
}
//if (isError)
{
FreeTemporaryAreaLightMeshes();
}
}
//progressBarEnabled = false;
ftRenderLightmap.simpleProgressBarEnd();
}
void OnInspectorUpdate()
{
Repaint();
}
static void CloseAllFiles()
{
if (fscene != null) fscene.Close();
if (fmesh != null) fmesh.Close();
if (flmid != null) flmid.Close();
if (fseamfix != null) fseamfix.Close();
if (fsurf != null) fsurf.Close();
if (fmatid != null) fmatid.Close();
if (fmatide != null) fmatide.Close();
if (fmatideb != null) fmatideb.Close();
if (fmatidh != null) fmatidh.Close();
if (falphaid != null) falphaid.Close();
if (fvbfull != null) fvbfull.Close();
if (fvbtrace != null) fvbtrace.Close();
if (fvbtraceTex != null) fvbtraceTex.Close();
if (fvbtraceUV0 != null) fvbtraceUV0.Close();
if (fib != null) fib.Close();
if (fib32 != null) fib32.Close();
if (fhmaps != null) fhmaps.Close();
if (fib32lod != null)
{
for(int i=0; i<fib32lod.Length; i++) fib32lod[i].Close();
}
if (falphaidlod != null)
{
for(int i=0; i<falphaidlod.Length; i++) falphaidlod[i].Close();
}
fvbfull = fvbtrace = fvbtraceTex = fvbtraceUV0 = null;
fib = null;
fscene = fmesh = flmid = fsurf = fmatid = fmatide = fmatideb = falphaid = fib32 = fseamfix = fmatidh = fhmaps = null;
fib32lod = falphaidlod = null;
}
static bool CheckUnwrapError()
{
if (ftModelPostProcessor.unwrapError)
{
DebugLogError("Unity failed unwrapping some models. See console for details. Last failed model: " + ftModelPostProcessor.lastUnwrapErrorAsset+"\nModel will now be reverted to original state.");
int mstoreIndex = gstorage.modifiedAssetPathList.IndexOf(ftModelPostProcessor.lastUnwrapErrorAsset);
if (mstoreIndex < 0)
{
Debug.LogError("Failed to find failed asset?");
}
else
{
gstorage.ClearAssetModifications(mstoreIndex);
}
ftModelPostProcessor.unwrapError = false;
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
return true;
}
return false;
}
static Mesh BuildAreaLightMesh(Light areaLight)
{
var mesh = new Mesh();
var verts = new Vector3[4];
Vector2 areaSize = ftLightMeshInspector.GetAreaLightSize(areaLight);
verts[0] = new Vector3(-0.5f * areaSize.x, -0.5f * areaSize.y, 0);
verts[1] = new Vector3(0.5f * areaSize.x, -0.5f * areaSize.y, 0);
verts[2] = new Vector3(0.5f * areaSize.x, 0.5f * areaSize.y, 0);
verts[3] = new Vector3(-0.5f * areaSize.x, 0.5f * areaSize.y, 0);
var uvs = new Vector2[4];
uvs[0] = new Vector2(0, 0);
uvs[1] = new Vector2(1, 0);
uvs[2] = new Vector2(1, 1);
uvs[3] = new Vector2(0, 1);
var indices = new int[6];
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 0;
indices[4] = 2;
indices[5] = 3;
var normals = new Vector3[4];
var n = Vector3.forward;// -areaLight.transform.forward; // transformation will be applied later
for(int i=0; i<4; i++) normals[i] = n;
mesh.vertices = verts;
mesh.triangles = indices;
mesh.normals = normals;
mesh.uv = uvs;
return mesh;
}
static bool CheckForTangentSHLights()
{
var All2 = FindObjectsOfType(typeof(BakerySkyLight));
for(int i=0; i<All2.Length; i++)
{
var obj = All2[i] as BakerySkyLight;
if (!obj.enabled) continue;
if (!obj.gameObject.activeInHierarchy) continue;
if (obj.tangentSH)
{
return true;
}
}
return false;
}
public static void CreateSceneFolder()
{
if (scenePath == "" || !Directory.Exists(scenePath))
{
// Default scene path is TEMP/frender
var tempDir = System.Environment.GetEnvironmentVariable("TEMP", System.EnvironmentVariableTarget.Process);
scenePath = tempDir + "\\frender";
if (!Directory.Exists(scenePath)) Directory.CreateDirectory(scenePath);
}
}
static int CorrectLMGroupID(int id, BakeryLightmapGroup lmgroup, List<BakeryLightmapGroup> groupList)
{
id = id < 0 ? -1 : id;
if (lmgroup != null && lmgroup.parentName != null && lmgroup.parentName.Length > 0 && lmgroup.parentName != "|")
{
for(int g=0; g<groupList.Count; g++)
{
if (groupList[g].name == lmgroup.parentName)
{
id = g;
break;
}
}
}
return id;
}
static void InitSceneStorage(ExportSceneData data)
{
var storages = data.storages;
var sceneToID = data.sceneToID;
bool first = true;
for(int i=0; i<storages.Length; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (!scene.isLoaded) continue;
var gg = ftLightmaps.FindInScene("!ftraceLightmaps", scene);
storages[i] = gg.GetComponent<ftLightmapsStorage>();
if (modifyLightmapStorage)
{
/*
storages[i].bakedRenderers = new List<Renderer>();
storages[i].bakedIDs = new List<int>();
storages[i].bakedScaleOffset = new List<Vector4>();
storages[i].bakedVertexOffset = new List<int>();
storages[i].bakedVertexColorMesh = new List<Mesh>();
storages[i].bakedRenderersTerrain = new List<Terrain>();
storages[i].bakedIDsTerrain = new List<int>();
storages[i].bakedScaleOffsetTerrain = new List<Vector4>();
*/
storages[i].hasEmissive = new List<bool>();
storages[i].lmGroupLODResFlags = null;
storages[i].lmGroupMinLOD = null;
storages[i].lmGroupLODMatrix = null;
storages[i].nonBakedRenderers = new List<Renderer>();
}
if (first)
{
data.firstNonNullStorage = i;
first = false;
}
storages[i].implicitGroups = new List<UnityEngine.Object>();
storages[i].implicitGroupedObjects = new List<GameObject>();
sceneToID[scene] = i;
}
//var go = GameObject.Find("!ftraceLightmaps");
//data.settingsStorage = go.GetComponent<ftLightmapsStorage>();
}
static void InitSceneStorage2(ExportSceneData data)
{
var storages = data.storages;
for(int i=0; i<storages.Length; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (!scene.isLoaded) continue;
if (modifyLightmapStorage)
{
storages[i].bakedRenderers = new List<Renderer>();
storages[i].bakedIDs = new List<int>();
storages[i].bakedScaleOffset = new List<Vector4>();
storages[i].bakedVertexOffset = new List<int>();
storages[i].bakedVertexColorMesh = new List<Mesh>();
storages[i].bakedRenderersTerrain = new List<Terrain>();
storages[i].bakedIDsTerrain = new List<int>();
storages[i].bakedScaleOffsetTerrain = new List<Vector4>();
}
}
}
static IEnumerator CreateLightProbeLMGroup(ExportSceneData data)
{
var storages = data.storages;
var sceneToID = data.sceneToID;
var lmBounds = data.lmBounds;
var groupList = data.groupList;
var probes = LightmapSettings.lightProbes;
if (probes == null)
{
DebugLogError("No probes in LightingDataAsset");
yield break;
}
var positions = probes.positions;
int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)probes.count));
atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize;
var uvpos = new float[atlasTexSize * atlasTexSize * 4];
var uvnormal = new byte[atlasTexSize * atlasTexSize * 4];
for(int i=0; i<probes.count; i++)
{
int x = i % atlasTexSize;
int y = i / atlasTexSize;
int index = y * atlasTexSize + x;
uvpos[index * 4] = positions[i].x;
uvpos[index * 4 + 1] = positions[i].y;
uvpos[index * 4 + 2] = positions[i].z;
uvpos[index * 4 + 3] = 1.0f;
uvnormal[index * 4 + 1] = 255;
uvnormal[index * 4 + 3] = 255;
}
var posFile = new byte[128 + uvpos.Length * 4];
System.Buffer.BlockCopy(ftDDS.ddsHeaderFloat4, 0, posFile, 0, 128);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 12, 4);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 16, 4);
System.Buffer.BlockCopy(uvpos, 0, posFile, 128, uvpos.Length * 4);
SaveGBufferMapFromRAM(posFile, posFile.Length, scenePath + "/uvpos_probes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(8);
yield return null;
var posNormal = new byte[128 + uvnormal.Length];
System.Buffer.BlockCopy(ftDDS.ddsHeaderRGBA8, 0, posNormal, 0, 128);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 12, 4);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 16, 4);
System.Buffer.BlockCopy(uvnormal, 0, posNormal, 128, uvnormal.Length);
SaveGBufferMapFromRAM(posNormal, posNormal.Length, scenePath + "/uvnormal_probes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(8);
yield return null;
lightProbeLMGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
lightProbeLMGroup.name = "probes";
lightProbeLMGroup.probes = true;
lightProbeLMGroup.isImplicit = true;
lightProbeLMGroup.resolution = 256;
lightProbeLMGroup.bitmask = 1;
lightProbeLMGroup.mode = BakeryLightmapGroup.ftLMGroupMode.Vertex;
lightProbeLMGroup.id = data.lmid;
lightProbeLMGroup.totalVertexCount = probes.count;
lightProbeLMGroup.vertexCounter = 0;
lightProbeLMGroup.renderDirMode = BakeryLightmapGroup.RenderDirMode.ProbeSH;
groupList.Add(lightProbeLMGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(10000,10000,10000)));
data.lmid++;
storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroups.Add(lightProbeLMGroup);
storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroupedObjects.Add(null);
}
static IEnumerator CreateVolumeLMGroup(ExportSceneData data)
{
ftRenderLightmap.hasAnyVolumes = false;
var vols = ftRenderLightmap.FindBakeableVolumes();
if (vols.Length == 0) yield break;
ftRenderLightmap.hasAnyVolumes = true;
var storages = data.storages;
var sceneToID = data.sceneToID;
var lmBounds = data.lmBounds;
var groupList = data.groupList;
int numTotalProbes = 0;
for(int v=0; v<vols.Length; v++)
{
numTotalProbes += vols[v].resolutionX * vols[v].resolutionY * vols[v].resolutionZ;
}
var positions = new Vector3[numTotalProbes];
int i = 0;
Vector3 halfVoxelSize = Vector3.one;
for(int v=0; v<vols.Length; v++)
{
var vol = vols[v];
int rx = vol.resolutionX;
int ry = vol.resolutionY;
int rz = vol.resolutionZ;
var bmin = vol.bounds.min;
var bmax = vol.bounds.max;
halfVoxelSize = bmax - bmin;
halfVoxelSize = new Vector3(halfVoxelSize.x/rx, halfVoxelSize.y/ry, halfVoxelSize.z/rz) * 0.5f;
float lx, ly, lz;
for(int z=0; z<rz; z++)
{
lz = Mathf.Lerp(bmin.z, bmax.z, z/(float)rz) + halfVoxelSize.z;
for(int y=0; y<ry; y++)
{
ly = Mathf.Lerp(bmin.y, bmax.y, y/(float)ry) + halfVoxelSize.y;
for(int x=0; x<rx; x++)
{
lx = Mathf.Lerp(bmin.x, bmax.x, x/(float)rx) + halfVoxelSize.x;
positions[i] = new Vector3(lx, ly, lz);
i++;
}
}
}
}
int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)numTotalProbes));
atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize;
var uvpos = new float[atlasTexSize * atlasTexSize * 4];
var uvnormal = new byte[atlasTexSize * atlasTexSize * 4];
for(i=0; i<numTotalProbes; i++)
{
int x = i % atlasTexSize;
int y = i / atlasTexSize;
int index = y * atlasTexSize + x;
uvpos[index * 4] = positions[i].x;
uvpos[index * 4 + 1] = positions[i].y;
uvpos[index * 4 + 2] = positions[i].z;
uvpos[index * 4 + 3] = 1.0f;
uvnormal[index * 4 + 1] = 255;
uvnormal[index * 4 + 3] = 255;
}
var posFile = new byte[128 + uvpos.Length * 4];
System.Buffer.BlockCopy(ftDDS.ddsHeaderFloat4, 0, posFile, 0, 128);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 12, 4);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posFile, 16, 4);
System.Buffer.BlockCopy(uvpos, 0, posFile, 128, uvpos.Length * 4);
SaveGBufferMapFromRAM(posFile, posFile.Length, scenePath + "/uvpos_volumes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(8);
yield return null;
var posNormal = new byte[128 + uvnormal.Length];
System.Buffer.BlockCopy(ftDDS.ddsHeaderRGBA8, 0, posNormal, 0, 128);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 12, 4);
System.Buffer.BlockCopy(BitConverter.GetBytes(atlasTexSize), 0, posNormal, 16, 4);
System.Buffer.BlockCopy(uvnormal, 0, posNormal, 128, uvnormal.Length);
SaveGBufferMapFromRAM(posNormal, posNormal.Length, scenePath + "/uvnormal_volumes" + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"), ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(8);
yield return null;
volumeLMGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
volumeLMGroup.name = "volumes";
volumeLMGroup.probes = true;
volumeLMGroup.fixPos3D = true;
volumeLMGroup.voxelSize = halfVoxelSize * 2; // incorrect... should be different for every probe
volumeLMGroup.isImplicit = true;
volumeLMGroup.resolution = 256;
volumeLMGroup.bitmask = 1;
volumeLMGroup.mode = BakeryLightmapGroup.ftLMGroupMode.Vertex;
volumeLMGroup.id = data.lmid;
volumeLMGroup.totalVertexCount = numTotalProbes;
volumeLMGroup.vertexCounter = 0;
volumeLMGroup.renderDirMode = BakeryLightmapGroup.RenderDirMode.ProbeSH;
groupList.Add(volumeLMGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(10000,10000,10000)));
data.lmid++;
storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroups.Add(volumeLMGroup);
storages[sceneToID[EditorSceneManager.GetActiveScene()]].implicitGroupedObjects.Add(null);
}
static void CollectExplicitLMGroups(ExportSceneData data)
{
var groupList = data.groupList;
var lmBounds = data.lmBounds;
// Find explicit LMGroups
// (Also init lmBounds and LMID)
var groupSelectors = new List<BakeryLightmapGroupSelector>(FindObjectsOfType(typeof(BakeryLightmapGroupSelector)) as BakeryLightmapGroupSelector[]);
for(int i=0; i<groupSelectors.Count; i++)
{
var lmgroup = groupSelectors[i].lmgroupAsset as BakeryLightmapGroup;
if (lmgroup == null) continue;
if (!groupList.Contains(lmgroup))
{
lmgroup.id = data.lmid;
lmgroup.sceneLodLevel = -1;
lmgroup.sceneName = "";
groupList.Add(lmgroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0)));
data.lmid++;
}
EditorUtility.SetDirty(lmgroup);
}
}
static bool CheckForMultipleSceneStorages(GameObject obj, ExportSceneData data)
{
var sceneHasStorage = data.sceneHasStorage;
// Check for storage count in each scene
if (obj.name == "!ftraceLightmaps")
{
if (!sceneHasStorage.ContainsKey(obj.scene))
{
sceneHasStorage[obj.scene] = true;
}
else
{
return ExportSceneValidationMessage("Scene " + obj.scene.name + " has multiple lightmap storage objects. This is not currently supported. Make sure you don't bake a scene with already baked prefabs.");
}
}
return true;
}
static void ConvertTerrain(GameObject obj)
{
var terr = obj.GetComponent<Terrain>();
if (terr == null) return;
if (!terr.enabled) return;
if (!obj.activeInHierarchy) return;
if ((obj.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) return; // skip temp objects
if (obj.tag == "EditorOnly") return; // skip temp objects
if ((GameObjectUtility.GetStaticEditorFlags(obj) & StaticEditorFlags.ContributeGI) == 0) return; // skip dynamic
var so = new SerializedObject(terr);
var scaleInLmTerr = so.FindProperty("m_ScaleInLightmap").floatValue;
var terrParent = new GameObject();
SceneManager.MoveGameObjectToScene(terrParent, obj.scene);
terrParent.transform.parent = obj.transform.parent;
var expGroup = obj.GetComponent<BakeryLightmapGroupSelector>();
if (expGroup != null)
{
var expGroup2 = terrParent.AddComponent<BakeryLightmapGroupSelector>();
expGroup2.lmgroupAsset = expGroup.lmgroupAsset;
expGroup2.instanceResolutionOverride = expGroup.instanceResolutionOverride;
expGroup2.instanceResolution = expGroup.instanceResolution;
}
terrParent.name = "__ExportTerrainParent";
terrainObjectList.Add(terrParent);
terrainObjectToActual.Add(terr);
var tdata = terr.terrainData;
int res = tdata.heightmapResolution;
var heightmap = tdata.GetHeights(0, 0, res, res);
var uvscale = new Vector2(1,1) / (res-1);
var uvoffset = new Vector2(0,0);
var gposOffset = obj.transform.position;
float scaleX = tdata.size.x / (res-1);
float scaleY = tdata.size.y;
float scaleZ = tdata.size.z / (res-1);
int patchRes = res;
while(patchRes > 254) patchRes = 254;//patchRes /= 2;
int numVerts = patchRes * patchRes;
int numPatches = (int)Mathf.Ceil(res / (float)patchRes);
// Gen terrain texture
var oldMat = terr.materialTemplate;
var oldMatType = terr.materialType;
var oldPos = obj.transform.position;
var unlitTerrainMat = new Material(Shader.Find("Hidden/ftUnlitTerrain"));
//unlitTerrainMat = AssetDatabase.LoadAssetAtPath("Assets/Bakery/ftUnlitTerrain.mat", typeof(Material)) as Material;
terr.materialTemplate = unlitTerrainMat;
terr.materialType = Terrain.MaterialType.Custom;
obj.transform.position = new Vector3(-10000, -10000, -10000); // let's hope it's not the worst idea
var tempCamGO = new GameObject();
tempCamGO.transform.parent = obj.transform;
tempCamGO.transform.localPosition = new Vector3(tdata.size.x * 0.5f, scaleY + 1, tdata.size.z * 0.5f);
tempCamGO.transform.eulerAngles = new Vector3(90,0,0);
var tempCam = tempCamGO.AddComponent<Camera>();
tempCam.orthographic = true;
tempCam.orthographicSize = Mathf.Max(tdata.size.x, tdata.size.z) * 0.5f;
tempCam.aspect = Mathf.Max(tdata.size.x, tdata.size.z) / Mathf.Min(tdata.size.x, tdata.size.z);
tempCam.enabled = false;
tempCam.clearFlags = CameraClearFlags.SolidColor;
tempCam.backgroundColor = new Color(0,0,0,0);
tempCam.targetTexture =
new RenderTexture(tdata.baseMapResolution, tdata.baseMapResolution, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
var tex = new Texture2D(tdata.baseMapResolution, tdata.baseMapResolution, TextureFormat.ARGB32, true, false);
RenderTexture.active = tempCam.targetTexture;
tempCam.Render();
terr.materialTemplate = oldMat;
terr.materialType = oldMatType;
obj.transform.position = oldPos;
RenderTexture.active = tempCam.targetTexture;
tex.ReadPixels(new Rect(0,0,tdata.baseMapResolution, tdata.baseMapResolution), 0, 0, true);
tex.Apply();
unlitTerrainMat.mainTexture = tex;
Graphics.SetRenderTarget(null);
DestroyImmediate(tempCamGO);
if (exportTerrainAsHeightmap)
{
var hmap = new BinaryWriter(File.Open(scenePath + "/height" + terrainObjectToHeightMap.Count + ".dds", FileMode.Create));
if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("height" + terrainObjectToHeightMap.Count + ".dds");
hmap.Write(ftDDS.ddsHeaderR32F);
var bytes = new byte[res * res * 4];
// Normalize heights
float maxHeight = 0;
float height;
for(int y=0; y<res; y++)
{
for(int x=0; x<res; x++)
{
height = heightmap[x,y];
if (height > maxHeight) maxHeight = height;
}
}
maxHeight = Mathf.Max(maxHeight, 0.0001f);
float invMaxHeight = 1.0f / maxHeight;
for(int y=0; y<res; y++)
{
for(int x=0; x<res; x++)
{
heightmap[x,y] *= invMaxHeight;
}
}
float aabbHeight = maxHeight * scaleY;
//Debug.Log("aabbHeight: " + aabbHeight);
// First mip is the real heightmap
System.Buffer.BlockCopy(heightmap, 0, bytes, 0, bytes.Length);
hmap.Write(bytes);
var htex = new Texture2D(res, res, TextureFormat.RFloat, false, true);
htex.LoadRawTextureData(bytes);
htex.Apply();
terrainObjectToHeightMap.Add(htex);
// min
terrainObjectToBounds.Add(obj.transform.position.x);
terrainObjectToBounds.Add(obj.transform.position.y);
terrainObjectToBounds.Add(obj.transform.position.z);
// max
terrainObjectToBounds.Add(obj.transform.position.x + tdata.size.x);
terrainObjectToBounds.Add(obj.transform.position.y + aabbHeight);
terrainObjectToBounds.Add(obj.transform.position.z + tdata.size.z);
// filled later
terrainObjectToLMID.Add(0);
terrainObjectToBoundsUV.Add(0);
terrainObjectToBoundsUV.Add(0);
terrainObjectToBoundsUV.Add(0);
terrainObjectToBoundsUV.Add(0);
terrainObjectToFlags.Add(terr.castShadows ? 1 : 0);
// Second mip is max() of 3x3 corners
float[] floats = null;
float[] floatsPrev = null;
float[] floatsTmp;
int mipCount = 1;
int mipRes = res / 2;
//int prevRes = res;
float h00, h10, h01, h11;
/*Vector3 n00, n10, n01, n11;
Vector3[] normals = null;
Vector3[] normalsPrev = null;
var origNormals = new Vector3[res * res];
for(int y=0; y<res; y++)
{
for(int x=0; x<res; x++)
{
origNormals[y*res+x] = data.GetInterpolatedNormal(x / (float)res, y / (float)res);
}
}*/
int terrIndex = terrainObjectToHeightMap.Count - 1;//terrainObjectToNormalMip0.Count;
//terrainObjectToNormalMip0.Add(origNormals);
//normalsPrev = origNormals;
terrainObjectToHeightMips.Add(new List<float[]>());
//terrainObjectToNormalMips.Add(new List<Vector3[]>());
if (mipRes > 0)
{
floats = new float[mipRes * mipRes];
//normals = new Vector3[mipRes * mipRes];
for(int y=0; y<mipRes; y++)
{
for(int x=0; x<mipRes; x++)
{
/*h00 = heightmap[y*2,x*2];
h10 = heightmap[y*2,x*2+1];
h01 = heightmap[y*2+1,x*2];
h11 = heightmap[y*2+1,x*2+1];
height = h00 > h10 ? h00 : h10;
height = height > h01 ? height : h01;
height = height > h11 ? height : h11;
floats[y*mipRes+x] = height;*/
float maxVal = 0;
for(int yy=0; yy<3; yy++)
{
for(int xx=0; xx<3; xx++)
{
float val = heightmap[y*2+yy, x*2+xx];
if (val > maxVal) maxVal = val;
}
}
floats[y*mipRes+x] = maxVal;
//n00 = normalsPrev[y*2 * res + x*2];
//n10 = normalsPrev[y*2 * res + x*2+1];
//n01 = normalsPrev[(y*2+1) * res + x*2];
//n11 = normalsPrev[(y*2+1) * res + x*2+1];
//normals[y*mipRes+x] = (n00 + n10 + n01 + n11);
}
}
System.Buffer.BlockCopy(floats, 0, bytes, 0, mipRes*mipRes*4);
hmap.Write(bytes, 0, mipRes*mipRes*4);
float[] storedMip = new float[mipRes*mipRes];
System.Buffer.BlockCopy(floats, 0, storedMip, 0, mipRes*mipRes*4);
terrainObjectToHeightMips[terrIndex].Add(storedMip);
//terrainObjectToNormalMips[terrIndex].Add(normals);
mipCount++;
mipRes /= 2;
}
// Next mips are regular max() of 4 texels
while(mipRes > 1)
{
if (floatsPrev == null)
{
floatsPrev = floats;
floats = new float[mipRes * mipRes];
}
//normalsPrev = normals;
//normals = new Vector3[mipRes * mipRes];
for(int y=0; y<mipRes; y++)
{
for(int x=0; x<mipRes; x++)
{
h00 = floatsPrev[y*2 * mipRes*2 + x*2];
h10 = floatsPrev[y*2 * mipRes*2 + x*2+1];
h01 = floatsPrev[(y*2+1) * mipRes*2 + x*2];
h11 = floatsPrev[(y*2+1) * mipRes*2 + x*2+1];
height = h00 > h10 ? h00 : h10;
height = height > h01 ? height : h01;
height = height > h11 ? height : h11;
floats[y*mipRes+x] = height;
//n00 = normalsPrev[y*2 * mipRes*2 + x*2];
//n10 = normalsPrev[y*2 * mipRes*2 + x*2+1];
//n01 = normalsPrev[(y*2+1) * mipRes*2 + x*2];
//n11 = normalsPrev[(y*2+1) * mipRes*2 + x*2+1];
//normals[y*mipRes+x] = (n00 + n10 + n01 + n11);
}
}
System.Buffer.BlockCopy(floats, 0, bytes, 0, mipRes*mipRes*4);
hmap.Write(bytes, 0, mipRes*mipRes*4);
float[] storedMip = new float[mipRes*mipRes];
System.Buffer.BlockCopy(floats, 0, storedMip, 0, mipRes*mipRes*4);
terrainObjectToHeightMips[terrIndex].Add(storedMip);
//terrainObjectToNormalMips[terrIndex].Add(normals);
mipCount++;
mipRes /= 2;
floatsTmp = floatsPrev;
floatsPrev = floats;
floats = floatsTmp;
}
hmap.BaseStream.Seek(12, SeekOrigin.Begin);
hmap.Write(res);
hmap.Write(res);
hmap.BaseStream.Seek(28, SeekOrigin.Begin);
hmap.Write(mipCount);
hmap.Close();
// Create dummy plane for packing/albedo/emissive purposes
var mesh = new Mesh();
mesh.vertices = new Vector3[] { gposOffset,
gposOffset + new Vector3(tdata.size.x, 0, 0),
gposOffset + new Vector3(tdata.size.x, 0, tdata.size.z),
gposOffset + new Vector3(0, 0, tdata.size.z) };
mesh.triangles = new int[]{0,1,2, 2,3,0};
mesh.normals = new Vector3[]{Vector3.up, Vector3.up, Vector3.up, Vector3.up};
mesh.uv = new Vector2[]{new Vector2(0,0), new Vector2(1,0), new Vector2(1,1), new Vector2(0,1)};
mesh.uv2 = mesh.uv;
var terrGO = new GameObject();
terrGO.name = "__ExportTerrain";
GameObjectUtility.SetStaticEditorFlags(terrGO, StaticEditorFlags.ContributeGI);
terrGO.transform.parent = terrParent.transform;
var mf = terrGO.AddComponent<MeshFilter>();
var mr = terrGO.AddComponent<MeshRenderer>();
mf.sharedMesh = mesh;
#if UNITY_2019_3_OR_NEWER
// using standard materialTemplates in 2019.3 doesn't work
mr.sharedMaterial = unlitTerrainMat;
#else
mr.sharedMaterial = (terr.materialTemplate == null) ? unlitTerrainMat : terr.materialTemplate;
#endif
terrGO.transform.position = obj.transform.position;
var so2 = new SerializedObject(mr);
so2.FindProperty("m_ScaleInLightmap").floatValue = scaleInLmTerr;
so2.ApplyModifiedProperties();
//terrainObjectList.Add(terrGO);
//terrainObjectToActual.Add(terr);
}
else
{
for (int patchX=0; patchX<numPatches; patchX++)
{
for (int patchY=0; patchY<numPatches; patchY++)
{
int patchResX = patchX < numPatches-1 ? patchRes : (res - patchRes*(numPatches-1));
int patchResY = patchY < numPatches-1 ? patchRes : (res - patchRes*(numPatches-1));
if (patchX < numPatches-1) patchResX += 1;
if (patchY < numPatches-1) patchResY += 1;
numVerts = patchResX * patchResY;
var posOffset = gposOffset + new Vector3(patchX*patchRes*scaleX, 0, patchY*patchRes*scaleZ);
var positions = new Vector3[numVerts];
var uvs = new Vector2[numVerts];
var normals = new Vector3[numVerts];
var indices = new int[(patchResX-1) * (patchResY-1) * 2 * 3];
int vertOffset = 0;
int indexOffset = 0;
for (int x=0;x<patchResX;x++)
{
for (int y=0;y<patchResY;y++)
{
int gx = x + patchX * patchRes;
int gy = y + patchY * patchRes;
int index = x * patchResY + y;
float height = heightmap[gy,gx];
positions[index] = new Vector3(x * scaleX, height * scaleY, y * scaleZ) + posOffset;
uvs[index] = new Vector2(gx * uvscale.x + uvoffset.x, gy * uvscale.y + uvoffset.y);
normals[index] = tdata.GetInterpolatedNormal(gx / (float)res, gy / (float)res);
if (x < patchResX-1 && y < patchResY-1)
{
indices[indexOffset] = vertOffset + patchResY + 1;
indices[indexOffset + 1] = vertOffset + patchResY;
indices[indexOffset + 2] = vertOffset;
indices[indexOffset + 3] = vertOffset + 1;
indices[indexOffset + 4] = vertOffset + patchResY + 1;
indices[indexOffset + 5] = vertOffset;
indexOffset += 6;
}
vertOffset++;
}
}
var mesh = new Mesh();
mesh.vertices = positions;
mesh.triangles = indices;
mesh.normals = normals;
mesh.uv = uvs;
mesh.uv2 = uvs;
var terrGO = new GameObject();
terrGO.name = "__ExportTerrain";
GameObjectUtility.SetStaticEditorFlags(terrGO, StaticEditorFlags.ContributeGI);
terrGO.transform.parent = terrParent.transform;
var mf = terrGO.AddComponent<MeshFilter>();
var mr = terrGO.AddComponent<MeshRenderer>();
mf.sharedMesh = mesh;
#if UNITY_2019_3_OR_NEWER
// using standard materialTemplates in 2019.3 doesn't work
mr.sharedMaterial = unlitTerrainMat;
#else
mr.sharedMaterial = (terr.materialTemplate == null) ? unlitTerrainMat : terr.materialTemplate;
#endif
var so2 = new SerializedObject(mr);
so2.FindProperty("m_ScaleInLightmap").floatValue = scaleInLmTerr;
so2.ApplyModifiedProperties();
mr.shadowCastingMode = terr.castShadows ? UnityEngine.Rendering.ShadowCastingMode.On : UnityEngine.Rendering.ShadowCastingMode.Off;
terrainObjectList.Add(terrGO);
terrainObjectToActual.Add(terr);
}
}
}
if (exportTerrainTrees && terr.drawTreesAndFoliage)
{
var trees = tdata.treeInstances;
for (int t = 0; t < trees.Length; t++)
{
Vector3 pos = Vector3.Scale(trees[t].position, tdata.size) + obj.transform.position;
var treeProt = tdata.treePrototypes[trees[t].prototypeIndex];
var prefab = treeProt.prefab;
var newObj = GameObject.Instantiate(prefab, pos, Quaternion.AngleAxis(trees[t].rotation, Vector3.up)) as GameObject;
newObj.name = "__Export" + newObj.name;
treeObjectList.Add(newObj);
var lodGroup = newObj.GetComponent<LODGroup>();
if (lodGroup == null)
{
var renderers = newObj.GetComponentsInChildren<Renderer>();
for(int r=0; r<renderers.Length; r++)
{
GameObjectUtility.SetStaticEditorFlags(renderers[r].gameObject, StaticEditorFlags.ContributeGI);
var s = new SerializedObject(renderers[r]);
s.FindProperty("m_ScaleInLightmap").floatValue = 0;
s.ApplyModifiedProperties();
}
}
else
{
var lods = lodGroup.GetLODs();
for (int tl = 0; tl < lods.Length; tl++)
{
for (int h = 0; h < lods[tl].renderers.Length; h++)
{
GameObjectUtility.SetStaticEditorFlags(lods[tl].renderers[h].gameObject, tl==0 ? StaticEditorFlags.ContributeGI : 0);
if (tl == 0)
{
var s = new SerializedObject(lods[tl].renderers[h]);
s.FindProperty("m_ScaleInLightmap").floatValue = 0;
s.ApplyModifiedProperties();
}
}
}
}
var xform = newObj.transform;
xform.localScale = new Vector3(trees[t].widthScale, trees[t].heightScale, trees[t].widthScale);
}
}
}
static bool ConvertUnityAreaLight(GameObject obj)
{
// Add temporary meshes to area lights
var areaLightMesh = obj.GetComponent<BakeryLightMesh>();
if (areaLightMesh != null)
{
var areaLight = obj.GetComponent<Light>();
var mr = obj.GetComponent<Renderer>();
var mf = obj.GetComponent<MeshFilter>();
if (!forceAllAreaLightsSelfshadow)
{
if (!areaLightMesh.selfShadow) return true; // no selfshadow - ignore mesh export
}
if (areaLight != null && ftLightMeshInspector.IsArea(areaLight) && (mr == null || mf == null))
{
var areaObj = new GameObject();
mf = areaObj.AddComponent<MeshFilter>();
mf.sharedMesh = BuildAreaLightMesh(areaLight);
mr = areaObj.AddComponent<MeshRenderer>();
var props = new MaterialPropertyBlock();
props.SetColor("_Color", areaLightMesh.color);
props.SetFloat("intensity", areaLightMesh.intensity);
if (areaLightMesh.texture != null) props.SetTexture("_MainTex", areaLightMesh.texture);
mr.SetPropertyBlock(props);
GameObjectUtility.SetStaticEditorFlags(areaObj, StaticEditorFlags.ContributeGI);
temporaryAreaLightMeshList.Add(areaObj);
temporaryAreaLightMeshList2.Add(areaLightMesh);
var xformSrc = obj.transform;
var xformDest = areaObj.transform;
xformDest.position = xformSrc.position;
xformDest.rotation = xformSrc.rotation;
var srcMtx = xformSrc.localToWorldMatrix;
xformDest.localScale = new Vector3(srcMtx.GetColumn(0).magnitude, srcMtx.GetColumn(1).magnitude, srcMtx.GetColumn(2).magnitude);
return true; // mesh created
}
}
return false; // not Light Mesh
}
static void MapObjectsToSceneLODs(ExportSceneData data, UnityEngine.Object[] objects)
{
var objToLodLevel = data.objToLodLevel;
var objToLodLevelVisible = data.objToLodLevelVisible;
const int maxSceneLodLevels = 100;
var sceneLodUsed = new int[maxSceneLodLevels];
for(int i=0; i<maxSceneLodLevels; i++) sceneLodUsed[i] = -1;
var lodGroups = Resources.FindObjectsOfTypeAll(typeof(LODGroup));
var lodLevelsInLodGroup = new List<int>[lodGroups.Length];
var localLodLevelsInLodGroup = new List<int>[lodGroups.Length];
int lcounter = -1;
foreach(LODGroup lodgroup in lodGroups)
{
lcounter++;
if (!lodgroup.enabled) continue;
var obj = lodgroup.gameObject;
if (obj == null) continue;
if (!obj.activeInHierarchy) continue;
var path = AssetDatabase.GetAssetPath(obj);
if (path != "") continue; // must belond to scene
if ((obj.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) continue; // skip temp objects
if (obj.tag == "EditorOnly") continue; // skip temp objects
var lods = lodgroup.GetLODs();
if (lods.Length == 0) continue;
for(int i=0; i<lods.Length; i++)
{
var lodRenderers = lods[i].renderers;
if (lodRenderers.Length == 0) continue;
bool lightmappedLOD = false;
for(int j=0; j<lodRenderers.Length; j++)
{
var r = lodRenderers[j];
if (r == null) continue;
if (!r.enabled) continue;
if (!r.gameObject.activeInHierarchy) continue;
if ((r.gameObject.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) continue; // skip temp objects
if (r.gameObject.tag == "EditorOnly") continue; // skip temp objects
if ((GameObjectUtility.GetStaticEditorFlags(r.gameObject) & StaticEditorFlags.ContributeGI) == 0) continue; // skip dynamic
var mr = r.gameObject.GetComponent<Renderer>();
var sharedMesh = GetSharedMesh(mr);
if (mr == null || sharedMesh == null) continue; // must have visible mesh
var mrEnabled = mr.enabled || r.gameObject.GetComponent<BakeryAlwaysRender>() != null;
if (!mrEnabled) continue;
//if (mf.sharedMesh == null) continue;
var so = new SerializedObject(mr);
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
if (scaleInLm == 0) continue;
lightmappedLOD = true;
break;
}
if (!lightmappedLOD) continue;
var lodDist = i == 0 ? 0 : (int)Mathf.Clamp((1.0f-lods[i-1].screenRelativeTransitionHeight) * (maxSceneLodLevels-1), 0, maxSceneLodLevels-1);
if (sceneLodUsed[lodDist] < 0)
{
sceneLodUsed[lodDist] = sceneLodsUsed;
sceneLodsUsed++;
}
int newLodLevel = sceneLodUsed[lodDist];
if (lodLevelsInLodGroup[lcounter] == null)
{
lodLevelsInLodGroup[lcounter] = new List<int>();
localLodLevelsInLodGroup[lcounter] = new List<int>();
}
if (lodLevelsInLodGroup[lcounter].IndexOf(newLodLevel) < 0)
{
lodLevelsInLodGroup[lcounter].Add(newLodLevel);
localLodLevelsInLodGroup[lcounter].Add(i);
}
for(int j=0; j<lodRenderers.Length; j++)
{
var r = lodRenderers[j];
if (r == null) continue;
int existingLodLevel = -1;
if (objToLodLevel.ContainsKey(r.gameObject)) existingLodLevel = objToLodLevel[r.gameObject];
if (existingLodLevel < newLodLevel)
{
objToLodLevel[r.gameObject] = existingLodLevel < 0 ? newLodLevel : existingLodLevel; // set to lowest LOD
// Collect LOD levels where this object is visible
List<int> visList;
if (!objToLodLevelVisible.TryGetValue(r.gameObject, out visList)) objToLodLevelVisible[r.gameObject] = visList = new List<int>();
visList.Add(newLodLevel);
}
}
}
}
// Sort scene LOD levels
int counter = 0;
var unsortedLodToSortedLod = new int[maxSceneLodLevels];
for(int i=0; i<maxSceneLodLevels; i++)
{
int unsorted = sceneLodUsed[i];
if (unsorted >= 0)
{
unsortedLodToSortedLod[unsorted] = counter;
sceneLodUsed[i] = counter;
counter++;
}
}
var keys = new GameObject[objToLodLevel.Count];
counter = 0;
foreach(var pair in objToLodLevel)
{
keys[counter] = pair.Key;
counter++;
}
foreach(var key in keys)
{
int unsorted = objToLodLevel[key];
objToLodLevel[key] = unsortedLodToSortedLod[unsorted];
var visList = objToLodLevelVisible[key];
for(int j=0; j<visList.Count; j++)
{
visList[j] = unsortedLodToSortedLod[visList[j]];
}
}
for(int i=0; i<lodLevelsInLodGroup.Length; i++)
{
if (lodLevelsInLodGroup[i] == null) continue;
var levels = lodLevelsInLodGroup[i];
for(int j=0; j<levels.Count; j++)
{
levels[j] = unsortedLodToSortedLod[levels[j]];
}
}
// Fill LOD gaps
for(int i=0; i<lodLevelsInLodGroup.Length; i++)
{
if (lodLevelsInLodGroup[i] == null) continue;
var levels = lodLevelsInLodGroup[i];
var localLevels = localLodLevelsInLodGroup[i];
var lgroup = lodGroups[i] as LODGroup;
var lods = lgroup.GetLODs();
for(int j=0; j<levels.Count; j++)
{
int level = levels[j];
int localLevel = localLevels[j];
int nextLevel = (j == levels.Count-1) ? (sceneLodsUsed-1) : levels[j+1];
if (nextLevel - level > 1)
{
var lodRenderers = lods[localLevel].renderers;
for(int k=0; k<lodRenderers.Length; k++)
{
var r = lodRenderers[k];
if (r == null) continue;
var visList = objToLodLevelVisible[r.gameObject];
for(int l=level+1; l<nextLevel; l++)
{
visList.Add(l);
}
}
}
}
}
Debug.Log("Scene LOD levels: " + sceneLodsUsed);
// Init scene LOD index buffers
data.indicesOpaqueLOD = new List<int>[sceneLodsUsed];
data.indicesTransparentLOD = new List<int>[sceneLodsUsed];
for(int i=0; i<sceneLodsUsed; i++)
{
data.indicesOpaqueLOD[i] = new List<int>();
data.indicesTransparentLOD[i] = new List<int>();
}
// Sort objects by scene-wide LOD level
if (sceneLodsUsed > 0)
{
Array.Sort(objects, delegate(UnityEngine.Object a, UnityEngine.Object b)
{
if (a == null || b == null) return 0;
int lodLevelA = -1;
int lodLevelB = -1;
if (!objToLodLevel.TryGetValue((GameObject)a, out lodLevelA)) lodLevelA = -1;
if (!objToLodLevel.TryGetValue((GameObject)b, out lodLevelB)) lodLevelB = -1;
return lodLevelA.CompareTo(lodLevelB);
});
}
}
static bool FilterObjects(ExportSceneData data, UnityEngine.Object[] objects)
{
var objToLodLevel = data.objToLodLevel;
var groupList = data.groupList;
var lmBounds = data.lmBounds;
var storages = data.storages;
var sceneToID = data.sceneToID;
var objsToWrite = data.objsToWrite;
var objsToWriteNames = data.objsToWriteNames;
var objsToWriteLightmapped = data.objsToWriteLightmapped;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteHolder = data.objsToWriteHolder;
var objsToWriteVerticesUV = data.objsToWriteVerticesUV;
var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2;
var objsToWriteIndices = data.objsToWriteIndices;
var prop = new MaterialPropertyBlock();
foreach(GameObject obj in objects)
{
if (obj == null) continue;
if (!obj.activeInHierarchy) continue;
var path = AssetDatabase.GetAssetPath(obj);
if (path != "") continue; // must belond to scene
if ((obj.hideFlags & (HideFlags.DontSave|HideFlags.HideAndDontSave)) != 0) continue; // skip temp objects
if (obj.tag == "EditorOnly") continue; // skip temp objects
var areaLight = obj.GetComponent<BakeryLightMesh>();
if (areaLight == null)
{
int areaIndex = temporaryAreaLightMeshList.IndexOf(obj);
if (areaIndex >= 0) areaLight = temporaryAreaLightMeshList2[areaIndex];
}
if (areaLight != null)
{
if (!forceAllAreaLightsSelfshadow)
{
if (!areaLight.selfShadow) continue;
}
}
var mr = obj.GetComponent<Renderer>();
if (mr as MeshRenderer == null && mr as SkinnedMeshRenderer == null)
{
// must be MR or SMR
continue;
}
var sharedMesh = GetSharedMesh(mr);
if (sharedMesh == null) continue; // must have visible mesh
// Remove previous lightmap
#if UNITY_2018_1_OR_NEWER
if (mr.HasPropertyBlock())
{
// Reset shader props
mr.GetPropertyBlock(prop);
prop.SetFloat("bakeryLightmapMode", 0);
mr.SetPropertyBlock(prop);
}
#else
mr.GetPropertyBlock(prop);
if (!prop.isEmpty)
{
prop.SetFloat("bakeryLightmapMode", 0);
mr.SetPropertyBlock(prop);
}
#endif
if (((GameObjectUtility.GetStaticEditorFlags(obj) & StaticEditorFlags.ContributeGI) == 0) && areaLight==null)
{
mr.lightmapIndex = 0xFFFF;
continue; // skip dynamic
}
var mrEnabled = mr.enabled || obj.GetComponent<BakeryAlwaysRender>() != null;
if (!mrEnabled && areaLight == null) continue;
var so = new SerializedObject(obj.GetComponent<Renderer>());
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
BakeryLightmapGroup group = null;
if (scaleInLm > 0)
{
group = GetLMGroupFromObjectExplicit(obj, data);
if (group != null)
{
// Set LOD level for explicit group
int lodLevel;
if (!objToLodLevel.TryGetValue(obj, out lodLevel)) lodLevel = -1;
if (!postPacking)
{
if (group.sceneLodLevel == -1)
{
group.sceneLodLevel = lodLevel;
}
else
{
if (!ExportSceneValidationMessage("Multiple LOD levels in " + group.name + ", this is only supported when xatlas is set as the atlas packer and post-packing is enabled.")) return false;
}
}
if (splitByScene) group.sceneName = obj.scene.name;
// New explicit Pack Atlas holder selection
if (!group.isImplicit && group.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
lmgroupHolder = obj; // by default pack each object
lmgroupHolder = TestPackAsSingleSquare(lmgroupHolder);
var prefabParent = PrefabUtility.GetPrefabParent(obj) as GameObject;
if (prefabParent != null)
{
var ptype = PrefabUtility.GetPrefabType(prefabParent);
if (ptype == PrefabType.ModelPrefab)
{
// but if object is a part of prefab/model
var sharedMesh2 = GetSharedMesh(obj);
var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(sharedMesh2)) as ModelImporter;
if (importer != null && !ModelUVsOverlap(importer, gstorage))
{
// or actually just non-overlapping model,
// then pack it as a whole
// find topmost asset parent
var t = prefabParent.transform;
while(t.parent != null) t = t.parent;
var assetTopMost = t.gameObject;
// find topmost scene instance parent
var g = obj;
while(PrefabUtility.GetPrefabParent(g) as GameObject != assetTopMost && g.transform.parent != null)
{
g = g.transform.parent.gameObject;
}
lmgroupHolder = g;
}
}
}
}
}
}
else
{
if (data.autoVertexGroup == null)
{
data.autoVertexGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
data.autoVertexGroup.name = obj.scene.name + "_VLM";
data.autoVertexGroup.isImplicit = true;
data.autoVertexGroup.resolution = 256;
data.autoVertexGroup.bitmask = 1;
data.autoVertexGroup.mode = BakeryLightmapGroup.ftLMGroupMode.Vertex;
data.autoVertexGroup.id = data.lmid;
groupList.Add(data.autoVertexGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0)));
data.lmid++;
}
group = data.autoVertexGroup;
storages[sceneToID[obj.scene]].implicitGroupedObjects.Add(obj);
storages[sceneToID[obj.scene]].implicitGroups.Add(data.autoVertexGroup);
tempStorage.implicitGroupMap[obj] = data.autoVertexGroup;
storages[sceneToID[obj.scene]].nonBakedRenderers.Add(mr);
}
bool vertexBake = (group != null && group.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex);
// must have UVs or be arealight or vertexbaked
var uv = sharedMesh.uv;
var uv2 = sharedMesh.uv2;
if (uv.Length == 0 && uv2.Length == 0 && areaLight==null && !vertexBake) continue;
var usedUVs = uv2.Length == 0 ? uv : uv2;
//bool validUVs = true;
for(int v=0; v<usedUVs.Length; v++)
{
if (usedUVs[v].x < -0.0001f || usedUVs[v].x > 1.0001f || usedUVs[v].y < -0.0001f || usedUVs[v].y > 1.0001f)
{
Debug.LogWarning("Mesh " + sharedMesh.name + " on object " + obj.name + " possibly has incorrect UVs (UV2: " + (uv2.Length == 0 ? "no" : "yes")+", U: " + usedUVs[v].x + ", V: " + usedUVs[v].y + ")");
//validUVs = false;
break;
}
}
//if (!validUVs) continue;
if (vertexBake)
{
group.totalVertexCount = 0;
group.vertexCounter = 0;
}
objsToWrite.Add(obj);
objsToWriteNames.Add(obj.name);
objsToWriteLightmapped.Add((scaleInLm > 0 && areaLight == null) ? true : false);
objsToWriteGroup.Add(group);
objsToWriteHolder.Add(lmgroupHolder);
objsToWriteVerticesUV.Add(uv);
objsToWriteVerticesUV2.Add(uv2);
var inds = new int[sharedMesh.subMeshCount][];
for(int n=0; n<inds.Length; n++) inds[n] = sharedMesh.GetTriangles(n);
objsToWriteIndices.Add(inds);
}
return true;
}
static void CalculateVertexCountForVertexGroups(ExportSceneData data)
{
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
// Calculate total vertex count for vertex-baked groups
for(int i=0; i<objsToWrite.Count; i++)
{
var lmgroup = objsToWriteGroup[i];
if (lmgroup == null || lmgroup.mode != BakeryLightmapGroup.ftLMGroupMode.Vertex) continue;
var sharedMesh = GetSharedMesh(objsToWrite[i]);
lmgroup.totalVertexCount += sharedMesh.vertexCount;
}
}
static void CreateAutoAtlasLMGroups(ExportSceneData data, bool renderTextures, bool atlasOnly)
{
var objsToWrite = data.objsToWrite;
var objsToWriteLightmapped = data.objsToWriteLightmapped;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteHolder = data.objsToWriteHolder;
var objToLodLevel = data.objToLodLevel;
var storages = data.storages;
var sceneToID = data.sceneToID;
var groupList = data.groupList;
var lmBounds = data.lmBounds;
var autoAtlasGroups = data.autoAtlasGroups;
var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes;
// Create implicit temp LMGroups.
// If object is a part of prefab, and if UVs are not generated in Unity, group is only addded to the topmost object (aka holder).
// Implicit groups are added on every static object without ftLMGroupSelector.
// (Also init lmBounds and LMID as well)
// if autoAtlas == false: new group for every holder.
// if autoAtlas == true: single group for all holders (will be split later).
for(int i=0; i<objsToWrite.Count; i++)
{
if (!objsToWriteLightmapped[i]) continue; // skip objects with scaleInLM == 0
if (objsToWriteGroup[i] != null) continue; // skip if already has lightmap assigned
var obj = objsToWrite[i];
var holder = obj; // holder is object itself (packed individually)
holder = TestPackAsSingleSquare(holder);
var prefabParent = PrefabUtility.GetPrefabParent(obj) as GameObject;
if (prefabParent != null) // object is part of prefab
{
// unity doesn't generate non-overlapping UVs for the whole model, only submeshes
// // if importer == null, it's an actual prefab, not model <-- not really; importer points to mesh's prefab, not real
// importer of a mesh is always model asset
// importers of components never exist
// at least check the prefab type
var ptype = PrefabUtility.GetPrefabType(prefabParent);
if (ptype == PrefabType.ModelPrefab)
{
var sharedMesh = GetSharedMesh(obj);
var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(sharedMesh)) as ModelImporter;
if (importer != null && !ModelUVsOverlap(importer, gstorage))
{
// find topmost asset parent
var t = prefabParent.transform;
while(t.parent != null) t = t.parent;
var assetTopMost = t.gameObject;
// find topmost scene instance parent
var g = obj;
var assetG = PrefabUtility.GetPrefabParent(g) as GameObject;
while(assetG != assetTopMost && g.transform.parent != null && assetG.transform.parent != null)
{
var g2 = g.transform.parent.gameObject;
var assetG2 = assetG.transform.parent.gameObject;
if (PrefabUtility.GetPrefabParent(g2) != assetG2) break; // avoid using parents which don't belong to this model
g = g2;
assetG = assetG2;
}
var sceneTopMost = g;
holder = sceneTopMost; // holder is topmost model object (non-overlapped UVs)
int lodLevel;
if (objToLodLevel.TryGetValue(obj, out lodLevel)) holder = obj; // separated if used in LOD
}
}
}
else if (obj.name == "__ExportTerrain")
{
holder = obj.transform.parent.gameObject; // holder is terrain parent
int lodLevel;
if (objToLodLevel.TryGetValue(obj, out lodLevel)) holder = obj; // separated if used in LOD
}
if (!storages[sceneToID[holder.scene]].implicitGroupedObjects.Contains(holder))
{
BakeryLightmapGroup newGroup;
if (autoAtlas && autoAtlasGroups.Count > 0)
{
newGroup = autoAtlasGroups[0];
}
else
{
newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
// Make sure first lightmap is always LM0, not LM1, if probes are used
int lmNum = storages[sceneToID[holder.scene]].implicitGroups.Count;
if (ftRenderLightmap.lightProbeMode == ftRenderLightmap.LightProbeMode.L1 && ftRenderLightmap.hasAnyProbes && renderTextures && !atlasOnly) lmNum--;
newGroup.name = holder.scene.name + "_LM" + autoAtlasGroups.Count;//lmNum;
newGroup.isImplicit = true;
newGroup.resolution = 256;
newGroup.bitmask = 1;
newGroup.area = 0;
newGroup.mode = autoAtlas ? BakeryLightmapGroup.ftLMGroupMode.PackAtlas : BakeryLightmapGroup.ftLMGroupMode.OriginalUV;
newGroup.id = data.lmid;
groupList.Add(newGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0)));
data.lmid++;
if (autoAtlas)
{
autoAtlasGroups.Add(newGroup);
var rootNode = new AtlasNode();
rootNode.rc = new Rect(0, 0, 1, 1);
autoAtlasGroupRootNodes.Add(rootNode);
}
}
storages[sceneToID[holder.scene]].implicitGroupedObjects.Add(holder);
storages[sceneToID[holder.scene]].implicitGroups.Add(newGroup);
//Debug.LogError("Add "+(storages[sceneToID[holder.scene]].implicitGroups.Count-1)+" "+newGroup.name);
tempStorage.implicitGroupMap[holder] = newGroup;
if (splitByScene) newGroup.sceneName = holder.scene.name;
}
if (!tempStorage.implicitGroupMap.ContainsKey(holder))
{
// happens with modifyLightmapStorage == false
var gholders = storages[sceneToID[holder.scene]].implicitGroupedObjects;
var grs = storages[sceneToID[holder.scene]].implicitGroups;
for(int g=0; g<gholders.Count; g++)
{
if (gholders[g] == holder)
{
tempStorage.implicitGroupMap[holder] = grs[g];
break;
}
}
}
objsToWriteGroup[i] = (BakeryLightmapGroup)tempStorage.implicitGroupMap[holder];
objsToWriteHolder[i] = holder;
}
}
static void TransformVertices(ExportSceneData data, bool tangentSHLights, int onlyID = -1)
{
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW;
var objsToWriteVerticesNormalW = data.objsToWriteVerticesNormalW;
var objsToWriteVerticesTangentW = data.objsToWriteVerticesTangentW;
int startIndex = 0;
int endIndex = objsToWrite.Count-1;
if (onlyID >= 0)
{
startIndex = onlyID;
endIndex = onlyID;
}
// Transform vertices to world space
for(int i=startIndex; i<=endIndex; i++)
{
var obj = objsToWrite[i];
var lmgroup = objsToWriteGroup[i];
bool isSkin;
var m = GetSharedMeshSkinned(obj, out isSkin);
var vertices = m.vertices;
var tform = obj.transform;
while(objsToWriteVerticesPosW.Count <= i)
{
objsToWriteVerticesPosW.Add(null);
objsToWriteVerticesNormalW.Add(null);
}
objsToWriteVerticesPosW[i] = new Vector3[vertices.Length];
if (isSkin)
{
var lossyScale = tform.lossyScale;
var inverseWorldScale = new Vector3(1.0f/lossyScale.x, 1.0f/lossyScale.y, 1.0f/lossyScale.z);
for(int t=0; t<vertices.Length; t++)
{
vertices[t].Scale(inverseWorldScale);
}
}
for(int t=0; t<vertices.Length; t++)
{
objsToWriteVerticesPosW[i][t] = tform.TransformPoint(vertices[t]);
}
var normals = m.normals;
objsToWriteVerticesNormalW[i] = new Vector3[vertices.Length];
var nbuff = objsToWriteVerticesNormalW[i];
var localScale = obj.transform.localScale;
bool flipX = localScale.x < 0;
bool flipY = localScale.y < 0;
bool flipZ = localScale.z < 0;
if (lmgroup != null && lmgroup.flipNormal)
{
flipX = !flipX;
flipY = !flipY;
flipZ = !flipZ;
}
for(int t=0; t<vertices.Length; t++)
{
if (normals.Length == 0)
{
nbuff[t] = Vector3.up;
}
else
{
nbuff[t] = normals[t];
if (flipX) nbuff[t].x *= -1;
if (flipY) nbuff[t].y *= -1;
if (flipZ) nbuff[t].z *= -1;
nbuff[t] = tform.TransformDirection(nbuff[t]);
}
}
if (NeedsTangents(lmgroup, tangentSHLights))
{
var tangents = m.tangents;
while(objsToWriteVerticesTangentW.Count <= i) objsToWriteVerticesTangentW.Add(null);
objsToWriteVerticesTangentW[i] = new Vector4[vertices.Length];
var tbuff = objsToWriteVerticesTangentW[i];
Vector3 tangent = Vector3.zero;
for(int t=0; t<vertices.Length; t++)
{
if (tangents.Length == 0)
{
tbuff[t] = Vector3.right;
}
else
{
tangent.Set(flipX ? -tangents[t].x : tangents[t].x,
flipY ? -tangents[t].y : tangents[t].y,
flipZ ? -tangents[t].z : tangents[t].z);
tangent = tform.TransformDirection(tangent);
tbuff[t] = new Vector4(tangent.x, tangent.y, tangent.z, tangents[t].w);
}
}
}
}
}
static void CalculateUVPadding(ExportSceneData data, AdjustUVPaddingData adata)
{
var meshToPaddingMap = adata.meshToPaddingMap;
var meshToObjIDs = adata.meshToObjIDs;
float smallestMapScale = 1;
float colorScale = 1.0f / (1 << (int)((1.0f - ftBuildGraphics.mainLightmapScale) * 6));
float maskScale = 1.0f / (1 << (int)((1.0f - ftBuildGraphics.maskLightmapScale) * 6));
float dirScale = 1.0f / (1 << (int)((1.0f - ftBuildGraphics.dirLightmapScale) * 6));
smallestMapScale = Mathf.Min(colorScale, maskScale);
smallestMapScale = Mathf.Min(smallestMapScale, dirScale);
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW;
var objsToWriteIndices = data.objsToWriteIndices;
var objsToWriteHolder = data.objsToWriteHolder;
// Calculate every implicit mesh area and convert to proper padding value
var explicitGroupTotalArea = new Dictionary<int, float>();
var objsWithExplicitGroupPadding = new List<int>();
var objsWithExplicitGroupPaddingWidth = new List<float>();
for(int i=0; i<objsToWrite.Count; i++)
{
var lmgroup = objsToWriteGroup[i];
if (lmgroup == null) continue;
var prefabParent = PrefabUtility.GetPrefabParent(objsToWrite[i]) as GameObject;
if (prefabParent == null) continue;
var sharedMesh = GetSharedMesh(objsToWrite[i]);
var assetPath = AssetDatabase.GetAssetPath(sharedMesh);
var importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
if (importer == null || !importer.generateSecondaryUV) continue;
// user doesn't care much about UVs - adjust
var m = sharedMesh;
var vpos = objsToWriteVerticesPosW[i];
float area = 0;
var inds = objsToWriteIndices[i];
for(int k=0;k<m.subMeshCount;k++) {
var indices = inds[k];// m.GetTriangles(k);
int indexA, indexB, indexC;
for(int j=0;j<indices.Length;j+=3)
{
indexA = indices[j];
indexB = indices[j + 1];
indexC = indices[j + 2];
var v1 = vpos[indexA];
var v2 = vpos[indexB];
var v3 = vpos[indexC];
area += Vector3.Cross(v2 - v1, v3 - v1).magnitude;
}
}
var so = new SerializedObject(objsToWrite[i].GetComponent<Renderer>());
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
area *= scaleInLm;
float width = Mathf.Sqrt(area);
float twidth = 1;
if (lmgroup.isImplicit)
{
twidth = width * texelsPerUnit;
}
else
{
float currentArea;
if (!explicitGroupTotalArea.TryGetValue(lmgroup.id, out currentArea)) currentArea = 0;
explicitGroupTotalArea[lmgroup.id] = currentArea + area;
var holder = objsToWriteHolder[i];
BakeryLightmapGroupSelector comp = null;
if (holder != null) comp = holder.GetComponent<BakeryLightmapGroupSelector>();
if (comp != null && comp.instanceResolutionOverride)
{
// Explicit holder size
twidth = width * comp.instanceResolution;
}
else
{
// Texel size in atlas - can't calculate at this point
objsWithExplicitGroupPadding.Add(i);
objsWithExplicitGroupPaddingWidth.Add(width);
continue;
}
}
float requiredPadding = 4 * (1024.0f / (twidth * smallestMapScale));
int requiredPaddingClamped = (int)Mathf.Clamp(requiredPadding, 1, 256);
int existingPadding = 0;
meshToPaddingMap.TryGetValue(m, out existingPadding);
meshToPaddingMap[m] = Math.Max(requiredPaddingClamped, existingPadding); // select largest padding among instances
List<int> arr;
if (!meshToObjIDs.TryGetValue(m, out arr))
{
meshToObjIDs[m] = arr = new List<int>();
}
if (!arr.Contains(i)) arr.Add(i);
}
for(int j=0; j<objsWithExplicitGroupPadding.Count; j++)
{
int i = objsWithExplicitGroupPadding[j];
float width = objsWithExplicitGroupPaddingWidth[j];
var lmgroup = objsToWriteGroup[i];
float totalArea = explicitGroupTotalArea[lmgroup.id];
float twidth = (width / Mathf.Sqrt(totalArea)) * lmgroup.resolution;
var m = GetSharedMesh(objsToWrite[i]);
// Following is copy-pasted from the loop above
float requiredPadding = 4 * (1024.0f / (twidth * smallestMapScale));
int requiredPaddingClamped = (int)Mathf.Clamp(requiredPadding, 1, 256);
int existingPadding = 0;
meshToPaddingMap.TryGetValue(m, out existingPadding);
meshToPaddingMap[m] = Math.Max(requiredPaddingClamped, existingPadding); // select largest padding among instances
List<int> arr;
if (!meshToObjIDs.TryGetValue(m, out arr))
{
meshToObjIDs[m] = arr = new List<int>();
}
if (!arr.Contains(i)) arr.Add(i);
}
}
static void ResetPaddingStorageData(ExportSceneData data)
{
var storages = data.storages;
// Reset scene padding backup
for(int s=0; s<storages.Length; s++)
{
var str = storages[s];
if (str == null) continue;
str.modifiedAssetPathList = new List<string>();
str.modifiedAssets = new List<ftGlobalStorage.AdjustedMesh>();
}
}
static void StoreNewUVPadding(ExportSceneData data, AdjustUVPaddingData adata)
{
var meshToPaddingMap = adata.meshToPaddingMap;
var meshToObjIDs = adata.meshToObjIDs;
var dirtyAssetList = adata.dirtyAssetList;
var dirtyObjList = adata.dirtyObjList;
var storages = data.storages;
foreach(var pair in meshToPaddingMap)
{
var m = pair.Key;
var requiredPaddingClamped = pair.Value;
var assetPath = AssetDatabase.GetAssetPath(m);
var ids = meshToObjIDs[m];
//for(int s=0; s<sceneCount; s++)
{
var objStorage = gstorage;// == null ? storages[0] : gstorage;// storages[s];
int mstoreIndex = objStorage.modifiedAssetPathList.IndexOf(assetPath);
int ind = -1;
var mname = m.name;
if (mstoreIndex >= 0) ind = objStorage.modifiedAssets[mstoreIndex].meshName.IndexOf(mname);
if (ind < 0)
{
if (mstoreIndex < 0)
{
// add new record to globalstorage
objStorage.modifiedAssetPathList.Add(assetPath);
var newStruct = new ftGlobalStorage.AdjustedMesh();
newStruct.meshName = new List<string>();
newStruct.padding = new List<int>();
objStorage.modifiedAssets.Add(newStruct);
mstoreIndex = objStorage.modifiedAssets.Count - 1;
}
var nameList = objStorage.modifiedAssets[mstoreIndex].meshName;
var paddingList = objStorage.modifiedAssets[mstoreIndex].padding;
var unwrapperList = objStorage.modifiedAssets[mstoreIndex].unwrapper;
if (unwrapperList == null)
{
var s = objStorage.modifiedAssets[mstoreIndex];
unwrapperList = s.unwrapper = new List<int>();
objStorage.modifiedAssets[mstoreIndex] = s;
}
while(nameList.Count > unwrapperList.Count) unwrapperList.Add(0); // fix legacy
nameList.Add(mname);
paddingList.Add(requiredPaddingClamped);
unwrapperList.Add((int)ftRenderLightmap.unwrapper);
if (!dirtyAssetList.Contains(assetPath)) dirtyAssetList.Add(assetPath);
for(int xx=0; xx<ids.Count; xx++) dirtyObjList.Add(ids[xx]);
#if UNITY_2017_1_OR_NEWER
objStorage.SyncModifiedAsset(mstoreIndex);
#endif
}
else
{
var nameList = objStorage.modifiedAssets[mstoreIndex].meshName;
var paddingList = objStorage.modifiedAssets[mstoreIndex].padding;
var unwrapperList = objStorage.modifiedAssets[mstoreIndex].unwrapper;
if (unwrapperList == null)
{
var s = objStorage.modifiedAssets[mstoreIndex];
unwrapperList = s.unwrapper = new List<int>();
objStorage.modifiedAssets[mstoreIndex] = s;
}
while(nameList.Count > unwrapperList.Count) unwrapperList.Add(0); // fix legacy
// modify existing record
var oldValue = paddingList[ind];
var oldUnwrapperValue = (ftGlobalStorage.Unwrapper)unwrapperList[ind];
bool shouldModify = oldValue != requiredPaddingClamped;
if (uvPaddingMax)
{
shouldModify = oldValue < requiredPaddingClamped;
}
if (oldUnwrapperValue != ftRenderLightmap.unwrapper) shouldModify = true;
if (shouldModify)
{
if (!dirtyAssetList.Contains(assetPath)) dirtyAssetList.Add(assetPath);
for(int xx=0; xx<ids.Count; xx++) dirtyObjList.Add(ids[xx]);
paddingList[ind] = requiredPaddingClamped;
unwrapperList[ind] = (int)ftRenderLightmap.unwrapper;
#if UNITY_2017_1_OR_NEWER
objStorage.SyncModifiedAsset(mstoreIndex);
#endif
}
}
// Backup padding storage to scene
for(int s=0; s<storages.Length; s++)
{
var str = storages[s];
if (str == null) continue;
var localIndex = str.modifiedAssetPathList.IndexOf(assetPath);
if (localIndex < 0)
{
str.modifiedAssetPathList.Add(assetPath);
str.modifiedAssets.Add(objStorage.modifiedAssets[mstoreIndex]);
}
else
{
str.modifiedAssets[localIndex] = objStorage.modifiedAssets[mstoreIndex];
}
}
}
}
EditorUtility.SetDirty(gstorage);
}
static bool ValidatePaddingImmutability(AdjustUVPaddingData adata)
{
if (validateLightmapStorageImmutability)
{
if (adata.dirtyAssetList.Count > 0)
{
sceneNeedsToBeRebuilt = true;
return false;
}
}
return true;
}
static bool ValidateScaleOffsetImmutability(ExportSceneData data)
{
if (validateLightmapStorageImmutability)
{
var holderRect = data.holderRect;
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteHolder = data.objsToWriteHolder;
var storages = data.storages;
var sceneToID = data.sceneToID;
var emptyVec4 = new Vector4(1,1,0,0);
Rect rc = new Rect();
for(int i=0; i<objsToWrite.Count; i++)
{
var obj = objsToWrite[i];
var lmgroup = objsToWriteGroup[i];
var holderObj = objsToWriteHolder[i];
if (holderObj != null)
{
if (!holderRect.TryGetValue(holderObj, out rc))
{
holderObj = null;
}
}
var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y);
var sceneID = sceneToID[obj.scene];
var st = storages[sceneID];
if (st == null)
{
Debug.LogError("ValidateScaleOffsetImmutability: no storage");
return false;
}
var storedScaleOffset = Vector4.zero;
if (obj.name == "__ExportTerrain")
{
var tindex = terrainObjectList.IndexOf(obj.transform.parent.gameObject);
var terrain = terrainObjectToActual[tindex];
int index = st.bakedRenderersTerrain.IndexOf(terrain);
/*if (st.bakedIDsTerrain[index] != lmgroup.id)
{
Debug.LogError("ValidateScaleOffsetImmutability: terrain LMID does not match");
return false;
}*/
if (index < 0 || st.bakedScaleOffsetTerrain.Count <= index) continue;
storedScaleOffset = st.bakedScaleOffsetTerrain[index];
}
else
{
int index = st.bakedRenderers.IndexOf(obj.GetComponent<Renderer>());
/*if (st.bakedIDs[index] != lmgroup.id)
{
Debug.LogError("ValidateScaleOffsetImmutability: LMID does not match");
Debug.LogError(st.bakedIDs[index]+" "+lmgroup.id+" "+lmgroup.name);
return false;
}*/
if (index < 0 || st.bakedScaleOffset.Count <= index) continue;
storedScaleOffset = st.bakedScaleOffset[index];
}
// approx equality
if (!(scaleOffset == storedScaleOffset))
{
Debug.LogError("ValidateScaleOffsetImmutability: scale/offset does not match");
return false;
}
}
}
return true;
}
static bool ClearUVPadding(ExportSceneData data, AdjustUVPaddingData adata)
{
var objsToWrite = data.objsToWrite;
var dirtyAssetList = adata.dirtyAssetList;
var dirtyObjList = adata.dirtyObjList;
for(int i=0; i<objsToWrite.Count; i++)
{
var sharedMesh = GetSharedMesh(objsToWrite[i]);
var assetPath = AssetDatabase.GetAssetPath(sharedMesh);
int mstoreIndex = gstorage.modifiedAssetPathList.IndexOf(assetPath);
if (mstoreIndex < 0) continue;
dirtyObjList.Add(i);
if (!dirtyAssetList.Contains(assetPath))
{
dirtyAssetList.Add(assetPath);
}
}
if (!ValidatePaddingImmutability(adata)) return false;
for(int i=0; i<dirtyAssetList.Count; i++)
{
var assetPath = dirtyAssetList[i];
int mstoreIndex = gstorage.modifiedAssetPathList.IndexOf(assetPath);
Debug.Log("Reimport " + assetPath);
ProgressBarShow("Exporting scene - clearing UV adjustment for " + assetPath + "...", 0);
gstorage.ClearAssetModifications(mstoreIndex);
}
EditorUtility.SetDirty(gstorage);
return true;
}
static bool ReimportModifiedAssets(AdjustUVPaddingData adata)
{
var dirtyAssetList = adata.dirtyAssetList;
for(int i=0; i<dirtyAssetList.Count; i++)
{
var assetPath = dirtyAssetList[i];
Debug.Log("Reimport " + assetPath);
ProgressBarShow("Exporting scene - adjusting UV padding for " + assetPath + "...", 0);
//AssetDatabase.ImportAsset(assetPath);
(AssetImporter.GetAtPath(assetPath) as ModelImporter).SaveAndReimport();
if (CheckUnwrapError())
{
return false;
}
}
return true;
}
static void TransformModifiedAssets(ExportSceneData data, AdjustUVPaddingData adata, bool tangentSHLights)
{
// Transform modified vertices to world space again
for(int d=0; d<adata.dirtyObjList.Count; d++)
{
int i = adata.dirtyObjList[d];
// Refresh attributes and indices after reimport
bool isSkin;
var m = GetSharedMeshSkinned(data.objsToWrite[i], out isSkin);
data.objsToWriteVerticesUV[i] = m.uv;
data.objsToWriteVerticesUV2[i] = m.uv2;
var inds = new int[m.subMeshCount][];
for(int n=0; n<inds.Length; n++) inds[n] = m.GetTriangles(n);
data.objsToWriteIndices[i] = inds;
TransformVertices(data, tangentSHLights, i); // because vertex count/order could be modified
}
}
static void CalculateHolderUVBounds(ExportSceneData data)
{
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteHolder = data.objsToWriteHolder;
var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW;
var objsToWriteVerticesUV = data.objsToWriteVerticesUV;
var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2;
var objsToWriteIndices = data.objsToWriteIndices;
var objToLodLevel = data.objToLodLevel;
var holderObjUVBounds = data.holderObjUVBounds;
var holderObjArea = data.holderObjArea;
var groupToHolderObjects = data.groupToHolderObjects;
// Calculate implicit group / atlas packing data
// UV bounds and worldspace area
for(int i=0; i<objsToWrite.Count; i++)
{
var obj = objsToWrite[i];
var lmgroup = objsToWriteGroup[i];
var calculateArea = lmgroup == null ? false : (lmgroup.isImplicit || lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas);
if (!calculateArea) continue;
var holderObj = objsToWriteHolder[i];
var m = GetSharedMesh(obj);
var mr = obj.GetComponent<Renderer>();
var vpos = objsToWriteVerticesPosW[i];
var vuv = objsToWriteVerticesUV2[i];//m.uv2;
var inds = objsToWriteIndices[i];
//if (vuv.Length == 0 || obj.GetComponent<BakeryLightMesh>()!=null) vuv = objsToWriteVerticesUV[i];//m.uv; // area lights or objects without UV2 export UV1 instead
if (vuv.Length == 0 || obj.GetComponent<BakeryLightMesh>()!=null || temporaryAreaLightMeshList.Contains(obj)) vuv = objsToWriteVerticesUV[i];//m.uv; // area lights or objects without UV2 export UV1 instead
Vector2 uv1 = Vector2.zero;
Vector2 uv2 = Vector2.zero;
Vector2 uv3 = Vector2.zero;
int lodLevel;
if (!objToLodLevel.TryGetValue(obj, out lodLevel)) lodLevel = -1;
for(int k=0;k<m.subMeshCount;k++) {
var indices = inds[k];//m.GetTriangles(k);
int indexA, indexB, indexC;
float area = 0;
//float areaUV = 0;
Vector4 uvBounds = new Vector4(1,1,0,0); // minx, miny, maxx, maxy
for(int j=0;j<indices.Length;j+=3)
{
indexA = indices[j];
indexB = indices[j + 1];
indexC = indices[j + 2];
var v1 = vpos[indexA];
var v2 = vpos[indexB];
var v3 = vpos[indexC];
area += Vector3.Cross(v2 - v1, v3 - v1).magnitude;
if (vuv.Length > 0)
{
uv1 = vuv[indexA];
uv2 = vuv[indexB];
uv3 = vuv[indexC];
}
/*var uv31 = new Vector3(uv1.x, uv1.y, 0);
var uv32 = new Vector3(uv2.x, uv2.y, 0);
var uv33 = new Vector3(uv3.x, uv3.y, 0);
areaUV += Vector3.Cross(uv32 - uv31, uv33 - uv31).magnitude;*/
if (uv1.x < uvBounds.x) uvBounds.x = uv1.x;
if (uv1.y < uvBounds.y) uvBounds.y = uv1.y;
if (uv1.x > uvBounds.z) uvBounds.z = uv1.x;
if (uv1.y > uvBounds.w) uvBounds.w = uv1.y;
if (uv2.x < uvBounds.x) uvBounds.x = uv2.x;
if (uv2.y < uvBounds.y) uvBounds.y = uv2.y;
if (uv2.x > uvBounds.z) uvBounds.z = uv2.x;
if (uv2.y > uvBounds.w) uvBounds.w = uv2.y;
if (uv3.x < uvBounds.x) uvBounds.x = uv3.x;
if (uv3.y < uvBounds.y) uvBounds.y = uv3.y;
if (uv3.x > uvBounds.z) uvBounds.z = uv3.x;
if (uv3.y > uvBounds.w) uvBounds.w = uv3.y;
}
// uv layouts always have empty spaces
//area /= areaUV;
var so = new SerializedObject(mr);
var scaleInLm = so.FindProperty("m_ScaleInLightmap").floatValue;
area *= scaleInLm;
if (lmgroup.isImplicit && lodLevel == -1)
{
lmgroup.area += area; // accumulate LMGroup area
// only use base scene values, no LODs, to properly initialize autoatlas size
}
if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
// Accumulate per-holder area and UV bounds
float existingArea;
Vector4 existingBounds;
holderObjUVBounds.TryGetValue(holderObj, out existingBounds);
if (!holderObjArea.TryGetValue(holderObj, out existingArea))
{
existingArea = 0;
existingBounds = uvBounds;
List<GameObject> holderList;
if (!groupToHolderObjects.TryGetValue(lmgroup, out holderList))
{
groupToHolderObjects[lmgroup] = holderList = new List<GameObject>();
}
holderList.Add(holderObj);
}
holderObjArea[holderObj] = existingArea + area;
existingBounds.x = existingBounds.x < uvBounds.x ? existingBounds.x : uvBounds.x;
existingBounds.y = existingBounds.y < uvBounds.y ? existingBounds.y : uvBounds.y;
existingBounds.z = existingBounds.z > uvBounds.z ? existingBounds.z : uvBounds.z;
existingBounds.w = existingBounds.w > uvBounds.w ? existingBounds.w : uvBounds.w;
holderObjUVBounds[holderObj] = existingBounds;
}
}
}
}
static int ResolutionFromArea(float area)
{
int resolution = (int)(Mathf.Sqrt(area) * texelsPerUnit);
if (mustBePOT)
{
if (atlasCountPriority)
{
resolution = Mathf.NextPowerOfTwo(resolution);
}
else
{
resolution = Mathf.ClosestPowerOfTwo(resolution);
}
}
resolution = Math.Max(resolution, minAutoResolution);
resolution = Math.Min(resolution, maxAutoResolution);
return resolution;
}
static void CalculateAutoAtlasInitResolution(ExportSceneData data)
{
var groupList = data.groupList;
// Calculate implicit lightmap resolution
for(int i=0; i<groupList.Count; i++)
{
var lmgroup = groupList[i];
if (lmgroup.isImplicit)
{
lmgroup.resolution = ResolutionFromArea(lmgroup.area);
}
}
}
static void NormalizeHolderArea(BakeryLightmapGroup lmgroup, List<GameObject> holderObjs, ExportSceneData data)
{
var holderObjArea = data.holderObjArea;
var holderObjUVBounds = data.holderObjUVBounds;
// Divide holders area to get from world space to -> UV space
float areaMult = 1.0f;
if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
// ...by maximum lightmap area given texel size (autoAtlas)
//areaMult = 1.0f / lightmapMaxArea;
// don't modify
}
else
{
// ... by maximum holder area (normalize)
float lmgroupArea = 0;
for(int i=0; i<holderObjs.Count; i++)
{
// space outside of UV bounds shouldn't affect area
var uvbounds = holderObjUVBounds[holderObjs[i]];
var width = uvbounds.z - uvbounds.x;
var height = uvbounds.w - uvbounds.y;
float uvboundsArea = width * height;
lmgroupArea += holderObjArea[holderObjs[i]] * uvboundsArea;
}
areaMult = 1.0f / lmgroupArea;
}
// Perform the division and sum up total UV area
for(int i=0; i<holderObjs.Count; i++)
{
holderObjArea[holderObjs[i]] *= areaMult;
}
}
static void SumHolderAreaPerLODLevel(List<GameObject> holderObjs, ExportSceneData data, PackData pdata)
{
var objToLodLevel = data.objToLodLevel;
var holderObjArea = data.holderObjArea;
var remainingAreaPerLodLevel = pdata.remainingAreaPerLodLevel;
for(int i=0; i<holderObjs.Count; i++)
{
int lodLevel = -1;
if (!objToLodLevel.TryGetValue(holderObjs[i], out lodLevel)) lodLevel = -1;
float lodArea = 0;
if (!remainingAreaPerLodLevel.TryGetValue(lodLevel, out lodArea)) lodArea = 0;
remainingAreaPerLodLevel[lodLevel] = lodArea + holderObjArea[holderObjs[i]];
}
}
static int CompareGameObjectsForPacking(GameObject a, GameObject b)
{
if (splitByScene)
{
if (a.scene.name != b.scene.name) return a.scene.name.CompareTo(b.scene.name);
}
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
bool ba = a.name != "__ExportTerrainParent";
bool bb = b.name != "__ExportTerrainParent";
if (ba != bb) return ba.CompareTo(bb);
}
int lodLevelA = -1;
int lodLevelB = -1;
if (!cmp_objToLodLevel.TryGetValue(a, out lodLevelA)) lodLevelA = -1;
if (!cmp_objToLodLevel.TryGetValue(b, out lodLevelB)) lodLevelB = -1;
if (lodLevelA != lodLevelB) return lodLevelA.CompareTo(lodLevelB);
float areaA = cmp_holderObjArea[a];
float areaB = cmp_holderObjArea[b];
// Workaround for "override resolution"
// Always pack such rectangles first
var comp = a.GetComponent<BakeryLightmapGroupSelector>();
if (comp != null && comp.instanceResolutionOverride) areaA = comp.instanceResolution * 10000;
comp = b.GetComponent<BakeryLightmapGroupSelector>();
if (comp != null && comp.instanceResolutionOverride) areaB = comp.instanceResolution * 10000;
return areaB.CompareTo(areaA);
}
static void ApplyAreaToUVBounds(float area, Vector4 uvbounds, out float width, out float height)
{
width = height = Mathf.Sqrt(area);
float uwidth = uvbounds.z - uvbounds.x;
float uheight = uvbounds.w - uvbounds.y;
if (uwidth == 0 && uheight == 0)
{
width = height = 0;
}
else
{
float uvratio = uheight / uwidth;
if (uvratio <= 1.0f)
{
width /= uvratio;
//height *= uvratio;
}
else
{
height *= uvratio;
//width /= uvratio;
}
}
}
static bool Pack(BakeryLightmapGroup lmgroup, List<GameObject> holderObjs, ExportSceneData data, PackData pdata)
{
var holderObjArea = data.holderObjArea;
var holderObjUVBounds = data.holderObjUVBounds;
var holderRect = data.holderRect;
var objToLodLevel = data.objToLodLevel;
var groupList = data.groupList;
var lmBounds = data.lmBounds;
var autoAtlasGroups = data.autoAtlasGroups;
var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes;
var remainingAreaPerLodLevel = pdata.remainingAreaPerLodLevel;
//Debug.LogError("repack: "+repackScale);
pdata.repack = false;
AtlasNode rootNode;
if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas && autoAtlasGroupRootNodes != null && autoAtlasGroupRootNodes.Count > 0)
{
rootNode = autoAtlasGroupRootNodes[0];
}
else
{
rootNode = new AtlasNode();
}
rootNode.rc = new Rect(0, 0, 1, 1);
for(int i=0; i<holderObjs.Count; i++)
{
var area = holderObjArea[holderObjs[i]];
var uvbounds = holderObjUVBounds[holderObjs[i]];
// Calculate width and height of each holder in atlas UV space
float width, height;
var comp = holderObjs[i].GetComponent<BakeryLightmapGroupSelector>();
if (comp != null && comp.instanceResolutionOverride)
{
// Explicit holder size
pdata.hasResOverrides = true;
width = height = comp.instanceResolution / (float)lmgroup.resolution;
}
else
{
// Automatic: width and height = sqrt(area) transformed by UV AABB aspect ratio
ApplyAreaToUVBounds(area, uvbounds, out width, out height);
}
// Clamp to full lightmap size
float twidth = width;
float theight = height;
if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
twidth = (width * texelsPerUnit) / lmgroup.resolution;
theight = (height * texelsPerUnit) / lmgroup.resolution;
//if (i==0) Debug.LogError(texelsPerUnit+" "+twidth);
}
//float unclampedTwidth = twidth;
//float unclampedTheight = twidth;
if (comp != null && comp.instanceResolutionOverride)
{
}
else
{
twidth *= pdata.repackScale;
theight *= pdata.repackScale;
}
twidth = twidth > 1 ? 1 : twidth;
theight = theight > 1 ? 1 : theight;
twidth = Mathf.Max(twidth, 1.0f / lmgroup.resolution);
theight = Mathf.Max(theight, 1.0f / lmgroup.resolution);
var rect = new Rect(0, 0, twidth, theight);
if (float.IsNaN(twidth) || float.IsNaN(theight))
{
ExportSceneError("NaN UVs detected for " + holderObjs[i].name+" "+rect.width+" "+rect.height+" "+width+" "+height+" "+lmgroup.resolution+" "+area+" "+(uvbounds.z - uvbounds.x)+" "+(uvbounds.w - uvbounds.y));
return false;
}
// Try inserting this rect
// Break autoatlas if lod level changes
// Optionally break autoatlas if scene changes
AtlasNode node = null;
int lodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[i], out lodLevel)) lodLevel = -1;
bool splitAtlas = false;
if (splitByScene)
{
if (holderObjs[i].scene.name != lmgroup.sceneName)
{
splitAtlas = true;
}
}
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
bool ba = holderObjs[i].name == "__ExportTerrainParent";
if (ba) lmgroup.containsTerrains = true;
if (i > 0)
{
bool bb = holderObjs[i-1].name == "__ExportTerrainParent";
if (ba != bb)
{
splitAtlas = true;
}
}
}
if (!splitAtlas)
{
if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
if (lodLevel == lmgroup.sceneLodLevel)
{
node = rootNode.Insert(holderObjs[i], rect);
}
}
else
{
node = rootNode.Insert(holderObjs[i], rect);
}
}
/*if (node!=null)
{
Debug.Log(holderObjs[i].name+" goes straight into "+lmgroup.name);
}*/
if (node == null)
{
if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
// Can't fit - try other autoAtlas lightmaps
BakeryLightmapGroup newGroup = null;
var holder = holderObjs[i];
int goodGroup = -1;
for(int g=1; g<autoAtlasGroups.Count; g++)
{
if (splitByScene)
{
if (autoAtlasGroups[g].sceneName != holderObjs[i].scene.name) continue;
}
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
bool ba = holderObjs[i].name != "__ExportTerrainParent";
bool bb = !autoAtlasGroups[g].containsTerrains;
if (ba != bb) continue;
}
if (autoAtlasGroups[g].sceneLodLevel != lodLevel) continue;
twidth = (width * texelsPerUnit) / autoAtlasGroups[g].resolution;
theight = (height * texelsPerUnit) / autoAtlasGroups[g].resolution;
//unclampedTwidth = twidth;
//unclampedTheight = twidth;
twidth = twidth > 1 ? 1 : twidth;
theight = theight > 1 ? 1 : theight;
rect = new Rect(0, 0, twidth, theight);
node = autoAtlasGroupRootNodes[g].Insert(holder, rect);
if (node != null)
{
//Debug.Log(holder.name+" fits into "+autoAtlasGroups[g].name);
newGroup = autoAtlasGroups[g];
goodGroup = g;
break;
}
}
// Can't fit - create new lightmap (autoAtlas)
if (goodGroup < 0)
{
newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
newGroup.name = holder.scene.name + "_LMA" + autoAtlasGroups.Count;
newGroup.isImplicit = true;
newGroup.sceneLodLevel = lodLevel;
if (splitByScene) newGroup.sceneName = holderObjs[i].scene.name;
//Debug.Log(holder.name+" creates "+newGroup.name);
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
newGroup.containsTerrains = holderObjs[i].name == "__ExportTerrainParent";
}
newGroup.resolution = (int)(Mathf.Sqrt(remainingAreaPerLodLevel[lodLevel]) * texelsPerUnit);
if (mustBePOT)
{
if (atlasCountPriority)
{
newGroup.resolution = Mathf.NextPowerOfTwo(newGroup.resolution);
}
else
{
newGroup.resolution = Mathf.ClosestPowerOfTwo(newGroup.resolution);
}
}
newGroup.resolution = Math.Max(newGroup.resolution, minAutoResolution);
newGroup.resolution = Math.Min(newGroup.resolution, maxAutoResolution);
newGroup.bitmask = 1;
newGroup.area = 0;
newGroup.mode = BakeryLightmapGroup.ftLMGroupMode.PackAtlas;
newGroup.id = data.lmid;
groupList.Add(newGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0)));
data.lmid++;
autoAtlasGroups.Add(newGroup);
var rootNode2 = new AtlasNode();
rootNode2.rc = new Rect(0, 0, 1, 1);
autoAtlasGroupRootNodes.Add(rootNode2);
twidth = (width * texelsPerUnit) / newGroup.resolution;
theight = (height * texelsPerUnit) / newGroup.resolution;
//unclampedTwidth = twidth;
//unclampedTheight = twidth;
twidth = twidth > 1 ? 1 : twidth;
theight = theight > 1 ? 1 : theight;
rect = new Rect(0, 0, twidth, theight);
node = rootNode2.Insert(holder, rect);
}
// Modify implicit group storage
MoveObjectToImplicitGroup(holder, newGroup, data);
/*
var scn = holder.scene;
tempStorage.implicitGroupMap[holder] = newGroup;
for(int k=0; k<storages[sceneToID[holder.scene]].implicitGroupedObjects.Count; k++)
{
if (storages[sceneToID[holder.scene]].implicitGroupedObjects[k] == holder)
{
storages[sceneToID[holder.scene]].implicitGroups[k] = newGroup;
//Debug.LogError("Implicit set: " + k+" "+newGroup.name+" "+holder.name);
}
}
*/
//lmgroup = newGroup;
}
else
{
if (!pdata.repackStage2)
{
// explicit packed atlas - can't fit - try shrinking the whole atlas
pdata.repackTries++;
if (pdata.repackTries < atlasMaxTries)
{
pdata.repack = true;
pdata.repackScale *= 0.75f;
//Debug.LogError("Can't fit, set " +repackScale);
break;
}
}
else
{
// explicit packed atlas - did fit, now trying to scale up, doesn't work - found optimal fit
pdata.repack = true;
pdata.repackScale /= atlasScaleUpValue;//*= 0.75f;
//Debug.LogError("Final, set " +repackScale);
pdata.finalRepack = true;
pdata.repackTries = 0;
break;
}
}
}
if (node == null)
{
// No way to fit
ExportSceneError("Can't fit " + holderObjs[i].name+" "+rect.width+" "+rect.height);
return false;
}
else
{
// Generate final rectangle to transform local UV -> atlas UV
float padding = ((float)atlasPaddingPixels) / lmgroup.resolution;
var paddedRc = new Rect(node.rc.x + padding,
node.rc.y + padding,
node.rc.width - padding * 2,
node.rc.height - padding * 2);
paddedRc.x -= uvbounds.x * (paddedRc.width / (uvbounds.z - uvbounds.x));
paddedRc.y -= uvbounds.y * (paddedRc.height / (uvbounds.w - uvbounds.y));
paddedRc.width /= uvbounds.z - uvbounds.x;
paddedRc.height /= uvbounds.w - uvbounds.y;
holderRect[holderObjs[i]] = paddedRc;
}
//float areaReduction = (twidth*theight) / (unclampedTwidth*unclampedTheight);
remainingAreaPerLodLevel[lodLevel] -= area;// * areaReduction;
}
if (!lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
if (pdata.finalRepack && pdata.repack)
{
pdata.continueRepack = true;
return true;
}
if (pdata.finalRepack)
{
pdata.continueRepack = false;
return true;
}
if (!pdata.repack && !pdata.repackStage2)
{
//if (repackTries > 0) break; // shrinked down just now - don't scale up
pdata.repackStage2 = true; // scale up now
pdata.repack = true;
pdata.repackScale *= atlasScaleUpValue;///= 0.75f;
pdata.repackTries = 0;
//Debug.LogError("Scale up, set " +repackScale);
}
else if (pdata.repackStage2)
{
pdata.repackTries++;
if (pdata.repackTries == atlasMaxTries)
{
pdata.continueRepack = false;
return true;
}
pdata.repack = true;
pdata.repackScale *= atlasScaleUpValue;///= 0.75f;
//Debug.LogError("Scale up cont, set " +repackScale);
}
}
return true;
}
static void MoveObjectToImplicitGroup(GameObject holder, BakeryLightmapGroup newGroup, ExportSceneData data)
{
var storages = data.storages;
var sceneToID = data.sceneToID;
// Modify implicit group storage
var scn = holder.scene;
tempStorage.implicitGroupMap[holder] = newGroup;
for(int k=0; k<storages[sceneToID[holder.scene]].implicitGroupedObjects.Count; k++)
{
if (storages[sceneToID[holder.scene]].implicitGroupedObjects[k] == holder)
{
storages[sceneToID[holder.scene]].implicitGroups[k] = newGroup;
//Debug.LogError("Implicit set: " + k+" "+newGroup.name+" "+holder.name);
}
}
}
static List<int> GetAtlasBucketRanges(List<GameObject> holderObjs, ExportSceneData data, bool onlyUserSplits)
{
var objToLodLevel = data.objToLodLevel;
var ranges = new List<int>();
int start = 0;
int end = 0;
if (holderObjs.Count > 0)
{
var sceneName = holderObjs[0].scene.name;
int lodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[0], out lodLevel)) lodLevel = -1;
bool isTerrain = holderObjs[0].name == "__ExportTerrainParent";
for(int i=0; i<holderObjs.Count; i++)
{
bool splitAtlas = false;
// Split by scene
if (splitByScene)
{
var objSceneName = holderObjs[i].scene.name;
if (objSceneName != sceneName)
{
splitAtlas = true;
sceneName = objSceneName;
}
}
if (!onlyUserSplits)
{
// Split by LOD
int objLodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[i], out objLodLevel)) objLodLevel = -1;
if (objLodLevel != lodLevel)
{
lodLevel = objLodLevel;
splitAtlas = true;
}
// Split by terrain
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
bool ba = holderObjs[i].name == "__ExportTerrainParent";
if (ba != isTerrain)
{
isTerrain = ba;
splitAtlas = true;
}
}
}
if (splitAtlas)
{
end = i;
ranges.Add(start);
ranges.Add(end-1);
start = end;
}
}
}
end = holderObjs.Count-1;
ranges.Add(start);
ranges.Add(end);
return ranges;
}
static float SumObjectsArea(List<GameObject> holderObjs, int start, int end, ExportSceneData data)
{
var holderObjArea = data.holderObjArea;
float area = 0;
for(int i=start; i<=end; i++)
{
area += holderObjArea[holderObjs[i]];
}
return area;
}
static BakeryLightmapGroup AllocateAutoAtlas(int count, BakeryLightmapGroup lmgroup, ExportSceneData data, int[] atlasSizes = null)
{
var lmBounds = data.lmBounds;
var groupList = data.groupList;
var autoAtlasGroups = data.autoAtlasGroups;
var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes;
BakeryLightmapGroup newGroup = null;
for(int i=0; i<count; i++)
{
// Create additional lightmaps
newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
newGroup.name = lmgroup.sceneName + "_LMA" + autoAtlasGroups.Count;
newGroup.isImplicit = true;
newGroup.sceneLodLevel = lmgroup.sceneLodLevel;
if (splitByScene) newGroup.sceneName = lmgroup.sceneName;
newGroup.containsTerrains = lmgroup.containsTerrains;
newGroup.resolution = atlasSizes != null ? atlasSizes[i] : lmgroup.resolution;
newGroup.bitmask = 1;
newGroup.area = 0;
newGroup.mode = lmgroup.mode;// BakeryLightmapGroup.ftLMGroupMode.PackAtlas;
newGroup.renderMode = lmgroup.renderMode;
newGroup.renderDirMode = lmgroup.renderDirMode;
newGroup.atlasPacker = lmgroup.atlasPacker;
newGroup.computeSSS = lmgroup.computeSSS;
newGroup.sssSamples = lmgroup.sssSamples;
newGroup.sssDensity = lmgroup.sssDensity;
newGroup.sssColor = lmgroup.sssColor;
newGroup.fakeShadowBias = lmgroup.fakeShadowBias;
newGroup.transparentSelfShadow = lmgroup.transparentSelfShadow;
newGroup.flipNormal = lmgroup.flipNormal;
newGroup.id = data.lmid;
groupList.Add(newGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0)));
data.lmid++;
autoAtlasGroups.Add(newGroup);
var rootNode2 = new AtlasNode();
rootNode2.rc = new Rect(0, 0, 1, 1);
autoAtlasGroupRootNodes.Add(rootNode2);
}
return newGroup;
}
static bool PackWithXatlas(BakeryLightmapGroup lmgroup, List<GameObject> holderObjs, ExportSceneData data, PackData pdata)
{
var holderObjArea = data.holderObjArea;
var holderObjUVBounds = data.holderObjUVBounds;
var holderRect = data.holderRect;
var autoAtlasGroups = data.autoAtlasGroups;
var objToLodLevel = data.objToLodLevel;
var objsToWriteHolder = data.objsToWriteHolder;
var objsToWrite = data.objsToWrite;
bool warned = false;
// Split objects into "buckets" by scene, terrains, LODs, etc
// Objects are already pre-sorted, so we need only ranges
int bStart = 0;
int bEnd = holderObjs.Count-1;
int bucketCount = 2;
List<int> buckets = null;
if (lmgroup.isImplicit)
{
buckets = GetAtlasBucketRanges(holderObjs, data, postPacking);
bucketCount = buckets.Count;
}
var holderAutoIndex = new int[holderObjs.Count];
for(int bucket=0; bucket<bucketCount; bucket+=2)
{
if (lmgroup.isImplicit)
{
bStart = buckets[bucket];
bEnd = buckets[bucket+1];
}
int bSize = bEnd - bStart;
if (bucket > 0)
{
// Start new bucket
lmgroup = AllocateAutoAtlas(1, lmgroup, data);
}
int firstAutoAtlasIndex = autoAtlasGroups.Count - 1;
if (lmgroup.isImplicit)
{
float bucketArea = SumObjectsArea(holderObjs, bStart, bEnd, data);
lmgroup.resolution = ResolutionFromArea(bucketArea);
}
// Fill some LMGroup data
lmgroup.sceneName = holderObjs[bStart].scene.name;
int lodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1;
lmgroup.sceneLodLevel = lodLevel;
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
lmgroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent";
}
var atlas = xatlas.xatlasCreateAtlas();
const int attempts = 4096;
const int padding = 1;
const bool allowRotate = false;
float packTexelsPerUnit = lmgroup.isImplicit ? 1.0f : 0.0f; // multiple atlaseses vs single atlas
int packResolution = lmgroup.resolution;
int maxChartSize = 0;//packResolution;
bool bruteForce = true; // high quality
int vertCount = 4;
int indexCount = 6;
Vector2[] uv = null;
int[] indices = null;
if (!holeFilling)
{
uv = new Vector2[4];
indices = new int[6];
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 2;
indices[4] = 3;
indices[5] = 0;
}
var uvBuffer = new Vector2[4];
var xrefBuffer = new int[4];
var indexBuffer = new int[6];
for(int i=bStart; i<=bEnd; i++)
{
if (!warned)
{
var comp = holderObjs[i].GetComponent<BakeryLightmapGroupSelector>();
if (comp != null && comp.instanceResolutionOverride)
{
if (!ExportSceneValidationMessage("When using xatlas as atlas packer, 'Override resolution' option is not supported for LMGroups.\nOption is used on: " + holderObjs[i].name))
{
xatlas.xatlasClear(atlas);
return false;
}
warned = true;
}
}
var area = holderObjArea[holderObjs[i]];
var uvbounds = holderObjUVBounds[holderObjs[i]];
// Automatic: width and height = sqrt(area) transformed by UV AABB aspect ratio
float width, height;
ApplyAreaToUVBounds(area, uvbounds, out width, out height);
// Clamp to full lightmap size
float twidth = width;
float theight = height;
if (lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas)
{
twidth = (width * texelsPerUnit);// / lmgroup.resolution;
theight = (height * texelsPerUnit);// / lmgroup.resolution;
}
if (!holeFilling)
{
uv[0] = new Vector2(0,0);
uv[1] = new Vector2(twidth,0);
uv[2] = new Vector2(twidth,theight);
uv[3] = new Vector2(0,theight);
}
else
{
List<int> indexList = null;
List<Vector2> uvList = null;
vertCount = indexCount = 0;
int numMeshes = 0;
var ubounds = holderObjUVBounds[holderObjs[i]];
var holder = holderObjs[i];
for(int o=0; o<objsToWriteHolder.Count; o++)
{
if (objsToWriteHolder[o] != holder) continue;
if (numMeshes == 1)
{
indexList = new List<int>();
uvList = new List<Vector2>();
for(int j=0; j<indices.Length; j++)
{
indexList.Add(indices[j]);
}
for(int j=0; j<uv.Length; j++)
{
uvList.Add(uv[j]);
}
}
bool isSkin;
var mesh = GetSharedMeshSkinned(objsToWrite[o], out isSkin);
indices = mesh.triangles;
var uv1 = mesh.uv;
var uv2 = mesh.uv2;
if (uv2 == null || uv2.Length == 0)
{
uv = uv1;
}
else
{
uv = uv2;
}
for(int t=0; t<uv.Length; t++)
{
float u = (uv[t].x - ubounds.x) / (ubounds.z - ubounds.x);
float v = (uv[t].y - ubounds.y) / (ubounds.w - ubounds.y);
u *= twidth;
v *= theight;
uv[t] = new Vector2(u, v);
}
if (numMeshes > 0)
{
for(int j=0; j<indices.Length; j++)
{
indexList.Add(indices[j] + vertCount);
}
for(int j=0; j<uv.Length; j++)
{
uvList.Add(uv[j]);
}
}
vertCount += uv.Length;
indexCount += indices.Length;
numMeshes++;
}
if (numMeshes > 1)
{
uv = uvList.ToArray();
indices = indexList.ToArray();
}
}
var handleUV = GCHandle.Alloc(uv, GCHandleType.Pinned);
int err = 0;
try
{
var pointerUV = handleUV.AddrOfPinnedObject();
err = xatlas.xatlasAddUVMesh(atlas, vertCount, pointerUV, indexCount, indices, allowRotate);
}
finally
{
if (handleUV.IsAllocated) handleUV.Free();
}
if (err == 1)
{
Debug.LogError("xatlas::AddMesh: indices are out of range");
xatlas.xatlasClear(atlas);
return false;
}
else if (err == 2)
{
Debug.LogError("xatlas::AddMesh: index count is incorrect");
xatlas.xatlasClear(atlas);
return false;
}
else if (err != 0)
{
Debug.LogError("xatlas::AddMesh: unknown error");
xatlas.xatlasClear(atlas);
return false;
}
}
//xatlas.xatlasParametrize(atlas);
xatlas.xatlasPack(atlas, attempts, packTexelsPerUnit, packResolution, maxChartSize, padding, bruteForce);//, allowRotate);
int atlasCount = xatlas.xatlasGetAtlasCount(atlas);
var atlasSizes = new int[atlasCount];
xatlas.xatlasNormalize(atlas, atlasSizes);
// Create additional lightmaps
AllocateAutoAtlas(atlasCount-1, lmgroup, data, atlasSizes);
// Move objects into new atlases
if (lmgroup.isImplicit)
{
for(int i=0; i<=bSize; i++)
{
int atlasIndex = xatlas.xatlasGetAtlasIndex(atlas, i, 0);
// Modify implicit group storage
var holder = holderObjs[bStart + i];
var newGroup = autoAtlasGroups[firstAutoAtlasIndex + atlasIndex];
MoveObjectToImplicitGroup(holderObjs[bStart + i], newGroup, data);
holderAutoIndex[bStart + i] = firstAutoAtlasIndex + atlasIndex;
}
}
for(int i=0; i<=bSize; i++)
{
// Get data from xatlas
int newVertCount = xatlas.xatlasGetVertexCount(atlas, i);
uvBuffer = new Vector2[newVertCount];
xrefBuffer = new int[newVertCount];
int newIndexCount = xatlas.xatlasGetIndexCount(atlas, i);
indexBuffer = new int[newIndexCount];
if (holeFilling)
{
uvBuffer = new Vector2[newVertCount];
xrefBuffer = new int[newVertCount];
indexBuffer = new int[newIndexCount];
}
var handleT = GCHandle.Alloc(uvBuffer, GCHandleType.Pinned);
var handleX = GCHandle.Alloc(xrefBuffer, GCHandleType.Pinned);
var handleI = GCHandle.Alloc(indexBuffer, GCHandleType.Pinned);
try
{
var pointerT = handleT.AddrOfPinnedObject();
var pointerX = handleX.AddrOfPinnedObject();
var pointerI = handleI.AddrOfPinnedObject();
xatlas.xatlasGetData(atlas, i, pointerT, pointerX, pointerI);
}
finally
{
if (handleT.IsAllocated) handleT.Free();
if (handleX.IsAllocated) handleX.Free();
if (handleI.IsAllocated) handleI.Free();
}
float minU = float.MaxValue;
float minV = float.MaxValue;
float maxU = -float.MaxValue;
float maxV = -float.MaxValue;
for(int j=0; j<newVertCount; j++)
{
if (uvBuffer[j].x < minU) minU = uvBuffer[j].x;
if (uvBuffer[j].y < minV) minV = uvBuffer[j].y;
if (uvBuffer[j].x > maxU) maxU = uvBuffer[j].x;
if (uvBuffer[j].y > maxV) maxV = uvBuffer[j].y;
}
// Generate final rectangle to transform local UV -> atlas UV
float upadding = 0;
var uvbounds = holderObjUVBounds[holderObjs[bStart + i]];
var paddedRc = new Rect(minU + upadding,
minV + upadding,
(maxU-minU) - upadding * 2,
(maxV-minV) - upadding * 2);
paddedRc.x -= uvbounds.x * (paddedRc.width / (uvbounds.z - uvbounds.x));
paddedRc.y -= uvbounds.y * (paddedRc.height / (uvbounds.w - uvbounds.y));
paddedRc.width /= uvbounds.z - uvbounds.x;
paddedRc.height /= uvbounds.w - uvbounds.y;
holderRect[holderObjs[bStart + i]] = paddedRc;
}
xatlas.xatlasClear(atlas);
}
if (postPacking)
{
buckets = GetAtlasBucketRanges(holderObjs, data, false);
bucketCount = buckets.Count;
Debug.Log("Bucket count for " + lmgroup.name +": " + (bucketCount/2));
if (lmgroup.isImplicit)
{
// Post-packing for auto-atlased groups
var autoLMBuckets = new List<int>[autoAtlasGroups.Count];
for(int bucket=0; bucket<bucketCount; bucket+=2)
{
bStart = buckets[bucket];
bEnd = buckets[bucket+1];
for(int i=bStart; i<=bEnd; i++)
{
int autoLM = holderAutoIndex[i];
if (autoLMBuckets[autoLM] == null) autoLMBuckets[autoLM] = new List<int>();
if (!autoLMBuckets[autoLM].Contains(bucket)) autoLMBuckets[autoLM].Add(bucket);
}
}
int origGroupCount = autoAtlasGroups.Count;
for(int i=0; i<origGroupCount; i++)
{
if (autoLMBuckets[i] != null && autoLMBuckets[i].Count > 1)
{
// Split
for(int j=1; j<autoLMBuckets[i].Count; j++)
{
autoAtlasGroups[i].sceneName = holderObjs[bStart].scene.name;
var newGroup = AllocateAutoAtlas(1, autoAtlasGroups[i], data);
int bucket = autoLMBuckets[i][j];
bStart = buckets[bucket];
bEnd = buckets[bucket+1];
// Update LMGroup data
//newGroup.sceneName = holderObjs[bStart].scene.name;
int lodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1;
newGroup.sceneLodLevel = lodLevel;
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
newGroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent";
}
newGroup.parentName = autoAtlasGroups[i].name;
autoAtlasGroups[i].parentName = "|";
//Debug.LogError(autoAtlasGroups[i].name+" (" +autoAtlasGroups[i].id+") -> "+newGroup.name + " (" + newGroup.id+", "+newGroup.parentID+")");
for(int k=bStart; k<=bEnd; k++)
{
int autoLM = holderAutoIndex[k];
if (autoLM == i)
{
MoveObjectToImplicitGroup(holderObjs[k], newGroup, data);
holderAutoIndex[k] = -1; // mark as moved
}
}
}
}
}
for(int i=0; i<origGroupCount; i++)
{
if (autoLMBuckets[i] != null)
{
for(int j=0; j<holderObjs.Count; j++)
{
if (holderAutoIndex[j] != i) continue;
// Update LMGroup data
var newGroup = autoAtlasGroups[i];
newGroup.sceneName = holderObjs[j].scene.name;
int lodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[j], out lodLevel)) lodLevel = -1;
newGroup.sceneLodLevel = lodLevel;
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
newGroup.containsTerrains = holderObjs[j].name == "__ExportTerrainParent";
}
break;
}
}
}
}
else if (bucketCount > 0)
{
// Post-packing for explicit groups
// Single LMGroup -> LMGroup*buckets
// Setup first bucket
bStart = buckets[0];
bEnd = buckets[1];
int lodLevel;
if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1;
lmgroup.sceneLodLevel = lodLevel;
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
lmgroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent";
}
//Debug.LogError(lmgroup.name+": "+ lmgroup.sceneLodLevel+" because of " + holderObjs[bStart].name);
// Skip first bucket
for(int bucket=2; bucket<bucketCount; bucket+=2)
{
bStart = buckets[bucket];
bEnd = buckets[bucket+1];
var newGroup = AllocateAutoAtlas(1, lmgroup, data);
if (!objToLodLevel.TryGetValue(holderObjs[bStart], out lodLevel)) lodLevel = -1;
newGroup.sceneLodLevel = lodLevel;
if (ftRenderLightmap.giLodMode != ftRenderLightmap.GILODMode.ForceOff && exportTerrainAsHeightmap)
{
newGroup.containsTerrains = holderObjs[bStart].name == "__ExportTerrainParent";
}
newGroup.mode = lmgroup.mode;
newGroup.parentName = lmgroup.name;
lmgroup.parentName = "|";
//Debug.LogError(newGroup.name+": "+ newGroup.sceneLodLevel+" because of " + holderObjs[bStart].name);
for(int k=bStart; k<=bEnd; k++)
{
//MoveObjectToImplicitGroup(holderObjs[k], newGroup, data);
data.storages[data.sceneToID[holderObjs[k].scene]].implicitGroupedObjects.Add(holderObjs[k]);
data.storages[data.sceneToID[holderObjs[k].scene]].implicitGroups.Add(newGroup);
tempStorage.implicitGroupMap[holderObjs[k]] = newGroup;
}
}
}
}
return true;
}
static void NormalizeAtlas(BakeryLightmapGroup lmgroup, List<GameObject> holderObjs, ExportSceneData data, PackData pdata)
{
var holderRect = data.holderRect;
if (!lmgroup.isImplicit && lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.PackAtlas && !pdata.hasResOverrides)
{
float maxx = 0;
float maxy = 0;
for(int i=0; i<holderObjs.Count; i++)
{
var rect = holderRect[holderObjs[i]];
if ((rect.x + rect.width) > maxx) maxx = rect.x + rect.width;
if ((rect.y + rect.height) > maxy) maxy = rect.y + rect.height;
}
float maxDimension = maxx > maxy ? maxx : maxy;
float normalizeScale = 1.0f / maxDimension;
for(int i=0; i<holderObjs.Count; i++)
{
var rect = holderRect[holderObjs[i]];
holderRect[holderObjs[i]] = new Rect(rect.x * normalizeScale, rect.y * normalizeScale, rect.width * normalizeScale, rect.height * normalizeScale);
}
}
}
static bool PackAtlases(ExportSceneData data)
{
// IN, OUT lmgroup.containsTerrains, OUT holderObjs (sort)
var groupToHolderObjects = data.groupToHolderObjects;
// IN
var objToLodLevel = data.objToLodLevel; // LODs packed to separate atlases
cmp_objToLodLevel = objToLodLevel;
// IN/OUT
var holderObjArea = data.holderObjArea; // performs normalization
cmp_holderObjArea = holderObjArea;
// Pack atlases
// Try to scale all objects to occupy all atlas space
foreach(var pair in groupToHolderObjects)
{
// For every LMGroup with PackAtlas mode
var lmgroup = pair.Key;
var holderObjs = pair.Value; // get all objects
var pdata = new PackData();
// Normalize by worldspace area and uv area
// Read/write holderObjArea
NormalizeHolderArea(lmgroup, holderObjs, data);
// Sort objects by area and scene LOD level
// + optionally by scene
// + split by terrain
holderObjs.Sort(CompareGameObjectsForPacking);
var packer = lmgroup.atlasPacker == BakeryLightmapGroup.AtlasPacker.Auto ? atlasPacker : (ftGlobalStorage.AtlasPacker)lmgroup.atlasPacker;
if (packer == ftGlobalStorage.AtlasPacker.xatlas)
{
if (!PackWithXatlas(lmgroup, holderObjs, data, pdata))
{
ExportSceneError("Failed packing atlas");
return false;
}
}
else
{
// Calculate area sum for every scene LOD level in LMGroup
// Write remainingAreaPerLodLevel
SumHolderAreaPerLODLevel(holderObjs, data, pdata);
// Perform recursive packing
while(pdata.repack)
{
pdata.continueRepack = true;
if (!Pack(lmgroup, holderObjs, data, pdata)) return false;
if (!pdata.continueRepack) break;
}
// Normalize atlas by largest axis
NormalizeAtlas(lmgroup, holderObjs, data, pdata);
}
}
cmp_objToLodLevel = null;
cmp_holderObjArea = null;
if (!ValidateScaleOffsetImmutability(data))
{
sceneNeedsToBeRebuilt = true;
return false;
}
return true;
}
static void NormalizeAutoAtlases(ExportSceneData data)
{
var autoAtlasGroups = data.autoAtlasGroups;
var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes;
var holderRect = data.holderRect;
// Normalize autoatlases
var stack = new Stack<AtlasNode>();
for(int g=0; g<autoAtlasGroups.Count; g++)
{
var lmgroup = autoAtlasGroups[g];
var rootNode = autoAtlasGroupRootNodes[g];
float maxx = 0;
float maxy = 0;
rootNode.GetMax(ref maxx, ref maxy);
float maxDimension = maxx > maxy ? maxx : maxy;
float normalizeScale = 1.0f / maxDimension;
stack.Clear();
stack.Push(rootNode);
while(stack.Count > 0)
{
var node = stack.Pop();
if (node.obj != null)
{
var rect = holderRect[node.obj];
holderRect[node.obj] = new Rect(rect.x * normalizeScale, rect.y * normalizeScale, rect.width * normalizeScale, rect.height * normalizeScale);
}
if (node.child0 != null) stack.Push(node.child0);
if (node.child1 != null) stack.Push(node.child1);
}
if (maxDimension < 0.5f)
{
lmgroup.resolution /= 2; // shrink the lightmap after normalization if it was too empty
lmgroup.resolution = Math.Max(lmgroup.resolution, minAutoResolution);
}
}
}
static void JoinAutoAtlases(ExportSceneData data)
{
var autoAtlasGroups = data.autoAtlasGroups;
var autoAtlasGroupRootNodes = data.autoAtlasGroupRootNodes;
var groupList = data.groupList;
var lmBounds = data.lmBounds;
var holderRect = data.holderRect;
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
var stack = new Stack<AtlasNode>();
// Join autoatlases
var autoAtlasCategories = new List<string>();
bool joined = false;
for(int g=0; g<autoAtlasGroups.Count; g++)
{
string cat = "/" + autoAtlasGroups[g].sceneLodLevel;
if (splitByScene) cat = autoAtlasGroups[g].sceneName + cat;
if (!autoAtlasCategories.Contains(cat)) autoAtlasCategories.Add(cat);
}
for(int alod=0; alod<autoAtlasCategories.Count; alod++)
{
var cat = autoAtlasCategories[alod];
var autoAtlasSizes = new List<int>();
var atlasStack = new Stack<BakeryLightmapGroup>();
for(int g=0; g<autoAtlasGroups.Count; g++)
{
var thisCat = "/" + autoAtlasGroups[g].sceneLodLevel;
if (splitByScene) thisCat = autoAtlasGroups[g].sceneName + thisCat;
if (thisCat != cat) continue;
if (autoAtlasGroups[g].resolution == maxAutoResolution) continue;
if (!autoAtlasSizes.Contains(autoAtlasGroups[g].resolution)) autoAtlasSizes.Add(autoAtlasGroups[g].resolution);
}
autoAtlasSizes.Sort();
for(int s=0; s<autoAtlasSizes.Count; s++)
{
int asize = autoAtlasSizes[s];
atlasStack.Clear();
for(int g=0; g<autoAtlasGroups.Count; g++)
{
var thisCat = "/" + autoAtlasGroups[g].sceneLodLevel;
if (splitByScene) thisCat = autoAtlasGroups[g].sceneName + thisCat;
if (thisCat != cat) continue;
if (autoAtlasGroups[g].resolution != asize) continue;
atlasStack.Push(autoAtlasGroups[g]);
if (atlasStack.Count == 4)
{
var newGroup = ScriptableObject.CreateInstance<BakeryLightmapGroup>();
newGroup.name = autoAtlasGroups[g].name;
newGroup.isImplicit = true;
newGroup.sceneLodLevel = autoAtlasGroups[g].sceneLodLevel;
newGroup.sceneName = autoAtlasGroups[g].sceneName;
newGroup.resolution = asize * 2;
newGroup.bitmask = autoAtlasGroups[g].bitmask;
newGroup.mode = BakeryLightmapGroup.ftLMGroupMode.PackAtlas;
newGroup.id = data.lmid;
groupList.Add(newGroup);
lmBounds.Add(new Bounds(new Vector3(0,0,0), new Vector3(0,0,0)));
data.lmid++;
autoAtlasGroups.Add(newGroup);
var rootNode2 = new AtlasNode();
rootNode2.rc = new Rect(0, 0, 1, 1);
autoAtlasGroupRootNodes.Add(rootNode2);
// Top
rootNode2.child0 = new AtlasNode();
rootNode2.child0.rc = new Rect(0, 0, 1, 0.5f);
// Bottom
rootNode2.child1 = new AtlasNode();
rootNode2.child1.rc = new Rect(0, 0.5f, 1, 0.5f);
for(int gg=0; gg<4; gg++)
{
var subgroup = atlasStack.Pop();
var id = autoAtlasGroups.IndexOf(subgroup);
var subgroupRootNode = autoAtlasGroupRootNodes[id];
float ox, oy, sx, sy;
if (gg == 0)
{
// Left top
rootNode2.child0.child0 = subgroupRootNode;
//rootNode2.child0.child0.Transform(0, 0, 0.5f, 0.5f);
//offsetScale = rootNode2.child0.child0.rc;
ox = 0; oy = 0; sx = 0.5f; sy = 0.5f;
}
else if (gg == 1)
{
// Right top
rootNode2.child0.child1 = subgroupRootNode;
//rootNode2.child0.child1.Transform(0.5f, 0, 0.5f, 0.5f);
//offsetScale = rootNode2.child0.child1.rc;
ox = 0.5f; oy = 0; sx = 0.5f; sy = 0.5f;
}
else if (gg == 2)
{
// Left bottom
rootNode2.child1.child0 = subgroupRootNode;
//rootNode2.child1.child0.Transform(0, 0.5f, 0.5f, 0.5f);
//offsetScale = rootNode2.child1.child0.rc;
ox = 0; oy = 0.5f; sx = 0.5f; sy = 0.5f;
}
else
{
// Right bottom
rootNode2.child1.child1 = subgroupRootNode;
//rootNode2.child1.child1.Transform(0.5f, 0.5f, 0.5f, 0.5f);
//offsetScale = rootNode2.child1.child1.rc;
ox = 0.5f; oy = 0.5f; sx = 0.5f; sy = 0.5f;
}
autoAtlasGroups.RemoveAt(id);
autoAtlasGroupRootNodes.RemoveAt(id);
id = groupList.IndexOf(subgroup);
groupList.RemoveAt(id);
lmBounds.RemoveAt(id);
for(int x=id; x<groupList.Count; x++)
{
groupList[x].id--;
data.lmid--;
}
// Modify implicit group storage
joined = true;
stack.Clear();
stack.Push(subgroupRootNode);
while(stack.Count > 0)
{
var node = stack.Pop();
if (node.obj != null)
{
var rect = holderRect[node.obj];
holderRect[node.obj] = new Rect(rect.x * sx + ox,
rect.y * sy + oy,
rect.width * sx,
rect.height * sy);
MoveObjectToImplicitGroup(node.obj, newGroup, data);
/*
tempStorage.implicitGroupMap[node.obj] = newGroup;
for(int k=0; k<storages[sceneToID[node.obj.scene]].implicitGroupedObjects.Count; k++)
{
if (storages[sceneToID[node.obj.scene]].implicitGroupedObjects[k] == node.obj)
{
storages[sceneToID[node.obj.scene]].implicitGroups[k] = newGroup;
//Debug.LogError("Implicit set (join): " + k+" "+newGroup.name);
}
}
*/
}
if (node.child0 != null) stack.Push(node.child0);
if (node.child1 != null) stack.Push(node.child1);
}
}
}
}
}
}
if (joined)
{
for(int i=0; i<objsToWrite.Count; i++)
{
objsToWriteGroup[i] = GetLMGroupFromObject(objsToWrite[i], data);
}
}
}
static bool ExportSceneValidationMessage(string msg)
{
ProgressBarEnd(false);
if (ftRenderLightmap.verbose)
{
if (!EditorUtility.DisplayDialog("Bakery", msg, "Continue anyway", "Cancel"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
return false;
}
}
else
{
Debug.LogError(msg);
}
ProgressBarInit("Exporting scene - preparing...");
return true;
}
static void ExportSceneError(string phase)
{
DebugLogError("Error exporting scene (" + phase + ") - see console for details");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
}
class ExportSceneData
{
// Per-scene data
public ftLightmapsStorage[] storages;
public int firstNonNullStorage;
//public ftLightmapsStorage settingsStorage;
public Dictionary<Scene, int> sceneToID = new Dictionary<Scene, int>();
public Dictionary<Scene,bool> sceneHasStorage = new Dictionary<Scene,bool>();
// Object properties
public Dictionary<GameObject,int> objToLodLevel = new Dictionary<GameObject,int>(); // defines atlas LOD level
public Dictionary<GameObject,List<int>> objToLodLevelVisible = new Dictionary<GameObject,List<int>>(); // defines LOD levels where this object is visible
public List<GameObject> objsToWrite = new List<GameObject>();
public List<bool> objsToWriteLightmapped = new List<bool>();
public List<BakeryLightmapGroup> objsToWriteGroup = new List<BakeryLightmapGroup>();
public List<GameObject> objsToWriteHolder = new List<GameObject>();
public List<Vector4> objsToWriteScaleOffset = new List<Vector4>();
public List<Vector2[]> objsToWriteUVOverride = new List<Vector2[]>();
public List<string> objsToWriteNames = new List<string>();
public List<Vector3[]> objsToWriteVerticesPosW = new List<Vector3[]>();
public List<Vector3[]> objsToWriteVerticesNormalW = new List<Vector3[]>();
public List<Vector4[]> objsToWriteVerticesTangentW = new List<Vector4[]>();
public List<Vector2[]> objsToWriteVerticesUV = new List<Vector2[]>();
public List<Vector2[]> objsToWriteVerticesUV2 = new List<Vector2[]>();
public List<int[][]> objsToWriteIndices = new List<int[][]>();
// Auto-atlasing
public List<BakeryLightmapGroup> autoAtlasGroups = new List<BakeryLightmapGroup>();
public List<AtlasNode> autoAtlasGroupRootNodes = new List<AtlasNode>();
public BakeryLightmapGroup autoVertexGroup;
// Data to collect for atlas packing
public Dictionary<GameObject, float> holderObjArea = new Dictionary<GameObject, float>(); // LMGroup holder area, accumulated from all children
public Dictionary<GameObject, Vector4> holderObjUVBounds = new Dictionary<GameObject, Vector4>(); // LMGroup holder 2D UV AABB
public Dictionary<BakeryLightmapGroup, List<GameObject>> groupToHolderObjects = new Dictionary<BakeryLightmapGroup, List<GameObject>>(); // LMGroup -> holders map
public Dictionary<GameObject, Rect> holderRect = new Dictionary<GameObject, Rect>();
// Per-LMGroup data
public List<BakeryLightmapGroup> groupList = new List<BakeryLightmapGroup>();
public List<Bounds> lmBounds = new List<Bounds>(); // list of bounding boxes around LMGroups for testing lights
// Geometry data
public List<int>[] indicesOpaqueLOD = null;
public List<int>[] indicesTransparentLOD = null;
public int lmid = 0; // LMID counter
public ExportSceneData(int sceneCount)
{
storages = new ftLightmapsStorage[sceneCount];
}
}
class AdjustUVPaddingData
{
public List<int> dirtyObjList = new List<int>();
public List<string> dirtyAssetList = new List<string>();
public Dictionary<Mesh, List<int>> meshToObjIDs = new Dictionary<Mesh, List<int>>();
public Dictionary<Mesh, int> meshToPaddingMap = new Dictionary<Mesh, int>();
}
class PackData
{
public Dictionary<int,float> remainingAreaPerLodLevel = new Dictionary<int,float>();
public bool repack = true;
public bool repackStage2 = false;
public bool finalRepack = false;
public float repackScale = 1;
public int repackTries = 0;
public bool hasResOverrides = false;
public bool continueRepack = false;
}
static public IEnumerator ExportScene(EditorWindow window, bool renderTextures = true, bool atlasOnly = false)
{
userCanceled = false;
ProgressBarInit("Exporting scene - preparing...", window);
yield return null;
var bakeryRuntimePath = ftLightmaps.GetRuntimePath();
gstorage = AssetDatabase.LoadAssetAtPath(bakeryRuntimePath + "ftGlobalStorage.asset", typeof(ftGlobalStorage)) as ftGlobalStorage;
var time = GetTime();
var ms = time;
var startMsU = ms;
double totalTime = GetTime();
double vbTimeRead = 0;
double vbTimeWrite = 0;
double vbTimeWriteFull = 0;
double vbTimeWriteT = 0;
double vbTimeWriteT2 = 0;
double vbTimeWriteT3 = 0;
double ibTime = 0;
var sceneCount = SceneManager.sceneCount;
var indicesOpaque = new List<int>();
var indicesTransparent = new List<int>();
var data = new ExportSceneData(sceneCount);
bool tangentSHLights = CheckForTangentSHLights();
// Per-LMGroup data
var lmAlbedoList = new List<IntPtr>(); // list of albedo texture for UV GBuffer rendering
var lmAlbedoListTex = new List<Texture>();
var lmAlphaList = new List<IntPtr>(); // list of alpha textures for alpha buffer generation
var lmAlphaListTex = new List<Texture>();
var lmAlphaRefList = new List<float>(); // list of alpha texture refs
var lmAlphaChannelList = new List<int>(); // list of alpha channels
// lod-related
var lmVOffset = new List<int>();
var lmUVArrays = new List<List<float>>();
var lmUVArrays2 = new List<float[]>();
var lmUVArrays3 = new List<float[]>();
var lmIndexArrays = new List<List<int>>();
var lmIndexArrays2 = new List<int[]>();
var lmLocalToGlobalIndices = new List<List<int>>();
vbtraceTexPosNormalArray = new List<float>();
vbtraceTexUVArray = new List<float>();
sceneLodsUsed = 0;
// Create temp path
CreateSceneFolder();
// Init storages
try
{
InitSceneStorage(data);
}
catch(Exception e)
{
ExportSceneError("Global storage init");
Debug.LogError("Exception caught: " + e.ToString());
throw;
}
// Create LMGroup for light probes
if (ftRenderLightmap.lightProbeMode == ftRenderLightmap.LightProbeMode.L1 && renderTextures && !atlasOnly && ftRenderLightmap.hasAnyProbes)
{
var c = CreateLightProbeLMGroup(data);
while(c.MoveNext()) yield return null;
}
if (ftRenderLightmap.hasAnyVolumes)
{
var c2 = CreateVolumeLMGroup(data);
while(c2.MoveNext()) yield return null;
}
// wip
var lmBounds = data.lmBounds;
var storages = data.storages;
var sceneToID = data.sceneToID;
var groupList = data.groupList;
var objToLodLevel = data.objToLodLevel;
var objToLodLevelVisible = data.objToLodLevelVisible;
var objsToWrite = data.objsToWrite;
var objsToWriteGroup = data.objsToWriteGroup;
var objsToWriteHolder = data.objsToWriteHolder;
var objsToWriteIndices = data.objsToWriteIndices;
var objsToWriteNames = data.objsToWriteNames;
var objsToWriteUVOverride = data.objsToWriteUVOverride;
var objsToWriteScaleOffset = data.objsToWriteScaleOffset;
var objsToWriteVerticesUV = data.objsToWriteVerticesUV;
var objsToWriteVerticesUV2 = data.objsToWriteVerticesUV2;
var objsToWriteVerticesPosW = data.objsToWriteVerticesPosW;
var objsToWriteVerticesNormalW = data.objsToWriteVerticesNormalW;
var objsToWriteVerticesTangentW = data.objsToWriteVerticesTangentW;
var holderRect = data.holderRect;
terrainObjectList = new List<GameObject>();
terrainObjectToActual = new List<Terrain>();
terrainObjectToHeightMap = new List<Texture>();
terrainObjectToBounds = new List<float>();
terrainObjectToBoundsUV = new List<float>();
terrainObjectToFlags = new List<int>();
terrainObjectToLMID = new List<int>();
terrainObjectToHeightMips = new List<List<float[]>>();
treeObjectList = new List<GameObject>();
temporaryAreaLightMeshList = new List<GameObject>();
temporaryAreaLightMeshList2 = new List<BakeryLightMesh>();
var objects = Resources.FindObjectsOfTypeAll(typeof(GameObject));
//var objects = UnityEngine.Object.FindObjectsOfTypeAll(typeof(GameObject));
try
{
ms = GetTime();
//if (!onlyUVdata)
//{
time = ms;
// Get manually created LMGroups
CollectExplicitLMGroups(data);
// Object conversion loop / also validate for multiple scene storages
for(int objNum = 0; objNum < objects.Length; objNum++)
{
GameObject obj = (GameObject)objects[objNum];
if (obj == null) continue;
if (!CheckForMultipleSceneStorages(obj, data)) yield break;
if (ConvertUnityAreaLight(obj)) continue;
ConvertTerrain(obj);
}
// Regather objects if new were added
if (terrainObjectList.Count > 0 || treeObjectList.Count > 0 || temporaryAreaLightMeshList.Count > 0)
{
//objects = UnityEngine.Object.FindObjectsOfTypeAll(typeof(GameObject));
objects = Resources.FindObjectsOfTypeAll(typeof(GameObject));
}
tempStorage.implicitGroupMap = new Dictionary<GameObject, UnityEngine.Object>(); // implicit holder -> LMGroup map. used by GetLMGroupFromObject
// Find LODGroups -> LODs -> scene-wide LOD distances
// Map objects to scene-wide LOD levels
MapObjectsToSceneLODs(data, objects);
ftModelPostProcessor.Init();
// Filter objects, convert to property arrays
if (!FilterObjects(data, objects)) yield break;
CalculateVertexCountForVertexGroups(data);
CreateAutoAtlasLMGroups(data, renderTextures, atlasOnly);
TransformVertices(data, tangentSHLights);
if (unwrapUVs)
{
var adata = new AdjustUVPaddingData();
CalculateUVPadding(data, adata);
ResetPaddingStorageData(data);
StoreNewUVPadding(data, adata);
if (!ValidatePaddingImmutability(adata)) yield break;
if (CheckUnwrapError()) yield break;
// Reimport assets with adjusted padding
if (modifyLightmapStorage)
{
if (!ReimportModifiedAssets(adata)) yield break;
TransformModifiedAssets(data, adata, tangentSHLights);
}
}
else if (forceDisableUnwrapUVs)
{
var adata = new AdjustUVPaddingData();
ResetPaddingStorageData(data);
if (!ClearUVPadding(data, adata)) yield break;
if (CheckUnwrapError()) yield break;
TransformModifiedAssets(data, adata, tangentSHLights);
}
CalculateHolderUVBounds(data);
CalculateAutoAtlasInitResolution(data);
if (!PackAtlases(data)) yield break;
if (atlasPacker == ftGlobalStorage.AtlasPacker.Default)
{
NormalizeAutoAtlases(data);
JoinAutoAtlases(data);
}
InitSceneStorage2(data);
//TransformVertices(data); // shouldn't be necessary
// Update objToWriteGroups because of autoAtlas
if (autoAtlas)
{
for(int i=0; i<objsToWrite.Count; i++)
{
objsToWriteGroup[i] = GetLMGroupFromObject(objsToWrite[i], data);
}
}
// Done collecting groups
if (groupList.Count == 0 && modifyLightmapStorage)
{
DebugLogError("You need to mark some objects static or add Bakery Lightmap Group Selector components on them.");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
if (objsToWrite.Count == 0)
{
DebugLogError("You need to mark some objects static or add Bakery Lightmap Group Selector components on them.");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
if (atlasOnly)
{
atlasOnlyObj = new List<Renderer>();
atlasOnlySize = new List<int>();
atlasOnlyID = new List<int>();
atlasOnlyScaleOffset = new List<Vector4>();
var emptyVec4 = new Vector4(1,1,0,0);
Rect rc = new Rect();
for(int i=0; i<objsToWrite.Count; i++)
{
var lmgroup = objsToWriteGroup[i];
var holderObj = objsToWriteHolder[i];
if (holderObj != null)
{
if (!holderRect.TryGetValue(holderObj, out rc))
{
holderObj = null;
}
}
var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y);
atlasOnlyObj.Add(objsToWrite[i].GetComponent<Renderer>());
atlasOnlyScaleOffset.Add(scaleOffset);
atlasOnlySize.Add(lmgroup == null ? 0 : lmgroup.resolution);
atlasOnlyID.Add(lmgroup == null ? 0 : lmgroup.id);
}
yield break;
}
// Sort LMGroups so vertex groups are never first (because Unity assumes lightmap compression on LM0)
for(int i=0; i<groupList.Count; i++)
{
groupList[i].sortingID = i;
}
groupList.Sort(delegate(BakeryLightmapGroup a, BakeryLightmapGroup b)
{
int aa = (a.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) ? -1 : 1;
int bb = (b.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) ? -1 : 1;
return bb.CompareTo(aa);
});
var lmBounds2 = new List<Bounds>();
for(int i=0; i<groupList.Count; i++)
{
lmBounds2.Add(lmBounds[groupList[i].sortingID]); // apply same sorting to lmBounds
groupList[i].id = i;
}
lmBounds = lmBounds2;
// Check for existing files
if (overwriteWarning)
{
var checkGroupList = groupList;
if (overwriteWarningSelectedOnly)
{
var selObjs = Selection.objects;
checkGroupList = new List<BakeryLightmapGroup>();
for(int o=0; o<selObjs.Length; o++)
{
if (selObjs[o] as GameObject == null) continue;
var selGroup = GetLMGroupFromObject(selObjs[o] as GameObject, data);
if (selGroup == null) continue;
if (!checkGroupList.Contains(selGroup))
{
checkGroupList.Add(selGroup);
}
}
}
var existingFilenames = "";
for(int i=0; i<checkGroupList.Count; i++)
{
var nm = checkGroupList[i].name;
var filename = nm + "_final" + overwriteExtensionCheck;
var outputPath = ftRenderLightmap.outputPath;
if (File.Exists("Assets/" + outputPath + "/" + filename))
{
existingFilenames += filename + "\n";
}
}
if (existingFilenames.Length > 0 && ftRenderLightmap.verbose)
{
ProgressBarEnd(false);
if (!EditorUtility.DisplayDialog("Lightmap overwrite", "These lightmaps will be overwritten:\n\n" + existingFilenames, "Overwrite", "Cancel"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
ProgressBarInit("Exporting scene - preparing...", window);
}
}
ftRenderLightmap.giLodModeEnabled = ftRenderLightmap.giLodMode == ftRenderLightmap.GILODMode.ForceOn;
ulong approxMem = 0;
if (groupList.Count > 100 && ftRenderLightmap.verbose)
{
ProgressBarEnd(false);
if (!EditorUtility.DisplayDialog("Lightmap count check", groupList.Count + " lightmaps are going to be rendered. Continue?", "Continue", "Cancel"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
ProgressBarInit("Exporting scene - preparing...", window);
}
if (memoryWarning || ftRenderLightmap.giLodMode == ftRenderLightmap.GILODMode.Auto)
{
for(int i=0; i<groupList.Count; i++)
{
var lmgroup = groupList[i];
var res = lmgroup.resolution;
ulong lightingSize = (ulong)(res * res * 4 * 2); // RGBA16f
approxMem += lightingSize;
}
var tileSize = ftRenderLightmap.tileSize;
approxMem += (ulong)(tileSize * tileSize * 16 * 2); // maximum 2xRGBA32f (for fixPos12)
}
if (memoryWarning && ftRenderLightmap.verbose)
{
ProgressBarEnd(false);
if (!EditorUtility.DisplayDialog("Lightmap memory check", "Rendering may require more than " + (ulong)((approxMem/1024)/1024) + "MB of video memory. Continue?", "Continue", "Cancel"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
ProgressBarInit("Exporting scene - preparing...", window);
}
if (ftRenderLightmap.giLodMode == ftRenderLightmap.GILODMode.Auto)
{
approxMem /= 1024;
approxMem /= 1024;
approxMem += 1024; // scene geometry size estimation - completely random
if ((int)approxMem > SystemInfo.graphicsMemorySize)
{
Debug.Log("GI VRAM auto optimization ON: estimated usage " + (int)approxMem + " > " + SystemInfo.graphicsMemorySize);
ftRenderLightmap.giLodModeEnabled = true;
}
else
{
Debug.Log("GI VRAM auto optimization OFF: estimated usage " + (int)approxMem + " < " + SystemInfo.graphicsMemorySize);
}
}
// Generate terrain geometry with detail enough for given size for UVGBuffer purposes
fhmaps = new BinaryWriter(File.Open(scenePath + "/heightmaps.bin", FileMode.Create));
if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("heightmaps.bin");
if (exportTerrainAsHeightmap)
{
for(int i=0; i<objsToWrite.Count; i++)
{
var obj = objsToWrite[i];
if (obj.name != "__ExportTerrain") continue;
var holderObj = objsToWriteHolder[i];
Rect rc = new Rect();
if (holderObj != null)
{
if (!holderRect.TryGetValue(holderObj, out rc))
{
holderObj = null;
}
}
if (holderObj == null) continue;
var lmgroup = objsToWriteGroup[i];
//float terrainPixelWidth = rc.width * lmgroup.resolution;
//float terrainPixelHeight = rc.height * lmgroup.resolution;
var index = terrainObjectList.IndexOf(obj.transform.parent.gameObject);
var terrain = terrainObjectToActual[index];
var tdata = terrain.terrainData;
//var heightmapResolution = tdata.heightmapResolution;
//int closestSize = (int)Mathf.Min(Mathf.NextPowerOfTwo((int)Mathf.Max(terrainPixelWidth, terrainPixelHeight)), heightmapResolution-1);
//if (closestSize < 2) continue;
//int mipLog2 = (int)(Mathf.Log(closestSize) / Mathf.Log(2.0f));
//int maxMipLog2 = (int)(Mathf.Log(heightmapResolution-1) / Mathf.Log(2.0f));
//int mip = maxMipLog2 - mipLog2;
float scaleX = tdata.size.x;// / (heightmapResolution-1);
//float scaleY = tdata.size.y;
float scaleZ = tdata.size.z;// / (heightmapResolution-1);
float offsetX = obj.transform.position.x;
float offsetY = obj.transform.position.y;
float offsetZ = obj.transform.position.z;
terrainObjectToLMID[index] = lmgroup.id;
terrainObjectToBoundsUV[index*4] = rc.x;
terrainObjectToBoundsUV[index*4+1] = rc.y;
terrainObjectToBoundsUV[index*4+2] = rc.width;
terrainObjectToBoundsUV[index*4+3] = rc.height;
if (uvgbHeightmap)
{
var indexArrays = objsToWriteIndices[i] = new int[1][];
var indexArray = indexArrays[0] = new int[6];//(closestSize-1)*(closestSize-1)*6];
//int indexOffset = 0;
//int vertOffset = 0;
var uvArray = objsToWriteVerticesUV[i] = objsToWriteVerticesUV2[i] = new Vector2[4];//closestSize*closestSize];
var posArray = objsToWriteVerticesPosW[i] = new Vector3[4];
var normalArray = objsToWriteVerticesNormalW[i] = new Vector3[4];
posArray[0] = new Vector3(offsetX, offsetY, offsetZ);
posArray[1] = new Vector3(offsetX + scaleX, offsetY, offsetZ);
posArray[2] = new Vector3(offsetX, offsetY, offsetZ + scaleZ);
posArray[3] = new Vector3(offsetX + scaleX, offsetY, offsetZ + scaleZ);
normalArray[0] = Vector3.up;
normalArray[1] = Vector3.up;
normalArray[2] = Vector3.up;
normalArray[3] = Vector3.up;
uvArray[0] = new Vector2(0,0);
uvArray[1] = new Vector2(1,0);
uvArray[2] = new Vector2(0,1);
uvArray[3] = new Vector2(1,1);
indexArray[0] = 0;
indexArray[1] = 2;
indexArray[2] = 3;
indexArray[3] = 0;
indexArray[4] = 3;
indexArray[5] = 1;
}
else
{
/*if (mip == 0)
{
// use existing heightmap
var heights = tdata.GetHeights(0, 0, heightmapResolution, heightmapResolution);
var posArray = objsToWriteVerticesPosW[i] = new Vector3[heightmapResolution * heightmapResolution];
objsToWriteVerticesNormalW[i] = terrainObjectToNormalMip0[index];
closestSize = heightmapResolution;
scaleX /= closestSize-1;
scaleZ /= closestSize-1;
for(int y=0; y<closestSize; y++)
{
for(int x=0; x<closestSize; x++)
{
float px = x * scaleX + offsetX;
float pz = y * scaleZ + offsetZ;
posArray[y * closestSize + x] = new Vector3(px, heights[y, x] * scaleY + offsetY, pz);
}
}
}
else
{
// use mip
var heights = terrainObjectToHeightMips[index][mip - 1];
var posArray = objsToWriteVerticesPosW[i] = new Vector3[closestSize * closestSize];
objsToWriteVerticesNormalW[i] = terrainObjectToNormalMips[index][mip - 1];
scaleX /= closestSize-1;
scaleZ /= closestSize-1;
for(int y=0; y<closestSize; y++)
{
for(int x=0; x<closestSize; x++)
{
float px = x * scaleX + offsetX;
float pz = y * scaleZ + offsetZ;
posArray[y * closestSize + x] = new Vector3(px, heights[y * closestSize + x] * scaleY + offsetY, pz);
}
}
}
var indexArrays = objsToWriteIndices[i] = new int[1][];
var indexArray = indexArrays[0] = new int[(closestSize-1)*(closestSize-1)*6];
int indexOffset = 0;
int vertOffset = 0;
var uvArray = objsToWriteVerticesUV[i] = objsToWriteVerticesUV2[i] = new Vector2[closestSize*closestSize];
for(int y=0; y<closestSize; y++)
{
for(int x=0; x<closestSize; x++)
{
uvArray[y * closestSize + x] = new Vector2(x / (float)(closestSize-1), y / (float)(closestSize-1));
if (x < closestSize-1 && y < closestSize-1)
{
indexArray[indexOffset] = vertOffset;
indexArray[indexOffset + 1] = vertOffset + closestSize;
indexArray[indexOffset + 2] = vertOffset + closestSize + 1;
indexArray[indexOffset + 3] = vertOffset;
indexArray[indexOffset + 4] = vertOffset + closestSize + 1;
indexArray[indexOffset + 5] = vertOffset + 1;
indexOffset += 6;
}
vertOffset++;
}
}*/
}
}
// Export heightmap metadata
if (terrainObjectToActual.Count > 0)
{
terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count];
for(int i=0; i<terrainObjectToHeightMap.Count; i++)
{
fhmaps.Write(terrainObjectToLMID[i]);
for(int fl=0; fl<6; fl++) fhmaps.Write(terrainObjectToBounds[i*6+fl]);
for(int fl=0; fl<4; fl++) fhmaps.Write(terrainObjectToBoundsUV[i*4+fl]);
fhmaps.Write(terrainObjectToFlags[i]);
}
}
}
// Write mark last written scene
File.WriteAllText(scenePath + "/lastscene.txt", ftRenderLightmap.GenerateLightingDataAssetName());
// Write lightmap definitions
var flms = new BinaryWriter(File.Open(scenePath + "/lms.bin", FileMode.Create));
var flmlod = new BinaryWriter(File.Open(scenePath + "/lmlod.bin", FileMode.Create));
var flmuvgb = new BinaryWriter(File.Open(scenePath + "/lmuvgb.bin", FileMode.Create));
if (ftRenderLightmap.clientMode)
{
ftClient.serverFileList.Add("lms.bin");
ftClient.serverFileList.Add("lmlod.bin");
ftClient.serverFileList.Add("lmuvgb.bin");
}
// Init global UVGBuffer flags
int uvgbGlobalFlags = 0;
if (exportShaderColors)
{
if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.BakedNormalMaps)
{
uvgbGlobalFlags = UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS;
}
else if (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.RNM ||
(ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.SH && tangentSHLights))
{
uvgbGlobalFlags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS | UVGBFLAG_TANGENT;
}
else
{
uvgbGlobalFlags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS;
}
}
else
{
uvgbGlobalFlags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_ALBEDO | UVGBFLAG_EMISSIVE | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS;
}
if (terrainObjectToActual.Count > 0) uvgbGlobalFlags |= UVGBFLAG_TERRAIN;
SetUVGBFlags(uvgbGlobalFlags);
for(int i=0; i<groupList.Count; i++)
{
var lmgroup = groupList[i];
flms.Write(lmgroup.name);
flmlod.Write(lmgroup.sceneLodLevel);
int uvgbflags = 0;
if (lmgroup.containsTerrains && exportTerrainAsHeightmap)
uvgbflags = uvgbGlobalFlags | (UVGBFLAG_NORMAL | UVGBFLAG_TERRAIN);
if (lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.BakedNormalMaps)
uvgbflags = UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS;
if (lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.RNM ||
(lmgroup.renderDirMode == BakeryLightmapGroup.RenderDirMode.SH && tangentSHLights))
uvgbflags = UVGBFLAG_NORMAL | UVGBFLAG_FACENORMAL | UVGBFLAG_POS | UVGBFLAG_SMOOTHPOS | UVGBFLAG_TANGENT;
if (lmgroup.probes) uvgbflags = UVGBFLAG_RESERVED;
flmuvgb.Write(uvgbflags);
if (ftRenderLightmap.clientMode)
{
if (uvgbflags == 0) uvgbflags = uvgbGlobalFlags;
ftClient.serverFileList.Add("uvpos_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
ftClient.serverFileList.Add("uvnormal_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
ftClient.serverFileList.Add("uvalbedo_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
if (lmgroup.mode != BakeryLightmapGroup.ftLMGroupMode.Vertex)
{
if ((uvgbflags & UVGBFLAG_SMOOTHPOS) != 0) ftClient.serverFileList.Add("uvsmoothpos_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
}
if ((uvgbflags & UVGBFLAG_FACENORMAL) != 0) ftClient.serverFileList.Add("uvfacenormal_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
if ((uvgbflags & UVGBFLAG_TANGENT) != 0) ftClient.serverFileList.Add("uvtangent_" + lmgroup.name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
}
if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex)
{
int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)lmgroup.totalVertexCount));
if (atlasTexSize > 8192) Debug.LogWarning("Warning: vertex lightmap group " + lmgroup.name + " uses resolution of " + atlasTexSize);
atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize;
flms.Write(-atlasTexSize);
}
else
{
flms.Write(lmgroup.resolution);
}
//Debug.LogError(lmgroup.name+": " + lmgroup.resolution);
}
flms.Close();
flmlod.Close();
flmuvgb.Close();
voffset = ioffset = soffset = 0; // vertex/index/surface write
// Per-surface alpha texture IDs
var alphaIDs = new List<ushort>();
int albedoCounter = 0;
var albedoMap = new Dictionary<IntPtr, int>(); // albedo ptr -> ID map
int alphaCounter = 0;
var alphaMap = new Dictionary<IntPtr, List<int>>(); // alpha ptr -> ID map
var dummyTexList = new List<Texture>(); // list of single-color 1px textures
var dummyPixelArray = new Color[1];
if (ftRenderLightmap.checkOverlaps)
{
var quad = GameObject.CreatePrimitive(PrimitiveType.Quad);
var plane = GameObject.CreatePrimitive(PrimitiveType.Plane);
var qmesh = quad.GetComponent<MeshFilter>().sharedMesh;
var pmesh = plane.GetComponent<MeshFilter>().sharedMesh;
DestroyImmediate(quad);
DestroyImmediate(plane);
bool canCheck = ftModelPostProcessor.InitOverlapCheck();
if (!canCheck)
{
DebugLogError("Can't load ftOverlapTest.shader");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
for(int g=0; g<groupList.Count; g++)
{
var lmgroup = groupList[g];
if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) continue;
for(int i=0; i<objsToWrite.Count; i++)
{
if (objsToWriteGroup[i] != lmgroup) continue;
var obj = objsToWrite[i];
var mesh = GetSharedMesh(obj);
if (mesh == qmesh || mesh == pmesh) continue;
var uv = objsToWriteVerticesUV[i];//mesh.uv;
var uv2 = objsToWriteVerticesUV2[i];//mesh.uv2;
var usedUVs = uv2.Length == 0 ? uv : uv2;
bool validUVs = true;
for(int v=0; v<usedUVs.Length; v++)
{
if (usedUVs[v].x < -0.0001f || usedUVs[v].x > 1.0001f || usedUVs[v].y < -0.0001f || usedUVs[v].y > 1.0001f)
{
validUVs = false;
break;
}
}
if (!validUVs && ftRenderLightmap.verbose)
{
string objPath = obj.name;
var prt = obj.transform.parent;
while(prt != null)
{
objPath = prt.name + "\\" + objPath;
prt = prt.parent;
}
ftRenderLightmap.simpleProgressBarEnd();
if (!EditorUtility.DisplayDialog("Incorrect UVs", "Object " + objPath + " UVs are out of 0-1 bounds", "Continue", "Stop"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
ProgressBarInit("Exporting scene - preparing...", window);
}
int overlap = ftModelPostProcessor.DoOverlapCheck(obj, false);
if (overlap != 0 && ftRenderLightmap.verbose)
{
//storage.debugRT = ftModelPostProcessor.rt;
string objPath = obj.name;
var prt = obj.transform.parent;
while(prt != null)
{
objPath = prt.name + "\\" + objPath;
prt = prt.parent;
}
if (overlap < 0)
{
ftRenderLightmap.simpleProgressBarEnd();
if (!EditorUtility.DisplayDialog("Incorrect UVs", "Object " + objPath + " has no UV2", "Continue", "Stop"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
ProgressBarInit("Exporting scene - preparing...", window);
}
else
{
ftRenderLightmap.simpleProgressBarEnd();
if (!EditorUtility.DisplayDialog("Incorrect UVs", "Object " + objPath + " has overlapping UVs", "Continue", "Stop"))
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
ProgressBarInit("Exporting scene - preparing...", window);
}
}
}
}
ftModelPostProcessor.EndOverlapCheck();
}
// Prepare progressbar
int progressNumObjects = 0;
foreach(GameObject obj in objects)
{
if (obj == null) continue;
if (!obj.activeInHierarchy) continue;
progressNumObjects++;
}
// Open files to write
fscene = new BinaryWriter(File.Open(scenePath + "/objects.bin", FileMode.Create));
fmesh = new BinaryWriter(File.Open(scenePath + "/mesh.bin", FileMode.Create));
flmid = new BinaryWriter(File.Open(scenePath + "/lmid.bin", FileMode.Create));
fseamfix = new BinaryWriter(File.Open(scenePath + "/seamfix.bin", FileMode.Create));
fsurf = new BinaryWriter(File.Open(scenePath + "/surf.bin", FileMode.Create));
fmatid = new BinaryWriter(File.Open(scenePath + "/matid.bin", FileMode.Create));
fmatide = new BinaryWriter(File.Open(scenePath + "/emissiveid.bin", FileMode.Create));
fmatideb = new BinaryWriter(File.Open(scenePath + "/emissivemul.bin", FileMode.Create));
fmatidh = new BinaryWriter(File.Open(scenePath + "/heightmapid.bin", FileMode.Create));
falphaid = new BinaryWriter(File.Open(scenePath + "/alphaid.bin", FileMode.Create));
fvbfull = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbfull.bin", FileMode.Create)) );
fvbtrace = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbtrace.bin", FileMode.Create)) );
fvbtraceTex = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbtraceTex.bin", FileMode.Create)) );
fvbtraceUV0 = new BufferedBinaryWriterFloat( new BinaryWriter(File.Open(scenePath + "/vbtraceUV0.bin", FileMode.Create)) );
fib = new BufferedBinaryWriterInt( new BinaryWriter(File.Open(scenePath + "/ib.bin", FileMode.Create)) );
fib32 = new BinaryWriter(File.Open(scenePath + "/ib32.bin", FileMode.Create));
fib32lod = new BinaryWriter[sceneLodsUsed];
for(int i=0; i<sceneLodsUsed; i++)
{
fib32lod[i] = new BinaryWriter(File.Open(scenePath + "/ib32_lod" + i + ".bin", FileMode.Create));
if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("ib32_lod" + i + ".bin");
}
falphaidlod = new BinaryWriter[sceneLodsUsed];
for(int i=0; i<sceneLodsUsed; i++)
{
falphaidlod[i] = new BinaryWriter(File.Open(scenePath + "/alphaid_lod" + i + ".bin", FileMode.Create));
if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("alphaid2_lod" + i + ".bin"); // alphaid2, not alphaid
}
if (ftRenderLightmap.clientMode)
{
ftClient.serverFileList.Add("objects.bin");
ftClient.serverFileList.Add("mesh.bin");
ftClient.serverFileList.Add("lmid.bin");
ftClient.serverFileList.Add("seamfix.bin");
ftClient.serverFileList.Add("surf.bin");
ftClient.serverFileList.Add("matid.bin");
ftClient.serverFileList.Add("emissiveid.bin");
ftClient.serverFileList.Add("emissivemul.bin");
ftClient.serverFileList.Add("heightmapid.bin");
ftClient.serverFileList.Add("alphaid2.bin"); // alphaid2, not alphaid
ftClient.serverFileList.Add("alphabuffer.bin");
ftClient.serverFileList.Add("vbfull.bin");
ftClient.serverFileList.Add("vbtrace.bin");
ftClient.serverFileList.Add("vbtraceTex.bin");
ftClient.serverFileList.Add("vbtraceUV0.bin");
ftClient.serverFileList.Add("ib.bin");
ftClient.serverFileList.Add("ib32.bin");
}
// Export heightmap metadata
//fhmaps.Write(terrainObjectToActual.Count);
if (terrainObjectToActual.Count > 0)
{
//terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count];
/*for(int i=0; i<terrainObjectToHeightMap.Count; i++)
{
for(int fl=0; fl<6; fl++) fhmaps.Write(terrainObjectToBounds[i*6+fl]);
}*/
}
// Export some scene data
// - LMIDs
// - mesh definitions
// - surface definitions
// - albedo IDs
// - alpha IDs
// - update LMGroup bounds
// - export index buffer
// - generate tracing index buffer
areaLightCounter = -2;
//var defaultTexST = new Vector4(1,1,0,0);
//var objsToWriteTexST = new List<Vector4>();
for(int i=0; i<objsToWrite.Count; i++)
{
var obj = objsToWrite[i];
var lmgroup = objsToWriteGroup[i];
var holderObj = objsToWriteHolder[i];
if (obj == null)
{
// wtf
DebugLogError("Object " + objsToWriteNames[i] + " was destroyed mid-export");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
var mr = obj.GetComponent<Renderer>();
var m = GetSharedMesh(mr);
var inds = objsToWriteIndices[i];
// Write LMID, mesh and surface definition
int id = exportLMID(flmid, obj, lmgroup);
exportMesh(fmesh, m);
exportSurfs(fsurf, inds, inds.Length);// m);
int lodLevel;
if (!objToLodLevel.TryGetValue(obj, out lodLevel)) lodLevel = -1;
bool isTerrain = (exportTerrainAsHeightmap && obj.name == "__ExportTerrain");
// Write albedo IDs, collect alpha IDs, update LMGroup bounds
if (id >= 0) {
for(int k=0; k<m.subMeshCount; k++) {
// Get mesh albedos
int texID = -1;
Material mat = null;
Texture tex = null;
//var texST = defaultTexST;
if (k < mr.sharedMaterials.Length) {
mat = mr.sharedMaterials[k];
if (mat != null)
{
if (mat.HasProperty("_MainTex"))
{
tex = mat.mainTexture;
//if (mat.HasProperty("_MainTex_ST"))
//{
// texST = mat.GetVector("_MainTex_ST");
//}
}
else if (mat.HasProperty("_BaseColorMap"))
{
// HDRP
tex = mat.GetTexture("_BaseColorMap");
}
else if (mat.HasProperty("_BaseMap"))
{
// URP
tex = mat.GetTexture("_BaseMap");
}
}
}
IntPtr texPtr = (IntPtr)0;
Texture texWrite = null;
if (tex != null)
{
texPtr = tex.GetNativeTexturePtr();
texWrite = tex;
if (texPtr == (IntPtr)0)
{
if ((tex as RenderTexture) != null)
{
Debug.LogError("RenderTexture " + tex.name + " cannot be used as albedo (GetNativeTexturePtr returned null)");
tex = null;
texWrite = null;
}
else
{
Debug.LogError("Texture " + tex.name + " cannot be used as albedo (GetNativeTexturePtr returned null)");
tex = null;
texWrite = null;
}
}
}
if (tex == null)
{
// Create dummy 1px texture
var dummyTex = new Texture2D(1,1);
dummyPixelArray[0] = (mat == null || !mat.HasProperty("_Color")) ? Color.white : mat.color;
dummyTex.SetPixels(dummyPixelArray);
dummyTex.Apply();
texWrite = dummyTex;
dummyTexList.Add(dummyTex);
texPtr = dummyTex.GetNativeTexturePtr();
if (texPtr == (IntPtr)0)
{
Debug.LogError("Failed to call GetNativeTexturePtr() on newly created texture");
texWrite = null;
}
}
if (!albedoMap.TryGetValue(texPtr, out texID))
{
lmAlbedoList.Add(texPtr);
lmAlbedoListTex.Add(texWrite);
albedoMap[texPtr] = albedoCounter;
texID = albedoCounter;
albedoCounter++;
}
// Write albedo ID
fmatid.Write((ushort)texID);
// Get mesh alphas
ushort alphaID = 0xFFFF;
int alphaChannel = 3; // A
if (mat != null && mat.HasProperty("_TransparencyLM")) // will override _MainTex.a if present
{
var tex2 = mat.GetTexture("_TransparencyLM");
if (tex2 != null)
{
tex = tex2;
texPtr = tex.GetNativeTexturePtr();
alphaChannel = 0; // R
}
}
if (tex != null) {
var matTag = mat.GetTag("RenderType", true);
bool isCutout = matTag == "TransparentCutout";
if (isCutout || matTag == "Transparent" || matTag == "TreeLeaf") {
float alphaRef = 0.5f;
if (mat != null && mat.HasProperty("_Cutoff"))
{
alphaRef = mat.GetFloat("_Cutoff");
}
float opacity = 1.0f;
if (!isCutout && mat.HasProperty("_Color"))
{
opacity = mat.color.a;
}
// let constant alpha affect cutout theshold for alphablend materials
alphaRef = 1.0f - (1.0f - alphaRef) * opacity;
if (alphaRef > 1) alphaRef = 1;
// allow same map instances with different threshold
List<int> texIDs;
if (!alphaMap.TryGetValue(texPtr, out texIDs))
{
alphaMap[texPtr] = texIDs = new List<int>();
lmAlphaList.Add(texPtr);
lmAlphaListTex.Add(tex);
lmAlphaRefList.Add(alphaRef);
lmAlphaChannelList.Add(alphaChannel);
texIDs.Add(alphaCounter);
texID = alphaCounter;
alphaCounter++;
//Debug.Log("Alpha " + texID+": " + tex.name+" "+alphaRef);
alphaID = (ushort)texID;
}
else
{
int matchingInstance = -1;
for(int instance=0; instance<texIDs.Count; instance++)
{
texID = texIDs[instance];
if (Mathf.Abs(lmAlphaRefList[texID] - alphaRef) <= alphaInstanceThreshold)
{
if (lmAlphaChannelList[texID] == alphaChannel)
{
matchingInstance = instance;
alphaID = (ushort)texID;
break;
}
}
}
if (matchingInstance < 0)
{
lmAlphaList.Add(texPtr);
lmAlphaListTex.Add(tex);
lmAlphaRefList.Add(alphaRef);
lmAlphaChannelList.Add(alphaChannel);
texIDs.Add(alphaCounter);
texID = alphaCounter;
alphaCounter++;
//Debug.Log("Alpha " + texID+": " + tex.name+" "+alphaRef);
alphaID = (ushort)texID;
}
}
}
}
alphaIDs.Add(alphaID);
// Get mesh emissives
if (exportShaderColors)
{
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
while(storages[s].hasEmissive.Count <= id) storages[s].hasEmissive.Add(true);
storages[s].hasEmissive[id] = true;
}
}
texID = -1;
tex = null;
if (mat!=null && mat.shaderKeywords.Contains("_EMISSION"))
{
if (mat.HasProperty("_EmissionMap")) tex = mat.GetTexture("_EmissionMap");
if (tex != null)
{
texPtr = tex.GetNativeTexturePtr();
if (texPtr == (IntPtr)0)
{
if ((tex as RenderTexture) != null)
{
Debug.LogError("RenderTexture " + tex.name + " cannot be used as emission (GetNativeTexturePtr returned null)");
tex = null;
}
else
{
Debug.LogError("Texture " + tex.name + " cannot be used as emission (GetNativeTexturePtr returned null)");
tex = null;
}
//DebugLogError("Null emission tex ptr");
}
}
if (tex == null && mat.HasProperty("_EmissionColor"))
{
// Create dummy 1px texture
var dummyTex = new Texture2D(1,1);
dummyPixelArray[0] = mat.GetColor("_EmissionColor");
dummyTex.SetPixels(dummyPixelArray);
dummyTex.Apply();
tex = dummyTex;
dummyTexList.Add(dummyTex);
texPtr = dummyTex.GetNativeTexturePtr();
if (texPtr == (IntPtr)0)
{
Debug.LogError("Failed to call GetNativeTexturePtr() on newly created texture");
texWrite = null;
//DebugLogError("Null dummy tex ptr");
}
}
if (!albedoMap.TryGetValue(texPtr, out texID))
{
lmAlbedoList.Add(texPtr);
lmAlbedoListTex.Add(tex);
albedoMap[texPtr] = albedoCounter;
texID = albedoCounter;
albedoCounter++;
}
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
while(storages[s].hasEmissive.Count <= id) storages[s].hasEmissive.Add(false);
storages[s].hasEmissive[id] = true;
}
fmatide.Write((ushort)texID);
fmatideb.Write(mat.HasProperty("_EmissionColor") ? mat.GetColor("_EmissionColor").maxColorComponent : 1);
}
else
{
fmatide.Write((ushort)0xFFFF);
fmatideb.Write(0.0f);
}
if (isTerrain && uvgbHeightmap)
{
var hindex = terrainObjectList.IndexOf(obj.transform.parent.gameObject);
//var htex = terrainObjectToHeightMap[hindex];
//texPtr = htex.GetNativeTexturePtr();
//heightmapList.Add(texPtr);
//heightmapListTex.Add(htex);
//heightmapListBounds.Add();
//texID = heightmapCounter;
//heightmapCounter++;
fmatidh.Write((ushort)hindex);//texID);
}
else
{
fmatidh.Write((ushort)0xFFFF);
}
}
// Update LMGroup bounds
if (modifyLightmapStorage)
{
if (lmBounds[id].size == Vector3.zero) {
lmBounds[id] = mr.bounds;
} else {
var b = lmBounds[id];
b.Encapsulate(mr.bounds);
lmBounds[id] = b;
}
}
} else {
// Write empty albedo/alpha IDs for non-lightmapped
for(int k=0; k<m.subMeshCount; k++) {
fmatid.Write((ushort)0);
alphaIDs.Add(0xFFFF);
fmatide.Write((ushort)0xFFFF);
fmatideb.Write(0.0f);
fmatidh.Write((ushort)0xFFFF);
}
}
int currentVoffset = voffset;
voffset += objsToWriteVerticesPosW[i].Length;// m.vertexCount;
// Check if mesh is flipped
bool isFlipped = Mathf.Sign(obj.transform.lossyScale.x*obj.transform.lossyScale.y*obj.transform.lossyScale.z) < 0;
if (lmgroup != null && lmgroup.flipNormal) isFlipped = !isFlipped;
while(lmIndexArrays.Count <= id)
{
lmIndexArrays.Add(new List<int>());
lmLocalToGlobalIndices.Add(new List<int>());
lmVOffset.Add(0);
}
var mmr = obj.GetComponent<Renderer>();
var castsShadows = mmr.shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off;
if (exportTerrainAsHeightmap && obj.name == "__ExportTerrain") castsShadows = false; // prevent exporting placeholder quads to ftrace
time = GetTime();
for(int k=0;k<m.subMeshCount;k++) {
// Export regular index buffer
//var indexCount = exportIB(fib, m, k, isFlipped, false, 0, null, 0);
var indexCount = exportIB(fib, inds[k], isFlipped, false, 0, null, 0);
bool submeshCastsShadows = castsShadows;
if (submeshCastsShadows)
{
var mats = mmr.sharedMaterials;
if (mats.Length > k)
{
if (mats[k] != null)
{
var matTag = mats[k].GetTag("RenderType", true);
if (matTag == "Transparent" || matTag == "TreeLeaf")
{
if (mats[k].HasProperty("_Color"))
{
if (mats[k].color.a < 0.5f) submeshCastsShadows = false;
}
}
}
}
}
// Generate tracing index buffer, write alpha IDs per triangle
if (submeshCastsShadows)
{
var alphaID = alphaIDs[(alphaIDs.Count - m.subMeshCount) + k];
if (lodLevel < 0)
{
// Export persistent IB
var indicesOpaqueArray = indicesOpaque;
var indicesTransparentArray = indicesTransparent;
var falphaidFile = falphaid;
exportIB32(indicesOpaqueArray, indicesTransparentArray, id>=0 ? lmIndexArrays[id] : null,
inds[k], isFlipped, currentVoffset, id>=0 ? lmVOffset[id] : 0, falphaidFile, alphaID);
}
else
{
// Export LOD IBs
var visList = objToLodLevelVisible[obj];
for(int vlod=0; vlod<visList.Count; vlod++)
{
int lod = visList[vlod];
var indicesOpaqueArray = data.indicesOpaqueLOD[lod];
var indicesTransparentArray = data.indicesTransparentLOD[lod];
var falphaidFile = falphaidlod[lod];
exportIB32(indicesOpaqueArray, indicesTransparentArray, id>=0 ? lmIndexArrays[id] : null,
inds[k], isFlipped, currentVoffset, id>=0 ? lmVOffset[id] : 0, falphaidFile, alphaID);
}
}
}
ioffset += indexCount;
}
ibTime += GetTime() - time;
if (id >= 0)
{
var vcount = objsToWriteVerticesPosW[i].Length;//m.vertexCount;
var remapArray = lmLocalToGlobalIndices[id];
var addition = lmVOffset[id];
for(int k=0; k<vcount; k++)
{
remapArray.Add(k + currentVoffset);
}
lmVOffset[id] += vcount;
}
}
}
catch(Exception e)
{
DebugLogError("Error exporting scene - see console for details");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
Debug.LogError("Exception caught: " + e.ToString());
throw;
}
ProgressBarShow("Exporting scene - finishing objects...", 0.5f);
if (userCanceled)
{
CloseAllFiles();
ProgressBarEnd(true);
yield break;
}
yield return null;
try
{
// Write vertex buffers and update storage
Rect rc = new Rect();
var emptyVec4 = new Vector4(1,1,0,0);
for(int i=0; i<objsToWrite.Count; i++)
{
var obj = objsToWrite[i];
var m = GetSharedMesh(obj);
var lmgroup = objsToWriteGroup[i];
var id = lmgroup == null ? -1 : objsToWriteGroup[i].id;
BakeryLightMesh areaLight = obj.GetComponent<BakeryLightMesh>();
if (areaLight == null)
{
var areaIndex = temporaryAreaLightMeshList.IndexOf(obj);
if (areaIndex >= 0) areaLight = temporaryAreaLightMeshList2[areaIndex];
}
//var areaLight =
if (areaLight != null) id = areaLight.lmid;
var vertexBake = lmgroup != null ? (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex) : false;
//var castsShadows = obj.GetComponent<Renderer>().shadowCastingMode != UnityEngine.Rendering.ShadowCastingMode.Off;
var holderObj = objsToWriteHolder[i];
if (holderObj != null)
{
if (!holderRect.TryGetValue(holderObj, out rc))
{
holderObj = null;
}
}
time = GetTime();
//var vertices = m.vertices;
//var normals = m.normals;
//var tangents = m.tangents;
var uv = objsToWriteVerticesUV[i];//m.uv;
var uv2 = objsToWriteVerticesUV2[i];//m.uv2;
if (uv2.Length == 0 && !vertexBake) uv2 = uv;//m.uv;
vbTimeRead += GetTime() - time;
var inds = objsToWriteIndices[i];
var time2 = GetTime();
time = time2;
// Transform UVs
var tformedPos = objsToWriteVerticesPosW[i];// new Vector3[vertices.Length];
var tformedNormals = objsToWriteVerticesNormalW[i];// new Vector3[normals.Length];
Vector4[] tformedTangents = null;
if (NeedsTangents(lmgroup, tangentSHLights))
{
tformedTangents = objsToWriteVerticesTangentW[i];
}
Vector2[] tformedUV2;
if (areaLight == null && !vertexBake)
{
tformedUV2 = holderObj == null ? uv2 : new Vector2[tformedPos.Length];
for(int t=0; t<tformedPos.Length; t++)
{
if (holderObj != null)
{
tformedUV2[t].x = uv2[t].x * rc.width + rc.x;
tformedUV2[t].y = uv2[t].y * rc.height + rc.y;
}
}
objsToWriteUVOverride.Add(null);
}
else if (vertexBake)
{
tformedUV2 = GenerateVertexBakeUVs(lmgroup.vertexCounter, tformedPos.Length, lmgroup.totalVertexCount);
lmgroup.vertexCounter += tformedPos.Length;
objsToWriteUVOverride.Add(tformedUV2);
}
else
{
tformedUV2 = uv;
objsToWriteUVOverride.Add(null);
}
if (id >= 0)
{
while(lmUVArrays.Count <= id)
{
lmUVArrays.Add(new List<float>());
}
var lmUVArray = lmUVArrays[id];
for(int k=0; k<tformedUV2.Length; k++)
{
lmUVArray.Add(tformedUV2[k].x);
lmUVArray.Add(tformedUV2[k].y);
}
}
exportVBFull(fvbfull, tformedPos, tformedNormals, tformedTangents, uv, tformedUV2);
vbTimeWriteFull += GetTime() - time;
time = GetTime();
//if (castsShadows)
//{
exportVBTrace(fvbtrace, m, tformedPos, tformedNormals);
vbTimeWriteT += GetTime() - time;
time = GetTime();
exportVBTraceTexAttribs(vbtraceTexPosNormalArray, vbtraceTexUVArray, tformedPos, tformedNormals, tformedUV2, id, vertexBake, obj);
vbTimeWriteT2 += GetTime() - time;
time = GetTime();
exportVBTraceUV0(fvbtraceUV0, uv, tformedPos.Length);
vbTimeWriteT3 += GetTime() - time;
time = GetTime();
//}
voffset += tformedPos.Length;
vbTimeWrite += GetTime() - time2;
// update storage
// also write seamfix.bin
var sceneID = sceneToID[obj.scene];
if (obj.name == "__ExportTerrain")
{
fseamfix.Write(false);
var index = terrainObjectList.IndexOf(obj.transform.parent.gameObject);
var terrain = terrainObjectToActual[index];
var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y);
if (!storages[sceneID].bakedRenderersTerrain.Contains(terrain))
{
if (modifyLightmapStorage)
{
storages[sceneID].bakedRenderersTerrain.Add(terrain);
storages[sceneID].bakedIDsTerrain.Add(CorrectLMGroupID(id, lmgroup, groupList));
storages[sceneID].bakedScaleOffsetTerrain.Add(scaleOffset);
}
}
objsToWriteScaleOffset.Add(scaleOffset);
}
else
{
fseamfix.Write(true);
var scaleOffset = holderObj == null ? emptyVec4 : new Vector4(rc.width, rc.height, rc.x, rc.y);
if (modifyLightmapStorage)
{
bool vertexImplicit = false;
if (vertexBake)
{
if (lmgroup.isImplicit) vertexImplicit = true;
}
if (!vertexImplicit)
{
storages[sceneID].bakedRenderers.Add(obj.GetComponent<Renderer>());
storages[sceneID].bakedIDs.Add(CorrectLMGroupID(id, lmgroup, groupList));
storages[sceneID].bakedScaleOffset.Add(scaleOffset);
storages[sceneID].bakedVertexOffset.Add(vertexBake ? (lmgroup.vertexCounter - tformedPos.Length) : -1);
storages[sceneID].bakedVertexColorMesh.Add(null);
}
}
objsToWriteScaleOffset.Add(scaleOffset);
}
}
// Generate LOD UVs
if (ftRenderLightmap.giLodModeEnabled)
{
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
storages[s].lmGroupMinLOD = new int[groupList.Count];
storages[s].lmGroupLODMatrix = new int[groupList.Count * groupList.Count];
}
for(int i=0; i<groupList.Count; i++)
{
var lmgroup = groupList[i];
if (lmgroup.resolution < 128)
{
lmUVArrays2.Add(null);
lmIndexArrays2.Add(null);
lmUVArrays3.Add(null);
continue;
}
if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex || lmgroup.containsTerrains)
{
lmUVArrays2.Add(null);
lmIndexArrays2.Add(null);
lmUVArrays3.Add(null);
if (lmgroup.containsTerrains)
{
int minLodResolutionTerrain = 128;
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
int minLOD = (int)(Mathf.Log(lmgroup.resolution, 2.0f) - Mathf.Log(minLodResolutionTerrain, 2.0f)) - 1;
if (minLOD < 0) minLOD = 0;
storages[s].lmGroupMinLOD[lmgroup.id] = minLOD;
}
}
continue;
}
int id = lmgroup.id;
lmUVArrays2.Add(lmUVArrays[i].ToArray());
lmIndexArrays2.Add(lmIndexArrays[i].ToArray());
lmUVArrays3.Add(lmUVArrays[i].ToArray());
int uvIslands = uvrLoad(lmUVArrays2[i], lmUVArrays2[i].Length/2, lmIndexArrays2[i], lmIndexArrays2[i].Length);
if (uvIslands <= 0)
{
Debug.LogError("Can't generate LOD UVs for " + lmgroup.name+" "+lmUVArrays2[i].Length+" "+lmIndexArrays2[i].Length+" "+lmgroup.containsTerrains);
uvrUnload();
continue;
}
int minLodResolution = Mathf.NextPowerOfTwo((int)Mathf.Ceil(Mathf.Sqrt((float)uvIslands)));
minLodResolution = minLodResolution << 1;
if (minLodResolution > lmgroup.resolution)
{
Debug.LogWarning("Not generating LOD UVs for " + lmgroup.name + ", because there are too many UV islands");
uvrUnload();
continue;
}
Debug.Log("Min LOD resolution for " + lmgroup.name + " is " + minLodResolution);
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
int minLOD = (int)(Mathf.Log(lmgroup.resolution, 2.0f) - Mathf.Log(minLodResolution, 2.0f)) - 1;
if (minLOD < 0) minLOD = 0;
storages[s].lmGroupMinLOD[lmgroup.id] = minLOD;
}
int uvrErrCode = uvrRepack(0, minLodResolution);
if (uvrErrCode == -1)
{
Debug.LogError("Can't repack LOD UVs for " + lmgroup.name);
uvrUnload();
continue;
}
Debug.Log("Tries left: " + uvrErrCode);
uvrUnload();
var numLocalVerts = lmUVArrays2[i].Length / 2;
for(int k=0; k<numLocalVerts; k++)
{
float u = lmUVArrays2[i][k * 2];
u = Mathf.Clamp(u, 0, 0.99999f);
u += id * 10;
if (i >= 0 && (int)u > id*10)
{
Debug.LogError("Float overflow (GI LOD)");
}
lmUVArrays2[i][k * 2] = u;
}
}
}
// Write vbTraceTex
int numTraceVerts = vbtraceTexUVArray.Count/2;
for(int i=0; i<numTraceVerts; i++)
{
fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6]);
fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 1]);
fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 2]);
fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 3]);
fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 4]);
fvbtraceTex.Write(vbtraceTexPosNormalArray[i * 6 + 5]);
fvbtraceTex.Write(vbtraceTexUVArray[i * 2]);
fvbtraceTex.Write(vbtraceTexUVArray[i * 2 + 1]);
}
// Generate LOD UV buffer
if (ftRenderLightmap.giLodModeEnabled)
{
var uvBuffOffsets = new int[lmUVArrays3.Count];
var uvBuffLengths = new int[lmUVArrays3.Count];
int uvBuffSize = 0;
for(int i=0; i< lmUVArrays3.Count; i++)
{
if (lmUVArrays3[i] == null) continue;
uvBuffOffsets[i] = uvBuffSize;
uvBuffLengths[i] = lmUVArrays3[i].Length;
uvBuffSize += lmUVArrays3[i].Length;
}
var uvSrcBuff = new float[uvBuffSize];
var uvDestBuff = new float[uvBuffSize];
for(int i=0; i< lmUVArrays3.Count; i++)
{
if (lmUVArrays3[i] == null) continue;
var arr = lmUVArrays3[i];
var arr2 = lmUVArrays2[i];
var offset = uvBuffOffsets[i];
for(int j=0; j<arr.Length; j++)
{
uvSrcBuff[j + offset] = arr[j];
uvDestBuff[j + offset] = arr2[j];
}
}
var lmrIndicesOffsets = new int[lmIndexArrays2.Count];
var lmrIndicesLengths = new int[lmIndexArrays2.Count];
int lmrIndicesSize = 0;
for(int i=0; i< lmIndexArrays2.Count; i++)
{
if (lmIndexArrays2[i] == null) continue;
lmrIndicesOffsets[i] = lmrIndicesSize;
lmrIndicesLengths[i] = lmIndexArrays2[i].Length;
lmrIndicesSize += lmIndexArrays2[i].Length;
}
var lmrIndicesBuff = new int[lmrIndicesSize];
for(int i=0; i< lmIndexArrays2.Count; i++)
{
if (lmIndexArrays2[i] == null) continue;
var arr = lmIndexArrays2[i];
var offset = lmrIndicesOffsets[i];
for(int j=0; j<arr.Length; j++)
{
lmrIndicesBuff[j + offset] = arr[j];
}
}
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
storages[s].uvBuffOffsets = uvBuffOffsets;
storages[s].uvBuffLengths = uvBuffLengths;
storages[s].uvSrcBuff = uvSrcBuff;
storages[s].uvDestBuff = uvDestBuff;
storages[s].lmrIndicesOffsets = lmrIndicesOffsets;
storages[s].lmrIndicesLengths = lmrIndicesLengths;
storages[s].lmrIndicesBuff = lmrIndicesBuff;
}
vbtraceTexUVArrayLOD = new float[vbtraceTexUVArray.Count];
for(int i=0; i<groupList.Count; i++)
{
var lmgroup = groupList[i];
if (lmgroup.resolution < 128) continue;
if (lmgroup.mode == BakeryLightmapGroup.ftLMGroupMode.Vertex || lmgroup.containsTerrains) continue;
var remapArray = lmLocalToGlobalIndices[i];
var uvArray = lmUVArrays2[i];
for(int j=0; j<remapArray.Count; j++)
{
vbtraceTexUVArrayLOD[remapArray[j]*2] = uvArray[j*2];
vbtraceTexUVArrayLOD[remapArray[j]*2+1] = uvArray[j*2+1];
}
}
}
// Write tracing index buffer
fib32.Write(indicesOpaque.Count); // firstAlphaTriangle
for(int i=0; i<indicesOpaque.Count; i++) fib32.Write(indicesOpaque[i]); // opaque triangles
for(int i=0; i<indicesTransparent.Count; i++) fib32.Write(indicesTransparent[i]); // alpha triangles
// Write scene LOD tracing index buffers
for(int lod=0; lod<sceneLodsUsed; lod++)
{
var indicesOpaqueArray = data.indicesOpaqueLOD[lod];
var indicesTransparentArray = data.indicesTransparentLOD[lod];
fib32lod[lod].Write(indicesOpaqueArray.Count);
for(int i=0; i<indicesOpaqueArray.Count; i++) fib32lod[lod].Write(indicesOpaqueArray[i]); // opaque triangles
for(int i=0; i<indicesTransparentArray.Count; i++) fib32lod[lod].Write(indicesTransparentArray[i]); // alpha triangles
}
Debug.Log("Wrote binaries in " + ((GetTime() - totalTime)/1000.0) + "s");
Debug.Log("VB read time " + (vbTimeRead/1000.0) + "s");
Debug.Log("VB write time " + (vbTimeWrite/1000.0) + "s");
Debug.Log("VB write time (full) " + (vbTimeWriteFull/1000.0) + "s");
Debug.Log("VB write time (trace) " + (vbTimeWriteT/1000.0) + "s");
Debug.Log("VB write time (trace tex) " + (vbTimeWriteT2/1000.0) + "s");
Debug.Log("VB write time (UV0) " + (vbTimeWriteT3/1000.0) + "s");
Debug.Log("IB time " + (ibTime/1000.0) + "s");
fscene.Write(objsToWrite.Count);
int meshID = 0;
foreach(var obj in objsToWrite) {
fscene.Write(meshID);
meshID++;
}
foreach(var obj in objsToWrite) {
fscene.Write(obj.name);
}
fscene.Close();
fmesh.Close();
flmid.Close();
fseamfix.Close();
fsurf.Close();
fmatid.Close();
fmatide.Close();
fmatideb.Close();
fmatidh.Close();
fvbfull.Close();
fvbtrace.Close();
fvbtraceTex.Close();
fvbtraceUV0.Close();
fib.Close();
fib32.Close();
falphaid.Close();
fhmaps.Close();
if (fib32lod != null)
{
for(int i=0; i<fib32lod.Length; i++) fib32lod[i].Close();
}
if (falphaidlod != null)
{
for(int i=0; i<falphaidlod.Length; i++) falphaidlod[i].Close();
}
if (modifyLightmapStorage)
{
for(int s=0; s<sceneCount; s++)
{
if (storages[s] == null) continue;
storages[s].bounds = lmBounds;
}
}
//}
startMsU = GetTime();
}
catch(Exception e)
{
DebugLogError("Error exporting scene - see console for details");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
Debug.LogError("Exception caught: " + e.ToString());
throw;
}
if (exportShaderColors && renderTextures)
{
yield return null;
ProgressBarShow("Exporting scene - shaded surface colors...", 0.55f);
for(int g=0; g<groupList.Count; g++)
{
var str = storages[data.firstNonNullStorage];
if (str == null) Debug.LogError("storages[data.firstNonNullStorage] == null");
if (str.hasEmissive == null) Debug.LogError("storages[data.firstNonNullStorage].hasEmissive == null");
if (groupList[g] == null) Debug.LogError("group is null");
var hasEmissive = str.hasEmissive.Count > groupList[g].id && str.hasEmissive[groupList[g].id];
bool vertexBake = groupList[g].mode == BakeryLightmapGroup.ftLMGroupMode.Vertex;
int res = groupList[g].resolution;
if (vertexBake)
{
if (groupList[g].totalVertexCount == 0)
{
DebugLogError("Vertex lightmap group " + groupList[g].name + " has 0 static vertices. Make sure objects inside the group don't all have Scale In Lightmap == 0.");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
int atlasTexSize = (int)Mathf.Ceil(Mathf.Sqrt((float)groupList[g].totalVertexCount));
atlasTexSize = (int)Mathf.Ceil(atlasTexSize / (float)ftRenderLightmap.tileSize) * ftRenderLightmap.tileSize;
res = atlasTexSize;
}
var bakeWithNormalMaps = (groupList[g].renderDirMode == BakeryLightmapGroup.RenderDirMode.BakedNormalMaps) ?
true : (ftRenderLightmap.renderDirMode == ftRenderLightmap.RenderDirMode.BakedNormalMaps);
if (groupList[g].probes) bakeWithNormalMaps = false;
ftUVGBufferGen.StartUVGBuffer(res, hasEmissive, bakeWithNormalMaps);
for(int i=0; i<objsToWrite.Count; i++)
{
var obj = objsToWrite[i];
var lmgroup = objsToWriteGroup[i];
if (lmgroup == null) continue;
if (lmgroup.id != groupList[g].id) continue;
if (obj.GetComponent<BakeryLightMesh>()) continue;
var bakedMesh = GetSharedMeshBaked(obj);
ftUVGBufferGen.RenderUVGBuffer(bakedMesh,
obj.GetComponent<Renderer>(),
objsToWriteScaleOffset[i],
obj.transform.localToWorldMatrix,
vertexBake,
objsToWriteUVOverride[i],
bakeWithNormalMaps && !exportTerrainAsHeightmap && obj.name == "__ExportTerrain");
}
ftUVGBufferGen.EndUVGBuffer();
var albedo = ftUVGBufferGen.texAlbedo;
var emissive = ftUVGBufferGen.texEmissive;
var normal = ftUVGBufferGen.texNormal;
if (hasEmissive)
{
//albedo = ftUVGBufferGen.GetAlbedoWithoutEmissive(ftUVGBufferGen.texAlbedo, ftUVGBufferGen.texEmissive);
//if ((unityVersionMajor == 2017 && unityVersionMinor < 2) || unityVersionMajor < 2017)
//{
#if UNITY_2017_2_OR_NEWER
#else
// Unity before 2017.2: emissive packed to RGBM
// Unity after 2017.2: linear emissive
emissive = ftUVGBufferGen.DecodeFromRGBM(emissive);
#endif
//}
if (ftRenderLightmap.hackEmissiveBoost != 1.0f)
{
ftUVGBufferGen.Multiply(emissive, ftRenderLightmap.hackEmissiveBoost);
}
if (!vertexBake) ftUVGBufferGen.Dilate(emissive);
}
if (!vertexBake) ftUVGBufferGen.Dilate(albedo);
SaveGBufferMap(albedo.GetNativeTexturePtr(),
scenePath + "/uvalbedo_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"),
ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(5);
//if (g==2) storage.debugTex = emissive;
yield return null;
if (hasEmissive)
{
SaveGBufferMap(emissive.GetNativeTexturePtr(),
scenePath + "/uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"),
ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(5);
yield return null;
if (ftRenderLightmap.clientMode) ftClient.serverFileList.Add("uvemissive_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"));
}
if (bakeWithNormalMaps)
{
SaveGBufferMap(normal.GetNativeTexturePtr(),
scenePath + "/uvnormal_" + groupList[g].name + (ftRenderLightmap.compressedGBuffer ? ".lz4" : ".dds"),
ftRenderLightmap.compressedGBuffer);
GL.IssuePluginEvent(5);
yield return null;
}
}
}
ProgressBarShow(exportShaderColors ? "Exporting scene - alpha buffer..." : "Exporting scene - UV GBuffer and alpha buffer...", 0.55f);
if (userCanceled)
{
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
yield return null;
InitShaders();
LoadScene(scenePath);
// Force load textures to VRAM
var forceRt = new RenderTexture(1, 1, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
var forceTex = new Texture2D(1, 1, TextureFormat.ARGB32, false, false);
if (!exportShaderColors)
{
for(int i=0; i<lmAlbedoListTex.Count; i++)
{
Graphics.Blit(lmAlbedoListTex[i] as Texture2D, forceRt);
Graphics.SetRenderTarget(forceRt);
forceTex.ReadPixels(new Rect(0,0,1, 1), 0, 0, true);
forceTex.Apply();
lmAlbedoList[i] = lmAlbedoListTex[i].GetNativeTexturePtr();
}
}
for(int i=0; i<lmAlphaListTex.Count; i++)
{
Graphics.Blit(lmAlphaListTex[i] as Texture2D, forceRt);
Graphics.SetRenderTarget(forceRt);
forceTex.ReadPixels(new Rect(0,0,1, 1), 0, 0, true);
forceTex.Apply();
lmAlphaList[i] = lmAlphaListTex[i].GetNativeTexturePtr();
}
if (exportShaderColors)
{
if (terrainObjectToActual.Count > 0)
{
//for(int i=0; i<heightmapListTex.Count; i++)
terrainObjectToHeightMapPtr = new IntPtr[terrainObjectToHeightMap.Count];
for(int i=0; i<terrainObjectToHeightMap.Count; i++)
{
Graphics.Blit(terrainObjectToHeightMap[i] as Texture2D, forceRt);
Graphics.SetRenderTarget(forceRt);
forceTex.ReadPixels(new Rect(0,0,1, 1), 0, 0, true);
forceTex.Apply();
terrainObjectToHeightMapPtr[i] = terrainObjectToHeightMap[i].GetNativeTexturePtr();
}
SetAlbedos(terrainObjectToHeightMap.Count, terrainObjectToHeightMapPtr);
int cerr = CopyAlbedos();
if (cerr != 0)
{
DebugLogError("Failed to copy textures (" + cerr + ")");
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
}
else
{
SetAlbedos(0, null);
}
}
else
{
SetAlbedos(lmAlbedoList.Count, lmAlbedoList.ToArray());
}
SetAlphas(lmAlphaList.Count, lmAlphaList.ToArray(), lmAlphaRefList.ToArray(), lmAlphaChannelList.ToArray(), sceneLodsUsed, flipAlpha);
GL.IssuePluginEvent(6); // render alpha buffer
int uerr = 0;
while(uerr == 0)
{
uerr = GetABGErrorCode();
yield return null;
}
/*yield return new WaitForEndOfFrame();
int uerr = ftGenerateAlphaBuffer();*/
if (uerr != 0 && uerr != 99999)
{
DebugLogError("ftGenerateAlphaBuffer error: " + uerr);
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
if (!renderTextures)
{
ProgressBarEnd(true);//false);
yield break;
}
//ProgressBarShow("Exporting scene - UV GBuffer...", 0.8f);
//if (userCanceled) yield break;
//yield return null;
//GL.IssuePluginEvent(1); // render UV GBuffer
//yield return new WaitForEndOfFrame();
SetFixPos(false);//true); // do it manually
SetCompression(ftRenderLightmap.compressedGBuffer);
if (!exportShaderColors)
{
uerr = ftRenderUVGBuffer();
if (uerr != 0)
{
DebugLogError("ftRenderUVGBuffer error: " + uerr);
CloseAllFiles();
userCanceled = true;
ProgressBarEnd(true);
yield break;
}
}
ms = GetTime();
Debug.Log("UVGB/fixPos/alpha time: " + ((ms - startMsU) / 1000.0) + " seconds");
ProgressBarEnd(true);
Debug.Log("Scene export finished");
}
int countChildrenFlat(Transform tform)
{
int count = 1;
foreach(Transform t in tform)
{
count += countChildrenFlat(t);
}
return count;
}
}
#endif