private byte[] brushToByteArray(MAPBrush inData, int num) { if (inData.Patch != null) { return(patchToByteArray(inData.Patch, num)); } if (inData.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inData.NumSides + " sides!"); return(new byte[0]); } string brush = "// brush " + num + (char)0x0D + (char)0x0A + "{" + (char)0x0D + (char)0x0A; for (int i = 0; i < inData.NumSides; i++) { brush += (brushSideToString(inData[i], (inData.Detail || inData[0].Displacement != null)) + (char)0x0D + (char)0x0A); } brush += ("}" + (char)0x0D + (char)0x0A); if (brush.Length < 45) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return(new byte[0]); } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte)brush[i]; } return(brushbytes); } }
// CONSTRUCTORS // This constructor sets everything according to specified settings. public BSP42Decompiler(BSP BSPObject, int jobnum, DecompilerThread parent) { // Set up global variables this.BSPObject = BSPObject; this.jobnum = jobnum; this.parent = parent; }
private byte[] brushToByteArray(MAPBrush inBrush, int num) { if (inBrush.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inBrush.NumSides + " sides!"); return(new byte[0]); } string brush = "// primitive " + num + (char)0x0A + "{" + (char)0x0A + " brushDef3" + (char)0x0A + " {" + (char)0x0A; for (int i = 0; i < inBrush.NumSides; i++) { brush += (" " + brushSideToString(inBrush[i]) + (char)0x0A); } brush += (" }" + (char)0x0A + "}" + (char)0x0A); if (brush.Length < 58) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return(new byte[0]); } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte)brush[i]; } return(brushbytes); } }
public Job(int id, string map, DecompilerThread runnable) { this.id = id; this.map = map; this.runnable = runnable; runnable.reportProgress += new ProgressEventHandler(updateProgress); }
// +getLeavesInModel(int) // Returns an array of Leaf containing all the leaves referenced from // this model's head node. This array cannot be referenced by index numbers // from other lumps, but if simply iterating through, getting information // it'll be just fine. public virtual Leaf[] getLeavesInModel(int model) { int head = models[model].HeadNode; if (head < 0) { //head = Math.Abs(head); DecompilerThread.OnMessage(this, "WARNING: Model " + model + " links to a negative node!"); } return(getLeavesInNode(head)); }
// METHODS // renameAttribute(String, String) // Renames the specified attribute to the second String. public virtual void renameAttribute(string attribute, string to) { if (attributes.ContainsKey(attribute)) { string val = attributes[attribute]; attributes.Remove(attribute); if (attributes.ContainsKey(to)) { attributes.Remove(to); DecompilerThread.OnMessage(this, "WARNING: Attribute " + to + " already existed in entity, overwritten!"); } attributes.Add(to, val); } }
private void SetOriginSize_Click(object sender, RoutedEventArgs e) { try { double input = Double.Parse(Microsoft.VisualBasic.Interaction.InputBox("Please enter a new origin brush size.\nCurrent value: " + Settings.originBrushSize, "Enter new origin brush size", Settings.originBrushSize.ToString(), -1, -1)); if (input <= 0) { throw new Exception("f**k"); } Settings.precision = input; } catch { DecompilerThread.OnMessage(this, "Invalid origin brush size! Please enter a positive number."); } }
private void NumThreads_Click(object sender, RoutedEventArgs e) { try { int input = Int32.Parse(Microsoft.VisualBasic.Interaction.InputBox("Please enter number of concurrent decompiles allowed.\nCurrent value: " + Settings.numThreads, "Enter new thread amount", Settings.numThreads.ToString(), -1, -1)); if (input < 1) { throw new Exception("f**k"); } Settings.numThreads = input; } catch { DecompilerThread.OnMessage(this, "Please enter a whole number greater than 0!"); } }
private void Ppts_Click(object sender, RoutedEventArgs e) { try { double input = Double.Parse(Microsoft.VisualBasic.Interaction.InputBox("Please enter plane point coefficient.\nCurrent Value: " + Settings.planePointCoef, "Enter new coefficient", Settings.planePointCoef.ToString(), -1, -1)); if (input == 0) { throw new Exception("f**k"); } Settings.planePointCoef = input; } catch { DecompilerThread.OnMessage(this, "Invalid plane point coefficient! Please enter a nonzero number!"); } }
private void SetEpsilon_Click(object sender, RoutedEventArgs e) { try { double input = Double.Parse(Microsoft.VisualBasic.Interaction.InputBox("Please enter a new error tolerance value.\n" + "This value is used to compensate for error propagation in double precision calculations.\n" + "Typical values are between 0.0001 and 0.5. Current value: " + Settings.precision, "Enter new error tolerance", Settings.precision.ToString(), -1, -1)); if (input < 0) { throw new Exception("f**k"); } Settings.precision = input; } catch { DecompilerThread.OnMessage(this, "Invalid error tolerance! Please enter a positive number or 0."); } }
// METHODS // toString() // Returns the brush side exactly as it would look in a .MAP file. // This is on multiple lines simply for readability. the returned // String will have no line breaks. This isn't used anymore for // file output, this would be slower. public override string ToString() { try { return("( " + triangle[0].X + " " + triangle[0].Y + " " + triangle[0].Z + " ) " + "( " + triangle[1].X + " " + triangle[1].Y + " " + triangle[1].Z + " ) " + "( " + triangle[2].X + " " + triangle[2].Y + " " + triangle[2].Z + " ) " + texture + " [ " + textureS.X + " " + textureS.Y + " " + textureS.Z + " " + textureShiftS + " ]" + " [ " + textureT.X + " " + textureT.Y + " " + textureT.Z + " " + textureShiftT + " ] " + texRot + " " + texScaleX + " " + texScaleY + " " + flags + " " + material + " [ " + lgtScale + " " + lgtRot + " ]"); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Side with bad data! Not exported!"); return(null); } }
private void SetMMSS_Click(object sender, RoutedEventArgs e) { try { int input = Int32.Parse(Microsoft.VisualBasic.Interaction.InputBox("Please enter a new multimanager stack size.\n" + "When converting other entity systems to Source Engine entity I/O, you must recurse through\n" + "multi_managers. If they reference each other in a cycle, it will loop forever. The stack prevents\n" + "this from happening. Increase this to recurse further. Current value: " + Settings.mmStack, "Enter new stack size", Settings.mmStack.ToString(), -1, -1)); if (input < 0) { throw new Exception("f**k"); } Settings.mmStack = input; } catch { DecompilerThread.OnMessage(this, "Invalid stack size! Please enter a whole positive number or 0."); } }
// METHODS private void StartNextIfAble() { if (jobQueue.Count > 0) { if (active.Count < Settings.numThreads) { DecompilerThread next = jobQueue.Dequeue(); active.Add(next, new Thread(next.Run)); active[next].Start(); StartNextIfAble(); // Recursively call this until either the queue is empty or the active jobs is full } } else { System.GC.Collect(); // No jobs currently processing; use as little memory as possible ;) } }
private void SaveLog_Click(object sender, RoutedEventArgs e) { try { SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "Text file|*.txt"; if (dialog.ShowDialog() == true) { FileStream stream = new FileStream(dialog.FileName, FileMode.Create, FileAccess.Write); BinaryWriter bw = new BinaryWriter(stream); stream.Seek(0, SeekOrigin.Begin); bw.Write(txtConsole.Text); bw.Close(); } } catch { DecompilerThread.OnMessage(this, "Unable to write file! Make sure the file is not read-only and that you have access to it."); } }
private void OutFolder_Click(object sender, RoutedEventArgs e) { try { System.Windows.Forms.FolderBrowserDialog dialog = new System.Windows.Forms.FolderBrowserDialog(); if (dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) { Settings.outputFolder = dialog.SelectedPath + "\\"; DecompilerThread.OnMessage(this, Settings.outputFolder); } else { Settings.outputFolder = "default"; } } catch { Settings.outputFolder = "default"; } }
private void FileOpen_Click(Object sender, RoutedEventArgs e) { OpenFileDialog fileOpener = new OpenFileDialog(); fileOpener.Filter = "All Supported Files|*.bsp;*.d3dbsp;*.wad|BSP Files|*.bsp;*.d3dbsp|WAD Files|*.wad|All Files|*.*"; fileOpener.Multiselect = true; // Process open file dialog box results if (fileOpener.ShowDialog() == true) { string[] filesToOpen = fileOpener.FileNames; for(int i=0;i<filesToOpen.Length;i++) { DecompilerThread thread = new DecompilerThread(new FileInfo(filesToOpen[i]), finished + active.Count + jobQueue.Count, Settings.openAs); Job theJob = new Job(finished + active.Count + jobQueue.Count, filesToOpen[i], thread); theJob.PropertyChanged += new PropertyChangedEventHandler(UpdateTaskbar); thread.error += new ErrorHandler(Error); jobQueue.Enqueue(thread); jobs.Add(theJob); } StartNextIfAble(); } }
// If only one thread is allowed to use new Object() method at once, only one map will be saved at once, meaning less // jumping hard drive seek time used. public static void write(byte[] data, string destinationString, bool toVMF) { try { if (!destinationString.Substring(destinationString.Length - 4).ToUpper().Equals(".map".ToUpper()) && !destinationString.Substring(destinationString.Length - 4).ToUpper().Equals(".vmf".ToUpper())) { if (toVMF) { destinationString = destinationString + ".vmf"; } else { destinationString = destinationString + ".map"; } } } catch (System.ArgumentOutOfRangeException) { if (toVMF) { destinationString = destinationString + ".vmf"; } else { destinationString = destinationString + ".map"; } } DecompilerThread.OnMessage(new Object(), "Saving " + destinationString + "..."); try { FileStream stream = new FileStream(destinationString, FileMode.Create, FileAccess.Write); BinaryWriter bw = new BinaryWriter(stream); stream.Seek(0, SeekOrigin.Begin); bw.Write(data); bw.Close(); } catch (System.IO.IOException e) { DecompilerThread.OnMessage(new Object(), "ERROR: Could not save " + destinationString + ", ensure the file is not open in another program."); throw e; } }
private void FileOpen_Click(Object sender, RoutedEventArgs e) { OpenFileDialog fileOpener = new OpenFileDialog(); fileOpener.Filter = "All Supported Files|*.bsp;*.d3dbsp;*.wad|BSP Files|*.bsp;*.d3dbsp|WAD Files|*.wad|All Files|*.*"; fileOpener.Multiselect = true; // Process open file dialog box results if (fileOpener.ShowDialog() == true) { string[] filesToOpen = fileOpener.FileNames; for (int i = 0; i < filesToOpen.Length; i++) { DecompilerThread thread = new DecompilerThread(new FileInfo(filesToOpen[i]), finished + active.Count + jobQueue.Count, Settings.openAs); Job theJob = new Job(finished + active.Count + jobQueue.Count, filesToOpen[i], thread); theJob.PropertyChanged += new PropertyChangedEventHandler(UpdateTaskbar); thread.error += new ErrorHandler(Error); jobQueue.Enqueue(thread); jobs.Add(theJob); } StartNextIfAble(); } }
public virtual void setSide(Plane plane, Vector3D[] triangle) { if (triangle.Length >= 3) { if (triangle[0] == null) { DecompilerThread.OnMessage(this, "WARNING: Tried to set triangle but point 0 was null!"); } else { if (triangle[1] == null) { DecompilerThread.OnMessage(this, "WARNING: Tried to set triangle but point 1 was null!"); } else { if (triangle[2] == null) { DecompilerThread.OnMessage(this, "WARNING: Tried to set triangle but point 2 was null!"); } else { this.triangle[0] = triangle[0]; this.triangle[1] = triangle[1]; this.triangle[2] = triangle[2]; triangleDefined = true; this.plane = plane; planeDefined = true; } } } } else { DecompilerThread.OnMessage(this, "WARNING: Tried to define side with " + triangle.Length + " points!"); } }
// METHODS // -decompile() // Attempts to convert the Nightfire BSP file back into a .MAP file. // // This is another one of the most complex things I've ever had to code. I've // never nested for loops four deep before. // Iterators: // i: Current entity in the list // j: Current leaf, referenced in a list by the model referenced by the current entity // k: Current brush, referenced in a list by the current leaf. // l: Current side of the current brush. // m: When attempting vertex decompilation, the current vertex. public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); // In the decompiler, it is not necessary to copy all entities to a new object, since // no writing is ever done back to the BSP file. mapFile = BSPObject.Entities; int numTotalItems = 0; int onePercent = (int)((BSPObject.Brushes.Count + BSPObject.Entities.Count) / 100); if (onePercent < 1) { onePercent = 1; } // I need to go through each entity and see if it's brush-based. // Worldspawn is brush-based as well as any entity with model *#. for (int i = 0; i < BSPObject.Entities.Count; i++) { // For each entity //DecompilerThread.OnMessage(this, "Entity " + i + ": " + mapFile[i]["classname"]); // getModelNumber() returns 0 for worldspawn, the *# for brush based entities, and -1 for everything else int currentModel = mapFile[i].ModelNumber; if (currentModel > -1) { // If this is still -1 then it's strictly a point-based entity. Move on to the next one. int firstLeaf = BSPObject.Models[currentModel].FirstLeaf; int numLeaves = BSPObject.Models[currentModel].NumLeaves; bool[] brushesUsed = new bool[BSPObject.Brushes.Count]; // Keep a list of brushes already in the model, since sometimes the leaves lump references one brush several times numBrshs = 0; for (int j = 0; j < numLeaves; j++) { // For each leaf in the bunch Leaf currentLeaf = BSPObject.Leaves[j + firstLeaf]; int firstBrushIndex = currentLeaf.FirstMarkBrush; int numBrushIndices = currentLeaf.NumMarkBrushes; if (numBrushIndices > 0) { // A lot of leaves reference no brushes. If this is one, this iteration of the j loop is finished for (int k = 0; k < numBrushIndices; k++) { // For each brush referenced if (!brushesUsed[(int)BSPObject.MarkBrushes[firstBrushIndex + k]]) { // If the current brush has NOT been used in this entity //Console.Write("Brush " + numBrshs); brushesUsed[(int)BSPObject.MarkBrushes[firstBrushIndex + k]] = true; decompileBrush(BSPObject.Brushes[(int)BSPObject.MarkBrushes[firstBrushIndex + k]], i); // Decompile the brush numBrshs++; numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count)); } } } } } } numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count)); } } if (!Settings.skipPlaneFlip) { DecompilerThread.OnMessage(this, "Num simple corrected brushes: " + numSimpleCorrects); DecompilerThread.OnMessage(this, "Num advanced corrected brushes: " + numAdvancedCorrects); DecompilerThread.OnMessage(this, "Num good brushes: " + numGoodBrushes); } parent.OnProgress(this, 1.0); return(mapFile); }
public virtual void Run() { DateTime begin = DateTime.Now; try { Entities output = null; if (doomMap != null) { // If this is a Doom map extracted from a WAD //Window.setProgress(jobnum, 0, doomMap.getSubSectors().size(), "Decompiling..."); WADDecompiler decompiler = new WADDecompiler(doomMap, jobnum, this); output = decompiler.decompile(); } else { DecompilerThread.OnMessage(this, "Opening file " + BSPFile.FullName); //Window.setProgress(jobnum, 0, 1, "Reading..."); BSPReader reader = new BSPReader(BSPFile, openAs); reader.readBSP(); //if (!reader.WAD) { BSPObject = reader.BSPData; /*try { * Window.setProgress(jobnum, 0, reader.BSPData.getBrushes().size() + reader.BSPData.getEntities().size(), "Decompiling..."); * } catch (System.NullReferenceException e) { * try { * Window.setProgress(jobnum, 0, reader.BSPData.getLeaves().size() + reader.BSPData.getEntities().size(), "Decompiling..."); * } catch (System.NullReferenceException f) { * Window.setProgress(jobnum, 0, 1, "Decompiling..."); // What's going on here? Put in a failsafe progress bar for now * } * }*/ switch (reader.Version) { case mapType.TYPE_QUAKE: //DecompilerThread.OnMessage(this, "ERROR: Algorithm for decompiling Quake BSPs not written yet.",Window.VERBOSITY_ALWAYS); //throw new java.lang.Exception(); // Throw an exception to the exception handler to indicate it didn't work QuakeDecompiler decompiler29 = new QuakeDecompiler(reader.BSPData, jobnum, this); output = decompiler29.decompile(); break; case mapType.TYPE_NIGHTFIRE: BSP42Decompiler decompiler42 = new BSP42Decompiler(reader.BSPData, jobnum, this); output = decompiler42.decompile(); break; case mapType.TYPE_QUAKE2: case mapType.TYPE_SIN: case mapType.TYPE_SOF: case mapType.TYPE_DAIKATANA: BSP38Decompiler decompiler38 = new BSP38Decompiler(reader.BSPData, jobnum, this); output = decompiler38.decompile(); break; case mapType.TYPE_SOURCE17: case mapType.TYPE_SOURCE18: case mapType.TYPE_SOURCE19: case mapType.TYPE_SOURCE20: case mapType.TYPE_SOURCE21: case mapType.TYPE_SOURCE22: case mapType.TYPE_SOURCE23: case mapType.TYPE_SOURCE27: case mapType.TYPE_DMOMAM: case mapType.TYPE_VINDICTUS: case mapType.TYPE_TACTICALINTERVENTION: SourceBSPDecompiler sourceDecompiler = new SourceBSPDecompiler(reader.BSPData, jobnum, this); output = sourceDecompiler.decompile(); break; case mapType.TYPE_QUAKE3: case mapType.TYPE_RAVEN: case mapType.TYPE_COD: case mapType.TYPE_COD2: case mapType.TYPE_COD4: case mapType.TYPE_STEF2: case mapType.TYPE_STEF2DEMO: case mapType.TYPE_MOHAA: case mapType.TYPE_FAKK: BSP46Decompiler decompiler46 = new BSP46Decompiler(reader.BSPData, jobnum, this); output = decompiler46.decompile(); break; case mapType.TYPE_DOOM: case mapType.TYPE_HEXEN: foreach (DoomMap map in reader.DoomMaps) { WADDecompiler wadDecompiler = new WADDecompiler(map, jobnum, this); Entities holyshit = wadDecompiler.decompile(); MAPMaker.outputMaps(holyshit, map.MapName, map.Folder + map.WadName + "\\", map.Version); } break; default: DecompilerThread.OnMessage(this, "ERROR: Unknown BSP version: " + reader.Version); throw new System.Exception(); // Throw an exception to the exception handler to indicate it didn't work } //} } if (output != null) { //Window.setProgress(jobnum, 1, 1, "Saving..."); if (doomMap == null) { MAPMaker.outputMaps(output, BSPObject.MapNameNoExtension, BSPObject.Folder, BSPObject.Version); } else { MAPMaker.outputMaps(output, doomMap.MapName, doomMap.Folder + doomMap.WadName + "\\", doomMap.Version); } } //Window.setProgress(jobnum, 1, 1, "Done!"); //System.Drawing.Color tempAux = System.Drawing.Color.FromArgb(64, 192, 64); //Window.setProgressColor(jobnum, ref tempAux); DateTime end = DateTime.Now; DecompilerThread.OnMessage(this, "Time taken: " + (end - begin).ToString() + (char)0x0D + (char)0x0A); OnFinish(this, jobnum); } catch (System.OutOfMemoryException) { string st = ""; if (openAs != mapType.TYPE_UNDEFINED) { st = "VM ran out of memory on job " + (jobnum + 1) + ". Are you using \"Open as...\" with the wrong game?" + (char)0x0D + (char)0x0A + "If not, please let me know on the issue tracker!" + (char)0x0D + (char)0x0A + "http://code.google.com/p/jbn-bsp-lump-tools/issues/entry"; } else { st = "VM ran out of memory on job " + (jobnum + 1) + "." + (char)0x0D + (char)0x0A + "Please let me know on the issue tracker!" + (char)0x0D + (char)0x0A + "http://code.google.com/p/jbn-bsp-lump-tools/issues/entry"; } //Window.setProgress(jobnum, 1, 1, "ERROR! See log!"); //System.Drawing.Color tempAux4 = System.Drawing.Color.FromArgb(255, 128, 128); //UPGRADE_NOTE: ref keyword was added to struct-type parameters. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1303'" //Window.setProgressColor(jobnum, ref tempAux4); OnError(this, st); } catch (Exception e) { string st; if (openAs != mapType.TYPE_UNDEFINED) { //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'" st = "" + (char)0x0D + (char)0x0A + "Exception caught in job " + (jobnum + 1) + ": " + e + (char)0x0D + (char)0x0A + "Are you using \"Open as...\" with the wrong game?" + (char)0x0D + (char)0x0A + "If not, please let me know on the issue tracker!" + (char)0x0D + (char)0x0A + "http://code.google.com/p/jbn-bsp-lump-tools/issues/entry"; } else { //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.toString' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'" st = "" + (char)0x0D + (char)0x0A + "Exception caught in job " + (jobnum + 1) + ": " + e + (char)0x0D + (char)0x0A + "Please let me know on the issue tracker!" + (char)0x0D + (char)0x0A + "http://code.google.com/p/jbn-bsp-lump-tools/issues/entry"; } /*System.String stackTrace = "Stack Trace: " + (char) 0x0D + (char) 0x0A; * StackTraceElement[] trace = e.getStackTrace(); * for (int i = 0; i < trace.length; i++) * { * stackTrace += (trace[i].toString() + Window.LF); * } * //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.getMessage' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'" * DecompilerThread.OnMessage(this, e.Message + Window.LF + stackTrace); * DecompilerThread.OnMessage(this, ); * Window.setProgress(jobnum, 1, 1, "ERROR! See log!"); * System.Drawing.Color tempAux3 = System.Drawing.Color.FromArgb(255, 128, 128); * //UPGRADE_NOTE: ref keyword was added to struct-type parameters. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1303'" * Window.setProgressColor(jobnum, ref tempAux3);*/ OnError(this, st); } finally { doomMap = null; BSPObject = null; BSPFile = null; Thread.CurrentThread.Abort(); } /*else * { * Window.print("Job " + (jobnum + 1) + " aborted by user."); * Window.print(" When: While initializing job."); * DecompilerThread.OnMessage(this, ); * Window.setProgress(jobnum, 1, 1, "Aborted!"); * System.Drawing.Color tempAux5 = System.Drawing.Color.FromArgb(255, 128, 128); * //UPGRADE_NOTE: ref keyword was added to struct-type parameters. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1303'" * Window.setProgressColor(jobnum, ref tempAux5); * SupportClass.ThreadClass.Current().Interrupt(); * }*/ //Window.setAbortButtonEnabled(jobnum, false); }
// -decompileBrush(Brush, int, boolean) // Decompiles the Brush and adds it to entitiy #currentEntity as .MAP data. private void decompileBrush(Brush brush, int currentEntity) { Vector3D origin = mapFile[currentEntity].Origin; int firstSide = brush.FirstSide; int numSides = brush.NumSides; MAPBrushSide[] brushSides = new MAPBrushSide[0]; bool isDetail = false; if (!Settings.noDetail && (brush.Contents[1] & ((sbyte)1 << 1)) != 0) { isDetail = true; } MAPBrush mapBrush = new MAPBrush(numBrshs, currentEntity, isDetail); int numRealFaces = 0; Plane[] brushPlanes = new Plane[0]; //DecompilerThread.OnMessage(this, ": " + numSides + " sides"); if (mapFile[currentEntity]["classname"] == "func_water") { mapBrush.Water = true; } for (int l = 0; l < numSides; l++) { // For each side of the brush BrushSide currentSide = BSPObject.BrushSides[firstSide + l]; Face currentFace = BSPObject.Faces[currentSide.Face]; // To find those three points, I can use vertices referenced by faces. string texture = BSPObject.Textures[currentFace.Texture].Name; if ((currentFace.Flags & 0x00000100) == 0) { // Surfaceflags 512 + 256 + 32 are set only by the compiler, on faces that need to be thrown out. if (!texture.ToUpper().Equals("special/clip".ToUpper()) && !texture.ToUpper().Equals("special/playerclip".ToUpper()) && !texture.ToUpper().Equals("special/enemyclip".ToUpper())) { if (Settings.replaceWithNull && ((currentFace.Flags & 0x00000200) != 0) && !texture.ToUpper().Equals("special/trigger".ToUpper())) { texture = "special/null"; currentFace.Flags = 0; } } int firstVertex = currentFace.FirstVertex; int numVertices = currentFace.NumVertices; Plane currentPlane; try { // I've only ever come across this error once or twice, but something causes it very rarely currentPlane = BSPObject.Planes[currentSide.Plane]; } catch (System.IndexOutOfRangeException) { try { // So try to get the plane index from somewhere else currentPlane = BSPObject.Planes[currentFace.Plane]; } catch (System.IndexOutOfRangeException f) { // If that fails, BS something DecompilerThread.OnMessage(this, "WARNING: BSP has error, references nonexistant plane " + currentSide.Plane + ", bad side " + (l) + " of brush " + numBrshs + " Entity " + currentEntity); currentPlane = new Plane((double)1, (double)0, (double)0, (double)0); } } Vector3D[] triangle = new Vector3D[0]; // Three points define a plane. All I have to do is find three points on that plane. bool pointsWorked = false; if (numVertices != 0 && !Settings.planarDecomp) { // If the face actually references a set of vertices triangle = new Vector3D[3]; double currentHighest = 0.0; // Find the combination of three vertices which gives the greatest area for (int p1 = 0; p1 < numVertices - 2; p1++) { for (int p2 = p1 + 1; p2 < numVertices - 1; p2++) { for (int p3 = p2 + 1; p3 < numVertices; p3++) { double currentArea = Vector3D.SqrTriangleArea(BSPObject.Vertices[firstVertex + p1].Vector, BSPObject.Vertices[firstVertex + p2].Vector, BSPObject.Vertices[firstVertex + p3].Vector); if (currentArea > Settings.precision * Settings.precision * 4.0) // Three collinear points will generate an area of 0 or almost 0 { pointsWorked = true; if (currentArea > currentHighest) { currentHighest = currentArea; triangle[0] = BSPObject.Vertices[firstVertex + p1].Vector; triangle[1] = BSPObject.Vertices[firstVertex + p2].Vector; triangle[2] = BSPObject.Vertices[firstVertex + p3].Vector; } } } } } } double[] textureU = new double[3]; double[] textureV = new double[3]; TexInfo currentTexInfo = BSPObject.TexInfo[currentFace.TextureScale]; // Get the lengths of the axis vectors double SAxisLength = System.Math.Sqrt(System.Math.Pow((double)currentTexInfo.SAxis.X, 2) + System.Math.Pow((double)currentTexInfo.SAxis.Y, 2) + System.Math.Pow((double)currentTexInfo.SAxis.Z, 2)); double TAxisLength = System.Math.Sqrt(System.Math.Pow((double)currentTexInfo.TAxis.X, 2) + System.Math.Pow((double)currentTexInfo.TAxis.Y, 2) + System.Math.Pow((double)currentTexInfo.TAxis.Z, 2)); // In compiled maps, shorter vectors=longer textures and vice versa. This will convert their lengths back to 1. We'll use the actual scale values for length. double texScaleU = (1 / SAxisLength); // Let's use these values using the lengths of the U and V axes we found above. double texScaleV = (1 / TAxisLength); textureU[0] = ((double)currentTexInfo.SAxis.X / SAxisLength); textureU[1] = ((double)currentTexInfo.SAxis.Y / SAxisLength); textureU[2] = ((double)currentTexInfo.SAxis.Z / SAxisLength); double originShiftU = (textureU[0] * origin[X] + textureU[1] * origin[Y] + textureU[2] * origin[Z]) / texScaleU; double textureUhiftU = (double)currentTexInfo.SShift - originShiftU; textureV[0] = ((double)currentTexInfo.TAxis.X / TAxisLength); textureV[1] = ((double)currentTexInfo.TAxis.Y / TAxisLength); textureV[2] = ((double)currentTexInfo.TAxis.Z / TAxisLength); double originShiftV = (textureV[0] * origin[X] + textureV[1] * origin[Y] + textureV[2] * origin[Z]) / texScaleV; double textureUhiftV = (double)currentTexInfo.TShift - originShiftV; float texRot = 0; // In compiled maps this is calculated into the U and V axes, so set it to 0 until I can figure out a good way to determine a better value. string material; try { material = BSPObject.Materials[currentFace.Material].Name; } catch (System.IndexOutOfRangeException) { // In case the BSP has some strange error making it reference nonexistant materials DecompilerThread.OnMessage(this, "WARNING: Map referenced nonexistant material #" + currentFace.Material + ", using wld_lightmap instead!"); material = "wld_lightmap"; } double lgtScale = 16; // These values are impossible to get from a compiled map since they double lgtRot = 0; // are used by RAD for generating lightmaps, then are discarded, I believe. MAPBrushSide[] newList = new MAPBrushSide[brushSides.Length + 1]; for (int i = 0; i < brushSides.Length; i++) { newList[i] = brushSides[i]; } if (Settings.noFaceFlags) { currentFace.Flags = 0; } if (pointsWorked) { newList[brushSides.Length] = new MAPBrushSide(currentPlane, triangle, texture, textureU, textureUhiftU, textureV, textureUhiftV, texRot, texScaleU, texScaleV, currentFace.Flags, material, lgtScale, lgtRot); } else { newList[brushSides.Length] = new MAPBrushSide(currentPlane, texture, textureU, textureUhiftU, textureV, textureUhiftV, texRot, texScaleU, texScaleV, currentFace.Flags, material, lgtScale, lgtRot); } brushSides = newList; numRealFaces++; } } for (int i = 0; i < brushSides.Length; i++) { mapBrush.add(brushSides[i]); } brushPlanes = new Plane[mapBrush.NumSides]; for (int i = 0; i < brushPlanes.Length; i++) { brushPlanes[i] = mapBrush[i].Plane; } if (!Settings.skipPlaneFlip) { if (mapBrush.hasBadSide()) { // If there's a side that might be backward if (mapBrush.hasGoodSide()) { // If there's a side that is forward mapBrush = MAPBrush.SimpleCorrectPlanes(mapBrush); numSimpleCorrects++; if (Settings.calcVerts) { // This is performed in advancedcorrect, so don't use it if that's happening try { mapBrush = MAPBrush.CalcBrushVertices(mapBrush); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Brush vertex calculation failed on entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } else { // If no forward side exists try { mapBrush = MAPBrush.AdvancedCorrectPlanes(mapBrush); numAdvancedCorrects++; } catch (System.ArithmeticException) { DecompilerThread.OnMessage(this, "WARNING: Plane correct returned 0 triangles for entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } else { numGoodBrushes++; } } else { if (Settings.calcVerts) { // This is performed in advancedcorrect, so don't use it if that's happening try { mapBrush = MAPBrush.CalcBrushVertices(mapBrush); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Brush vertex calculation failed on entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } // This adds the brush we've been finding and creating to // the current entity as an attribute. The way I've coded // this whole program and the entities parser, this shouldn't // cause any issues at all. if (Settings.brushesToWorld) { mapBrush.Water = false; mapFile[0].Brushes.Add(mapBrush); } else { mapFile[currentEntity].Brushes.Add(mapBrush); } }
// METHODS public virtual void printBSPReport() { // If there's a NullReferenceException here, the BSPReader class didn't initialize the object and therefore // this is either a BSP format which doesn't use that lump, or there's an error which will become apparent. DecompilerThread.OnMessage(this, "Internal version number: " + (int)version + " (" + version + ")"); if (entities != null) { DecompilerThread.OnMessage(this, "Entities lump: " + entities.Length + " bytes, " + entities.Count + " items"); } if (planes != null) { DecompilerThread.OnMessage(this, "Planes lump: " + planes.Length + " bytes, " + planes.Count + " items"); if (planes.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Planes"); } } if (textures != null) { DecompilerThread.OnMessage(this, "Texture lump: " + textures.Length + " bytes, " + textures.Count + " items"); if (textures.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Texture"); } } if (materials != null) { DecompilerThread.OnMessage(this, "Materials lump: " + materials.Length + " bytes, " + materials.Count + " items"); if (materials.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Materials"); } } if (vertices != null) { DecompilerThread.OnMessage(this, "Vertices lump: " + vertices.Length + " bytes, " + vertices.Count + " items"); if (vertices.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Vertices"); } } if (nodes != null) { DecompilerThread.OnMessage(this, "Nodes lump: " + nodes.Length + " bytes, " + nodes.Count + " items"); if (nodes.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Nodes"); } } if (texInfo != null) { DecompilerThread.OnMessage(this, "Texture info lump: " + texInfo.Length + " bytes, " + texInfo.Count + " items"); if (texInfo.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Texture info"); } } if (faces != null) { DecompilerThread.OnMessage(this, "Faces lump: " + faces.Length + " bytes, " + faces.Count + " items"); if (faces.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Faces"); } } if (leaves != null) { DecompilerThread.OnMessage(this, "Leaves lump: " + leaves.Length + " bytes, " + leaves.Count + " items"); if (leaves.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Leaves"); } } if (markSurfaces != null) { DecompilerThread.OnMessage(this, "Mark surfaces lump: " + markSurfaces.Length + " bytes, " + markSurfaces.Count + " items"); if (markSurfaces.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Mark surfaces"); } } if (edges != null) { DecompilerThread.OnMessage(this, "Edges lump: " + edges.Length + " bytes, " + edges.Count + " items"); if (edges.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Edges"); } } if (surfEdges != null) { DecompilerThread.OnMessage(this, "Surface Edges lump: " + surfEdges.Length + " bytes, " + surfEdges.Count + " items"); if (surfEdges.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Surface Edges"); } } if (models != null) { DecompilerThread.OnMessage(this, "Models lump: " + models.Length + " bytes, " + models.Count + " items"); if (models.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Models"); } } if (brushes != null) { DecompilerThread.OnMessage(this, "Brushes lump: " + brushes.Length + " bytes, " + brushes.Count + " items"); if (brushes.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Brushes"); } } if (brushSides != null) { DecompilerThread.OnMessage(this, "Brush sides lump: " + brushSides.Length + " bytes, " + brushSides.Count + " items"); if (brushSides.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Brush sides"); } } if (markBrushes != null) { DecompilerThread.OnMessage(this, "Mark brushes lump: " + markBrushes.Length + " bytes, " + markBrushes.Count + " items"); if (markBrushes.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Mark brushes"); } } if (originalFaces != null) { DecompilerThread.OnMessage(this, "Original Faces lump: " + originalFaces.Length + " bytes, " + originalFaces.Count + " items"); if (originalFaces.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Original Faces"); } } if (texTable != null) { DecompilerThread.OnMessage(this, "Texture index table lump: " + texTable.Length + " bytes, " + texTable.Count + " items"); if (texTable.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Texture index table"); } } if (texDatas != null) { DecompilerThread.OnMessage(this, "Texture data lump: " + texDatas.Length + " bytes, " + texDatas.Count + " items"); if (texDatas.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Texture data"); } } if (dispInfos != null) { DecompilerThread.OnMessage(this, "Displacement info lump: " + dispInfos.Length + " bytes, " + dispInfos.Count + " items"); if (dispInfos.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Displacement info"); } } if (dispVerts != null) { DecompilerThread.OnMessage(this, "Displacement Vertices lump: " + dispVerts.Length + " bytes, " + dispVerts.Count + " items"); if (dispVerts.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Displacement Vertices"); } } if (displacementTriangles != null) { DecompilerThread.OnMessage(this, "Displacement Triangle Tags lump: " + displacementTriangles.Length + " bytes, " + displacementTriangles.Count + " items"); if (displacementTriangles.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Displacement Triangle Tags"); } } if (staticProps != null) { DecompilerThread.OnMessage(this, "Static Props lump: " + staticProps.Length + " bytes, " + staticProps.Count + " items, " + staticProps.Dictionary.Length + " unique models"); } if (cubemaps != null) { DecompilerThread.OnMessage(this, "Cubemaps lump: " + cubemaps.Length + " bytes, " + cubemaps.Count + " items"); if (cubemaps.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Cubemaps"); } } /* * if(overlays != null) { * DecompilerThread.OnMessage(this, "Overlays lump: "+overlays.Length+" bytes, "+overlays.Count+" items",Settings.VERBOSITY_MAPSTATS); * if(overlays.hasFunnySize()) { * DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Overlays",Settings.VERBOSITY_WARNINGS); * } * } catch(System.NullReferenceException) { * }*/ }
// METHODS public virtual void printBSPReport() { try { DecompilerThread.OnMessage(this, "Things lump: " + things.Length + " bytes, " + things.Count + " items"); if (things.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Things"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Linedefs lump: " + linedefs.Length + " bytes, " + linedefs.Count + " items"); if (linedefs.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Linedefs"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Sizedefs lump: " + sidedefs.Length + " bytes, " + sidedefs.Count + " items"); if (sidedefs.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Sidedefs"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Vertices lump: " + vertices.Length + " bytes, " + vertices.Count + " items"); if (vertices.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Vertices"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Segments lump: " + segs.Length + " bytes, " + segs.Count + " items"); if (segs.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Segments"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Subsectors lump: " + subsectors.Length + " bytes, " + subsectors.Count + " items"); if (subsectors.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Subsectors"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Nodes lump: " + nodes.Length + " bytes, " + nodes.Count + " items"); if (nodes.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Nodes"); } } catch (System.NullReferenceException) { } try { DecompilerThread.OnMessage(this, "Sectors lump: " + sectors.Length + " bytes, " + sectors.Count + " items"); if (sectors.hasFunnySize()) { DecompilerThread.OnMessage(this, "WARNING: Funny lump size in Sectors"); } } catch (System.NullReferenceException) { } }
// METHODS // +decompile() // Attempts to convert the BSP file back into a .MAP file. public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); // In the decompiler, it is not necessary to copy all entities to a new object, since // no writing is ever done back to the BSP file. mapFile = BSPObject.Entities; int numTotalItems = 0; worldspawn = mapFile[mapFile.findAllWithAttribute("classname", "worldspawn")[0]]; int onePercent = (int)((BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count) / 100); if (onePercent < 1) { onePercent = 1; } // I need to go through each entity and see if it's brush-based. // Worldspawn is brush-based as well as any entity with model *#. for (int i = 0; i < BSPObject.Entities.Count; i++) { // For each entity //DecompilerThread.OnMessage(this, "Entity " + i + ": " + mapFile[i]["classname"]); numBrshs = 0; // Reset the brush count for each entity // getModelNumber() returns 0 for worldspawn, the *# for brush based entities, and -1 for everything else int currentModel = mapFile[i].ModelNumber; if (currentModel > -1) { // If this is still -1 then it's strictly a point-based entity. Move on to the next one. int firstBrush = BSPObject.Models[currentModel].FirstBrush; int numBrushes = BSPObject.Models[currentModel].NumBrushes; numBrshs = 0; for (int j = 0; j < numBrushes; j++) { // For each brush //Console.Write("Brush " + j); decompileBrush(BSPObject.Brushes[j + firstBrush], i); // Decompile the brush numBrshs++; numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)); } } } numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)); } } if (!Settings.skipPlaneFlip) { DecompilerThread.OnMessage(this, "Num simple corrected brushes: " + numSimpleCorrects); DecompilerThread.OnMessage(this, "Num advanced corrected brushes: " + numAdvancedCorrects); DecompilerThread.OnMessage(this, "Num good brushes: " + numGoodBrushes); } foreach (Face face in BSPObject.Faces) { if (face.Facetype == Face.faceType.PATCH) { MAPPatch mapPatch = new MAPPatch(face.PatchSize[0], face.PatchSize[1], BSPObject.Textures[face.Texture].Name); for (int i = 0; i < face.NumVertices; i++) { mapPatch.Add(BSPObject.Vertices[face.FirstVertex + i]); } MAPBrush mapBrush = new MAPBrush(mapPatch); worldspawn.Brushes.Add(mapBrush); } } parent.OnProgress(this, 1.0); return(mapFile); }
// -decompileBrush(Brush, int) // Decompiles the Brush and adds it to entitiy #currentEntity as MAPBrush classes. private void decompileBrush(Brush brush, int currentEntity) { Vector3D origin = mapFile[currentEntity].Origin; int firstSide = brush.FirstSide; int numSides = brush.NumSides; if (firstSide < 0) { isCoD = true; firstSide = currentSideIndex; currentSideIndex += numSides; } MAPBrushSide[] brushSides = new MAPBrushSide[0]; bool isDetail = false; int brushTextureIndex = brush.Texture; byte[] contents = new byte[4]; if (brushTextureIndex >= 0) { contents = BSPObject.Textures[brushTextureIndex].Contents; } if (!Settings.noDetail && (contents[3] & ((byte)1 << 3)) != 0) { // This is the flag according to q3 source isDetail = true; // it's the same as Q2 (and Source), but I haven't found any Q3 maps that use it, so far } MAPBrush mapBrush = new MAPBrush(numBrshs, currentEntity, isDetail); int numRealFaces = 0; Plane[] brushPlanes = new Plane[0]; //DecompilerThread.OnMessage(this, ": " + numSides + " sides"); if (!Settings.noWater && (contents[0] & ((byte)1 << 5)) != 0) { mapBrush.Water = true; } bool isVisBrush = false; for (int i = 0; i < numSides; i++) { // For each side of the brush BrushSide currentSide = BSPObject.BrushSides[firstSide + i]; int currentFaceIndex = currentSide.Face; Plane currentPlane; if (isCoD) { switch (i) { case 0: // XMin currentPlane = new Plane((double)(-1), (double)0, (double)0, (double)(-currentSide.Dist)); break; case 1: // XMax currentPlane = new Plane((double)1, (double)0, (double)0, (double)currentSide.Dist); break; case 2: // YMin currentPlane = new Plane((double)0, (double)(-1), (double)0, (double)(-currentSide.Dist)); break; case 3: // YMax currentPlane = new Plane((double)0, (double)1, (double)0, (double)currentSide.Dist); break; case 4: // ZMin currentPlane = new Plane((double)0, (double)0, (double)(-1), (double)(-currentSide.Dist)); break; case 5: // ZMax currentPlane = new Plane((double)0, (double)0, (double)1, (double)currentSide.Dist); break; default: currentPlane = BSPObject.Planes[currentSide.Plane]; break; } } else { currentPlane = BSPObject.Planes[currentSide.Plane]; } Vector3D[] triangle = new Vector3D[0]; // Three points define a plane. All I have to do is find three points on that plane. bool pointsWorked = false; int firstVertex = -1; int numVertices = 0; string texture = "noshader"; bool masked = false; if (currentFaceIndex > -1) { Face currentFace = BSPObject.Faces[currentFaceIndex]; int currentTextureIndex = currentFace.Texture; firstVertex = currentFace.FirstVertex; numVertices = currentFace.NumVertices; string mask = BSPObject.Textures[currentTextureIndex].Mask; if (mask.ToUpper().Equals("ignore".ToUpper()) || mask.Length == 0) { texture = BSPObject.Textures[currentTextureIndex].Name; } else { texture = mask.Substring(0, (mask.Length - 4) - (0)); // Because mask includes file extensions masked = true; } if (numVertices != 0 && !Settings.planarDecomp) { // If the face actually references a set of vertices triangle = new Vector3D[3]; double currentHighest = 0.0; // Find the combination of three vertices which gives the greatest area for (int p1 = 0; p1 < numVertices - 2; p1++) { for (int p2 = p1 + 1; p2 < numVertices - 1; p2++) { for (int p3 = p2 + 1; p3 < numVertices; p3++) { double currentArea = Vector3D.SqrTriangleArea(BSPObject.Vertices[firstVertex + p1].Vector, BSPObject.Vertices[firstVertex + p2].Vector, BSPObject.Vertices[firstVertex + p3].Vector); if (currentArea > Settings.precision * Settings.precision * 4.0) // Three collinear points will generate an area of 0 or almost 0 { pointsWorked = true; if (currentArea > currentHighest) { currentHighest = currentArea; triangle[0] = BSPObject.Vertices[firstVertex + p1].Vector; triangle[1] = BSPObject.Vertices[firstVertex + p2].Vector; triangle[2] = BSPObject.Vertices[firstVertex + p3].Vector; } } } } } } } else { // If face information is not available, use the brush side's info instead int currentTextureIndex = currentSide.Texture; if (currentTextureIndex >= 0) { string mask = BSPObject.Textures[currentTextureIndex].Mask; if (mask.ToUpper().Equals("ignore".ToUpper()) || mask.Length == 0) { texture = BSPObject.Textures[currentTextureIndex].Name; } else { texture = mask.Substring(0, (mask.Length - 4) - (0)); // Because mask includes file extensions masked = true; } } else { // If neither face or brush side has texture info, fall all the way back to brush. I don't know if this ever happens. if (brushTextureIndex >= 0) { // If none of them have any info, noshader string mask = BSPObject.Textures[brushTextureIndex].Mask; if (mask.ToUpper().Equals("ignore".ToUpper()) || mask.Length == 0) { texture = BSPObject.Textures[brushTextureIndex].Name; } else { texture = mask.Substring(0, (mask.Length - 4) - (0)); // Because mask includes file extensions masked = true; } } } } if (texture.ToUpper().Equals("textures/common/vis".ToUpper())) { isVisBrush = true; return; // TODO: Try to recreate the vis entity? It's impossible to recreate the links... } // Get the lengths of the axis vectors. // TODO: This information seems to be contained in Q3's vertex structure. But there doesn't seem // to be a way to directly link faces to brush sides. double UAxisLength = 1; double VAxisLength = 1; double texScaleS = 1; double texScaleT = 1; Vector3D[] textureAxes = TexInfo.textureAxisFromPlane(currentPlane); double originShiftS = (textureAxes[0].X * origin[X]) + (textureAxes[0].Y * origin[Y]) + (textureAxes[0].Z * origin[Z]); double originShiftT = (textureAxes[1].X * origin[X]) + (textureAxes[1].Y * origin[Y]) + (textureAxes[1].Z * origin[Z]); double textureShiftS; double textureShiftT; if (firstVertex >= 0) { textureShiftS = (double)BSPObject.Vertices[firstVertex].TexCoordX - originShiftS; textureShiftT = (double)BSPObject.Vertices[firstVertex].TexCoordY - originShiftT; } else { textureShiftS = 0 - originShiftS; textureShiftT = 0 - originShiftT; } float texRot = 0; string material; if (masked) { material = "wld_masked"; } else { material = "wld_lightmap"; } double lgtScale = 16; double lgtRot = 0; MAPBrushSide[] newList = new MAPBrushSide[brushSides.Length + 1]; for (int j = 0; j < brushSides.Length; j++) { newList[j] = brushSides[j]; } int flags; //if(Settings.noFaceFlags) { flags = 0; //} if (pointsWorked) { newList[brushSides.Length] = new MAPBrushSide(currentPlane, triangle, texture, textureAxes[0].Point, textureShiftS, textureAxes[1].Point, textureShiftT, texRot, texScaleS, texScaleT, flags, material, lgtScale, lgtRot); } else { newList[brushSides.Length] = new MAPBrushSide(currentPlane, texture, textureAxes[0].Point, textureShiftS, textureAxes[1].Point, textureShiftT, texRot, texScaleS, texScaleT, flags, material, lgtScale, lgtRot); } brushSides = newList; numRealFaces++; } for (int i = 0; i < brushSides.Length; i++) { mapBrush.add(brushSides[i]); } brushPlanes = new Plane[mapBrush.NumSides]; for (int i = 0; i < brushPlanes.Length; i++) { brushPlanes[i] = mapBrush[i].Plane; } if (isCoD && mapBrush.NumSides > 6) { // Now we need to get rid of all the sides that aren't used. Get a list of // the useless sides from one brush, and delete those sides from all of them, // since they all have the same sides. if (!Settings.dontCull && numSides > 6) { int[] badSides = MAPBrush.findUnusedPlanes(mapBrush); // Need to iterate backward, since these lists go from low indices to high, and // the index of all subsequent items changes when something before it is removed. if (mapBrush.NumSides - badSides.Length < 4) { DecompilerThread.OnMessage(this, "WARNING: Plane cull returned less than 4 sides for entity " + currentEntity + " brush " + numBrshs); } else { for (int i = badSides.Length - 1; i > -1; i--) { mapBrush.delete(badSides[i]); } } } } if (!Settings.skipPlaneFlip) { if (mapBrush.hasBadSide()) { // If there's a side that might be backward if (mapBrush.hasGoodSide()) { // If there's a side that is forward mapBrush = MAPBrush.SimpleCorrectPlanes(mapBrush); numSimpleCorrects++; if (Settings.calcVerts) { // This is performed in advancedcorrect, so don't use it if that's happening try { mapBrush = MAPBrush.CalcBrushVertices(mapBrush); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Brush vertex calculation failed on entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } else { // If no forward side exists try { mapBrush = MAPBrush.AdvancedCorrectPlanes(mapBrush); numAdvancedCorrects++; } catch (System.ArithmeticException) { DecompilerThread.OnMessage(this, "WARNING: Plane correct returned 0 triangles for entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } else { numGoodBrushes++; } } else { if (Settings.calcVerts) { // This is performed in advancedcorrect, so don't use it if that's happening try { mapBrush = MAPBrush.CalcBrushVertices(mapBrush); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Brush vertex calculation failed on entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } // This adds the brush we've been finding and creating to // the current entity as an attribute. The way I've coded // this whole program and the entities parser, this shouldn't // cause any issues at all. if (Settings.brushesToWorld) { mapBrush.Water = false; worldspawn.Brushes.Add(mapBrush); } else { mapFile[currentEntity].Brushes.Add(mapBrush); } }
private string brushSideToString(MAPBrushSide inputData, bool isDetail) { try { Vector3D[] triangle = inputData.Triangle; string texture = inputData.Texture; Vector3D textureS = inputData.TextureS; Vector3D textureT = inputData.TextureT; double textureShiftS = inputData.TextureShiftS; double textureShiftT = inputData.TextureShiftT; float texRot = inputData.TexRot; double texScaleX = inputData.TexScaleX; double texScaleY = inputData.TexScaleY; int flags = inputData.Flags; string material = inputData.Material; double lgtScale = inputData.LgtScale; double lgtRot = inputData.LgtRot; string temp = ""; // Correct textures here try { if (texture.Substring(0, (9) - (0)).ToUpper().Equals("textures/".ToUpper())) { texture = texture.Substring(9); } } catch (System.ArgumentOutOfRangeException) { ; } if (BSPVersion == mapType.TYPE_NIGHTFIRE || BSPVersion == mapType.TYPE_DOOM || BSPVersion == mapType.TYPE_HEXEN) { if (texture.ToUpper().Equals("special/nodraw".ToUpper()) || texture.ToUpper().Equals("special/null".ToUpper())) { texture = "common/nodraw"; } else { if (texture.ToUpper().Equals("special/clip".ToUpper())) { texture = "common/clip"; } else { if (texture.ToUpper().Equals("special/sky".ToUpper())) { texture = "common/skyportal"; } else { if (texture.ToUpper().Equals("special/trigger".ToUpper())) { texture = "common/trigger"; } else { if (texture.ToUpper().Equals("special/playerclip".ToUpper())) { texture = "common/playerclip"; } else { if (texture.ToUpper().Equals("special/npcclip".ToUpper()) || texture.ToUpper().Equals("special/enemyclip".ToUpper())) { texture = "common/tankclip"; } } } } } } } else { if (BSPVersion == mapType.TYPE_QUAKE2) { try { if (texture.ToUpper().Equals("special/hint".ToUpper())) { texture = "common/hint"; } else { if (texture.ToUpper().Equals("special/skip".ToUpper())) { texture = "common/skip"; } else { if (texture.ToUpper().Equals("special/sky".ToUpper())) { texture = "common/skyportal"; } else { if (texture.Substring(texture.Length - 8).ToUpper().Equals("/trigger".ToUpper())) { texture = "common/trigger"; } else { if (texture.Substring(texture.Length - 5).ToUpper().Equals("/clip".ToUpper())) { texture = "common/clip"; } } } } } } catch (System.ArgumentOutOfRangeException e) { ; } } else { if (BSPVersion == mapType.TYPE_SOURCE17 || BSPVersion == mapType.TYPE_SOURCE18 || BSPVersion == mapType.TYPE_SOURCE19 || BSPVersion == mapType.TYPE_SOURCE20 || BSPVersion == mapType.TYPE_SOURCE21 || BSPVersion == mapType.TYPE_SOURCE22 || BSPVersion == mapType.TYPE_SOURCE23 || BSPVersion == mapType.TYPE_DMOMAM || BSPVersion == mapType.TYPE_VINDICTUS || BSPVersion == mapType.TYPE_TACTICALINTERVENTION) { try { if (texture.Substring(0, (5) - (0)).ToUpper().Equals("maps/".ToUpper())) { texture = texture.Substring(5); for (int i = 0; i < texture.Length; i++) { if (texture[i] == '/') { texture = texture.Substring(i + 1); break; } } } } catch (System.ArgumentOutOfRangeException e) { ; } // Find cubemap textures int numUnderscores = 0; bool validnumber = false; for (int i = texture.Length - 1; i > 0; i--) { if (texture[i] <= '9' && texture[i] >= '0') { // Current is a number, start building string validnumber = true; } else { if (texture[i] == '-') { // Current is a minus sign (-). if (!validnumber) { break; // Make sure there's a number to add the minus sign to. If not, kill the loop. } } else { if (texture[i] == '_') { // Current is an underscore (_) if (validnumber) { // Make sure there is a number in the current string numUnderscores++; // before moving on to the next one. validnumber = false; if (numUnderscores == 3) { // If we've got all our numbers texture = texture.Substring(0, (i) - (0)); // Cut the texture string break; // Kill the loop, we're done } } else { // No number after the underscore break; } } else { // Not an acceptable character break; } } } } } } } if (Double.IsInfinity(texScaleX) || Double.IsNaN(texScaleX)) { texScaleX = 1; } if (Double.IsInfinity(texScaleY) || Double.IsNaN(texScaleY)) { texScaleY = 1; } if (Double.IsInfinity(textureShiftS) || Double.IsNaN(textureShiftS)) { textureShiftS = 0; } if (Double.IsInfinity(textureShiftT) || Double.IsNaN(textureShiftT)) { textureShiftT = 0; } if (Double.IsInfinity(textureS.X) || Double.IsNaN(textureS.X) || Double.IsInfinity(textureS.Y) || Double.IsNaN(textureS.Y) || Double.IsInfinity(textureS.Z) || Double.IsNaN(textureS.Z)) { textureS = TexInfo.textureAxisFromPlane(inputData.Plane)[0]; } if (Double.IsInfinity(textureT.X) || Double.IsNaN(textureT.X) || Double.IsInfinity(textureT.Y) || Double.IsNaN(textureT.Y) || Double.IsInfinity(textureT.Z) || Double.IsNaN(textureT.Z)) { textureT = TexInfo.textureAxisFromPlane(inputData.Plane)[1]; } if (Settings.roundNums) { temp = "( " + MAPMaker.Round(triangle[0].X, 6) + " " + MAPMaker.Round(triangle[0].Y, 6) + " " + MAPMaker.Round(triangle[0].Z, 6) + " ) " + "( " + MAPMaker.Round(triangle[1].X, 6) + " " + MAPMaker.Round(triangle[1].Y, 6) + " " + MAPMaker.Round(triangle[1].Z, 6) + " ) " + "( " + MAPMaker.Round(triangle[2].X, 6) + " " + MAPMaker.Round(triangle[2].Y, 6) + " " + MAPMaker.Round(triangle[2].Z, 6) + " ) " + texture + " " + System.Math.Floor(textureShiftS) + " " + System.Math.Floor(textureShiftT) + " " + MAPMaker.FormattedRound(texRot, 2, "######0.00") + " " + MAPMaker.Round(texScaleX, 6) + " " + MAPMaker.Round(texScaleY, 6) + " " + flags + " 0 0 "; } else { temp = "( " + triangle[0].X + " " + triangle[0].Y + " " + triangle[0].Z + " ) " + "( " + triangle[1].X + " " + triangle[1].Y + " " + triangle[1].Z + " ) " + "( " + triangle[2].X + " " + triangle[2].Y + " " + triangle[2].Z + " ) " + texture + " " + textureShiftS + " " + textureShiftT + " " + texRot + " " + texScaleX + " " + texScaleY + " " + flags + " 0 0 "; } if (isDetail) { temp += "+surfaceparm detail "; } return(temp); } catch (System.NullReferenceException e) { DecompilerThread.OnMessage(this, "WARNING: Side with bad data! Not exported!"); return(""); } }
// -decompileBrush38(Brush, int, boolean) // Decompiles the Brush and adds it to entitiy #currentEntity as .MAP data. private void decompileBrush(Brush brush, int currentEntity) { Vector3D origin = mapFile[currentEntity].Origin; int firstSide = brush.FirstSide; int numSides = brush.NumSides; bool isDetail = false; MAPBrushSide[] brushSides = new MAPBrushSide[numSides]; if (!Settings.noDetail && (brush.Contents[3] & ((sbyte)1 << 3)) != 0) { // According to Q2's source, this is the detail flag isDetail = true; } MAPBrush mapBrush = new MAPBrush(numBrshs, currentEntity, isDetail); //DecompilerThread.OnMessage(this, ": " + numSides + " sides"); if (!Settings.noWater && (brush.Contents[0] & ((sbyte)1 << 5)) != 0) { mapBrush.Water = true; } for (int i = 0; i < numSides; i++) { // For each side of the brush Vector3D[] plane = new Vector3D[3]; // Three points define a plane. All I have to do is find three points on that plane. BrushSide currentSide = BSPObject.BrushSides[firstSide + i]; Plane currentPlane = BSPObject.Planes[currentSide.Plane]; // To find those three points, I must extrapolate from planes until I find a way to associate faces with brushes Texture currentTexture; bool isDuplicate = false; for (int j = i + 1; j < numSides; j++) { // For each subsequent side of the brush // For some reason, QUAKE 2 MAKES COPLANAR SIDES OF BRUSHES. I don't know why but it's stupid. if (currentPlane.Equals(BSPObject.Planes[BSPObject.BrushSides[firstSide + j].Plane])) { DecompilerThread.OnMessage(this, "WARNING: Duplicate planes in entity " + currentEntity + " brush " + numBrshs + ", sides " + i + " and " + j + " (BSP planes " + currentSide.Plane + " and " + BSPObject.BrushSides[firstSide + j].Plane); isDuplicate = true; } } if (!isDuplicate) { /* * if(!Settings.planarDecomp) { * // Find a face whose plane and texture information corresponds to the current side * // It doesn't really matter if it's the actual brush's face, just as long as it provides vertices. * SiNFace currentFace=null; * boolean faceFound=false; * for(int j=0;j<BSP.getSFaces().size();j++) { * currentFace=BSP.getSFaces().getFace(j); * if(currentFace.getPlane()==currentSide.getPlane() && currentFace.getTexInfo()==currentSide.getTexInfo() && currentFace.getNumEdges()>1) { * faceFound=true; * break; * } * } * if(faceFound) { * int markEdge=BSP.getMarkEdges().getInt(currentFace.getFirstEdge()); * int currentMarkEdge=0; * int firstVertex; * int secondVertex; * if(markEdge>0) { * firstVertex=BSP.getEdges().getEdge(markEdge).getFirstVertex(); * secondVertex=BSP.getEdges().getEdge(markEdge).getSecondVertex(); * } else { * firstVertex=BSP.getEdges().getEdge(-markEdge).getSecondVertex(); * secondVertex=BSP.getEdges().getEdge(-markEdge).getFirstVertex(); * } * int numVertices=currentFace.getNumEdges()+1; * boolean pointsWorked=false; * plane[0]=new Vector3D(BSP.getVertices().getVertex(firstVertex)); // Grab and store the first one * plane[1]=new Vector3D(BSP.getVertices().getVertex(secondVertex)); // The second should be unique from the first * boolean second=false; * if(plane[0].equals(plane[1])) { // If for some messed up reason they are the same * for(currentMarkEdge=1;currentMarkEdge<currentFace.getNumEdges();currentMarkEdge++) { // For each edge after the first one * markEdge=BSP.getMarkEdges().getInt(currentFace.getFirstEdge()+currentMarkEdge); * if(markEdge>0) { * plane[1]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(markEdge).getFirstVertex())); * } else { * plane[1]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(-markEdge).getSecondVertex())); * } * if(!plane[0].equals(plane[1])) { // Make sure the point isn't the same as the first one * second=false; * break; // If it isn't the same, this point is good * } else { * if(markEdge>0) { * plane[1]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(markEdge).getSecondVertex())); * } else { * plane[1]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(-markEdge).getFirstVertex())); * } * if(!plane[0].equals(plane[1])) { * second=true; * break; * } * } * } * } * if(second) { * currentMarkEdge++; * } * for(;currentMarkEdge<currentFace.getNumEdges();currentMarkEdge++) { * markEdge=BSP.getMarkEdges().getInt(currentFace.getFirstEdge()+currentMarkEdge); * if(second) { * if(markEdge>0) { * plane[2]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(markEdge).getFirstVertex())); * } else { * plane[2]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(-markEdge).getSecondVertex())); * } * if(!plane[2].equals(plane[0]) && !plane[2].equals(plane[1])) { // Make sure no point is equal to the third one * if((Vector3D.crossProduct(plane[0].subtract(plane[1]), plane[0].subtract(plane[2])).X!=0) || // Make sure all * (Vector3D.crossProduct(plane[0].subtract(plane[1]), plane[0].subtract(plane[2])).Y!=0) || // three points * (Vector3D.crossProduct(plane[0].subtract(plane[1]), plane[0].subtract(plane[2])).Z!=0)) { // are not collinear * pointsWorked=true; * break; * } * } * } * // if we get to here, the first vertex of the edge failed, or was already used * if(markEdge>0) { // use the second vertex * plane[2]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(markEdge).getSecondVertex())); * } else { * plane[2]=new Vector3D(BSP.getVertices().getVertex(BSP.getEdges().getEdge(-markEdge).getFirstVertex())); * } * if(!plane[2].equals(plane[0]) && !plane[2].equals(plane[1])) { // Make sure no point is equal to the third one * if((Vector3D.crossProduct(plane[0].subtract(plane[1]), plane[0].subtract(plane[2])).X!=0) || // Make sure all * (Vector3D.crossProduct(plane[0].subtract(plane[1]), plane[0].subtract(plane[2])).Y!=0) || // three points * (Vector3D.crossProduct(plane[0].subtract(plane[1]), plane[0].subtract(plane[2])).Z!=0)) { // are not collinear * pointsWorked=true; * break; * } * } * // If we get here, neither point worked and we need to try the next edge. * second=true; * } * if(!pointsWorked) { * plane=Plane.generatePlanePoints(currentPlane); * } * } else { // Face not found * plane=Plane.generatePlanePoints(currentPlane); * } * } else { // Planar decomp only */ plane = Plane.generatePlanePoints(currentPlane); // } string texture = "special/clip"; double[] textureU = new double[3]; double[] textureV = new double[3]; double UShift = 0; double VShift = 0; double texScaleU = 1; double texScaleV = 1; if (currentSide.Texture > -1) { currentTexture = BSPObject.Textures[currentSide.Texture]; if ((currentTexture.Flags[0] & ((sbyte)1 << 2)) != 0) { texture = "special/sky"; } else { if ((currentTexture.Flags[1] & ((sbyte)1 << 1)) != 0) { texture = "special/skip"; } else { if ((currentTexture.Flags[1] & ((sbyte)1 << 0)) != 0) { if (currentEntity == 0) { texture = "special/hint"; // Hint was not used the same way in Quake 2 as other games. } else { // For example, a Hint brush CAN be used for a trigger in Q2 and is used as such a lot. texture = "special/trigger"; } } else { texture = currentTexture.Name; } } } // Get the lengths of the axis vectors double SAxisLength = System.Math.Sqrt(System.Math.Pow((double)currentTexture.TexAxes.SAxis.X, 2) + System.Math.Pow((double)currentTexture.TexAxes.SAxis.Y, 2) + System.Math.Pow((double)currentTexture.TexAxes.SAxis.Z, 2)); double TAxisLength = System.Math.Sqrt(System.Math.Pow((double)currentTexture.TexAxes.TAxis.X, 2) + System.Math.Pow((double)currentTexture.TexAxes.TAxis.Y, 2) + System.Math.Pow((double)currentTexture.TexAxes.TAxis.Z, 2)); // In compiled maps, shorter vectors=longer textures and vice versa. This will convert their lengths back to 1. We'll use the actual scale values for length. texScaleU = (1 / SAxisLength); // Let's use these values using the lengths of the U and V axes we found above. texScaleV = (1 / TAxisLength); textureU[0] = ((double)currentTexture.TexAxes.SAxis.X / SAxisLength); textureU[1] = ((double)currentTexture.TexAxes.SAxis.Y / SAxisLength); textureU[2] = ((double)currentTexture.TexAxes.SAxis.Z / SAxisLength); textureV[0] = ((double)currentTexture.TexAxes.TAxis.X / TAxisLength); textureV[1] = ((double)currentTexture.TexAxes.TAxis.Y / TAxisLength); textureV[2] = ((double)currentTexture.TexAxes.TAxis.Z / TAxisLength); UShift = (double)currentTexture.TexAxes.SShift; VShift = (double)currentTexture.TexAxes.TShift; } else { Vector3D[] axes = TexInfo.textureAxisFromPlane(currentPlane); textureU = axes[0].Point; textureV = axes[1].Point; } double originShiftU = (textureU[0] * origin[X] + textureU[1] * origin[Y] + textureU[2] * origin[Z]) / texScaleU; double textureShiftU = UShift - originShiftU; double originShiftV = (textureV[0] * origin[X] + textureV[1] * origin[Y] + textureV[2] * origin[Z]) / texScaleV; double textureShiftV = VShift - originShiftV; float texRot = 0; // In compiled maps this is calculated into the U and V axes, so set it to 0 until I can figure out a good way to determine a better value. int flags = 0; // Set this to 0 until we can somehow associate faces with brushes string material = "wld_lightmap"; // Since materials are a NightFire only thing, set this to a good default double lgtScale = 16; // These values are impossible to get from a compiled map since they double lgtRot = 0; // are used by RAD for generating lightmaps, then are discarded, I believe. brushSides[i] = new MAPBrushSide(plane, texture, textureU, textureShiftU, textureV, textureShiftV, texRot, texScaleU, texScaleV, flags, material, lgtScale, lgtRot); mapBrush.add(brushSides[i]); } } if (!Settings.skipPlaneFlip) { if (mapBrush.hasBadSide()) { // If there's a side that might be backward if (mapBrush.hasGoodSide()) { // If there's a side that is forward mapBrush = MAPBrush.SimpleCorrectPlanes(mapBrush); numSimpleCorrects++; if (Settings.calcVerts) { // This is performed in advancedcorrect, so don't use it if that's happening try { mapBrush = MAPBrush.CalcBrushVertices(mapBrush); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Brush vertex calculation failed on entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } else { // If no forward side exists try { mapBrush = MAPBrush.AdvancedCorrectPlanes(mapBrush); numAdvancedCorrects++; } catch (System.ArithmeticException) { DecompilerThread.OnMessage(this, "WARNING: Plane correct returned 0 triangles for entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } else { numGoodBrushes++; } } else { if (Settings.calcVerts) { // This is performed in advancedcorrect, so don't use it if that's happening try { mapBrush = MAPBrush.CalcBrushVertices(mapBrush); } catch (System.NullReferenceException) { DecompilerThread.OnMessage(this, "WARNING: Brush vertex calculation failed on entity " + mapBrush.Entnum + " brush " + mapBrush.Brushnum + ""); } } } // This adds the brush we've been finding and creating to // the current entity as an attribute. The way I've coded // this whole program and the entities parser, this shouldn't // cause any issues at all. if (Settings.brushesToWorld) { mapBrush.Water = false; mapFile[0].Brushes.Add(mapBrush); } else { mapFile[currentEntity].Brushes.Add(mapBrush); } }
// METHODS // Attempt to turn the Quake 2 BSP into a .MAP file public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); // In the decompiler, it is not necessary to copy all entities to a new object, since // no writing is ever done back to the BSP file. mapFile = BSPObject.Entities; //int numAreaPortals=0; int numTotalItems = 0; int onePercent = (int)((BSPObject.Brushes.Count + BSPObject.Entities.Count) / 100); if (onePercent < 1) { onePercent = 1; } bool containsAreaPortals = false; for (int i = 0; i < BSPObject.Entities.Count; i++) { // For each entity //DecompilerThread.OnMessage(this, "Entity " + i + ": " + mapFile[i]["classname"]); // Deal with area portals. if (mapFile[i]["classname"].Equals("func_areaportal", StringComparison.CurrentCultureIgnoreCase)) { mapFile[i].Attributes.Remove("style"); containsAreaPortals = true; } // getModelNumber() returns 0 for worldspawn, the *# for brush based entities, and -1 for everything else int currentModel = mapFile[i].ModelNumber; if (currentModel > -1) { // If this is still -1 then it's strictly a point-based entity. Move on to the next one. Leaf[] leaves = BSPObject.getLeavesInModel(currentModel); int numLeaves = leaves.Length; bool[] brushesUsed = new bool[BSPObject.Brushes.Count]; // Keep a list of brushes already in the model, since sometimes the leaves lump references one brush several times numBrshs = 0; // Reset the brush count for each entity for (int j = 0; j < numLeaves; j++) { // For each leaf in the bunch Leaf currentLeaf = leaves[j]; int firstBrushIndex = currentLeaf.FirstMarkBrush; int numBrushIndices = currentLeaf.NumMarkBrushes; if (numBrushIndices > 0) { // A lot of leaves reference no brushes. If this is one, this iteration of the j loop is finished for (int k = 0; k < numBrushIndices; k++) { // For each brush referenced if (!brushesUsed[(int)BSPObject.MarkBrushes[firstBrushIndex + k]]) { // If the current brush has NOT been used in this entity //Console.Write("Brush " + numBrshs); brushesUsed[(int)BSPObject.MarkBrushes[firstBrushIndex + k]] = true; Brush brush = BSPObject.Brushes[(int)BSPObject.MarkBrushes[firstBrushIndex + k]]; if ((brush.Contents[1] & ((sbyte)1 << 7)) == 0) { decompileBrush(brush, i); // Decompile the brush } else { containsAreaPortals = true; } numBrshs++; numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count)); } } } } } } numTotalItems++; // This entity if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count)); } } if (containsAreaPortals) { // If this map was found to have area portals int j = 0; for (int i = 0; i < BSPObject.Brushes.Count; i++) { // For each brush in this map if ((BSPObject.Brushes[i].Contents[1] & ((sbyte)1 << 7)) != 0) { // If the brush is an area portal brush for (j++; j < BSPObject.Entities.Count; j++) { // Find an areaportal entity if (BSPObject.Entities[j]["classname"].Equals("func_areaportal", StringComparison.CurrentCultureIgnoreCase)) { decompileBrush(BSPObject.Brushes[i], j); // Add the brush to that entity break; // And break out of the inner loop, but remember your place. } } if (j == BSPObject.Entities.Count) { // If we're out of entities, stop this whole thing. break; } } } } if (!Settings.skipPlaneFlip) { DecompilerThread.OnMessage(this, "Num simple corrected brushes: " + numSimpleCorrects); DecompilerThread.OnMessage(this, "Num advanced corrected brushes: " + numAdvancedCorrects); DecompilerThread.OnMessage(this, "Num good brushes: " + numGoodBrushes); } parent.OnProgress(this, 1.0); return(mapFile); }
private string brushSideToString(MAPBrushSide inputData) { try { string texture = inputData.Texture; if (BSPVersion == mapType.TYPE_SOURCE17 || BSPVersion == mapType.TYPE_SOURCE18 || BSPVersion == mapType.TYPE_SOURCE19 || BSPVersion == mapType.TYPE_SOURCE20 || BSPVersion == mapType.TYPE_SOURCE21 || BSPVersion == mapType.TYPE_SOURCE22 || BSPVersion == mapType.TYPE_SOURCE23 || BSPVersion == mapType.TYPE_DMOMAM || BSPVersion == mapType.TYPE_VINDICTUS || BSPVersion == mapType.TYPE_TACTICALINTERVENTION) { try { if (texture.Substring(0, (5) - (0)).ToUpper().Equals("maps/".ToUpper())) { texture = texture.Substring(5); for (int i = 0; i < texture.Length; i++) { if (texture[i] == '/') { texture = texture.Substring(i + 1); break; } } } } catch (System.ArgumentOutOfRangeException) { ; } // Find cubemap textures int numUnderscores = 0; bool validnumber = false; for (int i = texture.Length - 1; i > 0; i--) { if (texture[i] <= '9' && texture[i] >= '0') { // Current is a number, start building string validnumber = true; } else { if (texture[i] == '-') { // Current is a minus sign (-). if (!validnumber) { break; // Make sure there's a number to add the minus sign to. If not, kill the loop. } } else { if (texture[i] == '_') { // Current is an underscore (_) if (validnumber) { // Make sure there is a number in the current string numUnderscores++; // before moving on to the next one. validnumber = false; if (numUnderscores == 3) { // If we've got all our numbers texture = texture.Substring(0, (i) - (0)); // Cut the texture string break; // Kill the loop, we're done } } else { // No number after the underscore break; } } else { // Not an acceptable character break; } } } } } Plane plane = inputData.Plane; Vector3D textureS = inputData.TextureS; Vector3D textureT = inputData.TextureT; double textureShiftS = inputData.TextureShiftS; double textureShiftT = inputData.TextureShiftT; double texScaleX = inputData.TexScaleX; double texScaleY = inputData.TexScaleY; if (Double.IsInfinity(texScaleX) || Double.IsNaN(texScaleX)) { texScaleX = 1; } if (Double.IsInfinity(texScaleY) || Double.IsNaN(texScaleY)) { texScaleY = 1; } if (Double.IsInfinity(textureShiftS) || Double.IsNaN(textureShiftS)) { textureShiftS = 0; } if (Double.IsInfinity(textureShiftT) || Double.IsNaN(textureShiftT)) { textureShiftT = 0; } if (Double.IsInfinity(textureS.X) || Double.IsNaN(textureS.X) || Double.IsInfinity(textureS.Y) || Double.IsNaN(textureS.Y) || Double.IsInfinity(textureS.Z) || Double.IsNaN(textureS.Z)) { textureS = TexInfo.textureAxisFromPlane(inputData.Plane)[0]; } if (Double.IsInfinity(textureT.X) || Double.IsNaN(textureT.X) || Double.IsInfinity(textureT.Y) || Double.IsNaN(textureT.Y) || Double.IsInfinity(textureT.Z) || Double.IsNaN(textureT.Z)) { textureT = TexInfo.textureAxisFromPlane(inputData.Plane)[1]; } if (Settings.roundNums) { return("( " + MAPMaker.Round(plane.A, 10) + " " + MAPMaker.Round(plane.B, 10) + " " + MAPMaker.Round(plane.C, 10) + " " + MAPMaker.Round(plane.Dist, 10) + " ) " + "( ( 1 0 " + MAPMaker.Round(textureShiftS, 10) + " ) ( 0 1 " + MAPMaker.Round(textureShiftT, 10) + " ) ) " + "\"" + texture + "\" 0 0 0"); } else { return("( " + plane.A + " " + plane.B + " " + plane.C + " " + plane.Dist + " ) " + "( ( 1 0 " + textureShiftS + " ) ( 0 1 " + textureShiftT + " ) ) " + "\"" + texture + "\" 0 0 0"); } } catch (System.NullReferenceException e) { DecompilerThread.OnMessage(this, "WARNING: Side with bad data! Not exported!"); return(null); } }
// CONSTRUCTORS // This constructor sets up everything to convert a Doom map into brushes compatible with modern map editors. // I don't know if this is decompiling, per se. I don't know if Doom maps were ever compiled or if they just had nodes built. public WADDecompiler(DoomMap doomMap, int jobnum, DecompilerThread parent) { this.doomMap = doomMap; this.jobnum = jobnum; this.parent = parent; }
// METHODS // Attempt to turn the BSP into a .MAP file public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); // In the decompiler, it is not necessary to copy all entities to a new object, since // no writing is ever done back to the BSP file. mapFile = BSPObject.Entities; //int numAreaPortals=0; int numTotalItems = 0; int onePercent = (int)((BSPObject.Brushes.Count + BSPObject.Entities.Count) / 100); if (onePercent < 1) { onePercent = 1; } int originalNumEntities = BSPObject.Entities.Count; // Need to keep track of this in this algorithm, since I create more entities on the fly for (int i = 0; i < originalNumEntities; i++) { // For each entity //DecompilerThread.OnMessage(this, "Entity " + i + ": " + mapFile[i]["classname"]); // getModelNumber() returns 0 for worldspawn, the *# for brush based entities, and -1 for everything else int currentModel = mapFile[i].ModelNumber; if (currentModel > -1) // If this is still -1 then it's strictly a point-based entity. Move on to the next one. { Leaf[] leaves = BSPObject.getLeavesInModel(currentModel); int numLeaves = leaves.Length; bool[] brushesUsed = new bool[BSPObject.Brushes.Count]; // Keep a list of brushes already in the model, since sometimes the leaves lump references one brush several times numBrshs = 0; // Reset the brush count for each entity for (int j = 0; j < numLeaves; j++) { // For each leaf in the bunch Leaf currentLeaf = leaves[j]; int firstMarkBrushIndex = currentLeaf.FirstMarkBrush; int numBrushIndices = currentLeaf.NumMarkBrushes; if (numBrushIndices > 0) { // A lot of leaves reference no brushes. If this is one, this iteration of the j loop is finished for (int k = 0; k < numBrushIndices; k++) { // For each brush referenced long currentBrushIndex = BSPObject.MarkBrushes[firstMarkBrushIndex + k]; if (!brushesUsed[(int)currentBrushIndex]) { // If the current brush has NOT been used in this entity //Console.Write("Brush " + numBrshs); brushesUsed[(int)currentBrushIndex] = true; Brush brush = BSPObject.Brushes[(int)currentBrushIndex]; decompileBrush(brush, i); // Decompile the brush numBrshs++; numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count)); } } } } } } numTotalItems++; // This entity if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count)); } } // Find displacement faces and generate brushes for them for (int i = 0; i < BSPObject.Faces.Count; i++) { Face face = BSPObject.Faces[i]; if (face.Displacement > -1) { SourceDispInfo disp = BSPObject.DispInfos[face.Displacement]; TexInfo currentTexInfo; if (face.Texture > -1) { currentTexInfo = BSPObject.TexInfo[face.Texture]; } else { Vector3D[] axes = TexInfo.textureAxisFromPlane(BSPObject.Planes[face.Plane]); currentTexInfo = new TexInfo(axes[0], 0, axes[1], 0, 0, BSPObject.findTexDataWithTexture("tools/toolsclip")); } SourceTexData currentTexData = BSPObject.TexDatas[currentTexInfo.Texture]; string texture = BSPObject.Textures.getTextureAtOffset((uint)BSPObject.TexTable[currentTexData.StringTableIndex]); double[] textureU = new double[3]; double[] textureV = new double[3]; // Get the lengths of the axis vectors double SAxisLength = System.Math.Sqrt(System.Math.Pow((double)currentTexInfo.SAxis.X, 2) + System.Math.Pow((double)currentTexInfo.SAxis.Y, 2) + System.Math.Pow((double)currentTexInfo.SAxis.Z, 2)); double TAxisLength = System.Math.Sqrt(System.Math.Pow((double)currentTexInfo.TAxis.X, 2) + System.Math.Pow((double)currentTexInfo.TAxis.Y, 2) + System.Math.Pow((double)currentTexInfo.TAxis.Z, 2)); // In compiled maps, shorter vectors=longer textures and vice versa. This will convert their lengths back to 1. We'll use the actual scale values for length. double texScaleU = (1 / SAxisLength); // Let's use these values using the lengths of the U and V axes we found above. double texScaleV = (1 / TAxisLength); textureU[0] = ((double)currentTexInfo.SAxis.X / SAxisLength); textureU[1] = ((double)currentTexInfo.SAxis.Y / SAxisLength); textureU[2] = ((double)currentTexInfo.SAxis.Z / SAxisLength); //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextSettingsIndex'&keyword='jlca1042'" double textureShiftU = (double)currentTexInfo.SShift; textureV[0] = ((double)currentTexInfo.TAxis.X / TAxisLength); textureV[1] = ((double)currentTexInfo.TAxis.Y / TAxisLength); textureV[2] = ((double)currentTexInfo.TAxis.Z / TAxisLength); //UPGRADE_WARNING: Data types in Visual C# might be different. Verify the accuracy of narrowing conversions. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextSettingsIndex'&keyword='jlca1042'" double textureShiftV = (double)currentTexInfo.TShift; if (face.NumEdges != 4) { DecompilerThread.OnMessage(this, "Displacement face with " + face.NumEdges + " edges!"); } // Turn vertices and edges into arrays of vectors Vector3D[] froms = new Vector3D[face.NumEdges]; Vector3D[] tos = new Vector3D[face.NumEdges]; for (int j = 0; j < face.NumEdges; j++) { if (BSPObject.SurfEdges[face.FirstEdge + j] > 0) { froms[j] = BSPObject.Vertices[BSPObject.Edges[(int)BSPObject.SurfEdges[face.FirstEdge + j]].FirstVertex].Vector; tos[j] = BSPObject.Vertices[BSPObject.Edges[(int)BSPObject.SurfEdges[face.FirstEdge + j]].SecondVertex].Vector; } else { tos[j] = BSPObject.Vertices[BSPObject.Edges[(int)BSPObject.SurfEdges[face.FirstEdge + j] * (-1)].FirstVertex].Vector; froms[j] = BSPObject.Vertices[BSPObject.Edges[(int)BSPObject.SurfEdges[face.FirstEdge + j] * (-1)].SecondVertex].Vector; } } MAPBrush displacementBrush = MAPBrush.createBrushFromWind(froms, tos, texture, "TOOLS/TOOLSNODRAW", currentTexInfo); MAPDisplacement mapdisp = new MAPDisplacement(disp, BSPObject.DispVerts.getVertsInDisp(disp.DispVertStart, disp.Power)); displacementBrush[0].Displacement = mapdisp; mapFile[0].Brushes.Add(displacementBrush); } } for (int i = 0; i < BSPObject.StaticProps.Count; i++) { Entity newStaticProp = new Entity("prop_static"); SourceStaticProp currentProp = BSPObject.StaticProps[i]; newStaticProp["model"] = BSPObject.StaticProps.Dictionary[currentProp.DictionaryEntry]; newStaticProp["skin"] = currentProp.Skin + ""; newStaticProp["origin"] = currentProp.Origin.X + " " + currentProp.Origin.Y + " " + currentProp.Origin.Z; newStaticProp["angles"] = currentProp.Angles.X + " " + currentProp.Angles.Y + " " + currentProp.Angles.Z; newStaticProp["solid"] = currentProp.Solidity + ""; newStaticProp["fademindist"] = currentProp.MinFadeDist + ""; newStaticProp["fademaxdist"] = currentProp.MaxFadeDist + ""; newStaticProp["fadescale"] = currentProp.ForcedFadeScale + ""; if (currentProp.Targetname != null) { newStaticProp["targetname"] = currentProp.Targetname; } mapFile.Add(newStaticProp); } for (int i = 0; i < BSPObject.Cubemaps.Count; i++) { Entity newCubemap = new Entity("env_cubemap"); SourceCubemap currentCube = BSPObject.Cubemaps[i]; newCubemap["origin"] = currentCube.Origin.X + " " + currentCube.Origin.Y + " " + currentCube.Origin.Z; newCubemap["cubemapsize"] = currentCube.Size + ""; mapFile.Add(newCubemap); } if (!Settings.skipPlaneFlip) { DecompilerThread.OnMessage(this, "Num simple corrected brushes: " + numSimpleCorrects); DecompilerThread.OnMessage(this, "Num advanced corrected brushes: " + numAdvancedCorrects); DecompilerThread.OnMessage(this, "Num good brushes: " + numGoodBrushes); } parent.OnProgress(this, 1.0); return(mapFile); }