1 /*
2 Copyright (c) 2017-2022 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 Permission is hereby granted, free of charge, to any person or organization
6 obtaining a copy of the software and accompanying documentation covered by
7 this license (the "Software") to use, reproduce, display, distribute,
8 execute, and transmit the Software, and to prepare derivative works of the
9 Software, and to permit third-parties to whom the Software is furnished to
10 do so, all subject to the following:
11 
12 The copyright notices in the Software and this entire statement, including
13 the above license grant, this restriction and the following disclaimer,
14 must be included in all copies of the Software, in whole or in part, and
15 all derivative works of the Software, unless such copies or derivative
16 works are solely in the form of machine-executable object code generated by
17 a source language processor.
18 
19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25 DEALINGS IN THE SOFTWARE.
26 */
27 
28 module dagon.resource.obj;
29 
30 import std.stdio;
31 import std.string;
32 import std.format;
33 
34 import dlib.core.memory;
35 import dlib.core.ownership;
36 import dlib.core.stream;
37 import dlib.math.vector;
38 import dlib.geometry.triangle;
39 import dlib.filesystem.filesystem;
40 import dlib.filesystem.stdfs;
41 import dlib.container.array;
42 
43 import dagon.core.bindings;
44 import dagon.resource.asset;
45 import dagon.graphics.mesh;
46 
47 struct ObjFace
48 {
49     uint[3] v;
50     uint[3] t;
51     uint[3] n;
52     
53     this(uint v1, uint v2, uint v3,
54          uint t1, uint t2, uint t3,
55          uint n1, uint n2, uint n3)
56     {
57         v[0] = v1;
58         v[1] = v2;
59         v[2] = v3;
60         
61         t[0] = t1;
62         t[1] = t2;
63         t[2] = t3;
64         
65         n[0] = n1;
66         n[1] = n2;
67         n[2] = n3;
68     }
69 }
70 
71 class OBJAsset: Asset
72 {
73     Mesh mesh;
74 
75     this(Owner o)
76     {
77         super(o);
78         mesh = New!Mesh(this);
79     }
80 
81     ~this()
82     {
83         release();
84     }
85 
86     override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr)
87     {
88         uint numVerts = 0;
89         uint numNormals = 0;
90         uint numTexcoords = 0;
91         uint numFaces = 0;
92         
93         string fileStr = readText(istrm);
94         
95         foreach(line; lineSplitter(fileStr))
96         {
97             if (line.startsWith("v "))
98                 numVerts++;
99             else if (line.startsWith("vn "))
100                 numNormals++;
101             else if (line.startsWith("vt "))
102                 numTexcoords++;
103             else if (line.startsWith("f "))
104                 numFaces++;
105             else if (line.startsWith("g "))
106             {
107                 writeln("Warning: OBJ file \"", filename, "\" contains groups, Dagon currently can't handle such files");
108                 Delete(fileStr);
109                 return false;
110             }
111             else if (line.startsWith("mtllib "))
112                 writeln("Warning: OBJ file \"", filename, "\" contains materials, but Dagon currently doesn't support them");
113         }
114         
115         Vector3f[] tmpVertices;
116         Vector3f[] tmpNormals;
117         Vector2f[] tmpTexcoords;
118         Array!ObjFace tmpFaces;
119         
120         bool needGenNormals = false;
121         
122         if (!numVerts)
123             writeln("Warning: OBJ file \"", filename, "\" has no vertices");
124         if (!numNormals)
125         {
126             writeln("Warning: OBJ file \"", filename, "\" has no normals (they will be generated)");
127             numNormals = numVerts;
128             needGenNormals = true;
129         }
130         if (!numTexcoords)
131         {
132             writeln("Warning: OBJ file \"", filename, "\" has no texcoords");
133             numTexcoords = numVerts;
134         }
135         
136         if (numVerts)
137             tmpVertices = New!(Vector3f[])(numVerts);
138         if (numNormals)
139             tmpNormals = New!(Vector3f[])(numNormals);
140         if (numTexcoords)
141             tmpTexcoords = New!(Vector2f[])(numTexcoords);
142         if (numFaces)
143             tmpFaces.reserve(numFaces);
144         
145         tmpVertices[] = Vector3f(0, 0, 0);
146         tmpNormals[] = Vector3f(0, 0, 0);
147         tmpTexcoords[] = Vector2f(0, 0);
148         
149         float x, y, z;
150         uint v1, v2, v3, v4;
151         uint t1, t2, t3, t4;
152         uint n1, n2, n3, n4;
153         uint vi = 0;
154         uint ni = 0;
155         uint ti = 0;
156         
157         foreach(line; lineSplitter(fileStr))
158         {
159             if (line.startsWith("v "))
160             {
161                 if (formattedRead(line, "v %s %s %s", &x, &y, &z))
162                 {
163                     tmpVertices[vi] = Vector3f(x, y, z);
164                     vi++;
165                 }
166             }
167             else if (line.startsWith("vn"))
168             {
169                 if (formattedRead(line, "vn %s %s %s", &x, &y, &z))
170                 {
171                     tmpNormals[ni] = Vector3f(x, y, z);
172                     ni++;
173                 }
174             }
175             else if (line.startsWith("vt"))
176             {
177                 if (formattedRead(line, "vt %s %s", &x, &y))
178                 {
179                     tmpTexcoords[ti] = Vector2f(x, -y);
180                     ti++;
181                 }
182             }
183             else if (line.startsWith("vp"))
184             {
185             }
186             else if (line.startsWith("f"))
187             {
188                 char[256] tmpStr;
189                 tmpStr[0..line.length] = line[];
190                 tmpStr[line.length] = 0;
191                 
192                 ObjFace face;
193                 
194                 if (sscanf(tmpStr.ptr, "f %u/%u/%u %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3, &v4, &t4, &n4) == 12)
195                 {
196                     face = ObjFace(v1-1, v2-1, v3-1, t1-1, t2-1, t3-1, n1-1, n2-1, n3-1);
197                     tmpFaces.insertBack(face);
198                     
199                     face = ObjFace(v1-1, v3-1, v4-1, t1-1, t3-1, t4-1, n1-1, n3-1, n4-1);
200                     tmpFaces.insertBack(face);
201                 }
202                 else if (sscanf(tmpStr.ptr, "f %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3) == 9)
203                 {
204                     face = ObjFace(v1-1, v2-1, v3-1, t1-1, t2-1, t3-1, n1-1, n2-1, n3-1);
205                     tmpFaces.insertBack(face);
206                 }
207                 else if (sscanf(tmpStr.ptr, "f %u//%u %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3, &v4, &n4) == 8)
208                 {
209                     face = ObjFace(v1-1, v2-1, v3-1, 0, 0, 0, n1-1, n2-1, n3-1);
210                     tmpFaces.insertBack(face);
211                     
212                     face = ObjFace(v1-1, v3-1, v4-1, 0, 0, 0, n1-1, n3-1, n4-1);
213                     tmpFaces.insertBack(face);
214                 } 
215                 else if (sscanf(tmpStr.ptr, "f %u/%u %u/%u %u/%u", &v1, &t1, &v2, &t2, &v3, &t3) == 6)
216                 {
217                     face = ObjFace(v1-1, v2-1, v3-1, t1-1, t2-1, t3-1, 0, 0, 0);
218                     tmpFaces.insertBack(face);
219                 }
220                 else if (sscanf(tmpStr.ptr, "f %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3) == 6)
221                 {
222                     face = ObjFace(v1-1, v2-1, v3-1, 0, 0, 0, n1-1, n2-1, n3-1);
223                     tmpFaces.insertBack(face);
224                 }
225                 else if (sscanf(tmpStr.ptr, "f %u %u %u %u", &v1, &v2, &v3, &v4) == 4)
226                 {
227                     face = ObjFace(v1-1, v2-1, v3-1, 0, 0, 0, 0, 0, 0);
228                     tmpFaces.insertBack(face);
229                     
230                     face = ObjFace(v1-1, v3-1, v4-1, 0, 0, 0, 0, 0, 0);
231                     tmpFaces.insertBack(face);
232                 }
233                 else if (sscanf(tmpStr.ptr, "f %u %u %u", &v1, &v2, &v3) == 3)
234                 {
235                     face = ObjFace(v1-1, v2-1, v3-1, 0, 0, 0, 0, 0, 0);
236                     tmpFaces.insertBack(face);
237                 }
238                 else
239                     assert(0);
240             }
241         }
242         
243         Delete(fileStr);
244         
245         mesh.indices = New!(uint[3][])(tmpFaces.length);
246         uint numUniqueVerts = cast(uint)mesh.indices.length * 3;
247         mesh.vertices = New!(Vector3f[])(numUniqueVerts);
248         mesh.normals = New!(Vector3f[])(numUniqueVerts);
249         mesh.texcoords = New!(Vector2f[])(numUniqueVerts);
250         
251         uint index = 0;
252         
253         foreach(i, ref ObjFace f; tmpFaces)
254         {
255             if (numVerts)
256             {
257                 mesh.vertices[index] = tmpVertices[f.v[0]];
258                 mesh.vertices[index+1] = tmpVertices[f.v[1]];
259                 mesh.vertices[index+2] = tmpVertices[f.v[2]];
260             }
261             else
262             {
263                 mesh.vertices[index] = Vector3f(0, 0, 0);
264                 mesh.vertices[index+1] = Vector3f(0, 0, 0);
265                 mesh.vertices[index+2] = Vector3f(0, 0, 0);
266             }
267             
268             if (numNormals)
269             {
270                 mesh.normals[index] = tmpNormals[f.n[0]];
271                 mesh.normals[index+1] = tmpNormals[f.n[1]];
272                 mesh.normals[index+2] = tmpNormals[f.n[2]];
273             }
274             else
275             {
276                 mesh.normals[index] = Vector3f(0, 0, 0);
277                 mesh.normals[index+1] = Vector3f(0, 0, 0);
278                 mesh.normals[index+2] = Vector3f(0, 0, 0);
279             }
280             
281             if (numTexcoords)
282             {
283                 mesh.texcoords[index] = tmpTexcoords[f.t[0]];
284                 mesh.texcoords[index+1] = tmpTexcoords[f.t[1]];
285                 mesh.texcoords[index+2] = tmpTexcoords[f.t[2]];
286             }
287             else
288             {
289                 mesh.texcoords[index] = Vector2f(0, 0);
290                 mesh.texcoords[index+1] = Vector2f(0, 0);
291                 mesh.texcoords[index+2] = Vector2f(0, 0);
292             }
293             
294             mesh.indices[i][0] = index;
295             mesh.indices[i][1] = index + 1;
296             mesh.indices[i][2] = index + 2;
297             
298             index += 3;
299         }
300         
301         
302         if (needGenNormals)
303             mesh.generateNormals();
304         
305         if (tmpVertices.length)
306             Delete(tmpVertices);
307         if (tmpNormals.length)
308             Delete(tmpNormals);
309         if (tmpTexcoords.length)
310             Delete(tmpTexcoords);
311         tmpFaces.free();
312         
313         mesh.calcBoundingBox();
314         
315         mesh.dataReady = true;
316         
317         return true;
318     }
319 
320     override bool loadThreadUnsafePart()
321     {
322         mesh.prepareVAO();
323         return true;
324     }
325 
326     override void release()
327     {
328         clearOwnedObjects();
329     }
330 }