Linked repository source
examples/voxel_stream/subsystem/mesh.odin
This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.
Download the original file · Return to the guide
// mesh.odin greedy-meshes one chunk into byte buffers laid out exactly as the
// pinned Godot 4.8-dev5 RenderingServer surface contract consumes them, so a
// chunk crosses the boundary as three whole-buffer byte arrays and one
// mesh_add_surface call with no repacking anywhere.
//
// The layout is earned from the pinned source, not assumed:
//
// - `servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp`,
// `MeshStorage::mesh_add_surface` DEBUG validation (line 263): with
// format = ARRAY_FORMAT_VERTEX | ARRAY_FORMAT_NORMAL | ARRAY_FORMAT_COLOR
// and no compression flags, vertex_data.size() must equal
// (3*4 + 2*2) * vertex_count = 16 * vertex_count exactly, and the
// attribute buffer is 4 bytes (color, one uint32) per vertex.
// - THE VERTEX DATA IS TWO SEQUENTIAL SECTIONS, NOT INTERLEAVED. The GPU
// wiring is `_mesh_surface_generate_vertex_format`
// (mesh_storage.cpp:1296; position stride 12 for the uncompressed 3D
// case at line 1352) and `_mesh_surface_generate_version_for_input_mask`
// (mesh_storage.cpp:1489), which binds the normal attribute to the SAME
// vertex buffer at `offset = position_stride * vertex_count` with stride
// 4 (line 1507). So vertex_data = [positions: 12*vertex_count
// bytes][normals: 4*vertex_count bytes]. (The DEBUG validation above
// only checks the 16*vertex_count TOTAL, which both layouts satisfy —
// an interleaved buffer validates cleanly and renders as garbage. This
// cost one rendered debugging cycle; it is written down so it costs no
// more.)
// - `servers/rendering/rendering_server.cpp`, `_surface_set_data`
// (ARRAY_NORMAL case, line 611): the uncompressed normal is
// `Vector3::octahedron_encode()` scaled by 65535 and clamped into two
// little-endian uint16 (`core/math/vector3.cpp`, octahedron_encode,
// line 90). Because the format carries normals without tangents, the
// ENGINE appends the 4-byte tail pad itself (mesh_storage.cpp line 392);
// the submitted buffer stays exactly 16 * vertex_count bytes.
// - `rendering_server.cpp`, `_surface_set_data`, case ARRAY_COLOR (line
// 708): the attribute byte per channel is `CLAMP(c * 255, 0, 255)` in
// r, g, b, a order — this mesher writes the palette color itself as the
// albedo; every shading decision (sun, ambient, shadows, fog) belongs to
// the engine's lit material on top of it.
// - `mesh_storage.cpp` line 418: the index buffer is uint16 when
// vertex_count <= 65536 and uint32 otherwise. Fragmented chunks can
// exceed that bound: this example refuses them before writing beyond its
// uint16 buffers, rather than switching layouts silently.
//
// Faces between opaque solids are hidden regardless of material. Each chunk
// emits only faces owned by its own solids; aprons supply culling information,
// never geometry belonging to a neighbor. The LOD interior is sampled at
// stride 1, 2 or 4, with an exact one-voxel shell on the four horizontal borders.
// Greedy meshing runs on this effective unit-cell field. Adjacent chunks thus
// agree on border occupancy even at different LODs; the exact shell also closes
// the transition to each chunk's sampled interior. Positions are world-space.
package voxel
import "core:mem"
// LOD_STEPS are the supported mesh strides. LOD 0 renders every voxel
// boundary; LOD 1 and 2 sample the interior every second and fourth cell.
// The horizontal border shell remains exact at every LOD.
LOD_STEPS :: [3]i32{1, 2, 4}
// lod_step maps a LOD index (0..2) onto its voxel stride. Anything else is a
// caller bug and returns 0, which mesh_chunk refuses.
lod_step :: proc(lod: i32) -> i32 {
steps := LOD_STEPS
if lod < 0 || lod >= i32(len(steps)) {
return 0
}
return steps[lod]
}
// MESH_VERTEX_BYTES is the per-vertex size of the vertex buffer: three f32
// position components plus a 4-byte octahedral normal.
MESH_VERTEX_BYTES :: 16
// MESH_ATTRIBUTE_BYTES is the per-vertex size of the attribute buffer: one
// RGBA8 color.
MESH_ATTRIBUTE_BYTES :: 4
// MAX_MESH_VERTICES is a multiple of four below the uint16 index ceiling.
// Fragmented inputs can exceed it and are refused before any out-of-range write.
MAX_MESH_VERTICES :: 65532
MAX_MESH_QUADS :: MAX_MESH_VERTICES / 4
// Pinned ARRAY_FORMAT flag values (bindings/raw,
// raw_godot_class_renderingserver_enum_arrayformat_*): vertex bit 0, normal
// bit 1, color bit 3, index bit 12, and the current surface-format version tag.
SURFACE_FORMAT_VERTEX :: u64(1)
SURFACE_FORMAT_NORMAL :: u64(2)
SURFACE_FORMAT_COLOR :: u64(8)
SURFACE_FORMAT_INDEX :: u64(4096)
SURFACE_FORMAT_CURRENT_VERSION :: u64(34359738368)
// SURFACE_FORMAT is the exact format word every chunk mesh reports.
SURFACE_FORMAT :: u64(SURFACE_FORMAT_CURRENT_VERSION |
SURFACE_FORMAT_VERTEX |
SURFACE_FORMAT_NORMAL |
SURFACE_FORMAT_COLOR |
SURFACE_FORMAT_INDEX)
// Mesh_Data describes one mesh in the exact engine byte layout. mesh_chunk
// returns owned buffers; mesh_chunk_into returns a borrow of a Mesh_Workspace.
// A borrowed mesh expires when that workspace is reused or destroyed.
Mesh_Data :: struct {
owned_buffers: bool,
vertices: []u8,
attributes: []u8,
indices: []u8,
vertex_count: u32,
index_count: u32,
// aabb is min.xyz then max.xyz in world coordinates, the exact six f32
// values the surface Dictionary's "aabb" key needs.
aabb: [6]f32,
quads: i64,
}
// mesh_data_destroy frees the three owned buffers.
mesh_data_destroy :: proc(data: ^Mesh_Data, allocator: mem.Allocator) {
if data == nil {
return
}
if data.owned_buffers && len(data.vertices) > 0 {
delete(data.vertices, allocator)
}
if data.owned_buffers && len(data.attributes) > 0 {
delete(data.attributes, allocator)
}
if data.owned_buffers && len(data.indices) > 0 {
delete(data.indices, allocator)
}
data^ = {}
}
// Mesh_Workspace owns fixed-capacity buffers and scratch storage. A worker
// retains one per result slot, so a published result remains stable until the
// consumer releases that slot. No mesh_chunk_into path allocates or resizes.
Mesh_Workspace :: struct {
vertices, attributes, indices: []u8,
mask: [CHUNK * CHUNK]u32,
field: Chunk,
padded: [PADDED_LEN]u8,
}
// mesh_workspace_create requires a zeroed owner. On failure it frees every
// partial allocation and leaves the owner zeroed.
mesh_workspace_create :: proc(allocator: mem.Allocator, workspace: ^Mesh_Workspace) -> bool {
if workspace == nil || workspace.vertices != nil || workspace.attributes != nil || workspace.indices != nil {
return false
}
workspace.vertices = make([]u8, MAX_MESH_VERTICES * MESH_VERTEX_BYTES, allocator)
workspace.attributes = make([]u8, MAX_MESH_VERTICES * MESH_ATTRIBUTE_BYTES, allocator)
workspace.indices = make([]u8, MAX_MESH_QUADS * 6 * 2, allocator)
if workspace.vertices == nil || workspace.attributes == nil || workspace.indices == nil {
mesh_workspace_destroy(workspace, allocator)
return false
}
return true
}
// mesh_workspace_destroy invalidates every mesh borrowed from this owner.
mesh_workspace_destroy :: proc(workspace: ^Mesh_Workspace, allocator: mem.Allocator) {
if workspace == nil do return
if workspace.vertices != nil do delete(workspace.vertices, allocator)
if workspace.attributes != nil do delete(workspace.attributes, allocator)
if workspace.indices != nil do delete(workspace.indices, allocator)
workspace^ = {}
}
// oct16_encode is the pinned engine's own normal encoder
// (`core/math/vector3.cpp` octahedron_encode plus the CLAMP(res * 65535)
// store): divide by the L1 norm, fold negative z by octahedral mirroring,
// remap into [0, 1], and store two little-endian uint16.
oct16_encode :: proc(nx, ny, nz: f32) -> (u16, u16) {
l1 := abs_f32(nx) + abs_f32(ny) + abs_f32(nz)
if l1 == 0.0 {
l1 = 1.0
}
x := nx / l1
y := ny / l1
z := nz / l1
ox: f32
oy: f32
if z >= 0.0 {
ox = x
oy = y
} else {
ox = (1.0 - abs_f32(y)) * (1.0 if x >= 0.0 else -1.0)
oy = (1.0 - abs_f32(x)) * (1.0 if y >= 0.0 else -1.0)
}
ox = ox * 0.5 + 0.5
oy = oy * 0.5 + 0.5
return f32_to_u16_clamped(ox * 65535.0), f32_to_u16_clamped(oy * 65535.0)
}
abs_f32 :: proc(value: f32) -> f32 {
return value if value >= 0.0 else -value
}
f32_to_u16_clamped :: proc(value: f32) -> u16 {
if value <= 0.0 {
return 0
}
if value >= 65535.0 {
return 65535
}
return u16(value)
}
// FACE_NORMALS holds the six face directions in the face_id order:
// -x, +x, -y, +y, -z, +z.
FACE_NORMALS :: [6][3]f32{
{-1, 0, 0}, {1, 0, 0},
{0, -1, 0}, {0, 1, 0},
{0, 0, -1}, {0, 0, 1},
}
// face_id maps an axis and a direction onto the six-face index above:
// axis 0 = x, 1 = y, 2 = z; a negative direction precedes its positive twin.
face_id :: proc(axis: int, positive: bool) -> int {
return axis * 2 + (1 if positive else 0)
}
// albedo_color is the palette row for one voxel id, written to the RGBA8
// attribute bytes unchanged. The example used to bake a per-face shade into
// these bytes as a cheap depth cue for the old unshaded material; with the
// engine's lit material doing real sun/shadow work, baked shading double-
// darkened faces, so the mesher ships pure albedo.
albedo_color :: proc(id: u8) -> Palette_Entry {
palette := PALETTE
return palette[id]
}
// chunk_is_all_air reports whether the chunk owns any solid cells. Aprons and
// the below-layer half-space never contribute geometry owned by this chunk,
// so an all-air chunk has an empty mesh regardless of its neighbors.
chunk_is_all_air :: proc(chunk: ^Chunk) -> bool {
if chunk == nil {
return true
}
for &byte in chunk^ {
if byte != VOXEL_AIR {
return false
}
}
return true
}
// PADDED is the chunk edge plus one border cell on each side, and PADDED_LEN
// the full padded field. fill_padded materializes it once per mesh job so the
// greedy scans read raw bytes with no bounds or apron branching: y below the
// layer is stone, y at or above CHUNK is air, and the four horizontal borders
// carry the neighbor aprons. Corner cells of the border ring are never read
// (a mask cell is only ever one axis-step outside the chunk) and stay zero.
PADDED :: CHUNK + 2
PADDED_LEN :: PADDED * PADDED * PADDED
padded_index :: proc(x, y, z: int) -> int {
return ((y+1)*PADDED + (z+1))*PADDED + (x+1)
}
// fill_padded builds the padded field from a Mesher_View. Interior rows are
// memcpy'd whole (32 contiguous bytes each); only the border planes are
// gathered per cell.
fill_padded :: proc(field: []u8, view: Mesher_View) -> bool {
if len(field) < PADDED_LEN || view.chunk == nil || view.aprons == nil {
return false
}
stone_plane := PADDED * PADDED
for i in 0..<stone_plane {
field[i] = VOXEL_STONE
field[stone_plane*(CHUNK+1) + i] = VOXEL_AIR
}
aprons := view.aprons
for y in 0..<CHUNK {
ybase := (y+1) * PADDED * PADDED
for z in 0..<CHUNK {
row := ybase + (z+1)*PADDED
mem.copy(raw_data(field[row+1:]), raw_data(view.chunk[(y*CHUNK + z)*CHUNK:][:CHUNK]), CHUNK)
field[row] = aprons[0][y*CHUNK + z]
field[row + CHUNK + 1] = aprons[1][y*CHUNK + z]
}
low_row := ybase
high_row := ybase + (CHUNK+1)*PADDED
for x in 0..<CHUNK {
field[low_row + 1 + x] = aprons[2][y*CHUNK + x]
field[high_row + 1 + x] = aprons[3][y*CHUNK + x]
}
}
return true
}
// Mesher_View bounds one mesher's world lookups: the center chunk plus the
// four horizontal neighbor aprons. Positions outside the chunk route through
// the aprons on x/z; y below the layer is solid stone and y above it is air.
// Bedrock occludes bottom faces but contributes no geometry of its own.
Mesher_View :: struct {
chunk: ^Chunk,
aprons: ^Aprons,
}
// MASK_POSITIVE flags the face whose normal points along the positive axis.
// A boundary between a solid cell and an air cell always faces AWAY from the
// solid cell: solid-below-air reads positive, air-below-solid negative.
MASK_POSITIVE :: u32(0x8000_0000)
// mask_value returns a face only when exactly one side is opaque and that
// solid belongs to the center chunk. Material boundaries inside solids are
// hidden. Outside solids merely occlude the center's faces. `field` is the
// padded field fill_padded produced: every lookup here is in range by
// construction and costs one indexed byte read.
mask_value :: proc(field: []u8, axis, slice, b, c, step: int) -> u32 {
u := (axis + 1) % 3
v := (axis + 2) % 3
pa, pb: [3]int
pa[axis] = slice - step
pa[u], pa[v] = b, c
pb[axis] = slice
pb[u], pb[v] = b, c
va := field[padded_index(pa[0], pa[1], pa[2])]
vb := field[padded_index(pb[0], pb[1], pb[2])]
if (va == VOXEL_AIR) == (vb == VOXEL_AIR) do return 0
if va != VOXEL_AIR {
if slice == 0 do return 0
return u32(va) | MASK_POSITIVE
}
if slice == CHUNK do return 0
return u32(vb)
}
// mesh_chunk is the convenience allocating API for isolated callers. Streaming
// uses mesh_chunk_into with retained workspaces. The returned buffers are owned
// by data and released with mesh_data_destroy using this same allocator.
mesh_chunk :: proc(
view: Mesher_View,
coords: [3]i32,
params: Params,
step: i32,
allocator: mem.Allocator,
data: ^Mesh_Data,
) -> bool {
if data == nil do return false
data^ = {}
workspace: Mesh_Workspace
if !mesh_workspace_create(allocator, &workspace) do return false
defer mesh_workspace_destroy(&workspace, allocator)
borrow: Mesh_Data
if !mesh_chunk_into(view, coords, params, step, &workspace, &borrow) do return false
data^ = borrow
data.vertices = nil
data.attributes = nil
data.indices = nil
data.owned_buffers = true
if len(borrow.vertices) > 0 {
data.vertices = make([]u8, len(borrow.vertices), allocator)
data.attributes = make([]u8, len(borrow.attributes), allocator)
data.indices = make([]u8, len(borrow.indices), allocator)
if data.vertices == nil || data.attributes == nil || data.indices == nil {
mesh_data_destroy(data, allocator)
return false
}
copy(data.vertices, borrow.vertices)
copy(data.attributes, borrow.attributes)
copy(data.indices, borrow.indices)
}
return true
}
// mesh_chunk_into borrows fixed buffers from workspace, with zero allocation.
// Output is cleared on every refusal, including fragmented meshes exceeding
// MAX_MESH_VERTICES. Reusing workspace invalidates the prior output. Each
// border cell remains exact; only cells with x/z in 1..CHUNK-2 use the sampled
// interior. Greedy merging coalesces repeated samples without losing the exact
// shell, at the cost of scanning a unit-cell mask for every LOD.
mesh_chunk_into :: proc(
view: Mesher_View,
coords: [3]i32,
params: Params,
step: i32,
workspace: ^Mesh_Workspace,
data: ^Mesh_Data,
) -> bool {
if data == nil do return false
data^ = {}
if view.chunk == nil || view.aprons == nil || workspace == nil {
return false
}
if params_valid(params) != .None {
return false
}
if coords[1] != 0 {
return false
}
if step != 1 && step != 2 && step != 4 {
return false
}
data^ = {}
origin_x := f32(coords[0] * CHUNK)
origin_z := f32(coords[2] * CHUNK)
if len(workspace.vertices) != MAX_MESH_VERTICES * MESH_VERTEX_BYTES ||
len(workspace.attributes) != MAX_MESH_VERTICES * MESH_ATTRIBUTE_BYTES ||
len(workspace.indices) != MAX_MESH_QUADS * 6 * 2 {
return false
}
// An empty center owns no geometry, even beside solid apron cells.
// Keep this fast path in the canonical mesher so every caller agrees.
if chunk_is_all_air(view.chunk) {
data.aabb = {origin_x, 0, origin_z, origin_x + CHUNK, 0, origin_z + CHUNK}
return true
}
// Sampling is confined to the interior. The border shell is authoritative
// at unit resolution and closes the interior-to-border transitions.
field_view := view
if step > 1 {
stride := int(step)
for y in 0..<CHUNK {
for z in 0..<CHUNK {
for x in 0..<CHUNK {
sx, sy, sz := x, y, z
if x > 0 && x < CHUNK-1 && z > 0 && z < CHUNK-1 {
sx, sy, sz = x / stride * stride, y / stride * stride, z / stride * stride
}
workspace.field[chunk_index(x, y, z)] = view.chunk[chunk_index(sx, sy, sz)]
}
}
}
field_view.chunk = &workspace.field
}
field := workspace.padded[:]
if !fill_padded(field, field_view) {
return false
}
data.vertices = workspace.vertices
data.attributes = workspace.attributes
data.indices = workspace.indices
position_bytes := MESH_VERTEX_BYTES - 4
normal_section := MAX_MESH_VERTICES * position_bytes
mask := workspace.mask[:]
vertex_count: i32 = 0
index_count: i32 = 0
quad_count: i64 = 0
aabb_min := [3]f32{max(f32), max(f32), max(f32)}
aabb_max := [3]f32{min(f32), min(f32), min(f32)}
for axis in 0..<3 {
u := (axis + 1) % 3
v := (axis + 2) % 3
face_normals := FACE_NORMALS
slice := 0
for slice <= CHUNK {
// Build the unit-cell mask from the sampled interior plus exact
// shell. All LODs use the same topology and ownership rules.
for c in 0..<CHUNK {
for b in 0..<CHUNK {
mask[c*CHUNK + b] = mask_value(field, axis, slice, b, c, 1)
}
}
// Greedy-merge the mask into maximal rectangles.
for c in 0..<CHUNK {
for b in 0..<CHUNK {
value := mask[c*CHUNK + b]
if value == 0 {
continue
}
// Width along u, in unit cells.
width := 1
for b + width < CHUNK && mask[c*CHUNK + (b + width)] == value {
width += 1
}
// Height along v, in unit cells.
height := 1
height_done := false
for !height_done && (c + height) < CHUNK {
row_ok := true
for w in 0..<width {
if mask[(c + height)*CHUNK + b + w] != value {
row_ok = false
break
}
}
if row_ok {
height += 1
} else {
height_done = true
}
}
positive := (value & MASK_POSITIVE) != 0
voxel_id := u8(value & 0xFF)
face := face_id(axis, positive)
normal := face_normals[face]
color := albedo_color(voxel_id)
// Emit one quad on the slice plane: the cell offset (b
// along u, c along v) positions the rectangle inside the
// plane, du spans its merged width and dv its height.
base: [3]f32
base[axis] = f32(slice)
base[u] = f32(b)
base[v] = f32(c)
du: [3]f32
du[u] = f32(width)
dv: [3]f32
dv[v] = f32(height)
corners := [4][3]f32{
base,
{base[0] + du[0], base[1] + du[1], base[2] + du[2]},
{base[0] + du[0] + dv[0], base[1] + du[1] + dv[1], base[2] + du[2] + dv[2]},
{base[0] + dv[0], base[1] + dv[1], base[2] + dv[2]},
}
nu, nv := oct16_encode(normal[0], normal[1], normal[2])
if vertex_count + 4 > MAX_MESH_VERTICES {
data^ = {}
return false
}
first := vertex_count
for corner in corners {
world := [3]f32{
corner[0] + origin_x,
corner[1],
corner[2] + origin_z,
}
write_vertex(
data.vertices[int(vertex_count)*position_bytes:][:position_bytes],
data.vertices[normal_section + int(vertex_count)*4:][:4],
world, nu, nv,
)
write_color(
data.attributes[int(vertex_count)*MESH_ATTRIBUTE_BYTES:][:MESH_ATTRIBUTE_BYTES],
color,
)
vertex_count += 1
for k in 0..<3 {
aabb_min[k] = min(aabb_min[k], world[k])
aabb_max[k] = max(aabb_max[k], world[k])
}
}
// Godot front faces wind clockwise viewed from outside.
// du cross dv is always +axis, so reverse the triangles
// only for the positive face; negative faces use 0,1,2.
triangles := [6]i32{0, 1, 2, 0, 2, 3}
if positive do triangles = {0, 2, 1, 0, 3, 2}
for offset, i in triangles {
write_index(data.indices, index_count + i32(i), u16(first + offset))
}
index_count += 6
quad_count += 1
// Clear the merged rectangle.
for h in 0..<height {
for w in 0..<width {
mask[(c + h)*CHUNK + b + w] = 0
}
}
}
}
slice += 1
}
}
// Compact normals in place after the positions section. mem.copy permits
// overlap; the workspace retains the original full allocations for reuse.
position_section := int(vertex_count) * position_bytes
normal_bytes := int(vertex_count) * 4
if normal_bytes > 0 {
mem.copy(raw_data(data.vertices[position_section:]), raw_data(data.vertices[normal_section:]), normal_bytes)
}
data.vertices = data.vertices[:position_section + normal_bytes]
data.attributes = data.attributes[:int(vertex_count)*MESH_ATTRIBUTE_BYTES]
data.indices = data.indices[:int(index_count)*2]
data.vertex_count = u32(vertex_count)
data.index_count = u32(index_count)
data.quads = quad_count
if vertex_count > 0 {
data.aabb = [6]f32{
aabb_min[0], aabb_min[1], aabb_min[2],
aabb_max[0], aabb_max[1], aabb_max[2],
}
} else {
// An empty mesh reports the chunk's own footprint so the surface
// Dictionary's required "aabb" key never carries a degenerate box.
data.aabb = [6]f32{
origin_x, 0.0, origin_z,
origin_x + CHUNK, 0.0, origin_z + CHUNK,
}
}
return true
}
// write_vertex stores one vertex across the engine's two sequential vertex
// sections: the f32 x3 position into the position section (stride 12) and the
// octahedral normal as two little-endian uint16 into the normal section
// (stride 4, at offset position_stride * vertex_count).
write_vertex :: proc(position_sink, normal_sink: []u8, position: [3]f32, nu, nv: u16) {
put_f32(position_sink[0:], position[0])
put_f32(position_sink[4:], position[1])
put_f32(position_sink[8:], position[2])
put_u16(normal_sink[0:], nu)
put_u16(normal_sink[2:], nv)
}
// write_color stores one RGBA8 attribute record.
write_color :: proc(sink: []u8, color: Palette_Entry) {
sink[0] = color.r
sink[1] = color.g
sink[2] = color.b
sink[3] = color.a
}
// write_index stores one little-endian uint16 index at element `at`.
write_index :: proc(sink: []u8, at: i32, value: u16) {
put_u16(sink[int(at)*2:], value)
}
put_f32 :: proc(sink: []u8, value: f32) {
// Chunks are arm64/x86-64 little-endian, and the pinned engine's memcpy
// stores make the byte order explicit.
bits := transmute([4]u8)value
sink[0] = bits[0]
sink[1] = bits[1]
sink[2] = bits[2]
sink[3] = bits[3]
}
put_u16 :: proc(sink: []u8, value: u16) {
bits := transmute([2]u8)value
sink[0] = bits[0]
sink[1] = bits[1]
}
// mesh_digest folds the engine-layout buffers, counts, and AABB. This is a
// deterministic comparison oracle, not a collision-free proof of equality.
mesh_digest :: proc(data: ^Mesh_Data) -> u64 {
if data == nil {
return 0
}
hash := bytes_digest(data.vertices)
hash = (hash ~ bytes_digest(data.attributes)) * 0x100000001b3
hash = (hash ~ bytes_digest(data.indices)) * 0x100000001b3
hash = (hash ~ u64(data.vertex_count)) * 0x100000001b3
hash = (hash ~ u64(data.index_count)) * 0x100000001b3
for value in data.aabb {
hash = (hash ~ u64(transmute(u32)value)) * 0x100000001b3
}
return hash
}
// count_faces counts visible faces without merging — the naive per-face
// baseline the greedy ratio test compares against. `field` is caller-provided
// padded storage; it is rebuilt from the raw view on every call.
count_faces :: proc(view: Mesher_View, field: []u8) -> i64 {
if !fill_padded(field, view) {
return -1
}
count: i64 = 0
for axis in 0..<3 {
for slice in 0..=CHUNK {
for c in 0..<CHUNK {
for b in 0..<CHUNK {
if mask_value(field, axis, slice, b, c, 1) != 0 {
count += 1
}
}
}
}
}
return count
}