From c96a9b1f6e2435f2b6446f8ee3c20ef0726fa9f3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 14:36:12 +0000 Subject: [PATCH 1/2] Cylinder Wrap: Radius goes up to 30000 % (#3368) The Radius parameter was limited to 400 %, so a larger value was clamped and drew a smaller cylinder than After Effects' CC Cylinder, whose range the report gives as 0 to 30000 %. Raise the hard maximum; the slider's visible range is unchanged. --- crates/effects/src/perspective.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/effects/src/perspective.rs b/crates/effects/src/perspective.rs index 69c63caf9..2aa9fe69d 100644 --- a/crates/effects/src/perspective.rs +++ b/crates/effects/src/perspective.rs @@ -394,7 +394,7 @@ pub fn specs() -> Vec { }; let render = || popup(&["Full", "Outside", "Inside"]); let mut cyl = vec![ - p("radius", "Radius", num(100.0), slider(0.0, 400.0, 0.0, 200.0, 1)), + p("radius", "Radius", num(100.0), slider(0.0, 30000.0, 0.0, 200.0, 1)), p("position", "Position", pt(0.5, 0.5), ParamUi::Point), p("rotation", "Rotation", num(0.0), ParamUi::Angle), p("rotationX", "Rotation X", num(0.0), ParamUi::Angle), @@ -565,6 +565,19 @@ mod tests { assert_eq!(out.get(50 + 18, 10)[3], 0.0); } + /// #3368: Radius takes the range After Effects' CC Cylinder has, up to 30000 %, and a cylinder + /// that large still renders (nearly flat, fully opaque) instead of clamping to 400 %. + #[test] + fn cylinder_radius_goes_up_to_thirty_thousand_percent() { + let spec = find("ec.perspective.cccylinder").unwrap(); + let radius = spec.params.iter().find(|p| p.id == "radius").unwrap(); + assert!(matches!(radius.ui, ParamUi::Slider { min, max, .. } if min == 0.0 && max == 30000.0), "{:?}", radius.ui); + let img = Image::filled(100, 20, [0.5, 0.5, 0.5, 1.0]); + let out = run("ec.perspective.cccylinder", &img, &[("radius", num(30000.0))]).img; + let px = out.get(50 + 40, 10); + assert!(px.iter().all(|v| v.is_finite()) && px[3] > 0.99, "{px:?}"); + } + #[test] fn cylinder_axis_rotations_affect_geometry_and_texture() { let img = Image::filled(100, 100, [0.5, 0.5, 0.5, 1.0]); From 2226af65f2672586bc6db3313bbc03c62c883893 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 10 Nov 2026 15:51:52 +0000 Subject: [PATCH 2/2] Cylinder Wrap test: make the 30000 % render check fail on a 400 % clamp (#3368) The pixel it read was inside the cylinder at 400 % too. Put the axis on the left edge and read a pixel past the 400 % radius, with a 400 % run showing it is empty. --- crates/effects/src/perspective.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/effects/src/perspective.rs b/crates/effects/src/perspective.rs index 2aa9fe69d..8f55bc408 100644 --- a/crates/effects/src/perspective.rs +++ b/crates/effects/src/perspective.rs @@ -572,9 +572,13 @@ mod tests { let spec = find("ec.perspective.cccylinder").unwrap(); let radius = spec.params.iter().find(|p| p.id == "radius").unwrap(); assert!(matches!(radius.ui, ParamUi::Slider { min, max, .. } if min == 0.0 && max == 30000.0), "{:?}", radius.ui); + // The cylinder's axis on the left edge: at 400 % its radius is 400 / 100 * 100 / 2π ≈ 63.7 px, + // so a pixel 70 px along is past it there and inside the 30000 % one. let img = Image::filled(100, 20, [0.5, 0.5, 0.5, 1.0]); - let out = run("ec.perspective.cccylinder", &img, &[("radius", num(30000.0))]).img; - let px = out.get(50 + 40, 10); + let at_edge = |radius: f64| run("ec.perspective.cccylinder", &img, &[("radius", num(radius)), ("position", Value::Vec2([0.0, 10.0]))]).img; + let clamped = at_edge(400.0).get(70, 10); + assert_eq!(clamped[3], 0.0, "beyond a 400 % cylinder: {clamped:?}"); + let px = at_edge(30000.0).get(70, 10); assert!(px.iter().all(|v| v.is_finite()) && px[3] > 0.99, "{px:?}"); }