Как я могу полигонизировать бул [,,]

Если кому-то все равно, я использую WPF....

Начало истории:

Я получил серию полутоновых изображений (кусочки модели). Пользователь вводит "диапазон" значений оттенков серого для построения 3D-модели. Таким образом я создал 3D bool массив для облегчения построения 3D-модели. Этот массив представляет собой блок пикселей, указывающий, должен ли каждый пиксель быть построен / сгенерирован / заполнен или нет.


Галстук:

Используя bool[,,] Я хочу получить List<Point3D[]> где каждый Point3D[] имеет длину 3 и представляет собой треугольник в трехмерном пространстве.


Дополнительная информация:

Сгенерированная модель будет напечатана в 3D. в bool[,,], true указывает на наличие материи, в то время как false указывает на отсутствие материи. Мне нужно избегать кубических моделей, где каждый пиксель заменяется кубом (это было бы бесполезно в соответствии с моей целью). Модель должна быть максимально гладкой и максимально точной.


Что я пытался сделать:

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

2- Я пару дней продолжал бороться, пытаясь создать свой собственный алгоритм, но частично потерпел неудачу. (Это действительно сложно. Если вы хотите узнать больше об этом, пожалуйста, сообщите)


Некоторые ожидаемые решения, которые я не знаю, как реализовать:

1- Измените алгоритм Марширующих кубов, чтобы он работал, используя bool[,,]

2- Измените алгоритм Марширующих кубов, чтобы он работал, используя работу с использованием "диапазона" isolevel ценности

3. Показывает, как реализовать другой алгоритм, который подходит для моих целей (может быть, алгоритм Ray-Casting) в WPF.

4- Запрос источника алгоритма, который я попытался реализовать, затем показал мне, как его решить. (Это было сделано, чтобы полигонизировать bool[,,] на первом месте)

5- Другое магическое решение.

Заранее спасибо.


РЕДАКТИРОВАТЬ:

Говоря

Используя bool[,,] Я хочу получить List<Point3D[]> где каждый Point3D[] имеет длину 3 и представляет собой треугольник в трехмерном пространстве.

Я имею в виду, что я хочу получить группу треугольников. Каждый треугольник должен быть представлен как 3 Point3D s. Если вы не знаете, что такое Point3D это struct содержит 3 двойных (X,Y и Z), которые используются для представления позиции в трехмерном пространстве.

Проблема с алгоритмом марширующих кубов состоит в том, что он немного неопределенный. Я имею в виду, что вы понимаете под этим??

cubeindex = 0; 
if (grid.val[0] < isolevel) cubeindex |= 1; 
if (grid.val[1] < isolevel) cubeindex |= 2; 
if (grid.val[2] < isolevel) cubeindex |= 4; 
if (grid.val[3] < isolevel) cubeindex |= 8; 
if (grid.val[4] < isolevel) cubeindex |= 16; 
if (grid.val[5] < isolevel) cubeindex |= 32; 
if (grid.val[6] < isolevel) cubeindex |= 64; 
if (grid.val[7] < isolevel) cubeindex |= 128; 

Таким образом, я просто не знаю, с чего начать его модификацию.


Другой РЕДАКТИРОВАТЬ:

После некоторых экспериментов с алгоритмом марширующих кубов, я заметил, что полигонизация bool[,,] не приведет к созданию гладкой трехмерной модели, которую я с нетерпением жду, поэтому мое первое ожидаемое решение и мое четвертое ожидаемое решение оба вне игры. После долгих исследований я узнал о методе объемного рендеринга Ray-Casting. Кажется, это действительно круто, но я не совсем уверен, подходит ли это мне. Я не могу понять, как это реализовано, хотя я знаю, как это работает.


Еще один РЕДАКТИРОВАТЬ: TriTable.LookupTable а также EdgeTable.LookupTable здесь представлены таблицы

Вот MarchingCubes учебный класс:

public class MarchingCubes
{
    public static Point3D VertexInterp(double isolevel, Point3D p1, Point3D p2, double valp1, double valp2)
    {
        double mu;
        Point3D p = new Point3D();

        if (Math.Abs(isolevel - valp1) < 0.00001)
            return (p1);

        if (Math.Abs(isolevel - valp2) < 0.00001)
            return (p2);

        if (Math.Abs(valp1 - valp2) < 0.00001)
            return (p1);

        mu = (isolevel - valp1) / (valp2 - valp1);

        p.X = p1.X + mu * (p2.X - p1.X);
        p.Y = p1.Y + mu * (p2.Y - p1.Y);
        p.Z = p1.Z + mu * (p2.Z - p1.Z);

        return (p);
    }

    public static void Polygonise (ref List<Point3D[]> Triangles, int isolevel, GridCell gridcell)
    {
        int cubeindex = 0;
        if (grid.val[0] < isolevel) cubeindex |= 1;
        if (grid.val[1] < isolevel) cubeindex |= 2;
        if (grid.val[2] < isolevel) cubeindex |= 4;
        if (grid.val[3] < isolevel) cubeindex |= 8;
        if (grid.val[4] < isolevel) cubeindex |= 16;
        if (grid.val[5] < isolevel) cubeindex |= 32;
        if (grid.val[6] < isolevel) cubeindex |= 64;
        if (grid.val[7] < isolevel) cubeindex |= 128;

        // Cube is entirely in/out of the surface 
        if (EdgeTable.LookupTable[cubeindex] == 0)
            return;

        Point3D[] vertlist = new Point3D[12];

        // Find the vertices where the surface intersects the cube 
        if ((EdgeTable.LookupTable[cubeindex] & 1) > 0)
            vertlist[0] = VertexInterp(isolevel, grid.p[0], grid.p[1], grid.val[0], grid.val[1]);

        if ((EdgeTable.LookupTable[cubeindex] & 2) > 0)
            vertlist[1] = VertexInterp(isolevel, grid.p[1], grid.p[2], grid.val[1], grid.val[2]);

        if ((EdgeTable.LookupTable[cubeindex] & 4) > 0)
            vertlist[2] = VertexInterp(isolevel, grid.p[2], grid.p[3], grid.val[2], grid.val[3]);

        if ((EdgeTable.LookupTable[cubeindex] & 8) > 0)
            vertlist[3] = VertexInterp(isolevel, grid.p[3], grid.p[0], grid.val[3], grid.val[0]);

        if ((EdgeTable.LookupTable[cubeindex] & 16) > 0)
            vertlist[4] = VertexInterp(isolevel, grid.p[4], grid.p[5], grid.val[4], grid.val[5]);

        if ((EdgeTable.LookupTable[cubeindex] & 32) > 0)
            vertlist[5] = VertexInterp(isolevel, grid.p[5], grid.p[6], grid.val[5], grid.val[6]);

        if ((EdgeTable.LookupTable[cubeindex] & 64) > 0)
            vertlist[6] = VertexInterp(isolevel, grid.p[6], grid.p[7], grid.val[6], grid.val[7]);

        if ((EdgeTable.LookupTable[cubeindex] & 128) > 0)
            vertlist[7] = VertexInterp(isolevel, grid.p[7], grid.p[4], grid.val[7], grid.val[4]);

        if ((EdgeTable.LookupTable[cubeindex] & 256) > 0)
            vertlist[8] = VertexInterp(isolevel, grid.p[0], grid.p[4], grid.val[0], grid.val[4]);

        if ((EdgeTable.LookupTable[cubeindex] & 512) > 0)
            vertlist[9] = VertexInterp(isolevel, grid.p[1], grid.p[5], grid.val[1], grid.val[5]);

        if ((EdgeTable.LookupTable[cubeindex] & 1024) > 0)
            vertlist[10] = VertexInterp(isolevel, grid.p[2], grid.p[6], grid.val[2], grid.val[6]);

        if ((EdgeTable.LookupTable[cubeindex] & 2048) > 0)
            vertlist[11] = VertexInterp(isolevel, grid.p[3], grid.p[7], grid.val[3], grid.val[7]);

        // Create the triangle 
        for (int i = 0; TriTable.LookupTable[cubeindex, i] != -1; i += 3)
        {
            Point3D[] aTriangle = new Point3D[3];

            aTriangle[0] = vertlist[TriTable.LookupTable[cubeindex, i]];
            aTriangle[1] = vertlist[TriTable.LookupTable[cubeindex, i + 1]];
            aTriangle[2] = vertlist[TriTable.LookupTable[cubeindex, i + 2]];

            theTriangleList.Add(aTriangle);
        }
    }
}

Вот GridCell учебный класс:

public class GridCell
{
    public Point3D[] p = new Point3D[8];
    public Int32[] val = new Int32[8];
}

Хорошо, вот что я делаю:

List<int[][]> Slices = new List<int[][]> (); //Each slice is a 2D jagged array. This is a list of slices, each slice is a value between about -1000 to 2300

public static void Main ()
{
    List <Point3D[]> Triangles = new List<Point3D[]> ();
    int RowCount;//That is my row count
    int ColumnCount;//That is my column count
    //Here I fill the List with my values
    //Now I'll polygonise each GridCell
    for (int i = 0; i < Slices.Count - 1; i++)
    {
        int[][] Slice1 = Slices[i];
        int[][] Slice2 = Slices[i + 1];
        for (int j = 0; j < RowCount - 1; j++)
        {
            for (int k = 0; k < ColumnCount - 1; k++)
            {
                GridCell currentCell = GetCurrentCell (Slice1, Slice2, j, k);
                Polygonise (ref Triangles, int isoLevel, GridCell currentCell);
            }
        }
    }
    //I got the "Triangles" :D
}

//What this simply does is that it returns in 3D space from RI and CI
public static GridCell GetCurrentCell (int[][] CTSliceFront, int[][] CTSliceBack, int RI, int CI)//RI is RowIndex and CI is ColumnIndex
{
    //Those are preset indicating X,Y or Z coordinates of points/edges
    double X_Left_Front;
    double X_Right_Front;
    double X_Left_Back;
    double X_Right_Back;
    double Y_Top_Front;
    double Y_Botton_Front;
    double Y_Top_Back;
    double Y_Botton_Back;
    double Z_Front;
    double Z_Back;

    GridCell currentCell = new GridCell();
    currentCell.p[0] = new Point3D(X_Left_Back, Y_Botton_Back, Z_Back);
    currentCell.p[1] = new Point3D(X_Right_Back, Y_Botton_Back, Z_Back);
    currentCell.p[2] = new Point3D(X_Right_Front, Y_Botton_Front, Z_Front);
    currentCell.p[3] = new Point3D(X_Left_Front, Y_Botton_Front, Z_Front);
    currentCell.p[4] = new Point3D(X_Left_Back, Y_Top_Back, Z_Back);
    currentCell.p[5] = new Point3D(X_Right_Back, Y_Top_Back, Z_Back);
    currentCell.p[6] = new Point3D(X_Right_Front, Y_Top_Front, Z_Front);
    currentCell.p[7] = new Point3D(X_Left_Front, Y_Top_Front, Z_Front);

    currentCell.val[] = CTSliceBack[theRowIndex + 1][theColumnIndex];
    currentCell.val[] = CTSliceBack[theRowIndex + 1][theColumnIndex + 1];
    currentCell.val[] = CTSliceFront[theRowIndex + 1][theColumnIndex + 1];
    currentCell.val[] = CTSliceFront[theRowIndex][theColumnIndex];
    currentCell.val[] = CTSliceBack[theRowIndex][theColumnIndex + 1];
    currentCell.val[] = CTSliceFront[theRowIndex][theColumnIndex + 1];
    currentCell.val[] = CTSliceFront[theRowIndex][theColumnIndex];

    return currentCell;
}

Я сделал это прямо из Stack-Overflow, поэтому, пожалуйста, укажите на любые опечатки. Это работает, это приводит к оболочке модели. Я хочу сделать, это заменить isolevel переменная в Polygonise функция с 2 целыми числами: isolevelMin а также isolevelMax, Не только 1 int, но ассортимент.


РЕДАКТИРОВАТЬ: Ребята, пожалуйста, помогите. 50 повторений - это почти половина моей репутации. Я надеюсь, что смогу изменить свои ожидания. Теперь мне просто нужна идея, как я могу реализовать то, что я хочу, любой фрагмент о том, как это сделать, или если кто-то выдаст мне несколько ключевых слов, которые я могу использовать в Google, и которые я использовал для решения своей проблемы, он получит представителя. Мне просто нужно немного света - здесь все темно.


РЕДАКТИРОВАТЬ потрясающие: после еще большего, все большего и большего количества исследований, я обнаружил, что алгоритм объемного лучевого марширования фактически не генерирует поверхности. Поскольку мне нужно распечатать сгенерированную 3D-модель, у меня есть только один выход. Я должен заставить алгоритм движущихся кубов генерировать полую модель из максимальных и минимальных значений изоуровня.

2 ответа

Решение

Одно из предположений алгоритма Marching Cube состоит в том, что каждая геометрия куба является отдельной сущностью и не зависит от других кубов (что не совсем верно, но давайте сейчас придерживаться этого).

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

Например: скажем, ваш isolevel установлен в 0 и ваше трехмерное поле имеет положительные и отрицательные числа. Если вы теперь измените какое-то значение из 0.1 в 1.0 тогда вы измените только пропорции некоторых треугольников, но не их количество и / или топологию. С другой стороны, если вы измените какое-то значение из 0.1 в -0.1 тогда ваши треугольники могут переключиться на другое расположение, и их количество может измениться.

Благодаря этому мы можем использовать некоторую умную оптимизацию - для каждого из 8 углов, которые вы проверяете, находится ли угол внутри или снаружи сетки, вы используете эти 8 битов для построения числа - что становится индексом для вашей предварительно вычисленной таблицы возможных решений куба давайте назовем их Variants,

каждый Cube Variant список треугольников для этой конкретной конфигурации углов. Треугольники в марширующих кубах всегда проходят между ребрами куба, поэтому мы используем индексы ребер для его описания. Эти индексы - точные данные, которые вы видите в этих "волшебных" таблицах в коде Пола Бурка:).

Но это еще не окончательная сетка. Треугольники, которые вы здесь получаете, основаны только на факте, если какой-то угол находится внутри или снаружи сетки, но как далеко он от поверхности? Влияет ли это на результат? Здесь снова появляются ваши значения сетки - когда вы выбрали Variant, получил список треугольников и 3 ребра для каждого из этих треугольников, затем вы получите значения на концах ребра и интерполировать их, чтобы найти 0 значение (мы установили его как isolevel, Помните?). Эти последние, интерполированные вершины должны использоваться для вашей сетки.

Это стало довольно длинным вступлением, надеюсь, вы понимаете, что я написал.

Мое предложение: самое простое изменение с углами, вместо того, чтобы делать это:

if (grid.val[0] < isolevel) cubeindex |= 1;

Попробуйте изменить это так:

if (grid.val[0] > isolevelMin && grid.val[0] < isolevelMax) cubeindex |= 1;
// And the same for other lines...

Окончательная интерполяция немного сложнее, и у меня нет возможности проверить это, но давайте попробуем:

  • Изменить ваш VertexInterp пройти два уровня - мин и макс
  • внутри VertexInterp проверить, какой уровень находится между значениями valp1 и valp2
  • Интерполировать, как в текущем коде, но используя выбранный уровень (минимальный или максимальный)

И дайте мне знать, если это сработало:) или если у вас есть какие-либо вопросы.

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

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

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

Луч - это вектор, в этом случае, если вы перейдете от плоскости x,y с одной стороны к другой, это будет что-то вроде этого [псевдокод]

bool[,,] points = getpoints();
bool[,,] outsidepoints = new bool[width,height,depth];
//this is traceray from x,y,0 to x,y,depth
for(int x=0;x<width;x++){
  for(int y=0;y<height;y++){
    for(int z=0;z<depth;z++){
      if (points[x,y,z]==true){
        outsidepoints[x,y,z]=true
        z=depth;//we break the last for
      }
    }
  } 
}

сделать то же самое для всех 6 комбинаций в последнем цикле (x=0..width, x=width..0, y=0..height и т. д.)

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

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

ps: если вы хотите просто отобразить, вы можете использовать трассировку лучей, взяв значение первой точки, которая соединяется с лучом для каждой точки вашего дисплея (ожидайте изображение каждые пару секунд в зависимости от вашего оборудования и размера изображения) для это то, что вы можете использовать алгоритм рисования линий (проверьте википедию, есть образец, хотя в 2d), лучше всего то, что вы можете напрямую получить цвет из вашей картинки (просто установите порог, при котором пиксель будет недействительным (луч проходит) через))

другое редактирование: если вам нужна точность, пишите мне в личку или комментируйте здесь

используя проход через лучи:

bool lastwasempty=true;
for(int x=0;x<width;x++){
  for(int y=0;y<height;y++){
    for(int z=0;z<depth;z++){
      if (points[x,y,z]==true){
        if(lastwasempty){
           outsidepoints[x,y,z]=true
        }
        lastwasempty=false;
      }else{
        lastwasempty=true;
      }
    }
  } 
}
Другие вопросы по тегам