Отображение сферы в куб - PullRequest
21 голосов
/ 17 апреля 2010

Существует особый способ отображения куба на сферу, описанный здесь: 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 - координаты куба.

Ответы [ 4 ]

13 голосов
/ 23 апреля 2010

Я хочу отдать должное gmatt, потому что он проделал большую работу. Единственная разница в наших ответах - это уравнение для х.

Чтобы выполнить обратное отображение сферы на куб, сначала определите грань куба, на которую проецируется точка сферы. Этот шаг прост - просто найдите компонент вектора сферы с наибольшей длиной, например, так:

// 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);
}
7 голосов
/ 17 апреля 2010

После некоторой перестановки вы можете получить «красивые» формы

(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^2, beta = sx^2 - sz^2 и gamma = 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. Квадратичная формула дает 2 корня для (4), которые вы должны иметь в виду.

  2. Используя (1), (2), (4) вычислить выражение для z^2 в терминах только x^2.

  3. Используя (3) выведите полиномиальное корневое уравнение вида:

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

    где a,b,c не функции, а константы. Решите это, используя квадратную формулу. Всего у вас будет 2 * 2 = 4 возможных решения для пары x^2,y^2,z^2, что означает, что вы будете иметь 4 * 2 = 8 полных решений для возможных x,y,z пар, удовлетворяющих этим уравнениям. Проверьте условия для каждой пары x,y,z и (надеюсь) исключите все, кроме одного (в противном случае обратного отображения не существует.)

Удачи.

PS. Вполне возможно, что обратного отображения не существует, подумайте о геометрии: сфера имеет площадь поверхности 4*pi*r^2, а куб имеет площадь поверхности 6*d^2=6*(2r)^2=24r^2, так что интуитивно у вас есть еще много точек на кубе, которые отображаются на сфера. Это означает сопоставление много-к-одному, и любое такое сопоставление не является инъективным и, следовательно, не является биективным (т. Е. Отображение не имеет обратного.) Извините, но я думаю, что вам не повезло.

----- редактировать --------------

если вы следуете советам МО, установка z=1 означает, что вы смотрите на сплошной квадрат в плоскости z=1.

Используйте первые два уравнения для решения для x, y, вольфрам альфа дает результат:

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=sx и t=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.

Обратите внимание, что это только отобразит верхнюю часть куба на only верхнюю плоскость куба z=1. Вам придется сделать это для всех 6 сторон (x=1, y=1, z=0 ... и т. Д.). Я предлагаю использовать вольфрам альфа для решения полученных в результате уравнений, которые вы получаете для каждого подслоя, потому что они будут такими же уродливыми или уродливыми, как и приведенные выше.

1 голос
/ 17 апреля 2010

Вот один способ думать об этом: для данной точки P в сфере возьмите отрезок, который начинается в начале координат, проходит через P и заканчивается на поверхности куба. Пусть L - длина этого отрезка. Теперь все, что вам нужно сделать, это умножить P на L; это эквивалентно отображению || P || от интервала [0, 1] до интервала [0, L]. Это отображение должно быть взаимно-однозначным - каждая точка в сфере переходит в уникальную точку в кубе (а точки на поверхности остаются на поверхности). Обратите внимание, что это предполагает единичную сферу и куб; идея должна быть в другом месте, у вас будет только несколько масштабных факторов.

Я затушевал сложную часть (поиск сегмента), но это стандартная проблема с радиопередачей. Здесь есть несколько ссылок здесь , которые объясняют, как вычислить это для произвольного ограничивающего прямоугольника в зависимости от оси; Вы, вероятно, можете упростить вещи, так как ваш луч начинается в начале координат и переходит в единичный куб. Если вам нужна помощь в упрощении уравнений, дайте мне знать, и я попробую.

0 голосов
/ 27 июня 2017

Похоже, что есть более чистое решение, если вы не боитесь триггера и пи, но не уверены, что оно быстрее / сопоставимо.

Просто возьмите оставшиеся компоненты после определения лица и выполните:

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

Это интуитивный скачок, но этот , кажется, поддерживает его (хотя и не совсем по той же теме), поэтому, пожалуйста, исправьте меня, если я ошибаюсь.

Мне лень публиковать иллюстрацию, объясняющую почему. : D

...