// Helper: leftOf(segment, point) returns true if point is "left" // of segment treated as a vector. Note that this assumes a 2D // coordinate system in which the Y axis grows downwards, which // matches common 2D graphics libraries, but is the opposite of // the usual convention from mathematics and in 3D graphics // libraries. public static bool GetIsLeft(Segment s, Vector2 p) { // This is based on a 3d cross product, but we don't need to // use z coordinate inputs (they're 0), and we only need the // sign. If you're annoyed that cross product is only defined // in 3d, see "outer product" in Geometric Algebra. // <http://en.wikipedia.org/wiki/Geometric_algebra> var cross = (s.End.Point.x - s.Start.Point.x) * (p.y - s.Start.Point.y) - (s.End.Point.y - s.Start.Point.y) * (p.x - s.Start.Point.x); return cross < 0; // Also note that this is the naive version of the test and // isn't numerically robust. See // <https://github.com/mikolalysenko/robust-arithmetic> for a // demo of how this fails when a point is very close to the // line. }
// Helper: do we know that segment a is in front of b? // Implementation not anti-symmetric (that is to say, // _segment_in_front_of(a, b) != (!_segment_in_front_of(b, a)). // Also note that it only has to work in a restricted set of cases // in the visibility algorithm; I don't think it handles all // cases. See http://www.redblobgames.com/articles/visibility/segment-sorting.html public static IntersectionResult IsInFrontOf(Segment a, Segment b, Vector2 relativeTo) { // NOTE: we slightly shorten the segments so that // intersections of the endpoints (common) don't count as // intersections in this algorithm const float epsilon = 0.01f; bool A1 = ShadowMathUtils.GetIsLeft(a, ShadowMathUtils.Interpolate(b.Start.Point, b.End.Point, epsilon)); bool A2 = ShadowMathUtils.GetIsLeft(a, ShadowMathUtils.Interpolate(b.End.Point, b.Start.Point, epsilon)); bool A3 = ShadowMathUtils.GetIsLeft(a, relativeTo); bool B1 = ShadowMathUtils.GetIsLeft(b, ShadowMathUtils.Interpolate(a.Start.Point, a.End.Point, epsilon)); bool B2 = ShadowMathUtils.GetIsLeft(b, ShadowMathUtils.Interpolate(a.End.Point, a.Start.Point, epsilon)); bool B3 = ShadowMathUtils.GetIsLeft(b, relativeTo); // NOTE: this algorithm is probably worthy of a short article // but for now, draw it on paper to see how it works. Consider // the line A1-A2. If both B1 and B2 are on one side and // relativeTo is on the other side, then A is in between the // viewer and B. We can do the same with B1-B2: if A1 and A2 // are on one side, and relativeTo is on the other side, then // B is in between the viewer and A. if (B1 == B2 && B2 != B3) return IntersectionResult.InFront; if (A1 == A2 && A2 == A3) return IntersectionResult.InFront; if (A1 == A2 && A2 != A3) return IntersectionResult.Behind; if (B1 == B2 && B2 == B3) return IntersectionResult.Behind; // If A1 != A2 and B1 != B2 then we have an intersection. // Expose it for the GUI to show a message. A more robust // implementation would split segments at intersections so // that part of the segment is in front and part is behind. //demo_intersectionsDetected.push([a.p1, a.p2, b.p1, b.p2]); return IntersectionResult.Intersects; // NOTE: previous implementation was a.d < b.d. That's simpler // but trouble when the segments are of dissimilar sizes. If // you're on a grid and the segments are similarly sized, then // using distance will be a simpler and faster implementation. }
private void Split(Segment segment, Vector2 middle, Queue<Segment> open) { Vector2 start = segment.Start.Point; Vector2 end = segment.End.Point; //Debug.LogFormat("Splitting {0}-{1} at {2}", start, end, middle); open.Enqueue(new Segment(start, middle)); open.Enqueue(new Segment(middle, end)); //Segment endSegment = new Segment(position, segment.End.Point); //segment.End.Point = position; //open.Enqueue(segment); //open.Enqueue(endSegment); }
private void AddTriangle(float angle1, float angle2, Segment segment, Segment previous) { Vector2 delta1 = new Vector2(Mathf.Cos(angle1), Mathf.Sin(angle1)); Vector2 delta2 = new Vector2(Mathf.Cos(angle2), Mathf.Sin(angle2)); Vector2 p1 = Center; Vector2 p2 = p1 + delta1; Vector2 p3 = new Vector2(0.0f, 0.0f); Vector2 p4 = new Vector2(0.0f, 0.0f); Gizmos.color = Color.blue; if (segment != null) { // Stop the triangle at the intersecting segment p3 = segment.Start.Point; p4 = segment.End.Point; } else { // Stop the triangle at a fixed distance; this probably is // not what we want, but it never gets used in the demo p3 = p1 + delta1 * 500; p4 = p1 + delta2 * 500; } Vector2 pBegin = ShadowMathUtils.LineIntersection(p3, p4, p1, p2); p2 = p1 + delta2; Vector2 pEnd = ShadowMathUtils.LineIntersection(p3, p4, p1, p2); if (_drawGizmos) { Gizmos.color = Color.green; Gizmos.DrawLine(p1, pBegin); Gizmos.DrawLine(p2, pEnd); //Gizmos.DrawLine(pBegin, pEnd); Gizmos.color = Color.yellow; Gizmos.DrawSphere(pBegin, 0.1f); Gizmos.color = Color.blue; Gizmos.DrawSphere(pEnd, 0.1f); } if (previous != null && ShadowMathUtils.Approximately(segment.Slope, previous.Slope) && ShadowMathUtils.Approximately(Output.Last(),pBegin)) { // It's a continuation of the previous segment! Output.RemoveAt(Output.Count-1); Output.Add(pEnd); } else { Output.Add(pBegin); Output.Add(pEnd); } }
public EndPoint(Vector2 point, Segment segment) { Point = point; Segment = segment; }
public static SideTestResult SideTest(Segment s, Vector2 p) { float cross = (s.End.Point.x - s.Start.Point.x) * (p.y - s.Start.Point.y) - (s.End.Point.y - s.Start.Point.y) * (p.x - s.Start.Point.x); if (Math.Abs(cross) < 0.00001f) { return SideTestResult.On; } if (cross < 0) { return SideTestResult.Left; } return SideTestResult.Right; }