public void ClassifyFace(BSPFace face, Vector3 planePoint, Vector3 planeNorm) { face.Classification = BSPClassification.OnPlane; foreach (BSPVertex point in face.Points) { ClassifyPoint(point, planePoint, planeNorm); if (point.Classification != face.Classification) { if (face.Classification == BSPClassification.OnPlane) { face.Classification = point.Classification; } else if (point.Classification != BSPClassification.OnPlane) { face.Classification = BSPClassification.Spanning; return; } } } if (face.Classification == BSPClassification.OnPlane) //Place coplanar faces on the front side of the plane if it is facing the same direction { if (Vector3.Dot(face.Normal, planeNorm) >= 0) { face.Classification = BSPClassification.Front; } else { face.Classification = BSPClassification.Back; } } }
public BSPFace FindSplitter(List <BSPFace> faces, ref Vector3 planePoint, ref Vector3 planeNorm) { int bestScore = int.MaxValue; BSPFace bestFace = null; int score; foreach (BSPFace potential in faces) { #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 2) { Console.WriteLine("Evalulating splitter {0}.", faces.IndexOf(potential)); } #endif score = EvalulateSplitter(faces, potential); if (score < bestScore) { bestScore = score; bestFace = potential; } } if (bestFace != null) { planePoint = bestFace.Point; planeNorm = bestFace.Normal; } #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 2) { if (bestFace != null) { Console.WriteLine("Best face is {0}.", faces.IndexOf(bestFace)); } else { Console.WriteLine("This object is convex."); } } #endif return(bestFace); }
public void SplitPolygon(BSPFace face, Vector3 planePoint, Vector3 planeNorm, ref BSPFace front, ref BSPFace back) { front.TextureID = face.TextureID; back.TextureID = face.TextureID; BSPVertex firstPoint = face.Points[0]; if (firstPoint.Classification == BSPClassification.OnPlane) { front.Points.Add(firstPoint); back.Points.Add(firstPoint); } else if (firstPoint.Classification == BSPClassification.Front) { front.Points.Add(firstPoint); } else { back.Points.Add(firstPoint); } int current = 0; BSPVertex vert1, vert2; for (int i = 1; i < face.Points.Count + 1; i++) { if (i == face.Points.Count) { current = 0; } else { current = i; } vert1 = face.Points[i - 1]; vert2 = face.Points[current]; ClassifyPoint(vert2, planePoint, planeNorm); if (vert2.Classification == BSPClassification.OnPlane) { front.Points.Add(vert2); back.Points.Add(vert2); } else { Vector3 intersect = new Vector3(); float percentage = 0.0f; bool split = SplitEdge(vert1.Point, vert2.Point, planePoint, planeNorm, ref percentage, ref intersect); if (split) { Vector3 texDelta = vert2.UVs - vert1.UVs; BSPVertex newVert = new BSPVertex { Classification = BSPClassification.OnPlane, Point = intersect, UVs = texDelta * percentage + vert1.UVs }; if (vert2.Classification == BSPClassification.Front) { back.Points.Add(newVert); front.Points.Add(newVert); front.Points.Add(vert2); } else if (vert2.Classification == BSPClassification.Back) { front.Points.Add(newVert); back.Points.Add(newVert); back.Points.Add(vert2); } } else { if (vert2.Classification == BSPClassification.Front) { front.Points.Add(vert2); } else if (vert2.Classification == BSPClassification.Back) { back.Points.Add(vert2); } } } } //TODO: This isn't always accurate at extreme splits. front.Normal = face.Normal; back.Normal = face.Normal; //front.CalculateNormal(); front.CalculateCenter(); //back.CalculateNormal(); back.CalculateCenter(); }
public int EvalulateSplitter(List <BSPFace> faces, BSPFace splitter) { int numFront = 0, numBack = 0, numSplits = 0; //int numVertsFront = 0, numVertsBack = 0; foreach (BSPFace face in faces) { if (face == splitter) //Bugfix? Splitter is always on front side. { numFront++; continue; } ClassifyFace(face, splitter.Point, splitter.Normal); switch (face.Classification) { case BSPClassification.Front: numFront++; break; case BSPClassification.Back: numBack++; break; case BSPClassification.Spanning: numFront++; numBack++; numSplits++; break; } /*foreach (BSPVertex vert in face.Points) * { * if (vert.Classification == BSPClassification.Back) * numVertsBack++; * else if (vert.Classification == BSPClassification.Front) * numVertsFront++; * else if (vert.Classification == BSPClassification.OnPlane) * { * numVertsFront++; * } * }*/ } int newFaces = (numFront + numBack) - faces.Count; if (numSplits == 0 && (numFront == 0 || numBack == 0)) //If everything is on one side of this splitter, it has no value. { #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 2) { Console.WriteLine("\tSplitter is not needed."); } #endif return(int.MaxValue); } #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 2) { Console.WriteLine("\tSplitter score is {0} (Base:{1}, f:{2} b:{3} s:{4})", Math.Abs(numFront - numBack) + (numSplits * 8), faces.Count, numFront, numBack, numSplits); } #endif return(Math.Abs(numFront - numBack) + (numSplits * 8)); //return Math.Abs(numVertsFront - numVertsBack) + (numSplits * 6); //return Math.Max(numFront, numBack) + (newFaces * 24); }
public void BuildTree(BSPNode node, List <BSPFace> faces) { #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel == 0) { Console.WriteLine("----------------------------------------------"); Console.WriteLine("STARTING"); Console.WriteLine("----------------------------------------------"); } recursionLevel++; #endif List <BSPFace> frontList = new List <BSPFace>(); List <BSPFace> backList = new List <BSPFace>(); node.Splitter = FindSplitter(faces, ref node.Point, ref node.Normal); if (node.Splitter == null) //If a splitter wasn't found, this set of faces is convex { node.faces = faces; node.type = BSPNodeType.Leaf; } else //A splitter is known, so do any needed splits and recurse { foreach (BSPFace face in faces) { if (face != node.Splitter) //splitter is classified and added later. { ClassifyFace(face, node.Splitter.Point, node.Splitter.Normal); //else // //[ISB] Fix bug with splitters ending up on both sides. Doom puts them in front implicity // face.Classification = BSPClassification.Front; switch (face.Classification) { case BSPClassification.Front: frontList.Add(face); break; case BSPClassification.Back: backList.Add(face); break; case BSPClassification.Spanning: BSPFace frontFace = new BSPFace(); BSPFace backFace = new BSPFace(); if (face.TextureID == -1) // colored face { frontFace.Color = backFace.Color = face.Color; } SplitPolygon(face, node.Splitter.Point, node.Splitter.Normal, ref frontFace, ref backFace); frontList.Add(frontFace); backList.Add(backFace); break; default: throw new Exception("BSPTree::BuildTree: Face has invalid classification."); } } } //Where does the splitter go? //Try it in both the front and back. Whichever one generates the highest score fails. int frontScore = 0; int backScore = 0; int tempScore; List <BSPFace> faceTemp; if (frontList.Count > 0) { faceTemp = new List <BSPFace>(frontList); faceTemp.Add(node.Splitter); foreach (BSPFace face in faceTemp) { tempScore = EvalulateSplitter(faceTemp, face); if (tempScore != int.MaxValue && tempScore > frontScore) { frontScore = tempScore; } } } else { frontScore = int.MaxValue; } if (backList.Count > 0) { faceTemp = new List <BSPFace>(backList); faceTemp.Add(node.Splitter); foreach (BSPFace face in faceTemp) { tempScore = EvalulateSplitter(faceTemp, face); if (tempScore != int.MaxValue && tempScore > backScore) { backScore = tempScore; } } } else { backScore = int.MaxValue; } if (frontScore > backScore) { frontList.Add(node.Splitter); } else { backList.Add(node.Splitter); } #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 2) { Console.WriteLine("Frontlist:{0} Backlist:{1}, fs<bs:{2}", frontList.Count, backList.Count, frontScore < backScore); } #endif if (frontList.Count > 0) { BSPNode newNode = new BSPNode(); newNode.type = BSPNodeType.Node; #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 1) { Console.WriteLine("Doing front."); } #endif BuildTree(newNode, frontList); node.Front = newNode; } if (backList.Count > 0) { BSPNode newNode = new BSPNode(); newNode.type = BSPNodeType.Node; #if DEBUG_SPLITTER_DIAGONISTICS if (recursionLevel <= 1) { Console.WriteLine("Doing back."); } #endif BuildTree(newNode, backList); node.Back = newNode; } } #if DEBUG_SPLITTER_DIAGONISTICS recursionLevel--; #endif }
private void Execute(byte[] data, int offset, Polymodel mainModel, Submodel model, BSPModel currentModel) { short instruction = GetShort(data, ref offset); while (true) { switch (instruction) { case ModelOpCode.End: return; case ModelOpCode.Points: { short pointc = GetShort(data, ref offset); for (int i = 0; i < pointc; i++) { interpPoints[i] = GetFixVector(data, ref offset); } } break; case ModelOpCode.FlatPoly: //FLATPOLY { short pointc = GetShort(data, ref offset); FixVector point = GetFixVector(data, ref offset); FixVector normal = GetFixVector(data, ref offset); short color = GetShort(data, ref offset); short[] points = new short[pointc]; //TODO: seems wasteful to do all these allocations? for (int i = 0; i < pointc; i++) { points[i] = GetShort(data, ref offset); } if (pointc % 2 == 0) { GetShort(data, ref offset); } if (pointc >= 3) { var triangle = new BSPFace(); triangle.Normal = new Vector3(normal.X, normal.Y, normal.Z); triangle.Point = new Vector3(point.X, point.Y, point.Z); triangle.Color = color; triangle.TextureID = -1; currentModel.Polygons.Add(triangle); for (int i = 0; i < pointc; i++) { var vxA = interpPoints[points[i]].X; var vyA = interpPoints[points[i]].Y; var vzA = interpPoints[points[i]].Z; triangle.Points.Add(new BSPVertex { Point = new Vector3(vxA, vyA, vzA), UVs = new Vector3(0.0f, 0.0f, 0.0f) }); } } } break; case ModelOpCode.TexturedPoly: //TMAPPOLY { short pointc = GetShort(data, ref offset); FixVector point = GetFixVector(data, ref offset); FixVector normal = GetFixVector(data, ref offset); short texture = GetShort(data, ref offset); short[] points = new short[pointc]; //TODO: seems wasteful to do all these allocations? FixVector[] uvls = new FixVector[pointc]; for (int i = 0; i < pointc; i++) { points[i] = GetShort(data, ref offset); } if (pointc % 2 == 0) { GetShort(data, ref offset); } for (int i = 0; i < pointc; i++) { uvls[i] = GetFixVector(data, ref offset); } if (pointc >= 3) { var triangle = new BSPFace(); triangle.Normal = new Vector3(normal.X, normal.Y, normal.Z); triangle.Point = new Vector3(point.X, point.Y, point.Z); triangle.TextureID = texture; currentModel.Polygons.Add(triangle); for (int i = 0; i < pointc; i++) { var vxA = interpPoints[points[i]].X; var vyA = interpPoints[points[i]].Y; var vzA = interpPoints[points[i]].Z; var uvxA = uvls[i].X; var uvyA = uvls[i].Y; triangle.Points.Add(new BSPVertex { Point = new Vector3(vxA, vyA, vzA), UVs = new Vector3(uvxA, uvyA, 0.0f) }); } } } break; case ModelOpCode.SortNormal: //SORTNORM { IsPartitioned = true; int baseOffset = offset - 2; int n_points = GetShort(data, ref offset); FixVector norm = GetFixVector(data, ref offset); FixVector point = GetFixVector(data, ref offset); short backOffset = GetShort(data, ref offset); short frontOffset = GetShort(data, ref offset); Execute(data, baseOffset + frontOffset, mainModel, model, currentModel); Execute(data, baseOffset + backOffset, mainModel, model, currentModel); } break; case ModelOpCode.Rod: //RODBM { offset += 34; } break; case ModelOpCode.SubCall: //SUBCALL { int baseOffset = offset - 2; short submodelNum = GetShort(data, ref offset); FixVector submodelOffset = GetFixVector(data, ref offset); short modelOffset = GetShort(data, ref offset); offset += 2; Submodel newModel = mainModel.Submodels[submodelNum]; currentModel.modelOffset = submodelOffset; Execute(data, baseOffset + modelOffset, mainModel, newModel, modelDatas[submodelNum]); } break; case ModelOpCode.DefinePointStart: //DEFPSTART { short pointc = GetShort(data, ref offset); short firstPoint = GetShort(data, ref offset); offset += 2; for (int i = 0; i < pointc; i++) { interpPoints[i + firstPoint] = GetFixVector(data, ref offset); } } break; case ModelOpCode.Glow: offset += 2; break; default: throw new Exception(string.Format("Unknown interpreter instruction {0} at offset {1}\n", instruction, offset)); } instruction = GetShort(data, ref offset); } }