techkaczynski.dev / code

summaryrefslogtreecommitdiff
path: root/editor.c
diff options
context:
space:
mode:
Diffstat (limited to 'editor.c')
-rw-r--r--editor.c1538
1 files changed, 1459 insertions, 79 deletions
diff --git a/editor.c b/editor.c
index 0e7fd58..1feb6ca 100644
--- a/editor.c
+++ b/editor.c
@@ -41,6 +41,7 @@ static void action_apply(EditorAction*, bool);
static void action_free(EditorAction*);
static void model_picker_open(ModelPickerMode mode);
static void model_picker_close(void);
+static void physics_pick(void);
static void
history_push(EditorAction action) {
@@ -195,6 +196,7 @@ action_begin(EditorActionTag tag, bool roots_only) {
case ED_ACTION_RENAME:
case ED_ACTION_MODEL:
+ case ED_ACTION_PHYSICS:
break;
default:
@@ -266,6 +268,10 @@ action_begin(EditorActionTag tag, bool roots_only) {
strdup(ent->mdl.key) : NULL;
break;
+ case ED_ACTION_PHYSICS:
+ before->physics = entity_physics_copy(&ent->phys);
+ break;
+
default:
TraceLog(LOG_FATAL,
"action_begin: unhandled action tag.");
@@ -293,6 +299,12 @@ action_apply(EditorAction *action, bool undo) {
ent->tr.rot = data->transform.rot;
ent->tr.scale = data->transform.scale;
}
+
+ /* second pass ensures transforms are properly updated */
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ entity_physics_transform_changed(id);
+ }
break;
case ED_ACTION_CREATE:
@@ -334,6 +346,18 @@ action_apply(EditorAction *action, bool undo) {
}
break;
+ case ED_ACTION_PHYSICS:
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ Entity *ent = entity_get(id);
+
+ entity_physics_clear(&ent->phys);
+ PhysicsCmp pre = ent->phys;
+ ent->phys = action->item[i].before.physics;
+ action->item[i].before.physics = pre;
+ }
+ break;
+
default:
TraceLog(LOG_FATAL, "action with invalid tag.");
break;
@@ -365,6 +389,12 @@ action_free(EditorAction *action) {
}
break;
+ case ED_ACTION_PHYSICS:
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ entity_physics_free(&action->item[i].before.physics);
+ }
+ break;
+
default:
TraceLog(LOG_FATAL, "action_free: unhandled action tag.");
break;
@@ -396,6 +426,8 @@ action_commit(EditorAction *action) {
after->transform.rot)
|| !Vector3Equals(before->transform.scale,
after->transform.scale);
+
+ entity_physics_transform_changed(id);
}
if (!changed) {
@@ -441,6 +473,10 @@ action_commit(EditorAction *action) {
}
break;
+ case ED_ACTION_PHYSICS:
+ changed = action->item_cnt != 0;
+ break;
+
default:
TraceLog(LOG_FATAL, "action_commit: unhangled action tag.");
break;
@@ -458,6 +494,8 @@ action_commit(EditorAction *action) {
void
editor_init(void) {
// GuiLoadStyle("style_amber.rgs");
+ // GuiLoadStyle("style_candy.rgs");
+ // GuiLoadStyle("style_rltech.rgs");
Asset *fnt_asset =
asset_get("res/fnt/Aileron-Regular.otf");
@@ -483,6 +521,85 @@ editor_exit(void) {
ed.history_cap = 0;
}
+static void
+editor_focus_selection(void) {
+ Vec3 minimum = {FLT_MAX, FLT_MAX, FLT_MAX};
+ Vec3 maximum = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
+ bool found = false;
+
+ for (u32 i = 0; i < entity_count(); ++i) {
+ u64 id = entity_id_at(i);
+ Entity *ent = entity_get(id);
+
+ if (!ent->ed.selected) {
+ continue;
+ }
+
+ found = true;
+ Matrix world = entity_world_matrix(id);
+ BoundingBox bounds = {0};
+
+ if (ent->mdl.has && ent->mdl.draw &&
+ ent->mdl.mdl) {
+ Model model = *ent->mdl.mdl;
+ model.transform = MatrixIdentity();
+ bounds = GetModelBoundingBox(model);
+ }
+
+ for (int corner = 0; corner < 8; ++corner) {
+ Vec3 point = {
+ (corner & 1) ?
+ bounds.max.x : bounds.min.x,
+ (corner & 2) ?
+ bounds.max.y : bounds.min.y,
+ (corner & 4) ?
+ bounds.max.z : bounds.min.z,
+ };
+ point = Vector3Transform(point, world);
+ minimum = Vector3Min(minimum, point);
+ maximum = Vector3Max(maximum, point);
+ }
+ }
+
+ if (!found) {
+ return;
+ }
+
+ Vec3 center = Vector3Scale(
+ Vector3Add(minimum, maximum), 0.5f);
+ f32 radius = fmaxf(
+ Vector3Distance(minimum, maximum) * 0.5f, 0.5f);
+
+ /* Leave space for panels on either side of the view. */
+ f32 width = GetScreenWidth();
+ f32 height = GetScreenHeight();
+ f32 margin = 0;
+
+ if (ed.panel[ED_PNL_LEFT].enabled) {
+ Rect bounds = ed.panel[ED_PNL_LEFT].bounds;
+ margin = fmaxf(margin, bounds.x + bounds.width);
+ }
+
+ if (ed.panel[ED_PNL_RIGHT].enabled) {
+ Rect bounds = ed.panel[ED_PNL_RIGHT].bounds;
+ margin = fmaxf(margin, width - bounds.x);
+ }
+
+ f32 visible_width = fmaxf(width - 2.0f * margin, 1.0f);
+ f32 aspect = visible_width / height;
+ f32 half_fov = main_cam.perspective_fov * DEG2RAD * 0.5f;
+ f32 horizontal = atanf(tanf(half_fov) * aspect);
+ f32 angle = fminf(half_fov, horizontal);
+ f32 distance = radius / sinf(angle) * 1.15f;
+
+ f32 view_height = 0;
+ if (main_cam.cam.projection == CAMERA_ORTHOGRAPHIC) {
+ view_height = 2.0f * radius * 1.15f / fminf(aspect, 1.0f);
+ }
+
+ cam_focus(center, distance, view_height);
+}
+
void
editor_update(void) {
bool ctrl = IsKeyDown(KEY_LEFT_CONTROL);
@@ -490,8 +607,8 @@ editor_update(void) {
bool alt = IsKeyDown(KEY_LEFT_ALT);
/* sorry about the indentation */
- switch (app.mode) {
-case APP_MODE_EDIT_NONE:
+ switch (ed.interact) {
+case ED_INTERACT_NONE:
if (!ed.focus_captured && IsKeyPressed(KEY_SPACE)) {
ed.panel[ED_PNL_CENTER].type = ED_PNL_TYPE_SPACE;
ed.panel[ED_PNL_CENTER].enabled = true;
@@ -524,30 +641,48 @@ case APP_MODE_EDIT_NONE:
break;
}
- if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
- app.mode = APP_MODE_EDIT_CTRL;
- main_cam.update = true;
- HideCursor();
+ if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) ||
+ (main_cam.state == CAM_MODE_EDIT_ORBIT &&
+ IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE))) {
+ ed.interact = ED_INTERACT_CAMERA;
+ main_cam.update = false;
DisableCursor();
break;
}
+ if (ed.mode == ED_MODE_PHYSICS) {
+ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+ physics_pick();
+ }
+ break;
+ }
+
+ if (ed.mode != ED_MODE_SCENE) {
+ break;
+ }
+
+
+ if (!ctrl && IsKeyPressed(KEY_F)) {
+ editor_focus_selection();
+ break;
+ }
+
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
- app.mode = APP_MODE_EDIT_SELECT;
+ ed.interact = ED_INTERACT_SELECT;
editor_select(); // capture this frame
break;
}
if (IsKeyPressed(KEY_G)) {
if (!ctrl && ed.sel_cnt) {
- app.mode = APP_MODE_EDIT_MOVE;
+ ed.interact = ED_INTERACT_MOVE;
break;
}
}
if (IsKeyPressed(KEY_R)) {
if (!ctrl && ed.sel_cnt) {
- app.mode = APP_MODE_EDIT_ROTATE;
+ ed.interact = ED_INTERACT_ROTATE;
break;
}
@@ -602,38 +737,50 @@ case APP_MODE_EDIT_NONE:
}
break;
-case APP_MODE_EDIT_SELECT:
+case ED_INTERACT_SELECT:
editor_select();
break;
-case APP_MODE_EDIT_CTRL:
- cam_ctrl_editor();
- if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) {
- app.mode = APP_MODE_EDIT_NONE;
+case ED_INTERACT_CAMERA: {
+ bool held = IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
+
+ if (main_cam.state == CAM_MODE_EDIT_ORBIT) {
+ held |= IsMouseButtonDown(MOUSE_BUTTON_MIDDLE);
+ }
+
+ if (!held) {
+ ed.interact = ED_INTERACT_NONE;
main_cam.update = false;
- ShowCursor();
+ EnableCursor();
+ break;
}
+
+ main_cam.update = true;
break;
+}
-case APP_MODE_EDIT_MOVE:
+case ED_INTERACT_MOVE:
editor_move();
break;
-case APP_MODE_EDIT_ROTATE:
+case ED_INTERACT_ROTATE:
editor_rotate();
break;
-case APP_MODE_EDIT_SPACE:
- break;
-
-// case APP_MODE_EDIT_CMD:
- // break;
-
default:
- TraceLog(LOG_FATAL, "editor_update: unhandled app.mode value");
+ TraceLog(LOG_FATAL, "editor_update: unhandled ed.interact value");
break;
}
+ main_cam.input =
+ (ed.interact == ED_INTERACT_CAMERA && main_cam.update) ||
+ (ed.interact == ED_INTERACT_NONE &&
+ ed.focus == ED_PNL_NULL &&
+ !ed.focus_captured &&
+ !ed.panel[ED_PNL_CENTER].enabled);
+
+ cam_update();
+
/* reset selection data */
ed.sel_cnt = 0;
ed.sel_point = Vector3Zero();
@@ -666,34 +813,81 @@ default:
static void
sel_ray(void) {
Ray ray = GetScreenToWorldRay(end_point, main_cam.cam);
+ Vec3 dir = Vector3Scale(ray.direction, 1000.0f);
b3QueryFilter filter = {
.categoryBits = BIT(PHYS_MASK_EDITOR_RAYCAST),
.maskBits = BIT(PHYS_MASK_EDITOR_SELECTABLE),
};
- Vec3 dir = Vector3Scale(ray.direction, 1000.0f);
- b3RayResult res =
- b3World_CastRayClosest(physics.world_id,
- *(b3Pos*)&ray.position,
- *(b3Pos*)&dir,
- filter);
+ b3RayResult res = b3World_CastRayClosest(
+ physics.world_id,
+ (b3Pos){ray.position.x, ray.position.y, ray.position.z},
+ (b3Vec3){dir.x, dir.y, dir.z},
+ filter);
if (ed.focus == ED_PNL_NULL &&
!IsKeyDown(KEY_LEFT_SHIFT)) {
entities_clear_selected();
}
- if (res.hit) {
- b3BodyId bid = b3Shape_GetBody(res.shapeId);
- u64 eid = *(u64*)b3Body_GetUserData(bid);
- Entity *ent = entity_get(eid);
+ if (!res.hit) {
+ return;
+ }
- if (ent->pid == 0) {
- return;
- }
+ b3BodyId body = b3Shape_GetBody(res.shapeId);
+ u64 *id = b3Body_GetUserData(body);
+ if (!id) {
+ return;
+ }
- ent->ed.selected = !ent->ed.selected;
+ Entity *ent = entity_get(*id);
+ if (!ent || !ent->pid) {
+ return;
+ }
+
+ ent->ed.selected = !ent->ed.selected;
+}
+
+static void
+physics_pick(void) {
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+
+ Entity *ent = entity_get(entity_id_from_pid(ed.physics_pid));
+ if (!ent || !ent->phys.runtime.bodies) {
+ return;
+ }
+
+ Ray ray = GetScreenToWorldRay(GetMousePosition(), main_cam.cam);
+ Vec3 dir = Vector3Scale(ray.direction, 1000.0f);
+
+ b3Pos origin = {
+ ray.position.x, ray.position.y, ray.position.z,
+ };
+ b3Vec3 translation = {dir.x, dir.y, dir.z};
+ f32 closest = 1.0f;
+
+ PhysicsCmp *phys = &ent->phys;
+ for (u32 i = 0; i < phys->body_cnt; ++i) {
+ PhysicsBody *body = &phys->bodies[i];
+ PhysicsBodyRuntime *runtime = &phys->runtime.bodies[i];
+
+ for (u32 j = 0; j < body->shape_cnt; ++j) {
+ b3ShapeId shape = runtime->shapes[j];
+ if (!b3Shape_IsValid(shape)) {
+ continue;
+ }
+
+ b3WorldCastOutput hit = b3Shape_RayCast(
+ shape, origin, translation);
+
+ if (hit.hit && hit.fraction <= closest) {
+ closest = hit.fraction;
+ ed.physics_body = (int)i;
+ ed.physics_shape = (int)j;
+ }
+ }
}
}
@@ -806,7 +1000,7 @@ editor_select(void) {
}
started = false;
sel_dist = 0;
- app.mode = APP_MODE_EDIT_NONE;
+ ed.interact = ED_INTERACT_NONE;
}
}
@@ -851,7 +1045,7 @@ editor_move(void) {
action_commit(&tr_action);
}
started = false;
- app.mode = APP_MODE_EDIT_NONE;
+ ed.interact = ED_INTERACT_NONE;
return;
}
@@ -900,6 +1094,7 @@ editor_move(void) {
Vec3 pos = Vector3Add(entity_world_pos(id), delta);
entity_set_world_pos(ent, pos);
+ entity_physics_transform_changed(id);
}
}
@@ -922,7 +1117,7 @@ editor_rotate(void) {
}
started = false;
- app.mode = APP_MODE_EDIT_NONE;
+ ed.interact = ED_INTERACT_NONE;
return;
}
@@ -945,6 +1140,7 @@ editor_rotate(void) {
Entity *ent = entity_get(id);
assert(ent);
entity_set_world_rot(ent, rot);
+ entity_physics_transform_changed(id);
}
}
@@ -956,8 +1152,8 @@ statusline(void) {
// Color bg = { 255, 255, 255, 150 };
Color bg = GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR));
- switch (app.mode) {
- case APP_MODE_EDIT_NONE:
+ switch (ed.interact) {
+ case ED_INTERACT_NONE:
snprintf(mode_str, 16, "EDITOR");
if (ed.sel_cnt > 0) {
snprintf(ctx_str, sizeof(ctx_str),
@@ -970,17 +1166,17 @@ statusline(void) {
}
break;
- case APP_MODE_EDIT_SELECT:
+ case ED_INTERACT_SELECT:
snprintf(mode_str, 16, "SELECT");
break;
- case APP_MODE_EDIT_CTRL:
+ case ED_INTERACT_CAMERA:
snprintf(mode_str, 16, "VIEW");
snprintf(ctx_str, sizeof(ctx_str), "controls: "
"shift: boost speed, scroll: adjust speed");
break;
- case APP_MODE_EDIT_MOVE:
+ case ED_INTERACT_MOVE:
snprintf(mode_str, 16, "MOVE");
cstr axis_str = IsKeyDown(KEY_LEFT_ALT) ?
@@ -995,7 +1191,7 @@ statusline(void) {
ed.sel_cnt, axis_str);
break;
- case APP_MODE_EDIT_ROTATE:
+ case ED_INTERACT_ROTATE:
snprintf(mode_str, 16, "ROTATE");
snprintf(ctx_str, sizeof(ctx_str), "%i Selected | "
"axis: global up, pivot: each entity | "
@@ -1009,6 +1205,25 @@ statusline(void) {
break;
};
+ Color heading_bg = { 0, 0, 0, 25 };
+
+ if (ed.mode == ED_MODE_PHYSICS) {
+ u64 id = entity_id_from_pid(ed.physics_pid);
+ Entity *ent = entity_get(id);
+ cstr name = ent && ent->name ? ent->name : "<unnamed>";
+
+ snprintf(mode_str, sizeof(mode_str), "PHYSICS");
+ heading_bg = (Color){70, 140, 210, 90};
+
+ if (ed.interact == ED_INTERACT_CAMERA) {
+ snprintf(ctx_str, sizeof(ctx_str),
+ "%s | MMB: pan, scroll: zoom", name);
+ } else {
+ snprintf(ctx_str, sizeof(ctx_str),
+ "%s | RMB: orbit, scroll: zoom", name);
+ }
+ }
+
/* statusline bg */
Rect rec = {
// 0, 0, /* top bar */
@@ -1016,12 +1231,13 @@ statusline(void) {
GetScreenWidth(), 25
// editor_label_w(mode_str) , 25
};
+
DrawRectangleRec(rec, bg);
/* mode str */
f32 mode_str_w = editor_label_w(mode_str);
rec.width = 70;
- DrawRectangleRec(rec, (Color){0,0,0,25});
+ DrawRectangleRec(rec, heading_bg);
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER);
GuiLabel(rec, mode_str);
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
@@ -1131,7 +1347,7 @@ panel_scene(EditorPanelContainer container, bool input) {
Rect bounds = ed.panel[container].bounds;
Vec2 *scroll = &ed.panel[container].scroll;
- input = input && app.mode == APP_MODE_EDIT_NONE;
+ input = input && ed.interact == ED_INTERACT_NONE;
int state = GuiGetState();
if (!input) {
@@ -1143,7 +1359,7 @@ panel_scene(EditorPanelContainer container, bool input) {
ed.focus_captured = true;
}
- GuiPanel(bounds, "Entities");
+ GuiPanel(bounds, "Scene");
f32 btn_w = editor_label_w("Create Entity") + 16;
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER);
@@ -1155,10 +1371,18 @@ panel_scene(EditorPanelContainer container, bool input) {
}
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
+ GuiLabel((Rect){
+ bounds.x + 8, bounds.y + 64, 110, 20,
+ }, "Draw Physics");
+
+ GuiCheckBox((Rect){
+ bounds.x + 126, bounds.y + 64, 20, 20,
+ }, "", &ed.draw_physics);
+
bounds.x += 8;
- bounds.y += 64;
+ bounds.y += 94;
bounds.width -= 16;
- bounds.height -= 72;
+ bounds.height -= 102;
u32 count = 0;
for (u32 i = 0; i < entity_count(); ++i) {
@@ -1175,9 +1399,14 @@ panel_scene(EditorPanelContainer container, bool input) {
(Rect){ 0, 0, width, count * 26.0f },
scroll, &view);
- BeginScissorMode(
- (int)view.x, (int)view.y,
- (int)view.width, (int)view.height);
+ BeginScissorMode(view.x, view.y, view.width, view.height);
+
+ if (input && state != STATE_DISABLED && !GuiIsLocked() &&
+ IsMouseButtonPressed(MOUSE_BUTTON_LEFT) &&
+ !IsKeyDown(KEY_LEFT_SHIFT) &&
+ CheckCollisionPointRec(GetMousePosition(), view)) {
+ entities_clear_selected();
+ }
f32 y = view.y + scroll->y;
for (u32 i = 0; i < entity_count(); ++i) {
@@ -1316,7 +1545,6 @@ panel_vec3(PanelEdit *edit, Rect row, cstr label, Vec3 *value, bool mixed) {
return submitted;
}
-
static void
panel_properties(EditorPanelContainer container, bool input) {
static PanelEdit edit;
@@ -1327,7 +1555,7 @@ panel_properties(EditorPanelContainer container, bool input) {
int state = GuiGetState();
f32 pad = 8;
- input = input && app.mode == APP_MODE_EDIT_NONE
+ input = input && ed.interact == ED_INTERACT_NONE
&& state != STATE_DISABLED;
edit.next_id = 1;
@@ -1367,16 +1595,23 @@ panel_properties(EditorPanelContainer container, bool input) {
}
}
+ /* selection info prep */
ModelCmp *model = NULL;
int model_cnt = 0;
+ int physics_cnt = 0;
bool key_mixed = false;
bool draw_mixed = false;
for (u32 i = 0; i < entity_count(); ++i) {
Entity *candidate = entity_get(entity_id_at(i));
- if (!candidate->pid || !candidate->ed.selected ||
- !candidate->mdl.has) {
+ if (!candidate->pid || !candidate->ed.selected) {
+ continue;
+ }
+
+ physics_cnt += candidate->phys.has;
+
+ if (!candidate->mdl.has) {
continue;
}
@@ -1392,6 +1627,7 @@ panel_properties(EditorPanelContainer container, bool input) {
}
bool all_have_model = model_cnt == ed.sel_cnt;
+ bool all_have_physics = physics_cnt == ed.sel_cnt;
/* Name */
cstr name = mixed ? "<multiple entities selected>"
@@ -1512,12 +1748,14 @@ panel_properties(EditorPanelContainer container, bool input) {
action_commit(&action);
}
+ f32 next_y = group.y + group.height + 16;
+
model_comp:
if (!all_have_model) {
- goto comp_add;
+ goto physics_comp;
}
- group.y += group.height + 16;
+ group.y = next_y;
group.height = 80;
GuiGroupBox(group, "Model");
@@ -1578,38 +1816,171 @@ panel_properties(EditorPanelContainer container, bool input) {
action_commit(&action);
}
- row.y = group.y + group.height + 20;
+ next_y = group.y + group.height + pad + 20 + 16;
+
+ physics_comp:
+ if (!all_have_physics) {
+ goto comp_add;
+ }
+
+ group.y = next_y;
+ group.height = 80;
+ GuiGroupBox(group, "Physics");
+
+ row = (Rect){
+ group.x + pad,
+ group.y + 15,
+ group.width - 2 * pad,
+ 20,
+ };
+
+ GuiLabel(row, mixed ? "<multiple entities>" :
+ TextFormat("%u bodies, %u joints",
+ ent->phys.body_cnt, ent->phys.joint_cnt));
+
+ row.y += 30;
+ if (ent) {
+ if (GuiButton(row, "Edit Physics")) {
+ ed.scene_cam = main_cam;
+
+ main_cam.allow_fly = false;
+ main_cam.input = false;
+ main_cam.update = false;
+
+ u64 id = entity_id_from_pid(ent->pid);
+
+ f32 view_height = 10.0f *
+ tanf(main_cam.perspective_fov *
+ DEG2RAD * 0.5f);
+
+ cam_focus(entity_world_pos(id), 5.0f, view_height);
+
+ ed.physics_pid = ent->pid;
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+ ed.physics_scroll = (Vec2){0};
+ ed.prev_mode = ed.mode;
+ ed.mode = ED_MODE_PHYSICS;
+ ed.panel[ED_PNL_LEFT].type =
+ ED_PNL_TYPE_PHYSICS_ELEMENTS;
+ ed.panel[ED_PNL_RIGHT].type =
+ ED_PNL_TYPE_PHYSICS_PROPERTIES;
+
+ edit.focus = 0;
+ add_open = false;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ goto reset;
+ }
+ } else {
+ GuiLabel(row, "Select one entity to edit physics");
+ }
+
+ if (GuiButton((Rect){
+ group.x,
+ group.y + group.height + pad,
+ 120,
+ 20,
+ }, "Remove Physics")) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ for (u32 i = 0; i < action.item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action.item[i].pid);
+ Entity *target = entity_get(id);
+ entity_physics_free(&target->phys);
+ }
+
+ action_commit(&action);
+ goto reset;
+ }
+
+ next_y = group.y + group.height + pad + 20 + 16;
comp_add:
GuiUnlock();
- row.y += 40;
+
+ row = (Rect){
+ bounds.x + pad,
+ next_y,
+ bounds.width - 2 * pad,
+ 20,
+ };
+
+ char choices[128] = "Add Component";
+ int model_choice = -1;
+ int physics_choice = -1;
+ int choice_cnt = 1;
+
+ if (!all_have_model) {
+ model_choice = choice_cnt++;
+ strcat(choices, model_cnt
+ ? ";Add Model to Missing" : ";Model");
+ }
+
+ if (!all_have_physics) {
+ physics_choice = choice_cnt++;
+ strcat(choices, physics_cnt
+ ? ";Add Physics to Missing" : ";Physics");
+ }
int add_state = GuiGetState();
- if (all_have_model) {
+ if (choice_cnt == 1) {
add_open = false;
GuiSetState(STATE_DISABLED);
}
- cstr choices = model_cnt
- ? "Add Component;Add Model to Missing"
- : "Add Component;Model";
+ bool toggle = GuiDropdownBox(
+ row, choices, &add_choice, add_open);
- if (GuiDropdownBox(row, choices, &add_choice, add_open)) {
- add_open = !add_open;
- edit.focus = 0;
+ GuiSetState(add_state);
+
+ if (!toggle) {
+ goto reset;
+ }
+
+ add_open = !add_open;
+ edit.focus = 0;
+
+ if (add_open) {
+ goto reset;
+ }
+
+ int choice = add_choice;
+ add_choice = 0;
+
+ if (choice == model_choice) {
+ model_picker_open(MDL_PICK_COMP_ADD);
+ goto reset;
+ }
+
+ if (choice != physics_choice) {
+ goto reset;
+ }
- if (!add_open && add_choice == 1) {
- model_picker_open(MDL_PICK_COMP_ADD);
- add_choice = 0;
+ EditorAction action = action_begin(ED_ACTION_PHYSICS, false);
+
+ for (u32 i = 0; i < action.item_cnt; ++i) {
+ Entity *target = entity_get(
+ entity_id_from_pid(action.item[i].pid)
+ );
+
+ if (target->phys.has) {
+ continue;
}
+
+ target->phys.has = true;
}
- GuiSetState(add_state);
+ action_commit(&action);
reset:
GuiUnlock();
GuiSetState(state);
+ GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
if (input && container != ED_PNL_CENTER) {
ed.focus_captured = edit.focus != 0 ||
add_open ||
@@ -2002,12 +2373,850 @@ panel_model_picker(Rect bounds, bool input) {
}
static void
+panel_physics_elements(EditorPanelContainer container) {
+ Rect bounds = ed.panel[container].bounds;
+ GuiPanel(bounds, "Physics Elements");
+
+ Entity *ent = entity_get(entity_id_from_pid(ed.physics_pid));
+ if (!ent) {
+ return;
+ }
+
+ PhysicsCmp *phys = &ent->phys;
+
+ Rect row = {
+ bounds.x + 8,
+ bounds.y + 30,
+ bounds.width - 16,
+ 20,
+ };
+
+ if (GuiButton(row, "Add Body")) {
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(phys);
+
+ PhysicsBody *bodies = realloc(
+ phys->bodies,
+ (phys->body_cnt + 1) * sizeof(*bodies));
+
+ if (!bodies) {
+ perror("physics body allocation failed");
+ exit(EXIT_FAILURE);
+ }
+
+ phys->bodies = bodies;
+ phys->bodies[phys->body_cnt] = (PhysicsBody) {
+ .rot = QuaternionIdentity(),
+ .gravity_scale = 1.0f,
+ .mode = PHYS_MODE_DYNAMIC,
+ .enable_sleep = true,
+ };
+
+ if (!phys->body_cnt) {
+ phys->root_body = 0;
+ }
+
+ ed.physics_body = (int)phys->body_cnt;
+ ed.physics_shape = -1;
+ phys->body_cnt++;
+
+ action_commit(&action);
+ }
+
+ Rect list = {
+ bounds.x + 8,
+ bounds.y + 60,
+ bounds.width - 16,
+ bounds.height - 68,
+ };
+
+ Rect view = {0};
+ f32 width = list.width
+ - 2 * GuiGetStyle(DEFAULT, BORDER_WIDTH)
+ - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH);
+
+ u32 row_count = phys->body_cnt;
+ for (u32 i = 0; i < phys->body_cnt; ++i) {
+ row_count += phys->bodies[i].shape_cnt;
+ }
+
+ GuiScrollPanel(list, NULL,
+ (Rect){0, 0, width, row_count * 26.0f},
+ &ed.physics_scroll, &view);
+
+ bool input = GuiGetState() != STATE_DISABLED && !GuiIsLocked();
+ Vec2 mouse = GetMousePosition();
+
+ if (input && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) &&
+ CheckCollisionPointRec(mouse, view)) {
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+ }
+
+ BeginScissorMode(
+ (int)view.x, (int)view.y,
+ (int)view.width, (int)view.height);
+
+ cstr shape_names[] = {
+ "Box",
+ "Sphere",
+ "Capsule",
+ "Cylinder",
+ "Convex Hull",
+ "Triangle Mesh",
+ };
+
+ f32 y = view.y + ed.physics_scroll.y;
+
+ for (u32 i = 0; i < phys->body_cnt; ++i) {
+ PhysicsBody *body = &phys->bodies[i];
+
+ for (int j = -1; j < (int)body->shape_cnt; ++j) {
+ row = (Rect){view.x, y, width, 26};
+ y += 26;
+
+ bool hovered = input &&
+ CheckCollisionPointRec(mouse, view) &&
+ CheckCollisionPointRec(mouse, row);
+
+ if (hovered &&
+ IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+ ed.physics_body = (int)i;
+ ed.physics_shape = j;
+ }
+
+ bool selected = ed.physics_body == (int)i &&
+ ed.physics_shape == j;
+
+ if (selected || hovered) {
+ int colour = selected
+ ? BASE_COLOR_PRESSED
+ : BASE_COLOR_FOCUSED;
+
+ DrawRectangleRec(row, GetColor(
+ GuiGetStyle(DEFAULT, colour)));
+ }
+
+ f32 indent = j < 0 ? 8 : 26;
+ row.x += indent;
+ row.width -= indent;
+
+ if (j < 0) {
+ GuiLabel(row, TextFormat("Body %u%s",
+ i + 1, i == phys->root_body ?
+ " (root)" : ""));
+ } else {
+ GuiLabel(row, TextFormat("%s %i",
+ shape_names[body->shapes[j].tag],
+ j + 1));
+ }
+ }
+ }
+
+ EndScissorMode();
+}
+
+static void
+panel_physics_shape(PanelEdit *edit, Rect row,
+ Entity *ent, PhysicsBody *body) {
+ u32 index = (u32)ed.physics_shape;
+ if (index >= body->shape_cnt) {
+ edit->focus = 0;
+ return;
+ }
+
+ PhysicsShape *shape = &body->shapes[index];
+
+ GuiLabel(row, TextFormat("Body %i | Shape %u",
+ ed.physics_body + 1, index + 1));
+
+ row.y += 30;
+
+ if (GuiButton(row, "Remove Shape")) {
+ edit->focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ switch (shape->tag) {
+ case PHYS_SHAPE_CONVEX_HULL:
+ free(shape->ConvexHull.points);
+ break;
+
+ case PHYS_SHAPE_STATIC_TRIMESH:
+ free(shape->TriMesh.points);
+ free(shape->TriMesh.indices);
+ break;
+
+ default:
+ break;
+ }
+
+ memmove(shape, shape + 1,
+ (body->shape_cnt - index - 1) * sizeof(*shape));
+ body->shape_cnt--;
+
+ if (!body->shape_cnt) {
+ free(body->shapes);
+ body->shapes = NULL;
+ }
+
+ ed.physics_shape = -1;
+ action_commit(&action);
+ return;
+ }
+
+ Vec3 values[3] = {
+ shape->pos,
+ Vector3Scale(QuaternionToEuler(shape->rot), RAD2DEG),
+ {0},
+ };
+
+ cstr labels[3] = {"Pos", "Rot", "Size"};
+ bool changed[3] = {false};
+ int count = 2;
+
+ if (shape->tag == PHYS_SHAPE_BOX) {
+ values[2] = shape->Box.sz;
+ count = 3;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ row.y += 30;
+
+ Vec3 value = values[i];
+ int axis = panel_vec3(edit, row, labels[i], &value, false);
+
+ if (axis < 0) {
+ continue;
+ }
+
+ if (!isfinite(value.x) ||
+ !isfinite(value.y) ||
+ !isfinite(value.z)) {
+ continue;
+ }
+
+ if (i == 2 &&
+ (value.x <= 0 || value.y <= 0 || value.z <= 0)) {
+ continue;
+ }
+
+ changed[i] = !Vector3Equals(value, values[i]);
+ values[i] = value;
+ }
+
+ f32 total_height = 0;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE) {
+ total_height = shape->Capsule.height +
+ 2.0f * shape->Capsule.radius;
+ }
+
+ f32 radius = 0;
+ bool radius_changed = false;
+
+ if (shape->tag == PHYS_SHAPE_SPHERE ||
+ shape->tag == PHYS_SHAPE_CAPSULE ||
+ shape->tag == PHYS_SHAPE_CYLINDER) {
+ row.y += 30;
+
+ GuiLabel((Rect){
+ row.x, row.y, 60, row.height,
+ }, "Radius");
+
+ Rect box = {
+ row.x + 60, row.y,
+ row.width - 60, row.height,
+ };
+
+ f32 orig_radius;
+ if (shape->tag == PHYS_SHAPE_SPHERE) {
+ orig_radius = shape->Sphere.radius;
+ } else if (shape->tag == PHYS_SHAPE_CAPSULE) {
+ orig_radius = shape->Capsule.radius;
+ } else {
+ orig_radius = shape->Cylinder.radius;
+ }
+ radius = orig_radius;
+
+ if (panel_float(edit, box, &radius, false)) {
+ radius_changed = isfinite(radius) &&
+ radius > 0 &&
+ radius != orig_radius;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE &&
+ radius > total_height * 0.5f) {
+ radius_changed = false;
+ }
+ }
+ }
+
+ f32 height = 0;
+ bool height_changed = false;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE ||
+ shape->tag == PHYS_SHAPE_CYLINDER) {
+ row.y += 30;
+
+ GuiLabel((Rect){
+ row.x, row.y, 100, row.height,
+ }, "Height");
+
+ Rect box = {
+ row.x + 100, row.y,
+ row.width - 100, row.height,
+ };
+
+ f32 original_height = shape->tag == PHYS_SHAPE_CAPSULE
+ ? total_height : shape->Cylinder.height;
+
+ height = original_height;
+
+ if (panel_float(edit, box, &height, false)) {
+ height_changed = isfinite(height) &&
+ height > 0 &&
+ height != original_height;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE) {
+ f32 effective_radius = radius_changed
+ ? radius : shape->Capsule.radius;
+
+ if (height < 2.0f * effective_radius) {
+ height_changed = false;
+ }
+ }
+ }
+ }
+
+ cstr material_labels[3] = {
+ "Density",
+ "Friction",
+ "Restitution",
+ };
+
+ f32 material[3] = {
+ shape->density,
+ shape->friction,
+ shape->restitution,
+ };
+
+ bool material_changed = false;
+
+ for (int i = 0; i < 3; ++i) {
+ row.y += 30;
+
+ GuiLabel((Rect){
+ row.x, row.y, 100, row.height,
+ }, material_labels[i]);
+
+ Rect box = {
+ row.x + 100, row.y,
+ row.width - 100, row.height,
+ };
+
+ f32 value = material[i];
+ if (!panel_float(edit, box, &value, false)) {
+ continue;
+ }
+
+ if (!isfinite(value) || value < 0) {
+ continue;
+ }
+
+ if (i == 2 && value > 1) {
+ continue;
+ }
+
+ material_changed |= value != material[i];
+ material[i] = value;
+ }
+
+ if (!changed[0] && !changed[1] && !changed[2] &&
+ !radius_changed && !height_changed &&
+ !material_changed) {
+ return;
+ }
+
+ EditorAction action = action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ if (changed[0]) {
+ shape->pos = values[0];
+ }
+
+ if (changed[1]) {
+ shape->rot = QuaternionFromEuler(
+ values[1].x * DEG2RAD,
+ values[1].y * DEG2RAD,
+ values[1].z * DEG2RAD);
+ }
+
+ if (changed[2]) {
+ shape->Box.sz = values[2];
+ }
+
+ if (shape->tag == PHYS_SHAPE_SPHERE && radius_changed) {
+ shape->Sphere.radius = radius;
+ }
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE &&
+ (radius_changed || height_changed)) {
+ f32 new_radius = radius_changed
+ ? radius : shape->Capsule.radius;
+ f32 new_height = height_changed ? height : total_height;
+
+ shape->Capsule.radius = new_radius;
+ shape->Capsule.height =
+ fmaxf(0, new_height - 2.0f * new_radius);
+ }
+
+ if (shape->tag == PHYS_SHAPE_CYLINDER) {
+ if (radius_changed) {
+ shape->Cylinder.radius = radius;
+ }
+
+ if (height_changed) {
+ shape->Cylinder.height = height;
+ }
+ }
+
+ shape->density = material[0];
+ shape->friction = material[1];
+ shape->restitution = material[2];
+
+ action_commit(&action);
+}
+
+static void
+panel_physics_properties(EditorPanelContainer container, bool input) {
+ Rect bounds = ed.panel[container].bounds;
+
+ static bool type_open;
+ static PanelEdit edit;
+
+ edit.next_id = 1;
+ edit.input = input && !type_open;
+
+ if (!edit.input) {
+ edit.focus = 0;
+ }
+
+ if (!input) {
+ type_open = false;
+ }
+
+ if (type_open) {
+ GuiLock();
+ }
+
+ GuiPanel(bounds, "Physics Properties");
+
+ Rect button = {
+ bounds.x + 8,
+ bounds.y + 30,
+ bounds.width - 16,
+ 20,
+ };
+
+ if (GuiButton(button, "Done")) {
+ main_cam = ed.scene_cam;
+ edit.focus = 0;
+ ed.mode = ed.prev_mode;
+ ed.physics_pid = 0;
+ ed.panel[ED_PNL_LEFT].type = ED_PNL_TYPE_SCENE;
+ ed.panel[ED_PNL_RIGHT].type = ED_PNL_TYPE_PROPERTIES;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ return;
+ }
+
+ Entity *ent = entity_get(entity_id_from_pid(ed.physics_pid));
+
+ Rect info = {
+ bounds.x + 8,
+ bounds.y + 60,
+ bounds.width - 16,
+ 20,
+ };
+
+ if (ent && ed.physics_body >= 0 &&
+ (u32)ed.physics_body < ent->phys.body_cnt) {
+ PhysicsBody *body = &ent->phys.bodies[ed.physics_body];
+
+ if (ed.physics_shape >= 0) {
+ type_open = false;
+ GuiUnlock();
+
+ panel_physics_shape(&edit, info, ent, body);
+ goto reset;
+ }
+
+ GuiLabel(info, TextFormat("Body %i | %u shapes",
+ ed.physics_body + 1, body->shape_cnt));
+
+ info.y += 30;
+
+ if (ent->phys.root_body == (u32)ed.physics_body) {
+ GuiLabel(info, "Root body");
+ } else if (GuiButton(info, "Make Root Body")) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ ent->phys.root_body = (u32)ed.physics_body;
+
+ action_commit(&action);
+ }
+
+ info.y += 30;
+
+ if (GuiButton(info, "Remove Body")) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ PhysicsCmp *phys = &ent->phys;
+ u32 index = (u32)ed.physics_body;
+
+ entity_physics_clear(phys);
+
+ for (u32 i = 0; i < body->shape_cnt; ++i) {
+ PhysicsShape *shape = &body->shapes[i];
+
+ switch (shape->tag) {
+ case PHYS_SHAPE_CONVEX_HULL:
+ free(shape->ConvexHull.points);
+ break;
+
+ case PHYS_SHAPE_STATIC_TRIMESH:
+ free(shape->TriMesh.points);
+ free(shape->TriMesh.indices);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ free(body->shapes);
+
+ memmove(body, body + 1,
+ (phys->body_cnt - index - 1) * sizeof(*body));
+
+ phys->body_cnt--;
+
+ if (phys->body_cnt == index) {
+ phys->root_body = 0;
+ } else if (phys->root_body > index) {
+ phys->root_body -= 1;
+ }
+
+ u32 kept = 0;
+ for (u32 i = 0; i < phys->joint_cnt; ++i) {
+ PhysicsJoint joint = phys->joints[i];
+
+ if (joint.body_a == index ||
+ joint.body_b == index) {
+ continue;
+ }
+
+ if (joint.body_a > index) {
+ joint.body_a--;
+ }
+
+ if (joint.body_b > index) {
+ joint.body_b--;
+ }
+
+ phys->joints[kept++] = joint;
+ }
+ phys->joint_cnt = kept;
+
+ if (!phys->body_cnt) {
+ free(phys->bodies);
+ phys->bodies = NULL;
+ }
+
+ if (!phys->joint_cnt) {
+ free(phys->joints);
+ phys->joints = NULL;
+ }
+
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+
+ action_commit(&action);
+ goto reset;
+ }
+
+ info.y += 30;
+
+ Vec3 pos = body->pos;
+ int pos_axis = panel_vec3(
+ &edit, info, "Pos", &pos, false);
+
+ bool pos_changed = pos_axis >= 0 &&
+ isfinite(pos.x) &&
+ isfinite(pos.y) &&
+ isfinite(pos.z) &&
+ !Vector3Equals(pos, body->pos);
+
+ info.y += 30;
+
+ Vec3 original_rot = Vector3Scale(
+ QuaternionToEuler(body->rot), RAD2DEG);
+
+ Vec3 rot = original_rot;
+ int rot_axis = panel_vec3(
+ &edit, info, "Rot", &rot, false);
+
+ bool rot_changed = rot_axis >= 0 &&
+ isfinite(rot.x) &&
+ isfinite(rot.y) &&
+ isfinite(rot.z) &&
+ !Vector3Equals(rot, original_rot);
+
+ info.y += 30;
+
+ GuiLabel((Rect){
+ info.x, info.y, 45, info.height,
+ }, "Type");
+
+ Rect type_box = {
+ info.x + 45, info.y,
+ info.width - 45, info.height,
+ };
+
+ info.y += 30;
+
+ bool sleep = body->enable_sleep;
+ panel_bool(info, "Enable Sleep", &sleep, false);
+
+ f32 values[3] = {
+ body->gravity_scale,
+ body->linear_damping,
+ body->angular_damping,
+ };
+
+ cstr labels[3] = {
+ "Gravity scale",
+ "Linear damping",
+ "Angular damping",
+ };
+
+ bool floats_changed = false;
+
+ for (int i = 0; i < 3; ++i) {
+ info.y += 30;
+
+ GuiLabel((Rect){
+ info.x, info.y, 130, info.height,
+ }, labels[i]);
+
+ Rect box = {
+ info.x + 130, info.y,
+ info.width - 130, info.height,
+ };
+
+ f32 value = values[i];
+ if (panel_float(&edit, box, &value, false)) {
+ if (!isfinite(value)) {
+ continue;
+ }
+
+ if (i > 0 && value < 0) {
+ continue;
+ }
+
+ floats_changed |= value != values[i];
+ values[i] = value;
+ }
+ }
+
+ info.y += 30;
+ bool add_box = GuiButton(info, "Add Box");
+
+ info.y += 30;
+ bool add_sphere = GuiButton(info, "Add Sphere");
+
+ info.y += 30;
+ bool add_capsule = GuiButton(info, "Add Capsule");
+
+ info.y += 30;
+ bool add_cylinder = GuiButton(info, "Add Cylinder");
+
+ info.y += 30;
+ int button_state = GuiGetState();
+
+ if (!ent->mdl.has) {
+ GuiSetState(STATE_DISABLED);
+ }
+
+ bool fit_model = GuiButton(info, "Add Model Box");
+ GuiSetState(button_state);
+
+ Model *model = NULL;
+ if (fit_model) {
+ Asset *asset = asset_get(ent->mdl.key);
+ if (asset && asset->tag == ASSET_TAG_MODEL &&
+ asset->mdl.meshCount > 0) {
+ model = &asset->mdl;
+ }
+ }
+
+ if (add_box || add_sphere || add_capsule || add_cylinder ||
+ model) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ PhysicsShape *shapes = realloc(
+ body->shapes,
+ (body->shape_cnt + 1) * sizeof(*shapes));
+
+ if (!shapes) {
+ perror("physics shape allocation failed");
+ exit(1);
+ }
+
+ body->shapes = shapes;
+ body->shapes[body->shape_cnt] = (PhysicsShape){
+ .tag = PHYS_SHAPE_BOX,
+ .Box.sz = {1, 1, 1},
+ .rot = QuaternionIdentity(),
+ .density = 1.0f,
+ .friction = 0.5f,
+ .category_bits = BIT(PHYS_MASK_DEFAULT),
+ .mask_bits = UINT32_MAX,
+ };
+
+ if (add_sphere) {
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->tag = PHYS_SHAPE_SPHERE;
+ shape->Sphere.radius = 0.5f;
+ }
+
+ if (add_capsule) {
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->tag = PHYS_SHAPE_CAPSULE;
+ shape->Capsule.radius = 0.5f;
+ shape->Capsule.height = 1.0f;
+ }
+
+ if (add_cylinder) {
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->tag = PHYS_SHAPE_CYLINDER;
+ shape->Cylinder.radius = 0.5f;
+ shape->Cylinder.height = 1.0f;
+ }
+
+ if (model) {
+ Model local_model = *model;
+ local_model.transform = MatrixIdentity();
+
+ BoundingBox bounds =
+ GetModelBoundingBox(local_model);
+
+ Vec3 center = Vector3Scale(
+ Vector3Add(
+ bounds.min,
+ bounds.max), 0.5f);
+
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->Box.sz =
+ Vector3Subtract(bounds.max,
+ bounds.min);
+ shape->rot = QuaternionInvert(body->rot);
+ shape->pos = Vector3RotateByQuaternion(
+ Vector3Subtract(center, body->pos),
+ shape->rot);
+ }
+
+ body->shape_cnt++;
+
+ action_commit(&action);
+ }
+
+ GuiUnlock();
+ int mode = body->mode;
+ if (GuiDropdownBox(type_box,
+ "Static;Kinematic;Dynamic",
+ &mode, type_open)) {
+ type_open = !type_open;
+ edit.focus = 0;
+ }
+
+ if (input && IsKeyPressed(KEY_ESCAPE)) {
+ type_open = false;
+ }
+
+ if (mode != body->mode ||
+ sleep != body->enable_sleep ||
+ floats_changed ||
+ pos_changed ||
+ rot_changed) {
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ if (pos_changed) {
+ body->pos = pos;
+ }
+
+ if (rot_changed) {
+ body->rot = QuaternionFromEuler(
+ rot.x * DEG2RAD,
+ rot.y * DEG2RAD,
+ rot.z * DEG2RAD);
+ }
+
+ body->mode = (PhysicsMode)mode;
+ body->enable_sleep = sleep;
+
+ body->gravity_scale = values[0];
+ body->linear_damping = values[1];
+ body->angular_damping = values[2];
+
+ action_commit(&action);
+ }
+ } else {
+ GuiLabel(info, "Select a body");
+ }
+
+ reset:
+ GuiUnlock();
+ if (input) {
+ ed.focus_captured = type_open || edit.focus != 0;
+ }
+}
+
+static void
panel_draw(EditorPanelContainer container) {
if (!ed.panel[container].enabled) {
return;
}
- bool input = app.mode == APP_MODE_EDIT_NONE &&
+ bool input = ed.interact == ED_INTERACT_NONE &&
ed.focus == container &&
(!ed.panel[ED_PNL_CENTER].enabled ||
container == ED_PNL_CENTER);
@@ -2051,7 +3260,6 @@ panel_draw(EditorPanelContainer container) {
ed.panel[container].enabled = false;
ed.focus = ED_PNL_NULL;
ed.focus_captured = false;
- app.mode = APP_MODE_EDIT_NONE;
}
panel_space(container);
break;
@@ -2081,6 +3289,14 @@ panel_draw(EditorPanelContainer container) {
}
break;
+
+ case ED_PNL_TYPE_PHYSICS_ELEMENTS:
+ panel_physics_elements(container);
+ break;
+
+ case ED_PNL_TYPE_PHYSICS_PROPERTIES:
+ panel_physics_properties(container, input);
+ break;
}
GuiSetState(state);
@@ -2115,7 +3331,7 @@ editor_draw_ui(void) {
panel_draw(ED_PNL_LEFT);
panel_draw(ED_PNL_RIGHT);
- if (app.mode == APP_MODE_EDIT_SELECT) {
+ if (ed.interact == ED_INTERACT_SELECT) {
if (started && sel_dist > 20) {
sel_rec_draw();
}
@@ -2186,13 +3402,177 @@ gizmo_rotate(void) {
}
}
+static void
+physics_draw_shapes(u64 id) {
+ Entity *ent = entity_get(id);
+ if (!ent) {
+ return;
+ }
+
+ Matrix entity_world = entity_world_matrix(id);
+ Quaternion entity_rot = entity_world_rot(id);
+
+ for (u32 i = 0; i < ent->phys.body_cnt; ++i) {
+ PhysicsBody *body = &ent->phys.bodies[i];
+
+ Matrix body_local = MatrixMultiply(
+ QuaternionToMatrix(body->rot),
+ MatrixTranslate(
+ body->pos.x, body->pos.y, body->pos.z));
+
+ Matrix body_world = MatrixMultiply(body_local, entity_world);
+
+ for (u32 j = 0; j < body->shape_cnt; ++j) {
+ PhysicsShape *shape = &body->shapes[j];
+
+ bool selected = ent->ed.selected;
+
+ if (ed.mode == ED_MODE_PHYSICS) {
+ selected = ent->pid == ed.physics_pid &&
+ ed.physics_body == (int)i &&
+ (ed.physics_shape < 0 ||
+ ed.physics_shape == (int)j);
+ }
+
+ Color colour = selected ? ORANGE : SKYBLUE;
+
+ Matrix shape_local = MatrixMultiply(
+ QuaternionToMatrix(shape->rot),
+ MatrixTranslate(
+ shape->pos.x,
+ shape->pos.y,
+ shape->pos.z));
+
+ Matrix world = MatrixMultiply(shape_local, body_world);
+
+ Vec3 scale = {
+ Vector3Length(
+ (Vec3){world.m0, world.m1, world.m2}),
+ Vector3Length(
+ (Vec3){world.m4, world.m5, world.m6}),
+ Vector3Length(
+ (Vec3){world.m8, world.m9, world.m10}),
+ };
+
+ Vec3 center = {world.m12, world.m13, world.m14};
+
+ Quaternion rotation = QuaternionMultiply(
+ QuaternionMultiply(entity_rot, body->rot),
+ shape->rot);
+
+ Matrix transform = MatrixMultiply(
+ QuaternionToMatrix(rotation),
+ MatrixTranslate(center.x, center.y, center.z));
+
+ rlPushMatrix();
+ rlMultMatrixf(MatrixToFloat(transform));
+
+ switch (shape->tag) {
+ case PHYS_SHAPE_BOX: {
+ Vec3 size = Vector3Multiply(
+ shape->Box.sz, scale);
+
+ DrawCubeWires((Vec3){0},
+ size.x, size.y, size.z, colour);
+ break;
+ }
+
+ case PHYS_SHAPE_SPHERE: {
+ Vec3 entity_scale = {
+ Vector3Length((Vec3){
+ entity_world.m0,
+ entity_world.m1,
+ entity_world.m2,
+ }),
+ Vector3Length((Vec3){
+ entity_world.m4,
+ entity_world.m5,
+ entity_world.m6,
+ }),
+ Vector3Length((Vec3){
+ entity_world.m8,
+ entity_world.m9,
+ entity_world.m10,
+ }),
+ };
+
+ f32 largest = fmaxf(entity_scale.x,
+ fmaxf(entity_scale.y,
+ entity_scale.z));
+
+ DrawSphereWires((Vec3){0},
+ shape->Sphere.radius * largest,
+ 8, 12, colour);
+ break;
+ }
+
+ case PHYS_SHAPE_CAPSULE: {
+ f32 radius = shape->Capsule.radius *
+ fmaxf(scale.x, scale.z);
+ f32 half_height =
+ shape->Capsule.height * scale.y * 0.5;
+
+ if (half_height == 0) {
+ DrawSphereWires((Vec3){0}, radius, 8,
+ 12, colour);
+ } else {
+ DrawCapsuleWires(
+ (Vec3) {0,-half_height,0},
+ (Vec3){0,half_height,0},
+ radius, 4, 8, colour);
+ }
+ break;
+ }
+
+ case PHYS_SHAPE_CYLINDER: {
+ f32 radius = shape->Cylinder.radius *
+ fmaxf(scale.x, scale.z);
+ f32 half_height =
+ shape->Cylinder.height * scale.y * 0.5f;
+
+ DrawCylinderWiresEx(
+ (Vec3){0, -half_height, 0},
+ (Vec3){0, half_height, 0},
+ radius, radius, 12, colour);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ rlPopMatrix();
+ }
+ }
+}
+
void
editor_draw_3d(void) {
- if (app.mode == APP_MODE_EDIT_MOVE) {
+ if (ed.mode == ED_MODE_PHYSICS) {
+ physics_draw_shapes(entity_id_from_pid(ed.physics_pid));
+ return;
+ }
+
+ if (ed.mode != ED_MODE_SCENE) {
+ return;
+ }
+
+ if (ed.draw_physics) {
+ for (u32 i = 0; i < entity_count(); ++i) {
+ u64 id = entity_id_at(i);
+ Entity *ent = entity_get(id);
+
+ if (ent->phys.has) {
+ physics_draw_shapes(id);
+ }
+ }
+ }
+
+ if (ed.interact == ED_INTERACT_MOVE) {
gizmo_move();
}
- if (app.mode == APP_MODE_EDIT_ROTATE) {
+ if (ed.interact == ED_INTERACT_ROTATE) {
gizmo_rotate();
}