Add the 'Blend Shapes' and 'Origins to Polyline' nodes; generalize the 'Morph' node to >2 states (#3405)
* New nodes: 'Morph' and 'Multi-Morph' * Blend Shapes node * Add the 'Index Points' node * Fix failing test
This commit is contained in:
parent
d247b81966
commit
9eb8835bd5
|
|
@ -416,6 +416,539 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."),
|
description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."),
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Blend Shapes",
|
||||||
|
category: "Vector",
|
||||||
|
// [IMPORTS]2 -> 0[0:Floor]
|
||||||
|
// [0:Floor]0 -> 0[1:Subtract]
|
||||||
|
// "1: f64" -> 1[1:Subtract]
|
||||||
|
// "(): ()" -> 0[2:Instance Index]
|
||||||
|
// "0: u32" -> 1[2:Instance Index]
|
||||||
|
// [2:Instance Index]0 -> 0[3:Divide]
|
||||||
|
// [1:Subtract]0 -> 1[3:Divide]
|
||||||
|
// [IMPORTS]1 -> 0[4:Position on Path]
|
||||||
|
// [3:Divide]0 -> 1[4:Position on Path]
|
||||||
|
// "false: bool" -> 2[4:Position on Path]
|
||||||
|
// "false: bool" -> 3[4:Position on Path]
|
||||||
|
// "(): ()" -> 0[5:Instance Vector]
|
||||||
|
// [5:Instance Vector]0 -> 0[6:Reset Transform]
|
||||||
|
// "true: bool" -> 1[6:Reset Transform]
|
||||||
|
// "false: bool" -> 2[6:Reset Transform]
|
||||||
|
// "false: bool" -> 3[6:Reset Transform]
|
||||||
|
// [12:Flatten Vector]0 -> 0[7:Instance Map]
|
||||||
|
// [6:Reset Transform]0 -> 1[7:Instance Map]
|
||||||
|
// [7:Instance Map]0 -> 0[8:Morph]
|
||||||
|
// [15:Multiply]0 -> 1[8:Morph]
|
||||||
|
// [8:Morph]0 -> 0[9:Transform]
|
||||||
|
// [4:Position on Path]0 -> 1[9:Transform]
|
||||||
|
// "0: f64" -> 2[9:Transform]
|
||||||
|
// "(0, 0): DVec2" -> 3[9:Transform]
|
||||||
|
// "(0, 0): DVec2" -> 4[9:Transform]
|
||||||
|
// [IMPORTS]1 -> 0[10:Count Points]
|
||||||
|
// [10:Count Points]0 -> 0[11:Equals]
|
||||||
|
// [13:Count Elements]0 -> 1[11:Equals]
|
||||||
|
// [IMPORTS]0 -> 0[12:Flatten Vector]
|
||||||
|
// [12:Flatten Vector]0 -> 0[13:Count Elements]
|
||||||
|
// [13:Count Elements]0 -> 0[14:Subtract]
|
||||||
|
// "1: f64" -> 1[14:Subtract]
|
||||||
|
// [3:Divide]0 -> 0[15:Multiply]
|
||||||
|
// [14:Subtract]0 -> 1[15:Multiply]
|
||||||
|
// [12:Flatten Vector]0 -> 0[16:Morph]
|
||||||
|
// [15:Multiply]0 -> 1[16:Morph]
|
||||||
|
// [11:Equals]0 -> 0[17:Switch]
|
||||||
|
// [9:Transform]0 -> 1[17:Switch]
|
||||||
|
// [16:Morph]0 -> 2[17:Switch]
|
||||||
|
// [17:Switch]0 -> 0[18:Instance Repeat]
|
||||||
|
// [0:Floor]0 -> 1[18:Instance Repeat]
|
||||||
|
// [IMPORTS]3 -> 2[18:Instance Repeat]
|
||||||
|
// [18:Instance Repeat]0 -> 0[EXPORTS]
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
exports: vec![NodeInput::node(NodeId(18), 0)],
|
||||||
|
nodes: [
|
||||||
|
// 0: Floor
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::floor::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::import(concrete!(f64), 2)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 1: Subtract
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 2: Instance Index
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_index::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::U32(0), false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 3: Divide
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::divide::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 4: Position on Path
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::position_on_path::IDENTIFIER),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::import(generic!(T), 1),
|
||||||
|
NodeInput::node(NodeId(3), 0),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 5: Instance Vector
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_vector::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::None, false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 6: Reset Transform
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::reset_transform::IDENTIFIER),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::node(NodeId(5), 0),
|
||||||
|
NodeInput::value(TaggedValue::Bool(true), false),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 7: Instance Map
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_map::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(6), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 8: Morph
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(7), 0), NodeInput::node(NodeId(15), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 9: Transform
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::node(NodeId(8), 0),
|
||||||
|
NodeInput::node(NodeId(4), 0),
|
||||||
|
NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
|
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||||
|
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 10: Count Points
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::count_points::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::import(generic!(T), 1)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 11: Equals
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::equals::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(10), 0), NodeInput::node(NodeId(13), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 12: Flatten Vector
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::import(generic!(T), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 13: Count Elements
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(12), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 14: Subtract
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(13), 0), NodeInput::value(TaggedValue::F64(1.), false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 15: Multiply
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::multiply::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(3), 0), NodeInput::node(NodeId(14), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 16: Morph
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(15), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 17: Switch
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(logic::switch::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(11), 0), NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(16), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 18: Instance Repeat
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_repeat::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(0), 0), NodeInput::import(generic!(T), 3)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![
|
||||||
|
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||||
|
NodeInput::value(TaggedValue::Vector(Default::default()), true),
|
||||||
|
NodeInput::value(TaggedValue::F64(10.), false),
|
||||||
|
NodeInput::value(TaggedValue::Bool(Default::default()), false),
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
input_metadata: vec![("Content", "TODO").into(), ("Path", "TODO").into(), ("Count", "TODO").into(), ("Reverse", "TODO").into()],
|
||||||
|
output_names: vec!["Out".to_string()],
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||||
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
|
node_metadata: [
|
||||||
|
// 0: Floor
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 1: Subtract
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 2: Instance Index
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -2)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 3: Divide
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, -2)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 4: Position on Path
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, -3)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 5: Instance Vector
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 6: Reset Transform
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 2)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 7: Instance Map
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 8: Morph
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 9: Transform
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 10: Count Points
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 4)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 11: Equals
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 4)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 12: Flatten Vector
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 13: Count Elements
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 8)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 14: Subtract
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 8)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 15: Multiply
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 7)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 16: Morph
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 6)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 17: Switch
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 4)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 18: Instance Repeat
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: Cow::Borrowed("TODO"),
|
||||||
|
properties: None,
|
||||||
|
},
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Origins to Polyline",
|
||||||
|
category: "Vector",
|
||||||
|
// "(): ()" -> 0[0:Instance Vector]
|
||||||
|
// [0:Instance Vector]0 -> 0[1:Extract Transform]
|
||||||
|
// [1:Extract Transform]0 -> 0[2:Decompose Translation]
|
||||||
|
// [2:Decompose Translation]0 -> 0[3:Vec2 to Point]
|
||||||
|
// [IMPORTS]0 -> 0[4:Flatten Vector]
|
||||||
|
// [4:Flatten Vector]0 -> 0[5:Instance Map]
|
||||||
|
// [3:Vec2 to Point]0 -> 1[5:Instance Map]
|
||||||
|
// [5:Instance Map]0 -> 0[6: Flatten Path]
|
||||||
|
// [6:Flatten Path]0 -> 0[7:Points to Polyline]
|
||||||
|
// "false: bool" -> 1[7:Points to Polyline]
|
||||||
|
// [7:Points to Polyline]0 -> 0[EXPORTS]
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
exports: vec![NodeInput::node(NodeId(7), 0)],
|
||||||
|
nodes: [
|
||||||
|
// 0: Instance Vector
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_vector::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::None, false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 1: Extract Transform
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::extract_transform::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 2: Decompose Translation
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::decompose_translation::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 3: Vec2 to Point
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::vec_2_to_point::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 4: Flatten Vector
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::import(generic!(T), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 5: Instance Map
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_map::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(3), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 6: Flatten Path
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector::flatten_path::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(5), 0)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 7: Points to Polyline
|
||||||
|
DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(vector::points_to_polyline::IDENTIFIER),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(6), 0), NodeInput::value(TaggedValue::Bool(false), false)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::Vector(Default::default()), true)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
input_metadata: vec![("Vector", "TODO").into()],
|
||||||
|
output_names: vec!["Vector".to_string()],
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||||
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
|
node_metadata: [
|
||||||
|
// 0: Instance Vector
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 1: Extract Transform
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 2: Decompose Transform
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 3: Vec2 to Point
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 4: Flatten Vector
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 5: Instance Map
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 6: Flatten Path
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
// 7: Points to Polyline
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: Cow::Borrowed("TODO"),
|
||||||
|
properties: None,
|
||||||
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Load Image",
|
identifier: "Load Image",
|
||||||
category: "Web Request",
|
category: "Web Request",
|
||||||
|
|
@ -745,13 +1278,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
inputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||||
call_argument: generic!(T),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
inputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||||
call_argument: generic!(T),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ pub(crate) fn property_from_type(
|
||||||
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
|
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
|
||||||
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
|
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
|
||||||
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
||||||
|
Some("Progression") => progression_widget(default_info, number_input.min(min(0.))).into(),
|
||||||
Some("SignedInteger") => number_widget(default_info, number_input.int()).into(),
|
Some("SignedInteger") => number_widget(default_info, number_input.int()).into(),
|
||||||
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
||||||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
|
|
@ -794,6 +795,50 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
||||||
(first_widgets, second_widgets)
|
(first_widgets, second_widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Two number fields beside one another, the first for the fractional part (decimals, range mode) and the second for the whole part (integers, increment mode)
|
||||||
|
pub fn progression_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||||
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return Vec::new() };
|
||||||
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
if let Some(&TaggedValue::F64(x)) = input.as_non_exposed_value() {
|
||||||
|
let whole_part = x.trunc();
|
||||||
|
let fractional_part = x.fract();
|
||||||
|
|
||||||
|
widgets.extend_from_slice(&[
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
number_props
|
||||||
|
.clone()
|
||||||
|
.label("Progress")
|
||||||
|
.mode_range()
|
||||||
|
.min(0.)
|
||||||
|
.max(0.99999)
|
||||||
|
.value(Some(fractional_part))
|
||||||
|
.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(whole_part + input.value.unwrap()), node_id, index))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
TextLabel::new("+").widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
number_props
|
||||||
|
.label("Element #")
|
||||||
|
.mode_increment()
|
||||||
|
.min(0.)
|
||||||
|
.is_integer(true)
|
||||||
|
.value(Some(whole_part))
|
||||||
|
.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap() + fractional_part), node_id, index))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
widgets
|
||||||
|
}
|
||||||
|
|
||||||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1582,6 +1582,53 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate from the old source/target "Morph" node to the new vector table based "Morph" node.
|
||||||
|
// This doesn't produce exactly equivalent results in cases involving input vector tables with multiple rows.
|
||||||
|
// The old version would zip the source and target table rows, interpoleating each pair together.
|
||||||
|
// The migrated version will instead deeply flatten both merged tables and morph sequentially between all source vectors and all target vector elements.
|
||||||
|
// This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element.
|
||||||
|
if reference == "Morph" && inputs_count == 3 {
|
||||||
|
// Old signature:
|
||||||
|
// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction) -> Table<Vector> { ... }
|
||||||
|
//
|
||||||
|
// New signature:
|
||||||
|
// async fn morph<I: IntoGraphicTable>(_: impl Ctx, content: #[implementations(Table<Graphic>, Table<Vector>)] content: I, progression: Progression) -> Table<Vector> { ... }
|
||||||
|
|
||||||
|
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
|
||||||
|
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
|
||||||
|
|
||||||
|
// Create a new Merge node
|
||||||
|
let Some(merge_node_type) = resolve_document_node_type("Merge") else {
|
||||||
|
log::error!("Could not get merge node from definition when upgrading morph");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let merge_template = merge_node_type.default_node_template();
|
||||||
|
let merge_node_id = NodeId::new();
|
||||||
|
|
||||||
|
// Decide on the placement position of the new Merge node
|
||||||
|
let Some(morph_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else {
|
||||||
|
log::error!("Could not get position for morph node {node_id}");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let merge_position = morph_position + IVec2::new(-7, 0);
|
||||||
|
|
||||||
|
// Insert the new Merge node into the network
|
||||||
|
document.network_interface.insert_node(merge_node_id, merge_template, network_path);
|
||||||
|
document.network_interface.set_to_node_or_layer(&merge_node_id, network_path, false);
|
||||||
|
document.network_interface.shift_absolute_node_position(&merge_node_id, merge_position, network_path);
|
||||||
|
|
||||||
|
// Connect the old 'source' and 'target' inputs to the new Merge node
|
||||||
|
document.network_interface.set_input(&InputConnector::node(merge_node_id, 0), old_inputs[0].clone(), network_path);
|
||||||
|
document.network_interface.set_input(&InputConnector::node(merge_node_id, 1), old_inputs[1].clone(), network_path);
|
||||||
|
|
||||||
|
// Connect the new Merge node to the 'content' input of the Morph node
|
||||||
|
document
|
||||||
|
.network_interface
|
||||||
|
.set_input(&InputConnector::node(*node_id, 0), NodeInput::node(merge_node_id, 0), network_path);
|
||||||
|
// Connect the old 'progression' input to the new 'progression' input of the Morph node
|
||||||
|
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
|
||||||
|
}
|
||||||
|
|
||||||
// Add context features to nodes that don't have them (fine-grained context caching migration)
|
// Add context features to nodes that don't have them (fine-grained context caching migration)
|
||||||
if node.context_features == graphene_std::ContextDependencies::default()
|
if node.context_features == graphene_std::ContextDependencies::default()
|
||||||
&& let Some(reference) = document.network_interface.reference(node_id, network_path).cloned().flatten()
|
&& let Some(reference) = document.network_interface.reference(node_id, network_path).cloned().flatten()
|
||||||
|
|
|
||||||
|
|
@ -505,7 +505,7 @@
|
||||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||||
style:--layer-area-width={layerAreaWidth}
|
style:--layer-area-width={layerAreaWidth}
|
||||||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}, Position: (${node.position.x}, ${node.position.y})` : "")}
|
||||||
data-node={node.id}
|
data-node={node.id}
|
||||||
>
|
>
|
||||||
<div class="thumbnail">
|
<div class="thumbnail">
|
||||||
|
|
@ -650,7 +650,7 @@
|
||||||
style:--clip-path-id={`url(#${clipPathId})`}
|
style:--clip-path-id={`url(#${clipPathId})`}
|
||||||
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
|
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}, Position: (${node.position.x}, ${node.position.y})` : "")}
|
||||||
data-node={node.id}
|
data-node={node.id}
|
||||||
>
|
>
|
||||||
<!-- Primary row -->
|
<!-- Primary row -->
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ pub mod types {
|
||||||
pub type Length = f64;
|
pub type Length = f64;
|
||||||
/// 0 to 1
|
/// 0 to 1
|
||||||
pub type Fraction = f64;
|
pub type Fraction = f64;
|
||||||
|
/// Non-negative number broken into whole and fractional parts
|
||||||
|
pub type Progression = f64;
|
||||||
/// Signed integer that's actually a float because we don't handle type conversions very well yet
|
/// Signed integer that's actually a float because we don't handle type conversions very well yet
|
||||||
pub type SignedInteger = f64;
|
pub type SignedInteger = f64;
|
||||||
/// Unsigned integer
|
/// Unsigned integer
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::PI;
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue};
|
use core_types::registry::types::{Angle, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, Progression, SeedValue};
|
||||||
use core_types::table::{Table, TableRow, TableRowMut};
|
use core_types::table::{Table, TableRow, TableRowMut};
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::{Footprint, Transform};
|
||||||
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::Graphic;
|
|
||||||
use graphic_types::Vector;
|
use graphic_types::Vector;
|
||||||
use graphic_types::raster_types::{CPU, GPU, Raster};
|
use graphic_types::raster_types::{CPU, GPU, Raster};
|
||||||
|
use graphic_types::{Graphic, IntoGraphicTable};
|
||||||
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, PathEl, PathSeg, Shape};
|
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, PathEl, PathSeg, Shape};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
@ -1147,7 +1147,17 @@ async fn sample_polyline(
|
||||||
///
|
///
|
||||||
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
/// If multiple subpaths make up the path, the whole number part of the progression value selects the subpath and the decimal part determines the position along it.
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
|
||||||
async fn cut_path(_: impl Ctx, mut content: Table<Vector>, progression: Fraction, parameterized_distance: bool, reverse: bool) -> Table<Vector> {
|
async fn cut_path(
|
||||||
|
_: impl Ctx,
|
||||||
|
/// The path to insert a cut into.
|
||||||
|
mut content: Table<Vector>,
|
||||||
|
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
||||||
|
progression: Progression,
|
||||||
|
/// Swap the direction of the path.
|
||||||
|
reverse: bool,
|
||||||
|
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
|
||||||
|
parameterized_distance: bool,
|
||||||
|
) -> Table<Vector> {
|
||||||
let euclidian = !parameterized_distance;
|
let euclidian = !parameterized_distance;
|
||||||
|
|
||||||
let bezpaths = content
|
let bezpaths = content
|
||||||
|
|
@ -1252,7 +1262,7 @@ async fn position_on_path(
|
||||||
/// The path to traverse.
|
/// The path to traverse.
|
||||||
content: Table<Vector>,
|
content: Table<Vector>,
|
||||||
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
||||||
progression: Fraction,
|
progression: Progression,
|
||||||
/// Swap the direction of the path.
|
/// Swap the direction of the path.
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
|
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
|
||||||
|
|
@ -1291,7 +1301,7 @@ async fn tangent_on_path(
|
||||||
/// The path to traverse.
|
/// The path to traverse.
|
||||||
content: Table<Vector>,
|
content: Table<Vector>,
|
||||||
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
/// The factor from the start to the end of the path, 0–1 for one subpath, 1–2 for a second subpath, and so on.
|
||||||
progression: Fraction,
|
progression: Progression,
|
||||||
/// Swap the direction of the path.
|
/// Swap the direction of the path.
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
|
/// Traverse the path using each segment's Bézier curve parameterization instead of the Euclidean distance. Faster to compute but doesn't respect actual distances.
|
||||||
|
|
@ -1513,8 +1523,18 @@ async fn jitter_points(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Interpolates the geometry and styles between multiple vector layers, producing a single morphed vector shape.
|
||||||
|
///
|
||||||
|
/// Based on the progression value, adjacent vector elements are blended together. From 0 until 1, the first element (bottom layer) morphs into the second element (next layer up). From 1 until 2, it then morphs into the third element, and so on until progression is capped at the last element (top layer).
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction) -> Table<Vector> {
|
async fn morph<I: IntoGraphicTable + 'n + Send + Clone>(
|
||||||
|
_: impl Ctx,
|
||||||
|
/// The vector elements to interpolate between. Mixed graphic content is deeply flattened to keep only vector elements.
|
||||||
|
#[implementations(Table<Graphic>, Table<Vector>)]
|
||||||
|
content: I,
|
||||||
|
/// The factor from one vector element to the next in sequence. The whole number part selects the source element, and the decimal part determines the interpolation amount towards the next element.
|
||||||
|
progression: Progression,
|
||||||
|
) -> Table<Vector> {
|
||||||
/// Subdivides the last segment of the bezpath to until it appends 'count' number of segments.
|
/// Subdivides the last segment of the bezpath to until it appends 'count' number of segments.
|
||||||
fn make_new_segments(bezpath: &mut BezPath, count: usize) {
|
fn make_new_segments(bezpath: &mut BezPath, count: usize) {
|
||||||
let bezpath_segment_count = bezpath.segments().count();
|
let bezpath_segment_count = bezpath.segments().count();
|
||||||
|
|
@ -1558,127 +1578,147 @@ async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vecto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let time = time.clamp(0., 1.);
|
// Preserve original graphic table as upstream data so this group layer's nested layers can be edited by the tools.
|
||||||
|
let graphic_table_content = content.clone().into_graphic_table();
|
||||||
|
|
||||||
source
|
// If the input isn't a Table<Vector>, we convert it into one by flattening any Table<Graphic> content.
|
||||||
.into_iter()
|
let content = content.into_flattened_vector_table();
|
||||||
.zip(target.into_iter())
|
|
||||||
.map(|(source_row, target_row)| {
|
|
||||||
let mut vector = Vector::default();
|
|
||||||
|
|
||||||
// Lerp styles
|
// Determine source and target indices and interpolation time fraction
|
||||||
let vector_alpha_blending = source_row.alpha_blending.lerp(&target_row.alpha_blending, time as f32);
|
let progression = progression.max(0.);
|
||||||
vector.style = source_row.element.style.lerp(&target_row.element.style, time);
|
let source_index = progression.floor() as usize;
|
||||||
|
let time = progression.fract();
|
||||||
|
|
||||||
// Before and after transforms
|
// Not enough elements to interpolate between, so we return the input as-is
|
||||||
let source_transform = source_row.transform;
|
if content.len() <= 1 {
|
||||||
let target_transform = target_row.transform;
|
return content;
|
||||||
|
}
|
||||||
|
// Progression is at or past the last element, so we return the last element without interpolation
|
||||||
|
if source_index >= content.len() - 1 {
|
||||||
|
return content.into_iter().last().into_iter().collect();
|
||||||
|
}
|
||||||
|
|
||||||
// Before and after paths
|
// Interpolation between two elements
|
||||||
let source_bezpaths = source_row.element.stroke_bezpath_iter();
|
let mut content_iter = content.into_iter();
|
||||||
let target_bezpaths = target_row.element.stroke_bezpath_iter();
|
let source_row = content_iter.nth(source_index).unwrap();
|
||||||
|
let target_row = content_iter.next().unwrap();
|
||||||
|
|
||||||
for (mut source_bezpath, mut target_bezpath) in source_bezpaths.zip(target_bezpaths) {
|
let mut vector = Vector {
|
||||||
if source_bezpath.elements().is_empty() || target_bezpath.elements().is_empty() {
|
upstream_data: Some(graphic_table_content),
|
||||||
continue;
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lerp styles
|
||||||
|
let vector_alpha_blending = source_row.alpha_blending.lerp(&target_row.alpha_blending, time as f32);
|
||||||
|
vector.style = source_row.element.style.lerp(&target_row.element.style, time);
|
||||||
|
|
||||||
|
// Before and after transforms
|
||||||
|
let source_transform = source_row.transform;
|
||||||
|
let target_transform = target_row.transform;
|
||||||
|
|
||||||
|
// Before and after paths
|
||||||
|
let source_bezpaths = source_row.element.stroke_bezpath_iter();
|
||||||
|
let target_bezpaths = target_row.element.stroke_bezpath_iter();
|
||||||
|
|
||||||
|
for (mut source_bezpath, mut target_bezpath) in source_bezpaths.zip(target_bezpaths) {
|
||||||
|
if source_bezpath.elements().is_empty() || target_bezpath.elements().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
source_bezpath.apply_affine(Affine::new(source_transform.to_cols_array()));
|
||||||
|
target_bezpath.apply_affine(Affine::new(target_transform.to_cols_array()));
|
||||||
|
|
||||||
|
let target_segment_len = target_bezpath.segments().count();
|
||||||
|
let source_segment_len = source_bezpath.segments().count();
|
||||||
|
|
||||||
|
// Insert new segments to align the number of segments in sorce_bezpath and target_bezpath.
|
||||||
|
make_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len);
|
||||||
|
make_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len);
|
||||||
|
|
||||||
|
let source_segments = source_bezpath.segments().collect::<Vec<PathSeg>>();
|
||||||
|
let target_segments = target_bezpath.segments().collect::<Vec<PathSeg>>();
|
||||||
|
|
||||||
|
// Interpolate anchors and handles
|
||||||
|
for (i, (source_element, target_element)) in source_bezpath.elements_mut().iter_mut().zip(target_bezpath.elements_mut().iter_mut()).enumerate() {
|
||||||
|
match source_element {
|
||||||
|
PathEl::MoveTo(point) => *point = point.lerp(target_element.end_point().unwrap(), time),
|
||||||
|
PathEl::ClosePath => {}
|
||||||
|
elm => {
|
||||||
|
let mut source_segment = source_segments.get(i - 1).unwrap().to_cubic();
|
||||||
|
let target_segment = target_segments.get(i - 1).unwrap().to_cubic();
|
||||||
|
source_segment.p0 = source_segment.p0.lerp(target_segment.p0, time);
|
||||||
|
source_segment.p1 = source_segment.p1.lerp(target_segment.p1, time);
|
||||||
|
source_segment.p2 = source_segment.p2.lerp(target_segment.p2, time);
|
||||||
|
source_segment.p3 = source_segment.p3.lerp(target_segment.p3, time);
|
||||||
|
*elm = PathSeg::Cubic(source_segment).as_path_el();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
source_bezpath.apply_affine(Affine::new(source_transform.to_cols_array()));
|
vector.append_bezpath(source_bezpath.clone());
|
||||||
target_bezpath.apply_affine(Affine::new(target_transform.to_cols_array()));
|
}
|
||||||
|
|
||||||
let target_segment_len = target_bezpath.segments().count();
|
// Deal with unmatched extra paths by collapsing them
|
||||||
let source_segment_len = source_bezpath.segments().count();
|
let source_paths_count = source_row.element.stroke_bezpath_iter().count();
|
||||||
|
let target_paths_count = target_row.element.stroke_bezpath_iter().count();
|
||||||
|
let source_paths = source_row.element.stroke_bezpath_iter().skip(target_paths_count);
|
||||||
|
let target_paths = target_row.element.stroke_bezpath_iter().skip(source_paths_count);
|
||||||
|
|
||||||
// Insert new segments to align the number of segments in sorce_bezpath and target_bezpath.
|
for mut source_path in source_paths {
|
||||||
make_new_segments(&mut source_bezpath, target_segment_len.max(source_segment_len) - source_segment_len);
|
source_path.apply_affine(Affine::new(source_transform.to_cols_array()));
|
||||||
make_new_segments(&mut target_bezpath, source_segment_len.max(target_segment_len) - target_segment_len);
|
|
||||||
|
|
||||||
let source_segments = source_bezpath.segments().collect::<Vec<PathSeg>>();
|
// Skip if the path has no segments else get the point at the end of the path.
|
||||||
let target_segments = target_bezpath.segments().collect::<Vec<PathSeg>>();
|
let Some(end) = source_path.segments().last().map(|element| element.end()) else { continue };
|
||||||
|
|
||||||
// Interpolate anchors and handles
|
for element in source_path.elements_mut() {
|
||||||
for (i, (source_element, target_element)) in source_bezpath.elements_mut().iter_mut().zip(target_bezpath.elements_mut().iter_mut()).enumerate() {
|
match element {
|
||||||
match source_element {
|
PathEl::MoveTo(point) => *point = point.lerp(end, time),
|
||||||
PathEl::MoveTo(point) => *point = point.lerp(target_element.end_point().unwrap(), time),
|
PathEl::LineTo(point) => *point = point.lerp(end, time),
|
||||||
PathEl::ClosePath => {}
|
PathEl::QuadTo(point, point1) => {
|
||||||
elm => {
|
*point = point.lerp(end, time);
|
||||||
let mut source_segment = source_segments.get(i - 1).unwrap().to_cubic();
|
*point1 = point1.lerp(end, time);
|
||||||
let target_segment = target_segments.get(i - 1).unwrap().to_cubic();
|
|
||||||
source_segment.p0 = source_segment.p0.lerp(target_segment.p0, time);
|
|
||||||
source_segment.p1 = source_segment.p1.lerp(target_segment.p1, time);
|
|
||||||
source_segment.p2 = source_segment.p2.lerp(target_segment.p2, time);
|
|
||||||
source_segment.p3 = source_segment.p3.lerp(target_segment.p3, time);
|
|
||||||
*elm = PathSeg::Cubic(source_segment).as_path_el();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
PathEl::CurveTo(point, point1, point2) => {
|
||||||
vector.append_bezpath(source_bezpath.clone());
|
*point = point.lerp(end, time);
|
||||||
}
|
*point1 = point1.lerp(end, time);
|
||||||
|
*point2 = point2.lerp(end, time);
|
||||||
// Deal with unmatched extra paths by collapsing them
|
|
||||||
let source_paths_count = source_row.element.stroke_bezpath_iter().count();
|
|
||||||
let target_paths_count = target_row.element.stroke_bezpath_iter().count();
|
|
||||||
let source_paths = source_row.element.stroke_bezpath_iter().skip(target_paths_count);
|
|
||||||
let target_paths = target_row.element.stroke_bezpath_iter().skip(source_paths_count);
|
|
||||||
|
|
||||||
for mut source_path in source_paths {
|
|
||||||
source_path.apply_affine(Affine::new(source_transform.to_cols_array()));
|
|
||||||
|
|
||||||
// Skip if the path has no segments else get the point at the end of the path.
|
|
||||||
let Some(end) = source_path.segments().last().map(|element| element.end()) else { continue };
|
|
||||||
|
|
||||||
for element in source_path.elements_mut() {
|
|
||||||
match element {
|
|
||||||
PathEl::MoveTo(point) => *point = point.lerp(end, time),
|
|
||||||
PathEl::LineTo(point) => *point = point.lerp(end, time),
|
|
||||||
PathEl::QuadTo(point, point1) => {
|
|
||||||
*point = point.lerp(end, time);
|
|
||||||
*point1 = point1.lerp(end, time);
|
|
||||||
}
|
|
||||||
PathEl::CurveTo(point, point1, point2) => {
|
|
||||||
*point = point.lerp(end, time);
|
|
||||||
*point1 = point1.lerp(end, time);
|
|
||||||
*point2 = point2.lerp(end, time);
|
|
||||||
}
|
|
||||||
PathEl::ClosePath => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vector.append_bezpath(source_path);
|
PathEl::ClosePath => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
vector.append_bezpath(source_path);
|
||||||
|
}
|
||||||
|
|
||||||
for mut target_path in target_paths {
|
for mut target_path in target_paths {
|
||||||
target_path.apply_affine(Affine::new(source_transform.to_cols_array()));
|
target_path.apply_affine(Affine::new(source_transform.to_cols_array()));
|
||||||
|
|
||||||
// Skip if the path has no segments else get the point at the start of the path.
|
// Skip if the path has no segments else get the point at the start of the path.
|
||||||
let Some(start) = target_path.segments().next().map(|element| element.start()) else { continue };
|
let Some(start) = target_path.segments().next().map(|element| element.start()) else { continue };
|
||||||
|
|
||||||
for element in target_path.elements_mut() {
|
for element in target_path.elements_mut() {
|
||||||
match element {
|
match element {
|
||||||
PathEl::MoveTo(point) => *point = start.lerp(*point, time),
|
PathEl::MoveTo(point) => *point = start.lerp(*point, time),
|
||||||
PathEl::LineTo(point) => *point = start.lerp(*point, time),
|
PathEl::LineTo(point) => *point = start.lerp(*point, time),
|
||||||
PathEl::QuadTo(point, point1) => {
|
PathEl::QuadTo(point, point1) => {
|
||||||
*point = start.lerp(*point, time);
|
*point = start.lerp(*point, time);
|
||||||
*point1 = start.lerp(*point1, time);
|
*point1 = start.lerp(*point1, time);
|
||||||
}
|
|
||||||
PathEl::CurveTo(point, point1, point2) => {
|
|
||||||
*point = start.lerp(*point, time);
|
|
||||||
*point1 = start.lerp(*point1, time);
|
|
||||||
*point2 = start.lerp(*point2, time);
|
|
||||||
}
|
|
||||||
PathEl::ClosePath => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
vector.append_bezpath(target_path);
|
PathEl::CurveTo(point, point1, point2) => {
|
||||||
|
*point = start.lerp(*point, time);
|
||||||
|
*point1 = start.lerp(*point1, time);
|
||||||
|
*point2 = start.lerp(*point2, time);
|
||||||
|
}
|
||||||
|
PathEl::ClosePath => {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
vector.append_bezpath(target_path);
|
||||||
|
}
|
||||||
|
|
||||||
TableRow {
|
Table::new_from_row(TableRow {
|
||||||
element: vector,
|
element: vector,
|
||||||
alpha_blending: vector_alpha_blending,
|
alpha_blending: vector_alpha_blending,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Vector {
|
fn bevel_algorithm(mut vector: Vector, transform: DAffine2, distance: f64) -> Vector {
|
||||||
|
|
@ -2343,12 +2383,12 @@ mod test {
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn morph() {
|
async fn morph() {
|
||||||
let source = Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY);
|
let rectangle = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY));
|
||||||
let target = Rect::new(-100., -100., 0., 0.).to_path(DEFAULT_ACCURACY);
|
let rectangles = super::repeat(Footprint::default(), rectangle, DVec2::new(-100., -100.), 0., 2).await;
|
||||||
let morphed = super::morph(Footprint::default(), vector_node_from_bezpath(source), vector_node_from_bezpath(target), 0.5).await;
|
let morphed = super::morph(Footprint::default(), rectangles, 0.5).await;
|
||||||
let morphed = morphed.iter().next().unwrap().element;
|
let element = morphed.iter().next().unwrap().element;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&morphed.point_domain.positions()[..4],
|
&element.point_domain.positions()[..4],
|
||||||
vec![DVec2::new(-50., -50.), DVec2::new(50., -50.), DVec2::new(50., 50.), DVec2::new(-50., 50.)]
|
vec![DVec2::new(-50., -50.), DVec2::new(50., -50.), DVec2::new(50., 50.), DVec2::new(-50., 50.)]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue