source: OniSplit/Akira/AkiraDaeWriter.cs@ 1194

Last change on this file since 1194 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: 25.5 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Globalization;
4using System.IO;
5using Oni.Imaging;
6
7namespace Oni.Akira
8{
9 internal class AkiraDaeWriter
10 {
11 #region Private data
12 private readonly PolygonMesh source;
13 private DaeSceneBuilder world;
14 private DaeSceneBuilder worldMarkers;
15 private DaeSceneBuilder[] objects;
16 private DaeSceneBuilder rooms;
17 private Dictionary<int, DaeSceneBuilder> scripts;
18
19 private static readonly string[] objectTypeNames = new[] {
20 "",
21 "char",
22 "patr",
23 "door",
24 "flag",
25 "furn",
26 "",
27 "",
28 "part",
29 "pwru",
30 "sndg",
31 "trgv",
32 "weap",
33 "trig",
34 "turr",
35 "cons",
36 "cmbt",
37 "mele",
38 "neut"
39 };
40
41 #endregion
42
43 #region private class DaePolygon
44
45 private class DaePolygon
46 {
47 private readonly Polygon source;
48 private readonly Material material;
49 private readonly int[] pointIndices;
50 private readonly int[] texCoordIndices;
51 private readonly int[] colorIndices;
52
53 public DaePolygon(Polygon source, int[] pointIndices, int[] texCoordIndices, int[] colorIndices)
54 {
55 this.source = source;
56 material = source.Material;
57 this.pointIndices = pointIndices;
58 this.texCoordIndices = texCoordIndices;
59 this.colorIndices = colorIndices;
60 }
61
62 public DaePolygon(Material material, int[] pointIndices, int[] texCoordIndices)
63 {
64 this.material = material;
65 this.pointIndices = pointIndices;
66 this.texCoordIndices = texCoordIndices;
67 }
68
69 public Polygon Source => source;
70 public Material Material => material;
71 public int[] PointIndices => pointIndices;
72 public int[] TexCoordIndices => texCoordIndices;
73 public int[] ColorIndices => colorIndices;
74 }
75
76 #endregion
77 #region private class DaeMeshBuilder
78
79 private class DaeMeshBuilder
80 {
81 private readonly List<DaePolygon> polygons = new List<DaePolygon>();
82 private readonly List<Vector3> points = new List<Vector3>();
83 private readonly Dictionary<Vector3, int> uniquePoints = new Dictionary<Vector3, int>();
84 private readonly List<Vector2> texCoords = new List<Vector2>();
85 private readonly Dictionary<Vector2, int> uniqueTexCoords = new Dictionary<Vector2, int>();
86 private readonly List<Color> colors = new List<Color>();
87 private readonly Dictionary<Color, int> uniqueColors = new Dictionary<Color, int>();
88 private string name;
89 private Vector3 translation;
90 private Dae.Geometry geometry;
91
92 public DaeMeshBuilder(string name)
93 {
94 this.name = name;
95 }
96
97 public string Name
98 {
99 get { return name; }
100 set { name = value; }
101 }
102
103 public Vector3 Translation => translation;
104
105 public void ResetTransform()
106 {
107 //
108 // Attempt to un-bake the translation of the furniture
109 //
110
111 var center = BoundingSphere.CreateFromPoints(points).Center;
112 center.Y = BoundingBox.CreateFromPoints(points).Min.Y;
113
114 translation = center;
115
116 for (int i = 0; i < points.Count; i++)
117 points[i] -= center;
118 }
119
120 public void AddPolygon(Material material, Vector3[] polygonPoints, Vector2[] polygonTexCoords)
121 {
122 polygons.Add(new DaePolygon(
123 material,
124 Remap(polygonPoints, points, uniquePoints),
125 Remap(polygonTexCoords, texCoords, uniqueTexCoords)));
126 }
127
128 public void AddPolygon(Polygon polygon)
129 {
130 polygons.Add(new DaePolygon(
131 polygon,
132 Remap(polygon.Mesh.Points, polygon.PointIndices, points, uniquePoints),
133 Remap(polygon.Mesh.TexCoords, polygon.TexCoordIndices, texCoords, uniqueTexCoords),
134 Remap(polygon.Colors, colors, uniqueColors)));
135 }
136
137 public IEnumerable<Polygon> Polygons
138 {
139 get
140 {
141 return from p in polygons
142 where p.Source != null
143 select p.Source;
144 }
145 }
146
147 private static int[] Remap<T>(IList<T> values, int[] indices, List<T> list, Dictionary<T, int> unique)
148 where T : struct
149 {
150 var result = new int[indices.Length];
151
152 for (int i = 0; i < indices.Length; i++)
153 result[i] = AddUnique(list, unique, values[indices[i]]);
154
155 return result;
156 }
157
158 private static int[] Remap<T>(IList<T> values, List<T> list, Dictionary<T, int> unique)
159 where T : struct
160 {
161 var result = new int[values.Count];
162
163 for (int i = 0; i < values.Count; i++)
164 result[i] = AddUnique(list, unique, values[i]);
165
166 return result;
167 }
168
169 private static int AddUnique<T>(List<T> list, Dictionary<T, int> unique, T value)
170 where T : struct
171 {
172 int index;
173
174 if (!unique.TryGetValue(value, out index))
175 {
176 index = list.Count;
177 unique.Add(value, index);
178 list.Add(value);
179 }
180
181 return index;
182 }
183
184 public void Build()
185 {
186 geometry = new Dae.Geometry();
187 geometry.Name = Name + "_geo";
188
189 var positionSource = new Dae.Source(points);
190 var texCoordSource = new Dae.Source(texCoords);
191 var colorSource = new Dae.Source(ColorArrayToFloatArray(colors), 4);
192
193 geometry.Vertices.Add(new Dae.Input(Dae.Semantic.Position, positionSource));
194
195 Dae.IndexedInput posInput = null;
196 Dae.IndexedInput texCoordInput = null;
197 Dae.IndexedInput colorInput = null;
198
199 var materialPrimitives = new Dictionary<Material, Dae.MeshPrimitives>();
200
201 polygons.Sort((x, y) => string.Compare(x.Material.Name, y.Material.Name));
202
203 foreach (var poly in polygons)
204 {
205 Dae.MeshPrimitives primitives;
206
207 if (!materialPrimitives.TryGetValue(poly.Material, out primitives))
208 {
209 primitives = new Dae.MeshPrimitives(Dae.MeshPrimitiveType.Polygons);
210 materialPrimitives.Add(poly.Material, primitives);
211
212 posInput = new Dae.IndexedInput(Dae.Semantic.Position, positionSource);
213 primitives.Inputs.Add(posInput);
214
215 texCoordInput = new Dae.IndexedInput(Dae.Semantic.TexCoord, texCoordSource);
216 primitives.Inputs.Add(texCoordInput);
217
218 if (poly.ColorIndices != null /*&& !poly.Material.Image.HasAlpha*/)
219 {
220 colorInput = new Dae.IndexedInput(Dae.Semantic.Color, colorSource);
221 primitives.Inputs.Add(colorInput);
222 }
223
224 primitives.MaterialSymbol = poly.Material.Name;
225 geometry.Primitives.Add(primitives);
226 }
227
228 primitives.VertexCounts.Add(poly.PointIndices.Length);
229
230 posInput.Indices.AddRange(poly.PointIndices);
231 texCoordInput.Indices.AddRange(poly.TexCoordIndices);
232
233 if (colorInput != null)
234 {
235 //
236 // If the first polygon had color indices then the rest better have them too...
237 //
238
239 colorInput.Indices.AddRange(poly.ColorIndices);
240 }
241 }
242 }
243
244 public Dae.Geometry Geometry => geometry;
245
246 public void InstantiateMaterials(Dae.GeometryInstance inst, DaeSceneBuilder sceneBuilder)
247 {
248 var matInstances = new Dictionary<Material, Dae.MaterialInstance>();
249
250 foreach (var poly in polygons)
251 {
252 if (matInstances.ContainsKey(poly.Material))
253 continue;
254
255 string matSymbol = poly.Material.Name;
256
257 var matInstance = new Dae.MaterialInstance(
258 matSymbol,
259 sceneBuilder.GetMaterial(poly.Material));
260
261 matInstances.Add(poly.Material, matInstance);
262
263 var primitives = geometry.Primitives.FirstOrDefault(p => p.MaterialSymbol == matSymbol);
264
265 if (primitives == null)
266 continue;
267
268 var texCoordInput = primitives.Inputs.Find(i => i.Semantic == Dae.Semantic.TexCoord);
269
270 if (texCoordInput == null)
271 continue;
272
273 matInstance.Bindings.Add(new Dae.MaterialBinding("diffuse_TEXCOORD", texCoordInput));
274 inst.Materials.Add(matInstance);
275 }
276 }
277
278 private static float[] ColorArrayToFloatArray(IList<Color> array)
279 {
280 var result = new float[array.Count * 4];
281
282 for (int i = 0; i < array.Count; i++)
283 {
284 var color = array[i].ToVector3();
285
286 result[i * 4 + 0] = color.X;
287 result[i * 4 + 1] = color.Y;
288 result[i * 4 + 2] = color.Z;
289 }
290
291 return result;
292 }
293 }
294
295 #endregion
296 #region private class DaeSceneBuilder
297
298 private class DaeSceneBuilder
299 {
300 private readonly Dae.Scene scene;
301 private readonly Dictionary<string, DaeMeshBuilder> nameMeshBuilder;
302 private readonly List<DaeMeshBuilder> meshBuilders;
303 private readonly Dictionary<Material, Dae.Material> materials;
304 private string imagesFolder = "images";
305
306 public DaeSceneBuilder()
307 {
308 scene = new Dae.Scene();
309 nameMeshBuilder = new Dictionary<string, DaeMeshBuilder>(StringComparer.Ordinal);
310 meshBuilders = new List<DaeMeshBuilder>();
311 materials = new Dictionary<Material, Dae.Material>();
312 }
313
314 public string ImagesFolder
315 {
316 get { return imagesFolder; }
317 set { imagesFolder = value; }
318 }
319
320 public DaeMeshBuilder GetMeshBuilder(string name)
321 {
322 DaeMeshBuilder result;
323
324 if (!nameMeshBuilder.TryGetValue(name, out result))
325 {
326 result = new DaeMeshBuilder(name);
327 nameMeshBuilder.Add(name, result);
328 meshBuilders.Add(result);
329 }
330
331 return result;
332 }
333
334 public IEnumerable<DaeMeshBuilder> MeshBuilders => meshBuilders;
335
336 public Dae.Material GetMaterial(Material material)
337 {
338 Dae.Material result;
339
340 if (!materials.TryGetValue(material, out result))
341 {
342 result = new Dae.Material();
343 materials.Add(material, result);
344 }
345
346 return result;
347 }
348
349 public void Build()
350 {
351 BuildNodes();
352 BuildMaterials();
353 }
354
355 private void BuildNodes()
356 {
357 foreach (var meshBuilder in meshBuilders)
358 {
359 meshBuilder.Build();
360
361 var instance = new Dae.GeometryInstance(meshBuilder.Geometry);
362
363 meshBuilder.InstantiateMaterials(instance, this);
364
365 var node = new Dae.Node();
366 node.Name = meshBuilder.Name;
367 node.Instances.Add(instance);
368
369 if (meshBuilder.Translation != Vector3.Zero)
370 node.Transforms.Add(new Dae.TransformTranslate(meshBuilder.Translation));
371
372 scene.Nodes.Add(node);
373 }
374 }
375
376 private void BuildMaterials()
377 {
378 foreach (var pair in materials)
379 {
380 var material = pair.Key;
381 var daeMaterial = pair.Value;
382
383 string imageFileName = GetImageFileName(material);
384
385 var image = new Dae.Image
386 {
387 FilePath = "./" + imageFileName.Replace('\\', '/'),
388 Name = material.Name + "_img"
389 };
390
391 var effectSurface = new Dae.EffectSurface(image);
392
393 var effectSampler = new Dae.EffectSampler(effectSurface)
394 {
395 // WrapS = texture.WrapU ? Dae.EffectSamplerWrap.Wrap : Dae.EffectSamplerWrap.None,
396 // WrapT = texture.WrapV ? Dae.EffectSamplerWrap.Wrap : Dae.EffectSamplerWrap.None
397 };
398
399 var effectTexture = new Dae.EffectTexture(effectSampler, "diffuse_TEXCOORD");
400
401 var effect = new Dae.Effect
402 {
403 Name = material.Name + "_fx",
404 AmbientValue = Vector4.One,
405 SpecularValue = Vector4.Zero,
406 DiffuseValue = effectTexture,
407 TransparentValue = material.Image.HasAlpha ? effectTexture : null,
408 Parameters = {
409 new Dae.EffectParameter("surface", effectSurface),
410 new Dae.EffectParameter("sampler", effectSampler)
411 }
412 };
413
414 daeMaterial.Name = material.Name;
415 daeMaterial.Effect = effect;
416 }
417 }
418
419 private string GetImageFileName(Material material)
420 {
421 string fileName = material.Name + ".tga";
422
423 if (material.IsMarker)
424 return Path.Combine("markers", fileName);
425
426 return Path.Combine(imagesFolder, fileName);
427 }
428
429 public void Write(string filePath)
430 {
431 string outputDirPath = Path.GetDirectoryName(filePath);
432
433 foreach (var material in materials.Keys)
434 TgaWriter.Write(material.Image, Path.Combine(outputDirPath, GetImageFileName(material)));
435
436 Dae.Writer.WriteFile(filePath, scene);
437 }
438 }
439
440 #endregion
441
442 public static void WriteRooms(PolygonMesh mesh, string name, string outputDirPath)
443 {
444 var writer = new AkiraDaeWriter(mesh);
445 writer.WriteRooms();
446 writer.rooms.Write(Path.Combine(outputDirPath, name + "_bnv.dae"));
447 }
448
449 public static void WriteRooms(PolygonMesh mesh, string filePath)
450 {
451 var writer = new AkiraDaeWriter(mesh);
452 writer.WriteRooms();
453 writer.rooms.Write(filePath);
454 }
455
456 public static void Write(PolygonMesh mesh, string name, string outputDirPath, string fileType)
457 {
458 var writer = new AkiraDaeWriter(mesh);
459
460 writer.WriteGeometry();
461 writer.WriteRooms();
462
463 writer.world.Write(Path.Combine(outputDirPath, name + "_env." + fileType));
464 writer.worldMarkers.Write(Path.Combine(outputDirPath, name + "_env_markers." + fileType));
465 writer.rooms.Write(Path.Combine(outputDirPath, name + "_bnv." + fileType));
466
467 for (int i = 0; i < writer.objects.Length; i++)
468 {
469 var builder = writer.objects[i];
470
471 if (builder != null)
472 builder.Write(Path.Combine(outputDirPath, string.Format("{0}_{1}." + fileType, name, objectTypeNames[i])));
473 }
474
475 foreach (var pair in writer.scripts)
476 {
477 var scriptId = pair.Key;
478 var builder = pair.Value;
479
480 builder.Write(Path.Combine(outputDirPath, string.Format("{0}_script_{1}." + fileType, name, scriptId)));
481 }
482 }
483
484 private AkiraDaeWriter(PolygonMesh source)
485 {
486 this.source = source;
487 }
488
489 private void WriteGeometry()
490 {
491 world = new DaeSceneBuilder();
492 worldMarkers = new DaeSceneBuilder();
493 objects = new DaeSceneBuilder[objectTypeNames.Length];
494 scripts = new Dictionary<int, DaeSceneBuilder>();
495
496 foreach (var polygon in source.Polygons)
497 {
498 if (polygon.Material == null)
499 continue;
500
501 int objectType = polygon.ObjectType;
502 int scriptId = polygon.ScriptId;
503
504 if (scriptId != 0)
505 {
506 string name = string.Format(CultureInfo.InvariantCulture, "script_{0}", scriptId);
507
508 DaeSceneBuilder sceneBuilder;
509
510 if (!scripts.TryGetValue(scriptId, out sceneBuilder))
511 {
512 sceneBuilder = new DaeSceneBuilder();
513 scripts.Add(scriptId, sceneBuilder);
514 }
515
516 var meshBuilder = sceneBuilder.GetMeshBuilder(name);
517
518 meshBuilder.AddPolygon(polygon);
519 }
520 else if (objectType == -1)
521 {
522 //
523 // If it doesn't have an object type then it's probably an environment polygon.
524 //
525
526 string name;
527
528 if (source.HasDebugInfo)
529 name = polygon.FileName;
530 else
531 name = "world";
532
533 DaeMeshBuilder meshBuilder;
534
535 if (polygon.Material.IsMarker)
536 meshBuilder = worldMarkers.GetMeshBuilder(name);
537 else
538 meshBuilder = world.GetMeshBuilder(name);
539
540 meshBuilder.AddPolygon(polygon);
541 }
542 else
543 {
544 //
545 // This polygon belongs to a object. Export it to one of the object files.
546 //
547
548 string name = string.Format(CultureInfo.InvariantCulture,
549 "{0}_{1}", objectTypeNames[objectType], polygon.ObjectId);
550
551 var sceneBuilder = objects[objectType];
552
553 if (sceneBuilder == null)
554 {
555 sceneBuilder = new DaeSceneBuilder();
556 objects[objectType] = sceneBuilder;
557 }
558
559 var meshBuilder = sceneBuilder.GetMeshBuilder(name);
560
561 meshBuilder.AddPolygon(polygon);
562 }
563 }
564
565 foreach (var sceneBuilder in objects)
566 {
567 if (sceneBuilder == null)
568 continue;
569
570 foreach (var meshBuilder in sceneBuilder.MeshBuilders)
571 {
572 meshBuilder.ResetTransform();
573
574 if (source.HasDebugInfo)
575 {
576 //
577 // Polygons that belong to an object have useful object names in the debug info.
578 // Try to use them.
579 //
580
581 var names = new List<string>();
582 int objectId = 0;
583
584 foreach (var polygon in meshBuilder.Polygons)
585 {
586 objectId = polygon.ObjectId;
587 names.Add(polygon.ObjectName);
588 }
589
590 string name = Utils.CommonPrefix(names);
591
592 if (!string.IsNullOrEmpty(name) && name.Length > 3)
593 {
594 if (!name.EndsWith("_", StringComparison.Ordinal))
595 name += "_";
596
597 meshBuilder.Name = string.Format(CultureInfo.InvariantCulture,
598 "{0}{1}", name, objectId);
599 }
600 }
601 }
602 }
603
604 foreach (var sceneBuilder in scripts.Values)
605 {
606 foreach (var meshBuilder in sceneBuilder.MeshBuilders)
607 {
608 meshBuilder.ResetTransform();
609
610 if (source.HasDebugInfo)
611 {
612 //
613 // Polygons that belong to an object have useful object names in the debug info.
614 // Try to use them.
615 //
616
617 var names = new List<string>();
618 int scriptId = 0;
619
620 foreach (var polygon in meshBuilder.Polygons)
621 {
622 scriptId = polygon.ScriptId;
623 names.Add(polygon.ObjectName);
624 }
625
626 string name = Utils.CommonPrefix(names);
627
628 if (!string.IsNullOrEmpty(name) && name.Length > 3)
629 {
630 if (!name.EndsWith("_", StringComparison.Ordinal))
631 name += "_";
632
633 meshBuilder.Name = string.Format(CultureInfo.InvariantCulture,
634 "{0}{1}", name, scriptId);
635 }
636 }
637 }
638 }
639
640 world.Build();
641 worldMarkers.Build();
642
643 foreach (var sceneBuilder in objects)
644 {
645 if (sceneBuilder == null)
646 continue;
647
648 sceneBuilder.Build();
649 }
650
651 foreach (var sceneBuilder in scripts.Values)
652 {
653 sceneBuilder.Build();
654 }
655 }
656
657 private void WriteRooms()
658 {
659 rooms = new DaeSceneBuilder();
660 rooms.ImagesFolder = "grids";
661
662 for (int i = 0; i < source.Rooms.Count; i++)
663 {
664 var room = source.Rooms[i];
665
666 var meshBuilder = rooms.GetMeshBuilder(
667 string.Format(CultureInfo.InvariantCulture, "room_{0}", i));
668
669 var material = source.Materials.GetMaterial(
670 string.Format(CultureInfo.InvariantCulture, "bnv_grid_{0:d3}", i));
671
672 material.Image = room.Grid.ToImage();
673
674 foreach (var polygonPoints in room.GetFloorPolygons())
675 {
676 var texCoords = new Vector2[polygonPoints.Length];
677
678 for (int j = 0; j < polygonPoints.Length; j++)
679 {
680 var point = polygonPoints[j];
681 var min = room.BoundingBox.Min;
682 var max = room.BoundingBox.Max;
683
684 min += new Vector3(room.Grid.TileSize * room.Grid.XOrigin, 0.0f, room.Grid.TileSize * room.Grid.ZOrigin);
685 max -= new Vector3(room.Grid.TileSize * room.Grid.XOrigin, 0.0f, room.Grid.TileSize * room.Grid.ZOrigin);
686
687 var size = max - min;
688
689 float x = (point.X - min.X) / size.X;
690 float z = (point.Z - min.Z) / size.Z;
691
692 texCoords[j] = new Vector2(x, z);
693 }
694
695 meshBuilder.AddPolygon(material, polygonPoints, texCoords);
696 meshBuilder.Build();
697 }
698 }
699
700 var ghostTexCoords = new[] {
701 new Vector2(0.0f, 0.0f),
702 new Vector2(1.0f, 0.0f),
703 new Vector2(1.0f, 1.0f),
704 new Vector2(0.0f, 1.0f)
705 };
706
707 for (int i = 0; i < source.Ghosts.Count; i++)
708 {
709 var ghost = source.Ghosts[i];
710
711 var meshBuilder = rooms.GetMeshBuilder(
712 string.Format(CultureInfo.InvariantCulture, "ghost_{0}", i));
713
714 meshBuilder.AddPolygon(
715 source.Materials.Markers.Ghost,
716 ghost.Points.ToArray(),
717 ghostTexCoords);
718
719 meshBuilder.Build();
720 meshBuilder.ResetTransform();
721 }
722
723 rooms.Build();
724 }
725 }
726}
Note: See TracBrowser for help on using the repository browser.