#scope_file Pool :: #import "Pool"; CHUNK_SIZE :: 32; Current_World :: struct { world : World; pool : Pool.Pool; // A memory pool to allocate stuff for the lifetime of this level being active. For example RDMs. valid : bool = false; }; current_world : Current_World; #scope_export Chunk_Key :: struct { x: s32; y: s32; z: s32; } operator == :: (a: Chunk_Key, b: Chunk_Key) -> bool { return a.x == b.x && a.y == b.y && a.z == b.z; } chunk_key_hash :: (key: Chunk_Key) -> u32 { h := cast(u32) 2166136261; h = (h ^ (cast,no_check(u32) key.x)) * 16777619; h = (h ^ (cast,no_check(u32) key.y)) * 16777619; h = (h ^ (cast,no_check(u32) key.z)) * 16777619; return h; } chunk_key_compare :: (a: Chunk_Key, b: Chunk_Key) -> bool { return a.x == b.x && a.y == b.y && a.z == b.z; } Trile_Instance :: struct { x: u8; // local position within chunk (0..31) y: u8; z: u8; orientation: u8; // 0..23, index into cube rotation group } Chunk_Trile_Group :: struct { trile_name: string; instances: [..]Trile_Instance; is_buried: [..]bool; // Runtime-only, parallel to instances. Not serialized. } Chunk :: struct { coord: Chunk_Key; groups: [..]Chunk_Trile_Group; sh_probe_grid: sg_image; // 192x4096 RGBA16F 2D texture (2 probes/trile/axis) sh_valid: bool; sh_dirty: bool; } Editor_Note :: struct { position : Chunk_Key; text : string; } RDM_DEFAULT_SIZE :: 128; Rdm_Instance_Override :: struct { x : s32; y : s32; z : s32; rdm_enabled : bool; rdm_size : s32; // side in px; 0 means legacy — treat as RDM_DEFAULT_SIZE. } // Per-world entry of the global RDM atlas. atlas_rect is normalized UV (x, y, w, h). Rdm_Atlas_Entry :: struct { x, y, z : s32; atlas_rect : Vector4; } World :: struct { name : string; conf : World_Config; chunks : Table(Chunk_Key, Chunk, chunk_key_hash, chunk_key_compare); emitter_instances : [..]Particle_Emitter_Instance; notes : [..]Editor_Note; rdm_overrides : [..]Rdm_Instance_Override; rdm_lookup : [..]Rdm_Atlas_Entry; // populated by bake (Step 5) and by loader (Step 6) } rdm_get_atlas_rect :: (world: *World, x: s32, y: s32, z: s32) -> (Vector4, bool) { for world.rdm_lookup { if it.x == x && it.y == y && it.z == z then return it.atlas_rect, true; } return .{}, false; } is_rdm_instance_enabled :: (world: *World, x: s32, y: s32, z: s32) -> bool { for world.rdm_overrides { if it.x == x && it.y == y && it.z == z then return it.rdm_enabled; } return false; } set_rdm_instance_enabled :: (world: *World, x: s32, y: s32, z: s32, enabled: bool) { for *world.rdm_overrides { if it.x == x && it.y == y && it.z == z { if !enabled { array_ordered_remove_by_index(*world.rdm_overrides, it_index); } else { it.rdm_enabled = true; if it.rdm_size == 0 then it.rdm_size = RDM_DEFAULT_SIZE; } return; } } if enabled { array_add(*world.rdm_overrides, .{x=x, y=y, z=z, rdm_enabled=true, rdm_size=RDM_DEFAULT_SIZE}); } } get_rdm_instance_size :: (world: *World, x: s32, y: s32, z: s32) -> s32 { for world.rdm_overrides { if it.x == x && it.y == y && it.z == z { if !it.rdm_enabled then return 0; return ifx it.rdm_size > 0 then it.rdm_size else RDM_DEFAULT_SIZE; } } return 0; } set_rdm_instance_size :: (world: *World, x: s32, y: s32, z: s32, size: s32) { for *world.rdm_overrides { if it.x == x && it.y == y && it.z == z { it.rdm_size = size; return; } } } RDM_SIZE_LADDER :: s32.[32, 64, 128, 256, 512, 1024, 2048, 4096]; rdm_cycle_size :: (cur: s32, dir: s32) -> s32 { idx : s32 = 2; for RDM_SIZE_LADDER { if it == cur { idx = cast(s32) it_index; break; } } idx += dir; if idx < 0 then idx = 0; if idx >= cast(s32) RDM_SIZE_LADDER.count then idx = cast(s32) RDM_SIZE_LADDER.count - 1; return RDM_SIZE_LADDER[idx]; } // Convert a world-space integer position to chunk coordinate. world_to_chunk_coord :: (wx: s32, wy: s32, wz: s32) -> Chunk_Key { return .{ x = floor_div(wx, CHUNK_SIZE), y = floor_div(wy, CHUNK_SIZE), z = floor_div(wz, CHUNK_SIZE), }; } // Convert a world-space integer position to local position within its chunk (0..31). world_to_local :: (wx: s32, wy: s32, wz: s32) -> (u8, u8, u8) { return cast(u8) floor_mod(wx, CHUNK_SIZE), cast(u8) floor_mod(wy, CHUNK_SIZE), cast(u8) floor_mod(wz, CHUNK_SIZE); } // Convert chunk coord + local position back to world position. chunk_local_to_world :: (chunk: Chunk_Key, lx: u8, ly: u8, lz: u8) -> (s32, s32, s32) { return chunk.x * CHUNK_SIZE + cast(s32) lx, chunk.y * CHUNK_SIZE + cast(s32) ly, chunk.z * CHUNK_SIZE + cast(s32) lz; } // Floor division that handles negatives correctly (rounds toward -infinity). floor_div :: (a: s32, b: s32) -> s32 { d := a / b; r := a % b; if (r != 0) && ((r ^ b) < 0) then d -= 1; return d; } // Floor modulo that always returns a non-negative result. floor_mod :: (a: s32, b: s32) -> s32 { r := a % b; if (r != 0) && ((r ^ b) < 0) then r += b; return r; } nworld :: (name: string) { unload_current_world(); current_world.world = .{}; current_world.world.name = sprint("%", name); current_world.valid = true; } @Command; lworld :: (name: string) { load_world(name); } @Command; init_world_system :: () { Pool.set_allocators(*current_world.pool); } unload_current_world :: () { if !current_world.valid then return; rdm_loader_cancel_all(); for *chunk: current_world.world.chunks { for *group: chunk.groups { array_free(group.instances); array_free(group.is_buried); } array_free(chunk.groups); } deinit(*current_world.world.chunks); array_free(current_world.world.rdm_lookup); Pool.reset(*current_world.pool); current_world.valid = false; } set_loaded_world :: (world: World) { unload_current_world(); current_world.world = world; current_world.valid = true; resolve_emitter_definitions(*current_world.world); for *chunk: current_world.world.chunks { recompute_buried_for_chunk(*current_world.world, chunk); } } is_cell_opaque :: (name: string) -> bool { if name.count == 0 return false; t, ok := get_trile(name); if !ok return false; return t.is_opaque; } trile_at_world :: (world: *World, wx: s32, wy: s32, wz: s32) -> string { key := world_to_chunk_coord(wx, wy, wz); chunk := table_find_pointer(*world.chunks, key); if !chunk return ""; lx, ly, lz := world_to_local(wx, wy, wz); for *group: chunk.groups { for inst: group.instances { if inst.x == lx && inst.y == ly && inst.z == lz { return group.trile_name; } } } return ""; } BURIED_DIRS :: s32.[ 1,0,0, -1,0,0, 0,1,0, 0,-1,0, 0,0,1, 0,0,-1 ]; compute_buried_at_world :: (world: *World, wx: s32, wy: s32, wz: s32) -> bool { for axis: 0..5 { nwx := wx + BURIED_DIRS[axis*3+0]; nwy := wy + BURIED_DIRS[axis*3+1]; nwz := wz + BURIED_DIRS[axis*3+2]; if !is_cell_opaque(trile_at_world(world, nwx, nwy, nwz)) return false; } return true; } recompute_buried_for_chunk :: (world: *World, chunk: *Chunk) { // Build an O(1) local lookup grid (32^3 trile names) for this chunk so the // common in-chunk neighbor case is a single index lookup. Cross-chunk // neighbors fall back to trile_at_world. GRID :: CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; grid: [GRID] string; grid_data := grid; for *group: chunk.groups { for inst: group.instances { idx := cast(int)inst.x + CHUNK_SIZE * (cast(int)inst.y + CHUNK_SIZE * cast(int)inst.z); grid_data[idx] = group.trile_name; } } for *group: chunk.groups { array_resize(*group.is_buried, group.instances.count); for inst, i: group.instances { wx, wy, wz := chunk_local_to_world(chunk.coord, inst.x, inst.y, inst.z); buried := true; for axis: 0..5 { nwx := wx + BURIED_DIRS[axis*3+0]; nwy := wy + BURIED_DIRS[axis*3+1]; nwz := wz + BURIED_DIRS[axis*3+2]; neighbor_name: string; nkey := world_to_chunk_coord(nwx, nwy, nwz); if nkey == chunk.coord { nlx, nly, nlz := world_to_local(nwx, nwy, nwz); nidx := cast(int)nlx + CHUNK_SIZE * (cast(int)nly + CHUNK_SIZE * cast(int)nlz); neighbor_name = grid_data[nidx]; } else { neighbor_name = trile_at_world(world, nwx, nwy, nwz); } if !is_cell_opaque(neighbor_name) { buried = false; break; } } group.is_buried[i] = buried; } } } recompute_buried_at_cell :: (world: *World, wx: s32, wy: s32, wz: s32) { key := world_to_chunk_coord(wx, wy, wz); chunk := table_find_pointer(*world.chunks, key); if !chunk return; lx, ly, lz := world_to_local(wx, wy, wz); for *group: chunk.groups { for inst, i: group.instances { if inst.x == lx && inst.y == ly && inst.z == lz { if group.is_buried.count != group.instances.count array_resize(*group.is_buried, group.instances.count); group.is_buried[i] = compute_buried_at_world(world, wx, wy, wz); return; } } } } invalidate_buried_around :: (world: *World, wx: s32, wy: s32, wz: s32) { recompute_buried_at_cell(world, wx, wy, wz); for axis: 0..5 { recompute_buried_at_cell(world, wx + BURIED_DIRS[axis*3+0], wy + BURIED_DIRS[axis*3+1], wz + BURIED_DIRS[axis*3+2]); } } resolve_emitter_definitions :: (world: *World) { for *inst: world.emitter_instances { inst.definition = get_emitter_def(inst.definition_name); } } clear_world :: () { if !current_world.valid then return; for *chunk: current_world.world.chunks { for *group: chunk.groups { array_free(group.instances); array_free(group.is_buried); } array_free(chunk.groups); } deinit(*current_world.world.chunks); current_world.world.chunks = .{}; } @Command get_current_world :: () -> *Current_World { return *current_world; } // --- Binary serialization (.world format) --- WORLD_MAGIC :: u32.[0x4C575254][0]; // "TRWL" as little-endian u32 WORLD_VERSION :: cast(u16) 3; World_Json :: struct { version : s32; name : string; config : World_Json_Config; chunks : [..]World_Json_Chunk; emitters : [..]World_Json_Emitter; notes : [..]World_Json_Note; rdm_overrides : [..]World_Json_Rdm_Override; } World_Json_Config :: struct { skyBase : [3]float; skyTop : [3]float; sunDisk : [3]float; horizonHalo : [3]float; sunHalo : [3]float; sunLightColor : [3]float; sunPosition : [3]float; sunIntensity : float; skyIntensity : float; hasClouds : s32; planeHeight : float; animatePlaneHeight : s32; waterColor : [3]float; deepColor : [3]float; waterShininess : float; } World_Json_Chunk :: struct { x : s32; y : s32; z : s32; offset : s32; size : s32; } World_Json_Emitter :: struct { definition_name : string; position : [3]float; offset : [3]float; } World_Json_Note :: struct { text : string; position : [3]s32; } World_Json_Rdm_Override :: struct { x : s32; y : s32; z : s32; rdm_enabled : bool; rdm_size : s32; } // World_Config serialized as a fixed-size binary blob. // We serialize it field-by-field to avoid padding issues. World_Config_Binary :: struct { sky_base: [3]float; sky_top: [3]float; sun_disk: [3]float; horizon_halo: [3]float; sun_halo: [3]float; sun_light_color: [3]float; sun_position: [3]float; sun_intensity: float; sky_intensity: float; has_clouds: s32; plane_height: float; animate_plane_height: s32; water_color: [3]float; deep_color: [3]float; water_shininess: float; } world_config_to_binary :: (conf: *World_Config) -> World_Config_Binary { b: World_Config_Binary; b.sky_base = conf.skyBase.component; b.sky_top = conf.skyTop.component; b.sun_disk = conf.sunDisk.component; b.horizon_halo = conf.horizonHalo.component; b.sun_halo = conf.sunHalo.component; b.sun_light_color = conf.sunLightColor.component; b.sun_position = conf.sunPosition.component; b.sun_intensity = conf.sunIntensity; b.sky_intensity = conf.skyIntensity; b.has_clouds = conf.hasClouds; b.plane_height = conf.planeHeight; b.animate_plane_height = conf.animatePlaneHeight; b.water_color = conf.waterColor.component; b.deep_color = conf.deepColor.component; b.water_shininess = conf.waterShininess; return b; } world_config_from_binary :: (b: *World_Config_Binary) -> World_Config { conf: World_Config; conf.skyBase.component = b.sky_base; conf.skyTop.component = b.sky_top; conf.sunDisk.component = b.sun_disk; conf.horizonHalo.component = b.horizon_halo; conf.sunHalo.component = b.sun_halo; conf.sunLightColor.component = b.sun_light_color; conf.sunPosition.component = b.sun_position; conf.sunIntensity = b.sun_intensity; conf.skyIntensity = b.sky_intensity; conf.hasClouds = b.has_clouds; conf.planeHeight = b.plane_height; conf.animatePlaneHeight = b.animate_plane_height; conf.waterColor.component = b.water_color; conf.deepColor.component = b.deep_color; conf.waterShininess = b.water_shininess; return conf; } write_bytes :: (builder: *String_Builder, data: *void, count: s64) { s: string; s.data = data; s.count = count; append(builder, s); } write_value :: (builder: *String_Builder, value: $T) { v := value; write_bytes(builder, *v, size_of(T)); } read_value :: (data: []u8, cursor: *s64, $T: Type) -> T { result: T; assert(cursor.* + size_of(T) <= data.count, "read_value: out of bounds"); memcpy(*result, data.data + cursor.*, size_of(T)); cursor.* += size_of(T); return result; } read_string :: (data: []u8, cursor: *s64, len: s64) -> string { assert(cursor.* + len <= data.count, "read_string: out of bounds"); result := sprint("%", string.{count = len, data = data.data + cursor.*}); cursor.* += len; return result; } sworld :: () { if !current_world.valid { log_error("Cannot save: no world loaded"); return; } #if OS != .WASM { file :: #import "File"; name := current_world.world.name; dir := tprint("%/worlds/%", GAME_RESOURCES_DIR, name); file.make_directory_if_it_does_not_exist(dir, recursive = true); json_data, bin_data := save_world(*current_world.world); file.write_entire_file(tprint("%/world.json", dir), json_data); file.write_entire_file(tprint("%/chunks.bin", dir), bin_data); log_info("Saved world '%' (json=% bytes, bin=% bytes)", name, json_data.count, bin_data.count); } } @Command world_config_to_json :: (conf: *World_Config) -> World_Json_Config { jc: World_Json_Config; jc.skyBase = conf.skyBase.component; jc.skyTop = conf.skyTop.component; jc.sunDisk = conf.sunDisk.component; jc.horizonHalo = conf.horizonHalo.component; jc.sunHalo = conf.sunHalo.component; jc.sunLightColor = conf.sunLightColor.component; jc.sunPosition = conf.sunPosition.component; jc.sunIntensity = conf.sunIntensity; jc.skyIntensity = conf.skyIntensity; jc.hasClouds = conf.hasClouds; jc.planeHeight = conf.planeHeight; jc.animatePlaneHeight = conf.animatePlaneHeight; jc.waterColor = conf.waterColor.component; jc.deepColor = conf.deepColor.component; jc.waterShininess = conf.waterShininess; return jc; } world_config_from_json :: (jc: *World_Json_Config) -> World_Config { conf: World_Config; conf.skyBase.component = jc.skyBase; conf.skyTop.component = jc.skyTop; conf.sunDisk.component = jc.sunDisk; conf.horizonHalo.component = jc.horizonHalo; conf.sunHalo.component = jc.sunHalo; conf.sunLightColor.component = jc.sunLightColor; conf.sunPosition.component = jc.sunPosition; conf.sunIntensity = jc.sunIntensity; conf.skyIntensity = jc.skyIntensity; conf.hasClouds = jc.hasClouds; conf.planeHeight = jc.planeHeight; conf.animatePlaneHeight = jc.animatePlaneHeight; conf.waterColor.component = jc.waterColor; conf.deepColor.component = jc.deepColor; conf.waterShininess = ifx jc.waterShininess > 0 then jc.waterShininess else 64.0; return conf; } save_world :: (world: *World) -> (json: string, chunks_bin: string) { bin_builder: String_Builder; Chunk_Save_Entry :: struct { coord: Chunk_Key; offset: s32; size: s32; } chunk_entries: [..]Chunk_Save_Entry; chunk_entries.allocator = temp; running_offset: s32 = 0; for chunk: world.chunks { if chunk.groups.count == 0 then continue; chunk_builder: String_Builder; chunk_builder.allocator = temp; num_types := cast(u16) chunk.groups.count; write_value(*chunk_builder, num_types); for group: chunk.groups { gname_len := cast(u16) group.trile_name.count; write_value(*chunk_builder, gname_len); append(*chunk_builder, group.trile_name); count := cast(u16) group.instances.count; write_value(*chunk_builder, count); for inst: group.instances { write_value(*chunk_builder, inst); } } chunk_data := builder_to_string(*chunk_builder,, temp); data_size := cast(s32) chunk_data.count; array_add(*chunk_entries, .{ coord = chunk.coord, offset = running_offset, size = data_size, }); append(*bin_builder, chunk_data); running_offset += data_size; } wj: World_Json; wj.version = 4; wj.name = world.name; wj.config = world_config_to_json(*world.conf); for entry: chunk_entries { jc: World_Json_Chunk; jc.x = entry.coord.x; jc.y = entry.coord.y; jc.z = entry.coord.z; jc.offset = entry.offset; jc.size = entry.size; array_add(*wj.chunks, jc); } for inst: world.emitter_instances { je: World_Json_Emitter; je.definition_name = inst.definition_name; je.position = inst.position.component; je.offset = inst.offset.component; array_add(*wj.emitters, je); } for note: world.notes { jn: World_Json_Note; jn.text = note.text; jn.position = .[note.position.x, note.position.y, note.position.z]; array_add(*wj.notes, jn); } for ov: world.rdm_overrides { size := ifx ov.rdm_size > 0 then ov.rdm_size else RDM_DEFAULT_SIZE; array_add(*wj.rdm_overrides, .{x=ov.x, y=ov.y, z=ov.z, rdm_enabled=ov.rdm_enabled, rdm_size=size}); } json_str := Jaison.json_write_string(wj, " "); return json_str, builder_to_string(*bin_builder); } load_world_from_json :: (json_str: string, chunk_bin: []u8) -> (World, bool) { world: World; success, wj := Jaison.json_parse_string(json_str, World_Json); if !success { log_error("Failed to parse world JSON"); return world, false; } world.name = sprint("%", wj.name); world.conf = world_config_from_json(*wj.config); for jc: wj.chunks { chunk: Chunk; chunk.coord = .{x = jc.x, y = jc.y, z = jc.z}; offset := cast(s64) jc.offset; size := cast(s64) jc.size; if offset + size > chunk_bin.count { log_error("Chunk data out of bounds: offset=%, size=%, bin=%", offset, size, chunk_bin.count); return world, false; } chunk_data: []u8; chunk_data.data = chunk_bin.data + offset; chunk_data.count = size; chunk_cursor: s64 = 0; num_types := read_value(chunk_data, *chunk_cursor, u16); for t: 0..cast(s64)num_types-1 { group: Chunk_Trile_Group; gname_len := cast(s64) read_value(chunk_data, *chunk_cursor, u16); group.trile_name = read_string(chunk_data, *chunk_cursor, gname_len); count := cast(s64) read_value(chunk_data, *chunk_cursor, u16); for i: 0..count-1 { inst := read_value(chunk_data, *chunk_cursor, Trile_Instance); array_add(*group.instances, inst); } array_add(*chunk.groups, group); } table_set(*world.chunks, chunk.coord, chunk); } for je: wj.emitters { inst: Particle_Emitter_Instance; inst.definition_name = sprint("%", je.definition_name); inst.position.component = je.position; inst.offset.component = je.offset; inst.active = true; array_add(*world.emitter_instances, inst); } for jn: wj.notes { note: Editor_Note; note.text = sprint("%", jn.text); note.position = .{x = jn.position[0], y = jn.position[1], z = jn.position[2]}; array_add(*world.notes, note); } for jov: wj.rdm_overrides { size := ifx jov.rdm_size > 0 then jov.rdm_size else RDM_DEFAULT_SIZE; array_add(*world.rdm_overrides, .{x=jov.x, y=jov.y, z=jov.z, rdm_enabled=jov.rdm_enabled, rdm_size=size}); } return world, true; } load_world_from_data :: (data: []u8) -> (World, bool) { world: World; cursor: s64 = 0; if data.count < size_of(u32) + size_of(u16) { log_error("World file too small"); return world, false; } // Header magic := read_value(data, *cursor, u32); if magic != WORLD_MAGIC { log_error("Invalid world file magic"); return world, false; } version := read_value(data, *cursor, u16); if version != 1 && version != 2 && version != 3 { log_error("Unsupported world version: %", version); return world, false; } name_len := cast(s64) read_value(data, *cursor, u16); world.name = read_string(data, *cursor, name_len); // World config conf_bin := read_value(data, *cursor, World_Config_Binary); world.conf = world_config_from_binary(*conf_bin); num_chunks := read_value(data, *cursor, u32); // Read chunk table Chunk_Table_Entry :: struct { coord: Chunk_Key; offset: u32; size: u32; } chunk_table: [..]Chunk_Table_Entry; chunk_table.allocator = temp; for i: 0..cast(s64)num_chunks-1 { entry: Chunk_Table_Entry; entry.coord.x = read_value(data, *cursor, s32); entry.coord.y = read_value(data, *cursor, s32); entry.coord.z = read_value(data, *cursor, s32); entry.offset = read_value(data, *cursor, u32); entry.size = read_value(data, *cursor, u32); array_add(*chunk_table, entry); } // Read chunk data for entry: chunk_table { chunk_cursor: s64 = cast(s64) entry.offset; chunk: Chunk; chunk.coord = entry.coord; num_types := read_value(data, *chunk_cursor, u16); for t: 0..cast(s64)num_types-1 { group: Chunk_Trile_Group; gname_len := cast(s64) read_value(data, *chunk_cursor, u16); group.trile_name = read_string(data, *chunk_cursor, gname_len); count := cast(s64) read_value(data, *chunk_cursor, u16); for i: 0..count-1 { inst := read_value(data, *chunk_cursor, Trile_Instance); array_add(*group.instances, inst); } array_add(*chunk.groups, group); } table_set(*world.chunks, chunk.coord, chunk); } if chunk_table.count > 0 { last := chunk_table[chunk_table.count - 1]; cursor = cast(s64)(last.offset + last.size); } if version >= 2 && cursor < data.count { num_emitters := cast(s64) read_value(data, *cursor, u16); for i: 0..num_emitters-1 { inst: Particle_Emitter_Instance; name_len := cast(s64) read_value(data, *cursor, u16); inst.definition_name = read_string(data, *cursor, name_len); inst.position.x = read_value(data, *cursor, float); inst.position.y = read_value(data, *cursor, float); inst.position.z = read_value(data, *cursor, float); inst.active = true; array_add(*world.emitter_instances, inst); } } if version >= 3 && cursor < data.count { num_notes := cast(s64) read_value(data, *cursor, u16); for i: 0..num_notes-1 { note: Editor_Note; text_len := cast(s64) read_value(data, *cursor, u16); note.text = read_string(data, *cursor, text_len); note.position.x = read_value(data, *cursor, s32); note.position.y = read_value(data, *cursor, s32); note.position.z = read_value(data, *cursor, s32); array_add(*world.notes, note); } } return world, true; } World_Config :: struct { // All of the @Notes are for the autoedit functionality. skyBase : Vector3 = .{0.38, 0.81, 0.95}; @Color skyTop : Vector3 = .{0.17, 0.4, 0.95}; @Color sunDisk : Vector3 = .{1.0, 1.0, 1.0}; @Color horizonHalo : Vector3 = .{1.0, 1.0, 1.0}; @Color sunHalo : Vector3 = .{1.0, 1.0, 1.0}; @Color sunLightColor : Vector3 = .{1.0, 1.0, 1.0}; @Color sunPosition : Vector3 = #run normalize(Vector3.{0.2, 0.3, 0.4}); sunIntensity : float = 1.0; @Slider,0,4,0.1 skyIntensity : float = 0.3; @Slider,0,5,0.1 hasClouds : s32 = 1; @Slider,0,1,1 planeHeight : float = 0.0; @Slider,0,3,0.1 animatePlaneHeight : s32 = 1; @Slider,0,1,1 waterColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // @ToDo: sensible default values. deepColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // @ToDo: sensible default values. waterShininess : float = 64.0; @Slider,1,512,8 hsv_lighting : s32 = 1; @Slider,0,1,1 // ambientColor : Vector3 = .{1.0, 1.0, 1.0}; @Color // ambientIntensity : float = 0.3; @Slider,0,3,0.1 } // Copies over all the fields of our world config into a given shader type. // Requires that the shader type has all of the fields the world config has. world_config_to_shader_type :: (wc: *World_Config, data: *$T) { generate_copy_code :: () -> string { builder : String_Builder; ti_src := type_info(World_Config); ti_dst := type_info(T); for src_member: ti_src.members { has_field := false; for dst_member: ti_dst.members { if dst_member.name == src_member.name { has_field = true; break; } } if !has_field continue; if src_member.type == type_info(Vector3) then print_to_builder(*builder, "data.% = wc.%.component;\n", src_member.name, src_member.name); else print_to_builder(*builder, "data.% = wc.%;\n", src_member.name, src_member.name); } return builder_to_string(*builder); } data.time = xx get_time(); #insert #run,stallable generate_copy_code(); } effective_plane_height :: (wc: *World_Config) -> float { if wc.animatePlaneHeight { return wc.planeHeight * (1.0 + sin(cast(float)get_time() * 0.5) * 0.1); } return wc.planeHeight; } draw_world_picker :: (r_in: GR.Rect, theme: *GR.Overall_Theme) { r := r_in; r.h = ui_h(4,4); #if OS != .WASM { File_Utilities :: #import "File_Utilities"; world_names: [..]string; world_names.allocator = temp; dir_visitor :: (info: *File_Utilities.File_Visit_Info, names: *[..]string) { if info.short_name == "world.json" || info.short_name == "index.world" { #import "String"; suffix := ifx info.short_name == "world.json" then "/world.json" else "/index.world"; _, left, _ := split_from_right(info.full_name, suffix); _, _, name := split_from_right(left, "/"); if name.count > 0 { for names.* { if it == name then return; } array_add(names, name); } } } File_Utilities.visit_files(tprint("%/worlds", GAME_RESOURCES_DIR), true, *world_names, dir_visitor); count := 0; for name: world_names { is_current := current_world.valid && current_world.world.name == name; if GR.button(r, name, *t_button_selectable(theme, is_current), count) { lworld(name); } count += 1; r.y += r.h; } } else { if current_world.valid { GR.label(r, tprint("Current: %", current_world.world.name), *theme.label_theme); } else { GR.label(r, "No world loaded", *theme.label_theme); } } }