mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
impl radius 1 and radius 2 for inner-radius and tightness,need to fix for turns
This commit is contained in:
parent
0ec46dbd12
commit
8e43744d7f
12 changed files with 420 additions and 374 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<LayerNodeIdentifier>,
|
||||
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<Message>) {
|
||||
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<LayerNodeIdentifier>,
|
||||
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<Message>, 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<LayerNodeIdentifier>,
|
||||
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<Message>) {
|
||||
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<LayerNodeIdentifier>, 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<Message>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<LayerNodeIdentifier>,
|
||||
|
|
@ -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<LayerNodeIdentifier>,
|
||||
_shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
pub fn overlays(&self, document: &DocumentMessageHandler, selected_spiral_layer: Option<LayerNodeIdentifier>, _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<Message>, 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<Message>,
|
||||
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<Message>) {
|
||||
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<Message>, 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<LayerNodeIdentifier>, 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<DVec2> {
|
||||
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<DVec2> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<Message>) {
|
||||
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<LayerNodeIdentifier>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -272,14 +272,14 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
|||
)
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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"))]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue