use super::rectangles::TexturedRect;
#[derive(Clone, Copy)]
pub(crate) struct RectangleClusterInfo {
pub sorting_position: glam::Vec3A,
pub has_coplanar_overlap: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
struct PlanarBucketId {
quantized_normal: [i16; 2],
quantized_distance: i32,
}
#[derive(Clone)]
struct BucketedRectangle {
index: usize,
center: glam::Vec3A,
projected_rect: OrientedRect2D,
}
#[derive(Clone, Copy)]
struct OrientedRect2D {
center: glam::Vec2,
axes: [glam::Vec2; 2],
half_extents: [f32; 2],
}
pub(crate) fn cluster_rectangles(rectangles: &[TexturedRect]) -> Vec<RectangleClusterInfo> {
re_tracing::profile_function!();
let mut cluster_infos = rectangles
.iter()
.map(|rectangle| RectangleClusterInfo {
sorting_position: rectangle.center(),
has_coplanar_overlap: false,
})
.collect::<Vec<_>>();
for bucket_rectangles in bucket_rectangles_by_plane(rectangles).into_values() {
for cluster in overlapping_clusters(&bucket_rectangles) {
if cluster.len() <= 1 {
continue;
}
let cluster_center =
cluster.iter().map(|rect| rect.center).sum::<glam::Vec3A>() / cluster.len() as f32;
for rectangle in cluster {
cluster_infos[rectangle.index] = RectangleClusterInfo {
sorting_position: cluster_center,
has_coplanar_overlap: true,
};
}
}
}
cluster_infos
}
fn bucket_rectangles_by_plane(
rectangles: &[TexturedRect],
) -> std::collections::HashMap<PlanarBucketId, Vec<BucketedRectangle>> {
let mut buckets: std::collections::HashMap<PlanarBucketId, Vec<BucketedRectangle>> =
std::collections::HashMap::default();
for (index, rectangle) in rectangles.iter().enumerate() {
let (Some(bucket_id), Some(projected_rect)) =
(planar_bucket_id(rectangle), projected_rect(rectangle))
else {
continue;
};
buckets
.entry(bucket_id)
.or_default()
.push(BucketedRectangle {
index,
center: rectangle.center(),
projected_rect,
});
}
buckets
}
fn overlapping_clusters(bucket_rectangles: &[BucketedRectangle]) -> Vec<Vec<&BucketedRectangle>> {
let mut visited = vec![false; bucket_rectangles.len()];
let mut clusters = Vec::new();
for start_idx in 0..bucket_rectangles.len() {
if visited[start_idx] {
continue;
}
let mut remaining_unchecked_cluster_members = vec![start_idx];
visited[start_idx] = true;
let mut current_cluster = Vec::new();
while let Some(current_idx) = remaining_unchecked_cluster_members.pop() {
let current = &bucket_rectangles[current_idx];
current_cluster.push(current);
for (next_idx, next) in bucket_rectangles.iter().enumerate() {
if visited[next_idx] {
continue;
}
if rectangles_overlap(¤t.projected_rect, &next.projected_rect) {
visited[next_idx] = true;
remaining_unchecked_cluster_members.push(next_idx);
}
}
}
clusters.push(current_cluster);
}
clusters
}
fn planar_bucket_id(rectangle: &TexturedRect) -> Option<PlanarBucketId> {
let normal = canonical_plane_normal(rectangle.extent_u.cross(rectangle.extent_v))?;
let distance = normal.dot(rectangle.top_left_corner_position);
Some(PlanarBucketId {
quantized_normal: quantize_normal_octahedral(normal),
quantized_distance: (distance / 1.0e-3).round() as i32,
})
}
fn projected_rect(rectangle: &TexturedRect) -> Option<OrientedRect2D> {
let normal = canonical_plane_normal(rectangle.extent_u.cross(rectangle.extent_v))?;
let basis_u = if normal.z.abs() < 0.999 {
normal.cross(glam::Vec3::Z).try_normalize()?
} else {
normal.cross(glam::Vec3::X).try_normalize()?
};
let basis_v = normal.cross(basis_u);
let project = |point: glam::Vec3| glam::vec2(point.dot(basis_u), point.dot(basis_v));
let extent_u_2d = project(rectangle.extent_u);
let extent_v_2d = project(rectangle.extent_v);
let half_u = extent_u_2d.length() * 0.5;
let half_v = extent_v_2d.length() * 0.5;
if half_u == 0.0 || half_v == 0.0 {
return None;
}
Some(OrientedRect2D {
center: project(
rectangle.top_left_corner_position
+ rectangle.extent_u * 0.5
+ rectangle.extent_v * 0.5,
),
axes: [extent_u_2d / (half_u * 2.0), extent_v_2d / (half_v * 2.0)],
half_extents: [half_u, half_v],
})
}
fn canonical_plane_normal(normal: glam::Vec3) -> Option<glam::Vec3> {
let normal = normal.try_normalize()?;
let canonical_sign = if normal.z != 0.0 {
normal.z.signum()
} else if normal.y != 0.0 {
normal.y.signum()
} else {
normal.x.signum()
};
Some(if canonical_sign < 0.0 {
-normal
} else {
normal
})
}
fn rectangles_overlap(a: &OrientedRect2D, b: &OrientedRect2D) -> bool {
overlap_on_axis(a, b, a.axes[0])
&& overlap_on_axis(a, b, a.axes[1])
&& overlap_on_axis(a, b, b.axes[0])
&& overlap_on_axis(a, b, b.axes[1])
}
fn overlap_on_axis(a: &OrientedRect2D, b: &OrientedRect2D, axis: glam::Vec2) -> bool {
let center_distance = (b.center - a.center).dot(axis).abs();
let a_radius = project_rect_radius(a, axis);
let b_radius = project_rect_radius(b, axis);
center_distance <= a_radius + b_radius
}
fn project_rect_radius(rect: &OrientedRect2D, axis: glam::Vec2) -> f32 {
rect.half_extents[0] * rect.axes[0].dot(axis).abs()
+ rect.half_extents[1] * rect.axes[1].dot(axis).abs()
}
fn quantize_normal_octahedral(normal: glam::Vec3) -> [i16; 2] {
let projected = normal / (normal.x.abs() + normal.y.abs() + normal.z.abs());
let encoded = if projected.z < 0.0 {
let folded = glam::vec2(projected.y, projected.x);
(glam::Vec2::ONE - folded.abs()) * glam::vec2(projected.x, projected.y).signum()
} else {
glam::vec2(projected.x, projected.y)
};
[
quantize_signed_unit_to_i16(encoded.x),
quantize_signed_unit_to_i16(encoded.y),
]
}
fn quantize_signed_unit_to_i16(value: f32) -> i16 {
(value.clamp(-1.0, 1.0) * f32::from(i16::MAX)).round() as i16
}