use super::super::*;
use super::command::*;
use crate::numerics::*;
use bevy_ecs::prelude::*;
use windows::Win32::Graphics::Direct2D::*;
use windows_numerics::*;
type NodeData = (
Option<&'static VisualTransform>,
Option<&'static VisualDrawContent>,
Option<&'static VisualClip>,
Option<&'static Children>,
);
enum ClipKind {
Culled,
NoClip,
Rect { push: DrawCommand, new_eff: Aabb },
Geometry { push: DrawCommand, new_eff: Aabb },
}
#[allow(dead_code)]
pub(crate) fn build(
out: &mut Vec<DrawCommand>,
unit_rect: &ID2D1Geometry,
root_mat: Matrix3x2,
viewport: Aabb,
nodes: &Query<NodeData>,
root: Entity,
) {
out.clear();
visit(out, unit_rect, nodes, root, root_mat, viewport);
}
fn visit(
out: &mut Vec<DrawCommand>,
unit_rect: &ID2D1Geometry,
nodes: &Query<NodeData>,
entity: Entity,
parent_world: Matrix3x2,
eff: Aabb,
) {
if aabb_is_empty(&eff) {
return;
}
let Ok((transform, draw, clip, children)) = nodes.get(entity) else {
return;
};
let local = transform
.map(|t| t.0.into())
.unwrap_or(Matrix3x2::identity());
let world_mat = local * parent_world;
let (new_eff, pop) = match plan_clip(clip, unit_rect, entity, world_mat, eff) {
ClipKind::Culled => return,
ClipKind::NoClip => (eff, None),
ClipKind::Rect { push, new_eff } => {
out.push(push);
(new_eff, Some(DrawCommand::PopClipRect))
}
ClipKind::Geometry { push, new_eff } => {
out.push(push);
(new_eff, Some(DrawCommand::PopClipGeometry))
}
};
if let Some(content) = draw {
let world_aabb = transform_aabb(&content.local_aabb, &world_mat);
if aabb_intersects(&world_aabb, &new_eff) {
out.push(DrawCommand::Draw(DrawItem {
hash: hash_draw(content, &world_mat),
world_mat,
world_aabb,
entity,
}));
}
}
if let Some(children) = children {
for child in children.iter() {
visit(out, unit_rect, nodes, child, world_mat, new_eff);
}
}
if let Some(cmd) = pop {
out.push(cmd);
}
}
fn plan_clip(
clip: Option<&VisualClip>,
unit_rect: &ID2D1Geometry,
entity: Entity,
world_mat: Matrix3x2,
eff: Aabb,
) -> ClipKind {
let Some(clip) = clip else {
return ClipKind::NoClip;
};
match clip {
VisualClip::Rect(local) => {
if is_rect_preserving(&world_mat) {
let world_aabb = transform_aabb(local, &world_mat);
let new_eff = aabb_intersect(&eff, &world_aabb);
if aabb_is_empty(&new_eff) {
return ClipKind::Culled;
}
ClipKind::Rect {
push: DrawCommand::PushClipRect(ClipRect { world_aabb }),
new_eff,
}
} else {
let m = unit_to_rect(local) * world_mat;
let world_aabb = geometry_bounds(unit_rect, &m);
let new_eff = aabb_intersect(&eff, &world_aabb);
if aabb_is_empty(&new_eff) {
return ClipKind::Culled;
}
ClipKind::Geometry {
push: DrawCommand::PushClipGeometryRect(ClipGeometryRect {
world_mat: m,
world_aabb,
geometry: unit_rect.clone(), }),
new_eff,
}
}
}
VisualClip::Geometry(local_geom) => {
let world_aabb = geometry_bounds(local_geom, &world_mat);
let new_eff = aabb_intersect(&eff, &world_aabb);
if aabb_is_empty(&new_eff) {
return ClipKind::Culled;
}
ClipKind::Geometry {
push: DrawCommand::PushClipGeometryEntity(ClipGeometryEntity {
world_mat,
world_aabb,
entity,
}),
new_eff,
}
}
}
}
fn aabb_intersect(a: &Aabb, b: &Aabb) -> Aabb {
Aabb::new(
a.left().max(b.left()),
a.top().max(b.top()),
a.right().min(b.right()),
a.bottom().min(b.bottom()),
)
}
fn aabb_is_empty(a: &Aabb) -> bool {
a.right() <= a.left() || a.bottom() <= a.top()
}
fn aabb_intersects(a: &Aabb, b: &Aabb) -> bool {
a.left() < b.right() && b.left() < a.right() && a.top() < b.bottom() && b.top() < a.bottom()
}
fn geometry_bounds(g: &ID2D1Geometry, m: &Matrix3x2) -> Aabb {
let r = unsafe { g.GetBounds(Some(m as *const _)) }.unwrap_or_default();
Aabb::new_rect(r)
}
fn hash_draw(c: &VisualDrawContent, world: &Matrix3x2) -> u64 {
use std::hash::{Hash, Hasher};
let mut state = std::collections::hash_map::DefaultHasher::new();
c.hash(&mut state);
world.calc_hash(&mut state);
state.finish()
}
fn unit_to_rect(r: &Aabb) -> Matrix3x2 {
Matrix3x2 {
M11: r.width(),
M12: 0.0,
M21: 0.0,
M22: r.height(),
M31: r.left(),
M32: r.top(),
}
}
fn is_rect_preserving(m: &Matrix3x2) -> bool {
const EPS: f32 = 1e-4;
let no_skew = m.M12.abs() < EPS && m.M21.abs() < EPS;
let quarter = m.M11.abs() < EPS && m.M22.abs() < EPS;
no_skew || quarter
}
fn transform_aabb(a: &Aabb, m: &Matrix3x2) -> Aabb {
let corners = [
(a.left(), a.top()),
(a.right(), a.top()),
(a.right(), a.bottom()),
(a.left(), a.bottom()),
];
let (mut left, mut top, mut right, mut bottom) = (
f32::INFINITY,
f32::INFINITY,
f32::NEG_INFINITY,
f32::NEG_INFINITY,
);
for (x, y) in corners {
let wx = x * m.M11 + y * m.M21 + m.M31;
let wy = x * m.M12 + y * m.M22 + m.M32;
left = left.min(wx);
top = top.min(wy);
right = right.max(wx);
bottom = bottom.max(wy);
}
Aabb::new(left, top, right, bottom)
}