1023 lines
36 KiB
C#
1023 lines
36 KiB
C#
using log4net.Util;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using TexturePacker;
|
|
using Unity.Collections;
|
|
using Unity.Mathematics;
|
|
using UnityEditor;
|
|
using UnityEditor.Formats.Fbx.Exporter;
|
|
using UnityEngine;
|
|
using UnityEngine.Experimental.Rendering;
|
|
using UnityEngine.Rendering;
|
|
using UnityEngine.UIElements;
|
|
|
|
public sealed class TexturePackerEditor : EditorWindow
|
|
{
|
|
[SerializeField]
|
|
private VisualTreeAsset m_VisualTreeAsset = default;
|
|
private TextureMapInfo textureMapInfo;
|
|
|
|
private Texture2D textureAtlas;
|
|
private Texture2DArray textureArray;
|
|
|
|
private IMGUIContainer imageListContainer;
|
|
private List<Texture2D> selectedTexs = new();
|
|
private List<Rect> texRects = new();
|
|
|
|
private static Type gameObjectInspectorType;
|
|
private List<Editor> gameObjectInspectorView = new();
|
|
private List<UnityEngine.Object> models = new();
|
|
private List<Rect> modelRects = new();
|
|
private Button btApplyMesh;
|
|
|
|
private Vector2 cellSize = new Vector2(100, 100);
|
|
private const int MaxArraySize = 255;
|
|
|
|
private int selectIndex = -1;
|
|
private int selectIndexForMove = -1;
|
|
private Vector2 toolbarOffset = new Vector2(0, 35);
|
|
|
|
private int atlasPadding = 0;
|
|
private static Func<UnityEngine.Object, bool, EditorWindow> fnOpenPropertyEditor;
|
|
private int textureSize = 512;
|
|
private EditorWindow atlasPropertiesWindow = null;
|
|
private EditorWindow arrayPropertiesWindow = null;
|
|
private Label imgCntLabel = null;
|
|
private Label infoLabel = null;
|
|
private GraphicsFormat graphicsFormat = GraphicsFormat.R32G32B32A32_SFloat;
|
|
|
|
private bool dockPreviewWindowFail = false;
|
|
|
|
public enum ETextureSize
|
|
{
|
|
_128 = 1 << 7,
|
|
_256 = 1 << 8,
|
|
_512 = 1 << 9,
|
|
_1024 = 1 << 10,
|
|
}
|
|
|
|
static readonly object fbxExportSetting = null;
|
|
static Delegate exportObjectFunc;
|
|
|
|
static TexturePackerEditor()
|
|
{
|
|
var editorCore = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name.Equals("UnityEditor.CoreModule")).FirstOrDefault();
|
|
var type = editorCore.GetType("UnityEditor.PropertyEditor");
|
|
var methodInfo = type.GetMethod("OpenPropertyEditor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, null, new Type[] { typeof(UnityEngine.Object), typeof(bool) }, null);
|
|
fnOpenPropertyEditor = methodInfo.CreateDelegate(typeof(Func<UnityEngine.Object, bool, EditorWindow>)) as Func<UnityEngine.Object, bool, EditorWindow>;
|
|
gameObjectInspectorType = editorCore.GetType("UnityEditor.GameObjectInspector");
|
|
|
|
var fbxAssembly = Assembly.GetAssembly(typeof(ModelExporter));
|
|
|
|
var exportMethodInfo = typeof(ModelExporter).GetMethod("ExportObject", BindingFlags.NonPublic | BindingFlags.Static);
|
|
exportObjectFunc = exportMethodInfo.CreateDelegate(Expression.GetDelegateType(exportMethodInfo.GetParameters().Select(p => p.ParameterType).Concat(new[] { exportMethodInfo.ReturnType }).ToArray()));
|
|
Type exportModelSettingsSerialize = fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportModelSettingsSerialize");
|
|
fbxExportSetting = Activator.CreateInstance(exportModelSettingsSerialize);
|
|
|
|
foreach (var item in exportModelSettingsSerialize.GetMethods().Union(exportModelSettingsSerialize.BaseType.GetMethods()))
|
|
{
|
|
switch (item.Name)
|
|
{
|
|
case "SetExportFormat":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { 1 });
|
|
item.Invoke(fbxExportSetting, new object[] { Enum.Parse(fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportSettings+ExportFormat"), "1") });
|
|
break;
|
|
}
|
|
case "SetAnimatedSkinnedMesh":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { false });
|
|
break;
|
|
}
|
|
case "SetUseMayaCompatibleNames":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { false });
|
|
break;
|
|
}
|
|
case "SetModelAnimIncludeOption":
|
|
{
|
|
|
|
item.Invoke(fbxExportSetting, new object[] { Enum.Parse(fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportSettings+Include"), "0") });
|
|
break;
|
|
}
|
|
case "SetLODExportType":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { Enum.Parse(fbxAssembly.GetType("UnityEditor.Formats.Fbx.Exporter.ExportSettings+LODExportType"), "0") });
|
|
break;
|
|
}
|
|
case "SetEmbedTextures":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { false });
|
|
break;
|
|
}
|
|
case "SetPreserveImportSettings":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { false });
|
|
break;
|
|
}
|
|
case "SetKeepInstances":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { false });
|
|
break;
|
|
}
|
|
case "SetExportUnredererd":
|
|
{
|
|
item.Invoke(fbxExportSetting, new object[] { false });
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
[MenuItem("Tools/Performance/TexturePackerEditor")]
|
|
public static void OpenTexturePackerEditor()
|
|
{
|
|
TexturePackerEditor wnd = CreateInstance<TexturePackerEditor>();
|
|
wnd.titleContent = new GUIContent("TexturePackerEditor");
|
|
wnd.Show();
|
|
}
|
|
|
|
public static void OpenTexturePackerEditor(Texture2D[] textures)
|
|
{
|
|
TexturePackerEditor wnd = CreateInstance<TexturePackerEditor>();
|
|
wnd.titleContent = new GUIContent("TexturePackerEditor");
|
|
for (int i = 0; i < textures.Length; i++)
|
|
{
|
|
wnd.AddTexture(textures[i]);
|
|
}
|
|
wnd.Show();
|
|
}
|
|
|
|
[UnityEditor.Callbacks.OnOpenAsset(0)]
|
|
private static bool OnOpenTextureMapInfo(int instanceID, int line)
|
|
{
|
|
var textureMapInfo = EditorUtility.InstanceIDToObject(instanceID) as TextureMapInfo;
|
|
if (!textureMapInfo)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TexturePackerEditor wnd = GetWindow<TexturePackerEditor>();
|
|
wnd.titleContent = new GUIContent("TexturePackerEditor");
|
|
wnd.InitWithTextureMapInfo(textureMapInfo);
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
wnd.UpdateResult();
|
|
};
|
|
return false;
|
|
}
|
|
|
|
private static readonly Color btNormalColor = ColorUtils.ToRGBA(0xFF585858);
|
|
private static readonly Color btClikedColor = ColorUtils.ToRGBA(0xFF46607C);
|
|
|
|
public void CreateGUI()
|
|
{
|
|
// Each editor window contains a root VisualElement object
|
|
VisualElement root = rootVisualElement;
|
|
|
|
// Instantiate UXML
|
|
VisualElement labelFromUXML = m_VisualTreeAsset.Instantiate();
|
|
root.Add(labelFromUXML);
|
|
var btArray = root.Q<Button>("btArray");
|
|
var btAtlas = root.Q<Button>("btAtlas");
|
|
var btSave = root.Q<Button>("btSave");
|
|
btApplyMesh = root.Q<Button>("btApplyMesh");
|
|
|
|
infoLabel = root.Q<Label>("infoLabel");
|
|
|
|
var slAtlasPadding = root.Q<SliderInt>("slAtlasPadding");
|
|
var textureSizeEnum = root.Q<EnumField>("textureSizeEnum");
|
|
imgCntLabel = root.Q<Label>("imgCntLabel");
|
|
root.Q<Toggle>("tgIsNormal").RegisterValueChangedCallback(b =>
|
|
{
|
|
textureMapInfo.IsNormalMap = b.newValue;
|
|
SetNormalOriginTextureFormat(!textureMapInfo.IsNormalMap);
|
|
UpdateResult();
|
|
});
|
|
|
|
textureSizeEnum.RegisterCallback<ChangeEvent<Enum>>((e) =>
|
|
{
|
|
var newTextureSize = (int)(ETextureSize)e.newValue;
|
|
if (textureSize != newTextureSize)
|
|
{
|
|
textureSize = newTextureSize;
|
|
UpdateResult();
|
|
}
|
|
});
|
|
|
|
btArray.clicked += () =>
|
|
{
|
|
btArray.style.backgroundColor = btClikedColor;
|
|
btAtlas.style.backgroundColor = btNormalColor;
|
|
textureMapInfo.PackType = TextureMapInfo.EPackType.Array;
|
|
slAtlasPadding.visible = false;
|
|
UpdateResult();
|
|
};
|
|
|
|
btAtlas.clicked += () =>
|
|
{
|
|
btAtlas.style.backgroundColor = btClikedColor;
|
|
btArray.style.backgroundColor = btNormalColor;
|
|
textureMapInfo.PackType = TextureMapInfo.EPackType.Atlas;
|
|
slAtlasPadding.visible = true;
|
|
UpdateResult();
|
|
};
|
|
|
|
|
|
btSave.clicked += Save;
|
|
|
|
btAtlas.style.backgroundColor = btClikedColor;
|
|
|
|
slAtlasPadding.RegisterCallback<ChangeEvent<int>>((i) =>
|
|
{
|
|
atlasPadding = i.newValue;
|
|
UpdateResult();
|
|
});
|
|
|
|
imageListContainer = root.Q<IMGUIContainer>("imgList");
|
|
imageListContainer.onGUIHandler = OnImageListGUI;
|
|
|
|
imageListContainer.RegisterCallback<MouseDownEvent>((e) =>
|
|
{
|
|
var mousePos = Event.current.mousePosition - toolbarOffset;
|
|
int clickIndex = -1;
|
|
for (int i = 0; i < texRects.Count; i++)
|
|
{
|
|
if (texRects[i].Contains(mousePos))
|
|
{
|
|
selectIndexForMove = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Event.current.type == EventType.MouseDown && Event.current.clickCount == 2)
|
|
{
|
|
if (clickIndex == -1)
|
|
{
|
|
for (int i = 0; i < modelRects.Count; i++)
|
|
{
|
|
if (modelRects[i].Contains(mousePos))
|
|
{
|
|
clickIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (clickIndex != -1)
|
|
{
|
|
EditorGUIUtility.PingObject(models[clickIndex]);
|
|
}
|
|
}
|
|
else if (clickIndex != -1)
|
|
{
|
|
EditorGUIUtility.PingObject(selectedTexs[clickIndex]);
|
|
}
|
|
}
|
|
});
|
|
|
|
imageListContainer.RegisterCallback<MouseUpEvent>((e) =>
|
|
{
|
|
var swapIndex = -1;
|
|
var mousePos = Event.current.mousePosition - toolbarOffset;
|
|
for (int i = 0; i < texRects.Count; i++)
|
|
{
|
|
if (texRects[i].Contains(mousePos))
|
|
{
|
|
swapIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (swapIndex != -1 && selectIndexForMove != -1)
|
|
{
|
|
if (selectIndexForMove != swapIndex)
|
|
{
|
|
(selectedTexs[selectIndexForMove], selectedTexs[swapIndex]) = (selectedTexs[swapIndex], selectedTexs[selectIndexForMove]);
|
|
(texRects[selectIndexForMove], texRects[swapIndex]) = (texRects[swapIndex], texRects[selectIndexForMove]);
|
|
UpdateResult();
|
|
}
|
|
}
|
|
|
|
selectIndexForMove = -1;
|
|
});
|
|
|
|
imageListContainer.RegisterCallback<DragUpdatedEvent>((e) =>
|
|
{
|
|
var mousePos = Event.current.mousePosition - toolbarOffset;
|
|
var replaceIdx = -1;
|
|
for (int i = 0; i < modelRects.Count; i++)
|
|
{
|
|
if (modelRects[i].Contains(mousePos))
|
|
{
|
|
replaceIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (replaceIdx != -1 && DragAndDrop.objectReferences.Length > 0)
|
|
{
|
|
if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
|
|
}
|
|
}
|
|
});
|
|
|
|
imageListContainer.RegisterCallback<DragExitedEvent>((e) =>
|
|
{
|
|
var mousePos = Event.current.mousePosition - toolbarOffset;
|
|
var replaceIdx = -1;
|
|
for (int i = 0; i < modelRects.Count; i++)
|
|
{
|
|
if (modelRects[i].Contains(mousePos))
|
|
{
|
|
replaceIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (replaceIdx != -1 && DragAndDrop.objectReferences.Length > 0)
|
|
{
|
|
if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
|
|
{
|
|
ReplaceModel(replaceIdx, DragAndDrop.objectReferences[0]);
|
|
}
|
|
}
|
|
});
|
|
|
|
btApplyMesh.clicked += () =>
|
|
{
|
|
ModifyMesh();
|
|
};
|
|
}
|
|
|
|
private void SetNormalOriginTextureFormat(bool toNormal)
|
|
{
|
|
foreach (var item in selectedTexs)
|
|
{
|
|
var ti = TextureImporter.GetAtPath(AssetDatabase.GetAssetPath(item)) as TextureImporter;
|
|
if (toNormal)
|
|
{
|
|
ti.textureType = TextureImporterType.NormalMap;
|
|
}
|
|
else if (ti.textureType == TextureImporterType.NormalMap)
|
|
{
|
|
ti.textureType = TextureImporterType.Default;
|
|
ti.sRGBTexture = false;
|
|
}
|
|
ti.SaveAndReimport();
|
|
}
|
|
}
|
|
|
|
private void OnImageListGUI()
|
|
{
|
|
float windowWidth = position.width;
|
|
int dynamicColumns = Mathf.FloorToInt(windowWidth / (cellSize.x + 5));
|
|
|
|
int count = selectedTexs.Count;
|
|
int currentColumn = 0;
|
|
int currentRow = 0;
|
|
|
|
var bkColor = GUI.backgroundColor;
|
|
EditorGUILayout.LabelField("Texture:");
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
if (currentColumn == 0)
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
}
|
|
|
|
var rect = EditorGUILayout.GetControlRect(GUILayout.Width(cellSize.x), GUILayout.Height(cellSize.y));
|
|
texRects[i] = rect;
|
|
|
|
if (i == selectIndex || selectIndexForMove != -1 && rect.Contains(Event.current.mousePosition - toolbarOffset))
|
|
{
|
|
GUI.backgroundColor = Color.blue;
|
|
}
|
|
else
|
|
{
|
|
GUI.backgroundColor = bkColor;
|
|
}
|
|
|
|
if (GUI.Button(rect, selectedTexs[i]))
|
|
{
|
|
if (selectIndex == i)
|
|
{
|
|
EditorGUIUtility.PingObject(selectedTexs[i]);
|
|
}
|
|
selectIndex = i;
|
|
}
|
|
|
|
currentColumn++;
|
|
if (currentColumn >= dynamicColumns)
|
|
{
|
|
GUILayout.EndHorizontal();
|
|
currentRow++;
|
|
currentColumn = 0;
|
|
}
|
|
}
|
|
|
|
GUI.backgroundColor = bkColor;
|
|
if (selectIndexForMove != -1)
|
|
{
|
|
GUI.Button(new Rect(Event.current.mousePosition, cellSize), selectedTexs[selectIndexForMove]);
|
|
}
|
|
|
|
if (selectIndex != -1)
|
|
{
|
|
infoLabel.visible = true;
|
|
var tex = selectedTexs[selectIndex];
|
|
var tinfo = textureMapInfo.OriginTexture2TextureInfo[tex];
|
|
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Array)
|
|
{
|
|
infoLabel.text = $"{tex.name} array index:{tinfo.ArrayIndex}";
|
|
}
|
|
else
|
|
{
|
|
infoLabel.text = $"{tex.name} atlas offset:{tinfo.Offset}";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infoLabel.visible = false;
|
|
}
|
|
if (currentColumn != 0)
|
|
{
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
EditorGUILayout.LabelField("", GUI.skin.horizontalSlider);
|
|
EditorGUILayout.LabelField("Model:");
|
|
GUILayout.BeginHorizontal();
|
|
for (int i = 0; i < gameObjectInspectorView.Count; i++)
|
|
{
|
|
var pv = gameObjectInspectorView[i];
|
|
var rect = EditorGUILayout.GetControlRect(GUILayout.Width(cellSize.x * 2), GUILayout.Height(cellSize.y * 2));
|
|
modelRects[i] = rect;
|
|
pv.DrawPreview(rect);
|
|
}
|
|
GUILayout.EndHorizontal();
|
|
|
|
if (dockPreviewWindowFail)
|
|
{
|
|
dockPreviewWindowFail = false;
|
|
try
|
|
{
|
|
if (atlasPropertiesWindow != null)
|
|
{
|
|
this.Dock(atlasPropertiesWindow, Docker.DockPosition.Right);
|
|
}
|
|
if (arrayPropertiesWindow != null)
|
|
{
|
|
this.Dock(arrayPropertiesWindow, Docker.DockPosition.Right);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
dockPreviewWindowFail = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void InitWithTextureMapInfo(TextureMapInfo textureMapInfo)
|
|
{
|
|
this.textureMapInfo = textureMapInfo;
|
|
VisualElement root = rootVisualElement;
|
|
var btArray = root.Q<Button>("btArray");
|
|
var btAtlas = root.Q<Button>("btAtlas");
|
|
var slAtlasPadding = root.Q<SliderInt>("slAtlasPadding");
|
|
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
|
|
{
|
|
btArray.style.backgroundColor = btNormalColor;
|
|
btAtlas.style.backgroundColor = btClikedColor;
|
|
slAtlasPadding.visible = true;
|
|
}
|
|
else
|
|
{
|
|
btArray.style.backgroundColor = btClikedColor;
|
|
btAtlas.style.backgroundColor = btNormalColor;
|
|
slAtlasPadding.visible = false;
|
|
}
|
|
|
|
if (textureMapInfo.IsNormalMap)
|
|
{
|
|
root.Q<Toggle>("tgIsNormal").value = true;
|
|
SetNormalOriginTextureFormat(!textureMapInfo.IsNormalMap);
|
|
}
|
|
|
|
texRects = new List<Rect>(new Rect[selectedTexs.Count]);
|
|
foreach (var item in textureMapInfo.Textures)
|
|
{
|
|
AddTexture(item);
|
|
}
|
|
|
|
foreach (var item in textureMapInfo.Fbxs)
|
|
{
|
|
AddModel(item);
|
|
}
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
if (textureMapInfo == null)
|
|
{
|
|
textureMapInfo = ScriptableObject.CreateInstance<TextureMapInfo>();
|
|
}
|
|
|
|
switch (Event.current.type)
|
|
{
|
|
case EventType.MouseDrag:
|
|
break;
|
|
case EventType.MouseDown:
|
|
selectIndex = -1;
|
|
Repaint();
|
|
break;
|
|
case EventType.KeyDown:
|
|
break;
|
|
case EventType.KeyUp:
|
|
if (Event.current.modifiers == EventModifiers.Control && Event.current.keyCode == KeyCode.S)
|
|
{
|
|
Save();
|
|
}
|
|
|
|
if (Event.current.keyCode == KeyCode.Delete)
|
|
{
|
|
if (selectIndex != -1)
|
|
{
|
|
RmTexture(selectedTexs[selectIndex]);
|
|
selectIndex = -1;
|
|
}
|
|
}
|
|
break;
|
|
case EventType.DragUpdated:
|
|
{
|
|
for (var i = 0; i < DragAndDrop.objectReferences.Length; i++)
|
|
{
|
|
if (DragAndDrop.objectReferences[i] is Texture || AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
|
|
{
|
|
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EventType.DragExited:
|
|
{
|
|
for (var i = 0; i < DragAndDrop.objectReferences.Length; i++)
|
|
{
|
|
var tex = DragAndDrop.objectReferences[i] as Texture2D;
|
|
if (tex)
|
|
{
|
|
AddTexture(tex);
|
|
}
|
|
else if (AssetDatabase.GetAssetPath(DragAndDrop.objectReferences[0]).ToLower().EndsWith(".fbx"))
|
|
{
|
|
AddModel(DragAndDrop.objectReferences[i]);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case EventType.ContextClick:
|
|
break;
|
|
case EventType.MouseEnterWindow:
|
|
break;
|
|
case EventType.MouseLeaveWindow:
|
|
break;
|
|
case EventType.Repaint:
|
|
{
|
|
btApplyMesh.visible = models.Count > 0;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void UpdateResult(Texture2D result, int cellCnt)
|
|
{
|
|
result.Apply();
|
|
var scale = (float)textureSize / result.width;
|
|
for (int i = 0; i < selectedTexs.Count; i++)
|
|
{
|
|
var tex = selectedTexs[i];
|
|
if (tex.width != textureSize)
|
|
{
|
|
Debug.LogWarning($"{tex.name} 尺寸错误: {tex.width} 正确值: {textureSize}");
|
|
}
|
|
int cellx = i % cellCnt;
|
|
int celly = i / cellCnt;
|
|
var mip = 0;
|
|
if (tex.width > textureSize)
|
|
{
|
|
mip = (int)Mathf.Log(2, tex.width / textureSize);
|
|
}
|
|
{
|
|
var temp = new Texture2D(textureSize, textureSize, graphicsFormat, 0, TextureCreationFlags.None);
|
|
Graphics.ConvertTexture(tex, temp);
|
|
|
|
var offsetx = cellx * (textureSize + atlasPadding);
|
|
var offsety = celly * (textureSize + atlasPadding);
|
|
|
|
Graphics.CopyTexture(temp, 0, 0, 0, 0, textureSize, textureSize, result, 0, 0, offsetx, offsety);
|
|
GameObject.DestroyImmediate(temp);
|
|
textureMapInfo.AtlasScale = scale;
|
|
textureMapInfo.OriginTexture2TextureInfo[tex] = new TextureMapInfo.TextureInfo()
|
|
{
|
|
OriginTexture = tex,
|
|
Offset = new float2((float)offsetx / result.width, (float)offsety / result.height),
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateResult(Texture2DArray result)
|
|
{
|
|
for (int i = 0; i < selectedTexs.Count; i++)
|
|
{
|
|
var tex = selectedTexs[i];
|
|
if (tex.width != textureSize)
|
|
{
|
|
Debug.LogWarning($"{tex.name} 尺寸错误: {tex.width} 正确值: {textureSize}");
|
|
}
|
|
|
|
Graphics.ConvertTexture(tex, 0, result, i);
|
|
textureMapInfo.OriginTexture2TextureInfo[tex] = new TextureMapInfo.TextureInfo()
|
|
{
|
|
OriginTexture = tex,
|
|
ArrayIndex = i,
|
|
};
|
|
}
|
|
}
|
|
|
|
private void UpdateResult()
|
|
{
|
|
if (selectedTexs.Count < 2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
|
|
{
|
|
arrayPropertiesWindow?.Close();
|
|
arrayPropertiesWindow = null;
|
|
|
|
var cellCnt = Mathf.CeilToInt(Mathf.Sqrt(selectedTexs.Count));
|
|
var atlasPaddedSize = cellCnt * textureSize + (cellCnt - 1) * atlasPadding;
|
|
if (textureAtlas == null || textureAtlas.width != atlasPaddedSize)
|
|
{
|
|
if (textureAtlas != null)
|
|
{
|
|
GameObject.DestroyImmediate(textureAtlas, false);
|
|
}
|
|
|
|
textureAtlas = new Texture2D(atlasPaddedSize, atlasPaddedSize,
|
|
graphicsFormat, (int)Mathf.Log(2, atlasPaddedSize) + 1, TextureCreationFlags.None)
|
|
{
|
|
wrapMode = TextureWrapMode.Clamp,
|
|
};
|
|
atlasPropertiesWindow?.Close();
|
|
atlasPropertiesWindow = null;
|
|
}
|
|
|
|
UpdateResult(textureAtlas, cellCnt);
|
|
if (!atlasPropertiesWindow)
|
|
{
|
|
atlasPropertiesWindow = fnOpenPropertyEditor(textureAtlas, true);
|
|
try
|
|
{
|
|
this.Dock(atlasPropertiesWindow, Docker.DockPosition.Right);
|
|
}
|
|
catch
|
|
{
|
|
dockPreviewWindowFail = true;
|
|
}
|
|
}
|
|
atlasPropertiesWindow.Repaint();
|
|
}
|
|
else
|
|
{
|
|
atlasPropertiesWindow?.Close();
|
|
atlasPropertiesWindow = null;
|
|
if (textureArray == null || textureArray.depth != selectedTexs.Count || textureArray.width != textureSize)
|
|
{
|
|
if (textureArray != null)
|
|
{
|
|
GameObject.DestroyImmediate(textureArray, false);
|
|
}
|
|
|
|
textureArray = new Texture2DArray(textureSize,
|
|
textureSize, selectedTexs.Count, graphicsFormat, TextureCreationFlags.None, 12)
|
|
{
|
|
wrapMode = TextureWrapMode.Clamp,
|
|
};
|
|
|
|
arrayPropertiesWindow?.Close();
|
|
arrayPropertiesWindow = null;
|
|
}
|
|
|
|
UpdateResult(textureArray);
|
|
if (!arrayPropertiesWindow)
|
|
{
|
|
arrayPropertiesWindow = fnOpenPropertyEditor(textureArray, true);
|
|
try
|
|
{
|
|
this.Dock(arrayPropertiesWindow, Docker.DockPosition.Right);
|
|
}
|
|
catch
|
|
{
|
|
dockPreviewWindowFail = true;
|
|
}
|
|
}
|
|
arrayPropertiesWindow.Repaint();
|
|
}
|
|
}
|
|
|
|
private void AddTexture(Texture2D texture2D)
|
|
{
|
|
if (TextureMapIndex.Instance.ResultGuid2TextureMap.TryGetValue(AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(texture2D)), out var mapInfo))
|
|
{
|
|
selectedTexs.Clear();
|
|
textureMapInfo = mapInfo;
|
|
InitWithTextureMapInfo(textureMapInfo);
|
|
}
|
|
|
|
if (texture2D.width % textureSize != 0)
|
|
{
|
|
EditorUtility.DisplayDialog("提示", $"贴图 {texture2D.name} 尺寸错误", "ok");
|
|
return;
|
|
}
|
|
|
|
if (selectedTexs.Count == MaxArraySize)
|
|
{
|
|
EditorUtility.DisplayDialog("提示", $"贴图数量最多 {MaxArraySize}", "ok");
|
|
return;
|
|
}
|
|
|
|
if (!selectedTexs.Contains(texture2D))
|
|
{
|
|
selectedTexs.Add(texture2D);
|
|
texRects.Add(new Rect());
|
|
}
|
|
else
|
|
{
|
|
ToastManager.ShowNotification(new Toast.ToastArgs()
|
|
{
|
|
Title = "Info",
|
|
Message = "当前图片已存在",
|
|
LifeTime = 2f,
|
|
Severity = Toast.ToastSeverity.Info,
|
|
ToastPosition = Toast.ToastPosition.TopCenter
|
|
});
|
|
return;
|
|
}
|
|
|
|
imgCntLabel.text = selectedTexs.Count.ToString();
|
|
UpdateResult();
|
|
}
|
|
|
|
private void RmTexture(Texture2D texture2D)
|
|
{
|
|
var idx = selectedTexs.IndexOf(texture2D);
|
|
if (idx != -1)
|
|
{
|
|
texRects.RemoveAt(idx);
|
|
selectedTexs.RemoveAt(idx);
|
|
if (models.Count > idx)
|
|
{
|
|
models.RemoveAt(idx);
|
|
modelRects.RemoveAt(idx);
|
|
gameObjectInspectorView.RemoveAt(idx);
|
|
}
|
|
}
|
|
|
|
imgCntLabel.text = selectedTexs.Count.ToString();
|
|
UpdateResult();
|
|
}
|
|
|
|
private void AddModel(UnityEngine.Object gameObject)
|
|
{
|
|
if (!models.Contains(gameObject) && models.Count < selectedTexs.Count)
|
|
{
|
|
models.Add(gameObject);
|
|
modelRects.Add(new());
|
|
gameObjectInspectorView.Add(Editor.CreateEditor(gameObject, gameObjectInspectorType));
|
|
}
|
|
}
|
|
|
|
private void ReplaceModel(int replaceIdx, UnityEngine.Object gameObject)
|
|
{
|
|
if (gameObject != models[replaceIdx])
|
|
{
|
|
models[replaceIdx] = gameObject;
|
|
gameObjectInspectorView[replaceIdx] = Editor.CreateEditor(gameObject, gameObjectInspectorType);
|
|
}
|
|
}
|
|
|
|
private void UpdateMapinfoBeforeSave()
|
|
{
|
|
textureMapInfo.Textures = selectedTexs;
|
|
textureMapInfo.Fbxs = models;
|
|
var hasAlpha = GraphicsFormatUtility.HasAlphaChannel(selectedTexs[0].graphicsFormat);
|
|
for (int i = 1; i < selectedTexs.Count; i++)
|
|
{
|
|
var tex = selectedTexs[i];
|
|
if (hasAlpha != GraphicsFormatUtility.HasAlphaChannel(tex.graphicsFormat))
|
|
{
|
|
if (!EditorUtility.DisplayDialog("警告", $"源贴图 alpha 不一致 :{tex.name}", "继续", "取消"))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
var rmLs = textureMapInfo.OriginTexture2TextureInfo.Keys.Where(t => !selectedTexs.Contains(t)).ToList();
|
|
foreach (var rm in rmLs)
|
|
{
|
|
textureMapInfo.OriginTexture2TextureInfo.Remove(rm);
|
|
}
|
|
|
|
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
|
|
{
|
|
GameObject.DestroyImmediate(textureArray);
|
|
textureArray = null;
|
|
textureMapInfo.TextureArray = null;
|
|
|
|
if (string.IsNullOrEmpty(textureMapInfo.TextureAtlasPath))
|
|
{
|
|
var savePath = EditorUtility.SaveFilePanelInProject("保存", "", "png", "");
|
|
if (string.IsNullOrEmpty(savePath))
|
|
{
|
|
return;
|
|
}
|
|
textureMapInfo.TextureAtlasPath = savePath;
|
|
}
|
|
|
|
AsyncGPUReadback.Request(textureAtlas, 0, (req) =>
|
|
{
|
|
if (!req.hasError)
|
|
{
|
|
{
|
|
using var dataArray = ImageConversion.EncodeNativeArrayToPNG(req.GetData<byte>(), textureAtlas.graphicsFormat, (uint)textureAtlas.width, (uint)textureAtlas.height);
|
|
using var fr = File.OpenWrite(textureMapInfo.TextureAtlasPath);
|
|
fr.Write(dataArray);
|
|
}
|
|
|
|
AssetDatabase.ImportAsset(textureMapInfo.TextureAtlasPath);
|
|
if (TextureImporter.GetAtPath(textureMapInfo.TextureAtlasPath) is TextureImporter ti)
|
|
{
|
|
ti.SetTextureSettings(new TextureImporterSettings()
|
|
{
|
|
textureShape = TextureImporterShape.Texture2D,
|
|
flipbookColumns = 1,
|
|
flipbookRows = 1,
|
|
sRGBTexture = false,
|
|
readable = false,
|
|
mipmapEnabled = true,
|
|
wrapMode = TextureWrapMode.Clamp,
|
|
});
|
|
ti.SaveAndReimport();
|
|
}
|
|
|
|
EditorApplication.delayCall += () =>
|
|
{
|
|
textureMapInfo.TextureAtlas = AssetDatabase.LoadAssetAtPath<Texture2D>(textureMapInfo.TextureAtlasPath);
|
|
var guid = AssetDatabase.AssetPathToGUID(textureMapInfo.TextureAtlasPath);
|
|
TextureMapIndex.Instance.ResultGuid2TextureMap[guid] = textureMapInfo;
|
|
EditorGUIUtility.PingObject(textureMapInfo.TextureAtlas);
|
|
};
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
GameObject.DestroyImmediate(textureAtlas);
|
|
textureAtlas = null;
|
|
textureMapInfo.TextureAtlas = null;
|
|
if (string.IsNullOrEmpty(textureMapInfo.TextureArrayPath))
|
|
{
|
|
var savePath = EditorUtility.SaveFilePanelInProject("保存", "", "png", "");
|
|
if (string.IsNullOrEmpty(savePath))
|
|
{
|
|
return;
|
|
}
|
|
textureMapInfo.TextureArrayPath = savePath;
|
|
}
|
|
|
|
AsyncGPUReadback.Request(textureArray, 0, 0, textureArray.width, 0, textureArray.height, 0, textureArray.depth, (req) =>
|
|
{
|
|
if (!req.hasError)
|
|
{
|
|
{
|
|
using var fr = File.Open(textureMapInfo.TextureArrayPath, FileMode.OpenOrCreate);
|
|
using MemoryStream memoryStream = new MemoryStream();
|
|
for (int i = textureArray.depth - 1; i >= 0; --i)
|
|
{
|
|
var array = req.GetData<byte>(i);
|
|
memoryStream.Write(array);
|
|
}
|
|
memoryStream.Seek(0, SeekOrigin.Begin);
|
|
|
|
var dataArray = ImageConversion.EncodeArrayToPNG(memoryStream.GetBuffer(), textureArray.graphicsFormat, (uint)textureArray.width, (uint)textureArray.height * (uint)textureArray.depth);
|
|
fr.Write(dataArray);
|
|
}
|
|
|
|
AssetDatabase.ImportAsset(textureMapInfo.TextureArrayPath);
|
|
if (TextureImporter.GetAtPath(textureMapInfo.TextureArrayPath) is TextureImporter ti)
|
|
{
|
|
ti.SetTextureSettings(new TextureImporterSettings()
|
|
{
|
|
textureShape = TextureImporterShape.Texture2DArray,
|
|
flipbookColumns = 1,
|
|
flipbookRows = textureArray.depth,
|
|
sRGBTexture = false,
|
|
readable = false,
|
|
mipmapEnabled = true,
|
|
wrapMode = TextureWrapMode.Clamp,
|
|
});
|
|
ti.SaveAndReimport();
|
|
}
|
|
|
|
textureMapInfo.TextureArray = AssetDatabase.LoadAssetAtPath<Texture2DArray>(textureMapInfo.TextureArrayPath);
|
|
var guid = AssetDatabase.AssetPathToGUID(textureMapInfo.TextureArrayPath);
|
|
TextureMapIndex.Instance.ResultGuid2TextureMap[guid] = textureMapInfo;
|
|
EditorGUIUtility.PingObject(textureMapInfo.TextureArray);
|
|
}
|
|
});
|
|
}
|
|
|
|
AssetDatabase.SaveAssetIfDirty(TextureMapIndex.Instance);
|
|
}
|
|
|
|
private void Save()
|
|
{
|
|
if (AssetDatabase.IsMainAsset(textureMapInfo))
|
|
{
|
|
UpdateMapinfoBeforeSave();
|
|
AssetDatabase.SaveAssetIfDirty(textureMapInfo);
|
|
}
|
|
else
|
|
{
|
|
var path = EditorUtility.SaveFilePanelInProject("保存映射关系", DateTime.Now.ToString("yy_MM_dd_hhmmss"), "asset", "保存源贴图到 atlas 对应关系", TextureMapInfo.TextureMapDir);
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AssetDatabase.CreateAsset(textureMapInfo, path);
|
|
UpdateMapinfoBeforeSave();
|
|
AssetDatabase.SaveAssetIfDirty(textureMapInfo);
|
|
}
|
|
base.SaveChanges();
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
private void ModifyMesh()
|
|
{
|
|
for (int i = 0; i < models.Count; i++)
|
|
{
|
|
var model = models[i] as GameObject;
|
|
var mfs = model.GetComponentsInChildren<MeshFilter>();
|
|
var tex = selectedTexs[i];
|
|
var info = textureMapInfo.OriginTexture2TextureInfo[tex];
|
|
for (int j = 0; j < mfs.Length; j++)
|
|
{
|
|
var mf = mfs[j];
|
|
if (textureMapInfo.PackType == TextureMapInfo.EPackType.Atlas)
|
|
{
|
|
NativeArray<float2> uv2 = new (mf.sharedMesh.vertexCount, Allocator.Temp);
|
|
for (int k = 0; k < mf.sharedMesh.uv.Length; k++)
|
|
{
|
|
var uv = mf.sharedMesh.uv[k];
|
|
uv2[k] = uv * textureMapInfo.AtlasScale + (Vector2)info.Offset;
|
|
}
|
|
mf.sharedMesh.SetUVs(0, uv2);
|
|
uv2.Dispose();
|
|
}
|
|
else
|
|
{
|
|
using NativeArray<int2> uv = new (mf.sharedMesh.vertexCount, Allocator.Temp);
|
|
uv.AsSpan().Fill(new int2(info.ArrayIndex));
|
|
mf.sharedMesh.SetUVs(3, uv);
|
|
}
|
|
}
|
|
|
|
var path = AssetDatabase.GetAssetPath(model);
|
|
exportObjectFunc.DynamicInvoke(path, model, fbxExportSetting);
|
|
}
|
|
|
|
AssetDatabase.Refresh();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
SetNormalOriginTextureFormat(textureMapInfo.IsNormalMap);
|
|
AssetDatabase.SaveAssetIfDirty(textureMapInfo);
|
|
}
|
|
}
|