--- /dev/null
+#!/usr/bin/perl -w
+# Copyright © 2003 Jamie Zawinski <jwz@jwz.org>
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation. No representations are made about the suitability of this
+# software for any purpose. It is provided "as is" without express or
+# implied warranty.
+#
+# Reads a DXF file, and emits C data suitable for use with OpenGL's
+# glInterleavedArrays() and glDrawArrays() routines.
+#
+# Options:
+#
+# --normalize Compute the bounding box of the object, and scale all
+# coordinates so that the object fits inside a unit cube.
+#
+# --smooth When computing normals for the vertexes, average the
+# normals at any edge which is less than 90 degrees.
+# If this option is not specified, planar normals will be
+# used, resulting in a "faceted" object.
+#
+# Created: 8-Mar-2003.
+
+require 5;
+use diagnostics;
+use strict;
+
+my $progname = $0; $progname =~ s@.*/@@g;
+my $version = q{ $Revision: 1.1 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
+
+my $verbose = 0;
+
+
+# convert a vector to a unit vector
+sub normalize {
+ my ($x, $y, $z) = @_;
+ my $L = sqrt (($x * $x) + ($y * $y) + ($z * $z));
+ if ($L != 0) {
+ $x /= $L;
+ $y /= $L;
+ $z /= $L;
+ } else {
+ $x = $y = $z = 0;
+ }
+ return ($x, $y, $z);
+}
+
+
+# Calculate the unit normal at p0 given two other points p1,p2 on the
+# surface. The normal points in the direction of p1 crossproduct p2.
+#
+sub face_normal {
+ my ($p0x, $p0y, $p0z,
+ $p1x, $p1y, $p1z,
+ $p2x, $p2y, $p2z) = @_;
+
+ my ($nx, $ny, $nz);
+ my ($pax, $pay, $paz);
+ my ($pbx, $pby, $pbz);
+
+ $pax = $p1x - $p0x;
+ $pay = $p1y - $p0y;
+ $paz = $p1z - $p0z;
+ $pbx = $p2x - $p0x;
+ $pby = $p2y - $p0y;
+ $pbz = $p2z - $p0z;
+ $nx = $pay * $pbz - $paz * $pby;
+ $ny = $paz * $pbx - $pax * $pbz;
+ $nz = $pax * $pby - $pay * $pbx;
+
+ return (normalize ($nx, $ny, $nz));
+}
+
+
+# why this isn't in perlfunc, I don't know.
+sub acos { atan2( sqrt(1 - $_[0] * $_[0]), $_[0] ) }
+my $pi = 3.141592653589793;
+my $radians_to_degrees = 180.0 / $pi;
+
+# Calculate the angle (in degrees) between two vectors.
+#
+sub vector_angle {
+ my ($x1, $y1, $z1,
+ $x2, $y2, $z2) = @_;
+
+ my $L1 = sqrt ($x1*$x1 + $y1*$y1 + $z1*$z1);
+ my $L2 = sqrt ($x2*$x2 + $y2*$y2 + $z2*$z2);
+
+ return 0 if ($L1 == 0 || $L2 == 0);
+ return 0 if ($x1 == $x2 && $y1 == $y2 && $z1 == $z2);
+
+ # dot product of two vectors is defined as:
+ # $L1 * $L1 * cos(angle between vectors)
+ # and is also defined as:
+ # $x1*$x2 + $y1*$y2 + $z1*$z2
+ # so:
+ # $L1 * $L1 * cos($angle) = $x1*$x2 + $y1*$y2 + $z1*$z2
+ # cos($angle) = ($x1*$x2 + $y1*$y2 + $z1*$z2) / ($L1 * $L2)
+ # $angle = acos (($x1*$x2 + $y1*$y2 + $z1*$z2) / ($L1 * $L2));
+ #
+ my $cos = ($x1*$x2 + $y1*$y2 + $z1*$z2) / ($L1 * $L2);
+ $cos = 1 if ($cos > 1); # avoid fp rounding error (1.000001 => sqrt error)
+ my $angle = acos ($cos);
+
+ return ($angle * $radians_to_degrees);
+}
+
+# given a list of triangles ( [ X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, ]+ )
+# returns a list of the normals for each vertex.
+#
+sub compute_vertex_normals {
+ my (@points) = @_;
+ my $npoints = ($#points+1) / 3;
+ my $nfaces = $npoints / 3;
+
+ my @face_normals = ();
+ my %point_faces;
+
+ for (my $i = 0; $i < $nfaces; $i++) {
+ my ($ax, $ay, $az, $bx, $by, $bz, $cx, $cy, $cz) =
+ @points[($i*9) .. ($i*9)+8];
+
+ # store the normal for each face in the $face_normals array
+ # indexed by face number.
+ #
+ my @norm = face_normal ($ax, $ay, $az,
+ $bx, $by, $bz,
+ $cx, $cy, $cz);
+ $face_normals[$i] = \@norm;
+
+ # store in the %point_faces hash table a list of every face number
+ # in which a point participates
+
+ my $p;
+ my @flist;
+
+ $p = "$ax $ay $az";
+ @flist = (defined($point_faces{$p}) ? @{$point_faces{$p}} : ());
+ push @flist, $i;
+ $point_faces{$p} = \@flist;
+
+ $p = "$bx $by $bz";
+ @flist = (defined($point_faces{$p}) ? @{$point_faces{$p}} : ());
+ push @flist, $i;
+ $point_faces{$p} = \@flist;
+
+ $p = "$cx $cy $cz";
+ @flist = (defined($point_faces{$p}) ? @{$point_faces{$p}} : ());
+ push @flist, $i;
+ $point_faces{$p} = \@flist;
+ }
+
+
+ # compute the normal for each vertex of each face.
+ # (these points are not unique -- because there might be multiple
+ # normals associated with the same vertex for different faces,
+ # in the case where it's a sharp angle.)
+ #
+ my @normals = ();
+ for (my $i = 0; $i < $nfaces; $i++) {
+ my @verts = @points[($i*9) .. ($i*9)+8];
+ error ("overshot in points?") unless defined($verts[8]);
+
+ my @norm = @{$face_normals[$i]};
+ error ("no normal $i?") unless defined($norm[2]);
+
+ # iterate over the (three) vertexes in this face.
+ #
+ for (my $j = 0; $j < 3; $j++) {
+ my ($x, $y, $z) = @verts[($j*3) .. ($j*3)+2];
+ error ("overshot in verts?") unless defined($z);
+
+ # Iterate over the faces in which this point participates.
+ # This face's normal is the average of the normals of those faces.
+ # Except, faces are ignored if any point in them is at more than
+ # a 90 degree angle from the zeroth face (arbitrarily.)
+ #
+ my ($nx, $ny, $nz) = (0, 0, 0);
+ my @faces = @{$point_faces{"$x $y $z"}};
+ foreach my $fn (@faces) {
+ my ($ax, $ay, $az, $bx, $by, $bz, $cx, $cy, $cz) =
+ @points[($fn*9) .. ($fn*9)+8];
+ my @fnorm = @{$face_normals[$fn]};
+
+ # ignore any adjascent faces that are more than 90 degrees off.
+ my $angle = vector_angle (@norm, @fnorm);
+ next if ($angle >= 90);
+
+ $nx += $fnorm[0];
+ $ny += $fnorm[1];
+ $nz += $fnorm[2];
+ }
+
+ push @normals, normalize ($nx, $ny, $nz);
+ }
+ }
+
+ return @normals;
+}
+
+
+
+sub parse_dxf {
+ my ($filename, $dxf, $normalize_p) = @_;
+
+ $_ = $dxf;
+ my ($points_txt, $coords_txt);
+ my $vvers;
+
+ $dxf =~ s/([^\n]*)\n([^\n]*)\n/$1\t$2\n/g; # join even and odd lines
+
+ my @triangles = ();
+
+ my @items = split (/\n/, $dxf);
+ while ($#items >= 0) {
+
+ $_ = shift @items;
+
+ if (m/^\s* 0 \s+ SECTION \b/x) {
+ } elsif (m/^\s* 2 \s+ HEADER \b/x) {
+ } elsif (m/^\s* 0 \s+ ENDSEC \b/x) {
+ } elsif (m/^\s* 2 \s+ ENTITIES \b/x) {
+ } elsif (m/^\s* 0 \s+ EOF \b/x) {
+ } elsif (m/^\s* 0 \s+ 3DFACE \b/x) {
+
+ my @points = ();
+ my $pc = 0;
+
+ while ($#items >= 0) {
+ $_ = shift @items; # get next line
+
+ my $d = '(-?\d+\.?\d+)';
+ if (m/^\s* 8 \b/x) { # layer name
+ } elsif (m/^\s* 62 \b/x) { # color number
+
+ } elsif (m/^\s* 10 \s+ $d/xo) { $pc++; $points[ 0] = $1; # X1
+ } elsif (m/^\s* 20 \s+ $d/xo) { $pc++; $points[ 1] = $1; # Y1
+ } elsif (m/^\s* 30 \s+ $d/xo) { $pc++; $points[ 2] = $1; # Z1
+
+ } elsif (m/^\s* 11 \s+ $d/xo) { $pc++; $points[ 3] = $1; # X2
+ } elsif (m/^\s* 21 \s+ $d/xo) { $pc++; $points[ 4] = $1; # Y2
+ } elsif (m/^\s* 31 \s+ $d/xo) { $pc++; $points[ 5] = $1; # Z2
+
+ } elsif (m/^\s* 12 \s+ $d/xo) { $pc++; $points[ 6] = $1; # X3
+ } elsif (m/^\s* 22 \s+ $d/xo) { $pc++; $points[ 7] = $1; # Y3
+ } elsif (m/^\s* 32 \s+ $d/xo) { $pc++; $points[ 8] = $1; # Z3
+
+ } elsif (m/^\s* 13 \s+ $d/xo) { $pc++; $points[ 9] = $1; # X4
+ } elsif (m/^\s* 23 \s+ $d/xo) { $pc++; $points[10] = $1; # Y4
+ } elsif (m/^\s* 33 \s+ $d/xo) { $pc++; $points[11] = $1; # Z4
+ } else {
+ error ("$filename: unknown 3DFACE entry: $_\n");
+ }
+
+ last if ($pc >= 12);
+ }
+
+ if ($points[6] != $points[9] ||
+ $points[7] != $points[10] ||
+ $points[8] != $points[11]) {
+ error ("$filename: got a quad, not a triangle\n");
+ } else {
+ @points = @points[0 .. 8];
+ }
+
+ foreach (@points) { $_ += 0; } # convert strings to numbers
+
+ push @triangles, @points;
+
+ } else {
+ error ("$filename: unknown: $_\n");
+ }
+ }
+
+
+ my $npoints = ($#triangles+1) / 3;
+
+
+ # find bounding box, and normalize
+ #
+ if ($normalize_p || $verbose) {
+ my $minx = 999999999;
+ my $miny = 999999999;
+ my $minz = 999999999;
+ my $maxx = -999999999;
+ my $maxy = -999999999;
+ my $maxz = -999999999;
+ my $i = 0;
+ foreach my $n (@triangles) {
+ if ($i == 0) { $minx = $n if ($n < $minx);
+ $maxx = $n if ($n > $maxx); }
+ elsif ($i == 1) { $miny = $n if ($n < $miny);
+ $maxy = $n if ($n > $maxy); }
+ else { $minz = $n if ($n < $minz);
+ $maxz = $n if ($n > $maxz); }
+ $i = 0 if (++$i == 3);
+ }
+
+ my $w = ($maxx - $minx);
+ my $h = ($maxy - $miny);
+ my $d = ($maxz - $minz);
+ my $sizea = ($w > $h ? $w : $h);
+ my $sizeb = ($w > $d ? $w : $d);
+ my $size = ($sizea > $sizeb ? $sizea : $sizeb);
+
+ print STDERR "$progname: $filename: bbox is " .
+ sprintf("%.2f x %.2f x %.2f\n", $w, $h, $d)
+ if ($verbose);
+
+ if ($normalize_p) {
+ $w /= $size;
+ $h /= $size;
+ $d /= $size;
+ print STDERR "$progname: $filename: dividing by $size for bbox of " .
+ sprintf("%.2f x %.2f x %.2f\n", $w, $h, $d)
+ if ($verbose);
+ foreach my $n (@triangles) {
+ $n /= $size;
+ }
+ }
+ }
+
+ return (@triangles);
+}
+
+
+sub generate_c {
+ my ($filename, $smooth_p, @points) = @_;
+
+ my $ccw_p = 1; # counter-clockwise winding rule for computing normals
+
+ my $code = '';
+
+ $code .= "#include \"gllist.h\"\n";
+ $code .= "static float data[]={\n";
+
+ my $npoints = ($#points + 1) / 3;
+ my $nfaces = $npoints / 3;
+
+ my @normals;
+ if ($smooth_p) {
+ @normals = compute_vertex_normals (@points);
+
+ if ($#normals != $#points) {
+ error ("computed " . (($#normals+1)/3) . " normals for " .
+ (($#points+1)/3) . " points?");
+ }
+ }
+
+ for (my $i = 0; $i < $nfaces; $i++) {
+ my $ax = $points[$i*9];
+ my $ay = $points[$i*9+1];
+ my $az = $points[$i*9+2];
+
+ my $bx = $points[$i*9+3];
+ my $by = $points[$i*9+4];
+ my $bz = $points[$i*9+5];
+
+ my $cx = $points[$i*9+6];
+ my $cy = $points[$i*9+7];
+ my $cz = $points[$i*9+8];
+
+ my ($nax, $nay, $naz,
+ $nbx, $nby, $nbz,
+ $ncx, $ncy, $ncz);
+
+ if ($smooth_p) {
+ $nax = $normals[$i*9];
+ $nay = $normals[$i*9+1];
+ $naz = $normals[$i*9+2];
+
+ $nbx = $normals[$i*9+3];
+ $nby = $normals[$i*9+4];
+ $nbz = $normals[$i*9+5];
+
+ $ncx = $normals[$i*9+6];
+ $ncy = $normals[$i*9+7];
+ $ncz = $normals[$i*9+8];
+
+ } else {
+ if ($ccw_p) {
+ ($nax, $nay, $naz) = face_normal ($ax, $ay, $az,
+ $bx, $by, $bz,
+ $cx, $cy, $cz);
+ } else {
+ ($nax, $nay, $naz) = face_normal ($ax, $ay, $az,
+ $cx, $cy, $cz,
+ $bx, $by, $bz);
+ }
+ ($nbx, $nby, $nbz) = ($nax, $nay, $naz);
+ ($ncx, $ncy, $ncz) = ($nax, $nay, $naz);
+ }
+
+ my $lines = sprintf("\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n" .
+ "\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n" .
+ "\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n",
+ $nax, $nay, $naz, $ax, $ay, $az,
+ $nbx, $nby, $nbz, $bx, $by, $bz,
+ $ncx, $ncy, $ncz, $cx, $cy, $cz);
+ $lines =~ s/([.\d])0+,/$1,/g; # lose trailing insignificant zeroes
+ $lines =~ s/\.,/,/g;
+
+ $code .= $lines;
+ }
+
+ my $token = $filename; # guess at a C token from the filename
+ $token =~ s/\<[^<>]*\>//;
+ $token =~ s@^.*/@@;
+ $token =~ s/\.[^.]*$//;
+ $token =~ s/[^a-z\d]/_/gi;
+ $token =~ s/__+/_/g;
+ $token =~ s/^_//g;
+ $token =~ s/_$//g;
+ $token =~ tr [A-Z] [a-z];
+ $token = 'foo' if ($token eq '');
+
+ my $format = 'GL_N3F_V3F';
+ my $primitive = 'GL_TRIANGLES';
+
+ $code =~ s/,\n$//s;
+ $code .= "\n};\n";
+ $code .= "static struct gllist frame={";
+ $code .= "$format,$primitive,$npoints,data,NULL};\n";
+ $code .= "struct gllist *$token=&frame;\n";
+
+ print STDERR "$progname: $filename: " .
+ (($#points+1)/3) . " points, " .
+ (($#points+1)/9) . " faces.\n"
+ if ($verbose);
+
+ return $code;
+}
+
+
+sub dxf_to_gl {
+ my ($infile, $outfile, $smooth_p, $normalize_p) = @_;
+ local *IN;
+ my $dxf = '';
+ open (IN, "<$infile") || error ("$infile: $!");
+ my $filename = ($infile eq '-' ? "<stdin>" : $infile);
+ print STDERR "$progname: reading $filename...\n"
+ if ($verbose);
+ while (<IN>) { $dxf .= $_; }
+ close IN;
+
+ $dxf =~ s/\r\n/\n/g; # CRLF -> LF
+ $dxf =~ s/\r/\n/g; # CR -> LF
+
+ my @data = parse_dxf ($filename, $dxf, $normalize_p);
+
+ $filename = ($outfile eq '-' ? "<stdout>" : $outfile);
+ my $code = generate_c ($filename, $smooth_p, @data);
+
+ local *OUT;
+ open (OUT, ">$outfile") || error ("$outfile: $!");
+ print OUT $code || error ("$filename: $!");
+ close OUT || error ("$filename: $!");
+
+ print STDERR "$progname: wrote $filename\n"
+ if ($verbose || $outfile ne '-');
+}
+
+
+sub error {
+ ($_) = @_;
+ print STDERR "$progname: $_\n";
+ exit 1;
+}
+
+sub usage {
+ print STDERR "usage: $progname [--verbose] [--smooth] [infile [outfile]]\n";
+ exit 1;
+}
+
+sub main {
+ my ($infile, $outfile);
+ my $normalize_p = 0;
+ my $smooth_p = 0;
+ while ($_ = $ARGV[0]) {
+ shift @ARGV;
+ if ($_ eq "--verbose") { $verbose++; }
+ elsif (m/^-v+$/) { $verbose += length($_)-1; }
+ elsif ($_ eq "--normalize") { $normalize_p = 1; }
+ elsif ($_ eq "--smooth") { $smooth_p = 1; }
+ elsif (m/^-./) { usage; }
+ elsif (!defined($infile)) { $infile = $_; }
+ elsif (!defined($outfile)) { $outfile = $_; }
+ else { usage; }
+ }
+
+ $infile = "-" unless defined ($infile);
+ $outfile = "-" unless defined ($outfile);
+
+ dxf_to_gl ($infile, $outfile, $smooth_p, $normalize_p);
+}
+
+main;
+exit 0;