#!/usr/bin/perl

# powloka_wypukla_graham( @xy )
#   Procedura oblicza powloke wypukla dla punktow @xy z uzyciem 
#   algorytmu Grahama. Procedura zwraca kolejne punkty powloki w postaci.
#   listy ($x,$y,...).

sub powloka_wypukla_graham {
    my ( @xy ) = @_;

    my $n = @xy / 2;
    my @i = map { 2 * $_ } 0 .. ( $#xy / 2 ); # Parzyste indeksy.
    my @x = map { $xy[ $_ ]     } @i;
    my @y = map { $xy[ $_ + 1 ] } @i;

    # Najpierw nalezy odnalezc najmniejsza wartosc y z najmniejsza wartoscia x.

    # $ymin to najmniejsza wartosc y w danym momencie, @xmini zawiera indeksy
    # najmniejszych wartosci y, $xmini to indeks najmniejszej wartosci x,
    # a $xmin to najmniejsza wartosc x.
    my ( $ymin, $xmini, $xmin, $i );

    for ( $ymin = $ymax = $y[ 0 ], $i = 1; $i < $n; $i++ ) {
        if ( $y[ $i ] + epsilon < $ymin ) {
            $ymin  = $y[ $i ];
            @xmini = ( $i );
        } elsif ( abs( $y[ $i ] - $ymin ) < epsilon ) {
            $xmini = $i # Zapamietanie indeksu najmniejszej wartosci x.
                if not defined $xmini or $x[ $i ] < $xmini;
        }
    }

    $xmin  = $x[ $xmini ];
    splice @x, $xmini, 1;         # Usuniecie punktu minimum.
    splice @y, $xmini, 1;

    my @a = map {  # Posortowanie punktow zgodnie z wartosciami katow.
                   atan2( $y[ $_ ] - $ymin,
                          $x[ $_ ] - $xmin)
                } 0 .. $#x;

    # Rzadki przypadek transformacji Schwartza, ktora daje w wyniku posortowane
    # indeksy. Pozwala to na wielokrotne sortowanie, czyli uzycie permutacji.

    my @j = map { $_->[ 0 ] }
                sort {      # Posortowanie wedlug katow, wartosci x i wartosci y.
                      return $a->[ 1 ] <=> $b->[ 1 ]             ||
                             $x[ $a->[ 0 ] ] <=> $x[ $b->[ 0 ] ] ||
                             $y[ $a->[ 0 ] ] <=> $y[ $b->[ 0 ] ];
                      }
                     map { [ $_, $a[ $_ ] ] } 0 .. $#a;

    @x = @x[ @j ];          # Permutacja.
    @y = @y[ @j ];
    @a = @a[ @j ];

    unshift @x, $xmin;      # Przywrocenie punktu minimum.
    unshift @y, $ymin;
    unshift @a, 0;

    my @h = ( 0, 1 );        # Powloka.
    my $cw;

  # Cofniecie: zmniejszenie powloki, jesli nalezy skrecic w prawo lub jesli 
  # nie ma mozliwosci skretu.
    for ( $i = 2; $i < $n; $i++ ) {
        while (
            kierunek( $x[ $h[ $#h - 1 ] ],
                       $y[ $h[ $#h - 1 ] ],
                       $x[ $h[ $#h ] ],
                       $y[ $h[ $#h ] ],
                       $x[ $i ],
                       $y[ $i ] ) > epsilon
               and @h >= 2 ) {  # Te 2 punkty zawsze beda stanowily czesc powloki.
            pop @h;
        }
        push @h, $i;  # Zwiekszenie powloki.
    }

    # Przeniesienie wartosci x i y powloki z powrotem na liste, a nastepnie powrot.
    return map { ( $x[ $_ ], $y[ $_ ] ) } @h;
}

