private byte[] brushToByteArray(MAPBrush inputData, int num) { if (inputData.NumSides < 4) { // Can't create a brush with less than 4 sides Console.WriteLine("WARNING: Tried to create brush from " + inputData.NumSides + " sides!"); return(new byte[0]); } string brush = "{ // Brush " + num + (char)0x0D + (char)0x0A; if ((inputData.Detail) && currentEntity == 0) { brush += ("\"BRUSHFLAGS\" \"DETAIL\"" + (char)0x0D + (char)0x0A); } for (int i = 0; i < inputData.NumSides; i++) { brush += (brushSideToString(inputData[i]) + (char)0x0D + (char)0x0A); } brush += ("}" + (char)0x0D + (char)0x0A); if (brush.Length < 45) { // Any brush this short contains no sides. Console.WriteLine("WARNING: Brush with no sides being written! Oh no!"); return(new byte[0]); } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte)brush[i]; } return(brushbytes); } }
private byte[] brushToByteArray(MAPBrush inData, int num) { if (inData.Patch != null) { return(patchToByteArray(inData.Patch, num)); } if (inData.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inData.NumSides + " sides!"); return(new byte[0]); } string brush = "// brush " + num + (char)0x0D + (char)0x0A + "{" + (char)0x0D + (char)0x0A; for (int i = 0; i < inData.NumSides; i++) { brush += (brushSideToString(inData[i], (inData.Detail || inData[0].Displacement != null)) + (char)0x0D + (char)0x0A); } brush += ("}" + (char)0x0D + (char)0x0A); if (brush.Length < 45) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return(new byte[0]); } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte)brush[i]; } return(brushbytes); } }
/// <summary> /// Processes a <see cref="Brush"/> into a state where it can be output into a file that map editors can read. /// </summary> /// <param name="brush">The <see cref="Brush"/> 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> /// <returns>The processed <see cref="MAPBrush"/> object, to be added to an <see cref="Entity"/> object.</returns> private MAPBrush ProcessBrush(Brush brush, Vector3d worldPosition) { List <BrushSide> sides; // CoD BSPs store brush sides sequentially so the brush structure doesn't reference a first side. if (brush.firstSide < 0) { sides = _bsp.brushSides.GetRange(_currentSideIndex, brush.numSides); _currentSideIndex += brush.numSides; } else { sides = _bsp.GetReferencedObjects <BrushSide>(brush, "brushSides"); } MAPBrush mapBrush = new MAPBrush(); mapBrush.isDetail = brush.IsDetail(_bsp); mapBrush.isWater = brush.IsWater(_bsp); mapBrush.isManVis = brush.IsManVis(_bsp); int sideNum = 0; foreach (BrushSide side in sides) { MAPBrushSide mapBrushSide = ProcessBrushSide(side, worldPosition, sideNum); if (mapBrushSide != null) { mapBrush.sides.Add(mapBrushSide); } ++sideNum; } return(mapBrush); }
/// <summary> /// Process the data in a <see cref="MAPBrush"/> into the passed <see cref="StringBuilder"/>. /// </summary> /// <param name="brush">The <see cref="MAPBrush"/> to process.</param> /// <param name="index">The index of <see cref="MAPBrush"/> entity in the <see cref="Entity"/>.</param> /// <param name="sb">A <see cref="StringBuilder"/> object to append processed data from <paramref name="brush"/> to.</param> private void ParseBrush(MAPBrush brush, int index, StringBuilder sb) { // Unsupported features. Ignore these completely. if (brush.patch != null || brush.ef2Terrain != null || brush.mohTerrain != null) { return; } if (brush.sides.Count < 4) { // Can't create a brush with less than 4 sides _master.Print("WARNING: Tried to create brush from " + brush.sides.Count + " sides!"); return; } sb.Append("{ // Brush ") .Append(index.ToString()) .Append("\r\n"); if (brush.isDetail) { sb.Append("\"BRUSHFLAGS\" \"DETAIL\"\r\n"); } foreach (MAPBrushSide brushSide in brush.sides) { ParseBrushSide(brushSide, sb); } sb.Append("}\r\n"); }
/// <summary> /// Process the data in a <see cref="MAPBrush"/> into the passed <see cref="StringBuilder"/>. /// </summary> /// <param name="brush">The <see cref="MAPBrush"/> to process.</param> /// <param name="index">The index of <see cref="MAPBrush"/> entity in the <see cref="Entity"/>.</param> /// <param name="sb">A <see cref="StringBuilder"/> object to append processed data from <paramref name="brush"/> to.</param> private void ParseBrush(MAPBrush brush, int index, StringBuilder sb) { // Unsupported features. Ignore these completely. if (brush.ef2Terrain != null) { return; } if (brush.sides.Count < 4 && brush.patch == null && brush.mohTerrain == null) { // Can't create a brush with less than 4 sides _master.Print("WARNING: Tried to create brush from " + brush.sides.Count + " sides!"); return; } sb.Append("// Brush ") .Append(index.ToString()) .Append("\r\n"); if (brush.patch != null) { ParsePatch(brush.patch, sb); } else if (brush.mohTerrain != null) { ParseTerrain(brush.mohTerrain, sb); } else { sb.Append("{\r\n"); foreach (MAPBrushSide brushSide in brush.sides) { ParseBrushSide(brushSide, brush.isDetail, sb); } sb.Append("}\r\n"); } }
/// <summary> /// For <paramref name="brush"/>, sets the top <see cref="MAPBrushSide"/>'s texture /// to a water texture and sets all others to nodraw. /// </summary> /// <param name="brush">The <see cref="MAPBrush"/> to make into a water brush.</param> private void ConvertToWater(MAPBrush brush) { foreach (MAPBrushSide side in brush.sides) { side.texture = "common/water"; } }
/// <summary> /// For <paramref name="brush"/>, sets the top <see cref="MAPBrushSide"/>'s texture /// to a water texture and sets all others to nodraw. /// </summary> /// <param name="brush">The <see cref="MAPBrush"/> to make into a water brush.</param> private void ConvertToWater(MAPBrush brush) { foreach (MAPBrushSide side in brush.sides) { side.texture = "Shaders/liquids/clear_calm1"; } }
/// <summary> /// Sends <paramref name="entity"/> to be postprocessed into the appropriate method based on version. /// </summary> /// <param name="entity"><see cref="Entity"/> to postprocess.</param> private void PostProcessEntity(Entity entity) { if (entity.IsBrushBased) { Vector3 origin = entity.Origin; entity.Remove("origin"); entity.Remove("model"); if (origin != Vector3.Zero) { // If this brush has an origin MAPBrush neworiginBrush = MAPBrushExtensions.CreateCube(new Vector3(-16, -16, -16), new Vector3(16, 16, 16), "common/origin"); entity.brushes.Add(neworiginBrush); } foreach (MAPBrush brush in entity.brushes) { brush.Translate(origin); } } switch (_version) { case MapType.Nightfire: { PostProcessNightfireEntity(entity); break; } } }
private byte[] brushToByteArray(MAPBrush inBrush, int num) { if (inBrush.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inBrush.NumSides + " sides!"); return(new byte[0]); } string brush = "// primitive " + num + (char)0x0A + "{" + (char)0x0A + " brushDef3" + (char)0x0A + " {" + (char)0x0A; for (int i = 0; i < inBrush.NumSides; i++) { brush += (" " + brushSideToString(inBrush[i]) + (char)0x0A); } brush += (" }" + (char)0x0A + "}" + (char)0x0A); if (brush.Length < 58) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return(new byte[0]); } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte)brush[i]; } return(brushbytes); } }
/// <summary> /// For <paramref name="brush"/>, sets the top <see cref="MAPBrushSide"/>'s texture /// to a water texture and sets all others to nodraw. /// </summary> /// <param name="brush">The <see cref="MAPBrush"/> to make into a water brush.</param> private void ConvertToWater(MAPBrush brush) { foreach (MAPBrushSide side in brush.sides) { side.texture = "test/omaha_pjspick5"; } }
public MAPBrush(MAPBrush sides) { brushnum = sides.Brushnum; entnum = sides.Entnum; this.isDetailBrush = sides.Detail; this.isWaterBrush = sides.Water; this.sides = new MAPBrushSide[sides.NumSides]; for (int i = 0; i < sides.NumSides; i++) { this.sides[i] = new MAPBrushSide(sides[i]); } }
/// <summary> /// Postprocesser to convert an <see cref="Entity"/> from a Call of Duty BSP to one for Gearcraft. /// </summary> /// <param name="entity">The <see cref="Entity"/> to parse.</param> private void PostProcessCoDEntity(Entity entity) { if (entity.IsBrushBased) { Vector3 origin = entity.Origin; entity.Remove("origin"); entity.Remove("model"); if (entity["classname"].ToUpper().Equals("func_rotating".ToUpper())) { // TODO: What entities require origin brushes in CoD? if (origin == Vector3.Zero) { // If this brush uses the "origin" attribute MAPBrush neworiginBrush = MAPBrushExtensions.CreateCube(new Vector3(-16, -16, -16), new Vector3(16, 16, 16), "special/origin"); entity.brushes.Add(neworiginBrush); } } foreach (MAPBrush brush in entity.brushes) { brush.Translate(origin); } } switch (entity["classname"].ToLower()) { case "light": { entity["_light"] = "255 255 255 " + entity["light"]; entity.Remove("light"); break; } case "mp_teamdeathmatch_spawn": case "mp_deathmatch_spawn": { entity["classname"] = "info_player_deathmatch"; break; } case "mp_searchanddestroy_spawn_allied": { entity["classname"] = "info_player_ctfspawn"; entity["team_no"] = "1"; entity.Remove("model"); break; } case "mp_searchanddestroy_spawn_axis": { entity["classname"] = "info_player_ctfspawn"; entity["team_no"] = "2"; entity.Remove("model"); break; } } }
// SimpleCorrectPlanes(MAPBrush, float) // Uses all sides' defined points to ensure all planes are flipped correctly. public static MAPBrush SimpleCorrectPlanes(MAPBrush brush) { //DecompilerThread.OnMessage(this, "Plane flip. Method: simple"); // Find midpoint of triangle, and use that to normalise all other planes. int triIndex = -1; // So we know which plane the triangle belongs to. Vector3D[] triangle = new Vector3D[0]; // This'll cause an exception if the loop fails for (int i = 0; i < brush.NumSides; i++) { if (brush[i].DefinedByTriangle) { triangle = brush[i].Triangle; triIndex = i; break; } } double[] normPoint = new double[] { (triangle[0].X + triangle[1].X + triangle[2].X) / 3.0, (triangle[0].Y + triangle[1].Y + triangle[2].Y) / 3.0, (triangle[0].Z + triangle[1].Z + triangle[2].Z) / 3.0 }; Plane[] allplanes = brush.Planes; //bool foundTriPlane = false; for (int iPlane = 0; iPlane < allplanes.Length; iPlane++) { // For each plane double dist = allplanes[iPlane].distance(normPoint); // calculate distance from point if (iPlane == triIndex) { // if triangle's plane & normals point in opposite direction Vector3D tmp = new Plane(triangle[2], triangle[0], triangle[1]).Normal; if (allplanes[iPlane].Normal * (new Plane(triangle[2], triangle[0], triangle[1]).Normal) < 0) { brush[iPlane].flipSide(); // flip plane } } else { // or if not the triangle's plane if (dist > Settings.precision) { // if point is on positive (outside) side of plane brush[iPlane].flipSide(); // flip plane } } } return(brush); }
/// <summary> /// Postprocesser to convert an <see cref="Entity"/> from a Nightfire BSP to one for Gearcraft. /// </summary> /// <param name="entity">The <see cref="Entity"/> to parse.</param> private void PostProcessNightfireEntity(Entity entity) { if (entity.brushBased) { Vector3d origin = entity.origin; entity.Remove("origin"); entity.Remove("model"); if (origin != Vector3d.zero) { // If this brush has an origin MAPBrush neworiginBrush = MAPBrushExtensions.CreateCube(new Vector3d(-16, -16, -16), new Vector3d(16, 16, 16), "special/origin"); entity.brushes.Add(neworiginBrush); } foreach (MAPBrush brush in entity.brushes) { brush.Translate(origin); } } }
public virtual Entity ent42ToEntM510(Entity inputData) { if (inputData.BrushBased) { Vector3D origin = inputData.Origin; inputData.Attributes.Remove("origin"); inputData.Attributes.Remove("model"); if ((origin[0] != 0 || origin[1] != 0 || origin[2] != 0) && !Settings.noOriginBrushes) { // If this brush uses the "origin" attribute MAPBrush newOriginBrush = MAPBrush.createBrush(new Vector3D(-Settings.originBrushSize, -Settings.originBrushSize, -Settings.originBrushSize), new Vector3D(Settings.originBrushSize, Settings.originBrushSize, Settings.originBrushSize), "special/origin"); inputData.Brushes.Add(newOriginBrush); } for (int i = 0; i < inputData.Brushes.Count; i++) { MAPBrush currentBrush = inputData.Brushes[i]; currentBrush.translate(new Vector3D(origin)); } } return(inputData); }
/// <summary> /// Process the data in a <see cref="MAPBrush"/> into the passed <see cref="StringBuilder"/>. /// </summary> /// <param name="brush">The <see cref="MAPBrush"/> to process.</param> /// <param name="sb">A <see cref="StringBuilder"/> object to append processed data from <paramref name="brush"/> to.</param> private void ParseBrush(MAPBrush brush, StringBuilder sb) { // Unsupported features. Ignore these completely. if (brush.patch != null || brush.ef2Terrain != null || brush.mohTerrain != null) { return; } if (brush.sides.Count < 4) { // Can't create a brush with less than 4 sides _master.Print("WARNING: Tried to create brush from " + brush.sides.Count + " sides!"); return; } sb.Append("\tsolid\r\n\t{\r\n\t\t\"id\" \"") .Append(_nextID) .Append("\"\r\n"); foreach (MAPBrushSide brushSide in brush.sides) { ++_nextID; ParseBrushSide(brushSide, sb); } sb.Append("\t}\r\n"); }
/// <summary> /// Moves this <see cref="MAPBrush"/> object in the world by the vector <paramref name="v"/>. /// </summary> /// <param name="mapBrush">This <see cref="MAPBrush"/>.</param> /// <param name="v">Translation vector.</param> public static void Translate(this MAPBrush mapBrush, Vector3d v) { if (v == Vector3d.zero) { return; } foreach (MAPBrushSide side in mapBrush.sides) { side.Translate(v); if (side.displacement != null) { side.displacement.start += v; } } if (mapBrush.patch != null) { mapBrush.patch.Translate(v); } if (mapBrush.ef2Terrain != null) { mapBrush.ef2Terrain.start += v; } }
/// <summary> /// Creates an axis-aligned cubic brush with bounds from <paramref name="mins"/> to <paramref name="maxs"/>. /// </summary> /// <param name="mins">The minimum extents of the new brush.</param> /// <param name="maxs">The maximum extents of the new brush.</param> /// <param name="texture">The texture to use on this brush.</param> /// <returns>The resulting <see cref="MAPBrush"/> object.</returns> public static MAPBrush CreateCube(Vector3d mins, Vector3d maxs, string texture) { MAPBrush newBrush = new MAPBrush(); 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 i = 0; i < 6; ++i) { textureS[i] = new double[3]; } double[][] textureT = new double[6][]; for (int i = 0; i < 6; ++i) { textureT[i] = 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 i = 0; i < 6; i++) { MAPBrushSide currentSide = new MAPBrushSide() { vertices = planes[i], plane = new Plane(planes[i]), texture = texture, textureInfo = new TextureInfo(new Vector3d(textureS[i]), new Vector3d(textureT[i]), Vector2d.zero, Vector2d.one, 0, 0, 0), material = "wld_lightmap", lgtScale = 16, lgtRot = 0 }; newBrush.sides.Add(currentSide); } return(newBrush); }
private byte[] brushToByteArray(MAPBrush inputData, int num) { if (inputData.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inputData.NumSides + " sides!"); return new byte[0]; } string brush = "{ // Brush " + num + (char) 0x0D + (char) 0x0A; if ((inputData.Detail || inputData[0].Displacement != null) && currentEntity == 0) { brush += ("\"BRUSHFLAGS\" \"DETAIL\"" + (char) 0x0D + (char) 0x0A); } for (int i = 0; i < inputData.NumSides; i++) { brush += (brushSideToString(inputData[i]) + (char) 0x0D + (char) 0x0A); } brush += ("}" + (char) 0x0D + (char) 0x0A); if (brush.Length < 45) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return new byte[0]; } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte) brush[i]; } return brushbytes; } }
/// <summary> /// Processes an <see cref="Entity"/> into a state where it can be output into a file that map editors can read. /// </summary> /// <param name="entity">The <see cref="Entity"/> to process.</param> /// <remarks>This method does not return anything, since the <see cref="Entity"/> object is modified by reference.</remarks> private void ProcessEntity(Entity entity) { int modelNumber = entity.modelNumber; // If this Entity has no modelNumber, then this is a no-op. No processing is needed. // A modelnumber of 0 indicates the world entity. if (modelNumber >= 0) { Model model = _bsp.models[modelNumber]; if (_bsp.brushes != null) { List <Brush> brushes = _bsp.GetBrushesInModel(model); if (brushes != null) { foreach (Brush brush in brushes) { MAPBrush result = ProcessBrush(brush, entity.origin); result.isWater |= (entity.className == "func_water"); if (_master.settings.brushesToWorld) { _bsp.entities.GetWithAttribute("classname", "worldspawn").brushes.Add(result); } else { entity.brushes.Add(result); } ++_itemsProcessed; ReportProgress(); } } } if (model.numLeafPatches > 0 && _bsp.markSurfaces != null && _bsp.patches != null) { HashSet <Patch> patches = new HashSet <Patch>(); List <long> leafPatchesInModel = _bsp.GetReferencedObjects <long>(model, "markSurfaces"); foreach (long leafPatch in leafPatchesInModel) { if (leafPatch >= 0) { patches.Add(_bsp.patches[(int)leafPatch]); } } foreach (Patch patch in patches) { if (_bsp.version != MapType.CoD || patch.patchType == 0) { MAPPatch mappatch = ProcessPatch(patch); MAPBrush newBrush = new MAPBrush(); newBrush.patch = mappatch; entity.brushes.Add(newBrush); } } } if (_bsp.faces != null) { List <Face> surfaces = _bsp.GetFacesInModel(model); foreach (Face face in surfaces) { if (face.displacement >= 0) { if (modelNumber != 0) { _master.Print("WARNING: Displacement not part of world in " + _bsp.MapNameNoExtension); } MAPDisplacement displacement = ProcessDisplacement(_bsp.dispInfos[face.displacement]); MAPBrush newBrush = face.CreateBrush(_bsp, 32); newBrush.sides[0].displacement = displacement; // If we are not decompiling to VMF, vis will need to skip this brush. newBrush.isDetail = true; entity.brushes.Add(newBrush); } else if (face.flags == 2) { MAPPatch patch = ProcessPatch(face); MAPBrush newBrush = new MAPBrush(); newBrush.patch = patch; entity.brushes.Add(newBrush); } else if ((_bsp.version == MapType.STEF2 || _bsp.version == MapType.STEF2Demo) && face.flags == 5) { if (modelNumber != 0) { _master.Print("WARNING: Terrain not part of world in " + _bsp.MapNameNoExtension); } MAPTerrainEF2 terrain = ProcessEF2Terrain(face); MAPBrush newBrush = new MAPBrush(); newBrush.ef2Terrain = terrain; entity.brushes.Add(newBrush); } } } // If this is model 0 (worldspawn) there are other things that need to be taken into account. if (modelNumber == 0) { if (_bsp.lodTerrains != null) { foreach (LODTerrain lodTerrain in _bsp.lodTerrains) { MAPTerrainMoHAA terrain = ProcessTerrainMoHAA(lodTerrain); MAPBrush newBrush = new MAPBrush(); newBrush.mohTerrain = terrain; entity.brushes.Add(newBrush); } } } entity.Remove("model"); } }
// -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); } }
// 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); }
// Calculates 3 face corners, to be used to define the plane in ASCII format. /// Author: UltimateSniper /// Returns: List of normalised plane vertex triplets. public static MAPBrush CalcBrushVertices(MAPBrush mapBrush) { //DecompilerThread.OnMessage(this, "Recalculating vertices"); Plane[] planes = mapBrush.Planes; Vector3D[][] out_Renamed = new Vector3D[planes.Length][]; // For each triplet of planes, find intersect point. for (int iP1 = 0; iP1 < planes.Length; iP1++) { for (int iP2 = iP1 + 1; iP2 < planes.Length; iP2++) { for (int iP3 = iP2 + 1; iP3 < planes.Length; iP3++) { Vector3D testV = planes[iP1].trisect(planes[iP2], planes[iP3]); if (testV!=Vector3D.UNDEFINED) { bool isCorner = true; // If point is not null, test if point is behind/on all planes (if so, it is a corner). for (int iTest = 0; iTest < planes.Length; iTest++) { if (planes[iTest].Normal!=planes[iP1].Normal && planes[iTest].Normal!=planes[iP2].Normal && planes[iTest].Normal!=planes[iP3].Normal) { if (planes[iTest].distance(testV) > Settings.precision) { isCorner = false; break; } } } // If so, check which planes it is on. if (isCorner) { for (int iChk = 0; iChk < planes.Length; iChk++) { // If on this plane, and plane's vertex triplet missing min 1 point (and does not already have this point), add it. double dist = planes[iChk].distance(testV); if (System.Math.Abs(dist) <= Settings.precision) { // If first point on this plane, must create array. if (out_Renamed[iChk] == null) { out_Renamed[iChk] = new Vector3D[]{new Vector3D(testV), null, null}; } else { // Check each value in the array for open spot OR identical point. for (int iChk2 = 0; iChk2 < 3; iChk2++) { // Open spot, fill it. if (out_Renamed[iChk][iChk2] == null) { out_Renamed[iChk][iChk2] = new Vector3D(testV); // If this is now a complete plane. if (iChk2 == 2) { // Order complete triplet to make a plane facing same way as given plane. Plane testP = new Plane(out_Renamed[iChk][0], out_Renamed[iChk][1], out_Renamed[iChk][2]); // If normals are not pointing in same direction, re-order points. if (testP.Normal*planes[iChk].Normal < 0) { Vector3D temp = new Vector3D(out_Renamed[iChk][1]); out_Renamed[iChk][1] = new Vector3D(out_Renamed[iChk][2]); out_Renamed[iChk][2] = temp; } } break; // Else, if this list already has this point, skip out (to avoid doubling it). } else if (out_Renamed[iChk][iChk2]==testV) { break; } } } } } } } } } } for (int i = 0; i < mapBrush.NumSides; i++) { mapBrush[i].setSide(mapBrush[i].Plane, out_Renamed[i]); } return mapBrush; }
// 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; }
// SimpleCorrectPlanes(MAPBrush, float) // Uses all sides' defined points to ensure all planes are flipped correctly. public static MAPBrush SimpleCorrectPlanes(MAPBrush brush) { //DecompilerThread.OnMessage(this, "Plane flip. Method: simple"); // Find midpoint of triangle, and use that to normalise all other planes. int triIndex = - 1; // So we know which plane the triangle belongs to. Vector3D[] triangle = new Vector3D[0]; // This'll cause an exception if the loop fails for (int i = 0; i < brush.NumSides; i++) { if (brush[i].DefinedByTriangle) { triangle = brush[i].Triangle; triIndex = i; break; } } double[] normPoint = new double[]{(triangle[0].X + triangle[1].X + triangle[2].X) / 3.0, (triangle[0].Y + triangle[1].Y + triangle[2].Y) / 3.0, (triangle[0].Z + triangle[1].Z + triangle[2].Z) / 3.0}; Plane[] allplanes = brush.Planes; //bool foundTriPlane = false; for (int iPlane = 0; iPlane < allplanes.Length; iPlane++) { // For each plane double dist = allplanes[iPlane].distance(normPoint); // calculate distance from point if (iPlane == triIndex) { // if triangle's plane & normals point in opposite direction Vector3D tmp = new Plane(triangle[2], triangle[0], triangle[1]).Normal; if (allplanes[iPlane].Normal*(new Plane(triangle[2], triangle[0], triangle[1]).Normal) < 0) { brush[iPlane].flipSide(); // flip plane } } else { // or if not the triangle's plane if (dist > Settings.precision) { // if point is on positive (outside) side of plane brush[iPlane].flipSide(); // flip plane } } } return brush; }
// -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); } }
// -entityToByteArray() // Converts the entity and its brushes into byte arrays rather than Strings, // which can then be written to a file much faster. Concatenating Strings is // a costly operation, especially when hundreds of thousands of Strings are // in play. This is one of two parts to writing a file quickly. The second // part is to call the FileOutputStream.write() method only once, with a // gigantic array, rather than several times with many small arrays. File I/O // from a hard drive is another costly operation, best done by handling // massive amounts of data in one go, rather than tiny amounts of data thousands // of times. private byte[] entityToByteArray(Entity inputData, int num) { byte[] outputData; Vector3D origin; if (inputData.BrushBased) { origin = inputData.Origin; inputData.Remove("origin"); inputData.Remove("model"); if (origin[0] != 0 || origin[1] != 0 || origin[2] != 0) { // If this entity uses the "origin" attribute MAPBrush newOriginBrush = MAPBrush.createBrush(new Vector3D(-Settings.originBrushSize, -Settings.originBrushSize, -Settings.originBrushSize), new Vector3D(Settings.originBrushSize, Settings.originBrushSize, Settings.originBrushSize), "special/origin"); inputData.Brushes.Add(newOriginBrush); } } else { origin = Vector3D.ZERO; } string temp = ""; if (!inputData["classname"].Equals("worldspawn", StringComparison.InvariantCultureIgnoreCase)) { temp += "// entity " + num + (char)0x0D + (char)0x0A; } temp += "{"; int len = temp.Length + 5; // Get the lengths of all attributes together foreach (string key in inputData.Attributes.Keys) { len += key.Length + inputData[key].Length + 7; // Four quotes, a space and a newline } outputData = new byte[len]; int offset = 0; for (int i = 0; i < temp.Length; i++) { outputData[offset++] = (byte)temp[i]; } outputData[offset++] = (byte)0x0D; outputData[offset++] = (byte)0x0A; foreach (string key in inputData.Attributes.Keys) { // For each attribute outputData[offset++] = (byte)'\"'; // 1 for (int j = 0; j < key.Length; j++) { // Then for each byte in the attribute outputData[offset++] = (byte)key[j]; // add it to the output array } outputData[offset++] = (byte)'\"'; // 2 outputData[offset++] = (byte)' '; // 3 outputData[offset++] = (byte)'\"'; // 4 for (int j = 0; j < inputData.Attributes[key].Length; j++) { // Then for each byte in the attribute outputData[offset++] = (byte)inputData.Attributes[key][j]; // add it to the output array } outputData[offset++] = (byte)'\"'; // 5 outputData[offset++] = (byte)0x0D; // 6 outputData[offset++] = (byte)0x0A; // 7 } int brushArraySize = 0; byte[][] brushes = new byte[inputData.Brushes.Count][]; for (int j = 0; j < inputData.Brushes.Count; j++) { brushes[j] = brushToByteArray(inputData.Brushes[j], j); brushArraySize += brushes[j].Length; } int brushoffset = 0; byte[] brushArray = new byte[brushArraySize]; for (int j = 0; j < inputData.Brushes.Count; j++) { // For each brush in the entity for (int k = 0; k < brushes[j].Length; k++) { brushArray[brushoffset + k] = brushes[j][k]; } brushoffset += brushes[j].Length; } if (brushArray.Length != 0) { len += brushArray.Length; byte[] newOut = new byte[len]; for (int j = 0; j < outputData.Length; j++) { newOut[j] = outputData[j]; } for (int j = 0; j < brushArray.Length; j++) { newOut[j + outputData.Length - 3] = brushArray[j]; } offset += brushArray.Length; outputData = newOut; } outputData[offset++] = (byte)'}'; outputData[offset++] = (byte)0x0D; outputData[offset++] = (byte)0x0A; return(outputData); }
private byte[] brushToByteArray(MAPBrush inData, int num) { if (inData.Patch != null) { return patchToByteArray(inData.Patch, num); } if (inData.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inData.NumSides + " sides!"); return new byte[0]; } string brush = "// brush " + num + (char) 0x0D + (char) 0x0A + "{" + (char) 0x0D + (char) 0x0A; for (int i = 0; i < inData.NumSides; i++) { brush += (brushSideToString(inData[i], (inData.Detail || inData[0].Displacement != null)) + (char) 0x0D + (char) 0x0A); } brush += ("}" + (char) 0x0D + (char) 0x0A); if (brush.Length < 45) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return new byte[0]; } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte) brush[i]; } return brushbytes; } }
/// <summary> /// Postprocesser to convert an <see cref="Entity"/> from a Quake 3-based BSP to one for Gearcraft. /// </summary> /// <param name="entity">The <see cref="Entity"/> to parse.</param> private void PostProcessQuake3Entity(Entity entity) { if (entity.IsBrushBased) { Vector3 origin = entity.Origin; entity.Remove("origin"); entity.Remove("model"); if (entity.ValueIs("classname", "func_rotating") || entity.ValueIs("classname", "func_rotatingdoor")) { // TODO: What entities require origin brushes in Quake 3? if (origin != Vector3.Zero) { MAPBrush neworiginBrush = MAPBrushExtensions.CreateCube(new Vector3(-16, -16, -16), new Vector3(16, 16, 16), "special/origin"); entity.brushes.Add(neworiginBrush); } } foreach (MAPBrush brush in entity.brushes) { brush.Translate(origin); } } switch (entity["classname"].ToLower()) { case "worldspawn": { if (!entity["suncolor"].Equals("")) { Entity light_environment = new Entity("light_environment"); light_environment["_light"] = entity["suncolor"]; light_environment["angles"] = entity["sundirection"]; light_environment["_fade"] = entity["sundiffuse"]; entity.Remove("suncolor"); entity.Remove("sundirection"); entity.Remove("sundiffuse"); entity.Remove("sundiffusecolor"); _entities.Add(light_environment); } break; } case "team_ctf_blueflag": { // Blue flag entity["classname"] = "item_ctfflag"; entity["skin"] = "1"; // 0 for PHX, 1 for MI6 entity["goal_no"] = "1"; // 2 for PHX, 1 for MI6 entity["goal_max"] = "16 16 72"; entity["goal_min"] = "-16 -16 0"; entity["model"] = "models/ctf_flag.mdl"; Entity flagBase = new Entity("item_ctfbase"); flagBase["origin"] = entity["origin"]; flagBase["angles"] = entity["angles"]; flagBase["angle"] = entity["angle"]; flagBase["goal_no"] = "1"; flagBase["model"] = "models/ctf_flag_stand_mi6.mdl"; flagBase["goal_max"] = "16 16 72"; flagBase["goal_min"] = "-16 -16 0"; _entities.Add(flagBase); break; } case "team_ctf_redflag": { // Red flag entity["classname"] = "item_ctfflag"; entity["skin"] = "0"; // 0 for PHX, 1 for MI6 entity["goal_no"] = "2"; // 2 for PHX, 1 for MI6 entity["goal_max"] = "16 16 72"; entity["goal_min"] = "-16 -16 0"; entity["model"] = "models/ctf_flag.mdl"; Entity flagBase = new Entity("item_ctfbase"); flagBase["origin"] = entity["origin"]; flagBase["angles"] = entity["angles"]; flagBase["angle"] = entity["angle"]; flagBase["goal_no"] = "2"; flagBase["model"] = "models/ctf_flag_stand_phoenix.mdl"; flagBase["goal_max"] = "16 16 72"; flagBase["goal_min"] = "-16 -16 0"; _entities.Add(flagBase); break; } case "team_ctf_redspawn": case "info_player_axis": { entity["classname"] = "info_ctfspawn"; entity["team_no"] = "2"; goto case "info_player_start"; } case "team_ctf_bluespawn": case "info_player_allied": { entity["classname"] = "info_ctfspawn"; entity["team_no"] = "1"; goto case "info_player_start"; } case "info_player_start": case "info_player_coop": case "info_player_deathmatch": { Vector3 origin = entity.Origin; entity["origin"] = origin.X + " " + origin.Y + " " + (origin.Z + 24); break; } case "light": { Vector4 color; if (entity.ContainsKey("_color")) { color = entity.GetVector("_color"); } else { color = Vector4.One; } color *= 255; float intensity = entity.GetFloat("light", 1); entity.Remove("_color"); entity.Remove("light"); entity["_light"] = color.X + " " + color.Y + " " + color.Z + " " + intensity; break; } case "func_rotatingdoor": { entity["classname"] = "func_door_rotating"; break; } case "info_pathnode": { entity["classname"] = "info_node"; break; } case "trigger_ladder": { entity["classname"] = "func_ladder"; break; } case "trigger_use": { entity["classname"] = "func_button"; entity["spawnflags"] = "1"; entity["wait"] = "1"; break; } } }
// -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); } }
// Use if brush has no triangles. /// Author: UltimateSniper /// Returns: Ordered list of normalised vertex triplets (ready to feed in to map). public static MAPBrush AdvancedCorrectPlanes(MAPBrush mapBrush) { //DecompilerThread.OnMessage(this, "Plane flip. Method: advanced"); Plane[] allplanes = mapBrush.Planes; // Method: //1. Collect all vertices created by plane intercepts. //2. Create arrays of these vertices and inputted planes, to access planes via points they intersect and vice versa. // MORE IMPORTANTLY, create an array indicating sides of each plane each vertex is on (-1 for -, 0 for on, 1 for +). //3. Run through each possible cavity (each combination of sides of each plane), collecting satisfying vertices. // Correct cavity is found when there are at least 3 vertices on each plane. // If fail, returns Vector3D[0][]. //4. Generate central point of brush, and use it to produce normalised vertex triplets to return. // 1. Collect all plane intersects (all possible corners). //Find MaxVerts: max = N!/3!(N-3)! = (1/3!) * (N/(N-3)) * ((N-1)/(N-4)) * ((N-2)/(N-5)) * ... * (5/2) * 4 * 3! double dmaxVerts = 4.0; for (int iP = allplanes.Length; iP > 4; iP--) { dmaxVerts *= iP / (iP - 3.00); } //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'" Vector3D[] allverts = new Vector3D[(int) dmaxVerts]; // Max possible number of unique plane trisects: nC3. int iallverts = 0; // Pointer, so we know next vacant index. for (int iP1 = 0; iP1 < allplanes.Length; iP1++) { for (int iP2 = iP1 + 1; iP2 < allplanes.Length; iP2++) { for (int iP3 = iP2 + 1; iP3 < allplanes.Length; iP3++) { Vector3D testV = allplanes[iP1].trisect(allplanes[iP2], allplanes[iP3]); if (testV!=Vector3D.UNDEFINED) { // Arbitrary precision: Just checking if UNDEFINED or real. bool hasVtx = false; for (int iVtx = 0; iVtx < iallverts; iVtx++) { if (allverts[iVtx]==testV) { hasVtx = true; break; } } if (!hasVtx) { allverts[iallverts] = testV; iallverts++; } } } } } Vector3D[] tmp = new Vector3D[iallverts]; Array.Copy(allverts, 0, tmp, 0, iallverts); allverts = tmp; // 2. Make array to access verts' sides of planes (can also be used to check if vert is on plane). sbyte[][] VertPlaneSides = new sbyte[allverts.Length][]; for (int iV = 0; iV < allverts.Length; iV++) { sbyte[] PlaneSides = new sbyte[allplanes.Length]; for (int iVP = 0; iVP < allplanes.Length; iVP++) { double dist = allplanes[iVP].distance(allverts[iV]); if (System.Math.Abs(dist) < Settings.precision) { PlaneSides[iVP] = 0; } else { PlaneSides[iVP] = ((dist >= Settings.precision)?(sbyte) 1:(sbyte) (- 1)); } } VertPlaneSides[iV] = PlaneSides; } // THEORY: Collect vertices that are all either on, or on the same side of all planes. // If there are at least 3 vertices on each plane, then this is the correct shape. // NOTES: -Some vertices may be included in multiple collections. // -Must have 3 vertices sharing 1 plane, and a fourth which is not on that plane to start 'cavity'. // -4 defining vertices cannot make more than 1 'cavity'. // Java is retarded, as it doesn't allow uints. This causes me a serious problem with this, because I am not sure // whether or not bitwise operations will return positive values, or switch the signs, or switch the bit order... // Cannot handle more than the max positive val for a long, and need a bit to represent each plane. if (allplanes.Length > 62) { throw new System.IndexOutOfRangeException("More than 62 planes in brush!"); } // 3. Find all vertices which satisfy each possible cavity, and break when true brush is found. // Let the madness of this one great f*****g for-loop commence... int[] TrueCorns = new int[0]; for (long lCav = 0; lCav < (1 << allplanes.Length); lCav++) { int[] Corns = new int[allverts.Length]; int iCorns = 0; for (int iCorn = 0; iCorn < allverts.Length; iCorn++) { bool addable = true; for (int iPlane = 0; iPlane < allplanes.Length; iPlane++) { // Get bit value of lCav which represents this plane (true = +, false = -), check if vert is addable. if (((lCav >> iPlane) & 1) == 1) { if (VertPlaneSides[iCorn][iPlane] == - 1) { addable = false; } } else { if (VertPlaneSides[iCorn][iPlane] == 1) { addable = false; } } } if (addable) { Corns[iCorns] = iCorn; iCorns++; } } // Check if we already have the brush... if (iCorns >= allplanes.Length) { bool isBrush = true; for (int iChkP = 0; iChkP < allplanes.Length; iChkP++) { // If all planes have at least 3 verts in this solid, IT IS THE BRUSH. int numOnPlane = 0; for (int iChkC = 0; iChkC < iCorns; iChkC++) { if (VertPlaneSides[Corns[iChkC]][iChkP] == 0) { numOnPlane++; if (numOnPlane >= 3) { break; } } } if (numOnPlane < 3) { isBrush = false; break; } } if (isBrush) { // Copy to TrueCorns. TrueCorns = new int[iCorns]; Array.Copy(Corns, 0, TrueCorns, 0, iCorns); break; } } } // Idea: Loop all verts on 1 plane. // Collect 1 cavity. // Collect others, but make sure that points are not on same sides of planes as others. // (Start plane side array (make upper-level one, too), and make sure that it doesn't become identical to any previous arrays.) /*Vector3D[] corners = new Vector3D[allverts.length]; // SCREW IT. NEW PLAN... // Iterate through all possibilities of all sides of all planes. // NEED: byte[vert][plane] = -1, 0, +1, to tell whether or not vertex can be included in decompile. // 2^n iterations, sequence of booleans determines sides of planes. Vector3D[][] solidCollection = new Vector3D[allverts.length]; int isolidCollection = 0; // METHOD 1: Take 1 starting vert, and find all verts on same side of all planes, and on same side of startplanes as first verts found not on startplanes. for (int iStart = 0; iStart < PlaneVerts[0].length; iStart++) { // Set up list of sides of planes which define this solid. 0=ON 1=+Norm -1=-Norm byte[] planeSidesStart = new byte[allplanes.length]; // Collect info from first point. for (int i1Side = 1; i1Side < allplanes.length; i1Side++) { if (indexOf(PlaneVerts[i1Side], PlaneVerts[0][iStart]) == -1) { planeSidesStart[i1Side] = ((allplanes[i1Side].distance(PlaneVerts[0][iStart]) > 0) ? 1 : -1); } else { planeSidesStart[i1Side] = 0; } } // Collect a list of points which satisfy the conditions in planeSideStart. int[] potCorns = new int[allverts.length]; int ipotCorns = 0; for (int iTCorn = 0; iTCorn < allverts.length; iTCorn++) { if (iTCorn != indexOf(allverts, PlaneVerts[0][iStart])) { // Check if on same side of planes as solid-defining list... boolean addable = true; for (int iPlanes = 0; iPlanes < allplanes.length; iPlanes++) { if (indexOf(PlaneVerts[iPlanes], allpoints[iCorn]) == -1 && planeSidesStart[iPlanes] != 0) { double dist = allplanes[iPlanes].distance(allpoints[iCorn]); if ((dist > 0 && planeSidesStart[iPlanes] < 0) || (dist < 0 && planeSidesStart[iPlanes] > 0)) { addable = false; } } } if (addable) { potCorns[ipotCorns] = iTCorn; ipotCorns++; } } } int[] basePlanes = new int[VertPlanes[indexOf(allverts, PlaneVerts[0][iStart])].length]; // A possibility of 2^NumUnknownPlaneSides number of valid solids. Possibly on + or - side of each unknown plane. // Need to code something to run over each possibility once. for (int iSideCheck = 0; iSideCheck < basePlanes.length; iSideCheck++) { // For all possible solids starting from this point... for (int iChkNum = 0; iChkNum < allverts.length; iChkNum++) { // Look through all verts & find all on same side of planes. int[] blockcorners = new int[allpoints.length]; blockcorners[0] = indexOf(allverts, PlaneVerts[0][iStart]); int iblockcorners = 1; // Set up list of sides of planes which define this solid. 0=ON 1=+Norm -1=-Norm // DEEP COPY IT PLEASE? byte[] planeSides = planeSidesStart; // Screw this complicated shit, doesn't work anyway. // INSTEAD, must find valid solid, and verts on opposite side of a plane that startVert is on than other solids. // int FirstID = 0; for (int iCorn = 0; iCorn < allverts.length; iCorn++) { boolean checkable = true; boolean isFirst = // If is startvert or is contained in any solid also containing startvert, IS NOT CHECKABLE FIRST TIME AROUND. // Do for this vert, then reset to find all verts in solid. for (int iCheck = 0; iCheck < iSolidCollection; iCheck++) { if (!allpoints[iCorn].equals(PlaneVerts[0][iStart]) && iCorn != FirstID) if (!allpoints[iCorn].equals(PlaneVerts[0][iStart])) { // Check if on same side of planes as solid-defining list... int[] thisPlaneSides = new int[allplanes.length] int ithisPlaneSides = 0; boolean addable = true; for (int iPlanes = 0; iPlanes < allplanes.length; iPlanes++) { if (indexOf(PlaneVerts[iPlanes], allpoints[iCorn]) == -1) { double dist = allplanes[iPlanes].distance(allpoints[iCorn]); if (planeSides[iPlanes] == 0) { // If point is valid at end, temp set vert's planesides to +- plane index. thisPlaneSides[ithisPlaneSides] = ((dist > 0) ? iPlanes + 1 : -iPlanes - 1); } else if ((dist > 0 && planeSides[iPlanes] < 0) || (dist < 0 && planeSides[iPlanes] > 0)) { addable = false; } } } if (addable) { for (int iNewSides = 0; iNewSides < ithisPlaneSides; iNewSides++) { if (thisPlaneSides[iNewSides] > 0) { planeSides[thisPlaneSides[iNewSides] - 1] = 1; } else { planeSides[-thisPlaneSides[iNewSides] - 1] = -1; } } blockcorners[iblockcorners] = iCorn; iblockcorners++; } } } solidCollection[isolidCollection] = new Vector3D[iblockcorners]; for (int iKnownCorn = 0; iKnownCorn < iblockcorners; iKnownCorn++) { solidCollection[isolidCollection][iKnownCorn] = allverts[blockcorners[iKnownCorn]]; } isolidCollection++; } // For each vert on base plane, find 2 adjacent vertices. for (int iV1 = 0; iV1 < PlaneVerts[0].length; iV1++) { // For each other vert on base plane, check if 1st vert and this one share 2 planes & are on same sides of all planes. for (int iV2 = iV1 + 1; iV2 < PlaneVerts[0].length; iV2++) { boolean isOKVert = false; // Check if these 2 share 2 planes. // For each plane on first vert... for (int iChkP = 0; iChkP < VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])].length; iChkP++) // If plane is NOT base-plane... if (!allplanes[0].getNormal().equals(VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])][iChkP]) || allplanes[0].getDist() != VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])][iChkP].getDist()) // If this plane is common, VERT IS OK... if (indexOf(VertPlanes[indexOf(allverts, PlaneVerts[0][iV2])], VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])][iChkP]) > -1) isOKVert = true; // Check if these 2 are on same side of all planes they are not on. if (isOKVert) { isOKVert = false; // For each plane, not base-plane... for (int iChkP2 = 1; iChkP2 < allplanes.length; iChkP2++) // If V1 and V2 are NOT on plane... if (indexOf(PlaneVerts[iChkP2], PlaneVerts[0][iV1]) == -1 && indexOf(PlaneVerts[iChkP2], PlaneVerts[0][iV2]) == -1) { // If V1 and V2 are on same side of plane... double dv1 = allplanes[iChkP2].distance(PlaneVerts[0][iV1]); double dv2 = allplanes[iChkP2].distance(PlaneVerts[0][iV2]); if ((dv1 > 0 && dv2 > 0) || (dv1 < 0 && dv2 < 0)) isOKVert = true; } } if (isOKVert) { for (int iV3 = iV2 + 1; iV3 < PlaneVerts[0].length; iV3++) { */ // Return null value if method failed. if (TrueCorns.Length == 0) { throw new System.ArithmeticException("No corners found for brush!"); } // 4. Create brush central point, and use it to create normalised plane triplets. // Create central point of brush for normalising vert-planes. Vector3D centrePoint = new Vector3D(0.0, 0.0, 0.0); for (int iCorn = 0; iCorn < TrueCorns.Length; iCorn++) { centrePoint = centrePoint+(allverts[TrueCorns[iCorn]]); } centrePoint = centrePoint/((double) TrueCorns.Length); // Use corners to generate brush plane triplets. Vector3D[][] output = new Vector3D[allplanes.Length][]; for (int iPlane = 0; iPlane < allplanes.Length; iPlane++) { int[] vertPlane = new int[3]; int ivertPlane = 0; for (int iCorn = 0; iCorn < TrueCorns.Length; iCorn++) { if (VertPlaneSides[TrueCorns[iCorn]][iPlane] == 0) { vertPlane[ivertPlane] = TrueCorns[iCorn]; ivertPlane++; if (ivertPlane == 3) { break; } } } // Order triplet correctly & save to output array. if (new Plane(allverts[vertPlane[0]], allverts[vertPlane[1]], allverts[vertPlane[2]]).distance(centrePoint) > 0) { output[iPlane] = new Vector3D[]{allverts[vertPlane[0]], allverts[vertPlane[2]], allverts[vertPlane[1]]}; } else { output[iPlane] = new Vector3D[]{allverts[vertPlane[0]], allverts[vertPlane[1]], allverts[vertPlane[2]]}; } } for (int i = 0; i < output.Length; i++) { // This isn't setSide because the plane definition is no longer reliable; it might be flipped the wrong way mapBrush[i].Triangle = output[i]; } return mapBrush; }
// METHODS // write() // Saves the lump to the specified path. // Handling file I/O with Strings is generally a bad idea. If you have maybe a couple hundred // Strings to write then it'll probably be okay, but when you have on the order of 10,000 Strings // it gets VERY slow, even if you concatenate them all before writing. public virtual void write() { // Preprocessing entity corrections if (!Settings.noEntCorrection) { if (BSPVersion == mapType.TYPE_NIGHTFIRE) { for (int i = 1; i < data.Count; i++) { for (int j = 0; j < data[i].Brushes.Count; j++) { if (data[i].Brushes[j].Water) { MAPBrush currentBrush = data[i].Brushes[j]; for (int k = 0; k < currentBrush.NumSides; k++) { MAPBrushSide currentBrushSide = currentBrush[k]; if (currentBrushSide.Plane.Normal.Equals(Vector3D.UP) || currentBrushSide.Plane.Normal.Equals(-Vector3D.UP)) { currentBrushSide.Texture = "test/omaha_pjspick5"; } else { currentBrushSide.Texture = "common/nodraw"; } } data[0].Brushes.Add(data[i].Brushes[j]); // TODO: Textures on this brush } } if (data[i]["classname"].Equals("func_water", StringComparison.InvariantCultureIgnoreCase)) { data.RemoveAt(i); i--; } } } // Correct some attributes of entities for (int i = 0; i < data.Count; i++) { Entity current = data[i]; switch (BSPVersion) { case mapType.TYPE_NIGHTFIRE: // Nightfire current = ent42ToEntRad(current); break; case mapType.TYPE_QUAKE2: current = ent38ToEntRad(current); break; case mapType.TYPE_DOOM: case mapType.TYPE_HEXEN: break; } } } byte[][] entityBytes = new byte[data.Count][]; int totalLength = 0; for (currentEntity = 0; currentEntity < data.Count; currentEntity++) { try { entityBytes[currentEntity] = entityToByteArray(data[currentEntity], currentEntity); } catch (System.IndexOutOfRangeException) { // This happens when entities are added after the array is made byte[][] newList = new byte[data.Count][]; // Create a new array with the new length for (int j = 0; j < entityBytes.Length; j++) { newList[j] = entityBytes[j]; } newList[currentEntity] = entityToByteArray(data[currentEntity], currentEntity); entityBytes = newList; } totalLength += entityBytes[currentEntity].Length; } byte[] allEnts = new byte[totalLength]; int offset = 0; for (int i = 0; i < data.Count; i++) { for (int j = 0; j < entityBytes[i].Length; j++) { allEnts[offset + j] = entityBytes[i][j]; } offset += entityBytes[i].Length; } MAPMaker.write(allEnts, path, false); }
// Use if brush has no triangles. /// Author: UltimateSniper /// Returns: Ordered list of normalised vertex triplets (ready to feed in to map). public static MAPBrush AdvancedCorrectPlanes(MAPBrush mapBrush) { //DecompilerThread.OnMessage(this, "Plane flip. Method: advanced"); Plane[] allplanes = mapBrush.Planes; // Method: //1. Collect all vertices created by plane intercepts. //2. Create arrays of these vertices and inputted planes, to access planes via points they intersect and vice versa. // MORE IMPORTANTLY, create an array indicating sides of each plane each vertex is on (-1 for -, 0 for on, 1 for +). //3. Run through each possible cavity (each combination of sides of each plane), collecting satisfying vertices. // Correct cavity is found when there are at least 3 vertices on each plane. // If fail, returns Vector3D[0][]. //4. Generate central point of brush, and use it to produce normalised vertex triplets to return. // 1. Collect all plane intersects (all possible corners). //Find MaxVerts: max = N!/3!(N-3)! = (1/3!) * (N/(N-3)) * ((N-1)/(N-4)) * ((N-2)/(N-5)) * ... * (5/2) * 4 * 3! double dmaxVerts = 4.0; for (int iP = allplanes.Length; iP > 4; iP--) { dmaxVerts *= iP / (iP - 3.00); } //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'" Vector3D[] allverts = new Vector3D[(int)dmaxVerts]; // Max possible number of unique plane trisects: nC3. int iallverts = 0; // Pointer, so we know next vacant index. for (int iP1 = 0; iP1 < allplanes.Length; iP1++) { for (int iP2 = iP1 + 1; iP2 < allplanes.Length; iP2++) { for (int iP3 = iP2 + 1; iP3 < allplanes.Length; iP3++) { Vector3D testV = allplanes[iP1].trisect(allplanes[iP2], allplanes[iP3]); if (testV != Vector3D.UNDEFINED) { // Arbitrary precision: Just checking if UNDEFINED or real. bool hasVtx = false; for (int iVtx = 0; iVtx < iallverts; iVtx++) { if (allverts[iVtx] == testV) { hasVtx = true; break; } } if (!hasVtx) { allverts[iallverts] = testV; iallverts++; } } } } } Vector3D[] tmp = new Vector3D[iallverts]; Array.Copy(allverts, 0, tmp, 0, iallverts); allverts = tmp; // 2. Make array to access verts' sides of planes (can also be used to check if vert is on plane). sbyte[][] VertPlaneSides = new sbyte[allverts.Length][]; for (int iV = 0; iV < allverts.Length; iV++) { sbyte[] PlaneSides = new sbyte[allplanes.Length]; for (int iVP = 0; iVP < allplanes.Length; iVP++) { double dist = allplanes[iVP].distance(allverts[iV]); if (System.Math.Abs(dist) < Settings.precision) { PlaneSides[iVP] = 0; } else { PlaneSides[iVP] = ((dist >= Settings.precision)?(sbyte)1:(sbyte)(-1)); } } VertPlaneSides[iV] = PlaneSides; } // THEORY: Collect vertices that are all either on, or on the same side of all planes. // If there are at least 3 vertices on each plane, then this is the correct shape. // NOTES: -Some vertices may be included in multiple collections. // -Must have 3 vertices sharing 1 plane, and a fourth which is not on that plane to start 'cavity'. // -4 defining vertices cannot make more than 1 'cavity'. // Java is retarded, as it doesn't allow uints. This causes me a serious problem with this, because I am not sure // whether or not bitwise operations will return positive values, or switch the signs, or switch the bit order... // Cannot handle more than the max positive val for a long, and need a bit to represent each plane. if (allplanes.Length > 62) { throw new System.IndexOutOfRangeException("More than 62 planes in brush!"); } // 3. Find all vertices which satisfy each possible cavity, and break when true brush is found. // Let the madness of this one great f*****g for-loop commence... int[] TrueCorns = new int[0]; for (long lCav = 0; lCav < (1 << allplanes.Length); lCav++) { int[] Corns = new int[allverts.Length]; int iCorns = 0; for (int iCorn = 0; iCorn < allverts.Length; iCorn++) { bool addable = true; for (int iPlane = 0; iPlane < allplanes.Length; iPlane++) { // Get bit value of lCav which represents this plane (true = +, false = -), check if vert is addable. if (((lCav >> iPlane) & 1) == 1) { if (VertPlaneSides[iCorn][iPlane] == -1) { addable = false; } } else { if (VertPlaneSides[iCorn][iPlane] == 1) { addable = false; } } } if (addable) { Corns[iCorns] = iCorn; iCorns++; } } // Check if we already have the brush... if (iCorns >= allplanes.Length) { bool isBrush = true; for (int iChkP = 0; iChkP < allplanes.Length; iChkP++) { // If all planes have at least 3 verts in this solid, IT IS THE BRUSH. int numOnPlane = 0; for (int iChkC = 0; iChkC < iCorns; iChkC++) { if (VertPlaneSides[Corns[iChkC]][iChkP] == 0) { numOnPlane++; if (numOnPlane >= 3) { break; } } } if (numOnPlane < 3) { isBrush = false; break; } } if (isBrush) { // Copy to TrueCorns. TrueCorns = new int[iCorns]; Array.Copy(Corns, 0, TrueCorns, 0, iCorns); break; } } } // Idea: Loop all verts on 1 plane. // Collect 1 cavity. // Collect others, but make sure that points are not on same sides of planes as others. // (Start plane side array (make upper-level one, too), and make sure that it doesn't become identical to any previous arrays.) /*Vector3D[] corners = new Vector3D[allverts.length]; * * * * // SCREW IT. NEW PLAN... * // Iterate through all possibilities of all sides of all planes. * // NEED: byte[vert][plane] = -1, 0, +1, to tell whether or not vertex can be included in decompile. * // 2^n iterations, sequence of booleans determines sides of planes. * * * * * * Vector3D[][] solidCollection = new Vector3D[allverts.length]; * int isolidCollection = 0; * * // METHOD 1: Take 1 starting vert, and find all verts on same side of all planes, and on same side of startplanes as first verts found not on startplanes. * for (int iStart = 0; iStart < PlaneVerts[0].length; iStart++) { * * // Set up list of sides of planes which define this solid. 0=ON 1=+Norm -1=-Norm * byte[] planeSidesStart = new byte[allplanes.length]; * // Collect info from first point. * for (int i1Side = 1; i1Side < allplanes.length; i1Side++) { * if (indexOf(PlaneVerts[i1Side], PlaneVerts[0][iStart]) == -1) { * planeSidesStart[i1Side] = ((allplanes[i1Side].distance(PlaneVerts[0][iStart]) > 0) ? 1 : -1); * } else { * planeSidesStart[i1Side] = 0; * } * } * * // Collect a list of points which satisfy the conditions in planeSideStart. * int[] potCorns = new int[allverts.length]; * int ipotCorns = 0; * for (int iTCorn = 0; iTCorn < allverts.length; iTCorn++) { * if (iTCorn != indexOf(allverts, PlaneVerts[0][iStart])) { * // Check if on same side of planes as solid-defining list... * boolean addable = true; * for (int iPlanes = 0; iPlanes < allplanes.length; iPlanes++) { * if (indexOf(PlaneVerts[iPlanes], allpoints[iCorn]) == -1 && planeSidesStart[iPlanes] != 0) { * double dist = allplanes[iPlanes].distance(allpoints[iCorn]); * if ((dist > 0 && planeSidesStart[iPlanes] < 0) || (dist < 0 && planeSidesStart[iPlanes] > 0)) { * addable = false; * } * } * } * if (addable) { * potCorns[ipotCorns] = iTCorn; * ipotCorns++; * } * } * } * * int[] basePlanes = new int[VertPlanes[indexOf(allverts, PlaneVerts[0][iStart])].length]; * // A possibility of 2^NumUnknownPlaneSides number of valid solids. Possibly on + or - side of each unknown plane. * // Need to code something to run over each possibility once. * for (int iSideCheck = 0; iSideCheck < basePlanes.length; iSideCheck++) { * * * // For all possible solids starting from this point... * for (int iChkNum = 0; iChkNum < allverts.length; iChkNum++) { * // Look through all verts & find all on same side of planes. * int[] blockcorners = new int[allpoints.length]; * blockcorners[0] = indexOf(allverts, PlaneVerts[0][iStart]); * int iblockcorners = 1; * * // Set up list of sides of planes which define this solid. 0=ON 1=+Norm -1=-Norm * // DEEP COPY IT PLEASE? * byte[] planeSides = planeSidesStart; * * // Screw this complicated shit, doesn't work anyway. * // INSTEAD, must find valid solid, and verts on opposite side of a plane that startVert is on than other solids. * * // * * * int FirstID = 0; * for (int iCorn = 0; iCorn < allverts.length; iCorn++) { * boolean checkable = true; * boolean isFirst = * // If is startvert or is contained in any solid also containing startvert, IS NOT CHECKABLE FIRST TIME AROUND. * // Do for this vert, then reset to find all verts in solid. * for (int iCheck = 0; iCheck < iSolidCollection; iCheck++) { * * if (!allpoints[iCorn].equals(PlaneVerts[0][iStart]) && iCorn != FirstID) * if (!allpoints[iCorn].equals(PlaneVerts[0][iStart])) { * // Check if on same side of planes as solid-defining list... * int[] thisPlaneSides = new int[allplanes.length] * int ithisPlaneSides = 0; * boolean addable = true; * for (int iPlanes = 0; iPlanes < allplanes.length; iPlanes++) { * if (indexOf(PlaneVerts[iPlanes], allpoints[iCorn]) == -1) { * double dist = allplanes[iPlanes].distance(allpoints[iCorn]); * if (planeSides[iPlanes] == 0) { * // If point is valid at end, temp set vert's planesides to +- plane index. * thisPlaneSides[ithisPlaneSides] = ((dist > 0) ? iPlanes + 1 : -iPlanes - 1); * } else if ((dist > 0 && planeSides[iPlanes] < 0) || (dist < 0 && planeSides[iPlanes] > 0)) { * addable = false; * } * } * } * if (addable) { * for (int iNewSides = 0; iNewSides < ithisPlaneSides; iNewSides++) { * if (thisPlaneSides[iNewSides] > 0) { * planeSides[thisPlaneSides[iNewSides] - 1] = 1; * } else { * planeSides[-thisPlaneSides[iNewSides] - 1] = -1; * } * } * blockcorners[iblockcorners] = iCorn; * iblockcorners++; * } * } * } * solidCollection[isolidCollection] = new Vector3D[iblockcorners]; * for (int iKnownCorn = 0; iKnownCorn < iblockcorners; iKnownCorn++) { * solidCollection[isolidCollection][iKnownCorn] = allverts[blockcorners[iKnownCorn]]; * } * isolidCollection++; * } * * * * * // For each vert on base plane, find 2 adjacent vertices. * for (int iV1 = 0; iV1 < PlaneVerts[0].length; iV1++) { * // For each other vert on base plane, check if 1st vert and this one share 2 planes & are on same sides of all planes. * for (int iV2 = iV1 + 1; iV2 < PlaneVerts[0].length; iV2++) { * boolean isOKVert = false; * // Check if these 2 share 2 planes. * // For each plane on first vert... * for (int iChkP = 0; iChkP < VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])].length; iChkP++) * // If plane is NOT base-plane... * if (!allplanes[0].getNormal().equals(VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])][iChkP]) || allplanes[0].getDist() != VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])][iChkP].getDist()) * // If this plane is common, VERT IS OK... * if (indexOf(VertPlanes[indexOf(allverts, PlaneVerts[0][iV2])], VertPlanes[indexOf(allverts, PlaneVerts[0][iV1])][iChkP]) > -1) * isOKVert = true; * // Check if these 2 are on same side of all planes they are not on. * if (isOKVert) { * isOKVert = false; * // For each plane, not base-plane... * for (int iChkP2 = 1; iChkP2 < allplanes.length; iChkP2++) * // If V1 and V2 are NOT on plane... * if (indexOf(PlaneVerts[iChkP2], PlaneVerts[0][iV1]) == -1 && indexOf(PlaneVerts[iChkP2], PlaneVerts[0][iV2]) == -1) { * // If V1 and V2 are on same side of plane... * double dv1 = allplanes[iChkP2].distance(PlaneVerts[0][iV1]); * double dv2 = allplanes[iChkP2].distance(PlaneVerts[0][iV2]); * if ((dv1 > 0 && dv2 > 0) || (dv1 < 0 && dv2 < 0)) * isOKVert = true; * } * } * if (isOKVert) { * for (int iV3 = iV2 + 1; iV3 < PlaneVerts[0].length; iV3++) { * */ // Return null value if method failed. if (TrueCorns.Length == 0) { throw new System.ArithmeticException("No corners found for brush!"); } // 4. Create brush central point, and use it to create normalised plane triplets. // Create central point of brush for normalising vert-planes. Vector3D centrePoint = new Vector3D(0.0, 0.0, 0.0); for (int iCorn = 0; iCorn < TrueCorns.Length; iCorn++) { centrePoint = centrePoint + (allverts[TrueCorns[iCorn]]); } centrePoint = centrePoint / ((double)TrueCorns.Length); // Use corners to generate brush plane triplets. Vector3D[][] output = new Vector3D[allplanes.Length][]; for (int iPlane = 0; iPlane < allplanes.Length; iPlane++) { int[] vertPlane = new int[3]; int ivertPlane = 0; for (int iCorn = 0; iCorn < TrueCorns.Length; iCorn++) { if (VertPlaneSides[TrueCorns[iCorn]][iPlane] == 0) { vertPlane[ivertPlane] = TrueCorns[iCorn]; ivertPlane++; if (ivertPlane == 3) { break; } } } // Order triplet correctly & save to output array. if (new Plane(allverts[vertPlane[0]], allverts[vertPlane[1]], allverts[vertPlane[2]]).distance(centrePoint) > 0) { output[iPlane] = new Vector3D[] { allverts[vertPlane[0]], allverts[vertPlane[2]], allverts[vertPlane[1]] }; } else { output[iPlane] = new Vector3D[] { allverts[vertPlane[0]], allverts[vertPlane[1]], allverts[vertPlane[2]] }; } } for (int i = 0; i < output.Length; i++) { // This isn't setSide because the plane definition is no longer reliable; it might be flipped the wrong way mapBrush[i].Triangle = output[i]; } return(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); } }
// Some algorithms might produce planes which are correctly normalized, but // some don't actually contribute to the solid (such as those solids created // by iterating through a binary tree, and keeping track of all node subdivisions). // This finds them, returns a list of their indices as an array. I could return // a MAPBrush with those planes culled, but oftentimes there are two or three // brushes with the same unnecessary planes. public static int[] findUnusedPlanes(MAPBrush in_Renamed) { Plane[] thePlanes = in_Renamed.Planes; //Console.Write("Finding unnecessary planes. Before: " + thePlanes.Length); // Step 1: Get all points of intersection double numVerts = 4; // Iterative nCr algorithm; thanks to Alex's code for (int i = thePlanes.Length; i > 4; i--) { numVerts *= (double)i / (double)(i - 3); } //UPGRADE_TODO: Method 'java.lang.Math.round' was converted to 'System.Math.Round' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextSettingsIndex'&keyword='jlca1073_javalangMathround_double'" Vector3D[] theVerts = new Vector3D[(int)System.Math.Round(numVerts)]; int index = 0; for (int i = 0; i < thePlanes.Length - 2; i++) { for (int j = i + 1; j < thePlanes.Length - 1; j++) { for (int k = j + 1; k < thePlanes.Length; k++) { theVerts[index++] = thePlanes[i].trisect(thePlanes[j], thePlanes[k]); } } } // Step 2: Throw out all vertices on the wrong side of any plane, since they're // all facing the "right" way. for (int i = 0; i < theVerts.Length; i++) { for (int j = 0; j < thePlanes.Length; j++) { if (thePlanes[j].distance(theVerts[i]) > Settings.precision) { theVerts[i] = Vector3D.UNDEFINED; break; //break the inner loop, let the outer loop iterate } } } // Step 3: Only keep sides which have three or more vertices defined int[] badSides = new int[0]; for (int i = 0; i < thePlanes.Length; i++) { int numMatches = 0; Vector3D[] matches = new Vector3D[3]; for (int j = 0; j < theVerts.Length; j++) { if (System.Math.Abs(thePlanes[i].distance(theVerts[j])) < Settings.precision) { bool duplicate = false; for (int k = 0; k < numMatches; k++) { if (theVerts[j] == matches[k]) { duplicate = true; } } if (!duplicate) { matches[numMatches] = theVerts[j]; numMatches++; } } if (numMatches >= 3) { // We have enough points. break; } } if (numMatches < 3) { int[] newList = new int[badSides.Length + 1]; for (int j = 0; j < badSides.Length; j++) { newList[j] = badSides[j]; } newList[newList.Length - 1] = i; badSides = newList; } } //DecompilerThread.OnMessage(this, " After: " + (thePlanes.Length - badSides.Length)); return(badSides); }
// METHODS // +decompile() // Attempts to convert the BSP file back into a .MAP file. public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); // In the decompiler, it is not necessary to copy all entities to a new object, since // no writing is ever done back to the BSP file. mapFile = BSPObject.Entities; int numTotalItems = 0; worldspawn = mapFile[mapFile.findAllWithAttribute("classname", "worldspawn")[0]]; int onePercent = (int)((BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count) / 100); if (onePercent < 1) { onePercent = 1; } // I need to go through each entity and see if it's brush-based. // Worldspawn is brush-based as well as any entity with model *#. for (int i = 0; i < BSPObject.Entities.Count; i++) { // For each entity //DecompilerThread.OnMessage(this, "Entity " + i + ": " + mapFile[i]["classname"]); numBrshs = 0; // Reset the brush count for each entity // getModelNumber() returns 0 for worldspawn, the *# for brush based entities, and -1 for everything else int currentModel = mapFile[i].ModelNumber; if (currentModel > -1) { // If this is still -1 then it's strictly a point-based entity. Move on to the next one. int firstBrush = BSPObject.Models[currentModel].FirstBrush; int numBrushes = BSPObject.Models[currentModel].NumBrushes; numBrshs = 0; for (int j = 0; j < numBrushes; j++) { // For each brush //Console.Write("Brush " + j); decompileBrush(BSPObject.Brushes[j + firstBrush], i); // Decompile the brush numBrshs++; numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)); } } } numTotalItems++; if (numTotalItems % onePercent == 0) { parent.OnProgress(this, numTotalItems / (double)(BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)); } } if (!Settings.skipPlaneFlip) { DecompilerThread.OnMessage(this, "Num simple corrected brushes: " + numSimpleCorrects); DecompilerThread.OnMessage(this, "Num advanced corrected brushes: " + numAdvancedCorrects); DecompilerThread.OnMessage(this, "Num good brushes: " + numGoodBrushes); } foreach (Face face in BSPObject.Faces) { if (face.Facetype == Face.faceType.PATCH) { MAPPatch mapPatch = new MAPPatch(face.PatchSize[0], face.PatchSize[1], BSPObject.Textures[face.Texture].Name); for (int i = 0; i < face.NumVertices; i++) { mapPatch.Add(BSPObject.Vertices[face.FirstVertex + i]); } MAPBrush mapBrush = new MAPBrush(mapPatch); worldspawn.Brushes.Add(mapBrush); } } parent.OnProgress(this, 1.0); return(mapFile); }
// 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); }
// 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; }
// -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); } }
// Some algorithms might produce planes which are correctly normalized, but // some don't actually contribute to the solid (such as those solids created // by iterating through a binary tree, and keeping track of all node subdivisions). // This finds them, returns a list of their indices as an array. I could return // a MAPBrush with those planes culled, but oftentimes there are two or three // brushes with the same unnecessary planes. public static int[] findUnusedPlanes(MAPBrush in_Renamed) { Plane[] thePlanes = in_Renamed.Planes; //Console.Write("Finding unnecessary planes. Before: " + thePlanes.Length); // Step 1: Get all points of intersection double numVerts = 4; // Iterative nCr algorithm; thanks to Alex's code for (int i = thePlanes.Length; i > 4; i--) { numVerts *= (double) i / (double) (i - 3); } //UPGRADE_TODO: Method 'java.lang.Math.round' was converted to 'System.Math.Round' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextSettingsIndex'&keyword='jlca1073_javalangMathround_double'" Vector3D[] theVerts = new Vector3D[(int) System.Math.Round(numVerts)]; int index = 0; for (int i = 0; i < thePlanes.Length - 2; i++) { for (int j = i + 1; j < thePlanes.Length - 1; j++) { for (int k = j + 1; k < thePlanes.Length; k++) { theVerts[index++] = thePlanes[i].trisect(thePlanes[j], thePlanes[k]); } } } // Step 2: Throw out all vertices on the wrong side of any plane, since they're // all facing the "right" way. for (int i = 0; i < theVerts.Length; i++) { for (int j = 0; j < thePlanes.Length; j++) { if (thePlanes[j].distance(theVerts[i]) > Settings.precision) { theVerts[i] = Vector3D.UNDEFINED; break; //break the inner loop, let the outer loop iterate } } } // Step 3: Only keep sides which have three or more vertices defined int[] badSides = new int[0]; for (int i = 0; i < thePlanes.Length; i++) { int numMatches = 0; Vector3D[] matches = new Vector3D[3]; for (int j = 0; j < theVerts.Length; j++) { if (System.Math.Abs(thePlanes[i].distance(theVerts[j])) < Settings.precision) { bool duplicate = false; for (int k = 0; k < numMatches; k++) { if (theVerts[j]==matches[k]) { duplicate = true; } } if (!duplicate) { matches[numMatches] = theVerts[j]; numMatches++; } } if (numMatches >= 3) { // We have enough points. break; } } if (numMatches < 3) { int[] newList = new int[badSides.Length + 1]; for (int j = 0; j < badSides.Length; j++) { newList[j] = badSides[j]; } newList[newList.Length - 1] = i; badSides = newList; } } //DecompilerThread.OnMessage(this, " After: " + (thePlanes.Length - badSides.Length)); return badSides; }
// 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; }
private byte[] brushToByteArray(MAPBrush inMapBrush) { if (inMapBrush.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inMapBrush.NumSides + " sides!"); return new byte[0]; } string brush = (char) 0x09 + "solid" + (char) 0x0D + (char) 0x0A + (char) 0x09 + "{" + (char) 0x0D + (char) 0x0A + (char) 0x09 + (char) 0x09 + "\"id\" \"" + (nextID++) + "\"" + (char) 0x0D + (char) 0x0A; for (int i = 0; i < inMapBrush.NumSides; i++) { brush += brushSideToString(inMapBrush[i]); } brush += ((char) 0x09 + "}" + (char) 0x0D + (char) 0x0A); if (brush.Length < 40) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return new byte[0]; } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte) brush[i]; } return brushbytes; } }
// METHODS // +decompile() // Attempts to convert the BSP file back into a .MAP file. public virtual Entities decompile() { DecompilerThread.OnMessage(this, "Decompiling..."); // In the decompiler, it is not necessary to copy all entities to a new object, since // no writing is ever done back to the BSP file. mapFile = BSPObject.Entities; int numTotalItems = 0; worldspawn = mapFile[mapFile.findAllWithAttribute("classname", "worldspawn")[0]]; int onePercent = (int)((BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)/100); if(onePercent < 1) { onePercent = 1; } // I need to go through each entity and see if it's brush-based. // Worldspawn is brush-based as well as any entity with model *#. for (int i = 0; i < BSPObject.Entities.Count; i++) { // For each entity //DecompilerThread.OnMessage(this, "Entity " + i + ": " + mapFile[i]["classname"]); numBrshs = 0; // Reset the brush count for each entity // getModelNumber() returns 0 for worldspawn, the *# for brush based entities, and -1 for everything else int currentModel = mapFile[i].ModelNumber; if (currentModel > - 1) { // If this is still -1 then it's strictly a point-based entity. Move on to the next one. int firstBrush = BSPObject.Models[currentModel].FirstBrush; int numBrushes = BSPObject.Models[currentModel].NumBrushes; numBrshs = 0; for (int j = 0; j < numBrushes; j++) { // For each brush //Console.Write("Brush " + j); decompileBrush(BSPObject.Brushes[j + firstBrush], i); // Decompile the brush numBrshs++; numTotalItems++; if(numTotalItems%onePercent == 0) { parent.OnProgress(this, numTotalItems/(double)(BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)); } } } numTotalItems++; if(numTotalItems%onePercent == 0) { parent.OnProgress(this, numTotalItems/(double)(BSPObject.Brushes.Count + BSPObject.Entities.Count + BSPObject.Faces.Count)); } } if (!Settings.skipPlaneFlip) { DecompilerThread.OnMessage(this, "Num simple corrected brushes: " + numSimpleCorrects); DecompilerThread.OnMessage(this, "Num advanced corrected brushes: " + numAdvancedCorrects); DecompilerThread.OnMessage(this, "Num good brushes: " + numGoodBrushes); } foreach(Face face in BSPObject.Faces) { if(face.Facetype == Face.faceType.PATCH) { MAPPatch mapPatch = new MAPPatch(face.PatchSize[0], face.PatchSize[1], BSPObject.Textures[face.Texture].Name); for(int i=0; i<face.NumVertices; i++) { mapPatch.Add(BSPObject.Vertices[face.FirstVertex+i]); } MAPBrush mapBrush = new MAPBrush(mapPatch); worldspawn.Brushes.Add(mapBrush); } } parent.OnProgress(this, 1.0); return mapFile; }
// Calculates 3 face corners, to be used to define the plane in ASCII format. /// Author: UltimateSniper /// Returns: List of normalised plane vertex triplets. public static MAPBrush CalcBrushVertices(MAPBrush mapBrush) { //DecompilerThread.OnMessage(this, "Recalculating vertices"); Plane[] planes = mapBrush.Planes; Vector3D[][] out_Renamed = new Vector3D[planes.Length][]; // For each triplet of planes, find intersect point. for (int iP1 = 0; iP1 < planes.Length; iP1++) { for (int iP2 = iP1 + 1; iP2 < planes.Length; iP2++) { for (int iP3 = iP2 + 1; iP3 < planes.Length; iP3++) { Vector3D testV = planes[iP1].trisect(planes[iP2], planes[iP3]); if (testV != Vector3D.UNDEFINED) { bool isCorner = true; // If point is not null, test if point is behind/on all planes (if so, it is a corner). for (int iTest = 0; iTest < planes.Length; iTest++) { if (planes[iTest].Normal != planes[iP1].Normal && planes[iTest].Normal != planes[iP2].Normal && planes[iTest].Normal != planes[iP3].Normal) { if (planes[iTest].distance(testV) > Settings.precision) { isCorner = false; break; } } } // If so, check which planes it is on. if (isCorner) { for (int iChk = 0; iChk < planes.Length; iChk++) { // If on this plane, and plane's vertex triplet missing min 1 point (and does not already have this point), add it. double dist = planes[iChk].distance(testV); if (System.Math.Abs(dist) <= Settings.precision) { // If first point on this plane, must create array. if (out_Renamed[iChk] == null) { out_Renamed[iChk] = new Vector3D[] { new Vector3D(testV), null, null }; } else { // Check each value in the array for open spot OR identical point. for (int iChk2 = 0; iChk2 < 3; iChk2++) { // Open spot, fill it. if (out_Renamed[iChk][iChk2] == null) { out_Renamed[iChk][iChk2] = new Vector3D(testV); // If this is now a complete plane. if (iChk2 == 2) { // Order complete triplet to make a plane facing same way as given plane. Plane testP = new Plane(out_Renamed[iChk][0], out_Renamed[iChk][1], out_Renamed[iChk][2]); // If normals are not pointing in same direction, re-order points. if (testP.Normal * planes[iChk].Normal < 0) { Vector3D temp = new Vector3D(out_Renamed[iChk][1]); out_Renamed[iChk][1] = new Vector3D(out_Renamed[iChk][2]); out_Renamed[iChk][2] = temp; } } break; // Else, if this list already has this point, skip out (to avoid doubling it). } else if (out_Renamed[iChk][iChk2] == testV) { break; } } } } } } } } } } for (int i = 0; i < mapBrush.NumSides; i++) { mapBrush[i].setSide(mapBrush[i].Plane, out_Renamed[i]); } return(mapBrush); }
private byte[] brushToByteArray(MAPBrush inBrush, int num) { if (inBrush.NumSides < 4) { // Can't create a brush with less than 4 sides DecompilerThread.OnMessage(this, "WARNING: Tried to create brush from " + inBrush.NumSides + " sides!"); return new byte[0]; } string brush = "// primitive " + num + (char) 0x0A + "{" + (char) 0x0A + " brushDef3" + (char) 0x0A + " {" + (char) 0x0A; for (int i = 0; i < inBrush.NumSides; i++) { brush += (" " + brushSideToString(inBrush[i]) + (char) 0x0A); } brush += (" }" + (char) 0x0A + "}" + (char) 0x0A); if (brush.Length < 58) { // Any brush this short contains no sides. DecompilerThread.OnMessage(this, "WARNING: Brush with no sides being written! Oh no!"); return new byte[0]; } else { byte[] brushbytes = new byte[brush.Length]; for (int i = 0; i < brush.Length; i++) { brushbytes[i] = (byte) brush[i]; } return brushbytes; } }
// -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); } }