Как бы ни было интересно исследование планет с орбиты, чтобы получать больше профита в виде баллов науки, нужно иногда спускаться с небес на землю. Этим и займёмся.
Здесь рассмотрю только ракетный способ посадки и только на планеты без плотной атмосферы (т.е. все стоковые планеты, кроме Кербина, Евы, Джула и Лейт; на Дюну способ посадки подойдёт после некоторой доработки напильником). Рассматриваться будет посадка с орбиты в точку с заданными координатами, чтобы разобрать использование встроенной структуры GEOCOORDINATES. Задача посадки корабля в заданную точку, с точки зрения практического приложения, наиболее ценна для строительства наземных баз.
Часто обсуждаемые на около-KSP-шных ресурсах подходы для посадки аппаратов:
- Suicide burn. Состоит во включении двигателя на 100% мощности, удерживая при этом вектор тяги строго против вектора скорости относительно поверхности. При правильном расчёте момента включения скорость гасится ровно в момент касания поверхности.
Преимущества: в теории, это способ посадки с минимальными затратами топлива; для выполнения не требуется возможность дросселирования и повторного запуска двигателя.
Недостатки: сложно рассчитать правильный момент включения; ещё сложнее рассчитать начальную орбиту так, чтобы сесть в заданной точке; высокая чувствительность точки посадки к ошибкам управления (неправильный момент начала прожига, отличие угла тяга-скорость от 180°) и невозможность их исправления; из-за движения по пологой дуге довольно близко к поверхности, есть риск столкнуться с горой, т.е. не вся поверхность доступна для посадки. - Торможение "буквой Г". Полное гашение горизонтальной скорости, вертикальное падение на поверхность, затем полное гашение вертикальной скорости.
Преимущества: проще всего для расчётов; при достаточной начальной высоте полное отсутствие риска столкновения с горой или стеной кратера; если гашение вертикальной скорости делается через suicide burn, можно обойтись двигателем без дросселирования.
Недостатки: самый неэффективный способ, с точки зрения затрат топлива; требует, как минимум, двух включений двигателя. - Zero-lift maneuver (снижение с нулевой подъёмной силой). Аппарат ориентируется таким образом, чтобы обеспечить снижение с постоянной вертикальной скоростью до полного гашения горизонтальной скорости. Затем идёт вертикальный спуск с торможением. По сути, является "взлётом на орбиту наоборот".
Преимущества: если двигатель дросселируется, то легко можно поддерживать заранее рассчитанное горизонтальное ускорение, что упрощает посадку в заданной точке; при правильном расчёте можно после гашения горизонтальной скорости сразу перейти на suicide burn без необходимости перезапуска двигателя; широкие возможности для управления и коррекции траектории спуска.
Недостатки: не так эффективен по затратам топлива, как suicide burn в чистом виде; для посадки в заданной точке требуется либо дросселируемый двигатель, либо сложный баллистический расчёт.
Из списка преимуществ и недостатков должно быть более-менее ясно видно, что в реальности наиболее популярным способом обычно является что-то типа третьего варианта. Экономия топлива - это хорошо, но ей жертвуют в пользу возможности компенсировать отклонения в характеристиках двигателя, массе аппарата и прочих параметров полёта от расчётных, что невозможно при реализации suicide burn в чистом виде.
Здесь рассмотрю вариацию последнего способа, которую можно описать как "снижение с постоянным ускорением". Спуск будет организован так, чтобы ускорения как вдоль локальной горизонтали, так и вдоль локальной вертикали были постоянны. Такой способ ощутимо экономнее посадки "буквой Г" на тела размером с Муну и больше. Для тел с низкой гравитацией экономия топлива будет менее заметной, и посадка с полным гашением сначала горизонтальной, а потом вертикальной скорости может быть удобнее с точки зрения простоты расчётов.
Наиболее эффективно, с точки зрения затрат топлива, садиться в точку, лежащую на наземной трассе орбиты, в этом случае не требуется коррекция бокового отклонения. Поэтому начальным этапом будет коррекция плоскости орбиты, чтобы она проходила над нужной точкой. На активном участке торможения будем предполагать, что боковое отклонение представляет собой лишь небольшую погрешность управления. Начальную орбиту будем считать круговой.
Процедура посадки, таким образом, состоит из следующих этапов.
1) Дождаться, пока желаемая точка посадки оказывается в пределах возможностей коррекции наклонения.
2) Сделать коррекцию наклонения, желательно, совместив её с торможением до суборбитальной траектории. Делается это, когда до посадки остаётся пройти дугу 90°.
3) Задать желаемую величину горизонтального ускорения. В случае TWR > 1 всё более-менее просто, достаточно взять
ah = [(T/m)2 - g2]1/2, в этом случае гарантированно тяги хватит и на компенсацию ускорения свободного падения, и на замедление по горизонтали. При TWR < 1 задача немного усложняется, но при условии увеличения TWR по мере опорожнения баков до величины > 1 иногда всё-таки возможна мягкая посадка.
4) Дождаться комбинации горизонтальной скорости
vh и расстояния
lground до точки посадки, удовлетворяющей условию
lground = vh2/(2ah) - начав активный участок снижения в этой точке, мы гасим горизонтальную скорость ровно над нужным участком.
5) Вертикальное ускорение нужно определить таким образом, чтобы после окончания гашения горизонтальной скорости переходить насколько возможно ближе к режиму suicide burn (как это сделать - см. дальше)
6) Рулить и управлять тягой таким образом, чтобы поддерживать всегда заданные значения ускорений по горизонтали и вертикали. Поскольку высота и расстояние по поверхности до заданной точки посадки должны меняться по известному закону, их можно удерживать на нужных значениях через ПИД-регуляторы. Ещё одним ПИД-регулятором нужно корректировать боковое отклонение от цели.
7) Дождаться гашения горизонтальной скорости, развернуться соплом вниз и выполнить вертикальное снижение. Если всё рассчитано правильно, сесть должны одним куском.
Теперь разбираем по пунктам более подробно.
1. Дождаться нужного положения точки посадки относительно орбиты.
Пусть вектор нормали к плоскости орбиты
n, максимальная возможность коррекции плоскости составляет
αmax градусов. Расстояние на поверхности, на которое возможна боковая коррекция, в этом случае составляет
dmax = R×sin αmax, где
R - радиус планеты. Если точка посадки находится от плоскости на расстоянии меньше
dmax, коррекция допустима. Расстояние от точки до плоскости находится по формуле
d = |(Rland·n)|, где
Rland - радиус-вектор точки в системе координат с началом в центре планеты.
Осталась задача: найти радиус-вектор точки посадки по известным географическим координатам (широте и долготе).
В kOS для этого встроен тип данных GEOCOORDINATES, имеющий следующие поля:
LAT // широта в градусах
LNG // долгота в градусах
DISTANCE // расстояние в метрах от активного корабля
TERRAINHEIGHT // высота поверхности над уровнем моря
HEADING // абсолютный азимут от корабля
BEARING // на сколько градусов нужно повернуть нос корабля, чтобы он смотрел на заданную точку
POSITION // координаты точки на поверхности (вектор)
ALTITUDEPOSITION // координаты точки на заданной высоте над поверхностью
VELOCITY // скорость точки на поверхности за счёт вращения планеты (OrbitableVelocity, чтобы получить вектор, нужно взять COORD:VELOCITY:ORBIT)
ALTITUDEVELOCITY // скорость точки на заданной высоте над поверхностью (OrbitableVelocity, чтобы получить вектор, нужно взять COORD:ALTITUDEVELOCITY(H):ORBIT)
Задать переменную этого типа можно следующими способами:
1) по заданным широте и долготе - функцией LATLNG(lat, lng);
2) получив координаты объекта на орбите через суффикс orbitable:GEOPOSITION (привязывает координаты к телу, в сфере действия которого в текущий момент находится объект, т.е. Mun:geoposition будет возвращать координаты, привязанные к Кербину, Duna:geoposition - к Солнцу);
3) функцией body:GEOPOSITIONOF(position), которая возвращает координаты, привязанные к телу, указанному как body (Sun:GEOPOSITIONOF(ship:position) вернёт координаты на Солнце, над которыми в данный момент находится корабль, вне зависимости от текущей сферы действия).
Расстояние точки с заданными координатами до плоскости, заданной нормалью:
function GeoDist {
parameter geocoord.
parameter normvec to V(0,1,0).
return abs( vdot(geocoord:position, normvec:normalized) ).
}
При расчёте положения точки посадки относительно орбиты нужно, конечно, учесть суточное вращение планеты. Оно приводит к повороту точки с фиксированными широтой и долготой на восток с течением времени.
Рисунок 1. Сдвиг точки посадки за счёт суточного вращения.
Как видно из рисунка 1, чтобы попасть в цель, орбита после коррекции должна проходить восточнее, чем точка посадки в момент коррекции. Величина сдвига по долготе
Δφ за время
Δt равна
Δφ = 360°/Trot×Δt.
Trot - длительность звёздных суток.
Отсюда следует, что нужно дожидаться попадания на заданное расстояние от плоскости орбиты не точки посадки, а точки с широтой точки посадки и долготой, большей на
Δφ = 360°/Trot×Δt.
Δt - это время, через которое аппарат пройдёт над точкой посадки; оно определяется углом между радиус-вектором аппарата и проекцией радиус-вектора точки посадки на орбитальную плоскость. Угол отсчитывается в направлении движения спутника. Для вычисления угла удобно пользоваться функцией ARCTAN2(y, x) - она возвращает угол между осью X и радиус-вектором (x, y) в двухмерном пространстве с учётом знака этого угла (ARCTAN2(1, 0) = 90°, ARCTAN2(-1, 0) = -90°). Свойство этой функции состоит в том, что ARCTAN2(
Rsin
α,
Rcos
α) =
α, где
R - произвольное положительное число. Для двух векторов
R1 и
R2 выполняются равенства
|[R1×R2]| = |R1|·|R2|sinα, (R1·R2) = |R1|·|R2|cosα. Значит, для нахождения угла между двумя векторами в орбитальной плоскости нужно брать ARCTAN2(y, x), где y - проекция векторного произведения векторов на нормаль к орбите, x - скалярное произведение этих же векторов.
Поскольку прохождение угла
α по орбите занимает время
Δt = Torb×α/360°, то место, где окажется точка посадки в момент ближайшего к ней прохождения аппарата на текущем витке, сдвинуто от текущего положения точки посадки по долготе на
Δφ = 360°/Trot×Δt = α×Torb/Trot.
Таким образом, ожидание положения точки посадки относительно орбиты реализуется следующим кодом:
function WaitOrient {
parameter tgtcoord.
parameter maxangle to arcsin(50/velocity:orbit). // по умолчанию разрешаем коррекцию плоскости, если она не больше 50 м/с
if abs(tgtcoord:lat) > orbit:inclination + maxangle or abs(tgtcoord:lat) > 180 - orbit:inclination + maxangle {return 1/0.} // если широта отличается от наклонения больше, чем на maxangle, коррекция невозможна
local lngcorr to OrbAngTo(tgtcoord:position)*orbit:period/body:rotationperiod. // поправка на вращение планеты
local corrtgt to latlng(tgtcoord:lat, tgtcoord:lng + lngcorr).
local nvec to vcrs( body:position, velocity:orbit ).
until GeoDist(corrtgt, nvec) < body:radius * sin(maxangle) {
set warp to 4. // 100x, если позволяет высота
wait 10.
set lngcorr to OrbAngTo(tgtcoord:position)*orbit:period/body:rotationperiod.
set corrtgt to latlng(tgtcoord:lat, tgtcoord:lng + lngcorr).
set nvec to vcrs( body:position, velocity:orbit ).
}
set warp to 0.
}
function OrbAngTo {
//возвращает угол между орбитальным положением корабля и проекцией другого вектора на орбитальную плоскость
parameter pos.
local nvec to vcrs( body:position, velocity:orbit ):normalized.
local proj to pos - body:position - vxcl(nvec, pos - body:position).
local angl to arctan2( vdot(nvec, vcrs(body:position, proj)), -vdot(body:position, proj) ).
if angl < 0 set angl to 360 + angl.
return angl.
}
2. Поворот орбиты так, чтобы она прошла над нужной точкой.
С точки зрения затрат топлива, поворот орбиты выгоднее всего делать примерно за 90° до посадки. При проведении коррекции необходимо учесть вращение тела. Кроме этого, выгодно изменение плоскости совмещать с другим орбитальным манёвром, например, торможением до суборбитальной траектории.
Расчёт коррекции:
function RotateOrbit {
// совмещает поворот орбиты со сбросом траектории на суборбитальную
parameter tgtcoord.
parameter newpe to 0.
warpfor((OrbAngTo(tgtcoord:position) - 90)/360*orbit:period).
local lngcorr to 90*orbit:period/body:rotationperiod.
local corrtgt to latlng(tgtcoord:lat, tgtcoord:lng + lngcorr).
local lock tgtdir to corrtgt:heading. // направление на точку посадки с поправкой на вращение планеты
local lock newvdir to heading(tgtdir, 0):vector. // направление скорости, которое должно быть, чтобы орбита прошла над заданной точкой
local lock newsma to body:radius + (newpe + altitude)*0.5.
local lock newvmag to sqrt( body:mu * (2/(body:radius + altitude) - 1/newsma) ).
local newv to newvmag*newvdir.
local deltav to newv - velocity:orbit.
lock steering to lookdirup(deltav, up:vector).
wait until vang(facing:vector, deltav) < 1.
lock throttle to 1.
wait until GeoDist(corrtgt, vcrs(body:position, velocity:orbit)) < 50.
lock throttle to 0.
unlock steering.
}
3. Установка максимального горизонтального ускорения.
Предположим, этот шаг сделан, и нужная величина (по модулю) записана в переменной maxah.
4. Ожидание такого расстояния до цели, чтобы с заданным ускорением затормозить точно над ней.
Если со скорости
v0 тормозить с постоянным ускорением
ah, то пойденный путь к моменту остановки, согласно кинематическим формулам, будет равен
s = |v02/(2×ah)|. Т.е. всё, что нужно сделать - дождаться, когда горизонтальная скорость и расстояние до точки посадки будут удовлетворять этому соотношению.
function waitdownrange {
parameter tgtcoord.
parameter maxah.
local lock vh to ship:groundspeed. // groundspeed - скорость относительно поверхности
local hland to tgtcoord:terrainheight.
local stoptime to vh/maxah.
until false {
wait 0.
set stoptime to vh/maxah.
local stopdist to vh^2/(2*maxah).
// расстояние по поверхности равно R_ave * alpha,
// где alpha - угол в радианах между векторами
// из центра притяжения на корабль и на цель,
// R_ave - среднее расстояние от центра притяжения
// до корабля за время снижения.
// При равноускоренном снижении R_ave будет находиться
// на 1/3 пути от поверхности до высоты аппарата
local tgtdist to (body:radius + (altitude -hland)/3)*vang(up:vector,tgtcoord:position - body:position)*constant:degtorad.
if tgtdist < stopdist + vh*0.2 break. // 0.2 секунды - время запуска двигателя в стоке
set kuniverse:timewarp:rate to (tgtdist - stopdist)/(vh*5).
}
return stoptime.
}
5. Расчёт необходимого вертикального ускорения, чтобы после гашения горизонтальной скорости аппарат оказался на высоте suicide burn-а.
Здесь нужно решить сложное уравнение.
При движении с начальной высоты
h0 с начальной вертикальной скоростью
v0v и вертикальным ускорением
av, за время
tstop корабль окажется на высоте
h1 = h0 + v0v·tstop + av·t2stop/2
Скорость при этом будет равна
v1v = v0v + av·tstop
Чтобы не тратить лишнее топливо, нужно, чтобы эти высота и скорость соответство переходу в suicide burn сразу же после гашения горизонтальной скорости, т.е.
h1 = hground + v21v/[2(T/m - g)],
где
T - тяга двигателя,
m - масса корабля (нужно брать оценку на момент посадки),
g - ускорение свободного падения,
hground - высота поверхности в точке посадки.
Из этих трёх выражений получается квадратное уравнение относительно
av, решение которого даёт нужное ускорение.
function GetVertAcc {
parameter tgtcoord.
parameter stoptime, Isp. // обе величины в секундах, stoptime - время гашения горизонтальной скорости
local hstart to altitude.
local vv to ship:verticalspeed.
local hland to tgtcoord:terrainheight.
// оценка скорости, которую нужно стравить
// складывается из непосредственно скорости и гравипотерь;
// потери оценены как g*stoptime/5
local dv to sqrt(velocity:surface:sqrmagnitude + 2*body:mu*(1/(body:radius + hland) - 1/(body:radius + hstart)) ) + body:mu/body:position:sqrmagnitude*stoptime/5.
// оценка массы и максимально достижимого вертикального ускорения перед переходом к вертикальному спуску
local endmass to mass*constant:e^(-dv/(Isp*9.80665)).
local endav to availablethrust/endmass - body:mu/(body:radius + hland)^2.
local a to stoptime^2/(2*endav).
local b to stoptime * ( vv/endav - stoptime/2 ).
local c to vv^2/(2*endav) - vv*stoptime - hstart + hland.
return (-b - sqrt(b^2 - 4*a*c) ) / (2*a).
}
6. Активный участок снижения.
Требуемая тяга складывается из трёх компонент: в горизонтальном направлении определяется нужным горизонтальным ускорением; в вертикальном направлении нужно обеспечить снижение с постоянным ускорением, при учёте силы тяжести и центробежной силы; в боковом направлении нужно стараться держать горизонтальную компоненту скорости относительно поверхности всё время направленной на точку посадки.
Поскольку движение как по горизонтали, так и по вертикали равноускоренное, зависимости высоты и расстояния до цели от времени задаются в момент начала активного участка. На нужных значениях эти величины удерживаются ПИД-регуляторами.
function Descent {
parameter tgtcoord.
parameter stoptime, av.
local tstart to time:seconds.
local hland to tgtcoord:terrainheight.
local vh0 to ship:groundspeed.
local ah to vh0/stoptime.
local endvv to ship:verticalspeed + av*stoptime.
// высота перехода к вертикальному спуску
local vdstarth to altitude + stoptime * (ship:verticalspeed + av * stoptime / 2).
local hpid to pidloop(0.04, 0, 0.4).
local vpid to pidloop(0.04, 0, 0.4).
set hpid:setpoint to 0.
set vpid:setpoint to 0.
set hpid:minoutput to -ah/2.
set hpid:maxoutput to ah/2.
set vpid:minoutput to -body:mu/(body:radius^2*2).
until altitude < vdstarth {
local hdir to vxcl(up:vector,tgtcoord:position):normalized.
local dcenter to body:radius + altitude.
// ускорение свободного падения с поправкой на центробежную силу
local geff to (body:mu/dcenter - vxcl(up:vector,velocity:orbit):sqrmagnitude)/dcenter.
local maxa to ship:availablethrust / mass.
local tleft to tstart + stoptime - time:seconds.
// ожидаемые значения высоты и расстояния до цели
local expdh to expvh^2/(2*ah).
local expalt to vdstarth + endvv*(time:seconds - tstart - stoptime) + av/2*(stoptime - time:seconds + tstart)^2.
//реальные значения
local vh to ship:groundspeed.
local vv to ship:verticalspeed.
local hasl to altitude.
local dh to (body:radius + (hasl - hland)/3)*vang(up:vector,tgtcoord:position-body:position)*constant:degtorad.
set ahvec to -hdir*(ah + hpid:update(time:seconds,dh - expdh)).
set avvec to up:vector*(av + geff + vpid:update(time:seconds,hasl - expalt)).
// боковая скорость и настройка компенсации
local latvel to vxcl(hdir,vxcl(up:vector,velocity:surface)).
local alatvec to -latvel*5/max(tleft,1).
print "Hacc: " + round(ahvec:mag,2) + " Vacc: " + round(avvec:mag,2) + " Lacc: " + round(alatvec:mag,2) + " " at (0,13).
set avec to ahvec + avvec + alatvec.
lock steering to lookdirup(avec,up:vector).
lock throttle to avec:mag/maxa.
if avec:mag/maxa > 1.005 { print " WARNING: not enough TWR" at (25,33). }
wait 0.
if dh < 25 break. // за 25 м до точки посадки переходим к следующему этапу
}
}
7. Терминальный участок - вертикальное снижение.
Для простоты, на этом участке будет поддерживаться такое ускорение, чтобы при касании поверхности точно погасить скорость. Формула для расчёта аналогична формуле из п. 4.
function VertDescent {
parameter shipheight. // сюда нужно передать высоту kOS модуля над поверхностью, когда корабль посажен
parameter endspeed to 1.5. // желаемая скорость касания
unlock steering.
wait 0.
local av to ship:verticalspeed^2 * 0.5 / max(alt:radar - shipheight, 0.1).
// выполняем цикл до мягкой посадки
until status = "Landed" {
local vh to vxcl(up:vector, velocity:surface).
set vh to vh / max(1, vh:mag).
lock steering to up:vector - 0.2*vh.
if verticalspeed < -abs(endspeed) {
set av to ship:verticalspeed^2 * 0.5 / max(alt:radar - shipheight, 0.1).
}
else set av to -0.1.
local dcenter to body:position:mag.
local geff to (body:mu/dcenter - vxcl(up:vector,velocity:orbit):sqrmagnitude)/dcenter.
lock throttle to mass * (av + geff) / (availablethrust * vdot(facing:vector, up:vector)).
wait 0.
}
lock throttle to 0.
}
Управляющий блок
После того, как определены все функции, реализующие посадку, объединяем их в общую программу:
function nextmode {
parameter newmode is mode+1.
set mode to newmode.
log "set mode to " + newmode + "." to "mode.ks".
}
function landing {
parameter landsite.
parameter ahmax, shipheight, Isp.
local stoptime to 0.
if mode = 1 {
waitorient(landsite, 5).
nextmode().
}
if mode = 2 {
until ship:availablethrust > 0 stage.
rotateorbit(landsite).
nextmode().
}
if mode = 3 {
set stoptime to waitdownrange(landsite, ahmax).
nextmode().
}
if mode = 4 {
local av to getvertacc(landsite, stoptime, Isp).
gear on.
Descent(landsite, stoptime, av).
nextmode().
}
if mode = 5 {
VertDescent(shipheight).
lock steering to "kill".
wait 5.
nextmode()
}
set ship:control:pilotmainthrottle to 0.
unlock throttle.
unlock steering.
}
local mode to 0.
if exists("mode.ks") runpath("mode.ks").
if mode = 0 nextmode().
landing( latlng(0, 270), 3.0, 3.0, 315 ).
В итоге получается довольно универсальный алгоритм, единственная требуемая настройка к которому - это ввести требуемое горизонтальное ускорение и удельный импульс посадочного двигателя.
Демонстрация работы на примере посадки возле арки с орбиты наклонением 45 градусов:
[media=//www.youtube.com/watch?v=Zzj1nlqAIuc]
landing.ks - описанный в статье алгоритм посадки
landing1.ks - алгоритм, оптимизированный под аппараты без гиродинов. Отличается более плавным переходом от торможения к вертикальному спуску.
ships.zip