// Note: Holes in polytree are in reverse clockness than lands. private static IntVector2[] FindContourWaypoints(this PolyNode node) { if (node.visibilityGraphNodeData.ContourWaypoints != null) { return(node.visibilityGraphNodeData.ContourWaypoints); } var results = new List <IntVector2>(); var contour = node.Contour; var contourIsOpen = contour.First() != contour.Last(); var pointCount = contourIsOpen ? node.Contour.Count : node.Contour.Count - 1; for (var i = 0; i < pointCount; i++) { var a = contour[i]; var b = contour[(i + 1) % pointCount]; var c = contour[(i + 2) % pointCount]; var clockness = GeometryOperations.Clockness(a.X, a.Y, b.X, b.Y, c.X, c.Y); if (clockness == Clockness.CounterClockwise) { results.Add(b); } } return(node.visibilityGraphNodeData.ContourWaypoints = results.ToArray()); }
private static void RenderVisualizationFrame() { var segments = Util.Generate(200, RandomSegment); // var quad = new[] { new IntVector2(100, 100), new IntVector2(200, 200), new IntVector2(300, 300), new IntVector2(400, 400) }; var quad = Util.Generate(4, RandomPoint); var hull = GeometryOperations.ConvexHull4(quad[0], quad[1], quad[2], quad[3]); var canvas = host.CreateAndAddCanvas(frameCounter++); canvas.BatchDraw(() => { foreach (var(x, y) in quad.Zip(quad.RotateLeft())) { canvas.DrawLine(x, y, StrokeStyle.BlackHairLineDashed5); } foreach (var(x, y) in hull.Zip(hull.RotateLeft())) { canvas.DrawLine(x, y, StrokeStyle.BlackHairLineSolid); } foreach (var p in hull) { canvas.DrawPoint(p, StrokeStyle.RedThick5Solid); } foreach (var s in segments) { canvas.DrawLine(s.First, s.Second, GeometryOperations.SegmentIntersectsConvexPolygonInterior(s, hull) ? StrokeStyle.LimeHairLineDashed5 : StrokeStyle.RedHairLineDashed5); } }); }
// Todo: Can we support DV2s? public int[] AddMany(DoubleLineSegment2 edgeSegment, IntVector2[] points) { Interlocked.Increment(ref AddManyInvocationCount); // return points.Map(p => 0); var segmentSeeingWaypoints = landPolyNode.ComputeSegmentSeeingWaypoints(edgeSegment); // It's safe to assume <some> point in points will be new, so preprocess which segments are betwen us and other edge segments var barriers = landPolyNode.FindContourAndChildHoleBarriers(); Dictionary <DoubleLineSegment2, IntLineSegment2[]> barriersBySegment = indicesBySegment.Map((s, _) => { Interlocked.Increment(ref AddManyConvexHullsComputed); var hull = GeometryOperations.ConvexHull4(s.First, s.Second, edgeSegment.First, edgeSegment.Second); return(barriers.Where(b => { var barrierDv2 = new DoubleLineSegment2(b.First.ToDoubleVector2(), b.Second.ToDoubleVector2()); return GeometryOperations.SegmentIntersectsConvexPolygonInterior(barrierDv2, hull); }).ToArray()); }); return(indicesBySegment[edgeSegment] = points.Map(p => { if (TryAdd(p, out var cpi)) { Interlocked.Increment(ref CrossoverPointsAdded); segmentByCrossoverPoint[p] = edgeSegment; } return cpi; })); bool TryAdd(IntVector2 crossoverPoint, out int crossoverPointIndex) { if (!crossoverPoints.TryAdd(crossoverPoint, out crossoverPointIndex)) { return(false); } var(visibleWaypointLinks, visibleWaypointLinksLength, optimalLinkToWaypoints, optimalLinkToCrossovers) = FindOptimalLinksToCrossovers(crossoverPoint, segmentSeeingWaypoints, barriersBySegment); // visibleWaypointLinksByCrossoverPointIndex.Add(visibleWaypointLinks); optimalLinkToWaypointsByCrossoverPointIndex.Add(optimalLinkToWaypoints); optimalLinkToOtherCrossoversByCrossoverPointIndex.Add(optimalLinkToCrossovers); Trace.Assert(optimalLinkToOtherCrossoversByCrossoverPointIndex.Count == optimalLinkToCrossovers.Count); for (var otherCpi = 0; otherCpi < crossoverPoints.Count - 1; otherCpi++) { var linkToOther = optimalLinkToCrossovers[otherCpi]; var linkFromOther = linkToOther.PriorIndex < 0 ? new PathLink { PriorIndex = linkToOther.PriorIndex, TotalCost = linkToOther.TotalCost } : new PathLink { PriorIndex = optimalLinkToWaypointsByCrossoverPointIndex[otherCpi][linkToOther.PriorIndex].PriorIndex, TotalCost = linkToOther.TotalCost }; optimalLinkToOtherCrossoversByCrossoverPointIndex[otherCpi].Add(linkFromOther); } return(true); } }
private static void RenderRandomVisualizationFrames() { for (var i = 0; i < 100; i++) { var(p, segments) = RandomInput(); p = new DoubleVector2(bounds.Width / 2, bounds.Height / 2); segments = segments.Where(s => GeometryOperations.Clockness(p.X, p.Y, s.X1, s.Y1, s.X2, s.Y2) == Clockness.Clockwise).ToArray(); RenderVisualizationFrame(p, segments); } }
public void OnPaint(PaintEventArgs e) { e.Graphics.TranslateTransform((float)currentShiftX, (float)currentShiftY); foreach (var obj in Shapes) { if (obj.Shape.Any(p => GeometryOperations.IsPointInRectangle(p, -currentShiftX - 50, -currentShiftY - 50, -currentShiftX + game.Width + 50, -currentShiftY + game.Height + 50))) { obj.OnPaint(this, e); } } e.Graphics.TranslateTransform(-(float)currentShiftX, -(float)currentShiftY); }
private static void Z(this IDebugCanvas canvas, IntVector2 visibilityPolygonOrigin, TerrainOverlayNetworkNode terrainNode, IReadOnlyCollection <IntLineSegment2> inboundCrossoverSegments, HashSet <TerrainOverlayNetworkNode> visited, FillStyle fillStyle) { canvas.Transform = terrainNode.SectorNodeDescription.WorldTransform; // canvas.DrawPoint(visibilityPolygonOrigin, StrokeStyle.RedThick25Solid); var visibilityPolygon = new VisibilityPolygon( visibilityPolygonOrigin.ToDoubleVector2(), new[] { new VisibilityPolygon.IntervalRange { Id = VisibilityPolygon.RANGE_ID_INFINITESIMALLY_NEAR, ThetaStart = 0, ThetaEnd = VisibilityPolygon.TwoPi }, }); foreach (var inboundCrossoverSegment in inboundCrossoverSegments) { visibilityPolygon.ClearBefore(inboundCrossoverSegment); } // Console.WriteLine("===="); foreach (var seg in terrainNode.LandPolyNode.FindContourAndChildHoleBarriers()) { if (GeometryOperations.Clockness(visibilityPolygon.Origin, seg.First.ToDoubleVector2(), seg.Second.ToDoubleVector2()) == Clockness.CounterClockwise) { continue; } visibilityPolygon.Insert(seg); // Console.WriteLine(seg); } // Console.WriteLine("===="); var visibleCrossoverSegmentsByNeighbor = FindVisibleCrossoverSegmentsByNeighborAndClearLocalAt(canvas, terrainNode, visibilityPolygon, visibilityPolygonOrigin, visited); canvas.DrawVisibilityPolygon(visibilityPolygon, fillStyle: fillStyle ?? kDefaultFillStyle, angleBoundaryStrokeStyle: StrokeStyle.None, visibleWallStrokeStyle: StrokeStyle.None); var visibilityPolygonOriginWorld = Vector3.Transform(new Vector3(visibilityPolygonOrigin.ToDotNetVector(), 0), terrainNode.SectorNodeDescription.WorldTransform); foreach (var(neighbor, nextInboundCrossoverSegments) in visibleCrossoverSegmentsByNeighbor) { var neighborPolygonOrigin = Vector3.Transform(visibilityPolygonOriginWorld, neighbor.SectorNodeDescription.WorldTransformInv); //visibilityPolygonOrigin Z(canvas, new IntVector2((int)neighborPolygonOrigin.X, (int)neighborPolygonOrigin.Y), neighbor, nextInboundCrossoverSegments, visited.Concat(new[] { terrainNode }).ToHashSet(), fillStyle); } }
private static void Benchmark() { var segments = Util.Generate(100000, RandomSegment); var quad = Util.Generate(4, RandomPoint); var hull = GeometryOperations.ConvexHull(quad); var sw = new Stopwatch(); sw.Start(); const int niters = 100; for (var i = 0; i < niters; i++) { for (var j = 0; j < segments.Length; j++) { GeometryOperations.SegmentIntersectsConvexPolygonInterior(segments[j], hull); } } Console.WriteLine(sw.Elapsed.TotalMilliseconds / niters); }
public bool ContainsOrIntersects(ref IntLineSegment2 segment) { var tl = GeometryOperations.Clockness(segment.X1, segment.Y1, segment.X2, segment.Y2, Left, Top); var tr = GeometryOperations.Clockness(segment.X1, segment.Y1, segment.X2, segment.Y2, Right, Top); var bl = GeometryOperations.Clockness(segment.X1, segment.Y1, segment.X2, segment.Y2, Left, Bottom); var br = GeometryOperations.Clockness(segment.X1, segment.Y1, segment.X2, segment.Y2, Right, Bottom); // If all on same side (and assuming assume not all collinear), then not intersecting! if (tl == tr && tr == bl && bl == br && br != Clockness.Neither) { return(false); } // Some point (or cross-sectional segment!) of rect intersects with line formed by segment return((segment.X1 >= Left || segment.X2 >= Left) && (segment.X1 <= Right || segment.X2 <= Right) && (segment.Y1 >= Top || segment.Y2 >= Top) && (segment.Y1 <= Bottom || segment.Y2 <= Bottom)); }
// public static bool TryFindSector(this TerrainSnapshot terrainSnapshot, IntVector3 queryWorld, out SectorSnapshot result) { // return TryFindSector(terrainSnapshot, queryWorld.ToDoubleVector3(), out result); // } // public static bool TryFindSector(this TerrainSnapshot terrainSnapshot, DoubleVector3 queryWorld, out SectorSnapshot result) { // return terrainSnapshot.SectorSnapshots.TryFindFirst(sectorSnapshot => { // var queryLocal = sectorSnapshot.WorldToLocal(queryWorld); // var localBoundary = sectorSnapshot.StaticMetadata.LocalBoundary; // return localBoundary.X <= queryLocal.X && queryLocal.X <= localBoundary.Right && // localBoundary.Y <= queryLocal.Y && queryLocal.Y <= localBoundary.Bottom; // }, out result); // } // public static bool IsInHole(this SectorSnapshotGeometryContext sectorSnapshotGeometryContext, IntVector3 query) { // var punchedLandPolytree = sectorSnapshotGeometryContext.PunchedLand; // punchedLandPolytree.AssertIsContourlessRootHolePunchResult(); // // PolyNode pickedNode; // bool isHole; // punchedLandPolytree.PickDeepestPolynode(query.XY, out pickedNode, out isHole); // // return isHole; // } /// <summary> /// Note: Containment definition varies by hole vs terrain: Containment for holes /// does not include the hole edge, containment for terrain includes the terrain edge. /// This is important, else e.g. knockback + terrain push placing an entity on an edge /// would potentially infinite loop. /// </summary> public static bool FindNearestLandPointAndIsInHole(this LocalGeometryView localGeometryView, DoubleVector2 query, out DoubleVector2 nearestLandPoint) { var punchedLandPolytree = localGeometryView.PunchedLand; punchedLandPolytree.AssertIsContourlessRootHolePunchResult(); PolyNode pickedNode; bool isHole; punchedLandPolytree.PickDeepestPolynode(query.LossyToIntVector2(), out pickedNode, out isHole); // If query point not in a hole, nearest land point is query point if (!isHole) { nearestLandPoint = query; return(false); } // Else, two cases to consider: nearest point is on an island inside this hole, alternatively // and (only if the hole has a contour), nearest point is on the hole contour. nearestLandPoint = DoubleVector2.Zero; double bestDistance = double.PositiveInfinity; if (pickedNode.Contour.Any()) { // the hole has a contour; that is, it's a hole inside of a landmass var result = GeometryOperations.FindNearestPointOnContour(pickedNode.Contour, query); bestDistance = result.Distance; nearestLandPoint = result.NearestPoint; } foreach (var childLandNode in pickedNode.Childs) { var result = GeometryOperations.FindNearestPointOnContour(childLandNode.Contour, query); if (result.Distance < bestDistance) { bestDistance = result.Distance; nearestLandPoint = result.NearestPoint; } } return(true); }
public bool TryIntersect(ref IntLineSegment2 segment, out DoubleVector2 p) { if (!Bounds.ContainsOrIntersects(ref segment)) { p = default(DoubleVector2); return(false); } if (First != null) { return(First.TryIntersect(ref segment, out p) || Second.TryIntersect(ref segment, out p)); } for (var i = SegmentsStartIndexInclusive; i < SegmentsEndIndexExclusive; i++) { if (GeometryOperations.TryFindSegmentSegmentIntersection(ref Segments[i], ref segment, out p)) { return(true); } } p = default(DoubleVector2); return(false); }
public static void DrawVisibilityPolygon(this IDebugCanvas debugCanvas, VisibilityPolygon avss, double z = 0.0, FillStyle fillStyle = null, StrokeStyle angleBoundaryStrokeStyle = null, StrokeStyle visibleWallStrokeStyle = null) { fillStyle = fillStyle ?? DefaultFillStyle; var oxy = avss.Origin; foreach (var range in avss.Get().Where(range => range.Id != VisibilityPolygon.RANGE_ID_INFINITELY_FAR && range.Id != VisibilityPolygon.RANGE_ID_INFINITESIMALLY_NEAR)) { var rstart = DoubleVector2.FromRadiusAngle(100, range.ThetaStart); var rend = DoubleVector2.FromRadiusAngle(100, range.ThetaEnd); var s = range.Segment; var s1 = s.First.ToDoubleVector2(); var s2 = s.Second.ToDoubleVector2(); DoubleVector2 visibleStart, visibleEnd; if (!GeometryOperations.TryFindLineLineIntersection(oxy, oxy + rstart, s1, s2, out visibleStart) || !GeometryOperations.TryFindLineLineIntersection(oxy, oxy + rend, s1, s2, out visibleEnd)) { continue; } debugCanvas.FillTriangle(oxy, visibleStart, visibleEnd, fillStyle); debugCanvas.DrawLine( new DoubleVector3(oxy.X, oxy.Y, z), new DoubleVector3(visibleStart.X, visibleStart.Y, z), angleBoundaryStrokeStyle ?? DefaultAngleBoundaryStrokeStyle); debugCanvas.DrawLine( new DoubleVector3(oxy.X, oxy.Y, z), new DoubleVector3(visibleEnd.X, visibleEnd.Y, z), angleBoundaryStrokeStyle ?? DefaultAngleBoundaryStrokeStyle); debugCanvas.DrawLine( new DoubleVector3(visibleStart.X, visibleStart.Y, z), new DoubleVector3(visibleEnd.X, visibleEnd.Y, z), visibleWallStrokeStyle ?? DefaultVisibleWallStrokeStyle); } }
private void ButtonOnClick(object sender, RoutedEventArgs routedEventArgs) { if (InputPorts[0].Data == null || InputPorts[1].Data == null) { return; } var modelInfo1 = InputPorts[0].Data as ModelInfo; var modelInfo2 = InputPorts[1].Data as ModelInfo; if (modelInfo1 != null && modelInfo2 != null) { } else { return; } if (modelInfo1 != null && modelInfo2 != null) { var res = GeometryOperations.OverlapOperator(modelInfo1, modelInfo2); OutputPorts[0].Data = res; } }
public override void Calculate() { if (InputPorts[0].Data == null) { return; } var modelInfo = InputPorts[0].Data as ModelInfo; if (modelInfo == null) { return; } var model = modelController.GetModel(modelInfo.modelId) as IfcModel; if (model == null) { return; } // Get the model content xModel = model.GetModel(); context = model.xModelContext; var storeys = xModel.Instances.OfType <IIfcBuildingStorey>(); var elements = storeys.FirstOrDefault().ContainsElements.FirstOrDefault().RelatedElements; var spaces = storeys.FirstOrDefault().Spaces.ToList(); // Visualize the identified elements control.Visualize(control.CreateModelUiElementsDs(model, spaces.Select(item => item.GlobalId).Distinct().ToList())); // var spaces = elements.Where(item => item is IIfcSpace) ; // var walls = elements.Where(item => item is IIfcWallStandardCase); var stairs = elements.Where(item => item is IIfcStair).ToList(); var ramps = elements.Where(item => item is IIfcRamp).ToList(); var doors = elements.Where(item => item is IIfcDoor).ToList(); var doorOpenings = new List <IIfcProduct>(); foreach (var door in doors) { var ifcRelFillsElement = (door as IIfcDoor).FillsVoids.FirstOrDefault(); if (ifcRelFillsElement != null) { doorOpenings.Add(ifcRelFillsElement.RelatingOpeningElement); } } foreach (var opening in doorOpenings) { var ifcOpeningElement = opening as IIfcOpeningElement; if (ifcOpeningElement != null) { var wall = ifcOpeningElement.VoidsElements.RelatingBuildingElement; var info1 = new ModelInfo(modelInfo.modelId, new List <string>() { wall.GlobalId }, ModelTypes.IFC); var info2 = new ModelInfo(modelInfo.modelId, spaces.Select(item => item.GlobalId.ToString()).ToList(), ModelTypes.IFC); var res = GeometryOperations.OverlapOperator(info1, info2); } } // Visualize the identified elements control.Visualize(control.CreateModelUiElementsDs(model, doorOpenings.Select(item => item.GlobalId).Distinct().ToList(), false)); // Get the doors // meshBuilder.AddPolygon(points); // // // var material = new DiffuseMaterial { Brush = Brushes.Red }; // var myGeometryModel = new GeometryModel3D // { // Material = material, // BackMaterial = material, // Geometry = meshBuilder.ToMesh(true) // }; // // var element = new ModelUIElement3D { Model = myGeometryModel }; // // control.Viewport3D.Children.Add(element); }
private void TriangulateHelper(PolyNode node, List <TriangulationIsland> islands) { DebugPrint("TriangulateRoot out"); var cps = new Polygon(ConvertToTriangulationPoints(node.Contour)); foreach (var hole in node.Childs) { cps.AddHole(new Polygon(ConvertToTriangulationPoints(hole.Contour))); } P2T.Triangulate(cps); var triangles = new Triangle3[cps.Triangles.Count]; var p2tTriangleToIndex = new Dictionary <Poly2Tri.Triangulation.Delaunay.DelaunayTriangle, int>(); for (int i = 0; i < cps.Triangles.Count; i++) { triangles[i].Index = i; p2tTriangleToIndex[cps.Triangles[i]] = i; } for (var i = 0; i < cps.Triangles.Count; i++) { var p2tTriangle = cps.Triangles[i]; ref Triangle3 t = ref triangles[i]; t.Points = new Array3 <DoubleVector2>( p2tTriangle.Points[0].ToOpenMobaPointD(), p2tTriangle.Points[1].ToOpenMobaPointD(), p2tTriangle.Points[2].ToOpenMobaPointD() ); // Console.WriteLine(string.Join(", ", p2tTriangle.Points.Select(p => p.Z))); t.Centroid = (t.Points[0] + t.Points[1] + t.Points[2]) / 3.0; t.IntPaddedBounds2D = CreatePaddedIntAxisAlignedBoundingBoxXY2D(ref triangles[i].Points); for (int j = 0; j < 3; j++) { if (p2tTriangle.Neighbors[j] != null && p2tTriangle.Neighbors[j].IsInterior) { triangles[i].NeighborOppositePointIndices[j] = p2tTriangleToIndex[p2tTriangle.Neighbors[j]]; } else { triangles[i].NeighborOppositePointIndices[j] = Triangle3.NO_NEIGHBOR_INDEX; } #if DEBUG var p0 = triangles[i].Points[0]; var p1 = triangles[i].Points[1]; var p2 = triangles[i].Points[2]; var centroid = triangles[i].Centroid; var cp0 = p0 - centroid; var cp1 = p1 - centroid; var cp2 = p2 - centroid; var cl01 = GeometryOperations.Clockness(cp0, cp1); var cl12 = GeometryOperations.Clockness(cp1, cp2); var cl20 = GeometryOperations.Clockness(cp2, cp0); var cond = cl01 == cl12 && cl12 == cl20 && cl20 == Clockness.CounterClockwise; if (!cond) { throw new ArgumentException("P2T Triangle Clockness wasn't expected"); } #endif } }
private static MultiValueDictionary <TerrainOverlayNetworkNode, IntLineSegment2> FindVisibleCrossoverSegmentsByNeighborAndClearLocalAt( IDebugCanvas canvas, TerrainOverlayNetworkNode terrainNode, VisibilityPolygon visibilityPolygon, IntVector2 visibilityPolygonOrigin, HashSet <TerrainOverlayNetworkNode> visited = null) { var visibleCrossoverSegmentsByNeighbor = MultiValueDictionary <TerrainOverlayNetworkNode, IntLineSegment2> .Create(() => new HashSet <IntLineSegment2>()); foreach (var outboundEdgeGroup in terrainNode.OutboundEdgeGroups) { var otherTerrainNode = outboundEdgeGroup.Key; if (visited?.Contains(otherTerrainNode) ?? false) { continue; } foreach (var outboundEdge in outboundEdgeGroup.Value) { var ranges = visibilityPolygon.Get(); (IntLineSegment2, bool) FlipMaybeSorta(IntLineSegment2 x) => GeometryOperations.Clockness(visibilityPolygonOrigin, x.First, x.Second) == Clockness.CounterClockwise ? (new IntLineSegment2(x.Second, x.First), true) : (x, false); var(localCrossoverSegment, lcsFlipped) = FlipMaybeSorta(outboundEdge.EdgeJob.EdgeDescription.SourceSegment); var(remoteCrossoverSegment, rcsFlipped) = FlipMaybeSorta(outboundEdge.EdgeJob.EdgeDescription.DestinationSegment); // todo: clamp visibleStartT, visibleEndT to account for agent radius eroding crossover segmetmentnt var rangeIndexIntervals = visibilityPolygon.RangeStab(localCrossoverSegment); var locallyClearedSegments = new List <IntLineSegment2>(); foreach (var(startIndexInclusive, endIndexInclusive) in rangeIndexIntervals) { for (var i = startIndexInclusive; i <= endIndexInclusive; i++) { if (ranges[i].Id == VisibilityPolygon.RANGE_ID_INFINITELY_FAR || ranges[i].Id == VisibilityPolygon.RANGE_ID_INFINITESIMALLY_NEAR) { continue; } var seg = ranges[i].Segment; var rstart = DoubleVector2.FromRadiusAngle(100, ranges[i].ThetaStart) * 100; var rend = DoubleVector2.FromRadiusAngle(100, ranges[i].ThetaEnd) * 100; double visibleStartT, visibleEndT; if (!GeometryOperations.TryFindNonoverlappingLineLineIntersectionT(localCrossoverSegment.First.ToDoubleVector2(), localCrossoverSegment.Second.ToDoubleVector2(), visibilityPolygonOrigin.ToDoubleVector2(), visibilityPolygonOrigin.ToDoubleVector2() + rstart, out visibleStartT) || !GeometryOperations.TryFindNonoverlappingLineLineIntersectionT(localCrossoverSegment.First.ToDoubleVector2(), localCrossoverSegment.Second.ToDoubleVector2(), visibilityPolygonOrigin.ToDoubleVector2(), visibilityPolygonOrigin.ToDoubleVector2() + rend, out visibleEndT)) { // wtf? Console.WriteLine("???"); continue; } // Todo: I don't actually understand why visibleEndT > 1 is a thing? // t values are for parameterization of crossover line segment, so must be within [0, 1] if ((visibleStartT < 0 && visibleEndT < 0) || (visibleStartT > 1 && visibleEndT > 1)) { continue; } visibleStartT = Math.Min(1.0, Math.Max(0.0, visibleStartT)); visibleEndT = Math.Min(1.0, Math.Max(0.0, visibleEndT)); if (visibilityPolygon.SegmentComparer.Compare(localCrossoverSegment, seg) < 0) { var localVisibleStart = localCrossoverSegment.PointAt(visibleStartT).LossyToIntVector2(); var localVisibleEnd = localCrossoverSegment.PointAt(visibleEndT).LossyToIntVector2(); var remoteVisibleStart = remoteCrossoverSegment.PointAt(lcsFlipped == rcsFlipped ? visibleStartT : 1.0 - visibleStartT).LossyToIntVector2(); var remoteVisibleEnd = remoteCrossoverSegment.PointAt(lcsFlipped == rcsFlipped ? visibleEndT : 1.0 - visibleEndT).LossyToIntVector2(); if (localVisibleStart == localVisibleEnd) { continue; } if (remoteVisibleStart == remoteVisibleEnd) { continue; } var locallyClearedSegment = new IntLineSegment2(localVisibleStart, localVisibleEnd); locallyClearedSegments.Add(locallyClearedSegment); visibleCrossoverSegmentsByNeighbor.Add(otherTerrainNode, new IntLineSegment2(remoteVisibleStart, remoteVisibleEnd)); } } } foreach (var locallyClearedSegment in locallyClearedSegments) { visibilityPolygon.ClearBefore(locallyClearedSegment); } } } return(visibleCrossoverSegmentsByNeighbor); }