diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs index c6fef1fbda..8501057b59 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/number_of_points_dial.rs @@ -13,6 +13,7 @@ use crate::messages::tool::common_functionality::shapes::shape_utility::{extract use glam::{DAffine2, DVec2}; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::vector::generator_nodes::calculate_effective_radius; use std::collections::VecDeque; use std::f64::consts::TAU; @@ -68,11 +69,12 @@ impl NumberOfPointsDial { } // Polygon - if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + if let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); - let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius); + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); + let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, effective_radius); if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { self.layer = Some(layer); @@ -122,7 +124,7 @@ impl NumberOfPointsDial { } // Polygon - if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + if let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); let center = viewport.transform_point2(DVec2::ZERO); @@ -131,10 +133,11 @@ impl NumberOfPointsDial { { return; } - let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius); + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); + let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, effective_radius); - if inside_polygon(viewport, sides, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { - self.draw_spokes(center, viewport, sides, radius, overlay_context); + if inside_polygon(viewport, sides, effective_radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD { + self.draw_spokes(center, viewport, sides, effective_radius, overlay_context); } } } @@ -144,10 +147,12 @@ impl NumberOfPointsDial { }; // Get the star's greater radius or polygon's radius, as well as the number of sides - let Some((sides, radius)) = extract_star_parameters(Some(layer), document) - .map(|(sides, r1, r2)| (sides, r1.max(r2))) - .or_else(|| extract_polygon_parameters(Some(layer), document)) - else { + let Some((sides, radius)) = extract_star_parameters(Some(layer), document).map(|(sides, r1, r2)| (sides, r1.max(r2))).or_else(|| { + extract_polygon_parameters(Some(layer), document).map(|(sides, radius, is_inner_radius)| { + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); + (sides, effective_radius) + }) + }) else { return; }; diff --git a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs index d72414f48a..2ada6448a9 100644 --- a/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs +++ b/editor/src/messages/tool/common_functionality/gizmos/shape_gizmos/point_radius_handle.rs @@ -9,11 +9,13 @@ 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::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline}; -use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position}; +use crate::messages::tool::common_functionality::shapes::shape_utility::{ + draw_snapping_ticks, extract_polygon_parameters, extract_star_parameters, polygon_edge_midpoint, polygon_outline, polygon_vertex_position, star_outline, star_vertex_position, +}; use glam::DVec2; use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; +use graphene_std::vector::calculate_effective_radius; use std::collections::VecDeque; use std::f64::consts::{FRAC_1_SQRT_2, FRAC_PI_4, PI, SQRT_2}; @@ -87,11 +89,16 @@ impl PointRadiusHandle { } // Draw the point handle gizmo for the polygon shape - if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + if let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); for i in 0..sides { - let point = polygon_vertex_position(viewport, i as i32, sides, radius); + let point = if is_inner_radius { + polygon_edge_midpoint(viewport, i as i32, sides, radius) + } else { + polygon_vertex_position(viewport, i as i32, sides, effective_radius) + }; let center = viewport.transform_point2(DVec2::ZERO); // If the user zooms out such that shape is very small hide the gizmo @@ -130,8 +137,13 @@ impl PointRadiusHandle { } // Polygon - if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { - let point = polygon_vertex_position(viewport, self.point as i32, sides, radius); + if let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) { + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); + let point = if is_inner_radius { + polygon_edge_midpoint(viewport, self.point as i32, sides, radius) + } else { + polygon_vertex_position(viewport, self.point as i32, sides, effective_radius) + }; if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. { self.update_state(PointRadiusHandleState::Inactive); @@ -166,11 +178,16 @@ impl PointRadiusHandle { } // Draw the point handle gizmo for the Polygon shape - if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { + if let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) { let viewport = document.metadata().transform_to_viewport(layer); + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); for i in 0..sides { - let point = polygon_vertex_position(viewport, i as i32, sides, radius); + let point = if is_inner_radius { + polygon_edge_midpoint(viewport, i as i32, sides, radius) + } else { + polygon_vertex_position(viewport, i as i32, sides, effective_radius) + }; let center = viewport.transform_point2(DVec2::ZERO); // If the user zooms out such that shape is very small hide the gizmo @@ -211,8 +228,13 @@ impl PointRadiusHandle { } // Polygon - if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) { - let point = polygon_vertex_position(viewport, self.point as i32, sides, radius); + if let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) { + let effective_radius = calculate_effective_radius(radius, sides, is_inner_radius); + let point = if is_inner_radius { + polygon_edge_midpoint(viewport, self.point as i32, sides, radius) + } else { + polygon_vertex_position(viewport, self.point as i32, sides, effective_radius) + }; let Some(direction) = (point - center).try_normalize() else { return }; @@ -421,13 +443,14 @@ impl PointRadiusHandle { }; let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer); - let center = viewport_transform.transform_point2(DVec2::ZERO); + let radius_index = self.radius_index; let original_radius = self.initial_radius; - let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - viewport_transform.inverse().transform_point2(drag_start); - let radius = drag_start - center; + let drag_start = viewport_transform.inverse().transform_point2(drag_start); + let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - drag_start; + let radius = drag_start; let projection = delta.project_onto(radius); let sign = radius.dot(delta).signum(); 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 46baf0f1d4..c54f3310c7 100644 --- a/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs +++ b/editor/src/messages/tool/common_functionality/shapes/shape_utility.rs @@ -17,6 +17,7 @@ use graph_craft::document::NodeInput; use graph_craft::document::value::TaggedValue; use graphene_std::NodeInputDecleration; use graphene_std::subpath::{self, Subpath}; +use graphene_std::vector::calculate_effective_radius; use graphene_std::vector::click_target::ClickTargetType; use graphene_std::vector::misc::{ArcType, GridType, SpiralType, dvec2_to_point}; use kurbo::{BezPath, PathEl, Shape}; @@ -265,15 +266,26 @@ pub fn extract_star_parameters(layer: Option, document: &Do /// Extract the node input values of Polygon. /// Returns an option of (sides, radius). -pub fn extract_polygon_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64)> { +pub fn extract_polygon_parameters(layer: Option, document: &DocumentMessageHandler) -> Option<(u32, f64, bool)> { let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER))?; - let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value()) else { + let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(radius)), Some(&TaggedValue::Bool(is_inner_radius))) = + (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value()) + else { return None; }; - Some((n, radius)) + Some((n, radius, is_inner_radius)) +} + +pub fn polygon_edge_midpoint(viewport: DAffine2, edge_index: i32, n: u32, radius: f64) -> DVec2 { + let angle = ((edge_index as f64 + 0.5) * TAU) / (n as f64); + + viewport.transform_point2(DVec2 { + x: radius * angle.sin(), + y: -radius * angle.cos(), + }) } /// Extract the node input values of an arc. @@ -399,14 +411,14 @@ pub fn star_outline(layer: Option, document: &DocumentMessa /// Outlines the geometric shape made by polygon-node pub fn polygon_outline(layer: Option, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) { let Some(layer) = layer else { return }; - let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) else { + let Some((sides, radius, is_inner_radius)) = extract_polygon_parameters(Some(layer), document) else { return; }; let viewport = document.metadata().transform_to_viewport(layer); let points = sides as u64; - let radius: f64 = radius * 2.; + let radius: f64 = calculate_effective_radius(radius, sides, is_inner_radius) * 2.; let subpath: Vec = vec![ClickTargetType::Subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))]; diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 6527c9c3a6..393f15bb02 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -158,10 +158,20 @@ fn regular_polygon( #[unit(" px")] #[default(50)] radius: f64, + #[default(false)] is_inner_radius: bool, ) -> Table { let points = sides.as_u64(); - let radius: f64 = radius * 2.; - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))) + let effective_radius = calculate_effective_radius(radius, sides.as_u64() as u32, is_inner_radius) * 2.; + Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_regular_polygon(DVec2::splat(-effective_radius), points, effective_radius))) +} + +pub fn calculate_effective_radius(radius: f64, sides: u32, is_inner: bool) -> f64 { + if is_inner { + let cosine = (std::f64::consts::PI / sides as f64).cos(); + if cosine.abs() > 1e-6 { radius / cosine } else { radius } + } else { + radius + } } /// Generates an n-pointed star shape with inner and outer points at chosen radii from the center.