diff --git a/crates/engine/src/commands/comp.rs b/crates/engine/src/commands/comp.rs index dec74406..57bd4855 100644 --- a/crates/engine/src/commands/comp.rs +++ b/crates/engine/src/commands/comp.rs @@ -90,6 +90,12 @@ fn apply_settings(c: &mut Comp, p: &Value) { if let Some(n) = p.get("adaptiveSampleLimit").and_then(Value::as_u64) { c.motion_blur_adaptive_limit = (n as u32).clamp(16, 256); } + if let Some(b) = p.get("preserveFrameRate").and_then(Value::as_bool) { + c.preserve_frame_rate = b; + } + if let Some(b) = p.get("preserveResolution").and_then(Value::as_bool) { + c.preserve_resolution = b; + } } fn new_comp(s: &mut Session, p: &Value) -> Result { @@ -252,7 +258,7 @@ pub fn specs() -> Vec { "New Composition...", ["Composition"], Some("Cmd+N"), - "{name?, width?, height?, frameRate?, duration? (s), startTime? (s) | startTimecode?, background? [r,g,b]|#hex, pixelAspect?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), renderer? classic3D|advanced3D, anchor?, open?}", + "{name?, width?, height?, frameRate?, duration? (s), startTime? (s) | startTimecode?, background? [r,g,b]|#hex, pixelAspect?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), preserveFrameRate?: bool, preserveResolution?: bool, renderer? classic3D|advanced3D, anchor?, open?}", always, new_comp ), @@ -261,7 +267,7 @@ pub fn specs() -> Vec { "Composition Settings...", ["Composition"], Some("Cmd+K"), - "{comp?, name?, width?, height?, anchor? 0-8 (resize anchor, 4 = center), frameRate?, duration?, startTime? (s) | startTimecode?, background?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), pixelAspect?, renderer? classic3D|advanced3D}", + "{comp?, name?, width?, height?, anchor? 0-8 (resize anchor, 4 = center), frameRate?, duration?, startTime? (s) | startTimecode?, background?, shutterAngle?, shutterPhase?, motionBlurSamples?, adaptiveSampleLimit? (16–256), preserveFrameRate?: bool (nested or in the render queue it shows only its own frames), preserveResolution?: bool (nested, it renders at full size), pixelAspect?, renderer? classic3D|advanced3D}", has_comp, settings ), diff --git a/crates/engine/src/commands/query.rs b/crates/engine/src/commands/query.rs index 267b4605..27588907 100644 --- a/crates/engine/src/commands/query.rs +++ b/crates/engine/src/commands/query.rs @@ -79,6 +79,7 @@ fn comp_info(s: &mut Session, p: &Value) -> Result { Ok(json!({ "id": cid.0, "name": name, "width": c.width, "height": c.height, "frameRate": c.frame_rate.as_f64(), "duration": c.duration.seconds(), "workArea": [c.work_area.0.seconds(), c.work_area.1.seconds()], "background": c.background, + "preserveFrameRate": c.preserve_frame_rate, "preserveResolution": c.preserve_resolution, "layers": c.layers.iter().enumerate().map(|(i, l)| layer_json(l, i + 1)).collect::>(), })) } diff --git a/crates/engine/src/tests_menu_cmds.rs b/crates/engine/src/tests_menu_cmds.rs index ec6e09b6..af005b50 100644 --- a/crates/engine/src/tests_menu_cmds.rs +++ b/crates/engine/src/tests_menu_cmds.rs @@ -811,3 +811,24 @@ fn project_files_keep_their_format_warn_about_newer_versions_and_never_save_unre let e = s.execute("file.open", json!({"path": junk})).unwrap_err().to_string(); assert!(e.contains("is not a project EffectCraft can open"), "{e}"); } + +#[test] +fn comp_settings_preserve_frame_rate_and_resolution() { + let mut s = comp(); + let info = s.execute("comp.info", json!({})).unwrap(); + assert_eq!((info["preserveFrameRate"].clone(), info["preserveResolution"].clone()), (json!(false), json!(false))); + s.execute_checked("comp.settings", json!({"preserveFrameRate": true, "preserveResolution": true})).unwrap(); + let info = s.execute("comp.info", json!({})).unwrap(); + assert_eq!((info["preserveFrameRate"].clone(), info["preserveResolution"].clone()), (json!(true), json!(true))); + // Pre-compose keeps them; one undo step clears them. + let a = solid(&mut s, "#ff0000"); + let r = s.execute("layer.precompose", json!({"layers": [a], "name": "Inner"})).unwrap(); + let inner = s.project.comp(effectcraft_project::ItemId(r["comp"].as_u64().unwrap())).unwrap(); + assert!(inner.preserve_frame_rate && inner.preserve_resolution); + // Undo Pre-compose and the solid; then one more step undoes the settings. + s.execute("edit.undo", json!({})).unwrap(); + s.execute("edit.undo", json!({})).unwrap(); + assert!(s.active_comp().unwrap().preserve_frame_rate); + s.execute("edit.undo", json!({})).unwrap(); + assert!(!s.active_comp().unwrap().preserve_frame_rate && !s.active_comp().unwrap().preserve_resolution); +} diff --git a/crates/project/src/lib.rs b/crates/project/src/lib.rs index 6074cb53..0e7f8c27 100644 --- a/crates/project/src/lib.rs +++ b/crates/project/src/lib.rs @@ -308,6 +308,14 @@ pub struct Comp { pub enable_frame_blending: bool, #[serde(default)] pub draft_3d: bool, + /// Composition Settings ▸ Advanced ▸ Preserve frame rate when nested or in render queue: + /// nested in another comp, or rendered at another rate, it shows only its own frames. + #[serde(default)] + pub preserve_frame_rate: bool, + /// Composition Settings ▸ Advanced ▸ Preserve resolution when nested: nested in a comp + /// previewed at a lower resolution, it still renders at full size. + #[serde(default)] + pub preserve_resolution: bool, #[serde(default)] pub poster_time: Tick, /// Global Light for layer styles (Layer ▸ Layer Styles ▸ Blending Options). @@ -358,6 +366,8 @@ impl Comp { enable_motion_blur: true, enable_frame_blending: true, draft_3d: false, + preserve_frame_rate: false, + preserve_resolution: false, poster_time: Tick::ZERO, guides: vec![], global_light: styles::GlobalLight::default(), @@ -377,6 +387,8 @@ impl Comp { renderer: self.renderer, enable_motion_blur: self.enable_motion_blur, enable_frame_blending: self.enable_frame_blending, + preserve_frame_rate: self.preserve_frame_rate, + preserve_resolution: self.preserve_resolution, ..Comp::new(width, height, self.frame_rate, duration) } } diff --git a/crates/project/src/render_queue.rs b/crates/project/src/render_queue.rs index aca651b9..f6a46caa 100644 --- a/crates/project/src/render_queue.rs +++ b/crates/project/src/render_queue.rs @@ -418,8 +418,10 @@ impl RenderSettings { let b = b.clamp(a, comp.duration); (a, b) } + /// The output frame rate: the Frame Rate override, unless the comp preserves its frame rate + /// (Composition Settings ▸ Preserve frame rate when nested or in render queue). pub fn rate(&self, comp: &Comp) -> FrameRate { - self.frame_rate.unwrap_or(comp.frame_rate) + if comp.preserve_frame_rate { comp.frame_rate } else { self.frame_rate.unwrap_or(comp.frame_rate) } } /// First output-rate frame starting at or after `t`. fn ceil_frame(r: FrameRate, t: Tick) -> i64 { diff --git a/crates/render/src/eval.rs b/crates/render/src/eval.rs index d8d3f641..b082e5f8 100644 --- a/crates/render/src/eval.rs +++ b/crates/render/src/eval.rs @@ -114,6 +114,19 @@ impl<'a> EvalCtx<'a> { } layer.layer_time(self.time) } + /// The time a precomp layer's nested comp is shown at: [`Self::source_time`], floored to + /// the nested comp's own frames when it preserves its frame rate (Composition Settings ▸ + /// Preserve frame rate when nested or in render queue). + pub fn nested_time(&self, layer: &Layer) -> Tick { + let t = self.source_time(layer); + match layer.source { + LayerSource::Comp { item } => match self.project.comp(item) { + Some(nc) if nc.preserve_frame_rate => nc.frame_rate.tick_of(crate::frame_position(t, nc.frame_rate).0), + _ => t, + }, + _ => t, + } + } pub fn layer(&self, id: LayerId) -> Option<&'a Layer> { self.comp.layer(id) } diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 13d8fc4b..777fae25 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -157,7 +157,7 @@ impl EffectHost for FxHost<'_, '_, '_> { if self.r.depth > MAX_FX_DEPTH || self.r.project.comp_contains(*item, self.ctx.comp_id) { return None; } - self.r.nested().comp_aux(*item, self.ctx.source_time(self.layer)).map(Arc::new) + self.r.nested().comp_aux(*item, self.ctx.nested_time(self.layer)).map(Arc::new) } _ => None, } @@ -558,7 +558,7 @@ impl<'a> Renderer<'a> { /// 3D layers use this comp's camera and lights. pub fn collapse_into(&self, ctx: &EvalCtx<'a>, layer: &Layer, item: ItemId, opacity: f32) -> Option<(Renderer<'a>, EvalCtx<'a>)> { let nc = self.project.comp(item)?; - let nctx = EvalCtx { comp_id: item, comp: nc, time: ctx.source_time(layer), ..*ctx }; + let nctx = EvalCtx { comp_id: item, comp: nc, time: ctx.nested_time(layer), ..*ctx }; let (l2c, _) = ctx.layer_to_comp(layer); let outer = Some(self.outer.map_or(l2c, |o| o * l2c)); let world = ctx.world_matrix(layer); @@ -928,19 +928,24 @@ impl<'a> Renderer<'a> { if let Some(pf) = self.proxy_for(*item) && let Some(nc) = self.project.comp(*item) { - let img = self.footage.frame(*item, pf, ctx.source_time(layer))?; + let img = self.footage.frame(*item, pf, ctx.nested_time(layer))?; return Some(self.footage_buf(&img, pf, nc.width, nc.height)); } // Essential Properties overrides render the nested comp with this instance's // values. let ov = essential_overrides(ctx, layer); let tmp = if ov.is_empty() { None } else { effectcraft_project::essential::with_overrides(self.project, *item, &ov) }; + // Preserve resolution when nested: drawn at full size even when this comp renders + // smaller (the buffer's scale places it). + let full = s < 1.0 && self.project.comp(*item).is_some_and(|nc| nc.preserve_resolution); + let bs = if full { 1.0 } else { s }; let base = self.nested_through(layer); + let base = Renderer { opts: RenderOpts { scale: bs, ..base.opts }, ..base }; let sub = match &tmp { Some(p) => Renderer { project: p, ..base }, None => base, }; - let lt = ctx.source_time(layer); + let lt = ctx.nested_time(layer); // Frame blending between the nested comp's frames (time-stretched/remapped). let mode = frame_blend_mode(ctx, layer); if mode != FrameBlend::Off @@ -950,10 +955,10 @@ impl<'a> Renderer<'a> { if w > 1e-6 { let a = sub.comp_frame(*item, nc.frame_rate.tick_of(i)); let b = sub.comp_frame(*item, nc.frame_rate.tick_of(i + 1)); - return Some(Buf { img: blend_frames(mode, &a, &b, w as f32), offset: [0.0; 2], scale: s }); + return Some(Buf { img: blend_frames(mode, &a, &b, w as f32), offset: [0.0; 2], scale: bs }); } } - Some(Buf { img: sub.comp_frame(*item, lt), offset: [0.0; 2], scale: s }) + Some(Buf { img: sub.comp_frame(*item, lt), offset: [0.0; 2], scale: bs }) } LayerSource::Footage { item } => { let it = self.project.item(*item)?; @@ -1901,6 +1906,8 @@ mod tests_color; #[cfg(test)] mod tests_frame_blend; #[cfg(test)] +mod tests_nested; +#[cfg(test)] mod tests_paint; #[cfg(test)] mod tests_time; diff --git a/crates/render/src/tests_nested.rs b/crates/render/src/tests_nested.rs new file mode 100644 index 00000000..ea0e03e5 --- /dev/null +++ b/crates/render/src/tests_nested.rs @@ -0,0 +1,67 @@ +//! Nested compositions: Preserve frame rate when nested or in render queue, Preserve resolution +//! when nested. + +use effectcraft_color::Label; +use effectcraft_keyframe::{Keyframe, Value}; +use effectcraft_project::build; +use effectcraft_project::render_queue::RenderSettings; +use effectcraft_project::{Comp, ItemId, ItemKind, LayerSource, Project, Solid}; +use effectcraft_time::{FrameRate, Tick}; + +use crate::render_frame; + +fn s(x: f64) -> Tick { + Tick::from_seconds_f64(x) +} + +/// A 30 fps outer comp nesting a 10 fps 100×100 comp whose 10×10 solid moves from x=10 (0 s) +/// to x=90 (2 s). Returns (project, outer, inner). +fn nested(preserve_rate: bool, preserve_res: bool) -> (Project, ItemId, ItemId) { + let mut p = Project::default(); + let outer = Comp::new(100, 100, FrameRate::FPS_30, s(4.0)); + let mut inner = Comp::new(100, 100, FrameRate::from_f64(10.0), s(4.0)); + inner.preserve_frame_rate = preserve_rate; + inner.preserve_resolution = preserve_res; + let sid = p.add_item("S", Label::Red, None, ItemKind::Solid(Solid { color: [1.0, 1.0, 1.0], width: 10, height: 10, pixel_aspect: 1.0 })); + let mut l = build::layer(&mut p, &inner, "S", LayerSource::Solid { item: sid }, (10, 10), None); + l.props.prop_mut("transform/position").unwrap().keys = + vec![Keyframe::new(Tick::ZERO, Value::Vec3([10.0, 50.0, 0.0])), Keyframe::new(s(2.0), Value::Vec3([90.0, 50.0, 0.0]))]; + inner.layers.push(l); + let iid = p.add_item("Inner", Label::Sandstone, None, ItemKind::Comp(inner.into())); + let cid = p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(outer.clone().into())); + let pre = build::layer(&mut p, &outer, "Inner", LayerSource::Comp { item: iid }, (100, 100), None); + p.comp_mut(cid).unwrap().layers.push(pre); + (p, cid, iid) +} + +#[test] +fn a_comp_preserving_its_frame_rate_shows_only_its_own_frames_when_nested() { + // 2/30 s falls between the nested comp's frames 0 (0 s) and 1 (0.1 s). + let between = s(2.0 / 30.0); + let (p, cid, _) = nested(false, false); + assert_ne!(render_frame(&p, cid, between, 1.0).data, render_frame(&p, cid, Tick::ZERO, 1.0).data, "follows the outer comp's frames"); + let (p, cid, _) = nested(true, false); + assert_eq!(render_frame(&p, cid, between, 1.0).data, render_frame(&p, cid, Tick::ZERO, 1.0).data, "holds its own frame 0"); + assert_ne!(render_frame(&p, cid, s(0.1), 1.0).data, render_frame(&p, cid, Tick::ZERO, 1.0).data, "then its frame 1"); +} + +#[test] +fn the_render_queue_keeps_a_preserved_frame_rate() { + let (p, _, iid) = nested(true, false); + let settings = RenderSettings { frame_rate: Some(FrameRate::FPS_30), ..Default::default() }; + assert_eq!(settings.rate(p.comp(iid).unwrap()), FrameRate::from_f64(10.0)); + let (p, _, iid) = nested(false, false); + assert_eq!(settings.rate(p.comp(iid).unwrap()), FrameRate::FPS_30); +} + +#[test] +fn a_comp_preserving_its_resolution_renders_full_size_and_lands_in_place() { + for preserve in [false, true] { + let (p, cid, _) = nested(false, preserve); + let img = render_frame(&p, cid, Tick::ZERO, 0.5); + assert_eq!((img.width, img.height), (50, 50)); + // The solid covers x 5..15, y 45..55 in the 100×100 comp: 2.5..7.5, 22.5..27.5 at half size. + assert!(img.get(5, 25)[3] > 0.99, "preserve {preserve}: inside"); + assert!(img.get(20, 25)[3] < 0.01 && img.get(5, 40)[3] < 0.01, "preserve {preserve}: outside"); + } +} diff --git a/crates/script/src/model.rs b/crates/script/src/model.rs index 18270590..4a92f396 100644 --- a/crates/script/src/model.rs +++ b/crates/script/src/model.rs @@ -131,6 +131,7 @@ fn item(s: &Session, a: &J) -> R { "draft3d": c.draft_3d, "shutterAngle": c.shutter_angle, "shutterPhase": c.shutter_phase, "motionBlurSamplesPerFrame": c.motion_blur_samples, "motionBlurAdaptiveSampleLimit": c.motion_blur_adaptive_limit, + "preserveNestedFrameRate": c.preserve_frame_rate, "preserveNestedResolution": c.preserve_resolution, "renderer": match c.renderer { effectcraft_project::Renderer::Classic3D => "ADBE Classic 3D", effectcraft_project::Renderer::Advanced3D => "ADBE Advanced 3d" }, "open": s.state.open_comps.contains(&cid), "active": s.active_comp_id() == Some(cid), "hasVideo": true, "hasAudio": c.layers.iter().any(|l| l.switches.audio && matches!(l.source, LayerSource::Footage { .. } | LayerSource::Comp { .. })), diff --git a/crates/script/src/prelude.js b/crates/script/src/prelude.js index c06bc9bf..423e1b89 100644 --- a/crates/script/src/prelude.js +++ b/crates/script/src/prelude.js @@ -1409,6 +1409,8 @@ CompItem.prototype.constructor = CompItem; def("shutterAngle", function () { return this.__info().shutterAngle; }, function (v) { this.__settings({ shutterAngle: __num(v) }); }); def("shutterPhase", function () { return this.__info().shutterPhase; }, function (v) { this.__settings({ shutterPhase: __num(v) }); }); def("motionBlurSamplesPerFrame", function () { return this.__info().motionBlurSamplesPerFrame; }, function (v) { this.__settings({ motionBlurSamples: __num(v) }); }); + def("preserveNestedFrameRate", function () { return this.__info().preserveNestedFrameRate; }, function (v) { this.__settings({ preserveFrameRate: !!v }); }); + def("preserveNestedResolution", function () { return this.__info().preserveNestedResolution; }, function (v) { this.__settings({ preserveResolution: !!v }); }); def("renderer", function () { return this.__info().renderer; }, function (v) { this.__settings({ renderer: String(v).toLowerCase().indexOf("advanced") >= 0 ? "advanced3D" : "classic3D" }); }); diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs index 4df252b6..d71d20cb 100644 --- a/crates/script/src/tests.rs +++ b/crates/script/src/tests.rs @@ -484,3 +484,21 @@ fn motion_graphics_template_hooks_add_mirrors_and_controllers() { let eg = c.essential.as_ref().unwrap(); assert!(matches!(eg.controls[1].kind, effectcraft_engine::project::essential::EgKind::Mirror { of } if of == eg.controls[0].id)); } + +#[test] +fn comp_preserves_nested_frame_rate_and_resolution() { + let mut s = session(); + let o = ok( + &mut s, + r#" + var comp = app.project.items.addComp("Nested", 320, 180, 1, 2, 12); + var before = [comp.preserveNestedFrameRate, comp.preserveNestedResolution]; + comp.preserveNestedFrameRate = true; + comp.preserveNestedResolution = true; + before.concat([comp.preserveNestedFrameRate, comp.preserveNestedResolution]).join(","); + "#, + ); + assert_eq!(o.result, json!("false,false,true,true")); + let (_, c) = comp_named(&s, "Nested"); + assert!(c.preserve_frame_rate && c.preserve_resolution); +} diff --git a/crates/ui-egui/src/panels/comp_settings.rs b/crates/ui-egui/src/panels/comp_settings.rs index 6e481c6b..2befd5e6 100644 --- a/crates/ui-egui/src/panels/comp_settings.rs +++ b/crates/ui-egui/src/panels/comp_settings.rs @@ -88,6 +88,8 @@ pub struct CompDraft { pub shutter_phase: f64, pub samples: u32, pub adaptive_limit: u32, + pub preserve_frame_rate: bool, + pub preserve_resolution: bool, pub advanced_3d: bool, pub tab: Tab, /// Timecode text being edited (start, duration). @@ -112,6 +114,8 @@ impl Default for CompDraft { shutter_phase: -90.0, samples: 16, adaptive_limit: 128, + preserve_frame_rate: false, + preserve_resolution: false, advanced_3d: false, tab: Tab::Basic, start_tc: None, @@ -304,6 +308,14 @@ pub fn show(ui: &mut egui::Ui, d: &mut CompDraft, t: &Tokens, auto: &mut Registr } }); ui.end_row(); + ui.label(""); + let r = ui.checkbox(&mut d.preserve_frame_rate, "Preserve frame rate when nested or in render queue"); + auto.add("dialog.comp.preserveFrameRate", r.rect, "Preserve frame rate when nested or in render queue"); + ui.end_row(); + ui.label(""); + let r = ui.checkbox(&mut d.preserve_resolution, "Preserve resolution when nested"); + auto.add("dialog.comp.preserveResolution", r.rect, "Preserve resolution when nested"); + ui.end_row(); ui.label(egui::RichText::new("Motion Blur").strong()); ui.end_row(); ui.label("Shutter Angle:"); @@ -360,6 +372,7 @@ pub fn params(d: &CompDraft) -> serde_json::Value { "name": d.name, "width": d.width, "height": d.height, "pixelAspect": d.pixel_aspect, "frameRate": d.fps, "duration": d.duration, "startTime": d.start, "background": [d.bg[0], d.bg[1], d.bg[2]], "anchor": d.anchor, "shutterAngle": d.shutter_angle, "shutterPhase": d.shutter_phase, "motionBlurSamples": d.samples, "adaptiveSampleLimit": d.adaptive_limit, + "preserveFrameRate": d.preserve_frame_rate, "preserveResolution": d.preserve_resolution, "renderer": if d.advanced_3d { "advanced3D" } else { "classic3D" }, }) } diff --git a/crates/ui-egui/src/panels/dialogs.rs b/crates/ui-egui/src/panels/dialogs.rs index 3820adcc..d806827c 100644 --- a/crates/ui-egui/src/panels/dialogs.rs +++ b/crates/ui-egui/src/panels/dialogs.rs @@ -83,6 +83,8 @@ pub fn open_comp_settings(app: &mut EffectcraftApp) -> Result<(), String> { shutter_phase: c.shutter_phase, samples: c.motion_blur_samples, adaptive_limit: c.motion_blur_adaptive_limit, + preserve_frame_rate: c.preserve_frame_rate, + preserve_resolution: c.preserve_resolution, advanced_3d: c.renderer == effectcraft_engine::project::Renderer::Advanced3D, ..Default::default() }; @@ -604,7 +606,7 @@ mod tests { } app.dialog_state.comp.tab = super::super::comp_settings::Tab::Advanced; frame(&mut app, &ctx); - for id in ["anchor.4", "shutterAngle", "shutterPhase", "samples", "adaptiveLimit"] { + for id in ["anchor.4", "preserveFrameRate", "preserveResolution", "shutterAngle", "shutterPhase", "samples", "adaptiveLimit"] { assert!(app.auto.find(&format!("dialog.comp.{id}")).is_some(), "dialog.comp.{id}"); } app.dialog_state.comp.tab = super::super::comp_settings::Tab::Basic; diff --git a/crates/ui-egui/src/panels/viewer.rs b/crates/ui-egui/src/panels/viewer.rs index 7a0a126f..8e9197c0 100644 --- a/crates/ui-egui/src/panels/viewer.rs +++ b/crates/ui-egui/src/panels/viewer.rs @@ -674,7 +674,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { && let Some(nc) = app.session.project.comp(*item) { let (outer, _) = l2c(&ectx, l); - let nctx = EvalCtx::new(&app.session.project, *item, nc, ectx.source_time(l)); + let nctx = EvalCtx::new(&app.session.project, *item, nc, ectx.nested_time(l)); for nl in nc.layers.iter().filter(|nl| nl.is_active_at(nctx.time) && !nl.is_3d()) { if let Some((_, q, _)) = layer_quad(&nctx, nl) { let sq: Vec = q