diff --git a/editor/src/consts.rs b/editor/src/consts.rs index f05f156b8..ea52e7662 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -125,7 +125,7 @@ pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.; pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9; pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2; pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.; -pub const SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD: f64 = 10.; +pub const SPIRAL_INNER_RADIUS_INDEX_GIZMO_THRESHOLD: f64 = 10.; pub const GIZMO_HIDE_THRESHOLD: f64 = 20.; // SCROLLBARS @@ -154,6 +154,7 @@ pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500; /// SPIRAL NODE INPUT INDICES pub const SPIRAL_TYPE_INDEX: usize = 1; -pub const SPIRAL_INNER_RADIUS: usize = 2; +pub const SPIRAL_INNER_RADIUS_INDEX: usize = 2; pub const SPIRAL_OUTER_RADIUS_INDEX: usize = 3; pub const SPIRAL_TURNS_INDEX: usize = 4; +pub const SPIRAL_START_ANGLE: usize = 6; diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 9f27f5ed5..9e65e14b6 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1255,8 +1255,13 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon ParameterWidgetsInfo::new(node_id, AngleOffsetInput::INDEX, true, context), NumberInput::default().min(0.1).max(180.).unit("°"), ); + let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°")); - widgets.extend([LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: angle_offset }]); + widgets.extend([ + LayoutGroup::Row { widgets: turns }, + LayoutGroup::Row { widgets: angle_offset }, + LayoutGroup::Row { widgets: start_angle }, + ]); widgets } diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index 7c26b166b..ae05d2588 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -390,7 +390,7 @@ impl OverlayContext { if let Some(transform) = transform { let [a, b, c, d, e, f] = transform.to_cols_array(); - self.render_context.transform(a, b, c, d, e, f); + self.render_context.transform(a, b, c, d, e, f).expect("Failed to transform circle"); } if let Some(dash_width) = dash_width { diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs deleted file mode 100644 index 247027a83..000000000 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/arc_spiral_inner_radius_handle.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::consts::ARCHIMEDEAN_INNER_RADIUS_INDEX; -use crate::consts::COLOR_OVERLAY_RED; -use crate::consts::GIZMO_HIDE_THRESHOLD; -use crate::consts::LOGARITHMIC_START_RADIUS_INDEX; -use crate::consts::NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH; -use crate::consts::SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD; -use crate::messages::frontend::utility_types::MouseCursorIcon; -use crate::messages::message::Message; -use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}; -use crate::messages::prelude::FrontendMessage; -use crate::messages::prelude::Responses; -use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; -use crate::messages::tool::common_functionality::shapes::shape_utility::archimedean_spiral_point; -use crate::messages::tool::common_functionality::shapes::shape_utility::calculate_b; -use crate::messages::tool::common_functionality::shapes::shape_utility::extract_arc_spiral_parameters; -use crate::messages::tool::common_functionality::shapes::shape_utility::extract_log_spiral_parameters; -use crate::messages::tool::common_functionality::shapes::shape_utility::get_arc_spiral_end_point; -use crate::messages::tool::common_functionality::shapes::shape_utility::get_log_spiral_end_point; -use glam::DVec2; -use graph_craft::document::NodeInput; -use graph_craft::document::value::TaggedValue; -use graphene_std::num_traits::sign; -use graphene_std::vector::misc::SpiralType; -use graphene_std::vector::misc::dvec2_to_point; -use kurbo::BezPath; -use kurbo::Circle; -use kurbo::Line; -use kurbo::ParamCurveNearest; -use kurbo::Point; -use std::collections::VecDeque; - -#[derive(Clone, Debug, Default, PartialEq)] -pub enum RadiusGizmoState { - #[default] - Inactive, - Hover, - Dragging, -} - -#[derive(Clone, Debug, Default)] -pub struct RadiusGizmo { - pub layer: Option, - pub handle_state: RadiusGizmoState, - pub spiral_type: SpiralType, - initial_radius: f64, - previous_mouse: DVec2, -} - -impl RadiusGizmo { - pub fn cleanup(&mut self) { - self.layer = None; - self.handle_state = RadiusGizmoState::Inactive; - self.initial_radius = 0.; - self.previous_mouse = DVec2::ZERO; - } - - pub fn hovered(&self) -> bool { - self.handle_state == RadiusGizmoState::Hover - } - - pub fn is_dragging(&self) -> bool { - self.handle_state == RadiusGizmoState::Dragging - } - - pub fn update_state(&mut self, state: RadiusGizmoState) { - self.handle_state = state; - } - - pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { - match &self.handle_state { - RadiusGizmoState::Inactive => { - // Archimedean - if let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) { - let viewport = document.metadata().transform_to_viewport(layer); - let layer_mouse = viewport.inverse().transform_point2(mouse_position); - - let center = viewport.transform_point2(DVec2::ZERO); - - if (DVec2::ZERO.distance(layer_mouse) - a).abs() < 5. { - self.layer = Some(layer); - self.initial_radius = a; - self.spiral_type = SpiralType::Archimedean; - self.update_state(RadiusGizmoState::Hover); - self.previous_mouse = mouse_position; - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); - } - } - - // Logarithmic - if let Some((a, outer_radius, turns)) = extract_log_spiral_parameters(layer, document) { - let viewport = document.metadata().transform_to_viewport(layer); - let layer_mouse = viewport.inverse().transform_point2(mouse_position); - - if (DVec2::ZERO.distance(layer_mouse) - a).abs() < 5. { - self.layer = Some(layer); - self.initial_radius = a; - self.spiral_type = SpiralType::Logarithmic; - self.update_state(RadiusGizmoState::Hover); - self.previous_mouse = mouse_position; - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); - } - } - } - RadiusGizmoState::Hover | RadiusGizmoState::Dragging => {} - } - } - - pub fn overlays( - &self, - document: &DocumentMessageHandler, - selected_spiral_layer: Option, - input: &InputPreprocessorMessageHandler, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { - match &self.handle_state { - _ => { - let Some(layer) = selected_spiral_layer.or(self.layer) else { return }; - - let viewport = document.metadata().transform_to_viewport(layer); - if let Some((radius, _, _)) = extract_arc_spiral_parameters(layer, document).or(extract_log_spiral_parameters(layer, document)) { - overlay_context.dashed_circle(DVec2::ZERO, radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport)); - } - } - } - } - - pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { - let Some(layer) = self.layer else { return }; - - let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { - return; - }; - - let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let center = viewport_transform.transform_point2(DVec2::ZERO); - let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position); - let previous_mouse_layer = viewport_transform.inverse().transform_point2(self.previous_mouse); - let drag_start = viewport_transform.inverse().transform_point2(drag_start); - let center_layer = DVec2::ZERO; - - let delta_vector = current_mouse_layer - previous_mouse_layer; - let sign = (current_mouse_layer - previous_mouse_layer).dot(drag_start - center_layer).signum(); - let delta = delta_vector.length() * sign; - - self.previous_mouse = input.mouse.position; - - let (net_radius, index) = match self.spiral_type { - SpiralType::Archimedean => { - let current_radius = extract_arc_spiral_parameters(layer, document) - .map(|(a, _, _)| a) - .expect("Failed to get archimedean spiral inner radius"); - ((current_radius + delta).max(0.), ARCHIMEDEAN_INNER_RADIUS_INDEX) - } - SpiralType::Logarithmic => { - let current_radius = extract_log_spiral_parameters(layer, document) - .map(|(a, _, _)| a) - .expect("Failed to get logarithmic spiral inner radius"); - ((current_radius + delta).max(0.001), LOGARITHMIC_START_RADIUS_INDEX) - } - }; - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, index), - input: NodeInput::value(TaggedValue::F64(net_radius), false), - }); - responses.add(NodeGraphMessage::RunDocumentGraph); - } -} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs index b604ec1ee..8bc8065c6 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/mod.rs @@ -1,5 +1,5 @@ -pub mod arc_spiral_inner_radius_handle; pub mod number_of_points_dial; pub mod point_radius_handle; +pub mod spiral_inner_radius_handle; pub mod spiral_tightness_gizmo; pub mod spiral_turns_handle; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_inner_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_inner_radius_handle.rs new file mode 100644 index 000000000..3f7bf6689 --- /dev/null +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_inner_radius_handle.rs @@ -0,0 +1,143 @@ +use crate::consts::{COLOR_OVERLAY_RED, SPIRAL_INNER_RADIUS_INDEX, SPIRAL_OUTER_RADIUS_INDEX}; +use crate::messages::frontend::utility_types::MouseCursorIcon; +use crate::messages::message::Message; +use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; +use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector}; +use crate::messages::prelude::FrontendMessage; +use crate::messages::prelude::Responses; +use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage}; +use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{calculate_b, get_spiral_type}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_arc_or_log_spiral_parameters, spiral_point}; +use glam::DVec2; +use graph_craft::document::NodeInput; +use graph_craft::document::value::TaggedValue; +use graphene_std::vector::misc::SpiralType; +use std::collections::VecDeque; +use std::f64::consts::TAU; + +#[derive(Clone, Debug, Default, PartialEq)] +pub enum RadiusGizmoState { + #[default] + Inactive, + Hover, + Dragging, +} + +#[derive(Clone, Debug, Default)] +pub struct RadiusGizmo { + pub layer: Option, + pub handle_state: RadiusGizmoState, + pub spiral_type: SpiralType, + radius_index: usize, + previous_mouse_position: DVec2, + initial_radius: f64, +} + +impl RadiusGizmo { + pub fn cleanup(&mut self) { + self.layer = None; + self.handle_state = RadiusGizmoState::Inactive; + self.initial_radius = 0.; + } + + pub fn hovered(&self) -> bool { + self.handle_state == RadiusGizmoState::Hover + } + + pub fn is_dragging(&self) -> bool { + self.handle_state == RadiusGizmoState::Dragging + } + + pub fn update_state(&mut self, state: RadiusGizmoState) { + self.handle_state = state; + } + + pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque) { + match &self.handle_state { + RadiusGizmoState::Inactive => { + if let Some(((inner_radius, outer_radius, _, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) { + let smaller_radius = (inner_radius.min(outer_radius)).max(5.); + let viewport = document.metadata().transform_to_viewport(layer); + let layer_mouse = viewport.inverse().transform_point2(mouse_position); + + if DVec2::ZERO.distance(layer_mouse) < smaller_radius.max(5.) { + self.layer = Some(layer); + self.initial_radius = inner_radius; + self.spiral_type = spiral_type; + self.previous_mouse_position = mouse_position; + self.radius_index = if inner_radius > outer_radius { SPIRAL_OUTER_RADIUS_INDEX } else { SPIRAL_INNER_RADIUS_INDEX }; + self.update_state(RadiusGizmoState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize }); + } + } + } + RadiusGizmoState::Hover | RadiusGizmoState::Dragging => {} + } + } + + pub fn overlays(&self, document: &DocumentMessageHandler, selected_spiral_layer: Option, overlay_context: &mut OverlayContext) { + match &self.handle_state { + RadiusGizmoState::Hover | RadiusGizmoState::Dragging => { + let Some(layer) = selected_spiral_layer.or(self.layer) else { return }; + + let viewport = document.metadata().transform_to_viewport(layer); + if let Some(((inner_radius, outer_radius, turns, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) { + let b = calculate_b(inner_radius, turns, outer_radius, spiral_type); + let (radius, endpoint) = if self.radius_index == SPIRAL_INNER_RADIUS_INDEX { + (inner_radius, spiral_point(0., inner_radius, b, spiral_type)) + } else { + (outer_radius, spiral_point(turns * TAU, inner_radius, b, spiral_type)) + }; + + overlay_context.manipulator_handle(viewport.transform_point2(endpoint), true, Some(COLOR_OVERLAY_RED)); + overlay_context.dashed_circle(DVec2::ZERO, radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport)); + } + } + _ => {} + } + } + + pub fn update_inner_radius(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + let Some(layer) = self.layer else { return }; + + let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else { + return; + }; + + let node_inputs = NodeGraphLayer::new(layer, &document.network_interface) + .find_node_inputs("Spiral") + .expect("Failed to find inputs of Spiral"); + + let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); + + let center = DVec2::ZERO; + let layer_drag_start = viewport_transform.inverse().transform_point2(drag_start); + + let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position); + let previous_mouse_layer = viewport_transform.inverse().transform_point2(self.previous_mouse_position); + + let sign = (current_mouse_layer - previous_mouse_layer).dot(layer_drag_start).signum(); + + let delta = current_mouse_layer.distance(previous_mouse_layer) * sign; + + let net_radius = current_mouse_layer.distance(DVec2::ZERO); + + let Some(&TaggedValue::F64(radius)) = node_inputs.get(self.radius_index).expect("Failed to get radius of Spiral").as_value() else { + return; + }; + + let net_radius = match self.spiral_type { + SpiralType::Archimedean => (radius + delta).max(0.), + SpiralType::Logarithmic => (radius + delta).max(0.001), + }; + + self.previous_mouse_position = input.mouse.position; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, self.radius_index), + input: NodeInput::value(TaggedValue::F64(net_radius), false), + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + } +} diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs index dbf8882a3..5b61b4e99 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_tightness_gizmo.rs @@ -1,4 +1,4 @@ -use crate::consts::{COLOR_OVERLAY_RED, SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_TURNS_INDEX}; +use crate::consts::{COLOR_OVERLAY_RED, SPIRAL_INNER_RADIUS_INDEX, SPIRAL_INNER_RADIUS_INDEX_GIZMO_THRESHOLD, SPIRAL_OUTER_RADIUS_INDEX}; use crate::messages::frontend::utility_types::MouseCursorIcon; use crate::messages::message::Message; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; @@ -6,14 +6,15 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::Responses; use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage}; -use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_stroke_width}; +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::shapes::shape_utility::{ - archimedean_spiral_point, calculate_b, extract_arc_spiral_parameters, extract_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, + calculate_b, extract_arc_or_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, get_spiral_type, spiral_point, }; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::uuid::NodeId; use graphene_std::vector::misc::{SpiralType, dvec2_to_point}; use kurbo::{Line, ParamCurveNearest}; use std::collections::VecDeque; @@ -27,6 +28,14 @@ pub enum TightnessGizmoState { Dragging, } +#[derive(Clone, Debug, Default, PartialEq)] +enum TightnessGizmoType { + #[default] + None, + Circle, + DashLines, +} + #[derive(Clone, Debug, Default)] pub struct TightnessGizmo { pub layer: Option, @@ -34,7 +43,11 @@ pub struct TightnessGizmo { initial_outer_radius: f64, spiral_type: SpiralType, gizmo_line_points: Option<(DVec2, DVec2)>, + inner_radius: f64, + angle: f64, + spiral_slot: i32, previous_mouse: DVec2, + gizmo_type: TightnessGizmoType, } impl TightnessGizmo { @@ -42,6 +55,7 @@ impl TightnessGizmo { self.handle_state = TightnessGizmoState::Inactive; self.layer = None; self.gizmo_line_points = None; + self.gizmo_type = TightnessGizmoType::None; } pub fn update_state(&mut self, state: TightnessGizmoState) { @@ -62,203 +76,205 @@ impl TightnessGizmo { match &self.handle_state { TightnessGizmoState::Inactive => { // Archimedean - if let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) { - let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean); - if let Some((start, end)) = Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), outer_radius, turns, a, viewport) { - let line = Line::new(dvec2_to_point(start), dvec2_to_point(end)); - if line.nearest(dvec2_to_point(mouse_position), 1e-6).distance_sq < SPIRAL_INNER_RADIUS_GIZMO_THRESHOLD { - self.layer = Some(layer); - self.initial_outer_radius = outer_radius; - self.previous_mouse = mouse_position; - self.gizmo_line_points = Some((start, end)); - self.spiral_type = SpiralType::Archimedean; - self.update_state(TightnessGizmoState::Hover); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - } - return; - }; - - let layer_mouse = viewport.inverse().transform_point2(mouse_position); - - let center = viewport.transform_point2(DVec2::ZERO); - - let Some(endpoint) = get_arc_spiral_end_point(layer, document, viewport, TAU) else { return }; - - let close_to_circle = (DVec2::ZERO.distance(layer_mouse) - outer_radius).abs() < 5.; - - if close_to_circle { + if let Some(((a, outer_radius, turns, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) { + if let Some((start, end, slot_index)) = Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), outer_radius, turns, a, spiral_type, viewport) { self.layer = Some(layer); self.initial_outer_radius = outer_radius; self.previous_mouse = mouse_position; - // self.gizmo_line_points = Some((start, end)); - self.spiral_type = SpiralType::Archimedean; + self.gizmo_line_points = Some((start, end)); + self.spiral_type = spiral_type; + self.spiral_slot = slot_index; + self.inner_radius = a; + self.gizmo_type = TightnessGizmoType::DashLines; + self.angle = viewport.inverse().transform_point2(mouse_position).angle_to(DVec2::X).rem_euclid(TAU); + self.update_state(TightnessGizmoState::Hover); + responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); + + return; + }; + + let center = viewport.transform_point2(DVec2::ZERO); + + let angle = if a > outer_radius { 0. } else { TAU }; + + let Some(endpoint) = get_arc_spiral_end_point(layer, document, viewport, angle).or(get_log_spiral_end_point(layer, document, viewport, angle)) else { + return; + }; + + let close_to_circle = (endpoint.distance(center) - mouse_position.distance(center)).abs() < 5.; + + if close_to_circle { + self.layer = Some(layer); + self.inner_radius = a; + self.initial_outer_radius = outer_radius; + self.previous_mouse = mouse_position; + self.gizmo_type = TightnessGizmoType::Circle; + self.spiral_type = spiral_type; self.update_state(TightnessGizmoState::Hover); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } } - - // // Logarithmic - // if let Some(((_, _, turns), end_point)) = extract_log_spiral_parameters(layer, document).zip(get_log_spiral_end_point(layer, document, viewport)) { - // if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD { - // self.layer = Some(layer); - // self.initial_turns = turns; - // self.previous_mouse_position = mouse_position; - // self.spiral_type = SpiralType::Logarithmic; - // self.update_state(SpiralTurnsState::Hover); - // responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - // } - // } - } - TightnessGizmoState::Hover | TightnessGizmoState::Dragging => { - // let Some(layer) = self.layer else { return }; - - // let viewport = document.metadata().transform_to_viewport(layer); - // let center = viewport.transform_point2(DVec2::ZERO); - - // if mouse_position.distance(center) > NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsDialState::Hover) { - // self.update_state(NumberOfPointsDialState::Inactive); - // self.layer = None; - // responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - // } } + TightnessGizmoState::Hover | TightnessGizmoState::Dragging => {} } } - pub fn overlays( - &self, - document: &DocumentMessageHandler, - selected_spiral_layer: Option, - _shape_editor: &mut &mut ShapeState, - mouse_position: DVec2, - overlay_context: &mut OverlayContext, - ) { + pub fn overlays(&self, document: &DocumentMessageHandler, selected_spiral_layer: Option, _shape_editor: &mut &mut ShapeState, overlay_context: &mut OverlayContext) { let Some(layer) = selected_spiral_layer.or(self.layer) else { return; }; let viewport = document.metadata().transform_to_viewport(layer); match &self.handle_state { - TightnessGizmoState::Hover | TightnessGizmoState::Dragging => { - let Some(layer) = selected_spiral_layer.or(self.layer) else { - return; - }; - - if let Some((start, end)) = self.gizmo_line_points { - overlay_context.dashed_line(start, end, Some(COLOR_OVERLAY_RED), None, Some(4.0), Some(4.0), Some(0.5)); - }; - - let viewport = document.metadata().transform_to_viewport(layer); - if let Some((_, outer_radius, _)) = extract_arc_spiral_parameters(layer, document).or(extract_log_spiral_parameters(layer, document)) { - overlay_context.dashed_circle(DVec2::ZERO, outer_radius.max(5.), None, Some(COLOR_OVERLAY_RED), Some(8.), Some(4.), Some(0.5), Some(viewport)); + TightnessGizmoState::Hover | TightnessGizmoState::Dragging => match self.gizmo_type { + TightnessGizmoType::Circle => { + if let Some((inner_radius, outer_radius, _, _)) = extract_arc_or_log_spiral_parameters(layer, document) { + let required_radius = if self.inner_radius > self.initial_outer_radius { inner_radius } else { outer_radius }; + overlay_context.dashed_circle(DVec2::ZERO, required_radius.max(5.), None, None, Some(8.), Some(4.), Some(0.5), Some(viewport)); + } } - - // let viewport = document.metadata().transform_to_viewport(layer); - // if let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) { - // let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean); - // let Some((start, end)) = Self::check_which_inter_segment(viewport.inverse().transform_point2(mouse_position), outer_radius, turns, a, b, viewport) else { - // return; - // }; - // overlay_context.dashed_line(start, end, None, None, Some(4.), Some(4.), Some(0.5)); - // } - } - TightnessGizmoState::Inactive => { - if let Some((_, outer_radius, _)) = extract_arc_spiral_parameters(layer, document).or(extract_log_spiral_parameters(layer, document)) { - overlay_context.dashed_circle(DVec2::ZERO, outer_radius.max(5.), None, Some(COLOR_OVERLAY_RED), Some(8.), Some(4.), Some(0.5), Some(viewport)); + TightnessGizmoType::DashLines => { + if let Some((start, end)) = self.gizmo_line_points { + overlay_context.dashed_line(start, end, None, None, Some(4.0), Some(4.0), Some(0.5)); + if self.spiral_slot == 0 { + let required_radius = if self.inner_radius > self.initial_outer_radius { + self.initial_outer_radius + } else { + self.inner_radius + }; + overlay_context.dashed_circle(DVec2::ZERO, required_radius.max(5.), None, None, Some(4.), Some(4.), Some(0.5), Some(viewport)); + } + }; } - } + TightnessGizmoType::None => {} + }, + TightnessGizmoState::Inactive => {} } } - fn check_which_inter_segment(mouse_position: DVec2, outer_radius: f64, turns: f64, a: f64, transform: DAffine2) -> Option<(DVec2, DVec2)> { - let b = calculate_b(a, turns, outer_radius, SpiralType::Archimedean); + fn check_which_inter_segment(layer_mouse_position: DVec2, outer_radius: f64, turns: f64, inner_radius: f64, spiral_type: SpiralType, viewport: DAffine2) -> Option<(DVec2, DVec2, i32)> { let center = DVec2::ZERO; - let angle = mouse_position.angle_to(DVec2::X).rem_euclid(TAU); - - let viewport_mouse = transform.transform_point2(mouse_position); - let viewport_center = transform.transform_point2(center); + let mut angle = layer_mouse_position.angle_to(DVec2::X).rem_euclid(TAU); + let is_reversed = inner_radius > outer_radius; + let b = calculate_b(inner_radius, turns, outer_radius, spiral_type); let max_theta = turns * TAU; - let spiral_outer = archimedean_spiral_point(max_theta, a, b); - let viewport_outer = transform.transform_point2(spiral_outer); - if viewport_mouse.distance(viewport_center) > viewport_outer.distance(viewport_center) { + let viewport_mouse = viewport.transform_point2(layer_mouse_position); + let viewport_center = viewport.transform_point2(center); + + // Compute spiral endpoints at θ = 0 and θ = max + let spiral_outer = spiral_point(max_theta, inner_radius, b, spiral_type); + let spiral_inner = spiral_point(0., inner_radius, b, spiral_type); + let viewport_outer = viewport.transform_point2(spiral_outer); + let viewport_inner = viewport.transform_point2(spiral_inner); + + let smaller_radius = inner_radius.min(outer_radius); + let adjusted_angle = if is_reversed { max_theta - (TAU - angle) } else { angle }; + + let required_endpoint = if is_reversed { viewport_inner } else { viewport_outer }; + + // Reject if mouse is beyond spiral's radial extent + if viewport_mouse.distance(viewport_center) > required_endpoint.distance(viewport_center) { return None; } let mouse_distance = viewport_mouse.distance(viewport_center); - let mut segment_index = 0; - // ---- First segment: from center to spiral at θ = angle + // First segment: from center to first spiral point at θ = adjusted_angle { - let start = viewport_center; - let spiral_end = archimedean_spiral_point(angle, a, b); - let end = transform.transform_point2(spiral_end); - - let r_end = end.distance(viewport_center); + let spiral_end = spiral_point(adjusted_angle, inner_radius, b, spiral_type); + let first_point = viewport.transform_point2(spiral_end); + let r_end = first_point.distance(viewport_center); if mouse_distance <= r_end { - return Some(Self::calculate_gizmo_line_points(viewport_center, end)); + let direction = DVec2::new(adjusted_angle.cos(), -adjusted_angle.sin()); + return Some((viewport.transform_point2(smaller_radius.max(5.) * direction), first_point, segment_index)); } segment_index += 1; } - // ---- Remaining segments: each full turn outward along the ray - let mut base_theta = angle; - - while base_theta <= max_theta { + // Loop through each full turn segment along the spiral ray + let mut base_theta = adjusted_angle; + while if is_reversed { base_theta >= 0. } else { base_theta <= max_theta } { let theta_start = base_theta; - let theta_end = base_theta + TAU; + let theta_end = if is_reversed { base_theta - TAU } else { base_theta + TAU }; - if theta_end > max_theta { + if (!is_reversed && theta_end > max_theta) || (is_reversed && theta_end < 0.) { break; } - let spiral_start = archimedean_spiral_point(theta_start, a, b); - let spiral_end = archimedean_spiral_point(theta_end, a, b); + let spiral_start = spiral_point(theta_start, inner_radius, b, spiral_type); + let spiral_end = spiral_point(theta_end, inner_radius, b, spiral_type); - let viewport_start = transform.transform_point2(spiral_start); - let viewport_end = transform.transform_point2(spiral_end); + let viewport_start = viewport.transform_point2(spiral_start); + let viewport_end = viewport.transform_point2(spiral_end); let r_start = viewport_start.distance(viewport_center); let r_end = viewport_end.distance(viewport_center); - if mouse_distance >= r_start && mouse_distance <= r_end { - return Some(Self::calculate_gizmo_line_points(viewport_start, viewport_end)); + if mouse_distance >= r_start.min(r_end) && mouse_distance <= r_start.max(r_end) { + let (point1, point2) = Self::calculate_gizmo_line_points(viewport_start, viewport_end); + return Some((point1, point2, segment_index)); } - base_theta += TAU; + base_theta = if is_reversed { base_theta - TAU } else { base_theta + TAU }; + segment_index += 1; } None } + pub fn calculate_updated_dash_lines(&self, inner_radius: f64, outer_radius: f64, turns: f64, spiral_type: SpiralType, viewport: DAffine2, drag_start: DVec2, reversed: bool) -> (DVec2, DVec2) { + let b = calculate_b(inner_radius, turns, outer_radius, spiral_type); + let max_theta = turns * TAU; + let base_angle = if reversed { max_theta - (TAU - self.angle) } else { self.angle }; + let smaller_radius = inner_radius.min(outer_radius); + + let center = DVec2::ZERO; + + let (start_point, end_point) = if self.spiral_slot == 0 { + ( + viewport.transform_point2(smaller_radius * DVec2::new(base_angle.cos(), -base_angle.sin())), + viewport.transform_point2(spiral_point(base_angle, inner_radius, b, spiral_type)), + ) + } else { + let ref_angle = (self.spiral_slot as f64 - 1.) * TAU + base_angle; + let end_point_angle = if reversed { ref_angle - TAU } else { ref_angle + TAU }; + ( + viewport.transform_point2(spiral_point(ref_angle, inner_radius, b, spiral_type)), + viewport.transform_point2(spiral_point(end_point_angle, inner_radius, b, spiral_type)), + ) + }; + + Self::calculate_gizmo_line_points(start_point, end_point) + } + // (start_point,end_point) fn calculate_gizmo_line_points(start_point: DVec2, end_point: DVec2) -> (DVec2, DVec2) { let length = start_point.distance(end_point); - let factor = 0.25 * length; - let direction = (end_point - start_point).normalize(); + let direction = (end_point - start_point).normalize_or_zero(); - let new_endpoint = end_point - direction * factor; - let new_start_point = start_point + direction * factor; + let new_endpoint = end_point - direction * length; + let new_start_point = start_point + direction * length; (new_start_point, new_endpoint) } - pub fn update_number_of_turns(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { - let Some(layer) = self.layer else { - return; - }; - - let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else { - return; - }; - - let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let center = viewport_transform.transform_point2(DVec2::ZERO); + pub fn update_outer_radius_via_dashed_lines( + &mut self, + layer: LayerNodeIdentifier, + node_id: NodeId, + viewport_transform: DAffine2, + document: &DocumentMessageHandler, + input: &InputPreprocessorMessageHandler, + responses: &mut VecDeque, + drag_start: DVec2, + ) { let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position); let previous_mouse_layer = viewport_transform.inverse().transform_point2(self.previous_mouse); let drag_start = viewport_transform.inverse().transform_point2(drag_start); @@ -268,25 +284,80 @@ impl TightnessGizmo { let sign = (current_mouse_layer - previous_mouse_layer).dot(drag_start - center_layer).signum(); let delta = delta_vector.length() * sign; + let reversed = self.inner_radius > self.initial_outer_radius; self.previous_mouse = input.mouse.position; - let (a, turns, net_radius) = match self.spiral_type { + let (a, outer_radius, turns, _) = extract_arc_or_log_spiral_parameters(layer, document).expect("Failed to get archimedean spiral inner radius"); + let (new_inner_radius, turns, new_outer_radius) = match self.spiral_type { SpiralType::Archimedean => { - let (a, outer_radius, turns) = extract_arc_spiral_parameters(layer, document).expect("Failed to get archimedean spiral inner radius"); - (a, turns, (outer_radius + delta).max(0.0)) + if reversed { + ((a + delta).max(0.), turns, outer_radius) + } else { + (a, turns, (outer_radius + delta).max(0.)) + } } SpiralType::Logarithmic => { - let (a, outer_radius, turns) = extract_log_spiral_parameters(layer, document).expect("Failed to get logarithmic spiral inner radius"); - (a, turns, (outer_radius + delta).max(0.001)) + if reversed { + ((a + delta).max(0.001), turns, outer_radius) + } else { + (a, turns, (outer_radius + delta).max(0.001)) + } } }; - self.gizmo_line_points = Self::check_which_inter_segment(current_mouse_layer, net_radius, turns, a, viewport_transform); + let b = calculate_b(new_inner_radius, turns, new_outer_radius, self.spiral_type); + self.gizmo_line_points = Some(self.calculate_updated_dash_lines(new_inner_radius, new_outer_radius, turns, self.spiral_type, viewport_transform, drag_start, reversed)); + + let (index, new_radius) = if reversed { + (SPIRAL_INNER_RADIUS_INDEX, new_inner_radius) + } else { + (SPIRAL_OUTER_RADIUS_INDEX, new_outer_radius) + }; responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, SPIRAL_OUTER_RADIUS_INDEX), + input_connector: InputConnector::node(node_id, index), + input: NodeInput::value(TaggedValue::F64(new_radius), false), + }); + } + + pub fn update_outer_radius_via_circle(&mut self, node_id: NodeId, viewport_transform: DAffine2, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { + let current_mouse_layer = viewport_transform.inverse().transform_point2(input.mouse.position); + let net_radius = current_mouse_layer.distance(DVec2::ZERO); + + let net_radius = match self.spiral_type { + SpiralType::Archimedean => net_radius.max(0.), + SpiralType::Logarithmic => net_radius.max(0.001), + }; + + let index = if self.initial_outer_radius > self.inner_radius { + SPIRAL_OUTER_RADIUS_INDEX + } else { + SPIRAL_INNER_RADIUS_INDEX + }; + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, index), input: NodeInput::value(TaggedValue::F64(net_radius), false), }); + } + + pub fn update_outer_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, drag_start: DVec2) { + let Some(layer) = self.layer else { + return; + }; + + let Some(node_id) = graph_modification_utils::get_spiral_id(layer, &document.network_interface) else { + return; + }; + + let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); + + match &self.gizmo_type { + TightnessGizmoType::Circle => self.update_outer_radius_via_circle(node_id, viewport_transform, input, responses), + TightnessGizmoType::DashLines => self.update_outer_radius_via_dashed_lines(layer, node_id, viewport_transform, document, input, responses, drag_start), + TightnessGizmoType::None => {} + } + responses.add(NodeGraphMessage::RunDocumentGraph); } } diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs index 5d9bff779..9bee1a1cb 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/spiral_turns_handle.rs @@ -9,7 +9,7 @@ use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPre use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shapes::shape_utility::{ - calculate_b, extract_arc_spiral_parameters, extract_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, + calculate_b, extract_arc_or_log_spiral_parameters, get_arc_spiral_end_point, get_log_spiral_end_point, get_spiral_type, spiral_point, }; use glam::DVec2; use graph_craft::document::NodeInput; @@ -75,17 +75,11 @@ impl SpiralTurns { match &self.handle_state { SpiralTurnsState::Inactive => { // Archimedean - if let Some(((inner_radius, outer_radius, turns), end_point)) = extract_arc_spiral_parameters(layer, document).zip(get_arc_spiral_end_point(layer, document, viewport, TAU)) { + if let Some(((inner_radius, outer_radius, turns, _), spiral_type)) = extract_arc_or_log_spiral_parameters(layer, document).zip(get_spiral_type(layer, document)) { + let b = calculate_b(inner_radius, turns, outer_radius, spiral_type); + let end_point = viewport.transform_point2(spiral_point(turns * TAU, inner_radius, b, spiral_type)); if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD { - self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, SpiralType::Archimedean); - responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); - } - } - - // Logarithmic - if let Some(((inner_radius, outer_radius, turns), end_point)) = extract_log_spiral_parameters(layer, document).zip(get_log_spiral_end_point(layer, document, viewport, TAU)) { - if mouse_position.distance(end_point) < POINT_RADIUS_HANDLE_SNAP_THRESHOLD { - self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, SpiralType::Logarithmic); + self.store_initial_parameters(layer, inner_radius, turns, outer_radius, mouse_position, spiral_type); responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default }); } } diff --git a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs index c11dc3110..575a54b2a 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -1,5 +1,5 @@ use super::ShapeToolData; -use crate::consts::{SPIRAL_INNER_RADIUS, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_TURNS_INDEX}; +use crate::consts::{SPIRAL_INNER_RADIUS_INDEX, SPIRAL_OUTER_RADIUS_INDEX, SPIRAL_START_ANGLE, SPIRAL_TURNS_INDEX}; use crate::messages::message::Message; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; @@ -232,33 +232,9 @@ pub fn extract_star_parameters(layer: Option, document: &Do Some((sides, radius_1, radius_2)) } -/// Extract the node input values of Archimedean spiral. -/// Returns an option of (Inner radius, Outer radius, Turns, ). -pub fn extract_arc_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(f64, f64, f64)> { - let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral")?; - - let Some(spiral_type) = get_spiral_type(layer, document) else { - return None; - }; - - if spiral_type == SpiralType::Archimedean { - let (Some(&TaggedValue::F64(inner_radius)), Some(&TaggedValue::F64(tightness)), Some(&TaggedValue::F64(turns))) = ( - node_inputs.get(SPIRAL_INNER_RADIUS)?.as_value(), - node_inputs.get(SPIRAL_OUTER_RADIUS_INDEX)?.as_value(), - node_inputs.get(SPIRAL_TURNS_INDEX)?.as_value(), - ) else { - return None; - }; - - return Some((inner_radius, tightness, turns)); - } - - None -} - /// Extract the node input values of Logarithmic spiral. /// Returns an option of (Start radius, Outer radius, Turns, ). -pub fn extract_log_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(f64, f64, f64)> { +pub fn extract_arc_or_log_spiral_parameters(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<(f64, f64, f64, f64)> { let node_inputs = NodeGraphLayer::new(layer, &document.network_interface).find_node_inputs("Spiral")?; let Some(spiral_type) = get_spiral_type(layer, document) else { @@ -266,15 +242,16 @@ pub fn extract_log_spiral_parameters(layer: LayerNodeIdentifier, document: &Docu }; if spiral_type == SpiralType::Logarithmic { - let (Some(&TaggedValue::F64(inner_radius)), Some(&TaggedValue::F64(tightness)), Some(&TaggedValue::F64(turns))) = ( - node_inputs.get(SPIRAL_INNER_RADIUS)?.as_value(), + let (Some(&TaggedValue::F64(inner_radius)), Some(&TaggedValue::F64(tightness)), Some(&TaggedValue::F64(turns)), Some(&TaggedValue::F64(start_angle))) = ( + node_inputs.get(SPIRAL_INNER_RADIUS_INDEX)?.as_value(), node_inputs.get(SPIRAL_OUTER_RADIUS_INDEX)?.as_value(), node_inputs.get(SPIRAL_TURNS_INDEX)?.as_value(), + node_inputs.get(SPIRAL_START_ANGLE)?.as_value(), ) else { return None; }; - return Some((inner_radius, tightness, turns)); + return Some((inner_radius, tightness, turns, start_angle)); } None @@ -291,7 +268,7 @@ pub fn get_spiral_type(layer: LayerNodeIdentifier, document: &DocumentMessageHan } pub fn get_arc_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option { - let Some((a, outer_radius, turns)) = extract_arc_spiral_parameters(layer, document) else { + let Some((a, outer_radius, turns, _)) = extract_arc_or_log_spiral_parameters(layer, document) else { return None; }; @@ -303,7 +280,7 @@ pub fn get_arc_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentM } pub fn get_log_spiral_end_point(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, viewport: DAffine2, theta: f64) -> Option { - let Some((_, outer_radius, turns)) = extract_log_spiral_parameters(layer, document) else { + let Some((_, outer_radius, turns, _)) = extract_arc_or_log_spiral_parameters(layer, document) else { return None; }; @@ -323,6 +300,14 @@ pub fn calculate_b(a: f64, turns: f64, outer_radius: f64, spiral_type: SpiralTyp } } +/// Returns a point on the given spiral type at angle `theta`. +pub fn spiral_point(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 { + match spiral_type { + SpiralType::Archimedean => archimedean_spiral_point(theta, a, b), + SpiralType::Logarithmic => log_spiral_point(theta, a, b), + } +} + /// Returns a point on an Archimedean spiral at angle `theta`. pub fn archimedean_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { let r = a + b * theta; diff --git a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs index ef1548cbc..b1f3b7c92 100644 --- a/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/spiral_shape.rs @@ -5,7 +5,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: 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::{InputConnector, NodeTemplate}; -use crate::messages::tool::common_functionality::gizmos::shape_gizmos::arc_spiral_inner_radius_handle::{RadiusGizmo, RadiusGizmoState}; +use crate::messages::tool::common_functionality::gizmos::shape_gizmos::spiral_inner_radius_handle::{RadiusGizmo, RadiusGizmoState}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::spiral_tightness_gizmo::{TightnessGizmo, TightnessGizmoState}; use crate::messages::tool::common_functionality::gizmos::shape_gizmos::spiral_turns_handle::{SpiralTurns, SpiralTurnsState}; use crate::messages::tool::common_functionality::graph_modification_utils; @@ -52,6 +52,12 @@ impl ShapeGizmoHandler for SpiralGizmoHandler { return; } + if self.turns_handle.hovered() && self.radius_handle.hovered() { + self.turns_handle.update_state(SpiralTurnsState::Dragging); + self.radius_handle.update_state(RadiusGizmoState::Inactive); + return; + } + if self.turns_handle.hovered() && self.tightness_handle.hovered() { self.turns_handle.update_state(SpiralTurnsState::Dragging); self.tightness_handle.update_state(TightnessGizmoState::Inactive); @@ -75,7 +81,7 @@ impl ShapeGizmoHandler for SpiralGizmoHandler { fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque) { if self.radius_handle.is_dragging() { - self.radius_handle.update_inner_radius(document, input, responses, drag_start); + self.radius_handle.update_inner_radius(drag_start, document, input, responses); } if self.turns_handle.is_dragging() { @@ -83,7 +89,7 @@ impl ShapeGizmoHandler for SpiralGizmoHandler { } if self.tightness_handle.is_dragging() { - self.tightness_handle.update_number_of_turns(document, input, responses, drag_start); + self.tightness_handle.update_outer_radius(document, input, responses, drag_start); } } @@ -91,32 +97,36 @@ impl ShapeGizmoHandler for SpiralGizmoHandler { &self, document: &DocumentMessageHandler, selected_spiral_layer: Option, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, ) { if self.radius_handle.hovered() && self.tightness_handle.hovered() { - self.radius_handle.overlays(document, selected_spiral_layer, input, mouse_position, overlay_context); + self.radius_handle.overlays(document, selected_spiral_layer, overlay_context); return; } - self.radius_handle.overlays(document, selected_spiral_layer, input, mouse_position, overlay_context); - self.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context); - self.tightness_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context); - // polygon_outline(selected_polygon_layer, document, overlay_context); + if (self.turns_handle.hovered() && self.radius_handle.hovered()) || (self.turns_handle.hovered() && self.tightness_handle.hovered()) { + self.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context); + return; + } + + self.radius_handle.overlays(document, selected_spiral_layer, overlay_context); + self.turns_handle.overlays(document, selected_spiral_layer, shape_editor, mouse_position, overlay_context); + self.tightness_handle.overlays(document, selected_spiral_layer, shape_editor, overlay_context); } fn dragging_overlays( &self, document: &DocumentMessageHandler, - input: &InputPreprocessorMessageHandler, + _input: &InputPreprocessorMessageHandler, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext, ) { if self.radius_handle.is_dragging() { - self.radius_handle.overlays(document, None, input, mouse_position, overlay_context); + self.radius_handle.overlays(document, None, overlay_context); } if self.radius_handle.is_dragging() { @@ -124,7 +134,7 @@ impl ShapeGizmoHandler for SpiralGizmoHandler { } if self.tightness_handle.is_dragging() { - self.tightness_handle.overlays(document, None, shape_editor, mouse_position, overlay_context); + self.tightness_handle.overlays(document, None, shape_editor, overlay_context); } } diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index 1f5106eed..fd83334b2 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -272,14 +272,14 @@ impl Subpath { ) } - pub fn new_spiral(a: f64, outer_radius: f64, turns: f64, delta_theta: f64, spiral_type: SpiralType) -> Self { + pub fn new_spiral(a: f64, outer_radius: f64, turns: f64, start_angle: f64, delta_theta: f64, spiral_type: SpiralType) -> Self { let mut manipulator_groups = Vec::new(); let mut prev_in_handle = None; - let theta_end = turns * std::f64::consts::TAU; + let theta_end = turns * std::f64::consts::TAU + start_angle; let b = calculate_b(a, turns, outer_radius, spiral_type); - let mut theta = 0.0; + let mut theta = start_angle; while theta < theta_end { let theta_next = f64::min(theta + delta_theta, theta_end); diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index e04d1546a..939d945f0 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -83,13 +83,21 @@ fn spiral( #[default(25)] outer_radius: f64, #[default(5.)] turns: f64, #[default(90.)] angle_offset: f64, + #[default(0.)] start_angle: f64, ) -> VectorDataTable { let spiral_type = match spiral_type { SpiralType::Archimedean => bezier_rs::SpiralType::Archimedean, SpiralType::Logarithmic => bezier_rs::SpiralType::Logarithmic, }; - VectorDataTable::new(VectorData::from_subpath(Subpath::new_spiral(inner_radius, outer_radius, turns, angle_offset.to_radians(), spiral_type))) + VectorDataTable::new(VectorData::from_subpath(Subpath::new_spiral( + inner_radius, + outer_radius, + turns, + start_angle.to_radians(), + angle_offset.to_radians(), + spiral_type, + ))) } #[node_macro::node(category("Vector: Shape"))]