我不相信在每个层上运行中点圆算法会产生期望的结果,因为当你到达极点时,表面上会有未点亮的LED间隙。这可能会产生您想要的结果,但这取决于美学。本帖子基于使用中点圆算法通过中间的两个垂直八分之一确定层的半径,然后在绘制每个圆时还设置了两极八分之一的点。
我认为根据@Nick Udall的评论和答案
here,使用圆算法来确定水平切片的半径将与我在他的答案评论中提出的修改一起使用。圆算法应该被修改为将初始误差作为输入,并且还应为两极八分之一绘制附加点。
- 在
y0 + y1
和y0 - y1
处绘制标准圆算法点:x0 +/- x,z0 +/- z,y0 +/- y1
,x0 +/- z,z0 +/- x,y0 +/- y1
,共16个点。这形成了球体的垂直部分。
- 此外,还要绘制点
x0 +/- y1,z0 +/- x,y0 +/- z
和x0 +/- x,z0 +/- y1,y0 +/- z
,共16个点,这将形成球体的极冠。
通过将外部算法的误差传递到圆形算法中,可以允许对每个层的圆进行亚像素调整。如果没有将误差传递到内部算法中,则圆的赤道将近似为一个圆柱体,并且在x、y和z轴上的每个近似球面将形成一个正方形。包含误差后,给定足够大的半径,每个面都将近似为填充的圆形。
以下代码修改自维基百科的
Midpoint circle algorithm。
DrawCircle
算法的术语已更改为在xz平面上运行,添加第三个初始点
y0
,y偏移量
y1
和初始误差
error0
。从相同函数修改的
DrawSphere
获取第三个初始点
y0
并调用
DrawCircle
而不是
DrawPixel
。
public static void DrawCircle(int x0, int y0, int z0, int y1, int radius, int error0)
{
int x = radius, z = 0;
int radiusError = error0;
while(x >= z)
{
z++;
if(radiusError<0)
{
radiusError+=2*z+1;
}
else
{
x--;
radiusError+=2*(z-x+1);
}
}
}
public static void DrawSphere(int x0, int y0, int z0, int radius)
{
int x = radius, y = 0;
int radiusError = 1-x;
while(x >= y)
{
DrawCircle(x0, y0, z0, y, x, radiusError);
y++;
if(radiusError<0)
{
radiusError+=2*y+1;
}
else
{
x--;
radiusError+=2*(y-x+1);
}
}
}
对于半径为4的球体(实际需要9x9x9),这将运行三次
DrawCircle
例程,第一次绘制典型的半径为4的圆形(三个步骤),第二次绘制初始误差为0的半径为4的圆形(同样是三个步骤),然后第三次绘制初始误差为0的半径为3的圆形(同样是三个步骤)。这最终将计算出九个点,每个点绘制32个像素。
这使得每个计算点需要执行32(每个圆的点数)x 3(每个点的加或减操作数)+ 6(每次迭代的加、减、移位操作数)= 102次基本算术运算。在这个例子中,每个圆有3个点 = 每层306个操作。半径算法还会增加每层6个操作,并迭代3次,因此对于半径为4的示例,总共需要
306 + 6 * 3 = 936
基本算术运算。
这里的成本是您将重复设置一些像素而无需进行其他条件检查(即x = 0、y = 0或z = 0),因此如果您的I/O速度较慢,则最好添加条件检查。假设所有LED都在开始时被清除,则示例圆将设置288个LED,而实际上由于重复设置而实际点亮的LED要少得多。
看起来这种方法在适合8x8x8网格中的所有球体上都比暴力方法表现更好,但暴力方法的时间始终保持一致,而这种方法在绘制大半径球体时会变慢,因为只有部分会显示。然而,随着显示立方体分辨率的增加,这种算法的计时将保持一致,而暴力方法将增加。
Solid - Erode(Solid)
应该可以解决问题。 - japreiss