Нарисуйте сферу с использованием 3D пикселей (вокселей) - PullRequest
10 голосов
/ 31 января 2012

Можете ли вы предложить алгоритм, который может рисовать сферу в трехмерном пространстве, используя только базовый plot(x,y,z) примитив (который будет рисовать один воксель)?

Я надеялся на что-то похожее на Брезенхемаалгоритм круга , но для 3D вместо 2D.

К вашему сведению, я работаю над аппаратным проектом, который представляет собой 3D-дисплей с низким разрешением, использующий 3-мерную матрицу светодиодов, поэтому мне нужнона самом деле нарисуйте сферу, а не просто 2D-проекцию (т.е. круг).

Проект очень похож на этот:

3D LED cube

... или посмотрите егов действии здесь .

Одна возможность, которую я имею в виду, заключается в следующем:

  • вычисление координат Y полюсов (с учетом радиуса) (для сферыпо центру в начале координат это были бы -r и +r)
  • среза сферы: для каждой горизонтальной плоскости p i между этими координатами вычислите радиус кругаполучается при пересечении указанной плоскости со сферой => r i .
  • нарисуйте действительный круг радиуса r i на плоскости p i , используя алгоритм Брезенхэма.

FWIW, я использую .NET микропроцессор микропроцессора , поэтому программирование на C #, но мне не нужны ответы, чтобы быть в C #.

Ответы [ 4 ]

8 голосов
/ 31 января 2012

Простой метод грубой силы состоит в том, чтобы перебрать каждый воксель в сетке и вычислить его расстояние от центра сферы. Затем раскрасьте воксель, если его расстояние меньше радиуса сферы. Вы можете сохранить много инструкций, убрав квадратный корень и сравнив произведение точек с радиусом в квадрате.

Довольно далеко от оптимального, конечно. Но на сетке 8x8x8, как показано, вам нужно будет выполнить эту операцию 512 раз на сферу. Если центр сферы находится на сетке, а его радиус является целым числом, вам нужна только целочисленная математика. Точечный продукт - 3 умножения и 2 сложения. Умножения медленные; скажем, они принимают 4 инструкции каждый. Плюс тебе нужно сравнение. Добавьте в загрузки и магазины, скажем, это стоит 20 инструкций за воксель. Это 10240 инструкций на сферу.

Arduino, работающий на частоте 16 МГц, может выдавать 1562 шара в секунду. Если вы не выполняете тонны других математических операций и операций ввода-вывода, этот алгоритм должен быть достаточно хорошим.

4 голосов
/ 05 декабря 2013

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

Я думаю, что на основании комментария и ответа @Nick Udall здесь использование алгоритма окружности для определения радиуса вашего горизонтального среза будет работать с модификацией, которую я предложил в комментарии к его ответу. Алгоритм окружности должен быть модифицирован, чтобы принимать в качестве входных данных начальную ошибку, а также рисовать дополнительные точки для полярных октантов.

  • Нарисуйте стандартные точки алгоритма окружности в y0 + y1 и y0 - y1: x0 +/- x, z0 +/- z, y0 +/- y1, x0 +/- z, z0 +/- x, y0 +/- y1, всего 16 баллов. Это составляет основную часть вертикали сферы.
  • Дополнительно нарисуйте точки x0 +/- y1, z0 +/- x, y0 +/- z и x0 +/- x, z0 +/- y1, y0 +/- z, всего 16 точек, которые сформируют полярные шапки для сферы.

Путем передачи ошибки внешнего алгоритма в алгоритм окружности, это позволит под-воксельной настройке круга каждого слоя. Не передавая ошибку во внутренний алгоритм, экватор круга будет приближен к цилиндру, и каждая приближенная грань сферы по осям x, y и z будет образовывать квадрат. С учетом ошибки каждая грань с достаточно большим радиусом будет аппроксимирована в виде закрашенного круга.


Следующий код изменен из алгоритма окружности средней точки Википедии . Алгоритм DrawCircle имеет измененную номенклатуру для работы в плоскости xz, добавления третьей начальной точки y0, смещения по y y1 и начальной ошибки error0. DrawSphere был изменен из той же функции, чтобы взять третью начальную точку y0 и вызывает DrawCircle вместо DrawPixel

public static void DrawCircle(int x0, int y0, int z0, int y1, int radius, int error0)
{
  int x = radius, z = 0;
  int radiusError = error0; // Initial error state passed in, NOT 1-x

  while(x >= z)
  {
    // draw the 32 points here.
    z++;
    if(radiusError<0)
    {
      radiusError+=2*z+1;
    }
    else
    {
      x--;
      radiusError+=2*(z-x+1);
    }
  }
}

public static void DrawSphere(int x0, int y0, int z0, int radius)
{
  int x = radius, y = 0;
  int radiusError = 1-x;

  while(x >= y)
  {
    // pass in base point (x0,y0,z0), this algorithm's y as y1,
    // this algorithm's x as the radius, and pass along radius error.
    DrawCircle(x0, y0, z0, y, x, radiusError);
    y++;
    if(radiusError<0)
    {
      radiusError+=2*y+1;
    }
    else
    {
      x--;
      radiusError+=2*(y-x+1);
    }
  }
}

Для сферы радиуса 4 (которая на самом деле требует 9x9x9), это будет запускать три итерации процедуры DrawCircle, при этом первый рисует типичный круг радиуса 4 (три шага), а второй рисует круг радиуса 4 с начальная ошибка 0 (также три шага), а затем третий рисунок окружности радиуса 3 с начальной ошибкой 0 (также три шага). В итоге получается девять вычисленных точек, каждая из которых рисует 32 пикселя. Это составляет 32 (точки на окружность) x 3 (операции сложения или вычитания на точку) + 6 (операции сложения, вычитания, сдвига на одну итерацию) = 102 операции сложения, вычитания или смещения на вычисленную точку. В этом примере это 3 балла за каждый круг = 306 операций на слой. Алгоритм радиуса также добавляет 6 операций на слой и повторяет 3 раза, поэтому 306 + 6 * 3 = 936 базовые арифметические операции для примера радиуса 4. Здесь стоит заплатить несколько пикселей без дополнительных проверок условий (т. Е. X = 0, y = 0 или z = 0), поэтому, если ваш ввод / вывод медленный, вам лучше добавить проверки условий. Предполагая, что все светодиоды были очищены при запуске, пример круга установит 288 светодиодов, тогда как на самом деле будет гораздо меньше светодиодов, которые фактически загорятся из-за повторяющихся наборов.

Похоже, что это будет работать лучше, чем метод bruteforce для всех сфер, которые будут помещаться в сетку 8x8x8, но метод bruteforce будет иметь согласованное время независимо от радиуса, в то время как этот метод будет замедляться при рисовании сфер большого радиуса, где только часть будет отображаться. Однако при увеличении разрешения у экрана дисплея время выполнения алгоритма будет оставаться неизменным, в то время как перебор будет увеличиваться.

4 голосов
/ 31 января 2012

Предполагая, что у вас уже есть функция построения, как вы сказали:

    public static void DrawSphere(double r, int lats, int longs)
    {
        int i, j;
        for (i = 0; i <= lats; i++)
        {
            double lat0 = Math.PI * (-0.5 + (double)(i - 1) / lats);
            double z0 = Math.Sin(lat0) * r;
            double zr0 = Math.Cos(lat0) * r;

            double lat1 = Math.PI * (-0.5 + (double)i / lats);
            double z1 = Math.Sin(lat1) * r;
            double zr1 = Math.Cos(lat1) * r;

            for (j = 0; j <= longs; j++)
            {
                double lng = 2 * Math.PI * (double)(j - 1) / longs;
                double x = Math.Cos(lng);
                double y = Math.Sin(lng);

                plot(x * zr0, y * zr0, z0);
                plot(x * zr1, y * zr1, z1);
            }
        }
    }

Эта функция должна отображать сферу в начале координат с указанным разрешением по широте и долготе (судя по вашему кубу, вы, вероятно, хотите что-то около 40или 50 как грубое предположение).Однако этот алгоритм не «заполняет» сферу, поэтому он только обеспечивает контур, но игра с радиусом должна позволить вам заполнить внутреннюю часть, вероятно, с уменьшением разрешения латов и длин по пути.

1 голос
/ 31 января 2012

Только что нашел старые вопросы и ответы о создании Sphere Mesh, но верхний ответ на самом деле дает вам небольшой кусок псевдокода для генерации ваших X, Y и Z:

(x, y, z) = (sin(Pi * m/M) cos(2Pi * n/N), sin(Pi * m/M) sin(2Pi * n/N), cos(Pi * m/M))

Проверьте это Q & A для деталей :) процедурно сгенерируйте сферу меш

...