diff options
| -rw-r--r-- | editor.c | 296 | ||||
| -rw-r--r-- | editor.h | 9 | ||||
| -rw-r--r-- | env.c | 12 | ||||
| -rw-r--r-- | env.h | 5 | ||||
| -rw-r--r-- | save.c | 51 |
5 files changed, 357 insertions, 16 deletions
@@ -1,11 +1,12 @@ #include "editor.h" +#include "raygui.h" #include "save.h" #include "cam.h" #include "physics.h" #include "ent.h" #include "app.h" #include "res.h" -#include "raygui.h" +#include "env.h" static bool started = false; static Vec2 start_point = {0}; @@ -62,6 +63,18 @@ static struct { char query[128]; } asset_picker; +typedef enum { + ED_CMD_NONE = -1, + ED_CMD_TIME, + ED_CMD_SPAWN_PREFAB, + ED_CMD_CNT, +} EditorCommand; + +static struct { + char query[128]; + int selected; +} command_menu; + static const cstr material_shader_keys[] = { "model", "model_cutoff" @@ -240,6 +253,19 @@ action_begin(EditorActionTag tag, bool roots_only) { case ED_ACTION_PHYSICS: break; + case ED_ACTION_TIME: + action.item_cnt = 1; + action.item = calloc(1, sizeof(*action.item)); + if (!action.item) { + perror("action_begin: time action alloc failed."); + exit(1); + } + + /* Scene settings: this item's pid is unused. */ + action.item[0].before.time.day_cur = env.day_cur; + action.item[0].before.time.day_len = env.day_len; + return action; + default: TraceLog(LOG_FATAL, "action_begin: unsupported tag."); } @@ -402,6 +428,16 @@ action_apply(EditorAction *action, bool undo) { } break; + case ED_ACTION_TIME: { + const EditorActionData *data = undo ? + &action->item[0].before : + &action->item[0].after; + + env.day_cur = data->time.day_cur; + env.day_len = data->time.day_len; + break; + } + default: TraceLog(LOG_FATAL, "action with invalid tag."); break; @@ -440,6 +476,9 @@ action_free(EditorAction *action) { } break; + case ED_ACTION_TIME: + break; + default: TraceLog(LOG_FATAL, "action_free: unhandled action tag."); break; @@ -544,6 +583,19 @@ action_commit(EditorAction *action) { changed = action->item_cnt != 0; break; + case ED_ACTION_TIME: { + const EditorActionData *before = &action->item[0].before; + EditorActionData *after = &action->item[0].after; + + after->time.day_cur = env.day_cur; + after->time.day_len = env.day_len; + + changed = + before->time.day_cur != after->time.day_cur || + before->time.day_len != after->time.day_len; + break; + } + default: TraceLog(LOG_FATAL, "action_commit: unhangled action tag."); break; @@ -576,6 +628,8 @@ editor_init(void) { save_file_read("res/save/scene.fgs") ? ed.history_csr : UINT32_MAX; + ed.time_preview = env.day_cur; + ed.grid_sz = 1.0f; ed.rotation_snap = 2; // 15 degrees @@ -765,7 +819,15 @@ editor_update(void) { bool shift = IsKeyDown(KEY_LEFT_SHIFT); bool alt = IsKeyDown(KEY_LEFT_ALT); - /* sorry about the indentation */ + if (ed.time_preview_playing) { + ed.time_preview = fmodf( + ed.time_preview + GetFrameTime() / env.day_len, + 1.0f); + } + + env.cycle = ed.time_preview; + + /* sorry about the indentation :^) */ switch (ed.interact) { case ED_INTERACT_NONE: if (!ed.focus_captured && IsKeyPressed(KEY_SPACE)) { @@ -855,6 +917,8 @@ case ED_INTERACT_NONE: ed.history_save_csr = save_file_read("res/save/scene.fgs") ? ed.history_csr : UINT32_MAX; + + ed.time_preview = env.day_cur; } } @@ -4060,6 +4124,197 @@ panel_physics_properties(EditorPanelContainer container, bool input) { } static void +panel_time(EditorPanelContainer container, bool input) { + static PanelEdit edit = {0}; + + Rect bounds = ed.panel[container].bounds; + edit.next_id = 1; + edit.input = input; + + /* Escape cancels numeric entry before closing the panel. */ + if (input && IsKeyPressed(KEY_ESCAPE)) { + if (edit.focus != 0) { + edit.focus = 0; + return; + } + + ed.panel[container].enabled = false; + ed.focus = ED_PNL_NULL; + ed.focus_captured = false; + return; + } + + if (input && edit.focus == 0 && + IsKeyDown(KEY_LEFT_CONTROL) && IsKeyPressed(KEY_Z)) { + if (IsKeyDown(KEY_LEFT_SHIFT)) { + history_redo(); + } else { + history_undo(); + } + } + + GuiPanel(bounds, "Time of Day"); + + Rect row = { + bounds.x + 16, bounds.y + 40, + bounds.width - 32, 24 + }; + + int minutes = (int)(ed.time_preview * 24.0f * 60.0f); + char label[64]; + snprintf(label, sizeof(label), "Preview time: %02i:%02i", + minutes / 60, minutes % 60); + GuiLabel(row, label); + + row.y += 32; + f32 hours = ed.time_preview * 24.0f; + if (GuiSlider(row, NULL, NULL, &hours, 0.0f, 24.0f)) { + ed.time_preview = fmodf(hours / 24.0f, 1.0f); + } + + row.y += 40; + if (GuiButton(row, ed.time_preview_playing ? + "Pause preview" : "Play preview")) { + edit.focus = 0; + ed.time_preview_playing = !ed.time_preview_playing; + } + + row.y += 40; + minutes = (int)(env.day_cur * 24.0f * 60.0f); + snprintf(label, sizeof(label), "Scene starting time: %02i:%02i", + minutes / 60, minutes % 60); + GuiLabel(row, label); + + row.y += 28; + if (GuiButton(row, "Use preview as scene starting time")) { + edit.focus = 0; + EditorAction action = action_begin(ED_ACTION_TIME, false); + env.day_cur = ed.time_preview; + action_commit(&action); + } + + row.y += 40; + GuiLabel(row, "Day length (real seconds)"); + + row.y += 28; + f32 day_len = env.day_len; + if (panel_float(&edit, row, &day_len, false)) { + if (isfinite(day_len) && day_len > 0.0f) { + EditorAction action = + action_begin(ED_ACTION_TIME, false); + env.day_len = day_len; + action_commit(&action); + } + } +} + +static EditorCommand +panel_commands(Rect bounds, bool input) { + static const cstr names[ED_CMD_CNT] = { + [ED_CMD_TIME] = "Time of day", + [ED_CMD_SPAWN_PREFAB] = "Spawn prefab", + }; + + GuiPanel(bounds, "Command Menu"); + + Rect search = { + bounds.x + 8, bounds.y + 30, + bounds.width - 16, 24 + }; + + char previous[sizeof(command_menu.query)]; + memcpy(previous, command_menu.query, sizeof(previous)); + + /* Keep keyboard focus in the search field. */ + GuiTextBox(search, command_menu.query, + sizeof(command_menu.query), input); + + if (strcmp(previous, command_menu.query) != 0) { + command_menu.selected = 0; + } + + struct CommandMatch { + EditorCommand command; + int score; + } matches[ED_CMD_CNT]; + + int count = 0; + for (int i = 0; i < ED_CMD_CNT; ++i) { + int score = fuzzy_match(command_menu.query, names[i]); + if (score == INT32_MIN) { + continue; + } + + int index = count; + while (index > 0 && matches[index - 1].score < score) { + matches[index] = matches[index - 1]; + index--; + } + + matches[index] = (struct CommandMatch){i, score}; + count++; + } + + Rect row = { + search.x, search.y + 32, + search.width, 24 + }; + + if (!count) { + GuiLabel(row, "No matching commands"); + return ED_CMD_NONE; + } + + if (input) { + int step = 0; + if (IsKeyPressed(KEY_UP)) { + step = -1; + } else if (IsKeyPressed(KEY_DOWN)) { + step = 1; + } else if (IsKeyPressed(KEY_TAB)) { + bool backwards = IsKeyDown(KEY_LEFT_SHIFT) || + IsKeyDown(KEY_RIGHT_SHIFT); + step = backwards ? -1 : 1; + } + + command_menu.selected = + (command_menu.selected + step + count) % count; + } + + EditorCommand chosen = ED_CMD_NONE; + + for (int i = 0; i < count; ++i) { + bool hovered = input && + CheckCollisionPointRec(GetMousePosition(), row); + + if (hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { + command_menu.selected = i; + chosen = matches[i].command; + } + + if (i == command_menu.selected) { + DrawRectangleRec(row, + GetColor(GuiGetStyle(LISTVIEW, + BASE_COLOR_PRESSED))); + } + + GuiLabel(row, names[matches[i].command]); + row.y += row.height; + } + + if (input && IsKeyPressed(KEY_ENTER)) { + chosen = matches[command_menu.selected].command; + } + + GuiLabel((Rect){ + search.x, bounds.y + bounds.height - 30, + search.width, 22 + }, "Up/Down/Tab: select Enter: run Esc: close"); + + return chosen; +} + +static void panel_draw(EditorPanelContainer container) { if (!ed.panel[container].enabled) { return; @@ -4105,6 +4360,12 @@ panel_draw(EditorPanelContainer container) { exit = true; } + if (IsKeyPressed(KEY_SLASH)) { + memset(&command_menu, 0, sizeof(command_menu)); + ed.panel[container].type = ED_PNL_TYPE_CMD; + break; + } + if (exit) { ed.panel[container].enabled = false; ed.focus = ED_PNL_NULL; @@ -4113,8 +4374,35 @@ panel_draw(EditorPanelContainer container) { panel_space(container); break; - case ED_PNL_TYPE_CMD: - GuiPanel(bounds, "Command Menu"); + case ED_PNL_TYPE_CMD: { + if (input && IsKeyPressed(KEY_ESCAPE)) { + ed.panel[container].enabled = false; + ed.focus = ED_PNL_NULL; + ed.focus_captured = false; + break; + } + + EditorCommand command = panel_commands(bounds, input); + + switch (command) { + case ED_CMD_TIME: + ed.panel[container].type = ED_PNL_TYPE_TIME; + break; + + case ED_CMD_SPAWN_PREFAB: + ed.panel[container].enabled = false; + ed.focus = ED_PNL_NULL; + ed.focus_captured = false; + break; + + default: + break; + } + break; + } + + case ED_PNL_TYPE_TIME: + panel_time(container, input); break; case ED_PNL_TYPE_ASSET_PICKER: { @@ -13,6 +13,7 @@ typedef enum { ED_ACTION_RENAME, ED_ACTION_MODEL, ED_ACTION_PHYSICS, + ED_ACTION_TIME, } EditorActionTag; typedef union { @@ -31,6 +32,10 @@ typedef union { char *name; } rename; + struct { + f32 day_cur, day_len; + } time; + ModelCmp model; PhysicsCmp physics; } EditorActionData; @@ -59,6 +64,7 @@ typedef enum { ED_PNL_TYPE_ASSET_PICKER, ED_PNL_TYPE_PHYSICS_ELEMENTS, ED_PNL_TYPE_PHYSICS_PROPERTIES, + ED_PNL_TYPE_TIME, ED_PNL_TYPE_CMD, } EditorPanelType; @@ -91,6 +97,9 @@ typedef struct { CameraInfo scene_cam; bool draw_physics; + f32 time_preview; /* preview phase, 0 inclusive to 1 exclusive */ + bool time_preview_playing; + EditorModeTag mode, prev_mode; EditorInteraction interact; EditorPanelContainer focus; @@ -250,8 +250,11 @@ env_init(void) { env.sky_sdr_key = "sky"; env.sun_tex_key = "res/tex/sun.png"; env.moon_tex_key = "res/tex/moon.png"; - env.cycle = 0.35; - env.day_len = 240; // 600s = 5 min day/night cycle + + env.day_cur = 0.35f; + env.day_len = 240.0f; + env.cycle = env.day_cur; + env.sun_sz = 14; env.moon_sz = 4; // env.sun_tint = (Color) { 255, 255, 255, 40 }; @@ -267,11 +270,6 @@ env_exit(void) { void env_update(void) { - // if (env.day_len > 0.0f) { - // env.cycle += GetFrameTime() / env.day_len; - // env.cycle -= floorf(env.cycle); - // } - sky_update_lighting(); env_update_shader("model"); env_update_shader("model_cutoff"); @@ -23,8 +23,9 @@ typedef struct { f32 day_amount; f32 twilight_amount; - f32 cycle; - f32 day_len; + f32 day_cur; /* starting phase, 0 inclusive to 1 exclusive */ + f32 day_len; /* real seconds per complete lighting cycle */ + f32 cycle; /* current lighting phase */ /* sun/moon textures */ cstr sun_tex_key, moon_tex_key; @@ -1,5 +1,6 @@ #include "save.h" #include "ent.h" +#include "env.h" typedef struct { i64 size_pos; @@ -8,6 +9,7 @@ typedef struct { typedef struct { u64 next_pid; + f32 day_cur, day_len; } SaveLoadContext; static bool @@ -1082,6 +1084,37 @@ write_physics_chunk(FILE *fp) { return chunk_end(fp, &chunk); } + +static bool +read_environment_chunk(FILE *fp, SaveLoadContext *ctx) { + f32 day_cur, day_len; + + if (!read_f32_le(fp, &day_cur) || + !read_f32_le(fp, &day_len)) { + return false; + } + + if (!isfinite(day_cur) || + day_cur < 0.0f || day_cur >= 1.0f || + !isfinite(day_len) || day_len <= 0.0f) { + return false; + } + + ctx->day_cur = day_cur; + ctx->day_len = day_len; + return true; +} + +static bool +write_environment_chunk(FILE *fp) { + SaveChunk chunk; + + return chunk_begin(fp, "ENVR", &chunk) && + write_f32_le(fp, env.day_cur) && + write_f32_le(fp, env.day_len) && + chunk_end(fp, &chunk); +} + bool save_file_read(cstr path) { FILE *fp = fopen(path, "rb"); @@ -1090,7 +1123,10 @@ save_file_read(cstr path) { return false; } - SaveLoadContext ctx = {0}; + SaveLoadContext ctx = { + .day_cur = env.day_cur, + .day_len = env.day_len, + }; bool success = read_header(fp); bool read_entt = false; @@ -1122,13 +1158,15 @@ save_file_read(cstr path) { u32 end_pos = data_pos + chunk_size; - if (memcmp(label, "ENTT", 4) == 0) { + if (memcmp(label, "ENVR", 4) == 0) { + success = chunk_size >= 8 && + read_environment_chunk(fp, &ctx); + } else if (memcmp(label, "ENTT", 4) == 0) { if (read_entt) { TraceLog(LOG_ERROR, "duplicate ENTT chunk."); success = false; break; } - success = read_entity_chunk(fp, &ctx); read_entt = success; } else if (memcmp(label, "TRFM", 4) == 0) { @@ -1195,6 +1233,12 @@ save_file_read(cstr path) { success = false; } + if (success) { + env.day_cur = ctx.day_cur; + env.day_len = ctx.day_len; + env.cycle = env.day_cur; + } + return success; } @@ -1207,6 +1251,7 @@ save_file_write(cstr path) { bool success = write_header(fp) && + write_environment_chunk(fp) && write_entity_chunk(fp) && write_transform_chunk(fp) && write_model_chunk(fp) && |
