斐波那契球面算法非常适合这个问题。它快速且结果一眼看去很容易欺骗人眼。您可以查看使用processing完成的示例,它将随着点的添加而显示结果。这是@gman制作的另一个出色的交互式示例。这里还有一个简单的Python实现。
import math
def fibonacci_sphere(samples=1000):
points = []
phi = math.pi * (math.sqrt(5.) - 1.) # golden angle in radians
for i in range(samples):
y = 1 - (i / float(samples - 1)) * 2 # y goes from 1 to -1
radius = math.sqrt(1 - y * y) # radius at y
theta = phi * i # golden angle increment
x = math.cos(theta) * radius
z = math.sin(theta) * radius
points.append((x, y, z))
return points
1000个样本会给你这个结果:
你说你无法使用黄金螺旋法,这真是可惜,因为它非常好。我想给你一个完整的了解,这样也许你就能明白如何避免“挤在一起”的情况。
下面是一个快速的、非随机的方法来创建一个近似正确的格子;正如上文所述,没有哪个格子会是完美的,但这可能已经足够了。与其他方法(例如 BendWavy.org)相比,它看起来漂亮,并保证在极限情况下间距均匀。
(1 + sqrt(5))/2
,如果以“站在中心,转黄金比例圈,然后朝那个方向发射另一个点”的方式发射点,就会自然地构建一个螺旋形状,随着您增加更多的点数,它仍然拒绝具有明确定义的'条纹',使得点不排成一条线。(注1.)
均匀分布在圆盘上的算法如下:from numpy import pi, cos, sin, sqrt, arange
import matplotlib.pyplot as pp
num_pts = 100
indices = arange(0, num_pts, dtype=float) + 0.5
r = sqrt(indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
pp.scatter(r*cos(theta), r*sin(theta))
pp.show()
它产生的结果看起来像这样(n=100和n=1000):
关键的奇怪之处在于公式r = sqrt(indices / num_pts)
;我是如何得出这个公式的?(注2.)
嗯,我在这里使用平方根,因为我希望它们在圆盘周围具有均匀的面积间隔。也就是说,在大的N的极限情况下,我希望一个小区域R ∈ (r, r + dr),Θ ∈ (θ, θ + dθ) 包含的点数与其面积成比例,即r dr dθ。现在,如果我们假装这里涉及到一个随机变量,那么这就有一个简单的解释,即(R,Θ)的联合概率密度仅为c r,其中c是某个常数。在单位圆上的归一化会强制c = 1/π。
现在让我介绍一个技巧。它来自概率论中被称为逆转换采样的方法:假设你想要生成一个具有概率密度函数f(z)的随机变量,并且你有一个随机变量U ~ Uniform(0,1),就像大多数编程语言中的random()
函数一样。你该如何实现呢?
r = sqrt(random()); theta = 2 * pi * random()
在极坐标下生成圆盘上的随机点。(arange(0, num_pts, dtype=float) + 0.5)/num_pts
而不是 random()
,例如,如果我们要采样10个点,则它们为 r = 0.05, 0.15, 0.25, ... 0.95
。我们均匀采样r以获得等面积间距,并使用向日葵增量来避免输出中可怕的“条”形点。
from numpy import pi, cos, sin, arccos, arange
import mpl_toolkits.mplot3d
import matplotlib.pyplot as pp
num_pts = 1000
indices = arange(0, num_pts, dtype=float) + 0.5
phi = arccos(1 - 2*indices/num_pts)
theta = pi * (1 + 5**0.5) * indices
x, y, z = cos(theta) * sin(phi), sin(theta) * sin(phi), cos(phi);
pp.figure().add_subplot(111, projection='3d').scatter(x, y, z);
pp.show()
Those “bars” are formed by rational approximations to a number, and the best rational approximations to a number come from its continued fraction expression, z + 1/(n_1 + 1/(n_2 + 1/(n_3 + ...)))
where z
is an integer and n_1, n_2, n_3, ...
is either a finite or infinite sequence of positive integers:
def continued_fraction(r):
while r != 0:
n = floor(r)
yield n
r = 1/(r - n)
Since the fraction part 1/(...)
is always between zero and one, a large integer in the continued fraction allows for a particularly good rational approximation: “one divided by something between 100 and 101” is better than “one divided by something between 1 and 2.” The most irrational number is therefore the one which is 1 + 1/(1 + 1/(1 + ...))
and has no particularly good rational approximations; one can solve φ = 1 + 1/φ by multiplying through by φ to get the formula for the golden ratio.
For folks who are not so familiar with NumPy -- all of the functions are “vectorized,” so that sqrt(array)
is the same as what other languages might write map(sqrt, array)
. So this is a component-by-component sqrt
application. The same also holds for division by a scalar or addition with scalars -- those apply to all components in parallel.
The proof is simple once you know that this is the result. If you ask what's the probability that z < Z < z + dz, this is the same as asking what's the probability that z < F-1(U) < z + dz, apply F to all three expressions noting that it is a monotonically increasing function, hence F(z) < U < F(z + dz), expand the right hand side out to find F(z) + f(z) dz, and since U is uniform this probability is just f(z) dz as promised.
n
多得多),然后拒绝球外的点。将剩余的点视为向量,并将其归一化。这些是您的“样本”-使用某种方法(随机、贪婪等)选择其中的 n
个。这个问题有here提供更详细的信息。
node[k]
表示第k个节点。你正在生成一个包含N个点的数组,而node[k]
就是这些点中的第k个(从0到N-1)。如果只有这个让你感到困惑,那么现在你应该可以理解了。k
是一个大小为N的数组,在代码片段开始之前定义,并包含一系列点的列表。)> cat ll.py
from math import asin
nx = 4; ny = 5
for x in range(nx):
lon = 360 * ((x+0.5) / nx)
for y in range(ny):
midpt = (y+0.5) / ny
lat = 180 * asin(2*((y+0.5)/ny-0.5))
print lon,lat
> python2.7 ll.py
45.0 -166.91313924
45.0 -74.0730322921
45.0 0.0
45.0 74.0730322921
45.0 166.91313924
135.0 -166.91313924
135.0 -74.0730322921
135.0 0.0
135.0 74.0730322921
135.0 166.91313924
225.0 -166.91313924
225.0 -74.0730322921
225.0 0.0
225.0 74.0730322921
225.0 166.91313924
315.0 -166.91313924
315.0 -74.0730322921
315.0 0.0
315.0 74.0730322921
315.0 166.91313924
如果你绘制它,你会发现极地附近的垂直间距更大,以便每个点位于大约相同的空间面积中(在极地附近,"水平"空间较少,因此会多一些"垂直"空间)。
这不同于所有点与其邻居大约具有相同的距离(我认为你的链接谈论的是这个),但可能对你想要的内容足够了,并且改进了简单制作均匀的纬度/经度网格。
d = (4-csc^2(\pi n/6(n-2)))^(1/2)
的点。Random r = new Random();
double[] p = { r.nextGaussian(), r.nextGaussian(), r.nextGaussian() };
然后通过将点与原点的距离归一化来将其投影到球体上
double norm = Math.sqrt( (p[0])^2 + (p[1])^2 + (p[2])^2 );
double[] sphereRandomPoint = { p[0]/norm, p[1]/norm, p[2]/norm };
在 n 维度中,高斯分布具有球对称性,因此其投影到球面上是均匀的。
当然,并不能保证均匀生成的点集中任意两点之间的距离都被限制在某个下限范围内,所以你可以使用拒绝抽样来强制实现这些约束条件:最好先生成整个集合,如果需要的话再拒绝整个集合(或者使用“早期拒绝”来拒绝到目前为止已经生成的整个集合;只是不要保留一些点而放弃其他点)。您可以使用上述给出的公式减去一些余量,以确定点之间的最小距离,低于这个距离将拒绝一组点。你将不得不计算n选2个距离,并且拒绝的概率将取决于余量;很难说如何,因此运行模拟以了解相关统计数据的情况。
然后在80处加入N:
from math import cos, sin, pi, sqrt
def GetPointsEquiAngularlyDistancedOnSphere(numberOfPoints=45):
""" each point you get will be of form 'x, y, z'; in cartesian coordinates
eg. the 'l2 distance' from the origion [0., 0., 0.] for each point will be 1.0
------------
converted from: http://web.archive.org/web/20120421191837/http://www.cgafaq.info/wiki/Evenly_distributed_points_on_sphere )
"""
dlong = pi*(3.0-sqrt(5.0)) # ~2.39996323
dz = 2.0/numberOfPoints
long = 0.0
z = 1.0 - dz/2.0
ptsOnSphere =[]
for k in range( 0, numberOfPoints):
r = sqrt(1.0-z*z)
ptNew = (cos(long)*r, sin(long)*r, z)
ptsOnSphere.append( ptNew )
z = z - dz
long = long + dlong
return ptsOnSphere
if __name__ == '__main__':
ptsOnSphere = GetPointsEquiAngularlyDistancedOnSphere( 80)
#toggle True/False to print them
if( True ):
for pt in ptsOnSphere: print( pt)
#toggle True/False to plot them
if(True):
from numpy import *
import pylab as p
import mpl_toolkits.mplot3d.axes3d as p3
fig=p.figure()
ax = p3.Axes3D(fig)
x_s=[];y_s=[]; z_s=[]
for pt in ptsOnSphere:
x_s.append( pt[0]); y_s.append( pt[1]); z_s.append( pt[2])
ax.scatter3D( array( x_s), array( y_s), array( z_s) )
ax.set_xlabel('X'); ax.set_ylabel('Y'); ax.set_zlabel('Z')
p.show()
#end
经过低计数(N为2、5、7、13等)测试,看起来运作良好。
尝试:
function sphere ( N:float,k:int):Vector3 {
var inc = Mathf.PI * (3 - Mathf.Sqrt(5));
var off = 2 / N;
var y = k * off - 1 + (off / 2);
var r = Mathf.Sqrt(1 - y*y);
var phi = k * inc;
return Vector3((Mathf.Cos(phi)*r), y, Mathf.Sin(phi)*r);
};
上述函数应在循环中运行,总共有N个循环和k个当前迭代。
它基于向日葵种子的图案,除了向日葵种子弯曲成半圆顶,再弯曲成球体。
这是一张图片,只不过我把相机放在球体的一半里面。
编辑:这并没有回答OP想问的问题,但如果人们在某种程度上发现它有用,我会留下它。
我们使用概率的乘法规则,结合无穷小。这将导致两行代码来实现您所需的结果:
longitude: φ = uniform([0,2pi))
azimuth: θ = -arcsin(1 - 2*uniform([0,1]))
(定义在以下坐标系中:)
您的语言通常具有均匀随机数原语。例如,在Python中,您可以使用random.random()
返回范围在[0,1)
内的数字。您可以将此数字乘以k以获得[0,k)
范围内的随机数。因此,在Python中,uniform([0,2π))
表示random.random()* 2 * math.pi
。
证明
现在我们不能均匀地分配θ,否则我们会在极点处聚集。我们希望按球面楔形体(此图中的θ实际上是φ)的表面积分配概率:
在赤道处的角位移dφ将导致dφ*r的位移。在任意方位角θ处的位移是多少?嗯,从z轴的半径为r*sin(θ)
, 因此与楔形相交的“纬度”弧长是dφ* r*sin(θ)
。因此,我们通过对从南极到北极的片的面积进行积分来计算采样的累积分布函数(CDF)。
(其中stuff=dφ*r
)
现在我们将尝试获取逆CDF以从中采样:http://en.wikipedia.org/wiki/Inverse_transform_sampling
首先,通过将几乎是CDF的值除以其最大值来进行归一化。这会取消dφ和r的副作用。
azimuthalCDF: cumProb = (sin(θ)+1)/2 from -pi/2 to pi/2
inverseCDF: θ = -sin^(-1)(1 - 2*cumProb)
let x by a random float in range [0,1]
θ = -arcsin(1-2*x)
如果点数较少,您可以运行模拟:
from random import random,randint
r = 10
n = 20
best_closest_d = 0
best_points = []
points = [(r,0,0) for i in range(n)]
for simulation in range(10000):
x = random()*r
y = random()*r
z = r-(x**2+y**2)**0.5
if randint(0,1):
x = -x
if randint(0,1):
y = -y
if randint(0,1):
z = -z
closest_dist = (2*r)**2
closest_index = None
for i in range(n):
for j in range(n):
if i==j:
continue
p1,p2 = points[i],points[j]
x1,y1,z1 = p1
x2,y2,z2 = p2
d = (x1-x2)**2+(y1-y2)**2+(z1-z2)**2
if d < closest_dist:
closest_dist = d
closest_index = i
if simulation % 100 == 0:
print simulation,closest_dist
if closest_dist > best_closest_d:
best_closest_d = closest_dist
best_points = points[:]
points[closest_index]=(x,y,z)
print best_points
>>> best_points
[(9.921692138442777, -9.930808529773849, 4.037839326088124),
(5.141893371460546, 1.7274947332807744, -4.575674650522637),
(-4.917695758662436, -1.090127967097737, -4.9629263893193745),
(3.6164803265540666, 7.004158551438312, -2.1172868271109184),
(-9.550655088997003, -9.580386054762917, 3.5277052594769422),
(-0.062238110294250415, 6.803105171979587, 3.1966101417463655),
(-9.600996012203195, 9.488067284474834, -3.498242301168819),
(-8.601522086624803, 4.519484132245867, -0.2834204048792728),
(-1.1198210500791472, -2.2916581379035694, 7.44937337008726),
(7.981831370440529, 8.539378431788634, 1.6889099589074377),
(0.513546008372332, -2.974333486904779, -6.981657873262494),
(-4.13615438946178, -6.707488383678717, 2.1197605651446807),
(2.2859494919024326, -8.14336582650039, 1.5418694699275672),
(-7.241410895247996, 9.907335206038226, 2.271647103735541),
(-9.433349952523232, -7.999106443463781, -2.3682575660694347),
(3.704772125650199, 1.0526567864085812, 6.148581714099761),
(-3.5710511242327048, 5.512552040316693, -3.4318468250897647),
(-7.483466337225052, -1.506434920354559, 2.36641535124918),
(7.73363824231576, -8.460241422163824, -1.4623228616326003),
(10, 0, 0)]