将球体映射到立方体

时间:2010-04-17 01:46:59

标签: math geometry mapping

有一种将立方体映射到此处描述的球体的特殊方法: http://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html

这不是你的基本“正常化点和你已经完成”的方法,并提供更均匀间隔的映射。

我试图做出从球体坐标到立方体坐标的映射的逆过程,并且无法得出工作方程式。这是一个相当复杂的方程组,有很多平方根。

任何数学天才都希望对此有所了解?

这是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 - 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是立方体坐标。

5 个答案:

答案 0 :(得分:13)

我想为此付出代价,因为他完成了很多工作。我们答案的唯一区别是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;
        }
    }

所以,这个解决方案并不像立方体到球体的映射那么漂亮,但是它完成了工作!

欢迎任何提高上述代码效率或阅读能力的建议!

---编辑--- 我应该提一下,我已经测试了这个,到目前为止,在我的测试中代码看起来是正确的,结果至少精确到小数点后7位。那是从我使用花车的时候开始,现在双打可能更准确。

---编辑--- 这是丹尼尔优化的glsl片段着色器版本,表明它不必是如此大的可怕功能。 Daniel使用它来过滤立方体贴图上的采样!好主意!

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);
}

答案 1 :(得分:7)

经过一些重新安排后,您可以获得“漂亮”的表格

(1)   1/2 z^2 = (alpha) / ( y^2 - x^2) + 1
(2)   1/2 y^2 = (beta)  / ( z^2 - x^2) + 1
(3)   1/2 x^2 = (gamma) / ( y^2 - z^2) + 1

其中alpha = sx^2-sy^2beta = sx^2 - sz^2gamma = sz^2 - sy^2。自己验证一下。

现在我既没有动力,也没有时间,但从这一点开始,它很容易解决:

  1. 将(1)代入(2)。重新排列(2),直到得到形式

    的多项式(根)方程
    (4)    a(x) * y^4  + b(x) * y^2 + c(x) = 0
    

    这可以使用y^2的二次方程式求解。请注意a(x),b(x),c(x)x的一些功能。二次公式为(4)产生2个根,你必须牢记这一点。

  2. 使用(1),(2),(4)仅用z^2计算x^2的表达式。

  3. 使用(3)写出形式的多项式根方程:

    (5)    a * x^4  + b * x^2 + c = 0
    

    其中a,b,c不是函数而是常量。使用二次公式解决这个问题。对于x^2,y^2,z^2对,您将有2 * 2 = 4种可能的解决方案 对于满足这些方程的可能x,y,z对,有4 * 2 = 8个总解。检查每个x,y,z对上的条件,并(希望)消除除一个之外的所有条件(否则不存在逆映射。)

  4. 祝你好运。

    PS。很可能是逆映射不存在,想想几何:球体有表面区域4*pi*r^2而立方体有表面区域6*d^2=6*(2r)^2=24r^2所以直观地你在立方体上有更多的点得到映射到球体。这意味着多对一的映射,任何这样的映射都不是单射的,因此不是双射的(即映射没有逆。)抱歉,但我认为你运气不好。

    -----编辑--------------

    如果您遵循MO的建议,设置z=1表示您正在查看平面z=1中的实心方块。

    使用前两个方程求解x,y,wolfram alpha给出结果:

    x = (sqrt(6) s^2 sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3)-sqrt(6) t^2 sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3)-sqrt(3/2) sqrt((2 s^2-2 t^2-3)^2-24 t^2) sqrt(1/2 (sqrt((2 s^2-2 t^2-3)^2-24 t^2)+2 s^2-2 t^2-3)+3)+3 sqrt(3/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((2 s^2-2 t^2-3)^2-24 t^2)-2 s^2+2 t^2+3)/sqrt(2)

    上面我使用s=sxt=sy,我会使用u=sz。然后,您可以使用u=sz的第三个等式。也就是说,您可以将球体的顶部部分映射到立方体。然后对于任何0 <= s,t <= 1(其中s,t位于球体的坐标系中),则元组(s,t,u)映射到(x,y,1)(此处x,y位于立方体坐标系中。)唯一剩下的就是让你弄明白u是什么。您可以使用s,t解析x,y,然后使用x,y来解决u,从而解决这个问题。

    请注意,这只会将多维数据集的顶部映射到多维数据集z=1的顶部平面。您必须为所有6个边(x=1y=1z=0 ...等)执行此操作。我建议使用wolfram alpha来解决每个子案例得到的公式,因为它们会像上面那些一样丑陋或丑陋。

答案 2 :(得分:1)

这是你可以考虑的一种方式:对于球体中的给定点P,取出从原点开始的段,穿过P,并在立方体的表面结束。设L是该段的长度。现在你需要做的就是将P乘以L;这相当于映射|| P ||从区间[0,1]到区间[0,L]。此映射应该是一对一的 - 球体中的每个点都会到达立方体中的唯一点(并且表面上的点保留在曲面上)。请注意,这是假设一个单位球体和立方体;这个想法应该在其他地方举行,你只会涉及一些比例因素。

我已经掩盖了困难部分(找到片段),但这是一个标准的光线投射问题。有一些链接here解释了如何为任意光线与轴对齐边界框计算此值;你可以简化一些事情,因为你的光线从原点开始并进入单位立方体。如果您需要帮助来简化方程式,请告诉我,我会对它进行一次尝试。

答案 3 :(得分:1)

此答案包含cube2spheresphere2cube,不受a = 1的限制。因此,立方体的侧面2a-aa,球体的半径为a

我知道问这个问题已经十年了。不过,如果有人需要,我会给出答案。该实现使用Python,

我将(x, y, z)用于立方体坐标,将(p, q, r)用于球坐标和相关的下划线变量(x_, y_, z_),这意味着它们是使用反函数生成的。

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

我还绘制了一些点以直观地检查功能,这些是结果。

enter image description here

enter image description here

答案 4 :(得分:0)

如果你不害怕触发和pi,看起来有一个更清洁的解决方案,不确定它是否更快/可比。

确定面部后,只需取出剩余的组件,然后执行:

u = asin ( x ) / half_pi
v = asin ( y ) / half_pi

这是一个直观的飞跃,但this似乎支持它(虽然不是完全相同的主题),所以如果我错了请纠正我。

我懒得发布解释原因的插图。 :d