kerbaldyn 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +11 -0
  3. data/README.rdoc +57 -0
  4. data/Rakefile +23 -0
  5. data/kerbaldyn.gemspec +30 -0
  6. data/lib/kerbaldyn.rb +14 -0
  7. data/lib/kerbaldyn/body.rb +48 -0
  8. data/lib/kerbaldyn/constants.rb +12 -0
  9. data/lib/kerbaldyn/data.rb +56 -0
  10. data/lib/kerbaldyn/data/planet_data.json +249 -0
  11. data/lib/kerbaldyn/mixin.rb +2 -0
  12. data/lib/kerbaldyn/mixin/options_processor.rb +17 -0
  13. data/lib/kerbaldyn/mixin/parameter_attributes.rb +38 -0
  14. data/lib/kerbaldyn/orbit.rb +379 -0
  15. data/lib/kerbaldyn/orbital_maneuver.rb +6 -0
  16. data/lib/kerbaldyn/orbital_maneuver/base.rb +159 -0
  17. data/lib/kerbaldyn/orbital_maneuver/bielliptic.rb +61 -0
  18. data/lib/kerbaldyn/orbital_maneuver/burn_event.rb +57 -0
  19. data/lib/kerbaldyn/orbital_maneuver/hohmann.rb +48 -0
  20. data/lib/kerbaldyn/orbital_maneuver/inclination_change.rb +0 -0
  21. data/lib/kerbaldyn/part.rb +15 -0
  22. data/lib/kerbaldyn/part/base.rb +154 -0
  23. data/lib/kerbaldyn/part/fuel_tank.rb +11 -0
  24. data/lib/kerbaldyn/part/generic.rb +10 -0
  25. data/lib/kerbaldyn/part/liquid_fuel_engine.rb +69 -0
  26. data/lib/kerbaldyn/part/mixin.rb +1 -0
  27. data/lib/kerbaldyn/part/mixin/fuel_tank.rb +35 -0
  28. data/lib/kerbaldyn/part/rcs_fuel_tank.rb +10 -0
  29. data/lib/kerbaldyn/part/solid_rocket.rb +30 -0
  30. data/lib/kerbaldyn/part_library.rb +55 -0
  31. data/lib/kerbaldyn/planetoid.rb +214 -0
  32. data/lib/kerbaldyn/version.rb +13 -0
  33. data/spec/bielliptic_orbital_maneuver_spec.rb +60 -0
  34. data/spec/constants_spec.rb +9 -0
  35. data/spec/hohmann_orbital_maneuver_spec.rb +385 -0
  36. data/spec/options_processor_spec.rb +33 -0
  37. data/spec/orbit_spec.rb +357 -0
  38. data/spec/orbital_maneuver_base_spec.rb +74 -0
  39. data/spec/parameter_attributes_spec.rb +82 -0
  40. data/spec/part_library_spec.rb +21 -0
  41. data/spec/part_spec.rb +218 -0
  42. data/spec/planetoid_spec.rb +117 -0
  43. data/spec/spec_helper.rb +110 -0
  44. data/spec/support/parts/RCSFuelTank/part.cfg +42 -0
  45. data/spec/support/parts/fuelTank/part.cfg +48 -0
  46. data/spec/support/parts/liquidEngine1/part.cfg +60 -0
  47. data/spec/support/parts/liquidEngine2/part.cfg +64 -0
  48. data/spec/support/parts/solidBooster/part.cfg +67 -0
  49. data/spec/support/planet_test_data.json +340 -0
  50. metadata +95 -0
@@ -0,0 +1,2 @@
1
+ require_relative 'mixin/parameter_attributes'
2
+ require_relative 'mixin/options_processor'
@@ -0,0 +1,17 @@
1
+ module KerbalDyn
2
+ module Mixin
3
+ module OptionsProcessor
4
+
5
+ private
6
+
7
+ # Iterates over all the key/value pairs in the options, calling the relevant
8
+ # accessor. Uses the passed block to get defaults for given keys.
9
+ def process_options(options, defaults={})
10
+ defaults.merge(options).each do |key,value|
11
+ self.send("#{key}=", value)
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,38 @@
1
+ module KerbalDyn
2
+ module Mixin
3
+ # See ClassMethods for methods added by this module.
4
+ module ParameterAttributes
5
+
6
+ def self.included(mod)
7
+ mod.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ # Metaprogramming method for setting physical parameters, which are
13
+ # always of float type.
14
+ def attr_parameter(*params)
15
+ params.each do |param|
16
+ attr_reader param
17
+
18
+ setter_line = __LINE__ + 1
19
+ setter = <<-METHOD
20
+ def #{param}=(value)
21
+ @#{param} = value && value.to_f
22
+ end
23
+ METHOD
24
+ class_eval(setter, __FILE__, setter_line)
25
+ end
26
+ end
27
+
28
+ # Alias a parameter getter and setter methods.
29
+ def alias_parameter(to, from)
30
+ alias_method to, from
31
+ alias_method "#{to}=", "#{from}="
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,379 @@
1
+ module KerbalDyn
2
+ # The primary class for encapsulating an orbit around a primary body.
3
+ # At this time orbits are simplified to only consider the mass of the primary
4
+ # body, thus the orbit of the secondary body is around the center-of-mass of
5
+ # the primary body. This approximation works well for the Kerbal universe.
6
+ class Orbit
7
+ include Mixin::ParameterAttributes
8
+ include Mixin::OptionsProcessor
9
+
10
+ # A list of the independent parameters that define an orbit for this class.
11
+ BASE_PARAMETERS = [:periapsis, :periapsis_velocity, :inclination, :longitude_of_ascending_node, :argument_of_periapsis, :mean_anomaly, :epoch]
12
+
13
+ # A map of default values for initialization parameters.
14
+ DEFAULT_OPTIONS = BASE_PARAMETERS.inject({}) {|opts,param| opts[param] = 0.0; opts}.merge(:secondary_body => Body::TEST_PARTICLE)
15
+
16
+ # A quick-reference system structure data structure. Each parent body has a key with an array of children. Subsequent hits are necessary for additional lookups.
17
+ #SYSTEM_STRUCTURE = {:kerbol => [:moho, :eve, :kerbin, :duna, :jool], :eve => [:gilly], :kerbin => [:mun, :minmus], :duna => [:ike], :jool => [:laythe, :vall, :tylo, :bop]}.each do |primary, secondaries|
18
+
19
+ # For data read in from data files, this private method DRYs the process.
20
+ def self.make(planet_ref)
21
+ # Get the data and dup it so we can muck with it.
22
+ data = Data.fetch(:planet_data)[planet_ref][:orbit].dup
23
+ # Get the primary/secondary body refs from it and then lookup to get the values.
24
+ primary_body = Planetoid.send( data.delete(:primary_body) )
25
+ secondary_body = Planetoid.send( data.delete(:secondary_body) )
26
+ # Now construct the object.
27
+ return self.new( primary_body, data.merge(:secondary_body => secondary_body) ).freeze
28
+ end
29
+
30
+ class << self
31
+ private :make
32
+ end
33
+
34
+ # :category: Library Methods
35
+ def self.kerbin
36
+ return @kerbin ||= make(__method__)
37
+ end
38
+
39
+ # :category: Library Methods
40
+ def self.mun
41
+ return @mun ||= make(__method__)
42
+ end
43
+
44
+ # :category: Library Methods
45
+ def self.minmus
46
+ return @minmus ||= make(__method__)
47
+ end
48
+
49
+ # :category: Library Methods
50
+ def self.moho
51
+ return @moho ||= make(__method__)
52
+ end
53
+
54
+ # :category: Library Methods
55
+ def self.eve
56
+ return @eve ||= make(__method__)
57
+ end
58
+
59
+ # :category: Library Methods
60
+ def self.gilly
61
+ return @gilly ||= make(__method__)
62
+ end
63
+
64
+ # :category: Library Methods
65
+ def self.duna
66
+ return @duna ||= make(__method__)
67
+ end
68
+
69
+ # :category: Library Methods
70
+ def self.ike
71
+ return @ike ||= make(__method__)
72
+ end
73
+
74
+ # :category: Library Methods
75
+ def self.jool
76
+ return @jool ||= make(__method__)
77
+ end
78
+
79
+ # :category: Library Methods
80
+ def self.laythe
81
+ return @laythe ||= make(__method__)
82
+ end
83
+
84
+ # :category: Library Methods
85
+ def self.vall
86
+ return @vall ||= make(__method__)
87
+ end
88
+
89
+ # :category: Library Methods
90
+ def self.tylo
91
+ return @tylo ||= make(__method__)
92
+ end
93
+
94
+ # :category: Library Methods
95
+ def self.bop
96
+ return @bop ||= make(__method__)
97
+ end
98
+
99
+ # Convenience method for creating a circular orbit about the given body.
100
+ # This is redundant to calling new with the option of :radius.
101
+ def self.circular_orbit(primary_body, radius)
102
+ return self.new(primary_body, :radius => radius)
103
+ end
104
+
105
+ # Convenience method for creating a circular orbit of a given period around
106
+ # a given body.
107
+ def self.circular_orbit_of_period(primary_body, period)
108
+ planetoid_angular_velocity = 2.0 * Math::PI / period
109
+ radius = (primary_body.gravitational_parameter / planetoid_angular_velocity**2)**(1.0/3.0)
110
+ return self.circular_orbit(primary_body, radius)
111
+ end
112
+
113
+ # Convenience method for creating a geostationary orbit around the given body.
114
+ def self.geostationary_orbit(primary_body)
115
+ return self.circular_orbit_of_period(primary_body, primary_body.rotational_period)
116
+ end
117
+
118
+ # Convenience method for creating an escape orbit around the given body.
119
+ def self.escape_orbit(primary_body, periapsis)
120
+ periapsis_escape_velocity = Math.sqrt(2.0 * primary_body.gravitational_parameter / periapsis)
121
+ return self.new(primary_body, :periapsis => periapsis, :periapsis_velocity => periapsis_escape_velocity)
122
+ end
123
+
124
+ # Create a new orbit.
125
+ #
126
+ # The first argument should be the Body (usually a Planetoid) that is being orbited.
127
+ #
128
+ # The second argument is a list of parameters which should include a size
129
+ # and eccentricity specification, and a may include other optional parameters.
130
+ #
131
+ # To specify an orbit, at least one of the following pairs must be given:
132
+ # * periapsis, periapsis_velocity
133
+ # * periapsis, apoapsis
134
+ # * semimajor_axis, eccentricity
135
+ # * radius (assumes circular orbit)
136
+ #
137
+ # The following additional parameters may be given:
138
+ # * inclination
139
+ # * longitude_of_ascending_node
140
+ # * argument_of_periapsis
141
+ # * mean_anomaly
142
+ # * epoch
143
+ def initialize(primary_body, options={})
144
+ # Set the primary planetoid
145
+ self.primary_body = primary_body
146
+
147
+ # Set the periapsis and periapsis_velocity from the options
148
+ replaced_options = replace_orbital_parameters(options)
149
+
150
+ # Default all the base parameters to zero if not given.
151
+ process_options(replaced_options, DEFAULT_OPTIONS)
152
+ end
153
+
154
+ attr_parameter *BASE_PARAMETERS
155
+
156
+ # The body being orbited (required)
157
+ # Expected to be an instance of Planetoid.
158
+ attr_accessor :primary_body
159
+
160
+ # The body in orbit (optional)
161
+ # Expected to be an instance of Planetoid.
162
+ attr_accessor :secondary_body
163
+
164
+ # Returns the sphere of influence (SOI) for the primary body in the context
165
+ # of the two-body system.
166
+ #
167
+ # This is NOT the KSP SOI, for it, use +kerbal_sphere_of_influence+
168
+ def primary_body_sphere_of_influence
169
+ return self.semimajor_axis * (self.primary_body.mass / self.secondary_body.mass)**(0.4)
170
+ end
171
+
172
+ # Returns the sphere of influence (SOI) for the secondary body in the context
173
+ # of the two-body system.
174
+ #
175
+ # This is NOT the KSP SOI, for it, use +kerbal_sphere_of_influence+
176
+ def secondary_body_sphere_of_influence
177
+ return self.semimajor_axis * (self.secondary_body.mass / self.primary_body.mass)**(0.4)
178
+ end
179
+ alias_method :sphere_of_influence, :secondary_body_sphere_of_influence
180
+
181
+ # The Hill Sphere radius.
182
+ #
183
+ # This is NOT the KSP SOI, for it, use +kerbal_sphere_of_influence+
184
+ def hill_sphere_radius
185
+ return self.periapsis * (self.secondary_body.mass / (3.0*self.primary_body.mass))**(2.0/3.0)
186
+ end
187
+
188
+
189
+ # KSP uses this alternate hill sphere radius I found on Wikipedia.
190
+ def kerbal_sphere_of_influence
191
+ return self.periapsis * (self.secondary_body.mass / self.primary_body.mass)**(1.0/3.0)
192
+ end
193
+ alias_method :kerbal_soi, :kerbal_sphere_of_influence
194
+
195
+
196
+ # Orbit classification, returns one of :subelliptical, :circular, :elliptical,
197
+ # :parabolic, or :hyperbolic.
198
+ #
199
+ # Because of how floats work, a delta is given for evaluation of "critical"
200
+ # points such as circular and elliptical orbits. The default value is 1e-9,
201
+ # however other values may be given.
202
+ def classification(delta=0.000000001)
203
+ e = self.eccentricity
204
+ if( e.abs < delta )
205
+ return :circular
206
+ elsif( (e-1.0).abs < delta )
207
+ return :parabolic
208
+ elsif( e > 0.0 && e < 1.0 )
209
+ return :elliptical
210
+ elsif( e > 1.0 )
211
+ return :hyperbolic
212
+ elsif( e < 0.0 )
213
+ return :subelliptical
214
+ end
215
+ end
216
+ alias_method :kind, :classification
217
+
218
+ # Returns true if the orbit is a closed orbit (eccentricity < 1)
219
+ def closed?
220
+ return self.eccentricity < 1.0
221
+ end
222
+
223
+ # Returns false if the orbit is closed.
224
+ def open?
225
+ return !self.closed?
226
+ end
227
+
228
+ # Returns the gravitational parameter for this orbit.
229
+ # Note that this currently is G*M rather than G*(M+m).
230
+ def gravitational_parameter
231
+ return self.primary_body.gravitational_parameter
232
+ end
233
+
234
+ # The specific potential energy for any given orbit radius.
235
+ def specific_potential_energy(r)
236
+ return -self.gravitational_parameter / r
237
+ end
238
+
239
+ # The specific kinetic energy for any given velocity.
240
+ def specific_kinetic_energy(v)
241
+ return 0.5 * v**2
242
+ end
243
+
244
+ # The total specific energyy of the orbit; this is constant over the entire
245
+ # orbit.
246
+ def specific_energy
247
+ return self.specific_potential_energy(self.periapsis) + self.specific_kinetic_energy(self.periapsis_velocity)
248
+ end
249
+ alias_method :vis_viva_energy, :specific_energy
250
+
251
+ # The specific angular momentum for this orbit; this is constant over the
252
+ # entire orbit.
253
+ def specific_angular_momentum
254
+ return self.periapsis * self.periapsis_velocity
255
+ end
256
+ alias_method :angular_momentum, :specific_angular_momentum
257
+
258
+ # The orbit eccentricity.
259
+ def eccentricity
260
+ return (self.periapsis * self.periapsis_velocity**2 / self.gravitational_parameter) - 1
261
+ end
262
+
263
+ # The orbit semimajor-axis.
264
+ def semimajor_axis
265
+ if self.closed?
266
+ return self.periapsis / (1.0 - self.eccentricity)
267
+ else
268
+ return self.periapsis / (self.eccentricity - 1.0)
269
+ end
270
+ end
271
+
272
+ # The orbit semiminor_axis, if the eccentricity is less than one.
273
+ def semiminor_axis
274
+ if self.closed?
275
+ return self.semimajor_axis * Math.sqrt(1.0 - self.eccentricity**2)
276
+ else
277
+ return nil
278
+ end
279
+ end
280
+
281
+ # The orbital period, if the eccentricity is less than one.
282
+ def period
283
+ if self.closed?
284
+ return 2.0 * Math::PI * Math.sqrt( self.semimajor_axis**3 / self.gravitational_parameter )
285
+ else
286
+ return nil
287
+ end
288
+ end
289
+
290
+ # This is the mean angular velocity (angle change per unit time) for this orbit.
291
+ def mean_angular_velocity
292
+ if self.closed?
293
+ return 2.0 * Math::PI / self.period
294
+ else
295
+ return nil
296
+ end
297
+ end
298
+
299
+ # This is the mean angular velocity (angle change per unit time) for this orbit.
300
+ #
301
+ # This simply calls +mean_angular_velocity+ on self.
302
+ def mean_motion
303
+ return self.mean_angular_velocity
304
+ end
305
+
306
+ # This is the velocity associated with the mean motion at the semimajor axis,
307
+ # This works out to be the velocity of an object in a circular orbit with the same period.
308
+ def mean_velocity
309
+ return self.mean_motion * self.semimajor_axis
310
+ end
311
+
312
+ # The apoapsis radius, if the eccentricity is less than one.
313
+ def apoapsis
314
+ if self.closed?
315
+ return self.semimajor_axis * (1 + self.eccentricity)
316
+ else
317
+ return nil
318
+ end
319
+ end
320
+
321
+ # The apoapsis velocity, if the eccentricity is less than one.
322
+ def apoapsis_velocity
323
+ if self.closed?
324
+ e = self.eccentricity
325
+ k = self.gravitational_parameter / self.semimajor_axis
326
+ return Math.sqrt( (1-e)/(1+e) * k )
327
+ else
328
+ return nil
329
+ end
330
+ end
331
+
332
+ # Instantiates a new orbit with the same period and semimajor axis, zero
333
+ # inclination, and is circular.
334
+ #
335
+ # This is useful for idealizing orbits for rough calculations.
336
+ #--
337
+ # TODO: Figure out how to translate parameters such as mean anomaly and LAN.
338
+ #++
339
+ def circularize
340
+ return self.class.new(primary_body, {
341
+ :secondary_body => self.secondary_body,
342
+ :radius => self.semimajor_axis,
343
+ :inclination => 0.0
344
+ })
345
+ end
346
+
347
+ private
348
+
349
+ # This takes one several sets of base parameters, and derives the periapsis
350
+ # and periapsis_velocity, and then replaces the original keys with them.
351
+ #
352
+ # This is helper method for initialization.
353
+ def replace_orbital_parameters(options)
354
+ # We don't want to mutate the options hash they passed in.
355
+ opts = options.dup
356
+
357
+ if( opts.include?(:periapsis) && opts.include?(:periapsis_velocity) )
358
+ return opts
359
+ elsif( opts.include?(:periapsis) && opts.include?(:apoapsis) )
360
+ rp = opts[:periapsis]
361
+ ra = opts.delete(:apoapsis)
362
+ opts[:periapsis_velocity] = Math.sqrt( (2.0 * ra)/(ra+rp) * (self.gravitational_parameter / rp) )
363
+ elsif( opts.include?(:eccentricity) && opts.include?(:semimajor_axis) )
364
+ e = opts.delete(:eccentricity)
365
+ a = opts.delete(:semimajor_axis)
366
+ mu = self.gravitational_parameter
367
+ opts[:periapsis] = rp = a*(1-e)
368
+ opts[:periapsis_velocity] = Math.sqrt( (e+1)*mu/rp )
369
+ elsif( opts.include?(:radius) )
370
+ opts[:periapsis] = opts.delete(:radius)
371
+ # This is a special case of the equation used for apoapsis/periapsis configuration, where they are equal.
372
+ opts[:periapsis_velocity] = Math.sqrt( self.gravitational_parameter / opts[:periapsis] )
373
+ end
374
+
375
+ return opts
376
+ end
377
+
378
+ end
379
+ end
@@ -0,0 +1,6 @@
1
+ require_relative 'orbital_maneuver/burn_event'
2
+
3
+ require_relative 'orbital_maneuver/base'
4
+ require_relative 'orbital_maneuver/hohmann'
5
+ require_relative 'orbital_maneuver/bielliptic'
6
+ require_relative 'orbital_maneuver/inclination_change'
@@ -0,0 +1,159 @@
1
+ module KerbalDyn
2
+ # The namespace for all orbital maneuvers
3
+ module OrbitalManeuver
4
+ # The base-class for all orbital maneuvers.
5
+ class Base
6
+ include Mixin::OptionsProcessor
7
+ include Mixin::ParameterAttributes
8
+
9
+ def initialize(initial_orbit, final_orbit, options={})
10
+ # For now this is a safe thing to do, *way* in the future we may have to differentiate
11
+ # between complex maneuvers to other systems and maneuvers within the system.
12
+ raise ArgumentError, "Expected the initial and final orbit to orbit the same body." unless initial_orbit.primary_body == final_orbit.primary_body
13
+ self.initial_orbit = initial_orbit
14
+ self.final_orbit = final_orbit
15
+
16
+ process_options(options)
17
+ end
18
+
19
+ # The orbit you are starting at.
20
+ attr_accessor :initial_orbit
21
+
22
+ # The orbit you are going to maneuver into.
23
+ attr_accessor :final_orbit
24
+
25
+
26
+ # Returns an array of the orbits, including the initial and final orbit.
27
+ #
28
+ # Subclasses should override this method.
29
+ def orbits
30
+ return [initial_orbit, final_orbit]
31
+ end
32
+
33
+ # Returns the array of burn events.
34
+ #
35
+ # Subclasses should override this method, as most other values are derived
36
+ # from it.
37
+ def burn_events
38
+ return @burn_events ||= []
39
+ end
40
+
41
+ # Returns true if the final orbit has a larger semimajor axis than the
42
+ # initial orbit.
43
+ def moving_to_higher_orbit?
44
+ return final_orbit.semimajor_axis > initial_orbit.semimajor_axis
45
+ end
46
+
47
+ # Returns an array of the before/after burn even velocity pairs for each burn event.
48
+ def velocities
49
+ return self.burn_events.map {|be| [be.initial_velocity, be.final_velocity]}
50
+ end
51
+
52
+ # Returns an array of the velocity changes for each burn event.
53
+ def delta_velocities
54
+ return self.burn_events.map {|be| be.delta_velocity}
55
+ end
56
+
57
+ # Returns an array of the effective impulse times of each maneuver.
58
+ def times
59
+ return self.burn_events.map {|be| be.time}
60
+ end
61
+
62
+ # Returns an array of the orbital radii for the burn events.
63
+ def orbital_radii
64
+ return self.burn_events.map {|be| be.orbital_radius}
65
+ end
66
+
67
+ # Returns an array of the mean anomaly at each maneuver.
68
+ def mean_anomalies
69
+ return self.burn_events.map {|be| be.mean_anomaly}
70
+ end
71
+
72
+ # Calculates the total delta-v for this maneuver.
73
+ #
74
+ # This is always a positive quantity that relates the total amount of
75
+ # velocity change necessary, and hence a sense of the total amount of
76
+ # fuel used during the maneuver.
77
+ #
78
+ # The baseclass implementation is to sum the absolute values of the
79
+ # deltas in the velocity list +delta_velocities+.
80
+ def delta_velocity
81
+ return self.delta_velocities.reduce(0) {|a,b| a.abs + b.abs}
82
+ end
83
+ alias_method :delta_v, :delta_velocity
84
+
85
+ # Calculates the total time for completion.
86
+ #
87
+ # For some kinds of idealized maneuvers this may be meaningless, in which
88
+ # case nil is returned.
89
+ #
90
+ # Subclasses should override this.
91
+ def delta_time
92
+ return self.times.last - self.times.first
93
+ end
94
+
95
+ # An alias to delta_time.
96
+ def delta_t
97
+ return self.delta_time
98
+ end
99
+
100
+ # Calculates the total mean anomaly covered during the maneuver.
101
+ def delta_mean_anomaly
102
+ return self.mean_anomalies.last - self.mean_anomalies.first
103
+ end
104
+
105
+ # Calculates the mean lead-angle for a given maneuver.
106
+ #
107
+ # For a target in a higher orbit, this is the mean anomaly ahead of you
108
+ # that the target should be located at.
109
+ def mean_lead_angle
110
+ target_delta_mean_anomaly = self.delta_time * self.final_orbit.mean_angular_velocity
111
+ return self.delta_mean_anomaly - target_delta_mean_anomaly
112
+ end
113
+
114
+ # The time elapsed such that--if started when the target is directly radial
115
+ # from you (same true anomaly)--you would start your transfer orbit in order
116
+ # to intercept.
117
+ def mean_lead_time
118
+ # theta_f1 = theta_01 + omega_1*t
119
+ # theta_f2 = theta_02 + omega_2*t
120
+ # theta_f2 - theta_f1 = theta_lead (After time t, we want to achieve lead angle)
121
+ # theta_02 - theta_01 = 0 (When we start the clock, we want no separation)
122
+ #
123
+ # Thefefore: theta_lead = (omega_2 - omega_1) * t
124
+ #
125
+ # We also have to find the offset (n*2*pi) to the lead angle that will lead to the
126
+ # first positive time. This depends on wether delta_omega is positive or
127
+ # negative.
128
+ delta_omega = self.final_orbit.mean_angular_velocity - self.initial_orbit.mean_angular_velocity
129
+
130
+ two_pi = 2.0*Math::PI
131
+ theta_lead = (self.mean_lead_angle % two_pi)
132
+ theta_lead = (delta_omega>0.0) ? theta_lead : (theta_lead-two_pi)
133
+
134
+ return theta_lead / delta_omega
135
+ end
136
+
137
+ # For every full cycle of the initial orbit (2pi radians), the final orbit
138
+ # covers either less than 2pi radians (if it is bigger) or more than 2pi radians
139
+ # (if it is smaller) in the same amount of time. The relative_delta_anomaly
140
+ # is the change difference covered.
141
+ #
142
+ # If it is positive, then the initial orbit is slower,
143
+ # If it is zero, then they are in lock-step,
144
+ # If it is negative, then the initial orbit is faster.
145
+ #
146
+ # Note that for a large orbit ratio, going from the lower to higher orbit
147
+ # will have a very negative anomaly close to -2pi (the target didn't move much
148
+ # much , while you did your rotation), while going from a higher to lower
149
+ # orbit has a high positive number (the target did a lot of laps while you
150
+ # were waiting for it.
151
+ def relative_anomaly_delta
152
+ initial_orbit_anomaly = 2.0 * Math::PI
153
+ final_orbit_anomaly = 2.0 * Math::PI * (self.initial_orbit.period / self.final_orbit.period)
154
+ return final_orbit_anomaly - initial_orbit_anomaly
155
+ end
156
+
157
+ end
158
+ end
159
+ end