C 在3d网格上高效地查找iso成本点,并将点的成本降至最低
我有一个三维网格,其中网格上的每个点(x,y,z)都与成本值关联。任何点(x,y,z)的成本事先不知道。要知道成本,我们需要进行一个非常昂贵的复杂查询。关于该对象,我们知道的一件事是成本在所有3个维度上都是单调的非递减的 现在给定成本C,我需要找到曲面上成本为C的点(x,y,z)。这必须通过只需最低成本的来完成。如何解决我的问题 当我在网上搜索时,我得到了轮廓识别相关技术,但所有这些技术都假设所有点的成本都是事先知道的,如Marching cubes方法等。在我的情况下,主要指标是成本点数应该是最小的C 在3d网格上高效地查找iso成本点,并将点的成本降至最低,c,algorithm,math,data-structures,contour,C,Algorithm,Math,Data Structures,Contour,我有一个三维网格,其中网格上的每个点(x,y,z)都与成本值关联。任何点(x,y,z)的成本事先不知道。要知道成本,我们需要进行一个非常昂贵的复杂查询。关于该对象,我们知道的一件事是成本在所有3个维度上都是单调的非递减的 现在给定成本C,我需要找到曲面上成本为C的点(x,y,z)。这必须通过只需最低成本的来完成。如何解决我的问题 当我在网上搜索时,我得到了轮廓识别相关技术,但所有这些技术都假设所有点的成本都是事先知道的,如Marching cubes方法等。在我的情况下,主要指标是成本点数应该是
如果有人能建议一种方法来获得大致位置(如果不准确的话),这将是很有帮助的。您应该阅读这篇文章,它讨论了二维情况,并让您深入了解不同的方法:
在我看来,逐步线性搜索(在第二部分中)对于您来说将是一个伟大的第一步,因为它非常容易应用于三维案例,并且不需要太多的经验来理解。
因为这非常简单,而且仍然非常有效,所以我会使用它,看看它是否适合您在三维中使用的数据类型。
但是,如果您的唯一目标是性能,那么您应该将二进制分区应用于3-d。这变得有点复杂,因为他所说的“二进制分区”实际上变成了“二进制平面分区”。
因此,没有一条线将矩阵划分为两个可能更小的矩阵。
相反,您有一个平面将多维数据集划分为两个可能更小的多维数据集。
要使该平面(或矩阵)中的搜索有效,首先必须实现他的一个方法:)
然后用较小的立方体重复所有操作。
请记住,以一种非常有效的方式实现这一点(即,记住内存访问)并非易事。我将给出这个答案,以尽量减少计算的成本数量。Matt Ko链接到了一个好的解决方案,但它假设了一个廉价的成本函数和一个基于矩阵的数据,而您似乎没有这两个。我给出的方法需要更接近成本函数的
O(logn+k)
调用,其中k
是具有所需成本的点数。请注意,这个具有一些性能优化的算法可以在3D矩阵上设置为O(N)
,而性能代价函数调用的机会很小,尽管它有点复杂
基于中使用的技术的psudoCode如下所示:
While there are still points under considerations:
Find the ideal pivot point and calculate it's cost
Remove the pivot from the point set
If the cost is the desired cost then:
Add the pivot to the solution set
Else:
Separate the points into 3 groups:
G1. Those that are in in the pivot's octant `VII`
G2. Those have the same x, y, or z of the pivot
G3. Those that are not in the pivot's octant `VII`
# Note this can be done in O(N)
If all points are in group 2:
Use 1D binary searches in each dimension to find points with the desired cost
Else:
Compute the cost of the pivot
Keep all points in group 2
If the pivot cost is greater than desired:
Keep only the points in group 1
Else:
Keep only the points in group 3
基于该线内外的点选择的轴。形成八分之一的三条线上的任何一条线上的点,如果需要,将在后面处理(G2)
理想的轴心点是第1组(G1)和第3组(G3)中的点数尽可能接近相等。从数学的角度来看,可以将两者中的较大者最大化,而不是两者中的较小者,或者最大化(max(| G1 |,| G3 |))/min(| G1 |,| G3 |))
。即使是一个寻找理想轴心点的相当简单的算法也可以在O(N^2)
中找到它(一个O(N logn)
算法可能存在),但在找到理想轴心点后,需要O(N^3)
来计算其成本
找到理想轴心并计算其成本后,每次迭代应平均看到大约一半的剩余点被丢弃,这同样只会导致O(logn+k)
调用成本函数
最后一个注释:
回顾过去,我不确定第2组是否需要特别考虑,因为它可能在第3组中,但我不是100%确定。然而,将它分离出来似乎并没有改变大O,因此我认为没有必要改变它,尽管这样做会稍微简化算法。重写解释:
(原文,以防向某人阐明想法,在行下保持不变)
我们有一些三维函数f(x,y,z),我们希望找到曲面f(x,y,z)=c。因为函数只产生一个数字,所以它定义了,我们要寻找的曲面是c
在我们的例子中,计算函数f(x,y,z)的成本非常高,因此我们希望尽量减少使用它的次数。不幸的是,大多数等值面算法的假设正好相反
我的建议是使用类似于Fractint用于二维分形的等值面行走。在代码方面,它是复杂的,但它应该最小化所需的函数求值量——这正是它在Fractint中实现的目的
背景/历史:
在20世纪80年代末和90年代初,我遇到了一个分形绘图套件。当时计算机的速度要慢得多,评估每一点都慢得令人痛苦。在Fractint中做了很多努力,使其尽可能快地显示分形,但仍然精确。(你们中的一些人可能还记得它可以通过旋转调色板中的颜色来实现颜色循环。它很有催眠作用;是1995年纪录片《无限的颜色》中的Youtube剪辑,它可以进行颜色循环和放大。计算全屏分形可能需要数小时(在高缩放因子下,接近实际分形集),但您可以(将其保存为图像并)使用颜色循环来“设置动画”。)
有些分形是,或者有区域,在这些区域中,所需的迭代次数是单调的,不向实际的分形集递减——也就是说,没有突出的“孤岛”,只是
f(x,y,z) ≤ c
f(x+1, y, z) > c
f(x, y+1, z) > c
f(x+1, y+1, z) > c
f(x, y, z+1) > c
f(x+1, y, z+1) > c
f(x, y+1, z+1) > c
f(x+1, y+1, z+1) > c
f(x,y,z) < c
f(x+1, y, z) ≥ c
f(x, y+1, z) ≥ c
f(x+1, y+1, z) ≥ c
f(x, y, z+1) ≥ c
f(x+1, y, z+1) ≥ c
f(x, y+1, z+1) ≥ c
f(x+1, y+1, z+1) ≥ c
typedef struct {
size_t xsize;
size_t ysize;
size_t zsize;
size_t size; /* xsize * ysize * zsize */
size_t xstride; /* [z][y][x] array = 1 */
size_t ystride; /* [z][y][x] array = xsize */
size_t zstride; /* [z][y][x] array = xsize * ysize */
double xorigin; /* Function x for grid coordinate x = 0 */
double yorigin; /* Function y for grid coordinate y = 0 */
double zorigin; /* Function z for grid coordinate z = 0 */
double xunit; /* Function x for grid coordinate x = 1 */
double yunit; /* Function y for grid coordinate y = 1 */
double zunit; /* Function z for grid coordinate z = 1 */
/* Function to obtain a new sample */
void *data;
double *sample(void *data, double x, double y, double z);
/* Walking stack */
size_t stack_size;
size_t stack_used;
size_t *stack;
unsigned char *cell; /* CELL_ flags */
double *cache; /* Cached samples */
} grid;
#define CELL_UNKNOWN (0U)
#define CELL_SAMPLED (1U)
#define CELL_STACKED (2U)
#define CELL_WALKED (4U)
double grid_sample(const grid *const g, const size_t gx, const size_t gy, const size_t gz)
{
const size_t i = gx * g->xstride + gy * g->ystride + gz * g->zstride;
if (!(g->cell[i] & CELL_SAMPLED)) {
g->cell[i] |= CELL_SAMPLED;
g->cache[i] = g->sample(g->data, g->xorigin + (double)gx * g->xunit,
g->yorigin + (double)gy * g->yunit,
g->zorigin + (double)gz * g->zunit);
}
return g->cache[i];
}
size_t grid_find(const grid *const g, const double c)
{
const size_t none = g->size;
size_t xmin = 0;
size_t ymin = 0;
size_t zmin = 0;
size_t xmax = g->xsize - 2;
size_t ymax = g->ysize - 2;
size_t zmax = g->zsize - 2;
double s;
s = grid_sample(g, xmin, ymin, zmin);
if (s > c) {
return none;
}
if (s == c)
return xmin*g->xstride + ymin*g->ystride + zmin*g->zstride;
s = grid_sample(g, xmax, ymax, zmax);
if (s < c)
return none;
if (s == c)
return xmax*g->xstride + ymax*g->ystride + zmax*g->zstride;
while (1) {
const size_t x = xmin + (xmax - xmin) / 2;
const size_t y = ymin + (ymax - ymin) / 2;
const size_t z = zmin + (zmax - zmin) / 2;
if (x == xmin && y == ymin && z == zmin)
return x*g->xstride + y*g->ystride + z*g->zstride;
s = grid_sample(g, x, y, z);
if (s < c) {
xmin = x;
ymin = y;
zmin = z;
} else
if (s > c) {
xmax = x;
ymax = y;
zmax = z;
} else
return x*g->xstride + y*g->ystride + z*g->zstride;
}
}
#define GRID_X(grid, index) (((index) / (grid)->xstride)) % (grid)->xsize)
#define GRID_Y(grid, index) (((index) / (grid)->ystride)) % (grid)->ysize)
#define GRID_Z(grid, index) (((index) / (grid)->zstride)) % (grid)->zsize)
static void grid_push(grid *const g, const size_t cell_index)
{
/* If the stack is full, remove cells already walked. */
if (g->stack_used >= g->stack_size) {
const size_t n = g->stack_used;
size_t *const s = g->stack;
unsigned char *const c = g->cell;
size_t i = 0;
size_t o = 0;
while (i < n)
if (c[s[i]] & CELL_WALKED)
i++;
else
s[o++] = s[i++];
g->stack_used = o;
}
/* Grow stack if still necessary. */
if (g->stack_used >= g->stack_size) {
size_t *new_stack;
size_t new_size;
if (g->stack_used < 1024)
new_size = 1024;
else
if (g->stack_used < 1048576)
new_size = g->stack_used * 2;
else
new_size = (g->stack_used | 1048575) + 1048448;
new_stack = realloc(g->stack, new_size * sizeof g->stack[0]);
if (new_stack == NULL) {
/* FATAL ERROR, out of memory */
}
g->stack = new_stack;
g->stack_size = new_size;
}
/* Unnecessary check.. */
if (!(g->cell[cell_index] & (CELL_STACKED | CELL_WALKED)))
g->stack[g->stack_used++] = cell_index;
}
static size_t grid_pop(grid *const g)
{
while (g->stack_used > 0 &&
g->cell[g->stack[g->stack_used - 1]] & CELL_WALKED)
g->stack_used--;
if (g->stack_used > 0)
return g->stack[--g->stack_used];
return g->size; /* "none" */
}
int isosurface(grid *const g, const double c,
int (*report)(grid *const g,
const size_t x, const size_t y, const size_t z,
const double c,
const double x0y0z0,
const double x1y0z0,
const double x0y1z0,
const double x1y1z0,
const double x0y0z1,
const double x1y0z1,
const double x0y1z1,
const double x1y1z1))
{
const size_t xend = g->xsize - 2; /* Since we examine x+1, too */
const size_t yend = g->ysize - 2; /* Since we examine y+1, too */
const size_t zend = g->zsize - 2; /* Since we examine z+1, too */
const size_t xstride = g->xstride;
const size_t ystride = g->ystride;
const size_t zstride = g->zstride;
unsigned char *const cell = g->cell;
double x0y0z0, x1y0z0, x0y1z0, x1y1z0,
x0y0z1, x1y0z1, x0y1z1, x1y1z1; /* Cell corner samples */
size_t x, y, z, i;
int r;
/* Clear walk stack. */
g->stack_used = 0;
/* Clear walked and stacked flags from the grid cell map. */
i = g->size;
while (i-->0)
g->cell[i] &= ~(CELL_WALKED | CELL_STACKED);
i = grid_find(g, c);
if (i >= g->size)
return errno = ENOENT; /* No isosurface c */
x = (i / g->xstride) % g->xsize;
y = (i / g->ystride) % g->ysize;
z = (i / g->zstride) % g->zsize;
/* We need to limit x,y,z to the valid *cell* coordinates. */
if (x > xend) x = xend;
if (y > yend) y = yend;
if (z > zend) z = zend;
i = x*g->xstride + y*g->ystride + z*g->zstride;
if (x > xend || y > yend || z > zend)
return errno = ENOENT; /* grid_find() returned an edge cell */
grid_push(g, i);
while ((i = grid_pop) < g->size) {
x = (i / g->xstride) % g->xsize;
y = (i / g->ystride) % g->ysize;
z = (i / g->zstride) % g->zsize;
cell[i] |= CELL_WALKED;
x0y0z0 = grid_sample(g, x, y, z);
if (x0y0z0 > c)
continue;
x1y0z0 = grid_sample(g, 1+x, y, z);
x0y1z0 = grid_sample(g, x, 1+y, z);
x1y1z0 = grid_sample(g, 1+x, 1+y, z);
x0y0z1 = grid_sample(g, x, y, 1+z);
x1y0z1 = grid_sample(g, 1+x, y, 1+z);
x0y1z1 = grid_sample(g, x, 1+y, 1+z);
x1y1z1 = grid_sample(g, 1+x, 1+y, 1+z);
/* Isosurface does not pass through this cell?!
* (Note: I think this check is unnecessary.) */
if (x1y0z0 < c && x0y1z0 < c && x1y1z0 < c &&
x0y0z1 < c && x1y0z1 < c && x0y1z1 < c &&
x1y1z1 < c)
continue;
/* Report the cell. */
if (report) {
r = report(g, x, y, z, c, x0y0z0, x1y0z0,
x0y1z0, x1y1z0, x0y0z1, x1y0z1,
x0y1z1, x1y1z1);
if (r) {
errno = 0;
return r;
}
}
/* Could the surface extend to -x? */
if (x > 0 &&
!(cell[i - xstride] & (CELL_WALKED | CELL_STACKED)) &&
( x0y1z0 >= c || x0y0z1 >= c ))
grid_push(g, i - xstride);
/* Could the surface extend to -y? */
if (y > 0 &&
!(cell[i - ystride] & (CELL_WALKED | CELL_STACKED)) &&
( x0y0z1 >= c || x1y0z0 >= c ))
grid_push(g, i - ystride);
/* Could the surface extend to -z? */
if (z > 0 &&
!(cell[i - zstride] & (CELL_WALKED | CELL_STACKED)) &&
( x1y0z0 >= c || x0y1z0 >= c ))
grid_push(g, i - zstride);
/* Could the surface extend to +x? */
if (x < xend &&
!(cell[i + xstride] & (CELL_WALKED | CELL_STACKED)) &&
( x0y1z0 >= c || x0y0z1 >= c ))
grid_push(g, i + xstride);
/* Could the surface extend to +y? */
if (y < xend &&
!(cell[i + ystride] & (CELL_WALKED | CELL_STACKED)) &&
( x1y0z0 >= c || x0y0z1 >= c ))
grid_push(g, i + ystride);
/* Could the surface extend to +z? */
if (z < xend &&
!(cell[i + zstride] & (CELL_WALKED | CELL_STACKED)) &&
( x1y0z0 >= c || x0y1z0 >= c ))
grid_push(g, i + zstride);
}
/* All done. */
errno = 0;
return 0;
}
if (c0 == c && c1 == c)
/* Entire edge is on the isosurface */
else
if (c0 == c)
/* Isosurface intersects edge at p0 */
else
if (c1 == c)
/* Isosurface intersects edge at p1 */
else
if (c0 < c && c1 > c)
/* Isosurface intersects edge at p0 + (p1-p0)*(c-c0)/(c1-c0) */
else
if (c0 > c && c1 < c)
/* Isosurface intersects edge at p1 + (p0-p1)*(c-c1)/(c0-c1) */
else
/* Isosurface does not intersect the edge */
gcc -Wall -std=c99 -Wno-unused -O2 grid.c -o isosurface
./isosurface 50 -1.0 1.0 0.0 > out-0.0
./isosurface 50 -1.0 1.0 0.5 > out-0.5
./isosurface 50 -1.0 1.0 1.0 > out-1.0
splot "out-0.0" u 1:2:3 notitle w dots, "out-0.5" u 1:2:3 notitle w dots, "out-1.0" u notitle w dots