Пример #1
0
        // 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());
        }
Пример #2
0
        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);
                }
            });
        }
Пример #3
0
        // 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);
            }
        }
Пример #4
0
 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);
     }
 }
Пример #5
0
 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);
 }
Пример #6
0
        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);
            }
        }
Пример #7
0
        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);
        }
Пример #8
0
        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));
        }
Пример #9
0
        // 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);
        }
Пример #10
0
 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);
 }
Пример #11
0
        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);
            }
        }
Пример #12
0
        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;
            }
        }
Пример #13
0
        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);
        }
Пример #14
0
        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
                }
            }
Пример #15
0
        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);
        }