mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-24 08:05:04 +00:00
Instance tables refactor part 2: move the transform and alpha_blending fields up a level (#2249)
* Fix domain data structure field plural naming * Rename method one_item to one_instance Rename method one_item to one_instance * Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T> Move the Instance<T> methods over to providing an Instance<T>/InstanceMut<T> * Add transform and alpha_blending fields to Instances<T> * Finish the refactor (Brush tool is broken though) * Add test for brush node * Fix brush node * Fix default empty images being 1x1 instead of 0x0 as they should be * Fix tests * Fix path transform * Add correct upgrading to move the transform/blending up a level --------- Co-authored-by: hypercube <0hypercube@gmail.com>
This commit is contained in:
parent
4ff2bdb04f
commit
f1160e1ca6
33 changed files with 1099 additions and 984 deletions
|
@ -2,7 +2,6 @@ use crate::instances::Instances;
|
|||
use crate::text::FontCache;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::vector::style::ViewMode;
|
||||
use crate::AlphaBlending;
|
||||
|
||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||
|
||||
|
@ -73,36 +72,20 @@ pub struct TextureFrame {
|
|||
pub texture: Arc<wgpu::Texture>,
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
pub texture: (),
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blend: AlphaBlending,
|
||||
}
|
||||
|
||||
impl Hash for TextureFrame {
|
||||
#[cfg(feature = "wgpu")]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
#[cfg(feature = "wgpu")]
|
||||
self.texture.hash(state);
|
||||
}
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
fn hash<H: Hasher>(&self, _state: &mut H) {}
|
||||
}
|
||||
|
||||
impl PartialEq for TextureFrame {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
#[cfg(feature = "wgpu")]
|
||||
return self.transform.eq(&other.transform) && self.texture == other.texture;
|
||||
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
self.transform.eq(&other.transform)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform for TextureFrame {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for TextureFrame {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
self.texture == other.texture
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,15 +45,30 @@ impl AlphaBlending {
|
|||
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<GraphicGroupTable, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldGraphicGroup {
|
||||
elements: Vec<(GraphicElement, Option<NodeId>)>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EitherFormat {
|
||||
GraphicGroup(GraphicGroup),
|
||||
OldGraphicGroup(OldGraphicGroup),
|
||||
GraphicGroupTable(GraphicGroupTable),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::GraphicGroup(graphic_group) => GraphicGroupTable::new(graphic_group),
|
||||
EitherFormat::OldGraphicGroup(old) => {
|
||||
let mut graphic_group_table = GraphicGroupTable::new(GraphicGroup { elements: old.elements });
|
||||
*graphic_group_table.one_instance_mut().transform = old.transform;
|
||||
*graphic_group_table.one_instance_mut().alpha_blending = old.alpha_blending;
|
||||
graphic_group_table
|
||||
}
|
||||
EitherFormat::GraphicGroupTable(graphic_group_table) => graphic_group_table,
|
||||
})
|
||||
}
|
||||
|
@ -65,15 +80,11 @@ pub type GraphicGroupTable = Instances<GraphicGroup>;
|
|||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct GraphicGroup {
|
||||
elements: Vec<(GraphicElement, Option<NodeId>)>,
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
impl core::hash::Hash for GraphicGroup {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.transform.to_cols_array().iter().for_each(|element| element.to_bits().hash(state));
|
||||
self.elements.hash(state);
|
||||
self.alpha_blending.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,8 +92,6 @@ impl GraphicGroup {
|
|||
pub fn new(elements: Vec<GraphicElement>) -> Self {
|
||||
Self {
|
||||
elements: elements.into_iter().map(|element| (element, None)).collect(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,29 +223,6 @@ impl serde::Serialize for RasterFrame {
|
|||
}
|
||||
}
|
||||
|
||||
impl Transform for RasterFrame {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(frame) => frame.transform(),
|
||||
RasterFrame::TextureFrame(frame) => frame.transform(),
|
||||
}
|
||||
}
|
||||
fn local_pivot(&self, pivot: glam::DVec2) -> glam::DVec2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(frame) => frame.local_pivot(pivot),
|
||||
RasterFrame::TextureFrame(frame) => frame.local_pivot(pivot),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for RasterFrame {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(frame) => frame.transform_mut(),
|
||||
RasterFrame::TextureFrame(frame) => frame.transform_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -281,20 +267,20 @@ impl ArtboardGroup {
|
|||
|
||||
#[node_macro::node(category(""))]
|
||||
async fn layer(_: impl Ctx, stack: GraphicGroupTable, mut element: GraphicElement, node_path: Vec<NodeId>) -> GraphicGroupTable {
|
||||
let mut stack = stack.one_item().clone();
|
||||
let mut stack = stack;
|
||||
|
||||
if stack.transform.matrix2.determinant() != 0. {
|
||||
*element.transform_mut() = stack.transform.inverse() * element.transform();
|
||||
if stack.transform().matrix2.determinant() != 0. {
|
||||
*element.transform_mut() = stack.transform().inverse() * element.transform();
|
||||
} else {
|
||||
stack.clear();
|
||||
stack.transform = DAffine2::IDENTITY;
|
||||
stack.one_instance_mut().instance.clear();
|
||||
*stack.transform_mut() = DAffine2::IDENTITY;
|
||||
}
|
||||
|
||||
// Get the penultimate element of the node path, or None if the path is too short
|
||||
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
|
||||
stack.push((element, encapsulating_node_id));
|
||||
stack.one_instance_mut().instance.push((element, encapsulating_node_id));
|
||||
|
||||
GraphicGroupTable::new(stack)
|
||||
stack
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
|
@ -327,40 +313,38 @@ async fn to_group<Data: Into<GraphicGroupTable> + 'n>(
|
|||
|
||||
#[node_macro::node(category("General"))]
|
||||
async fn flatten_group(_: impl Ctx, group: GraphicGroupTable, fully_flatten: bool) -> GraphicGroupTable {
|
||||
let nested_group = group.one_item().clone();
|
||||
|
||||
let mut flat_group = GraphicGroup::default();
|
||||
|
||||
fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) {
|
||||
fn flatten_group(result_group: &mut GraphicGroupTable, current_group_table: GraphicGroupTable, fully_flatten: bool) {
|
||||
let mut collection_group = GraphicGroup::default();
|
||||
for (element, reference) in current_group.elements {
|
||||
if let GraphicElement::GraphicGroup(nested_group) = element {
|
||||
let nested_group = nested_group.one_item();
|
||||
let mut nested_group = nested_group.clone();
|
||||
let current_group_elements = current_group_table.one_instance().instance.elements.clone();
|
||||
|
||||
*nested_group.transform_mut() = nested_group.transform() * current_group.transform;
|
||||
for (element, reference) in current_group_elements {
|
||||
if let GraphicElement::GraphicGroup(mut nested_group_table) = element {
|
||||
*nested_group_table.transform_mut() = nested_group_table.transform() * current_group_table.transform();
|
||||
|
||||
let mut sub_group = GraphicGroup::default();
|
||||
let mut sub_group_table = GraphicGroupTable::default();
|
||||
if fully_flatten {
|
||||
flatten_group(&mut sub_group, nested_group, fully_flatten);
|
||||
flatten_group(&mut sub_group_table, nested_group_table, fully_flatten);
|
||||
} else {
|
||||
for (collection_element, _) in &mut nested_group.elements {
|
||||
*collection_element.transform_mut() = nested_group.transform * collection_element.transform();
|
||||
let nested_group_table_transform = nested_group_table.transform();
|
||||
for (collection_element, _) in &mut nested_group_table.one_instance_mut().instance.elements {
|
||||
*collection_element.transform_mut() = nested_group_table_transform * collection_element.transform();
|
||||
}
|
||||
sub_group = nested_group;
|
||||
sub_group_table = nested_group_table;
|
||||
}
|
||||
collection_group.append(&mut sub_group.elements);
|
||||
|
||||
collection_group.append(&mut sub_group_table.one_instance_mut().instance.elements);
|
||||
} else {
|
||||
collection_group.push((element, reference));
|
||||
}
|
||||
}
|
||||
|
||||
result_group.append(&mut collection_group.elements);
|
||||
result_group.one_instance_mut().instance.append(&mut collection_group.elements);
|
||||
}
|
||||
|
||||
flatten_group(&mut flat_group, nested_group, fully_flatten);
|
||||
let mut flat_group = GraphicGroupTable::default();
|
||||
flatten_group(&mut flat_group, group, fully_flatten);
|
||||
|
||||
GraphicGroupTable::new(flat_group)
|
||||
flat_group
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
|
@ -487,8 +471,6 @@ where
|
|||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
elements: (vec![(value.into(), None)]),
|
||||
transform: DAffine2::IDENTITY,
|
||||
alpha_blending: AlphaBlending::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::transform::{Footprint, Transform};
|
|||
use crate::uuid::{generate_uuid, NodeId};
|
||||
use crate::vector::style::{Fill, Stroke, ViewMode};
|
||||
use crate::vector::{PointId, VectorDataTable};
|
||||
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame};
|
||||
use crate::{Artboard, ArtboardGroup, Color, GraphicElement, GraphicGroupTable, RasterFrame};
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use dyn_any::DynAny;
|
||||
|
@ -291,14 +291,7 @@ pub trait GraphicElementRendered {
|
|||
fn collect_metadata(&self, _metadata: &mut RenderMetadata, _footprint: Footprint, _element_id: Option<NodeId>) {}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn to_vello_scene(&self, transform: DAffine2, context: &mut RenderContext) -> Scene {
|
||||
let mut scene = vello::Scene::new();
|
||||
self.render_to_vello(&mut scene, transform, context);
|
||||
scene
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_condext: &mut RenderContext) {}
|
||||
fn render_to_vello(&self, _scene: &mut Scene, _transform: DAffine2, _render_context: &mut RenderContext) {}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
false
|
||||
|
@ -311,40 +304,54 @@ pub trait GraphicElementRendered {
|
|||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for GraphicGroup {
|
||||
impl GraphicElementRendered for GraphicGroupTable {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(self.transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
for instance in self.instances() {
|
||||
render.parent_tag(
|
||||
"g",
|
||||
|attributes| {
|
||||
let matrix = format_transform_matrix(instance.transform());
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
|
||||
if self.alpha_blending.opacity < 1. {
|
||||
attributes.push("opacity", self.alpha_blending.opacity.to_string());
|
||||
}
|
||||
if instance.alpha_blending.opacity < 1. {
|
||||
attributes.push("opacity", instance.alpha_blending.opacity.to_string());
|
||||
}
|
||||
|
||||
if self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", self.alpha_blending.blend_mode.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
for (element, _) in self.iter() {
|
||||
element.render_svg(render, render_params);
|
||||
}
|
||||
},
|
||||
);
|
||||
if instance.alpha_blending.blend_mode != BlendMode::default() {
|
||||
attributes.push("style", instance.alpha_blending.blend_mode.render());
|
||||
}
|
||||
},
|
||||
|render| {
|
||||
for (element, _) in instance.instance.iter() {
|
||||
element.render_svg(render, render_params);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.iter().filter_map(|(element, _)| element.bounding_box(transform * self.transform)).reduce(Quad::combine_bounds)
|
||||
self.instances()
|
||||
.flat_map(|instance| {
|
||||
instance
|
||||
.instance
|
||||
.iter()
|
||||
.filter_map(|(element, _)| element.bounding_box(transform * instance.transform()))
|
||||
.reduce(Quad::combine_bounds)
|
||||
})
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
|
||||
footprint.transform *= self.transform;
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance_transform = self.transform();
|
||||
let instance = self.one_instance().instance;
|
||||
|
||||
for (element, element_id) in self.elements.iter() {
|
||||
let mut footprint = footprint;
|
||||
footprint.transform *= instance_transform;
|
||||
|
||||
for (element, element_id) in instance.elements.iter() {
|
||||
if let Some(element_id) = element_id {
|
||||
element.collect_metadata(metadata, footprint, Some(*element_id));
|
||||
}
|
||||
|
@ -352,104 +359,79 @@ impl GraphicElementRendered for GraphicGroup {
|
|||
|
||||
if let Some(graphic_group_id) = element_id {
|
||||
let mut all_upstream_click_targets = Vec::new();
|
||||
self.add_upstream_click_targets(&mut all_upstream_click_targets);
|
||||
|
||||
for (element, _) in instance.elements.iter() {
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(element.transform())
|
||||
}
|
||||
|
||||
all_upstream_click_targets.extend(new_click_targets);
|
||||
}
|
||||
|
||||
metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for (element, _) in self.elements.iter() {
|
||||
let mut new_click_targets = Vec::new();
|
||||
for instance in self.instances() {
|
||||
for (element, _) in instance.instance.elements.iter() {
|
||||
let mut new_click_targets = Vec::new();
|
||||
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
element.add_upstream_click_targets(&mut new_click_targets);
|
||||
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(element.transform())
|
||||
for click_target in new_click_targets.iter_mut() {
|
||||
click_target.apply_transform(element.transform())
|
||||
}
|
||||
|
||||
click_targets.extend(new_click_targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
|
||||
for instance in self.instances() {
|
||||
let transform = transform * instance.transform();
|
||||
let alpha_blending = *instance.alpha_blending;
|
||||
|
||||
let blending = vello::peniko::BlendMode::new(alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver);
|
||||
let mut layer = false;
|
||||
|
||||
if alpha_blending.opacity < 1. || alpha_blending.blend_mode != BlendMode::default() {
|
||||
if let Some(bounds) = instance.instance.iter().filter_map(|(element, _)| element.bounding_box(transform)).reduce(Quad::combine_bounds) {
|
||||
scene.push_layer(
|
||||
blending,
|
||||
alpha_blending.opacity,
|
||||
kurbo::Affine::IDENTITY,
|
||||
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
|
||||
);
|
||||
layer = true;
|
||||
}
|
||||
}
|
||||
|
||||
click_targets.extend(new_click_targets);
|
||||
}
|
||||
}
|
||||
for (element, _) in instance.instance.iter() {
|
||||
element.render_to_vello(scene, transform, context);
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
|
||||
let child_transform = transform * self.transform;
|
||||
|
||||
let Some(bounds) = self.bounding_box(transform) else { return };
|
||||
let blending = vello::peniko::BlendMode::new(self.alpha_blending.blend_mode.into(), vello::peniko::Compose::SrcOver);
|
||||
let mut layer = false;
|
||||
|
||||
if self.alpha_blending.opacity < 1. || self.alpha_blending.blend_mode != BlendMode::default() {
|
||||
layer = true;
|
||||
scene.push_layer(
|
||||
blending,
|
||||
self.alpha_blending.opacity,
|
||||
kurbo::Affine::IDENTITY,
|
||||
&vello::kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y),
|
||||
);
|
||||
}
|
||||
|
||||
for (element, _) in self.iter() {
|
||||
element.render_to_vello(scene, child_transform, context);
|
||||
}
|
||||
|
||||
if layer {
|
||||
scene.pop_layer();
|
||||
if layer {
|
||||
scene.pop_layer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.iter().any(|(element, _)| element.contains_artboard())
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
for (element, node_id) in self.elements.iter_mut() {
|
||||
element.new_ids_from_hash(*node_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn to_graphic_element(&self) -> GraphicElement {
|
||||
GraphicElement::GraphicGroup(GraphicGroupTable::new(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphicElementRendered for GraphicGroupTable {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
instance.render_svg(render, render_params);
|
||||
}
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances().flat_map(|instance| instance.bounding_box(transform)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance = self.one_item();
|
||||
|
||||
instance.collect_metadata(metadata, footprint, element_id);
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for instance in self.instances() {
|
||||
instance.add_upstream_click_targets(click_targets);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "vello")]
|
||||
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext) {
|
||||
for instance in self.instances() {
|
||||
instance.render_to_vello(scene, transform, context);
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_artboard(&self) -> bool {
|
||||
self.instances().any(|instance| instance.contains_artboard())
|
||||
self.instances().any(|instance| instance.instance.iter().any(|(element, _)| element.contains_artboard()))
|
||||
}
|
||||
|
||||
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
|
||||
for instance in self.instances_mut() {
|
||||
instance.new_ids_from_hash(None);
|
||||
for (element, node_id) in instance.instance.elements.iter_mut() {
|
||||
element.new_ids_from_hash(*node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,16 +443,21 @@ impl GraphicElementRendered for GraphicGroupTable {
|
|||
impl GraphicElementRendered for VectorDataTable {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
let multiplied_transform = render.transform * instance.transform;
|
||||
let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform);
|
||||
let multiplied_transform = render.transform * instance.transform();
|
||||
let set_stroke_transform = instance
|
||||
.instance
|
||||
.style
|
||||
.stroke()
|
||||
.map(|stroke| stroke.transform)
|
||||
.filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(instance.transform());
|
||||
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
|
||||
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
|
||||
let layer_bounds = instance.bounding_box().unwrap_or_default();
|
||||
let transformed_bounds = instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
|
||||
let layer_bounds = instance.instance.bounding_box().unwrap_or_default();
|
||||
let transformed_bounds = instance.instance.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
|
||||
|
||||
let mut path = String::new();
|
||||
for subpath in instance.stroke_bezier_paths() {
|
||||
for subpath in instance.instance.stroke_bezier_paths() {
|
||||
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
|
||||
}
|
||||
|
||||
|
@ -481,6 +468,7 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
|
||||
let defs = &mut attributes.0.svg_defs;
|
||||
let fill_and_stroke = instance
|
||||
.instance
|
||||
.style
|
||||
.render(render_params.view_mode, defs, element_transform, applied_stroke_transform, layer_bounds, transformed_bounds);
|
||||
attributes.push_val(fill_and_stroke);
|
||||
|
@ -499,22 +487,23 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances()
|
||||
.flat_map(|instance| {
|
||||
let stroke_width = instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
|
||||
let stroke_width = instance.instance.style.stroke().map(|s| s.weight()).unwrap_or_default();
|
||||
|
||||
let miter_limit = instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.);
|
||||
let miter_limit = instance.instance.style.stroke().map(|s| s.line_join_miter_limit).unwrap_or(1.);
|
||||
|
||||
let scale = transform.decompose_scale();
|
||||
|
||||
// We use the full line width here to account for different styles of line caps
|
||||
let offset = DVec2::splat(stroke_width * scale.x.max(scale.y) * miter_limit);
|
||||
|
||||
instance.bounding_box_with_transform(transform * instance.transform).map(|[a, b]| [a - offset, b + offset])
|
||||
instance.instance.bounding_box_with_transform(transform * instance.transform()).map(|[a, b]| [a - offset, b + offset])
|
||||
})
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance = self.one_item();
|
||||
let instance_transform = self.transform();
|
||||
let instance = self.one_instance().instance;
|
||||
|
||||
if let Some(element_id) = element_id {
|
||||
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
|
||||
|
@ -536,15 +525,15 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
}
|
||||
|
||||
if let Some(upstream_graphic_group) = &instance.upstream_graphic_group {
|
||||
footprint.transform *= instance.transform;
|
||||
footprint.transform *= instance_transform;
|
||||
upstream_graphic_group.collect_metadata(metadata, footprint, None);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
for instance in self.instances() {
|
||||
let stroke_width = instance.style.stroke().as_ref().map_or(0., Stroke::weight);
|
||||
let filled = instance.style.fill() != &Fill::None;
|
||||
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<_>| {
|
||||
if filled {
|
||||
subpath.set_closed(true);
|
||||
|
@ -552,7 +541,7 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
subpath
|
||||
};
|
||||
|
||||
click_targets.extend(instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
|
||||
click_targets.extend(instance.instance.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget::new(subpath, stroke_width)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -564,12 +553,17 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
for instance in self.instances() {
|
||||
let mut layer = false;
|
||||
|
||||
let multiplied_transform = parent_transform * instance.transform;
|
||||
let set_stroke_transform = instance.style.stroke().map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let multiplied_transform = parent_transform * instance.transform();
|
||||
let set_stroke_transform = instance
|
||||
.instance
|
||||
.style
|
||||
.stroke()
|
||||
.map(|stroke| stroke.transform)
|
||||
.filter(|transform| transform.matrix2.determinant() != 0.);
|
||||
let applied_stroke_transform = set_stroke_transform.unwrap_or(multiplied_transform);
|
||||
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
|
||||
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
|
||||
let layer_bounds = instance.bounding_box().unwrap_or_default();
|
||||
let layer_bounds = instance.instance.bounding_box().unwrap_or_default();
|
||||
|
||||
if instance.alpha_blending.opacity < 1. || instance.alpha_blending.blend_mode != BlendMode::default() {
|
||||
layer = true;
|
||||
|
@ -583,11 +577,11 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
|
||||
let to_point = |p: DVec2| kurbo::Point::new(p.x, p.y);
|
||||
let mut path = kurbo::BezPath::new();
|
||||
for subpath in instance.stroke_bezier_paths() {
|
||||
for subpath in instance.instance.stroke_bezier_paths() {
|
||||
subpath.to_vello_path(applied_stroke_transform, &mut path);
|
||||
}
|
||||
|
||||
match instance.style.fill() {
|
||||
match instance.instance.style.fill() {
|
||||
Fill::Solid(color) => {
|
||||
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
|
||||
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
|
||||
|
@ -601,7 +595,7 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
});
|
||||
}
|
||||
// Compute bounding box of the shape to determine the gradient start and end points
|
||||
let bounds = instance.nonzero_bounding_box();
|
||||
let bounds = instance.instance.nonzero_bounding_box();
|
||||
let bound_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||
|
||||
let inverse_parent_transform = (parent_transform.matrix2.determinant() != 0.).then(|| parent_transform.inverse()).unwrap_or_default();
|
||||
|
@ -638,7 +632,7 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
Fill::None => (),
|
||||
};
|
||||
|
||||
if let Some(stroke) = instance.style.stroke() {
|
||||
if let Some(stroke) = instance.instance.style.stroke() {
|
||||
let color = match stroke.color {
|
||||
Some(color) => peniko::Color::new([color.r(), color.g(), color.b(), color.a()]),
|
||||
None => peniko::Color::TRANSPARENT,
|
||||
|
@ -676,12 +670,12 @@ impl GraphicElementRendered for VectorDataTable {
|
|||
|
||||
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
|
||||
for instance in self.instances_mut() {
|
||||
instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
instance.instance.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
fn to_graphic_element(&self) -> GraphicElement {
|
||||
let instance = self.one_item();
|
||||
let instance = self.one_instance().instance;
|
||||
|
||||
GraphicElement::VectorData(VectorDataTable::new(instance.clone()))
|
||||
}
|
||||
|
@ -833,11 +827,11 @@ impl GraphicElementRendered for ArtboardGroup {
|
|||
impl GraphicElementRendered for ImageFrameTable<Color> {
|
||||
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
|
||||
for instance in self.instances() {
|
||||
let transform = instance.transform * render.transform;
|
||||
let transform = instance.transform() * render.transform;
|
||||
|
||||
match render_params.image_render_mode {
|
||||
ImageRenderMode::Base64 => {
|
||||
let image = &instance.image;
|
||||
let image = &instance.instance.image;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
@ -874,20 +868,20 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
|
|||
fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.instances()
|
||||
.flat_map(|instance| {
|
||||
let transform = transform * instance.transform;
|
||||
let transform = transform * instance.transform();
|
||||
(transform.matrix2.determinant() != 0.).then(|| (transform * Quad::from_box([DVec2::ZERO, DVec2::ONE])).bounding_box())
|
||||
})
|
||||
.reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
|
||||
let instance = self.one_item();
|
||||
let instance_transform = self.transform();
|
||||
|
||||
let Some(element_id) = element_id else { return };
|
||||
let subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
|
||||
metadata.footprints.insert(element_id, (footprint, instance.transform));
|
||||
metadata.footprints.insert(element_id, (footprint, instance_transform));
|
||||
}
|
||||
|
||||
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||
|
@ -900,12 +894,12 @@ impl GraphicElementRendered for ImageFrameTable<Color> {
|
|||
use vello::peniko;
|
||||
|
||||
for instance in self.instances() {
|
||||
let image = &instance.image;
|
||||
let image = &instance.instance.image;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
}
|
||||
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
|
||||
let transform = transform * instance.transform * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
let transform = transform * instance.transform() * DAffine2::from_scale(1. / DVec2::new(image.width as f64, image.height as f64));
|
||||
|
||||
scene.draw_image(&image, vello::kurbo::Affine::new(transform.to_cols_array()));
|
||||
}
|
||||
|
@ -923,8 +917,8 @@ impl GraphicElementRendered for RasterFrame {
|
|||
RasterFrame::TextureFrame(_) => return,
|
||||
};
|
||||
|
||||
for image in image.instances() {
|
||||
let (image, blending) = (&image.image, image.alpha_blending);
|
||||
for instance in image.instances() {
|
||||
let (image, blending) = (&instance.instance.image, instance.alpha_blending);
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
@ -999,25 +993,26 @@ impl GraphicElementRendered for RasterFrame {
|
|||
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_frame) => {
|
||||
for image_frame in image_frame.instances() {
|
||||
let image = &image_frame.image;
|
||||
for instance in image_frame.instances() {
|
||||
let image = &instance.instance.image;
|
||||
if image.data.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let image = vello::peniko::Image::new(image.to_flat_u8().0.into(), peniko::Format::Rgba8, image.width, image.height).with_extend(peniko::Extend::Repeat);
|
||||
|
||||
render_stuff(image, image_frame.alpha_blending);
|
||||
render_stuff(image, *instance.alpha_blending);
|
||||
}
|
||||
}
|
||||
RasterFrame::TextureFrame(texture) => {
|
||||
for texture in texture.instances() {
|
||||
let image = vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, texture.texture.width(), texture.texture.height()).with_extend(peniko::Extend::Repeat);
|
||||
for instance in texture.instances() {
|
||||
let image =
|
||||
vello::peniko::Image::new(vec![].into(), peniko::Format::Rgba8, instance.instance.texture.width(), instance.instance.texture.height()).with_extend(peniko::Extend::Repeat);
|
||||
|
||||
let id = image.data.id();
|
||||
context.resource_overrides.insert(id, texture.texture.clone());
|
||||
context.resource_overrides.insert(id, instance.instance.texture.clone());
|
||||
|
||||
render_stuff(image, texture.alpha_blend);
|
||||
render_stuff(image, *instance.alpha_blending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
use crate::vector::InstanceId;
|
||||
use crate::GraphicElement;
|
||||
use crate::application_io::{TextureFrame, TextureFrameTable};
|
||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::Pixel;
|
||||
use crate::transform::{Transform, TransformMut};
|
||||
use crate::vector::{InstanceId, VectorData, VectorDataTable};
|
||||
use crate::{AlphaBlending, GraphicElement, GraphicGroup, GraphicGroupTable, RasterFrame};
|
||||
|
||||
use dyn_any::StaticType;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::hash::Hash;
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -11,54 +15,73 @@ where
|
|||
T: Into<GraphicElement> + StaticType + 'static,
|
||||
{
|
||||
id: Vec<InstanceId>,
|
||||
instances: Vec<T>,
|
||||
#[serde(alias = "instances")]
|
||||
instance: Vec<T>,
|
||||
#[serde(default = "one_daffine2_default")]
|
||||
transform: Vec<DAffine2>,
|
||||
#[serde(default = "one_alpha_blending_default")]
|
||||
alpha_blending: Vec<AlphaBlending>,
|
||||
}
|
||||
|
||||
impl<T: Into<GraphicElement> + StaticType + 'static> Instances<T> {
|
||||
pub fn new(instance: T) -> Self {
|
||||
Self {
|
||||
id: vec![InstanceId::generate()],
|
||||
instances: vec![instance],
|
||||
instance: vec![instance],
|
||||
transform: vec![DAffine2::IDENTITY],
|
||||
alpha_blending: vec![AlphaBlending::default()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one_item(&self) -> &T {
|
||||
self.instances.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item)", self.instances.len()))
|
||||
pub fn one_instance(&self) -> Instance<T> {
|
||||
Instance {
|
||||
id: self.id.first().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", self.instance.len())),
|
||||
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())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn one_item_mut(&mut self) -> &mut T {
|
||||
let length = self.instances.len();
|
||||
self.instances.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {} (one_item_mut)", length))
|
||||
pub fn one_instance_mut(&mut self) -> InstanceMut<T> {
|
||||
let length = self.instance.len();
|
||||
|
||||
InstanceMut {
|
||||
id: self.id.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
instance: self.instance.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
transform: self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
alpha_blending: self.alpha_blending.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED, FOUND {}", length)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instances(&self) -> impl Iterator<Item = &T> {
|
||||
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instances.len());
|
||||
self.instances.iter()
|
||||
pub fn instances(&self) -> impl Iterator<Item = Instance<T>> {
|
||||
assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances)", self.instance.len());
|
||||
self.id
|
||||
.iter()
|
||||
.zip(self.instance.iter())
|
||||
.zip(self.transform.iter())
|
||||
.zip(self.alpha_blending.iter())
|
||||
.map(|(((id, instance), transform), alpha_blending)| Instance {
|
||||
id,
|
||||
instance,
|
||||
transform,
|
||||
alpha_blending,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn instances_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||
assert!(self.instances.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instances.len());
|
||||
self.instances.iter_mut()
|
||||
pub fn instances_mut(&mut self) -> impl Iterator<Item = InstanceMut<T>> {
|
||||
assert!(self.instance.len() == 1, "ONE INSTANCE EXPECTED, FOUND {} (instances_mut)", self.instance.len());
|
||||
self.id
|
||||
.iter_mut()
|
||||
.zip(self.instance.iter_mut())
|
||||
.zip(self.transform.iter_mut())
|
||||
.zip(self.alpha_blending.iter_mut())
|
||||
.map(|(((id, instance), transform), alpha_blending)| InstanceMut {
|
||||
id,
|
||||
instance,
|
||||
transform,
|
||||
alpha_blending,
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn id(&self) -> impl Iterator<Item = InstanceId> + '_ {
|
||||
// self.id.iter().copied()
|
||||
// }
|
||||
|
||||
// pub fn push(&mut self, id: InstanceId, instance: T) {
|
||||
// self.id.push(id);
|
||||
// self.instances.push(instance);
|
||||
// }
|
||||
|
||||
// pub fn replace_all(&mut self, id: InstanceId, instance: T) {
|
||||
// let mut instance = instance;
|
||||
|
||||
// for (old_id, old_instance) in self.id.iter_mut().zip(self.instances.iter_mut()) {
|
||||
// let mut new_id = id;
|
||||
// std::mem::swap(old_id, &mut new_id);
|
||||
// std::mem::swap(&mut instance, old_instance);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default for Instances<T> {
|
||||
|
@ -70,7 +93,7 @@ impl<T: Into<GraphicElement> + Default + Hash + StaticType + 'static> Default fo
|
|||
impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for Instances<T> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
for instance in &self.instances {
|
||||
for instance in &self.instance {
|
||||
instance.hash(state);
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +101,208 @@ impl<T: Into<GraphicElement> + Hash + StaticType + 'static> core::hash::Hash for
|
|||
|
||||
impl<T: Into<GraphicElement> + PartialEq + StaticType + 'static> PartialEq for Instances<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id && self.instances.len() == other.instances.len() && { self.instances.iter().zip(other.instances.iter()).all(|(a, b)| a == b) }
|
||||
self.id == other.id && self.instance.len() == other.instance.len() && { self.instance.iter().zip(other.instance.iter()).all(|(a, b)| a == b) }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T: Into<GraphicElement> + StaticType + 'static> dyn_any::StaticType for Instances<T> {
|
||||
type Static = Instances<T>;
|
||||
}
|
||||
|
||||
fn one_daffine2_default() -> Vec<DAffine2> {
|
||||
vec![DAffine2::IDENTITY]
|
||||
}
|
||||
fn one_alpha_blending_default() -> Vec<AlphaBlending> {
|
||||
vec![AlphaBlending::default()]
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Instance<'a, T> {
|
||||
pub id: &'a InstanceId,
|
||||
pub instance: &'a T,
|
||||
pub transform: &'a DAffine2,
|
||||
pub alpha_blending: &'a AlphaBlending,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct InstanceMut<'a, T> {
|
||||
pub id: &'a mut InstanceId,
|
||||
pub instance: &'a mut T,
|
||||
pub transform: &'a mut DAffine2,
|
||||
pub alpha_blending: &'a mut AlphaBlending,
|
||||
}
|
||||
|
||||
// GRAPHIC ELEMENT
|
||||
impl Transform for GraphicElement {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
GraphicElement::GraphicGroup(group) => group.transform(),
|
||||
GraphicElement::VectorData(vector_data) => vector_data.transform(),
|
||||
GraphicElement::RasterFrame(frame) => frame.transform(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for GraphicElement {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
match self {
|
||||
GraphicElement::GraphicGroup(group) => group.transform_mut(),
|
||||
GraphicElement::VectorData(vector_data) => vector_data.transform_mut(),
|
||||
GraphicElement::RasterFrame(frame) => frame.transform_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GRAPHIC GROUP
|
||||
impl Transform for Instance<'_, GraphicGroup> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
}
|
||||
impl Transform for InstanceMut<'_, GraphicGroup> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for InstanceMut<'_, GraphicGroup> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// GRAPHIC GROUP TABLE
|
||||
impl Transform for GraphicGroupTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.one_instance().transform()
|
||||
}
|
||||
}
|
||||
impl TransformMut for GraphicGroupTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
|
||||
}
|
||||
}
|
||||
|
||||
// TEXTURE FRAME
|
||||
impl Transform for Instance<'_, TextureFrame> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
}
|
||||
impl Transform for InstanceMut<'_, TextureFrame> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for InstanceMut<'_, TextureFrame> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// TEXTURE FRAME TABLE
|
||||
impl Transform for TextureFrameTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.one_instance().transform()
|
||||
}
|
||||
}
|
||||
impl TransformMut for TextureFrameTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
|
||||
}
|
||||
}
|
||||
|
||||
// IMAGE FRAME
|
||||
impl<P: Pixel> Transform for Instance<'_, ImageFrame<P>> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(pivot)
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> Transform for InstanceMut<'_, ImageFrame<P>> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(pivot)
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> TransformMut for InstanceMut<'_, ImageFrame<P>> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// IMAGE FRAME TABLE
|
||||
impl<P: Pixel> Transform for ImageFrameTable<P>
|
||||
where
|
||||
P: dyn_any::StaticType,
|
||||
P::Static: Pixel,
|
||||
GraphicElement: From<ImageFrame<P>>,
|
||||
{
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.one_instance().transform()
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> TransformMut for ImageFrameTable<P>
|
||||
where
|
||||
P: dyn_any::StaticType,
|
||||
P::Static: Pixel,
|
||||
GraphicElement: From<ImageFrame<P>>,
|
||||
{
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
|
||||
}
|
||||
}
|
||||
|
||||
// VECTOR DATA
|
||||
impl Transform for Instance<'_, VectorData> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(self.instance.layerspace_pivot(pivot))
|
||||
}
|
||||
}
|
||||
impl Transform for InstanceMut<'_, VectorData> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
*self.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(self.instance.layerspace_pivot(pivot))
|
||||
}
|
||||
}
|
||||
impl TransformMut for InstanceMut<'_, VectorData> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// VECTOR DATA TABLE
|
||||
impl Transform for VectorDataTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.one_instance().transform()
|
||||
}
|
||||
}
|
||||
impl TransformMut for VectorDataTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
self.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED"))
|
||||
}
|
||||
}
|
||||
|
||||
// RASTER FRAME
|
||||
impl Transform for RasterFrame {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_frame) => image_frame.transform(),
|
||||
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for RasterFrame {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
match self {
|
||||
RasterFrame::ImageFrame(image_frame) => image_frame.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")),
|
||||
RasterFrame::TextureFrame(texture_frame) => texture_frame.transform.first_mut().unwrap_or_else(|| panic!("ONE INSTANCE EXPECTED")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ impl<T: serde::Serialize + for<'a> serde::Deserialize<'a>> Serde for T {}
|
|||
impl<T> Serde for T {}
|
||||
|
||||
// TODO: Come up with a better name for this trait
|
||||
pub trait Pixel: Clone + Pod + Zeroable {
|
||||
pub trait Pixel: Clone + Pod + Zeroable + Default {
|
||||
#[cfg(not(target_arch = "spirv"))]
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
bytemuck::bytes_of(self).to_vec()
|
||||
|
|
|
@ -605,17 +605,15 @@ impl Blend<Color> for ImageFrameTable<Color> {
|
|||
let mut result = self.clone();
|
||||
|
||||
for (over, under) in result.instances_mut().zip(under.instances()) {
|
||||
let data = over.image.data.iter().zip(under.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
let data = over.instance.image.data.iter().zip(under.instance.image.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
|
||||
|
||||
*over = ImageFrame {
|
||||
*over.instance = ImageFrame {
|
||||
image: super::Image {
|
||||
data,
|
||||
width: over.image.width,
|
||||
height: over.image.height,
|
||||
width: over.instance.image.width,
|
||||
height: over.instance.image.height,
|
||||
base64_string: None,
|
||||
},
|
||||
transform: over.transform,
|
||||
alpha_blending: over.alpha_blending,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -744,7 +742,7 @@ where
|
|||
{
|
||||
fn adjust(&mut self, map_fn: impl Fn(&P) -> P) {
|
||||
for instance in self.instances_mut() {
|
||||
for c in instance.image.data.iter_mut() {
|
||||
for c in instance.instance.image.data.iter_mut() {
|
||||
*c = map_fn(c);
|
||||
}
|
||||
}
|
||||
|
@ -1582,10 +1580,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn color_overlay_multiply() {
|
||||
let image_color = Color::from_rgbaf32_unchecked(0.7, 0.6, 0.5, 0.4);
|
||||
let image = ImageFrame {
|
||||
image: Image::new(1, 1, image_color),
|
||||
..Default::default()
|
||||
};
|
||||
let image = ImageFrame { image: Image::new(1, 1, image_color) };
|
||||
|
||||
// Color { red: 0., green: 1., blue: 0., alpha: 1. }
|
||||
let overlay_color = Color::GREEN;
|
||||
|
@ -1594,7 +1589,7 @@ mod test {
|
|||
let opacity = 100_f64;
|
||||
|
||||
let result = super::color_overlay((), ImageFrameTable::new(image.clone()), overlay_color, BlendMode::Multiply, opacity);
|
||||
let result = result.one_item();
|
||||
let result = result.one_instance().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.image.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use core::hash::Hash;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use dyn_any::DynAny;
|
||||
|
||||
use crate::raster::image::ImageFrame;
|
||||
use crate::graphene_core::raster::image::ImageFrameTable;
|
||||
use crate::raster::Image;
|
||||
use crate::vector::brush_stroke::BrushStroke;
|
||||
use crate::vector::brush_stroke::BrushStyle;
|
||||
use crate::Color;
|
||||
|
||||
use core::hash::Hash;
|
||||
use dyn_any::DynAny;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
struct BrushCacheImpl {
|
||||
|
@ -18,9 +17,12 @@ struct BrushCacheImpl {
|
|||
prev_input: Vec<BrushStroke>,
|
||||
|
||||
// The strokes that have been fully processed and blended into the background.
|
||||
background: ImageFrame<Color>,
|
||||
blended_image: ImageFrame<Color>,
|
||||
last_stroke_texture: ImageFrame<Color>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
|
||||
background: ImageFrameTable<Color>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
|
||||
blended_image: ImageFrameTable<Color>,
|
||||
#[cfg_attr(feature = "serde", serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame"))]
|
||||
last_stroke_texture: ImageFrameTable<Color>,
|
||||
|
||||
// A cache for brush textures.
|
||||
#[cfg_attr(feature = "serde", serde(skip))]
|
||||
|
@ -28,9 +30,9 @@ struct BrushCacheImpl {
|
|||
}
|
||||
|
||||
impl BrushCacheImpl {
|
||||
fn compute_brush_plan(&mut self, mut background: ImageFrame<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
fn compute_brush_plan(&mut self, mut background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
// Do background invalidation.
|
||||
if background.transform != self.background.transform || background.image != self.background.image {
|
||||
if background.one_instance().instance.image != self.background.one_instance().instance.image {
|
||||
self.background = background.clone();
|
||||
return BrushPlan {
|
||||
strokes: input.to_vec(),
|
||||
|
@ -55,7 +57,7 @@ impl BrushCacheImpl {
|
|||
background = core::mem::take(&mut self.blended_image);
|
||||
|
||||
// Check if the first non-blended stroke is an extension of the last one.
|
||||
let mut first_stroke_texture = ImageFrame::default();
|
||||
let mut first_stroke_texture = ImageFrameTable::empty();
|
||||
let mut first_stroke_point_skip = 0;
|
||||
let strokes = input[num_blended_strokes..].to_vec();
|
||||
if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes {
|
||||
|
@ -79,7 +81,7 @@ impl BrushCacheImpl {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrame<Color>, last_stroke_texture: ImageFrame<Color>) {
|
||||
pub fn cache_results(&mut self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
|
||||
self.prev_input = input;
|
||||
self.blended_image = blended_image;
|
||||
self.last_stroke_texture = last_stroke_texture;
|
||||
|
@ -94,8 +96,8 @@ impl Hash for BrushCacheImpl {
|
|||
#[derive(Clone, Debug, Default)]
|
||||
pub struct BrushPlan {
|
||||
pub strokes: Vec<BrushStroke>,
|
||||
pub background: ImageFrame<Color>,
|
||||
pub first_stroke_texture: ImageFrame<Color>,
|
||||
pub background: ImageFrameTable<Color>,
|
||||
pub first_stroke_texture: ImageFrameTable<Color>,
|
||||
pub first_stroke_point_skip: usize,
|
||||
}
|
||||
|
||||
|
@ -159,12 +161,12 @@ impl BrushCache {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn compute_brush_plan(&self, background: ImageFrame<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
pub fn compute_brush_plan(&self, background: ImageFrameTable<Color>, input: &[BrushStroke]) -> BrushPlan {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.compute_brush_plan(background, input)
|
||||
}
|
||||
|
||||
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrame<Color>, last_stroke_texture: ImageFrame<Color>) {
|
||||
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: ImageFrameTable<Color>, last_stroke_texture: ImageFrameTable<Color>) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.cache_results(input, blended_image, last_stroke_texture)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::discrete_srgb::float_to_srgb_u8;
|
||||
use super::Color;
|
||||
use crate::instances::Instances;
|
||||
use crate::{instances::Instances, transform::TransformMut};
|
||||
use crate::{AlphaBlending, GraphicElement};
|
||||
use alloc::vec::Vec;
|
||||
use core::hash::{Hash, Hasher};
|
||||
|
@ -110,15 +110,6 @@ impl<P: Hash + Pixel> Hash for Image<P> {
|
|||
}
|
||||
|
||||
impl<P: Pixel> Image<P> {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: Vec::new(),
|
||||
base64_string: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(width: u32, height: u32, color: P) -> Self {
|
||||
Self {
|
||||
width,
|
||||
|
@ -221,47 +212,50 @@ impl<P: Pixel> IntoIterator for Image<P> {
|
|||
pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<ImageFrameTable<Color>, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldImageFrame<P: Pixel> {
|
||||
image: Image<P>,
|
||||
transform: DAffine2,
|
||||
alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum EitherFormat {
|
||||
ImageFrame(ImageFrame<Color>),
|
||||
OldImageFrame(OldImageFrame<Color>),
|
||||
ImageFrameTable(ImageFrameTable<Color>),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::ImageFrame(image_frame) => ImageFrameTable::<Color>::new(image_frame),
|
||||
EitherFormat::OldImageFrame(image_frame_with_transform_and_blending) => {
|
||||
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
|
||||
let mut image_frame_table = ImageFrameTable::new(ImageFrame { image });
|
||||
*image_frame_table.one_instance_mut().transform = transform;
|
||||
*image_frame_table.one_instance_mut().alpha_blending = alpha_blending;
|
||||
image_frame_table
|
||||
}
|
||||
EitherFormat::ImageFrameTable(image_frame_table) => image_frame_table,
|
||||
})
|
||||
}
|
||||
|
||||
pub type ImageFrameTable<P> = Instances<ImageFrame<P>>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, specta::Type)]
|
||||
/// Construct a 0x0 image frame table. This is useful because ImageFrameTable::default() will return a 1x1 image frame table.
|
||||
impl ImageFrameTable<Color> {
|
||||
pub fn empty() -> Self {
|
||||
let mut result = Self::new(ImageFrame::default());
|
||||
*result.transform_mut() = DAffine2::ZERO;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ImageFrame<P: Pixel> {
|
||||
pub image: Image<P>,
|
||||
// The transform that maps image space to layer space.
|
||||
//
|
||||
// Image space is unitless [0, 1] for both axes, with x axis positive
|
||||
// going right and y axis positive going down, with the origin lying at
|
||||
// the topleft of the image and (1, 1) lying at the bottom right of the image.
|
||||
//
|
||||
// Layer space has pixels as its units for both axes, with the x axis
|
||||
// positive going right and y axis positive going down, with the origin
|
||||
// being an unspecified quantity.
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
}
|
||||
|
||||
impl<P: Pixel> Default for ImageFrame<P> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
image: Image::empty(),
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
// Different from DAffine2::default() which is IDENTITY
|
||||
transform: DAffine2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
||||
|
@ -271,7 +265,6 @@ impl<P: Debug + Copy + Pixel> Sample for ImageFrame<P> {
|
|||
#[inline(always)]
|
||||
fn sample(&self, pos: DVec2, _area: DVec2) -> Option<Self::Pixel> {
|
||||
let image_size = DVec2::new(self.image.width() as f64, self.image.height() as f64);
|
||||
let pos = (DAffine2::from_scale(image_size) * self.transform.inverse()).transform_point2(pos);
|
||||
if pos.x < 0. || pos.y < 0. || pos.x >= image_size.x || pos.y >= image_size.y {
|
||||
return None;
|
||||
}
|
||||
|
@ -289,7 +282,11 @@ where
|
|||
// TODO: Improve sampling logic
|
||||
#[inline(always)]
|
||||
fn sample(&self, pos: DVec2, area: DVec2) -> Option<Self::Pixel> {
|
||||
let image = self.one_item();
|
||||
let image_transform = self.one_instance().transform;
|
||||
let image = self.one_instance().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);
|
||||
|
||||
Sample::sample(image, pos, area)
|
||||
}
|
||||
|
@ -319,19 +316,19 @@ where
|
|||
type Pixel = P;
|
||||
|
||||
fn width(&self) -> u32 {
|
||||
let image = self.one_item();
|
||||
let image = self.one_instance().instance;
|
||||
|
||||
image.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
let image = self.one_item();
|
||||
let image = self.one_instance().instance;
|
||||
|
||||
image.height()
|
||||
}
|
||||
|
||||
fn get_pixel(&self, x: u32, y: u32) -> Option<Self::Pixel> {
|
||||
let image = self.one_item();
|
||||
let image = self.one_instance().instance;
|
||||
|
||||
image.get_pixel(x, y)
|
||||
}
|
||||
|
@ -349,7 +346,7 @@ where
|
|||
P::Static: Pixel,
|
||||
{
|
||||
fn get_pixel_mut(&mut self, x: u32, y: u32) -> Option<&mut Self::Pixel> {
|
||||
let image = self.one_item_mut();
|
||||
let image = self.one_instance_mut().instance;
|
||||
|
||||
BitmapMut::get_pixel_mut(image, x, y)
|
||||
}
|
||||
|
@ -384,19 +381,11 @@ impl<P: Pixel> AsRef<ImageFrame<P>> for ImageFrame<P> {
|
|||
|
||||
impl<P: Hash + Pixel> Hash for ImageFrame<P> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
0.hash(state);
|
||||
self.image.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Pixel> ImageFrame<P> {
|
||||
/// Compute the pivot in local space with the current transform applied
|
||||
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(normalized_pivot)
|
||||
}
|
||||
}
|
||||
|
||||
/* This does not work because of missing specialization
|
||||
* so we have to manually implement this for now
|
||||
impl<S: Into<P> + Pixel, P: Pixel> From<Image<S>> for Image<P> {
|
||||
|
@ -420,8 +409,6 @@ impl From<ImageFrame<Color>> for ImageFrame<SRGBA8> {
|
|||
height: image.image.height,
|
||||
base64_string: None,
|
||||
},
|
||||
transform: image.transform,
|
||||
alpha_blending: image.alpha_blending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -436,8 +423,6 @@ impl From<ImageFrame<SRGBA8>> for ImageFrame<Color> {
|
|||
height: image.image.height,
|
||||
base64_string: None,
|
||||
},
|
||||
transform: image.transform,
|
||||
alpha_blending: image.alpha_blending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use crate::application_io::TextureFrameTable;
|
||||
use crate::raster::bbox::AxisAlignedBbox;
|
||||
use crate::raster::image::{ImageFrame, ImageFrameTable};
|
||||
use crate::raster::Pixel;
|
||||
use crate::vector::{VectorData, VectorDataTable};
|
||||
use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
|
||||
use crate::raster::image::ImageFrameTable;
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Artboard, ArtboardGroup, CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicGroupTable, OwnedContextImpl};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
|
@ -34,153 +33,6 @@ impl<T: Transform> Transform for &T {
|
|||
}
|
||||
}
|
||||
|
||||
// Implementations for ImageFrame<P>
|
||||
impl<P: Pixel> Transform for ImageFrame<P> {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
self.local_pivot(pivot)
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> TransformMut for ImageFrame<P> {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for ImageFrameTable<P>
|
||||
impl<P: Pixel> Transform for ImageFrameTable<P>
|
||||
where
|
||||
P: dyn_any::StaticType,
|
||||
P::Static: Pixel,
|
||||
GraphicElement: From<ImageFrame<P>>,
|
||||
{
|
||||
fn transform(&self) -> DAffine2 {
|
||||
let image_frame = self.one_item();
|
||||
image_frame.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
let image_frame = self.one_item();
|
||||
image_frame.local_pivot(pivot)
|
||||
}
|
||||
}
|
||||
impl<P: Pixel> TransformMut for ImageFrameTable<P>
|
||||
where
|
||||
P: dyn_any::StaticType,
|
||||
P::Static: Pixel,
|
||||
GraphicElement: From<ImageFrame<P>>,
|
||||
{
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
let image_frame = self.one_item_mut();
|
||||
&mut image_frame.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for TextureTable
|
||||
impl Transform for TextureFrameTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
let image_frame = self.one_item();
|
||||
image_frame.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
let image_frame = self.one_item();
|
||||
image_frame.local_pivot(pivot)
|
||||
}
|
||||
}
|
||||
impl TransformMut for TextureFrameTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
let image_frame = self.one_item_mut();
|
||||
&mut image_frame.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for GraphicGroup
|
||||
impl Transform for GraphicGroup {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for GraphicGroup {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for GraphicGroupTable
|
||||
impl Transform for GraphicGroupTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
let graphic_group = self.one_item();
|
||||
graphic_group.transform
|
||||
}
|
||||
}
|
||||
impl TransformMut for GraphicGroupTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
let graphic_group = self.one_item_mut();
|
||||
&mut graphic_group.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for GraphicElement
|
||||
impl Transform for GraphicElement {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
match self {
|
||||
GraphicElement::VectorData(vector_shape) => vector_shape.transform(),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform(),
|
||||
GraphicElement::RasterFrame(raster) => raster.transform(),
|
||||
}
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
match self {
|
||||
GraphicElement::VectorData(vector_shape) => vector_shape.local_pivot(pivot),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.local_pivot(pivot),
|
||||
GraphicElement::RasterFrame(raster) => raster.local_pivot(pivot),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TransformMut for GraphicElement {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
match self {
|
||||
GraphicElement::VectorData(vector_shape) => vector_shape.transform_mut(),
|
||||
GraphicElement::GraphicGroup(graphic_group) => graphic_group.transform_mut(),
|
||||
GraphicElement::RasterFrame(raster) => raster.transform_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for VectorData
|
||||
impl Transform for VectorData {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
self.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
self.local_pivot(pivot)
|
||||
}
|
||||
}
|
||||
impl TransformMut for VectorData {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
&mut self.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for VectorDataTable
|
||||
impl Transform for VectorDataTable {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
let vector_data = self.one_item();
|
||||
vector_data.transform
|
||||
}
|
||||
fn local_pivot(&self, pivot: DVec2) -> DVec2 {
|
||||
let vector_data = self.one_item();
|
||||
vector_data.local_pivot(pivot)
|
||||
}
|
||||
}
|
||||
impl TransformMut for VectorDataTable {
|
||||
fn transform_mut(&mut self) -> &mut DAffine2 {
|
||||
let vector_data = self.one_item_mut();
|
||||
&mut vector_data.transform
|
||||
}
|
||||
}
|
||||
|
||||
// Implementations for Artboard
|
||||
impl Transform for Artboard {
|
||||
fn transform(&self) -> DAffine2 {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::vector::{HandleId, PointId, VectorData, VectorDataTable};
|
||||
use crate::vector::{HandleId, VectorData, VectorDataTable};
|
||||
use crate::Ctx;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
|
@ -105,15 +105,3 @@ fn star(
|
|||
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: DVec2, #[default((0., 50.))] end: DVec2) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||
#[node_macro::node(category(""))]
|
||||
fn path(_: impl Ctx, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
||||
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||
vector_data.colinear_manipulators = colinear_manipulators
|
||||
.iter()
|
||||
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||
.collect();
|
||||
|
||||
VectorDataTable::new(vector_data)
|
||||
}
|
||||
|
|
|
@ -17,16 +17,50 @@ use glam::{DAffine2, DVec2};
|
|||
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<VectorDataTable, D::Error> {
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct OldVectorData {
|
||||
pub transform: DAffine2,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
|
||||
pub style: PathStyle,
|
||||
|
||||
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
||||
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
|
||||
pub colinear_manipulators: Vec<[HandleId; 2]>,
|
||||
|
||||
pub point_domain: PointDomain,
|
||||
pub segment_domain: SegmentDomain,
|
||||
pub region_domain: RegionDomain,
|
||||
|
||||
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
|
||||
pub upstream_graphic_group: Option<GraphicGroupTable>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum EitherFormat {
|
||||
VectorData(VectorData),
|
||||
OldVectorData(OldVectorData),
|
||||
VectorDataTable(VectorDataTable),
|
||||
}
|
||||
|
||||
Ok(match EitherFormat::deserialize(deserializer)? {
|
||||
EitherFormat::VectorData(vector_data) => VectorDataTable::new(vector_data),
|
||||
EitherFormat::OldVectorData(old) => {
|
||||
let mut vector_data_table = VectorDataTable::new(VectorData {
|
||||
style: old.style,
|
||||
colinear_manipulators: old.colinear_manipulators,
|
||||
point_domain: old.point_domain,
|
||||
segment_domain: old.segment_domain,
|
||||
region_domain: old.region_domain,
|
||||
upstream_graphic_group: old.upstream_graphic_group,
|
||||
});
|
||||
*vector_data_table.one_instance_mut().transform = old.transform;
|
||||
*vector_data_table.one_instance_mut().alpha_blending = old.alpha_blending;
|
||||
vector_data_table
|
||||
}
|
||||
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
|
||||
})
|
||||
}
|
||||
|
@ -38,9 +72,8 @@ pub type VectorDataTable = Instances<VectorData>;
|
|||
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct VectorData {
|
||||
pub transform: DAffine2,
|
||||
pub style: PathStyle,
|
||||
pub alpha_blending: AlphaBlending,
|
||||
|
||||
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
||||
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
|
||||
pub colinear_manipulators: Vec<[HandleId; 2]>,
|
||||
|
@ -58,20 +91,17 @@ impl core::hash::Hash for VectorData {
|
|||
self.point_domain.hash(state);
|
||||
self.segment_domain.hash(state);
|
||||
self.region_domain.hash(state);
|
||||
self.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
self.style.hash(state);
|
||||
self.alpha_blending.hash(state);
|
||||
self.colinear_manipulators.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl VectorData {
|
||||
/// An empty subpath with no data, an identity transform, and a black fill.
|
||||
// TODO: Replace with just `Default`
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
transform: DAffine2::IDENTITY,
|
||||
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
|
||||
alpha_blending: AlphaBlending::new(),
|
||||
colinear_manipulators: Vec::new(),
|
||||
point_domain: PointDomain::new(),
|
||||
segment_domain: SegmentDomain::new(),
|
||||
|
@ -190,11 +220,6 @@ impl VectorData {
|
|||
bounds_min + bounds_size * normalized_pivot
|
||||
}
|
||||
|
||||
/// Compute the pivot in local space with the current transform applied
|
||||
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(self.layerspace_pivot(normalized_pivot))
|
||||
}
|
||||
|
||||
pub fn start_point(&self) -> impl Iterator<Item = PointId> + '_ {
|
||||
self.segment_domain.start_point().iter().map(|&index| self.point_domain.ids()[index])
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::transform::Transform;
|
||||
use crate::vector::vector_data::{HandleId, VectorData, VectorDataTable};
|
||||
use crate::vector::ConcatElement;
|
||||
|
||||
|
@ -82,32 +83,30 @@ impl core::hash::BuildHasher for NoHashBuilder {
|
|||
/// Stores data which is per-point. Each point is merely a position and can be used in a point cloud or to for a bézier path. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct PointDomain {
|
||||
id: Vec<PointId>,
|
||||
positions: Vec<DVec2>,
|
||||
#[serde(alias = "positions")]
|
||||
position: Vec<DVec2>,
|
||||
}
|
||||
|
||||
impl core::hash::Hash for PointDomain {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.positions.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state));
|
||||
self.position.iter().for_each(|pos| pos.to_array().map(|v| v.to_bits()).hash(state));
|
||||
}
|
||||
}
|
||||
|
||||
impl PointDomain {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
id: Vec::new(),
|
||||
positions: Vec::new(),
|
||||
}
|
||||
Self { id: Vec::new(), position: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.id.clear();
|
||||
self.positions.clear();
|
||||
self.position.clear();
|
||||
}
|
||||
|
||||
pub fn retain(&mut self, segment_domain: &mut SegmentDomain, f: impl Fn(&PointId) -> bool) {
|
||||
let mut keep = self.id.iter().map(&f);
|
||||
self.positions.retain(|_| keep.next().unwrap_or_default());
|
||||
self.position.retain(|_| keep.next().unwrap_or_default());
|
||||
|
||||
// TODO(TrueDoctor): Consider using a prefix sum to avoid this Vec allocation (https://github.com/GraphiteEditor/Graphite/pull/1949#discussion_r1741711562)
|
||||
let mut id_map = Vec::with_capacity(self.ids().len());
|
||||
|
@ -131,19 +130,19 @@ impl PointDomain {
|
|||
pub fn push(&mut self, id: PointId, position: DVec2) {
|
||||
debug_assert!(!self.id.contains(&id));
|
||||
self.id.push(id);
|
||||
self.positions.push(position);
|
||||
self.position.push(position);
|
||||
}
|
||||
|
||||
pub fn positions(&self) -> &[DVec2] {
|
||||
&self.positions
|
||||
&self.position
|
||||
}
|
||||
|
||||
pub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> {
|
||||
self.id.iter().copied().zip(self.positions.iter_mut())
|
||||
self.id.iter().copied().zip(self.position.iter_mut())
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, index: usize, position: DVec2) {
|
||||
self.positions[index] = position;
|
||||
self.position[index] = position;
|
||||
}
|
||||
|
||||
pub fn ids(&self) -> &[PointId] {
|
||||
|
@ -156,7 +155,7 @@ impl PointDomain {
|
|||
|
||||
#[track_caller]
|
||||
pub fn position_from_id(&self, id: PointId) -> Option<DVec2> {
|
||||
let pos = self.resolve_id(id).map(|index| self.positions[index]);
|
||||
let pos = self.resolve_id(id).map(|index| self.position[index]);
|
||||
if pos.is_none() {
|
||||
warn!("Resolving pos of invalid id");
|
||||
}
|
||||
|
@ -169,7 +168,7 @@ impl PointDomain {
|
|||
|
||||
fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {
|
||||
self.id.extend(other.id.iter().map(|id| *id_map.point_map.get(id).unwrap_or(id)));
|
||||
self.positions.extend(other.positions.iter().map(|&pos| transform.transform_point2(pos)));
|
||||
self.position.extend(other.position.iter().map(|&pos| transform.transform_point2(pos)));
|
||||
}
|
||||
|
||||
fn map_ids(&mut self, id_map: &IdMap) {
|
||||
|
@ -177,7 +176,7 @@ impl PointDomain {
|
|||
}
|
||||
|
||||
fn transform(&mut self, transform: DAffine2) {
|
||||
for pos in &mut self.positions {
|
||||
for pos in &mut self.position {
|
||||
*pos = transform.transform_point2(*pos);
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +186,8 @@ impl PointDomain {
|
|||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Stores data which is per-segment. A segment is a bézier curve between two end points with a stroke. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct SegmentDomain {
|
||||
ids: Vec<SegmentId>,
|
||||
#[serde(alias = "ids")]
|
||||
id: Vec<SegmentId>,
|
||||
start_point: Vec<usize>,
|
||||
end_point: Vec<usize>,
|
||||
handles: Vec<bezier_rs::BezierHandles>,
|
||||
|
@ -197,7 +197,7 @@ pub struct SegmentDomain {
|
|||
impl SegmentDomain {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ids: Vec::new(),
|
||||
id: Vec::new(),
|
||||
start_point: Vec::new(),
|
||||
end_point: Vec::new(),
|
||||
handles: Vec::new(),
|
||||
|
@ -206,7 +206,7 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.ids.clear();
|
||||
self.id.clear();
|
||||
self.start_point.clear();
|
||||
self.end_point.clear();
|
||||
self.handles.clear();
|
||||
|
@ -215,7 +215,7 @@ impl SegmentDomain {
|
|||
|
||||
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool, points_length: usize) {
|
||||
let additional_delete_ids = self
|
||||
.ids
|
||||
.id
|
||||
.iter()
|
||||
.zip(&self.start_point)
|
||||
.zip(&self.end_point)
|
||||
|
@ -236,17 +236,17 @@ impl SegmentDomain {
|
|||
}
|
||||
};
|
||||
|
||||
let mut keep = self.ids.iter().map(can_delete());
|
||||
let mut keep = self.id.iter().map(can_delete());
|
||||
self.start_point.retain(|_| keep.next().unwrap_or_default());
|
||||
let mut keep = self.ids.iter().map(can_delete());
|
||||
let mut keep = self.id.iter().map(can_delete());
|
||||
self.end_point.retain(|_| keep.next().unwrap_or_default());
|
||||
let mut keep = self.ids.iter().map(can_delete());
|
||||
let mut keep = self.id.iter().map(can_delete());
|
||||
self.handles.retain(|_| keep.next().unwrap_or_default());
|
||||
let mut keep = self.ids.iter().map(can_delete());
|
||||
let mut keep = self.id.iter().map(can_delete());
|
||||
self.stroke.retain(|_| keep.next().unwrap_or_default());
|
||||
|
||||
let mut delete_iter = additional_delete_ids.iter().peekable();
|
||||
self.ids.retain(move |id| {
|
||||
self.id.retain(move |id| {
|
||||
if delete_iter.peek() == Some(&id) {
|
||||
delete_iter.next();
|
||||
false
|
||||
|
@ -257,7 +257,7 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
pub fn ids(&self) -> &[SegmentId] {
|
||||
&self.ids
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn next_id(&self) -> SegmentId {
|
||||
|
@ -289,9 +289,9 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
|
||||
debug_assert!(!self.ids.contains(&id), "Tried to push an existing point to a point domain");
|
||||
debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain");
|
||||
|
||||
self.ids.push(id);
|
||||
self.id.push(id);
|
||||
self.start_point.push(start);
|
||||
self.end_point.push(end);
|
||||
self.handles.push(handles);
|
||||
|
@ -299,15 +299,15 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
pub(crate) fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {
|
||||
self.ids.iter().copied().zip(self.start_point.iter_mut())
|
||||
self.id.iter().copied().zip(self.start_point.iter_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut usize)> {
|
||||
self.ids.iter().copied().zip(self.end_point.iter_mut())
|
||||
self.id.iter().copied().zip(self.end_point.iter_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, usize, usize)> {
|
||||
let nested = self.ids.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);
|
||||
let nested = self.id.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);
|
||||
nested.map(|(((&a, b), &c), &d)| (a, b, c, d))
|
||||
}
|
||||
|
||||
|
@ -317,7 +317,7 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
pub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> {
|
||||
self.ids.iter().copied().zip(self.stroke.iter_mut())
|
||||
self.id.iter().copied().zip(self.stroke.iter_mut())
|
||||
}
|
||||
|
||||
pub(crate) fn segment_start_from_id(&self, segment: SegmentId) -> Option<usize> {
|
||||
|
@ -348,15 +348,15 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
fn id_to_index(&self, id: SegmentId) -> Option<usize> {
|
||||
debug_assert_eq!(self.ids.len(), self.handles.len());
|
||||
debug_assert_eq!(self.ids.len(), self.start_point.len());
|
||||
debug_assert_eq!(self.ids.len(), self.end_point.len());
|
||||
self.ids.iter().position(|&check_id| check_id == id)
|
||||
debug_assert_eq!(self.id.len(), self.handles.len());
|
||||
debug_assert_eq!(self.id.len(), self.start_point.len());
|
||||
debug_assert_eq!(self.id.len(), self.end_point.len());
|
||||
self.id.iter().position(|&check_id| check_id == id)
|
||||
}
|
||||
|
||||
fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> {
|
||||
match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) {
|
||||
(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.ids.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),
|
||||
(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.id.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),
|
||||
_ => {
|
||||
warn!("Resolving range with invalid id");
|
||||
None
|
||||
|
@ -365,7 +365,7 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
fn concat(&mut self, other: &Self, transform: DAffine2, id_map: &IdMap) {
|
||||
self.ids.extend(other.ids.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id)));
|
||||
self.id.extend(other.id.iter().map(|id| *id_map.segment_map.get(id).unwrap_or(id)));
|
||||
self.start_point.extend(other.start_point.iter().map(|&index| id_map.point_offset + index));
|
||||
self.end_point.extend(other.end_point.iter().map(|&index| id_map.point_offset + index));
|
||||
self.handles.extend(other.handles.iter().map(|handles| handles.apply_transformation(|p| transform.transform_point2(p))));
|
||||
|
@ -373,7 +373,7 @@ impl SegmentDomain {
|
|||
}
|
||||
|
||||
fn map_ids(&mut self, id_map: &IdMap) {
|
||||
self.ids.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
|
||||
self.id.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
|
||||
}
|
||||
|
||||
fn transform(&mut self, transform: DAffine2) {
|
||||
|
@ -384,12 +384,12 @@ impl SegmentDomain {
|
|||
|
||||
/// Enumerate all segments that start at the point.
|
||||
pub(crate) fn start_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
|
||||
self.start_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
||||
self.start_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
||||
}
|
||||
|
||||
/// Enumerate all segments that end at the point.
|
||||
pub(crate) fn end_connected(&self, point: usize) -> impl Iterator<Item = SegmentId> + '_ {
|
||||
self.end_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
||||
self.end_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
||||
}
|
||||
|
||||
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
|
||||
|
@ -407,7 +407,8 @@ impl SegmentDomain {
|
|||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// Stores data which is per-region. A region is an enclosed area composed of a range of segments from the [`SegmentDomain`] that can be given a fill. In future this will be extendable at runtime with custom attributes.
|
||||
pub struct RegionDomain {
|
||||
ids: Vec<RegionId>,
|
||||
#[serde(alias = "ids")]
|
||||
id: Vec<RegionId>,
|
||||
segment_range: Vec<core::ops::RangeInclusive<SegmentId>>,
|
||||
fill: Vec<FillId>,
|
||||
}
|
||||
|
@ -415,54 +416,54 @@ pub struct RegionDomain {
|
|||
impl RegionDomain {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
ids: Vec::new(),
|
||||
id: Vec::new(),
|
||||
segment_range: Vec::new(),
|
||||
fill: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.ids.clear();
|
||||
self.id.clear();
|
||||
self.segment_range.clear();
|
||||
self.fill.clear();
|
||||
}
|
||||
|
||||
pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) {
|
||||
let mut keep = self.ids.iter().map(&f);
|
||||
let mut keep = self.id.iter().map(&f);
|
||||
self.segment_range.retain(|_| keep.next().unwrap_or_default());
|
||||
let mut keep = self.ids.iter().map(&f);
|
||||
let mut keep = self.id.iter().map(&f);
|
||||
self.fill.retain(|_| keep.next().unwrap_or_default());
|
||||
self.ids.retain(&f);
|
||||
self.id.retain(&f);
|
||||
}
|
||||
|
||||
pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) {
|
||||
if self.ids.contains(&id) {
|
||||
if self.id.contains(&id) {
|
||||
warn!("Duplicate region");
|
||||
return;
|
||||
}
|
||||
self.ids.push(id);
|
||||
self.id.push(id);
|
||||
self.segment_range.push(segment_range);
|
||||
self.fill.push(fill);
|
||||
}
|
||||
|
||||
fn _resolve_id(&self, id: RegionId) -> Option<usize> {
|
||||
self.ids.iter().position(|&check_id| check_id == id)
|
||||
self.id.iter().position(|&check_id| check_id == id)
|
||||
}
|
||||
|
||||
pub fn next_id(&self) -> RegionId {
|
||||
self.ids.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)
|
||||
self.id.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)
|
||||
}
|
||||
|
||||
pub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut core::ops::RangeInclusive<SegmentId>)> {
|
||||
self.ids.iter().copied().zip(self.segment_range.iter_mut())
|
||||
self.id.iter().copied().zip(self.segment_range.iter_mut())
|
||||
}
|
||||
|
||||
pub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> {
|
||||
self.ids.iter().copied().zip(self.fill.iter_mut())
|
||||
self.id.iter().copied().zip(self.fill.iter_mut())
|
||||
}
|
||||
|
||||
pub fn ids(&self) -> &[RegionId] {
|
||||
&self.ids
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn segment_range(&self) -> &[core::ops::RangeInclusive<SegmentId>] {
|
||||
|
@ -474,7 +475,7 @@ impl RegionDomain {
|
|||
}
|
||||
|
||||
fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {
|
||||
self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
|
||||
self.id.extend(other.id.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
|
||||
self.segment_range.extend(
|
||||
other
|
||||
.segment_range
|
||||
|
@ -485,7 +486,7 @@ impl RegionDomain {
|
|||
}
|
||||
|
||||
fn map_ids(&mut self, id_map: &IdMap) {
|
||||
self.ids.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id));
|
||||
self.id.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id));
|
||||
self.segment_range
|
||||
.iter_mut()
|
||||
.for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end()));
|
||||
|
@ -525,7 +526,7 @@ impl VectorData {
|
|||
self.segment_domain
|
||||
.handles
|
||||
.iter()
|
||||
.zip(&self.segment_domain.ids)
|
||||
.zip(&self.segment_domain.id)
|
||||
.zip(self.segment_domain.start_point())
|
||||
.zip(self.segment_domain.end_point())
|
||||
.map(to_bezier)
|
||||
|
@ -574,7 +575,7 @@ impl VectorData {
|
|||
/// Construct a [`bezier_rs::Bezier`] curve for each region, skipping invalid regions.
|
||||
pub fn region_bezier_paths(&self) -> impl Iterator<Item = (RegionId, bezier_rs::Subpath<PointId>)> + '_ {
|
||||
self.region_domain
|
||||
.ids
|
||||
.id
|
||||
.iter()
|
||||
.zip(&self.region_domain.segment_range)
|
||||
.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))
|
||||
|
@ -774,16 +775,16 @@ impl ConcatElement for VectorData {
|
|||
let point_map = new_ids.collect::<HashMap<_, _>>();
|
||||
let new_ids = other
|
||||
.segment_domain
|
||||
.ids
|
||||
.id
|
||||
.iter()
|
||||
.filter(|id| self.segment_domain.ids.contains(id))
|
||||
.filter(|id| self.segment_domain.id.contains(id))
|
||||
.map(|&old| (old, old.generate_from_hash(node_id)));
|
||||
let segment_map = new_ids.collect::<HashMap<_, _>>();
|
||||
let new_ids = other
|
||||
.region_domain
|
||||
.ids
|
||||
.id
|
||||
.iter()
|
||||
.filter(|id| self.region_domain.ids.contains(id))
|
||||
.filter(|id| self.region_domain.id.contains(id))
|
||||
.map(|&old| (old, old.generate_from_hash(node_id)));
|
||||
let region_map = new_ids.collect::<HashMap<_, _>>();
|
||||
let id_map = IdMap {
|
||||
|
@ -792,20 +793,20 @@ impl ConcatElement for VectorData {
|
|||
segment_map,
|
||||
region_map,
|
||||
};
|
||||
self.point_domain.concat(&other.point_domain, transform * other.transform, &id_map);
|
||||
self.segment_domain.concat(&other.segment_domain, transform * other.transform, &id_map);
|
||||
self.region_domain.concat(&other.region_domain, transform * other.transform, &id_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);
|
||||
// TODO: properly deal with fills such as gradients
|
||||
self.style = other.style.clone();
|
||||
self.colinear_manipulators.extend(other.colinear_manipulators.iter().copied());
|
||||
self.alpha_blending = other.alpha_blending;
|
||||
}
|
||||
}
|
||||
|
||||
impl ConcatElement for VectorDataTable {
|
||||
fn concat(&mut self, other: &Self, transform: glam::DAffine2, node_id: u64) {
|
||||
for (instance, other_instance) in self.instances_mut().zip(other.instances()) {
|
||||
instance.concat(other_instance, transform, node_id);
|
||||
*instance.alpha_blending = *other_instance.alpha_blending;
|
||||
instance.instance.concat(other_instance.instance, transform * other_instance.transform(), node_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
use crate::transform::TransformMut;
|
||||
use crate::uuid::generate_uuid;
|
||||
use crate::Ctx;
|
||||
|
||||
|
@ -425,11 +426,14 @@ 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 = vector_data.one_item_mut();
|
||||
let vector_data_transform = *vector_data.one_instance().transform;
|
||||
let vector_data = vector_data.one_instance_mut().instance;
|
||||
|
||||
modification.apply(vector_data);
|
||||
|
||||
VectorDataTable::new(vector_data.clone())
|
||||
let mut result = VectorDataTable::new(vector_data.clone());
|
||||
*result.transform_mut() = vector_data_transform;
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use super::misc::CentroidType;
|
||||
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
||||
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable};
|
||||
use crate::instances::InstanceMut;
|
||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
|
||||
use crate::renderer::GraphicElementRendered;
|
||||
use crate::transform::{Footprint, Transform, TransformMut};
|
||||
use crate::vector::style::LineJoin;
|
||||
use crate::vector::PointDomain;
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroup, GraphicGroupTable, OwnedContextImpl};
|
||||
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl};
|
||||
|
||||
use bezier_rs::{Cap, Join, Subpath, SubpathTValue, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
@ -20,15 +21,16 @@ trait VectorIterMut {
|
|||
|
||||
impl VectorIterMut for GraphicGroupTable {
|
||||
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
|
||||
let instance = self.one_item_mut();
|
||||
|
||||
let parent_transform = instance.transform;
|
||||
let parent_transform = self.transform();
|
||||
let instance = self.one_instance_mut().instance;
|
||||
|
||||
// Grab only the direct children
|
||||
instance.iter_mut().filter_map(|(element, _)| element.as_vector_data_mut()).map(move |vector_data| {
|
||||
let vector_data = vector_data.one_item_mut();
|
||||
let transform = parent_transform * vector_data.transform;
|
||||
(vector_data, transform)
|
||||
let transform = parent_transform * vector_data.transform();
|
||||
|
||||
let vector_data_instance = vector_data.one_instance_mut().instance;
|
||||
|
||||
(vector_data_instance, transform)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -36,8 +38,8 @@ impl VectorIterMut for GraphicGroupTable {
|
|||
impl VectorIterMut for VectorDataTable {
|
||||
fn vector_iter_mut(&mut self) -> impl Iterator<Item = (&mut VectorData, DAffine2)> {
|
||||
self.instances_mut().map(|instance| {
|
||||
let transform = instance.transform;
|
||||
(instance, transform)
|
||||
let transform = instance.transform();
|
||||
(instance.instance, transform)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -176,11 +178,10 @@ async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send
|
|||
let instances = instances.max(1);
|
||||
let total = (instances - 1) as f64;
|
||||
|
||||
let mut result = GraphicGroup::default();
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let result = result_table.one_instance_mut().instance;
|
||||
|
||||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
|
||||
return GraphicGroupTable::new(result);
|
||||
};
|
||||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { return result_table };
|
||||
|
||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||
|
||||
|
@ -196,7 +197,7 @@ async fn repeat<I: 'n + GraphicElementRendered + Transform + TransformMut + Send
|
|||
result.push((new_instance, None));
|
||||
}
|
||||
|
||||
GraphicGroupTable::new(result)
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
|
@ -211,11 +212,10 @@ async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformM
|
|||
let first_vector_transform = instance.transform();
|
||||
let instances = instances.max(1);
|
||||
|
||||
let mut result = GraphicGroup::default();
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let result = result_table.one_instance_mut().instance;
|
||||
|
||||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else {
|
||||
return GraphicGroupTable::new(result);
|
||||
};
|
||||
let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY) else { return result_table };
|
||||
|
||||
let center = (bounding_box[0] + bounding_box[1]) / 2.;
|
||||
let base_transform = DVec2::new(0., radius) - center;
|
||||
|
@ -232,7 +232,7 @@ async fn circular_repeat<I: 'n + GraphicElementRendered + Transform + TransformM
|
|||
result.push((new_instance, None));
|
||||
}
|
||||
|
||||
GraphicGroupTable::new(result)
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
|
@ -249,7 +249,8 @@ async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
|
|||
random_rotation: Angle,
|
||||
random_rotation_seed: SeedValue,
|
||||
) -> GraphicGroupTable {
|
||||
let points = points.one_item();
|
||||
let points_transform = points.transform();
|
||||
let points = points.one_instance().instance;
|
||||
|
||||
let instance_transform = instance.transform();
|
||||
|
||||
|
@ -266,11 +267,13 @@ async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
|
|||
let do_scale = random_scale_difference.abs() > 1e-6;
|
||||
let do_rotation = random_rotation.abs() > 1e-6;
|
||||
|
||||
let mut result = GraphicGroup::default();
|
||||
let mut result_table = GraphicGroupTable::default();
|
||||
let result = result_table.one_instance_mut().instance;
|
||||
|
||||
for &point in points_list {
|
||||
let center_transform = DAffine2::from_translation(instance_center);
|
||||
|
||||
let translation = points.transform.transform_point2(point);
|
||||
let translation = points_transform.transform_point2(point);
|
||||
|
||||
let rotation = if do_rotation {
|
||||
let degrees = (rotation_rng.random::<f64>() - 0.5) * random_rotation;
|
||||
|
@ -301,14 +304,15 @@ async fn copy_to_points<I: GraphicElementRendered + TransformMut + Send + 'n>(
|
|||
result.push((new_instance, None));
|
||||
}
|
||||
|
||||
GraphicGroupTable::new(result)
|
||||
result_table
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
let bounding_box = vector_data.bounding_box_with_transform(vector_data.transform).unwrap();
|
||||
let bounding_box = vector_data.bounding_box_with_transform(vector_data_transform).unwrap();
|
||||
let mut result = VectorData::from_subpath(Subpath::new_rect(bounding_box[0], bounding_box[1]));
|
||||
result.style = vector_data.style.clone();
|
||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
@ -318,7 +322,8 @@ async fn bounding_box(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTa
|
|||
|
||||
#[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 = vector_data.one_item();
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
let mut result = VectorData::empty();
|
||||
|
@ -327,7 +332,7 @@ async fn offset_path(_: impl Ctx, vector_data: VectorDataTable, distance: f64, l
|
|||
|
||||
// Perform operation on all subpaths in this shape.
|
||||
for mut subpath in subpaths {
|
||||
subpath.apply_transform(vector_data.transform);
|
||||
subpath.apply_transform(vector_data_transform);
|
||||
|
||||
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
|
||||
let subpath_out = subpath.offset(
|
||||
|
@ -348,9 +353,9 @@ 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 = vector_data.one_item();
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
let transform = &vector_data.transform;
|
||||
let style = &vector_data.style;
|
||||
|
||||
let subpaths = vector_data.stroke_bezier_paths();
|
||||
|
@ -359,7 +364,7 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat
|
|||
// Perform operation on all subpaths in this shape.
|
||||
for mut subpath in subpaths {
|
||||
let stroke = style.stroke().unwrap();
|
||||
subpath.apply_transform(*transform);
|
||||
subpath.apply_transform(vector_data_transform);
|
||||
|
||||
// Taking the existing stroke data and passing it to Bezier-rs to generate new paths.
|
||||
let subpath_out = subpath.outline(
|
||||
|
@ -398,34 +403,35 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat
|
|||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn flatten_vector_elements(_: impl Ctx, graphic_group_input: GraphicGroupTable) -> VectorDataTable {
|
||||
let graphic_group_input = graphic_group_input.one_item();
|
||||
|
||||
// A node based solution to support passing through vector data could be a network node with a cache node connected to
|
||||
// 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 concat_group(graphic_group: &GraphicGroup, current_transform: DAffine2, result: &mut VectorData) {
|
||||
for (element, reference) in graphic_group.iter() {
|
||||
fn concat_group(graphic_group_table: &GraphicGroupTable, current_transform: DAffine2, result: &mut InstanceMut<VectorData>) {
|
||||
for (element, reference) in graphic_group_table.one_instance().instance.iter() {
|
||||
match element {
|
||||
GraphicElement::VectorData(vector_data) => {
|
||||
for instance in vector_data.instances() {
|
||||
result.concat(instance, current_transform, reference.map(|node_id| node_id.0).unwrap_or_default());
|
||||
*result.alpha_blending = *instance.alpha_blending;
|
||||
result
|
||||
.instance
|
||||
.concat(instance.instance, current_transform * instance.transform(), reference.map(|node_id| node_id.0).unwrap_or_default());
|
||||
}
|
||||
}
|
||||
GraphicElement::GraphicGroup(graphic_group) => {
|
||||
let graphic_group = graphic_group.one_item();
|
||||
concat_group(graphic_group, current_transform * graphic_group.transform, result);
|
||||
concat_group(graphic_group, current_transform * graphic_group.transform(), result);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = VectorData::empty();
|
||||
concat_group(graphic_group_input, DAffine2::IDENTITY, &mut result);
|
||||
let mut result_table = VectorDataTable::default();
|
||||
let mut result_instance = result_table.one_instance_mut();
|
||||
// TODO: This leads to incorrect stroke widths when flattening groups with different transforms.
|
||||
result.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
result_instance.instance.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
concat_group(&graphic_group_input, DAffine2::IDENTITY, &mut result_instance);
|
||||
|
||||
VectorDataTable::new(result)
|
||||
result_table
|
||||
}
|
||||
|
||||
pub trait ConcatElement {
|
||||
|
@ -434,16 +440,18 @@ pub trait ConcatElement {
|
|||
|
||||
impl ConcatElement for GraphicGroupTable {
|
||||
fn concat(&mut self, other: &Self, transform: DAffine2, _node_id: u64) {
|
||||
let own = self.one_item_mut();
|
||||
let other = other.one_item();
|
||||
let other_transform = other.transform();
|
||||
|
||||
let self_group = self.one_instance_mut().instance;
|
||||
let other_group = other.one_instance().instance;
|
||||
|
||||
// TODO: Decide if we want to keep this behavior whereby the layers are flattened
|
||||
for (mut element, footprint_mapping) in other.iter().cloned() {
|
||||
*element.transform_mut() = transform * element.transform() * other.transform();
|
||||
own.push((element, footprint_mapping));
|
||||
for (mut element, footprint_mapping) in other_group.iter().cloned() {
|
||||
*element.transform_mut() = transform * element.transform() * other_transform;
|
||||
self_group.push((element, footprint_mapping));
|
||||
}
|
||||
|
||||
own.alpha_blending = other.alpha_blending;
|
||||
*self.one_instance_mut().alpha_blending = *other.one_instance().alpha_blending;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -451,14 +459,16 @@ impl ConcatElement for GraphicGroupTable {
|
|||
async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64, start_offset: f64, stop_offset: f64, adaptive_spacing: bool, subpath_segment_lengths: Vec<f64>) -> VectorDataTable {
|
||||
// Limit the smallest spacing to something sensible to avoid freezing the application.
|
||||
let spacing = spacing.max(0.01);
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
// Create an iterator over the bezier segments with enumeration and peeking capability.
|
||||
let mut bezier = vector_data.segment_bezier_iter().enumerate().peekable();
|
||||
|
||||
// Initialize the result VectorData with the same transformation as the input.
|
||||
let mut result = VectorData::empty();
|
||||
result.transform = vector_data.transform;
|
||||
let mut result = VectorDataTable::default();
|
||||
*result.transform_mut() = vector_data_transform;
|
||||
|
||||
// Iterate over each segment in the bezier iterator.
|
||||
while let Some((index, (segment_id, _, start_point_index, mut last_end))) = bezier.next() {
|
||||
|
@ -537,7 +547,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
|||
|
||||
// Retrieve the segment and apply transformation.
|
||||
let Some(segment) = vector_data.segment_from_id(current_segment_id) else { continue };
|
||||
let segment = segment.apply_transformation(|point| vector_data.transform.transform_point2(point));
|
||||
let segment = segment.apply_transformation(|point| vector_data_transform.transform_point2(point));
|
||||
|
||||
// Calculate the position on the segment.
|
||||
let parametric_t = segment.euclidean_to_parametric_with_total_length((total_distance - total_length_before) / length, 0.001, length);
|
||||
|
@ -545,10 +555,10 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
|||
|
||||
// Generate a new PointId and add the point to result.point_domain.
|
||||
let point_id = PointId::generate();
|
||||
result.point_domain.push(point_id, vector_data.transform.inverse().transform_point2(point));
|
||||
result.one_instance_mut().instance.point_domain.push(point_id, vector_data_transform.inverse().transform_point2(point));
|
||||
|
||||
// Store the index of the point.
|
||||
let point_index = result.point_domain.ids().len() - 1;
|
||||
let point_index = result.one_instance_mut().instance.point_domain.ids().len() - 1;
|
||||
point_indices.push(point_index);
|
||||
}
|
||||
|
||||
|
@ -565,7 +575,7 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
|||
let stroke_id = StrokeId::generate();
|
||||
|
||||
// Add the segment to result.segment_domain.
|
||||
result.segment_domain.push(segment_id, start_index, end_index, handles, stroke_id);
|
||||
result.one_instance_mut().instance.segment_domain.push(segment_id, start_index, end_index, handles, stroke_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -582,17 +592,17 @@ async fn sample_points(_: impl Ctx, vector_data: VectorDataTable, spacing: f64,
|
|||
let stroke_id = StrokeId::generate();
|
||||
|
||||
// Add the closing segment to result.segment_domain.
|
||||
result.segment_domain.push(segment_id, last_index, first_index, handles, stroke_id);
|
||||
result.one_instance_mut().instance.segment_domain.push(segment_id, last_index, first_index, handles, stroke_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer the style from the input vector data to the result.
|
||||
result.style = vector_data.style.clone();
|
||||
result.style.set_stroke_transform(vector_data.transform);
|
||||
result.one_instance_mut().instance.style = vector_data.style.clone();
|
||||
result.one_instance_mut().instance.style.set_stroke_transform(vector_data_transform);
|
||||
|
||||
// Return the resulting vector data with newly generated points and segments.
|
||||
VectorDataTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category(""), path(graphene_core::vector))]
|
||||
|
@ -604,7 +614,8 @@ async fn poisson_disk_points(
|
|||
separation_disk_diameter: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item();
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
let mut result = VectorData::empty();
|
||||
|
@ -618,7 +629,7 @@ async fn poisson_disk_points(
|
|||
continue;
|
||||
}
|
||||
|
||||
subpath.apply_transform(vector_data.transform);
|
||||
subpath.apply_transform(vector_data_transform);
|
||||
|
||||
let mut previous_point_index: Option<usize> = None;
|
||||
|
||||
|
@ -648,17 +659,18 @@ 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 = vector_data.one_item();
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
vector_data
|
||||
.segment_bezier_iter()
|
||||
.map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data.transform.transform_point2(point)).length(None))
|
||||
.map(|(_id, bezier, _, _)| bezier.apply_transformation(|point| vector_data_transform.transform_point2(point)).length(None))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
|
||||
async fn spline(_: impl Ctx, mut vector_data: VectorDataTable) -> VectorDataTable {
|
||||
let vector_data = vector_data.one_item_mut();
|
||||
let vector_data = vector_data.one_instance_mut().instance;
|
||||
|
||||
// Exit early if there are no points to generate splines from.
|
||||
if vector_data.point_domain.positions().is_empty() {
|
||||
|
@ -700,7 +712,8 @@ 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 mut vector_data = vector_data.one_item().clone();
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let mut vector_data = vector_data.one_instance().instance.clone();
|
||||
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
|
||||
|
||||
|
@ -718,30 +731,29 @@ async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)]
|
|||
|
||||
if !already_applied[*start] {
|
||||
let start_position = vector_data.point_domain.positions()[*start];
|
||||
let start_position = vector_data.transform.transform_point2(start_position);
|
||||
let start_position = vector_data_transform.transform_point2(start_position);
|
||||
vector_data.point_domain.set_position(*start, start_position + start_delta);
|
||||
already_applied[*start] = true;
|
||||
}
|
||||
if !already_applied[*end] {
|
||||
let end_position = vector_data.point_domain.positions()[*end];
|
||||
let end_position = vector_data.transform.transform_point2(end_position);
|
||||
let end_position = vector_data_transform.transform_point2(end_position);
|
||||
vector_data.point_domain.set_position(*end, end_position + end_delta);
|
||||
already_applied[*end] = true;
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
*handle_start = vector_data.transform.transform_point2(*handle_start) + start_delta;
|
||||
*handle_end = vector_data.transform.transform_point2(*handle_end) + end_delta;
|
||||
*handle_start = vector_data_transform.transform_point2(*handle_start) + start_delta;
|
||||
*handle_end = vector_data_transform.transform_point2(*handle_end) + end_delta;
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
*handle = vector_data.transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
|
||||
*handle = vector_data_transform.transform_point2(*handle) + (start_delta + end_delta) / 2.;
|
||||
}
|
||||
bezier_rs::BezierHandles::Linear => {}
|
||||
}
|
||||
}
|
||||
|
||||
vector_data.transform = DAffine2::IDENTITY;
|
||||
vector_data.style.set_stroke_transform(DAffine2::IDENTITY);
|
||||
|
||||
VectorDataTable::new(vector_data)
|
||||
|
@ -757,23 +769,29 @@ async fn morph(
|
|||
time: Fraction,
|
||||
#[min(0.)] start_index: IntegerCount,
|
||||
) -> VectorDataTable {
|
||||
let source = source.one_item();
|
||||
let target = target.one_item();
|
||||
|
||||
let mut result = VectorData::empty();
|
||||
|
||||
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_transform = source.transform();
|
||||
let target_transform = target.transform();
|
||||
|
||||
let source = source.one_instance().instance;
|
||||
let target = target.one_instance().instance;
|
||||
|
||||
let mut result = VectorDataTable::default();
|
||||
|
||||
// Lerp styles
|
||||
result.alpha_blending = if time < 0.5 { source.alpha_blending } else { target.alpha_blending };
|
||||
result.style = source.style.lerp(&target.style, time);
|
||||
*result.one_instance_mut().alpha_blending = if time < 0.5 { *source_alpha_blending } else { *target_alpha_blending };
|
||||
result.one_instance_mut().instance.style = source.style.lerp(&target.style, time);
|
||||
|
||||
let mut source_paths = source.stroke_bezier_paths();
|
||||
let mut target_paths = target.stroke_bezier_paths();
|
||||
for (mut source_path, mut target_path) in (&mut source_paths).zip(&mut target_paths) {
|
||||
// Deal with mismatched transforms
|
||||
source_path.apply_transform(source.transform);
|
||||
target_path.apply_transform(target.transform);
|
||||
source_path.apply_transform(source_transform);
|
||||
target_path.apply_transform(target_transform);
|
||||
|
||||
// Deal with mismatched start index
|
||||
for _ in 0..start_index {
|
||||
|
@ -816,11 +834,12 @@ async fn morph(
|
|||
manipulator.anchor = manipulator.anchor.lerp(target.anchor, time);
|
||||
}
|
||||
|
||||
result.append_subpath(source_path, true);
|
||||
result.one_instance_mut().instance.append_subpath(source_path, true);
|
||||
}
|
||||
|
||||
// Mismatched subpath count
|
||||
for mut source_path in source_paths {
|
||||
source_path.apply_transform(source.transform);
|
||||
source_path.apply_transform(source_transform);
|
||||
let end = source_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
|
||||
for group in source_path.manipulator_groups_mut() {
|
||||
group.anchor = group.anchor.lerp(end, time);
|
||||
|
@ -829,7 +848,7 @@ async fn morph(
|
|||
}
|
||||
}
|
||||
for mut target_path in target_paths {
|
||||
target_path.apply_transform(target.transform);
|
||||
target_path.apply_transform(target_transform);
|
||||
let start = target_path.manipulator_groups().first().map(|group| group.anchor).unwrap_or_default();
|
||||
for group in target_path.manipulator_groups_mut() {
|
||||
group.anchor = start.lerp(group.anchor, time);
|
||||
|
@ -838,10 +857,10 @@ async fn morph(
|
|||
}
|
||||
}
|
||||
|
||||
VectorDataTable::new(result)
|
||||
result
|
||||
}
|
||||
|
||||
fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
||||
fn bevel_algorithm(mut vector_data: VectorData, vector_data_transform: DAffine2, distance: f64) -> VectorData {
|
||||
// Splits a bézier curve based on a distance measurement
|
||||
fn split_distance(bezier: bezier_rs::Bezier, distance: f64, length: f64) -> bezier_rs::Bezier {
|
||||
const EUCLIDEAN_ERROR: f64 = 0.001;
|
||||
|
@ -885,7 +904,7 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_existing_segments(vector_data: &mut VectorData, distance: f64, segments_connected: &mut [u8]) -> Vec<[usize; 2]> {
|
||||
fn update_existing_segments(vector_data: &mut VectorData, vector_data_transform: DAffine2, distance: f64, segments_connected: &mut [u8]) -> Vec<[usize; 2]> {
|
||||
let mut next_id = vector_data.point_domain.next_id();
|
||||
let mut new_segments = Vec::new();
|
||||
|
||||
|
@ -900,8 +919,8 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
|||
if bezier.is_linear() {
|
||||
bezier.handles = bezier_rs::BezierHandles::Linear;
|
||||
}
|
||||
bezier = bezier.apply_transformation(|p| vector_data.transform.transform_point2(p));
|
||||
let inverse_transform = (vector_data.transform.matrix2.determinant() != 0.).then(|| vector_data.transform.inverse()).unwrap_or_default();
|
||||
bezier = bezier.apply_transformation(|p| vector_data_transform.transform_point2(p));
|
||||
let inverse_transform = (vector_data_transform.matrix2.determinant() != 0.).then(|| vector_data_transform.inverse()).unwrap_or_default();
|
||||
|
||||
let original_length = bezier.length(None);
|
||||
let mut length = original_length;
|
||||
|
@ -942,7 +961,7 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
|||
}
|
||||
|
||||
let mut segments_connected = segments_connected_count(&vector_data);
|
||||
let new_segments = update_existing_segments(&mut vector_data, distance, &mut segments_connected);
|
||||
let new_segments = update_existing_segments(&mut vector_data, vector_data_transform, distance, &mut segments_connected);
|
||||
insert_new_segments(&mut vector_data, &new_segments);
|
||||
|
||||
vector_data
|
||||
|
@ -950,22 +969,28 @@ fn bevel_algorithm(mut vector_data: VectorData, distance: f64) -> VectorData {
|
|||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
fn bevel(_: impl Ctx, source: VectorDataTable, #[default(10.)] distance: Length) -> VectorDataTable {
|
||||
let source = source.one_item();
|
||||
let source_transform = source.transform();
|
||||
let source = source.one_instance().instance;
|
||||
|
||||
VectorDataTable::new(bevel_algorithm(source.clone(), distance))
|
||||
let mut result = VectorDataTable::new(bevel_algorithm(source.clone(), source_transform, distance));
|
||||
*result.transform_mut() = source_transform;
|
||||
result
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||
async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>) -> f64 {
|
||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||
let vector_data = vector_data.eval(new_ctx).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
let mut area = 0.;
|
||||
let scale = vector_data.transform.decompose_scale();
|
||||
let scale = vector_data_transform.decompose_scale();
|
||||
for subpath in vector_data.stroke_bezier_paths() {
|
||||
area += subpath.area(Some(1e-3), Some(1e-3));
|
||||
}
|
||||
|
||||
area * scale[0] * scale[1]
|
||||
}
|
||||
|
||||
|
@ -973,7 +998,9 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<
|
|||
async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl Node<Context<'static>, Output = VectorDataTable>, centroid_type: CentroidType) -> DVec2 {
|
||||
let new_ctx = OwnedContextImpl::from(ctx).with_footprint(Footprint::default()).into_context();
|
||||
let vector_data = vector_data.eval(new_ctx).await;
|
||||
let vector_data = vector_data.one_item();
|
||||
|
||||
let vector_data_transform = vector_data.transform();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
|
||||
if centroid_type == CentroidType::Area {
|
||||
let mut area = 0.;
|
||||
|
@ -990,7 +1017,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
|
|||
|
||||
if area != 0. {
|
||||
centroid /= area;
|
||||
return vector_data.transform().transform_point2(centroid);
|
||||
return vector_data_transform.transform_point2(centroid);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1005,13 +1032,13 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
|
|||
|
||||
if length != 0. {
|
||||
centroid /= length;
|
||||
return vector_data.transform().transform_point2(centroid);
|
||||
return vector_data_transform.transform_point2(centroid);
|
||||
}
|
||||
|
||||
let positions = vector_data.point_domain.positions();
|
||||
if !positions.is_empty() {
|
||||
let centroid = positions.iter().sum::<DVec2>() / (positions.len() as f64);
|
||||
return vector_data.transform().transform_point2(centroid);
|
||||
return vector_data_transform.transform_point2(centroid);
|
||||
}
|
||||
|
||||
DVec2::ZERO
|
||||
|
@ -1047,7 +1074,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.one_item();
|
||||
let vector_data = vector_data.one_instance().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);
|
||||
|
@ -1059,7 +1086,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.one_item();
|
||||
let vector_data = vector_data.one_instance().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);
|
||||
|
@ -1069,7 +1096,7 @@ mod test {
|
|||
async fn circle_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.one_item();
|
||||
let vector_data = vector_data.one_instance().instance;
|
||||
assert_eq!(vector_data.region_bezier_paths().count(), 8);
|
||||
for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() {
|
||||
let expected_angle = (index as f64 + 1.) * 45.;
|
||||
|
@ -1081,20 +1108,21 @@ 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.one_item();
|
||||
let bounding_box = bounding_box.one_instance().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.),]);
|
||||
|
||||
// Test a VectorData with non-zero rotation
|
||||
let mut square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
|
||||
square.transform *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
|
||||
let square = VectorData::from_subpath(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE));
|
||||
let mut square = VectorDataTable::new(square);
|
||||
*square.one_instance_mut().transform_mut() *= DAffine2::from_angle(core::f64::consts::FRAC_PI_4);
|
||||
let bounding_box = BoundingBoxNode {
|
||||
vector_data: FutureWrapperNode(VectorDataTable::new(square)),
|
||||
vector_data: FutureWrapperNode(square),
|
||||
}
|
||||
.eval(Footprint::default())
|
||||
.await;
|
||||
let bounding_box = bounding_box.one_item();
|
||||
let bounding_box = bounding_box.one_instance().instance;
|
||||
assert_eq!(bounding_box.region_bezier_paths().count(), 1);
|
||||
let subpath = bounding_box.region_bezier_paths().next().unwrap().1;
|
||||
let sqrt2 = core::f64::consts::SQRT_2;
|
||||
|
@ -1108,7 +1136,7 @@ mod test {
|
|||
let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec();
|
||||
let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await;
|
||||
let flattened_copy_to_points = super::flatten_vector_elements(Footprint::default(), copy_to_points).await;
|
||||
let flattened_copy_to_points = flattened_copy_to_points.one_item();
|
||||
let flattened_copy_to_points = flattened_copy_to_points.one_instance().instance;
|
||||
assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len());
|
||||
for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() {
|
||||
let offset = expected_points[index];
|
||||
|
@ -1122,7 +1150,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.one_item();
|
||||
let sample_points = sample_points.one_instance().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}");
|
||||
|
@ -1132,7 +1160,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.one_item();
|
||||
let sample_points = sample_points.one_instance().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}");
|
||||
|
@ -1147,7 +1175,7 @@ mod test {
|
|||
0,
|
||||
)
|
||||
.await;
|
||||
let sample_points = sample_points.one_item();
|
||||
let sample_points = sample_points.one_instance().instance;
|
||||
assert!(
|
||||
(20..=40).contains(&sample_points.point_domain.positions().len()),
|
||||
"actual len {}",
|
||||
|
@ -1166,7 +1194,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.one_item();
|
||||
let spline = spline.one_instance().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.)]);
|
||||
}
|
||||
|
@ -1175,7 +1203,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.one_item();
|
||||
let sample_points = sample_points.one_instance().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.)]
|
||||
|
@ -1186,14 +1214,19 @@ mod test {
|
|||
fn contains_segment(vector: VectorData, target: bezier_rs::Bezier) {
|
||||
let segments = vector.segment_bezier_iter().map(|x| x.1);
|
||||
let count = segments.filter(|bezier| bezier.abs_diff_eq(&target, 0.01) || bezier.reversed().abs_diff_eq(&target, 0.01)).count();
|
||||
assert_eq!(count, 1, "Incorrect number of {target:#?} in {:#?}", vector.segment_bezier_iter().collect::<Vec<_>>());
|
||||
assert_eq!(
|
||||
count,
|
||||
1,
|
||||
"Expected exactly one matching segment for {target:?}, but found {count}. The given segments are: {:#?}",
|
||||
vector.segment_bezier_iter().collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::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.one_item();
|
||||
let beveled = beveled.one_instance().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 8);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 8);
|
||||
|
@ -1216,7 +1249,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.one_item();
|
||||
let beveled = beveled.one_instance().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
|
@ -1232,32 +1265,34 @@ mod test {
|
|||
|
||||
#[tokio::test]
|
||||
async fn bevel_with_transform() {
|
||||
let curve = Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::new(1., 0.), DVec2::new(1., 10.), DVec2::X * 10.);
|
||||
let source = Subpath::<PointId>::from_beziers(&[Bezier::from_linear_dvec2(DVec2::X * -10., DVec2::ZERO), curve], false);
|
||||
let mut vector_data = VectorData::from_subpath(source);
|
||||
let curve = Bezier::from_cubic_dvec2(DVec2::new(0., 0.), DVec2::new(1., 0.), DVec2::new(1., 10.), DVec2::new(10., 0.));
|
||||
let source = Subpath::<PointId>::from_beziers(&[Bezier::from_linear_dvec2(DVec2::new(-10., 0.), DVec2::ZERO), curve], false);
|
||||
let vector_data = VectorData::from_subpath(source);
|
||||
let mut vector_data_table = VectorDataTable::new(vector_data.clone());
|
||||
|
||||
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.));
|
||||
vector_data.transform = transform;
|
||||
*vector_data_table.one_instance_mut().transform_mut() = transform;
|
||||
|
||||
let beveled = super::bevel((), VectorDataTable::new(vector_data), 5.);
|
||||
let beveled = beveled.one_item();
|
||||
let beveled = beveled.one_instance().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 4);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 3);
|
||||
assert_eq!(beveled.transform, transform);
|
||||
|
||||
// Segments
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), DVec2::new(-10., 0.)));
|
||||
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(0.5 / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), DVec2::new(-10., 0.)));
|
||||
let trimmed = curve.trim(bezier_rs::TValue::Euclidean(5. / curve.length(Some(0.00001))), bezier_rs::TValue::Parametric(1.));
|
||||
contains_segment(beveled.clone(), trimmed);
|
||||
|
||||
// Join
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-0.5, 0.), trimmed.start));
|
||||
contains_segment(beveled.clone(), bezier_rs::Bezier::from_linear_dvec2(DVec2::new(-5., 0.), trimmed.start));
|
||||
}
|
||||
|
||||
#[tokio::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.one_item();
|
||||
let beveled = beveled.one_instance().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
@ -1278,7 +1313,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.one_item();
|
||||
let beveled = beveled.one_instance().instance;
|
||||
|
||||
assert_eq!(beveled.point_domain.positions().len(), 6);
|
||||
assert_eq!(beveled.segment_domain.ids().len(), 5);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue