Thread kollisionen zweier polygonen (16 answers)
Opened by #Kein Kommentar at 2010-12-10 18:47

Kalle
 2011-01-10 06:29
#144376 #144376
User since
2007-03-18
48 Artikel
BenutzerIn
[default_avatar]
Hallöchen,

schaumal, ich hab was gebastelt für dich. Ob die Performance reicht für das was du vorhast weiss ich nicht, aber zumindest scheint es zu funktionieren (bis auf ein Pixel Abweichung manchmal, glaub ich).

Die Kollisionsprüfung lasse ich von canvas find overlapping machen und gebe nur solange bounding boxes für den Vergleich vor, bis eindeutig eine Kollision vorliegt.
Dabei zerlege ich die bounding box bei einer Kollisionmeldung in 4 kleinere Bereiche, die dann wiederum separat geprüft und gegebenenfalls zerlegt werden, bis zum Schluss nur noch ein Punkt übrigbleibt, der garantiert eine Kollision darstellt.
Alle anderen Boxen, in denen von find kein weiteres Objekt gemeldet wird, werden gelöscht, ohne weiter zerlegt zu werden.

Ich habe jedem Polygon einen Namen gegeben (tag) und der return code ist eine Liste der Namen der getroffenen Objekte.
Ich traue dem Code also zu, zu bemerken, daß mehr als ein Objekt getroffen wurde :)
Nachteil dabei: Je mehr die Polygone sich überlappen, umso rechenintensiver wird das Ganze, weil jede Menge bounding boxes bis ins kleinste zerlegt werden müssen.
Wenn man nur eine Kollision braucht, kann man das sicher noch etwas abkürzen.

(mit $DEBUG=1 gibts auch bissel tracing)

Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/usr/bin/perl -w

use strict;
use warnings;

use Tk;
use Tk::Canvas;
use Data::Dumper;
use Storable qw(dclone);

my $mw = MainWindow->new();
$mw->state('zoomed');

my $canvas =
        $mw->Canvas()->pack(-expand => 1, -fill => 'both');
        
my %coord_1 = (
        'a' => [100,100,'b'],
        'b' => [200,100,'c'],
        'c' => [200,200,'d'],
        'd' => [150,250,'e'],
        'e' => [100,200,'a'],
);

my %coord_2 = (
        'f' => [250,250,'g'],
        'g' => [300,200,'h'],
        'h' => [300,300,'i'],
        'i' => [250,350,'j'],
        'j' => [200,300,'f'],
);

drawPolygon($canvas, \%coord_1, 'yellow', 'poly1'); #modified
my $moving_pol =  drawPolygon($canvas, \%coord_2, 'lightblue', 'poly2'); #modified
my $moving_pol_tag='poly2';

$mw->bind('<Up>', sub{movePolygon($canvas,$moving_pol,0,-5,$moving_pol_tag);}); #modified
$mw->bind('<Down>', sub{movePolygon($canvas,$moving_pol,0,5,$moving_pol_tag);}); #modified
$mw->bind('<Left>', sub{movePolygon($canvas,$moving_pol,-5,0,$moving_pol_tag);}); #modified
$mw->bind('<Right>', sub{movePolygon($canvas,$moving_pol,5,0,$moving_pol_tag);}); #modified

Tk::MainLoop;

sub drawPolygon{
        my $widget      = shift;
        my $coord       = shift;
        my $color       = shift;
        my $tag         = shift;     #added
        
        my @coords;
        foreach my $point (sort keys %{$coord}){
                push(@coords, $coord->{$point}->[0], $coord->{$point}->[1]);
        }

        return  $widget->createPolygon(
                @coords,
                -fill => $color,
                -outline => 'black',
                -tag => $tag        #added
        );
}

sub movePolygon{
        my $widget  = shift;
        my $polygon = shift;
        my $x_move  = shift;
        my $y_move  = shift;
        my $tag     = shift;        #added

        foreach my $point (sort keys %coord_2){
                $coord_2{$point}->[0] += $x_move;
                $coord_2{$point}->[1] += $y_move;
        }

        $widget->move($polygon, $x_move, $y_move);
        
        if (PolygonsOverlap(\%coord_2,$tag)){                  #modified
                $widget->itemconfigure(
                        $polygon, 
                        -outline        => 'red',
                        -fill => 'lightgreen'
                );
        }else{
                $widget->itemconfigure(
                        $polygon, 
                        -outline        => 'black',
                        -fill => 'lightblue'
                );
        }
}

###################################
# algorithmus zum testen, ob sich #
# die polygone überschneiden.     #
###################################
sub PolygonsOverlap{
        #my %coord_1 = %{shift()}; 
        my %coord_2 = %{shift()};
        my $moving_pol_tag = shift;         # name of the polygon that we are moving around

        my $bboxes;        # hash of all bounding boxes to check   
        my $clonedbboxes;  # clone of bboxes. Used only for loop iteration to avoid side effects when manipulating $bboxes inside the loop
        #my $tbboxes;
        my $objectshit;    # names of the objects you have hit
        my $xm=1;          # 1 needed for first run
        my $ym=1;
        my $DEBUG=0;

        print "--- entering colissioncheck ---\n" if ($DEBUG);

        my $bboxname='1';   # The first bounding box is a large rectangle that encloses the polygon completly. Lets call it '1'
        $bboxes->{$bboxname}->{bbxmin}=999999; 
        $bboxes->{$bboxname}->{bbxmax}=0;
        $bboxes->{$bboxname}->{bbymin}=999999;
        $bboxes->{$bboxname}->{bbymax}=0;

        #reduce the size of the same rectangle to exactly enclose our moving polygon
        foreach my $point (keys %coord_2){
         $bboxes->{$bboxname}->{bbxmin}=$coord_2{$point}->[0] if ($coord_2{$point}->[0] < $bboxes->{$bboxname}->{bbxmin});
         $bboxes->{$bboxname}->{bbxmax}=$coord_2{$point}->[0] if ($coord_2{$point}->[0] > $bboxes->{$bboxname}->{bbxmax});
         $bboxes->{$bboxname}->{bbymin}=$coord_2{$point}->[1] if ($coord_2{$point}->[1] < $bboxes->{$bboxname}->{bbymin});
         $bboxes->{$bboxname}->{bbymax}=$coord_2{$point}->[1] if ($coord_2{$point}->[1] > $bboxes->{$bboxname}->{bbymax});
        }
        
        while (scalar keys %{$bboxes} > 0) {    # keep checking as long as we have bboxes
          #my @deleteme;   # remove comment if you dont want to use dclone
          #my $tbboxes;    # remove comment if you dont want to use dclone
          my $numberofbboxes = scalar keys %{$bboxes};
          print "number of bboxes to check: $numberofbboxes\n" if ($DEBUG);

          %{$clonedbboxes} = %{ dclone \%{$bboxes} };          # clone the original bboxes hash and use it to control the loop, just to avoid side effects
          foreach my $thisbboxname (keys %{$clonedbboxes}) {   # process all bounding boxes
            my @objectsfound;
            print "next bbox to check: $thisbboxname (".$bboxes->{$thisbboxname}->{bbxmin}.",".$bboxes->{$thisbboxname}->{bbymin}."  ". $bboxes->{$thisbboxname}->{bbxmax}.",".$bboxes->{$thisbboxname}->{bbymax}.")\n" if ($DEBUG);

            #find canvas objects overlapping the current boundary box
            foreach my $thisobject ($canvas->find('overlapping', $bboxes->{$thisbboxname}->{bbxmin}, $bboxes->{$thisbboxname}->{bbymin},$bboxes->{$thisbboxname}->{bbxmax},$bboxes->{$thisbboxname}->{bbymax})) {
              my @tags = $canvas->gettags($thisobject);          # get the tags of the objects found
              #next if ($tags[0] eq $moving_pol_tag);            
              push (@objectsfound,$tags[0]);                     # save the name we have given to our objects. Be careful here when using more than one tag for your objects
            }
              my $objcount=scalar @objectsfound;
              print "$moving_pol_tag: $objcount overlapping objects inside bboxname: $thisbboxname (".$bboxes->{$thisbboxname}->{bbxmin}.",".$bboxes->{$thisbboxname}->{bbymin}."  ".$bboxes->{$thisbboxname}->{bbxmax}.",".$bboxes->{$thisbboxname}->{bbymax}."): ".join(',',@objectsfound)."\n" if ($DEBUG);
              
              if ($objcount > 1) {    # found another object in the near so start locating it
                # split this bounding box in the middle and create four smaller ones
                # AB
                # CD
                if (($xm>0) || ($ym>0)) {    # can the bbox be split any further ?
                  $xm=int(($bboxes->{$thisbboxname}->{bbxmax} - $bboxes->{$thisbboxname}->{bbxmin})/2);
                  $ym=int(($bboxes->{$thisbboxname}->{bbymax} - $bboxes->{$thisbboxname}->{bbymin})/2);
                  print "creating new bounding boxes:\n" if ($DEBUG);
                  # change the following 16 $bboxes on the left hand side to $tbboxes (and the print statements) to save new bboxes to a temp hash if you dont want to use dclone
                  #create rectangle A
                  $bboxname=$thisbboxname.'A';
                  $bboxes->{$bboxname}->{bbxmin}=$bboxes->{$thisbboxname}->{bbxmin};
                  $bboxes->{$bboxname}->{bbymin}=$bboxes->{$thisbboxname}->{bbymin};
                  $bboxes->{$bboxname}->{bbxmax}=$bboxes->{$thisbboxname}->{bbxmin} + $xm;
                  $bboxes->{$bboxname}->{bbymax}=$bboxes->{$thisbboxname}->{bbymin} + $ym;
                  print "$bboxname: ".$bboxes->{$bboxname}->{bbxmin}.",".$bboxes->{$bboxname}->{bbymin}."  ".$bboxes->{$bboxname}->{bbxmax}.",".$bboxes->{$bboxname}->{bbymax}."\n" if ($DEBUG);
                  #create rectangle B
                  $bboxname=$thisbboxname.'B';
                  $bboxes->{$bboxname}->{bbxmin}=$bboxes->{$thisbboxname}->{bbxmin} + $xm + 1;
                  $bboxes->{$bboxname}->{bbymin}=$bboxes->{$thisbboxname}->{bbymin};
                  $bboxes->{$bboxname}->{bbxmax}=$bboxes->{$thisbboxname}->{bbxmax};
                  $bboxes->{$bboxname}->{bbymax}=$bboxes->{$thisbboxname}->{bbymin} + $ym;
                  print "$bboxname: ".$bboxes->{$bboxname}->{bbxmin}.",".$bboxes->{$bboxname}->{bbymin}."  ".$bboxes->{$bboxname}->{bbxmax}.",".$bboxes->{$bboxname}->{bbymax}."\n" if ($DEBUG);
                  #create rectangle C
                  $bboxname=$thisbboxname.'C';
                  $bboxes->{$bboxname}->{bbxmin}=$bboxes->{$thisbboxname}->{bbxmin};
                  $bboxes->{$bboxname}->{bbymin}=$bboxes->{$thisbboxname}->{bbymin} + $ym + 1;
                  $bboxes->{$bboxname}->{bbxmax}=$bboxes->{$thisbboxname}->{bbxmin} + $xm;
                  $bboxes->{$bboxname}->{bbymax}=$bboxes->{$thisbboxname}->{bbymax};
                  print "$bboxname: ".$bboxes->{$bboxname}->{bbxmin}.",".$bboxes->{$bboxname}->{bbymin}."  ".$bboxes->{$bboxname}->{bbxmax}.",".$bboxes->{$bboxname}->{bbymax}."\n" if ($DEBUG);
                  #create rectangle D
                  $bboxname=$thisbboxname.'D';
                  $bboxes->{$bboxname}->{bbxmin}=$bboxes->{$thisbboxname}->{bbxmin} + $xm + 1;
                  $bboxes->{$bboxname}->{bbymin}=$bboxes->{$thisbboxname}->{bbymin} + $ym + 1;
                  $bboxes->{$bboxname}->{bbxmax}=$bboxes->{$thisbboxname}->{bbxmax};
                  $bboxes->{$bboxname}->{bbymax}=$bboxes->{$thisbboxname}->{bbymax};
                  print "$bboxname: ".$bboxes->{$bboxname}->{bbxmin}.",".$bboxes->{$bboxname}->{bbymin}."  ".$bboxes->{$bboxname}->{bbxmax}.",".$bboxes->{$bboxname}->{bbymax}."\n" if ($DEBUG);
                  print Dumper($bboxes) if ($DEBUG);
                } #end if $xm>0...
                else { #smallest bbox still has something overlapping...so its a collision
                  print "$moving_pol_tag: collision detected: ".join(',',@objectsfound)." in $thisbboxname (".$bboxes->{$thisbboxname}->{bbxmin}.",".$bboxes->{$thisbboxname}->{bbymin}."  ".$bboxes->{$thisbboxname}->{bbxmax}.",".$bboxes->{$thisbboxname}->{bbymax}.") !!!!!\n" if ($DEBUG);
                  foreach my $thishit (@objectsfound) {
                    next if ($thishit eq $moving_pol_tag);   # do not add the moving polygon
                    print "adding $thishit to objectshit hash\n" if ($DEBUG);
                    $objectshit->{$thishit}=1;               # use hash to get a unique list of objects hit. Save collision coords here if you need it
                  }
                }
              }

            delete $bboxes->{$thisbboxname};                           # comment this line out if you dont want to use dclone
            print "deleting bbox $thisbboxname\n" if ($DEBUG);         # comment this line out if you dont want to use dclone
            #print "marking $thisbboxname for deletion\n" if ($DEBUG); # uncomment this line if you dont want to use dclone
            #push (@deleteme,$thisbboxname);                           # uncomment this line if you dont want to use dclone

          } #end of the loop foreach my thisbboxname

          print "end of foreach thisbboxname\n" if ($DEBUG);

#          foreach (@deleteme) {                                       # uncomment this line if you dont want to use dclone
#            print "deleting bbox $_\n" if ($DEBUG);                   # uncomment this line if you dont want to use dclone
#            delete $bboxes->{$_} ;                                    # uncomment this line if you dont want to use dclone
#          }                                                           # uncomment this line if you dont want to use dclone
#          foreach my $thisbboxname (keys %{$tbboxes}) {                                # uncomment this line if you dont want to use dclone
#            print "adding bbox: $thisbboxname to original bboxhash\n" if ($DEBUG);     # uncomment this line if you dont want to use dclone
#            $bboxes->{$thisbboxname}->{bbxmin}=$tbboxes->{$thisbboxname}->{bbxmin};    # uncomment this line if you dont want to use dclone
#            $bboxes->{$thisbboxname}->{bbymin}=$tbboxes->{$thisbboxname}->{bbymin};    # uncomment this line if you dont want to use dclone
#            $bboxes->{$thisbboxname}->{bbxmax}=$tbboxes->{$thisbboxname}->{bbxmax};    # uncomment this line if you dont want to use dclone
#            $bboxes->{$thisbboxname}->{bbymax}=$tbboxes->{$thisbboxname}->{bbymax};    # uncomment this line if you dont want to use dclone
#          }                                                                            # uncomment this line if you dont want to use dclone

        } #end while

        my $numberofobjectshit= scalar keys %{$objectshit};
        print "Number of objects hit: $numberofobjectshit\n" if ($DEBUG);
        if ($numberofobjectshit > 0) {
          print "The following objects have been hit by $moving_pol_tag: ",join (",",keys %{$objectshit})."\n";
        }
        print "--- end of collisioncheck---\n" if ($DEBUG);

  return $numberofobjectshit;
}


Mir war nicht ganz geheuer keys hash für die Schleife zu benutzen und dann den Hash innerhalb der Schleife zu modifizieren, deshalb habe ich per dclone eine Kopie erstellt. Wer dclone nicht installiert hat, kann auch die Kommentare im Code befolgen und die hash Modifikationen nach draussen verlagern.

Gruß

Kalle

View full thread kollisionen zweier polygonen