Effizienter Algorithmus zum Erhalten von Punkten in einem Kreis um ein Zentrum

8

Problem

Ich möchte alle Pixel erhalten, die sich innerhalb eines Kreises eines bestimmten Radius um einen bestimmten Punkt befinden, wobei Punkte nur ganzzahlige Koordinaten haben können , dh Pixel in einer Leinwand.

Illustration

Also möchte ich alle Punkte im gelben Bereich erhalten (x, y)und r.

Nähert sich

Der effizienteste Weg, den ich mir vorstellen kann, besteht darin, ein Quadrat zu durchlaufen (x, y)und den euklidischen Abstand für jeden Punkt zu überprüfen :

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.
    }
  }
}

Dies bedeutet jedoch, dass dieser Algorithmus (r * 2)^2 * (4 - pi) / 4Pixel überprüft , die nicht Teil des Kreises sind. dx * dx + dy * dy <= r * r, was ziemlich teuer erscheint, wird fast immer redundant genannt 1 / 4.

Die Integration von etwas wie dem, was hier vorgeschlagen wurde, könnte die Leistung verbessern:

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.
    }
  }
}

Wie der Autor selbst betonte, wird dies wahrscheinlich nicht schneller sein, wenn sich die meisten Punkte innerhalb des Kreises befinden (insbesondere wegen abs), was pi / 4in diesem Fall der Fall ist.


Ich konnte zu dieser Frage keine Ressourcen finden. Ich suche speziell nach einer Lösung in C ++ und nicht nach etwas in SQL .

CreativeCreatorormaybenot
quelle
1
Die ganzzahlige Multiplikation und Addition ist ziemlich schnell. Ich bin mir nicht sicher, ob das Hinzufügen einer Reihe zusätzlicher Vergleiche hilfreich sein wird, insbesondere wegen all der möglichen Fehler bei der Verzweigungsvorhersage, die dadurch entstehen könnten.
François Andrieux
Fragen, die "die meisten" oder "die besten" stellen, können normalerweise nicht definitiv beantwortet werden, da es selten eine einzige beste Antwort gibt. Welche Lösung am besten geeignet ist, hängt vom tatsächlichen Anwendungsfall und der Architektur ab, auf der sie ausgeführt wird.
François Andrieux
3
Nutzen Sie die Tatsache, dass Sie durch Kenntnis des Bereichs der x-Werte für einen bestimmten y-Wert einen Einblick in die Bereiche der x-Werte für den vorherigen und den nächsten y-Wert erhalten.
Scott Hunter
1
Sie sollten zumindest in der Lage sein, aus der Schleife auszubrechen, wenn Sie vom vorherigen Punkt, der gut ist, zum nächsten Punkt, der schlecht ist, wechseln. An diesem Punkt wissen Sie, dass Sie die Grenze überschritten haben und auf dieser Linie keine guten Punkte mehr vorhanden sind.
NathanOliver
1
Eine andere Option, um das Beispiel von Scotts zu erstellen, beginnt oben oder unten in der Mitte und führt dann eine Schleife nach links und rechts durch, bis Sie die Grenze erreichen. Dies bedeutet, dass Ihre inneren Schleifen anhalten, sobald Sie die Grenze überschreiten.
NathanOliver

Antworten:

4

Okay, hier sind die Benchmarks, die ich versprochen habe.

Installieren

Ich habe Google Benchmark verwendet und die Aufgabe bestand darin, alle Punkte innerhalb des Umfangs des Kreises in a einzufügen std::vector<point>. Ich vergleiche für eine Reihe von Radien und ein konstantes Zentrum:

radii = {10, 20, 50, 100, 200, 500, 1000}
center = {100, 500}
  • Sprache: C ++ 17
  • Compiler: msvc 19.24.28316 x64
  • Plattform: Windows 10
  • Optimierung: O2 (vollständige Optimierung)
  • Threading: Single-Threaded-Ausführung

Die Ergebnisse jedes Algorithmus werden auf Richtigkeit geprüft (verglichen mit der Ausgabe des OP-Algorithmus).

Bisher werden folgende Algorithmen verglichen:

  1. OPs Algorithmus enclosing_square.
  2. Mein Algorithmus containing_square .
  3. Der Algorithmus von creativecreatorormaybenot edge_walking .
  4. Mandy007s Algorithmus binary_search .

Ergebnisse

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

Code

Der vollständige Testcode ist unten aufgeführt. Sie können ihn kopieren, einfügen und selbst testen. fill_circle.cppenthält die Implementierung der verschiedenen Algorithmen.

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
quelle
2
@creativecreatorormaybenot np. Bitte bearbeiten Sie diese Antwort, wenn Sie die Algorithmen anpassen, damit wir die Ergebnisse hier aktualisieren können.
Timo
1
@ Mandy007 Ich habe die Benchmarks aktualisiert.
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)
   } 
}

Um eine wiederholte Erzeugung von Pixeln an Achsen zu vermeiden, sollten Schleifen von 1 gestartet und Mittellinien (ix == 0 oder Linie == 0) in einer separaten Schleife gezeichnet werden.

Beachten Sie, dass es auch einen reinen ganzzahligen Bresenham-Algorithmus zum Erzeugen von Umfangspunkten gibt.

MBo
quelle
Es gibt hier keinen wirklichen Vorteil , Symmetrie zu verwenden. Ich habe den Code geändert, um ein Viertel des Kreises zu erzeugen.
MBo
2

Okay, zuerst berechnen wir das innere Quadrat des Kreises. Die Formel dafür ist einfach:

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

Nun liegt jeder Punkt zwischen (-h, -h)und (+h, +h)innerhalb des Kreises. Hier ist ein Bild von dem, was ich meine:

1

Der verbleibende blaue Teil ist etwas knifflig, aber auch nicht zu kompliziert. Wir beginnen ganz oben im blauen Kreis (x = 0, y = -radius). Als nächstes gehen wir nach rechts ( x++), bis wir den Kreisperimiter verlassen (bis x²+y² < r²nicht mehr gilt). Alles zwischen (0, y) und (x, y) liegt innerhalb des Kreises. Aufgrund der Symmetrie können wir diese 8-fach erweitern

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

Jetzt gehen wir 1 Zeile nach unten ( y--) und wiederholen die obigen Schritte (wobei wir den neuesten Wert von beibehalten x). Fügen Sie jedem Punkt den Mittelpunkt des Kreises hinzu, und fertig.

Hier ist eine Visualisierung. Aufgrund der Hochskalierung gibt es einige Artefakte. Der rote Punkt zeigt, was wir bei jeder Iteration testen:

1

Hier ist der vollständige Code (mit opencv zeichnen Sie das Zeug):

#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
quelle
@creativecreatorormaybenot Ich denke, meine Version sollte für alles außer für sehr kleine Bilder schneller sein, da die Berechnung des Quadrats in der Mitte eine konstante Zeit ist (ohne Triggerfunktionen verwenden wir nur grundlegende Zahlen). Dies bedeutet auch, dass Sie keine Schecks innerhalb des Quadrats durchführen müssen. Für den Rest des Bildes sind die Algorithmen grundsätzlich gleich.
Timo
1
Ich habe das auch bemerkt. Dies kann auf Mikrooptimierungen auf Assembler-Ebene hinauslaufen. Vielleicht mache ich später einen Benchmark.
Timo
2

Dies ist eine Optimierung, die die Suchdimension um 1/4 reduziert:

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
    }
  }
}

oder besser, Verbesserung der Leistung der Iteration des zweiten Kreises unter for Vermeidung der ifBedingung

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.*/
  }
}

Nun, ich denke, dass eine andere Option eine binäre Suche nach der Obergrenze ist:

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.*/
  }
}

Die Idee der binären Suche besteht darin, die Obergrenze optimal zu finden und die ifBedingungen und Berechnungen innerhalb des forZyklus zu vermeiden . Dazu wird geprüft, welche die größte Ganzzahl ist, die den Abstand zwischen dem aktuellen Punkt und dem Radius innerhalb des Kreises ergibt.

PD: Entschuldigung mein Englisch.

Mandy007
quelle
2

Code

Basierend auf der Idee von @ScottHunter habe ich den folgenden Algorithmus entwickelt:

#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;
    }
}

Algorithmus erklärt

Dieser Algorithmus führt eine winzige Anzahl von Überprüfungen durch. Insbesondere wird nur in jeder Zeile geprüft, bis der erste Punkt erreicht ist, der Teil des Kreises ist. Außerdem werden Punkte links vom zuvor identifizierten Punkt in der nächsten Zeile übersprungen. Zusätzlich wird durch Verwendung von Symmetrie nur die Hälfte der Zeilen ( n/2 + 1/2wie wir bei 0 beginnen) überprüft.

Visualisierung

Dies ist eine Visualisierung des von mir erstellten Algorithmus. Der rote Umriss zeigt das Quadrat an, das zuvor überprüft worden wäre, und die schwarzen Pixel geben den realen Kreis an (wobei das rote Pixel in der Mitte die Mitte ist). Der Algorithmus überprüft Punkte (blau markiert) und durchläuft gültige Punkte (grün markiert).
Wie Sie sehen können, ist die Anzahl der blauen Pixel am Ende winzig, dh es werden nur wenige Punkte durchlaufen, die nicht Teil des Kreises sind. Beachten Sie außerdem, dass jedes Mal nur das erste grüne Pixel überprüft werden muss. Die anderen Pixel werden nur durchlaufen, weshalb sie sofort angezeigt werden.

Anmerkungen

Die Achsen könnten natürlich leicht umgekehrt werden.

Dies könnte optimiert werden, indem die Symmetrie noch stärker ausgenutzt wird, dh dass die Zeilen mit den Spalten identisch sind (das Durchlaufen aller Zeilen entspricht dem Durchlaufen aller Spalten von links nach rechts, von oben nach unten, umgekehrt, umgekehrt). vise vera) und nur ein Viertel der Reihen von der Mitte nach unten zu gehen, würde ausreichen, um genau zu bestimmen, welche Punkte Teil des Kreises sein werden. Ich bin jedoch der Meinung, dass die geringfügige Leistungssteigerung, die dies verursachen wird, den zusätzlichen Code nicht wert ist.
Wenn jemand es codieren möchte, schlagen Sie eine Bearbeitung dieser Antwort vor.

Code mit Kommentaren

#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
quelle
JS ist wahrscheinlich schnell genug, um die Pixelkoordinaten für jeden Kreis "Brute Force" im laufenden Betrieb zu berechnen (möglicherweise ist keine Optimierung erforderlich). Aber werden Sie nur 1 Radius oder nur ein paar Radien testen? Wenn ja, können Sie die internen Punkte eines Kreises in einem Array vorberechnen und diese berechneten Punkte wiederverwenden - versetzt zum Ursprung des Kreises, den Sie testen.
Markieren Sie den
1
Sie können das letzte Update meines Codes sehen, ich denke, das ist besser
Mandy007
2

Das Problem hat eine feste Komplexität von O (n ^ 2), wobei n der Radius des Kreises ist. Die gleiche Komplexität wie ein Quadrat oder eine normale 2D-Form

Es kommt nicht daran vorbei, dass Sie die Anzahl der Pixel in einem Kreis nicht reduzieren können, auch wenn Sie die Symmetrie ausnutzen, bleibt die Komplexität gleich.

Ignorieren Sie also die Komplexität und suchen Sie nach Optimierung.

In Ihrer Frage geben Sie an, dass das abspro Pixel (oder 4. Pixel) etwas zu teuer ist.

Einmal pro Zeile ist besser als einmal pro Pixel

Sie können es auf 1 Quadratwurzel pro Zeile reduzieren. Für einen Kreisradius 256 sind das 128 Quadratwurzeln

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++;
    }
}

Um mehr daraus zu machen, können Sie eine Nachschlagetabelle für die SQL-Root-Berechnungen erstellen.

Alles ganzzahlig

Alternativ können Sie die Variation der Bresenham-Linie verwenden , die die Quadratwurzel durch alle ganzzahligen Berechnungen ersetzt. Es ist jedoch ein Chaos und würde keinen Nutzen bringen, wenn das Gerät keine Gleitkommaeinheit hat.

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
quelle
1

Sie können ein Quadrat zeichnen, das in den Kreis passt, und es ist ziemlich einfach festzustellen, ob der Punkt hineinfällt.

Dadurch werden die meisten Punkte (2 * r ^ 2) in O (1) -Zeit gelöst, anstatt alle (4 * r ^ 2) Punkte zu durchsuchen.

Bearbeiten: Für den Rest der Punkte müssen Sie nicht alle anderen Pixel schleifen. Sie müssen die 4 Rechtecke mit den Abmessungen [(2r / sqrt (2)), r- (r / sqrt (2))] auf den 4 Seiten (Nord, Ost, Süd, West) des Quadrats schleifen Innerhalb. Es bedeutet, dass Sie nie nach den Quadraten an den Ecken suchen müssen. Da es vollständig symmetrisch ist, können wir die absoluten Werte der Eingabepunkte nehmen und suchen, ob der Punkt innerhalb von Halbquadraten auf der positiven Seite der Koordinatenebene liegt. Das heißt, wir schleifen nur einmal statt 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
             }
         }    
    }    
}        

Die Gesamtkomplexität des Codes beträgt O ((rr / sqrt (2)) * (r / sqrt (2))). Dies ist nur eine Schleife für die Hälfte eines einzelnen Rechtecks ​​(8-Wege-Symmetrie), das sich zwischen dem inneren Quadrat und dem äußeren Rand des Kreises befindet.

Heapoverflow
quelle
Dieser Ansatz zeichnet ein Quadrat innerhalb des Kreises. Was ist mit dem Rest des Kreises?
MBo
Wie ich bereits erwähnt habe, werden die anderen Punkte außerhalb des Quadrats mit einer Schleife durchsucht.
Heapoverflow
Die Gesamtkomplexität besteht O(n^2)nicht darin, dass O((r-r/sqrt(2))* (r/sqrt(2)))Sie eine quadratische und große O-Notation angeben. Sie sollte die Koeffizienten nicht enthalten und alle außer der höchsten Potenz müssen ignoriert werden. Dieses Problem kann unten nicht vereinfacht werdenO(n^2)
Blindman67
Mir ist die große O-Notation bekannt, aber ich wollte feststellen, dass dieser Algorithmus im Vergleich zu anderen O (n ^ 2) weniger Operationen ausführt.
Heapoverflow