using System; using System.Collections.Generic; using System.IO; using System.Xml; using Oni.Xml; namespace Oni.Level { using Akira; using Motoko; using Objects; using Physics; partial class LevelImporter { private List objects; private ObjectLoadContext objectLoadContext; private void ReadObjects(XmlReader xml, string basePath) { info.WriteLine("Reading objects..."); objects = new List(); objectLoadContext = new ObjectLoadContext(FindSharedInstance, info); xml.ReadStartElement("Objects"); while (xml.IsStartElement()) ReadObjectFile(Path.Combine(basePath, xml.ReadElementContentAsString("Import", ""))); xml.ReadEndElement(); } private void ReadObjectFile(string filePath) { var basePath = Path.GetDirectoryName(filePath); objectLoadContext.BasePath = basePath; objectLoadContext.FilePath = filePath; var settings = new XmlReaderSettings { IgnoreWhitespace = true, IgnoreProcessingInstructions = true, IgnoreComments = true }; using (var xml = XmlReader.Create(filePath, settings)) { xml.ReadStartElement("Oni"); switch (xml.LocalName) { case "Objects": objects.AddRange(ReadObjects(xml)); break; case "Particles": level.particles.AddRange(ReadParticles(xml, basePath)); break; case "Characters": level.characters.AddRange(ReadCharacters(xml, basePath)); break; case "Physics": ReadPhysics(xml, basePath); break; case "Corpses": case "CRSA": level.corpses.AddRange(ReadCorpses(xml, basePath)); break; default: error.WriteLine("Unknown object file type {0}", xml.LocalName); xml.Skip(); break; } xml.ReadEndElement(); } } private IEnumerable ReadObjects(XmlReader xml) { if (xml.SkipEmpty()) yield break; xml.ReadStartElement(); while (xml.IsStartElement()) { ObjectBase obj; switch (xml.LocalName) { case "CHAR": case "Character": obj = new Character(); break; case "WEAP": case "Weapon": obj = new Weapon(); break; case "PART": case "Particle": obj = new Particle(); break; case "PWRU": case "PowerUp": obj = new PowerUp(); break; case "FLAG": case "Flag": obj = new Flag(); break; case "DOOR": case "Door": obj = new Door(); break; case "CONS": case "Console": obj = new Console(); break; case "FURN": case "Furniture": obj = new Furniture(); break; case "TURR": case "Turret": obj = new Turret(); break; case "SNDG": case "Sound": obj = new Sound(); break; case "TRIG": case "Trigger": obj = new Trigger(); break; case "TRGV": case "TriggerVolume": obj = new TriggerVolume(); break; case "NEUT": case "Neutral": obj = new Neutral(); break; case "PATR": case "Patrol": obj = new PatrolPath(); break; default: error.WriteLine("Unknonw object type {0}", xml.LocalName); xml.Skip(); continue; } obj.Read(xml, objectLoadContext); var gunkObject = obj as GunkObject; if (gunkObject != null && gunkObject.GunkClass == null) continue; yield return obj; } xml.ReadEndElement(); } private void ImportGunkObjects() { int nextObjectId = 1; foreach (var obj in objects) { obj.ObjectId = nextObjectId++; if (obj is Door) ImportDoor((Door)obj); else if (obj is Furniture) ImportFurniture((Furniture)obj); else if (obj is GunkObject) ImportGunkObject((GunkObject)obj, GunkFlags.NoOcclusion); } } private void ImportFurniture(Furniture furniture) { ImportGunkObject(furniture, GunkFlags.NoOcclusion | GunkFlags.Furniture); foreach (var particle in furniture.Class.Geometry.Particles) ImportParticle(furniture.ParticleTag, furniture.Transform, particle); } private void ImportGunkObject(GunkObject gunkObject, GunkFlags flags) { foreach (var node in gunkObject.GunkClass.GunkNodes) ImportGunkNode(gunkObject.GunkId, gunkObject.Transform, node.Flags | flags, node.Geometry); } private void ImportDoor(Door door) { var placement = door.Transform; float minY = 0.0f; float minX = 0.0f; float maxX = 0.0f; var geometryTransform = Matrix.CreateScale(door.Class.Animation.Keys[0].Scale) * Matrix.CreateRotationX(-MathHelper.HalfPi); foreach (var node in door.Class.GunkNodes) { var bbox = BoundingBox.CreateFromPoints(Vector3.Transform(node.Geometry.Points, ref geometryTransform)); minY = Math.Min(minY, bbox.Min.Y); minX = Math.Min(minX, bbox.Min.X); maxX = Math.Max(maxX, bbox.Max.X); } placement.Translation -= Vector3.UnitY * minY; float xOffset; int sides; if ((door.Flags & DoorFlags.DoubleDoor) == 0) { xOffset = 0.0f; sides = 1; } else { xOffset = (maxX - minX) / 2.0f; sides = 2; } for (int side = 0; side < sides; side++) { Matrix origin, gunkTransform; if (side == 0) { var m1 = Matrix.CreateTranslation(xOffset, 0.0f, 0.0f) * placement; origin = geometryTransform * m1; gunkTransform = origin; } else { var m2 = Matrix.CreateTranslation(-xOffset, 0.0f, 0.0f) * placement; origin = Matrix.CreateRotationY(MathHelper.Pi) * geometryTransform * m2; gunkTransform = geometryTransform * Matrix.CreateRotationY(MathHelper.Pi) * m2; } var scriptId = door.ScriptId | (side << 12); var geometries = ImportDoorGeometry(door, side); level.physics.Add(new ObjectSetup { Name = string.Format("door_{0}", scriptId), Flags = ObjectSetupFlags.FaceCollision, DoorScriptId = scriptId, Origin = origin, Geometries = geometries }); foreach (var geometry in geometries) ImportGunkNode(door.GunkId, gunkTransform, GunkFlags.NoDecals | GunkFlags.NoCollision, geometry); } } private Geometry[] ImportDoorGeometry(Door door, int side) { InstanceDescriptor overrideTexture = null; if (!string.IsNullOrEmpty(door.Textures[side])) overrideTexture = FindSharedInstance(TemplateTag.TXMP, door.Textures[side]); var nodes = door.Class.Geometry.Geometries; var geometries = new Geometry[nodes.Length]; for (int i = 0; i < nodes.Length; i++) { var node = nodes[i]; var geometry = new Geometry { Points = node.Geometry.Points, TexCoords = node.Geometry.TexCoords, Normals = node.Geometry.Normals, Triangles = node.Geometry.Triangles }; if (overrideTexture != null) { geometry.Texture = overrideTexture; geometry.TextureName = overrideTexture.Name; } else if (node.Geometry.Texture != null) { geometry.TextureName = node.Geometry.Texture.Name; } geometries[i] = geometry; } return geometries; } private void WriteObjects() { ObjcDatWriter.Write(objects, outputDirPath); } private IEnumerable ReadCorpses(XmlReader xml, string basePath) { var fileName = Path.GetFileName(objectLoadContext.FilePath); var isOldFormat = xml.IsStartElement("CRSA"); int readCount = 0, fixedCount = 0, usedCount = 0; if (isOldFormat) { xml.ReadStartElement("CRSA"); if (xml.IsStartElement("FixedCount")) fixedCount = xml.ReadElementContentAsInt("FixedCount", ""); if (xml.IsStartElement("UsedCount")) usedCount = xml.ReadElementContentAsInt("UsedCount", ""); if (usedCount < fixedCount) { error.WriteLine("There are more fixed corpses ({0}) than used corpses ({1}) - assuming fixed = used", fixedCount, usedCount); fixedCount = usedCount; } } xml.ReadStartElement("Corpses"); while (xml.IsStartElement()) { var corpse = new Corpse(); corpse.IsFixed = isOldFormat && readCount < fixedCount; corpse.IsUsed = !isOldFormat || readCount < usedCount; corpse.FileName = fileName; if (xml.IsEmptyElement) { corpse.IsUsed = false; } if (!corpse.IsUsed) { xml.Skip(); } else if (xml.LocalName == "Corpse" || xml.LocalName == "CRSACorpse") { xml.ReadStartElement(); if (!isOldFormat) { if (xml.IsStartElement("CanDelete")) { corpse.IsFixed = false; xml.Skip(); } else { corpse.IsFixed = true; } } if (xml.IsStartElement("Class") || xml.IsStartElement("CharacterClass")) corpse.CharacterClass = xml.ReadElementContentAsString(); if (string.IsNullOrEmpty(corpse.CharacterClass)) { corpse.IsUsed = false; corpse.IsFixed = false; } xml.ReadStartElement("Transforms"); for (int j = 0; j < corpse.Transforms.Length; j++) { if (xml.IsStartElement("Matrix4x3")) corpse.Transforms[j] = xml.ReadElementContentAsMatrix43("Matrix4x3"); else if (xml.IsStartElement("Matrix")) corpse.Transforms[j] = xml.ReadElementContentAsMatrix43("Matrix"); } xml.ReadEndElement(); if (xml.IsStartElement("BoundingBox")) { xml.ReadStartElement("BoundingBox"); corpse.BoundingBox.Min = xml.ReadElementContentAsVector3("Min"); corpse.BoundingBox.Max = xml.ReadElementContentAsVector3("Max"); xml.ReadEndElement(); } else { corpse.BoundingBox.Min = corpse.Transforms[0].Translation; corpse.BoundingBox.Max = corpse.Transforms[0].Translation; corpse.BoundingBox.Inflate(new Vector3(10.0f, 5.0f, 10.0f)); } xml.ReadEndElement(); } else { var filePath = xml.ReadElementContentAsString("Import", ""); filePath = Path.Combine(basePath, filePath); using (var reader = new BinaryReader(filePath)) { corpse.FileName = Path.GetFileName(filePath); corpse.IsUsed = true; corpse.IsFixed = true; corpse.CharacterClass = reader.ReadString(128); reader.Skip(4); for (int i = 0; i < corpse.Transforms.Length; i++) corpse.Transforms[i] = reader.ReadMatrix4x3(); corpse.BoundingBox = reader.ReadBoundingBox(); } } readCount++; yield return corpse; } if (readCount < usedCount) { error.WriteLine("{0} corpses were expected but only {1} have been read", usedCount, readCount); } info.WriteLine("Read {0} corpses", readCount); } private InstanceFileManager fileManager; private InstanceDescriptor FindSharedInstance(TemplateTag tag, string name, ObjectLoadContext loadContext) { if (!name.EndsWith(".oni", StringComparison.OrdinalIgnoreCase)) return FindSharedInstance(tag, name); string filePath = Path.GetFullPath(Path.Combine(loadContext.BasePath, name)); if (File.Exists(filePath)) { if (fileManager == null) fileManager = new InstanceFileManager(); return fileManager.OpenFile(filePath).Descriptors[0]; } filePath = Path.GetFullPath(Path.Combine(sharedPath, name)); if (File.Exists(filePath)) { var file = sharedManager.OpenFile(filePath); return file.Descriptors[0]; } error.WriteLine("Could not find {0}", name); return null; } } }