| 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__ |