mesh dump routines
[vrtris] / src / cmesh.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <float.h>
4 #include <assert.h>
5 #include "opengl.h"
6 #include "cmesh.h"
7 #include "logger.h"
8
9
10 struct cmesh_vattrib {
11         int nelem;      /* num elements per attribute [1, 4] */
12         float *data;    /* dynarr */
13         unsigned int vbo;
14         int vbo_valid, data_valid;
15 };
16
17
18 struct cmesh {
19         char *name;
20         unsigned int nverts, nfaces;
21
22         /* current value for each attribute for the immediate mode interface */
23         cgm_vec4 cur_val[CMESH_NUM_ATTR];
24
25         unsigned int buffer_objects[CMESH_NUM_ATTR + 1];
26         struct cmesh_vattrib vattr[CMESH_NUM_ATTR];
27
28         unsigned int *idata;    /* dynarr */
29         unsigned int ibo;
30         int ibo_valid, idata_valid;
31
32         /* index buffer for wireframe rendering (constructed on demand) */
33         unsigned int wire_ibo;
34         int wire_ibo_valid;
35
36         /* axis-aligned bounding box */
37         cgm_vec3 aabb_min, aabb_max;
38         int aabb_valid;
39         /* bounding sphere */
40         cgm_vec3 bsph_center;
41         float bsph_radius;
42         int bsph_valid;
43 };
44
45
46 static int pre_draw(struct cmesh *cm);
47 static void post_draw(struct cmesh *cm, int cur_sdr);
48 static void update_buffers(struct cmesh *cm);
49 static void update_wire_ibo(struct cmesh *cm);
50 static void calc_aabb(struct cmesh *cm);
51 static void calc_bsph(struct cmesh *cm);
52
53 static int def_nelem[CMESH_NUM_ATTR] = {3, 3, 3, 2, 4, 4, 4, 2};
54
55 static int sdr_loc[CMESH_NUM_ATTR] = {0, 1, 2, 3, 4, 5, 6, 7};
56 static int use_custom_sdr_attr;
57
58
59 /* global state */
60 void cmesh_set_attrib_sdrloc(int attr, int loc)
61 {
62         sdr_loc[attr] = loc;
63 }
64
65 int cmesh_get_attrib_sdrloc(int attr)
66 {
67         return sdr_loc[attr];
68 }
69
70 void cmesh_clear_attrib_sdrloc(void)
71 {
72         int i;
73         for(i=0; i<CMESH_NUM_ATTR; i++) {
74                 sdr_loc[i] = -1;
75         }
76 }
77
78 /* mesh functions */
79 struct cmesh *cmesh_alloc(void)
80 {
81         struct cmesh *cm;
82
83         if(!(cm = malloc(sizeof *cm))) {
84                 return 0;
85         }
86         if(cmesh_init(cm) == -1) {
87                 free(cm);
88                 return 0;
89         }
90         return cm;
91 }
92
93 void cmesh_free(struct cmesh *cm)
94 {
95         cmesh_destroy(cm);
96         free(cm);
97 }
98
99 int cmesh_init(struct cmesh *cm)
100 {
101         int i;
102
103         memset(cm, 0, sizeof *cm);
104         cgm_wcons(cm->cur_val + CMESH_ATTR_COLOR, 1, 1, 1, 1);
105
106         glGenBuffers(CMESH_NUM_ATTR + 1, cm->buffer_objects);
107
108         for(i=0; i<CMESH_NUM_ATTR; i++) {
109                 if(!(cm->vattr[i].data = dynarr_alloc(0, sizeof(float)))) {
110                         cmesh_destroy(cm);
111                         return -1;
112                 }
113                 cm->vattr[i].vbo = cm->buffer_objects[i];
114         }
115
116         cm->ibo = cm->buffer_objects[CMESH_NUM_ATTR];
117         if(!(cm->idata = dynarr_alloc(0, sizeof *cm->idata))) {
118                 cmesh_destroy(cm);
119                 return -1;
120         }
121         return 0;
122 }
123
124 void cmesh_destroy(struct cmesh *cm)
125 {
126         int i;
127
128         free(cm->name);
129
130         for(i=0; i<CMESH_NUM_ATTR; i++) {
131                 dynarr_free(cm->vattr[i].data);
132         }
133         dynarr_free(cm->idata);
134
135         glDeleteBuffers(CMESH_NUM_ATTR + 1, cm->buffer_objects);
136         if(cm->wire_ibo) {
137                 glDeleteBuffers(1, &cm->wire_ibo);
138         }
139 }
140
141 void cmesh_clear(struct cmesh *cm)
142 {
143         int i;
144
145         for(i=0; i<CMESH_NUM_ATTR; i++) {
146                 cm->vattr[i].nelem = 0;
147                 cm->vattr[i].vbo_valid = 0;
148                 cm->vattr[i].data_valid = 0;
149                 cm->vattr[i].data = dynarr_clear(cm->vattr[i].data);
150         }
151         cm->ibo_valid = cm->idata_valid = 0;
152         cm->idata = dynarr_clear(cm->idata);
153
154         cm->wire_ibo_valid = 0;
155         cm->nverts = cm->nfaces = 0;
156
157         cm->bsph_valid = cm->aabb_valid = 0;
158 }
159
160 int cmesh_clone(struct cmesh *cmdest, struct cmesh *cmsrc)
161 {
162         int i, num, nelem;
163         char *name = 0;
164         float *varr[CMESH_NUM_ATTR] = {0};
165         unsigned int *iarr = 0;
166
167         /* do anything that can fail first, before making any changes to cmdest
168          * so we have the option of recovering gracefuly
169          */
170         if(cmsrc->name) {
171                 if(!(name = malloc(strlen(cmsrc->name)))) {
172                         return -1;
173                 }
174                 strcpy(name, cmsrc->name);
175         }
176         if(cmesh_indexed(cmsrc)) {
177                 num = dynarr_size(cmsrc->idata);
178                 if(!(iarr = dynarr_alloc(num, sizeof *iarr))) {
179                         free(name);
180                         return -1;
181                 }
182         }
183         for(i=0; i<CMESH_NUM_ATTR; i++) {
184                 if(cmesh_has_attrib(cmsrc, i)) {
185                         nelem = cmsrc->vattr[i].nelem;
186                         num = dynarr_size(cmsrc->vattr[i].data);
187                         if(!(varr[i] = dynarr_alloc(num * nelem, sizeof(float)))) {
188                                 while(--i >= 0) {
189                                         dynarr_free(varr[i]);
190                                 }
191                                 dynarr_free(iarr);
192                                 free(name);
193                                 return -1;
194                         }
195                 }
196         }
197
198         cmesh_clear(cmdest);
199
200         for(i=0; i<CMESH_NUM_ATTR; i++) {
201                 dynarr_free(cmdest->vattr[i].data);
202
203                 if(cmesh_has_attrib(cmsrc, i)) {
204                         cmesh_attrib(cmsrc, i); /* force validation of the actual data on the source mesh */
205
206                         nelem = cmsrc->vattr[i].nelem;
207                         cmdest->vattr[i].nelem = nelem;
208                         num = dynarr_size(cmsrc->vattr[i].data);
209                         cmdest->vattr[i].data = varr[i];
210                         memcpy(cmdest->vattr[i].data, cmsrc->vattr[i].data, num * nelem * sizeof(float));
211                         cmdest->vattr[i].data_valid = 1;
212                         cmdest->vattr[i].vbo_valid = 0;
213                 } else {
214                         memset(cmdest->vattr + i, 0, sizeof cmdest->vattr[i]);
215                 }
216         }
217
218         dynarr_free(cmdest->idata);
219         if(cmesh_indexed(cmsrc)) {
220                 cmesh_index(cmsrc);     /* force validation .... */
221
222                 num = dynarr_size(cmsrc->idata);
223                 cmdest->idata = iarr;
224                 memcpy(cmdest->idata, cmsrc->idata, num * sizeof *cmdest->idata);
225                 cmdest->idata_valid = 1;
226         } else {
227                 cmdest->idata = 0;
228                 cmdest->idata_valid = cmdest->ibo_valid = 0;
229         }
230
231         free(cmdest->name);
232         cmdest->name = name;
233
234         cmdest->nverts = cmsrc->nverts;
235         cmdest->nfaces = cmsrc->nfaces;
236
237         memcpy(cmdest->cur_val, cmsrc->cur_val, sizeof cmdest->cur_val);
238
239         cmdest->aabb_min = cmsrc->aabb_min;
240         cmdest->aabb_max = cmsrc->aabb_max;
241         cmdest->aabb_valid = cmsrc->aabb_valid;
242         cmdest->bsph_center = cmsrc->bsph_center;
243         cmdest->bsph_radius = cmsrc->bsph_radius;
244         cmdest->bsph_valid = cmsrc->bsph_valid;
245
246         return 0;
247 }
248
249 int cmesh_set_name(struct cmesh *cm, const char *name)
250 {
251         int len = strlen(name);
252         char *tmp = malloc(len + 1);
253         if(!tmp) return -1;
254         free(cm->name);
255         cm->name = tmp;
256         memcpy(cm->name, name, len + 1);
257         return 0;
258 }
259
260 const char *cmesh_name(struct cmesh *cm)
261 {
262         return cm->name;
263 }
264
265 int cmesh_has_attrib(struct cmesh *cm, int attr)
266 {
267         if(attr < 0 || attr >= CMESH_NUM_ATTR) {
268                 return 0;
269         }
270         return cm->vattr[attr].vbo_valid | cm->vattr[attr].data_valid;
271 }
272
273 int cmesh_indexed(struct cmesh *cm)
274 {
275         return cm->ibo_valid | cm->idata_valid;
276 }
277
278 /* vdata can be 0, in which case only memory is allocated
279  * returns pointer to the attribute array
280  */
281 float *cmesh_set_attrib(struct cmesh *cm, int attr, int nelem, unsigned int num,
282                 const float *vdata)
283 {
284         float *newarr;
285
286         if(attr < 0 || attr >= CMESH_NUM_ATTR) {
287                 return 0;
288         }
289         if(cm->nverts && num != cm->nverts) {
290                 return 0;
291         }
292
293         if(!(newarr = dynarr_alloc(num * nelem, sizeof *newarr))) {
294                 return 0;
295         }
296         if(vdata) {
297                 memcpy(newarr, vdata, num * nelem * sizeof *newarr);
298         }
299
300         cm->nverts = num;
301
302         dynarr_free(cm->vattr[attr].data);
303         cm->vattr[attr].data = newarr;
304         cm->vattr[attr].nelem = nelem;
305         cm->vattr[attr].data_valid = 1;
306         cm->vattr[attr].vbo_valid = 0;
307         return newarr;
308 }
309
310 float *cmesh_attrib(struct cmesh *cm, int attr)
311 {
312         if(attr < 0 || attr >= CMESH_NUM_ATTR) {
313                 return 0;
314         }
315         cm->vattr[attr].vbo_valid = 0;
316         return (float*)cmesh_attrib_ro(cm, attr);
317 }
318
319 const float *cmesh_attrib_ro(struct cmesh *cm, int attr)
320 {
321         float *tmp;
322         int nelem;
323
324         if(attr < 0 || attr >= CMESH_NUM_ATTR) {
325                 return 0;
326         }
327
328         if(!cm->vattr[attr].data_valid) {
329 #if GL_ES_VERSION_2_0
330                 return 0;
331 #else
332                 if(!cm->vattr[attr].vbo_valid) {
333                         return 0;
334                 }
335
336                 /* local data copy unavailable, grab the data from the vbo */
337                 nelem = cm->vattr[attr].nelem;
338                 if(!(tmp = dynarr_resize(cm->vattr[attr].data, cm->nverts * nelem))) {
339                         return 0;
340                 }
341                 cm->vattr[attr].data = tmp;
342
343                 glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[attr].vbo);
344                 tmp = glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);
345                 memcpy(cm->vattr[attr].data, tmp, cm->nverts * nelem * sizeof(float));
346                 glUnmapBuffer(GL_ARRAY_BUFFER);
347
348                 cm->vattr[attr].data_valid = 1;
349 #endif
350         }
351         return cm->vattr[attr].data;
352 }
353
354 float *cmesh_attrib_at(struct cmesh *cm, int attr, int idx)
355 {
356         float *vptr = cmesh_attrib(cm, attr);
357         return vptr ? vptr + idx * cm->vattr[attr].nelem : 0;
358 }
359
360 const float *cmesh_attrib_at_ro(struct cmesh *cm, int attr, int idx)
361 {
362         const float *vptr = cmesh_attrib_ro(cm, attr);
363         return vptr ? vptr + idx * cm->vattr[attr].nelem : 0;
364 }
365
366 int cmesh_attrib_count(struct cmesh *cm, int attr)
367 {
368         return cmesh_has_attrib(cm, attr) ? cm->nverts : 0;
369 }
370
371 int cmesh_push_attrib(struct cmesh *cm, int attr, float *v)
372 {
373         float *vptr;
374         int i, cursz, newsz;
375
376         if(!cm->vattr[attr].nelem) {
377                 cm->vattr[attr].nelem = def_nelem[attr];
378         }
379
380         cursz = dynarr_size(cm->vattr[attr].data);
381         newsz = cursz + cm->vattr[attr].nelem;
382         if(!(vptr = dynarr_resize(cm->vattr[attr].data, newsz))) {
383                 return -1;
384         }
385         cm->vattr[attr].data = vptr;
386         vptr += cursz;
387
388         for(i=0; i<cm->vattr[attr].nelem; i++) {
389                 *vptr++ = *v++;
390         }
391         cm->vattr[attr].data_valid = 1;
392         cm->vattr[attr].vbo_valid = 0;
393
394         if(attr == CMESH_ATTR_VERTEX) {
395                 cm->nverts = newsz / cm->vattr[attr].nelem;
396         }
397         return 0;
398 }
399
400 int cmesh_push_attrib1f(struct cmesh *cm, int attr, float x)
401 {
402         float v[4];
403         v[0] = x;
404         v[1] = v[2] = 0.0f;
405         v[3] = 1.0f;
406         return cmesh_push_attrib(cm, attr, v);
407 }
408
409 int cmesh_push_attrib2f(struct cmesh *cm, int attr, float x, float y)
410 {
411         float v[4];
412         v[0] = x;
413         v[1] = y;
414         v[2] = 0.0f;
415         v[3] = 1.0f;
416         return cmesh_push_attrib(cm, attr, v);
417 }
418
419 int cmesh_push_attrib3f(struct cmesh *cm, int attr, float x, float y, float z)
420 {
421         float v[4];
422         v[0] = x;
423         v[1] = y;
424         v[2] = z;
425         v[3] = 1.0f;
426         return cmesh_push_attrib(cm, attr, v);
427 }
428
429 int cmesh_push_attrib4f(struct cmesh *cm, int attr, float x, float y, float z, float w)
430 {
431         float v[4];
432         v[0] = x;
433         v[1] = y;
434         v[2] = z;
435         v[3] = w;
436         return cmesh_push_attrib(cm, attr, v);
437 }
438
439 /* indices can be 0, in which case only memory is allocated
440  * returns pointer to the index array
441  */
442 unsigned int *cmesh_set_index(struct cmesh *cm, int num, const unsigned int *indices)
443 {
444         unsigned int *tmp;
445         int nidx = cm->nfaces * 3;
446
447         if(nidx && num != nidx) {
448                 return 0;
449         }
450
451         if(!(tmp = dynarr_alloc(num, sizeof *tmp))) {
452                 return 0;
453         }
454         if(indices) {
455                 memcpy(tmp, indices, num * sizeof *tmp);
456         }
457
458         dynarr_free(cm->idata);
459         cm->idata = tmp;
460         cm->idata_valid = 1;
461         cm->ibo_valid = 0;
462         return tmp;
463 }
464
465 unsigned int *cmesh_index(struct cmesh *cm)
466 {
467         cm->ibo_valid = 0;
468         return (unsigned int*)cmesh_index_ro(cm);
469 }
470
471 const unsigned int *cmesh_index_ro(struct cmesh *cm)
472 {
473         int nidx;
474         unsigned int *tmp;
475
476         if(!cm->idata_valid) {
477 #if GL_ES_VERSION_2_0
478                 return 0;
479 #else
480                 if(!cm->ibo_valid) {
481                         return 0;
482                 }
483
484                 /* local copy is unavailable, grab the data from the ibo */
485                 nidx = cm->nfaces * 3;
486                 if(!(tmp = dynarr_alloc(nidx, sizeof *cm->idata))) {
487                         return 0;
488                 }
489                 dynarr_free(cm->idata);
490                 cm->idata = tmp;
491
492                 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cm->ibo);
493                 tmp = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY);
494                 memcpy(cm->idata, tmp, nidx * sizeof *cm->idata);
495                 glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
496
497                 cm->idata_valid = 1;
498 #endif
499         }
500         return cm->idata;
501 }
502
503 int cmesh_index_count(struct cmesh *cm)
504 {
505         return cm->nfaces * 3;
506 }
507
508 int cmesh_push_index(struct cmesh *cm, unsigned int idx)
509 {
510         unsigned int *iptr;
511         if(!(iptr = dynarr_push(cm->idata, &idx))) {
512                 return -1;
513         }
514         cm->idata = iptr;
515         cm->idata_valid = 1;
516         cm->ibo_valid = 0;
517
518         cm->nfaces = dynarr_size(cm->idata) / 3;
519         return 0;
520 }
521
522 int cmesh_poly_count(struct cmesh *cm)
523 {
524         if(cm->nfaces) {
525                 return cm->nfaces;
526         }
527         if(cm->nverts) {
528                 return cm->nverts / 3;
529         }
530         return 0;
531 }
532
533 /* attr can be -1 to invalidate all attributes */
534 void cmesh_invalidate_vbo(struct cmesh *cm, int attr)
535 {
536         int i;
537
538         if(attr >= CMESH_NUM_ATTR) {
539                 return;
540         }
541
542         if(attr < 0) {
543                 for(i=0; i<CMESH_NUM_ATTR; i++) {
544                         cm->vattr[i].vbo_valid = 0;
545                 }
546         } else {
547                 cm->vattr[attr].vbo_valid = 0;
548         }
549 }
550
551 void cmesh_invalidate_index(struct cmesh *cm)
552 {
553         cm->ibo_valid = 0;
554 }
555
556 int cmesh_append(struct cmesh *cmdest, struct cmesh *cmsrc)
557 {
558         int i, nelem, newsz, origsz, srcsz;
559         float *vptr;
560         unsigned int *iptr;
561         unsigned int idxoffs;
562
563         if(!cmdest->nverts) {
564                 return cmesh_clone(cmdest, cmsrc);
565         }
566
567         for(i=0; i<CMESH_NUM_ATTR; i++) {
568                 if(cmesh_has_attrib(cmdest, i) && cmesh_has_attrib(cmsrc, i)) {
569                         /* force validation of the data arrays */
570                         cmesh_attrib(cmdest, i);
571                         cmesh_attrib_ro(cmsrc, i);
572
573                         assert(cmdest->vattr[i].nelem == cmsrc->vattr[i].nelem);
574                         nelem = cmdest->vattr[i].nelem;
575                         origsz = cmdest->nverts * nelem;
576                         newsz = cmdest->nverts + cmsrc->nverts * nelem;
577
578                         if(!(vptr = dynarr_resize(cmdest->vattr[i].data, newsz))) {
579                                 return -1;
580                         }
581                         memcpy(vptr + origsz, cmsrc->vattr[i].data, cmsrc->nverts * nelem * sizeof(float));
582                         cmdest->vattr[i].data = vptr;
583                 }
584         }
585
586         if(cmesh_indexed(cmdest)) {
587                 assert(cmesh_indexed(cmsrc));
588                 /* force validation ... */
589                 cmesh_index(cmdest);
590                 cmesh_index_ro(cmsrc);
591
592                 idxoffs = cmdest->nverts;
593                 origsz = dynarr_size(cmdest->idata);
594                 srcsz = dynarr_size(cmsrc->idata);
595                 newsz = origsz + srcsz;
596
597                 if(!(iptr = dynarr_resize(cmdest->idata, newsz))) {
598                         return -1;
599                 }
600                 cmdest->idata = iptr;
601
602                 /* copy and fixup all the new indices */
603                 iptr += origsz;
604                 for(i=0; i<srcsz; i++) {
605                         *iptr++ = cmsrc->idata[i] + idxoffs;
606                 }
607         }
608
609         cmdest->wire_ibo_valid = 0;
610         cmdest->aabb_valid = 0;
611         cmdest->bsph_valid = 0;
612         return 0;
613 }
614
615 /* assemble a complete vertex by adding all the useful attributes */
616 int cmesh_vertex(struct cmesh *cm, float x, float y, float z)
617 {
618         int i, j;
619
620         cgm_wcons(cm->cur_val + CMESH_ATTR_VERTEX, x, y, z, 1.0f);
621         cm->vattr[CMESH_ATTR_VERTEX].data_valid = 1;
622         cm->vattr[CMESH_ATTR_VERTEX].nelem = 3;
623
624         for(i=0; i<CMESH_ATTR_VERTEX; i++) {
625                 if(cm->vattr[i].data_valid) {
626                         for(j=0; j<cm->vattr[CMESH_ATTR_VERTEX].nelem; j++) {
627                                 float *tmp = dynarr_push(cm->vattr[i].data, &cm->cur_val[i].x + j);
628                                 if(!tmp) return -1;
629                                 cm->vattr[i].data = tmp;
630                         }
631                 }
632                 cm->vattr[i].vbo_valid = 0;
633                 cm->vattr[i].data_valid = 1;
634         }
635
636         if(cm->idata_valid) {
637                 cm->idata = dynarr_clear(cm->idata);
638         }
639         cm->ibo_valid = cm->idata_valid = 0;
640         return 0;
641 }
642
643 void cmesh_normal(struct cmesh *cm, float nx, float ny, float nz)
644 {
645         cgm_wcons(cm->cur_val + CMESH_ATTR_NORMAL, nx, ny, nz, 1.0f);
646         cm->vattr[CMESH_ATTR_NORMAL].nelem = 3;
647 }
648
649 void cmesh_tangent(struct cmesh *cm, float tx, float ty, float tz)
650 {
651         cgm_wcons(cm->cur_val + CMESH_ATTR_TANGENT, tx, ty, tz, 1.0f);
652         cm->vattr[CMESH_ATTR_TANGENT].nelem = 3;
653 }
654
655 void cmesh_texcoord(struct cmesh *cm, float u, float v, float w)
656 {
657         cgm_wcons(cm->cur_val + CMESH_ATTR_TEXCOORD, u, v, w, 1.0f);
658         cm->vattr[CMESH_ATTR_TEXCOORD].nelem = 3;
659 }
660
661 void cmesh_boneweights(struct cmesh *cm, float w1, float w2, float w3, float w4)
662 {
663         cgm_wcons(cm->cur_val + CMESH_ATTR_BONEWEIGHTS, w1, w2, w3, w4);
664         cm->vattr[CMESH_ATTR_BONEWEIGHTS].nelem = 4;
665 }
666
667 void cmesh_boneidx(struct cmesh *cm, int idx1, int idx2, int idx3, int idx4)
668 {
669         cgm_wcons(cm->cur_val + CMESH_ATTR_BONEIDX, idx1, idx2, idx3, idx4);
670         cm->vattr[CMESH_ATTR_BONEIDX].nelem = 4;
671 }
672
673 static float *get_vec4(struct cmesh *cm, int attr, int idx, cgm_vec4 *res)
674 {
675         int i;
676         float *sptr, *dptr;
677         cgm_wcons(res, 0, 0, 0, 1);
678         if(!(sptr = cmesh_attrib_at(cm, attr, idx))) {
679                 return 0;
680         }
681         dptr = &res->x;
682
683         for(i=0; i<cm->vattr[attr].nelem; i++) {
684                 *dptr++ = sptr[i];
685         }
686         return sptr;
687 }
688
689 static float *get_vec3(struct cmesh *cm, int attr, int idx, cgm_vec3 *res)
690 {
691         int i;
692         float *sptr, *dptr;
693         cgm_vcons(res, 0, 0, 0);
694         if(!(sptr = cmesh_attrib_at(cm, attr, idx))) {
695                 return 0;
696         }
697         dptr = &res->x;
698
699         for(i=0; i<cm->vattr[attr].nelem; i++) {
700                 *dptr++ = sptr[i];
701         }
702         return sptr;
703 }
704
705 /* dir_xform can be null, in which case it's calculated from xform */
706 void cmesh_apply_xform(struct cmesh *cm, float *xform, float *dir_xform)
707 {
708         unsigned int i;
709         int j;
710         cgm_vec4 v;
711         cgm_vec3 n, t;
712         float *vptr;
713
714         for(i=0; i<cm->nverts; i++) {
715                 if(!(vptr = get_vec4(cm, CMESH_ATTR_VERTEX, i, &v))) {
716                         return;
717                 }
718                 cgm_wmul_m4v4(&v, xform);
719                 for(j=0; j<cm->vattr[CMESH_ATTR_VERTEX].nelem; j++) {
720                         *vptr++ = (&v.x)[j];
721                 }
722
723                 if(cmesh_has_attrib(cm, CMESH_ATTR_NORMAL)) {
724                         if((vptr = get_vec3(cm, CMESH_ATTR_NORMAL, i, &n))) {
725                                 cgm_vmul_m3v3(&n, dir_xform);
726                                 for(j=0; j<cm->vattr[CMESH_ATTR_NORMAL].nelem; j++) {
727                                         *vptr++ = (&n.x)[j];
728                                 }
729                         }
730                 }
731                 if(cmesh_has_attrib(cm, CMESH_ATTR_TANGENT)) {
732                         if((vptr = get_vec3(cm, CMESH_ATTR_TANGENT, i, &t))) {
733                                 cgm_vmul_m3v3(&t, dir_xform);
734                                 for(j=0; j<cm->vattr[CMESH_ATTR_TANGENT].nelem; j++) {
735                                         *vptr++ = (&t.x)[j];
736                                 }
737                         }
738                 }
739         }
740 }
741
742 void cmesh_flip(struct cmesh *cm)
743 {
744         cmesh_flip_faces(cm);
745         cmesh_flip_normals(cm);
746 }
747
748 void cmesh_flip_faces(struct cmesh *cm)
749 {
750         int i, j, idxnum, vnum, nelem;
751         unsigned int *indices;
752         float *verts, *vptr;
753
754         if(cmesh_indexed(cm)) {
755                 if(!(indices = cmesh_index(cm))) {
756                         return;
757                 }
758                 idxnum = cmesh_index_count(cm);
759                 for(i=0; i<idxnum; i+=3) {
760                         unsigned int tmp = indices[i + 2];
761                         indices[i + 2] = indices[i + 1];
762                         indices[i + 1] = tmp;
763                 }
764         } else {
765                 if(!(verts = cmesh_attrib(cm, CMESH_ATTR_VERTEX))) {
766                         return;
767                 }
768                 vnum = cmesh_attrib_count(cm, CMESH_ATTR_VERTEX);
769                 nelem = cm->vattr[CMESH_ATTR_VERTEX].nelem;
770                 for(i=0; i<vnum; i+=3) {
771                         for(j=0; j<nelem; j++) {
772                                 vptr = verts + (i + 1) * nelem + j;
773                                 float tmp = vptr[nelem];
774                                 vptr[nelem] = vptr[0];
775                                 vptr[0] = tmp;
776                         }
777                 }
778         }
779 }
780 void cmesh_flip_normals(struct cmesh *cm)
781 {
782         int i, num;
783         float *nptr = cmesh_attrib(cm, CMESH_ATTR_NORMAL);
784         if(!nptr) return;
785
786         num = cm->nverts * cm->vattr[CMESH_ATTR_NORMAL].nelem;
787         for(i=0; i<num; i++) {
788                 *nptr = -*nptr;
789                 nptr++;
790         }
791 }
792
793 int cmesh_explode(struct cmesh *cm)
794 {
795         int i, j, k, idxnum, nnverts;
796         unsigned int *indices;
797
798         if(!cmesh_indexed(cm)) return 0;
799
800         indices = cmesh_index(cm);
801         assert(indices);
802
803         idxnum = cmesh_index_count(cm);
804         nnverts = idxnum;
805
806         for(i=0; i<CMESH_NUM_ATTR; i++) {
807                 const float *srcbuf;
808                 float *tmpbuf, *dstptr;
809
810                 if(!cmesh_has_attrib(cm, i)) continue;
811
812                 srcbuf = cmesh_attrib(cm, i);
813                 if(!(tmpbuf = dynarr_alloc(nnverts * cm->vattr[i].nelem, sizeof(float)))) {
814                         return -1;
815                 }
816                 dstptr = tmpbuf;
817
818                 for(j=0; j<idxnum; j++) {
819                         unsigned int idx = indices[j];
820                         const float *srcptr = srcbuf + idx * cm->vattr[i].nelem;
821
822                         for(k=0; k<cm->vattr[i].nelem; k++) {
823                                 *dstptr++ = *srcptr++;
824                         }
825                 }
826
827                 dynarr_free(cm->vattr[i].data);
828                 cm->vattr[i].data = tmpbuf;
829                 cm->vattr[i].data_valid = 1;
830         }
831
832         cm->ibo_valid = 0;
833         cm->idata_valid = 0;
834         cm->idata = dynarr_clear(cm->idata);
835
836         cm->nverts = nnverts;
837         cm->nfaces = idxnum / 3;
838         return 0;
839 }
840
841 void cmesh_calc_face_normals(struct cmesh *cm)
842 {
843         /* TODO */
844 }
845
846 static int pre_draw(struct cmesh *cm)
847 {
848         int i, loc, cur_sdr;
849
850         glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
851
852         update_buffers(cm);
853
854         if(!cm->vattr[CMESH_ATTR_VERTEX].vbo_valid) {
855                 return -1;
856         }
857
858         if(cur_sdr && use_custom_sdr_attr) {
859                 if(sdr_loc[CMESH_ATTR_VERTEX] == -1) {
860                         return -1;
861                 }
862
863                 for(i=0; i<CMESH_NUM_ATTR; i++) {
864                         loc = sdr_loc[i];
865                         if(loc >= 0 && cm->vattr[i].vbo_valid) {
866                                 glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[i].vbo);
867                                 glVertexAttribPointer(loc, cm->vattr[i].nelem, GL_FLOAT, GL_FALSE, 0, 0);
868                                 glEnableVertexAttribArray(loc);
869                         }
870                 }
871         } else {
872 #ifndef GL_ES_VERSION_2_0
873                 glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[CMESH_ATTR_VERTEX].vbo);
874                 glVertexPointer(cm->vattr[CMESH_ATTR_VERTEX].nelem, GL_FLOAT, 0, 0);
875                 glEnableClientState(GL_VERTEX_ARRAY);
876
877                 if(cm->vattr[CMESH_ATTR_NORMAL].vbo_valid) {
878                         glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[CMESH_ATTR_NORMAL].vbo);
879                         glNormalPointer(GL_FLOAT, 0, 0);
880                         glEnableClientState(GL_NORMAL_ARRAY);
881                 }
882                 if(cm->vattr[CMESH_ATTR_TEXCOORD].vbo_valid) {
883                         glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[CMESH_ATTR_TEXCOORD].vbo);
884                         glTexCoordPointer(cm->vattr[CMESH_ATTR_TEXCOORD].nelem, GL_FLOAT, 0, 0);
885                         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
886                 }
887                 if(cm->vattr[CMESH_ATTR_COLOR].vbo_valid) {
888                         glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[CMESH_ATTR_COLOR].vbo);
889                         glColorPointer(cm->vattr[CMESH_ATTR_COLOR].nelem, GL_FLOAT, 0, 0);
890                         glEnableClientState(GL_COLOR_ARRAY);
891                 }
892                 if(cm->vattr[CMESH_ATTR_TEXCOORD2].vbo_valid) {
893                         glClientActiveTexture(GL_TEXTURE1);
894                         glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[CMESH_ATTR_TEXCOORD2].vbo);
895                         glTexCoordPointer(cm->vattr[CMESH_ATTR_TEXCOORD2].nelem, GL_FLOAT, 0, 0);
896                         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
897                         glClientActiveTexture(GL_TEXTURE0);
898                 }
899 #endif  /* GL_ES_VERSION_2_0 */
900         }
901         glBindBuffer(GL_ARRAY_BUFFER, 0);
902         return cur_sdr;
903 }
904
905 void cmesh_draw(struct cmesh *cm)
906 {
907         int cur_sdr;
908
909         if((cur_sdr = pre_draw(cm)) == -1) {
910                 return;
911         }
912
913         if(cm->ibo_valid) {
914                 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cm->ibo);
915                 glDrawElements(GL_TRIANGLES, cm->nfaces * 3, GL_UNSIGNED_INT, 0);
916                 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
917         } else {
918                 glDrawArrays(GL_TRIANGLES, 0, cm->nverts);
919         }
920
921         post_draw(cm, cur_sdr);
922 }
923
924 static void post_draw(struct cmesh *cm, int cur_sdr)
925 {
926         int i;
927
928         if(cur_sdr && use_custom_sdr_attr) {
929                 for(i=0; i<CMESH_NUM_ATTR; i++) {
930                         int loc = sdr_loc[i];
931                         if(loc >= 0 && cm->vattr[i].vbo_valid) {
932                                 glDisableVertexAttribArray(loc);
933                         }
934                 }
935         } else {
936 #ifndef GL_ES_VERSION_2_0
937                 glDisableClientState(GL_VERTEX_ARRAY);
938                 if(cm->vattr[CMESH_ATTR_NORMAL].vbo_valid) {
939                         glDisableClientState(GL_NORMAL_ARRAY);
940                 }
941                 if(cm->vattr[CMESH_ATTR_TEXCOORD].vbo_valid) {
942                         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
943                 }
944                 if(cm->vattr[CMESH_ATTR_COLOR].vbo_valid) {
945                         glDisableClientState(GL_COLOR_ARRAY);
946                 }
947                 if(cm->vattr[CMESH_ATTR_TEXCOORD2].vbo_valid) {
948                         glClientActiveTexture(GL_TEXTURE1);
949                         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
950                         glClientActiveTexture(GL_TEXTURE0);
951                 }
952 #endif  /* GL_ES_VERSION_2_0 */
953         }
954 }
955
956 void cmesh_draw_wire(struct cmesh *cm, float linesz)
957 {
958         int cur_sdr, nfaces;
959
960         if((cur_sdr = pre_draw(cm)) == -1) {
961                 return;
962         }
963         update_wire_ibo(cm);
964
965         nfaces = cmesh_poly_count(cm);
966         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cm->wire_ibo);
967         glDrawElements(GL_LINES, nfaces * 6, GL_UNSIGNED_INT, 0);
968         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
969
970         post_draw(cm, cur_sdr);
971 }
972
973 void cmesh_draw_vertices(struct cmesh *cm, float ptsz)
974 {
975         int cur_sdr;
976         if((cur_sdr = pre_draw(cm)) == -1) {
977                 return;
978         }
979
980         glPushAttrib(GL_POINT_BIT);
981         glPointSize(ptsz);
982         glDrawArrays(GL_POINTS, 0, cm->nverts);
983         glPopAttrib();
984
985         post_draw(cm, cur_sdr);
986 }
987
988 void cmesh_draw_normals(struct cmesh *cm, float len)
989 {
990 #ifndef GL_ES_VERSION_2_0
991         int i, cur_sdr, vert_nelem, norm_nelem;
992         int loc = -1;
993         const float *varr, *norm;
994
995         varr = cmesh_attrib_ro(cm, CMESH_ATTR_VERTEX);
996         norm = cmesh_attrib_ro(cm, CMESH_ATTR_NORMAL);
997         if(!varr || !norm) return;
998
999         vert_nelem = cm->vattr[CMESH_ATTR_VERTEX].nelem;
1000         norm_nelem = cm->vattr[CMESH_ATTR_NORMAL].nelem;
1001
1002         glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
1003         if(cur_sdr && use_custom_sdr_attr) {
1004                 if((loc = sdr_loc[CMESH_ATTR_VERTEX]) < 0) {
1005                         return;
1006                 }
1007         }
1008
1009         glBegin(GL_LINES);
1010         for(i=0; i<cm->nverts; i++) {
1011                 float x, y, z, endx, endy, endz;
1012
1013                 x = varr[i * vert_nelem];
1014                 y = varr[i * vert_nelem + 1];
1015                 z = varr[i * vert_nelem + 2];
1016                 endx = x + norm[i * norm_nelem] * len;
1017                 endy = y + norm[i * norm_nelem + 1] * len;
1018                 endz = z + norm[i * norm_nelem + 2] * len;
1019
1020                 if(loc == -1) {
1021                         glVertex3f(x, y, z);
1022                         glVertex3f(endx, endy, endz);
1023                 } else {
1024                         glVertexAttrib3f(loc, x, y, z);
1025                         glVertexAttrib3f(loc, endx, endy, endz);
1026                 }
1027         }
1028         glEnd();
1029 #endif  /* GL_ES_VERSION_2_0 */
1030 }
1031
1032 void cmesh_draw_tangents(struct cmesh *cm, float len)
1033 {
1034 #ifndef GL_ES_VERSION_2_0
1035         int i, cur_sdr, vert_nelem, tang_nelem;
1036         int loc = -1;
1037         const float *varr, *tang;
1038
1039         varr = cmesh_attrib_ro(cm, CMESH_ATTR_VERTEX);
1040         tang = cmesh_attrib_ro(cm, CMESH_ATTR_TANGENT);
1041         if(!varr || !tang) return;
1042
1043         vert_nelem = cm->vattr[CMESH_ATTR_VERTEX].nelem;
1044         tang_nelem = cm->vattr[CMESH_ATTR_TANGENT].nelem;
1045
1046         glGetIntegerv(GL_CURRENT_PROGRAM, &cur_sdr);
1047         if(cur_sdr && use_custom_sdr_attr) {
1048                 if((loc = sdr_loc[CMESH_ATTR_VERTEX]) < 0) {
1049                         return;
1050                 }
1051         }
1052
1053         glBegin(GL_LINES);
1054         for(i=0; i<cm->nverts; i++) {
1055                 float x, y, z, endx, endy, endz;
1056
1057                 x = varr[i * vert_nelem];
1058                 y = varr[i * vert_nelem + 1];
1059                 z = varr[i * vert_nelem + 2];
1060                 endx = x + tang[i * tang_nelem] * len;
1061                 endy = y + tang[i * tang_nelem + 1] * len;
1062                 endz = z + tang[i * tang_nelem + 2] * len;
1063
1064                 if(loc == -1) {
1065                         glVertex3f(x, y, z);
1066                         glVertex3f(endx, endy, endz);
1067                 } else {
1068                         glVertexAttrib3f(loc, x, y, z);
1069                         glVertexAttrib3f(loc, endx, endy, endz);
1070                 }
1071         }
1072         glEnd();
1073 #endif  /* GL_ES_VERSION_2_0 */
1074 }
1075
1076 static void update_buffers(struct cmesh *cm)
1077 {
1078         int i;
1079
1080         for(i=0; i<CMESH_NUM_ATTR; i++) {
1081                 if(cmesh_has_attrib(cm, i) && !cm->vattr[i].vbo_valid) {
1082                         glBindBuffer(GL_ARRAY_BUFFER, cm->vattr[i].vbo);
1083                         glBufferData(GL_ARRAY_BUFFER, cm->nverts * cm->vattr[i].nelem * sizeof(float),
1084                                         cm->vattr[i].data, GL_STATIC_DRAW);
1085                         cm->vattr[i].vbo_valid = 1;
1086                 }
1087         }
1088         glBindBuffer(GL_ARRAY_BUFFER, 0);
1089
1090         if(cm->idata_valid && !cm->ibo_valid) {
1091                 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cm->ibo);
1092                 glBufferData(GL_ELEMENT_ARRAY_BUFFER, cm->nfaces * 3 * sizeof(unsigned int),
1093                                 cm->idata, GL_STATIC_DRAW);
1094                 cm->ibo_valid = 1;
1095                 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1096         }
1097 }
1098
1099 static void update_wire_ibo(struct cmesh *cm)
1100 {
1101         int i, num_faces;
1102         unsigned int *wire_idxarr, *dest;
1103
1104         update_buffers(cm);
1105
1106         if(cm->wire_ibo_valid) return;
1107
1108         if(!cm->wire_ibo) {
1109                 glGenBuffers(1, &cm->wire_ibo);
1110         }
1111         num_faces = cmesh_poly_count(cm);
1112
1113         if(!(wire_idxarr = malloc(num_faces * 6 * sizeof *wire_idxarr))) {
1114                 return;
1115         }
1116         dest = wire_idxarr;
1117
1118         if(cm->ibo_valid) {
1119                 /* we're dealing with an indexed mesh */
1120                 const unsigned int *idxarr = cmesh_index_ro(cm);
1121
1122                 for(i=0; i<num_faces; i++) {
1123                         *dest++ = idxarr[0];
1124                         *dest++ = idxarr[1];
1125                         *dest++ = idxarr[1];
1126                         *dest++ = idxarr[2];
1127                         *dest++ = idxarr[2];
1128                         *dest++ = idxarr[0];
1129                         idxarr += 3;
1130                 }
1131         } else {
1132                 /* not an indexed mesh */
1133                 for(i=0; i<num_faces; i++) {
1134                         int vidx = i * 3;
1135                         *dest++ = vidx;
1136                         *dest++ = vidx + 1;
1137                         *dest++ = vidx + 1;
1138                         *dest++ = vidx + 2;
1139                         *dest++ = vidx + 2;
1140                         *dest++ = vidx;
1141                 }
1142         }
1143
1144         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cm->wire_ibo);
1145         glBufferData(GL_ELEMENT_ARRAY_BUFFER, num_faces * 6 * sizeof(unsigned int),
1146                         wire_idxarr, GL_STATIC_DRAW);
1147         free(wire_idxarr);
1148         cm->wire_ibo_valid = 1;
1149         glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
1150 }
1151
1152 static void calc_aabb(struct cmesh *cm)
1153 {
1154         int i, j;
1155
1156         if(!cmesh_attrib_ro(cm, CMESH_ATTR_VERTEX)) {
1157                 return;
1158         }
1159
1160         cgm_vcons(&cm->aabb_min, FLT_MAX, FLT_MAX, FLT_MAX);
1161         cgm_vcons(&cm->aabb_max, -FLT_MAX, -FLT_MAX, -FLT_MAX);
1162
1163         for(i=0; i<cm->nverts; i++) {
1164                 const float *v = cmesh_attrib_at_ro(cm, CMESH_ATTR_VERTEX, i);
1165                 for(j=0; j<3; j++) {
1166                         if(v[j] < (&cm->aabb_min.x)[j]) {
1167                                 (&cm->aabb_min.x)[j] = v[j];
1168                         }
1169                         if(v[j] > (&cm->aabb_max.x)[j]) {
1170                                 (&cm->aabb_max.x)[j] = v[j];
1171                         }
1172                 }
1173         }
1174         cm->aabb_valid = 1;
1175 }
1176
1177 void cmesh_aabbox(struct cmesh *cm, cgm_vec3 *vmin, cgm_vec3 *vmax)
1178 {
1179         if(!cm->aabb_valid) {
1180                 calc_aabb(cm);
1181         }
1182         *vmin = cm->aabb_min;
1183         *vmax = cm->aabb_max;
1184 }
1185
1186 static void calc_bsph(struct cmesh *cm)
1187 {
1188         int i;
1189         float s, dist_sq;
1190
1191         if(!cmesh_attrib_ro(cm, CMESH_ATTR_VERTEX)) {
1192                 return;
1193         }
1194
1195         cgm_vcons(&cm->bsph_center, 0, 0, 0);
1196
1197         /* first find the center */
1198         for(i=0; i<cm->nverts; i++) {
1199                 const float *v = cmesh_attrib_at_ro(cm, CMESH_ATTR_VERTEX, i);
1200                 cm->bsph_center.x += v[0];
1201                 cm->bsph_center.y += v[1];
1202                 cm->bsph_center.z += v[2];
1203         }
1204         s = 1.0f / (float)cm->nverts;
1205         cm->bsph_center.x *= s;
1206         cm->bsph_center.y *= s;
1207         cm->bsph_center.z *= s;
1208
1209         cm->bsph_radius = 0.0f;
1210         for(i=0; i<cm->nverts; i++) {
1211                 const cgm_vec3 *v = (const cgm_vec3*)cmesh_attrib_at_ro(cm, CMESH_ATTR_VERTEX, i);
1212                 if((dist_sq = cgm_vdist_sq(v, &cm->bsph_center)) > cm->bsph_radius) {
1213                         cm->bsph_radius = dist_sq;
1214                 }
1215         }
1216         cm->bsph_radius = sqrt(cm->bsph_radius);
1217         cm->bsph_valid = 1;
1218 }
1219
1220 float cmesh_bsphere(struct cmesh *cm, cgm_vec3 *center, float *rad)
1221 {
1222         if(!cm->bsph_valid) {
1223                 calc_bsph(cm);
1224         }
1225         *center = cm->bsph_center;
1226         *rad = cm->bsph_radius;
1227         return cm->bsph_radius;
1228 }
1229
1230 /* TODO */
1231 void cmesh_texcoord_apply_xform(struct cmesh *cm, float *xform);
1232 void cmesh_texcoord_gen_plane(struct cmesh *cm, cgm_vec3 *norm, cgm_vec3 *tang);
1233 void cmesh_texcoord_gen_box(struct cmesh *cm);
1234 void cmesh_texcoord_gen_cylinder(struct cmesh *cm);
1235
1236 int cmesh_dump(struct cmesh *cm, const char *fname)
1237 {
1238         FILE *fp = fopen(fname, "wb");
1239         if(fp) {
1240                 int res = cmesh_dump_file(cm, fp);
1241                 fclose(fp);
1242                 return res;
1243         }
1244         return -1;
1245 }
1246
1247 int cmesh_dump_file(struct cmesh *cm, FILE *fp)
1248 {
1249         static const char *label[] = { "pos", "nor", "tan", "tex", "col", "bw", "bid", "tex2" };
1250         static const char *elemfmt[] = { 0, " %s(%g)", " %s(%g, %g)", " %s(%g, %g, %g)", " %s(%g, %g, %g, %g)", 0 };
1251         int i, j;
1252
1253         if(!cmesh_has_attrib(cm, CMESH_ATTR_VERTEX)) {
1254                 return -1;
1255         }
1256
1257         fprintf(fp, "VERTEX ATTRIBUTES\n");
1258
1259         for(i=0; i<cm->nverts; i++) {
1260                 fprintf(fp, "%5u:", i);
1261                 for(j=0; j<CMESH_NUM_ATTR; j++) {
1262                         if(cmesh_has_attrib(cm, j)) {
1263                                 const float *v = cmesh_attrib_at_ro(cm, j, i);
1264                                 int nelem = cm->vattr[j].nelem;
1265                                 fprintf(fp, elemfmt[nelem], label[j], v[0], nelem > 1 ? v[1] : 0.0f,
1266                                                 nelem > 2 ? v[2] : 0.0f, nelem > 3 ? v[3] : 0.0f);
1267                         }
1268                 }
1269                 fputc('\n', fp);
1270         }
1271
1272         if(cmesh_indexed(cm)) {
1273                 const unsigned int *idx = cmesh_index_ro(cm);
1274                 int numidx = cmesh_index_count(cm);
1275                 int numtri = numidx / 3;
1276                 assert(numidx % 3 == 0);
1277
1278                 fprintf(fp, "FACES\n");
1279
1280                 for(i=0; i<numtri; i++) {
1281                         fprintf(fp, "%5d: %d %d %d\n", i, idx[0], idx[1], idx[2]);
1282                         idx += 3;
1283                 }
1284         }
1285         return 0;
1286 }
1287
1288 int cmesh_dump_obj(struct cmesh *cm, const char *fname)
1289 {
1290         FILE *fp = fopen(fname, "wb");
1291         if(fp) {
1292                 int res = cmesh_dump_obj_file(cm, fp, 0);
1293                 fclose(fp);
1294                 return res;
1295         }
1296         return -1;
1297 }
1298
1299 int cmesh_dump_obj_file(struct cmesh *cm, FILE *fp, int voffs)
1300 {
1301         int i, j, num, nelem;
1302
1303         if(!cmesh_has_attrib(cm, CMESH_ATTR_VERTEX)) {
1304                 return -1;
1305         }
1306
1307
1308         nelem = cm->vattr[CMESH_ATTR_VERTEX].nelem;
1309         if((num = dynarr_size(cm->vattr[CMESH_ATTR_VERTEX].data)) != cm->nverts * nelem) {
1310                 warning_log("vertex array size (%d) != nverts (%d)\n", num, cm->nverts);
1311         }
1312         for(i=0; i<cm->nverts; i++) {
1313                 const float *v = cmesh_attrib_at_ro(cm, CMESH_ATTR_VERTEX, i);
1314                 fprintf(fp, "v %f %f %f\n", v[0], nelem > 1 ? v[1] : 0.0f, nelem > 2 ? v[2] : 0.0f);
1315         }
1316
1317         if(cmesh_has_attrib(cm, CMESH_ATTR_NORMAL)) {
1318                 nelem = cm->vattr[CMESH_ATTR_NORMAL].nelem;
1319                 if((num = dynarr_size(cm->vattr[CMESH_ATTR_NORMAL].data)) != cm->nverts * nelem) {
1320                         warning_log("normal array size (%d) != nverts (%d)\n", num, cm->nverts);
1321                 }
1322                 for(i=0; i<cm->nverts; i++) {
1323                         const float *v = cmesh_attrib_at_ro(cm, CMESH_ATTR_NORMAL, i);
1324                         fprintf(fp, "vn %f %f %f\n", v[0], nelem > 1 ? v[1] : 0.0f, nelem > 2 ? v[2] : 0.0f);
1325                 }
1326         }
1327
1328         if(cmesh_has_attrib(cm, CMESH_ATTR_TEXCOORD)) {
1329                 nelem = cm->vattr[CMESH_ATTR_TEXCOORD].nelem;
1330                 if((num = dynarr_size(cm->vattr[CMESH_ATTR_TEXCOORD].data)) != cm->nverts * nelem) {
1331                         warning_log("texcoord array size (%d) != nverts (%d)\n", num, cm->nverts);
1332                 }
1333                 for(i=0; i<cm->nverts; i++) {
1334                         const float *v = cmesh_attrib_at_ro(cm, CMESH_ATTR_TEXCOORD, i);
1335                         fprintf(fp, "vt %f %f\n", v[0], nelem > 1 ? v[1] : 0.0f);
1336                 }
1337         }
1338
1339         if(cmesh_indexed(cm)) {
1340                 const unsigned int *idxptr = cmesh_index_ro(cm);
1341                 int numidx = cmesh_index_count(cm);
1342                 int numtri = numidx / 3;
1343                 assert(numidx % 3 == 0);
1344
1345                 for(i=0; i<numtri; i++) {
1346                         fputc('f', fp);
1347                         for(j=0; j<3; j++) {
1348                                 unsigned int idx = *idxptr++ + 1 + voffs;
1349                                 fprintf(fp, " %u/%u/%u", idx, idx, idx);
1350                         }
1351                         fputc('\n', fp);
1352                 }
1353         } else {
1354                 int numtri = cm->nverts / 3;
1355                 unsigned int idx = 1 + voffs;
1356                 for(i=0; i<numtri; i++) {
1357                         fputc('f', fp);
1358                         for(j=0; j<3; j++) {
1359                                 fprintf(fp, " %u/%u/%u", idx, idx, idx);
1360                                 ++idx;
1361                         }
1362                         fputc('\n', fp);
1363                 }
1364         }
1365         return 0;
1366 }