00001 using System; 00002 using System.Collections.Generic; 00003 using System.IO; 00004 00005 namespace SpaceFlight.Simulation 00006 { 00007 public struct Vector2D 00008 { 00009 public double X { get; set; } 00010 public double Y { get; set; } 00011 00012 public double Length 00013 { 00014 get { return Math.Sqrt(X * X + Y * Y); } 00015 } 00016 00017 public static double Angle(Vector2D first, Vector2D second) 00018 { 00019 if (first.Length != 0 && second.Length != 0) 00020 { 00021 double angle = Math.Atan2(second.Y, second.X) - Math.Atan2(first.Y, first.X); 00022 return Math.IEEERemainder(angle, 2 * Math.PI); 00023 } 00024 else 00025 { 00026 return 0; 00027 } 00028 } 00029 00030 public static Vector2D operator +(Vector2D first, Vector2D second) 00031 { 00032 return new Vector2D 00033 { 00034 X = first.X + second.X, 00035 Y = first.Y + second.Y, 00036 }; 00037 } 00038 00039 public static Vector2D operator -(Vector2D first, Vector2D second) 00040 { 00041 return new Vector2D 00042 { 00043 X = first.X - second.X, 00044 Y = first.Y - second.Y, 00045 }; 00046 } 00047 00048 public static Vector2D operator *(double scalar, Vector2D vector) 00049 { 00050 return new Vector2D 00051 { 00052 X = scalar * vector.X, 00053 Y = scalar * vector.Y, 00054 }; 00055 } 00056 } 00057 00058 public class Planet 00059 { 00060 public Vector2D Position { get; set; } 00061 public double Radius { get; set; } 00062 } 00063 00064 public class Ship 00065 { 00066 public Vector2D Position { get; set; } 00067 public Vector2D Velocity { get; set; } 00068 public double Angle { get; set; } 00069 public double Turn { get; set; } 00070 00071 public Vector2D Direction 00072 { 00073 get { return new Vector2D { X = Math.Cos(Angle), Y = Math.Sin(Angle) }; } 00074 } 00075 } 00076 00077 public interface IShipController 00078 { 00079 void UpdateShip(TestCase testCase, Vector2D gravity); 00080 } 00081 00082 public class TestCase 00083 { 00084 public IList<Planet> Planets { get; private set; } 00085 public IList<Vector2D> Waypoints { get; private set; } 00086 public Vector2D StartingPosition { get; set; } 00087 00088 public Ship Ship { get; set; } 00089 public int TimeSteps { get; set; } 00090 public int TargetWaypoint { get; set; } 00091 00092 public bool IsActive { get; set; } 00093 00094 public bool TraceMovement { get; set; } 00095 public IList<Vector2D> Trace { get; private set; } 00096 00097 public TestCase() 00098 { 00099 Planets = new List<Planet>(); 00100 Waypoints = new List<Vector2D>(); 00101 Trace = new List<Vector2D>(); 00102 } 00103 00104 public void Reset() 00105 { 00106 Ship = new Ship 00107 { 00108 Position = StartingPosition, 00109 Velocity = new Vector2D(), 00110 Angle = Vector2D.Angle(new Vector2D { X = 1 }, Waypoints[0] - StartingPosition) 00111 }; 00112 TimeSteps = 0; 00113 TargetWaypoint = 0; 00114 IsActive = true; 00115 Trace.Clear(); 00116 } 00117 00118 public void Simulate(IShipController controller, int steps) 00119 { 00120 for (int i = 0; i < steps && IsActive; ++i) 00121 { 00122 Simulate(controller); 00123 } 00124 } 00125 00126 private void Simulate(IShipController controller) 00127 { 00128 Vector2D gravity = new Vector2D(); 00129 foreach (Planet planet in Planets) 00130 { 00131 Vector2D distance = planet.Position - Ship.Position; 00132 if (distance.Length <= planet.Radius) 00133 { 00134 IsActive = false; 00135 } 00136 gravity += 15 * 0.001 * (Math.Pow(planet.Radius, 3) / Math.Pow(distance.Length, 3)) * distance; 00137 } 00138 Ship.Velocity += gravity; 00139 00140 controller.UpdateShip(this, gravity); 00141 ++TimeSteps; 00142 00143 const double MaxTurn = 2 * Math.PI / 25; 00144 Ship.Angle += Math.Max(-MaxTurn, Math.Min(MaxTurn, Ship.Turn)); 00145 00146 Ship.Velocity += 12 * 0.02 * Ship.Direction; 00147 Ship.Position += Ship.Velocity; 00148 00149 double targetDistance = (Ship.Position - Waypoints[TargetWaypoint]).Length; 00150 if (targetDistance < 20) 00151 { 00152 ++TargetWaypoint; 00153 if (TargetWaypoint == Waypoints.Count) 00154 { 00155 IsActive = false; 00156 } 00157 } 00158 else if (targetDistance > 1500) 00159 { 00160 IsActive = false; 00161 } 00162 00163 if (TraceMovement) 00164 { 00165 Trace.Add(Ship.Position); 00166 } 00167 } 00168 00169 public static TestCase Load(string filename) 00170 { 00171 TestCase testCase = new TestCase(); 00172 using (var stream = new StreamReader(filename)) 00173 { 00174 string line; 00175 while ((line = stream.ReadLine()).Length != 0) 00176 { 00177 string[] parts = line.Split(' '); 00178 if (parts.Length != 3) 00179 { 00180 throw new Exception(); 00181 } 00182 testCase.Planets.Add(new Planet 00183 { 00184 Position = new Vector2D { X = double.Parse(parts[0]), Y = double.Parse(parts[1]) }, 00185 Radius = double.Parse(parts[2]) 00186 }); 00187 } 00188 00189 while ((line = stream.ReadLine()).Length != 0) 00190 { 00191 string[] parts = line.Split(' '); 00192 if (parts.Length != 2) 00193 { 00194 throw new Exception(); 00195 } 00196 testCase.Waypoints.Add(new Vector2D { X = double.Parse(parts[0]), Y = double.Parse(parts[1]) }); 00197 } 00198 00199 { 00200 line = stream.ReadLine(); 00201 string[] parts = line.Split(' '); 00202 if (parts.Length != 2) 00203 { 00204 throw new Exception(); 00205 } 00206 testCase.StartingPosition = new Vector2D { X = double.Parse(parts[0]), Y = double.Parse(parts[1]) }; 00207 } 00208 00209 if (stream.ReadToEnd().Trim().Length != 0) 00210 { 00211 throw new Exception(); 00212 } 00213 } 00214 00215 return testCase; 00216 } 00217 00218 public static void Save(TestCase testCase, string filename) 00219 { 00220 using (var stream = new StreamWriter(filename)) 00221 { 00222 foreach (Planet planet in testCase.Planets) 00223 { 00224 stream.WriteLine("{0:0} {1:0} {2:0}", planet.Position.X, planet.Position.Y, planet.Radius); 00225 } 00226 stream.WriteLine(); 00227 00228 foreach (Vector2D waypoint in testCase.Waypoints) 00229 { 00230 stream.WriteLine("{0:0} {1:0}", waypoint.X, waypoint.Y); 00231 } 00232 stream.WriteLine(); 00233 00234 stream.WriteLine("{0:0} {1:0}", testCase.StartingPosition.X, testCase.StartingPosition.Y); 00235 } 00236 } 00237 00238 public static TestCase CreateRandom(int numPlanets, int numWaypoints) 00239 { 00240 Random random = new Random(); 00241 TestCase testCase = new TestCase(); 00242 00243 testCase.StartingPosition = new Vector2D 00244 { 00245 X = 200 + random.NextDouble() * 600, 00246 Y = 200 + random.NextDouble() * 600 00247 }; 00248 00249 int tries = 0; 00250 while (testCase.Planets.Count != numPlanets && tries < numPlanets * 100) 00251 { 00252 Planet planet = new Planet 00253 { 00254 Position = new Vector2D 00255 { 00256 X = 100 + random.NextDouble() * 800, 00257 Y = 100 + random.NextDouble() * 800 00258 }, 00259 Radius = 15 + random.NextDouble() * 15 00260 }; 00261 ++tries; 00262 00263 if (!IsTooClose(planet.Position, testCase, planet.Radius + 200)) 00264 { 00265 testCase.Planets.Add(planet); 00266 } 00267 } 00268 00269 tries = 0; 00270 while (testCase.Waypoints.Count != numWaypoints && tries < numWaypoints * 100) 00271 { 00272 Vector2D waypoint = new Vector2D 00273 { 00274 X = 100 + random.NextDouble() * 800, 00275 Y = 100 + random.NextDouble() * 800 00276 }; 00277 ++tries; 00278 00279 if (!IsTooClose(waypoint, testCase, 50)) 00280 { 00281 testCase.Waypoints.Add(waypoint); 00282 } 00283 } 00284 00285 return testCase; 00286 } 00287 00288 private static bool IsTooClose(Vector2D position, TestCase testCase, double minDistance) 00289 { 00290 if ((position - testCase.StartingPosition).Length < minDistance) 00291 { 00292 return true; 00293 } 00294 00295 foreach (Planet planet in testCase.Planets) 00296 { 00297 if ((position - planet.Position).Length - planet.Radius < minDistance) 00298 { 00299 return true; 00300 } 00301 } 00302 00303 foreach (Vector2D waypoint in testCase.Waypoints) 00304 { 00305 if ((position - waypoint).Length < minDistance) 00306 { 00307 return true; 00308 } 00309 } 00310 00311 return false; 00312 } 00313 } 00314 }