]> git.lizzy.rs Git - nothing.git/blobdiff - src/game/level/level_editor/rect_layer.c
Add TODO(#1251)
[nothing.git] / src / game / level / level_editor / rect_layer.c
index 65ce1d809ee478b4ba222d95e0913c654c8caa6a..b80fd2b765bb8fcf71acd77764c2a6c533f4c283 100644 (file)
@@ -2,7 +2,6 @@
 #include <errno.h>
 
 #include "game/camera.h"
-#include "system/lt.h"
 #include "system/stacktrace.h"
 #include "system/nth_alloc.h"
 #include "system/log.h"
@@ -94,10 +93,9 @@ RectUndoContext create_rect_undo_add_context(RectLayer *layer, size_t index)
 }
 
 static
-RectUndoContext create_rect_undo_element_context(RectLayer *layer)
+RectUndoContext create_rect_undo_element_context(RectLayer *layer, size_t index)
 {
     trace_assert(layer);
-    size_t index = (size_t) layer->selection;
     trace_assert(index < layer->rects.count);
 
     RectUndoContext undo_context;
@@ -111,17 +109,17 @@ RectUndoContext create_rect_undo_element_context(RectLayer *layer)
 }
 
 static
-RectUndoContext create_rect_undo_update_context(RectLayer *rect_layer)
+RectUndoContext create_rect_undo_update_context(RectLayer *rect_layer, size_t index)
 {
-    RectUndoContext undo_context = create_rect_undo_element_context(rect_layer);
+    RectUndoContext undo_context = create_rect_undo_element_context(rect_layer, index);
     undo_context.type = RECT_UNDO_UPDATE;
     return undo_context;
 }
 
 static
-RectUndoContext create_rect_undo_delete_context(RectLayer *rect_layer)
+RectUndoContext create_rect_undo_delete_context(RectLayer *rect_layer, size_t i)
 {
-    RectUndoContext undo_context = create_rect_undo_element_context(rect_layer);
+    RectUndoContext undo_context = create_rect_undo_element_context(rect_layer, i);
     undo_context.type = RECT_UNDO_DELETE;
     return undo_context;
 }
@@ -250,13 +248,13 @@ static void rect_layer_swap_elements(RectLayer *layer, size_t a, size_t b,
     RECT_UNDO_PUSH(undo_history, create_rect_undo_swap_context(layer, a, b));
 }
 
-static int rect_layer_delete_rect_at(RectLayer *layer,
+static int rect_layer_delete_rect_at_index(RectLayer *layer,
                                      size_t i,
                                      UndoHistory *undo_history)
 {
     trace_assert(layer);
 
-    RECT_UNDO_PUSH(undo_history, create_rect_undo_delete_context(layer));
+    RECT_UNDO_PUSH(undo_history, create_rect_undo_delete_context(layer, i));
 
     dynarray_delete_at(&layer->rects, i);
     dynarray_delete_at(&layer->colors, i);
@@ -277,6 +275,32 @@ static int calc_resize_mask(Vec2f point, Rect rect)
     return mask;
 }
 
+#define TOOL_BUTTON_WIDTH 50.0f
+#define TOOL_BUTTON_HEIGHT 50.0f
+#define TOOL_BAR_PADDING 20.0f
+
+static
+Rect subtract_tool_button_rect(const Camera *camera)
+{
+    const Rect view_port = camera_view_port_screen(camera);
+    return rect(
+        TOOL_BAR_PADDING,
+        view_port.h - TOOL_BUTTON_HEIGHT - TOOL_BAR_PADDING,
+        TOOL_BUTTON_WIDTH,
+        TOOL_BUTTON_HEIGHT);
+}
+
+static
+Rect snapping_tool_button_rect(const Camera *camera)
+{
+    const Rect view_port = camera_view_port_screen(camera);
+    return rect(
+        TOOL_BAR_PADDING + TOOL_BUTTON_WIDTH + TOOL_BAR_PADDING,
+        view_port.h - TOOL_BUTTON_HEIGHT - TOOL_BAR_PADDING,
+        TOOL_BUTTON_WIDTH,
+        TOOL_BUTTON_HEIGHT);
+}
+
 static int rect_layer_event_idle(RectLayer *layer,
                                  const SDL_Event *event,
                                  const Camera *camera,
@@ -305,6 +329,19 @@ static int rect_layer_event_idle(RectLayer *layer,
     case SDL_MOUSEBUTTONDOWN: {
         switch (event->button.button) {
         case SDL_BUTTON_LEFT: {
+            Vec2f screen_position =
+                vec((float) event->button.x,
+                    (float) event->button.y);
+            if (rect_contains_point(subtract_tool_button_rect(camera), screen_position)) {
+                layer->subtract_enabled = !layer->subtract_enabled;
+                return 0;
+            }
+
+            if (rect_contains_point(snapping_tool_button_rect(camera), screen_position)) {
+                layer->snapping_enabled = !layer->snapping_enabled;
+                return 0;
+            }
+
             Vec2f position = camera_map_screen(
                 camera,
                 event->button.x,
@@ -337,7 +374,9 @@ static int rect_layer_event_idle(RectLayer *layer,
                 layer->selection = rect_at_position;
 
                 if (layer->selection < 0) {
-                    layer->state = RECT_LAYER_CREATE;
+                    layer->state = layer->subtract_enabled
+                        ? RECT_LAYER_SUBTRACT
+                        : RECT_LAYER_CREATE;
                     layer->create_begin = position;
                     layer->create_end = position;
                 }
@@ -393,7 +432,10 @@ static int rect_layer_event_idle(RectLayer *layer,
 
         case SDLK_DELETE: {
             if (layer->selection >= 0) {
-                rect_layer_delete_rect_at(layer, (size_t) layer->selection, undo_history);
+                rect_layer_delete_rect_at_index(
+                    layer,
+                    (size_t) layer->selection,
+                    undo_history);
                 layer->selection = -1;
             }
         } break;
@@ -450,6 +492,143 @@ static int rect_layer_event_idle(RectLayer *layer,
     return 0;
 }
 
+#define GEOMETRY_CAPACITY 256
+
+typedef struct {
+    size_t first;
+    size_t count;
+    Rect rects[GEOMETRY_CAPACITY];
+    Color colors[GEOMETRY_CAPACITY];
+} Geometry;
+
+static
+void push_geometry(Geometry *geometry, Rect rect, Color color)
+{
+    assert(geometry);
+    assert(geometry->count < GEOMETRY_CAPACITY);
+
+    if ((rect.w * rect.h) > 1e-6f) {
+        size_t i = (geometry->first + geometry->count) % GEOMETRY_CAPACITY;
+        geometry->rects[i] = rect;
+        geometry->colors[i] = color;
+        geometry->count++;
+    }
+}
+
+static
+void subtract_rect_from_rect(Rect a, Color color_a, Rect c, Geometry *result)
+{
+    assert(result);
+
+    Rect b = rects_overlap_area(a, c);
+
+    if (b.w * b.h < 1e-6) {
+        push_geometry(result, a, color_a);
+        return;
+    }
+
+    push_geometry(result, (Rect) {a.x, a.y, a.w, b.y - a.y}, color_a);
+    push_geometry(result, (Rect) {a.x, b.y, b.x - a.x, b.h}, color_a);
+    push_geometry(result, (Rect) {
+        b.x + b.w,
+        b.y,
+        a.w - (b.x - a.x) - b.w,
+        b.h
+    }, color_a);
+    push_geometry(result, (Rect) {
+        a.x,
+        b.y + b.h,
+        a.w,
+        a.h - (b.y - a.y) - b.h
+    }, color_a);
+}
+
+static
+void subtract_rect_from_geometry(Geometry *result, Rect b)
+{
+    assert(result);
+
+    size_t count = result->count;
+    size_t first = result->first;
+
+    for (size_t i = 0; i < count; ++i) {
+        result->first = (result->first + 1) % GEOMETRY_CAPACITY;
+        result->count -= 1;
+        subtract_rect_from_rect(
+            result->rects[(i + first) % GEOMETRY_CAPACITY],
+            result->colors[(i + first) % GEOMETRY_CAPACITY],
+            b,
+            result);
+    }
+}
+
+static int rect_layer_event_subtract(RectLayer *layer,
+                                     const SDL_Event *event,
+                                     const Camera *camera,
+                                     UndoHistory *undo_history)
+{
+    trace_assert(layer);
+    trace_assert(event);
+    trace_assert(camera);
+    trace_assert(undo_history);
+
+    Rect *rects = layer->rects.data;
+    Color *colors = layer->colors.data;
+
+    switch (event->type) {
+    case SDL_MOUSEBUTTONUP: {
+        switch (event->button.button) {
+        case SDL_BUTTON_LEFT: {
+            const Rect real_rect =
+                rect_from_points(
+                    layer->create_begin,
+                    layer->create_end);
+            const float area = real_rect.w * real_rect.h;
+
+            Geometry geometry = {0};
+
+            if (area >= CREATE_AREA_THRESHOLD) {
+                for (size_t i = 0; i < layer->rects.count;) {
+                    Rect overlap_area = rects_overlap_area(
+                        real_rect,
+                        rects[i]);
+                    if (overlap_area.w * overlap_area.h > 1e-6) {
+                        push_geometry(&geometry, rects[i], colors[i]);
+                        rect_layer_delete_rect_at_index(layer, i, undo_history);
+                    } else {
+                        i++;
+                    }
+                }
+
+                subtract_rect_from_geometry(&geometry, real_rect);
+
+                for (size_t i = 0; i < geometry.count; ++i) {
+                    size_t j = (i + geometry.first) % GEOMETRY_CAPACITY;
+                    rect_layer_add_rect(
+                        layer,
+                        geometry.rects[j],
+                        geometry.colors[j],
+                        undo_history);
+                }
+            } else {
+                log_info("The area is too small %f. Such small box won't be cut.\n", area);
+            }
+            layer->state = RECT_LAYER_IDLE;
+        } break;
+        }
+    } break;
+
+    case SDL_MOUSEMOTION: {
+        layer->create_end = camera_map_screen(
+            camera,
+            event->motion.x,
+            event->motion.y);
+    } break;
+    }
+
+    return 0;
+}
+
 static int rect_layer_event_create(RectLayer *layer,
                                    const SDL_Event *event,
                                    const Camera *camera,
@@ -658,7 +837,11 @@ static int rect_layer_event_resize(RectLayer *layer,
 
     case SDL_MOUSEBUTTONUP: {
         layer->state = RECT_LAYER_IDLE;
-        RECT_UNDO_PUSH(undo_history, create_rect_undo_update_context(layer));
+        RECT_UNDO_PUSH(
+            undo_history,
+            create_rect_undo_update_context(
+                layer,
+                (size_t) layer->selection));
         dynarray_replace_at(&layer->rects, (size_t) layer->selection, &layer->inter_rect);
     } break;
     }
@@ -746,8 +929,14 @@ static int rect_layer_event_move(RectLayer *layer,
                     rect_position(rects[layer->selection])));
 
         if (distance > 1e-6) {
-            RECT_UNDO_PUSH(undo_history, create_rect_undo_update_context(layer));
-            dynarray_replace_at(&layer->rects, (size_t) layer->selection, &layer->inter_rect);
+            RECT_UNDO_PUSH(
+                undo_history,
+                create_rect_undo_update_context(
+                    layer, (size_t) layer->selection));
+            dynarray_replace_at(
+                &layer->rects,
+                (size_t) layer->selection,
+                &layer->inter_rect);
         }
     } break;
     }
@@ -768,7 +957,11 @@ static int rect_layer_event_id_rename(RectLayer *layer,
     case SDL_KEYDOWN: {
         switch (event->key.keysym.sym) {
         case SDLK_RETURN: {
-            RECT_UNDO_PUSH(undo_history, create_rect_undo_update_context(layer));
+            RECT_UNDO_PUSH(
+                undo_history,
+                create_rect_undo_update_context(
+                    layer,
+                    (size_t) layer->selection));
 
             char *id = dynarray_pointer_at(&layer->ids, (size_t)layer->selection);
             memset(id, 0, ENTITY_MAX_ID_SIZE);
@@ -797,36 +990,36 @@ LayerPtr rect_layer_as_layer(RectLayer *rect_layer)
     return layer;
 }
 
-RectLayer create_rect_layer(const char *id_name_prefix, Cursor *cursor)
+RectLayer *create_rect_layer(Memory *memory,
+                             const char *id_name_prefix,
+                             Cursor *cursor)
 {
+    trace_assert(memory);
+    trace_assert(id_name_prefix);
     trace_assert(cursor);
 
-    RectLayer result = {0};
+    RectLayer *rect_layer = memory_alloc(memory, sizeof(RectLayer));
 
-    result.ids = create_dynarray(sizeof(char) * ENTITY_MAX_ID_SIZE);
-    result.rects = create_dynarray(sizeof(Rect));
-    result.colors = create_dynarray(sizeof(Color));
-    result.actions = create_dynarray(sizeof(Action));
-    result.id_edit_field.font_size = RECT_LAYER_ID_LABEL_SIZE;
-    result.id_edit_field.font_color = COLOR_BLACK;
-    result.color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
-    result.selection = -1;
-    result.id_name_prefix = id_name_prefix;
-    result.cursor = cursor;
+    rect_layer->ids = create_dynarray(memory, sizeof(char) * ENTITY_MAX_ID_SIZE);
+    rect_layer->rects = create_dynarray(memory, sizeof(Rect));
+    rect_layer->colors = create_dynarray(memory, sizeof(Color));
+    rect_layer->actions = create_dynarray(memory, sizeof(Action));
+    rect_layer->id_edit_field.font_size = RECT_LAYER_ID_LABEL_SIZE;
+    rect_layer->id_edit_field.font_color = COLOR_BLACK;
+    rect_layer->color_picker = create_color_picker_from_rgba(rgba(1.0f, 0.0f, 0.0f, 1.0f));
+    rect_layer->selection = -1;
+    rect_layer->id_name_prefix = id_name_prefix;
+    rect_layer->cursor = cursor;
 
-    return result;
+    return rect_layer;
 }
 
-RectLayer chop_rect_layer(Memory *memory,
-                          String *input,
-                          const char *id_name_prefix,
-                          Cursor *cursor)
+void rect_layer_load(RectLayer *layer, Memory *memory, String *input)
 {
+    trace_assert(layer);
     trace_assert(memory);
     trace_assert(input);
 
-    RectLayer layer = create_rect_layer(id_name_prefix, cursor);
-
     int n = atoi(string_to_cstr(memory, trim(chop_by_delim(input, '\n'))));
     char id[ENTITY_MAX_ID_SIZE];
     for (int i = 0; i < n; ++i) {
@@ -845,9 +1038,9 @@ RectLayer chop_rect_layer(Memory *memory,
             string_id.data,
             min_size_t(ENTITY_MAX_ID_SIZE - 1, string_id.count));
 
-        dynarray_push(&layer.rects, &rect);
-        dynarray_push(&layer.colors, &color);
-        dynarray_push(&layer.ids, id);
+        dynarray_push(&layer->rects, &rect);
+        dynarray_push(&layer->colors, &color);
+        dynarray_push(&layer->ids, id);
 
         Action action = {
             .type = ACTION_NONE,
@@ -875,10 +1068,8 @@ RectLayer chop_rect_layer(Memory *memory,
             }
         }
 
-        dynarray_push(&layer.actions, &action);
+        dynarray_push(&layer->actions, &action);
     }
-
-    return layer;
 }
 
 int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
@@ -981,10 +1172,29 @@ int rect_layer_render(const RectLayer *layer, const Camera *camera, int active)
         }
     }
 
+    if (layer->state == RECT_LAYER_SUBTRACT) {
+        if (camera_draw_rect(camera, rect_from_points(layer->create_begin, layer->create_end), color) < 0) {
+            return -1;
+        }
+    }
+
     if (active && color_picker_render(&layer->color_picker, camera) < 0) {
         return -1;
     }
 
+    // Tool bar
+    if (active) {
+        // TODO(#1251): subtract and snapping tools don't have any icons
+        camera_fill_rect_screen(
+            camera,
+            subtract_tool_button_rect(camera),
+            layer->subtract_enabled ? COLOR_RED : rgba(0.2f, 0.2f, 0.2f, 1.0f));
+        camera_fill_rect_screen(
+            camera,
+            snapping_tool_button_rect(camera),
+            layer->snapping_enabled ? COLOR_RED : rgba(0.2f, 0.2f, 0.2f, 1.0f));
+    }
+
     return 0;
 }
 
@@ -1009,7 +1219,11 @@ int rect_layer_event_recolor(RectLayer *layer,
         layer->inter_color = color_picker_rgba(&layer->color_picker);
 
         if (!color_picker_drag(&layer->color_picker)) {
-            RECT_UNDO_PUSH(undo_history, create_rect_undo_update_context(layer));
+            RECT_UNDO_PUSH(
+                undo_history,
+                create_rect_undo_update_context(
+                    layer,
+                    (size_t)layer->selection));
             dynarray_replace_at(&layer->colors, (size_t) layer->selection, &layer->inter_color);
             layer->state = RECT_LAYER_IDLE;
         }
@@ -1045,6 +1259,9 @@ int rect_layer_event(RectLayer *layer,
 
     case RECT_LAYER_RECOLOR:
         return rect_layer_event_recolor(layer, event, camera, undo_history);
+
+    case RECT_LAYER_SUBTRACT:
+        return rect_layer_event_subtract(layer, event, camera, undo_history);
     }