use crate::normalization::{NormalizedElement, NormalizedElementKind};
use crate::portable_math::sin_cos;
use crate::relationships::ResolvedRelationshipGraph;
use crate::scene_surface::SurfaceFallback;
use super::super::frame_target::{
relationship_frame_target, EffectiveFrameRendering, FrameViewport,
};
use super::super::geometry::{
include_diamond_bounds, include_ellipse_bounds, include_transformed_bounds,
linear_points_bounds, point_rotate_rads, reference_curve_bounds, source_layout_bounds,
GeneratedGeometry, Point,
};
use super::super::text::{FrameName, FRAME_NAME_BOX_HEIGHT, FRAME_NAME_OFFSET_Y};
pub(super) fn scene_bounds(
outlines: &[(NormalizedElement, Option<GeneratedGeometry>)],
fallbacks: &[SurfaceFallback],
frame_names: &[(usize, FrameName)],
relationships: &ResolvedRelationshipGraph,
frame_rendering: EffectiveFrameRendering,
viewport: Option<FrameViewport>,
) -> Option<(f64, f64, f64, f64)> {
let (mut min_x, mut min_y, mut max_x, mut max_y) = viewport.map_or(
(
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
),
|viewport| {
(
viewport.min_x,
viewport.min_y,
viewport.max_x,
viewport.max_y,
)
},
);
for (source, geometry) in outlines {
if source.stroke_width < 0.0
|| source.roughness < 0.0
|| !(0.0..=100.0).contains(&source.opacity)
|| source.seed == 0
{
return None;
}
let is_frame = matches!(
source.kind,
NormalizedElementKind::Frame(_) | NormalizedElementKind::MagicFrame(_)
);
if is_frame && !frame_rendering.enabled {
continue;
}
if let Some((_, name)) = frame_names
.iter()
.find(|(index, _)| *index == source.source.source_index())
{
include_transformed_bounds(
source.x,
source.y - FRAME_NAME_OFFSET_Y - FRAME_NAME_BOX_HEIGHT,
name.width,
FRAME_NAME_BOX_HEIGHT,
0.0,
&mut min_x,
&mut min_y,
&mut max_x,
&mut max_y,
);
}
if frame_rendering.enabled
&& frame_rendering.clip
&& relationship_frame_target(source.source.source_index(), relationships).is_some()
{
continue;
}
if viewport.is_some() {
continue;
}
include_element_bounds(
source,
geometry.as_ref(),
&mut min_x,
&mut min_y,
&mut max_x,
&mut max_y,
);
}
for fallback in fallbacks {
if viewport.is_some() {
continue;
}
let is_clipped_child = frame_rendering.enabled
&& frame_rendering.clip
&& fallback.element_id.as_deref().is_some_and(|id| {
relationships
.nodes
.iter()
.find(|node| node.id == id)
.and_then(|node| relationship_frame_target(node.source_index, relationships))
.is_some()
});
if is_clipped_child {
continue;
}
include_transformed_bounds(
fallback.x,
fallback.y,
fallback.width,
fallback.height,
fallback.angle,
&mut min_x,
&mut min_y,
&mut max_x,
&mut max_y,
);
}
Some((min_x, min_y, max_x, max_y))
}
fn include_element_bounds(
source: &NormalizedElement,
geometry: Option<&GeneratedGeometry>,
min_x: &mut f64,
min_y: &mut f64,
max_x: &mut f64,
max_y: &mut f64,
) {
match &source.kind {
NormalizedElementKind::Freedraw(freedraw) if !freedraw.points.is_empty() => {
let (local_min_x, local_min_y, local_max_x, local_max_y) = source_layout_bounds(source);
let (x1, y1, x2, y2) = (
local_min_x + source.x,
local_min_y + source.y,
local_max_x + source.x,
local_max_y + source.y,
);
let center = Point {
x: (x1 + x2) / 2.0 - source.x,
y: (y1 + y2) / 2.0 - source.y,
};
let sin_cos = sin_cos(source.angle);
let (mut local_min_x, mut local_min_y, mut local_max_x, mut local_max_y) = (
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
);
for point in &freedraw.points {
let rotated = point_rotate_rads(
Point {
x: point[0],
y: point[1],
},
center,
sin_cos,
);
local_min_x = local_min_x.min(rotated.x);
local_min_y = local_min_y.min(rotated.y);
local_max_x = local_max_x.max(rotated.x);
local_max_y = local_max_y.max(rotated.y);
}
*min_x = min_x.min(local_min_x + source.x);
*min_y = min_y.min(local_min_y + source.y);
*max_x = max_x.max(local_max_x + source.x);
*max_y = max_y.max(local_max_y + source.y);
}
NormalizedElementKind::Linear(linear) => {
include_linear_bounds(source, linear, geometry, min_x, min_y, max_x, max_y);
}
NormalizedElementKind::Diamond => include_diamond_bounds(
source.x,
source.y,
source.width,
source.height,
source.angle,
min_x,
min_y,
max_x,
max_y,
),
NormalizedElementKind::Ellipse => include_ellipse_bounds(
source.x,
source.y,
source.width,
source.height,
source.angle,
min_x,
min_y,
max_x,
max_y,
),
_ => include_transformed_bounds(
source.x,
source.y,
source.width,
source.height,
source.angle,
min_x,
min_y,
max_x,
max_y,
),
}
}
pub(super) fn element_bounds(
source: &NormalizedElement,
geometry: Option<&GeneratedGeometry>,
) -> (f64, f64, f64, f64) {
let (mut x1, mut y1, mut x2, mut y2) = (
f64::INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
f64::NEG_INFINITY,
);
include_element_bounds(source, geometry, &mut x1, &mut y1, &mut x2, &mut y2);
(x1, y1, x2, y2)
}
fn include_linear_bounds(
source: &NormalizedElement,
linear: &crate::normalization::NormalizedLinear,
geometry: Option<&GeneratedGeometry>,
min_x: &mut f64,
min_y: &mut f64,
max_x: &mut f64,
max_y: &mut f64,
) {
let (points_min_x, points_min_y, points_max_x, points_max_y) = linear_points_bounds(linear);
let (x1, y1, x2, y2) = (
points_min_x + source.x,
points_min_y + source.y,
points_max_x + source.x,
points_max_y + source.y,
);
let center = Point {
x: (x1 + x2) / 2.0,
y: (y1 + y2) / 2.0,
};
let sin_cos = sin_cos(source.angle);
let transform = |point: Point| {
point_rotate_rads(
Point {
x: source.x + point.x,
y: source.y + point.y,
},
center,
sin_cos,
)
};
let outline = match geometry {
Some(GeneratedGeometry::Rough(rough)) => {
let operations = match &rough.extent_outline {
Some(extent_outline) if linear.elbowed => &extent_outline.operations,
_ => &rough.drawing.outline.operations,
};
reference_curve_bounds(operations, transform)
}
_ => None,
};
match outline {
Some((curve_min_x, curve_min_y, curve_max_x, curve_max_y)) => {
*min_x = min_x.min(curve_min_x);
*min_y = min_y.min(curve_min_y);
*max_x = max_x.max(curve_max_x);
*max_y = max_y.max(curve_max_y);
}
None => {
for point in &linear.points {
let rotated = transform(Point {
x: point[0],
y: point[1],
});
*min_x = min_x.min(rotated.x);
*min_y = min_y.min(rotated.y);
*max_x = max_x.max(rotated.x);
*max_y = max_y.max(rotated.y);
}
}
}
}