Skip to content
Snippets Groups Projects
Verified Commit 0d45a6d1 authored by Louis's avatar Louis :fire:
Browse files

Handle tile maps from LDTK, show basic UI for towns

parent bdab637d
No related branches found
No related tags found
No related merge requests found
Pipeline #235 passed with stages
in 5 minutes and 44 seconds
Showing
with 1040 additions and 80 deletions
This diff is collapsed.
......@@ -33,6 +33,9 @@ check:
pak:
cd raw_assets && tar -cJf ../assets/resources.apack ./
msdf:
msdf-atlas-gen -font raw_assets/fonts/CompassPro.ttf -type msdf -minsize 18 -format png -imageout raw_assets/fonts/CompassPro.png -json raw_assets/fonts/CompassPro.kayak_font
build-windows: clean_dist top_tail
docker run --rm --name "${PROJECT_NAME}-build-windows" -v "$(CURRENT_DIRECTORY):/app" -w /app --user $(shell id -u):$(shell id -g) r.lcr.gr/microhacks/bevy-builder \
cargo build --release -p game_core --target x86_64-pc-windows-gnu
......
No preview for this file type
......@@ -20,11 +20,16 @@ micro_banimate.workspace = true
micro_musicbox.workspace = true
micro_asset_io = { path = "../micro_asset_io" }
num-traits = "0.2.15"
bevy_ecs_tilemap = { git = "https://github.com/StarArawn/bevy_ecs_tilemap", rev = "eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" }
bevy_tweening = "0.6.0"
toml = "0.5.9"
noise = "0.8.2"
ldtk_rust = "0.6.0"
kayak_ui.workspace = true
kayak_font.workspace = true
#remote_events = { git = "https://lab.lcr.gr/microhacks/micro-bevy-remote-events.git", rev = "be0c6b43a73e4c5e7ece20797e3d6f59340147b4"}
......
......@@ -12,9 +12,12 @@ use bevy::ecs::system::{SystemParam, SystemParamFetch, SystemState};
use bevy::math::vec2;
use bevy::prelude::*;
use bevy::render::texture::{CompressedImageFormats, ImageType};
use kayak_font::{KayakFont, Sdf};
use ldtk_rust::Project;
use micro_asset_io::{APack, APackProcessingComplete};
use serde::{Deserialize, Serialize};
use crate::assets::asset_types::ldtk_project::LdtkProject;
use crate::assets::AssetHandles;
fn file_prefix(path: &PathBuf) -> Option<String> {
......@@ -47,6 +50,21 @@ pub struct ManifestGenericAsset {
pub path: String,
}
#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
pub enum ManifestFontAsset {
Basic {
name: String,
path: String,
},
MSDF {
name: String,
ttf: String,
image: String,
msdf: String,
},
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ManifestImage {
pub name: String,
......@@ -75,7 +93,9 @@ pub struct APackManifest {
#[serde(default = "Vec::new")]
pub spritesheets: Vec<ManifestSpriteSheet>,
#[serde(default = "Vec::new")]
pub fonts: Vec<ManifestGenericAsset>,
pub fonts: Vec<ManifestFontAsset>,
#[serde(default = "Vec::new")]
pub ldtk: Vec<ManifestGenericAsset>,
}
fn in_world<Param: SystemParam + 'static>(
......@@ -157,23 +177,75 @@ pub fn handle_apack_process_events(world: &mut World) {
}
for font in &manifest.fonts {
let asset = pack
.get(&format!("./{}", &font.path))
.expect("Missing asset");
match font {
ManifestFontAsset::Basic { path, name } => {
let asset = pack.get(&format!("./{}", &path)).expect("Missing asset");
if let Ok(font_data) = Font::try_from_bytes(asset.to_vec()) {
in_world::<ParamSet<(ResMut<Assets<Font>>, ResMut<AssetHandles>)>>(
world,
move |params: &mut ParamSet<(
ResMut<Assets<Font>>,
ResMut<AssetHandles>,
)>| {
let handle = params.p0().add(font_data);
params.p1().fonts.insert(name.clone(), handle);
},
);
} else {
log::warn!("Malformed font {}", path);
}
}
ManifestFontAsset::MSDF {
name,
ttf,
msdf,
image,
} => {
let image_asset =
pack.get(&format!("./{}", &image)).expect("Missing asset");
let ttf_asset = pack.get(&format!("./{}", &ttf)).expect("Missing asset");
let msdf_asset = pack.get(&format!("./{}", &msdf)).expect("Missing asset");
if let Ok(font_data) = Font::try_from_bytes(asset.to_vec()) {
in_world::<ParamSet<(ResMut<Assets<Font>>, ResMut<AssetHandles>)>>(
world,
move |params: &mut ParamSet<(
ResMut<Assets<Font>>,
ResMut<AssetHandles>,
)>| {
let handle = params.p0().add(font_data);
params.p1().fonts.insert(font.name.clone(), handle);
},
);
} else {
log::warn!("Malformed font {}", font.path);
in_world::<
ParamSet<(
ResMut<Assets<Font>>,
ResMut<Assets<Image>>,
ResMut<Assets<KayakFont>>,
ResMut<AssetHandles>,
)>,
>(
world,
move |params: &mut ParamSet<(
ResMut<Assets<Font>>,
ResMut<Assets<Image>>,
ResMut<Assets<KayakFont>>,
ResMut<AssetHandles>,
)>| {
let ttf_handle = params
.p0()
.add(Font::try_from_bytes(ttf_asset.to_vec()).unwrap());
let image_handle = params.p1().add(
Image::from_buffer(
image_asset.as_slice(),
ImageType::Extension("png"),
CompressedImageFormats::all(),
true,
)
.unwrap(),
);
let msdf_handle = params.p2().add(KayakFont::new(
Sdf::from_bytes(msdf_asset.as_slice()),
kayak_font::ImageType::Atlas(image_handle.clone_weak()),
));
let mut assets = params.p3();
assets.kayak_fonts.insert(name.clone(), msdf_handle);
assets.images.insert(name.clone(), image_handle);
assets.fonts.insert(name.clone(), ttf_handle);
},
)
}
}
}
......@@ -221,6 +293,33 @@ pub fn handle_apack_process_events(world: &mut World) {
log::warn!("Malformed spritesheet {}", &spritesheet.path);
}
}
for ldtk_entry in &manifest.ldtk {
let asset = pack
.get(&format!("./{}", &ldtk_entry.path))
.expect("Missing asset");
let mut ldtk_file: Project = serde_json::from_slice(asset.as_slice()).unwrap();
in_world::<ParamSet<(ResMut<Assets<LdtkProject>>, ResMut<AssetHandles>)>>(
world,
move |params: &mut ParamSet<(
ResMut<Assets<LdtkProject>>,
ResMut<AssetHandles>,
)>| {
let handle = params.p0().add(LdtkProject(ldtk_file));
params.p1().ldtk.insert(ldtk_entry.name.clone(), handle);
},
);
}
}
log::info!("LOADED AN APACK");
{
let mut state: SystemState<ResMut<Assets<APack>>> = SystemState::new(world);
let mut packs = state.get_mut(world);
if let Some(mut pack) = packs.get_mut(&event.0) {
pack.loaded = true
}
}
}
}
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use anyhow::Error;
use bevy::asset::{AssetEvent, AssetLoader, Assets, BoxedFuture, LoadContext, LoadedAsset};
use bevy::prelude::{EventReader, Res, ResMut, Resource};
use bevy::reflect::TypeUuid;
use ldtk_rust::{Level, Project};
#[derive(TypeUuid)]
#[uuid = "eb97c508-61ea-11ed-abd3-db91dd0a6e8b"]
pub struct LdtkProject(pub Project);
impl Deref for LdtkProject {
type Target = Project;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for LdtkProject {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl LdtkProject {}
#[derive(Default)]
pub struct LdtkLoader;
impl AssetLoader for LdtkLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<(), Error>> {
Box::pin(async move {
let mut ldtk: Project = serde_json::from_slice(bytes)?;
if ldtk.external_levels {
ldtk.load_external_levels(load_context.path());
}
load_context.set_default_asset(LoadedAsset::new(LdtkProject(ldtk)));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&["ldtk"]
}
}
#[derive(Resource, Default)]
pub struct LevelIndex(pub HashMap<String, Level>);
impl Deref for LevelIndex {
type Target = HashMap<String, Level>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for LevelIndex {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub fn handle_ldtk_project_events(
mut events: EventReader<AssetEvent<LdtkProject>>,
assets: Res<Assets<LdtkProject>>,
mut level_index: ResMut<LevelIndex>,
) {
for event in events.iter() {
match event {
AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
if let Some(LdtkProject(project)) = assets.get(handle) {
for level in &project.levels {
level_index.insert(
level.identifier.clone(),
serde_json::from_value(serde_json::to_value(level).unwrap()).unwrap(),
);
}
}
}
_ => {}
}
}
}
pub mod ldtk_project;
......@@ -4,9 +4,11 @@ use bevy::asset::LoadState;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use bevy::reflect::TypeUuid;
use kayak_font::KayakFont;
use micro_asset_io::APack;
use micro_musicbox::prelude::AudioSource;
use crate::assets::asset_types::ldtk_project::LdtkProject;
use crate::assets::{AssetHandles, FixedAssetNameMapping, SpriteSheetConfig};
#[derive(SystemParam)]
......@@ -14,6 +16,7 @@ pub struct AssetTypeLoader<'w, 's> {
pub handles: ResMut<'w, AssetHandles>,
pub asset_server: Res<'w, AssetServer>,
pub atlas: ResMut<'w, Assets<TextureAtlas>>,
pub apack: ResMut<'w, Assets<APack>>,
#[system_param(ignore)]
marker: PhantomData<&'s usize>,
}
......@@ -34,7 +37,7 @@ macro_rules! load_state {
($container: expr => $key: ident) => {
$container
.asset_server
.get_group_load_state($container.handles.$key.values().map(|f| f.id))
.get_group_load_state($container.handles.$key.values().map(|f| f.id()))
};
}
......@@ -57,6 +60,8 @@ impl<'w, 's> AssetTypeLoader<'w, 's> {
load_basic_type!(load_audio, AudioSource => sounds);
load_basic_type!(load_font, Font => fonts);
load_basic_type!(load_apack, APack => apacks);
load_basic_type!(load_ldtk, LdtkProject => ldtk);
load_basic_type!(load_kayak_font, KayakFont => kayak_fonts);
pub fn load_spritesheet(
&mut self,
......@@ -97,4 +102,14 @@ impl<'w, 's> AssetTypeLoader<'w, 's> {
vec![image_state, atlas_state]
}
pub fn check_apack_process_status(&self) -> bool {
self.handles
.apacks
.values()
.all(|handle| match self.apack.get(handle) {
Some(pack) => pack.processed && pack.loaded,
_ => false,
})
}
}
mod apack_handler;
mod asset_types;
mod loader;
mod resources;
mod startup;
pub use asset_types::ldtk_project::{LdtkLoader, LdtkProject, LevelIndex};
use bevy::app::{App, Plugin};
use bevy::prelude::AddAsset;
use iyes_loopless::condition::ConditionSet;
use iyes_loopless::prelude::AppLooplessStateExt;
pub use loader::AssetTypeLoader;
pub use resources::{AssetHandles, AssetNameMapping, FixedAssetNameMapping, SpriteSheetConfig};
use crate::assets::apack_handler::handle_apack_process_events;
use crate::assets::asset_types::ldtk_project::handle_ldtk_project_events;
use crate::system::flow::AppState;
pub struct AssetsPlugin;
impl Plugin for AssetsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<AssetHandles>()
.init_resource::<LevelIndex>()
.add_asset::<LdtkProject>()
.add_asset_loader(LdtkLoader)
.add_enter_system(AppState::Preload, startup::start_preload_resources)
.add_enter_system(AppState::Preload, startup::start_load_resources)
.add_system(handle_apack_process_events)
.add_enter_system(
AppState::Menu,
|assets: bevy::prelude::Res<AssetHandles>| {
log::info!("{:?}", assets.images);
},
)
.add_system(handle_ldtk_project_events)
.add_system_set(
ConditionSet::new()
.run_in_state(AppState::Setup)
......
use bevy::prelude::*;
use bevy::utils::HashMap;
use kayak_font::KayakFont;
use micro_asset_io::APack;
use micro_musicbox::prelude::AudioSource;
use micro_musicbox::utilities::{SuppliesAudio, TrackType};
use crate::assets::asset_types::ldtk_project::LdtkProject;
#[derive(Copy, Clone, Debug)]
pub struct SpriteSheetConfig {
pub tile_width: usize,
......@@ -39,6 +42,8 @@ pub struct AssetHandles {
pub sounds: HashMap<String, Handle<AudioSource>>,
pub fonts: HashMap<String, Handle<Font>>,
pub apacks: HashMap<String, Handle<APack>>,
pub ldtk: HashMap<String, Handle<LdtkProject>>,
pub kayak_fonts: HashMap<String, Handle<KayakFont>>,
}
macro_rules! fetch_wrapper {
......@@ -69,6 +74,8 @@ impl AssetHandles {
fetch_wrapper!(sound, AudioSource => sounds);
fetch_wrapper!(font, Font => fonts);
fetch_wrapper!(apack, APack => apacks);
fetch_wrapper!(ldtk, LdtkProject => ldtk);
fetch_wrapper!(kayak_font, KayakFont => kayak_fonts);
}
impl SuppliesAudio for AssetHandles {
......
......@@ -19,7 +19,8 @@ pub fn start_load_resources(mut loader: AssetTypeLoader) {
pub fn check_load_resources(mut commands: Commands, loader: AssetTypeLoader) {
let load_states = loader.get_all_load_state();
if load_states.iter().all(|state| *state == LoadState::Loaded) {
if loader.check_apack_process_status() {
log::info!("Assets loaded successfully");
commands.insert_resource(NextState(AppState::Splash))
}
......
use bevy::prelude::*;
#[derive(Component, Copy, Clone)]
pub struct AlignedBackground;
pub fn adjust_aligned_backgrounds(
mut query: Query<(&mut Transform, &Handle<Image>), With<AlignedBackground>>,
windows: Res<Windows>,
images: Res<Assets<Image>>,
) {
if let Some(window) = windows.get_primary() {
let width = window.width();
let height = window.height();
for (mut transform, handle) in &mut query {
if let Some(image) = images.get(handle) {
if width > height {
let scale = image.texture_descriptor.size.width as f32 / width;
transform.scale = Vec3::splat(scale);
} else {
let scale = image.texture_descriptor.size.height as f32 / height;
transform.scale = Vec3::splat(scale);
}
}
}
}
}
pub struct GraphicsPlugin;
impl Plugin for GraphicsPlugin {
fn build(&self, app: &mut App) {
app.add_system(adjust_aligned_backgrounds);
}
}
extern crate core;
pub mod assets;
pub mod graphics;
pub mod multiplayer;
pub mod splash_screen;
pub mod states;
pub mod system;
pub mod ui;
pub mod world;
......@@ -3,6 +3,7 @@ use bevy_ecs_tilemap::TilemapPlugin;
use game_core::assets::AssetHandles;
use game_core::system::flow::AppState;
use game_core::system::resources::InitAppPlugins;
use game_core::ui::AdventUIPlugins;
use iyes_loopless::prelude::AppLooplessStateExt;
use micro_musicbox::CombinedAudioPlugins;
......@@ -19,5 +20,7 @@ fn main() {
.add_plugin(bevy_tweening::TweeningPlugin)
.add_plugin(game_core::world::WorldPlugin)
.add_plugin(TilemapPlugin)
.add_plugin(game_core::graphics::GraphicsPlugin)
.add_plugins(AdventUIPlugins)
.run();
}
use bevy::prelude::Component;
#[derive(Component, Debug, Default, Copy, Clone)]
pub struct Player;
......@@ -6,6 +6,7 @@ use bevy_tweening::{Animator, EaseFunction, RepeatCount, RepeatStrategy, Tween};
use iyes_loopless::state::NextState;
use crate::assets::AssetHandles;
use crate::graphics::AlignedBackground;
use crate::system::flow::AppState;
#[derive(Component)]
......@@ -39,7 +40,7 @@ pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) {
"Trader Tales",
TextStyle {
font_size: 72.0,
font: assets.font("default"),
font: assets.font("compass_pro"),
color: Color::ANTIQUE_WHITE,
},
),
......@@ -55,7 +56,7 @@ pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) {
"> Press Space <",
TextStyle {
font_size: 48.0,
font: assets.font("default"),
font: assets.font("compass_pro"),
color: Color::ANTIQUE_WHITE,
},
),
......
......@@ -4,6 +4,7 @@ use iyes_loopless::prelude::{AppLooplessStateExt, ConditionSet};
use crate::states::menu_state::go_to_game;
use crate::system::flow::AppState;
mod game_state;
mod menu_state;
pub struct StatesPlugin;
......@@ -19,3 +20,5 @@ impl Plugin for StatesPlugin {
);
}
}
pub use game_state::Player;
use bevy::app::App;
use bevy::input::mouse::MouseMotion;
use bevy::math::{Vec2, Vec3Swizzles};
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::math::{vec3, Vec2, Vec3Swizzles};
use bevy::prelude::{
Camera2dBundle, Commands, Component, CoreStage, Entity, EventReader, Input, MouseButton,
OrthographicProjection, Plugin, Projection, Query, Res, Transform, Windows, With,
Camera2dBundle, Commands, Component, CoreStage, Entity, EventReader, Input, Local, MouseButton,
OrthographicProjection, Plugin, Projection, Query, Res, Transform, Windows, With, Without,
};
use bevy::render::camera::ScalingMode;
use iyes_loopless::prelude::{AppLooplessStateExt, ConditionSet};
use crate::system::flow::AppState;
use crate::system::load_config::virtual_size;
use crate::system::utilities::{f32_max, f32_min};
use crate::system::window::WindowManager;
use crate::world::MapQuery;
/// A flag component to indicate which entity should be followed by the camera
#[derive(Component)]
......@@ -43,20 +45,33 @@ pub fn spawn_orthographic_camera(mut commands: Commands) {
pub fn pan_camera(
mut ev_motion: EventReader<MouseMotion>,
mut ev_scroll: EventReader<MouseWheel>,
input_mouse: Res<Input<MouseButton>>,
mut query: Query<(&mut Transform, &Projection), With<GameCamera>>,
mut query: Query<(&mut Transform, &OrthographicProjection), With<GameCamera>>,
windows: Res<Windows>,
mut last_mouse_pos: Local<Vec2>,
) {
let mut pan = Vec2::ZERO;
if input_mouse.pressed(MouseButton::Right) {
for ev in ev_motion.iter() {
pan += ev.delta;
}
}
if let Some(mouse) = windows
.get_primary()
.and_then(|window| window.cursor_position())
{
let previous_mouse = std::mem::replace(&mut *last_mouse_pos, mouse);
let mouse_diff = mouse - previous_mouse;
for (mut transform, _) in &mut query {
if pan.length_squared() > 0.0 {}
if input_mouse.pressed(MouseButton::Right) {
for ev in ev_motion.iter() {
pan += ev.delta;
}
if mouse_diff != Vec2::ZERO && mouse_diff.x < 50.0 && mouse_diff.y < 50.0 {
for (mut transform, projection) in &mut query {
transform.translation -= mouse_diff.extend(0.0);
}
}
}
}
}
......@@ -67,8 +82,12 @@ pub fn pan_camera(
/// by updated to the midpoint between the player and the mouse
pub fn sync_chase_camera_location(
mut commands: Commands,
chased_query: Query<&Transform, With<ChaseCam>>,
camera_query: Query<(Entity, &Transform), With<GameCamera>>,
chased_query: Query<&Transform, (With<ChaseCam>, Without<GameCamera>)>,
mut camera_query: Query<
(Entity, &mut Transform, &OrthographicProjection),
(With<GameCamera>, Without<ChaseCam>),
>,
map_query: MapQuery,
) {
if chased_query.is_empty() {
return;
......@@ -85,11 +104,26 @@ pub fn sync_chase_camera_location(
average_location /= count as f32;
}
for (entity, location) in camera_query.iter() {
commands.entity(entity).insert(Transform {
translation: average_location.extend(location.translation.z),
..*location
});
for (_, mut location, proj) in camera_query.iter_mut() {
if let Some(bounds) = map_query.get_camera_bounds() {
// log::info!("BOUNDS {:?}", bounds);
let width = proj.right - proj.left;
let height = proj.top - proj.bottom;
let val_x = f32_max(
bounds.get_min_x(width),
f32_min(bounds.get_max_x(width), average_location.x),
);
let val_y = f32_max(
bounds.get_min_y(height),
f32_min(bounds.get_max_y(height), average_location.y),
);
location.translation = vec3(val_x, val_y, location.translation.z);
} else {
location.translation = average_location.extend(location.translation.z);
}
}
}
......
......@@ -13,7 +13,7 @@ mod setup {
(1280.0, 720.0)
}
pub fn virtual_size() -> (f32, f32) {
(640.0, 360.0)
(320.0, 180.0)
}
}
......
use std::ops::{Deref, DerefMut};
use bevy::prelude::UVec2;
use num_traits::AsPrimitive;
#[inline]
pub fn f32_max(a: f32, b: f32) -> f32 {
......@@ -42,41 +43,83 @@ pub struct Indexer {
}
impl Indexer {
pub fn new(width: usize, height: usize) -> Self {
Indexer { width, height }
pub fn new(width: impl AsPrimitive<usize>, height: impl AsPrimitive<usize>) -> Self {
Indexer {
width: width.as_(),
height: height.as_(),
}
}
pub fn index(&self, x: usize, y: usize) -> usize {
(y * self.width) + x
pub fn index(&self, x: impl AsPrimitive<usize>, y: impl AsPrimitive<usize>) -> usize {
(y.as_() * self.width) + x.as_()
}
pub fn checked_index(&self, x: usize, y: usize) -> Option<usize> {
pub fn checked_index(
&self,
x: impl AsPrimitive<usize>,
y: impl AsPrimitive<usize>,
) -> Option<usize> {
if self.is_coordinate_valid(x, y) {
Some(self.index(x, y))
} else {
None
}
}
pub fn reverse(&self, index: usize) -> (usize, usize) {
(index % self.width, index / self.width)
pub fn reverse(&self, index: impl AsPrimitive<usize>) -> (usize, usize) {
(index.as_() % self.width, index.as_() / self.width)
}
pub fn checked_reverse(&self, idx: usize) -> Option<(usize, usize)> {
pub fn checked_reverse(&self, idx: impl AsPrimitive<usize>) -> Option<(usize, usize)> {
if self.is_index_valid(idx) {
Some(self.reverse(idx))
} else {
None
}
}
pub fn index_within(&self, idx: usize) -> bool {
pub fn index_within(&self, idx: impl AsPrimitive<usize>) -> bool {
let (x, y) = self.reverse(idx);
x >= 0 && x < self.width && y >= 0 && y < self.height
}
pub fn is_uvec2_valid(&self, point: UVec2) -> bool {
self.is_coordinate_valid(point.x as usize, point.y as usize)
self.is_coordinate_valid(point.x, point.y)
}
pub fn square_adjacent(
&self,
x: impl AsPrimitive<u32>,
y: impl AsPrimitive<u32>,
) -> Vec<UVec2> {
let x = x.as_();
let y = y.as_();
let initial_point = UVec2::new(x, y);
let left = UVec2::new(x.saturating_sub(1), y);
let right = UVec2::new(x.saturating_add(1), y);
let top = UVec2::new(x, y.saturating_add(1));
let bottom = UVec2::new(x, y.saturating_sub(1));
let mut output = Vec::with_capacity(4);
if left != initial_point {
output.push(left);
}
if right != initial_point && right.x < self.width as u32 {
output.push(right);
}
if bottom != initial_point {
output.push(bottom)
}
if top != initial_point && top.y < self.height as u32 {
output.push(top)
}
output
}
pub fn is_coordinate_valid(&self, x: usize, y: usize) -> bool {
x < self.width && y < self.height
pub fn is_coordinate_valid(
&self,
x: impl AsPrimitive<usize>,
y: impl AsPrimitive<usize>,
) -> bool {
x.as_() < self.width && y.as_() < self.height
}
pub fn is_index_valid(&self, idx: usize) -> bool {
idx < self.width * self.height
pub fn is_index_valid(&self, idx: impl AsPrimitive<usize>) -> bool {
idx.as_() < self.width * self.height
}
pub fn width(&self) -> usize {
self.width
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment