using System; using System.Collections.Generic; using System.IO; using System.Xml; namespace Oni.Motoko { using Imaging; using Metadata; internal class TextureImporter3 { private readonly string outputDirPath; private readonly Dictionary textures = new Dictionary(StringComparer.Ordinal); private TextureFormat defaultFormat = TextureFormat.BGR; private TextureFormat defaultSquareFormat = TextureFormat.BGR; private TextureFormat defaultAlphaFormat = TextureFormat.RGBA; private int maxSize = 512; public TextureImporter3(string outputDirPath) { this.outputDirPath = outputDirPath; } public TextureFormat DefaultFormat { get { return defaultFormat; } set { defaultFormat = value; defaultSquareFormat = value; } } public TextureFormat DefaultAlphaFormat { get { return defaultAlphaFormat; } set { defaultAlphaFormat = value; } } public int MaxSize { get { return maxSize; } set { maxSize = value; } } public string AddMaterial(Dae.Material material) { if (material == null || material.Effect == null) return null; var texture = material.Effect.Textures.FirstOrDefault(t => t.Channel == Dae.EffectTextureChannel.Diffuse); if (texture == null) return null; var sampler = texture.Sampler; if (sampler == null || sampler.Surface == null || sampler.Surface.InitFrom == null) return null; var filePath = Path.GetFullPath(sampler.Surface.InitFrom.FilePath); if (!File.Exists(filePath)) return null; var options = GetOptions(Path.GetFileNameWithoutExtension(filePath), filePath); return options.Name; } public TextureImporterOptions AddMaterial(Akira.Material material) { return GetOptions(material.Name, material.ImageFilePath); } private TextureImporterOptions GetOptions(string name, string filePath) { TextureImporterOptions options; if (!textures.TryGetValue(name, out options)) { options = new TextureImporterOptions { Name = name, Images = new[] { filePath } }; textures.Add(name, options); } return options; } public TextureImporterOptions GetOptions(string name, bool create) { TextureImporterOptions options; if (!textures.TryGetValue(name, out options) && create) { options = new TextureImporterOptions { Name = name }; textures.Add(name, options); } return options; } public void ReadOptions(XmlReader xml, string basePath) { var options = GetOptions(xml.GetAttribute("Name"), true); var images = new List(); xml.ReadStartElement("Texture"); while (xml.IsStartElement()) { switch (xml.LocalName) { case "Width": options.Width = xml.ReadElementContentAsInt(); break; case "Height": options.Height = xml.ReadElementContentAsInt(); break; case "Format": options.Format = TextureImporter.ParseTextureFormat(xml.ReadElementContentAsString()); break; case "Flags": options.Flags = xml.ReadElementContentAsEnum(); break; case "GunkFlags": options.GunkFlags = xml.ReadElementContentAsEnum(); break; case "EnvMap": options.EnvironmentMap = xml.ReadElementContentAsString(); break; case "Speed": options.Speed = xml.ReadElementContentAsInt(); break; case "Image": images.Add(Path.Combine(basePath, xml.ReadElementContentAsString())); break; default: Console.Error.WriteLine("Unknown texture option {0}", xml.LocalName); xml.Skip(); break; } } xml.ReadEndElement(); options.Images = images.ToArray(); } public void Write() { Parallel.ForEach(textures.Values, options => { if (options.Images.Length > 0) { var writer = new TexImporter(this, options); writer.Import(); writer.Write(outputDirPath); } }); Console.WriteLine("Imported {0} textures", textures.Count); } private class TexImporter : Importer { private readonly TextureImporter3 importer; private readonly TextureImporterOptions options; public TexImporter(TextureImporter3 importer, TextureImporterOptions options) { this.importer = importer; this.options = options; BeginImport(); } public void Import() { var surfaces = new List(); foreach (string imageFilePath in options.Images) surfaces.Add(TextureUtils.LoadImage(imageFilePath)); if (surfaces.Count == 0) throw new InvalidDataException("No images found. A texture must have at least one image."); TextureFormat format; if (options.Format != null) { format = options.Format.Value; } else { var surface = surfaces[0]; if (surface.HasTransparentPixels()) format = importer.defaultAlphaFormat; else if (surface.Width % 4 == 0 && surface.Height % 4 == 0) format = importer.defaultSquareFormat; else format = importer.defaultFormat; } int imageWidth = 0; int imageHeight = 0; foreach (var surface in surfaces) { if (imageWidth == 0) imageWidth = surface.Width; else if (imageWidth != surface.Width) throw new NotSupportedException("All animation frames must have the same size."); if (imageHeight == 0) imageHeight = surface.Height; else if (imageHeight != surface.Height) throw new NotSupportedException("All animation frames must have the same size."); } int width = options.Width; int height = options.Height; if (width == 0) width = imageWidth; else if (width > imageWidth) throw new NotSupportedException("Cannot upscale images."); if (height == 0) height = imageHeight; else if (height > imageHeight) throw new NotSupportedException("Cannot upscale images."); if (width > importer.maxSize || height > importer.maxSize) { if (width > height) { height = importer.maxSize * height / width; width = importer.maxSize; } else { width = importer.maxSize * width / height; height = importer.maxSize; } } width = TextureUtils.RoundToPowerOf2(width); height = TextureUtils.RoundToPowerOf2(height); if (width != imageWidth || height != imageHeight) { for (int i = 0; i < surfaces.Count; i++) surfaces[i] = surfaces[i].Resize(width, height); } var flags = options.Flags | TextureFlags.HasMipMaps; flags &= ~(TextureFlags.HasEnvMap | TextureFlags.SwapBytes); var envMapName = options.EnvironmentMap; if (format != TextureFormat.RGBA) flags |= TextureFlags.SwapBytes; if (!string.IsNullOrEmpty(envMapName)) flags |= TextureFlags.HasEnvMap; var name = options.Name; int speed = options.Speed; for (int i = 0; i < surfaces.Count; i++) { var descriptor = CreateInstance(TemplateTag.TXMP, i == 0 ? name : null); using (var writer = descriptor.OpenWrite(128)) { writer.Write((int)flags); writer.WriteUInt16(width); writer.WriteUInt16(height); writer.Write((int)format); if (i == 0 && surfaces.Count > 1) writer.WriteInstanceId(surfaces.Count); else writer.Write(0); if (!string.IsNullOrEmpty(envMapName)) writer.WriteInstanceId(surfaces.Count + ((surfaces.Count > 1) ? 1 : 0)); else writer.Write(0); writer.Write(RawWriter.Align32()); writer.Skip(12); var mainSurface = surfaces[i]; var levels = new List(16); levels.Add(mainSurface); if ((flags & TextureFlags.HasMipMaps) != 0) { int mipWidth = width; int mipHeight = height; var surface = mainSurface; while (mipWidth > 1 || mipHeight > 1) { mipWidth = Math.Max(mipWidth >> 1, 1); mipHeight = Math.Max(mipHeight >> 1, 1); surface = surface.Resize(mipWidth, mipHeight); levels.Add(surface); } } foreach (var level in levels) { var surface = level.Convert(format.ToSurfaceFormat()); RawWriter.Write(surface.Data); } } } if (surfaces.Count > 1) { var txan = CreateInstance(TemplateTag.TXAN); using (var writer = txan.OpenWrite(12)) { writer.WriteInt16(speed); writer.WriteInt16(speed); writer.Write(0); writer.Write(surfaces.Count); writer.Write(0); for (int i = 1; i < surfaces.Count; i++) writer.WriteInstanceId(i); } } if (!string.IsNullOrEmpty(envMapName)) CreateInstance(TemplateTag.TXMP, envMapName); } } } }