| 1 | module obj |
| 2 | |
| 3 | /********************************************************************** |
| 4 | * |
| 5 | * .obj loader |
| 6 | * |
| 7 | * Copyright (c) 2021 Dario Deledda. All rights reserved. |
| 8 | * Use of this source code is governed by an MIT license |
| 9 | * that can be found in the LICENSE file. |
| 10 | * |
| 11 | * TODO: |
| 12 | **********************************************************************/ |
| 13 | import gg.m4 |
| 14 | import strconv |
| 15 | |
| 16 | enum F_state { |
| 17 | start |
| 18 | first |
| 19 | ints |
| 20 | decimals |
| 21 | exp_start |
| 22 | exp_sign |
| 23 | exp_int |
| 24 | } |
| 25 | |
| 26 | // read a int from a string |
| 27 | fn get_int(s string, start_index int) (int, int) { |
| 28 | mut i := start_index |
| 29 | mut res := 0 |
| 30 | mut sgn := 1 |
| 31 | |
| 32 | mut state := F_state.start |
| 33 | for true { |
| 34 | if i >= s.len { |
| 35 | break |
| 36 | } |
| 37 | c := s[i] |
| 38 | if state == .start { |
| 39 | match c { |
| 40 | `+` { |
| 41 | i++ |
| 42 | state = .ints |
| 43 | continue |
| 44 | } |
| 45 | `-` { |
| 46 | sgn = -1 |
| 47 | i++ |
| 48 | state = .ints |
| 49 | continue |
| 50 | } |
| 51 | `0`...`9` { |
| 52 | state = .ints |
| 53 | } |
| 54 | ` `, `\t` { |
| 55 | i++ |
| 56 | continue |
| 57 | } |
| 58 | else { // no number found |
| 59 | break |
| 60 | } |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | if state == .ints { |
| 65 | match c { |
| 66 | `0`...`9` { |
| 67 | // println("${res} => ${(int(c) - 48)}") |
| 68 | res = res * 10 + (int(c) - 48) |
| 69 | i++ |
| 70 | continue |
| 71 | } |
| 72 | else { |
| 73 | break |
| 74 | } |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | // println("---") |
| 79 | return res * sgn, i |
| 80 | } |
| 81 | |
| 82 | // reas a float number from a string |
| 83 | fn get_float(s string, start_index int) (f64, int) { |
| 84 | mut i1 := start_index //+ 1 |
| 85 | for i1 < s.len && s[i1] in [` `, `\t`] { |
| 86 | i1++ |
| 87 | } |
| 88 | mut i := i1 |
| 89 | for i < s.len { |
| 90 | if s[i] in [` `, `\t`] { |
| 91 | break |
| 92 | } |
| 93 | i++ |
| 94 | } |
| 95 | // println(" get_float: (${start_index},${i}) [${s[start_index..i]}]") |
| 96 | // f_res := strconv.atof_quick(s[start_index..i]) |
| 97 | f_res := strconv.atof_quick(s[i1..i]) |
| 98 | return f_res, i |
| 99 | } |
| 100 | |
| 101 | // read 3 f32 in sequence from a string |
| 102 | fn parse_3f(row string, start_index int) m4.Vec4 { |
| 103 | // println(row) |
| 104 | mut i := start_index //+ 1 |
| 105 | mut f1 := f64(0) |
| 106 | mut f2 := f64(0) |
| 107 | f0, mut p := get_float(row, i) |
| 108 | // print("Here f0: ${f0} ${p} ") |
| 109 | f1, p = get_float(row, p + 1) |
| 110 | // print("Here f1: ${f1} ${p} ") |
| 111 | f2, p = get_float(row, p + 1) |
| 112 | // print("Here f2: ${f2} ${p} ") |
| 113 | return m4.Vec4{ |
| 114 | e: [f32(f0), f32(f1), f32(f2), 1]! |
| 115 | } |
| 116 | } |
| 117 | |
| 118 | // reas a sequence of f32 from a string |
| 119 | fn (mut m ObjPart) parse_floats(row string, start_index int) m4.Vec4 { |
| 120 | mut i := start_index //+ 1 |
| 121 | mut res_f := f64(0) |
| 122 | mut res := m4.Vec4{ |
| 123 | e: [f32(0), 0, 0, 1]! |
| 124 | } |
| 125 | mut c := 0 |
| 126 | for true { |
| 127 | res_f, i = get_float(row, i) |
| 128 | unsafe { |
| 129 | res.e[c] = f32(res_f) |
| 130 | } |
| 131 | c++ |
| 132 | i++ |
| 133 | if i >= row.len { |
| 134 | break |
| 135 | } |
| 136 | } |
| 137 | return res |
| 138 | } |
| 139 | |
| 140 | // read and manage all the faes from an .obj file data |
| 141 | fn (mut p Part) parse_faces(row string, start_index int, obj_part ObjPart) { |
| 142 | mut i := start_index + 1 |
| 143 | mut res := [][3]int{} |
| 144 | mut v := 0 |
| 145 | mut t := 0 |
| 146 | mut n := 0 |
| 147 | // println("row: ${row[i..]}") |
| 148 | for true { |
| 149 | t = 0 |
| 150 | n = 0 |
| 151 | if i >= row.len { |
| 152 | break |
| 153 | } |
| 154 | mut c := row[i] |
| 155 | if (c > `9` || c < `0`) && c != `-` { |
| 156 | i++ |
| 157 | continue |
| 158 | } |
| 159 | v, i = get_int(row, i) |
| 160 | if i < row.len && row[i] == `/` { |
| 161 | if row[i + 1] != `/` { |
| 162 | t, i = get_int(row, i + 1) |
| 163 | if i < row.len && row[i] == `/` { |
| 164 | n, i = get_int(row, i + 1) |
| 165 | } |
| 166 | } else { |
| 167 | i++ |
| 168 | n, i = get_int(row, i + 1) |
| 169 | } |
| 170 | } |
| 171 | // manage negative indexes |
| 172 | // NOTE: not well suporeted now |
| 173 | if v < 0 { |
| 174 | // println("${obj_part.v.len} ${obj_part.v.len-c}") |
| 175 | v = obj_part.v.len - v + 1 |
| 176 | // exit(0) |
| 177 | } |
| 178 | if n < 0 { |
| 179 | n = obj_part.vn.len - n + 1 |
| 180 | } |
| 181 | if t < 0 { |
| 182 | t = obj_part.vt.len - t + 1 |
| 183 | } |
| 184 | res << [v - 1, n - 1, t - 1]! |
| 185 | } |
| 186 | // println("ok res: ${res}") |
| 187 | // println(p.faces.len) |
| 188 | p.faces << res |
| 189 | } |
| 190 | |
| 191 | // parse the obj file, if single_material is true it use only one default material |
| 192 | pub fn (mut obj_part ObjPart) parse_obj_buffer(rows []string, single_material bool) { |
| 193 | mut mat_count := 0 |
| 194 | mut row_count := 0 |
| 195 | default_part := Part{ |
| 196 | name: 'default part' |
| 197 | } |
| 198 | obj_part.part << default_part |
| 199 | // println("OBJ file has ${rows.len} rows") |
| 200 | for c, row in rows { |
| 201 | // println("${c} ${row}") |
| 202 | mut i := 0 |
| 203 | row_count++ |
| 204 | for true { |
| 205 | if i >= row.len { |
| 206 | break |
| 207 | } |
| 208 | match row[i] { |
| 209 | `s` { |
| 210 | break |
| 211 | } |
| 212 | `m` { |
| 213 | if row[i..i + 6] == 'mtllib' { |
| 214 | obj_part.material_file = row[i + 7..].trim_space() |
| 215 | obj_part.load_materials() |
| 216 | } |
| 217 | break |
| 218 | } |
| 219 | `o`, `g` { |
| 220 | mut part := Part{} |
| 221 | part.name = row[i + 1..].trim_space() |
| 222 | obj_part.part << part |
| 223 | mat_count = 0 |
| 224 | break |
| 225 | } |
| 226 | `u` { |
| 227 | if single_material == false && row[i..i + 6] == 'usemtl' { |
| 228 | material := row[i + 7..].trim_space() |
| 229 | // println("material: ${material}") |
| 230 | // manage multiple materials in an part |
| 231 | if obj_part.part[obj_part.part.len - 1].material.len > 0 { |
| 232 | mat_count++ |
| 233 | mut part := Part{} |
| 234 | if mat_count > 1 { |
| 235 | li := obj_part.part[obj_part.part.len - 1].name.last_index('_m') or { |
| 236 | obj_part.part[obj_part.part.len - 1].name.len - 1 |
| 237 | } |
| 238 | part.name = obj_part.part[obj_part.part.len - 1].name[..li] + |
| 239 | '_m${mat_count:02}' |
| 240 | } else { |
| 241 | part.name = obj_part.part[obj_part.part.len - 1].name + '_m01' |
| 242 | } |
| 243 | obj_part.part << part |
| 244 | } |
| 245 | obj_part.part[obj_part.part.len - 1].material = material |
| 246 | } |
| 247 | break |
| 248 | } |
| 249 | `v` { |
| 250 | i++ |
| 251 | match row[i] { |
| 252 | // normals |
| 253 | `n` { |
| 254 | obj_part.vn << parse_3f(row, i + 2) |
| 255 | // println("Vertex line: ${c}") |
| 256 | break |
| 257 | } |
| 258 | // parameters uvw |
| 259 | `p` { |
| 260 | obj_part.vp << parse_3f(row, i + 2) |
| 261 | // println("Vertex line: ${obj_part.vp.len}") |
| 262 | break |
| 263 | } |
| 264 | // texture uvw |
| 265 | `t` { |
| 266 | obj_part.vt << obj_part.parse_floats(row, i + 2) |
| 267 | // println("Vertex line: ${c}") |
| 268 | break |
| 269 | } |
| 270 | else { |
| 271 | obj_part.v << parse_3f(row, i + 1) |
| 272 | // println("${row} => ${obj_part.v[obj_part.v.len-1]}") |
| 273 | break |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | `f` { |
| 278 | // println("${c} ${row}") |
| 279 | obj_part.part[obj_part.part.len - 1].parse_faces(row, i, obj_part) |
| 280 | // println(obj_part.part[obj_part.part.len - 1].faces.len) |
| 281 | // println("Faces line: ${c}") |
| 282 | break |
| 283 | } |
| 284 | // end of the line, comments |
| 285 | `\n`, `#` { |
| 286 | break |
| 287 | } |
| 288 | else {} |
| 289 | } |
| 290 | |
| 291 | i++ |
| 292 | } |
| 293 | // if c == 2 { break } |
| 294 | if c % 100000 == 0 && c > 0 { |
| 295 | println('${c} rows parsed') |
| 296 | } |
| 297 | } |
| 298 | println('${row_count} .obj Rows parsed') |
| 299 | // remove default part if empty |
| 300 | if obj_part.part.len > 1 && obj_part.part[0].faces.len == 0 { |
| 301 | obj_part.part = obj_part.part[1..] |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | // load the materials if found the .mtl file |
| 306 | fn (mut obj_part ObjPart) load_materials() { |
| 307 | rows := read_lines_from_file(obj_part.material_file) |
| 308 | println('Material file [${obj_part.material_file}] ${rows.len} Rows.') |
| 309 | for row in rows { |
| 310 | // println("${row}") |
| 311 | mut i := 0 |
| 312 | for true { |
| 313 | if i >= row.len { |
| 314 | break |
| 315 | } |
| 316 | match row[i] { |
| 317 | `n` { |
| 318 | if row[i..i + 6] == 'newmtl' { |
| 319 | name := row[i + 6..].trim_space() |
| 320 | mut mat := Material{ |
| 321 | name: name |
| 322 | } |
| 323 | obj_part.mat << mat |
| 324 | break |
| 325 | } |
| 326 | } |
| 327 | `K` { |
| 328 | if row[i + 1] !in [`a`, `d`, `e`, `s`] { |
| 329 | break |
| 330 | } |
| 331 | k_name := row[i..i + 2] |
| 332 | i += 3 |
| 333 | value := parse_3f(row, i) |
| 334 | obj_part.mat[obj_part.mat.len - 1].ks[k_name] = value |
| 335 | break |
| 336 | } |
| 337 | `N` { |
| 338 | n_name := row[i..i + 2] |
| 339 | i += 3 |
| 340 | value, _ := get_float(row, i) |
| 341 | obj_part.mat[obj_part.mat.len - 1].ns[n_name] = f32(value) |
| 342 | break |
| 343 | } |
| 344 | `m` { |
| 345 | if row[i..i + 4] == 'map_' { |
| 346 | name := row[i..i + 6] |
| 347 | if (i + 7) < row.len { |
| 348 | file_name := row[i + 7..].trim_space() |
| 349 | obj_part.mat[obj_part.mat.len - 1].maps[name] = file_name |
| 350 | } |
| 351 | break |
| 352 | } |
| 353 | } |
| 354 | // transparency |
| 355 | `d` { |
| 356 | if row[i + 1] == ` ` { |
| 357 | value, _ := get_float(row, i + 2) |
| 358 | obj_part.mat[obj_part.mat.len - 1].ns['Tr'] = f32(value) |
| 359 | } |
| 360 | } |
| 361 | `T` { |
| 362 | if row[i + 1] == `r` { |
| 363 | value, _ := get_float(row, i + 3) |
| 364 | obj_part.mat[obj_part.mat.len - 1].ns['Tr'] = f32(1.0 - value) |
| 365 | } |
| 366 | } |
| 367 | // end of the line, comments |
| 368 | `\n`, `#` { |
| 369 | break |
| 370 | } |
| 371 | ` `, `\t` { |
| 372 | i++ |
| 373 | continue |
| 374 | } |
| 375 | else { |
| 376 | break |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | i++ |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | // create map material name => material index |
| 385 | for i, m in obj_part.mat { |
| 386 | if m.name !in obj_part.mat_map { |
| 387 | obj_part.mat_map[m.name] = i |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | println('Material Loading Done!') |
| 392 | } |
| 393 | |
| 394 | //============================================================================== |
| 395 | // Sokol data |
| 396 | //============================================================================== |
| 397 | |
| 398 | // vertex data struct |
| 399 | pub struct Vertex_pnct { |
| 400 | pub mut: |
| 401 | x f32 // position |
| 402 | y f32 |
| 403 | z f32 |
| 404 | nx f32 // normal |
| 405 | ny f32 |
| 406 | nz f32 |
| 407 | // color u32 = 0xFFFFFFFF // color |
| 408 | u f32 // uv |
| 409 | v f32 |
| 410 | // u u16 // for compatibility with D3D11 |
| 411 | // v u16 // for compatibility with D3D11 |
| 412 | } |
| 413 | |
| 414 | // struct used to pass the data to the sokol calls |
| 415 | pub struct Skl_buffer { |
| 416 | pub mut: |
| 417 | vbuf []Vertex_pnct |
| 418 | ibuf []u32 |
| 419 | n_vertex u32 |
| 420 | } |
| 421 | |
| 422 | // transforms data from .obj format to buffer ready to be used in the render |
| 423 | pub fn (mut obj_part ObjPart) get_buffer(in_part_list []int) Skl_buffer { |
| 424 | // in_part := 0 |
| 425 | mut v_count_index := 0 |
| 426 | mut out_buf := Skl_buffer{} |
| 427 | |
| 428 | mut cache := map[string]int{} |
| 429 | mut cache_hit := 0 |
| 430 | |
| 431 | // has_normals := obj_part.vn.len > 0 |
| 432 | // has_uvs := obj_part.vt.len > 0 |
| 433 | |
| 434 | for in_part in in_part_list { |
| 435 | part := obj_part.part[in_part] |
| 436 | for fc, face in part.faces { |
| 437 | // println("${fc} ${face}") |
| 438 | // default 3 faces |
| 439 | mut v_seq := [0, 1, 2] |
| 440 | if face.len == 4 { |
| 441 | v_seq = [0, 1, 2, 0, 2, 3] |
| 442 | } |
| 443 | |
| 444 | // if big faces => use the fan of triangles as solution |
| 445 | // Note: this trick doesn't work with concave faces |
| 446 | if face.len > 4 { |
| 447 | v_seq = [] |
| 448 | mut i := 1 |
| 449 | for i < (face.len - 1) { |
| 450 | v_seq << 0 |
| 451 | v_seq << i |
| 452 | v_seq << (i + 1) |
| 453 | i++ |
| 454 | } |
| 455 | // println("BIG FACES! ${fc} ${face.len} v_seq:${v_seq.len}") |
| 456 | } |
| 457 | |
| 458 | // no vertex index, generate normals |
| 459 | if face[0][1] == -1 && face.len >= 3 { |
| 460 | mut v_count := 0 |
| 461 | v0 := face[v_count + 0][0] |
| 462 | v1 := face[v_count + 1][0] |
| 463 | v2 := face[v_count + 2][0] |
| 464 | |
| 465 | vec0 := obj_part.v[v2] - obj_part.v[v1] |
| 466 | vec1 := obj_part.v[v0] - obj_part.v[v1] |
| 467 | tmp_normal := vec0 % vec1 |
| 468 | |
| 469 | for v_count < face.len { |
| 470 | obj_part.vn << tmp_normal |
| 471 | obj_part.part[in_part].faces[fc][v_count][1] = obj_part.vn.len - 1 |
| 472 | v_count++ |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | for vertex_index in v_seq { |
| 477 | // position |
| 478 | if vertex_index >= face.len { |
| 479 | continue |
| 480 | } |
| 481 | v_index := face[vertex_index][0] // vertex index |
| 482 | n_index := face[vertex_index][1] // normal index |
| 483 | t_index := face[vertex_index][2] // uv texture index |
| 484 | key := '${v_index}_${n_index}_${t_index}' |
| 485 | if key !in cache { |
| 486 | cache[key] = v_count_index |
| 487 | mut pnct := Vertex_pnct{ |
| 488 | x: obj_part.v[v_index].e[0] |
| 489 | y: obj_part.v[v_index].e[1] |
| 490 | z: obj_part.v[v_index].e[2] |
| 491 | } |
| 492 | // normal |
| 493 | if n_index >= 0 { |
| 494 | pnct.nx = obj_part.vn[n_index].e[0] |
| 495 | pnct.ny = obj_part.vn[n_index].e[1] |
| 496 | pnct.nz = obj_part.vn[n_index].e[2] |
| 497 | } |
| 498 | // texture uv |
| 499 | if t_index >= 0 { |
| 500 | pnct.u = obj_part.vt[t_index].e[0] |
| 501 | pnct.v = obj_part.vt[t_index].e[1] |
| 502 | } |
| 503 | |
| 504 | out_buf.vbuf << pnct |
| 505 | out_buf.ibuf << u32(v_count_index) |
| 506 | v_count_index++ |
| 507 | } else { |
| 508 | // println("Cache used! ${key}") |
| 509 | out_buf.ibuf << u32(cache[key]) |
| 510 | cache_hit++ |
| 511 | } |
| 512 | } |
| 513 | } |
| 514 | } |
| 515 | |
| 516 | /* |
| 517 | println("------------") |
| 518 | for c1, x1 in out_buf.vbuf[..10] { |
| 519 | println("${c1} ${x1}") |
| 520 | } |
| 521 | println(out_buf.ibuf[..10]) |
| 522 | */ |
| 523 | // println("vbuf size: ${out_buf.vbuf.len} ibuf size: ${out_buf.ibuf.len} Cache hit: ${cache_hit}") |
| 524 | out_buf.n_vertex = u32(out_buf.ibuf.len) |
| 525 | return out_buf |
| 526 | } |
| 527 | |
| 528 | //============================================================================== |
| 529 | // Utility |
| 530 | //============================================================================== |
| 531 | // print on the console the summary of the .obj model loaded |
| 532 | pub fn (obj_part ObjPart) summary() { |
| 533 | println('---- Stats ----') |
| 534 | println('vertices: ${obj_part.v.len}') |
| 535 | println('normals : ${obj_part.vn.len}') |
| 536 | println('uv : ${obj_part.vt.len}') |
| 537 | println('parts : ${obj_part.part.len}') |
| 538 | // Parts |
| 539 | println('---- Parts ----') |
| 540 | for c, x in obj_part.part { |
| 541 | println('${c:3} [${x.name:-16}] mat:[${x.material:-10}] ${x.faces.len:7} faces') |
| 542 | } |
| 543 | // Materials |
| 544 | println('---- Materials ----') |
| 545 | println('Material dict: ${obj_part.mat_map.keys()}') |
| 546 | for c, mat in obj_part.mat { |
| 547 | println('${c:3} [${mat.name:-16}]') |
| 548 | for k, v in mat.ks { |
| 549 | print('${k} = ${v}') |
| 550 | } |
| 551 | for k, v in mat.ns { |
| 552 | println('${k} = ${v}') |
| 553 | } |
| 554 | for k, v in mat.maps { |
| 555 | println('${k} = ${v}') |
| 556 | } |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | // debug test function, do not remove. |
| 561 | pub fn tst() { |
| 562 | /* |
| 563 | //fname := "capsule.obj" |
| 564 | //fname := "Forklift.obj" |
| 565 | fname := "cube.obj" |
| 566 | //fname := "Orange Robot 3D ObjPart.obj" |
| 567 | |
| 568 | mut obj := ObjPart{} |
| 569 | buf := os.read_lines(fname) or { panic(err.msg) } |
| 570 | obj.parse_obj_buffer(buf) |
| 571 | obj.summary() |
| 572 | */ |
| 573 | /* |
| 574 | a :="f 7048 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7003" |
| 575 | mut f1 := 0 |
| 576 | mut f2 := 0 |
| 577 | f0,mut p := get_int(a,1) |
| 578 | f1, p = get_int(a,p) |
| 579 | f2, p = get_int(a,p) |
| 580 | println("res: ${f0} ${f1} ${f2}") |
| 581 | */ |
| 582 | /* |
| 583 | a :="v -0 0.107769 -0.755914" |
| 584 | println("${parse_3f(a,1)}") |
| 585 | */ |
| 586 | /* |
| 587 | ort := m4.ortho(0,300,0,200,0,0) |
| 588 | println(ort) |
| 589 | a := m4.vec3(0,0,0) |
| 590 | println("a: ${a}") |
| 591 | res := m4.mul_vec(ort, a) |
| 592 | println("res:\n${res}") |
| 593 | */ |
| 594 | s := 'K 1 1 1' |
| 595 | r := strconv.atof_quick(s[1..s.len - 1]) |
| 596 | println(r) |
| 597 | } |
| 598 | |