public void Setup(WebAppWrapper webAppWrapper)
        {
            // ensure nothing is running on the port
            ProcManager.KillByPort(webAppWrapper.HttpsPort);

            // ensure the output directory to which we'll publish is empty
            DirManager.DeleteAndRecreate(webAppWrapper.OutputPath);

            // publish and run the specified webApp
            webAppWrapper.ProcWrapper = PublishAndRun(webAppWrapper);
        }
Beispiel #2
0
 public void LoadLevelData(string aionRoot, string level)
 {
     using (var levelDir = new DirManager(Path.Combine(aionRoot, "Levels", level)))
         using (var meshesDir = new DirManager(aionRoot, new[] {
             @"levels\common",
             @"objects\npc\event_object",
             @"objects\npc\level_object",
             @"objects\npc\warship",
         }))
         {
             LoadContentHelper(levelDir, meshesDir);
         }
 }
Beispiel #3
0
        public override List<string> Branches()
        {
            using (DirManager d = new DirManager(root)) {
                var branches = new List<string>();
                Program task = new Program("bzr", "branches");

                string b;
                while ((b = task.stdout.ReadLine()) != null) {
                    branches.Add(b);
                }
                return branches;
            }
        }
Beispiel #4
0
        public override List<string> Branches()
        {
            using (DirManager d = new DirManager(root)) {
                var branches = new List<string>();

                Program task = new Program("hg", "branches");

                string line;
                while ((line = task.stdout.ReadLine()) != null) {
                    branches.Add(line.Substring(0, line.IndexOf(' ')));
                }
                return branches;
            }
        }
Beispiel #5
0
        private void LoadLevels()
        {
            var levelsNode = m_treeView.Nodes.Add("Levels");

            foreach (var path in Directory.EnumerateDirectories(Path.Combine(m_clientRoot, "levels")))
            {
                using (var dir = new DirManager(path))
                {
                    if (dir.Exists("leveldata.xml"))
                    {
                        var n = levelsNode.Nodes.Add("level-viewer", Path.GetFileName(path), 5, 5);
                    }
                }
            }
            levelsNode.Expand();
        }
Beispiel #6
0
        private void LoadDoors(DirManager meshesDir, DirManager levelDir, int startTicks, List <VertexPositionColor> doorVertices,
                               List <VertexPositionColor> doorCollisionVertices, List <VertexPositionColor> lineVertices)
        {
            var doorInfos = DoorLoader.LoadDoorInfosForLevel(levelDir);

            foreach (var door in doorInfos)
            {
                using (var s = meshesDir.OpenFile(door.object_AnimatedModel))
                {
                    var cgf   = new CgfLoader(s);
                    var xform = door.GetMatrix();

                    DrawDoorCgaInWorld(cgf, startTicks, ref xform, door.Pos, doorVertices, doorCollisionVertices, lineVertices);

                    m_renderData.labels.Add(new Label3D {
                        position = door.Pos, text = door.object_AnimatedModel
                    });
                }
            }
        }
Beispiel #7
0
        public override List<string> Branches()
        {
            using (DirManager d = new DirManager(root)) {
                var branches = new List<string>();
                Program task = new Program("git", "branch -a");

                string line;
                while ((line = task.stdout.ReadLine()) != null) {
                    var b = line.TrimStart();

                    if (b.StartsWith("*")) {
                        branches.Add(b.Substring(2));
                    } else {
                        branches.Add(b);
                    }
                }

                return branches;
            }
        }
Beispiel #8
0
        private void LoadVegetation(DirManager meshesDir, DirManager levelDir, List <VertexPositionColor> vertices,
                                    List <VertexPositionColor> collisionVertices, List <VertexPositionColor> lineVertices)
        {
            var ctx = LevelLoadHelper.LoadObjectsLst(meshesDir, levelDir, "");

            if (ctx == null)
            {
                return;
            }

            foreach (var o in ctx.objects)
            {
                if (o.ObjectId >= ctx.cgfMap.Count)
                {
                    Debug.WriteLine("Warning: object with invalid id: " + o.ObjectId + " - " + o.Position);
                    continue;
                }

                var cgf   = ctx.cgfMap[o.ObjectId];
                var xform = LevelLoadHelper.GetObjectMatrix(o);

                DrawCgfInWorld(cgf, ref xform, o.Position, CgfDrawStyle.Vegetation, vertices, collisionVertices, lineVertices);
            }
        }
Beispiel #9
0
        // generates navmesh data for the selected levels.
        public static void GenerateAllNav(WorldIdXmlLoader worldIdXmlLoader,
                                          DirManager meshesDir, DirManager levelsDir, string levelId,
                                          bool skipExistingNav, string outputPath)
        {
            int curFolderIndex = -1;

            foreach (var pairs in worldIdXmlLoader.FolderNamesById)
            {
                curFolderIndex++;

                string clientLevelId = pairs.Key;
                string levelFolder   = pairs.Value;

                if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId)
                {
                    // skip excluded
                    continue;
                }

                string outputNavFilename = Path.Combine(outputPath, clientLevelId + ".nav");

                if (skipExistingNav && File.Exists(outputNavFilename))
                {
                    Console.WriteLine($"    ** Skipping (already exists): {clientLevelId} - {levelFolder}");
                    continue;
                }


                Console.WriteLine($"    Loading meshes for {clientLevelId} - {levelFolder}");

                var LEVEL_TIME = DateTime.Now;
                var TIMER      = DateTime.Now;
                var geoSpace   = new GeoSpace();

                // brushes
                var brushlst = LevelLoadHelper.CreateBrushLstLoader(levelsDir,
                                                                    Path.Combine(levelFolder, "brush.lst"));
                if (brushlst != null)
                {
                    var cgfMap = LevelLoadHelper.CreateBrushLstCgfLoaderMap(meshesDir, brushlst);

                    foreach (var brush in brushlst.brushEntries)
                    {
                        CgfLoader cgf;
                        if (!cgfMap.TryGetValue(brush.meshIdx, out cgf))
                        {
                            continue;
                        }

                        Matrix brushMatrix = LevelLoadHelper.GetBrushMatrix(brush);
                        AddCgfToWorld(cgf, ref brushMatrix, brush.position, geoSpace);
                    }
                }

                // objects
                var ctx = LevelLoadHelper.LoadObjectsLst(meshesDir, levelsDir, levelFolder);
                if (ctx != null)
                {
                    foreach (var o in ctx.objects)
                    {
                        CgfLoader cgf;
                        if (!ctx.cgfMap.TryGetValue(o.ObjectId, out cgf))
                        {
                            continue;
                        }

                        var xform = LevelLoadHelper.GetObjectMatrix(o);
                        AddCgfToWorld(cgf, ref xform, o.Position, geoSpace);
                    }
                }

                // terrain
                bool   loadedH32 = false;
                string h32path   = Path.Combine(levelFolder, @"terrain\land_map.h32");
                if (levelsDir.Exists(h32path))
                {
                    using (var landMapStream = levelsDir.OpenFile(h32path))
                        new H32Loader(landMapStream).LoadIntoGeoSpace(geoSpace);
                    loadedH32 = true;
                }

                Console.WriteLine("      Data load time: " + (DateTime.Now - TIMER));

                if (brushlst == null && ctx == null && !loadedH32)
                {
                    Console.WriteLine("      ** Skipping (no level data found)");
                    continue;
                }

                // build geo
                TIMER = DateTime.Now;
                geoSpace.BuildTree();
                geoSpace.Validate();
                Console.WriteLine("      Geo build time: " + (DateTime.Now - TIMER));

                // get size of level for scanning
                var   bb     = geoSpace.GetBoundingBox();
                float startX = Math.Max(0, bb.Min.X);
                float endX   = bb.Max.X;
                float startY = Math.Max(0, bb.Min.Y);
                float endY   = bb.Max.Y;
                int   top    = (int)Math.Ceiling(bb.Max.Z);
                int   bot    = Math.Max(0, (int)bb.Min.Z);

                // TODO - print bounding box
                // TODO - save log file

                if (endX <= startX || endY <= startY || top < bot)
                {
                    throw new InvalidOperationException(
                              $"unexpected level size for {clientLevelId} {levelFolder} bb: {bb}");
                }

                // compile mesh
                TIMER = DateTime.Now;
                float step           = 1f;
                var   navMeshBuilder = new NavMeshBuilder(startX, startY, bot, endX, endY, top, step);
                CompiledNavMeshSet compiledMeshSet = navMeshBuilder.ScanFloor(geoSpace);
                Console.WriteLine("      Raycast time: " + (DateTime.Now - TIMER));

                // save to file
                using (var fs = File.OpenWrite(outputNavFilename))
                    compiledMeshSet.Save(fs);

                Console.WriteLine($"      Level {clientLevelId} finished in {DateTime.Now - LEVEL_TIME}");
            }
        }
Beispiel #10
0
        private void LoadContentHelper(DirManager levelDir, DirManager meshesDir)
        {
            var TIMER = DateTime.Now;

            //=========

            var vertices               = new List <VertexPositionColor>();
            var vegVertices            = new List <VertexPositionColor>();
            var collisionVertices      = new List <VertexPositionColor>();
            var doorVertices1          = new List <VertexPositionColor>();
            var doorVertices2          = new List <VertexPositionColor>();
            var doorCollisionVertices1 = new List <VertexPositionColor>();
            var doorCollisionVertices2 = new List <VertexPositionColor>();
            var lineVertices           = new List <VertexPositionColor>();

            //==========
            //Window.Title = "Loading brushes...";
            LoadBrushes(meshesDir, levelDir, vertices, collisionVertices, lineVertices);

            //============
            //Window.Title = "Loading vegetation...";
            LoadVegetation(meshesDir, levelDir, vegVertices, collisionVertices, lineVertices);

            //============
            // doors have 2 states - start and end. doors can start open or closed, so the end state is the opposite.
            LoadDoors(meshesDir, levelDir, 0, doorVertices1, doorCollisionVertices1, lineVertices);
            LoadDoors(meshesDir, levelDir, 999999, doorVertices2, doorCollisionVertices2, lineVertices);

            // ===========
            // terrain
            H32Loader h32;

            using (var landMapStream = levelDir.OpenFile(@"terrain\land_map.h32"))
                h32 = new H32Loader(landMapStream);

            Debug.WriteLine("Data load TIME: " + (DateTime.Now - TIMER));



            // ============== NAVMESH TEST =====================
            var floorLineVertices =
                new List <VertexPositionColor>(); // disable navmesh

            //LoadNavMeshTestData(h32);



            //============
            m_renderData.vertexBuffer = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, vertices);

            m_renderData.vegetationVertexBuffer = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, vegVertices);

            m_renderData.collisionVertexBuffer = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, collisionVertices);

            m_renderData.doorVertexBuffer1          = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, doorVertices1);
            m_renderData.doorVertexBuffer2          = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, doorVertices2);
            m_renderData.doorCollisionVertexBuffer1 = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, doorCollisionVertices1);
            m_renderData.doorCollisionVertexBuffer2 = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, doorCollisionVertices2);

            if (lineVertices.Count > 0)
            {
                m_renderData.lineVertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), lineVertices.Count, BufferUsage.WriteOnly);
                m_renderData.lineVertexBuffer.SetData <VertexPositionColor>(lineVertices.ToArray());
            }

            if (floorLineVertices.Count > 0)
            {
                m_renderData.floorLineVertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), floorLineVertices.Count, BufferUsage.WriteOnly);
                m_renderData.floorLineVertexBuffer.SetData <VertexPositionColor>(floorLineVertices.ToArray());
            }

            vertices               = null;
            vegVertices            = null;
            collisionVertices      = null;
            doorVertices1          = null;
            doorVertices2          = null;
            doorCollisionVertices1 = null;
            doorCollisionVertices2 = null;
            lineVertices           = null;
            GC.Collect();

            //============

            //Window.Title = "Loading terrain...";
            using (var levelDataXml = levelDir.OpenFile("leveldata.xml"))
            {
                m_renderData.terrainVertexBuffer =
                    LoadH32Terrain(GraphicsDevice, h32, new LevelDataXmlLoader(levelDataXml).WaterLevel);
            }
            GC.Collect();
        }
Beispiel #11
0
        public void Process()
        {
            DateTime timer = DateTime.Now;

            if (!Directory.Exists(aionClientPath))
            {
                throw new FileNotFoundException($"Aion client installation path [{aionClientPath}] doesn't exist or not a folder path");
            }

            var meshesDir = new DirManager(aionClientPath,
                                           new[] { @"Levels\Common", @"objects\npc\event_object", @"objects\npc\level_object" });

            // Read world_maps.xml and WorldId.xml and find Levels to process
            Console.WriteLine("  Generating available levels list...");
            WorldIdXmlLoader worldIdXmlLoader = LoadWorldIdXml();

            Console.WriteLine("  Done.");

            var worldGeoBuilders = new List <WorldGeoFileBuilder>();

            var levelMeshData = new Dictionary <string, CgfMeshesToLoad>(); // key is level folder name

            Console.WriteLine("  Processing levels...");
            bool containsValidLevel = false;
            int  curFolderIndex     = -1;

            // Load levels dir... load all except common.
            var subdirs = Directory.EnumerateDirectories(Path.Combine(aionClientPath, "Levels"))
                          .Select(fulldir => Path.GetFileName(fulldir))
                          .Where(dir => !dir.Equals("common", StringComparison.InvariantCultureIgnoreCase));
            var levelsDir = new DirManager(Path.Combine(aionClientPath, "Levels"), subdirs);

            // special case to extract the login level...
            // login is like any other level, except it has no ID and isn't included in WorldId.xml.
            if (levelId == "login")
            {
                Console.WriteLine("*** Extracting login level ***");
                worldIdXmlLoader.FolderNamesById["login"] = "******";
            }

            if (generateGeo)
            {
                foreach (var pairs in worldIdXmlLoader.FolderNamesById)
                {
                    curFolderIndex++;

                    string clientLevelId = pairs.Key;
                    string levelFolder   = pairs.Value;

                    if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId)
                    {
                        // skip excluded
                        continue;
                    }

                    Console.WriteLine($"    [{clientLevelId}] - ({curFolderIndex}/{worldIdXmlLoader.FolderNamesById.Count}) - {levelFolder} ...");

                    Console.WriteLine("      Parsing leveldata.xml ...");

                    if (!levelsDir.Exists(Path.Combine(levelFolder, "leveldata.xml")))
                    {
                        Console.WriteLine("        leveldata.xml not found, skipping level.");
                        continue;
                    }

                    // Note: this list is referenced later by index.
                    List <string> vegetationCgfFilenames;
                    Point         mapSize;
                    using (var levelDataXml = levelsDir.OpenFile(Path.Combine(levelFolder, "leveldata.xml")))
                    {
                        var levelData = new LevelDataXmlLoader(levelDataXml);
                        vegetationCgfFilenames = levelData.VegetationCgfFilenames.Select(Util.NormalizeMeshFilename).ToList();
                        mapSize = levelData.MapWidthAndHeight;
                    }

                    var meshData = new CgfMeshesToLoad();

                    BrushLstLoader brushLst = null;
                    if (levelsDir.Exists(Path.Combine(levelFolder, "brush.lst")))
                    {
                        Console.WriteLine("      Parsing brush.lst ... ");
                        using (var stream = levelsDir.OpenFile(Path.Combine(levelFolder, "brush.lst")))
                            brushLst = new BrushLstLoader(stream);

                        // TODO - un-hardcode
                        if (brushLst.m_eventUsage[1])
                        {
                            Console.WriteLine("        * Supports event: X-Mas");
                        }
                        if (brushLst.m_eventUsage[2])
                        {
                            Console.WriteLine("        * Supports event: Halloween");
                        }
                        if (brushLst.m_eventUsage[3])
                        {
                            Console.WriteLine("        * Supports event: Brax Cafe");
                        }
                        if (brushLst.m_eventUsage[4])
                        {
                            Console.WriteLine("        * Supports event: Valentines");
                        }
                    }
                    else
                    {
                        Console.WriteLine("      brush.lst not found, skipping");
                    }


                    List <ObjectsLstItem> objectsLst = null;
                    if (levelsDir.Exists(Path.Combine(levelFolder, "objects.lst")))
                    {
                        Console.WriteLine("      Parsing objects.lst ... ");
                        using (var stream = levelsDir.OpenFile(Path.Combine(levelFolder, "objects.lst")))
                            objectsLst = ObjectsLstLoader.Load(stream, mapSize.X, mapSize.Y);
                    }
                    else
                    {
                        Console.WriteLine("      objects.lst not found, skipping");
                    }

                    // ------------------------------
                    meshData.meshFiles = new List <string>();

                    // brushes
                    if (brushLst != null)
                    {
                        meshData.meshFiles.AddRange(brushLst.brushInfoList.Select(o => o.filename));
                    }

                    // vegetation
                    meshData.meshFiles.AddRange(vegetationCgfFilenames);

                    // normalize names and dedupe. example entry: "levels/common/dark/natural/rocks/base/na_d_rockgngrass_05a.cgf"
                    meshData.meshFiles = meshData.meshFiles.Select(Util.NormalizeMeshFilename).Distinct().ToList();

                    meshData.meshUsage = new int[meshData.meshFiles.Count];
                    if (!noMesh)
                    {
                        levelMeshData.Add(levelFolder, meshData);
                    }

                    byte[] landMapH32 = null;

                    if (levelsDir.Exists(Path.Combine(levelFolder, @"terrain\land_map.h32")))
                    {
                        using (var stream = levelsDir.OpenFile(Path.Combine(levelFolder, @"terrain\land_map.h32")))
                        {
                            using (var ms = new MemoryStream())
                            {
                                stream.CopyTo(ms);
                                landMapH32 = ms.ToArray();
                            }
                        }
                    }

                    // keep track of all required cgfs
                    if (brushLst != null)
                    {
                        m_requiredCgfs.UnionWith(brushLst.brushInfoList.Select(o => Util.NormalizeMeshFilename(o.filename)));
                    }
                    if (objectsLst != null)
                    {
                        m_requiredCgfs.UnionWith(vegetationCgfFilenames.Select(Util.NormalizeMeshFilename));
                    }

                    // level data must be loaded first to find the required cgfs. then, cgfs must be loaded to determine if
                    // they contain collision data or not. then we can write the geo file minus non-collidable meshes.
                    var w = new WorldGeoFileBuilder(clientLevelId, landMapH32, brushLst, vegetationCgfFilenames, objectsLst);

                    // these will be processed after meshs
                    worldGeoBuilders.Add(w);

                    containsValidLevel = true;

                    Console.WriteLine("    Done.");
                }
                Console.WriteLine("  Done.");

                // --------------------------------------------------------------------------------
                if (!noMesh && containsValidLevel)
                {
                    Console.WriteLine("  Generating meshs.geo ...");

                    int    meshesSaved   = 0;
                    string meshesGeoFile = Path.Combine(outputPath, "meshs.geo");
                    using (var meshesGeoDataStream = new BinaryWriter(File.Open(meshesGeoFile, FileMode.Create)))
                    {
                        foreach (string s in m_requiredCgfs)
                        {
                            string cgfPath = PakUtil.NormalizeFilename(s);
                            if (!meshesDir.Exists(cgfPath))
                            {
                                Console.WriteLine("    Cgf not found: " + cgfPath);
                                continue;
                            }
                            using (var cgfStream = meshesDir.OpenFile(cgfPath))
                                AddToMeshesGeo(cgfPath, new CgfLoader(cgfStream), meshesGeoDataStream,
                                               1 /*collisionIntention=physical*/);
                            meshesSaved++;
                        }
                    }

                    Console.WriteLine("  Done. " + meshesSaved + "/" + m_requiredCgfs.Count + " meshes saved.");

                    // -----------------------------------
                    Console.WriteLine("  Writing world.geo files ...");
                    int wc = 0;
                    foreach (var w in worldGeoBuilders)
                    {
                        wc++;
                        Console.Write($"      Creating {w.ClientLevelId}.geo file [{wc}/{worldGeoBuilders.Count}]... ");

                        w.CreateWorldGeoFile(outputPath, noH32, m_loadedCgfs, m_emptyCgfs);
                        Console.WriteLine("   Done.");
                    }
                    Console.WriteLine("  Done.");
                }

                // ------------------------------------

                /*VERBOSE Console.WriteLine("  Check meshes that were not found ...");
                 *          foreach (var levelMeshes in levelMeshData)
                 *          {
                 *                  Console.WriteLine("    " + levelMeshes.Key);
                 *                  CgfMeshesToLoad brushLstMeshData = levelMeshes.Value;
                 *                  for (int i = 0; i < brushLstMeshData.meshFiles.Count; i++)
                 *                  {
                 *                          if (brushLstMeshData.meshUsage[i] == 0 && !m_emptyCgfs.Contains(Util.NormalizeMeshFilename(brushLstMeshData.meshFiles[i])))
                 *                          {
                 *                                  Console.WriteLine("      " + brushLstMeshData.meshFiles[i]);
                 *                          }
                 *                  }
                 *          }*/
                Console.WriteLine("  Done.");
            }

            // --------------------------------------------------------------------------------
            if (generateDoors)
            {
                Console.WriteLine("Generating door mesh data...");

                // Writes 2 files:
                // - door data, pairing level+entityid to position
                // - door mesh file, containing start and end variations of each door model.

                // Original AL stores models in data\geo\model\static_doors as .cga files.
                // These are not actually .cga files, but instead are the same format as .geo files.
                // These meshes only contained a single state.
                //
                // This following code generates door data in a new format:
                // - Each door model is loaded twice, in the start and end positions.
                // - Each model has _start or _end appended to the mesh name.
                // - All unique door meshes are stored in doors.geo, following the same format as
                //   meshes.geo. Meshes should be identified by original name + _start or _end.
                // - All door instances are stored in doors.dat and define world placement.
                //   static_doors.xml is still necessary as it provides unique info such as key item id.

                var doorModelNames = new HashSet <string>();

                using (var doorsDat = new BinaryWriter(File.Open(Path.Combine(outputPath, "doors.dat"), FileMode.Create)))
                {
                    doorsDat.Write(0x524F4F44);

                    // collect unique doors
                    foreach (var pairs in worldIdXmlLoader.FolderNamesById)
                    {
                        curFolderIndex++;

                        string clientLevelId = pairs.Key;
                        string levelFolder   = pairs.Value;

                        if (!string.IsNullOrEmpty(levelId) && levelId != clientLevelId)
                        {
                            // skip excluded
                            continue;
                        }

                        Console.WriteLine($"    [{clientLevelId}] - ({curFolderIndex}/{worldIdXmlLoader.FolderNamesById.Count}) - {levelFolder} ...");

                        List <DoorInfo> doorInfos;
                        try
                        {
                            doorInfos = DoorLoader.LoadDoorInfosForLevel(levelsDir, levelFolder);
                        }
                        catch
                        {
                            Console.WriteLine("        Level folder not found.");
                            continue;
                        }

                        foreach (var door in doorInfos)
                        {
                            string meshName = PakUtil.NormalizeFilename(door.object_AnimatedModel);
                            doorModelNames.Add(meshName);

                            doorsDat.Write((short)meshName.Length);
                            byte[] meshFileNameBytes = Encoding.ASCII.GetBytes(meshName);
                            doorsDat.Write(meshFileNameBytes);
                            doorsDat.Write(int.Parse(clientLevelId));
                            doorsDat.Write(door.EntityId);
                            doorsDat.Write(door.Pos.X);
                            doorsDat.Write(door.Pos.Y);
                            doorsDat.Write(door.Pos.Z);
                            // TODO investigate dir / use_dir. value correlates to z angle...
                            doorsDat.Write(door.Angles.X);
                            doorsDat.Write(door.Angles.Y);
                            doorsDat.Write(door.Angles.Z);
                        }
                    }
                }

                m_loadedCgfs = new HashSet <string>();
                m_emptyCgfs  = new HashSet <string>();

                // generate door meshes.
                // a door model may be referenced many times, so models are saved separately from instances.
                int    meshesSaved  = 0;
                string doorsGeoFile = Path.Combine(outputPath, "doors.geo");
                using (var doorMeshesGeoDataStream = new BinaryWriter(File.Open(doorsGeoFile, FileMode.Create)))
                {
                    foreach (var cgfPath in doorModelNames.OrderBy(o => o))
                    {
                        if (!meshesDir.Exists(cgfPath))
                        {
                            Console.WriteLine("    Door Cgf/Cga not found: " + cgfPath);
                            continue;
                        }

                        // TODO - parameterize m_loadedCgfs and m_emptyCgfs...
                        using (var cgfStream = meshesDir.OpenFile(cgfPath))
                        {
                            var cgfFirstState  = new CgfLoader(cgfStream);
                            var cgfSecondState = cgfFirstState.CloneAtTime(999999);

                            byte collisionIntention = 1 + (1 << 4); // physical + door
                            AddToMeshesGeo(cgfPath + "_start", cgfFirstState, doorMeshesGeoDataStream, collisionIntention);
                            AddToMeshesGeo(cgfPath + "_end", cgfSecondState, doorMeshesGeoDataStream, collisionIntention);
                        }
                        meshesSaved++;
                    }
                }

                // doors should have collision data, otherwise they won't be very effective.
                foreach (var empty in m_emptyCgfs)
                {
                    Console.WriteLine("Warning: door has no collision data: " + empty);
                }
                Console.WriteLine("Door meshes: " + meshesSaved);
            }

            // --------------------------------------------------------------------------------
            if (generateNav)
            {
                Console.WriteLine("  Generating NavMesh .nav data...");
                var start = DateTime.Now;
                NavMeshProcessor.GenerateAllNav(worldIdXmlLoader, meshesDir, levelsDir,
                                                levelId, skipExistingNav, outputPath);
                Console.WriteLine("    NavMesh processing time: " + (DateTime.Now - start));
            }

            TimeSpan timerEnd = DateTime.Now - timer;

            Console.WriteLine("  Processing time: " + timerEnd);
        }