Skip to content

Instantly share code, notes, and snippets.

@huacnlee
Last active November 27, 2024 11:09
Show Gist options
  • Save huacnlee/37a84e47bf77efbd4aeb53794952de47 to your computer and use it in GitHub Desktop.
Save huacnlee/37a84e47bf77efbd4aeb53794952de47 to your computer and use it in GitHub Desktop.
GPUI build line path by points
fn build_line_path(points: Vec<Point<Pixels>>, width: f32) -> Path<Pixels> {
let mut path = Path::new(point(points[0].x, points[0].y));
let half_width = width / 2.0;
let angle_threshold: f32 = 15.;
// 4~6 for performance, 8~12 for medium, 16~24 for high quality
const SEGMENT: usize = 0;
let angle_threshold_cos = angle_threshold.to_radians().cos();
for i in 0..points.len() - 1 {
let p0 = points[i];
let p1 = points[i + 1];
// Calculate direction vector and normal
let dx = p1.x - p0.x;
let dy = p1.y - p0.y;
let length = (dx * dx + dy * dy).0.sqrt();
let dir = [dx / length, dy / length];
let normal = [-dir[1] * half_width, dir[0] * half_width];
// Current segment boundary vertices
let left0 = [p0.x - normal[0], p0.y - normal[1]];
let right0 = [p0.x + normal[0], p0.y + normal[1]];
let left1 = [p1.x - normal[0], p1.y - normal[1]];
let right1 = [p1.x + normal[0], p1.y + normal[1]];
// Add main triangles of the current segment
path.move_to(point(left0[0], left0[1]));
path.line_to(point(right0[0], right0[1]));
path.line_to(point(left1[0], left1[1]));
path.move_to(point(right0[0], right0[1]));
path.line_to(point(right1[0], right1[1]));
path.line_to(point(left1[0], left1[1]));
// Corner handling
if i < points.len() - 2 {
let p2 = points[i + 2];
// Previous and next direction vectors
let next_length = ((p2.x - p1.x).0.powi(2) + (p2.y - p1.y).0.powi(2)).sqrt();
let prev_dir = [dir[0], dir[1]];
let next_dir = [(p2.x - p1.x) / next_length, (p2.y - p1.y) / next_length];
// Calculate angle
let cos_angle = prev_dir[0] * next_dir[0] + prev_dir[1] * next_dir[1];
if cos_angle.0 < -0.99 {
// 180 degree turn: fill intersection area
path.line_to(point(p1.x - normal[0], p1.y - normal[1]));
path.line_to(point(p1.x + normal[0], p1.y + normal[1]));
continue;
} else if cos_angle.0 > angle_threshold_cos {
// Sharp angle: fill intersection area, generate polygon cover
let mut intersection_points = vec![
[p1.x + normal[0], p1.y + normal[1]],
[p1.x - normal[0], p1.y - normal[1]],
];
let step = (1.0 - cos_angle.0) * (std::f32::consts::PI / 2.0) / SEGMENT as f32;
for j in 0..=SEGMENT {
let theta = j as f32 * step;
let rotated = [
prev_dir[0] * theta.cos() - prev_dir[1] * theta.sin(),
prev_dir[0] * theta.sin() + prev_dir[1] * theta.cos(),
];
let rounded_vertex = [
p1.x + rotated[0] * half_width,
p1.y + rotated[1] * half_width,
];
intersection_points.push(rounded_vertex);
}
for k in 1..intersection_points.len() - 1 {
path.move_to(point(intersection_points[0][0], intersection_points[0][1]));
path.line_to(point(intersection_points[k][0], intersection_points[k][1]));
path.line_to(point(
intersection_points[k + 1][0],
intersection_points[k + 1][1],
));
}
} else {
// Regular corner handling
let step = (std::f32::consts::PI - cos_angle.0.acos()) / SEGMENT as f32;
for j in 0..=SEGMENT {
let theta = j as f32 * step;
let rotated = [
prev_dir[0] * theta.cos() - prev_dir[1] * theta.sin(),
prev_dir[0] * theta.sin() + prev_dir[1] * theta.cos(),
];
let rounded_vertex = [
p1.x + rotated[0] * half_width,
p1.y + rotated[1] * half_width,
];
path.line_to(point(rounded_vertex[0], rounded_vertex[1]));
}
}
}
}
path
}
@huacnlee
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment