Math 将球体映射到立方体
有一种特殊的方法可以将立方体映射到此处描述的球体: 这不是基本的“规格化点,然后完成”方法,而是提供了一个更均匀的映射 我试着做从球坐标到立方体坐标的逆映射,但一直无法得出工作方程。这是一个相当复杂的方程组,有很多平方根 有数学天才想尝试一下吗 下面是C++代码中的方程:Math 将球体映射到立方体,math,geometry,mapping,Math,Geometry,Mapping,有一种特殊的方法可以将立方体映射到此处描述的球体: 这不是基本的“规格化点,然后完成”方法,而是提供了一个更均匀的映射 我试着做从球坐标到立方体坐标的逆映射,但一直无法得出工作方程。这是一个相当复杂的方程组,有很多平方根 有数学天才想尝试一下吗 下面是C++代码中的方程: sx = x * sqrtf(1.0f - y * y * 0.5f - z * z * 0.5f + y * y * z * z / 3.0f); sy = y * sqrtf(1.0f - z * z * 0.5f -
sx = x * sqrtf(1.0f - y * y * 0.5f - z * z * 0.5f + y * y * z * z / 3.0f);
sy = y * sqrtf(1.0f - z * z * 0.5f - x * x * 0.5f + z * z * x * x / 3.0f);
sz = z * sqrtf(1.0f - x * x * 0.5f - y * y * 0.5f + x * x * y * y / 3.0f);
sx,sy,sz是球坐标,x,y,z是立方体坐标。重新排列后,你可以得到“漂亮”的形状 其中
alpha=sx^2-sy^2
,beta=sx^2-sz^2
和gamma=sz^2-sy^2
。你自己验证一下
现在,我既没有动机也没有时间,但从这一点上讲,解决这个问题非常简单:
(4) a(x) * y^4 + b(x) * y^2 + c(x) = 0
这可以使用y^2
的二次公式来解决。请注意,a(x)、b(x)、c(x)
是x
的一些函数。二次公式得出(4)的两个根,你必须记住z^2
的表达式,只需x^2
(5) a * x^4 + b * x^2 + c = 0
其中a、b、c
不是函数而是常数。使用二次公式解决此问题。对于x^2,y^2,z^2
pair,您总共有2*2=4个可能的解决方案,这意味着您将
对于满足这些方程的可能x,y,z
对,有4*2=8个总解。检查每个x,y,z
对上的条件,并(希望)消除除一个以外的所有条件(否则不存在反向映射)4*pi*r^2
,而立方体的表面积6*d^2=6*(2r)^2=24r^2
,因此直观地说,立方体上有更多的点映射到球体。这意味着一个多对一映射,任何这样的映射都不是内射的,因此也不是双射的(即映射没有逆)。对不起,我认为你运气不好
-----编辑--------------
如果您遵循MO的建议,设置z=1
表示您正在查看平面z=1
中的实心正方形
使用前两个方程求解x,y,wolfram alpha给出结果:
1/2(2 s^2-2 t^2-3 2-3 2-2 2-2 2-2 2-2 2-3 2-3)2-24 t^2 2-2 t^2-2 2 2-2 t^2-2 2 2-2 t^2-2-2 2-2-2(2 s^2 2-2-2 t^2-2-2-2-2 t 2-2 2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-2-3)2 2 2 2 2-3 2 2 2 2 2 2-3 2 2 2 2 2 2 2 2-3)3 2 2 2 2 2 2 2 2 2 2 2-3+3)3)3)3)3)3(2(3)3)2(3)3)2(3)2(2(3)2)2)2)2(3)2(3)2(3)2)2)2)2 2)sqrt(1/2(sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3))/(6 s)
及
y=sqrt(-sqrt((2s^2-2t^2-3)^2-24t^2)-2s^2+2t^2+3)/sqrt(2)
其中,我使用
s=sx
和t=sy
,我将使用u=sz
。然后你可以使用你的第三个等式来计算u=sz
。也就是说,假设要将球体的顶部映射到立方体。然后,对于任何0,有一种方法可以考虑:对于球体中的给定点p,取从原点开始,经过p,并在立方体表面结束的线段。让我是这段的长度。现在你需要做的就是把P乘以L;这相当于将| | P | |从区间[0,1]映射到区间[0,L]。此映射应该是一对一的-球体中的每个点都指向立方体中的唯一点(曲面上的点保持在曲面上)。注意,这是假设一个单位球体和立方体;这个想法应该适用于其他地方,你只需要考虑一些规模因素
我已经忽略了困难的部分(找到片段),但这是一个标准的光线投射问题。有一些链接解释了如何为任意光线与轴对齐的边界框计算此值;您可能可以简化事情,因为光线从原点开始,然后进入单位立方体。如果你需要帮助简化方程式,请告诉我,我会试试看 我想把这归功于gmatt,因为他做了很多工作。我们答案的唯一区别是x的方程式
要从球体到立方体进行反向映射,首先确定球体点投影到的立方体面。这一步很简单-只需找到具有最大长度的球体向量的分量,如下所示:
// map the given unit sphere position to a unit cube position
void cubizePoint(Vector3& position) {
double x,y,z;
x = position.x;
y = position.y;
z = position.z;
double fx, fy, fz;
fx = fabsf(x);
fy = fabsf(y);
fz = fabsf(z);
if (fy >= fx && fy >= fz) {
if (y > 0) {
// top face
position.y = 1.0;
}
else {
// bottom face
position.y = -1.0;
}
}
else if (fx >= fy && fx >= fz) {
if (x > 0) {
// right face
position.x = 1.0;
}
else {
// left face
position.x = -1.0;
}
}
else {
if (z > 0) {
// front face
position.z = 1.0;
}
else {
// back face
position.z = -1.0;
}
}
}
对于每个面-取表示为s和t的剩余立方体向量分量,并使用这些方程对其进行求解,这些方程基于表示为a和b的剩余球体向量分量:
s = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)+2 a^2-2 b^2+3)/sqrt(2)
t = sqrt(-sqrt((2 a^2-2 b^2-3)^2-24 a^2)-2 a^2+2 b^2+3)/sqrt(2)
您应该看到,两个方程中都使用了内平方根,因此只需执行一次该部分
这里是最后一个函数,其中包含了抛出的方程,并检查了0.0和-0.0,以及正确设置立方体组件符号的代码——它应该等于球体组件的符号
void cubizePoint2(Vector3& position)
{
double x,y,z;
x = position.x;
y = position.y;
z = position.z;
double fx, fy, fz;
fx = fabsf(x);
fy = fabsf(y);
fz = fabsf(z);
const double inverseSqrt2 = 0.70710676908493042;
if (fy >= fx && fy >= fz) {
double a2 = x * x * 2.0;
double b2 = z * z * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);
if(x == 0.0 || x == -0.0) {
position.x = 0.0;
}
else {
position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}
if(z == 0.0 || z == -0.0) {
position.z = 0.0;
}
else {
position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}
if(position.x > 1.0) position.x = 1.0;
if(position.z > 1.0) position.z = 1.0;
if(x < 0) position.x = -position.x;
if(z < 0) position.z = -position.z;
if (y > 0) {
// top face
position.y = 1.0;
}
else {
// bottom face
position.y = -1.0;
}
}
else if (fx >= fy && fx >= fz) {
double a2 = y * y * 2.0;
double b2 = z * z * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);
if(y == 0.0 || y == -0.0) {
position.y = 0.0;
}
else {
position.y = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}
if(z == 0.0 || z == -0.0) {
position.z = 0.0;
}
else {
position.z = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}
if(position.y > 1.0) position.y = 1.0;
if(position.z > 1.0) position.z = 1.0;
if(y < 0) position.y = -position.y;
if(z < 0) position.z = -position.z;
if (x > 0) {
// right face
position.x = 1.0;
}
else {
// left face
position.x = -1.0;
}
}
else {
double a2 = x * x * 2.0;
double b2 = y * y * 2.0;
double inner = -a2 + b2 -3;
double innersqrt = -sqrtf((inner * inner) - 12.0 * a2);
if(x == 0.0 || x == -0.0) {
position.x = 0.0;
}
else {
position.x = sqrtf(innersqrt + a2 - b2 + 3.0) * inverseSqrt2;
}
if(y == 0.0 || y == -0.0) {
position.y = 0.0;
}
else {
position.y = sqrtf(innersqrt - a2 + b2 + 3.0) * inverseSqrt2;
}
if(position.x > 1.0) position.x = 1.0;
if(position.y > 1.0) position.y = 1.0;
if(x < 0) position.x = -position.x;
if(y < 0) position.y = -position.y;
if (z > 0) {
// front face
position.z = 1.0;
}
else {
// back face
position.z = -1.0;
}
}
如果你不害怕trig和pi,看起来有一个更干净的解决方案,但不确定它是否更快/可比
确定面后,只需取下其余组件,然后执行以下操作:
u = asin ( x ) / half_pi
v = asin ( y ) / half_pi
这是一个直观的飞跃,但似乎支持它(虽然不是完全相同的主题),所以请纠正我,如果我错了
我懒得贴一张说明原因的插图D此答案包含cube2sphere
和sphere2cube
,不受a=1
的限制。因此立方体的侧面为2a
从-a
到a
,球体的半径为a
我知道这个问题已经问了10年了。不过,如果有人需要,我会给出答案
const float isqrt2 = 0.70710676908493042;
vec3 cubify(const in vec3 s)
{
float xx2 = s.x * s.x * 2.0;
float yy2 = s.y * s.y * 2.0;
vec2 v = vec2(xx2 – yy2, yy2 – xx2);
float ii = v.y – 3.0;
ii *= ii;
float isqrt = -sqrt(ii – 12.0 * xx2) + 3.0;
v = sqrt(v + isqrt);
v *= isqrt2;
return sign(s) * vec3(v, 1.0);
}
vec3 sphere2cube(const in vec3 sphere)
{
vec3 f = abs(sphere);
bool a = f.y >= f.x && f.y >= f.z;
bool b = f.x >= f.z;
return a ? cubify(sphere.xzy).xzy : b ? cubify(sphere.yzx).zxy : cubify(sphere);
}
u = asin ( x ) / half_pi
v = asin ( y ) / half_pi
import math
from random import randint # for testing
def sign_aux(x):
return lambda y: math.copysign(x, y)
sign = sign_aux(1) # no built-in sign function in python, I know...
def cube2sphere(x, y, z):
if (all([x == 0, y == 0, z == 0])):
return 0, 0, 0
def aux(x, y_2, z_2, a, a_2):
return x * math.sqrt(a_2 - y_2/2 - z_2/2 + y_2*z_2/(3*a_2))/a
x_2 = x*x
y_2 = y*y
z_2 = z*z
a = max(abs(x), abs(y), abs(z))
a_2 = a*a
return aux(x, y_2, z_2, a, a_2), aux(y, x_2, z_2, a, a_2), aux(z, x_2, y_2, a, a_2)
def sphere2cube(p, q, r):
if (all([p == 0, q == 0, r == 0])):
return 0, 0, 0
def aux(s, t, radius):
A = 3*radius*radius
R = 2*(s*s - t*t)
S = math.sqrt( max(0, (A+R)*(A+R) - 8*A*s*s) ) # use max 0 for accuraccy error
iot = math.sqrt(2)/2
s_ = sign(s) * iot * math.sqrt(max(0, A + R - S)) # use max 0 for accuraccy error
t_ = sign(t) * iot * math.sqrt(max(0, A - R - S)) # use max 0 for accuraccy error
return s_, t_
norm_p, norm_q, norm_r = abs(p), abs(q), abs(r)
norm_max = max(norm_p, norm_q, norm_r)
radius = math.sqrt(p*p + q*q + r*r)
if (norm_max == norm_p):
y, z = aux(q, r, radius)
x = sign(p) * radius
return x, y, z
if (norm_max == norm_q):
z, x = aux(r, p, radius)
y = sign(q) * radius
return x, y, z
x, y = aux(p, q, radius)
z = sign(r) * radius
return x, y, z
# measuring accuracy
max_mse = 0
for i in range(100000):
x = randint(-20, 20)
y = randint(-20, 20)
z = randint(-20, 20)
p, q, r = cube2sphere(x, y, z)
x_, y_, z_ = sphere2cube(p, q, r)
max_mse = max(max_mse, math.sqrt(((x-x_)**2 + (y-y_)**2 + (z-z_)**2))/3)
print(max_mse)
# 1.1239159602905078e-07
max_mse = 0
for i in range(100000):
p = randint(-20, 20)
q = randint(-20, 20)
r = randint(-20, 20)
x, y, z = sphere2cube(p, q, r)
p_, q_, r_ = cube2sphere(x, y, z)
max_mse = max(max_mse, math.sqrt(((p-p_)**2 + (q-q_)**2 + (r-r_)**2))/3)
print(max_mse)
# 9.832883321715792e-08