trueno/src/particles/particles.jai

286 lines
8.5 KiB
Plaintext

#scope_export
Particle_Blend_Mode :: enum {
ADDITIVE;
ALPHA;
}
Particle_Emitter_Config :: struct {
name : string;
animation_name : string;
emission_rate : float = 10.0;
lifetime_min : float = 0.5;
lifetime_max : float = 2.0;
velocity : Vector3;
velocity_spread : Vector3;
position_spread : Vector3;
size_start : float = 0.5;
size_end : float = 0.0;
color_start : Vector4 = .{1, 1, 1, 1};
color_end : Vector4 = .{1, 1, 1, 0};
gravity : float = 2.0;
blend_mode : Particle_Blend_Mode = .ADDITIVE;
}
Particle_Emitter_Instance :: struct {
definition : *Particle_Emitter_Config;
definition_name : string;
position : Vector3;
offset : Vector3;
active : bool = true;
spawn_accumulator : float;
}
Particle :: struct {
position : Vector3;
velocity : Vector3;
age : float;
lifetime : float;
gravity : float;
alive : bool;
definition : *Particle_Emitter_Config;
}
MAX_PARTICLES :: 16384;
g_particles : [MAX_PARTICLES] Particle;
g_emitter_defs : [..] Particle_Emitter_Config;
clear_particles :: () {
for *p: g_particles p.alive = false;
}
tick_particles_physics :: (dt: float) {
for *p: g_particles {
if !p.alive then continue;
p.age += dt;
if p.age >= p.lifetime {
p.alive = false;
continue;
}
p.velocity.y -= p.gravity * dt;
p.position += p.velocity * dt;
}
}
tick_world_emitters :: (dt: float) {
curworld := get_current_world();
if !curworld.valid then return;
for *inst: curworld.world.emitter_instances {
if !inst.active then continue;
if inst.definition == null {
inst.definition = get_emitter_def(inst.definition_name);
if inst.definition == null then continue;
}
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 + inst.offset, def);
}
}
}
tick_particles :: (dt: float) {
tick_particles_physics(dt);
tick_world_emitters(dt);
}
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 :: () {
Batch :: struct {
anim_name : string;
blend_mode : Particle_Blend_Mode;
anim : *Animation;
sheet : sg_image;
count : s32;
pos_size : *[MAX_PARTICLES]Vector4;
uv_rects : *[MAX_PARTICLES]Vector4;
colors : *[MAX_PARTICLES]Vector4;
}
batches : [..] Batch;
batches.allocator = temp;
has_water, water_y := particle_water_height();
for *p: g_particles {
if !p.alive then continue;
if p.definition == null then continue;
if has_water && p.position.y < water_y then continue;
def := p.definition;
if def.animation_name.count == 0 then continue;
batch : *Batch = null;
for *b: batches {
if b.anim_name == def.animation_name && b.blend_mode == def.blend_mode {
batch = b;
break;
}
}
if batch == null {
anim := get_animation_from_string(def.animation_name);
if anim == null then continue;
if anim.frames.count == 0 then continue;
array_add(*batches, .{
anim_name = def.animation_name,
blend_mode = def.blend_mode,
anim = anim,
sheet = anim.sheet,
pos_size = New([MAX_PARTICLES]Vector4,, temp),
uv_rects = New([MAX_PARTICLES]Vector4,, temp),
colors = New([MAX_PARTICLES]Vector4,, temp),
});
batch = *batches[batches.count - 1];
}
if batch.count >= MAX_PARTICLES then continue;
t := p.age / p.lifetime;
size := def.size_start + (def.size_end - def.size_start) * t;
col := lerp_color(def.color_start, def.color_end, t);
idx := batch.count;
batch.pos_size.*[idx] = .{p.position.x, p.position.y, p.position.z, size};
anim := batch.anim;
frame_count := anim.frames.count;
frame := cast(s32)(t * cast(float)frame_count);
if frame < 0 then frame = 0;
if frame >= frame_count then frame = cast(s32)(frame_count - 1);
f := anim.frames[frame];
batch.uv_rects.*[idx] = .{
cast(float)f.x / cast(float)anim.sheet_w,
cast(float)f.y / cast(float)anim.sheet_h,
cast(float)f.w / cast(float)anim.sheet_w,
cast(float)f.h / cast(float)anim.sheet_h,
};
batch.colors.*[idx] = col;
batch.count += 1;
}
buf_task := New(Rendering_Task_Particles_Buffer,, temp);
total_count : s32 = 0;
for *b: batches {
if b.count <= 0 then continue;
instance_offset := total_count;
memcpy(*buf_task.pos_size[instance_offset], cast(*void)b.pos_size, b.count * size_of(Vector4));
memcpy(*buf_task.uv_rects[instance_offset], cast(*void)b.uv_rects, b.count * size_of(Vector4));
memcpy(*buf_task.colors[instance_offset], cast(*void)b.colors, b.count * size_of(Vector4));
total_count += b.count;
draw_task : Rendering_Task_Particles;
draw_task.count = b.count;
draw_task.instance_offset = instance_offset;
draw_task.blend_mode = b.blend_mode;
draw_task.sheet = b.sheet;
add_rendering_task(draw_task);
}
if total_count > 0 {
buf_task.total_count = total_count;
add_rendering_task(buf_task.*);
}
}
particle_water_height :: () -> (has_water: bool, height: float) {
#if !FLAG_RELEASE_BUILD {
if in_editor_view && !editor_shows_water() return false, 0;
}
curworld := get_current_world();
if !curworld.valid return false, 0;
return true, effective_plane_height(*curworld.world.conf);
}
get_emitter_def :: (name: string) -> *Particle_Emitter_Config {
for *def: g_emitter_defs {
if def.name == name then return def;
}
return null;
}
spawn_particles_at :: (position: Vector3, def_name: string) {
for *def: g_emitter_defs {
if def.name == def_name {
spawn_one_particle(position, def);
return;
}
}
}
register_emitter_def :: (def: Particle_Emitter_Config) {
for *existing: g_emitter_defs {
if existing.name == def.name {
existing.* = def;
return;
}
}
array_add(*g_emitter_defs, def);
}
get_emitter_def_names :: () -> []string {
names : [..] string;
names.allocator = temp;
for def: g_emitter_defs {
array_add(*names, def.name);
}
return names;
}
save_particle_defs :: () {
#if OS != .WASM {
file :: #import "File";
json := Jaison.json_write_string(g_emitter_defs, " ");
file.write_entire_file(tprint("%/game_core/particles.json", GAME_RESOURCES_DIR), json);
log_info("Saved % particle definitions", g_emitter_defs.count);
}
}
spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Config) {
for *p: g_particles {
if p.alive then continue;
p.alive = true;
p.age = 0;
p.lifetime = rng(def.lifetime_min, def.lifetime_max);
p.position = position + Vector3.{
rng(-def.position_spread.x, def.position_spread.x),
rng(-def.position_spread.y, def.position_spread.y),
rng(-def.position_spread.z, def.position_spread.z),
};
p.velocity = def.velocity + Vector3.{
rng(-def.velocity_spread.x, def.velocity_spread.x),
rng(-def.velocity_spread.y, def.velocity_spread.y),
rng(-def.velocity_spread.z, def.velocity_spread.z),
};
p.gravity = def.gravity;
p.definition = def;
return;
}
}
#scope_file
Random :: #import "Random";
rng :: inline (lo: float, hi: float) -> float {
return lo + Random.random_get_zero_to_one() * (hi - lo);
}
lerp_color :: (a: Vector4, b: Vector4, t: float) -> Vector4 {
return a * (1.0 - t) + b * t;
}