From e1296a57c2c14b366e49d50b7c2e3d67daa16bcf Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sat, 3 Oct 2026 04:22:49 -0400 Subject: [PATCH] M13.2: Create VR Environment nests the scene as a collapsed 3D precomp in each face comp (lights copied) Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/commands/app_more.rs | 61 +++++++++++++++----------- crates/engine/src/tests_m145.rs | 5 ++- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/crates/engine/src/commands/app_more.rs b/crates/engine/src/commands/app_more.rs index 6431d5e2..fb0473d4 100644 --- a/crates/engine/src/commands/app_more.rs +++ b/crates/engine/src/commands/app_more.rs @@ -433,8 +433,8 @@ fn ae(v: [f64; 3]) -> Vec3 { } /// Composition ▸ VR ▸ Create VR Environment: renders the active comp's 3D scene in every -/// direction. Six face comps (a 90° camera each at `position`, default the comp centre, with a -/// copy of the scene's layers), a 3:2 cube map of them, and an equirectangular output comp +/// direction. Six face comps (a 90° camera each at `position`, default the comp centre, looking +/// at the scene nested as a collapsed 3D precomp), a 3:2 cube map of them, and an equirectangular output comp /// (VR Converter, cube map → equirectangular). One undo step. fn create_vr_environment(s: &mut Session, p: &Value) -> Result { let cid = comp_id(s, p)?; @@ -462,30 +462,41 @@ fn create_vr_environment(s: &mut Session, p: &Value) -> Result { id_of(&s.execute("layer.newCamera", json!({"comp": fc, "name": format!("{fname} Camera"), "angleOfView": 90.0, "type": "oneNode"}))?, "layer")?; s.execute("prop.set", json!({"comp": fc, "layer": cam, "path": "transform/position", "value": center}))?; s.execute("prop.set", json!({"comp": fc, "layer": cam, "path": "transform/orientation", "value": o}))?; - // The scene's layers (not its cameras), in the scene's coordinates, under the face - // camera. - let layers: Vec = src.layers.iter().filter(|l| !l.is_camera()).cloned().collect(); - s.edit("Create VR Environment", None, |proj, _| { - let mut next = proj.next_id; - let mut map = std::collections::BTreeMap::new(); - let mut copies = vec![]; - for l in &layers { - let mut c = l.clone(); - super::edit::reid(&mut c, &mut next); - map.insert(l.id, c.id); - copies.push(c); - } - for c in &mut copies { - c.parent = c.parent.and_then(|p| map.get(&p).copied()); - if let Some(m) = &mut c.track_matte { - m.layer = map.get(&m.layer).copied().unwrap_or(m.layer); + // The scene, nested as a collapsed 3D precomp whose layer maps the scene's + // coordinates onto the face comp's unchanged (anchor = position): its 3D layers are + // seen through the face camera and edits to the scene show in every face. Lights + // inside a collapsed precomp do not light it (the containing comp's do), so the + // scene's lights are copied next to it. + let pre = id_of(&s.execute("layer.addItem", json!({"comp": fc, "item": cid.0, "time": 0.0}))?, "layer")?; + let mid = [src.width as f64 / 2.0, src.height as f64 / 2.0, 0.0]; + s.execute("layer.setSwitch", json!({"comp": fc, "layers": [pre], "switch": "threeD", "value": true}))?; + s.execute("layer.setSwitch", json!({"comp": fc, "layers": [pre], "switch": "collapse", "value": true}))?; + s.execute("prop.set", json!({"comp": fc, "layer": pre, "path": "transform/anchor", "value": mid}))?; + s.execute("prop.set", json!({"comp": fc, "layer": pre, "path": "transform/position", "value": mid}))?; + let lights: Vec = src.layers.iter().filter(|l| l.is_light()).cloned().collect(); + if !lights.is_empty() { + s.edit("Create VR Environment", None, |proj, _| { + let mut next = proj.next_id; + let mut map = std::collections::BTreeMap::new(); + let mut copies = vec![]; + for l in &lights { + let mut c = l.clone(); + super::edit::reid(&mut c, &mut next); + map.insert(l.id, c.id); + copies.push(c); } - } - proj.next_id = next; - let comp = proj.comp_mut(ItemId(fc)).ok_or(EngineError::NoComp)?; - comp.layers.extend(copies); - Ok(()) - })?; + for c in &mut copies { + c.parent = c.parent.and_then(|p| map.get(&p).copied()); + } + proj.next_id = next; + let comp = proj.comp_mut(ItemId(fc)).ok_or(EngineError::NoComp)?; + let at = comp.layers.len().saturating_sub(1); + for (k, c) in copies.into_iter().enumerate() { + comp.layers.insert(at + k, c); + } + Ok(()) + })?; + } faces.push(fc); } let cube = id_of( diff --git a/crates/engine/src/tests_m145.rs b/crates/engine/src/tests_m145.rs index edc8fe18..d538da63 100644 --- a/crates/engine/src/tests_m145.rs +++ b/crates/engine/src/tests_m145.rs @@ -476,8 +476,9 @@ fn create_vr_environment_and_extract_cubemap() { let c = s.project.comp(ItemId(*f)).unwrap(); assert_eq!((c.width, c.height), (64, 64)); assert!(c.layers.iter().any(|l| l.is_camera())); - assert!(c.layers.iter().any(|l| l.name == aname && l.is_3d()), "the scene's layers"); - let _ = main; + let nested = c.layers.iter().find(|l| matches!(l.source, effectcraft_project::LayerSource::Comp { item } if item == main)).expect("the scene, nested"); + assert!(nested.switches.collapse && nested.is_3d(), "collapsed 3D precomp"); + let _ = &aname; } let cube = s.project.comp(ItemId(r["cubeMap"].as_u64().unwrap())).unwrap(); assert_eq!((cube.width, cube.height, cube.layers.len()), (192, 128, 6));