/// <summary> /// Looks at the information in the passed <paramref name="face"/> and tries to find the triangle defined /// by <paramref name="face"/> with the greatest area. If <paramref name="face"/> does not reference any /// vertices then we generate a triangle through the referenced <see cref="Plane"/> instead. /// </summary> /// <param name="face">The <see cref="Face"/> to find a triangle for.</param> /// <returns>Three points defining a triangle which define the plane which <paramref name="face"/> lies on.</returns> private Vector3d[] GetPointsForFace(Face face, BrushSide brushSide) { Vector3d[] ret; if (face.numVertices > 2) { ret = new Vector3d[3]; double bestArea = 0; for (int i = 0; i < face.numIndices / 3; ++i) { Vector3d[] temp = new Vector3d[] { _bsp.vertices[(int)(face.firstVertex + _bsp.indices[face.firstIndex + (i * 3)])].position, _bsp.vertices[(int)(face.firstVertex + _bsp.indices[face.firstIndex + 1 + (i * 3)])].position, _bsp.vertices[(int)(face.firstVertex + _bsp.indices[face.firstIndex + 2 + (i * 3)])].position }; double area = Vector3d.SqrTriangleArea(temp[0], temp[1], temp[2]); if (area > bestArea) { bestArea = area; ret = temp; } } if (bestArea > 0.001) { return(ret); } } if (face.numEdges > 0) { // TODO: Edges = triangles } if (face.plane >= 0 && face.plane < _bsp.planes.Count) { ret = _bsp.planes[face.plane].GenerateThreePoints(); } else if (brushSide.plane >= 0 && brushSide.plane < _bsp.planes.Count) { ret = _bsp.planes[brushSide.plane].GenerateThreePoints(); } else { _master.Print("WARNING: Brush side with no points!"); return(new Vector3d[3]); } return(ret); }
/// <summary> /// Processes a <see cref="BrushSide"/> into a state where it can be output into a file that map editors can read. /// </summary> /// <param name="brushSide">The <see cref="BrushSide"/> to process.</param> /// <param name="worldPosition">The position of the parent <see cref="Entity"/> in the world. This is important for calculating UVs on solids.</param> /// <param name="sideIndex">The index of this side reference in the parent <see cref="Brush"/>. Important for Call of Duty series maps, since /// the first six <see cref="BrushSide"/>s in a <see cref="Brush"/> don't contain <see cref="Plane"/> references.</param> /// <returns>The processed <see cref="MAPBrushSode"/> object, to be added to a <see cref="Brush"/> object.</returns> private MAPBrushSide ProcessBrushSide(BrushSide brushSide, Vector3d worldPosition, int sideIndex) { if (brushSide.bevel) { return(null); } MAPBrushSide mapBrushSide; // The things we'll need to define a .MAP brush side string texture; string material = "wld_lightmap"; TextureInfo texInfo; Vector3d[] threePoints; Plane plane; int flags = 0; // If we have a face reference here, let's use it! if (brushSide.face >= 0) { Face face = _bsp.faces[brushSide.face]; // In Nightfire, faces with "256" flag set should be ignored if ((face.flags & (1 << 8)) != 0) { return(null); } texture = (_master.settings.replace512WithNull && (face.flags & (1 << 9)) != 0) ? "**nulltexture**" : _bsp.textures[face.texture].name; threePoints = GetPointsForFace(face, brushSide); if (face.plane >= 0 && face.plane < _bsp.planes.Count) { plane = _bsp.planes[face.plane]; } else if (brushSide.plane >= 0 && brushSide.plane < _bsp.planes.Count) { plane = _bsp.planes[brushSide.plane]; } else { plane = new Plane(0, 0, 0, 0); } if (_bsp.texInfo != null) { texInfo = _bsp.texInfo[face.textureInfo]; } else { Vector3d[] newAxes = TextureInfo.TextureAxisFromPlane(plane); texInfo = new TextureInfo(newAxes[0], newAxes[1], Vector2d.zero, Vector2d.one, flags, -1, 0); } flags = _master.settings.noFaceFlags ? 0 : face.flags; if (face.material >= 0) { material = _bsp.materials[face.material].name; } } else { // TODO: This is awful. Let's rework the enum to have internal ways to check engine forks. if (_bsp.version == MapType.CoD || _bsp.version == MapType.CoD2 || _bsp.version == MapType.CoD4) { switch (sideIndex) { case 0: { // XMin plane = new Plane(-1, 0, 0, -brushSide.dist); break; } case 1: { // XMax plane = new Plane(1, 0, 0, brushSide.dist); break; } case 2: { // YMin plane = new Plane(0, -1, 0, -brushSide.dist); break; } case 3: { // YMax plane = new Plane(0, 1, 0, brushSide.dist); break; } case 4: { // ZMin plane = new Plane(0, 0, -1, -brushSide.dist); break; } case 5: { // ZMax plane = new Plane(0, 0, 1, brushSide.dist); break; } default: { plane = _bsp.planes[brushSide.plane]; break; } } } else { plane = _bsp.planes[brushSide.plane]; } threePoints = plane.GenerateThreePoints(); if (brushSide.texture >= 0) { // TODO: This is awful. Let's rework the enum to have internal ways to check engine forks. if (_bsp.version == MapType.Source17 || _bsp.version == MapType.Source18 || _bsp.version == MapType.Source19 || _bsp.version == MapType.Source20 || _bsp.version == MapType.Source21 || _bsp.version == MapType.Source22 || _bsp.version == MapType.Source23 || _bsp.version == MapType.Source27 || _bsp.version == MapType.Vindictus || _bsp.version == MapType.DMoMaM || _bsp.version == MapType.L4D2 || _bsp.version == MapType.TacticalInterventionEncrypted || _bsp.version == MapType.Titanfall) { texInfo = _bsp.texInfo[brushSide.texture]; TextureData currentTexData; // I've only found one case where this is bad: c2a3a in HL Source. Don't know why. if (texInfo.texture >= 0) { currentTexData = _bsp.texDatas[texInfo.texture]; texture = _bsp.textures.GetTextureAtOffset((uint)_bsp.texTable[currentTexData.stringTableIndex]); } else { texture = "**skiptexture**"; } } else { Texture textureDef = _bsp.textures[brushSide.texture]; texture = textureDef.name; texInfo = textureDef.texAxes; } } else { Vector3d[] newAxes = TextureInfo.TextureAxisFromPlane(plane); texInfo = new TextureInfo(newAxes[0], newAxes[1], Vector2d.zero, Vector2d.one, flags, -1, 0); texture = "**cliptexture**"; } } TextureInfo outputTexInfo; if (texInfo != null) { outputTexInfo = texInfo.BSP2MAPTexInfo(worldPosition); } else { Vector3d[] newAxes = TextureInfo.TextureAxisFromPlane(plane); outputTexInfo = new TextureInfo(newAxes[0], newAxes[1], Vector2d.zero, Vector2d.one, 0, -1, 0); } mapBrushSide = new MAPBrushSide() { vertices = threePoints, plane = plane, texture = texture, textureInfo = outputTexInfo, material = material, lgtScale = 16, lgtRot = 0 }; return(mapBrushSide); }
// -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); } }
// METHODS public void readBSP() { try { byte[] theLump = new byte[0]; byte[] vis = new byte[0]; BSPObject = new BSP(BSPFile.FullName); Console.WriteLine("Opening " + BSPFile.FullName); for (int i = 0; i < 18; i++) { try { theLump = readLumpNum(i); switch (i) { case 0: BSPObject.Entities = Entity.createLump(theLump); break; case 1: BSPObject.Planes = Plane.createLump(theLump); break; case 2: BSPObject.Textures = Texture.createLump(theLump); break; case 3: BSPObject.Materials = Texture.createLump(theLump); break; case 4: BSPObject.Vertices = Vertex.createLump(theLump); break; case 5: BSPObject.Normals = new Lump <LumpObject>(theLump.Length, 0); break; case 6: BSPObject.Indices = new NumList(theLump, NumList.dataType.UINT); break; case 7: vis = theLump; break; case 8: BSPObject.Nodes = Node.createLump(theLump); break; case 9: BSPObject.Faces = Face.createLump(theLump); break; case 10: BSPObject.Lightmaps = new NumList(theLump, NumList.dataType.UBYTE); break; case 11: BSPObject.Leaves = Leaf.createLump(theLump); break; case 12: BSPObject.MarkSurfaces = new NumList(theLump, NumList.dataType.UINT); break; case 13: BSPObject.MarkBrushes = new NumList(theLump, NumList.dataType.UINT); break; case 14: BSPObject.Models = Model.createLump(theLump); break; case 15: BSPObject.Brushes = Brush.createLump(theLump); break; case 16: BSPObject.BrushSides = BrushSide.createLump(theLump); break; case 17: BSPObject.TexInfo = TexInfo.createLump(theLump); break; } } catch { dumpLump(theLump); } } try { int visLength = BSPObject.Leaves[2].PVS; if (visLength > 0 && vis.Length > 0) { BSPObject.Vis = new Lump <LumpObject>(vis, visLength); } } catch (ArgumentOutOfRangeException) {; } if (BSPObject.Vis == null) { BSPObject.Vis = new Lump <LumpObject>(0, 0); } BSPObject.printBSPReport(); } catch (System.IO.IOException) { Console.WriteLine("Unable to access BSP file! Is it open in another program?"); } br.Close(); }
// -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); } }
// -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); } }