using System; using System.Collections.Generic; using System.Globalization; using System.IO; using Oni.Imaging; namespace Oni.Akira { internal class AkiraDaeWriter { #region Private data private readonly PolygonMesh source; private DaeSceneBuilder world; private DaeSceneBuilder worldMarkers; private DaeSceneBuilder[] objects; private DaeSceneBuilder rooms; private Dictionary scripts; private static readonly string[] objectTypeNames = new[] { "", "char", "patr", "door", "flag", "furn", "", "", "part", "pwru", "sndg", "trgv", "weap", "trig", "turr", "cons", "cmbt", "mele", "neut" }; #endregion #region private class DaePolygon private class DaePolygon { private readonly Polygon source; private readonly Material material; private readonly int[] pointIndices; private readonly int[] texCoordIndices; private readonly int[] colorIndices; public DaePolygon(Polygon source, int[] pointIndices, int[] texCoordIndices, int[] colorIndices) { this.source = source; material = source.Material; this.pointIndices = pointIndices; this.texCoordIndices = texCoordIndices; this.colorIndices = colorIndices; } public DaePolygon(Material material, int[] pointIndices, int[] texCoordIndices) { this.material = material; this.pointIndices = pointIndices; this.texCoordIndices = texCoordIndices; } public Polygon Source => source; public Material Material => material; public int[] PointIndices => pointIndices; public int[] TexCoordIndices => texCoordIndices; public int[] ColorIndices => colorIndices; } #endregion #region private class DaeMeshBuilder private class DaeMeshBuilder { private readonly List polygons = new List(); private readonly List points = new List(); private readonly Dictionary uniquePoints = new Dictionary(); private readonly List texCoords = new List(); private readonly Dictionary uniqueTexCoords = new Dictionary(); private readonly List colors = new List(); private readonly Dictionary uniqueColors = new Dictionary(); private string name; private Vector3 translation; private Dae.Geometry geometry; public DaeMeshBuilder(string name) { this.name = name; } public string Name { get { return name; } set { name = value; } } public Vector3 Translation => translation; public void ResetTransform() { // // Attempt to un-bake the translation of the furniture // var center = BoundingSphere.CreateFromPoints(points).Center; center.Y = BoundingBox.CreateFromPoints(points).Min.Y; translation = center; for (int i = 0; i < points.Count; i++) points[i] -= center; } public void AddPolygon(Material material, Vector3[] polygonPoints, Vector2[] polygonTexCoords) { polygons.Add(new DaePolygon( material, Remap(polygonPoints, points, uniquePoints), Remap(polygonTexCoords, texCoords, uniqueTexCoords))); } public void AddPolygon(Polygon polygon) { polygons.Add(new DaePolygon( polygon, Remap(polygon.Mesh.Points, polygon.PointIndices, points, uniquePoints), Remap(polygon.Mesh.TexCoords, polygon.TexCoordIndices, texCoords, uniqueTexCoords), Remap(polygon.Colors, colors, uniqueColors))); } public IEnumerable Polygons { get { return from p in polygons where p.Source != null select p.Source; } } private static int[] Remap(IList values, int[] indices, List list, Dictionary unique) where T : struct { var result = new int[indices.Length]; for (int i = 0; i < indices.Length; i++) result[i] = AddUnique(list, unique, values[indices[i]]); return result; } private static int[] Remap(IList values, List list, Dictionary unique) where T : struct { var result = new int[values.Count]; for (int i = 0; i < values.Count; i++) result[i] = AddUnique(list, unique, values[i]); return result; } private static int AddUnique(List list, Dictionary unique, T value) where T : struct { int index; if (!unique.TryGetValue(value, out index)) { index = list.Count; unique.Add(value, index); list.Add(value); } return index; } public void Build() { geometry = new Dae.Geometry(); geometry.Name = Name + "_geo"; var positionSource = new Dae.Source(points); var texCoordSource = new Dae.Source(texCoords); var colorSource = new Dae.Source(ColorArrayToFloatArray(colors), 4); geometry.Vertices.Add(new Dae.Input(Dae.Semantic.Position, positionSource)); Dae.IndexedInput posInput = null; Dae.IndexedInput texCoordInput = null; Dae.IndexedInput colorInput = null; var materialPrimitives = new Dictionary(); polygons.Sort((x, y) => string.Compare(x.Material.Name, y.Material.Name)); foreach (var poly in polygons) { Dae.MeshPrimitives primitives; if (!materialPrimitives.TryGetValue(poly.Material, out primitives)) { primitives = new Dae.MeshPrimitives(Dae.MeshPrimitiveType.Polygons); materialPrimitives.Add(poly.Material, primitives); posInput = new Dae.IndexedInput(Dae.Semantic.Position, positionSource); primitives.Inputs.Add(posInput); texCoordInput = new Dae.IndexedInput(Dae.Semantic.TexCoord, texCoordSource); primitives.Inputs.Add(texCoordInput); if (poly.ColorIndices != null /*&& !poly.Material.Image.HasAlpha*/) { colorInput = new Dae.IndexedInput(Dae.Semantic.Color, colorSource); primitives.Inputs.Add(colorInput); } primitives.MaterialSymbol = poly.Material.Name; geometry.Primitives.Add(primitives); } primitives.VertexCounts.Add(poly.PointIndices.Length); posInput.Indices.AddRange(poly.PointIndices); texCoordInput.Indices.AddRange(poly.TexCoordIndices); if (colorInput != null) { // // If the first polygon had color indices then the rest better have them too... // colorInput.Indices.AddRange(poly.ColorIndices); } } } public Dae.Geometry Geometry => geometry; public void InstantiateMaterials(Dae.GeometryInstance inst, DaeSceneBuilder sceneBuilder) { var matInstances = new Dictionary(); foreach (var poly in polygons) { if (matInstances.ContainsKey(poly.Material)) continue; string matSymbol = poly.Material.Name; var matInstance = new Dae.MaterialInstance( matSymbol, sceneBuilder.GetMaterial(poly.Material)); matInstances.Add(poly.Material, matInstance); var primitives = geometry.Primitives.FirstOrDefault(p => p.MaterialSymbol == matSymbol); if (primitives == null) continue; var texCoordInput = primitives.Inputs.Find(i => i.Semantic == Dae.Semantic.TexCoord); if (texCoordInput == null) continue; matInstance.Bindings.Add(new Dae.MaterialBinding("diffuse_TEXCOORD", texCoordInput)); inst.Materials.Add(matInstance); } } private static float[] ColorArrayToFloatArray(IList array) { var result = new float[array.Count * 4]; for (int i = 0; i < array.Count; i++) { var color = array[i].ToVector3(); result[i * 4 + 0] = color.X; result[i * 4 + 1] = color.Y; result[i * 4 + 2] = color.Z; } return result; } } #endregion #region private class DaeSceneBuilder private class DaeSceneBuilder { private readonly Dae.Scene scene; private readonly Dictionary nameMeshBuilder; private readonly List meshBuilders; private readonly Dictionary materials; private string imagesFolder = "images"; public DaeSceneBuilder() { scene = new Dae.Scene(); nameMeshBuilder = new Dictionary(StringComparer.Ordinal); meshBuilders = new List(); materials = new Dictionary(); } public string ImagesFolder { get { return imagesFolder; } set { imagesFolder = value; } } public DaeMeshBuilder GetMeshBuilder(string name) { DaeMeshBuilder result; if (!nameMeshBuilder.TryGetValue(name, out result)) { result = new DaeMeshBuilder(name); nameMeshBuilder.Add(name, result); meshBuilders.Add(result); } return result; } public IEnumerable MeshBuilders => meshBuilders; public Dae.Material GetMaterial(Material material) { Dae.Material result; if (!materials.TryGetValue(material, out result)) { result = new Dae.Material(); materials.Add(material, result); } return result; } public void Build() { BuildNodes(); BuildMaterials(); } private void BuildNodes() { foreach (var meshBuilder in meshBuilders) { meshBuilder.Build(); var instance = new Dae.GeometryInstance(meshBuilder.Geometry); meshBuilder.InstantiateMaterials(instance, this); var node = new Dae.Node(); node.Name = meshBuilder.Name; node.Instances.Add(instance); if (meshBuilder.Translation != Vector3.Zero) node.Transforms.Add(new Dae.TransformTranslate(meshBuilder.Translation)); scene.Nodes.Add(node); } } private void BuildMaterials() { foreach (var pair in materials) { var material = pair.Key; var daeMaterial = pair.Value; string imageFileName = GetImageFileName(material); var image = new Dae.Image { FilePath = "./" + imageFileName.Replace('\\', '/'), Name = material.Name + "_img" }; var effectSurface = new Dae.EffectSurface(image); var effectSampler = new Dae.EffectSampler(effectSurface) { // WrapS = texture.WrapU ? Dae.EffectSamplerWrap.Wrap : Dae.EffectSamplerWrap.None, // WrapT = texture.WrapV ? Dae.EffectSamplerWrap.Wrap : Dae.EffectSamplerWrap.None }; var effectTexture = new Dae.EffectTexture(effectSampler, "diffuse_TEXCOORD"); var effect = new Dae.Effect { Name = material.Name + "_fx", AmbientValue = Vector4.One, SpecularValue = Vector4.Zero, DiffuseValue = effectTexture, TransparentValue = material.Image.HasAlpha ? effectTexture : null, Parameters = { new Dae.EffectParameter("surface", effectSurface), new Dae.EffectParameter("sampler", effectSampler) } }; daeMaterial.Name = material.Name; daeMaterial.Effect = effect; } } private string GetImageFileName(Material material) { string fileName = material.Name + ".tga"; if (material.IsMarker) return Path.Combine("markers", fileName); return Path.Combine(imagesFolder, fileName); } public void Write(string filePath) { string outputDirPath = Path.GetDirectoryName(filePath); foreach (var material in materials.Keys) TgaWriter.Write(material.Image, Path.Combine(outputDirPath, GetImageFileName(material))); Dae.Writer.WriteFile(filePath, scene); } } #endregion public static void WriteRooms(PolygonMesh mesh, string name, string outputDirPath) { var writer = new AkiraDaeWriter(mesh); writer.WriteRooms(); writer.rooms.Write(Path.Combine(outputDirPath, name + "_bnv.dae")); } public static void WriteRooms(PolygonMesh mesh, string filePath) { var writer = new AkiraDaeWriter(mesh); writer.WriteRooms(); writer.rooms.Write(filePath); } public static void Write(PolygonMesh mesh, string name, string outputDirPath, string fileType) { var writer = new AkiraDaeWriter(mesh); writer.WriteGeometry(); writer.WriteRooms(); writer.world.Write(Path.Combine(outputDirPath, name + "_env." + fileType)); writer.worldMarkers.Write(Path.Combine(outputDirPath, name + "_env_markers." + fileType)); writer.rooms.Write(Path.Combine(outputDirPath, name + "_bnv." + fileType)); for (int i = 0; i < writer.objects.Length; i++) { var builder = writer.objects[i]; if (builder != null) builder.Write(Path.Combine(outputDirPath, string.Format("{0}_{1}." + fileType, name, objectTypeNames[i]))); } foreach (var pair in writer.scripts) { var scriptId = pair.Key; var builder = pair.Value; builder.Write(Path.Combine(outputDirPath, string.Format("{0}_script_{1}." + fileType, name, scriptId))); } } private AkiraDaeWriter(PolygonMesh source) { this.source = source; } private void WriteGeometry() { world = new DaeSceneBuilder(); worldMarkers = new DaeSceneBuilder(); objects = new DaeSceneBuilder[objectTypeNames.Length]; scripts = new Dictionary(); foreach (var polygon in source.Polygons) { if (polygon.Material == null) continue; int objectType = polygon.ObjectType; int scriptId = polygon.ScriptId; if (scriptId != 0) { string name = string.Format(CultureInfo.InvariantCulture, "script_{0}", scriptId); DaeSceneBuilder sceneBuilder; if (!scripts.TryGetValue(scriptId, out sceneBuilder)) { sceneBuilder = new DaeSceneBuilder(); scripts.Add(scriptId, sceneBuilder); } var meshBuilder = sceneBuilder.GetMeshBuilder(name); meshBuilder.AddPolygon(polygon); } else if (objectType == -1) { // // If it doesn't have an object type then it's probably an environment polygon. // string name; if (source.HasDebugInfo) name = polygon.FileName; else name = "world"; DaeMeshBuilder meshBuilder; if (polygon.Material.IsMarker) meshBuilder = worldMarkers.GetMeshBuilder(name); else meshBuilder = world.GetMeshBuilder(name); meshBuilder.AddPolygon(polygon); } else { // // This polygon belongs to a object. Export it to one of the object files. // string name = string.Format(CultureInfo.InvariantCulture, "{0}_{1}", objectTypeNames[objectType], polygon.ObjectId); var sceneBuilder = objects[objectType]; if (sceneBuilder == null) { sceneBuilder = new DaeSceneBuilder(); objects[objectType] = sceneBuilder; } var meshBuilder = sceneBuilder.GetMeshBuilder(name); meshBuilder.AddPolygon(polygon); } } foreach (var sceneBuilder in objects) { if (sceneBuilder == null) continue; foreach (var meshBuilder in sceneBuilder.MeshBuilders) { meshBuilder.ResetTransform(); if (source.HasDebugInfo) { // // Polygons that belong to an object have useful object names in the debug info. // Try to use them. // var names = new List(); int objectId = 0; foreach (var polygon in meshBuilder.Polygons) { objectId = polygon.ObjectId; names.Add(polygon.ObjectName); } string name = Utils.CommonPrefix(names); if (!string.IsNullOrEmpty(name) && name.Length > 3) { if (!name.EndsWith("_", StringComparison.Ordinal)) name += "_"; meshBuilder.Name = string.Format(CultureInfo.InvariantCulture, "{0}{1}", name, objectId); } } } } foreach (var sceneBuilder in scripts.Values) { foreach (var meshBuilder in sceneBuilder.MeshBuilders) { meshBuilder.ResetTransform(); if (source.HasDebugInfo) { // // Polygons that belong to an object have useful object names in the debug info. // Try to use them. // var names = new List(); int scriptId = 0; foreach (var polygon in meshBuilder.Polygons) { scriptId = polygon.ScriptId; names.Add(polygon.ObjectName); } string name = Utils.CommonPrefix(names); if (!string.IsNullOrEmpty(name) && name.Length > 3) { if (!name.EndsWith("_", StringComparison.Ordinal)) name += "_"; meshBuilder.Name = string.Format(CultureInfo.InvariantCulture, "{0}{1}", name, scriptId); } } } } world.Build(); worldMarkers.Build(); foreach (var sceneBuilder in objects) { if (sceneBuilder == null) continue; sceneBuilder.Build(); } foreach (var sceneBuilder in scripts.Values) { sceneBuilder.Build(); } } private void WriteRooms() { rooms = new DaeSceneBuilder(); rooms.ImagesFolder = "grids"; for (int i = 0; i < source.Rooms.Count; i++) { var room = source.Rooms[i]; var meshBuilder = rooms.GetMeshBuilder( string.Format(CultureInfo.InvariantCulture, "room_{0}", i)); var material = source.Materials.GetMaterial( string.Format(CultureInfo.InvariantCulture, "bnv_grid_{0:d3}", i)); material.Image = room.Grid.ToImage(); foreach (var polygonPoints in room.GetFloorPolygons()) { var texCoords = new Vector2[polygonPoints.Length]; for (int j = 0; j < polygonPoints.Length; j++) { var point = polygonPoints[j]; var min = room.BoundingBox.Min; var max = room.BoundingBox.Max; min += new Vector3(room.Grid.TileSize * room.Grid.XOrigin, 0.0f, room.Grid.TileSize * room.Grid.ZOrigin); max -= new Vector3(room.Grid.TileSize * room.Grid.XOrigin, 0.0f, room.Grid.TileSize * room.Grid.ZOrigin); var size = max - min; float x = (point.X - min.X) / size.X; float z = (point.Z - min.Z) / size.Z; texCoords[j] = new Vector2(x, z); } meshBuilder.AddPolygon(material, polygonPoints, texCoords); meshBuilder.Build(); } } var ghostTexCoords = new[] { new Vector2(0.0f, 0.0f), new Vector2(1.0f, 0.0f), new Vector2(1.0f, 1.0f), new Vector2(0.0f, 1.0f) }; for (int i = 0; i < source.Ghosts.Count; i++) { var ghost = source.Ghosts[i]; var meshBuilder = rooms.GetMeshBuilder( string.Format(CultureInfo.InvariantCulture, "ghost_{0}", i)); meshBuilder.AddPolygon( source.Materials.Markers.Ghost, ghost.Points.ToArray(), ghostTexCoords); meshBuilder.Build(); meshBuilder.ResetTransform(); } rooms.Build(); } } }