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