From a695f52ca156bc47a40bd58fc72e209bc96cf9fd Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Thu, 16 Mar 2023 13:24:39 +0000 Subject: [PATCH] Stash --- Cargo.lock | 7 + Cargo.toml | 5 +- src/system/model.rs | 5 + src/system/spaces/continuous_2d.rs | 2 +- src/system/spaces/hexagonal.rs | 2 +- src/system/spaces/vector_storage.rs | 4 +- src/{fd.rs => tools/boxcount.rs} | 88 ++------ src/tools/mod.rs | 29 +++ src/tools/render.rs | 109 +++++++++ src/tools_cli.rs | 48 ++++ src/ui.rs | 338 +--------------------------- 11 files changed, 243 insertions(+), 394 deletions(-) rename src/{fd.rs => tools/boxcount.rs} (59%) create mode 100644 src/tools/mod.rs create mode 100644 src/tools/render.rs create mode 100644 src/tools_cli.rs diff --git a/Cargo.lock b/Cargo.lock index 4d5760a..2e45fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2950,6 +2950,7 @@ dependencies = [ "rand", "serde", "serde_json", + "svg", ] [[package]] @@ -3145,6 +3146,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "svg" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e715e0c3fc987f4c435dc7189641fd9caa6919a74675ace605c38e201d278001" + [[package]] name = "svg_fmt" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 4d5c15d..c0bfdc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,8 @@ name = "ui" path = "src/ui.rs" [[bin]] -name = "fd-calc" -path = "src/fd.rs" +name = "tools" +path = "src/tools_cli.rs" # Set the default for crate. [profile.dev] @@ -48,6 +48,7 @@ nalgebra = "0.32.2" kiddo = "0.2.5" anyhow = "1.0.69" itertools = "0.10.5" +svg = "0.13.0" [build-dependencies] cbindgen = "0.24.3" diff --git a/src/system/model.rs b/src/system/model.rs index 735c66d..efc1b9f 100644 --- a/src/system/model.rs +++ b/src/system/model.rs @@ -100,15 +100,20 @@ impl, W: Walker

, Sp: Spawner

, St: Stick let distance = next_position.abs(); if distance > self.kill_circle { + println!("HE"); self.active_particle = None; } else if !self.space.is_occupied(&next_position) { if self.sticker.should_stick(&mut self.rng, &self.space, &next_position) { + println!("STY"); self.deposit(&next_position); self.active_particle = None; return; } else { + println!("MV"); self.active_particle.replace(next_position); } + } else { + println!("Err"); } } diff --git a/src/system/spaces/continuous_2d.rs b/src/system/spaces/continuous_2d.rs index 96b34fc..84d01ed 100644 --- a/src/system/spaces/continuous_2d.rs +++ b/src/system/spaces/continuous_2d.rs @@ -23,7 +23,7 @@ impl P2 { let (x, y) = ( radius * theta.sin(), - radius * theta.sin(), + radius * theta.cos(), ); P2 { x, y } diff --git a/src/system/spaces/hexagonal.rs b/src/system/spaces/hexagonal.rs index 618e59c..370d995 100644 --- a/src/system/spaces/hexagonal.rs +++ b/src/system/spaces/hexagonal.rs @@ -28,7 +28,7 @@ impl GriddedPosition for HexPosition { (-1, 0), (-1, 1), (0, 1), ]; - self.clone() + HexPosition { q: OFFSETS[neighbour_index].0, r: OFFSETS[neighbour_index].0 } + self.clone() + HexPosition { q: OFFSETS[neighbour_index].0, r: OFFSETS[neighbour_index].1 } } fn linear_index(&self, grid_size: u32) -> usize { diff --git a/src/system/spaces/vector_storage.rs b/src/system/spaces/vector_storage.rs index 1a08290..4818ec8 100644 --- a/src/system/spaces/vector_storage.rs +++ b/src/system/spaces/vector_storage.rs @@ -14,7 +14,9 @@ impl VectorStorage { impl Storage

for VectorStorage { fn is_occupied(&self, position: &P) -> bool { - return self.backing[position.linear_index(self.grid_size)]; + let i = position.linear_index(self.grid_size); + println!("{i}"); + return self.backing[i]; } fn deposit(&mut self, position: &P) { diff --git a/src/fd.rs b/src/tools/boxcount.rs similarity index 59% rename from src/fd.rs rename to src/tools/boxcount.rs index aac4fa4..b43b2f0 100644 --- a/src/fd.rs +++ b/src/tools/boxcount.rs @@ -1,55 +1,44 @@ -#![feature(generic_const_exprs)] -#![feature(let_chains)] - -mod system; -mod cli; - use std::fs::File; use std::os::unix::fs::symlink; use bevy::tasks::ParallelSlice; use crate::system::spaces::square_grid::{Grid2D, Grid3D}; use itertools::{Itertools, MinMaxResult}; use clap::Parser; +use crate::BoxCountCli; use crate::cli::cli::OutputFormat; use crate::system::{GriddedPosition, Position}; use crate::system::model::HistoryLine; +use crate::tools::read; -fn box_count_2d(data: &Vec, size: u32) -> usize { +fn bb(data: &Vec) -> ((i32, i32), (i32, i32)) { + let x = data + .iter().minmax_by(|a, b| a.x.cmp(&b.x)); + + let y = data + .iter().minmax_by(|a, b| a.x.cmp(&b.x)); + + match (x, y) { + (MinMaxResult::MinMax(min_x, max_x), MinMaxResult::MinMax(min_y, max_y)) => { + ((min_x.x, min_y.y), (max_x.x, max_y.y)) + }, + + _ => panic!("Cannot determine bounding box") + } +} + +fn box_count_2d(data: &Vec, box_number: u32) -> usize { let n = data.len(); - let x_min = data - .iter() - .min_by(|Grid2D { x: x1, y: y1 }, Grid2D { x: x2, y: y2 }| x1.cmp(x2)) - .unwrap().x; - - let x_max = data - .iter() - .max_by(|Grid2D { x: x1, y: y1 }, Grid2D { x: x2, y: y2 }| x1.cmp(x2)) - .unwrap().x; - - let y_min = data - .iter() - .min_by(|Grid2D { x: x1, y: y1 }, Grid2D { x: x2, y: y2 }| y1.cmp(y2)) - .unwrap().y; - - let y_max = data - .iter() - .max_by(|Grid2D { x: x1, y: y1 }, Grid2D { x: x2, y: y2 }| y1.cmp(y2)) - .unwrap().y; + let ((x_min, x_max), (y_min, y_max)) = bb(data); let x_range = (x_max - x_min) as f64; let y_range = (y_max - y_min) as f64; - let w: f64 = x_range / (size as f64); - // let n_x = size; - // let n_y = (y_range / w).ceil() as u32; + let w: f64 = x_range / (box_number as f64); - let grid_points = data.iter() + data.iter() .map(|Grid2D { x, y }| [((x - x_min) as f64 / w) as u32, ((y - y_min) as f64 / w) as u32]) - .collect::>(); - - return grid_points.iter() .unique() - .count(); + .count() } fn box_count_3d(data: &Vec, size: u32) -> usize { @@ -121,33 +110,6 @@ fn box_count_nd(data: &Vec<[f32; N]>, size: u32) -> usize { .count(); } -#[derive(Parser)] -struct FDArgs { - format: OutputFormat, - path: std::path::PathBuf, -} - -fn main() { - let args = FDArgs::parse(); - - let qa = match args.format { - OutputFormat::FullDataJson => { - serde_json::from_reader::<_, Vec>>(File::open(args.path).unwrap()) - .expect("Failed to read json") - .iter() - .map(|l| l.position.clone()) - .collect::>() - } - OutputFormat::Positions => { - csv::Reader::from_path(args.path).unwrap().deserialize::() - .collect::, _>>() - .unwrap() - } - }; - - assert_eq!(qa.iter().unique().collect::>().len(), qa.len()); - - for size in 1..250 { - println!("[{}, {:?}],", size, box_count_3d(&qa, size)); - } +pub fn main(cli: &BoxCountCli) { + let particles = read(&cli.path, cli.format); } diff --git a/src/tools/mod.rs b/src/tools/mod.rs new file mode 100644 index 0000000..b44669b --- /dev/null +++ b/src/tools/mod.rs @@ -0,0 +1,29 @@ +use std::path::Path; +use serde::de::DeserializeOwned; +use crate::cli::cli::OutputFormat; +use crate::system::model::HistoryLine; +use crate::system::Position; + +pub mod boxcount; +pub mod render; + +pub fn read(path: &Path, format: OutputFormat) -> Vec where T: DeserializeOwned { + match format { + OutputFormat::FullDataJson => read_json(path), + OutputFormat::Positions => read_csv(path) + } +} + +pub fn read_json(path: &Path) -> Vec where T: DeserializeOwned { + serde_json::from_reader::<_, Vec>>(File::open(path).expect("Failed to open file")) + .expect("Failed to read json") + .iter() + .map(|l| (l.position.clone())) + .collect::>() +} + +pub fn read_csv(path: &Path) -> Vec where T: DeserializeOwned { + csv::Reader::from_path(path).expect("Failed to read positions csv").deserialize::() + .collect::, _>>() + .unwrap() +} diff --git a/src/tools/render.rs b/src/tools/render.rs new file mode 100644 index 0000000..f8b5486 --- /dev/null +++ b/src/tools/render.rs @@ -0,0 +1,109 @@ +#![feature(generic_const_exprs)] +#![feature(let_chains)] + +use std::fs::File; +use std::path::PathBuf; +use anyhow::Context; +use crate::cli::cli::OutputFormat; +use crate::system::model::HistoryLine; +use crate::system::spaces::square_grid::Grid2D; +use clap::Parser; +use serde::de::DeserializeOwned; +use serde::Deserialize; +use svg::Node; +use svg::node::element::Rectangle; +use crate::system::Position; +use crate::system::spaces::hexagonal::HexPosition; + +#[derive(Debug, Parser)] +struct Args { + format: OutputFormat, + path: PathBuf, + output: PathBuf, +} + +trait ToSvg { + fn to_svg(&self, size: i32) -> Box; +} + +impl ToSvg for Grid2D { + fn to_svg(&self, size: i32) -> Box { + Box::new(Rectangle::new() + .set("fill", "rgb(0, 0, 0)") + .set("width", size) + .set("height", size) + .set("x", self.x * size) + .set("y", self.y * size)) + } +} + +impl ToSvg for HexPosition { + fn to_svg(&self, size: i32) -> Box { + let points = [ + [25.045, 128.0], [256.0, 0.0], [486.955, 128.0], [486.955, 384.0], [256.0, 512.0], [25.045, 384.0] + ]; + + let size = size as f32; + + let b = points.map(|x| [ + (x[0] / 512.0) * (size), + (x[1] / 512.0) * (size)] + ); + + let c = b.map(|p| format!("{},{}", p[0], p[1])).join(" "); + + let [x, y] = self.to_cartesian(); + Box::new(Rectangle::new() + .set("fill", "rgb(0, 0, 0)") + .set("x", x * size) + .set("y", y * size)) + } +} + +fn read_json(args: &Args) -> Vec where T: DeserializeOwned { + serde_json::from_reader::<_, Vec>>(File::open(&args.path).expect("Failed to open file")) + .expect("Failed to read json") + .iter() + .map(|l| (l.position.clone())) + .collect::>() +} + +fn read_csv(args: &Args) -> Vec where T: DeserializeOwned { + csv::Reader::from_path(&args.path).expect("Failed to read positions csv").deserialize::() + .collect::, _>>() + .unwrap() +} + +fn main() { + let args = Args::parse(); + dbg!(&args); + + let positions: Vec = match args.format { + OutputFormat::FullDataJson => read_json::(&args), + OutputFormat::Positions => read_csv::(&args), + }; + + let size: i32 = 800; + let max_x = positions.iter().max_by(|a, b| a.x.abs().cmp(&b.x.abs())).unwrap().x.abs(); + let max_y = positions.iter().max_by(|a, b| a.y.abs().cmp(&b.y.abs())).unwrap().y.abs(); + let max_size = max_x.max(max_y) * size; + + let mut svg = svg::Document::new() + .set("width", max_size * size) + .set("height", max_size * size) + .set("viewBox", format!("{} {} {} {}", -max_size, -max_size, max_size * 2, max_size * 2)); + + svg.append(Rectangle::new() + .set("fill", "white") + .set("width", max_size * 2) + .set("height", max_size * 2) + .set("x", -max_size) + .set("y", -max_size) + ); + + for position in positions { + svg.append(position.to_svg(size)); + } + + svg::write(File::create(args.output).unwrap(), &svg).unwrap(); +} diff --git a/src/tools_cli.rs b/src/tools_cli.rs new file mode 100644 index 0000000..2b84683 --- /dev/null +++ b/src/tools_cli.rs @@ -0,0 +1,48 @@ +#![feature(generic_const_exprs)] +#![feature(let_chains)] + +use std::fs::File; +use std::path::{Path, PathBuf}; +use anyhow::Context; +use crate::cli::cli::OutputFormat; +use crate::system::model::HistoryLine; +use crate::system::spaces::square_grid::Grid2D; +use clap::{Parser, Command, Args, Subcommand}; +use serde::de::DeserializeOwned; +use serde::Deserialize; +use svg::Node; +use svg::node::element::Rectangle; +use crate::system::Position; +use crate::system::spaces::hexagonal::HexPosition; + +mod system; +mod cli; +mod tools; + +#[derive(Debug, Parser)] +enum ToolsCli { + Render(RenderCli), + BoxCount(BoxCountCli) +} + +#[derive(Debug, Args)] +struct RenderCli { + +} + +#[derive(Debug, Args)] +struct BoxCountCli { + #[arg(value_enum, short, long, default_value_t = OutputFormat::Positions)] + format: OutputFormat, + path: PathBuf, +} + +fn main() { + let args = ToolsCli::parse(); + dbg!(&args); + + match args { + ToolsCli::Render(_) => {} + ToolsCli::BoxCount(cli) => tools::boxcount::main(&cli), + } +} diff --git a/src/ui.rs b/src/ui.rs index 3afb991..6422f6f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -2,36 +2,20 @@ use bevy::{ prelude::*, - sprite::collide_aabb::{collide, Collision}, - sprite::MaterialMesh2dBundle, }; -// Defines the amount of time that should elapse between each physics step. -const TIME_STEP: f32 = 1.0 / 60.0; - // These constants are defined in `Transform` units. // Using the default 2D camera they correspond 1:1 with screen pixels. -const PADDLE_SIZE: Vec3 = Vec3::new(120.0, 20.0, 0.0); const GAP_BETWEEN_PADDLE_AND_FLOOR: f32 = 60.0; -const PADDLE_SPEED: f32 = 500.0; -// How close can the paddle get to the wall -const PADDLE_PADDING: f32 = 10.0; -// We set the z-value of the ball to 1 so it renders on top in the case of overlapping sprites. -const BALL_STARTING_POSITION: Vec3 = Vec3::new(0.0, -50.0, 1.0); -const BALL_SIZE: Vec3 = Vec3::new(30.0, 30.0, 0.0); -const BALL_SPEED: f32 = 400.0; -const INITIAL_BALL_DIRECTION: Vec2 = Vec2::new(0.5, -0.5); - -const WALL_THICKNESS: f32 = 10.0; -// x coordinates const LEFT_WALL: f32 = -450.; const RIGHT_WALL: f32 = 450.; // y coordinates const BOTTOM_WALL: f32 = -300.; const TOP_WALL: f32 = 300.; -const BRICK_SIZE: Vec2 = Vec2::new(100., 30.); +const PARTICLE_SIZE: Vec2 = Vec2::new(10., 10.); + // These values are exact const GAP_BETWEEN_PADDLE_AND_BRICKS: f32 = 270.0; const GAP_BETWEEN_BRICKS: f32 = 5.0; @@ -39,227 +23,36 @@ const GAP_BETWEEN_BRICKS: f32 = 5.0; const GAP_BETWEEN_BRICKS_AND_CEILING: f32 = 20.0; const GAP_BETWEEN_BRICKS_AND_SIDES: f32 = 20.0; -const SCOREBOARD_FONT_SIZE: f32 = 40.0; -const SCOREBOARD_TEXT_PADDING: Val = Val::Px(5.0); - const BACKGROUND_COLOR: Color = Color::rgb(0.9, 0.9, 0.9); -const PADDLE_COLOR: Color = Color::rgb(0.3, 0.3, 0.7); -const BALL_COLOR: Color = Color::rgb(1.0, 0.5, 0.5); const BRICK_COLOR: Color = Color::rgb(0.5, 0.5, 1.0); -const WALL_COLOR: Color = Color::rgb(0.8, 0.8, 0.8); -const TEXT_COLOR: Color = Color::rgb(0.5, 0.5, 1.0); -const SCORE_COLOR: Color = Color::rgb(1.0, 0.5, 0.5); fn main() { App::new() .add_plugins(DefaultPlugins) - .insert_resource(Scoreboard { score: 0 }) .insert_resource(ClearColor(BACKGROUND_COLOR)) .add_startup_system(setup) - .add_event::() - // Add our gameplay simulation systems to the fixed timestep schedule - .add_systems( - ( - check_for_collisions, - apply_velocity.before(check_for_collisions), - move_paddle - .before(check_for_collisions) - .after(apply_velocity), - play_collision_sound.after(check_for_collisions), - ) - .in_schedule(CoreSchedule::FixedUpdate), - ) - // Configure how frequently our gameplay systems are run - .insert_resource(FixedTime::new_from_secs(TIME_STEP)) - .add_system(update_scoreboard) .add_system(bevy::window::close_on_esc) .run(); } -#[derive(Component)] -struct Paddle; - -#[derive(Component)] -struct Ball; - -#[derive(Component, Deref, DerefMut)] -struct Velocity(Vec2); - -#[derive(Component)] -struct Collider; - -#[derive(Default)] -struct CollisionEvent; - #[derive(Component)] struct Brick; -#[derive(Resource)] -struct CollisionSound(Handle); - -// This bundle is a collection of the components that define a "wall" in our game -#[derive(Bundle)] -struct WallBundle { - // You can nest bundles inside of other bundles like this - // Allowing you to compose their functionality - sprite_bundle: SpriteBundle, - collider: Collider, -} - -/// Which side of the arena is this wall located on? -enum WallLocation { - Left, - Right, - Bottom, - Top, -} - -impl WallLocation { - fn position(&self) -> Vec2 { - match self { - WallLocation::Left => Vec2::new(LEFT_WALL, 0.), - WallLocation::Right => Vec2::new(RIGHT_WALL, 0.), - WallLocation::Bottom => Vec2::new(0., BOTTOM_WALL), - WallLocation::Top => Vec2::new(0., TOP_WALL), - } - } - - fn size(&self) -> Vec2 { - let arena_height = TOP_WALL - BOTTOM_WALL; - let arena_width = RIGHT_WALL - LEFT_WALL; - // Make sure we haven't messed up our constants - assert!(arena_height > 0.0); - assert!(arena_width > 0.0); - - match self { - WallLocation::Left | WallLocation::Right => { - Vec2::new(WALL_THICKNESS, arena_height + WALL_THICKNESS) - } - WallLocation::Bottom | WallLocation::Top => { - Vec2::new(arena_width + WALL_THICKNESS, WALL_THICKNESS) - } - } - } -} - -impl WallBundle { - // This "builder method" allows us to reuse logic across our wall entities, - // making our code easier to read and less prone to bugs when we change the logic - fn new(location: WallLocation) -> WallBundle { - WallBundle { - sprite_bundle: SpriteBundle { - transform: Transform { - // We need to convert our Vec2 into a Vec3, by giving it a z-coordinate - // This is used to determine the order of our sprites - translation: location.position().extend(0.0), - // The z-scale of 2D objects must always be 1.0, - // or their ordering will be affected in surprising ways. - // See https://github.com/bevyengine/bevy/issues/4149 - scale: location.size().extend(1.0), - ..default() - }, - sprite: Sprite { - color: WALL_COLOR, - ..default() - }, - ..default() - }, - collider: Collider, - } - } -} - -// This resource tracks the game's score -#[derive(Resource)] -struct Scoreboard { - score: usize, -} - // Add the game's entities to our world fn setup( mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - asset_server: Res, ) { // Camera commands.spawn(Camera2dBundle::default()); - // Sound - let ball_collision_sound = asset_server.load("sounds/breakout_collision.ogg"); - commands.insert_resource(CollisionSound(ball_collision_sound)); - // Paddle let paddle_y = BOTTOM_WALL + GAP_BETWEEN_PADDLE_AND_FLOOR; - commands.spawn(( - SpriteBundle { - transform: Transform { - translation: Vec3::new(0.0, paddle_y, 0.0), - scale: PADDLE_SIZE, - ..default() - }, - sprite: Sprite { - color: PADDLE_COLOR, - ..default() - }, - ..default() - }, - Paddle, - Collider, - )); - - // Ball - commands.spawn(( - MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::default().into()).into(), - material: materials.add(ColorMaterial::from(BALL_COLOR)), - transform: Transform::from_translation(BALL_STARTING_POSITION).with_scale(BALL_SIZE), - ..default() - }, - Ball, - Velocity(INITIAL_BALL_DIRECTION.normalize() * BALL_SPEED), - )); - - // Scoreboard - commands.spawn( - TextBundle::from_sections([ - TextSection::new( - "Score: ", - TextStyle { - font: asset_server.load("fonts/FiraSans-Bold.ttf"), - font_size: SCOREBOARD_FONT_SIZE, - color: TEXT_COLOR, - }, - ), - TextSection::from_style(TextStyle { - font: asset_server.load("fonts/FiraMono-Medium.ttf"), - font_size: SCOREBOARD_FONT_SIZE, - color: SCORE_COLOR, - }), - ]) - .with_style(Style { - position_type: PositionType::Absolute, - position: UiRect { - top: SCOREBOARD_TEXT_PADDING, - left: SCOREBOARD_TEXT_PADDING, - ..default() - }, - ..default() - }), - ); - - // Walls - commands.spawn(WallBundle::new(WallLocation::Left)); - commands.spawn(WallBundle::new(WallLocation::Right)); - commands.spawn(WallBundle::new(WallLocation::Bottom)); - commands.spawn(WallBundle::new(WallLocation::Top)); - // Bricks // Negative scales result in flipped sprites / meshes, // which is definitely not what we want here - assert!(BRICK_SIZE.x > 0.0); - assert!(BRICK_SIZE.y > 0.0); + assert!(PARTICLE_SIZE.x > 0.0); + assert!(PARTICLE_SIZE.y > 0.0); let total_width_of_bricks = (RIGHT_WALL - LEFT_WALL) - 2. * GAP_BETWEEN_BRICKS_AND_SIDES; let bottom_edge_of_bricks = paddle_y + GAP_BETWEEN_PADDLE_AND_BRICKS; @@ -269,8 +62,8 @@ fn setup( assert!(total_height_of_bricks > 0.0); // Given the space available, compute how many rows and columns of bricks we can fit - let n_columns = (total_width_of_bricks / (BRICK_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize; - let n_rows = (total_height_of_bricks / (BRICK_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize; + let n_columns = (total_width_of_bricks / (PARTICLE_SIZE.x + GAP_BETWEEN_BRICKS)).floor() as usize; + let n_rows = (total_height_of_bricks / (PARTICLE_SIZE.y + GAP_BETWEEN_BRICKS)).floor() as usize; let n_vertical_gaps = n_columns - 1; // Because we need to round the number of columns, @@ -278,20 +71,20 @@ fn setup( let center_of_bricks = (LEFT_WALL + RIGHT_WALL) / 2.0; let left_edge_of_bricks = center_of_bricks // Space taken up by the bricks - - (n_columns as f32 / 2.0 * BRICK_SIZE.x) + - (n_columns as f32 / 2.0 * PARTICLE_SIZE.x) // Space taken up by the gaps - n_vertical_gaps as f32 / 2.0 * GAP_BETWEEN_BRICKS; // In Bevy, the `translation` of an entity describes the center point, // not its bottom-left corner - let offset_x = left_edge_of_bricks + BRICK_SIZE.x / 2.; - let offset_y = bottom_edge_of_bricks + BRICK_SIZE.y / 2.; + let offset_x = left_edge_of_bricks + PARTICLE_SIZE.x / 2.; + let offset_y = bottom_edge_of_bricks + PARTICLE_SIZE.y / 2.; for row in 0..n_rows { for column in 0..n_columns { let brick_position = Vec2::new( - offset_x + column as f32 * (BRICK_SIZE.x + GAP_BETWEEN_BRICKS), - offset_y + row as f32 * (BRICK_SIZE.y + GAP_BETWEEN_BRICKS), + offset_x + column as f32 * (PARTICLE_SIZE.x + GAP_BETWEEN_BRICKS), + offset_y + row as f32 * (PARTICLE_SIZE.y + GAP_BETWEEN_BRICKS), ); // brick @@ -303,120 +96,13 @@ fn setup( }, transform: Transform { translation: brick_position.extend(0.0), - scale: Vec3::new(BRICK_SIZE.x, BRICK_SIZE.y, 1.0), + scale: Vec3::new(PARTICLE_SIZE.x, PARTICLE_SIZE.y, 1.0), ..default() }, ..default() }, Brick, - Collider, )); } } } - -fn move_paddle( - keyboard_input: Res>, - mut query: Query<&mut Transform, With>, -) { - let mut paddle_transform = query.single_mut(); - let mut direction = 0.0; - - if keyboard_input.pressed(KeyCode::Left) { - direction -= 1.0; - } - - if keyboard_input.pressed(KeyCode::Right) { - direction += 1.0; - } - - // Calculate the new horizontal paddle position based on player input - let new_paddle_position = paddle_transform.translation.x + direction * PADDLE_SPEED * TIME_STEP; - - // Update the paddle position, - // making sure it doesn't cause the paddle to leave the arena - let left_bound = LEFT_WALL + WALL_THICKNESS / 2.0 + PADDLE_SIZE.x / 2.0 + PADDLE_PADDING; - let right_bound = RIGHT_WALL - WALL_THICKNESS / 2.0 - PADDLE_SIZE.x / 2.0 - PADDLE_PADDING; - - paddle_transform.translation.x = new_paddle_position.clamp(left_bound, right_bound); -} - -fn apply_velocity(mut query: Query<(&mut Transform, &Velocity)>) { - for (mut transform, velocity) in &mut query { - transform.translation.x += velocity.x * TIME_STEP; - transform.translation.y += velocity.y * TIME_STEP; - } -} - -fn update_scoreboard(scoreboard: Res, mut query: Query<&mut Text>) { - let mut text = query.single_mut(); - text.sections[1].value = scoreboard.score.to_string(); -} - -fn check_for_collisions( - mut commands: Commands, - mut scoreboard: ResMut, - mut ball_query: Query<(&mut Velocity, &Transform), With>, - collider_query: Query<(Entity, &Transform, Option<&Brick>), With>, - mut collision_events: EventWriter, -) { - let (mut ball_velocity, ball_transform) = ball_query.single_mut(); - let ball_size = ball_transform.scale.truncate(); - - // check collision with walls - for (collider_entity, transform, maybe_brick) in &collider_query { - let collision = collide( - ball_transform.translation, - ball_size, - transform.translation, - transform.scale.truncate(), - ); - if let Some(collision) = collision { - // Sends a collision event so that other systems can react to the collision - collision_events.send_default(); - - // Bricks should be despawned and increment the scoreboard on collision - if maybe_brick.is_some() { - scoreboard.score += 1; - commands.entity(collider_entity).despawn(); - } - - // reflect the ball when it collides - let mut reflect_x = false; - let mut reflect_y = false; - - // only reflect if the ball's velocity is going in the opposite direction of the - // collision - match collision { - Collision::Left => reflect_x = ball_velocity.x > 0.0, - Collision::Right => reflect_x = ball_velocity.x < 0.0, - Collision::Top => reflect_y = ball_velocity.y < 0.0, - Collision::Bottom => reflect_y = ball_velocity.y > 0.0, - Collision::Inside => { /* do nothing */ } - } - - // reflect velocity on the x-axis if we hit something on the x-axis - if reflect_x { - ball_velocity.x = -ball_velocity.x; - } - - // reflect velocity on the y-axis if we hit something on the y-axis - if reflect_y { - ball_velocity.y = -ball_velocity.y; - } - } - } -} - -fn play_collision_sound( - mut collision_events: EventReader, - audio: Res