Copy to Points node: add scale/rotation randomization parameters (#1592)
* WIP, transforms broken with rot/scale * Transform around bounding box centre * Add units and tooltips --------- Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
parent
51d6d4d30e
commit
8fa46ba63a
File diff suppressed because one or more lines are too long
|
|
@ -2644,11 +2644,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
name: "Copy to Points",
|
name: "Copy to Points",
|
||||||
category: "Vector",
|
category: "Vector",
|
||||||
// TODO: Wrap this implementation with a document node that has a cache node so the output is cached?
|
// TODO: Wrap this implementation with a document node that has a cache node so the output is cached?
|
||||||
implementation: NodeImplementation::proto("graphene_core::vector::CopyToPoints<_, _>"),
|
implementation: NodeImplementation::proto("graphene_core::vector::CopyToPoints<_, _, _, _, _>"),
|
||||||
manual_composition: Some(concrete!(Footprint)),
|
manual_composition: Some(concrete!(Footprint)),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Points", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
DocumentInputType::value("Points", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||||
DocumentInputType::value("Instance", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
DocumentInputType::value("Instance", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||||
|
DocumentInputType::value("Random Scale Min", TaggedValue::F32(1.), false),
|
||||||
|
DocumentInputType::value("Random Scale Max", TaggedValue::F32(1.), false),
|
||||||
|
DocumentInputType::value("Random Rotation", TaggedValue::F32(0.), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||||
properties: node_properties::copy_to_points_properties,
|
properties: node_properties::copy_to_points_properties,
|
||||||
|
|
|
||||||
|
|
@ -2036,15 +2036,39 @@ pub fn circular_repeat_properties(document_node: &DocumentNode, node_id: NodeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let instance = vector_widget(document_node, node_id, 1, "Spacing", true);
|
let instance = vector_widget(document_node, node_id, 1, "Instance", true);
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: instance }]
|
let random_scale_min = number_widget(
|
||||||
|
document_node,
|
||||||
|
node_id,
|
||||||
|
2,
|
||||||
|
"Random Scale Min",
|
||||||
|
NumberInput::default().min(0.).mode_range().range_min(Some(0.)).range_max(Some(2.)).unit("x"),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
let random_scale_max = number_widget(
|
||||||
|
document_node,
|
||||||
|
node_id,
|
||||||
|
3,
|
||||||
|
"Random Scale Max",
|
||||||
|
NumberInput::default().min(0.).mode_range().range_min(Some(0.)).range_max(Some(2.)).unit("x"),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
let random_rotation = number_widget(document_node, node_id, 4, "Random Rotation", NumberInput::default().min(0.).max(360.).mode_range().unit("°"), true);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
LayoutGroup::Row { widgets: instance }.with_tooltip("Artwork to be copied and placed at each point"),
|
||||||
|
LayoutGroup::Row { widgets: random_scale_min }.with_tooltip("Minimum range of randomized sizes given to each instance"),
|
||||||
|
LayoutGroup::Row { widgets: random_scale_max }.with_tooltip("Maximum range of randomized sizes given to each instance"),
|
||||||
|
LayoutGroup::Row { widgets: random_rotation }.with_tooltip("Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise"),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let spacing = number_widget(document_node, node_id, 1, "Spacing", NumberInput::default().min(1.), true);
|
let spacing = number_widget(document_node, node_id, 1, "Spacing", NumberInput::default().min(1.).unit(" px"), true);
|
||||||
let start_offset = number_widget(document_node, node_id, 2, "Start Offset", NumberInput::default().min(0.), true);
|
let start_offset = number_widget(document_node, node_id, 2, "Start Offset", NumberInput::default().min(0.).unit(" px"), true);
|
||||||
let stop_offset = number_widget(document_node, node_id, 3, "Stop Offset", NumberInput::default().min(0.), true);
|
let stop_offset = number_widget(document_node, node_id, 3, "Stop Offset", NumberInput::default().min(0.).unit(" px"), true);
|
||||||
let adaptive_spacing = bool_widget(document_node, node_id, 4, "Adaptive Spacing", true);
|
let adaptive_spacing = bool_widget(document_node, node_id, 4, "Adaptive Spacing", true);
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
|
|
|
||||||
|
|
@ -244,7 +244,7 @@ pub(crate) async fn transform_vector_data<Fut: Future>(
|
||||||
where
|
where
|
||||||
Fut::Output: TransformMut,
|
Fut::Output: TransformMut,
|
||||||
{
|
{
|
||||||
// TOOD: This is hack and might break for Vector data because the pivot may be incorrect
|
// TODO: This is hack and might break for Vector data because the pivot may be incorrect
|
||||||
let transform = DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
let transform = DAffine2::from_scale_angle_translation(scale, rotate as f64, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||||
if !footprint.ignore_modifications {
|
if !footprint.ignore_modifications {
|
||||||
let pivot_transform = DAffine2::from_translation(pivot);
|
let pivot_transform = DAffine2::from_translation(pivot);
|
||||||
|
|
|
||||||
|
|
@ -175,9 +175,12 @@ impl ConcatElement for GraphicGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct CopyToPoints<Points, Instance> {
|
pub struct CopyToPoints<Points, Instance, RandomScaleMin, RandomScaleMax, RandomRotation> {
|
||||||
points: Points,
|
points: Points,
|
||||||
instance: Instance,
|
instance: Instance,
|
||||||
|
random_scale_min: RandomScaleMin,
|
||||||
|
random_scale_max: RandomScaleMax,
|
||||||
|
random_rotation: RandomRotation,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(CopyToPoints)]
|
#[node_macro::node_fn(CopyToPoints)]
|
||||||
|
|
@ -185,19 +188,36 @@ async fn copy_to_points<I: GraphicElementRendered + Default + ConcatElement + Tr
|
||||||
footprint: Footprint,
|
footprint: Footprint,
|
||||||
points: impl Node<Footprint, Output = FP>,
|
points: impl Node<Footprint, Output = FP>,
|
||||||
instance: impl Node<Footprint, Output = FI>,
|
instance: impl Node<Footprint, Output = FI>,
|
||||||
|
random_scale_min: f32,
|
||||||
|
random_scale_max: f32,
|
||||||
|
random_rotation: f32,
|
||||||
) -> I {
|
) -> I {
|
||||||
let points = self.points.eval(footprint).await;
|
let points = self.points.eval(footprint).await;
|
||||||
let instance = self.instance.eval(footprint).await;
|
let instance = self.instance.eval(footprint).await;
|
||||||
|
let random_scale_min = random_scale_min as f64;
|
||||||
|
let random_scale_max = random_scale_max as f64;
|
||||||
|
let random_rotation = random_rotation as f64;
|
||||||
|
|
||||||
let points_list = points.subpaths.iter().flat_map(|s| s.anchors());
|
let points_list = points.subpaths.iter().flat_map(|s| s.anchors());
|
||||||
|
|
||||||
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
|
let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default();
|
||||||
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
|
let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]);
|
||||||
|
|
||||||
|
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||||
|
|
||||||
let mut result = I::default();
|
let mut result = I::default();
|
||||||
for point in points_list {
|
for point in points_list {
|
||||||
let translation = points.transform.transform_point2(point) + instance_center;
|
let center_transform = DAffine2::from_translation(instance_center);
|
||||||
result.concat(&instance, DAffine2::from_translation(translation));
|
|
||||||
|
let translation = points.transform.transform_point2(point);
|
||||||
|
|
||||||
|
let rotation = (rng.gen::<f64>() - 0.5) * random_rotation;
|
||||||
|
let rotation = rotation / 360. * std::f64::consts::TAU;
|
||||||
|
|
||||||
|
let scale = random_scale_min + rng.gen::<f64>() * (random_scale_max - random_scale_min);
|
||||||
|
let scale = DVec2::splat(scale);
|
||||||
|
|
||||||
|
result.concat(&instance, DAffine2::from_scale_angle_translation(scale, rotation, translation) * center_transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
|
||||||
|
|
@ -732,8 +732,8 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
)],
|
)],
|
||||||
register_node!(graphene_std::raster::SampleNode<_>, input: Footprint, params: [ImageFrame<Color>]),
|
register_node!(graphene_std::raster::SampleNode<_>, input: Footprint, params: [ImageFrame<Color>]),
|
||||||
register_node!(graphene_std::raster::MandelbrotNode, input: Footprint, params: []),
|
register_node!(graphene_std::raster::MandelbrotNode, input: Footprint, params: []),
|
||||||
async_node!(graphene_core::vector::CopyToPoints<_, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData]),
|
async_node!(graphene_core::vector::CopyToPoints<_, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, Footprint => VectorData, () => f32, () => f32, () => f32]),
|
||||||
async_node!(graphene_core::vector::CopyToPoints<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => VectorData, Footprint => GraphicGroup]),
|
async_node!(graphene_core::vector::CopyToPoints<_, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => VectorData, Footprint => GraphicGroup, () => f32, () => f32, () => f32]),
|
||||||
async_node!(graphene_core::vector::SamplePoints<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => f32, () => f32, () => f32, () => bool, Footprint => Vec<Vec<f64>>]),
|
async_node!(graphene_core::vector::SamplePoints<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => f32, () => f32, () => f32, () => bool, Footprint => Vec<Vec<f64>>]),
|
||||||
register_node!(graphene_core::vector::PoissonDiskPoints<_>, input: VectorData, params: [f32]),
|
register_node!(graphene_core::vector::PoissonDiskPoints<_>, input: VectorData, params: [f32]),
|
||||||
register_node!(graphene_core::vector::LengthsOfSegmentsOfSubpaths, input: VectorData, params: []),
|
register_node!(graphene_core::vector::LengthsOfSegmentsOfSubpaths, input: VectorData, params: []),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue