M13.2: collapsed precomps project their 3D layers (and environment sky) with the parent comp's view size

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-03 03:38:33 -04:00
co-authored by Claude Opus 5.5
parent ebcb8c95ce
commit 02c9a3f13c
2 changed files with 43 additions and 2 deletions
+34
View File
@@ -200,3 +200,37 @@ fn wireframe_quality_draws_the_layer_bounds() {
let img = render_frame(&p, cid, Tick::ZERO, 1.0);
assert!(img.data.iter().all(|q| q[3] == 0.0 || q[3] == 1.0));
}
#[test]
fn collapsed_precomp_of_another_size_renders_through_the_parent_camera() {
// A 1000×600 scene comp with a blue 3D solid at its centre, collapsed into a 300×200 comp
// whose camera looks at the precomp layer (at the parent's centre). The nested layers must
// be projected with the parent comp's view (its centre and size), not the scene comp's.
let run = |three_d_precomp: bool, camera: bool| {
let mut p = project();
let outer = Comp::new(300, 200, FrameRate::FPS_30, Tick::from_seconds_f64(2.0));
let inner = Comp::new(1000, 600, FrameRate::FPS_30, Tick::from_seconds_f64(2.0));
let mut blue = solid(&mut p, &inner, [0.0, 0.0, 1.0], (40, 40), [500.0, 300.0, 0.0]);
blue.switches.three_d = true;
let mut inner = inner;
inner.layers = vec![blue];
let iid = p.add_item("Scene", Label::Sandstone, None, ItemKind::Comp(inner.into()));
let mut pre = build::layer(&mut p, &outer, "Scene", LayerSource::Comp { item: iid }, (1000, 600), None);
pre.switches.three_d = three_d_precomp;
pre.switches.collapse = true;
let mut c = outer.clone();
c.layers = vec![pre];
if camera {
let cam = build::layer(&mut p, &outer, "Camera", LayerSource::Camera, (300, 200), None);
c.layers.insert(0, cam);
}
let cid = p.add_item("Face", Label::Sandstone, None, ItemKind::Comp(c.into()));
render_frame(&p, cid, Tick::ZERO, 1.0)
};
for (three_d, camera) in [(true, true), (true, false), (false, true)] {
let img = run(three_d, camera);
let c = img.get(150, 100);
assert!(c[2] > 0.99 && c[3] > 0.99, "3D precomp {three_d}, camera {camera}: centre {c:?}");
assert!(img.get(100, 100)[3] < 0.01, "only the 40 px solid is drawn");
}
}
+9 -2
View File
@@ -161,6 +161,13 @@ pub(crate) fn camera_for(r: &Renderer, ctx: &EvalCtx) -> CameraState {
}
}
/// The size of the comp the camera looks through: a collapsed precomp's layers are projected
/// into the outermost comp they collapse into (its centre and size), not their own comp's.
pub(crate) fn view_size(r: &Renderer, ctx: &EvalCtx) -> (f64, f64) {
let c = r.collapse3d.map_or(ctx.comp, |c| c.parent.comp);
(c.width as f64, c.height as f64)
}
/// Number of motion-blur sub-samples for a layer.
fn mb_samples(r: &Renderer, ctx: &EvalCtx, layer: &Layer) -> usize {
if r.opts.motion_blur && ctx.comp.enable_motion_blur && layer.switches.motion_blur {
@@ -202,7 +209,7 @@ fn prepare_with<'a>(
if buf.img.is_empty() {
return None;
}
let comp = (ctx.comp.width as f64, ctx.comp.height as f64);
let comp = view_size(r, ctx);
let s = r.opts.scale;
let cam = camera_for(r, ctx);
let outer = r.collapse3d.map_or(Mat4::IDENTITY, |c| c.world);
@@ -920,7 +927,7 @@ pub(crate) fn draw_sky(r: &Renderer, ctx: &EvalCtx, layer: &Layer, canvas: &mut
let cam = camera_for(r, ctx);
let (fwd, right, down) = (cam.forward(), cam.right(), cam.down());
let s = r.opts.scale.max(1e-9);
let (cw, ch) = (ctx.comp.width as f64, ctx.comp.height as f64);
let (cw, ch) = view_size(r, ctx);
let (ox, oy) = r.roi_offset().map_or((0.0, 0.0), |(x, y, _, _)| (x, y));
let rot = environment_rotation(ctx);
let (iw, ih) = (img.width as f64, img.height as f64);