Add obj model loading and suzanne test model
This commit is contained in:
+2027
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,148 @@
|
||||
package core
|
||||
|
||||
import "core:fmt"
|
||||
import "core:math/linalg"
|
||||
import "core:os"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
|
||||
import "sindri:platform"
|
||||
|
||||
// Minimal OBJ loader: reads v, vt, vn and triangulates convex polygon faces.
|
||||
// Produces one GPU vertex per face corner, then centers and normalizes the
|
||||
// model so it fits roughly inside a [-1, 1] cube at the origin
|
||||
load_obj_model :: proc(
|
||||
path: string,
|
||||
) -> (
|
||||
vertices: []platform.Vertex,
|
||||
indices: []u16,
|
||||
ok: bool,
|
||||
) {
|
||||
data, err := os.read_entire_file_from_path(path, context.allocator)
|
||||
if err != os.ERROR_NONE {
|
||||
fmt.eprintfln("[core] failed to read model: %s", path)
|
||||
return nil, nil, false
|
||||
}
|
||||
defer delete(data)
|
||||
|
||||
positions: [dynamic]platform.Vec3
|
||||
uvs: [dynamic]platform.Vec2
|
||||
normals: [dynamic]platform.Vec3
|
||||
temp_vertices: [dynamic]platform.Vertex
|
||||
temp_indices: [dynamic]u16
|
||||
defer {
|
||||
delete(positions)
|
||||
delete(uvs)
|
||||
delete(normals)
|
||||
delete(temp_vertices)
|
||||
delete(temp_indices)
|
||||
}
|
||||
|
||||
text := string(data)
|
||||
for line in strings.split_lines_iterator(&text) {
|
||||
trimmed := strings.trim_space(line)
|
||||
if len(trimmed) == 0 || trimmed[0] == '#' do continue
|
||||
|
||||
fields := strings.fields(trimmed)
|
||||
defer delete(fields)
|
||||
if len(fields) == 0 do continue
|
||||
|
||||
switch fields[0] {
|
||||
case "v":
|
||||
if len(fields) < 4 do continue
|
||||
x, _ := strconv.parse_f32(fields[1])
|
||||
y, _ := strconv.parse_f32(fields[2])
|
||||
z, _ := strconv.parse_f32(fields[3])
|
||||
append(&positions, platform.Vec3{x, y, z})
|
||||
case "vt":
|
||||
if len(fields) < 3 do continue
|
||||
u, _ := strconv.parse_f32(fields[1])
|
||||
v, _ := strconv.parse_f32(fields[2])
|
||||
append(&uvs, platform.Vec2{u, v})
|
||||
case "vn":
|
||||
if len(fields) < 4 do continue
|
||||
x, _ := strconv.parse_f32(fields[1])
|
||||
y, _ := strconv.parse_f32(fields[2])
|
||||
z, _ := strconv.parse_f32(fields[3])
|
||||
append(&normals, platform.Vec3{x, y, z})
|
||||
case "f":
|
||||
face_indices: [dynamic]u16
|
||||
defer delete(face_indices)
|
||||
|
||||
for i := 1; i < len(fields); i += 1 {
|
||||
token := fields[i]
|
||||
slash := strings.index_byte(token, '/')
|
||||
|
||||
vi_str := token
|
||||
vti_str := ""
|
||||
if slash != -1 {
|
||||
vi_str = token[:slash]
|
||||
slash2 := strings.index_byte(token[slash + 1:], '/')
|
||||
if slash2 != -1 {
|
||||
vti_str = token[slash + 1:slash + 1 + slash2]
|
||||
} else {
|
||||
vti_str = token[slash + 1:]
|
||||
}
|
||||
}
|
||||
|
||||
vi, _ := strconv.parse_int(vi_str)
|
||||
vi -= 1 // OBJ indices are 1-based
|
||||
if vi < 0 || vi >= len(positions) do continue
|
||||
|
||||
uv := platform.Vec2{0, 0}
|
||||
if len(vti_str) > 0 {
|
||||
vti, _ := strconv.parse_int(vti_str)
|
||||
vti -= 1
|
||||
if vti >= 0 && vti < len(uvs) {
|
||||
uv = uvs[vti]
|
||||
}
|
||||
} else if vi < len(uvs) {
|
||||
uv = uvs[vi]
|
||||
}
|
||||
|
||||
append(
|
||||
&temp_vertices,
|
||||
platform.Vertex {
|
||||
pos = positions[vi],
|
||||
col = {1, 1, 1},
|
||||
uv = uv,
|
||||
},
|
||||
)
|
||||
append(&face_indices, cast(u16)(len(temp_vertices) - 1))
|
||||
}
|
||||
|
||||
// Triangulate convex polygon by fanning from the first vertex
|
||||
for i := 2; i < len(face_indices); i += 1 {
|
||||
append(&temp_indices, face_indices[0])
|
||||
append(&temp_indices, face_indices[i - 1])
|
||||
append(&temp_indices, face_indices[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(temp_vertices) == 0 {
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// Center and normalize: fit the model inside a roughly [-1, 1] cube
|
||||
min_p := temp_vertices[0].pos
|
||||
max_p := temp_vertices[0].pos
|
||||
for v in temp_vertices {
|
||||
min_p = linalg.min(min_p, v.pos)
|
||||
max_p = linalg.max(max_p, v.pos)
|
||||
}
|
||||
center := (min_p + max_p) * 0.5
|
||||
size := max_p - min_p
|
||||
max_size := max(size.x, size.y, size.z)
|
||||
scale := 1.8 / max_size
|
||||
|
||||
for &v in temp_vertices {
|
||||
v.pos = (v.pos - center) * scale
|
||||
}
|
||||
|
||||
vertices = make([]platform.Vertex, len(temp_vertices))
|
||||
indices = make([]u16, len(temp_indices))
|
||||
copy(vertices, temp_vertices[:])
|
||||
copy(indices, temp_indices[:])
|
||||
return vertices, indices, true
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import "core:time"
|
||||
|
||||
import "game:game"
|
||||
import "sindri:base"
|
||||
import "sindri:core"
|
||||
import "sindri:platform"
|
||||
|
||||
VERSION :: #config(VERSION, "dev")
|
||||
@@ -41,6 +42,7 @@ main :: proc() {
|
||||
// Initialize GPU resources
|
||||
platform.instance_init(settings)
|
||||
defer platform.instance_destroy()
|
||||
load_test_model()
|
||||
|
||||
api.init_once()
|
||||
|
||||
@@ -84,3 +86,13 @@ test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
|
||||
fmt.println("main: @@@@@@@@@@@", e)
|
||||
return true
|
||||
}
|
||||
|
||||
// Load model from disk and upload it
|
||||
load_test_model :: proc() {
|
||||
vertices, indices, model_ok := core.load_obj_model("assets/suzanne.obj")
|
||||
if !model_ok do fmt.panicf("[main] could not load model")
|
||||
defer delete(vertices)
|
||||
defer delete(indices)
|
||||
|
||||
platform.fill_buffers_with_model(vertices, indices)
|
||||
}
|
||||
|
||||
+18
-49
@@ -4,6 +4,7 @@ import "base:runtime"
|
||||
import "core:fmt"
|
||||
import "core:math"
|
||||
import "core:math/linalg"
|
||||
import "core:os"
|
||||
import "core:slice"
|
||||
|
||||
import "wgpu:wgpu"
|
||||
@@ -19,7 +20,7 @@ Vec3 :: [3]f32
|
||||
Vertex :: struct #align (4) {
|
||||
pos: Vec3,
|
||||
col: Vec3,
|
||||
uv: Vec2,
|
||||
uv: Vec2, // texture coordinate
|
||||
}
|
||||
|
||||
Camera_Uniform :: struct {
|
||||
@@ -245,6 +246,21 @@ frame :: proc "c" () {
|
||||
wgpu.SurfacePresent(state.surface)
|
||||
}
|
||||
|
||||
// Upload arbitrary vertex/index data to GPU buffers
|
||||
fill_buffers_with_model :: proc(vertices: []Vertex, indices: []u16) {
|
||||
state.vertex_buffer = wgpu.DeviceCreateBufferWithData(
|
||||
state.device,
|
||||
&{label = "model vertices", usage = {.Vertex}},
|
||||
vertices,
|
||||
)
|
||||
state.index_buffer = wgpu.DeviceCreateBufferWithData(
|
||||
state.device,
|
||||
&{label = "model indices", usage = {.Index}},
|
||||
indices,
|
||||
)
|
||||
state.index_count = cast(u32)len(indices)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Private functions
|
||||
|
||||
@@ -399,9 +415,6 @@ on_device :: proc "c" (
|
||||
// ----------------------------------------
|
||||
// Buffer shape
|
||||
|
||||
// Fill vertex/index buffers
|
||||
fill_buffers_with_quad()
|
||||
|
||||
vertex_attributes := [?]wgpu.VertexAttribute {
|
||||
{
|
||||
format = .Float32x3,
|
||||
@@ -562,50 +575,6 @@ create_depth_texture :: proc() {
|
||||
state.depth_texture_view = wgpu.TextureCreateView(state.depth_texture, nil)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
fill_buffers_with_quad :: proc() {
|
||||
vertices := [?]Vertex {
|
||||
// Near quad: z = 0, colourful, drawn first
|
||||
{pos = {-0.4, -0.4, 0.0}, col = {1, 0, 0}, uv = {0, 1}}, // 0 bottom-left red
|
||||
{pos = {0.4, -0.4, 0.0}, col = {0, 1, 0}, uv = {1, 1}}, // 1 bottom-right green
|
||||
{pos = {0.4, 0.4, 0.0}, col = {0, 0, 1}, uv = {1, 0}}, // 2 top-right blue
|
||||
{pos = {-0.4, 0.4, 0.0}, col = {1, 1, 0}, uv = {0, 0}}, // 3 top-left yellow
|
||||
|
||||
// Far quad: z = -0.5, cyan, drawn second
|
||||
{pos = {-0.8, -0.8, -0.5}, col = {0, 1, 1}, uv = {0, 1}}, // 4 bottom-left
|
||||
{pos = {0.8, -0.8, -0.5}, col = {0, 1, 1}, uv = {1, 1}}, // 5 bottom-right
|
||||
{pos = {0.8, 0.8, -0.5}, col = {0, 1, 1}, uv = {1, 0}}, // 6 top-right
|
||||
{pos = {-0.8, 0.8, -0.5}, col = {0, 1, 1}, uv = {0, 0}}, // 7 top-left
|
||||
}
|
||||
|
||||
indices := [?]u16 {
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
0, // near quad
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
6,
|
||||
7,
|
||||
4, // far quad
|
||||
}
|
||||
|
||||
state.vertex_buffer = wgpu.DeviceCreateBufferWithData(
|
||||
state.device,
|
||||
&{label = "quad vertices", usage = {.Vertex}},
|
||||
vertices[:],
|
||||
)
|
||||
state.index_buffer = wgpu.DeviceCreateBufferWithData(
|
||||
state.device,
|
||||
&{label = "quad indices", usage = {.Index}},
|
||||
indices[:],
|
||||
)
|
||||
state.index_count = cast(u32)len(indices)
|
||||
}
|
||||
|
||||
@(private = "file")
|
||||
framebuffer_aspect :: proc() -> f32 {
|
||||
width, height := os_get_framebuffer_size()
|
||||
@@ -638,7 +607,7 @@ camera_perspective :: proc(
|
||||
camera_mvp :: proc(aspect: f32) -> linalg.Matrix4f32 {
|
||||
projection := camera_perspective(60, aspect, 0.1, 100)
|
||||
view := linalg.matrix4_look_at_f32({0, 0.75, 2}, {0, 0, 0}, {0, 1, 0})
|
||||
// model = identity: the quad lives at the origin, no pose of its own yet
|
||||
// model = identity: the loader already centered the model at the origin
|
||||
return projection * view
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user