use std::mem::MaybeUninit;
use bevy::{
asset::{AssetId, Assets, Handle},
color::Color,
ecs::{
component::Component,
entity::Entity,
query::{With, Without},
system::{Commands, Query, ResMut},
},
log::trace,
math::{bounding::Aabb2d, Rect, UVec2, Vec2, Vec3},
prelude::*,
render::{camera::Camera, texture::Image},
sprite::TextureAtlasLayout,
utils::default,
window::PrimaryWindow,
};
use bytemuck::{Pod, Zeroable};
use crate::{
render::{ExtractedCanvas, ExtractedText, PreparedPrimitive},
render_context::{ImageScaling, RenderContext, TextLayout},
ShapeRef,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PrimitiveInfo {
pub row_count: u32,
pub sub_prim_count: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum GpuPrimitiveKind {
Rect = 0,
Glyph = 1,
Line = 2,
QuarterPie = 3,
}
#[derive(Debug, Clone, Copy)]
pub enum Primitive {
Line(LinePrimitive),
Rect(RectPrimitive),
Text(TextPrimitive),
QuarterPie(QuarterPiePrimitive),
}
impl Primitive {
pub fn gpu_kind(&self) -> GpuPrimitiveKind {
match self {
Primitive::Line(_) => GpuPrimitiveKind::Line,
Primitive::Rect(_) => GpuPrimitiveKind::Rect,
Primitive::Text(_) => GpuPrimitiveKind::Glyph,
Primitive::QuarterPie(_) => GpuPrimitiveKind::QuarterPie,
}
}
pub fn aabb(&self) -> Aabb2d {
match self {
Primitive::Line(l) => l.aabb(),
Primitive::Rect(r) => r.aabb(),
Primitive::Text(_) => panic!("Cannot compute text AABB intrinsically."),
Primitive::QuarterPie(q) => q.aabb(),
}
}
pub fn is_textured(&self) -> bool {
match self {
Primitive::Line(_) => false,
Primitive::Rect(r) => r.is_textured(),
Primitive::Text(_) => false, Primitive::QuarterPie(_) => false,
}
}
pub fn is_bordered(&self) -> bool {
match self {
Primitive::Line(l) => l.is_bordered(),
Primitive::Rect(r) => r.is_bordered(),
Primitive::Text(_) => false,
Primitive::QuarterPie(_) => false,
}
}
pub(crate) fn info(&self, texts: &[ExtractedText]) -> PrimitiveInfo {
match &self {
Primitive::Line(l) => l.info(),
Primitive::Rect(r) => r.info(),
Primitive::Text(t) => t.info(texts),
Primitive::QuarterPie(q) => q.info(),
}
}
pub(crate) fn write(
&self,
texts: &[ExtractedText],
prim: &mut [MaybeUninit<f32>],
canvas_translation: Vec2,
scale_factor: f32,
) {
match &self {
Primitive::Line(l) => l.write(prim, canvas_translation, scale_factor),
Primitive::Rect(r) => r.write(prim, canvas_translation, scale_factor),
Primitive::Text(t) => t.write(texts, prim, canvas_translation, scale_factor),
Primitive::QuarterPie(q) => q.write(prim, canvas_translation, scale_factor),
};
}
}
impl From<LinePrimitive> for Primitive {
fn from(line: LinePrimitive) -> Self {
Self::Line(line)
}
}
impl From<RectPrimitive> for Primitive {
fn from(rect: RectPrimitive) -> Self {
Self::Rect(rect)
}
}
impl From<TextPrimitive> for Primitive {
fn from(text: TextPrimitive) -> Self {
Self::Text(text)
}
}
impl From<QuarterPiePrimitive> for Primitive {
fn from(qpie: QuarterPiePrimitive) -> Self {
Self::QuarterPie(qpie)
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct LinePrimitive {
pub start: Vec2,
pub end: Vec2,
pub color: Color,
pub thickness: f32,
pub border_width: f32,
pub border_color: Color,
}
impl LinePrimitive {
pub fn aabb(&self) -> Aabb2d {
let dir = (self.end - self.start).normalize();
let tg = Vec2::new(-dir.y, dir.x);
let e = self.thickness / 2.;
let p0 = self.start + tg * e;
let p1 = self.start - tg * e;
let p2 = self.end + tg * e;
let p3 = self.end - tg * e;
let min = p0.min(p1).min(p2).min(p3);
let max = p0.max(p1).max(p2).max(p3);
Aabb2d { min, max }
}
pub fn is_bordered(&self) -> bool {
self.border_width > 0.
}
fn info(&self) -> PrimitiveInfo {
PrimitiveInfo {
row_count: 6 + if self.is_bordered() { 2 } else { 0 },
sub_prim_count: 1,
}
}
fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
prim[0].write((self.start.x + canvas_translation.x) * scale_factor);
prim[1].write((self.start.y + canvas_translation.y) * scale_factor);
prim[2].write((self.end.x + canvas_translation.x) * scale_factor);
prim[3].write((self.end.y + canvas_translation.y) * scale_factor);
prim[4].write(bytemuck::cast(self.color.to_linear().as_u32()));
prim[5].write(self.thickness * scale_factor);
if self.is_bordered() {
assert_eq!(8, prim.len());
prim[6].write(self.border_width * scale_factor);
prim[7].write(bytemuck::cast(self.border_color.to_linear().as_u32()));
} else {
assert_eq!(6, prim.len());
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct RectPrimitive {
pub rect: Rect,
pub radius: f32,
pub color: Color,
pub image: Option<AssetId<Image>>,
pub image_size: Vec2,
pub image_scaling: ImageScaling,
pub flip_x: bool,
pub flip_y: bool,
pub border_width: f32,
pub border_color: Color,
}
impl RectPrimitive {
const ROW_COUNT_BASE: u32 = 6;
const ROW_COUNT_TEX: u32 = 4;
const ROW_COUNT_BORDER: u32 = 2;
pub fn aabb(&self) -> Aabb2d {
Aabb2d {
min: self.rect.min,
max: self.rect.max,
}
}
pub const fn is_textured(&self) -> bool {
self.image.is_some()
}
pub fn is_bordered(&self) -> bool {
self.border_width > 0.
}
#[inline]
fn row_count(&self) -> u32 {
let mut rows = Self::ROW_COUNT_BASE;
if self.is_textured() {
rows += Self::ROW_COUNT_TEX;
}
if self.is_bordered() {
rows += Self::ROW_COUNT_BORDER;
}
rows
}
fn info(&self) -> PrimitiveInfo {
PrimitiveInfo {
row_count: self.row_count(),
sub_prim_count: 1,
}
}
fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
assert_eq!(
self.row_count() as usize,
prim.len(),
"Invalid buffer size {} to write RectPrimitive (needs {})",
prim.len(),
self.row_count()
);
let half_min = self.rect.min * (0.5 * scale_factor);
let half_max = self.rect.max * (0.5 * scale_factor);
let center = half_min + half_max + canvas_translation * scale_factor;
let half_size = half_max - half_min;
prim[0].write(center.x);
prim[1].write(center.y);
prim[2].write(half_size.x);
prim[3].write(half_size.y);
prim[4].write(self.radius * scale_factor);
prim[5].write(bytemuck::cast(self.color.to_linear().as_u32()));
let mut idx = 6;
if self.is_textured() {
prim[idx + 0].write(0.5);
prim[idx + 1].write(0.5);
prim[idx + 2].write(1. / self.image_size.x);
prim[idx + 3].write(1. / self.image_size.y);
idx += 4;
}
if self.is_bordered() {
prim[idx + 0].write(self.border_width * scale_factor);
prim[idx + 1].write(bytemuck::cast(self.border_color.to_linear().as_u32()));
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TextPrimitive {
pub id: u32,
pub rect: Rect,
}
impl TextPrimitive {
pub const ROW_PER_GLYPH: u32 = RectPrimitive::ROW_COUNT_BASE + RectPrimitive::ROW_COUNT_TEX;
pub fn aabb(&self, canvas: &ExtractedCanvas) -> Aabb2d {
let text = &canvas.texts[self.id as usize];
let mut aabb = Aabb2d {
min: self.rect.min,
max: self.rect.max,
};
trace!("Text #{:?} aabb={:?}", self.id, aabb);
for glyph in &text.glyphs {
aabb.min = aabb.min.min(self.rect.min + glyph.offset);
aabb.max = aabb.max.max(self.rect.min + glyph.offset + glyph.size);
trace!(
" > add glyph offset={:?} size={:?}, new aabb {:?}",
glyph.offset,
glyph.size,
aabb
);
}
aabb
}
fn info(&self, texts: &[ExtractedText]) -> PrimitiveInfo {
let index = self.id as usize;
if index < texts.len() {
let glyph_count = texts[index].glyphs.len() as u32;
PrimitiveInfo {
row_count: Self::ROW_PER_GLYPH,
sub_prim_count: glyph_count,
}
} else {
PrimitiveInfo {
row_count: 0,
sub_prim_count: 0,
}
}
}
fn write(
&self,
texts: &[ExtractedText],
prim: &mut [MaybeUninit<f32>],
canvas_translation: Vec2,
scale_factor: f32,
) {
let index = self.id as usize;
let glyphs = &texts[index].glyphs;
let glyph_count = glyphs.len();
assert_eq!(glyph_count * Self::ROW_PER_GLYPH as usize, prim.len());
let mut ip = 0;
for i in 0..glyph_count {
let x = glyphs[i].offset.x + (self.rect.min.x + canvas_translation.x) * scale_factor;
let y = glyphs[i].offset.y + (self.rect.min.y + canvas_translation.y) * scale_factor;
let hw = glyphs[i].size.x / 2.0;
let hh = glyphs[i].size.y / 2.0;
let uv_x = glyphs[i].uv_rect.min.x / 1024.0;
let uv_y = glyphs[i].uv_rect.min.y / 1024.0;
let uv_w = glyphs[i].uv_rect.max.x / 1024.0 - uv_x;
let uv_h = glyphs[i].uv_rect.max.y / 1024.0 - uv_y;
prim[ip + 0].write(x.round() + hw);
prim[ip + 1].write(y.round() + hh);
prim[ip + 2].write(hw);
prim[ip + 3].write(hh);
prim[ip + 4].write(0.);
prim[ip + 5].write(bytemuck::cast(glyphs[i].color));
prim[ip + 6].write(uv_x + uv_w / 2.0);
prim[ip + 7].write(uv_y + uv_h / 2.0);
prim[ip + 8].write(1.0 / 1024.0);
prim[ip + 9].write(1.0 / 1024.0);
ip += Self::ROW_PER_GLYPH as usize;
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct QuarterPiePrimitive {
pub origin: Vec2,
pub radii: Vec2,
pub color: Color,
pub flip_x: bool,
pub flip_y: bool,
}
impl Default for QuarterPiePrimitive {
fn default() -> Self {
Self {
origin: Vec2::ZERO,
radii: Vec2::ONE,
color: Color::default(),
flip_x: false,
flip_y: false,
}
}
}
impl QuarterPiePrimitive {
const ROW_COUNT: u32 = 5;
pub fn aabb(&self) -> Aabb2d {
Aabb2d {
min: self.origin - self.radii,
max: self.origin + self.radii,
}
}
pub fn center(&self) -> Vec3 {
self.origin.extend(0.)
}
#[inline]
const fn row_count(&self) -> u32 {
Self::ROW_COUNT
}
fn info(&self) -> PrimitiveInfo {
PrimitiveInfo {
row_count: self.row_count(),
sub_prim_count: 1,
}
}
fn write(&self, prim: &mut [MaybeUninit<f32>], canvas_translation: Vec2, scale_factor: f32) {
assert_eq!(self.row_count() as usize, prim.len());
let radii_mask = BVec2::new(self.flip_x, self.flip_y);
let signed_radii = Vec2::select(radii_mask, -self.radii, self.radii);
prim[0].write((self.origin.x + canvas_translation.x) * scale_factor);
prim[1].write((self.origin.y + canvas_translation.y) * scale_factor);
prim[2].write(signed_radii.x * scale_factor);
prim[3].write(signed_radii.y * scale_factor);
prim[4].write(bytemuck::cast(self.color.to_linear().as_u32()));
}
}
#[derive(Component)]
pub struct Canvas {
rect: Rect,
pub background_color: Option<Color>,
primitives: Vec<Primitive>,
pub(crate) text_layouts: Vec<TextLayout>,
pub(crate) atlas_layout: Handle<TextureAtlasLayout>,
}
impl Default for Canvas {
fn default() -> Self {
Self {
rect: Rect::default(),
background_color: None,
primitives: vec![],
text_layouts: vec![],
atlas_layout: Handle::default(),
}
}
}
impl Canvas {
pub fn new(rect: Rect) -> Self {
Self { rect, ..default() }
}
pub fn set_rect(&mut self, rect: Rect) {
self.rect = rect;
}
pub fn rect(&self) -> Rect {
self.rect
}
pub fn clear(&mut self) {
self.primitives.clear();
self.text_layouts.clear();
if let Some(color) = self.background_color {
self.draw(RectPrimitive {
rect: self.rect,
color,
..default()
});
}
}
#[inline]
pub fn draw<'a>(&'a mut self, prim: impl Into<Primitive>) -> ShapeRef<'a> {
let prim = prim.into();
self.primitives.push(prim);
let sref = ShapeRef {
prim: self.primitives.last_mut().unwrap(),
};
sref
}
pub fn render_context(&mut self) -> RenderContext {
RenderContext::new(self)
}
pub(crate) fn finish(&mut self) {
}
pub(crate) fn finish_layout(&mut self, mut layout: TextLayout) -> u32 {
let id = self.text_layouts.len() as u32;
trace!("finish_layout() for text #{}", id);
layout.id = id;
self.text_layouts.push(layout);
id
}
pub(crate) fn buffer(&self) -> &Vec<Primitive> {
&self.primitives
}
pub(crate) fn text_layouts(&self) -> &[TextLayout] {
&self.text_layouts[..]
}
pub(crate) fn text_layouts_mut(&mut self) -> &mut [TextLayout] {
&mut self.text_layouts[..]
}
pub(crate) fn has_text(&self) -> bool {
!self.text_layouts.is_empty()
}
}
pub fn update_canvas_from_ortho_camera(mut query: Query<(&mut Canvas, &OrthographicProjection)>) {
trace!("PreUpdate: update_canvas_from_ortho_camera()");
for (mut canvas, ortho) in query.iter_mut() {
trace!("ortho canvas rect = {:?}", ortho.area);
canvas.set_rect(ortho.area);
}
}
#[derive(Default, Clone, Copy, Component)]
pub struct TileConfig {}
#[derive(Debug, Default, Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub(crate) struct OffsetAndCount {
pub offset: u32,
pub count: u32,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
#[repr(transparent)]
pub(crate) struct PackedPrimitiveIndex(pub u32);
impl PackedPrimitiveIndex {
pub fn new(index: u32, kind: GpuPrimitiveKind, textured: bool, bordered: bool) -> Self {
let textured = (textured as u32) << 31;
let bordered = (bordered as u32) << 27;
let value = (index & 0x07FF_FFFF) | (kind as u32) << 28 | textured | bordered;
Self(value)
}
}
#[derive(Clone, Copy)]
struct AssignedTile {
pub tile_index: i32,
pub prim_index: PackedPrimitiveIndex,
}
#[derive(Default, Clone, Component)]
pub struct Tiles {
pub(crate) tile_size: UVec2,
pub(crate) dimensions: UVec2,
pub(crate) primitives: Vec<PackedPrimitiveIndex>,
pub(crate) offset_and_count: Vec<OffsetAndCount>,
assigned_tiles: Vec<AssignedTile>,
}
impl Tiles {
pub fn update_size(&mut self, screen_size: UVec2) {
self.tile_size = UVec2::new(8, 8);
self.dimensions = (screen_size.as_vec2() / self.tile_size.as_vec2())
.ceil()
.as_uvec2();
assert!(self.dimensions.x * self.tile_size.x >= screen_size.x);
assert!(self.dimensions.y * self.tile_size.y >= screen_size.y);
self.primitives.clear();
self.offset_and_count.clear();
self.offset_and_count
.reserve(self.dimensions.x as usize * self.dimensions.y as usize);
trace!(
"Resized Tiles at tile_size={:?} dim={:?} and cleared buffers",
self.tile_size,
self.dimensions
);
}
pub(crate) fn assign_to_tiles(&mut self, primitives: &[PreparedPrimitive], screen_size: Vec2) {
let tile_size = self.tile_size.as_vec2();
let oc_extra = self.dimensions.x as usize * self.dimensions.y as usize;
self.offset_and_count.reserve(oc_extra);
self.assigned_tiles.reserve(primitives.len() * 4);
for prim in primitives {
let uv_min = (prim.aabb.min.clamp(Vec2::ZERO, screen_size) / tile_size)
.floor()
.as_ivec2();
let mut uv_max = (prim.aabb.max.clamp(Vec2::ZERO, screen_size) / tile_size)
.ceil()
.as_ivec2();
if prim.aabb.max.x == tile_size.x * uv_max.x as f32 {
uv_max.x -= 1;
}
if prim.aabb.max.y == tile_size.y * uv_max.y as f32 {
uv_max.y -= 1;
}
self.assigned_tiles
.reserve((uv_max.y - uv_min.y + 1) as usize * (uv_max.x - uv_min.x + 1) as usize);
for ty in uv_min.y..=uv_max.y {
let base_tile_index = ty * self.dimensions.x as i32;
for tx in uv_min.x..=uv_max.x {
let tile_index = base_tile_index + tx;
self.assigned_tiles.push(AssignedTile {
tile_index,
prim_index: prim.prim_index,
});
}
}
}
self.assigned_tiles.sort_by_key(|at| at.tile_index);
self.primitives.reserve(self.assigned_tiles.len());
let mut ti = -1;
let mut offset = 0;
let mut count = 0;
for at in &self.assigned_tiles {
if at.tile_index != ti {
if count > 0 {
self.offset_and_count.push(OffsetAndCount {
offset: offset as u32,
count,
});
}
for _ in ti + 1..at.tile_index {
self.offset_and_count.push(OffsetAndCount {
offset: offset as u32,
count: 0,
});
}
offset = self.primitives.len() as u32;
count = 0;
ti = at.tile_index;
}
self.primitives.push(at.prim_index);
count += 1;
}
if count > 0 {
self.offset_and_count.push(OffsetAndCount {
offset: offset as u32,
count,
});
}
for _ in ti + 1..oc_extra as i32 {
self.offset_and_count.push(OffsetAndCount {
offset: offset as u32,
count: 0,
});
}
self.assigned_tiles.clear();
}
}
pub fn spawn_missing_tiles_components(
mut commands: Commands,
cameras: Query<(Entity, Option<&TileConfig>, &Camera), (With<Canvas>, Without<Tiles>)>,
) {
for (entity, config, camera) in &cameras {
if !camera.is_active {
continue;
}
let config = config.copied().unwrap_or_default();
commands.entity(entity).insert((Tiles::default(), config));
}
}
pub fn resize_tiles_to_camera_render_target(
mut views: Query<(&Camera, &TileConfig, &mut Tiles), With<Canvas>>,
) {
for (camera, _tile_config, tiles) in &mut views {
let Some(screen_size) = camera.physical_viewport_size() else {
continue;
};
let tiles = tiles.into_inner();
tiles.update_size(screen_size);
}
}
pub fn allocate_atlas_layouts(
mut query: Query<&mut Canvas>,
mut layouts: ResMut<Assets<TextureAtlasLayout>>,
) {
for mut canvas in query.iter_mut() {
let size = UVec2::splat(1024);
if canvas.atlas_layout == Handle::<TextureAtlasLayout>::default() {
canvas.atlas_layout = layouts.add(TextureAtlasLayout::new_empty(size));
}
}
}
fn aspect_width(size: Vec2, content_height: f32) -> f32 {
size.x.max(0.) / size.y.max(1.) * content_height.max(0.)
}
fn fit_width(size: Vec2, content_size: Vec2, stretch_height: bool) -> Vec2 {
Vec2::new(
content_size.x,
if stretch_height {
content_size.y
} else {
aspect_height(size, content_size.x)
},
)
}
fn aspect_height(size: Vec2, content_width: f32) -> f32 {
size.y.max(0.) / size.x.max(1.) * content_width.max(0.)
}
fn fit_height(size: Vec2, content_size: Vec2, stretch_width: bool) -> Vec2 {
Vec2::new(
if stretch_width {
content_size.x
} else {
aspect_width(size, content_size.y)
},
content_size.y,
)
}
fn fit_any(size: Vec2, content_size: Vec2, stretch_other: bool) -> Vec2 {
let aspect = size.x.max(0.) / size.y.max(1.);
let content_aspect = content_size.x.max(0.) / content_size.y.max(1.);
if aspect >= content_aspect {
fit_height(size, content_size, stretch_other)
} else {
fit_width(size, content_size, stretch_other)
}
}
pub fn process_images(
images: Res<Assets<Image>>,
q_window: Query<&Window, With<PrimaryWindow>>,
mut q_canvas: Query<&mut Canvas>,
) {
let Ok(primary_window) = q_window.get_single() else {
return;
};
let scale_factor = primary_window.scale_factor() as f32;
for mut canvas in q_canvas.iter_mut() {
for prim in &mut canvas.primitives {
let Primitive::Rect(rect) = prim else {
continue;
};
let Some(id) = rect.image else {
continue;
};
if let Some(image) = images.get(id) {
let image_size = Vec2::new(
image.texture_descriptor.size.width as f32,
image.texture_descriptor.size.height as f32,
);
let content_size = rect.rect.size() * scale_factor;
rect.image_size = match rect.image_scaling {
ImageScaling::Uniform(ratio) => image_size * ratio,
ImageScaling::FitWidth(stretch_height) => {
fit_width(image_size, content_size, stretch_height)
}
ImageScaling::FitHeight(stretch_width) => {
fit_height(image_size, content_size, stretch_width)
}
ImageScaling::Fit(stretch_other) => {
fit_any(image_size, content_size, stretch_other)
}
ImageScaling::Stretch => content_size,
}
} else {
warn!("Unknown image asset ID {:?}; skipped.", id);
rect.image = None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tiles() {
let mut tiles = Tiles::default();
tiles.update_size(UVec2::new(32, 64));
assert_eq!(tiles.dimensions, UVec2::new(4, 8));
assert!(tiles.primitives.is_empty());
assert!(tiles.offset_and_count.is_empty());
assert_eq!(tiles.offset_and_count.capacity(), 32);
let prim_index = PackedPrimitiveIndex::new(42, GpuPrimitiveKind::Line, true, false);
tiles.assign_to_tiles(
&[PreparedPrimitive {
aabb: Aabb2d {
min: Vec2::new(8., 16.),
max: Vec2::new(16., 32.),
},
prim_index,
}],
Vec2::new(256., 128.),
);
assert_eq!(tiles.primitives.len(), 2);
assert_eq!(tiles.primitives[0], prim_index);
assert_eq!(tiles.primitives[1], prim_index);
assert_eq!(tiles.offset_and_count.len(), 32);
for (idx, oc) in tiles.offset_and_count.iter().enumerate() {
if idx == 9 || idx == 13 {
assert_eq!(oc.count, 1);
assert_eq!(oc.offset, if idx == 9 { 0 } else { 1 });
} else {
assert_eq!(oc.count, 0);
}
}
}
#[test]
fn aspect() {
assert_eq!(aspect_width(Vec2::ZERO, 0.), 0.);
assert_eq!(aspect_height(Vec2::ZERO, 0.), 0.);
assert_eq!(aspect_width(Vec2::ZERO, 1.), 0.);
assert_eq!(aspect_height(Vec2::ZERO, 1.), 0.);
assert_eq!(aspect_width(Vec2::ONE, 0.), 0.);
assert_eq!(aspect_height(Vec2::ONE, 0.), 0.);
assert_eq!(aspect_width(Vec2::new(256., 64.), 128.), 512.);
assert_eq!(aspect_height(Vec2::new(256., 64.), 512.), 128.);
assert_eq!(aspect_width(Vec2::new(256., 128.), 64.), 128.);
assert_eq!(aspect_height(Vec2::new(256., 64.), 128.), 32.);
}
#[test]
fn fit() {
assert_eq!(fit_width(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
assert_eq!(fit_height(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
assert_eq!(fit_any(Vec2::ZERO, Vec2::ZERO, false), Vec2::ZERO);
assert_eq!(fit_width(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
assert_eq!(fit_height(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
assert_eq!(fit_any(Vec2::ONE, Vec2::ZERO, false), Vec2::ZERO);
assert_eq!(fit_width(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
assert_eq!(fit_height(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
assert_eq!(fit_any(Vec2::ZERO, Vec2::ZERO, true), Vec2::ZERO);
assert_eq!(fit_width(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
assert_eq!(fit_height(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
assert_eq!(fit_any(Vec2::ONE, Vec2::ZERO, true), Vec2::ZERO);
assert_eq!(fit_width(Vec2::ZERO, Vec2::ONE, false), Vec2::X);
assert_eq!(fit_height(Vec2::ZERO, Vec2::ONE, false), Vec2::Y);
assert_eq!(fit_width(Vec2::ZERO, Vec2::ONE, true), Vec2::ONE);
assert_eq!(fit_height(Vec2::ZERO, Vec2::ONE, true), Vec2::ONE);
assert_eq!(
fit_width(Vec2::new(256., 64.), Vec2::new(512., 32.), false),
Vec2::new(512., 128.)
);
assert_eq!(
fit_height(Vec2::new(256., 64.), Vec2::new(128., 128.), false),
Vec2::new(512., 128.)
);
assert_eq!(
fit_width(Vec2::new(256., 64.), Vec2::new(512., 32.), true),
Vec2::new(512., 32.)
);
assert_eq!(
fit_height(Vec2::new(256., 64.), Vec2::new(128., 128.), true),
Vec2::new(128., 128.)
);
assert_eq!(
fit_width(Vec2::new(256., 64.), Vec2::new(128., 128.), false),
Vec2::new(128., 32.)
);
assert_eq!(
fit_height(Vec2::new(256., 64.), Vec2::new(512., 32.), false),
Vec2::new(128., 32.)
);
assert_eq!(
fit_width(Vec2::new(256., 64.), Vec2::new(128., 128.), true),
Vec2::new(128., 128.)
);
assert_eq!(
fit_height(Vec2::new(256., 64.), Vec2::new(512., 32.), true),
Vec2::new(512., 32.)
);
}
}