| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ############################################################################# | 
| 2 |  |  |  |  |  |  | # Find paths from node to node in a Manhattan-style grid via A*. | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # (c) by Tels - part of Graph::Easy | 
| 5 |  |  |  |  |  |  | ############################################################################# | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | package Graph::Easy::Layout::Scout; | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | $VERSION = '0.75'; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | ############################################################################# | 
| 12 |  |  |  |  |  |  | ############################################################################# | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | package Graph::Easy; | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 49 |  |  | 49 |  | 253 | use strict; | 
|  | 49 |  |  |  |  | 106 |  | 
|  | 49 |  |  |  |  | 2182 |  | 
| 17 | 49 |  |  | 49 |  | 247 | use warnings; | 
|  | 49 |  |  |  |  | 172 |  | 
|  | 49 |  |  |  |  | 1831 |  | 
| 18 | 49 |  |  | 49 |  | 268 | use Graph::Easy::Node::Cell; | 
|  | 49 |  |  |  |  | 100 |  | 
|  | 49 |  |  |  |  | 4499 |  | 
| 19 | 49 |  |  |  |  | 163668 | use Graph::Easy::Edge::Cell qw/ | 
| 20 |  |  |  |  |  |  | EDGE_SHORT_E EDGE_SHORT_W EDGE_SHORT_N EDGE_SHORT_S | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | EDGE_SHORT_BD_EW EDGE_SHORT_BD_NS | 
| 23 |  |  |  |  |  |  | EDGE_SHORT_UN_EW EDGE_SHORT_UN_NS | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | EDGE_START_E EDGE_START_W EDGE_START_N EDGE_START_S | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | EDGE_END_E EDGE_END_W EDGE_END_N EDGE_END_S | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | EDGE_N_E EDGE_N_W EDGE_S_E EDGE_S_W | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | EDGE_N_W_S EDGE_S_W_N EDGE_E_S_W EDGE_W_S_E | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | EDGE_LOOP_NORTH EDGE_LOOP_SOUTH EDGE_LOOP_WEST EDGE_LOOP_EAST | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | EDGE_HOR EDGE_VER EDGE_HOLE | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | EDGE_S_E_W EDGE_N_E_W EDGE_E_N_S EDGE_W_N_S | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | EDGE_LABEL_CELL | 
| 40 |  |  |  |  |  |  | EDGE_TYPE_MASK | 
| 41 |  |  |  |  |  |  | EDGE_ARROW_MASK | 
| 42 |  |  |  |  |  |  | EDGE_FLAG_MASK | 
| 43 |  |  |  |  |  |  | EDGE_START_MASK | 
| 44 |  |  |  |  |  |  | EDGE_END_MASK | 
| 45 |  |  |  |  |  |  | EDGE_NO_M_MASK | 
| 46 | 49 |  |  | 49 |  | 267 | /; | 
|  | 49 |  |  |  |  | 103 |  | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | ############################################################################# | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | # mapping edge type (HOR, VER, NW etc) and dx/dy to startpoint flag | 
| 51 |  |  |  |  |  |  | my $start_points = { | 
| 52 |  |  |  |  |  |  | #               [ dx == 1, 	dx == -1,     dy == 1,      dy == -1 , | 
| 53 |  |  |  |  |  |  | #                 dx == 1, 	dx == -1,     dy == 1,      dy == -1 ] | 
| 54 |  |  |  |  |  |  | EDGE_HOR() => [ EDGE_START_W, EDGE_START_E, 0,	    0 			, | 
| 55 |  |  |  |  |  |  | EDGE_END_E,   EDGE_END_W,   0,	    0,			], | 
| 56 |  |  |  |  |  |  | EDGE_VER() => [ 0,		0, 	      EDGE_START_N, EDGE_START_S 	, | 
| 57 |  |  |  |  |  |  | 0,		0,	      EDGE_END_S,   EDGE_END_N,		], | 
| 58 |  |  |  |  |  |  | EDGE_N_E() => [ 0,		EDGE_START_E, EDGE_START_N, 0		 	, | 
| 59 |  |  |  |  |  |  | EDGE_END_E,	0,	      0, 	    EDGE_END_N, 	], | 
| 60 |  |  |  |  |  |  | EDGE_N_W() => [ EDGE_START_W,	0, 	      EDGE_START_N, 0			, | 
| 61 |  |  |  |  |  |  | 0,	        EDGE_END_W,   0,	    EDGE_END_N,		], | 
| 62 |  |  |  |  |  |  | EDGE_S_E() => [ 0,		EDGE_START_E, 0,	    EDGE_START_S 	, | 
| 63 |  |  |  |  |  |  | EDGE_END_E,   0,            EDGE_END_S,   0,			], | 
| 64 |  |  |  |  |  |  | EDGE_S_W() => [ EDGE_START_W,	0, 	      0,	    EDGE_START_S	, | 
| 65 |  |  |  |  |  |  | 0,		EDGE_END_W,   EDGE_END_S,   0,			], | 
| 66 |  |  |  |  |  |  | }; | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | my $start_to_end = { | 
| 69 |  |  |  |  |  |  | EDGE_START_W() => EDGE_END_W(), | 
| 70 |  |  |  |  |  |  | EDGE_START_E() => EDGE_END_E(), | 
| 71 |  |  |  |  |  |  | EDGE_START_S() => EDGE_END_S(), | 
| 72 |  |  |  |  |  |  | EDGE_START_N() => EDGE_END_N(), | 
| 73 |  |  |  |  |  |  | }; | 
| 74 |  |  |  |  |  |  |  | 
| 75 |  |  |  |  |  |  | sub _end_points | 
| 76 |  |  |  |  |  |  | { | 
| 77 |  |  |  |  |  |  | # modify last field of path to be the correct endpoint; and the first field | 
| 78 |  |  |  |  |  |  | # to be the correct startpoint: | 
| 79 | 59 |  |  | 59 |  | 140 | my ($self, $edge, $coords, $dx, $dy) = @_; | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 59 | 100 |  |  |  | 237 | return $coords if $edge->undirected(); | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | # there are two cases (for each dx and dy) | 
| 84 | 57 |  |  |  |  | 101 | my $i = 0;					# index 0,1 | 
| 85 | 57 |  |  |  |  | 80 | my $co = 2; | 
| 86 | 57 |  |  |  |  | 73 | my $case; | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 57 |  |  |  |  | 129 | for my $d ($dx,$dy,$dx,$dy) | 
| 89 |  |  |  |  |  |  | { | 
| 90 | 228 | 100 |  |  |  | 1069 | next if $d == 0; | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 204 |  |  |  |  | 291 | my $type = $coords->[$co] & EDGE_TYPE_MASK; | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 204 | 100 |  |  |  | 267 | $case = 0; $case = 1 if $d == -1; | 
|  | 204 |  |  |  |  | 443 |  | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | # modify first/last cell | 
| 97 | 204 |  |  |  |  | 543 | my $t = $start_points->{ $type }->[ $case + $i ]; | 
| 98 |  |  |  |  |  |  | # on bidirectional edges, turn START_X into END_X | 
| 99 | 204 | 100 | 100 |  |  | 511 | $t = $start_to_end->{$t} || $t if $edge->{bidirectional}; | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 204 |  |  |  |  | 311 | $coords->[$co] += $t; | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | } continue { | 
| 104 | 228 |  |  |  |  | 240 | $i += 2; 					# index 2,3, 4,5 etc | 
| 105 | 228 | 100 |  |  |  | 547 | $co = -1 if $i == 4;			# modify now last cell | 
| 106 |  |  |  |  |  |  | } | 
| 107 | 57 |  |  |  |  | 296 | $coords; | 
| 108 |  |  |  |  |  |  | } | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | sub _find_path | 
| 111 |  |  |  |  |  |  | { | 
| 112 |  |  |  |  |  |  | # Try to find a path between two nodes. $options contains direction | 
| 113 |  |  |  |  |  |  | # preferences. Returns a list of cells like: | 
| 114 |  |  |  |  |  |  | # [ $x,$y,$type, $x1,$y1,$type1, ...] | 
| 115 | 905 |  |  | 905 |  | 7616 | my ($self, $src, $dst, $edge) = @_; | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | # one node pointing back to itself? | 
| 118 | 905 | 100 |  |  |  | 2793 | if ($src == $dst) | 
| 119 |  |  |  |  |  |  | { | 
| 120 | 31 |  |  |  |  | 145 | my $rc = $self->_find_path_loop($src,$edge); | 
| 121 | 31 | 50 |  |  |  | 157 | return $rc unless scalar @$rc == 0; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  |  | 
| 124 |  |  |  |  |  |  | # If one of the two nodes is bigger than 1 cell, use _find_path_astar(), | 
| 125 |  |  |  |  |  |  | # because it automatically handles all the possibilities: | 
| 126 | 874 | 100 | 100 |  |  | 4017 | return $self->_find_path_astar($edge) | 
|  |  |  | 100 |  |  |  |  | 
| 127 |  |  |  |  |  |  | if ($src->is_multicelled() || $dst->is_multicelled() || $edge->has_ports()); | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 742 |  |  |  |  | 2654 | my ($x0, $y0) = ($src->{x}, $src->{y}); | 
| 130 | 742 |  |  |  |  | 2076 | my ($x1, $y1) = ($dst->{x}, $dst->{y}); | 
| 131 | 742 |  |  |  |  | 1480 | my $dx = ($x1 - $x0) <=> 0; | 
| 132 | 742 |  |  |  |  | 1213 | my $dy = ($y1 - $y0) <=> 0; | 
| 133 |  |  |  |  |  |  |  | 
| 134 | 742 |  |  |  |  | 1336 | my $cells = $self->{cells}; | 
| 135 | 742 |  |  |  |  | 992 | my @coords; | 
| 136 | 742 |  |  |  |  | 1634 | my ($x,$y) = ($x0,$y0);			# starting pos | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | ########################################################################### | 
| 139 |  |  |  |  |  |  | # below follow some shortcuts for easy things like straight paths: | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 742 | 50 |  |  |  | 2042 | print STDERR "#  dx,dy: $dx,$dy\n" if $self->{debug}; | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 742 | 100 | 100 |  |  | 3898 | if ($dx == 0 || $dy == 0) | 
| 144 |  |  |  |  |  |  | { | 
| 145 |  |  |  |  |  |  | # try straight path to target: | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 679 | 50 |  |  |  | 8612 | print STDERR "#  $src->{x},$src->{y} => $dst->{x},$dst->{y} - trying short path\n" if $self->{debug}; | 
| 148 |  |  |  |  |  |  |  | 
| 149 |  |  |  |  |  |  | # distance to node: | 
| 150 | 679 |  |  |  |  | 1182 | my $dx1 = ($x1 - $x0); | 
| 151 | 679 |  |  |  |  | 1192 | my $dy1 = ($y1 - $y0); | 
| 152 | 679 |  |  |  |  | 1683 | ($x,$y) = ($x0+$dx,$y0+$dy);			# starting pos | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 679 | 100 | 100 |  |  | 2766 | if ((abs($dx1) == 2) || (abs($dy1) == 2)) | 
| 155 |  |  |  |  |  |  | { | 
| 156 | 579 | 100 |  |  |  | 2865 | if (!exists ($cells->{"$x,$y"})) | 
| 157 |  |  |  |  |  |  | { | 
| 158 |  |  |  |  |  |  | # a single step for this edge: | 
| 159 | 567 |  |  |  |  | 1270 | my $type = EDGE_LABEL_CELL; | 
| 160 |  |  |  |  |  |  | # short path | 
| 161 | 567 | 100 |  |  |  | 2708 | if ($edge->bidirectional()) | 
|  |  | 100 |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | { | 
| 163 | 5 | 100 |  |  |  | 24 | $type += EDGE_SHORT_BD_EW if $dy == 0; | 
| 164 | 5 | 100 |  |  |  | 22 | $type += EDGE_SHORT_BD_NS if $dx == 0; | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  | elsif ($edge->undirected()) | 
| 167 |  |  |  |  |  |  | { | 
| 168 | 13 | 100 |  |  |  | 46 | $type += EDGE_SHORT_UN_EW if $dy == 0; | 
| 169 | 13 | 100 |  |  |  | 43 | $type += EDGE_SHORT_UN_NS if $dx == 0; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  | else | 
| 172 |  |  |  |  |  |  | { | 
| 173 | 549 | 100 | 66 |  |  | 2952 | $type += EDGE_SHORT_E if ($dx ==  1 && $dy ==  0); | 
| 174 | 549 | 100 | 100 |  |  | 2194 | $type += EDGE_SHORT_S if ($dx ==  0 && $dy ==  1); | 
| 175 | 549 | 100 | 66 |  |  | 2071 | $type += EDGE_SHORT_W if ($dx == -1 && $dy ==  0); | 
| 176 | 549 | 100 | 100 |  |  | 2098 | $type += EDGE_SHORT_N if ($dx ==  0 && $dy == -1); | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  | # if one of the end points of the edge is of shape 'edge' | 
| 179 |  |  |  |  |  |  | # remove end/start flag | 
| 180 | 567 | 100 | 50 |  |  | 2749 | if (($edge->{to}->attribute('shape') ||'') eq 'edge') | 
| 181 |  |  |  |  |  |  | { | 
| 182 |  |  |  |  |  |  | # we only need to remove one start point, namely the one at the "end" | 
| 183 | 4 | 100 |  |  |  | 17 | if ($dx > 0) | 
|  |  | 50 |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | { | 
| 185 | 3 |  |  |  |  | 7 | $type &= ~EDGE_START_E; | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | elsif ($dx < 0) | 
| 188 |  |  |  |  |  |  | { | 
| 189 | 0 |  |  |  |  | 0 | $type &= ~EDGE_START_W; | 
| 190 |  |  |  |  |  |  | } | 
| 191 |  |  |  |  |  |  | } | 
| 192 | 567 | 100 | 50 |  |  | 4339 | if (($edge->{from}->attribute('shape') ||'') eq 'edge') | 
| 193 |  |  |  |  |  |  | { | 
| 194 | 3 |  |  |  |  | 5 | $type &= ~EDGE_START_MASK; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 567 |  |  |  |  | 3456 | return [ $x, $y, $type ];			# return a short EDGE | 
| 198 |  |  |  |  |  |  | } | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 112 | 100 |  |  |  | 245 | my $type = EDGE_HOR; $type = EDGE_VER if $dx == 0;	# - or | | 
|  | 112 |  |  |  |  | 380 |  | 
| 202 | 112 |  |  |  |  | 197 | my $done = 0; | 
| 203 | 112 |  |  |  |  | 212 | my $label_done = 0; | 
| 204 | 112 |  |  |  |  | 167 | while (3 < 5)		# endless loop | 
| 205 |  |  |  |  |  |  | { | 
| 206 |  |  |  |  |  |  | # Since we do not handle crossings here, A* will be tried if we hit an | 
| 207 |  |  |  |  |  |  | # edge in this test. | 
| 208 | 282 | 100 |  |  |  | 1253 | $done = 1, last if exists $cells->{"$x,$y"};	# cell already full | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | # the first cell gets the label | 
| 211 | 181 | 100 |  |  |  | 235 | my $t = $type; $t += EDGE_LABEL_CELL if $label_done++ == 0; | 
|  | 181 |  |  |  |  | 378 |  | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 181 |  |  |  |  | 698 | push @coords, $x, $y, $t;				# good one, is free | 
| 214 | 181 |  |  |  |  | 197 | $x += $dx; $y += $dy;				# next field | 
|  | 181 |  |  |  |  | 195 |  | 
| 215 | 181 | 100 | 100 |  |  | 679 | last if ($x == $x1) && ($y == $y1); | 
| 216 |  |  |  |  |  |  | } | 
| 217 |  |  |  |  |  |  |  | 
| 218 | 112 | 100 |  |  |  | 411 | if ($done == 0) | 
| 219 |  |  |  |  |  |  | { | 
| 220 | 11 | 50 |  |  |  | 35 | print STDERR "#  success for ", scalar @coords / 3, " steps in path\n" if $self->{debug}; | 
| 221 |  |  |  |  |  |  | # return all fields of path | 
| 222 | 11 |  |  |  |  | 65 | return $self->_end_points($edge, \@coords, $dx, $dy); | 
| 223 |  |  |  |  |  |  | } | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | } # end else straight path try | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | ########################################################################### | 
| 228 |  |  |  |  |  |  | # Try paths with one bend: | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # ($dx != 0 && $dy != 0) => path with one bend | 
| 231 |  |  |  |  |  |  | # XXX TODO: | 
| 232 |  |  |  |  |  |  | # This could be handled by A*, too, but it would be probably a bit slower. | 
| 233 |  |  |  |  |  |  | else | 
| 234 |  |  |  |  |  |  | { | 
| 235 |  |  |  |  |  |  | # straight path not possible, since x0 != x1 AND y0 != y1 | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | #           "  |"                        "|   " | 
| 238 |  |  |  |  |  |  | # try first "--+" (aka hor => ver), then "+---" (aka ver => hor) | 
| 239 | 63 |  |  |  |  | 145 | my $done = 0; | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 63 | 50 |  |  |  | 212 | print STDERR "#  bend path from $x,$y\n" if $self->{debug}; | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | # try hor => ver | 
| 244 | 63 |  |  |  |  | 128 | my $type = EDGE_HOR; | 
| 245 |  |  |  |  |  |  |  | 
| 246 | 63 |  |  |  |  | 94 | my $label = 0;						# attach label? | 
| 247 | 63 | 50 | 50 |  |  | 459 | $label = 1 if ref($edge) && ($edge->label()||'') eq '';	# no label? | 
|  |  |  | 33 |  |  |  |  | 
| 248 | 63 |  |  |  |  | 112 | $x += $dx; | 
| 249 | 63 |  |  |  |  | 192 | while ($x != $x1) | 
| 250 |  |  |  |  |  |  | { | 
| 251 | 67 | 100 |  |  |  | 306 | $done++, last if exists $cells->{"$x,$y"};	# cell already full | 
| 252 | 51 | 50 |  |  |  | 153 | print STDERR "#  at $x,$y\n" if $self->{debug}; | 
| 253 | 51 | 50 |  |  |  | 85 | my $t = $type; $t += EDGE_LABEL_CELL if $label++ == 0; | 
|  | 51 |  |  |  |  | 143 |  | 
| 254 | 51 |  |  |  |  | 138 | push @coords, $x, $y, $t;				# good one, is free | 
| 255 | 51 |  |  |  |  | 128 | $x += $dx;					# next field | 
| 256 |  |  |  |  |  |  | }; | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | # check the bend itself | 
| 259 | 63 | 100 |  |  |  | 246 | $done++ if exists $cells->{"$x,$y"};	# cell already full | 
| 260 |  |  |  |  |  |  |  | 
| 261 | 63 | 100 |  |  |  | 186 | if ($done == 0) | 
| 262 |  |  |  |  |  |  | { | 
| 263 | 27 |  |  |  |  | 113 | my $type_bend = _astar_edge_type ($x-$dx,$y, $x,$y, $x,$y+$dy); | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 27 |  |  |  |  | 109 | push @coords, $x, $y, $type_bend;			# put in bend | 
| 266 | 27 | 50 |  |  |  | 86 | print STDERR "# at $x,$y\n" if $self->{debug}; | 
| 267 | 27 |  |  |  |  | 38 | $y += $dy; | 
| 268 | 27 |  |  |  |  | 34 | $type = EDGE_VER; | 
| 269 | 27 |  |  |  |  | 101 | while ($y != $y1) | 
| 270 |  |  |  |  |  |  | { | 
| 271 | 19 | 50 |  |  |  | 70 | $done++, last if exists $cells->{"$x,$y"};	# cell already full | 
| 272 | 19 | 50 |  |  |  | 53 | print STDERR "# at $x,$y\n" if $self->{debug}; | 
| 273 | 19 |  |  |  |  | 77 | push @coords, $x, $y, $type;			# good one, is free | 
| 274 | 19 |  |  |  |  | 56 | $y += $dy; | 
| 275 |  |  |  |  |  |  | } | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 63 | 100 |  |  |  | 158 | if ($done != 0) | 
| 279 |  |  |  |  |  |  | { | 
| 280 | 36 |  |  |  |  | 65 | $done = 0; | 
| 281 |  |  |  |  |  |  | # try ver => hor | 
| 282 | 36 | 50 |  |  |  | 118 | print STDERR "# hm, now trying first vertical, then horizontal\n" if $self->{debug}; | 
| 283 | 36 |  |  |  |  | 59 | $type = EDGE_VER; | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 36 |  |  |  |  | 72 | @coords = ();					# drop old version | 
| 286 | 36 |  |  |  |  | 84 | ($x,$y) = ($x0, $y0 + $dy);			# starting pos | 
| 287 | 36 |  |  |  |  | 106 | while ($y != $y1) | 
| 288 |  |  |  |  |  |  | { | 
| 289 | 67 | 100 |  |  |  | 275 | $done++, last if exists $cells->{"$x,$y"};	# cell already full | 
| 290 | 59 | 50 |  |  |  | 143 | print STDERR "# at $x,$y\n" if $self->{debug}; | 
| 291 | 59 |  |  |  |  | 140 | push @coords, $x, $y, $type;			# good one, is free | 
| 292 | 59 |  |  |  |  | 137 | $y += $dy;					# next field | 
| 293 |  |  |  |  |  |  | }; | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | # check the bend itself | 
| 296 | 36 | 100 |  |  |  | 141 | $done++ if exists $cells->{"$x,$y"};		# cell already full | 
| 297 |  |  |  |  |  |  |  | 
| 298 | 36 | 100 |  |  |  | 123 | if ($done == 0) | 
| 299 |  |  |  |  |  |  | { | 
| 300 | 25 |  |  |  |  | 120 | my $type_bend = _astar_edge_type ($x,$y-$dy, $x,$y, $x+$dx,$y); | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 25 |  |  |  |  | 75 | push @coords, $x, $y, $type_bend;		# put in bend | 
| 303 | 25 | 50 |  |  |  | 83 | print STDERR "# at $x,$y\n" if $self->{debug}; | 
| 304 | 25 |  |  |  |  | 36 | $x += $dx; | 
| 305 | 25 |  |  |  |  | 45 | my $label = 0;					# attach label? | 
| 306 | 25 | 50 |  |  |  | 94 | $label = 1 if $edge->label() eq '';		# no label? | 
| 307 | 25 |  |  |  |  | 49 | $type = EDGE_HOR; | 
| 308 | 25 |  |  |  |  | 69 | while ($x != $x1) | 
| 309 |  |  |  |  |  |  | { | 
| 310 | 31 | 100 |  |  |  | 110 | $done++, last if exists $cells->{"$x,$y"};	# cell already full | 
| 311 | 27 | 50 |  |  |  | 87 | print STDERR "# at $x,$y\n" if $self->{debug}; | 
| 312 | 27 | 50 |  |  |  | 52 | my $t = $type; $t += EDGE_LABEL_CELL if $label++ == 0; | 
|  | 27 |  |  |  |  | 66 |  | 
| 313 | 27 |  |  |  |  | 90 | push @coords, $x, $y, $t;			# good one, is free | 
| 314 | 27 |  |  |  |  | 82 | $x += $dx; | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | } | 
| 317 |  |  |  |  |  |  | } | 
| 318 |  |  |  |  |  |  |  | 
| 319 | 63 | 100 |  |  |  | 163 | if ($done == 0) | 
| 320 |  |  |  |  |  |  | { | 
| 321 | 48 | 50 |  |  |  | 131 | print STDERR "# success for ", scalar @coords / 3, " steps in path\n" if $self->{debug}; | 
| 322 |  |  |  |  |  |  | # return all fields of path | 
| 323 | 48 |  |  |  |  | 339 | return $self->_end_points($edge, \@coords, $dx, $dy); | 
| 324 |  |  |  |  |  |  | } | 
| 325 |  |  |  |  |  |  |  | 
| 326 | 15 | 50 |  |  |  | 64 | print STDERR "# no success\n" if $self->{debug}; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | } # end path with $dx and $dy | 
| 329 |  |  |  |  |  |  |  | 
| 330 | 116 |  |  |  |  | 576 | $self->_find_path_astar($edge);		# try generic approach as last hope | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | sub _find_path_loop | 
| 334 |  |  |  |  |  |  | { | 
| 335 |  |  |  |  |  |  | # find a path from one node back to itself | 
| 336 | 31 |  |  | 31 |  | 58 | my ($self, $src, $edge) = @_; | 
| 337 |  |  |  |  |  |  |  | 
| 338 | 31 | 50 |  |  |  | 119 | print STDERR "# Finding looping path from $src->{name} to $src->{name}\n" if $self->{debug}; | 
| 339 |  |  |  |  |  |  |  | 
| 340 | 31 |  |  |  |  | 70 | my ($n, $cells, $d, $type, $loose) = @_; | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | # get a list of all places | 
| 343 |  |  |  |  |  |  |  | 
| 344 | 31 |  |  |  |  | 205 | my @places = $src->_near_places( | 
| 345 |  |  |  |  |  |  | $self->{cells}, 1, [ | 
| 346 |  |  |  |  |  |  | EDGE_LOOP_EAST, | 
| 347 |  |  |  |  |  |  | EDGE_LOOP_SOUTH, | 
| 348 |  |  |  |  |  |  | EDGE_LOOP_WEST, | 
| 349 |  |  |  |  |  |  | EDGE_LOOP_NORTH, | 
| 350 |  |  |  |  |  |  | ], 0, 90); | 
| 351 |  |  |  |  |  |  |  | 
| 352 | 31 |  |  |  |  | 153 | my $flow = $src->flow(); | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | # We cannot use _shuffle_dir() here, because self-loops | 
| 355 |  |  |  |  |  |  | # are tried in a different order: | 
| 356 |  |  |  |  |  |  |  | 
| 357 |  |  |  |  |  |  | # the default (east) | 
| 358 | 31 |  |  |  |  | 211 | my $index = [ | 
| 359 |  |  |  |  |  |  | EDGE_LOOP_NORTH, | 
| 360 |  |  |  |  |  |  | EDGE_LOOP_SOUTH, | 
| 361 |  |  |  |  |  |  | EDGE_LOOP_WEST, | 
| 362 |  |  |  |  |  |  | EDGE_LOOP_EAST, | 
| 363 |  |  |  |  |  |  | ]; | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | # west | 
| 366 | 31 | 100 |  |  |  | 111 | $index = [ | 
| 367 |  |  |  |  |  |  | EDGE_LOOP_SOUTH, | 
| 368 |  |  |  |  |  |  | EDGE_LOOP_NORTH, | 
| 369 |  |  |  |  |  |  | EDGE_LOOP_EAST, | 
| 370 |  |  |  |  |  |  | EDGE_LOOP_WEST, | 
| 371 |  |  |  |  |  |  | ] if $flow == 270; | 
| 372 |  |  |  |  |  |  |  | 
| 373 |  |  |  |  |  |  | # north | 
| 374 | 31 | 100 |  |  |  | 235 | $index = [ | 
| 375 |  |  |  |  |  |  | EDGE_LOOP_WEST, | 
| 376 |  |  |  |  |  |  | EDGE_LOOP_EAST, | 
| 377 |  |  |  |  |  |  | EDGE_LOOP_SOUTH, | 
| 378 |  |  |  |  |  |  | EDGE_LOOP_NORTH, | 
| 379 |  |  |  |  |  |  | ] if $flow == 0; | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | # south | 
| 382 | 31 | 100 |  |  |  | 105 | $index = [ | 
| 383 |  |  |  |  |  |  | EDGE_LOOP_EAST, | 
| 384 |  |  |  |  |  |  | EDGE_LOOP_WEST, | 
| 385 |  |  |  |  |  |  | EDGE_LOOP_NORTH, | 
| 386 |  |  |  |  |  |  | EDGE_LOOP_SOUTH, | 
| 387 |  |  |  |  |  |  | ] if $flow == 180; | 
| 388 |  |  |  |  |  |  |  | 
| 389 | 31 |  |  |  |  | 75 | for my $this_try (@$index) | 
| 390 |  |  |  |  |  |  | { | 
| 391 | 59 |  |  |  |  | 80 | my $idx = 0; | 
| 392 | 59 |  |  |  |  | 180 | while ($idx < @places) | 
| 393 |  |  |  |  |  |  | { | 
| 394 | 114 | 50 |  |  |  | 250 | print STDERR "# Trying $places[$idx+0],$places[$idx+1]\n" if $self->{debug}; | 
| 395 | 114 | 100 |  |  |  | 275 | next unless $places[$idx+2] == $this_try; | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  | # build a path from the returned piece | 
| 398 | 31 |  |  |  |  | 115 | my @rc = ($places[$idx], $places[$idx+1], $places[$idx+2]); | 
| 399 |  |  |  |  |  |  |  | 
| 400 | 31 | 50 |  |  |  | 100 | print STDERR "# Trying $rc[0],$rc[1]\n" if $self->{debug}; | 
| 401 |  |  |  |  |  |  |  | 
| 402 | 31 | 50 |  |  |  | 173 | next unless $self->_path_is_clear(\@rc); | 
| 403 |  |  |  |  |  |  |  | 
| 404 | 31 | 50 |  |  |  | 107 | print STDERR "# Found looping path\n" if $self->{debug}; | 
| 405 | 31 |  |  |  |  | 150 | return \@rc; | 
| 406 | 83 |  |  |  |  | 222 | } continue { $idx += 3; } | 
| 407 |  |  |  |  |  |  | } | 
| 408 |  |  |  |  |  |  |  | 
| 409 | 0 |  |  |  |  | 0 | [];		# no path found | 
| 410 |  |  |  |  |  |  | } | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | ############################################################################# | 
| 413 |  |  |  |  |  |  | ############################################################################# | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | # This package represents a simple/cheap/fast heap: | 
| 416 |  |  |  |  |  |  | package Graph::Easy::Heap; | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | require Graph::Easy::Base; | 
| 419 |  |  |  |  |  |  | our @ISA = qw/Graph::Easy::Base/; | 
| 420 |  |  |  |  |  |  |  | 
| 421 | 49 |  |  | 49 |  | 596 | use strict; | 
|  | 49 |  |  |  |  | 269 |  | 
|  | 49 |  |  |  |  | 130602 |  | 
| 422 |  |  |  |  |  |  |  | 
| 423 |  |  |  |  |  |  | sub _init | 
| 424 |  |  |  |  |  |  | { | 
| 425 | 745 |  |  | 745 |  | 2168 | my ($self,$args) = @_; | 
| 426 |  |  |  |  |  |  |  | 
| 427 | 745 |  |  |  |  | 5709 | $self->{_heap} = [ ]; | 
| 428 |  |  |  |  |  |  |  | 
| 429 | 745 |  |  |  |  | 2969 | $self; | 
| 430 |  |  |  |  |  |  | } | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | sub add | 
| 433 |  |  |  |  |  |  | { | 
| 434 |  |  |  |  |  |  | # add one element to the heap | 
| 435 | 7154 |  |  | 7154 |  | 12090 | my ($self,$elem) = @_; | 
| 436 |  |  |  |  |  |  |  | 
| 437 | 7154 |  |  |  |  | 11799 | my $heap = $self->{_heap}; | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # heap empty? | 
| 440 | 7154 | 100 |  |  |  | 30312 | if (@$heap == 0) | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | { | 
| 442 | 1262 |  |  |  |  | 3802 | push @$heap, $elem; | 
| 443 |  |  |  |  |  |  | } | 
| 444 |  |  |  |  |  |  | # smaller than first elem? | 
| 445 |  |  |  |  |  |  | elsif ($elem->[0] < $heap->[0]->[0]) | 
| 446 |  |  |  |  |  |  | { | 
| 447 |  |  |  |  |  |  | #print STDERR "# $elem->[0] is smaller then first elem $heap->[0]->[0] (with ", scalar @$heap," elems on heap)\n"; | 
| 448 | 1522 |  |  |  |  | 3594 | unshift @$heap, $elem; | 
| 449 |  |  |  |  |  |  | } | 
| 450 |  |  |  |  |  |  | # bigger than or equal to last elem? | 
| 451 |  |  |  |  |  |  | elsif ($elem->[0] > $heap->[-1]->[0]) | 
| 452 |  |  |  |  |  |  | { | 
| 453 |  |  |  |  |  |  | #print STDERR "# $elem->[0] is bigger then last elem $heap->[-1]->[0] (with ", scalar @$heap," elems on heap)\n"; | 
| 454 | 838 |  |  |  |  | 1806 | push @$heap, $elem; | 
| 455 |  |  |  |  |  |  | } | 
| 456 |  |  |  |  |  |  | else | 
| 457 |  |  |  |  |  |  | { | 
| 458 |  |  |  |  |  |  | # insert the elem at the right position | 
| 459 |  |  |  |  |  |  |  | 
| 460 |  |  |  |  |  |  | # if we have less than X elements, use linear search | 
| 461 | 3532 |  |  |  |  | 5372 | my $el = $elem->[0]; | 
| 462 | 3532 | 100 |  |  |  | 7548 | if (scalar @$heap < 10) | 
| 463 |  |  |  |  |  |  | { | 
| 464 | 1910 |  |  |  |  | 2895 | my $i = 0; | 
| 465 | 1910 |  |  |  |  | 16047 | for my $e (@$heap) | 
| 466 |  |  |  |  |  |  | { | 
| 467 | 5808 | 100 |  |  |  | 13958 | if ($e->[0] > $el) | 
| 468 |  |  |  |  |  |  | { | 
| 469 | 862 |  |  |  |  | 2965 | splice (@$heap, $i, 0, $elem);		# insert $elem | 
| 470 | 862 |  |  |  |  | 2493 | return undef; | 
| 471 |  |  |  |  |  |  | } | 
| 472 | 4946 |  |  |  |  | 7874 | $i++; | 
| 473 |  |  |  |  |  |  | } | 
| 474 |  |  |  |  |  |  | # else, append at the end | 
| 475 | 1048 |  |  |  |  | 2829 | push @$heap, $elem; | 
| 476 |  |  |  |  |  |  | } | 
| 477 |  |  |  |  |  |  | else | 
| 478 |  |  |  |  |  |  | { | 
| 479 |  |  |  |  |  |  | # use binary search | 
| 480 | 1622 |  |  |  |  | 2041 | my $l = 0; my $r = scalar @$heap; | 
|  | 1622 |  |  |  |  | 1974 |  | 
| 481 | 1622 |  |  |  |  | 3264 | while (($r - $l) > 2) | 
| 482 |  |  |  |  |  |  | { | 
| 483 | 7550 |  |  |  |  | 10363 | my $m = int((($r - $l) / 2) + $l); | 
| 484 |  |  |  |  |  |  | #        print "l=$l r=$r m=$m el=$el heap=$heap->[$m]->[0]\n"; | 
| 485 | 7550 | 100 |  |  |  | 13143 | if ($heap->[$m]->[0] <= $el) | 
| 486 |  |  |  |  |  |  | { | 
| 487 | 5040 |  |  |  |  | 9787 | $l = $m; | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  | else | 
| 490 |  |  |  |  |  |  | { | 
| 491 | 2510 |  |  |  |  | 4919 | $r = $m; | 
| 492 |  |  |  |  |  |  | } | 
| 493 |  |  |  |  |  |  | } | 
| 494 | 1622 |  |  |  |  | 3030 | while ($l < @$heap) | 
| 495 |  |  |  |  |  |  | { | 
| 496 | 3951 | 100 |  |  |  | 7606 | if ($heap->[$l]->[0] > $el) | 
| 497 |  |  |  |  |  |  | { | 
| 498 | 1438 |  |  |  |  | 2331 | splice (@$heap, $l, 0, $elem);		# insert $elem | 
| 499 | 1438 |  |  |  |  | 2714 | return undef; | 
| 500 |  |  |  |  |  |  | } | 
| 501 | 2513 |  |  |  |  | 4566 | $l++; | 
| 502 |  |  |  |  |  |  | } | 
| 503 |  |  |  |  |  |  | # else, append at the end | 
| 504 | 184 |  |  |  |  | 342 | push @$heap, $elem; | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  | } | 
| 507 | 4854 |  |  |  |  | 15681 | undef; | 
| 508 |  |  |  |  |  |  | } | 
| 509 |  |  |  |  |  |  |  | 
| 510 |  |  |  |  |  |  | sub elements | 
| 511 |  |  |  |  |  |  | { | 
| 512 | 523 |  |  | 523 |  | 1794 | scalar @{$_[0]->{_heap}}; | 
|  | 523 |  |  |  |  | 3840 |  | 
| 513 |  |  |  |  |  |  | } | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | sub extract_top | 
| 516 |  |  |  |  |  |  | { | 
| 517 |  |  |  |  |  |  | # remove and return the top elemt | 
| 518 | 6131 |  |  | 6131 |  | 24012 | shift @{$_[0]->{_heap}}; | 
|  | 6131 |  |  |  |  | 26636 |  | 
| 519 |  |  |  |  |  |  | } | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  | sub delete | 
| 522 |  |  |  |  |  |  | { | 
| 523 |  |  |  |  |  |  | # Find an element by $x,$y and delete it | 
| 524 | 0 |  |  | 0 |  | 0 | my ($self, $x, $y) = @_; | 
| 525 |  |  |  |  |  |  |  | 
| 526 | 0 |  |  |  |  | 0 | my $heap = $self->{_heap}; | 
| 527 |  |  |  |  |  |  |  | 
| 528 | 0 |  |  |  |  | 0 | my $i = 0; | 
| 529 | 0 |  |  |  |  | 0 | for my $e (@$heap) | 
| 530 |  |  |  |  |  |  | { | 
| 531 | 0 | 0 | 0 |  |  | 0 | if ($e->[1] == $x && $e->[2] == $y) | 
| 532 |  |  |  |  |  |  | { | 
| 533 | 0 |  |  |  |  | 0 | splice (@$heap, $i, 1); | 
| 534 | 0 |  |  |  |  | 0 | return; | 
| 535 |  |  |  |  |  |  | } | 
| 536 | 0 |  |  |  |  | 0 | $i++; | 
| 537 |  |  |  |  |  |  | } | 
| 538 |  |  |  |  |  |  |  | 
| 539 | 0 |  |  |  |  | 0 | $self; | 
| 540 |  |  |  |  |  |  | } | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | sub sort_sub | 
| 543 |  |  |  |  |  |  | { | 
| 544 | 502 |  |  | 502 |  | 15610 | my ($self) = shift; | 
| 545 |  |  |  |  |  |  |  | 
| 546 | 502 |  |  |  |  | 1965 | $self->{_sort} = shift; | 
| 547 |  |  |  |  |  |  | } | 
| 548 |  |  |  |  |  |  |  | 
| 549 |  |  |  |  |  |  | ############################################################################# | 
| 550 |  |  |  |  |  |  | ############################################################################# | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | package Graph::Easy; | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | # Generic pathfinding via the A* algorithm: | 
| 555 |  |  |  |  |  |  | # See http://bloodgate.com/perl/graph/astar.html for some background. | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | sub _astar_modifier | 
| 558 |  |  |  |  |  |  | { | 
| 559 |  |  |  |  |  |  | # calculate the cost for the path at cell x1,y1 | 
| 560 | 6564 |  |  | 6564 |  | 12314 | my ($x1,$y1,$x,$y,$px,$py, $cells) = @_; | 
| 561 |  |  |  |  |  |  |  | 
| 562 | 6564 |  |  |  |  | 7649 | my $add = 1; | 
| 563 |  |  |  |  |  |  |  | 
| 564 | 6564 | 50 |  |  |  | 13473 | if (defined $x1) | 
| 565 |  |  |  |  |  |  | { | 
| 566 | 6564 |  |  |  |  | 10232 | my $xy = "$x1,$y1"; | 
| 567 |  |  |  |  |  |  | # add a harsh penalty for crossing an edge, meaning we can travel many | 
| 568 |  |  |  |  |  |  | # fields to go around. | 
| 569 | 6564 | 100 | 100 |  |  | 27739 | $add += 30 if ref($cells->{$xy}) && $cells->{$xy}->isa('Graph::Easy::Edge'); | 
| 570 |  |  |  |  |  |  | } | 
| 571 |  |  |  |  |  |  |  | 
| 572 | 6564 | 100 |  |  |  | 14841 | if (defined $px) | 
| 573 |  |  |  |  |  |  | { | 
| 574 |  |  |  |  |  |  | # see whether the new position $x1,$y1 is a continuation from $px,$py => $x,$y | 
| 575 |  |  |  |  |  |  | # e.g. if from we go down from $px,$py to $x,$y, then anything else then $x,$y+1 will | 
| 576 |  |  |  |  |  |  | # get a penalty | 
| 577 | 6563 |  |  |  |  | 9802 | my $dx1 = ($px-$x) <=> 0; | 
| 578 | 6563 |  |  |  |  | 8732 | my $dy1 = ($py-$y) <=> 0; | 
| 579 | 6563 |  |  |  |  | 7759 | my $dx2 = ($x-$x1) <=> 0; | 
| 580 | 6563 |  |  |  |  | 7615 | my $dy2 = ($y-$y1) <=> 0; | 
| 581 | 6563 | 100 | 100 |  |  | 27194 | $add += 6 unless $dx1 == $dx2 || $dy1 == $dy2; | 
| 582 |  |  |  |  |  |  | } | 
| 583 | 6564 |  |  |  |  | 13901 | $add; | 
| 584 |  |  |  |  |  |  | } | 
| 585 |  |  |  |  |  |  |  | 
| 586 |  |  |  |  |  |  | sub _astar_distance | 
| 587 |  |  |  |  |  |  | { | 
| 588 |  |  |  |  |  |  | # calculate the manhattan distance between x1,y1 and x2,y2 | 
| 589 |  |  |  |  |  |  | #  my ($x1,$y1,$x2,$y2) = @_; | 
| 590 |  |  |  |  |  |  |  | 
| 591 | 20860 |  |  | 20860 |  | 28747 | my $dx = abs($_[2] - $_[0]); | 
| 592 | 20860 |  |  |  |  | 26345 | my $dy = abs($_[3] - $_[1]); | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | # plus 1 because we need to go around one corner if $dx != 0 && $dx != 0 | 
| 595 | 20860 | 100 | 100 |  |  | 83625 | $dx++ if $dx != 0 && $dy != 0; | 
| 596 |  |  |  |  |  |  |  | 
| 597 | 20860 |  |  |  |  | 38074 | $dx + $dy; | 
| 598 |  |  |  |  |  |  | } | 
| 599 |  |  |  |  |  |  |  | 
| 600 |  |  |  |  |  |  | my $edge_type = { | 
| 601 |  |  |  |  |  |  | '0,1,-1,0' => EDGE_N_W, | 
| 602 |  |  |  |  |  |  | '0,1,0,1' => EDGE_VER, | 
| 603 |  |  |  |  |  |  | '0,1,1,0' => EDGE_N_E, | 
| 604 |  |  |  |  |  |  |  | 
| 605 |  |  |  |  |  |  | '-1,0,0,-1' => EDGE_N_E, | 
| 606 |  |  |  |  |  |  | '-1,0,-1,0' => EDGE_HOR, | 
| 607 |  |  |  |  |  |  | '-1,0,0,1' => EDGE_S_E, | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | '0,-1,-1,0' => EDGE_S_W, | 
| 610 |  |  |  |  |  |  | '0,-1,0,-1' => EDGE_VER, | 
| 611 |  |  |  |  |  |  | '0,-1,1,0' => EDGE_S_E, | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | '1,0,0,-1' => EDGE_N_W, | 
| 614 |  |  |  |  |  |  | '1,0,1,0' => EDGE_HOR, | 
| 615 |  |  |  |  |  |  | '1,0,0,1' => EDGE_S_W, | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | # loops (left-right-left etc) | 
| 618 |  |  |  |  |  |  | '0,-1,0,1' => EDGE_N_W_S, | 
| 619 |  |  |  |  |  |  | '0,1,0,-1' => EDGE_S_W_N, | 
| 620 |  |  |  |  |  |  | '1,0,-1,0' => EDGE_E_S_W, | 
| 621 |  |  |  |  |  |  | '-1,0,1,0' => EDGE_W_S_E, | 
| 622 |  |  |  |  |  |  | }; | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | sub _astar_edge_type | 
| 625 |  |  |  |  |  |  | { | 
| 626 |  |  |  |  |  |  | # from three consecutive positions calculate the edge type (VER, HOR, N_W etc) | 
| 627 | 1219 |  |  | 1219 |  | 2082 | my ($x,$y, $x1,$y1, $x2, $y2) = @_; | 
| 628 |  |  |  |  |  |  |  | 
| 629 | 1219 |  |  |  |  | 1777 | my $dx1 = ($x1 - $x) <=> 0; | 
| 630 | 1219 |  |  |  |  | 2048 | my $dy1 = ($y1 - $y) <=> 0; | 
| 631 |  |  |  |  |  |  |  | 
| 632 | 1219 |  |  |  |  | 1871 | my $dx2 = ($x2 - $x1) <=> 0; | 
| 633 | 1219 |  |  |  |  | 1555 | my $dy2 = ($y2 - $y1) <=> 0; | 
| 634 |  |  |  |  |  |  |  | 
| 635 |  |  |  |  |  |  | # in some cases we get (0,-1,0,0), so set the missing parts | 
| 636 | 1219 | 100 | 100 |  |  | 3881 | ($dx2,$dy2) = ($dx1,$dy1) if $dx2 == 0 && $dy2 == 0; | 
| 637 |  |  |  |  |  |  | # can this case happen? | 
| 638 | 1219 | 50 | 66 |  |  | 3837 | ($dx1,$dy1) = ($dx2,$dy2) if $dx1 == 0 && $dy1 == 0; | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | # return correct type depending on differences | 
| 641 | 1219 | 50 |  |  |  | 6892 | $edge_type->{"$dx1,$dy1,$dx2,$dy2"} || EDGE_HOR; | 
| 642 |  |  |  |  |  |  | } | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  | sub _astar_near_nodes | 
| 645 |  |  |  |  |  |  | { | 
| 646 |  |  |  |  |  |  | # return possible next nodes from $nx,$ny | 
| 647 | 3202 |  |  | 3202 |  | 5841 | my ($self, $nx, $ny, $cells, $closed, $min_x, $min_y, $max_x, $max_y) = @_; | 
| 648 |  |  |  |  |  |  |  | 
| 649 | 3202 |  |  |  |  | 5196 | my @places = (); | 
| 650 |  |  |  |  |  |  |  | 
| 651 | 3202 |  |  |  |  | 12085 | my @tries  = (	# ordered E,S,W,N: | 
| 652 |  |  |  |  |  |  | $nx + 1, $ny, 	# right | 
| 653 |  |  |  |  |  |  | $nx, $ny + 1,	# down | 
| 654 |  |  |  |  |  |  | $nx - 1, $ny,	# left | 
| 655 |  |  |  |  |  |  | $nx, $ny - 1,	# up | 
| 656 |  |  |  |  |  |  | ); | 
| 657 |  |  |  |  |  |  |  | 
| 658 |  |  |  |  |  |  | # on crossings, only allow one direction (NS or EW) | 
| 659 | 3202 |  |  |  |  | 6190 | my $type = EDGE_CROSS; | 
| 660 |  |  |  |  |  |  | # including flags, because only flagless edges may be crossed | 
| 661 | 3202 | 100 |  |  |  | 8746 | $type = $cells->{"$nx,$ny"}->{type} if exists $cells->{"$nx,$ny"}; | 
| 662 | 3202 | 100 |  |  |  | 8418 | if ($type == EDGE_HOR) | 
|  |  | 100 |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | { | 
| 664 | 258 |  |  |  |  | 990 | @tries  = ( | 
| 665 |  |  |  |  |  |  | $nx, $ny + 1,	# down | 
| 666 |  |  |  |  |  |  | $nx, $ny - 1,	# up | 
| 667 |  |  |  |  |  |  | ); | 
| 668 |  |  |  |  |  |  | } | 
| 669 |  |  |  |  |  |  | elsif ($type == EDGE_VER) | 
| 670 |  |  |  |  |  |  | { | 
| 671 | 71 |  |  |  |  | 319 | @tries  = ( | 
| 672 |  |  |  |  |  |  | $nx + 1, $ny, 	# right | 
| 673 |  |  |  |  |  |  | $nx - 1, $ny,	# left | 
| 674 |  |  |  |  |  |  | ); | 
| 675 |  |  |  |  |  |  | } | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | # This loop does not check whether the position is already open or not, | 
| 678 |  |  |  |  |  |  | # the caller will later check if the already-open position needs to be | 
| 679 |  |  |  |  |  |  | # replaced by one with a lower cost. | 
| 680 |  |  |  |  |  |  |  | 
| 681 | 3202 |  |  |  |  | 4028 | my $i = 0; | 
| 682 | 3202 |  |  |  |  | 7001 | while ($i < @tries) | 
| 683 |  |  |  |  |  |  | { | 
| 684 | 12150 |  |  |  |  | 20820 | my ($x,$y) = ($tries[$i], $tries[$i+1]); | 
| 685 |  |  |  |  |  |  |  | 
| 686 | 12150 | 50 |  |  |  | 29093 | print STDERR "# $min_x,$min_y => $max_x,$max_y\n" if $self->{debug} > 2; | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | # drop cells outside our working space: | 
| 689 | 12150 | 100 | 100 |  |  | 97736 | next if $x < $min_x || $x > $max_x || $y < $min_y || $y > $max_y; | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 690 |  |  |  |  |  |  |  | 
| 691 | 10839 |  |  |  |  | 21558 | my $p = "$x,$y"; | 
| 692 | 10839 | 50 |  |  |  | 22015 | print STDERR "# examining pos $p\n" if $self->{debug} > 2; | 
| 693 |  |  |  |  |  |  |  | 
| 694 | 10839 | 100 |  |  |  | 24780 | next if exists $closed->{$p}; | 
| 695 |  |  |  |  |  |  |  | 
| 696 | 6957 | 100 | 66 |  |  | 47020 | if (exists $cells->{$p} && ref($cells->{$p}) && $cells->{$p}->isa('Graph::Easy::Edge')) | 
|  |  |  | 100 |  |  |  |  | 
| 697 |  |  |  |  |  |  | { | 
| 698 |  |  |  |  |  |  | # If the existing cell is an VER/HOR edge, then we may cross it | 
| 699 | 956 |  |  |  |  | 2422 | my $type = $cells->{$p}->{type};	# including flags, because only flagless edges | 
| 700 |  |  |  |  |  |  | # may be crossed | 
| 701 |  |  |  |  |  |  |  | 
| 702 | 956 | 100 | 100 |  |  | 4529 | push @places, $x, $y if ($type == EDGE_HOR) || ($type == EDGE_VER); | 
| 703 | 956 |  |  |  |  | 1645 | next; | 
| 704 |  |  |  |  |  |  | } | 
| 705 | 6001 | 100 |  |  |  | 12628 | next if exists $cells->{$p};	# uncrossable cell | 
| 706 |  |  |  |  |  |  |  | 
| 707 | 5249 |  |  |  |  | 11917 | push @places, $x, $y; | 
| 708 |  |  |  |  |  |  |  | 
| 709 | 12150 |  |  |  |  | 30740 | } continue { $i += 2; } | 
| 710 |  |  |  |  |  |  |  | 
| 711 | 3202 |  |  |  |  | 24446 | @places; | 
| 712 |  |  |  |  |  |  | } | 
| 713 |  |  |  |  |  |  |  | 
| 714 |  |  |  |  |  |  | sub _astar_boundaries | 
| 715 |  |  |  |  |  |  | { | 
| 716 |  |  |  |  |  |  | # Calculate boundaries for area that A* should not leave. | 
| 717 | 242 |  |  | 242 |  | 480 | my $self = shift; | 
| 718 |  |  |  |  |  |  |  | 
| 719 | 242 |  |  |  |  | 495 | my $cache = $self->{cache}; | 
| 720 |  |  |  |  |  |  |  | 
| 721 | 242 | 100 |  |  |  | 1788 | return ( $cache->{min_x}-1, $cache->{min_y}-1, | 
| 722 |  |  |  |  |  |  | $cache->{max_x}+1, $cache->{max_y}+1 ) if defined $cache->{min_x}; | 
| 723 |  |  |  |  |  |  |  | 
| 724 | 2 |  |  |  |  | 4 | my ($min_x, $min_y, $max_x, $max_y); | 
| 725 |  |  |  |  |  |  |  | 
| 726 | 2 |  |  |  |  | 18 | my $cells = $self->{cells}; | 
| 727 |  |  |  |  |  |  |  | 
| 728 | 2 |  |  |  |  | 7 | $min_x = 10000000; | 
| 729 | 2 |  |  |  |  | 5 | $min_y = 10000000; | 
| 730 | 2 |  |  |  |  | 2 | $max_x = -10000000; | 
| 731 | 2 |  |  |  |  | 4 | $max_y = -10000000; | 
| 732 |  |  |  |  |  |  |  | 
| 733 | 2 |  |  |  |  | 12 | for my $c (sort keys %$cells) | 
| 734 |  |  |  |  |  |  | { | 
| 735 | 4 |  |  |  |  | 13 | my ($x,$y) = split /,/, $c; | 
| 736 | 4 | 100 |  |  |  | 15 | $min_x = $x if $x < $min_x; | 
| 737 | 4 | 100 |  |  |  | 17 | $min_y = $y if $y < $min_y; | 
| 738 | 4 | 50 |  |  |  | 13 | $max_x = $x if $x > $max_x; | 
| 739 | 4 | 100 |  |  |  | 13 | $max_y = $y if $y > $max_y; | 
| 740 |  |  |  |  |  |  | } | 
| 741 |  |  |  |  |  |  |  | 
| 742 | 2 | 50 |  |  |  | 7 | print STDERR "# A* working space boundaries: $min_x, $min_y, $max_x, $max_y\n" if $self->{debug}; | 
| 743 |  |  |  |  |  |  |  | 
| 744 | 2 |  |  |  |  | 14 | ( $cache->{min_x}, $cache->{min_y}, $cache->{max_x}, $cache->{max_y} ) = | 
| 745 |  |  |  |  |  |  | ($min_x, $min_y, $max_x, $max_y); | 
| 746 |  |  |  |  |  |  |  | 
| 747 |  |  |  |  |  |  | # make the area one bigger in each direction | 
| 748 | 2 |  |  |  |  | 5 | $min_x --; $min_y --; $max_x ++; $max_y ++; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 3 |  | 
| 749 | 2 |  |  |  |  | 6 | ($min_x, $min_y, $max_x, $max_y); | 
| 750 |  |  |  |  |  |  | } | 
| 751 |  |  |  |  |  |  |  | 
| 752 |  |  |  |  |  |  | # on edge pieces, select start fields (left/right of a VER, above/below of a HOR etc) | 
| 753 |  |  |  |  |  |  | # contains also for each starting position the joint-type | 
| 754 |  |  |  |  |  |  | my $next_fields = | 
| 755 |  |  |  |  |  |  | { | 
| 756 |  |  |  |  |  |  | EDGE_VER() => [ -1,0, EDGE_W_N_S, +1,0, EDGE_E_N_S ], | 
| 757 |  |  |  |  |  |  | EDGE_HOR() => [ 0,-1, EDGE_N_E_W, 0,+1, EDGE_S_E_W ], | 
| 758 |  |  |  |  |  |  | EDGE_N_E() => [ 0,+1, EDGE_E_N_S, -1,0, EDGE_N_E_W ],		# |_ | 
| 759 |  |  |  |  |  |  | EDGE_N_W() => [ 0,+1, EDGE_W_N_S, +1,0, EDGE_N_E_W ],		# _| | 
| 760 |  |  |  |  |  |  | EDGE_S_E() => [ 0,-1, EDGE_E_N_S, -1,0, EDGE_S_E_W ], | 
| 761 |  |  |  |  |  |  | EDGE_S_W() => [ 0,-1, EDGE_W_N_S, +1,0, EDGE_S_E_W ], | 
| 762 |  |  |  |  |  |  | }; | 
| 763 |  |  |  |  |  |  |  | 
| 764 |  |  |  |  |  |  | # on edge pieces, select end fields (left/right of a VER, above/below of a HOR etc) | 
| 765 |  |  |  |  |  |  | # contains also for each end position the joint-type | 
| 766 |  |  |  |  |  |  | my $prev_fields = | 
| 767 |  |  |  |  |  |  | { | 
| 768 |  |  |  |  |  |  | EDGE_VER() => [ -1,0, EDGE_W_N_S, +1,0, EDGE_E_N_S ], | 
| 769 |  |  |  |  |  |  | EDGE_HOR() => [ 0,-1, EDGE_N_E_W, 0,+1, EDGE_S_E_W ], | 
| 770 |  |  |  |  |  |  | EDGE_N_E() => [ 0,+1, EDGE_E_N_S, -1,0, EDGE_N_E_W ],		# |_ | 
| 771 |  |  |  |  |  |  | EDGE_N_W() => [ 0,+1, EDGE_W_N_S, +1,0, EDGE_N_E_W ],		# _| | 
| 772 |  |  |  |  |  |  | EDGE_S_E() => [ 0,-1, EDGE_E_N_S, -1,0, EDGE_S_E_W ], | 
| 773 |  |  |  |  |  |  | EDGE_S_W() => [ 0,-1, EDGE_W_N_S, +1,0, EDGE_S_E_W ], | 
| 774 |  |  |  |  |  |  | }; | 
| 775 |  |  |  |  |  |  |  | 
| 776 | 49 |  |  | 49 |  | 458 | use Graph::Easy::Util qw(ord_values); | 
|  | 49 |  |  |  |  | 137 |  | 
|  | 49 |  |  |  |  | 371197 |  | 
| 777 |  |  |  |  |  |  |  | 
| 778 |  |  |  |  |  |  | sub _get_joints | 
| 779 |  |  |  |  |  |  | { | 
| 780 |  |  |  |  |  |  | # from a list of shared, already placed edges, get possible start/end fields | 
| 781 | 25 |  |  | 25 |  | 63 | my ($self, $shared, $mask, $types, $cells, $next_fields) = @_; | 
| 782 |  |  |  |  |  |  |  | 
| 783 |  |  |  |  |  |  | # XXX TODO: do not do this for edges with no free places for joints | 
| 784 |  |  |  |  |  |  |  | 
| 785 |  |  |  |  |  |  | # take each cell from all edges shared, already placed edges as start-point | 
| 786 | 25 |  |  |  |  | 48 | for my $e (@$shared) | 
| 787 |  |  |  |  |  |  | { | 
| 788 | 41 |  |  |  |  | 66 | for my $c (@{$e->{cells}}) | 
|  | 41 |  |  |  |  | 103 |  | 
| 789 |  |  |  |  |  |  | { | 
| 790 | 110 |  |  |  |  | 214 | my $type = $c->{type} & EDGE_TYPE_MASK; | 
| 791 |  |  |  |  |  |  |  | 
| 792 | 110 | 100 |  |  |  | 295 | next unless exists $next_fields->{ $type }; | 
| 793 |  |  |  |  |  |  |  | 
| 794 |  |  |  |  |  |  | # don't consider end/start (depending on $mask) cells | 
| 795 |  |  |  |  |  |  |  | 
| 796 |  |  |  |  |  |  | # do not join EDGE_HOR or EDGE_VER, but join corner pieces | 
| 797 | 94 | 100 | 100 |  |  | 586 | next if ( ($type == EDGE_HOR()) || | 
|  |  |  | 100 |  |  |  |  | 
| 798 |  |  |  |  |  |  | ($type == EDGE_VER()) ) && | 
| 799 |  |  |  |  |  |  | ($c->{type} & $mask); | 
| 800 |  |  |  |  |  |  |  | 
| 801 | 61 |  |  |  |  | 111 | my $fields = $next_fields->{$type}; | 
| 802 |  |  |  |  |  |  |  | 
| 803 | 61 |  |  |  |  | 164 | my ($px,$py) = ($c->{x},$c->{y}); | 
| 804 | 61 |  |  |  |  | 80 | my $i = 0; | 
| 805 | 61 |  |  |  |  | 134 | while ($i < @$fields) | 
| 806 |  |  |  |  |  |  | { | 
| 807 | 122 |  |  |  |  | 285 | my ($sx,$sy, $jt) = ($fields->[$i], $fields->[$i+1], $fields->[$i+2]); | 
| 808 | 122 |  |  |  |  | 220 | $sx += $px; $sy += $py; $i += 3; | 
|  | 122 |  |  |  |  | 192 |  | 
|  | 122 |  |  |  |  | 143 |  | 
| 809 | 122 |  |  |  |  | 220 | my $sxsy = "$sx,$sy"; | 
| 810 |  |  |  |  |  |  | # don't add the field twice | 
| 811 | 122 | 100 |  |  |  | 346 | next if exists $cells->{$sxsy}; | 
| 812 | 116 |  |  |  |  | 485 | $cells->{$sxsy} = [ $sx, $sy, undef, $px, $py ]; | 
| 813 |  |  |  |  |  |  | # keep eventually set start/end points on the original cell | 
| 814 | 116 |  |  |  |  | 487 | $types->{$sxsy} = $jt + ($c->{type} & EDGE_FLAG_MASK); | 
| 815 |  |  |  |  |  |  | } | 
| 816 |  |  |  |  |  |  | } | 
| 817 |  |  |  |  |  |  | } | 
| 818 |  |  |  |  |  |  |  | 
| 819 | 25 |  |  |  |  | 50 | my @R; | 
| 820 |  |  |  |  |  |  | # convert hash to array | 
| 821 | 25 |  |  |  |  | 141 | for my $s (ord_values ( $cells )) | 
| 822 |  |  |  |  |  |  | { | 
| 823 | 116 |  |  |  |  | 416 | push @R, @$s; | 
| 824 |  |  |  |  |  |  | } | 
| 825 | 25 |  |  |  |  | 335 | @R; | 
| 826 |  |  |  |  |  |  | } | 
| 827 |  |  |  |  |  |  |  | 
| 828 |  |  |  |  |  |  | sub _join_edge | 
| 829 |  |  |  |  |  |  | { | 
| 830 |  |  |  |  |  |  | # Find out whether an edge sharing an ending point with the source edge | 
| 831 |  |  |  |  |  |  | # runs alongside the source node, if so, convert it to a joint: | 
| 832 | 31 |  |  | 31 |  | 76 | my ($self, $node, $edge, $shared, $end) = @_; | 
| 833 |  |  |  |  |  |  |  | 
| 834 |  |  |  |  |  |  | # we check the sides B,C,D and E for HOR and VER edge pices: | 
| 835 |  |  |  |  |  |  | #   --D-- | 
| 836 |  |  |  |  |  |  | # | +---+ | | 
| 837 |  |  |  |  |  |  | # E | A | B | 
| 838 |  |  |  |  |  |  | # | +---+ | | 
| 839 |  |  |  |  |  |  | #   --C-- | 
| 840 |  |  |  |  |  |  |  | 
| 841 | 31 |  |  |  |  | 96 | my $flags = | 
| 842 |  |  |  |  |  |  | [ | 
| 843 |  |  |  |  |  |  | EDGE_W_N_S + EDGE_START_W, | 
| 844 |  |  |  |  |  |  | EDGE_N_E_W + EDGE_START_N, | 
| 845 |  |  |  |  |  |  | EDGE_E_N_S + EDGE_START_E, | 
| 846 |  |  |  |  |  |  | EDGE_S_E_W + EDGE_START_S, | 
| 847 |  |  |  |  |  |  | ]; | 
| 848 | 31 | 100 | 100 |  |  | 231 | $flags = | 
| 849 |  |  |  |  |  |  | [ | 
| 850 |  |  |  |  |  |  | EDGE_W_N_S + EDGE_END_W, | 
| 851 |  |  |  |  |  |  | EDGE_N_E_W + EDGE_END_N, | 
| 852 |  |  |  |  |  |  | EDGE_E_N_S + EDGE_END_E, | 
| 853 |  |  |  |  |  |  | EDGE_S_E_W + EDGE_END_S, | 
| 854 |  |  |  |  |  |  | ] if $end || $edge->{bidirectional}; | 
| 855 |  |  |  |  |  |  |  | 
| 856 | 31 |  |  |  |  | 74 | my $cells = $self->{cells}; | 
| 857 | 31 |  |  |  |  | 156 | my @places = $node->_near_places($cells, 1, # distance 1 | 
| 858 |  |  |  |  |  |  | $flags, 'loose'); | 
| 859 |  |  |  |  |  |  |  | 
| 860 | 31 |  |  |  |  | 156 | my $i = 0; | 
| 861 | 31 |  |  |  |  | 171 | while ($i < @places) | 
| 862 |  |  |  |  |  |  | { | 
| 863 | 108 |  |  |  |  | 196 | my ($x,$y) = ($places[$i], $places[$i+1]); $i += 3; | 
|  | 108 |  |  |  |  | 119 |  | 
| 864 |  |  |  |  |  |  |  | 
| 865 | 108 | 100 |  |  |  | 402 | next unless exists $cells->{"$x,$y"};		# empty space? | 
| 866 |  |  |  |  |  |  | # found some cell, check that it is a EDGE_HOR or EDGE_VER | 
| 867 | 13 |  |  |  |  | 43 | my $cell = $cells->{"$x,$y"}; | 
| 868 | 13 | 100 |  |  |  | 98 | next unless $cell->isa('Graph::Easy::Edge::Cell'); | 
| 869 |  |  |  |  |  |  |  | 
| 870 | 8 |  |  |  |  | 20 | my $cell_type = $cell->{type} & EDGE_TYPE_MASK; | 
| 871 |  |  |  |  |  |  |  | 
| 872 | 8 | 50 | 66 |  |  | 37 | next unless $cell_type == EDGE_HOR || $cell_type == EDGE_VER; | 
| 873 |  |  |  |  |  |  |  | 
| 874 |  |  |  |  |  |  | # the cell must belong to one of the shared edges | 
| 875 | 8 |  |  |  |  | 13 | my $e = $cell->{edge}; local $_; | 
|  | 8 |  |  |  |  | 14 |  | 
| 876 | 8 | 100 |  |  |  | 16 | next unless scalar grep { $e == $_ } @$shared; | 
|  | 10 |  |  |  |  | 50 |  | 
| 877 |  |  |  |  |  |  |  | 
| 878 |  |  |  |  |  |  | # make the cell at the current pos a joint | 
| 879 | 6 |  |  |  |  | 90 | $cell->_make_joint($edge,$places[$i-1]); | 
| 880 |  |  |  |  |  |  |  | 
| 881 |  |  |  |  |  |  | # The layouter will check that each edge has a cell, so add a dummy one to | 
| 882 |  |  |  |  |  |  | # $edge to make it happy: | 
| 883 | 6 |  |  |  |  | 33 | Graph::Easy::Edge::Cell->new( type => EDGE_HOLE, edge => $edge, x => $x, y => $y ); | 
| 884 |  |  |  |  |  |  |  | 
| 885 | 6 |  |  |  |  | 36 | return [];					# path is empty | 
| 886 |  |  |  |  |  |  | } | 
| 887 |  |  |  |  |  |  |  | 
| 888 | 25 |  |  |  |  | 91 | undef;		# did not find an edge cell that can be used as joint | 
| 889 |  |  |  |  |  |  | } | 
| 890 |  |  |  |  |  |  |  | 
| 891 |  |  |  |  |  |  | sub _find_path_astar | 
| 892 |  |  |  |  |  |  | { | 
| 893 |  |  |  |  |  |  | # Find a path with the A* algorithm for the given edge (from node A to B) | 
| 894 | 248 |  |  | 248 |  | 498 | my ($self,$edge) = @_; | 
| 895 |  |  |  |  |  |  |  | 
| 896 | 248 |  |  |  |  | 496 | my $cells = $self->{cells}; | 
| 897 | 248 |  |  |  |  | 571 | my $src = $edge->{from}; | 
| 898 | 248 |  |  |  |  | 529 | my $dst = $edge->{to}; | 
| 899 |  |  |  |  |  |  |  | 
| 900 | 248 | 50 |  |  |  | 659 | print STDERR "# A* from $src->{x},$src->{y} to $dst->{x},$dst->{y}\n" if $self->{debug}; | 
| 901 |  |  |  |  |  |  |  | 
| 902 | 248 |  |  |  |  | 1062 | my $start_flags = [ | 
| 903 |  |  |  |  |  |  | EDGE_START_W, | 
| 904 |  |  |  |  |  |  | EDGE_START_N, | 
| 905 |  |  |  |  |  |  | EDGE_START_E, | 
| 906 |  |  |  |  |  |  | EDGE_START_S, | 
| 907 |  |  |  |  |  |  | ]; | 
| 908 |  |  |  |  |  |  |  | 
| 909 | 248 |  |  |  |  | 1555 | my $end_flags = [ | 
| 910 |  |  |  |  |  |  | EDGE_END_W, | 
| 911 |  |  |  |  |  |  | EDGE_END_N, | 
| 912 |  |  |  |  |  |  | EDGE_END_E, | 
| 913 |  |  |  |  |  |  | EDGE_END_S, | 
| 914 |  |  |  |  |  |  | ]; | 
| 915 |  |  |  |  |  |  |  | 
| 916 |  |  |  |  |  |  | # if the target/source node is of shape "edge", remove the endpoint | 
| 917 | 248 | 50 |  |  |  | 1137 | if ( ($edge->{to}->attribute('shape')) eq 'edge') | 
| 918 |  |  |  |  |  |  | { | 
| 919 | 0 |  |  |  |  | 0 | $end_flags = [ 0,0,0,0 ]; | 
| 920 |  |  |  |  |  |  | } | 
| 921 | 248 | 50 |  |  |  | 1003 | if ( ($edge->{from}->attribute('shape')) eq 'edge') | 
| 922 |  |  |  |  |  |  | { | 
| 923 | 0 |  |  |  |  | 0 | $start_flags = [ 0,0,0,0 ]; | 
| 924 |  |  |  |  |  |  | } | 
| 925 |  |  |  |  |  |  |  | 
| 926 | 248 |  |  |  |  | 1167 | my ($s_p,@ss_p) = $edge->port('start'); | 
| 927 | 248 |  |  |  |  | 933 | my ($e_p,@ee_p) = $edge->port('end'); | 
| 928 | 248 |  |  |  |  | 443 | my (@A, @B);					# Start/Stop positions | 
| 929 | 0 |  |  |  |  | 0 | my @shared_start; | 
| 930 | 0 |  |  |  |  | 0 | my @shared_end; | 
| 931 |  |  |  |  |  |  |  | 
| 932 | 248 |  |  |  |  | 517 | my $joint_type = {}; | 
| 933 | 248 |  |  |  |  | 445 | my $joint_type_end = {}; | 
| 934 |  |  |  |  |  |  |  | 
| 935 | 248 |  |  |  |  | 562 | my $start_cells = {}; | 
| 936 | 248 |  |  |  |  | 484 | my $end_cells = {}; | 
| 937 |  |  |  |  |  |  |  | 
| 938 |  |  |  |  |  |  | ########################################################################### | 
| 939 |  |  |  |  |  |  | # end fields first (because maybe an edge runs alongside the node) | 
| 940 |  |  |  |  |  |  |  | 
| 941 |  |  |  |  |  |  | # has a end point restriction | 
| 942 | 248 | 100 | 100 |  |  | 1095 | @shared_end = $edge->{to}->edges_at_port('end', $e_p, $ee_p[0]) if defined $e_p && @ee_p == 1; | 
| 943 |  |  |  |  |  |  |  | 
| 944 | 248 |  |  |  |  | 457 | my @shared = (); | 
| 945 |  |  |  |  |  |  | # filter out all non-placed edges (this will also filter out $edge) | 
| 946 | 248 |  |  |  |  | 562 | for my $s (@shared_end) | 
| 947 |  |  |  |  |  |  | { | 
| 948 | 84 | 100 |  |  |  | 97 | push @shared, $s if @{$s->{cells}} > 0; | 
|  | 84 |  |  |  |  | 311 |  | 
| 949 |  |  |  |  |  |  | } | 
| 950 |  |  |  |  |  |  |  | 
| 951 | 248 |  |  |  |  | 517 | my $per_field = 5;			# for shared: x,y,undef, px,py | 
| 952 | 248 | 100 |  |  |  | 653 | if (@shared > 0) | 
| 953 |  |  |  |  |  |  | { | 
| 954 |  |  |  |  |  |  | # more than one edge share the same end port, and one of the others was | 
| 955 |  |  |  |  |  |  | # already placed | 
| 956 |  |  |  |  |  |  |  | 
| 957 | 18 | 50 |  |  |  | 61 | print STDERR "#  edge from '$edge->{from}->{name}' to '$edge->{to}->{name}' shares end port with ", | 
| 958 |  |  |  |  |  |  | scalar @shared, " other edge(s)\n" if $self->{debug}; | 
| 959 |  |  |  |  |  |  |  | 
| 960 |  |  |  |  |  |  | # if there is one of the already-placed edges running alongside the src | 
| 961 |  |  |  |  |  |  | # node, we can just convert the field to a joint and be done | 
| 962 | 18 |  |  |  |  | 91 | my $path = $self->_join_edge($src,$edge,\@shared); | 
| 963 | 18 | 100 |  |  |  | 98 | return $path if $path;				# already done? | 
| 964 |  |  |  |  |  |  |  | 
| 965 | 12 |  |  |  |  | 64 | @B = $self->_get_joints(\@shared, EDGE_START_MASK, $joint_type_end, $end_cells, $prev_fields); | 
| 966 |  |  |  |  |  |  | } | 
| 967 |  |  |  |  |  |  | else | 
| 968 |  |  |  |  |  |  | { | 
| 969 |  |  |  |  |  |  | # potential stop positions | 
| 970 | 230 |  |  |  |  | 1227 | @B = $dst->_near_places($cells, 1, $end_flags, 1);	# distance = 1: slots | 
| 971 |  |  |  |  |  |  |  | 
| 972 |  |  |  |  |  |  | # the edge has a port description, limiting the end places | 
| 973 | 230 | 100 |  |  |  | 863 | @B = $dst->_allowed_places( \@B, $dst->_allow( $e_p, @ee_p ), 3) | 
| 974 |  |  |  |  |  |  | if defined $e_p; | 
| 975 |  |  |  |  |  |  |  | 
| 976 | 230 |  |  |  |  | 414 | $per_field = 3;			# x,y,type | 
| 977 |  |  |  |  |  |  | } | 
| 978 |  |  |  |  |  |  |  | 
| 979 | 242 | 50 |  |  |  | 622 | return unless scalar @B > 0;			# no free slots on target node? | 
| 980 |  |  |  |  |  |  |  | 
| 981 |  |  |  |  |  |  | ########################################################################### | 
| 982 |  |  |  |  |  |  | # start fields | 
| 983 |  |  |  |  |  |  |  | 
| 984 |  |  |  |  |  |  | # has a starting point restriction: | 
| 985 | 242 | 100 | 100 |  |  | 1077 | @shared_start = $edge->{from}->edges_at_port('start', $s_p, $ss_p[0]) if defined $s_p && @ss_p == 1; | 
| 986 |  |  |  |  |  |  |  | 
| 987 | 242 |  |  |  |  | 519 | @shared = (); | 
| 988 |  |  |  |  |  |  | # filter out all non-placed edges (this will also filter out $edge) | 
| 989 | 242 |  |  |  |  | 639 | for my $s (@shared_start) | 
| 990 |  |  |  |  |  |  | { | 
| 991 | 62 | 100 |  |  |  | 76 | push @shared, $s if @{$s->{cells}} > 0; | 
|  | 62 |  |  |  |  | 215 |  | 
| 992 |  |  |  |  |  |  | } | 
| 993 |  |  |  |  |  |  |  | 
| 994 | 242 | 100 |  |  |  | 629 | if (@shared > 0) | 
| 995 |  |  |  |  |  |  | { | 
| 996 |  |  |  |  |  |  | # More than one edge share the same start port, and one of the others was | 
| 997 |  |  |  |  |  |  | # already placed, so we just run along until we catch it up with a joint: | 
| 998 |  |  |  |  |  |  |  | 
| 999 | 13 | 50 |  |  |  | 45 | print STDERR "#  edge from '$edge->{from}->{name}' to '$edge->{to}->{name}' shares start port with ", | 
| 1000 |  |  |  |  |  |  | scalar @shared, " other edge(s)\n" if $self->{debug}; | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 |  |  |  |  |  |  | # if there is one of the already-placed edges running alongside the src | 
| 1003 |  |  |  |  |  |  | # node, we can just convert the field to a joint and be done | 
| 1004 | 13 |  |  |  |  | 71 | my $path = $self->_join_edge($dst, $edge, \@shared, 'end'); | 
| 1005 | 13 | 50 |  |  |  | 40 | return $path if $path;				# already done? | 
| 1006 |  |  |  |  |  |  |  | 
| 1007 | 13 |  |  |  |  | 73 | @A = $self->_get_joints(\@shared, EDGE_END_MASK, $joint_type, $start_cells, $next_fields); | 
| 1008 |  |  |  |  |  |  | } | 
| 1009 |  |  |  |  |  |  | else | 
| 1010 |  |  |  |  |  |  | { | 
| 1011 |  |  |  |  |  |  | # from SRC to DST | 
| 1012 |  |  |  |  |  |  |  | 
| 1013 |  |  |  |  |  |  | # get all the starting positions | 
| 1014 |  |  |  |  |  |  | # distance = 1: slots, generate starting types, the direction is shifted | 
| 1015 |  |  |  |  |  |  | # by 90° counter-clockwise | 
| 1016 |  |  |  |  |  |  |  | 
| 1017 | 229 | 100 |  |  |  | 390 | my $s = $start_flags; $s = $end_flags if $edge->{bidirectional}; | 
|  | 229 |  |  |  |  | 692 |  | 
| 1018 | 229 |  |  |  |  | 1217 | my @start = $src->_near_places($cells, 1, $s, 1, $src->_shift(-90) ); | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | # the edge has a port description, limiting the start places | 
| 1021 | 229 | 100 |  |  |  | 1059 | @start = $src->_allowed_places( \@start, $src->_allow( $s_p, @ss_p ), 3) | 
| 1022 |  |  |  |  |  |  | if defined $s_p; | 
| 1023 |  |  |  |  |  |  |  | 
| 1024 | 229 | 50 |  |  |  | 862 | return unless @start > 0;			# no free slots on start node? | 
| 1025 |  |  |  |  |  |  |  | 
| 1026 | 229 |  |  |  |  | 379 | my $i = 0; | 
| 1027 | 229 |  |  |  |  | 645 | while ($i < scalar @start) | 
| 1028 |  |  |  |  |  |  | { | 
| 1029 | 1112 |  |  |  |  | 1511 | my $sx = $start[$i]; my $sy = $start[$i+1]; my $type = $start[$i+2]; $i += 3; | 
|  | 1112 |  |  |  |  | 1462 |  | 
|  | 1112 |  |  |  |  | 1506 |  | 
|  | 1112 |  |  |  |  | 1186 |  | 
| 1030 |  |  |  |  |  |  |  | 
| 1031 |  |  |  |  |  |  | # compute the field inside the node from where $sx,$sy is reached: | 
| 1032 | 1112 |  |  |  |  | 1202 | my $px = $sx; my $py = $sy; | 
|  | 1112 |  |  |  |  | 1180 |  | 
| 1033 | 1112 | 100 | 100 |  |  | 5440 | if ($sy < $src->{y} || $sy >= $src->{y} + $src->{cy}) | 
| 1034 |  |  |  |  |  |  | { | 
| 1035 | 602 | 100 |  |  |  | 1373 | $py = $sy + 1 if $sy < $src->{y};		# above | 
| 1036 | 602 | 100 |  |  |  | 1369 | $py = $sy - 1 if $sy > $src->{y};		# below | 
| 1037 |  |  |  |  |  |  | } | 
| 1038 |  |  |  |  |  |  | else | 
| 1039 |  |  |  |  |  |  | { | 
| 1040 | 510 | 100 |  |  |  | 1159 | $px = $sx + 1 if $sx < $src->{x};		# right | 
| 1041 | 510 | 100 |  |  |  | 1374 | $px = $sx - 1 if $sx > $src->{x};		# left | 
| 1042 |  |  |  |  |  |  | } | 
| 1043 |  |  |  |  |  |  |  | 
| 1044 | 1112 |  |  |  |  | 5030 | push @A, ($sx, $sy, $type, $px, $py); | 
| 1045 |  |  |  |  |  |  | } | 
| 1046 |  |  |  |  |  |  | } | 
| 1047 |  |  |  |  |  |  |  | 
| 1048 |  |  |  |  |  |  | ########################################################################### | 
| 1049 |  |  |  |  |  |  | # use A* to finally find the path: | 
| 1050 |  |  |  |  |  |  |  | 
| 1051 | 242 |  |  |  |  | 1405 | my $path = $self->_astar(\@A,\@B,$edge, $per_field); | 
| 1052 |  |  |  |  |  |  |  | 
| 1053 | 242 | 100 | 100 |  |  | 1946 | if (@$path > 0 && keys %$start_cells > 0) | 
| 1054 |  |  |  |  |  |  | { | 
| 1055 |  |  |  |  |  |  | # convert the edge piece of the starting edge-cell to a joint | 
| 1056 | 13 |  |  |  |  | 51 | my ($x, $y) = ($path->[0],$path->[1]); | 
| 1057 | 13 |  |  |  |  | 35 | my $xy = "$x,$y"; | 
| 1058 | 13 |  |  |  |  | 30 | my ($sx,$sy,$t,$px,$py) = @{$start_cells->{$xy}}; | 
|  | 13 |  |  |  |  | 72 |  | 
| 1059 |  |  |  |  |  |  |  | 
| 1060 | 13 |  |  |  |  | 39 | my $jt = $joint_type->{"$sx,$sy"}; | 
| 1061 | 13 |  |  |  |  | 101 | $cells->{"$px,$py"}->_make_joint($edge,$jt); | 
| 1062 |  |  |  |  |  |  | } | 
| 1063 |  |  |  |  |  |  |  | 
| 1064 | 242 | 100 | 100 |  |  | 1775 | if (@$path > 0 && keys %$end_cells > 0) | 
| 1065 |  |  |  |  |  |  | { | 
| 1066 |  |  |  |  |  |  | # convert the edge piece of the starting edge-cell to a joint | 
| 1067 | 12 |  |  |  |  | 44 | my ($x, $y) = ($path->[-3],$path->[-2]); | 
| 1068 | 12 |  |  |  |  | 35 | my $xy = "$x,$y"; | 
| 1069 | 12 |  |  |  |  | 21 | my ($sx,$sy,$t,$px,$py) = @{$end_cells->{$xy}}; | 
|  | 12 |  |  |  |  | 45 |  | 
| 1070 |  |  |  |  |  |  |  | 
| 1071 | 12 |  |  |  |  | 38 | my $jt = $joint_type_end->{"$sx,$sy"}; | 
| 1072 | 12 |  |  |  |  | 98 | $cells->{"$px,$py"}->_make_joint($edge,$jt); | 
| 1073 |  |  |  |  |  |  | } | 
| 1074 |  |  |  |  |  |  |  | 
| 1075 | 242 |  |  |  |  | 4081 | $path; | 
| 1076 |  |  |  |  |  |  | } | 
| 1077 |  |  |  |  |  |  |  | 
| 1078 |  |  |  |  |  |  | sub _astar | 
| 1079 |  |  |  |  |  |  | { | 
| 1080 |  |  |  |  |  |  | # The core A* algorithm, finds a path from a given list of start | 
| 1081 |  |  |  |  |  |  | # positions @A to and of the given stop positions @B. | 
| 1082 | 242 |  |  | 242 |  | 484 | my ($self, $A, $B, $edge, $per_field) = @_; | 
| 1083 |  |  |  |  |  |  |  | 
| 1084 | 242 |  |  |  |  | 1510 | my @start = @$A; | 
| 1085 | 242 |  |  |  |  | 1567 | my @stop = @$B; | 
| 1086 | 242 |  |  |  |  | 409 | my $stop = scalar @stop; | 
| 1087 |  |  |  |  |  |  |  | 
| 1088 | 242 |  |  |  |  | 518 | my $src = $edge->{from}; | 
| 1089 | 242 |  |  |  |  | 427 | my $dst = $edge->{to}; | 
| 1090 | 242 |  |  |  |  | 530 | my $cells = $self->{cells}; | 
| 1091 |  |  |  |  |  |  |  | 
| 1092 | 242 |  |  |  |  | 1541 | my $open = Graph::Easy::Heap->new();	# to find smallest elem fast | 
| 1093 | 242 |  |  |  |  | 882 | my $open_by_pos = {};			# to find open nodes by pos | 
| 1094 | 242 |  |  |  |  | 545 | my $closed = {};			# to find closed nodes by pos | 
| 1095 |  |  |  |  |  |  |  | 
| 1096 | 242 |  |  |  |  | 424 | my $elem; | 
| 1097 |  |  |  |  |  |  |  | 
| 1098 |  |  |  |  |  |  | # The boundaries of objects in $cell, e.g. the area that the algorithm shall | 
| 1099 |  |  |  |  |  |  | # never leave. | 
| 1100 | 242 |  |  |  |  | 945 | my ($min_x, $min_y, $max_x, $max_y) = $self->_astar_boundaries(); | 
| 1101 |  |  |  |  |  |  |  | 
| 1102 |  |  |  |  |  |  | # Max. steps to prevent endless searching in case of bugs like endless loops. | 
| 1103 | 242 |  |  |  |  | 530 | my $tries = 0; my $max_tries = 2000000; | 
|  | 242 |  |  |  |  | 424 |  | 
| 1104 |  |  |  |  |  |  |  | 
| 1105 |  |  |  |  |  |  | # count how many times we did A* | 
| 1106 | 242 |  |  |  |  | 807 | $self->{stats}->{astar}++; | 
| 1107 |  |  |  |  |  |  |  | 
| 1108 |  |  |  |  |  |  | ########################################################################### | 
| 1109 |  |  |  |  |  |  | ########################################################################### | 
| 1110 |  |  |  |  |  |  | # put the start positions into OPEN | 
| 1111 |  |  |  |  |  |  |  | 
| 1112 | 242 |  |  |  |  | 467 | my $i = 0; my $bias = 0; | 
|  | 242 |  |  |  |  | 470 |  | 
| 1113 | 242 |  |  |  |  | 801 | while ($i < scalar @start) | 
| 1114 |  |  |  |  |  |  | { | 
| 1115 | 1172 |  |  |  |  | 3226 | my ($sx,$sy,$type,$px,$py) = | 
| 1116 |  |  |  |  |  |  | ($start[$i],$start[$i+1],$start[$i+2],$start[$i+3],$start[$i+4]); | 
| 1117 | 1172 |  |  |  |  | 1423 | $i += 5; | 
| 1118 |  |  |  |  |  |  |  | 
| 1119 | 1172 |  |  |  |  | 2353 | my $cell = $cells->{"$sx,$sy"}; my $rcell = ref($cell); | 
|  | 1172 |  |  |  |  | 1699 |  | 
| 1120 | 1172 | 100 | 100 |  |  | 4232 | next if $rcell && $rcell !~ /::Edge/; | 
| 1121 |  |  |  |  |  |  |  | 
| 1122 | 1161 | 100 |  |  |  | 1656 | my $t = 0; $t = $cell->{type} & EDGE_NO_M_MASK if $rcell =~ /::Edge/; | 
|  | 1161 |  |  |  |  | 3227 |  | 
| 1123 | 1161 | 100 | 100 |  |  | 4524 | next if $t != 0 && $t != EDGE_HOR && $t != EDGE_VER; | 
|  |  |  | 100 |  |  |  |  | 
| 1124 |  |  |  |  |  |  |  | 
| 1125 |  |  |  |  |  |  | # For each start point, calculate the distance to each stop point, then use | 
| 1126 |  |  |  |  |  |  | # the smallest as value: | 
| 1127 | 895 |  |  |  |  | 1106 | my $lowest_x = $stop[0]; my $lowest_y = $stop[1]; | 
|  | 895 |  |  |  |  | 1315 |  | 
| 1128 | 895 |  |  |  |  | 2204 | my $lowest = _astar_distance($sx,$sy, $stop[0], $stop[1]); | 
| 1129 | 895 |  |  |  |  | 2465 | for (my $u = $per_field; $u < $stop; $u += $per_field) | 
| 1130 |  |  |  |  |  |  | { | 
| 1131 | 3218 |  |  |  |  | 6791 | my $dist = _astar_distance($sx,$sy, $stop[$u], $stop[$u+1]); | 
| 1132 | 3218 | 100 |  |  |  | 8480 | ($lowest_x, $lowest_y) = ($stop[$u],$stop[$u+1]) if $dist < $lowest; | 
| 1133 | 3218 | 100 |  |  |  | 10381 | $lowest = $dist if $dist < $lowest; | 
| 1134 |  |  |  |  |  |  | } | 
| 1135 |  |  |  |  |  |  |  | 
| 1136 |  |  |  |  |  |  |  | 
| 1137 |  |  |  |  |  |  | # add a penalty for crossings | 
| 1138 | 895 | 100 |  |  |  | 1317 | my $malus = 0; $malus = 30 if $t != 0; | 
|  | 895 |  |  |  |  | 1823 |  | 
| 1139 | 895 |  |  |  |  | 2021 | $malus += _astar_modifier($px,$py, $sx, $sy, $sx, $sy); | 
| 1140 | 895 |  |  |  |  | 4903 | $open->add( [ $lowest, $sx, $sy, $px, $py, $type, 1 ] ); | 
| 1141 |  |  |  |  |  |  |  | 
| 1142 | 895 |  |  |  |  | 2263 | my $o = $malus + $bias + $lowest; | 
| 1143 | 895 | 50 |  |  |  | 2331 | print STDERR "#   adding open pos $sx,$sy ($o = $malus + $bias + $lowest) at ($lowest_x,$lowest_y)\n" | 
| 1144 |  |  |  |  |  |  | if $self->{debug} > 1; | 
| 1145 |  |  |  |  |  |  |  | 
| 1146 |  |  |  |  |  |  | # The cost to reach the starting node is obviously 0. That means that there is | 
| 1147 |  |  |  |  |  |  | # a tie between going down/up if both possibilities are equal likely. We insert | 
| 1148 |  |  |  |  |  |  | # a small bias here that makes the prefered order east/south/west/north. Instead | 
| 1149 |  |  |  |  |  |  | # the algorithmn exploring both way and terminating arbitrarily on the one that | 
| 1150 |  |  |  |  |  |  | # first hits the target, it will explore only one. | 
| 1151 | 895 |  |  |  |  | 2674 | $open_by_pos->{"$sx,$sy"} = $o; | 
| 1152 |  |  |  |  |  |  |  | 
| 1153 | 895 |  | 50 |  |  | 3854 | $bias += $self->{_astar_bias} || 0; | 
| 1154 |  |  |  |  |  |  | } | 
| 1155 |  |  |  |  |  |  |  | 
| 1156 |  |  |  |  |  |  | ########################################################################### | 
| 1157 |  |  |  |  |  |  | ########################################################################### | 
| 1158 |  |  |  |  |  |  | # main A* loop | 
| 1159 |  |  |  |  |  |  |  | 
| 1160 | 242 |  |  |  |  | 673 | my $stats = $self->{stats}; | 
| 1161 |  |  |  |  |  |  |  | 
| 1162 |  |  |  |  |  |  | STEP: | 
| 1163 | 242 |  |  |  |  | 842 | while( defined( $elem = $open->extract_top() ) ) | 
| 1164 |  |  |  |  |  |  | { | 
| 1165 | 3442 | 50 |  |  |  | 11343 | $stats->{astar_steps}++ if $self->{debug}; | 
| 1166 |  |  |  |  |  |  |  | 
| 1167 |  |  |  |  |  |  | # hard limit on number of steps todo | 
| 1168 | 3442 | 50 |  |  |  | 7637 | if ($tries++ > $max_tries) | 
| 1169 |  |  |  |  |  |  | { | 
| 1170 | 0 |  |  |  |  | 0 | $self->warn("A* reached maximum number of tries ($max_tries), giving up."); | 
| 1171 | 0 |  |  |  |  | 0 | return []; | 
| 1172 |  |  |  |  |  |  | } | 
| 1173 |  |  |  |  |  |  |  | 
| 1174 | 3442 | 50 |  |  |  | 7036 | print STDERR "#  Smallest elem from ", $open->elements(), | 
| 1175 |  |  |  |  |  |  | " elems is: weight=", $elem->[0], " at $elem->[1],$elem->[2]\n" if $self->{debug} > 1; | 
| 1176 | 3442 |  |  |  |  | 7591 | my ($val, $x,$y, $px,$py, $type, $do_stop) = @$elem; | 
| 1177 |  |  |  |  |  |  |  | 
| 1178 | 3442 |  |  |  |  | 6146 | my $key = "$x,$y"; | 
| 1179 |  |  |  |  |  |  | # move node into CLOSE and remove from OPEN | 
| 1180 | 3442 |  | 50 |  |  | 8702 | my $g = $open_by_pos->{$key} || 0; | 
| 1181 | 3442 |  |  |  |  | 13123 | $closed->{$key} = [ $px, $py, $val - $g, $g, $type, $do_stop ]; | 
| 1182 | 3442 |  |  |  |  | 6372 | delete $open_by_pos->{$key}; | 
| 1183 |  |  |  |  |  |  |  | 
| 1184 |  |  |  |  |  |  | # we are done when we hit one of the potential stop positions | 
| 1185 | 3442 |  |  |  |  | 9678 | for (my $i = 0; $i < $stop; $i += $per_field) | 
| 1186 |  |  |  |  |  |  | { | 
| 1187 |  |  |  |  |  |  | # reached one stop position? | 
| 1188 | 13830 | 100 | 100 |  |  | 49377 | if ($x == $stop[$i] && $y == $stop[$i+1]) | 
| 1189 |  |  |  |  |  |  | { | 
| 1190 | 240 | 100 |  |  |  | 1746 | $closed->{$key}->[4] += $stop[$i+2] if defined $stop[$i+2]; | 
| 1191 |  |  |  |  |  |  | # store the reached stop position if it is known | 
| 1192 | 240 | 100 |  |  |  | 962 | if ($per_field > 3) | 
|  |  | 50 |  |  |  |  |  | 
| 1193 |  |  |  |  |  |  | { | 
| 1194 | 12 |  |  |  |  | 52 | $closed->{$key}->[6] = $stop[$i+3]; | 
| 1195 | 12 |  |  |  |  | 32 | $closed->{$key}->[7] = $stop[$i+4]; | 
| 1196 | 12 | 50 |  |  |  | 42 | print STDERR "#  Reached stop position $x,$y (lx,ly $stop[$i+3], $stop[$i+4])\n" if $self->{debug} > 1; | 
| 1197 |  |  |  |  |  |  | } | 
| 1198 |  |  |  |  |  |  | elsif ($self->{debug} > 1) { | 
| 1199 | 0 |  |  |  |  | 0 | print STDERR "#  Reached stop position $x,$y\n"; | 
| 1200 |  |  |  |  |  |  | } | 
| 1201 | 240 |  |  |  |  | 938 | last STEP; | 
| 1202 |  |  |  |  |  |  | } | 
| 1203 |  |  |  |  |  |  | } # end test for stop postion(s) | 
| 1204 |  |  |  |  |  |  |  | 
| 1205 | 3202 | 50 | 33 |  |  | 14256 | $self->_croak("On of '$x,$y' is not defined") | 
| 1206 |  |  |  |  |  |  | unless defined $x && defined $y; | 
| 1207 |  |  |  |  |  |  |  | 
| 1208 |  |  |  |  |  |  | # get list of potential positions we need to explore from the current one | 
| 1209 | 3202 |  |  |  |  | 8760 | my @p = $self->_astar_near_nodes($x,$y, $cells, $closed, $min_x, $min_y, $max_x, $max_y); | 
| 1210 |  |  |  |  |  |  |  | 
| 1211 | 3202 |  |  |  |  | 4643 | my $n = 0; | 
| 1212 | 3202 |  |  |  |  | 7117 | while ($n < scalar @p) | 
| 1213 |  |  |  |  |  |  | { | 
| 1214 | 5666 |  |  |  |  | 7804 | my $nx = $p[$n]; my $ny = $p[$n+1]; $n += 2; | 
|  | 5666 |  |  |  |  | 7551 |  | 
|  | 5666 |  |  |  |  | 6234 |  | 
| 1215 |  |  |  |  |  |  |  | 
| 1216 | 5666 | 50 | 33 |  |  | 18869 | if (!defined $nx || !defined $ny) | 
| 1217 |  |  |  |  |  |  | { | 
| 1218 | 0 |  |  |  |  | 0 | require Carp; | 
| 1219 | 0 |  |  |  |  | 0 | Carp::confess("On of '$nx,$ny' is not defined"); | 
| 1220 |  |  |  |  |  |  | } | 
| 1221 | 5666 |  |  |  |  | 7275 | my $lg = $g; | 
| 1222 | 5666 | 50 | 33 |  |  | 28044 | $lg += _astar_modifier($px,$py,$x,$y,$nx,$ny,$cells) if defined $px && defined $py; | 
| 1223 |  |  |  |  |  |  |  | 
| 1224 | 5666 |  |  |  |  | 9526 | my $n = "$nx,$ny"; | 
| 1225 |  |  |  |  |  |  |  | 
| 1226 |  |  |  |  |  |  | # was already open? | 
| 1227 | 5666 | 100 |  |  |  | 17021 | next if (exists $open_by_pos->{$n}); | 
| 1228 |  |  |  |  |  |  |  | 
| 1229 |  |  |  |  |  |  | #      print STDERR "#   Already open pos $nx,$ny with $open_by_pos->{$n} (would be $lg)\n" | 
| 1230 |  |  |  |  |  |  | #	 if $self->{debug} && exists $open_by_pos->{$n}; | 
| 1231 |  |  |  |  |  |  | # | 
| 1232 |  |  |  |  |  |  | #      next if exists $open_by_pos->{$n} && $open_by_pos->{$n} <= $lg; | 
| 1233 |  |  |  |  |  |  | # | 
| 1234 |  |  |  |  |  |  | #      if (exists $open_by_pos->{$n}) | 
| 1235 |  |  |  |  |  |  | #        { | 
| 1236 |  |  |  |  |  |  | #        $open->delete($nx, $ny); | 
| 1237 |  |  |  |  |  |  | #        } | 
| 1238 |  |  |  |  |  |  |  | 
| 1239 |  |  |  |  |  |  | # calculate distance to each possible stop position, and | 
| 1240 |  |  |  |  |  |  | # use the lowest one | 
| 1241 | 4100 |  |  |  |  | 8981 | my $lowest_distance = _astar_distance($nx, $ny, $stop[0], $stop[1]); | 
| 1242 | 4100 |  |  |  |  | 9239 | for (my $i = $per_field; $i < $stop; $i += $per_field) | 
| 1243 |  |  |  |  |  |  | { | 
| 1244 | 12644 |  |  |  |  | 25154 | my $d = _astar_distance($nx, $ny, $stop[$i], $stop[$i+1]); | 
| 1245 | 12644 | 100 |  |  |  | 40399 | $lowest_distance = $d if $d < $lowest_distance; | 
| 1246 |  |  |  |  |  |  | } | 
| 1247 |  |  |  |  |  |  |  | 
| 1248 | 4100 | 50 |  |  |  | 10357 | print STDERR "#   Opening pos $nx,$ny ($lowest_distance + $lg)\n" if $self->{debug} > 1; | 
| 1249 |  |  |  |  |  |  |  | 
| 1250 |  |  |  |  |  |  | # open new position into OPEN | 
| 1251 | 4100 |  |  |  |  | 21068 | $open->add( [ $lowest_distance + $lg, $nx, $ny, $x, $y, undef ] ); | 
| 1252 | 4100 |  |  |  |  | 22644 | $open_by_pos->{$n} = $lg; | 
| 1253 |  |  |  |  |  |  | } | 
| 1254 |  |  |  |  |  |  | } | 
| 1255 |  |  |  |  |  |  |  | 
| 1256 |  |  |  |  |  |  | ########################################################################### | 
| 1257 |  |  |  |  |  |  | # A* is done, now build a path from the information we computed above: | 
| 1258 |  |  |  |  |  |  |  | 
| 1259 |  |  |  |  |  |  | # count how many steps we did in A* | 
| 1260 | 242 |  |  |  |  | 682 | $self->{stats}->{astar_steps} += $tries; | 
| 1261 |  |  |  |  |  |  |  | 
| 1262 |  |  |  |  |  |  | # no more nodes to follow, so we couldn't find a path | 
| 1263 | 242 | 100 |  |  |  | 702 | if (!defined $elem) | 
| 1264 |  |  |  |  |  |  | { | 
| 1265 | 2 | 50 |  |  |  | 11 | print STDERR "# A* couldn't find a path after $max_tries steps.\n" if $self->{debug}; | 
| 1266 | 2 |  |  |  |  | 265 | return []; | 
| 1267 |  |  |  |  |  |  | } | 
| 1268 |  |  |  |  |  |  |  | 
| 1269 | 240 |  |  |  |  | 529 | my $path = []; | 
| 1270 | 240 |  |  |  |  | 768 | my ($cx,$cy) = ($elem->[1],$elem->[2]); | 
| 1271 |  |  |  |  |  |  | # the "last" cell in the path. Since we follow it backwards, it | 
| 1272 |  |  |  |  |  |  | # becomes actually the next cell | 
| 1273 | 240 |  |  |  |  | 583 | my ($lx,$ly); | 
| 1274 | 0 |  |  |  |  | 0 | my $type; | 
| 1275 |  |  |  |  |  |  |  | 
| 1276 | 240 |  |  |  |  | 310 | my $label_cell = 0;		# found a cell to attach the label to? | 
| 1277 |  |  |  |  |  |  |  | 
| 1278 | 240 |  |  |  |  | 336 | my @bends;			# record all bends in the path to straighten it out | 
| 1279 |  |  |  |  |  |  |  | 
| 1280 | 240 |  |  |  |  | 391 | my $idx = 0; | 
| 1281 |  |  |  |  |  |  | # follow $elem back to the source to find the path | 
| 1282 | 240 |  |  |  |  | 667 | while (defined $cx) | 
| 1283 |  |  |  |  |  |  | { | 
| 1284 | 1155 | 50 |  |  |  | 2930 | last unless exists $closed->{"$cx,$cy"}; | 
| 1285 | 1155 |  |  |  |  | 1688 | my $xy = "$cx,$cy"; | 
| 1286 |  |  |  |  |  |  |  | 
| 1287 | 1155 |  |  |  |  | 2007 | $type = $closed->{$xy}->[ 4 ]; | 
| 1288 |  |  |  |  |  |  |  | 
| 1289 | 1155 |  |  |  |  | 1277 | my ($px,$py) = @{ $closed->{$xy} };		# get X,Y of parent cell | 
|  | 1155 |  |  |  |  | 2518 |  | 
| 1290 |  |  |  |  |  |  |  | 
| 1291 | 1155 |  | 100 |  |  | 3860 | my $edge_type = ($type||0) & EDGE_TYPE_MASK; | 
| 1292 | 1155 | 50 |  |  |  | 2455 | if ($edge_type == 0) | 
| 1293 |  |  |  |  |  |  | { | 
| 1294 | 1155 |  | 100 |  |  | 3235 | my $edge_flags = ($type||0) & EDGE_FLAG_MASK; | 
| 1295 |  |  |  |  |  |  |  | 
| 1296 |  |  |  |  |  |  | # either a start or a stop cell | 
| 1297 | 1155 | 50 |  |  |  | 2394 | if (!defined $px) | 
| 1298 |  |  |  |  |  |  | { | 
| 1299 |  |  |  |  |  |  | # We can figure it out from the flag of the position of cx,cy | 
| 1300 |  |  |  |  |  |  | #        ................ | 
| 1301 |  |  |  |  |  |  | #         : EDGE_START_S : | 
| 1302 |  |  |  |  |  |  | # ....................................... | 
| 1303 |  |  |  |  |  |  | # START_E :    px,py     : EDGE_START_W : | 
| 1304 |  |  |  |  |  |  | # ....................................... | 
| 1305 |  |  |  |  |  |  | #         : EDGE_START_N : | 
| 1306 |  |  |  |  |  |  | #         ................ | 
| 1307 | 0 |  |  |  |  | 0 | ($px,$py) = ($cx, $cy);		# start with same cell | 
| 1308 | 0 | 0 |  |  |  | 0 | $py ++ if ($edge_flags & EDGE_START_S) != 0; | 
| 1309 | 0 | 0 |  |  |  | 0 | $py -- if ($edge_flags & EDGE_START_N) != 0; | 
| 1310 |  |  |  |  |  |  |  | 
| 1311 | 0 | 0 |  |  |  | 0 | $px ++ if ($edge_flags & EDGE_START_E) != 0; | 
| 1312 | 0 | 0 |  |  |  | 0 | $px -- if ($edge_flags & EDGE_START_W) != 0; | 
| 1313 |  |  |  |  |  |  | } | 
| 1314 |  |  |  |  |  |  |  | 
| 1315 |  |  |  |  |  |  | # if lx, ly is undefined because px,py is a joint, get it via the stored | 
| 1316 |  |  |  |  |  |  | # x,y pos of the very last cell in the path | 
| 1317 | 1155 | 100 |  |  |  | 2299 | if (!defined $lx) | 
| 1318 |  |  |  |  |  |  | { | 
| 1319 | 240 |  |  |  |  | 530 | $lx = $closed->{$xy}->[6]; | 
| 1320 | 240 |  |  |  |  | 472 | $ly = $closed->{$xy}->[7]; | 
| 1321 |  |  |  |  |  |  | } | 
| 1322 |  |  |  |  |  |  |  | 
| 1323 |  |  |  |  |  |  | # still not known? | 
| 1324 | 1155 | 100 |  |  |  | 2205 | if (!defined $lx) | 
| 1325 |  |  |  |  |  |  | { | 
| 1326 |  |  |  |  |  |  |  | 
| 1327 |  |  |  |  |  |  | # If lx,ly is undefined because we are at the end of the path, | 
| 1328 |  |  |  |  |  |  | # we can figure out from the flag of the position of cx,cy. | 
| 1329 |  |  |  |  |  |  | #       .............. | 
| 1330 |  |  |  |  |  |  | #       : EDGE_END_S : | 
| 1331 |  |  |  |  |  |  | # ................................. | 
| 1332 |  |  |  |  |  |  | # END_E :    lx,ly   : EDGE_END_W : | 
| 1333 |  |  |  |  |  |  | # ................................. | 
| 1334 |  |  |  |  |  |  | #       : EDGE_END_N : | 
| 1335 |  |  |  |  |  |  | #       .............. | 
| 1336 | 228 |  |  |  |  | 376 | ($lx,$ly) = ($cx, $cy);		# start with same cell | 
| 1337 |  |  |  |  |  |  |  | 
| 1338 | 228 | 100 |  |  |  | 642 | $ly ++ if ($edge_flags & EDGE_END_S) != 0; | 
| 1339 | 228 | 100 |  |  |  | 599 | $ly -- if ($edge_flags & EDGE_END_N) != 0; | 
| 1340 |  |  |  |  |  |  |  | 
| 1341 | 228 | 100 |  |  |  | 589 | $lx ++ if ($edge_flags & EDGE_END_E) != 0; | 
| 1342 | 228 | 100 |  |  |  | 560 | $lx -- if ($edge_flags & EDGE_END_W) != 0; | 
| 1343 |  |  |  |  |  |  | } | 
| 1344 |  |  |  |  |  |  |  | 
| 1345 |  |  |  |  |  |  | # now figure out correct type for this cell from positions of | 
| 1346 |  |  |  |  |  |  | # parent/following cell | 
| 1347 | 1155 |  |  |  |  | 2943 | $type += _astar_edge_type($px, $py, $cx, $cy, $lx,$ly); | 
| 1348 |  |  |  |  |  |  | } | 
| 1349 |  |  |  |  |  |  |  | 
| 1350 | 1155 | 50 |  |  |  | 3489 | print STDERR "#  Following back from $lx,$ly over $cx,$cy to $px,$py\n" if $self->{debug} > 1; | 
| 1351 |  |  |  |  |  |  |  | 
| 1352 | 1155 | 0 | 66 |  |  | 3378 | if ($px == $lx && $py == $ly && ($cx != $lx || $cy != $ly)) | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 1353 |  |  |  |  |  |  | { | 
| 1354 | 0 | 0 |  |  |  | 0 | print STDERR | 
| 1355 |  |  |  |  |  |  | "# Warning: A* detected loop in path-backtracking at $px,$py, $cx,$cy, $lx,$ly\n" | 
| 1356 |  |  |  |  |  |  | if $self->{debug}; | 
| 1357 | 0 |  |  |  |  | 0 | last; | 
| 1358 |  |  |  |  |  |  | } | 
| 1359 |  |  |  |  |  |  |  | 
| 1360 | 1155 | 50 |  |  |  | 2563 | $type = EDGE_HOR if ($type & EDGE_TYPE_MASK) == 0;		# last resort | 
| 1361 |  |  |  |  |  |  |  | 
| 1362 |  |  |  |  |  |  | # if this is the first hor edge, attach the label to it | 
| 1363 |  |  |  |  |  |  | # XXX TODO: This clearly is not optimal. Look for left-most HOR CELL | 
| 1364 | 1155 |  |  |  |  | 1398 | my $t = $type & EDGE_TYPE_MASK; | 
| 1365 |  |  |  |  |  |  |  | 
| 1366 |  |  |  |  |  |  | # Do not put the label on crossings: | 
| 1367 | 1155 | 100 | 66 |  |  | 5130 | if ($label_cell == 0 && (!exists $cells->{"$cx,$cy"}) && ($t == EDGE_HOR || $t == EDGE_VER)) | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 1368 |  |  |  |  |  |  | { | 
| 1369 | 239 |  |  |  |  | 342 | $label_cell++; | 
| 1370 | 239 |  |  |  |  | 3987 | $type += EDGE_LABEL_CELL; | 
| 1371 |  |  |  |  |  |  | } | 
| 1372 |  |  |  |  |  |  |  | 
| 1373 | 1155 | 100 | 100 |  |  | 15521 | push @bends, [ $type, $cx, $cy, -$idx ] | 
|  |  |  | 100 |  |  |  |  | 
|  |  |  | 100 |  |  |  |  | 
| 1374 |  |  |  |  |  |  | if ($type == EDGE_S_E || $t == EDGE_S_W || $t == EDGE_N_E || $t == EDGE_N_W); | 
| 1375 |  |  |  |  |  |  |  | 
| 1376 | 1155 |  |  |  |  | 3242 | unshift @$path, $cx, $cy, $type;		# unshift to reverse the path | 
| 1377 |  |  |  |  |  |  |  | 
| 1378 | 1155 | 100 |  |  |  | 4978 | last if $closed->{"$cx,$cy"}->[ 5 ];	# stop here? | 
| 1379 |  |  |  |  |  |  |  | 
| 1380 | 915 |  |  |  |  | 1568 | ($lx,$ly) = ($cx,$cy); | 
| 1381 | 915 |  |  |  |  | 1128 | ($cx,$cy) = @{ $closed->{"$cx,$cy"} };	# get X,Y of next cell | 
|  | 915 |  |  |  |  | 4773 |  | 
| 1382 |  |  |  |  |  |  |  | 
| 1383 | 915 |  |  |  |  | 2611 | $idx += 3;					# index into $path (for bends) | 
| 1384 |  |  |  |  |  |  | } | 
| 1385 |  |  |  |  |  |  |  | 
| 1386 | 240 | 50 | 66 |  |  | 1916 | print STDERR "# Trying to straighten path\n" if @bends >= 3 && $self->{debug}; | 
| 1387 |  |  |  |  |  |  |  | 
| 1388 |  |  |  |  |  |  | # try to straighten unnec. inward bends | 
| 1389 | 240 | 100 |  |  |  | 617 | $self->_straighten_path($path, \@bends, $edge) if @bends >= 3; | 
| 1390 |  |  |  |  |  |  |  | 
| 1391 | 240 | 50 |  |  |  | 1012 | return ($path,$closed,$open_by_pos) if wantarray; | 
| 1392 | 240 |  |  |  |  | 6989 | $path; | 
| 1393 |  |  |  |  |  |  | } | 
| 1394 |  |  |  |  |  |  |  | 
| 1395 |  |  |  |  |  |  | # 1: | 
| 1396 |  |  |  |  |  |  | #           |             | | 
| 1397 |  |  |  |  |  |  | #      +----+   =>        | | 
| 1398 |  |  |  |  |  |  | #      |                  | | 
| 1399 |  |  |  |  |  |  | #  ----+            ------+ | 
| 1400 |  |  |  |  |  |  |  | 
| 1401 |  |  |  |  |  |  | # 2: | 
| 1402 |  |  |  |  |  |  | #      +---         +------ | 
| 1403 |  |  |  |  |  |  | #      |            | | 
| 1404 |  |  |  |  |  |  | #  +---+        =>  | | 
| 1405 |  |  |  |  |  |  | #  |                | | 
| 1406 |  |  |  |  |  |  |  | 
| 1407 |  |  |  |  |  |  | # 3: | 
| 1408 |  |  |  |  |  |  | #  ----+            ------+ | 
| 1409 |  |  |  |  |  |  | #      |        =>        | | 
| 1410 |  |  |  |  |  |  | #      +----+             | | 
| 1411 |  |  |  |  |  |  | #           |             | | 
| 1412 |  |  |  |  |  |  |  | 
| 1413 |  |  |  |  |  |  | # 4: | 
| 1414 |  |  |  |  |  |  | #  |                | | 
| 1415 |  |  |  |  |  |  | #  +---+            | | 
| 1416 |  |  |  |  |  |  | #      |        =>  | | 
| 1417 |  |  |  |  |  |  | #      +----+       +------ | 
| 1418 |  |  |  |  |  |  |  | 
| 1419 |  |  |  |  |  |  | my $bend_patterns = [ | 
| 1420 |  |  |  |  |  |  |  | 
| 1421 |  |  |  |  |  |  | # The patterns are duplicated to catch both directions of the path: | 
| 1422 |  |  |  |  |  |  |  | 
| 1423 |  |  |  |  |  |  | # First five entries must match | 
| 1424 |  |  |  |  |  |  | #				 dx, dy, | 
| 1425 |  |  |  |  |  |  | #				        coordinates for new edge | 
| 1426 |  |  |  |  |  |  | #				        (2 == y, 1 == x, first is | 
| 1427 |  |  |  |  |  |  | #				        taken from A, second from B) | 
| 1428 |  |  |  |  |  |  | # 						  these replace the first & last bend | 
| 1429 |  |  |  |  |  |  | # 1: | 
| 1430 |  |  |  |  |  |  | [ EDGE_N_W, EDGE_S_E, EDGE_N_W, 0, -1, 2, 1, EDGE_HOR, EDGE_VER, 1,0,  0,-1 ],	# 0 | 
| 1431 |  |  |  |  |  |  | [ EDGE_N_W, EDGE_S_E, EDGE_N_W, -1, 0, 1, 2, EDGE_VER, EDGE_HOR, 0,1,  -1,0 ],	# 1 | 
| 1432 |  |  |  |  |  |  |  | 
| 1433 |  |  |  |  |  |  | # 2: | 
| 1434 |  |  |  |  |  |  | [ EDGE_S_E, EDGE_N_W, EDGE_S_E, 0, -1, 1, 2, EDGE_VER, EDGE_HOR, 0,-1, 1,0 ],		# 2 | 
| 1435 |  |  |  |  |  |  | [ EDGE_S_E, EDGE_N_W, EDGE_S_E, -1, 0, 2, 1, EDGE_HOR, EDGE_VER, -1,0, 0,1 ],		# 3 | 
| 1436 |  |  |  |  |  |  |  | 
| 1437 |  |  |  |  |  |  | # 3: | 
| 1438 |  |  |  |  |  |  | [ EDGE_S_W, EDGE_N_E, EDGE_S_W, 0,  1, 2, 1, EDGE_HOR, EDGE_VER, 1,0, 0,1 ],		# 4 | 
| 1439 |  |  |  |  |  |  | [ EDGE_S_W, EDGE_N_E, EDGE_S_W, -1, 0, 1, 2, EDGE_VER, EDGE_HOR, 0,-1, -1,0 ],	# 5 | 
| 1440 |  |  |  |  |  |  |  | 
| 1441 |  |  |  |  |  |  | # 4: | 
| 1442 |  |  |  |  |  |  | [ EDGE_N_E, EDGE_S_W, EDGE_N_E, 1,  0, 1, 2, EDGE_VER, EDGE_HOR, 0,1, 1,0 ],		# 6 | 
| 1443 |  |  |  |  |  |  | [ EDGE_N_E, EDGE_S_W, EDGE_N_E, 0, -1, 2, 1, EDGE_HOR, EDGE_VER, -1,0, 0,-1 ],	# 7 | 
| 1444 |  |  |  |  |  |  |  | 
| 1445 |  |  |  |  |  |  | ]; | 
| 1446 |  |  |  |  |  |  |  | 
| 1447 |  |  |  |  |  |  | sub _straighten_path | 
| 1448 |  |  |  |  |  |  | { | 
| 1449 | 8 |  |  | 8 |  | 26 | my ($self, $path, $bends, $edge) = @_; | 
| 1450 |  |  |  |  |  |  |  | 
| 1451 |  |  |  |  |  |  | # XXX TODO: | 
| 1452 |  |  |  |  |  |  | # in case of multiple bends, removes only one of them due to overlap | 
| 1453 |  |  |  |  |  |  |  | 
| 1454 | 8 |  |  |  |  | 24 | my $cells = $self->{cells}; | 
| 1455 |  |  |  |  |  |  |  | 
| 1456 | 8 |  |  |  |  | 17 | my $i = 0; | 
| 1457 |  |  |  |  |  |  | BEND: | 
| 1458 | 8 |  |  |  |  | 38 | while ($i < (scalar @$bends - 2)) | 
| 1459 |  |  |  |  |  |  | { | 
| 1460 |  |  |  |  |  |  | # for each bend, check it and the next two bends | 
| 1461 |  |  |  |  |  |  |  | 
| 1462 |  |  |  |  |  |  | #   print STDERR "Checking bend $i at $bends->[$i], $bends->[$i+1], $bends->[$i+2]\n"; | 
| 1463 |  |  |  |  |  |  |  | 
| 1464 | 10 |  |  |  |  | 35 | my ($a,$b,$c) = ($bends->[$i], | 
| 1465 |  |  |  |  |  |  | $bends->[$i+1], | 
| 1466 |  |  |  |  |  |  | $bends->[$i+2]); | 
| 1467 |  |  |  |  |  |  |  | 
| 1468 | 10 |  |  |  |  | 27 | my $dx = ($b->[1] - $a->[1]); | 
| 1469 | 10 |  |  |  |  | 21 | my $dy = ($b->[2] - $a->[2]); | 
| 1470 |  |  |  |  |  |  |  | 
| 1471 | 10 |  |  |  |  | 17 | my $p = 0; | 
| 1472 | 10 |  |  |  |  | 30 | for my $pattern (@$bend_patterns) | 
| 1473 |  |  |  |  |  |  | { | 
| 1474 | 80 |  |  |  |  | 82 | $p++; | 
| 1475 | 80 | 0 | 100 |  |  | 309 | next if ($a->[0] != $pattern->[0]) || | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 1476 |  |  |  |  |  |  | ($b->[0] != $pattern->[1]) || | 
| 1477 |  |  |  |  |  |  | ($c->[0] != $pattern->[2]) || | 
| 1478 |  |  |  |  |  |  | ($dx != $pattern->[3]) || | 
| 1479 |  |  |  |  |  |  | ($dy != $pattern->[4]); | 
| 1480 |  |  |  |  |  |  |  | 
| 1481 |  |  |  |  |  |  | # pattern matched | 
| 1482 |  |  |  |  |  |  | #      print STDERR "# Got bends for pattern ", $p-1," (@$pattern):\n"; | 
| 1483 |  |  |  |  |  |  | #      print STDERR "# type x,y,\n# @$a\n# @$b\n# @$c\n"; | 
| 1484 |  |  |  |  |  |  |  | 
| 1485 |  |  |  |  |  |  | # check that the alternative path is empty | 
| 1486 |  |  |  |  |  |  |  | 
| 1487 |  |  |  |  |  |  | # new corner: | 
| 1488 | 0 |  |  |  |  | 0 | my $cx = $a->[$pattern->[5]]; | 
| 1489 | 0 |  |  |  |  | 0 | my $cy = $c->[$pattern->[6]]; | 
| 1490 | 0 | 0 |  |  |  | 0 | ($cx,$cy) = ($cy,$cx) if $pattern->[5] == 2;	# need to swap? | 
| 1491 |  |  |  |  |  |  |  | 
| 1492 | 0 | 0 |  |  |  | 0 | next BEND if exists $cells->{"$cx,$cy"}; | 
| 1493 |  |  |  |  |  |  |  | 
| 1494 |  |  |  |  |  |  | #      print STDERR "# new corner at $cx,$cy (swap: $pattern->[5])\n"; | 
| 1495 |  |  |  |  |  |  |  | 
| 1496 |  |  |  |  |  |  | # check from A to new corner | 
| 1497 | 0 |  |  |  |  | 0 | my $x = $a->[1]; | 
| 1498 | 0 |  |  |  |  | 0 | my $y = $a->[2]; | 
| 1499 |  |  |  |  |  |  |  | 
| 1500 | 0 |  |  |  |  | 0 | my @replace = (); | 
| 1501 | 0 | 0 | 0 |  |  | 0 | push @replace, $cx, $cy, $pattern->[0] if ($x == $cx && $y == $cy); | 
| 1502 |  |  |  |  |  |  |  | 
| 1503 | 0 |  |  |  |  | 0 | my $ddx = $pattern->[9]; | 
| 1504 | 0 |  |  |  |  | 0 | my $ddy = $pattern->[10]; | 
| 1505 |  |  |  |  |  |  | #      print STDERR "# dx,dy: $ddx,$ddy\n"; | 
| 1506 | 0 |  | 0 |  |  | 0 | while ($x != $cx || $y != $cy) | 
| 1507 |  |  |  |  |  |  | { | 
| 1508 | 0 | 0 |  |  |  | 0 | next BEND if exists $cells->{"$x,$y"}; | 
| 1509 |  |  |  |  |  |  | #        print STDERR "# at $x $y (go to $cx,$cy)\n"; sleep(1); | 
| 1510 | 0 |  |  |  |  | 0 | push @replace, $x, $y, $pattern->[7]; | 
| 1511 | 0 |  |  |  |  | 0 | $x += $ddx; | 
| 1512 | 0 |  |  |  |  | 0 | $y += $ddy; | 
| 1513 |  |  |  |  |  |  | } | 
| 1514 |  |  |  |  |  |  |  | 
| 1515 | 0 |  |  |  |  | 0 | $x = $cx; $y = $cy; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1516 |  |  |  |  |  |  |  | 
| 1517 |  |  |  |  |  |  | # check from new corner to C | 
| 1518 | 0 |  |  |  |  | 0 | $ddx = $pattern->[11]; | 
| 1519 | 0 |  |  |  |  | 0 | $ddy = $pattern->[12]; | 
| 1520 | 0 |  | 0 |  |  | 0 | while ($x != $c->[1] || $y != $c->[2]) | 
| 1521 |  |  |  |  |  |  | { | 
| 1522 | 0 | 0 |  |  |  | 0 | next BEND if exists $cells->{"$x,$y"}; | 
| 1523 |  |  |  |  |  |  | #        print STDERR "# at $x $y (go to $cx,$cy)\n"; sleep(1); | 
| 1524 | 0 |  |  |  |  | 0 | push @replace, $x, $y, $pattern->[8]; | 
| 1525 |  |  |  |  |  |  |  | 
| 1526 |  |  |  |  |  |  | # set the correct type on the corner | 
| 1527 | 0 | 0 | 0 |  |  | 0 | $replace[-1] = $pattern->[0] if ($x == $cx && $y == $cy); | 
| 1528 | 0 |  |  |  |  | 0 | $x += $ddx; | 
| 1529 | 0 |  |  |  |  | 0 | $y += $ddy; | 
| 1530 |  |  |  |  |  |  | } | 
| 1531 |  |  |  |  |  |  | # insert Corner | 
| 1532 | 0 |  |  |  |  | 0 | push @replace, $x, $y, $pattern->[8]; | 
| 1533 |  |  |  |  |  |  |  | 
| 1534 |  |  |  |  |  |  | #	use Data::Dumper; print STDERR Dumper(@replace); | 
| 1535 |  |  |  |  |  |  | #	print STDERR "# generated ", scalar @replace, " entries\n"; | 
| 1536 |  |  |  |  |  |  | #	print STDERR "# idx A $a->[3] C $c->[3]\n"; | 
| 1537 |  |  |  |  |  |  |  | 
| 1538 |  |  |  |  |  |  | # the path is clear, so replace the inward bend with the new one | 
| 1539 | 0 | 0 |  |  |  | 0 | my $diff = $a->[3] - $c->[3] ? -3 : 3; | 
| 1540 |  |  |  |  |  |  |  | 
| 1541 | 0 |  |  |  |  | 0 | my $idx = 0; my $p_idx = $a->[3] + $diff; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1542 | 0 |  |  |  |  | 0 | while ($idx < @replace) | 
| 1543 |  |  |  |  |  |  | { | 
| 1544 |  |  |  |  |  |  | #	 print STDERR "# replace $p_idx .. $p_idx + 2\n"; | 
| 1545 |  |  |  |  |  |  | #	 print STDERR "# replace $path->[$p_idx] with $replace[$idx]\n"; | 
| 1546 |  |  |  |  |  |  | #	 print STDERR "# replace $path->[$p_idx+1] with $replace[$idx+1]\n"; | 
| 1547 |  |  |  |  |  |  | #	 print STDERR "# replace $path->[$p_idx+2] with $replace[$idx+2]\n"; | 
| 1548 |  |  |  |  |  |  |  | 
| 1549 | 0 |  |  |  |  | 0 | $path->[$p_idx] = $replace[$idx]; | 
| 1550 | 0 |  |  |  |  | 0 | $path->[$p_idx+1] = $replace[$idx+1]; | 
| 1551 | 0 |  |  |  |  | 0 | $path->[$p_idx+2] = $replace[$idx+2]; | 
| 1552 | 0 |  |  |  |  | 0 | $p_idx += $diff; | 
| 1553 | 0 |  |  |  |  | 0 | $idx += 3; | 
| 1554 |  |  |  |  |  |  | } | 
| 1555 |  |  |  |  |  |  | } # end for this pattern | 
| 1556 |  |  |  |  |  |  |  | 
| 1557 | 10 |  |  |  |  | 44 | } continue { $i++; }; | 
| 1558 |  |  |  |  |  |  | } | 
| 1559 |  |  |  |  |  |  |  | 
| 1560 |  |  |  |  |  |  | sub _map_as_html | 
| 1561 |  |  |  |  |  |  | { | 
| 1562 | 0 |  |  | 0 |  |  | my ($self, $cells, $p, $closed, $open, $w, $h) = @_; | 
| 1563 |  |  |  |  |  |  |  | 
| 1564 | 0 |  | 0 |  |  |  | $w ||= 20; | 
| 1565 | 0 |  | 0 |  |  |  | $h ||= 20; | 
| 1566 |  |  |  |  |  |  |  | 
| 1567 | 0 |  |  |  |  |  | my $html = < | 
| 1568 |  |  |  |  |  |  |  | 
| 1569 |  |  |  |  |  |  |  | 
| 1570 |  |  |  |  |  |  |  | 
| 1571 |  |  |  |  |  |  |  | 
| 1591 |  |  |  |  |  |  |  | 
| 1592 |  |  |  |  |  |  |  | 
| 1593 |  |  |  |  |  |  |  | 
| 1594 |  |  |  |  |  |  | A* Map | 
| 1595 |  |  |  |  |  |  |  | 
| 1596 |  |  |  |  |  |  |   | 
| 1597 |  |  |  |  |  |  | Nodes examined: ##closed## 
 | 
| 1598 |  |  |  |  |  |  | Nodes still to do (open): ##open## 
 | 
| 1599 |  |  |  |  |  |  | Nodes in path: ##path## | 
| 1600 |  |  |  |  |  |  |  | 
| 1601 |  |  |  |  |  |  | EOF | 
| 1602 |  |  |  |  |  |  | ; | 
| 1603 |  |  |  |  |  |  |  | 
| 1604 | 0 |  |  |  |  |  | $html =~ s/##closed##/keys %$closed /eg; | 
|  | 0 |  |  |  |  |  |  | 
| 1605 | 0 |  |  |  |  |  | $html =~ s/##open##/keys %$open /eg; | 
|  | 0 |  |  |  |  |  |  | 
| 1606 | 0 |  |  |  |  |  | my $path = {}; | 
| 1607 | 0 |  |  |  |  |  | while (@$p) | 
| 1608 |  |  |  |  |  |  | { | 
| 1609 | 0 |  |  |  |  |  | my $x = shift @$p; | 
| 1610 | 0 |  |  |  |  |  | my $y = shift @$p; | 
| 1611 | 0 |  |  |  |  |  | my $t = shift @$p; | 
| 1612 | 0 |  |  |  |  |  | $path->{"$x,$y"} = undef; | 
| 1613 |  |  |  |  |  |  | } | 
| 1614 | 0 |  |  |  |  |  | $html =~ s/##path##/keys %$path /eg; | 
|  | 0 |  |  |  |  |  |  | 
| 1615 | 0 |  |  |  |  |  | $html .= ' ' . "\n"; 
\n";
| 1616 |  |  |  |  |  |  |  |  
| 1617 | 0 |  |  |  |  |  | for my $y (0..$h) |  
| 1618 |  |  |  |  |  |  | { |  
| 1619 | 0 |  |  |  |  |  | $html .= " |  \n"; 
 
| 1620 | 0 |  |  |  |  |  | for my $x (0..$w) |  
| 1621 |  |  |  |  |  |  | { |  
| 1622 | 0 |  |  |  |  |  | my $xy = "$x,$y"; |  
| 1623 | 0 |  |  |  |  |  | my $c = ' ' x 4; |  
| 1624 | 0 | 0 | 0 |  |  |  | $html .= " | $c\n" and next if |  
|  |  |  | 0 |  |  |  |  |  
| 1625 |  |  |  |  |  |  | exists $cells->{$xy} and ref($cells->{$xy}) =~ /Node/; |  
| 1626 | 0 | 0 | 0 |  |  |  | $html .= " | $c\n" and next if |  
|  |  |  | 0 |  |  |  |  |  
| 1627 |  |  |  |  |  |  | exists $cells->{$xy} && !exists $path->{$xy}; |  
| 1628 |  |  |  |  |  |  |  |  
| 1629 | 0 | 0 | 0 |  |  |  | $html .= " | $c\n" and next unless |  
|  |  |  | 0 |  |  |  |  |  
| 1630 |  |  |  |  |  |  | exists $closed->{$xy} || |  
| 1631 |  |  |  |  |  |  | exists $open->{$xy}; |  
| 1632 |  |  |  |  |  |  |  |  
| 1633 | 0 |  |  |  |  |  | my $clr = '#a0a0a0'; |  
| 1634 | 0 | 0 |  |  |  |  | if (exists $closed->{$xy}) |  
|  |  | 0 |  |  |  |  |  |  
| 1635 |  |  |  |  |  |  | { |  
| 1636 | 0 |  | 0 |  |  |  | $c =  ($closed->{$xy}->[3] || '0') . '+' . ($closed->{$xy}->[2] || '0'); |  
|  |  |  | 0 |  |  |  |  |  
| 1637 | 0 |  | 0 |  |  |  | my $color = 0x10 + 8 * (($closed->{$xy}->[2] || 0)); |  
| 1638 | 0 |  | 0 |  |  |  | my $color2 = 0x10 + 8 * (($closed->{$xy}->[3] || 0)); |  
| 1639 | 0 |  |  |  |  |  | $clr = sprintf("%02x%02x",$color,$color2) . 'a0'; |  
| 1640 |  |  |  |  |  |  | } |  
| 1641 |  |  |  |  |  |  | elsif (exists $open->{$xy}) |  
| 1642 |  |  |  |  |  |  | { |  
| 1643 | 0 |  | 0 |  |  |  | $c = ' ' . $open->{$xy} || '0'; |  
| 1644 | 0 |  | 0 |  |  |  | my $color = 0xff - 8 * ($open->{$xy} || 0); |  
| 1645 | 0 |  |  |  |  |  | $clr = 'a0' . sprintf("%02x",$color) . '00'; |  
| 1646 |  |  |  |  |  |  | } |  
| 1647 | 0 |  |  |  |  |  | my $b = ''; |  
| 1648 | 0 | 0 |  |  |  |  | $b = 'border: 2px white solid;' if exists $path->{$xy}; |  
| 1649 | 0 |  |  |  |  |  | $html .= " | $c\n"; |  
| 1650 |  |  |  |  |  |  | } |  
\n";| 1651 | 0 |  |  |  |  |  | $html .= " |  
| 1652 |  |  |  |  |  |  | } |  
| 1653 |  |  |  |  |  |  |  |  
| 1654 | 0 |  |  |  |  |  | $html .= "\n |  | 
| 1655 |  |  |  |  |  |  |  | 
| 1656 | 0 |  |  |  |  |  | $html; | 
| 1657 |  |  |  |  |  |  | } | 
| 1658 |  |  |  |  |  |  |  | 
| 1659 |  |  |  |  |  |  | 1; | 
| 1660 |  |  |  |  |  |  | __END__ |