use super::*;
use crate::mesh::{Clip, NoClips, NoParts, Posing};
const WALKER: &str = "tests/assets/a_rig.glb";
const LEANING: f32 = 0.458_333_34;
const TALL: u32 = 64;
const ASIDE: Camera = Camera::new(
View::look_at(Vec3::new(8.0, 1.5, 0.0), Vec3::new(0.0, 1.5, 0.0)),
Projection::orthographic(4.0),
);
meshes! { enum WalkerSet { Walker, Bare } }
meshes! { enum StandingSet { Walker, Ground } }
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum Paces {
Idle,
Walk,
}
impl Clip for Paces {
fn from_name(name: &str) -> Option<Self> {
match name {
"idle" => Some(Self::Idle),
"walk" => Some(Self::Walk),
_ => None,
}
}
fn all() -> Vec<Self> {
vec![Self::Idle, Self::Walk]
}
fn index(&self) -> u32 {
self.clone() as u32
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct Walker;
impl Catalog for Walker {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh<NoParts, Paces> for Walker {
fn build(&self, assets: &Assets) -> MeshData<NoParts, Paces> {
assets.model("Rig")
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct Bare;
impl Catalog for Bare {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh<NoParts, NoClips> for Bare {
fn build(&self, assets: &Assets) -> MeshData {
assets.mesh("BodyMesh")
}
}
struct Wall {
at: Option<f32>,
joints: bool,
standing: Vec3,
}
impl Wall {
fn leaning() -> Self {
Self {
at: Some(LEANING),
joints: true,
standing: Vec3::ZERO,
}
}
fn at_rest() -> Self {
Self {
at: None,
joints: true,
standing: Vec3::ZERO,
}
}
}
impl Game for Wall {
type Meshes = WalkerSet;
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(ASIDE);
let white = Material::color(Color::WHITE);
if !self.joints {
ctx.draw(Bare.at(self.standing).material(white));
return;
}
let walker = Walker.at(self.standing).material(white);
ctx.draw(match self.at {
Some(at) => walker.posed_by(Posing::clip(Paces::Walk.index(), at)),
None => walker,
});
}
}
fn walled(scene: Wall) -> Option<Vec<u8>> {
sized(
raw("headless poses").with_assets([WALKER]),
UVec2::splat(TALL),
scene,
)
}
fn read(pixels: &[u8], x: u32, y: u32) -> [u8; 4] {
let at = ((y * TALL + x) * 4) as usize;
[pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]]
}
fn wall(pixels: &[u8], x: u32, y: u32) -> bool {
read(pixels, x, y) == [u8::MAX; 4]
}
#[test]
fn a_model_in_no_pose_draws_the_corners_a_mesh_with_no_joints_draws() {
let Some(posed) = walled(Wall::at_rest()) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(bare) = walled(Wall {
at: None,
joints: false,
standing: Vec3::ZERO,
}) else {
return;
};
assert!(drawn_pixels(&posed) > 0, "the wall is drawn at all");
assert_eq!(
posed, bare,
"a model at rest reads back as the same corners drawn with no joints"
);
}
#[test]
fn a_posed_draw_is_drawn_where_its_clip_holds_its_corners() {
let Some(resting) = walled(Wall::at_rest()) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(leaning) = walled(Wall::leaning()) else {
return;
};
assert_ne!(resting, leaning, "the pose reaches the corners drawn");
assert!(
wall(&resting, TALL / 2, 10) && !wall(&leaning, TALL / 2, 10),
"the top of the wall stands in the middle of the target at rest and \
the pose leans it out of there"
);
assert!(
!wall(&resting, 5, 30) && wall(&leaning, 5, 30),
"and leans it two meters across, where the rest never reaches"
);
}
struct Standing {
at: Option<f32>,
}
impl Game for Standing {
type Meshes = StandingSet;
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::Y * 12.0, Vec3::ZERO).with_up(Vec3::NEG_Z),
Projection::orthographic(ACROSS),
));
ctx.light(Light::directional(Vec3::new(1.0, -1.0, 0.0), Color::WHITE).shadow());
ctx.draw(
Ground::Floor
.at(Transform::from_scale(Vec3::splat(ACROSS)))
.material(Material::lit(Color::WHITE)),
);
let walker = Walker.at(Vec3::ZERO).material(Material::lit(Color::WHITE));
ctx.draw(match self.at {
Some(at) => walker.posed_by(Posing::clip(Paces::Walk.index(), at)),
None => walker,
});
}
}
fn sunlit(pixels: &[u8], x: u32, y: u32) -> u8 {
pixels[((y * YARD + x) * 4) as usize]
}
fn shadowed(at: Option<f32>) -> Option<Vec<u8>> {
let config = raw("headless posed shadow")
.with_assets([WALKER])
.with_shadow_resolution(512);
sized(config, UVec2::splat(YARD), Standing { at })
}
#[test]
fn the_shadow_of_a_posed_draw_follows_the_pose_it_is_drawn_in() {
let Some(resting) = shadowed(None) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(leaning) = shadowed(Some(LEANING)) else {
return;
};
let (end, leaned) = ((56, 32), (45, 45));
assert!(
sunlit(&resting, end.0, end.1) < sunlit(&leaning, end.0, end.1),
"the ground the wall shadows at rest is lit once the pose leans off \
it: {} against {}",
sunlit(&resting, end.0, end.1),
sunlit(&leaning, end.0, end.1)
);
assert!(
sunlit(&leaning, leaned.0, leaned.1) < sunlit(&resting, leaned.0, leaned.1),
"and the ground the pose leans over is shadowed where the rest left \
it lit: {} against {}",
sunlit(&leaning, leaned.0, leaned.1),
sunlit(&resting, leaned.0, leaned.1)
);
assert_eq!(
sunlit(&resting, 5, 5),
sunlit(&leaning, 5, 5),
"and the ground neither of them reaches is left as it was"
);
}
#[test]
fn a_draw_the_pose_of_a_clip_reaches_into_is_drawn_where_its_rest_is_not() {
let standing = Vec3::new(0.0, 0.0, -4.0);
let Some(resting) = walled(Wall {
at: None,
joints: true,
standing,
}) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(leaning) = walled(Wall {
at: Some(LEANING),
joints: true,
standing,
}) else {
return;
};
let Some(empty) = walled(Wall {
at: None,
joints: true,
standing: Vec3::new(0.0, 0.0, -40.0),
}) else {
return;
};
let covered = |pixels: &[u8]| {
(0..TALL)
.flat_map(|y| (0..TALL).map(move |x| (x, y)))
.filter(|&(x, y)| wall(pixels, x, y))
.count()
};
assert_eq!(
covered(&resting),
covered(&empty),
"the rest covers nothing"
);
assert_eq!(resting, empty, "so it reads back as an empty target");
assert!(
covered(&leaning) > 0,
"and the pose that leans into the view is drawn there"
);
}
#[test]
fn posed_draws_of_one_model_are_drawn_in_one_instanced_draw() {
let scene = Crowd {
count: 3,
apart: 2.0,
drift: 0.0,
};
let config = raw("headless crowd").with_assets([WALKER]);
let Ok(mut session) = Session::new(config, UVec2::splat(TALL), |_ctx| Ok(scene)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let stats = session.step();
assert_eq!(stats.instances(), 3, "one instance per draw");
assert_eq!(
stats.draw_calls(),
1,
"and one instanced draw over the three of them"
);
}
#[test]
fn a_frame_of_more_poses_than_the_palette_holds_draws_every_one_of_them() {
let drifting = Crowd {
count: 90,
apart: 0.0,
drift: 1e-7,
};
let Some(many) = crowded(drifting) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let Some(alone) = crowded(Crowd {
count: 1,
apart: 0.0,
drift: 0.0,
}) else {
return;
};
assert!(drawn_pixels(&alone) > 0, "the wall is drawn at all");
assert_eq!(
many, alone,
"the draws past what the palette held read the poses they were given"
);
}
fn crowded(scene: Crowd) -> Option<Vec<u8>> {
sized(
raw("headless crowd").with_assets([WALKER]),
UVec2::splat(TALL),
scene,
)
}
struct Crowd {
count: u32,
apart: f32,
drift: f32,
}
impl Game for Crowd {
type Meshes = WalkerSet;
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, 1.5, 16.0), Vec3::new(0.0, 1.5, 0.0)),
Projection::orthographic(12.0),
));
for at in 0..self.count {
let along = LEANING + at as f32 * self.drift;
ctx.draw(
Walker
.at(Vec3::X * (at as f32 * self.apart))
.material(Material::color(Color::WHITE))
.posed_by(Posing::clip(Paces::Walk.index(), along)),
);
}
}
}