Skip to content

Commit f74dcd7

Browse files
committed
chore
1 parent f3fc7b8 commit f74dcd7

File tree

3 files changed

+361
-57
lines changed

3 files changed

+361
-57
lines changed

node-graph/libraries/core-types/src/transform.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ pub trait Transform {
3636
// Non-orthogonal columns or different lengths indicate skew/non-uniform scaling
3737
col0.dot(col1).abs() > EPSILON || (col0.length() - col1.length()).abs() > EPSILON
3838
}
39+
40+
/// Decomposes the transform into its rotation (angle), scale, and skew components.
41+
fn decompose_rotation_scale_skew(&self) -> (f64, DVec2, DVec2) {
42+
let transform = self.transform();
43+
let x_axis = transform.matrix2.x_axis;
44+
let y_axis = transform.matrix2.y_axis;
45+
46+
// Assuming there is no vertical shear
47+
let angle = x_axis.y.atan2(x_axis.x);
48+
let (sin, cos) = angle.sin_cos();
49+
let scale_x = if cos.abs() > 1e-10 { x_axis.x / cos } else { x_axis.y / sin };
50+
51+
let mut shear_x = (sin * y_axis.y + cos * y_axis.x) / (sin * sin * scale_x + cos * cos * scale_x);
52+
if !shear_x.is_finite() {
53+
shear_x = 0.;
54+
}
55+
let scale_y = if cos.abs() > 1e-10 {
56+
(y_axis.y - scale_x * sin * shear_x) / cos
57+
} else {
58+
(scale_x * cos * shear_x - y_axis.x) / sin
59+
};
60+
61+
(angle, DVec2::new(scale_x, scale_y), DVec2::new(shear_x, 0.))
62+
}
3963
}
4064

4165
pub trait TransformMut: Transform {

node-graph/libraries/vector-types/src/vector/style.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ use core_types::bounds::{BoundingBox, RenderBoundingBox};
66
use core_types::color::Alpha;
77
use core_types::render_complexity::RenderComplexity;
88
use core_types::table::Table;
9+
use core_types::transform::Transform;
910
use dyn_any::DynAny;
1011
use glam::DAffine2;
12+
use std::f64::consts::{PI, TAU};
1113

1214
/// Describes the fill of a layer.
1315
///
@@ -85,8 +87,8 @@ impl Fill {
8587

8688
pub fn lerp(&self, other: &Self, time: f64) -> Self {
8789
let transparent = Self::solid(Color::TRANSPARENT);
88-
let a = if *self == Self::None { &transparent } else { self };
89-
let b = if *other == Self::None { &transparent } else { other };
90+
let a = if *self == Self::None && *other != Self::None { &transparent } else { self };
91+
let b = if *other == Self::None && *self != Self::None { &transparent } else { other };
9092

9193
match (a, b) {
9294
(Self::Solid(a), Self::Solid(b)) => Self::Solid(a.lerp(b, time as f32)),
@@ -103,7 +105,7 @@ impl Fill {
103105
Self::Gradient(a.lerp(b, time))
104106
}
105107
(Self::Gradient(a), Self::Gradient(b)) => Self::Gradient(a.lerp(b, time)),
106-
_ => Self::None,
108+
(Self::None, _) | (_, Self::None) => Self::None,
107109
}
108110
}
109111

@@ -461,10 +463,32 @@ impl Stroke {
461463
join: if time < 0.5 { self.join } else { other.join },
462464
join_miter_limit: self.join_miter_limit + (other.join_miter_limit - self.join_miter_limit) * time,
463465
align: if time < 0.5 { self.align } else { other.align },
464-
transform: DAffine2::from_mat2_translation(
465-
time * self.transform.matrix2 + (1. - time) * other.transform.matrix2,
466-
self.transform.translation * time + other.transform.translation * (1. - time),
467-
),
466+
transform: {
467+
// Decompose into scale/rotation/skew and interpolate each component separately.
468+
// We do this instead of linear matrix interpolation because that passes through a zero matrix
469+
// (and thus a division by 0 when rendering) when transforms have opposing rotations (e.g. 0° vs 180°).
470+
471+
let (s_angle, s_scale, s_skew) = self.transform.decompose_rotation_scale_skew();
472+
let (t_angle, t_scale, t_skew) = other.transform.decompose_rotation_scale_skew();
473+
474+
let lerped_translation = self.transform.translation.lerp(other.transform.translation, time);
475+
476+
// Shortest-arc rotation interpolation
477+
let mut rotation_diff = t_angle - s_angle;
478+
if rotation_diff > PI {
479+
rotation_diff -= TAU;
480+
} else if rotation_diff < -PI {
481+
rotation_diff += TAU;
482+
}
483+
let lerped_angle = s_angle + rotation_diff * time;
484+
485+
let lerped_scale = s_scale.lerp(t_scale, time);
486+
let lerped_skew = s_skew.lerp(t_skew, time);
487+
488+
let trs = DAffine2::from_scale_angle_translation(lerped_scale, lerped_angle, lerped_translation);
489+
let skew = DAffine2::from_cols_array(&[1., lerped_skew.y, lerped_skew.x, 1., 0., 0.]);
490+
trs * skew
491+
},
468492
non_scaling: if time < 0.5 { self.non_scaling } else { other.non_scaling },
469493
paint_order: if time < 0.5 { self.paint_order } else { other.paint_order },
470494
}
@@ -775,8 +799,8 @@ pub enum RenderMode {
775799
Normal = 0,
776800
/// Render only the outlines of shapes at the current viewport resolution
777801
Outline,
778-
// /// Render with normal coloration at the document resolution, showing the pixels when the current viewport resolution is higher
779-
// PixelPreview,
802+
/// Render with normal coloration at the document export resolution; at zoom > 100% this shows individual export pixels upscaled with nearest-neighbor filtering
803+
PixelPreview,
780804
/// Render a preview of how the object would be exported as an SVG.
781805
SvgPreview,
782806
}

0 commit comments

Comments
 (0)