Compare commits
No commits in common. "01eaff1c0fcdf9abcdcc10a499c73b98bdd61950" and "ad099d79b57f0d5aafbc6971282d4c978979de36" have entirely different histories.
01eaff1c0f
...
ad099d79b5
@ -11,9 +11,6 @@ cam : Camera = .{
|
||||
};
|
||||
|
||||
#scope_export
|
||||
game_engine_config :: () {
|
||||
}
|
||||
|
||||
game_init :: () {
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
master_volume 1
|
||||
music_volume 0.2
|
||||
sfx_volume 1
|
||||
fullscreen 1
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#import "String";
|
||||
hash :: #import "Hash";
|
||||
Pool :: #import "Pool";
|
||||
|
||||
#load "loaders.jai";
|
||||
|
||||
@ -78,7 +77,6 @@ fetch_callback :: (res: *sfetch_response_t) #c_call {
|
||||
mem := NewArray(res.data.size.(s64), u8, false);
|
||||
memcpy(mem.data, res.data.ptr, res.data.size.(s64));
|
||||
pack: Loaded_Pack;
|
||||
Pool.set_allocators(*pack.pool);
|
||||
pack.nameHash = hash.get_hash(req.pack_name);
|
||||
pack.name = sprint("%", req.pack_name);
|
||||
success := init_from_memory(*pack.content, mem, sprint("%", req.pack_name));
|
||||
@ -175,17 +173,9 @@ fetch_callback :: (res: *sfetch_response_t) #c_call {
|
||||
};
|
||||
chunk := table_find_pointer(*curworld.world.chunks, req.chunk_key);
|
||||
if chunk != null {
|
||||
chunk.rdm_atlas = req.rdm_pending_atlas;
|
||||
chunk.rdm_lookup = sg_make_image(*lookup_desc);
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
chunk.rdm_lookup_w = header.width;
|
||||
chunk.rdm_lookup_h = header.height;
|
||||
num_floats := cast(s64)(header.width * header.height * 4);
|
||||
cpu_copy := NewArray(num_floats, float);
|
||||
memcpy(cpu_copy.data, res.data.ptr + header_size, cast(s64)lookup_pixel_bytes);
|
||||
chunk.rdm_lookup_cpu = cpu_copy;
|
||||
}
|
||||
chunk.rdm_valid = true;
|
||||
chunk.rdm_atlas = req.rdm_pending_atlas;
|
||||
chunk.rdm_lookup = sg_make_image(*lookup_desc);
|
||||
chunk.rdm_valid = true;
|
||||
log_debug("RDM: loaded chunk %", req.chunk_key);
|
||||
} else {
|
||||
sg_destroy_image(req.rdm_pending_atlas);
|
||||
@ -201,12 +191,11 @@ Loaded_Pack :: struct {
|
||||
textures : Table(string, sg_image);
|
||||
animations : Table(string, Animation);
|
||||
audio : Table(string, Audio_Data);
|
||||
pool : Pool.Pool;
|
||||
//fonts : [..]Font??;
|
||||
}
|
||||
|
||||
add_resources_from_pack :: (pack: *Loaded_Pack) {
|
||||
push_allocator(.{Pool.pool_allocator_proc, *pack.pool});
|
||||
|
||||
// We need to go trough this at the end.
|
||||
Queued_Sheet_File :: struct {
|
||||
name : string;
|
||||
image : sg_image;
|
||||
@ -252,21 +241,6 @@ add_resources_from_pack :: (pack: *Loaded_Pack) {
|
||||
case "wav";
|
||||
audio := load_wav_from_memory(v.data);
|
||||
table_set(*pack.audio, name, audio);
|
||||
case "json";
|
||||
if name == "particles" {
|
||||
s := create_string_from_memory(v.data);
|
||||
success, defs := Jaison.json_parse_string(s, [..]Particle_Emitter_Config,, temp);
|
||||
if success {
|
||||
array_reset(*g_emitter_defs);
|
||||
for defs {
|
||||
def := it;
|
||||
def.name = sprint("%", it.name);
|
||||
def.animation_name = sprint("%", it.animation_name);
|
||||
array_add(*g_emitter_defs, def);
|
||||
}
|
||||
log_info("Loaded % particle definitions from pack", g_emitter_defs.count);
|
||||
}
|
||||
}
|
||||
case "ttf";
|
||||
// Load into a font. Add to free list.
|
||||
case;
|
||||
@ -318,10 +292,10 @@ find_pack_by_name :: (name: string) -> (bool, Loaded_Pack) {
|
||||
|
||||
free_resources_from_pack :: (pack: *Loaded_Pack) {
|
||||
for pack.textures sg_destroy_image(it);
|
||||
for *pack.audio array_free(it.data);
|
||||
table_reset(*pack.textures);
|
||||
table_reset(*pack.audio);
|
||||
table_reset(*pack.animations);
|
||||
Pool.reset(*pack.pool);
|
||||
}
|
||||
|
||||
asset_manager_init :: () {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
#load "rdm_disk.jai";
|
||||
#if OS != .WASM {
|
||||
#load "iprof.jai";
|
||||
#load "picker.jai";
|
||||
#load "trile_editor.jai";
|
||||
#load "level_editor.jai";
|
||||
#load "particle_editor.jai";
|
||||
}
|
||||
#if FLAG_TACOMA_ENABLED { #load "tacoma.jai"; }
|
||||
#load "console.jai";
|
||||
@ -14,8 +14,8 @@
|
||||
Editor_View :: enum {
|
||||
Closed_Editor;
|
||||
Trile_Editor;
|
||||
Level_Editor;
|
||||
Particle_Editor;
|
||||
Level_Editor;
|
||||
Material_Editor;
|
||||
};
|
||||
|
||||
current_editor_view : Editor_View = .Trile_Editor;
|
||||
@ -25,10 +25,8 @@ current_editor_view : Editor_View = .Trile_Editor;
|
||||
in_editor_view : bool = false;
|
||||
|
||||
init_editor :: () {
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
#if OS != .WASM {init_profiler();}
|
||||
init_console();
|
||||
}
|
||||
#if OS != .WASM {init_profiler();}
|
||||
init_console();
|
||||
init_keybinds();
|
||||
init_settings_menu();
|
||||
}
|
||||
@ -52,15 +50,14 @@ draw_editor_ui :: (theme: *GR.Overall_Theme) {
|
||||
r.x -= r.w;
|
||||
if keybind_button(r, "Trile studio", .STUDIO_TRILE, *t_button_selectable(theme, current_editor_view == .Trile_Editor)) then current_editor_view = .Trile_Editor;
|
||||
r.x -= r.w;
|
||||
if keybind_button(r, "Particle studio", .STUDIO_MATERIAL, *t_button_selectable(theme, current_editor_view == .Particle_Editor)) then current_editor_view = .Particle_Editor;
|
||||
if keybind_button(r, "Material studio", .STUDIO_MATERIAL, *t_button_selectable(theme, current_editor_view == .Material_Editor)) then current_editor_view = .Material_Editor;
|
||||
|
||||
|
||||
if current_editor_view == {
|
||||
case .Trile_Editor;
|
||||
draw_trile_editor_ui(theme);
|
||||
case .Level_Editor;
|
||||
draw_level_editor_ui(theme);
|
||||
case .Particle_Editor;
|
||||
draw_particle_editor_ui(theme);
|
||||
}
|
||||
}
|
||||
draw_profiler();
|
||||
@ -83,8 +80,6 @@ draw_editor :: () {
|
||||
draw_trile_editor();
|
||||
case .Level_Editor;
|
||||
draw_level_editor();
|
||||
case .Particle_Editor;
|
||||
draw_particle_editor();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,8 +105,6 @@ tick_editor_ui :: () {
|
||||
tick_trile_editor();
|
||||
case .Level_Editor;
|
||||
tick_level_editor();
|
||||
case .Particle_Editor;
|
||||
tick_particle_editor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@ MAX_CAMERA_DIST :: 25.0;
|
||||
MIN_CAMERA_DIST :: 2.0;
|
||||
DIST_SCROLL_SPEED :: 0.8;
|
||||
|
||||
mouse1Active : bool;
|
||||
mouse1ActivationPosition : Vector2;
|
||||
mouse2Active : bool;
|
||||
mouse2ActivationPosition : Vector2;
|
||||
mouse3Active : bool;
|
||||
@ -27,8 +25,6 @@ Level_Editor_Tool_Mode :: enum {
|
||||
BRUSH;
|
||||
AREA;
|
||||
LINE;
|
||||
INSPECTOR;
|
||||
VIEWER;
|
||||
}
|
||||
|
||||
current_tool_mode : Level_Editor_Tool_Mode = .POINT;
|
||||
@ -50,16 +46,6 @@ line_start_z : int;
|
||||
current_orientation_face : u8 = 0;
|
||||
current_orientation_twist : u8 = 0;
|
||||
|
||||
inspector_selected : bool;
|
||||
inspector_x : s32;
|
||||
inspector_y : s32;
|
||||
inspector_z : s32;
|
||||
inspector_emitter_def : s32 = -1;
|
||||
inspector_rdm_roughness : int = 0;
|
||||
inspector_note_input : string = "";
|
||||
|
||||
editor_billboards_visible : bool = true;
|
||||
|
||||
get_current_orientation :: () -> u8 {
|
||||
return current_orientation_face * 4 + current_orientation_twist;
|
||||
}
|
||||
@ -178,25 +164,6 @@ tick_level_editor_camera :: () {
|
||||
editY = max(editY - 1, 0);
|
||||
}
|
||||
|
||||
if current_tool_mode == .VIEWER && get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT) & .DOWN {
|
||||
if mouse1Active {
|
||||
lastInputTime = get_time();
|
||||
diff := mouse1ActivationPosition - Vector2.{input_mouse_x, input_mouse_y};
|
||||
diff *= 0.5;
|
||||
cameraRotation = oldCameraRotation + diff.x / 100;
|
||||
cameraTilt = oldCameraTilt - diff.y / 100;
|
||||
cameraTilt = max(0.1, cameraTilt);
|
||||
cameraTilt = min(PI/2.2, cameraTilt);
|
||||
} else {
|
||||
mouse1Active = true;
|
||||
mouse1ActivationPosition = Vector2.{input_mouse_x, input_mouse_y};
|
||||
oldCameraRotation = cameraRotation;
|
||||
oldCameraTilt = cameraTilt;
|
||||
}
|
||||
} else {
|
||||
mouse1Active = false;
|
||||
}
|
||||
|
||||
if get_mouse_state(Key_Code.MOUSE_BUTTON_MIDDLE) & .DOWN {
|
||||
if mouse2Active {
|
||||
lastInputTime = get_time();
|
||||
@ -325,10 +292,6 @@ draw_tools_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||
r.y += r.h;
|
||||
if keybind_button(r, "Line", .LEVEL_TOOL_LINE, *t_button_selectable(theme, current_tool_mode == .LINE)) { current_tool_mode = .LINE; line_active = false; }
|
||||
r.y += r.h;
|
||||
if keybind_button(r, "Inspector", .LEVEL_TOOL_INSPECTOR, *t_button_selectable(theme, current_tool_mode == .INSPECTOR)) then current_tool_mode = .INSPECTOR;
|
||||
r.y += r.h;
|
||||
if keybind_button(r, "Viewer", .LEVEL_TOOL_VIEWER, *t_button_selectable(theme, current_tool_mode == .VIEWER)) then current_tool_mode = .VIEWER;
|
||||
r.y += r.h;
|
||||
|
||||
// Brush radius/height (only for brush mode)
|
||||
if current_tool_mode == .BRUSH {
|
||||
@ -364,12 +327,6 @@ draw_tools_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||
GR.label(r, "Click start of line", *t_label_left(theme));
|
||||
}
|
||||
r.y += r.h;
|
||||
} else if current_tool_mode == .INSPECTOR {
|
||||
r.h = ui_h(3, 2);
|
||||
if GR.button(r, ifx editor_billboards_visible then "Hide Markers" else "Show Markers", *theme.button_theme, 199) {
|
||||
editor_billboards_visible = !editor_billboards_visible;
|
||||
}
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
// Orientation controls
|
||||
@ -500,10 +457,8 @@ tick_level_editor :: () {
|
||||
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_POINT) then current_tool_mode = .POINT;
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_BRUSH) then current_tool_mode = .BRUSH;
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_AREA) { current_tool_mode = .AREA; area_active = false; }
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_LINE) { current_tool_mode = .LINE; line_active = false; }
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_INSPECTOR) then current_tool_mode = .INSPECTOR;
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_VIEWER) then current_tool_mode = .VIEWER;
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_AREA) { current_tool_mode = .AREA; area_active = false; }
|
||||
if is_action_start(Editor_Action.LEVEL_TOOL_LINE) { current_tool_mode = .LINE; line_active = false; }
|
||||
if is_action_start(Editor_Action.LEVEL_TWIST_CCW) {
|
||||
lastInputTime = get_time();
|
||||
current_orientation_twist = (current_orientation_twist + 1) % 4;
|
||||
@ -603,23 +558,6 @@ tick_level_editor :: () {
|
||||
line_active = false; // cancel add selection
|
||||
}
|
||||
}
|
||||
} else if current_tool_mode == .INSPECTOR {
|
||||
if get_mouse_state(Key_Code.MOUSE_BUTTON_LEFT) & .START {
|
||||
inspector_selected = true;
|
||||
inspector_x = cast(s32)px;
|
||||
inspector_y = cast(s32)py;
|
||||
inspector_z = cast(s32)pz;
|
||||
inspector_note_input = "";
|
||||
curworld2 := get_current_world();
|
||||
if curworld2.valid {
|
||||
for note: curworld2.world.notes {
|
||||
if note.position.x == inspector_x && note.position.y == inspector_y && note.position.z == inspector_z {
|
||||
inspector_note_input = note.text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -697,263 +635,6 @@ create_level_editor_preview_tasks :: () {
|
||||
add_rendering_task(task);
|
||||
}
|
||||
|
||||
get_trile_at :: (world: *World, wx: s32, wy: s32, wz: s32) -> (name: string, orientation: u8, found: bool) {
|
||||
key := world_to_chunk_coord(wx, wy, wz);
|
||||
chunk := table_find_pointer(*world.chunks, key);
|
||||
if !chunk then return "", 0, false;
|
||||
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, inst.orientation, true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", 0, false;
|
||||
}
|
||||
|
||||
find_emitter_at :: (world: *World, x: s32, y: s32, z: s32) -> (idx: s32, found: bool) {
|
||||
for inst, i: world.emitter_instances {
|
||||
ix := cast(s32)inst.position.x;
|
||||
iy := cast(s32)inst.position.y;
|
||||
iz := cast(s32)inst.position.z;
|
||||
if ix == x && iy == y && iz == z then return cast(s32)i, true;
|
||||
}
|
||||
return -1, false;
|
||||
}
|
||||
|
||||
find_note_at :: (world: *World, x: s32, y: s32, z: s32) -> (idx: s32, found: bool) {
|
||||
for note, i: world.notes {
|
||||
if note.position.x == x && note.position.y == y && note.position.z == z then return cast(s32)i, true;
|
||||
}
|
||||
return -1, false;
|
||||
}
|
||||
|
||||
draw_inspector_panel :: (r: *GR.Rect, theme: *GR.Overall_Theme) {
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid then return;
|
||||
world := *curworld.world;
|
||||
|
||||
r.h = ui_h(3, 2);
|
||||
GR.label(r.*, tprint("Slot: (%, %, %)", inspector_x, inspector_y, inspector_z), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
|
||||
trile_name, orientation, has_trile := get_trile_at(world, inspector_x, inspector_y, inspector_z);
|
||||
if has_trile {
|
||||
GR.label(r.*, tprint("Trile: % orient: %", trile_name, orientation), *t_label_left(theme));
|
||||
} else {
|
||||
GR.label(r.*, "Trile: (empty)", *t_label_left(theme));
|
||||
}
|
||||
r.y += r.h;
|
||||
|
||||
r.y += r.h * 0.5;
|
||||
GR.label(r.*, "-- Emitter --", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
|
||||
emitter_idx, has_emitter := find_emitter_at(world, inspector_x, inspector_y, inspector_z);
|
||||
if has_emitter {
|
||||
inst := *world.emitter_instances[emitter_idx];
|
||||
GR.label(r.*, tprint("Def: %", inst.definition_name), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
r.h = ui_h(4, 0);
|
||||
if GR.button(r.*, "Remove Emitter", *theme.button_theme, 300) {
|
||||
array_ordered_remove_by_index(*world.emitter_instances, emitter_idx);
|
||||
}
|
||||
r.y += r.h;
|
||||
} else {
|
||||
r.h = ui_h(4, 0);
|
||||
GR.label(r.*, "Select emitter def:", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
for def, idx: g_emitter_defs {
|
||||
selected := (cast(s32)idx == inspector_emitter_def);
|
||||
if GR.button(r.*, def.name, *t_button_selectable(theme, selected), cast(s32)(200 + idx)) {
|
||||
inspector_emitter_def = cast(s32)idx;
|
||||
}
|
||||
r.y += r.h;
|
||||
}
|
||||
if inspector_emitter_def >= 0 && inspector_emitter_def < cast(s32)g_emitter_defs.count {
|
||||
if GR.button(r.*, "Add Emitter", *theme.button_theme, 301) {
|
||||
def := *g_emitter_defs[inspector_emitter_def];
|
||||
inst : Particle_Emitter_Instance;
|
||||
inst.definition = def;
|
||||
inst.definition_name = def.name;
|
||||
inst.position = .{cast(float)inspector_x + 0.5, cast(float)inspector_y, cast(float)inspector_z + 0.5};
|
||||
inst.active = true;
|
||||
array_add(*world.emitter_instances, inst);
|
||||
}
|
||||
r.y += r.h;
|
||||
}
|
||||
}
|
||||
|
||||
r.h = ui_h(3, 2);
|
||||
r.y += r.h * 0.5;
|
||||
GR.label(r.*, "-- Note --", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
|
||||
note_idx, has_note := find_note_at(world, inspector_x, inspector_y, inspector_z);
|
||||
if has_note {
|
||||
note := *world.notes[note_idx];
|
||||
r.h = ui_h(4, 0);
|
||||
a, new_text, _ := GR.text_input(r.*, inspector_note_input, *theme.text_input_theme, 500);
|
||||
if a & .ENTERED {
|
||||
note.text = copy_string(new_text);
|
||||
inspector_note_input = note.text;
|
||||
}
|
||||
r.y += r.h;
|
||||
if GR.button(r.*, "Remove Note", *theme.button_theme, 302) {
|
||||
array_ordered_remove_by_index(*world.notes, note_idx);
|
||||
inspector_note_input = "";
|
||||
}
|
||||
r.y += r.h;
|
||||
} else {
|
||||
r.h = ui_h(4, 0);
|
||||
a, new_text, _ := GR.text_input(r.*, inspector_note_input, *theme.text_input_theme, 501);
|
||||
if a & .ENTERED {
|
||||
inspector_note_input = copy_string(new_text);
|
||||
}
|
||||
r.y += r.h;
|
||||
if GR.button(r.*, "Add Note", *theme.button_theme, 303) {
|
||||
note : Editor_Note;
|
||||
note.position = .{inspector_x, inspector_y, inspector_z};
|
||||
note.text = copy_string(ifx inspector_note_input.count > 0 then inspector_note_input else "Note");
|
||||
array_add(*world.notes, note);
|
||||
inspector_note_input = note.text;
|
||||
}
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
r.h = ui_h(3, 2);
|
||||
r.y += r.h * 0.5;
|
||||
GR.label(r.*, "-- RDM --", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
|
||||
chunk_key := world_to_chunk_coord(inspector_x, inspector_y, inspector_z);
|
||||
chunk := table_find_pointer(*world.chunks, chunk_key);
|
||||
if chunk != null && chunk.rdm_valid {
|
||||
GR.label(r.*, "RDM: baked", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
GR.label(r.*, tprint("Chunk: (%, %, %)", chunk_key.x, chunk_key.y, chunk_key.z), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
|
||||
roughness_mask : u8 = 0;
|
||||
if has_trile {
|
||||
roughness_mask = get_trile_roughness_set(trile_name);
|
||||
}
|
||||
|
||||
r.h = ui_h(4, 0);
|
||||
GR.slider(r.*, *inspector_rdm_roughness, 0, 7, 1, *theme.slider_theme);
|
||||
r.y += r.h;
|
||||
r.h = ui_h(3, 2);
|
||||
|
||||
has_roughness := (roughness_mask & (1 << cast(u8)inspector_rdm_roughness)) != 0;
|
||||
if has_roughness {
|
||||
GR.label(r.*, tprint("Roughness %: present", inspector_rdm_roughness), *t_label_left(theme));
|
||||
} else {
|
||||
GR.label(r.*, tprint("Roughness %: not baked", inspector_rdm_roughness), *t_label_left(theme));
|
||||
}
|
||||
r.y += r.h;
|
||||
|
||||
available: String_Builder;
|
||||
available.allocator = temp;
|
||||
append(*available, "Available: ");
|
||||
for i: 0..7 {
|
||||
if roughness_mask & (1 << cast(u8)i) then print_to_builder(*available, "% ", i);
|
||||
}
|
||||
GR.label(r.*, builder_to_string(*available,, temp), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
|
||||
lx, ly, lz := world_to_local(inspector_x, inspector_y, inspector_z);
|
||||
lookup_idx := cast(s32)lx + cast(s32)ly * 32 + cast(s32)lz * 1024 + cast(s32)inspector_rdm_roughness * 32768;
|
||||
|
||||
uv0 := Vector2.{0, 0};
|
||||
uv1 := Vector2.{1, 0};
|
||||
uv2 := Vector2.{1, 1};
|
||||
uv3 := Vector2.{0, 1};
|
||||
has_rect := false;
|
||||
|
||||
if chunk.rdm_lookup_cpu.data != null {
|
||||
tx := lookup_idx % chunk.rdm_lookup_w;
|
||||
ty := lookup_idx / chunk.rdm_lookup_w;
|
||||
if tx >= 0 && ty >= 0 && tx < chunk.rdm_lookup_w && ty < chunk.rdm_lookup_h {
|
||||
pixel_offset := (ty * chunk.rdm_lookup_w + tx) * 4;
|
||||
rect_x := chunk.rdm_lookup_cpu[pixel_offset + 0];
|
||||
rect_y := chunk.rdm_lookup_cpu[pixel_offset + 1];
|
||||
rect_w := chunk.rdm_lookup_cpu[pixel_offset + 2];
|
||||
rect_h := chunk.rdm_lookup_cpu[pixel_offset + 3];
|
||||
if rect_w > 0 && rect_h > 0 {
|
||||
has_rect = true;
|
||||
fy0 := 1.0 - rect_y;
|
||||
fy1 := 1.0 - (rect_y + rect_h);
|
||||
uv0 = .{rect_x, fy0};
|
||||
uv1 = .{rect_x + rect_w, fy0};
|
||||
uv2 = .{rect_x + rect_w, fy1};
|
||||
uv3 = .{rect_x, fy1};
|
||||
GR.label(r.*, tprint("Rect: %.2,%.2 %.2x%.2", rect_x, rect_y, rect_w, rect_h), *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !has_rect {
|
||||
GR.label(r.*, "No RDM entry for this slot/roughness", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
} else {
|
||||
r.y += r.h * 0.5;
|
||||
tex_size := r.w * 0.9;
|
||||
tex_r : GR.Rect;
|
||||
tex_r.x = r.x + (r.w - tex_size) * 0.5;
|
||||
tex_r.y = r.y;
|
||||
tex_r.w = tex_size;
|
||||
tex_r.h = tex_size * 1.5;
|
||||
|
||||
uiTex := New(Ui_Texture,, temp);
|
||||
uiTex.tex = chunk.rdm_atlas;
|
||||
if uiTex.tex.id != INVALID_ID {
|
||||
set_shader_for_images(uiTex);
|
||||
immediate_quad(
|
||||
.{tex_r.x, tex_r.y},
|
||||
.{tex_r.x + tex_r.w, tex_r.y},
|
||||
.{tex_r.x + tex_r.w, tex_r.y + tex_r.h},
|
||||
.{tex_r.x, tex_r.y + tex_r.h},
|
||||
.{1,1,1,1},
|
||||
uv0, uv1, uv2, uv3
|
||||
);
|
||||
set_shader_for_color();
|
||||
immediate_flush();
|
||||
}
|
||||
r.y += tex_r.h + r.h * 0.5;
|
||||
}
|
||||
} else {
|
||||
GR.label(r.*, "RDM: not baked", *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
}
|
||||
}
|
||||
|
||||
add_editor_billboards :: () {
|
||||
if !editor_billboards_visible then return;
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid then return;
|
||||
|
||||
anim := get_animation_from_string("game_core.ball");
|
||||
if anim == null then return;
|
||||
|
||||
for inst: curworld.world.emitter_instances {
|
||||
task : Rendering_Task_Billboard;
|
||||
task.position = inst.position;
|
||||
task.animation = anim;
|
||||
task.frame = 0;
|
||||
add_rendering_task(task);
|
||||
}
|
||||
|
||||
for note: curworld.world.notes {
|
||||
task : Rendering_Task_Billboard;
|
||||
task.position = .{cast(float)note.position.x + 0.5, cast(float)note.position.y + 0.5, cast(float)note.position.z + 0.5};
|
||||
task.animation = anim;
|
||||
task.frame = 0;
|
||||
add_rendering_task(task);
|
||||
}
|
||||
}
|
||||
|
||||
draw_level_editor :: () {
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid then return;
|
||||
@ -963,7 +644,6 @@ draw_level_editor :: () {
|
||||
if show_trile_preview && !trile_preview_disabled {
|
||||
create_level_editor_preview_tasks();
|
||||
}
|
||||
add_editor_billboards();
|
||||
}
|
||||
|
||||
draw_level_editor_ui :: (theme: *GR.Overall_Theme) {
|
||||
@ -990,18 +670,5 @@ draw_level_editor_ui :: (theme: *GR.Overall_Theme) {
|
||||
case .INFO;
|
||||
if curworld.valid then autoedit(r, *curworld.world.conf, theme);
|
||||
}
|
||||
if current_tool_mode == .INSPECTOR {
|
||||
rr := GR.get_rect(ui_w(85,0), ui_h(5,0), ui_w(15, 0), ui_h(95, 0));
|
||||
draw_bg_rectangle(rr, theme);
|
||||
ui_add_mouse_occluder(rr);
|
||||
rr.y += ui_h(1, 0);
|
||||
if inspector_selected {
|
||||
draw_inspector_panel(*rr, theme);
|
||||
} else {
|
||||
rr.h = ui_h(3, 2);
|
||||
GR.label(rr, "Click a slot to inspect", *t_label_left(theme));
|
||||
}
|
||||
} else if current_tool_mode != .VIEWER {
|
||||
draw_picker(theme);
|
||||
}
|
||||
draw_picker(theme);
|
||||
}
|
||||
|
||||
@ -1,200 +0,0 @@
|
||||
#scope_file
|
||||
|
||||
pe_selected_index : s32 = -1;
|
||||
pe_preview_emitter : Particle_Emitter_Instance;
|
||||
|
||||
pe_cam : Camera = .{
|
||||
far = 2000.0,
|
||||
near = 1.0,
|
||||
target = .{0, 1, 0},
|
||||
position = .{5, 3, 0},
|
||||
};
|
||||
|
||||
pe_scroll : float = 0;
|
||||
|
||||
pe_name_input_start : string = "";
|
||||
pe_anim_input_start : string = "";
|
||||
|
||||
pe_label :: (r: *GR.Rect, text: string, theme: *GR.Overall_Theme) {
|
||||
GR.label(r.*, text, *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
pe_float :: (r: *GR.Rect, label: string, value: *float, lo: float, hi: float, step: float, theme: *GR.Overall_Theme, loc := #caller_location) {
|
||||
GR.label(r.*, label, *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
GR.slider(r.*, value, lo, hi, step, *theme.slider_theme, loc = loc);
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
pe_vec3 :: (r: *GR.Rect, label: string, value: *Vector3, lo: float, hi: float, theme: *GR.Overall_Theme, loc := #caller_location) {
|
||||
number_theme : GR.Number_Input_Theme;
|
||||
GR.label(r.*, label, *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
orig_w := r.w;
|
||||
orig_x := r.x;
|
||||
r.w = orig_w / 3;
|
||||
GR.number_input(r.*, tprint("%", value.x), *value.x, lo, hi, *number_theme, 0, loc);
|
||||
r.x += r.w;
|
||||
GR.number_input(r.*, tprint("%", value.y), *value.y, lo, hi, *number_theme, 1, loc);
|
||||
r.x += r.w;
|
||||
GR.number_input(r.*, tprint("%", value.z), *value.z, lo, hi, *number_theme, 2, loc);
|
||||
r.x = orig_x;
|
||||
r.w = orig_w;
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
pe_vec4 :: (r: *GR.Rect, label: string, value: *Vector4, lo: float, hi: float, theme: *GR.Overall_Theme, loc := #caller_location) {
|
||||
number_theme : GR.Number_Input_Theme;
|
||||
GR.label(r.*, label, *t_label_left(theme));
|
||||
r.y += r.h;
|
||||
orig_w := r.w;
|
||||
orig_x := r.x;
|
||||
r.w = orig_w / 4;
|
||||
GR.number_input(r.*, tprint("%", value.x), *value.x, lo, hi, *number_theme, 0, loc);
|
||||
r.x += r.w;
|
||||
GR.number_input(r.*, tprint("%", value.y), *value.y, lo, hi, *number_theme, 1, loc);
|
||||
r.x += r.w;
|
||||
GR.number_input(r.*, tprint("%", value.z), *value.z, lo, hi, *number_theme, 2, loc);
|
||||
r.x += r.w;
|
||||
GR.number_input(r.*, tprint("%", value.w), *value.w, lo, hi, *number_theme, 3, loc);
|
||||
r.x = orig_x;
|
||||
r.w = orig_w;
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
#scope_export
|
||||
|
||||
draw_particle_editor_ui :: (theme: *GR.Overall_Theme) {
|
||||
panel_w := ui_w(20, 20);
|
||||
row_h := ui_h(3, 0);
|
||||
|
||||
list_r := GR.get_rect(ui_w(80, 20), ui_h(5,0), panel_w, ui_h(95, 0));
|
||||
ui_add_mouse_occluder(list_r);
|
||||
draw_bg_rectangle(list_r, theme);
|
||||
|
||||
r := list_r;
|
||||
r.h = row_h;
|
||||
|
||||
if GR.button(r, "New Emitter", *theme.button_theme) {
|
||||
def : Particle_Emitter_Config;
|
||||
def.name = sprint("emitter_%", g_emitter_defs.count);
|
||||
array_add(*g_emitter_defs, def);
|
||||
pe_selected_index = cast(s32)(g_emitter_defs.count - 1);
|
||||
}
|
||||
r.y += r.h;
|
||||
|
||||
for def, idx : g_emitter_defs {
|
||||
if r.y > list_r.y + list_r.h - row_h * 3 then break;
|
||||
selected := (cast(s32)idx == pe_selected_index);
|
||||
if GR.button(r, def.name, *t_button_selectable(theme, selected), cast(s32)idx) {
|
||||
pe_selected_index = cast(s32)idx;
|
||||
pe_name_input_start = def.name;
|
||||
pe_anim_input_start = def.animation_name;
|
||||
}
|
||||
r.y += r.h;
|
||||
}
|
||||
|
||||
r.y = list_r.y + list_r.h - row_h * 2;
|
||||
half_w := r.w / 2;
|
||||
orig_w := r.w;
|
||||
r.w = half_w;
|
||||
if keybind_button(r, "Save", .SAVE, *theme.button_theme) {
|
||||
save_particle_defs();
|
||||
}
|
||||
r.x += half_w;
|
||||
if GR.button(r, "Delete", *theme.button_theme) {
|
||||
if pe_selected_index >= 0 && pe_selected_index < cast(s32)g_emitter_defs.count {
|
||||
array_ordered_remove_by_index(*g_emitter_defs, pe_selected_index);
|
||||
if pe_selected_index >= cast(s32)g_emitter_defs.count then pe_selected_index -= 1;
|
||||
}
|
||||
}
|
||||
r.w = orig_w;
|
||||
|
||||
if pe_selected_index < 0 || pe_selected_index >= cast(s32)g_emitter_defs.count then return;
|
||||
|
||||
def := *g_emitter_defs[pe_selected_index];
|
||||
|
||||
edit_r := GR.get_rect(0, ui_h(5,0), panel_w, ui_h(95, 0));
|
||||
ui_add_mouse_occluder(edit_r);
|
||||
draw_bg_rectangle(edit_r, theme);
|
||||
|
||||
region, inside := GR.begin_scrollable_region(edit_r, *theme.scrollable_region_theme);
|
||||
er := inside;
|
||||
er.y -= pe_scroll;
|
||||
er.h = row_h;
|
||||
|
||||
pe_label(*er, "Name", theme);
|
||||
a, new_name, _ := GR.text_input(er, pe_name_input_start, *theme.text_input_theme, 0);
|
||||
if a & .ENTERED {
|
||||
def.name = copy_string(new_name);
|
||||
pe_name_input_start = def.name;
|
||||
}
|
||||
er.y += er.h;
|
||||
|
||||
pe_label(*er, "Animation", theme);
|
||||
a2, new_anim, _ := GR.text_input(er, pe_anim_input_start, *theme.text_input_theme, 1);
|
||||
if a2 & .ENTERED {
|
||||
def.animation_name = copy_string(new_anim);
|
||||
pe_anim_input_start = def.animation_name;
|
||||
}
|
||||
er.y += er.h;
|
||||
|
||||
pe_float(*er, tprint("Emission Rate: %", def.emission_rate), *def.emission_rate, 0, 200, 1, theme);
|
||||
pe_float(*er, tprint("Lifetime Min: %", def.lifetime_min), *def.lifetime_min, 0, 10, 0.1, theme);
|
||||
pe_float(*er, tprint("Lifetime Max: %", def.lifetime_max), *def.lifetime_max, 0, 10, 0.1, theme);
|
||||
pe_vec3(*er, "Velocity", *def.velocity, -50, 50, theme);
|
||||
pe_vec3(*er, "Velocity Spread", *def.velocity_spread, 0, 50, theme);
|
||||
pe_vec3(*er, "Position Spread", *def.position_spread, 0, 10, theme);
|
||||
pe_float(*er, tprint("Size Start: %", def.size_start), *def.size_start, 0, 10, 0.05, theme);
|
||||
pe_float(*er, tprint("Size End: %", def.size_end), *def.size_end, 0, 10, 0.05, theme);
|
||||
pe_vec4(*er, "Color Start", *def.color_start, 0, 1, theme);
|
||||
pe_vec4(*er, "Color End", *def.color_end, 0, 1, theme);
|
||||
pe_float(*er, tprint("Gravity: %", def.gravity), *def.gravity, -20, 20, 0.1, theme);
|
||||
|
||||
pe_label(*er, tprint("Blend Mode: %", def.blend_mode), theme);
|
||||
orig_w2 := er.w;
|
||||
er.w = orig_w2 / 2;
|
||||
if GR.button(er, "Additive", *t_button_selectable(theme, def.blend_mode == .ADDITIVE), 100) then def.blend_mode = .ADDITIVE;
|
||||
er.x += er.w;
|
||||
if GR.button(er, "Alpha", *t_button_selectable(theme, def.blend_mode == .ALPHA), 101) then def.blend_mode = .ALPHA;
|
||||
er.x -= er.w;
|
||||
er.w = orig_w2;
|
||||
er.y += er.h;
|
||||
|
||||
er.y += er.h;
|
||||
|
||||
alive_count : s32 = 0;
|
||||
for p : g_particles {
|
||||
if p.alive then alive_count += 1;
|
||||
}
|
||||
pe_label(*er, tprint("Alive particles: %", alive_count), theme);
|
||||
pe_label(*er, tprint("Emitter defs: %", g_emitter_defs.count), theme);
|
||||
pe_label(*er, tprint("Preview active: %", pe_preview_emitter.active), theme);
|
||||
pe_label(*er, tprint("Preview def: %", pe_preview_emitter.definition != null), theme);
|
||||
anim := get_animation_from_string(def.animation_name);
|
||||
if anim != null {
|
||||
pe_label(*er, tprint("Animation: % frames", anim.frames.count), theme);
|
||||
} else {
|
||||
pe_label(*er, tprint("Animation: not found ('%')", def.animation_name), theme);
|
||||
}
|
||||
pe_label(*er, tprint("Spawn accum: %", pe_preview_emitter.spawn_accumulator), theme);
|
||||
|
||||
GR.end_scrollable_region(region, er.x + er.w, er.y, *pe_scroll);
|
||||
}
|
||||
|
||||
draw_particle_editor :: () {
|
||||
create_set_cam_rendering_task(pe_cam, 0.0);
|
||||
|
||||
if pe_selected_index >= 0 && pe_selected_index < cast(s32)g_emitter_defs.count {
|
||||
def := *g_emitter_defs[pe_selected_index];
|
||||
pe_preview_emitter.definition = def;
|
||||
pe_preview_emitter.position = .{0, 1, 0};
|
||||
pe_preview_emitter.active = true;
|
||||
tick_emitter_instance(*pe_preview_emitter, cast(float)delta_time);
|
||||
}
|
||||
}
|
||||
|
||||
tick_particle_editor :: () {
|
||||
tick_particles(cast(float)delta_time);
|
||||
}
|
||||
@ -57,6 +57,23 @@ RDM_Bake_State :: struct {
|
||||
|
||||
rdm_bake : RDM_Bake_State;
|
||||
|
||||
// Get the set of roughness values present in a trile's trixels.
|
||||
// Always includes 7 (used for diffuse light). Returns a bitmask.
|
||||
get_trile_roughness_set :: (trile_name: string) -> u8 {
|
||||
trile := get_trile(trile_name);
|
||||
mask : u8 = 1 << 7; // Always include roughness 7.
|
||||
for x: 0..15 {
|
||||
for y: 0..15 {
|
||||
for z: 0..15 {
|
||||
if !trile.trixels[x][y][z].empty {
|
||||
mask |= cast(u8)(1 << trile.trixels[x][y][z].material.roughness);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
// Predicted pixel dimensions of a single RDM entry at a given roughness level.
|
||||
// roughness 0 → 512×768, roughness 1 → 256×384, ..., roughness 7 → 4×6.
|
||||
rdm_entry_predicted_size :: (roughness: s32) -> (w: s32, h: s32) {
|
||||
|
||||
@ -26,8 +26,6 @@ Editor_Action :: enum {
|
||||
LEVEL_TOOL_BRUSH;
|
||||
LEVEL_TOOL_AREA;
|
||||
LEVEL_TOOL_LINE;
|
||||
LEVEL_TOOL_INSPECTOR;
|
||||
LEVEL_TOOL_VIEWER;
|
||||
// Level editor — tabs
|
||||
LEVEL_TAB_TOOLS;
|
||||
LEVEL_TAB_INFO;
|
||||
@ -170,8 +168,6 @@ set_default_bindings :: () {
|
||||
set(.LEVEL_TOOL_BRUSH, cast(Key_Code) #char "2");
|
||||
set(.LEVEL_TOOL_AREA, cast(Key_Code) #char "3");
|
||||
set(.LEVEL_TOOL_LINE, cast(Key_Code) #char "4");
|
||||
set(.LEVEL_TOOL_INSPECTOR, cast(Key_Code) #char "5");
|
||||
set(.LEVEL_TOOL_VIEWER, cast(Key_Code) #char "6");
|
||||
set(.LEVEL_TAB_TOOLS, cast(Key_Code) #char "T");
|
||||
set(.LEVEL_TAB_INFO, cast(Key_Code) #char "I");
|
||||
set(.LEVEL_TAB_TACOMA, cast(Key_Code) #char "X");
|
||||
|
||||
@ -17,12 +17,14 @@ _emit :: (level: Log_Level, message: string) {
|
||||
else ifx level == .ERROR then "[ERROR] "
|
||||
else "[INFO] ";
|
||||
|
||||
// Always allocate on the heap regardless of context.allocator (e.g. mesh pool).
|
||||
old_alloc := context.allocator;
|
||||
context.allocator = default_context.allocator;
|
||||
line := copy_string(tprint("%1%2", prefix, message));
|
||||
context.allocator = old_alloc;
|
||||
|
||||
print("%\n", line);
|
||||
console_add_output_line(line);
|
||||
context.allocator = old_alloc;
|
||||
}
|
||||
|
||||
#scope_export
|
||||
@ -47,17 +49,6 @@ log_info :: (fmt: string, args: ..Any) { logger(.INFO, fmt, ..args); }
|
||||
log_warn :: (fmt: string, args: ..Any) { logger(.WARN, fmt, ..args); }
|
||||
log_error :: (fmt: string, args: ..Any) { logger(.ERROR, fmt, ..args); }
|
||||
|
||||
#if FLAG_RELEASE_BUILD {
|
||||
console_add_output_line :: (s: string) {}
|
||||
console_open_ignore_input : bool = false;
|
||||
in_editor_view : bool = false;
|
||||
console_command_procs : [..]([]string) -> string;
|
||||
console_command_names : [..]string;
|
||||
verify_argument_count :: (range_start: s64, range_end: s64, count: s64) -> bool { return count >= range_start && count <= range_end; }
|
||||
tick_profiler :: () {}
|
||||
profiler_update :: () {}
|
||||
}
|
||||
|
||||
set_log_level :: (level_str: string) -> string {
|
||||
if level_str == {
|
||||
case "DEBUG"; log_min_level = .DEBUG;
|
||||
|
||||
27
src/main.jai
27
src/main.jai
@ -24,14 +24,13 @@ stbi :: #import "stb_image";
|
||||
#load "rendering/rendering.jai";
|
||||
#load "input/hotkeys.jai";
|
||||
#load "ui/ui.jai";
|
||||
#load "editor/rdm_disk.jai";
|
||||
#if !FLAG_RELEASE_BUILD { #load "editor/editor.jai"; }
|
||||
#load "editor/editor.jai";
|
||||
#load "time.jai";
|
||||
#load "events.jai";
|
||||
#load "load.jai";
|
||||
#load "ray.jai";
|
||||
#load "profiling.jai";
|
||||
#load "particles/particles.jai";
|
||||
// #load "particles/particles.jai";
|
||||
#load "world.jai";
|
||||
#load "utils.jai";
|
||||
#load "audio/audio.jai";
|
||||
@ -142,20 +141,12 @@ init_after_core :: () {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
init_editor();
|
||||
} else {
|
||||
init_keybinds();
|
||||
init_settings_menu();
|
||||
}
|
||||
init_editor();
|
||||
init_rendering();
|
||||
load_post_process_from_pack();
|
||||
|
||||
audio_init();
|
||||
|
||||
// Let the game configure engine settings before full init.
|
||||
game_engine_config();
|
||||
|
||||
// We want to do this last.
|
||||
game_init();
|
||||
|
||||
@ -197,14 +188,11 @@ frame :: () {
|
||||
|
||||
#if OS != .WASM { tick_profiler(); }
|
||||
|
||||
should_tick_game := #ifx FLAG_RELEASE_BUILD then true else !in_editor_view;
|
||||
if !in_editor_view then delta_time_accumulator += delta_time;
|
||||
|
||||
if should_tick_game then delta_time_accumulator += delta_time;
|
||||
|
||||
if should_tick_game && !settings_menu_blocks_game() {
|
||||
if !in_editor_view && !settings_menu_blocks_game() {
|
||||
while delta_time_accumulator > (1.0/480.0) {
|
||||
game_tick(1.0/480.0);
|
||||
tick_particles(1.0/480.0);
|
||||
delta_time_accumulator -= (1.0/480.0);
|
||||
}
|
||||
}
|
||||
@ -219,7 +207,7 @@ frame :: () {
|
||||
add_frame_profiling_point("After UI tick");
|
||||
|
||||
// This populates our render task queue.
|
||||
if should_tick_game then game_draw();
|
||||
if !in_editor_view then game_draw();
|
||||
add_frame_profiling_point("After game draw");
|
||||
|
||||
ui_clear_mouse_occluders();
|
||||
@ -227,8 +215,7 @@ frame :: () {
|
||||
add_frame_profiling_point("After UI draw");
|
||||
prepare_text(debug_font, tprint("frametime: % ms", latest_frametime * 1000));
|
||||
draw_prepared_text(debug_font, 10, 10, .{0.0, 1.0, 0.0, 1.0});
|
||||
#if !FLAG_RELEASE_BUILD { draw_editor(); }
|
||||
add_particle_render_tasks();
|
||||
draw_editor();
|
||||
add_frame_profiling_point("After editor draw");
|
||||
render();
|
||||
add_frame_profiling_point("After rendering");
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
#scope_export
|
||||
|
||||
Particle_Blend_Mode :: enum {
|
||||
ADDITIVE;
|
||||
ALPHA;
|
||||
}
|
||||
|
||||
Particle_Emitter_Config :: struct {
|
||||
name : string;
|
||||
animation_name : string;
|
||||
@ -13,13 +8,13 @@ Particle_Emitter_Config :: struct {
|
||||
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;
|
||||
|
||||
animation : *Animation; // resolved at runtime from animation_name
|
||||
}
|
||||
|
||||
Particle_Emitter_Instance :: struct {
|
||||
@ -46,6 +41,7 @@ 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;
|
||||
@ -57,14 +53,10 @@ tick_particles :: (dt: float) {
|
||||
p.position += p.velocity * dt;
|
||||
}
|
||||
|
||||
curworld := get_current_world();
|
||||
if !curworld.valid then return;
|
||||
for *inst: curworld.world.emitter_instances {
|
||||
for *inst: get_current_world().world.emitter_instances {
|
||||
if inst == null then continue;
|
||||
if !inst.active then continue;
|
||||
if inst.definition == null {
|
||||
inst.definition = get_emitter_def(inst.definition_name);
|
||||
if inst.definition == null then continue;
|
||||
}
|
||||
if inst.definition == null then continue;
|
||||
def := inst.definition;
|
||||
inst.spawn_accumulator += def.emission_rate * dt;
|
||||
while inst.spawn_accumulator >= 1.0 {
|
||||
@ -86,100 +78,7 @@ tick_emitter_instance :: (inst: *Particle_Emitter_Instance, dt: float) {
|
||||
}
|
||||
|
||||
add_particle_render_tasks :: () {
|
||||
Batch :: struct {
|
||||
anim_name : string;
|
||||
blend_mode : Particle_Blend_Mode;
|
||||
anim : *Animation;
|
||||
task : *Rendering_Task_Particles;
|
||||
count : s32;
|
||||
}
|
||||
|
||||
batches : [..] Batch;
|
||||
batches.allocator = temp;
|
||||
|
||||
for *p: g_particles {
|
||||
if !p.alive then continue;
|
||||
|
||||
if p.definition == null 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;
|
||||
array_add(*batches, .{ anim_name = def.animation_name, blend_mode = def.blend_mode, anim = anim, task = New(Rendering_Task_Particles,, temp) });
|
||||
batch = *batches[batches.count - 1];
|
||||
batch.task.sheet = anim.sheet;
|
||||
batch.task.blend_mode = def.blend_mode;
|
||||
}
|
||||
|
||||
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.task.pos_size[idx] = .{p.position.x, p.position.y, p.position.z, size};
|
||||
|
||||
anim := batch.anim;
|
||||
frame_count := anim.frames.count;
|
||||
if frame_count == 0 then continue;
|
||||
frame := cast(s32)(t * cast(float)(frame_count - 1));
|
||||
if frame < 0 then frame = 0;
|
||||
if frame >= frame_count then frame = cast(s32)(frame_count - 1);
|
||||
f := anim.frames[frame];
|
||||
batch.task.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.task.colors[idx] = col;
|
||||
batch.count += 1;
|
||||
}
|
||||
|
||||
for *b: batches {
|
||||
if b.count > 0 {
|
||||
b.task.count = b.count;
|
||||
add_rendering_task(b.task.*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// TODO
|
||||
}
|
||||
|
||||
get_emitter_def_names :: () -> []string {
|
||||
@ -191,12 +90,15 @@ get_emitter_def_names :: () -> []string {
|
||||
return names;
|
||||
}
|
||||
|
||||
save_particle_defs :: () {
|
||||
save_particle_definition :: (def: Particle_Emitter_Config) {
|
||||
#if OS != .WASM {
|
||||
file :: #import "File";
|
||||
json := Jaison.json_write_string(g_emitter_defs, " ");
|
||||
file.write_entire_file("./game/resources/game_core/particles.json", json);
|
||||
log_info("Saved % particle definitions", g_emitter_defs.count);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,11 +116,7 @@ spawn_one_particle :: (position: Vector3, def: *Particle_Emitter_Config) {
|
||||
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.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),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
g_animations: Table(string, Animation);
|
||||
|
||||
#scope_file
|
||||
get_animation_from_string :: (animation: string) -> *Animation {
|
||||
ok, pack, anim := split_from_left(animation, ".");
|
||||
if !ok {
|
||||
@ -8,6 +9,7 @@ get_animation_from_string :: (animation: string) -> *Animation {
|
||||
}
|
||||
return get_animation_from_pack(pack, anim);
|
||||
}
|
||||
#scope_export
|
||||
|
||||
Animation_Player :: struct {
|
||||
current_animation : *Animation;
|
||||
|
||||
@ -22,7 +22,6 @@ Render_Command_Type :: enum {
|
||||
DRAW_TRIXELS;
|
||||
SET_LIGHT;
|
||||
DRAW_BILLBOARD;
|
||||
DRAW_PARTICLES;
|
||||
}
|
||||
|
||||
Render_Command :: struct {
|
||||
@ -80,17 +79,6 @@ Render_Command_Draw_Trixels :: struct {
|
||||
c.type = .DRAW_TRIXELS;
|
||||
}
|
||||
|
||||
Render_Command_Draw_Particles :: struct {
|
||||
#as using c : Render_Command;
|
||||
c.type = .DRAW_PARTICLES;
|
||||
count : s32;
|
||||
blend_mode : Particle_Blend_Mode;
|
||||
sheet : sg_image;
|
||||
pos_size : [2048]Vector4;
|
||||
uv_rects : [2048]Vector4;
|
||||
colors : [2048]Vector4;
|
||||
}
|
||||
|
||||
Render_Command_Draw_Ground :: struct {
|
||||
#as using c : Render_Command;
|
||||
c.type = .DRAW_GROUND;
|
||||
|
||||
@ -50,9 +50,6 @@ backend_handle_command :: (cmd: *Render_Command) {
|
||||
} else {
|
||||
backend_gbuffer_draw_billboard(command.position, command.animation, command.frame, command.flipX);
|
||||
}
|
||||
case .DRAW_PARTICLES;
|
||||
particles_cmd := cast(*Render_Command_Draw_Particles)cmd;
|
||||
backend_draw_particles(particles_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,36 +346,6 @@ backend_gbuffer_draw_billboard :: (position: Vector3, anim: *Animation, frame_id
|
||||
sg_draw(0, 6, 1);
|
||||
}
|
||||
|
||||
backend_draw_particles :: (cmd: *Render_Command_Draw_Particles) {
|
||||
if cmd.count <= 0 then return;
|
||||
|
||||
pip := ifx cmd.blend_mode == .ALPHA then *gPipelines.particle_alpha else *gPipelines.particle_additive;
|
||||
|
||||
sg_update_buffer(pip.bind.vertex_buffers[1], *(sg_range.{
|
||||
ptr = cmd.pos_size.data,
|
||||
size = cast(u64)(cmd.count * size_of(Vector4)),
|
||||
}));
|
||||
sg_update_buffer(pip.bind.vertex_buffers[2], *(sg_range.{
|
||||
ptr = cmd.uv_rects.data,
|
||||
size = cast(u64)(cmd.count * size_of(Vector4)),
|
||||
}));
|
||||
sg_update_buffer(pip.bind.vertex_buffers[3], *(sg_range.{
|
||||
ptr = cmd.colors.data,
|
||||
size = cast(u64)(cmd.count * size_of(Vector4)),
|
||||
}));
|
||||
|
||||
mvp := create_viewproj(*camera);
|
||||
vs_params : Particle_Vs_Params;
|
||||
vs_params.mvp = mvp.floats;
|
||||
vs_params.cam = camera.position.component;
|
||||
|
||||
sg_apply_pipeline(pip.pipeline);
|
||||
pip.bind.images[IMG_particle_sprite] = cmd.sheet;
|
||||
sg_apply_bindings(*pip.bind);
|
||||
sg_apply_uniforms(UB_particle_vs_params, *(sg_range.{ ptr = *vs_params, size = size_of(type_of(vs_params)) }));
|
||||
sg_draw(0, 6, cmd.count);
|
||||
}
|
||||
|
||||
backend_draw_ground_gbuf :: (wc: *World_Config) {
|
||||
mvp := create_viewproj(*camera);
|
||||
view := create_lookat(*camera);
|
||||
|
||||
@ -73,9 +73,7 @@ gPipelines : struct {
|
||||
|
||||
gbuffer_billboard : Pipeline_Binding;
|
||||
|
||||
particle_additive : Pipeline_Binding;
|
||||
particle_alpha : Pipeline_Binding;
|
||||
|
||||
// Renders the SSAO texture using things from the gbuffer pass.
|
||||
ssao: Pipeline_Binding;
|
||||
}
|
||||
|
||||
@ -154,7 +152,6 @@ create_pipelines :: () {
|
||||
create_mix_pipeline();
|
||||
create_billboard_pipeline();
|
||||
create_gbuffer_billboard_pipeline();
|
||||
create_particle_pipeline();
|
||||
|
||||
create_shadowmap_image();
|
||||
create_final_image();
|
||||
@ -1188,87 +1185,3 @@ create_gbuffer_impostors :: () {
|
||||
g_plane_gbuffer_instance_buffer = sg_make_buffer(*instance_buffer);
|
||||
|
||||
}
|
||||
|
||||
create_particle_pipeline :: () {
|
||||
pipeline: sg_pipeline_desc;
|
||||
shader_desc := particle_shader_desc(sg_query_backend());
|
||||
shd := sg_make_shader(*shader_desc);
|
||||
pipeline.shader = shd;
|
||||
|
||||
pipeline.layout.buffers[0].stride = 4 * 3;
|
||||
pipeline.layout.buffers[1].stride = 4 * 4;
|
||||
pipeline.layout.buffers[1].step_func = .PER_INSTANCE;
|
||||
pipeline.layout.buffers[2].stride = 4 * 4;
|
||||
pipeline.layout.buffers[2].step_func = .PER_INSTANCE;
|
||||
pipeline.layout.buffers[3].stride = 4 * 4;
|
||||
pipeline.layout.buffers[3].step_func = .PER_INSTANCE;
|
||||
|
||||
pipeline.layout.attrs[ATTR_particle_position] = .{ format = .FLOAT3, buffer_index = 0 };
|
||||
pipeline.layout.attrs[ATTR_particle_inst_pos_size] = .{ format = .FLOAT4, buffer_index = 1 };
|
||||
pipeline.layout.attrs[ATTR_particle_inst_uv_rect] = .{ format = .FLOAT4, buffer_index = 2 };
|
||||
pipeline.layout.attrs[ATTR_particle_inst_color] = .{ format = .FLOAT4, buffer_index = 3 };
|
||||
|
||||
pipeline.index_type = .UINT16;
|
||||
pipeline.depth = .{
|
||||
write_enabled = false,
|
||||
compare = .LESS_EQUAL,
|
||||
pixel_format = .DEPTH,
|
||||
};
|
||||
pipeline.color_count = 1;
|
||||
|
||||
pipeline.colors[0] = .{
|
||||
pixel_format = .RGBA32F,
|
||||
blend = .{
|
||||
enabled = true,
|
||||
src_factor_rgb = .SRC_ALPHA,
|
||||
dst_factor_rgb = .ONE,
|
||||
src_factor_alpha = .ONE,
|
||||
dst_factor_alpha = .ONE,
|
||||
},
|
||||
};
|
||||
gPipelines.particle_additive.pipeline = sg_make_pipeline(*pipeline);
|
||||
|
||||
pipeline.colors[0].blend = .{
|
||||
enabled = true,
|
||||
src_factor_rgb = .SRC_ALPHA,
|
||||
dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
||||
src_factor_alpha = .ONE,
|
||||
dst_factor_alpha = .ONE_MINUS_SRC_ALPHA,
|
||||
};
|
||||
gPipelines.particle_alpha.pipeline = sg_make_pipeline(*pipeline);
|
||||
|
||||
vertices: [4]Vector3 = .[
|
||||
.{ 0.0, 0.0, 0.0 },
|
||||
.{ 1.0, 0.0, 0.0 },
|
||||
.{ 1.0, 1.0, 0.0 },
|
||||
.{ 0.0, 1.0, 0.0 },
|
||||
];
|
||||
|
||||
indices: [6]u16 = .[
|
||||
0, 1, 2,
|
||||
0, 2, 3,
|
||||
];
|
||||
|
||||
idx_buf := sg_make_buffer(*(sg_buffer_desc.{ type = .INDEXBUFFER, data = .{ ptr = indices.data, size = 6 * 2 } }));
|
||||
vtx_buf := sg_make_buffer(*(sg_buffer_desc.{ data = .{ ptr = vertices.data, size = 4 * 3 * 4 } }));
|
||||
inst1 := sg_make_buffer(*(sg_buffer_desc.{ size = cast(u64)(MAX_PARTICLES * size_of(Vector4)), usage = .STREAM }));
|
||||
inst2 := sg_make_buffer(*(sg_buffer_desc.{ size = cast(u64)(MAX_PARTICLES * size_of(Vector4)), usage = .STREAM }));
|
||||
inst3 := sg_make_buffer(*(sg_buffer_desc.{ size = cast(u64)(MAX_PARTICLES * size_of(Vector4)), usage = .STREAM }));
|
||||
smp := sg_make_sampler(*(sg_sampler_desc.{
|
||||
wrap_u = .CLAMP_TO_EDGE,
|
||||
wrap_v = .CLAMP_TO_EDGE,
|
||||
min_filter = .LINEAR,
|
||||
mag_filter = .LINEAR,
|
||||
}));
|
||||
|
||||
setup_bind :: (bind: *sg_bindings, idx_buf: sg_buffer, vtx_buf: sg_buffer, inst1: sg_buffer, inst2: sg_buffer, inst3: sg_buffer, smp: sg_sampler) {
|
||||
bind.index_buffer = idx_buf;
|
||||
bind.vertex_buffers[0] = vtx_buf;
|
||||
bind.vertex_buffers[1] = inst1;
|
||||
bind.vertex_buffers[2] = inst2;
|
||||
bind.vertex_buffers[3] = inst3;
|
||||
bind.samplers[SMP_particle_spritesmp] = smp;
|
||||
}
|
||||
setup_bind(*gPipelines.particle_additive.bind, idx_buf, vtx_buf, inst1, inst2, inst3, smp);
|
||||
setup_bind(*gPipelines.particle_alpha.bind, idx_buf, vtx_buf, inst1, inst2, inst3, smp);
|
||||
}
|
||||
|
||||
@ -67,17 +67,6 @@ Rendering_Task_Trixels :: struct {
|
||||
trile : *Trile;
|
||||
}
|
||||
|
||||
Rendering_Task_Particles :: struct {
|
||||
#as using t : Rendering_Task;
|
||||
t.type = .PARTICLES;
|
||||
count : s32;
|
||||
blend_mode : Particle_Blend_Mode;
|
||||
sheet : sg_image;
|
||||
pos_size : [2048]Vector4;
|
||||
uv_rects : [2048]Vector4;
|
||||
colors : [2048]Vector4;
|
||||
}
|
||||
|
||||
Rendering_Task_Set_Camera :: struct {
|
||||
#as using t : Rendering_Task;
|
||||
camera : Camera;
|
||||
@ -162,16 +151,6 @@ tasks_to_commands :: () {
|
||||
array_add(*render_command_buckets.main, commandDrawBillboard);
|
||||
array_add(*render_command_buckets.shadow, commandDrawBillboard);
|
||||
array_add(*render_command_buckets.reflection, commandDrawBillboard);
|
||||
case .PARTICLES;
|
||||
particleTask := (cast(*Rendering_Task_Particles)it);
|
||||
drawCmd := New(Render_Command_Draw_Particles,, temp);
|
||||
drawCmd.count = particleTask.count;
|
||||
drawCmd.blend_mode = particleTask.blend_mode;
|
||||
drawCmd.sheet = particleTask.sheet;
|
||||
memcpy(drawCmd.pos_size.data, particleTask.pos_size.data, particleTask.count * size_of(Vector4));
|
||||
memcpy(drawCmd.uv_rects.data, particleTask.uv_rects.data, particleTask.count * size_of(Vector4));
|
||||
memcpy(drawCmd.colors.data, particleTask.colors.data, particleTask.count * size_of(Vector4));
|
||||
array_add(*render_command_buckets.main, drawCmd);
|
||||
case .SET_CAMERA;
|
||||
task := (cast(*Rendering_Task_Set_Camera)it);
|
||||
command := New(Render_Command_Set_Camera,, temp);
|
||||
|
||||
@ -1,81 +1,25 @@
|
||||
#scope_file
|
||||
|
||||
Random :: #import "Random";
|
||||
|
||||
TRANSITION_SPEED :: 7.0;
|
||||
MAX_BLOBS :: 32;
|
||||
VOLUME_STEP_INITIAL :: 0.1;
|
||||
VOLUME_STEP_HELD :: 0.3;
|
||||
VOLUME_HOLD_DELAY :: 0.35;
|
||||
CONFIG_PATH :: "settings.cfg";
|
||||
TRANSITION_SPEED :: 3.0;
|
||||
|
||||
Settings_Page :: enum {
|
||||
MAIN;
|
||||
SETTINGS;
|
||||
AUDIO;
|
||||
GRAPHICS;
|
||||
}
|
||||
|
||||
Settings_State :: struct {
|
||||
open : bool = false;
|
||||
transition : float = 0.0;
|
||||
page : Settings_Page = .MAIN;
|
||||
cursor : s32 = 0;
|
||||
title_font : *Font;
|
||||
item_font : *Font;
|
||||
last_screen_h : s32 = 0;
|
||||
|
||||
vol_hold_time : float = 0.0;
|
||||
vol_hold_dir : s32 = 0;
|
||||
|
||||
mouse_hover : s32 = -1;
|
||||
mouse_moved : bool = false;
|
||||
last_mouse_x : float = -1;
|
||||
last_mouse_y : float = -1;
|
||||
|
||||
blobs : [MAX_BLOBS]Blob;
|
||||
blob_count : s32 = 0;
|
||||
}
|
||||
|
||||
Blob :: struct {
|
||||
cx, cy, radius : float;
|
||||
r, g, b : float;
|
||||
freq : float;
|
||||
drift_x : float;
|
||||
drift_y : float;
|
||||
phase : float;
|
||||
open : bool = false;
|
||||
transition : float = 0.0;
|
||||
page : Settings_Page = .MAIN;
|
||||
cursor : s32 = 0;
|
||||
title_font : *Font;
|
||||
item_font : *Font;
|
||||
}
|
||||
|
||||
g_settings : Settings_State;
|
||||
g_settings_config : Settings_Menu_Config;
|
||||
|
||||
MAIN_ITEMS :: string.["Resume", "Settings", "Exit"];
|
||||
SETTINGS_ITEMS :: string.["Audio", "Graphics"];
|
||||
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
||||
GRAPHICS_ITEMS :: string.["Fullscreen"];
|
||||
|
||||
page_items :: () -> []string {
|
||||
if g_settings.page == .MAIN return MAIN_ITEMS;
|
||||
if g_settings.page == .SETTINGS return SETTINGS_ITEMS;
|
||||
if g_settings.page == .AUDIO return AUDIO_LABELS;
|
||||
if g_settings.page == .GRAPHICS return GRAPHICS_ITEMS;
|
||||
return .[];
|
||||
}
|
||||
|
||||
page_parent :: () -> Settings_Page {
|
||||
if g_settings.page == .AUDIO return .SETTINGS;
|
||||
if g_settings.page == .GRAPHICS return .SETTINGS;
|
||||
if g_settings.page == .SETTINGS return .MAIN;
|
||||
return .MAIN;
|
||||
}
|
||||
|
||||
page_hint :: () -> string {
|
||||
if g_settings.page == .MAIN return "Up/Down navigate Enter select";
|
||||
if g_settings.page == .SETTINGS return "Up/Down navigate Enter select Esc back";
|
||||
if g_settings.page == .AUDIO return "Up/Down navigate Left/Right adjust Esc back";
|
||||
if g_settings.page == .GRAPHICS return "Enter toggle Esc back";
|
||||
return "";
|
||||
}
|
||||
MAIN_ITEMS :: string.["Audio"];
|
||||
AUDIO_LABELS :: string.["Master Volume", "Music Volume", "Sound Effects"];
|
||||
|
||||
audio_get :: (i: s32) -> float {
|
||||
if i == 0 return g_mixer.config.masterVolume;
|
||||
@ -90,188 +34,14 @@ audio_set :: (i: s32, v: float) {
|
||||
if i == 2 then g_mixer.config.soundEffectVolume = clamp(v, 0.0, 1.0);
|
||||
}
|
||||
|
||||
get_item_label :: (page: Settings_Page, index: s32) -> string {
|
||||
items := page_items();
|
||||
if index < 0 || index >= items.count then return "";
|
||||
|
||||
if page == .AUDIO {
|
||||
pct := cast(s32)(audio_get(index) * 100.0 + 0.5);
|
||||
return tprint("% %", items[index], make_volume_bar(pct));
|
||||
}
|
||||
if page == .GRAPHICS && index == 0 {
|
||||
return tprint("%: %", items[index], ifx sapp_is_fullscreen() then "On" else "Off");
|
||||
}
|
||||
return items[index];
|
||||
}
|
||||
|
||||
rand_range :: (lo: float, hi: float) -> float {
|
||||
return lo + Random.random_get_zero_to_one() * (hi - lo);
|
||||
}
|
||||
|
||||
generate_blobs :: () {
|
||||
cfg := *g_settings_config.phosphenes;
|
||||
count := clamp(cfg.blob_count, 0, MAX_BLOBS);
|
||||
g_settings.blob_count = count;
|
||||
|
||||
for i: 0..count-1 {
|
||||
blob := *g_settings.blobs[i];
|
||||
blob.cx = rand_range(0.05, 0.95);
|
||||
blob.cy = rand_range(0.15, 0.85);
|
||||
blob.radius = rand_range(cfg.radius_min, cfg.radius_max);
|
||||
blob.freq = rand_range(cfg.freq_min, cfg.freq_max);
|
||||
blob.drift_x = rand_range(cfg.drift_min, cfg.drift_max);
|
||||
blob.drift_y = rand_range(cfg.drift_min, cfg.drift_max);
|
||||
blob.phase = rand_range(0.0, 6.28);
|
||||
|
||||
if cfg.palette.count > 0 {
|
||||
idx := cast(s32)(Random.random_get_zero_to_one() * cast(float)(cfg.palette.count - 1) + 0.5);
|
||||
idx = clamp(idx, 0, cast(s32)(cfg.palette.count - 1));
|
||||
base := cfg.palette[idx];
|
||||
blob.r = clamp(base.x + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
||||
blob.g = clamp(base.y + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
||||
blob.b = clamp(base.z + rand_range(-cfg.color_jitter, cfg.color_jitter), 0.0, 1.0);
|
||||
} else {
|
||||
blob.r = rand_range(0.1, 0.5);
|
||||
blob.g = rand_range(0.1, 0.5);
|
||||
blob.b = rand_range(0.1, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
update_fonts_for_screen :: () {
|
||||
_, h := get_window_size();
|
||||
if h == g_settings.last_screen_h then return;
|
||||
g_settings.last_screen_h = h;
|
||||
g_settings.title_font = get_font_at_size(max(cast(s32)(cast(float)h * 0.055), 20));
|
||||
g_settings.item_font = get_font_at_size(max(cast(s32)(cast(float)h * 0.028), 14));
|
||||
}
|
||||
|
||||
save_settings :: () {
|
||||
file :: #import "File";
|
||||
builder : String_Builder;
|
||||
append(*builder, tprint("master_volume %\n", g_mixer.config.masterVolume));
|
||||
append(*builder, tprint("music_volume %\n", g_mixer.config.musicVolume));
|
||||
append(*builder, tprint("sfx_volume %\n", g_mixer.config.soundEffectVolume));
|
||||
append(*builder, tprint("fullscreen %\n", cast(s32) sapp_is_fullscreen()));
|
||||
file.write_entire_file(CONFIG_PATH, builder_to_string(*builder,, allocator = temp));
|
||||
}
|
||||
|
||||
load_settings :: () {
|
||||
file :: #import "File";
|
||||
data, ok := file.read_entire_file(CONFIG_PATH,, allocator = temp);
|
||||
if !ok then return;
|
||||
|
||||
text := cast(string) data;
|
||||
while text.count > 0 {
|
||||
line := consume_line(*text);
|
||||
if line.count == 0 then continue;
|
||||
key, val := split_first(line, #char " ");
|
||||
if key == "master_volume" then g_mixer.config.masterVolume = parse_float(val);
|
||||
if key == "music_volume" then g_mixer.config.musicVolume = parse_float(val);
|
||||
if key == "sfx_volume" then g_mixer.config.soundEffectVolume = parse_float(val);
|
||||
if key == "fullscreen" {
|
||||
if (parse_int(val) != 0) != sapp_is_fullscreen() then sapp_toggle_fullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
consume_line :: (text: *string) -> string {
|
||||
s := text.*;
|
||||
for i: 0..s.count-1 {
|
||||
if s[i] == #char "\n" {
|
||||
line := string.{count = i, data = s.data};
|
||||
text.data += i + 1;
|
||||
text.count -= i + 1;
|
||||
return line;
|
||||
}
|
||||
}
|
||||
result := s;
|
||||
text.count = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
split_first :: (s: string, sep: u8) -> (string, string) {
|
||||
for i: 0..s.count-1 {
|
||||
if s[i] == sep {
|
||||
return string.{count = i, data = s.data},
|
||||
string.{count = s.count - i - 1, data = s.data + i + 1};
|
||||
}
|
||||
}
|
||||
return s, "";
|
||||
}
|
||||
|
||||
parse_float :: (s: string) -> float {
|
||||
val, ok := string_to_float(s);
|
||||
if ok return val;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
parse_int :: (s: string) -> s64 {
|
||||
val, ok := string_to_int(s);
|
||||
if ok return val;
|
||||
page_count :: () -> s32 {
|
||||
if g_settings.page == .MAIN return MAIN_ITEMS.count;
|
||||
if g_settings.page == .AUDIO return AUDIO_LABELS.count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
navigate_to :: (page: Settings_Page) {
|
||||
g_settings.page = page;
|
||||
g_settings.cursor = 0;
|
||||
}
|
||||
|
||||
go_back :: () {
|
||||
if g_settings.page == .AUDIO || g_settings.page == .GRAPHICS then save_settings();
|
||||
if g_settings.page == .MAIN {
|
||||
g_settings.open = false;
|
||||
} else {
|
||||
navigate_to(page_parent());
|
||||
}
|
||||
}
|
||||
|
||||
handle_enter :: () {
|
||||
if g_settings.page == {
|
||||
case .MAIN;
|
||||
if g_settings.cursor == 0 then g_settings.open = false;
|
||||
if g_settings.cursor == 1 then navigate_to(.SETTINGS);
|
||||
if g_settings.cursor == 2 { save_settings(); sapp_request_quit(); }
|
||||
case .SETTINGS;
|
||||
if g_settings.cursor == 0 then navigate_to(.AUDIO);
|
||||
if g_settings.cursor == 1 then navigate_to(.GRAPHICS);
|
||||
case .GRAPHICS;
|
||||
if g_settings.cursor == 0 then sapp_toggle_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
#scope_export
|
||||
|
||||
Phosphene_Config :: struct {
|
||||
blob_count : s32 = 12;
|
||||
alpha : float = 0.06;
|
||||
layers : s32 = 4;
|
||||
radius_min : float = 0.08;
|
||||
radius_max : float = 0.28;
|
||||
freq_min : float = 0.1;
|
||||
freq_max : float = 0.4;
|
||||
drift_min : float = 0.02;
|
||||
drift_max : float = 0.07;
|
||||
color_jitter : float = 0.1;
|
||||
palette : []Vector3;
|
||||
}
|
||||
|
||||
Settings_Menu_Config :: struct {
|
||||
game_title : string = "TRUENO";
|
||||
credits : string = "Trueno engine by Tuomas Katajisto 2026";
|
||||
bg_color : Vector4 = .{0.04, 0.04, 0.06, 1.0};
|
||||
title_color : Vector4 = .{1.0, 1.0, 1.0, 1.0};
|
||||
item_color : Vector4 = .{0.45, 0.45, 0.45, 1.0};
|
||||
selected_color : Vector4 = .{1.0, 0.95, 0.65, 1.0};
|
||||
hint_color : Vector4 = .{0.45, 0.45, 0.45, 1.0};
|
||||
credits_color : Vector4 = .{0.3, 0.3, 0.3, 1.0};
|
||||
phosphenes : Phosphene_Config;
|
||||
}
|
||||
|
||||
set_settings_config :: (config: Settings_Menu_Config) {
|
||||
g_settings_config = config;
|
||||
}
|
||||
|
||||
settings_menu_blocks_game :: () -> bool {
|
||||
return g_settings.open;
|
||||
}
|
||||
@ -279,20 +49,21 @@ settings_menu_blocks_game :: () -> bool {
|
||||
init_settings_menu :: () {
|
||||
g_settings.title_font = get_font_at_size(60);
|
||||
g_settings.item_font = get_font_at_size(30);
|
||||
load_settings();
|
||||
}
|
||||
|
||||
tick_settings_menu :: () {
|
||||
if in_editor_view then return;
|
||||
update_fonts_for_screen();
|
||||
|
||||
if is_action_start(Editor_Action.TOGGLE_SETTINGS) {
|
||||
if !g_settings.open {
|
||||
g_settings.open = true;
|
||||
navigate_to(.MAIN);
|
||||
generate_blobs();
|
||||
g_settings.open = true;
|
||||
g_settings.page = .MAIN;
|
||||
g_settings.cursor = 0;
|
||||
} else if g_settings.page != .MAIN {
|
||||
g_settings.page = .MAIN;
|
||||
g_settings.cursor = 0;
|
||||
} else {
|
||||
go_back();
|
||||
g_settings.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -306,53 +77,29 @@ tick_settings_menu :: () {
|
||||
if !g_settings.open then return;
|
||||
if console_open_ignore_input then return;
|
||||
|
||||
count := cast(s32) page_items().count;
|
||||
count := page_count();
|
||||
|
||||
mx := input_mouse_x;
|
||||
my := input_mouse_y;
|
||||
g_settings.mouse_moved = (mx != g_settings.last_mouse_x || my != g_settings.last_mouse_y);
|
||||
g_settings.last_mouse_x = mx;
|
||||
g_settings.last_mouse_y = my;
|
||||
up := cast(bool)(input_button_states[Key_Code.ARROW_UP] & .START);
|
||||
down := cast(bool)(input_button_states[Key_Code.ARROW_DOWN] & .START);
|
||||
left := cast(bool)(input_button_states[Key_Code.ARROW_LEFT] & .START);
|
||||
right := cast(bool)(input_button_states[Key_Code.ARROW_RIGHT]& .START);
|
||||
enter := cast(bool)(input_button_states[Key_Code.ENTER] & .START);
|
||||
|
||||
if g_settings.mouse_moved && g_settings.mouse_hover >= 0 && g_settings.mouse_hover < count {
|
||||
g_settings.cursor = g_settings.mouse_hover;
|
||||
}
|
||||
|
||||
up := cast(bool)(input_button_states[Key_Code.ARROW_UP] & .START);
|
||||
down := cast(bool)(input_button_states[Key_Code.ARROW_DOWN] & .START);
|
||||
if up then g_settings.cursor = (g_settings.cursor - 1 + count) % count;
|
||||
if down then g_settings.cursor = (g_settings.cursor + 1) % count;
|
||||
|
||||
enter := cast(bool)(input_button_states[Key_Code.ENTER] & .START);
|
||||
click := cast(bool)(input_button_states[Key_Code.MOUSE_BUTTON_LEFT] & .START);
|
||||
if click && g_settings.mouse_hover >= 0 && g_settings.mouse_hover < count {
|
||||
g_settings.cursor = g_settings.mouse_hover;
|
||||
enter = true;
|
||||
if g_settings.page == .MAIN && enter {
|
||||
if g_settings.cursor == 0 {
|
||||
g_settings.page = .AUDIO;
|
||||
g_settings.cursor = 0;
|
||||
}
|
||||
}
|
||||
if enter then handle_enter();
|
||||
|
||||
if g_settings.page == .AUDIO {
|
||||
left := cast(bool)(input_button_states[Key_Code.ARROW_LEFT] & .START);
|
||||
right := cast(bool)(input_button_states[Key_Code.ARROW_RIGHT] & .START);
|
||||
left_held := cast(bool)(input_button_states[Key_Code.ARROW_LEFT] & .DOWN);
|
||||
right_held := cast(bool)(input_button_states[Key_Code.ARROW_RIGHT] & .DOWN);
|
||||
|
||||
if left { audio_set(g_settings.cursor, audio_get(g_settings.cursor) - VOLUME_STEP_INITIAL); g_settings.vol_hold_time = 0; g_settings.vol_hold_dir = -1; }
|
||||
if right { audio_set(g_settings.cursor, audio_get(g_settings.cursor) + VOLUME_STEP_INITIAL); g_settings.vol_hold_time = 0; g_settings.vol_hold_dir = 1; }
|
||||
|
||||
dir : s32 = 0;
|
||||
if left_held then dir = -1;
|
||||
if right_held then dir = 1;
|
||||
|
||||
if dir != 0 && dir == g_settings.vol_hold_dir {
|
||||
g_settings.vol_hold_time += dt;
|
||||
if g_settings.vol_hold_time > VOLUME_HOLD_DELAY {
|
||||
audio_set(g_settings.cursor, audio_get(g_settings.cursor) + cast(float)dir * VOLUME_STEP_HELD * dt);
|
||||
}
|
||||
} else if dir == 0 {
|
||||
g_settings.vol_hold_time = 0;
|
||||
g_settings.vol_hold_dir = 0;
|
||||
}
|
||||
delta : float = 0.0;
|
||||
if left then delta = -0.1;
|
||||
if right then delta = 0.1;
|
||||
if delta != 0.0 then audio_set(g_settings.cursor, audio_get(g_settings.cursor) + delta);
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,109 +107,83 @@ draw_settings_menu :: (theme: *GR.Overall_Theme) {
|
||||
t := g_settings.transition;
|
||||
if t < 0.001 then return;
|
||||
|
||||
fw := vw * 100.0;
|
||||
fh := vh * 100.0;
|
||||
bar_h := t * fh * 0.5;
|
||||
fw := vw * 100.0;
|
||||
fh := vh * 100.0;
|
||||
half_h := fh * 0.5;
|
||||
bar_h := t * half_h;
|
||||
|
||||
cfg := *g_settings_config;
|
||||
bg := Vector4.{0.04, 0.04, 0.06, 1.0};
|
||||
set_shader_for_color();
|
||||
|
||||
immediate_quad(.{0, 0}, .{fw, 0}, .{fw, bar_h}, .{0, bar_h}, cfg.bg_color);
|
||||
// Top eyelid sliding down
|
||||
immediate_quad(.{0, 0}, .{fw, 0}, .{fw, bar_h}, .{0, bar_h}, bg);
|
||||
immediate_flush();
|
||||
|
||||
// Bottom eyelid sliding up
|
||||
bottom_y := fh - bar_h;
|
||||
immediate_quad(.{0, bottom_y}, .{fw, bottom_y}, .{fw, fh}, .{0, fh}, cfg.bg_color);
|
||||
immediate_quad(.{0, bottom_y}, .{fw, bottom_y}, .{fw, fh}, .{0, fh}, bg);
|
||||
immediate_flush();
|
||||
|
||||
if t > 0.5 {
|
||||
draw_phosphenes(fw, fh, cast(float) get_time(), clamp((t - 0.5) / 0.5, 0.0, 1.0));
|
||||
}
|
||||
|
||||
// Content fades in during the last quarter of the transition
|
||||
content_alpha := clamp((t - 0.75) / 0.25, 0.0, 1.0);
|
||||
if content_alpha < 0.01 then return;
|
||||
|
||||
apply_alpha :: (base: Vector4, a: float) -> Vector4 {
|
||||
return .{base.x, base.y, base.z, base.w * a};
|
||||
}
|
||||
white := Vector4.{1.0, 1.0, 1.0, content_alpha};
|
||||
dim := Vector4.{0.45, 0.45, 0.45, content_alpha};
|
||||
selected := Vector4.{1.0, 0.95, 0.65, content_alpha};
|
||||
|
||||
title_col := apply_alpha(cfg.title_color, content_alpha);
|
||||
item_col := apply_alpha(cfg.item_color, content_alpha);
|
||||
sel_col := apply_alpha(cfg.selected_color, content_alpha);
|
||||
hint_col := apply_alpha(cfg.hint_color, content_alpha);
|
||||
credits_col := apply_alpha(cfg.credits_color, content_alpha);
|
||||
// Game title
|
||||
prepare_text(g_settings.title_font, "BEACHBALL");
|
||||
title_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||
draw_prepared_text(g_settings.title_font, xx title_x, xx (fh * 0.22), white);
|
||||
|
||||
prepare_text(g_settings.title_font, cfg.game_title);
|
||||
draw_prepared_text(g_settings.title_font, xx ((fw - cast(float)gPreppedTextWidth) * 0.5), xx (fh * 0.22), title_col);
|
||||
item_h := cast(float)(g_settings.item_font.character_height) * 1.7;
|
||||
|
||||
items := page_items();
|
||||
item_h := cast(float)(g_settings.item_font.character_height) * 1.7;
|
||||
total_h := cast(float)items.count * item_h;
|
||||
start_y := fh * 0.5 - total_h * 0.5;
|
||||
|
||||
g_settings.mouse_hover = -1;
|
||||
for i: 0..items.count-1 {
|
||||
col := ifx cast(s32)i == g_settings.cursor then sel_col else item_col;
|
||||
label := get_item_label(g_settings.page, cast(s32)i);
|
||||
prepare_text(g_settings.item_font, label);
|
||||
x := (fw - cast(float)gPreppedTextWidth) * 0.5;
|
||||
row_y := start_y + cast(float)i * item_h;
|
||||
draw_prepared_text(g_settings.item_font, xx x, xx row_y, col);
|
||||
|
||||
if input_mouse_y >= row_y && input_mouse_y < row_y + item_h {
|
||||
g_settings.mouse_hover = cast(s32)i;
|
||||
if g_settings.page == .MAIN {
|
||||
total_h := MAIN_ITEMS.count * item_h;
|
||||
start_y := half_h - total_h * 0.5;
|
||||
for i: 0..MAIN_ITEMS.count-1 {
|
||||
col := ifx cast(s32)i == g_settings.cursor then selected else dim;
|
||||
prepare_text(g_settings.item_font, MAIN_ITEMS[i]);
|
||||
x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||
draw_prepared_text(g_settings.item_font, xx x, xx (start_y + cast(float)i * item_h), col);
|
||||
}
|
||||
}
|
||||
hint_str := "↑↓ navigate Enter select";
|
||||
prepare_text(g_settings.item_font, hint_str);
|
||||
hint_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||
draw_prepared_text(g_settings.item_font, xx hint_x, xx (fh * 0.82), dim);
|
||||
|
||||
draw_centered_text(g_settings.item_font, page_hint(), fw, fh * 0.82, hint_col);
|
||||
|
||||
if g_settings.page == .MAIN && cfg.credits.count > 0 {
|
||||
draw_centered_text(g_settings.item_font, cfg.credits, fw, fh * 0.92, credits_col);
|
||||
} else if g_settings.page == .AUDIO {
|
||||
total_h := AUDIO_LABELS.count * item_h;
|
||||
start_y := half_h - total_h * 0.5;
|
||||
for i: 0..AUDIO_LABELS.count-1 {
|
||||
col := ifx cast(s32)i == g_settings.cursor then selected else dim;
|
||||
pct := cast(s32)(audio_get(cast(s32)i) * 100.0 + 0.5);
|
||||
bar := make_volume_bar(pct, cast(s32)i == g_settings.cursor);
|
||||
label := tprint("% %", AUDIO_LABELS[i], bar);
|
||||
prepare_text(g_settings.item_font, label);
|
||||
x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||
draw_prepared_text(g_settings.item_font, xx x, xx (start_y + cast(float)i * item_h), col);
|
||||
}
|
||||
hint_str := "↑↓ navigate ← → adjust Esc back";
|
||||
prepare_text(g_settings.item_font, hint_str);
|
||||
hint_x := (fw - cast(float) gPreppedTextWidth) * 0.5;
|
||||
draw_prepared_text(g_settings.item_font, xx hint_x, xx (fh * 0.82), dim);
|
||||
}
|
||||
}
|
||||
|
||||
#scope_file
|
||||
|
||||
draw_centered_text :: (font: *Font, text: string, fw: float, y: float, color: Vector4) {
|
||||
prepare_text(font, text);
|
||||
draw_prepared_text(font, xx ((fw - cast(float)gPreppedTextWidth) * 0.5), xx y, color);
|
||||
}
|
||||
|
||||
draw_phosphenes :: (fw: float, fh: float, time: float, fade: float) {
|
||||
if g_settings.blob_count < 1 then return;
|
||||
set_shader_for_color();
|
||||
|
||||
max_alpha := g_settings_config.phosphenes.alpha;
|
||||
num_layers := g_settings_config.phosphenes.layers;
|
||||
if num_layers < 1 then num_layers = 4;
|
||||
|
||||
for i: 0..g_settings.blob_count-1 {
|
||||
blob := g_settings.blobs[i];
|
||||
pulse := 0.5 + 0.5 * sin(time * blob.freq * 6.28 + blob.phase);
|
||||
alpha := fade * max_alpha * (0.6 + 0.4 * pulse);
|
||||
cx := (blob.cx + sin(time * 0.3 + blob.phase) * blob.drift_x) * fw;
|
||||
cy := (blob.cy + cos(time * 0.2 + blob.phase * 1.3) * blob.drift_y) * fh;
|
||||
blob_r := blob.radius * fh;
|
||||
|
||||
for li: 0..num_layers-1 {
|
||||
frac := cast(float)(num_layers - li) / cast(float)num_layers;
|
||||
r := blob_r * frac;
|
||||
layer_alpha := alpha * (1.0 - frac * 0.7);
|
||||
col := Vector4.{blob.r, blob.g, blob.b, layer_alpha};
|
||||
immediate_quad(.{cx - r, cy - r}, .{cx + r, cy - r}, .{cx + r, cy + r}, .{cx - r, cy + r}, col);
|
||||
}
|
||||
}
|
||||
immediate_flush();
|
||||
}
|
||||
|
||||
make_volume_bar :: (pct: s32) -> string {
|
||||
make_volume_bar :: (pct: s32, active: bool) -> string {
|
||||
STEPS :: 10;
|
||||
filled := (pct + 5) / STEPS;
|
||||
builder : String_Builder;
|
||||
append(*builder, "[ ");
|
||||
for i: 0..STEPS-1 {
|
||||
if i < filled then append(*builder, "=");
|
||||
else append(*builder, "-");
|
||||
if i < filled then append(*builder, "■");
|
||||
else append(*builder, "·");
|
||||
}
|
||||
append(*builder, tprint(" %\%%", pct));
|
||||
append(*builder, tprint(" %", pct));
|
||||
append(*builder, " ]");
|
||||
return builder_to_string(*builder,, allocator = temp);
|
||||
}
|
||||
|
||||
@ -85,22 +85,6 @@ get_trile :: (name: string) -> (*Trile, success: bool) {
|
||||
return trileptr, true;
|
||||
}
|
||||
|
||||
get_trile_roughness_set :: (trile_name: string) -> u8 {
|
||||
trile, ok := get_trile(trile_name);
|
||||
if !ok then return 1 << 7;
|
||||
mask : u8 = 1 << 7;
|
||||
for x: 0..15 {
|
||||
for y: 0..15 {
|
||||
for z: 0..15 {
|
||||
if !trile.trixels[x][y][z].empty {
|
||||
mask |= cast(u8)(1 << trile.trixels[x][y][z].material.roughness);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
lstrile :: () -> string {
|
||||
count := 0;
|
||||
for v : trile_table {
|
||||
|
||||
@ -369,11 +369,7 @@ tick_ui :: () {
|
||||
array_reset_keeping_memory(*ui_events);
|
||||
GR.ui_per_frame_update(1, xx w, xx h, get_time());
|
||||
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
tick_editor_ui();
|
||||
} else {
|
||||
tick_settings_menu();
|
||||
}
|
||||
tick_editor_ui();
|
||||
}
|
||||
|
||||
checkboxTest : bool = false;
|
||||
@ -427,15 +423,11 @@ render_ui :: () {
|
||||
proc := GR.default_theme_procs[0];
|
||||
my_theme := proc();
|
||||
GR.set_default_theme(my_theme);
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
draw_editor_ui(*my_theme);
|
||||
if !in_editor_view then game_ui(*my_theme);
|
||||
draw_console(*my_theme);
|
||||
} else {
|
||||
game_ui(*my_theme);
|
||||
}
|
||||
draw_editor_ui(*my_theme);
|
||||
if !in_editor_view then game_ui(*my_theme);
|
||||
#if FLAG_DEMO_BUILD { if !in_editor_view then draw_demo_ui(*my_theme); }
|
||||
draw_settings_menu(*my_theme);
|
||||
draw_console(*my_theme);
|
||||
}
|
||||
|
||||
ui_pass :: () {
|
||||
|
||||
@ -52,27 +52,18 @@ Chunk :: struct {
|
||||
coord: Chunk_Key;
|
||||
groups: [..]Chunk_Trile_Group;
|
||||
|
||||
rdm_atlas: sg_image;
|
||||
rdm_lookup: sg_image;
|
||||
rdm_valid: bool;
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
rdm_lookup_cpu: []float;
|
||||
rdm_lookup_w: s32;
|
||||
rdm_lookup_h: s32;
|
||||
}
|
||||
}
|
||||
|
||||
Editor_Note :: struct {
|
||||
position : Chunk_Key;
|
||||
text : string;
|
||||
// RDM bake results (populated by tacoma baking)
|
||||
rdm_atlas: sg_image;
|
||||
rdm_lookup: sg_image;
|
||||
rdm_valid: bool;
|
||||
}
|
||||
|
||||
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;
|
||||
// emitter_instances : [..]Particle_Emitter_Instance;
|
||||
emitter_instances : [..]string;
|
||||
}
|
||||
|
||||
// Convert a world-space integer position to chunk coordinate.
|
||||
@ -132,10 +123,6 @@ unload_current_world :: () {
|
||||
if chunk.rdm_valid {
|
||||
sg_destroy_image(chunk.rdm_atlas);
|
||||
sg_destroy_image(chunk.rdm_lookup);
|
||||
#if !FLAG_RELEASE_BUILD {
|
||||
if chunk.rdm_lookup_cpu.data then free(chunk.rdm_lookup_cpu.data);
|
||||
chunk.rdm_lookup_cpu = .{};
|
||||
}
|
||||
chunk.rdm_atlas = .{};
|
||||
chunk.rdm_lookup = .{};
|
||||
chunk.rdm_valid = false;
|
||||
@ -158,13 +145,7 @@ set_loaded_world :: (world: World) {
|
||||
unload_current_world();
|
||||
current_world.world = world;
|
||||
current_world.valid = true;
|
||||
resolve_emitter_definitions(*current_world.world);
|
||||
}
|
||||
|
||||
resolve_emitter_definitions :: (world: *World) {
|
||||
for *inst: world.emitter_instances {
|
||||
inst.definition = get_emitter_def(inst.definition_name);
|
||||
}
|
||||
// RDM loading is kicked off by the asset manager after calling this.
|
||||
}
|
||||
|
||||
clear_world :: () {
|
||||
@ -186,7 +167,7 @@ get_current_world :: () -> *Current_World {
|
||||
// --- Binary serialization (.world format) ---
|
||||
|
||||
WORLD_MAGIC :: u32.[0x4C575254][0]; // "TRWL" as little-endian u32
|
||||
WORLD_VERSION :: cast(u16) 3;
|
||||
WORLD_VERSION :: cast(u16) 1;
|
||||
|
||||
// World_Config serialized as a fixed-size binary blob.
|
||||
// We serialize it field-by-field to avoid padding issues.
|
||||
@ -364,30 +345,6 @@ save_world :: (world: *World) -> string {
|
||||
append(*builder, entry.data);
|
||||
}
|
||||
|
||||
// Write emitter instances
|
||||
num_emitters := cast(u16) world.emitter_instances.count;
|
||||
write_value(*builder, num_emitters);
|
||||
for inst: world.emitter_instances {
|
||||
name_len := cast(u16) inst.definition_name.count;
|
||||
write_value(*builder, name_len);
|
||||
append(*builder, inst.definition_name);
|
||||
write_value(*builder, inst.position.x);
|
||||
write_value(*builder, inst.position.y);
|
||||
write_value(*builder, inst.position.z);
|
||||
}
|
||||
|
||||
// Write notes
|
||||
num_notes := cast(u16) world.notes.count;
|
||||
write_value(*builder, num_notes);
|
||||
for note: world.notes {
|
||||
text_len := cast(u16) note.text.count;
|
||||
write_value(*builder, text_len);
|
||||
append(*builder, note.text);
|
||||
write_value(*builder, note.position.x);
|
||||
write_value(*builder, note.position.y);
|
||||
write_value(*builder, note.position.z);
|
||||
}
|
||||
|
||||
return builder_to_string(*builder);
|
||||
}
|
||||
|
||||
@ -408,7 +365,7 @@ load_world_from_data :: (data: []u8) -> (World, bool) {
|
||||
}
|
||||
|
||||
version := read_value(data, *cursor, u16);
|
||||
if version != 1 && version != 2 && version != 3 {
|
||||
if version != WORLD_VERSION {
|
||||
log_error("Unsupported world version: %", version);
|
||||
return world, false;
|
||||
}
|
||||
@ -462,33 +419,6 @@ load_world_from_data :: (data: []u8) -> (World, bool) {
|
||||
table_set(*world.chunks, chunk.coord, chunk);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user