Harbor

branch main
showing the latest snapshot on main
mesh.odin 9.1 KB · Plain text
resource/mesh.odin 0644 Raw
package resource

import "core:log"
import "core:math"
import bk "../backend"

MAX_MESHES :: 256

// Legacy 48-byte vertex (position + normal + tex_coord + color).
// Kept for backward compatibility with gen_mesh_custom.
Mesh_Vertex :: struct {
	position:  [3]f32,
	normal:    [3]f32,
	tex_coord: [2]f32,
	color:     [4]f32,
}

// 32-byte vertex (position + normal + tex_coord). Used by procedural generators.
Mesh_Vertex_PNU :: struct {
	position:  [3]f32,
	normal:    [3]f32,
	tex_coord: [2]f32,
}

Internal_Mesh :: struct {
	vertex_buffer: bk.Buffer_Handle,
	index_buffer:  bk.Buffer_Handle,
	vertex_count:  i32,
	index_count:   i32,
	layout:        bk.Vertex_Layout,
	active:        bool,
}

allocate_mesh_slot :: proc(state: ^Resource_State) -> (u32, bool) {
	for i in 1..<u32(MAX_MESHES) {
		if !state.meshes[i].active {
			return i, true
		}
	}
	log.error("gpu/resource: mesh pool exhausted")
	return 0, false
}

// Upload raw vertex data with an explicit layout descriptor.
upload_mesh_raw :: proc(
	state: ^Resource_State,
	b: ^bk.Backend,
	vertex_data: rawptr,
	vertex_data_size: int,
	vertex_count: i32,
	indices: []u32,
	layout: bk.Vertex_Layout,
) -> (id: u32, index_count: i32, ok: bool) {
	slot, slot_ok := allocate_mesh_slot(state)
	if !slot_ok {
		return 0, 0, false
	}

	ib_size := len(indices) * size_of(u32)

	vb, vb_ok := b.create_buffer_staged(
		vertex_data, vertex_data_size,
		{.Vertex},
	)
	if !vb_ok {
		log.error("gpu/resource: failed to create mesh vertex buffer")
		return 0, 0, false
	}

	ib, ib_ok := b.create_buffer_staged(
		raw_data(indices), ib_size,
		{.Index},
	)
	if !ib_ok {
		b.destroy_buffer(vb)
		log.error("gpu/resource: failed to create mesh index buffer")
		return 0, 0, false
	}

	state.meshes[slot] = Internal_Mesh{
		vertex_buffer = vb,
		index_buffer  = ib,
		vertex_count  = vertex_count,
		index_count   = i32(len(indices)),
		layout        = layout,
		active        = true,
	}

	return slot, i32(len(indices)), true
}

// Upload a []Mesh_Vertex slice (legacy 48-byte format).
upload_mesh :: proc(
	state: ^Resource_State,
	b: ^bk.Backend,
	vertices: []Mesh_Vertex,
	indices: []u32,
) -> (id: u32, vertex_count, index_count: i32, ok: bool) {
	mid, ic, upload_ok := upload_mesh_raw(
		state, b,
		raw_data(vertices), len(vertices) * size_of(Mesh_Vertex),
		i32(len(vertices)), indices,
		bk.LAYOUT_POS_NORM_UV_COLOR,
	)
	if !upload_ok {
		return 0, 0, 0, false
	}
	return mid, i32(len(vertices)), ic, true
}

get_mesh_layout :: proc(state: ^Resource_State, id: u32) -> (bk.Vertex_Layout, bool) {
	if id >= MAX_MESHES || !state.meshes[id].active {
		return {}, false
	}
	return state.meshes[id].layout, true
}

get_mesh_info :: proc(state: ^Resource_State, id: u32) -> (vertex_count, index_count: i32, layout: bk.Vertex_Layout, ok: bool) {
	if id >= MAX_MESHES || !state.meshes[id].active {
		return 0, 0, {}, false
	}
	m := &state.meshes[id]
	return m.vertex_count, m.index_count, m.layout, true
}

unload_mesh :: proc(state: ^Resource_State, b: ^bk.Backend, id: u32) {
	if id == 0 || id >= MAX_MESHES || !state.meshes[id].active {
		return
	}
	b.destroy_buffer(state.meshes[id].vertex_buffer)
	b.destroy_buffer(state.meshes[id].index_buffer)
	state.meshes[id].active = false
}

get_mesh_buffers :: proc(state: ^Resource_State, id: u32) -> (vb: bk.Buffer_Handle, ib: bk.Buffer_Handle, index_count: i32, ok: bool) {
	if id >= MAX_MESHES || !state.meshes[id].active {
		return {}, {}, 0, false
	}
	m := &state.meshes[id]
	return m.vertex_buffer, m.index_buffer, m.index_count, true
}

shutdown_meshes :: proc(state: ^Resource_State, b: ^bk.Backend) {
	for i in 0..<u32(MAX_MESHES) {
		if state.meshes[i].active {
			b.destroy_buffer(state.meshes[i].vertex_buffer)
			b.destroy_buffer(state.meshes[i].index_buffer)
			state.meshes[i].active = false
		}
	}
}

// --- Procedural Geometry Generators ---
// Produce LAYOUT_POS_NORM_UV (32 bytes) — no vertex color.
// 3D shaders get color from material push constants.

gen_cube_geometry :: proc(w, h, l: f32) -> ([]Mesh_Vertex_PNU, []u32) {
	hw := w * 0.5
	hh := h * 0.5
	hl := l * 0.5

	vertices := make([]Mesh_Vertex_PNU, 24)
	indices := make([]u32, 36)

	// Front face (+Z)
	vertices[0]  = {{-hw, -hh,  hl}, { 0,  0,  1}, {0, 1}}
	vertices[1]  = {{ hw, -hh,  hl}, { 0,  0,  1}, {1, 1}}
	vertices[2]  = {{ hw,  hh,  hl}, { 0,  0,  1}, {1, 0}}
	vertices[3]  = {{-hw,  hh,  hl}, { 0,  0,  1}, {0, 0}}

	// Back face (-Z)
	vertices[4]  = {{ hw, -hh, -hl}, { 0,  0, -1}, {0, 1}}
	vertices[5]  = {{-hw, -hh, -hl}, { 0,  0, -1}, {1, 1}}
	vertices[6]  = {{-hw,  hh, -hl}, { 0,  0, -1}, {1, 0}}
	vertices[7]  = {{ hw,  hh, -hl}, { 0,  0, -1}, {0, 0}}

	// Top face (+Y)
	vertices[8]  = {{-hw,  hh,  hl}, { 0,  1,  0}, {0, 1}}
	vertices[9]  = {{ hw,  hh,  hl}, { 0,  1,  0}, {1, 1}}
	vertices[10] = {{ hw,  hh, -hl}, { 0,  1,  0}, {1, 0}}
	vertices[11] = {{-hw,  hh, -hl}, { 0,  1,  0}, {0, 0}}

	// Bottom face (-Y)
	vertices[12] = {{-hw, -hh, -hl}, { 0, -1,  0}, {0, 1}}
	vertices[13] = {{ hw, -hh, -hl}, { 0, -1,  0}, {1, 1}}
	vertices[14] = {{ hw, -hh,  hl}, { 0, -1,  0}, {1, 0}}
	vertices[15] = {{-hw, -hh,  hl}, { 0, -1,  0}, {0, 0}}

	// Right face (+X)
	vertices[16] = {{ hw, -hh,  hl}, { 1,  0,  0}, {0, 1}}
	vertices[17] = {{ hw, -hh, -hl}, { 1,  0,  0}, {1, 1}}
	vertices[18] = {{ hw,  hh, -hl}, { 1,  0,  0}, {1, 0}}
	vertices[19] = {{ hw,  hh,  hl}, { 1,  0,  0}, {0, 0}}

	// Left face (-X)
	vertices[20] = {{-hw, -hh, -hl}, {-1,  0,  0}, {0, 1}}
	vertices[21] = {{-hw, -hh,  hl}, {-1,  0,  0}, {1, 1}}
	vertices[22] = {{-hw,  hh,  hl}, {-1,  0,  0}, {1, 0}}
	vertices[23] = {{-hw,  hh, -hl}, {-1,  0,  0}, {0, 0}}

	// Indices (two triangles per face, CCW winding)
	for face in 0..<u32(6) {
		base := face * 4
		idx := face * 6
		indices[idx + 0] = base + 0
		indices[idx + 1] = base + 1
		indices[idx + 2] = base + 2
		indices[idx + 3] = base + 0
		indices[idx + 4] = base + 2
		indices[idx + 5] = base + 3
	}

	return vertices, indices
}

gen_sphere_geometry :: proc(radius: f32, rings, slices: i32) -> ([]Mesh_Vertex_PNU, []u32) {
	vert_count := int((rings + 1) * (slices + 1))
	idx_count := int(rings * slices * 6)

	vertices := make([]Mesh_Vertex_PNU, vert_count)
	indices := make([]u32, idx_count)

	vi := 0
	ii := 0

	for ring in 0..=rings {
		phi := f32(ring) * math.PI / f32(rings)
		sp := math.sin(phi)
		cp := math.cos(phi)

		for slice in 0..=slices {
			theta := f32(slice) * 2.0 * math.PI / f32(slices)
			st := math.sin(theta)
			ct := math.cos(theta)

			nx := ct * sp
			ny := cp
			nz := st * sp

			vertices[vi] = {
				position  = {nx * radius, ny * radius, nz * radius},
				normal    = {nx, ny, nz},
				tex_coord = {f32(slice) / f32(slices), f32(ring) / f32(rings)},
			}
			vi += 1
		}
	}

	for ring in 0..<rings {
		for slice in 0..<slices {
			a := u32(ring * (slices + 1) + slice)
			b := a + u32(slices) + 1

			indices[ii + 0] = a
			indices[ii + 1] = a + 1
			indices[ii + 2] = b
			indices[ii + 3] = a + 1
			indices[ii + 4] = b + 1
			indices[ii + 5] = b
			ii += 6
		}
	}

	return vertices, indices
}

gen_plane_geometry :: proc(w, l: f32, res_x, res_z: i32) -> ([]Mesh_Vertex_PNU, []u32) {
	vert_count := int((res_x + 1) * (res_z + 1))
	idx_count := int(res_x * res_z * 6)

	vertices := make([]Mesh_Vertex_PNU, vert_count)
	indices := make([]u32, idx_count)

	vi := 0
	ii := 0

	for z in 0..=res_z {
		for x in 0..=res_x {
			fx := f32(x) / f32(res_x)
			fz := f32(z) / f32(res_z)

			vertices[vi] = {
				position  = {(fx - 0.5) * w, 0, (fz - 0.5) * l},
				normal    = {0, 1, 0},
				tex_coord = {fx, fz},
			}
			vi += 1
		}
	}

	for z in 0..<res_z {
		for x in 0..<res_x {
			a := u32(z * (res_x + 1) + x)
			b := a + u32(res_x) + 1

			indices[ii + 0] = a
			indices[ii + 1] = b
			indices[ii + 2] = a + 1
			indices[ii + 3] = a + 1
			indices[ii + 4] = b
			indices[ii + 5] = b + 1
			ii += 6
		}
	}

	return vertices, indices
}

gen_grid_geometry :: proc(slices: i32, spacing, thickness: f32) -> ([]Mesh_Vertex_PNU, []u32) {
	line_count := max(slices + 1, 0) * 2
	vertices := make([]Mesh_Vertex_PNU, int(line_count * 4))
	indices := make([]u32, int(line_count * 12))

	half := f32(slices) * spacing * 0.5
	half_t := thickness * 0.5
	vi := 0
	ii := 0

	add_quad :: proc(vertices: []Mesh_Vertex_PNU, indices: []u32, vi, ii: ^int, x0, z0, x1, z1: f32) {
		base := u32(vi^)
		vertices[vi^ + 0] = {{x0, 0, z0}, {0, 1, 0}, {0, 0}}
		vertices[vi^ + 1] = {{x1, 0, z0}, {0, 1, 0}, {1, 0}}
		vertices[vi^ + 2] = {{x1, 0, z1}, {0, 1, 0}, {1, 1}}
		vertices[vi^ + 3] = {{x0, 0, z1}, {0, 1, 0}, {0, 1}}
		// Emit both windings so the debug grid remains visible above and below with back-face culling.
		indices[ii^ + 0]  = base + 0
		indices[ii^ + 1]  = base + 2
		indices[ii^ + 2]  = base + 1
		indices[ii^ + 3]  = base + 0
		indices[ii^ + 4]  = base + 3
		indices[ii^ + 5]  = base + 2
		indices[ii^ + 6]  = base + 0
		indices[ii^ + 7]  = base + 1
		indices[ii^ + 8]  = base + 2
		indices[ii^ + 9]  = base + 0
		indices[ii^ + 10] = base + 2
		indices[ii^ + 11] = base + 3
		vi^ += 4
		ii^ += 12
	}

	for i in 0..=slices {
		x := -half + f32(i) * spacing
		add_quad(vertices, indices, &vi, &ii, x - half_t, -half, x + half_t, half)
		z := -half + f32(i) * spacing
		add_quad(vertices, indices, &vi, &ii, -half, z - half_t, half, z + half_t)
	}

	return vertices, indices
}