File Coverage

blib/lib/Game/TextMapper/Line.pm
Criterion Covered Total %
statement 52 102 50.9
branch 11 34 32.3
condition 1 15 6.6
subroutine 7 9 77.7
pod 3 6 50.0
total 74 166 44.5


line stmt bran cond sub pod time code
1             # Copyright (C) 2009-2021 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 1     1   8 use Modern::Perl '2018';
  1         2  
  1         8  
50 1     1   155 use Mojo::Util qw(url_escape);
  1         1  
  1         85  
51 1     1   7 use Mojo::Base -base;
  1         2  
  1         7  
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              
73             =head1 METHODS
74              
75             =head2 compute_missing_points
76              
77             Compute the missing points between the points in C and return it.
78              
79             =cut
80              
81             sub compute_missing_points {
82 328     328 1 599 my $self = shift;
83 328         588 my $i = 0;
84 328         721 my $current = $self->points->[$i++];
85 328         1627 my $z = $current->z;
86 328         1384 my @result = ($current);
87 328         877 while ($self->points->[$i]) {
88 2980         45150 $current = $self->one_step($current, $self->points->[$i]);
89 2980 50       8685 return unless $z == $current->z; # must all be on the same plane
90 2980         11402 push(@result, $current);
91 2980 100       4679 $i++ if $current->equal($self->points->[$i]);
92             }
93              
94 328         5975 return @result;
95             }
96              
97             sub partway {
98 6288     6288 0 8793 my ($self, $from, $to, $q) = @_;
99 6288         11214 my ($x1, $y1) = $self->pixels($from);
100 6288         11800 my ($x2, $y2) = $self->pixels($to);
101 6288   50     9995 $q ||= 1;
102 6288 50       8710 return $x1 + ($x2 - $x1) * $q, $y1 + ($y2 - $y1) * $q if wantarray;
103 6288         31646 return sprintf("%.1f,%.1f", $x1 + ($x2 - $x1) * $q, $y1 + ($y2 - $y1) * $q);
104             }
105              
106             =head2 svg($offset)
107              
108             This returns an SVG fragment, a string with a C.
109              
110             =cut
111              
112             sub svg {
113 328     328 1 1837 my ($self, $offset) = @_;
114 328         565 my ($path, $current, $next, $closed);
115 328         935 $self->offset($offset);
116 328         2072 my @points = $self->compute_missing_points();
117 328 50       941 return '' unless @points;
118 328 50       905 if ($points[0]->equal($points[$#points])) {
119 0         0 $closed = 1;
120             }
121              
122 328 50       2569 if ($closed) {
123 0         0 for my $i (0 .. $#points - 1) {
124 0         0 $current = $points[$i];
125 0         0 $next = $points[$i+1];
126 0 0       0 if (!$path) {
127 0         0 my $a = $self->partway($current, $next, 0.3);
128 0         0 my $b = $self->partway($current, $next, 0.5);
129 0         0 my $c = $self->partway($points[$#points-1], $current, 0.7);
130 0         0 my $d = $self->partway($points[$#points-1], $current, 0.5);
131 0         0 $path = "M$d C$c $a $b";
132             } else {
133             # continue curve
134 0         0 my $a = $self->partway($current, $next, 0.3);
135 0         0 my $b = $self->partway($current, $next, 0.5);
136 0         0 $path .= " S$a $b";
137             }
138             }
139             } else {
140 328         829 for my $i (0 .. $#points - 1) {
141 2980         3829 $current = $points[$i];
142 2980         3754 $next = $points[$i+1];
143 2980 100       4455 if (!$path) {
144             # line from a to b; control point a required for following S commands
145 328         681 my $a = $self->partway($current, $next, 0.3);
146 328         689 my $b = $self->partway($current, $next, 0.5);
147 328         1009 $path = "M$a C$b $a $b";
148             } else {
149             # continue curve
150 2652         4034 my $a = $self->partway($current, $next, 0.3);
151 2652         4911 my $b = $self->partway($current, $next, 0.5);
152 2652         5750 $path .= " S$a $b";
153             }
154             }
155             # end with a little stub
156 328         743 $path .= " L" . $self->partway($current, $next, 0.7);
157             }
158              
159 328         842 my $id = $self->id;
160 328         1471 my $type = $self->type;
161 328         1764 my $attributes = $self->map->path_attributes->{$type};
162 328         3788 my $data = qq{ \n};
163 328 50       840 $data .= $self->debug($closed) if $debug;
164 328         3716 return $data;
165             }
166              
167             =head2 svg_label
168              
169             This returns an SVG fragment, a group C with C and a C
170             element.
171              
172             =cut
173              
174             sub svg_label {
175 328     328 1 1104 my ($self) = @_;
176 328 50       459 return '' unless defined $self->label;
177 0           my $id = $self->id;
178 0           my $label = $self->label;
179 0   0       my $attributes = $self->map->label_attributes || "";
180 0   0       my $glow = $self->map->glow_attributes || "";
181 0           my $url = $self->map->url;
182 0 0 0       $url =~ s/\%s/url_escape($self->label)/e or $url .= url_escape($self->label) if $url;
  0            
183             # default is left, but if the line goes from right to left, then "left" means "upside down"
184 0           my $side = '';
185 0 0 0       if ($self->points->[1]->x < $self->points->[0]->x
      0        
186 0           or $#{$self->points} >= 2 and $self->points->[2]->x < $self->points->[0]->x) {
187 0           $side = ' side="right"';
188             }
189 0           my $data = qq{ \n};
190 0 0         $data .= qq{ $label\n} if $glow;
191 0 0         $data .= qq{ } if $url;
192 0           $data .= qq{ $label\n};
193 0 0         $data .= qq{ } if $url;
194 0           $data .= qq{ \n};
195 0           return $data;
196             }
197              
198             sub debug {
199 0     0 0   my ($self, $closed) = @_;
200 0           my ($data, $current, $next);
201 0           my @points = $self->compute_missing_points();
202 0           for my $i (0 .. $#points - 1) {
203 0           $current = $points[$i];
204 0           $next = $points[$i+1];
205 0           $data .= circle($self->pixels($current), 15, $i++);
206 0           $data .= circle($self->partway($current, $next, 0.3), 3, 'a');
207 0           $data .= circle($self->partway($current, $next, 0.5), 5, 'b');
208 0           $data .= circle($self->partway($current, $next, 0.7), 3, 'c');
209             }
210 0           $data .= circle($self->pixels($next), 15, $#points);
211              
212 0           my ($x, $y) = $self->pixels($points[0]); $y += 30;
  0            
213 0 0         $data .= "
214             . "text-anchor='middle' dominant-baseline='central' "
215             . "x='$x' y='$y'>closed"
216             if $closed;
217              
218 0           return $data;
219             }
220              
221             sub circle {
222 0     0 0   my ($x, $y, $r, $i) = @_;
223 0           my $data = "";
224 0 0         $data .= "
225             . "text-anchor='middle' dominant-baseline='central' "
226             . "x='$x' y='$y'>$i" if $i;
227 0           return "$data\n";
228             }
229              
230             =head1 SEE ALSO
231              
232             Lines consist of L instances.
233              
234             Use either L or L
235             to implement lines.
236              
237             =cut
238              
239             1;