根据链接,您似乎对 球体的体素算法 更感兴趣,而不是 图形 本身;说类似this page 的帮助。你不想要一个球,只想要一个表面。
Midpoint circle algorithm 可用于绘制 3D 体素球体:将球体视为一堆切片,每个切片包含一个圆。
实际上,您使用两个嵌套的中点圆,外圆定义内圆的半径。 (虽然一个简单的算法在彼此之上绘制圆可能会在体素上留下空洞,但中点圆算法利用了对称性,如果正确实施,就不会出现这样的空洞。)
您串联构建六个帽子,就像将一个立方体雕刻成一个球体。由于每个帽的表面斜率总是小于 1,所以在帽上向外移动最多会使每个坐标改变 1,因此不会出现孔。
这种方法的问题在于复杂性:您计算的每个点可能会影响多达 48 个体素单元。 (在每个上限处,每个点都在一个八分圆内计算,因此会影响八个单元格。有六个上限,6*8=48。)
我建议一种不同的方法。
以x0, y0为中心的r-半径球面的方程sub>,z0,是
(x - x0)2 + (y - y0)2 + (z - z0)2 = r2
使用整数坐标,网格点很少恰好在球面上,允许取值范围:
RRMIN ≤ (x - x0) 2 + (y - y0)2 + (z - z0)2 ≤ RRMAX
其中 RRMIN 和 RRMAX 是常量;具体来说,就是与球心平方的最小和最大距离。
我建议在一般情况下使用双坐标。这使您可以选择球的中心是位于某个坐标的中心(表示奇数直径),还是位于两个相邻坐标之间(表示偶数直径)。
如果你有一个SIZE×SIZE×SIZE 体素网格(灯光、积木等),那么
int sphere_surface(const char x, const char y, const char z,
const char size, const int rrmin, const int rrmax)
{
const int center = size - (size & 1); /* Size rounded down to even integer */
const int dx = center - x - x,
dy = center - y - y,
dz = center - z - z; /* Doubled coordinates */
const int rr = dx*dx + dy*dy + dz*dz; /* Distance squared */
return (rrmin <= rr) && (rr <= rrmax);
}
如果点 (x,y,z) 位于以立方体为中心的球体的表面区域内,则返回 1。 (从技术上讲,如果从该点到size 大小的立方体中心的距离在sqrt(rrmin)/2 和sqrt(rrmax)/2 之内,则返回。)
rrmin 和 rrmax 的“正确”值高度依赖于上下文。 rrmax 通常在 size*size 附近(请记住,该函数使用双倍坐标),rrmin 稍少。
例如,如果你有一个 3×3×3 的网格,你只希望每个面上的六个中心单元满足条件;你可以用size=3、rrmin=1、rrmax=4来做到这一点:
如果你有一个 4×4×4 的网格,你希望每个面上的四个中心单元满足条件(因此总共 64 个单元中的 24 个被认为在球面上);你可以用size=4、rrmin=11、rrmax=11来做到这一点:
使用 5×5×5 的网格,我们得到了上述算法的有趣副作用。
size=5、rrmin=8、rrmax=16 产生一个非常“棱角分明”的球体,几乎是一个立在角落的立方体:
size=5、rrmin=12、rrmax=20 产生我最喜欢的近似球体:
size=5、rrmin=16、rrmax=24 生成一个圆角立方体(每个面都是一个 3×3 平面):
显然,使用rrmin=0 也包括所有内部单元格,产生一个球,而不仅仅是球体的表面。
随着网格大小的增加,您可以表示的每个大小球体的变体越多。
上述函数在微控制器上特别有用,因为您可以简单地循环遍历您的晶格,根据需要更新每个点的状态。此外,大多数微控制器的内存都很紧张,但具有非常快的(单时钟)加法、减法和乘法指令。 (虽然 16×16 位乘法与 32 位结果通常需要两条或更多条指令。)
典型的微控制器没有 ROM/闪存功能来存储足够有趣的体素模式,只有少数具有通过 SPI 从 SD 卡获取 DMA 的能力(因此您可以从 microSD 卡加载体素模式的“帧” ),但是像上面这样的函数可以用很少的输入产生有趣的形状——尤其是你可以插值的输入。
如果您的体素不仅是二进制的,而且例如PWM 控制的 LED。
由于可视化上述内容通常有点困难,这里是我用来生成上述图像的快速小技巧。当给定 size、rrmin 和 rrmax 作为命令行参数时,它将 SVG 图像输出到标准输出,并将 ASCII 切片输出到标准错误。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define BORDER 2
#define XC(x,y,z) ((x)*16 + (y)*12)
#define YC(x,y,z) ((x)*6 - (y)*8 - (z)*17)
static int xt = 0;
static int yt = 0;
static void fcube(FILE *out, const int x, const int y, const int z, const int fill)
{
const int xc = xt + XC(x,y,z);
const int yc = yt + YC(x,y,z);
fprintf(out, "<path d=\"M%d,%dl16,6,12,-8,0,-17,-16,-6,-12,8z\" fill=\"#%06x\" stroke=\"#000\" />\n", xc, yc, fill & 0xFFFFFF);
fprintf(out, "<path d=\"M%d,%dl16,6,12,-8m-12,8l0,17\" fill=\"none\" stroke=\"#000\" />\n", xc, yc-17);
}
static unsigned long rrmin = 0UL;
static unsigned long rrmax = 0UL;
static int center = 0;
static int surface(const int x, const int y, const int z)
{
/* Doubled coordinates: */
const long dx = 2*x - center,
dy = 2*y - center,
dz = 2*z - center;
const unsigned long rr = dx*dx + dy*dy + dz*dz;
return (rrmin <= rr) && (rr <= rrmax);
}
int main(int argc, char *argv[])
{
int width, height;
int size, x, y, z;
char dummy;
if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s SIZE RRMIN RRMAX\n", argv[0]);
fprintf(stderr, "Where\n");
fprintf(stderr, " SIZE is the size of the voxel cube\n");
fprintf(stderr, " RRMIN is the minimum distance squared, and\n");
fprintf(stderr, " RRMAX is the maximum distance squared,\n");
fprintf(stderr, " using doubled coordinates.\n");
fprintf(stderr, "\n");
fprintf(stderr, "Examples:\n");
fprintf(stderr, " %s 3 1 4\n", argv[0]);
fprintf(stderr, " %s 4 11 11\n", argv[0]);
fprintf(stderr, " %s 5 8 16\n", argv[0]);
fprintf(stderr, " %s 5 12 20\n", argv[0]);
fprintf(stderr, " %s 5 16 24\n", argv[0]);
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (sscanf(argv[1], " %d %c", &size, &dummy) != 1 || size < 3) {
fprintf(stderr, "%s: Invalid size.\n", argv[1]);
return EXIT_FAILURE;
}
if (sscanf(argv[2], " %lu %c", &rrmin, &dummy) != 1) {
fprintf(stderr, "%s: Invalid rrmin.\n", argv[2]);
return EXIT_FAILURE;
}
if (sscanf(argv[3], " %lu %c", &rrmax, &dummy) != 1 || rrmax < rrmin) {
fprintf(stderr, "%s: Invalid rrmax.\n", argv[3]);
return EXIT_FAILURE;
}
/* Calculate coordinate range. */
{ int xmin = XC(0,0,0);
int ymin = YC(0,0,0);
int xmax = XC(0,0,0);
int ymax = YC(0,0,0);
for (z = 0; z <= size; z++)
for (y = 0; y <= size; y++)
for (x = 0; x <= size; x++) {
const int xc = XC(x,y,z);
const int yc = YC(x,y,z);
if (xc < xmin) xmin = xc;
if (xc > xmax) xmax = xc;
if (yc < ymin) ymin = yc;
if (yc > ymax) ymax = yc;
}
xt = BORDER - xmin;
width = xmax - xmin + 2*BORDER;
yt = BORDER - ymin;
height = ymax - ymin + 2*BORDER;
}
center = size - 1;
/* SVG preamble. */
printf("<?xml version=\"1.0\"?>\n");
printf("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 %d %d\">\n", width, height);
printf("<path d=\"M%d,%dL%d,%d,%d,%d,%d,%d,%d,%d,%d,%dz\" fill=\"#f7f7f7\" stroke=\"#666666\"/>\n",
xt+XC( 0, 0, 0), yt+YC( 0, 0, 0),
xt+XC(size, 0, 0), yt+YC(size, 0, 0),
xt+XC(size,size, 0), yt+YC(size,size, 0),
xt+XC(size,size,size), yt+YC(size,size,size),
xt+XC(0, size,size), yt+YC( 0,size,size),
xt+XC(0, 0,size), yt+YC( 0, 0,size));
printf("<path d=\"M%d,%dL%d,%d,%d,%dM%d,%dL%d,%d\" fill=\"none\" stroke=\"#666666\"/>\n",
xt+XC( 0, 0, 0), yt+YC( 0, 0, 0),
xt+XC( 0,size, 0), yt+YC( 0,size, 0),
xt+XC(size,size, 0), yt+YC(size,size, 0),
xt+XC( 0,size, 0), yt+YC( 0,size, 0),
xt+XC( 0,size,size), yt+YC( 0,size,size));
for (z = 0; z < size; z++)
for (y = size - 1; y >= 0; y--)
for (x = 0; x < size; x++)
if (surface(x,y,z))
fcube(stdout, x, y, z, 0x00CCFF);
printf("</svg>\n");
for (z=0; z < size; z++) {
for (y = 0; y < size; y++) {
for (x = 0; x < size; x++)
fputc(surface(x,y,z) ? 'X' : '.', stderr);
fputs(" ", stderr);
for (x = 0; x < size; x++)
fputc(surface(x,z,y) ? 'X' : '.', stderr);
fputs(" ", stderr);
for (x = 0; x < size; x++)
fputc(surface(y,z,x) ? 'X' : '.', stderr);
fputc('\n', stderr);
}
fputc('\n', stderr);
}
return EXIT_SUCCESS;
}
我没有费心去优化输出;你可以很容易地例如为每张脸选择不同的颜色,可能为背景平面添加阴影等等。
上面的图片是用这个程序创建的,然后用GIMP转换成PNG,但我建议用你的浏览器在本地查看生成的文件。
问题?