work on particles

This commit is contained in:
Tuomas Katajisto 2026-03-21 19:45:00 +02:00
parent 8ab0a1fd6b
commit 20b6b8ae29
4 changed files with 46 additions and 159 deletions

View File

@ -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 {

View File

@ -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,

View File

@ -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;
}

View File

@ -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.