#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
pub const ZERO: Vec2 = Vec2 { x: 0.0, y: 0.0 };
pub fn to_value(self) -> crate::value::Value {
use crate::value::Value;
Value::map([("x", Value::float(self.x)), ("y", Value::float(self.y))])
}
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn plus(self, o: Vec2) -> Vec2 {
Vec2::new(self.x + o.x, self.y + o.y)
}
pub fn minus(self, o: Vec2) -> Vec2 {
Vec2::new(self.x - o.x, self.y - o.y)
}
pub(crate) fn snapped(self, scale: f32) -> Self {
if scale <= 0.0 || !scale.is_finite() {
return self;
}
Self::new(
snap_px(self.x * scale) / scale,
snap_px(self.y * scale) / scale,
)
}
}
pub(crate) fn snap_px(v: f32) -> f32 {
const TIE: f32 = 1.0 / 1024.0;
(v + 0.5 + TIE).floor()
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Size {
pub w: f32,
pub h: f32,
}
impl Size {
pub const ZERO: Size = Size { w: 0.0, h: 0.0 };
pub fn new(w: f32, h: f32) -> Self {
Self { w, h }
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Rect {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
impl Rect {
pub fn to_value(self) -> crate::value::Value {
use crate::value::Value;
Value::map([
("x", Value::float(self.x)),
("y", Value::float(self.y)),
("w", Value::float(self.w)),
("h", Value::float(self.h)),
])
}
pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
Self { x, y, w, h }
}
pub fn from_pos_size(pos: Vec2, size: Size) -> Self {
Self {
x: pos.x,
y: pos.y,
w: size.w,
h: size.h,
}
}
pub fn center(&self) -> Vec2 {
Vec2 {
x: self.x + self.w / 2.0,
y: self.y + self.h / 2.0,
}
}
pub fn contains(&self, p: Vec2) -> bool {
p.x >= self.x && p.x < self.x + self.w && p.y >= self.y && p.y < self.y + self.h
}
pub fn scaled(&self, s: f32) -> Rect {
Rect {
x: self.x * s,
y: self.y * s,
w: self.w * s,
h: self.h * s,
}
}
pub(crate) fn on_pixels(&self) -> Rect {
let (x0, y0) = (snap_px(self.x), snap_px(self.y));
let (x1, y1) = (snap_px(self.x + self.w), snap_px(self.y + self.h));
Rect::new(x0, y0, x1 - x0, y1 - y0)
}
pub fn union(&self, other: &Rect) -> Rect {
let x = self.x.min(other.x);
let y = self.y.min(other.y);
let r = (self.x + self.w).max(other.x + other.w);
let b = (self.y + self.h).max(other.y + other.h);
Rect::new(x, y, r - x, b - y)
}
pub fn intersect(&self, other: &Rect) -> Rect {
let x = self.x.max(other.x);
let y = self.y.max(other.y);
let r = (self.x + self.w).min(other.x + other.w);
let b = (self.y + self.h).min(other.y + other.h);
Rect {
x,
y,
w: (r - x).max(0.0),
h: (b - y).max(0.0),
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transform {
pub angle: f32,
pub scale: f32,
pub tx: f32,
pub ty: f32,
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
impl Transform {
pub const IDENTITY: Transform = Transform {
angle: 0.0,
scale: 1.0,
tx: 0.0,
ty: 0.0,
};
pub fn about(pivot: Vec2, turns: f32, scale: f32) -> Self {
let angle = turns * std::f32::consts::TAU;
let (s, c) = angle.sin_cos();
let rx = (pivot.x * c - pivot.y * s) * scale;
let ry = (pivot.x * s + pivot.y * c) * scale;
Transform {
angle,
scale,
tx: pivot.x - rx,
ty: pivot.y - ry,
}
}
pub fn is_identity(&self) -> bool {
self.angle == 0.0 && self.scale == 1.0 && self.tx == 0.0 && self.ty == 0.0
}
pub fn apply(&self, p: Vec2) -> Vec2 {
let (s, c) = self.angle.sin_cos();
let x = p.x * self.scale;
let y = p.y * self.scale;
Vec2 {
x: x * c - y * s + self.tx,
y: x * s + y * c + self.ty,
}
}
pub fn unapply(&self, p: Vec2) -> Vec2 {
if self.scale == 0.0 {
return Vec2::new(f32::NAN, f32::NAN);
}
let (s, c) = (-self.angle).sin_cos();
let x = p.x - self.tx;
let y = p.y - self.ty;
Vec2 {
x: (x * c - y * s) / self.scale,
y: (x * s + y * c) / self.scale,
}
}
pub fn then(&self, outer: &Transform) -> Transform {
let t = outer.apply(Vec2::new(self.tx, self.ty));
Transform {
angle: self.angle + outer.angle,
scale: self.scale * outer.scale,
tx: t.x,
ty: t.y,
}
}
pub fn scaled(&self, s: f32) -> Transform {
Transform {
angle: self.angle,
scale: self.scale,
tx: self.tx * s,
ty: self.ty * s,
}
}
pub fn bounds(&self, r: Rect) -> Rect {
let corners = [
self.apply(Vec2::new(r.x, r.y)),
self.apply(Vec2::new(r.x + r.w, r.y)),
self.apply(Vec2::new(r.x + r.w, r.y + r.h)),
self.apply(Vec2::new(r.x, r.y + r.h)),
];
let mut x0 = f32::INFINITY;
let mut y0 = f32::INFINITY;
let mut x1 = f32::NEG_INFINITY;
let mut y1 = f32::NEG_INFINITY;
for c in corners {
x0 = x0.min(c.x);
y0 = y0.min(c.y);
x1 = x1.max(c.x);
y1 = y1.max(c.y);
}
Rect::new(x0, y0, x1 - x0, y1 - y0)
}
pub fn unbounds(&self, r: Rect) -> Rect {
if self.scale == 0.0 {
return Rect::new(0.0, 0.0, 0.0, 0.0);
}
let inv = Transform {
angle: -self.angle,
scale: 1.0 / self.scale,
tx: 0.0,
ty: 0.0,
};
let o = inv.apply(Vec2::new(-self.tx, -self.ty));
Transform {
tx: o.x,
ty: o.y,
..inv
}
.bounds(r)
}
pub(crate) fn lanes(turns: f32, scale: f32) -> [f32; 4] {
[finite_or(turns, 0.0), finite_or(scale, 1.0), 0.0, 0.0]
}
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Edges {
pub l: f32,
pub r: f32,
pub t: f32,
pub b: f32,
}
impl Edges {
pub fn all(v: f32) -> Self {
Self {
l: v,
r: v,
t: v,
b: v,
}
}
pub fn xy(x: f32, y: f32) -> Self {
Self {
l: x,
r: x,
t: y,
b: y,
}
}
pub fn x(&self) -> f32 {
self.l + self.r
}
pub fn y(&self) -> f32 {
self.t + self.b
}
}
#[inline]
pub(crate) fn finite_or(v: f32, none: f32) -> f32 {
if v.is_finite() { v } else { none }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_whole_pixel_shift_moves_a_snapped_coordinate_by_exactly_that() {
for v in [0.0f32, 0.25, 0.5, 10.5, -0.5, -26.5, 31.5, 7.3, -118.5] {
for k in [-200.0f32, -1.0, 0.0, 1.0, 3.0, 141.0] {
assert_eq!(
snap_px(v + k),
snap_px(v) + k,
"snap_px({v}) shifted by {k}"
);
}
}
}
#[test]
fn a_snapped_displacement_is_whole_physical_pixels() {
for scale in [1.0f32, 1.25, 1.5, 2.0, 3.0] {
for d in [0.0f32, 0.1, -0.4, 12.34, -99.9] {
let s = Vec2::new(d, -d).snapped(scale);
for v in [s.x, s.y] {
let px = v * scale;
assert!((px - px.round()).abs() < 1e-3, "{v} at {scale} is {px} px");
}
}
}
assert_eq!(Vec2::new(1.5, 2.5).snapped(0.0), Vec2::new(1.5, 2.5));
}
}