source: OniSplit/Motoko/TextureImporter3.cs@ 1159

Last change on this file since 1159 was 1114, checked in by iritscen, 5 years ago

Adding OniSplit source code (v0.9.99.0). Many thanks to Neo for all his work over the years.

File size: 12.4 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Xml;
5
6namespace Oni.Motoko
7{
8 using Imaging;
9 using Metadata;
10
11 internal class TextureImporter3
12 {
13 private readonly string outputDirPath;
14 private readonly Dictionary<string, TextureImporterOptions> textures = new Dictionary<string, TextureImporterOptions>(StringComparer.Ordinal);
15 private TextureFormat defaultFormat = TextureFormat.BGR;
16 private TextureFormat defaultSquareFormat = TextureFormat.BGR;
17 private TextureFormat defaultAlphaFormat = TextureFormat.RGBA;
18 private int maxSize = 512;
19
20 public TextureImporter3(string outputDirPath)
21 {
22 this.outputDirPath = outputDirPath;
23 }
24
25 public TextureFormat DefaultFormat
26 {
27 get
28 {
29 return defaultFormat;
30 }
31 set
32 {
33 defaultFormat = value;
34 defaultSquareFormat = value;
35 }
36 }
37
38 public TextureFormat DefaultAlphaFormat
39 {
40 get { return defaultAlphaFormat; }
41 set { defaultAlphaFormat = value; }
42 }
43
44 public int MaxSize
45 {
46 get { return maxSize; }
47 set { maxSize = value; }
48 }
49
50 public string AddMaterial(Dae.Material material)
51 {
52 if (material == null || material.Effect == null)
53 return null;
54
55 var texture = material.Effect.Textures.FirstOrDefault(t => t.Channel == Dae.EffectTextureChannel.Diffuse);
56
57 if (texture == null)
58 return null;
59
60 var sampler = texture.Sampler;
61
62 if (sampler == null || sampler.Surface == null || sampler.Surface.InitFrom == null)
63 return null;
64
65 var filePath = Path.GetFullPath(sampler.Surface.InitFrom.FilePath);
66
67 if (!File.Exists(filePath))
68 return null;
69
70 var options = GetOptions(Path.GetFileNameWithoutExtension(filePath), filePath);
71
72 return options.Name;
73 }
74
75 public TextureImporterOptions AddMaterial(Akira.Material material)
76 {
77 return GetOptions(material.Name, material.ImageFilePath);
78 }
79
80 private TextureImporterOptions GetOptions(string name, string filePath)
81 {
82 TextureImporterOptions options;
83
84 if (!textures.TryGetValue(name, out options))
85 {
86 options = new TextureImporterOptions
87 {
88 Name = name,
89 Images = new[] { filePath }
90 };
91
92 textures.Add(name, options);
93 }
94
95 return options;
96 }
97
98 public TextureImporterOptions GetOptions(string name, bool create)
99 {
100 TextureImporterOptions options;
101
102 if (!textures.TryGetValue(name, out options) && create)
103 {
104 options = new TextureImporterOptions
105 {
106 Name = name
107 };
108
109 textures.Add(name, options);
110 }
111
112 return options;
113 }
114
115 public void ReadOptions(XmlReader xml, string basePath)
116 {
117 var options = GetOptions(xml.GetAttribute("Name"), true);
118 var images = new List<string>();
119
120 xml.ReadStartElement("Texture");
121
122 while (xml.IsStartElement())
123 {
124 switch (xml.LocalName)
125 {
126 case "Width":
127 options.Width = xml.ReadElementContentAsInt();
128 break;
129 case "Height":
130 options.Height = xml.ReadElementContentAsInt();
131 break;
132 case "Format":
133 options.Format = TextureImporter.ParseTextureFormat(xml.ReadElementContentAsString());
134 break;
135 case "Flags":
136 options.Flags = xml.ReadElementContentAsEnum<TextureFlags>();
137 break;
138 case "GunkFlags":
139 options.GunkFlags = xml.ReadElementContentAsEnum<Akira.GunkFlags>();
140 break;
141 case "EnvMap":
142 options.EnvironmentMap = xml.ReadElementContentAsString();
143 break;
144 case "Speed":
145 options.Speed = xml.ReadElementContentAsInt();
146 break;
147 case "Image":
148 images.Add(Path.Combine(basePath, xml.ReadElementContentAsString()));
149 break;
150 default:
151 Console.Error.WriteLine("Unknown texture option {0}", xml.LocalName);
152 xml.Skip();
153 break;
154 }
155 }
156
157 xml.ReadEndElement();
158
159 options.Images = images.ToArray();
160 }
161
162 public void Write()
163 {
164 Parallel.ForEach(textures.Values, options =>
165 {
166 if (options.Images.Length > 0)
167 {
168 var writer = new TexImporter(this, options);
169 writer.Import();
170 writer.Write(outputDirPath);
171 }
172 });
173
174 Console.WriteLine("Imported {0} textures", textures.Count);
175 }
176
177 private class TexImporter : Importer
178 {
179 private readonly TextureImporter3 importer;
180 private readonly TextureImporterOptions options;
181
182 public TexImporter(TextureImporter3 importer, TextureImporterOptions options)
183 {
184 this.importer = importer;
185 this.options = options;
186
187 BeginImport();
188 }
189
190 public void Import()
191 {
192 var surfaces = new List<Surface>();
193
194 foreach (string imageFilePath in options.Images)
195 surfaces.Add(TextureUtils.LoadImage(imageFilePath));
196
197 if (surfaces.Count == 0)
198 throw new InvalidDataException("No images found. A texture must have at least one image.");
199
200 TextureFormat format;
201
202 if (options.Format != null)
203 {
204 format = options.Format.Value;
205 }
206 else
207 {
208 var surface = surfaces[0];
209
210 if (surface.HasTransparentPixels())
211 format = importer.defaultAlphaFormat;
212 else if (surface.Width % 4 == 0 && surface.Height % 4 == 0)
213 format = importer.defaultSquareFormat;
214 else
215 format = importer.defaultFormat;
216 }
217
218 int imageWidth = 0;
219 int imageHeight = 0;
220
221 foreach (var surface in surfaces)
222 {
223 if (imageWidth == 0)
224 imageWidth = surface.Width;
225 else if (imageWidth != surface.Width)
226 throw new NotSupportedException("All animation frames must have the same size.");
227
228 if (imageHeight == 0)
229 imageHeight = surface.Height;
230 else if (imageHeight != surface.Height)
231 throw new NotSupportedException("All animation frames must have the same size.");
232 }
233
234 int width = options.Width;
235 int height = options.Height;
236
237 if (width == 0)
238 width = imageWidth;
239 else if (width > imageWidth)
240 throw new NotSupportedException("Cannot upscale images.");
241
242 if (height == 0)
243 height = imageHeight;
244 else if (height > imageHeight)
245 throw new NotSupportedException("Cannot upscale images.");
246
247 if (width > importer.maxSize || height > importer.maxSize)
248 {
249 if (width > height)
250 {
251 height = importer.maxSize * height / width;
252 width = importer.maxSize;
253 }
254 else
255 {
256 width = importer.maxSize * width / height;
257 height = importer.maxSize;
258 }
259 }
260
261 width = TextureUtils.RoundToPowerOf2(width);
262 height = TextureUtils.RoundToPowerOf2(height);
263
264 if (width != imageWidth || height != imageHeight)
265 {
266 for (int i = 0; i < surfaces.Count; i++)
267 surfaces[i] = surfaces[i].Resize(width, height);
268 }
269
270 var flags = options.Flags | TextureFlags.HasMipMaps;
271 flags &= ~(TextureFlags.HasEnvMap | TextureFlags.SwapBytes);
272 var envMapName = options.EnvironmentMap;
273
274 if (format != TextureFormat.RGBA)
275 flags |= TextureFlags.SwapBytes;
276
277 if (!string.IsNullOrEmpty(envMapName))
278 flags |= TextureFlags.HasEnvMap;
279
280 var name = options.Name;
281 int speed = options.Speed;
282
283 for (int i = 0; i < surfaces.Count; i++)
284 {
285 var descriptor = CreateInstance(TemplateTag.TXMP, i == 0 ? name : null);
286
287 using (var writer = descriptor.OpenWrite(128))
288 {
289 writer.Write((int)flags);
290 writer.WriteUInt16(width);
291 writer.WriteUInt16(height);
292 writer.Write((int)format);
293
294 if (i == 0 && surfaces.Count > 1)
295 writer.WriteInstanceId(surfaces.Count);
296 else
297 writer.Write(0);
298
299 if (!string.IsNullOrEmpty(envMapName))
300 writer.WriteInstanceId(surfaces.Count + ((surfaces.Count > 1) ? 1 : 0));
301 else
302 writer.Write(0);
303
304 writer.Write(RawWriter.Align32());
305 writer.Skip(12);
306
307 var mainSurface = surfaces[i];
308
309 var levels = new List<Surface>(16);
310 levels.Add(mainSurface);
311
312 if ((flags & TextureFlags.HasMipMaps) != 0)
313 {
314 int mipWidth = width;
315 int mipHeight = height;
316
317 var surface = mainSurface;
318
319 while (mipWidth > 1 || mipHeight > 1)
320 {
321 mipWidth = Math.Max(mipWidth >> 1, 1);
322 mipHeight = Math.Max(mipHeight >> 1, 1);
323
324 surface = surface.Resize(mipWidth, mipHeight);
325
326 levels.Add(surface);
327 }
328 }
329
330 foreach (var level in levels)
331 {
332 var surface = level.Convert(format.ToSurfaceFormat());
333
334 RawWriter.Write(surface.Data);
335 }
336 }
337 }
338
339 if (surfaces.Count > 1)
340 {
341 var txan = CreateInstance(TemplateTag.TXAN);
342
343 using (var writer = txan.OpenWrite(12))
344 {
345 writer.WriteInt16(speed);
346 writer.WriteInt16(speed);
347 writer.Write(0);
348 writer.Write(surfaces.Count);
349 writer.Write(0);
350
351 for (int i = 1; i < surfaces.Count; i++)
352 writer.WriteInstanceId(i);
353 }
354 }
355
356 if (!string.IsNullOrEmpty(envMapName))
357 CreateInstance(TemplateTag.TXMP, envMapName);
358 }
359 }
360 }
361}
Note: See TracBrowser for help on using the repository browser.