481 lines
17 KiB
C#
481 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Unity.Collections;
|
|
using Unity.HLODSystem.Utils;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.HLODSystem
|
|
{
|
|
public class TexturePacker : IDisposable
|
|
{
|
|
private NativeArray<int> m_detector = new NativeArray<int>(1, Allocator.Persistent);
|
|
public class MaterialTexture : IDisposable
|
|
{
|
|
private DisposableList<WorkingTexture> m_textures = new DisposableList<WorkingTexture>();
|
|
private NativeArray<int> m_detector = new NativeArray<int>(1, Allocator.Persistent);
|
|
|
|
|
|
public void Add(WorkingTexture texture)
|
|
{
|
|
m_textures.Add(texture.Clone());
|
|
}
|
|
|
|
public MaterialTexture Clone()
|
|
{
|
|
MaterialTexture nmt = new MaterialTexture();
|
|
for (int i = 0; i < m_textures.Count; ++i)
|
|
{
|
|
nmt.Add(m_textures[i]);
|
|
}
|
|
|
|
return nmt;
|
|
}
|
|
|
|
|
|
public int Count => m_textures.Count;
|
|
|
|
public WorkingTexture this[int index]
|
|
{
|
|
get => m_textures[index];
|
|
set => m_textures[index] = value;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
m_textures.Dispose();
|
|
m_textures = null;
|
|
m_detector.Dispose();
|
|
}
|
|
}
|
|
|
|
public class TextureAtlas : IDisposable
|
|
{
|
|
private NativeArray<int> m_detector = new NativeArray<int>(1, Allocator.Persistent);
|
|
|
|
private List<object> m_objects;
|
|
private List<Rect> m_uvs;
|
|
private List<Guid> m_guids;
|
|
private DisposableList<WorkingTexture> m_textures;
|
|
|
|
public List<object> Objects => m_objects;
|
|
public List<Rect> UVs => m_uvs;
|
|
public DisposableList<WorkingTexture> Textures => m_textures;
|
|
|
|
protected TextureAtlas(List<object> objs, List<Rect> uvs, List<Guid> guids)
|
|
{
|
|
m_objects = objs;
|
|
m_uvs = uvs;
|
|
m_guids = guids;
|
|
m_textures = new DisposableList<WorkingTexture>();
|
|
}
|
|
public bool Contains(object obj)
|
|
{
|
|
return m_objects.Contains(obj);
|
|
}
|
|
|
|
public Rect GetUV(Guid textureGuid)
|
|
{
|
|
int index = m_guids.IndexOf(textureGuid);
|
|
return m_uvs[index];
|
|
}
|
|
public void Dispose()
|
|
{
|
|
m_textures.Dispose();
|
|
m_detector.Dispose();
|
|
}
|
|
}
|
|
|
|
class TextureAtlasCreator : TextureAtlas
|
|
{
|
|
public TextureAtlasCreator(List<object> objs, List<Rect> uvs, List<Guid> guids, DisposableList<TextureCombiner> combiners)
|
|
: base(objs, uvs, guids)
|
|
{
|
|
for (int i = 0; i < combiners.Count; ++i)
|
|
{
|
|
Textures.Add(combiners[i].GetTexture().Clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
class Score
|
|
{
|
|
public Source Lhs;
|
|
public Source Rhs;
|
|
public int MatchCount;
|
|
}
|
|
|
|
class TextureCombiner : IDisposable
|
|
{
|
|
private WorkingTexture m_texture;
|
|
private NativeArray<int> m_detector = new NativeArray<int>(1, Allocator.Persistent);
|
|
public TextureCombiner(Allocator allocator, TextureFormat format, int width, int height, bool linear)
|
|
{
|
|
m_texture = new WorkingTexture(allocator, format, width, height, linear);
|
|
}
|
|
public void Dispose()
|
|
{
|
|
m_texture?.Dispose();
|
|
m_detector.Dispose();
|
|
}
|
|
|
|
public void SetTexture(WorkingTexture source, int x, int y)
|
|
{
|
|
m_texture.Blit(source, x, y);
|
|
}
|
|
|
|
public WorkingTexture GetTexture()
|
|
{
|
|
return m_texture;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
class Source : IDisposable
|
|
{
|
|
private NativeArray<int> m_detector = new NativeArray<int>(1, Allocator.Persistent);
|
|
|
|
public static Score GetScore(Source lhs, Source rhs)
|
|
{
|
|
int match = lhs.m_textureGuids.Intersect(rhs.m_textureGuids).Count();
|
|
return new Score()
|
|
{
|
|
Lhs = lhs,
|
|
Rhs = rhs,
|
|
MatchCount = match
|
|
};
|
|
}
|
|
|
|
public static Source Combine(Source lhs, Source rhs)
|
|
{
|
|
Source newSource = new Source();
|
|
newSource.m_obj = new List<object>();
|
|
newSource.m_obj.AddRange(lhs.m_obj);
|
|
newSource.m_obj.AddRange(rhs.m_obj);
|
|
|
|
newSource.m_textures = new DisposableList<MaterialTexture>();
|
|
newSource.m_textureGuids = new List<Guid>();
|
|
|
|
for (int i = 0; i < lhs.m_textures.Count; ++i)
|
|
{
|
|
newSource.m_textures.Add(lhs.m_textures[i].Clone());
|
|
newSource.m_textureGuids.Add(lhs.m_textureGuids[i]);
|
|
}
|
|
|
|
for (int i = 0; i < rhs.m_textures.Count; ++i)
|
|
{
|
|
if (newSource.m_textureGuids.Contains(rhs.m_textureGuids[i]) == false)
|
|
{
|
|
newSource.m_textures.Add(rhs.m_textures[i].Clone());
|
|
newSource.m_textureGuids.Add(rhs.m_textureGuids[i]);
|
|
}
|
|
}
|
|
|
|
return newSource;
|
|
}
|
|
|
|
public static int CombineTextureCount(Source lhs, Source rhs)
|
|
{
|
|
int count = lhs.m_textureGuids.Count;
|
|
for (int i = 0; i < rhs.m_textureGuids.Count; ++i)
|
|
{
|
|
if (lhs.m_textureGuids.Contains(rhs.m_textureGuids[i]) == false)
|
|
{
|
|
count += 1;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
private List<object> m_obj;
|
|
private List<Guid> m_textureGuids;
|
|
private DisposableList<MaterialTexture> m_textures;
|
|
|
|
|
|
//use for combine.
|
|
//this constructor should not call from out
|
|
private Source()
|
|
{
|
|
|
|
}
|
|
public Source(object obj, DisposableList<MaterialTexture> textures)
|
|
{
|
|
m_obj = new List<object>();
|
|
m_obj.Add(obj);
|
|
|
|
m_textures = textures;
|
|
m_textureGuids = new List<Guid>(textures.Count);
|
|
|
|
for (int i = 0; i < textures.Count; ++i)
|
|
{
|
|
m_textureGuids.Add(textures[i][0].GetGUID());
|
|
}
|
|
}
|
|
public void Dispose()
|
|
{
|
|
m_textures?.Dispose();
|
|
m_detector.Dispose();
|
|
}
|
|
|
|
public Source Clone()
|
|
{
|
|
Source ns = new Source();
|
|
ns.m_obj = new List<object>();
|
|
ns.m_obj.AddRange(m_obj);
|
|
ns.m_textureGuids = new List<Guid>();
|
|
ns.m_textureGuids.AddRange(m_textureGuids);
|
|
ns.m_textures = new DisposableList<MaterialTexture>();
|
|
for (int i = 0; i < m_textures.Count; ++i)
|
|
{
|
|
ns.m_textures.Add(m_textures[i].Clone());
|
|
}
|
|
return ns;
|
|
}
|
|
|
|
public int GetMaxTextureCount(int packTextureSize, int maxSourceSize)
|
|
{
|
|
int minTextureCount = packTextureSize / maxSourceSize;
|
|
//width * height
|
|
minTextureCount = minTextureCount * minTextureCount;
|
|
|
|
//we can't pack one texture.
|
|
//so, we should use half size texture.
|
|
while (minTextureCount < m_textures.Count)
|
|
minTextureCount = minTextureCount * 4;
|
|
|
|
return minTextureCount;
|
|
}
|
|
|
|
public TextureAtlas CreateAtlas(TextureFormat format, int packTextureSize, bool linear)
|
|
{
|
|
if (m_textures.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
int itemCount = Mathf.CeilToInt(Mathf.Sqrt(m_textures.Count));
|
|
int itemSize = packTextureSize / itemCount;
|
|
TextureAtlas atlas;
|
|
|
|
using (DisposableList<MaterialTexture> resizedTextures = CreateResizedTextures(itemSize, itemSize))
|
|
using (DisposableList<TextureCombiner> combiners = new DisposableList<TextureCombiner>())
|
|
{
|
|
List<Rect> uvs = new List<Rect>(resizedTextures.Count);
|
|
List<Guid> guids = new List<Guid>(resizedTextures.Count);
|
|
for (int i = 0; i < resizedTextures.Count; ++i)
|
|
{
|
|
int x = i % itemCount;
|
|
int y = i / itemCount;
|
|
|
|
for (int k = combiners.Count; k < resizedTextures[i].Count; ++k)
|
|
combiners.Add(new TextureCombiner(Allocator.Persistent, format, packTextureSize,
|
|
packTextureSize, linear));
|
|
|
|
uvs.Add(new Rect(
|
|
(float) (x * itemSize)/ (float) packTextureSize,
|
|
(float) (y * itemSize)/ (float) packTextureSize,
|
|
(float) resizedTextures[i][0].Width / (float) packTextureSize,
|
|
(float) resizedTextures[i][0].Height / (float) packTextureSize));
|
|
|
|
guids.Add(m_textures[i][0].GetGUID());
|
|
|
|
for (int k = 0; k < resizedTextures[i].Count; ++k)
|
|
{
|
|
combiners[k].SetTexture(resizedTextures[i][k], x * itemSize, y * itemSize);
|
|
}
|
|
}
|
|
|
|
atlas = new TextureAtlasCreator(m_obj, uvs, guids, combiners);
|
|
}
|
|
|
|
return atlas;
|
|
}
|
|
|
|
private DisposableList<MaterialTexture> CreateResizedTextures(int newWidth, int newHeight)
|
|
{
|
|
DisposableList<MaterialTexture> resized = new DisposableList<MaterialTexture>();
|
|
for (int i = 0; i < m_textures.Count; ++i)
|
|
{
|
|
MaterialTexture newMT = new MaterialTexture();
|
|
|
|
for (int k = 0; k < m_textures[i].Count; ++k)
|
|
{
|
|
int targetWidth = Mathf.Min(newWidth, m_textures[i][k].Width);
|
|
int targetHeight = Mathf.Min(newHeight, m_textures[i][k].Height);
|
|
WorkingTexture resizedTexture =
|
|
m_textures[i][k].Resize(Allocator.Persistent, targetWidth, targetHeight);
|
|
newMT.Add(resizedTexture);
|
|
resizedTexture.Dispose();
|
|
}
|
|
|
|
resized.Add(newMT);
|
|
}
|
|
|
|
return resized;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
class TaskGroup : IDisposable
|
|
{
|
|
private TextureFormat m_format;
|
|
private int m_packTextureSize;
|
|
private int m_maxCount;
|
|
private bool m_linear;
|
|
private DisposableList<Source> m_sources = new DisposableList<Source>();
|
|
|
|
public TaskGroup(TextureFormat format, int packTextureSize, bool linear, int maxCount)
|
|
{
|
|
m_format = format;
|
|
m_packTextureSize = packTextureSize;
|
|
m_maxCount = maxCount;
|
|
m_linear = linear;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
m_sources.Dispose();
|
|
}
|
|
|
|
public void AddSource(Source source)
|
|
{
|
|
m_sources.Add(source.Clone());
|
|
}
|
|
|
|
|
|
public void CombineSources()
|
|
{
|
|
List<Score> scoreList = new List<Score>();
|
|
for (int i = 0; i < m_sources.Count; ++i)
|
|
{
|
|
for (int k = i + 1; k < m_sources.Count; ++k)
|
|
{
|
|
scoreList.Add(Source.GetScore(m_sources[i], m_sources[k]));
|
|
}
|
|
}
|
|
|
|
scoreList.Sort((lhs, rhs) => rhs.MatchCount - lhs.MatchCount);
|
|
|
|
for (int i = 0; i < scoreList.Count; ++i)
|
|
{
|
|
Score score = scoreList[i];
|
|
if (CanCombine(score.Lhs, score.Rhs) == false)
|
|
continue;
|
|
|
|
Source combinedSource = Source.Combine(score.Lhs, score.Rhs);
|
|
//Do not dispose lhs, rhs sources
|
|
//these just moved so must not be released.
|
|
m_sources.Remove(score.Lhs);
|
|
m_sources.Remove(score.Rhs);
|
|
m_sources.Add(combinedSource);
|
|
|
|
//Remove merged Score and make Score by combinedScore.
|
|
scoreList.RemoveAll(s => s.Lhs == score.Lhs || s.Lhs == score.Rhs ||
|
|
s.Rhs == score.Lhs || s.Rhs == score.Rhs);
|
|
|
|
//last is combinedSource.
|
|
for (int k = 0; k < m_sources.Count - 1; ++k)
|
|
{
|
|
scoreList.Add(Source.GetScore(m_sources[k], combinedSource));
|
|
}
|
|
|
|
scoreList.Sort((lhs, rhs) => rhs.MatchCount - lhs.MatchCount);
|
|
i = -1; //for back to the first loop..
|
|
|
|
}
|
|
}
|
|
|
|
|
|
public DisposableList<TextureAtlas> CreateTextureAtlases()
|
|
{
|
|
DisposableList<TextureAtlas> atlases = new DisposableList<TextureAtlas>();
|
|
|
|
for (int i = 0; i < m_sources.Count; ++i)
|
|
{
|
|
TextureAtlas item = m_sources[i].CreateAtlas(m_format, m_packTextureSize, m_linear);
|
|
if ( item != null )
|
|
atlases.Add(item);
|
|
}
|
|
return atlases;
|
|
|
|
}
|
|
|
|
private bool CanCombine(Source lhs, Source rhs)
|
|
{
|
|
|
|
return Source.CombineTextureCount(lhs, rhs) <= m_maxCount;
|
|
}
|
|
}
|
|
|
|
DisposableList<Source> m_sources = new DisposableList<Source>();
|
|
DisposableList<TextureAtlas> m_atlas = new DisposableList<TextureAtlas>();
|
|
|
|
public TexturePacker()
|
|
{
|
|
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
m_sources.Dispose();
|
|
m_atlas.Dispose();
|
|
m_detector.Dispose();
|
|
}
|
|
|
|
|
|
//TODO: must clear what the ownership of texture.
|
|
public void AddTextureGroup(object obj, List<MaterialTexture> textures)
|
|
{
|
|
DisposableList<MaterialTexture> copyTextures = new DisposableList<MaterialTexture>();
|
|
for (int i = 0; i < textures.Count; ++i)
|
|
{
|
|
copyTextures.Add(textures[i].Clone());
|
|
}
|
|
Source source = new Source(obj, copyTextures);
|
|
m_sources.Add(source);
|
|
}
|
|
|
|
public void Pack(TextureFormat format, int packTextureSize, int maxSourceSize, bool linear)
|
|
{
|
|
//First, we should separate each group by count.
|
|
using (DisposableDictionary<int, TaskGroup> taskGroups = new DisposableDictionary<int, TaskGroup>())
|
|
{
|
|
for (int i = 0; i < m_sources.Count; ++i)
|
|
{
|
|
int maxCount = m_sources[i].GetMaxTextureCount(packTextureSize, maxSourceSize);
|
|
if (taskGroups.ContainsKey(maxCount) == false)
|
|
taskGroups.Add(maxCount, new TaskGroup(format, packTextureSize, linear, maxCount));
|
|
|
|
taskGroups[maxCount].AddSource(m_sources[i]);
|
|
}
|
|
|
|
//Second, we should figure out which group should be combined from each taskGroup.
|
|
foreach (var taskGroup in taskGroups.Values)
|
|
{
|
|
taskGroup.CombineSources();
|
|
m_atlas.AddRange(taskGroup.CreateTextureAtlases());
|
|
}
|
|
}
|
|
}
|
|
|
|
public TextureAtlas GetAtlas(object obj)
|
|
{
|
|
for (int i = 0; i < m_atlas.Count; ++i)
|
|
{
|
|
if (m_atlas[i].Contains(obj))
|
|
return m_atlas[i];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public TextureAtlas[] GetAllAtlases()
|
|
{
|
|
return m_atlas.ToArray();
|
|
}
|
|
|
|
}
|
|
|
|
} |