using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; namespace FSO.Common.WorldGeometry.Paths { public class LinePath { public List Segments = new List(); public bool SharpStart; public bool SharpEnd; public int TemplateNum; public float StartOffset; public float Length; public LinePath() { } public LinePath(List line) { for (int i=0; i GetPositionNormalAt(float offset) { foreach (var seg in Segments) { //is the given offset in this segment? if (offset < seg.Length) { var i = offset / seg.Length; return new Tuple(Vector2.Lerp(seg.Start, seg.End, i), Vector2.Lerp(seg.StartNormal, seg.EndNormal, i)); } offset -= seg.Length; } var last = Segments.Last(); return new Tuple(last.End, last.EndNormal); } public List Split(float dist, float gap) { var result = new List(); var startGap = dist - gap / 2; var endGap = dist + gap / 2; bool before = 0 < startGap; LinePath current = new LinePath(); if (before) { current.SharpStart = SharpStart; current.SharpEnd = true; current.StartOffset = StartOffset; } else { current.SharpStart = true; current.SharpEnd = SharpEnd; current.StartOffset = StartOffset + endGap; } current.TemplateNum = TemplateNum; float soFar = 0; foreach (var segment in Segments) { if (before) { if (soFar + segment.Length <= startGap) { //add this segment current.Segments.Add(segment); } else { //this segment extends over the gap. //an additional segment must be added to reach the start gap if (soFar != startGap && segment.Length != 0) { var bridge = new LinePathSegment(segment.Start, Vector2.Lerp(segment.Start, segment.End, (startGap - soFar) / segment.Length)); bridge.StartNormal = segment.StartNormal; current.Segments.Add(bridge); } current.Length = current.Segments.Sum(x => x.Length); result.Add(current); current = new LinePath(); current.SharpStart = true; current.SharpEnd = SharpEnd; current.StartOffset = StartOffset + endGap; current.TemplateNum = TemplateNum; before = false; } } if (!before) { if (current.Segments.Count == 0) { //waiting to get to a segment that ends after the gap. if (soFar + segment.Length > endGap) { var bridge = new LinePathSegment(Vector2.Lerp(segment.Start, segment.End, (endGap - soFar) / segment.Length), segment.End); bridge.EndNormal = segment.EndNormal; current.Segments.Add(bridge); } } else { //add this segment current.Segments.Add(segment); } } soFar += segment.Length; } current.Length = current.Segments.Sum(x => x.Length); result.Add(current); return result; } public List Intersections(LinePath other) { var epsilon = (0.9f * 0.9f) / 0.5f; //finds intersections between this linepath and another. var result = new List(); float soFar = 0; for (int i=0; i x.Z).ToList(); for (int i = 0; i < result.Count - 1; i++) { var first = result[i]; while (i < result.Count - 1) { var second = result[i + 1]; var distance = second - first; distance.Z = 0; if (distance.LengthSquared() < epsilon) { result.RemoveAt(i); } else; { break; } } } return result; } public void PrepareJoins() { LinePathSegment last = null; foreach (var line in Segments) { if (last != null) { last.EndNormal = line.StartNormal = Vector2.Normalize(last.EndNormal + line.StartNormal); } last = line; } } } public class LinePathSegment { public Vector2 Start; public Vector2 End; public Vector2 Direction; //normals are used when constucting geometry from a line. they face to the right from the line. //to create a seamless line, we average the end normal of this line and the start normal of the last, setting both to the result. public Vector2 StartNormal; public Vector2 EndNormal; public float Length; public LinePathSegment(Vector2 start, Vector2 end) { Start = start; End = end; Direction = end - start; Length = Direction.Length(); var dirn = Direction; dirn.Normalize(); StartNormal = EndNormal = new Vector2(-dirn.Y, dirn.X); } public Vector3? Intersect(LinePathSegment other) //xy: point, z: distance along line { if (this.Length == 0 || other.Length == 0) return null; var epsilon = 0.0001f; Vector2 a = Direction; Vector2 b = other.Direction; Vector2 c = Start - other.Start; //percent of line 1 where we intersect with line 2 float ip = 1 / (-b.X * a.Y + a.X * b.Y); //projection float t = (b.X * c.Y - b.Y * c.X) * ip; //percent of line 2 where we intersect line 1 float ip2 = 1 / (-a.X * b.Y + b.X * a.Y); float s = (a.X * (-c.Y) - a.Y * (-c.X)) * ip2; if (float.IsNaN(t) || t < -epsilon || t > 1 + epsilon || float.IsNaN(s) || s < -epsilon || s > 1 + epsilon) { return null; } return new Vector3(Direction * t + Start, t * Length); } } }