1 /***********************************************************************
2  *	z:/wata/src/a/csmash/loadparts.cpp
3  *	$Id: loadparts.cpp,v 1.14 2003/11/19 16:49:31 nan Exp $
4  *
5  *	Copyright by ESESoft.
6  *
7  *	Redistribution and use in source and binary forms, with or without
8  *	modification, are permitted provided that the following conditions
9  *	are met:
10  *
11  *	Redistributions of source code must retain the above copyright
12  *	notice, this list of conditions and the following disclaimer.
13  *
14  *	Redistributions in binary form must reproduce the above copyright
15  *	notice, this list of conditions and the following disclaimer
16  *	in the documentation and/or other materials provided with the
17  *	distribution.
18  *
19  *	The name of the author may not be used to endorse or promote
20  *	products derived from this software without specific prior written
21  *	permission.
22  *
23  *	THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
24  *	OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25  *	WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  *	ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
27  *	DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  *	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29  *	GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  *	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31  *	WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32  *	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33  *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34  *
35  ***********************************************************************/
36 #include "ttinc.h"
37 
38 #include <iostream>
39 #include <string>
40 #include <map>
41 #include <list>
42 #include <algorithm>
43 
44 #include "float"
45 #include "matrix"
46 #include "affine"
47 
48 #include "LoadImage.h"
49 #include "parts.h"
50 #include "loadparts.h"
51 
52 // for(;;) namescoping hack.
53 // VC++ 6 is not compliant with latest ANSI C++ (but VC++7 does).
54 #if defined(_MSC_VER) && (_MSC_VER <= 1200)
55 # define for if(0);else for
56 #endif
57 #define elif else if
58 
59 #define BEGIN_ANONYMOUS namespace {
60 #define END_ANONYMOUS }
61 
62 /***********************************************************************
63  *	Local data and functions
64  ***********************************************************************/
65 BEGIN_ANONYMOUS
66 
67 template <typename T>
between(const T & a,const T & x,const T & b)68 inline bool between(const T& a, const T& x, const T& b) {
69     return a <= x && x <= b;
70 }
71 
clamp(long a,long x,long b)72 inline const long clamp(long a, long x, long b) {
73     if (a > x) return a;
74     elif (b < x) return b;
75     else return x;
76 }
77 
streq(const char * a,const char * b)78 inline bool streq(const char *a, const char *b) {
79     return 0 == strcmp(a, b);
80 }
81 
strieq(const char * a,const char * b)82 inline bool strieq(const char *a, const char *b) {
83 #ifdef _WIN32
84     return 0 == _stricmp(a, b);
85 #else
86     return 0 == strcasecmp(a, b);
87 #endif
88 }
89 
90 struct auto_fp
91 {
92     FILE *fp;
93     bool needclose;
auto_fpauto_fp94     inline auto_fp(FILE *fp, bool b = true) : fp(fp), needclose(b) {}
~auto_fpauto_fp95     inline ~auto_fp() { if (needclose && fp) fclose(fp); }
operator FILE*auto_fp96     inline operator FILE*() { return fp; }
operator !auto_fp97     inline bool operator!() const { return !fp; }
98 };
99 
loadAffine4F(const char * str,affine4F * pm)100 bool loadAffine4F(const char *str, affine4F *pm)
101 {
102     FILE *fp = fopen(str, "r");
103     if (!fp) return false;
104 
105     affine4F &m = *pm;
106     m = affine4F(1);
107     const char *delim = " \t(,);\r\n";
108     char line[256];
109     int i = 0;
110     while (NULL != fgets(line, sizeof(line), fp)) {
111 	const char *token = strtok(line, delim);
112 	if (!token) continue;
113 	do {
114 	    if (strieq("affine3", token)) continue;
115 	    Float f = (Float)strtod(token, NULL);
116 	    int x = i / 3;
117 	    int y = i % 3;
118 	    m[x][y] = f;
119 	    if (12 == ++i) break;
120 	}
121 	while ((token = strtok(NULL, delim)));
122     }
123     fclose(fp);
124     if (12 != i) return false;
125 
126     return true;
127 }
128 
129 END_ANONYMOUS
130 /***********************************************************************
131  *	Class parts
132  ***********************************************************************/
133 // symbol table
134 #define SYM(A) { parts::sym_##A, #A }
135 static struct symtab_t {
136     parts::symbol_t sym;
137     const char *str;
138 } symtab[] = {
139     SYM(null), SYM(load), SYM(create), SYM(polyhedron),
140     SYM(anim), SYM(texture), SYM(body),
141     { parts::sym_unknown, NULL },
142     { parts::sym_unknown, "NanTheBLACK, our guru:-p" }
143 };
144 #undef SYM
145 
146 // parts object store
147 static parts_map partsmap;
148 
getsym(const char * str)149 parts::symbol_t parts::getsym(const char *str)
150 {
151     if (!str) return sym_unknown;
152     for (symtab_t *p = symtab; p->str; ++p) {
153 	if (streq(p->str, str)) return p->sym;
154     }
155     return sym_unknown;
156 }
157 
sym2str(parts::symbol_t sym)158 const char* parts::sym2str(parts::symbol_t sym)
159 {
160     for (symtab_t *p = symtab; p->str; ++p) {
161 	if (p->sym == sym) return p->str;
162     }
163     return "sym_unknown";
164 }
165 
getobject(const char * name)166 parts* parts::getobject(const char* name)
167 {
168     const parts_map::iterator i = partsmap.find(name);
169     if (partsmap.end() == i) return NULL;
170     else return i->second;
171 }
172 
addobject(const char * name,parts * p)173 bool parts::addobject(const char *name, parts* p)
174 {
175     const parts_map::iterator i = partsmap.find(name);
176     if (partsmap.end() != i) return false;
177 
178     p->name = name;
179     partsmap[name] = p;
180     return true;
181 }
182 
delobject(const char * name)183 bool parts::delobject(const char *name)
184 {
185     parts_map::iterator i = partsmap.find(name);
186     if (partsmap.end() == i) return false;
187     delete i->second;
188     partsmap.erase(i);
189     return true;
190 }
191 
clearobjects()192 void parts::clearobjects()
193 {
194     partsmap.clear();
195 }
196 
realizeobjects()197 bool parts::realizeobjects()
198 {
199     bool r = true;
200     for (parts_map::iterator i = partsmap.begin(); i != partsmap.end(); ++i) {
201 	r &= (i->second)->realize();
202     }
203     return r;
204 }
205 
unrealizeobjects()206 void parts::unrealizeobjects()
207 {
208     for (parts_map::iterator i = partsmap.begin(); i != partsmap.end(); ++i) {
209 	(i->second)->unrealize();
210     }
211 }
212 
loadobjects(const char * str)213 bool parts::loadobjects(const char *str)
214 {
215     try {
216 	loadfile(str);
217     }
218     catch (const error &e) {
219 	printf("loadfile failed\n");
220 	printf(e.what());
221 	return false;
222     }
223     return true;
224 }
225 
loadfile(const char * str)226 bool parts::loadfile(const char *str)
227 {
228     auto_fp fp(fopen(str, "r"));
229     if (!fp) return false;
230 
231     int lineno = 0;
232     do {
233 	const char *delim = " \t\r\n;";
234 	char line[4096];
235 	fgets(line, sizeof(line), fp);
236 	if (feof((FILE*)fp)) break;
237 	++lineno;
238 	int l = strlen(line);
239 	int addline = 0;
240 
241 	while (l > 0 && (line[l-1] == '\r' || line[l-1] == '\n')) {
242 	    line[--l] = '\0';
243 	}
244 
245 	while ('\\' == line[l-1]) {
246             // concat next line(s)
247 	    int bufsize = clamp(0U, sizeof(line)-l, sizeof(line)-1);
248 	    fgets(&line[l-2], bufsize, fp);
249 	    if (feof((FILE*)fp)) break;
250 	    l = strlen(line);
251 	    while (l > 0 && (line[l-1] == '\r' || line[l-1] == '\n')) {
252 		line[--l] = '\0';
253 	    }
254 	    ++addline;
255 	}
256 
257 	int argc = 0;
258 	const char *argv[256];
259 	const char *token = strtok(line, delim);
260         const int argcmax = sizeof(argv) / sizeof(const char*);
261 	if (!token || '#' == *token) continue;
262 	do {
263 	    argv[argc++] = token;
264 	    if (argcmax == argc) {
265                 throw verror(lineno, "This line has %d or more arguments\n", argcmax);
266 	    }
267 	} while ((token = strtok(NULL, delim)));
268 	argv[argc] = NULL;
269 	int optind = 0;
270 
271 	token = argv[optind++];
272 	symbol_t sym = getsym(token);
273 	switch (sym) {
274 	case sym_load:
275 	    load_load(lineno, argc, argv, &optind); break;
276 	case sym_create:
277 	    load_create(lineno, argc, argv, &optind); break;
278 	default:
279             throw verror(lineno, "error unknown command %s\n", token);
280 	}
281 	lineno += addline;
282     } while (!ferror((FILE*)fp));
283 
284     return true;
285 }
286 
load_load(int lineno,int argc,const char * argv[],int * poptind)287 bool parts::load_load(int lineno, int argc, const char *argv[], int* poptind)
288 {
289     int& optind = *poptind;
290 
291     const char *token = argv[optind++];
292     if (!token) {
293 	throw verror(lineno, "error type not specified\n");
294     }
295     symbol_t sym = getsym(token);
296 
297     const char *objectname = argv[optind++];
298     if (!objectname) {
299         throw verror(lineno, "object name is not specified\n");
300     }
301 
302     const char *filename = argv[optind++];
303     switch (sym) {
304     case sym_texture: {
305 	texture_parts* object = new texture_parts(objectname);
306 	if (!addobject(objectname, object)) {
307 	    delete object;
308             throw verror(lineno, "%s is already loaded\n", objectname);
309 	}
310 	object->load(filename);
311 	break;
312     } /* texture */
313 
314     case sym_polyhedron: {
315 	polyhedron_parts *object = new polyhedron_parts(objectname);
316 	if (!addobject(objectname, object)) {
317 	    delete object;
318             throw verror(lineno, "%s is already loaded\n", objectname);
319 	}
320 	object->load(filename);
321 	load_polyhedron(lineno, object, argc, argv, &optind);
322 
323 	break;
324     }	/* polyhedron */
325 
326     case sym_anim: {
327 	anim_parts *object = new anim_parts(objectname);
328 	if (!addobject(objectname, object)) {
329 	    delete object;
330             throw verror(lineno, "%s is already loaded\n", objectname);
331 	}
332 	object->load(filename);
333 	load_anim(lineno, object, argc, argv, &optind);
334 
335 	break;
336     }	 /* anim */
337 
338     default:
339         throw verror(lineno, "error unknown type(%s) specified\n", token);
340     }
341 
342     return true;
343 }
344 
load_polyhedron(int lineno,polyhedron_parts * object,int argc,const char * argv[],int * poptind)345 bool parts::load_polyhedron(int lineno, polyhedron_parts *object,
346 			    int argc, const char* argv[], int *poptind)
347 {
348     int &optind = *poptind;
349     const char *option;
350     while ((option = argv[optind++])) {
351 	if ('-' != *option) {
352             throw verror(lineno, "unknown option %s\n", option);
353 	}
354 	const char *operand = argv[optind++];
355 	if (!operand) {
356             throw verror(lineno, "no operadnd for %s\n", option);
357 	}
358 	switch (option[1]) {
359 	case 'c': {	/* colormap */
360 	    colormap cmap;
361 	    if (!cmap.load(operand)) {
362                 throw verror(lineno, "could not load colormap %s\n", operand);
363 	    }
364 	    object->object->cmap = cmap;
365 	    break;
366 	}
367 	case 't': {	/* texture */
368 	    parts *tex = getobject(operand);
369 	    if (!tex) {
370                 throw verror(lineno, "texture %s not loaded\n", operand);
371 	    }
372 	    if (!object->assign(tex)) {
373                 throw verror(lineno, "%s is not assignable\n", operand);
374 	    }
375 	    break;
376 	}
377 	case 'm': {     /* matrix */
378 	    affine4F m;
379 	    if (!loadAffine4F(operand, &m)) {
380                 throw verror(lineno, "matrix %s cannot be loaded\n", operand);
381             }
382 	    *object->object *= m;
383 
384 	    break;
385 	}
386 	default:
387             throw verror(lineno, "unknown option %s\n", option);
388 	}
389     }
390     // create normal vectors of polyhedron
391     object->object->getNormal();
392     return true;
393 }
394 
load_anim(int lineno,anim_parts * object,int argc,const char * argv[],int * poptind)395 bool parts::load_anim(int lineno, anim_parts* object,
396 		      int argc, const char *argv[], int *poptind)
397 {
398     int& optind = *poptind;
399     int i = 0;
400     while (const char *name = argv[optind++]) {
401 	if ('-' == *name) {
402 	    if (streq("-pre", name) || streq("-post", name)) {
403 		affine4F m;
404 		const char *fname = argv[optind++];
405 		if (!fname || !loadAffine4F(fname, &m)) {
406 		    throw verror(lineno, "mat %s cannot be loaded\n", fname);
407 		}
408 		affineanim &anim = *object->object;
409 		for (int i = 0; anim.numFrames > i; ++i) {
410 		    if (streq("-pre", name)) {
411 			anim.matrices[i] = m * anim.matrices[i];
412 		    } else {
413 			anim.matrices[i] *= m;
414 		    }
415 		}
416 	    }
417 	    else {
418 		throw verror(lineno, "unknown option %s\n", name);
419 	    }
420 	} else {
421 	    parts *poly = getobject(name);
422 	    if (!poly) {
423                 throw verror(lineno, "%s not loaded\n", name);
424 	    }
425 	    if (!object->assign(poly)) {
426                 throw verror(lineno, "%s is not assignable\n", name);
427 	    }
428 	    ++i;
429 	}
430     }
431     if (!i) {
432 	printf("%d: %s is empty object\n", lineno, object->name.c_str());
433     }
434     return true;
435 }
436 
load_create(int lineno,int argc,const char * argv[],int * poptind)437 bool parts::load_create(int lineno, int argc, const char *argv[], int *poptind)
438 {
439     int &optind = *poptind;
440     const char *token = argv[optind++];
441     if (!token) {
442         throw verror(lineno, "object type is not specified\n");
443     }
444     switch (getsym(token)) {
445     case sym_body: {
446 	const char *objectname = argv[optind++];
447 	if (!objectname) {
448             throw verror(lineno, "object name is not specified\n");
449 	}
450 	body_parts *object = new body_parts(objectname);
451 	if (!addobject(objectname, object)) {
452             throw verror(lineno, "%s is already loaded\n", objectname);
453 	}
454 	int i = 0;
455 	while ((token = argv[optind++])) {
456 	    parts* p = getobject(token);
457 	    if (!p) {
458                 throw verror(lineno, "%s is not loaded\n", token);
459 	    }
460 	    object->assign(p);
461 	    ++i;
462 	}
463 	if (!i) {
464             printf("%d: %s is empty\n", lineno, objectname);
465 	}
466 	break;
467     }
468     default:
469         throw verror("type %s cannot be created\n", token);
470     }
471     return true;
472 }
473 
474 /***********************************************************************
475  *	Class texture_parts
476  ***********************************************************************/
load(const char * str)477 bool texture_parts::load(const char *str)
478 {
479     filename = str;
480     return true;
481 }
482 
unrealize()483 void texture_parts::unrealize()
484 {
485     if (object) {
486 	glDeleteTextures(1, &object);
487 	object = 0;
488     }
489 }
490 
realize()491 bool texture_parts::realize()
492 {
493     if (object) return true;
494 
495     static int allowedsize[] = {
496 	64, 128, 130, 256, 512, 0
497     };
498 
499     ImageData img;
500     bool loaded;
501 
502     loaded = img.LoadFile(filename.c_str());
503 
504     if (!loaded) {
505         throw verror("could not load texture %s\n", filename.c_str());
506     }
507     int width = img.GetWidth();
508     int height = img.GetHeight();
509     int i, j;
510     for (i = 0; 0 != allowedsize[i]; ++i) {
511 	if (width == allowedsize[i]) break;
512     }
513     for (j = 0; 0 != allowedsize[i]; ++j) {
514 	if (height == allowedsize[i]) break;
515     }
516     if (0 == allowedsize[i] || 0 == allowedsize[j]) {
517         throw verror("texture %s has illegal size(%d,%d)\n",
518                      filename.c_str(), width, height);
519     }
520 
521     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
522     glGenTextures(1, &object);
523     glBindTexture(GL_TEXTURE_2D, object);
524     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
525     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
526     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
527     glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
528     glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
529     glTexImage2D(GL_TEXTURE_2D, 0, 3, width, height, 0,
530 		 GL_RGBA, GL_UNSIGNED_BYTE, img.GetImage());
531 
532     if (0 == object) {
533 	char buf[256];
534 	snprintf(buf, sizeof(buf), "texture %s cannot be realized\n",
535                  filename.c_str());
536 	printf(buf);
537 	throw error(buf);
538         return false;
539     } else {
540         return true;
541     }
542 }
543 
544 /***********************************************************************
545  *	Class polyhedron_parts
546  ***********************************************************************/
load(const char * str)547 bool polyhedron_parts::load(const char *str)
548 {
549     object = new polyhedron(str);
550     if (!object->points) {
551 	delete object;
552 	object = NULL;
553         throw verror("polyhedron %s cannot be loaded\n", str);
554     }
555     return true;
556 }
557 
assign(parts * a)558 bool polyhedron_parts::assign(parts* a)
559 {
560     switch (a->type()) {
561     case sym_texture:
562 	tex = reinterpret_cast<texture_parts*>(a);
563 	break;
564     default:
565         throw verror("%s(%s) cannot be assigned to polyhedron(%s)\n",
566 		 a->name.c_str(), a->typestr(), name.c_str());
567     }
568     return true;
569 }
570 
render() const571 void polyhedron_parts::render() const
572 {
573     float NanTheBLACK[4] = { 0, 0, 0, 1 };
574     float ManOfVirtue[4] = { 1, 1, 1, 1 };
575 
576     polyhedron &poly = *object;
577     if (tex && poly.texcoord && tex->object) {
578 	glEnable(GL_TEXTURE_2D);
579 	glBindTexture(GL_TEXTURE_2D, tex->object);
580 //	glColor4fv(ManOfVirtue);
581 	for (int i = 0; poly.numPolygons > i; ++i) {
582 	    const polygon &face = poly.getPolygon(i);
583 	    glBegin(face.glBeginSize());
584 	    for (int j = 0; face.size > j; ++j) {
585 		poly.cmap[face.c()].glBind();
586 		glNormal3fv((float*)&face.rn(j));
587 		glTexCoord2fv((float*)&face.rst(j));
588 		glVertex3fv((float*)&face.rv(j));
589 	    }
590 	    glEnd();
591 	}
592     } else {
593 	glDisable(GL_TEXTURE_2D);
594 	for (int i = 0; poly.numPolygons > i; ++i) {
595 	    const polygon &face = poly.getPolygon(i);
596 	    glBegin(face.glBeginSize());
597 	    for (int j = 0; face.size > j; ++j) {
598 		poly.cmap[face.c()].glBind();
599 		glNormal3fv((float*)&face.rn(j));
600 		glVertex3fv((float*)&face.rv(j));
601 	    }
602 	    glEnd();
603 	}
604     }
605 }
606 
renderWire(const vector3F & origin) const607 void polyhedron_parts::renderWire(const vector3F& origin) const
608 {
609     polyhedron &poly = *object;
610 
611     glBegin(GL_LINES);
612     for (int i = 0; poly.numEdges > i; ++i) {
613 	int p0 = poly.edges[i].p0;
614 	int p1 = poly.edges[i].p1;
615 	bool draw = false;
616 	if (p1 >= 0) {
617             // Render if one polygon on the side of edge is visible
618             // while other side is not visible.
619             vector3F v = poly.points[poly.edges[i].v0] - origin;
620 	    Float i0 = v * poly.planeNormal[p0];
621 	    Float i1 = v * poly.planeNormal[p1];
622 	    if (i0 * i1 <= 0) draw = true;
623 	} else {
624             // This edge has a polygon only on one side.
625 	    draw = true;
626 	}
627 	if (draw) {
628 	    vector3F &v0 = poly.points[poly.edges[i].v0];
629 	    vector3F &v1 = poly.points[poly.edges[i].v1];
630 	    glVertex3fv((float*)&v0);
631 	    glVertex3fv((float*)&v1);
632 	}
633     }
634     glEnd();
635 }
636 /***********************************************************************
637  *	Class anim_parts
638  ***********************************************************************/
load(const char * str)639 bool anim_parts::load(const char *str)
640 {
641     object = new affineanim(str);
642     if (!object->matrices) {
643         throw verror("could not load anim %s\n", str);
644     }
645     return true;
646 }
647 
assign(parts * a)648 bool anim_parts::assign(parts* a)
649 {
650     switch (a->type()) {
651     case sym_polyhedron:
652 	poly.push_back(reinterpret_cast<polyhedron_parts*>(a));
653 	break;
654     default:
655 	throw verror("%s(%s) cannot be assigned to anim(%s)\n",
656                      a->name.c_str(), a->typestr(), name.c_str());
657     }
658     return true;
659 }
660 
render(int frame) const661 void anim_parts::render(int frame) const
662 {
663     affineanim &anim = *object;
664     glPushMatrix();
665 #ifdef CHIYO
666     glTranslatef(0,0,0.1F);	// Her shoes go underground without this:-)
667 #endif
668     glMultMatrixf((float*)&anim[frame]);
669     for (std::list<polyhedron_parts*>::const_iterator i = poly.begin();
670 	 poly.end() != i; ++i) {
671 	(*i)->render();
672     }
673     glPopMatrix();
674 }
675 
renderWire(int frame) const676 void anim_parts::renderWire(int frame) const
677 {
678     affineanim &anim = *object;
679     glPushMatrix();
680 #ifdef CHIYO
681     glTranslatef(0,0,0.1F);	// Her shoes go underground without this:-)
682 #endif
683     glMultMatrixf((float*)&anim[frame]);
684 
685     affine4F t;
686     glGetFloatv(GL_MODELVIEW_MATRIX, (float*)&t);
687     vector3F origin = vector3F(0) * ~t;
688 
689     for (std::list<polyhedron_parts*>::const_iterator i = poly.begin();
690 	 poly.end() != i; ++i) {
691 	(*i)->renderWire(origin);
692     }
693     glPopMatrix();
694 }
695 
696 /***********************************************************************
697  *	Class body_parts
698  ***********************************************************************/
assign(parts * a)699 bool body_parts::assign(parts* a)
700 {
701     switch (a->type()) {
702     case sym_anim:
703 	object.push_back(reinterpret_cast<anim_parts*>(a));
704 	break;
705     default:
706         throw verror("%s(%s) cannot be assigned to body (%s)\n",
707                      a->name.c_str(), a->typestr(), name.c_str());
708     }
709     return true;
710 }
711 
render(int frame) const712 void body_parts::render(int frame) const
713 {
714     for (std::list<anim_parts*>::const_iterator i = object.begin();
715 	 object.end() != i; ++i) {
716 	(*i)->render(frame);
717     }
718 }
719 
renderWire(int frame) const720 void body_parts::renderWire(int frame) const
721 {
722     for (std::list<anim_parts*>::const_iterator i = object.begin();
723 	 object.end() != i; ++i) {
724 	(*i)->renderWire(frame);
725     }
726 }
727 
728 /***********************************************************************
729  *	END OF loadparts.cpp
730  ***********************************************************************/
731