1 /*
2 Copyright (C) 1996-2001 Id Software, Inc.
3 Copyright (C) 2002-2009 John Fitzgibbons and others
4 Copyright (C) 2010-2014 QuakeSpasm developers
5 Copyright (C) 2016 Axel Gneiting
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15
16 See the 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21
22 */
23
24 //r_alias.c -- alias model rendering
25
26 #include "quakedef.h"
27
28 extern cvar_t r_drawflat, gl_fullbrights, r_lerpmodels, r_lerpmove, r_showtris; //johnfitz
29 extern cvar_t scr_fov, cl_gun_fovscale;
30
31 //up to 16 color translated skins
32 gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz -- changed to an array of pointers
33
34 #define NUMVERTEXNORMALS 162
35
36 float r_avertexnormals[NUMVERTEXNORMALS][3] =
37 {
38 #include "anorms.h"
39 };
40
41 extern vec3_t lightcolor; //johnfitz -- replaces "float shadelight" for lit support
42
43 // precalculated dot products for quantized angles
44 #define SHADEDOT_QUANT 16
45 float r_avertexnormal_dots[SHADEDOT_QUANT][256] =
46 {
47 #include "anorm_dots.h"
48 };
49
50 extern vec3_t lightspot;
51
52 float *shadedots = r_avertexnormal_dots[0];
53 vec3_t shadevector;
54
55 float entalpha; //johnfitz
56
57 qboolean shading = true; //johnfitz -- if false, disable vertex shading for various reasons (fullbright, r_lightmap, showtris, etc)
58
59 //johnfitz -- struct for passing lerp information to drawing functions
60 typedef struct {
61 short pose1;
62 short pose2;
63 float blend;
64 vec3_t origin;
65 vec3_t angles;
66 } lerpdata_t;
67 //johnfitz
68
69 typedef struct {
70 float model_matrix[16];
71 float shade_vector[3];
72 float blend_factor;
73 float light_color[3];
74 float entalpha;
75 unsigned int flags;
76 } aliasubo_t;
77
78 /*
79 =============
80 GLARB_GetXYZOffset
81
82 Returns the offset of the first vertex's meshxyz_t.xyz in the vbo for the given
83 model and pose.
84 =============
85 */
GLARB_GetXYZOffset(aliashdr_t * hdr,int pose)86 static VkDeviceSize GLARB_GetXYZOffset (aliashdr_t *hdr, int pose)
87 {
88 const int xyzoffs = offsetof (meshxyz_t, xyz);
89 return currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + xyzoffs;
90 }
91
92 /*
93 =============
94 GL_DrawAliasFrame -- ericw
95
96 Optimized alias model drawing codepath. This makes 1 draw call,
97 no vertex data is uploaded (it's already in the r_meshvbo and r_meshindexesvbo
98 static VBOs), and lerping and lighting is done in the vertex shader.
99
100 Supports optional fullbright pixels.
101
102 Based on code by MH from RMQEngine
103 =============
104 */
GL_DrawAliasFrame(aliashdr_t * paliashdr,lerpdata_t lerpdata,gltexture_t * tx,gltexture_t * fb,float model_matrix[16],float entity_alpha,qboolean alphatest)105 static void GL_DrawAliasFrame (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltexture_t *tx, gltexture_t *fb, float model_matrix[16], float entity_alpha, qboolean alphatest)
106 {
107 float blend;
108
109 if (lerpdata.pose1 != lerpdata.pose2)
110 blend = lerpdata.blend;
111 else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled
112 blend = 0;
113
114 vulkan_pipeline_t pipeline;
115 if (entity_alpha >= 1.0f)
116 {
117 if (!alphatest)
118 pipeline = vulkan_globals.alias_pipeline;
119 else
120 pipeline = vulkan_globals.alias_alphatest_pipeline;
121 }
122 else
123 {
124 if (!alphatest)
125 pipeline = vulkan_globals.alias_blend_pipeline;
126 else
127 pipeline = vulkan_globals.alias_alphatest_blend_pipeline;
128 }
129
130 R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
131
132 VkBuffer uniform_buffer;
133 uint32_t uniform_offset;
134 VkDescriptorSet ubo_set;
135 aliasubo_t * ubo = (aliasubo_t*)R_UniformAllocate(sizeof(aliasubo_t), &uniform_buffer, &uniform_offset, &ubo_set);
136
137 memcpy(ubo->model_matrix, model_matrix, 16 * sizeof(float));
138 memcpy(ubo->shade_vector, shadevector, 3 * sizeof(float));
139 ubo->blend_factor = blend;
140 memcpy(ubo->light_color, lightcolor, 3 * sizeof(float));
141 ubo->flags = (fb != NULL) ? 0x1 : 0x0;
142 if (r_fullbright_cheatsafe || r_lightmap_cheatsafe)
143 ubo->flags |= 0x2;
144 ubo->entalpha = entity_alpha;
145
146 VkDescriptorSet descriptor_sets[3] = { tx->descriptor_set, (fb != NULL) ? fb->descriptor_set : tx->descriptor_set, ubo_set };
147 vulkan_globals.vk_cmd_bind_descriptor_sets(vulkan_globals.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_pipeline.layout.handle, 0, 3, descriptor_sets, 1, &uniform_offset);
148
149 VkBuffer vertex_buffers[3] = { currententity->model->vertex_buffer, currententity->model->vertex_buffer, currententity->model->vertex_buffer };
150 VkDeviceSize vertex_offsets[3] = { (unsigned)currententity->model->vbostofs, GLARB_GetXYZOffset (paliashdr, lerpdata.pose1), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2) };
151 vulkan_globals.vk_cmd_bind_vertex_buffers(vulkan_globals.command_buffer, 0, 3, vertex_buffers, vertex_offsets);
152 vulkan_globals.vk_cmd_bind_index_buffer(vulkan_globals.command_buffer, currententity->model->index_buffer, 0, VK_INDEX_TYPE_UINT16);
153
154 vulkan_globals.vk_cmd_draw_indexed(vulkan_globals.command_buffer, paliashdr->numindexes, 1, 0, 0, 0);
155
156 rs_aliaspasses += paliashdr->numtris;
157 }
158
159 /*
160 =================
161 R_SetupAliasFrame -- johnfitz -- rewritten to support lerping
162 =================
163 */
R_SetupAliasFrame(aliashdr_t * paliashdr,int frame,lerpdata_t * lerpdata)164 void R_SetupAliasFrame (aliashdr_t *paliashdr, int frame, lerpdata_t *lerpdata)
165 {
166 entity_t *e = currententity;
167 int posenum, numposes;
168
169 if ((frame >= paliashdr->numframes) || (frame < 0))
170 {
171 Con_DPrintf ("R_AliasSetupFrame: no such frame %d for '%s'\n", frame, e->model->name);
172 frame = 0;
173 }
174
175 posenum = paliashdr->frames[frame].firstpose;
176 numposes = paliashdr->frames[frame].numposes;
177
178 if (numposes > 1)
179 {
180 e->lerptime = paliashdr->frames[frame].interval;
181 posenum += (int)(cl.time / e->lerptime) % numposes;
182 }
183 else
184 e->lerptime = 0.1;
185
186 if (e->lerpflags & LERP_RESETANIM) //kill any lerp in progress
187 {
188 e->lerpstart = 0;
189 e->previouspose = posenum;
190 e->currentpose = posenum;
191 e->lerpflags -= LERP_RESETANIM;
192 }
193 else if (e->currentpose != posenum) // pose changed, start new lerp
194 {
195 if (e->lerpflags & LERP_RESETANIM2) //defer lerping one more time
196 {
197 e->lerpstart = 0;
198 e->previouspose = posenum;
199 e->currentpose = posenum;
200 e->lerpflags -= LERP_RESETANIM2;
201 }
202 else
203 {
204 e->lerpstart = cl.time;
205 e->previouspose = e->currentpose;
206 e->currentpose = posenum;
207 }
208 }
209
210 //set up values
211 if (r_lerpmodels.value && !(e->model->flags & MOD_NOLERP && r_lerpmodels.value != 2))
212 {
213 if (e->lerpflags & LERP_FINISH && numposes == 1)
214 lerpdata->blend = CLAMP (0, (cl.time - e->lerpstart) / (e->lerpfinish - e->lerpstart), 1);
215 else
216 lerpdata->blend = CLAMP (0, (cl.time - e->lerpstart) / e->lerptime, 1);
217
218 if (e->currentpose >= paliashdr->numposes || e->currentpose < 0)
219 {
220 Con_DPrintf ("R_AliasSetupFrame: invalid current pose %d (%d total) for '%s'\n", e->currentpose, paliashdr->numposes, e->model->name);
221 e->currentpose = 0;
222 }
223
224 if (e->previouspose >= paliashdr->numposes || e->previouspose < 0)
225 {
226 Con_DPrintf ("R_AliasSetupFrame: invalid prev pose %d (%d total) for '%s'\n", e->previouspose, paliashdr->numposes, e->model->name);
227 e->previouspose = e->currentpose;
228 }
229
230 lerpdata->pose1 = e->previouspose;
231 lerpdata->pose2 = e->currentpose;
232 }
233 else //don't lerp
234 {
235 lerpdata->blend = 1;
236 lerpdata->pose1 = posenum;
237 lerpdata->pose2 = posenum;
238 }
239 }
240
241 /*
242 =================
243 R_SetupEntityTransform -- johnfitz -- set up transform part of lerpdata
244 =================
245 */
R_SetupEntityTransform(entity_t * e,lerpdata_t * lerpdata)246 void R_SetupEntityTransform (entity_t *e, lerpdata_t *lerpdata)
247 {
248 float blend;
249 vec3_t d;
250 int i;
251
252 // if LERP_RESETMOVE, kill any lerps in progress
253 if (e->lerpflags & LERP_RESETMOVE)
254 {
255 e->movelerpstart = 0;
256 VectorCopy (e->origin, e->previousorigin);
257 VectorCopy (e->origin, e->currentorigin);
258 VectorCopy (e->angles, e->previousangles);
259 VectorCopy (e->angles, e->currentangles);
260 e->lerpflags -= LERP_RESETMOVE;
261 }
262 else if (!VectorCompare (e->origin, e->currentorigin) || !VectorCompare (e->angles, e->currentangles)) // origin/angles changed, start new lerp
263 {
264 e->movelerpstart = cl.time;
265 VectorCopy (e->currentorigin, e->previousorigin);
266 VectorCopy (e->origin, e->currentorigin);
267 VectorCopy (e->currentangles, e->previousangles);
268 VectorCopy (e->angles, e->currentangles);
269 }
270
271 //set up values
272 if (r_lerpmove.value && e != &cl.viewent && e->lerpflags & LERP_MOVESTEP)
273 {
274 if (e->lerpflags & LERP_FINISH)
275 blend = CLAMP (0, (cl.time - e->movelerpstart) / (e->lerpfinish - e->movelerpstart), 1);
276 else
277 blend = CLAMP (0, (cl.time - e->movelerpstart) / 0.1, 1);
278
279 //translation
280 VectorSubtract (e->currentorigin, e->previousorigin, d);
281 lerpdata->origin[0] = e->previousorigin[0] + d[0] * blend;
282 lerpdata->origin[1] = e->previousorigin[1] + d[1] * blend;
283 lerpdata->origin[2] = e->previousorigin[2] + d[2] * blend;
284
285 //rotation
286 VectorSubtract (e->currentangles, e->previousangles, d);
287 for (i = 0; i < 3; i++)
288 {
289 if (d[i] > 180) d[i] -= 360;
290 if (d[i] < -180) d[i] += 360;
291 }
292 lerpdata->angles[0] = e->previousangles[0] + d[0] * blend;
293 lerpdata->angles[1] = e->previousangles[1] + d[1] * blend;
294 lerpdata->angles[2] = e->previousangles[2] + d[2] * blend;
295 }
296 else //don't lerp
297 {
298 VectorCopy (e->origin, lerpdata->origin);
299 VectorCopy (e->angles, lerpdata->angles);
300 }
301 }
302
303 /*
304 =================
305 R_SetupAliasLighting -- johnfitz -- broken out from R_DrawAliasModel and rewritten
306 =================
307 */
R_SetupAliasLighting(entity_t * e)308 void R_SetupAliasLighting (entity_t *e)
309 {
310 vec3_t dist;
311 float add;
312 int i;
313 int quantizedangle;
314 float radiansangle;
315 vec3_t lpos;
316
317 VectorCopy (e->origin, lpos);
318 // start the light trace from slightly above the origin
319 // this helps with models whose origin is below ground level, but are otherwise visible
320 // (e.g. some of the candles in the DOTM start map, which would otherwise appear black)
321 lpos[2] += e->model->maxs[2] * 0.5f;
322 R_LightPoint (lpos, &e->lightcache);
323
324 //add dlights
325 for (i=0 ; i<MAX_DLIGHTS ; i++)
326 {
327 if (cl_dlights[i].die >= cl.time)
328 {
329 VectorSubtract (currententity->origin, cl_dlights[i].origin, dist);
330 add = cl_dlights[i].radius - VectorLength(dist);
331 if (add > 0)
332 VectorMA (lightcolor, add, cl_dlights[i].color, lightcolor);
333 }
334 }
335
336 // minimum light value on gun (24)
337 if (e == &cl.viewent)
338 {
339 add = 72.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]);
340 if (add > 0.0f)
341 {
342 lightcolor[0] += add / 3.0f;
343 lightcolor[1] += add / 3.0f;
344 lightcolor[2] += add / 3.0f;
345 }
346 }
347
348 // minimum light value on players (8)
349 if (currententity > cl.entities && currententity <= cl.entities + cl.maxclients)
350 {
351 add = 24.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]);
352 if (add > 0.0f)
353 {
354 lightcolor[0] += add / 3.0f;
355 lightcolor[1] += add / 3.0f;
356 lightcolor[2] += add / 3.0f;
357 }
358 }
359
360 // clamp lighting so it doesn't overbright as much (96)
361 add = 288.0f / (lightcolor[0] + lightcolor[1] + lightcolor[2]);
362 if (add < 1.0f)
363 VectorScale(lightcolor, add, lightcolor);
364
365 quantizedangle = ((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1);
366
367 //ericw -- shadevector is passed to the shader to compute shadedots inside the
368 //shader, see GLAlias_CreateShaders()
369 radiansangle = (quantizedangle / 16.0) * 2.0 * 3.14159;
370 shadevector[0] = cos(-radiansangle);
371 shadevector[1] = sin(-radiansangle);
372 shadevector[2] = 1;
373 VectorNormalize(shadevector);
374 //ericw --
375
376 shadedots = r_avertexnormal_dots[quantizedangle];
377 VectorScale (lightcolor, 1.0f / 200.0f, lightcolor);
378 }
379
380 /*
381 =================
382 R_DrawAliasModel -- johnfitz -- almost completely rewritten
383 =================
384 */
R_DrawAliasModel(entity_t * e)385 void R_DrawAliasModel (entity_t *e)
386 {
387 aliashdr_t *paliashdr;
388 int i, anim, skinnum;
389 gltexture_t *tx, *fb;
390 lerpdata_t lerpdata;
391 qboolean alphatest = !!(e->model->flags & MF_HOLEY);
392
393 //
394 // setup pose/lerp data -- do it first so we don't miss updates due to culling
395 //
396 paliashdr = (aliashdr_t *)Mod_Extradata (e->model);
397 R_SetupAliasFrame (paliashdr, e->frame, &lerpdata);
398 R_SetupEntityTransform (e, &lerpdata);
399
400 //
401 // cull it
402 //
403 if (R_CullModelForEntity(e))
404 return;
405
406 //
407 // transform it
408 //
409 float model_matrix[16];
410 IdentityMatrix(model_matrix);
411 R_RotateForEntity (model_matrix, lerpdata.origin, lerpdata.angles);
412
413 float fovscale = 1.0f;
414 if (e == &cl.viewent && scr_fov.value > 90.f && cl_gun_fovscale.value)
415 {
416 fovscale = tan(scr_fov.value * (0.5f * M_PI / 180.f));
417 fovscale = 1.f + (fovscale - 1.f) * cl_gun_fovscale.value;
418 }
419
420 float translation_matrix[16];
421 TranslationMatrix (translation_matrix, paliashdr->scale_origin[0], paliashdr->scale_origin[1] * fovscale, paliashdr->scale_origin[2] * fovscale);
422 MatrixMultiply(model_matrix, translation_matrix);
423
424 // Scale multiplied by 255 because we use UNORM instead of USCALED in the vertex shader
425 float scale_matrix[16];
426 ScaleMatrix (scale_matrix, paliashdr->scale[0] * 255.0f, paliashdr->scale[1] * fovscale * 255.0f, paliashdr->scale[2] * fovscale * 255.0f);
427 MatrixMultiply(model_matrix, scale_matrix);
428
429 //
430 // random stuff
431 //
432 shading = true;
433
434 //
435 // set up for alpha blending
436 //
437 if (r_lightmap_cheatsafe)
438 entalpha = 1;
439 else
440 entalpha = ENTALPHA_DECODE(e->alpha);
441 if (entalpha == 0)
442 return;
443
444 //
445 // set up lighting
446 //
447 rs_aliaspolys += paliashdr->numtris;
448 R_SetupAliasLighting (e);
449
450 //
451 // set up textures
452 //
453 anim = (int)(cl.time*10) & 3;
454 skinnum = e->skinnum;
455 if ((skinnum >= paliashdr->numskins) || (skinnum < 0))
456 {
457 Con_DPrintf ("R_DrawAliasModel: no such skin # %d for '%s'\n", skinnum, e->model->name);
458 // ericw -- display skin 0 for winquake compatibility
459 skinnum = 0;
460 }
461 tx = paliashdr->gltextures[skinnum][anim];
462 fb = paliashdr->fbtextures[skinnum][anim];
463 if (e->colormap != vid.colormap && !gl_nocolors.value)
464 {
465 i = e - cl.entities;
466 if (i >= 1 && i<=cl.maxclients )
467 tx = playertextures[i - 1];
468 }
469 if (!gl_fullbrights.value)
470 fb = NULL;
471
472 if (r_fullbright_cheatsafe)
473 {
474 lightcolor[0] = 0.5f;
475 lightcolor[1] = 0.5f;
476 lightcolor[2] = 0.5f;
477 }
478 if (r_lightmap_cheatsafe)
479 {
480 tx = whitetexture;
481 fb = NULL;
482 lightcolor[0] = 1.0f;
483 lightcolor[1] = 1.0f;
484 lightcolor[2] = 1.0f;
485 }
486
487 //
488 // draw it
489 //
490 GL_DrawAliasFrame (paliashdr, lerpdata, tx, fb, model_matrix, entalpha, alphatest);
491 }
492
493 //johnfitz -- values for shadow matrix
494 #define SHADOW_SKEW_X -0.7 //skew along x axis. -0.7 to mimic glquake shadows
495 #define SHADOW_SKEW_Y 0 //skew along y axis. 0 to mimic glquake shadows
496 #define SHADOW_VSCALE 0 //0=completely flat
497 #define SHADOW_HEIGHT 0.1 //how far above the floor to render the shadow
498 //johnfitz
499
500 /*
501 =================
502 R_DrawAliasModel_ShowTris -- johnfitz
503 =================
504 */
R_DrawAliasModel_ShowTris(entity_t * e)505 void R_DrawAliasModel_ShowTris (entity_t *e)
506 {
507 aliashdr_t *paliashdr;
508 lerpdata_t lerpdata;
509 float blend;
510
511 //
512 // setup pose/lerp data -- do it first so we don't miss updates due to culling
513 //
514 paliashdr = (aliashdr_t *)Mod_Extradata (e->model);
515 R_SetupAliasFrame (paliashdr, e->frame, &lerpdata);
516 R_SetupEntityTransform (e, &lerpdata);
517
518 //
519 // cull it
520 //
521 if (R_CullModelForEntity(e))
522 return;
523
524 //
525 // transform it
526 //
527 float model_matrix[16];
528 IdentityMatrix(model_matrix);
529 R_RotateForEntity (model_matrix, lerpdata.origin, lerpdata.angles);
530
531 float fovscale = 1.0f;
532 if (e == &cl.viewent && scr_fov.value > 90.f)
533 {
534 fovscale = tan(scr_fov.value * (0.5f * M_PI / 180.f));
535 fovscale = 1.f + (fovscale - 1.f) * cl_gun_fovscale.value;
536 }
537
538 float translation_matrix[16];
539 TranslationMatrix (translation_matrix, paliashdr->scale_origin[0], paliashdr->scale_origin[1] * fovscale, paliashdr->scale_origin[2] * fovscale);
540 MatrixMultiply(model_matrix, translation_matrix);
541
542 // Scale multiplied by 255 because we use UNORM instead of USCALED in the vertex shader
543 float scale_matrix[16];
544 ScaleMatrix (scale_matrix, paliashdr->scale[0] * 255.0f, paliashdr->scale[1] * fovscale * 255.0f, paliashdr->scale[2] * fovscale * 255.0f);
545 MatrixMultiply(model_matrix, scale_matrix);
546
547 if (r_showtris.value == 1)
548 R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_showtris_pipeline);
549 else
550 R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_showtris_depth_test_pipeline);
551
552 if (lerpdata.pose1 != lerpdata.pose2)
553 blend = lerpdata.blend;
554 else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled
555 blend = 0;
556
557 VkBuffer uniform_buffer;
558 uint32_t uniform_offset;
559 VkDescriptorSet ubo_set;
560 aliasubo_t * ubo = (aliasubo_t*)R_UniformAllocate(sizeof(aliasubo_t), &uniform_buffer, &uniform_offset, &ubo_set);
561
562 memcpy(ubo->model_matrix, model_matrix, 16 * sizeof(float));
563 memset(ubo->shade_vector, 0, 3 * sizeof(float));
564 ubo->blend_factor = blend;
565 memset(ubo->light_color, 0, 3 * sizeof(float));
566 ubo->entalpha = 1.0f;
567 ubo->flags = 0;
568
569 VkDescriptorSet descriptor_sets[3] = { nulltexture->descriptor_set, nulltexture->descriptor_set, ubo_set };
570 vulkan_globals.vk_cmd_bind_descriptor_sets(vulkan_globals.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_pipeline.layout.handle, 0, 3, descriptor_sets, 1, &uniform_offset);
571
572 VkBuffer vertex_buffers[3] = { currententity->model->vertex_buffer, currententity->model->vertex_buffer, currententity->model->vertex_buffer };
573 VkDeviceSize vertex_offsets[3] = { (unsigned)currententity->model->vbostofs, GLARB_GetXYZOffset (paliashdr, lerpdata.pose1), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2) };
574 vulkan_globals.vk_cmd_bind_vertex_buffers(vulkan_globals.command_buffer, 0, 3, vertex_buffers, vertex_offsets);
575 vulkan_globals.vk_cmd_bind_index_buffer(vulkan_globals.command_buffer, currententity->model->index_buffer, 0, VK_INDEX_TYPE_UINT16);
576
577 vulkan_globals.vk_cmd_draw_indexed(vulkan_globals.command_buffer, paliashdr->numindexes, 1, 0, 0, 0);
578 }
579