Рисование эллипса с помощью алгоритма Брезенхэма

Здравствуйте,

Я пытаюсь нарисовать эллипс, параллельный ортогональной системе, используя алгоритм Брезенхэма. Я хочу нарисовать верхнюю левую (W,SW,S) четверть эллипса, а затем вывести другие.

что я хочу нарисовать

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

Проблема появляется, когда прорисовывается 2-й регион, и я не знаю, откуда он берется.

Вы можете видеть, что у меня есть (черный) и что я ожидаю (зеленый): (центр эллипса (xc, yc) и верхняя правая кнопка (x2,y2), то есть ~(xc+30,yc+20) в этом примере) DOTS(a – abs (x2-xc), а b – abs (y2-yc) ) Первый параметр — это середина эллипса (xc, yc), второй — правая верхняя точка, определяющая радиусы x и y. Вы можете видеть, что эллипс заходит слишком далеко (по 2 точки слева и справа). Вы можете увидеть другой пример (центр эллипса (xc, yc) и верхняя правая кнопка (x2,y2), которая в этом примере ~(xc+15,yc+18)) oexa

Алгоритм выводится из инкрементного алгоритма с логикой второго порядка.

Вот мой код (a - это абс (x2-xc), а b - это абс (y2-yc))

ellipse(int a, int b, int xc, int yc) {
    int a2 = a*a, b2 = b*b;
    int x = 0, y = b; //Starting point

    int incSW = b2*2 + a2*2;

    int deltaW = b2*(-2*x + 3); //deduced from incremental algorithm with the second-order logic
    int deltaS = a2*(-2*y + 3);
    int deltaSW = deltaW + deltaS;

    int d1 = b2 - a2*b + a2/4; //dp starting value in the first region
    int d2 = b2*(x - 0.5)*(x - 0.5) + a2*(y - 1)*(y - 1) - a2*b2; //dp starting value in the second region

    //First region
    while(a2*(y-0.5) >= b2*(-x-1)) {
        DrawPixel(g,-x+xc, -y+yc); // 1st case
        DrawPixel(g,-x+xc, y+yc); // 2nd case
        DrawPixel(g,x+xc, y+yc); // 3rd case
        DrawPixel(g,x+xc, -y+yc); // 4th case
        if(d1>0) {
            d1+=deltaSW;
            deltaW+=b2*2;
            deltaSW+=incSW;
            y--;
        }
        else {
            d1+=deltaW;
            deltaW+=2*b2;
            deltaSW+=2*b2;
        }
        x--;
    }

    deltaSW = b2*(2 - 2*x) + a2*(-2*y + 3);

    //Second region
    while(y>=0) {
        DrawPixel(g,-x+xc, -y+yc); // 1st case
        DrawPixel(g,-x+xc, y+yc); // 2nd case
        DrawPixel(g,x+xc, y+yc); // 3rd case
        DrawPixel(g,x+xc, -y+yc); // 4th case
        if(d2>0) {
            d2+=deltaS;
            deltaS+=a2*2;
            deltaSW+=a2*2;
        }
        else {
            d2+=deltaSW;
            deltaSW+=incSW;
            deltaS+=a2*2;
            x--;
        }
        y--;
    }
}

Я надеюсь, что вы можете мне помочь, спасибо.


person OrsCrous    schedule 26.03.2018    source источник
comment
Вы, вероятно, должны также добавить к вопросу числовые значения параметров для ваших примеров изображений.   -  person SergGr    schedule 26.03.2018
comment
Хорошо, я только что сделал это :)   -  person OrsCrous    schedule 26.03.2018
comment
OrsCrous, это не числовые значения! Вы ожидаете, что читатели будут считать пиксели на изображении вручную? Или как другие должны получать значения для a и b?   -  person SergGr    schedule 26.03.2018
comment
я думаю, что я сделал что-то лучше, спасибо   -  person OrsCrous    schedule 26.03.2018
comment
top-left (W,SW,S) если я возьму W за запад: что означает S?   -  person greybeard    schedule 27.03.2018
comment
S для юга. Если вы находитесь в точке 0,b, у вас есть 3 варианта: нарисовать запад, юго-запад или юг.   -  person OrsCrous    schedule 27.03.2018
comment
(Это могло быть рисование в западной/южной полуплоскости, в юго-западном квадранте - среди бесчисленных других вещей.) Смещения (особенно +3) выглядят подозрительно - есть ли представление алгоритма, которому вы пытаетесь следовать? (Я никогда не уверен, использовать ли две петли или одну - даже после факторизации draw(cx, cy, x, y).)   -  person greybeard    schedule 27.03.2018
comment
Я пытаюсь сделать что-то подобное cpp.edu/~raheja/CS445/MEA.pdf , но наоборот (от (0,b) до (-a,0), где a — горизонтальный радиус, а b — вертикальный радиус)   -  person OrsCrous    schedule 28.03.2018


Ответы (1)


Используя член ошибки e = ax^2 + by^2 - r^2, довольно легко показать, что шаг от (x,y) до (x,y+1) изменяет ошибку на 2by + b, шаг до (x+1,y+1) на 2ax + a + 2by + b и шаг до (x+1,y) на 2ax + a.

Начиная с точки (-x0, 0), выберите шаг с наименьшей абсолютной ошибкой из этих трех. Первые два случая - норма для "первого региона", как вы его называете.

В первый раз, когда шаг вправо, от (x,y) до (x+1,y), дает наименьшую ошибку, вы знаете, что находитесь во второй области. На данный момент первый случай больше не нужен. Четверть эллипса можно закончить, используя только вторые два случая.

Обратите внимание, что эта проверка позволяет избежать операций с плавающей запятой, которые вы использовали. Весь смысл алгоритмов Брезенхема заключается в том, чтобы избегать операций с плавающей запятой.

Последнее, на что следует обратить внимание, это то, что вы не хотите вычислять 2ax или 2by на каждой итерации. Умножений можно избежать, сохраняя переменные, скажем, dx=2ax и dy=2by, и обновляя их. Шаг от x до x+1 увеличивает dx на 2a, константу. Точно так же шаг от y до y+1 увеличивает dy на 2b.

Собрав все это вместе, вы получите (грубый) код ниже.

Обратите внимание, что вы можете проверить вычисление инкрементной ошибки, сравнив его с исходным членом ошибки. Если (x0,0) — начальная точка, то вы знаете, что x0^2 = r^2. Таким образом, фактическая ошибка на каждой итерации равна a * x^2 + b * y^2 - x0^2. Это должно быть равно e в приведенном ниже коде, и это так.

import static java.lang.Math.abs;
import java.util.Arrays;
import java.util.function.BiConsumer;

public class EllipseTracer {
  static char [] [] raster = new char[51][101]; 

  static void trace(int x, int y, int a, int b, BiConsumer<Integer, Integer> emitter) {
    emitter.accept(x, y);
    int e = 0;
    int dx = 2 * a * x;
    int dy = 2 * b * y;
    // First region: stepping north and northeast.
    while (x < 0) {
      int dxa = dx + a;
      int dyb = dy + b;
      int eUp = e + dyb;
      int eRt = e + dxa;
      int eDg = e + dxa + dyb;
      if (abs(eUp) < abs(eDg)) {
        emitter.accept(x, ++y);
        e = eUp;
        dy += 2 * b;
      } else {
        if (abs(eRt) < abs(eDg)) {
          // Step east is least error. Found second region.
          emitter.accept(++x, y);
          e = eRt;
          dx += 2 * a;
          break;
        }
        emitter.accept(++x, ++y);
        e = eDg;
        dy += 2 * b;
        dx += 2 * a;
      }
    }
    // Second region: step northeast and east.
    while (x < 0) {
      int dxa = dx + a;
      int dyb = dy + b;
      int eRt = e + dxa;
      int eDg = e + dxa + dyb;
      if (abs(eRt) < abs(eDg)) {
        emitter.accept(++x, y);
        e = eRt;
        dx += 2 * a;
      } else {
        emitter.accept(++x, ++y);
        e = eDg;
        dy += 2 * b;
        dx += 2 * a;
      }
    }
  }

  static void emit(int x, int y) {
    raster[y][x + 100] = '*';
  }

  public static void main(String [] args) {
    for (int i = 0; i < raster.length; ++i) {
      Arrays.fill(raster[i], ' ');
    }
    trace(-100, 0, 1, 4, EllipseTracer::emit);
    for (int i = 0; i < raster.length; ++i) {
      System.out.println(raster[i]);
    }
  }
}

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

person Gene    schedule 28.03.2018
comment
Итак, если мне нужно сделать это от 0,b до -a,0, я думаю, мне просто нужно добавить немного - и это должно работать. Я попробую - person OrsCrous; 28.03.2018
comment
@OrsCrous Нет, конечно, вы можете посчитать в любом квадранте, который захотите. Я выбрал это, потому что это делает все наклоны положительными, поэтому нет возможности ошибиться с отрицательным знаком. - person Gene; 28.03.2018
comment
@OrsCrous Если этот ответ полезен, было бы неплохо, если бы вы его приняли. - person Gene; 30.03.2018