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