]> git.mxchange.org Git - flightgear.git/blob - Tri2obj/tri2obj.cxx
20ecb0b2b733236195b0106ab2ce376a37f379aa
[flightgear.git] / Tri2obj / tri2obj.cxx
1 // tri2obj.cxx -- read in a .ele/.node file pair generated by the triangle 
2 //                program and output a simple Wavefront .obj file.
3 //
4 // Written by Curtis Olson, started October 1997.
5 //
6 // Copyright (C) 1997  Curtis L. Olson  - curt@infoplane.com
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 //
22 // $Id$
23 // (Log is kept at end of this file)
24
25
26 #include <stdio.h>
27 #include <stdlib.h>   // for atoi()
28 #include <string.h>
29 #include <sys/stat.h> // for stat()
30 #include <unistd.h>   // for stat()
31
32 #include "tri2obj.hxx"
33
34 #include <Include/fg_constants.h>
35 #include <Include/fg_types.h>
36 #include <Bucket/bucketutils.h>
37
38 #include <Math/fg_geodesy.hxx>
39 #include <Math/mat3.h>
40 #include <Math/polar3d.hxx>
41
42
43 int nodecount, tricount;
44 int normalcount = 0;
45 static Point3D nodes[MAX_NODES];
46 static int tris[MAX_TRIS][3];
47
48 static double normals[MAX_NODES][3];
49
50 fgBUCKET my_index;
51 fgBUCKET ne_index, nw_index, sw_index, se_index;
52 fgBUCKET north_index, south_index, east_index, west_index;
53
54
55 // given three points defining a triangle, calculate the normal
56 void calc_normal(Point3D p1, Point3D p2, 
57                  Point3D p3, double normal[3])
58 {
59     double v1[3], v2[3];
60     double temp;
61
62     v1[0] = p2.x() - p1.x(); v1[1] = p2.y() - p1.y(); v1[2] = p2.z() - p1.z();
63     v2[0] = p3.x() - p1.x(); v2[1] = p3.y() - p1.y(); v2[2] = p3.z() - p1.z();
64
65     MAT3cross_product(normal, v1, v2);
66     MAT3_NORMALIZE_VEC(normal, temp);
67
68 //  printf("  Normal = %.2f %.2f %.2f\n", normal[0], normal[1], normal[2]);
69 }
70
71
72 // return the index of all triangles containing the specified node
73 void find_tris(int n, int *t1, int *t2, int *t3, int *t4, int *t5) {
74     int i;
75
76     *t1 = *t2 = *t3 = *t4 = *t5 = 0;
77
78     i = 1;
79     while ( i <= tricount ) {
80         if ( (n == tris[i][0]) || (n == tris[i][1]) || (n == tris[i][2]) ) {
81             if ( *t1 == 0 ) {
82                 *t1 = i;
83             } else if ( *t2 == 0 ) {
84                 *t2 = i;
85             } else if ( *t3 == 0 ) {
86                 *t3 = i;
87             } else if ( *t4 == 0 ) {
88                 *t4 = i;
89             } else {
90                 *t5 = i;
91             }
92         }
93         i++;
94     }
95 }
96
97
98 // return the file base name ( foo/bar/file.ext = file.ext )
99 void extract_file(char *in, char *base) {
100     int len, i;
101
102     len = strlen(in);
103
104     i = len - 1;
105     while ( (i >= 0) && (in[i] != '/') ) {
106         i--;
107     }
108
109     in += (i + 1);
110     strcpy(base, in);
111 }
112
113
114 // return the file path name ( foo/bar/file.ext = foo/bar )
115 void extract_path(char *in, char *base) {
116     int len, i;
117
118     len = strlen(in);
119     strcpy(base, in);
120
121     i = len - 1;
122     while ( (i >= 0) && (in[i] != '/') ) {
123         i--;
124     }
125
126     base[i] = '\0';
127 }
128
129
130 // check if a file exists
131 int file_exists(char *file) {
132     struct stat stat_buf;
133     int result;
134
135     printf("checking %s ... ", file);
136
137     result = stat(file, &stat_buf);
138
139     if ( result != 0 ) {
140         // stat failed, no file
141         printf("not found.\n");
142         return(0);
143     } else {
144         // stat succeeded, file exists
145         printf("exists.\n");
146         return(1);
147     }
148 }
149
150
151 // check to see if a shared object exists
152 int shared_object_exists(char *basepath, char *ext, char *file) {
153     char scene_path[256];
154     long int index;
155
156     if ( strcmp(ext, ".sw") == 0 ) {
157         fgBucketGenBasePath(&my_index, scene_path);
158         index = fgBucketGenIndex(&my_index);
159         sprintf(file, "%s/%s/%ld.1.sw", basepath, scene_path, index);
160         if ( file_exists(file) ) {
161             return(1);
162         }
163         fgBucketGenBasePath(&west_index, scene_path);
164         index = fgBucketGenIndex(&west_index);
165         sprintf(file, "%s/%s/%ld.1.se", basepath, scene_path, index);
166         if ( file_exists(file) ) {
167             return(1);
168         }
169         fgBucketGenBasePath(&sw_index, scene_path);
170         index = fgBucketGenIndex(&sw_index);
171         sprintf(file, "%s/%s/%ld.1.ne", basepath, scene_path, index);
172         if ( file_exists(file) ) {
173             return(1);
174         }
175         fgBucketGenBasePath(&south_index, scene_path);
176         index = fgBucketGenIndex(&south_index);
177         sprintf(file, "%s/%s/%ld.1.nw", basepath, scene_path, index);
178         if ( file_exists(file) ) {
179             return(1);
180         }
181     }
182
183     if ( strcmp(ext, ".se") == 0 ) {
184         fgBucketGenBasePath(&my_index, scene_path);
185         index = fgBucketGenIndex(&my_index);
186         sprintf(file, "%s/%s/%ld.1.se", basepath, scene_path, index);
187         if ( file_exists(file) ) {
188             return(1);
189         }
190         fgBucketGenBasePath(&east_index, scene_path);
191         index = fgBucketGenIndex(&east_index);
192         sprintf(file, "%s/%s/%ld.1.sw", basepath, scene_path, index);
193         if ( file_exists(file) ) {
194             return(1);
195         }
196         fgBucketGenBasePath(&se_index, scene_path);
197         index = fgBucketGenIndex(&se_index);
198         sprintf(file, "%s/%s/%ld.1.nw", basepath, scene_path, index);
199         if ( file_exists(file) ) {
200             return(1);
201         }
202         fgBucketGenBasePath(&south_index, scene_path);
203         index = fgBucketGenIndex(&south_index);
204         sprintf(file, "%s/%s/%ld.1.ne", basepath, scene_path, index);
205         if ( file_exists(file) ) {
206             return(1);
207         }
208     }
209
210     if ( strcmp(ext, ".ne") == 0 ) {
211         fgBucketGenBasePath(&my_index, scene_path);
212         index = fgBucketGenIndex(&my_index);
213         sprintf(file, "%s/%s/%ld.1.ne", basepath, scene_path, index);
214         if ( file_exists(file) ) {
215             return(1);
216         }
217         fgBucketGenBasePath(&east_index, scene_path);
218         index = fgBucketGenIndex(&east_index);
219         sprintf(file, "%s/%s/%ld.1.nw", basepath, scene_path, index);
220         if ( file_exists(file) ) {
221             return(1);
222         }
223         fgBucketGenBasePath(&ne_index, scene_path);
224         index = fgBucketGenIndex(&ne_index);
225         sprintf(file, "%s/%s/%ld.1.sw", basepath, scene_path, index);
226         if ( file_exists(file) ) {
227             return(1);
228         }
229         fgBucketGenBasePath(&north_index, scene_path);
230         index = fgBucketGenIndex(&north_index);
231         sprintf(file, "%s/%s/%ld.1.se", basepath, scene_path, index);
232         if ( file_exists(file) ) {
233             return(1);
234         }
235     }
236
237     if ( strcmp(ext, ".nw") == 0 ) {
238         fgBucketGenBasePath(&my_index, scene_path);
239         index = fgBucketGenIndex(&my_index);
240         sprintf(file, "%s/%s/%ld.1.nw", basepath, scene_path, index);
241         if ( file_exists(file) ) {
242             return(1);
243         }
244         fgBucketGenBasePath(&west_index, scene_path);
245         index = fgBucketGenIndex(&west_index);
246         sprintf(file, "%s/%s/%ld.1.ne", basepath, scene_path, index);
247         if ( file_exists(file) ) {
248             return(1);
249         }
250         fgBucketGenBasePath(&nw_index, scene_path);
251         index = fgBucketGenIndex(&nw_index);
252         sprintf(file, "%s/%s/%ld.1.se", basepath, scene_path, index);
253         if ( file_exists(file) ) {
254             return(1);
255         }
256         fgBucketGenBasePath(&north_index, scene_path);
257         index = fgBucketGenIndex(&north_index);
258         sprintf(file, "%s/%s/%ld.1.sw", basepath, scene_path, index);
259         if ( file_exists(file) ) {
260             return(1);
261         }
262     }
263
264     if ( strcmp(ext, ".south") == 0 ) {
265         fgBucketGenBasePath(&my_index, scene_path);
266         index = fgBucketGenIndex(&my_index);
267         sprintf(file, "%s/%s/%ld.1.south", basepath, scene_path, index);
268         if ( file_exists(file) ) {
269             return(1);
270         }
271         fgBucketGenBasePath(&south_index, scene_path);
272         index = fgBucketGenIndex(&south_index);
273         sprintf(file, "%s/%s/%ld.1.north", basepath, scene_path, index);
274         if ( file_exists(file) ) {
275             return(1);
276         }
277     }
278
279     if ( strcmp(ext, ".north") == 0 ) {
280         fgBucketGenBasePath(&my_index, scene_path);
281         index = fgBucketGenIndex(&my_index);
282         sprintf(file, "%s/%s/%ld.1.north", basepath, scene_path, index);
283         if ( file_exists(file) ) {
284             return(1);
285         }
286         fgBucketGenBasePath(&north_index, scene_path);
287         index = fgBucketGenIndex(&north_index);
288         sprintf(file, "%s/%s/%ld.1.south", basepath, scene_path, index);
289         if ( file_exists(file) ) {
290             return(1);
291         }
292     }
293
294     if ( strcmp(ext, ".west") == 0 ) {
295         fgBucketGenBasePath(&my_index, scene_path);
296         index = fgBucketGenIndex(&my_index);
297         sprintf(file, "%s/%s/%ld.1.west", basepath, scene_path, index);
298         if ( file_exists(file) ) {
299             return(1);
300         }
301         fgBucketGenBasePath(&west_index, scene_path);
302         index = fgBucketGenIndex(&west_index);
303         sprintf(file, "%s/%s/%ld.1.east", basepath, scene_path, index);
304         if ( file_exists(file) ) {
305             return(1);
306         }
307     }
308
309     if ( strcmp(ext, ".east") == 0 ) {
310         fgBucketGenBasePath(&my_index, scene_path);
311         index = fgBucketGenIndex(&my_index);
312         sprintf(file, "%s/%s/%ld.1.east", basepath, scene_path, index);
313         if ( file_exists(file) ) {
314             return(1);
315         }
316         fgBucketGenBasePath(&east_index, scene_path);
317         index = fgBucketGenIndex(&east_index);
318         sprintf(file, "%s/%s/%ld.1.west", basepath, scene_path, index);
319         if ( file_exists(file) ) {
320             return(1);
321         }
322     }
323
324     if ( strcmp(ext, ".body") == 0 ) {
325         fgBucketGenBasePath(&my_index, scene_path);
326         index = fgBucketGenIndex(&my_index);
327         sprintf(file, "%s/%s/%ld.1.body", basepath, scene_path, index);
328         if ( file_exists(file) ) {
329             return(1);
330         }
331     }
332
333     return(0);
334 }
335
336
337 // given a file pointer, read all the vn (normals from it)
338 void read_normals(FILE *fp) {
339     char line[256];
340
341     while ( fgets(line, 250, fp) != NULL ) {
342         if ( strncmp(line, "vn ", 3) == 0 ) {
343             sscanf( line, "vn %lf %lf %lf\n", 
344                     &normals[normalcount][0], 
345                     &normals[normalcount][1], 
346                     &normals[normalcount][2] );
347             /*
348             printf("read_normals(%d) %.2f %.2f %.2f %s", normalcount, 
349                    normals[normalcount][0], normals[normalcount][1], 
350                    normals[normalcount][2], line);
351                    */
352             normalcount++;
353         }
354     }
355 }
356
357
358 // my custom file opening routine ... don't open if a shared edge or
359 // vertex alread exists
360 FILE *my_open(char *basename, char *basepath, char *ext) {
361     FILE *fp;
362     char filename[256];
363
364     // check if a shared object already exists
365     if ( shared_object_exists(basepath, ext, filename) ) {
366         // not an actual file open error, but we've already got the
367         // shared edge, so we don't want to create another one
368         fp = fopen(filename, "r");
369         printf("Opening %s\n", filename);
370         return(fp);
371     } else {
372         // open the file
373         printf("not opening\n");
374         return(NULL);
375     }
376 }
377
378
379 // Initialize a new mesh structure
380 void triload(char *basename, char *basepath) {
381     char nodename[256], elename[256];
382     double n[3];
383     Point3D p;
384     FILE *ne, *nw, *se, *sw, *north, *south, *east, *west;
385     FILE *node, *ele;
386     int dim, junk1, junk2;
387     int i;
388
389     ne = my_open(basename, basepath, ".ne");
390     read_normals(ne);
391     fclose(ne);
392
393     nw = my_open(basename, basepath, ".nw");
394     read_normals(nw);
395     fclose(nw);
396
397     se = my_open(basename, basepath, ".se");
398     read_normals(se);
399     fclose(se);
400
401     sw = my_open(basename, basepath, ".sw");
402     read_normals(sw);
403     fclose(sw);
404
405     north = my_open(basename, basepath, ".north");
406     read_normals(north);
407     fclose(north);
408
409     south = my_open(basename, basepath, ".south");
410     read_normals(south);
411     fclose(south);
412
413     east = my_open(basename, basepath, ".east");
414     read_normals(east);
415     fclose(east);
416
417     west = my_open(basename, basepath, ".west");
418     read_normals(west);
419     fclose(west);
420
421     strcpy(nodename, basename);
422     strcat(nodename, ".node");
423     strcpy(elename, basename);
424     strcat(elename, ".ele");
425
426     printf("Loading node file:  %s ...\n", nodename);
427     if ( (node = fopen(nodename, "r")) == NULL ) {
428         printf("Cannot open file '%s'\n", nodename);
429         exit(-1);
430     }
431
432     fscanf(node, "%d %d %d %d", &nodecount, &dim, &junk1, &junk2);
433
434     if ( nodecount > MAX_NODES - 1 ) {
435         printf("Error, too many nodes, need to increase array size\n");
436         exit(-1);
437     } else {
438         printf("    Expecting %d nodes\n", nodecount);
439     }
440
441     for ( i = 1; i <= nodecount; i++ ) {
442         fscanf(node, "%d %lf %lf %lf %d\n", &junk1, 
443                &n[0], &n[1], &n[2], &junk2);
444         // printf("%d %.2f %.2f %.2f\n", junk1, n[0], n[1], n[2]);
445         p = Point3D(n[0], n[1], n[2]);
446         nodes[i] = fgGeodToCart(p);
447         // printf("%d %.2f %.2f %.2f\n", 
448         //        junk1, nodes[i].x, nodes[i].y, nodes[i].z);
449     }
450
451     fclose(node);
452
453     printf("Loading element file:  %s ...\n", elename);
454     if ( (ele = fopen(elename, "r")) == NULL ) {
455         printf("Cannot open file '%s'\n", elename);
456         exit(-1);
457     }
458
459     fscanf(ele, "%d %d %d", &tricount, &junk1, &junk2);
460
461     if ( tricount > MAX_TRIS - 1 ) {
462         printf("Error, too many elements, need to increase array size\n");
463         exit(-1);
464     } else {
465         printf("    Expecting %d elements\n", tricount);
466     }
467
468     for ( i = 1; i <= tricount; i++ ) {
469         fscanf(ele, "%d %d %d %d\n", &junk1, 
470                &tris[i][0], &tris[i][1], &tris[i][2]);
471         // printf("%d %d %d %d\n", junk1, tris[i][0], tris[i][1], tris[i][2]);*/
472     }
473
474     fclose(ele);
475 }
476
477
478 // dump in WaveFront .obj format
479 void dump_obj(char *basename) {
480     char objname[256];
481     double n1[3], n2[3], n3[3], n4[3], n5[3], norm[3], temp;
482     FILE *obj;
483     int i, t1, t2, t3, t4, t5, count;
484     double x, y, z;
485
486     strcpy(objname, basename);
487     strcat(objname, ".obj");
488
489     printf("Dumping to file:  %s ...\n", objname);
490
491     obj = fopen(objname, "w");
492
493     // dump vertices
494     printf("  writing vertices\n");
495     for ( i = 1; i <= nodecount; i++ ) {
496         x = nodes[i].x();
497         y = nodes[i].y();
498         z = nodes[i].z();
499         fprintf(obj, "v %.6f %.6f %.6f\n", x, y, z);
500     }
501
502     printf("  calculating and writing normals\n");
503     printf("  First %d normals taken from shared files.\n", normalcount);
504                                                      
505     // calculate and generate normals
506     for ( i = 1; i <= nodecount; i++ ) {
507
508         if ( i <= normalcount ) {
509             // use precalculated (shared) normal
510             norm[0] = normals[i-1][0];
511             norm[1] = normals[i-1][1];
512             norm[2] = normals[i-1][2];
513         } else {
514             // printf("Finding normal\n");
515
516             find_tris(i, &t1, &t2, &t3, &t4, &t5);
517
518             n1[0] = n1[1] = n1[2] = 0.0;
519             n2[0] = n2[1] = n2[2] = 0.0;
520             n3[0] = n3[1] = n3[2] = 0.0;
521             n4[0] = n4[1] = n4[2] = 0.0;
522             n5[0] = n5[1] = n5[2] = 0.0;
523
524             count = 1;
525             calc_normal(nodes[tris[t1][0]], nodes[tris[t1][1]], 
526                         nodes[tris[t1][2]], n1);
527
528             if ( t2 > 0 ) {
529                 calc_normal(nodes[tris[t2][0]], nodes[tris[t2][1]], 
530                             nodes[tris[t2][2]], n2);
531                 count = 2;
532             }
533
534             if ( t3 > 0 ) {
535                 calc_normal(nodes[tris[t3][0]], nodes[tris[t3][1]],
536                             nodes[tris[t3][2]], n3);
537                 count = 3;
538             }
539
540             if ( t4 > 0 ) {
541                 calc_normal(nodes[tris[t4][0]], nodes[tris[t4][1]],
542                             nodes[tris[t4][2]], n4);
543                 count = 4;
544             }
545
546             if ( t5 > 0 ) {
547                 calc_normal(nodes[tris[t5][0]], nodes[tris[t5][1]],
548                             nodes[tris[t5][2]], n5);
549                 count = 5;
550             }
551
552             // printf("  norm[2] = %.2f %.2f %.2f\n", n1[2], n2[2], n3[2]);
553
554             norm[0] = ( n1[0] + n2[0] + n3[0] + n4[0] + n5[0] ) / (double)count;
555             norm[1] = ( n1[1] + n2[1] + n3[1] + n4[1] + n5[1] ) / (double)count;
556             norm[2] = ( n1[2] + n2[2] + n3[2] + n4[2] + n5[2] ) / (double)count;
557         
558             //  printf("  count = %d\n", count);
559             //  printf("  Ave. normal = %.4f %.4f %.4f\n", 
560             //         norm[0], norm[1], norm[2]);*/
561             MAT3_NORMALIZE_VEC(norm, temp);
562             //  printf("  Normalized ave. normal = %.4f %.4f %.4f\n", 
563             //         norm[0], norm[1], norm[2]);
564         }
565         // printf("%d vn %.4f %.4f %.4f\n", i, norm[0], norm[1], norm[2]);
566         fprintf(obj, "vn %.4f %.4f %.4f\n", norm[0], norm[1], norm[2]);
567     }
568
569     // dump faces
570     printf("  writing faces\n");
571     for ( i = 1; i <= tricount; i++ ) {
572         fprintf(obj, "f %d %d %d\n", tris[i][0], tris[i][1], tris[i][2]);
573     }
574
575     fclose(obj);
576 }
577
578 int main(int argc, char **argv) {
579     char basename[256], basepath[256], temp[256];
580     long int tmp_index;
581     int len;
582
583     strcpy(basename, argv[1]);
584
585     // find the base path of the file
586     extract_path(basename, basepath);
587     extract_path(basepath, basepath);
588     extract_path(basepath, basepath);
589     printf("%s\n", basepath);
590
591     // find the index of the current file
592     extract_file(basename, temp);
593     len = strlen(temp);
594     if ( len >= 2 ) {
595         temp[len-2] = '\0';
596     }
597     tmp_index = atoi(temp);
598     printf("%ld\n", tmp_index);
599     fgBucketParseIndex(tmp_index, &my_index);
600
601     printf("bucket = %d %d %d %d\n", 
602            my_index.lon, my_index.lat, my_index.x, my_index.y);
603     // generate the indexes of the neighbors
604     fgBucketOffset(&my_index, &ne_index,  1,  1);
605     fgBucketOffset(&my_index, &nw_index, -1,  1);
606     fgBucketOffset(&my_index, &se_index,  1, -1);
607     fgBucketOffset(&my_index, &sw_index, -1, -1);
608
609     fgBucketOffset(&my_index, &north_index,  0,  1);
610     fgBucketOffset(&my_index, &south_index,  0, -1);
611     fgBucketOffset(&my_index, &east_index,  1,  0);
612     fgBucketOffset(&my_index, &west_index, -1,  0);
613
614     // load the input data files
615     triload(basename, basepath);
616
617     // dump in WaveFront .obj format
618     dump_obj(basename);
619
620     return(0);
621 }
622
623
624 // $Log$
625 // Revision 1.3  1998/10/19 19:33:31  curt
626 // C++-ification.
627 //
628 // Revision 1.2  1998/10/18 01:17:29  curt
629 // Point3D tweaks.
630 //
631 // Revision 1.1  1998/07/08 14:54:53  curt
632 // renamed *.[ch] to *.[ch]xx
633 //
634 // Revision 1.17  1998/07/04 00:56:40  curt
635 // typedef'd struct fgBUCKET.
636 //
637 // Revision 1.16  1998/05/23 15:20:41  curt
638 // Output more digits after the decimal place.
639 //
640 // Revision 1.15  1998/05/02 01:54:39  curt
641 // Converting to polar3d.h routines.
642 //
643 // Revision 1.14  1998/04/18 04:01:32  curt
644 // Now use libMath rather than having local copies of math routines.
645 //
646 // Revision 1.13  1998/04/14 02:26:11  curt
647 // Code reorganizations.  Added a Lib/ directory for more general libraries.
648 //
649 // Revision 1.12  1998/04/08 23:22:18  curt
650 // Adopted Gnu automake/autoconf system.
651 //
652 // Revision 1.11  1998/03/03 16:01:00  curt
653 // More c++ compile tweaks.
654 //
655 // Revision 1.10  1998/01/31 00:41:27  curt
656 // Made a few changes converting floats to doubles.
657 //
658 // Revision 1.9  1998/01/27 18:37:04  curt
659 // Lots of updates to get back in sync with changes made over in .../Src/
660 //
661 // Revision 1.8  1998/01/17 01:25:39  curt
662 // Added support for shared normals.
663 //
664 // Revision 1.7  1998/01/12 02:42:00  curt
665 // Average up to five triangles per vertex instead of three.
666 //
667 // Revision 1.6  1998/01/09 23:03:15  curt
668 // Restructured to split 1deg x 1deg dem's into 64 subsections.
669 //
670 // Revision 1.5  1997/12/08 19:17:50  curt
671 // Fixed a type in the normal generation code.
672 //
673 // Revision 1.4  1997/12/02 13:13:32  curt
674 // Fixed problem with averaged vertex normals.
675 //
676 // Revision 1.3  1997/11/15 18:05:05  curt
677 // minor tweaks ...
678 //
679 // Revision 1.2  1997/11/14 00:29:13  curt
680 // Transform scenery coordinates at this point in pipeline when scenery is
681 // being translated to .obj format, not when it is being loaded into the end
682 // renderer.  Precalculate normals for each node as average of the normals
683 // of each containing polygon so Garoude shading is now supportable.
684 //
685 // Revision 1.1  1997/10/29 23:05:15  curt
686 // Initial revision.
687 //
688