techkaczynski.dev / code

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xccproc.sh2
-rwxr-xr-xcompile.sh2
-rw-r--r--editor.c524
-rw-r--r--editor.h7
-rw-r--r--ent.h3
5 files changed, 495 insertions, 43 deletions
diff --git a/ccproc.sh b/ccproc.sh
index 05c66dd..702833f 100755
--- a/ccproc.sh
+++ b/ccproc.sh
@@ -9,7 +9,7 @@ if cproc \
*.c ext/*.c ext/tinyfiledialogs/tinyfiledialogs.c \
-L/usr/local/lib64 \
-fno-builtin \
- -lraylib -lbox3d -lgcc -lm -lpthread -lX11 -lrt -g -o fungame_cproc
+ -lraylib -lbox3d -lgcc -lm -lpthread -lX11 -lrt -o fungame_cproc
then ./fungame_cproc
else echo "build failed."
fi
diff --git a/compile.sh b/compile.sh
index c98170a..ac8ae7b 100755
--- a/compile.sh
+++ b/compile.sh
@@ -2,7 +2,7 @@
if cc *.c ext/*.c ext/tinyfiledialogs/tinyfiledialogs.c \
-lraylib -lbox3d -lm -lpthread -lX11 -lrt \
- -g3 -O0 \
+ -pg -g3 -O0 \
-o fungame
then ./fungame
else echo "build failed."
diff --git a/editor.c b/editor.c
index c31c9a5..785a46e 100644
--- a/editor.c
+++ b/editor.c
@@ -376,6 +376,48 @@ action_apply(EditorAction *action, bool undo) {
}
break;
+ case ED_ACTION_REPARENT: {
+ /* Detach everything before restoring any recorded links. */
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ entity_set_parent(id, 0);
+ }
+
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ Entity *ent = entity_get(id);
+ const EditorActionData *data = undo
+ ? &action->item[i].before
+ : &action->item[i].after;
+
+ ent->tr.parent = entity_id_from_pid(data->reparent.parent);
+ ent->tr.prev = entity_id_from_pid(data->reparent.prev);
+ ent->tr.next = entity_id_from_pid(data->reparent.next);
+ ent->tr.pos = data->reparent.pos;
+ ent->tr.rot = data->reparent.rot;
+ ent->tr.scale = data->reparent.scale;
+ }
+
+ /* All moved entities now have their final relationships. */
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ Entity *ent = entity_get(id);
+
+ if (ent->tr.prev) {
+ entity_get(ent->tr.prev)->tr.next = id;
+ } else if (ent->tr.parent) {
+ entity_get(ent->tr.parent)->tr.first_child = id;
+ }
+
+ if (ent->tr.next) {
+ entity_get(ent->tr.next)->tr.prev = id;
+ }
+
+ entity_physics_transform_changed(id);
+ }
+ break;
+ }
+
case ED_ACTION_CREATE:
case ED_ACTION_DUPLICATE:
if (undo) {
@@ -450,6 +492,9 @@ action_free(EditorAction *action) {
case ED_ACTION_TRANSFORM:
break;
+ case ED_ACTION_REPARENT:
+ break;
+
case ED_ACTION_CREATE:
case ED_ACTION_DELETE:
case ED_ACTION_DUPLICATE:
@@ -858,6 +903,14 @@ case ED_INTERACT_NONE:
}
}
+ if (!ed.focus_captured && ctrl && IsKeyPressed(KEY_Z)) {
+ if (shift) {
+ history_redo();
+ } else {
+ history_undo();
+ }
+ }
+
if (ed.focus != 0) {
break;
}
@@ -949,25 +1002,18 @@ case ED_INTERACT_NONE:
}
}
- if (IsKeyPressed(KEY_A)) {
- if (ctrl) {
- for (u32 i = 0; i < entity_count(); ++i) {
- u64 id = entity_id_at(i);
- Entity *ent = entity_get(id);
- if (ent && ent->pid) {
- ent->ed.selected = true;
- }
- }
- }
- }
+ // if (IsKeyPressed(KEY_A)) {
+ // if (ctrl) {
+ // for (u32 i = 0; i < entity_count(); ++i) {
+ // u64 id = entity_id_at(i);
+ // Entity *ent = entity_get(id);
+ // if (ent && ent->pid) {
+ // ent->ed.selected = true;
+ // }
+ // }
+ // }
+ // }
- if (ctrl && IsKeyPressed(KEY_Z)) {
- if (!shift) {
- history_undo();
- } else {
- history_redo();
- }
- }
break;
case ED_INTERACT_SELECT:
@@ -1960,6 +2006,239 @@ statusline(void) {
/* Scene panel */
static void
+scene_collect_reparent(u64 id, EditorAction *action) {
+ Entity *ent = entity_get(id);
+
+ if (ent->pid && selection_root(ent)) {
+ action->item[action->item_cnt++].pid = ent->pid;
+ return;
+ }
+
+ for (u64 child = ent->tr.first_child; child;) {
+ scene_collect_reparent(child, action);
+ child = entity_get(child)->tr.next;
+ }
+}
+
+static void
+scene_reparent(u64 parent_id) {
+ Entity *parent = entity_get(parent_id);
+
+ if (parent_id && (!parent || !parent->pid)) {
+ return;
+ }
+
+ /* The destination cannot be within any selected subtree. */
+ for (u64 ancestor = parent_id; ancestor;) {
+ Entity *ent = entity_get(ancestor);
+
+ if (ent->ed.selected) {
+ return;
+ }
+
+ ancestor = ent->tr.parent;
+ }
+
+ EditorAction action = {
+ .tag = ED_ACTION_REPARENT,
+ };
+
+ action.item = calloc(entity_count(), sizeof(*action.item));
+ if (!action.item) {
+ perror("scene_reparent: action allocation failed.");
+ exit(1);
+ }
+
+ for (u32 i = 0; i < entity_count(); ++i) {
+ u64 id = entity_id_at(i);
+
+ if (!entity_get(id)->tr.parent) {
+ scene_collect_reparent(id, &action);
+ }
+ }
+
+ /* Leave entities already under this parent in place. */
+ u32 count = 0;
+ for (u32 i = 0; i < action.item_cnt; ++i) {
+ u64 pid = action.item[i].pid;
+ Entity *ent = entity_get(entity_id_from_pid(pid));
+
+ if (ent->tr.parent != parent_id) {
+ action.item[count++].pid = pid;
+ }
+ }
+ action.item_cnt = count;
+
+ if (!action.item_cnt) {
+ action_free(&action);
+ return;
+ }
+
+ Matrix parent_world = entity_world_matrix(parent_id);
+ f32 determinant = MatrixDeterminant(parent_world);
+
+ if (!isfinite(determinant) || determinant == 0.0f) {
+ TraceLog(LOG_WARNING,
+ "Cannot reparent through a zero-scale transform.");
+ action_free(&action);
+ return;
+ }
+
+ Matrix parent_inverse = MatrixInvert(parent_world);
+ Entity *first = parent ? entity_get(parent->tr.first_child) : NULL;
+
+ /* Prepare every item before changing the hierarchy. */
+ for (u32 i = 0; i < action.item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action.item[i].pid);
+ Entity *ent = entity_get(id);
+ EditorActionData *before = &action.item[i].before;
+ EditorActionData *after = &action.item[i].after;
+
+ Entity *old_parent = entity_get(ent->tr.parent);
+ Entity *prev = entity_get(ent->tr.prev);
+ Entity *next = entity_get(ent->tr.next);
+
+ before->reparent.parent = old_parent ? old_parent->pid : 0;
+ before->reparent.prev = prev ? prev->pid : 0;
+ before->reparent.next = next ? next->pid : 0;
+ before->reparent.pos = ent->tr.pos;
+ before->reparent.rot = ent->tr.rot;
+ before->reparent.scale = ent->tr.scale;
+
+ Matrix local = MatrixMultiply(
+ entity_world_matrix(id), parent_inverse);
+
+ Vec3 pos, scale;
+ Quaternion rot;
+ MatrixDecompose(local, &pos, &rot, &scale);
+ rot = QuaternionNormalize(rot);
+
+ Matrix rebuilt = MatrixMultiply(
+ MatrixMultiply(
+ MatrixScale(scale.x, scale.y, scale.z),
+ QuaternionToMatrix(rot)),
+ MatrixTranslate(pos.x, pos.y, pos.z));
+
+ float16 expected = MatrixToFloatV(local);
+ float16 actual = MatrixToFloatV(rebuilt);
+
+ for (int j = 0; j < 16; ++j) {
+ f32 tolerance = 0.0001f *
+ fmaxf(1.0f, fabsf(expected.v[j]));
+
+ if (!isfinite(expected.v[j]) ||
+ !isfinite(actual.v[j]) ||
+ fabsf(expected.v[j] - actual.v[j]) >
+ tolerance) {
+ TraceLog(LOG_WARNING,
+ "Cannot preserve all selected"
+ " transforms.");
+ action_free(&action);
+ return;
+ }
+ }
+
+ after->reparent.parent = parent ? parent->pid : 0;
+ after->reparent.pos = pos;
+ after->reparent.rot = rot;
+ after->reparent.scale = scale;
+
+ if (parent_id) {
+ after->reparent.prev = i > 0
+ ? action.item[i - 1].pid : 0;
+ after->reparent.next = i + 1 < action.item_cnt
+ ? action.item[i + 1].pid
+ : (first ? first->pid : 0);
+ }
+ }
+
+ action_apply(&action, false);
+ history_push(action);
+}
+
+static struct {
+ u64 id;
+ u64 hovered;
+ u64 expand_id;
+ Vec2 start;
+ f32 expand_time;
+ bool dragging;
+ bool invalid;
+} scene_drag;
+
+static u64 scene_anchor_pid;
+
+static void
+scene_collect_visible(u64 id, u64 *rows, u32 *count) {
+ Entity *ent = entity_get(id);
+
+ if (ent->pid) {
+ rows[(*count)++] = id;
+
+ if (ent->ed.collapsed) {
+ return;
+ }
+ }
+
+ for (u64 child = ent->tr.first_child; child;) {
+ scene_collect_visible(child, rows, count);
+ child = entity_get(child)->tr.next;
+ }
+}
+
+static void
+scene_select_range(u64 id, bool additive) {
+ u64 *rows = malloc(entity_count() * sizeof(*rows));
+ if (!rows) {
+ perror("scene_select_range: allocation failed.");
+ exit(1);
+ }
+
+ u32 count = 0;
+ for (u32 i = 0; i < entity_count(); ++i) {
+ u64 root = entity_id_at(i);
+
+ if (!entity_get(root)->tr.parent) {
+ scene_collect_visible(root, rows, &count);
+ }
+ }
+
+ int anchor = -1;
+ int clicked = -1;
+
+ for (u32 i = 0; i < count; ++i) {
+ if (entity_get(rows[i])->pid == scene_anchor_pid) {
+ anchor = (int)i;
+ }
+
+ if (rows[i] == id) {
+ clicked = (int)i;
+ }
+ }
+
+ assert(clicked >= 0);
+
+ /* A missing or hidden anchor starts a new range here. */
+ if (anchor < 0) {
+ anchor = clicked;
+ scene_anchor_pid = entity_get(id)->pid;
+ }
+
+ if (!additive) {
+ entities_clear_selected();
+ }
+
+ int first = MIN(anchor, clicked);
+ int last = MAX(anchor, clicked);
+
+ for (int i = first; i <= last; ++i) {
+ entity_get(rows[i])->ed.selected = true;
+ }
+
+ free(rows);
+}
+
+static void
scene_entity(u64 id, Rect view, f32 *y, int depth, bool input) {
Entity *ent = entity_get(id);
if (!ent) {
@@ -1973,12 +2252,60 @@ scene_entity(u64 id, Rect view, f32 *y, int depth, bool input) {
CheckCollisionPointRec(GetMousePosition(), view) &&
CheckCollisionPointRec(GetMousePosition(), row);
- if (hovered && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
- if (IsKeyDown(KEY_LEFT_SHIFT)) {
- ent->ed.selected = !ent->ed.selected;
- } else {
- entities_clear_selected();
- ent->ed.selected = true;
+ Rect arrow = {
+ row.x + 8 + depth * 18,
+ row.y,
+ 18,
+ row.height
+ };
+
+ bool arrow_hovered = hovered && ent->tr.first_child &&
+ CheckCollisionPointRec(GetMousePosition(), arrow);
+
+ if (arrow_hovered &&
+ IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
+ ent->ed.collapsed = !ent->ed.collapsed;
+ }
+
+ if (hovered) {
+ scene_drag.hovered = id;
+
+ if (!arrow_hovered &&
+ IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
+ bool ctrl = IsKeyDown(KEY_LEFT_CONTROL) ||
+ IsKeyDown(KEY_RIGHT_CONTROL);
+ bool shift = IsKeyDown(KEY_LEFT_SHIFT) ||
+ IsKeyDown(KEY_RIGHT_SHIFT);
+
+ if (shift) {
+ scene_select_range(id, ctrl);
+ } else if (ctrl) {
+ ent->ed.selected = !ent->ed.selected;
+ scene_anchor_pid = ent->pid;
+ } else {
+ if (!ent->ed.selected) {
+ entities_clear_selected();
+ ent->ed.selected = true;
+ }
+
+ scene_anchor_pid = ent->pid;
+ scene_drag.id = id;
+ scene_drag.start = GetMousePosition();
+ scene_drag.dragging = false;
+ }
+ }
+
+ if (scene_drag.dragging) {
+ for (u64 anc = id; anc;) {
+ Entity *ancestor = entity_get(anc);
+
+ if (ancestor->ed.selected) {
+ scene_drag.invalid = true;
+ break;
+ }
+
+ anc = ancestor->tr.parent;
+ }
}
}
@@ -1994,13 +2321,22 @@ scene_entity(u64 id, Rect view, f32 *y, int depth, bool input) {
colour)));
}
+ if (ent->tr.first_child) {
+ GuiLabel(arrow, ent->ed.collapsed ? "+" : "-");
+ }
+
Rect label = {
- row.x + 8 + depth * 18,
+ arrow.x + arrow.width,
row.y,
- fmaxf(0, row.width - 8 - depth * 18),
+ fmaxf(0, row.width - 26 - depth * 18),
row.height
};
+ if (hovered && scene_drag.dragging) {
+ DrawRectangleLinesEx(row, 2.0f,
+ scene_drag.invalid ? RED : GREEN);
+ }
+
if (label.width > 0) {
GuiLabel(label,
ent->name && ent->name[0] ?
@@ -2012,6 +2348,10 @@ scene_entity(u64 id, Rect view, f32 *y, int depth, bool input) {
depth++;
}
+ if (ent->pid && ent->ed.collapsed) {
+ return;
+ }
+
for (u64 child = ent->tr.first_child; child;) {
Entity *next = entity_get(child);
if (!next) {
@@ -2062,14 +2402,31 @@ panel_scene(EditorPanelContainer container, bool input) {
bounds.x + 126, bounds.y + 64, 20, 20,
}, "", &ed.draw_physics);
- bounds.x += 8;
- bounds.y += 94;
- bounds.width -= 16;
- bounds.height -= 102;
+ bounds.x += 8;
+ bounds.y += 94;
+ bounds.width -= 16;
+ bounds.height -= 102;
u32 count = 0;
for (u32 i = 0; i < entity_count(); ++i) {
- if (entity_get(entity_id_at(i))->pid) {
+ Entity *ent = entity_get(entity_id_at(i));
+ if (!ent->pid) {
+ continue;
+ }
+
+ bool hidden = false;
+ for (u64 ancestor = ent->tr.parent; ancestor;) {
+ Entity *parent = entity_get(ancestor);
+
+ if (parent->pid && parent->ed.collapsed) {
+ hidden = true;
+ break;
+ }
+
+ ancestor = parent->tr.parent;
+ }
+
+ if (!hidden) {
count++;
}
}
@@ -2079,30 +2436,117 @@ panel_scene(EditorPanelContainer container, bool input) {
- GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH);
GuiScrollPanel(bounds, NULL,
- (Rect){ 0, 0, width, count * 26.0f },
+ (Rect){ 0, 0, width, (count + 1) * 26.0f },
scroll, &view);
- BeginScissorMode(view.x, view.y, view.width, view.height);
+ bool tree_input = input &&
+ state != STATE_DISABLED && !GuiIsLocked();
- if (input && state != STATE_DISABLED && !GuiIsLocked() &&
- IsMouseButtonPressed(MOUSE_BUTTON_LEFT) &&
- !IsKeyDown(KEY_LEFT_SHIFT) &&
- CheckCollisionPointRec(GetMousePosition(), view)) {
- entities_clear_selected();
+ Vec2 mouse = GetMousePosition();
+ bool inside = tree_input && CheckCollisionPointRec(mouse, view);
+
+ if (scene_drag.dragging && inside &&
+ IsMouseButtonDown(MOUSE_LEFT_BUTTON)) {
+ f32 direction = 0.0f;
+
+ if (mouse.y < view.y + 24.0f) {
+ direction = 1.0f;
+ } else if (mouse.y > view.y + view.height - 24.0f) {
+ direction = -1.0f;
+ }
+
+ f32 content_height = (count + 1) * 26.0f;
+ f32 max_scroll = fmaxf(0.0f, content_height - view.height);
+
+ scroll->y = Clamp(
+ scroll->y + direction * 240.0f * GetFrameTime(),
+ -max_scroll, 0.0f);
+ }
+
+ scene_drag.hovered = 0;
+ scene_drag.invalid = false;
+
+ if (scene_drag.id) {
+ if (!tree_input || !entity_valid(scene_drag.id) ||
+ IsKeyPressed(KEY_ESCAPE)) {
+ scene_drag.id = 0;
+ scene_drag.dragging = false;
+ } else if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) &&
+ Vector2Distance(mouse, scene_drag.start) >= 5.0f) {
+ scene_drag.dragging = true;
+ }
}
+ BeginScissorMode(view.x, view.y, view.width, view.height);
+
f32 y = view.y + scroll->y;
+ Rect root_row = { view.x, y, view.width, 26 };
+
+ GuiLabel(root_row, "Scene Root");
+ y += root_row.height;
+
for (u32 i = 0; i < entity_count(); ++i) {
u64 id = entity_id_at(i);
Entity *ent = entity_get(id);
if (!ent->tr.parent) {
- scene_entity(id, view, &y, 0,
- input && state != STATE_DISABLED);
+ scene_entity(id, view, &y, 0, tree_input);
}
}
+ /* The root row and empty space both mean "make root". */
+ if (scene_drag.dragging && inside && !scene_drag.hovered) {
+ DrawRectangleLinesEx(root_row, 2.0f, GREEN);
+ }
+
+ /* Unmodified clicks on empty space clear the selection. */
+ if (inside && !scene_drag.hovered &&
+ IsMouseButtonPressed(MOUSE_LEFT_BUTTON) &&
+ !IsKeyDown(KEY_LEFT_CONTROL) &&
+ !IsKeyDown(KEY_RIGHT_CONTROL) &&
+ !IsKeyDown(KEY_LEFT_SHIFT) &&
+ !IsKeyDown(KEY_RIGHT_SHIFT)) {
+ entities_clear_selected();
+ scene_anchor_pid = 0;
+ }
+
EndScissorMode();
+
+ /* Apply changes after traversal has finished. */
+ if (scene_drag.id && IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
+ if (scene_drag.dragging) {
+ if (inside && !scene_drag.invalid) {
+ scene_reparent(scene_drag.hovered);
+ }
+ } else if (inside && scene_drag.hovered == scene_drag.id) {
+ entities_clear_selected();
+ entity_get(scene_drag.id)->ed.selected = true;
+ }
+
+ scene_drag.id = 0;
+ scene_drag.dragging = false;
+ }
+
+ Entity *target = entity_get(scene_drag.hovered);
+
+ if (scene_drag.dragging && inside && !scene_drag.invalid &&
+ IsMouseButtonDown(MOUSE_LEFT_BUTTON) &&
+ target && target->ed.collapsed && target->tr.first_child) {
+ if (scene_drag.expand_id != scene_drag.hovered) {
+ scene_drag.expand_id = scene_drag.hovered;
+ scene_drag.expand_time = 0.0f;
+ }
+
+ scene_drag.expand_time += GetFrameTime();
+
+ if (scene_drag.expand_time >= 0.6f) {
+ target->ed.collapsed = false;
+ }
+ } else {
+ scene_drag.expand_id = 0;
+ scene_drag.expand_time = 0.0f;
+ }
+
GuiSetState(state);
if (input && container != ED_PNL_CENTER &&
diff --git a/editor.h b/editor.h
index a566072..69a7c90 100644
--- a/editor.h
+++ b/editor.h
@@ -7,6 +7,7 @@
typedef enum {
ED_ACTION_TRANSFORM,
+ ED_ACTION_REPARENT,
ED_ACTION_CREATE,
ED_ACTION_DELETE,
ED_ACTION_DUPLICATE,
@@ -29,6 +30,12 @@ typedef union {
} stored;
struct {
+ u64 parent, prev, next; /* PIDs */
+ Vec3 pos, scale;
+ Quaternion rot;
+ } reparent;
+
+ struct {
char *name;
} rename;
diff --git a/ent.h b/ent.h
index 0b6a1bc..41eb60f 100644
--- a/ent.h
+++ b/ent.h
@@ -50,7 +50,8 @@ typedef struct {
} PhysicsCmp;
typedef struct {
- bool selected;
+ bool selected;
+ bool collapsed;
} EditorCmp; // transient data for non transient entities ^___^
typedef struct {