2012-01-31 19 views
8

Czy można zasugerować algorytm, który może narysować kulę w przestrzeni 3D przy użyciu tylko podstawowego prymitywu plot(x,y,z) (który będzie rysował pojedynczy woksel)?Rysowanie kuli przy użyciu pikseli 3D (wokseli)

Miałem nadzieję na coś podobnego do Bresenham's circle algorithm, ale do 3D zamiast 2D.

FYI, pracuję nad projektem sprzętowym, który jest wyświetlaniem 3D o niskiej rozdzielczości z wykorzystaniem trójwymiarowej matrycy diod LED, więc muszę faktycznie narysować sferę, a nie tylko rzut 2D (to jest koło).

Projekt ten jest bardzo podobny do tego:

3D LED cube

... albo zobaczyć go w akcji here.

Jedną z możliwości, mam na myśli to:

  • obliczyć współrzędne Y biegunów (biorąc pod uwagę promień) (dla kuli znajdującego się w centrum pochodzenia, to byłoby -r i +r)
  • wyciąć kulę: dla każdej płaszczyzny poziomej między tymi współrzędnymi obliczyć promień okręgu uzyskanego przez przecięcie płaszczyzny ze sferą =>r i.
  • czerpać rzeczywiste okręgu o promieniu r i na płaszczyźnie p i użyciu algorytmu Bresenham użytkownika.

FWIW, używam .NET micro-framework microprocessor, więc programowanie jest C#, ale nie potrzebuję odpowiedzi, aby być w C#.

+0

Sądząc po kuli LED, powinniśmy założyć, że potrzebne jest również wnętrze wykreowanej sfery? – NominSim

+0

@NominSim Nie sądzę. W tym przypadku nie chciał mówić o rasteryzacji okręgu Bresenhama i mógł po prostu użyć rozwiązania brutalnej siły japreiss. –

+0

@Christian Właściwie, chciałbym mieć obie opcje. –

Odpowiedz

7

Prostą metodą brutalnej siły jest zapętlenie każdego woksela w siatce i obliczenie jego odległości od środka kuli. Następnie kolor woksela, jeśli jego odległość jest mniejsza niż promień kuli. Możesz zapisać wiele instrukcji, eliminując pierwiastek kwadratowy i porównując produkt kropki z promieniem do kwadratu.

Dość daleko od optymalnego, pewny. Ale na siatce 8x8x8, jak pokazano, musisz wykonać tę operację 512 razy na kulę. Jeśli centrum sfery znajduje się na siatce, a jego promień jest liczbą całkowitą, potrzebujesz tylko matematyki całkowitej. Produkt kropkowany to 3 mnozy i 2 dodane. Mnożenia są powolne; załóżmy, że przyjmują po 4 instrukcje. Plus potrzebujesz porównania. Dodaj obciążenia i sklepy, powiedzmy, że kosztuje to 20 instrukcji na woksel. To 10240 instrukcji na kulę.

Arduino działający z częstotliwością 16 MHz może przepuścić 1562 kulek na sekundę. O ile nie robisz tonów innej matematyki i operacji we/wy, ten algorytm powinien być wystarczająco dobry.

+0

Problem polega na tym, że wydaje się, że nie chce stałej sfery. W tym przypadku masz klasyczny problem rasteryzacji, który zapobiega powstawaniu dziur i obrzęków na powierzchni kuli. Chociaż nadal mógł użyć rozwiązania i wykonać drugie przejście, usuwając wszystkie woksele wewnętrzne, sprawdzając ich 6-sąsiadów. –

+0

No właśnie, nie potrzebujesz drugiego przejścia do usuwania wokseli wewnętrznych, ponieważ obiekt jest zdefiniowany domyślnie. Ale w tym przypadku musisz wykonać obliczenia odległości więcej niż 1 raz na woksel średnio. –

+0

Tak, na początku myślałem, że zostało wypełnione wideo demo, ale po bliższej inspekcji wygląda tylko na powłokę. Zgadzam się z twoim pierwszym komentarzem, wykonanie operacji morfologicznej byłoby najprostsze. – japreiss

4

Zakładając, że masz już funkcję plot jak mówiłeś:

public static void DrawSphere(double r, int lats, int longs) 
    { 
     int i, j; 
     for (i = 0; i <= lats; i++) 
     { 
      double lat0 = Math.PI * (-0.5 + (double)(i - 1)/lats); 
      double z0 = Math.Sin(lat0) * r; 
      double zr0 = Math.Cos(lat0) * r; 

      double lat1 = Math.PI * (-0.5 + (double)i/lats); 
      double z1 = Math.Sin(lat1) * r; 
      double zr1 = Math.Cos(lat1) * r; 

      for (j = 0; j <= longs; j++) 
      { 
       double lng = 2 * Math.PI * (double)(j - 1)/longs; 
       double x = Math.Cos(lng); 
       double y = Math.Sin(lng); 

       plot(x * zr0, y * zr0, z0); 
       plot(x * zr1, y * zr1, z1); 
      } 
     } 
    } 

Ta funkcja powinna wykreślić kulę u źródła o określonej szerokości i rozdzielczości geograficznej (sądząc po twojej kostki prawdopodobnie chcesz coś około 40 lub 50 jak z grubsza przypuszczam). Algorytm ten nie "wypełnia" sfery, więc zapewnia jedynie kontur, ale gra z promieniem powinna pozwolić ci wypełnić wnętrze, prawdopodobnie z malejącą rozdzielczością łatów i longów po drodze.

+1

czy nie trzeba pomnożyć współrzędnych przez r w wywoływanych działkach? ponieważ jest to r nieużywane, co oznacza, że ​​zawsze narysuje sferę o promieniu 1. –

1

Wystarczy znaleźć starą & Q o generowanie kuli siatki, ale górna odpowiedź faktycznie daje krótki kawałek pseudo-kodu, aby wygenerować X, Y i Z:

(x, y, z) = (sin(Pi * m/M) cos(2Pi * n/N), sin(Pi * m/M) sin(2Pi * n/N), cos(Pi * m/M))

Sprawdź to Q & a dla szczegółów :) procedurally generate a sphere mesh

+1

Czy to nie jest po prostu rozwiązanie NominSim? –

3

nie wierzę działa algorytm punktu środkowego okręgu, na każdej warstwie dadzą pożądanych rezultatów po osiągnięciu bieguny, co będzie mieć luki w powierzchni, gdzie nie są diody oświetlony . Może to jednak dać pożądany rezultat, tak więc będzie to zależało od estetyki. Ten post opiera się na użyciu algorytmu okręgu środkowego w celu określenia promienia warstw przez dwa środkowe dwa pionowe oktanty, a następnie podczas rysowania każdego z tych okręgów również ustawienie punktów dla oktantów polarnych.

Myślę, że na podstawie komentarza @Nick Udall i odpowiedzi here za pomocą algorytmu okręgu do określenia promienia poziomego plasterka będzie działać z modyfikacją zaproponowałem w komentarzu do jego odpowiedzi. Algorytm koła powinien zostać zmodyfikowany, aby jako błąd wejściowy przyjąć błąd początkowy, a także narysować dodatkowe punkty dla oktantów biegunowych.

  • Draw standardowe punkty algorytmu okrąg na y0 + y1 i y0 - y1: x0 +/- x, z0 +/- z, y0 +/- y1, x0 +/- z, z0 +/- x, y0 +/- y1, łącznie 16 punktów. Stanowi to większość pionu kuli.
  • Dodatkowo narysuj punkty: x0 +/- y1, z0 +/- x, y0 +/- z i x0 +/- x, z0 +/- y1, y0 +/- z, łącznie 16 punktów, które utworzą polarne czapki dla kuli.

Po przekazaniu błędu zewnętrznego algorytmu do algorytmu koła, umożliwi to dostosowanie podokroksu w okręgu każdej warstwy. Bez podania błędu do wewnętrznego algorytmu, równik koła będzie przybliżony do cylindra, a każda przybliżona powierzchnia czołowa na osiach x, yiz będzie tworzyć kwadrat. Po uwzględnieniu błędu każda twarz o wystarczająco dużym promieniu będzie przybliżona jako wypełnione koło.


Poniższy kod został zmodyfikowany z Wikipedii Midpoint circle algorithm. Algorytm DrawCircle ma nomenklaturę zmienioną tak, aby działała w płaszczyźnie xz, dodawanie trzeciego początkowego punktu y0, przesunięcie y y1 i początkowy błąd error0. DrawSphere została zmodyfikowana z tej samej funkcji do podjęcia trzeci punkt początkowy y0 i wzywa DrawCircle zamiast DrawPixel

public static void DrawCircle(int x0, int y0, int z0, int y1, int radius, int error0) 
{ 
    int x = radius, z = 0; 
    int radiusError = error0; // Initial error state passed in, NOT 1-x 

    while(x >= z) 
    { 
    // draw the 32 points here. 
    z++; 
    if(radiusError<0) 
    { 
     radiusError+=2*z+1; 
    } 
    else 
    { 
     x--; 
     radiusError+=2*(z-x+1); 
    } 
    } 
} 

public static void DrawSphere(int x0, int y0, int z0, int radius) 
{ 
    int x = radius, y = 0; 
    int radiusError = 1-x; 

    while(x >= y) 
    { 
    // pass in base point (x0,y0,z0), this algorithm's y as y1, 
    // this algorithm's x as the radius, and pass along radius error. 
    DrawCircle(x0, y0, z0, y, x, radiusError); 
    y++; 
    if(radiusError<0) 
    { 
     radiusError+=2*y+1; 
    } 
    else 
    { 
     x--; 
     radiusError+=2*(y-x+1); 
    } 
    } 
} 

Na kuli o promieniu 4 (który rzeczywiście wymaga 9x9x9), byłoby to trzy iteracje DrawCircle rutynowe, z pierwszym rysunkiem o typowym promieniu 4 koła (trzy kroki), drugim rysunkiem o promieniu 4 okręgu z początkowym błędem 0 (również trzema krokami), a następnie trzecim rysunkiem o promieniu 3 okręgu z początkowym błędem 0 (również trzy kroki). To kończy się jako dziewięć obliczonych punktów, z których każdy ma 32 piksele. To powoduje, że 32 (punkty na koło) x 3 (dodawanie lub odejmowanie operacji na punkt) + 6 (dodawanie, odejmowanie, operacje zmiany w iteracji) = 102 dodawanie, odejmowanie lub zmiana operacji na obliczony punkt. W tym przykładzie jest to 3 punkty za każdy krąg = 306 operacji na warstwę.Algorytm promienia dodaje również 6 operacji na warstwę i iteruje 3 razy, więc 306 + 6 * 3 = 936 podstawowe operacje arytmetyczne dla przykładowego promienia 4. Koszt tutaj polega na tym, że będziesz wielokrotnie ustawiał niektóre piksele bez dodatkowych sprawdzeń stanu (tj. X = 0, y = 0 lub z = 0), więc jeśli twoje I/O jest wolne, możesz lepiej dodać testy warunków. Zakładając, że wszystkie diody LED zostały wyczyszczone na początku, przykładowy okrąg ustawiłby 288 diod LED, podczas gdy istnieje znacznie mniej diod LED, które byłyby faktycznie oświetlone z powodu powtarzania zestawów.

Wygląda na to, że byłoby to lepsze niż metoda bruteforce dla wszystkich sfer, które pasowałyby do siatki 8x8x8, ale metoda bruteforce miałaby stały czas niezależnie od promienia, podczas gdy ta metoda będzie spowalniać podczas rysowania sfer o dużym promieniu, gdzie wyświetli się tylko część. Ponieważ sześcian wyświetlania rośnie w rozdzielczości, to algorytm synchronizacji czasu pozostanie niezmieniony, podczas gdy zwiększa się bruteforce.

Powiązane problemy