2 # Copyright © 2003-2011 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 Wavefront OBJ file, and emits C data suitable for use with OpenGL's
13 # glInterleavedArrays() and glDrawArrays() routines.
15 # If the OBJ file does not contain face normals, they are computed.
16 # Texture coordinates are ignored.
20 # --normalize Compute the bounding box of the object, and scale all
21 # coordinates so that the object fits inside a unit cube.
23 # Created: 8-Mar-2003.
29 my $progname = $0; $progname =~ s@.*/@@g;
30 my $version = q{ $Revision: 1.4 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/;
35 # convert a vector to a unit vector
38 my $L = sqrt (($x * $x) + ($y * $y) + ($z * $z));
50 # Calculate the unit normal at p0 given two other points p1,p2 on the
51 # surface. The normal points in the direction of p1 crossproduct p2.
53 sub face_normal($$$$$$$$$) {
56 $p2x, $p2y, $p2z) = @_;
59 my ($pax, $pay, $paz);
60 my ($pbx, $pby, $pbz);
68 $nx = $pay * $pbz - $paz * $pby;
69 $ny = $paz * $pbx - $pax * $pbz;
70 $nz = $pax * $pby - $pay * $pbx;
72 return (normalize ($nx, $ny, $nz));
77 my ($filename, $obj, $normalize_p) = @_;
80 my @verts = (); # list of refs of coords, [x, y, z]
81 my @norms = (); # list of refs of coords, [x, y, z]
82 my @texts = (); # list of refs of coords, [u, v]
83 my @faces = (); # list of refs of [ point, point, point, ... ]
84 # where 'point' is a ref of indexes into the
85 # above lists, [ vert, text, norm ]
88 foreach (split (/\n/, $obj)) {
90 next if (m/^\s*$|^\s*\#/);
93 my ($x, $y, $z) = m/^v\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$/;
94 error ("line $lineno: unparsable V line: $_") unless defined($z);
95 push @verts, [$x+0, $y+0, $z+0];
98 my ($x, $y, $z) = m/^vn\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)\s*$/;
99 error ("line $lineno: unparsable VN line: $_") unless defined($z);
100 push @norms, [$x+0, $y+0, $z+0];
103 my ($u, $v) = m/^vt\s+([^\s]+)\s+([^\s]+)\s*$/;
104 error ("line $lineno: unparsable VT line: $_") unless defined($v);
105 push @texts, [$u+0, $v+0];
111 my @f = split(/\s+/, $_);
116 if ($f =~ m@^(\d+)$@s) { $v = $1; }
117 elsif ($f =~ m@^(\d+)/(\d+)$@s) { $v = $1, $t = $2; }
118 elsif ($f =~ m@^(\d+)/(\d+)/(\d+)$@s) { $v = $1, $t = $2; $n = $3; }
119 elsif ($f =~ m@^(\d+)///(\d+)$@s) { $v = $1; $n = $3; }
121 error ("line $lineno: unparsable F line: $_") unless defined($v);
123 $t = -1 unless defined($t);
124 $n = -1 unless defined($n);
125 push @vs, [$v+0, $t+0, $n+0];
130 # turn on smooth shading
131 } elsif (m/^(mtllib|usemtl)\b/) {
132 # reference to external materials file (textures, etc.)
134 error ("line $lineno: unknown line: $_");
139 # find bounding box, and normalize
141 if ($normalize_p || $verbose) {
142 my $minx = 999999999;
143 my $miny = 999999999;
144 my $minz = 999999999;
145 my $maxx = -999999999;
146 my $maxy = -999999999;
147 my $maxz = -999999999;
149 foreach my $v (@verts) {
150 my ($x, $y, $z) = @$v;
151 $minx = $x if ($x < $minx);
152 $maxx = $x if ($x > $maxx);
153 $miny = $y if ($y < $miny);
154 $maxy = $y if ($y > $maxy);
155 $minz = $z if ($z < $minz);
156 $maxz = $z if ($z > $maxz);
159 my $w = ($maxx - $minx);
160 my $h = ($maxy - $miny);
161 my $d = ($maxz - $minz);
162 my $sizea = ($w > $h ? $w : $h);
163 my $sizeb = ($w > $d ? $w : $d);
164 my $size = ($sizea > $sizeb ? $sizea : $sizeb);
166 print STDERR "$progname: bbox is " .
167 sprintf("%.2f x %.2f x %.2f\n", $w, $h, $d)
174 print STDERR "$progname: dividing by $size for bbox of " .
175 sprintf("%.2f x %.2f x %.2f\n", $w, $h, $d)
177 foreach my $n (@verts) {
179 foreach (@n) { $_ /= $size; }
185 # generate interleaved list of triangle coordinates and normals
188 my $nfaces = $#faces+1;
190 foreach my $f (@faces) {
191 # $f is [ [v, t, n], [v, t, n], ... ]
195 # # (Kludge for the companion cube model)
198 # @f = (@f[$i-1 .. $#f], @f[0 .. $i]);
201 error ("too few points in face") if ($#f < 2);
204 # If there are more than 3 points, do a triangle fan from the first one:
205 # [1 2 3] [1 3 4] [1 4 5] etc. Doesn't always work with convex shapes.
211 my $x1 = $verts[$p1->[0]-1]->[0]; my $nx1 = $norms[$p1->[2]-1]->[0];
212 my $y1 = $verts[$p1->[0]-1]->[1]; my $ny1 = $norms[$p1->[2]-1]->[1];
213 my $z1 = $verts[$p1->[0]-1]->[2]; my $nz1 = $norms[$p1->[2]-1]->[2];
215 my $x2 = $verts[$p2->[0]-1]->[0]; my $nx2 = $norms[$p2->[2]-1]->[0];
216 my $y2 = $verts[$p2->[0]-1]->[1]; my $ny2 = $norms[$p2->[2]-1]->[1];
217 my $z2 = $verts[$p2->[0]-1]->[2]; my $nz2 = $norms[$p2->[2]-1]->[2];
219 my $x3 = $verts[$p3->[0]-1]->[0]; my $nx3 = $norms[$p3->[2]-1]->[0];
220 my $y3 = $verts[$p3->[0]-1]->[1]; my $ny3 = $norms[$p3->[2]-1]->[1];
221 my $z3 = $verts[$p3->[0]-1]->[2]; my $nz3 = $norms[$p3->[2]-1]->[2];
223 error ("missing points in face") unless defined($z3);
225 if (!defined($nz3)) {
226 my ($nx, $ny, $nz) = face_normal ($x1, $y1, $z1,
229 $nx1 = $nx2 = $nx3 = $nx;
230 $ny1 = $ny2 = $ny3 = $ny;
231 $nz1 = $nz2 = $nz3 = $nz;
235 push @triangles, [$nx1, $ny1, $nz1, $x1, $y1, $z1,
236 $nx2, $ny2, $nz2, $x2, $y2, $z2,
237 $nx3, $ny3, $nz3, $x3, $y3, $z3];
246 my ($filename, @triangles) = @_;
250 $code .= "#include \"gllist.h\"\n";
251 $code .= "static const float data[]={\n";
253 my $nfaces = $#triangles + 1;
254 my $npoints = $nfaces * 3;
256 foreach my $t (@triangles) {
257 my ($nx1, $ny1, $nz1, $x1, $y1, $z1,
258 $nx2, $ny2, $nz2, $x2, $y2, $z2,
259 $nx3, $ny3, $nz3, $x3, $y3, $z3) = @$t;
260 my $lines = sprintf("\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n" .
261 "\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n" .
262 "\t" . "%.6f,%.6f,%.6f," . "%.6f,%.6f,%.6f,\n",
263 $nx1, $ny1, $nz1, $x1, $y1, $z1,
264 $nx2, $ny2, $nz2, $x2, $y2, $z2,
265 $nx3, $ny3, $nz3, $x3, $y3, $z3);
266 $lines =~ s/([.\d])0+,/$1,/g; # lose trailing insignificant zeroes
268 $lines =~ s/-0,/0,/g;
273 my $token = $filename; # guess at a C token from the filename
274 $token =~ s/\<[^<>]*\>//;
276 $token =~ s/\.[^.]*$//;
277 $token =~ s/[^a-z\d]/_/gi;
281 $token =~ tr [A-Z] [a-z];
282 $token = 'foo' if ($token eq '');
284 my $format = 'GL_N3F_V3F';
285 my $primitive = 'GL_TRIANGLES';
289 $code .= "static const struct gllist frame={";
290 $code .= "$format,$primitive,$npoints,data,NULL};\n";
291 $code .= "const struct gllist *$token=&frame;\n";
293 print STDERR "$filename: " .
294 (($#triangles+1)*3) . " points, " .
295 (($#triangles+1)) . " faces.\n"
303 my ($infile, $outfile, $normalize_p) = @_;
305 open (my $in, '<', $infile) || error ("$infile: $!");
306 my $filename = ($infile eq '-' ? "<stdin>" : $infile);
307 print STDERR "$progname: reading $filename...\n"
309 while (<$in>) { $obj .= $_; }
312 $obj =~ s/\r\n/\n/g; # CRLF -> LF
313 $obj =~ s/\r/\n/g; # CR -> LF
315 my @triangles = parse_obj ($filename, $obj, $normalize_p);
317 $filename = ($outfile eq '-' ? "<stdout>" : $outfile);
318 my $code = generate_c ($filename, @triangles);
320 open (my $out, '>', $outfile) || error ("$outfile: $!");
321 (print $out $code) || error ("$filename: $!");
322 (close $out) || error ("$filename: $!");
324 print STDERR "$progname: wrote $filename\n"
325 if ($verbose || $outfile ne '-');
331 print STDERR "$progname: $_\n";
336 print STDERR "usage: $progname [--verbose] [infile [outfile]]\n";
341 my ($infile, $outfile);
343 while ($_ = $ARGV[0]) {
345 if ($_ eq "--verbose") { $verbose++; }
346 elsif (m/^-v+$/) { $verbose += length($_)-1; }
347 elsif ($_ eq "--normalize") { $normalize_p = 1; }
348 elsif (m/^-./) { usage; }
349 elsif (!defined($infile)) { $infile = $_; }
350 elsif (!defined($outfile)) { $outfile = $_; }
354 $infile = "-" unless defined ($infile);
355 $outfile = "-" unless defined ($outfile);
357 obj_to_gl ($infile, $outfile, $normalize_p);