openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
9.7 KiB

import pyray as rl
2 weeks ago
import numpy as np
from typing import Any
FRAGMENT_SHADER = """
#version 300 es
precision mediump float;
in vec2 fragTexCoord;
out vec4 finalColor;
uniform vec2 points[100];
uniform int pointCount;
uniform vec4 fillColor;
uniform vec2 resolution;
2 weeks ago
uniform bool useGradient;
uniform vec2 gradientStart;
uniform vec2 gradientEnd;
uniform vec4 gradientColors[8];
uniform float gradientStops[8];
uniform int gradientColorCount;
vec4 getGradientColor(vec2 pos) {
2 weeks ago
vec2 dir = gradientEnd - gradientStart;
float len = length(dir);
if (len < 0.001) return gradientColors[0];
2 weeks ago
float t = clamp(dot(pos - gradientStart, dir) / (len * len), 0.0, 1.0);
2 weeks ago
if (gradientColorCount == 2) {
return mix(gradientColors[0], gradientColors[1], t);
}
2 weeks ago
int low = 0;
int high = gradientColorCount - 1;
while (low < high) {
int mid = (low + high) / 2;
if (t > gradientStops[mid]) low = mid + 1;
else high = mid;
}
2 weeks ago
if (low == 0) return gradientColors[0];
if (low == gradientColorCount) return gradientColors[gradientColorCount - 1];
float segmentT = (t - gradientStops[low - 1]) / (gradientStops[low] - gradientStops[low - 1]);
return mix(gradientColors[low - 1], gradientColors[low], segmentT);
}
float distanceToEdge(vec2 p) {
2 weeks ago
float minDist = resolution.x;
2 weeks ago
for (int i = 0, j = pointCount - 1; i < pointCount; j = i++) {
vec2 edge0 = points[j];
vec2 edge1 = points[i];
2 weeks ago
vec2 v2 = edge1 - edge0;
float l2 = dot(v2, v2);
if (l2 < 0.0001) continue;
2 weeks ago
vec2 v1 = p - edge0;
float t = clamp(dot(v1, v2) / l2, 0.0, 1.0);
minDist = min(minDist, length(v1 - t * v2));
}
2 weeks ago
return minDist;
}
bool isPointInsidePolygon(vec2 p) {
2 weeks ago
if (pointCount < 3) return false;
2 weeks ago
if (pointCount == 3) {
vec2 v0 = points[0], v1 = points[1], v2 = points[2];
float d = (v1.y - v2.y) * (v0.x - v2.x) + (v2.x - v1.x) * (v0.y - v2.y);
if (abs(d) < 0.0001) return false;
2 weeks ago
float a = ((v1.y - v2.y) * (p.x - v2.x) + (v2.x - v1.x) * (p.y - v2.y)) / d;
float b = ((v2.y - v0.y) * (p.x - v2.x) + (v0.x - v2.x) * (p.y - v2.y)) / d;
float c = 1.0 - a - b;
return a >= 0.0 && b >= 0.0 && c >= 0.0;
}
2 weeks ago
bool inside = false;
for (int i = 0, j = pointCount - 1; i < pointCount; j = i++) {
vec2 pi = points[i], pj = points[j];
float dy = pj.y - pi.y;
if (abs(dy) < 1e-4 || pi == pj) continue;
2 weeks ago
if ((pi.y > p.y) != (pj.y > p.y)) {
float x_intersect = pi.x + (pj.x - pi.x) * (p.y - pi.y) / dy;
if (p.x < x_intersect) inside = !inside;
}
}
2 weeks ago
return inside;
}
void main() {
2 weeks ago
vec2 pixel = fragTexCoord * resolution;
bool inside = isPointInsidePolygon(pixel);
float dist = distanceToEdge(pixel);
float aaWidth = 2.0 / min(resolution.x, resolution.y);
float alpha = inside ? min(1.0, dist / aaWidth) : max(0.0, 1.0 - dist / aaWidth);
if (alpha == 0.0) {
discard;
}
2 weeks ago
vec4 color = useGradient ? getGradientColor(pixel) : fillColor;
finalColor = vec4(color.rgb, color.a * alpha);
}
2 weeks ago
"""
# Default vertex shader
VERTEX_SHADER = """
#version 300 es
in vec3 vertexPosition;
in vec2 vertexTexCoord;
out vec2 fragTexCoord;
uniform mat4 mvp;
void main() {
2 weeks ago
fragTexCoord = vertexTexCoord;
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
"""
class ShaderState:
_instance: Any = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
if ShaderState._instance is not None:
raise Exception("This class is a singleton. Use get_instance() instead.")
self.initialized = False
self.shader = None
self.white_texture = None
# Shader uniform locations
self.locations = {
'pointCount': None,
'fillColor': None,
'resolution': None,
'points': None,
'useGradient': None,
'gradientStart': None,
'gradientEnd': None,
'gradientColors': None,
'gradientStops': None,
'gradientColorCount': None,
'mvp': None,
}
def initialize(self):
if self.initialized:
return
vertex_shader = rl.load_shader_from_memory(VERTEX_SHADER, FRAGMENT_SHADER)
self.shader = vertex_shader
# Create and cache white texture
white_img = rl.gen_image_color(2, 2, rl.WHITE)
self.white_texture = rl.load_texture_from_image(white_img)
rl.set_texture_filter(self.white_texture, rl.TEXTURE_FILTER_BILINEAR)
rl.unload_image(white_img)
# Cache all uniform locations
for uniform in self.locations.keys():
self.locations[uniform] = rl.get_shader_location(self.shader, uniform)
# Setup default MVP matrix
mvp_ptr = rl.ffi.new("float[16]", [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0])
rl.set_shader_value_matrix(self.shader, self.locations['mvp'], rl.Matrix(*mvp_ptr))
self.initialized = True
def cleanup(self):
if not self.initialized:
return
if self.white_texture:
rl.unload_texture(self.white_texture)
self.white_texture = None
if self.shader:
rl.unload_shader(self.shader)
self.shader = None
self.initialized = False
def draw_polygon(points: np.ndarray, color=None, gradient=None):
"""
Draw a complex polygon using shader-based even-odd fill rule
Args:
points: List of (x,y) points defining the polygon
color: Solid fill color (rl.Color)
gradient: Dict with gradient parameters:
{
'start': (x1, y1), # Start point (normalized 0-1)
'end': (x2, y2), # End point (normalized 0-1)
'colors': [rl.Color], # List of colors at stops
'stops': [float] # List of positions (0-1)
}
"""
if len(points) < 3:
return
# Get shader state singleton
state = ShaderState.get_instance()
# Initialize shader if not already done
if not state.initialized:
state.initialize()
# Find bounding box
2 weeks ago
min_xy = np.min(points, axis=0)
min_x, min_y = min_xy
max_x, max_y = np.max(points, axis=0)
width = max(1, max_x - min_x)
height = max(1, max_y - min_y)
# Transform points to shader space
2 weeks ago
transformed_points = points - min_xy
# Set basic shader uniforms using cached locations
point_count_ptr = rl.ffi.new("int[]", [len(transformed_points)])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['pointCount'], point_count_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_INT)
resolution_ptr = rl.ffi.new("float[]", [width, height])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['resolution'], resolution_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC2)
# Set points
2 weeks ago
flat_points = np.ascontiguousarray(transformed_points.flatten().astype(np.float32))
2 weeks ago
points_ptr = rl.ffi.cast("float *", flat_points.ctypes.data)
rl.set_shader_value_v(
2 weeks ago
state.shader, state.locations['points'], points_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC2, len(transformed_points)
)
# Set gradient or solid color based on what was provided
if gradient:
# Enable gradient
use_gradient_ptr = rl.ffi.new("int[]", [1])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['useGradient'], use_gradient_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_INT)
# Set gradient start/end
start_ptr = rl.ffi.new("float[]", [gradient['start'][0], gradient['start'][1]])
end_ptr = rl.ffi.new("float[]", [gradient['end'][0], gradient['end'][1]])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['gradientStart'], start_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC2)
rl.set_shader_value(state.shader, state.locations['gradientEnd'], end_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC2)
# Set gradient colors
colors = gradient['colors']
color_count = min(len(colors), 8) # Max 8 colors
colors_ptr = rl.ffi.new("float[]", color_count * 4)
for i, c in enumerate(colors[:color_count]):
colors_ptr[i * 4] = c.r / 255.0
colors_ptr[i * 4 + 1] = c.g / 255.0
colors_ptr[i * 4 + 2] = c.b / 255.0
colors_ptr[i * 4 + 3] = c.a / 255.0
rl.set_shader_value_v(
2 weeks ago
state.shader, state.locations['gradientColors'], colors_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC4, color_count
)
# Set gradient stops
stops = gradient.get('stops', [i / (color_count - 1) for i in range(color_count)])
stops_ptr = rl.ffi.new("float[]", color_count)
for i, s in enumerate(stops[:color_count]):
stops_ptr[i] = s
rl.set_shader_value_v(
2 weeks ago
state.shader, state.locations['gradientStops'], stops_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_FLOAT, color_count
)
# Set color count
color_count_ptr = rl.ffi.new("int[]", [color_count])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['gradientColorCount'], color_count_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_INT)
else:
# Disable gradient
use_gradient_ptr = rl.ffi.new("int[]", [0])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['useGradient'], use_gradient_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_INT)
# Set solid color
if color is None:
color = rl.WHITE
fill_color_ptr = rl.ffi.new("float[]", [color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0])
2 weeks ago
rl.set_shader_value(state.shader, state.locations['fillColor'], fill_color_ptr, rl.ShaderUniformDataType.SHADER_UNIFORM_VEC4)
# Draw with shader
rl.begin_shader_mode(state.shader)
rl.draw_texture_pro(
state.white_texture,
rl.Rectangle(0, 0, 2, 2),
rl.Rectangle(int(min_x), int(min_y), int(width), int(height)),
rl.Vector2(0, 0),
0.0,
rl.WHITE,
)
rl.end_shader_mode()
def cleanup_shader_resources():
state = ShaderState.get_instance()
state.cleanup()