This commit is contained in:
Tuomas Katajisto 2026-10-25 00:55:00 +03:00
parent 0b8c135d11
commit b45554afaa
11 changed files with 746 additions and 481 deletions

View File

@ -228,7 +228,7 @@ wasm_build :: (opts: Build_Options, trueno_opts: Trueno_Build_Options) {
"emcc", "emcc",
"src/platform_specific/main.c", "dist/main.o", "modules/sokol-jai/sokol/gfx/sokol_gfx_wasm_gl_release.a", "modules/sokol-jai/sokol/log/sokol_log_wasm_gl_release.a", "modules/sokol-jai/sokol/audio/sokol_audio_wasm_gl_release.a", "modules/sokol-jai/sokol/time/sokol_time_wasm_gl_release.a", "modules/sokol-jai/sokol/app/sokol_app_wasm_gl_release.a", "modules/sokol-jai/sokol/glue/sokol_glue_wasm_gl_release.a", "modules/sokol-jai/sokol/fetch/sokol_fetch_wasm_gl_release.a", "modules/Fontstash/wasm/fontstash.a", "src/platform_specific/main.c", "dist/main.o", "modules/sokol-jai/sokol/gfx/sokol_gfx_wasm_gl_release.a", "modules/sokol-jai/sokol/log/sokol_log_wasm_gl_release.a", "modules/sokol-jai/sokol/audio/sokol_audio_wasm_gl_release.a", "modules/sokol-jai/sokol/time/sokol_time_wasm_gl_release.a", "modules/sokol-jai/sokol/app/sokol_app_wasm_gl_release.a", "modules/sokol-jai/sokol/glue/sokol_glue_wasm_gl_release.a", "modules/sokol-jai/sokol/fetch/sokol_fetch_wasm_gl_release.a", "modules/Fontstash/wasm/fontstash.a",
"modules/sokol-jai/sokol/stbi/stb_image.a", "modules/sokol-jai/sokol/stbi/stb_image.a",
"modules/sokol-jai/sokol/stbv/stb_vorbis.a", "modules/stb_vorbis/wasm/stb_vorbis.a",
"-o", "dist/index.html", "-o", "dist/index.html",
"-sSTACK_SIZE=10MB", "-sSTACK_SIZE=10MB",
"-sALLOW_MEMORY_GROWTH", "-sALLOW_MEMORY_GROWTH",

View File

@ -265,6 +265,8 @@ STBVorbisError :: enum u32 {
} else #if CPU == .ARM64 { } else #if CPU == .ARM64 {
lib_stb_vorbis :: #library "switch2/stb_vorbis"; lib_stb_vorbis :: #library "switch2/stb_vorbis";
} }
} else #if OS == .WASM {
lib_stb_vorbis :: #library,no_dll "wasm/stb_vorbis";
} else { } else {
#assert false; #assert false;
} }

View File

@ -149,6 +149,8 @@ FOOTER_TEMPLATE :: #string END
} else #if CPU == .ARM64 { } else #if CPU == .ARM64 {
%2 :: #library "switch2/%1"; %2 :: #library "switch2/%1";
} }
} else #if OS == .WASM {
%2 :: #library,no_dll "wasm/%1";
} else { } else {
#assert false; #assert false;
} }

Binary file not shown.

View File

@ -1,4 +1,4 @@
master_volume 0.205032 master_volume 0.05216
music_volume 1 music_volume 1
sfx_volume 1 sfx_volume 1
fullscreen 0 fullscreen 1

View File

@ -92,6 +92,17 @@ mixer_queue_next :: (current_audio: *Audio_Data, next_task: Mixer_Play_Task) {
array_add(*g_mixer.tasks, next_task); array_add(*g_mixer.tasks, next_task);
} }
mixer_stop :: (audio: *Audio_Data) {
mixer_lock(*g_mixer);
defer mixer_unlock(*g_mixer);
for g_mixer.tasks {
if it.audio != audio continue;
if it.queued_next then free(it.queued_next);
free(it.audio_state);
remove it;
}
}
mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) { mixer_get_samples :: (buffer: *float, frame_count: s32, channel_count: s32) {
mixer_lock(*g_mixer); mixer_lock(*g_mixer);
defer mixer_unlock(*g_mixer); defer mixer_unlock(*g_mixer);

View File

@ -90,6 +90,7 @@ Pass :: struct {
kind : Pass_Kind; kind : Pass_Kind;
params : View_Params; params : View_Params;
shadow : Shadow_Params; shadow : Shadow_Params;
frustum : [6]Vector4;
} }
make_pass :: (scene: *Scene, view_index: int, kind: Pass_Kind) -> Pass { make_pass :: (scene: *Scene, view_index: int, kind: Pass_Kind) -> Pass {
@ -110,6 +111,7 @@ make_pass :: (scene: *Scene, view_index: int, kind: Pass_Kind) -> Pass {
camera = pass.view.camera.target + env.sun_direction * 1000; camera = pass.view.camera.target + env.sun_direction * 1000;
view_proj = scene.shadow_view_proj[view_index]; view_proj = scene.shadow_view_proj[view_index];
} }
pass.frustum = frustum_planes(view_proj);
_, inv_view_proj := inverse(view_proj); _, inv_view_proj := inverse(view_proj);
pass.params.view_proj = view_proj.floats; pass.params.view_proj = view_proj.floats;
pass.params.inv_view_proj = inv_view_proj.floats; pass.params.inv_view_proj = inv_view_proj.floats;
@ -131,6 +133,22 @@ make_pass :: (scene: *Scene, view_index: int, kind: Pass_Kind) -> Pass {
return pass; return pass;
} }
frustum_planes :: (m: Matrix4) -> [6]Vector4 {
column :: (m: Matrix4, j: int) -> Vector4 { return .{m.coef[0][j], m.coef[1][j], m.coef[2][j], m.coef[3][j]}; }
x, y, z, w := column(m, 0), column(m, 1), column(m, 2), column(m, 3);
return .[w + x, w - x, w + y, w - y, w + z, w - z];
}
box_in_frustum :: (planes: [6]Vector4, lo: Vector3, hi: Vector3) -> bool {
for planes {
x := ifx it.x > 0 then hi.x else lo.x;
y := ifx it.y > 0 then hi.y else lo.y;
z := ifx it.z > 0 then hi.z else lo.z;
if it.x * x + it.y * y + it.z * z + it.w < 0 return false;
}
return true;
}
apply_uniforms :: (slot: s32, params: *$T) { apply_uniforms :: (slot: s32, params: *$T) {
sg_apply_uniforms(slot, *(sg_range.{ params, size_of(T) })); sg_apply_uniforms(slot, *(sg_range.{ params, size_of(T) }));
} }

File diff suppressed because it is too large Load Diff

View File

@ -92,8 +92,8 @@ layout(binding=4) uniform texture3D probe_atlas; // per brick 32^3 probes, 3 t
layout(binding=2) uniform sampler probe_smp; layout(binding=2) uniform sampler probe_smp;
const float GLOSS_GLOW = 4.0; // how much a full gloss highlight blooms const float GLOSS_GLOW = 4.0; // how much a full gloss highlight blooms
const float SUN_GLOW = 1.5;
const float REFLECTED_GLOW_CURVE = 0.4; // below 1, reflected glow fades slower than reflected colour const float REFLECTED_GLOW_CURVE = 0.4; // below 1, reflected glow fades slower than reflected colour
const float GLASS_PROJECTION = 2.0; // how strongly coloured sunlight paints its colour on
const int PROBE_BRICKS_PER_COLUMN = 16; const int PROBE_BRICKS_PER_COLUMN = 16;
@ -160,20 +160,17 @@ void main() {
if (v_tint > 1.5) albedo = mix(albedo, vec3(1.0, 0.2, 0.2), 0.5); if (v_tint > 1.5) albedo = mix(albedo, vec3(1.0, 0.2, 0.2), 0.5);
else if (v_tint > 0.5) albedo = mix(albedo, vec3(0.2, 1.0, 0.2), 0.5); else if (v_tint > 0.5) albedo = mix(albedo, vec3(0.2, 1.0, 0.2), 0.5);
vec3 sun = v_facing * sun_shadow(v_world, v_normal); vec3 sun = v_facing * sun_shadow(v_world, v_normal);
vec3 lit = sun * mix(0.25, 1.0, v_ao);
vec4 probe = probe_min.w > 0.5 ? probe_light(v_world, v_normal) : vec4(0.0); vec4 probe = probe_min.w > 0.5 ? probe_light(v_world, v_normal) : vec4(0.0);
vec3 shade = v_shade + probe.rgb; vec3 shade = (v_shade + probe.rgb) * mix(0.6, 1.0, v_ao);
vec3 light = mix(shade * mix(0.6, 1.0, v_ao), v_sun, lit); vec3 incoming = v_sun * sun * mix(0.25, 1.0, v_ao);
vec3 color = albedo * light; vec3 color = albedo * (shade + incoming);
// Light through glass paints the glass colour onto what it hits, so the pattern
// shows on dark surfaces too, not only where the albedo can reflect it.
float bright = brightest(sun); float bright = brightest(sun);
if (bright > 0.001) { if (bright > 0.001) {
float tinted = (bright - min(sun.r, min(sun.g, sun.b))) / bright; float tinted = (bright - min(sun.r, min(sun.g, sun.b))) / bright;
color = mix(color, sun * v_sun, min(1.0, tinted * GLASS_PROJECTION)); color += incoming * (1.0 - albedo) * tinted;
} }
float glow = swatch.a * EMISSIVE_GLOW; float glow = swatch.a * EMISSIVE_GLOW + brightest(incoming) * SUN_GLOW;
// Glossier is both stronger and sharper. // Glossier is both stronger and sharper.
if (material.b > 0.0) { if (material.b > 0.0) {

View File

@ -73,23 +73,36 @@ triles_upload :: (scene: *Scene) {
triles.palette_image = sg_make_image(*desc); triles.palette_image = sg_make_image(*desc);
} }
if triles.world_version != scene.world_version world_rebuild(scene);
array_reset_keeping_memory(*triles.instances); array_reset_keeping_memory(*triles.instances);
array_reset_keeping_memory(*triles.batches); array_reset_keeping_memory(*triles.batches);
chunk_count := triles.world_chunks.count;
array_resize(*triles.cut_chunks, scene.views.count * chunk_count, initialize = false);
memset(triles.cut_chunks.data, 0, triles.cut_chunks.count);
draws : [..]Keyed(Instance); draws : [..]Keyed(Instance);
draws.allocator = temp; draws.allocator = temp;
for view, view_index: scene.views { for view, view_index: scene.views {
array_reset_keeping_memory(*draws); array_reset_keeping_memory(*draws);
if view.draw_world { if view.draw_world {
for chunk: scene.chunks for group: chunk.groups for group.instances { cut_chunks := array_view(triles.cut_chunks, view_index * chunk_count, chunk_count);
position := chunk.origin + Vector3.{xx it.x, xx it.y, xx it.z}; for view_cut_chunks(.{xx view_index}) {
cut := trile_cut(.{xx view_index}, group.mesh, position); index := table_find_pointer(*triles.chunk_index, it);
array_add(*draws, .{group.mesh.id, .{position, xx it.orientation, 0, cut}}); if index cut_chunks[index.*] = true;
}
for batch: triles.world_batches {
if !batch.cuttable || !cut_chunks[batch.chunk] continue;
for batch.first..batch.first + batch.count - 1 {
instance := triles.world_instances[it];
instance.cut = trile_cut(.{xx view_index}, .{batch.mesh}, instance.position);
array_add(*draws, .{MAIN_ONLY_KEY | cast(s64) batch.mesh, instance});
}
} }
} }
for scene.triles { for scene.triles {
if it.view.index != view_index continue; if it.view.index != view_index continue;
preview : s64 = ifx it.tint != .NONE then PREVIEW_KEY else 0; preview : s64 = ifx it.tint != .NONE then MAIN_ONLY_KEY else 0;
cut := ifx preview then 0.0 else trile_cut(it.view, it.mesh, it.position); cut := ifx preview then 0.0 else trile_cut(it.view, it.mesh, it.position);
array_add(*draws, .{preview | it.mesh.id, .{it.position, xx it.orientation, xx it.tint, cut}}); array_add(*draws, .{preview | it.mesh.id, .{it.position, xx it.orientation, xx it.tint, cut}});
} }
@ -104,14 +117,13 @@ triles_draw_glass_shadow :: (pass: *Pass) {
apply_uniforms(UB_view_params, *pass.params); apply_uniforms(UB_view_params, *pass.params);
triles.params.show_hidden = 1; triles.params.show_hidden = 1;
apply_uniforms(UB_trile_params, *triles.params); apply_uniforms(UB_trile_params, *triles.params);
for triles.batches { for pass_draws(pass) {
if it.view_index != pass.view_index || it.key & PREVIEW_KEY continue; mesh := slot_get(*triles.meshes, it.mesh);
mesh := slot_get(*triles.meshes, xx it.key);
if !mesh || mesh.index_count == 0 continue; if !mesh || mesh.index_count == 0 continue;
bind : sg_bindings; bind : sg_bindings;
bind.vertex_buffers[0] = mesh.vertices; bind.vertex_buffers[0] = mesh.vertices;
bind.index_buffer = mesh.indices; bind.index_buffer = mesh.indices;
bind.vertex_buffers[1] = triles.stream.buffer; bind.vertex_buffers[1] = it.buffer;
bind.vertex_buffer_offsets[1] = it.first * size_of(Instance); bind.vertex_buffer_offsets[1] = it.first * size_of(Instance);
bind.images[IMG_palette] = triles.palette_image; bind.images[IMG_palette] = triles.palette_image;
bind.samplers[SMP_palette_smp] = samplers.nearest_clamp; bind.samplers[SMP_palette_smp] = samplers.nearest_clamp;
@ -137,15 +149,14 @@ triles_draw :: (pass: *Pass) {
apply_uniforms(UB_trile_fs_params, *fs_params); apply_uniforms(UB_trile_fs_params, *fs_params);
} }
for triles.batches { for pass_draws(pass) {
if it.view_index != pass.view_index || (pass.kind != .MAIN && it.key & PREVIEW_KEY) continue; mesh := slot_get(*triles.meshes, it.mesh);
mesh := slot_get(*triles.meshes, xx (it.key & ~PREVIEW_KEY));
if !mesh || mesh.index_count == 0 continue; if !mesh || mesh.index_count == 0 continue;
bind : sg_bindings; bind : sg_bindings;
bind.vertex_buffers[0] = mesh.vertices; bind.vertex_buffers[0] = mesh.vertices;
bind.index_buffer = mesh.indices; bind.index_buffer = mesh.indices;
bind.vertex_buffers[1] = triles.stream.buffer; bind.vertex_buffers[1] = it.buffer;
bind.vertex_buffer_offsets[1] = it.first * size_of(Instance); bind.vertex_buffer_offsets[1] = it.first * size_of(Instance);
bind.images[IMG_palette] = triles.palette_image; bind.images[IMG_palette] = triles.palette_image;
bind.samplers[SMP_palette_smp] = samplers.nearest_clamp; bind.samplers[SMP_palette_smp] = samplers.nearest_clamp;
@ -176,7 +187,27 @@ Instance :: struct {
cut : float; // 0..1 dissolved away from the camera, still casts shadows cut : float; // 0..1 dissolved away from the camera, still casts shadows
} }
PREVIEW_KEY :: 1 << 32; MAIN_ONLY_KEY :: 1 << 32;
World_Chunk :: struct {
key : Chunk_Key;
origin : Vector3;
}
World_Batch :: struct {
mesh : u32;
chunk : s32;
cuttable : bool;
first : s32;
count : s32;
}
Trile_Draw :: struct {
mesh : u32;
buffer : sg_buffer;
first : s32;
count : s32;
}
triles : struct { triles : struct {
pipeline : sg_pipeline; pipeline : sg_pipeline;
@ -192,6 +223,94 @@ triles : struct {
instances : [..]Instance; instances : [..]Instance;
batches : [..]Batch; batches : [..]Batch;
stream : Stream_Buffer; stream : Stream_Buffer;
world_version : u64;
world_chunks : [..]World_Chunk;
chunk_index : Table(Chunk_Key, s32, chunk_key_hash, chunk_key_compare);
world_batches : [..]World_Batch;
world_instances : [..]Instance;
world_buffer : sg_buffer;
cut_chunks : [..]bool;
}
world_rebuild :: (scene: *Scene) {
triles.world_version = scene.world_version;
array_reset_keeping_memory(*triles.world_chunks);
array_reset_keeping_memory(*triles.world_batches);
array_reset_keeping_memory(*triles.world_instances);
table_reset(*triles.chunk_index);
for scene.chunks array_add(*triles.world_chunks, .{it_index, it.origin});
quick_sort(triles.world_chunks, (a: World_Chunk, b: World_Chunk) -> s64 {
if a.key.x != b.key.x return a.key.x - b.key.x;
if a.key.z != b.key.z return a.key.z - b.key.z;
return a.key.y - b.key.y;
});
Piece :: struct {
key : s64;
chunk : s32;
group : *World_Group;
}
pieces : [..]Piece;
pieces.allocator = temp;
for chunk, index: triles.world_chunks {
table_set(*triles.chunk_index, chunk.key, xx index);
scene_chunk := table_find_pointer(*scene.chunks, chunk.key);
for * scene_chunk.groups array_add(*pieces, .{(cast(s64) it.mesh.id << 32) | index, xx index, it});
}
quick_sort(pieces, (a: Piece, b: Piece) -> s64 { return a.key - b.key; });
occluders := world_occluders();
for pieces {
chunk := triles.world_chunks[it.chunk];
batch := World_Batch.{mesh = it.group.mesh.id, chunk = it.chunk, cuttable = trile_mesh_cuttable(it.group.mesh), first = xx triles.world_instances.count};
for instance: it.group.instances {
if trile_buried(*occluders, chunk.key, instance.x, instance.y, instance.z) continue;
position := chunk.origin + Vector3.{xx instance.x, xx instance.y, xx instance.z};
array_add(*triles.world_instances, .{position, xx instance.orientation, 0, 0});
}
batch.count = xx (triles.world_instances.count - batch.first);
if batch.count array_add(*triles.world_batches, batch);
}
if triles.world_buffer.id sg_destroy_buffer(triles.world_buffer);
triles.world_buffer = .{};
if triles.world_instances.count {
size := triles.world_instances.count * size_of(Instance);
triles.world_buffer = sg_make_buffer(*(sg_buffer_desc.{ data = .{ triles.world_instances.data, xx size } }));
}
}
pass_draws :: (pass: *Pass) -> []Trile_Draw {
draws : [..]Trile_Draw;
draws.allocator = temp;
main := pass.kind == .MAIN;
if pass.view.draw_world && triles.world_batches.count {
chunk_count := triles.world_chunks.count;
visible := NewArray(chunk_count, bool,, temp);
for triles.world_chunks visible[it_index] = box_in_frustum(pass.frustum, it.origin, it.origin + Vector3.{CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE});
cut_chunks := array_view(triles.cut_chunks, pass.view_index * chunk_count, chunk_count);
run : Trile_Draw;
for triles.world_batches {
if !visible[it.chunk] || (main && it.cuttable && cut_chunks[it.chunk]) continue;
if run.count && run.mesh == it.mesh && run.first + run.count == it.first {
run.count += it.count;
continue;
}
if run.count array_add(*draws, run);
run = .{it.mesh, triles.world_buffer, it.first, it.count};
}
if run.count array_add(*draws, run);
}
for triles.batches {
if it.view_index != pass.view_index || (!main && it.key & MAIN_ONLY_KEY) continue;
array_add(*draws, .{xx (it.key & ~MAIN_ONLY_KEY), triles.stream.buffer, it.first, it.count});
}
return draws;
} }
make_mesh :: (mesh: *Mesh, vertices: []Trile_Vertex, indices: []u16) { make_mesh :: (mesh: *Mesh, vertices: []Trile_Vertex, indices: []u16) {

View File

@ -94,19 +94,19 @@ Ui_Vertex :: struct {
trile_mesh_create :: (trile: *Trile) -> Trile_Mesh { trile_mesh_create :: (trile: *Trile) -> Trile_Mesh {
vertices, indices := mesh_trile(trile); vertices, indices := mesh_trile(trile);
mesh := Trile_Mesh.{renderer_trile_mesh_create(vertices, indices)}; mesh := Trile_Mesh.{renderer_trile_mesh_create(vertices, indices)};
set_mesh_cut(mesh, trile.cut); set_mesh_info(mesh, trile.cut, trile_fills_cell(trile));
return mesh; return mesh;
} }
trile_mesh_update :: (mesh: Trile_Mesh, trile: *Trile) { trile_mesh_update :: (mesh: Trile_Mesh, trile: *Trile) {
vertices, indices := mesh_trile(trile); vertices, indices := mesh_trile(trile);
renderer_trile_mesh_update(mesh.id, vertices, indices); renderer_trile_mesh_update(mesh.id, vertices, indices);
set_mesh_cut(mesh, trile.cut); set_mesh_info(mesh, trile.cut, trile_fills_cell(trile));
} }
trile_mesh_destroy :: (mesh: Trile_Mesh) { trile_mesh_destroy :: (mesh: Trile_Mesh) {
renderer_trile_mesh_destroy(mesh.id); renderer_trile_mesh_destroy(mesh.id);
set_mesh_cut(mesh, .NONE); set_mesh_info(mesh, .NONE, false);
} }
swatch_set :: (id: u8, color: [3]u8, reflectivity: u8, gloss: u8, emittance: u8, glass: bool) { swatch_set :: (id: u8, color: [3]u8, reflectivity: u8, gloss: u8, emittance: u8, glass: bool) {
@ -142,6 +142,7 @@ Probe :: struct {
PROBES_PER_CHUNK :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; PROBES_PER_CHUNK :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
world_set_chunk :: (key: Chunk_Key, origin: Vector3, groups: []World_Group) { world_set_chunk :: (key: Chunk_Key, origin: Vector3, groups: []World_Group) {
scene.world_version += 1;
chunk := find_or_add(*scene.chunks, key); chunk := find_or_add(*scene.chunks, key);
chunk.origin = origin; chunk.origin = origin;
for chunk.groups array_free(it.instances); for chunk.groups array_free(it.instances);
@ -156,12 +157,14 @@ world_set_chunk :: (key: Chunk_Key, origin: Vector3, groups: []World_Group) {
world_remove_chunk :: (key: Chunk_Key) { world_remove_chunk :: (key: Chunk_Key) {
chunk := table_find_pointer(*scene.chunks, key); chunk := table_find_pointer(*scene.chunks, key);
if !chunk return; if !chunk return;
scene.world_version += 1;
for chunk.groups array_free(it.instances); for chunk.groups array_free(it.instances);
array_free(chunk.groups); array_free(chunk.groups);
table_remove(*scene.chunks, key); table_remove(*scene.chunks, key);
} }
world_clear :: () { world_clear :: () {
scene.world_version += 1;
for chunk: scene.chunks { for chunk: scene.chunks {
for chunk.groups array_free(it.instances); for chunk.groups array_free(it.instances);
array_free(chunk.groups); array_free(chunk.groups);
@ -221,6 +224,65 @@ trile_cut :: (view: View_Id, mesh: Trile_Mesh, position: Vector3) -> float {
return result; return result;
} }
view_cut_chunks :: (view: View_Id) -> []Chunk_Key {
keys : [..]Chunk_Key;
keys.allocator = temp;
cut := scene.view_cuts[view.index];
if !cut.enabled return keys;
for scene.chunks {
if chunk_column_distance(it.origin, cut.focus) <= CUT_RADIUS array_add(*keys, it_index);
}
unpack :: (bits: s64) -> s32 { return xx (((bits & 0xFFFFF) ^ 0x80000) - 0x80000); }
for scene.cut_fades {
if (it_index >> 60) != view.index continue;
array_add_if_unique(*keys, world_to_chunk_coord(unpack(it_index >> 40), unpack(it_index >> 20), unpack(it_index)), chunk_key_compare);
}
return keys;
}
trile_mesh_cuttable :: (mesh: Trile_Mesh) -> bool {
return mesh_cut(mesh) != .NONE;
}
OCCLUDER_WORDS :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE / 64;
World_Occluders :: Table(Chunk_Key, *[OCCLUDER_WORDS]u64, chunk_key_hash, chunk_key_compare);
world_occluders :: () -> World_Occluders {
occluders : World_Occluders;
occluders.allocator = temp;
for chunk: scene.chunks {
bits := New([OCCLUDER_WORDS]u64,, temp);
for group: chunk.groups {
if !mesh_occludes(group.mesh) continue;
for group.instances {
cell := cast(s64) it.x + cast(s64) it.y * CHUNK_SIZE + cast(s64) it.z * CHUNK_SIZE * CHUNK_SIZE;
bits.*[cell >> 6] |= (cast(u64) 1) << cast(u64) (cell & 63);
}
}
table_set(*occluders, it_index, bits);
}
return occluders;
}
trile_buried :: (occluders: *World_Occluders, key: Chunk_Key, x: s32, y: s32, z: s32) -> bool {
occluded :: (occluders: *World_Occluders, key: Chunk_Key, x: s32, y: s32, z: s32) -> bool {
c := s32.[x, y, z];
k := s32.[key.x, key.y, key.z];
for 0..2 {
if c[it] < 0 { c[it] += CHUNK_SIZE; k[it] -= 1; }
if c[it] >= CHUNK_SIZE { c[it] -= CHUNK_SIZE; k[it] += 1; }
}
bits := table_find_pointer(occluders, .{k[0], k[1], k[2]});
if !bits return false;
cell := cast(s64) c[0] + c[1] * CHUNK_SIZE + c[2] * CHUNK_SIZE * CHUNK_SIZE;
return ((bits.*.*[cell >> 6] >> cast(u64) (cell & 63)) & 1) != 0;
}
return occluded(occluders, key, x - 1, y, z) && occluded(occluders, key, x + 1, y, z)
&& occluded(occluders, key, x, y - 1, z) && occluded(occluders, key, x, y + 1, z)
&& occluded(occluders, key, x, y, z - 1) && occluded(occluders, key, x, y, z + 1);
}
draw_trile :: (view: View_Id, mesh: Trile_Mesh, position: Vector3, orientation: u8, tint := Trile_Tint.NONE) { draw_trile :: (view: View_Id, mesh: Trile_Mesh, position: Vector3, orientation: u8, tint := Trile_Tint.NONE) {
array_add(*scene.triles, .{view, mesh, position, orientation, tint}); array_add(*scene.triles, .{view, mesh, position, orientation, tint});
} }
@ -309,7 +371,9 @@ Trile_Vertex :: struct {
Scene :: struct { Scene :: struct {
chunks : Table(Chunk_Key, Scene_Chunk, chunk_key_hash, chunk_key_compare); chunks : Table(Chunk_Key, Scene_Chunk, chunk_key_hash, chunk_key_compare);
mesh_cuts : [..]Trile_Cut; // by mesh id world_version : u64;
mesh_cuts : [..]Trile_Cut; // by mesh id
mesh_occluders : [..]bool;
views : [..]View; views : [..]View;
view_proj : [..]Matrix4; view_proj : [..]Matrix4;
@ -362,9 +426,34 @@ scene_end_frame :: (width: s32, height: s32, time: float) {
scene : Scene; scene : Scene;
set_mesh_cut :: (mesh: Trile_Mesh, cut: Trile_Cut) { set_mesh_info :: (mesh: Trile_Mesh, cut: Trile_Cut, fills_cell: bool) {
if scene.mesh_cuts.count <= mesh.id array_resize(*scene.mesh_cuts, mesh.id + 1); if scene.mesh_cuts.count <= mesh.id {
array_resize(*scene.mesh_cuts, mesh.id + 1);
array_resize(*scene.mesh_occluders, mesh.id + 1);
}
scene.mesh_cuts[mesh.id] = cut; scene.mesh_cuts[mesh.id] = cut;
scene.mesh_occluders[mesh.id] = fills_cell && cut == .NONE;
scene.world_version += 1;
}
mesh_occludes :: (mesh: Trile_Mesh) -> bool {
if mesh.id >= scene.mesh_occluders.count return false;
return scene.mesh_occluders[mesh.id];
}
trile_fills_cell :: (trile: *Trile) -> bool {
if !trile.is_opaque return false;
for x: 0..15 for y: 0..15 for z: 0..15 {
id := trile.trixels[x][y][z];
if !id || g_swatches[id].glass return false;
}
return true;
}
chunk_column_distance :: (origin: Vector3, point: Vector3) -> float {
dx := max(max(origin.x - point.x, point.x - origin.x - CHUNK_SIZE), 0);
dz := max(max(origin.z - point.z, point.z - origin.z - CHUNK_SIZE), 0);
return sqrt(dx * dx + dz * dz);
} }
mesh_cut :: (mesh: Trile_Mesh) -> Trile_Cut { mesh_cut :: (mesh: Trile_Mesh) -> Trile_Cut {
@ -382,13 +471,16 @@ make_view_cut :: (view: View) -> View_Cut {
to_camera := Vector2.{view.camera.position.x - view.focus.x, view.camera.position.z - view.focus.z}; to_camera := Vector2.{view.camera.position.x - view.focus.x, view.camera.position.z - view.focus.z};
if length(to_camera) > 0.0001 cut.toward_camera = normalize(to_camera); if length(to_camera) > 0.0001 cut.toward_camera = normalize(to_camera);
for chunk: scene.chunks for group: chunk.groups { for chunk: scene.chunks {
if mesh_cut(group.mesh) != .ROOF continue; if chunk.origin.y + CHUNK_SIZE <= cut.floor || chunk_column_distance(chunk.origin, view.focus) > CUT_RADIUS continue;
for group.instances { for group: chunk.groups {
p := chunk.origin + Vector3.{xx it.x, xx it.y, xx it.z}; if mesh_cut(group.mesh) != .ROOF continue;
if p.y <= cut.floor continue; for group.instances {
if length(Vector2.{p.x + 0.5 - view.focus.x, p.z + 0.5 - view.focus.z}) > CUT_RADIUS continue; p := chunk.origin + Vector3.{xx it.x, xx it.y, xx it.z};
cut.height = min(cut.height, p.y); if p.y <= cut.floor continue;
if length(Vector2.{p.x + 0.5 - view.focus.x, p.z + 0.5 - view.focus.z}) > CUT_RADIUS continue;
cut.height = min(cut.height, p.y);
}
} }
} }
return cut; return cut;