1 struct vertmodel : animmodel
2 {
3     struct vert { vec norm, pos; };
4     struct vvertff { vec pos; float u, v; };
5     struct vvert : vvertff { vec norm; };
6     struct vvertbump : vvert { vec tangent; float bitangent; };
7     struct tcvert { float u, v; };
8     struct bumpvert { vec tangent; float bitangent; };
9     struct tri { ushort vert[3]; };
10 
11     struct vbocacheentry
12     {
13         uchar *vdata;
14         GLuint vbuf;
15         animstate as;
16         int millis;
17 
vbocacheentryvertmodel::vbocacheentry18         vbocacheentry() : vdata(NULL), vbuf(0) { as.cur.fr1 = as.prev.fr1 = -1; }
19     };
20 
21     struct vertmesh : mesh
22     {
23         vert *verts;
24         tcvert *tcverts;
25         bumpvert *bumpverts;
26         tri *tris;
27         int numverts, numtris;
28 
29         int voffset, eoffset, elen;
30         ushort minvert, maxvert;
31 
vertmeshvertmodel::vertmesh32         vertmesh() : verts(0), tcverts(0), bumpverts(0), tris(0)
33         {
34         }
35 
~vertmeshvertmodel::vertmesh36         virtual ~vertmesh()
37         {
38             DELETEA(verts);
39             DELETEA(tcverts);
40             DELETEA(bumpverts);
41             DELETEA(tris);
42         }
43 
44         void buildnorms(bool areaweight = true)
45         {
46             loopk(((vertmeshgroup *)group)->numframes)
47             {
48                 vert *fverts = &verts[k*numverts];
49                 loopi(numverts) fverts[i].norm = vec(0, 0, 0);
loopivertmodel::vertmesh50                 loopi(numtris)
51                 {
52                     tri &t = tris[i];
53                     vert &v1 = fverts[t.vert[0]], &v2 = fverts[t.vert[1]], &v3 = fverts[t.vert[2]];
54                     vec norm;
55                     norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos));
56                     if(!areaweight) norm.normalize();
57                     v1.norm.add(norm);
58                     v2.norm.add(norm);
59                     v3.norm.add(norm);
60                 }
61                 loopi(numverts) fverts[i].norm.normalize();
62             }
63         }
64 
calctangentsvertmodel::vertmesh65         void calctangents()
66         {
67             if(bumpverts) return;
68             vec *tangent = new vec[2*numverts], *bitangent = tangent+numverts;
69             memset(tangent, 0, 2*numverts*sizeof(vec));
70             bumpverts = new bumpvert[((vertmeshgroup *)group)->numframes*numverts];
71             loopk(((vertmeshgroup *)group)->numframes)
72             {
73                 vert *fverts = &verts[k*numverts];
74                 loopi(numtris)
75                 {
76                     const tri &t = tris[i];
77                     const tcvert &tc0 = tcverts[t.vert[0]],
78                                  &tc1 = tcverts[t.vert[1]],
79                                  &tc2 = tcverts[t.vert[2]];
80 
81                     vec v0(fverts[t.vert[0]].pos),
82                         e1(fverts[t.vert[1]].pos),
83                         e2(fverts[t.vert[2]].pos);
84                     e1.sub(v0);
85                     e2.sub(v0);
86 
87                     float u1 = tc1.u - tc0.u, v1 = tc1.v - tc0.v,
88                           u2 = tc2.u - tc0.u, v2 = tc2.v - tc0.v,
89                           scale = u1*v2 - u2*v1;
90                     if(scale!=0) scale = 1.0f / scale;
91                     vec u(e1), v(e2);
92                     u.mul(v2).sub(vec(e2).mul(v1)).mul(scale);
93                     v.mul(u1).sub(vec(e1).mul(u2)).mul(scale);
94 
95                     loopj(3)
96                     {
97                         tangent[t.vert[j]].add(u);
98                         bitangent[t.vert[j]].add(v);
99                     }
100                 }
101                 bumpvert *fbumpverts = &bumpverts[k*numverts];
102                 loopi(numverts)
103                 {
104                     const vec &n = fverts[i].norm,
105                               &t = tangent[i],
106                               &bt = bitangent[i];
107                     bumpvert &bv = fbumpverts[i];
108                     (bv.tangent = t).sub(vec(n).mul(n.dot(t))).normalize();
109                     bv.bitangent = vec().cross(n, t).dot(bt) < 0 ? -1 : 1;
110                 }
111             }
112             delete[] tangent;
113         }
114 
calcbbvertmodel::vertmesh115         void calcbb(int frame, vec &bbmin, vec &bbmax, const matrix3x4 &m)
116         {
117             vert *fverts = &verts[frame*numverts];
118             loopj(numverts)
119             {
120                 vec v = m.transform(fverts[j].pos);
121                 loopi(3)
122                 {
123                     bbmin[i] = min(bbmin[i], v[i]);
124                     bbmax[i] = max(bbmax[i], v[i]);
125                 }
126             }
127         }
128 
gentrisvertmodel::vertmesh129         void gentris(int frame, Texture *tex, vector<BIH::tri> *out, const matrix3x4 &m)
130         {
131             vert *fverts = &verts[frame*numverts];
132             loopj(numtris)
133             {
134                 BIH::tri &t = out[noclip ? 1 : 0].add();
135                 t.tex = tex->bpp==4 ? tex : NULL;
136                 t.a = m.transform(fverts[tris[j].vert[0]].pos);
137                 t.b = m.transform(fverts[tris[j].vert[1]].pos);
138                 t.c = m.transform(fverts[tris[j].vert[2]].pos);
139                 tcvert &av = tcverts[tris[j].vert[0]],
140                        &bv = tcverts[tris[j].vert[1]],
141                        &cv = tcverts[tris[j].vert[2]];
142                 t.tc[0] = av.u;
143                 t.tc[1] = av.v;
144                 t.tc[2] = bv.u;
145                 t.tc[3] = bv.v;
146                 t.tc[4] = cv.u;
147                 t.tc[5] = cv.v;
148             }
149         }
150 
comparevertvertmodel::vertmesh151         static inline bool comparevert(vvertff &w, int j, tcvert &tc, vert &v)
152         {
153             return tc.u==w.u && tc.v==w.v && v.pos==w.pos;
154         }
155 
comparevertvertmodel::vertmesh156         static inline bool comparevert(vvert &w, int j, tcvert &tc, vert &v)
157         {
158             return tc.u==w.u && tc.v==w.v && v.pos==w.pos && v.norm==w.norm;
159         }
160 
comparevertvertmodel::vertmesh161         inline bool comparevert(vvertbump &w, int j, tcvert &tc, vert &v)
162         {
163             return tc.u==w.u && tc.v==w.v && v.pos==w.pos && v.norm==w.norm && (!bumpverts || (bumpverts[j].tangent==w.tangent && bumpverts[j].bitangent==w.bitangent));
164         }
165 
assignvertvertmodel::vertmesh166         static inline void assignvert(vvertff &vv, int j, tcvert &tc, vert &v)
167         {
168             vv.pos = v.pos;
169             vv.u = tc.u;
170             vv.v = tc.v;
171         }
172 
assignvertvertmodel::vertmesh173         static inline void assignvert(vvert &vv, int j, tcvert &tc, vert &v)
174         {
175             vv.pos = v.pos;
176             vv.norm = v.norm;
177             vv.u = tc.u;
178             vv.v = tc.v;
179         }
180 
assignvertvertmodel::vertmesh181         inline void assignvert(vvertbump &vv, int j, tcvert &tc, vert &v)
182         {
183             vv.pos = v.pos;
184             vv.norm = v.norm;
185             vv.u = tc.u;
186             vv.v = tc.v;
187             if(bumpverts)
188             {
189                 vv.tangent = bumpverts[j].tangent;
190                 vv.bitangent = bumpverts[j].bitangent;
191             }
192         }
193 
194         template<class T>
genvbovertmodel::vertmesh195         int genvbo(vector<ushort> &idxs, int offset, vector<T> &vverts)
196         {
197             voffset = offset;
198             eoffset = idxs.length();
199             minvert = 0xFFFF;
200             loopi(numtris)
201             {
202                 tri &t = tris[i];
203                 loopj(3)
204                 {
205                     int index = t.vert[j];
206                     tcvert &tc = tcverts[index];
207                     vert &v = verts[index];
208                     loopvk(vverts)
209                     {
210                         if(comparevert(vverts[k], index, tc, v)) { minvert = min(minvert, (ushort)k); idxs.add((ushort)k); goto found; }
211                     }
212                     idxs.add(vverts.length());
213                     assignvert(vverts.add(), index, tc, v);
214                 found:;
215                 }
216             }
217             minvert = min(minvert, ushort(voffset));
218             maxvert = max(minvert, ushort(vverts.length()-1));
219             elen = idxs.length()-eoffset;
220             return vverts.length()-voffset;
221         }
222 
genvbovertmodel::vertmesh223         int genvbo(vector<ushort> &idxs, int offset)
224         {
225             voffset = offset;
226             eoffset = idxs.length();
227             loopi(numtris)
228             {
229                 tri &t = tris[i];
230                 loopj(3) idxs.add(voffset+t.vert[j]);
231             }
232             minvert = voffset;
233             maxvert = voffset + numverts-1;
234             elen = idxs.length()-eoffset;
235             return numverts;
236         }
237 
filltcvertmodel::vertmesh238         void filltc(uchar *vdata, size_t stride)
239         {
240             vdata = (uchar *)&((vvertff *)&vdata[voffset*stride])->u;
241             loopi(numverts)
242             {
243                 *(tcvert *)vdata = tcverts[i];
244                 vdata += stride;
245             }
246         }
247 
interpvertsvertmodel::vertmesh248         void interpverts(const animstate &as, bool norms, bool tangents, void *vdata, skin &s)
249         {
250             vert *vert1 = &verts[as.cur.fr1 * numverts],
251                  *vert2 = &verts[as.cur.fr2 * numverts],
252                  *pvert1 = as.interp<1 ? &verts[as.prev.fr1 * numverts] : NULL,
253                  *pvert2 = as.interp<1 ? &verts[as.prev.fr2 * numverts] : NULL;
254             #define ip(p1, p2, t)   (p1+t*(p2-p1))
255             #define ip_v(p, c, t)   ip(p##1[i].c, p##2[i].c, t)
256             #define ip_v_ai(c)      ip(ip_v(pvert, c, as.prev.t), ip_v(vert, c, as.cur.t), as.interp)
257             #define ip_pos          vec(ip_v(vert, pos.x, as.cur.t), ip_v(vert, pos.y, as.cur.t), ip_v(vert, pos.z, as.cur.t))
258             #define ip_pos_ai       vec(ip_v_ai(pos.x), ip_v_ai(pos.y), ip_v_ai(pos.z))
259             #define ip_norm         vec(ip_v(vert, norm.x, as.cur.t), ip_v(vert, norm.y, as.cur.t), ip_v(vert, norm.z, as.cur.t))
260             #define ip_norm_ai      vec(ip_v_ai(norm.x), ip_v_ai(norm.y), ip_v_ai(norm.z))
261             #define ip_b_ai(c)      ip(ip_v(bpvert, c, as.prev.t), ip_v(bvert, c, as.cur.t), as.interp)
262             #define ip_tangent      vec(ip_v(bvert, tangent.x, as.cur.t), ip_v(bvert, tangent.y, as.cur.t), ip_v(bvert, tangent.z, as.cur.t))
263             #define ip_tangent_ai   vec(ip_b_ai(tangent.x), ip_b_ai(tangent.y), ip_b_ai(tangent.z))
264             #define iploop(type, body) \
265                 loopi(numverts) \
266                 { \
267                     type &v = ((type *)vdata)[i]; \
268                     body; \
269                 }
270             if(tangents)
271             {
272                 bumpvert *bvert1 = &bumpverts[as.cur.fr1 * numverts],
273                          *bvert2 = &bumpverts[as.cur.fr2 * numverts],
274                          *bpvert1 = as.interp<1 ? &bumpverts[as.prev.fr1 * numverts] : NULL,
275                          *bpvert2 = as.interp<1 ? &bumpverts[as.prev.fr2 * numverts] : NULL;
276                 if(as.interp<1) iploop(vvertbump, { v.pos = ip_pos_ai; v.norm = ip_norm_ai; v.tangent = ip_tangent_ai; v.bitangent = bvert1[i].bitangent; })
277                 else iploop(vvertbump, { v.pos = ip_pos; v.norm = ip_norm; v.tangent = ip_tangent; v.bitangent = bvert1[i].bitangent; })
278             }
279             else if(norms)
280             {
281                 if(as.interp<1) iploop(vvert, { v.pos = ip_pos_ai; v.norm = ip_norm_ai; })
282                 else iploop(vvert, { v.pos = ip_pos; v.norm = ip_norm; })
283             }
284             else if(as.interp<1) iploop(vvertff, v.pos = ip_pos_ai)
285             else iploop(vvertff, v.pos = ip_pos)
286             #undef iploop
287             #undef ip
288             #undef ip_v
289             #undef ip_v_ai
290             #undef ip_pos
291             #undef ip_pos_ai
292             #undef ip_norm
293             #undef ip_norm_ai
294             #undef ip_b_ai
295             #undef ip_tangent
296             #undef ip_tangent_ai
297         }
298 
rendervertmodel::vertmesh299         void render(const animstate *as, skin &s, vbocacheentry &vc)
300         {
301             s.bind(this, as);
302 
303             if(!(as->anim&ANIM_NOSKIN))
304             {
305                 if(s.multitextured())
306                 {
307                     if(!enablemtc || lastmtcbuf!=lastvbuf)
308                     {
309                         glClientActiveTexture_(GL_TEXTURE1_ARB);
310                         if(!enablemtc) glEnableClientState(GL_TEXTURE_COORD_ARRAY);
311                         if(lastmtcbuf!=lastvbuf)
312                         {
313                             vvertff *vverts = hasVBO ? 0 : (vvertff *)vc.vdata;
314                             glTexCoordPointer(2, GL_FLOAT, ((vertmeshgroup *)group)->vertsize, &vverts->u);
315                         }
316                         glClientActiveTexture_(GL_TEXTURE0_ARB);
317                         lastmtcbuf = lastvbuf;
318                         enablemtc = true;
319                     }
320                 }
321                 else if(enablemtc) disablemtc();
322 
323                 if(s.tangents())
324                 {
325                     if(!enabletangents || lastxbuf!=lastvbuf)
326                     {
327                         if(!enabletangents) glEnableVertexAttribArray_(1);
328                         if(lastxbuf!=lastvbuf)
329                         {
330                             vvertbump *vverts = hasVBO ? 0 : (vvertbump *)vc.vdata;
331                             glVertexAttribPointer_(1, 4, GL_FLOAT, GL_FALSE, ((vertmeshgroup *)group)->vertsize, &vverts->tangent.x);
332                         }
333                         lastxbuf = lastvbuf;
334                         enabletangents = true;
335                     }
336                 }
337                 else if(enabletangents) disabletangents();
338 
339                 if(renderpath==R_FIXEDFUNCTION && (s.scrollu || s.scrollv))
340                 {
341                     glMatrixMode(GL_TEXTURE);
342                     glPushMatrix();
343                     glTranslatef(s.scrollu*lastmillis/1000.0f, s.scrollv*lastmillis/1000.0f, 0);
344 
345                     if(s.multitextured())
346                     {
347                         glActiveTexture_(GL_TEXTURE1_ARB);
348                         glPushMatrix();
349                         glTranslatef(s.scrollu*lastmillis/1000.0f, s.scrollv*lastmillis/1000.0f, 0);
350                     }
351                 }
352             }
353 
354             if(hasDRE) glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, elen, GL_UNSIGNED_SHORT, &((vertmeshgroup *)group)->edata[eoffset]);
355             else glDrawElements(GL_TRIANGLES, elen, GL_UNSIGNED_SHORT, &((vertmeshgroup *)group)->edata[eoffset]);
356             glde++;
357             xtravertsva += numverts;
358 
359             if(renderpath==R_FIXEDFUNCTION && !(as->anim&ANIM_NOSKIN) && (s.scrollu || s.scrollv))
360             {
361                 if(s.multitextured())
362                 {
363                     glPopMatrix();
364                     glActiveTexture_(GL_TEXTURE0_ARB);
365                 }
366 
367                 glPopMatrix();
368                 glMatrixMode(GL_MODELVIEW);
369             }
370 
371             return;
372         }
373     };
374 
375     struct tag
376     {
377         char *name;
378         matrix3x4 transform;
379 
tagvertmodel::tag380         tag() : name(NULL) {}
~tagvertmodel::tag381         ~tag() { DELETEA(name); }
382     };
383 
384     struct vertmeshgroup : meshgroup
385     {
386         int numframes;
387         tag *tags;
388         int numtags;
389 
390         static const int MAXVBOCACHE = 16;
391         vbocacheentry vbocache[MAXVBOCACHE];
392 
393         ushort *edata;
394         GLuint ebuf;
395         bool vnorms, vtangents;
396         int vlen, vertsize;
397         uchar *vdata;
398 
vertmeshgroupvertmodel::vertmeshgroup399         vertmeshgroup() : numframes(0), tags(NULL), numtags(0), edata(NULL), ebuf(0), vdata(NULL)
400         {
401         }
402 
~vertmeshgroupvertmodel::vertmeshgroup403         virtual ~vertmeshgroup()
404         {
405             DELETEA(tags);
406             if(ebuf) glDeleteBuffers_(1, &ebuf);
407             loopi(MAXVBOCACHE)
408             {
409                 DELETEA(vbocache[i].vdata);
410                 if(vbocache[i].vbuf) glDeleteBuffers_(1, &vbocache[i].vbuf);
411             }
412             DELETEA(vdata);
413         }
414 
findtagvertmodel::vertmeshgroup415         int findtag(const char *name)
416         {
417             loopi(numtags) if(!strcmp(tags[i].name, name)) return i;
418             return -1;
419         }
420 
totalframesvertmodel::vertmeshgroup421         int totalframes() const { return numframes; }
422 
concattagtransformvertmodel::vertmeshgroup423         void concattagtransform(part *p, int frame, int i, const matrix3x4 &m, matrix3x4 &n)
424         {
425             n.mul(m, tags[frame*numtags + i].transform);
426             n.translate(m.transformnormal(p->translate).mul(p->model->scale));
427         }
428 
calctagmatrixvertmodel::vertmeshgroup429         void calctagmatrix(part *p, int i, const animstate &as, glmatrixf &matrix)
430         {
431             const matrix3x4 &tag1 = tags[as.cur.fr1*numtags + i].transform,
432                             &tag2 = tags[as.cur.fr2*numtags + i].transform;
433             #define ip(p1, p2, t) (p1+t*(p2-p1))
434             #define ip_ai_tag(c) ip( ip( tag1p.c, tag2p.c, as.prev.t), ip( tag1.c, tag2.c, as.cur.t), as.interp)
435             if(as.interp<1)
436             {
437                 const matrix3x4 &tag1p = tags[as.prev.fr1*numtags + i].transform,
438                                 &tag2p = tags[as.prev.fr2*numtags + i].transform;
439                 loopj(4)
440                 {
441                     matrix[4*j+0] = ip_ai_tag(a[j]);
442                     matrix[4*j+1] = ip_ai_tag(b[j]);
443                     matrix[4*j+2] = ip_ai_tag(c[j]);
444                 }
445             }
446             else loopj(4)
447             {
448                 matrix[4*j+0] = ip(tag1.a[j], tag2.a[j], as.cur.t);
449                 matrix[4*j+1] = ip(tag1.b[j], tag2.b[j], as.cur.t);
450                 matrix[4*j+2] = ip(tag1.c[j], tag2.c[j], as.cur.t);
451             }
452             #undef ip_ai_tag
453             #undef ip
454             matrix[12] = (matrix[12] + p->translate.x) * p->model->scale * sizescale;
455             matrix[13] = (matrix[13] + p->translate.y) * p->model->scale * sizescale;
456             matrix[14] = (matrix[14] + p->translate.z) * p->model->scale * sizescale;
457             matrix[3] = matrix[7] = matrix[11] = 0.0f;
458             matrix[15] = 1.0f;
459         }
460 
genvbovertmodel::vertmeshgroup461         void genvbo(bool norms, bool tangents, vbocacheentry &vc)
462         {
463             if(hasVBO)
464             {
465                 if(!vc.vbuf) glGenBuffers_(1, &vc.vbuf);
466                 if(ebuf) return;
467             }
468             else if(edata)
469             {
470                 #define ALLOCVDATA(vdata) \
471                     do \
472                     { \
473                         DELETEA(vdata); \
474                         vdata = new uchar[vlen*vertsize]; \
475                         loopv(meshes) ((vertmesh *)meshes[i])->filltc(vdata, vertsize); \
476                     } while(0)
477                 if(!vc.vdata) ALLOCVDATA(vc.vdata);
478                 return;
479             }
480 
481             vector<ushort> idxs;
482 
483             vnorms = norms;
484             vtangents = tangents;
485             vertsize = tangents ? sizeof(vvertbump) : (norms ? sizeof(vvert) : sizeof(vvertff));
486             vlen = 0;
487             if(numframes>1)
488             {
489                 loopv(meshes) vlen += ((vertmesh *)meshes[i])->genvbo(idxs, vlen);
490                 DELETEA(vdata);
491                 if(hasVBO) ALLOCVDATA(vdata);
492                 else ALLOCVDATA(vc.vdata);
493             }
494             else
495             {
496                 if(hasVBO) glBindBuffer_(GL_ARRAY_BUFFER_ARB, vc.vbuf);
497                 #define GENVBO(type) \
498                     do \
499                     { \
500                         vector<type> vverts; \
501                         loopv(meshes) vlen += ((vertmesh *)meshes[i])->genvbo(idxs, vlen, vverts); \
502                         if(hasVBO) glBufferData_(GL_ARRAY_BUFFER_ARB, vverts.length()*sizeof(type), vverts.getbuf(), GL_STATIC_DRAW_ARB); \
503                         else \
504                         { \
505                             DELETEA(vc.vdata); \
506                             vc.vdata = new uchar[vverts.length()*sizeof(type)]; \
507                             memcpy(vc.vdata, vverts.getbuf(), vverts.length()*sizeof(type)); \
508                         } \
509                     } while(0)
510                 if(tangents) GENVBO(vvertbump);
511                 else if(norms) GENVBO(vvert);
512                 else GENVBO(vvertff);
513             }
514 
515             if(hasVBO)
516             {
517                 glGenBuffers_(1, &ebuf);
518                 glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER_ARB, ebuf);
519                 glBufferData_(GL_ELEMENT_ARRAY_BUFFER_ARB, idxs.length()*sizeof(ushort), idxs.getbuf(), GL_STATIC_DRAW_ARB);
520             }
521             else
522             {
523                 edata = new ushort[idxs.length()];
524                 memcpy(edata, idxs.getbuf(), idxs.length()*sizeof(ushort));
525             }
526             #undef GENVBO
527             #undef ALLOCVDATA
528         }
529 
bindvbovertmodel::vertmeshgroup530         void bindvbo(const animstate *as, vbocacheentry &vc)
531         {
532             vvert *vverts = hasVBO ? 0 : (vvert *)vc.vdata;
533             if(hasVBO && lastebuf!=ebuf)
534             {
535                 glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER_ARB, ebuf);
536                 lastebuf = ebuf;
537             }
538             if(lastvbuf != (hasVBO ? (void *)(size_t)vc.vbuf : vc.vdata))
539             {
540                 if(hasVBO) glBindBuffer_(GL_ARRAY_BUFFER_ARB, vc.vbuf);
541                 if(!lastvbuf) glEnableClientState(GL_VERTEX_ARRAY);
542                 glVertexPointer(3, GL_FLOAT, vertsize, &vverts->pos);
543             }
544             lastvbuf = hasVBO ? (void *)(size_t)vc.vbuf : vc.vdata;
545             if(as->anim&ANIM_NOSKIN)
546             {
547                 if(enabletc) disabletc();
548                 if(enablenormals) disablenormals();
549             }
550             else
551             {
552                 if(vnorms || vtangents)
553                 {
554                     if(!enablenormals)
555                     {
556                         glEnableClientState(GL_NORMAL_ARRAY);
557                         enablenormals = true;
558                     }
559                     if(lastnbuf!=lastvbuf)
560                     {
561                         glNormalPointer(GL_FLOAT, vertsize, &vverts->norm);
562                         lastnbuf = lastvbuf;
563                     }
564                 }
565                 else if(enablenormals) disablenormals();
566 
567                 if(!enabletc)
568                 {
569                     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
570                     enabletc = true;
571                 }
572                 if(lasttcbuf!=lastvbuf)
573                 {
574                     glTexCoordPointer(2, GL_FLOAT, vertsize, &vverts->u);
575                     lasttcbuf = lastnbuf;
576                 }
577             }
578             if(enablebones) disablebones();
579         }
580 
cleanupvertmodel::vertmeshgroup581         void cleanup()
582         {
583             loopi(MAXVBOCACHE)
584             {
585                 vbocacheentry &c = vbocache[i];
586                 if(c.vbuf) { glDeleteBuffers_(1, &c.vbuf); c.vbuf = 0; }
587                 DELETEA(c.vdata);
588                 c.as.cur.fr1 = -1;
589             }
590             if(hasVBO) { if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } }
591             else DELETEA(vdata);
592         }
593 
rendervertmodel::vertmeshgroup594         void render(const animstate *as, float pitch, const vec &axis, dynent *d, part *p)
595         {
596             if(as->anim&ANIM_NORENDER)
597             {
598                 loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix);
599                 return;
600             }
601 
602             bool norms = false, tangents = false;
603             loopv(p->skins)
604             {
605                 if(p->skins[i].normals()) norms = true;
606                 if(p->skins[i].tangents()) tangents = true;
607             }
608             if(norms!=vnorms || tangents!=vtangents) { cleanup(); disablevbo(); }
609             vbocacheentry *vc = NULL;
610             if(numframes<=1) vc = vbocache;
611             else
612             {
613                 loopi(MAXVBOCACHE)
614                 {
615                     vbocacheentry &c = vbocache[i];
616                     if(hasVBO ? !c.vbuf : !c.vdata) continue;
617                     if(c.as==*as) { vc = &c; break; }
618                 }
619                 if(!vc) loopi(MAXVBOCACHE) { vc = &vbocache[i]; if((hasVBO ? !vc->vbuf : !vc->vdata) || vc->millis < lastmillis) break; }
620             }
621             if(hasVBO ? !vc->vbuf : !vc->vdata) genvbo(norms, tangents, *vc);
622             if(numframes>1)
623             {
624                 if(vc->as!=*as)
625                 {
626                     vc->as = *as;
627                     vc->millis = lastmillis;
628                     loopv(meshes)
629                     {
630                         vertmesh &m = *(vertmesh *)meshes[i];
631                         m.interpverts(*as, norms, tangents, (hasVBO ? vdata : vc->vdata) + m.voffset*vertsize, p->skins[i]);
632                     }
633                     if(hasVBO)
634                     {
635                         glBindBuffer_(GL_ARRAY_BUFFER_ARB, vc->vbuf);
636                         glBufferData_(GL_ARRAY_BUFFER_ARB, vlen*vertsize, vdata, GL_STREAM_DRAW_ARB);
637                     }
638                 }
639                 vc->millis = lastmillis;
640             }
641 
642             bindvbo(as, *vc);
643             loopv(meshes) ((vertmesh *)meshes[i])->render(as, p->skins[i], *vc);
644 
645             loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix);
646         }
647     };
648 
vertmodelvertmodel649     vertmodel(const char *name) : animmodel(name)
650     {
651     }
652 };
653 
654