use super::*;
const CELLS: [[u8; 4]; 4] = [
[u8::MAX, 0, 0, u8::MAX],
[0, u8::MAX, 0, u8::MAX],
[0, 0, u8::MAX, u8::MAX],
[u8::MAX, u8::MAX, 0, u8::MAX],
];
const COVERAGE: [u8; 4] = [0, 64, 128, u8::MAX];
const TILES: f32 = 2.0;
meshes! { enum CellsSet { Cells } }
meshes! { enum CoveringSet { Covering } }
meshes! { enum TexelsSet { Texels } }
meshes! { enum TiledSet { Tiled } }
#[derive(Clone, Copy)]
enum Turned {
AsPlaced,
Billboard,
Upright,
}
struct Square {
eye: Vec3,
up: Vec3,
turned: Turned,
}
impl Square {
fn seen_from(eye: Vec3, turned: Turned) -> Self {
Self {
eye,
up: Vec3::Y,
turned,
}
}
fn overhead(turned: Turned) -> Self {
Self {
eye: Vec3::Y * 3.0,
up: Vec3::NEG_Z,
turned,
}
}
}
impl Game for Square {
type Meshes = QuadSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(self.eye, Vec3::ZERO).with_up(self.up),
Projection::orthographic(2.0),
));
let square = Quad.at(Vec3::ZERO).material(Material::color(Color::WHITE));
ctx.draw(match self.turned {
Turned::AsPlaced => square,
Turned::Billboard => square.billboard(),
Turned::Upright => square.upright(),
});
}
}
#[test]
fn a_billboarded_draw_faces_the_camera_from_wherever_it_is_seen() {
let seen = |eye, turned| rendered(raw("headless billboard"), Square::seen_from(eye, turned));
let Some(ahead) = seen(Vec3::Z * 3.0, Turned::Billboard) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(from_a_corner) = seen(Vec3::new(3.0, 2.0, -1.0), Turned::Billboard) else {
return;
};
let Some(edge_on) = seen(Vec3::X * 3.0, Turned::AsPlaced) else {
return;
};
assert_eq!(
middle(&ahead, SIDE),
[u8::MAX; 4],
"a square facing the camera is drawn"
);
assert_eq!(
middle(&from_a_corner, SIDE),
[u8::MAX; 4],
"and from anywhere else too"
);
assert_eq!(
middle(&edge_on, SIDE),
pixel(&edge_on, 0, 0, SIDE),
"where a still one is edge-on and gone, reading as the untouched \
corner does"
);
}
#[test]
fn an_upright_draw_stands_while_a_billboarded_one_lies_toward_the_camera() {
let Some(standing) = rendered(raw("headless billboard"), Square::overhead(Turned::Upright))
else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(lying) = rendered(
raw("headless billboard"),
Square::overhead(Turned::Billboard),
) else {
return;
};
assert_eq!(
middle(&standing, SIDE),
pixel(&standing, 0, 0, SIDE),
"an upright square is edge-on from above, reading as the untouched corner does"
);
assert_eq!(
middle(&lying, SIDE),
[u8::MAX; 4],
"where a billboarded one turns its face up"
);
}
fn cells() -> TextureData {
let pixels = (0..4)
.flat_map(|down| (0..4).flat_map(move |across| CELLS[down / 2 * 2 + across / 2]))
.collect();
TextureData::rgba8(UVec2::splat(4), pixels)
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct Cells;
impl Catalog for Cells {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh for Cells {
fn build(&self, assets: &Assets) -> MeshData {
Quad.build(assets).with_texture(cells())
}
}
struct Sampled(Frame);
impl Game for Sampled {
type Meshes = CellsSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(Vec3::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(
Cells
.at(Vec3::ZERO)
.frame(self.0)
.material(Material::color(Color::WHITE)),
);
}
}
#[test]
fn a_draw_that_asks_for_one_cell_of_a_sheet_samples_that_cell_alone() {
let sheet = Sheet::new(UVec2::new(2, 2));
let Some(first) = center(Sampled(sheet.cell(0))) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert_eq!(first, CELLS[0], "the first cell is the top left quarter");
for (index, color) in CELLS.iter().enumerate().skip(1) {
let Some(cell) = center(Sampled(sheet.cell(index as u32))) else {
return;
};
assert_eq!(cell, *color, "cell {index} samples its own quarter");
}
}
fn inside(pixels: &[u8]) -> impl Iterator<Item = [u8; 4]> {
(1..SIDE - 1)
.flat_map(move |down| (1..SIDE - 1).map(move |across| pixel(pixels, across, down, SIDE)))
}
#[test]
fn a_windowed_draw_is_its_own_cell_to_the_edge_and_no_texel_beside_it() {
let sheet = Sheet::new(UVec2::new(2, 2));
for (index, color) in CELLS.iter().enumerate() {
let square = Sampled(sheet.cell(index as u32));
let Some(pixels) = rendered(raw("headless windowed"), square) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert!(
inside(&pixels).all(|pixel| pixel == *color),
"cell {index} is drawn whole, with none of the cells around it"
);
}
}
#[test]
fn a_mirrored_window_swaps_the_cells_it_reads_and_holds_its_own_edges() {
let halves = |min, max| {
let pixels = rendered(raw("headless mirrored"), Sampled(Frame::rect(min, max)))?;
Some((column(&pixels, SIDE / 4), column(&pixels, SIDE * 3 / 4)))
};
let Some((left, right)) = halves(Vec2::ZERO, Vec2::new(1.0, 0.5)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(mirrored) = halves(Vec2::new(1.0, 0.0), Vec2::new(0.0, 0.5)) else {
return;
};
let Some(cell) = rendered(
raw("headless mirrored cell"),
Sampled(Frame::rect(Vec2::new(0.5, 0.0), Vec2::new(0.0, 0.5))),
) else {
return;
};
assert_eq!([left, right], [CELLS[0], CELLS[1]], "the top row of cells");
assert_eq!(
mirrored,
(right, left),
"which a window running the other way round reads mirrored"
);
assert!(
inside(&cell).all(|pixel| pixel == CELLS[0]),
"and a mirrored cell holds its edges as a plain one does"
);
}
#[test]
fn a_window_narrower_than_a_texel_reads_from_its_own_middle() {
let pinched = Frame::rect(Vec2::splat(0.3), Vec2::splat(0.45));
let Some(pixels) = rendered(raw("headless pinched"), Sampled(pinched)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert!(
inside(&pixels).all(|pixel| pixel == CELLS[0]),
"the texel the window sits in, not the one half a texel past it"
);
}
fn along_the_top(pixels: &[u8], quarter: u32) -> [u8; 4] {
pixel(pixels, SIDE / 8 + quarter * SIDE / 4, SIDE / 8, SIDE)
}
#[test]
fn a_window_past_the_texture_repeats_it_across_the_draw() {
let twice = Frame::rect(Vec2::ZERO, Vec2::splat(2.0));
let Some(pixels) = rendered(raw("headless repeated window"), Sampled(twice)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert_eq!(
[0, 1, 2, 3].map(|quarter| along_the_top(&pixels, quarter)),
[CELLS[0], CELLS[1], CELLS[0], CELLS[1]],
"the window's two copies of the sheet, across their top cells"
);
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct Covering;
impl Catalog for Covering {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh for Covering {
fn build(&self, assets: &Assets) -> MeshData {
let pixels = COVERAGE
.iter()
.flat_map(|&alpha| [u8::MAX, u8::MAX, u8::MAX, alpha])
.collect();
Quad.build(assets).with_texture(
TextureData::rgba8(UVec2::new(COVERAGE.len() as u32, 1), pixels).pixelated(),
)
}
}
struct Adding(Material);
impl Game for Adding {
type Meshes = CoveringSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(Vec3::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(Covering.at(Vec3::ZERO).material(self.0));
}
}
#[test]
fn an_additive_draw_adds_by_its_texture_alpha_and_drops_none_of_it() {
let read = |material| rendered(raw("headless added texture"), Adding(material));
let alpha = |texel: usize| f32::from(COVERAGE[texel]) / f32::from(u8::MAX);
let texel = |pixels: &[u8], at: u32| column(pixels, SIDE / 8 + at * SIDE / 4);
let white = Material::color(Color::WHITE).additive();
let Some(added) = read(white) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(bare) = read(Material::color(Color::WHITE.with_alpha(0.0)).additive()) else {
return;
};
let Some(halved) = read(Material::color(Color::WHITE.with_alpha(alpha(2))).additive()) else {
return;
};
let Some(cut) = read(white.cutout()) else {
return;
};
assert_eq!(
texel(&added, 0),
texel(&bare, 0),
"an empty texel adds nothing"
);
assert_ne!(
texel(&added, 3),
texel(&bare, 3),
"where a whole one adds what it holds"
);
assert_eq!(
texel(&added, 2),
texel(&halved, 3),
"and half of it either way round, texture alpha or tint"
);
assert_eq!(cut, added, "a cutout drops no texel of what is added");
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct Tiled;
impl Catalog for Tiled {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh for Tiled {
fn build(&self, _assets: &Assets) -> MeshData {
let corners = vec![
Vertex::new(Vec3::new(-0.5, -0.5, 0.0), Vec3::Z, Vec2::new(0.0, TILES)),
Vertex::new(Vec3::new(0.5, -0.5, 0.0), Vec3::Z, Vec2::new(TILES, TILES)),
Vertex::new(Vec3::new(0.5, 0.5, 0.0), Vec3::Z, Vec2::new(TILES, 0.0)),
Vertex::new(Vec3::new(-0.5, 0.5, 0.0), Vec3::Z, Vec2::new(0.0, 0.0)),
];
MeshData::new(corners, vec![0, 1, 2, 0, 2, 3]).with_texture(cells())
}
}
struct Repeating(Frame);
impl Game for Repeating {
type Meshes = TiledSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(Vec3::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(
Tiled
.at(Vec3::ZERO)
.frame(self.0)
.material(Material::color(Color::WHITE)),
);
}
}
#[test]
fn a_draw_of_the_whole_texture_repeats_it_wherever_its_corners_reach() {
let square = Repeating(Frame::default());
let Some(pixels) = rendered(raw("headless repeat"), square) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert_eq!(
[0, 1, 2, 3].map(|quarter| along_the_top(&pixels, quarter)),
[CELLS[0], CELLS[1], CELLS[0], CELLS[1]],
"the texture's top two cells run twice across the square"
);
}
#[test]
fn a_windowed_draw_holds_a_uv_that_reaches_past_it_to_its_own_texels() {
let square = Repeating(Sheet::new(UVec2::new(2, 2)).cell(3));
let Some(pixels) = rendered(raw("headless windowed repeat"), square) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert!(
inside(&pixels).all(|pixel| pixel == CELLS[3]),
"the cell the window names, and nothing the uv ran on into"
);
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
enum Texels {
Blended,
Pixelated,
}
impl Catalog for Texels {
fn catalog() -> Vec<Self> {
vec![Self::Blended, Self::Pixelated]
}
}
impl Mesh for Texels {
fn build(&self, assets: &Assets) -> MeshData {
let texels = TextureData::rgba8(UVec2::splat(2), CELLS.concat());
Quad.build(assets).with_texture(match self {
Self::Blended => texels,
Self::Pixelated => texels.pixelated(),
})
}
}
struct Magnified(Texels);
impl Game for Magnified {
type Meshes = TexelsSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(Vec3::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(
self.0
.at(Vec3::ZERO)
.material(Material::color(Color::WHITE)),
);
}
}
#[test]
fn a_pixelated_texture_reads_back_one_texel_where_a_blended_one_mixes_four() {
let Some(nearest) = center(Magnified(Texels::Pixelated)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(blended) = center(Magnified(Texels::Blended)) else {
return;
};
assert_eq!(
nearest, CELLS[3],
"the texel the middle of the target is in"
);
assert!(
!CELLS.contains(&blended),
"where blending reads all four at once: {blended:?}"
);
}
struct Holes;
impl Game for Holes {
type Meshes = CutSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(Vec3::Z * 2.0, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(
Cut::Holed
.at(Vec3::ZERO)
.material(Material::color(Color::WHITE).cutout()),
);
ctx.draw(
Cut::Solid
.at(Vec3::NEG_Z * 0.5)
.material(Material::color(Color::rgb(1.0, 0.0, 0.0)).cutout()),
);
}
}
#[test]
fn a_cutout_draw_leaves_a_hole_to_see_through_and_depth_where_it_drew() {
let Some(pixels) = rendered(raw("headless cutout"), Holes) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let across = |x: u32| column(&pixels, x);
assert_eq!(across(SIDE / 4), BEHIND, "the square behind shows through");
assert_eq!(
across(SIDE * 3 / 4),
IN_FRONT,
"and is held out of the rest by the depth the cutout wrote"
);
}
#[test]
fn a_cutout_draw_that_fades_under_the_alpha_it_is_drawn_from_is_dropped() {
let read = |panes| {
Some(middle(
&rendered(raw("headless faded cutout"), panes)?,
SIDE,
))
};
let square = |alpha, cutting| {
let material = Material::color(Color::rgba(1.0, 1.0, 1.0, alpha));
Panes::new(vec![(
0.0,
if cutting { material.cutout() } else { material },
)])
};
let Some(cleared) = read(Panes::new(Vec::new())) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(blended) = read(square(0.4, false)) else {
return;
};
let Some(dropped) = read(square(0.4, true)) else {
return;
};
let Some(kept) = read(square(0.6, true)) else {
return;
};
assert_ne!(blended, cleared, "a fading square is blended over the rest");
assert_eq!(dropped, cleared, "and a cutout one fading that far is not");
assert_ne!(kept, cleared, "while one still over the alpha is");
}
struct Placed {
at: Vec3,
scale: f32,
projection: Projection,
}
impl Game for Placed {
type Meshes = TexelsSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = NoSurfaceStyles;
type PostEffects = NoPostEffects;
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(Camera::new(
View::look_at(Vec3::new(0.0, 6.0, 4.0), Vec3::ZERO),
self.projection,
));
ctx.light(Light::point(Vec3::new(0.0, 5.0, 0.0), Color::WHITE, 50.0));
ctx.draw(
Texels::Pixelated
.at(Transform::from_scale_rotation_translation(
Vec3::splat(self.scale),
Quat::IDENTITY,
self.at,
))
.billboard()
.material(Material::lit(Color::WHITE).cutout()),
);
}
}
#[test]
fn a_draw_the_view_cannot_place_draws_nothing_on_any_adapter() {
let lens = || Projection::perspective(50.0).clip(4.0..400.0);
let read = |at, scale| {
let mut session = started(raw("headless unplaceable"), UVec2::splat(SIDE), |_| {
Ok(Placed {
at,
scale,
projection: lens(),
})
})?;
let stats = session.step();
Some((
stats.instances(),
session.pixels().expect("the target reads back"),
))
};
let Some((_, cleared)) = read(Vec3::new(1000.0, 0.0, 0.0), 1.0) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some((drawn, shown)) = read(Vec3::ZERO, 1.0) else {
return;
};
assert_ne!(shown, cleared, "a placed square is seen");
assert_eq!(drawn, 1, "and is the frame's one instance");
let mut wrong = Vec::new();
for (name, at) in [
("a NaN position", Vec3::NAN),
("an infinite position", Vec3::splat(f32::INFINITY)),
("a position behind the eye", Vec3::new(0.0, 12.0, 8.0)),
(
"a position nearer than the near clip",
Vec3::new(0.0, 5.0, 3.3),
),
] {
let Some((instances, pixels)) = read(at, 1.0) else {
return;
};
if pixels != cleared {
wrong.push(format!("{name} draws something"));
}
if instances != 0 {
wrong.push(format!("{name} records {instances} instance(s)"));
}
}
assert!(wrong.is_empty(), "{}", wrong.join("; "));
}