Effects: an object's Transform effect moves its fills' raster effects beside a fill's 3D effect

When only a fill or stroke has an Extrude & Bevel, Inflate, Rotate or Revolve effect, the object's
Transform effects reshape the path, and now the raster effects of every fill and stroke follow them
too, as they do on an object without a 3D effect: a shadow after the fill's 3D effect and another
fill's shadow scale and turn with the art.
This commit is contained in:
dotCooCoo
2026-11-12 12:21:38 -08:00
parent 043e517ef6
commit bd973548d7
4 changed files with 37 additions and 2 deletions
+11 -1
View File
@@ -5,7 +5,7 @@ use std::borrow::Cow;
use serde_json::{Map, Value, json};
use vectorcraft_doc::color::{BlendMode, Color};
use vectorcraft_doc::rastersettings::MAX_ADD_AROUND;
use vectorcraft_doc::{Effect, Node, RasterColorModel, RasterEffectsSettings};
use vectorcraft_doc::{AppearanceItem, Effect, Node, RasterColorModel, RasterEffectsSettings};
use vectorcraft_geom::{Affine, Point, Rect, Vec2};
@@ -553,6 +553,16 @@ fn follow_items_by(n: &mut Node, a: Affine) -> bool {
changed
}
/// Make the raster effects of `items`, the fills and strokes of an object whose own effects are
/// `owner`, follow the Transform effects of `owner` ([`follow_contents`]): for art that applies
/// those effects to the object's path and draws each fill and stroke on it as a node of its own.
pub(crate) fn follow_paints(items: &mut [AppearanceItem], owner: &[Effect]) {
let Some(a) = contents_step(owner) else { return };
for item in items {
follow_contents(item.effects_mut(), a, Affine::IDENTITY);
}
}
/// `effects`, those of a fill or stroke of an object whose own effects are `owner`, with each
/// raster effect following the Transform effects of `owner` as well ([`follow_contents`]): the
/// list to draw that fill's or stroke's raster effects from ([`raster_effects`]). Borrowed when
+5 -1
View File
@@ -565,9 +565,13 @@ fn art(doc: Option<&Document>, n: &Node, expand: bool) -> Option<Node> {
} else {
let path = geometry(&path, &n.appearance.effects, &ctx);
remaining = crate::without_geometry(&remaining);
// The object's Transform effects, applied to the path above, transform the items' raster
// effects too.
let mut items: Vec<AppearanceItem> = n.appearance.items.iter().filter(|i| crate::paints(i)).cloned().collect();
crate::raster::follow_paints(&mut items, &n.appearance.effects);
// Every item's shadow goes behind every item's art.
let mut shadows = vec![];
for item in n.appearance.items.iter().filter(|i| crate::paints(i)) {
for item in &items {
if let Some(index) = item.effects().iter().position(|e| e.visible && is_revolve(&e.id)) {
let fx = item.effects().get(index)?;
let p = geometry(&path, item.effects().get(..index)?, &ctx.item(item));
+4
View File
@@ -982,6 +982,10 @@ pub fn solid_art_in(doc: Option<&Document>, n: &Node) -> Option<Node> {
} else {
let path = geometry(&path, &n.appearance.effects, &ctx);
remaining = crate::without_geometry(&n.appearance.effects);
// The object's Transform effects, applied to the path above, transform the items' raster
// effects too.
let mut paints = paints;
crate::raster::follow_paints(&mut paints, &n.appearance.effects);
// Every item's shadow goes behind every item's art.
let mut shadows = vec![];
for item in &paints {
+17
View File
@@ -241,6 +241,23 @@ fn a_shadow_before_a_transform_follows_it_into_3d_art() {
}
}
/// An object's Transform effect transforms the raster effects of its fills when only a fill has a
/// 3D effect: the shadow after that fill's 3D effect and another fill's shadow, as it does without
/// the 3D effect.
#[test]
fn an_objects_transform_moves_its_fills_shadows_beside_a_fills_3d_effect() {
for three_d in [crate::EXTRUDE, crate::REVOLVE] {
let art = |n: &Node| if three_d == crate::EXTRUDE { crate::solid_art(n) } else { crate::revolve_art(n) };
let mut n = fill_square(18, vec![fx(three_d, json!({})), shadow(10.0, 10.0, 0.0)]);
let mut other = n.appearance.items[0].clone();
*other.effects_mut() = vec![shadow(10.0, 10.0, 0.0)];
n.appearance.items.push(other);
n.appearance.effects = vec![transform(json!({"scaleH": 200, "scaleV": 100}))];
let drawn = shadows_in(&art(&n).unwrap());
assert!(drawn.len() > 1 && near(&drawn, &vec![(20.0, 10.0); drawn.len()]), "{three_d}: {drawn:?}");
}
}
#[test]
fn glows_blurs_and_feathers_scale_with_the_transform_after_them() {
let effects = [