From c95166c998f8a0d507b4e3f237ca0aacce430652 Mon Sep 17 00:00:00 2001 From: dugramen Date: Mon, 3 Mar 2025 14:06:30 -0500 Subject: [PATCH] StyleBoxFlat scale down corners when oveflowing to prevent glitchy overlapping shapes. --- scene/resources/style_box_flat.cpp | 104 +++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 12 deletions(-) diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp index e4dd48d1635..4e2e5823eff 100644 --- a/scene/resources/style_box_flat.cpp +++ b/scene/resources/style_box_flat.cpp @@ -234,6 +234,79 @@ inline void set_inner_corner_radius(const Rect2 style_rect, const Rect2 inner_re inner_corner_radius[3] = MAX(corner_radius[3] - MIN(border_bottom, border_left), 0); // Bottom left. } +inline void set_corner_scale(const Rect2 &style_rect, const Rect2 &inner_rect, const real_t corner_radius[4], Point2 *inner_scale) { + real_t border_left = inner_rect.position.x - style_rect.position.x; + real_t border_top = inner_rect.position.y - style_rect.position.y; + real_t border_right = style_rect.size.width - inner_rect.size.width - border_left; + real_t border_bottom = style_rect.size.height - inner_rect.size.height - border_top; + + // Amount of overflow along an edge. + // Ex. SIDE_LEFT edge is the overflow between top_left and bottom_left corners. + // MIN(0,) is to ignore underflow, and negating is to make values positive. + real_t edge_overflow[4] = { + -MIN(0, inner_rect.size.y - corner_radius[CORNER_TOP_LEFT] - corner_radius[CORNER_BOTTOM_LEFT]), + -MIN(0, inner_rect.size.x - corner_radius[CORNER_TOP_LEFT] - corner_radius[CORNER_TOP_RIGHT]), + -MIN(0, inner_rect.size.y - corner_radius[CORNER_TOP_RIGHT] - corner_radius[CORNER_BOTTOM_RIGHT]), + -MIN(0, inner_rect.size.x - corner_radius[CORNER_BOTTOM_LEFT] - corner_radius[CORNER_BOTTOM_RIGHT]) + }; + + // Sums of borders. + real_t hb_sum = border_left + border_right; + real_t vb_sum = border_top + border_bottom; + + // Ratio of each side to the sum of itself and opposite side. + // Since overflow only happens with opposite borders, you only need to get the ratio of each border relative to the sum of involved borders. + real_t ratios[4] = { + // Prevent divide by 0 errors. + hb_sum > 0 ? (border_left / hb_sum) : 0, + vb_sum > 0 ? (border_top / vb_sum) : 0, + hb_sum > 0 ? (border_right / hb_sum) : 0, + vb_sum > 0 ? (border_bottom / vb_sum) : 0 + }; + + // Raw amount each corner should shrink. + Point2 corner_reduction[4] = { + Point2(edge_overflow[SIDE_TOP] * ratios[SIDE_LEFT], edge_overflow[SIDE_LEFT] * ratios[SIDE_TOP]), + Point2(edge_overflow[SIDE_TOP] * ratios[SIDE_RIGHT], edge_overflow[SIDE_RIGHT] * ratios[SIDE_TOP]), + Point2(edge_overflow[SIDE_BOTTOM] * ratios[SIDE_RIGHT], edge_overflow[SIDE_RIGHT] * ratios[SIDE_BOTTOM]), + Point2(edge_overflow[SIDE_BOTTOM] * ratios[SIDE_LEFT], edge_overflow[SIDE_LEFT] * ratios[SIDE_BOTTOM]), + }; + + // Corner Radii as Point2s. + Point2 pcr[4] = { + Point2(corner_radius[0], corner_radius[0]), + Point2(corner_radius[1], corner_radius[1]), + Point2(corner_radius[2], corner_radius[2]), + Point2(corner_radius[3], corner_radius[3]), + }; + + // If corner radii are too small, they won't shrink the full amount. + // Adjacent corners will have to shrink the leftovers if they can. + // Minf(0) is to ignore non-leftovers, and negating is to make values positive. + Point2 leftovers[4] = { + -((pcr[0] - corner_reduction[0]).minf(0)), + -((pcr[1] - corner_reduction[1]).minf(0)), + -((pcr[2] - corner_reduction[2]).minf(0)), + -((pcr[3] - corner_reduction[3]).minf(0)), + }; + + // New shrunken radii after distributing the leftovers. + Point2 distributed[4] = { + ((pcr[0] - corner_reduction[0] - leftovers[3] - leftovers[1]).maxf(0)), + ((pcr[1] - corner_reduction[1] - leftovers[0] - leftovers[2]).maxf(0)), + ((pcr[2] - corner_reduction[2] - leftovers[1] - leftovers[3]).maxf(0)), + ((pcr[3] - corner_reduction[3] - leftovers[2] - leftovers[0]).maxf(0)), + }; + + // How much the curve should scale to achieve the shrunken radii. + for (int i = 0; i < 4; i++) { + // Unshrinkable is how much is still left over, even after distributing leftovers. + // Exclude it from the final scale. + Point2 unshrinkable = (leftovers[(i + 1) % 4] + leftovers[(i + 4 - 1) % 4] - distributed[i]).maxf(0); + inner_scale[i] = distributed[i] / (pcr[i] - unshrinkable).maxf(FLT_EPSILON); + } +} + inline void draw_rounded_rectangle(Vector &verts, Vector &indices, Vector &colors, const Rect2 &style_rect, const real_t corner_radius[4], const Rect2 &ring_rect, const Rect2 &inner_rect, const Color &inner_color, const Color &outer_color, const int corner_detail, const Vector2 &skew, bool is_filled = false) { int vert_offset = verts.size(); @@ -244,23 +317,30 @@ inline void draw_rounded_rectangle(Vector &verts, Vector &indices, real_t ring_corner_radius[4]; set_inner_corner_radius(style_rect, ring_rect, corner_radius, ring_corner_radius); + Point2 ring_scale[4]; + set_corner_scale(style_rect, ring_rect, ring_corner_radius, ring_scale); + // Corner radius center points. Vector outer_points = { - ring_rect.position + Vector2(ring_corner_radius[0], ring_corner_radius[0]), //tl - Point2(ring_rect.position.x + ring_rect.size.x - ring_corner_radius[1], ring_rect.position.y + ring_corner_radius[1]), //tr - ring_rect.position + ring_rect.size - Vector2(ring_corner_radius[2], ring_corner_radius[2]), //br - Point2(ring_rect.position.x + ring_corner_radius[3], ring_rect.position.y + ring_rect.size.y - ring_corner_radius[3]) //bl + ring_rect.position + Vector2(ring_corner_radius[0], ring_corner_radius[0]) * ring_scale[0], //tl + Point2(ring_rect.position.x + ring_rect.size.x - ring_corner_radius[1] * ring_scale[1].x, ring_rect.position.y + ring_corner_radius[1] * ring_scale[1].y), //tr + ring_rect.position + ring_rect.size - Vector2(ring_corner_radius[2], ring_corner_radius[2]) * ring_scale[2], //br + Point2(ring_rect.position.x + ring_corner_radius[3] * ring_scale[3].x, ring_rect.position.y + ring_rect.size.y - ring_corner_radius[3] * ring_scale[3].y) //bl }; real_t inner_corner_radius[4]; set_inner_corner_radius(style_rect, inner_rect, corner_radius, inner_corner_radius); + Point2 inner_scale[4]; + set_corner_scale(style_rect, inner_rect, inner_corner_radius, inner_scale); + Vector inner_points = { - inner_rect.position + Vector2(inner_corner_radius[0], inner_corner_radius[0]), //tl - Point2(inner_rect.position.x + inner_rect.size.x - inner_corner_radius[1], inner_rect.position.y + inner_corner_radius[1]), //tr - inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2]), //br - Point2(inner_rect.position.x + inner_corner_radius[3], inner_rect.position.y + inner_rect.size.y - inner_corner_radius[3]) //bl + inner_rect.position + Vector2(inner_corner_radius[0], inner_corner_radius[0]) * inner_scale[0], //tl + Point2(inner_rect.position.x + inner_rect.size.x - inner_corner_radius[1] * inner_scale[1].x, inner_rect.position.y + inner_corner_radius[1] * inner_scale[1].y), //tr + inner_rect.position + inner_rect.size - Vector2(inner_corner_radius[2], inner_corner_radius[2]) * inner_scale[2], //br + Point2(inner_rect.position.x + inner_corner_radius[3] * inner_scale[3].x, inner_rect.position.y + inner_rect.size.y - inner_corner_radius[3] * inner_scale[3].y) //bl }; + // Calculate the vertices. // If the center is filled, we do not draw the border and directly use the inner ring as reference. Because all calls to this @@ -289,8 +369,8 @@ inline void draw_rounded_rectangle(Vector &verts, Vector &indices, const real_t angle_sine = Math::sin(pt_angle); { - const real_t x = inner_corner_radius[corner_idx] * angle_cosine + inner_points[corner_idx].x; - const real_t y = inner_corner_radius[corner_idx] * angle_sine + inner_points[corner_idx].y; + const real_t x = inner_corner_radius[corner_idx] * angle_cosine * inner_scale[corner_idx].x + inner_points[corner_idx].x; + const real_t y = inner_corner_radius[corner_idx] * angle_sine * inner_scale[corner_idx].y + inner_points[corner_idx].y; const float x_skew = -skew.x * (y - style_rect_center.y); const float y_skew = -skew.y * (x - style_rect_center.x); verts_ptr[verts_size + idx_ofs] = Vector2(x + x_skew, y + y_skew); @@ -298,8 +378,8 @@ inline void draw_rounded_rectangle(Vector &verts, Vector &indices, } if (draw_border) { - const real_t x = ring_corner_radius[corner_idx] * angle_cosine + outer_points[corner_idx].x; - const real_t y = ring_corner_radius[corner_idx] * angle_sine + outer_points[corner_idx].y; + const real_t x = ring_corner_radius[corner_idx] * angle_cosine * ring_scale[corner_idx].x + outer_points[corner_idx].x; + const real_t y = ring_corner_radius[corner_idx] * angle_sine * ring_scale[corner_idx].y + outer_points[corner_idx].y; const float x_skew = -skew.x * (y - style_rect_center.y); const float y_skew = -skew.y * (x - style_rect_center.x); verts_ptr[verts_size + idx_ofs + 1] = Vector2(x + x_skew, y + y_skew);