use super::*;
const MODEL: &str = "examples/assets/hello.glb";
const CHECKER: [[u8; 3]; 2] = [[74, 97, 122], [232, 227, 209]];
const TONE: &str = "tests/assets/sweep.ogg";
meshes! { enum AsAuthoredSet { AsAuthored } }
meshes! { enum BeaconSet { Beacon } }
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum Face {
Panels,
Caps,
}
impl Part for Face {
fn from_name(name: &str) -> Option<Self> {
match name {
"Beacon Panels" => Some(Self::Panels),
"Beacon Caps" => Some(Self::Caps),
_ => None,
}
}
fn all() -> Vec<Self> {
vec![Self::Panels, Self::Caps]
}
fn index(&self) -> u32 {
match self {
Self::Panels => 0,
Self::Caps => 1,
}
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct Beacon;
impl Catalog for Beacon {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh<Face> for Beacon {
fn build(&self, assets: &Assets) -> MeshData<Face> {
assets.mesh("beacon")
}
}
impl Game for Beacon {
type Meshes = BeaconSet;
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::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(
Self.at(Vec3::ZERO)
.material_of(Face::Panels, Material::color(Color::WHITE)),
);
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
struct AsAuthored;
impl Catalog for AsAuthored {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Mesh for AsAuthored {
fn build(&self, assets: &Assets) -> MeshData {
assets.mesh("beacon")
}
}
impl Game for AsAuthored {
type Meshes = AsAuthoredSet;
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::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(Self.at(Vec3::ZERO));
}
}
struct EveryRed;
impl Game for EveryRed {
type Meshes = AsAuthoredSet;
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::Z, Vec3::ZERO),
Projection::orthographic(1.0),
));
ctx.draw(
AsAuthored
.at(Vec3::ZERO)
.material(Material::color(Color::rgb(1.0, 0.0, 0.0))),
);
}
}
fn cell(pixels: &[u8], column: u32) -> [u8; 3] {
let at = ((SIDE / 8 * SIDE + SIDE / 4 * column + SIDE / 8) * 4) as usize;
[pixels[at], pixels[at + 1], pixels[at + 2]]
}
fn model() -> Config {
raw("headless model").with_assets([MODEL])
}
#[test]
fn a_loaded_model_is_drawn_through_the_texture_it_came_with() {
let Some(pixels) = rendered(model(), Beacon) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert_eq!(
[cell(&pixels, 0), cell(&pixels, 1)],
CHECKER,
"neighbouring cells alternate"
);
assert_eq!(cell(&pixels, 2), CHECKER[0], "and the pattern repeats");
}
#[test]
fn an_anonymous_slot_still_draws_with_what_the_file_gave_it() {
let Some(pixels) = rendered(model(), AsAuthored) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
assert_ne!(cell(&pixels, 0), [0; 3], "a part no draw can name is drawn");
assert_ne!(
cell(&pixels, 0),
cell(&pixels, 1),
"through the texture the file gave it, which no draw asked for"
);
}
#[test]
fn a_draw_repaints_every_slot_of_a_mesh_with_no_parts_through_its_textures() {
let Some(pixels) = rendered(model(), EveryRed) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
let [first, second] = [cell(&pixels, 0), cell(&pixels, 1)];
assert_ne!(first, second, "the checker still shows through");
assert!(
first[1] == 0 && first[2] == 0 && second[1] == 0 && second[2] == 0,
"and every cell of it is red: {first:?} {second:?}"
);
assert!(
first[0] > 0 || second[0] > 0,
"with red where the texture is lit"
);
}
#[test]
fn a_catalog_naming_an_asset_no_source_loaded_stops_startup() {
let config = Config::new("headless assets");
let Err(error) = Session::new(config, UVec2::splat(SIDE), |_ctx| Ok(Beacon)) else {
panic!("a mesh naming an asset nothing loaded cannot start a game");
};
if error.to_string().contains("graphics adapter") {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
}
assert_eq!(
error.to_string(),
"the game's assets did not resolve: no asset is named `beacon`",
"the missing model alone: its parts are checked against a mesh that loaded"
);
}
#[derive(Clone, Eq, Hash, PartialEq)]
struct Sweep;
impl Catalog for Sweep {
fn catalog() -> Vec<Self> {
vec![Self]
}
}
impl Sounds for Sweep {
fn build(&self, assets: &Assets) -> SoundData {
assets.sound("sweep").streamed()
}
}
struct Player;
impl Game for Player {
type Meshes = CubeSet;
type Sounds = Sweep;
type InputActions = NoInputActions;
type Skyboxes = NoSkyboxes;
type SurfaceStyles = ();
type PostEffects = ();
fn tick(&mut self, ctx: &mut TickContext<'_, Self>) {
ctx.play(Sweep);
}
fn frame(&mut self, ctx: &mut FrameContext<'_, Self>) {
ctx.set_volume(0.8);
ctx.set_listener(View::look_at(Vec3::Z, Vec3::ZERO));
ctx.play(Sweep.at(Vec3::X).pitch(1.5));
ctx.sustain(Sweep.gain(0.4).loop_from(Duration::from_secs(1)));
}
}
#[test]
fn a_game_plays_through_a_session_that_has_nothing_to_play_to() {
let config = Config::new("headless sound").with_assets([TONE]);
let Ok(mut session) = Session::new(config, UVec2::splat(SIDE), |_ctx| Ok(Player)) else {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
};
session.tick();
session.step();
session.step();
}
#[test]
fn a_catalog_naming_a_sound_no_source_loaded_stops_startup() {
let config = Config::new("headless sound");
let Err(error) = Session::new(config, UVec2::splat(SIDE), |_ctx| Ok(Player)) else {
panic!("a game naming a sound nothing loaded cannot start");
};
if error.to_string().contains("graphics adapter") {
eprintln!("skipped: this machine has no usable graphics adapter");
return;
}
assert_eq!(
error.to_string(),
"the game's assets did not resolve: no asset is named `sweep`"
);
}