C/C++ & Visual C++

Kurz DirectX (33.)

┌vodem  |  DatovΘ struktury |  Kurz DirectX  |  Downloads  |  Otßzky a odpov∞di

V dneÜnφ lekci vylepÜφme kameru, p°idßme re₧im, kdy se kamera bude pohybovat po p°edem definovanΘ cest∞. Dßle p°idßme n∞kolik metod do t°φdy XDisplay starajφcφ se o mlhu.

33.1. Pohyb kamery

Do te∩ byl pohyb kamery zajiÜt∞n pouze pomocφ interaktivnφho ovlßdßnφ, kterΘ vy₧adovalo vstup z klßvesnice. V dneÜnφ lekci p°idßme do kamery neinteraktivnφ re₧im, kdy pohyb kamery bude °φzen automaticky. Abychom pohyb jednoznaΦn∞ urΦili, jsou t°eba t°i zßkladnφ informace: poloha kamery, sm∞r a Φas, kdy se v tΘto konfiguraci kamera nachßzφ.

Soubor cesty

V²Üe uvedeny informace budou ulo₧eny v souboru path.txt, kter² bude mφt nßsledujφcφ formßt:

Na ka₧dΘm °ßdku tedy bude jedna klφΦovß hodnoty polohy kamery. Mezi dv∞ma polohami se bude plynule interpolovat. Soubor budeme Φφst po °ßdcφch, jednotlivΘ °ßdky rozklßdat do jednotliv²ch polo₧ek pomocφ odd∞lovaΦe (zde st°ednφk).

Struktura PathSegment

Z tΘto struktury postavφme spojov² seznam vÜech segment∙ naΦten²ch ze souboru.

struct PathSegment
{
    float fTime;
    D3DXVECTOR3 vPos;
    D3DXVECTOR3 vDir;

    PathSegment *pNext;
    PathSegment *pPrevious;
};

Struktura uchovßvß vÜechny parametry kamery a navφc jeÜt∞ ukazatel na nßslednφka a p°edch∙dce ve spojovΘm seznamu. ╪et∞z bude vypadat nßsledovn∞:

Nynφ upravφme t°φdu XCamera. Za prvΘ p°idßme n∞kolik Φlensk²ch prom∞nn²ch:

// Cela path (1)
PathSegment *m_arPath;
// Current position of the camera (2)
PathSegment *m_curSeg;
// Aktualni kamera (3)
PathSegment m_curInterpolation;
// Stav prehravani kamery (4)
PATHSTATUS m_ePathStatus;
// Motion modifier (5)
float m_fSpeed;

// Rezim kamery (6)
CAMERAMODE m_cm;
// Informace o nahrani cesty (7)
BOOL m_bPathLoaded;

1) Ukazatel na prvnφ segment spojovΘho seznamu cesty
2) Aktußlnφ segment, mezi tφmto a nßsledujφcφm probφhß interpolace
3) Aktußlnφ konfigurace kamery (tedy interpolovanß hodnota)
4) Stav p°ehrßvßnφ - kamera lze v libovolnΘm mφst∞ pozastavit. Zde je pou₧it² typ PATHSTATUS:

enum PATHSTATUS
{
    PLAY, PAUSE, STOP
};

5) Modifikßtor rychlosti pohybu. Implicitn∞ je roven 1.0 (Φas v souboru path odpovφdß skuteΦnosti).
6) Re₧im kamery. Kamera m∙₧e b²t bu∩ v re₧imu PATH nebo INTERACTIVE. Typ CAMERAMODE je definovßn nßsledovn∞:

enum CAMERAMODE
{
    PATH, INTERACTIVE
};

7) Informace o tom, zda je cesta naΦtena ze souboru. Nejd°φve je pochopiteln∞ nutno soubor path naΦφst a teprve pak je mo₧no spustit vlastnφ pohyb.

Dßle p°idßme Üest ve°ejn²ch a jednu internφ metodu:

virtual HRESULT LoadPath(LPCSTR szPath); (1)
virtual HRESULT StartPath(); (2)
virtual HRESULT PausePath(); (3)
virtual HRESULT StopPath();// (4) reset
virtual HRESULT SetSpeed(float fRelativeSpeed) {m_fSpeed += fRelativeSpeed;return S_OK;} (5)
virtual HRESULT SetCameraMode(CAMERAMODE cm){m_cm = cm;return S_OK; } (6)

float ExtractNumber(int i, char *line); (7)

1) Metoda naΦte soubor path a vytvo°φ spojov² seznam popsan² v²Üe. Nakonec p°ipravφ kameru pro spuÜt∞nφ interpolace.
2) Spustφ pohyb kamery podle p°edem nahranΘ cesty.
3) Pozastavφ pohyb kamery. Kamera m∙₧e b²t znovu spuÜt∞na op∞tovn²m zavolßm metody StartPath().
4) Zastavφ pohyb kamery a nastavφ v²chozφ pozici.
5) Nastavφ rychlost pohybu. Nastavenφ je provßd∞no relativn∞ v∙Φi hodnot∞ 1.0.
6) Nastavφ re₧im kamery - bu∩ PATH nebo INTERACTIVE.
7) Poslednφ neve°ejnß metoda slou₧φ k rozklßdßnφ °ßdky ze souboru path.

Nynφ vÜechny tyto metody naimplementujeme. Nejslo₧it∞jÜφ metoda je LoadPath(), kterß vyu₧φvß metodu ExtractNumber(). ZaΦneme tedy od tΘto metody:

float XCamera::ExtractNumber(int i, char *line)
{   
    char number[20];
    char *pline = line;
    int c;

    for(int u = 0; u < i; u++)
    {
        // Find first ;
        c = strcspn(pline, ";");
        // Next number + ;
        if(c > 0)
        {
            pline += (c+1);
        }
    }

    // Find first ;
    c = strcspn(pline, ";");
    // Copy number
    strncpy(number, pline, c);
    // Ends number
    number[c] = '\0';
    // Convert number
    return (float)atof(number);
}

Mo₧nß nenφ na prvnφ pohled vid∞t, co tato metoda vlastn∞ d∞lß. P°ijφmß dva parametry: celΘ Φφslo jako po°adφ Φφsla, kterΘ chceme extrahovat a °et∞zec °ßdky ze souboru path. Z tohoto °et∞zce je vybrßno Φφslo podle parametru i, potΘ se p°evede na float, kter² je vrßcen. V prvnφ smyΦce tedy p°eskßΦeme vÜechny Φφsla p°ed po₧adovan²m. Funkce strspn() vracφ index v²skytu odd∞lovaΦe. Tuto hodnotu ve vstupnφm °et∞zci p°eskoΦφm a to opakujeme tak dlouho a₧ se dostaneme p°ed po₧adovanΘ Φφslo. V dalÜφ Φßsti se pou₧ije stejn² postup na zkopφrovßnφ Φßsti vstupu do pomocnΘ prom∞nnΘ number, kter² jeÜt∞ zakonΦφme znakem '\0' a nakonec p°evedeme funkci atof() na float.

Metoda LoadPath() naΦte ze souboru cestu a vytvo°φ spojov² seznam.

HRESULT XCamera::LoadPath(LPCSTR szPath)
{
    DWORD size;
    char line[256];
    char *pline = line;
    char c;    
    PathSegment *pLast = NULL;
    PathSegment ps;

    char szFullPath[MAX_PATH];
    // Build full path to path file
    cmnGetDataFilePath(szFullPath, szPath);
    // Load file contains
    cmnLoadFileFromPath(szFullPath, NULL, &size);
    char * file = new char[size+1];
    ZeroMemory(file, (size+1) * sizeof(char));
    cmnLoadFileFromPath(szFullPath, (LPBYTE)file, &size);

    // Release previous path if any
    if(m_arPath)
    {
        PathSegment *pNext = NULL;
        PathSegment *pToDelete = m_arPath;
        // Release path
        while(pToDelete)
        {
            pNext = pToDelete->pNext;
            SAFE_DELETE(pToDelete);
            pToDelete = pNext;
        }
    }
    char *pfile = file;
    m_arPath = NULL;

V prvnφ Φßsti naΦteme soubor pomocφ funkce cmnLoadFileFromPath(). Tato funkce je v knihovn∞ common.dll. Proto₧e nevφme velikost souboru, nejprve zavolßme funkci s hodnotou NULL. Takto nßm vrßtφ velikost bufferu, kter² musφme alokovat pro soubor. V druhΘm volßnφ ji₧ pou₧ijeme buffer file. Funkce cmnGetDataFilePath() je op∞t z knihovny common.dll a pouze spojφ cestu spuÜt∞nΘho programu a zadanΘho souboru, tφm vytvo°φ ·plnou cestu k souboru cesty. Na zßv∞r tΘto Φßsti vyma₧eme p°edchozφ cestu, pokud takovß existuje. pfile je ukazatel stejn∞ jako file, ale s pfile budeme pozd∞ji h²bat. file nesmφme zm∞nit, proto₧e ho nakonec budeme dealokovat.

while(pfile[0] != '\0')
{
    while(1)
    {
        c = pfile++[0];
        if(c == 0x0D)
        {
            pfile++; // jump over 0x0A
            break;
        }
        if(c == '\0')
        {
            pfile--; // return before '\0'
            break;
        }
        // Copy line char
        pline[0] = c;
        // Next char
        pline++;
    }
    // End of string
    pline[0] = '\0';
    // Reset pline
    pline = line;
    // Parse line
    ps.fTime = ExtractNumber(0, line);
    ps.vPos.x = ExtractNumber(1, line);
    ps.vPos.y = ExtractNumber(2, line);
    ps.vPos.z = ExtractNumber(3, line);
    ps.vDir.x = ExtractNumber(4, line);
    ps.vDir.y = ExtractNumber(5, line);
    ps.vDir.z = ExtractNumber(6, line);

    D3DXVec3Normalize(&ps.vDir, &ps.vDir);
    
    if(!m_arPath)
    {
        // Create root segment
        pLast = m_arPath = new PathSegment;
        *m_arPath = ps;
        m_arPath->pPrevious = NULL;
        m_arPath->pNext = NULL;
    }
    else
    {
        // Create new segment
        pLast->pNext = new PathSegment;
        *pLast->pNext = ps;
        pLast->pNext->pPrevious = pLast;
        pLast->pNext->pNext = NULL;
        pLast = pLast->pNext;
    }
     // Reset pline
    pline = line;
}

SAFE_DELETE_ARRAY(file);


V tΘto Φßsti Φteme °et∞zec souboru (od zaΦßtku do konce) a vybφrßme z n∞j jednotlivΘ °ßdky. Mezi °ßdky jsou znaky 0x0D a 0x0A, podle kter²ch poznßme kde °ßdek konΦφ. Vnit°nφ while() tedy Φte znaky tak dlouho, ne₧ narazφ na znak 0x0D (konec °ßdky) nebo na '\0' (konec "souboru"). V prvnφm p°φpad∞ jeÜt∞ ukazatel zv²Üφme, abychom p°eskoΦili znak 0x0A. Ve druhΘm naopak snφ₧φme, abychom nastavili znak '\0' jako prvnφ tak₧e se ukonΦφ vn∞jÜφ cyklus a dalÜφ °ßdka se neΦte. Zßrove≥ se ka₧d² znak °ßdky kopφruje do pomocnΘho pole pline. Op∞t je t°eba po p°eΦtenφ °ßdky °et∞zec °ßdn∞ ukonΦit. V dalÜφm kroku ji₧ m∙₧eme rozlo₧it °ßdku na jednotlivΘ polo₧ky pomocφ metody ExtractNumber(). Dßle normalizujeme vektor sm∞ru. Te∩ ji₧ mßme vÜechny pot°ebnΘ informace, abychom mohli vytvo°it nov² objekt PathSegment. Zde se problΘm rozÜt∞pφ na dva p°φpady. Pokud se jednß o prvnφ segment, je t°eba ulo₧it ukazatel do prom∞nnΘ m_arPath. P°edchozφ i nßsledujφcφ segment je NULL a do prom∞nnΘ pLast ulo₧φme poslednφ p°idan² segment - tedy m_arPath. Pokud se jednß o dalÜφ segment (ne prvnφ), p°ipojφme tento segment za poslednφ p°idan² v p°edchßzejφcφm kroku. P°edchozφ toho aktußlnφho segmentu bude minul² a nßsledujφcφ NULL. Nesmφme zapomenout op∞t ulo₧it poslednφ p°idan², kter²m se stane aktußlnφ segment. V obou p°φpadech kopφrujeme data z pomocnΘho objektu ps. Nakonec resetujeme prom∞nnou pline pro dalÜφ °ßdek.


// Path is ready
m_bPathLoaded = TRUE;
m_curInterpolation = *m_arPath;
m_curSeg = m_arPath;
m_vEyePt = m_curInterpolation.vPos;
m_vLookAtPoint = m_vEyePt + m_curInterpolation.vDir;

V poslednφ Φßsti p°ipravφme ostatnφ prom∞nnΘ pro spuÜt∞nφ pohybu. Za prvΘ °φkßme, ₧e cesta je naΦtena. Do prom∞nnΘ m_curInterpolation ulo₧φme poΦßteΦnφ segment stejn∞ jako do prom∞nnΘ m_curSeg. Nakonec nastavφme pozorovacφ a pozorovan² bod. K tomu pou₧ijeme objekt aktußlnφ polohy m_curInterpolation. m_curInterpolation.vPos je p°φmo pozorovacφ bod a pozorovan² dostaneme jednoduÜe tak, ₧e k tΘto hodnot∞ p°iΦteme sm∞r m_curInterpolation.vDir.

Nßsledujφcφ trojicφ metod se dß ovlßdat pohyb kamery. Ka₧dß z t∞chto metod se nejprve p°esv∞dΦφ, zda-li je naΦtena n∞jakß cesta. Metoda StartPath() provede dv∞ v∞ci. Nastavenφm p°φznaku m_ePathStatus na hodnotu PLAY zp∙sobφ, ₧e se zaΦne poΦφtat Φas interpolace, kter² je ulo₧en v prom∞nnΘ m_curInterpolation.fTime. Pomocφ tΘto hodnoty se interpoluje (k tomu se dostaneme za chvilku) a pokud se m∞nφ, kamera se pohybuje. Za druhΘ je t°eba kame°e °φci, aby p°eÜla z interaktivnφho re₧imu do re₧imu PATH. Toto se dß rovn∞₧ nastavit metodou SetCameraMode().
Metoda PausePath() pouze nastavφ p°φznak m_ePathStatus na hodnotu PAUSE. To zajistφ, ₧e jakoby zastavφ Φas.
Poslednφ metoda StopPath() funguje podobn∞ jako PausePath() s tφm rozdφlem, ₧e navφc nastavφ aktußlnφ segment na prvnφ segment. ╚ili po op∞tovnΘm spuÜt∞nφ pohybu, zaΦne sekvence od zaΦßtku.

Aby se pohyb kamery projevil po vizußlnφ strßnce, je t°eba jeÜt∞ trochu upravit metodu ProcessCamera(). Ta se rozd∞lφ na dva p°φpade: interaktivnφ a automatick² s cestou. Interaktivnφ re₧im z∙stane stejn² jako doposud. Zajφmav∞jÜφ bude druh² zmφn∞n² zp∙sob:

// Path mode
if(m_cm == PATH)
{
    if(m_ePathStatus == PLAY)
    {
        fElapsedTime *= m_fSpeed;
        fDif = m_curSeg->pNext->fTime - m_curSeg->fTime;

        s = (m_curInterpolation.fTime - m_curSeg->fTime) / fDif;
        if(s > 1.0)
        {
            // Next segment
            if(m_curSeg->pNext->pNext)
            {
                m_curSeg = m_curSeg->pNext;
            }
            else
            {
                // First segment
                m_curSeg = m_arPath;
                m_curInterpolation.fTime = 0.0f;
            }
            // Recompute factor
            fDif = m_curSeg->pNext->fTime - m_curSeg->fTime;
            s = (m_curInterpolation.fTime - m_curSeg->fTime) / fDif;
        }
        if(s < 0.0f)
        {
            // Previous segment
            if(m_curSeg->pPrevious)
            {
                m_curSeg = m_curSeg->pPrevious;
            }
            else
            {
                // First segment
                m_curSeg = m_arPath;
                m_curInterpolation.fTime = 0.0f;
                m_fSpeed = 1.0;
            }
            // Recompute factor
            fDif = m_curSeg->pNext->fTime - m_curSeg->fTime;
            s = (m_curInterpolation.fTime - m_curSeg->fTime) / fDif;
        }

        D3DXVec3Lerp(&m_curInterpolation.vPos, &m_curSeg->vPos, &m_curSeg->pNext->vPos, s);
        D3DXVec3Lerp(&m_curInterpolation.vDir, &m_curSeg->vDir, &m_curSeg->pNext->vDir, s);
        m_curInterpolation.fTime += fElapsedTime;
    }
    m_vEyePt = m_curInterpolation.vPos;
    m_vLookAtPoint = m_vEyePt + m_curInterpolation.vDir;
}

Pokud je nastaven p°φsluÜn² re₧im pomocφ metody SetCameraMode() (Φi metodou StartPath()), je pozorovan² a pozorovacφ bod poΦφtßn zcela jinak. Interpolace probφhß pouze pokud je sekvence spuÜt∞na (prvnφ podmφnka). V tomto p°φpad∞ se vynßsobφ Φas modifikßtorem zrychlenφ, spoΦφtß se Φasov² rozdφl mezi aktußlnφm a nßsledujφcφm segmentem a urΦφ se faktor interpolace. Ten je 0.0 pokud je kamera na zaΦßtku aktußlnφho segmentu nebo 1.0 pokud se kamera nachßzφ na zaΦßtku nßsledujφcφho segmentu. V p°φpad∞, ₧e faktor je v∞tÜφ ne₧ 1.0, znamenß to, ₧e jsme ji₧ v dalÜφm segmentu a je t°eba p°epnout aktußlnφ segment, tedy z nßsledujφcφho se stane aktußlnφ. V opaΦnΘm p°φpad∞, tedy kdy₧ je faktor zßporn², znamenß to, ₧e jsme se dostali do p°edchozφho segmentu, zde je t°eba pou₧φt ukazatel na p°edchßzejφcφ segment (kamera se pohybuje v opaΦnΘm sm∞ru). Pokud ukazatele pNext nebo pPrevious ukazujφ na NULL, znamenß to, ₧e jsme na konci sekvence a je t°eba se vrßtit na zaΦßtek. Po p°epnutφ aktußlnφho segmentu je t°eba znovu spoΦφtat faktor.
V dalÜφm kroku provedeme samotnou interpolacφ pomocφ metody D3DXVec3Lerp(), kterß p°ijφmß Φty°i parametry: prvnφ je v²stupnφ vektor (interpolovan²), nßsleduje dvojice vektor∙, mezi kter²mi se interpoluje a nakonec faktor.

Na zßv∞r nastavφme pozorovacφ a pozorovan² bod pro matici pohledu. VÜimn∞te si, ₧e tyto °ßdky jsou stejnΘ jako v metod∞ LoadPath().

33.2. Podpora mlhy ve t°φd∞ XDisplay

Minule jsme si °φkali n∞co mßlo o mlze, ale nastavovali jsme ji p°φmo ve t°φd∞ XTerrain dost neohraban²m zp∙sobem. Proto jsem p°idal t°i novΘ funkce do t°φdy XDisplay.

EnableFog(BOOL bEnable) - vypne nebo zapne mlhu
SetPixelFog(UINT uFogType, float fDensity, float fStart, float fEnd, D3DCOLOR Color) - nastavφ parametry pixelovΘ mlhy
SetVertexFog(UINT uFogType, float fDensity, float fStart, float fEnd, D3DCOLOR Color) - nastavφ parametry vertexovΘ mlhy

Parametr uFogType m∙₧e nab²vat hodnot: D3DFOG_LINEAR, D3DFOG_EXP nebo D3DFOG_EXP2. U lineßrnφ mlhy je t°eba nastavit parametry fStart a fEnd. U exponencißlnφ musφ b²t nastaven parametr fDensity. Parametry Color urΦuje barvu mlhy.

33.3. Zßv∞r

A je tu op∞t konec! P°φklad si samoz°ejm∞ m∙₧ete stßhnout v sekci Download vΦetn∞ zdrojov²ch k≤d∙. Abyste vid∞li v²sledek dneÜnφ prßce, stiskn∞te klßvesu C, kterou spustφte pohyb kamery podle cesty zadanΘ v souboru path.txt, kter² si samoz°ejm∞ m∙₧ete upravit dle libosti.

A jak² problΘm budeme °eÜit p°φÜt∞? P°φÜt∞ bych Vßm cht∞l ukßzat dalÜφ optimalizaΦnφ techniku LOD (Level Of Detail), kterß pracuje na principu sni₧ovßnφ slo₧itosti terΘnu.

T∞Üφm se p°φÜt∞ nashledanou.

 

Ond°ej BuriÜin a Ji°φ Formßnek