//----------------------------------------------
// MeshBaker
// Copyright © 2011-2012 Ian Deane
//----------------------------------------------
using UnityEngine;
using System.Collections;
using System.Collections.Specialized;
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using DigitalOpus.MB.Core;
using LuaFramework;
///
/// Component that handles baking materials into a combined material.
///
/// The result of the material baking process is a MB2_TextureBakeResults object, which
/// becomes the input for the mesh baking.
///
/// This class uses the MB_TextureCombiner to do the combining.
///
/// This class is a Component (MonoBehavior) so it is serialized and found using GetComponent. If
/// you want to access the texture baking functionality without creating a Component then use MB_TextureCombiner
/// directly.
///
public class MB3_TextureBaker : MB3_MeshBakerRoot {
public MB2_LogLevel LOG_LEVEL = MB2_LogLevel.info;
[SerializeField] protected MB2_TextureBakeResults _textureBakeResults;
public override MB2_TextureBakeResults textureBakeResults{
get{return _textureBakeResults;}
set{_textureBakeResults = value;}
}
[SerializeField] protected int _atlasPadding = 1;
public virtual int atlasPadding{
get{return _atlasPadding;}
set{_atlasPadding = value;}
}
[SerializeField] protected int _maxAtlasSize = 4096;
public virtual int maxAtlasSize{
get{return _maxAtlasSize;}
set{_maxAtlasSize = value;}
}
[SerializeField] protected bool _resizePowerOfTwoTextures = false;
public virtual bool resizePowerOfTwoTextures{
get{return _resizePowerOfTwoTextures;}
set{_resizePowerOfTwoTextures = value;}
}
[SerializeField] protected bool _fixOutOfBoundsUVs = false;
public virtual bool fixOutOfBoundsUVs{
get{return _fixOutOfBoundsUVs;}
set{_fixOutOfBoundsUVs = value;}
}
[SerializeField] protected int _maxTilingBakeSize = 1024;
public virtual int maxTilingBakeSize{
get{return _maxTilingBakeSize;}
set{_maxTilingBakeSize = value;}
}
[SerializeField] protected MB2_PackingAlgorithmEnum _packingAlgorithm = MB2_PackingAlgorithmEnum.MeshBakerTexturePacker;
public virtual MB2_PackingAlgorithmEnum packingAlgorithm{
get{return _packingAlgorithm;}
set{_packingAlgorithm = value;}
}
[SerializeField] protected bool _meshBakerTexturePackerForcePowerOfTwo = true;
public bool meshBakerTexturePackerForcePowerOfTwo{
get{return _meshBakerTexturePackerForcePowerOfTwo;}
set{_meshBakerTexturePackerForcePowerOfTwo = value;}
}
[SerializeField] protected List _customShaderProperties = new List();
public virtual List customShaderProperties{
get{return _customShaderProperties;}
set{_customShaderProperties = value;}
}
//this is depricated
[SerializeField] protected List _customShaderPropNames_Depricated = new List();
public virtual List customShaderPropNames{
get{return _customShaderPropNames_Depricated;}
set{_customShaderPropNames_Depricated = value;}
}
[SerializeField] protected bool _doMultiMaterial;
public virtual bool doMultiMaterial{
get{return _doMultiMaterial;}
set{_doMultiMaterial = value;}
}
[SerializeField] protected Material _resultMaterial;
public virtual Material resultMaterial{
get{return _resultMaterial;}
set{_resultMaterial = value;}
}
public MB_MultiMaterial[] resultMaterials = new MB_MultiMaterial[0];
public List objsToMesh; //todo make this Renderer
public override List GetObjectsToCombine(){
if (objsToMesh == null) objsToMesh = new List();
return objsToMesh;
}
public MB_AtlasesAndRects[] CreateAtlases(){
return CreateAtlases(null, false, null);
}
///
/// Creates the atlases.
///
///
/// The atlases.
///
///
/// Progress info is a delegate function that displays a progress dialog. Can be null
///
///
/// if true atlases are saved as assets in the project folder. Othersise they are instances in memory
///
///
/// Texture format tracker. Contains editor functionality such as save assets. Can be null.
///
public MB_AtlasesAndRects[] CreateAtlases(ProgressUpdateDelegate progressInfo, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods=null){
MB_AtlasesAndRects[] mAndAs = null;
try{
mAndAs = _CreateAtlases(progressInfo, saveAtlasesAsAssets, editorMethods);
} catch(Exception e){
Debug.LogError(e);
} finally {
if (saveAtlasesAsAssets){ //Atlases were saved to project so we don't need these ones
if (mAndAs != null){
for(int j = 0; j < mAndAs.Length; j++){
MB_AtlasesAndRects mAndA = mAndAs[j];
if (mAndA != null && mAndA.atlases != null){
for (int i = 0; i < mAndA.atlases.Length; i++){
if (mAndA.atlases[i] != null){
if (editorMethods != null) editorMethods.Destroy(mAndA.atlases[i]);
else MB_Utility.Destroy(mAndA.atlases[i]);
}
}
}
}
}
}
}
return mAndAs;
}
MB_AtlasesAndRects[] _CreateAtlases(ProgressUpdateDelegate progressInfo, bool saveAtlasesAsAssets = false, MB2_EditorMethodsInterface editorMethods=null){
//validation
if (saveAtlasesAsAssets && editorMethods == null){
LogManager.LogError("Error in CreateAtlases If saveAtlasesAsAssets = true then editorMethods cannot be null.");
return null;
}
if (saveAtlasesAsAssets && !Application.isEditor){
LogManager.LogError("Error in CreateAtlases If saveAtlasesAsAssets = true it must be called from the Unity Editor.");
return null;
}
MB2_ValidationLevel vl = Application.isPlaying ? MB2_ValidationLevel.quick : MB2_ValidationLevel.robust;
if (!DoCombinedValidate(this, MB_ObjsToCombineTypes.dontCare, editorMethods, vl)){
return null;
}
if (_doMultiMaterial && !_ValidateResultMaterials()){
return null;
} else if (!_doMultiMaterial){
if (_resultMaterial == null){
LogManager.LogError("Combined Material is null please create and assign a result material.");
return null;
}
Shader targShader = _resultMaterial.shader;
for (int i = 0; i < objsToMesh.Count; i++){
Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]);
for (int j = 0; j < ms.Length; j++){
Material m = ms[j];
if (m != null && m.shader != targShader){
LogManager.LogWarning("Game object " + objsToMesh[i] + " does not use shader " + targShader + " it may not have the required textures. If not 2x2 clear textures will be generated.");
}
}
}
}
for (int i = 0; i < objsToMesh.Count; i++){
Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]);
for (int j = 0; j < ms.Length; j++){
Material m = ms[j];
if (m == null){
LogManager.LogError("Game object " + objsToMesh[i] + " has a null material. Can't build atlases");
return null;
}
}
}
MB3_TextureCombiner combiner = new MB3_TextureCombiner();
combiner.LOG_LEVEL = LOG_LEVEL;
combiner.atlasPadding = _atlasPadding;
combiner.maxAtlasSize = _maxAtlasSize;
combiner.customShaderPropNames = _customShaderProperties;
combiner.fixOutOfBoundsUVs = _fixOutOfBoundsUVs;
combiner.maxTilingBakeSize = _maxTilingBakeSize;
combiner.packingAlgorithm = _packingAlgorithm;
combiner.meshBakerTexturePackerForcePowerOfTwo = _meshBakerTexturePackerForcePowerOfTwo;
combiner.resizePowerOfTwoTextures = _resizePowerOfTwoTextures;
combiner.saveAtlasesAsAssets = saveAtlasesAsAssets;
// if editor analyse meshes and suggest treatment
if (!Application.isPlaying){
Material[] rms;
if (_doMultiMaterial){
rms = new Material[resultMaterials.Length];
for (int i = 0; i < rms.Length; i++) rms[i] = resultMaterials[i].combinedMaterial;
} else {
rms = new Material[1];
rms[0] = _resultMaterial;
}
combiner.SuggestTreatment(objsToMesh, rms, combiner.customShaderPropNames);
}
//initialize structure to store results
int numResults = 1;
if (_doMultiMaterial) numResults = resultMaterials.Length;
MB_AtlasesAndRects[] resultAtlasesAndRects = new MB_AtlasesAndRects[numResults];
for (int i = 0; i < resultAtlasesAndRects.Length; i++){
resultAtlasesAndRects[i] = new MB_AtlasesAndRects();
}
//Do the material combining.
for (int i = 0; i < resultAtlasesAndRects.Length; i++){
Material resMatToPass = null;
List sourceMats = null;
if (_doMultiMaterial) {
sourceMats = resultMaterials[i].sourceMaterials;
resMatToPass = resultMaterials[i].combinedMaterial;
} else {
resMatToPass = _resultMaterial;
}
LogManager.Log("Creating atlases for result material " + resMatToPass);
if(!combiner.CombineTexturesIntoAtlases(progressInfo, resultAtlasesAndRects[i], resMatToPass, objsToMesh, sourceMats, editorMethods)){
return null;
}
}
//Save the results
textureBakeResults.combinedMaterialInfo = resultAtlasesAndRects;
textureBakeResults.doMultiMaterial = _doMultiMaterial;
textureBakeResults.resultMaterial = _resultMaterial;
textureBakeResults.resultMaterials = resultMaterials;
textureBakeResults.fixOutOfBoundsUVs = combiner.fixOutOfBoundsUVs;
unpackMat2RectMap(textureBakeResults);
//set the texture bake resultAtlasesAndRects on the Mesh Baker component if it exists
MB3_MeshBakerCommon[] mb = GetComponentsInChildren();
for (int i = 0; i < mb.Length; i++){
mb[i].textureBakeResults = textureBakeResults;
}
if (LOG_LEVEL >= MB2_LogLevel.info) LogManager.Log("Created Atlases");
return resultAtlasesAndRects;
}
void unpackMat2RectMap(MB2_TextureBakeResults tbr){
List ms = new List();
List mss = new List();
List rs = new List();
for (int i = 0; i < tbr.combinedMaterialInfo.Length; i++){
MB_AtlasesAndRects newMesh = tbr.combinedMaterialInfo[i];
List map = newMesh.mat2rect_map;
for(int j = 0; j < map.Count; j++){
mss.Add(map[j]);
ms.Add(map[j].material);
rs.Add(map[j].atlasRect);
}
}
tbr.materialsAndUVRects = mss.ToArray();
tbr.materials = ms.ToArray();
tbr.prefabUVRects = rs.ToArray();
}
public static void ConfigureNewMaterialToMatchOld(Material newMat, Material original){
if (original == null){
LogManager.LogWarning("Original material is null, could not copy properties to " + newMat + ". Setting shader to " + newMat.shader);
return;
}
newMat.shader = original.shader;
newMat.CopyPropertiesFromMaterial(original);
ShaderTextureProperty[] texPropertyNames = MB3_TextureCombiner.shaderTexPropertyNames;
for (int j = 0; j < texPropertyNames.Length; j++){
Vector2 scale = Vector2.one;
Vector2 offset = Vector2.zero;
if (newMat.HasProperty(texPropertyNames[j].name)){
newMat.SetTextureOffset(texPropertyNames[j].name,offset);
newMat.SetTextureScale(texPropertyNames[j].name,scale);
}
}
}
string PrintSet(HashSet s){
StringBuilder sb = new StringBuilder();
foreach(Material m in s){
sb.Append( m + ",");
}
return sb.ToString();
}
bool _ValidateResultMaterials(){
HashSet allMatsOnObjs = new HashSet();
for (int i = 0; i < objsToMesh.Count; i++){
if (objsToMesh[i] != null){
Material[] ms = MB_Utility.GetGOMaterials(objsToMesh[i]);
for (int j = 0; j < ms.Length; j++){
if (ms[j] != null) allMatsOnObjs.Add(ms[j]);
}
}
}
HashSet allMatsInMapping = new HashSet();
for (int i = 0; i < resultMaterials.Length; i++){
MB_MultiMaterial mm = resultMaterials[i];
if (mm.combinedMaterial == null){
LogManager.LogError("Combined Material is null please create and assign a result material.");
return false;
}
Shader targShader = mm.combinedMaterial.shader;
for (int j = 0; j < mm.sourceMaterials.Count; j++){
if (mm.sourceMaterials[j] == null){
LogManager.LogError("There are null entries in the list of Source Materials");
return false;
}
if (targShader != mm.sourceMaterials[j].shader){
LogManager.LogWarning("Source material " + mm.sourceMaterials[j] + " does not use shader " + targShader + " it may not have the required textures. If not empty textures will be generated.");
}
if (allMatsInMapping.Contains(mm.sourceMaterials[j])){
LogManager.LogError("A Material " + mm.sourceMaterials[j] + " appears more than once in the list of source materials in the source material to combined mapping. Each source material must be unique.");
return false;
}
allMatsInMapping.Add(mm.sourceMaterials[j]);
}
}
if (allMatsOnObjs.IsProperSubsetOf(allMatsInMapping)){
allMatsInMapping.ExceptWith(allMatsOnObjs);
LogManager.LogWarning("There are materials in the mapping that are not used on your source objects: " + PrintSet(allMatsInMapping));
}
if (allMatsInMapping.IsProperSubsetOf(allMatsOnObjs)){
allMatsOnObjs.ExceptWith(allMatsInMapping);
LogManager.LogError("There are materials on the objects to combine that are not in the mapping: " + PrintSet(allMatsOnObjs));
return false;
}
return true;
}
}