Oblicz losowe punkty (piksel) w okręgu (obraz)

19

Mam obraz, który zawiera koła w określonym miejscu i o określonej średnicy. Muszę tylko obliczyć losowe punkty w okręgu, a następnie manipulować pikselami, z którymi te punkty się korelują. Mam już następujący kod:

private Point CalculatePoint()
{
    var angle = _random.NextDouble() * ( Math.PI * 2 );
    var x = _originX + ( _radius * Math.Cos( angle ) );
    var y = _originY + ( _radius * Math.Sin( angle ) );
    return new Point( ( int )x, ( int )y );
}

I to działa dobrze, aby znaleźć wszystkie punkty na obwodzie koła, ale potrzebuję wszystkich punktów z dowolnego miejsca w okręgu. Jeśli to nie ma sensu, daj mi znać, a ja postaram się wyjaśnić.

DMills
źródło
Sprawdź aktualizację.
David Gouveia,
3
Dobre pytanie w tym sensie, że nieumyślne utworzenie rozkładu ważonego jest częstym błędem.
Tim Holt,

Odpowiedzi:

35

Jeśli chcesz prostego rozwiązania, po prostu losuj również promień:

private Point CalculatePoint()
{
    var angle = _random.NextDouble() * Math.PI * 2;
    var radius = _random.NextDouble() * _radius;
    var x = _originX + radius * Math.Cos(angle);
    var y = _originY + radius * Math.Sin(angle);
    return new Point((int)x,(int)y);
}

To jednak powoduje, że twoje punkty są bardziej skoncentrowane w kierunku środka koła:

wprowadź opis zdjęcia tutaj

Aby uzyskać jednolity rozkład, wprowadź następującą zmianę w algorytmie:

var radius = Math.Sqrt(_random.NextDouble()) * _radius;

Co da następujący wynik:

wprowadź opis zdjęcia tutaj

Aby uzyskać więcej informacji, sprawdź następujący link: MathWorld - Wybór punktu dysku .

I w końcu oto prosta demonstracja JsFiddle porównująca obie wersje algorytmu.

David Gouveia
źródło
1
Doskonała odpowiedź. Wystarczy dodać jedną rzecz: nie zapomnij
obsadzić
Ups, udało ci się go spotkać - nie widziałem tego posta, kiedy opublikowałem mój. Witryna wolfram jest niesamowitym zasobem do tego typu rzeczy.
Tim Holt,
1
@TimHolt zdarza się cały czas :)
David Gouveia
Czy to przy założeniu, że środek okręgu wynosi 0,0?
jjxtra 29.09.13
@PsychoDad Środek koła byłby (_originX, _originY)
David Gouveia
5

NIE używaj po prostu losowego r i theta! Tworzy to rozkład ważony z większą liczbą punktów w środku. Ta strona dobrze to ilustruje ...

http://mathworld.wolfram.com/DiskPointPicking.html

Oto metoda, która tworzy nieważony rozkład ...

var r = rand(0,1)
var theta = rand(0,360)

var x = sqrt(r) * cos(theta)
var y = sqrt(r) * sin(theta)
Tim Holt
źródło
Ups, duplikat wybranej odpowiedzi: P
Tim Holt
Jestem zdezorientowany, ponieważ mówisz, aby nie używać losowego r i theta, ponieważ tworzy on rozkład ważony, a następnie kod, który, jak twierdzisz, tworzy rozkład nieważony, generuje r w zakresie [0,1]. Czy planowałeś pierwiastek kwadratowy z liczby losowej?
PeteUK
Tak, wykonanie pierwiastka kwadratowego promienia (o ile wynosi 0-1) zmniejsza nieoczekiwane skupienie punktów na środku. Zobacz zamieszczony przeze mnie link Wolfram, który ilustruje go i wyjaśnia matematyką lepiej niż potrafię.
Tim Holt,
Mój błąd. Widzę, że wykonujesz sqrt (r) podczas obliczania xiy.
PeteUK
4

Jesteś w połowie drogi. Oprócz generowania losowego kąta, po prostu wygeneruj losową odległość, mniejszą lub równą promieniu, ważoną, aby uzyskać jednolity rozkład:

private Point CalculatePoint()
{
    var angle = _random.NextDouble() * Math.PI * 2;
    var distance = Math.Sqrt(_random.NextDouble()) * _radius;
    var x = _originX + (distance * Math.Cos(angle));
    var y = _originY + (distance * Math.Sin(angle));
    return new Point((int)x, (int)y);
}

Teraz myślisz z biegunem .

Możesz także zważyć taką odległość, aby uniknąć pierwiastka kwadratowego:

var distance = _random.NextDouble() + _random.NextDouble();
distance = (distance <= 1 ? distance : 2 - distance) * _radius;
Jon Purdy
źródło
OK, więc udzieliliśmy dokładnie tej samej odpowiedzi w ciągu kilku sekund od różnicy. Co teraz? :)
David Gouveia,
@DavidGouveia Oboje otrzymujemy głosy poparcia dla obu racji. Wszyscy wygrywają! : D
Jon Purdy,
Obie odpowiedzi bardzo mile widziane (i link też!). Człowieku, jestem idiotą, że sam tego nie widziałem, -1 do mnie :(
Jeszcze
To wygeneruje losowe punkty, ale nie będą one równomiernie rozmieszczone na dysku, prawda? Raczej będą ważone w kierunku środka. Tylko sprawdzam, że czegoś mi nie brakuje.
PeteUK
1
@PeteUK: Masz rację, odległość powinna być ważona. Pozwól mi zaktualizować.
Jon Purdy,
3

Jeśli wydajność stanowi problem, jednym z alternatywnych rozwiązań jest wygenerowanie losowej pozycji w polu o szerokości / wysokości koła, a następnie wyrzucenie punktów, które nie znajdują się w obszarze koła.

Zaletą tej metody jest brak wykonywania funkcji cos / sin / sqrt, co w zależności od platformy może być dużą oszczędnością prędkości.

var x = _random.NextDouble();
var y = _random.NextDouble();
if (x*x + y*y < 1.0f)
{
    // we have a usable point inside a circle
    x = x * diameter - _radius + _OriginX;
    y = y * diameter - _radius + _OriginY;
    // use the point(x,y)
}
M Meduza
źródło
Spróbuję tego i zobaczę, czy to przyspieszy. Nie jestem pewien, czy mam problem z wydajnością, ale i tak spróbuję, dzięki!
DMills,
0

Przyjąłem podejście do jednego z wymienionych komentarzy i rozszerzyłem funkcjonalność o system generowania punktów w kształcie pączka.

            private Vector2 CalculatePosition()
            {
                double angle = _rnd.NextDouble() * Math.PI * 2;
                double radius = InnerCircleRadius + (Math.Sqrt(_rnd.NextDouble()) * (OuterCircleRadius - InnerCircleRadius));
                double x = (_context.ScreenLayout.Width * 0.5f) + (radius * Math.Cos(angle));
                double y = (_context.ScreenLayout.Height * 0.5f) + (radius * Math.Sin(angle));
                return new Vector2((int)x, (int)y);
            }

Jest to podobne podejście, jak wspomniano wcześniej, ale zapewniło inne wyniki. Wewnętrzna część koła pozostanie pusta bez punktów.

Branden
źródło