Lekce 04 - Dynamická kamera
Předmluva
Vážení čtenáři, mějte prosím na paměti že já sám se dané téma teprve učím a moje schopnosti jsou omezené. U tohoto článku (včetně funkčního kódu) jsem proseděl několik nocí a přesto má k dokonalosti daleko. Pokud si někdo z vás troufne toto téma upřesnit nebo rozvinout, budu mu vděčný za jakoukoliv informaci.
Dynamický pohyb
V minulém díle jsem vám popsal funkce pro změnu pozice a rotaci. Jejich použití bylo ovšem statické (pokud to lze tak nazvat), to znamená že jsme dopředu věděly následující pozici kamery. Dnes budeme novou pozici počítat za běhu programu s ohledem na chování uživatele. Potřebujeme k tomu dát uživateli možnost řídit kameru dynamicky. Pro změnu pozice kamery jsem zvolil kursorové klávesy a rotace se ovládá myší. Jediné co tedy musíme udělat je vytvořit vzorec pro výpočet pozice na základě reakce uživatele. Ještě musím upozornit na skutečnost že, ačkoliv se hovoří o pozici kamery tak ve skutečnosti je kamera stále na stejném místě a pohybuje se scéna. Pokud se například chceme podívat vlevo, musíme otočit scénu vpravo atd.. . Běžne se ale používá termín "pozice kamery" nikoliv scény.
A další záludnost pokud jde o kód. Nelze totiž psát kód postupně a odlaďovat každou jeho část zvlášť. Veškeré hodnoty při posunu a rotaci se totiž vzájemně ovlivňují, Pokud se vám to zdá jednoduché tak si představte třeba takový letecký simulátor. Rotace ve všech osách plus změna pozice ve všech osách a pokud nejste matematický guru tak máte co dělat aby jste se z toho ne... :)
Pro začátek budeme vše řešit pomocí funkcí glTranslatef a glrotatef . Další možnost je potom vlastní výpočet transformační matice, těm se nevyhnete pokud potřebujete počítat kolize. To je ale na mne zatím moc. Pokud chcete vědět něco o maticích přečtěte si např. http://farao.czweb.org/cl_gl_matice2.htm
Teď už se podíváme jak je to v praxi. Použil jsem kód z první lekce. Vzhledem k tomu že nepoužáve skutečný fullscreen tak jsem něco málo vymazal ať je to přehlednejší.
Praxe
Přestože používáme maximalizované okno upravte volání funkce "CreateWindow" v proceduře "Main" na rozměry vašeho rozlišení aby snímání pozice myši pracovalo správně.
Sub Main()
Set Engine = New clsEngine
If Not (Engine.CreateWindow(frmMain, 1600, 1200, 32)) Then Exit Sub
..................
Funkci pro kresleni úplně předěláme.
Sub DrawScene()
Dim j As Integer
glClear GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT 'Vymaže obrazovku a hloubkový buffer
Parametrem funkce CameraMove je konstanta klávesy pro posun, pokud zadáme nulu bude se počítat rotace na základě pozice myši. Volání pro posun je deklarováno v modulu formuláře. Viz. Zdrojové kódy.
Engine.CameraMove 0 'vypocet pozice kamery dle pozice mysi
glLoadIdentity 'Reset matice
glRotatef Engine.CamrotX, 1, 0, 0 'rotace hodnota, x, y, z
glRotatef Engine.CamrotY, 0, 1, 0 'rotace hodnota, x, y, z
glRotatef Engine.CamrotZ, 0, 0, 1 'rotace hodnota, x, y, z
glTranslatef Engine.CamposX, Engine.CamposY, Engine.CamposZ 'posun X, Y, Z
'vykresleni orientacni mrizky
For j = -100 To 100 Step 5
glBegin (GL_LINES)
glColor3ub 24, 18, 252
glVertex3f -100, -10, j
glVertex3f 100, -10, j
glVertex3f j, -10, -100
glVertex3f j, -10, 100
glEnd
Next
End Sub
Teď se přesunu k tomu co nás zajímá a to je samotná kamera. Pokud jde o rotaci tak tady nebudete mít problém. V podstatě stačí zjistit pozici myši v okně na osách X, Y a z ní následně určit nějaký úhel. Můžete to počítat procentuelně s ohledem na roměry okna, nebo tabulkově s určením velikosti kroku nebo ještě jinak. To už je na vás.
Pokud jde o posun tak to už je těžší. Pamatujete si co jsem psal v předchozí lekci o vlivu rotace na posun?
V případě použití rotace musíme počítat posun s ohledem na aktuální úhel natočených os.
Výpočet ovšem nelze provádět lineárně, to bychom se pohybovaly ve čtverci a to je špatně. Abychom dosáhly správného posunu, musíme se pohybovat po kružnici (2D) nebo po kouli (3D). Tady se dostávají ke slovu goniometrické funkce.
Nezapomeňte že funkce Sin a Cos pracují v radiánech, ale OGL ve stupních. Musíme tedy ještě převést radiány na stupně tím že úhel vynásobíme hodnotou PI/180.
Nejdříve definice proměnných v modulu třídy.
'zde se budou ukladat jiz spocitane hodnoty, ktere pouzijeme v procedure DrawScene
Public CamposX As Single
Public CamposY As Single
Public CamposZ As Single
Public CamrotY As Single
Public CamrotX As Single
Public CamrotZ As Single
'pro snazsi orientaci jsem vytvoril strukturu jenz nam uchovava zpracovavana data
Private Cam As m_Camera
'pozice mysi (predpokladam ze vite o co jde)
Private Type POINTAPI
x As Long
y As Long
End Type
'definice struktury
Private Type m_Camera
posx As Single
posy As Single
posz As Single
roty As Single
rotx As Single
rotz As Single
End Type
'deklarace WinApi pro praci s mysi
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
'procedura kamery
Public Sub CameraMove(ByVal Keycode As Integer)
Dim mouse As POINTAPI 'ulozeni pozice mysi
Dim steepRotX As Single 'krok rotace na ose X
Dim steepPosX As Single, steepPosY As Single 'krok posunu X, Y
Static lastX As Integer 'pomocna promenna pro urceni smeru
steepRotX = 8
steepPosX = 2.6
steepPosY = 3
Select Case Keycode
Case 0 'filtrujeme pozici mysi
GetCursorPos mouse
If mouse.x > lastX Then 'otoceni doprava
'urceni rozsahu snimani pozice mysi (pomocne)
If mouse.x < Window.Width - 10 Then
Cam.roty = IIf(Abs(Cam.roty) = 359, 0, (Cam.roty - steepRotX))
Else
'pokud dosahneme praveho okraje okna, presuneme se zpet do jeho stredu
SetCursorPos Window.Width / 2, mouse.y
Cam.roty = IIf(Abs(Cam.roty) = 359, 0, (Cam.roty - steepRotX))
End If
ElseIf mouse.x < lastX Then 'otoceni doleva
'urceni rozsahu snimani pozice mysi (pomocne)
If mouse.x > 10 Then
Cam.roty = IIf(Abs(Cam.roty) = 359, 0, (Cam.roty + steepRotX))
Else
'pokud dosahneme praveho okraje okna, presuneme se zpet do jeho stredu
SetCursorPos Window.Width / 2, mouse.y
Cam.roty = IIf(Abs(Cam.roty) = 359, 0, (Cam.roty + steepRotX))
End If
End If
'pohled nahoru a dolu bez pouziti velikosti kroku
' a omezeni pretoceni (zjednoduseno)
Cam.rotx = mouse.y - ((Window.Width / 180))
lastX = mouse.x
'filtrujeme klavesy
Case vbKeyUp 'krok vpred
Cam.posx = Cam.posx - (Sin((Cam.roty) * (3.14 / 180)) * steepPosY)
Cam.posz = Cam.posz - (Cos((Cam.roty) * (3.14 / 180)) * steepPosY)
Case vbKeyDown 'krok vzad
Cam.posx = Cam.posx + (Sin((Cam.roty) * (3.14 / 180)) * steepPosY)
Cam.posz = Cam.posz + (Cos((Cam.roty) * (3.14 / 180)) * steepPosY)
Case vbKeyRight 'ukrok vpravo
Cam.posx = (Cam.posx - (Sin((Cam.roty - 90) * (3.14 / 180))) * steepPosX)
Cam.posz = (Cam.posz - (Cos((Cam.roty - 90) * (3.14 / 180))) * steepPosX)
Case vbKeyLeft 'ukrok vlevo
Cam.posx = (Cam.posx - (Sin((Cam.roty + 90) * (3.14 / 180))) * steepPosX)
Cam.posz = (Cam.posz - (Cos((Cam.roty + 90) * (3.14 / 180))) * steepPosX)
End Select
'ulozeni vypoctu do pomocnych promennych
CamposX = -Cam.posx
CamposY = Cam.posy
CamposZ = -Cam.posz
CamrotY = 360 - Cam.roty
CamrotX = Cam.rotx
End Sub
A to je vše. Pravda není to žádný zázrak, ale pro ukázku to stačí. Pro ostré použití je nutno trochu optimalizovat (zejména opakovaný převod stupňů na radiány přímo bije do očí) nebo možná úplně předělat. Nemám bohužel možnost nahlédnout do nějaké profi. kamery takže zejména rotace dle pozice myši je poněkud zmatená. Víc ze sebe, ale momentálně nedostanu.
Zdrojové kódy ke stažení zde.
Přístě už to bude zajímavější. Naučíme jak vytvářet textury ze souboru BMP a nanášet je na objekty.
|