我怎么能多边形化布尔值[,,]



如果有人关心,我正在使用WPF....

故事开头:

我得到了一系列灰度图像(模型切片)。用户输入灰度值的"范围"以构建3D模型。因此,我创建了一个 3Dbool数组,以便更轻松地构建 3D 模型。此数组表示一个像素框,指示是否应构建/生成/填充每个像素。


领带:

使用bool[,,],我想检索一个List<Point3D[]>,其中每个Point3D[]的长度为 3,表示 3D 空间中的一个三角形。


附加信息:

生成的模型将被3D打印。在bool[,,]中,true表示物质的存在,而false表示物质的不存在。我需要避免立方模型,其中每个像素都替换为立方体(根据我的目的,这将是无用的)。模型应尽可能平滑和准确。


我试图做什么:

1-我实现了行进立方体算法,但它似乎根本不是为了接受值的"范围"而创建的。

2-我一直在努力尝试制作自己的算法,但我部分失败了。(这真的很复杂。如果您想了解更多信息,请告知)


我不知道如何实施的一些预期解决方案:

1-修改行进立方体算法,使其使用bool[,,]

工作2-修改行进立方体算法,使其使用使用isolevel值"范围"的工作

3-展示如何在WPF中实现另一种适合我目的的算法(可能是光线投射算法)。

4-请求我尝试实现的算法的来源,然后向我展示如何解决它。(它首先是为了多边形化bool[,,]而制作的)

5-其他一些神奇的解决方案。

提前谢谢。


编辑:

通过说

使用bool[,,],我想检索一个List<Point3D[]>,其中每个Point3D[]的长度为 3,代表 3D 空间中的一个三角形。

我的意思是我想检索一组三角形。每个三角形应表示为 3Point3D秒。如果您不知道Point3D是什么,它是一个包含 3 个双精度(X、Y 和 Z)的struct,用于表示 3D 空间中的位置。

行进立方体算法的问题在于它有点模糊。我的意思是,你这样做能理解什么??

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 模型,所以我的第一个预期解决方案和第四个预期解决方案都超出了游戏范围。经过大量研究,我知道了体积渲染的光线投射方法。这看起来真的很酷,但我不确定它是否符合我的需求。尽管我知道它是如何工作的,但我无法真正理解它是如何实现的。


还有一个编辑:TriTable.LookupTableEdgeTable.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中做到了这一点,所以请指出任何拼写错误。这是有效的,这会产生模型的外壳。我想做的是将Polygonise函数中的isolevel变量替换为 2 个整数:isolevelMinisolevelMax。不仅是 1int,而是一个范围。


编辑:伙计们,请帮忙。 50 次几乎是我声誉的 1/2。我希望我能改变我的期望。现在我只想知道我如何实现我想要的东西,任何关于如何做到这一点的片段,或者如果有人给出一些我可以谷歌的关键字,我用来解决我的问题,他会得到代表。我只需要一些光——这里都是黑暗的。


编辑很棒:经过越来越多的研究,我发现体积光线行进算法实际上并没有生成表面。由于我需要打印生成的3D模型,我现在只有一条出路。我必须使行进立方体算法从最大和最小等值生成一个空心模型。

行进立方体算法的一个假设是,每个立方体几何体都是一个单独的实体,不依赖于其他立方体(这并不完全正确,但现在让我们坚持这一点)。

鉴于此,您可以将整个网格拆分为立方体并分别解决每个立方体。您可能还会注意到,尽管最终网格取决于 3D 标量场的精确值,但网格的拓扑仅取决于某个给定立方体角是否位于网格内部外部的事实。

例如:假设您的isolevel设置为0,并且您的 3D 字段具有正数和负数。如果现在将某些值从0.1更改为1.0,则只会更改某些三角形的比例,而不会更改它们的数量和/或拓扑。另一方面,如果您将某些值从0.1更改为-0.1,那么您的三角形可能会切换到另一种排列方式,并且它们的数量可能会发生变化。

多亏了这一点,我们可以使用一些巧妙的优化 - 对于 8 个角中的每一个,您检查角是在网格的内部还是外部,您可以使用这 8 位来构建一个数字 - 它成为您预先计算的可能的立方体解决方案表的索引,我们称它们为Variants.

每个Cube Variant都是此特定角配置的三角形列表。行进立方体中的三角形总是跨越立方体边缘之间,因此我们使用边缘索引来描述它。这些索引是你在Paul Bourke代码:)的那些"魔术"表中看到的确切数据。

但这还不是最终的网格。你在这里得到的三角形只是基于一个事实,如果某个角在网格的内侧或外侧,但它离表面有多远?对结果有影响吗?您的网格值再次出现 - 当您选择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..高度等)

如果您没有悬垂(例如单个凸形),这应该足够好,如果您这样做,它们可能会被切断。

一旦你有了外部点,你只需要一个算法将它们做成三角形(比如沿着一个轴在每个平面上制作连接切片,然后缝制最后 2 个连接。

PS:如果您想要的只是显示器,您可以通过为显示器的每个点获取与光线连接的第一个点的值来使用光线追踪(预计每隔几秒钟就会出现一张图像,具体取决于您的硬件和图片大小)为此您可以使用线条绘制算法(查看维基百科,有一个示例, 不过在2D中)最好的一点是你可以直接从你的图片中获取颜色(只需设置一个像素无效的阈值(光线通过))

另一个编辑:如果您需要任何精度,请PM Me或在此处发表评论

使用直通射线:

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;
}
}
} 
}

相关内容

  • 没有找到相关文章

最新更新