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