techkaczynski.dev / code

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--editor.c296
-rw-r--r--editor.h9
-rw-r--r--env.c12
-rw-r--r--env.h5
-rw-r--r--save.c51
5 files changed, 357 insertions, 16 deletions
diff --git a/editor.c b/editor.c
index ab70611..c31c9a5 100644
--- a/editor.c
+++ b/editor.c
@@ -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: {
diff --git a/editor.h b/editor.h
index d3b3640..a566072 100644
--- a/editor.h
+++ b/editor.h
@@ -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;
diff --git a/env.c b/env.c
index 412dbea..20466f8 100644
--- a/env.c
+++ b/env.c
@@ -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");
diff --git a/env.h b/env.h
index c62f884..7a9266a 100644
--- a/env.h
+++ b/env.h
@@ -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;
diff --git a/save.c b/save.c
index 23f2c1e..427ad3c 100644
--- a/save.c
+++ b/save.c
@@ -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) &&