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
pull/35410/head
Dean Lee 2 weeks ago committed by GitHub
parent 04115b6417
commit d992d138bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 124
      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,

Loading…
Cancel
Save