2 # Copyright © 2003 Jamie Zawinski <jwz@jwz.org>
4 # Permission to use, copy, modify, distribute, and sell this software and its
5 # documentation for any purpose is hereby granted without fee, provided that
6 # the above copyright notice appear in all copies and that both that
7 # copyright notice and this permission notice appear in supporting
8 # documentation. No representations are made about the suitability of this
9 # software for any purpose. It is provided "as is" without express or
12 # Reads a DXF file, and emits C data suitable for use with OpenGL's
13 # glInterleavedArrays() and glDrawArrays() routines.
17 # --normalize Compute the bounding box of the object, and scale all
18 # coordinates so that the object fits inside a unit cube.
20 # --smooth When computing normals for the vertexes, average the
21 # normals at any edge which is less than 90 degrees.
22 # If this option is not specified, planar normals will be
23 # used, resulting in a "faceted" object.
25 # Created: 8-Mar-2003.
31 my $progname = $0; $progname =~ s@.*/@@g;
32 my $version = q{ $Revision: 1.2 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
37 # convert a vector to a unit vector
40 my $L = sqrt (($x * $x) + ($y * $y) + ($z * $z));
52 # Calculate the unit normal at p0 given two other points p1,p2 on the
53 # surface. The normal points in the direction of p1 crossproduct p2.
58 $p2x, $p2y, $p2z) = @_;
61 my ($pax, $pay, $paz);
62 my ($pbx, $pby, $pbz);
70 $nx = $pay * $pbz - $paz * $pby;
71 $ny = $paz * $pbx - $pax * $pbz;
72 $nz = $pax * $pby - $pay * $pbx;
74 return (normalize ($nx, $ny, $nz));
78 # why this isn't in perlfunc, I don't know.
79 sub acos { atan2( sqrt(1 - $_[0] * $_[0]), $_[0] ) }
80 my $pi = 3.141592653589793;
81 my $radians_to_degrees = 180.0 / $pi;
83 # Calculate the angle (in degrees) between two vectors.
89 my $L1 = sqrt ($x1*$x1 + $y1*$y1 + $z1*$z1);
90 my $L2 = sqrt ($x2*$x2 + $y2*$y2 + $z2*$z2);
92 return 0 if ($L1 == 0 || $L2 == 0);
93 return 0 if ($x1 == $x2 && $y1 == $y2 && $z1 == $z2);
95 # dot product of two vectors is defined as:
96 # $L1 * $L1 * cos(angle between vectors)
97 # and is also defined as:
98 # $x1*$x2 + $y1*$y2 + $z1*$z2
100 # $L1 * $L1 * cos($angle) = $x1*$x2 + $y1*$y2 + $z1*$z2
101 # cos($angle) = ($x1*$x2 + $y1*$y2 + $z1*$z2) / ($L1 * $L2)
102 # $angle = acos (($x1*$x2 + $y1*$y2 + $z1*$z2) / ($L1 * $L2));
104 my $cos = ($x1*$x2 + $y1*$y2 + $z1*$z2) / ($L1 * $L2);
105 $cos = 1 if ($cos > 1); # avoid fp rounding error (1.000001 => sqrt error)
106 my $angle = acos ($cos);
108 return ($angle * $radians_to_degrees);
111 # given a list of triangles ( [ X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, ]+ )
112 # returns a list of the normals for each vertex.
114 sub compute_vertex_normals {
116 my $npoints = ($#points+1) / 3;
117 my $nfaces = $npoints / 3;
119 my @face_normals = ();
122 for (my $i = 0; $i < $nfaces; $i++) {
123 my ($ax, $ay, $az, $bx, $by, $bz, $cx, $cy, $cz) =
124 @points[($i*9) .. ($i*9)+8];
126 # store the normal for each face in the $face_normals array
127 # indexed by face number.
129 my @norm = face_normal ($ax, $ay, $az,
132 $face_normals[$i] = \@norm;
134 # store in the %point_faces hash table a list of every face number
135 # in which a point participates
141 @flist = (defined($point_faces{$p}) ? @{$point_faces{$p}} : ());
143 $point_faces{$p} = \@flist;
146 @flist = (defined($point_faces{$p}) ? @{$point_faces{$p}} : ());
148 $point_faces{$p} = \@flist;
151 @flist = (defined($point_faces{$p}) ? @{$point_faces{$p}} : ());
153 $point_faces{$p} = \@flist;
157 # compute the normal for each vertex of each face.
158 # (these points are not unique -- because there might be multiple
159 # normals associated with the same vertex for different faces,
160 # in the case where it's a sharp angle.)
163 for (my $i = 0; $i < $nfaces; $i++) {
164 my @verts = @points[($i*9) .. ($i*9)+8];
165 error ("overshot in points?") unless defined($verts[8]);
167 my @norm = @{$face_normals[$i]};
168 error ("no normal $i?") unless defined($norm[2]);
170 # iterate over the (three) vertexes in this face.
172 for (my $j = 0; $j < 3; $j++) {
173 my ($x, $y, $z) = @verts[($j*3) .. ($j*3)+2];
174 error ("overshot in verts?") unless defined($z);
176 # Iterate over the faces in which this point participates.
177 # This face's normal is the average of the normals of those faces.
178 # Except, faces are ignored if any point in them is at more than
179 # a 90 degree angle from the zeroth face (arbitrarily.)
181 my ($nx, $ny, $nz) = (0, 0, 0);
182 my @faces = @{$point_faces{"$x $y $z"}};
183 foreach my $fn (@faces) {
184 my ($ax, $ay, $az, $bx, $by, $bz, $cx, $cy, $cz) =
185 @points[($fn*9) .. ($fn*9)+8];
186 my @fnorm = @{$face_normals[$fn]};
188 # ignore any adjascent faces that are more than 90 degrees off.
189 my $angle = vector_angle (@norm, @fnorm);
190 next if ($angle >= 90);
197 push @normals, normalize ($nx, $ny, $nz);
207 my ($filename, $dxf, $normalize_p) = @_;
210 my ($points_txt, $coords_txt);
213 $dxf =~ s/([^\n]*)\n([^\n]*)\n/$1\t$2\n/g; # join even and odd lines
217 my @items = split (/\n/, $dxf);
218 while ($#items >= 0) {
222 if (m/^\s* 0 \s+ SECTION \b/x) {
223 } elsif (m/^\s* 2 \s+ HEADER \b/x) {
224 } elsif (m/^\s* 0 \s+ ENDSEC \b/x) {
225 } elsif (m/^\s* 2 \s+ ENTITIES \b/x) {
226 } elsif (m/^\s* 0 \s+ EOF \b/x) {
227 } elsif (m/^\s* 0 \s+ 3DFACE \b/x) {
232 while ($#items >= 0) {
233 $_ = shift @items; # get next line
235 my $d = '(-?\d+\.?\d+)';
236 if (m/^\s* 8 \b/x) { # layer name
237 } elsif (m/^\s* 62 \b/x) { # color number
239 } elsif (m/^\s* 10 \s+ $d/xo) { $pc++; $points[ 0] = $1; # X1
240 } elsif (m/^\s* 20 \s+ $d/xo) { $pc++; $points[ 1] = $1; # Y1
241 } elsif (m/^\s* 30 \s+ $d/xo) { $pc++; $points[ 2] = $1; # Z1
243 } elsif (m/^\s* 11 \s+ $d/xo) { $pc++; $points[ 3] = $1; # X2
244 } elsif (m/^\s* 21 \s+ $d/xo) { $pc++; $points[ 4] = $1; # Y2
245 } elsif (m/^\s* 31 \s+ $d/xo) { $pc++; $points[ 5] = $1; # Z2
247 } elsif (m/^\s* 12 \s+ $d/xo) { $pc++; $points[ 6] = $1; # X3
248 } elsif (m/^\s* 22 \s+ $d/xo) { $pc++; $points[ 7] = $1; # Y3
249 } elsif (m/^\s* 32 \s+ $d/xo) { $pc++; $points[ 8] = $1; # Z3
251 } elsif (m/^\s* 13 \s+ $d/xo) { $pc++; $points[ 9] = $1; # X4
252 } elsif (m/^\s* 23 \s+ $d/xo) { $pc++; $points[10] = $1; # Y4
253 } elsif (m/^\s* 33 \s+ $d/xo) { $pc++; $points[11] = $1; # Z4
255 error ("$filename: unknown 3DFACE entry: $_\n");
261 if ($points[6] != $points[9] ||
262 $points[7] != $points[10] ||
263 $points[8] != $points[11]) {
264 error ("$filename: got a quad, not a triangle\n");
266 @points = @points[0 .. 8];
269 foreach (@points) { $_ += 0; } # convert strings to numbers
271 push @triangles, @points;
274 error ("$filename: unknown: $_\n");
279 my $npoints = ($#triangles+1) / 3;
282 # find bounding box, and normalize
284 if ($normalize_p || $verbose) {
285 my $minx = 999999999;
286 my $miny = 999999999;
287 my $minz = 999999999;
288 my $maxx = -999999999;
289 my $maxy = -999999999;
290 my $maxz = -999999999;
292 foreach my $n (@triangles) {
293 if ($i == 0) { $minx = $n if ($n < $minx);
294 $maxx = $n if ($n > $maxx); }
295 elsif ($i == 1) { $miny = $n if ($n < $miny);
296 $maxy = $n if ($n > $maxy); }
297 else { $minz = $n if ($n < $minz);
298 $maxz = $n if ($n > $maxz); }
299 $i = 0 if (++$i == 3);
302 my $w = ($maxx - $minx);
303 my $h = ($maxy - $miny);
304 my $d = ($maxz - $minz);
305 my $sizea = ($w > $h ? $w : $h);
306 my $sizeb = ($w > $d ? $w : $d);
307 my $size = ($sizea > $sizeb ? $sizea : $sizeb);
309 print STDERR "$progname: $filename: bbox is " .
310 sprintf("%.2f x %.2f x %.2f\n", $w, $h, $d)
317 print STDERR "$progname: $filename: dividing by $size for bbox of " .
318 sprintf("%.2f x %.2f x %.2f\n", $w, $h, $d)
320 foreach my $n (@triangles) {
331 my ($filename, $smooth_p, @points) = @_;
333 my $ccw_p = 1; # counter-clockwise winding rule for computing normals
337 $code .= "#include \"gllist.h\"\n";
338 $code .= "static const float data[]={\n";
340 my $npoints = ($#points + 1) / 3;
341 my $nfaces = $npoints / 3;
345 @normals = compute_vertex_normals (@points);
347 if ($#normals != $#points) {
348 error ("computed " . (($#normals+1)/3) . " normals for " .
349 (($#points+1)/3) . " points?");
353 for (my $i = 0; $i < $nfaces; $i++) {
354 my $ax = $points[$i*9];
355 my $ay = $points[$i*9+1];
356 my $az = $points[$i*9+2];
358 my $bx = $points[$i*9+3];
359 my $by = $points[$i*9+4];
360 my $bz = $points[$i*9+5];
362 my $cx = $points[$i*9+6];
363 my $cy = $points[$i*9+7];
364 my $cz = $points[$i*9+8];
366 my ($nax, $nay, $naz,
371 $nax = $normals[$i*9];
372 $nay = $normals[$i*9+1];
373 $naz = $normals[$i*9+2];
375 $nbx = $normals[$i*9+3];
376 $nby = $normals[$i*9+4];
377 $nbz = $normals[$i*9+5];
379 $ncx = $normals[$i*9+6];
380 $ncy = $normals[$i*9+7];
381 $ncz = $normals[$i*9+8];
385 ($nax, $nay, $naz) = face_normal ($ax, $ay, $az,
389 ($nax, $nay, $naz) = face_normal ($ax, $ay, $az,
393 ($nbx, $nby, $nbz) = ($nax, $nay, $naz);
394 ($ncx, $ncy, $ncz) = ($nax, $nay, $naz);
397 my $lines = sprintf("\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n" .
398 "\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n" .
399 "\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n",
400 $nax, $nay, $naz, $ax, $ay, $az,
401 $nbx, $nby, $nbz, $bx, $by, $bz,
402 $ncx, $ncy, $ncz, $cx, $cy, $cz);
403 $lines =~ s/([.\d])0+,/$1,/g; # lose trailing insignificant zeroes
409 my $token = $filename; # guess at a C token from the filename
410 $token =~ s/\<[^<>]*\>//;
412 $token =~ s/\.[^.]*$//;
413 $token =~ s/[^a-z\d]/_/gi;
417 $token =~ tr [A-Z] [a-z];
418 $token = 'foo' if ($token eq '');
420 my $format = 'GL_N3F_V3F';
421 my $primitive = 'GL_TRIANGLES';
425 $code .= "static const struct gllist frame={";
426 $code .= "$format,$primitive,$npoints,data,NULL};\n";
427 $code .= "const struct gllist *$token=&frame;\n";
429 print STDERR "$progname: $filename: " .
430 (($#points+1)/3) . " points, " .
431 (($#points+1)/9) . " faces.\n"
439 my ($infile, $outfile, $smooth_p, $normalize_p) = @_;
442 open (IN, "<$infile") || error ("$infile: $!");
443 my $filename = ($infile eq '-' ? "<stdin>" : $infile);
444 print STDERR "$progname: reading $filename...\n"
446 while (<IN>) { $dxf .= $_; }
449 $dxf =~ s/\r\n/\n/g; # CRLF -> LF
450 $dxf =~ s/\r/\n/g; # CR -> LF
452 my @data = parse_dxf ($filename, $dxf, $normalize_p);
454 $filename = ($outfile eq '-' ? "<stdout>" : $outfile);
455 my $code = generate_c ($filename, $smooth_p, @data);
458 open (OUT, ">$outfile") || error ("$outfile: $!");
459 print OUT $code || error ("$filename: $!");
460 close OUT || error ("$filename: $!");
462 print STDERR "$progname: wrote $filename\n"
463 if ($verbose || $outfile ne '-');
469 print STDERR "$progname: $_\n";
474 print STDERR "usage: $progname [--verbose] [--smooth] [infile [outfile]]\n";
479 my ($infile, $outfile);
482 while ($_ = $ARGV[0]) {
484 if ($_ eq "--verbose") { $verbose++; }
485 elsif (m/^-v+$/) { $verbose += length($_)-1; }
486 elsif ($_ eq "--normalize") { $normalize_p = 1; }
487 elsif ($_ eq "--smooth") { $smooth_p = 1; }
488 elsif (m/^-./) { usage; }
489 elsif (!defined($infile)) { $infile = $_; }
490 elsif (!defined($outfile)) { $outfile = $_; }
494 $infile = "-" unless defined ($infile);
495 $outfile = "-" unless defined ($outfile);
497 dxf_to_gl ($infile, $outfile, $smooth_p, $normalize_p);