mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more) (#2610)
* Improve instancing nodes (make them output group data, add 'Instance Repeat', fix Flatten Vector Elements click targets, and more) * Fix test? * Fix more tests? * Fix moar test?? * Clean up instance method naming
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::application_io::{ImageTexture, TextureFrameTable};
|
||||
use crate::instances::Instances;
|
||||
use crate::instances::{Instance, Instances};
|
||||
use crate::raster::BlendMode;
|
||||
use crate::raster::image::{Image, ImageFrameTable};
|
||||
use crate::transform::TransformMut;
|
||||
@@ -67,10 +67,12 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
|
||||
EitherFormat::OldGraphicGroup(old) => {
|
||||
let mut graphic_group_table = GraphicGroupTable::empty();
|
||||
for (graphic_element, source_node_id) in old.elements {
|
||||
let last = graphic_group_table.push(graphic_element);
|
||||
*last.source_node_id = source_node_id;
|
||||
*last.transform = old.transform;
|
||||
*last.alpha_blending = old.alpha_blending;
|
||||
graphic_group_table.push(Instance {
|
||||
instance: graphic_element,
|
||||
transform: old.transform,
|
||||
alpha_blending: old.alpha_blending,
|
||||
source_node_id,
|
||||
});
|
||||
}
|
||||
graphic_group_table
|
||||
}
|
||||
@@ -78,12 +80,14 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
|
||||
// Try to deserialize as either table format
|
||||
if let Ok(old_table) = serde_json::from_value::<OldGraphicGroupTable>(value.clone()) {
|
||||
let mut graphic_group_table = GraphicGroupTable::empty();
|
||||
for instance in old_table.instances() {
|
||||
for instance in old_table.instance_ref_iter() {
|
||||
for (graphic_element, source_node_id) in &instance.instance.elements {
|
||||
let new_row = graphic_group_table.push(graphic_element.clone());
|
||||
*new_row.source_node_id = *source_node_id;
|
||||
*new_row.transform = *instance.transform;
|
||||
*new_row.alpha_blending = *instance.alpha_blending;
|
||||
graphic_group_table.push(Instance {
|
||||
instance: graphic_element.clone(),
|
||||
transform: *instance.transform,
|
||||
alpha_blending: *instance.alpha_blending,
|
||||
source_node_id: *source_node_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
graphic_group_table
|
||||
@@ -276,8 +280,12 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
|
||||
EitherFormat::ArtboardGroup(artboard_group) => {
|
||||
let mut table = ArtboardGroupTable::empty();
|
||||
for (artboard, source_node_id) in artboard_group.artboards {
|
||||
let pushed = table.push(artboard);
|
||||
*pushed.source_node_id = source_node_id;
|
||||
table.push(Instance {
|
||||
instance: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id,
|
||||
});
|
||||
}
|
||||
table
|
||||
}
|
||||
@@ -290,38 +298,18 @@ pub type ArtboardGroupTable = Instances<Artboard>;
|
||||
#[node_macro::node(category(""))]
|
||||
async fn layer(_: impl Ctx, mut stack: GraphicGroupTable, element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
let pushed = stack.push(element);
|
||||
*pushed.source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
let source_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
stack.push(Instance {
|
||||
instance: element,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id,
|
||||
});
|
||||
|
||||
stack
|
||||
}
|
||||
|
||||
// // TODO: Once we have nicely working spreadsheet tables, test this and make it nicely user-facing and move it from "Debug" to "General"
|
||||
// #[node_macro::node(category("Debug"))]
|
||||
// async fn concatenate<T: Clone>(
|
||||
// _: impl Ctx,
|
||||
// #[implementations(
|
||||
// GraphicGroupTable,
|
||||
// VectorDataTable,
|
||||
// ImageFrameTable<Color>,
|
||||
// TextureFrameTable,
|
||||
// )]
|
||||
// from: Instances<T>,
|
||||
// #[expose]
|
||||
// #[implementations(
|
||||
// GraphicGroupTable,
|
||||
// VectorDataTable,
|
||||
// ImageFrameTable<Color>,
|
||||
// TextureFrameTable,
|
||||
// )]
|
||||
// mut to: Instances<T>,
|
||||
// ) -> Instances<T> {
|
||||
// for instance in from.instances() {
|
||||
// to.push_instance(instance);
|
||||
// }
|
||||
// to
|
||||
// }
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn to_element<Data: Into<GraphicElement> + 'n>(
|
||||
_: impl Ctx,
|
||||
@@ -354,7 +342,7 @@ async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
|
||||
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
|
||||
// TODO: Avoid mutable reference, instead return a new GraphicGroupTable?
|
||||
fn flatten_group(output_group_table: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool, recursion_depth: usize) {
|
||||
for current_instance in current_group_table.instances() {
|
||||
for current_instance in current_group_table.instance_ref_iter() {
|
||||
let current_element = current_instance.instance.clone();
|
||||
let reference = *current_instance.source_node_id;
|
||||
|
||||
@@ -364,7 +352,7 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
|
||||
// If we're allowed to recurse, flatten any GraphicGroups we encounter
|
||||
GraphicElement::GraphicGroup(mut current_element) if recurse => {
|
||||
// Apply the parent group's transform to all child elements
|
||||
for graphic_element in current_element.instances_mut() {
|
||||
for graphic_element in current_element.instance_mut_iter() {
|
||||
*graphic_element.transform = *current_instance.transform * *graphic_element.transform;
|
||||
}
|
||||
|
||||
@@ -372,10 +360,12 @@ async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: boo
|
||||
}
|
||||
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
|
||||
_ => {
|
||||
let pushed = output_group_table.push(current_element);
|
||||
*pushed.source_node_id = reference;
|
||||
// Apply the parent group's transform to the leaf element
|
||||
*pushed.transform = *current_instance.transform * *pushed.transform;
|
||||
output_group_table.push(Instance {
|
||||
instance: current_element,
|
||||
transform: *current_instance.transform,
|
||||
alpha_blending: *current_instance.alpha_blending,
|
||||
source_node_id: reference,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,8 +417,12 @@ async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artb
|
||||
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
|
||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
|
||||
let pushed = artboards.push(artboard);
|
||||
*pushed.source_node_id = encapsulating_node_id;
|
||||
artboards.push(Instance {
|
||||
instance: artboard,
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
source_node_id: encapsulating_node_id,
|
||||
});
|
||||
|
||||
artboards
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ pub trait GraphicElementRendered {
|
||||
|
||||
impl GraphicElementRendered for GraphicGroupTable {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| {
|
||||
@@ -325,12 +325,12 @@ impl GraphicElementRendered for GraphicGroupTable {
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let transform = transform * *instance.transform;
|
||||
let alpha_blending = *instance.alpha_blending;
|
||||
|
||||
let mut layer = false;
|
||||
if let Some(bounds) = self.instances().filter_map(|element| element.instance.bounding_box(transform)).reduce(Quad::combine_bounds) {
|
||||
if let Some(bounds) = self.instance_ref_iter().filter_map(|element| element.instance.bounding_box(transform)).reduce(Quad::combine_bounds) {
|
||||
let blend_mode = match render_params.view_mode {
|
||||
ViewMode::Outline => peniko::Mix::Normal,
|
||||
_ => alpha_blending.blend_mode.into(),
|
||||
@@ -356,13 +356,13 @@ impl GraphicElementRendered for GraphicGroupTable {
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances()
|
||||
self.instance_ref_iter()
|
||||
.filter_map(|element| element.instance.bounding_box(transform * *element.transform))
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
if let Some(element_id) = instance.source_node_id {
|
||||
let mut footprint = footprint;
|
||||
footprint.transform *= *instance.transform;
|
||||
@@ -374,7 +374,7 @@ impl GraphicElementRendered for GraphicGroupTable {
|
||||
if let Some(graphic_group_id) = element_id {
|
||||
let mut all_upstream_click_targets = Vec::new();
|
||||
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let mut new_click_targets = Vec::new();
|
||||
instance.instance.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
@@ -390,7 +390,7 @@ impl GraphicElementRendered for GraphicGroupTable {
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
instance.instance.add_upstream_click_targets(&mut new_click_targets);
|
||||
@@ -404,11 +404,11 @@ impl GraphicElementRendered for GraphicGroupTable {
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.instances().any(|instance| instance.instance.contains_artboard())
|
||||
self.instance_ref_iter().any(|instance| instance.instance.contains_artboard())
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.instance.new_ids_from_hash(*instance.source_node_id);
|
||||
}
|
||||
}
|
||||
@@ -420,7 +420,7 @@ impl GraphicElementRendered for GraphicGroupTable {
|
||||
|
||||
impl GraphicElementRendered for VectorDataTable {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let multiplied_transform = render.transform * *instance.transform;
|
||||
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
|
||||
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
@@ -469,7 +469,7 @@ impl GraphicElementRendered for VectorDataTable {
|
||||
use vello::kurbo::{Cap, Join};
|
||||
use vello::peniko;
|
||||
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let multiplied_transform = parent_transform * *instance.transform;
|
||||
let has_real_stroke = instance.instance.style.stroke().filter(|stroke| stroke.weight() > 0.);
|
||||
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
@@ -614,7 +614,7 @@ impl GraphicElementRendered for VectorDataTable {
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances()
|
||||
self.instance_ref_iter()
|
||||
.flat_map(|instance| {
|
||||
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
|
||||
|
||||
@@ -633,7 +633,7 @@ impl GraphicElementRendered for VectorDataTable {
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance_transform = self.transform();
|
||||
|
||||
for instance in self.instances().map(|instance| instance.instance) {
|
||||
for instance in self.instance_ref_iter().map(|instance| instance.instance) {
|
||||
if let Some(element_id) = element_id {
|
||||
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
|
||||
let filled = instance.style.fill() != &Fill::None;
|
||||
@@ -661,7 +661,7 @@ impl GraphicElementRendered for VectorDataTable {
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let stroke_width = instance.instance.style.stroke().as_ref().map_or(0., Stroke::weight);
|
||||
let filled = instance.instance.style.fill() != &Fill::None;
|
||||
let fill = |mut subpath: bezier_rs::Subpath<_>| {
|
||||
@@ -679,7 +679,7 @@ impl GraphicElementRendered for VectorDataTable {
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
@@ -796,42 +796,42 @@ impl GraphicElementRendered for Artboard {
|
||||
|
||||
impl GraphicElementRendered for ArtboardGroupTable {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for artboard in self.instances() {
|
||||
for artboard in self.instance_ref_iter() {
|
||||
artboard.instance.render_svg(render, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
instance.instance.render_to_vello(scene, transform, context, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
|
||||
self.instance_ref_iter().filter_map(|instance| instance.instance.bounding_box(transform)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
instance.instance.collect_metadata(metadata, footprint, *instance.source_node_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
instance.instance.add_upstream_click_targets(click_targets);
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.instances().count() > 0
|
||||
self.instance_ref_iter().count() > 0
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for ImageFrameTable<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, _render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let transform = *instance.transform * render.transform;
|
||||
|
||||
let image = &instance.instance;
|
||||
@@ -870,7 +870,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, _render_params: &RenderParams) {
|
||||
use vello::peniko;
|
||||
|
||||
for instance in self.instances() {
|
||||
for instance in self.instance_ref_iter() {
|
||||
let image = &instance.instance;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
@@ -883,7 +883,7 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances()
|
||||
self.instance_ref_iter()
|
||||
.flat_map(|instance| {
|
||||
let transform = transform * *instance.transform;
|
||||
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
@@ -939,7 +939,7 @@ impl GraphicElementRendered for RasterFrame {
|
||||
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image) => {
|
||||
for instance in image.instances() {
|
||||
for instance in image.instance_ref_iter() {
|
||||
let image = &instance.instance;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
@@ -951,7 +951,7 @@ impl GraphicElementRendered for RasterFrame {
|
||||
}
|
||||
}
|
||||
RasterFrame::TextureFrame(image_texture) => {
|
||||
for instance in image_texture.instances() {
|
||||
for instance in image_texture.instance_ref_iter() {
|
||||
let image =
|
||||
vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.texture.width(), instance.instance.texture.height()).with_extend(peniko::Extend::Repeat);
|
||||
|
||||
|
||||
@@ -40,39 +40,15 @@ impl<T> Instances<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, instance: T) -> InstanceMut<T> {
|
||||
self.instance.push(instance);
|
||||
self.transform.push(DAffine2::IDENTITY);
|
||||
self.alpha_blending.push(AlphaBlending::default());
|
||||
self.source_node_id.push(None);
|
||||
|
||||
InstanceMut {
|
||||
instance: self.instance.last_mut().expect("Shouldn't be empty"),
|
||||
transform: self.transform.last_mut().expect("Shouldn't be empty"),
|
||||
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
|
||||
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
|
||||
}
|
||||
pub fn push(&mut self, instance: Instance<T>) {
|
||||
self.instance.push(instance.instance);
|
||||
self.transform.push(instance.transform);
|
||||
self.alpha_blending.push(instance.alpha_blending);
|
||||
self.source_node_id.push(instance.source_node_id);
|
||||
}
|
||||
|
||||
pub fn push_instance(&mut self, instance: Instance<T>) -> InstanceMut<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.instance.push(instance.instance.clone());
|
||||
self.transform.push(*instance.transform);
|
||||
self.alpha_blending.push(*instance.alpha_blending);
|
||||
self.source_node_id.push(*instance.source_node_id);
|
||||
|
||||
InstanceMut {
|
||||
instance: self.instance.last_mut().expect("Shouldn't be empty"),
|
||||
transform: self.transform.last_mut().expect("Shouldn't be empty"),
|
||||
alpha_blending: self.alpha_blending.last_mut().expect("Shouldn't be empty"),
|
||||
source_node_id: self.source_node_id.last_mut().expect("Shouldn't be empty"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one_instance(&self) -> Instance<T> {
|
||||
Instance {
|
||||
pub fn one_instance_ref(&self) -> InstanceRef<T> {
|
||||
InstanceRef {
|
||||
instance: self.instance.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
transform: self.transform.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
alpha_blending: self.alpha_blending.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
@@ -91,12 +67,12 @@ impl<T> Instances<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instances(&self) -> impl DoubleEndedIterator<Item = Instance<T>> {
|
||||
pub fn instance_iter(self) -> impl DoubleEndedIterator<Item = Instance<T>> {
|
||||
self.instance
|
||||
.iter()
|
||||
.zip(self.transform.iter())
|
||||
.zip(self.alpha_blending.iter())
|
||||
.zip(self.source_node_id.iter())
|
||||
.into_iter()
|
||||
.zip(self.transform)
|
||||
.zip(self.alpha_blending)
|
||||
.zip(self.source_node_id)
|
||||
.map(|(((instance, transform), alpha_blending), source_node_id)| Instance {
|
||||
instance,
|
||||
transform,
|
||||
@@ -105,7 +81,21 @@ impl<T> Instances<T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn instances_mut(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<T>> {
|
||||
pub fn instance_ref_iter(&self) -> impl DoubleEndedIterator<Item = InstanceRef<T>> {
|
||||
self.instance
|
||||
.iter()
|
||||
.zip(self.transform.iter())
|
||||
.zip(self.alpha_blending.iter())
|
||||
.zip(self.source_node_id.iter())
|
||||
.map(|(((instance, transform), alpha_blending), source_node_id)| InstanceRef {
|
||||
instance,
|
||||
transform,
|
||||
alpha_blending,
|
||||
source_node_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn instance_mut_iter(&mut self) -> impl DoubleEndedIterator<Item = InstanceMut<T>> {
|
||||
self.instance
|
||||
.iter_mut()
|
||||
.zip(self.transform.iter_mut())
|
||||
@@ -119,12 +109,12 @@ impl<T> Instances<T> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<Instance<T>> {
|
||||
pub fn get(&self, index: usize) -> Option<InstanceRef<T>> {
|
||||
if index >= self.instance.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Instance {
|
||||
Some(InstanceRef {
|
||||
instance: &self.instance[index],
|
||||
transform: &self.transform[index],
|
||||
alpha_blending: &self.alpha_blending[index],
|
||||
@@ -199,12 +189,13 @@ fn one_source_node_id_default() -> Vec<Option<NodeId>> {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Instance<'a, T> {
|
||||
pub struct InstanceRef<'a, T> {
|
||||
pub instance: &'a T,
|
||||
pub transform: &'a DAffine2,
|
||||
pub alpha_blending: &'a AlphaBlending,
|
||||
pub source_node_id: &'a Option<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceMut<'a, T> {
|
||||
pub instance: &'a mut T,
|
||||
@@ -213,10 +204,32 @@ pub struct InstanceMut<'a, T> {
|
||||
pub source_node_id: &'a mut Option<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Instance<T> {
|
||||
pub instance: T,
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
pub source_node_id: Option<NodeId>,
|
||||
}
|
||||
|
||||
impl<T> Instance<T> {
|
||||
pub fn to_graphic_element<U>(self) -> Instance<U>
|
||||
where
|
||||
T: Into<U>,
|
||||
{
|
||||
Instance {
|
||||
instance: self.instance.into(),
|
||||
transform: self.transform,
|
||||
alpha_blending: self.alpha_blending,
|
||||
source_node_id: self.source_node_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VECTOR DATA TABLE
|
||||
impl Transform for VectorDataTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.one_instance().transform
|
||||
*self.one_instance_ref().transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for VectorDataTable {
|
||||
@@ -228,7 +241,7 @@ impl TransformMut for VectorDataTable {
|
||||
// TEXTURE FRAME TABLE
|
||||
impl Transform for TextureFrameTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.one_instance().transform
|
||||
*self.one_instance_ref().transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for TextureFrameTable {
|
||||
@@ -243,7 +256,7 @@ where
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.one_instance().transform
|
||||
*self.one_instance_ref().transform
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> TransformMut for ImageFrameTable<P>
|
||||
|
||||
@@ -365,6 +365,30 @@ fn not_equals<U: core::cmp::PartialEq<T>, T>(
|
||||
other_value != value
|
||||
}
|
||||
|
||||
/// The less-than operation (<) compares two values and returns true if the first value is less than the second, or false if it is not.
|
||||
/// If enabled with "Or Equal", the less-than-or-equal operation (<=) will be used instead.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn less_than<T: core::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value <= other_value } else { value < other_value }
|
||||
}
|
||||
|
||||
/// The greater-than operation (>) compares two values and returns true if the first value is greater than the second, or false if it is not.
|
||||
/// If enabled with "Or Equal", the greater-than-or-equal operation (>=) will be used instead.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn greater_than<T: core::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value >= other_value } else { value > other_value }
|
||||
}
|
||||
|
||||
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
|
||||
@@ -300,21 +300,21 @@ trait SetBlendMode {
|
||||
|
||||
impl SetBlendMode for VectorDataTable {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for GraphicGroupTable {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl SetBlendMode for ImageFrameTable<Color> {
|
||||
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.blend_mode = blend_mode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ impl Blend<Color> for ImageFrameTable<Color> {
|
||||
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
|
||||
let mut result = self.clone();
|
||||
|
||||
for (over, under) in result.instances_mut().zip(under.instances()) {
|
||||
for (over, under) in result.instance_mut_iter().zip(under.instance_ref_iter()) {
|
||||
let data = over.instance.data.iter().zip(under.instance.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
|
||||
*over.instance = Image {
|
||||
@@ -731,7 +731,7 @@ where
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
for c in instance.instance.data.iter_mut() {
|
||||
*c = map_fn(c);
|
||||
}
|
||||
@@ -1360,14 +1360,14 @@ impl MultiplyAlpha for Color {
|
||||
}
|
||||
impl MultiplyAlpha for VectorDataTable {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MultiplyAlpha for GraphicGroupTable {
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
@@ -1377,7 +1377,7 @@ where
|
||||
GraphicElement: From<Image<P>>,
|
||||
{
|
||||
fn multiply_alpha(&mut self, factor: f64) {
|
||||
for instance in self.instances_mut() {
|
||||
for instance in self.instance_mut_iter() {
|
||||
instance.alpha_blending.opacity *= factor as f32;
|
||||
}
|
||||
}
|
||||
@@ -1577,7 +1577,7 @@ mod test {
|
||||
let opacity = 100_f64;
|
||||
|
||||
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
|
||||
let result = result.instances().next().unwrap().instance;
|
||||
let result = result.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
|
||||
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
|
||||
|
||||
@@ -31,7 +31,7 @@ struct BrushCacheImpl {
|
||||
impl BrushCacheImpl {
|
||||
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
// Do background invalidation.
|
||||
if background.one_instance().instance != self.background.one_instance().instance {
|
||||
if background.one_instance_ref().instance != self.background.one_instance_ref().instance {
|
||||
self.background = background.clone();
|
||||
return BrushPlan {
|
||||
strokes: input.to_vec(),
|
||||
|
||||
@@ -232,7 +232,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
fn from(element: GraphicElement) -> Self {
|
||||
match element {
|
||||
GraphicElement::RasterFrame(crate::RasterFrame::ImageFrame(image)) => Self {
|
||||
image: image.one_instance().instance.clone(),
|
||||
image: image.one_instance_ref().instance.clone(),
|
||||
},
|
||||
_ => panic!("Expected Image, found {:?}", element),
|
||||
}
|
||||
@@ -274,7 +274,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
||||
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
|
||||
image_frame_table
|
||||
}
|
||||
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance().instance.image.clone()),
|
||||
FormatVersions::ImageFrame(image_frame) => ImageFrameTable::new(image_frame.one_instance_ref().instance.image.clone()),
|
||||
FormatVersions::ImageFrameTable(image_frame_table) => image_frame_table,
|
||||
})
|
||||
}
|
||||
@@ -315,8 +315,8 @@ where
|
||||
// TODO: Improve sampling logic
|
||||
#[inline(always)]
|
||||
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
|
||||
let image_transform = self.one_instance().transform;
|
||||
let image = self.one_instance().instance;
|
||||
let image_transform = self.one_instance_ref().transform;
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
let image_size = DVec2::new(image.width() as f64, image.height() as f64);
|
||||
let pos = (DAffine2::from_scale(image_size) * image_transform.inverse()).transform_point2(pos);
|
||||
@@ -333,19 +333,19 @@ where
|
||||
type Pixel = P;
|
||||
|
||||
fn width(&self) -> u32 {
|
||||
let image = self.one_instance().instance;
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
image.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
let image = self.one_instance().instance;
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
image.height()
|
||||
}
|
||||
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
|
||||
let image = self.one_instance().instance;
|
||||
let image = self.one_instance_ref().instance;
|
||||
|
||||
image.get_pixel(x, y)
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ async fn transform<T: 'n + 'static>(
|
||||
|
||||
let mut transform_target = transform_target.eval(ctx.into_context()).await;
|
||||
|
||||
for data_transform in transform_target.instances_mut() {
|
||||
for data_transform in transform_target.instance_mut_iter() {
|
||||
*data_transform.transform = matrix * *data_transform.transform;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ fn replace_transform<Data, TransformInput: Transform>(
|
||||
#[implementations(VectorDataTable, ImageFrameTable<Color>, GraphicGroupTable)] mut data: Instances<Data>,
|
||||
#[implementations(DAffine2)] transform: TransformInput,
|
||||
) -> Instances<Data> {
|
||||
for data_transform in data.instances_mut() {
|
||||
for data_transform in data.instance_mut_iter() {
|
||||
*data_transform.transform = transform.transform();
|
||||
}
|
||||
data
|
||||
|
||||
@@ -1,39 +1,74 @@
|
||||
use crate::instances::Instance;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, OwnedContextImpl};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use crate::instances::{InstanceRef, Instances};
|
||||
use crate::raster::Color;
|
||||
use crate::raster::image::ImageFrameTable;
|
||||
use crate::transform::TransformMut;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||
use glam::DVec2;
|
||||
|
||||
#[node_macro::node(name("Instance on Points"), category("Vector: Shape"), path(graphene_core::vector))]
|
||||
async fn instance_on_points(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_on_points<T: Into<GraphicElement> + Default + Clone + 'static>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
|
||||
points: VectorDataTable,
|
||||
#[implementations(Context -> VectorDataTable)] instance_node: impl Node<'n, Context<'static>, Output = VectorDataTable>,
|
||||
) -> VectorDataTable {
|
||||
let mut result = VectorDataTable::empty();
|
||||
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
reverse: bool,
|
||||
) -> GraphicGroupTable {
|
||||
let mut result_table = GraphicGroupTable::empty();
|
||||
|
||||
for Instance { instance: points, transform, .. } in points.instances() {
|
||||
for (index, &point) in points.point_domain.positions().iter().enumerate() {
|
||||
for InstanceRef { instance: points, transform, .. } in points.instance_ref_iter() {
|
||||
let mut iteration = async |index, point| {
|
||||
let transformed_point = transform.transform_point2(point);
|
||||
|
||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
|
||||
let instanced = instance_node.eval(new_ctx.into_context()).await;
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for instanced in instanced.instances() {
|
||||
let instanced = result.push_instance(instanced);
|
||||
*instanced.transform *= DAffine2::from_translation(transformed_point);
|
||||
for mut instanced in generated_instance.instance_iter() {
|
||||
instanced.transform.translate(transformed_point);
|
||||
result_table.push(instanced.to_graphic_element());
|
||||
}
|
||||
};
|
||||
|
||||
let range = points.point_domain.positions().iter().enumerate();
|
||||
if reverse {
|
||||
for (index, &point) in range.rev() {
|
||||
iteration(index, point).await;
|
||||
}
|
||||
} else {
|
||||
for (index, &point) in range {
|
||||
iteration(index, point).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove once we support empty tables, currently this is here to avoid crashing
|
||||
if result.is_empty() {
|
||||
return VectorDataTable::new(VectorData::empty());
|
||||
}
|
||||
|
||||
result
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_repeat<T: Into<GraphicElement> + Default + Clone + 'static>(
|
||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||
#[implementations(Context -> GraphicGroupTable, Context -> VectorDataTable, Context -> ImageFrameTable<Color>)] instance: impl Node<'n, Context<'static>, Output = Instances<T>>,
|
||||
#[default(1)] count: u64,
|
||||
reverse: bool,
|
||||
) -> GraphicGroupTable {
|
||||
let count = count.max(1) as usize;
|
||||
|
||||
let mut result_table = GraphicGroupTable::empty();
|
||||
|
||||
for index in 0..count {
|
||||
let index = if reverse { count - index - 1 } else { index };
|
||||
|
||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
|
||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||
|
||||
for instanced in generated_instance.instance_iter() {
|
||||
result_table.push(instanced.to_graphic_element());
|
||||
}
|
||||
}
|
||||
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
|
||||
match ctx.vararg(0).map(|dynamic| dynamic.downcast_ref::<DVec2>()) {
|
||||
Ok(Some(position)) => return *position,
|
||||
@@ -43,7 +78,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Attributes"), path(graphene_core::vector))]
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
|
||||
match ctx.try_index() {
|
||||
Some(index) => return index as f64,
|
||||
@@ -87,10 +122,17 @@ mod test {
|
||||
|
||||
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
|
||||
let points = VectorDataTable::new(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
|
||||
let repeated = super::instance_on_points(owned, points, &rect).await;
|
||||
let repeated = super::instance_on_points(owned, points, &rect, false).await;
|
||||
assert_eq!(repeated.len(), positions.len());
|
||||
for (position, instanced) in positions.into_iter().zip(repeated.instances()) {
|
||||
let bounds = instanced.instance.bounding_box_with_transform(*instanced.transform).unwrap();
|
||||
for (position, instanced) in positions.into_iter().zip(repeated.instance_ref_iter()) {
|
||||
let bounds = instanced
|
||||
.instance
|
||||
.as_vector_data()
|
||||
.unwrap()
|
||||
.one_instance_ref()
|
||||
.instance
|
||||
.bounding_box_with_transform(*instanced.transform)
|
||||
.unwrap();
|
||||
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
||||
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
||||
}
|
||||
|
||||
@@ -253,9 +253,9 @@ fn isometric_grid_test() {
|
||||
|
||||
// Works properly
|
||||
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
|
||||
assert_eq!(grid.one_instance().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
|
||||
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
assert!(
|
||||
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
|
||||
@@ -268,9 +268,9 @@ fn isometric_grid_test() {
|
||||
#[test]
|
||||
fn skew_isometric_grid_test() {
|
||||
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
|
||||
assert_eq!(grid.one_instance().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.one_instance().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.one_instance().instance.segment_bezier_iter() {
|
||||
assert_eq!(grid.one_instance_ref().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.one_instance_ref().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.one_instance_ref().instance.segment_bezier_iter() {
|
||||
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
|
||||
let vector = bezier.start - bezier.end;
|
||||
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;
|
||||
|
||||
@@ -433,29 +433,29 @@ impl VectorData {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn concat(&mut self, other: &Self, transform: DAffine2, node_id: u64) {
|
||||
let point_map = other
|
||||
pub fn concat(&mut self, additional: &Self, transform_of_additional: DAffine2, collision_hash_seed: u64) {
|
||||
let point_map = additional
|
||||
.point_domain
|
||||
.ids()
|
||||
.iter()
|
||||
.filter(|id| self.point_domain.ids().contains(id))
|
||||
.map(|&old| (old, old.generate_from_hash(node_id)))
|
||||
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let segment_map = other
|
||||
let segment_map = additional
|
||||
.segment_domain
|
||||
.ids()
|
||||
.iter()
|
||||
.filter(|id| self.segment_domain.ids().contains(id))
|
||||
.map(|&old| (old, old.generate_from_hash(node_id)))
|
||||
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let region_map = other
|
||||
let region_map = additional
|
||||
.region_domain
|
||||
.ids()
|
||||
.iter()
|
||||
.filter(|id| self.region_domain.ids().contains(id))
|
||||
.map(|&old| (old, old.generate_from_hash(node_id)))
|
||||
.map(|&old| (old, old.generate_from_hash(collision_hash_seed)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let id_map = IdMap {
|
||||
@@ -465,14 +465,14 @@ impl VectorData {
|
||||
region_map,
|
||||
};
|
||||
|
||||
self.point_domain.concat(&other.point_domain, transform, &id_map);
|
||||
self.segment_domain.concat(&other.segment_domain, transform, &id_map);
|
||||
self.region_domain.concat(&other.region_domain, transform, &id_map);
|
||||
self.point_domain.concat(&additional.point_domain, transform_of_additional, &id_map);
|
||||
self.segment_domain.concat(&additional.segment_domain, transform_of_additional, &id_map);
|
||||
self.region_domain.concat(&additional.region_domain, transform_of_additional, &id_map);
|
||||
|
||||
// TODO: properly deal with fills such as gradients
|
||||
self.style = other.style.clone();
|
||||
self.style = additional.style.clone();
|
||||
|
||||
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
|
||||
self.colinear_manipulators.extend(additional.colinear_manipulators.iter().copied());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,14 @@ impl PointDomain {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.id.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.id.is_empty()
|
||||
}
|
||||
|
||||
/// Iterate over point IDs and positions
|
||||
pub fn iter(&self) -> impl Iterator<Item = (PointId, DVec2)> + '_ {
|
||||
self.ids().iter().copied().zip(self.positions().iter().copied())
|
||||
|
||||
@@ -424,7 +424,7 @@ impl core::hash::Hash for VectorModification {
|
||||
/// A node that applies a procedural modification to some [`VectorData`].
|
||||
#[node_macro::node(category(""))]
|
||||
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable {
|
||||
let vector_data_transform = *vector_data.one_instance().transform;
|
||||
let vector_data_transform = *vector_data.one_instance_ref().transform;
|
||||
let vector_data = vector_data.one_instance_mut().instance;
|
||||
|
||||
modification.apply(vector_data);
|
||||
|
||||
@@ -2,7 +2,8 @@ use super::algorithms::offset_subpath::offset_subpath;
|
||||
use super::misc::CentroidType;
|
||||
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
||||
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
|
||||
use crate::instances::{InstanceMut, Instances};
|
||||
use crate::instances::{Instance, InstanceMut, Instances};
|
||||
use crate::raster::image::ImageFrameTable;
|
||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, SeedValue};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
@@ -11,8 +12,10 @@ use crate::vector::style::{LineCap, LineJoin};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||
use bezier_rs::{Join, ManipulatorGroup, Subpath, SubpathTValue, TValue};
|
||||
use core::f64::consts::PI;
|
||||
use core::hash::{Hash, Hasher};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
|
||||
/// Implemented for types that can be converted to an iterator of vector data.
|
||||
/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup
|
||||
@@ -23,15 +26,15 @@ trait VectorDataTableIterMut {
|
||||
impl VectorDataTableIterMut for GraphicGroupTable {
|
||||
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
|
||||
// Grab only the direct children
|
||||
self.instances_mut()
|
||||
self.instance_mut_iter()
|
||||
.filter_map(|element| element.instance.as_vector_data_mut())
|
||||
.flat_map(move |vector_data| vector_data.instances_mut())
|
||||
.flat_map(move |vector_data| vector_data.instance_mut_iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl VectorDataTableIterMut for VectorDataTable {
|
||||
fn vector_iter_mut(&mut self) -> impl Iterator<Item = InstanceMut<VectorData>> {
|
||||
self.instances_mut()
|
||||
self.instance_mut_iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +201,7 @@ where
|
||||
async fn repeat<I: 'n + Send>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
|
||||
#[default(100., 100.)]
|
||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||
direction: DVec2,
|
||||
@@ -221,13 +224,14 @@ where
|
||||
for index in 0..instances {
|
||||
let angle = index as f64 * angle / total;
|
||||
let translation = index as f64 * direction / total;
|
||||
let modification = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
|
||||
let transform = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center);
|
||||
|
||||
let mut new_graphic_element = instance.to_graphic_element().clone();
|
||||
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
|
||||
|
||||
let new_instance = result_table.push(new_graphic_element);
|
||||
*new_instance.transform = modification;
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
result_table
|
||||
@@ -237,7 +241,7 @@ where
|
||||
async fn circular_repeat<I: 'n + Send>(
|
||||
_: impl Ctx,
|
||||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: f64,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
@@ -256,13 +260,14 @@ where
|
||||
|
||||
for index in 0..instances {
|
||||
let rotation = DAffine2::from_angle((std::f64::consts::TAU / instances as f64) * index as f64 + angle_offset.to_radians());
|
||||
let modification = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
|
||||
let transform = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform);
|
||||
|
||||
let mut new_graphic_element = instance.to_graphic_element().clone();
|
||||
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
|
||||
|
||||
let new_instance = result_table.push(new_graphic_element);
|
||||
*new_instance.transform = modification;
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
result_table
|
||||
@@ -274,7 +279,7 @@ async fn copy_to_points<I: 'n + Send>(
|
||||
points: VectorDataTable,
|
||||
#[expose]
|
||||
/// Artwork to be copied and placed at each point.
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)]
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)]
|
||||
instance: Instances<I>,
|
||||
/// Minimum range of randomized sizes given to each instance.
|
||||
#[default(1)]
|
||||
@@ -301,7 +306,7 @@ where
|
||||
Instances<I>: GraphicElementRendered,
|
||||
{
|
||||
let points_transform = points.transform();
|
||||
let points_list = points.instances().flat_map(|element| element.instance.point_domain.positions());
|
||||
let points_list = points.instance_ref_iter().flat_map(|element| element.instance.point_domain.positions());
|
||||
|
||||
let random_scale_difference = random_scale_max - random_scale_min;
|
||||
|
||||
@@ -316,7 +321,7 @@ where
|
||||
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
|
||||
for (index, &point) in points_list.into_iter().enumerate() {
|
||||
for &point in points_list.into_iter() {
|
||||
let center_transform = DAffine2::from_translation(instance_center);
|
||||
|
||||
let translation = points_transform.transform_point2(point);
|
||||
@@ -342,11 +347,14 @@ where
|
||||
random_scale_min
|
||||
};
|
||||
|
||||
let mut new_graphic_element = instance.to_graphic_element().clone();
|
||||
new_graphic_element.new_ids_from_hash(Some(crate::uuid::NodeId(index as u64)));
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
|
||||
|
||||
let new_instance = result_table.push(new_graphic_element);
|
||||
*new_instance.transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform;
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
result_table
|
||||
@@ -355,7 +363,7 @@ where
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn mirror<I: 'n + Send>(
|
||||
_: impl Ctx,
|
||||
#[implementations(VectorDataTable, GraphicGroupTable)] instance: Instances<I>,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, ImageFrameTable<Color>)] instance: Instances<I>,
|
||||
#[default(0., 0.)] center: DVec2,
|
||||
#[range((-90., 90.))] angle: Angle,
|
||||
#[default(true)] keep_original: bool,
|
||||
@@ -382,20 +390,25 @@ where
|
||||
);
|
||||
|
||||
// Apply reflection around the center point
|
||||
let modification = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
|
||||
let transform = DAffine2::from_translation(mirror_center) * reflection * DAffine2::from_translation(-mirror_center);
|
||||
|
||||
// Add original instance depending on the keep_original flag
|
||||
if keep_original {
|
||||
result_table.push(instance.to_graphic_element());
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element().clone(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
}
|
||||
|
||||
// Create and add mirrored instance
|
||||
let mut mirrored_element = instance.to_graphic_element();
|
||||
mirrored_element.new_ids_from_hash(None);
|
||||
|
||||
// Apply the transformation to the mirrored instance
|
||||
let mirrored_instance = result_table.push(mirrored_element);
|
||||
*mirrored_instance.transform = modification;
|
||||
result_table.push(Instance {
|
||||
instance: instance.to_graphic_element(),
|
||||
transform,
|
||||
alpha_blending: Default::default(),
|
||||
source_node_id: None,
|
||||
});
|
||||
|
||||
result_table
|
||||
}
|
||||
@@ -417,7 +430,7 @@ async fn round_corners(
|
||||
) -> VectorDataTable {
|
||||
let source_transform = source.transform();
|
||||
let source_transform_inverse = source_transform.inverse();
|
||||
let source = source.one_instance().instance;
|
||||
let source = source.one_instance_ref().instance;
|
||||
let upstream_graphics_group = source.upstream_graphic_group.clone();
|
||||
|
||||
// Flip the roundness to help with user intuition
|
||||
@@ -517,7 +530,7 @@ async fn spatial_merge_by_distance(
|
||||
distance: f64,
|
||||
) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
let point_count = vector_data.point_domain.positions().len();
|
||||
|
||||
// Find min x and y for grid cell normalization
|
||||
@@ -638,10 +651,10 @@ async fn spatial_merge_by_distance(
|
||||
#[node_macro::node(category("Debug"), path(graphene_core::vector))]
|
||||
async fn box_warp(_: impl Ctx, vector_data: VectorDataTable, #[expose] rectangle: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance.clone();
|
||||
let vector_data = vector_data.one_instance_ref().instance.clone();
|
||||
|
||||
let target_transform = rectangle.transform();
|
||||
let target = rectangle.one_instance().instance;
|
||||
let target = rectangle.one_instance_ref().instance;
|
||||
|
||||
// Get the bounding box of the source vector data
|
||||
let source_bbox = vector_data.bounding_box_with_transform(vector_data_transform).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
@@ -727,7 +740,7 @@ async fn remove_handles(
|
||||
max_handle_distance: f64,
|
||||
) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let mut vector_data = vector_data.one_instance().instance.clone();
|
||||
let mut vector_data = vector_data.one_instance_ref().instance.clone();
|
||||
|
||||
for (_, handles, start, end) in vector_data.segment_domain.handles_mut() {
|
||||
// Only convert to linear if handles are within the threshold distance
|
||||
@@ -773,7 +786,7 @@ async fn generate_handles(
|
||||
curvature: f64,
|
||||
) -> VectorDataTable {
|
||||
let source_transform = source.transform();
|
||||
let source = source.one_instance().instance;
|
||||
let source = source.one_instance_ref().instance;
|
||||
|
||||
let mut result = VectorData::empty();
|
||||
result.style = source.style.clone();
|
||||
@@ -955,7 +968,7 @@ async fn generate_handles(
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let mut result = vector_data
|
||||
.bounding_box()
|
||||
@@ -972,7 +985,7 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
vector_data
|
||||
.bounding_box_with_transform(vector_data_transform)
|
||||
.map(|[top_left, bottom_right]| bottom_right - top_left)
|
||||
@@ -982,7 +995,7 @@ async fn dimensions(_: impl Ctx, vector_data: VectorDataTable) -> DVec2 {
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))]
|
||||
async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, line_join: LineJoin, #[default(4.)] miter_limit: f64) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
let mut result = VectorData::empty();
|
||||
@@ -1018,7 +1031,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let stroke = vector_data.style.stroke().clone().unwrap_or_default();
|
||||
let bezpaths = vector_data.stroke_bezpath_iter();
|
||||
@@ -1072,15 +1085,20 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
|
||||
// a flatten vector elements connected to an if else node, another connection from the cache directly
|
||||
// To the if else node, and another connection from the cache to a matches type node connected to the if else node.
|
||||
fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut<VectorData>) {
|
||||
for current_element in graphic_group_table.instances() {
|
||||
for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() {
|
||||
match current_element.instance {
|
||||
GraphicElement::VectorData(vector_data_table) => {
|
||||
// Loop through every row of the VectorDataTable and concatenate each instance's subpath into the output VectorData instance.
|
||||
for vector_data_instance in vector_data_table.instances() {
|
||||
for (vector_index, vector_data_instance) in vector_data_table.instance_ref_iter().enumerate() {
|
||||
let other = vector_data_instance.instance;
|
||||
let transform = *current_element.transform * *vector_data_instance.transform;
|
||||
let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default();
|
||||
output.instance.concat(other, transform, node_id);
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
(group_index, vector_index, node_id).hash(&mut hasher);
|
||||
let collision_hash_seed = hasher.finish();
|
||||
|
||||
output.instance.concat(other, transform, collision_hash_seed);
|
||||
|
||||
// Use the last encountered style as the output style
|
||||
output.instance.style = vector_data_instance.instance.style.clone();
|
||||
@@ -1088,7 +1106,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
|
||||
}
|
||||
GraphicElement::GraphicGroup(graphic_group) => {
|
||||
let mut graphic_group = graphic_group.clone();
|
||||
for instance in graphic_group.instances_mut() {
|
||||
for instance in graphic_group.instance_mut_iter() {
|
||||
*instance.transform = *current_element.transform * *instance.transform;
|
||||
}
|
||||
|
||||
@@ -1100,7 +1118,7 @@ async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupT
|
||||
}
|
||||
|
||||
let mut output_table = VectorDataTable::default();
|
||||
let Some(mut output) = output_table.instances_mut().next() else { return output_table };
|
||||
let Some(mut output) = output_table.instance_mut_iter().next() else { return output_table };
|
||||
|
||||
flatten_group(&graphic_group_input, &mut output);
|
||||
|
||||
@@ -1113,7 +1131,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
||||
let spacing = spacing.max(0.01);
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
// Create an iterator over the bezier segments with enumeration and peeking capability.
|
||||
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
|
||||
@@ -1274,7 +1292,7 @@ async fn position_on_path(
|
||||
let euclidian = !parameterized_distance;
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
|
||||
let progress = progress.clamp(0., subpaths_count);
|
||||
@@ -1306,7 +1324,7 @@ async fn tangent_on_path(
|
||||
let euclidian = !parameterized_distance;
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let subpaths_count = vector_data.stroke_bezier_paths().count() as f64;
|
||||
let progress = progress.clamp(0., subpaths_count);
|
||||
@@ -1340,7 +1358,7 @@ async fn poisson_disk_points(
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
let mut result = VectorData::empty();
|
||||
@@ -1391,7 +1409,7 @@ async fn poisson_disk_points(
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
async fn subpath_segment_lengths(_: impl Ctx, vector_data: VectorDataTable) -> Vec<f64> {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
@@ -1447,7 +1465,7 @@ async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTabl
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let mut vector_data = vector_data.one_instance().instance.clone();
|
||||
let mut vector_data = vector_data.one_instance_ref().instance.clone();
|
||||
|
||||
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
|
||||
|
||||
@@ -1500,14 +1518,14 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
|
||||
async fn morph(_: impl Ctx, source: VectorDataTable, #[expose] target: VectorDataTable, #[default(0.5)] time: Fraction, #[min(0.)] start_index: IntegerCount) -> VectorDataTable {
|
||||
let time = time.clamp(0., 1.);
|
||||
|
||||
let source_alpha_blending = source.one_instance().alpha_blending;
|
||||
let target_alpha_blending = target.one_instance().alpha_blending;
|
||||
let source_alpha_blending = source.one_instance_ref().alpha_blending;
|
||||
let target_alpha_blending = target.one_instance_ref().alpha_blending;
|
||||
|
||||
let source_transform = source.transform();
|
||||
let target_transform = target.transform();
|
||||
|
||||
let source = source.one_instance().instance;
|
||||
let target = target.one_instance().instance;
|
||||
let source = source.one_instance_ref().instance;
|
||||
let target = target.one_instance_ref().instance;
|
||||
|
||||
let mut result = VectorDataTable::default();
|
||||
|
||||
@@ -1699,7 +1717,7 @@ fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2,
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
||||
let source_transform = source.transform();
|
||||
let source = source.one_instance().instance;
|
||||
let source = source.one_instance_ref().instance;
|
||||
|
||||
let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
|
||||
*result.transform_mut() = source_transform;
|
||||
@@ -1709,7 +1727,7 @@ fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length)
|
||||
#[node_macro::node(name("Merge by Distance"), category("Vector"), path(graphene_core::vector))]
|
||||
fn merge_by_distance(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
||||
let source_transform = source.transform();
|
||||
let mut source = source.one_instance().instance.clone();
|
||||
let mut source = source.one_instance_ref().instance.clone();
|
||||
|
||||
source.merge_by_distance(distance);
|
||||
|
||||
@@ -1725,7 +1743,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
|
||||
let vector_data = vector_data.eval(new_ctx).await;
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
let mut area = 0.;
|
||||
let scale = vector_data_transform.decompose_scale();
|
||||
@@ -1742,7 +1760,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
|
||||
let vector_data = vector_data.eval(new_ctx).await;
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
let vector_data = vector_data.one_instance_ref().instance;
|
||||
|
||||
if centroid_type == CentroidType::Area {
|
||||
let mut area = 0.;
|
||||
@@ -1814,7 +1832,7 @@ mod test {
|
||||
let instances = 3;
|
||||
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.instances().next().unwrap().instance;
|
||||
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 3);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
||||
@@ -1826,7 +1844,7 @@ mod test {
|
||||
let instances = 8;
|
||||
let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.instances().next().unwrap().instance;
|
||||
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5);
|
||||
@@ -1836,7 +1854,7 @@ mod test {
|
||||
async fn circular_repeat() {
|
||||
let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await;
|
||||
let vector_data = super::flatten_vector_elements(Footprint::default(), repeated).await;
|
||||
let vector_data = vector_data.instances().next().unwrap().instance;
|
||||
let vector_data = vector_data.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
@@ -1851,7 +1869,7 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn bounding_box() {
|
||||
let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await;
|
||||
let bounding_box = bounding_box.instances().next().unwrap().instance;
|
||||
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||
assert_eq!(&subpath.anchors()[..4], &[DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.),]);
|
||||
@@ -1865,7 +1883,7 @@ mod test {
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
let bounding_box = bounding_box.instances().next().unwrap().instance;
|
||||
let bounding_box = bounding_box.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||
let expected_bounding_box = [DVec2::NEG_ONE, DVec2::new(1., -1.), DVec2::ONE, DVec2::new(-1., 1.)];
|
||||
@@ -1882,7 +1900,7 @@ mod test {
|
||||
|
||||
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||
let flatten_vector_elements = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
|
||||
let flattened_copy_to_points = flatten_vector_elements.instances().next().unwrap().instance;
|
||||
let flattened_copy_to_points = flatten_vector_elements.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
||||
|
||||
@@ -1898,7 +1916,7 @@ mod test {
|
||||
async fn sample_points() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 30., 0., 0., false, vec![100.]).await;
|
||||
let sample_points = sample_points.instances().next().unwrap().instance;
|
||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
||||
@@ -1908,7 +1926,7 @@ mod test {
|
||||
async fn adaptive_spacing() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_points = super::sample_points(Footprint::default(), vector_node(path), 18., 45., 10., true, vec![100.]).await;
|
||||
let sample_points = sample_points.instances().next().unwrap().instance;
|
||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(sample_points.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_points.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||
assert!(pos.distance(expected) < 1e-3, "Expected {expected} found {pos}");
|
||||
@@ -1923,7 +1941,7 @@ mod test {
|
||||
0,
|
||||
)
|
||||
.await;
|
||||
let sample_points = sample_points.instances().next().unwrap().instance;
|
||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
||||
assert!(
|
||||
(20..=40).contains(&sample_points.point_domain.positions().len()),
|
||||
"actual len {}",
|
||||
@@ -1942,7 +1960,7 @@ mod test {
|
||||
#[tokio::test]
|
||||
async fn spline() {
|
||||
let spline = super::spline(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
|
||||
let spline = spline.instances().next().unwrap().instance;
|
||||
let spline = spline.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(spline.stroke_bezier_paths().count(), 1);
|
||||
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
||||
}
|
||||
@@ -1951,7 +1969,7 @@ mod test {
|
||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||
let target = Subpath::new_ellipse(DVec2::NEG_ONE * 100., DVec2::ZERO);
|
||||
let sample_points = super::morph(Footprint::default(), vector_node(source), vector_node(target), 0.5, 0).await;
|
||||
let sample_points = sample_points.instances().next().unwrap().instance;
|
||||
let sample_points = sample_points.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(
|
||||
&sample_points.point_domain.positions()[..4],
|
||||
vec![DVec2::new(-25., -50.), DVec2::new(50., -25.), DVec2::new(25., 50.), DVec2::new(-50., 25.)]
|
||||
@@ -1974,7 +1992,7 @@ mod test {
|
||||
async fn bevel_rect() {
|
||||
let source = Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.);
|
||||
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
|
||||
let beveled = beveled.instances().next().unwrap().instance;
|
||||
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
||||
@@ -1997,7 +2015,7 @@ mod test {
|
||||
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(10., 0.), DVec2::new(10., 100.), DVec2::X * 100.);
|
||||
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), curve], false);
|
||||
let beveled = super::bevel((), vector_node(source), 5.);
|
||||
let beveled = beveled.instances().next().unwrap().instance;
|
||||
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
@@ -2021,7 +2039,7 @@ mod test {
|
||||
*vector_data_table.one_instance_mut().transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
||||
|
||||
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
|
||||
let beveled = beveled.instances().next().unwrap().instance;
|
||||
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
@@ -2039,7 +2057,7 @@ mod test {
|
||||
async fn bevel_too_high() {
|
||||
let source = Subpath::from_anchors([DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)], false);
|
||||
let beveled = super::bevel(Footprint::default(), vector_node(source), 999.);
|
||||
let beveled = beveled.instances().next().unwrap().instance;
|
||||
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
@@ -2060,7 +2078,7 @@ mod test {
|
||||
let point = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::ZERO, DVec2::ZERO);
|
||||
let source = Subpath::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -100., DVec2::ZERO), point, curve], false);
|
||||
let beveled = super::bevel(Footprint::default(), vector_node(source), 5.);
|
||||
let beveled = beveled.instances().next().unwrap().instance;
|
||||
let beveled = beveled.instance_ref_iter().next().unwrap().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
||||
Reference in New Issue
Block a user