Skip to content

Instantly share code, notes, and snippets.

@jakubtomsu
Created October 6, 2024 09:34
Show Gist options
  • Save jakubtomsu/23053f2f024474e65db4ff1ae818b3da to your computer and use it in GitHub Desktop.
Save jakubtomsu/23053f2f024474e65db4ff1ae818b3da to your computer and use it in GitHub Desktop.
Simple .obj 3d model parser, doesn't support 100% features like curves and whatnot but it's good enough for most regular models.
package obj
import "core:fmt"
import "core:math/linalg"
import "core:os"
import "core:strconv"
import "core:strings"
// https://en.wikipedia.org/wiki/Wavefront_.obj_file
// Not supported:
// - groups
// - smoothing groups
// - nurbs shit
File :: struct {
mtl_paths: [dynamic]string,
objects: [dynamic]Object,
vertex_positions: [dynamic][4]f32,
vertex_normals: [dynamic][3]f32,
vertex_uvs: [dynamic][3]f32,
}
Object :: struct {
name: string,
faces: [dynamic]Face,
}
Face :: struct {
material: string,
vertices: [dynamic]Vertex,
}
// Contains indices into the File vertex arrays
Vertex :: struct {
position: i32,
normal: i32,
uv: i32,
}
Error :: union #shared_nil {}
load :: proc(file_name: string, allocator := context.allocator) -> (result: File, ok: bool) {
context.allocator = allocator
data := os.read_entire_file_from_filename(file_name) or_return
return parse(string(data)), true
}
parse :: proc(data: string, allocator := context.allocator) -> (result: File) {
context.allocator = allocator
iter := data
curr_mat: string
for line in strings.split_lines_iterator(&iter) {
if len(line) == 0 do continue
switch line[0] {
case '#':
continue
case 'v':
switch line[1] {
// List of geometric vertices, with (x, y, z, [w]) coordinates, w is optional and defaults to 1.0.
// v 0.123 0.234 0.345 1.0
case ' ':
pos: [4]f32 = {0, 0, 0, 1}
temp := line[2:]
for i in 0 ..< 4 {
val, num, _ := strconv.parse_f32_prefix(temp)
assert(num > 0)
pos[i] = val
if len(temp) - num > 0 {
temp = temp[num + 1:]
} else {
break
}
}
append(&result.vertex_positions, pos)
// List of vertex normals in (x,y,z) form; normals might not be unit vectors.
// vn 0.707 0.000 0.707
case 'n':
nor: [3]f32
temp := line[3:]
for i in 0 ..< 3 {
val, num, _ := strconv.parse_f32_prefix(temp)
assert(num > 0)
nor[i] = val
temp = temp[num + (i == 2 ? 0 : 1):]
}
nor = linalg.normalize0(nor)
append(&result.vertex_normals, nor)
// List of texture coordinates, in (u, [v, w]) coordinates, these will vary between 0 and 1.
// v, w are optional and default to 0.
// vt 0.500 1 [0]
case 't':
uv: [3]f32 = {0, 0, 0}
temp := line[3:]
for i in 0 ..< 3 {
val, num, _ := strconv.parse_f32_prefix(temp)
assert(num > 0)
uv[i] = val
if len(temp) - num > 0 {
temp = temp[num + 1:]
} else {
break
}
}
append(&result.vertex_uvs, uv)
}
case 'o':
name := line[2:]
object := Object {
name = name,
faces = make([dynamic]Face, 0, 16),
}
append(&result.objects, object)
// f 1 2 3
// f 3/1 4/2 5/3
// f 6/4/1 3/5/3 7/6/5
// f 7//1 8//2 9//3
case 'f':
temp := line[2:]
face := Face {
material = curr_mat,
vertices = make([dynamic]Vertex, 0, 4),
}
for _ in 0 ..< 4096 {
num: int
pos, _ := strconv.parse_i64_of_base(temp, 10, &num)
temp = temp[num + 1:]
uv, _ := strconv.parse_i64_of_base(temp, 10, &num)
temp = temp[num + 1:]
nor, _ := strconv.parse_i64_of_base(temp, 10, &num)
append(&face.vertices, Vertex{position = i32(pos), normal = i32(nor), uv = i32(uv)})
if len(temp) - num > 0 {
temp = temp[num + 1:]
} else {
break
}
}
assert(len(result.objects) > 0)
object := &result.objects[len(result.objects) - 1]
append(&object.faces, face)
case 'u':
assert(strings.has_prefix(line, "usemtl"))
name := line[len("usemtl "):]
curr_mat = name
case 'm':
assert(strings.has_prefix(line, "mtllib "))
path := line[len("mtllib "):]
append(&result.mtl_paths, path)
case:
}
}
return result
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment