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 "game:game"
|
||||||
import "sindri:base"
|
import "sindri:base"
|
||||||
|
import "sindri:core"
|
||||||
import "sindri:platform"
|
import "sindri:platform"
|
||||||
|
|
||||||
VERSION :: #config(VERSION, "dev")
|
VERSION :: #config(VERSION, "dev")
|
||||||
@@ -41,6 +42,7 @@ main :: proc() {
|
|||||||
// Initialize GPU resources
|
// Initialize GPU resources
|
||||||
platform.instance_init(settings)
|
platform.instance_init(settings)
|
||||||
defer platform.instance_destroy()
|
defer platform.instance_destroy()
|
||||||
|
load_test_model()
|
||||||
|
|
||||||
api.init_once()
|
api.init_once()
|
||||||
|
|
||||||
@@ -84,3 +86,13 @@ test :: proc(e: ^base.Key_Press_Event, _: rawptr) -> bool {
|
|||||||
fmt.println("main: @@@@@@@@@@@", e)
|
fmt.println("main: @@@@@@@@@@@", e)
|
||||||
return true
|
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:fmt"
|
||||||
import "core:math"
|
import "core:math"
|
||||||
import "core:math/linalg"
|
import "core:math/linalg"
|
||||||
|
import "core:os"
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
|
|
||||||
import "wgpu:wgpu"
|
import "wgpu:wgpu"
|
||||||
@@ -19,7 +20,7 @@ Vec3 :: [3]f32
|
|||||||
Vertex :: struct #align (4) {
|
Vertex :: struct #align (4) {
|
||||||
pos: Vec3,
|
pos: Vec3,
|
||||||
col: Vec3,
|
col: Vec3,
|
||||||
uv: Vec2,
|
uv: Vec2, // texture coordinate
|
||||||
}
|
}
|
||||||
|
|
||||||
Camera_Uniform :: struct {
|
Camera_Uniform :: struct {
|
||||||
@@ -245,6 +246,21 @@ frame :: proc "c" () {
|
|||||||
wgpu.SurfacePresent(state.surface)
|
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
|
// Private functions
|
||||||
|
|
||||||
@@ -399,9 +415,6 @@ on_device :: proc "c" (
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Buffer shape
|
// Buffer shape
|
||||||
|
|
||||||
// Fill vertex/index buffers
|
|
||||||
fill_buffers_with_quad()
|
|
||||||
|
|
||||||
vertex_attributes := [?]wgpu.VertexAttribute {
|
vertex_attributes := [?]wgpu.VertexAttribute {
|
||||||
{
|
{
|
||||||
format = .Float32x3,
|
format = .Float32x3,
|
||||||
@@ -562,50 +575,6 @@ create_depth_texture :: proc() {
|
|||||||
state.depth_texture_view = wgpu.TextureCreateView(state.depth_texture, nil)
|
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")
|
@(private = "file")
|
||||||
framebuffer_aspect :: proc() -> f32 {
|
framebuffer_aspect :: proc() -> f32 {
|
||||||
width, height := os_get_framebuffer_size()
|
width, height := os_get_framebuffer_size()
|
||||||
@@ -638,7 +607,7 @@ camera_perspective :: proc(
|
|||||||
camera_mvp :: proc(aspect: f32) -> linalg.Matrix4f32 {
|
camera_mvp :: proc(aspect: f32) -> linalg.Matrix4f32 {
|
||||||
projection := camera_perspective(60, aspect, 0.1, 100)
|
projection := camera_perspective(60, aspect, 0.1, 100)
|
||||||
view := linalg.matrix4_look_at_f32({0, 0.75, 2}, {0, 0, 0}, {0, 1, 0})
|
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
|
return projection * view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user