-
Notifications
You must be signed in to change notification settings - Fork 324
/
TextureAtlasBuilder.cs
287 lines (252 loc) · 10 KB
/
TextureAtlasBuilder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DaggerfallWorkshop.Utility
{
/// <summary>
/// Builds an atlas from any combination of texture archives.
/// Does not yet integrate with main material system.
/// Currently some limitations around caching and serialization.
/// At this time used only for static misc billboards in cities.
/// Will be enhanced in future.
/// </summary>
public class TextureAtlasBuilder
{
#region Fields
Dictionary<int, TextureItem> textureItems = new Dictionary<int, TextureItem>();
Dictionary<int, AtlasItem> atlasItems = new Dictionary<int, AtlasItem>();
Texture2D atlasTexture = null;
Material atlasMaterial = null;
int maxAtlasDim = 2048;
bool mipMaps = true;
int padding = 4;
bool stayReadable = false;
#endregion
#region Structures & Enums
/// <summary>
/// Each individual texture is held in a list of texture items.
/// </summary>
public struct TextureItem
{
public int key; // Unique key of this texture item
public int archive; // Texture archive (index of texture file, such as TEXTURE.210)
public int record; // Archive record (index of texture inside file)
public int frame; // Record frame (index of frame inside record)
public int frameCount; // Number of frames in this set
public Vector2 size; // Size of this texture in DaggerfallUnits.
public Vector2 scale; // Scale of this texture in DaggerfallUnits.
public Texture2D texture; // Individual texture used when repacking
}
/// <summary>
/// The packed textures are each referenced by an atlas item with a unique key
/// </summary>
public struct AtlasItem
{
public int key; // Unique key of this atlas item
public Rect rect; // Rect of this texture inside atlas
public TextureItem textureItem; // Details of source texture this item created from
}
#endregion
#region Properties
/// <summary>
/// Gets the atlas Texture2D created after packing.
/// </summary>
public Texture2D AtlasTexture
{
get { return atlasTexture; }
}
/// <summary>
/// Gets the atlas material generated from texture.
/// </summary>
public Material AtlasMaterial
{
get { return (atlasMaterial != null) ? atlasMaterial : GetMaterial(); }
}
/// <summary>
/// Gets or sets max dimensions of texture atlas.
/// </summary>
public int MaxAtlasDim
{
get { return maxAtlasDim; }
set { maxAtlasDim = value; }
}
/// <summary>
/// Gets or sets flag to create mipmaps for atlas texture.
/// </summary>
public bool MipMaps
{
get { return mipMaps; }
set { mipMaps = value; }
}
/// <summary>
/// Gets or sets padding around each texture in atlas.
/// </summary>
public int Padding
{
get { return padding; }
set { padding = value; }
}
#endregion
#region Public Methods
/// <summary>
/// Add a new texture item to source list.
/// These items are used to build atlas.
/// </summary>
/// <param name="texture">Individual Texture2D. Must be readable.</param>
/// <param name="archive">Archive index of this texture.</param>
/// <param name="record">Record index of this texture.</param>
/// <param name="frame">Frame index of this texture.</param>
/// <param name="frameCount">Number of frames in this set.</param>
public void AddTextureItem(Texture2D texture, int archive, int record, int frame, int frameCount, Vector2 size, Vector2 scale)
{
// Create texture item
int key = MakeTextureKey((short)archive, (byte)record, (byte)frame);
TextureItem item = new TextureItem()
{
key = key,
texture = texture,
archive = archive,
record = record,
frame = frame,
frameCount = frameCount,
size = size,
scale = scale,
};
textureItems.Add(key, item);
}
/// <summary>
/// Rebuilds source textures into new atlas.
/// </summary>
public void Rebuild(int borderSize)
{
// Create sequential array of texture references
int index = 0;
Texture2D[] textureRefs = new Texture2D[textureItems.Count];
foreach (var ti in textureItems)
{
textureRefs[index++] = ti.Value.texture;
}
// Create atlas texture
atlasTexture = new Texture2D(maxAtlasDim, maxAtlasDim, TextureFormat.ARGB32, mipMaps);
Rect[] rects = atlasTexture.PackTextures(textureRefs, padding, maxAtlasDim, !stayReadable);
// Shrink UV rect to compensate for internal border
if (borderSize > 0)
{
float ru = 1f / atlasTexture.width;
float rv = 1f / atlasTexture.height;
for (int i = 0; i < rects.Length; i++)
{
Rect rct = rects[i];
rct.xMin += borderSize * ru;
rct.xMax -= borderSize * ru;
rct.yMin += borderSize * rv;
rct.yMax -= borderSize * rv;
rects[i] = rct;
}
}
// Rebuild dict
index = 0;
atlasItems.Clear();
foreach (var ti in textureItems)
{
// Create atlas item
AtlasItem item = new AtlasItem()
{
key = ti.Key,
rect = rects[index],
textureItem = ti.Value,
};
atlasItems.Add(ti.Key, item);
index++;
}
// Promote to new material
GetMaterial();
}
/// <summary>
/// Gets a Daggerfall/Default or Daggerfall/Billboard material from current atlas texture based on blendMode.
/// Does not return normal or emission map.
/// </summary>
/// <param name="blendMode">Blend mode of material used to determine material returned.</param>
/// <returns>Material.</returns>
public Material GetMaterial(MaterialReader.CustomBlendMode blendMode = MaterialReader.CustomBlendMode.Cutout)
{
Material material = (blendMode == MaterialReader.CustomBlendMode.Cutout) ? MaterialReader.CreateBillboardMaterial() : MaterialReader.CreateDefaultMaterial();
material.name = "SuperAtlas Material";
material.mainTexture = atlasTexture;
material.mainTexture.filterMode = DaggerfallUnity.Instance.MaterialReader.MainFilterMode;
atlasMaterial = material;
return material;
}
/// <summary>
/// Gets atlas details of a particular item.
/// If texture does not exist in atlas an empty item is returned.
/// </summary>
/// <param name="archive">Archive index of texture.</param>
/// <param name="record">Record index of texture.</param>
/// <param name="frame">Frame index of texture.</param>
/// <returns>AtlasItem. Key member contains -1 if item not present.</returns>
public AtlasItem GetAtlasItem(int archive, int record, int frame = 0)
{
int key = MakeTextureKey((short)archive, (byte)record, (byte)frame);
return GetAtlasItem(key);
}
/// <summary>
/// Gets details of a particular atlas item.
/// If texture does not exist in atlas an empty item is returned.
/// </summary>
/// <param name="key">Key of item.</param>
/// <returns>AtlasItem. Key member contains -1 if item not present.</returns>
public AtlasItem GetAtlasItem(int key)
{
AtlasItem item = new AtlasItem();
if (!atlasItems.TryGetValue(key, out item))
item.key = -1;
return item;
}
/// <summary>
/// Gets frame count of any item.
/// </summary>
/// <returns>Number of frame in this set.</returns>
public int GetFrameCount(int archive, int record)
{
int count = 0;
int key = MakeTextureKey((short)archive, (byte)record, (byte)0);
TextureItem item = new TextureItem();
if (textureItems.TryGetValue(key, out item))
{
count = item.frameCount;
}
return count;
}
/// <summary>
/// Clear all texture items and reset atlas.
/// </summary>
public void Clear()
{
textureItems.Clear();
atlasItems.Clear();
atlasTexture = null;
atlasMaterial = null;
}
#endregion
#region Public Static Methods
/// <summary>
/// Create a texture key from indices.
/// </summary>
public static int MakeTextureKey(short archive, byte record, byte frame)
{
return (archive << 16) + (record << 8) + frame;
}
/// <summary>
/// Reverse a texture key back into indices.
/// </summary>
public static void ReverseTextureKey(int key, out int archiveOut, out int recordOut, out int frameOut)
{
archiveOut = (key >> 16);
recordOut = (key >> 8) & 0xff;
frameOut = key & 0xff;
}
#endregion
}
}