2012-09-08 3 views
6

konfiguracji:Doing podstawowe rachunku przy użyciu Reactive Banana

używam reaktywny Banana wraz z OpenGL i mam sprzęt, który chcę kręcić. Mam następujące sygnały:

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 

Ostatecznie, muszę obliczyć bAngle który następnie przeszły do ​​funkcji rysunek:

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

Kąt łatwo obliczyć: a = ∫v(t) dt

Pytanie:

I myśl co chcę zrobić, to przybliżyć tę całkę jako a = ∑ v Δt dla każdego zdarzenia eDisplay (lub częściej jeśli potrzebuję). Czy to jest właściwy sposób, aby o tym porozmawiać? Jeśli tak, jak mogę uzyskać Δt od bTime?

Zobacz także: Podejrzewam, że odpowiedź wykorzystuje funkcję mapAccum. Jeśli tak, zobacz także my other question.

+0

Jeśli chcesz, możesz ominąć rachunek różniczkowy, pozwalając użytkownikowi oszacować czas trwania bTime w taki sam sposób jak asteroids przykład: – AndrewC

+0

@AndrewC, myślę [to] (https://github.com/HeinrichApfelmus/reactive-banana/blob/master/reactive-banana -wx/src/Asteroids.hs) jest linkiem, którego szukasz? – huon

+0

Tak, . Wady: szarpanie przy małych prędkościach, przeciążanie silnika graficznego przy dużych prędkościach, brzydota. Odbieram moją sugestię: użycie prędkości kątowej jest o wiele bardziej elegancki – AndrewC

Odpowiedz

6

Edycja: aby odpowiedzieć na pytanie, tak, masz rację, używając przybliżenia, którego używasz, jest to metoda Eulera rozwiązywania równania różniczkowego pierwszego rzędu i jest wystarczająco dokładna dla twoich celów, szczególnie, że użytkownik robi nie mają wartości bezwzględnej dla prędkości kątowej leżącej wokół, aby cię osądzić. Zmniejszenie odstępu czasu sprawiłoby, że byłby on dokładniejszy, ale to nieważne.

Możesz to zrobić na mniejszej, większej liczbie kroków (patrz niżej), ale ta metoda wydaje mi się najjaśniejsza, mam nadzieję, że to dla ciebie.

Po co zawracać sobie głowę tym dłuższym rozwiązaniem? Działa to nawet, gdy eDisplay dzieje się w nieregularnych odstępach czasu, ponieważ oblicza eDeltaT.

Dajmy sobie zdarzenie czasu:

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

Aby uzyskać DeltaT, musimy śledzić przedziale czasowym Podania:

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

więc możemy je konwertować do delt:

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

W jaki sposób powinniśmy zaktualizować przedział czasu, kiedy otrzymamy nowy t2?

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

Więc częściowo zastosować że do czasu, aby dać nam przedział updater:

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

i wtedy możemy accumE -accumulate że działają na początkowym przedziale czasowym:

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

Ponieważ czas trwania eTime jest mierzony od początku renderowania, odpowiedni jest początkowy (0,0).

Wreszcie możemy mieć nasze zdarzenie DeltaT, poprzez zastosowanie (fmap ping) delta w przedziale czasu.

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

Teraz musimy zaktualizować kąt, używając podobnych pomysłów.

Zrobię updater kąt, po prostu obracając bAngularVelocity do mnożnika:

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

wtedy możemy używać, aby dokonać eDeltaAngle: (edit: zmieniono (+) i konwertowane do Double)

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

i gromadzą się, aby uzyskać kąt:

eAngle :: Event t Double 
eAngle = accumE 0.0 eDeltaAngle 

Jeśli lubisz jednej wkładki, można napisać

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) = 

ale nie sądzę, że jest strasznie pouczające, i szczerze mówiąc, nie jestem pewien, mam moje fixities prawo odkąd nie testowałem tego w ghci.

Oczywiście, ponieważ zrobiłem eAngle zamiast bAngle, trzeba

reactimate $ (draw gears) <$> eAngle 

zamiast oryginalnego

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

Zobacz także moją odpowiedź na pytanie [mapAccum] (http://stackoverflow.com/questions/12327424/how-does-reactive-bananas-mapaccum-function-work/12332814#12332814) dla ładniejszego rozwiązania 'mapAccum' do zrobienia 'eDeltaT'. – AndrewC

3

proste podejście jest założenie, że eDisplay dzieje się w regularnych odstępach czasu, i uważam, że bAngularVelocity jest względną, a nie bezwzględną miarą, co dałoby ci naprawdę dość krótkie rozwiązanie poniżej. [Zauważ, że nie jest to dobre, jeśli eDisplay jest poza twoją kontrolą, lub jeśli płomień jest wyraźnie nieregularny lub zmienia się w regularności, ponieważ spowoduje to obrót twojego sprzętu z różnymi prędkościami w miarę zmiany odstępu czasu pomiędzy Twoimi eDisplay. W takim przypadku potrzebowałbyś mojego (dłuższego) podejścia.]

tj.przekręcić bAngularVelocity do sumatora zdarzenie, które pożary podczas eDisplay, więc wtedy

eAngle :: Event t Double 
eAngle = accumE 0.0 eDeltaAngle 

i wreszcie

reactimate $ (draw gears) <$> eAngle 

Tak, zbliżenie całka jako suma jest odpowiednia, a tu mam dalsze zbliżanie przez co może być nieco niedokładne, jeśli chodzi o szerokość kroku, ale jest jasne i powinno być płynne, o ile twój eDisplay jest mniej lub bardziej regularny.