Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,13 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
use graphene_std::math::math_ext::QuadExt;
use graphene_std::vector::misc::subpath_to_kurbo_bezpath;
use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath;
use graphene_std::*;
use kurbo::{Line, Point};
use kurbo::{DEFAULT_ACCURACY, Shape};
use renderer::Quad;
use std::cmp::Ordering;

Expand Down Expand Up @@ -958,8 +957,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
to_connector_is_layer,
GraphWireStyle::Direct,
);
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
let path_string = vector_wire.to_svg();
let wire_path = WirePath {
path_string,
data_type: self.wire_in_progress_type,
Expand Down Expand Up @@ -1196,7 +1194,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
.filter(|input| input.1.as_value().is_some())
.map(|input| input.0);
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
let Some(node_bbox) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
log::error!("Could not get bounding box for node: {selected_node_id}");
return;
};
Expand All @@ -1220,31 +1218,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;

let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
let node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);
let inside = bezpath_is_inside_bezpath(&wire, &node_bbox, None, None);

let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0);
let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0);
let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1);
let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1);
let ps = [p1, p2, p3, p4];

let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None);

let wire = subpath_to_kurbo_bezpath(wire);

let intersect = wire.segments().any(|segment| {
let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);

let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0));
let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1));
let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1));
let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1));

!segment.intersect_line(top_line).is_empty()
|| !segment.intersect_line(bottom_line).is_empty()
|| !segment.intersect_line(left_line).is_empty()
|| !segment.intersect_line(right_line).is_empty()
});
let intersect = wire
.segments()
.any(|segment| node_bbox.segments().filter_map(|segment| segment.as_line()).any(|line| !segment.intersect_line(line).is_empty()));

(intersect || inside).then_some((input, is_stack))
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, Vector, VectorModificationType};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY;
use kurbo::BezPath;
use serde_json::{Value, json};
use std::collections::{HashMap, HashSet, VecDeque};
use std::hash::{DefaultHasher, Hash, Hasher};
Expand Down Expand Up @@ -2713,8 +2714,7 @@ impl NodeNetworkInterface {
let thick = vertical_end && vertical_start;
let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style);

let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
let path_string = vector_wire.to_svg();
let data_type = FrontendGraphDataType::from_type(&self.input_type(&input, network_path).0);
let wire_path_update = Some(WirePath {
path_string,
Expand All @@ -2731,14 +2731,14 @@ impl NodeNetworkInterface {
}

/// Returns the vector subpath and a boolean of whether the wire should be thick.
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath<PointId>, bool)> {
pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(BezPath, bool)> {
let Some(input_position) = self.get_input_center(input, network_path) else {
log::error!("Could not get dom rect for wire end: {:?}", input);
return None;
};
// An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector
let Some(upstream_output) = self.upstream_output_connector(input, network_path) else {
return Some((Subpath::from_anchors(std::iter::empty(), false), false));
return Some((BezPath::new(), false));
};
let Some(output_position) = self.get_output_center(&upstream_output, network_path) else {
log::error!("Could not get dom rect for wire start: {:?}", upstream_output);
Expand All @@ -2752,8 +2752,7 @@ impl NodeNetworkInterface {

pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option<WirePath> {
let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?;
let mut path_string = String::new();
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
let path_string = vector_wire.to_svg();
let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0);
Some(WirePath {
path_string,
Expand Down
108 changes: 25 additions & 83 deletions editor/src/messages/portfolio/document/utility_types/wires.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use bezier_rs::{ManipulatorGroup, Subpath};
use glam::{DVec2, IVec2};
use graphene_std::uuid::NodeId;
use graphene_std::vector::PointId;
use graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};

#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct WirePath {
Expand Down Expand Up @@ -53,7 +52,7 @@ impl GraphWireStyle {
}
}

pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> BezPath {
let grid_spacing = 24.;
match graph_wire_style {
GraphWireStyle::Direct => {
Expand Down Expand Up @@ -85,44 +84,21 @@ pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);

Subpath::new(
vec![
ManipulatorGroup {
anchor: locations[0],
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[1],
in_handle: None,
out_handle: Some(locations[1] + delta01),
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[2],
in_handle: Some(locations[2] - delta23),
out_handle: None,
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[3],
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
],
false,
)
let mut wire = BezPath::new();
wire.move_to(dvec2_to_point(locations[0]));
wire.line_to(dvec2_to_point(locations[1]));
wire.curve_to(dvec2_to_point(locations[1] + delta01), dvec2_to_point(locations[2] - delta23), dvec2_to_point(locations[2]));
wire.line_to(dvec2_to_point(locations[3]));
wire
}
GraphWireStyle::GridAligned => {
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
straight_wire_subpath(locations)
let locations = straight_wire_path(output_position, input_position, vertical_out, vertical_in);
straight_wire_to_bezpath(locations)
}
}
}

fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
fn straight_wire_path(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
let grid_spacing = 24;
let line_width = 2;

Expand Down Expand Up @@ -446,40 +422,24 @@ fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_o
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
}

fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
fn straight_wire_to_bezpath(locations: Vec<IVec2>) -> BezPath {
if locations.is_empty() {
return Subpath::new(Vec::new(), false);
return BezPath::new();
}

let to_point = |location: IVec2| Point::new(location.x as f64, location.y as f64);

if locations.len() == 2 {
return Subpath::new(
vec![
ManipulatorGroup {
anchor: locations[0].into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
ManipulatorGroup {
anchor: locations[1].into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
},
],
false,
);
let p1 = to_point(locations[0]);
let p2 = to_point(locations[1]);
Line::new(p1, p2).to_path(DEFAULT_ACCURACY);
}

let corner_radius = 10;

// Create path with rounded corners
let mut path = vec![ManipulatorGroup {
anchor: locations[0].into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
}];
let mut path = BezPath::new();
path.move_to(to_point(locations[0]));

for i in 1..(locations.len() - 1) {
let prev = locations[i - 1];
Expand Down Expand Up @@ -563,27 +523,9 @@ fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
},
);

path.extend(vec![
ManipulatorGroup {
anchor: corner_start.into(),
in_handle: None,
out_handle: Some(corner_start_mid.into()),
id: PointId::generate(),
},
ManipulatorGroup {
anchor: corner_end.into(),
in_handle: Some(corner_end_mid.into()),
out_handle: None,
id: PointId::generate(),
},
])
path.line_to(to_point(corner_start));
path.curve_to(to_point(corner_start_mid), to_point(corner_end_mid), to_point(corner_end));
}

path.push(ManipulatorGroup {
anchor: (*locations.last().unwrap()).into(),
in_handle: None,
out_handle: None,
id: PointId::generate(),
});
Subpath::new(path, false)
path.line_to(to_point(*locations.last().unwrap()));
path
}
67 changes: 67 additions & 0 deletions node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,3 +466,70 @@ pub fn round_line_join(bezpath1: &BezPath, bezpath2: &BezPath, center: DVec2) ->

compute_circular_subpath_details(left, arc_point, right, center, Some(angle))
}

/// Returns `true` if the `bezpath1` is completely inside the `bezpath2`.
pub fn bezpath_is_inside_bezpath(bezpath1: &BezPath, bezpath2: &BezPath, accuracy: Option<f64>, minimum_separation: Option<f64>) -> bool {
// Eliminate any possibility of one being inside the other, if either of them is empty
if bezpath1.is_empty() || bezpath2.is_empty() {
return false;
}

let inner_bbox = bezpath1.bounding_box();
let outer_bbox = bezpath2.bounding_box();

// Eliminate bezpath1 if its bounding box is not completely inside the bezpath2's bounding box.
// Reasoning:
// If the inner bezpath bounding box is larger than the outer bezpath bounding box in any direction
// then the inner bezpath is intersecting with or outside the outer bezpath.
if !outer_bbox.contains_rect(inner_bbox) {
return false;
}

// Eliminate bezpath1 if any of its anchor points are outside the bezpath2.
if !bezpath1.elements().iter().filter_map(|el| el.end_point()).all(|point| bezpath2.contains(point)) {
return false;
}

// Eliminate this subpath if it intersects with the other subpath.
if !bezpath_intersections(bezpath1, bezpath2, accuracy, minimum_separation).is_empty() {
return false;
}

// At this point:
// (1) This subpath's bounding box is inside the other subpath's bounding box,
// (2) Its anchors are inside the other subpath, and
// (3) It is not intersecting with the other subpath.
// Hence, this subpath is completely inside the given other subpath.
true
}

#[cfg(test)]
mod tests {
// TODO: add more intersection tests

use super::bezpath_is_inside_bezpath;
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Rect, Shape};

#[test]
fn is_inside_subpath() {
let boundary_polygon = Rect::new(100., 100., 500., 500.).to_path(DEFAULT_ACCURACY);

let mut curve_intersection = BezPath::new();
curve_intersection.move_to(Point::new(189., 289.));
curve_intersection.quad_to(Point::new(9., 286.), Point::new(45., 410.));
assert!(!bezpath_is_inside_bezpath(&curve_intersection, &boundary_polygon, None, None));

let mut curve_outside = BezPath::new();
curve_outside.move_to(Point::new(115., 37.));
curve_outside.quad_to(Point::new(51.4, 91.8), Point::new(76.5, 242.));
assert!(!bezpath_is_inside_bezpath(&curve_outside, &boundary_polygon, None, None));

let mut curve_inside = BezPath::new();
curve_inside.move_to(Point::new(210.1, 133.5));
curve_inside.curve_to(Point::new(150.2, 436.9), Point::new(436., 285.), Point::new(247.6, 240.7));
assert!(bezpath_is_inside_bezpath(&curve_inside, &boundary_polygon, None, None));

let line_inside = Line::new(Point::new(101., 101.5), Point::new(150.2, 499.)).to_path(DEFAULT_ACCURACY);
assert!(bezpath_is_inside_bezpath(&line_inside, &boundary_polygon, None, None));
}
}
8 changes: 1 addition & 7 deletions node-graph/gcore/src/vector/misc.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::PointId;
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
use crate::vector::{SegmentId, Vector};
use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath};
use bezier_rs::{BezierHandles, ManipulatorGroup};
use dyn_any::DynAny;
use glam::DVec2;
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
Expand Down Expand Up @@ -136,12 +136,6 @@ pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> P
}
}

pub fn subpath_to_kurbo_bezpath(subpath: Subpath<PointId>) -> BezPath {
let manipulator_groups = subpath.manipulator_groups();
let closed = subpath.closed();
bezpath_from_manipulator_groups(manipulator_groups, closed)
}

pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>], closed: bool) -> BezPath {
let mut bezpath = kurbo::BezPath::new();
let mut out_handle;
Expand Down
Loading