Mathematically producing sphere-shaped hexagonal grid
我正在尝试创建一个类似于此形状的,具有12个五边形的六边形,并且尺寸任意。
(图片来源)
唯一的是,我完全不知道需要哪种代码来生成它!
目的是能够在3D空间中获取一个点并将其转换为网格上的位置坐标,反之亦然,并获取网格位置并获得用于绘制网格的相关顶点。
我什至不知道该如何存储网格位置。 3个五边形之间的每个"三角形部分"是否都有自己的2D坐标集?
我很可能会为此使用C#,但是我对使用哪种算法以及如何工作的解释更感兴趣,而不是有人给我一段代码。
您拥有的形状是所谓的" Goldberg多面体"之一,也是测地线多面体。
可以(称为Conway多面体表示法)简洁地编码用于生成此(以及更多)的(相当优雅的)算法。
施工过程很容易逐步进行,您可以点击下面的图片进行实时预览。
您要查找的多面体可以从等面体生成-使用等面体初始化网格。
我们对网格应用"截断"操作(Conway表示法
我们应用"双"运算符(Conway表示法
我们再次应用"截断"操作。此时,配方为
重复执行步骤3和4,直到满意为止。
例如,下面是
这很容易实现。我建议使用一种易于遍历邻域的数据结构,以给出顶点,边等,例如网格的有翼边或半边的数据结构。您只需要为所需的形状实现截断和双重运算符即可。
首先对问题中的图像进行一些分析:由相邻五边形中心跨越的球形三角形似乎是等边的。当五个等边三角形在一个角处相交并覆盖整个球体时,这只能是二十面体引起的构型。因此,有12个五边形和映射到球体的六边形网格的三角形切口的20个补丁。
因此,这是在球体上构造这种六边形网格的方法:
创建六边形网格的三角形切口:固定三角形(我选择了(-0.5,0),(0.5,0),(0,sqrt(3)/ 2))叠加在具有所需分辨率
计算二十面体的角并定义其二十个三角形面(请参见下面的代码)。二十面体的角定义五边形的中心,二十面体的面定义映射的六边形网格的面片。 (二十面体将球体表面最好的规则划分为三角形,即划分为等边的等边三角形。其他这样的划分可以从四面体或八面体得出;然后在三角形的角处将有三角形或正方形,此外,三角形的越来越小将使在平面网格到曲面上的任何映射中不可避免的变形更加明显。因此,选择二十面体作为三角形斑块的基础有助于最大程度地减少六边形的变形。
将六边形网格的三角形切口映射到对应于二十面体面的球形三角形:基于重心坐标的双刀齿就可以解决问题。下图说明了将分辨率为
这是Python代码,用于生成二十面体的角(坐标)和三角形(点索引):
1 2 3 4 5 6 7 8 9 | 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代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # 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) |
[完成重新编辑18.10.2017]
几何存储就在您身上。您可以将其存储在某种网格中,也可以即时生成它。我喜欢存储它。以2张桌子的形式。一个拥有所有顶点(无重复),另一个拥有每个获得的十六进制的6个已使用点索引,以及一些辅助信息(例如球形位置)以简化后期处理。
现在如何生成它:
创建六角形三角形
大小应为球体的半径。不包括转角点,也跳过三角形的最后一条线(在径向和轴向上,因此球体上相邻三角形之间有1个十六进制的间隙),因为在连接三角形线段时会重叠。
将
因此,只需将其转换为极坐标Coordiantes(
复制并旋转三角形以填充5个五边形段
只需旋转第一个三角形的点并存储为新的点即可。
计算
基于此半球形的六边形修整,您可以将2D贴图中的距离转换为弧长,以最大程度地限制变形。
但是,当我尝试使用它(下面的示例)时,六边形有些扭曲,因此深度和缩放比例需要进行一些调整。或后期处理。
复制半球形成一个球
只需复制点/轴,并抵消
添加赤道以及所有缺失的五边形和六边形
您应该使用相邻十六进制的坐标,这样就不会在网格上添加更多的失真和重叠。这里预览:
蓝色是起始三角形。深蓝色是其副本。红色是极五边形。深绿色是赤道,浅绿色是三角形之间的连接线。在淡黄色中,深橙色五边形附近缺少赤道六边形。
这里是简单的C ++ OpenGL示例(由#4中的链接答案制成):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 | //$$---- 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; } } //--------------------------------------------------------------------------- |
我知道这有点索引混乱,而且由于我懒于进行统一索引,因此无法保证缠绕规则。请注意,每个十六进制的
这里预览:
仍然存在一些失真。它们是由以下事实造成的:我们使用5个三角形在赤道处进行连接(因此可以保证连接)。这意味着周长是
另一个选择是找出一些方程式以重新映射网格点(类似于我对三角形到饼形转换所做的操作),这样可以得到更好的结果。