【问题标题】:How can you write an algorithm to properly fill a circle using lines from the center?您如何编写算法以使用中心线正确填充圆?
【发布时间】:2026-01-04 21:50:01
【问题描述】:

目前我尝试编写代码来计算您可以看到的屏幕部分以及那些因为在 2d 中阻挡光线的物体而不能看到的部分,例如在我们当中:

代码应在规格非常低的处理器(至少在 2020 年)C64 上运行。在如此简单的 CPU 上,对于游戏来说,完成如此复杂的数学运算是不可能的,所以我想出了一个想法:首先,我让一切都基于 tile,这使得处理更容易,也意味着我可以改变整个字符或其颜色单元格。然后我只是在 Processing 中为 PC 编写代码(这是一种类似于 Java 但更易于使用的编码语言)来计算光线的移动方式(下图应该更容易理解),首先是一个矩形(和一个单象限):

然后我编写了一些完全混乱的汇编代码,用于使用记录的坐标,根据当前在射线上绘制的射线的数量,用倒置字符填充瓷砖,直到它们击中一个对象(/它想要的瓷砖填充不是倒置的,也不是空格),然后转到下一条射线。我将半径减小到 7,所以它只占用 256 个字节,这对 ASM 很有用。这完全奏效了,我能够修复每一个错误,结果令人印象深刻,因为我需要添加暂停语句,否则一切都运行得太快以至于你什么都看不到。

成功后,我用圆圈试了一下,使用以下代码设置点:

int pointNum = ceil(radius * PI * 2); // calculates the circumference
for(int i = 0;i < pointNum;i++){
  float angle = map(i, 0, pointNum, 0, PI*2);
  setPixel(sin(angle) * radius, cos(angle) * radius);
}

我以前使用过 Bresenham 圆算法,但效果不太好,所以我尝试了一种更简单的方法。所以...

所有标记的黑色图块都不会被任何光线击中,这是一个相当大的问题,因为在游戏中你只是看不到这些图块没有多大意义。我使用的代码,写在Processing,是:

float[] xPoints = new float[0];
float[] yPoints = new float[0];
float[] xPointsT;
float[] yPointsT;
float[] xPointsHad = new float[0];
float[] yPointsHad = new float[0];
int pos = 0;
float interpolPos = 0;

int radius = 12;
float tileSize = 800.0 / (2*radius+1);

String output = "  !byte ";
int pointNum = ceil(radius * PI * 2);
void setup() {
  size(800, 800);
  frameRate(60);
  xPointsT = new float[0];
  yPointsT = new float[0];
  /*for(int i = 0;i <= radius;i++){
    setPixel(radius, i);
    setPixel(i, radius);
  }*/ //Uncomment this and comment the next 4 lines to get the rectangle version
  for(int i = 0;i < pointNum;i++){
    float angle = map(i, 0, pointNum, 0, PI*2);
    setPixel(sin(angle) * radius, cos(angle) * radius);
  }
  xPoints = concat(xPoints, xPointsT);
  yPoints = concat(yPoints, yPointsT);
}
void draw(){
  if(interpolPos > radius){
    pos++;
    interpolPos = 0;
    println(output);
    output = "  !byte ";
  }
  float x=0, y=0;
  float interpolMul = interpolPos / radius;
  x = xPoints[pos] * interpolMul;
  y = yPoints[pos] * interpolMul;
  interpolPos+=1;//sorta the resolution
  background(0);
  
  stroke(255);
  for(int i = 0;i < 2*radius+1;i++){
    for(int j = 0;j < 2*radius+1;j++){
      if((round(x) + radius) == i && (round(y) + radius) == j){
        fill(0, 255, 0);
        if(output != "  !byte ")
          output += ", ";
        output += i-radius;
        output += ", ";
        output += j-radius;
        xPointsHad = append(xPointsHad, i);
        yPointsHad = append(yPointsHad, j);
      }
      else{
        int fillVal = 0;
        for(int k = 0; k < xPoints.length;k++){
          if(round(xPoints[k])+radius == i && round(yPoints[k])+radius == j){
            fillVal += 64;
          }
        }
        fill(0, 0, fillVal);
        if(fillVal == 0){
          for(int k = 0; k < xPointsHad.length;k++){
            if(round(xPointsHad[k]) == i && round(yPointsHad[k]) == j){
              fill(128, 0, 0);
            }
          }
        }
      }
      rect(i * tileSize, j * tileSize, tileSize, tileSize);
    }
  }
  
  strokeWeight(3);
  stroke(0, 255, 255, 64);
  for(int i = 0;i < xPoints.length;i++){
    line((float(radius)+0.5) * tileSize, (float(radius)+0.5) * tileSize, (float(radius)+0.5+xPoints[i]) * tileSize, (float(radius)+0.5+yPoints[i]) * tileSize);
  }
  strokeWeight(1);
  
  fill(255, 255, 0);
  ellipse((x + radius + 0.5) * tileSize, (y + radius + 0.5) * tileSize, 10, 10);
}

void setPixel(float _x, float _y){
  for(int i = 0; i < xPoints.length;i++){
    if(_x == xPoints[i] && _y == yPoints[i]){
      return;
    }
  }
  for(int i = 0; i < xPointsT.length;i++){
    if(_x == xPointsT[i] && _y == yPointsT[i]){
      return;
    }
  }
  
  xPointsT = append(xPointsT, _x);
  yPointsT = append(yPointsT, _y);
}

(获取矩形的说明在代码中) 那些提到的瓷砖似乎永远不会被击中,因为它们上面的光线只是跳过它们,但我能做些什么来防止这种情况发生呢?你可以减少 interpolPos+=x;打更多的瓷砖,因为这样你的步数更小,但这会浪费相当多的空间,所以我认为这不是一个好的解决方案。理想情况下,您也可以减少绘制的坐标数以获得更小的视野。有没有人知道如何做到这一点?

【问题讨论】:

    标签: algorithm math geometry processing fill


    【解决方案1】:

    好的,我找到了适合我的情况:我只使用了完全有效的部分(矩形),然后通过忽略距离光源较远的每一个瓷砖撞击然后半径来制作一个圆圈+ 0.5,因为没有 + .5,圆圈看起来很奇怪。你可以自己试试,代码如下:

    float[] xPoints = new float[0];
    float[] yPoints = new float[0];
    float[] xPointsT;
    float[] yPointsT;
    float[] xPointsHad = new float[0];
    float[] yPointsHad = new float[0];
    int pos = 0;
    float interpolPos = 0;
    
    int radius = 7;
    float tileSize = 800.0 / (2*radius+1);
    
    int pointNum = ceil(radius * PI * 2);
    
    String standardOutput = "  !align 15,0\n  !byte ";
    void setup() {
      size(800, 800);
      frameRate(60);
      xPointsT = new float[0];
      yPointsT = new float[0];
      for(int i = 0;i <= radius;i++){
        setPixel(radius, i);
        setPixel(i, radius);
      } //Uncomment this and comment the next 4 lines to get the rectangle version
      /*for(int i = 0;i < pointNum;i++){
        float angle = map(i, 0, pointNum, 0, PI*2);
        setPixel(sin(angle) * radius, cos(angle) * radius);
      }*/
      xPoints = concat(xPoints, xPointsT);
      yPoints = concat(yPoints, yPointsT);
      
      xPointsT = new float[0];
      yPointsT = new float[0];
    }
    void draw(){
      if(interpolPos > radius){
        pos++;
        interpolPos = 0;
        String output = standardOutput;
        for(int i = 0;i < radius + 1;i++){
          int indexPos = floor(map(i, 0, radius + 1, 0, xPointsT.length));
          output += round(xPointsT[indexPos]);
          output += ",";
          output += round(yPointsT[indexPos]);
          if(i < radius){
            output += ", ";
          }
        }
        println(output);
        xPointsT = new float[0];
        yPointsT = new float[0];
      }
      float x=0, y=0;
      float interpolMul = interpolPos / radius;
      x = xPoints[pos] * interpolMul;
      y = yPoints[pos] * interpolMul;
      interpolPos+=1;//sorta the resolution
      background(0);
      
      stroke(255);
      for(int i = 0;i < 2*radius+1;i++){
        for(int j = 0;j < 2*radius+1;j++){
          if((round(x) + radius) == i && (round(y) + radius) == j && sqrt(sq(round(x)) + sq(round(y))) < radius + 0.5){
            fill(0, 255, 0);
            xPointsT = append(xPointsT, i-radius);
            yPointsT = append(yPointsT, j-radius);
            xPointsHad = append(xPointsHad, i);
            yPointsHad = append(yPointsHad, j);
          }
          else{
            int fillVal = 0;
            for(int k = 0; k < xPoints.length;k++){
              if(round(xPoints[k])+radius == i && round(yPoints[k])+radius == j){
                fillVal += 64;
              }
            }
            fill(0, 0, fillVal);
            if(fillVal == 0){
              for(int k = 0; k < xPointsHad.length;k++){
                if(round(xPointsHad[k]) == i && round(yPointsHad[k]) == j){
                  fill(128, 0, 0);
                }
              }
            }
          }
          rect(i * tileSize, j * tileSize, tileSize, tileSize);
        }
      }
      
      strokeWeight(3);
      stroke(0, 255, 255, 64);
      for(int i = 0;i < xPoints.length;i++){
        line((float(radius)+0.5) * tileSize, (float(radius)+0.5) * tileSize, (float(radius)+0.5+xPoints[i]) * tileSize, (float(radius)+0.5+yPoints[i]) * tileSize);
      }
      strokeWeight(1);
      
      fill(255, 255, 0);
      ellipse((x + radius + 0.5) * tileSize, (y + radius + 0.5) * tileSize, 10, 10);
    }
    
    void setPixel(float _x, float _y){
      for(int i = 0; i < xPoints.length;i++){
        if(_x == xPoints[i] && _y == yPoints[i]){
          return;
        }
      }
      for(int i = 0; i < xPointsT.length;i++){
        if(_x == xPointsT[i] && _y == yPointsT[i]){
          return;
        }
      }
      
      xPointsT = append(xPointsT, _x);
      yPointsT = append(yPointsT, _y);
    }
    

    除了忽略不在圆圈中的图块的主要区别之外,我还更改了我将坐标存储在两个数组中而不是字符串中,因为当半径小于半径 + 1 时,我使用代码来拉伸它们点,所以我不必在 C64 的 RAM 中存储多个不同大小的圆,所以它满足了我的主要要求:它应该填充每个图块,并且应该通过忽略光线末端的一些点来缩小比例。如果有效吗?呃......可能有一个更好的解决方案,用更少的光线填充圆圈,但我不太在乎。不过,如果你有一个想法,如果你能告诉我就好了,否则这个问题就解决了。

    编辑:我忘了添加图片。不要迷茫,我在贴出来后修改了代码,这样你也可以看到圆圈上的蓝色方块了。

    【讨论】:

      【解决方案2】:

      您选择了错误的方法来查找所有接触的单元格 - 而不是基于点的方式,您需要基于单元格(正方形)的方法 - 射线与矩形相交而不是点。

      There is article 来自 Amanatides 和 Woo “用于光线追踪的快速体素遍历算法”,用于 2D。

      Practical implementation.

      例子:

      快速制作的跟踪示例。从左上角发出的光线会到达蓝点。如果光线遇到黑细胞障碍物,它就会停止。粉红色的细胞会被光线照亮,灰色的则不会。

      【讨论】:

      • 我没有使用 Bresenham 和正常的填充圆圈的方式,因为线条必须从中心开始。我不知道您是否阅读了整篇文章(如果您没有阅读我可以理解)但我想模拟光线的传播方式,在这种情况下使用垂直线或水平线并不太好,我想要浮动圆的结果,而不是 Bresenham 创建的圆的 int 值。另外,像素不是正方形和体素立方体吗?而且我不太明白如何检测蓝色方块。
      • 是的,我确实阅读了整篇文章,但不确定线条必须从中心开始。好的,我将删除帖子的第二部分。也许我犯了错误的矛盾 - 只是想强调你不能像这样标记单元格检查点if(round(xPoints[k])+radius == i &amp;&amp; round(yPoints[k])+radius == j)
      • 好的,但是我怎样才能准确地检测到蓝色瓷砖?
      • 模糊方块被触摸但不与边缘相交(我需要将这些情况分开)。也许你不需要它们。当射线穿过网格点时,这些单元格被注册(我也沿着边缘分离了水平和垂直线的情况)
      • @laancelot 我目前仍在上面编写代码(我住在德国,出于某种原因,我的父母不希望我在晚上 7:30 之后使用我的电脑,这就是我被打断的原因)但我认为我走在正确的道路上,可以在几个小时内分享一个可能并不完美但可行的解决方案。