1 struct obj;
2 
3 obj *loadingobj = 0;
4 
5 string objdir;
6 
7 struct obj : vertmodel
8 {
objobj9     obj(const char *name) : vertmodel(name) {}
10 
typeobj11     int type() const { return MDL_OBJ; }
12 
13     struct objmeshgroup : vertmeshgroup
14     {
parsevertobj::objmeshgroup15         void parsevert(char *s, vector<vec> &out)
16         {
17             vec &v = out.add(vec(0, 0, 0));
18             while(isalpha(*s)) s++;
19             loopi(3)
20             {
21                 v[i] = strtod(s, &s);
22                 while(isspace(*s)) s++;
23                 if(!*s) break;
24             }
25         }
26 
loadobj::objmeshgroup27         bool load(char *filename)
28         {
29             int len = strlen(filename);
30             if(len < 4 || strcasecmp(&filename[len-4], ".obj")) return false;
31 
32             stream *file = openfile(filename, "rb");
33             if(!file) return false;
34 
35             name = newstring(filename);
36 
37             numframes = 1;
38 
39             vector<vec> attrib[3];
40             char buf[512];
41 
42             hashtable<ivec, int> verthash;
43             vector<vert> verts;
44             vector<tcvert> tcverts;
45             vector<tri> tris;
46 
47             #define STARTMESH do { \
48                 vertmesh &m = *new vertmesh; \
49                 m.group = this; \
50                 m.name = meshname[0] ? newstring(meshname) : NULL; \
51                 meshes.add(&m); \
52                 curmesh = &m; \
53                 verthash.clear(); \
54                 verts.setsizenodelete(0); \
55                 tcverts.setsizenodelete(0); \
56                 tris.setsizenodelete(0); \
57             } while(0)
58 
59             #define FLUSHMESH do { \
60                 curmesh->numverts = verts.length(); \
61                 if(verts.length()) \
62                 { \
63                     curmesh->verts = new vert[verts.length()]; \
64                     memcpy(curmesh->verts, verts.getbuf(), verts.length()*sizeof(vert)); \
65                     curmesh->tcverts = new tcvert[verts.length()]; \
66                     memcpy(curmesh->tcverts, tcverts.getbuf(), tcverts.length()*sizeof(tcvert)); \
67                 } \
68                 curmesh->numtris = tris.length(); \
69                 if(tris.length()) \
70                 { \
71                     curmesh->tris = new tri[tris.length()]; \
72                     memcpy(curmesh->tris, tris.getbuf(), tris.length()*sizeof(tri)); \
73                 } \
74                 if(attrib[2].empty()) curmesh->buildnorms(); \
75             } while(0)
76 
77             string meshname = "";
78             vertmesh *curmesh = NULL;
79             while(file->getline(buf, sizeof(buf)))
80             {
81                 char *c = buf;
82                 while(isspace(*c)) c++;
83                 switch(*c)
84                 {
85                     case '#': continue;
86                     case 'v':
87                         if(isspace(c[1])) parsevert(c, attrib[0]);
88                         else if(c[1]=='t') parsevert(c, attrib[1]);
89                         else if(c[1]=='n') parsevert(c, attrib[2]);
90                         break;
91                     case 'g':
92                     {
93                         while(isalpha(*c)) c++;
94                         while(isspace(*c)) c++;
95                         char *name = c;
96                         size_t namelen = strlen(name);
97                         while(namelen > 0 && isspace(name[namelen-1])) namelen--;
98                         copystring(meshname, name, min(namelen+1, sizeof(meshname)));
99 
100                         if(curmesh) FLUSHMESH;
101                         curmesh = NULL;
102                         break;
103                     }
104                     case 'f':
105                     {
106                         if(!curmesh) STARTMESH;
107                         int v0 = -1, v1 = -1;
108                         while(isalpha(*c)) c++;
109                         for(;;)
110                         {
111                             while(isspace(*c)) c++;
112                             if(!*c) break;
113                             ivec vkey(-1, -1, -1);
114                             loopi(3)
115                             {
116                                 vkey[i] = strtol(c, &c, 10);
117                                 if(vkey[i] < 0) vkey[i] = attrib[i].length() + vkey[i];
118                                 else vkey[i]--;
119                                 if(!attrib[i].inrange(vkey[i])) vkey[i] = -1;
120                                 if(*c!='/') break;
121                                 c++;
122                             }
123                             int *index = verthash.access(vkey);
124                             if(!index)
125                             {
126                                 index = &verthash[vkey];
127                                 *index = verts.length();
128                                 vert &v = verts.add();
129                                 v.pos = vkey.x < 0 ? vec(0, 0, 0) : attrib[0][vkey.x];
130                                 v.pos = vec(v.pos.z, -v.pos.x, v.pos.y);
131                                 v.norm = vkey.z < 0 ? vec(0, 0, 0) : attrib[2][vkey.z];
132                                 v.norm = vec(v.norm.z, -v.norm.x, v.norm.y);
133                                 tcvert &tcv = tcverts.add();
134                                 if(vkey.y < 0) tcv.u = tcv.v = 0;
135                                 else { tcv.u = attrib[1][vkey.y].x; tcv.v = 1-attrib[1][vkey.y].y; }
136                             }
137                             if(v0 < 0) v0 = *index;
138                             else if(v1 < 0) v1 = *index;
139                             else
140                             {
141                                 tri &t = tris.add();
142                                 t.vert[0] = ushort(*index);
143                                 t.vert[1] = ushort(v1);
144                                 t.vert[2] = ushort(v0);
145                                 v1 = *index;
146                             }
147                         }
148                         break;
149                     }
150                 }
151             }
152 
153             if(curmesh) FLUSHMESH;
154 
155             delete file;
156 
157             return true;
158         }
159     };
160 
loadmeshesobj161     meshgroup *loadmeshes(char *name, va_list args)
162     {
163         objmeshgroup *group = new objmeshgroup;
164         if(!group->load(name)) { delete group; return NULL; }
165         return group;
166     }
167 
loaddefaultpartsobj168     bool loaddefaultparts()
169     {
170         part &mdl = *new part;
171         parts.add(&mdl);
172         mdl.model = this;
173         mdl.index = 0;
174         const char *pname = parentdir(loadname);
175         defformatstring(name1)("models/%s/tris.obj", loadname);
176         mdl.meshes = sharemeshes(path(name1));
177         if(!mdl.meshes)
178         {
179             defformatstring(name2)("models/%s/tris.obj", pname);    // try obj in parent folder (vert sharing)
180             mdl.meshes = sharemeshes(path(name2));
181             if(!mdl.meshes) return false;
182         }
183         Texture *tex, *masks;
184         loadskin(loadname, pname, tex, masks);
185         mdl.initskins(tex, masks);
186         if(tex==notexture) conoutf("\frcould not load model skin for %s", name1);
187         return true;
188     }
189 
loadobj190     bool load()
191     {
192         if(loaded) return true;
193         formatstring(objdir)("models/%s", loadname);
194         defformatstring(cfgname)("models/%s/obj.cfg", loadname);
195 
196         loadingobj = this;
197         persistidents = false;
198         if(execfile(cfgname) && parts.length()) // configured obj, will call the obj* commands below
199         {
200             persistidents = true;
201             loadingobj = NULL;
202             loopv(parts) if(!parts[i]->meshes) return false;
203         }
204         else // obj without configuration, try default tris and skin
205         {
206             persistidents = true;
207             loadingobj = NULL;
208             if(!loaddefaultparts()) return false;
209         }
210         scale /= 4;
211         translate.y = -translate.y;
212         parts[0]->translate = translate;
213         loopv(parts) parts[i]->meshes->shared++;
214         preloadshaders();
215         return loaded = true;
216     }
217 };
218 
objload(char * model)219 void objload(char *model)
220 {
221     if(!loadingobj) { conoutf("\frnot loading an obj"); return; }
222     defformatstring(filename)("%s/%s", objdir, model);
223     obj::part &mdl = *new obj::part;
224     loadingobj->parts.add(&mdl);
225     mdl.model = loadingobj;
226     mdl.index = loadingobj->parts.length()-1;
227     if(mdl.index) mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
228     mdl.meshes = loadingobj->sharemeshes(path(filename));
229     if(!mdl.meshes) conoutf("\frcould not load %s", filename); // ignore failure
230     else mdl.initskins();
231 }
232 
objpitch(float * pitchscale,float * pitchoffset,float * pitchmin,float * pitchmax)233 void objpitch(float *pitchscale, float *pitchoffset, float *pitchmin, float *pitchmax)
234 {
235     if(!loadingobj || loadingobj->parts.empty()) { conoutf("\frnot loading an obj"); return; }
236     obj::part &mdl = *loadingobj->parts.last();
237 
238     mdl.pitchscale = *pitchscale;
239     mdl.pitchoffset = *pitchoffset;
240     if(*pitchmin || *pitchmax)
241     {
242         mdl.pitchmin = *pitchmin;
243         mdl.pitchmax = *pitchmax;
244     }
245     else
246     {
247         mdl.pitchmin = -360*mdl.pitchscale;
248         mdl.pitchmax = 360*mdl.pitchscale;
249     }
250 }
251 
252 #define loopobjmeshes(meshname, m, body) \
253     if(!loadingobj || loadingobj->parts.empty()) { conoutf("\frnot loading an obj"); return; } \
254     obj::part &mdl = *loadingobj->parts.last(); \
255     if(!mdl.meshes) return; \
256     loopv(mdl.meshes->meshes) \
257     { \
258         obj::vertmesh &m = *(obj::vertmesh *)mdl.meshes->meshes[i]; \
259         if(!strcmp(meshname, "*") || (m.name && !strcmp(m.name, meshname))) \
260         { \
261             body; \
262         } \
263     }
264 
265 #define loopobjskins(meshname, s, body) loopobjmeshes(meshname, m, { obj::skin &s = mdl.skins[i]; body; })
266 
objskin(char * meshname,char * tex,char * masks,float * envmapmax,float * envmapmin)267 void objskin(char *meshname, char *tex, char *masks, float *envmapmax, float *envmapmin)
268 {
269     loopobjskins(meshname, s,
270         s.tex = textureload(makerelpath(objdir, tex), 0, true, false);
271         if(*masks)
272         {
273             s.masks = textureload(makerelpath(objdir, masks, NULL, "<ffmask:25>"), 0, true, false);
274             s.envmapmax = *envmapmax;
275             s.envmapmin = *envmapmin;
276         }
277     );
278 }
279 
objspec(char * meshname,int * percent)280 void objspec(char *meshname, int *percent)
281 {
282     float spec = 1.0f;
283     if(*percent>0) spec = *percent/100.0f;
284     else if(*percent<0) spec = 0.0f;
285     loopobjskins(meshname, s, s.spec = spec);
286 }
287 
objambient(char * meshname,int * percent)288 void objambient(char *meshname, int *percent)
289 {
290     float ambient = 0.3f;
291     if(*percent>0) ambient = *percent/100.0f;
292     else if(*percent<0) ambient = 0.0f;
293     loopobjskins(meshname, s, s.ambient = ambient);
294 }
295 
objglow(char * meshname,int * percent)296 void objglow(char *meshname, int *percent)
297 {
298     float glow = 3.0f;
299     if(*percent>0) glow = *percent/100.0f;
300     else if(*percent<0) glow = 0.0f;
301     loopobjskins(meshname, s, s.glow = glow);
302 }
303 
objglare(char * meshname,float * specglare,float * glowglare)304 void objglare(char *meshname, float *specglare, float *glowglare)
305 {
306     loopobjskins(meshname, s, { s.specglare = *specglare; s.glowglare = *glowglare; });
307 }
308 
objalphatest(char * meshname,float * cutoff)309 void objalphatest(char *meshname, float *cutoff)
310 {
311     loopobjskins(meshname, s, s.alphatest = max(0.0f, min(1.0f, *cutoff)));
312 }
313 
objalphablend(char * meshname,int * blend)314 void objalphablend(char *meshname, int *blend)
315 {
316     loopobjskins(meshname, s, s.alphablend = *blend!=0);
317 }
318 
objcullface(char * meshname,int * cullface)319 void objcullface(char *meshname, int *cullface)
320 {
321     loopobjskins(meshname, s, s.cullface = *cullface!=0);
322 }
323 
objenvmap(char * meshname,char * envmap)324 void objenvmap(char *meshname, char *envmap)
325 {
326     Texture *tex = cubemapload(envmap);
327     loopobjskins(meshname, s, s.envmap = tex);
328 }
329 
objbumpmap(char * meshname,char * normalmap,char * skin)330 void objbumpmap(char *meshname, char *normalmap, char *skin)
331 {
332     Texture *normalmaptex = NULL, *skintex = NULL;
333     normalmaptex = textureload(makerelpath(objdir, normalmap, "<noff>"), 0, true, false);
334     if(skin[0]) skintex = textureload(makerelpath(objdir, skin, "<noff>"), 0, true, false);
335     loopobjskins(meshname, s, { s.unlittex = skintex; s.normalmap = normalmaptex; m.calctangents(); });
336 }
337 
objfullbright(char * meshname,float * fullbright)338 void objfullbright(char *meshname, float *fullbright)
339 {
340     loopobjskins(meshname, s, s.fullbright = *fullbright);
341 }
342 
objshader(char * meshname,char * shader)343 void objshader(char *meshname, char *shader)
344 {
345     loopobjskins(meshname, s, s.shader = lookupshaderbyname(shader));
346 }
347 
objscroll(char * meshname,float * scrollu,float * scrollv)348 void objscroll(char *meshname, float *scrollu, float *scrollv)
349 {
350     loopobjskins(meshname, s, { s.scrollu = *scrollu; s.scrollv = *scrollv; });
351 }
352 
objnoclip(char * meshname,int * noclip)353 void objnoclip(char *meshname, int *noclip)
354 {
355     loopobjmeshes(meshname, m, m.noclip = *noclip!=0);
356 }
357 
358 COMMAND(objload, "s");
359 COMMAND(objpitch, "ffff");
360 COMMAND(objskin, "sssff");
361 COMMAND(objspec, "si");
362 COMMAND(objambient, "si");
363 COMMAND(objglow, "si");
364 COMMAND(objglare, "sff");
365 COMMAND(objalphatest, "sf");
366 COMMAND(objalphablend, "si");
367 COMMAND(objcullface, "si");
368 COMMAND(objenvmap, "ss");
369 COMMAND(objbumpmap, "sss");
370 COMMAND(objfullbright, "sf");
371 COMMAND(objshader, "ss");
372 COMMAND(objscroll, "sff");
373 COMMAND(objnoclip, "si");
374 
375