File Coverage

blib/lib/Vector/Object3D.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


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;