Algoritmo eficiente para obter pontos em um círculo em torno de um centro

8

Problema

Eu quero obter todos os pixels que estão dentro de um círculo de um determinado raio sobre um determinado ponto, onde os pontos podem ter apenas coordenadas inteiras , como pixels em uma tela.

Ilustração

Então, eu quero obter todos os pontos na área amarela dada (x, y)e r.

Abordagens

A maneira mais eficiente em que consigo pensar é percorrer um quadrado (x, y)e verificar a distância euclidiana de cada ponto:

for (int px = x - r; px <= x + r; px++) {
  for (int py = y - r; py <= y + r; py++) {
    int dx = x - px, dy = y - py;

    if (dx * dx + dy * dy <= r * r) {
      // Point is part of the circle.
    }
  }
}

No entanto, isso significa que esse algoritmo verificará (r * 2)^2 * (4 - pi) / 4pixels que não fazem parte do círculo. dx * dx + dy * dy <= r * r, que parece bastante caro, é chamado de forma redundante quase 1 / 4na maioria das vezes.

A integração de algo como o que foi proposto aqui pode melhorar o desempenho:

for (int px = x - r; px <= x + r; px++) {
  for (int py = y - r; py <= y + r; py++) {
    int dx = abs(x - px), dy = abs(y - py);

    if (dx + dy <= r || (!(dx > r || dy > r) && (dx * dx + dy * dy <= r * r))) {
      // Point is part of the circle.
    }
  }
}

No entanto, como o próprio autor apontou, isso provavelmente não será mais rápido quando a maioria dos pontos estiver dentro do círculo (especialmente por causa de abs), que pi / 4são neste caso.


Não consegui encontrar nenhum recurso sobre esta questão. Estou procurando especificamente uma solução em C ++ e não algo em SQL .

creativecreatorormaybenot
fonte
1
A multiplicação e adição de números inteiros é bastante rápida. Não tenho certeza se adicionar muitas comparações extras ajudará, principalmente por causa de todas as possíveis falhas de previsão de ramificação que ela possa apresentar.
François Andrieux
A pergunta que faz "O mais" ou "O melhor" geralmente não pode ser respondida definitivamente, porque raramente existe uma única melhor resposta. Qual a melhor solução pode variar com base no caso de uso e na arquitetura em que será executada.
François Andrieux
3
Aproveite o fato de que conhecer o intervalo de valores de x para um determinado valor de y fornece informações sobre os intervalos de valores de x para os valores de y anteriores e seguintes.
Scott Hunter
1
Você deve pelo menos ser capaz de interromper o loop depois de passar do ponto anterior sendo bom para o próximo ponto ruim. Nesse ponto, você sabe que cruzou a fronteira e não há mais bons pontos nessa linha.
NathanOliver 5/03
1
Outra opção, para fazer o exemplo de Scotts, comece no meio superior ou inferior e, em seguida, faça um loop para a esquerda e para a direita até atingir o limite. Isso significa que seus loops internos param assim que você ultrapassa o limite.
NathanOliver 5/03

Respostas:

4

Tudo bem, aqui estão os benchmarks que prometi.

Configuração

Eu usei o google benchmark e a tarefa era inserir todos os pontos dentro do perímetro do círculo em um std::vector<point>. Eu comparo para um conjunto de raios e um centro constante:

radii = {10, 20, 50, 100, 200, 500, 1000}
center = {100, 500}
  • idioma: C ++ 17
  • compilador: msvc 19.24.28316 x64
  • plataforma: windows 10
  • otimização: O2 (otimização completa)
  • rosqueamento: execução de thread único

Os resultados de cada algoritmo são testados quanto à correção (em comparação com a saída do algoritmo de OPs).

Até agora, os seguintes algoritmos são comparados:

  1. Algoritmo do OP enclosing_square.
  2. Meu algoritmo containing_square .
  3. creativecreatorormayben do algoritmo edge_walking .
  4. Algoritmo de Mandy007 binary_search .

Resultados

Run on (12 X 3400 MHz CPU s)
CPU Caches:
  L1 Data 32K (x6)
  L1 Instruction 32K (x6)
  L2 Unified 262K (x6)
  L3 Unified 15728K (x1)
-----------------------------------------------------------------------------
Benchmark                                   Time             CPU   Iterations
-----------------------------------------------------------------------------
binary_search/10/manual_time              804 ns         3692 ns       888722
binary_search/20/manual_time             2794 ns        16665 ns       229705
binary_search/50/manual_time            16562 ns       105676 ns        42583
binary_search/100/manual_time           66130 ns       478029 ns        10525
binary_search/200/manual_time          389964 ns      2261971 ns         1796
binary_search/500/manual_time         2286526 ns     15573432 ns          303
binary_search/1000/manual_time        9141874 ns     68384740 ns           77
edge_walking/10/manual_time               703 ns         5492 ns       998536
edge_walking/20/manual_time              2571 ns        49807 ns       263515
edge_walking/50/manual_time             15533 ns       408855 ns        45019
edge_walking/100/manual_time            64500 ns      1794889 ns        10899
edge_walking/200/manual_time           389960 ns      7970151 ns         1784
edge_walking/500/manual_time          2286964 ns     55194805 ns          308
edge_walking/1000/manual_time         9009054 ns    234575321 ns           78
containing_square/10/manual_time          629 ns         4942 ns      1109820
containing_square/20/manual_time         2485 ns        40827 ns       282058
containing_square/50/manual_time        15089 ns       361010 ns        46311
containing_square/100/manual_time       62825 ns      1565343 ns        10990
containing_square/200/manual_time      381614 ns      6788676 ns         1839
containing_square/500/manual_time     2276318 ns     45973558 ns          312
containing_square/1000/manual_time    8886649 ns    196004747 ns           79
enclosing_square/10/manual_time          1056 ns         4045 ns       660499
enclosing_square/20/manual_time          3389 ns        17307 ns       206739
enclosing_square/50/manual_time         18861 ns       106184 ns        37082
enclosing_square/100/manual_time        76254 ns       483317 ns         9246
enclosing_square/200/manual_time       421856 ns      2295571 ns         1654
enclosing_square/500/manual_time      2474404 ns     15625000 ns          284
enclosing_square/1000/manual_time     9728718 ns     68576389 ns           72

Código

O código de teste completo está abaixo, você pode copiá-lo e colá-lo e testá-lo você mesmo. fill_circle.cppcontém a implementação dos diferentes algoritmos.

main.cpp

#include <string>
#include <unordered_map>
#include <chrono>

#include <benchmark/benchmark.h>

#include "fill_circle.hpp"

using namespace std::string_literals;

std::unordered_map<const char*, circle_fill_func> bench_tests =
{
    {"enclosing_square", enclosing_square},
    {"containing_square", containing_square},
    {"edge_walking", edge_walking},
    {"binary_search", binary_search},
};

std::vector<int> bench_radii = {10, 20, 50, 100, 200, 500, 1000};

void postprocess(std::vector<point>& points)
{
    std::sort(points.begin(), points.end());
    //points.erase(std::unique(points.begin(), points.end()), points.end());
}

std::vector<point> prepare(int radius)
{
    std::vector<point> vec;
    vec.reserve(10ull * radius * radius);
    return vec;
}

void bm_run(benchmark::State& state, circle_fill_func target, int radius)
{
    using namespace std::chrono;
    constexpr point center = {100, 500};

    auto expected_points = prepare(radius);
    enclosing_square(center, radius, expected_points);
    postprocess(expected_points);

    for (auto _ : state)
    {
        auto points = prepare(radius);

        auto start = high_resolution_clock::now();
        target(center, radius, points);
        auto stop = high_resolution_clock::now();

        postprocess(points);
        if (expected_points != points)
        {
            auto text = "Computation result incorrect. Expected size: " + std::to_string(expected_points.size()) + ". Actual size: " + std::to_string(points.size()) + ".";
            state.SkipWithError(text.c_str());
            break;
        }

        state.SetIterationTime(duration<double>(stop - start).count());
    }
}

int main(int argc, char** argv)
{
    for (auto [name, target] : bench_tests)
        for (int radius : bench_radii)
            benchmark::RegisterBenchmark(name, bm_run, target, radius)->Arg(radius)->UseManualTime();

    benchmark::Initialize(&argc, argv);
    if (benchmark::ReportUnrecognizedArguments(argc, argv))
        return 1;
    benchmark::RunSpecifiedBenchmarks();
}

fill_circle.hpp

#pragma once

#include <vector>

struct point
{
    int x = 0;
    int y = 0;
};

constexpr bool operator<(point const& lhs, point const& rhs) noexcept
{
    return lhs.x != rhs.x
               ? lhs.x < rhs.x
               : lhs.y < rhs.y;
}

constexpr bool operator==(point const& lhs, point const& rhs) noexcept
{
    return lhs.x == rhs.x && lhs.y == rhs.y;
}

using circle_fill_func = void(*)(point const& center, int radius, std::vector<point>& points);

void enclosing_square(point const& center, int radius, std::vector<point>& points);
void containing_square(point const& center, int radius, std::vector<point>& points);
void edge_walking(point const& center, int radius, std::vector<point>& points);
void binary_search(point const& center, int radius, std::vector<point>& points);

fill_circle.cpp

#include "fill_circle.hpp"

constexpr double sqrt2 = 1.41421356237309504880168;
constexpr double pi = 3.141592653589793238462643;

void enclosing_square(point const& center, int radius, std::vector<point>& points)
{
    int sqr_rad = radius * radius;

    for (int px = center.x - radius; px <= center.x + radius; px++)
    {
        for (int py = center.y - radius; py <= center.y + radius; py++)
        {
            int dx = center.x - px, dy = center.y - py;
            if (dx * dx + dy * dy <= sqr_rad)
                points.push_back({px, py});
        }
    }
}

void containing_square(point const& center, int radius, std::vector<point>& points)
{
    int sqr_rad = radius * radius;
    int half_side_len = radius / sqrt2;
    int sq_x_end = center.x + half_side_len;
    int sq_y_end = center.y + half_side_len;

    // handle inner square
    for (int x = center.x - half_side_len; x <= sq_x_end; x++)
        for (int y = center.y - half_side_len; y <= sq_y_end; y++)
            points.push_back({x, y});

    // probe the rest
    int x = 0;
    for (int y = radius; y > half_side_len; y--)
    {
        int x_line1 = center.x - y;
        int x_line2 = center.x + y;
        int y_line1 = center.y - y;
        int y_line2 = center.y + y;

        while (x * x + y * y <= sqr_rad)
            x++;

        for (int i = 1 - x; i < x; i++)
        {
            points.push_back({x_line1, center.y + i});
            points.push_back({x_line2, center.y + i});
            points.push_back({center.x + i, y_line1});
            points.push_back({center.x + i, y_line2});
        }
    }
}

void edge_walking(point const& center, int radius, std::vector<point>& points)
{
    int sqr_rad = radius * radius;
    int mdx = radius;

    for (int dy = 0; dy <= radius; dy++)
    {
        for (int dx = mdx; dx >= 0; dx--)
        {
            if (dx * dx + dy * dy > sqr_rad)
                continue;

            for (int px = center.x - dx; px <= center.x + dx; px++)
            {
                for (int py = center.y - dy; py <= center.y + dy; py += 2 * dy)
                {
                    points.push_back({px, py});
                    if (dy == 0)
                        break;
                }
            }

            mdx = dx;
            break;
        }
    }
}

void binary_search(point const& center, int radius, std::vector<point>& points)
{
    constexpr auto search = []( const int &radius, const int &squad_radius, int dx, const int &y)
    {
        int l = y, r = y + radius, distance;

        while (l < r)
        {
            int m = l + (r - l) / 2;
            distance = dx * dx + (y - m) * (y - m);
            if (distance > squad_radius)
                r = m - 1;
            else if (distance < squad_radius)
                l = m + 1;
            else
                r = m;
        }

        if (dx * dx + (y - l) * (y - l) > squad_radius)
            --l;

        return l;
    };

    int squad_radius = radius * radius;    
    for (int px = center.x - radius; px <= center.x + radius; ++px)
    {
        int upper_limit = search(radius, squad_radius, px - center.x, center.y);
        for (int py = 2*center.y - upper_limit; py <= upper_limit; ++py)
        {
            points.push_back({px, py});
        }
    }
}
Timo
fonte
2
@creativecreatorormaybenot np. Edite esta resposta se você ajustar os algoritmos para que possamos atualizar os resultados aqui.
Timo
1
@ Mandy007 Atualizei os benchmarks.
Timo
2
for (line = 1; line <= r; line++) {
   dx = (int) sqrt(r * r - line * line);
   for (ix = 1; ix <= dx; ix++) {
       putpixel(x - ix, y + line)
       putpixel(x + ix, y + line)
       putpixel(x - ix, y - line)
       putpixel(x + ix, y - line)
   } 
}

Para evitar a geração repetida de pixels nos eixos, vale a pena iniciar loops a partir de 1 e desenhar linhas centrais (ix == 0 ou linha == 0) em um loop separado.

Observe que também existe o algoritmo inteiro Bresenham para gerar pontos de circunferência.

MBo
fonte
Não há ganho real no uso de simetria aqui. Alterei o código para gerar um quarto de círculo.
MBo 5/03
2

Tudo bem, primeiro calculamos o quadrado interno do círculo. A fórmula para isso é simples:

x² + y² = r²    // circle formula
2h² = r²        // all sides of square are of equal length so x == y, lets define h := x
h = r / sqrt(2) // half side length of the inner square

Agora, todos os pontos entre (-h, -h)e (+h, +h)estão dentro do círculo. Aqui está uma imagem do que quero dizer:

1

A parte azul restante é um pouco complicada, mas também não é muito complicada. Começamos no topo do círculo azul (x = 0, y = -radius). Em seguida, caminhamos à direita ( x++) até deixarmos o perímetro do círculo (até x²+y² < r²que não segure mais). Tudo entre (0, y) e (x, y) está dentro do círculo. Devido à simetria, podemos estender 8 vezes mais

  • (-x, -y), (+ x, -y)
  • (-x, + y), (+ x, + y)
  • (-y, -x), (-y, + x)
  • (+ y, -x), (+ y, + x)

agora descemos 1 linha ( y--) e repetimos as etapas acima (mantendo o valor mais recente de x). Adicione o centro do círculo a cada um dos pontos e pronto.

Aqui está uma visualização. Existem alguns artefatos devido ao aumento de escala. O ponto vermelho mostra o que estamos testando a cada iteração:

1

Aqui está o código completo (usando o opencv para desenhar o material):

#include <opencv2/opencv.hpp>

constexpr double sqrt2 = 1.41421356237309504880168;

int main()
{
    cv::Point center(200, 200);
    constexpr int radius = 180;

    // create test image
    cv::Mat img(400, 400, CV_8UC3);
    cv::circle(img, center, radius, {180, 0, 0}, cv::FILLED);
    cv::imshow("img", img);
    cv::waitKey();

    // calculate inner rectangle
    int halfSideLen = radius / sqrt2;
    cv::Rect innerRect(center.x - halfSideLen, center.y - halfSideLen, halfSideLen * 2, halfSideLen * 2);
    cv::rectangle(img, innerRect, {0, 180, 0}, cv::FILLED);
    cv::imshow("img", img);
    cv::waitKey();

    // probe the rest
    int x = 0;
    for (int y = radius; y >= halfSideLen; y--)
    {
        for (; x * x + y * y < radius * radius; x++)
        {
            // anything between the following points lies within the circle
            // each pair of points represents a line
            // (-x, -y), (+x, -y)
            // (-x, +y), (+x, +y)
            // (-y, -x), (-y, +x)
            // (+y, -x), (+y, +x)

            // center + {(-X..X) x (-Y..Y)} is inside the circle
            cv::line(img, cv::Point(center.x - x, center.y - y), cv::Point(center.x + x, center.y - y), {180, 180, 0});
            cv::line(img, cv::Point(center.x - x, center.y + y), cv::Point(center.x + x, center.y + y), {180, 180, 0});
            cv::line(img, cv::Point(center.x - y, center.y - x), cv::Point(center.x - y, center.y + x), {180, 180, 0});
            cv::line(img, cv::Point(center.x + y, center.y - x), cv::Point(center.x + y, center.y + x), {180, 180, 0});

            cv::imshow("img", img);
            cv::waitKey(20);
        }
    }

    cv::waitKey();
    return 0;
}
Timo
fonte
@creativecreatororybenot Acho que minha versão deve ser mais rápida para qualquer coisa, exceto imagens muito pequenas, porque o cálculo do quadrado no centro é de tempo constante (sem nenhuma função trigonométrica, usamos apenas números básicos). Isso também significa que você não precisa realizar nenhum teste dentro do quadrado. Para o resto da imagem, os algoritmos são basicamente os mesmos.
Timo
1
Só notei isso também. Isso pode se resumir a micro otimizações no nível do montador. Talvez eu faça uma referência mais tarde.
Timo
2

Essa é uma otimização que reduz 1/4 da dimensão da pesquisa:

for (int px = x; px <= x + r; ++px) {
  bool find = false;
  int dx = x - px, dy;
  for (int py = y; !find && py <= y + r; ++py) {
    dy = y - py;
    if (dx * dx + dy * dy <= r * r)) {
      /* (px, py), (px, y+y-py+r), (x+x-px+r, py) 
       & (x+x-px+r, y+y-py+r) are part of the circle.*/
    }else{
      find = true; //Avoid increasing on the axis y
    }
  }
}

ou melhor, melhorando o desempenho da iteração do segundo círculo, for evitando a ifcondição

for (int px = x; px <= x + r; ++px) {
  int dx = x - px, py = y;
  for (; dx * dx + (py-y) * (py-y) <= r * r; ++py) {
    /* (px, py), (px, y+y-py+r), (x+x-px+r, py) 
     & (x+x-px+r, y+y-py+r) are part of the circle.*/
  }
}

Bem, eu acho que outra opção é uma pesquisa binária para o limite superior:

int binarySearch(int R, int dx, int y){
  int l=y, r=y+R;
  while (l < r) { 
    int m = l + (r - l) / 2;  
    if(dx*dx + (y - m)*(y - m) > R*R) r = m - 1; 
    else if(dx*dx + (y - m)*(y - m) < R*R) l = m + 1; 
    else r = m;
  }
  if(dx*dx + (y - l)*(y - l) > R*R) --l;
  return l;
}

for (int px = x; px <= x + r; ++px) {
  int upperLimit = binarySearch(r, px-x, y);
  for (int py = y; py <= upperLimit; ++py) {
    /* (px, py), (px, y+y-py+r), (x+x-px+r, py) 
     & (x+x-px+r, y+y-py+r) are part of the circle.*/
  }
}

A idéia da pesquisa binária é encontrar o limite superior de maneira ideal, evitando a ifcondição e os cálculos dentro do forciclo. Para isso, é verificado qual é o maior número inteiro que faz a distância entre o ponto atual e o raio dentro do círculo.

PD: Desculpe meu inglês.

Mandy007
fonte
2

Código

Com base na ideia do @ScottHunter , criei o seguinte algoritmo:

#include <functional>

// Executes point_callback for every point that is part of the circle
// defined by the center (x, y) and radius r.
void walk_circle(int x, int y, int r,
                 std::function<void(int x, int y)> point_callback) {
  for (int px = x - r; px < x + r; px++)
    point_callback(px, y);
  int mdx = r;
  for (int dy = 1; dy <= r; dy++)
    for (int dx = mdx; dx >= 0; dx--) {
      if (dx * dx + dy * dy > r * r)
        continue;
      for (int px = x - dx; px <= x + dx; px++) {
        point_callback(px, y + dy);
        point_callback(px, y - dy);
      }
      mdx = dx;
      break;
    }
}

Algoritmo explicado

Este algoritmo realiza um número minucioso de verificações. Especificamente, ele verifica apenas em cada linha até que o primeiro ponto que faz parte do círculo seja atingido. Além disso, ele pulará os pontos à esquerda do ponto identificado anteriormente na próxima linha. Além disso, usando simetria, apenas metade das linhas ( n/2 + 1/2como começamos em 0) são verificadas.

Visualização

Esta é uma visualização do algoritmo que criei. O contorno vermelho indica o quadrado que teria sido verificado anteriormente e os pixels pretos indicam o círculo real (com o pixel vermelho no meio sendo o centro). O algoritmo verifica os pontos (marcados em azul) e passa pelos pontos válidos (marcados em verde).
Como você pode ver, o número de pixels azuis no final é minuto, ou seja, existem apenas alguns pontos em loop que não fazem parte do círculo. Além disso, observe que apenas o primeiro pixel verde de cada vez precisa de uma verificação, os outros são repetidos, e é por isso que eles aparecem instantaneamente.

Notas

Os eixos podiam facilmente ser revertidos, obviamente.

Isso pode ser otimizado aproveitando ainda mais a simetria, ou seja, que as linhas serão iguais às colunas (passar por todas as linhas é o mesmo que passar por todas as colunas, da esquerda para a direita, de cima para baixo, vice-versa, vise vera) e descer apenas um quarto das linhas do centro seria suficiente para determinar exatamente quais pontos farão parte do círculo. No entanto, sinto que o pequeno aumento no desempenho que isso dará não vale o código adicional.
Se alguém quiser codificá-lo, proponha uma edição para esta resposta.

Código com comentários

#include <functional>

// Executes point_callback for every point that is part of the circle
// defined by the center (x, y) and radius r.
void walk_circle(int x, int y, int r,
                 std::function<void(int x, int y)> point_callback) {
  // Walk through the whole center line as it will always be completely
  // part of the circle.
  for (int px = x - r; px < x + r; px++)
    point_callback(px, y);
  // Define a maximum delta x that shrinks whith every row as the arc
  // is closing.
  int mdx = r;
  // Start directly below the center row to make use of symmetry.
  for (int dy = 1; dy <= r; dy++)
    for (int dx = mdx; dx >= 0; dx--) {
      // Check if the point is part of the circle using Euclidean distance.
      if (dx * dx + dy * dy > r * r)
        continue;

      // If a point in a row left to the center is part of the circle,
      // all points to the right of it until the center are going to be
      // part of the circle as well.
      // Then, we can use horizontal symmetry to move the same distance
      // to the right from the center.
      for (int px = x - dx; px <= x + dx; px++) {
        // Use y - dy and y + dy thanks to vertical symmetry
        point_callback(px, y + dy);
        point_callback(px, y - dy);
      }

      // The next row will never have a point in the circle further left.
      mdx = dx;
      break;
    }
}
creativecreatorormaybenot
fonte
O JS provavelmente é rápido o suficiente para "força bruta" calcular as coordenadas de pixel rapidamente para cada círculo (talvez não seja necessária otimização). Mas, você estará testando apenas 1 raio ou apenas alguns raios? Se sim, você pode pré-calcular os pontos internos de um círculo em uma matriz e reutilizá-los - compensar a origem do círculo que está testando.
markE
1
Você pode ver a última atualização do meu código, eu acho que é melhor
Mandy007
2

O problema tem uma complexidade fixa de O (n ^ 2), em que n é o raio do círculo. A mesma complexidade que um quadrado ou qualquer forma 2D regular

Não há como ignorar o fato de que você não pode reduzir o número de pixels em um círculo, mesmo que você aproveite a simetria, a complexidade permanece a mesma.

Portanto, ignorando a complexidade e procurando otimização.

Na sua pergunta, você declara que absé um pouco caro demais por pixel (ou quarto pixel)

Uma vez por linha é melhor que uma vez por pixel

Você pode reduzi-lo para 1 raiz quadrada por linha. Para um raio de círculo 256 que 128 raízes quadradas

void circle(int x, int y, int radius) {
    int y1 = y, y2 = y + 1, r = 0, rSqr = radius * radius;
    while (r < radius) {
        int x1 = x, x2 = x + 1, right = x + sqrt(rSqr - r * r) + 1.5;;
        while (x2 < right) {
            pixel(x1, y1);
            pixel(x2, y1);
            pixel(x1--, y2);
            pixel(x2++, y2);
        }
        y1--;
        y2++;
        r++;
    }
}

Para aproveitar melhor, você pode criar uma tabela de pesquisa para os cálculos da raiz do sqrt.

Todo inteiro

Como alternativa, você pode usar a variação na linha bresenham que substitui a raiz quadrada por toda a matemática inteira. No entanto, é uma bagunça e não seria de nenhum benefício, a menos que o dispositivo não possua uma unidade de ponto flutuante.

void circle(int x, int y, int radius) {
    int l, yy = 0, xx = radius - 1, dx = 1, dy = 1;
    int err = dx - (radius << 1);
    int l2 = x, y0 = y, r2 = x + 1;
    int l1 = x - xx, r1 = r2 + xx;
    int y2 = y0 - xx, y1 = y0 + 1, y3 = y1 + xx;
    while (xx >= yy) {
        l = l1;
        while (l < r1) {
            pixel(l, y1);
            pixel(l++, y0);
        }
        l = l2;
        while (l < r2) {
            pixel(l, y3);
            pixel(l++, y2);
        }
        err += dy;
        dy += 2;
        y0--;
        yy++;
        y1++;
        l2--;
        r2++;
        if (err > 0) {
            dx += 2;
            err += (-radius << 1) + dx;
            xx--;
            r1--
            l1++
            y3--
            y2++
        }
    }
}
Blindman67
fonte
1

Você pode desenhar um quadrado que se encaixe dentro do círculo e é bastante simples descobrir se o ponto se encaixa.

Isso resolverá a maioria dos pontos (2 * r ^ 2) no tempo O (1), em vez de pesquisar todos os pontos (4 * r ^ 2).

Editar: Nos demais pontos, você não precisa repetir todos os outros pixels. Você precisa fazer um loop para os 4 retângulos dimensionados com as dimensões [(2r / sqrt (2)), r- (r / sqrt (2))] nos 4 lados (norte, leste, sul, oeste) do quadrado que é dentro. Isso significa que você nunca precisa procurar os quadrados nos cantos. Como é completamente simétrico, podemos pegar os valores absolutos dos pontos de entrada e pesquisar se o ponto está dentro de meio quadrado no lado positivo do plano de coordenadas. O que significa que fazemos loop apenas uma vez em vez de 4.

int square_range = r/sqrt(2);
int abs_x = abs(x);
int abs_y = abs(y);

if(abs_x < square_range && abs_y < square_range){
    //point is in
}
else if(abs_x < r && abs_y < r){  // if it falls in the outer square
    // this is the only loop that has to be done
    if(abs_x < abs_y){
        int temp = abs_y;
        abs_y = abs_x;
        abs_x = temp;
    }
    for(int x = r/sqrt(2) ; x < r ; x++){
        for(int y = 0 ; y < r/sqrt(2) ; y++){
             if(x*x + y*y < r*r){
                 //point is in
             }
         }    
    }    
}        

A complexidade geral do código é O ((rr / sqrt (2)) * (r / sqrt (2))). Que é apenas um loop para metade de um único retângulo (simetria de 8 direções) que fica entre o quadrado interno e a borda externa do cirle.

heapoverflow
fonte
Essa abordagem desenhará um quadrado dentro do círculo. E o resto do círculo?
MBo 5/03
Como mencionei, os outros pontos que estão fora do quadrado serão pesquisados ​​com um loop.
heapoverflow 5/03
A complexidade geral O(n^2)não é a que O((r-r/sqrt(2))* (r/sqrt(2)))você fornece uma notação quadrática e grande de O não deve incluir os coeficientes e todos, exceto a potência mais alta, devem ser ignorados. Esse problema não pode ser simplificado abaixoO(n^2)
Blindman67
Estou ciente da grande notação, mas meu objetivo era afirmar que esse algoritmo realiza menos operações em comparação com outros O (n ^ 2).
heapoverflow 8/03