use crate::Rect;
use bytemuck::{Pod, Zeroable};
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct InstanceTransform {
pub mat: [f32; 6],
}
impl InstanceTransform {
pub fn identity() -> Self {
Self {
mat: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
}
}
pub fn translation(x: f32, y: f32) -> Self {
Self {
mat: [1.0, 0.0, 0.0, 1.0, x, y],
}
}
pub fn scale(s: f32) -> Self {
Self {
mat: [s, 0.0, 0.0, s, 0.0, 0.0],
}
}
pub fn rotation(angle: f32) -> Self {
let c = angle.cos();
let s = angle.sin();
Self {
mat: [c, s, -s, c, 0.0, 0.0],
}
}
pub fn translate_scale(x: f32, y: f32, s: f32) -> Self {
Self {
mat: [s, 0.0, 0.0, s, x, y],
}
}
pub fn trs(x: f32, y: f32, angle: f32, s: f32) -> Self {
let c = angle.cos() * s;
let sn = angle.sin() * s;
Self {
mat: [c, sn, -sn, c, x, y],
}
}
}
impl Default for InstanceTransform {
fn default() -> Self {
Self::identity()
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct InstanceColor {
pub rgba: [f32; 4],
}
impl InstanceColor {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
Self { rgba: [r, g, b, a] }
}
}
impl From<[f32; 4]> for InstanceColor {
fn from(rgba: [f32; 4]) -> Self {
Self { rgba }
}
}
#[derive(Debug)]
pub struct DrawBatch {
pub transforms: Vec<InstanceTransform>,
pub colors: Vec<InstanceColor>,
pub vertices: Vec<[f32; 2]>,
pub indices: Vec<u32>,
pub scissor: Option<Rect>,
pub z_index: f32,
}
impl DrawBatch {
pub fn new() -> Self {
Self {
transforms: Vec::new(),
colors: Vec::new(),
vertices: Vec::new(),
indices: Vec::new(),
scissor: None,
z_index: 0.0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
transforms: Vec::with_capacity(capacity),
colors: Vec::with_capacity(capacity),
vertices: Vec::new(),
indices: Vec::new(),
scissor: None,
z_index: 0.0,
}
}
pub fn with_scissor(mut self, rect: Rect) -> Self {
self.scissor = Some(rect);
self
}
pub fn with_z_index(mut self, z: f32) -> Self {
self.z_index = z;
self
}
pub fn push_instance(&mut self, transform: InstanceTransform, color: Option<InstanceColor>) {
self.transforms.push(transform);
if let Some(c) = color {
self.colors.push(c);
}
}
pub fn set_vertices(&mut self, vertices: &[[f32; 2]], indices: &[u32]) {
self.vertices = vertices.to_vec();
self.indices = indices.to_vec();
}
pub fn instance_count(&self) -> usize {
self.transforms.len()
}
pub fn is_empty(&self) -> bool {
self.transforms.is_empty()
}
pub fn clear(&mut self) {
self.transforms.clear();
self.colors.clear();
}
pub fn validate(&self) -> Result<(), &'static str> {
if !self.colors.is_empty() && self.colors.len() != self.transforms.len() {
return Err("colors length must match transforms length");
}
if self.vertices.is_empty() {
return Err("vertices must not be empty");
}
if self.indices.is_empty() {
return Err("indices must not be empty");
}
Ok(())
}
}
impl Default for DrawBatch {
fn default() -> Self {
Self::new()
}
}
pub fn unit_quad() -> (Vec<[f32; 2]>, Vec<u32>) {
(
vec![[-0.5, -0.5], [0.5, -0.5], [0.5, 0.5], [-0.5, 0.5]],
vec![0, 1, 2, 0, 2, 3],
)
}
pub fn unit_circle(segments: usize) -> (Vec<[f32; 2]>, Vec<u32>) {
let n = segments.max(8);
let mut vertices = Vec::with_capacity(n);
let mut indices = Vec::with_capacity(n * 3);
for i in 0..n {
let angle = 2.0 * std::f32::consts::PI * (i as f32) / (n as f32);
vertices.push([angle.cos(), angle.sin()]);
}
for i in 1..n - 1 {
indices.push(0);
indices.push(i as u32);
indices.push((i + 1) as u32);
}
(vertices, indices)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn instance_transform_identity() {
let t = InstanceTransform::identity();
assert_eq!(t.mat[0], 1.0);
assert_eq!(t.mat[3], 1.0);
}
#[test]
fn instance_transform_translation() {
let t = InstanceTransform::translation(10.0, 20.0);
assert_eq!(t.mat[4], 10.0);
assert_eq!(t.mat[5], 20.0);
}
#[test]
fn instance_transform_trs() {
let t = InstanceTransform::trs(5.0, 10.0, std::f32::consts::PI / 2.0, 2.0);
assert!((t.mat[0]).abs() < 0.001);
assert!((t.mat[1] - 2.0).abs() < 0.001);
assert!((t.mat[2] + 2.0).abs() < 0.001);
assert!((t.mat[3]).abs() < 0.001);
assert_eq!(t.mat[4], 5.0);
assert_eq!(t.mat[5], 10.0);
}
#[test]
fn draw_batch_push_and_clear() {
let mut batch = DrawBatch::new();
assert!(batch.is_empty());
let (verts, inds) = unit_quad();
batch.set_vertices(&verts, &inds);
batch.push_instance(
InstanceTransform::translation(100.0, 200.0),
Some(InstanceColor::new(1.0, 1.0, 1.0, 1.0)),
);
batch.push_instance(
InstanceTransform::translation(300.0, 400.0),
Some(InstanceColor::new(1.0, 0.0, 0.0, 1.0)),
);
assert_eq!(batch.instance_count(), 2);
assert!(batch.validate().is_ok());
batch.clear();
assert!(batch.is_empty());
}
#[test]
fn draw_batch_validate_colors_mismatch() {
let mut batch = DrawBatch::new();
let (verts, inds) = unit_quad();
batch.set_vertices(&verts, &inds);
batch.push_instance(
InstanceTransform::identity(),
Some(InstanceColor::new(1.0, 0.0, 0.0, 1.0)),
);
batch.push_instance(InstanceTransform::identity(), None);
assert!(batch.validate().is_err());
}
#[test]
fn unit_quad_geometry() {
let (verts, inds) = unit_quad();
assert_eq!(verts.len(), 4);
assert_eq!(inds.len(), 6);
}
#[test]
fn unit_circle_geometry() {
let (verts, inds) = unit_circle(16);
assert_eq!(verts.len(), 16);
assert_eq!(inds.len(), (16 - 2) * 3);
}
}