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

Если кому интересно, я использую 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 Point3Ds. Если вы не знаете, что такое 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[,,] не приведет к гладкой 3D-модели, которую я с нетерпением жду, поэтому мое первое ожидаемое решение и мое четвертое ожидаемое решение не учитываются. После долгих исследований я узнал о методе объемного рендеринга 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 двумя целыми числами: isolevelMin и isolevelMax. Не только 1 int, но и диапазон.


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


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


person None    schedule 01.12.2016    source источник
comment
Используя bool[,,], я хочу получить список List‹Point3D[]›, где каждый Point3D[] имеет длину 3 и представляет собой треугольник в трехмерном пространстве: 1. В блокноте напишите от руки точно что вы имеете в виду под этим, ваша догадка определенно намного лучше моей. 2. Усовершенствуйте его, пока он не станет похож на псевдокод. 3. Простая часть: переведите это в реальный код. 4. Попросите о помощи, если его borken.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 01.12.2016
comment
Я реализовал алгоритм марширующих кубов, но, похоже, он просто не создан для того, чтобы принимать диапазон значений: когда вопрос говорит, что не кажется, я немного умираю. Информация в этом предложении округляется до нуля. Если у вас есть конкретная проблема с реализацией алгоритма, опубликуйте свой код и скажите, где он болит. Но вы не описываете конкретную проблему реализации; Вы описываете эмоциональное состояние. Может быть, кто-то здесь может выписать рецепты, я не знаю. Я не могу.   -  person 15ee8f99-57ff-4f92-890c-b56153    schedule 01.12.2016
comment
здесь может помочь документ, в котором рассказывается о том, как сгладьте сетку при использовании алгоритма марширующего куба   -  person Jess Anderson    schedule 02.12.2016
comment
@ люди голосуют близко – не закрывайте это, пожалуйста. Спрашивающий - новый человек. Он явно приложил усилия к своему исследованию и написал хорошо отформатированный вопрос, и только потому, что кода недостаточно (я уверен, что это из-за сложности, а не из-за неспособности кодировать), он получает близкие голоса. Эд Планкетт прав, нам нужна точная информация, но я уверен, что ОП будет рад ее предоставить. Пожалуйста, по крайней мере, дайте время щедрости умереть, прежде чем голосовать.   -  person Maverik    schedule 04.12.2016
comment
@ Никто не будет публиковать ответы, пока у них не будет точного кода для работы. Вы говорите, что видели псевдокод, который не понимаете, дайте ссылку на этот и другой исходный материал, с которым вы имели дело (даже если он не работает). Настройте проект github минимальный полный образец со всем вашим кодом в его текущем состоянии и свяжите его здесь, чтобы мы могли точно видеть, что вы сделали и что нужно исправить. В нынешнем виде ваша репутация за вознаграждение будет потрачена впустую.   -  person Maverik    schedule 04.12.2016
comment
в вашем случае я бы просто использовал словарь, такой как Dic‹bool, Point3d›, чтобы у вас были точки в одной руке и буллы в другой.   -  person Mightee    schedule 08.12.2016
comment
Как вы пришли к размеру вашего массива bool? Соответствует ли ваша модель данных диапазону 3D-принтера?   -  person cowboydan    schedule 09.12.2016
comment
Вход представляет собой серию срезов в оттенках серого (КТ). Длина массива bool — это длина 1 среза, ширина массива — это ширина 1 среза, а высота массива — это количество срезов. Размеры всех ломтиков одинаковы. Моя модель данных соответствует диапазону 3D-принтера.   -  person None    schedule 09.12.2016


Ответы (2)


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

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

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

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

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

Но это еще не финальная сетка. Треугольники, которые вы здесь получаете, основаны только на том факте, что какой-то угол находится внутри или снаружи сетки, но как далеко он находится от поверхности? Это как-то влияет на результат? Вот еще раз ваши значения сетки - когда вы выбрали свой 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.
  • Интерполировать как в текущем коде, но используя выбранный изоуровень (минимум или максимум)

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

person kolenda    schedule 12.12.2016

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

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

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

луч - это вектор, в этом случае, если вы пойдете от плоскости 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;
      }
    }
  } 
}
person satibel    schedule 08.12.2016
comment
Проблема в том, что мне нужно, чтобы изображение было полым в соответствии с минимальным заданным значением. Я бы дал +1, если вы покажете мне, как получить точки на краях внутри сферы, а не только на внешней границе. Спасибо за алгоритм рисования линий. - person None; 08.12.2016
comment
@None, вы можете попытаться заставить лучи проходить (не ломаться при первом попадании), а затем проверить, была ли точка до этого прозрачной (ложь), я добавлю код для этого в ответ. - person satibel; 08.12.2016
comment
@None код обнаружит, есть ли пустое место после того, как вы проанализируете все стороны (и верх, и низ), прежде чем один путь будет другим. чтобы создать модель для печати, вы, вероятно, могли бы просто сканировать по линиям или сделать куб с центром вокруг каждой точки, что, вероятно, сделало бы что-то уродливое. Поэтому я подумал, что если вам нужна более гладкая модель, вы можете использовать алгоритм подразделения поверхности Катмулла-Кларка. чтобы создать файл для 3D-печати, вы можете экспортировать его в obj hodge.net.au/sam/blog/wp-content/uploads/obj_format.txt и используйте слайсер или просто сгенерируйте g-код. - person satibel; 09.12.2016