work on particles
This commit is contained in:
parent
a262a590b2
commit
1b061f929f
257
src/particles/particles.jai
Normal file
257
src/particles/particles.jai
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
#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 {
|
||||||
|
name : string;
|
||||||
|
sprite_sheet : string;
|
||||||
|
emission_rate : float = 10.0;
|
||||||
|
lifetime_min : float = 0.5;
|
||||||
|
lifetime_max : float = 2.0;
|
||||||
|
velocity : Vector3;
|
||||||
|
velocity_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};
|
||||||
|
frame_count : int = 1;
|
||||||
|
gravity : float = 2.0;
|
||||||
|
|
||||||
|
sprite_image : sg_image; // resolved at runtime from sprite_sheet name
|
||||||
|
}
|
||||||
|
|
||||||
|
Particle_Emitter_Instance :: struct {
|
||||||
|
definition : *Particle_Emitter_Definition;
|
||||||
|
definition_name : string;
|
||||||
|
position : Vector3;
|
||||||
|
active : bool = true;
|
||||||
|
spawn_accumulator : float;
|
||||||
|
}
|
||||||
|
|
||||||
|
Particle :: struct {
|
||||||
|
position : Vector3;
|
||||||
|
velocity : Vector3;
|
||||||
|
age : float;
|
||||||
|
lifetime : float;
|
||||||
|
gravity : float;
|
||||||
|
alive : bool;
|
||||||
|
definition : *Particle_Emitter_Definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
MAX_PARTICLES : s32 : 2048;
|
||||||
|
|
||||||
|
g_particles : [2048] Particle;
|
||||||
|
g_emitter_instances : [..] Particle_Emitter_Instance;
|
||||||
|
g_emitter_defs : [..] Particle_Emitter_Definition;
|
||||||
|
|
||||||
|
tick_particles :: (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
for *inst: g_emitter_instances {
|
||||||
|
if !inst.active then continue;
|
||||||
|
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, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get_emitter_def_names :: () -> []string {
|
||||||
|
names : [..] string;
|
||||||
|
names.allocator = temp;
|
||||||
|
for def: g_emitter_defs {
|
||||||
|
array_add(*names, def.name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
save_particle_definition :: (def: Particle_Emitter_Definition) {
|
||||||
|
#if OS != .WASM {
|
||||||
|
file :: #import "File";
|
||||||
|
dir := "./game/resources/particles";
|
||||||
|
file.make_directory_if_it_does_not_exist(dir, recursive = true);
|
||||||
|
path := tprint("%/%.emitter.json", dir, def.name);
|
||||||
|
json := Jaison.json_write_string(def,, temp);
|
||||||
|
file.write_entire_file(path, json);
|
||||||
|
log_info("Saved particle definition '%'", def.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user