Lab 9-2: Complex 的應用與 Mandelbrot 圖形界面

   
實習目標 使用實習 3-1, 4-2 中的 Complex 類別
做一個 Mandelbrot 碎形動畫
進一步練習與視窗圖形界面的合作
練習視窗界面程式的 debug
   
步驟一 這個實習接續 Lab 9-1, 我們稍微修改一下 Mandelbrot 類別的界面, 讓它能夠支援一個 Mandelbrot 動畫的程式, 範例執行程式 Mandelbrot03a, 這個程式會慢慢畫出每計算一次 z = z * z + c 之後的圖形, 在狀態列會顯示目前已經作了多少次了, 以及每一次有多少個點發散掉了, 程式一直執行到 256 次為止

在任何時候, 你可以

  1. 用滑鼠左鍵點兩下來 暫停/繼續 動畫,
  2. 用選單上的暫停模擬選項來暫停動畫,
  3. 按下滑鼠左鍵在視窗中拖曳來指定一個區域, 指定區域中的圖案會在另外一個視窗中放大顯示,
  4. 用右鍵點選視窗內任一區域可以更換一組顏色。
步驟二 請下載圖形界面程式碼 Mandelbrot03b_vc2010.rar, 解壓縮出來, 請編譯並且執行, 你應該可以看到視窗中是全部空白的, 選單中有 檔案/暫停動畫 的選項, 如同上一個實習, 我們將在這個 project 中應用你先前寫的 Complex 類別來撰寫程式
步驟三 首先在類別檢視視窗中你可以找到 Mandelbrot 類別, 打開這個類別你可以看到下列類別的定義以及空的成員函式:
    class Mandelbrot  
    {
    public:
        int updateData(int ** const &data, int currentIteration);
        void generateData(int ** const &data, int numIterations);
        Mandelbrot(double centerX, double centerY, 
                   double range, int windowSize);
        virtual ~Mandelbrot();
    
    private:
    ...
    };

這個類別和前一個實習裡的 Mandelbrot 類別的運作模型有一些不一樣, 畫面中會顯示動畫的最主要原因就是我們每隔固定時間 (例如 40 毫秒) 就會重新繪製一次畫面, 每次繪製畫面的內容都有一點點差異,所以眼睛就自動把他變成連續的動作了, 所以這個新的 Mandelbrot 類別中主要增加的界面為:


    int updateData(int ** const &data, int currentIteration);

這個介面就是為了支援動畫的繪製, 為了繪製動畫, Mandelbrot 物件不能用和上次一樣的 generateData() 函式一次把整個圖形都計算完, 而需要這一個 updateData() 的介面來慢慢計算圖形中每一點是否發散, 你可以想像視窗圖形界面函式做的動作變成


    while (每隔固定時間)
        mandelbrot.updateData(...);

每次當 updateData() 被呼叫到的時候, 在 updateData() 裡把還沒有發散的座標點都再計算一次 z = z * z + c, 判斷該點是否發散, 修改對應的 data[i][j] 的數值

Mandelbrot 建構元函式會傳進來繪圖中心點的複數平面的座標, 例如 (-0.5, 0), 以及繪圖區域的寬與高 (目前所畫的是一個方形區域), 例如 2.3, 以及視窗顯示區域的點數, 例如 500, 請在 Mandelbrot 類別中設計常數 (const) 資料成員記錄下來, 例如:


    const double m_range;
    const double m_centerY;
    const double m_centerX;
    const int m_windowSize;

除此之外, 類別內也需要設計一個寬度及高度都是 m_windowSize 的二維Complex型態的資料成員陣列 m_z, 主要目的是記錄每一回合計算後 z 的數值, 這個陣列我們為了有彈性地使用記憶體, 還是用下圖動態的方式來配置:

建構元中必須負責配置這個二維陣列, 解構元中必須負責釋放這個二維陣列
步驟四

這個類別也需要用到複數的類別, 請由前一個實習中拷貝進來

並且在 Complex.cpp 的第一列加入 #include "stdafx.h" (這個動作是因為圖形化介面是使用 MFC 來製作的, 所以才需要)

步驟五

    void generateData(int ** const &data, int numIterations);
這個函式和前一個實習中實作的 generateData() 概念相同, 每次被呼叫到時, 代表繪圖界面程式需要知道在 numIterations 回合的限制下有多少點發散掉了; 但是這一次的 generateData() 負責的事情較少, 它不需要去配置 data 陣列的記憶體也不允許修改 data 指標變數了, 它仍然需要從頭計算 numIterations 個回合, 算出每一個點經過多少回合才會發散, 並且將結果記錄在 data 陣列中,

對於所有沒有發散的點, data[i][j] 都必須設定為 0

你依照上面的說明實作的 generateData() 函式, 對於圖形化介面程式有兩個用途 (你可以在 CChildView::OnCreate() 和 CZoomWnd::setMandel() 函式中看到):

  1. generateData() 第一次被呼叫時, 目的是計算由第一回合到第numIterations回合時, m_z 二維陣列的內容, 後續可以再呼叫 updateData() 一回合一回合運用 z = z * z + c 計算新的 mandelbrot 圖形, 所以 generateData() 函式中必須把所有沒有發散的點, 在經過numIterations回合之後的 z 值記錄在 m_z 陣列中
  2. 用來重新產生小視窗裡面放大過的 mandelbrot 圖形
步驟六

    int updateData(int ** const &data, int currentIteration);

每次 updateData() 被呼叫到時, 代表界面程式需要下一個時間點的動畫資料 (你可以在 CChildView::OnTimer() 函式中看到), 請根據先前所記錄在 m_z 陣列中的 z 值, 繼續運用 z = z * z + c 計算下一個回合的 data[i][j] 數值並且存放在 data[i][j] 陣列中, z 的資料一樣需要記錄下來。

另外在函式裡只需要對於 data[i][j] 為 0 的點來計算就可以了, data[i][j] 不等於 0 的點在前面的運算中都已經發散了, 不需要再繼續計算下去, 如果在計算的過程中發現某一個點發散掉了, 請將 data[i][j] 設為 currentIteration+1, 這個傳進來的 currentIteration 數值代表目前進行到第幾個回合了 (第一回合 currentIteration == 0)。

updateData() 函式回傳的數值是在這個回合中發散的點數, 圖形界面程式用來在視窗下緣的狀態列中顯示本回合中發散掉的點數。

步驟七 請助教檢查後, 將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .suo, .sdf, .filters, .users, debug\ 資料匣, 以及 ipch\ 資料匣下的所有內容) 壓縮起來, 選擇 Lab9-2 上傳, 後面的實習課程可能需要使用這裡所完成的程式

C++ 物件導向程式設計課程 首頁

製作日期: 04/11/2013 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon