Как я могу полигонизировать бул [,,]
Если кому-то все равно, я использую 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;
}
}
}
}