Line data Source code
1 : /**
2 : * @file obj.cpp
3 : * @brief wavefront model support
4 : *
5 : * Libprimis supports the Wavefront (obj) model format for simple static models.
6 : * This file contains the implementation functions, while the class for the obj
7 : * model type is located in obj.h.
8 : */
9 : #include "../libprimis-headers/cube.h"
10 : #include "../../shared/geomexts.h"
11 : #include "../../shared/glemu.h"
12 : #include "../../shared/glexts.h"
13 : #include "../../shared/stream.h"
14 :
15 : #include <optional>
16 : #include <memory>
17 : #include <format>
18 :
19 : #include "render/rendergl.h"
20 : #include "render/rendermodel.h"
21 : #include "render/renderwindow.h"
22 : #include "render/shader.h"
23 : #include "render/shaderparam.h"
24 : #include "render/texture.h"
25 :
26 : #include "interface/console.h"
27 : #include "interface/control.h"
28 : #include "interface/cs.h"
29 :
30 : #include "world/entities.h"
31 : #include "world/octaworld.h"
32 : #include "world/bih.h"
33 :
34 : #include "model.h"
35 : #include "ragdoll.h"
36 : #include "animmodel.h"
37 : #include "vertmodel.h"
38 :
39 : #include "obj.h"
40 :
41 : #include "interface/console.h"
42 :
43 : vertcommands<obj> obj::objcommands;
44 :
45 0 : obj::obj(std::string name) : vertloader(name)
46 : {
47 0 : }
48 :
49 59 : const char *obj::formatname()
50 : {
51 59 : return "obj";
52 : }
53 :
54 1 : bool obj::cananimate()
55 : {
56 1 : return false;
57 : }
58 :
59 0 : bool obj::flipy() const
60 : {
61 0 : return true;
62 : }
63 :
64 0 : int obj::type() const
65 : {
66 0 : return MDL_OBJ;
67 : }
68 :
69 0 : bool obj::skeletal() const
70 : {
71 0 : return false;
72 : }
73 :
74 0 : bool obj::objmeshgroup::load(const char *filename, float smooth)
75 : {
76 0 : int len = std::strlen(filename);
77 0 : if(len < 4 || strcasecmp(&filename[len-4], ".obj")) //note: strcasecmp is not in std namespace, it is POSIX
78 : {
79 0 : return false;
80 : }
81 0 : stream *file = openfile(filename, "rb");
82 0 : if(!file)
83 : {
84 0 : return false;
85 : }
86 0 : name = filename;
87 0 : numframes = 1;
88 0 : std::array<std::vector<vec>, 3> attrib;
89 : std::array<char, 512> buf;
90 0 : std::unordered_map<ivec, uint> verthash;
91 0 : std::vector<vert> verts;
92 0 : std::vector<tcvert> tcverts;
93 0 : std::vector<tri> tris;
94 :
95 0 : string meshname = "";
96 0 : vertmesh *curmesh = nullptr;
97 0 : while(file->getline(buf.data(), buf.size()))
98 : {
99 0 : char *c = buf.data();
100 0 : while(std::isspace(*c))
101 : {
102 0 : c++;
103 : }
104 0 : switch(*c)
105 : {
106 0 : case '#':
107 : {
108 0 : continue;
109 : }
110 0 : case 'v':
111 : {
112 0 : if(std::isspace(c[1]))
113 : {
114 0 : parsevert(c, attrib[0]);
115 : }
116 0 : else if(c[1]=='t')
117 : {
118 0 : parsevert(c, attrib[1]);
119 : }
120 0 : else if(c[1]=='n')
121 : {
122 0 : parsevert(c, attrib[2]);
123 : }
124 0 : break;
125 : }
126 0 : case 'g':
127 : {
128 0 : while(std::isalpha(*c))
129 : {
130 0 : c++;
131 : }
132 0 : while(std::isspace(*c))
133 : {
134 0 : c++;
135 : }
136 0 : char *name = c;
137 0 : size_t namelen = std::strlen(name);
138 0 : while(namelen > 0 && std::isspace(name[namelen-1]))
139 : {
140 0 : namelen--;
141 : }
142 0 : copystring(meshname, name, std::min(namelen+1, sizeof(meshname)));
143 0 : if(curmesh)
144 : {
145 0 : flushmesh(*curmesh, verts, tcverts, tris, attrib[2], smooth);
146 : }
147 0 : curmesh = nullptr;
148 0 : break;
149 : }
150 0 : case 'f':
151 : {
152 0 : if(!curmesh)
153 : {
154 : //startmesh
155 0 : vertmesh &m = *new vertmesh(meshname[0] ? std::string(meshname) : "", this);
156 0 : meshes.push_back(&m);
157 0 : curmesh = &m;
158 0 : verthash.clear();
159 0 : verts.clear();
160 0 : tcverts.clear();
161 0 : tris.clear();
162 : }
163 0 : std::optional<uint> v0 = std::nullopt,
164 0 : v1 = std::nullopt;
165 0 : while(std::isalpha(*c))
166 : {
167 0 : c++;
168 : }
169 : for(;;)
170 : {
171 0 : while(std::isspace(*c))
172 : {
173 0 : c++;
174 : }
175 0 : if(!*c)
176 : {
177 0 : break;
178 : }
179 0 : ivec vkey(-1, -1, -1);
180 0 : for(int i = 0; i < 3; ++i)
181 : {
182 0 : vkey[i] = std::strtol(c, &c, 10);
183 0 : if(vkey[i] < 0)
184 : {
185 0 : vkey[i] = attrib[i].size() + vkey[i];
186 : }
187 : else
188 : {
189 0 : vkey[i]--;
190 : }
191 0 : if(!(attrib[i].size() > static_cast<uint>(vkey[i])))
192 : {
193 0 : vkey[i] = -1;
194 : }
195 0 : if(*c!='/')
196 : {
197 0 : break;
198 : }
199 0 : c++;
200 : }
201 0 : std::unordered_map<ivec, uint>::iterator itr = verthash.find(vkey);
202 : uint *index;
203 0 : if(itr == verthash.end())
204 : {
205 0 : index = &verthash[vkey];
206 0 : *index = verts.size();
207 0 : vert v;
208 0 : v.pos = vkey.x < 0 ? vec(0, 0, 0) : attrib[0][vkey.x];
209 0 : v.pos = vec(v.pos.z, -v.pos.x, v.pos.y);
210 0 : v.norm = vkey.z < 0 ? vec(0, 0, 0) : attrib[2][vkey.z];
211 0 : v.norm = vec(v.norm.z, -v.norm.x, v.norm.y);
212 0 : verts.push_back(std::move(v));
213 0 : tcverts.push_back({vkey.y < 0 ? vec2(0, 0) : vec2(attrib[1][vkey.y].x, 1-attrib[1][vkey.y].y)});
214 : }
215 : else
216 : {
217 0 : index = &(*itr).second;
218 : }
219 0 : if(!v0)
220 : {
221 0 : v0 = *index;
222 : }
223 0 : else if(!v1)
224 : {
225 0 : v1 = *index;
226 : }
227 : else
228 : {
229 0 : tris.push_back({*index, v1.value(), v0.value()});
230 0 : v1 = *index;
231 : }
232 0 : }
233 0 : break;
234 : }
235 0 : }
236 : }
237 0 : if(curmesh)
238 : {
239 0 : flushmesh(*curmesh, verts, tcverts, tris, attrib[2], smooth);
240 : }
241 0 : delete file;
242 0 : return true;
243 0 : }
244 :
245 0 : void obj::objmeshgroup::parsevert(char *s, std::vector<vec> &out)
246 : {
247 0 : out.emplace_back(0, 0, 0);
248 0 : vec &v = out.back();
249 0 : while(std::isalpha(*s))
250 : {
251 0 : s++;
252 : }
253 0 : for(int i = 0; i < 3; ++i)
254 : {
255 0 : v[i] = std::strtod(s, &s);
256 0 : while(std::isspace(*s))
257 : {
258 0 : s++;
259 : }
260 0 : if(!*s)
261 : {
262 0 : break;
263 : }
264 : }
265 0 : }
266 :
267 0 : void obj::objmeshgroup::flushmesh(vertmesh &curmesh,
268 : const std::vector<vert> &verts,
269 : const std::vector<tcvert> &tcverts,
270 : const std::vector<tri> &tris,
271 : const std::vector<vec> &attrib,
272 : float smooth)
273 : {
274 0 : curmesh.numverts = verts.size();
275 0 : if(verts.size())
276 : {
277 0 : curmesh.verts = new vert[verts.size()];
278 0 : std::memcpy(curmesh.verts, verts.data(), verts.size()*sizeof(vert));
279 0 : curmesh.tcverts = new tcvert[verts.size()];
280 0 : std::memcpy(curmesh.tcverts, tcverts.data(), tcverts.size()*sizeof(tcvert));
281 : }
282 0 : curmesh.numtris = tris.size();
283 0 : if(tris.size())
284 : {
285 0 : curmesh.tris = new tri[tris.size()];
286 0 : std::memcpy(curmesh.tris, tris.data(), tris.size()*sizeof(tri));
287 : }
288 0 : if(attrib.empty())
289 : {
290 0 : if(smooth <= 1)
291 : {
292 0 : curmesh.smoothnorms(smooth);
293 : }
294 : else
295 : {
296 0 : curmesh.buildnorms();
297 : }
298 : }
299 0 : curmesh.calctangents();
300 0 : }
301 :
302 0 : bool obj::loaddefaultparts()
303 : {
304 0 : part &mdl = addpart();
305 0 : std::string pname = parentdir(modelname().c_str());
306 0 : std::string name1 = std::format("{}{}/tris.obj", modelpath, modelname());
307 0 : mdl.meshes = sharemeshes(path(name1.data()));
308 0 : if(!mdl.meshes)
309 : {
310 0 : std::string name2 = std::format("{}{}/tris.obj", modelpath, pname);
311 0 : mdl.meshes = sharemeshes(path(name2.data()));
312 0 : if(!mdl.meshes)
313 : {
314 0 : return false;
315 : }
316 0 : }
317 : Texture *tex, *masks;
318 0 : loadskin(modelname(), pname, tex, masks);
319 0 : mdl.initskins(tex, masks);
320 0 : if(tex==notexture)
321 : {
322 0 : conoutf("could not load model skin for %s", name1.c_str());
323 : }
324 0 : return true;
325 0 : }
326 :
327 0 : vertmodel::vertmeshgroup *obj::newmeshes()
328 : {
329 0 : return new objmeshgroup;
330 : }
|