mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 23:05:39 +00:00
Effects: nested groups' Transform effects round a member's direction once
A member's light or stroke direction under groups that each have a Transform effect is now rounded once for the turn of all of them. The inner group's members are evaluated first, so each group rounded the direction again: two 30-degree turns of a top light gave Left instead of Top Left. While a group's members are evaluated, the group's own Transform effects are counted among those the members still go through.
This commit is contained in:
@@ -868,7 +868,11 @@ pub fn evaluate_container(doc: Option<&Document>, n: &Node) -> Option<Node> {
|
||||
}
|
||||
if !fx.is_empty() {
|
||||
m = crate::reshape::outlined_members(doc, &m);
|
||||
members_as_drawn(doc, &mut m);
|
||||
{
|
||||
// The members' raster effects then follow the group's own Transform effects as well.
|
||||
let _later = crate::raster::LaterGroups::enter(&n.appearance.effects);
|
||||
members_as_drawn(doc, &mut m);
|
||||
}
|
||||
let carry = crate::reshape::reshape_leaves_carrying(&mut m, &fx, &n.appearance.effects, doc);
|
||||
// The group's own fills and strokes paint the members as a whole: their gradients follow
|
||||
// the effects as the members' do.
|
||||
|
||||
@@ -613,12 +613,43 @@ pub(crate) fn follows(id: &str) -> bool {
|
||||
crate::is_raster(id) && id != crate::SVG_FILTER && id != RASTERIZE
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// The linear part of the Transform effects of the groups and layers whose members are being
|
||||
/// evaluated ([`LaterGroups`]), which those groups apply to the members' raster effects once
|
||||
/// the members are evaluated: the members' light and stroke directions are rounded for them as
|
||||
/// well ([`follow_transform`]).
|
||||
static LATER_GROUPS: std::cell::Cell<Affine> = const { std::cell::Cell::new(Affine::IDENTITY) };
|
||||
}
|
||||
|
||||
/// The Transform effects of a group or layer whose members are being evaluated before the group's
|
||||
/// own effects are applied to them, added to [`LATER_GROUPS`] until it is dropped.
|
||||
pub(crate) struct LaterGroups(Affine);
|
||||
|
||||
impl LaterGroups {
|
||||
/// Add the Transform effects of `owner`, a group's or layer's own effects ([`contents_step`]),
|
||||
/// to those the members will still go through.
|
||||
pub(crate) fn enter(owner: &[Effect]) -> Self {
|
||||
let was = LATER_GROUPS.with(|l| l.get());
|
||||
if let Some(step) = contents_step(owner) {
|
||||
LATER_GROUPS.with(|l| l.set(was * step));
|
||||
}
|
||||
LaterGroups(was)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LaterGroups {
|
||||
fn drop(&mut self) {
|
||||
LATER_GROUPS.with(|l| l.set(self.0));
|
||||
}
|
||||
}
|
||||
|
||||
/// Make raster effect `e` follow `a`, the linear part of a Transform effect listed after it, as
|
||||
/// that effect transforms what `e` paints: its distances and raster sizes scale by `a`'s mean
|
||||
/// scale ([`crate::scale_effect`]), a Drop Shadow's offset turns, reflects and scales with `a`,
|
||||
/// and a pixel filter's light direction, stroke direction or screen angles turn and reflect with
|
||||
/// it ([`pixel::turned_directions`]; `later` is the map `e` still goes through when it is drawn).
|
||||
/// Effects that [`follows`] leaves out stay as they are.
|
||||
/// it ([`pixel::turned_directions`]; `later` is the map `e` still goes through when it is drawn,
|
||||
/// before the Transform effects of the groups around it in [`LATER_GROUPS`]). Effects that
|
||||
/// [`follows`] leaves out stay as they are.
|
||||
fn follow_transform(e: &mut Effect, a: Affine, later: Affine) {
|
||||
if !follows(&e.id) {
|
||||
return;
|
||||
@@ -627,7 +658,7 @@ fn follow_transform(e: &mut Effect, a: Affine, later: Affine) {
|
||||
let p = merged_params(&e.id, &e.params);
|
||||
a * Point::new(num(&p, "x", 7.0), num(&p, "y", 7.0))
|
||||
});
|
||||
let turned = pixel::turned_directions(&e.id, &e.params, a, later);
|
||||
let turned = pixel::turned_directions(&e.id, &e.params, a, LATER_GROUPS.with(|l| l.get()) * later);
|
||||
if let Some(k) = vectorcraft_doc::Scaling::factor(a) {
|
||||
crate::scale_effect(e, k);
|
||||
}
|
||||
|
||||
@@ -580,6 +580,29 @@ fn nested_and_baked_directions_match_canvas() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A member's light under two nested groups that each turn 30° turns 60° in all, rounded once:
|
||||
/// Top Left, not Left (Top Left after the inner group's turn, then Left after the outer one's).
|
||||
#[test]
|
||||
fn nested_group_turns_round_the_light_once() {
|
||||
let relief = || fx("sketch.basRelief", json!({"lightDirection": "top"}));
|
||||
let thirty = || transform(json!({"rotate": 30}));
|
||||
let inner = group(41, vec![square(42, vec![relief()]), fill_square(43, vec![relief()])], vec![thirty()]);
|
||||
let outer = group(40, vec![inner], vec![thirty()]);
|
||||
let art = crate::evaluate_container(None, &outer).unwrap();
|
||||
assert_eq!(lights_in(&art), vec![Light::TopLeft; 2]);
|
||||
let mut d = Document::new(800.0, 800.0);
|
||||
let layer = d.layers[0].id;
|
||||
d.insert(Some(layer), 0, outer).unwrap();
|
||||
let baked = bake_document(&d).unwrap();
|
||||
for id in [42, 43] {
|
||||
assert_eq!(lights_in(baked.node(NodeId(id)).unwrap()), vec![Light::TopLeft], "{id}");
|
||||
}
|
||||
// Three groups make 90°: Left, not Bottom Left.
|
||||
let three = group(44, vec![group(45, vec![group(46, vec![square(47, vec![relief()])], vec![thirty()])], vec![thirty()])], vec![thirty()]);
|
||||
let art = crate::evaluate_container(None, &three).unwrap();
|
||||
assert_eq!(lights_in(&art), vec![Light::Left]);
|
||||
}
|
||||
|
||||
/// A Transform effect after a 3D effect that makes copies or moves at random holds back the shadow
|
||||
/// before the 3D effect, as it does without the 3D effect.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user