diff --git a/examples/Makefile b/examples/Makefile index 235deda28..01458c6bc 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -585,7 +585,8 @@ OTHERS = \ others/raylib_opengl_interop \ others/raymath_vector_angle \ others/rlgl_compute_shader \ - others/rlgl_standalone + others/rlgl_standalone \ + others/gizmo CURRENT_MAKEFILE = $(lastword $(MAKEFILE_LIST)) diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 8bec06452..d7dbffcb5 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -491,7 +491,8 @@ OTHERS = \ others/raylib_opengl_interop \ others/raymath_vector_angle \ others/rlgl_compute_shader \ - others/rlgl_standalone + others/rlgl_standalone \ + others/gizmo CURRENT_MAKEFILE = $(lastword $(MAKEFILE_LIST)) @@ -1099,6 +1100,9 @@ others/rlgl_compute_shader: others/rlgl_standalone: $(info Skipping_others_rlgl_standalone) +others/gizmo: others/gizmo.c + $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + # Clean everything clean: ifeq ($(PLATFORM),PLATFORM_DESKTOP) diff --git a/examples/README.md b/examples/README.md index 0fed8acd0..dcbdd6e8a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -200,6 +200,7 @@ Examples showing raylib misc functionality that does not fit in other categories | 124 | [easings_testbed](others/easings_testbed.c) | easings_testbed | ⭐️⭐️⭐️☆ | 3.0 | 3.0 | [Juan Miguel López](https://github.com/flashback-fx) | | 125 | [raylib_opengl_interop](others/raylib_opengl_interop.c) | raylib_opengl_interop | ⭐️⭐️⭐️⭐️ | **4.0** | **4.0** | [Stephan Soller](https://github.com/arkanis) | | 126 | [embedded_files_loading](others/embedded_files_loading.c) | embedded_files_loading | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Kristian Holmgren](https://github.com/defutura) | +| 127 | [gizmo](others/gizmo.c) | gizmo | ⭐️⭐️☆☆ | 5.0 | 5.0 | [Alexey Karnachev](https://github.com/alexeykarnachev) | As always contributions are welcome, feel free to send new examples! Here it is an [examples template](examples_template.c) to start with! diff --git a/examples/others/gizmo.c b/examples/others/gizmo.c new file mode 100644 index 000000000..13729b6b0 --- /dev/null +++ b/examples/others/gizmo.c @@ -0,0 +1,113 @@ +#include "raylib.h" +#include "rcamera.h" + +#define RAYGIZMO_IMPLEMENTATION +#include "gizmo.h" +#undef RAYGIZMO_IMPLEMENTATION + +void updateCamera(Camera3D* camera) { + float rot_speed = 0.003f; + float move_speed = 0.01f; + float zoom_speed = 1.0f; + + bool is_middle = IsMouseButtonDown(2); + bool is_shift = IsKeyDown(KEY_LEFT_SHIFT); + Vector2 mouse_delta = GetMouseDelta(); + + if (is_middle && is_shift) { + CameraMoveRight(camera, -move_speed * mouse_delta.x, true); + + Vector3 right = GetCameraRight(camera); + Vector3 up = Vector3CrossProduct( + Vector3Subtract(camera->position, camera->target), right + ); + up = Vector3Scale(Vector3Normalize(up), move_speed * mouse_delta.y); + camera->position = Vector3Add(camera->position, up); + camera->target = Vector3Add(camera->target, up); + } else if (is_middle) { + CameraYaw(camera, -rot_speed * mouse_delta.x, true); + CameraPitch(camera, rot_speed * mouse_delta.y, true, true, false); + } + + CameraMoveToTarget(camera, -GetMouseWheelMove() * zoom_speed); +} + +int main(void) { + const int screen_width = 800; + const int screen_height = 450; + InitWindow(screen_width, screen_height, "Gizmo"); + SetTargetFPS(60); + + Camera3D camera; + camera.fovy = 45.0f; + camera.target = (Vector3){0.0f, 0.0f, 0.0f}; + camera.position = (Vector3){5.0f, 5.0f, 5.0f}; + camera.up = (Vector3){0.0f, 1.0f, 0.0f}; + camera.projection = CAMERA_PERSPECTIVE; + + Model model = LoadModelFromMesh(GenMeshTorus(0.3, 1.5, 16.0, 16.0)); + + LoadGizmo(); + + while (!WindowShouldClose()) { + updateCamera(&camera); + + BeginDrawing(); + { + ClearBackground(DARKGRAY); + rlEnableDepthTest(); + + BeginMode3D(camera); + { + // Draw main model + DrawModel(model, (Vector3){0.0, 0.0, 0.0}, 1.0, PURPLE); + + // Draw coordinates grid + rlSetLineWidth(1.0); + DrawGrid(100.0, 1.0); + + // Draw coordinate x, y and z axis + rlSetLineWidth(2.0); + DrawLine3D( + (Vector3){-50.0f, 0.0f, 0.0f}, + (Vector3){50.0f, 0.0f, 0.0f}, + RED + ); + DrawLine3D( + (Vector3){0.0f, -50.0f, 0.0f}, + (Vector3){0.0f, 50.0f, 0.0f}, + GREEN + ); + DrawLine3D( + (Vector3){0.0f, 0.0f, -50.0f}, + (Vector3){0.0f, 0.0f, 50.0f}, + DARKBLUE + ); + } + EndMode3D(); + + // Immediately update and draw gizmo + Vector3 position = { + model.transform.m12, model.transform.m13, model.transform.m14}; + Matrix transform = UpdateGizmo(camera, position); + + // Apply gizmo-produced transformation to the model + model.transform = MatrixMultiply(model.transform, transform); + + // Draw camera controll keys + DrawRectangle(0, 0, 280, 90, RAYWHITE); + DrawText("CAMERA:", 5, 5, 20, RED); + DrawText(" zoom: wheel", 5, 25, 20, RED); + DrawText(" rotate: mmb", 5, 45, 20, RED); + DrawText(" translate: shift + mmb", 5, 65, 20, RED); + } + EndDrawing(); + } + + UnloadGizmo(); + UnloadModel(model); + CloseWindow(); + + return 0; +} + diff --git a/examples/others/gizmo.h b/examples/others/gizmo.h new file mode 100644 index 000000000..c7348f67b --- /dev/null +++ b/examples/others/gizmo.h @@ -0,0 +1,656 @@ +#include +#include + +void LoadGizmo(); +void UnloadGizmo(); +Matrix UpdateGizmo(Camera3D camera, Vector3 position); + +#ifdef RAYGIZMO_IMPLEMENTATION + +#include "raymath.h" +#include +#include +#include +#include + +// This will be multiplied by the distance from the camera to the gizmo, +// Which keeps the screen-space gizmo size constant. +#define GIZMO_SIZE 0.12f + +// Line drawing thicknesses +#define GIZMO_HANDLE_DRAW_THICKNESS 5.0f +#define GIZMO_ACTIVE_AXIS_DRAW_THICKNESS 2.0f + +// These sizes are relative to the gizmo radius +#define GIZMO_AXIS_HANDLE_LENGTH 1.2; +#define GIZMO_AXIS_HANDLE_TIP_LENGTH 0.3; +#define GIZMO_AXIS_HANDLE_TIP_RADIUS 0.1; +#define GIZMO_PLANE_HANDLE_OFFSET 0.4; +#define GIZMO_PLANE_HANDLE_SIZE 0.2; + +#define X_AXIS \ + (Vector3) { 1.0, 0.0, 0.0 } +#define Y_AXIS \ + (Vector3) { 0.0, 1.0, 0.0 } +#define Z_AXIS \ + (Vector3) { 0.0, 0.0, 1.0 } + +#define MASK_FRAMEBUFFER_WIDTH 500.0 +#define MASK_FRAMEBUFFER_HEIGHT 500.0 + +#define swap(x, y) \ + do { \ + unsigned char \ + swap_temp[sizeof(x) == sizeof(y) ? (signed)sizeof(x) : -1]; \ + memcpy(swap_temp, &y, sizeof(x)); \ + memcpy(&y, &x, sizeof(x)); \ + memcpy(&x, swap_temp, sizeof(x)); \ + } while (0) + +#define id_to_red_color(id) \ + (Color) { id, 0, 0, 0 } + +static const char* SHADER_COLOR_VERT = "\ +#version 330\n\ +in vec3 vertexPosition; \ +in vec4 vertexColor; \ +out vec4 fragColor; \ +out vec3 fragPosition; \ +uniform mat4 mvp; \ +void main() { \ + fragColor = vertexColor; \ + fragPosition = vertexPosition; \ + gl_Position = mvp * vec4(vertexPosition, 1.0); \ +} \ +"; + +static const char* SHADER_ROT_HANDLE_COLOR_FRAG = "\ +#version 330\n\ +in vec4 fragColor; \ +in vec3 fragPosition; \ +uniform vec3 cameraPosition; \ +uniform vec3 gizmoPosition; \ +out vec4 finalColor; \ +void main() { \ + vec3 r = normalize(fragPosition - gizmoPosition); \ + vec3 c = normalize(fragPosition - cameraPosition); \ + if (dot(r, c) > 0.1) discard; \ + finalColor = fragColor; \ +} \ +"; + +static Shader SHADER_ROT_HANDLE_COLOR; +static int SHADER_ROT_HANDLE_CAMERA_POS_LOC; +static int SHADER_ROT_HANDLE_GIZMO_POS_LOC; + +static unsigned int MASK_FRAMEBUFFER; +static unsigned int MASK_TEXTURE; + +static bool IS_GIZMO_LOADED; +static Vector3 GIZMO_CURRENT_AXIS; + +typedef enum HandleId { + HANDLE_X, + + ROT_HANDLE_X, + AXIS_HANDLE_X, + PLANE_HANDLE_X, + + HANDLE_Y, + + ROT_HANDLE_Y, + AXIS_HANDLE_Y, + PLANE_HANDLE_Y, + + HANDLE_Z, + + ROT_HANDLE_Z, + AXIS_HANDLE_Z, + PLANE_HANDLE_Z +} HandleId; + +typedef enum GizmoState { + GIZMO_COLD, + + GIZMO_HOT, + + GIZMO_HOT_ROT, + GIZMO_HOT_AXIS, + GIZMO_HOT_PLANE, + + GIZMO_ACTIVE, + + GIZMO_ACTIVE_ROT, + GIZMO_ACTIVE_AXIS, + GIZMO_ACTIVE_PLANE, +} GizmoState; + +typedef struct Handle { + Vector3 position; + Vector3 axis; + Color color; + float dist_to_cam; +} Handle; + +typedef struct HandleColors { + Color x; + Color y; + Color z; +} HandleColors; + +typedef struct Handles { + Handle arr[3]; +} Handles; + +static GizmoState GIZMO_STATE; + +bool check_if_mouse_moved() { + Vector2 mouse_delta = GetMouseDelta(); + return (fabs(mouse_delta.x) + fabs(mouse_delta.y)) > EPSILON; +} + +static float vector2_get_angle(Vector2 v1, Vector2 v2) { + Vector2 v1_norm = Vector2Normalize(v1); + Vector2 v2_norm = Vector2Normalize(v2); + float dot = Vector2DotProduct(v1_norm, v2_norm); + if (1.0 - fabs(dot) < EPSILON) return 0.0; + + float angle = acos(dot); + float z = v1.x * v2.y - v1.y * v2.x; + + if (fabs(z) < EPSILON) return 0.0; + else if (z > 0) return angle; + else return -angle; +} + +static int isect_line_plane( + Vector3* out_p, + Vector3 line_p0, + Vector3 line_p1, + Vector3 plane_p, + Vector3 plane_normal +) { + Vector3 u = Vector3Subtract(line_p1, line_p0); + float dot = Vector3DotProduct(plane_normal, u); + if (fabs(dot) <= EPSILON) { + return 0; + } + + Vector3 w = Vector3Subtract(line_p0, plane_p); + float k = -Vector3DotProduct(plane_normal, w) / dot; + u = Vector3Scale(u, k); + *out_p = Vector3Add(line_p0, u); + return 1; +} + +static int get_two_vecs_nearest_point( + Vector3* vec0_out_nearest_point, + Vector3 vec0_p0, + Vector3 vec0_p1, + Vector3 vec1_p0, + Vector3 vec1_p1 +) { + Vector3 vec0 = Vector3Subtract(vec0_p1, vec0_p0); + Vector3 vec1 = Vector3Subtract(vec1_p1, vec1_p0); + Vector3 plane_vec = Vector3CrossProduct(vec0, vec1); + plane_vec = Vector3Normalize(plane_vec); + Vector3 plane_normal = Vector3CrossProduct(vec0, plane_vec); + plane_normal = Vector3Normalize(plane_normal); + + int is_isect = isect_line_plane( + vec0_out_nearest_point, vec1_p0, vec1_p1, vec0_p0, plane_normal + ); + + return is_isect; +} + +static RayCollision get_ray_plane_collision( + Ray ray, Vector3 plane_point, Vector3 plane_normal +) { + RayCollision collision = {0}; + + // Calculate the parameter t + float denominator = ray.direction.x * plane_normal.x + + ray.direction.y * plane_normal.y + + ray.direction.z * plane_normal.z; + + if (denominator == 0) { + // Ray is parallel to the plane, no collision + return collision; + } + + float t = ((plane_point.x - ray.position.x) * plane_normal.x + + (plane_point.y - ray.position.y) * plane_normal.y + + (plane_point.z - ray.position.z) * plane_normal.z) + / denominator; + + if (t < 0) { + // Intersection point is behind the ray's starting point, no collision + return collision; + } + + // Calculate the collision point + collision.point.x = ray.position.x + t * ray.direction.x; + collision.point.y = ray.position.y + t * ray.direction.y; + collision.point.z = ray.position.z + t * ray.direction.z; + collision.hit = true; + + return collision; +} + +static Handles get_sorted_handles(Handle h0, Handle h1, Handle h2) { + if (h0.dist_to_cam < h1.dist_to_cam) swap(h0, h1); + if (h1.dist_to_cam < h2.dist_to_cam) swap(h1, h2); + if (h0.dist_to_cam < h1.dist_to_cam) swap(h0, h1); + + Handles handles = {.arr = {h0, h1, h2}}; + return handles; +} + +static HandleColors get_handle_colors(GizmoState hot_state) { + bool is_hot = GIZMO_STATE == hot_state || GIZMO_STATE == hot_state + 4; + Color x = is_hot && GIZMO_CURRENT_AXIS.x == 1.0 ? WHITE : RED; + Color y = is_hot && GIZMO_CURRENT_AXIS.y == 1.0 ? WHITE : GREEN; + Color z = is_hot && GIZMO_CURRENT_AXIS.z == 1.0 ? WHITE : BLUE; + HandleColors colors = {x, y, z}; + return colors; +} + +static void draw_axis_handles( + Camera3D camera, + Vector3 position, + float gizmo_radius, + Color color_x, + Color color_y, + Color color_z +) { + float length = gizmo_radius * GIZMO_AXIS_HANDLE_LENGTH; + float tip_length = gizmo_radius * GIZMO_AXIS_HANDLE_TIP_LENGTH; + float tip_radius = gizmo_radius * GIZMO_AXIS_HANDLE_TIP_RADIUS; + + Vector3 px = Vector3Add(position, Vector3Scale(X_AXIS, length)); + Vector3 py = Vector3Add(position, Vector3Scale(Y_AXIS, length)); + Vector3 pz = Vector3Add(position, Vector3Scale(Z_AXIS, length)); + + Handle hx = {px, X_AXIS, color_x, Vector3DistanceSqr(px, camera.position)}; + Handle hy = {py, Y_AXIS, color_y, Vector3DistanceSqr(py, camera.position)}; + Handle hz = {pz, Z_AXIS, color_z, Vector3DistanceSqr(pz, camera.position)}; + + Handles handles = get_sorted_handles(hx, hy, hz); + + for (int i = 0; i < 3; ++i) { + Handle* h = &handles.arr[i]; + Vector3 tip_end = Vector3Add( + h->position, Vector3Scale(h->axis, tip_length) + ); + DrawLine3D(position, h->position, h->color); + DrawCylinderEx(h->position, tip_end, tip_radius, 0.0, 16, h->color); + } +} + +static void draw_plane_handles( + Camera3D camera, + Vector3 position, + float gizmo_radius, + Color color_x, + Color color_y, + Color color_z +) { + float offset = gizmo_radius * GIZMO_PLANE_HANDLE_OFFSET; + float size = gizmo_radius * GIZMO_PLANE_HANDLE_SIZE; + + Vector3 px = Vector3Add(position, (Vector3){0.0, offset, offset}); + Vector3 py = Vector3Add(position, (Vector3){offset, 0.0, offset}); + Vector3 pz = Vector3Add(position, (Vector3){offset, offset, 0.0}); + + Handle hx = {px, Z_AXIS, color_x, Vector3DistanceSqr(px, camera.position)}; + Handle hy = {py, Y_AXIS, color_y, Vector3DistanceSqr(py, camera.position)}; + Handle hz = {pz, X_AXIS, color_z, Vector3DistanceSqr(pz, camera.position)}; + + Handles handles = get_sorted_handles(hx, hy, hz); + + rlDisableBackfaceCulling(); + for (int i = 0; i < 3; ++i) { + Handle* h = &handles.arr[i]; + rlPushMatrix(); + { + rlTranslatef(h->position.x, h->position.y, h->position.z); + rlRotatef(90.0, h->axis.x, h->axis.y, h->axis.z); + DrawPlane( + Vector3Zero(), Vector2Scale(Vector2One(), size), h->color + ); + } + rlPopMatrix(); + } +} + +static void draw_rot_handles( + Camera3D camera, + Vector3 position, + float gizmo_radius, + Color color_x, + Color color_y, + Color color_z +) { + BeginShaderMode(SHADER_ROT_HANDLE_COLOR); + { + SetShaderValue( + SHADER_ROT_HANDLE_COLOR, + SHADER_ROT_HANDLE_CAMERA_POS_LOC, + &camera.position, + SHADER_UNIFORM_VEC3 + ); + SetShaderValue( + SHADER_ROT_HANDLE_COLOR, + SHADER_ROT_HANDLE_GIZMO_POS_LOC, + &position, + SHADER_UNIFORM_VEC3 + ); + DrawCircle3D(position, gizmo_radius, Y_AXIS, 90.0, color_x); + DrawCircle3D(position, gizmo_radius, X_AXIS, 90.0, color_y); + DrawCircle3D(position, gizmo_radius, X_AXIS, 0.0, color_z); + } + EndShaderMode(); +} + +static void draw_gizmo( + Camera3D camera, + Vector3 position, + Color rot_handle_color_x, + Color rot_handle_color_y, + Color rot_handle_color_z, + Color axis_handle_color_x, + Color axis_handle_color_y, + Color axis_handle_color_z, + Color plane_handle_color_x, + Color plane_handle_color_y, + Color plane_handle_color_z +) { + float radius = GIZMO_SIZE * Vector3Distance(camera.position, position); + + // Draw gizmo's handle elements + BeginMode3D(camera); + { + rlSetLineWidth(GIZMO_HANDLE_DRAW_THICKNESS); + rlDisableDepthTest(); + + draw_plane_handles( + camera, + position, + radius, + plane_handle_color_x, + plane_handle_color_y, + plane_handle_color_z + ); + + draw_rot_handles( + camera, + position, + radius, + rot_handle_color_x, + rot_handle_color_y, + rot_handle_color_z + ); + + draw_axis_handles( + camera, + position, + radius, + axis_handle_color_x, + axis_handle_color_y, + axis_handle_color_z + ); + } + EndMode3D(); + + // Draw long white line which represents current active axis + if (GIZMO_STATE == GIZMO_ACTIVE_ROT || GIZMO_STATE == GIZMO_ACTIVE_AXIS) { + BeginMode3D(camera); + { + rlSetLineWidth(GIZMO_ACTIVE_AXIS_DRAW_THICKNESS); + Vector3 half_axis_line = Vector3Scale(GIZMO_CURRENT_AXIS, 1000.0); + DrawLine3D( + Vector3Subtract(position, half_axis_line), + Vector3Add(position, half_axis_line), + WHITE + ); + } + EndMode3D(); + } + + // Draw white line from the gizmo's center to the mouse cursor when rotating + if (GIZMO_STATE == GIZMO_ACTIVE_ROT) { + rlSetLineWidth(GIZMO_ACTIVE_AXIS_DRAW_THICKNESS); + DrawLineV( + GetWorldToScreen(position, camera), GetMousePosition(), WHITE + ); + } +} + +static unsigned char get_gizmo_mask_pixel(Camera3D camera, Vector3 position) { + // ------------------------------------------------------------------- + // Draw gizmo into a separate framebuffer for the mouse pixel-picking + rlEnableFramebuffer(MASK_FRAMEBUFFER); + rlViewport(0, 0, MASK_FRAMEBUFFER_WIDTH, MASK_FRAMEBUFFER_HEIGHT); + rlClearScreenBuffers(); + rlDisableColorBlend(); + + draw_gizmo( + camera, + position, + id_to_red_color(ROT_HANDLE_X), + id_to_red_color(ROT_HANDLE_Y), + id_to_red_color(ROT_HANDLE_Z), + id_to_red_color(AXIS_HANDLE_X), + id_to_red_color(AXIS_HANDLE_Y), + id_to_red_color(AXIS_HANDLE_Z), + id_to_red_color(PLANE_HANDLE_X), + id_to_red_color(PLANE_HANDLE_Y), + id_to_red_color(PLANE_HANDLE_Z) + ); + + rlDisableFramebuffer(); + rlEnableColorBlend(); + rlViewport(0, 0, GetScreenWidth(), GetScreenHeight()); + + // ------------------------------------------------------------------- + // Pick the pixel under the mouse cursor + Vector2 mouse_position = GetMousePosition(); + unsigned char* pixels = (unsigned char*)rlReadTexturePixels( + MASK_TEXTURE, + MASK_FRAMEBUFFER_WIDTH, + MASK_FRAMEBUFFER_HEIGHT, + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + ); + + float x_ratio = Clamp(mouse_position.x / (float)GetScreenWidth(), 0.0, 1.0); + float y_ratio = Clamp( + 1.0 - (mouse_position.y / (float)GetScreenHeight()), 0.0, 1.0 + ); + int x = (int)(MASK_FRAMEBUFFER_WIDTH * x_ratio); + int y = (int)(MASK_FRAMEBUFFER_HEIGHT * y_ratio); + int idx = 4 * (y * MASK_FRAMEBUFFER_WIDTH + x); + unsigned char mask_val = pixels[idx]; + + free(pixels); + return mask_val; +} + +Matrix update_gizmo_rot(Camera3D camera, Vector3 position) { + if (!check_if_mouse_moved()) return MatrixIdentity(); + + Vector2 rot_center = GetWorldToScreen(position, camera); + Vector2 p1 = Vector2Subtract(GetMousePosition(), rot_center); + Vector2 p0 = Vector2Subtract(p1, GetMouseDelta()); + float angle = vector2_get_angle(p1, p0); + + // If we look at gizmo from behind, we should flip the rotation + if (Vector3DotProduct(GIZMO_CURRENT_AXIS, position) + > Vector3DotProduct(GIZMO_CURRENT_AXIS, camera.position)) { + angle *= -1; + } + + // We rotate gizmo relative to it's own center, so we first translate it to + // the center of world coordinates, then rotate and then translate back + Matrix transform = MatrixMultiply( + MatrixMultiply( + MatrixTranslate(-position.x, -position.y, -position.z), + MatrixRotate(GIZMO_CURRENT_AXIS, angle) + ), + MatrixTranslate(position.x, position.y, position.z) + ); + + return transform; +} + +Matrix update_gizmo_axis(Camera3D camera, Vector3 position) { + if (!check_if_mouse_moved()) return MatrixIdentity(); + + Vector2 p = Vector2Add(GetWorldToScreen(position, camera), GetMouseDelta()); + Ray r = GetMouseRay(p, camera); + Vector3 isect_p = Vector3Zero(); + bool is_isect = get_two_vecs_nearest_point( + &isect_p, + camera.position, + Vector3Add(camera.position, r.direction), + position, + Vector3Add(position, GIZMO_CURRENT_AXIS) + ); + + if (!is_isect) + { + return MatrixIdentity(); + } + + Vector3 offset = Vector3Subtract(isect_p, position); + offset = Vector3Multiply(offset, GIZMO_CURRENT_AXIS); + Matrix transform = MatrixTranslate(offset.x, offset.y, offset.z); + + return transform; +} + +Matrix update_gizmo_plane(Camera3D camera, Vector3 position) { + if (!check_if_mouse_moved()) return MatrixIdentity(); + Vector2 p = Vector2Add(GetWorldToScreen(position, camera), GetMouseDelta()); + Ray r = GetMouseRay(p, camera); + RayCollision c = get_ray_plane_collision(r, position, GIZMO_CURRENT_AXIS); + Vector3 offset = Vector3Subtract(c.point, position); + Matrix transform = MatrixTranslate(offset.x, offset.y, offset.z); + + return transform; +} + +Matrix update_gizmo(Camera3D camera, Vector3 position, unsigned char mask_val) { + bool is_lmb_down = IsMouseButtonDown(0); + + if (!is_lmb_down) GIZMO_STATE = GIZMO_COLD; + + if (GIZMO_STATE < GIZMO_ACTIVE) { + if (mask_val < HANDLE_Y) GIZMO_CURRENT_AXIS = X_AXIS; + else if (mask_val < HANDLE_Z) GIZMO_CURRENT_AXIS = Y_AXIS; + else GIZMO_CURRENT_AXIS = Z_AXIS; + + if (mask_val % 4 == 1) + GIZMO_STATE = is_lmb_down ? GIZMO_ACTIVE_ROT : GIZMO_HOT_ROT; + else if (mask_val % 4 == 2) + GIZMO_STATE = is_lmb_down ? GIZMO_ACTIVE_AXIS : GIZMO_HOT_AXIS; + else if (mask_val % 4 == 3) + GIZMO_STATE = is_lmb_down ? GIZMO_ACTIVE_PLANE : GIZMO_HOT_PLANE; + } + + switch (GIZMO_STATE) { + case GIZMO_ACTIVE_ROT: return update_gizmo_rot(camera, position); + case GIZMO_ACTIVE_AXIS: return update_gizmo_axis(camera, position); + case GIZMO_ACTIVE_PLANE: return update_gizmo_plane(camera, position); + default: return MatrixIdentity(); + } +} + +void LoadGizmo() { + if (IS_GIZMO_LOADED) return; + + SHADER_ROT_HANDLE_COLOR = LoadShaderFromMemory( + SHADER_COLOR_VERT, SHADER_ROT_HANDLE_COLOR_FRAG + ); + SHADER_ROT_HANDLE_CAMERA_POS_LOC = GetShaderLocation( + SHADER_ROT_HANDLE_COLOR, "cameraPosition" + ); + SHADER_ROT_HANDLE_GIZMO_POS_LOC = GetShaderLocation( + SHADER_ROT_HANDLE_COLOR, "gizmoPosition" + ); + + MASK_FRAMEBUFFER = rlLoadFramebuffer( + MASK_FRAMEBUFFER_WIDTH, MASK_FRAMEBUFFER_HEIGHT + ); + if (!MASK_FRAMEBUFFER) { + TraceLog(LOG_ERROR, "Failed to create gizmo's mask framebuffer"); + exit(1); + } + rlEnableFramebuffer(MASK_FRAMEBUFFER); + + MASK_TEXTURE = rlLoadTexture( + NULL, + MASK_FRAMEBUFFER_WIDTH, + MASK_FRAMEBUFFER_HEIGHT, + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, + 1 + ); + rlActiveDrawBuffers(1); + rlFramebufferAttach( + MASK_FRAMEBUFFER, + MASK_TEXTURE, + RL_ATTACHMENT_COLOR_CHANNEL0, + RL_ATTACHMENT_TEXTURE2D, + 0 + ); + if (!rlFramebufferComplete(MASK_FRAMEBUFFER)) { + TraceLog(LOG_ERROR, "Gizmo's framebuffer is not complete"); + exit(1); + } + + IS_GIZMO_LOADED = true; +} + +void UnloadGizmo() { + if (!IS_GIZMO_LOADED) return; + + UnloadShader(SHADER_ROT_HANDLE_COLOR); + rlUnloadFramebuffer(MASK_FRAMEBUFFER); + rlUnloadTexture(MASK_TEXTURE); + IS_GIZMO_LOADED = false; +} + +Matrix UpdateGizmo(Camera3D camera, Vector3 position) { + if (!IS_GIZMO_LOADED) { + TraceLog(LOG_ERROR, "Gizmo must be loaded before the update"); + exit(1); + } + + unsigned char mask_val = get_gizmo_mask_pixel(camera, position); + Matrix transform = update_gizmo(camera, position, mask_val); + + // ------------------------------------------------------------------- + // Draw gizmo + HandleColors rot_handle_colors = get_handle_colors(GIZMO_HOT_ROT); + HandleColors axis_handle_colors = get_handle_colors(GIZMO_HOT_AXIS); + HandleColors plane_handle_colors = get_handle_colors(GIZMO_HOT_PLANE); + + draw_gizmo( + camera, + position, + rot_handle_colors.x, + rot_handle_colors.y, + rot_handle_colors.z, + axis_handle_colors.x, + axis_handle_colors.y, + axis_handle_colors.z, + plane_handle_colors.x, + plane_handle_colors.y, + plane_handle_colors.z + ); + + return transform; +} + +#endif + diff --git a/examples/others/gizmo.png b/examples/others/gizmo.png new file mode 100644 index 000000000..b2346d4ef Binary files /dev/null and b/examples/others/gizmo.png differ