Делать некоторые основные исчисления с использованием Reactive Banana

Настройка:

Я использую Reactive Banana вместе с OpenGL, и у меня есть механизм, который я хочу крутить. У меня есть следующие сигналы:

bTime :: Behavior t Int -- the time in ms from start of rendering
bAngularVelosity :: Behavior t Double -- the angular velocity
                                      -- which can be increase or
                                      -- decreased by the user
eDisplay :: Event t ()     -- need to redraw the screen
eKey :: Event t KeyState   -- user input

В конечном итоге мне нужно рассчитать bAngle который затем передается функции рисования:

reactimate $ (draw gears) <$> (bAngle <@ eDisp)

Угол легко рассчитать: a = ∫v(t) dt

Вопрос:

Я думаю, что я хочу сделать, это приблизить этот интеграл как a = ∑ v Δt для каждого события eDisplay (или чаще, если нужно). Это правильный путь? Если так, как я могу получить Δt от bTime?

Смотрите также: я подозреваю, что ответ использует mapAccum функция. Если так, пожалуйста, также посмотрите мой другой вопрос.

2 ответа

Решение

Редактировать: чтобы ответить на вопрос, да, вы правы в использовании используемого вами приближения. Это метод Эйлера для решения дифференциального уравнения первого порядка, и он достаточно точен для ваших целей, особенно если у пользователя нет абсолютное значение для угловой скорости, лежащей вокруг, чтобы судить вас. Уменьшение временного интервала сделает его более точным, но это не важно.

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

Зачем беспокоиться об этом более длительном решении? Это работает даже когда eDisplay происходит с нерегулярными интервалами, потому что он рассчитывает eDeltaT,

Давайте дадим себе временное событие:

eTime :: Event t Int
eTime = bTime <@ eDisplay

Чтобы получить DeltaT, нам нужно отслеживать прохождение временного интервала:

type TimeInterval = (Int,Int) -- (previous time, current time)

поэтому мы можем преобразовать их в дельты:

delta :: TimeInterval -> Int
delta (t0,t1) = t1 - t0

Как мы должны обновить интервал времени, когда мы получаем новый t2?

tick :: Int -> TimeInterval -> TimeInterval
tick t2 (t0,t1) = (t1,t2)

Итак, давайте частично применим это ко времени, чтобы дать нам интервал обновления:

eTicker :: Event t (TimeInterval->TimeInterval)
eTicker = tick <$> eTime

и тогда мы сможем accumE- накопить эту функцию на начальном интервале времени:

eTimeInterval :: Event t TimeInterval
eTimeInterval = accumE (0,0) eTicker

Поскольку eTime измеряется с начала рендеринга, начальная (0,0) является целесообразным.

Наконец, мы можем получить событие DeltaT, просто применив (fmapпинг) delta на интервале времени.

eDeltaT :: Event t Int
eDeltaT = delta <$> eTimeInterval

Теперь нам нужно обновить угол, используя похожие идеи.

Я сделаю обновление угла, просто повернув bAngularVelocity в множитель:

bAngleMultiplier :: Behaviour t (Double->Double)
bAngleMultiplier = (*) <$> bAngularVelocity

тогда мы можем использовать это, чтобы сделать eDeltaAngle: (изменить: изменено на (+) и преобразован в Double)

eDeltaAngle :: Event t (Double -> Double)
eDeltaAngle = (+) <$> (bAngleMultiplier <@> ((fromInteger.toInteger) <$> eDeltaT)

и накопить, чтобы получить угол:

eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle

Если вам нравятся однострочники, вы можете написать

eDeltaT = delta <$> (accumE (0,0) $ tick <$> (bTime <@ eDisplay)) where
    delta (t0,t1) = t1 - t0
    tick t2 (t0,t1) = (t1,t2)

eAngle = accumE 0.0 $ (+) <$> ((*) <$> bAngularVelocity <@> eDeltaT) = 

но я не думаю, что это ужасно освещает, и, честно говоря, я не уверен, что я правильно понял, так как я не проверял это в ghci.

Конечно, так как я сделал eAngle вместо bAngle, тебе нужно

reactimate $ (draw gears) <$> eAngle

вместо вашего оригинала

reactimate $ (draw gears) <$> (bAngle <@ eDisp)

Простой подход состоит в том, чтобы предположить, что eDisplay происходит через регулярные промежутки времени, и рассмотрим bAngularVelocity быть относительной, а не абсолютной мерой, что даст вам действительно довольно короткое решение ниже. [Обратите внимание, что это не хорошо, если eDisplay находится вне вашего контроля, или если он стреляет заметно нерегулярно, или меняется регулярно, потому что это заставит ваше снаряжение вращаться с различными скоростями, как интервал вашего eDisplay изменения. Тебе нужен мой другой (более длинный) подход, если это так.]

eDeltaAngle :: Event t (Double -> Double)
eDeltaAngle = (+) <$> bAngularVelocity <@ eDisplay

то есть повернуть bAngularVelocity в сумматор Событие, которое срабатывает, когда вы eDisplayитак

eAngle :: Event t Double
eAngle = accumE 0.0 eDeltaAngle

и наконец

reactimate $ (draw gears) <$> eAngle

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

Другие вопросы по тегам