Merge pull request #289 from storytold/fix/227-shape-effect-points

Effects: effect points on shape layers start at the layer centre (#227)

# Conflicts:
#	crates/ui-egui/src/panels/puppet_tool.rs
#	crates/ui-egui/src/panels/viewer.rs
This commit is contained in:
Brandon Thomas
2026-10-09 09:32:13 -04:00
36 changed files with 684 additions and 154 deletions
-2
View File
@@ -253,8 +253,6 @@ pub(crate) fn chain_key(chain: &[FxStep], buf: &Buf, levels: Option<f32>) -> u12
k.debug(&e.working_space);
k.u64(e.working_linear as u64);
k.debug(&e.shutter);
k.f64(e.bounds_origin[0]);
k.f64(e.bounds_origin[1]);
}
k.finish()
}
+3 -3
View File
@@ -96,7 +96,7 @@ fn bulge(e: &mut Enc, ctx: &EffectCtx, b: GBuf) -> Option<GBuf> {
let h = ctx.params.f("height");
let taper = 1.0 + ctx.params.f("taperRadius").max(0.0) / 100.0 * 3.0;
let pin = ctx.params.b("pinAllEdges");
let (x0, y0, lw, lh) = util::pin_rect(ctx, b.offset, b.scale);
let (x0, y0, lw, lh) = util::layer_rect_at(ctx, b.offset, b.scale);
let edge = (lw.min(lh) * 0.1).max(1.0);
let mut p = Params::default();
p.f[0] = [c.0 as f32, c.1 as f32, rx as f32, ry as f32];
@@ -115,7 +115,7 @@ fn wave_warp(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
if !ctx.adjustment {
b.pad(e, h.abs().ceil() as u32 + 1)?;
}
let (x0, y0, lw, lh) = util::pin_rect(ctx, b.offset, b.scale);
let (x0, y0, lw, lh) = util::layer_rect_at(ctx, b.offset, b.scale);
let rect = [x0 as f32, y0 as f32, lw.max(1.0) as f32, lh.max(1.0) as f32];
// The wave phase in cycles, (x·dx + y·dy) / width + phase / 2π, is separable: per column
// and per row (fraction, whole cycles) in f64, so the kernel's sum keeps the fraction exact
@@ -183,7 +183,7 @@ fn turbulent(e: &mut Enc, ctx: &EffectCtx, mut b: GBuf) -> Option<GBuf> {
let evo = ctx.params.f("evolution") / 360.0;
let cycle = ctx.params.b("evolutionOptions/cycleEvolution").then(|| ctx.params.f("evolutionOptions/cycle").round().max(1.0));
let falloff = if (3..=5).contains(&kind) { 0.3 } else { 0.5 };
let (x0, y0, lw, lh) = util::pin_rect(ctx, b.offset, b.scale);
let (x0, y0, lw, lh) = util::layer_rect_at(ctx, b.offset, b.scale);
let edge = (lw.min(lh) * 0.1).max(1.0);
let seed = (ctx.seed ^ 0x7d15).wrapping_add((ctx.params.f("evolutionOptions/randomSeed") as i64 as u32).wrapping_mul(0x9e37_79b9));
let (z0, z1, t) = match cycle {
+1 -1
View File
@@ -125,7 +125,7 @@ fn run(e: &mut Enc, entry: &str, p: &Params, mut b: GBuf, data: Option<&wgpu::Bu
/// util::layer_rect.
fn layer_rect(ctx: &EffectCtx, b: &GBuf) -> (f64, f64, f64, f64) {
(b.offset[0], b.offset[1], ctx.layer_size[0] * b.scale, ctx.layer_size[1] * b.scale)
effectcraft_effects::util::layer_rect_at(ctx, b.offset, b.scale)
}
// ---------------------------------------------------------------- per-pixel (fxs_point)
+2 -2
View File
@@ -160,8 +160,8 @@ impl Scene {
pub(crate) fn effect(&mut self, l: &mut Layer, id: &str, vals: &[(&str, Value)]) {
let spec = effectcraft_effects::find(id).unwrap();
let mut next = self.p.next_id;
let size = effectcraft_render::source_size(&self.p, l);
let mut g = effectcraft_effects::instantiate(spec, &mut Ids(&mut next), spec.name, [size.0 as f64, size.1 as f64]);
let size = effectcraft_render::effect_bounds(&self.p, &self.comp, l).0;
let mut g = effectcraft_effects::instantiate(spec, &mut Ids(&mut next), spec.name, size);
self.p.next_id = next;
for (k, v) in vals {
g.prop_mut(k).unwrap_or_else(|| panic!("{id}: no {k}")).value = v.clone();
+36 -20
View File
@@ -55,28 +55,44 @@ fn turbulent_displace_and_cc_lens() {
effect_case("ec.distort.cclens", &[("convergence", n(-70.0))]);
}
/// Edge pinning on a shape layer, whose comp-sized bounds are centred on its origin (#227).
/// Effect `id` on a shape layer, whose comp-sized bounds are centred on its origin and whose
/// effects run in effect space from the bounds' top-left (#227), at full and half resolution
/// (the 97 × 61 comp puts that corner between pixels).
fn shape_case(id: &str, vals: &[(&str, Value)]) {
let mut s = Scene::new(BitDepth::Bpc32);
let mut l = build::layer(&mut s.p, &s.comp, "Shape", LayerSource::Shape, (97, 61), None);
let mut next = s.p.next_id;
let mut ids = Ids(&mut next);
let rect = build::shape_rect(&mut ids, [90.0, 54.0], [0.0, 0.0], 0.0);
let fill = build::shape_fill(&mut ids, [0.9, 0.3, 0.1, 1.0]);
let g = build::shape_group(&mut ids, "Rectangle 1", vec![rect, fill]);
s.p.next_id = next;
l.props.sub_mut("contents").unwrap().children.push(g.into());
s.effect(&mut l, id, vals);
s.push(l);
check(&format!("{id} on a shape layer"), compare_at(&s, opts(), Tick::ZERO), 0.0);
check(&format!("{id} on a shape layer, half"), compare_at(&s, RenderOpts { scale: 0.5, ..opts() }, Tick::ZERO), 0.0);
}
/// Edge pinning on a shape layer (#227).
#[test]
fn edge_pinning_on_a_shape_layer() {
let cases: [(&str, &[(&str, Value)]); 3] = [
("ec.distort.turbulentdisplace", &[("size", n(30.0)), ("amount", n(20.0))]),
("ec.distort.wavewarp", &[("pinning", e(1)), ("height", n(6.0)), ("width", n(25.0))]),
("ec.distort.bulge", &[("pinAllEdges", on()), ("height", n(2.0)), ("hradius", n(40.0)), ("vradius", n(30.0)), ("center", Value::Vec2([-20.0, -10.0]))]),
];
for (id, vals) in cases {
let mut s = Scene::new(BitDepth::Bpc32);
let mut l = build::layer(&mut s.p, &s.comp, "Shape", LayerSource::Shape, (97, 61), None);
let mut next = s.p.next_id;
let mut ids = Ids(&mut next);
let rect = build::shape_rect(&mut ids, [90.0, 54.0], [0.0, 0.0], 0.0);
let fill = build::shape_fill(&mut ids, [0.9, 0.3, 0.1, 1.0]);
let g = build::shape_group(&mut ids, "Rectangle 1", vec![rect, fill]);
s.p.next_id = next;
l.props.sub_mut("contents").unwrap().children.push(g.into());
s.effect(&mut l, id, vals);
s.push(l);
check(&format!("{id} on a shape layer"), compare_at(&s, opts(), Tick::ZERO), 0.0);
}
shape_case("ec.distort.turbulentdisplace", &[("size", n(30.0)), ("amount", n(20.0))]);
shape_case("ec.distort.wavewarp", &[("pinning", e(1)), ("height", n(6.0)), ("width", n(25.0))]);
shape_case(
"ec.distort.bulge",
&[("pinAllEdges", on()), ("height", n(2.0)), ("hradius", n(40.0)), ("vradius", n(30.0)), ("center", Value::Vec2([28.5, 20.5]))],
);
}
/// Effect points on a shape layer at their defaults (the bounds' centre) and set (#227).
#[test]
fn effect_points_on_a_shape_layer() {
shape_case("ec.distort.twirl", &[("angle", n(120.0))]);
shape_case("ec.distort.bulge", &[("hradius", n(30.0)), ("vradius", n(20.0)), ("height", n(1.8))]);
shape_case("ec.distort.ripple", &[("radius", n(80.0)), ("waveWidth", n(9.0)), ("waveHeight", n(4.0))]);
shape_case("ec.distort.cclens", &[("size", n(45.0)), ("convergence", n(60.0)), ("center", Value::Vec2([30.0, 20.0]))]);
shape_case("ec.distort.turbulentdisplace", &[("size", n(25.0)), ("amount", n(30.0)), ("offset", Value::Vec2([10.0, 40.0]))]);
}
#[test]