用DirectX實現魔方(二)

這篇說一下如何構造魔方,主要包括魔方几何體的構造及紋理貼圖。如下論述皆以三階魔方爲例,三階魔方共有3 x 3 x 3 = 27個小立方體。數組

構造魔方

在第一篇裏面說過,最初模型用的是微軟的.x文件格式,因爲魔方要實現按層旋轉,因此不能將整個模型作成一個.x文件,只能分紅若干個小立方體,每一個立方體對應一個.x文件。這致使在發佈程序的時候也要發佈這些模型文件,並且.x文件已經逐漸爲微軟遺棄,因此就乾脆不用了,本身畫吧。魔方由27個小立方體構成,因此只要繪製一個小立方體,並複製27分,再將這個27個小立方體按必定順序堆疊在一塊兒,最後貼上紋理,就能夠構成一個完整的魔方了。數據結構

一個小立方體包含六個面,因爲每一個面的紋理可能不一樣,因此須要逐個面繪製,這樣能夠方便的爲每一個面單獨設置紋理。ide

一個面由兩個三角形構成,這裏採用TriangleStrip的方式進行繪製,只須要指定四個頂點便可,若是是TriangleList,則須要六個頂點。函數

頂點結構

下面來分析一下頂點的數據結構,首先要有一個位置座標(位置是一個頂點必需要包含的信息),其次,爲了添加光照效果,還須要一個法向量。最後,爲了實現紋理貼圖,須要有紋理座標。因此一個完整的頂點有如下三部分構成:佈局

  • 位置
  • 法向量
  • 紋理座標

用一個結構體來表示頂點,以下:ui

struct Vertex
{
    float  x,  y,  z; // position
    float nx, ny, nz; // normal
    float  u,  v;     // texture
};

定義頂點數組

對於任意一個立方體,它的邊長是固定的,因此只要給定立方體8個頂點中任意一個,就能夠推算出其餘的頂點座標,這裏使用立方體的左下角頂點來計算其餘頂點。假設左下角頂點座標爲P(x,y,z),正方形邊長爲length,那麼有以下關係成立。spa

頂點數組按面定義,順序以下:3d

  • Front face
  • Back  face
  • Left   face
  • Right face
  • Top   face
  • Bottom face

在定義任意一個面的四個頂點時,從左下角點開始按順時針方向至右下角點結束,以下:code

代碼以下,解釋一下第一行,其餘行相似。component

  • x,y,z是位置座標
  • 0.0f, 0.0f, -1.0f是法向量,法向量垂直於該面指向外。
  • 0.0f, 0.0f是紋理座標
// Vertex buffer data
Vertex vertices[] =
{
    // Front face
    {          x,           y,           z,  0.0f,  0.0f, -1.0f, 0.0f, 0.0f}, // 0
    {          x, y + length_,           z,  0.0f,  0.0f, -1.0f, 1.0f, 0.0f}, // 1
    {x + length_, y + length_,           z,  0.0f,  0.0f, -1.0f, 1.0f, 1.0f}, // 2
    {x + length_,           y,           z,  0.0f,  0.0f, -1.0f, 0.0f, 1.0f}, // 3

    // Back face
    {x + length_,           y, z + length_,  0.0f,  0.0f,  1.0f, 0.0f, 0.0f}, // 4
    {x + length_, y + length_, z + length_,  0.0f,  0.0f,  1.0f, 1.0f, 0.0f}, // 5
    {          x, y + length_, z + length_,  0.0f,  0.0f,  1.0f, 1.0f, 1.0f}, // 6
    {          x,           y, z + length_,  0.0f,  0.0f,  1.0f, 0.0f, 1.0f}, // 7

    // Left face
    {          x,           y, z + length_, -1.0f,  0.0f,  0.0f, 0.0f, 0.0f}, // 8
    {          x, y + length_, z + length_, -1.0f,  0.0f,  0.0f, 1.0f, 0.0f}, // 9
    {          x, y + length_,           z, -1.0f,  0.0f,  0.0f, 1.0f, 1.0f}, // 10
    {          x,           y,           z, -1.0f,  0.0f,  0.0f, 0.0f, 1.0f}, // 11

    // Right face 
    {x + length_,           y,           z,  1.0f,  0.0f,  0.0f, 0.0f, 0.0f}, // 12
    {x + length_, y + length_,           z,  1.0f,  0.0f,  0.0f, 1.0f, 0.0f}, // 13
    {x + length_, y + length_, z + length_,  1.0f,  0.0f,  0.0f, 1.0f, 1.0f}, // 14
    {x + length_,           y, z + length_,  1.0f,  0.0f,  0.0f, 0.0f, 1.0f}, // 15

    // Top face
    {          x, y + length_,           z,  0.0f,  1.0f,  0.0f, 0.0f, 0.0f}, // 16
    {          x, y + length_, z + length_,  0.0f,  1.0f,  0.0f, 1.0f, 0.0f}, // 17
    {x + length_, y + length_, z + length_,  0.0f,  1.0f,  0.0f, 1.0f, 1.0f}, // 18
    {x + length_, y + length_,           z,  0.0f,  1.0f,  0.0f, 0.0f, 1.0f}, // 19

    // Bottom face
    {x + length_,           y,           z,  0.0f, -1.0f,  0.0f, 0.0f, 0.0f}, // 20
    {x + length_,           y, z + length_,  0.0f, -1.0f,  0.0f, 1.0f, 0.0f}, // 21
    {          x,           y, z + length_,  0.0f, -1.0f,  0.0f, 1.0f, 1.0f}, // 22
    {          x,           y,           z,  0.0f, -1.0f,  0.0f, 0.0f, 1.0f}, // 23
};

層的編號

層的編號主要用來肯定旋轉那一層,層的編號依以下順序進行。

  • X軸,從左到右,依次爲0,1,2層
  • Y軸,從下到上,依次爲3,4,5層
  • Z軸,從後至前,依次爲6,7,8層

實際上編號都是由各個座標軸的負方向到正方向依次遞增,由於DirectX使用左手系,因此Z軸垂直屏幕向內爲正,這與OpenGL正好相反,若是是OpenGL的話,須要將678層顛倒一下。

 

小立方體編號

小立方體編號是初始化小立方體數組時的順序,在本程序中不以立方體編號來肯定哪些立方體要旋轉,由於這樣比較麻煩,在旋轉以後須要更新編號,並且擴展性很差。小立方體編號按上圖中6,7,8號層依次進行。順序從左到右,從下到上,以下圖所示(注意,這裏只標出了能看見的立方體,看不見的能夠按順序計算出來)

面的編號 

給面編號的緣由是,當鼠標點擊魔方時,須要肯定當前拾取的是哪一個面,肯定了面之後,再根據鼠標的位置來肯定旋轉那一層,後續的篇章有詳細介紹。面的編號按以下規則。下圖中只有三個面可見,看不見的面能夠推算出來。

  • Front face    - 0
  • Back face     - 1
  • Left face      - 2
  • Right face    - 3
  • Top face      - 4
  • Bottom face - 5 

紋理貼圖

紋理佈局以下:前白,後黃,左紅,右橙,上綠,下藍。

 

最初紋理是從圖片生成的,後來發現魔方的顏色都是簡單顏色,能夠省去加載圖片的步驟,直接在內存中建立紋理便可。函數CreateTexture有三個參數,分別是紋理寬度,高度及顏色,該函數內部調用D3DXCreateTexture來建立紋理。紋理建立好之後,調用Lock函數鎖定之,而後使用memcpy進行顏色填充。

LPDIRECT3DTEXTURE9 D3D9::CreateTexture(int texWidth, int texHeight, D3DCOLOR color)
{
    LPDIRECT3DTEXTURE9 pTexture;

    HRESULT hr = D3DXCreateTexture(d3ddevice_, 
        texWidth, 
        texHeight, 
        0, 
        0, 
        D3DFMT_A8R8G8B8,  // 4 bytes for a pixel 
        D3DPOOL_MANAGED, 
        &pTexture);

    if (FAILED(hr))
    {
        MessageBox(NULL, L"Create texture failed", L"Error", 0);
    }

    // Lock the texture and fill in color
    D3DLOCKED_RECT lockedRect;
    hr = pTexture->LockRect(0, &lockedRect, NULL, 0);
    if (FAILED(hr))
    {
        MessageBox(NULL, L"Lock texture failed!", L"Error", 0);
    }

    DWORD sideColor = 0xff000000; // the side line color

    int side_width = 10;

    // Calculate number of rows in the locked Rect
    int rowCount = (texWidth * texHeight * 4 ) / lockedRect.Pitch;

    for (int i = 0; i < texWidth; ++i)
    {
        for (int j = 0; j < texHeight; ++j)
        {
            int index = i * rowCount + j;

            int* pData = (int*)lockedRect.pBits;

            if (i <= side_width || i >= texWidth - side_width 
                || j <= side_width || j >= texHeight - side_width)
            {
                memcpy(&pData[index], &sideColor, 4);
            }
            else
            {
                memcpy(&pData[index], &color, 4);
            }
        }
    }

    pTexture->UnlockRect(0);

    return pTexture;
}

調用上面的函數依次建立6個面的顏色紋理及魔方內部的紋理(旋轉時可見,白色)。

void RubikCube::InitTextures()
{
    DWORD colors[] = 
    {
        0xffffffff, // White,   front face
        0xffffff00, // Yellow,    back face
        0xffff0000, // Red,        left face
        0xffffa500,    // Orange,    right face
        0xff00ff00, // Green,    top face
        0xff0000ff, // Blue,    bottom face
    };

    // Create face textures
    for(int i = 0; i < kNumFaces; ++i)
    {
        face_textures_[i] = d3d9->CreateTexture(texture_width_, texture_height_, colors[i]);
    }

    // Create inner texture
    inner_textures_ = d3d9->CreateInnerTexture(texture_width_, texture_height_, 0xffffffff);

    Cube::SetFaceTexture(face_textures_, kNumFaces);
    Cube::SetInnerTexture(inner_textures_);
}

繪製

在RubikCube類裏面依次初始化全部的小立方體。

void RubikCube::InitCubes()
{// Get unit cube length and gaps between layers
    float length = cubes[0].GetLength();
    float cube_length = cubes[0].GetLength();
    float gap = gap_between_layers_;

    // Calculate half face length
    float half_face_length = face_length_ / 2;
for (int i = 0; i < kNumLayers; ++i) { for (int j = 0; j < kNumLayers; ++j) { for (int k = 0; k < kNumLayers; ++k) { // calculate the front-bottom-left corner coodinates for current cube // The Rubik Cube's center was the coordinate center, but the calculation assume the front-bottom-left corner // of the Rubik Cube was in the coodinates center, so move half_face_length for each coordinates component. float x = i * (cube_length + gap) - half_face_length; float y = j * (cube_length + gap) - half_face_length; float z = k * (cube_length + gap) - half_face_length; // calculate the unit cube index in inti_pos int n = i + (j * kNumLayers) + (k * kNumLayers * kNumLayers); // Initiliaze cube n cubes[n].Init(D3DXVECTOR3(x, y, z)); } } } }

繪製一個小立方體,pIB是一個index buffer數組,共有六個元素,每一個元素表明一個面的index buffer。常量kNumFaces_=6。在繪製每一個面的時候要先設置這個面的紋理。

void Cube::Draw()
{
    // Setup world matrix for current cube
    d3d_device_->SetTransform(D3DTS_WORLD, &world_matrix_) ;

    // Draw cube by draw every face of the cube
    for(int i = 0; i < kNumFaces_; ++i)
    {
        if(textureId[i] >= 0)
        {
            d3d_device_->SetTexture(0, pTextures[textureId[i]]);
        }
        else
        {
            d3d_device_->SetTexture(0, inner_texture_);
        }

        d3d_device_->SetStreamSource(0, vertex_buffer_, 0, sizeof(Vertex));
        d3d_device_->SetIndices(pIB[i]) ;
        d3d_device_->SetFVF(VERTEX_FVF);

        d3d_device_->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP, 0, 0, 24, 0, 2);
    }
}

繪製整個魔方,kNumCubes是一個魔方中小立方體的總數,對於三階魔方來講是3 x 3 x 3 = 27。

//draw all unit cubes to build the Rubik cube
for(int i = 0; i < kNumCubes; i++)
{
    cubes[i].Draw();
}

程序下載

RubikCube

上次發佈的時候有一個嚴重的bug,在旋轉的時候會出現丟失某一層的狀況,現已修復,歡迎各位繼續捉蟲。

Haypp coding!

相關文章
相關標籤/搜索