1 /* 2 Copyright (c) 2017-2018 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.stream; 36 import dlib.math.vector; 37 import dlib.geometry.triangle; 38 import dlib.filesystem.filesystem; 39 import dlib.filesystem.stdfs; 40 import dlib.container.array; 41 import derelict.opengl.gl; 42 import dagon.core.ownership; 43 import dagon.core.interfaces; 44 import dagon.resource.asset; 45 import dagon.graphics.mesh; 46 47 import std.regex; 48 49 struct ObjFace 50 { 51 uint[3] v; 52 uint[3] t; 53 uint[3] n; 54 } 55 56 class OBJAsset: Asset 57 { 58 Mesh mesh; 59 60 this(Owner o) 61 { 62 super(o); 63 mesh = New!Mesh(this); 64 } 65 66 ~this() 67 { 68 release(); 69 } 70 71 override bool loadThreadSafePart(string filename, InputStream istrm, ReadOnlyFileSystem fs, AssetManager mngr) 72 { 73 uint numVerts = 0; 74 uint numNormals = 0; 75 uint numTexcoords = 0; 76 uint numFaces = 0; 77 78 string fileStr = readText(istrm); 79 foreach(line; lineSplitter(fileStr)) 80 { 81 if (line.startsWith("v ")) 82 numVerts++; 83 else if (line.startsWith("vn ")) 84 numNormals++; 85 else if (line.startsWith("vt ")) 86 numTexcoords++; 87 else if (line.startsWith("f ")) 88 numFaces++; 89 } 90 91 Vector3f[] tmpVertices; 92 Vector3f[] tmpNormals; 93 Vector2f[] tmpTexcoords; 94 ObjFace[] tmpFaces; 95 96 if (!numVerts) 97 writeln("Warning: OBJ file \"", filename, "\" has no vertices"); 98 if (!numNormals) 99 writeln("Warning: OBJ file \"", filename, "\" has no normals"); 100 if (!numTexcoords) 101 writeln("Warning: OBJ file \"", filename, "\" has no texcoords"); 102 103 if (numVerts) 104 tmpVertices = New!(Vector3f[])(numVerts); 105 if (numNormals) 106 tmpNormals = New!(Vector3f[])(numNormals); 107 if (numTexcoords) 108 tmpTexcoords = New!(Vector2f[])(numTexcoords); 109 if (numFaces) 110 tmpFaces = New!(ObjFace[])(numFaces); 111 112 float x, y, z; 113 int v1, v2, v3, v4; 114 int t1, t2, t3, t4; 115 int n1, n2, n3, n4; 116 uint vi = 0; 117 uint ni = 0; 118 uint ti = 0; 119 uint fi = 0; 120 121 bool warnAboutQuads = false; 122 123 foreach(line; lineSplitter(fileStr)) 124 { 125 if (line.startsWith("v ")) 126 { 127 if (formattedRead(line, "v %s %s %s", &x, &y, &z)) 128 { 129 tmpVertices[vi] = Vector3f(x, y, z); 130 vi++; 131 } 132 } 133 else if (line.startsWith("vn")) 134 { 135 if (formattedRead(line, "vn %s %s %s", &x, &y, &z)) 136 { 137 tmpNormals[ni] = Vector3f(x, y, z); 138 ni++; 139 } 140 } 141 else if (line.startsWith("vt")) 142 { 143 if (formattedRead(line, "vt %s %s", &x, &y)) 144 { 145 tmpTexcoords[ti] = Vector2f(x, -y); 146 ti++; 147 } 148 } 149 else if (line.startsWith("vp")) 150 { 151 } 152 else if (line.startsWith("f")) 153 { 154 char[256] tmpStr; 155 tmpStr[0..line.length] = line[]; 156 tmpStr[line.length] = 0; 157 158 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) 159 { 160 tmpFaces[fi].v[0] = v1-1; 161 tmpFaces[fi].v[1] = v2-1; 162 tmpFaces[fi].v[2] = v3-1; 163 164 tmpFaces[fi].t[0] = t1-1; 165 tmpFaces[fi].t[1] = t2-1; 166 tmpFaces[fi].t[2] = t3-1; 167 168 tmpFaces[fi].n[0] = n1-1; 169 tmpFaces[fi].n[1] = n2-1; 170 tmpFaces[fi].n[2] = n3-1; 171 172 fi++; 173 174 warnAboutQuads = true; 175 } 176 if (sscanf(tmpStr.ptr, "f %u/%u/%u %u/%u/%u %u/%u/%u", &v1, &t1, &n1, &v2, &t2, &n2, &v3, &t3, &n3) == 9) 177 { 178 tmpFaces[fi].v[0] = v1-1; 179 tmpFaces[fi].v[1] = v2-1; 180 tmpFaces[fi].v[2] = v3-1; 181 182 tmpFaces[fi].t[0] = t1-1; 183 tmpFaces[fi].t[1] = t2-1; 184 tmpFaces[fi].t[2] = t3-1; 185 186 tmpFaces[fi].n[0] = n1-1; 187 tmpFaces[fi].n[1] = n2-1; 188 tmpFaces[fi].n[2] = n3-1; 189 190 fi++; 191 } 192 else if (sscanf(tmpStr.ptr, "f %u//%u %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3, &v4, &n4) == 8) 193 { 194 tmpFaces[fi].v[0] = v1-1; 195 tmpFaces[fi].v[1] = v2-1; 196 tmpFaces[fi].v[2] = v3-1; 197 198 tmpFaces[fi].n[0] = n1-1; 199 tmpFaces[fi].n[1] = n2-1; 200 tmpFaces[fi].n[2] = n3-1; 201 202 fi++; 203 204 warnAboutQuads = true; 205 } 206 else if (sscanf(tmpStr.ptr, "f %u//%u %u//%u %u//%u", &v1, &n1, &v2, &n2, &v3, &n3) == 6) 207 { 208 tmpFaces[fi].v[0] = v1-1; 209 tmpFaces[fi].v[1] = v2-1; 210 tmpFaces[fi].v[2] = v3-1; 211 212 tmpFaces[fi].n[0] = n1-1; 213 tmpFaces[fi].n[1] = n2-1; 214 tmpFaces[fi].n[2] = n3-1; 215 216 fi++; 217 } 218 else if (sscanf(tmpStr.ptr, "f %u %u %u %u", &v1, &v2, &v3, &v4) == 4) 219 { 220 tmpFaces[fi].v[0] = v1-1; 221 tmpFaces[fi].v[1] = v2-1; 222 tmpFaces[fi].v[2] = v3-1; 223 224 fi++; 225 226 warnAboutQuads = true; 227 } 228 else if (sscanf(tmpStr.ptr, "f %u %u %u", &v1, &v2, &v3) == 3) 229 { 230 tmpFaces[fi].v[0] = v1-1; 231 tmpFaces[fi].v[1] = v2-1; 232 tmpFaces[fi].v[2] = v3-1; 233 234 fi++; 235 } 236 else 237 assert(0); 238 } 239 } 240 241 Delete(fileStr); 242 243 if (warnAboutQuads) 244 writeln("Warning: OBJ file \"", filename, "\" includes quads, but Dagon supports only triangles"); 245 246 mesh.indices = New!(uint[3][])(tmpFaces.length); 247 uint numUniqueVerts = cast(uint)mesh.indices.length * 3; 248 mesh.vertices = New!(Vector3f[])(numUniqueVerts); 249 mesh.normals = New!(Vector3f[])(numUniqueVerts); 250 mesh.texcoords = New!(Vector2f[])(numUniqueVerts); 251 252 uint index = 0; 253 254 foreach(i, ref ObjFace f; tmpFaces) 255 { 256 if (numVerts) 257 { 258 mesh.vertices[index] = tmpVertices[f.v[0]]; 259 mesh.vertices[index+1] = tmpVertices[f.v[1]]; 260 mesh.vertices[index+2] = tmpVertices[f.v[2]]; 261 } 262 else 263 { 264 mesh.vertices[index] = Vector3f(0, 0, 0); 265 mesh.vertices[index+1] = Vector3f(0, 0, 0); 266 mesh.vertices[index+2] = Vector3f(0, 0, 0); 267 } 268 269 if (numNormals) 270 { 271 mesh.normals[index] = tmpNormals[f.n[0]]; 272 mesh.normals[index+1] = tmpNormals[f.n[1]]; 273 mesh.normals[index+2] = tmpNormals[f.n[2]]; 274 } 275 else 276 { 277 mesh.normals[index] = Vector3f(0, 0, 0); 278 mesh.normals[index+1] = Vector3f(0, 0, 0); 279 mesh.normals[index+2] = Vector3f(0, 0, 0); 280 } 281 282 if (numTexcoords) 283 { 284 mesh.texcoords[index] = tmpTexcoords[f.t[0]]; 285 mesh.texcoords[index+1] = tmpTexcoords[f.t[1]]; 286 mesh.texcoords[index+2] = tmpTexcoords[f.t[2]]; 287 } 288 else 289 { 290 mesh.texcoords[index] = Vector2f(0, 0); 291 mesh.texcoords[index+1] = Vector2f(0, 0); 292 mesh.texcoords[index+2] = Vector2f(0, 0); 293 } 294 295 mesh.indices[i][0] = index; 296 mesh.indices[i][1] = index + 1; 297 mesh.indices[i][2] = index + 2; 298 299 index += 3; 300 } 301 302 // TODO: generate normals if they are not present 303 304 if (tmpVertices.length) 305 Delete(tmpVertices); 306 if (tmpNormals.length) 307 Delete(tmpNormals); 308 if (tmpTexcoords.length) 309 Delete(tmpTexcoords); 310 if (tmpFaces.length) 311 Delete(tmpFaces); 312 313 mesh.dataReady = true; 314 315 return true; 316 } 317 318 override bool loadThreadUnsafePart() 319 { 320 mesh.prepareVAO(); 321 return true; 322 } 323 324 override void release() 325 { 326 clearOwnedObjects(); 327 } 328 } 329