v / examples / sokol / 06_obj_viewer / modules / obj / obj.v
597 lines · 565 sloc · 12.9 KB · 8e35f4d9848f7ad35d857a187dddbfd2eca5e19d
Raw
1module 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**********************************************************************/
13import gg.m4
14import strconv
15
16enum 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
27fn 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
83fn 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
102fn 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
119fn (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
141fn (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
192pub 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
306fn (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
399pub struct Vertex_pnct {
400pub 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
415pub struct Skl_buffer {
416pub 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
423pub 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
532pub 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.
561pub 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