1 2 module ws.file.obj; 3 4 import std.conv; 5 6 import ws.math.vector; 7 import ws.decode; 8 import ws.io; 9 import ws.string; 10 11 12 __gshared: 13 14 15 class MTL { 16 public: 17 class Material { 18 this(string n){ 19 name = n; 20 } 21 string name; 22 Vector!3 ambient; 23 Vector!3 diffuse; 24 Vector!3 specular; 25 double dissolve; 26 int illum; 27 string mapDiffuse; 28 string mapAmbient; 29 string mapSpecular; 30 string mapBump; 31 string ext; 32 }; 33 Material[] mtls; 34 35 void load(string path){ 36 Material currentMat; 37 MTL currentMtl; 38 currentMtl = this; 39 try Decode.file(path, delegate(string command, string content, bool b){ 40 switch(command){ 41 case "newmtl": 42 currentMat = new Material(content); 43 currentMtl.mtls ~= currentMat; 44 break; 45 46 case "Ka": 47 double[] v = toNumbers(content); 48 for(size_t i=0; i<v.length; i++) 49 currentMat.ambient[i] = v[i]; 50 break; 51 52 case "Kd": 53 double[] v = toNumbers(content); 54 for(size_t i=0; i<v.length; i++) 55 currentMat.diffuse[i] = v[i]; 56 break; 57 58 case "Ks": 59 double[] v = toNumbers(content); 60 for(size_t i=0; i<v.length; i++) 61 currentMat.specular[i] = v[i]; 62 break; 63 64 case "illum": 65 currentMat.illum = to!int(content); 66 break; 67 68 case "d": 69 currentMat.dissolve = to!double(content); 70 break; 71 72 case "map_Ka": 73 currentMat.mapAmbient = content; 74 break; 75 76 case "map_Kd": 77 currentMat.mapDiffuse = content; 78 break; 79 80 case "map_Ks": 81 currentMat.mapSpecular = content; 82 break; 83 84 case "map_bump": 85 case "bump": 86 currentMat.mapBump = content; 87 break; 88 89 case "extern": 90 currentMat.ext = content; 91 break; 92 93 default: 94 break;//writeln("Unknown command: " ~ command); 95 96 } 97 98 }); 99 catch(Exception e) 100 writeln("Failed to load MTL \"%\"\n", path, e); 101 } 102 }; 103 104 105 class OBJ { 106 107 /+ 108 Decodes a wavefront obj file and stores it in this format: 109 mtllibs: 110 objects: 111 name 112 materials: 113 name 114 vertcount 115 polygons: 116 group 117 smoothinggroup 118 vertices: 119 pos 120 uvw 121 normal 122 +/ 123 124 this(string name){ 125 path = name; 126 dataVectors = [[], [], []]; 127 128 currentObject = null; 129 currentPath = ""; 130 if(name.findLast('/') < name.length) 131 currentPath = name[0..name.findLast('/')+1]; 132 133 currentObject = new OBJ.CObject("__default__"); 134 objects["__default__"] = currentObject; 135 136 try Decode.file(name, delegate(string cmd, string content, bool block){ 137 switch(cmd){ 138 case "v": 139 Vector!3 t; 140 foreach(i, n; toNumbers(content)){ 141 if(i>2) break; 142 t[i] = n; 143 } 144 dataVectors[0] ~= t; 145 break; 146 147 case "vt": 148 Vector!3 t; 149 foreach(i, n; toNumbers(content)){ 150 if(i>2) break; 151 t[i] = n; 152 } 153 dataVectors[1] ~= t; 154 break; 155 156 case "vn": 157 Vector!3 t; 158 foreach(i, n; toNumbers(content)){ 159 if(i>2) break; 160 t[i] = n; 161 } 162 dataVectors[2] ~= t; 163 break; 164 165 case "f": 166 auto f = new OBJ.Polygon; 167 currentMaterial.polygons ~= f; 168 foreach(i, string s; content.split(' ')){ 169 if(s == "") continue; 170 if(i > 2){ 171 // Convert all polygons with more than 3 vertices to triangles 172 f.vertices ~= f.vertices[$-3]; 173 f.vertices ~= f.vertices[$-2]; 174 currentMaterial.vertcount += 2; 175 } 176 auto e = new OBJ.Vertex; 177 f.vertices ~= e; 178 string[] vertSplit = s.split('/'); 179 currentMaterial.vertcount++; 180 for(size_t mode = 0; mode < 3; mode++){ 181 if(mode >= vertSplit.length || vertSplit[mode] == ""){ 182 // fill values that are not given 183 e[mode] = (mode==1 ? Vector!3(e.pos[1], e.pos[0]+e.pos[2], 0)/100 : Vector!3(0,1,0)); 184 }else{ 185 size_t idx = to!size_t(vertSplit[mode]); 186 if(idx<1 || idx>dataVectors[mode].length) 187 throw new Exception(tostring("Model has dangerous % index (%)", (mode==0 ? "vertex" : (mode==1 ? "uvw" : "normal")), idx)); 188 else 189 e[mode] = dataVectors[mode][idx-1]; 190 } 191 } 192 } 193 break; 194 195 case "mtllib": 196 auto m = new MTL; 197 m.load(currentPath ~ content); 198 mtllibs ~= m; 199 break; 200 201 case "usemtl": 202 if(content in currentObject.materials){ 203 currentMaterial = currentObject.materials[content]; 204 return; 205 } 206 currentMaterial = new OBJ.Material(content); 207 currentObject.materials[content] = currentMaterial; 208 break; 209 210 case "o": 211 if(content in objects){ 212 currentObject = objects[content]; 213 return; 214 } 215 currentObject = new OBJ.CObject(content); 216 objects[content] = currentObject; 217 break; 218 219 case "g": 220 currentGroup = content; 221 break; 222 223 case "s": 224 if(content == "off") 225 currentSmooth = 0; 226 else 227 currentSmooth = to!long(content); 228 break; 229 230 default: 231 options[cmd] = content; 232 if(cmd !in unknownCommands){ 233 writeln("OBJ \"%\": Command not recognized: \"%\"", name, cmd); 234 unknownCommands[cmd] = true; 235 } 236 } 237 }); 238 catch(Exception e){ 239 throw new Exception("Failed to decode \"" ~ name ~ "\", " ~ e.toString); 240 } 241 } 242 243 static class Vertex { 244 Vector!3 pos; 245 Vector!3 uvw; 246 Vector!3 normal; 247 ref Vector!3 opIndex(long i){ 248 switch(i){ 249 case 0: return pos; 250 case 1: return uvw; 251 case 2: return normal; 252 default: return pos; 253 } 254 } 255 } 256 257 static class Polygon { 258 string group; 259 long smoothinggroup; 260 Vertex[] vertices; 261 } 262 263 static class Material { 264 this(string n){ 265 name = n; 266 } 267 string name; 268 int vertcount = 0; 269 Polygon[] polygons; 270 } 271 272 static class CObject { 273 this(string n){ 274 name = n; 275 } 276 string name; 277 Material[string] materials; 278 } 279 280 MTL[] mtllibs; 281 282 CObject[string] objects; 283 284 bool[string] unknownCommands; 285 string[string] options; 286 Vector!3[][] dataVectors; 287 Material currentMaterial; 288 CObject currentObject; 289 string currentGroup; 290 long currentSmooth; 291 string currentPath; 292 string path; 293 294 } 295 296 297 298 class DataOBJ { 299 Vector!3[] vertices; 300 Vector!3[] texCoords; 301 Vector!3[] normals; 302 Polygon[] polygons; 303 304 this(string name){ 305 306 currentObject = null; 307 currentPath = ""; 308 if(name.findLast('/') < name.length) 309 currentPath = name[0..name.findLast('/')+1]; 310 311 currentObject = new DataOBJ.CObject("__default__"); 312 objects["__default__"] = currentObject; 313 314 try Decode.file(name, delegate(string cmd, string content, bool block){ 315 switch(cmd){ 316 case "v": 317 Vector!3 t; 318 foreach(i, n; toNumbers(content)){ 319 if(i>2) break; 320 t[i] = n; 321 } 322 vertices ~= t; 323 break; 324 325 case "vt": 326 Vector!3 t; 327 foreach(i, n; toNumbers(content)){ 328 if(i>2) break; 329 t[i] = n; 330 } 331 texCoords ~= t; 332 break; 333 334 case "vn": 335 Vector!3 t; 336 foreach(i, n; toNumbers(content)){ 337 if(i>2) break; 338 t[i] = n; 339 } 340 normals ~= t; 341 break; 342 343 case "f": 344 Polygon f = []; 345 foreach(i, string s; content.split(' ')){ 346 if(s == "") continue; 347 f ~= [-1, -1, -1]; 348 string[] vertSplit = s.split('/'); 349 currentMaterial.vertcount++; 350 for(size_t mode = 0; mode < 3; mode++){ 351 if(mode < vertSplit.length && vertSplit[mode] != "") 352 f[i][mode] = to!size_t(vertSplit[mode])-1; 353 } 354 } 355 currentMaterial.polygons ~= f; 356 break; 357 358 case "mtllib": 359 auto m = new MTL; 360 m.load(currentPath ~ content); 361 mtllibs ~= m; 362 break; 363 364 case "usemtl": 365 if(content in currentObject.materials){ 366 currentMaterial = currentObject.materials[content]; 367 return; 368 } 369 currentMaterial = new DataOBJ.Material(content); 370 currentObject.materials[content] = currentMaterial; 371 break; 372 373 case "o": 374 case "g": 375 if(content in objects){ 376 currentObject = objects[content]; 377 return; 378 } 379 currentObject = new DataOBJ.CObject(content); 380 objects[content] = currentObject; 381 break; 382 383 //case "g": 384 // currentGroup = content; 385 //break; 386 387 case "s": 388 if(content == "off") 389 currentSmooth = 0; 390 else 391 currentSmooth = to!long(content); 392 break; 393 394 default: 395 options[cmd] = content; 396 } 397 }); 398 catch(Exception e){ 399 throw new Exception("Failed to decode \"" ~ name ~ "\"", e); 400 } 401 } 402 403 alias size_t[3][] Polygon; // [vertIndex, normalIndex, texIndex] 404 405 406 static class Material { 407 this(string n){ 408 name = n; 409 } 410 string name; 411 int vertcount = 0; 412 Polygon[] polygons; 413 } 414 415 static class CObject { 416 this(string n){ 417 name = n; 418 } 419 string name; 420 Material[string] materials; 421 } 422 423 MTL[] mtllibs; 424 425 CObject[string] objects; 426 427 string[string] options; 428 Material currentMaterial; 429 CObject currentObject; 430 string currentGroup; 431 long currentSmooth; 432 string currentPath; 433 434 }