Растеризация строк: покрыть все пиксели, независимо от градиента линии?

В принципе, я хочу использовать линейный алгоритм, чтобы определить, какие ячейки проверять наличие конфликтов для моего raycaster.

Bresenham не подходит для этого, поскольку он использует подход с единой толщиной, что означает, что он игнорирует ячейки, которые не находятся в наименьшая половина покрытия линии. Не очень важно, потому что это означает, что некоторые сегменты моей строки не проверяются на пересечения с ячейками, что приводит к ошибкам.

Я не могу найти никаких "толстых" алгоритмов, может ли кто-нибудь помочь мне найти его?

Red: Bad. Green: Good!
Зеленый: Что бы я хотел. Красный: что у меня сейчас есть и чего не хочу.

Ответ 1

У меня была такая же проблема, как у вас, и я нашел очень простое решение. Обычно, Bresenham имеет два последовательных, если определить, нужно ли увеличить координату для двух измерений:

public void drawLine(int x0, int y0, int x1, int y1, char ch) {
    int dx =  Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy, e2; // error value e_xy

    for (;;) {
        put(x0, y0, ch);

        if (x0 == x1 && y0 == y1) break;

        e2 = 2 * err;

        // horizontal step?
        if (e2 > dy) {
            err += dy;
            x0 += sx;
        }

        // vertical step?
        if (e2 < dx) {
            err += dx;
            y0 += sy;
        }
    }
}

Теперь все, что вам нужно сделать, это вставить else перед вторым if:

public void drawLineNoDiagonalSteps(int x0, int y0, int x1, int y1, char ch) {
    int dx =  Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
    int dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
    int err = dx + dy, e2;

    for (;;) {
        put(x0, y0, ch);

        if (x0 == x1 && y0 == y1) break;

        e2 = 2 * err;

        // EITHER horizontal OR vertical step (but not both!)
        if (e2 > dy) { 
            err += dy;
            x0 += sx;
        } else if (e2 < dx) { // <--- this "else" makes the difference
            err += dx;
            y0 += sy;
        }
    }
}

Теперь алгоритм не меняет обеих координат сразу. Я не тестировал это полностью, но, похоже, он работает очень хорошо.

Ответ 2

Этот поток старый, но я подумал, что стоит поместить его в Интернет:

// This prints the pixels from (x, y), increasing by dx and dy.
// Based on the DDA algorithm (uses floating point calculations).
void pixelsAfter(int x, int y, int dx, int dy)
{
    // Do not count pixels |dx|==|dy| diagonals twice:
    int steps = Math.abs(dx) == Math.abs(dy)
            ? Math.abs(dx) : Math.abs(dx) + Math.abs(dy);
    double xPos = x;
    double yPos = y;
    double incX = (dx + 0.0d) / steps;
    double incY = (dy + 0.0d) / steps;
    System.out.println(String.format("The pixels after (%d,%d) are:", x, y));
    for(int k = 0; k < steps; k++)
    {
        xPos += incX;
        yPos += incY;
        System.out.println(String.format("A pixel (%d) after is (%d, %d)",
            k + 1, (int)Math.floor(xPos), (int)Math.floor(yPos)));
    }
}

Ответ 3

Без ограничения общности предположим, что x2 >= x1, тогда

int x = floor(x1);
int y = floor(y1);
double slope = (x2 - x1) / (y2 - y1);
if (y2 >= y1) {
  while (y < y2) {
    int r = floor(slope * (y - y1) + x1);
    do {
      usepixel(x, y);
      ++x;
    } while (x < r);
    usepixel(x, y);
    ++y;
  }
}
else {
  while (y > y2) {
    int r = floor(slope * (y - y1) + x1);
    do {
      usepixel(x, y);
      ++x;
    } while (x < r);
    usepixel(x, y);
    --y;
  }
}

Напольные вызовы, вероятно, могут быть записаны так же, как приведение к целям.

Ответ 5

Как насчет Брешенема с дополнительным ограничением, что нельзя допускать никаких диагональных движений: Создайте точки с помощью традиционного алгоритма, затем в качестве этапа последующей обработки добавьте дополнительные шаги, необходимые для выполнения только ортогональных движений.

Ответ 6

Вы можете найти все пересечения вашего луча с горизонтальными линиями сетки, а затем пометить все ячейки в строке, которые либо имеют точку пересечения с одной стороны, либо находятся между двумя ячейками с пересечениями в строке.

Поиск пересечений можно сделать, начиная с начала координат, продвигая точку к первому пересечению (и маркируя ячейки в процессе), обнаруживая вектор, который переносит вас от пересечения к следующему (обе эти операции основные аналогичные треугольники (или тригг)), а затем продвигающиеся столбцы по столбцам, пока вы не пройдете достаточно далеко. Ускорение столбца по столбцу включает в себя одно добавление вектора для каждого столбца и небольшой цикл для заполнения ячеек между ними с пересечениями. Замените "отметку" на "процесс", если вы обрабатываете ячейки "на лету" - этот алгоритм гарантированно будет отмечать каждую ячейку только один раз.

То же самое можно сделать с вертикальными линиями, но сетки обычно хранятся в горизонтальных срезах, поэтому я выбрал это. Если вы используете триггер, вам нужно будет обрабатывать прямые горизонтальные линии со специальным случаем.

Кстати, насколько я знаю, именно так были сделаны старые сетчатые расикарные "3D" игры (например, Wolfenstein 3D). Я впервые прочитал об этом алгоритме из этой книги, эоны назад.