// Create a rectangular brush from mins to maxs, with specified texture public static MAPBrush createBrush(Vector3D mins, Vector3D maxs, string texture) { MAPBrush newBrush = new MAPBrush(-1, 0, false); Vector3D[][] planes = new Vector3D[6][]; for (int i = 0; i < 6; i++) { planes[i] = new Vector3D[3]; } // Six planes for a cube brush, three vertices for each plane double[][] textureS = new double[6][]; for (int i2 = 0; i2 < 6; i2++) { textureS[i2] = new double[3]; } double[][] textureT = new double[6][]; for (int i3 = 0; i3 < 6; i3++) { textureT[i3] = new double[3]; } // The planes and their texture scales // I got these from an origin brush created by Gearcraft. Don't worry where these numbers came from, they work. // Top planes[0][0] = new Vector3D(mins.X, maxs.Y, maxs.Z); planes[0][1] = new Vector3D(maxs.X, maxs.Y, maxs.Z); planes[0][2] = new Vector3D(maxs.X, mins.Y, maxs.Z); textureS[0][0] = 1; textureT[0][1] = -1; // Bottom planes[1][0] = new Vector3D(mins.X, mins.Y, mins.Z); planes[1][1] = new Vector3D(maxs.X, mins.Y, mins.Z); planes[1][2] = new Vector3D(maxs.X, maxs.Y, mins.Z); textureS[1][0] = 1; textureT[1][1] = -1; // Left planes[2][0] = new Vector3D(mins.X, maxs.Y, maxs.Z); planes[2][1] = new Vector3D(mins.X, mins.Y, maxs.Z); planes[2][2] = new Vector3D(mins.X, mins.Y, mins.Z); textureS[2][1] = 1; textureT[2][2] = -1; // Right planes[3][0] = new Vector3D(maxs.X, maxs.Y, mins.Z); planes[3][1] = new Vector3D(maxs.X, mins.Y, mins.Z); planes[3][2] = new Vector3D(maxs.X, mins.Y, maxs.Z); textureS[3][1] = 1; textureT[3][2] = -1; // Near planes[4][0] = new Vector3D(maxs.X, maxs.Y, maxs.Z); planes[4][1] = new Vector3D(mins.X, maxs.Y, maxs.Z); planes[4][2] = new Vector3D(mins.X, maxs.Y, mins.Z); textureS[4][0] = 1; textureT[4][2] = -1; // Far planes[5][0] = new Vector3D(maxs.X, mins.Y, mins.Z); planes[5][1] = new Vector3D(mins.X, mins.Y, mins.Z); planes[5][2] = new Vector3D(mins.X, mins.Y, maxs.Z); textureS[5][0] = 1; textureT[5][2] = -1; for (int j = 0; j < 6; j++) { MAPBrushSide currentEdge = new MAPBrushSide(planes[j], texture, textureS[j], 0, textureT[j], 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); newBrush.add(currentEdge); } return(newBrush); }
// -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); } }
// -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); } }
// createFaceBrush(String, String, Vector3D, Vector3D) // This creates a rectangular brush. The String is assumed to be a texture for a face, and // the two vectors are a bounding box to create a plane with (mins-maxs). // The second String is the texture to apply to all other sides. public static MAPBrush createFaceBrush(string texture, string backTexture, Vector3D mins, Vector3D maxs, double xoff, double yoff, bool lowerUnpegged, int shiftYCieling, int shiftYFloor) { //DecompilerThread.OnMessage(this, "Creating brush for face with " + texture); MAPBrush newBrush = new MAPBrush(0, 0, false); Vector3D[][] planes = new Vector3D[6][]; for (int i = 0; i < 6; i++) { planes[i] = new Vector3D[3]; } // Six planes for a cube brush, three vertices for each plane double[][] texS = new double[6][]; for (int i2 = 0; i2 < 6; i2++) { texS[i2] = new double[3]; } double[][] texT = new double[6][]; for (int i3 = 0; i3 < 6; i3++) { texT[i3] = new double[3]; } double sideLengthXY = System.Math.Sqrt(System.Math.Pow(mins.X - maxs.X, 2) + System.Math.Pow(mins.Y - maxs.Y, 2)); Vector3D diffVec1 = new Vector3D(mins.X, mins.Y, maxs.Z) - mins; Vector3D diffVec2 = new Vector3D(maxs.X, maxs.Y, mins.Z) - mins; Vector3D cross = diffVec2 ^ diffVec1; cross.normalize(); // Face planes[0][0] = new Vector3D(mins.X, mins.Y, maxs.Z); planes[0][1] = new Vector3D(maxs.X, maxs.Y, mins.Z); planes[0][2] = mins; texS[0][0] = (-(mins.X - maxs.X)) / sideLengthXY; texS[0][1] = (-(mins.Y - maxs.Y)) / sideLengthXY; texT[0][2] = -1; // To be fair, to find these properly you ought to take the dot product of these over the length. // However, length is always one, and there's only two components (the third sum would turn out to be 0) double SShift = xoff - (texS[0][0] * mins.X) - (texS[0][1] * mins.Y); double TShift = yoff; // One sided linedefs (which this usually makes walls for) are only affected by lower unpegged. Upper is // always assumed unless lower is true. if (lowerUnpegged) { TShift += shiftYFloor; } else { TShift += shiftYCieling; } // Far planes[1][0] = new Vector3D(mins.X, mins.Y, maxs.Z) - (cross); planes[1][1] = mins - (cross); planes[1][2] = new Vector3D(maxs.X, maxs.Y, mins.Z) - (cross); texS[1][0] = texS[0][0]; texS[1][1] = texS[0][1]; texT[1][2] = -1; // Top planes[2][0] = new Vector3D(mins.X, mins.Y, maxs.Z); planes[2][1] = new Vector3D(mins.X, mins.Y, maxs.Z) - (cross); planes[2][2] = maxs; texS[2][0] = 1; texT[2][1] = 1; // Bottom planes[3][0] = mins; planes[3][1] = new Vector3D(maxs.X, maxs.Y, mins.Z); planes[3][2] = new Vector3D(maxs.X, maxs.Y, mins.Z) - (cross); texS[3][0] = 1; texT[3][1] = 1; // Left planes[4][0] = mins; planes[4][1] = mins - cross; planes[4][2] = new Vector3D(mins.X, mins.Y, maxs.Z); texS[4][0] = texS[0][1]; texS[4][1] = texS[0][0]; texT[4][2] = 1; // Right planes[5][0] = maxs; planes[5][1] = maxs - cross; planes[5][2] = new Vector3D(maxs.X, maxs.Y, mins.Z); texS[5][0] = texS[0][1]; texS[5][1] = texS[0][0]; texT[5][2] = 1; MAPBrushSide front = new MAPBrushSide(planes[0], texture, texS[0], SShift, texT[0], TShift, 0, 1, 1, 0, "wld_lightmap", 16, 0); newBrush.add(front); for (int i = 1; i < 6; i++) { newBrush.add(new MAPBrushSide(planes[i], backTexture, texS[i], 0, texT[i], 0, 0, 1, 1, 32, "wld_lightmap", 16, 0)); } return(newBrush); }
// -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; MAPBrushSide[] brushSides = new MAPBrushSide[numSides]; bool isDetail = false; if (currentEntity == 0 && !Settings.noDetail && (brush.Contents[3] & ((sbyte) 1 << 3)) != 0) { isDetail = true; } MAPBrush mapBrush = new MAPBrush(numBrshs, currentEntity, isDetail); if (currentEntity == 0 && !Settings.noWater && (brush.Contents[0] & ((sbyte) 1 << 5)) != 0) { mapBrush.Water = true; } //DecompilerThread.OnMessage(this, ": " + numSides + " sides, detail: " + isDetail); for (int i = 0; i < numSides; i++) { // For each side of the brush BrushSide currentSide = BSPObject.BrushSides[firstSide + i]; if (currentSide.isBevel() == 0) { // Bevel sides are evil Vector3D[] plane = new Vector3D[3]; // Three points define a plane. All I have to do is find three points on that plane. 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 bool isDuplicate = false; /* TODO: We sure don't want duplicate planes (though this is already handled by the MAPBrush class). Make sure neither checked side is bevel. for(int j=i+1;j<numSides;j++) { // For each subsequent side of the brush if(currentPlane.equals(BSPObject.Planes.getPlane(BSPObject.getBrushSides()[firstSide+j).getPlane()))) { DecompilerThread.OnMessage(this, "WARNING: Duplicate planes in a brush, sides "+i+" and "+j,Settings.VERBOSITY_WARNINGS); isDuplicate=true; } }*/ if (!isDuplicate) { TexInfo currentTexInfo = null; string texture = "tools/toolsclip"; if (currentSide.Texture > - 1) { currentTexInfo = BSPObject.TexInfo[currentSide.Texture]; } else { int dataIndex = BSPObject.findTexDataWithTexture("tools/toolsclip"); if (dataIndex >= 0) { currentTexInfo = new TexInfo(new Vector3D(0, 0, 0), 0, new Vector3D(0, 0, 0), 0, 0, dataIndex); } } if (currentTexInfo != null) { SourceTexData currentTexData; if (currentTexInfo.Texture >= 0) { // I've only found one case where this is a problem: c2a3a in HL Source. Don't know why. currentTexData = BSPObject.TexDatas[currentTexInfo.Texture]; texture = BSPObject.Textures.getTextureAtOffset((uint)BSPObject.TexTable[currentTexData.StringTableIndex]); } else { texture = "tools/toolsskip"; } } double[] textureU = new double[3]; double[] textureV = new double[3]; double textureShiftU = 0; double textureShiftV = 0; double texScaleU = 1; double texScaleV = 1; // Get the lengths of the axis vectors if ((texture.Length > 6 && texture.Substring(0, (6) - (0)).ToUpper().Equals("tools/".ToUpper())) || currentTexInfo == null) { // Tools textured faces do not maintain their own texture axes. Therefore, an arbitrary axis is // used in the compiled map. When decompiled, these axes might smear the texture on the face. Fix that. Vector3D[] axes = TexInfo.textureAxisFromPlane(currentPlane); textureU = axes[0].Point; textureV = axes[1].Point; } else { 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. 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) currentTexInfo.SAxis.X / SAxisLength); textureU[1] = ((double) currentTexInfo.SAxis.Y / SAxisLength); textureU[2] = ((double) currentTexInfo.SAxis.Z / SAxisLength); double originShiftU = (((double) currentTexInfo.SAxis.X / SAxisLength) * origin[X] + ((double) currentTexInfo.SAxis.Y / SAxisLength) * origin[Y] + ((double) currentTexInfo.SAxis.Z / SAxisLength) * origin[Z]) / texScaleU; //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'" textureShiftU = (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 = (((double) currentTexInfo.TAxis.X / TAxisLength) * origin[X] + ((double) currentTexInfo.TAxis.Y / TAxisLength) * origin[Y] + ((double) currentTexInfo.TAxis.Z / TAxisLength) * origin[Z]) / texScaleV; //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'" textureShiftV = (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. 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(currentPlane, 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); } }
// -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); } }
// createFaceBrush(String, String, Vector3D, Vector3D) // This creates a rectangular brush. The String is assumed to be a texture for a face, and // the two vectors are a bounding box to create a plane with (mins-maxs). // The second String is the texture to apply to all other sides. public static MAPBrush createFaceBrush(string texture, string backTexture, Vector3D mins, Vector3D maxs, double xoff, double yoff, bool lowerUnpegged, int shiftYCieling, int shiftYFloor) { //DecompilerThread.OnMessage(this, "Creating brush for face with " + texture); MAPBrush newBrush = new MAPBrush(0, 0, false); Vector3D[][] planes = new Vector3D[6][]; for (int i = 0; i < 6; i++) { planes[i] = new Vector3D[3]; } // Six planes for a cube brush, three vertices for each plane double[][] texS = new double[6][]; for (int i2 = 0; i2 < 6; i2++) { texS[i2] = new double[3]; } double[][] texT = new double[6][]; for (int i3 = 0; i3 < 6; i3++) { texT[i3] = new double[3]; } double sideLengthXY = System.Math.Sqrt(System.Math.Pow(mins.X - maxs.X, 2) + System.Math.Pow(mins.Y - maxs.Y, 2)); Vector3D diffVec1 = new Vector3D(mins.X, mins.Y, maxs.Z)-mins; Vector3D diffVec2 = new Vector3D(maxs.X, maxs.Y, mins.Z)-mins; Vector3D cross = diffVec2^diffVec1; cross.normalize(); // Face planes[0][0] = new Vector3D(mins.X, mins.Y, maxs.Z); planes[0][1] = new Vector3D(maxs.X, maxs.Y, mins.Z); planes[0][2] = mins; texS[0][0] = (- (mins.X - maxs.X)) / sideLengthXY; texS[0][1] = (- (mins.Y - maxs.Y)) / sideLengthXY; texT[0][2] = - 1; // To be fair, to find these properly you ought to take the dot product of these over the length. // However, length is always one, and there's only two components (the third sum would turn out to be 0) double SShift = xoff - (texS[0][0] * mins.X) - (texS[0][1] * mins.Y); double TShift = yoff; // One sided linedefs (which this usually makes walls for) are only affected by lower unpegged. Upper is // always assumed unless lower is true. if (lowerUnpegged) { TShift += shiftYFloor; } else { TShift += shiftYCieling; } // Far planes[1][0] = new Vector3D(mins.X, mins.Y, maxs.Z)-(cross); planes[1][1] = mins-(cross); planes[1][2] = new Vector3D(maxs.X, maxs.Y, mins.Z)-(cross); texS[1][0] = texS[0][0]; texS[1][1] = texS[0][1]; texT[1][2] = - 1; // Top planes[2][0] = new Vector3D(mins.X, mins.Y, maxs.Z); planes[2][1] = new Vector3D(mins.X, mins.Y, maxs.Z)-(cross); planes[2][2] = maxs; texS[2][0] = 1; texT[2][1] = 1; // Bottom planes[3][0] = mins; planes[3][1] = new Vector3D(maxs.X, maxs.Y, mins.Z); planes[3][2] = new Vector3D(maxs.X, maxs.Y, mins.Z)-(cross); texS[3][0] = 1; texT[3][1] = 1; // Left planes[4][0] = mins; planes[4][1] = mins-cross; planes[4][2] = new Vector3D(mins.X, mins.Y, maxs.Z); texS[4][0] = texS[0][1]; texS[4][1] = texS[0][0]; texT[4][2] = 1; // Right planes[5][0] = maxs; planes[5][1] = maxs-cross; planes[5][2] = new Vector3D(maxs.X, maxs.Y, mins.Z); texS[5][0] = texS[0][1]; texS[5][1] = texS[0][0]; texT[5][2] = 1; MAPBrushSide front = new MAPBrushSide(planes[0], texture, texS[0], SShift, texT[0], TShift, 0, 1, 1, 0, "wld_lightmap", 16, 0); newBrush.add(front); for (int i = 1; i < 6; i++) { newBrush.add(new MAPBrushSide(planes[i], backTexture, texS[i], 0, texT[i], 0, 0, 1, 1, 32, "wld_lightmap", 16, 0)); } return newBrush; }
// Create a rectangular brush from mins to maxs, with specified texture public static MAPBrush createBrush(Vector3D mins, Vector3D maxs, string texture) { MAPBrush newBrush = new MAPBrush(- 1, 0, false); Vector3D[][] planes = new Vector3D[6][]; for (int i = 0; i < 6; i++) { planes[i] = new Vector3D[3]; } // Six planes for a cube brush, three vertices for each plane double[][] textureS = new double[6][]; for (int i2 = 0; i2 < 6; i2++) { textureS[i2] = new double[3]; } double[][] textureT = new double[6][]; for (int i3 = 0; i3 < 6; i3++) { textureT[i3] = new double[3]; } // The planes and their texture scales // I got these from an origin brush created by Gearcraft. Don't worry where these numbers came from, they work. // Top planes[0][0] = new Vector3D(mins.X, maxs.Y, maxs.Z); planes[0][1] = new Vector3D(maxs.X, maxs.Y, maxs.Z); planes[0][2] = new Vector3D(maxs.X, mins.Y, maxs.Z); textureS[0][0] = 1; textureT[0][1] = - 1; // Bottom planes[1][0] = new Vector3D(mins.X, mins.Y, mins.Z); planes[1][1] = new Vector3D(maxs.X, mins.Y, mins.Z); planes[1][2] = new Vector3D(maxs.X, maxs.Y, mins.Z); textureS[1][0] = 1; textureT[1][1] = - 1; // Left planes[2][0] = new Vector3D(mins.X, maxs.Y, maxs.Z); planes[2][1] = new Vector3D(mins.X, mins.Y, maxs.Z); planes[2][2] = new Vector3D(mins.X, mins.Y, mins.Z); textureS[2][1] = 1; textureT[2][2] = - 1; // Right planes[3][0] = new Vector3D(maxs.X, maxs.Y, mins.Z); planes[3][1] = new Vector3D(maxs.X, mins.Y, mins.Z); planes[3][2] = new Vector3D(maxs.X, mins.Y, maxs.Z); textureS[3][1] = 1; textureT[3][2] = - 1; // Near planes[4][0] = new Vector3D(maxs.X, maxs.Y, maxs.Z); planes[4][1] = new Vector3D(mins.X, maxs.Y, maxs.Z); planes[4][2] = new Vector3D(mins.X, maxs.Y, mins.Z); textureS[4][0] = 1; textureT[4][2] = - 1; // Far planes[5][0] = new Vector3D(maxs.X, mins.Y, mins.Z); planes[5][1] = new Vector3D(mins.X, mins.Y, mins.Z); planes[5][2] = new Vector3D(mins.X, mins.Y, maxs.Z); textureS[5][0] = 1; textureT[5][2] = - 1; for (int j = 0; j < 6; j++) { MAPBrushSide currentEdge = new MAPBrushSide(planes[j], texture, textureS[j], 0, textureT[j], 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); newBrush.add(currentEdge); } return newBrush; }
// METHODS // +decompile() // Attempts to convert a map in a Doom WAD into a usable .MAP file. This has many // challenges, not the least of which is the fact that the Doom engine didn't use // brushes (at least, not in any sane way). public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); DecompilerThread.OnMessage(this, doomMap.MapName); mapFile = new Entities(); Entity world = new Entity("worldspawn"); mapFile.Add(world); string[] lowerWallTextures = new string[doomMap.Sidedefs.Count]; string[] midWallTextures = new string[doomMap.Sidedefs.Count]; string[] higherWallTextures = new string[doomMap.Sidedefs.Count]; short[] sectorTag = new short[doomMap.Sectors.Count]; string playerStartOrigin = ""; // Since Doom relied on sectors to define a cieling and floor height, and nothing else, // need to find the minimum and maximum used Z values. This is because the Doom engine // is only a pseudo-3D engine. For all it cares, the cieling and floor extend to their // respective infinities. For a GC/Hammer map, however, this cannot be the case. int ZMin = 32767; // Even though the values in the map will never exceed these, use ints here to avoid int ZMax = - 32768; // overflows, in case the map DOES go within 32 units of these values. for (int i = 0; i < doomMap.Sectors.Count; i++) { DSector currentSector = doomMap.Sectors[i]; sectorTag[i] = currentSector.Tag; if (currentSector.FloorHeight < ZMin + 32) { ZMin = currentSector.FloorHeight - 32; // Can't use the actual value, because that IS the floor } else { if (currentSector.CielingHeight > ZMax - 32) { ZMax = currentSector.CielingHeight + 32; // or the cieling. Subtract or add a sane value to it. } } } // I need to analyze the binary tree and get more information, particularly the // parent nodes of each subsector and node, as well as whether it's the right or // left child of that node. These are extremely important, as the parent defines // boundaries for the children, as well as inheriting further boundaries from its // parents. These boundaries are invaluable for forming brushes. int[] nodeparents = new int[doomMap.Nodes.Count]; bool[] nodeIsLeft = new bool[doomMap.Nodes.Count]; for (int i = 0; i < doomMap.Nodes.Count; i++) { nodeparents[i] = - 1; // There should only be one node left with -1 as a parent. This SHOULD be the root. for (int j = 0; j < doomMap.Nodes.Count; j++) { if (doomMap.Nodes[j].Child1 == i) { nodeparents[i] = j; break; } else { if (doomMap.Nodes[j].Child2 == i) { nodeparents[i] = j; nodeIsLeft[i] = true; break; } } } } // Keep a list of what subsectors belong to which sector int[] subsectorSectors = new int[doomMap.SubSectors.Count]; // Keep a list of what sidedefs belong to what subsector as well int[][] subsectorSidedefs = new int[doomMap.SubSectors.Count][]; short[][] sideDefShifts = new short[2][]; for (int i2 = 0; i2 < 2; i2++) { sideDefShifts[i2] = new short[doomMap.Sidedefs.Count]; } // Figure out what sector each subsector belongs to, and what node is its parent. // Depending on sector "tags" this will help greatly in creation of brushbased entities, // and also helps in finding subsector floor and cieling heights. int[] ssparents = new int[doomMap.SubSectors.Count]; bool[] ssIsLeft = new bool[doomMap.SubSectors.Count]; for (int i = 0; i < doomMap.SubSectors.Count; i++) { //DecompilerThread.OnMessage(this, "Populating texture lists for subsector " + i); // First, find the subsector's parent and whether it is the left or right child. ssparents[i] = - 1; // No subsector should have a -1 in here for (int j = 0; j < doomMap.Nodes.Count; j++) { // When a node references a subsector, it is not referenced by negative // index, as future BSP versions do. The bits 0-14 ARE the index, and // bit 15 (which is the sign bit in two's compliment math) determines // whether or not it is a node or subsector. Therefore, we need to add // 2^15 to the number to produce the actual index. if (doomMap.Nodes[j].Child1 + 32768 == i) { ssparents[i] = j; break; } else { if (doomMap.Nodes[j].Child2 + 32768 == i) { ssparents[i] = j; ssIsLeft[i] = true; break; } } } // Second, figure out what sector a subsector belongs to, and the type of sector it is. subsectorSectors[i] = - 1; Edge currentsubsector = doomMap.SubSectors[i]; subsectorSidedefs[i] = new int[currentsubsector.NumSegs]; for (int j = 0; j < currentsubsector.NumSegs; j++) { // For each segment the subsector references DSegment currentsegment = doomMap.Segments[currentsubsector.FirstSeg + j]; DLinedef currentlinedef = doomMap.Linedefs[currentsegment.Linedef]; int currentsidedefIndex; int othersideIndex; if (currentsegment.Direction == 0) { currentsidedefIndex = currentlinedef.Right; othersideIndex = currentlinedef.Left; } else { currentsidedefIndex = currentlinedef.Left; othersideIndex = currentlinedef.Right; } subsectorSidedefs[i][j] = currentsidedefIndex; DSidedef currentSidedef = doomMap.Sidedefs[currentsidedefIndex]; if (currentlinedef.OneSided) { // A one-sided linedef should always be like this midWallTextures[currentsidedefIndex] = doomMap.WadName + "/" + currentSidedef.MidTexture; higherWallTextures[currentsidedefIndex] = "special/nodraw"; lowerWallTextures[currentsidedefIndex] = "special/nodraw"; sideDefShifts[X][currentsidedefIndex] = currentSidedef.OffsetX; sideDefShifts[Y][currentsidedefIndex] = currentSidedef.OffsetY; } else { // I don't really get why I need to apply these textures to the other side. But if it works I won't argue... if (!currentSidedef.MidTexture.Equals("-")) { midWallTextures[othersideIndex] = doomMap.WadName + "/" + currentSidedef.MidTexture; } else { midWallTextures[othersideIndex] = "special/nodraw"; } if (!currentSidedef.HighTexture.Equals("-")) { higherWallTextures[othersideIndex] = doomMap.WadName + "/" + currentSidedef.HighTexture; } else { higherWallTextures[othersideIndex] = "special/nodraw"; } if (!currentSidedef.LowTexture.Equals("-")) { lowerWallTextures[othersideIndex] = doomMap.WadName + "/" + currentSidedef.LowTexture; } else { lowerWallTextures[othersideIndex] = "special/nodraw"; } sideDefShifts[X][othersideIndex] = currentSidedef.OffsetX; sideDefShifts[Y][othersideIndex] = currentSidedef.OffsetY; } // Sometimes a subsector seems to belong to more than one sector. Only the reference in the first seg is true. if (j == 0) { subsectorSectors[i] = currentSidedef.Sector; } } } bool[] linedefFlagsDealtWith = new bool[doomMap.Linedefs.Count]; bool[] linedefSpecialsDealtWith = new bool[doomMap.Linedefs.Count]; MAPBrush[][] sectorFloorBrushes = new MAPBrush[doomMap.Sectors.Count][]; for (int i3 = 0; i3 < doomMap.Sectors.Count; i3++) { sectorFloorBrushes[i3] = new MAPBrush[0]; } MAPBrush[][] sectorCielingBrushes = new MAPBrush[doomMap.Sectors.Count][]; for (int i4 = 0; i4 < doomMap.Sectors.Count; i4++) { sectorCielingBrushes[i4] = new MAPBrush[0]; } // For one-sided linedefs referenced by more than one subsector bool[] outsideBrushAlreadyCreated = new bool[doomMap.Linedefs.Count]; int onePercent = (int)((doomMap.SubSectors.Count)/100); if(onePercent < 1) { onePercent = 1; } for (int i = 0; i < doomMap.SubSectors.Count; i++) { //DecompilerThread.OnMessage(this, "Creating brushes for subsector " + i); Edge currentsubsector = doomMap.SubSectors[i]; // Third, create a few brushes out of the geometry. MAPBrush cielingBrush = new MAPBrush(numBrshs++, 0, false); MAPBrush floorBrush = new MAPBrush(numBrshs++, 0, false); MAPBrush midBrush = new MAPBrush(numBrshs++, 0, false); MAPBrush sectorTriggerBrush = new MAPBrush(numBrshs++, 0, false); DSector currentSector = doomMap.Sectors[subsectorSectors[i]]; Vector3D[] roofPlane = new Vector3D[3]; double[] roofTexS = new double[3]; double[] roofTexT = new double[3]; roofPlane[0] = new Vector3D(0, Settings.planePointCoef, ZMax); roofPlane[1] = new Vector3D(Settings.planePointCoef, Settings.planePointCoef, ZMax); roofPlane[2] = new Vector3D(Settings.planePointCoef, 0, ZMax); roofTexS[0] = 1; roofTexT[1] = - 1; Vector3D[] cileingPlane = new Vector3D[3]; double[] cileingTexS = new double[3]; double[] cileingTexT = new double[3]; cileingPlane[0] = new Vector3D(0, 0, currentSector.CielingHeight); cileingPlane[1] = new Vector3D(Settings.planePointCoef, 0, currentSector.CielingHeight); cileingPlane[2] = new Vector3D(Settings.planePointCoef, Settings.planePointCoef, currentSector.CielingHeight); cileingTexS[0] = 1; cileingTexT[1] = - 1; Vector3D[] floorPlane = new Vector3D[3]; double[] floorTexS = new double[3]; double[] floorTexT = new double[3]; floorPlane[0] = new Vector3D(0, Settings.planePointCoef, currentSector.FloorHeight); floorPlane[1] = new Vector3D(Settings.planePointCoef, Settings.planePointCoef, currentSector.FloorHeight); floorPlane[2] = new Vector3D(Settings.planePointCoef, 0, currentSector.FloorHeight); floorTexS[0] = 1; floorTexT[1] = - 1; Vector3D[] foundationPlane = new Vector3D[3]; double[] foundationTexS = new double[3]; double[] foundationTexT = new double[3]; foundationPlane[0] = new Vector3D(0, 0, ZMin); foundationPlane[1] = new Vector3D(Settings.planePointCoef, 0, ZMin); foundationPlane[2] = new Vector3D(Settings.planePointCoef, Settings.planePointCoef, ZMin); foundationTexS[0] = 1; foundationTexT[1] = - 1; Vector3D[] topPlane = new Vector3D[3]; double[] topTexS = new double[3]; double[] topTexT = new double[3]; topPlane[0] = new Vector3D(0, Settings.planePointCoef, currentSector.CielingHeight); topPlane[1] = new Vector3D(Settings.planePointCoef, Settings.planePointCoef, currentSector.CielingHeight); topPlane[2] = new Vector3D(Settings.planePointCoef, 0, currentSector.CielingHeight); topTexS[0] = 1; topTexT[1] = - 1; Vector3D[] bottomPlane = new Vector3D[3]; double[] bottomTexS = new double[3]; double[] bottomTexT = new double[3]; bottomPlane[0] = new Vector3D(0, 0, currentSector.FloorHeight); bottomPlane[1] = new Vector3D(Settings.planePointCoef, 0, currentSector.FloorHeight); bottomPlane[2] = new Vector3D(Settings.planePointCoef, Settings.planePointCoef, currentSector.FloorHeight); bottomTexS[0] = 1; bottomTexT[1] = - 1; int nextNode = ssparents[i]; bool leftSide = ssIsLeft[i]; for (int j = 0; j < currentsubsector.NumSegs; j++) { // Iterate through the sidedefs defined by segments of this subsector DSegment currentseg = doomMap.Segments[currentsubsector.FirstSeg + j]; Vector3D start = doomMap.Vertices[currentseg.StartVertex].Vector; Vector3D end = doomMap.Vertices[currentseg.EndVertex].Vector; DLinedef currentLinedef = doomMap.Linedefs[(int) currentseg.Linedef]; Vector3D[] plane = new Vector3D[3]; double[] texS = new double[3]; double[] texT = new double[3]; plane[0] = new Vector3D(start.X, start.Y, ZMin); plane[1] = new Vector3D(end.X, end.Y, ZMin); plane[2] = new Vector3D(end.X, end.Y, ZMax); Vector3D linestart = new Vector3D(doomMap.Vertices[currentLinedef.Start].Vector.X, doomMap.Vertices[currentLinedef.Start].Vector.Y, ZMin); Vector3D lineend = new Vector3D(doomMap.Vertices[currentLinedef.End].Vector.X, doomMap.Vertices[currentLinedef.End].Vector.Y, ZMax); double sideLength = System.Math.Sqrt(System.Math.Pow(start.X - end.X, 2) + System.Math.Pow(start.Y - end.Y, 2)); bool upperUnpegged = !((currentLinedef.Flags[0] & ((sbyte) 1 << 3)) == 0); bool lowerUnpegged = !((currentLinedef.Flags[0] & ((sbyte) 1 << 4)) == 0); texS[0] = (start.X - end.X) / sideLength; texS[1] = (start.Y - end.Y) / sideLength; texS[2] = 0; texT[0] = 0; texT[1] = 0; texT[2] = - 1; double SShift = sideDefShifts[X][subsectorSidedefs[i][j]] - (texS[0] * end.X) - (texS[1] * end.Y); double lowTShift = 0; double highTShift = 0; if (!currentLinedef.OneSided) { DSector otherSideSector; if (doomMap.Sectors[doomMap.Sidedefs[currentLinedef.Left].Sector] == currentSector) { otherSideSector = doomMap.Sectors[doomMap.Sidedefs[currentLinedef.Right].Sector]; } else { otherSideSector = doomMap.Sectors[doomMap.Sidedefs[currentLinedef.Left].Sector]; } if (lowerUnpegged) { lowTShift = otherSideSector.CielingHeight; } else { lowTShift = currentSector.FloorHeight; } if (upperUnpegged) { highTShift = otherSideSector.CielingHeight; } else { highTShift = currentSector.CielingHeight; } lowTShift += sideDefShifts[Y][subsectorSidedefs[i][j]]; highTShift += sideDefShifts[Y][subsectorSidedefs[i][j]]; } MAPBrushSide low = new MAPBrushSide(plane, lowerWallTextures[subsectorSidedefs[i][j]], texS, SShift, texT, lowTShift, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide high = new MAPBrushSide(plane, higherWallTextures[subsectorSidedefs[i][j]], texS, SShift, texT, highTShift, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide mid; MAPBrushSide damage = new MAPBrushSide(plane, "special/trigger", texS, 0, texT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); if (currentLinedef.OneSided) { if (!outsideBrushAlreadyCreated[currentseg.Linedef]) { outsideBrushAlreadyCreated[currentseg.Linedef] = true; double highestCieling = currentSector.CielingHeight; double lowestFloor = currentSector.FloorHeight; if (currentSector.Tag != 0) { double temp = getHighestNeighborCielingHeight(subsectorSectors[i]); if (temp > highestCieling) { highestCieling = temp; } temp = getLowestNeighborFloorHeight(subsectorSectors[i]); if (temp < lowestFloor) { lowestFloor = temp; } } MAPBrush outsideBrush = null; if (lowestFloor <= highestCieling) { outsideBrush = MAPBrush.createFaceBrush(midWallTextures[subsectorSidedefs[i][j]], "special/nodraw", new Vector3D(linestart.X, linestart.Y, ZMin), new Vector3D(lineend.X, lineend.Y, ZMax), sideDefShifts[X][subsectorSidedefs[i][j]], sideDefShifts[Y][subsectorSidedefs[i][j]], lowerUnpegged, currentSector.CielingHeight, currentSector.FloorHeight); } else { outsideBrush = MAPBrush.createFaceBrush(midWallTextures[subsectorSidedefs[i][j]], "special/nodraw", new Vector3D(linestart.X, linestart.Y, lowestFloor), new Vector3D(lineend.X, lineend.Y, highestCieling), sideDefShifts[X][subsectorSidedefs[i][j]], sideDefShifts[Y][subsectorSidedefs[i][j]], lowerUnpegged, currentSector.CielingHeight, currentSector.FloorHeight); } world.Brushes.Add(outsideBrush); } mid = new MAPBrushSide(plane, "special/nodraw", texS, 0, texT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); } else { double midTShift = sideDefShifts[Y][subsectorSidedefs[i][j]]; if (lowerUnpegged) { midTShift += currentSector.FloorHeight; } else { midTShift += currentSector.CielingHeight; } mid = new MAPBrushSide(plane, midWallTextures[subsectorSidedefs[i][j]], texS, SShift, texT, midTShift, 0, 1, 1, 0, "wld_masked", 16, 0); if (!linedefFlagsDealtWith[currentseg.Linedef]) { linedefFlagsDealtWith[currentseg.Linedef] = true; if (!((currentLinedef.Flags[0] & ((sbyte) 1 << 0)) == 0)) { // Flag 0x0001 indicates "solid" but doesn't block bullets. It is assumed for all one-sided. MAPBrush solidBrush = MAPBrush.createFaceBrush("special/clip", "special/clip", linestart, lineend, 0, 0, false, 0, 0); world.Brushes.Add(solidBrush); } else { if (!((currentLinedef.Flags[0] & ((sbyte) 1 << 1)) == 0)) { // Flag 0x0002 indicates "monster clip". MAPBrush solidBrush = MAPBrush.createFaceBrush("special/enemyclip", "special/enemyclip", linestart, lineend, 0, 0, false, 0, 0); world.Brushes.Add(solidBrush); } } } DSidedef otherside = doomMap.Sidedefs[currentLinedef.Left]; if (currentLinedef.Action != 0 && !linedefSpecialsDealtWith[currentseg.Linedef]) { linedefSpecialsDealtWith[currentseg.Linedef] = true; Entity trigger = null; MAPBrush triggerBrush = MAPBrush.createFaceBrush("special/trigger", "special/trigger", linestart, lineend, 0, 0, false, 0, 0); if (doomMap.Version == mapType.TYPE_HEXEN) { bool[] bitset = new bool[16]; for (int k = 0; k < 8; k++) { bitset[k] = !((currentLinedef.Flags[0] & ((sbyte) k << 1)) == 0); } for (int k = 0; k < 8; k++) { bitset[k + 8] = !((currentLinedef.Flags[1] & ((sbyte) k << 1)) == 0); } if (bitset[10] && bitset[11] && !bitset[12]) { // Triggered when "Used" by player trigger = new Entity("func_button"); trigger["spawnflags"] = "1"; if (bitset[9]) { trigger["wait"] = "1"; } else { trigger["wait"] = "-1"; } } else { if (bitset[9]) { // Can be activated more than once trigger = new Entity("trigger_multiple"); trigger["wait"] = "1"; } else { trigger = new Entity("trigger_once"); } } switch (currentLinedef.Action) { case 21: // Floor lower to lowest surrounding floor case 22: // Floor lower to next lowest surrounding floor if (currentLinedef.Arguments[0] != 0) { trigger["target"] = "sector" + currentLinedef.Arguments[0] + "lowerfloor"; } else { trigger["target"] = "sectornum" + otherside.Sector + "lowerfloor"; } break; case 24: // Floor raise to highest surrounding floor case 25: // Floor raise to next highest surrounding floor if (currentLinedef.Arguments[0] != 0) { trigger["target"] = "sector" + currentLinedef.Arguments[0] + "raisefloor"; } else { trigger["target"] = "sectornum" + otherside.Sector + "raisefloor"; } break; case 70: // Teleport trigger = new Entity("trigger_teleport"); if (currentLinedef.Arguments[0] != 0) { trigger["target"] = "teledest" + currentLinedef.Arguments[0]; } else { trigger["target"] = "sector" + currentLinedef.Tag + "teledest"; } break; case 80: // Exec script // This is a toughie. I can't write a script-to-entity converter. trigger["target"] = "script" + currentLinedef.Arguments[0]; trigger["arg0"] = "" + currentLinedef.Arguments[2]; trigger["arg1"] = "" + currentLinedef.Arguments[3]; trigger["arg2"] = "" + currentLinedef.Arguments[4]; break; case 181: // PLANE_ALIGN trigger = null; if (!leftSide) { DSidedef getsector = doomMap.Sidedefs[currentLinedef.Left]; DSector copyheight = doomMap.Sectors[getsector.Sector]; short newHeight = copyheight.FloorHeight; //floorPlane[0]=new Vector3D(0, Settings.getPlanePointCoef(), 2000); //floorPlane[1]=new Vector3D(Settings.getPlanePointCoef(), Settings.getPlanePointCoef(), currentSector.getFloorHeight()); //floorPlane[2]=new Vector3D(Settings.getPlanePointCoef(), 0, currentSector.getFloorHeight()); } else { linedefSpecialsDealtWith[currentseg.Linedef] = false; } break; default: trigger = null; break; } } else { switch (currentLinedef.Action) { case 1: // Use Door. open, wait, close case 31: // Use Door. Open, stay. trigger = new Entity("func_button"); trigger["wait"] = "1"; if (currentLinedef.Action == 31) { trigger["wait"] = "-1"; } trigger["spawnflags"] = "1"; if (doomMap.Sectors[otherside.Sector].Tag == 0) { trigger["target"] = "sectornum" + otherside.Sector + "door"; if (currentLinedef.Action == 1) { sectorTag[otherside.Sector] = - 1; } if (currentLinedef.Action == 31) { sectorTag[otherside.Sector] = - 2; } } else { trigger["target"] = "sector" + doomMap.Sectors[otherside.Sector].Tag + "door"; } break; case 36: // Floor lower to 8 above next lowest neighboring sector case 38: // Floor lower to next lowest neighboring sector trigger = new Entity("trigger_once"); trigger["target"] = "sector" + currentLinedef.Tag + "lowerfloor"; break; case 62: // Floor lower to next lowest neighboring sector, wait 4s, goes back up trigger = new Entity("func_button"); trigger["target"] = "sector" + currentLinedef.Tag + "vator"; trigger["wait"] = "1"; trigger["spawnflags"] = "1"; break; case 63: // Door with button, retriggerable case 103: // Push button, one-time door open stay open trigger = new Entity("func_button"); trigger["target"] = "sector" + currentLinedef.Tag + "door"; trigger["wait"] = "1"; if (currentLinedef.Action == 103) { trigger["wait"] = "-1"; } trigger["spawnflags"] = "1"; break; case 88: // Walkover retriggerable elevator trigger trigger = new Entity("trigger_multiple"); trigger["target"] = "sector" + currentLinedef.Tag + "vator"; break; case 97: // Walkover retriggerable Teleport trigger = new Entity("trigger_teleport"); trigger["target"] = "sector" + currentLinedef.Tag + "teledest"; break; case 109: // Walkover one-time door open stay open trigger = new Entity("trigger_once"); trigger["target"] = "sector" + currentLinedef.Tag + "door"; break; } } if (trigger != null) { trigger.Brushes.Add(triggerBrush); mapFile.Add(trigger); } } } cielingBrush.add(high); midBrush.add(mid); floorBrush.add(low); sectorTriggerBrush.add(damage); } MAPBrushSide roof = new MAPBrushSide(roofPlane, "special/nodraw", roofTexS, 0, roofTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide cieling = new MAPBrushSide(cileingPlane, doomMap.WadName + "/" + currentSector.CielingTexture, cileingTexS, 0, cileingTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide floor = new MAPBrushSide(floorPlane, doomMap.WadName + "/" + currentSector.FloorTexture, floorTexS, 0, floorTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide foundation = new MAPBrushSide(foundationPlane, "special/nodraw", foundationTexS, 0, foundationTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide top = new MAPBrushSide(topPlane, "special/nodraw", topTexS, 0, topTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide bottom = new MAPBrushSide(bottomPlane, "special/nodraw", bottomTexS, 0, bottomTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide invertedFloor = new MAPBrushSide(Plane.flip(floorPlane), "special/trigger", floorTexS, 0, floorTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide damageTop = new MAPBrushSide(new Vector3D[]{floorPlane[0]+new Vector3D(0, 0, 1), floorPlane[1]+new Vector3D(0, 0, 1), floorPlane[2]+new Vector3D(0, 0, 1)}, "special/trigger", floorTexS, 0, floorTexT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); sectorTriggerBrush.add(damageTop); sectorTriggerBrush.add(invertedFloor); midBrush.add(top); midBrush.add(bottom); cielingBrush.add(cieling); cielingBrush.add(roof); floorBrush.add(floor); floorBrush.add(foundation); // Now need to add the data from node subdivisions. Neither segments nor nodes // will completely define a usable brush, but both of them together will. do { DNode currentNode = doomMap.Nodes[nextNode]; Vector3D start; Vector3D end; if (leftSide) { start = currentNode.VecHead+currentNode.VecTail; end = currentNode.VecHead; } else { start = currentNode.VecHead; end = currentNode.VecHead+currentNode.VecTail; } Vector3D[] plane = new Vector3D[3]; double[] texS = new double[3]; double[] texT = new double[3]; // This is somehow always correct. And I'm okay with that. plane[0] = new Vector3D(start.X, start.Y, ZMin); plane[1] = new Vector3D(end.X, end.Y, ZMin); plane[2] = new Vector3D(start.X, start.Y, ZMax); double sideLength = System.Math.Sqrt(System.Math.Pow(start.X - end.X, 2) + System.Math.Pow(start.Y - end.Y, 2)); texS[0] = (start.X - end.X) / sideLength; texS[1] = (start.Y - end.Y) / sideLength; texS[2] = 0; texT[0] = 0; texT[1] = 0; texT[2] = 1; MAPBrushSide low = new MAPBrushSide(plane, "special/nodraw", texS, 0, texT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide high = new MAPBrushSide(plane, "special/nodraw", texS, 0, texT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide mid = new MAPBrushSide(plane, "special/nodraw", texS, 0, texT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); MAPBrushSide damage = new MAPBrushSide(plane, "special/trigger", texS, 0, texT, 0, 0, 1, 1, 0, "wld_lightmap", 16, 0); cielingBrush.add(high); midBrush.add(mid); floorBrush.add(low); sectorTriggerBrush.add(damage); leftSide = nodeIsLeft[nextNode]; nextNode = nodeparents[nextNode]; } while (nextNode != - 1); // 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. int[] badSides = new int[0]; if (!Settings.dontCull) { badSides = MAPBrush.findUnusedPlanes(cielingBrush); // 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 (cielingBrush.NumSides - badSides.Length < 4) { DecompilerThread.OnMessage(this, "WARNING: Plane cull returned less than 4 sides for subsector " + i); badSides = new int[0]; } else { for (int j = badSides.Length - 1; j > - 1; j--) { cielingBrush.delete(badSides[j]); floorBrush.delete(badSides[j]); } } } MAPBrush[] newFloorList = new MAPBrush[sectorFloorBrushes[subsectorSectors[i]].Length + 1]; MAPBrush[] newCielingList = new MAPBrush[sectorCielingBrushes[subsectorSectors[i]].Length + 1]; for (int j = 0; j < sectorFloorBrushes[subsectorSectors[i]].Length; j++) { newFloorList[j] = sectorFloorBrushes[subsectorSectors[i]][j]; newCielingList[j] = sectorCielingBrushes[subsectorSectors[i]][j]; } newFloorList[newFloorList.Length - 1] = floorBrush; newCielingList[newCielingList.Length - 1] = cielingBrush; sectorFloorBrushes[subsectorSectors[i]] = newFloorList; sectorCielingBrushes[subsectorSectors[i]] = newCielingList; bool containsMiddle = false; for (int j = 0; j < midBrush.NumSides; j++) { if (!midBrush[j].Texture.ToUpper().Equals("special/nodraw".ToUpper())) { containsMiddle = true; break; } } if (containsMiddle && currentSector.CielingHeight > currentSector.FloorHeight) { Entity middleEnt = new Entity("func_illusionary"); if (midBrush.NumSides - badSides.Length >= 4) { for (int j = badSides.Length - 1; j > - 1; j--) { midBrush.delete(badSides[j]); } } middleEnt.Brushes.Add(midBrush); mapFile.Add(middleEnt); } Entity hurtMe = new Entity("trigger_hurt"); Entity triggerSecret = new Entity("trigger_bondsecret"); switch (currentSector.Type) { case 4: // 20% damage/s with lighting properties case 11: // 20% damage/s case 16: // 20% damage/s plus end level when player is almost dead hurtMe["dmg"] = "40"; if (!Settings.dontCull) { if (sectorTriggerBrush.NumSides - badSides.Length >= 4) { for (int j = badSides.Length - 1; j > - 1; j--) { sectorTriggerBrush.delete(badSides[j]); } } } hurtMe.Brushes.Add(sectorTriggerBrush); mapFile.Add(hurtMe); break; case 5: // 10% damage/s hurtMe["dmg"] = "20"; if (!Settings.dontCull) { if (sectorTriggerBrush.NumSides - badSides.Length >= 4) { for (int j = badSides.Length - 1; j > - 1; j--) { sectorTriggerBrush.delete(badSides[j]); } } } hurtMe.Brushes.Add(sectorTriggerBrush); mapFile.Add(hurtMe); break; case 7: // 5% damage/s hurtMe["dmg"] = "10"; if (!Settings.dontCull) { if (sectorTriggerBrush.NumSides - badSides.Length >= 4) { for (int j = badSides.Length - 1; j > - 1; j--) { sectorTriggerBrush.delete(badSides[j]); } } } hurtMe.Brushes.Add(sectorTriggerBrush); mapFile.Add(hurtMe); break; case 9: // "secret" triggerSecret["Sound"] = "common/mission_success.wav"; if (!Settings.dontCull) { if (sectorTriggerBrush.NumSides - badSides.Length >= 4) { for (int j = badSides.Length - 1; j > - 1; j--) { sectorTriggerBrush.delete(badSides[j]); } } } triggerSecret.Brushes.Add(sectorTriggerBrush); mapFile.Add(triggerSecret); break; case 10: // 30 seconds after level start, "close" like a door if (currentSector.Tag == 0) { sectorTag[subsectorSectors[i]] = - 3; } break; case 14: // 300 seconds after level start, "open" like a door if (currentSector.Tag == 0) { sectorTag[subsectorSectors[i]] = - 4; } break; default: if (!Settings.dontCull) { if (sectorTriggerBrush.NumSides - badSides.Length >= 4) { for (int j = badSides.Length - 1; j > - 1; j--) { sectorTriggerBrush.delete(badSides[j]); } } } if ((currentSector.Type & 96) != 0) { // "Generalized" pain sectors if ((currentSector.Type & 96) == 32) { hurtMe["dmg"] = "10"; } else { if ((currentSector.Type & 96) == 64) { hurtMe["dmg"] = "20"; } else { if ((currentSector.Type & 96) == 96) { hurtMe["dmg"] = "40"; } } } hurtMe.Brushes.Add(sectorTriggerBrush); mapFile.Add(hurtMe); } if ((currentSector.Type & 128) == 128) { // "Generalized" secret trigger triggerSecret["Sound"] = "common/mission_success.wav"; triggerSecret.Brushes.Add(sectorTriggerBrush); mapFile.Add(triggerSecret); } break; } if((i+1)%onePercent == 0) { parent.OnProgress(this, (i+1)/(double)(doomMap.SubSectors.Count)); } //Settings.setProgress(jobnum, i + 1, doomMap.SubSectors.Count, "Decompiling..."); } // Add the brushes to the map, as world by default, or entities if they are supported. for (int i = 0; i < doomMap.Sectors.Count; i++) { bool[] floorsUsed = new bool[sectorFloorBrushes[i].Length]; bool[] cielingsUsed = new bool[sectorCielingBrushes[i].Length]; if (sectorTag[i] == 0) { for (int j = 0; j < sectorFloorBrushes[i].Length; j++) { world.Brushes.Add(sectorFloorBrushes[i][j]); floorsUsed[j] = true; world.Brushes.Add(sectorCielingBrushes[i][j]); cielingsUsed[j] = true; } } else { if (sectorTag[i] == - 1 || sectorTag[i] == - 2 || sectorTag[i] == - 3 || sectorTag[i] == - 4) { // I'm using this to mean a door with no tag number Entity newDoor = new Entity("func_door"); newDoor["speed"] = "60"; newDoor["angles"] = "270 0 0"; newDoor["spawnflags"] = "256"; newDoor["targetname"] = "sectornum" + i + "door"; if (sectorTag[i] == - 1) { newDoor["wait"] = "4"; } else { if (sectorTag[i] == - 2) { newDoor["wait"] = "-1"; } } if (sectorTag[i] == - 3) { Entity triggerAuto = new Entity("trigger_auto"); triggerAuto["target"] = "sectornum" + i + "door_mm"; Entity multiManager = new Entity("multi_manager"); multiManager["sectornum" + i + "door"] = "30"; mapFile.Add(triggerAuto); mapFile.Add(multiManager); } if (sectorTag[i] == - 4) { newDoor["Spawnflags"] = "1"; Entity triggerAuto = new Entity("trigger_auto"); triggerAuto["target"] = "sectornum" + i + "door_mm"; Entity multiManager = new Entity("multi_manager"); multiManager["sectornum" + i + "door"] = "300"; mapFile.Add(triggerAuto); mapFile.Add(multiManager); } int lowestNeighborCielingHeight = getLowestNeighborCielingHeight(i); int lip = ZMax - lowestNeighborCielingHeight + 4; newDoor["lip"] = "" + lip; for (int j = 0; j < sectorFloorBrushes[i].Length; j++) { cielingsUsed[j] = true; newDoor.Brushes.Add(sectorCielingBrushes[i][j]); } mapFile.Add(newDoor); } else { for (int j = 0; j < doomMap.Linedefs.Count; j++) { DLinedef currentLinedef = doomMap.Linedefs[j]; int linedefTriggerType = currentLinedef.Action; if (doomMap.Version == mapType.TYPE_HEXEN) { switch (linedefTriggerType) { case 21: // Floor lower to lowest neighbor case 22: // Floor lower to nearest lower neighbor // I don't know where retriggerability is determined, or whether or not it goes back up. if (currentLinedef.Arguments[0] == sectorTag[i]) { Entity newFloor = new Entity("func_door"); newFloor["angles"] = "90 0 0"; newFloor["wait"] = "-1"; newFloor["speed"] = "" + currentLinedef.Arguments[1]; if (currentLinedef.Arguments[0] == 0) { newFloor["targetname"] = "sectornum" + i + "lowerfloor"; } else { newFloor["targetname"] = "sector" + currentLinedef.Arguments[0] + "lowerfloor"; } int lowestNeighborFloorHeight; if (linedefTriggerType == 21) { lowestNeighborFloorHeight = getLowestNeighborFloorHeight(i); } else { lowestNeighborFloorHeight = getNextLowestNeighborFloorHeight(i); } if (lowestNeighborFloorHeight == 32768) { lowestNeighborFloorHeight = doomMap.Sectors[i].FloorHeight; } int lip = ZMin - lowestNeighborFloorHeight; newFloor["lip"] = "" + System.Math.Abs(lip); for (int k = 0; k < sectorFloorBrushes[i].Length; k++) { if (!floorsUsed[k]) { floorsUsed[k] = true; newFloor.Brushes.Add(sectorFloorBrushes[i][k]); } } mapFile.Add(newFloor); } break; case 24: // Floor raise to highest neighbor case 25: // Floor raise to nearest higher neighbor // I don't know where retriggerability is determined, or whether or not it goes back up. if (currentLinedef.Arguments[0] == sectorTag[i]) { Entity newFloor = new Entity("func_door"); newFloor["angles"] = "270 0 0"; newFloor["wait"] = "-1"; newFloor["speed"] = "" + currentLinedef.Arguments[1]; if (currentLinedef.Arguments[0] == 0) { newFloor["targetname"] = "sectornum" + i + "raisefloor"; } else { newFloor["targetname"] = "sector" + currentLinedef.Arguments[0] + "raisefloor"; } int highestNeighborFloorHeight; if (linedefTriggerType == 24) { highestNeighborFloorHeight = getHighestNeighborFloorHeight(i); } else { highestNeighborFloorHeight = getNextHighestNeighborFloorHeight(i); } if (highestNeighborFloorHeight == - 32768) { highestNeighborFloorHeight = doomMap.Sectors[i].FloorHeight; } int lip = ZMin - highestNeighborFloorHeight; newFloor["lip"] = "" + System.Math.Abs(lip); for (int k = 0; k < sectorFloorBrushes[i].Length; k++) { if (!floorsUsed[k]) { floorsUsed[k] = true; newFloor.Brushes.Add(sectorFloorBrushes[i][k]); } } mapFile.Add(newFloor); } break; } } else { if (currentLinedef.Tag == sectorTag[i]) { switch (linedefTriggerType) { case 36: // Line crossed, floor lowers, stays 8 above next lowest case 38: // Line crossed, floor lowers, stays at next lowest Entity newFloor = new Entity("func_door"); newFloor["speed"] = "120"; newFloor["angles"] = "90 0 0"; newFloor["targetname"] = "sector" + sectorTag[i] + "lowerfloor"; newFloor["wait"] = "-1"; int lowestNeighborFloorHeight = getLowestNeighborFloorHeight(i); int lip = ZMin - lowestNeighborFloorHeight; if (linedefTriggerType == 36) { lip -= 8; } newFloor["lip"] = "" + System.Math.Abs(lip); for (int k = 0; k < sectorFloorBrushes[i].Length; k++) { if (!floorsUsed[k]) { floorsUsed[k] = true; newFloor.Brushes.Add(sectorFloorBrushes[i][k]); } } mapFile.Add(newFloor); break; case 63: // Push button, door opens, waits 4s, closes case 103: // Push button, door opens, stays case 109: // Cross line, door opens, stays Entity newDoor = new Entity("func_door"); newDoor["speed"] = "60"; newDoor["angles"] = "270 0 0"; newDoor["targetname"] = "sector" + sectorTag[i] + "door"; newDoor["wait"] = "-1"; if (sectorTag[i] == 63) { newDoor["wait"] = "4"; } int lowestNeighborCielingHeight = getLowestNeighborCielingHeight(i); lip = ZMax - lowestNeighborCielingHeight + 4; newDoor["lip"] = "" + lip; for (int k = 0; k < sectorFloorBrushes[i].Length; k++) { if (!cielingsUsed[k]) { cielingsUsed[k] = true; newDoor.Brushes.Add(sectorCielingBrushes[i][k]); } } mapFile.Add(newDoor); break; case 62: // Push button, Elevator goes down to lowest, wait 4s, goes up case 88: // Elevator goes down to lowest, wait 4s, goes up Entity newVator = new Entity("func_door"); newVator["speed"] = "120"; newVator["angles"] = "90 0 0"; newVator["targetname"] = "sector" + sectorTag[i] + "vator"; newVator["wait"] = "4"; lowestNeighborFloorHeight = getLowestNeighborFloorHeight(i); lip = System.Math.Abs(ZMin - lowestNeighborFloorHeight); newVator["lip"] = "" + lip; for (int k = 0; k < sectorFloorBrushes[i].Length; k++) { if (!floorsUsed[k]) { newVator.Brushes.Add(sectorFloorBrushes[i][k]); floorsUsed[k] = true; } } mapFile.Add(newVator); break; default: // I'd like to not use this evenutally, all the trigger types ought to be handled DecompilerThread.OnMessage(this, "WARNING: Unimplemented linedef trigger type " + linedefTriggerType + " for sector " + i + " tagged " + sectorTag[i]); for (int k = 0; k < sectorFloorBrushes[i].Length; k++) { if (!floorsUsed[k]) { world.Brushes.Add(sectorFloorBrushes[i][k]); floorsUsed[k] = true; } if (!cielingsUsed[k]) { world.Brushes.Add(sectorCielingBrushes[i][k]); cielingsUsed[k] = true; } } break; } } } } } } for (int j = 0; j < sectorFloorBrushes[i].Length; j++) { if (!cielingsUsed[j]) { world.Brushes.Add(sectorCielingBrushes[i][j]); } if (!floorsUsed[j]) { world.Brushes.Add(sectorFloorBrushes[i][j]); } } } // Convert THINGS for (int i = 0; i < doomMap.Things.Count; i++) { DThing currentThing = doomMap.Things[i]; // To find the true height of a thing, I need to iterate through nodes until I come to a subsector // definition. Then I need to use the floor height of the sector that subsector belongs to. Vector3D origin = currentThing.Origin; int subsectorIndex = doomMap.Nodes.Count - 1; while (subsectorIndex >= 0) { // Once child is negative, subsector is found DNode currentNode = doomMap.Nodes[subsectorIndex]; Vector3D start = currentNode.VecHead; Vector3D end = currentNode.VecHead+currentNode.VecTail; Plane currentPlane = new Plane(start, end, new Vector3D(start.X, start.Y, 1)); if (currentPlane.distance(origin) < 0) { subsectorIndex = currentNode.Child1; } else { subsectorIndex = currentNode.Child2; } } subsectorIndex += 32768; int sectorIndex = subsectorSectors[subsectorIndex]; DSector thingSector = doomMap.Sectors[sectorIndex]; if (origin.Z == 0) { origin.Z=thingSector.FloorHeight; } Entity thing = null; // Things from both Doom. Currently converting to appropriate Doom 3 entities. switch (currentThing.ClassNum) { case 1: // Single player spawn case 2: // coop case 3: // coop case 4: // coop thing = new Entity("info_player_start"); if (currentThing.ClassNum > 1) { thing["targetname"] = "coopspawn" + currentThing.ClassNum; } playerStartOrigin = origin.X + " " + origin.Y + " " + (origin.Z + 36); thing["origin"] = playerStartOrigin; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 11: // Deathmatch spawn thing = new Entity("info_player_deathmatch"); thing["origin"] = origin.X + " " + origin.Y + " " + (origin.Z + 36); thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 14: // Teleport destination thing = new Entity("info_teleport_destination"); if (currentThing.ID != 0) { thing["targetname"] = "teledest" + currentThing.ID; } else { thing["targetname"] = "sector" + thingSector.Tag + "teledest"; } thing["origin"] = origin.X + " " + origin.Y + " " + (origin.Z + 36); thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 17: // Big cell pack thing = new Entity("ammo_cells_large"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 82: // Super shotgun thing = new Entity("weapon_shotgun_double"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2001: // Shotgun thing = new Entity("weapon_shotgun"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2002: // Chaingun thing = new Entity("weapon_chaingun"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2003: // Rocket launcher thing = new Entity("weapon_rocketlauncher"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2004: // Plasma gun thing = new Entity("weapon_plasmagun"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2005: // Chainsaw thing = new Entity("weapon_chainsaw"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2006: // BFG9000 thing = new Entity("weapon_bfg"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2007: // Ammo clip thing = new Entity("ammo_clip_small"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2008: // Shotgun shells thing = new Entity("ammo_shells_small"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2010: // Rocket thing = new Entity("ammo_rockets_small"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2046: // Box of Rockets thing = new Entity("ammo_rockets_large"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2047: // Cell pack thing = new Entity("ammo_cells_small"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2048: // Box of ammo thing = new Entity("ammo_bullets_large"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; case 2049: // Box of shells thing = new Entity("ammo_shells_large"); thing["origin"] = origin.X + " " + origin.Y + " " + origin.Z; thing["angles"] = "0 " + currentThing.Angle + " 0"; break; } if (thing != null) { mapFile.Add(thing); } } Entity playerequip = new Entity("game_player_equip"); playerequip["weapon_pistol"] = "1"; playerequip["origin"] = playerStartOrigin; mapFile.Add(playerequip); parent.OnProgress(this, 1.0); return mapFile; }
// -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); } }
// -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); } }
// -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); } }
// -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; MAPBrushSide[] brushSides = new MAPBrushSide[numSides]; bool isDetail = false; if (currentEntity == 0 && !Settings.noDetail && (brush.Contents[3] & ((sbyte)1 << 3)) != 0) { isDetail = true; } MAPBrush mapBrush = new MAPBrush(numBrshs, currentEntity, isDetail); if (currentEntity == 0 && !Settings.noWater && (brush.Contents[0] & ((sbyte)1 << 5)) != 0) { mapBrush.Water = true; } //DecompilerThread.OnMessage(this, ": " + numSides + " sides, detail: " + isDetail); for (int i = 0; i < numSides; i++) { // For each side of the brush BrushSide currentSide = BSPObject.BrushSides[firstSide + i]; if (currentSide.isBevel() == 0) { // Bevel sides are evil Vector3D[] plane = new Vector3D[3]; // Three points define a plane. All I have to do is find three points on that plane. 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 bool isDuplicate = false; /* TODO: We sure don't want duplicate planes (though this is already handled by the MAPBrush class). Make sure neither checked side is bevel. * for(int j=i+1;j<numSides;j++) { // For each subsequent side of the brush * if(currentPlane.equals(BSPObject.Planes.getPlane(BSPObject.getBrushSides()[firstSide+j).getPlane()))) { * DecompilerThread.OnMessage(this, "WARNING: Duplicate planes in a brush, sides "+i+" and "+j,Settings.VERBOSITY_WARNINGS); * isDuplicate=true; * } * }*/ if (!isDuplicate) { TexInfo currentTexInfo = null; string texture = "tools/toolsclip"; if (currentSide.Texture > -1) { currentTexInfo = BSPObject.TexInfo[currentSide.Texture]; } else { int dataIndex = BSPObject.findTexDataWithTexture("tools/toolsclip"); if (dataIndex >= 0) { currentTexInfo = new TexInfo(new Vector3D(0, 0, 0), 0, new Vector3D(0, 0, 0), 0, 0, dataIndex); } } if (currentTexInfo != null) { SourceTexData currentTexData; if (currentTexInfo.Texture >= 0) { // I've only found one case where this is a problem: c2a3a in HL Source. Don't know why. currentTexData = BSPObject.TexDatas[currentTexInfo.Texture]; texture = BSPObject.Textures.getTextureAtOffset((uint)BSPObject.TexTable[currentTexData.StringTableIndex]); } else { texture = "tools/toolsskip"; } } double[] textureU = new double[3]; double[] textureV = new double[3]; double textureShiftU = 0; double textureShiftV = 0; double texScaleU = 1; double texScaleV = 1; // Get the lengths of the axis vectors if ((texture.Length > 6 && texture.Substring(0, (6) - (0)).ToUpper().Equals("tools/".ToUpper())) || currentTexInfo == null) { // Tools textured faces do not maintain their own texture axes. Therefore, an arbitrary axis is // used in the compiled map. When decompiled, these axes might smear the texture on the face. Fix that. Vector3D[] axes = TexInfo.textureAxisFromPlane(currentPlane); textureU = axes[0].Point; textureV = axes[1].Point; } else { 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. 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)currentTexInfo.SAxis.X / SAxisLength); textureU[1] = ((double)currentTexInfo.SAxis.Y / SAxisLength); textureU[2] = ((double)currentTexInfo.SAxis.Z / SAxisLength); double originShiftU = (((double)currentTexInfo.SAxis.X / SAxisLength) * origin[X] + ((double)currentTexInfo.SAxis.Y / SAxisLength) * origin[Y] + ((double)currentTexInfo.SAxis.Z / SAxisLength) * origin[Z]) / texScaleU; //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'" textureShiftU = (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 = (((double)currentTexInfo.TAxis.X / TAxisLength) * origin[X] + ((double)currentTexInfo.TAxis.Y / TAxisLength) * origin[Y] + ((double)currentTexInfo.TAxis.Z / TAxisLength) * origin[Z]) / texScaleV; //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'" textureShiftV = (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. 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(currentPlane, 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); } }