Improve the Bézier demo: drag control points and simplify code
Follow-up to https://github.com/emilk/egui/pull/1178
This commit is contained in:
parent
3a5ec4733f
commit
10634fc344
3 changed files with 150 additions and 217 deletions
|
@ -16,6 +16,7 @@ struct Demos {
|
||||||
impl Default for Demos {
|
impl Default for Demos {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::from_demos(vec![
|
Self::from_demos(vec![
|
||||||
|
Box::new(super::paint_bezier::PaintBezier::default()),
|
||||||
Box::new(super::code_editor::CodeEditor::default()),
|
Box::new(super::code_editor::CodeEditor::default()),
|
||||||
Box::new(super::code_example::CodeExample::default()),
|
Box::new(super::code_example::CodeExample::default()),
|
||||||
Box::new(super::context_menu::ContextMenus::default()),
|
Box::new(super::context_menu::ContextMenus::default()),
|
||||||
|
@ -25,7 +26,6 @@ impl Default for Demos {
|
||||||
Box::new(super::MiscDemoWindow::default()),
|
Box::new(super::MiscDemoWindow::default()),
|
||||||
Box::new(super::multi_touch::MultiTouch::default()),
|
Box::new(super::multi_touch::MultiTouch::default()),
|
||||||
Box::new(super::painting::Painting::default()),
|
Box::new(super::painting::Painting::default()),
|
||||||
Box::new(super::paint_bezier::PaintBezier::default()),
|
|
||||||
Box::new(super::plot_demo::PlotDemo::default()),
|
Box::new(super::plot_demo::PlotDemo::default()),
|
||||||
Box::new(super::scrolling::Scrolling::default()),
|
Box::new(super::scrolling::Scrolling::default()),
|
||||||
Box::new(super::sliders::Sliders::default()),
|
Box::new(super::sliders::Sliders::default()),
|
||||||
|
|
|
@ -1,52 +1,40 @@
|
||||||
use egui::emath::RectTransform;
|
use egui::epaint::{CubicBezierShape, PathShape, QuadraticBezierShape};
|
||||||
use egui::epaint::{CircleShape, CubicBezierShape, QuadraticBezierShape};
|
|
||||||
use egui::*;
|
use egui::*;
|
||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
#[cfg_attr(feature = "serde", serde(default))]
|
#[cfg_attr(feature = "serde", serde(default))]
|
||||||
pub struct PaintBezier {
|
pub struct PaintBezier {
|
||||||
/// Current bezier curve degree, it can be 3, 4.
|
/// Bézier curve degree, it can be 3, 4.
|
||||||
bezier: usize,
|
degree: usize,
|
||||||
/// Track the bezier degree before change in order to clean the remaining points.
|
/// The control points. The [`Self::degree`] first of them are used.
|
||||||
degree_backup: usize,
|
control_points: [Pos2; 4],
|
||||||
/// Points already clicked. once it reaches the 'bezier' degree, it will be pushed into the 'shapes'
|
|
||||||
points: Vec<Pos2>,
|
/// Stroke for Bézier curve.
|
||||||
/// Track last points set in order to draw auxiliary lines.
|
stroke: Stroke,
|
||||||
backup_points: Vec<Pos2>,
|
|
||||||
/// Quadratic shapes already drawn.
|
/// Fill for Bézier curve.
|
||||||
q_shapes: Vec<QuadraticBezierShape>,
|
fill: Color32,
|
||||||
/// Cubic shapes already drawn.
|
|
||||||
/// Since `Shape` can't be 'serialized', we can't use Shape as variable type.
|
|
||||||
c_shapes: Vec<CubicBezierShape>,
|
|
||||||
/// Stroke for auxiliary lines.
|
/// Stroke for auxiliary lines.
|
||||||
aux_stroke: Stroke,
|
aux_stroke: Stroke,
|
||||||
/// Stroke for bezier curve.
|
|
||||||
stroke: Stroke,
|
|
||||||
/// Fill for bezier curve.
|
|
||||||
fill: Color32,
|
|
||||||
/// The curve should be closed or not.
|
|
||||||
closed: bool,
|
|
||||||
/// Display the bounding box or not.
|
|
||||||
show_bounding_box: bool,
|
|
||||||
/// Storke for the bounding box.
|
|
||||||
bounding_box_stroke: Stroke,
|
bounding_box_stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PaintBezier {
|
impl Default for PaintBezier {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
bezier: 4, // default bezier degree, a cubic bezier curve
|
degree: 4,
|
||||||
degree_backup: 4,
|
control_points: [
|
||||||
points: Default::default(),
|
pos2(50.0, 50.0),
|
||||||
backup_points: Default::default(),
|
pos2(60.0, 150.0),
|
||||||
q_shapes: Default::default(),
|
pos2(140.0, 150.0),
|
||||||
c_shapes: Default::default(),
|
pos2(150.0, 50.0),
|
||||||
aux_stroke: Stroke::new(1.0, Color32::RED),
|
],
|
||||||
stroke: Stroke::new(1.0, Color32::LIGHT_BLUE),
|
stroke: Stroke::new(1.0, Color32::LIGHT_BLUE),
|
||||||
fill: Default::default(),
|
fill: Color32::from_rgb(50, 100, 150).linear_multiply(0.25),
|
||||||
closed: false,
|
aux_stroke: Stroke::new(1.0, Color32::RED.linear_multiply(0.25)),
|
||||||
show_bounding_box: false,
|
bounding_box_stroke: Stroke::new(0.0, Color32::LIGHT_GREEN.linear_multiply(0.25)),
|
||||||
bounding_box_stroke: Stroke::new(1.0, Color32::LIGHT_GREEN),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,187 +43,125 @@ impl PaintBezier {
|
||||||
pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
|
ui.radio_value(&mut self.degree, 3, "Quadratic Bézier");
|
||||||
|
ui.radio_value(&mut self.degree, 4, "Cubic Bézier");
|
||||||
|
ui.label("Move the points by dragging them.");
|
||||||
|
ui.label("Only convex curves can be accurately filled.")
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
|
||||||
|
ui.vertical(|ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Fill color:");
|
||||||
|
ui.color_edit_button_srgba(&mut self.fill);
|
||||||
|
});
|
||||||
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
|
egui::stroke_ui(ui, &mut self.stroke, "Curve Stroke");
|
||||||
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
|
egui::stroke_ui(ui, &mut self.aux_stroke, "Auxiliary Stroke");
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.label("Fill Color:");
|
|
||||||
if ui.color_edit_button_srgba(&mut self.fill).changed()
|
|
||||||
&& self.fill != Color32::TRANSPARENT
|
|
||||||
{
|
|
||||||
self.closed = true;
|
|
||||||
}
|
|
||||||
if ui.checkbox(&mut self.closed, "Closed").clicked() && !self.closed {
|
|
||||||
self.fill = Color32::TRANSPARENT;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
|
egui::stroke_ui(ui, &mut self.bounding_box_stroke, "Bounding Box Stroke");
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
ui.vertical(|ui| {
|
||||||
{
|
ui.label("Global tessellation options:");
|
||||||
let mut tessellation_options = *(ui.ctx().tessellation_options());
|
let mut tessellation_options = *(ui.ctx().tessellation_options());
|
||||||
let tessellation_options = &mut tessellation_options;
|
let tessellation_options = &mut tessellation_options;
|
||||||
tessellation_options.ui(ui);
|
tessellation_options.ui(ui);
|
||||||
let mut new_tessellation_options = ui.ctx().tessellation_options();
|
let mut new_tessellation_options = ui.ctx().tessellation_options();
|
||||||
*new_tessellation_options = *tessellation_options;
|
*new_tessellation_options = *tessellation_options;
|
||||||
}
|
|
||||||
|
|
||||||
ui.checkbox(&mut self.show_bounding_box, "Bounding Box");
|
|
||||||
});
|
});
|
||||||
ui.separator();
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
if ui.radio_value(&mut self.bezier, 3, "Quadratic").clicked()
|
|
||||||
&& self.degree_backup != self.bezier
|
|
||||||
{
|
|
||||||
self.points.clear();
|
|
||||||
self.degree_backup = self.bezier;
|
|
||||||
};
|
|
||||||
if ui.radio_value(&mut self.bezier, 4, "Cubic").clicked()
|
|
||||||
&& self.degree_backup != self.bezier
|
|
||||||
{
|
|
||||||
self.points.clear();
|
|
||||||
self.degree_backup = self.bezier;
|
|
||||||
};
|
|
||||||
// ui.radio_value(self.bezier, 5, "Quintic");
|
|
||||||
ui.label("Click 3 or 4 points to build a bezier curve!");
|
|
||||||
if ui.button("Clear Painting").clicked() {
|
|
||||||
self.points.clear();
|
|
||||||
self.backup_points.clear();
|
|
||||||
self.q_shapes.clear();
|
|
||||||
self.c_shapes.clear();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.response
|
.response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response {
|
pub fn ui_content(&mut self, ui: &mut Ui) -> egui::Response {
|
||||||
let (mut response, painter) =
|
let (response, painter) =
|
||||||
ui.allocate_painter(ui.available_size_before_wrap(), Sense::click());
|
ui.allocate_painter(Vec2::new(ui.available_width(), 300.0), Sense::hover());
|
||||||
|
|
||||||
let to_screen = emath::RectTransform::from_to(
|
let to_screen = emath::RectTransform::from_to(
|
||||||
Rect::from_min_size(Pos2::ZERO, response.rect.square_proportions()),
|
Rect::from_min_size(Pos2::ZERO, response.rect.size()),
|
||||||
response.rect,
|
response.rect,
|
||||||
);
|
);
|
||||||
let from_screen = to_screen.inverse();
|
|
||||||
|
|
||||||
if response.clicked() {
|
let control_point_radius = 8.0;
|
||||||
if let Some(pointer_pos) = response.interact_pointer_pos() {
|
|
||||||
let canvas_pos = from_screen * pointer_pos;
|
let mut control_point_shapes = vec![];
|
||||||
self.points.push(canvas_pos);
|
|
||||||
if self.points.len() >= self.bezier {
|
for (i, point) in self.control_points.iter_mut().enumerate().take(self.degree) {
|
||||||
self.backup_points = self.points.clone();
|
let size = Vec2::splat(2.0 * control_point_radius);
|
||||||
let points = self.points.drain(..).collect::<Vec<_>>();
|
|
||||||
match points.len() {
|
let point_in_screen = to_screen.transform_pos(*point);
|
||||||
|
let point_rect = Rect::from_center_size(point_in_screen, size);
|
||||||
|
let point_id = response.id.with(i);
|
||||||
|
let point_response = ui.interact(point_rect, point_id, Sense::drag());
|
||||||
|
|
||||||
|
*point += point_response.drag_delta();
|
||||||
|
*point = to_screen.from().clamp(*point);
|
||||||
|
|
||||||
|
let point_in_screen = to_screen.transform_pos(*point);
|
||||||
|
let stroke = ui.style().interact(&point_response).fg_stroke;
|
||||||
|
|
||||||
|
control_point_shapes.push(Shape::circle_stroke(
|
||||||
|
point_in_screen,
|
||||||
|
control_point_radius,
|
||||||
|
stroke,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let points_in_screen: Vec<Pos2> = self
|
||||||
|
.control_points
|
||||||
|
.iter()
|
||||||
|
.take(self.degree)
|
||||||
|
.map(|p| to_screen * *p)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
match self.degree {
|
||||||
3 => {
|
3 => {
|
||||||
let quadratic = QuadraticBezierShape::from_points_stroke(
|
let points = points_in_screen.clone().try_into().unwrap();
|
||||||
points,
|
let shape =
|
||||||
self.closed,
|
QuadraticBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
|
||||||
self.fill,
|
painter.add(epaint::RectShape::stroke(
|
||||||
self.stroke,
|
shape.bounding_rect(),
|
||||||
);
|
0.0,
|
||||||
self.q_shapes.push(quadratic);
|
self.bounding_box_stroke,
|
||||||
|
));
|
||||||
|
painter.add(shape);
|
||||||
}
|
}
|
||||||
4 => {
|
4 => {
|
||||||
let cubic = CubicBezierShape::from_points_stroke(
|
let points = points_in_screen.clone().try_into().unwrap();
|
||||||
points,
|
let shape =
|
||||||
self.closed,
|
CubicBezierShape::from_points_stroke(points, true, self.fill, self.stroke);
|
||||||
self.fill,
|
painter.add(epaint::RectShape::stroke(
|
||||||
self.stroke,
|
shape.bounding_rect(),
|
||||||
);
|
0.0,
|
||||||
self.c_shapes.push(cubic);
|
self.bounding_box_stroke,
|
||||||
|
));
|
||||||
|
painter.add(shape);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
response.mark_changed();
|
painter.add(PathShape::line(points_in_screen, self.aux_stroke));
|
||||||
}
|
painter.extend(control_point_shapes);
|
||||||
}
|
|
||||||
let mut shapes = Vec::new();
|
|
||||||
for shape in self.q_shapes.iter() {
|
|
||||||
shapes.push(shape.to_screen(&to_screen).into());
|
|
||||||
if self.show_bounding_box {
|
|
||||||
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for shape in self.c_shapes.iter() {
|
|
||||||
shapes.push(shape.to_screen(&to_screen).into());
|
|
||||||
if self.show_bounding_box {
|
|
||||||
shapes.push(self.build_bounding_box(shape.bounding_rect(), &to_screen));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
painter.extend(shapes);
|
|
||||||
|
|
||||||
if !self.points.is_empty() {
|
|
||||||
painter.extend(build_auxiliary_line(
|
|
||||||
&self.points,
|
|
||||||
&to_screen,
|
|
||||||
&self.aux_stroke,
|
|
||||||
));
|
|
||||||
} else if !self.backup_points.is_empty() {
|
|
||||||
painter.extend(build_auxiliary_line(
|
|
||||||
&self.backup_points,
|
|
||||||
&to_screen,
|
|
||||||
&self.aux_stroke,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_bounding_box(&self, bbox: Rect, to_screen: &RectTransform) -> Shape {
|
|
||||||
let bbox = Rect {
|
|
||||||
min: to_screen * bbox.min,
|
|
||||||
max: to_screen * bbox.max,
|
|
||||||
};
|
|
||||||
let bbox_shape = epaint::RectShape::stroke(bbox, 0.0, self.bounding_box_stroke);
|
|
||||||
bbox_shape.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An internal function to create auxiliary lines around the current bezier curve
|
|
||||||
/// or to auxiliary lines (points) before the points meet the bezier curve requirements.
|
|
||||||
fn build_auxiliary_line(
|
|
||||||
points: &[Pos2],
|
|
||||||
to_screen: &RectTransform,
|
|
||||||
aux_stroke: &Stroke,
|
|
||||||
) -> Vec<Shape> {
|
|
||||||
let mut shapes = Vec::new();
|
|
||||||
if points.len() >= 2 {
|
|
||||||
let points: Vec<Pos2> = points.iter().map(|p| to_screen * *p).collect();
|
|
||||||
shapes.push(egui::Shape::line(points, *aux_stroke));
|
|
||||||
}
|
|
||||||
for point in points.iter() {
|
|
||||||
let center = to_screen * *point;
|
|
||||||
let radius = aux_stroke.width * 3.0;
|
|
||||||
let circle = CircleShape {
|
|
||||||
center,
|
|
||||||
radius,
|
|
||||||
fill: aux_stroke.color,
|
|
||||||
stroke: *aux_stroke,
|
|
||||||
};
|
|
||||||
|
|
||||||
shapes.push(circle.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
shapes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Demo for PaintBezier {
|
impl super::Demo for PaintBezier {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"✔ Bezier Curve"
|
") Bézier Curve"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
fn show(&mut self, ctx: &Context, open: &mut bool) {
|
||||||
use super::View as _;
|
use super::View as _;
|
||||||
Window::new(self.name())
|
Window::new(self.name())
|
||||||
.open(open)
|
.open(open)
|
||||||
.default_size(vec2(512.0, 512.0))
|
|
||||||
.vscroll(false)
|
.vscroll(false)
|
||||||
|
.resizable(false)
|
||||||
.show(ctx, |ui| self.ui(ui));
|
.show(ctx, |ui| self.ui(ui));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ use emath::*;
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
/// How to paint a cubic Bezier curve on screen.
|
/// A cubic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||||
/// The definition: [Bezier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
///
|
||||||
/// This implementation is only for cubic Bezier curve, or the Bezier curve of degree 3.
|
/// See also [`QuadraticBezierShape`].
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct CubicBezierShape {
|
pub struct CubicBezierShape {
|
||||||
|
@ -22,30 +22,29 @@ pub struct CubicBezierShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CubicBezierShape {
|
impl CubicBezierShape {
|
||||||
/// Creates a cubic Bezier curve based on 4 points and stroke.
|
/// Creates a cubic Bézier curve based on 4 points and stroke.
|
||||||
|
///
|
||||||
/// The first point is the starting point and the last one is the ending point of the curve.
|
/// The first point is the starting point and the last one is the ending point of the curve.
|
||||||
/// The middle points are the control points.
|
/// The middle points are the control points.
|
||||||
/// The number of points must be 4.
|
|
||||||
pub fn from_points_stroke(
|
pub fn from_points_stroke(
|
||||||
points: Vec<Pos2>,
|
points: [Pos2; 4],
|
||||||
closed: bool,
|
closed: bool,
|
||||||
fill: Color32,
|
fill: Color32,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<Stroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::epaint_assert!(points.len() == 4, "Cubic needs 4 points");
|
|
||||||
Self {
|
Self {
|
||||||
points: points.try_into().unwrap(),
|
points,
|
||||||
closed,
|
closed,
|
||||||
fill,
|
fill,
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a cubic Bezier curve based on the screen coordinates for the 4 points.
|
/// Transform the curve with the given transform.
|
||||||
pub fn to_screen(&self, to_screen: &RectTransform) -> Self {
|
pub fn transform(&self, transform: &RectTransform) -> Self {
|
||||||
let mut points = [Pos2::default(); 4];
|
let mut points = [Pos2::default(); 4];
|
||||||
for (i, origin_point) in self.points.iter().enumerate() {
|
for (i, origin_point) in self.points.iter().enumerate() {
|
||||||
points[i] = to_screen * *origin_point;
|
points[i] = transform * *origin_point;
|
||||||
}
|
}
|
||||||
CubicBezierShape {
|
CubicBezierShape {
|
||||||
points,
|
points,
|
||||||
|
@ -55,12 +54,12 @@ impl CubicBezierShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the cubic Bezier curve to one or two `PathShapes`.
|
/// Convert the cubic Bézier curve to one or two `PathShapes`.
|
||||||
/// When the curve is closed and it has to intersect with the base line, it will be converted into two shapes.
|
/// When the curve is closed and it has to intersect with the base line, it will be converted into two shapes.
|
||||||
/// Otherwise, it will be converted into one shape.
|
/// Otherwise, it will be converted into one shape.
|
||||||
/// The `tolerance` will be used to control the max distance between the curve and the base line.
|
/// The `tolerance` will be used to control the max distance between the curve and the base line.
|
||||||
/// The `epsilon` is used when comparing two floats.
|
/// The `epsilon` is used when comparing two floats.
|
||||||
pub fn to_pathshapes(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<PathShape> {
|
pub fn to_path_shapes(&self, tolerance: Option<f32>, epsilon: Option<f32>) -> Vec<PathShape> {
|
||||||
let mut pathshapes = Vec::new();
|
let mut pathshapes = Vec::new();
|
||||||
let mut points_vec = self.flatten_closed(tolerance, epsilon);
|
let mut points_vec = self.flatten_closed(tolerance, epsilon);
|
||||||
for points in points_vec.drain(..) {
|
for points in points_vec.drain(..) {
|
||||||
|
@ -74,6 +73,7 @@ impl CubicBezierShape {
|
||||||
}
|
}
|
||||||
pathshapes
|
pathshapes
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Screen-space bounding rectangle.
|
/// Screen-space bounding rectangle.
|
||||||
pub fn bounding_rect(&self) -> Rect {
|
pub fn bounding_rect(&self) -> Rect {
|
||||||
//temporary solution
|
//temporary solution
|
||||||
|
@ -256,9 +256,9 @@ impl CubicBezierShape {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the point (x,y) at t based on the cubic bezier curve equation.
|
/// Calculate the point (x,y) at t based on the cubic Bézier curve equation.
|
||||||
/// t is in [0.0,1.0]
|
/// t is in [0.0,1.0]
|
||||||
/// [Bezier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves)
|
/// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves)
|
||||||
///
|
///
|
||||||
pub fn sample(&self, t: f32) -> Pos2 {
|
pub fn sample(&self, t: f32) -> Pos2 {
|
||||||
crate::epaint_assert!(
|
crate::epaint_assert!(
|
||||||
|
@ -278,7 +278,7 @@ impl CubicBezierShape {
|
||||||
result.to_pos2()
|
result.to_pos2()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// find a set of points that approximate the cubic bezier curve.
|
/// find a set of points that approximate the cubic Bézier curve.
|
||||||
/// the number of points is determined by the tolerance.
|
/// the number of points is determined by the tolerance.
|
||||||
/// the points may not be evenly distributed in the range [0.0,1.0] (t value)
|
/// the points may not be evenly distributed in the range [0.0,1.0] (t value)
|
||||||
pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
|
pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
|
||||||
|
@ -290,7 +290,7 @@ impl CubicBezierShape {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// find a set of points that approximate the cubic bezier curve.
|
/// find a set of points that approximate the cubic Bézier curve.
|
||||||
/// the number of points is determined by the tolerance.
|
/// the number of points is determined by the tolerance.
|
||||||
/// the points may not be evenly distributed in the range [0.0,1.0] (t value)
|
/// the points may not be evenly distributed in the range [0.0,1.0] (t value)
|
||||||
/// this api will check whether the curve will cross the base line or not when closed = true.
|
/// this api will check whether the curve will cross the base line or not when closed = true.
|
||||||
|
@ -358,6 +358,11 @@ impl From<CubicBezierShape> for Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// A quadratic [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve).
|
||||||
|
///
|
||||||
|
/// See also [`CubicBezierShape`].
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
pub struct QuadraticBezierShape {
|
pub struct QuadraticBezierShape {
|
||||||
|
@ -371,32 +376,30 @@ pub struct QuadraticBezierShape {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QuadraticBezierShape {
|
impl QuadraticBezierShape {
|
||||||
/// create a new quadratic bezier shape based on the 3 points and stroke.
|
/// Create a new quadratic Bézier shape based on the 3 points and stroke.
|
||||||
/// the first point is the starting point and the last one is the ending point of the curve.
|
|
||||||
/// the middle point is the control points.
|
|
||||||
/// the points should be in the order [start, control, end]
|
|
||||||
///
|
///
|
||||||
|
/// The first point is the starting point and the last one is the ending point of the curve.
|
||||||
|
/// The middle point is the control points.
|
||||||
|
/// The points should be in the order [start, control, end]
|
||||||
pub fn from_points_stroke(
|
pub fn from_points_stroke(
|
||||||
points: Vec<Pos2>,
|
points: [Pos2; 3],
|
||||||
closed: bool,
|
closed: bool,
|
||||||
fill: Color32,
|
fill: Color32,
|
||||||
stroke: impl Into<Stroke>,
|
stroke: impl Into<Stroke>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
crate::epaint_assert!(points.len() == 3, "Quadratic needs 3 points");
|
|
||||||
|
|
||||||
QuadraticBezierShape {
|
QuadraticBezierShape {
|
||||||
points: points.try_into().unwrap(), // it's safe to unwrap because we just checked
|
points,
|
||||||
closed,
|
closed,
|
||||||
fill,
|
fill,
|
||||||
stroke: stroke.into(),
|
stroke: stroke.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create a new quadratic bezier shape based on the screen coordination for the 3 points.
|
/// Transform the curve with the given transform.
|
||||||
pub fn to_screen(&self, to_screen: &RectTransform) -> Self {
|
pub fn transform(&self, transform: &RectTransform) -> Self {
|
||||||
let mut points = [Pos2::default(); 3];
|
let mut points = [Pos2::default(); 3];
|
||||||
for (i, origin_point) in self.points.iter().enumerate() {
|
for (i, origin_point) in self.points.iter().enumerate() {
|
||||||
points[i] = to_screen * *origin_point;
|
points[i] = transform * *origin_point;
|
||||||
}
|
}
|
||||||
QuadraticBezierShape {
|
QuadraticBezierShape {
|
||||||
points,
|
points,
|
||||||
|
@ -406,9 +409,9 @@ impl QuadraticBezierShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert the quadratic Bezier curve to one `PathShape`.
|
/// Convert the quadratic Bézier curve to one `PathShape`.
|
||||||
/// The `tolerance` will be used to control the max distance between the curve and the base line.
|
/// The `tolerance` will be used to control the max distance between the curve and the base line.
|
||||||
pub fn to_pathshape(&self, tolerance: Option<f32>) -> PathShape {
|
pub fn to_path_shape(&self, tolerance: Option<f32>) -> PathShape {
|
||||||
let points = self.flatten(tolerance);
|
let points = self.flatten(tolerance);
|
||||||
PathShape {
|
PathShape {
|
||||||
points,
|
points,
|
||||||
|
@ -417,7 +420,8 @@ impl QuadraticBezierShape {
|
||||||
stroke: self.stroke,
|
stroke: self.stroke,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// bounding box of the quadratic bezier shape
|
|
||||||
|
/// bounding box of the quadratic Bézier shape
|
||||||
pub fn bounding_rect(&self) -> Rect {
|
pub fn bounding_rect(&self) -> Rect {
|
||||||
let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x {
|
let (mut min_x, mut max_x) = if self.points[0].x < self.points[2].x {
|
||||||
(self.points[0].x, self.points[2].x)
|
(self.points[0].x, self.points[2].x)
|
||||||
|
@ -466,9 +470,9 @@ impl QuadraticBezierShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the point (x,y) at t based on the quadratic bezier curve equation.
|
/// Calculate the point (x,y) at t based on the quadratic Bézier curve equation.
|
||||||
/// t is in [0.0,1.0]
|
/// t is in [0.0,1.0]
|
||||||
/// [Bezier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves)
|
/// [Bézier Curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Quadratic_B.C3.A9zier_curves)
|
||||||
///
|
///
|
||||||
pub fn sample(&self, t: f32) -> Pos2 {
|
pub fn sample(&self, t: f32) -> Pos2 {
|
||||||
crate::epaint_assert!(
|
crate::epaint_assert!(
|
||||||
|
@ -486,7 +490,7 @@ impl QuadraticBezierShape {
|
||||||
result.to_pos2()
|
result.to_pos2()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// find a set of points that approximate the quadratic bezier curve.
|
/// find a set of points that approximate the quadratic Bézier curve.
|
||||||
/// the number of points is determined by the tolerance.
|
/// the number of points is determined by the tolerance.
|
||||||
/// the points may not be evenly distributed in the range [0.0,1.0] (t value)
|
/// the points may not be evenly distributed in the range [0.0,1.0] (t value)
|
||||||
pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
|
pub fn flatten(&self, tolerance: Option<f32>) -> Vec<Pos2> {
|
||||||
|
@ -533,6 +537,8 @@ impl From<QuadraticBezierShape> for Shape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// lyon_geom::flatten_cubic.rs
|
// lyon_geom::flatten_cubic.rs
|
||||||
// copied from https://docs.rs/lyon_geom/latest/lyon_geom/
|
// copied from https://docs.rs/lyon_geom/latest/lyon_geom/
|
||||||
fn flatten_cubic_bezier_with_t<F: FnMut(Pos2, f32)>(
|
fn flatten_cubic_bezier_with_t<F: FnMut(Pos2, f32)>(
|
||||||
|
@ -567,6 +573,7 @@ fn flatten_cubic_bezier_with_t<F: FnMut(Pos2, f32)>(
|
||||||
callback(point, t);
|
callback(point, t);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// from lyon_geom::quadratic_bezier.rs
|
// from lyon_geom::quadratic_bezier.rs
|
||||||
// copied from https://docs.rs/lyon_geom/latest/lyon_geom/
|
// copied from https://docs.rs/lyon_geom/latest/lyon_geom/
|
||||||
struct FlatteningParameters {
|
struct FlatteningParameters {
|
||||||
|
@ -665,7 +672,7 @@ fn single_curve_approximation(curve: &CubicBezierShape) -> QuadraticBezierShape
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, cb: &mut F) {
|
fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, cb: &mut F) {
|
||||||
// A quadratic bezier curve can be derived by a linear function:
|
// A quadratic Bézier curve can be derived by a linear function:
|
||||||
// p(t) = p0 + t(p1 - p0) + t^2(p2 - 2p1 + p0)
|
// p(t) = p0 + t(p1 - p0) + t^2(p2 - 2p1 + p0)
|
||||||
// The derivative is:
|
// The derivative is:
|
||||||
// p'(t) = (p1 - p0) + 2(p2 - 2p1 + p0)t or:
|
// p'(t) = (p1 - p0) + 2(p2 - 2p1 + p0)t or:
|
||||||
|
@ -685,7 +692,7 @@ fn quadratic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, c
|
||||||
|
|
||||||
fn cubic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) {
|
fn cubic_for_each_local_extremum<F: FnMut(f32)>(p0: f32, p1: f32, p2: f32, p3: f32, cb: &mut F) {
|
||||||
// See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation
|
// See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation
|
||||||
// A cubic bezier curve can be derivated by the following equation:
|
// A cubic Bézier curve can be derivated by the following equation:
|
||||||
// B'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2) or
|
// B'(t) = 3(1-t)^2(p1-p0) + 6(1-t)t(p2-p1) + 3t^2(p3-p2) or
|
||||||
// f(x) = a * x² + b * x + c
|
// f(x) = a * x² + b * x + c
|
||||||
let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0);
|
let a = 3.0 * (p3 + 3.0 * (p1 - p2) - p0);
|
||||||
|
|
Loading…
Reference in a new issue