mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 03:38:11 +08:00
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:
co-authored by
Keavon Chambers
parent
99984fc2d6
commit
d22b2ca927
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user