1 /**
2  * @file
3  * @brief ASE model loading
4  */
5 
6 /*
7 Copyright (C) 1999-2007 id Software, Inc. and contributors.
8 For a list of contributors, see the accompanying CONTRIBUTORS file.
9 
10 This file is part of GtkRadiant.
11 
12 GtkRadiant is free software; you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation; either version 2 of the License, or
15 (at your option) any later version.
16 
17 GtkRadiant is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 GNU General Public License for more details.
21 
22 You should have received a copy of the GNU General Public License
23 along with GtkRadiant; if not, write to the Free Software
24 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
25 */
26 
27 #include "aselib.h"
28 #include "../bsp.h"
29 #include "shared.h"
30 
31 #define MAX_ASE_MATERIALS			32
32 #define MAX_ASE_OBJECTS				64
33 #define MAX_ASE_ANIMATIONS			32
34 #define MAX_ASE_ANIMATION_FRAMES	512
35 
36 #define VERBOSE(x) { if (ase.verbose) { Com_Printf x; } }
37 
38 typedef struct {
39 	float x, y, z;
40 	float nx, ny, nz;
41 	float s, t;
42 } aseVertex_t;
43 
44 typedef struct {
45 	float s, t;
46 } aseTVertex_t;
47 
48 typedef int aseFace_t[3];
49 
50 typedef struct {
51 	int numFaces;
52 	int numVertexes;
53 	int numTVertexes;
54 
55 	int timeValue;
56 
57 	aseVertex_t* vertexes;
58 	aseTVertex_t* tvertexes;
59 	aseFace_t* faces, *tfaces;
60 
61 	int currentFace, currentVertex;
62 } aseMesh_t;
63 
64 typedef struct {
65 	int numFrames;
66 	aseMesh_t frames[MAX_ASE_ANIMATION_FRAMES];
67 
68 	int currentFrame;
69 } aseMeshAnimation_t;
70 
71 typedef struct {
72 	char name[MAX_QPATH];
73 } aseMaterial_t;
74 
75 /**
76  * @brief contains the animate sequence of a single surface using a single material
77  */
78 typedef struct {
79 	char name[MAX_QPATH];
80 
81 	int materialRef;
82 	int numAnimations;
83 
84 	aseMeshAnimation_t anim;
85 } aseGeomObject_t;
86 
87 typedef struct {
88 	int numMaterials;
89 	aseMaterial_t materials[MAX_ASE_MATERIALS];
90 	aseGeomObject_t objects[MAX_ASE_OBJECTS];
91 
92 	char* buffer;
93 	char* curpos;
94 	int len;
95 
96 	int currentObject;
97 	bool verbose;
98 } ase_t;
99 
100 static char s_token[1024];
101 static ase_t ase;
102 
103 static void ASE_Process(void);
104 static void ASE_FreeGeomObject(int ndx);
105 
ASE_Load(const char * filename,bool verbose)106 void ASE_Load (const char* filename, bool verbose)
107 {
108 	ScopedFile file;
109 	FS_OpenFile(filename, &file, FILE_READ);
110 	if (!file)
111 		Sys_Error("File not found '%s'", filename);
112 
113 	OBJZERO(ase);
114 
115 	ase.verbose = verbose;
116 	ase.len = FS_FileLength(&file);
117 
118 	ase.curpos = ase.buffer = Mem_AllocTypeN(char, ase.len);
119 	if (!ase.curpos)
120 		Sys_Error("Could not allocate memory for ase loading");
121 
122 	Verb_Printf(VERB_EXTRA, "Processing '%s'\n", filename);
123 
124 	if (FS_Read(ase.buffer, ase.len, &file) != 1) {
125 		Sys_Error("fread() != -1 for '%s'", filename);
126 	}
127 
128 	ASE_Process();
129 }
130 
ASE_Free(void)131 void ASE_Free (void)
132 {
133 	int i;
134 
135 	for (i = 0; i < ase.currentObject; i++)
136 		ASE_FreeGeomObject(i);
137 }
138 
ASE_GetNumSurfaces(void)139 int ASE_GetNumSurfaces (void)
140 {
141 	return ase.currentObject;
142 }
143 
ASE_GetSurfaceName(int which)144 const char* ASE_GetSurfaceName (int which)
145 {
146 	aseGeomObject_t* pObject = &ase.objects[which];
147 
148 	if (!pObject->anim.numFrames)
149 		return 0;
150 
151 	return pObject->name;
152 }
153 
154 /**
155  * @brief Returns an animation (sequence of polysets)
156  */
ASE_GetSurfaceAnimation(int whichSurface)157 polyset_t* ASE_GetSurfaceAnimation (int whichSurface)
158 {
159 	aseGeomObject_t* pObject = &ase.objects[whichSurface];
160 	polyset_t* psets;
161 	int numFramesInAnimation;
162 	int i, f;
163 
164 	if (!pObject->anim.numFrames)
165 		return 0;
166 
167 	numFramesInAnimation = pObject->anim.numFrames;
168 
169 	psets = Mem_AllocTypeN(polyset_t, numFramesInAnimation);
170 
171 	for (f = 0, i = 0; i < numFramesInAnimation; i++) {
172 		int t;
173 		aseMesh_t* pMesh = &pObject->anim.frames[i];
174 		polyset_t* ps = &psets[f];
175 
176 		strcpy(ps->name, pObject->name);
177 		strcpy(ps->materialname, ase.materials[pObject->materialRef].name);
178 
179 		ps->triangles = Mem_AllocTypeN(triangle_t, pObject->anim.frames[i].numFaces);
180 		ps->numtriangles = pObject->anim.frames[i].numFaces;
181 
182 		for (t = 0; t < pObject->anim.frames[i].numFaces; t++) {
183 			int k;
184 
185 			for (k = 0; k < 3; k++) {
186 				triangle_t* tri = &ps->triangles[t];
187 				const int vIdx = pMesh->faces[t][k];
188 				aseVertex_t* v = &pMesh->vertexes[vIdx];
189 				VectorSet(tri->verts[k], v->x, v->y, v->z);
190 
191 				if (pMesh->tvertexes && pMesh->tfaces) {
192 					aseTVertex_t* tv = &pMesh->tvertexes[pMesh->tfaces[t][k]];
193 					Vector2Set(tri->texcoords[k], tv->s, tv->t);
194 				}
195 			}
196 		}
197 
198 		f++;
199 	}
200 
201 	return psets;
202 }
203 
ASE_FreeGeomObject(int ndx)204 static void ASE_FreeGeomObject (int ndx)
205 {
206 	aseGeomObject_t* pObject;
207 	int i;
208 
209 	pObject = &ase.objects[ndx];
210 
211 	for (i = 0; i < pObject->anim.numFrames; i++) {
212 		Mem_Free(pObject->anim.frames[i].vertexes);
213 		Mem_Free(pObject->anim.frames[i].tvertexes);
214 		Mem_Free(pObject->anim.frames[i].faces);
215 		Mem_Free(pObject->anim.frames[i].tfaces);
216 	}
217 
218 	OBJZERO(*pObject);
219 }
220 
ASE_GetCurrentMesh(void)221 static aseMesh_t* ASE_GetCurrentMesh (void)
222 {
223 	aseGeomObject_t* pObject;
224 
225 	if (ase.currentObject >= MAX_ASE_OBJECTS)
226 		Sys_Error("Too many GEOMOBJECTs");
227 
228 	pObject = &ase.objects[ase.currentObject];
229 
230 	if (pObject->anim.currentFrame >= MAX_ASE_ANIMATION_FRAMES)
231 		Sys_Error("Too many MESHes");
232 
233 	return &pObject->anim.frames[pObject->anim.currentFrame];
234 }
235 
CharIsTokenDelimiter(int ch)236 static inline int CharIsTokenDelimiter (int ch)
237 {
238 	if (ch <= ' ')
239 		return 1;
240 	return 0;
241 }
242 
ASE_GetToken(bool restOfLine)243 static int ASE_GetToken (bool restOfLine)
244 {
245 	int i = 0;
246 
247 	if (ase.buffer == 0)
248 		return 0;
249 
250 	if ((ase.curpos - ase.buffer) == ase.len)
251 		return 0;
252 
253 	/* skip over crap */
254 	while (ase.curpos - ase.buffer < ase.len && *ase.curpos <= ' ') {
255 		ase.curpos++;
256 	}
257 
258 	while ((ase.curpos - ase.buffer) < ase.len) {
259 		s_token[i] = *ase.curpos;
260 
261 		ase.curpos++;
262 		i++;
263 
264 		if ((CharIsTokenDelimiter(s_token[i - 1]) && !restOfLine) ||
265 			(s_token[i - 1] == '\n' || s_token[i - 1] == '\r')) {
266 			s_token[i - 1] = 0;
267 			break;
268 		}
269 	}
270 
271 	s_token[i] = 0;
272 
273 	return 1;
274 }
275 
ASE_ParseBracedBlock(void (* parser)(const char * token))276 static void ASE_ParseBracedBlock (void (*parser)(const char* token))
277 {
278 	int indent = 0;
279 
280 	while (ASE_GetToken(false)) {
281 		if (Q_streq(s_token, "{")) {
282 			indent++;
283 		} else if (Q_streq(s_token, "}")) {
284 			--indent;
285 			if (indent == 0)
286 				break;
287 			else if (indent < 0)
288 				Sys_Error("Unexpected '}'");
289 		} else {
290 			if (parser)
291 				parser(s_token);
292 		}
293 	}
294 }
295 
ASE_SkipEnclosingBraces(void)296 static void ASE_SkipEnclosingBraces (void)
297 {
298 	int indent = 0;
299 
300 	while (ASE_GetToken(false)) {
301 		if (Q_streq(s_token, "{")) {
302 			indent++;
303 		} else if (Q_streq(s_token, "}")) {
304 			indent--;
305 			if (indent == 0)
306 				break;
307 			else if (indent < 0)
308 				Sys_Error("Unexpected '}'");
309 		}
310 	}
311 }
312 
ASE_SkipRestOfLine(void)313 static void ASE_SkipRestOfLine (void)
314 {
315 	ASE_GetToken(true);
316 }
317 
ASE_KeyMAP_DIFFUSE(const char * token)318 static void ASE_KeyMAP_DIFFUSE (const char* token)
319 {
320 	if (Q_streq(token, "*BITMAP")) {
321 		const char* bitmap;
322 		size_t len;
323 
324 		ASE_GetToken(false);
325 
326 		/* skip the " */
327 		bitmap = &s_token[1];
328 		len = strlen(bitmap) - 1;
329 		s_token[len] = '\0';
330 
331 		Com_StripExtension(bitmap, ase.materials[ase.numMaterials].name, MAX_QPATH);
332 		Verb_Printf(VERB_EXTRA, "ase material name: \'%s\'\n", ase.materials[ase.numMaterials].name);
333 	}
334 }
335 
ASE_KeyMATERIAL(const char * token)336 static void ASE_KeyMATERIAL (const char* token)
337 {
338 	if (Q_streq(token, "*MAP_DIFFUSE"))
339 		ASE_ParseBracedBlock(ASE_KeyMAP_DIFFUSE);
340 }
341 
ASE_KeyMATERIAL_LIST(const char * token)342 static void ASE_KeyMATERIAL_LIST (const char* token)
343 {
344 	if (Q_streq(token, "*MATERIAL_COUNT")) {
345 		ASE_GetToken(false);
346 		VERBOSE(("..num materials: %s\n", s_token));
347 		if (atoi(s_token) > MAX_ASE_MATERIALS) {
348 			Sys_Error("Too many materials!");
349 		}
350 		ase.numMaterials = 0;
351 	} else if (Q_streq(token, "*MATERIAL")) {
352 		VERBOSE(("..material %d ", ase.numMaterials));
353 		ASE_ParseBracedBlock(ASE_KeyMATERIAL);
354 		ase.numMaterials++;
355 	}
356 }
357 
ASE_KeyMESH_VERTEX_LIST(const char * token)358 static void ASE_KeyMESH_VERTEX_LIST (const char* token)
359 {
360 	aseMesh_t* pMesh = ASE_GetCurrentMesh();
361 
362 	if (Q_streq(token, "*MESH_VERTEX")) {
363 		ASE_GetToken(false);		/* skip number */
364 
365 		ASE_GetToken(false);
366 		pMesh->vertexes[pMesh->currentVertex].y = atof(s_token);
367 
368 		ASE_GetToken(false);
369 		pMesh->vertexes[pMesh->currentVertex].x = -atof(s_token);
370 
371 		ASE_GetToken(false);
372 		pMesh->vertexes[pMesh->currentVertex].z = atof(s_token);
373 
374 		pMesh->currentVertex++;
375 
376 		if (pMesh->currentVertex > pMesh->numVertexes)
377 			Sys_Error("pMesh->currentVertex >= pMesh->numVertexes");
378 	} else
379 		Sys_Error("Unknown token '%s' while parsing MESH_VERTEX_LIST", token);
380 }
381 
ASE_KeyMESH_FACE_LIST(const char * token)382 static void ASE_KeyMESH_FACE_LIST (const char* token)
383 {
384 	aseMesh_t* pMesh = ASE_GetCurrentMesh();
385 
386 	if (Q_streq(token, "*MESH_FACE")) {
387 		ASE_GetToken(false);	/* skip face number */
388 
389 		ASE_GetToken(false);	/* skip label */
390 		ASE_GetToken(false);	/* first vertex */
391 		pMesh->faces[pMesh->currentFace][0] = atoi(s_token);
392 
393 		ASE_GetToken(false);	/* skip label */
394 		ASE_GetToken(false);	/* second vertex */
395 		pMesh->faces[pMesh->currentFace][2] = atoi(s_token);
396 
397 		ASE_GetToken(false);	/* skip label */
398 		ASE_GetToken(false);	/* third vertex */
399 		pMesh->faces[pMesh->currentFace][1] = atoi(s_token);
400 
401 		ASE_GetToken(true);
402 
403 #if 0
404 		if ((p = strstr(s_token, "*MESH_MTLID")) != 0) {
405 			p += strlen("*MESH_MTLID") + 1;
406 			mtlID = atoi(p);
407 		} else {
408 			Sys_Error("No *MESH_MTLID found for face!");
409 		}
410 #endif
411 
412 		pMesh->currentFace++;
413 	} else
414 		Sys_Error("Unknown token '%s' while parsing MESH_FACE_LIST", token);
415 }
416 
ASE_KeyTFACE_LIST(const char * token)417 static void ASE_KeyTFACE_LIST (const char* token)
418 {
419 	aseMesh_t* pMesh = ASE_GetCurrentMesh();
420 
421 	if (Q_streq(token, "*MESH_TFACE")) {
422 		int a, b, c;
423 		aseFace_t* f;
424 
425 		ASE_GetToken(false);
426 
427 		ASE_GetToken(false);
428 		a = atoi(s_token);
429 		ASE_GetToken(false);
430 		c = atoi(s_token);
431 		ASE_GetToken(false);
432 		b = atoi(s_token);
433 
434 		f = &pMesh->tfaces[pMesh->currentFace];
435 		*f[0] = a;
436 		*f[1] = b;
437 		*f[2] = c;
438 
439 		pMesh->currentFace++;
440 	} else
441 		Sys_Error("Unknown token '%s' in MESH_TFACE", token);
442 }
443 
ASE_KeyMESH_TVERTLIST(const char * token)444 static void ASE_KeyMESH_TVERTLIST (const char* token)
445 {
446 	aseMesh_t* pMesh = ASE_GetCurrentMesh();
447 
448 	if (Q_streq(token, "*MESH_TVERT")) {
449 		char u[80], v[80], w[80];
450 
451 		ASE_GetToken(false);
452 
453 		ASE_GetToken(false);
454 		strcpy(u, s_token);
455 
456 		ASE_GetToken(false);
457 		strcpy(v, s_token);
458 
459 		ASE_GetToken(false);
460 		strcpy(w, s_token);
461 
462 		pMesh->tvertexes[pMesh->currentVertex].s = atof(u);
463 		pMesh->tvertexes[pMesh->currentVertex].t = 1.0f - atof(v);
464 
465 		pMesh->currentVertex++;
466 
467 		if (pMesh->currentVertex > pMesh->numTVertexes) {
468 			Sys_Error("pMesh->currentVertex > pMesh->numTVertexes");
469 		}
470 	} else
471 		Sys_Error("Unknown token '%s' while parsing MESH_TVERTLIST", token);
472 }
473 
ASE_KeyMESH(const char * token)474 static void ASE_KeyMESH (const char* token)
475 {
476 	aseMesh_t* pMesh = ASE_GetCurrentMesh();
477 
478 	if (Q_streq(token, "*TIMEVALUE")) {
479 		ASE_GetToken(false);
480 
481 		pMesh->timeValue = atoi(s_token);
482 		VERBOSE((".....timevalue: %d\n", pMesh->timeValue));
483 	} else if (Q_streq(token, "*MESH_NUMVERTEX")) {
484 		ASE_GetToken(false);
485 
486 		pMesh->numVertexes = atoi(s_token);
487 		VERBOSE((".....TIMEVALUE: %d\n", pMesh->timeValue));
488 		VERBOSE((".....num vertexes: %d\n", pMesh->numVertexes));
489 	} else if (Q_streq(token, "*MESH_NUMFACES")) {
490 		ASE_GetToken(false);
491 
492 		pMesh->numFaces = atoi(s_token);
493 		VERBOSE((".....num faces: %d\n", pMesh->numFaces));
494 	} else if (Q_streq(token, "*MESH_NUMTVFACES")) {
495 		ASE_GetToken(false);
496 
497 		if (atoi(s_token) != pMesh->numFaces)
498 			Sys_Error("MESH_NUMTVFACES != MESH_NUMFACES");
499 	} else if (Q_streq(token, "*MESH_NUMTVERTEX")) {
500 		ASE_GetToken(false);
501 
502 		pMesh->numTVertexes = atoi(s_token);
503 		VERBOSE((".....num tvertexes: %d\n", pMesh->numTVertexes));
504 	} else if (Q_streq(token, "*MESH_VERTEX_LIST")) {
505 		pMesh->vertexes = Mem_AllocTypeN(aseVertex_t, pMesh->numVertexes);
506 		pMesh->currentVertex = 0;
507 		VERBOSE((".....parsing MESH_VERTEX_LIST\n"));
508 		ASE_ParseBracedBlock(ASE_KeyMESH_VERTEX_LIST);
509 	} else if (Q_streq(token, "*MESH_TVERTLIST")) {
510 		pMesh->currentVertex = 0;
511 		pMesh->tvertexes = Mem_AllocTypeN(aseTVertex_t, pMesh->numTVertexes);
512 		VERBOSE((".....parsing MESH_TVERTLIST\n"));
513 		ASE_ParseBracedBlock(ASE_KeyMESH_TVERTLIST);
514 	} else if (Q_streq(token, "*MESH_FACE_LIST")) {
515 		pMesh->faces = Mem_AllocTypeN(aseFace_t, pMesh->numFaces);
516 		pMesh->currentFace = 0;
517 		VERBOSE((".....parsing MESH_FACE_LIST\n"));
518 		ASE_ParseBracedBlock(ASE_KeyMESH_FACE_LIST);
519 	} else if (Q_streq(token, "*MESH_TFACELIST")) {
520 		pMesh->tfaces = Mem_AllocTypeN(aseFace_t, pMesh->numFaces);
521 		pMesh->currentFace = 0;
522 		VERBOSE((".....parsing MESH_TFACE_LIST\n"));
523 		ASE_ParseBracedBlock(ASE_KeyTFACE_LIST);
524 	} else if (Q_streq(token, "*MESH_NORMALS")) {
525 		ASE_ParseBracedBlock(0);
526 	}
527 }
528 
ASE_KeyGEOMOBJECT(const char * token)529 static void ASE_KeyGEOMOBJECT (const char* token)
530 {
531 	if (Q_streq(token, "*NODE_NAME")) {
532 		char* name = ase.objects[ase.currentObject].name;
533 
534 		ASE_GetToken(true);
535 		VERBOSE((" %s\n", s_token));
536 		strcpy(ase.objects[ase.currentObject].name, s_token + 1);
537 		if (strchr(ase.objects[ase.currentObject].name, '"'))
538 			*strchr(ase.objects[ase.currentObject].name, '"') = 0;
539 
540 		if (strstr(name, "tag") == name) {
541 			while (strchr(name, '_') != strrchr(name, '_')) {
542 				*strrchr(name, '_') = 0;
543 			}
544 			while (strrchr(name, ' ')) {
545 				*strrchr(name, ' ') = 0;
546 			}
547 		}
548 	} else if (Q_streq(token, "*NODE_PARENT")) {
549 		ASE_SkipRestOfLine();
550 	}
551 	/* ignore unused data blocks */
552 	else if (Q_streq(token, "*NODE_TM") || Q_streq(token, "*TM_ANIMATION")) {
553 		ASE_ParseBracedBlock(0);
554 	}
555 	/* ignore regular meshes that aren't part of animation */
556 	else if (Q_streq(token, "*MESH")) {
557 #if 0
558 		if (strstr(ase.objects[ase.currentObject].name, "tag_") == ase.objects[ase.currentObject].name) {
559 			s_forceStaticMesh = true;
560 			ASE_ParseBracedBlock(ASE_KeyMESH);
561 			s_forceStaticMesh = false;
562 		}
563 #endif
564 		ASE_ParseBracedBlock(ASE_KeyMESH);
565 		if (++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES) {
566 			Sys_Error("Too many animation frames");
567 		}
568 		ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame;
569 		ase.objects[ase.currentObject].numAnimations++;
570 #if 0
571 		/* ignore meshes that aren't part of animations if this object isn't a a tag */
572 		else {
573 			ASE_ParseBracedBlock(0);
574 		}
575 #endif
576 	}
577 	/* according to spec these are obsolete */
578 	else if (Q_streq(token, "*MATERIAL_REF")) {
579 		ASE_GetToken(false);
580 
581 		ase.objects[ase.currentObject].materialRef = atoi(s_token);
582 	}
583 	/* ignore sequences of animation frames */
584 	else if (Q_streq(token, "*MESH_ANIMATION")) {
585 		ASE_SkipEnclosingBraces();
586 	}
587 	/* skip unused info */
588 	else if (Q_streq(token, "*PROP_MOTIONBLUR") || Q_streq(token, "*PROP_CASTSHADOW") || Q_streq(token, "*PROP_RECVSHADOW")) {
589 		ASE_SkipRestOfLine();
590 	}
591 }
592 
ConcatenateObjects(aseGeomObject_t * pObjA,aseGeomObject_t * pObjB)593 static void ConcatenateObjects (aseGeomObject_t* pObjA, aseGeomObject_t* pObjB)
594 {
595 }
596 
CollapseObjects(void)597 static void CollapseObjects (void)
598 {
599 	int i;
600 	int numObjects = ase.currentObject;
601 
602 	for (i = 0; i < numObjects; i++) {
603 		int j;
604 
605 		/* skip tags */
606 		if (strstr(ase.objects[i].name, "tag") == ase.objects[i].name)
607 			continue;
608 
609 		if (!ase.objects[i].numAnimations)
610 			continue;
611 
612 		for (j = i + 1; j < numObjects; j++) {
613 			if (strstr(ase.objects[j].name, "tag") == ase.objects[j].name)
614 				continue;
615 
616 			if (ase.objects[i].materialRef == ase.objects[j].materialRef)
617 				if (ase.objects[j].numAnimations)
618 					ConcatenateObjects(&ase.objects[i], &ase.objects[j]);
619 		}
620 	}
621 }
622 
ASE_Process(void)623 static void ASE_Process (void)
624 {
625 	while (ASE_GetToken(false)) {
626 		if (Q_streq(s_token, "*3DSMAX_ASCIIEXPORT") || Q_streq(s_token, "*COMMENT")) {
627 			ASE_SkipRestOfLine();
628 		} else if (Q_streq(s_token, "*SCENE"))
629 			ASE_SkipEnclosingBraces();
630 		else if (Q_streq(s_token, "*MATERIAL_LIST")) {
631 			VERBOSE(("MATERIAL_LIST\n"));
632 
633 			ASE_ParseBracedBlock(ASE_KeyMATERIAL_LIST);
634 		} else if (Q_streq(s_token, "*GEOMOBJECT")) {
635 			VERBOSE(("GEOMOBJECT"));
636 
637 			ASE_ParseBracedBlock(ASE_KeyGEOMOBJECT);
638 
639 			if (strstr(ase.objects[ase.currentObject].name, "Bip") ||
640 				strstr(ase.objects[ase.currentObject].name, "ignore_")) {
641 				ASE_FreeGeomObject(ase.currentObject);
642 				VERBOSE(("(discarding BIP/ignore object)\n"));
643 			} else {
644 				if (++ase.currentObject == MAX_ASE_OBJECTS) {
645 					Sys_Error("Too many GEOMOBJECTs");
646 				}
647 			}
648 		} else if (s_token[0]) {
649 			Com_Printf("Unknown token '%s'\n", s_token);
650 		}
651 	}
652 
653 	if (!ase.currentObject)
654 		Sys_Error("No animation data!");
655 
656 	CollapseObjects();
657 }
658