File Coverage

blib/lib/Math/Fractal/Curve.pm
Criterion Covered Total %
statement 76 79 96.2
branch 12 16 75.0
condition 5 9 55.5
subroutine 9 9 100.0
pod 5 5 100.0
total 107 118 90.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Math::Fractal::Curve - Generate fractal curves
4              
5             =head1 SYNOPSIS
6              
7             use Math::Fractal::Curve;
8              
9             # This generates a von Koch-curve.
10             my $generator = [
11             [0, 0, 1/3, 0 ],
12             [1/3, 0, 1/2, sqrt(5)/6],
13             [1/2, sqrt(5)/6, 2/3, 0 ],
14             [2/3, 0, 1, 0 ],
15             ];
16             # $generator may also be an anonymous subroutine that returns a
17             # data structure like the above.
18            
19             # New curve generator
20             my $curve_gen = Math::Fractal::Curve->new(generator => $generator);
21            
22             # New curve
23             my $curve = $curve_gen->line(
24             start => [-2, 1],
25             end => [2, -1],
26             );
27            
28             my $edges = $curve->fractal($depth);
29             # (now containing array ref of array refs of x1,y1,x2,y2 coordinates)
30              
31             =head1 DESCRIPTION
32              
33             This module is intended to generate 2-dimensional fractal curves such as
34             the von Koch curve from simple generator functions.
35              
36             The fractals are generated by recursively replacing a distance with the
37             generator. Hence, the starting distance and the generator define such a
38             fractal curve. Generators describe what a given distance is going to be
39             replaced with in terms of lengths of the distance. For example,
40             a generator of ([0, 0, 1/3, 0], [2/3, 0, 1, 0]) describes a
41             Mid-third Cantor Set which means the the middle third of every distance
42             in the set is deleted. Syntax for generator data structures in the context
43             of this module is [[x1, y1, x2, y2], [X1, Y1, X2, Y2]] (array ref of array
44             refs of edge coordinates) where xn,yn are the two coordinate pairs
45             specifying the first edge a distance is to be replaced with
46             and Xn,Yn are the second edge. There may be any number of edges.
47              
48             For more telling examples, please have a thorough look at the examples
49             subdirectory that came with this distribution or look through the examples
50             page of this module on
51             http://steffen-mueller.net/modules/Math-Fractal-Curve/examples
52              
53             Furthermore, the generator may be either one of the aformentioned nested
54             array references, or it may be an anonymous subroutine that returns such
55             a data structure. This enables you to generate I fractal curves
56             or fractal curves whose trajectories depend on the distance any
57             generator is to replace, etc.
58              
59             While the above feature makes the probablistic / dynamic curves non-fractal,
60             they preserve some properties real fractals have. Please refer to the
61             literature mentioned under L for more information. The examples
62             subdirectory of the distribution also holds an example of a probalistic
63             von Koch-curve and a Koch curve whose excavation-direction (the direction
64             the triangle points at) depends on the orientation of the distance the
65             generator is applied to (spatial.pl).
66              
67             Generator subroutines are passed the curve object as first argument. They
68             may access any attributes of the curve segment they are applied to, but
69             most interestingly, they may access their {start} and {end} attributes that
70             hold array references [x,y] of the start- and end points of the distance
71             they are being applied to.
72              
73             =head2 EXPORT
74              
75             None.
76              
77             =head1 METHODS
78              
79             =cut
80              
81             package Math::Fractal::Curve;
82              
83 1     1   663 use 5.006;
  1         4  
  1         30  
84 1     1   4 use strict;
  1         1  
  1         24  
85 1     1   4 use warnings;
  1         2  
  1         25  
86              
87 1     1   3 use Carp;
  1         1  
  1         694  
88              
89             our $VERSION = '1.03';
90              
91              
92             =head2 Constructor new
93              
94             The new() constructor requires one named argument:
95              
96             generator => GENERATOR
97              
98             where GENERATOR may either be a generator-datastructure as described
99             earlier or a subroutine reference (or closure) that returns such a
100             data structure.
101              
102             Furthermore, new accepts any key/value pairs that will be made attributes
103             of the curve object.
104              
105             new() is both a class- and an object method and thus can be used to clone
106             existing curves. (And is internally used to do so.)
107              
108             =cut
109              
110             sub new {
111 4     4 1 767 my $proto = shift;
112 4   66     16 my $class = ref($proto) || $proto;
113              
114 4         5 my $self = {};
115 4 100       11 if (ref $proto) {
116 3         6 $self->{generator} = $proto->{generator};
117 3 100 66     21 if (exists $proto->{end} and exists $proto->{start}) {
118 2         3 $self->{end} = [@{$proto->{end}}];
  2         6  
119 2         3 $self->{start} = [@{$proto->{start}}];
  2         16  
120             }
121             }
122 4         15 for (my $i = 0; $i < @_; $i+=2) {
123 7         24 $self->{$_[$i]} = $_[$i+1];
124             }
125              
126 4         5 delete $self->{_edges};
127 4         10 bless $self => $class;
128              
129 4 50       10 if (not exists $self->{generator}) {
130 0         0 croak "You need to supply a generator subroutine.";
131             }
132            
133 4         10 return $self;
134             }
135              
136              
137              
138             =head2 Method line
139              
140             The line() method takes two required named arguments:
141              
142             start => [START_X, START_Y],
143             end => [END_X, END_Y ]
144              
145             where START_X, START_Y and END_X, END_Y are the coordinates of the
146             start- and end points of the distance to create the fractal curve from.
147              
148             line() stores this data in the {start} and {end} attributes of the
149             curve object.
150              
151             =cut
152              
153             sub line {
154 1     1 1 355 my $self = shift;
155 1         4 my %args = @_;
156 1         1 my $start = $args{start};
157 1         2 my $end = $args{end};
158            
159 1 50 33     6 if (not defined $start or not defined $end) {
160 0         0 croak "You need to supply start- and end point.";
161             }
162              
163 1         3 $self = $self->new(start => $start, end => $end);
164 1         4 return $self;
165             }
166              
167              
168              
169             =head2 Method recurse()
170              
171             The recurse() method applies the generator to the curve's distance
172             and returns a reference to an array of new curve objects that represent
173             the newly generated edges.
174              
175             =cut
176              
177             sub recurse {
178 2     2 1 347 my $self = shift;
179 2         5 my $edges = $self->edges();
180              
181 2         3 my $obj = [];
182 2         4 foreach my $e (@$edges) {
183 2         7 push @$obj, $self->new(
184             start => [$e->[0], $e->[1]],
185             end => [$e->[2], $e->[3]],
186             );
187             }
188            
189 2         7 return $obj;
190             }
191              
192              
193              
194             =head2 Method fractal()
195              
196             The fractal() method takes one argument: The recursion depth of the
197             discrete fractal representation. Obviously, the complexity is
198             Edges^Depth with Edges equal to the number of edges of the generator.
199              
200             fractal() returns a reference to an array of array references. These
201             referenced arrays contain (x1, y1, x2, y2) coordinates of edges.
202              
203             =cut
204              
205             sub fractal {
206 3     3 1 1012 my $self = shift;
207 3         3 my $depth = shift;
208              
209 3 50       8 croak "First argument must be recursion depth!" unless defined $depth;
210              
211 3 100       6 return [[@{$self->{start}}, @{$self->{end}}]] if $depth <= 0;
  1         3  
  1         3  
212            
213 2         2 $depth--;
214              
215 2         4 my $result = [$self];
216 2         4 foreach (1..$depth) {
217 1         2 my $temp = [];
218 1         2 foreach (@$result) {
219 1         2 push @$temp, @{$_->recurse()};
  1         2  
220             }
221 1         2 $result = $temp;
222             }
223              
224 2         4 @$result = map {@{$_->edges()}} @$result;
  2         3  
  2         3  
225            
226 2         4 return $result;
227             }
228              
229              
230              
231             =head2 Method edges()
232              
233             The edges() method returns a reference to an array of array references.
234             These referenced arrays contain (x1, y1, x2, y2) coordinates of the
235             edges that are generated by the generator from the curve's starting
236             edge.
237              
238             =cut
239              
240             sub edges {
241 5     5 1 346 my $self = shift;
242              
243 5 100       16 return $self->{_edges} if exists $self->{_edges};
244 2         4 my $edges;
245 2 50       6 if (ref $self->{generator} eq 'CODE') {
246 0         0 $edges = $self->{generator}->($self)
247             }
248             else {
249 2         3 $edges = $self->{generator};
250             }
251              
252 2         3 my $start = $self->{start};
253 2         2 my $end = $self->{end};
254            
255 2         5 my $vec = [
256             $end->[0] - $start->[0],
257             $end->[1] - $start->[1],
258             ];
259 2         12 my $len = sqrt(
260             $vec->[0]**2 +
261             $vec->[1]**2
262             );
263            
264 2         3 my $sin = $vec->[1]/$len;
265 2         3 my $cos = $vec->[0]/$len;
266              
267 2         3 my $edges_res = [];
268 2         3 foreach my $e (@$edges) {
269 2         7 my ($x1, $y1, $x2, $y2) = map $_*$len, @$e;
270              
271 2         11 push @$edges_res, [
272             $start->[0] + $x1*$cos - $y1*$sin,
273             $start->[1] + $x1*$sin + $y1*$cos,
274             $start->[0] + $x2*$cos - $y2*$sin,
275             $start->[1] + $x2*$sin + $y2*$cos
276             ];
277             }
278 2         4 $self->{_edges} = $edges_res;
279 2         8 return $edges_res;
280             }
281              
282              
283             1;
284             __END__