From 20b6b8ae297ddd88ed258d6273f8daeffe24a262 Mon Sep 17 00:00:00 2001 From: Katajisto Date: Sat, 21 Mar 2026 19:45:00 +0200 Subject: [PATCH] work on particles --- src/assets/asset_manager.jai | 19 ++-- src/main.jai | 2 + src/particles/particles.jai | 176 ++++++----------------------------- src/world.jai | 8 +- 4 files changed, 46 insertions(+), 159 deletions(-) diff --git a/src/assets/asset_manager.jai b/src/assets/asset_manager.jai index 1c93829..cacb904 100644 --- a/src/assets/asset_manager.jai +++ b/src/assets/asset_manager.jai @@ -45,14 +45,14 @@ g_asset_manager : Asset_Manager; #load "rdm_loader.jai"; -MAX_FILE_SIZE :: 200_000_000; -buf : [MAX_FILE_SIZE]u8; -world_buf : [MAX_FILE_SIZE]u8; - +MAX_FILE_SIZE :: 200_000_000; RDM_ATLAS_MAX_BYTES :: 4096 * 4096 * 4 * 4 + size_of(RDM_File_Header); RDM_LOOKUP_MAX_BYTES :: 512 * 512 * 4 * 4 + size_of(RDM_File_Header); -rdm_atlas_buf : [RDM_ATLAS_MAX_BYTES]u8; -rdm_lookup_buf : [RDM_LOOKUP_MAX_BYTES]u8; + +buf : []u8; +world_buf : []u8; +rdm_atlas_buf : []u8; +rdm_lookup_buf : []u8; buffer_for_fetch :: (type: Fetch_Type) -> (*u8, u64) { if type == .PACK return buf.data, xx buf.count; @@ -298,6 +298,13 @@ free_resources_from_pack :: (pack: *Loaded_Pack) { table_reset(*pack.animations); } +asset_manager_init :: () { + buf = NewArray(MAX_FILE_SIZE, u8, false); + world_buf = NewArray(MAX_FILE_SIZE, u8, false); + rdm_atlas_buf = NewArray(RDM_ATLAS_MAX_BYTES, u8, false); + rdm_lookup_buf = NewArray(RDM_LOOKUP_MAX_BYTES, u8, false); +} + mandatory_loads_done :: () -> bool { if g_asset_manager.is_fetching && g_asset_manager.current_fetch.should_block_engine then return false; for g_asset_manager.fetch_queue { diff --git a/src/main.jai b/src/main.jai index 804316d..679242c 100644 --- a/src/main.jai +++ b/src/main.jai @@ -30,6 +30,7 @@ stbi :: #import "stb_image"; #load "load.jai"; #load "ray.jai"; #load "profiling.jai"; +// #load "particles/particles.jai"; #load "world.jai"; #load "utils.jai"; #load "audio/audio.jai"; @@ -92,6 +93,7 @@ init :: () { sfetch_setup(*(sfetch_desc_t.{ logger = .{ func = slog_func }, })); + asset_manager_init(); saudio_setup(*(saudio_desc.{ logger = .{ func = slog_func }, stream_cb = sokol_audio_callback, diff --git a/src/particles/particles.jai b/src/particles/particles.jai index d03c4f1..c682623 100644 --- a/src/particles/particles.jai +++ b/src/particles/particles.jai @@ -1,18 +1,8 @@ #scope_export -g_pending_emitter_world_name : string; - -particles_process_deferred_loads :: () { - if g_pending_emitter_world_name.count == 0 then return; - name := g_pending_emitter_world_name; - g_pending_emitter_world_name = ""; - load_emitters_for_world(name); - free(name.data); -} - -Particle_Emitter_Definition :: struct { +Particle_Emitter_Config :: struct { name : string; - sprite_sheet : string; + animation_name : string; emission_rate : float = 10.0; lifetime_min : float = 0.5; lifetime_max : float = 2.0; @@ -22,14 +12,13 @@ Particle_Emitter_Definition :: struct { size_end : float = 0.0; color_start : Vector4 = .{1, 1, 1, 1}; color_end : Vector4 = .{1, 1, 1, 0}; - frame_count : int = 1; gravity : float = 2.0; - sprite_image : sg_image; // resolved at runtime from sprite_sheet name + animation : *Animation; // resolved at runtime from animation_name } Particle_Emitter_Instance :: struct { - definition : *Particle_Emitter_Definition; + definition : *Particle_Emitter_Config; definition_name : string; position : Vector3; active : bool = true; @@ -43,16 +32,16 @@ Particle :: struct { lifetime : float; gravity : float; alive : bool; - definition : *Particle_Emitter_Definition; + definition : *Particle_Emitter_Config; } MAX_PARTICLES : s32 : 2048; -g_particles : [2048] Particle; -g_emitter_instances : [..] Particle_Emitter_Instance; -g_emitter_defs : [..] Particle_Emitter_Definition; +g_particles : [2048] Particle; +g_emitter_defs : [..] Particle_Emitter_Config; tick_particles :: (dt: float) { + return; for *p: g_particles { if !p.alive then continue; p.age += dt; @@ -60,11 +49,12 @@ tick_particles :: (dt: float) { p.alive = false; continue; } - p.velocity.y -= p.gravity * dt; - p.position += p.velocity * dt; + p.velocity.y -= p.gravity * dt; + p.position += p.velocity * dt; } - for *inst: g_emitter_instances { + for *inst: get_current_world().world.emitter_instances { + if inst == null then continue; if !inst.active then continue; if inst.definition == null then continue; def := inst.definition; @@ -76,71 +66,19 @@ tick_particles :: (dt: float) { } } +tick_emitter_instance :: (inst: *Particle_Emitter_Instance, dt: float) { + if !inst.active then return; + if inst.definition == null then return; + def := inst.definition; + inst.spawn_accumulator += def.emission_rate * dt; + while inst.spawn_accumulator >= 1.0 { + inst.spawn_accumulator -= 1.0; + spawn_one_particle(inst.position, def); + } +} + add_particle_render_tasks :: () { - for *def: g_emitter_defs { - if def.sprite_image.id == 0 then continue; - - instances : [..] Particle_Instance_Data; - instances.allocator = temp; - - for *p: g_particles { - if !p.alive then continue; - if p.definition != def then continue; - - t := p.age / p.lifetime; - t = clamp(t, 0.0, 1.0); - - frame_count := cast(s32) max(def.frame_count, 1); - frame_idx := cast(s32)(t * cast(float) frame_count); - frame_idx = clamp(frame_idx, 0, frame_count - 1); - - frame_w := 1.0 / cast(float) frame_count; - uv_rect := Vector4.{frame_idx * frame_w, 0.0, frame_w, 1.0}; - - size := def.size_start + (def.size_end - def.size_start) * t; - color := lerp_color(def.color_start, def.color_end, t); - - inst : Particle_Instance_Data; - inst.pos_size = .{p.position.x, p.position.y, p.position.z, size}; - inst.uv_rect = uv_rect; - inst.color = color; - array_add(*instances, inst); - } - - if instances.count == 0 then continue; - - task : Rendering_Task_Particles; - task.instances = instances; - task.sprite_image = def.sprite_image; - add_rendering_task(task); - } -} - -clear_particle_emitter_instances :: () { - array_reset(*g_emitter_instances); - for *p: g_particles { - p.alive = false; - } -} - -place_emitter :: (def_name: string, position: Vector3) { - inst : Particle_Emitter_Instance; - inst.definition_name = sprint("%", def_name); - inst.position = position; - inst.active = true; - resolve_emitter_definition(*inst); - array_add(*g_emitter_instances, inst); -} - -remove_emitter_at_index :: (idx: int) { - if idx < 0 || idx >= g_emitter_instances.count then return; - array_unordered_remove_by_index(*g_emitter_instances, idx); -} - -resolve_all_emitter_instances :: () { - for *inst: g_emitter_instances { - resolve_emitter_definition(inst); - } + // TODO } get_emitter_def_names :: () -> []string { @@ -152,7 +90,7 @@ get_emitter_def_names :: () -> []string { return names; } -save_particle_definition :: (def: Particle_Emitter_Definition) { +save_particle_definition :: (def: Particle_Emitter_Config) { #if OS != .WASM { file :: #import "File"; dir := "./game/resources/particles"; @@ -164,58 +102,6 @@ save_particle_definition :: (def: Particle_Emitter_Definition) { } } -load_emitters_for_world :: (world_name: string) { - #if OS != .WASM { - file :: #import "File"; - path := tprint("./game/resources/worlds/%/emitters.json", world_name); - data, ok := file.read_entire_file(path); - if !ok then return; - defer free(data.data); - - Emitter_Entry :: struct { - definition : string; - position : [3] float; - } - success, entries := Jaison.json_parse_string(cast(string) data, [] Emitter_Entry,, temp); - if !success { - log_error("Failed to parse emitters.json for world '%'", world_name); - return; - } - for entry: entries { - pos := Vector3.{entry.position[0], entry.position[1], entry.position[2]}; - place_emitter(entry.definition, pos); - } - log_info("Loaded % emitters for world '%'", entries.count, world_name); - } -} - -save_emitters_for_world :: (world_name: string) { - #if OS != .WASM { - file :: #import "File"; - dir := tprint("./game/resources/worlds/%", world_name); - file.make_directory_if_it_does_not_exist(dir, recursive = true); - path := tprint("%/emitters.json", dir); - - Emitter_Entry :: struct { - definition : string; - position : [3] float; - } - entries : [..] Emitter_Entry; - entries.allocator = temp; - for inst: g_emitter_instances { - e : Emitter_Entry; - e.definition = inst.definition_name; - e.position[0] = inst.position.x; - e.position[1] = inst.position.y; - e.position[2] = inst.position.z; - array_add(*entries, e); - } - json := Jaison.json_write_string(entries,, temp); - file.write_entire_file(path, json); - log_info("Saved % emitters for world '%'", entries.count, world_name); - } -} - #scope_file Random :: #import "Random"; @@ -224,7 +110,7 @@ rng :: inline (lo: float, hi: float) -> float { return lo + Random.random_get_zero_to_one() * (hi - lo); } -spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Definition) { +spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Config) { for *p: g_particles { if p.alive then continue; p.alive = true; @@ -245,13 +131,3 @@ spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Definition) { lerp_color :: (a: Vector4, b: Vector4, t: float) -> Vector4 { return a * (1.0 - t) + b * t; } - -resolve_emitter_definition :: (inst: *Particle_Emitter_Instance) { - for *def: g_emitter_defs { - if def.name == inst.definition_name { - inst.definition = def; - return; - } - } - inst.definition = null; -} diff --git a/src/world.jai b/src/world.jai index 3d2826f..2f9b3b9 100644 --- a/src/world.jai +++ b/src/world.jai @@ -59,9 +59,11 @@ Chunk :: struct { } World :: struct { - name : string; - conf : World_Config; - chunks : Table(Chunk_Key, Chunk, chunk_key_hash, chunk_key_compare); + name : string; + conf : World_Config; + chunks : Table(Chunk_Key, Chunk, chunk_key_hash, chunk_key_compare); + // emitter_instances : [..]Particle_Emitter_Instance; + emitter_instances : [..]string; } // Convert a world-space integer position to chunk coordinate.