forked from maneatingape/rsvp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtransfer.ks
304 lines (242 loc) · 12.1 KB
/
transfer.ks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
@lazyglobal off.
parameter export.
export("vessel_to_vessel", vessel_to_vessel@).
export("vessel_to_body", vessel_to_body@).
export("body_to_vessel", body_to_vessel@).
export("body_to_body", body_to_body@).
// Vessel to vessel rendezvous (asteroids and comets also technically count
// as vessels) within the same SOI is the most straightforward case. The values
// from the Lambert solver are more than accurate enough to be used directly
// resulting in very precise intercepts, even over interplanetary distances.
local function vessel_to_vessel {
parameter destination, settings, transfer, result.
// Calculate transfer orbit details from search result
local flip_direction is transfer:flip_direction.
local departure_time is transfer:departure_time.
local arrival_time is transfer:arrival_time.
local details is rsvp:transfer_deltav(ship, destination, flip_direction, departure_time, arrival_time).
// 1st node
local maneuver is create_vessel_node(departure_time, details:dv1).
local departure is lex("time", departure_time, "deltav", details:dv1:mag).
result:add("actual", lex("departure", departure)).
// Check for unexpected encounters
local expected_patches is list(ship:body).
local validate_patches is maneuver:validate_patches(expected_patches, arrival_time).
if not validate_patches:success {
return validate_patches.
}
// 2nd node
if settings:create_maneuver_nodes = "both" {
create_vessel_node(arrival_time, details:dv2).
local arrival is lex("time", arrival_time, "deltav", details:dv2:mag).
result:actual:add("arrival", arrival).
}
return result.
}
// Vessel to body rendezvous requires some tweaking in order for the ship
// to avoid colliding directly with the center of the destination.
local function vessel_to_body {
parameter destination, settings, transfer, result.
// Calculate transfer orbit details from search result. This transfer will be
// *too* accurate with a trajectory that collides with the center of the body.
local flip_direction is transfer:flip_direction.
local departure_time is transfer:departure_time.
local arrival_time is transfer:arrival_time.
local details is rsvp:transfer_deltav(ship, destination, flip_direction, departure_time, arrival_time).
// Refine the transfer, taking destination's SOI into account.
local delta is 1.
local iterations is 0.
local final_orbit_periapsis is settings:final_orbit_periapsis.
local final_orbit_orientation is settings:final_orbit_orientation.
until delta < 0.01 or iterations = 15 {
local offset is rsvp:offset_from_soi_edge(destination, final_orbit_periapsis, final_orbit_orientation, details:dv2).
local duration is rsvp:duration_from_soi_edge(destination, final_orbit_periapsis, details:dv2).
local next is rsvp:transfer_deltav(ship, destination, flip_direction, departure_time, arrival_time - duration, ship:body, offset).
local delta is (next:dv2 - details:dv2):mag.
set details to next.
set iterations to iterations + 1.
}
// Create initial maneuver node
local maneuver is create_vessel_node(departure_time, details:dv1).
// Check for unexpected encounters
local expected_patches is list(ship:body, destination).
local validate_patches is maneuver:validate_patches(expected_patches, arrival_time).
if not validate_patches:success {
return validate_patches.
}
// Add actual departure deltav to the result. This will differ slightly
// from the predicted value due to the offset tweaks.
local departure is lex("time", departure_time, "deltav", maneuver:deltav()).
result:add("actual", lex("departure", departure)).
// 2nd node
if settings:create_maneuver_nodes = "both" {
local arrival is create_body_arrival_node(destination, settings, maneuver).
result:actual:add("arrival", arrival).
}
return result.
}
// Vessel to body rendezvous is not as accurate as other transfer types, so a
// correction burn is recommended once in interplanetary space.
local function body_to_vessel {
parameter destination, settings, transfer, result.
// 1st node
local maybe is create_body_departure_node(false, destination, settings, transfer).
// Node creation could fail due to unexpected encounter
if not maybe:success {
return maybe.
}
// Check for unexpected encounters
local maneuver is maybe:maneuver.
local expected_patches is list(ship:body, ship:body:body).
local arrival_time is transfer:arrival_time.
local validate_patches is maneuver:validate_patches(expected_patches, arrival_time).
if not validate_patches:success {
return validate_patches.
}
// Add actual departure deltav to the result. This will differ quite a bit
// from the predicted value due to the difficulties ejecting from a body
// exactly at the predicted time and orientation.
local departure is lex("time", maneuver:time(), "deltav", maneuver:deltav()).
result:add("actual", lex("departure", departure)).
// 2nd node
if settings:create_maneuver_nodes = "both" {
local osv1 is rsvp:orbital_state_vectors(ship, arrival_time).
local osv2 is rsvp:orbital_state_vectors(destination, arrival_time).
local deltav is osv2:velocity - osv1:velocity.
create_vessel_node(arrival_time, deltav).
local arrival is lex("time", arrival_time, "deltav", deltav:mag).
result:actual:add("arrival", arrival).
}
return result.
}
// Body to body rendezvous is reasonably accurate as the predicted intercept
// can be used to refine the initial transfer.
local function body_to_body {
parameter destination, settings, transfer, result.
// 1st node
local maybe is create_body_departure_node(true, destination, settings, transfer).
// Node creation could fail due to unexpected encounter
if not maybe:success {
return maybe.
}
// Check for unexpected encounters
local maneuver is maybe:maneuver.
local expected_patches is list(ship:body, ship:body:body, destination).
local arrival_time is transfer:arrival_time.
local validate_patches is maneuver:validate_patches(expected_patches, arrival_time).
if not validate_patches:success {
return validate_patches.
}
// Add actual departure deltav to the result. This will differ somewhat
// from the predicted value due to the difficulties ejecting from a body
// exactly at the predicted time and orientation.
set maneuver to maybe:maneuver.
local departure is lex("time", maneuver:time(), "deltav", maneuver:deltav()).
result:add("actual", lex("departure", departure)).
// 2nd node
if settings:create_maneuver_nodes = "both" {
local arrival is create_body_arrival_node(destination, settings, maneuver).
result:actual:add("arrival", arrival).
}
return result.
}
// Creates both departure and arrival nodes for vessels, as the steps are the
// same for both situations.
local function create_vessel_node {
parameter epoch_time, deltav.
return rsvp:create_maneuver(true, epoch_time, deltav).
}
// Create an arrival node for a body, using the various "final_orbit..."
// setttings to result in the desired orbit periapsis and shape.
local function create_body_arrival_node {
parameter destination, settings, maneuver.
local patch_details is maneuver:patch_details(destination).
local soi_velocity is patch_details:soi_velocity.
local periapsis_altitude is patch_details:periapsis_altitude.
local periapsis_time is patch_details:periapsis_time.
local insertion_deltav is rsvp[settings:final_orbit_type + "_insertion_deltav"].
local deltav is insertion_deltav(destination, periapsis_altitude, soi_velocity).
// Brake by the right amount at the right time.
rsvp:create_raw_maneuver(false, periapsis_time, v(0, 0, -deltav)).
return lex("time", periapsis_time, "deltav", deltav).
}
// Creates an ejection maneuver node by applying a feedback loop to refine it.
// The initial transfer will be incorrect as it assumes that the vessel
// is floating in free space and neglects the time taken to climb out of the
// origin planet's SOI.
// We re-calculate the transfer immediately after leaving the origin's SOI
// then feedback this error in order to correct our initial guess. This
// converges rapidly to an accurate intercept.
local function create_body_departure_node {
parameter to_body, destination, settings, transfer.
local flip_direction is transfer:flip_direction.
local departure_time is transfer:departure_time.
local arrival_time is transfer:arrival_time.
local parent is ship:body.
local grandparent is parent:body.
local expected_patches is list(parent, grandparent).
// Initial guess
local details is rsvp:transfer_deltav(parent, destination, flip_direction, departure_time, arrival_time).
local departure_deltav is details:dv1.
local maneuver is create_maneuver_node_in_correct_location(departure_time, departure_deltav).
// Refine the node
local delta is v(1, 0, 0).
local iterations is 0.
local final_orbit_periapsis is settings:final_orbit_periapsis.
local final_orbit_orientation is settings:final_orbit_orientation.
local duration is 0.
local offset is v(0, 0, 0).
until delta:mag < 0.01 or iterations = 15 {
// Expect the unexpected
local patch_details is maneuver:patch_details(grandparent).
local soi_time is choose "max" if patch_details = "none" else patch_details:soi_time.
local validate_patches is maneuver:validate_patches(expected_patches, soi_time).
if not validate_patches:success {
return validate_patches.
}
// Take SOI into account
if to_body {
set offset to rsvp:offset_from_soi_edge(destination, final_orbit_periapsis, final_orbit_orientation, details:dv2).
set duration to rsvp:duration_from_soi_edge(destination, final_orbit_periapsis, details:dv2).
}
// Calculate correction using predicted flight path
local details is rsvp:transfer_deltav(ship, destination, flip_direction, soi_time, arrival_time - duration, grandparent, offset).
// Update our current departure velocity with this correction.
set delta to details:dv1.
set iterations to iterations + 1.
set departure_time to maneuver:time().
set departure_deltav to departure_deltav + delta.
// Apply the new node, rinse and repeat.
maneuver:delete().
set maneuver to create_maneuver_node_in_correct_location(departure_time, departure_deltav).
}
return lex("success", true, "maneuver", maneuver).
}
// Creates maneuver node at the correct location around the origin planet in
// order to eject at the desired orientation.
// Using the raw magnitude of the delta-v as our cost function handles
// mutliple situtations and edge cases in one simple robust approach.
// For example prograde/retrograde ejection combined with
// clockwise/anti-clockwise orbit gives at least 4 valid possibilities
// that need to handled.
//
// Additionaly this method implicitly includes the necessary adjustment
// to the manuever node position to account for the radial component
// of the ejection velocity.
//
// Finally it can handle non-perfectly circular and inclined orbits.
local function create_maneuver_node_in_correct_location {
parameter departure_time, departure_deltav.
function ejection_details {
parameter cost_only, v.
local epoch_time is v:x.
local osv is rsvp:orbital_state_vectors(ship, epoch_time).
local ejection_deltav is rsvp:vessel_ejection_deltav_from_body(ship:body, osv, departure_deltav).
return choose ejection_deltav:mag if cost_only else ejection_deltav.
}
// Search for time in ship's orbit where ejection deltav is lowest.
local cost is ejection_details@:bind(true).
local result is rsvp:line_search(cost, departure_time, 120, 1).
local ejection_deltav is ejection_details(false, result:position).
return rsvp:create_maneuver(false, result:position:x, ejection_deltav).
}