2011/11/21

C++ で 3 次元 Delaunay 分割

某所にて、『2 次元の Delaunay 分割は簡単だから別にいらない。むしろ 3 次元 Delaunay 分割のソースコードが欲しい』という要望を間接的に頂いたので、みんなが大好きな C++ で作ってみましたよー (´-ω-`)
OpenProcessing に投稿した 3D Delaunay Triangulation よりも、若干ですがソースコードがブラッシュアップされているような気がします。



今回は 3D という事もあり、結果表示用のプログラムには DirectX 9 を使用しました。最新バージョンを使わなかったのは、Windows XP でも実行できるようにという配慮からです。あ、あくまで環境依存のプログラムは GUI 部分のみであり、アルゴリズム(Delaunay3d.h)は標準 C++ のみで実装しているため、他の環境にもそのまま移植できると思います。

使用方法は昨日の 2 次元 Delaunay 分割とほぼ同じで、以下の通りです(Main.cpp のコールバック関数、および render() 関数に実際のサンプルがあります)。

(※ただし、2 次元と 3 次元のモジュールを共存させようとすると、いたるところで名前が衝突するので注意)
  1. 頂点セット(std::set<Tercel::Vector> オブジェクト)を宣言します。
  2. 先ほどの頂点セットに、適当な頂点オブジェクトを格納します。
  3. 三角形セット(std::set<Tercel::Triangle> オブジェクト)を宣言します。
  4. Tercel::Delaunay3d::getDelaunayTriangles() 関数を呼びます。
このとき、getDelaunayTriangles() の第一引数に頂点セットの『参照』を、第2引数には三角形セットの『ポインタ』を渡します。

例によって、三角形(Tercel::Triangle)は、頂点オブジェクトのポインタを保持していますので、頂点セットから要素を削除した場合(あるいはメモリ上の位置が変わるような事が起きた場合)には、三角形の頂点は不正なポインタになってしまいます。

ご注意ください。


Delaunay3d.h
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
//
// 3次元Delaunay分割
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-


#ifndef ___TERCEL_DELAUNAY3D
#define ___TERCEL_DELAUNAY3D

#include <algorithm>
#include <cfloat>
#include <cmath>

#include <map>
#include <set>

namespace Tercel 
{

  // ポインタの参照先を比較するためのファンクタ
  class PtrComp
  {
  public:
    template <class Type> 
    bool operator()(const Type* a, const Type* b) const
    {
      return *a < *b;
    }
  };


  struct Vector {
    double x, y, z;

    // ======================================  
    // 等価性の判定
    // ====================================== 
    bool operator==(const Vector& v) const 
    {
      return x == v.x && y == v.y && z == v.z;
    }

    // ======================================  
    // 大小判定(set / mapを構築する際に使用)
    // ====================================== 
    bool operator<(const Vector& v) const 
    {
      return x != v.x ? x < v.x :
             y != v.y ? y < v.y :
                        z < v.z;
    }
  };


  struct Circle 
  {
    Vector center;  // 中心座標
    double radius;  // 半径
  };


  class Triangle {
  public:
    const static size_t NUM_VERTEX = 3;
    const Vector* p[NUM_VERTEX];  // 頂点座標
    
    // ======================================  
    // 等価性の判定
    // ====================================== 
    bool operator==(const Triangle& t) const 
    {
      const Vector* p1[NUM_VERTEX], *p2[NUM_VERTEX];
      for(int i = 0; i < NUM_VERTEX; ++i)
      { // const Vector const * → const Vector *
        p1[i] = const_cast<const Vector*>(p[i]);
        p2[i] = const_cast<const Vector*>(t.p[i]);
      }
      std::sort(p1, p1 + NUM_VERTEX, PtrComp());
      std::sort(p2, p2 + NUM_VERTEX, PtrComp());

      for(int i = 0; i < NUM_VERTEX; ++i)
        if(!(p1[i] == p2[i])) return false;
      return true;
    }

    // ======================================  
    // 大小判定(set / mapを構築する際に使用)
    // ====================================== 
    bool operator<(const Triangle& t) const 
    {
      const Vector* p1[NUM_VERTEX], *p2[NUM_VERTEX];
      for(int i = 0; i < NUM_VERTEX; ++i)
      { // const Vector const * → const Vector *
        p1[i] = const_cast<const Vector*>(p[i]);
        p2[i] = const_cast<const Vector*>(t.p[i]);
      }
      std::sort(p1, p1 + NUM_VERTEX, PtrComp());
      std::sort(p2, p2 + NUM_VERTEX, PtrComp());
      for(int i = 0; i < NUM_VERTEX - 1; ++i)
        if(!(*p1[i] == *p2[i])) return *p1[i] < *p2[i];
      return *p1[NUM_VERTEX - 1] < *p2[NUM_VERTEX - 1];
    }
  };


  class Tetrahedron {
  public:
    const static size_t NUM_VERTEX = 4;
    const Vector* p[NUM_VERTEX];

    // ======================================  
    // 等価性の判定
    // ====================================== 
    bool operator==(const Tetrahedron& t) const 
    {
      const Vector* p1[NUM_VERTEX], *p2[NUM_VERTEX];
      for(int i = 0; i < NUM_VERTEX; ++i)
      { // const Vector const * → const Vector *
        p1[i] = const_cast<const Vector*>(p[i]);
        p2[i] = const_cast<const Vector*>(t.p[i]);
      }
      std::sort(p1, p1 + NUM_VERTEX, PtrComp());
      std::sort(p2, p2 + NUM_VERTEX, PtrComp());
      for(int i = 0; i < NUM_VERTEX; ++i)
        if(!(p1[i] == p2[i])) return false;
      return true;
    }

    // ======================================  
    // 大小判定(set / mapを構築する際に使用)
    // ====================================== 
    bool operator<(const Tetrahedron& t) const 
    {
      const Vector* p1[NUM_VERTEX], *p2[NUM_VERTEX];
      for(int i = 0; i < NUM_VERTEX; ++i)
      { // const Vector const * → const Vector *
        p1[i] = const_cast<const Vector*>( p[i] );
        p2[i] = const_cast<const Vector*>(t.p[i]);
      }
      std::sort(p1, p1 + NUM_VERTEX, PtrComp());
      std::sort(p2, p2 + NUM_VERTEX, PtrComp());

      for(int i = 0; i < NUM_VERTEX - 1; ++i) 
        if(!(*p1[i] == *p2[i])) return *p1[i] < *p2[i];
      return *p1[NUM_VERTEX - 1] < *p2[NUM_VERTEX - 1];
    }

    // ======================================  
    // 他の四面体と共有点を持つか  
    // ====================================== 
    bool hasCommonPoints(const Tetrahedron& t) const
    {
      for(int i = 0; i < NUM_VERTEX; ++i) 
        for(int j = 0; j < NUM_VERTEX; ++j)
          if(*p[i] == *t.p[j]) return true;
      return false;
    }
  };
  
  class Delaunay3d 
  {
    // ======================================  
    // 型定義
    // ====================================== 
    typedef const std::set<Vector>         ConstVertexSet;
    typedef ConstVertexSet::const_iterator ConstVertexIter;

    typedef std::set<Triangle>             TriangleSet;

    typedef std::set<Tetrahedron>          TetraSet;
    typedef TetraSet::iterator             TetraIter;

    typedef std::map<Tetrahedron, bool>    TetraMap;


  private:
    // ======================================  
    // LU分解による三元一次連立方程式の解法
    // ======================================
    static double lu(double a[3][3], int ip[3]) 
    {
      const int n = 3;
      double weight[n];
   
      for(int k = 0; k < n; ++k)
      {
        ip[k] = k;
        double u = 0;
        for(int j = 0; j < n; ++j) 
        {
          double t = fabs(a[k][j]);
          if (t > u) u = t;
        }
        if (u == 0) return 0;
        weight[k] = 1/u;
      }
      double det = 1;
      for(int k = 0; k < n; ++k) 
      {
        double u = -1;
        int m = 0;
        for(int i = k; i < n; ++i) 
        {
          int ii = ip[i];
          double t = fabs(a[ii][k]) * weight[ii];
          if(t>u) 
          { 
            u = t; 
            m = i;
          }
        }
        int ik = ip[m];
        if (m != k) 
        {
          ip[m] = ip[k]; ip[k] = ik;
          det = -det;
        }
        u = a[ik][k]; det *= u;
        if (u == 0) return 0;
        for (int i = k+1; i < n; ++i) 
        {
          int ii = ip[i];
          double t = (a[ii][k] /= u);
          for(int j = k+1; j < n; ++j) 
            a[ii][j] -= t * a[ik][j];
        }
      }
      return det;
    }

    static void solve(double a[3][3], double b[3], int ip[3], double x[3]) 
    {
      const int n = 3;

      for(int i = 0; i < n; ++i) 
      {
        int ii = ip[i];
        double t = b[ii];
        for (int j = 0; j < i; ++j) 
          t -= a[ii][j] * x[j];
        x[i] = t;
      }
      
      for (int i = n-1; i >= 0; --i) 
      {
        double t = x[i];
        int ii = ip[i];
        for(int j = i+1; j < n; ++j) 
          t -= a[ii][j] * x[j];
        x[i] = t / a[ii][i];
      }
    }

    static double gauss(double a[3][3], double b[3], double x[3]) 
    {
      const int n = 3;
      int ip[n];
      double det = lu(a, ip);
   
      if(det != 0) solve(a, b, ip, x);
      return det;
    }

    // ======================================  
    // 与えられた四面体の外接球を求める
    // ======================================
    static void getCircumcircle(const Tetrahedron& tetra, Circle *dst) 
    {
      const Vector* p0 = tetra.p[0];
      const Vector* p1 = tetra.p[1];
      const Vector* p2 = tetra.p[2];
      const Vector* p3 = tetra.p[3];

      double A[3][3] = {
        {p1->x - p0->x, p1->y - p0->y, p1->z - p0->z},
        {p2->x - p0->x, p2->y - p0->y, p2->z - p0->z},
        {p3->x - p0->x, p3->y - p0->y, p3->z - p0->z}
      };

      double b[3] = {
        0.5 * (p1->x*p1->x - p0->x*p0->x + p1->y*p1->y - p0->y*p0->y + p1->z*p1->z - p0->z*p0->z),
        0.5 * (p2->x*p2->x - p0->x*p0->x + p2->y*p2->y - p0->y*p0->y + p2->z*p2->z - p0->z*p0->z),
        0.5 * (p3->x*p3->x - p0->x*p0->x + p3->y*p3->y - p0->y*p0->y + p3->z*p3->z - p0->z*p0->z)
      };

      double x[3] = {0.0, 0.0, 0.0};

      if(gauss(A, b, x) == 0)
      {
        dst->center.x = dst->center.y = dst->center.z = 0;
        dst->radius   = -1;
      } 
      else 
      {
        dst->center.x = x[0];
        dst->center.y = x[1];
        dst->center.z = x[2];

        double dx = x[0] - p0->x;
        double dy = x[1] - p0->y;
        double dz = x[2] - p0->z;
        dst->radius =  sqrt(dx * dx + dy * dy + dz * dz);
      }
    }
    
    // ======================================  
    // 四面体の重複管理
    // ======================================
    static inline void addElementToRedundanciesMap(TetraMap* pRddcMap,  
        const Tetrahedron& t)   
    {  
      TetraMap::iterator it = pRddcMap->find(t);  
      if(it != pRddcMap->end() && it->second)   
      {  
        // 値を (t, true) から (t, false) に置換  
        pRddcMap->erase(it);  
        pRddcMap->insert(TetraMap::value_type(t, false));  
      } 
      else
        pRddcMap->insert(TetraMap::value_type(t, true));  
    }

  public:
    static void getDelaunayTriangles(ConstVertexSet& pVertexSet, 
        TriangleSet* triangleSet) 
    {

      TetraSet tetraSet;

      // ======================================  
      // 巨大な外部四面体を作る  
      // ======================================
      Vector p0, p1, p2, p3;
      Tetrahedron hugeTetrahedron = {&p0, &p1, &p2, &p3};
      //               !! 注意 !!
      // --------------------------------------
      // hugeTetrahedronの要素はローカル変数への
      // ポインタなので、これらの頂点を共有して
      // いるTetrahedronオブジェクトは、この関数
      // を抜ける前に必ず除去しておく必要がある。
      {
        double maxX, maxY, maxZ;
        double minX, minY, minZ;
        maxX = maxY = maxZ = DBL_MIN;
        minX = minY = minZ = DBL_MAX;
        
        // まず、全ての頂点を包含する球を得る

        // 中心座標を求める
        for(ConstVertexIter it = pVertexSet.begin();
          it != pVertexSet.end(); ++it) 
        {
          if(maxX  < it->x) maxX = it->x; 
          if(it->x < minX ) minX = it->x;
          
          if(maxY  < it->y) maxY = it->y; 
          if(it->y < minY ) minY = it->y;
          
          if(maxZ  < it->z) maxZ = it->z; 
          if(it->z < minZ ) minZ = it->z;
        }
        double centerX = 0.5 * (maxX - minX);
        double centerY = 0.5 * (maxY - minY);
        double centerZ = 0.5 * (maxZ - minZ);

        // 半径を求める
        double dx      = centerX - minX;
        double dy      = centerY - minY;
        double dz      = centerZ - minZ;
        double radius  = sqrt(dx * dx + dy * dy + dz * dz) + 0.1;

        // 4つの頂点
        p0.x = centerX;
        p0.y = centerY + 3.0 * radius;
        p0.z = centerZ;

        p1.x = centerX - 2.0 * sqrt(2.0) * radius;
        p1.y = centerY - radius;
        p1.z = centerZ;

        p2.x = centerX + sqrt(2.0) * radius;
        p2.y = centerY - radius;
        p2.z = centerZ + sqrt(6.0) * radius;

        p3.x = centerX + sqrt(2.0) * radius;
        p3.y = centerY - radius;
        p3.z = centerZ - sqrt(6.0) * radius;
      }

      tetraSet.insert(hugeTetrahedron);

      // --------------------------------------  
      // 点を逐次添加し、反復的に四面体分割を行う  
      // --------------------------------------  
      for(ConstVertexIter vIter = pVertexSet.begin(); 
        vIter != pVertexSet.end(); ++vIter) 
      {
        const Vector* p = &*vIter;

        // --------------------------------------  
        // 追加候補の四面体を保持する一時マップ  
        // --------------------------------------
        TetraMap rddcMap;

        // --------------------------------------  
        // 現在の四面体セットから要素を一つずつ取り出して、    
        // 与えられた点が各々の四面体の外接球の中に含まれるかどうか判定    
        // --------------------------------------  
        for(TetraIter tIter = tetraSet.begin();   
          tIter != tetraSet.end();) 
        {
          // 四面体セットから要素を取りだして…  
          Tetrahedron t = *tIter;

          // 外接球を求める
          Circle c;
          getCircumcircle(t, &c);
          double dx  = c.center.x - p->x;
          double dy  = c.center.y - p->y;
          double dz  = c.center.z - p->z;
          double len = sqrt(dx * dx + dy * dy + dz * dz);

          if(len < c.radius)
          {
            Tetrahedron t0 = {p, t.p[0], t.p[1], t.p[2]};
            addElementToRedundanciesMap(&rddcMap, t0);

            Tetrahedron t1 = {p, t.p[0], t.p[2], t.p[3]};
            addElementToRedundanciesMap(&rddcMap, t1);

            Tetrahedron t2 = {p, t.p[0], t.p[3], t.p[1]};
            addElementToRedundanciesMap(&rddcMap, t2);

            Tetrahedron t3 = {p, t.p[1], t.p[2], t.p[3]};
            addElementToRedundanciesMap(&rddcMap, t3);

            tetraSet.erase(tIter++);
          } else ++tIter;
        }

        for(TetraMap::iterator iter = rddcMap.begin();
          iter != rddcMap.end(); ++iter) 
        {
          if(iter->second) tetraSet.insert(iter->first);
        }
      }

      // --------------------------------------  
      // 最後に、外部三角形の頂点を削除  
      // --------------------------------------  
      for(TetraIter tIter = tetraSet.begin();   
        tIter != tetraSet.end(); )   
      {  
        if(hugeTetrahedron.hasCommonPoints(*tIter))  
           tetraSet.erase(tIter++);  
        else ++tIter;  
      }

      // そして、伝説へ…
      for(TetraIter tIter = tetraSet.begin();   
        tIter != tetraSet.end(); ++tIter) 
      {
        Tetrahedron tetra = *tIter;

        Triangle t0 = {tetra.p[0], tetra.p[1], tetra.p[2]};
        triangleSet->insert(t0);

        Triangle t1 = {tetra.p[0], tetra.p[2], tetra.p[3]};
        triangleSet->insert(t1);

        Triangle t2 = {tetra.p[0], tetra.p[3], tetra.p[1]};
        triangleSet->insert(t2);

        Triangle t3 = {tetra.p[1], tetra.p[2], tetra.p[3]};
        triangleSet->insert(t3);
      }
    }
  };
}

#endif

Main.cpp
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
//
// 3次元Delaunay分割(表示用GUI)
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>

#include "Delaunay3d.h"

//------ライブラリの設定とか
#pragma comment(lib, "d3dxof.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "winmm.lib")

//------ウィンドウを出すために必要な物あれこれ
const LPCTSTR APP_NAME    = TEXT("DXT2_BY_TERCEL");
const LPCTSTR MUTEX_NAME  = TEXT("MUTEX_DXT2_BY_TERCEL");
const LPCTSTR WINDOW_NAME = TEXT("3D Delaunay Triangulation");

const UINT SCREEN_WIDTH  = 800;
const UINT SCREEN_HEIGHT = 600;
const UINT WINDOW_WIDTH  = (SCREEN_WIDTH + 
              GetSystemMetrics(SM_CXFIXEDFRAME) * 2);
const UINT WINDOW_HEIGHT = (SCREEN_HEIGHT + 
              GetSystemMetrics(SM_CYFIXEDFRAME) * 2 + 
              GetSystemMetrics(SM_CYCAPTION));

// ----------------------------------------
// 頂点集合、およびそれらを基に
// Delaunay 分割された三角形を格納しておくSTLコンテナ
// ----------------------------------------
std::set<Tercel::Vector>   vertices;
std::set<Tercel::Triangle> triangles;


//------Direct3Dに必要なものあれこれ
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pD3DDev = NULL;
D3DPRESENT_PARAMETERS g_D3DPresentParam;


//------関数プロトタイプ
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
HRESULT InitD3D(HWND);
VOID    Render();

//------FVF
struct ColorPoint
{
  D3DXVECTOR3 vCoord; // 座標
  DWORD   color;  // 色
};

//------エントリポイント
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                   LPSTR lpCmdLine, int nCmdShow)
{
  MSG msg;

  // 多重起動防止
  HANDLE hMutex = CreateMutex(NULL, TRUE, MUTEX_NAME);
  if(GetLastError() == ERROR_ALREADY_EXISTS)
  {
    // 多重起動を検知した際には、
    // 既存のウィンドウを最前面に表示してプログラムを終了する
    HWND existingWnd = FindWindow(APP_NAME, NULL);
    if(existingWnd != NULL)
    {
      if(IsIconic(existingWnd))
        ShowWindowAsync(existingWnd, SW_RESTORE);

      SetForegroundWindow(existingWnd);
    }
    return -1;
  }

  // ウィンドウクラス登録 
  WNDCLASSEX wc;

  wc.cbSize = sizeof(WNDCLASSEX);
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = WindowProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = (HICON)LoadImage(NULL,
    MAKEINTRESOURCE(IDI_APPLICATION),
    IMAGE_ICON,
    0,
    0,
    LR_DEFAULTSIZE | LR_SHARED);
  wc.hCursor = (HCURSOR)LoadImage(NULL,
    MAKEINTRESOURCE(IDC_ARROW),
    IMAGE_CURSOR,
    0,
    0,
    LR_DEFAULTSIZE | LR_SHARED);
  wc.hbrBackground = NULL;
  wc.lpszMenuName = NULL;
  wc.lpszClassName = APP_NAME;
  wc.hIconSm = (HICON)LoadImage(NULL,
    MAKEINTRESOURCE(IDI_APPLICATION),
    IMAGE_ICON,
    0,
    0,
    LR_DEFAULTSIZE | LR_SHARED);
  if(!RegisterClassEx(&wc)) return -1;

  // --------------------------------------
  // 固定サイズのウィンドウ作成
  // --------------------------------------
  HWND hWnd = CreateWindow(APP_NAME,
    WINDOW_NAME,
    WS_OVERLAPPEDWINDOW^WS_THICKFRAME^WS_MAXIMIZEBOX, // サイズ変更禁止
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    WINDOW_WIDTH,
    WINDOW_HEIGHT,
    NULL,
    NULL,
    hInstance,
    NULL);
  if(!hWnd) return -1;
  ValidateRect(hWnd, NULL);

  // --------------------------------------
  // Direct3Dを初期化
  // --------------------------------------
  if(FAILED(InitD3D(hWnd))) return -1;

  // --------------------------------------
  // ウィンドウ表示
  // --------------------------------------
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  while(TRUE)
  {
    if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
    {
      if (msg.message == WM_QUIT) break;
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    else
    {
      Render();
    }
  }
  
  if (g_pD3DDev) g_pD3DDev->Release();
  if (g_pD3D)    g_pD3D->Release();
  CloseHandle(hMutex);
  return (int)msg.wParam;
}

//------Direct3Dを初期化します
HRESULT InitD3D(HWND hWnd)
{
  if((g_pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL)
    return E_FAIL;

  D3DDISPLAYMODE d3ddm;
  if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
    return E_FAIL;

  ZeroMemory(&g_D3DPresentParam, sizeof(g_D3DPresentParam));
  g_D3DPresentParam.BackBufferCount = 1;
  g_D3DPresentParam.Windowed = TRUE;
  g_D3DPresentParam.BackBufferFormat = D3DFMT_UNKNOWN;
  g_D3DPresentParam.SwapEffect = D3DSWAPEFFECT_DISCARD;
  g_D3DPresentParam.EnableAutoDepthStencil = TRUE;
  g_D3DPresentParam.AutoDepthStencilFormat = D3DFMT_D16;

  if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
    D3DDEVTYPE_HAL,
    hWnd,
    D3DCREATE_HARDWARE_VERTEXPROCESSING,
    &g_D3DPresentParam,
    &g_pD3DDev)))
  {

    if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
      D3DDEVTYPE_HAL,
      hWnd,
      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
      &g_D3DPresentParam,
      &g_pD3DDev)))
    {
      if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
        D3DDEVTYPE_REF,
        hWnd,
        D3DCREATE_SOFTWARE_VERTEXPROCESSING,
        &g_D3DPresentParam,
        &g_pD3DDev)))
      {
        return E_FAIL;
      }
    }
  }

  return S_OK;
}

//------コールバック関数
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, 
              WPARAM wParam, LPARAM lParam)
{
  switch(uMsg)
  {
    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;

    case WM_CREATE:
      // ======================================  
      // 頂点集合にランダムでデータをセット
      // ======================================  
      for(int i = 0; i < 200; ++i) {
        Tercel::Vector v; // ベクトルを宣言
        float r   = 0.6f * SCREEN_WIDTH + 0.1f * (rand() % 10);
        float phi = D3DXToRadian(rand() % 180 - 90);
        float theta = D3DXToRadian(rand() % 360);

        v.x = r*cos(phi)*cos(theta);
        v.y = r*sin(phi);
        v.z = r*cos(phi)*sin(theta);

        vertices.insert(v);
      }

      // ======================================  
      // Delaunay分割を行う関数
      // ======================================  
      
      // 結果は第2引数(std::set<Tercel::Triangle> 型)に格納される。
      // 
      // Tercel::Triangle のメンバには、3つの頂点p1, p2, p3 があって、
      // いずれも Tercel::Vertex オブジェクトへのポインタになっている
      // これらのポインタは、vertices リストの要素を参照している。
      Tercel::Delaunay3d::getDelaunayTriangles(vertices, &triangles);
      //                                       ~~~~~~~~  ~~~~~~~~~~
      //                                       頂点集合  三角形集合
      //                                       (参照) (ポインタ)
      return 0;
  }
  return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

//------描画関数
VOID Render() {
  if(FAILED(g_pD3DDev->Clear(0, NULL,
    D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
    D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f),
    1.0f, 0))) return;
  // ワールド行列
  D3DXMATRIX mWorld;
  D3DXMatrixIdentity(&mWorld);
  D3DXMatrixRotationY(&mWorld, 
                      D3DXToRadian(timeGetTime()/50.0f));
  g_pD3DDev->SetTransform(D3DTS_WORLD, &mWorld);

  // ビュー行列
  const float PI = 3.1415926535f;
  D3DXVECTOR3 vEyePoint(0.0f, 0.0f, 1500.0f);
  D3DXVECTOR3 vLookAtPt(0.0f, 0.0f, 0.0f);
  D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);

  D3DXMATRIX mView;
  D3DXMatrixLookAtLH(&mView, &vEyePoint, &vLookAtPt, &vUp);
  g_pD3DDev->SetTransform(D3DTS_VIEW, &mView);

  float aspect;
  aspect = (float)SCREEN_WIDTH / (float)SCREEN_HEIGHT;
    
  // 射影行列
  D3DXMATRIX mProj;
  D3DXMatrixPerspectiveFovLH(&mProj,
    D3DXToRadian(45.0f),
    aspect,
    1.0f,
    10000.0f);
  g_pD3DDev->SetTransform(D3DTS_PROJECTION, &mProj);

  if(SUCCEEDED(g_pD3DDev->BeginScene()))
  {
    // ======================================  
    // ここから描画処理
    // ======================================
    ColorPoint vPoint[2];

    vPoint[0].color = D3DCOLOR_ARGB(255, 0, 100, 200);
    vPoint[1].color = D3DCOLOR_ARGB(255, 0, 100, 200);

    g_pD3DDev->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
    g_pD3DDev->SetRenderState(D3DRS_LIGHTING, FALSE);

    // ======================================  
    // Delaunay分割された三角形の集合を一つ一つ取りだして描画
    // ======================================  
    for(std::set<Tercel::Triangle>::iterator it = triangles.begin();
      it != triangles.end(); ++it) 
    {
      Tercel::Triangle t = *it;  // 三角形取得
      for(int i = 0; i < 3; ++i) {
        float x1 = (float)t.p[i]->x;
        float y1 = (float)t.p[i]->y;
        float z1 = (float)t.p[i]->z;

        float x2 = (float)t.p[(i+1)%3]->x;
        float y2 = (float)t.p[(i+1)%3]->y;
        float z2 = (float)t.p[(i+1)%3]->z;
        vPoint[0].vCoord = D3DXVECTOR3(x1, y1, z1);
        vPoint[1].vCoord = D3DXVECTOR3(x2, y2, z2);
        g_pD3DDev->DrawPrimitiveUP(D3DPT_LINELIST, 1, 
          vPoint, sizeof(ColorPoint));
      }
    }
    g_pD3DDev->EndScene();
  }

  //----- デバイスロスト対策
  if(g_pD3DDev->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
  {
    if(g_pD3DDev->TestCooperativeLevel() == D3DERR_DEVICELOST)
    {
      return;
    }
    g_pD3DDev->Reset(&g_D3DPresentParam);
  }
}

【おまけ】

勢いでリツイートしてしまったがコード付きという神記事だった2011年11月21日 21:10 via TweetDeck



わわっΣ(・Д・ノ)ノ 恐縮です(照

6 件のコメント:

  1. おーーーーーーーーーーーーーーーーーーーーー、つぶやいてみるものですね。

    返信削除
  2. とても魅力的な記事でした!!
    また遊びに来ます!!
    ありがとうございます。。

    返信削除
    返信
    1. コメントありがとうございます。ぜひお越しください。

      削除
  3. 大変参考になりました!

    実際に使ってみたところ、
    ・外部四面体を求める際の中心の演算(L.363-365)が-ではなく+
    ・四面体から三角形を求める際(L.449以降)は手順を逆にして、四面体→三角形→外部四面体を含む三角形の除去
    の方が良いのでは、と思いました。

    返信削除
  4. 参考にさせて頂きました。

    質問があるのですが、getCircumcircle関数で定義しているb[]はどのようにして求められたのでしょうか。

    Aはp0で平行移動したデータでXは外接球の中心座標だと思うのですが、b[]が良く分かりませんでした。可能であれば返信をお願い致します。

    返信削除

ひとことどうぞφ(・ω・,,)