Refactor the Centroid node and `Subpath` struct and methods to use Kurbo, eliminating all remaining usages of Bezier-rs (#3036)
* define Subpath struct in gcore and refactor node-graph * Refactor few methods * refactoring worked! * refactor centoid area and length * remove unused * cleanup * fix pathseg_points function * fix tranforming segments * fix segment intersection * refactor to_path_segments fn in gpath-bool crate * refactor gcraft * add bezier-rs dep * Code review the editor directory * use path-bool for solving roots * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
99984fc2d6
commit
d22b2ca927
|
|
@ -1885,7 +1885,6 @@ dependencies = [
|
|||
name = "graph-craft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"criterion",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
|
|
@ -1963,7 +1962,6 @@ name = "graphene-core"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bezier-rs",
|
||||
"bytemuck",
|
||||
"ctor",
|
||||
"dyn-any",
|
||||
|
|
@ -1976,6 +1974,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"parley",
|
||||
"petgraph 0.7.1",
|
||||
"poly-cool",
|
||||
"rand 0.9.1",
|
||||
"rand_chacha 0.9.0",
|
||||
"rustc-hash 2.1.1",
|
||||
|
|
@ -2020,10 +2019,10 @@ dependencies = [
|
|||
name = "graphene-path-bool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"kurbo",
|
||||
"log",
|
||||
"node-macro",
|
||||
"path-bool",
|
||||
|
|
@ -2035,7 +2034,6 @@ dependencies = [
|
|||
name = "graphene-raster-nodes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"bytemuck",
|
||||
"dyn-any",
|
||||
"fastnoise-lite",
|
||||
|
|
@ -2044,6 +2042,7 @@ dependencies = [
|
|||
"graphene-core",
|
||||
"graphene-core-shaders",
|
||||
"image",
|
||||
"kurbo",
|
||||
"ndarray",
|
||||
"node-macro",
|
||||
"rand 0.9.1",
|
||||
|
|
@ -2091,10 +2090,10 @@ name = "graphene-svg-renderer"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bezier-rs",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"kurbo",
|
||||
"log",
|
||||
"num-traits",
|
||||
"serde",
|
||||
|
|
@ -2134,7 +2133,6 @@ name = "graphite-editor"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bezier-rs",
|
||||
"bitflags 2.9.1",
|
||||
"bytemuck",
|
||||
"derivative",
|
||||
|
|
|
|||
24
Cargo.toml
24
Cargo.toml
|
|
@ -45,8 +45,14 @@ resolver = "2"
|
|||
[workspace.dependencies]
|
||||
# Local dependencies
|
||||
bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] }
|
||||
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] }
|
||||
preprocessor = { path = "node-graph/preprocessor"}
|
||||
dyn-any = { path = "libraries/dyn-any", features = [
|
||||
"derive",
|
||||
"glam",
|
||||
"reqwest",
|
||||
"log-bad-types",
|
||||
"rc",
|
||||
] }
|
||||
preprocessor = { path = "node-graph/preprocessor" }
|
||||
math-parser = { path = "libraries/math-parser" }
|
||||
path-bool = { path = "libraries/path-bool" }
|
||||
graphene-application-io = { path = "node-graph/gapplication-io" }
|
||||
|
|
@ -80,7 +86,7 @@ convert_case = "0.7"
|
|||
derivative = "2.2"
|
||||
thiserror = "2"
|
||||
anyhow = "1.0"
|
||||
proc-macro2 = { version = "1", features = [ "span-locations" ] }
|
||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
axum = "0.8"
|
||||
chrono = "0.4"
|
||||
|
|
@ -122,9 +128,17 @@ resvg = "0.44"
|
|||
usvg = "0.44"
|
||||
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
|
||||
rand_chacha = "0.9"
|
||||
glam = { version = "0.29", default-features = false, features = ["serde", "scalar-math", "debug-glam-assert"] }
|
||||
glam = { version = "0.29", default-features = false, features = [
|
||||
"serde",
|
||||
"scalar-math",
|
||||
"debug-glam-assert",
|
||||
] }
|
||||
base64 = "0.22"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp"] }
|
||||
image = { version = "0.25", default-features = false, features = [
|
||||
"png",
|
||||
"jpeg",
|
||||
"bmp",
|
||||
] }
|
||||
parley = "0.5.0"
|
||||
skrifa = "0.32.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ ron = ["dep:ron"]
|
|||
graphite-proc-macros = { workspace = true }
|
||||
graph-craft = { workspace = true }
|
||||
interpreted-executor = { workspace = true }
|
||||
graphene-std = { workspace = true }
|
||||
graphene-std = { workspace = true } # NOTE: `graphene-core` should not be added here because `graphene-std` re-exports its contents
|
||||
preprocessor = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
|
|
@ -33,7 +33,6 @@ bitflags = { workspace = true }
|
|||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
|||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||
|
|
@ -35,10 +34,13 @@ use graphene_std::math::quad::Quad;
|
|||
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use kurbo::{Affine, CubicBez, Line, ParamCurve, PathSeg, QuadBez};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -2982,10 +2984,10 @@ fn quad_to_path_lib_segments(quad: Quad) -> Vec<path_bool_lib::PathSegment> {
|
|||
}
|
||||
|
||||
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
|
||||
let segment = |bezier: bezier_rs::Bezier| match bezier.handles {
|
||||
bezier_rs::BezierHandles::Linear => path_bool_lib::PathSegment::Line(bezier.start, bezier.end),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => path_bool_lib::PathSegment::Quadratic(bezier.start, handle, bezier.end),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path_bool_lib::PathSegment::Cubic(bezier.start, handle_start, handle_end, bezier.end),
|
||||
let segment = |bezier: PathSeg| match bezier {
|
||||
PathSeg::Line(line) => path_bool_lib::PathSegment::Line(point_to_dvec2(line.p0), point_to_dvec2(line.p1)),
|
||||
PathSeg::Quad(quad_bez) => path_bool_lib::PathSegment::Quadratic(point_to_dvec2(quad_bez.p0), point_to_dvec2(quad_bez.p1), point_to_dvec2(quad_bez.p2)),
|
||||
PathSeg::Cubic(cubic_bez) => path_bool_lib::PathSegment::Cubic(point_to_dvec2(cubic_bez.p0), point_to_dvec2(cubic_bez.p1), point_to_dvec2(cubic_bez.p2), point_to_dvec2(cubic_bez.p3)),
|
||||
};
|
||||
click_targets
|
||||
.filter_map(|target| {
|
||||
|
|
@ -2996,7 +2998,7 @@ fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'
|
|||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(|bezier| segment(bezier.apply_transformation(|x| transform.transform_point2(x))))
|
||||
.map(|bezier| segment(Affine::new(transform.to_cols_array()) * bezier))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
|
@ -3019,11 +3021,11 @@ impl<'a> ClickXRayIter<'a> {
|
|||
|
||||
/// Handles the checking of the layer where the target is a rect or path
|
||||
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: Vec<path_bool_lib::PathSegment>, transform: DAffine2) -> XRayResult {
|
||||
// Convert back to Bezier-rs types for intersections
|
||||
// Convert back to Kurbo types for intersections
|
||||
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
|
||||
path_bool_lib::PathSegment::Line(start, end) => bezier_rs::Bezier::from_linear_dvec2(start, end),
|
||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => bezier_rs::Bezier::from_cubic_dvec2(start, h1, h2, end),
|
||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => bezier_rs::Bezier::from_quadratic_dvec2(start, h1, end),
|
||||
path_bool_lib::PathSegment::Line(start, end) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(h2), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
|
||||
};
|
||||
let get_clip = || path.iter().map(segment);
|
||||
|
|
@ -3072,7 +3074,10 @@ impl<'a> ClickXRayIter<'a> {
|
|||
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
|
||||
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
|
||||
XRayTarget::Polygon(polygon) => {
|
||||
let polygon = polygon.iter_closed().map(|line| path_bool_lib::PathSegment::Line(line.start, line.end)).collect();
|
||||
let polygon = polygon
|
||||
.iter_closed()
|
||||
.map(|line| path_bool_lib::PathSegment::Line(point_to_dvec2(line.start()), point_to_dvec2(line.end())))
|
||||
.collect();
|
||||
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ use super::utility_types::TransformIn;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
|
|
@ -12,6 +11,7 @@ use graphene_std::Artboard;
|
|||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::Vector;
|
||||
|
|
|
|||
|
|
@ -1836,7 +1836,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
continue;
|
||||
};
|
||||
let quad = Quad::from_box([box_selection_start, box_selection_end_graph]);
|
||||
if click_targets.node_click_target.intersect_path(|| quad.bezier_lines(), DAffine2::IDENTITY) {
|
||||
if click_targets.node_click_target.intersect_path(|| quad.to_lines(), DAffine2::IDENTITY) {
|
||||
nodes.insert(node_id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
|
|||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::{PointId, SegmentId};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
|
@ -125,7 +125,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
|||
}
|
||||
|
||||
// Get the selected segments and then add a bold line overlay on them
|
||||
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, _, _) in vector.segment_iter() {
|
||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||
continue;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@ use crate::consts::{
|
|||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
||||
|
|
@ -571,11 +573,7 @@ impl OverlayContext {
|
|||
let handle_start = start + start_vec.perp() * radius * factor;
|
||||
let handle_end = end - end_vec.perp() * radius * factor;
|
||||
|
||||
let bezier = Bezier {
|
||||
start,
|
||||
end,
|
||||
handles: bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||
|
||||
self.bezier_command(bezier, DAffine2::IDENTITY, i == 0);
|
||||
}
|
||||
|
|
@ -762,7 +760,7 @@ impl OverlayContext {
|
|||
|
||||
self.render_context.begin_path();
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
|
|
@ -776,7 +774,7 @@ impl OverlayContext {
|
|||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
|
|
@ -788,7 +786,7 @@ impl OverlayContext {
|
|||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
|
|
@ -802,7 +800,7 @@ impl OverlayContext {
|
|||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
|
|
@ -816,18 +814,18 @@ impl OverlayContext {
|
|||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
||||
fn bezier_command(&self, bezier: PathSeg, transform: DAffine2, move_to: bool) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||
if move_to {
|
||||
self.render_context.move_to(start.x, start.y);
|
||||
self.render_context.move_to(bezier.start().x, bezier.start().y);
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => self.render_context.line_to(end.x, end.y),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => self.render_context.quadratic_curve_to(handle.x, handle.y, end.x, end.y),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(handle_start.x, handle_start.y, handle_end.x, handle_end.y, end.x, end.y),
|
||||
match bezier.as_path_el() {
|
||||
kurbo::PathEl::LineTo(point) => self.render_context.line_to(point.x, point.y),
|
||||
kurbo::PathEl::QuadTo(point, point1) => self.render_context.quadratic_curve_to(point.x, point.y, point1.x, point1.y),
|
||||
kurbo::PathEl::CurveTo(point, point1, point2) => self.render_context.bezier_curve_to(point.x, point.y, point1.x, point1.y, point2.x, point2.y),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
|
|
@ -841,36 +839,35 @@ impl OverlayContext {
|
|||
let subpath = subpath.borrow();
|
||||
let mut curves = subpath.iter().peekable();
|
||||
|
||||
let Some(first) = curves.peek() else {
|
||||
let Some(&first) = curves.peek() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.render_context.move_to(transform.transform_point2(first.start()).x, transform.transform_point2(first.start()).y);
|
||||
for curve in curves {
|
||||
match curve.handles {
|
||||
bezier_rs::BezierHandles::Linear => {
|
||||
let a = transform.transform_point2(curve.end());
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||
self.render_context.move_to(start_point.x, start_point.y);
|
||||
|
||||
self.render_context.line_to(a.x, a.y)
|
||||
for curve in curves {
|
||||
match curve {
|
||||
PathSeg::Line(line) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
self.render_context.line_to(a.x, a.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
let a = transform.transform_point2(handle);
|
||||
let b = transform.transform_point2(curve.end());
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y)
|
||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let a = transform.transform_point2(handle_start);
|
||||
let b = transform.transform_point2(handle_end);
|
||||
let c = transform.transform_point2(curve.end());
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
let c = c.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y)
|
||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -885,7 +882,7 @@ impl OverlayContext {
|
|||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||
let mut subpaths: Vec<Subpath<PointId>> = vec![];
|
||||
|
||||
target_types.for_each(|target_type| match target_type.borrow() {
|
||||
ClickTargetType::FreePoint(point) => {
|
||||
|
|
|
|||
|
|
@ -4,20 +4,22 @@ use crate::consts::{
|
|||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::{self, Subpath};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig, load_font, to_path};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use kurbo::{self, BezPath, ParamCurve};
|
||||
use kurbo::{Affine, PathSeg};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use vello::Scene;
|
||||
use vello::kurbo::{self, BezPath};
|
||||
use vello::peniko;
|
||||
|
||||
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||
|
|
@ -345,16 +347,16 @@ impl OverlayContext {
|
|||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.internal().outline_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.internal().outline_select_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.internal().outline_overlay_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
|
|
@ -842,7 +844,7 @@ impl OverlayContextInternal {
|
|||
let mut path = BezPath::new();
|
||||
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
|
|
@ -853,7 +855,7 @@ impl OverlayContextInternal {
|
|||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
|
@ -862,7 +864,7 @@ impl OverlayContextInternal {
|
|||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
|
@ -870,7 +872,7 @@ impl OverlayContextInternal {
|
|||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
|
@ -878,21 +880,12 @@ impl OverlayContextInternal {
|
|||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
||||
}
|
||||
|
||||
fn bezier_to_path(&self, bezier: Bezier, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||
fn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
||||
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||
if move_to {
|
||||
path.move_to(kurbo::Point::new(start.x, start.y));
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => path.line_to(kurbo::Point::new(end.x, end.y)),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => path.quad_to(kurbo::Point::new(handle.x, handle.y), kurbo::Point::new(end.x, end.y)),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path.curve_to(
|
||||
kurbo::Point::new(handle_start.x, handle_start.y),
|
||||
kurbo::Point::new(handle_end.x, handle_end.y),
|
||||
kurbo::Point::new(end.x, end.y),
|
||||
),
|
||||
path.move_to(bezier.start());
|
||||
}
|
||||
path.push(bezier.as_path_el());
|
||||
}
|
||||
|
||||
fn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
|
||||
|
|
@ -906,27 +899,27 @@ impl OverlayContextInternal {
|
|||
continue;
|
||||
};
|
||||
|
||||
let start_point = transform.transform_point2(first.start());
|
||||
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||
path.move_to(kurbo::Point::new(start_point.x, start_point.y));
|
||||
|
||||
for curve in curves {
|
||||
match curve.handles {
|
||||
bezier_rs::BezierHandles::Linear => {
|
||||
let a = transform.transform_point2(curve.end());
|
||||
match curve {
|
||||
PathSeg::Line(line) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
path.line_to(kurbo::Point::new(a.x, a.y));
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
let a = transform.transform_point2(handle);
|
||||
let b = transform.transform_point2(curve.end());
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
path.quad_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y));
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let a = transform.transform_point2(handle_start);
|
||||
let b = transform.transform_point2(handle_end);
|
||||
let c = transform.transform_point2(curve.end());
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
let c = c.round() - DVec2::splat(0.5);
|
||||
|
|
@ -945,7 +938,7 @@ impl OverlayContextInternal {
|
|||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||
let mut subpaths: Vec<subpath::Subpath<PointId>> = vec![];
|
||||
|
||||
for target_type in target_types {
|
||||
match target_type.borrow() {
|
||||
|
|
@ -1118,13 +1111,13 @@ impl OverlayContextInternal {
|
|||
|
||||
// Add handle points if they exist
|
||||
match transformed_bezier.handles {
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
subpath::BezierHandles::Quadratic { handle } => {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
max_x = max_x.max(handle.x);
|
||||
max_y = max_y.max(handle.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
subpath::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
for handle in [handle_start, handle_end] {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
|
|
@ -1154,7 +1147,7 @@ impl OverlayContextInternal {
|
|||
let mut path = BezPath::new();
|
||||
let mut last_point = None;
|
||||
|
||||
for (_, bezier, start_id, end_id) in row.element.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in row.element.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, Vector};
|
||||
|
|
@ -196,7 +197,7 @@ impl DocumentMetadata {
|
|||
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &bezier_rs::Subpath<PointId>> {
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &subpath::Subpath<PointId>> {
|
||||
static EMPTY: Vec<ClickTarget> = Vec::new();
|
||||
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
||||
click_targets.iter().filter_map(|target| match target.target_type() {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ use crate::messages::portfolio::document::node_graph::utility_types::{Direction,
|
|||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
|
|
@ -2832,7 +2832,7 @@ impl NodeNetworkInterface {
|
|||
let node_click_target_bottom_right = node_click_target_top_left + DVec2::new(width as f64, height as f64);
|
||||
|
||||
let radius = 3.;
|
||||
let subpath = bezier_rs::Subpath::new_rounded_rect(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);
|
||||
let subpath = Subpath::new_rounded_rect(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);
|
||||
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||
|
||||
DocumentNodeClickTargets {
|
||||
|
|
@ -2871,7 +2871,7 @@ impl NodeNetworkInterface {
|
|||
let node_bottom_right = node_top_left + DVec2::new(width as f64, height as f64);
|
||||
let chain_top_left = node_top_left - DVec2::new((chain_width_grid_spaces * crate::consts::GRID_SIZE) as f64, 0.);
|
||||
let radius = 10.;
|
||||
let subpath = bezier_rs::Subpath::new_rounded_rect(chain_top_left, node_bottom_right, [radius; 4]);
|
||||
let subpath = Subpath::new_rounded_rect(chain_top_left, node_bottom_right, [radius; 4]);
|
||||
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||
|
||||
DocumentNodeClickTargets {
|
||||
|
|
@ -3059,27 +3059,21 @@ impl NodeNetworkInterface {
|
|||
let mut node_path = String::new();
|
||||
|
||||
if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {
|
||||
let _ = subpath.subpath_to_svg(&mut node_path, DAffine2::IDENTITY);
|
||||
node_path.push_str(subpath.to_bezpath().to_svg().as_str())
|
||||
}
|
||||
all_node_click_targets.push((node_id, node_path));
|
||||
for port in node_click_targets.port_click_targets.click_targets().chain(import_export_click_targets.click_targets()) {
|
||||
if let ClickTargetType::Subpath(subpath) = port.target_type() {
|
||||
let mut port_path = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
||||
connector_click_targets.push(port_path);
|
||||
connector_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
}
|
||||
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
|
||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {
|
||||
let mut port_path = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
||||
icon_click_targets.push(port_path);
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
|
||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {
|
||||
let mut port_path = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
||||
icon_click_targets.push(port_path);
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3095,9 +3089,8 @@ impl NodeNetworkInterface {
|
|||
});
|
||||
|
||||
let bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||
let rect = bezier_rs::Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
let mut all_nodes_bounding_box = String::new();
|
||||
let _ = rect.subpath_to_svg(&mut all_nodes_bounding_box, DAffine2::IDENTITY);
|
||||
let rect = Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
let all_nodes_bounding_box = rect.to_bezpath().to_svg();
|
||||
|
||||
let Some(rounded_network_edge_distance) = self.rounded_network_edge_distance(network_path).cloned() else {
|
||||
log::error!("Could not get rounded_network_edge_distance in collect_frontend_click_targets");
|
||||
|
|
@ -3123,9 +3116,8 @@ impl NodeNetworkInterface {
|
|||
.inverse()
|
||||
.transform_point2(import_exports_viewport_bottom_right);
|
||||
|
||||
let import_exports_target = bezier_rs::Subpath::<PointId>::new_rect(node_graph_top_left, node_graph_bottom_right);
|
||||
let mut import_exports_bounding_box = String::new();
|
||||
let _ = import_exports_target.subpath_to_svg(&mut import_exports_bounding_box, DAffine2::IDENTITY);
|
||||
let import_exports_target = Subpath::<PointId>::new_rect(node_graph_top_left, node_graph_bottom_right);
|
||||
let import_exports_bounding_box = import_exports_target.to_bezpath().to_svg();
|
||||
|
||||
let mut modify_import_export = Vec::new();
|
||||
if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) {
|
||||
|
|
@ -3136,9 +3128,7 @@ impl NodeNetworkInterface {
|
|||
.chain(modify_import_export_click_targets.reorder_imports_exports.click_targets())
|
||||
{
|
||||
if let ClickTargetType::Subpath(subpath) = click_target.target_type() {
|
||||
let mut remove_string = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut remove_string, DAffine2::IDENTITY);
|
||||
modify_import_export.push(remove_string);
|
||||
modify_import_export.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3407,7 +3397,7 @@ impl NodeNetworkInterface {
|
|||
return None;
|
||||
};
|
||||
|
||||
let bounding_box_subpath = bezier_rs::Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
let bounding_box_subpath = Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
bounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::DocumentNode;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::ProtoNodeIdentifier;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||
use graphene_std::uuid::NodeId;
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||
use bezier_rs::BezierHandles;
|
||||
use derivative::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::misc::HandleId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
|
|
@ -12,6 +11,7 @@ use graphene_std::Color;
|
|||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::graph_modification_utils::merge_layers;
|
||||
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
|
||||
use super::utility_functions::{adjust_handle_colinearity, calculate_bezier_bbox, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||
use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||
use crate::consts::HANDLE_LENGTH_FACTOR;
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
|
|
@ -9,12 +9,15 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
|
|||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{is_intersecting, is_visible_point};
|
||||
use crate::messages::tool::common_functionality::utility_functions::is_visible_point;
|
||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::subpath::{BezierHandles, Subpath};
|
||||
use graphene_std::subpath::{PathSegPoints, pathseg_points};
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape};
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
|
@ -26,7 +29,7 @@ pub enum SelectionChange {
|
|||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum SelectionShape<'a> {
|
||||
Box([DVec2; 2]),
|
||||
Box(Rect),
|
||||
Lasso(&'a Vec<DVec2>),
|
||||
}
|
||||
|
||||
|
|
@ -185,7 +188,7 @@ pub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<HandleId,
|
|||
pub struct ClosestSegment {
|
||||
layer: LayerNodeIdentifier,
|
||||
segment: SegmentId,
|
||||
bezier: Bezier,
|
||||
bezier: PathSeg,
|
||||
points: [PointId; 2],
|
||||
colinear: [Option<HandleId>; 2],
|
||||
t: f64,
|
||||
|
|
@ -205,12 +208,12 @@ impl ClosestSegment {
|
|||
self.points
|
||||
}
|
||||
|
||||
pub fn bezier(&self) -> Bezier {
|
||||
pub fn pathseg(&self) -> PathSeg {
|
||||
self.bezier
|
||||
}
|
||||
|
||||
pub fn closest_point_document(&self) -> DVec2 {
|
||||
self.bezier.evaluate(TValue::Parametric(self.t))
|
||||
point_to_dvec2(self.bezier.eval(self.t))
|
||||
}
|
||||
|
||||
pub fn closest_point_to_viewport(&self) -> DVec2 {
|
||||
|
|
@ -219,7 +222,7 @@ impl ClosestSegment {
|
|||
|
||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
||||
let bezier_point = point_to_dvec2(self.bezier.eval(self.t));
|
||||
transform.transform_point2(bezier_point)
|
||||
}
|
||||
|
||||
|
|
@ -228,10 +231,10 @@ impl ClosestSegment {
|
|||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
||||
|
||||
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
||||
let t = self.bezier.nearest(dvec2_to_point(layer_mouse_pos), DEFAULT_ACCURACY).t.clamp(0., 1.);
|
||||
self.t = t;
|
||||
|
||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(t));
|
||||
let bezier_point = point_to_dvec2(self.bezier.eval(t));
|
||||
let bezier_point = transform.transform_point2(bezier_point);
|
||||
self.bezier_point_to_viewport = bezier_point;
|
||||
}
|
||||
|
|
@ -249,22 +252,24 @@ impl ClosestSegment {
|
|||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
|
||||
// Split the Bezier at the parameter `t`
|
||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||
let first = self.bezier.subsegment(0_f64..self.t);
|
||||
let second = self.bezier.subsegment(self.t..1.);
|
||||
|
||||
// Transform the handle positions to viewport space
|
||||
let first_handle = first.handle_end().map(|handle| transform.transform_point2(handle));
|
||||
let second_handle = second.handle_start().map(|handle| transform.transform_point2(handle));
|
||||
let first_handle = pathseg_points(first).p2.map(|handle| transform.transform_point2(handle));
|
||||
let second_handle = pathseg_points(second).p1.map(|handle| transform.transform_point2(handle));
|
||||
|
||||
(first_handle, second_handle)
|
||||
}
|
||||
|
||||
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {
|
||||
let layer = self.layer;
|
||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||
let first = pathseg_points(self.bezier.subsegment(0_f64..self.t));
|
||||
let second = pathseg_points(self.bezier.subsegment(self.t..1.));
|
||||
|
||||
// Point
|
||||
let midpoint = PointId::generate();
|
||||
let modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.end };
|
||||
let modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.p3 };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
// First segment
|
||||
|
|
@ -272,7 +277,7 @@ impl ClosestSegment {
|
|||
let modification_type = VectorModificationType::InsertSegment {
|
||||
id: segment_ids[0],
|
||||
points: [self.points[0], midpoint],
|
||||
handles: [first.handle_start().map(|handle| handle - first.start), first.handle_end().map(|handle| handle - first.end)],
|
||||
handles: [first.p1.map(|handle| handle - first.p0), first.p2.map(|handle| handle - first.p3)],
|
||||
};
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
|
|
@ -280,12 +285,12 @@ impl ClosestSegment {
|
|||
let modification_type = VectorModificationType::InsertSegment {
|
||||
id: segment_ids[1],
|
||||
points: [midpoint, self.points[1]],
|
||||
handles: [second.handle_start().map(|handle| handle - second.start), second.handle_end().map(|handle| handle - second.end)],
|
||||
handles: [second.p1.map(|handle| handle - second.p0), second.p2.map(|handle| handle - second.p3)],
|
||||
};
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
// G1 continuous on new handles
|
||||
if self.bezier.handle_end().is_some() {
|
||||
if pathseg_points(self.bezier).p2.is_some() {
|
||||
let handles = [HandleId::end(segment_ids[0]), HandleId::primary(segment_ids[1])];
|
||||
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
|
@ -353,8 +358,8 @@ impl ClosestSegment {
|
|||
) -> Option<[Option<HandleId>; 2]> {
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
||||
|
||||
let start = self.bezier.start;
|
||||
let end = self.bezier.end;
|
||||
let start = point_to_dvec2(self.bezier.start());
|
||||
let end = point_to_dvec2(self.bezier.end());
|
||||
|
||||
// Apply the drag delta to the segment's handles
|
||||
let b = self.bezier_point_to_viewport;
|
||||
|
|
@ -1686,9 +1691,9 @@ impl ShapeState {
|
|||
|
||||
let vector = network_interface.compute_modified_vector(layer)?;
|
||||
|
||||
for (segment, mut bezier, start, end) in vector.segment_bezier_iter() {
|
||||
let t = bezier.project(layer_pos);
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
for (segment_id, mut segment, start, end) in vector.segment_iter() {
|
||||
let t = segment.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let layerspace = point_to_dvec2(segment.eval(t));
|
||||
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
|
@ -1697,20 +1702,22 @@ impl ShapeState {
|
|||
closest_distance_squared = distance_squared;
|
||||
|
||||
// Convert to linear if handes are on top of control points
|
||||
if let bezier_rs::BezierHandles::Cubic { handle_start, handle_end } = bezier.handles {
|
||||
if handle_start.abs_diff_eq(bezier.start(), f64::EPSILON * 100.) && handle_end.abs_diff_eq(bezier.end(), f64::EPSILON * 100.) {
|
||||
bezier = Bezier::from_linear_dvec2(bezier.start, bezier.end);
|
||||
let PathSegPoints { p0: _, p1, p2, p3: _ } = pathseg_points(segment);
|
||||
if let (Some(p1), Some(p2)) = (p1, p2) {
|
||||
let segment_points = pathseg_points(segment);
|
||||
if p1.abs_diff_eq(segment_points.p0, f64::EPSILON * 100.) && p2.abs_diff_eq(segment_points.p3, f64::EPSILON * 100.) {
|
||||
segment = PathSeg::Line(Line::new(segment.start(), segment.end()));
|
||||
}
|
||||
}
|
||||
|
||||
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment)));
|
||||
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment)));
|
||||
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
|
||||
let end_handle = end_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
|
||||
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment_id)));
|
||||
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment_id)));
|
||||
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));
|
||||
let end_handle = end_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));
|
||||
|
||||
closest = Some(ClosestSegment {
|
||||
segment,
|
||||
bezier,
|
||||
segment: segment_id,
|
||||
bezier: segment,
|
||||
points: [start, end],
|
||||
colinear: [primary_handle, end_handle],
|
||||
t,
|
||||
|
|
@ -2076,21 +2083,24 @@ impl ShapeState {
|
|||
};
|
||||
|
||||
// Selection segments
|
||||
for (id, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (id, segment, _, _) in vector.segment_iter() {
|
||||
if select_segments {
|
||||
// Select segments if they lie inside the bounding box or lasso polygon
|
||||
let segment_bbox = calculate_bezier_bbox(bezier);
|
||||
let bottom_left = transform.transform_point2(segment_bbox[0]);
|
||||
let top_right = transform.transform_point2(segment_bbox[1]);
|
||||
let transformed_segment = Affine::new(transform.to_cols_array()) * segment;
|
||||
let segment_bbox = transformed_segment.bounding_box();
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => {
|
||||
let enclosed = quad[0].min(quad[1]).cmple(bottom_left).all() && quad[0].max(quad[1]).cmpge(top_right).all();
|
||||
SelectionShape::Box(rect) => {
|
||||
let enclosed = segment_bbox.contains_rect(rect);
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => enclosed,
|
||||
_ => {
|
||||
// Check for intersection with the segment
|
||||
enclosed || is_intersecting(bezier, quad, transform)
|
||||
enclosed
|
||||
|| rect
|
||||
.path_segments(DEFAULT_ACCURACY)
|
||||
.map(|seg| seg.as_line().unwrap())
|
||||
.any(|line| !transformed_segment.intersect_line(line).is_empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2098,7 +2108,7 @@ impl ShapeState {
|
|||
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
|
||||
|
||||
// Sample 10 points on the bezier and check if all or some lie inside the polygon
|
||||
let points = bezier.compute_lookup_table(Some(10), None);
|
||||
let points = pathseg_compute_lookup_table(segment, Some(10), false);
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
|
||||
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
|
||||
|
|
@ -2111,13 +2121,15 @@ impl ShapeState {
|
|||
}
|
||||
}
|
||||
|
||||
let segment_points = pathseg_points(segment);
|
||||
|
||||
// Selecting handles
|
||||
for (position, id) in [(bezier.handle_start(), ManipulatorPointId::PrimaryHandle(id)), (bezier.handle_end(), ManipulatorPointId::EndHandle(id))] {
|
||||
for (position, id) in [(segment_points.p1, ManipulatorPointId::PrimaryHandle(id)), (segment_points.p2, ManipulatorPointId::EndHandle(id))] {
|
||||
let Some(position) = position else { continue };
|
||||
let transformed_position = transform.transform_point2(position);
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(transformed_position).all() && quad[0].max(quad[1]).cmpge(transformed_position).all(),
|
||||
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||
SelectionShape::Lasso(_) => polygon_subpath
|
||||
.as_ref()
|
||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||
|
|
@ -2139,7 +2151,7 @@ impl ShapeState {
|
|||
let transformed_position = transform.transform_point2(position);
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(transformed_position).all() && quad[0].max(quad[1]).cmpge(transformed_position).all(),
|
||||
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||
SelectionShape::Lasso(_) => polygon_subpath
|
||||
.as_ref()
|
||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
|||
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::subpath::{self, Subpath};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{ArcType, dvec2_to_point};
|
||||
use kurbo::{BezPath, PathEl, Shape};
|
||||
|
|
@ -363,9 +363,9 @@ pub fn arc_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessag
|
|||
start_angle / 360. * std::f64::consts::TAU,
|
||||
sweep_angle / 360. * std::f64::consts::TAU,
|
||||
match arc_type {
|
||||
ArcType::Open => bezier_rs::ArcType::Open,
|
||||
ArcType::Closed => bezier_rs::ArcType::Closed,
|
||||
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
|
||||
ArcType::Open => subpath::ArcType::Open,
|
||||
ArcType::Closed => subpath::ArcType::Closed,
|
||||
ArcType::PieSlice => subpath::ArcType::PieSlice,
|
||||
},
|
||||
))];
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
|
|
|||
|
|
@ -10,14 +10,16 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||
use crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};
|
||||
use crate::messages::prelude::*;
|
||||
pub use alignment_snapper::*;
|
||||
use bezier_rs::TValue;
|
||||
pub use distribution_snapper::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::vector::NoHashBuilder;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
pub use grid_snapper::*;
|
||||
use kurbo::ParamCurve;
|
||||
pub use layer_snapper::*;
|
||||
pub use snap_results::*;
|
||||
use std::cmp::Ordering;
|
||||
|
|
@ -81,6 +83,7 @@ impl SnapConstraint {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 {
|
||||
document.snapping_state.tolerance / document.document_ptz.zoom()
|
||||
}
|
||||
|
|
@ -127,13 +130,16 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_closest_curve(curves: &[SnappedCurve], exclude_paths: bool) -> Option<&SnappedPoint> {
|
||||
let keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Path(PathSnapTarget::AlongPath);
|
||||
curves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)
|
||||
}
|
||||
|
||||
fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
|
||||
lines.iter().map(|curve| &curve.point).min_by(compare_points)
|
||||
}
|
||||
|
||||
fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {
|
||||
let mut best = None;
|
||||
for curve_i in curves {
|
||||
|
|
@ -141,8 +147,8 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
|
|||
if curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {
|
||||
continue;
|
||||
}
|
||||
for curve_i_t in curve_i.document_curve.intersections(&curve_j.document_curve, None, None) {
|
||||
let snapped_point_document = curve_i.document_curve.evaluate(TValue::Parametric(curve_i_t));
|
||||
for curve_i_t in filtered_segment_intersections(curve_i.document_curve, curve_j.document_curve, None, None) {
|
||||
let snapped_point_document = point_to_dvec2(curve_i.document_curve.eval(curve_i_t));
|
||||
let distance = snap_to.distance(snapped_point_document);
|
||||
let i_closer = curve_i.point.distance < curve_j.point.distance;
|
||||
let close = if i_closer { curve_i } else { curve_j };
|
||||
|
|
@ -165,6 +171,7 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
|
|||
}
|
||||
best
|
||||
}
|
||||
|
||||
fn get_grid_intersection(snap_to: DVec2, lines: &[SnappedLine]) -> Option<SnappedPoint> {
|
||||
let mut best = None;
|
||||
for line_i in lines {
|
||||
|
|
@ -237,6 +244,7 @@ impl<'a> SnapData<'a> {
|
|||
self.node_snap_cache.is_some_and(|cache| !cache.manipulators.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl SnapManager {
|
||||
pub fn update_indicator(&mut self, snapped_point: SnappedPoint) {
|
||||
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
||||
|
|
|
|||
|
|
@ -3,11 +3,17 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::*;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::subpath::{Identifier, ManipulatorGroup, Subpath};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::{pathseg_normals_to_point, pathseg_tangents_to_point};
|
||||
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use graphene_std::vector::misc::dvec2_to_point;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use kurbo::{Affine, DEFAULT_ACCURACY, Nearest, ParamCurve, ParamCurveNearest, PathSeg};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LayerSnapper {
|
||||
|
|
@ -37,7 +43,7 @@ impl LayerSnapper {
|
|||
return;
|
||||
}
|
||||
|
||||
for document_curve in bounds.bezier_lines() {
|
||||
for document_curve in bounds.to_lines() {
|
||||
self.paths_to_snap.push(SnapCandidatePath {
|
||||
document_curve,
|
||||
layer,
|
||||
|
|
@ -70,7 +76,7 @@ impl LayerSnapper {
|
|||
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) || document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath)) {
|
||||
for subpath in document.metadata().layer_outline(layer) {
|
||||
for (start_index, curve) in subpath.iter().enumerate() {
|
||||
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
|
||||
let document_curve = Affine::new(transform.to_cols_array()) * curve;
|
||||
let start = subpath.manipulator_groups()[start_index].id;
|
||||
if snap_data.ignore_manipulator(layer, start) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(start_index + 1) % subpath.len()].id) {
|
||||
continue;
|
||||
|
|
@ -98,13 +104,12 @@ impl LayerSnapper {
|
|||
|
||||
for path in &self.paths_to_snap {
|
||||
// Skip very short paths
|
||||
if path.document_curve.start.distance_squared(path.document_curve.end) < tolerance * tolerance * 2. {
|
||||
if path.document_curve.start().distance_squared(path.document_curve.end()) < tolerance * tolerance * 2. {
|
||||
continue;
|
||||
}
|
||||
let time = path.document_curve.project(point.document_point);
|
||||
let snapped_point_document = path.document_curve.evaluate(bezier_rs::TValue::Parametric(time));
|
||||
|
||||
let distance = snapped_point_document.distance(point.document_point);
|
||||
let Nearest { distance_sq, t } = path.document_curve.nearest(dvec2_to_point(point.document_point), DEFAULT_ACCURACY);
|
||||
let snapped_point_document = point_to_dvec2(path.document_curve.eval(t));
|
||||
let distance = distance_sq.sqrt();
|
||||
|
||||
if distance < tolerance {
|
||||
snap_results.curves.push(SnappedCurve {
|
||||
|
|
@ -144,8 +149,8 @@ impl LayerSnapper {
|
|||
|
||||
for path in &self.paths_to_snap {
|
||||
for constraint_path in constraint_path.iter() {
|
||||
for time in path.document_curve.intersections(&constraint_path, None, None) {
|
||||
let snapped_point_document = path.document_curve.evaluate(bezier_rs::TValue::Parametric(time));
|
||||
for time in filtered_segment_intersections(path.document_curve, constraint_path, None, None) {
|
||||
let snapped_point_document = point_to_dvec2(path.document_curve.eval(time));
|
||||
|
||||
let distance = snapped_point_document.distance(point.document_point);
|
||||
|
||||
|
|
@ -266,8 +271,8 @@ impl LayerSnapper {
|
|||
fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
|
||||
if normals && path.bounds.is_none() {
|
||||
for &neighbor in &point.neighbors {
|
||||
for t in path.document_curve.normals_to_point(neighbor) {
|
||||
let normal_point = path.document_curve.evaluate(TValue::Parametric(t));
|
||||
for t in pathseg_normals_to_point(path.document_curve, dvec2_to_point(neighbor)) {
|
||||
let normal_point = point_to_dvec2(path.document_curve.eval(t));
|
||||
let distance = normal_point.distance(point.document_point);
|
||||
if distance > tolerance {
|
||||
continue;
|
||||
|
|
@ -287,8 +292,8 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||
}
|
||||
if tangents && path.bounds.is_none() {
|
||||
for &neighbor in &point.neighbors {
|
||||
for t in path.document_curve.tangents_to_point(neighbor) {
|
||||
let tangent_point = path.document_curve.evaluate(TValue::Parametric(t));
|
||||
for t in pathseg_tangents_to_point(path.document_curve, dvec2_to_point(neighbor)) {
|
||||
let tangent_point = point_to_dvec2(path.document_curve.eval(t));
|
||||
let distance = tangent_point.distance(point.document_point);
|
||||
if distance > tolerance {
|
||||
continue;
|
||||
|
|
@ -310,7 +315,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SnapCandidatePath {
|
||||
document_curve: Bezier,
|
||||
document_curve: PathSeg,
|
||||
layer: LayerNodeIdentifier,
|
||||
start: PointId,
|
||||
target: SnapTarget,
|
||||
|
|
@ -440,12 +445,13 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
|||
if points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {
|
||||
return;
|
||||
}
|
||||
let curve = pathseg_points(curve);
|
||||
|
||||
let in_handle = curve.handle_start().map(|handle| handle - curve.start).filter(handle_not_under(to_document));
|
||||
let out_handle = curve.handle_end().map(|handle| handle - curve.end).filter(handle_not_under(to_document));
|
||||
let in_handle = curve.p1.map(|handle| handle - curve.p0).filter(handle_not_under(to_document));
|
||||
let out_handle = curve.p2.map(|handle| handle - curve.p3).filter(handle_not_under(to_document));
|
||||
if in_handle.is_none() && out_handle.is_none() {
|
||||
points.push(SnapCandidatePoint::new(
|
||||
to_document.transform_point2(curve.start() * 0.5 + curve.end * 0.5),
|
||||
to_document.transform_point2(curve.p0 * 0.5 + curve.p3 * 0.5),
|
||||
SnapSource::Path(PathSnapSource::LineMidpoint),
|
||||
SnapTarget::Path(PathSnapTarget::LineMidpoint),
|
||||
Some(layer),
|
||||
|
|
@ -487,7 +493,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
|||
}
|
||||
}
|
||||
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
let anchor = manipulators.anchor;
|
||||
let handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||
let handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ use super::DistributionMatch;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{DistributionSnapTarget, SnapSource, SnapTarget};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
|
||||
use bezier_rs::Bezier;
|
||||
use glam::DVec2;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::vector::PointId;
|
||||
use kurbo::PathSeg;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
|
@ -120,5 +120,5 @@ pub struct SnappedCurve {
|
|||
pub layer: LayerNodeIdentifier,
|
||||
pub start: PointId,
|
||||
pub point: SnappedPoint,
|
||||
pub document_curve: Bezier,
|
||||
pub document_curve: PathSeg,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,17 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
|
|||
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
|
||||
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
||||
use kurbo::{CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, PathSeg, Point, QuadBez, Shape};
|
||||
|
||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||
pub fn should_extend(
|
||||
|
|
@ -208,25 +209,6 @@ pub fn is_visible_point(
|
|||
}
|
||||
}
|
||||
|
||||
/// Function to find the bounding box of bezier (uses method from kurbo)
|
||||
pub fn calculate_bezier_bbox(bezier: Bezier) -> [DVec2; 2] {
|
||||
let start = Point::new(bezier.start.x, bezier.start.y);
|
||||
let end = Point::new(bezier.end.x, bezier.end.y);
|
||||
let bbox = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p1 = Point::new(handle_start.x, handle_start.y);
|
||||
let p2 = Point::new(handle_end.x, handle_end.y);
|
||||
CubicBez::new(start, p1, p2, end).bounding_box()
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p1 = Point::new(handle.x, handle.y);
|
||||
QuadBez::new(start, p1, end).bounding_box()
|
||||
}
|
||||
BezierHandles::Linear => Line::new(start, end).bounding_box(),
|
||||
};
|
||||
[DVec2::new(bbox.x0, bbox.y0), DVec2::new(bbox.x1, bbox.y1)]
|
||||
}
|
||||
|
||||
pub fn is_intersecting(bezier: Bezier, quad: [DVec2; 2], transform: DAffine2) -> bool {
|
||||
let to_layerspace = transform.inverse();
|
||||
let quad = [to_layerspace.transform_point2(quad[0]), to_layerspace.transform_point2(quad[1])];
|
||||
|
|
@ -496,19 +478,19 @@ pub fn log_optimization(a: f64, b: f64, p1: DVec2, p3: DVec2, d1: DVec2, d2: DVe
|
|||
let c1 = p1 + d1 * start_handle_length;
|
||||
let c2 = p3 + d2 * end_handle_length;
|
||||
|
||||
let new_curve = Bezier::from_cubic_coordinates(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p3.x, p3.y);
|
||||
let new_curve = PathSeg::Cubic(CubicBez::new(Point::new(p1.x, p1.y), Point::new(c1.x, c1.y), Point::new(c2.x, c2.y), Point::new(p3.x, p3.y)));
|
||||
|
||||
// Sample 2*n points from new curve and get the L2 metric between all of points
|
||||
let points = new_curve.compute_lookup_table(Some(2 * n), None).collect::<Vec<_>>();
|
||||
let points = pathseg_compute_lookup_table(new_curve, Some(2 * n), false);
|
||||
|
||||
let dist = points1.iter().zip(points.iter()).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||
let dist = points1.iter().zip(points).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||
|
||||
dist / (2 * n) as f64
|
||||
}
|
||||
|
||||
/// Calculates optimal handle lengths with adam optimization.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, farther_segment: Bezier, other_segment: Bezier) -> (DVec2, DVec2) {
|
||||
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, further_segment: PathSeg, other_segment: PathSeg) -> (DVec2, DVec2) {
|
||||
let h = 1e-6;
|
||||
let tol = 1e-6;
|
||||
let max_iter = 200;
|
||||
|
|
@ -530,21 +512,25 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
|||
|
||||
let n = 20;
|
||||
|
||||
let farther_segment = if farther_segment.start.distance(p1) >= f64::EPSILON {
|
||||
farther_segment.reverse()
|
||||
let further_segment = if further_segment.start().distance(dvec2_to_point(p1)) >= f64::EPSILON {
|
||||
further_segment.reverse()
|
||||
} else {
|
||||
farther_segment
|
||||
further_segment
|
||||
};
|
||||
|
||||
let other_segment = if other_segment.end.distance(p3) >= f64::EPSILON { other_segment.reverse() } else { other_segment };
|
||||
let other_segment = if other_segment.end().distance(dvec2_to_point(p3)) >= f64::EPSILON {
|
||||
other_segment.reverse()
|
||||
} else {
|
||||
other_segment
|
||||
};
|
||||
|
||||
// Now we sample points proportional to the lengths of the beziers
|
||||
let l1 = farther_segment.length(None);
|
||||
let l2 = other_segment.length(None);
|
||||
let l1 = further_segment.perimeter(DEFAULT_ACCURACY);
|
||||
let l2 = other_segment.perimeter(DEFAULT_ACCURACY);
|
||||
let ratio = l1 / (l1 + l2);
|
||||
let n_points1 = ((2 * n) as f64 * ratio).floor() as usize;
|
||||
let mut points1 = farther_segment.compute_lookup_table(Some(n_points1), None).collect::<Vec<_>>();
|
||||
let mut points2 = other_segment.compute_lookup_table(Some(n), None).collect::<Vec<_>>();
|
||||
let mut points1 = pathseg_compute_lookup_table(further_segment, Some(n_points1), false).collect::<Vec<_>>();
|
||||
let mut points2 = pathseg_compute_lookup_table(other_segment, Some(n), false).collect::<Vec<_>>();
|
||||
points1.append(&mut points2);
|
||||
|
||||
let f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };
|
||||
|
|
|
|||
|
|
@ -21,14 +21,16 @@ use crate::messages::tool::common_functionality::shape_editor::{
|
|||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::algorithms::util::pathseg_tangent;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};
|
||||
use std::vec;
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
|
|
@ -494,7 +496,7 @@ pub enum PointSelectState {
|
|||
#[derive(Clone, Copy)]
|
||||
pub struct SlidingSegmentData {
|
||||
segment_id: SegmentId,
|
||||
bezier: Bezier,
|
||||
bezier: PathSeg,
|
||||
start: PointId,
|
||||
}
|
||||
|
||||
|
|
@ -840,13 +842,12 @@ impl PathToolData {
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
} else {
|
||||
let start_pos = segment.bezier().start;
|
||||
let end_pos = segment.bezier().end;
|
||||
let points = pathseg_points(segment.pathseg());
|
||||
|
||||
let [pos1, pos2] = match segment.bezier().handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
||||
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
||||
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
||||
let [pos1, pos2] = match (points.p1, points.p2) {
|
||||
(Some(p1), Some(p2)) => [p1, p2],
|
||||
(Some(p1), None) | (None, Some(p1)) => [p1, points.p3],
|
||||
(None, None) => [points.p0 + (points.p3 - points.p0) / 3., points.p3 + (points.p0 - points.p3) / 3.],
|
||||
};
|
||||
self.molding_info = Some((pos1, pos2));
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
|
|
@ -1215,7 +1216,7 @@ impl PathToolData {
|
|||
let Some(point_id) = anchor.as_anchor() else { return false };
|
||||
|
||||
let mut connected_segments = [None, None];
|
||||
for (segment, bezier, start, end) in vector.segment_bezier_iter() {
|
||||
for (segment, bezier, start, end) in vector.segment_iter() {
|
||||
if start == point_id || end == point_id {
|
||||
match (connected_segments[0], connected_segments[1]) {
|
||||
(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),
|
||||
|
|
@ -1255,13 +1256,13 @@ impl PathToolData {
|
|||
|
||||
let segments = sliding_point_info.connected_segments;
|
||||
|
||||
let t1 = segments[0].bezier.project(layer_pos);
|
||||
let position1 = segments[0].bezier.evaluate(TValue::Parametric(t1));
|
||||
let t1 = segments[0].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let position1 = point_to_dvec2(segments[0].bezier.eval(t1));
|
||||
|
||||
let t2 = segments[1].bezier.project(layer_pos);
|
||||
let position2 = segments[1].bezier.evaluate(TValue::Parametric(t2));
|
||||
let t2 = segments[1].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let position2 = point_to_dvec2(segments[1].bezier.eval(t2));
|
||||
|
||||
let (closer_segment, farther_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||
let (closer_segment, further_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||
(segments[1], segments[0], t2, position2)
|
||||
} else {
|
||||
(segments[0], segments[1], t1, position1)
|
||||
|
|
@ -1276,50 +1277,59 @@ impl PathToolData {
|
|||
shape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);
|
||||
|
||||
// Make a split at the t_value
|
||||
let [first, second] = closer_segment.bezier.split(TValue::Parametric(t_value));
|
||||
let closer_segment_other_point = if anchor == closer_segment.start { closer_segment.bezier.end } else { closer_segment.bezier.start };
|
||||
let first = closer_segment.bezier.subsegment(0_f64..t_value);
|
||||
let second = closer_segment.bezier.subsegment(t_value..1.);
|
||||
|
||||
let (split_segment, other_segment) = if first.start == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||
let closer_segment_other_point = if anchor == closer_segment.start {
|
||||
closer_segment.bezier.end()
|
||||
} else {
|
||||
closer_segment.bezier.start()
|
||||
};
|
||||
|
||||
let (split_segment, other_segment) = if first.start() == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||
let split_segment_points = pathseg_points(split_segment);
|
||||
|
||||
// Primary handle maps to primary handle and secondary maps to secondary
|
||||
let closer_primary_handle = HandleId::primary(closer_segment.segment_id);
|
||||
let Some(handle_position) = split_segment.handle_start() else { return };
|
||||
let relative_position1 = handle_position - split_segment.start;
|
||||
let Some(handle_position) = split_segment_points.p1 else { return };
|
||||
let relative_position1 = handle_position - split_segment_points.p0;
|
||||
let modification_type = closer_primary_handle.set_relative_position(relative_position1);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let closer_secondary_handle = HandleId::end(closer_segment.segment_id);
|
||||
let Some(handle_position) = split_segment.handle_end() else { return };
|
||||
let relative_position2 = handle_position - split_segment.end;
|
||||
let Some(handle_position) = split_segment_points.p2 else { return };
|
||||
let relative_position2 = handle_position - split_segment_points.p3;
|
||||
let modification_type = closer_secondary_handle.set_relative_position(relative_position2);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let end_handle_direction = if anchor == closer_segment.start { -relative_position1 } else { -relative_position2 };
|
||||
|
||||
let (farther_other_point, start_handle, end_handle, start_handle_pos) = if anchor == farther_segment.start {
|
||||
let further_segment_points = pathseg_points(further_segment.bezier);
|
||||
|
||||
let (further_other_point, start_handle, end_handle, start_handle_pos) = if anchor == further_segment.start {
|
||||
(
|
||||
farther_segment.bezier.end,
|
||||
HandleId::end(farther_segment.segment_id),
|
||||
HandleId::primary(farther_segment.segment_id),
|
||||
farther_segment.bezier.handle_end(),
|
||||
further_segment_points.p3,
|
||||
HandleId::end(further_segment.segment_id),
|
||||
HandleId::primary(further_segment.segment_id),
|
||||
further_segment_points.p2,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
farther_segment.bezier.start,
|
||||
HandleId::primary(farther_segment.segment_id),
|
||||
HandleId::end(farther_segment.segment_id),
|
||||
farther_segment.bezier.handle_start(),
|
||||
further_segment_points.p0,
|
||||
HandleId::primary(further_segment.segment_id),
|
||||
HandleId::end(further_segment.segment_id),
|
||||
further_segment_points.p1,
|
||||
)
|
||||
};
|
||||
let Some(start_handle_position) = start_handle_pos else { return };
|
||||
let start_handle_direction = start_handle_position - farther_other_point;
|
||||
let start_handle_direction = start_handle_position - further_other_point;
|
||||
|
||||
// Get normalized direction vectors, if cubic handle is zero then we consider corresponding tangent
|
||||
let d1 = start_handle_direction.try_normalize().unwrap_or({
|
||||
if anchor == farther_segment.start {
|
||||
-farther_segment.bezier.tangent(TValue::Parametric(0.99))
|
||||
if anchor == further_segment.start {
|
||||
-pathseg_tangent(further_segment.bezier, 1.)
|
||||
} else {
|
||||
farther_segment.bezier.tangent(TValue::Parametric(0.01))
|
||||
pathseg_tangent(further_segment.bezier, 0.)
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1328,7 +1338,7 @@ impl PathToolData {
|
|||
let min_len1 = start_handle_direction.length() * 0.4;
|
||||
let min_len2 = end_handle_direction.length() * 0.4;
|
||||
|
||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(farther_other_point, new_position, d1, d2, min_len1, min_len2, farther_segment.bezier, other_segment);
|
||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(further_other_point, new_position, d1, d2, min_len1, min_len2, further_segment.bezier, other_segment);
|
||||
|
||||
// Now set those handles to these handle lengths keeping the directions d1, d2
|
||||
let modification_type = start_handle.set_relative_position(relative_pos1);
|
||||
|
|
@ -1737,13 +1747,13 @@ impl Fsm for PathToolFsmState {
|
|||
if tool_options.path_editing_mode.segment_editing_mode && !tool_data.segment_editing_modifier {
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||
|
||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||
overlay_context.outline_overlay_bezier(closest_segment.pathseg(), transform);
|
||||
|
||||
// Draw the anchors again
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
if display_anchors {
|
||||
let start_pos = transform.transform_point2(closest_segment.bezier().start);
|
||||
let end_pos = transform.transform_point2(closest_segment.bezier().end);
|
||||
let start_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().start()));
|
||||
let end_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().end()));
|
||||
let start_id = closest_segment.points()[0];
|
||||
let end_id = closest_segment.points()[1];
|
||||
if let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {
|
||||
|
|
@ -1820,7 +1830,7 @@ impl Fsm for PathToolFsmState {
|
|||
let (points_inside, segments_inside) = match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position);
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||
shape_editor.get_inside_points_and_segments(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
|
|
@ -1865,7 +1875,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
|
||||
for (segment, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (segment, bezier, _, _) in vector.segment_iter() {
|
||||
if segments.contains(&segment) {
|
||||
overlay_context.outline_overlay_bezier(bezier, transform);
|
||||
}
|
||||
|
|
@ -2241,7 +2251,8 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||
|
||||
shape_editor.select_all_in_shape(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
|
|
@ -2337,7 +2348,8 @@ impl Fsm for PathToolFsmState {
|
|||
} else {
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||
|
||||
shape_editor.select_all_in_shape(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
|
|
@ -2697,16 +2709,13 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
// Create new segment ids and add the segments into the existing vector content
|
||||
let mut segments_map = HashMap::new();
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_iter() {
|
||||
let new_segment_id = SegmentId::generate();
|
||||
|
||||
segments_map.insert(segment_id, new_segment_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
let points = pathseg_points(bezier);
|
||||
let handles = [points.p1, points.p2];
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
||||
|
|
@ -2802,7 +2811,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
let mut segments_map = HashMap::new();
|
||||
|
||||
for (segment_id, bezier, start, end) in old_vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in old_vector.segment_iter() {
|
||||
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
|
||||
|
||||
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
|
||||
|
|
@ -2811,11 +2820,8 @@ impl Fsm for PathToolFsmState {
|
|||
let new_id = SegmentId::generate();
|
||||
segments_map.insert(segment_id, new_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
let points = pathseg_points(bezier);
|
||||
let handles = [points.p1, points.p2];
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
|
|||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
||||
use kurbo::{CubicBez, PathSeg};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PenTool {
|
||||
|
|
@ -1257,7 +1258,7 @@ impl PenToolData {
|
|||
let vector = document.network_interface.compute_modified_vector(layer);
|
||||
let (handle_start, in_segment) = if let Some(vector) = &vector {
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.segment_iter()
|
||||
.find_map(|(segment_id, bezier, start, end)| {
|
||||
let is_end = point == end;
|
||||
let is_start = point == start;
|
||||
|
|
@ -1265,15 +1266,11 @@ impl PenToolData {
|
|||
return None;
|
||||
}
|
||||
|
||||
let handle = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end, .. } => {
|
||||
if is_start {
|
||||
handle_start
|
||||
} else {
|
||||
handle_end
|
||||
}
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => handle,
|
||||
let points = pathseg_points(bezier);
|
||||
let handle = match (points.p1, points.p2) {
|
||||
(Some(p1), Some(_)) if is_start => p1,
|
||||
(Some(_), Some(p2)) if !is_start => p2,
|
||||
(Some(p1), None) | (None, Some(p1)) => p1,
|
||||
_ => return None,
|
||||
};
|
||||
Some((segment_id, is_end, handle))
|
||||
|
|
@ -1599,9 +1596,8 @@ impl Fsm for PenToolFsmState {
|
|||
let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));
|
||||
|
||||
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
|
||||
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
||||
let end = tool_data.next_point;
|
||||
let bezier = Bezier { start, handles, end };
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||
if (end - start).length_squared() > f64::EPSILON {
|
||||
// Draw the curve for the currently-being-placed segment
|
||||
overlay_context.outline_bezier(bezier, transform);
|
||||
|
|
@ -1723,7 +1719,7 @@ impl Fsm for PenToolFsmState {
|
|||
// We have the point. Join the 2 vertices and check if any path is closed.
|
||||
if let Some(end) = closest_point {
|
||||
let segment_id = SegmentId::generate();
|
||||
vector.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
||||
vector.push(segment_id, start, end, (Some(handle_start), Some(handle_end)), StrokeId::ZERO);
|
||||
|
||||
let grouped_segments = vector.auto_join_paths();
|
||||
let closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
|
||||
|
|
|
|||
|
|
@ -20,12 +20,12 @@ use crate::messages::tool::common_functionality::shape_editor::SelectionShapeTyp
|
|||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DMat2;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use std::fmt;
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ rustc-hash = { workspace = true }
|
|||
dyn-any = { workspace = true }
|
||||
ctor = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
image = { workspace = true }
|
||||
tinyvec = { workspace = true }
|
||||
|
|
@ -38,6 +37,7 @@ skrifa = { workspace = true }
|
|||
kurbo = { workspace = true }
|
||||
log = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
poly-cool = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true }
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ pub mod raster_types;
|
|||
pub mod registry;
|
||||
pub mod render_complexity;
|
||||
pub mod structural;
|
||||
pub mod subpath;
|
||||
pub mod table;
|
||||
pub mod text;
|
||||
pub mod transform;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
use crate::math::quad::Quad;
|
||||
use crate::math::rect::Rect;
|
||||
use bezier_rs::Bezier;
|
||||
use crate::subpath::Bezier;
|
||||
use crate::vector::misc::dvec2_to_point;
|
||||
use kurbo::{Line, PathSeg};
|
||||
|
||||
pub trait QuadExt {
|
||||
/// Get all the edges in the rect as linear bezier curves
|
||||
fn bezier_lines(&self) -> impl Iterator<Item = Bezier> + '_;
|
||||
fn to_lines(&self) -> impl Iterator<Item = PathSeg>;
|
||||
}
|
||||
|
||||
impl QuadExt for Quad {
|
||||
fn bezier_lines(&self) -> impl Iterator<Item = Bezier> + '_ {
|
||||
self.all_edges().into_iter().map(|[start, end]| Bezier::from_linear_dvec2(start, end))
|
||||
}
|
||||
|
||||
fn to_lines(&self) -> impl Iterator<Item = PathSeg> {
|
||||
self.all_edges().into_iter().map(|[start, end]| PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RectExt {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod bbox;
|
||||
pub mod math_ext;
|
||||
pub mod polynomial;
|
||||
pub mod quad;
|
||||
pub mod rect;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,292 @@
|
|||
use kurbo::PathSeg;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
||||
/// A struct that represents a polynomial with a maximum degree of `N-1`.
|
||||
///
|
||||
/// It provides basic mathematical operations for polynomials like addition, multiplication, differentiation, integration, etc.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Polynomial<const N: usize> {
|
||||
coefficients: [f64; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Polynomial<N> {
|
||||
/// Create a new polynomial from the coefficients given in the array.
|
||||
///
|
||||
/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.
|
||||
pub fn new(coefficients: [f64; N]) -> Polynomial<N> {
|
||||
Polynomial { coefficients }
|
||||
}
|
||||
|
||||
/// Create a polynomial where all its coefficients are zero.
|
||||
pub fn zero() -> Polynomial<N> {
|
||||
Polynomial { coefficients: [0.; N] }
|
||||
}
|
||||
|
||||
/// Return an immutable reference to the coefficients.
|
||||
///
|
||||
/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.
|
||||
pub fn coefficients(&self) -> &[f64; N] {
|
||||
&self.coefficients
|
||||
}
|
||||
|
||||
/// Return a mutable reference to the coefficients.
|
||||
///
|
||||
/// The coefficient for nth degree is at the nth index in array. Therefore the order of coefficients are reversed than the usual order for writing polynomials mathematically.
|
||||
pub fn coefficients_mut(&mut self) -> &mut [f64; N] {
|
||||
&mut self.coefficients
|
||||
}
|
||||
|
||||
/// Evaluate the polynomial at `value`.
|
||||
pub fn eval(&self, value: f64) -> f64 {
|
||||
self.coefficients.iter().rev().copied().reduce(|acc, x| acc * value + x).unwrap()
|
||||
}
|
||||
|
||||
/// Return the same polynomial but with a different maximum degree of `M-1`.\
|
||||
///
|
||||
/// Returns `None` if the polynomial cannot fit in the specified size.
|
||||
pub fn as_size<const M: usize>(&self) -> Option<Polynomial<M>> {
|
||||
let mut coefficients = [0.; M];
|
||||
|
||||
if M >= N {
|
||||
coefficients[..N].copy_from_slice(&self.coefficients);
|
||||
} else if self.coefficients.iter().rev().take(N - M).all(|&x| x == 0.) {
|
||||
coefficients.copy_from_slice(&self.coefficients[..M])
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Polynomial { coefficients })
|
||||
}
|
||||
|
||||
/// Computes the derivative in place.
|
||||
pub fn derivative_mut(&mut self) {
|
||||
self.coefficients.iter_mut().enumerate().for_each(|(index, x)| *x *= index as f64);
|
||||
self.coefficients.rotate_left(1);
|
||||
}
|
||||
|
||||
/// Computes the antiderivative at `C = 0` in place.
|
||||
///
|
||||
/// Returns `None` if the polynomial is not big enough to accommodate the extra degree.
|
||||
pub fn antiderivative_mut(&mut self) -> Option<()> {
|
||||
if self.coefficients[N - 1] != 0. {
|
||||
return None;
|
||||
}
|
||||
self.coefficients.rotate_right(1);
|
||||
self.coefficients.iter_mut().enumerate().skip(1).for_each(|(index, x)| *x /= index as f64);
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Computes the polynomial's derivative.
|
||||
pub fn derivative(&self) -> Polynomial<N> {
|
||||
let mut ans = *self;
|
||||
ans.derivative_mut();
|
||||
ans
|
||||
}
|
||||
|
||||
/// Computes the antiderivative at `C = 0`.
|
||||
///
|
||||
/// Returns `None` if the polynomial is not big enough to accommodate the extra degree.
|
||||
pub fn antiderivative(&self) -> Option<Polynomial<N>> {
|
||||
let mut ans = *self;
|
||||
ans.antiderivative_mut()?;
|
||||
Some(ans)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Default for Polynomial<N> {
|
||||
fn default() -> Self {
|
||||
Self::zero()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Display for Polynomial<N> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let mut first = true;
|
||||
for (index, coefficient) in self.coefficients.iter().enumerate().rev().filter(|&(_, &coefficient)| coefficient != 0.) {
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
f.write_str(" + ")?
|
||||
}
|
||||
|
||||
coefficient.fmt(f)?;
|
||||
if index == 0 {
|
||||
continue;
|
||||
}
|
||||
f.write_str("x")?;
|
||||
if index == 1 {
|
||||
continue;
|
||||
}
|
||||
f.write_str("^")?;
|
||||
index.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AddAssign<&Polynomial<N>> for Polynomial<N> {
|
||||
fn add_assign(&mut self, rhs: &Polynomial<N>) {
|
||||
self.coefficients.iter_mut().zip(rhs.coefficients.iter()).for_each(|(a, b)| *a += b);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Add for &Polynomial<N> {
|
||||
type Output = Polynomial<N>;
|
||||
|
||||
fn add(self, other: &Polynomial<N>) -> Polynomial<N> {
|
||||
let mut output = *self;
|
||||
output += other;
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Neg for &Polynomial<N> {
|
||||
type Output = Polynomial<N>;
|
||||
|
||||
fn neg(self) -> Polynomial<N> {
|
||||
let mut output = *self;
|
||||
output.coefficients.iter_mut().for_each(|x| *x = -*x);
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Neg for Polynomial<N> {
|
||||
type Output = Polynomial<N>;
|
||||
|
||||
fn neg(mut self) -> Polynomial<N> {
|
||||
self.coefficients.iter_mut().for_each(|x| *x = -*x);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> SubAssign<&Polynomial<N>> for Polynomial<N> {
|
||||
fn sub_assign(&mut self, rhs: &Polynomial<N>) {
|
||||
self.coefficients.iter_mut().zip(rhs.coefficients.iter()).for_each(|(a, b)| *a -= b);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Sub for &Polynomial<N> {
|
||||
type Output = Polynomial<N>;
|
||||
|
||||
fn sub(self, other: &Polynomial<N>) -> Polynomial<N> {
|
||||
let mut output = *self;
|
||||
output -= other;
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> MulAssign<&Polynomial<N>> for Polynomial<N> {
|
||||
fn mul_assign(&mut self, rhs: &Polynomial<N>) {
|
||||
for i in (0..N).rev() {
|
||||
self.coefficients[i] = self.coefficients[i] * rhs.coefficients[0];
|
||||
for j in 0..i {
|
||||
self.coefficients[i] += self.coefficients[j] * rhs.coefficients[i - j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Mul for &Polynomial<N> {
|
||||
type Output = Polynomial<N>;
|
||||
|
||||
fn mul(self, other: &Polynomial<N>) -> Polynomial<N> {
|
||||
let mut output = *self;
|
||||
output *= other;
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns two [`Polynomial`]s representing the parametric equations for x and y coordinates of the bezier curve respectively.
|
||||
/// The domain of both the equations are from t=0.0 representing the start and t=1.0 representing the end of the bezier curve.
|
||||
pub fn pathseg_to_parametric_polynomial(segment: PathSeg) -> (Polynomial<4>, Polynomial<4>) {
|
||||
match segment {
|
||||
PathSeg::Line(line) => {
|
||||
let term1 = line.p0 - line.p1;
|
||||
(Polynomial::new([line.p0.x, term1.x, 0., 0.]), Polynomial::new([line.p0.y, term1.y, 0., 0.]))
|
||||
}
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let term1 = 2. * (quad_bez.p1 - quad_bez.p0);
|
||||
let term2 = quad_bez.p0 - 2. * quad_bez.p1.to_vec2() + quad_bez.p2.to_vec2();
|
||||
|
||||
(Polynomial::new([quad_bez.p0.x, term1.x, term2.x, 0.]), Polynomial::new([quad_bez.p0.y, term1.y, term2.y, 0.]))
|
||||
}
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let term1 = 3. * (cubic_bez.p1 - cubic_bez.p0);
|
||||
let term2 = 3. * (cubic_bez.p2 - cubic_bez.p1) - term1;
|
||||
let term3 = cubic_bez.p3 - cubic_bez.p0 - term2 - term1;
|
||||
|
||||
(
|
||||
Polynomial::new([cubic_bez.p0.x, term1.x, term2.x, term3.x]),
|
||||
Polynomial::new([cubic_bez.p0.y, term1.y, term2.y, term3.y]),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn evaluation() {
|
||||
let p = Polynomial::new([1., 2., 3.]);
|
||||
|
||||
assert_eq!(p.eval(1.), 6.);
|
||||
assert_eq!(p.eval(2.), 17.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_change() {
|
||||
let p1 = Polynomial::new([1., 2., 3.]);
|
||||
let p2 = Polynomial::new([1., 2., 3., 0.]);
|
||||
|
||||
assert_eq!(p1.as_size(), Some(p2));
|
||||
assert_eq!(p2.as_size(), Some(p1));
|
||||
|
||||
assert_eq!(p2.as_size::<2>(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addition_and_subtaction() {
|
||||
let p1 = Polynomial::new([1., 2., 3.]);
|
||||
let p2 = Polynomial::new([4., 5., 6.]);
|
||||
|
||||
let addition = Polynomial::new([5., 7., 9.]);
|
||||
let subtraction = Polynomial::new([-3., -3., -3.]);
|
||||
|
||||
assert_eq!(&p1 + &p2, addition);
|
||||
assert_eq!(&p1 - &p2, subtraction);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiplication() {
|
||||
let p1 = Polynomial::new([1., 2., 3.]).as_size().unwrap();
|
||||
let p2 = Polynomial::new([4., 5., 6.]).as_size().unwrap();
|
||||
|
||||
let multiplication = Polynomial::new([4., 13., 28., 27., 18.]);
|
||||
|
||||
assert_eq!(&p1 * &p2, multiplication);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivative_and_antiderivative() {
|
||||
let mut p = Polynomial::new([1., 2., 3.]);
|
||||
let p_deriv = Polynomial::new([2., 6., 0.]);
|
||||
|
||||
assert_eq!(p.derivative(), p_deriv);
|
||||
|
||||
p.coefficients_mut()[0] = 0.;
|
||||
assert_eq!(p_deriv.antiderivative().unwrap(), p);
|
||||
|
||||
assert_eq!(p.antiderivative(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
let p = Polynomial::new([1., 2., 0., 3.]);
|
||||
|
||||
assert_eq!(format!("{:.2}", p), "3.00x^3 + 2.00x + 1.00");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Implementation constants
|
||||
|
||||
/// Constant used to determine if `f64`s are equivalent.
|
||||
pub const MAX_ABSOLUTE_DIFFERENCE: f64 = 1e-3;
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
use super::consts::*;
|
||||
use super::*;
|
||||
use crate::vector::misc::point_to_dvec2;
|
||||
use glam::DVec2;
|
||||
use kurbo::PathSeg;
|
||||
|
||||
pub struct PathSegPoints {
|
||||
pub p0: DVec2,
|
||||
pub p1: Option<DVec2>,
|
||||
pub p2: Option<DVec2>,
|
||||
pub p3: DVec2,
|
||||
}
|
||||
|
||||
impl PathSegPoints {
|
||||
pub fn new(p0: DVec2, p1: Option<DVec2>, p2: Option<DVec2>, p3: DVec2) -> Self {
|
||||
Self { p0, p1, p2, p3 }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pathseg_points(segment: PathSeg) -> PathSegPoints {
|
||||
match segment {
|
||||
PathSeg::Line(line) => PathSegPoints::new(point_to_dvec2(line.p0), None, None, point_to_dvec2(line.p1)),
|
||||
PathSeg::Quad(quad) => PathSegPoints::new(point_to_dvec2(quad.p0), None, Some(point_to_dvec2(quad.p1)), point_to_dvec2(quad.p2)),
|
||||
PathSeg::Cubic(cube) => PathSegPoints::new(point_to_dvec2(cube.p0), Some(point_to_dvec2(cube.p1)), Some(point_to_dvec2(cube.p2)), point_to_dvec2(cube.p3)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
|
||||
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
|
||||
#[track_caller]
|
||||
pub fn new(manipulator_groups: Vec<ManipulatorGroup<PointId>>, closed: bool) -> Self {
|
||||
assert!(!closed || !manipulator_groups.is_empty(), "A closed Subpath must contain more than 0 ManipulatorGroups.");
|
||||
Self { manipulator_groups, closed }
|
||||
}
|
||||
|
||||
/// Create a `Subpath` consisting of 2 manipulator groups from a `Bezier`.
|
||||
pub fn from_bezier(segment: PathSeg) -> Self {
|
||||
let PathSegPoints { p0, p1, p2, p3 } = pathseg_points(segment);
|
||||
Subpath::new(vec![ManipulatorGroup::new(p0, None, p1), ManipulatorGroup::new(p3, p2, None)], false)
|
||||
}
|
||||
|
||||
/// Creates a subpath from a slice of [Bezier]. When two consecutive Beziers do not share an end and start point, this function
|
||||
/// resolves the discrepancy by simply taking the start-point of the second Bezier as the anchor of the Manipulator Group.
|
||||
pub fn from_beziers(beziers: &[PathSeg], closed: bool) -> Self {
|
||||
assert!(!closed || beziers.len() > 1, "A closed Subpath must contain at least 1 Bezier.");
|
||||
if beziers.is_empty() {
|
||||
return Subpath::new(vec![], closed);
|
||||
}
|
||||
|
||||
let beziers: Vec<_> = beziers.iter().map(|b| pathseg_points(*b)).collect();
|
||||
|
||||
let first = beziers.first().unwrap();
|
||||
let mut manipulator_groups = vec![ManipulatorGroup {
|
||||
anchor: first.p0,
|
||||
in_handle: None,
|
||||
out_handle: first.p1,
|
||||
id: PointId::new(),
|
||||
}];
|
||||
let mut inner_groups: Vec<ManipulatorGroup<PointId>> = beziers
|
||||
.windows(2)
|
||||
.map(|bezier_pair| ManipulatorGroup {
|
||||
anchor: bezier_pair[1].p0,
|
||||
in_handle: bezier_pair[0].p2,
|
||||
out_handle: bezier_pair[1].p1,
|
||||
id: PointId::new(),
|
||||
})
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>();
|
||||
manipulator_groups.append(&mut inner_groups);
|
||||
|
||||
let last = beziers.last().unwrap();
|
||||
if !closed {
|
||||
manipulator_groups.push(ManipulatorGroup {
|
||||
anchor: last.p3,
|
||||
in_handle: last.p2,
|
||||
out_handle: None,
|
||||
id: PointId::new(),
|
||||
});
|
||||
return Subpath::new(manipulator_groups, false);
|
||||
}
|
||||
|
||||
manipulator_groups[0].in_handle = last.p2;
|
||||
Subpath::new(manipulator_groups, true)
|
||||
}
|
||||
|
||||
/// Returns true if the `Subpath` contains no [ManipulatorGroup].
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.manipulator_groups.is_empty()
|
||||
}
|
||||
|
||||
/// Returns the number of [ManipulatorGroup]s contained within the `Subpath`.
|
||||
pub fn len(&self) -> usize {
|
||||
self.manipulator_groups.len()
|
||||
}
|
||||
|
||||
/// Returns the number of segments contained within the `Subpath`.
|
||||
pub fn len_segments(&self) -> usize {
|
||||
let mut number_of_curves = self.len();
|
||||
if !self.closed && number_of_curves > 0 {
|
||||
number_of_curves -= 1
|
||||
}
|
||||
number_of_curves
|
||||
}
|
||||
|
||||
/// Returns a copy of the bezier segment at the given segment index, if this segment exists.
|
||||
pub fn get_segment(&self, segment_index: usize) -> Option<PathSeg> {
|
||||
if segment_index >= self.len_segments() {
|
||||
return None;
|
||||
}
|
||||
Some(self[segment_index].to_bezier(&self[(segment_index + 1) % self.len()]))
|
||||
}
|
||||
|
||||
/// Returns an iterator of the [Bezier]s along the `Subpath`.
|
||||
pub fn iter(&self) -> SubpathIter<'_, PointId> {
|
||||
SubpathIter {
|
||||
subpath: self,
|
||||
index: 0,
|
||||
is_always_closed: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator of the [Bezier]s along the `Subpath` always considering it as a closed subpath.
|
||||
pub fn iter_closed(&self) -> SubpathIter<'_, PointId> {
|
||||
SubpathIter {
|
||||
subpath: self,
|
||||
index: 0,
|
||||
is_always_closed: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a slice of the [ManipulatorGroup]s in the `Subpath`.
|
||||
pub fn manipulator_groups(&self) -> &[ManipulatorGroup<PointId>] {
|
||||
&self.manipulator_groups
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the [ManipulatorGroup]s in the `Subpath`.
|
||||
pub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<PointId>> {
|
||||
&mut self.manipulator_groups
|
||||
}
|
||||
|
||||
/// Returns a vector of all the anchors (DVec2) for this `Subpath`.
|
||||
pub fn anchors(&self) -> Vec<DVec2> {
|
||||
self.manipulator_groups().iter().map(|group| group.anchor).collect()
|
||||
}
|
||||
|
||||
/// Returns if the Subpath is equivalent to a single point.
|
||||
pub fn is_point(&self) -> bool {
|
||||
if self.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let point = self.manipulator_groups[0].anchor;
|
||||
self.manipulator_groups
|
||||
.iter()
|
||||
.all(|manipulator_group| manipulator_group.anchor.abs_diff_eq(point, MAX_ABSOLUTE_DIFFERENCE))
|
||||
}
|
||||
|
||||
/// Construct a [Subpath] from an iter of anchor positions.
|
||||
pub fn from_anchors(anchor_positions: impl IntoIterator<Item = DVec2>, closed: bool) -> Self {
|
||||
Self::new(anchor_positions.into_iter().map(|anchor| ManipulatorGroup::new_anchor(anchor)).collect(), closed)
|
||||
}
|
||||
|
||||
pub fn from_anchors_linear(anchor_positions: impl IntoIterator<Item = DVec2>, closed: bool) -> Self {
|
||||
Self::new(anchor_positions.into_iter().map(|anchor| ManipulatorGroup::new_anchor_linear(anchor)).collect(), closed)
|
||||
}
|
||||
|
||||
/// Constructs a rectangle with `corner1` and `corner2` as the two corners.
|
||||
pub fn new_rect(corner1: DVec2, corner2: DVec2) -> Self {
|
||||
Self::from_anchors_linear([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true)
|
||||
}
|
||||
|
||||
/// Constructs a rounded rectangle with `corner1` and `corner2` as the two corners and `corner_radii` as the radii of the corners: `[top_left, top_right, bottom_right, bottom_left]`.
|
||||
pub fn new_rounded_rect(corner1: DVec2, corner2: DVec2, corner_radii: [f64; 4]) -> Self {
|
||||
if corner_radii.iter().all(|radii| radii.abs() < f64::EPSILON * 100.) {
|
||||
return Self::new_rect(corner1, corner2);
|
||||
}
|
||||
|
||||
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
||||
|
||||
let new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<PointId>> {
|
||||
let point1 = center + DVec2::from_angle(-PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||
let point2 = center + DVec2::from_angle(PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||
if radius == 0. {
|
||||
return vec![ManipulatorGroup::new_anchor(point1), ManipulatorGroup::new_anchor(point2)];
|
||||
}
|
||||
|
||||
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
||||
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
||||
let handle_offset = radius * HANDLE_OFFSET_FACTOR;
|
||||
vec![
|
||||
ManipulatorGroup::new(point1, None, Some(point1 + handle_offset * (corner - point1).normalize())),
|
||||
ManipulatorGroup::new(point2, Some(point2 + handle_offset * (corner - point2).normalize()), None),
|
||||
]
|
||||
};
|
||||
Self::new(
|
||||
[
|
||||
new_arc(DVec2::new(corner1.x + corner_radii[0], corner1.y + corner_radii[0]), DVec2::new(corner1.x, corner1.y), corner_radii[0]),
|
||||
new_arc(DVec2::new(corner2.x - corner_radii[1], corner1.y + corner_radii[1]), DVec2::new(corner2.x, corner1.y), corner_radii[1]),
|
||||
new_arc(DVec2::new(corner2.x - corner_radii[2], corner2.y - corner_radii[2]), DVec2::new(corner2.x, corner2.y), corner_radii[2]),
|
||||
new_arc(DVec2::new(corner1.x + corner_radii[3], corner2.y - corner_radii[3]), DVec2::new(corner1.x, corner2.y), corner_radii[3]),
|
||||
]
|
||||
.concat(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box.
|
||||
pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self {
|
||||
let size = (corner1 - corner2).abs();
|
||||
let center = (corner1 + corner2) / 2.;
|
||||
let top = DVec2::new(center.x, corner1.y);
|
||||
let bottom = DVec2::new(center.x, corner2.y);
|
||||
let left = DVec2::new(corner1.x, center.y);
|
||||
let right = DVec2::new(corner2.x, center.y);
|
||||
|
||||
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
||||
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
||||
let handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5;
|
||||
|
||||
let manipulator_groups = vec![
|
||||
ManipulatorGroup::new(top, Some(top - handle_offset * DVec2::X), Some(top + handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(right, Some(right - handle_offset * DVec2::Y), Some(right + handle_offset * DVec2::Y)),
|
||||
ManipulatorGroup::new(bottom, Some(bottom + handle_offset * DVec2::X), Some(bottom - handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(left, Some(left + handle_offset * DVec2::Y), Some(left - handle_offset * DVec2::Y)),
|
||||
];
|
||||
Self::new(manipulator_groups, true)
|
||||
}
|
||||
|
||||
/// Constructs an arc by a `radius`, `angle_start` and `angle_size`. Angles must be in radians. Slice option makes it look like pie or pacman.
|
||||
pub fn new_arc(radius: f64, start_angle: f64, sweep_angle: f64, arc_type: ArcType) -> Self {
|
||||
// Prevents glitches from numerical imprecision that have been observed during animation playback after about a minute
|
||||
let start_angle = start_angle % (std::f64::consts::TAU * 2.);
|
||||
let sweep_angle = sweep_angle % (std::f64::consts::TAU * 2.);
|
||||
|
||||
let original_start_angle = start_angle;
|
||||
let sweep_angle_sign = sweep_angle.signum();
|
||||
|
||||
let mut start_angle = 0.;
|
||||
let mut sweep_angle = sweep_angle.abs();
|
||||
|
||||
if (sweep_angle / std::f64::consts::TAU).floor() as u32 % 2 == 0 {
|
||||
sweep_angle %= std::f64::consts::TAU;
|
||||
} else {
|
||||
start_angle = sweep_angle % std::f64::consts::TAU;
|
||||
sweep_angle = std::f64::consts::TAU - start_angle;
|
||||
}
|
||||
|
||||
sweep_angle *= sweep_angle_sign;
|
||||
start_angle *= sweep_angle_sign;
|
||||
start_angle += original_start_angle;
|
||||
|
||||
let closed = arc_type == ArcType::Closed;
|
||||
let slice = arc_type == ArcType::PieSlice;
|
||||
|
||||
let center = DVec2::new(0., 0.);
|
||||
let segments = (sweep_angle.abs() / (std::f64::consts::PI / 4.)).ceil().max(1.) as usize;
|
||||
let step = sweep_angle / segments as f64;
|
||||
let factor = 4. / 3. * (step / 2.).sin() / (1. + (step / 2.).cos());
|
||||
|
||||
let mut manipulator_groups = Vec::with_capacity(segments);
|
||||
let mut prev_in_handle = None;
|
||||
let mut prev_end = DVec2::new(0., 0.);
|
||||
|
||||
for i in 0..segments {
|
||||
let start_angle = start_angle + step * i as f64;
|
||||
let end_angle = start_angle + step;
|
||||
let start_vec = DVec2::from_angle(start_angle);
|
||||
let end_vec = DVec2::from_angle(end_angle);
|
||||
|
||||
let start = center + radius * start_vec;
|
||||
let end = center + radius * end_vec;
|
||||
|
||||
let handle_start = start + start_vec.perp() * radius * factor;
|
||||
let handle_end = end - end_vec.perp() * radius * factor;
|
||||
|
||||
manipulator_groups.push(ManipulatorGroup::new(start, prev_in_handle, Some(handle_start)));
|
||||
prev_in_handle = Some(handle_end);
|
||||
prev_end = end;
|
||||
}
|
||||
manipulator_groups.push(ManipulatorGroup::new(prev_end, prev_in_handle, None));
|
||||
|
||||
if slice {
|
||||
manipulator_groups.push(ManipulatorGroup::new(center, None, None));
|
||||
}
|
||||
|
||||
Self::new(manipulator_groups, closed || slice)
|
||||
}
|
||||
|
||||
/// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex.
|
||||
pub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self {
|
||||
let sides = sides.max(3);
|
||||
let angle_increment = std::f64::consts::TAU / (sides as f64);
|
||||
let anchor_positions = (0..sides).map(|i| {
|
||||
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
||||
let center = center + DVec2::ONE * radius;
|
||||
DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5
|
||||
});
|
||||
Self::from_anchors(anchor_positions, true)
|
||||
}
|
||||
|
||||
/// Constructs a star polygon (n-star). See [new_regular_polygon], but with interspersed vertices at an `inner_radius`.
|
||||
pub fn new_star_polygon(center: DVec2, sides: u64, radius: f64, inner_radius: f64) -> Self {
|
||||
let sides = sides.max(2);
|
||||
let angle_increment = 0.5 * std::f64::consts::TAU / (sides as f64);
|
||||
let anchor_positions = (0..sides * 2).map(|i| {
|
||||
let angle = (i as f64) * angle_increment - std::f64::consts::FRAC_PI_2;
|
||||
let center = center + DVec2::ONE * radius;
|
||||
let r = if i % 2 == 0 { radius } else { inner_radius };
|
||||
DVec2::new(center.x + r * f64::cos(angle), center.y + r * f64::sin(angle)) * 0.5
|
||||
});
|
||||
Self::from_anchors(anchor_positions, true)
|
||||
}
|
||||
|
||||
/// Constructs a line from `p1` to `p2`
|
||||
pub fn new_line(p1: DVec2, p2: DVec2) -> Self {
|
||||
Self::from_anchors([p1, p2], false)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
use super::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use super::*;
|
||||
use crate::math::polynomial::pathseg_to_parametric_polynomial;
|
||||
use crate::vector::algorithms::bezpath_algorithms::pathseg_length_centroid_and_length;
|
||||
use crate::vector::algorithms::intersection::{filtered_all_segment_intersections, pathseg_self_intersections};
|
||||
use glam::DVec2;
|
||||
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
/// Returns a list of `t` values that correspond to all the self intersection points of the subpath always considering it as a closed subpath. The index and `t` value of both will be returned that corresponds to a point.
|
||||
/// The points will be sorted based on their index and `t` repsectively.
|
||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||
/// - `minimum_separation`: the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
|
||||
///
|
||||
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two
|
||||
///
|
||||
/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.
|
||||
pub fn all_self_intersections(&self, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||
let mut intersections_vec = Vec::new();
|
||||
let err = accuracy.unwrap_or(MAX_ABSOLUTE_DIFFERENCE);
|
||||
let num_curves = self.len();
|
||||
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
|
||||
self.iter_closed().enumerate().for_each(|(i, other)| {
|
||||
intersections_vec.extend(pathseg_self_intersections(other, accuracy, minimum_separation).iter().flat_map(|value| [(i, value.0), (i, value.1)]));
|
||||
self.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
||||
intersections_vec.extend(
|
||||
filtered_all_segment_intersections(curve, other, accuracy, minimum_separation)
|
||||
.iter()
|
||||
.filter(|&value| (j != i + 1 || value.0 > err || (1. - value.1) > err) && (j != num_curves - 1 || i != 0 || value.1 > err || (1. - value.0) > err))
|
||||
.flat_map(|value| [(j, value.0), (i, value.1)]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
intersections_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
intersections_vec
|
||||
}
|
||||
|
||||
/// Return the area centroid, together with the area, of the `Subpath` always considering it as a closed subpath. The area will always be a positive value.
|
||||
///
|
||||
/// The area centroid is the center of mass for the area of a solid shape's interior.
|
||||
/// An infinitely flat material forming the subpath's closed shape would balance at this point.
|
||||
///
|
||||
/// It will return `None` if no manipulator is present. If the area is less than `error`, it will return `Some((DVec2::NAN, 0.))`.
|
||||
///
|
||||
/// Because the calculation of area and centroid for self-intersecting path requires finding the intersections, the following parameters are used:
|
||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||
/// - `minimum_separation` - the minimum difference two adjacent `t`-values must have when comparing adjacent `t`-values in sorted order.
|
||||
///
|
||||
/// If the comparison condition is not satisfied, the function takes the larger `t`-value of the two.
|
||||
///
|
||||
/// **NOTE**: if an intersection were to occur within an `error` distance away from an anchor point, the algorithm will filter that intersection out.
|
||||
pub fn area_centroid_and_area(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Option<(DVec2, f64)> {
|
||||
let all_intersections = self.all_self_intersections(error, minimum_separation);
|
||||
let mut current_sign: f64 = 1.;
|
||||
|
||||
let (x_sum, y_sum, area) = self
|
||||
.iter_closed()
|
||||
.enumerate()
|
||||
.map(|(index, bezier)| {
|
||||
let (f_x, f_y) = pathseg_to_parametric_polynomial(bezier);
|
||||
let (f_x, f_y) = (f_x.as_size::<10>().unwrap(), f_y.as_size::<10>().unwrap());
|
||||
let f_y_prime = f_y.derivative();
|
||||
let f_x_prime = f_x.derivative();
|
||||
let f_xy = &f_x * &f_y;
|
||||
|
||||
let mut x_part = &f_xy * &f_x_prime;
|
||||
let mut y_part = &f_xy * &f_y_prime;
|
||||
let mut area_part = &f_x * &f_y_prime;
|
||||
x_part.antiderivative_mut();
|
||||
y_part.antiderivative_mut();
|
||||
area_part.antiderivative_mut();
|
||||
|
||||
let mut curve_sum_x = -current_sign * x_part.eval(0.);
|
||||
let mut curve_sum_y = -current_sign * y_part.eval(0.);
|
||||
let mut curve_sum_area = -current_sign * area_part.eval(0.);
|
||||
for (_, t) in all_intersections.iter().filter(|(i, _)| *i == index) {
|
||||
curve_sum_x += 2. * current_sign * x_part.eval(*t);
|
||||
curve_sum_y += 2. * current_sign * y_part.eval(*t);
|
||||
curve_sum_area += 2. * current_sign * area_part.eval(*t);
|
||||
current_sign *= -1.;
|
||||
}
|
||||
curve_sum_x += current_sign * x_part.eval(1.);
|
||||
curve_sum_y += current_sign * y_part.eval(1.);
|
||||
curve_sum_area += current_sign * area_part.eval(1.);
|
||||
|
||||
(-curve_sum_x, curve_sum_y, curve_sum_area)
|
||||
})
|
||||
.reduce(|(x1, y1, area1), (x2, y2, area2)| (x1 + x2, y1 + y2, area1 + area2))?;
|
||||
|
||||
if area.abs() < error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE) {
|
||||
return Some((DVec2::NAN, 0.));
|
||||
}
|
||||
|
||||
Some((DVec2::new(x_sum / area, y_sum / area), area.abs()))
|
||||
}
|
||||
|
||||
/// Return the approximation of the length centroid, together with the length, of the `Subpath`.
|
||||
///
|
||||
/// The length centroid is the center of mass for the arc length of the solid shape's perimeter.
|
||||
/// An infinitely thin wire forming the subpath's closed shape would balance at this point.
|
||||
///
|
||||
/// It will return `None` if no manipulator is present.
|
||||
/// - `accuracy` is used to approximate the curve.
|
||||
/// - `always_closed` is to consider the subpath as closed always.
|
||||
pub fn length_centroid_and_length(&self, accuracy: Option<f64>, always_closed: bool) -> Option<(DVec2, f64)> {
|
||||
if always_closed { self.iter_closed() } else { self.iter() }
|
||||
.map(|bezier| pathseg_length_centroid_and_length(bezier, accuracy))
|
||||
.map(|(centroid, length)| (centroid * length, length))
|
||||
.reduce(|(centroid_part1, length1), (centroid_part2, length2)| (centroid_part1 + centroid_part2, length1 + length2))
|
||||
.map(|(centroid_part, length)| (centroid_part / length, length))
|
||||
.map(|(centroid_part, length)| (DVec2::new(centroid_part.x, centroid_part.y), length))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// use super::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
// use super::utils::{SubpathTValue};
|
||||
use super::*;
|
||||
|
||||
impl<PointId: super::structs::Identifier> Subpath<PointId> {
|
||||
/// Get whether the subpath is closed.
|
||||
pub fn closed(&self) -> bool {
|
||||
self.closed
|
||||
}
|
||||
|
||||
/// Set whether the subpath is closed.
|
||||
pub fn set_closed(&mut self, new_closed: bool) {
|
||||
self.closed = new_closed;
|
||||
}
|
||||
|
||||
/// Access a [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_from_id(&self, id: PointId) -> Option<&ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access a mutable [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_mut_from_id(&mut self, id: PointId) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access the index of a [ManipulatorGroup] from a PointId.
|
||||
pub fn manipulator_index_from_id(&self, id: PointId) -> Option<usize> {
|
||||
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Insert a manipulator group at an index.
|
||||
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<PointId>) {
|
||||
assert!(group.is_finite(), "Inserting non finite manipulator group");
|
||||
self.manipulator_groups.insert(index, group)
|
||||
}
|
||||
|
||||
/// Push a manipulator group to the end.
|
||||
pub fn push_manipulator_group(&mut self, group: ManipulatorGroup<PointId>) {
|
||||
assert!(group.is_finite(), "Pushing non finite manipulator group");
|
||||
self.manipulator_groups.push(group)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the last manipulator
|
||||
pub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||
self.manipulator_groups.last_mut()
|
||||
}
|
||||
|
||||
/// Remove a manipulator group at an index.
|
||||
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<PointId> {
|
||||
self.manipulator_groups.remove(index)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
mod consts;
|
||||
mod core;
|
||||
mod lookup;
|
||||
mod manipulators;
|
||||
mod solvers;
|
||||
mod structs;
|
||||
mod transform;
|
||||
|
||||
pub use core::*;
|
||||
use kurbo::PathSeg;
|
||||
use std::fmt::{Debug, Formatter, Result};
|
||||
use std::ops::{Index, IndexMut};
|
||||
pub use structs::*;
|
||||
|
||||
/// Structure used to represent a path composed of [Bezier] curves.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Subpath<PointId: Identifier> {
|
||||
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
||||
pub closed: bool,
|
||||
}
|
||||
|
||||
/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
|
||||
pub struct SubpathIter<'a, PointId: Identifier> {
|
||||
index: usize,
|
||||
subpath: &'a Subpath<PointId>,
|
||||
is_always_closed: bool,
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Index<usize> for Subpath<PointId> {
|
||||
type Output = ManipulatorGroup<PointId>;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
|
||||
&self.manipulator_groups[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> IndexMut<usize> for Subpath<PointId> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
|
||||
&mut self.manipulator_groups[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Iterator for SubpathIter<'_, PointId> {
|
||||
type Item = PathSeg;
|
||||
|
||||
// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.subpath.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let closed = if self.is_always_closed { true } else { self.subpath.closed };
|
||||
let len = self.subpath.len() - 1 + if closed { 1 } else { 0 };
|
||||
if self.index >= len {
|
||||
return None;
|
||||
}
|
||||
let start_index = self.index;
|
||||
let end_index = (self.index + 1) % self.subpath.len();
|
||||
self.index += 1;
|
||||
|
||||
Some(self.subpath[start_index].to_bezier(&self.subpath[end_index]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Debug for Subpath<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("Subpath").field("closed", &self.closed).field("manipulator_groups", &self.manipulator_groups).finish()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
use crate::subpath::{Identifier, Subpath};
|
||||
use crate::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
|
||||
use crate::vector::misc::dvec2_to_point;
|
||||
use glam::DVec2;
|
||||
use kurbo::{Affine, BezPath, Shape};
|
||||
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
pub fn contains_point(&self, point: DVec2) -> bool {
|
||||
self.to_bezpath().contains(dvec2_to_point(point))
|
||||
}
|
||||
|
||||
pub fn to_bezpath(&self) -> BezPath {
|
||||
let mut bezpath = kurbo::BezPath::new();
|
||||
let mut out_handle;
|
||||
|
||||
let Some(first) = self.manipulator_groups.first() else { return bezpath };
|
||||
bezpath.move_to(dvec2_to_point(first.anchor));
|
||||
out_handle = first.out_handle;
|
||||
|
||||
for manipulator in self.manipulator_groups.iter().skip(1) {
|
||||
match (out_handle, manipulator.in_handle) {
|
||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),
|
||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
||||
}
|
||||
out_handle = manipulator.out_handle;
|
||||
}
|
||||
|
||||
if self.closed {
|
||||
match (out_handle, first.in_handle) {
|
||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),
|
||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
||||
}
|
||||
bezpath.close_path();
|
||||
}
|
||||
bezpath
|
||||
}
|
||||
|
||||
/// Returns `true` if this subpath is completely inside the `other` subpath.
|
||||
pub fn is_inside_subpath(&self, other: &Subpath<PointId>, accuracy: Option<f64>, minimum_separation: Option<f64>) -> bool {
|
||||
bezpath_is_inside_bezpath(&self.to_bezpath(), &other.to_bezpath(), accuracy, minimum_separation)
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the bounding box of the subpath. Return `None` if the subpath is empty.
|
||||
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.iter()
|
||||
.map(|bezier| bezier.bounding_box())
|
||||
.map(|bbox| [DVec2::new(bbox.min_x(), bbox.min_y()), DVec2::new(bbox.max_x(), bbox.max_y())])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the bounding box of the subpath, after a given affine transform.
|
||||
pub fn bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.iter()
|
||||
.map(|bezier| (Affine::new(transform.to_cols_array()) * bezier).bounding_box())
|
||||
.map(|bbox| [DVec2::new(bbox.min_x(), bbox.min_y()), DVec2::new(bbox.max_x(), bbox.max_y())])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the loose bounding box of the subpath (bounding box of all handles and anchors).
|
||||
pub fn loose_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.manipulator_groups
|
||||
.iter()
|
||||
.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)])
|
||||
.flatten()
|
||||
.map(|pos| [pos, pos])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the loose bounding box of the subpath, after a given affine transform.
|
||||
pub fn loose_bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.manipulator_groups
|
||||
.iter()
|
||||
.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)])
|
||||
.flatten()
|
||||
.map(|pos| transform.transform_point2(pos))
|
||||
.map(|pos| [pos, pos])
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,427 @@
|
|||
use crate::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use crate::vector::misc::{dvec2_to_point, handles_to_segment};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{CubicBez, Line, PathSeg, QuadBez, Shape};
|
||||
use std::fmt::{Debug, Formatter, Result};
|
||||
use std::hash::Hash;
|
||||
|
||||
/// An id type used for each [ManipulatorGroup].
|
||||
pub trait Identifier: Sized + Clone + PartialEq + Hash + 'static {
|
||||
fn new() -> Self;
|
||||
}
|
||||
|
||||
/// An empty id type for use in tests
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
#[cfg(test)]
|
||||
pub(crate) struct EmptyId;
|
||||
|
||||
#[cfg(test)]
|
||||
impl Identifier for EmptyId {
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorGroup<PointId: Identifier> {
|
||||
pub anchor: DVec2,
|
||||
pub in_handle: Option<DVec2>,
|
||||
pub out_handle: Option<DVec2>,
|
||||
pub id: PointId,
|
||||
}
|
||||
|
||||
// TODO: Remove once we no longer need to hash floats in Graphite
|
||||
impl<PointId: Identifier> Hash for ManipulatorGroup<PointId> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.in_handle.is_some().hash(state);
|
||||
if let Some(in_handle) = self.in_handle {
|
||||
in_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.out_handle.is_some().hash(state);
|
||||
if let Some(out_handle) = self.out_handle {
|
||||
out_handle.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
}
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> Debug for ManipulatorGroup<PointId> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.debug_struct("ManipulatorGroup")
|
||||
.field("anchor", &self.anchor)
|
||||
.field("in_handle", &self.in_handle)
|
||||
.field("out_handle", &self.out_handle)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<PointId: Identifier> ManipulatorGroup<PointId> {
|
||||
/// Construct a new manipulator group from an anchor, in handle and out handle
|
||||
pub fn new(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>) -> Self {
|
||||
let id = PointId::new();
|
||||
Self { anchor, in_handle, out_handle, id }
|
||||
}
|
||||
|
||||
/// Construct a new manipulator point with just an anchor position
|
||||
pub fn new_anchor(anchor: DVec2) -> Self {
|
||||
Self::new(anchor, Some(anchor), Some(anchor))
|
||||
}
|
||||
|
||||
pub fn new_anchor_linear(anchor: DVec2) -> Self {
|
||||
Self::new(anchor, None, None)
|
||||
}
|
||||
|
||||
/// Construct a new manipulator group from an anchor, in handle, out handle and an id
|
||||
pub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: PointId) -> Self {
|
||||
Self { anchor, in_handle, out_handle, id }
|
||||
}
|
||||
|
||||
/// Construct a new manipulator point with just an anchor position and an id
|
||||
pub fn new_anchor_with_id(anchor: DVec2, id: PointId) -> Self {
|
||||
Self::new_with_id(anchor, Some(anchor), Some(anchor), id)
|
||||
}
|
||||
|
||||
/// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group.
|
||||
pub fn to_bezier(&self, end_group: &ManipulatorGroup<PointId>) -> PathSeg {
|
||||
let start = self.anchor;
|
||||
let end = end_group.anchor;
|
||||
let out_handle = self.out_handle;
|
||||
let in_handle = end_group.in_handle;
|
||||
|
||||
match (out_handle, in_handle) {
|
||||
(Some(handle1), Some(handle2)) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle1), dvec2_to_point(handle2), dvec2_to_point(end))),
|
||||
(Some(handle), None) | (None, Some(handle)) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(handle), dvec2_to_point(end))),
|
||||
(None, None) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a transformation to all of the [ManipulatorGroup] points
|
||||
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
|
||||
self.anchor = affine_transform.transform_point2(self.anchor);
|
||||
self.in_handle = self.in_handle.map(|in_handle| affine_transform.transform_point2(in_handle));
|
||||
self.out_handle = self.out_handle.map(|out_handle| affine_transform.transform_point2(out_handle));
|
||||
}
|
||||
|
||||
/// Are all handles at finite positions
|
||||
pub fn is_finite(&self) -> bool {
|
||||
self.anchor.is_finite() && self.in_handle.is_none_or(|handle| handle.is_finite()) && self.out_handle.is_none_or(|handle| handle.is_finite())
|
||||
}
|
||||
|
||||
/// Reverse directions of handles
|
||||
pub fn flip(mut self) -> Self {
|
||||
std::mem::swap(&mut self.in_handle, &mut self.out_handle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_in_handle(&self) -> bool {
|
||||
self.in_handle.map(|handle| Self::has_handle(self.anchor, handle)).unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn has_out_handle(&self) -> bool {
|
||||
self.out_handle.map(|handle| Self::has_handle(self.anchor, handle)).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn has_handle(anchor: DVec2, handle: DVec2) -> bool {
|
||||
!((handle.x - anchor.x).abs() < f64::EPSILON && (handle.y - anchor.y).abs() < f64::EPSILON)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum AppendType {
|
||||
IgnoreStart,
|
||||
SmoothJoin(f64),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ArcType {
|
||||
Open,
|
||||
Closed,
|
||||
PieSlice,
|
||||
}
|
||||
|
||||
/// Representation of the handle point(s) in a bezier segment.
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum BezierHandles {
|
||||
Linear,
|
||||
/// Handles for a quadratic curve.
|
||||
Quadratic {
|
||||
/// Point representing the location of the single handle.
|
||||
handle: DVec2,
|
||||
},
|
||||
/// Handles for a cubic curve.
|
||||
Cubic {
|
||||
/// Point representing the location of the handle associated to the start point.
|
||||
handle_start: DVec2,
|
||||
/// Point representing the location of the handle associated to the end point.
|
||||
handle_end: DVec2,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::hash::Hash for BezierHandles {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
BezierHandles::Linear => {}
|
||||
BezierHandles::Quadratic { handle } => handle.to_array().map(|v| v.to_bits()).hash(state),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end].map(|handle| handle.to_array().map(|v| v.to_bits())).hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BezierHandles {
|
||||
pub fn is_cubic(&self) -> bool {
|
||||
matches!(self, Self::Cubic { .. })
|
||||
}
|
||||
|
||||
pub fn is_finite(&self) -> bool {
|
||||
match self {
|
||||
BezierHandles::Linear => true,
|
||||
BezierHandles::Quadratic { handle } => handle.is_finite(),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => handle_start.is_finite() && handle_end.is_finite(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn start(&self) -> Option<DVec2> {
|
||||
match *self {
|
||||
BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } => Some(handle_start),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.
|
||||
pub fn end(&self) -> Option<DVec2> {
|
||||
match *self {
|
||||
BezierHandles::Cubic { handle_end, .. } => Some(handle_end),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_start(&mut self, delta: DVec2) {
|
||||
if let BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } = self {
|
||||
*handle_start += delta
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_end(&mut self, delta: DVec2) {
|
||||
if let BezierHandles::Cubic { handle_end, .. } = self {
|
||||
*handle_end += delta
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each handle point in the Bezier.
|
||||
#[must_use]
|
||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Self {
|
||||
match *self {
|
||||
BezierHandles::Linear => Self::Linear,
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let handle = transformation_function(handle);
|
||||
Self::Quadratic { handle }
|
||||
}
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let handle_start = transformation_function(handle_start);
|
||||
let handle_end = transformation_function(handle_end);
|
||||
Self::Cubic { handle_start, handle_end }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn reversed(self) -> Self {
|
||||
match self {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => Self::Cubic {
|
||||
handle_start: handle_end,
|
||||
handle_end: handle_start,
|
||||
},
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a bezier curve with 2D points.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Bezier {
|
||||
/// Start point of the bezier curve.
|
||||
pub start: DVec2,
|
||||
/// End point of the bezier curve.
|
||||
pub end: DVec2,
|
||||
/// Handles of the bezier curve.
|
||||
pub handles: BezierHandles,
|
||||
}
|
||||
|
||||
impl Debug for Bezier {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let mut debug_struct = f.debug_struct("Bezier");
|
||||
let mut debug_struct_ref = debug_struct.field("start", &self.start);
|
||||
debug_struct_ref = match self.handles {
|
||||
BezierHandles::Linear => debug_struct_ref,
|
||||
BezierHandles::Quadratic { handle } => debug_struct_ref.field("handle", &handle),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => debug_struct_ref.field("handle_start", &handle_start).field("handle_end", &handle_end),
|
||||
};
|
||||
debug_struct_ref.field("end", &self.end).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionality for the getters and setters of the various points in a Bezier
|
||||
impl Bezier {
|
||||
/// Set the coordinates of the start point.
|
||||
pub fn set_start(&mut self, s: DVec2) {
|
||||
self.start = s;
|
||||
}
|
||||
|
||||
/// Set the coordinates of the end point.
|
||||
pub fn set_end(&mut self, e: DVec2) {
|
||||
self.end = e;
|
||||
}
|
||||
|
||||
/// Set the coordinates of the first handle point. This represents the only handle in a quadratic segment. If used on a linear segment, it will be changed to a quadratic.
|
||||
pub fn set_handle_start(&mut self, h1: DVec2) {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => {
|
||||
self.handles = BezierHandles::Quadratic { handle: h1 };
|
||||
}
|
||||
BezierHandles::Quadratic { ref mut handle } => {
|
||||
*handle = h1;
|
||||
}
|
||||
BezierHandles::Cubic { ref mut handle_start, .. } => {
|
||||
*handle_start = h1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the coordinates of the second handle point. This will convert both linear and quadratic segments into cubic ones. For a linear segment, the first handle will be set to the start point.
|
||||
pub fn set_handle_end(&mut self, h2: DVec2) {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => {
|
||||
self.handles = BezierHandles::Cubic {
|
||||
handle_start: self.start,
|
||||
handle_end: h2,
|
||||
};
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
self.handles = BezierHandles::Cubic { handle_start: handle, handle_end: h2 };
|
||||
}
|
||||
BezierHandles::Cubic { ref mut handle_end, .. } => {
|
||||
*handle_end = h2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's start point.
|
||||
pub fn start(&self) -> DVec2 {
|
||||
self.start
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's end point.
|
||||
pub fn end(&self) -> DVec2 {
|
||||
self.end
|
||||
}
|
||||
|
||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn handle_start(&self) -> Option<DVec2> {
|
||||
self.handles.start()
|
||||
}
|
||||
|
||||
/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.
|
||||
pub fn handle_end(&self) -> Option<DVec2> {
|
||||
self.handles.end()
|
||||
}
|
||||
|
||||
/// Get an iterator over the coordinates of all points in a vector.
|
||||
/// - For a linear segment, the order of the points will be: `start`, `end`.
|
||||
/// - For a quadratic segment, the order of the points will be: `start`, `handle`, `end`.
|
||||
/// - For a cubic segment, the order of the points will be: `start`, `handle_start`, `handle_end`, `end`.
|
||||
pub fn get_points(&self) -> impl Iterator<Item = DVec2> + use<> {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => [self.start, self.end, DVec2::ZERO, DVec2::ZERO].into_iter().take(2),
|
||||
BezierHandles::Quadratic { handle } => [self.start, handle, self.end, DVec2::ZERO].into_iter().take(3),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [self.start, handle_start, handle_end, self.end].into_iter().take(4),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider removing this function
|
||||
/// Create a linear bezier using the provided coordinates as the start and end points.
|
||||
pub fn from_linear_coordinates(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
|
||||
Bezier {
|
||||
start: DVec2::new(x1, y1),
|
||||
handles: BezierHandles::Linear,
|
||||
end: DVec2::new(x2, y2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a linear bezier using the provided DVec2s as the start and end points.
|
||||
pub fn from_linear_dvec2(p1: DVec2, p2: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Linear,
|
||||
end: p2,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider removing this function
|
||||
/// Create a quadratic bezier using the provided coordinates as the start, handle, and end points.
|
||||
pub fn from_quadratic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
|
||||
Bezier {
|
||||
start: DVec2::new(x1, y1),
|
||||
handles: BezierHandles::Quadratic { handle: DVec2::new(x2, y2) },
|
||||
end: DVec2::new(x3, y3),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a quadratic bezier using the provided DVec2s as the start, handle, and end points.
|
||||
pub fn from_quadratic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Quadratic { handle: p2 },
|
||||
end: p3,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider removing this function
|
||||
/// Create a cubic bezier using the provided coordinates as the start, handles, and end points.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn from_cubic_coordinates(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64, x4: f64, y4: f64) -> Self {
|
||||
Bezier {
|
||||
start: DVec2::new(x1, y1),
|
||||
handles: BezierHandles::Cubic {
|
||||
handle_start: DVec2::new(x2, y2),
|
||||
handle_end: DVec2::new(x3, y3),
|
||||
},
|
||||
end: DVec2::new(x4, y4),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a cubic bezier using the provided DVec2s as the start, handles, and end points.
|
||||
pub fn from_cubic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2, p4: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Cubic { handle_start: p2, handle_end: p3 },
|
||||
end: p4,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier.
|
||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Bezier {
|
||||
Self {
|
||||
start: transformation_function(self.start),
|
||||
end: transformation_function(self.end),
|
||||
handles: self.handles.apply_transformation(transformation_function),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersections(&self, other: &Bezier, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<f64> {
|
||||
let this = handles_to_segment(self.start, self.handles, self.end);
|
||||
let other = handles_to_segment(other.start, other.handles, other.end);
|
||||
filtered_segment_intersections(this, other, accuracy, minimum_separation)
|
||||
}
|
||||
|
||||
pub fn winding(&self, point: DVec2) -> i32 {
|
||||
let this = handles_to_segment(self.start, self.handles, self.end);
|
||||
this.winding(dvec2_to_point(point))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
use super::structs::Identifier;
|
||||
use super::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
|
||||
impl<PointId: Identifier> Subpath<PointId> {
|
||||
/// Returns [ManipulatorGroup]s with a reversed winding order.
|
||||
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>]) -> Vec<ManipulatorGroup<PointId>> {
|
||||
manipulator_groups
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|group| ManipulatorGroup {
|
||||
anchor: group.anchor,
|
||||
in_handle: group.out_handle,
|
||||
out_handle: group.in_handle,
|
||||
id: PointId::new(),
|
||||
})
|
||||
.collect::<Vec<ManipulatorGroup<PointId>>>()
|
||||
}
|
||||
|
||||
/// Returns a [Subpath] with a reversed winding order.
|
||||
/// Note that a reversed closed subpath will start on the same manipulator group and simply wind the other direction
|
||||
pub fn reverse(&self) -> Subpath<PointId> {
|
||||
let mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());
|
||||
if self.closed {
|
||||
reversed.rotate_right(1);
|
||||
};
|
||||
Subpath {
|
||||
manipulator_groups: reversed,
|
||||
closed: self.closed,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a transformation to all of the [ManipulatorGroup]s in the [Subpath].
|
||||
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
|
||||
for manipulator_group in &mut self.manipulator_groups {
|
||||
manipulator_group.apply_transform(affine_transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians).
|
||||
pub fn rotate(&self, angle: f64) -> Subpath<PointId> {
|
||||
let mut rotated_subpath = self.clone();
|
||||
|
||||
let affine_transform: DAffine2 = DAffine2::from_angle(angle);
|
||||
rotated_subpath.apply_transform(affine_transform);
|
||||
|
||||
rotated_subpath
|
||||
}
|
||||
|
||||
/// Returns a subpath that results from rotating this subpath around the provided point by the given angle (in radians).
|
||||
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<PointId> {
|
||||
// Translate before and after the rotation to account for the pivot
|
||||
let translate: DAffine2 = DAffine2::from_translation(pivot);
|
||||
let rotate: DAffine2 = DAffine2::from_angle(angle);
|
||||
let translate_inverse = translate.inverse();
|
||||
|
||||
let mut rotated_subpath = self.clone();
|
||||
rotated_subpath.apply_transform(translate * rotate * translate_inverse);
|
||||
rotated_subpath
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use super::TextAlign;
|
||||
use crate::subpath::{ManipulatorGroup, Subpath};
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::vector::{PointId, Vector};
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use core::cell::RefCell;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use parley::fontique::Blob;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use super::intersection::bezpath_intersections;
|
||||
use super::poisson_disk::poisson_disk_sample;
|
||||
use super::util::segment_tangent;
|
||||
use super::util::pathseg_tangent;
|
||||
use crate::math::polynomial::pathseg_to_parametric_polynomial;
|
||||
use crate::vector::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::vector::misc::{PointSpacingType, dvec2_to_point, point_to_dvec2};
|
||||
use glam::{DMat2, DVec2};
|
||||
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape};
|
||||
use kurbo::common::{solve_cubic, solve_quadratic};
|
||||
use kurbo::{BezPath, CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, QuadBez, Rect, Shape, Vec2};
|
||||
use std::f64::consts::{FRAC_PI_2, PI};
|
||||
|
||||
/// Splits the [`BezPath`] at segment index at `t` value which lie in the range of [0, 1].
|
||||
|
|
@ -187,6 +189,82 @@ pub enum TValue {
|
|||
Euclidean(f64),
|
||||
}
|
||||
|
||||
/// Default LUT step size in `compute_lookup_table` function.
|
||||
pub const DEFAULT_LUT_STEP_SIZE: usize = 10;
|
||||
|
||||
/// Return a selection of equidistant points on the bezier curve.
|
||||
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
||||
pub fn pathseg_compute_lookup_table(segment: PathSeg, steps: Option<usize>, eucliean: bool) -> impl Iterator<Item = DVec2> {
|
||||
let steps = steps.unwrap_or(DEFAULT_LUT_STEP_SIZE);
|
||||
|
||||
(0..=steps).map(move |t| {
|
||||
let tvalue = if eucliean {
|
||||
TValue::Euclidean(t as f64 / steps as f64)
|
||||
} else {
|
||||
TValue::Parametric(t as f64 / steps as f64)
|
||||
};
|
||||
let t = eval_pathseg(segment, tvalue);
|
||||
point_to_dvec2(segment.eval(t))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an `Iterator` containing all possible parametric `t`-values at the given `x`-coordinate.
|
||||
pub fn pathseg_find_tvalues_for_x(segment: PathSeg, x: f64) -> impl Iterator<Item = f64> + use<> {
|
||||
match segment {
|
||||
PathSeg::Line(Line { p0, p1 }) => {
|
||||
// If the transformed linear bezier is on the x-axis, `a` and `b` will both be zero and `solve_linear` will return no roots
|
||||
let a = p1.x - p0.x;
|
||||
let b = p0.x - x;
|
||||
|
||||
// Find the roots of the linear equation `ax + b`.
|
||||
// There exist roots when `a` is not 0
|
||||
if a.abs() > MAX_ABSOLUTE_DIFFERENCE { [Some(-b / a), None, None] } else { [None; 3] }
|
||||
}
|
||||
PathSeg::Quad(QuadBez { p0, p1, p2 }) => {
|
||||
let a = p2.x - 2.0 * p1.x + p0.x;
|
||||
let b = 2.0 * (p1.x - p0.x);
|
||||
let c = p0.x - x;
|
||||
let r = solve_quadratic(c, b, a);
|
||||
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), None]
|
||||
}
|
||||
PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => {
|
||||
let a = p3.x - 3.0 * p2.x + 3.0 * p1.x - p0.x;
|
||||
let b = 3.0 * (p2.x - 2.0 * p1.x + p0.x);
|
||||
let c = 3.0 * (p1.x - p0.x);
|
||||
let d = p0.x - x;
|
||||
let r = solve_cubic(d, c, b, a);
|
||||
[r.get(0).map(|t| *t), r.get(1).map(|t| *t), r.get(2).map(|t| *t)]
|
||||
}
|
||||
}
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|&t| (0.0..1.).contains(&t))
|
||||
}
|
||||
|
||||
/// Find the `t`-value(s) such that the normal(s) at `t` pass through the specified point.
|
||||
pub fn pathseg_normals_to_point(segment: PathSeg, point: Point) -> Vec<f64> {
|
||||
// We solve deriv(t) dot (self(t) - point) = 0.
|
||||
let (mut x, mut y) = pathseg_to_parametric_polynomial(segment);
|
||||
let x = x.coefficients_mut();
|
||||
let y = y.coefficients_mut();
|
||||
x[0] -= point.x;
|
||||
y[0] -= point.y;
|
||||
let poly = poly_cool::Poly::new([
|
||||
x[0] * x[1] + y[0] * y[1],
|
||||
x[1] * x[1] + y[1] * y[1] + 2. * (x[0] * x[2] + y[0] * y[2]),
|
||||
3. * (x[2] * x[1] + y[2] * y[1]) + 3. * (x[0] * x[3] + y[0] * y[3]),
|
||||
4. * (x[3] * x[1] + y[3] * y[1]) + 2. * (x[2] * x[2] + y[2] * y[2]),
|
||||
5. * (x[3] * x[2] + y[3] * y[2]),
|
||||
3. * (x[3] * x[3] + y[3] * y[3]),
|
||||
]);
|
||||
poly.roots_between(0., 1., 1e-8)
|
||||
}
|
||||
|
||||
/// Find the `t`-value(s) such that the tangent(s) at `t` pass through the given point.
|
||||
pub fn pathseg_tangents_to_point(segment: PathSeg, point: Point) -> Vec<f64> {
|
||||
segment.to_cubic().tangents_to_point(point).to_vec()
|
||||
}
|
||||
|
||||
/// Return the subsegment for the given [TValue] range. Returns None if parametric value of `t1` is greater than `t2`.
|
||||
pub fn trim_pathseg(segment: PathSeg, t1: TValue, t2: TValue) -> Option<PathSeg> {
|
||||
let t1 = eval_pathseg(segment, t1);
|
||||
|
|
@ -202,6 +280,68 @@ pub fn eval_pathseg(segment: PathSeg, t_value: TValue) -> f64 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return an approximation of the length centroid, together with the length, of the bezier curve.
|
||||
///
|
||||
/// The length centroid is the center of mass for the arc length of the Bezier segment.
|
||||
/// An infinitely thin wire forming the Bezier segment's shape would balance at this point.
|
||||
///
|
||||
/// - `accuracy` is used to approximate the curve.
|
||||
pub(crate) fn pathseg_length_centroid_and_length(segment: PathSeg, accuracy: Option<f64>) -> (Vec2, f64) {
|
||||
match segment {
|
||||
PathSeg::Line(line) => ((line.start().to_vec2() + line.end().to_vec2()) / 2., (line.start().to_vec2() - line.end().to_vec2()).length()),
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let QuadBez { p0, p1, p2 } = quad_bez;
|
||||
// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles
|
||||
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
|
||||
let lower = (a2 - a1).length();
|
||||
let upper = (a1 - a0).length() + (a2 - a1).length();
|
||||
if upper - lower <= 2. * accuracy || level >= 8 {
|
||||
let length = (lower + upper) / 2.;
|
||||
return (length, length * (a0 + a1 + a2) / 3.);
|
||||
}
|
||||
|
||||
let b1 = 0.5 * (a0 + a1);
|
||||
let c1 = 0.5 * (a1 + a2);
|
||||
let b2 = 0.5 * (b1 + c1);
|
||||
|
||||
let (length1, centroid_part1) = recurse(a0, b1, b2, 0.5 * accuracy, level + 1);
|
||||
let (length2, centroid_part2) = recurse(b2, c1, a2, 0.5 * accuracy, level + 1);
|
||||
(length1 + length2, centroid_part1 + centroid_part2)
|
||||
}
|
||||
|
||||
let (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), accuracy.unwrap_or_default(), 0);
|
||||
(centroid_parts / length, length)
|
||||
}
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let CubicBez { p0, p1, p2, p3 } = cubic_bez;
|
||||
|
||||
// Use Casteljau subdivision, noting that the length is more than the straight line distance from start to end but less than the straight line distance through the handles
|
||||
fn recurse(a0: Vec2, a1: Vec2, a2: Vec2, a3: Vec2, accuracy: f64, level: u8) -> (f64, Vec2) {
|
||||
let lower = (a3 - a0).length();
|
||||
let upper = (a1 - a0).length() + (a2 - a1).length() + (a3 - a2).length();
|
||||
if upper - lower <= 2. * accuracy || level >= 8 {
|
||||
let length = (lower + upper) / 2.;
|
||||
return (length, length * (a0 + a1 + a2 + a3) / 4.);
|
||||
}
|
||||
|
||||
let b1 = 0.5 * (a0 + a1);
|
||||
let t0 = 0.5 * (a1 + a2);
|
||||
let c1 = 0.5 * (a2 + a3);
|
||||
let b2 = 0.5 * (b1 + t0);
|
||||
let c2 = 0.5 * (t0 + c1);
|
||||
let b3 = 0.5 * (b2 + c2);
|
||||
|
||||
let (length1, centroid_part1) = recurse(a0, b1, b2, b3, 0.5 * accuracy, level + 1);
|
||||
let (length2, centroid_part2) = recurse(b3, c2, c1, a3, 0.5 * accuracy, level + 1);
|
||||
(length1 + length2, centroid_part1 + centroid_part2)
|
||||
}
|
||||
|
||||
let (length, centroid_parts) = recurse(p0.to_vec2(), p1.to_vec2(), p2.to_vec2(), p3.to_vec2(), accuracy.unwrap_or_default(), 0);
|
||||
(centroid_parts / length, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the t value of point on the given path segment i.e fractional distance along the segment's total length.
|
||||
/// It uses a binary search to find the value `t` such that the ratio `length_up_to_t / total_length` approximates the input `distance`.
|
||||
pub fn eval_pathseg_euclidean(segment: PathSeg, distance: f64, accuracy: f64) -> f64 {
|
||||
|
|
@ -392,8 +532,8 @@ pub fn miter_line_join(bezpath1: &BezPath, bezpath2: &BezPath, miter_limit: Opti
|
|||
let in_segment = bezpath1.segments().last()?;
|
||||
let out_segment = bezpath2.segments().next()?;
|
||||
|
||||
let in_tangent = segment_tangent(in_segment, 1.);
|
||||
let out_tangent = segment_tangent(out_segment, 0.);
|
||||
let in_tangent = pathseg_tangent(in_segment, 1.);
|
||||
let out_tangent = pathseg_tangent(out_segment, 0.);
|
||||
|
||||
if in_tangent == DVec2::ZERO || out_tangent == DVec2::ZERO {
|
||||
// Avoid panic from normalizing zero vectors
|
||||
|
|
@ -454,7 +594,7 @@ pub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) ->
|
|||
let center_to_left = left - center;
|
||||
|
||||
let in_segment = bezpath1.segments().last();
|
||||
let in_tangent = in_segment.map(|in_segment| segment_tangent(in_segment, 1.));
|
||||
let in_tangent = in_segment.map(|in_segment| pathseg_tangent(in_segment, 1.));
|
||||
|
||||
let mut angle = center_to_right.angle_to(center_to_left) / 2.;
|
||||
let mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right);
|
||||
|
|
|
|||
|
|
@ -105,8 +105,8 @@ mod test {
|
|||
use super::*;
|
||||
use crate::Node;
|
||||
use crate::extract_xy::{ExtractXyNode, XY};
|
||||
use crate::subpath::Subpath;
|
||||
use crate::vector::Vector;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use std::pin::Pin;
|
||||
|
||||
|
|
|
|||
|
|
@ -45,12 +45,26 @@ pub fn segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Opt
|
|||
}
|
||||
}
|
||||
|
||||
pub fn subsegment_intersections(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let accuracy = accuracy.unwrap_or(DEFAULT_ACCURACY);
|
||||
|
||||
match (segment1, segment2) {
|
||||
(PathSeg::Line(line), segment2) => segment2.intersect_line(line).iter().map(|i| (i.line_t, i.segment_t)).collect(),
|
||||
(segment1, PathSeg::Line(line)) => segment1.intersect_line(line).iter().map(|i| (i.segment_t, i.line_t)).collect(),
|
||||
(segment1, segment2) => {
|
||||
let mut intersections = Vec::new();
|
||||
segment_intersections_inner(segment1, min_t1, max_t1, segment2, min_t2, max_t2, accuracy, &mut intersections);
|
||||
intersections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [https://pomax.github.io/bezierinfo/#curveintersection] to find intersection between two Bezier segments
|
||||
/// by splitting the segment recursively until the size of the subsegment's bounding box is smaller than the accuracy.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: f64, intersections: &mut Vec<(f64, f64)>) {
|
||||
let bbox1 = segment1.bounding_box();
|
||||
let bbox2 = segment2.bounding_box();
|
||||
let bbox1 = segment1.subsegment(min_t1..max_t1).bounding_box();
|
||||
let bbox2 = segment2.subsegment(min_t2..max_t2).bounding_box();
|
||||
|
||||
let mid_t1 = (min_t1 + max_t1) / 2.;
|
||||
let mid_t2 = (min_t2 + max_t2) / 2.;
|
||||
|
|
@ -58,7 +72,7 @@ fn segment_intersections_inner(segment1: PathSeg, min_t1: f64, max_t1: f64, segm
|
|||
// Check if the bounding boxes overlap
|
||||
if bbox1.overlaps(bbox2) {
|
||||
// If bounding boxes overlap and they are small enough, we have found an intersection
|
||||
if bbox1.width() < accuracy && bbox1.height() < accuracy && bbox2.width() < accuracy && bbox2.height() < accuracy {
|
||||
if bbox1.width().abs() < accuracy && bbox1.height().abs() < accuracy && bbox2.width().abs() < accuracy && bbox2.height().abs() < accuracy {
|
||||
// Use the middle `t` value, append the corresponding `t` value
|
||||
intersections.push((mid_t1, mid_t2));
|
||||
return;
|
||||
|
|
@ -125,6 +139,66 @@ pub fn filtered_all_segment_intersections(segment1: PathSeg, segment2: PathSeg,
|
|||
})
|
||||
}
|
||||
|
||||
/// Helper function to compute intersections between lists of subcurves.
|
||||
/// This function uses the algorithm implemented in `intersections_between_subcurves`.
|
||||
fn intersections_between_vectors_of_path_segments(subcurves1: &[(f64, f64, PathSeg)], subcurves2: &[(f64, f64, PathSeg)], accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let segment_pairs = subcurves1.iter().flat_map(move |(t11, t12, curve1)| {
|
||||
subcurves2
|
||||
.iter()
|
||||
.filter_map(move |(t21, t22, curve2)| curve1.bounding_box().overlaps(curve2.bounding_box()).then_some((t11, t12, curve1, t21, t22, curve2)))
|
||||
});
|
||||
|
||||
segment_pairs
|
||||
.flat_map(|(&t11, &t12, &curve1, &t21, &t22, &curve2)| subsegment_intersections(curve1, t11, t12, curve2, t21, t22, accuracy))
|
||||
.collect::<Vec<(f64, f64)>>()
|
||||
}
|
||||
|
||||
fn pathseg_self_intersection(segment: PathSeg, accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let cubic_bez = match segment {
|
||||
PathSeg::Line(_) | PathSeg::Quad(_) => return vec![],
|
||||
PathSeg::Cubic(cubic_bez) => cubic_bez,
|
||||
};
|
||||
|
||||
// Get 2 copies of the reduced curves
|
||||
let quads1 = cubic_bez.to_quads(DEFAULT_ACCURACY).map(|(t1, t2, quad_bez)| (t1, t2, PathSeg::Quad(quad_bez))).collect::<Vec<_>>();
|
||||
let quads2 = quads1.clone();
|
||||
|
||||
let num_curves = quads1.len();
|
||||
|
||||
// Adjacent reduced curves cannot intersect
|
||||
if num_curves <= 2 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// For each curve, look for intersections with every curve that is at least 2 indices away
|
||||
quads1
|
||||
.iter()
|
||||
.take(num_curves - 2)
|
||||
.enumerate()
|
||||
.flat_map(|(index, &subsegment)| intersections_between_vectors_of_path_segments(&[subsegment], &quads2[index + 2..], accuracy))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
||||
/// If the difference between 2 adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.
|
||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||
/// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order
|
||||
pub fn pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let mut intersection_t_values = pathseg_self_intersection(segment, accuracy);
|
||||
intersection_t_values.sort_by(|a, b| (a.0 + a.1).partial_cmp(&(b.0 + b.1)).unwrap());
|
||||
|
||||
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
|
||||
if !accumulator.is_empty()
|
||||
&& (accumulator.last().unwrap().0 - t.0).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
|
||||
&& (accumulator.last().unwrap().1 - t.1).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
|
||||
{
|
||||
accumulator.pop();
|
||||
}
|
||||
accumulator.push(*t);
|
||||
accumulator
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{bezpath_and_segment_intersections, filtered_segment_intersections};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use glam::DVec2;
|
||||
use kurbo::{ParamCurve, ParamCurveDeriv, PathSeg};
|
||||
|
||||
pub fn segment_tangent(segment: PathSeg, t: f64) -> DVec2 {
|
||||
pub fn pathseg_tangent(segment: PathSeg, t: f64) -> DVec2 {
|
||||
// NOTE: .deriv() method gives inaccurate result when it is 1.
|
||||
let t = if t == 1. { 1. - f64::EPSILON } else { t };
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
use super::algorithms::intersection::filtered_segment_intersections;
|
||||
use super::misc::dvec2_to_point;
|
||||
use crate::math::math_ext::QuadExt;
|
||||
use crate::math::quad::Quad;
|
||||
use crate::subpath::Subpath;
|
||||
use crate::vector::PointId;
|
||||
use bezier_rs::Subpath;
|
||||
use crate::vector::misc::point_to_dvec2;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use kurbo::{Affine, ParamCurve, PathSeg, Point, Shape};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FreePoint {
|
||||
|
|
@ -99,7 +103,7 @@ impl ClickTarget {
|
|||
}
|
||||
|
||||
/// Does the click target intersect the path
|
||||
pub fn intersect_path<It: Iterator<Item = bezier_rs::Bezier>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
|
||||
pub fn intersect_path<It: Iterator<Item = PathSeg>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
|
||||
// Check if the matrix is not invertible
|
||||
let mut layer_transform = layer_transform;
|
||||
if layer_transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
|
|
@ -107,25 +111,25 @@ impl ClickTarget {
|
|||
}
|
||||
|
||||
let inverse = layer_transform.inverse();
|
||||
let mut bezier_iter = || bezier_iter().map(|bezier| bezier.apply_transformation(|point| inverse.transform_point2(point)));
|
||||
let mut bezier_iter = || bezier_iter().map(|bezier| Affine::new(inverse.to_cols_array()) * bezier);
|
||||
|
||||
match self.target_type() {
|
||||
ClickTargetType::Subpath(subpath) => {
|
||||
// Check if outlines intersect
|
||||
let outline_intersects = |path_segment: bezier_rs::Bezier| bezier_iter().any(|line| !path_segment.intersections(&line, None, None).is_empty());
|
||||
let outline_intersects = |path_segment: PathSeg| bezier_iter().any(|line| !filtered_segment_intersections(path_segment, line, None, None).is_empty());
|
||||
if subpath.iter().any(outline_intersects) {
|
||||
return true;
|
||||
}
|
||||
// Check if selection is entirely within the shape
|
||||
if subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(bezier.start)) {
|
||||
if subpath.closed() && bezier_iter().next().is_some_and(|bezier| subpath.contains_point(point_to_dvec2(bezier.start()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if shape is entirely within selection
|
||||
let any_point_from_subpath = subpath.manipulator_groups().first().map(|manipulators| manipulators.anchor);
|
||||
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(shape_point)).sum::<i32>() != 0)
|
||||
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(Point::new(shape_point.x, shape_point.y))).sum::<i32>() != 0)
|
||||
}
|
||||
ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: bezier_rs::Bezier| bezier.winding(point.position)).sum::<i32>() != 0,
|
||||
ClickTargetType::FreePoint(point) => bezier_iter().map(|bezier: PathSeg| bezier.winding(dvec2_to_point(point.position))).sum::<i32>() != 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +148,7 @@ impl ClickTarget {
|
|||
// Allows for selecting lines
|
||||
// TODO: actual intersection of stroke
|
||||
let inflated_quad = Quad::from_box(target_bounds);
|
||||
self.intersect_path(|| inflated_quad.bezier_lines(), layer_transform)
|
||||
self.intersect_path(|| inflated_quad.to_lines(), layer_transform)
|
||||
}
|
||||
|
||||
/// Does the click target intersect the point (not accounting for stroke size)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use super::misc::{ArcType, AsU64, GridType};
|
|||
use super::{PointId, SegmentId, StrokeId};
|
||||
use crate::Ctx;
|
||||
use crate::registry::types::{Angle, PixelSize};
|
||||
use crate::subpath;
|
||||
use crate::table::Table;
|
||||
use crate::vector::Vector;
|
||||
use crate::vector::misc::HandleId;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
|
||||
trait CornerRadius {
|
||||
|
|
@ -14,7 +14,7 @@ trait CornerRadius {
|
|||
impl CornerRadius for f64 {
|
||||
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
|
||||
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
|
||||
}
|
||||
}
|
||||
impl CornerRadius for [f64; 4] {
|
||||
|
|
@ -34,7 +34,7 @@ impl CornerRadius for [f64; 4] {
|
|||
} else {
|
||||
self
|
||||
};
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ fn circle(
|
|||
radius: f64,
|
||||
) -> Table<Vector> {
|
||||
let radius = radius.abs();
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
|
|
@ -63,14 +63,14 @@ fn arc(
|
|||
sweep_angle: Angle,
|
||||
arc_type: ArcType,
|
||||
) -> Table<Vector> {
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_arc(
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc(
|
||||
radius,
|
||||
start_angle / 360. * std::f64::consts::TAU,
|
||||
sweep_angle / 360. * std::f64::consts::TAU,
|
||||
match arc_type {
|
||||
ArcType::Open => bezier_rs::ArcType::Open,
|
||||
ArcType::Closed => bezier_rs::ArcType::Closed,
|
||||
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
|
||||
ArcType::Open => subpath::ArcType::Open,
|
||||
ArcType::Closed => subpath::ArcType::Closed,
|
||||
ArcType::PieSlice => subpath::ArcType::PieSlice,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ fn ellipse(
|
|||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
|
||||
let mut ellipse = Vector::from_subpath(Subpath::new_ellipse(corner1, corner2));
|
||||
let mut ellipse = Vector::from_subpath(subpath::Subpath::new_ellipse(corner1, corner2));
|
||||
|
||||
let len = ellipse.segment_domain.ids().len();
|
||||
for i in 0..len {
|
||||
|
|
@ -133,7 +133,7 @@ fn regular_polygon<T: AsU64>(
|
|||
) -> Table<Vector> {
|
||||
let points = sides.as_u64();
|
||||
let radius: f64 = radius * 2.;
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
|
|
@ -155,12 +155,12 @@ fn star<T: AsU64>(
|
|||
let diameter: f64 = radius_1 * 2.;
|
||||
let inner_diameter = radius_2 * 2.;
|
||||
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<Vector> {
|
||||
Table::new_from_element(Vector::from_subpath(Subpath::new_line(start, end)))
|
||||
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
trait GridSpacing {
|
||||
|
|
@ -212,7 +212,7 @@ fn grid<T: GridSpacing>(
|
|||
if let Some(other_index) = to_index {
|
||||
vector
|
||||
.segment_domain
|
||||
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -249,7 +249,7 @@ fn grid<T: GridSpacing>(
|
|||
if let Some(other_index) = to_index {
|
||||
vector
|
||||
.segment_domain
|
||||
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
.push(segment_id.next_id(), other_index, current_index, subpath::BezierHandles::Linear, StrokeId::ZERO);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ mod tests {
|
|||
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
assert!(
|
||||
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
|
||||
"Length of {} should be 10",
|
||||
|
|
@ -304,7 +304,7 @@ mod tests {
|
|||
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert_eq!(bezier.handles, subpath::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
|
||||
assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {}", angle)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::PointId;
|
||||
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup};
|
||||
use crate::vector::{SegmentId, Vector};
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
|
||||
|
|
@ -115,18 +115,18 @@ pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles {
|
|||
|
||||
pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg {
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => {
|
||||
BezierHandles::Linear => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(end);
|
||||
PathSeg::Line(Line::new(p0, p1))
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(handle);
|
||||
let p2 = dvec2_to_point(end);
|
||||
PathSeg::Quad(QuadBez::new(p0, p1, p2))
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p0 = dvec2_to_point(start);
|
||||
let p1 = dvec2_to_point(handle_start);
|
||||
let p2 = dvec2_to_point(handle_end);
|
||||
|
|
@ -211,8 +211,8 @@ pub fn is_linear(segment: PathSeg) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get an iterator over the coordinates of all points in a path segment.
|
||||
pub fn get_segment_points(segment: PathSeg) -> Vec<Point> {
|
||||
/// Get an vec of all the points in a path segment.
|
||||
pub fn pathseg_points_vec(segment: PathSeg) -> Vec<Point> {
|
||||
match segment {
|
||||
PathSeg::Line(line) => [line.p0, line.p1].to_vec(),
|
||||
PathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(),
|
||||
|
|
@ -225,8 +225,8 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
|
|||
let seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 };
|
||||
let seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 };
|
||||
|
||||
let seg1_points = get_segment_points(seg1);
|
||||
let seg2_points = get_segment_points(seg2);
|
||||
let seg1_points = pathseg_points_vec(seg1);
|
||||
let seg2_points = pathseg_points_vec(seg2);
|
||||
|
||||
let cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ mod vector_modification;
|
|||
mod vector_nodes;
|
||||
mod vector_types;
|
||||
|
||||
pub use bezier_rs;
|
||||
pub use reference_point::*;
|
||||
pub use style::PathStyle;
|
||||
pub use vector_nodes::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::subpath::{Bezier, BezierHandles, Identifier, ManipulatorGroup, Subpath};
|
||||
use crate::vector::misc::{HandleId, dvec2_to_point};
|
||||
use crate::vector::vector_types::Vector;
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{CubicBez, Line, PathSeg, QuadBez};
|
||||
|
|
@ -442,11 +442,7 @@ impl SegmentDomain {
|
|||
zip(ids, zip(start_point, zip(end_point, handles))).map(|(id, (start_point, (end_point, handles)))| (id, start_point, end_point, handles))
|
||||
}
|
||||
|
||||
pub(crate) fn pair_handles_and_points_mut_by_index(
|
||||
&mut self,
|
||||
index1: usize,
|
||||
index2: usize,
|
||||
) -> (&mut bezier_rs::BezierHandles, &mut usize, &mut usize, &mut bezier_rs::BezierHandles, &mut usize, &mut usize) {
|
||||
pub(crate) fn pair_handles_and_points_mut_by_index(&mut self, index1: usize, index2: usize) -> (&mut BezierHandles, &mut usize, &mut usize, &mut BezierHandles, &mut usize, &mut usize) {
|
||||
// Use split_at_mut to avoid multiple mutable borrows of the same slice
|
||||
let (handles_first, handles_second) = self.handles.split_at_mut(index2.max(index1));
|
||||
let (start_first, start_second) = self.start_point.split_at_mut(index2.max(index1));
|
||||
|
|
@ -686,25 +682,25 @@ impl Vector {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
|
||||
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> bezier_rs::Bezier {
|
||||
/// Construct a [`Bezier`] curve spanning from the resolved position of the start and end points with the specified handles.
|
||||
fn segment_to_bezier_with_index(&self, start: usize, end: usize, handles: BezierHandles) -> Bezier {
|
||||
let start = self.point_domain.positions()[start];
|
||||
let end = self.point_domain.positions()[end];
|
||||
bezier_rs::Bezier { start, end, handles }
|
||||
Bezier { start, end, handles }
|
||||
}
|
||||
|
||||
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
|
||||
/// Tries to convert a segment with the specified id to a [`Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_from_id(&self, id: SegmentId) -> Option<Bezier> {
|
||||
self.segment_points_from_id(id).map(|(_, _, bezier)| bezier)
|
||||
}
|
||||
|
||||
/// Tries to convert a segment with the specified id to the start and end points and a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, bezier_rs::Bezier)> {
|
||||
/// Tries to convert a segment with the specified id to the start and end points and a [`Bezier`], returning None if the id is invalid.
|
||||
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, Bezier)> {
|
||||
Some(self.segment_points_from_index(self.segment_domain.id_to_index(id)?))
|
||||
}
|
||||
|
||||
/// Tries to convert a segment with the specified index to the start and end points and a [`bezier_rs::Bezier`].
|
||||
pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, bezier_rs::Bezier) {
|
||||
/// Tries to convert a segment with the specified index to the start and end points and a [`Bezier`].
|
||||
pub fn segment_points_from_index(&self, index: usize) -> (PointId, PointId, Bezier) {
|
||||
let start = self.segment_domain.start_point[index];
|
||||
let end = self.segment_domain.end_point[index];
|
||||
let start_id = self.point_domain.ids()[start];
|
||||
|
|
@ -712,7 +708,7 @@ impl Vector {
|
|||
(start_id, end_id, self.segment_to_bezier_with_index(start, end, self.segment_domain.handles[index]))
|
||||
}
|
||||
|
||||
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
pub fn segment_iter(&self) -> impl Iterator<Item = (SegmentId, PathSeg, PointId, PointId)> {
|
||||
let to_segment = |(((&handles, &id), &start), &end)| (id, self.path_segment_from_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);
|
||||
|
||||
|
|
@ -725,8 +721,8 @@ impl Vector {
|
|||
.map(to_segment)
|
||||
}
|
||||
|
||||
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, bezier_rs::Bezier, PointId, PointId)> + '_ {
|
||||
/// Iterator over all of the [`Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||
pub fn segment_bezier_iter(&self) -> impl Iterator<Item = (SegmentId, Bezier, PointId, PointId)> + '_ {
|
||||
let to_bezier = |(((&handles, &id), &start), &end)| (id, self.segment_to_bezier_with_index(start, end, handles), self.point_domain.ids()[start], self.point_domain.ids()[end]);
|
||||
self.segment_domain
|
||||
.handles
|
||||
|
|
@ -808,8 +804,8 @@ impl Vector {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.
|
||||
pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<bezier_rs::Subpath<PointId>> {
|
||||
/// Construct a [`Bezier`] curve from an iterator of segments with (handles, start point, end point) independently of discontinuities.
|
||||
pub fn subpath_from_segments_ignore_discontinuities(&self, segments: impl Iterator<Item = (BezierHandles, usize, usize)>) -> Option<Subpath<PointId>> {
|
||||
let mut first_point = None;
|
||||
let mut manipulators_list = Vec::new();
|
||||
let mut last: Option<(usize, BezierHandles)> = None;
|
||||
|
|
@ -842,10 +838,10 @@ impl Vector {
|
|||
}
|
||||
}
|
||||
|
||||
Some(bezier_rs::Subpath::new(manipulators_list, closed))
|
||||
Some(Subpath::new(manipulators_list, closed))
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions.
|
||||
/// Construct a [`Bezier`] curve for each region, skipping invalid regions.
|
||||
pub fn region_manipulator_groups(&self) -> impl Iterator<Item = (RegionId, Vec<ManipulatorGroup<PointId>>)> + '_ {
|
||||
self.region_domain
|
||||
.id
|
||||
|
|
@ -903,12 +899,12 @@ impl Vector {
|
|||
}
|
||||
}
|
||||
|
||||
/// Construct a [`bezier_rs::Bezier`] curve for stroke.
|
||||
pub fn stroke_bezier_paths(&self) -> impl Iterator<Item = bezier_rs::Subpath<PointId>> {
|
||||
self.build_stroke_path_iter().map(|(manipulators_list, closed)| bezier_rs::Subpath::new(manipulators_list, closed))
|
||||
/// Construct a [`Bezier`] curve for stroke.
|
||||
pub fn stroke_bezier_paths(&self) -> impl Iterator<Item = Subpath<PointId>> {
|
||||
self.build_stroke_path_iter().map(|(manipulators_list, closed)| Subpath::new(manipulators_list, closed))
|
||||
}
|
||||
|
||||
/// Construct and return an iterator of Vec of `(bezier_rs::ManipulatorGroup<PointId>], bool)` for stroke.
|
||||
/// Construct and return an iterator of Vec of `(ManipulatorGroup<PointId>], bool)` for stroke.
|
||||
/// The boolean in the tuple indicates if the path is closed.
|
||||
pub fn stroke_manipulator_groups(&self) -> impl Iterator<Item = (Vec<ManipulatorGroup<PointId>>, bool)> {
|
||||
self.build_stroke_path_iter()
|
||||
|
|
@ -1094,7 +1090,7 @@ impl Iterator for StrokePathIter<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl bezier_rs::Identifier for PointId {
|
||||
impl Identifier for PointId {
|
||||
fn new() -> Self {
|
||||
Self::generate()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use super::*;
|
||||
use crate::Ctx;
|
||||
use crate::subpath::BezierHandles;
|
||||
use crate::table::{Table, TableRow};
|
||||
use crate::uuid::{NodeId, generate_uuid};
|
||||
use crate::vector::misc::{HandleId, HandleType, point_to_dvec2};
|
||||
use bezier_rs::BezierHandles;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, PathEl, Point};
|
||||
|
|
@ -684,14 +684,15 @@ impl HandleExt for HandleId {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kurbo::{PathSeg, QuadBez};
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::subpath::{Bezier, Subpath};
|
||||
|
||||
#[test]
|
||||
fn modify_new() {
|
||||
let vector = Vector::from_subpaths(
|
||||
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
|
||||
false,
|
||||
);
|
||||
let vector = Vector::from_subpaths([Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)], false);
|
||||
|
||||
let modify = VectorModification::create_from_vector(&vector);
|
||||
|
||||
|
|
@ -702,14 +703,13 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn modify_existing() {
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
let subpaths = [
|
||||
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE),
|
||||
Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO),
|
||||
Subpath::from_beziers(
|
||||
&[
|
||||
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(10., 0.)),
|
||||
Bezier::from_quadratic_dvec2(DVec2::new(10., 0.), DVec2::new(15., 10.), DVec2::new(20., 0.)),
|
||||
PathSeg::Quad(QuadBez::new(Point::new(0., 0.), Point::new(5., 10.), Point::new(10., 0.))),
|
||||
PathSeg::Quad(QuadBez::new(Point::new(10., 0.), Point::new(15., 10.), Point::new(20., 0.))),
|
||||
],
|
||||
false,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use super::{PointId, SegmentDomain, SegmentId, StrokeId, Vector, VectorExt};
|
|||
use crate::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use crate::raster_types::{CPU, GPU, Raster};
|
||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup};
|
||||
use crate::table::{Table, TableRow, TableRowMut};
|
||||
use crate::transform::{Footprint, ReferencePoint, Transform};
|
||||
use crate::vector::PointDomain;
|
||||
|
|
@ -17,7 +18,6 @@ use crate::vector::misc::{handles_to_segment, segment_to_handles};
|
|||
use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use crate::vector::{FillId, RegionId};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use core::f64::consts::PI;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
|
@ -827,11 +827,11 @@ async fn points_to_polyline(_: impl Ctx, mut points: Table<Vector>, #[default(tr
|
|||
|
||||
if points_count > 2 {
|
||||
(0..points_count - 1).for_each(|i| {
|
||||
segment_domain.push(SegmentId::generate(), i, i + 1, bezier_rs::BezierHandles::Linear, StrokeId::generate());
|
||||
segment_domain.push(SegmentId::generate(), i, i + 1, BezierHandles::Linear, StrokeId::generate());
|
||||
});
|
||||
|
||||
if closed {
|
||||
segment_domain.push(SegmentId::generate(), points_count - 1, 0, bezier_rs::BezierHandles::Linear, StrokeId::generate());
|
||||
segment_domain.push(SegmentId::generate(), points_count - 1, 0, BezierHandles::Linear, StrokeId::generate());
|
||||
|
||||
row.element
|
||||
.region_domain
|
||||
|
|
@ -1351,7 +1351,7 @@ async fn spline(_: impl Ctx, content: Table<Vector>) -> Table<Vector> {
|
|||
|
||||
let handle_start = first_handles[i];
|
||||
let handle_end = positions[next_index] * 2. - first_handles[next_index];
|
||||
let handles = bezier_rs::BezierHandles::Cubic { handle_start, handle_end };
|
||||
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
||||
|
||||
segment_domain.push(SegmentId::generate(), start_index, end_index, handles, stroke_id);
|
||||
}
|
||||
|
|
@ -1405,14 +1405,14 @@ async fn jitter_points(
|
|||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
*handle_start += start_delta;
|
||||
*handle_end += end_delta;
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
*handle = row.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
|
||||
}
|
||||
bezier_rs::BezierHandles::Linear => {}
|
||||
BezierHandles::Linear => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1846,7 +1846,7 @@ fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Ve
|
|||
let mut next_id = vector.segment_domain.next_id();
|
||||
|
||||
for &[start, end] in new_segments {
|
||||
let handles = bezier_rs::BezierHandles::Linear;
|
||||
let handles = BezierHandles::Linear;
|
||||
vector.segment_domain.push(next_id.next_id(), start, end, handles, StrokeId::ZERO);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ pub use super::vector_attributes::*;
|
|||
pub use super::vector_modification::*;
|
||||
use crate::bounds::{BoundingBox, RenderBoundingBox};
|
||||
use crate::math::quad::Quad;
|
||||
use crate::subpath::{BezierHandles, ManipulatorGroup, Subpath};
|
||||
use crate::table::Table;
|
||||
use crate::transform::Transform;
|
||||
use crate::vector::click_target::{ClickTargetType, FreePoint};
|
||||
use crate::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use crate::{AlphaBlending, Color, Graphic};
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup};
|
||||
use core::borrow::Borrow;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
|
@ -60,15 +60,15 @@ impl std::hash::Hash for Vector {
|
|||
|
||||
impl Vector {
|
||||
/// Add a Bezier-rs subpath to this path.
|
||||
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
|
||||
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
|
||||
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
|
||||
let subpath: &Subpath<PointId> = subpath.borrow();
|
||||
let stroke_id = StrokeId::ZERO;
|
||||
let mut point_id = self.point_domain.next_id();
|
||||
|
||||
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
|
||||
(None, None) => bezier_rs::BezierHandles::Linear,
|
||||
(Some(handle), None) | (None, Some(handle)) => bezier_rs::BezierHandles::Quadratic { handle },
|
||||
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
||||
(None, None) => BezierHandles::Linear,
|
||||
(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle },
|
||||
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
let [mut first_seg, mut last_seg] = [None, None];
|
||||
let mut segment_id = self.segment_domain.next_id();
|
||||
|
|
@ -132,7 +132,7 @@ impl Vector {
|
|||
}
|
||||
|
||||
/// Construct some new vector path from a single Bezier-rs subpath with an identity transform and black fill.
|
||||
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
|
||||
pub fn from_subpath(subpath: impl Borrow<Subpath<PointId>>) -> Self {
|
||||
Self::from_subpaths([subpath], false)
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +144,7 @@ impl Vector {
|
|||
}
|
||||
|
||||
/// Construct some new vector path from Bezier-rs subpaths with an identity transform and black fill.
|
||||
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
|
||||
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<Subpath<PointId>>>, preserve_id: bool) -> Self {
|
||||
let mut vector = Self::default();
|
||||
|
||||
for subpath in subpaths.into_iter() {
|
||||
|
|
@ -185,7 +185,7 @@ impl Vector {
|
|||
|
||||
for (start, end) in segments_to_add {
|
||||
let segment_id = self.segment_domain.next_id().next_id();
|
||||
self.segment_domain.push(segment_id, start, end, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
|
||||
self.segment_domain.push(segment_id, start, end, BezierHandles::Linear, StrokeId::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,14 +244,19 @@ impl Vector {
|
|||
self.segment_domain.end_point().iter().map(|&index| self.point_domain.ids()[index])
|
||||
}
|
||||
|
||||
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
|
||||
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: (Option<DVec2>, Option<DVec2>), stroke: StrokeId) {
|
||||
let [Some(start), Some(end)] = [start, end].map(|id| self.point_domain.resolve_id(id)) else {
|
||||
return;
|
||||
};
|
||||
let handles = match handles {
|
||||
(None, None) => BezierHandles::Linear,
|
||||
(None, Some(handle)) | (Some(handle), None) => BezierHandles::Quadratic { handle },
|
||||
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
self.segment_domain.push(id, start, end, handles, stroke)
|
||||
}
|
||||
|
||||
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, PointId, PointId)> {
|
||||
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut BezierHandles, PointId, PointId)> {
|
||||
self.segment_domain
|
||||
.handles_mut()
|
||||
.map(|(id, handles, start, end)| (id, handles, self.point_domain.ids()[start], self.point_domain.ids()[end]))
|
||||
|
|
@ -515,9 +520,11 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use kurbo::{CubicBez, PathSeg, Point};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
|
||||
fn assert_subpath_eq(generated: &[Subpath<PointId>], expected: &[Subpath<PointId>]) {
|
||||
assert_eq!(generated.len(), expected.len());
|
||||
for (generated, expected) in generated.iter().zip(expected) {
|
||||
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
|
||||
|
|
@ -532,10 +539,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn construct_closed_subpath() {
|
||||
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let vector = Vector::from_subpath(&circle);
|
||||
assert_eq!(vector.point_domain.ids().len(), 4);
|
||||
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths.len(), 4);
|
||||
assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));
|
||||
|
||||
|
|
@ -545,11 +552,11 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn construct_open_subpath() {
|
||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||
let subpath = bezier_rs::Subpath::from_bezier(&bezier);
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
|
||||
let subpath = Subpath::from_bezier(bezier);
|
||||
let vector = Vector::from_subpath(&subpath);
|
||||
assert_eq!(vector.point_domain.ids().len(), 2);
|
||||
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths, vec![bezier]);
|
||||
|
||||
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
|
||||
|
|
@ -558,14 +565,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn construct_many_subpath() {
|
||||
let curve = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||
let curve = bezier_rs::Subpath::from_bezier(&curve);
|
||||
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let curve = PathSeg::Cubic(CubicBez::new(Point::ZERO, Point::new(-1., -1.), Point::new(1., 1.), Point::new(1., 0.)));
|
||||
let curve = Subpath::from_bezier(curve);
|
||||
let circle = Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
|
||||
let vector = Vector::from_subpaths([&curve, &circle], false);
|
||||
assert_eq!(vector.point_domain.ids().len(), 6);
|
||||
|
||||
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
let bezier_paths = vector.segment_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths.len(), 5);
|
||||
assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ license = "MIT OR Apache-2.0"
|
|||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
|
|
@ -17,3 +16,4 @@ specta = { workspace = true }
|
|||
log = { workspace = true }
|
||||
path-bool = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::subpath::{ManipulatorGroup, PathSegPoints, Subpath, pathseg_points};
|
||||
use graphene_core::table::{Table, TableRow, TableRowRef};
|
||||
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||
use graphene_core::vector::style::Fill;
|
||||
|
|
@ -330,20 +330,29 @@ fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &Subpath<Po
|
|||
use path_bool::PathSegment;
|
||||
let mut global_start = None;
|
||||
let mut global_end = DVec2::ZERO;
|
||||
|
||||
for bezier in subpath.iter() {
|
||||
const EPS: f64 = 1e-8;
|
||||
let transformed = bezier.apply_transformation(|pos| transform.transform_point2(pos).mul(EPS.recip()).round().mul(EPS));
|
||||
let start = transformed.start;
|
||||
let end = transformed.end;
|
||||
let transform_point = |pos: DVec2| transform.transform_point2(pos).mul(EPS.recip()).round().mul(EPS);
|
||||
|
||||
let PathSegPoints { p0, p1, p2, p3 } = pathseg_points(bezier);
|
||||
|
||||
let p0 = transform_point(p0);
|
||||
let p1 = p1.map(|p1| transform_point(p1));
|
||||
let p2 = p2.map(|p2| transform_point(p2));
|
||||
let p3 = transform_point(p3);
|
||||
|
||||
if global_start.is_none() {
|
||||
global_start = Some(start);
|
||||
global_start = Some(p0);
|
||||
}
|
||||
global_end = end;
|
||||
let segment = match transformed.handles {
|
||||
bezier_rs::BezierHandles::Linear => PathSegment::Line(start, end),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => PathSegment::Quadratic(start, handle, end),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => PathSegment::Cubic(start, handle_start, handle_end, end),
|
||||
global_end = p3;
|
||||
|
||||
let segment = match (p1, p2) {
|
||||
(None, None) => PathSegment::Line(p0, p3),
|
||||
(None, Some(p2)) | (Some(p2), None) => PathSegment::Quadratic(p0, p2, p3),
|
||||
(Some(p1), Some(p2)) => PathSegment::Cubic(p0, p1, p2, p3),
|
||||
};
|
||||
|
||||
path.push(segment);
|
||||
}
|
||||
if let Some(start) = global_start {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ graphene-raster-nodes = { workspace = true }
|
|||
# Workspace dependencies
|
||||
log = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
url = { workspace = true }
|
||||
|
|
@ -39,10 +38,7 @@ serde_json = { workspace = true, optional = true }
|
|||
|
||||
# Workspace dependencies
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
web-sys = { workspace = true, features = [
|
||||
"Navigator",
|
||||
"Gpu",
|
||||
] }
|
||||
web-sys = { workspace = true, features = ["Navigator", "Gpu"] }
|
||||
js-sys = { workspace = true }
|
||||
wasm-bindgen = { workspace = true }
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ std = [
|
|||
"dep:dyn-any",
|
||||
"dep:image",
|
||||
"dep:ndarray",
|
||||
"dep:bezier-rs",
|
||||
"dep:rand",
|
||||
"dep:rand_chacha",
|
||||
"dep:fastnoise-lite",
|
||||
"dep:serde",
|
||||
"dep:specta",
|
||||
"dep:glam"
|
||||
"dep:glam",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
|
@ -40,11 +39,11 @@ glam = { workspace = true, optional = true }
|
|||
specta = { workspace = true, optional = true }
|
||||
image = { workspace = true, optional = true }
|
||||
ndarray = { workspace = true, optional = true }
|
||||
bezier-rs = { workspace = true, optional = true }
|
||||
rand = { workspace = true, optional = true }
|
||||
rand_chacha = { workspace = true, optional = true }
|
||||
fastnoise-lite = { workspace = true, optional = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
kurbo = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
//! requires bezier-rs
|
||||
|
||||
use crate::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use graphene_core::color::{Channel, Linear};
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::vector::algorithms::bezpath_algorithms::pathseg_find_tvalues_for_x;
|
||||
use kurbo::{CubicBez, ParamCurve, PathSeg, Point};
|
||||
|
||||
const WINDOW_SIZE: usize = 1024;
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ fn generate_curves<C: Channel + Linear>(_: impl Ctx, curve: Curve, #[implementat
|
|||
for sample in curve.manipulator_groups.iter().chain(std::iter::once(&end)) {
|
||||
let [x0, y0, x1, y1, x2, y2, x3, y3] = [pos[0], pos[1], param[0], param[1], sample.handles[0][0], sample.handles[0][1], sample.anchor[0], sample.anchor[1]].map(f64::from);
|
||||
|
||||
let bezier = Bezier::from_cubic_coordinates(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
let segment = PathSeg::Cubic(CubicBez::new(Point::new(x0, y0), Point::new(x1, y1), Point::new(x2, y2), Point::new(x3, y3)));
|
||||
|
||||
let [left, right] = [pos[0], sample.anchor[0]].map(|c| c.clamp(0., 1.));
|
||||
let lut_index_left: usize = (left * (lut.len() - 1) as f32).floor() as _;
|
||||
|
|
@ -30,9 +31,9 @@ fn generate_curves<C: Channel + Linear>(_: impl Ctx, curve: Curve, #[implementat
|
|||
} else if x >= x3 {
|
||||
y3
|
||||
} else {
|
||||
bezier.find_tvalues_for_x(x)
|
||||
pathseg_find_tvalues_for_x(segment, x)
|
||||
.next()
|
||||
.map(|t| bezier.evaluate(TValue::Parametric(t.clamp(0., 1.))).y)
|
||||
.map(|t| segment.eval(t.clamp(0., 1.)).y)
|
||||
// Fall back to a very bad approximation if Bezier-rs fails
|
||||
.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,14 +6,10 @@ description = "graphene svg renderer"
|
|||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
vello = ["dep:vello", "bezier-rs/kurbo"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
|
|
@ -22,6 +18,7 @@ base64 = { workspace = true }
|
|||
log = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
usvg = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
vello = { workspace = true, optional = true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::DVec2;
|
||||
use graphene_core::subpath::{ManipulatorGroup, Subpath};
|
||||
use graphene_core::vector::PointId;
|
||||
|
||||
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use crate::render_ext::RenderExt;
|
||||
use crate::to_peniko::BlendModeExt;
|
||||
use bezier_rs::Subpath;
|
||||
use dyn_any::DynAny;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::blending::BlendMode;
|
||||
|
|
@ -14,6 +13,7 @@ use graphene_core::raster::BitmapMut;
|
|||
use graphene_core::raster::Image;
|
||||
use graphene_core::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_core::render_complexity::RenderComplexity;
|
||||
use graphene_core::subpath::Subpath;
|
||||
use graphene_core::table::{Table, TableRow};
|
||||
use graphene_core::transform::{Footprint, Transform};
|
||||
use graphene_core::uuid::{NodeId, generate_uuid};
|
||||
|
|
@ -21,6 +21,7 @@ use graphene_core::vector::Vector;
|
|||
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
|
||||
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
|
||||
use graphene_core::{Artboard, Graphic};
|
||||
use kurbo::Affine;
|
||||
use num_traits::Zero;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
|
|
@ -678,8 +679,10 @@ impl Render for Table<Vector> {
|
|||
let transformed_bounds_matrix = element_transform * DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
|
||||
|
||||
let mut path = String::new();
|
||||
for subpath in row.element.stroke_bezier_paths() {
|
||||
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
|
||||
|
||||
for mut bezpath in row.element.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
path.push_str(bezpath.to_svg().as_str());
|
||||
}
|
||||
|
||||
let mask_type = if vector.style.stroke().map(|x| x.align) == Some(StrokeAlign::Inside) {
|
||||
|
|
@ -780,8 +783,11 @@ impl Render for Table<Vector> {
|
|||
|
||||
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
|
||||
let mut path = kurbo::BezPath::new();
|
||||
for subpath in row.element.stroke_bezier_paths() {
|
||||
subpath.to_vello_path(applied_stroke_transform, &mut path);
|
||||
for mut bezpath in row.element.stroke_bezpath_iter() {
|
||||
bezpath.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
for element in bezpath {
|
||||
path.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're using opacity or a blend mode, we need to push a layer
|
||||
|
|
|
|||
|
|
@ -45,4 +45,6 @@ demangle-name-section = false
|
|||
dwarf-debug-info = false
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
|
||||
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||
'cfg(wasm_bindgen_unstable_test_coverage)',
|
||||
] }
|
||||
|
|
|
|||
Loading…
Reference in New Issue