line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Vector::Object3D; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Vector::Object3D - Three-dimensional object type definitions and operations |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head2 SYNOPSIS |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use Vector::Object3D; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Create an instance of a class: |
12
|
|
|
|
|
|
|
my $object = Vector::Object3D->new(polygons => [$polygon1, $polygon2, $polygon3]); |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# Create a new object as a copy of an existing object: |
15
|
|
|
|
|
|
|
my $copy = $object->copy; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Get number of polygons that make up an object: |
18
|
|
|
|
|
|
|
my $num_faces = $object->num_faces; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# Get index of last polygon: |
21
|
|
|
|
|
|
|
my $last_face_index = $object->last_face_index; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# Get first polygon: |
24
|
|
|
|
|
|
|
my $polygon1 = $object->get_polygon(index => 0); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# Get last polygon: |
27
|
|
|
|
|
|
|
my $polygonn = $object->get_polygon(index => $last_face_index); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
# Get all polygons: |
30
|
|
|
|
|
|
|
my @polygons = $object->get_polygons; |
31
|
|
|
|
|
|
|
my @polygons = $object->get_polygons(mode => 'all'); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# Get visible polygons only: |
34
|
|
|
|
|
|
|
my $observer = Vector::Object3D::Point->new(x => 0, y => 0, z => 5); |
35
|
|
|
|
|
|
|
my @polygons = $object->get_polygons(mode => 'visible', observer => $observer); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Print out formatted object data: |
38
|
|
|
|
|
|
|
$object->print(fh => $fh, precision => $precision); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# Move object a constant distance in a specified direction: |
41
|
|
|
|
|
|
|
my $object_translated = $object->translate( |
42
|
|
|
|
|
|
|
shift_x => -2, |
43
|
|
|
|
|
|
|
shift_y => 1, |
44
|
|
|
|
|
|
|
shift_z => 3, |
45
|
|
|
|
|
|
|
); |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
# Enlarge, shrink or stretch object by a scale factor: |
48
|
|
|
|
|
|
|
my $object_scaled = $object->scale( |
49
|
|
|
|
|
|
|
scale_x => 2, |
50
|
|
|
|
|
|
|
scale_y => 2, |
51
|
|
|
|
|
|
|
scale_z => 3, |
52
|
|
|
|
|
|
|
); |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# Rotate object by a given angle around three rotation axis: |
55
|
|
|
|
|
|
|
my $object_rotated = $object->rotate( |
56
|
|
|
|
|
|
|
rotate_xy => 30 * ($pi / 180), |
57
|
|
|
|
|
|
|
rotate_yz => -30 * ($pi / 180), |
58
|
|
|
|
|
|
|
rotate_xz => 45 * ($pi / 180), |
59
|
|
|
|
|
|
|
); |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
# Project object onto a two-dimensional plane using an orthographic projection: |
62
|
|
|
|
|
|
|
my $object2D = $object->cast(type => 'parallel'); |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# Project object onto a two-dimensional plane using a perspective projection: |
65
|
|
|
|
|
|
|
my $distance = 5; |
66
|
|
|
|
|
|
|
my $object2D = $object->cast(type => 'perspective', distance => $distance); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Compare two objects: |
69
|
|
|
|
|
|
|
my $are_the_same = $object1 == $object2; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 DESCRIPTION |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
C<Vector::Object3D> provides an abstraction layer for describing objects made of polygons in a three-dimensional space. It has been primarily designed to help with rapid prototyping of simple 3D vector graphic transformations, and is most likely unsuitable for realtime calculations that usually demand high computational CPU power. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
This version of C<Vector::Object3D> package has been entirely rewritten using Moose object system and is significantly slower than its predecessor initially developed using classic Perl's object system. Main reasoning for switching over to Moose was my desire to comply with the concepts of modern Perl programming. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head1 METHODS |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head2 new |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Create an instance of a C<Vector::Object3D> class: |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
my $object = Vector::Object3D->new(polygons => [$polygon1, $polygon2, $polygon3]); |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
C<Vector::Object3D> require provision of at least one polygon in order to successfully construct an object instance, there is no exception from this rule. |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=cut |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
our $VERSION = '0.01'; |
90
|
|
|
|
|
|
|
|
91
|
1
|
|
|
1
|
|
25939
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
41
|
|
92
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
31
|
|
93
|
|
|
|
|
|
|
|
94
|
1
|
|
|
1
|
|
501
|
use Moose; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
use Carp qw(croak); |
97
|
|
|
|
|
|
|
use Scalar::Util qw(looks_like_number); |
98
|
|
|
|
|
|
|
use Vector::Object3D::Polygon; |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
use overload |
101
|
|
|
|
|
|
|
'==' => \&_comparison, |
102
|
|
|
|
|
|
|
'!=' => \&_negative_comparison; |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
has 'polygons' => ( |
105
|
|
|
|
|
|
|
is => 'ro', |
106
|
|
|
|
|
|
|
isa => 'ArrayRef[Vector::Object3D::Polygon]', |
107
|
|
|
|
|
|
|
reader => '_get_polygons', |
108
|
|
|
|
|
|
|
required => 1, |
109
|
|
|
|
|
|
|
); |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
around BUILDARGS => sub { |
112
|
|
|
|
|
|
|
my ($orig, $class, %args) = @_; |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
my $polygons_orig = $args{polygons}; |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
my @polygons_copy; |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
if (defined $polygons_orig and ref $polygons_orig eq 'ARRAY') { |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
for my $polygon (@{$polygons_orig}) { |
121
|
|
|
|
|
|
|
push @polygons_copy, $polygon->copy; |
122
|
|
|
|
|
|
|
} |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
$args{polygons} = \@polygons_copy; |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
return $class->$orig(%args); |
128
|
|
|
|
|
|
|
}; |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub BUILD { |
131
|
|
|
|
|
|
|
my ($self) = @_; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
my $num_faces = $self->num_faces; |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
if ($num_faces < 1) { |
136
|
|
|
|
|
|
|
croak qq{Insufficient number of polygons used to initialize object: $num_faces (expected at least 1 polygon)}; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
return; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head2 copy |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
Create a new C<Vector::Object3D> object as a copy of an existing object: |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
my $copy = $object->copy; |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=cut |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
sub copy { |
151
|
|
|
|
|
|
|
my ($self) = @_; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
my $class = $self->meta->name; |
156
|
|
|
|
|
|
|
my $copy = $class->new(polygons => $polygons); |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
return $copy; |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head2 num_faces |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Get number of polygons that make up an object: |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
my $num_faces = $object->num_faces; |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=cut |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub num_faces { |
170
|
|
|
|
|
|
|
my ($self) = @_; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
my $faces = $self->_get_polygons; |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
return scalar @{$faces}; |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=head2 last_face_index |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
Get index of last polygon: |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
my $last_face_index = $object->last_face_index; |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=cut |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub last_face_index { |
186
|
|
|
|
|
|
|
my ($self) = @_; |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
my $faces = $self->_get_polygons; |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
return $#{$faces}; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=head2 get_polygon |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
Get C<$n>-th polygon, where C<$n> is expected to be any number between first and last polygon index: |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
my $polygonn = $object->get_polygon(index => $n); |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=cut |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub get_polygon { |
202
|
|
|
|
|
|
|
my ($self, %args) = @_; |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
my $index = $args{index}; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
unless (looks_like_number $index) { |
207
|
|
|
|
|
|
|
croak qq{Unable to get polygon with a non-numeric index value: $index}; |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
if ($index < 0) { |
211
|
|
|
|
|
|
|
croak qq{Unable to get polygon with index value below acceptable range: $index}; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
if ($index > $self->last_face_index) { |
215
|
|
|
|
|
|
|
croak qq{Unable to get polygon with index value beyond acceptable range: $index}; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
my @polygons = $self->get_polygons; |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
return $polygons[$index]; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head2 get_polygons |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Get all polygons: |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
my @polygons = $object->get_polygons; |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
The same effect is achieved by explicitly setting mode of getting polygons to C<all>: |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
my @polygons = $object->get_polygons(mode => 'all'); |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Get visible polygons only by setting mode of getting polygons to C<visible> and specifying optional observer: |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
my $observer = Vector::Object3D::Point->new(x => 0, y => 0, z => 5); |
236
|
|
|
|
|
|
|
my @polygons = $object->get_polygons(mode => 'visible', observer => $observer); |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=cut |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
sub get_polygons { |
241
|
|
|
|
|
|
|
my ($self, %args) = @_; |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
my $mode = $args{mode} || 'all'; |
244
|
|
|
|
|
|
|
my $observer = $args{observer}; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
unless (grep { $mode eq $_ } qw/all visible/) { |
247
|
|
|
|
|
|
|
croak qq{Invalid mode used to get polygons: $mode}; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
my @polygons = map { $_->copy } grep { |
253
|
|
|
|
|
|
|
if ($mode eq 'visible') { |
254
|
|
|
|
|
|
|
$_->is_plane_visible(observer => $observer); |
255
|
|
|
|
|
|
|
} |
256
|
|
|
|
|
|
|
else { |
257
|
|
|
|
|
|
|
1; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
} @{$polygons}; |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
return @polygons; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=head2 print |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
Print out text-formatted object data (which might be, for instance, useful for debugging purposes): |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
$object->print(fh => $fh, precision => $precision); |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
C<fh> defaults to the standard output. C<precision> is intended for internal use by string format specifier that outputs individual point coordinates as decimal floating points, and defaults to 2 (unless adjusted individually for each vertex). |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=cut |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub print { |
275
|
|
|
|
|
|
|
my ($self, %args) = @_; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
my $fh = $args{fh} || *STDOUT; |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
my $stdout = select $fh; |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
my $num_faces = $self->num_faces; |
284
|
|
|
|
|
|
|
my $num_length = length $num_faces; |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
for (my $i = 0; $i < @{$polygons}; $i++) { |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
my $polygon = $polygons->[$i]; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
printf "\nPolygon %0${num_length}d/%0${num_length}d:", $i + 1, $num_faces; |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
$polygon->print(%args); |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
select $stdout; |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
return; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=head2 |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
Move object a constant distance in a specified direction: |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
my $object_translated = $object->translate( |
305
|
|
|
|
|
|
|
shift_x => -2, |
306
|
|
|
|
|
|
|
shift_y => 1, |
307
|
|
|
|
|
|
|
shift_z => 3, |
308
|
|
|
|
|
|
|
); |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
=cut |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
sub translate { |
313
|
|
|
|
|
|
|
my ($self, %args) = @_; |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
my @new_polygons; |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
for my $polygon (@{$polygons}) { |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
push @new_polygons, $polygon->translate(%args); |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
my $object_translated = $self->new(polygons => \@new_polygons); |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
return $object_translated; |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=head2 |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
Enlarge, shrink or stretch object by a scale factor: |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
my $object_scaled = $object->scale( |
334
|
|
|
|
|
|
|
scale_x => 2, |
335
|
|
|
|
|
|
|
scale_y => 2, |
336
|
|
|
|
|
|
|
scale_z => 3, |
337
|
|
|
|
|
|
|
); |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=cut |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub scale { |
342
|
|
|
|
|
|
|
my ($self, %args) = @_; |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
my @new_polygons; |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
for my $polygon (@{$polygons}) { |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
push @new_polygons, $polygon->scale(%args); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
my $object_scaled = $self->new(polygons => \@new_polygons); |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
return $object_scaled; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=head2 |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
Rotate object by a given angle around three rotation axis: |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
my $object_rotated = $object->rotate( |
363
|
|
|
|
|
|
|
rotate_xy => 30 * ($pi / 180), |
364
|
|
|
|
|
|
|
rotate_yz => -30 * ($pi / 180), |
365
|
|
|
|
|
|
|
rotate_xz => 45 * ($pi / 180), |
366
|
|
|
|
|
|
|
); |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=cut |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
sub rotate { |
371
|
|
|
|
|
|
|
my ($self, %args) = @_; |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
my @new_polygons; |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
for my $polygon (@{$polygons}) { |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
push @new_polygons, $polygon->rotate(%args); |
380
|
|
|
|
|
|
|
} |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
my $object_rotated = $self->new(polygons => \@new_polygons); |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
return $object_rotated; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=head2 |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
Project object onto a two-dimensional plane using an orthographic projection: |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
my $object2D = $object->cast(type => 'parallel'); |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
Project object onto a two-dimensional plane using a perspective projection: |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
my $distance = 5; |
396
|
|
|
|
|
|
|
my $object2D = $object->cast(type => 'perspective', distance => $distance); |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
=cut |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
sub cast { |
401
|
|
|
|
|
|
|
my ($self, %args) = @_; |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
my $polygons = $self->_get_polygons; |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
my @new_polygons; |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
for my $polygon (@{$polygons}) { |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
push @new_polygons, $polygon->cast(%args); |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
my $object_casted = $self->new(polygons => \@new_polygons); |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
return $object_casted; |
415
|
|
|
|
|
|
|
} |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=head2 compare (==) |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
Compare two objects: |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
my $are_the_same = $object1 == $object2; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
Overloaded comparison operator evaluates to true whenever two object objects are identical (all their endpoints are located at exactly same positions, note that polygon order matters as well). |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
=cut |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
sub _comparison { |
428
|
|
|
|
|
|
|
my ($self, $arg) = @_; |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
my $polygons1 = $self->_get_polygons; |
431
|
|
|
|
|
|
|
my $polygons2 = $arg->_get_polygons; |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
return unless @{$polygons1} == @{$polygons2}; |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
for (my $i = 0; $i < @{$polygons1}; $i++) { |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
my $polygon1 = $polygons1->[$i]; |
438
|
|
|
|
|
|
|
my $polygon2 = $polygons2->[$i]; |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
return unless $polygon1 == $polygon2; |
441
|
|
|
|
|
|
|
} |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
return 1; |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
sub _negative_comparison { |
447
|
|
|
|
|
|
|
my ($self, $arg) = @_; |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
return not $self->_comparison($arg); |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=head1 BUGS |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
There are no known bugs at the moment. Please report any bugs or feature requests. |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=head1 EXPORT |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
C<Vector::Object3D> exports nothing neither by default nor explicitly. |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=head1 SEE ALSO |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
L<Vector::Object3D::Examples>, L<Vector::Object3D::Point>, L<Vector::Object3D::Polygon>. |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
=head1 AUTHOR |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
Pawel Krol, E<lt>pawelkrol@cpan.orgE<gt>. |
467
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
=head1 VERSION |
469
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
Version 0.01 (2012-12-24) |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
Copyright (C) 2012 by Pawel Krol. |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
This library is free open source software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available. |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
PLEASE NOTE THAT IT COMES WITHOUT A WARRANTY OF ANY KIND! |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=cut |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
no Moose; |
483
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
484
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
1; |