Serve bounding boxes through the lane source

This commit is contained in:
Dennis Kobert
2026-08-26 19:43:31 +00:00
parent c7605d12a6
commit 5987c449b2
3 changed files with 70 additions and 39 deletions

View File

@@ -1,4 +1,5 @@
use crate::Color;
use crate::lane::LaneSource;
use glam::{DAffine2, DVec2};
#[derive(Clone, Copy, Default, Debug, PartialEq)]
@@ -51,3 +52,60 @@ impl BoundingBox for Color {
RenderBoundingBox::Infinite
}
}
/// Combined bounding box of a lane source's elements, composing each lane's
/// transform attribute with the given transform.
pub fn lane_bounding_box<S: LaneSource>(source: &S, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox
where
S::Element: BoundingBox,
{
let mut combined_bounds = None;
for lane in 0..source.lane_count() {
let Some(element) = source.element(lane) else { continue };
let lane_transform: DAffine2 = source.attr::<crate::attribute::Transform>(lane);
match element.bounding_box(transform * lane_transform, include_stroke) {
RenderBoundingBox::None => continue,
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
RenderBoundingBox::Rectangle(bounds) => match combined_bounds {
Some(existing) => combined_bounds = Some(crate::math::quad::Quad::combine_bounds(existing, bounds)),
None => combined_bounds = Some(bounds),
},
}
}
match combined_bounds {
Some(bounds) => RenderBoundingBox::Rectangle(bounds),
None => RenderBoundingBox::None,
}
}
/// As [`lane_bounding_box`], but `Infinite` lanes are skipped (rather than
/// propagating outward) so a finite sibling in a mixed group dictates the
/// framing.
pub fn lane_thumbnail_bounding_box<S: LaneSource>(source: &S, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox
where
S::Element: BoundingBox,
{
let mut combined_bounds = None;
let mut any_infinite = false;
for lane in 0..source.lane_count() {
let Some(element) = source.element(lane) else { continue };
let lane_transform: DAffine2 = source.attr::<crate::attribute::Transform>(lane);
match element.thumbnail_bounding_box(transform * lane_transform, include_stroke) {
RenderBoundingBox::None => continue,
RenderBoundingBox::Infinite => any_infinite = true,
RenderBoundingBox::Rectangle(bounds) => match combined_bounds {
Some(existing) => combined_bounds = Some(crate::math::quad::Quad::combine_bounds(existing, bounds)),
None => combined_bounds = Some(bounds),
},
}
}
match (combined_bounds, any_infinite) {
(Some(bounds), _) => RenderBoundingBox::Rectangle(bounds),
(None, true) => RenderBoundingBox::Infinite,
(None, false) => RenderBoundingBox::None,
}
}

View File

@@ -1,6 +1,5 @@
use crate::attribute::Attribute as _;
use crate::bounds::{BoundingBox, RenderBoundingBox};
use crate::math::quad::Quad;
use crate::transform::ApplyTransform;
use dyn_any::{StaticType, StaticTypeSized};
use glam::DAffine2;
@@ -1105,48 +1104,12 @@ impl<T> List<T> {
}
impl<T: BoundingBox> BoundingBox for List<T> {
/// Computes the combined bounding box of all items, composing each item's transform attribute with the given transform.
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
let mut combined_bounds = None;
for (element, item_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM)) {
match element.bounding_box(transform * item_transform, include_stroke) {
RenderBoundingBox::None => continue,
RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite,
RenderBoundingBox::Rectangle(bounds) => match combined_bounds {
Some(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)),
None => combined_bounds = Some(bounds),
},
}
}
match combined_bounds {
Some(bounds) => RenderBoundingBox::Rectangle(bounds),
None => RenderBoundingBox::None,
}
crate::bounds::lane_bounding_box(self, transform, include_stroke)
}
fn thumbnail_bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox {
// `Infinite` items are skipped here (rather than propagating outward as in `bounding_box`) so a finite sibling in a mixed group dictates the framing
let mut combined_bounds = None;
let mut any_infinite = false;
for (element, item_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::<DAffine2>(ATTR_TRANSFORM)) {
match element.thumbnail_bounding_box(transform * item_transform, include_stroke) {
RenderBoundingBox::None => continue,
RenderBoundingBox::Infinite => any_infinite = true,
RenderBoundingBox::Rectangle(bounds) => match combined_bounds {
Some(existing) => combined_bounds = Some(Quad::combine_bounds(existing, bounds)),
None => combined_bounds = Some(bounds),
},
}
}
match (combined_bounds, any_infinite) {
(Some(bounds), _) => RenderBoundingBox::Rectangle(bounds),
(None, true) => RenderBoundingBox::Infinite,
(None, false) => RenderBoundingBox::None,
}
crate::bounds::lane_thumbnail_bounding_box(self, transform, include_stroke)
}
}

View File

@@ -1919,6 +1919,16 @@ impl<'a, T: 'static> crate::lane::LaneSource for RunView<'a, T> {
}
}
impl<T: crate::bounds::BoundingBox + 'static> crate::bounds::BoundingBox for RunView<'_, T> {
fn bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox {
crate::bounds::lane_bounding_box(self, transform, include_stroke)
}
fn thumbnail_bounding_box(&self, transform: glam::DAffine2, include_stroke: bool) -> crate::bounds::RenderBoundingBox {
crate::bounds::lane_thumbnail_bounding_box(self, transform, include_stroke)
}
}
/// The records a group stores: a single homogeneous run, or a list of
/// segments.
#[derive(Clone, Debug)]