use super::*;
const EARLIER: Color = Color::rgb(1.0, 0.0, 0.0);
const LATER: Color = Color::rgb(0.0, 0.0, 1.0);
const HEDGE_WIDE: u32 = 320;
const HEDGE_TALL: u32 = 180;
const HEDGE_AT: f32 = 10.4;
const HEDGE_WIDTH: f32 = 2.0;
const HEDGE_HEIGHT: f32 = 1.9375;
const HEDGE_STEP: f32 = 1.3866667;
const PAVED_ACROSS: f32 = 2.0;
const RESTING: f32 = 0.01;
const MARKER_ACROSS: f32 = 2.0;
const MARKER: Color = Color::rgb(0.0, 1.0, 0.0);
const PAVED_DEEP: usize = 200;
struct Hedge(Vec<(f32, f32, Material)>);
impl Game for Hedge {
type Meshes = QuadSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = ();
type PostEffects = ();
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, 9.0, 19.0), Vec3::new(0.0, 0.0, 8.0)),
Projection::perspective(45.0),
));
for &(scale, along, material) in &self.0 {
let height = HEDGE_HEIGHT * scale;
ctx.draw(
Quad.at(Transform::from_scale_rotation_translation(
Vec3::new(HEDGE_WIDTH * scale, height, 1.0),
Quat::IDENTITY,
Vec3::new(along, height * 0.5, HEDGE_AT),
))
.upright()
.material(material),
);
}
}
}
impl Hedge {
fn row(count: usize) -> Self {
Self(
(0..count)
.map(|step| {
let scale = if step % 2 == 0 { 1.0 } else { 0.8 };
let color = if step + 1 == count { LATER } else { EARLIER };
let along = -HEDGE_AT + step as f32 * HEDGE_STEP;
(scale, along, Material::color(color).cutout())
})
.collect(),
)
}
fn last_of(count: usize) -> Self {
Self(Self::row(count).0.split_off(count - 1))
}
}
fn hedged(hedge: Hedge) -> Option<Vec<u8>> {
let config = raw("headless coplanar").with_antialiasing(false);
sized(config, UVec2::new(HEDGE_WIDE, HEDGE_TALL), hedge)
}
fn bluer(pixels: &[u8], background: [u8; 4]) -> Vec<usize> {
(0..pixels.len())
.step_by(4)
.filter(|&at| {
let texel = [pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]];
texel != background && pixels[at + 2] > pixels[at]
})
.collect()
}
#[test]
fn coplanar_cutout_draws_resolve_in_the_order_they_were_submitted() {
const ROW: usize = 13;
let Some(alone) = hedged(Hedge::last_of(ROW)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(pixels) = hedged(Hedge::row(ROW)) else {
return;
};
let Some(again) = hedged(Hedge::row(ROW)) else {
return;
};
let background = [alone[0], alone[1], alone[2], alone[3]];
let reached = bluer(&alone, background);
assert!(!reached.is_empty(), "the last square is drawn");
let lost = reached
.iter()
.filter(|at| pixels[**at + 2] <= pixels[**at])
.count();
assert!(
(0..pixels.len())
.step_by(4)
.any(|at| pixels[at] > pixels[at + 2]),
"and the squares before it are drawn where it leaves room"
);
assert_eq!(
lost,
0,
"the last draw takes every pixel it reaches, texel by texel to \
none of the ones before it: {lost} of {} went to them",
reached.len()
);
assert_eq!(pixels, again, "and takes them again, frame after frame");
}
#[test]
fn a_coplanar_draw_that_writes_no_depth_still_draws_over_the_ones_before_it() {
let beside = (1.0, -4.0, Material::color(EARLIER).cutout());
let post = (1.0, 0.0, Material::color(EARLIER).cutout());
let flame = (0.5, 0.0, Material::color(LATER).additive());
let Some(alone) = hedged(Hedge(vec![beside, post])) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(pixels) = hedged(Hedge(vec![beside, post, flame])) else {
return;
};
let over = (0..alone.len())
.step_by(4)
.filter(|at| alone[*at] == u8::MAX)
.any(|at| pixels[at + 2] > alone[at + 2]);
assert!(
over,
"an additive draw tests a depth it never writes, so only the depth \
of the plane it lies in clears what the post wrote"
);
}
struct Paving {
squares: Vec<(f32, Vec3, Material)>,
camera: Camera,
sun: Option<Light>,
marker: bool,
}
impl Game for Paving {
type Meshes = GroundSet;
type Sounds = NoSounds;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = ();
type PostEffects = ();
fn tick(&mut self, _ctx: &mut TickContext<'_, Self>) {}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_camera(self.camera);
if let Some(sun) = self.sun {
ctx.light(sun);
}
if self.marker {
ctx.draw(
Ground::Blocker
.at(Transform::from_scale_rotation_translation(
Vec3::new(MARKER_ACROSS, RESTING, MARKER_ACROSS),
Quat::IDENTITY,
Vec3::Y * RESTING * 0.5,
))
.material(Material::color(MARKER)),
);
}
for &(across, at, material) in &self.squares {
ctx.draw(
Ground::Floor
.at(Transform::from_scale_rotation_translation(
Vec3::splat(across),
Quat::IDENTITY,
at,
))
.material(material),
);
}
}
}
impl Paving {
fn tilted(squares: Vec<(f32, Vec3, Material)>) -> Self {
Self {
squares,
camera: Camera::new(
View::look_at(Vec3::new(0.0, 6.0, 9.0), Vec3::ZERO),
Projection::perspective(45.0),
),
sun: None,
marker: false,
}
}
fn resting(squares: Vec<(f32, Vec3, Material)>) -> Self {
Self {
marker: true,
..Self::tilted(squares)
}
}
fn shone(squares: Vec<(f32, Vec3, Material)>) -> Self {
Self {
squares,
camera: Camera::new(
View::look_at(Vec3::Y * 10.0, Vec3::ZERO).with_up(Vec3::NEG_Z),
Projection::orthographic(PAVED_ACROSS),
),
sun: Some(Light::directional(Vec3::new(1.0, -1.0, 0.0), Color::WHITE).shadow()),
marker: false,
}
}
}
fn paved(paving: Paving) -> Option<Vec<u8>> {
let config = raw("headless flat")
.with_antialiasing(false)
.with_shadow_resolution(512);
sized(config, UVec2::new(HEDGE_WIDE, HEDGE_TALL), paving)
}
#[test]
fn draws_lying_in_one_world_plane_resolve_in_the_order_they_were_submitted() {
let ground = (6.0, Vec3::ZERO, Material::color(EARLIER));
let tile = (2.0, Vec3::new(0.6, 0.0, 0.9), Material::color(LATER));
let Some(alone) = paved(Paving::tilted(vec![tile])) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(pixels) = paved(Paving::tilted(vec![ground, tile])) else {
return;
};
let background = [alone[0], alone[1], alone[2], alone[3]];
let reached = bluer(&alone, background);
assert!(!reached.is_empty(), "the later square is drawn");
let lost = reached
.iter()
.filter(|at| pixels[**at + 2] <= pixels[**at])
.count();
assert!(
(0..pixels.len())
.step_by(4)
.any(|at| pixels[at] > pixels[at + 2]),
"and the square before it is drawn where it leaves room"
);
assert_eq!(
lost,
0,
"the later draw takes every pixel it reaches, texel by texel to \
none of the ones before it: {lost} of {} went to them",
reached.len()
);
}
fn greener(pixels: &[u8]) -> Vec<usize> {
(0..pixels.len())
.step_by(4)
.filter(|at| pixels[*at + 1] > pixels[*at])
.collect()
}
#[test]
fn a_marker_resting_on_a_plane_stays_in_front_of_every_draw_lying_in_it() {
let squares = (0..PAVED_DEEP)
.map(|step| {
let last = step + 1 == PAVED_DEEP;
let across = if last { 8.0 } else { [6.0, 4.0, 2.0][step % 3] };
let color = if last { LATER } else { EARLIER };
(across, Vec3::ZERO, Material::color(color))
})
.collect::<Vec<_>>();
let widest = vec![squares[PAVED_DEEP - 1]];
let Some(alone) = paved(Paving::tilted(widest)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(standing) = paved(Paving::resting(Vec::new())) else {
return;
};
let Some(pixels) = paved(Paving::resting(squares)) else {
return;
};
let background = [standing[0], standing[1], standing[2], standing[3]];
let marked = greener(&standing);
let reached = bluer(&alone, background);
assert!(!marked.is_empty() && !reached.is_empty(), "both are drawn");
let swallowed = marked
.iter()
.filter(|at| pixels[**at + 1] <= pixels[**at])
.count();
let covered = |at: usize| standing[at + 1] > standing[at];
let lost = reached
.iter()
.filter(|at| !covered(**at) && pixels[**at + 2] <= pixels[**at])
.count();
assert_eq!(
swallowed,
0,
"a marker resting {RESTING} meters off the plane is drawn over \
every one of {PAVED_DEEP} draws of it: {swallowed} of {} pixels \
went to them",
marked.len()
);
assert_eq!(
lost,
0,
"and the last of those draws takes every pixel the marker leaves: \
{lost} of {} went to the draws before it",
reached.len()
);
}
#[test]
fn a_square_lying_in_the_ground_plane_casts_nothing_past_its_own_edges() {
let ground = (PAVED_ACROSS, Vec3::ZERO, Material::lit(Color::WHITE));
let lying = (0.5, Vec3::ZERO, Material::lit(EARLIER));
let Some(clear) = paved(Paving::shone(vec![ground])) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(pixels) = paved(Paving::shone(vec![ground, lying])) else {
return;
};
let Some(alone) = paved(Paving::shone(vec![lying])) else {
return;
};
let changed = |at: usize| pixels[at..at + 4] != clear[at..at + 4];
let covered = |at: usize| alone[at..at + 3] != [0, 0, 0];
let over = (0..clear.len())
.step_by(4)
.filter(|&at| changed(at))
.count();
let past = (0..clear.len())
.step_by(4)
.filter(|&at| changed(at) && !covered(at))
.count();
assert!(over > 0, "the square draws over the ground it lies in");
assert_eq!(
past, 0,
"and the ground beside it reads the light it read with no square \
there: {past} pixels of {over} changed past the square's own"
);
}