Плавное движение, чтобы подняться через атмосферу - PullRequest
1 голос
/ 01 апреля 2009

Я перемещаюсь по атмосфере с помощью Microsoft Virtual Earth 3D и могу плавно спускаться, но я не знаю, как правильно подняться.

Я спускаюсь вот так:

for(int curAlt = startAlt; curAlt < endAlt; curAlt--){
    //do something
    curAlt -= curAlt/150
}

Это работает, уменьшая размер прыжка по мере приближения к земле (меньшая высота). Мне нужно решение, которое сделало бы подобное, только наоборот, при этом сохраняя меньшие прыжки на меньшей высоте.

Как я могу это сделать? Или то, что я делаю, неприемлемо и должно быть сделано иначе (скажем, с логарифмами)?

Ответы [ 4 ]

4 голосов
/ 01 апреля 2009

Еще лучшим решением может быть использование такой функции, как Логистическая функция .

Double minAlt = 0.0;
Double maxAlt = 500000.0;

Int32 numberSteps = 1000;

Double boundary = +6.0;

for (Int32 step = 0; step < numberSteps; step++)
{
   Double t = -boundary + 2.0 * boundary * step / (numberSteps - 1);
   Double correction = 1.0 / (1.0 + Math.Exp(Math.Abs(boundary)));
   Double value = 1.0 / (1.0 + Math.Exp(-t));
   Double correctedValue = (value - correction) / (1.0 - 2.0 * correction);
   Double curAlt = correctedValue * (maxAlt - minAlt) + minAlt;
}

Поскольку текущая высота вычисляется в явном виде, вам не нужно полагаться на итеративное вычисление, вводящее все виды ошибок, связанных с точностью.

См. Пример кода для настройки формы функции.


Вот пример консольного приложения, отображающего функцию. Вы можете немного поиграть с параметрами, чтобы почувствовать поведение.

using System;

namespace LogisticFunction
{
    class Program
    {
        static void Main(string[] args)
        {
            Double minAlt = 5.0;
            Double maxAlt = 95.0;

            Int32 numberSteps = 60;

            // Keep maxAlt and numberSteps small if you don't want a giant console window.
            Console.SetWindowSize((Int32)maxAlt + 12, numberSteps + 1);

            // Positive values produce ascending functions.
            // Negative values produce descending functions.
            // Values with smaller magnitude produce more linear functions.
            // Values with larger magnitude produce more step like functions.
            // Zero causes an error.
            // Try for example +1.0, +6.0, +20.0 and -1.0, -6.0, -20.0
            Double boundary = +6.0;

            for (Int32 step = 0; step < numberSteps; step++)
            {
                Double t = -boundary + 2.0 * boundary * step / (numberSteps - 1);
                Double correction = 1.0 / (1.0 + Math.Exp(Math.Abs(boundary)));
                Double value = 1.0 / (1.0 + Math.Exp(-t));
                Double correctedValue = (value - correction) / (1.0 - 2.0 * correction);
                Double curAlt = correctedValue * (maxAlt - minAlt) + minAlt;

                Console.WriteLine(String.Format("{0, 10:N4} {1}", curAlt, new String('#', (Int32)Math.Round(curAlt))));
            }

            Console.ReadLine();
        }
    }
}
1 голос
/ 01 апреля 2009

Кстати, вы действительно должны сделать время восхождения зависимым (с учетом частоты кадров). Все ответы здесь зависят от кода, вызываемого через определенный интервал; что это не так. Если какой-то процесс запускается, Виртуальная Земля каким-то образом испытывает стресс, если вы минимизируете Виртуальную Землю или если происходит что-то, что влияет на производительность Виртуальной Земли, движение будет совсем не плавным. Даже если с Virtual Earth «ничего» не происходит, иногда ваша 3D-карта будет зависать, что означает, что потенциально вы будете время от времени совершать прыжки.

В частности, если у пользователя отключен VSync, вы получите несколько действительно неприятных вещей:

  • На медленной машине восхождение будет длиться вечно (даже при включенной VSync).
  • На быстрых машинах это будет так быстро, что вы даже не заметите этого.

В вашем классе:

private int lastTime;

В вашем цикле / событии:

if(lastTime == 0)
{
    lastTime = Environment.TickCount;
    return;
}

int curTime = Environment.TickCount; // store this baby.
int timeDiff = lastTime - curTime;

if(timeDiff == 0)
   return;

curAlt += (maxAlt - curAlt) * timeDiff / (150000); // TickCount reports
                                                   // time in Ticks
                                                   // (1000 ticks per second)

lastTime = curTime;

Если вы хотите стать модным, вы можете подключить код из DX SDK. Environment.TickCount имеет разрешение 15 мс (поэтому я проверяю, что timeDiff равен нулю, потому что это может быть легко). Пример структуры управляемого DX SDK имеет класс с именем DxTimer (или сортировки), который имеет лучшее разрешение.

Есть статья, в которой используется тот же API .

0 голосов
/ 02 апреля 2009

Поскольку вы используете curAlt - = curAlt / 150 для спуска, почему бы не использовать curAlt + = curAlt * 150 для поднятия?

0 голосов
/ 01 апреля 2009

Это зависит от того, чего вы хотите достичь, плавно поднимаясь. Вы можете ограничить высоту до некоторого максимального значения maxAlt и плавно подойти к этому значению так же, как вы делаете это с землей.

curAlt += (maxAlt - curAlt) / 150

Но если максимальная высота не ограничена, вы должны уточнить, что именно вы хотите сделать гладким.

Также обратите внимание, что ваш код работает только с некоторыми побочными эффектами. Вы близки к бесконечному циклу. Я хотел бы предложить следующее.

epsilon = 0.1; // A small value that fits your needs

curAlt = startAlt;

while (curAlt > endAlt + epsilon)
{
   curAlt -= (curAlt - endAlt) / 150;
}

Итерация curAlt - = (curAlt - endAlt) / 150 никогда не достигнет endAlt в теории и в реальности в большинстве случаев из-за ошибок при обработке Ваш код работает только потому, что вы вычитаете одну дополнительную единицу высоты за итерацию. Я не уверен, если это из-за ошибки или из-за ошибки. Добавление порога эпсилон прерывает цикл более логичным способом.

...