File Coverage

blib/lib/Gesture/Simple/Gesture.pm
Criterion Covered Total %
statement 100 100 100.0
branch 11 12 91.6
condition 3 6 50.0
subroutine 15 15 100.0
pod 1 10 10.0
total 130 143 90.9


line stmt bran cond sub pod time code
1             package Gesture::Simple::Gesture;
2 1     1   10 use Any::Moose;
  1         1  
  1         7  
3              
4             has points => (
5             is => 'ro',
6             isa => 'ArrayRef',
7             required => 1,
8             );
9              
10 3     3   80 sub _set_points { $_[0]->{points} = $_[1] }
11              
12             sub BUILD {
13 3     3 1 211 my $self = shift;
14              
15 3         11 my $resampled = $self->resample;
16 3         12 my $rotated = $self->rotate_to_zero($resampled);
17 3         16 my $scaled = $self->scale_to_square($rotated);
18 3         13 my $translated = $self->translate_to_origin($scaled);
19              
20 3         13 $self->_set_points($translated);
21             }
22              
23 1     1   544 use constant resample_point_count => 64;
  1         2  
  1         389  
24              
25             sub resample {
26 3     3 0 4 my $self = shift;
27 3 50       4 my @points = @{ shift || $self->points };
  3         28  
28 3         7 my @new_points = $points[0];
29              
30 3         11 my $I = $self->path_length(\@points) / ($self->resample_point_count - 1);
31 3         5 my $D = 0;
32              
33 3         9 for (my $i = 1; $i < @points; ++$i) {
34 292         393 my ($a, $b) = @points[$i-1, $i];
35 292         478 my $d = $self->distance($a, $b);
36              
37 292 100       484 if ($D + $d >= $I) {
38 188         308 my $q_x = $a->[0] + (($I - $D) / $d) * ($b->[0] - $a->[0]);
39 188         269 my $q_y = $a->[1] + (($I - $D) / $d) * ($b->[1] - $a->[1]);
40 188         303 my $q = [$q_x, $q_y];
41              
42 188         233 push @new_points, $q;
43 188         246 splice @points, $i, 0, $q;
44 188         465 $D = 0;
45             }
46             else {
47 104         219 $D += $d;
48             }
49             }
50              
51 3         14 return \@new_points;
52             }
53              
54             sub path_length {
55 3     3 0 14 my $self = shift;
56 3         2 my $points = shift;
57              
58 3         6 my $length = 0;
59 3         8 for my $i (1 .. @$points - 1) {
60 104         109 my ($a, $b) = @{$points}[$i-1, $i];
  104         133  
61 104         179 $length += $self->distance($a, $b);
62             }
63              
64 3         18 return $length;
65             }
66              
67             sub distance {
68 8908     8908 0 11788 my (undef, $a, $b) = @_;
69 1     1   7 no warnings 'uninitialized';
  1         1  
  1         661  
70 8908         32150 return sqrt( ($a->[0] - $b->[0]) ** 2 + ($a->[1] - $b->[1]) ** 2 );
71             }
72              
73             sub centroid {
74 142     142 0 148 my $self = shift;
75 142         138 my $points = shift;
76              
77 142         183 my ($X, $Y) = (0, 0);
78              
79 142         256 for my $point (@$points) {
80 9028         8468 $X += $point->[0];
81 9028         9932 $Y += $point->[1];
82             }
83              
84 142         534 return [ $X / @$points, $Y / @$points ];
85             }
86              
87             sub rotate_to_zero {
88 3     3 0 3 my $self = shift;
89 3         4 my $points = shift;
90              
91 3         10 my $c = $self->centroid($points);
92 3         35 my $theta = atan2($c->[1] - $points->[0][1], $c->[0] - $points->[0][0]);
93              
94 3         11 return $self->rotate_by($points, -$theta);
95             }
96              
97             sub rotate_by {
98 136     136 0 159 my $self = shift;
99 136         127 my $points = shift;
100 136         157 my $theta = shift;
101              
102 136         247 my $c = $self->centroid($points);
103              
104 136         170 my @new_points;
105              
106 136         203 for my $point (@$points) {
107 8646         16558 my $x = ($point->[0] - $c->[0]) * cos($theta)
108             - ($point->[1] - $c->[1]) * sin($theta)
109             + $c->[0];
110              
111 8646         15318 my $y = ($point->[0] - $c->[0]) * sin($theta)
112             + ($point->[1] - $c->[1]) * cos($theta)
113             + $c->[1];
114              
115 8646         17423 push @new_points, [$x, $y];
116             }
117              
118 136         531 return \@new_points;
119             }
120              
121             sub scale_to_square {
122 3     3 0 4 my $self = shift;
123 3         4 my $points = shift;
124 3   50     14 my $size = shift || 100;
125              
126 3         3 my @new_points;
127              
128 3         9 my ($width, $height) = $self->bounding_box($points);
129              
130 3         4 for my $point (@$points) {
131 191         198 my $x = $point->[0] * ($size / $width);
132 191         170 my $y = $point->[1] * ($size / $height);
133              
134 191         367 push @new_points, [$x, $y];
135             }
136              
137 3         12 return \@new_points;
138             }
139              
140             sub bounding_box {
141 3     3 0 4 my $self = shift;
142 3         4 my $points = shift;
143              
144 3         4 my ($min_x, $min_y, $max_x, $max_y) = (@{ $points->[0] }) x 2;
  3         8  
145              
146 3         4 for my $point (@$points) {
147 191 100       273 $min_x = $point->[0] if $point->[0] < $min_x;
148 191 100       262 $min_y = $point->[1] if $point->[1] < $min_y;
149              
150 191 100       322 $max_x = $point->[0] if $point->[0] > $max_x;
151 191 100       308 $max_y = $point->[1] if $point->[1] > $max_y;
152             }
153              
154 3   50     11 my $width = ($max_x - $min_x) || 1;
155 3   50     6 my $height = ($max_y - $min_y) || 1;
156              
157 3         14 return ($width, $height);
158             }
159              
160             sub translate_to_origin {
161 3     3 0 4 my $self = shift;
162 3         3 my $points = shift;
163              
164 3         6 my $c = $self->centroid($points);
165 3         5 my @new_points;
166              
167 3         4 for my $point (@$points) {
168 191         201 my $x = $point->[0] - $c->[0];
169 191         198 my $y = $point->[1] - $c->[1];
170 191         412 push @new_points, [$x + 50, $y + 50];
171             }
172              
173 3         8 return \@new_points;
174             }
175              
176             __PACKAGE__->meta->make_immutable;
177 1     1   6 no Any::Moose;
  1         2  
  1         4  
178              
179             1;
180