private void LoadMeshFromCgfCallback(NodeData node, Matrix m, CgfLoader cgf, List <MeshData> meshesPack) { if (node.Mesh.vertices.Length <= 0 || node.Mesh.indices.Length <= 0) { //VERBOSE Console.WriteLine(" Skipping (empty): " + node.Mesh.vertices.Length + "/" + node.Mesh.indices.Length); } /*else if ((node.Mesh.vertices.Length & 0xffff0000) != 0 || ((node.Mesh.indices.Length * 3) & 0xffff0000) != 0) || { || Console.WriteLine("*** ERROR: Count of elements is bigger than MAX_SHORT"); || Console.WriteLine(" Skipping: " + node.Mesh.vertices.Length + "/" + node.Mesh.indices.Length * 3 + " [" + node.Mesh.indices.Length + " * 3]"); + }*/ else if (!cgf.IsNodeCollidable(node)) { //VERBOSE Console.WriteLine(" Skipping (not collidable): " + node.Mesh.vertices.Length + "/" + node.Mesh.indices.Length); } else { // transform the node into object coordinates. indices stay the same. var xformMesh = new MeshData(); xformMesh.vertices = new Vector3[node.Mesh.vertices.Length]; Vector3.Transform(node.Mesh.vertices, ref m, xformMesh.vertices); xformMesh.indices = node.Mesh.indices; meshesPack.Add(xformMesh); } }
private void DrawDoorCgaInWorld(CgfLoader cga, int startTicks, ref Matrix xform, Vector3 position, List <VertexPositionColor> vertices, List <VertexPositionColor> collisionVertices, List <VertexPositionColor> lineVertices) { // Collect collidable meshes int collisionStart = collisionVertices.Count; var vertStartCount = vertices.Count; DOOR_HACK = true;//draw numbers on nodes cga.CloneAtTime(startTicks).TraverseNodes((node, transform) => NodeHandler(cga, node, position, transform, vertices, collisionVertices, lineVertices, CgfDrawStyle.Door2), ref xform); DOOR_HACK = false; var bboxTemp = new List <Vector3>(); for (int i = collisionStart; i < collisionVertices.Count; i++) { bboxTemp.Add(collisionVertices[i].Position); } for (int i = vertStartCount; i < vertices.Count; i++) { bboxTemp.Add(vertices[i].Position); } Util.DrawBoundingBox(Util.GetBoundingBox(bboxTemp), lineVertices, new Color(25, 66, 250)); }
private void AddToMeshesGeo(string cgfPath, CgfLoader cgf, BinaryWriter meshesGeoDataStream, byte collisionIntention) { Matrix identity = Matrix.Identity; // process each unique filename only once. if (m_loadedCgfs.Contains(cgfPath)) { throw new InvalidOperationException("input should have been deduped already"); } m_loadedCgfs.Add(cgfPath); try { // collect not empty, collidable meshes. var meshesPack = new List <MeshData>(); cgf.TraverseNodes((node, m) => LoadMeshFromCgfCallback(node, m, cgf, meshesPack), ref identity); if (meshesPack.Count > 0) { WriteToMeshsGeo(cgfPath, meshesPack, meshesGeoDataStream, collisionIntention); } else { m_emptyCgfs.Add(cgfPath); } } catch (Exception e) { Console.WriteLine("*** ERROR: Cannot process mesh: " + e); } }
private static void AddCgfToWorld(CgfLoader cgf, ref Matrix xform, Vector3 position, GeoSpace geoSpace) { s_tempVertices.Clear(); // traverse nodes cgf.TraverseNodes((node, transform) => NodeHandler(cgf, node, position, transform), ref xform); // Add collected vertices as a new mesh. geoSpace.AddCollidableMeshToTree(s_tempVertices); }
private void DrawCgfInWorld(CgfLoader cgf, ref Matrix xform, Vector3 position, CgfDrawStyle style, List <VertexPositionColor> vertices, List <VertexPositionColor> collisionVertices, List <VertexPositionColor> lineVertices) { // Collect collidable meshes int collisionStart = collisionVertices.Count; // traverse nodes cgf.TraverseNodes((node, transform) => NodeHandler(cgf, node, position, transform, vertices, collisionVertices, lineVertices, style), ref xform); // Add collected vertices as a new mesh. m_geoSpace.AddCollidableMeshToTree(collisionVertices, collisionStart, collisionVertices.Count - collisionStart); }
private void ShowCgfViewer(CgfLoader cgf) { HideLevelViewer(); HideImageViewer(); HideTextViewer(); m_vc = new AionLevelViewerControl(null, null, cgf); m_vc.Location = new Point(m_treeView.Width, m_menuStrip.Bottom); var clientHeight = ClientSize.Height - m_menuStrip.Height; m_vc.Size = new Size(ClientSize.Width - m_treeView.Width, clientHeight); m_vc.OnUpdateTitle += OnLevelViewerControlTitleUpdated; Controls.Add(m_vc); m_vc.Focus(); Invalidate(); }
private void NodeHandler(CgfLoader cgf, NodeData node, Vector3 worldPosition, Matrix transform, List <VertexPositionColor> vertices, List <VertexPositionColor> collisionVertices, List <VertexPositionColor> lineVertices, CgfDrawStyle style) { var hackackack = true; //Debug.WriteLine($"NODE {transform.Translation} === {node.chunkId}"); bool isNodeCollidable = cgf.IsNodeCollidable(node); foreach (var vi in node.Mesh.indices) { var v0 = Vector3.Transform(node.Mesh.vertices[vi.v0], transform); var v1 = Vector3.Transform(node.Mesh.vertices[vi.v1], transform); var v2 = Vector3.Transform(node.Mesh.vertices[vi.v2], transform); if (hackackack && DOOR_HACK) { m_renderData.labels.Add(new Label3D { position = v0, text = node.objectId.ToString() }); hackackack = false; } Color color = NodeColorizer(style, isNodeCollidable, vi.v0); var dest = isNodeCollidable ? collisionVertices : vertices; dest.Add(new VertexPositionColor(v1, color)); dest.Add(new VertexPositionColor(v0, color)); dest.Add(new VertexPositionColor(v2, color)); } var zero = Vector3.Transform(Vector3.Zero, transform); lineVertices.Add(new VertexPositionColor(zero, Color.Blue)); lineVertices.Add(new VertexPositionColor(Vector3.Transform(Vector3.UnitX, transform), Color.Blue)); lineVertices.Add(new VertexPositionColor(zero, Color.Red)); lineVertices.Add(new VertexPositionColor(Vector3.Transform(Vector3.UnitY, transform), Color.Red)); lineVertices.Add(new VertexPositionColor(zero, Color.Yellow)); lineVertices.Add(new VertexPositionColor(Vector3.Transform(Vector3.UnitZ, transform), Color.Yellow)); lineVertices.Add(new VertexPositionColor(worldPosition, Color.Cyan)); lineVertices.Add(new VertexPositionColor(zero, Color.Cyan)); }
private static void NodeHandler(CgfLoader cgf, NodeData node, Vector3 worldPosition, Matrix transform) { if (!cgf.IsNodeCollidable(node)) { return; } foreach (var vi in node.Mesh.indices) { var v0 = Vector3.Transform(node.Mesh.vertices[vi.v0], transform); var v1 = Vector3.Transform(node.Mesh.vertices[vi.v1], transform); var v2 = Vector3.Transform(node.Mesh.vertices[vi.v2], transform); s_tempVertices.Add(v1); s_tempVertices.Add(v0); s_tempVertices.Add(v2); } }
public void LoadCgfDoorHack(CgfLoader cgf, int ticks) { var vertices = new List <VertexPositionColor>(); var collisionVertices = new List <VertexPositionColor>(); var lineVertices = new List <VertexPositionColor>(); var xform = Matrix.Identity; DrawDoorCgaInWorld(cgf, ticks, ref xform, Vector3.Zero, vertices, collisionVertices, lineVertices); m_renderData.vertexBuffer = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, vertices); m_renderData.collisionVertexBuffer = VertexBufferUtil.CreateLargeVertexBuffer(GraphicsDevice, collisionVertices); if (lineVertices.Count > 0) { m_renderData.lineVertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), lineVertices.Count, BufferUsage.WriteOnly); m_renderData.lineVertexBuffer.SetData <VertexPositionColor>(lineVertices.ToArray()); } }
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 }); } } }
public static AionLevelViewerImpl CreateCgfViewer(GraphicsDevice GraphicsDevice, IServiceProvider serviceProvider, CgfLoader cgf) { var result = new AionLevelViewerImpl(GraphicsDevice, serviceProvider); result.camera.StepSpeed = 1; result.camera.UpDownSpeed = 1; result.m_contentLoader.LoadCgf(cgf); //result.doorHackCgf = cgf; return(result); }
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); }
// TODO configure better... load level OR cgf, not both public AionLevelViewerControl(string aionClientRoot, string levelFolder, CgfLoader cgf) { m_aionClientRoot = aionClientRoot; m_levelFolder = levelFolder; m_cgf = cgf; }