Делать некоторые основные исчисления с использованием 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
более-менее регулярно.