Add transform-related nodes to improve transformation abilities (#2893)
* Improve transformation abilities with transform-related nodes * Fix Transform -> Merge and Transform -> Artboard connections
This commit is contained in:
parent
8f26c5c2ad
commit
561b671f8d
|
|
@ -1303,11 +1303,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Transform",
|
identifier: "Transform",
|
||||||
category: "General",
|
category: "Math: Transform",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||||
NodeInput::value(TaggedValue::F64(0.), false),
|
NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||||
|
|
@ -1317,7 +1317,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
manual_composition: Some(generic!(T)),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
|
|
@ -1374,7 +1374,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
input_metadata: vec![
|
input_metadata: vec![
|
||||||
("Vector Data", "TODO").into(),
|
("Value", "TODO").into(),
|
||||||
InputMetadata::with_name_description_override(
|
InputMetadata::with_name_description_override(
|
||||||
"Translation",
|
"Translation",
|
||||||
"TODO",
|
"TODO",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ use graphene_std::raster::{
|
||||||
};
|
};
|
||||||
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||||
use graphene_std::text::Font;
|
use graphene_std::text::Font;
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint};
|
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||||
use graphene_std::vector::VectorDataTable;
|
use graphene_std::vector::VectorDataTable;
|
||||||
use graphene_std::vector::misc::GridType;
|
use graphene_std::vector::misc::GridType;
|
||||||
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
||||||
|
|
@ -176,6 +176,7 @@ pub(crate) fn property_from_type(
|
||||||
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
||||||
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
||||||
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None, false),
|
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None, false),
|
||||||
|
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
|
||||||
// ==========================
|
// ==========================
|
||||||
// PRIMITIVE COLLECTION TYPES
|
// PRIMITIVE COLLECTION TYPES
|
||||||
// ==========================
|
// ==========================
|
||||||
|
|
@ -504,6 +505,126 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
||||||
last.clone()
|
last.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
||||||
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
|
let mut location_widgets = start_widgets(parameter_widgets_info);
|
||||||
|
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let mut rotation_widgets = vec![TextLabel::new("").widget_holder()];
|
||||||
|
add_blank_assist(&mut rotation_widgets);
|
||||||
|
rotation_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
||||||
|
add_blank_assist(&mut scale_widgets);
|
||||||
|
scale_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
|
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::new().into();
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = if let Some(&TaggedValue::DAffine2(transform)) = input.as_non_exposed_value() {
|
||||||
|
let translation = transform.translation;
|
||||||
|
let rotation = transform.decompose_rotation();
|
||||||
|
let scale = transform.decompose_scale();
|
||||||
|
|
||||||
|
location_widgets.extend_from_slice(&[
|
||||||
|
NumberInput::new(Some(translation.x))
|
||||||
|
.label("X")
|
||||||
|
.unit(" px")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |x: &NumberInput| {
|
||||||
|
let mut transform = transform;
|
||||||
|
transform.translation.x = x.value.unwrap_or(transform.translation.x);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
NumberInput::new(Some(translation.y))
|
||||||
|
.label("Y")
|
||||||
|
.unit(" px")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |y: &NumberInput| {
|
||||||
|
let mut transform = transform;
|
||||||
|
transform.translation.y = y.value.unwrap_or(transform.translation.y);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
rotation_widgets.extend_from_slice(&[NumberInput::new(Some(rotation.to_degrees()))
|
||||||
|
.unit("°")
|
||||||
|
.mode(NumberInputMode::Range)
|
||||||
|
.range_min(Some(-180.))
|
||||||
|
.range_max(Some(180.))
|
||||||
|
.on_update(update_value(
|
||||||
|
move |r: &NumberInput| {
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(scale, r.value.map(|r| r.to_radians()).unwrap_or(rotation), translation);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder()]);
|
||||||
|
|
||||||
|
scale_widgets.extend_from_slice(&[
|
||||||
|
NumberInput::new(Some(scale.x))
|
||||||
|
.label("W")
|
||||||
|
.unit("x")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |w: &NumberInput| {
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(DVec2::new(w.value.unwrap_or(scale.x), scale.y), rotation, translation);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
NumberInput::new(Some(scale.y))
|
||||||
|
.label("H")
|
||||||
|
.unit("x")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |h: &NumberInput| {
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(DVec2::new(scale.x, h.value.unwrap_or(scale.y)), rotation, translation);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
LayoutGroup::Row { widgets: location_widgets },
|
||||||
|
LayoutGroup::Row { widgets: rotation_widgets },
|
||||||
|
LayoutGroup::Row { widgets: scale_widgets },
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![LayoutGroup::Row { widgets: location_widgets }]
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((last, rest)) = widgets.split_last() {
|
||||||
|
*extra_widgets = rest.to_vec();
|
||||||
|
last.clone()
|
||||||
|
} else {
|
||||||
|
LayoutGroup::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {
|
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
|
|
@ -1345,9 +1466,9 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
||||||
|
|
||||||
pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let text = if context.network_interface.is_layer(&node_id, context.selection_network_path) {
|
let text = if context.network_interface.is_layer(&node_id, context.selection_network_path) {
|
||||||
"Layer has no properties"
|
"Layer has no parameters"
|
||||||
} else {
|
} else {
|
||||||
"Node has no properties"
|
"Node has no parameters"
|
||||||
};
|
};
|
||||||
string_properties(text)
|
string_properties(text)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ impl FrontendGraphDataType {
|
||||||
| TaggedValue::OptionalDVec2(_)
|
| TaggedValue::OptionalDVec2(_)
|
||||||
| TaggedValue::F64Array4(_)
|
| TaggedValue::F64Array4(_)
|
||||||
| TaggedValue::VecF64(_)
|
| TaggedValue::VecF64(_)
|
||||||
| TaggedValue::VecDVec2(_) => Self::Number,
|
| TaggedValue::VecDVec2(_)
|
||||||
|
| TaggedValue::DAffine2(_) => Self::Number,
|
||||||
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
|
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
|
||||||
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
||||||
_ => Self::General,
|
_ => Self::General,
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,11 @@ impl From<RasterDataTable<GPU>> for GraphicGroupTable {
|
||||||
Self::new(GraphicElement::RasterDataGPU(raster_data_table))
|
Self::new(GraphicElement::RasterDataGPU(raster_data_table))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<DAffine2> for GraphicGroupTable {
|
||||||
|
fn from(_: DAffine2) -> Self {
|
||||||
|
GraphicGroupTable::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
|
/// The possible forms of graphical content held in a Vec by the `elements` field of [`GraphicElement`].
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
|
||||||
|
|
@ -118,6 +123,12 @@ impl Default for GraphicElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<DAffine2> for GraphicElement {
|
||||||
|
fn from(_: DAffine2) -> Self {
|
||||||
|
GraphicElement::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GraphicElement {
|
impl GraphicElement {
|
||||||
pub fn as_group(&self) -> Option<&GraphicGroupTable> {
|
pub fn as_group(&self) -> Option<&GraphicGroupTable> {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -351,6 +362,7 @@ async fn to_element<Data: Into<GraphicElement> + 'n>(
|
||||||
VectorDataTable,
|
VectorDataTable,
|
||||||
RasterDataTable<CPU>,
|
RasterDataTable<CPU>,
|
||||||
RasterDataTable<GPU>,
|
RasterDataTable<GPU>,
|
||||||
|
DAffine2,
|
||||||
)]
|
)]
|
||||||
data: Data,
|
data: Data,
|
||||||
) -> GraphicElement {
|
) -> GraphicElement {
|
||||||
|
|
@ -463,6 +475,7 @@ async fn to_artboard<Data: Into<GraphicGroupTable> + 'n>(
|
||||||
Context -> VectorDataTable,
|
Context -> VectorDataTable,
|
||||||
Context -> RasterDataTable<CPU>,
|
Context -> RasterDataTable<CPU>,
|
||||||
Context -> RasterDataTable<GPU>,
|
Context -> RasterDataTable<GPU>,
|
||||||
|
Context -> DAffine2,
|
||||||
)]
|
)]
|
||||||
contents: impl Node<Context<'static>, Output = Data>,
|
contents: impl Node<Context<'static>, Output = Data>,
|
||||||
label: String,
|
label: String,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::AlphaBlending;
|
use crate::AlphaBlending;
|
||||||
|
use crate::transform::ApplyTransform;
|
||||||
use crate::uuid::NodeId;
|
use crate::uuid::NodeId;
|
||||||
use dyn_any::StaticType;
|
use dyn_any::StaticType;
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
|
|
@ -136,6 +137,20 @@ impl<T: Hash> Hash for Instances<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ApplyTransform for Instances<T> {
|
||||||
|
fn apply_transform(&mut self, modification: &DAffine2) {
|
||||||
|
for transform in &mut self.transform {
|
||||||
|
*transform *= *modification;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left_apply_transform(&mut self, modification: &DAffine2) {
|
||||||
|
for transform in &mut self.transform {
|
||||||
|
*transform = *modification * *transform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: PartialEq> PartialEq for Instances<T> {
|
impl<T: PartialEq> PartialEq for Instances<T> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
|
self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,20 @@ use glam::{DAffine2, DMat2, DVec2};
|
||||||
|
|
||||||
pub trait Transform {
|
pub trait Transform {
|
||||||
fn transform(&self) -> DAffine2;
|
fn transform(&self) -> DAffine2;
|
||||||
|
|
||||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||||
pivot
|
pivot
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompose_scale(&self) -> DVec2 {
|
fn decompose_scale(&self) -> DVec2 {
|
||||||
DVec2::new(
|
DVec2::new(self.transform().transform_vector2(DVec2::X).length(), self.transform().transform_vector2(DVec2::Y).length())
|
||||||
self.transform().transform_vector2((1., 0.).into()).length(),
|
}
|
||||||
self.transform().transform_vector2((0., 1.).into()).length(),
|
|
||||||
)
|
/// Requires that the transform does not contain any skew.
|
||||||
|
fn decompose_rotation(&self) -> f64 {
|
||||||
|
let rotation_matrix = (self.transform() * DAffine2::from_scale(self.decompose_scale().recip())).matrix2;
|
||||||
|
let rotation = -rotation_matrix.mul_vec2(DVec2::X).angle_to(DVec2::X);
|
||||||
|
if rotation == -0. { 0. } else { rotation }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,12 +147,21 @@ impl std::hash::Hash for Footprint {
|
||||||
|
|
||||||
pub trait ApplyTransform {
|
pub trait ApplyTransform {
|
||||||
fn apply_transform(&mut self, modification: &DAffine2);
|
fn apply_transform(&mut self, modification: &DAffine2);
|
||||||
|
fn left_apply_transform(&mut self, modification: &DAffine2);
|
||||||
}
|
}
|
||||||
impl<T: TransformMut> ApplyTransform for T {
|
impl<T: TransformMut> ApplyTransform for T {
|
||||||
fn apply_transform(&mut self, &modification: &DAffine2) {
|
fn apply_transform(&mut self, &modification: &DAffine2) {
|
||||||
*self.transform_mut() = self.transform() * modification
|
*self.transform_mut() = self.transform() * modification
|
||||||
}
|
}
|
||||||
|
fn left_apply_transform(&mut self, &modification: &DAffine2) {
|
||||||
|
*self.transform_mut() = modification * self.transform()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl ApplyTransform for () {
|
impl ApplyTransform for DVec2 {
|
||||||
fn apply_transform(&mut self, &_modification: &DAffine2) {}
|
fn apply_transform(&mut self, modification: &DAffine2) {
|
||||||
|
*self = modification.transform_point2(*self);
|
||||||
|
}
|
||||||
|
fn left_apply_transform(&mut self, modification: &DAffine2) {
|
||||||
|
*self = modification.inverse().transform_point2(*self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,22 @@ use core::f64;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn transform<T: 'n + 'static>(
|
async fn transform<T: ApplyTransform + 'n + 'static>(
|
||||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
|
Context -> DAffine2,
|
||||||
|
Context -> DVec2,
|
||||||
Context -> VectorDataTable,
|
Context -> VectorDataTable,
|
||||||
Context -> GraphicGroupTable,
|
Context -> GraphicGroupTable,
|
||||||
Context -> RasterDataTable<CPU>,
|
Context -> RasterDataTable<CPU>,
|
||||||
Context -> RasterDataTable<GPU>,
|
Context -> RasterDataTable<GPU>,
|
||||||
)]
|
)]
|
||||||
transform_target: impl Node<Context<'static>, Output = Instances<T>>,
|
value: impl Node<Context<'static>, Output = T>,
|
||||||
translate: DVec2,
|
translate: DVec2,
|
||||||
rotate: f64,
|
rotate: f64,
|
||||||
scale: DVec2,
|
scale: DVec2,
|
||||||
skew: DVec2,
|
skew: DVec2,
|
||||||
) -> Instances<T> {
|
) -> T {
|
||||||
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
|
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
|
||||||
|
|
||||||
let footprint = ctx.try_footprint().copied();
|
let footprint = ctx.try_footprint().copied();
|
||||||
|
|
@ -31,11 +33,9 @@ async fn transform<T: 'n + 'static>(
|
||||||
ctx = ctx.with_footprint(footprint);
|
ctx = ctx.with_footprint(footprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut transform_target = transform_target.eval(ctx.into_context()).await;
|
let mut transform_target = value.eval(ctx.into_context()).await;
|
||||||
|
|
||||||
for data_transform in transform_target.instance_mut_iter() {
|
transform_target.left_apply_transform(&matrix);
|
||||||
*data_transform.transform = matrix * *data_transform.transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
transform_target
|
transform_target
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +52,40 @@ fn replace_transform<Data, TransformInput: Transform>(
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Math: Transform"), path(graphene_core::vector))]
|
||||||
|
async fn extract_transform<T>(
|
||||||
|
_: impl Ctx,
|
||||||
|
#[implementations(
|
||||||
|
GraphicGroupTable,
|
||||||
|
VectorDataTable,
|
||||||
|
RasterDataTable<CPU>,
|
||||||
|
RasterDataTable<GPU>,
|
||||||
|
)]
|
||||||
|
vector_data: Instances<T>,
|
||||||
|
) -> DAffine2 {
|
||||||
|
vector_data.instance_ref_iter().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
|
fn invert_transform(_: impl Ctx, transform: DAffine2) -> DAffine2 {
|
||||||
|
transform.inverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
|
fn decompose_translation(_: impl Ctx, transform: DAffine2) -> DVec2 {
|
||||||
|
transform.translation
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
|
fn decompose_rotation(_: impl Ctx, transform: DAffine2) -> f64 {
|
||||||
|
transform.decompose_rotation()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Math: Transform"))]
|
||||||
|
fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
|
||||||
|
transform.decompose_scale()
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Debug"))]
|
#[node_macro::node(category("Debug"))]
|
||||||
async fn boundless_footprint<T: 'n + 'static>(
|
async fn boundless_footprint<T: 'n + 'static>(
|
||||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||||
|
|
|
||||||
|
|
@ -418,7 +418,7 @@ impl Hash for VectorModification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node that applies a procedural modification to some [`VectorData`].
|
/// Applies a diff modification to a vector path.
|
||||||
#[node_macro::node(category(""))]
|
#[node_macro::node(category(""))]
|
||||||
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
|
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
|
||||||
if vector_data.is_empty() {
|
if vector_data.is_empty() {
|
||||||
|
|
@ -437,6 +437,23 @@ async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modificat
|
||||||
vector_data
|
vector_data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies the vector path's local transformation to its geometry and resets it to the identity.
|
||||||
|
#[node_macro::node(category("Vector"))]
|
||||||
|
async fn apply_transform(_ctx: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
|
||||||
|
for vector_data_instance in vector_data.instance_mut_iter() {
|
||||||
|
let vector_data = vector_data_instance.instance;
|
||||||
|
let transform = *vector_data_instance.transform;
|
||||||
|
|
||||||
|
for (_, point) in vector_data.point_domain.positions_mut() {
|
||||||
|
*point = transform.transform_point2(*point);
|
||||||
|
}
|
||||||
|
|
||||||
|
*vector_data_instance.transform = DAffine2::IDENTITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector_data
|
||||||
|
}
|
||||||
|
|
||||||
// Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
|
// Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
use serde::de::{SeqAccess, Visitor};
|
use serde::de::{SeqAccess, Visitor};
|
||||||
|
|
|
||||||
|
|
@ -1874,7 +1874,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
|
||||||
}
|
}
|
||||||
|
|
||||||
if segment_domain_length != sorted_segments.len() {
|
if segment_domain_length != sorted_segments.len() {
|
||||||
for i in 0..segment_domain_length as usize {
|
for i in 0..segment_domain_length {
|
||||||
if !sorted_segments.contains(&i) {
|
if !sorted_segments.contains(&i) {
|
||||||
sorted_segments.push(i);
|
sorted_segments.push(i);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use glam::DVec2;
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_core::gradient::GradientStops;
|
use graphene_core::gradient::GradientStops;
|
||||||
use graphene_core::registry::types::{Fraction, Percentage, TextArea};
|
use graphene_core::registry::types::{Fraction, Percentage, PixelSize, TextArea};
|
||||||
|
use graphene_core::transform::Footprint;
|
||||||
use graphene_core::{Color, Ctx, num_traits};
|
use graphene_core::{Color, Ctx, num_traits};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use math_parser::ast;
|
use math_parser::ast;
|
||||||
|
|
@ -107,11 +108,11 @@ fn subtract<U: Sub<T>, T>(
|
||||||
fn multiply<U: Mul<T>, T>(
|
fn multiply<U: Mul<T>, T>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
/// The left-hand side of the multiplication operation.
|
/// The left-hand side of the multiplication operation.
|
||||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
#[implementations(f64, f32, u32, f64, DVec2, DVec2, DAffine2)]
|
||||||
multiplier: U,
|
multiplier: U,
|
||||||
/// The right-hand side of the multiplication operation.
|
/// The right-hand side of the multiplication operation.
|
||||||
#[default(1.)]
|
#[default(1.)]
|
||||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
#[implementations(f64, f32, u32, DVec2, f64, DVec2, DAffine2)]
|
||||||
multiplicand: T,
|
multiplicand: T,
|
||||||
) -> <U as Mul<T>>::Output {
|
) -> <U as Mul<T>>::Output {
|
||||||
multiplier * multiplicand
|
multiplier * multiplicand
|
||||||
|
|
@ -681,6 +682,16 @@ fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String {
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels.
|
||||||
|
#[node_macro::node(category("Value"))]
|
||||||
|
fn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100., 100.)] resolution: PixelSize) -> Footprint {
|
||||||
|
Footprint {
|
||||||
|
transform,
|
||||||
|
resolution: resolution.max(DVec2::ONE).as_uvec2(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Math: Vector"))]
|
#[node_macro::node(category("Math: Vector"))]
|
||||||
fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
|
fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
|
||||||
vector_a.dot(vector_b)
|
vector_a.dot(vector_b)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use dyn_any::StaticType;
|
use dyn_any::StaticType;
|
||||||
use glam::{DVec2, IVec2, UVec2};
|
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||||
use graph_craft::document::value::RenderOutput;
|
use graph_craft::document::value::RenderOutput;
|
||||||
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
use graph_craft::proto::{NodeConstructor, TypeErasedBox};
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
|
|
@ -52,6 +52,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => String]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => IVec2]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DVec2]),
|
||||||
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => DAffine2]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => bool]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => bool]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => f64]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => f64]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => u32]),
|
||||||
|
|
@ -166,7 +167,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
||||||
|
|
||||||
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
for (id, entry) in graphene_core::registry::NODE_REGISTRY.lock().unwrap().iter() {
|
||||||
for (constructor, types) in entry.iter() {
|
for (constructor, types) in entry.iter() {
|
||||||
map.entry(id.clone().into()).or_default().insert(types.clone(), *constructor);
|
map.entry(id.clone()).or_default().insert(types.clone(), *constructor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue