Implement initial infrastructure for the Spline tool to join itself with other splines and paths (#2269)

* merge 2 spline layers

* merge path layers with spline_tool

* merge path and spline path with spline_tool

* handle line layers

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Sidharth-Singh10 2025-02-13 16:13:00 +05:30 committed by GitHub
parent 26fa8d967e
commit e44c460cf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 555 additions and 11 deletions

View File

@ -1,12 +1,14 @@
use super::tool_prelude::*;
use crate::consts::{DEFAULT_STROKE_WIDTH, DRAG_THRESHOLD, PATH_JOIN_THRESHOLD, SNAP_POINT_TOLERANCE};
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_document_node_type};
use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector};
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::graph_modification_utils::{self};
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapData, SnapManager, SnapTypeConfiguration, SnappedPoint};
use crate::messages::tool::common_functionality::utility_functions::{closest_point, should_extend};
@ -195,6 +197,7 @@ struct SplineToolData {
extend: bool,
weight: f64,
layer: Option<LayerNodeIdentifier>,
starting_layer: Option<LayerNodeIdentifier>,
snap_manager: SnapManager,
auto_panning: AutoPanning,
}
@ -206,6 +209,7 @@ impl SplineToolData {
self.preview_segment = None;
self.extend = false;
self.points = Vec::new();
self.starting_layer = None;
}
/// Get the snapped point while ignoring current layer
@ -249,6 +253,19 @@ impl Fsm for SplineToolFsmState {
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, SnapTypeConfiguration::default());
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
// Check if we're starting from an endpoint of any layer, even if not extending
let closest_endpoint = closest_point(
document,
viewport,
PATH_JOIN_THRESHOLD,
LayerNodeIdentifier::ROOT_PARENT.descendants(document.metadata()),
|_| false, // Don't exclude any points
preferences,
);
if let Some((start_layer, _, _)) = closest_endpoint {
tool_data.starting_layer = Some(start_layer);
}
// Extend an endpoint of the selected path
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
if let Some((layer, point, position)) = should_extend(document, viewport, SNAP_POINT_TOLERANCE, selected_nodes.selected_layers(document.metadata()), preferences) {
@ -405,29 +422,133 @@ impl Fsm for SplineToolFsmState {
/// Return `true` only if new segment is inserted to connect two end points in the selected layer otherwise `false`.
fn join_path(document: &DocumentMessageHandler, mouse_pos: DVec2, tool_data: &mut SplineToolData, preferences: &PreferencesMessageHandler, responses: &mut VecDeque<Message>) -> bool {
let Some(&(endpoint, _)) = tool_data.points.last() else { return false };
let Some(&(start_point, _)) = tool_data.points.first() else { return false };
let Some(starting_layer) = tool_data.starting_layer else { return false };
let Some(current_layer) = tool_data.layer else { return false };
let preview_point = tool_data.preview_point;
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
let selected_layers = selected_nodes.selected_layers(document.metadata());
// Get the closest point to mouse position which is not preview_point or end_point.
let closest_point = closest_point(
document,
mouse_pos,
PATH_JOIN_THRESHOLD,
selected_layers,
LayerNodeIdentifier::ROOT_PARENT.descendants(document.metadata()),
|cp| preview_point.is_some_and(|pp| pp == cp) || cp == endpoint,
preferences,
);
let Some((layer, join_point, _)) = closest_point else { return false };
let Some((other_layer, join_point, _)) = closest_point else { return false };
// Last end point inserted was the preview point and segment therefore we delete it before joining the end_point & join_point.
delete_preview(tool_data, responses);
let points = [endpoint, join_point];
let id = SegmentId::generate();
let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// If the points are in different layers, merge them first
if current_layer == other_layer {
// If points are in the same layer, just connect them
let points = [endpoint, join_point];
let id = SegmentId::generate();
let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] };
responses.add(GraphOperationMessage::Vector {
layer: current_layer,
modification_type,
});
return true;
}
match (is_layer_spline(document, starting_layer), is_layer_spline(document, other_layer)) {
(true, true) => {
merge_two_spline_layer(document, current_layer, other_layer, responses);
let points = [endpoint, join_point];
let id = SegmentId::generate();
let modification_type = VectorModificationType::InsertSegment { id, points, handles: [None, None] };
responses.add(GraphOperationMessage::Vector {
layer: current_layer,
modification_type,
});
}
(false, false) => {
let Some(current_vector_data) = document.network_interface.compute_modified_vector(current_layer) else {
log::error!("Could not get vector data for current layer");
return false;
};
let Some(starting_vector_data) = document.network_interface.compute_modified_vector(starting_layer) else {
log::error!("Could not get vector data for other layer");
return false;
};
let Some(starting_layer_endpoint) = starting_vector_data.end_point().last() else {
log::error!("Could not get endpoint");
return false;
};
let handles = (0..current_vector_data.segment_domain.handles().len())
.find_map(|index| {
let (start_id, end_id, bezier) = current_vector_data.segment_points_from_index(index);
if start_id == endpoint {
Some([bezier.handles.start(), bezier.handles.end()])
} else if end_id == endpoint {
// Reverse the handles if connecting to end point
Some([bezier.handles.end(), bezier.handles.start()])
} else {
None
}
})
.unwrap();
// Merge the layers first
merge_non_spline_layers(document, starting_layer, current_layer, other_layer, responses);
let points = [endpoint, join_point];
let points2 = [start_point, starting_layer_endpoint];
let id = SegmentId::generate();
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
responses.add(GraphOperationMessage::Vector {
layer: starting_layer,
modification_type,
});
let id = SegmentId::generate();
let modification_type = VectorModificationType::InsertSegment { id, points: points2, handles };
responses.add(GraphOperationMessage::Vector {
layer: starting_layer,
modification_type,
});
}
_ => {
let current_vector_data = match document.network_interface.compute_modified_vector(current_layer) {
Some(data) => data,
None => {
log::error!("Could not get vector data for current layer");
return false;
}
};
let handles = (0..current_vector_data.segment_domain.handles().len()).find_map(|index| {
let (start_id, end_id, bezier) = current_vector_data.segment_points_from_index(index);
if start_id == endpoint {
Some([bezier.handles.start(), bezier.handles.end()])
} else if end_id == endpoint {
Some([bezier.handles.end(), bezier.handles.start()])
} else {
None
}
});
let Some(handles) = handles else {
log::error!("Could not find handles for endpoint");
return false;
};
merge_path_spline_layer(document, current_layer, other_layer, responses);
let points = [endpoint, join_point];
let id = SegmentId::generate();
responses.add(GraphOperationMessage::Vector {
layer: other_layer,
modification_type: VectorModificationType::InsertSegment { id, points, handles },
});
}
}
true
}
@ -478,3 +599,426 @@ fn delete_preview(tool_data: &mut SplineToolData, responses: &mut VecDeque<Messa
tool_data.preview_point = None;
tool_data.preview_segment = None;
}
fn merge_two_spline_layer(document: &DocumentMessageHandler, current_layer: LayerNodeIdentifier, other_layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
// Calculate the downstream transforms in order to bring the other vector data into the same layer space
let current_transform = document.metadata().downstream_transform_to_document(current_layer);
let other_transform = document.metadata().downstream_transform_to_document(other_layer);
// Represents the change in position that would occur if the other layer was moved below the current layer
let transform_delta = current_transform * other_transform.inverse();
let offset = transform_delta.inverse();
responses.add(GraphOperationMessage::TransformChange {
layer: other_layer,
transform: offset,
transform_in: TransformIn::Local,
skip_rerender: false,
});
// First find their IDs
let current_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(
vec![current_layer.to_node()],
&[],
crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
)
.collect::<Vec<_>>();
let other_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<_>>();
// Add merge node and insert between path and spline
let merge_node_id = NodeId::new();
let merge_node = document_node_definitions::resolve_document_node_type("Merge")
.expect("Failed to create merge node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: merge_node_id,
node_template: merge_node,
});
responses.add(NodeGraphMessage::SetToNodeOrLayer {
node_id: merge_node_id,
is_layer: false,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: merge_node_id,
parent: current_layer,
});
responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {
downstream_input: InputConnector::node(other_layer.to_node(), 1),
input_connector: InputConnector::node(merge_node_id, 1),
});
// Add flatten vector elements node after merge
let flatten_node_id = NodeId::new();
let flatten_node = document_node_definitions::resolve_document_node_type("Flatten Vector Elements")
.expect("Failed to create flatten node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: flatten_node_id,
node_template: flatten_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: flatten_node_id,
parent: current_layer,
});
let path_node_id = NodeId::new();
let path_node = document_node_definitions::resolve_document_node_type("Path")
.expect("Failed to create path node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: path_node_id,
node_template: path_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: path_node_id,
parent: current_layer,
});
let spline_node_id = NodeId::new();
let spline_node = document_node_definitions::resolve_document_node_type("Splines from Points")
.expect("Failed to create spline node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: spline_node_id,
node_template: spline_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: spline_node_id,
parent: current_layer,
});
let stroke_node_id = NodeId::new();
let stroke_node = document_node_definitions::resolve_document_node_type("Stroke")
.expect("Failed to create stroke node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: stroke_node_id,
node_template: stroke_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: stroke_node_id,
parent: current_layer,
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: current_layer_nodes[1..3].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: other_layer_nodes[..3].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
}
fn merge_non_spline_layers(
document: &DocumentMessageHandler,
starting_layer: LayerNodeIdentifier,
current_layer: LayerNodeIdentifier,
other_layer: LayerNodeIdentifier,
responses: &mut VecDeque<Message>,
) {
// Calculate the downstream transforms in order to bring the other vector data into the same layer space
let current_transform = document.metadata().downstream_transform_to_document(current_layer);
let other_transform = document.metadata().downstream_transform_to_document(other_layer);
// Represents the change in position that would occur if the other layer was moved below the current layer
let transform_delta = current_transform * other_transform.inverse();
let offset = transform_delta.inverse();
responses.add(GraphOperationMessage::TransformChange {
layer: other_layer,
transform: offset,
transform_in: TransformIn::Local,
skip_rerender: false,
});
// Delete spline and stroke nodes from both layers
// First find their IDs
let starting_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(
vec![starting_layer.to_node()],
&[],
crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
)
.collect::<Vec<_>>();
let current_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(
vec![current_layer.to_node()],
&[],
crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
)
.collect::<Vec<_>>();
let other_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<_>>();
// Add merge node and insert between path and spline
let merge_node_id = NodeId::new();
let merge_node = document_node_definitions::resolve_document_node_type("Merge")
.expect("Failed to create merge node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: merge_node_id,
node_template: merge_node,
});
responses.add(NodeGraphMessage::SetToNodeOrLayer {
node_id: merge_node_id,
is_layer: false,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: merge_node_id,
parent: starting_layer,
});
responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {
downstream_input: InputConnector::node(other_layer.to_node(), 1),
input_connector: InputConnector::node(merge_node_id, 1),
});
let merge_node_id2 = NodeId::new();
let merge_node2 = document_node_definitions::resolve_document_node_type("Merge")
.expect("Failed to create merge node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: merge_node_id2,
node_template: merge_node2,
});
responses.add(NodeGraphMessage::SetToNodeOrLayer {
node_id: merge_node_id2,
is_layer: false,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: merge_node_id2,
parent: starting_layer,
});
responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {
downstream_input: InputConnector::node(current_layer.to_node(), 1),
input_connector: InputConnector::node(merge_node_id2, 1),
});
// Add flatten vector elements node after merge
let flatten_node_id2 = NodeId::new();
let flatten_node2 = document_node_definitions::resolve_document_node_type("Flatten Vector Elements")
.expect("Failed to create flatten node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: flatten_node_id2,
node_template: flatten_node2,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: flatten_node_id2,
parent: starting_layer,
});
let path_node_id = NodeId::new();
let path_node = document_node_definitions::resolve_document_node_type("Path")
.expect("Failed to create path node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: path_node_id,
node_template: path_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: path_node_id,
parent: starting_layer,
});
let stroke_node_id = NodeId::new();
let stroke_node = document_node_definitions::resolve_document_node_type("Stroke")
.expect("Failed to create stroke node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: stroke_node_id,
node_template: stroke_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: stroke_node_id,
parent: starting_layer,
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: starting_layer_nodes[1..(if is_layer_line(document, starting_layer) { 2 } else { 3 })].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: other_layer_nodes[..(if is_layer_line(document, other_layer) { 2 } else { 3 })].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: current_layer_nodes[..2].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
}
fn merge_path_spline_layer(document: &DocumentMessageHandler, current_layer: LayerNodeIdentifier, other_layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
// Calculate the downstream transforms in order to bring the other vector data into the same layer space
let current_transform = document.metadata().downstream_transform_to_document(current_layer);
let other_transform = document.metadata().downstream_transform_to_document(other_layer);
// Represents the change in position that would occur if the other layer was moved below the current layer
let transform_delta = current_transform * other_transform.inverse();
let offset = transform_delta.inverse();
responses.add(GraphOperationMessage::TransformChange {
layer: other_layer,
transform: offset,
transform_in: TransformIn::Local,
skip_rerender: false,
});
// First find their IDs
let current_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(
vec![current_layer.to_node()],
&[],
crate::messages::portfolio::document::utility_types::network_interface::FlowType::HorizontalFlow,
)
.collect::<Vec<_>>();
let other_layer_nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![other_layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<_>>();
// Add merge node and insert between path and spline
let merge_node_id = NodeId::new();
let merge_node = document_node_definitions::resolve_document_node_type("Merge")
.expect("Failed to create merge node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: merge_node_id,
node_template: merge_node,
});
responses.add(NodeGraphMessage::SetToNodeOrLayer {
node_id: merge_node_id,
is_layer: false,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: merge_node_id,
parent: other_layer,
});
responses.add(NodeGraphMessage::ConnectUpstreamOutputToInput {
downstream_input: InputConnector::node(current_layer.to_node(), 1),
input_connector: InputConnector::node(merge_node_id, 1),
});
// Add flatten vector elements node after merge
let flatten_node_id = NodeId::new();
let flatten_node = document_node_definitions::resolve_document_node_type("Flatten Vector Elements")
.expect("Failed to create flatten node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: flatten_node_id,
node_template: flatten_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: flatten_node_id,
parent: other_layer,
});
let path_node_id = NodeId::new();
let path_node = document_node_definitions::resolve_document_node_type("Path")
.expect("Failed to create path node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: path_node_id,
node_template: path_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: path_node_id,
parent: other_layer,
});
let stroke_node_id = NodeId::new();
let stroke_node = document_node_definitions::resolve_document_node_type("Stroke")
.expect("Failed to create stroke node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: stroke_node_id,
node_template: stroke_node,
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: stroke_node_id,
parent: other_layer,
});
let node_range = if is_layer_line(document, other_layer) {
1..2 // transform node is not deleted
} else {
1..3
};
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: other_layer_nodes[node_range].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: current_layer_nodes[..2].to_vec(),
delete_children: false,
});
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(Message::StartBuffer);
}
fn is_layer_spline(document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool {
let nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<NodeId>>();
// Check node types in the chain
let mut has_spline = false;
for node in nodes {
if let Some(reference) = document.network_interface.reference(&node, &[]) {
match reference.as_deref() {
Some("Splines from Points") => has_spline = true,
_ => continue,
}
}
}
has_spline
}
fn is_layer_line(document: &DocumentMessageHandler, layer: LayerNodeIdentifier) -> bool {
let nodes = document
.network_interface
.upstream_flow_back_from_nodes(vec![layer.to_node()], &[], FlowType::HorizontalFlow)
.collect::<Vec<NodeId>>();
// Check node types in the chain
let mut has_line = false;
for node in nodes {
if let Some(reference) = document.network_interface.reference(&node, &[]) {
match reference.as_deref() {
Some("Line") => has_line = true,
_ => continue,
}
}
}
has_line
}