File Coverage

blib/lib/OpenGL/PLG.pm
Criterion Covered Total %
statement 89 149 59.7
branch 34 64 53.1
condition 6 12 50.0
subroutine 13 19 68.4
pod 16 16 100.0
total 158 260 60.7


line stmt bran cond sub pod time code
1             package OpenGL::PLG;
2 3     3   99728 use Carp;
  3         8  
  3         375  
3              
4 3     3   19 use warnings;
  3         6  
  3         92  
5 3     3   16 use strict;
  3         9  
  3         16663  
6              
7             our $VERSION = '0.03';
8              
9              
10             # creates a new PoLyGon object
11             sub new {
12 1     1 1 11 my ($class) = @_;
13 1         4 my $self = bless {},$class;
14              
15 1         7 $self->{'vertices'} = [];
16 1         4 $self->{'polygons'} = [];
17              
18 1         4 return $self;
19             }
20              
21              
22             # reads a given PLG file
23             # and parse it into the object
24             # TODO: handle multiple PLG formats
25             sub parse_file {
26 0     0 1 0 my ($self, $filename) = @_;
27              
28 0 0       0 open my $fh, '<', $filename
29             or croak "error loading '$filename': $!\n";
30              
31 0         0 my $reading_vertexes = 1;
32              
33 0         0 while (my $line = <$fh>) {
34 0         0 my @fields = split /\s+/, $line;
35              
36             # if we reach an empty line,
37             # then what will follow is
38             # the list of positions for each
39             # collected vertex
40 0 0       0 if ( @fields == 0 ) {
41 0         0 $reading_vertexes = 0;
42 0         0 next;
43             }
44              
45             # if we are reading vertices,
46             # load them into our object
47 0 0       0 if ($reading_vertexes) {
48 0         0 $self->set_vertex(@fields);
49             }
50              
51             # otherwise, we are reading
52             # a polygon list (each of it
53             # using some of the vertices
54             # collected beforehand)
55             else {
56              
57             # validade the input
58 0 0       0 if ($fields[0] !~ m/^\d+$/ ) {
59 0         0 croak 'error parsing file. Expected single number of '
60             . "vertices in polygon, but got '@fields' instead\n";
61             }
62              
63 0         0 my $n_vertices = $fields[0];
64              
65             # retrieve the next line
66 0         0 $line = <$fh>;
67 0         0 @fields = split /\s+/, $line;
68              
69 0         0 $self->set_polygon($n_vertices, @fields);
70             }
71             }
72 0         0 close $fh;
73             }
74              
75              
76             # include a polygon shape into the object.
77             # the first parameter indicates the number
78             # of vertices in the polygon. Next follows
79             # the list of vertices ids. This function
80             # does not return anything, but croak's if
81             # the number of vertices is not the same as
82             # expected.
83             sub set_polygon {
84 4     4 1 3158 my $self = shift;
85 4         12 my ($n_vertices, @fields) = @_;
86              
87             # validade input (at least some of it)
88 4 100       10 if ( @fields != $n_vertices ) {
89 2         259 croak "error reading polygon input. Expected $n_vertices "
90             . 'vertices but got ' . scalar @fields . " instead.\n";
91             }
92             else {
93 2         3 push @{ $self->{'polygons'} }, [ @fields ];
  2         12  
94             }
95             }
96              
97             # deletes a given polygon by its id number.
98             # The first included polygon has id "1",
99             # while the last one is total_polygons().
100             # Circular references work, so you can
101             # get to the last polygon via a -1 id and
102             # so on. The id "0" does not exist. If you
103             # try to use it, you'll get an error.
104             # Also note that, once you delete a polygon,
105             # the id's of the polygons following it
106             # will be decreased by one.
107             sub delete_polygon {
108 3     3 1 2448 my ($self, $id) = @_;
109              
110 3 50       15 if ( not defined $id ) {
    100          
111 0         0 croak "delete_polygon needs a loaded polygon id\n";
112             }
113             elsif ( $id == 0 ) {
114 1         75 croak "polygon id cannot be zero\n";
115             }
116              
117             # corrects behavior of plg files,
118             # as they start with 1, not 0
119 2 50       8 $id-- if $id > 0;
120              
121 2 100       9 if ( not defined $self->{'polygons'}->[$id] ) {
122 1         113 croak "delete_polygon needs a loaded polygon id\n";
123             }
124             else {
125 1         3 splice @{ $self->{'polygons'} }, $id, 1;
  1         35  
126 1         4 return 1;
127             }
128            
129             }
130              
131              
132             # create a vertex in positions
133             # x, y, z. You can optionally specify an id
134             # for it ( >= 1 ), and PLG will put your
135             # vertex in that slot, replacing any vertex
136             # there (if available). Otherwise
137             # we'll place it in the last available
138             # id slot.
139             # TODO: croak if x,y,z values are not floats
140             # (regexp for -?\d+(?:\.\d+)?+e\d+ special float cases
141             # or something like that)
142             sub set_vertex {
143 11     11 1 2602 my $self = shift;
144 11         21 my ($x, $y, $z, $id) = @_;
145              
146             # if 'id' is not valid or doesn't exist,
147             # we'll use the last position on the array
148 11 100 66     90 if (not defined $id or $id !~ /^\d+$/ ) {
    100          
149 3         4 $id = @{ $self->{'vertices'} } + 1;
  3         8  
150             }
151             elsif ( $id == 0 ) {
152 2         190 croak "vertex id cannot be zero\n";
153             }
154              
155 9 100 33     50 if (not defined $x
      66        
156             or not defined $y
157             or not defined $z
158             ) {
159 2         153 croak "vertices must have all three coordinates (x, y, z)";
160             }
161              
162             # corrects behavior of plg files,
163             # as they start with 1, not 0
164 7 50       18 $id-- if $id > 0;
165              
166             # insert the vertex in the correct
167             # index (id) position
168 7         20 $self->{'vertices'}->[$id] = [$x, $y, $z];
169              
170 7         21 return $id;
171             }
172              
173              
174             # same as set_vertex, but lets you create
175             # several vertices at once.
176             sub set_vertices {
177 2     2 1 1213 my ($self, $v_ref) = @_;
178              
179 2 50 33     20 if (not defined $v_ref
180             or ref($v_ref) ne 'HASH') {
181 0         0 croak "set_vertices must receive a hash reference.\n";
182             }
183              
184 2         5 foreach (sort keys %{$v_ref} ) {
  2         15  
185 4         6 $self->set_vertex(@{$v_ref->{$_}}, $_);
  4         14  
186             }
187             }
188              
189              
190             # returns the total number of vertices
191             # in the object
192             sub total_vertices {
193 0     0 1 0 my $self = shift;
194 0         0 return scalar ( @{ $self->{'vertices'} } );
  0         0  
195             }
196              
197             # returns the total number of polygons
198             # in the object
199             sub total_polygons {
200 0     0 1 0 my $self = shift;
201 0         0 return scalar ( @{ $self->{'polygons'} } );
  0         0  
202             }
203              
204              
205             # retrieves a vertex by its id
206             sub get_vertex {
207 15     15 1 4893 my ($self, $id) = @_;
208              
209 15 50       48 if ( not defined $id ) {
    100          
210 0         0 croak "get_vertex needs a loaded vertex id\n";
211             }
212             elsif ( $id == 0 ) {
213 1         79 croak "vertex id cannot be zero\n";
214             }
215              
216             # corrects behavior of plg files,
217             # as they start with 1, not 0
218 14 50       36 $id-- if $id > 0;
219              
220 14 100       36 if ( not defined $self->{'vertices'}->[$id] ) {
221 2         169 croak "get_vertex needs a loaded vertex id\n";
222             }
223             else {
224 12         16 return @{ $self->{'vertices'}->[$id] };
  12         68  
225             }
226             }
227              
228              
229             # returns an array reference containing
230             # the coordinates of each vertex inside a
231             # given polygon
232             sub get_vertices_from_polygon {
233 3     3 1 1180 my ($self, $polygon) = @_;
234 3         8 my $vertices_ref = [];
235              
236 3         6 my $i = 0;
237 3         15 foreach ( $self->get_polygon($polygon) ) {
238 3         4 push @{$vertices_ref->[$i++]}, $self->get_vertex($_);
  3         9  
239             }
240 1         4 return $vertices_ref;
241             }
242              
243              
244             # deletes a given vertex by its id number.
245             # The first included vertex has id "1",
246             # while the last one is total_vertices().
247             # Circular references work, so you can
248             # get to the last vertex via a -1 id and
249             # so on. The id "0" does not exist. If you
250             # try to use it, you'll get an error.
251             # Also note that, once you delete a vertex,
252             # the id's of the vertices following it will
253             # be decreased by one.
254             sub delete_vertex {
255 3     3 1 1674 my ($self, $id) = @_;
256              
257 3 50       17 if ( not defined $id ) {
    100          
258 0         0 croak "delete_vertex needs a loaded vertex id\n";
259             }
260             elsif ( $id == 0 ) {
261 1         108 croak "vertex id cannot be zero\n";
262             }
263              
264             # corrects behavior of plg files,
265             # as they start with 1, not 0
266 2 50       24 $id-- if $id > 0;
267              
268 2 100       10 if ( not defined $self->{'vertices'}->[$id] ) {
269 1         90 croak "delete_vertex needs a loaded vertex id\n";
270             }
271             else {
272 1         2 splice @{ $self->{'vertices'} }, $id, 1;
  1         4  
273 1         4 return 1;
274             }
275             }
276              
277              
278             # retrieves a polygon by its id
279             sub get_polygon {
280 9     9 1 2471 my ($self, $id) = @_;
281              
282 9 50       36 if ( not defined $id ) {
    100          
283 0         0 croak "get_polygon needs a loaded polygon id\n";
284             }
285             elsif ( $id == 0 ) {
286 2         166 croak "polygon id cannot be zero\n";
287             }
288              
289             # corrects behavior of plg files,
290             # as they start with 1, not 0
291 7 50       18 $id-- if $id > 0;
292              
293 7 100       19 if ( not defined $self->{'polygons'}->[$id] ) {
294 3         377 croak "get_polygon needs a loaded polygon id\n";
295             }
296             else {
297 4         6 return @{ $self->{'polygons'}->[$id] };
  4         18  
298             }
299             }
300              
301              
302             # renders OpenGL code to display the
303             # object (i.e. the collection of polygons
304             # forming whatever-it-is-they-form).
305             # Note that using this outside of an
306             # OpenGL program will most likely *not*
307             # produce the expected results (see example
308             # in the USAGE section for a way to actually
309             # display the image on the screen). This
310             # method needs the Perl OpenGL library to work.
311             sub render {
312 0     0 1 0 my $self = shift;
313              
314 0         0 eval 'use OpenGL; 1';
315 0 0       0 if ($@) {
316 0         0 croak "Can't load Perl OpenGL: $@\n";
317             }
318              
319 0         0 foreach my $plg_ref ( @{$self->{'polygons'} } ) {
  0         0  
320              
321             # each polygon is represented
322             # independently
323 0         0 &OpenGL::glBegin(&OpenGL::GL_POLYGON);
324              
325 0         0 foreach my $vertex_id ( @{$plg_ref} ) {
  0         0  
326 0         0 my @v = $self->get_vertex($vertex_id);
327 0         0 &OpenGL::glVertex3f(@v);
328             }
329              
330 0         0 &OpenGL::glEnd;
331             }
332             }
333              
334             # dumps a string containing the OpenGL code
335             # to display the object (i.e. the collection
336             # of polygons forming whatever-it-is-they-form).
337             # Use this to see the code that will be produced
338             # by render() should you use it inside an OpenGL
339             # display. You do *NOT* need OpenGL to use this,
340             # as it will just output the code, not eval it.
341             sub dump_code {
342 1     1 1 837 my $self = shift;
343 1         4 my $dump = '';
344              
345 1         3 foreach my $plg_ref ( @{$self->{'polygons'} } ) {
  1         4  
346 1         4 $dump .= "glBegin(GL_POLYGON);\n";
347 1         3 foreach my $vertex_id ( @{$plg_ref} ) {
  1         4  
348 2         20 my $v = join ( ', ', $self->get_vertex($vertex_id) );
349 2         8 $dump .= " glVertex3f($v);\n";
350             }
351 1         4 $dump .= "glEnd();\n";
352             }
353              
354 1         5 return $dump;
355             }
356              
357              
358             # this is the same as dump_code(), but will write
359             # the string to a given file instead of giving
360             # it back to the user. This is useful if you want
361             # to use the OpenGL code in another language
362             # (like C or C++).
363             sub dump_code_to_file {
364 0     0 1   my ($self, $filename) = @_;
365              
366 0 0         open my $fh, '>', $filename
367             or croak "Error opening '$filename': $!\n";
368              
369 0 0         print $fh $self->dump()
370             or croak "Error writing to file '$filename': $!\n";
371              
372 0           close $fh;
373             }
374              
375             # this will write the object's vertices and
376             # polygons to a file, in raw PLG format. This
377             # is the exact reverse of parse_file().
378             # TODO: option to include vertices index next to them
379             # TODO: option to save in multiple PLG formats
380             sub write_to_file {
381 0     0 1   my ($self, $filename) = @_;
382              
383 0 0         open my $fh, '>', $filename
384             or croak "Error opening '$filename': $!\n";
385              
386             # write vertex data
387 0           my $i = 0;
388 0           while ($i < $self->total_vertices) {
389 0           my @vertex = $self->get_vertex($i);
390 0 0         next unless defined $vertex[0];
391 0           print $fh join (' ', @vertex ) . "\n";
392             }
393             continue {
394 0           $i++;
395             }
396              
397             # insert a blank line
398 0           print $fh "\n";
399              
400             # write polygon data
401 0           $i = 0;
402 0           while ($i < $self->total_polygons) {
403 0           my @polygon = $self->get_polygon($i);
404 0 0         next unless defined $polygon[0];
405              
406 0           print $fh scalar (@polygon) . "\n"
407             . join (' ', @polygon ) . "\n"
408             ;
409             }
410             continue {
411 0           $i++;
412             }
413 0           close $fh;
414             }
415              
416             42; # End of OpenGL::PLG
417              
418             __END__