私はこれに似た形、12の五角形、6つの六角形を任意のサイズで作成しようとしています。
( 画像ソース )
唯一のことは、それを生成するためにどのようなコードが必要になるかまったくわからないことです!
目標は、3D空間の点を取り、それをグリッド上の位置座標に変換できるか、またはその逆で、グリッドの位置を取り、メッシュの描画に関連する頂点を取得できるようにすることです。
このためのグリッド位置をどのように保存するかさえわかりません。 3つの五角形の間の各「三角形部分」は、独自の2D座標セットを取得しますか?
私はこれにC#を使用する可能性が高いですが、誰かにコードを渡してもらうよりも、これに使用するアルゴリズムとその動作の説明に興味があります。
問題の画像の最初のいくつかの分析:隣接する五角形の中心がまたがる球形の三角形は正三角形のようです。 5つの正三角形が1つのコーナーで出会い、球全体をカバーする場合、これは 20面体 によって引き起こされる構成のみになります。したがって、球にマッピングされた六角形メッシュの三角形の切り抜きの12の五角形と20のパッチがあります。
だから、これは球にそのような六角形のグリッドを構築する方法です:
六角形グリッドの三角形カットアウトを作成します。固定三角形((-0.5,0)、(0.5,0)、(0、sqrt(3)/ 2)を選択)は、希望する解像度の六角形グリッドを重ね合わせますn
st三角形の角は六角形の中心と一致します。n = 0,1,2,20
の例を参照してください:
icosahedron の角を計算し、20の三角形の面を定義します(以下のコードを参照)。二十面体の角は五角形の中心を定義し、二十面体の面はマッピングされた六角形グリッドのパッチを定義します。 (二十面体は、球表面の三角形への細かい正則分割、つまり合同な正三角形への分割を提供します。他のそのような分割は、四面体から導出できます。または八面体;次に、三角形の角に三角形または正方形が表示されます。さらに、三角形が少なくて大きくなると、曲面への平面メッシュのマッピングで避けられない歪みがより見えやすくなります。したがって、20面体を三角形パッチの基準は、六角形の歪みを最小限に抑えるのに役立ちます。)
六角形のグリッドの三角形のカットアウトを、二十面体の面に対応する球形の三角形にマップします。ダブル 重心座標 に基づく二重 slerp がトリックを行います。以下は、解像度n = 10
の六角形グリッドの三角形カットアウトを1つの球面三角形(氷の1つの面で定義)にマッピングした図と、グリッドをカバーするこれらすべての球面三角形にグリッドをマッピングした図です。全球(マッピングごとに異なる色):
二十面体の角(座標)と三角形(点インデックス)を生成するPythonコード:
from math import sin,cos,acos,sqrt,pi
s,c = 2/sqrt(5),1/sqrt(5)
topPoints = [(0,0,1)] + [(s*cos(i*2*pi/5.), s*sin(i*2*pi/5.), c) for i in range(5)]
bottomPoints = [(-x,y,-z) for (x,y,z) in topPoints]
icoPoints = topPoints + bottomPoints
icoTriangs = [(0,i+1,(i+1)%5+1) for i in range(5)] +\
[(6,i+7,(i+1)%5+7) for i in range(5)] +\
[(i+1,(i+1)%5+1,(7-i)%5+7) for i in range(5)] +\
[(i+1,(7-i)%5+7,(8-i)%5+7) for i in range(5)]
そして、ここにPython固定の三角形(のポイント)をダブルslerpを使用して球面の三角形にマップするコードがあります:
# barycentric coords for triangle (-0.5,0),(0.5,0),(0,sqrt(3)/2)
def barycentricCoords(p):
x,y = p
# l3*sqrt(3)/2 = y
l3 = y*2./sqrt(3.)
# l1 + l2 + l3 = 1
# 0.5*(l2 - l1) = x
l2 = x + 0.5*(1 - l3)
l1 = 1 - l2 - l3
return l1,l2,l3
from math import atan2
def scalProd(p1,p2):
return sum([p1[i]*p2[i] for i in range(len(p1))])
# uniform interpolation of arc defined by p0, p1 (around Origin)
# t=0 -> p0, t=1 -> p1
def slerp(p0,p1,t):
assert abs(scalProd(p0,p0) - scalProd(p1,p1)) < 1e-7
ang0Cos = scalProd(p0,p1)/scalProd(p0,p0)
ang0Sin = sqrt(1 - ang0Cos*ang0Cos)
ang0 = atan2(ang0Sin,ang0Cos)
l0 = sin((1-t)*ang0)
l1 = sin(t *ang0)
return Tuple([(l0*p0[i] + l1*p1[i])/ang0Sin for i in range(len(p0))])
# map 2D point p to spherical triangle s1,s2,s3 (3D vectors of equal length)
def mapGridpoint2Sphere(p,s1,s2,s3):
l1,l2,l3 = barycentricCoords(p)
if abs(l3-1) < 1e-10: return s3
l2s = l2/(l1+l2)
p12 = slerp(s1,s2,l2s)
return slerp(p12,s3,l3)
あなたが持っている形状は、いわゆる「ゴールドバーグ多面体」の1つであり、 geodesic多面体 でもあります。
これを生成する(ややエレガント)アルゴリズムは、 Conway多面体と呼ばれるもので簡潔にエンコードできます)表記 。
構造はステップバイステップで簡単にフォローできます。下の画像をクリックして、ライブプレビューを取得できます。
"Truncate" 演算(Conway表記t
)をメッシュに適用します(このメッシュの特定のマッピングはサッカーです)。
"Dual" 演算子(Conway表記d
)を適用します。
「切り捨て」操作を再度適用します。この時点で、レシピはtdtI
です(右から読んでください)。これがどこに向かっているのか、あなたはすでに見ることができます。
満足するまで手順3と4を繰り返し適用します。
これは簡単に実装できます。メッシュのウィングドエッジやハーフエッジのデータ構造など、頂点やエッジなどを指定して近傍をトラバースしやすくするデータ構造を使用することをお勧めします。探している形状に対して、切り捨て演算子と二重演算子を実装するだけで済みます。
[2017年10月18日、再編集を完了]
ジオメトリストレージはあなたにあります。何らかのメッシュに保存するか、その場で生成します。保管したい。 2つのテーブルの形で。 1つはすべての頂点(重複なし)を保持し、もう1つは取得したヘクスごとに使用済みポイントの6つのインデックスを保持し、球面処理などの追加情報を保持して後処理を容易にします。
これを生成する方法:
六角形の三角形を作成
サイズは球の半径でなければなりません。コーナーhexessを含めないでください。また、三角形のセグメントを結合するときに重なるため、三角形の最後の行をスキップしてください(半径方向と軸方向の両方で、隣接する三角形間に1ヘックスのギャップがあります)。
_60deg
_六角形の三角形を_72deg
_ pieに変換します
したがって、単純に極座標(_radius,angle
_)に変換し、_0 deg
_の周りに三角形を配置します。次に、半径にcos(angle)/cos(30);
を掛けます。これにより、三角形が円グラフに変換されます。そして、比率_72/60
_で角度を再スケーリングします。これで三角形が結合可能になります...
五角形の5つのセグメントを埋めるために三角形をコピーして回転します
最初の三角形の点を回転させ、新しいものとして保存するだけです。
compute z
これに基づいて 半球の六角形の傾斜2Dマップの距離を弧長に変換して、歪みを制限することができます。可能な限り。
しかし、私がそれを試したとき(下の例)六角形は少し歪んでいるので、深度とスケーリングを調整する必要があります。または後者の後処理。
半球をコピーして球を形成します
単にポイント/ヘクスをコピーし、z
軸を無効にします(または、曲がりを維持したい場合は、180度回転します)。
赤道と不足しているすべての五角形と六角形を追加します
隣接するヘクスの座標を使用して、グリッドに歪みやオーバーラップが追加されないようにする必要があります。ここでプレビュー:
Blueは開始三角形です。 ダークブルーはそのコピーです。 赤は五角形の極です。 濃い緑は赤道、薄い緑は三角形間の結合線です。 Yellowishには、Dark Orange五角形の近くに欠けている赤道六角形があります。
ここでは単純なC++ OpenGLの例(#4のリンクされた回答から作成) ):
_//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
#include "gl/OpenGL3D_double.cpp"
#include "PolyLine.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
OpenGLscreen scr;
bool _redraw=true;
double animx= 0.0,danimx=0.0;
double animy= 0.0,danimy=0.0;
//---------------------------------------------------------------------------
PointTab pnt; // (x,y,z)
struct _hexagon
{
int ix[6]; // index of 6 points, last point duplicate for pentagon
int a,b; // spherical coordinate
DWORD col; // color
// inline
_hexagon() {}
_hexagon(_hexagon& a) { *this=a; }
~_hexagon() {}
_hexagon* operator = (const _hexagon *a) { *this=*a; return this; }
//_hexagon* operator = (const _hexagon &a) { ...copy... return this; }
};
List<_hexagon> hex;
//---------------------------------------------------------------------------
// https://stackoverflow.com/a/46787885/2521214
//---------------------------------------------------------------------------
void hex_sphere(int N,double R)
{
const double c=cos(60.0*deg);
const double s=sin(60.0*deg);
const double sy= R/(N+N-2);
const double sz=sy/s;
const double sx=sz*c;
const double sz2=0.5*sz;
const int na=5*(N-2);
const int nb= N;
const int b0= N;
double *q,p[3],ang,len,l,l0,ll;
int i,j,n,a,b,ix;
_hexagon h,*ph;
hex.allocate(na*nb);
hex.num=0;
pnt.reset3D(N*N);
b=0; a=0; ix=0;
// generate triangle hex grid
h.col=0x00804000;
for (b=1;b<N-1;b++) // skip first line b=0
for (a=1;a<b;a++) // skip first and last line
{
p[0]=double(a )*(sx+sz);
p[1]=double(b-(a>>1))*(sy*2.0);
p[2]=0.0;
if (int(a&1)!=0) p[1]-=sy;
ix=pnt.add(p[0]+sz2+sx,p[1] ,p[2]); h.ix[0]=ix; // 2 1
ix=pnt.add(p[0]+sz2 ,p[1]+sy,p[2]); h.ix[1]=ix; // 3 0
ix=pnt.add(p[0]-sz2 ,p[1]+sy,p[2]); h.ix[2]=ix; // 4 5
ix=pnt.add(p[0]-sz2-sx,p[1] ,p[2]); h.ix[3]=ix;
ix=pnt.add(p[0]-sz2 ,p[1]-sy,p[2]); h.ix[4]=ix;
ix=pnt.add(p[0]+sz2 ,p[1]-sy,p[2]); h.ix[5]=ix;
h.a=a;
h.b=N-1-b;
hex.add(h);
} n=hex.num; // remember number of hexs for the first triangle
// distort points to match area
for (ix=0;ix<pnt.nn;ix+=3)
{
// point pointer
q=pnt.pnt.dat+ix;
// convert to polar coordinates
ang=atan2(q[1],q[0]);
len=vector_len(q);
// match area of pentagon (72deg) triangle as we got hexagon (60deg) triangle
ang-=60.0*deg; // rotate so center of generated triangle is angle 0deg
while (ang>+60.0*deg) ang-=pi2;
while (ang<-60.0*deg) ang+=pi2;
len*=cos(ang)/cos(30.0*deg); // scale radius so triangle converts to pie
ang*=72.0/60.0; // scale up angle so rotated triangles merge
// convert back to cartesian
q[0]=len*cos(ang);
q[1]=len*sin(ang);
}
// copy and rotate the triangle to cover pentagon
h.col=0x00404000;
for (ang=72.0*deg,a=1;a<5;a++,ang+=72.0*deg)
for (ph=hex.dat,i=0;i<n;i++,ph++)
{
for (j=0;j<6;j++)
{
vector_copy(p,pnt.pnt.dat+ph->ix[j]);
rotate2d(-ang,p[0],p[1]);
h.ix[j]=pnt.add(p[0],p[1],p[2]);
}
h.a=ph->a+(a*(N-2));
h.b=ph->b;
hex.add(h);
}
// compute z
for (q=pnt.pnt.dat,ix=0;ix<pnt.nn;ix+=pnt.dn,q+=pnt.dn)
{
q[2]=0.0;
ang=vector_len(q)*0.5*pi/R;
q[2]=R*cos(ang);
ll=fabs(R*sin(ang)/sqrt((q[0]*q[0])+(q[1]*q[1])));
q[0]*=ll;
q[1]*=ll;
}
// copy and mirror the other half-sphere
n=hex.num;
for (ph=hex.dat,i=0;i<n;i++,ph++)
{
for (j=0;j<6;j++)
{
vector_copy(p,pnt.pnt.dat+ph->ix[j]);
p[2]=-p[2];
h.ix[j]=pnt.add(p[0],p[1],p[2]);
}
h.a= ph->a;
h.b=-ph->b;
hex.add(h);
}
// create index search table
int i0,i1,j0,j1,a0,a1,ii[5];
int **ab=new int*[na];
for (a=0;a<na;a++)
{
ab[a]=new int[nb+nb+1];
for (b=-nb;b<=nb;b++) ab[a][b0+b]=-1;
}
n=hex.num;
for (ph=hex.dat,i=0;i<n;i++,ph++) ab[ph->a][b0+ph->b]=i;
// add join ring
h.col=0x00408000;
for (a=0;a<na;a++)
{
h.a=a;
h.b=0;
a0=a;
a1=a+1; if (a1>=na) a1-=na;
i0=ab[a0][b0+1];
i1=ab[a1][b0+1];
j0=ab[a0][b0-1];
j1=ab[a1][b0-1];
if ((i0>=0)&&(i1>=0))
if ((j0>=0)&&(j1>=0))
{
h.ix[0]=hex[i1].ix[1];
h.ix[1]=hex[i0].ix[0];
h.ix[2]=hex[i0].ix[1];
h.ix[3]=hex[j0].ix[1];
h.ix[4]=hex[j0].ix[0];
h.ix[5]=hex[j1].ix[1];
hex.add(h);
ab[h.a][b0+h.b]=hex.num-1;
}
}
// add 2x5 join lines
h.col=0x00008040;
for (a=0;a<na;a+=N-2)
for (b=1;b<N-3;b++)
{
// +b hemisphere
h.a= a;
h.b=+b;
a0=a-b; if (a0< 0) a0+=na; i0=ab[a0][b0+b+0];
a0--; if (a0< 0) a0+=na; i1=ab[a0][b0+b+1];
a1=a+1; if (a1>=na) a1-=na; j0=ab[a1][b0+b+0];
j1=ab[a1][b0+b+1];
if ((i0>=0)&&(i1>=0))
if ((j0>=0)&&(j1>=0))
{
h.ix[0]=hex[i0].ix[5];
h.ix[1]=hex[i0].ix[4];
h.ix[2]=hex[i1].ix[5];
h.ix[3]=hex[j1].ix[3];
h.ix[4]=hex[j0].ix[4];
h.ix[5]=hex[j0].ix[3];
hex.add(h);
}
// -b hemisphere
h.a= a;
h.b=-b;
a0=a-b; if (a0< 0) a0+=na; i0=ab[a0][b0-b+0];
a0--; if (a0< 0) a0+=na; i1=ab[a0][b0-b-1];
a1=a+1; if (a1>=na) a1-=na; j0=ab[a1][b0-b+0];
j1=ab[a1][b0-b-1];
if ((i0>=0)&&(i1>=0))
if ((j0>=0)&&(j1>=0))
{
h.ix[0]=hex[i0].ix[5];
h.ix[1]=hex[i0].ix[4];
h.ix[2]=hex[i1].ix[5];
h.ix[3]=hex[j1].ix[3];
h.ix[4]=hex[j0].ix[4];
h.ix[5]=hex[j0].ix[3];
hex.add(h);
}
}
// add pentagons at poles
_hexagon h0,h1;
h0.col=0x00000080;
h0.a=0; h0.b=N-1; h1=h0; h1.b=-h1.b;
p[2]=sqrt((R*R)-(sz*sz));
for (ang=0.0,a=0;a<5;a++,ang+=72.0*deg)
{
p[0]=2.0*sz*cos(ang);
p[1]=2.0*sz*sin(ang);
h0.ix[a]=pnt.add(p[0],p[1],+p[2]);
h1.ix[a]=pnt.add(p[0],p[1],-p[2]);
}
h0.ix[5]=h0.ix[4]; hex.add(h0);
h1.ix[5]=h1.ix[4]; hex.add(h1);
// add 5 missing hexagons at poles
h.col=0x00600060;
for (ph=&h0,b=N-3,h.b=N-2,i=0;i<2;i++,b=-b,ph=&h1,h.b=-h.b)
{
a = 1; if (a>=na) a-=na; ii[0]=ab[a][b0+b];
a+=N-2; if (a>=na) a-=na; ii[1]=ab[a][b0+b];
a+=N-2; if (a>=na) a-=na; ii[2]=ab[a][b0+b];
a+=N-2; if (a>=na) a-=na; ii[3]=ab[a][b0+b];
a+=N-2; if (a>=na) a-=na; ii[4]=ab[a][b0+b];
for (j=0;j<5;j++)
{
h.a=((4+j)%5)*(N-2)+1;
h.ix[0]=ph->ix[ (5-j)%5 ];
h.ix[1]=ph->ix[ (6-j)%5 ];
h.ix[2]=hex[ii[(j+4)%5]].ix[4];
h.ix[3]=hex[ii[(j+4)%5]].ix[5];
h.ix[4]=hex[ii[ j ]].ix[3];
h.ix[5]=hex[ii[ j ]].ix[4];
hex.add(h);
}
}
// add 2*5 pentagons and 2*5 missing hexagons at equator
h0.a=0; h0.b=N-1; h1=h0; h1.b=-h1.b;
for (ang=36.0*deg,a=0;a<na;a+=N-2,ang-=72.0*deg)
{
p[0]=R*cos(ang);
p[1]=R*sin(ang);
p[2]=sz;
i0=pnt.add(p[0],p[1],+p[2]);
i1=pnt.add(p[0],p[1],-p[2]);
a0=a-1;if (a0< 0) a0+=na;
a1=a+1;if (a1>=na) a1-=na;
ii[0]=ab[a0][b0-1]; ii[2]=ab[a1][b0-1];
ii[1]=ab[a0][b0+1]; ii[3]=ab[a1][b0+1];
// hexagons
h.col=0x00008080;
h.a=a; h.b=0;
h.ix[0]=hex[ii[0]].ix[0];
h.ix[1]=hex[ii[0]].ix[1];
h.ix[2]=hex[ii[1]].ix[1];
h.ix[3]=hex[ii[1]].ix[0];
h.ix[4]=i0;
h.ix[5]=i1;
hex.add(h);
h.a=a; h.b=0;
h.ix[0]=hex[ii[2]].ix[2];
h.ix[1]=hex[ii[2]].ix[1];
h.ix[2]=hex[ii[3]].ix[1];
h.ix[3]=hex[ii[3]].ix[2];
h.ix[4]=i0;
h.ix[5]=i1;
hex.add(h);
// pentagons
h.col=0x000040A0;
h.a=a; h.b=0;
h.ix[0]=hex[ii[0]].ix[0];
h.ix[1]=hex[ii[0]].ix[5];
h.ix[2]=hex[ii[2]].ix[3];
h.ix[3]=hex[ii[2]].ix[2];
h.ix[4]=i1;
h.ix[5]=i1;
hex.add(h);
h.a=a; h.b=0;
h.ix[0]=hex[ii[1]].ix[0];
h.ix[1]=hex[ii[1]].ix[5];
h.ix[2]=hex[ii[3]].ix[3];
h.ix[3]=hex[ii[3]].ix[2];
h.ix[4]=i0;
h.ix[5]=i0;
hex.add(h);
}
// release index search table
for (a=0;a<na;a++) delete[] ab[a];
delete[] ab;
}
//---------------------------------------------------------------------------
void hex_draw(GLuint style) // draw hex
{
int i,j;
_hexagon *h;
for (h=hex.dat,i=0;i<hex.num;i++,h++)
{
if (style==GL_POLYGON) glColor4ubv((BYTE*)&h->col);
glBegin(style);
for (j=0;j<6;j++) glVertex3dv(pnt.pnt.dat+h->ix[j]);
glEnd();
}
if (0)
if (style==GL_POLYGON)
{
scr.text_init_pixel(0.1,-0.2);
glColor3f(1.0,1.0,1.0);
for (h=hex.dat,i=0;i<hex.num;i++,h++)
if (abs(h->b)<2)
{
double p[3];
vector_ld(p,0.0,0.0,0.0);
for (j=0;j<6;j++)
vector_add(p,p,pnt.pnt.dat+h->ix[j]);
vector_mul(p,p,1.0/6.0);
scr.text(p[0],p[1],p[2],AnsiString().sprintf("%i,%i",h->a,h->b));
}
scr.text_exit_pixel();
}
}
//---------------------------------------------------------------------------
void TMain::draw()
{
scr.cls();
int x,y;
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-5.0);
glRotated(animx,1.0,0.0,0.0);
glRotated(animy,0.0,1.0,0.0);
hex_draw(GL_POLYGON);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-5.0+0.01);
glRotated(animx,1.0,0.0,0.0);
glRotated(animy,0.0,1.0,0.0);
glColor3f(1.0,1.0,1.0);
glLineWidth(2);
hex_draw(GL_LINE_LOOP);
glCirclexy(0.0,0.0,0.0,1.5);
glLineWidth(1);
scr.exe();
scr.rfs();
}
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
{
scr.init(this);
hex_sphere(10,1.5);
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
{
scr.exit();
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
{
_redraw=true;
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
{
scr.resize();
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60,float(scr.xs)/float(scr.ys),0.1,100.0);
_redraw=true;
}
//-----------------------------------------------------------------------
void __fastcall TMain::Timer1Timer(TObject *Sender)
{
animx+=danimx; if (animx>=360.0) animx-=360.0; _redraw=true;
animy+=danimy; if (animy>=360.0) animy-=360.0; _redraw=true;
if (_redraw) { draw(); _redraw=false; }
}
//---------------------------------------------------------------------------
void __fastcall TMain::FormKeyDown(TObject *Sender, Word &Key, TShiftState Shift)
{
Caption=Key;
if (Key==40){ animx+=2.0; _redraw=true; }
if (Key==38){ animx-=2.0; _redraw=true; }
if (Key==39){ animy+=2.0; _redraw=true; }
if (Key==37){ animy-=2.0; _redraw=true; }
}
//---------------------------------------------------------------------------
_
私はそれがインデックスの混乱のビットであることを知っています、そして私は均一なインデックスを作成するのが面倒だったのでワインディングルールは保証されません。各ヘックスのa
インデックスは線形ではなく、それらを使用して2Dマップにマップする場合は、再計算する必要があることに注意してください。中心点の位置の_atan2
_で_x,y
_を使用します。
プレビュー:
それでもいくつかの歪みが存在します。これらは、赤道で接続するために5つの三角形を使用しているために発生します(したがって、接続が保証されます)。これは、円周が_5*R
_ではなく_6.28*R
_であることを意味します。これは、フィールドシミュレーションによってさらに改善することができます。すべてのポイントを取り、それらの距離と球表面にバインドされた値に基づいて、収縮力を追加します。シミュレーションを実行し、振動がしきい値を下回ると、球グリッドが得られます...
別のオプションは、グリッドポイントを再マッピングするための方程式を見つけて(三角形からパイへの変換で私が行ったのと同様に)、より良い結果が得られるでしょう。