techkaczynski.dev / code

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app.c4
-rw-r--r--app.h8
-rw-r--r--b3internalassert.c4
-rw-r--r--cam.c203
-rw-r--r--cam.h18
-rw-r--r--editor.c1538
-rw-r--r--editor.h28
-rw-r--r--ent.c609
-rw-r--r--ent.h11
-rw-r--r--env.c3
-rw-r--r--physics.h10
-rw-r--r--reasings.h265
-rwxr-xr-xres/mdl/ak74.glbbin0 -> 60996 bytes
-rwxr-xr-xres/mdl/m16a2.glbbin0 -> 42152 bytes
-rw-r--r--res/mdl/mega-gun-pack/AK47/AK.pngbin0 -> 89281 bytes
-rw-r--r--res/mdl/mega-gun-pack/AK47/ak_fbx.fbxbin0 -> 73980 bytes
-rw-r--r--res/mdl/mega-gun-pack/AK47/ak_glb.glbbin0 -> 188344 bytes
-rw-r--r--res/mdl/mega-gun-pack/AUG/AUG.blendbin0 -> 603964 bytes
-rw-r--r--res/mdl/mega-gun-pack/AUG/AUG.blend1bin0 -> 645492 bytes
-rw-r--r--res/mdl/mega-gun-pack/AUG/AUG.fbxbin0 -> 54668 bytes
-rw-r--r--res/mdl/mega-gun-pack/AUG/AUG.glbbin0 -> 116892 bytes
-rw-r--r--res/mdl/mega-gun-pack/AUG/AUG.pngbin0 -> 85068 bytes
-rw-r--r--res/mdl/mega-gun-pack/AUG/AUG_Psd.psdbin0 -> 660131 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo.blendbin0 -> 1269580 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo.blend1bin0 -> 1252060 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo_Box.pngbin0 -> 84866 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbxbin0 -> 59564 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo_glb.glbbin0 -> 219112 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo_psd.psdbin0 -> 2872205 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/Ammo_tex.pngbin0 -> 112113 bytes
-rw-r--r--res/mdl/mega-gun-pack/Ammo/ammo_box.fbxbin0 -> 18652 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/Attachments_1.pngbin0 -> 40773 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/Attachments_1.psdbin0 -> 815865 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/Screenshot.pngbin0 -> 680778 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/attachments.blendbin0 -> 647620 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/attachments.blend1bin0 -> 647620 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/attachments.fbxbin0 -> 59100 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/attachments.glbbin0 -> 76272 bytes
-rw-r--r--res/mdl/mega-gun-pack/Attachments/attachments1.blendbin0 -> 641604 bytes
-rw-r--r--res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blendbin0 -> 583408 bytes
-rw-r--r--res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1bin0 -> 583408 bytes
-rw-r--r--res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbxbin0 -> 50988 bytes
-rw-r--r--res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glbbin0 -> 103188 bytes
-rw-r--r--res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.pngbin0 -> 76066 bytes
-rw-r--r--res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psdbin0 -> 925531 bytes
-rw-r--r--res/mdl/mega-gun-pack/G36/g36.blendbin0 -> 596436 bytes
-rw-r--r--res/mdl/mega-gun-pack/G36/g36.blend1bin0 -> 638932 bytes
-rw-r--r--res/mdl/mega-gun-pack/G36/g36.fbxbin0 -> 48572 bytes
-rw-r--r--res/mdl/mega-gun-pack/G36/g36.glbbin0 -> 134740 bytes
-rw-r--r--res/mdl/mega-gun-pack/G36/g36.pngbin0 -> 101080 bytes
-rw-r--r--res/mdl/mega-gun-pack/G36/g36.psdbin0 -> 698094 bytes
-rw-r--r--res/mdl/mega-gun-pack/Glock/Glock.blendbin0 -> 1100032 bytes
-rw-r--r--res/mdl/mega-gun-pack/Glock/Glock.blend1bin0 -> 1100032 bytes
-rw-r--r--res/mdl/mega-gun-pack/Glock/Glock.pngbin0 -> 25766 bytes
-rw-r--r--res/mdl/mega-gun-pack/Glock/Glock_PSD.psdbin0 -> 219291 bytes
-rw-r--r--res/mdl/mega-gun-pack/Glock/Glock_fbx.fbxbin0 -> 41100 bytes
-rw-r--r--res/mdl/mega-gun-pack/Glock/Glock_glb.glbbin0 -> 57688 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/Grenades.blendbin0 -> 553864 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/Grenades.blend1bin0 -> 553544 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/Grenades.fbxbin0 -> 32892 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/Grenades.glbbin0 -> 109432 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/Grenades.pngbin0 -> 97560 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/Grenades.psdbin0 -> 423545 bytes
-rw-r--r--res/mdl/mega-gun-pack/Grenades/screenshot.pngbin0 -> 572171 bytes
-rw-r--r--res/mdl/mega-gun-pack/Lugar/Lugar.blendbin0 -> 1060412 bytes
-rw-r--r--res/mdl/mega-gun-pack/Lugar/Lugar.blend1bin0 -> 1043036 bytes
-rw-r--r--res/mdl/mega-gun-pack/Lugar/Lugar.fbxbin0 -> 29036 bytes
-rw-r--r--res/mdl/mega-gun-pack/Lugar/Lugar.glbbin0 -> 136036 bytes
-rw-r--r--res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psdbin0 -> 1074792 bytes
-rw-r--r--res/mdl/mega-gun-pack/Lugar/Lugar_tex.pngbin0 -> 114382 bytes
-rw-r--r--res/mdl/mega-gun-pack/M16/M16.blendbin0 -> 1003800 bytes
-rw-r--r--res/mdl/mega-gun-pack/M16/M16.blend1bin0 -> 1011544 bytes
-rw-r--r--res/mdl/mega-gun-pack/M16/M16_fbx.fbxbin0 -> 39020 bytes
-rw-r--r--res/mdl/mega-gun-pack/M16/M16_glb.glbbin0 -> 153756 bytes
-rw-r--r--res/mdl/mega-gun-pack/M16/m16_psd.psdbin0 -> 601292 bytes
-rw-r--r--res/mdl/mega-gun-pack/M16/m16_tex.pngbin0 -> 105884 bytes
-rw-r--r--res/mdl/mega-gun-pack/MP5/MP5k.blendbin0 -> 1045568 bytes
-rw-r--r--res/mdl/mega-gun-pack/MP5/MP5k.blend1bin0 -> 1037016 bytes
-rw-r--r--res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbxbin0 -> 67964 bytes
-rw-r--r--res/mdl/mega-gun-pack/MP5/MP5k_glb.glbbin0 -> 152144 bytes
-rw-r--r--res/mdl/mega-gun-pack/MP5/mp5_psd.psdbin0 -> 654889 bytes
-rw-r--r--res/mdl/mega-gun-pack/MP5/mp5_tex.pngbin0 -> 102110 bytes
-rw-r--r--res/mdl/mega-gun-pack/PKM/PKM.blendbin0 -> 745536 bytes
-rw-r--r--res/mdl/mega-gun-pack/PKM/PKM.blend1bin0 -> 786208 bytes
-rw-r--r--res/mdl/mega-gun-pack/PKM/PKM.fbxbin0 -> 99052 bytes
-rw-r--r--res/mdl/mega-gun-pack/PKM/PKM.glbbin0 -> 241596 bytes
-rw-r--r--res/mdl/mega-gun-pack/PKM/pkm_psd.psdbin0 -> 604557 bytes
-rw-r--r--res/mdl/mega-gun-pack/PPSH/PPSH.blendbin0 -> 592408 bytes
-rw-r--r--res/mdl/mega-gun-pack/PPSH/PPSH.blend1bin0 -> 592088 bytes
-rw-r--r--res/mdl/mega-gun-pack/PPSH/PPSH.fbxbin0 -> 53100 bytes
-rw-r--r--res/mdl/mega-gun-pack/PPSH/PPSH.glbbin0 -> 129768 bytes
-rw-r--r--res/mdl/mega-gun-pack/PPSH/ppsh.pngbin0 -> 96349 bytes
-rw-r--r--res/mdl/mega-gun-pack/PPSH/ppsh_psd.psdbin0 -> 422378 bytes
-rw-r--r--res/mdl/mega-gun-pack/RPG/RPG.blendbin0 -> 1486048 bytes
-rw-r--r--res/mdl/mega-gun-pack/RPG/RPG.blend1bin0 -> 1485728 bytes
-rw-r--r--res/mdl/mega-gun-pack/RPG/RPG.fbxbin0 -> 61116 bytes
-rw-r--r--res/mdl/mega-gun-pack/RPG/RPG.glbbin0 -> 158296 bytes
-rw-r--r--res/mdl/mega-gun-pack/RPG/rpg.pngbin0 -> 111780 bytes
-rw-r--r--res/mdl/mega-gun-pack/RPG/rpg_psd.psdbin0 -> 868682 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver.blendbin0 -> 614476 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver.blend1bin0 -> 655612 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver.pngbin0 -> 69094 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbxbin0 -> 54796 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver_glb.glbbin0 -> 104344 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver_psd.psdbin0 -> 414395 bytes
-rw-r--r--res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.pngbin0 -> 660184 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/Rifle.blendbin0 -> 1117128 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/Rifle.blend1bin0 -> 1131768 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/Rifle.fbxbin0 -> 131164 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/Rifle.glbbin0 -> 135676 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/Rifle.pngbin0 -> 103393 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psdbin0 -> 2915952 bytes
-rw-r--r--res/mdl/mega-gun-pack/Rifle/scope_fbx.fbxbin0 -> 21580 bytes
-rw-r--r--res/mdl/mega-gun-pack/SCAR/SCAR.blendbin0 -> 724468 bytes
-rw-r--r--res/mdl/mega-gun-pack/SCAR/SCAR.blend1bin0 -> 724388 bytes
-rw-r--r--res/mdl/mega-gun-pack/SCAR/SCAR.pngbin0 -> 99710 bytes
-rw-r--r--res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psdbin0 -> 637940 bytes
-rw-r--r--res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbxbin0 -> 60140 bytes
-rw-r--r--res/mdl/mega-gun-pack/SCAR/SCAR_glb.glbbin0 -> 143916 bytes
-rw-r--r--res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blendbin0 -> 1002896 bytes
-rw-r--r--res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend1bin0 -> 1018008 bytes
-rw-r--r--res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_fbx.fbxbin0 -> 42684 bytes
-rw-r--r--res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_glb.glbbin0 -> 118228 bytes
-rw-r--r--res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun.psdbin0 -> 895949 bytes
-rw-r--r--res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun_tex.pngbin0 -> 98741 bytes
-rw-r--r--res/mdl/mega-gun-pack/Skorpion/Skorpion.blendbin0 -> 604504 bytes
-rw-r--r--res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1bin0 -> 604184 bytes
-rw-r--r--res/mdl/mega-gun-pack/Skorpion/Skorpion.fbxbin0 -> 53532 bytes
-rw-r--r--res/mdl/mega-gun-pack/Skorpion/Skorpion.glbbin0 -> 143076 bytes
-rw-r--r--res/mdl/mega-gun-pack/Skorpion/Skorpion.pngbin0 -> 104978 bytes
-rw-r--r--res/mdl/mega-gun-pack/Skorpion/Skorpion.psdbin0 -> 432722 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas.blendbin0 -> 781400 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas.blend1bin0 -> 781400 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas.fbxbin0 -> 59308 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas_PSD.psdbin0 -> 477168 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas_fbx.fbxbin0 -> 67196 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas_glb.glbbin0 -> 138280 bytes
-rw-r--r--res/mdl/mega-gun-pack/Spas/Spas_tex.pngbin0 -> 99953 bytes
-rw-r--r--res/mdl/mega-gun-pack/Uzi/Uzi.blendbin0 -> 984092 bytes
-rw-r--r--res/mdl/mega-gun-pack/Uzi/Uzi.blend1bin0 -> 984092 bytes
-rw-r--r--res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psdbin0 -> 1020050 bytes
-rw-r--r--res/mdl/mega-gun-pack/Uzi/Uzi_Tex.pngbin0 -> 79748 bytes
-rw-r--r--res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbxbin0 -> 39356 bytes
-rw-r--r--res/mdl/mega-gun-pack/Uzi/Uzi_glb.glbbin0 -> 102432 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/P90.pngbin0 -> 88829 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/P902.pngbin0 -> 64405 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/P90_psd.psdbin0 -> 907119 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/P90_uv.pngbin0 -> 107577 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/p90.blendbin0 -> 1497612 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/p90.blend1bin0 -> 1538956 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/p90_fbx.fbxbin0 -> 58892 bytes
-rw-r--r--res/mdl/mega-gun-pack/p90/p90_glb.glbbin0 -> 77920 bytes
-rwxr-xr-xres/mdl/pkm.glbbin0 -> 60964 bytes
-rwxr-xr-xres/mdl/rpg7.glbbin0 -> 41268 bytes
-rw-r--r--res/mdl/soldier.glbbin0 -> 171872 bytes
-rwxr-xr-xres/mdl/t55_tank.glbbin0 -> 17188 bytes
-rwxr-xr-xres/mdl/tank.glbbin0 -> 13684 bytes
-rw-r--r--res/mdl/tree-pack/tree01.glbbin0 -> 25616 bytes
-rw-r--r--res/mdl/tree-pack/tree02.glbbin0 -> 33224 bytes
-rw-r--r--res/save/scene.fgsbin1552 -> 2985 bytes
-rw-r--r--save.c28
-rw-r--r--save.h2
162 files changed, 2480 insertions, 251 deletions
diff --git a/app.c b/app.c
index 18f0d9c..851b179 100644
--- a/app.c
+++ b/app.c
@@ -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);
diff --git a/app.h b/app.h
index b39ae56..73d85d4 100644
--- a/app.h
+++ b/app.h
@@ -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;
diff --git a/cam.c b/cam.c
index f14487c..4576f93 100644
--- a/cam.c
+++ b/cam.c
@@ -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);
+ }
}
diff --git a/cam.h b/cam.h
index 4b2465b..8af2614 100644
--- a/cam.h
+++ b/cam.h
@@ -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
diff --git a/editor.c b/editor.c
index 0e7fd58..1feb6ca 100644
--- a/editor.c
+++ b/editor.c
@@ -41,6 +41,7 @@ static void action_apply(EditorAction*, bool);
static void action_free(EditorAction*);
static void model_picker_open(ModelPickerMode mode);
static void model_picker_close(void);
+static void physics_pick(void);
static void
history_push(EditorAction action) {
@@ -195,6 +196,7 @@ action_begin(EditorActionTag tag, bool roots_only) {
case ED_ACTION_RENAME:
case ED_ACTION_MODEL:
+ case ED_ACTION_PHYSICS:
break;
default:
@@ -266,6 +268,10 @@ action_begin(EditorActionTag tag, bool roots_only) {
strdup(ent->mdl.key) : NULL;
break;
+ case ED_ACTION_PHYSICS:
+ before->physics = entity_physics_copy(&ent->phys);
+ break;
+
default:
TraceLog(LOG_FATAL,
"action_begin: unhandled action tag.");
@@ -293,6 +299,12 @@ action_apply(EditorAction *action, bool undo) {
ent->tr.rot = data->transform.rot;
ent->tr.scale = data->transform.scale;
}
+
+ /* second pass ensures transforms are properly updated */
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ entity_physics_transform_changed(id);
+ }
break;
case ED_ACTION_CREATE:
@@ -334,6 +346,18 @@ action_apply(EditorAction *action, bool undo) {
}
break;
+ case ED_ACTION_PHYSICS:
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action->item[i].pid);
+ Entity *ent = entity_get(id);
+
+ entity_physics_clear(&ent->phys);
+ PhysicsCmp pre = ent->phys;
+ ent->phys = action->item[i].before.physics;
+ action->item[i].before.physics = pre;
+ }
+ break;
+
default:
TraceLog(LOG_FATAL, "action with invalid tag.");
break;
@@ -365,6 +389,12 @@ action_free(EditorAction *action) {
}
break;
+ case ED_ACTION_PHYSICS:
+ for (u32 i = 0; i < action->item_cnt; ++i) {
+ entity_physics_free(&action->item[i].before.physics);
+ }
+ break;
+
default:
TraceLog(LOG_FATAL, "action_free: unhandled action tag.");
break;
@@ -396,6 +426,8 @@ action_commit(EditorAction *action) {
after->transform.rot)
|| !Vector3Equals(before->transform.scale,
after->transform.scale);
+
+ entity_physics_transform_changed(id);
}
if (!changed) {
@@ -441,6 +473,10 @@ action_commit(EditorAction *action) {
}
break;
+ case ED_ACTION_PHYSICS:
+ changed = action->item_cnt != 0;
+ break;
+
default:
TraceLog(LOG_FATAL, "action_commit: unhangled action tag.");
break;
@@ -458,6 +494,8 @@ action_commit(EditorAction *action) {
void
editor_init(void) {
// GuiLoadStyle("style_amber.rgs");
+ // GuiLoadStyle("style_candy.rgs");
+ // GuiLoadStyle("style_rltech.rgs");
Asset *fnt_asset =
asset_get("res/fnt/Aileron-Regular.otf");
@@ -483,6 +521,85 @@ editor_exit(void) {
ed.history_cap = 0;
}
+static void
+editor_focus_selection(void) {
+ Vec3 minimum = {FLT_MAX, FLT_MAX, FLT_MAX};
+ Vec3 maximum = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
+ bool found = false;
+
+ for (u32 i = 0; i < entity_count(); ++i) {
+ u64 id = entity_id_at(i);
+ Entity *ent = entity_get(id);
+
+ if (!ent->ed.selected) {
+ continue;
+ }
+
+ found = true;
+ Matrix world = entity_world_matrix(id);
+ BoundingBox bounds = {0};
+
+ if (ent->mdl.has && ent->mdl.draw &&
+ ent->mdl.mdl) {
+ Model model = *ent->mdl.mdl;
+ model.transform = MatrixIdentity();
+ bounds = GetModelBoundingBox(model);
+ }
+
+ for (int corner = 0; corner < 8; ++corner) {
+ Vec3 point = {
+ (corner & 1) ?
+ bounds.max.x : bounds.min.x,
+ (corner & 2) ?
+ bounds.max.y : bounds.min.y,
+ (corner & 4) ?
+ bounds.max.z : bounds.min.z,
+ };
+ point = Vector3Transform(point, world);
+ minimum = Vector3Min(minimum, point);
+ maximum = Vector3Max(maximum, point);
+ }
+ }
+
+ if (!found) {
+ return;
+ }
+
+ Vec3 center = Vector3Scale(
+ Vector3Add(minimum, maximum), 0.5f);
+ f32 radius = fmaxf(
+ Vector3Distance(minimum, maximum) * 0.5f, 0.5f);
+
+ /* Leave space for panels on either side of the view. */
+ f32 width = GetScreenWidth();
+ f32 height = GetScreenHeight();
+ f32 margin = 0;
+
+ if (ed.panel[ED_PNL_LEFT].enabled) {
+ Rect bounds = ed.panel[ED_PNL_LEFT].bounds;
+ margin = fmaxf(margin, bounds.x + bounds.width);
+ }
+
+ if (ed.panel[ED_PNL_RIGHT].enabled) {
+ Rect bounds = ed.panel[ED_PNL_RIGHT].bounds;
+ margin = fmaxf(margin, width - bounds.x);
+ }
+
+ f32 visible_width = fmaxf(width - 2.0f * margin, 1.0f);
+ f32 aspect = visible_width / height;
+ f32 half_fov = main_cam.perspective_fov * DEG2RAD * 0.5f;
+ f32 horizontal = atanf(tanf(half_fov) * aspect);
+ f32 angle = fminf(half_fov, horizontal);
+ f32 distance = radius / sinf(angle) * 1.15f;
+
+ f32 view_height = 0;
+ if (main_cam.cam.projection == CAMERA_ORTHOGRAPHIC) {
+ view_height = 2.0f * radius * 1.15f / fminf(aspect, 1.0f);
+ }
+
+ cam_focus(center, distance, view_height);
+}
+
void
editor_update(void) {
bool ctrl = IsKeyDown(KEY_LEFT_CONTROL);
@@ -490,8 +607,8 @@ editor_update(void) {
bool alt = IsKeyDown(KEY_LEFT_ALT);
/* sorry about the indentation */
- switch (app.mode) {
-case APP_MODE_EDIT_NONE:
+ switch (ed.interact) {
+case ED_INTERACT_NONE:
if (!ed.focus_captured && IsKeyPressed(KEY_SPACE)) {
ed.panel[ED_PNL_CENTER].type = ED_PNL_TYPE_SPACE;
ed.panel[ED_PNL_CENTER].enabled = true;
@@ -524,30 +641,48 @@ case APP_MODE_EDIT_NONE:
break;
}
- if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) {
- app.mode = APP_MODE_EDIT_CTRL;
- main_cam.update = true;
- HideCursor();
+ if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT) ||
+ (main_cam.state == CAM_MODE_EDIT_ORBIT &&
+ IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE))) {
+ ed.interact = ED_INTERACT_CAMERA;
+ main_cam.update = false;
DisableCursor();
break;
}
+ if (ed.mode == ED_MODE_PHYSICS) {
+ if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+ physics_pick();
+ }
+ break;
+ }
+
+ if (ed.mode != ED_MODE_SCENE) {
+ break;
+ }
+
+
+ if (!ctrl && IsKeyPressed(KEY_F)) {
+ editor_focus_selection();
+ break;
+ }
+
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
- app.mode = APP_MODE_EDIT_SELECT;
+ ed.interact = ED_INTERACT_SELECT;
editor_select(); // capture this frame
break;
}
if (IsKeyPressed(KEY_G)) {
if (!ctrl && ed.sel_cnt) {
- app.mode = APP_MODE_EDIT_MOVE;
+ ed.interact = ED_INTERACT_MOVE;
break;
}
}
if (IsKeyPressed(KEY_R)) {
if (!ctrl && ed.sel_cnt) {
- app.mode = APP_MODE_EDIT_ROTATE;
+ ed.interact = ED_INTERACT_ROTATE;
break;
}
@@ -602,38 +737,50 @@ case APP_MODE_EDIT_NONE:
}
break;
-case APP_MODE_EDIT_SELECT:
+case ED_INTERACT_SELECT:
editor_select();
break;
-case APP_MODE_EDIT_CTRL:
- cam_ctrl_editor();
- if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) {
- app.mode = APP_MODE_EDIT_NONE;
+case ED_INTERACT_CAMERA: {
+ bool held = IsMouseButtonDown(MOUSE_BUTTON_RIGHT);
+
+ if (main_cam.state == CAM_MODE_EDIT_ORBIT) {
+ held |= IsMouseButtonDown(MOUSE_BUTTON_MIDDLE);
+ }
+
+ if (!held) {
+ ed.interact = ED_INTERACT_NONE;
main_cam.update = false;
- ShowCursor();
+ EnableCursor();
+ break;
}
+
+ main_cam.update = true;
break;
+}
-case APP_MODE_EDIT_MOVE:
+case ED_INTERACT_MOVE:
editor_move();
break;
-case APP_MODE_EDIT_ROTATE:
+case ED_INTERACT_ROTATE:
editor_rotate();
break;
-case APP_MODE_EDIT_SPACE:
- break;
-
-// case APP_MODE_EDIT_CMD:
- // break;
-
default:
- TraceLog(LOG_FATAL, "editor_update: unhandled app.mode value");
+ TraceLog(LOG_FATAL, "editor_update: unhandled ed.interact value");
break;
}
+ main_cam.input =
+ (ed.interact == ED_INTERACT_CAMERA && main_cam.update) ||
+ (ed.interact == ED_INTERACT_NONE &&
+ ed.focus == ED_PNL_NULL &&
+ !ed.focus_captured &&
+ !ed.panel[ED_PNL_CENTER].enabled);
+
+ cam_update();
+
/* reset selection data */
ed.sel_cnt = 0;
ed.sel_point = Vector3Zero();
@@ -666,34 +813,81 @@ default:
static void
sel_ray(void) {
Ray ray = GetScreenToWorldRay(end_point, main_cam.cam);
+ Vec3 dir = Vector3Scale(ray.direction, 1000.0f);
b3QueryFilter filter = {
.categoryBits = BIT(PHYS_MASK_EDITOR_RAYCAST),
.maskBits = BIT(PHYS_MASK_EDITOR_SELECTABLE),
};
- Vec3 dir = Vector3Scale(ray.direction, 1000.0f);
- b3RayResult res =
- b3World_CastRayClosest(physics.world_id,
- *(b3Pos*)&ray.position,
- *(b3Pos*)&dir,
- filter);
+ b3RayResult res = b3World_CastRayClosest(
+ physics.world_id,
+ (b3Pos){ray.position.x, ray.position.y, ray.position.z},
+ (b3Vec3){dir.x, dir.y, dir.z},
+ filter);
if (ed.focus == ED_PNL_NULL &&
!IsKeyDown(KEY_LEFT_SHIFT)) {
entities_clear_selected();
}
- if (res.hit) {
- b3BodyId bid = b3Shape_GetBody(res.shapeId);
- u64 eid = *(u64*)b3Body_GetUserData(bid);
- Entity *ent = entity_get(eid);
+ if (!res.hit) {
+ return;
+ }
- if (ent->pid == 0) {
- return;
- }
+ b3BodyId body = b3Shape_GetBody(res.shapeId);
+ u64 *id = b3Body_GetUserData(body);
+ if (!id) {
+ return;
+ }
- ent->ed.selected = !ent->ed.selected;
+ Entity *ent = entity_get(*id);
+ if (!ent || !ent->pid) {
+ return;
+ }
+
+ ent->ed.selected = !ent->ed.selected;
+}
+
+static void
+physics_pick(void) {
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+
+ Entity *ent = entity_get(entity_id_from_pid(ed.physics_pid));
+ if (!ent || !ent->phys.runtime.bodies) {
+ return;
+ }
+
+ Ray ray = GetScreenToWorldRay(GetMousePosition(), main_cam.cam);
+ Vec3 dir = Vector3Scale(ray.direction, 1000.0f);
+
+ b3Pos origin = {
+ ray.position.x, ray.position.y, ray.position.z,
+ };
+ b3Vec3 translation = {dir.x, dir.y, dir.z};
+ f32 closest = 1.0f;
+
+ PhysicsCmp *phys = &ent->phys;
+ for (u32 i = 0; i < phys->body_cnt; ++i) {
+ PhysicsBody *body = &phys->bodies[i];
+ PhysicsBodyRuntime *runtime = &phys->runtime.bodies[i];
+
+ for (u32 j = 0; j < body->shape_cnt; ++j) {
+ b3ShapeId shape = runtime->shapes[j];
+ if (!b3Shape_IsValid(shape)) {
+ continue;
+ }
+
+ b3WorldCastOutput hit = b3Shape_RayCast(
+ shape, origin, translation);
+
+ if (hit.hit && hit.fraction <= closest) {
+ closest = hit.fraction;
+ ed.physics_body = (int)i;
+ ed.physics_shape = (int)j;
+ }
+ }
}
}
@@ -806,7 +1000,7 @@ editor_select(void) {
}
started = false;
sel_dist = 0;
- app.mode = APP_MODE_EDIT_NONE;
+ ed.interact = ED_INTERACT_NONE;
}
}
@@ -851,7 +1045,7 @@ editor_move(void) {
action_commit(&tr_action);
}
started = false;
- app.mode = APP_MODE_EDIT_NONE;
+ ed.interact = ED_INTERACT_NONE;
return;
}
@@ -900,6 +1094,7 @@ editor_move(void) {
Vec3 pos = Vector3Add(entity_world_pos(id), delta);
entity_set_world_pos(ent, pos);
+ entity_physics_transform_changed(id);
}
}
@@ -922,7 +1117,7 @@ editor_rotate(void) {
}
started = false;
- app.mode = APP_MODE_EDIT_NONE;
+ ed.interact = ED_INTERACT_NONE;
return;
}
@@ -945,6 +1140,7 @@ editor_rotate(void) {
Entity *ent = entity_get(id);
assert(ent);
entity_set_world_rot(ent, rot);
+ entity_physics_transform_changed(id);
}
}
@@ -956,8 +1152,8 @@ statusline(void) {
// Color bg = { 255, 255, 255, 150 };
Color bg = GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR));
- switch (app.mode) {
- case APP_MODE_EDIT_NONE:
+ switch (ed.interact) {
+ case ED_INTERACT_NONE:
snprintf(mode_str, 16, "EDITOR");
if (ed.sel_cnt > 0) {
snprintf(ctx_str, sizeof(ctx_str),
@@ -970,17 +1166,17 @@ statusline(void) {
}
break;
- case APP_MODE_EDIT_SELECT:
+ case ED_INTERACT_SELECT:
snprintf(mode_str, 16, "SELECT");
break;
- case APP_MODE_EDIT_CTRL:
+ case ED_INTERACT_CAMERA:
snprintf(mode_str, 16, "VIEW");
snprintf(ctx_str, sizeof(ctx_str), "controls: "
"shift: boost speed, scroll: adjust speed");
break;
- case APP_MODE_EDIT_MOVE:
+ case ED_INTERACT_MOVE:
snprintf(mode_str, 16, "MOVE");
cstr axis_str = IsKeyDown(KEY_LEFT_ALT) ?
@@ -995,7 +1191,7 @@ statusline(void) {
ed.sel_cnt, axis_str);
break;
- case APP_MODE_EDIT_ROTATE:
+ case ED_INTERACT_ROTATE:
snprintf(mode_str, 16, "ROTATE");
snprintf(ctx_str, sizeof(ctx_str), "%i Selected | "
"axis: global up, pivot: each entity | "
@@ -1009,6 +1205,25 @@ statusline(void) {
break;
};
+ Color heading_bg = { 0, 0, 0, 25 };
+
+ if (ed.mode == ED_MODE_PHYSICS) {
+ u64 id = entity_id_from_pid(ed.physics_pid);
+ Entity *ent = entity_get(id);
+ cstr name = ent && ent->name ? ent->name : "<unnamed>";
+
+ snprintf(mode_str, sizeof(mode_str), "PHYSICS");
+ heading_bg = (Color){70, 140, 210, 90};
+
+ if (ed.interact == ED_INTERACT_CAMERA) {
+ snprintf(ctx_str, sizeof(ctx_str),
+ "%s | MMB: pan, scroll: zoom", name);
+ } else {
+ snprintf(ctx_str, sizeof(ctx_str),
+ "%s | RMB: orbit, scroll: zoom", name);
+ }
+ }
+
/* statusline bg */
Rect rec = {
// 0, 0, /* top bar */
@@ -1016,12 +1231,13 @@ statusline(void) {
GetScreenWidth(), 25
// editor_label_w(mode_str) , 25
};
+
DrawRectangleRec(rec, bg);
/* mode str */
f32 mode_str_w = editor_label_w(mode_str);
rec.width = 70;
- DrawRectangleRec(rec, (Color){0,0,0,25});
+ DrawRectangleRec(rec, heading_bg);
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER);
GuiLabel(rec, mode_str);
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
@@ -1131,7 +1347,7 @@ panel_scene(EditorPanelContainer container, bool input) {
Rect bounds = ed.panel[container].bounds;
Vec2 *scroll = &ed.panel[container].scroll;
- input = input && app.mode == APP_MODE_EDIT_NONE;
+ input = input && ed.interact == ED_INTERACT_NONE;
int state = GuiGetState();
if (!input) {
@@ -1143,7 +1359,7 @@ panel_scene(EditorPanelContainer container, bool input) {
ed.focus_captured = true;
}
- GuiPanel(bounds, "Entities");
+ GuiPanel(bounds, "Scene");
f32 btn_w = editor_label_w("Create Entity") + 16;
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER);
@@ -1155,10 +1371,18 @@ panel_scene(EditorPanelContainer container, bool input) {
}
GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
+ GuiLabel((Rect){
+ bounds.x + 8, bounds.y + 64, 110, 20,
+ }, "Draw Physics");
+
+ GuiCheckBox((Rect){
+ bounds.x + 126, bounds.y + 64, 20, 20,
+ }, "", &ed.draw_physics);
+
bounds.x += 8;
- bounds.y += 64;
+ bounds.y += 94;
bounds.width -= 16;
- bounds.height -= 72;
+ bounds.height -= 102;
u32 count = 0;
for (u32 i = 0; i < entity_count(); ++i) {
@@ -1175,9 +1399,14 @@ panel_scene(EditorPanelContainer container, bool input) {
(Rect){ 0, 0, width, count * 26.0f },
scroll, &view);
- BeginScissorMode(
- (int)view.x, (int)view.y,
- (int)view.width, (int)view.height);
+ BeginScissorMode(view.x, view.y, view.width, view.height);
+
+ if (input && state != STATE_DISABLED && !GuiIsLocked() &&
+ IsMouseButtonPressed(MOUSE_BUTTON_LEFT) &&
+ !IsKeyDown(KEY_LEFT_SHIFT) &&
+ CheckCollisionPointRec(GetMousePosition(), view)) {
+ entities_clear_selected();
+ }
f32 y = view.y + scroll->y;
for (u32 i = 0; i < entity_count(); ++i) {
@@ -1316,7 +1545,6 @@ panel_vec3(PanelEdit *edit, Rect row, cstr label, Vec3 *value, bool mixed) {
return submitted;
}
-
static void
panel_properties(EditorPanelContainer container, bool input) {
static PanelEdit edit;
@@ -1327,7 +1555,7 @@ panel_properties(EditorPanelContainer container, bool input) {
int state = GuiGetState();
f32 pad = 8;
- input = input && app.mode == APP_MODE_EDIT_NONE
+ input = input && ed.interact == ED_INTERACT_NONE
&& state != STATE_DISABLED;
edit.next_id = 1;
@@ -1367,16 +1595,23 @@ panel_properties(EditorPanelContainer container, bool input) {
}
}
+ /* selection info prep */
ModelCmp *model = NULL;
int model_cnt = 0;
+ int physics_cnt = 0;
bool key_mixed = false;
bool draw_mixed = false;
for (u32 i = 0; i < entity_count(); ++i) {
Entity *candidate = entity_get(entity_id_at(i));
- if (!candidate->pid || !candidate->ed.selected ||
- !candidate->mdl.has) {
+ if (!candidate->pid || !candidate->ed.selected) {
+ continue;
+ }
+
+ physics_cnt += candidate->phys.has;
+
+ if (!candidate->mdl.has) {
continue;
}
@@ -1392,6 +1627,7 @@ panel_properties(EditorPanelContainer container, bool input) {
}
bool all_have_model = model_cnt == ed.sel_cnt;
+ bool all_have_physics = physics_cnt == ed.sel_cnt;
/* Name */
cstr name = mixed ? "<multiple entities selected>"
@@ -1512,12 +1748,14 @@ panel_properties(EditorPanelContainer container, bool input) {
action_commit(&action);
}
+ f32 next_y = group.y + group.height + 16;
+
model_comp:
if (!all_have_model) {
- goto comp_add;
+ goto physics_comp;
}
- group.y += group.height + 16;
+ group.y = next_y;
group.height = 80;
GuiGroupBox(group, "Model");
@@ -1578,38 +1816,171 @@ panel_properties(EditorPanelContainer container, bool input) {
action_commit(&action);
}
- row.y = group.y + group.height + 20;
+ next_y = group.y + group.height + pad + 20 + 16;
+
+ physics_comp:
+ if (!all_have_physics) {
+ goto comp_add;
+ }
+
+ group.y = next_y;
+ group.height = 80;
+ GuiGroupBox(group, "Physics");
+
+ row = (Rect){
+ group.x + pad,
+ group.y + 15,
+ group.width - 2 * pad,
+ 20,
+ };
+
+ GuiLabel(row, mixed ? "<multiple entities>" :
+ TextFormat("%u bodies, %u joints",
+ ent->phys.body_cnt, ent->phys.joint_cnt));
+
+ row.y += 30;
+ if (ent) {
+ if (GuiButton(row, "Edit Physics")) {
+ ed.scene_cam = main_cam;
+
+ main_cam.allow_fly = false;
+ main_cam.input = false;
+ main_cam.update = false;
+
+ u64 id = entity_id_from_pid(ent->pid);
+
+ f32 view_height = 10.0f *
+ tanf(main_cam.perspective_fov *
+ DEG2RAD * 0.5f);
+
+ cam_focus(entity_world_pos(id), 5.0f, view_height);
+
+ ed.physics_pid = ent->pid;
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+ ed.physics_scroll = (Vec2){0};
+ ed.prev_mode = ed.mode;
+ ed.mode = ED_MODE_PHYSICS;
+ ed.panel[ED_PNL_LEFT].type =
+ ED_PNL_TYPE_PHYSICS_ELEMENTS;
+ ed.panel[ED_PNL_RIGHT].type =
+ ED_PNL_TYPE_PHYSICS_PROPERTIES;
+
+ edit.focus = 0;
+ add_open = false;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ goto reset;
+ }
+ } else {
+ GuiLabel(row, "Select one entity to edit physics");
+ }
+
+ if (GuiButton((Rect){
+ group.x,
+ group.y + group.height + pad,
+ 120,
+ 20,
+ }, "Remove Physics")) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ for (u32 i = 0; i < action.item_cnt; ++i) {
+ u64 id = entity_id_from_pid(action.item[i].pid);
+ Entity *target = entity_get(id);
+ entity_physics_free(&target->phys);
+ }
+
+ action_commit(&action);
+ goto reset;
+ }
+
+ next_y = group.y + group.height + pad + 20 + 16;
comp_add:
GuiUnlock();
- row.y += 40;
+
+ row = (Rect){
+ bounds.x + pad,
+ next_y,
+ bounds.width - 2 * pad,
+ 20,
+ };
+
+ char choices[128] = "Add Component";
+ int model_choice = -1;
+ int physics_choice = -1;
+ int choice_cnt = 1;
+
+ if (!all_have_model) {
+ model_choice = choice_cnt++;
+ strcat(choices, model_cnt
+ ? ";Add Model to Missing" : ";Model");
+ }
+
+ if (!all_have_physics) {
+ physics_choice = choice_cnt++;
+ strcat(choices, physics_cnt
+ ? ";Add Physics to Missing" : ";Physics");
+ }
int add_state = GuiGetState();
- if (all_have_model) {
+ if (choice_cnt == 1) {
add_open = false;
GuiSetState(STATE_DISABLED);
}
- cstr choices = model_cnt
- ? "Add Component;Add Model to Missing"
- : "Add Component;Model";
+ bool toggle = GuiDropdownBox(
+ row, choices, &add_choice, add_open);
- if (GuiDropdownBox(row, choices, &add_choice, add_open)) {
- add_open = !add_open;
- edit.focus = 0;
+ GuiSetState(add_state);
+
+ if (!toggle) {
+ goto reset;
+ }
+
+ add_open = !add_open;
+ edit.focus = 0;
+
+ if (add_open) {
+ goto reset;
+ }
+
+ int choice = add_choice;
+ add_choice = 0;
+
+ if (choice == model_choice) {
+ model_picker_open(MDL_PICK_COMP_ADD);
+ goto reset;
+ }
+
+ if (choice != physics_choice) {
+ goto reset;
+ }
- if (!add_open && add_choice == 1) {
- model_picker_open(MDL_PICK_COMP_ADD);
- add_choice = 0;
+ EditorAction action = action_begin(ED_ACTION_PHYSICS, false);
+
+ for (u32 i = 0; i < action.item_cnt; ++i) {
+ Entity *target = entity_get(
+ entity_id_from_pid(action.item[i].pid)
+ );
+
+ if (target->phys.has) {
+ continue;
}
+
+ target->phys.has = true;
}
- GuiSetState(add_state);
+ action_commit(&action);
reset:
GuiUnlock();
GuiSetState(state);
+ GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
if (input && container != ED_PNL_CENTER) {
ed.focus_captured = edit.focus != 0 ||
add_open ||
@@ -2002,12 +2373,850 @@ panel_model_picker(Rect bounds, bool input) {
}
static void
+panel_physics_elements(EditorPanelContainer container) {
+ Rect bounds = ed.panel[container].bounds;
+ GuiPanel(bounds, "Physics Elements");
+
+ Entity *ent = entity_get(entity_id_from_pid(ed.physics_pid));
+ if (!ent) {
+ return;
+ }
+
+ PhysicsCmp *phys = &ent->phys;
+
+ Rect row = {
+ bounds.x + 8,
+ bounds.y + 30,
+ bounds.width - 16,
+ 20,
+ };
+
+ if (GuiButton(row, "Add Body")) {
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(phys);
+
+ PhysicsBody *bodies = realloc(
+ phys->bodies,
+ (phys->body_cnt + 1) * sizeof(*bodies));
+
+ if (!bodies) {
+ perror("physics body allocation failed");
+ exit(EXIT_FAILURE);
+ }
+
+ phys->bodies = bodies;
+ phys->bodies[phys->body_cnt] = (PhysicsBody) {
+ .rot = QuaternionIdentity(),
+ .gravity_scale = 1.0f,
+ .mode = PHYS_MODE_DYNAMIC,
+ .enable_sleep = true,
+ };
+
+ if (!phys->body_cnt) {
+ phys->root_body = 0;
+ }
+
+ ed.physics_body = (int)phys->body_cnt;
+ ed.physics_shape = -1;
+ phys->body_cnt++;
+
+ action_commit(&action);
+ }
+
+ Rect list = {
+ bounds.x + 8,
+ bounds.y + 60,
+ bounds.width - 16,
+ bounds.height - 68,
+ };
+
+ Rect view = {0};
+ f32 width = list.width
+ - 2 * GuiGetStyle(DEFAULT, BORDER_WIDTH)
+ - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH);
+
+ u32 row_count = phys->body_cnt;
+ for (u32 i = 0; i < phys->body_cnt; ++i) {
+ row_count += phys->bodies[i].shape_cnt;
+ }
+
+ GuiScrollPanel(list, NULL,
+ (Rect){0, 0, width, row_count * 26.0f},
+ &ed.physics_scroll, &view);
+
+ bool input = GuiGetState() != STATE_DISABLED && !GuiIsLocked();
+ Vec2 mouse = GetMousePosition();
+
+ if (input && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) &&
+ CheckCollisionPointRec(mouse, view)) {
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+ }
+
+ BeginScissorMode(
+ (int)view.x, (int)view.y,
+ (int)view.width, (int)view.height);
+
+ cstr shape_names[] = {
+ "Box",
+ "Sphere",
+ "Capsule",
+ "Cylinder",
+ "Convex Hull",
+ "Triangle Mesh",
+ };
+
+ f32 y = view.y + ed.physics_scroll.y;
+
+ for (u32 i = 0; i < phys->body_cnt; ++i) {
+ PhysicsBody *body = &phys->bodies[i];
+
+ for (int j = -1; j < (int)body->shape_cnt; ++j) {
+ row = (Rect){view.x, y, width, 26};
+ y += 26;
+
+ bool hovered = input &&
+ CheckCollisionPointRec(mouse, view) &&
+ CheckCollisionPointRec(mouse, row);
+
+ if (hovered &&
+ IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
+ ed.physics_body = (int)i;
+ ed.physics_shape = j;
+ }
+
+ bool selected = ed.physics_body == (int)i &&
+ ed.physics_shape == j;
+
+ if (selected || hovered) {
+ int colour = selected
+ ? BASE_COLOR_PRESSED
+ : BASE_COLOR_FOCUSED;
+
+ DrawRectangleRec(row, GetColor(
+ GuiGetStyle(DEFAULT, colour)));
+ }
+
+ f32 indent = j < 0 ? 8 : 26;
+ row.x += indent;
+ row.width -= indent;
+
+ if (j < 0) {
+ GuiLabel(row, TextFormat("Body %u%s",
+ i + 1, i == phys->root_body ?
+ " (root)" : ""));
+ } else {
+ GuiLabel(row, TextFormat("%s %i",
+ shape_names[body->shapes[j].tag],
+ j + 1));
+ }
+ }
+ }
+
+ EndScissorMode();
+}
+
+static void
+panel_physics_shape(PanelEdit *edit, Rect row,
+ Entity *ent, PhysicsBody *body) {
+ u32 index = (u32)ed.physics_shape;
+ if (index >= body->shape_cnt) {
+ edit->focus = 0;
+ return;
+ }
+
+ PhysicsShape *shape = &body->shapes[index];
+
+ GuiLabel(row, TextFormat("Body %i | Shape %u",
+ ed.physics_body + 1, index + 1));
+
+ row.y += 30;
+
+ if (GuiButton(row, "Remove Shape")) {
+ edit->focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ switch (shape->tag) {
+ case PHYS_SHAPE_CONVEX_HULL:
+ free(shape->ConvexHull.points);
+ break;
+
+ case PHYS_SHAPE_STATIC_TRIMESH:
+ free(shape->TriMesh.points);
+ free(shape->TriMesh.indices);
+ break;
+
+ default:
+ break;
+ }
+
+ memmove(shape, shape + 1,
+ (body->shape_cnt - index - 1) * sizeof(*shape));
+ body->shape_cnt--;
+
+ if (!body->shape_cnt) {
+ free(body->shapes);
+ body->shapes = NULL;
+ }
+
+ ed.physics_shape = -1;
+ action_commit(&action);
+ return;
+ }
+
+ Vec3 values[3] = {
+ shape->pos,
+ Vector3Scale(QuaternionToEuler(shape->rot), RAD2DEG),
+ {0},
+ };
+
+ cstr labels[3] = {"Pos", "Rot", "Size"};
+ bool changed[3] = {false};
+ int count = 2;
+
+ if (shape->tag == PHYS_SHAPE_BOX) {
+ values[2] = shape->Box.sz;
+ count = 3;
+ }
+
+ for (int i = 0; i < count; ++i) {
+ row.y += 30;
+
+ Vec3 value = values[i];
+ int axis = panel_vec3(edit, row, labels[i], &value, false);
+
+ if (axis < 0) {
+ continue;
+ }
+
+ if (!isfinite(value.x) ||
+ !isfinite(value.y) ||
+ !isfinite(value.z)) {
+ continue;
+ }
+
+ if (i == 2 &&
+ (value.x <= 0 || value.y <= 0 || value.z <= 0)) {
+ continue;
+ }
+
+ changed[i] = !Vector3Equals(value, values[i]);
+ values[i] = value;
+ }
+
+ f32 total_height = 0;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE) {
+ total_height = shape->Capsule.height +
+ 2.0f * shape->Capsule.radius;
+ }
+
+ f32 radius = 0;
+ bool radius_changed = false;
+
+ if (shape->tag == PHYS_SHAPE_SPHERE ||
+ shape->tag == PHYS_SHAPE_CAPSULE ||
+ shape->tag == PHYS_SHAPE_CYLINDER) {
+ row.y += 30;
+
+ GuiLabel((Rect){
+ row.x, row.y, 60, row.height,
+ }, "Radius");
+
+ Rect box = {
+ row.x + 60, row.y,
+ row.width - 60, row.height,
+ };
+
+ f32 orig_radius;
+ if (shape->tag == PHYS_SHAPE_SPHERE) {
+ orig_radius = shape->Sphere.radius;
+ } else if (shape->tag == PHYS_SHAPE_CAPSULE) {
+ orig_radius = shape->Capsule.radius;
+ } else {
+ orig_radius = shape->Cylinder.radius;
+ }
+ radius = orig_radius;
+
+ if (panel_float(edit, box, &radius, false)) {
+ radius_changed = isfinite(radius) &&
+ radius > 0 &&
+ radius != orig_radius;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE &&
+ radius > total_height * 0.5f) {
+ radius_changed = false;
+ }
+ }
+ }
+
+ f32 height = 0;
+ bool height_changed = false;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE ||
+ shape->tag == PHYS_SHAPE_CYLINDER) {
+ row.y += 30;
+
+ GuiLabel((Rect){
+ row.x, row.y, 100, row.height,
+ }, "Height");
+
+ Rect box = {
+ row.x + 100, row.y,
+ row.width - 100, row.height,
+ };
+
+ f32 original_height = shape->tag == PHYS_SHAPE_CAPSULE
+ ? total_height : shape->Cylinder.height;
+
+ height = original_height;
+
+ if (panel_float(edit, box, &height, false)) {
+ height_changed = isfinite(height) &&
+ height > 0 &&
+ height != original_height;
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE) {
+ f32 effective_radius = radius_changed
+ ? radius : shape->Capsule.radius;
+
+ if (height < 2.0f * effective_radius) {
+ height_changed = false;
+ }
+ }
+ }
+ }
+
+ cstr material_labels[3] = {
+ "Density",
+ "Friction",
+ "Restitution",
+ };
+
+ f32 material[3] = {
+ shape->density,
+ shape->friction,
+ shape->restitution,
+ };
+
+ bool material_changed = false;
+
+ for (int i = 0; i < 3; ++i) {
+ row.y += 30;
+
+ GuiLabel((Rect){
+ row.x, row.y, 100, row.height,
+ }, material_labels[i]);
+
+ Rect box = {
+ row.x + 100, row.y,
+ row.width - 100, row.height,
+ };
+
+ f32 value = material[i];
+ if (!panel_float(edit, box, &value, false)) {
+ continue;
+ }
+
+ if (!isfinite(value) || value < 0) {
+ continue;
+ }
+
+ if (i == 2 && value > 1) {
+ continue;
+ }
+
+ material_changed |= value != material[i];
+ material[i] = value;
+ }
+
+ if (!changed[0] && !changed[1] && !changed[2] &&
+ !radius_changed && !height_changed &&
+ !material_changed) {
+ return;
+ }
+
+ EditorAction action = action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ if (changed[0]) {
+ shape->pos = values[0];
+ }
+
+ if (changed[1]) {
+ shape->rot = QuaternionFromEuler(
+ values[1].x * DEG2RAD,
+ values[1].y * DEG2RAD,
+ values[1].z * DEG2RAD);
+ }
+
+ if (changed[2]) {
+ shape->Box.sz = values[2];
+ }
+
+ if (shape->tag == PHYS_SHAPE_SPHERE && radius_changed) {
+ shape->Sphere.radius = radius;
+ }
+
+ if (shape->tag == PHYS_SHAPE_CAPSULE &&
+ (radius_changed || height_changed)) {
+ f32 new_radius = radius_changed
+ ? radius : shape->Capsule.radius;
+ f32 new_height = height_changed ? height : total_height;
+
+ shape->Capsule.radius = new_radius;
+ shape->Capsule.height =
+ fmaxf(0, new_height - 2.0f * new_radius);
+ }
+
+ if (shape->tag == PHYS_SHAPE_CYLINDER) {
+ if (radius_changed) {
+ shape->Cylinder.radius = radius;
+ }
+
+ if (height_changed) {
+ shape->Cylinder.height = height;
+ }
+ }
+
+ shape->density = material[0];
+ shape->friction = material[1];
+ shape->restitution = material[2];
+
+ action_commit(&action);
+}
+
+static void
+panel_physics_properties(EditorPanelContainer container, bool input) {
+ Rect bounds = ed.panel[container].bounds;
+
+ static bool type_open;
+ static PanelEdit edit;
+
+ edit.next_id = 1;
+ edit.input = input && !type_open;
+
+ if (!edit.input) {
+ edit.focus = 0;
+ }
+
+ if (!input) {
+ type_open = false;
+ }
+
+ if (type_open) {
+ GuiLock();
+ }
+
+ GuiPanel(bounds, "Physics Properties");
+
+ Rect button = {
+ bounds.x + 8,
+ bounds.y + 30,
+ bounds.width - 16,
+ 20,
+ };
+
+ if (GuiButton(button, "Done")) {
+ main_cam = ed.scene_cam;
+ edit.focus = 0;
+ ed.mode = ed.prev_mode;
+ ed.physics_pid = 0;
+ ed.panel[ED_PNL_LEFT].type = ED_PNL_TYPE_SCENE;
+ ed.panel[ED_PNL_RIGHT].type = ED_PNL_TYPE_PROPERTIES;
+ ed.focus = ED_PNL_NULL;
+ ed.focus_captured = false;
+ return;
+ }
+
+ Entity *ent = entity_get(entity_id_from_pid(ed.physics_pid));
+
+ Rect info = {
+ bounds.x + 8,
+ bounds.y + 60,
+ bounds.width - 16,
+ 20,
+ };
+
+ if (ent && ed.physics_body >= 0 &&
+ (u32)ed.physics_body < ent->phys.body_cnt) {
+ PhysicsBody *body = &ent->phys.bodies[ed.physics_body];
+
+ if (ed.physics_shape >= 0) {
+ type_open = false;
+ GuiUnlock();
+
+ panel_physics_shape(&edit, info, ent, body);
+ goto reset;
+ }
+
+ GuiLabel(info, TextFormat("Body %i | %u shapes",
+ ed.physics_body + 1, body->shape_cnt));
+
+ info.y += 30;
+
+ if (ent->phys.root_body == (u32)ed.physics_body) {
+ GuiLabel(info, "Root body");
+ } else if (GuiButton(info, "Make Root Body")) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ ent->phys.root_body = (u32)ed.physics_body;
+
+ action_commit(&action);
+ }
+
+ info.y += 30;
+
+ if (GuiButton(info, "Remove Body")) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ PhysicsCmp *phys = &ent->phys;
+ u32 index = (u32)ed.physics_body;
+
+ entity_physics_clear(phys);
+
+ for (u32 i = 0; i < body->shape_cnt; ++i) {
+ PhysicsShape *shape = &body->shapes[i];
+
+ switch (shape->tag) {
+ case PHYS_SHAPE_CONVEX_HULL:
+ free(shape->ConvexHull.points);
+ break;
+
+ case PHYS_SHAPE_STATIC_TRIMESH:
+ free(shape->TriMesh.points);
+ free(shape->TriMesh.indices);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ free(body->shapes);
+
+ memmove(body, body + 1,
+ (phys->body_cnt - index - 1) * sizeof(*body));
+
+ phys->body_cnt--;
+
+ if (phys->body_cnt == index) {
+ phys->root_body = 0;
+ } else if (phys->root_body > index) {
+ phys->root_body -= 1;
+ }
+
+ u32 kept = 0;
+ for (u32 i = 0; i < phys->joint_cnt; ++i) {
+ PhysicsJoint joint = phys->joints[i];
+
+ if (joint.body_a == index ||
+ joint.body_b == index) {
+ continue;
+ }
+
+ if (joint.body_a > index) {
+ joint.body_a--;
+ }
+
+ if (joint.body_b > index) {
+ joint.body_b--;
+ }
+
+ phys->joints[kept++] = joint;
+ }
+ phys->joint_cnt = kept;
+
+ if (!phys->body_cnt) {
+ free(phys->bodies);
+ phys->bodies = NULL;
+ }
+
+ if (!phys->joint_cnt) {
+ free(phys->joints);
+ phys->joints = NULL;
+ }
+
+ ed.physics_body = -1;
+ ed.physics_shape = -1;
+
+ action_commit(&action);
+ goto reset;
+ }
+
+ info.y += 30;
+
+ Vec3 pos = body->pos;
+ int pos_axis = panel_vec3(
+ &edit, info, "Pos", &pos, false);
+
+ bool pos_changed = pos_axis >= 0 &&
+ isfinite(pos.x) &&
+ isfinite(pos.y) &&
+ isfinite(pos.z) &&
+ !Vector3Equals(pos, body->pos);
+
+ info.y += 30;
+
+ Vec3 original_rot = Vector3Scale(
+ QuaternionToEuler(body->rot), RAD2DEG);
+
+ Vec3 rot = original_rot;
+ int rot_axis = panel_vec3(
+ &edit, info, "Rot", &rot, false);
+
+ bool rot_changed = rot_axis >= 0 &&
+ isfinite(rot.x) &&
+ isfinite(rot.y) &&
+ isfinite(rot.z) &&
+ !Vector3Equals(rot, original_rot);
+
+ info.y += 30;
+
+ GuiLabel((Rect){
+ info.x, info.y, 45, info.height,
+ }, "Type");
+
+ Rect type_box = {
+ info.x + 45, info.y,
+ info.width - 45, info.height,
+ };
+
+ info.y += 30;
+
+ bool sleep = body->enable_sleep;
+ panel_bool(info, "Enable Sleep", &sleep, false);
+
+ f32 values[3] = {
+ body->gravity_scale,
+ body->linear_damping,
+ body->angular_damping,
+ };
+
+ cstr labels[3] = {
+ "Gravity scale",
+ "Linear damping",
+ "Angular damping",
+ };
+
+ bool floats_changed = false;
+
+ for (int i = 0; i < 3; ++i) {
+ info.y += 30;
+
+ GuiLabel((Rect){
+ info.x, info.y, 130, info.height,
+ }, labels[i]);
+
+ Rect box = {
+ info.x + 130, info.y,
+ info.width - 130, info.height,
+ };
+
+ f32 value = values[i];
+ if (panel_float(&edit, box, &value, false)) {
+ if (!isfinite(value)) {
+ continue;
+ }
+
+ if (i > 0 && value < 0) {
+ continue;
+ }
+
+ floats_changed |= value != values[i];
+ values[i] = value;
+ }
+ }
+
+ info.y += 30;
+ bool add_box = GuiButton(info, "Add Box");
+
+ info.y += 30;
+ bool add_sphere = GuiButton(info, "Add Sphere");
+
+ info.y += 30;
+ bool add_capsule = GuiButton(info, "Add Capsule");
+
+ info.y += 30;
+ bool add_cylinder = GuiButton(info, "Add Cylinder");
+
+ info.y += 30;
+ int button_state = GuiGetState();
+
+ if (!ent->mdl.has) {
+ GuiSetState(STATE_DISABLED);
+ }
+
+ bool fit_model = GuiButton(info, "Add Model Box");
+ GuiSetState(button_state);
+
+ Model *model = NULL;
+ if (fit_model) {
+ Asset *asset = asset_get(ent->mdl.key);
+ if (asset && asset->tag == ASSET_TAG_MODEL &&
+ asset->mdl.meshCount > 0) {
+ model = &asset->mdl;
+ }
+ }
+
+ if (add_box || add_sphere || add_capsule || add_cylinder ||
+ model) {
+ edit.focus = 0;
+
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ PhysicsShape *shapes = realloc(
+ body->shapes,
+ (body->shape_cnt + 1) * sizeof(*shapes));
+
+ if (!shapes) {
+ perror("physics shape allocation failed");
+ exit(1);
+ }
+
+ body->shapes = shapes;
+ body->shapes[body->shape_cnt] = (PhysicsShape){
+ .tag = PHYS_SHAPE_BOX,
+ .Box.sz = {1, 1, 1},
+ .rot = QuaternionIdentity(),
+ .density = 1.0f,
+ .friction = 0.5f,
+ .category_bits = BIT(PHYS_MASK_DEFAULT),
+ .mask_bits = UINT32_MAX,
+ };
+
+ if (add_sphere) {
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->tag = PHYS_SHAPE_SPHERE;
+ shape->Sphere.radius = 0.5f;
+ }
+
+ if (add_capsule) {
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->tag = PHYS_SHAPE_CAPSULE;
+ shape->Capsule.radius = 0.5f;
+ shape->Capsule.height = 1.0f;
+ }
+
+ if (add_cylinder) {
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->tag = PHYS_SHAPE_CYLINDER;
+ shape->Cylinder.radius = 0.5f;
+ shape->Cylinder.height = 1.0f;
+ }
+
+ if (model) {
+ Model local_model = *model;
+ local_model.transform = MatrixIdentity();
+
+ BoundingBox bounds =
+ GetModelBoundingBox(local_model);
+
+ Vec3 center = Vector3Scale(
+ Vector3Add(
+ bounds.min,
+ bounds.max), 0.5f);
+
+ PhysicsShape *shape =
+ &body->shapes[body->shape_cnt];
+
+ shape->Box.sz =
+ Vector3Subtract(bounds.max,
+ bounds.min);
+ shape->rot = QuaternionInvert(body->rot);
+ shape->pos = Vector3RotateByQuaternion(
+ Vector3Subtract(center, body->pos),
+ shape->rot);
+ }
+
+ body->shape_cnt++;
+
+ action_commit(&action);
+ }
+
+ GuiUnlock();
+ int mode = body->mode;
+ if (GuiDropdownBox(type_box,
+ "Static;Kinematic;Dynamic",
+ &mode, type_open)) {
+ type_open = !type_open;
+ edit.focus = 0;
+ }
+
+ if (input && IsKeyPressed(KEY_ESCAPE)) {
+ type_open = false;
+ }
+
+ if (mode != body->mode ||
+ sleep != body->enable_sleep ||
+ floats_changed ||
+ pos_changed ||
+ rot_changed) {
+ EditorAction action =
+ action_begin(ED_ACTION_PHYSICS, false);
+
+ entity_physics_clear(&ent->phys);
+
+ if (pos_changed) {
+ body->pos = pos;
+ }
+
+ if (rot_changed) {
+ body->rot = QuaternionFromEuler(
+ rot.x * DEG2RAD,
+ rot.y * DEG2RAD,
+ rot.z * DEG2RAD);
+ }
+
+ body->mode = (PhysicsMode)mode;
+ body->enable_sleep = sleep;
+
+ body->gravity_scale = values[0];
+ body->linear_damping = values[1];
+ body->angular_damping = values[2];
+
+ action_commit(&action);
+ }
+ } else {
+ GuiLabel(info, "Select a body");
+ }
+
+ reset:
+ GuiUnlock();
+ if (input) {
+ ed.focus_captured = type_open || edit.focus != 0;
+ }
+}
+
+static void
panel_draw(EditorPanelContainer container) {
if (!ed.panel[container].enabled) {
return;
}
- bool input = app.mode == APP_MODE_EDIT_NONE &&
+ bool input = ed.interact == ED_INTERACT_NONE &&
ed.focus == container &&
(!ed.panel[ED_PNL_CENTER].enabled ||
container == ED_PNL_CENTER);
@@ -2051,7 +3260,6 @@ panel_draw(EditorPanelContainer container) {
ed.panel[container].enabled = false;
ed.focus = ED_PNL_NULL;
ed.focus_captured = false;
- app.mode = APP_MODE_EDIT_NONE;
}
panel_space(container);
break;
@@ -2081,6 +3289,14 @@ panel_draw(EditorPanelContainer container) {
}
break;
+
+ case ED_PNL_TYPE_PHYSICS_ELEMENTS:
+ panel_physics_elements(container);
+ break;
+
+ case ED_PNL_TYPE_PHYSICS_PROPERTIES:
+ panel_physics_properties(container, input);
+ break;
}
GuiSetState(state);
@@ -2115,7 +3331,7 @@ editor_draw_ui(void) {
panel_draw(ED_PNL_LEFT);
panel_draw(ED_PNL_RIGHT);
- if (app.mode == APP_MODE_EDIT_SELECT) {
+ if (ed.interact == ED_INTERACT_SELECT) {
if (started && sel_dist > 20) {
sel_rec_draw();
}
@@ -2186,13 +3402,177 @@ gizmo_rotate(void) {
}
}
+static void
+physics_draw_shapes(u64 id) {
+ Entity *ent = entity_get(id);
+ if (!ent) {
+ return;
+ }
+
+ Matrix entity_world = entity_world_matrix(id);
+ Quaternion entity_rot = entity_world_rot(id);
+
+ for (u32 i = 0; i < ent->phys.body_cnt; ++i) {
+ PhysicsBody *body = &ent->phys.bodies[i];
+
+ Matrix body_local = MatrixMultiply(
+ QuaternionToMatrix(body->rot),
+ MatrixTranslate(
+ body->pos.x, body->pos.y, body->pos.z));
+
+ Matrix body_world = MatrixMultiply(body_local, entity_world);
+
+ for (u32 j = 0; j < body->shape_cnt; ++j) {
+ PhysicsShape *shape = &body->shapes[j];
+
+ bool selected = ent->ed.selected;
+
+ if (ed.mode == ED_MODE_PHYSICS) {
+ selected = ent->pid == ed.physics_pid &&
+ ed.physics_body == (int)i &&
+ (ed.physics_shape < 0 ||
+ ed.physics_shape == (int)j);
+ }
+
+ Color colour = selected ? ORANGE : SKYBLUE;
+
+ Matrix shape_local = MatrixMultiply(
+ QuaternionToMatrix(shape->rot),
+ MatrixTranslate(
+ shape->pos.x,
+ shape->pos.y,
+ shape->pos.z));
+
+ Matrix world = MatrixMultiply(shape_local, body_world);
+
+ Vec3 scale = {
+ Vector3Length(
+ (Vec3){world.m0, world.m1, world.m2}),
+ Vector3Length(
+ (Vec3){world.m4, world.m5, world.m6}),
+ Vector3Length(
+ (Vec3){world.m8, world.m9, world.m10}),
+ };
+
+ Vec3 center = {world.m12, world.m13, world.m14};
+
+ Quaternion rotation = QuaternionMultiply(
+ QuaternionMultiply(entity_rot, body->rot),
+ shape->rot);
+
+ Matrix transform = MatrixMultiply(
+ QuaternionToMatrix(rotation),
+ MatrixTranslate(center.x, center.y, center.z));
+
+ rlPushMatrix();
+ rlMultMatrixf(MatrixToFloat(transform));
+
+ switch (shape->tag) {
+ case PHYS_SHAPE_BOX: {
+ Vec3 size = Vector3Multiply(
+ shape->Box.sz, scale);
+
+ DrawCubeWires((Vec3){0},
+ size.x, size.y, size.z, colour);
+ break;
+ }
+
+ case PHYS_SHAPE_SPHERE: {
+ Vec3 entity_scale = {
+ Vector3Length((Vec3){
+ entity_world.m0,
+ entity_world.m1,
+ entity_world.m2,
+ }),
+ Vector3Length((Vec3){
+ entity_world.m4,
+ entity_world.m5,
+ entity_world.m6,
+ }),
+ Vector3Length((Vec3){
+ entity_world.m8,
+ entity_world.m9,
+ entity_world.m10,
+ }),
+ };
+
+ f32 largest = fmaxf(entity_scale.x,
+ fmaxf(entity_scale.y,
+ entity_scale.z));
+
+ DrawSphereWires((Vec3){0},
+ shape->Sphere.radius * largest,
+ 8, 12, colour);
+ break;
+ }
+
+ case PHYS_SHAPE_CAPSULE: {
+ f32 radius = shape->Capsule.radius *
+ fmaxf(scale.x, scale.z);
+ f32 half_height =
+ shape->Capsule.height * scale.y * 0.5;
+
+ if (half_height == 0) {
+ DrawSphereWires((Vec3){0}, radius, 8,
+ 12, colour);
+ } else {
+ DrawCapsuleWires(
+ (Vec3) {0,-half_height,0},
+ (Vec3){0,half_height,0},
+ radius, 4, 8, colour);
+ }
+ break;
+ }
+
+ case PHYS_SHAPE_CYLINDER: {
+ f32 radius = shape->Cylinder.radius *
+ fmaxf(scale.x, scale.z);
+ f32 half_height =
+ shape->Cylinder.height * scale.y * 0.5f;
+
+ DrawCylinderWiresEx(
+ (Vec3){0, -half_height, 0},
+ (Vec3){0, half_height, 0},
+ radius, radius, 12, colour);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ rlPopMatrix();
+ }
+ }
+}
+
void
editor_draw_3d(void) {
- if (app.mode == APP_MODE_EDIT_MOVE) {
+ if (ed.mode == ED_MODE_PHYSICS) {
+ physics_draw_shapes(entity_id_from_pid(ed.physics_pid));
+ return;
+ }
+
+ if (ed.mode != ED_MODE_SCENE) {
+ return;
+ }
+
+ if (ed.draw_physics) {
+ for (u32 i = 0; i < entity_count(); ++i) {
+ u64 id = entity_id_at(i);
+ Entity *ent = entity_get(id);
+
+ if (ent->phys.has) {
+ physics_draw_shapes(id);
+ }
+ }
+ }
+
+ if (ed.interact == ED_INTERACT_MOVE) {
gizmo_move();
}
- if (app.mode == APP_MODE_EDIT_ROTATE) {
+ if (ed.interact == ED_INTERACT_ROTATE) {
gizmo_rotate();
}
diff --git a/editor.h b/editor.h
index 07ad48d..808a1ee 100644
--- a/editor.h
+++ b/editor.h
@@ -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 {
diff --git a/ent.c b/ent.c
index 61c6c6a..2b23f0f 100644
--- a/ent.c
+++ b/ent.c
@@ -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 = &copy.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;
}
diff --git a/ent.h b/ent.h
index 48ecbec..81ac395 100644
--- a/ent.h
+++ b/ent.h
@@ -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
diff --git a/env.c b/env.c
index f00b669..2ed7012 100644
--- a/env.c
+++ b/env.c
@@ -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;
diff --git a/physics.h b/physics.h
index 0a92118..5a9a8d0 100644
--- a/physics.h
+++ b/physics.h
@@ -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
new file mode 100755
index 0000000..d1d7d1c
--- /dev/null
+++ b/res/mdl/ak74.glb
Binary files differ
diff --git a/res/mdl/m16a2.glb b/res/mdl/m16a2.glb
new file mode 100755
index 0000000..baa680d
--- /dev/null
+++ b/res/mdl/m16a2.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AK47/AK.png b/res/mdl/mega-gun-pack/AK47/AK.png
new file mode 100644
index 0000000..e4a1713
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AK47/AK.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AK47/ak_fbx.fbx b/res/mdl/mega-gun-pack/AK47/ak_fbx.fbx
new file mode 100644
index 0000000..b793949
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AK47/ak_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AK47/ak_glb.glb b/res/mdl/mega-gun-pack/AK47/ak_glb.glb
new file mode 100644
index 0000000..daeda33
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AK47/ak_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AUG/AUG.blend b/res/mdl/mega-gun-pack/AUG/AUG.blend
new file mode 100644
index 0000000..253f160
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AUG/AUG.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AUG/AUG.blend1 b/res/mdl/mega-gun-pack/AUG/AUG.blend1
new file mode 100644
index 0000000..de85af6
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AUG/AUG.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AUG/AUG.fbx b/res/mdl/mega-gun-pack/AUG/AUG.fbx
new file mode 100644
index 0000000..cfa213f
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AUG/AUG.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AUG/AUG.glb b/res/mdl/mega-gun-pack/AUG/AUG.glb
new file mode 100644
index 0000000..22e2ce0
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AUG/AUG.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AUG/AUG.png b/res/mdl/mega-gun-pack/AUG/AUG.png
new file mode 100644
index 0000000..5c5c6fc
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AUG/AUG.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/AUG/AUG_Psd.psd b/res/mdl/mega-gun-pack/AUG/AUG_Psd.psd
new file mode 100644
index 0000000..cf63a71
--- /dev/null
+++ b/res/mdl/mega-gun-pack/AUG/AUG_Psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo.blend b/res/mdl/mega-gun-pack/Ammo/Ammo.blend
new file mode 100644
index 0000000..9dfa782
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo.blend1 b/res/mdl/mega-gun-pack/Ammo/Ammo.blend1
new file mode 100644
index 0000000..87c095a
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_Box.png b/res/mdl/mega-gun-pack/Ammo/Ammo_Box.png
new file mode 100644
index 0000000..0c1a1ee
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo_Box.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbx b/res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbx
new file mode 100644
index 0000000..8d74d84
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_glb.glb b/res/mdl/mega-gun-pack/Ammo/Ammo_glb.glb
new file mode 100644
index 0000000..0ad3cb9
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_psd.psd b/res/mdl/mega-gun-pack/Ammo/Ammo_psd.psd
new file mode 100644
index 0000000..72297ae
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/Ammo_tex.png b/res/mdl/mega-gun-pack/Ammo/Ammo_tex.png
new file mode 100644
index 0000000..ea6c557
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/Ammo_tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Ammo/ammo_box.fbx b/res/mdl/mega-gun-pack/Ammo/ammo_box.fbx
new file mode 100644
index 0000000..3b603f9
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Ammo/ammo_box.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/Attachments_1.png b/res/mdl/mega-gun-pack/Attachments/Attachments_1.png
new file mode 100644
index 0000000..a142a83
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/Attachments_1.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/Attachments_1.psd b/res/mdl/mega-gun-pack/Attachments/Attachments_1.psd
new file mode 100644
index 0000000..cff0caf
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/Attachments_1.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/Screenshot.png b/res/mdl/mega-gun-pack/Attachments/Screenshot.png
new file mode 100644
index 0000000..ced46c6
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/Screenshot.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.blend b/res/mdl/mega-gun-pack/Attachments/attachments.blend
new file mode 100644
index 0000000..287b5a0
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/attachments.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.blend1 b/res/mdl/mega-gun-pack/Attachments/attachments.blend1
new file mode 100644
index 0000000..cd97f95
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/attachments.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.fbx b/res/mdl/mega-gun-pack/Attachments/attachments.fbx
new file mode 100644
index 0000000..495f0bd
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/attachments.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/attachments.glb b/res/mdl/mega-gun-pack/Attachments/attachments.glb
new file mode 100644
index 0000000..1794c06
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/attachments.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Attachments/attachments1.blend b/res/mdl/mega-gun-pack/Attachments/attachments1.blend
new file mode 100644
index 0000000..d127464
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Attachments/attachments1.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend
new file mode 100644
index 0000000..a483308
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1 b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1
new file mode 100644
index 0000000..6811e8c
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbx b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbx
new file mode 100644
index 0000000..03614a8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glb b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glb
new file mode 100644
index 0000000..00372b2
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.png b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.png
new file mode 100644
index 0000000..da775ae
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psd b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psd
new file mode 100644
index 0000000..7c646b8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Bolt Action Sniper/BoltActionSniper.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/G36/g36.blend b/res/mdl/mega-gun-pack/G36/g36.blend
new file mode 100644
index 0000000..4287841
--- /dev/null
+++ b/res/mdl/mega-gun-pack/G36/g36.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/G36/g36.blend1 b/res/mdl/mega-gun-pack/G36/g36.blend1
new file mode 100644
index 0000000..be75f45
--- /dev/null
+++ b/res/mdl/mega-gun-pack/G36/g36.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/G36/g36.fbx b/res/mdl/mega-gun-pack/G36/g36.fbx
new file mode 100644
index 0000000..ed98201
--- /dev/null
+++ b/res/mdl/mega-gun-pack/G36/g36.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/G36/g36.glb b/res/mdl/mega-gun-pack/G36/g36.glb
new file mode 100644
index 0000000..67e6683
--- /dev/null
+++ b/res/mdl/mega-gun-pack/G36/g36.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/G36/g36.png b/res/mdl/mega-gun-pack/G36/g36.png
new file mode 100644
index 0000000..3771227
--- /dev/null
+++ b/res/mdl/mega-gun-pack/G36/g36.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/G36/g36.psd b/res/mdl/mega-gun-pack/G36/g36.psd
new file mode 100644
index 0000000..1816a6f
--- /dev/null
+++ b/res/mdl/mega-gun-pack/G36/g36.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Glock/Glock.blend b/res/mdl/mega-gun-pack/Glock/Glock.blend
new file mode 100644
index 0000000..5a2dd65
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Glock/Glock.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Glock/Glock.blend1 b/res/mdl/mega-gun-pack/Glock/Glock.blend1
new file mode 100644
index 0000000..ba94af1
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Glock/Glock.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Glock/Glock.png b/res/mdl/mega-gun-pack/Glock/Glock.png
new file mode 100644
index 0000000..860bc00
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Glock/Glock.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Glock/Glock_PSD.psd b/res/mdl/mega-gun-pack/Glock/Glock_PSD.psd
new file mode 100644
index 0000000..71b171e
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Glock/Glock_PSD.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Glock/Glock_fbx.fbx b/res/mdl/mega-gun-pack/Glock/Glock_fbx.fbx
new file mode 100644
index 0000000..0314650
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Glock/Glock_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Glock/Glock_glb.glb b/res/mdl/mega-gun-pack/Glock/Glock_glb.glb
new file mode 100644
index 0000000..326776e
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Glock/Glock_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.blend b/res/mdl/mega-gun-pack/Grenades/Grenades.blend
new file mode 100644
index 0000000..c63172a
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/Grenades.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.blend1 b/res/mdl/mega-gun-pack/Grenades/Grenades.blend1
new file mode 100644
index 0000000..3ed0852
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/Grenades.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.fbx b/res/mdl/mega-gun-pack/Grenades/Grenades.fbx
new file mode 100644
index 0000000..b18fe94
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/Grenades.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.glb b/res/mdl/mega-gun-pack/Grenades/Grenades.glb
new file mode 100644
index 0000000..ebb30dd
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/Grenades.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.png b/res/mdl/mega-gun-pack/Grenades/Grenades.png
new file mode 100644
index 0000000..b8a0bbb
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/Grenades.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/Grenades.psd b/res/mdl/mega-gun-pack/Grenades/Grenades.psd
new file mode 100644
index 0000000..87a71d2
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/Grenades.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Grenades/screenshot.png b/res/mdl/mega-gun-pack/Grenades/screenshot.png
new file mode 100644
index 0000000..18f2188
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Grenades/screenshot.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.blend b/res/mdl/mega-gun-pack/Lugar/Lugar.blend
new file mode 100644
index 0000000..7bef264
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Lugar/Lugar.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.blend1 b/res/mdl/mega-gun-pack/Lugar/Lugar.blend1
new file mode 100644
index 0000000..4ebf5d8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Lugar/Lugar.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.fbx b/res/mdl/mega-gun-pack/Lugar/Lugar.fbx
new file mode 100644
index 0000000..4cec6d8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Lugar/Lugar.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar.glb b/res/mdl/mega-gun-pack/Lugar/Lugar.glb
new file mode 100644
index 0000000..f8c3ca9
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Lugar/Lugar.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psd b/res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psd
new file mode 100644
index 0000000..ea9b19e
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Lugar/Lugar_Psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Lugar/Lugar_tex.png b/res/mdl/mega-gun-pack/Lugar/Lugar_tex.png
new file mode 100644
index 0000000..7232b99
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Lugar/Lugar_tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/M16/M16.blend b/res/mdl/mega-gun-pack/M16/M16.blend
new file mode 100644
index 0000000..ff35d7f
--- /dev/null
+++ b/res/mdl/mega-gun-pack/M16/M16.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/M16/M16.blend1 b/res/mdl/mega-gun-pack/M16/M16.blend1
new file mode 100644
index 0000000..dbfe184
--- /dev/null
+++ b/res/mdl/mega-gun-pack/M16/M16.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/M16/M16_fbx.fbx b/res/mdl/mega-gun-pack/M16/M16_fbx.fbx
new file mode 100644
index 0000000..bce5abf
--- /dev/null
+++ b/res/mdl/mega-gun-pack/M16/M16_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/M16/M16_glb.glb b/res/mdl/mega-gun-pack/M16/M16_glb.glb
new file mode 100644
index 0000000..1ff9916
--- /dev/null
+++ b/res/mdl/mega-gun-pack/M16/M16_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/M16/m16_psd.psd b/res/mdl/mega-gun-pack/M16/m16_psd.psd
new file mode 100644
index 0000000..5a13df2
--- /dev/null
+++ b/res/mdl/mega-gun-pack/M16/m16_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/M16/m16_tex.png b/res/mdl/mega-gun-pack/M16/m16_tex.png
new file mode 100644
index 0000000..30450ff
--- /dev/null
+++ b/res/mdl/mega-gun-pack/M16/m16_tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/MP5/MP5k.blend b/res/mdl/mega-gun-pack/MP5/MP5k.blend
new file mode 100644
index 0000000..e3daf82
--- /dev/null
+++ b/res/mdl/mega-gun-pack/MP5/MP5k.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/MP5/MP5k.blend1 b/res/mdl/mega-gun-pack/MP5/MP5k.blend1
new file mode 100644
index 0000000..45149a5
--- /dev/null
+++ b/res/mdl/mega-gun-pack/MP5/MP5k.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbx b/res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbx
new file mode 100644
index 0000000..40404b8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/MP5/MP5k_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/MP5/MP5k_glb.glb b/res/mdl/mega-gun-pack/MP5/MP5k_glb.glb
new file mode 100644
index 0000000..236ea03
--- /dev/null
+++ b/res/mdl/mega-gun-pack/MP5/MP5k_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/MP5/mp5_psd.psd b/res/mdl/mega-gun-pack/MP5/mp5_psd.psd
new file mode 100644
index 0000000..e12220d
--- /dev/null
+++ b/res/mdl/mega-gun-pack/MP5/mp5_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/MP5/mp5_tex.png b/res/mdl/mega-gun-pack/MP5/mp5_tex.png
new file mode 100644
index 0000000..f91224d
--- /dev/null
+++ b/res/mdl/mega-gun-pack/MP5/mp5_tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PKM/PKM.blend b/res/mdl/mega-gun-pack/PKM/PKM.blend
new file mode 100644
index 0000000..f91c645
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PKM/PKM.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PKM/PKM.blend1 b/res/mdl/mega-gun-pack/PKM/PKM.blend1
new file mode 100644
index 0000000..f6d5bc8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PKM/PKM.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PKM/PKM.fbx b/res/mdl/mega-gun-pack/PKM/PKM.fbx
new file mode 100644
index 0000000..79db9ce
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PKM/PKM.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PKM/PKM.glb b/res/mdl/mega-gun-pack/PKM/PKM.glb
new file mode 100644
index 0000000..42fe954
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PKM/PKM.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PKM/pkm_psd.psd b/res/mdl/mega-gun-pack/PKM/pkm_psd.psd
new file mode 100644
index 0000000..e55d455
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PKM/pkm_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.blend b/res/mdl/mega-gun-pack/PPSH/PPSH.blend
new file mode 100644
index 0000000..e364e92
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PPSH/PPSH.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.blend1 b/res/mdl/mega-gun-pack/PPSH/PPSH.blend1
new file mode 100644
index 0000000..75883e8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PPSH/PPSH.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.fbx b/res/mdl/mega-gun-pack/PPSH/PPSH.fbx
new file mode 100644
index 0000000..be3732b
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PPSH/PPSH.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PPSH/PPSH.glb b/res/mdl/mega-gun-pack/PPSH/PPSH.glb
new file mode 100644
index 0000000..2f15238
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PPSH/PPSH.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PPSH/ppsh.png b/res/mdl/mega-gun-pack/PPSH/ppsh.png
new file mode 100644
index 0000000..0922a16
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PPSH/ppsh.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/PPSH/ppsh_psd.psd b/res/mdl/mega-gun-pack/PPSH/ppsh_psd.psd
new file mode 100644
index 0000000..7251fdf
--- /dev/null
+++ b/res/mdl/mega-gun-pack/PPSH/ppsh_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/RPG/RPG.blend b/res/mdl/mega-gun-pack/RPG/RPG.blend
new file mode 100644
index 0000000..4413e67
--- /dev/null
+++ b/res/mdl/mega-gun-pack/RPG/RPG.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/RPG/RPG.blend1 b/res/mdl/mega-gun-pack/RPG/RPG.blend1
new file mode 100644
index 0000000..ca34267
--- /dev/null
+++ b/res/mdl/mega-gun-pack/RPG/RPG.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/RPG/RPG.fbx b/res/mdl/mega-gun-pack/RPG/RPG.fbx
new file mode 100644
index 0000000..90fca8c
--- /dev/null
+++ b/res/mdl/mega-gun-pack/RPG/RPG.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/RPG/RPG.glb b/res/mdl/mega-gun-pack/RPG/RPG.glb
new file mode 100644
index 0000000..7e7c85f
--- /dev/null
+++ b/res/mdl/mega-gun-pack/RPG/RPG.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/RPG/rpg.png b/res/mdl/mega-gun-pack/RPG/rpg.png
new file mode 100644
index 0000000..4724799
--- /dev/null
+++ b/res/mdl/mega-gun-pack/RPG/rpg.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/RPG/rpg_psd.psd b/res/mdl/mega-gun-pack/RPG/rpg_psd.psd
new file mode 100644
index 0000000..a0c6409
--- /dev/null
+++ b/res/mdl/mega-gun-pack/RPG/rpg_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver.blend b/res/mdl/mega-gun-pack/Revolver/Revolver.blend
new file mode 100644
index 0000000..5b9cf51
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver.blend1 b/res/mdl/mega-gun-pack/Revolver/Revolver.blend1
new file mode 100644
index 0000000..3be7919
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver.png b/res/mdl/mega-gun-pack/Revolver/Revolver.png
new file mode 100644
index 0000000..2b042ca
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbx b/res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbx
new file mode 100644
index 0000000..fb3dc43
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_glb.glb b/res/mdl/mega-gun-pack/Revolver/Revolver_glb.glb
new file mode 100644
index 0000000..f7ef1b8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_psd.psd b/res/mdl/mega-gun-pack/Revolver/Revolver_psd.psd
new file mode 100644
index 0000000..ff553a0
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.png b/res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.png
new file mode 100644
index 0000000..f5f65b8
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Revolver/Revolver_screenshot.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.blend b/res/mdl/mega-gun-pack/Rifle/Rifle.blend
new file mode 100644
index 0000000..9f25d9a
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/Rifle.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.blend1 b/res/mdl/mega-gun-pack/Rifle/Rifle.blend1
new file mode 100644
index 0000000..6301baa
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/Rifle.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.fbx b/res/mdl/mega-gun-pack/Rifle/Rifle.fbx
new file mode 100644
index 0000000..3a83012
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/Rifle.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.glb b/res/mdl/mega-gun-pack/Rifle/Rifle.glb
new file mode 100644
index 0000000..7ec08b6
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/Rifle.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle.png b/res/mdl/mega-gun-pack/Rifle/Rifle.png
new file mode 100644
index 0000000..fab3dfe
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/Rifle.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psd b/res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psd
new file mode 100644
index 0000000..aae5450
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/Rifle_PSD.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Rifle/scope_fbx.fbx b/res/mdl/mega-gun-pack/Rifle/scope_fbx.fbx
new file mode 100644
index 0000000..1ee1735
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Rifle/scope_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR.blend b/res/mdl/mega-gun-pack/SCAR/SCAR.blend
new file mode 100644
index 0000000..a169333
--- /dev/null
+++ b/res/mdl/mega-gun-pack/SCAR/SCAR.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR.blend1 b/res/mdl/mega-gun-pack/SCAR/SCAR.blend1
new file mode 100644
index 0000000..c672d12
--- /dev/null
+++ b/res/mdl/mega-gun-pack/SCAR/SCAR.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR.png b/res/mdl/mega-gun-pack/SCAR/SCAR.png
new file mode 100644
index 0000000..194f379
--- /dev/null
+++ b/res/mdl/mega-gun-pack/SCAR/SCAR.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psd b/res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psd
new file mode 100644
index 0000000..55888e6
--- /dev/null
+++ b/res/mdl/mega-gun-pack/SCAR/SCAR_PSD.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbx b/res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbx
new file mode 100644
index 0000000..653c849
--- /dev/null
+++ b/res/mdl/mega-gun-pack/SCAR/SCAR_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/SCAR/SCAR_glb.glb b/res/mdl/mega-gun-pack/SCAR/SCAR_glb.glb
new file mode 100644
index 0000000..36be5b1
--- /dev/null
+++ b/res/mdl/mega-gun-pack/SCAR/SCAR_glb.glb
Binary files differ
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
new file mode 100644
index 0000000..6f0df84
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend
Binary files differ
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
new file mode 100644
index 0000000..0858a8f
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel.blend1
Binary files differ
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
new file mode 100644
index 0000000..5f7215b
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_fbx.fbx
Binary files differ
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
new file mode 100644
index 0000000..2de4f64
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Double_Barrel_glb.glb
Binary files differ
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
new file mode 100644
index 0000000..a2aedae
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun.psd
Binary files differ
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
new file mode 100644
index 0000000..450f084
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Sawed Off Double Barrel/Shotgun_tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend
new file mode 100644
index 0000000..df0274c
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1 b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1
new file mode 100644
index 0000000..2aee3fc
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.fbx b/res/mdl/mega-gun-pack/Skorpion/Skorpion.fbx
new file mode 100644
index 0000000..a98905f
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.glb b/res/mdl/mega-gun-pack/Skorpion/Skorpion.glb
new file mode 100644
index 0000000..2a0d883
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.png b/res/mdl/mega-gun-pack/Skorpion/Skorpion.png
new file mode 100644
index 0000000..2f9e1f3
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Skorpion/Skorpion.psd b/res/mdl/mega-gun-pack/Skorpion/Skorpion.psd
new file mode 100644
index 0000000..9240195
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Skorpion/Skorpion.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas.blend b/res/mdl/mega-gun-pack/Spas/Spas.blend
new file mode 100644
index 0000000..46fc9bf
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas.blend1 b/res/mdl/mega-gun-pack/Spas/Spas.blend1
new file mode 100644
index 0000000..648d053
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas.fbx b/res/mdl/mega-gun-pack/Spas/Spas.fbx
new file mode 100644
index 0000000..1674c4e
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas_PSD.psd b/res/mdl/mega-gun-pack/Spas/Spas_PSD.psd
new file mode 100644
index 0000000..3a0c0ee
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas_PSD.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas_fbx.fbx b/res/mdl/mega-gun-pack/Spas/Spas_fbx.fbx
new file mode 100644
index 0000000..4db7c4b
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas_glb.glb b/res/mdl/mega-gun-pack/Spas/Spas_glb.glb
new file mode 100644
index 0000000..c1eb678
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Spas/Spas_tex.png b/res/mdl/mega-gun-pack/Spas/Spas_tex.png
new file mode 100644
index 0000000..4291349
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Spas/Spas_tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi.blend b/res/mdl/mega-gun-pack/Uzi/Uzi.blend
new file mode 100644
index 0000000..e796fd6
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Uzi/Uzi.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi.blend1 b/res/mdl/mega-gun-pack/Uzi/Uzi.blend1
new file mode 100644
index 0000000..574b9cf
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Uzi/Uzi.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psd b/res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psd
new file mode 100644
index 0000000..1114563
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Uzi/Uzi_PSD.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_Tex.png b/res/mdl/mega-gun-pack/Uzi/Uzi_Tex.png
new file mode 100644
index 0000000..db6bd3b
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Uzi/Uzi_Tex.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbx b/res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbx
new file mode 100644
index 0000000..ce7fb39
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Uzi/Uzi_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/Uzi/Uzi_glb.glb b/res/mdl/mega-gun-pack/Uzi/Uzi_glb.glb
new file mode 100644
index 0000000..8a4fa06
--- /dev/null
+++ b/res/mdl/mega-gun-pack/Uzi/Uzi_glb.glb
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/P90.png b/res/mdl/mega-gun-pack/p90/P90.png
new file mode 100644
index 0000000..b971c42
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/P90.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/P902.png b/res/mdl/mega-gun-pack/p90/P902.png
new file mode 100644
index 0000000..289c4ea
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/P902.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/P90_psd.psd b/res/mdl/mega-gun-pack/p90/P90_psd.psd
new file mode 100644
index 0000000..729f11b
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/P90_psd.psd
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/P90_uv.png b/res/mdl/mega-gun-pack/p90/P90_uv.png
new file mode 100644
index 0000000..98ec6ba
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/P90_uv.png
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/p90.blend b/res/mdl/mega-gun-pack/p90/p90.blend
new file mode 100644
index 0000000..836ed47
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/p90.blend
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/p90.blend1 b/res/mdl/mega-gun-pack/p90/p90.blend1
new file mode 100644
index 0000000..6512750
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/p90.blend1
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/p90_fbx.fbx b/res/mdl/mega-gun-pack/p90/p90_fbx.fbx
new file mode 100644
index 0000000..fe9298d
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/p90_fbx.fbx
Binary files differ
diff --git a/res/mdl/mega-gun-pack/p90/p90_glb.glb b/res/mdl/mega-gun-pack/p90/p90_glb.glb
new file mode 100644
index 0000000..b2f2503
--- /dev/null
+++ b/res/mdl/mega-gun-pack/p90/p90_glb.glb
Binary files differ
diff --git a/res/mdl/pkm.glb b/res/mdl/pkm.glb
new file mode 100755
index 0000000..b76babf
--- /dev/null
+++ b/res/mdl/pkm.glb
Binary files differ
diff --git a/res/mdl/rpg7.glb b/res/mdl/rpg7.glb
new file mode 100755
index 0000000..bb54370
--- /dev/null
+++ b/res/mdl/rpg7.glb
Binary files differ
diff --git a/res/mdl/soldier.glb b/res/mdl/soldier.glb
new file mode 100644
index 0000000..375655e
--- /dev/null
+++ b/res/mdl/soldier.glb
Binary files differ
diff --git a/res/mdl/t55_tank.glb b/res/mdl/t55_tank.glb
new file mode 100755
index 0000000..50836da
--- /dev/null
+++ b/res/mdl/t55_tank.glb
Binary files differ
diff --git a/res/mdl/tank.glb b/res/mdl/tank.glb
new file mode 100755
index 0000000..9e25003
--- /dev/null
+++ b/res/mdl/tank.glb
Binary files differ
diff --git a/res/mdl/tree-pack/tree01.glb b/res/mdl/tree-pack/tree01.glb
new file mode 100644
index 0000000..4663b98
--- /dev/null
+++ b/res/mdl/tree-pack/tree01.glb
Binary files differ
diff --git a/res/mdl/tree-pack/tree02.glb b/res/mdl/tree-pack/tree02.glb
new file mode 100644
index 0000000..0652bfb
--- /dev/null
+++ b/res/mdl/tree-pack/tree02.glb
Binary files differ
diff --git a/res/save/scene.fgs b/res/save/scene.fgs
index 191cfe7..23f4927 100644
--- a/res/save/scene.fgs
+++ b/res/save/scene.fgs
Binary files differ
diff --git a/save.c b/save.c
index 9d46fe3..db906a2 100644
--- a/save.c
+++ b/save.c
@@ -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;
}
diff --git a/save.h b/save.h
index d7e2be4..be79d0d 100644
--- a/save.h
+++ b/save.h
@@ -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);