convex hull
3D convex hull
3D convex hull:
Graham's scan
3D convex hull:
gift wrapping algorithm
3D convex hull:
monotone DAG
3D convex hull:
incremental method
3D convex hull:
divide-and-conquer method
3D convex hull:
quick hull algorithm

3D convex hull

3D convex hull

三維凸包的表面,由數個凸多邊形拼成。

為了方便儲存,凸多邊形剖分成數個三角形。

為了方便儲存,多點共面剖分成數個三角形。

三維凸包的表面,由數個三角形拼成。

p = {{5,4,8},{1,8,6},{6,6,4},{9,6,4},{7,9,7},{6,9,9},{8,0,8},{1,5,4},{1,1,5},{6,6,3},{8,6,7},{0,1,6},{9,7,10},{7,6,3},{6,2,7},{10,4,4},{5,8,2},{8,3,2},{2,6,3},{0,0,1},{7,3,3},{6,9,8},{2,8,1},{10,6,10},{5,8,10},{8,2,4},{5,7,6},{6,7,2},{1,10,1},{0,4,5},{1,3,2},{8,1,0},{4,10,2},{1,9,0},{6,3,5},{9,1,3},{8,10,1},{9,6,8},{6,2,3},{10,0,2},{8,1,10},{10,2,4},{9,0,10},{2,9,6},{10,5,10},{4,0,5},{2,3,8},{3,8,4},{3,3,10},{4,5,1}};

我們習慣以逆時針順序記錄三角形的三個頂點(三角形的三條邊變成有向邊)。這麼做的好處是,三角形依序取兩條邊計算叉積,就得到朝外的法向量。

facet

高維度空間的物體,一塊平坦表面稱作「刻面」,就好像被刻了一刀。鑽石就是刻面的極致工藝。

為了方便儲存,刻面從凸多邊形變成三角形。

3D convex hull:
Graham's scan

三維凸包的繞行順序是什麼?

二維凸包的頂點,以時針順序繞行一圈。三維凸包的頂點,至今仍未發現適當的繞行順序。三維的 Graham's scan 至今仍是懸案。

3D convex hull:
gift wrapping algorithm

演算法

找到第一個凸包刻面:座標最低的三個點。

以邊為軸,旋轉的掃描面,尋找最外圍的點,包覆成新刻面。

當凸包多點共面,在掃描面上尋找二維凸包、尋找三角剖分。

時間複雜度 O(FNlogN) , F 是凸包刻面數量。多面體最多有 O(N) 個面,時間複雜度可寫作 O(N²logN) 。

3D convex hull:
monotone DAG

monotone chain 可以推廣成 monotone DAG 嗎?

等你發表論文。

3D convex hull:
incremental method

演算法

逐次增加一點,更新刻面。

運用刻面法向量,判斷刻面屬於凸面、凹面、切面。

運用刻面有向邊,找到凹切分際。

保留凸面與切面,移除凹面,添加當前輸入點到凹切分際的新刻面。如此便完成了新凸包。

預先按照 XYZ 座標排序所有點(平移的掃描面),就能保證當前輸入點都在凸包外部,就能保留多點共面的三角剖分。

時間複雜度 O(N²) 。加速為 O(NlogN) 至今仍是懸案。

  1. // 凸包的頂點
  2. struct Point {float x, y, z;} p[10];
  3. Point operator-(Point a, Point b)
  4. {
  5. return (Point){a.x - b.x, a.y - b.y, a.z - b.z};
  6. }
  7. double dot(Point a, Point b)
  8. {
  9. return a.x * b.x + a.y * b.y + a.z * b.z;
  10. }
  11. Point cross(Point a, Point b)
  12. {
  13. return (Point){a.y * b.z - b.y * a.z,
  14. a.z * b.x - b.z * a.x,
  15. a.x * b.y - b.x * a.y};
  16. }
  17. bool zero(Point a)
  18. {
  19. return a.x == 0 && a.y == 0 && a.z == 0;
  20. }
  21. bool cmp(Point a, Point b)
  22. {
  23. if (a.x != b.x) return a.x < b.x;
  24. if (a.y != b.y) return a.y < b.y;
  25. return a.z < b.z;
  26. }
  27. // 凸包的刻面
  28. // 一個刻面為三個頂點(索引值),逆時針順序為向外。
  29. struct Facet {int a, b, c;};
  30. Point normal(Facet f)
  31. {
  32. return cross(p[f.b] - p[f.a], p[f.c] - p[f.a]);
  33. }
  34. // 凸包的刻面的有向邊
  35. // 一條有向邊為兩個頂點(索引值)
  36. int vis[10][10];
  37. void convex_hull()
  38. {
  39. // 預先排序
  40. sort(p, p+10, cmp);
  41. /* 必須弄出一個四面體。 */
  42. // 四面體頂點(索引值)
  43. int v0, v1, v2, v3;
  44. // 指定第一點
  45. v0 = 0;
  46. // 令前兩點不共點
  47. int i = 0;
  48. for (++i; i<10; ++i)
  49. if (!zero(p[i] - p[v0]))
  50. {
  51. v1 = i;
  52. break;
  53. }
  54. if (i == 10) return; // 通通共點
  55. // 令前三點不共線
  56. for (++i; i<10; ++i)
  57. if (!zero(cross(p[v1] - p[v0], p[i] - p[v0])))
  58. {
  59. v2 = i;
  60. break;
  61. }
  62. if (i == 10) return; // 通通共線
  63. // 令前四點不共面
  64. Point n = cross(p[v1] - p[v0], p[v2] - p[v0]);
  65. float d = 0;
  66. for (++i; i<10; ++i)
  67. if ((d = dot(n, p[i] - p[v0])) != 0)
  68. {
  69. v3 = i;
  70. break;
  71. }
  72. if (i == 10) return; // 通通共面
  73. /* 開始建立凸包 */
  74. // 前四點形成的凸包
  75. vector<Facet> cur;
  76. if (d > 0)
  77. {
  78. cur.push_back((Facet){v1,v0,v2});
  79. cur.push_back((Facet){v0,v1,v3});
  80. cur.push_back((Facet){v1,v2,v3});
  81. cur.push_back((Facet){v2,v0,v3});
  82. }
  83. else
  84. {
  85. cur.push_back((Facet){v0,v1,v2});
  86. cur.push_back((Facet){v1,v0,v3});
  87. cur.push_back((Facet){v2,v1,v3});
  88. cur.push_back((Facet){v0,v2,v3});
  89. }
  90. // 處理其餘點
  91. for (int i=0; i<10; ++i)
  92. {
  93. if (i == v0 || i == v1 || i == v2 || i == v3)
  94. continue;
  95. vector<Facet> next;
  96. // 保留凸面與切面、移除凹面
  97. for (int j=0; j<cur.size(); ++j)
  98. {
  99. Facet& f = cur[j];
  100. // 保留凸面與切面
  101. double d = dot(p[f.a] - p[i], normal(f));
  102. if (d >= 0) next.push_back(f);
  103. // 刻面有向邊,記錄凸面、凹面、切面。
  104. int side = 0; // 切面
  105. if (d > 0) side = +1; // 凸面
  106. if (d < 0) side = -1; // 凹面
  107. vis[f.a][f.b] = side;
  108. vis[f.b][f.c] = side;
  109. vis[f.c][f.a] = side;
  110. }
  111. // 添加當前輸入點到凹凸分際的新刻面
  112. for (int j=0; j<cur.size(); ++j)
  113. {
  114. Facet& f = cur[j];
  115. int a = f.a, b = f.b, c = f.c;
  116. // 找到凹面,分別觀察三條邊,判斷是否為凹凸分際。
  117. if (vis[a][b] < 0 && vis[a][b] != vis[b][a])
  118. next.push_back((Facet){a, b, i});
  119. if (vis[b][c] < 0 && vis[b][c] != vis[c][b])
  120. next.push_back((Facet){b, c, i});
  121. if (vis[c][a] < 0 && vis[c][a] != vis[a][c])
  122. next.push_back((Facet){c, a, i});
  123. }
  124. cur = next;
  125. }
  126. }

UVa 11769 12513 ICPC 5090 4795

3D convex hull:
divide-and-conquer method

演算法

所有點分成左右兩堆,分別求凸包。合併兩個凸包,用滾動的。筷子串起兩個凸包,放在地上滾,以筷子為軸轉一圈。

時間複雜度 O(NlogN) 。

3D convex hull:
quick hull algorithm

演算法

時間複雜度 O(N²) 。實務上最快的演算法。