mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 17:44:02 +00:00
Merge pull request #6602 from dotCooCoo/fix/3d-group-member-blend-mode
3D effects: Extrude & Bevel, Inflate and Rotate on a group keep each member's blending mode
This commit is contained in:
@@ -275,23 +275,30 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
/// Does `scope` draw its members' faces other than plainly: does it have an opacity mask or a
|
||||
/// blending mode other than Normal?
|
||||
fn composites(scope: &Scope) -> bool {
|
||||
scope.mask.is_some() || scope.blend != BlendMode::Normal
|
||||
}
|
||||
|
||||
/// `surfaces`, each with the scopes of the member it was built from, in groups with the opacity
|
||||
/// masks of those scopes, so each mask applies to the faces of the members it holds. The members'
|
||||
/// opacity is already in their paint ([`Profile::opacity`]), so these groups are opaque.
|
||||
/// masks and blending modes of those scopes, so each applies to the faces of the members it holds.
|
||||
/// The members' opacity is already in their paint ([`Profile::opacity`]), so these groups are
|
||||
/// opaque.
|
||||
fn masked(id: NodeId, surfaces: Vec<(&[Scope], Arc<Node>)>) -> Vec<Arc<Node>> {
|
||||
let masks: Vec<(Vec<Scope>, Arc<Node>)> = surfaces
|
||||
.into_iter()
|
||||
.map(|(scopes, s)| {
|
||||
let masks = scopes.iter().filter(|s| s.mask.is_some()).map(|s| Scope { opacity: 1.0, blend: BlendMode::Normal, ..s.clone() }).collect();
|
||||
let masks = scopes.iter().filter(|s| composites(s)).map(|s| Scope { opacity: 1.0, ..s.clone() }).collect();
|
||||
(masks, s)
|
||||
})
|
||||
.collect();
|
||||
crate::revolve::scoped(id, masks.iter().map(|(scopes, s)| (scopes.as_slice(), s.clone())).collect(), 0)
|
||||
}
|
||||
|
||||
/// Does any of `shapes` sit in a scope with an opacity mask?
|
||||
/// Does any of `shapes` sit in a scope with an opacity mask or a blending mode other than Normal?
|
||||
fn has_masks(shapes: &[Profile]) -> bool {
|
||||
shapes.iter().any(|s| s.scopes.iter().any(|s| s.mask.is_some()))
|
||||
shapes.iter().any(|s| s.scopes.iter().any(composites))
|
||||
}
|
||||
|
||||
/// One shape a solid is built from: its outline and fill rule, the fills and strokes that paint
|
||||
@@ -550,8 +557,9 @@ pub(crate) fn reshape_members(n: &Node, before: &[Effect], shapes: &mut [Profile
|
||||
/// each in its own paint. When every one is opaque with normal blending and no colored light makes
|
||||
/// a surface one composite or masks it, their faces are drawn in one back-to-front order (where
|
||||
/// depths tie, as for the caps turned toward the viewer, in the order of the appearance);
|
||||
/// otherwise each solid is a surface of its own, in that order. A member's opacity mask, or that of
|
||||
/// a group it is in, masks the surfaces of the members it holds ([`masked`]).
|
||||
/// otherwise each solid is a surface of its own, in that order. A member's opacity mask and
|
||||
/// blending mode, and those of a group it is in, apply to the surfaces of the members it holds
|
||||
/// ([`masked`]).
|
||||
fn art_for(n: &Node, fx: &Effect, shapes: &[Profile]) -> Option<Vec<Arc<Node>>> {
|
||||
let b = union_bounds(shapes)?;
|
||||
let p = crate::merged_params(&fx.id, &fx.params);
|
||||
|
||||
@@ -523,6 +523,52 @@ fn an_opacity_mask_on_a_member_or_a_group_in_a_three_d_group_masks_its_solids()
|
||||
}
|
||||
}
|
||||
|
||||
/// The leaf faces of `art` that draw something, each with its visual bounds and whether it is drawn
|
||||
/// inside a group with blending mode `mode`.
|
||||
fn faces_in_mode(art: &Node, mode: BlendMode) -> Vec<(Rect, bool)> {
|
||||
fn walk(n: &Node, inside: bool, mode: BlendMode, out: &mut Vec<(Rect, bool)>) {
|
||||
let inside = inside || (n.children().is_some() && n.blend == mode);
|
||||
match n.children() {
|
||||
Some(children) => children.iter().for_each(|c| walk(c, inside, mode, out)),
|
||||
None => out.extend(n.visual_bounds().map(|b| (b, inside))),
|
||||
}
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
walk(art, false, mode, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_blending_mode_on_a_member_or_a_group_in_a_three_d_group_applies_to_its_solids() {
|
||||
let d = document();
|
||||
let mode = |mut n: Node, mode: BlendMode| {
|
||||
n.blend = mode;
|
||||
n
|
||||
};
|
||||
let other = || Arc::new(rect(91, 600.0, 600.0, 650.0, 650.0, 0.0));
|
||||
let member = || rect(1, 100.0, 100.0, 150.0, 200.0, 2.0);
|
||||
let cases = [
|
||||
("a multiplied member", Node::group(NodeId(90), vec![Arc::new(mode(member(), BlendMode::Multiply)), other()])),
|
||||
(
|
||||
"a member of a multiplied group",
|
||||
Node::group(NodeId(90), vec![Arc::new(mode(Node::group(NodeId(80), vec![Arc::new(member())]), BlendMode::Multiply)), other()]),
|
||||
),
|
||||
];
|
||||
for (what, g) in cases {
|
||||
for id in TYPES {
|
||||
let g = with(g.clone(), id, json!({}));
|
||||
// The member spans y 100 to 200 and the other member y 600 to 650, and every type keeps
|
||||
// its faces near those heights (Revolve turns them around a vertical axis): a face whose
|
||||
// center is above y 400 belongs to the member.
|
||||
let (mine, others): (Vec<_>, Vec<_>) =
|
||||
faces_in_mode(&art(&d, &g, id).expect("drawn"), BlendMode::Multiply).into_iter().partition(|(b, _)| b.center().y < 400.0);
|
||||
assert!(!mine.is_empty() && !others.is_empty(), "{id}: both members draw faces ({} and {})", mine.len(), others.len());
|
||||
assert!(mine.iter().all(|(_, m)| *m), "{id}: every face of {what} is drawn in Multiply");
|
||||
assert!(others.iter().all(|(_, m)| !m), "{id}: no face of the other member is drawn in Multiply");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolved_objects_and_groups_in_a_three_d_group_keep_their_geometry_effects() {
|
||||
let mut d = document();
|
||||
|
||||
+4
-3
@@ -122,11 +122,12 @@ them all. The members stay editable: moving or editing one reshapes the solids.
|
||||
- Each member's own geometry effects shape it first, then those of each group inside the 3D group that holds it,
|
||||
applied to that group's members together; the 3D group's geometry effects before the 3D effect reshape the
|
||||
members, and those after it bend the result as a whole. The members' other effects, and the group's own fills and
|
||||
strokes, are not drawn. A member's opacity applies to its solids; its blending mode does not. An opacity mask on
|
||||
strokes, are not drawn. A member's opacity and blending mode apply to its solids, and the blending mode of a group
|
||||
inside the 3D group applies to the solids of that group's members together. An opacity mask on
|
||||
a member masks its solids, and one on a group inside the 3D group masks the solids of that group's members
|
||||
together. The mask stays where it is on the page; it does not turn with the solids. A disabled mask is ignored.
|
||||
- When a member, or a group inside the 3D group, has an opacity mask, each solid is drawn on its own, the backmost
|
||||
member's first, as when a solid is transparent.
|
||||
- When a member, or a group inside the 3D group, has an opacity mask or a blending mode other than Normal, each
|
||||
solid is drawn on its own, the backmost member's first, as when a solid is transparent.
|
||||
- When every solid is opaque, all members' faces are drawn in one back-to-front order; otherwise each solid is drawn
|
||||
on its own, the backmost member's first.
|
||||
- Hidden members and guides are left out. A group holding a live object other than a blend (an envelope, a mesh, a
|
||||
|
||||
Reference in New Issue
Block a user