Skip to content

Commit adfcff7

Browse files
optozoraxKeavon
andauthored
New node: Arc (#2470)
* init * add closed and slice options * Make it work beyond -360 to 360 degrees * Switch "closed" and "slice" to ArcType enum * Update default ranges --------- Co-authored-by: Keavon Chambers <[email protected]>
1 parent 33de539 commit adfcff7

File tree

7 files changed

+131
-9
lines changed

7 files changed

+131
-9
lines changed

editor/src/messages/portfolio/document/node_graph/node_properties.rs

+27
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use graphene_std::application_io::TextureFrameTable;
2424
use graphene_std::ops::XY;
2525
use graphene_std::transform::Footprint;
2626
use graphene_std::vector::VectorDataTable;
27+
use graphene_std::vector::misc::ArcType;
2728
use graphene_std::vector::misc::{BooleanOperation, GridType};
2829
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
2930
use graphene_std::{GraphicGroupTable, RasterFrame};
@@ -208,6 +209,7 @@ pub(crate) fn property_from_type(
208209
Some(x) if x == TypeId::of::<GridType>() => grid_type_widget(document_node, node_id, index, name, description, true),
209210
Some(x) if x == TypeId::of::<LineCap>() => line_cap_widget(document_node, node_id, index, name, description, true),
210211
Some(x) if x == TypeId::of::<LineJoin>() => line_join_widget(document_node, node_id, index, name, description, true),
212+
Some(x) if x == TypeId::of::<ArcType>() => arc_type_widget(document_node, node_id, index, name, description, true),
211213
Some(x) if x == TypeId::of::<FillType>() => vec![
212214
DropdownInput::new(vec![vec![
213215
MenuListEntry::new("Solid")
@@ -1219,6 +1221,31 @@ pub fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: us
12191221
LayoutGroup::Row { widgets }
12201222
}
12211223

1224+
pub fn arc_type_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, description: &str, blank_assist: bool) -> LayoutGroup {
1225+
let mut widgets = start_widgets(document_node, node_id, index, name, description, FrontendGraphDataType::General, blank_assist);
1226+
let Some(input) = document_node.inputs.get(index) else {
1227+
log::warn!("A widget failed to be built because its node's input index is invalid.");
1228+
return LayoutGroup::Row { widgets: vec![] };
1229+
};
1230+
if let Some(&TaggedValue::ArcType(arc_type)) = input.as_non_exposed_value() {
1231+
let entries = [("Open", ArcType::Open), ("Closed", ArcType::Closed), ("Pie Slice", ArcType::PieSlice)]
1232+
.into_iter()
1233+
.map(|(name, val)| {
1234+
RadioEntryData::new(format!("{val:?}"))
1235+
.label(name)
1236+
.on_update(update_value(move |_| TaggedValue::ArcType(val), node_id, index))
1237+
.on_commit(commit_value)
1238+
})
1239+
.collect();
1240+
1241+
widgets.extend_from_slice(&[
1242+
Separator::new(SeparatorType::Unrelated).widget_holder(),
1243+
RadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_holder(),
1244+
]);
1245+
}
1246+
LayoutGroup::Row { widgets }
1247+
}
1248+
12221249
pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, description: &str, color_button: ColorInput, blank_assist: bool) -> LayoutGroup {
12231250
let mut widgets = start_widgets(document_node, node_id, index, name, description, FrontendGraphDataType::General, blank_assist);
12241251

editor/src/messages/tool/tool_messages/pen_tool.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ impl PenToolData {
11391139
let extension_choice = should_extend(document, viewport, tolerance, selected_nodes.selected_layers(document.metadata()), preferences);
11401140
if let Some((layer, point, position)) = extension_choice {
11411141
self.current_layer = Some(layer);
1142-
self.extend_existing_path(document, layer, point, position, responses);
1142+
self.extend_existing_path(document, layer, point, position);
11431143
return;
11441144
}
11451145

@@ -1191,7 +1191,7 @@ impl PenToolData {
11911191
}
11921192

11931193
/// Perform extension of an existing path
1194-
fn extend_existing_path(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, point: PointId, position: DVec2, responses: &mut VecDeque<Message>) {
1194+
fn extend_existing_path(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, point: PointId, position: DVec2) {
11951195
let vector_data = document.network_interface.compute_modified_vector(layer);
11961196
let (handle_start, in_segment) = if let Some(vector_data) = &vector_data {
11971197
vector_data

libraries/bezier-rs/src/subpath/core.rs

+60
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,66 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
293293
Self::new(manipulator_groups, true)
294294
}
295295

296+
/// Constructs an arc by a `radius`, `angle_start` and `angle_size`. Angles must be in radians. Slice option makes it look like pie or pacman.
297+
pub fn new_arc(radius: f64, start_angle: f64, sweep_angle: f64, arc_type: ArcType) -> Self {
298+
// Prevents glitches from numerical imprecision that have been observed during animation playback after about a minute
299+
let start_angle = start_angle % (std::f64::consts::TAU * 2.);
300+
let sweep_angle = sweep_angle % (std::f64::consts::TAU * 2.);
301+
302+
let original_start_angle = start_angle;
303+
let sweep_angle_sign = sweep_angle.signum();
304+
305+
let mut start_angle = 0.;
306+
let mut sweep_angle = sweep_angle.abs();
307+
308+
if (sweep_angle / std::f64::consts::TAU).floor() as u32 % 2 == 0 {
309+
sweep_angle %= std::f64::consts::TAU;
310+
} else {
311+
start_angle = sweep_angle % std::f64::consts::TAU;
312+
sweep_angle = std::f64::consts::TAU - start_angle;
313+
}
314+
315+
sweep_angle *= sweep_angle_sign;
316+
start_angle *= sweep_angle_sign;
317+
start_angle += original_start_angle;
318+
319+
let closed = arc_type == ArcType::Closed;
320+
let slice = arc_type == ArcType::PieSlice;
321+
322+
let center = DVec2::new(0., 0.);
323+
let segments = (sweep_angle.abs() / (std::f64::consts::PI / 4.)).ceil().max(1.) as usize;
324+
let step = sweep_angle / segments as f64;
325+
let factor = 4. / 3. * (step / 2.).sin() / (1. + (step / 2.).cos());
326+
327+
let mut manipulator_groups = Vec::with_capacity(segments);
328+
let mut prev_in_handle = None;
329+
let mut prev_end = DVec2::new(0., 0.);
330+
331+
for i in 0..segments {
332+
let start_angle = start_angle + step * i as f64;
333+
let end_angle = start_angle + step;
334+
let start_vec = DVec2::from_angle(start_angle);
335+
let end_vec = DVec2::from_angle(end_angle);
336+
337+
let start = center + radius * start_vec;
338+
let end = center + radius * end_vec;
339+
340+
let handle_start = start + start_vec.perp() * radius * factor;
341+
let handle_end = end - end_vec.perp() * radius * factor;
342+
343+
manipulator_groups.push(ManipulatorGroup::new(start, prev_in_handle, Some(handle_start)));
344+
prev_in_handle = Some(handle_end);
345+
prev_end = end;
346+
}
347+
manipulator_groups.push(ManipulatorGroup::new(prev_end, prev_in_handle, None));
348+
349+
if slice {
350+
manipulator_groups.push(ManipulatorGroup::new(center, None, None));
351+
}
352+
353+
Self::new(manipulator_groups, closed || slice)
354+
}
355+
296356
/// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex.
297357
pub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self {
298358
let sides = sides.max(3);

libraries/bezier-rs/src/subpath/structs.rs

+7
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,10 @@ pub enum AppendType {
137137
IgnoreStart,
138138
SmoothJoin(f64),
139139
}
140+
141+
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
142+
pub enum ArcType {
143+
Open,
144+
Closed,
145+
PieSlice,
146+
}

node-graph/gcore/src/vector/generator_nodes.rs

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use super::misc::{AsU64, GridType};
1+
use super::misc::{ArcType, AsU64, GridType};
22
use super::{PointId, SegmentId, StrokeId};
33
use crate::Ctx;
4+
use crate::registry::types::Angle;
45
use crate::vector::{HandleId, VectorData, VectorDataTable};
56
use bezier_rs::Subpath;
67
use glam::DVec2;
@@ -36,15 +37,32 @@ impl CornerRadius for [f64; 4] {
3637
}
3738

3839
#[node_macro::node(category("Vector: Shape"))]
39-
fn circle(
40+
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
41+
let radius = radius.abs();
42+
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
43+
}
44+
45+
#[node_macro::node(category("Vector: Shape"))]
46+
fn arc(
4047
_: impl Ctx,
4148
_primary: (),
42-
#[default(50.)]
43-
#[min(0.)]
44-
radius: f64,
49+
#[default(50.)] radius: f64,
50+
start_angle: Angle,
51+
#[default(270.)]
52+
#[range((0., 360.))]
53+
sweep_angle: Angle,
54+
arc_type: ArcType,
4555
) -> VectorDataTable {
46-
let radius = radius.max(0.);
47-
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
56+
VectorDataTable::new(VectorData::from_subpath(Subpath::new_arc(
57+
radius,
58+
start_angle / 360. * std::f64::consts::TAU,
59+
sweep_angle / 360. * std::f64::consts::TAU,
60+
match arc_type {
61+
ArcType::Open => bezier_rs::ArcType::Open,
62+
ArcType::Closed => bezier_rs::ArcType::Closed,
63+
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
64+
},
65+
)))
4866
}
4967

5068
#[node_macro::node(category("Vector: Shape"))]

node-graph/gcore/src/vector/misc.rs

+9
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,12 @@ pub enum GridType {
9292
Rectangular,
9393
Isometric,
9494
}
95+
96+
#[repr(C)]
97+
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
98+
pub enum ArcType {
99+
#[default]
100+
Open,
101+
Closed,
102+
PieSlice,
103+
}

node-graph/graph-craft/src/document/value.rs

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ tagged_value! {
214214
RelativeAbsolute(graphene_core::raster::RelativeAbsolute),
215215
SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice),
216216
GridType(graphene_core::vector::misc::GridType),
217+
ArcType(graphene_core::vector::misc::ArcType),
217218
LineCap(graphene_core::vector::style::LineCap),
218219
LineJoin(graphene_core::vector::style::LineJoin),
219220
FillType(graphene_core::vector::style::FillType),

0 commit comments

Comments
 (0)