diff options
| author | tech kaczynski <tk@techkaczynski.dev> | 2026-10-19 01:47:08 +1000 |
|---|---|---|
| committer | tech kaczynski <tk@techkaczynski.dev> | 2026-10-19 01:47:08 +1000 |
| commit | edc50a43a0ab064472aa7fde90f14cf456d92076 (patch) | |
| tree | c0281123898ddfa2d94683cbcd4cd85dd0d6ad6b | |
| parent | cc4ba95edf2927eda63af1aeb79a503bc6fe6182 (diff) | |
lots of editor work. Improved editor camera controls.
162 files changed, 2480 insertions, 251 deletions
@@ -49,16 +49,16 @@ app_exit(void) { static void app_loop(void) { app.run = !WindowShouldClose(); + entity_system_update(); /* this is some hypothetical bullshit, probably will change */ - if (app.mode != APP_MODE_PLAY) { + if (app.mode == APP_MODE_EDIT) { editor_update(); } physics_update(); res_update(); env_update(); - entity_system_update(); BeginDrawing(); ClearBackground(RAYWHITE); @@ -4,13 +4,7 @@ #include "common.h" typedef enum { - APP_MODE_EDIT_NONE, - APP_MODE_EDIT_SELECT, - APP_MODE_EDIT_CTRL, - APP_MODE_EDIT_MOVE, - APP_MODE_EDIT_ROTATE, - APP_MODE_EDIT_SPACE, - APP_MODE_EDIT_CMD, + APP_MODE_EDIT, APP_MODE_PLAY, } AppMode; diff --git a/b3internalassert.c b/b3internalassert.c index 5beecbe..aec7fda 100644 --- a/b3internalassert.c +++ b/b3internalassert.c @@ -2,7 +2,9 @@ #include <stdlib.h> /* deepseek gave me this when I pasted errors from cproc C compiler */ -int b3InternalAssert(const char* condition, const char* fileName, int lineNumber) { +int +b3InternalAssert(const char* condition, + const char* fileName, int lineNumber) { fprintf(stderr, "Box3D Assertion Failed: (%s) in %s at line %d\n", condition, fileName, lineNumber); return 1; @@ -1,25 +1,157 @@ #include "cam.h" +#include "reasings.h" #include <rcamera.h> +static void cam_ctrl_editor(void); +static void cam_ctrl_orbit(void); + CameraInfo main_cam = {0}; void cam_init(void) { rlSetClipPlanes(0.3, 5000); main_cam.cam = (Camera3D) { - .position = { 0, 10, -10 }, + .position = { 0, 10, 10 }, .target = { 0, 1, 0 }, .up = { 0, 1, 0}, .fovy = 70.0f, .projection = CAMERA_PERSPECTIVE, }; main_cam.update = false; + main_cam.allow_fly = true; + main_cam.state = CAM_MODE_EDIT_FLY; + main_cam.perspective_fov = main_cam.cam.fovy; main_cam.mv_speed = 10; main_cam.mv_scale = 1; - main_cam.lk_speed = 0.2; + main_cam.lk_speed = 0.003f; } void +cam_focus(Vec3 target, f32 distance, f32 view_height) { + Camera *cam = &main_cam.cam; + Vec3 backward = Vector3Normalize( + Vector3Subtract(cam->position, cam->target)); + + main_cam.transition.start_pos = cam->position; + main_cam.transition.start_target = cam->target; + + main_cam.transition.end_target = target; + main_cam.transition.end_pos = Vector3Add(target, + Vector3Scale(backward, fmaxf(distance, 0.5f))); + + main_cam.transition.start_fovy = cam->fovy; + main_cam.transition.end_fovy = cam->fovy; + + if (cam->projection == CAMERA_ORTHOGRAPHIC) { + main_cam.transition.end_fovy = fmaxf(view_height, 0.1f); + } + + main_cam.transition.elapsed = 0; + main_cam.transition.active = true; + main_cam.state = CAM_MODE_EDIT_ORBIT; +} + +void +cam_update(void) { + if (main_cam.input) { + if (!main_cam.update && !main_cam.transition.active && + IsKeyPressed(KEY_P)) { + Camera *cam = &main_cam.cam; + f32 distance = Vector3Distance( + cam->position, cam->target); + + if (cam->projection == CAMERA_PERSPECTIVE) { + main_cam.perspective_fov = cam->fovy; + + cam->fovy = 2.0f * distance * + tanf(main_cam.perspective_fov * + DEG2RAD * 0.5f); + + cam->projection = CAMERA_ORTHOGRAPHIC; + main_cam.state = CAM_MODE_EDIT_ORBIT; + } else { + Vec3 backward = Vector3Normalize( + Vector3Subtract(cam->position, + cam->target)); + + distance = cam->fovy / (2.0f * + tanf(main_cam.perspective_fov * + DEG2RAD * 0.5f)); + + cam->position = Vector3Add( + cam->target, + Vector3Scale(backward, distance) + ); + + cam->fovy = main_cam.perspective_fov; + cam->projection = CAMERA_PERSPECTIVE; + } + } + + bool fly = main_cam.update && + main_cam.allow_fly && + IsMouseButtonDown(MOUSE_BUTTON_RIGHT) && + (IsKeyDown(KEY_W) || IsKeyDown(KEY_A) || + IsKeyDown(KEY_S) || IsKeyDown(KEY_D) || + IsKeyDown(KEY_Q) || IsKeyDown(KEY_E)); + + if (fly) { + main_cam.transition.active = false; + main_cam.state = CAM_MODE_EDIT_FLY; + } + + bool navigate = main_cam.update || + (main_cam.state == CAM_MODE_EDIT_ORBIT && + GetMouseWheelMove() != 0); + + if (navigate && !main_cam.transition.active) { + switch (main_cam.state) { + case CAM_MODE_EDIT_FLY: + cam_ctrl_editor(); + break; + + case CAM_MODE_EDIT_ORBIT: + cam_ctrl_orbit(); + break; + + default: + break; + } + } + } + + if (!main_cam.transition.active) { + return; + } + + const f32 duration = 0.25f; + main_cam.transition.elapsed = fminf( + main_cam.transition.elapsed + GetFrameTime(), duration); + + f32 t = EaseSineInOut( + main_cam.transition.elapsed, 0.0f, 1.0f, duration); + + main_cam.cam.position = Vector3Lerp( + main_cam.transition.start_pos, + main_cam.transition.end_pos, t); + + main_cam.cam.target = Vector3Lerp( + main_cam.transition.start_target, + main_cam.transition.end_target, t); + + main_cam.cam.fovy = Lerp( + main_cam.transition.start_fovy, + main_cam.transition.end_fovy, t); + + if (main_cam.transition.elapsed >= duration) { + main_cam.cam.position = main_cam.transition.end_pos; + main_cam.cam.target = main_cam.transition.end_target; + main_cam.cam.fovy = main_cam.transition.end_fovy; + main_cam.transition.active = false; + } +} + +static void cam_ctrl_editor(void) { if (!main_cam.update) { return; @@ -61,14 +193,14 @@ cam_ctrl_editor(void) { // makes it so you can stop faster than you can speed up f32 ms_amount = GetMouseWheelMove() < 0 ? - GetMouseWheelMove() * 1.5 : - GetMouseWheelMove() * 0.75; + GetMouseWheelMove() * 1.0 : + GetMouseWheelMove() * 0.5; main_cam.mv_scale += ms_amount; - main_cam.mv_scale = CLAMP(main_cam.mv_scale, 0.1, 50); + main_cam.mv_scale = CLAMP(main_cam.mv_scale, 0.05, 80); mv_speed *= main_cam.mv_scale; - mv_speed = CLAMP(mv_speed, 2, 1000); + mv_speed = CLAMP(mv_speed, 1, 1000); CameraMoveForward(&main_cam.cam, input_dir.z * mv_speed * GetFrameTime(), @@ -80,9 +212,60 @@ cam_ctrl_editor(void) { CameraMoveUp(&main_cam.cam, input_dir.y * mv_speed * GetFrameTime()); - CameraYaw(&main_cam.cam, - -GetMouseDelta().x * lk_speed * GetFrameTime(), 0); + Vec2 delta = GetMouseDelta(); + CameraYaw(&main_cam.cam, -delta.x * lk_speed, false); + CameraPitch(&main_cam.cam, -delta.y * lk_speed, true, false, false); +} + +static void +cam_ctrl_orbit(void) { + Camera *cam = &main_cam.cam; + f32 wheel = GetMouseWheelMove(); + f32 view_height; + + if (cam->projection == CAMERA_ORTHOGRAPHIC) { + if (wheel != 0) { + cam->fovy = Clamp( + cam->fovy * powf(0.85f, wheel), 0.1f, 2000.0f); + } - CameraPitch(&main_cam.cam, - -GetMouseDelta().y * lk_speed * GetFrameTime(), 1, 0, 0); + view_height = cam->fovy; + } else { + f32 distance = Vector3Distance(cam->position, cam->target); + + if (wheel != 0) { + f32 zoom = Clamp( + distance * powf(0.85f, wheel), 0.5f, 1000.0f); + CameraMoveToTarget(cam, zoom - distance); + distance = zoom; + } + + view_height = 2.0f * distance * + tanf(cam->fovy * DEG2RAD * 0.5f); + } + + if (!main_cam.update) { + return; + } + + Vec2 delta = GetMouseDelta(); + + if (IsMouseButtonDown(MOUSE_MIDDLE_BUTTON)) { + Vec3 forward = GetCameraForward(cam); + Vec3 right = GetCameraRight(cam); + Vec3 up = Vector3Normalize( + Vector3CrossProduct(right, forward)); + + f32 scale = view_height / GetScreenHeight(); + Vec3 offset = Vector3Add( + Vector3Scale(right, -delta.x * scale), + Vector3Scale(up, delta.y * scale)); + + cam->position = Vector3Add(cam->position, offset); + cam->target = Vector3Add(cam->target, offset); + } else { + CameraYaw(cam, -delta.x * main_cam.lk_speed, true); + CameraPitch(cam, -delta.y * main_cam.lk_speed, + true, true, false); + } } @@ -6,19 +6,29 @@ typedef enum { CAM_MODE_NONE, CAM_MODE_EDIT_FLY, - // CAM_MODE_EDIT_ORTHO, + CAM_MODE_EDIT_ORBIT, } CamState; typedef struct { Camera cam; CamState state; f32 mv_speed, mv_scale, lk_speed; - bool update; + f32 perspective_fov; + bool update, input, allow_fly; + + struct { + Vec3 start_pos, start_target; + Vec3 end_pos, end_target; + f32 start_fovy, end_fovy; + f32 elapsed; + bool active; + } transition; } CameraInfo; extern CameraInfo main_cam; -void cam_init(void); -void cam_ctrl_editor(void); +void cam_init(void); +void cam_update(void); +void cam_focus(Vec3 target, f32 distance, f32 view_height); #endif @@ -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(); } @@ -3,6 +3,7 @@ #include "common.h" #include "ent.h" +#include "cam.h" typedef enum { ED_ACTION_TRANSFORM, @@ -10,6 +11,7 @@ typedef enum { ED_ACTION_DELETE, ED_ACTION_RENAME, ED_ACTION_MODEL, + ED_ACTION_PHYSICS, } EditorActionTag; typedef union { @@ -29,6 +31,7 @@ typedef union { } rename; ModelCmp model; + PhysicsCmp physics; } EditorActionData; typedef struct { @@ -53,9 +56,26 @@ typedef enum { ED_PNL_TYPE_PROPERTIES, ED_PNL_TYPE_SPACE, ED_PNL_TYPE_MODEL_PICKER, + ED_PNL_TYPE_PHYSICS_ELEMENTS, + ED_PNL_TYPE_PHYSICS_PROPERTIES, ED_PNL_TYPE_CMD, } EditorPanelType; +typedef enum { + ED_MODE_SCENE, + ED_MODE_ENTITY, + ED_MODE_PHYSICS, +} EditorModeTag; + +typedef enum { + ED_INTERACT_NONE, + ED_INTERACT_SELECT, + ED_INTERACT_MOVE, + ED_INTERACT_ROTATE, + ED_INTERACT_SCALE, + ED_INTERACT_CAMERA, +} EditorInteraction; + typedef struct { Vec3 sel_point; Quaternion sel_rot; @@ -63,6 +83,14 @@ typedef struct { f32 grid_sz; + u64 physics_pid; + int physics_body, physics_shape; + Vec2 physics_scroll; + CameraInfo scene_cam; + bool draw_physics; + + EditorModeTag mode, prev_mode; + EditorInteraction interact; EditorPanelContainer focus; bool focus_captured; struct { @@ -1,5 +1,7 @@ #include "ent.h" #include "res.h" +#include "app.h" +#include "editor.h" typedef struct { u32 gen, dense_id, next_free; @@ -243,30 +245,11 @@ entity_system_clear(void) { en.delete_queue_cnt = 0; en.free_slot = ENTITY_INVALID_IDX; en.next_pid = 1; - - } static void -entity_draw_mdl(Entity *ent) { - Matrix T = MatrixTranslate( - ent->tr.pos.x, - ent->tr.pos.y, - ent->tr.pos.z); - - // Matrix R = MatrixRotateXYZ((Vec3) { - // ent->tr.rot.x * DEG2RAD, - // ent->tr.rot.y * DEG2RAD, - // ent->tr.rot.z * DEG2RAD, - // }); - - Matrix R = QuaternionToMatrix(ent->tr.rot); - - Matrix S = MatrixScale( - ent->tr.scale.x, - ent->tr.scale.y, - ent->tr.scale.z); - +entity_draw_mdl(u64 id) { + Entity *ent = entity_get(id); if (!ent->mdl.mdl) { Asset *asset = asset_get(ent->mdl.key); if (!asset) { @@ -276,108 +259,32 @@ entity_draw_mdl(Entity *ent) { } Model mdl = *ent->mdl.mdl; - mdl.transform = MatrixMultiply(S, MatrixMultiply(R, T)); + mdl.transform = entity_world_matrix(id); - Color tint = ent->ed.selected ? + Color tint = ent->ed.selected && ed.mode == ED_MODE_SCENE ? (Color){ 100, 100, 100, 255 } : BLACK; DrawModel(mdl, (Vec3) {0}, 1, tint); } -// static void -// entity_draw_col(u64 id) { -// Entity *ent = entity_get(id); - -// if (!b3Body_IsValid(ent->body_id)) { -// TraceLog(LOG_WARNING, "tried to draw invalid physics body"); -// return; -// } - -// if (b3Body_GetShapes(ent->body_id, &ent->shape_id, 1) == 0) { -// return; -// } - -// const b3HullData *hull = b3Shape_GetHull(ent->shape_id); -// const b3Vec3 *points = b3GetHullPoints(hull); -// const b3HullHalfEdge *edges = b3GetHullEdges(hull); -// b3WorldTransform tr = b3Body_GetTransform(ent->body_id); - -// for (int i = 0; i < hull->edgeCount; ++i) { -// if (i > edges[i].twin) { -// continue; -// } - -// const b3HullHalfEdge *edge = &edges[i]; -// const b3HullHalfEdge *twin = &edges[edge->twin]; - -// b3Pos world_a = -// b3TransformWorldPoint(tr, points[edge->origin]); -// b3Pos world_b = -// b3TransformWorldPoint(tr, points[twin->origin]); - -// Color tint = GRAY; -// if (FLAG_CHECK(ent->flags, BIT(EFLAG_EDITOR_SELECTED))) { -// tint = LIME; -// } - -// DrawLine3D(*(Vec3*)&world_a, *(Vec3*)&world_b, tint); -// } -// } - -// static void -// entity_set_b3_transform(u64 id) { -// Entity *ent = entity_get(id); - -// if (!b3Body_IsValid(ent->body_id)) { -// return; -// } - -// b3WorldTransform tr = b3Body_GetTransform(ent->body_id); - -// // if in edit mode, entity transform is authoritative -// if (FLAG_CHECK(app.flags, BIT(APP_FLAG_DEBUG))) { -// Quaternion eq = QuaternionFromEuler( -// ent->rot.x * DEG2RAD, -// ent->rot.y * DEG2RAD, -// ent->rot.z * DEG2RAD); - -// Quaternion bq = { -// .x = tr.q.v.x, -// .y = tr.q.v.y, -// .z = tr.q.v.z, -// .w = tr.q.s -// }; - -// if (Vector3Equals(ent->pos, *(Vec3*)&tr.p) && -// QuaternionEquals(eq, bq)) { -// return; -// } - -// b3Quat b3rot = { .v = { eq.x, eq.y, eq.z }, .s = eq.w }; -// b3Body_SetTransform(ent->body_id, *(b3Pos*)&ent->pos, b3rot); -// } else { -// ent->pos.x = tr.p.x; -// ent->pos.y = tr.p.y; -// ent->pos.z = tr.p.y; - -// Vec3 euler_angles = -// QuaternionToEuler((Quaternion) { -// .x = tr.q.v.x, -// .y = tr.q.v.y, -// .z = tr.q.v.z, -// .w = tr.q.s -// }); - -// ent->rot.x = euler_angles.x * RAD2DEG; -// ent->rot.y = euler_angles.y * RAD2DEG; -// ent->rot.z = euler_angles.z * RAD2DEG; -// } -// } - void entity_system_update(void) { entity_process_delete_queue(); + + if (physics.simulate) { + return; + } + + for (u32 i = 0; i < entity_count(); ++i) { + u64 id = entity_id_at(i); + Entity *ent = entity_get(id); + PhysicsCmp *phys = &ent->phys; + + if (phys->has && phys->body_cnt && !phys->runtime.bodies) { + entity_physics_build(id); + } + } } void @@ -386,8 +293,14 @@ entity_system_draw_3d(void) { u64 id = entity_id_at(i); Entity *ent = entity_get(id); + if (app.mode == APP_MODE_EDIT && + ed.mode == ED_MODE_PHYSICS && + ent->pid != ed.physics_pid) { + continue; + } + if (ent->mdl.has && ent->mdl.draw) { - entity_draw_mdl(ent); + entity_draw_mdl(id); } } } @@ -475,10 +388,75 @@ entity_delete(u64 id) { en.delete_queue_cnt += 1; } -static void -entity_physics_clear(Entity *ent) { - PhysicsCmp *phys = &ent->phys; +static void * +physics_copy_data(const void *src, size_t size) { + if (!size) { + return NULL; + } + + void *copy = malloc(size); + if (!copy) { + perror("physics_copy_data"); + exit(1); + } + + memcpy(copy, src, size); + return copy; +} + +PhysicsCmp +entity_physics_copy(const PhysicsCmp *src) { + PhysicsCmp copy = *src; + + copy.runtime.bodies = NULL; + copy.runtime.joints = NULL; + + copy.bodies = physics_copy_data( + src->bodies, src->body_cnt * sizeof(*src->bodies)); + copy.joints = physics_copy_data( + src->joints, src->joint_cnt * sizeof(*src->joints)); + + for (u32 i = 0; i < src->body_cnt; ++i) { + const PhysicsBody *source = &src->bodies[i]; + PhysicsBody *body = ©.bodies[i]; + + body->shapes = physics_copy_data( + source->shapes, + source->shape_cnt * sizeof(*source->shapes)); + + for (u32 j = 0; j < body->shape_cnt; ++j) { + PhysicsShape *shape = &body->shapes[j]; + + switch (shape->tag) { + case PHYS_SHAPE_CONVEX_HULL: + shape->ConvexHull.points = physics_copy_data( + shape->ConvexHull.points, + shape->ConvexHull.point_cnt * + sizeof(*shape->ConvexHull.points)); + break; + + case PHYS_SHAPE_STATIC_TRIMESH: + shape->TriMesh.points = physics_copy_data( + shape->TriMesh.points, + shape->TriMesh.point_cnt * + sizeof(*shape->TriMesh.points)); + shape->TriMesh.indices = physics_copy_data( + shape->TriMesh.indices, + shape->TriMesh.idx_cnt * + sizeof(*shape->TriMesh.indices)); + break; + + default: + break; + } + } + } + return copy; +} + +void +entity_physics_clear(PhysicsCmp *phys) { if (phys->runtime.joints) { for (int i = 0; i < phys->joint_cnt; ++i) { b3JointId joint_id = phys->runtime.joints[i]; @@ -504,41 +482,400 @@ entity_physics_clear(Entity *ent) { phys->runtime.bodies = NULL; } +static void +physics_build_shapes(u64 id, u32 body_index) { + Entity *ent = entity_get(id); + PhysicsBody *body = &ent->phys.bodies[body_index]; + PhysicsBodyRuntime *runtime = &ent->phys.runtime.bodies[body_index]; + + if (!body->shape_cnt) { + return; + } + + runtime->shapes = calloc( + body->shape_cnt, sizeof(*runtime->shapes)); + + if (!runtime->shapes) { + perror("physics shape handles allocation failed"); + exit(EXIT_FAILURE); + } + + Matrix entity_world = entity_world_matrix(id); + Quaternion entity_rot = entity_world_rot(id); + + Vec3 pos = Vector3Transform(body->pos, entity_world); + Quaternion rot = QuaternionMultiply(entity_rot, body->rot); + Quaternion inverse_rot = QuaternionInvert(rot); + + 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]; + + 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}; + center = Vector3RotateByQuaternion( + Vector3Subtract(center, pos), inverse_rot); + + b3ShapeDef def = b3DefaultShapeDef(); + def.density = shape->density; + def.baseMaterial.friction = shape->friction; + def.baseMaterial.restitution = shape->restitution; + + def.filter.categoryBits = shape->category_bits | + BIT(PHYS_MASK_EDITOR_SELECTABLE); + def.filter.maskBits = shape->mask_bits | + BIT(PHYS_MASK_EDITOR_RAYCAST); + + switch (shape->tag) { + case PHYS_SHAPE_BOX: { + Vec3 size = Vector3Multiply(shape->Box.sz, scale); + + if (size.x <= 0 || size.y <= 0 || size.z <= 0) { + break; + } + + b3Transform local = { + .p = {center.x, center.y, center.z}, + .q = { + .v = { + shape->rot.x, + shape->rot.y, + shape->rot.z, + }, + .s = shape->rot.w, + }, + }; + + b3BoxHull hull = b3MakeTransformedBoxHull( + size.x * 0.5f, + size.y * 0.5f, + size.z * 0.5f, + local); + + runtime->shapes[j] = b3CreateHullShape( + runtime->id, &def, &hull.base); + 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)); + f32 radius = shape->Sphere.radius * largest; + + if (radius <= 0) { + break; + } + + b3Sphere sphere = { + .center = {center.x, center.y, center.z}, + .radius = radius, + }; + + runtime->shapes[j] = b3CreateSphereShape( + runtime->id, &def, &sphere); + break; + } + + case PHYS_SHAPE_CAPSULE: { + f32 radius = shape->Capsule.radius * + fmaxf(scale.x, scale.z); + f32 half_height = + shape->Capsule.height * scale.y * 0.5f; + + if (radius <= 0) { + break; + } + + if (half_height == 0) { + b3Sphere sphere = { + .center = { + center.x, + center.y, + center.z + }, + .radius = radius, + }; + + runtime->shapes[j] = b3CreateSphereShape( + runtime->id, &def, &sphere); + break; + } + + Vec3 offset = Vector3RotateByQuaternion( + (Vec3){0, half_height, 0}, shape->rot); + + Vec3 start = Vector3Subtract(center, offset); + Vec3 end = Vector3Add(center, offset); + + b3Capsule capsule = { + .center1 = {start.x, start.y, start.z}, + .center2 = {end.x, end.y, end.z}, + .radius = radius, + }; + + runtime->shapes[j] = b3CreateCapsuleShape( + runtime->id, &def, &capsule); + break; + } + + case PHYS_SHAPE_CYLINDER: { + f32 radius = shape->Cylinder.radius * + fmaxf(scale.x, scale.z); + f32 height = shape->Cylinder.height * scale.y; + + if (radius <= 0 || height <= 0) { + break; + } + + b3HullData *hull = b3CreateCylinder( + height, radius, -height * 0.5f, 12); + + if (!hull) { + TraceLog(LOG_ERROR, + "cylinder hull creation failed"); + break; + } + + b3Transform local = { + .p = {center.x, center.y, center.z}, + .q = { + .v = { + shape->rot.x, + shape->rot.y, + shape->rot.z, + }, + .s = shape->rot.w, + }, + }; + + runtime->shapes[j] = b3CreateTransformedHullShape( + runtime->id, &def, hull, local, + (b3Vec3){1, 1, 1}); + + b3DestroyHull(hull); + break; + } + + default: + break; + } + } +} + void -entity_clean(Entity *ent) { - free(ent->name); +entity_physics_build(u64 id) { + Entity *ent = entity_get(id); + if (!ent) { + TraceLog(LOG_ERROR, + "entity_physics_build: invalid entity id"); + return; + } - /* model component data */ - free(ent->mdl.key); + PhysicsCmp *phys = &ent->phys; + entity_physics_clear(phys); - /* physics component data */ - entity_physics_clear(ent); // runtime data - for (int i = 0; i < ent->phys.body_cnt; ++i) { - PhysicsBody *body = &ent->phys.bodies[i]; + if (!phys->has || !phys->body_cnt) { + return; + } + + phys->runtime.bodies = calloc(phys->body_cnt, + sizeof(*phys->runtime.bodies)); + + if (!phys->runtime.bodies) { + perror("entity_physics_build: allocation failed"); + exit(1); + } + + Matrix entity_world = entity_world_matrix(id); + Quaternion entity_rot = entity_world_rot(id); + + phys->runtime.world = entity_world; + phys->runtime.world_rot = entity_rot; + + for (u32 i = 0; i < phys->body_cnt; ++i) { + PhysicsBody *body = &phys->bodies[i]; + PhysicsBodyRuntime *runtime = &phys->runtime.bodies[i]; + + runtime->entity_id = id; + + Vec3 pos = Vector3Transform(body->pos, entity_world); + Quaternion rot = QuaternionMultiply(entity_rot, body->rot); + + b3BodyDef def = b3DefaultBodyDef(); + + switch (body->mode) { + case PHYS_MODE_STATIC: + def.type = b3_staticBody; + break; + + case PHYS_MODE_KINEMATIC: + def.type = b3_kinematicBody; + break; + + case PHYS_MODE_DYNAMIC: + def.type = b3_dynamicBody; + break; + } + + def.position = (b3Pos){pos.x, pos.y, pos.z}; + def.rotation = (b3Quat){ + .v = {rot.x, rot.y, rot.z}, + .s = rot.w, + }; + + def.linearDamping = body->linear_damping; + def.angularDamping = body->angular_damping; + def.gravityScale = body->gravity_scale; + def.enableSleep = body->enable_sleep; + def.userData = &runtime->entity_id; - for (int j = 0; j < body->shape_cnt; ++j) { + runtime->id = b3CreateBody(physics.world_id, &def); + physics_build_shapes(id, i); + } +} + +void +entity_physics_free(PhysicsCmp *phys) { + entity_physics_clear(phys); + + for (u32 i = 0; i < phys->body_cnt; ++i) { + PhysicsBody *body = &phys->bodies[i]; + + for (u32 j = 0; j < body->shape_cnt; ++j) { PhysicsShape *shape = &body->shapes[j]; switch (shape->tag) { case PHYS_SHAPE_CONVEX_HULL: - free(shape->ConvexHull.points); - break; + free(shape->ConvexHull.points); + break; case PHYS_SHAPE_STATIC_TRIMESH: - free(shape->TriMesh.points); - free(shape->TriMesh.indices); - break; + free(shape->TriMesh.points); + free(shape->TriMesh.indices); + break; default: - break; + break; } } free(body->shapes); } - free(ent->phys.bodies); - free(ent->phys.joints); + free(phys->bodies); + free(phys->joints); + *phys = (PhysicsCmp){0}; +} + +void +entity_physics_transform_changed(u64 id) { + if (physics.simulate) { + return; + } + + Entity *ent = entity_get(id); + assert(ent); + + /* Children inherit this entity's transform. */ + for (u64 child = ent->tr.first_child; child; + child = entity_get(child)->tr.next) { + entity_physics_transform_changed(child); + } + + PhysicsCmp *phys = &ent->phys; + if (!phys->runtime.bodies) { + return; + } + + Matrix world = entity_world_matrix(id); + Quaternion world_rot = entity_world_rot(id); + + /* Remove world rotation, leaving scale and distortion. */ + Matrix previous = MatrixMultiply( + phys->runtime.world, + QuaternionToMatrix( + QuaternionInvert(phys->runtime.world_rot))); + + Matrix current = MatrixMultiply( + world, + QuaternionToMatrix(QuaternionInvert(world_rot))); + + float16 before = MatrixToFloatV(previous); + float16 after = MatrixToFloatV(current); + + /* Compare only the upper-left 3x3; translation is irrelevant. */ + for (int column = 0; column < 3; ++column) { + for (int row = 0; row < 3; ++row) { + int index = column * 4 + row; + + if (!FloatEquals(before.v[index], after.v[index])) { + entity_physics_clear(phys); + return; + } + } + } + + /* Geometry still fits: update the existing body poses. */ + for (u32 i = 0; i < phys->body_cnt; ++i) { + PhysicsBody *body = &phys->bodies[i]; + + Vec3 pos = Vector3Transform(body->pos, world); + Quaternion rot = QuaternionMultiply(world_rot, body->rot); + + b3Body_SetTransform( + phys->runtime.bodies[i].id, + (b3Pos){pos.x, pos.y, pos.z}, + (b3Quat){ + .v = {rot.x, rot.y, rot.z}, + .s = rot.w, + }); + } +} + +void +entity_clean(Entity *ent) { + free(ent->name); + + /* model component data */ + free(ent->mdl.key); + + /* physics component data */ + entity_physics_free(&ent->phys); } Entity @@ -552,7 +889,7 @@ entity_extract_now(u64 id) { assert(!en.entity[dense_id].tr.first_child); - entity_physics_clear(&en.entity[dense_id]); + entity_physics_clear(&en.entity[dense_id].phys); entity_set_parent(id, 0); Entity extracted = en.entity[dense_id]; @@ -661,6 +998,7 @@ entity_set_parent(u64 id, u64 parent_id) { entity_unlink_parent(id); if (!parent) { + entity_physics_transform_changed(id); return true; } @@ -676,6 +1014,7 @@ entity_set_parent(u64 id, u64 parent_id) { } parent->tr.first_child = id; + entity_physics_transform_changed(id); return true; } @@ -33,10 +33,13 @@ typedef struct { PhysicsBody *bodies; PhysicsJoint *joints; u32 body_cnt, joint_cnt; + u32 root_body; struct { PhysicsBodyRuntime *bodies; b3JointId *joints; + Matrix world; + Quaternion world_rot; } runtime; bool has, debug_draw; @@ -73,6 +76,7 @@ void entity_clean(Entity*); // clean entity data without removing Entity entity_extract_now(u64 id); // removes, returns ownership bool entity_valid(u64 id); + Entity* entity_get(u64 id); u64 entity_id_at(u32 dense_idx); u64 entity_id_from_pid(u64 pid); @@ -93,4 +97,11 @@ Quaternion entity_world_rot(u64 id); void entity_set_world_pos(Entity*, Vec3); void entity_set_world_rot(Entity*, Quaternion); +/* physics component data */ +PhysicsCmp entity_physics_copy(const PhysicsCmp*); +void entity_physics_build(u64 id); +void entity_physics_clear(PhysicsCmp*); +void entity_physics_free(PhysicsCmp*); +void entity_physics_transform_changed(u64 id); + #endif @@ -244,10 +244,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.2; + env.cycle = 0.35; env.day_len = 240; // 600s = 5 min day/night cycle env.sun_sz = 14; env.moon_sz = 4; + // env.sun_tint = (Color) { 255, 255, 255, 40 }; env.sun_tint = WHITE; env.moon_tint = GRAY; @@ -96,9 +96,9 @@ typedef struct { /* physics joint fields meaning changes depending on their context. - anchor_a/axis_a are local to body_a. - anchor_b/axis_b are local to body_b. - axes should be normalized. + anchor_a/frame_rot_a define the joint frame local to body_a. + anchor_b/frame_rot_b define the joint frame local to body_b. + frame rotations are normalized quaternions. lower/upper are generic limits - revolute: radians @@ -129,7 +129,8 @@ typedef struct { Flags32 flags; Vec3 anchor_a, anchor_b; - Vec3 axis_a, axis_b; + Quaternion frame_rot_a, frame_rot_b; + // Vec3 axis_a, axis_b; f32 lower, upper; f32 spring_hz, spring_damping; @@ -137,6 +138,7 @@ typedef struct { } PhysicsJoint; typedef struct { + u64 entity_id; b3BodyId id; b3ShapeId *shapes; // count is stored in PhysicsCmp } PhysicsBodyRuntime; diff --git a/reasings.h b/reasings.h new file mode 100644 index 0000000..4f41514 --- /dev/null +++ b/reasings.h @@ -0,0 +1,265 @@ +/******************************************************************************************* +* +* reasings - raylib easings library, based on Robert Penner library +* +* Useful easing functions for values animation +* +* This header uses: +* #define REASINGS_STATIC_INLINE // Inlines all functions code, so it runs faster. +* // This requires lots of memory on system. +* How to use: +* The four inputs t,b,c,d are defined as follows: +* t = current time (in any unit measure, but same unit as duration) +* b = starting value to interpolate +* c = the total change in value of b that needs to occur +* d = total time it should take to complete (duration) +* +* Example: +* +* int currentTime = 0; +* int duration = 100; +* float startPositionX = 0.0f; +* float finalPositionX = 30.0f; +* float currentPositionX = startPositionX; +* +* while (currentPositionX < finalPositionX) +* { +* currentPositionX = EaseSineIn(currentTime, startPositionX, finalPositionX - startPositionX, duration); +* currentTime++; +* } +* +* A port of Robert Penner's easing equations to C (http://robertpenner.com/easing/) +* +* Robert Penner License +* --------------------------------------------------------------------------------- +* Open source under the BSD License. +* +* Copyright (c) 2001 Robert Penner. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without modification, +* are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* - Neither the name of the author nor the names of contributors may be used +* to endorse or promote products derived from this software without specific +* prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +* --------------------------------------------------------------------------------- +* +* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef REASINGS_H +#define REASINGS_H + +#define REASINGS_STATIC_INLINE // NOTE: By default, compile functions as static inline + +#ifndef EASEDEF +#if defined(REASINGS_STATIC_INLINE) + #define EASEDEF static inline +#else + #define EASEDEF extern +#endif +#endif + +#include <math.h> // Required for: sinf(), cosf(), sqrtf(), powf() + +#ifndef PI + #define PI 3.14159265358979323846f //Required as PI is not always defined in math.h +#endif + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Linear Easing functions +EASEDEF float EaseLinearNone(float t, float b, float c, float d) { return (c*t/d + b); } // Ease: Linear +EASEDEF float EaseLinearIn(float t, float b, float c, float d) { return (c*t/d + b); } // Ease: Linear In +EASEDEF float EaseLinearOut(float t, float b, float c, float d) { return (c*t/d + b); } // Ease: Linear Out +EASEDEF float EaseLinearInOut(float t, float b, float c, float d) { return (c*t/d + b); } // Ease: Linear In Out + +// Sine Easing functions +EASEDEF float EaseSineIn(float t, float b, float c, float d) { return (-c*cosf(t/d*(PI/2.0f)) + c + b); } // Ease: Sine In +EASEDEF float EaseSineOut(float t, float b, float c, float d) { return (c*sinf(t/d*(PI/2.0f)) + b); } // Ease: Sine Out +EASEDEF float EaseSineInOut(float t, float b, float c, float d) { return (-c/2.0f*(cosf(PI*t/d) - 1.0f) + b); } // Ease: Sine Out + +// Circular Easing functions +EASEDEF float EaseCircIn(float t, float b, float c, float d) { t /= d; return (-c*(sqrtf(1.0f - t*t) - 1.0f) + b); } // Ease: Circular In +EASEDEF float EaseCircOut(float t, float b, float c, float d) { t = t/d - 1.0f; return (c*sqrtf(1.0f - t*t) + b); } // Ease: Circular Out +EASEDEF float EaseCircInOut(float t, float b, float c, float d) // Ease: Circular In Out +{ + if ((t/=d/2.0f) < 1.0f) return (-c/2.0f*(sqrtf(1.0f - t*t) - 1.0f) + b); + t -= 2.0f; return (c/2.0f*(sqrtf(1.0f - t*t) + 1.0f) + b); +} + +// Cubic Easing functions +EASEDEF float EaseCubicIn(float t, float b, float c, float d) { t /= d; return (c*t*t*t + b); } // Ease: Cubic In +EASEDEF float EaseCubicOut(float t, float b, float c, float d) { t = t/d - 1.0f; return (c*(t*t*t + 1.0f) + b); } // Ease: Cubic Out +EASEDEF float EaseCubicInOut(float t, float b, float c, float d) // Ease: Cubic In Out +{ + if ((t/=d/2.0f) < 1.0f) return (c/2.0f*t*t*t + b); + t -= 2.0f; return (c/2.0f*(t*t*t + 2.0f) + b); +} + +// Quadratic Easing functions +EASEDEF float EaseQuadIn(float t, float b, float c, float d) { t /= d; return (c*t*t + b); } // Ease: Quadratic In +EASEDEF float EaseQuadOut(float t, float b, float c, float d) { t /= d; return (-c*t*(t - 2.0f) + b); } // Ease: Quadratic Out +EASEDEF float EaseQuadInOut(float t, float b, float c, float d) // Ease: Quadratic In Out +{ + if ((t/=d/2) < 1) return (((c/2)*(t*t)) + b); + return (-c/2.0f*(((t - 1.0f)*(t - 3.0f)) - 1.0f) + b); +} + +// Exponential Easing functions +EASEDEF float EaseExpoIn(float t, float b, float c, float d) { return (t == 0.0f) ? b : (c*powf(2.0f, 10.0f*(t/d - 1.0f)) + b); } // Ease: Exponential In +EASEDEF float EaseExpoOut(float t, float b, float c, float d) { return (t == d) ? (b + c) : (c*(-powf(2.0f, -10.0f*t/d) + 1.0f) + b); } // Ease: Exponential Out +EASEDEF float EaseExpoInOut(float t, float b, float c, float d) // Ease: Exponential In Out +{ + if (t == 0.0f) return b; + if (t == d) return (b + c); + if ((t/=d/2.0f) < 1.0f) return (c/2.0f*powf(2.0f, 10.0f*(t - 1.0f)) + b); + + return (c/2.0f*(-powf(2.0f, -10.0f*(t - 1.0f)) + 2.0f) + b); +} + +// Back Easing functions +EASEDEF float EaseBackIn(float t, float b, float c, float d) // Ease: Back In +{ + float s = 1.70158f; + float postFix = t/=d; + return (c*(postFix)*t*((s + 1.0f)*t - s) + b); +} + +EASEDEF float EaseBackOut(float t, float b, float c, float d) // Ease: Back Out +{ + float s = 1.70158f; + t = t/d - 1.0f; + return (c*(t*t*((s + 1.0f)*t + s) + 1.0f) + b); +} + +EASEDEF float EaseBackInOut(float t, float b, float c, float d) // Ease: Back In Out +{ + float s = 1.70158f; + if ((t/=d/2.0f) < 1.0f) + { + s *= 1.525f; + return (c/2.0f*(t*t*((s + 1.0f)*t - s)) + b); + } + + float postFix = t-=2.0f; + s *= 1.525f; + return (c/2.0f*((postFix)*t*((s + 1.0f)*t + s) + 2.0f) + b); +} + +// Bounce Easing functions +EASEDEF float EaseBounceOut(float t, float b, float c, float d) // Ease: Bounce Out +{ + if ((t/=d) < (1.0f/2.75f)) + { + return (c*(7.5625f*t*t) + b); + } + else if (t < (2.0f/2.75f)) + { + float postFix = t-=(1.5f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.75f) + b); + } + else if (t < (2.5/2.75)) + { + float postFix = t-=(2.25f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.9375f) + b); + } + else + { + float postFix = t-=(2.625f/2.75f); + return (c*(7.5625f*(postFix)*t + 0.984375f) + b); + } +} + +EASEDEF float EaseBounceIn(float t, float b, float c, float d) { return (c - EaseBounceOut(d - t, 0.0f, c, d) + b); } // Ease: Bounce In +EASEDEF float EaseBounceInOut(float t, float b, float c, float d) // Ease: Bounce In Out +{ + if (t < d/2.0f) return (EaseBounceIn(t*2.0f, 0.0f, c, d)*0.5f + b); + else return (EaseBounceOut(t*2.0f - d, 0.0f, c, d)*0.5f + c*0.5f + b); +} + +// Elastic Easing functions +EASEDEF float EaseElasticIn(float t, float b, float c, float d) // Ease: Elastic In +{ + if (t == 0.0f) return b; + if ((t/=d) == 1.0f) return (b + c); + + float p = d*0.3f; + float a = c; + float s = p/4.0f; + float postFix = a*powf(2.0f, 10.0f*(t-=1.0f)); + + return (-(postFix*sinf((t*d-s)*(2.0f*PI)/p )) + b); +} + +EASEDEF float EaseElasticOut(float t, float b, float c, float d) // Ease: Elastic Out +{ + if (t == 0.0f) return b; + if ((t/=d) == 1.0f) return (b + c); + + float p = d*0.3f; + float a = c; + float s = p/4.0f; + + return (a*powf(2.0f,-10.0f*t)*sinf((t*d-s)*(2.0f*PI)/p) + c + b); +} + +EASEDEF float EaseElasticInOut(float t, float b, float c, float d) // Ease: Elastic In Out +{ + if (t == 0.0f) return b; + if ((t/=d/2.0f) == 2.0f) return (b + c); + + float p = d*(0.3f*1.5f); + float a = c; + float s = p/4.0f; + + if (t < 1.0f) + { + float postFix = a*powf(2.0f, 10.0f*(t-=1.0f)); + return -0.5f*(postFix*sinf((t*d-s)*(2.0f*PI)/p)) + b; + } + + float postFix = a*powf(2.0f, -10.0f*(t-=1.0f)); + + return (postFix*sinf((t*d-s)*(2.0f*PI)/p)*0.5f + c + b); +} + +#if defined(__cplusplus) +} +#endif + +#endif // REASINGS_H diff --git a/res/mdl/ak74.glb b/res/mdl/ak74.glb Binary files differnew file mode 100755 index 0000000..d1d7d1c --- /dev/null +++ b/res/mdl/ak74.glb diff --git a/res/mdl/m16a2.glb b/res/mdl/m16a2.glb Binary files differnew file mode 100755 index 0000000..baa680d --- /dev/null +++ b/res/mdl/m16a2.glb diff --git a/res/mdl/mega-gun-pack/AK47/AK.png b/res/mdl/mega-gun-pack/AK47/AK.png Binary files differnew file mode 100644 index 0000000..e4a1713 --- /dev/null +++ b/res/mdl/mega-gun-pack/AK47/AK.png diff --git a/res/mdl/mega-gun-pack/AK47/ak_fbx.fbx b/res/mdl/mega-gun-pack/AK47/ak_fbx.fbx Binary files differnew file mode 100644 index 0000000..b793949 --- /dev/null +++ b/res/mdl/mega-gun-pack/AK47/ak_fbx.fbx diff --git a/res/mdl/mega-gun-pack/AK47/ak_glb.glb b/res/mdl/mega-gun-pack/AK47/ak_glb.glb Binary files differnew file mode 100644 index 0000000..daeda33 --- /dev/null +++ b/res/mdl/mega-gun-pack/AK47/ak_glb.glb diff --git a/res/mdl/mega-gun-pack/AUG/AUG.blend b/res/mdl/mega-gun-pack/AUG/AUG.blend Binary files differnew file mode 100644 index 0000000..253f160 --- /dev/null +++ b/res/mdl/mega-gun-pack/AUG/AUG.blend diff --git a/res/mdl/mega-gun-pack/AUG/AUG.blend1 b/res/mdl/mega-gun-pack/AUG/AUG.blend1 Binary files differnew file mode 100644 index 0000000..de85af6 --- /dev/null +++ b/res/mdl/mega-gun-pack/AUG/AUG.blend1 diff --git a/res/mdl/mega-gun-pack/AUG/AUG.fbx b/res/mdl/mega-gun-pack/AUG/AUG.fbx Binary files differnew file mode 100644 index 0000000..cfa213f --- /dev/null +++ b/res/mdl/mega-gun-pack/AUG/AUG.fbx diff --git a/res/mdl/mega-gun-pack/AUG/AUG.glb b/res/mdl/mega-gun-pack/AUG/AUG.glb Binary files differnew file mode 100644 index 0000000..22e2ce0 --- /dev/null +++ b/res/mdl/mega-gun-pack/AUG/AUG.glb diff --git a/res/mdl/mega-gun-pack/AUG/AUG.png b/res/mdl/mega-gun-pack/AUG/AUG.png Binary files differnew file mode 100644 index 0000000..5c5c6fc --- /dev/null +++ b/res/mdl/mega-gun-pack/AUG/AUG.png diff --git a/res/mdl/mega-gun-pack/AUG/AUG_Psd.psd b/res/mdl/mega-gun-pack/AUG/AUG_Psd.psd Binary files differnew file mode 100644 index 0000000..cf63a71 --- /dev/null +++ b/res/mdl/mega-gun-pack/AUG/AUG_Psd.psd diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo.blend b/res/mdl/mega-gun-pack/Ammo/Ammo.blend Binary files differnew file mode 100644 index 0000000..9dfa782 --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo.blend diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo.blend1 b/res/mdl/mega-gun-pack/Ammo/Ammo.blend1 Binary files differnew file mode 100644 index 0000000..87c095a --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo.blend1 diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_Box.png b/res/mdl/mega-gun-pack/Ammo/Ammo_Box.png Binary files differnew file mode 100644 index 0000000..0c1a1ee --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo_Box.png diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbx b/res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbx Binary files differnew file mode 100644 index 0000000..8d74d84 --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbx diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_glb.glb b/res/mdl/mega-gun-pack/Ammo/Ammo_glb.glb Binary files differnew file mode 100644 index 0000000..0ad3cb9 --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo_glb.glb diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_psd.psd b/res/mdl/mega-gun-pack/Ammo/Ammo_psd.psd Binary files differnew file mode 100644 index 0000000..72297ae --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo_psd.psd diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_tex.png b/res/mdl/mega-gun-pack/Ammo/Ammo_tex.png Binary files differnew file mode 100644 index 0000000..ea6c557 --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/Ammo_tex.png diff --git a/res/mdl/mega-gun-pack/Ammo/ammo_box.fbx b/res/mdl/mega-gun-pack/Ammo/ammo_box.fbx Binary files differnew file mode 100644 index 0000000..3b603f9 --- /dev/null +++ b/res/mdl/mega-gun-pack/Ammo/ammo_box.fbx diff --git a/res/mdl/mega-gun-pack/Attachments/Attachments_1.png b/res/mdl/mega-gun-pack/Attachments/Attachments_1.png Binary files differnew file mode 100644 index 0000000..a142a83 --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/Attachments_1.png diff --git a/res/mdl/mega-gun-pack/Attachments/Attachments_1.psd b/res/mdl/mega-gun-pack/Attachments/Attachments_1.psd Binary files differnew file mode 100644 index 0000000..cff0caf --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/Attachments_1.psd diff --git a/res/mdl/mega-gun-pack/Attachments/Screenshot.png b/res/mdl/mega-gun-pack/Attachments/Screenshot.png Binary files differnew file mode 100644 index 0000000..ced46c6 --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/Screenshot.png diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.blend b/res/mdl/mega-gun-pack/Attachments/attachments.blend Binary files differnew file mode 100644 index 0000000..287b5a0 --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/attachments.blend diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.blend1 b/res/mdl/mega-gun-pack/Attachments/attachments.blend1 Binary files differnew file mode 100644 index 0000000..cd97f95 --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/attachments.blend1 diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.fbx b/res/mdl/mega-gun-pack/Attachments/attachments.fbx Binary files differnew file mode 100644 index 0000000..495f0bd --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/attachments.fbx diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.glb b/res/mdl/mega-gun-pack/Attachments/attachments.glb Binary files differnew file mode 100644 index 0000000..1794c06 --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/attachments.glb diff --git a/res/mdl/mega-gun-pack/Attachments/attachments1.blend b/res/mdl/mega-gun-pack/Attachments/attachments1.blend Binary files differnew file mode 100644 index 0000000..d127464 --- /dev/null +++ b/res/mdl/mega-gun-pack/Attachments/attachments1.blend diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend Binary files differnew file mode 100644 index 0000000..a483308 --- /dev/null +++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1 b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1 Binary files differnew file mode 100644 index 0000000..6811e8c --- /dev/null +++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1 diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbx b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbx Binary files differnew file mode 100644 index 0000000..03614a8 --- /dev/null +++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbx diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glb b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glb Binary files differnew file mode 100644 index 0000000..00372b2 --- /dev/null +++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glb diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.png b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.png Binary files differnew file mode 100644 index 0000000..da775ae --- /dev/null +++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.png diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psd b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psd Binary files differnew file mode 100644 index 0000000..7c646b8 --- /dev/null +++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psd diff --git a/res/mdl/mega-gun-pack/G36/g36.blend b/res/mdl/mega-gun-pack/G36/g36.blend Binary files differnew file mode 100644 index 0000000..4287841 --- /dev/null +++ b/res/mdl/mega-gun-pack/G36/g36.blend diff --git a/res/mdl/mega-gun-pack/G36/g36.blend1 b/res/mdl/mega-gun-pack/G36/g36.blend1 Binary files differnew file mode 100644 index 0000000..be75f45 --- /dev/null +++ b/res/mdl/mega-gun-pack/G36/g36.blend1 diff --git a/res/mdl/mega-gun-pack/G36/g36.fbx b/res/mdl/mega-gun-pack/G36/g36.fbx Binary files differnew file mode 100644 index 0000000..ed98201 --- /dev/null +++ b/res/mdl/mega-gun-pack/G36/g36.fbx diff --git a/res/mdl/mega-gun-pack/G36/g36.glb b/res/mdl/mega-gun-pack/G36/g36.glb Binary files differnew file mode 100644 index 0000000..67e6683 --- /dev/null +++ b/res/mdl/mega-gun-pack/G36/g36.glb diff --git a/res/mdl/mega-gun-pack/G36/g36.png b/res/mdl/mega-gun-pack/G36/g36.png Binary files differnew file mode 100644 index 0000000..3771227 --- /dev/null +++ b/res/mdl/mega-gun-pack/G36/g36.png diff --git a/res/mdl/mega-gun-pack/G36/g36.psd b/res/mdl/mega-gun-pack/G36/g36.psd Binary files differnew file mode 100644 index 0000000..1816a6f --- /dev/null +++ b/res/mdl/mega-gun-pack/G36/g36.psd diff --git a/res/mdl/mega-gun-pack/Glock/Glock.blend b/res/mdl/mega-gun-pack/Glock/Glock.blend Binary files differnew file mode 100644 index 0000000..5a2dd65 --- /dev/null +++ b/res/mdl/mega-gun-pack/Glock/Glock.blend diff --git a/res/mdl/mega-gun-pack/Glock/Glock.blend1 b/res/mdl/mega-gun-pack/Glock/Glock.blend1 Binary files differnew file mode 100644 index 0000000..ba94af1 --- /dev/null +++ b/res/mdl/mega-gun-pack/Glock/Glock.blend1 diff --git a/res/mdl/mega-gun-pack/Glock/Glock.png b/res/mdl/mega-gun-pack/Glock/Glock.png Binary files differnew file mode 100644 index 0000000..860bc00 --- /dev/null +++ b/res/mdl/mega-gun-pack/Glock/Glock.png diff --git a/res/mdl/mega-gun-pack/Glock/Glock_PSD.psd b/res/mdl/mega-gun-pack/Glock/Glock_PSD.psd Binary files differnew file mode 100644 index 0000000..71b171e --- /dev/null +++ b/res/mdl/mega-gun-pack/Glock/Glock_PSD.psd diff --git a/res/mdl/mega-gun-pack/Glock/Glock_fbx.fbx b/res/mdl/mega-gun-pack/Glock/Glock_fbx.fbx Binary files differnew file mode 100644 index 0000000..0314650 --- /dev/null +++ b/res/mdl/mega-gun-pack/Glock/Glock_fbx.fbx diff --git a/res/mdl/mega-gun-pack/Glock/Glock_glb.glb b/res/mdl/mega-gun-pack/Glock/Glock_glb.glb Binary files differnew file mode 100644 index 0000000..326776e --- /dev/null +++ b/res/mdl/mega-gun-pack/Glock/Glock_glb.glb diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.blend b/res/mdl/mega-gun-pack/Grenades/Grenades.blend Binary files differnew file mode 100644 index 0000000..c63172a --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/Grenades.blend diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.blend1 b/res/mdl/mega-gun-pack/Grenades/Grenades.blend1 Binary files differnew file mode 100644 index 0000000..3ed0852 --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/Grenades.blend1 diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.fbx b/res/mdl/mega-gun-pack/Grenades/Grenades.fbx Binary files differnew file mode 100644 index 0000000..b18fe94 --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/Grenades.fbx diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.glb b/res/mdl/mega-gun-pack/Grenades/Grenades.glb Binary files differnew file mode 100644 index 0000000..ebb30dd --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/Grenades.glb diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.png b/res/mdl/mega-gun-pack/Grenades/Grenades.png Binary files differnew file mode 100644 index 0000000..b8a0bbb --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/Grenades.png diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.psd b/res/mdl/mega-gun-pack/Grenades/Grenades.psd Binary files differnew file mode 100644 index 0000000..87a71d2 --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/Grenades.psd diff --git a/res/mdl/mega-gun-pack/Grenades/screenshot.png b/res/mdl/mega-gun-pack/Grenades/screenshot.png Binary files differnew file mode 100644 index 0000000..18f2188 --- /dev/null +++ b/res/mdl/mega-gun-pack/Grenades/screenshot.png diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.blend b/res/mdl/mega-gun-pack/Lugar/Lugar.blend Binary files differnew file mode 100644 index 0000000..7bef264 --- /dev/null +++ b/res/mdl/mega-gun-pack/Lugar/Lugar.blend diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.blend1 b/res/mdl/mega-gun-pack/Lugar/Lugar.blend1 Binary files differnew file mode 100644 index 0000000..4ebf5d8 --- /dev/null +++ b/res/mdl/mega-gun-pack/Lugar/Lugar.blend1 diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.fbx b/res/mdl/mega-gun-pack/Lugar/Lugar.fbx Binary files differnew file mode 100644 index 0000000..4cec6d8 --- /dev/null +++ b/res/mdl/mega-gun-pack/Lugar/Lugar.fbx diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.glb b/res/mdl/mega-gun-pack/Lugar/Lugar.glb Binary files differnew file mode 100644 index 0000000..f8c3ca9 --- /dev/null +++ b/res/mdl/mega-gun-pack/Lugar/Lugar.glb diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psd b/res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psd Binary files differnew file mode 100644 index 0000000..ea9b19e --- /dev/null +++ b/res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psd diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar_tex.png b/res/mdl/mega-gun-pack/Lugar/Lugar_tex.png Binary files differnew file mode 100644 index 0000000..7232b99 --- /dev/null +++ b/res/mdl/mega-gun-pack/Lugar/Lugar_tex.png diff --git a/res/mdl/mega-gun-pack/M16/M16.blend b/res/mdl/mega-gun-pack/M16/M16.blend Binary files differnew file mode 100644 index 0000000..ff35d7f --- /dev/null +++ b/res/mdl/mega-gun-pack/M16/M16.blend diff --git a/res/mdl/mega-gun-pack/M16/M16.blend1 b/res/mdl/mega-gun-pack/M16/M16.blend1 Binary files differnew file mode 100644 index 0000000..dbfe184 --- /dev/null +++ b/res/mdl/mega-gun-pack/M16/M16.blend1 diff --git a/res/mdl/mega-gun-pack/M16/M16_fbx.fbx b/res/mdl/mega-gun-pack/M16/M16_fbx.fbx Binary files differnew file mode 100644 index 0000000..bce5abf --- /dev/null +++ b/res/mdl/mega-gun-pack/M16/M16_fbx.fbx diff --git a/res/mdl/mega-gun-pack/M16/M16_glb.glb b/res/mdl/mega-gun-pack/M16/M16_glb.glb Binary files differnew file mode 100644 index 0000000..1ff9916 --- /dev/null +++ b/res/mdl/mega-gun-pack/M16/M16_glb.glb diff --git a/res/mdl/mega-gun-pack/M16/m16_psd.psd b/res/mdl/mega-gun-pack/M16/m16_psd.psd Binary files differnew file mode 100644 index 0000000..5a13df2 --- /dev/null +++ b/res/mdl/mega-gun-pack/M16/m16_psd.psd diff --git a/res/mdl/mega-gun-pack/M16/m16_tex.png b/res/mdl/mega-gun-pack/M16/m16_tex.png Binary files differnew file mode 100644 index 0000000..30450ff --- /dev/null +++ b/res/mdl/mega-gun-pack/M16/m16_tex.png diff --git a/res/mdl/mega-gun-pack/MP5/MP5k.blend b/res/mdl/mega-gun-pack/MP5/MP5k.blend Binary files differnew file mode 100644 index 0000000..e3daf82 --- /dev/null +++ b/res/mdl/mega-gun-pack/MP5/MP5k.blend diff --git a/res/mdl/mega-gun-pack/MP5/MP5k.blend1 b/res/mdl/mega-gun-pack/MP5/MP5k.blend1 Binary files differnew file mode 100644 index 0000000..45149a5 --- /dev/null +++ b/res/mdl/mega-gun-pack/MP5/MP5k.blend1 diff --git a/res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbx b/res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbx Binary files differnew file mode 100644 index 0000000..40404b8 --- /dev/null +++ b/res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbx diff --git a/res/mdl/mega-gun-pack/MP5/MP5k_glb.glb b/res/mdl/mega-gun-pack/MP5/MP5k_glb.glb Binary files differnew file mode 100644 index 0000000..236ea03 --- /dev/null +++ b/res/mdl/mega-gun-pack/MP5/MP5k_glb.glb diff --git a/res/mdl/mega-gun-pack/MP5/mp5_psd.psd b/res/mdl/mega-gun-pack/MP5/mp5_psd.psd Binary files differnew file mode 100644 index 0000000..e12220d --- /dev/null +++ b/res/mdl/mega-gun-pack/MP5/mp5_psd.psd diff --git a/res/mdl/mega-gun-pack/MP5/mp5_tex.png b/res/mdl/mega-gun-pack/MP5/mp5_tex.png Binary files differnew file mode 100644 index 0000000..f91224d --- /dev/null +++ b/res/mdl/mega-gun-pack/MP5/mp5_tex.png diff --git a/res/mdl/mega-gun-pack/PKM/PKM.blend b/res/mdl/mega-gun-pack/PKM/PKM.blend Binary files differnew file mode 100644 index 0000000..f91c645 --- /dev/null +++ b/res/mdl/mega-gun-pack/PKM/PKM.blend diff --git a/res/mdl/mega-gun-pack/PKM/PKM.blend1 b/res/mdl/mega-gun-pack/PKM/PKM.blend1 Binary files differnew file mode 100644 index 0000000..f6d5bc8 --- /dev/null +++ b/res/mdl/mega-gun-pack/PKM/PKM.blend1 diff --git a/res/mdl/mega-gun-pack/PKM/PKM.fbx b/res/mdl/mega-gun-pack/PKM/PKM.fbx Binary files differnew file mode 100644 index 0000000..79db9ce --- /dev/null +++ b/res/mdl/mega-gun-pack/PKM/PKM.fbx diff --git a/res/mdl/mega-gun-pack/PKM/PKM.glb b/res/mdl/mega-gun-pack/PKM/PKM.glb Binary files differnew file mode 100644 index 0000000..42fe954 --- /dev/null +++ b/res/mdl/mega-gun-pack/PKM/PKM.glb diff --git a/res/mdl/mega-gun-pack/PKM/pkm_psd.psd b/res/mdl/mega-gun-pack/PKM/pkm_psd.psd Binary files differnew file mode 100644 index 0000000..e55d455 --- /dev/null +++ b/res/mdl/mega-gun-pack/PKM/pkm_psd.psd diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.blend b/res/mdl/mega-gun-pack/PPSH/PPSH.blend Binary files differnew file mode 100644 index 0000000..e364e92 --- /dev/null +++ b/res/mdl/mega-gun-pack/PPSH/PPSH.blend diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.blend1 b/res/mdl/mega-gun-pack/PPSH/PPSH.blend1 Binary files differnew file mode 100644 index 0000000..75883e8 --- /dev/null +++ b/res/mdl/mega-gun-pack/PPSH/PPSH.blend1 diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.fbx b/res/mdl/mega-gun-pack/PPSH/PPSH.fbx Binary files differnew file mode 100644 index 0000000..be3732b --- /dev/null +++ b/res/mdl/mega-gun-pack/PPSH/PPSH.fbx diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.glb b/res/mdl/mega-gun-pack/PPSH/PPSH.glb Binary files differnew file mode 100644 index 0000000..2f15238 --- /dev/null +++ b/res/mdl/mega-gun-pack/PPSH/PPSH.glb diff --git a/res/mdl/mega-gun-pack/PPSH/ppsh.png b/res/mdl/mega-gun-pack/PPSH/ppsh.png Binary files differnew file mode 100644 index 0000000..0922a16 --- /dev/null +++ b/res/mdl/mega-gun-pack/PPSH/ppsh.png diff --git a/res/mdl/mega-gun-pack/PPSH/ppsh_psd.psd b/res/mdl/mega-gun-pack/PPSH/ppsh_psd.psd Binary files differnew file mode 100644 index 0000000..7251fdf --- /dev/null +++ b/res/mdl/mega-gun-pack/PPSH/ppsh_psd.psd diff --git a/res/mdl/mega-gun-pack/RPG/RPG.blend b/res/mdl/mega-gun-pack/RPG/RPG.blend Binary files differnew file mode 100644 index 0000000..4413e67 --- /dev/null +++ b/res/mdl/mega-gun-pack/RPG/RPG.blend diff --git a/res/mdl/mega-gun-pack/RPG/RPG.blend1 b/res/mdl/mega-gun-pack/RPG/RPG.blend1 Binary files differnew file mode 100644 index 0000000..ca34267 --- /dev/null +++ b/res/mdl/mega-gun-pack/RPG/RPG.blend1 diff --git a/res/mdl/mega-gun-pack/RPG/RPG.fbx b/res/mdl/mega-gun-pack/RPG/RPG.fbx Binary files differnew file mode 100644 index 0000000..90fca8c --- /dev/null +++ b/res/mdl/mega-gun-pack/RPG/RPG.fbx diff --git a/res/mdl/mega-gun-pack/RPG/RPG.glb b/res/mdl/mega-gun-pack/RPG/RPG.glb Binary files differnew file mode 100644 index 0000000..7e7c85f --- /dev/null +++ b/res/mdl/mega-gun-pack/RPG/RPG.glb diff --git a/res/mdl/mega-gun-pack/RPG/rpg.png b/res/mdl/mega-gun-pack/RPG/rpg.png Binary files differnew file mode 100644 index 0000000..4724799 --- /dev/null +++ b/res/mdl/mega-gun-pack/RPG/rpg.png diff --git a/res/mdl/mega-gun-pack/RPG/rpg_psd.psd b/res/mdl/mega-gun-pack/RPG/rpg_psd.psd Binary files differnew file mode 100644 index 0000000..a0c6409 --- /dev/null +++ b/res/mdl/mega-gun-pack/RPG/rpg_psd.psd diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver.blend b/res/mdl/mega-gun-pack/Revolver/Revolver.blend Binary files differnew file mode 100644 index 0000000..5b9cf51 --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver.blend diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver.blend1 b/res/mdl/mega-gun-pack/Revolver/Revolver.blend1 Binary files differnew file mode 100644 index 0000000..3be7919 --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver.blend1 diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver.png b/res/mdl/mega-gun-pack/Revolver/Revolver.png Binary files differnew file mode 100644 index 0000000..2b042ca --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver.png diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbx b/res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbx Binary files differnew file mode 100644 index 0000000..fb3dc43 --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbx diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_glb.glb b/res/mdl/mega-gun-pack/Revolver/Revolver_glb.glb Binary files differnew file mode 100644 index 0000000..f7ef1b8 --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver_glb.glb diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_psd.psd b/res/mdl/mega-gun-pack/Revolver/Revolver_psd.psd Binary files differnew file mode 100644 index 0000000..ff553a0 --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver_psd.psd diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.png b/res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.png Binary files differnew file mode 100644 index 0000000..f5f65b8 --- /dev/null +++ b/res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.png diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.blend b/res/mdl/mega-gun-pack/Rifle/Rifle.blend Binary files differnew file mode 100644 index 0000000..9f25d9a --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/Rifle.blend diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.blend1 b/res/mdl/mega-gun-pack/Rifle/Rifle.blend1 Binary files differnew file mode 100644 index 0000000..6301baa --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/Rifle.blend1 diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.fbx b/res/mdl/mega-gun-pack/Rifle/Rifle.fbx Binary files differnew file mode 100644 index 0000000..3a83012 --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/Rifle.fbx diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.glb b/res/mdl/mega-gun-pack/Rifle/Rifle.glb Binary files differnew file mode 100644 index 0000000..7ec08b6 --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/Rifle.glb diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.png b/res/mdl/mega-gun-pack/Rifle/Rifle.png Binary files differnew file mode 100644 index 0000000..fab3dfe --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/Rifle.png diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psd b/res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psd Binary files differnew file mode 100644 index 0000000..aae5450 --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psd diff --git a/res/mdl/mega-gun-pack/Rifle/scope_fbx.fbx b/res/mdl/mega-gun-pack/Rifle/scope_fbx.fbx Binary files differnew file mode 100644 index 0000000..1ee1735 --- /dev/null +++ b/res/mdl/mega-gun-pack/Rifle/scope_fbx.fbx diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR.blend b/res/mdl/mega-gun-pack/SCAR/SCAR.blend Binary files differnew file mode 100644 index 0000000..a169333 --- /dev/null +++ b/res/mdl/mega-gun-pack/SCAR/SCAR.blend diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR.blend1 b/res/mdl/mega-gun-pack/SCAR/SCAR.blend1 Binary files differnew file mode 100644 index 0000000..c672d12 --- /dev/null +++ b/res/mdl/mega-gun-pack/SCAR/SCAR.blend1 diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR.png b/res/mdl/mega-gun-pack/SCAR/SCAR.png Binary files differnew file mode 100644 index 0000000..194f379 --- /dev/null +++ b/res/mdl/mega-gun-pack/SCAR/SCAR.png diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psd b/res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psd Binary files differnew file mode 100644 index 0000000..55888e6 --- /dev/null +++ b/res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psd diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbx b/res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbx Binary files differnew file mode 100644 index 0000000..653c849 --- /dev/null +++ b/res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbx diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR_glb.glb b/res/mdl/mega-gun-pack/SCAR/SCAR_glb.glb Binary files differnew file mode 100644 index 0000000..36be5b1 --- /dev/null +++ b/res/mdl/mega-gun-pack/SCAR/SCAR_glb.glb diff --git a/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend Binary files differnew file mode 100644 index 0000000..6f0df84 --- /dev/null +++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend diff --git a/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend1 b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend1 Binary files differnew file mode 100644 index 0000000..0858a8f --- /dev/null +++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend1 diff --git a/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_fbx.fbx b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_fbx.fbx Binary files differnew file mode 100644 index 0000000..5f7215b --- /dev/null +++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_fbx.fbx diff --git a/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_glb.glb b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_glb.glb Binary files differnew file mode 100644 index 0000000..2de4f64 --- /dev/null +++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_glb.glb diff --git a/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun.psd b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun.psd Binary files differnew file mode 100644 index 0000000..a2aedae --- /dev/null +++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun.psd diff --git a/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun_tex.png b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun_tex.png Binary files differnew file mode 100644 index 0000000..450f084 --- /dev/null +++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun_tex.png diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend Binary files differnew file mode 100644 index 0000000..df0274c --- /dev/null +++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1 b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1 Binary files differnew file mode 100644 index 0000000..2aee3fc --- /dev/null +++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1 diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.fbx b/res/mdl/mega-gun-pack/Skorpion/Skorpion.fbx Binary files differnew file mode 100644 index 0000000..a98905f --- /dev/null +++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.fbx diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.glb b/res/mdl/mega-gun-pack/Skorpion/Skorpion.glb Binary files differnew file mode 100644 index 0000000..2a0d883 --- /dev/null +++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.glb diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.png b/res/mdl/mega-gun-pack/Skorpion/Skorpion.png Binary files differnew file mode 100644 index 0000000..2f9e1f3 --- /dev/null +++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.png diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.psd b/res/mdl/mega-gun-pack/Skorpion/Skorpion.psd Binary files differnew file mode 100644 index 0000000..9240195 --- /dev/null +++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.psd diff --git a/res/mdl/mega-gun-pack/Spas/Spas.blend b/res/mdl/mega-gun-pack/Spas/Spas.blend Binary files differnew file mode 100644 index 0000000..46fc9bf --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas.blend diff --git a/res/mdl/mega-gun-pack/Spas/Spas.blend1 b/res/mdl/mega-gun-pack/Spas/Spas.blend1 Binary files differnew file mode 100644 index 0000000..648d053 --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas.blend1 diff --git a/res/mdl/mega-gun-pack/Spas/Spas.fbx b/res/mdl/mega-gun-pack/Spas/Spas.fbx Binary files differnew file mode 100644 index 0000000..1674c4e --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas.fbx diff --git a/res/mdl/mega-gun-pack/Spas/Spas_PSD.psd b/res/mdl/mega-gun-pack/Spas/Spas_PSD.psd Binary files differnew file mode 100644 index 0000000..3a0c0ee --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas_PSD.psd diff --git a/res/mdl/mega-gun-pack/Spas/Spas_fbx.fbx b/res/mdl/mega-gun-pack/Spas/Spas_fbx.fbx Binary files differnew file mode 100644 index 0000000..4db7c4b --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas_fbx.fbx diff --git a/res/mdl/mega-gun-pack/Spas/Spas_glb.glb b/res/mdl/mega-gun-pack/Spas/Spas_glb.glb Binary files differnew file mode 100644 index 0000000..c1eb678 --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas_glb.glb diff --git a/res/mdl/mega-gun-pack/Spas/Spas_tex.png b/res/mdl/mega-gun-pack/Spas/Spas_tex.png Binary files differnew file mode 100644 index 0000000..4291349 --- /dev/null +++ b/res/mdl/mega-gun-pack/Spas/Spas_tex.png diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi.blend b/res/mdl/mega-gun-pack/Uzi/Uzi.blend Binary files differnew file mode 100644 index 0000000..e796fd6 --- /dev/null +++ b/res/mdl/mega-gun-pack/Uzi/Uzi.blend diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi.blend1 b/res/mdl/mega-gun-pack/Uzi/Uzi.blend1 Binary files differnew file mode 100644 index 0000000..574b9cf --- /dev/null +++ b/res/mdl/mega-gun-pack/Uzi/Uzi.blend1 diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psd b/res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psd Binary files differnew file mode 100644 index 0000000..1114563 --- /dev/null +++ b/res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psd diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_Tex.png b/res/mdl/mega-gun-pack/Uzi/Uzi_Tex.png Binary files differnew file mode 100644 index 0000000..db6bd3b --- /dev/null +++ b/res/mdl/mega-gun-pack/Uzi/Uzi_Tex.png diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbx b/res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbx Binary files differnew file mode 100644 index 0000000..ce7fb39 --- /dev/null +++ b/res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbx diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_glb.glb b/res/mdl/mega-gun-pack/Uzi/Uzi_glb.glb Binary files differnew file mode 100644 index 0000000..8a4fa06 --- /dev/null +++ b/res/mdl/mega-gun-pack/Uzi/Uzi_glb.glb diff --git a/res/mdl/mega-gun-pack/p90/P90.png b/res/mdl/mega-gun-pack/p90/P90.png Binary files differnew file mode 100644 index 0000000..b971c42 --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/P90.png diff --git a/res/mdl/mega-gun-pack/p90/P902.png b/res/mdl/mega-gun-pack/p90/P902.png Binary files differnew file mode 100644 index 0000000..289c4ea --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/P902.png diff --git a/res/mdl/mega-gun-pack/p90/P90_psd.psd b/res/mdl/mega-gun-pack/p90/P90_psd.psd Binary files differnew file mode 100644 index 0000000..729f11b --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/P90_psd.psd diff --git a/res/mdl/mega-gun-pack/p90/P90_uv.png b/res/mdl/mega-gun-pack/p90/P90_uv.png Binary files differnew file mode 100644 index 0000000..98ec6ba --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/P90_uv.png diff --git a/res/mdl/mega-gun-pack/p90/p90.blend b/res/mdl/mega-gun-pack/p90/p90.blend Binary files differnew file mode 100644 index 0000000..836ed47 --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/p90.blend diff --git a/res/mdl/mega-gun-pack/p90/p90.blend1 b/res/mdl/mega-gun-pack/p90/p90.blend1 Binary files differnew file mode 100644 index 0000000..6512750 --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/p90.blend1 diff --git a/res/mdl/mega-gun-pack/p90/p90_fbx.fbx b/res/mdl/mega-gun-pack/p90/p90_fbx.fbx Binary files differnew file mode 100644 index 0000000..fe9298d --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/p90_fbx.fbx diff --git a/res/mdl/mega-gun-pack/p90/p90_glb.glb b/res/mdl/mega-gun-pack/p90/p90_glb.glb Binary files differnew file mode 100644 index 0000000..b2f2503 --- /dev/null +++ b/res/mdl/mega-gun-pack/p90/p90_glb.glb diff --git a/res/mdl/pkm.glb b/res/mdl/pkm.glb Binary files differnew file mode 100755 index 0000000..b76babf --- /dev/null +++ b/res/mdl/pkm.glb diff --git a/res/mdl/rpg7.glb b/res/mdl/rpg7.glb Binary files differnew file mode 100755 index 0000000..bb54370 --- /dev/null +++ b/res/mdl/rpg7.glb diff --git a/res/mdl/soldier.glb b/res/mdl/soldier.glb Binary files differnew file mode 100644 index 0000000..375655e --- /dev/null +++ b/res/mdl/soldier.glb diff --git a/res/mdl/t55_tank.glb b/res/mdl/t55_tank.glb Binary files differnew file mode 100755 index 0000000..50836da --- /dev/null +++ b/res/mdl/t55_tank.glb diff --git a/res/mdl/tank.glb b/res/mdl/tank.glb Binary files differnew file mode 100755 index 0000000..9e25003 --- /dev/null +++ b/res/mdl/tank.glb diff --git a/res/mdl/tree-pack/tree01.glb b/res/mdl/tree-pack/tree01.glb Binary files differnew file mode 100644 index 0000000..4663b98 --- /dev/null +++ b/res/mdl/tree-pack/tree01.glb diff --git a/res/mdl/tree-pack/tree02.glb b/res/mdl/tree-pack/tree02.glb Binary files differnew file mode 100644 index 0000000..0652bfb --- /dev/null +++ b/res/mdl/tree-pack/tree02.glb diff --git a/res/save/scene.fgs b/res/save/scene.fgs Binary files differindex 191cfe7..23f4927 100644 --- a/res/save/scene.fgs +++ b/res/save/scene.fgs @@ -772,8 +772,8 @@ read_physics_joint(FILE *fp, PhysicsJoint *joint, u32 body_cnt) { !read_u32_le(fp, &joint->flags) || !read_vec3(fp, &joint->anchor_a) || !read_vec3(fp, &joint->anchor_b) || - !read_vec3(fp, &joint->axis_a) || - !read_vec3(fp, &joint->axis_b) || + !read_quat(fp, &joint->frame_rot_a) || + !read_quat(fp, &joint->frame_rot_b) || !read_f32_le(fp, &joint->lower) || !read_f32_le(fp, &joint->upper) || !read_f32_le(fp, &joint->spring_hz) || @@ -800,8 +800,8 @@ write_physics_joint(FILE *fp, PhysicsJoint *joint, u32 body_cnt) { write_u32_le(fp, joint->flags) && write_vec3(fp, joint->anchor_a) && write_vec3(fp, joint->anchor_b) && - write_vec3(fp, joint->axis_a) && - write_vec3(fp, joint->axis_b) && + write_quat(fp, joint->frame_rot_a) && + write_quat(fp, joint->frame_rot_b) && write_f32_le(fp, joint->lower) && write_f32_le(fp, joint->upper) && write_f32_le(fp, joint->spring_hz) && @@ -832,6 +832,17 @@ read_physics_chunk(FILE *fp, SaveLoadContext *ctx) { return false; } + u32 root_body; + if (!read_u32_le(fp, &root_body)) { + return false; + } + + if ((body_cnt && root_body >= body_cnt) || + (!body_cnt && root_body != 0)) { + return false; + } + + ent->phys.root_body = root_body; ent->phys.body_cnt = body_cnt; if (body_cnt) { @@ -902,9 +913,12 @@ write_physics_chunk(FILE *fp) { } if ((phys->body_cnt && !phys->bodies) || - (phys->joint_cnt && !phys->joints) || - !write_u64_le(fp, ent->pid) || - !write_u32_le(fp, phys->body_cnt)) { + (phys->joint_cnt && !phys->joints) || + (phys->body_cnt && phys->root_body >= phys->body_cnt) || + (!phys->body_cnt && phys->root_body != 0) || + !write_u64_le(fp, ent->pid) || + !write_u32_le(fp, phys->body_cnt) || + !write_u32_le(fp, phys->root_body)) { return false; } @@ -5,7 +5,7 @@ /* fun game save format defined in fgsf_spec.txt */ #define SAVE_VERSION_MAJOR 0 -#define SAVE_VERSION_MINOR 1 +#define SAVE_VERSION_MINOR 3 bool save_file_read(cstr path); bool save_file_write(cstr path); |
