Только что столкнулся с той же проблемой, я могу подтвердить, что это возможно, как вы ожидали.
Во-первых, вернуться к простейшей форме алгоритма: (игнорируемых фракции, они исчезнут позже)
x = x0
y = y0
dx = x1 - x0
dy = y1 - y0
error = -0.5
while x < x1:
if error > 0:
y += 1
error -= 1
paint(x, y)
x += 1
error += dy/dx
Это означает, что для целыми координатами, мы начинаем половину пикселя над границей пикселя (error = -0.5
), и для каждого пикселя мы продвигаемся в x
, мы увеличиваем идеальную координату y (и, следовательно, ток error
) на dy/dx
.
Во-первых, давайте посмотрим, что произойдет, если мы перестанем принуждая x0
, y0
, x1
и y1
быть целыми числами: (это также будет предполагать, что вместо использования пиксельных центров, координаты относительно верхней левой части каждого пикселя, так как как только вы поддерживаете позиции Субпиксельных вы можете просто добавить половину ширину пикселя к й и у, чтобы вернуться к пиксельному центру логике)
x = x0
y = y0
dx = x1 - x0
dy = y1 - y0
error = (0.5 - (x0 % 1)) * dy/dx + (y0 % 1) - 1
while x < x1:
if error > 0:
y += 1
error -= 1
paint(x, y)
x += 1
error += dy/dx
Единственного изменение был расчетом начальной ошибки. Новое значение происходит от простого триггера для вычисления координаты y, когда x находится в центре пикселя. Стоит отметить, что вы можете использовать ту же самую идею, чтобы скопировать начальную позицию линии в пределах некоторой границы, что является еще одной проблемой, с которой вам, вероятно, придется столкнуться, когда вы хотите начать оптимизацию.
Теперь нам просто нужно преобразовать это в арифметику с целым числом. Нам понадобится фиксированный множитель для дробных входов (scale
), и деления можно обработать путем их умножения, как это делает стандартный алгоритм.
# assumes x0, y0, x1 and y1 are pre-multiplied by scale
x = x0
y = y0
dx = x1 - x0
dy = y1 - y0
error = (scale - 2 * (x0 % scale)) * dy + 2 * (y0 % scale) * dx - 2 * dx * scale
while x < x1:
if error > 0:
y += scale
error -= 2 * dx * scale
paint(x/scale, y/scale)
x += scale
error += 2 * dy * scale
Обратите внимание, что x
, y
, dx
и dy
сохранить тот же коэффициент масштабирования в качестве входных переменных (scale
), тогда как error
имеет более сложный коэффициент масштабирования: 2 * dx * scale
. Это позволяет ему поглощать деление и фракцию в своей первоначальной формулировке, но означает, что мы должны применять один и тот же масштаб везде, где мы его используем.
Очевидно, что здесь есть много возможностей для оптимизации, но это основной алгоритм. Если предположить, масштаб сила двоих детей (2^n
), мы можем начать, чтобы сделать вещи немного более эффективным:
dx = x1 - x0
dy = y1 - y0
mask = (1 << n) - 1
error = (2 * (y0 & mask) - (2 << n)) * dx - (2 * (x0 & mask) - (1 << n)) * dy
x = x0 >> n
y = y0 >> n
while x < (x1 >> n):
if error > 0:
y += 1
error -= 2 * dx << n
paint(x, y)
x += 1
error += 2 * dy << n
Как и в оригинале, это работает только в (х> = у, х > 0, y> = 0) октант. Обычные правила применяются для его распространения во всех случаях, но обратите внимание, что есть несколько дополнительных gotchyas из-за того, что координаты больше не центрируются в пикселе (т. Е. Отражения становятся более сложными).
Вам также необходимо следить за целыми переполнениями: error
имеет в два раза точность входных переменных и диапазон до удвоенной длины строки. Планируйте свои входы, точность и переменные типы соответственно!
Я полагаю, что можно применить коэффициент масштабирования с фиксированной точкой и исчислить начальный коэффициент ошибки из дробной части начальной точки. Я не вижу преимущества Bresenham над более легкими алгоритмами, хотя, скажем, непосредственно оценивая функцию или DDA с фиксированной точкой? Оптимизация для установки кажется маловероятной победой, если ваши средние линии не будут крошечными, и мне было бы трудно отказаться от разделения на вычисление семени ошибок в любом случае. – doynax
Хотя DDA с фиксированной точкой, вероятно, является хорошим решением, я бы хотел убедиться, что применение предвзятости к методу Брешенема, как описано в вопросе, по какой-то причине нецелесообразно. Я не могу сделать слишком много предположений о том, сколько и сколько строк могут быть, но я хотел бы поддерживать примерно такую же производительность, кроме небольшого числа ударов по начальной настройке. – ideasman42
Я предполагаю, что вы выбрали 'e = frac (x1) * (y2 - y1) + frac (y1) * (x2 - x1)', хотя и с координатами в фиксированной точке, чтобы избежать ошибок округления. Моя точка зрения заключается в том, что внутренняя точка DDA (в основном, добавление и смена) обычно быстрее, чем Bresenham, причем недостатком является небольшой удар по инициализации, который обычно не имеет значения, если ваши строки не очень короткие или деление чрезвычайно дорого. – doynax