4829d48174599bb6906c802e0cfeef0446d3a20c
[csRadar.git] / vmf.h
1 #define SOLID_MAX_SIDES 512
2 #define VMF_FLAG_IS_PROP 0x1
3 #define VMF_FLAG_IS_INSTANCE 0x2
4 #define VMF_FLAG_BRUSH_ENT 0x4
5
6 typedef struct vmf_solid vmf_solid;
7 typedef struct vmf_vert vmf_vert;
8 typedef struct vmf_mat vmf_mat;
9 typedef struct vmf_face vmf_face;
10 typedef struct vmf_userdata vmf_userdata;
11 typedef struct vmf_map vmf_map;
12
13 typedef enum ESolidResult ESolidResult;
14
15 enum ESolidResult
16 {
17 k_ESolidResult_valid,
18 k_ESolidResult_maxsides,
19 k_ESolidResult_invalid,
20 k_ESolidResult_errnomem,
21 k_ESolidResult_corrupt,
22 k_ESolidResult_degenerate
23 };
24
25 struct vmf_vert
26 {
27 v3f co;
28 v3f nrm;
29 v2f xy;
30 };
31
32 struct vmf_face
33 {
34 u32 *indices;
35
36 vdf_node *dispinfo;
37
38 const char *material;
39 int blacklisted;
40 };
41
42 struct vmf_solid
43 {
44 vmf_vert *verts;
45 u32 *indices;
46 };
47
48 struct vmf_mat
49 {
50 char *str;
51 u32 hash;
52 };
53
54 struct vmf_map
55 {
56 vdf_node *root;
57
58 struct vmf_model
59 {
60 char *str;
61 u32 hash;
62
63 mdl_mesh_t mdl;
64
65 int need_load;
66 }
67 *models;
68
69 struct vmf_instance
70 {
71 char *name;
72 u32 hash;
73
74 vdf_node *root;
75
76 m4x3f transform;
77 }
78 *cache;
79
80 m4x3f transform;
81 };
82
83 // IMPLEMENTATION
84
85 void solidgen_ctx_reset( vmf_solid *ctx )
86 {
87 csr_sb_clear( ctx->verts );
88 csr_sb_clear( ctx->indices );
89 }
90
91 void solidgen_ctx_init( vmf_solid *ctx )
92 {
93 const u32 init_size = 128;
94
95 ctx->verts = csr_sb_reserve( NULL, init_size, sizeof(vmf_vert) );
96 ctx->indices = csr_sb_reserve( NULL, init_size, sizeof(u32) );
97 }
98
99 void solidgen_ctx_free( vmf_solid *ctx )
100 {
101 csr_sb_free( ctx->verts );
102 csr_sb_free( ctx->indices );
103 }
104
105 // Compute bounds of solid gen ctx
106 void solidgen_bounds( vmf_solid *ctx, boxf box )
107 {
108 v3f mine = { INFINITY, INFINITY, INFINITY };
109 v3f maxe = {-INFINITY,-INFINITY,-INFINITY };
110
111 for( int i = 0; i < csr_sb_count( ctx->verts ); i ++ )
112 {
113 vmf_vert *vert = ctx->verts + i;
114 v3_minv( mine, vert->co, mine );
115 v3_maxv( maxe, vert->co, maxe );
116 }
117
118 v3_copy( mine, box[0] );
119 v3_copy( maxe, box[1] );
120 }
121
122 struct
123 {
124 vmf_mat *blacklist;
125
126 double planes[ SOLID_MAX_SIDES*4 ];
127 u32 bisectors;
128 }
129 vmf_api;
130
131 // put an extra plane into the planes list
132 void vmf_addbisector( double p[4] )
133 {
134 double *plane = vmf_api.planes + vmf_api.bisectors * 4;
135
136 plane[0] = p[0];
137 plane[1] = p[1];
138 plane[2] = p[2];
139 plane[3] = p[3];
140
141 vmf_api.bisectors ++;
142 }
143
144 void vmf_clearbisectors( void )
145 {
146 vmf_api.bisectors = 0;
147 }
148
149 void vmf_ignore_mat( const char *material )
150 {
151 vmf_api.blacklist = csr_sb_reserve( vmf_api.blacklist, 1, sizeof( vmf_mat ) );
152 vmf_mat *mat = (vmf_mat *)csr_sb_use( vmf_api.blacklist );
153
154 mat->str = csr_malloc( strlen( material ) + 1 );
155 strcpy( mat->str, material );
156
157 mat->hash = djb2( ( const unsigned char * )material );
158 }
159
160 void vmf_clearignore( void )
161 {
162 for( int i = 0; i < csr_sb_count( vmf_api.blacklist ); i ++ )
163 {
164 free( vmf_api.blacklist[ i ].str );
165 }
166
167 csr_sb_free( vmf_api.blacklist );
168 vmf_api.blacklist = NULL;
169 }
170
171 int mat_blacklisted( const char *material )
172 {
173 u32 hash = djb2((const u8 *)material);
174
175 for( int j = 0; j < csr_sb_count( vmf_api.blacklist ); j ++ )
176 {
177 if( vmf_api.blacklist[ j ].hash == hash )
178 {
179 if( !strcmp( material, vmf_api.blacklist[ j ].str ) )
180 {
181 return 1;
182 }
183 }
184 }
185
186 return 0;
187 }
188
189 void sort_coplanar( double p[4], vmf_vert *points, u32 *indices, u32 count )
190 {
191 v3f center = {0.f, 0.f, 0.f};
192 v3f norm;
193 v3d_v3f( p, norm );
194 v3_normalize( norm );
195
196 for( int i = 0; i < count; i ++ )
197 {
198 v3_add( points[ indices[i] ].co, center, center );
199 }
200 v3_divs( center, count, center );
201
202 v3f ref;
203 v3_sub( points[ indices[0] ].co, center, ref );
204
205 // Calc angles compared to ref
206 float *angles = (float*)alloca( sizeof(float)*count );
207 for( int i = 0; i < count; i ++ )
208 {
209 v3f diff;
210 v3f c;
211
212 v3_sub( points[ indices[i] ].co, center, diff );
213 v3_cross( diff, ref, c );
214
215 angles[i] =
216 atan2f( v3_length(c), v3_dot( diff, ref ) )
217 * (v3_dot( c, norm ) < 0.f ? -1.f: 1.f);
218 }
219
220 // Temporary local indexes
221 u32 *temp_indices = (u32 *)alloca( sizeof(u32)*count );
222 for( u32 i = 0; i < count; i ++ ) temp_indices[i] = i;
223
224 // Slow sort on large vertex counts
225 int it = 0;
226 while(1)
227 {
228 int modified = 0;
229 for( int i = 0; i < count-1; i ++ )
230 {
231 int s0 = i; int s1 = i + 1;
232
233 if( angles[temp_indices[s0]] > angles[temp_indices[s1]] )
234 {
235 // swap indices and mirror on local
236 u32 temp = indices[s1];
237 indices[s1] = indices[s0];
238 indices[s0] = temp;
239
240 temp = temp_indices[s1];
241 temp_indices[s1] = temp_indices[s0];
242 temp_indices[s0] = temp;
243
244 modified = 1;
245 }
246 }
247
248 it ++;
249 if( !modified ) break;
250 }
251 }
252
253 int solid_has_displacement( vdf_node *node )
254 {
255 int it = 0;
256 vdf_node *pSide;
257
258 while( (pSide = vdf_next(node, "side", &it)) )
259 {
260 if( vdf_next( pSide, "dispinfo", NULL ) )
261 {
262 return 1;
263 }
264 }
265 return 0;
266 }
267
268 void solid_disp_tri( vmf_solid *ctx, u32 a, u32 b, u32 c )
269 {
270 *((u32 *)csr_sb_use( ctx->indices )) = a;
271 *((u32 *)csr_sb_use( ctx->indices )) = b;
272 *((u32 *)csr_sb_use( ctx->indices )) = c;
273 }
274
275 void face_add_indice( vmf_face *f, u32 idx )
276 {
277 f->indices = csr_sb_reserve( f->indices, 1, sizeof( u32 ) );
278 *((u32 *)csr_sb_use( f->indices )) = idx;
279 }
280
281 ESolidResult solidgen_push( vmf_solid *ctx, vdf_node *node )
282 {
283 ESolidResult flag = k_ESolidResult_valid;
284
285 double planes[ SOLID_MAX_SIDES*4 ];
286 vmf_face faces[ SOLID_MAX_SIDES ];
287
288 int is_displacement = solid_has_displacement( node );
289 int num_planes = 0;
290
291 int it = 0;
292 vdf_node *pSide;
293 while( (pSide = vdf_next(node, "side", &it)) )
294 {
295 if( num_planes >= SOLID_MAX_SIDES )
296 {
297 flag = k_ESolidResult_maxsides;
298 log_error( "Solid over maxsides limit (%i)\n", SOLID_MAX_SIDES );
299 break;
300 }
301
302 double points[3*3];
303
304 vmf_face *face = faces + num_planes;
305 face->indices = NULL;
306 face->dispinfo = vdf_next( pSide, "dispinfo", NULL );
307 face->material = kv_get( pSide, "material", "" );
308 face->blacklisted = mat_blacklisted( face->material );
309
310 kv_double_array( pSide, "plane", 9, points );
311
312 tri_to_plane( points+6, points+3, points+0, planes + num_planes * 4 );
313 num_planes ++;
314 }
315
316 // Compute plane intersections
317 int i[3];
318 csr_comb_init( 3, i );
319
320 v3f center = { 0.f, 0.f, 0.f };
321 int numpoints = 0;
322 u32 vert_start = csr_sb_count( ctx->verts );
323
324 do
325 {
326 // DO something with i j k
327 double p[3];
328
329 if( (faces[ i[0] ].blacklisted && faces[ i[1] ].blacklisted && faces[ i[2] ].blacklisted) )
330 continue;
331
332 if( !plane_intersect( planes+i[0]*4, planes+i[1]*4, planes+i[2]*4, p ) )
333 continue;
334
335 // Check for illegal verts (eg: got clipped by bisectors)
336 int valid = 1;
337 for( int m = 0; m < num_planes; m ++ )
338 {
339 if( plane_polarity( planes+m*4, p ) > 1e-6f )
340 {
341 valid = 0;
342 break;
343 }
344 }
345
346 if( valid )
347 {
348 ctx->verts = csr_sb_reserve( ctx->verts, 3, sizeof( vmf_vert ) );
349
350 // Take the vertex position and add it for centering base on average
351 numpoints ++;
352 v3_add( (v3f){ p[0], p[1], p[2] }, center, center );
353
354 // Store point / respecive normal for each plane that triggered the collision
355 for( int k = 0; k < 3; k ++ )
356 {
357 if( !faces[ i[k] ].blacklisted )
358 {
359 u32 c = csr_sb_count( ctx->verts );
360
361 face_add_indice( faces + i[k], c );
362
363 v3d_v3f( p, ctx->verts[ c ].co );
364 v3d_v3f( planes+i[k]*4, ctx->verts[ c ].nrm );
365
366 csr_sb_inc( ctx->verts, 1 );
367 }
368 }
369 }
370 }
371 while( csr_comb( 3, num_planes, i ) );
372
373 // Retrospectively set the center for each point
374 v3_divs( center, (float)numpoints, center );
375 for( ; vert_start < csr_sb_count( ctx->verts ); vert_start ++ )
376 {
377 v2_copy( center, ctx->verts[ vert_start ].xy );
378 }
379
380 // Sort each faces and trianglulalate them
381 for( int k = 0; k < num_planes; k ++ )
382 {
383 vmf_face *face = faces + k;
384
385 if( face->blacklisted ) continue;
386
387 if( csr_sb_count( face->indices ) < 3 )
388 {
389 flag = k_ESolidResult_degenerate;
390 log_error( "Skipping degenerate face\n" );
391 continue;
392 }
393
394 // Sort only if there is no displacements, or if this side is
395 if( !is_displacement || ( is_displacement && face->dispinfo ) )
396 {
397 sort_coplanar( planes+k*4, ctx->verts, face->indices, csr_sb_count( face->indices ) );
398 }
399
400 if( is_displacement )
401 {
402 // Compute displacement
403 if( face->dispinfo )
404 {
405 if( csr_sb_count( face->indices ) != 4 )
406 {
407 flag = k_ESolidResult_degenerate;
408 log_error( "Skipping degenerate displacement\n" );
409 continue;
410 }
411
412 // Match starting position
413 v3f start;
414 int sw = 0;
415 float dmin = 999999.f;
416
417 vdf_node *dispinfo = face->dispinfo;
418 vdf_node *vdf_normals = vdf_next( dispinfo, "normals", NULL );
419 vdf_node *vdf_distances = vdf_next( dispinfo, "distances", NULL );
420
421 kv_float_array( dispinfo, "startposition", 3, start );
422
423 for( int j = 0; j < csr_sb_count( face->indices ); j ++ )
424 {
425 float d2 = v3_dist2( start, ctx->verts[ face->indices[ j ] ].co );
426 if( d2 < dmin )
427 {
428 dmin = d2;
429 sw = j;
430 }
431 }
432
433 // Can be either 5, 9, 17
434 numpoints = pow( 2, kv_get_int( dispinfo, "power", 2 ) ) + 1;
435 u32 reqverts = numpoints*numpoints;
436 u32 reqidx = (numpoints-1)*(numpoints-1)*6;
437
438 ctx->verts = csr_sb_reserve( ctx->verts, reqverts, sizeof( vmf_vert ) );
439 ctx->indices = csr_sb_reserve( ctx->indices, reqidx, sizeof( u32 ) );
440
441 // Get corners of displacement
442 float *SW = ctx->verts[ face->indices[ sw ] ].co;
443 float *NW = ctx->verts[ face->indices[ (sw+1) % 4] ].co;
444 float *NE = ctx->verts[ face->indices[ (sw+2) % 4] ].co;
445 float *SE = ctx->verts[ face->indices[ (sw+3) % 4] ].co;
446
447 float normals[ 17*3 ];
448 float distances[ 17 ];
449
450 // Calculate displacement positions
451 for( int j = 0; j < numpoints; j ++ )
452 {
453 char key[14];
454 sprintf( key, "row%i", j );
455
456 kv_float_array( vdf_normals, key, 17*3, normals );
457 kv_float_array( vdf_distances, key, 17, distances );
458
459 float dx = (float)j / (float)(numpoints - 1); //Time values for linear interpolation
460
461 for( int m = 0; m < numpoints; m ++ )
462 {
463 vmf_vert *vert = &ctx->verts[ csr_sb_count( ctx->verts ) + j*numpoints + m ];
464
465 float dy = (float)m / (float)(numpoints - 1);
466
467 v3f lwr; v3f upr;
468
469 v3_lerp( SW, SE, dx, lwr );
470 v3_lerp( NW, NE, dx, upr );
471 v3_lerp( lwr, upr, dy, vert->co );
472
473 v3_muladds( vert->co, normals + m * 3, distances[ m ], vert->co );
474
475 // Todo, put correct normal
476 v3_copy( (v3f){ 0.f, 0.f, 1.f }, vert->nrm );
477 }
478 }
479
480 // Build displacement indices
481 int condition = 0;
482 for( int row = 0; row < numpoints - 1; row ++ )
483 {
484 for( int col = 0; col < numpoints - 1; col ++ )
485 {
486 u32 c = csr_sb_count( ctx->verts );
487
488 u32 idxsw = c + ( row + 0 ) * numpoints + col + 0 ;
489 u32 idxse = c + ( row + 0 ) * numpoints + col + 1 ;
490 u32 idxnw = c + ( row + 1 ) * numpoints + col + 0 ;
491 u32 idxne = c + ( row + 1 ) * numpoints + col + 1 ;
492
493 if( (condition ++) % 2 == 0 )
494 {
495 solid_disp_tri( ctx, idxne, idxnw, idxsw );
496 solid_disp_tri( ctx, idxse, idxne, idxsw );
497 }
498 else
499 {
500 solid_disp_tri( ctx, idxse, idxnw, idxsw );
501 solid_disp_tri( ctx, idxse, idxne, idxnw );
502 }
503 }
504 condition ++;
505 }
506
507 csr_sb_inc( ctx->verts, numpoints*numpoints );
508 }
509 }
510 else
511 {
512 u32 tris = csr_sb_count( face->indices ) -2;
513 ctx->indices = csr_sb_reserve( ctx->indices, tris*3, sizeof( u32 ) );
514
515 u32 c = csr_sb_count( ctx->indices );
516
517 for( int j = 0; j < tris; j ++ )
518 {
519 ctx->indices[ c +j*3 +0 ] = face->indices[ 0 ];
520 ctx->indices[ c +j*3 +1 ] = face->indices[ j+1 ];
521 ctx->indices[ c +j*3 +2 ] = face->indices[ j+2 ];
522
523 // A 0,1,2:: A,B,C
524 // D B 0,2,3:: A,C,D
525 // C
526 }
527
528 csr_sb_inc( ctx->indices, tris*3 );
529 }
530 }
531
532 // Free temp polyon buffers
533 for( int j = 0; j < num_planes; j ++ )
534 {
535 csr_sb_free( faces[ j ].indices );
536 }
537
538 return flag;
539 }
540
541 u32 vmf_get_mdl( vmf_map *map, const char *mdl )
542 {
543 u32 hash = djb2( (const unsigned char *)mdl );
544
545 for( u32 i = 0; i < csr_sb_count( map->models ); i ++ )
546 {
547 if( hash == map->models[i].hash && !strcmp( map->models[i].str, mdl ) )
548 {
549 return i;
550 }
551 }
552
553 return 0;
554 }
555
556 int vmf_class_is_prop( vdf_node *ent )
557 {
558 return !strncmp( kv_get( ent, "classname", "" ), "prop_", 5 );
559 }
560
561 void vmf_populate_models( vdf_node *vmf, vmf_map *map )
562 {
563 vdf_foreach( vmf, "entity", ent )
564 {
565 // Use any class name with prop_
566 if( vmf_class_is_prop( ent ) )
567 {
568 // Check if it exists
569 const char *model_path = kv_get( ent, "model", "" );
570 u32 mdl_id = vmf_get_mdl( map, model_path );
571
572 if( !mdl_id )
573 {
574 map->models = csr_sb_reserve( map->models, 1, sizeof( struct vmf_model ));
575
576 struct vmf_model *entry = &map->models[ csr_sb_count( map->models ) ];
577 entry->need_load = 0;
578 mdl_error( &entry->mdl ); // (just setting arrays to 0)
579
580 entry->str = csr_malloc( strlen( model_path ) +1 );
581 strcpy( entry->str, model_path );
582 entry->hash = djb2( (const unsigned char *)model_path );
583
584 mdl_id = csr_sb_count( map->models );
585 csr_sb_use( map->models );
586 }
587
588 // Assign prop-ID for later use
589 ent->user = VMF_FLAG_IS_PROP;
590 ent->user1 = mdl_id;
591 }
592 }
593 }
594
595 // Load all models
596 void vmf_index_models( vmf_map *map )
597 {
598 log_info( "Indexing all models\n" );
599
600 // Error model == blank
601 map->models = csr_sb_reserve( map->models, 1, sizeof( struct vmf_model ));
602 csr_sb_use( map->models );
603 mdl_error( &map->models[0].mdl );
604
605 // Create listings for each model
606 vmf_populate_models( map->root, map );
607
608 for( int i = 0; i < csr_sb_count( map->cache ); i ++ )
609 {
610 vmf_populate_models( map->cache[i].root, map );
611 }
612
613 log_info( "Indexed (%u) models\n", csr_sb_count( map->models )-1 );
614 }
615
616 void vmf_load_models( vmf_map *map )
617 {
618 u32 num_success = 0;
619 u32 num_reqs = 0;
620
621 for( int i = 1; i < csr_sb_count( map->models ); i ++ )
622 {
623 struct vmf_model *mdl = &map->models[i];
624
625 if( mdl->need_load )
626 {
627 num_reqs ++;
628
629 if( mdl_from_find_files( mdl->str, &mdl->mdl ) )
630 {
631 num_success ++;
632 }
633 else
634 {
635 log_warn( "Failed to load model: %s\n", mdl->str );
636 }
637 }
638 }
639
640 log_info( "Done (%u of %u loaded)\n", num_success, num_reqs );
641 }
642
643 u32 vmf_init_subvmf( vmf_map *map, const char *subvmf );
644
645 void vmf_load_all_instances( vmf_map *map, vdf_node *vmf )
646 {
647 vdf_foreach( vmf, "entity", ent )
648 {
649 if( !strcmp( kv_get( ent, "classname", "" ), "func_instance" ))
650 {
651 // Entity is in use if file is specified, if not just ignore the entity.
652 const char *path = kv_get( ent, "file", "" );
653 if( strcmp( path, "" ) )
654 {
655 if( (ent->user1 = vmf_init_subvmf( map, path )))
656 {
657 ent->user1 --;
658 ent->user = VMF_FLAG_IS_INSTANCE;
659 }
660 }
661 }
662 }
663 }
664
665 // TODO: Merge this into above function.. doesnt need to be seperated
666 u32 vmf_init_subvmf( vmf_map *map, const char *subvmf )
667 {
668 u32 id;
669 u32 hash = djb2( (const unsigned char *)subvmf );
670
671 // Check if present
672 for( u32 i = 0; i < csr_sb_count( map->cache ); i ++ )
673 {
674 if( hash == map->cache[i].hash )
675 {
676 if( !strcmp( map->cache[i].name, subvmf ) )
677 {
678 if( map->cache[i].root )
679 return i+1;
680 else
681 return 0;
682 }
683 }
684 }
685
686 id = csr_sb_count( map->cache );
687 map->cache = csr_sb_reserve( map->cache, 1, sizeof( struct vmf_instance ));
688 struct vmf_instance *inst = &map->cache[ id ];
689
690 csr_sb_use( map->cache );
691 inst->hash = hash;
692 inst->name = csr_malloc( strlen( subvmf )+1 );
693 strcpy( inst->name, subvmf );
694
695 if( (inst->root = vdf_open_file( subvmf )) )
696 {
697 // Recursive load other instances
698 vmf_load_all_instances( map, inst->root );
699 return id+1;
700 }
701 else
702 {
703 log_error( "Failed to load instance file: %s\n", subvmf );
704 return 0;
705 }
706 }
707
708 vmf_map *vmf_init( const char *path )
709 {
710 vmf_map *map = csr_calloc( sizeof( vmf_map ) );
711 map->root = vdf_open_file( path );
712
713 if( !map->root )
714 {
715 free( map );
716 return NULL;
717 }
718
719 // Prepare instances
720 vmf_load_all_instances( map, map->root );
721
722 // Other resources
723 vmf_index_models( map );
724 return map;
725 }
726
727 void vmf_free( vmf_map *map )
728 {
729 for( int i = 0; i < csr_sb_count( map->cache ); i ++ )
730 {
731 if( map->cache[i].root )
732 vdf_free_r( map->cache[i].root );
733
734 free( map->cache[i].name );
735 }
736
737 for( int i = 1; i < csr_sb_count( map->models ); i ++ )
738 {
739 free( map->models[i].str );
740 mdl_free( &map->models[i].mdl );
741 }
742
743 vdf_free_r( map->root );
744 csr_sb_free( map->models );
745 csr_sb_free( map->cache );
746 free( map );
747 }
748
749 void vmf_entity_transform( vdf_node *ent, m4x3f mat )
750 {
751 v3f angles = {0.f,0.f,0.f};
752 v3f offset = {0.f,0.f,0.f};
753 float scale;
754
755 // Parse
756 scale = kv_get_float( ent, "uniformscale", 1.f );
757 kv_float_array( ent, "angles", 3, angles );
758 kv_float_array( ent, "origin", 3, offset );
759
760 // Translation
761 m4x3_translate( mat, offset );
762
763 // Make rotation ( Pitch yaw roll // YZX. Source->OpenGL ordering a lil messed up )
764 m4x3_rotate_z( mat, csr_rad( angles[1] ) );
765 m4x3_rotate_y( mat, csr_rad( angles[0] ) );
766 m4x3_rotate_x( mat, csr_rad( angles[2] ) );
767
768 // Scale
769 m4x3_scale( mat, scale );
770 }
771
772 u32 vmf_visgroup_id( vdf_node *root, const char *name )
773 {
774 vdf_node *dict = vdf_next( root, "visgroups", NULL );
775
776 if( dict )
777 {
778 vdf_foreach( dict, "visgroup", group )
779 {
780 if( !strcmp( kv_get( group, "name", "" ), name ) )
781 {
782 return kv_get_int( group, "visgroupid", 0 );
783 }
784 }
785 }
786
787 return 0;
788 }
789
790 int vmf_visgroup_match( vdf_node *ent, u32 target )
791 {
792 vdf_node *editor = vdf_next( ent, "editor", NULL );
793
794 if( editor )
795 {
796 kv_foreach( editor, "visgroupid", groupe )
797 {
798 if( target == atoi( groupe ) )
799 return 1;
800 }
801 }
802
803 return 0;
804 }