Motion.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. namespace Terminal.Gui.TextEffects;
  2. public class Coord
  3. {
  4. public int Column { get; set; }
  5. public int Row { get; set; }
  6. public Coord (int column, int row)
  7. {
  8. Column = column;
  9. Row = row;
  10. }
  11. public override string ToString () => $"({Column}, {Row})";
  12. public override bool Equals (object obj)
  13. {
  14. if (obj is Coord other)
  15. {
  16. return Column == other.Column && Row == other.Row;
  17. }
  18. return false;
  19. }
  20. public override int GetHashCode ()
  21. {
  22. return HashCode.Combine (Column, Row);
  23. }
  24. public static bool operator == (Coord left, Coord right)
  25. {
  26. if (left is null)
  27. {
  28. return right is null;
  29. }
  30. return left.Equals (right);
  31. }
  32. public static bool operator != (Coord left, Coord right)
  33. {
  34. return !(left == right);
  35. }
  36. }
  37. public class Waypoint
  38. {
  39. public string WaypointId { get; set; }
  40. public Coord Coord { get; set; }
  41. public List<Coord> BezierControl { get; set; }
  42. public Waypoint (string waypointId, Coord coord, List<Coord> bezierControl = null)
  43. {
  44. WaypointId = waypointId;
  45. Coord = coord;
  46. BezierControl = bezierControl ?? new List<Coord> ();
  47. }
  48. }
  49. public class Segment
  50. {
  51. public Waypoint Start { get; private set; }
  52. public Waypoint End { get; private set; }
  53. public double Distance { get; private set; }
  54. public bool EnterEventTriggered { get; set; }
  55. public bool ExitEventTriggered { get; set; }
  56. public Segment (Waypoint start, Waypoint end)
  57. {
  58. Start = start;
  59. End = end;
  60. Distance = CalculateDistance (start, end);
  61. }
  62. private double CalculateDistance (Waypoint start, Waypoint end)
  63. {
  64. // Add bezier control point distance calculation if needed
  65. return Math.Sqrt (Math.Pow (end.Coord.Column - start.Coord.Column, 2) + Math.Pow (end.Coord.Row - start.Coord.Row, 2));
  66. }
  67. public Coord GetCoordOnSegment (double distanceFactor)
  68. {
  69. int column = (int)(Start.Coord.Column + (End.Coord.Column - Start.Coord.Column) * distanceFactor);
  70. int row = (int)(Start.Coord.Row + (End.Coord.Row - Start.Coord.Row) * distanceFactor);
  71. return new Coord (column, row);
  72. }
  73. }
  74. public class Path
  75. {
  76. public string PathId { get; private set; }
  77. public double Speed { get; set; }
  78. public Func<double, double> EaseFunction { get; set; }
  79. public int Layer { get; set; }
  80. public int HoldTime { get; set; }
  81. public bool Loop { get; set; }
  82. public List<Segment> Segments { get; private set; } = new List<Segment> ();
  83. public int CurrentStep { get; set; }
  84. public double TotalDistance { get; set; }
  85. public double LastDistanceReached { get; set; }
  86. public int MaxSteps => (int)Math.Ceiling (TotalDistance / Speed); // Calculates max steps based on total distance and speed
  87. public Path (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
  88. {
  89. PathId = pathId;
  90. Speed = speed;
  91. EaseFunction = easeFunction;
  92. Layer = layer;
  93. HoldTime = holdTime;
  94. Loop = loop;
  95. }
  96. public void AddWaypoint (Waypoint waypoint)
  97. {
  98. if (Segments.Count > 0)
  99. {
  100. var lastSegment = Segments.Last ();
  101. var newSegment = new Segment (lastSegment.End, waypoint);
  102. Segments.Add (newSegment);
  103. TotalDistance += newSegment.Distance;
  104. }
  105. else
  106. {
  107. var originWaypoint = new Waypoint ("origin", new Coord (0, 0)); // Assuming the path starts at origin
  108. var initialSegment = new Segment (originWaypoint, waypoint);
  109. Segments.Add (initialSegment);
  110. TotalDistance = initialSegment.Distance;
  111. }
  112. }
  113. public Coord Step ()
  114. {
  115. if (CurrentStep <= MaxSteps)
  116. {
  117. double progress = EaseFunction?.Invoke ((double)CurrentStep / TotalDistance) ?? (double)CurrentStep / TotalDistance;
  118. double distanceTravelled = TotalDistance * progress;
  119. LastDistanceReached = distanceTravelled;
  120. foreach (var segment in Segments)
  121. {
  122. if (distanceTravelled <= segment.Distance)
  123. {
  124. double segmentProgress = distanceTravelled / segment.Distance;
  125. return segment.GetCoordOnSegment (segmentProgress);
  126. }
  127. distanceTravelled -= segment.Distance;
  128. }
  129. }
  130. return Segments.Last ().End.Coord; // Return the end of the last segment if out of bounds
  131. }
  132. }
  133. public class Motion
  134. {
  135. public Dictionary<string, Path> Paths { get; private set; } = new Dictionary<string, Path> ();
  136. public Path ActivePath { get; private set; }
  137. public Coord CurrentCoord { get; set; }
  138. public Coord PreviousCoord { get; set; }
  139. public EffectCharacter Character { get; private set; } // Assuming EffectCharacter is similar to base_character.EffectCharacter
  140. public Motion (EffectCharacter character)
  141. {
  142. Character = character;
  143. CurrentCoord = new Coord (character.InputCoord.Column, character.InputCoord.Row); // Assuming similar properties
  144. PreviousCoord = new Coord (-1, -1);
  145. }
  146. public void SetCoordinate (Coord coord)
  147. {
  148. CurrentCoord = coord;
  149. }
  150. public Path CreatePath (string pathId, double speed, Func<double, double> easeFunction = null, int layer = 0, int holdTime = 0, bool loop = false)
  151. {
  152. if (Paths.ContainsKey (pathId))
  153. throw new ArgumentException ($"A path with ID {pathId} already exists.");
  154. var path = new Path (pathId, speed, easeFunction, layer, holdTime, loop);
  155. Paths [pathId] = path;
  156. return path;
  157. }
  158. public Path QueryPath (string pathId)
  159. {
  160. if (!Paths.TryGetValue (pathId, out var path))
  161. throw new KeyNotFoundException ($"No path found with ID {pathId}.");
  162. return path;
  163. }
  164. public bool MovementIsComplete ()
  165. {
  166. return ActivePath == null || ActivePath.CurrentStep >= ActivePath.TotalDistance;
  167. }
  168. public void ActivatePath (Path path)
  169. {
  170. if (path == null)
  171. throw new ArgumentNullException (nameof (path), "Path cannot be null when activating.");
  172. ActivePath = path;
  173. ActivePath.CurrentStep = 0; // Reset the path's progress
  174. }
  175. /// <summary>
  176. /// Set the active path to None if the active path is the given path.
  177. /// </summary>
  178. public void DeactivatePath (Path p)
  179. {
  180. if (p == ActivePath)
  181. {
  182. ActivePath = null;
  183. }
  184. }
  185. public void DeactivatePath ()
  186. {
  187. ActivePath = null;
  188. }
  189. public void Move ()
  190. {
  191. if (ActivePath != null)
  192. {
  193. PreviousCoord = CurrentCoord;
  194. CurrentCoord = ActivePath.Step ();
  195. ActivePath.CurrentStep++;
  196. if (ActivePath.CurrentStep >= ActivePath.TotalDistance)
  197. {
  198. if (ActivePath.Loop)
  199. ActivePath.CurrentStep = 0; // Reset the path for looping
  200. else
  201. DeactivatePath (); // Deactivate the path if it is not set to loop
  202. }
  203. }
  204. }
  205. public void ChainPaths (IEnumerable<Path> paths, bool loop = false)
  206. {
  207. var pathList = paths.ToList ();
  208. for (int i = 0; i < pathList.Count; i++)
  209. {
  210. var currentPath = pathList [i];
  211. var nextPath = i + 1 < pathList.Count ? pathList [i + 1] : pathList.FirstOrDefault ();
  212. // Here we could define an event system to trigger path activation when another completes
  213. // For example, you could listen for a "path complete" event and then activate the next path
  214. if (loop && nextPath != null)
  215. {
  216. // Implementation depends on your event system
  217. }
  218. }
  219. }
  220. }