Mesh Icosahedral Equal Area Points

在平面上均匀分布的点构成正三角形网格,在平面上划出正二十面体的表面。若将正二十面体叠好,则原均匀分布于平面上的点会均匀分布在正二十面体表面。将正二十面体的表面保面积地映到球面上,则得到球面上近似均匀分布的点。

详见 https://doi.org/10.48550/arXiv.1607.04590

使用python实现的代码如下:

def EqualAreaIcosahedral(m, n): """ D.P. Hardin, T. Michaels and E.B. Saff A Comparison of Popular Point Configurations on S2. October 25, 2016 Parameters: m - integer n - integer Returns: a list of 3-element list, every 3-element list means the 3D cartesian coordinate of a point """ import math def rotate(r, n, phi): # Rodrigues Formula # r is a 3D vector, n is a unit vector describing an axis of rotation # about which r rotates by an angle phi according to the LEFT hand rule # return the rotated vector cosphi = math.cos(phi) sinphi = math.sin(phi) nr = n[0]*r[0] + n[1]*r[1] + n[2]*r[2] x = r[0]*cosphi + n[0]*nr*(1-cosphi) + (r[1]*n[2]-r[2]*n[1])*sinphi y = r[1]*cosphi + n[1]*nr*(1-cosphi) + (r[2]*n[0]-r[0]*n[2])*sinphi z = r[2]*cosphi + n[2]*nr*(1-cosphi) + (r[0]*n[1]-r[1]*n[0])*sinphi # print(x*x+y*y+z*z) return (x,y,z) s_3 = math.sqrt(3) M = max(m, n) N = min(m, n) assert N >= 0 and M > 0 # the length of icosahedral's edge L1 = 2*math.sqrt(math.pi)/math.sqrt(math.sqrt(75)) L2 = M*M+N*N+M*N # the greatest common divisor of M & N for i in range(N): if M%(i+1) == 0: gcd = i+1 if N == 0: gcd = M # whether there is a point at the center of icosahedral's face flag_center = (M-N)%3 == 0 # points on the first 2/3 angular bisectors L60 = [[], [], []] # cartesian coordinates of points p1 = [] for i in range(-N, M+1): for j in range(M+N+1): # i & j - skew coordinates of points # i & j in diamond contain the equilateral triangle if flag_center and 3*i == (M-N) and 3*j == (M+2*N): continue x = i + j/2 y = j*s_3/2 # 3 angular bisector if j>0 and i*(M+2*N) == (M-N)*j and M+2*N > 3*j: L60[0].append((x,y)) continue if i<M and (2*M+N)*(j-N) == (M-N)*(M-i) and 2*M+N > 3*(M-i): L60[1].append((x,y)) continue if j<M+N and (M+2*N)*(M+N-j) == (i+N)*(2*M+N) and (M+2*N) > 3*(i+N): L60[2].append((x,y)) continue # check whether points in the equilateral triangle x1 = (M+N)*x*s_3 - (M-N)*y x2 = N*x*s_3 - (2*M+N)*y x3 = M*x*s_3 + (M+2*N)*y if x1 > 0 and x2 < 0 and x3 < L2*s_3: p1.append((x,y)) # cartesian coordinates of points, while rotate the equilateral triangle so that # its edge coincide with the x-axis and with length 1 p2 = [] for p in p1: x = p[0]*(M+N/2) + p[1]*N*s_3/2 y = p[1]*(M+N/2) - p[0]*s_3*N/2 p2.append((x/L2, y/L2)) # h & w in reference paper hw = [[], [], []] for p in p2: if p[1] < p[0]/s_3 and p[0] + p[1]*s_3 <= 1: h = s_3/6 - p[1] w = p[0] - 0.5 hw[0].append((h*L1,w*L1)) elif p[0] < 0.5: h = (p[1] - p[0]*s_3 + 1/s_3)/2 w = (1 - p[0] - p[1]*s_3)/2 hw[1].append((h*L1,w*L1)) else: h = (p[0]*s_3 + p[1])/2 - 1/s_3 w = (p[1]*s_3 - p[0])/2 hw[2].append((h*L1,w*L1)) # lambda, psi in reference paper lp = [[], [], []] for i in range(3): for p in hw[i]: ps = p[0]*p[0]*s_3/2 + math.pi/6 la = math.atan(math.sin(p[0]*p[1]/2)/(math.cos(p[0]*p[1]/2)-2*math.cos(ps)/s_3)) lp[i].append((la,ps)) for p in L60[0]: y = (p[1]*(M+N/2) - p[0]*s_3*N/2) / L2 h = (s_3/6 - y) * L1 ps = h*h*s_3/2 + math.pi/6 lp[i].append((math.pi/3,ps)) # theta, phi - spherical coordinates of points tp = [] for i in range(3): for p in lp[0]: cosPhi = math.cos(p[1])*math.sin(p[0])*2/s_3 # print(math.cos(p[0]), math.pi/p[0], math.pi/p[1], math.sqrt(1-cosPhi**2), # math.cos(p[0])*math.cos(p[1])*2/math.sqrt(3*(1-cosPhi**2))) th = math.acos(math.cos(p[0])*math.cos(p[1])*2/math.sqrt(3*(1-cosPhi**2))) tp.append((th, p[0]+i*math.pi*2/3)) # 3D cartesian coordinates of vertex and points on the 1st ~ 20th face xyz = [[] for i in range(21)] # points on the first face for p in tp: x = math.sin(p[0]) * math.cos(p[1]) y = math.sin(p[0]) * math.sin(p[1]) z = math.cos(p[0]) xyz[1].append((x,y,z)) # north pole if flag_center: xyz[1].append((0,0,1)) # rotate and reflex to obtain the else ponits cosc = 1/(s_3*math.tan(math.pi/5)) sinc = math.sqrt(1-cosc**2) for p in xyz[1]: for i in range(4): xyz[i+2].append(rotate(p,(-sinc,0,cosc),math.pi*(i+1)*0.4)) xyz[6].append(rotate(p,(sinc/2,s_3*sinc/2,cosc),-math.pi*0.4)) xyz[11].append(rotate(p,(sinc/2,s_3*sinc/2,cosc),-math.pi*0.8)) xyz[16].append((-p[0],-p[1],-p[2])) for p6, p11, p18 in zip(xyz[6], xyz[11], xyz[16]): for i in range(4): xyz[7+i].append(rotate(p6,(-sinc,0,cosc),math.pi*(i+1)*0.4)) xyz[12+i].append(rotate(p11,(-sinc,0,cosc),math.pi*(i+1)*0.4)) xyz[17+i].append(rotate(p18,(-sinc,0,cosc),math.pi*(i+1)*0.4)) # vertex xyz[0] += [(-sinc,0,cosc), (sinc/2,s_3*sinc/2,cosc)] for a in [math.pi*(i+1)*0.4 for i in range(4)]: xyz[0].append(rotate((sinc/2,s_3*sinc/2,cosc),(-sinc,0,cosc),a)) for p in xyz[0][:6]: xyz[0].append((-p[0],-p[1],-p[2])) # all ponits xyz.append([]) for ps in xyz[:-1]: xyz[-1] += ps # if there are points on edges if gcd > 1: xyze = [] hwe = [(s_3*L1/6,(-0.5+(i+1)/gcd)*L1) for i in range(gcd-1)] lpe = [] for p in hwe: ps = p[0]*p[0]*s_3/2 + math.pi/6 la = math.atan(math.sin(p[0]*p[1]/2)/(math.cos(p[0]*p[1]/2)-2*math.cos(ps)/s_3)) lpe.append((la,ps)) tpe = [] for p in lpe: cosPhi = math.cos(p[1])*math.sin(p[0])*2/s_3 th = math.acos(math.cos(p[0])*math.cos(p[1])*2/math.sqrt(3*(1-cosPhi**2))) tpe.append((th, p[0])) tpe.append((th, p[0]+math.pi*4/3)) for p in tpe: x = math.sin(p[0]) * math.cos(p[1]) y = math.sin(p[0]) * math.sin(p[1]) z = math.cos(p[0]) xyze.append((x,y,z)) xyze.append((-x,-y,-z)) xyze.append(rotate((x,y,z),(sinc/2,s_3*sinc/2,cosc),-math.pi*0.4)) for p in xyze[:3*len(tpe)]: for a in [math.pi*(i+1)*0.4 for i in range(4)]: xyze.append(rotate(p,(-sinc,0,cosc),a)) xyz[-1] += xyze return xyz[-1]