protected void HandleEndpointIntersectionSplit(BspSegment splitter, BspSegment segmentToSplit, Endpoint endpoint) { // We know that the endpoint argument is the vertex that was // intersected by the splitter. This means the other endpoint is // on the left or the right side of the splitter, so we'll use // that 'opposite' endpoint to check the side we should place the // segment on. BspVertex oppositeVertex = segmentToSplit.Opposite(endpoint); Rotation side = splitter.ToSide(oppositeVertex); Debug.Assert(side != Rotation.On, "Ambiguous split, segment too small to determine splitter side"); if (side == Rotation.Right) { States.RightSegments.Add(segmentToSplit); } else { States.LeftSegments.Add(segmentToSplit); } BspVertex vertex = segmentToSplit.VertexFrom(endpoint); States.CollinearVertices.Add(vertex); }
protected void HandleCollinearSegment(BspSegment splitter, BspSegment segment) { Debug.Assert(!segment.IsMiniseg, "Should never be collinear to a miniseg"); States.CollinearVertices.Add(segment.StartVertex); States.CollinearVertices.Add(segment.EndVertex); // We don't want the back side of a one-sided line (which doesn't // exist) to be visible to the side of the partition that shouldn't // care about it. if (segment.OneSided) { if (splitter.SameDirection(segment)) { States.RightSegments.Add(segment); } else { States.LeftSegments.Add(segment); } } else { States.RightSegments.Add(segment); States.LeftSegments.Add(segment); } }
private static List <SubsectorEdge> CreateSubsectorEdges(ConvexTraversal convexTraversal, Rotation rotation) { List <ConvexTraversalPoint> traversal = convexTraversal.Traversal; Debug.Assert(traversal.Count >= 3, "Traversal must yield at least a triangle in size"); List <SubsectorEdge> subsectorEdges = new List <SubsectorEdge>(); ConvexTraversalPoint firstTraversal = traversal.First(); Vec2D startPoint = firstTraversal.Vertex; foreach (ConvexTraversalPoint traversalPoint in traversal) { BspSegment segment = traversalPoint.Segment; Vec2D endingPoint = segment.Opposite(traversalPoint.Endpoint).Struct(); bool traversedFrontSide = CheckIfTraversedFrontSide(traversalPoint, rotation); SubsectorEdge edge = new SubsectorEdge(startPoint, endingPoint, segment.Line.Value, traversedFrontSide); subsectorEdges.Add(edge); Debug.Assert(startPoint != endingPoint, "Traversal produced the wrong endpoint indices"); startPoint = endingPoint; } Debug.Assert(subsectorEdges.Count == traversal.Count, "Added too many subsector edges in traversal"); return(subsectorEdges); }
/// <summary> /// To be called every time a one sided segment is discovered when /// reading the valid map entry collection. After that, then the /// <see cref="NotifyDoneAdding"/> should be invoked. /// </summary> /// <remarks> /// This does not make any junctions, that has to be held off until /// later for both code cleanliness and performance reasons. /// </remarks> /// <param name="segment">The segment to track.</param> private void Add(BspSegment segment) { if (!segment.OneSided) { return; } if (!vertexToJunction.TryGetValue(segment.EndVertex, out Junction endJunction)) { endJunction = new Junction(); vertexToJunction.Add(segment.EndVertex, endJunction); } Debug.Assert(!endJunction.InboundSegments.Contains(segment), "Adding same segment to inbound junction twice"); endJunction.InboundSegments.Add(segment); if (!vertexToJunction.TryGetValue(segment.StartVertex, out Junction startJunction)) { startJunction = new Junction(); vertexToJunction.Add(segment.StartVertex, startJunction); } Debug.Assert(!startJunction.OutboundSegments.Contains(segment), "Adding same segment to outbound junction twice"); startJunction.OutboundSegments.Add(segment); }
public void Execute() { Debug.Assert(States.State != SplitterState.Finished, "Trying to run a split checker when finished"); Debug.Assert(States.CurrentSegmentIndex < States.Segments.Count, "Out of range split calculator segment index"); BspSegment splitter = States.Segments[States.CurrentSegmentIndex]; if (!seenCollinear.Get(splitter.CollinearIndex)) { States.CurrentSegScore = CalculateScore(splitter); if (States.CurrentSegScore < States.BestSegScore) { Debug.Assert(!splitter.IsMiniseg, "Should never be selecting a miniseg as a splitter"); States.BestSegScore = States.CurrentSegScore; States.BestSplitter = splitter; } seenCollinear.Set(splitter.CollinearIndex, true); } States.CurrentSegmentIndex++; bool hasSegmentsLeft = States.CurrentSegmentIndex < States.Segments.Count; States.State = (hasSegmentsLeft ? SplitterState.Working : SplitterState.Finished); }
private static double CalculateDistanceToNearestEndpoint(BspSegment segment, double tSegment) { Vec2D endpointVertex = (tSegment < 0.5 ? segment.Start.Struct() : segment.End.Struct()); Vec2D intersectionPoint = segment.FromTime(tSegment); return(endpointVertex.Distance(intersectionPoint)); }
/// <summary> /// Creates a wedge from the inbound and outbound segments. /// </summary> /// <param name="inbound">The inbound segment, which means its ending /// point is shared with the outbound starting vertex.</param> /// <param name="outbound">The outbound segment, which means its /// starting point matches the inbounds ending point.</param> public JunctionWedge(BspSegment inbound, BspSegment outbound) { Debug.Assert(inbound.EndIndex == outbound.StartIndex, "The inbound and outbound do not meet at a shared vertex"); Inbound = inbound; Outbound = outbound; Obtuse = !inbound.OnRight(outbound); }
protected void HandleSegmentOnSide(BspSegment splitter, BspSegment segmentToSplit) { if (splitter.OnRight(segmentToSplit)) { States.RightSegments.Add(segmentToSplit); } else { States.LeftSegments.Add(segmentToSplit); } }
public void Execute() { Debug.Assert(States.State != PartitionState.Finished, "Trying to partition when it's already completed"); if (States.Splitter == null) { throw new NullReferenceException("Unexpected null splitter"); } BspSegment splitter = States.Splitter; BspSegment segToSplit = States.SegsToSplit[States.CurrentSegToPartitionIndex]; States.CurrentSegToPartitionIndex++; bool doneAllSplitting = (States.CurrentSegToPartitionIndex >= States.SegsToSplit.Count); States.State = (doneAllSplitting ? PartitionState.Finished : PartitionState.Working); if (ReferenceEquals(segToSplit, splitter)) { HandleSplitter(splitter); return; } if (segToSplit.Parallel(splitter)) { HandleParallelSegment(splitter, segToSplit); return; } bool splits = splitter.IntersectionAsLine(segToSplit, out double splitterTime, out double segmentTime); Debug.Assert(splits, "A non-parallel line should intersect"); // Note that it is possible for two segments that share endpoints // to calculate intersections to each other outside of the normal // range due (ex: D2M29 has one at approx t = 1.0000000000000002). // Because they share a point and aren't parallel, then the line // must be on one of the sides and isn't intersecting. This will // also avoid very small cuts as well since we definitely do not // want a cut happening at the pathological time seen above! if (splitter.SharesAnyEndpoints(segToSplit)) { HandleSegmentOnSide(splitter, segToSplit); } else if (segmentTime.InNormalRange()) { HandleSplit(splitter, segToSplit, segmentTime, splitterTime); } else { HandleSegmentOnSide(splitter, segToSplit); } }
private void SetStateLoadedInfo(List <BspSegment> segments) { // We're just picking a random vertex, and taking some random segment that // comes out of that vertex. int randomVertexIndex = VertexMap.Keys.First(); ConvexTraversalPoint randomLinePoint = VertexMap[randomVertexIndex].First(); BspSegment startSegment = randomLinePoint.Segment; States.CurrentEndpoint = randomLinePoint.Endpoint; States.StartSegment = startSegment; States.CurrentSegment = startSegment; States.TotalSegs = segments.Count; }
protected void HandleSplit(BspSegment splitter, BspSegment segmentToSplit, double segmentTime, double splitterTime) { Debug.Assert(!BetweenEndpoints(splitterTime), "Should not have a segment crossing the splitter"); if (IntersectionTimeAtEndpoint(segmentToSplit, segmentTime, out Endpoint endpoint)) { HandleEndpointIntersectionSplit(splitter, segmentToSplit, endpoint); } else { HandleNonEndpointIntersectionSplit(splitter, segmentToSplit, segmentTime); } }
private static bool IsEffectivelyRightOfSplitter(BspSegment splitter, BspSegment segment) { Rotation startSide = splitter.ToSide(segment.Start); if (startSide == Rotation.On) { Rotation endSide = splitter.ToSide(segment.End); Debug.Assert(endSide != Rotation.On, "Splitter and segment are too close to determine sides"); return(endSide == Rotation.Right); } return(startSide == Rotation.Right); }
protected void HandleParallelSegment(BspSegment splitter, BspSegment segment) { if (splitter.Collinear(segment)) { HandleCollinearSegment(splitter, segment); } else if (splitter.OnRight(segment.Start)) { States.RightSegments.Add(segment); } else { States.LeftSegments.Add(segment); } }
public void Load(BspSegment splitter, HashSet <BspVertex> collinearVertices) { Debug.Assert(collinearVertices.Count >= 2, "Requires two or more vertices for miniseg generation (the splitter should have contributed two)"); States = new MinisegStates(); foreach (BspVertex vertex in collinearVertices) { double splitterTime = splitter.ToTime(vertex); VertexSplitterTime vertexSplitterTime = new VertexSplitterTime(vertex, splitterTime); States.Vertices.Add(vertexSplitterTime); } States.Vertices.Sort(); }
public void Load(BspSegment splitter, List <BspSegment> segments) { if (splitter == null) { throw new NullReferenceException("Invalid split calculator state (likely convex polygon that was classified as splittable wrongly)"); } Debug.Assert(!splitter.IsMiniseg, "Cannot have a miniseg as a splitter"); // OPTIMIZE: A better way of finding segments to split would be to // take the result when we did the checking for the best splitter // and remember having all the segments to be split for the best // segment. We throw away all that work, and then do an O(n) check // again on top of it. States = new PartitionStates { Splitter = splitter, SegsToSplit = segments }; }
private void AddSegmentEndpoint(BspSegment segment, int index, Endpoint endpoint) { ConvexTraversalPoint linePoint = new ConvexTraversalPoint(segment, endpoint); if (VertexMap.TryGetValue(index, out List <ConvexTraversalPoint> linePoints)) { linePoints.Add(linePoint); VertexTracker.Track(linePoints.Count); } else { List <ConvexTraversalPoint> newLinePoints = new List <ConvexTraversalPoint> { linePoint }; VertexMap.Add(index, newLinePoints); VertexTracker.Track(newLinePoints.Count); } }
private List <BspSegment> ReadLinesFrom(MapData map) { List <BspSegment> segments = new List <BspSegment>(); foreach (MapLinedef line in map.Linedefs) { MapVertex startMapVertex = map.Vertices[line.StartVertex]; MapVertex endMapVertex = map.Vertices[line.EndVertex]; BspVertex start = VertexAllocator[startMapVertex.Struct()]; BspVertex end = VertexAllocator[endMapVertex.Struct()]; BspSegment segment = SegmentAllocator.GetOrCreate(start, end, line); segments.Add(segment); } return(segments); }
protected bool IntersectionTimeAtEndpoint(BspSegment segmentToSplit, double segmentTime, out Endpoint endpoint) { // Note that we cannot attempt to look up the endpoint, because it // is possible that it may detect an unrelated vertex from a split // on another side which happens to be right where the split would // happen. // // For example, suppose a perpendicular split is going to occur. // The vertical splitter line would intersect it like so at the X: // // o // | Splitter // o // // o----X----o // Segment // // Now suppose that the bottom side of `Segment` was recursed on // first, and some splitter happened to already create a vertex at // the exact point that `Splitter` would hit `Segment` on the X. If // we were to check the vertex allocator for whether a vertex is // near/at X or not, it will return true. However this split will // clearly not be hitting an endpoint of `Segment`, so we'd get a // 'true' result when it is definitely not intersecting at/near an // endpoint. // // Therefore our only solution is to do it by checking distances. Vec2D vertex = segmentToSplit.FromTime(segmentTime); if (vertex.Distance(segmentToSplit.Start) <= BspConfig.VertexWeldingEpsilon) { endpoint = Endpoint.Start; return(true); } if (vertex.Distance(segmentToSplit.End) <= BspConfig.VertexWeldingEpsilon) { endpoint = Endpoint.End; return(true); } endpoint = default; return(false); }
private void ExecuteSegmentPartitioning() { switch (Partitioner.States.State) { case PartitionState.Loaded: case PartitionState.Working: Partitioner.Execute(); break; case PartitionState.Finished: if (Partitioner.States.Splitter == null) { throw new NullReferenceException("Unexpected null partition splitter"); } BspSegment splitter = Partitioner.States.Splitter; MinisegCreator.Load(splitter, Partitioner.States.CollinearVertices); State = BspState.GeneratingMinisegs; break; } }
/// <summary> /// A function called during BSP partitioning where we create a new /// junction when we split a one sided line in two. /// </summary> /// <param name="inboundSegment">The inbound segment.</param> /// <param name="outboundSegment">The outbound segment.</param> public void AddSplitJunction(BspSegment inboundSegment, BspSegment outboundSegment) { Debug.Assert(!ReferenceEquals(inboundSegment, outboundSegment), "Trying to add the same segment as an inbound/outbound junction"); BspVertex middleVertex = inboundSegment.EndVertex; Debug.Assert(outboundSegment.StartVertex == middleVertex, "Adding split junction where inbound/outbound segs are not connected"); Debug.Assert(!vertexToJunction.ContainsKey(middleVertex), "When creating a split, the middle vertex shouldn't already exist as a junction"); // We create new junctions because this function is called from a // newly created split. This means the middle vertex is new and the // junction cannot exist by virtue of the pivot point never having // existed. Junction junction = new Junction(); vertexToJunction[middleVertex] = junction; junction.InboundSegments.Add(inboundSegment); junction.OutboundSegments.Add(outboundSegment); junction.AddWedge(inboundSegment, outboundSegment); }
protected void HandleSplitter(BspSegment splitter) { // The reason we add these is because we (frequently) have cases // where there is only one intersection from the splitter to some // other line. Usually we relied on the endpoints of lines counting // as an intersection and causing them to end up in the collinear // vertex set, but we had to change that due to rounding issues. // // This change meant that it no longer recognized the endpoints of // the lines attached to the splitter as ones that will end up in // the collinear set. We have to provide these ones here so that // miniseg generation has at least one reference point. States.CollinearVertices.Add(splitter.StartVertex); States.CollinearVertices.Add(splitter.EndVertex); States.RightSegments.Add(splitter); if (splitter.TwoSided) { States.LeftSegments.Add(splitter); } }
protected void HandleMinisegGeneration(BspVertex first, BspVertex second) { States.VoidStatus = VoidStatus.NotInVoid; // If a segment exists for the vertices then we're walking along a // segment that was collinear with the splitter, so we don't need a // miniseg. if (SegmentAllocator.ContainsSegment(first, second)) { return; } if (JunctionClassifier.CheckCrossingVoid(first, second)) { States.VoidStatus = VoidStatus.InVoid; } else { BspSegment miniseg = SegmentAllocator.GetOrCreate(first, second); States.Minisegs.Add(miniseg); } }
/// <summary> /// Calculates a 'score', which is an arbitrary number that tells us /// how close the angle is for the wedge provided. /// </summary> /// <remarks> /// For example, if there were 3 segs A, B, and C, where we were /// trying to see whether AB has a smaller wedge than AC, if the /// score of AB is less than that of AC, it has a smaller angle. /// </remarks> /// <param name="inbound">The inbound segment.</param> /// <param name="outbound">The outbound segment.</param> /// <returns>A score that is to be used only for ordering reasons /// (where a lower score means it's a tighter angle than a larger /// score).</returns> private static double CalculateAngleScore(BspSegment inbound, BspSegment outbound) { Vec2D endToOriginPoint = (inbound.Start - inbound.End).Struct(); Vec2D startToOriginPoint = (outbound.End - outbound.Start).Struct(); double dot = startToOriginPoint.Dot(endToOriginPoint); double length = startToOriginPoint.Length() * endToOriginPoint.Length(); double cosTheta = dot / length; double score = cosTheta; if (inbound.OnRight(outbound.End)) { score = -score; } else { score += 2.0; } return(score); }
private bool IsProperlyConnectedEndpoint(BspSegment segment, Endpoint endpoint) { if (Traversal.Empty()) { return(true); } ConvexTraversalPoint lastPoint = Traversal.Last(); BspSegment lastSeg = lastPoint.Segment; Endpoint lastEndpoint = lastPoint.Endpoint; if (ReferenceEquals(segment, lastSeg)) { Debug.Assert(false, "Trying to add the same segment twice"); return(false); } // Because our traversal uses the first endpoint we reached, that // means the last segment's opposite endpoint should match this // segment's current endpoint. Graphically, this means: // // LastEndpoint Endpoint // o-----------o------------o // LastSeg Segment // // Notice that the opposite endpoint of LastEndpoint is equal to // the middle vertex labeled Endpoint. This is what we want to make // sure are equal since it is not a valid traversal if they do not // match. if (segment.IndexFrom(endpoint) != lastSeg.OppositeIndex(lastEndpoint)) { Debug.Assert(false, "Expect a tail-to-head connection"); return(false); } return(true); }
/// <summary> /// Generates all the wedges from the tracked in/outbound segments. /// </summary> public void GenerateWedges() { // TODO: Is there a way we can get rid of having to explicitly call // this function? I don't like 'having to know to call functions' // on a class to make it work. Why not generate it lazily as we need // it? Debug.Assert(Wedges.Empty(), "Trying to create BSP junction wedges when they already were made"); // This is a guard against malformed/dangling one-sided lines. if (OutboundSegments.Empty()) { return; } // TODO: Since we know we have one or more outbound segments, we // can ignore the check at the start. Any way LINQ can help // us write this in a lot less lines of code? foreach (BspSegment inbound in InboundSegments) { BspSegment closestOutbound = OutboundSegments[0]; double bestScore = CalculateAngleScore(inbound, closestOutbound); for (int i = 1; i < OutboundSegments.Count; i++) { double score = CalculateAngleScore(inbound, OutboundSegments[i]); if (score < bestScore) { bestScore = score; closestOutbound = OutboundSegments[i]; } } Wedges.Add(new JunctionWedge(inbound, closestOutbound)); } }
protected void HandleNonEndpointIntersectionSplit(BspSegment splitter, BspSegment segmentToSplit, double segmentTime) { // Note for the future: If we ever change to using pointers or some // kind of reference that invalidates on a list resizing (like a // std::vector in C++), this will lead to undefined behavior since // it will add new segments and cause them to become dangling. (BspSegment segA, BspSegment segB) = SegmentAllocator.Split(segmentToSplit, segmentTime); // Since we split the segment such that the line is: // // Seg A | Seg B // [A]=======o=========[B] // | // | <-- Splitter // X // // We can find which side of the splitter the segments are on by // looking at where [A] or [B] ended up. Since the split causes us // to know that the middle index is at the first segment's endpoint // (and the second segment start point), either or of these would // tell us which side the segment is on... and when we know which // one is on which side of the splitter, we automatically know the // other segment from the split is on the other. Rotation side = splitter.ToSide(segA.Start); // It may be possible that a split occurs so close to the endpoint // (by some unfortunate map geometry) which screws up detecting // which side we are on for the splitting. In such a case, we'll // try checking both endpoints to see if this is the case and make // our decision accordingly. if (side == Rotation.On) { Log.Warn("Very tight endpoint split perform with segment {0}", segmentToSplit); Rotation otherSide = splitter.ToSide(segmentToSplit.End); Debug.Assert(otherSide != Rotation.On, "Segment being split is too small to detect which side of the splitter it's on"); side = (otherSide == Rotation.Left ? Rotation.Right : Rotation.Left); } if (side == Rotation.Right) { States.RightSegments.Add(segA); States.LeftSegments.Add(segB); } else { States.LeftSegments.Add(segA); States.RightSegments.Add(segB); } // The reason we know it's the end index of segA is because the // splitter always has the first seg splitting based on the start. // A corollary is that this would be equal to segB.getStartIndex() // since they are equal. States.CollinearVertices.Add(segA.EndVertex); if (segmentToSplit.OneSided) { JunctionClassifier.AddSplitJunction(segA, segB); } }
/// <summary> /// Adds a new segment to the traversal. It is assumed that this is a /// valid addition. /// </summary> /// <param name="segment">The segment visited.</param> /// <param name="endpoint">The first vertex endpoint that was visited /// when traversing the convex subsector.</param> public void AddTraversal(BspSegment segment, Endpoint endpoint) { Debug.Assert(IsProperlyConnectedEndpoint(segment, endpoint), "Provided a disconnected segment"); Traversal.Add(new ConvexTraversalPoint(segment, endpoint)); }
/// <summary> /// Creates a parent node from some split. /// </summary> /// <param name="left">The left child.</param> /// <param name="right">The right child.</param> /// <param name="splitter">The splitter that divided the children. /// </param> public BspNode(BspNode left, BspNode right, BspSegment splitter) { Left = left; Right = right; Splitter = splitter; }
/// <summary> /// Adds a wedge, which implies we know that this is a valid segment. /// </summary> /// <remarks> /// This is intended for when a one-sided segment is split by some /// splitter, since we know that new split point will create a wedge. /// </remarks> /// <param name="inbound">The inbound segment.</param> /// <param name="outbound">The outbound segment.</param> public void AddWedge(BspSegment inbound, BspSegment outbound) { Wedges.Add(new JunctionWedge(inbound, outbound)); }
/// <summary> /// Creates a new traversal point. /// </summary> /// <param name="segment">The segment for the point we are referencing. /// </param> /// <param name="endpoint">The endpoint on the segment we are /// referencing.</param> public ConvexTraversalPoint(BspSegment segment, Endpoint endpoint) { Segment = segment; Endpoint = endpoint; Vertex = Segment.VertexFrom(Endpoint).Struct(); }