From d992d138bf6f4a72a7bc21442ddfb8deb2901105 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 1 Jun 2025 01:17:15 +0800 Subject: [PATCH] system/ui: enhance polygon fill with AABB clipping and fixed fill color issues (#35408) enhance polygon fill with AABB clipping and fixed fill color issues --- system/ui/lib/shader_polygon.py | 124 ++++++++++---------------------- 1 file changed, 36 insertions(+), 88 deletions(-) diff --git a/system/ui/lib/shader_polygon.py b/system/ui/lib/shader_polygon.py index 9ecaa926bc..94190abc36 100644 --- a/system/ui/lib/shader_polygon.py +++ b/system/ui/lib/shader_polygon.py @@ -16,13 +16,12 @@ uniform int pointCount; uniform vec4 fillColor; uniform vec2 resolution; -uniform bool useGradient; +uniform int useGradient; uniform vec2 gradientStart; uniform vec2 gradientEnd; uniform vec4 gradientColors[15]; uniform float gradientStops[15]; uniform int gradientColorCount; -uniform vec2 visibleGradientRange; vec4 getGradientColor(vec2 pos) { vec2 gradientDir = gradientEnd - gradientStart; @@ -30,22 +29,9 @@ vec4 getGradientColor(vec2 pos) { if (gradientLength < 0.001) return gradientColors[0]; vec2 normalizedDir = gradientDir / gradientLength; - vec2 pointVec = pos - gradientStart; - float projection = dot(pointVec, normalizedDir); + float t = clamp(dot(pos - gradientStart, normalizedDir) / gradientLength, 0.0, 1.0); - float t = projection / gradientLength; - - // Gradient clipping: remap t to visible range - float visibleStart = visibleGradientRange.x; - float visibleEnd = visibleGradientRange.y; - float visibleRange = visibleEnd - visibleStart; - - // Remap t to visible range - if (visibleRange > 0.001) { - t = visibleStart + t * visibleRange; - } - - t = clamp(t, 0.0, 1.0); + if (gradientColorCount <= 1) return gradientColors[0]; for (int i = 0; i < gradientColorCount - 1; i++) { if (t >= gradientStops[i] && t <= gradientStops[i+1]) { float segmentT = (t - gradientStops[i]) / (gradientStops[i+1] - gradientStops[i]); @@ -58,16 +44,11 @@ vec4 getGradientColor(vec2 pos) { bool isPointInsidePolygon(vec2 p) { if (pointCount < 3) return false; - int crossings = 0; for (int i = 0, j = pointCount - 1; i < pointCount; j = i++) { vec2 pi = points[i]; vec2 pj = points[j]; - - // Skip degenerate edges if (distance(pi, pj) < 0.001) continue; - - // Ray-casting if (((pi.y > p.y) != (pj.y > p.y)) && (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y + 0.001) + pi.x)) { crossings++; @@ -104,24 +85,24 @@ float distanceToEdge(vec2 p) { return minDist; } -float signedDistanceToPolygon(vec2 p) { - float dist = distanceToEdge(p); - bool inside = isPointInsidePolygon(p); - return inside ? dist : -dist; -} - void main() { vec2 pixel = fragTexCoord * resolution; - float signedDist = signedDistanceToPolygon(pixel); - + // Compute pixel size for anti-aliasing vec2 pixelGrad = vec2(dFdx(pixel.x), dFdy(pixel.y)); float pixelSize = length(pixelGrad); - float aaWidth = max(0.5, pixelSize * 0.5); // Sharper anti-aliasing + float aaWidth = max(0.5, pixelSize * 1.5); + + bool inside = isPointInsidePolygon(pixel); + if (inside) { + finalColor = useGradient == 1 ? getGradientColor(pixel) : fillColor; + return; + } - float alpha = smoothstep(-aaWidth, aaWidth, signedDist); - if (alpha > 0.0) { - vec4 color = useGradient ? getGradientColor(fragTexCoord) : fillColor; + float sd = -distanceToEdge(pixel); + float alpha = smoothstep(-aaWidth, aaWidth, sd); + if (alpha > 0.0){ + vec4 color = useGradient == 1 ? getGradientColor(pixel) : fillColor; finalColor = vec4(color.rgb, color.a * alpha); } else { finalColor = vec4(0.0); @@ -180,7 +161,6 @@ class ShaderState: 'gradientStops': None, 'gradientColorCount': None, 'mvp': None, - 'visibleGradientRange': None, } # Pre-allocated FFI objects @@ -191,7 +171,6 @@ class ShaderState: self.gradient_start_ptr = rl.ffi.new("float[]", [0.0, 0.0]) self.gradient_end_ptr = rl.ffi.new("float[]", [0.0, 0.0]) self.color_count_ptr = rl.ffi.new("int[]", [0]) - self.visible_gradient_range_ptr = rl.ffi.new("float[]", [0.0, 0.0]) self.gradient_colors_ptr = rl.ffi.new("float[]", MAX_GRADIENT_COLORS * 4) self.gradient_stops_ptr = rl.ffi.new("float[]", MAX_GRADIENT_COLORS) @@ -232,66 +211,40 @@ class ShaderState: self.initialized = False -def _configure_shader_color(state, color, gradient, rect, min_xy, max_xy): - """Configure shader uniforms for solid color or gradient rendering""" +def _configure_shader_color(state, color, gradient, clipped_rect, original_rect): use_gradient = 1 if gradient else 0 state.use_gradient_ptr[0] = use_gradient rl.set_shader_value(state.shader, state.locations['useGradient'], state.use_gradient_ptr, UNIFORM_INT) if use_gradient: - # Set gradient start/end - state.gradient_start_ptr[0:2] = gradient['start'] - state.gradient_end_ptr[0:2] = gradient['end'] + start = np.array(gradient['start']) * np.array([original_rect.width, original_rect.height]) + np.array([original_rect.x, original_rect.y]) + end = np.array(gradient['end']) * np.array([original_rect.width, original_rect.height]) + np.array([original_rect.x, original_rect.y]) + start = start - np.array([clipped_rect.x, clipped_rect.y]) + end = end - np.array([clipped_rect.x, clipped_rect.y]) + state.gradient_start_ptr[0:2] = start.astype(np.float32) + state.gradient_end_ptr[0:2] = end.astype(np.float32) rl.set_shader_value(state.shader, state.locations['gradientStart'], state.gradient_start_ptr, UNIFORM_VEC2) rl.set_shader_value(state.shader, state.locations['gradientEnd'], state.gradient_end_ptr, UNIFORM_VEC2) - # Calculate visible gradient range - width = max_xy[0] - min_xy[0] - height = max_xy[1] - min_xy[1] - - gradient_dir = (gradient['end'][0] - gradient['start'][0], gradient['end'][1] - gradient['start'][1]) - is_vertical = abs(gradient_dir[1]) > abs(gradient_dir[0]) - - visible_start = 0.0 - visible_end = 1.0 - - if is_vertical and height > 0: - visible_start = (rect.y - min_xy[1]) / height - visible_end = visible_start + rect.height / height - elif width > 0: - visible_start = (rect.x - min_xy[0]) / width - visible_end = visible_start + rect.width / width - - # Clamp visible range - visible_start = max(0.0, min(1.0, visible_start)) - visible_end = max(0.0, min(1.0, visible_end)) - - state.visible_gradient_range_ptr[0:2] = [visible_start, visible_end] - rl.set_shader_value(state.shader, state.locations['visibleGradientRange'], state.visible_gradient_range_ptr, UNIFORM_VEC2) - - # Set gradient colors colors = gradient['colors'] color_count = min(len(colors), MAX_GRADIENT_COLORS) + state.color_count_ptr[0] = color_count for i, c in enumerate(colors[:color_count]): base_idx = i * 4 state.gradient_colors_ptr[base_idx:base_idx+4] = [c.r / 255.0, c.g / 255.0, c.b / 255.0, c.a / 255.0] rl.set_shader_value_v(state.shader, state.locations['gradientColors'], state.gradient_colors_ptr, UNIFORM_VEC4, color_count) - # Set gradient stops - stops = gradient.get('stops', [i / (color_count - 1) for i in range(color_count)]) - state.gradient_stops_ptr[0:color_count] = stops[:color_count] + stops = gradient.get('stops', [i / max(1, color_count - 1) for i in range(color_count)]) + stops = np.clip(stops[:color_count], 0.0, 1.0) + state.gradient_stops_ptr[0:color_count] = stops rl.set_shader_value_v(state.shader, state.locations['gradientStops'], state.gradient_stops_ptr, UNIFORM_FLOAT, color_count) - - # Set color count - state.color_count_ptr[0] = color_count rl.set_shader_value(state.shader, state.locations['gradientColorCount'], state.color_count_ptr, UNIFORM_INT) else: - color = color or rl.WHITE # Default to white if no color provided + color = color or rl.WHITE state.fill_color_ptr[0:4] = [color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0] rl.set_shader_value(state.shader, state.locations['fillColor'], state.fill_color_ptr, UNIFORM_VEC4) - -def draw_polygon(rect: rl.Rectangle, points: np.ndarray, color=None, gradient=None): +def draw_polygon(origin_rect: rl.Rectangle, points: np.ndarray, color=None, gradient=None): """ Draw a complex polygon using shader-based even-odd fill rule @@ -317,21 +270,16 @@ def draw_polygon(rect: rl.Rectangle, points: np.ndarray, color=None, gradient=No # Find bounding box min_xy = np.min(points, axis=0) max_xy = np.max(points, axis=0) - - # Clip coordinates to rectangle - clip_x = max(rect.x, min_xy[0]) - clip_y = max(rect.y, min_xy[1]) - clip_right = min(rect.x + rect.width, max_xy[0]) - clip_bottom = min(rect.y + rect.height, max_xy[1]) + clip_x = max(origin_rect.x, min_xy[0]) + clip_y = max(origin_rect.y, min_xy[1]) + clip_right = min(origin_rect.x + origin_rect.width, max_xy[0]) + clip_bottom = min(origin_rect.y + origin_rect.height, max_xy[1]) # Check if polygon is completely off-screen if clip_x >= clip_right or clip_y >= clip_bottom: return - clipped_width = clip_right - clip_x - clipped_height = clip_bottom - clip_y - - clip_rect = rl.Rectangle(clip_x, clip_y, clipped_width, clipped_height) + clipped_rect = rl.Rectangle(clip_x, clip_y, clip_right - clip_x, clip_bottom - clip_y) # Transform points relative to the CLIPPED area transformed_points = points - np.array([clip_x, clip_y]) @@ -340,21 +288,21 @@ def draw_polygon(rect: rl.Rectangle, points: np.ndarray, color=None, gradient=No state.point_count_ptr[0] = len(transformed_points) rl.set_shader_value(state.shader, state.locations['pointCount'], state.point_count_ptr, UNIFORM_INT) - state.resolution_ptr[0:2] = [clipped_width, clipped_height] + state.resolution_ptr[0:2] = [clipped_rect.width, clipped_rect.height] rl.set_shader_value(state.shader, state.locations['resolution'], state.resolution_ptr, UNIFORM_VEC2) flat_points = np.ascontiguousarray(transformed_points.flatten().astype(np.float32)) points_ptr = rl.ffi.cast("float *", flat_points.ctypes.data) rl.set_shader_value_v(state.shader, state.locations['points'], points_ptr, UNIFORM_VEC2, len(transformed_points)) - _configure_shader_color(state, color, gradient, clip_rect, min_xy, max_xy) + _configure_shader_color(state, color, gradient, clipped_rect, origin_rect) # Render rl.begin_shader_mode(state.shader) rl.draw_texture_pro( state.white_texture, rl.Rectangle(0, 0, 2, 2), - clip_rect, + clipped_rect, rl.Vector2(0, 0), 0.0, rl.WHITE,