File Coverage

blib/lib/Game/TextMapper/Line.pm
Criterion Covered Total %
statement 52 105 49.5
branch 11 38 28.9
condition 1 15 6.6
subroutine 7 9 77.7
pod 3 6 50.0
total 74 173 42.7


line stmt bran cond sub pod time code
1             # Copyright (C) 2009-2022 Alex Schroeder
2             #
3             # This program is free software: you can redistribute it and/or modify it under
4             # the terms of the GNU Affero General Public License as published by the Free
5             # Software Foundation, either version 3 of the License, or (at your option) any
6             # later version.
7             #
8             # This program is distributed in the hope that it will be useful, but WITHOUT
9             # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10             # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11             # details.
12             #
13             # You should have received a copy of the GNU Affero General Public License along
14             # with this program. If not, see .
15              
16             =encoding utf8
17              
18             =head1 NAME
19              
20             Game::TextMapper::Line - a line between two points
21              
22             =head1 SYNOPSIS
23              
24             use Modern::Perl;
25             use Game::TextMapper::Line::Hex;
26             use Game::TextMapper::Point::Hex;
27             my $line = Game::TextMapper::Line::Hex->new();
28             my $from = Game::TextMapper::Point::Hex->new(x => 1, y => 1, z => 0);
29             my $to = Game::TextMapper::Point::Hex->new(x => 5, y => 3, z => 0);
30             $line->points([$from, $to]);
31             my @line = $line->compute_missing_points;
32             say join(" ", map { $_->str } @line);
33             # (1,1,0) (2,1,0) (3,2,0) (4,2,0) (5,3,0)
34              
35             =head1 DESCRIPTION
36              
37             The line connects two points. This class knows how to compute all the regions
38             between these two points, how to compute the next region along the line, and how
39             to output SVG.
40              
41             In order to do this, the class needs to know how to work with the regions on the
42             map. This is different for hexes and squares. Therefore you should always be
43             using the appropriate Hex or Square class instead.
44              
45             =cut
46              
47             package Game::TextMapper::Line;
48              
49 11     11   81 use Modern::Perl '2018';
  11         28  
  11         74  
50 11     11   3824 use Mojo::Util qw(url_escape);
  11         28  
  11         1312  
51 11     11   130 use Mojo::Base -base;
  11         25  
  11         123  
52              
53             our $debug;
54              
55             =head1 ATTRIBUTES
56              
57             =head2 points
58              
59             An array reference of points using a class derived from
60             L, i.e. L uses
61             L and L uses
62             L.
63              
64             =cut
65              
66             has 'id';
67             has 'points';
68             has 'offset';
69             has 'type';
70             has 'label';
71             has 'map';
72             has 'side';
73             has 'start';
74              
75             =head1 METHODS
76              
77             =head2 compute_missing_points
78              
79             Compute the missing points between the points in C and return it.
80              
81             =cut
82              
83             sub compute_missing_points {
84 352     352 1 727 my $self = shift;
85 352         603 my $i = 0;
86 352         1251 my $current = $self->points->[$i++];
87 352         2195 my $z = $current->z;
88 352         2060 my @result = ($current);
89 352         885 while ($self->points->[$i]) {
90 2148         45821 $current = $self->one_step($current, $self->points->[$i]);
91 2148 50       28117 return unless $z == $current->z; # must all be on the same plane
92 2148         11200 push(@result, $current);
93 2148 100       5801 $i++ if $current->equal($self->points->[$i]);
94             }
95              
96 352         9537 return @result;
97             }
98              
99             sub partway {
100 4648     4648 0 9072 my ($self, $from, $to, $q) = @_;
101 4648         11311 my ($x1, $y1) = $self->pixels($from);
102 4648         10960 my ($x2, $y2) = $self->pixels($to);
103 4648   50     12542 $q ||= 1;
104 4648 50       9445 return $x1 + ($x2 - $x1) * $q, $y1 + ($y2 - $y1) * $q if wantarray;
105 4648         35264 return sprintf("%.1f,%.1f", $x1 + ($x2 - $x1) * $q, $y1 + ($y2 - $y1) * $q);
106             }
107              
108             =head2 svg($offset)
109              
110             This returns an SVG fragment, a string with a C.
111              
112             =cut
113              
114             sub svg {
115 352     352 1 2210 my ($self, $offset) = @_;
116 352         890 my ($path, $current, $next, $closed);
117 352         1196 $self->offset($offset);
118 352         2882 my @points = $self->compute_missing_points();
119 352 50       983 return '' unless @points;
120 352 50       1137 if ($points[0]->equal($points[$#points])) {
121 0         0 $closed = 1;
122             }
123              
124 352 50       5228 if ($closed) {
125 0         0 for my $i (0 .. $#points - 1) {
126 0         0 $current = $points[$i];
127 0         0 $next = $points[$i+1];
128 0 0       0 if (!$path) {
129 0         0 my $a = $self->partway($current, $next, 0.3);
130 0         0 my $b = $self->partway($current, $next, 0.5);
131 0         0 my $c = $self->partway($points[$#points-1], $current, 0.7);
132 0         0 my $d = $self->partway($points[$#points-1], $current, 0.5);
133 0         0 $path = "M$d C$c $a $b";
134             } else {
135             # continue curve
136 0         0 my $a = $self->partway($current, $next, 0.3);
137 0         0 my $b = $self->partway($current, $next, 0.5);
138 0         0 $path .= " S$a $b";
139             }
140             }
141             } else {
142 352         1157 for my $i (0 .. $#points - 1) {
143 2148         4011 $current = $points[$i];
144 2148         3775 $next = $points[$i+1];
145 2148 100       4292 if (!$path) {
146             # line from a to b; control point a required for following S commands
147 352         1027 my $a = $self->partway($current, $next, 0.3);
148 352         12647 my $b = $self->partway($current, $next, 0.5);
149 352         1180 $path = "M$a C$b $a $b";
150             } else {
151             # continue curve
152 1796         3693 my $a = $self->partway($current, $next, 0.3);
153 1796         4202 my $b = $self->partway($current, $next, 0.5);
154 1796         4981 $path .= " S$a $b";
155             }
156             }
157             # end with a little stub
158 352         938 $path .= " L" . $self->partway($current, $next, 0.7);
159             }
160              
161 352         1265 my $id = $self->id;
162 352         2229 my $type = $self->type;
163 352         2149 my $attributes = $self->map->path_attributes->{$type};
164 352         4544 my $data = qq{ \n};
165 352 50       896 $data .= $self->debug($closed) if $debug;
166 352         4172 return $data;
167             }
168              
169             =head2 svg_label
170              
171             This returns an SVG fragment, a group C with C and a C
172             element.
173              
174             =cut
175              
176             sub svg_label {
177 352     352 1 1628 my ($self) = @_;
178 352 50       729 return '' unless defined $self->label;
179 0           my $id = $self->id;
180 0           my $label = $self->label;
181 0   0       my $attributes = $self->map->label_attributes || "";
182 0   0       my $glow = $self->map->glow_attributes || "";
183 0           my $url = $self->map->url;
184 0 0 0       $url =~ s/\%s/url_escape($self->label)/e or $url .= url_escape($self->label) if $url;
  0            
185             # Default side is left, but if the line goes from right to left, then "left"
186             # means "upside down", so allow people to control it.
187 0           my $pathAttributes = '';
188 0 0 0       if ($self->side) {
    0 0        
189 0           $pathAttributes = ' side="' . $self->side . '"';
190             } elsif ($self->points->[1]->x < $self->points->[0]->x
191 0           or $#{$self->points} >= 2 and $self->points->[2]->x < $self->points->[0]->x) {
192 0           $pathAttributes = ' side="right"';
193             }
194 0 0         if ($self->start) {
195 0           $pathAttributes .= ' startOffset="' . $self->start . '"';
196             }
197 0           my $data = qq{ \n};
198 0 0         $data .= qq{ $label\n} if $glow;
199 0 0         $data .= qq{ } if $url;
200 0           $data .= qq{ $label\n};
201 0 0         $data .= qq{ } if $url;
202 0           $data .= qq{ \n};
203 0           return $data;
204             }
205              
206             sub debug {
207 0     0 0   my ($self, $closed) = @_;
208 0           my ($data, $current, $next);
209 0           my @points = $self->compute_missing_points();
210 0           for my $i (0 .. $#points - 1) {
211 0           $current = $points[$i];
212 0           $next = $points[$i+1];
213 0           $data .= circle($self->pixels($current), 15, $i++);
214 0           $data .= circle($self->partway($current, $next, 0.3), 3, 'a');
215 0           $data .= circle($self->partway($current, $next, 0.5), 5, 'b');
216 0           $data .= circle($self->partway($current, $next, 0.7), 3, 'c');
217             }
218 0           $data .= circle($self->pixels($next), 15, $#points);
219              
220 0           my ($x, $y) = $self->pixels($points[0]); $y += 30;
  0            
221 0 0         $data .= "
222             . "text-anchor='middle' dominant-baseline='central' "
223             . "x='$x' y='$y'>closed"
224             if $closed;
225              
226 0           return $data;
227             }
228              
229             sub circle {
230 0     0 0   my ($x, $y, $r, $i) = @_;
231 0           my $data = "";
232 0 0         $data .= "
233             . "text-anchor='middle' dominant-baseline='central' "
234             . "x='$x' y='$y'>$i" if $i;
235 0           return "$data\n";
236             }
237              
238             =head1 SEE ALSO
239              
240             Lines consist of L instances.
241              
242             Use either L or L
243             to implement lines.
244              
245             =cut
246              
247             1;