From 5835fd081cadd194b498941a10724ca9c377a009 Mon Sep 17 00:00:00 2001 From: Manuel Mauro Date: Sat, 4 Apr 2026 23:25:46 +0200 Subject: [PATCH 1/4] feat: add shape cues as a new stimulus channel Add a shape cue channel alongside position, color, and sound. The tile now renders as a 2D mesh (circle, triangle, square, pentagon, or hexagon) instead of a plain sprite. Changes: - New TileShape enum with 5 shape variants + None default - Tile rendering switched from Sprite to Mesh2d + ColorMaterial so the mesh can be swapped per shape - TileMeshes resource holds pre-built mesh handles for each shape - CueEngine gains a shapes channel (Option>) - CueEngine::new_cue() returns a Cue struct (cleaner than 4-tuple) - Answer gains a shape field - GameSettings gains a shape toggle (off by default) - Game UI: 4 action buttons (Position A, Color S, Shape D, Sound F) - Menu UI: shape checkbox in the cue selection grid - Debug overlay: shape match and shape answer display Keyboard shortcuts reshuffled to home row: A=Position, S=Color, D=Shape, F=Sound --- src/debug.rs | 16 +++++++++ src/game/mod.rs | 38 +++++++++++++-------- src/game/session/answer.rs | 2 ++ src/game/session/engine.rs | 31 ++++++++++++----- src/game/session/mod.rs | 23 +++++++++---- src/game/settings.rs | 2 ++ src/game/tile/mod.rs | 68 +++++++++++++++++++++++++++++++++----- src/game/tile/shape.rs | 28 ++++++++++++++++ src/game/ui/button.rs | 9 +++-- src/game/ui/mod.rs | 14 +++++--- src/menu/checkbox.rs | 6 ++-- src/menu/ui.rs | 7 ++-- 12 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 src/game/tile/shape.rs diff --git a/src/debug.rs b/src/debug.rs index 7fbb906..111aa1b 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -61,6 +61,10 @@ fn debug_ui_system( ui.label(if settings.color { "✅" } else { "❌" }); ui.end_row(); + ui.label("Shape cues"); + ui.label(if settings.shape { "✅" } else { "❌" }); + ui.end_row(); + ui.label("Sound cues"); ui.label(if settings.sound { "✅" } else { "❌" }); ui.end_row(); @@ -119,6 +123,14 @@ fn debug_ui_system( }); ui.end_row(); + ui.label("Shape match"); + ui.label(match &engine.shapes { + Some(s) if s.is_match() => "🟢 YES", + Some(_) => "⚫ no", + None => "—", + }); + ui.end_row(); + ui.label("Sound match"); ui.label(match &engine.sounds { Some(s) if s.is_match() => "🟢 YES", @@ -163,6 +175,10 @@ fn debug_ui_system( ui.label(if answer.color { "🟢" } else { "⚫" }); ui.end_row(); + ui.label("Shape"); + ui.label(if answer.shape { "🟢" } else { "⚫" }); + ui.end_row(); + ui.label("Sound"); ui.label(if answer.sound { "🟢" } else { "⚫" }); ui.end_row(); diff --git a/src/game/mod.rs b/src/game/mod.rs index 79fd2e9..9212189 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -8,7 +8,7 @@ use self::{ score::Score, }, settings::GameSettings, - tile::{Tile, TilePlugin}, + tile::{Tile, TileMeshes, TilePlugin}, ui::{UiPlugin, button::GameButtonPlugin}, }; @@ -33,7 +33,12 @@ impl Plugin for GamePlugin { } /// Spawn the arena, the tile with its first cue, and the session entity. -fn setup_game(mut commands: Commands, settings: Res) { +fn setup_game( + mut commands: Commands, + settings: Res, + mut meshes: ResMut>, + mut materials: ResMut>, +) { let edge = (config::TILE_SIZE * 3.0) + (config::TILE_SPACING * 4.0); let bounds = Vec2::new(edge, edge); let marker = DespawnOnExit(AppState::Game); @@ -76,34 +81,42 @@ fn setup_game(mut commands: Commands, settings: Res) { )); } + // Pre-build mesh handles for every shape variant. + let tile_meshes = TileMeshes::new(&mut meshes); + // Create engine and generate the first cue up-front so the player // sees a real cue from the start (no phantom round). let mut engine = CueEngine::new( settings.n, settings.position, settings.color, + settings.shape, settings.sound, ); - let (first_pos, first_color, first_sound) = engine.new_cue(); + let first = engine.new_cue(); + + let tile_pos = first.position.unwrap_or_default(); + let tile_color = first.color.unwrap_or_default(); + let tile_shape = first.shape.unwrap_or_default(); + let tile_sound = first.sound.unwrap_or_default(); - let tile_pos = first_pos.unwrap_or_default(); - let tile_color = first_color.unwrap_or_default(); - let tile_sound = first_sound.unwrap_or_default(); + let mesh_handle = tile_meshes.get(&tile_shape); + let mat_handle = materials.add(ColorMaterial::from_color(Color::from(&tile_color))); - // Spawn tile with the first cue already applied. + commands.insert_resource(tile_meshes); + + // Spawn tile with the first cue already applied (mesh-based rendering). // Change-detection will fire on the first frame, playing the sound // and triggering the pop animation. commands.spawn(( Name::new("tile"), Tile, - Sprite { - color: (&tile_color).into(), - custom_size: Some(Vec2::new(config::TILE_SIZE, config::TILE_SIZE)), - ..default() - }, + Mesh2d(mesh_handle), + MeshMaterial2d(mat_handle), Transform::from_translation((&tile_pos).into()), tile_pos, tile_color, + tile_shape, tile_sound, marker.clone(), )); @@ -158,7 +171,6 @@ fn spawn_pause_overlay(mut commands: Commands, asset_server: Res) { ..default() }, BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.6)), - // Render on top of the game UI GlobalZIndex(10), children![( Text::new("PAUSED"), diff --git a/src/game/session/answer.rs b/src/game/session/answer.rs index 75f1da1..a5b796e 100644 --- a/src/game/session/answer.rs +++ b/src/game/session/answer.rs @@ -8,6 +8,7 @@ use bevy::prelude::*; pub struct Answer { pub position: bool, pub color: bool, + pub shape: bool, pub sound: bool, } @@ -16,6 +17,7 @@ impl Answer { info!("reset answer"); self.position = false; self.color = false; + self.shape = false; self.sound = false; } } diff --git a/src/game/session/engine.rs b/src/game/session/engine.rs index 7662d6f..e51ea11 100644 --- a/src/game/session/engine.rs +++ b/src/game/session/engine.rs @@ -1,24 +1,36 @@ use bevy::prelude::*; -use crate::game::tile::{color::TileColor, position::TilePosition, sound::TileSound}; +use crate::game::tile::{ + color::TileColor, position::TilePosition, shape::TileShape, sound::TileSound, +}; use super::cue::CueChain; +/// Generated cues for one round. +pub struct Cue { + pub position: Option, + pub color: Option, + pub shape: Option, + pub sound: Option, +} + /// The n-back game engine. Owns one [`CueChain`] per enabled stimulus channel. #[derive(Component)] pub struct CueEngine { n: usize, pub positions: Option>, pub colors: Option>, + pub shapes: Option>, pub sounds: Option>, } impl CueEngine { - pub fn new(n: usize, position: bool, color: bool, sound: bool) -> Self { + pub fn new(n: usize, position: bool, color: bool, shape: bool, sound: bool) -> Self { CueEngine { n, positions: position.then(|| CueChain::with_n_back(n)), colors: color.then(|| CueChain::with_n_back(n)), + shapes: shape.then(|| CueChain::with_n_back(n)), sounds: sound.then(|| CueChain::with_n_back(n)), } } @@ -27,17 +39,18 @@ impl CueEngine { self.n } - pub fn new_cue(&mut self) -> (Option, Option, Option) { - let new_position = self.positions.as_mut().map(|p| p.next_cue()); - let new_color = self.colors.as_mut().map(|c| c.next_cue()); - let new_sound = self.sounds.as_mut().map(|s| s.next_cue()); - - (new_position, new_color, new_sound) + pub fn new_cue(&mut self) -> Cue { + Cue { + position: self.positions.as_mut().map(|p| p.next_cue()), + color: self.colors.as_mut().map(|c| c.next_cue()), + shape: self.shapes.as_mut().map(|s| s.next_cue()), + sound: self.sounds.as_mut().map(|s| s.next_cue()), + } } } impl Default for CueEngine { fn default() -> Self { - CueEngine::new(2, true, true, true) + CueEngine::new(2, true, true, false, true) } } diff --git a/src/game/session/mod.rs b/src/game/session/mod.rs index 1422a0f..ccd6f11 100644 --- a/src/game/session/mod.rs +++ b/src/game/session/mod.rs @@ -5,7 +5,7 @@ use crate::{ phase::GamePhase, score::{ScoreHistory, ScoreRecord}, settings::GameSettings, - tile::{color::TileColor, position::TilePosition, sound::TileSound}, + tile::{color::TileColor, position::TilePosition, shape::TileShape, sound::TileSound}, }, state::AppState, }; @@ -66,10 +66,15 @@ fn end_of_round_system( ), With, >, - mut tile: Single<(&mut TilePosition, &mut TileColor, &mut TileSound)>, + mut tile: Single<( + &mut TilePosition, + &mut TileColor, + &mut TileShape, + &mut TileSound, + )>, ) { let (engine, round, score, answer, timer) = &mut *session; - let (position, color, sound) = &mut *tile; + let (position, color, shape, sound) = &mut *tile; if !timer.just_finished() { return; @@ -78,19 +83,23 @@ fn end_of_round_system( // Evaluate each cue channel score.evaluate(&engine.positions, answer.position); score.evaluate(&engine.colors, answer.color); + score.evaluate(&engine.shapes, answer.shape); score.evaluate(&engine.sounds, answer.sound); answer.reset(); // Generate next cues - let (new_position, new_color, new_sound) = engine.new_cue(); - if let Some(p) = new_position { + let cue = engine.new_cue(); + if let Some(p) = cue.position { **position = p; } - if let Some(c) = new_color { + if let Some(c) = cue.color { **color = c; } - if let Some(s) = new_sound { + if let Some(s) = cue.shape { + **shape = s; + } + if let Some(s) = cue.sound { **sound = s; } diff --git a/src/game/settings.rs b/src/game/settings.rs index 8ac7413..d84c4f8 100644 --- a/src/game/settings.rs +++ b/src/game/settings.rs @@ -7,6 +7,7 @@ pub struct GameSettings { pub round_time: f32, pub position: bool, pub color: bool, + pub shape: bool, pub sound: bool, } @@ -24,6 +25,7 @@ impl Default for GameSettings { round_time: 3.0, position: true, color: true, + shape: false, sound: true, } } diff --git a/src/game/tile/mod.rs b/src/game/tile/mod.rs index 7359b0b..841a2db 100644 --- a/src/game/tile/mod.rs +++ b/src/game/tile/mod.rs @@ -1,12 +1,13 @@ use bevy::prelude::*; use bevy_kira_audio::prelude::*; -use crate::{asset::AudioAssets, state::AppState}; +use crate::{asset::AudioAssets, config, state::AppState}; -use self::{color::TileColor, position::TilePosition, sound::TileSound}; +use self::{color::TileColor, position::TilePosition, shape::TileShape, sound::TileSound}; pub mod color; pub mod position; +pub mod shape; pub mod sound; pub struct TilePlugin; @@ -18,6 +19,7 @@ impl Plugin for TilePlugin { ( tile_position_system, tile_color_system, + tile_shape_system, tile_sound_system, tile_pop_animation_system, ) @@ -42,9 +44,42 @@ impl Default for TilePopAnimation { /// Marker component for the game tile. Required components are auto-inserted. #[derive(Component, Default)] -#[require(TilePopAnimation, TilePosition, TileColor, TileSound)] +#[require(TilePopAnimation, TilePosition, TileColor, TileShape, TileSound)] pub struct Tile; +/// Pre-computed mesh handles for each tile shape. +#[derive(Resource)] +pub struct TileMeshes { + pub circle: Handle, + pub triangle: Handle, + pub square: Handle, + pub pentagon: Handle, + pub hexagon: Handle, +} + +impl TileMeshes { + pub fn new(meshes: &mut Assets) -> Self { + let r = config::TILE_SIZE / 2.0; + TileMeshes { + circle: meshes.add(Circle::new(r)), + triangle: meshes.add(RegularPolygon::new(r, 3)), + square: meshes.add(RegularPolygon::new(r, 4)), + pentagon: meshes.add(RegularPolygon::new(r, 5)), + hexagon: meshes.add(RegularPolygon::new(r, 6)), + } + } + + pub fn get(&self, shape: &TileShape) -> Handle { + match shape { + TileShape::Circle => self.circle.clone(), + TileShape::Triangle => self.triangle.clone(), + TileShape::Square | TileShape::None => self.square.clone(), + TileShape::Pentagon => self.pentagon.clone(), + TileShape::Hexagon => self.hexagon.clone(), + } + } +} + /// Update tile state every time the position changes. pub fn tile_position_system( mut tile: Single<(&mut Transform, &mut TilePopAnimation, &TilePosition), Changed>, @@ -52,7 +87,6 @@ pub fn tile_position_system( let (transform, anim, position) = &mut *tile; info!(?position, "tile updated"); transform.translation = (*position).into(); - // Reset and start the pop animation anim.timer.reset(); } @@ -71,11 +105,27 @@ pub fn tile_pop_animation_system( } } -/// Update tile state every time the color changes. -pub fn tile_color_system(mut tile: Single<(&mut Sprite, &TileColor), Changed>) { - let (sprite, color) = &mut *tile; - info!(?color, "tile updated"); - sprite.color = (*color).into(); +/// Update tile material color when the color cue changes. +pub fn tile_color_system( + mut materials: ResMut>, + tile: Single<(&MeshMaterial2d, &TileColor), Changed>, +) { + let (mat_handle, color) = *tile; + info!(?color, "tile color updated"); + if let Some(material) = materials.get_mut(mat_handle) { + material.color = color.into(); + } +} + +/// Swap the tile mesh when the shape cue changes. +pub fn tile_shape_system( + tile_meshes: Res, + mut tile: Single<(&mut Mesh2d, &mut TilePopAnimation, &TileShape), Changed>, +) { + let (mesh, anim, shape) = &mut *tile; + info!(?shape, "tile shape updated"); + mesh.0 = tile_meshes.get(shape); + anim.timer.reset(); } /// Update tile state every time the sound changes. diff --git a/src/game/tile/shape.rs b/src/game/tile/shape.rs new file mode 100644 index 0000000..16ac4f5 --- /dev/null +++ b/src/game/tile/shape.rs @@ -0,0 +1,28 @@ +use bevy::prelude::*; +use rand::{ + Rng, RngExt, + distr::{Distribution, StandardUniform}, +}; + +#[derive(Component, Clone, Debug, Default, PartialEq)] +pub enum TileShape { + Circle, + Triangle, + Square, + Pentagon, + Hexagon, + #[default] + None, +} + +impl Distribution for StandardUniform { + fn sample(&self, rng: &mut R) -> TileShape { + match rng.random_range(0..=4) { + 0 => TileShape::Circle, + 1 => TileShape::Triangle, + 2 => TileShape::Square, + 3 => TileShape::Pentagon, + _ => TileShape::Hexagon, + } + } +} diff --git a/src/game/ui/button.rs b/src/game/ui/button.rs index 67c5baa..4e45d42 100644 --- a/src/game/ui/button.rs +++ b/src/game/ui/button.rs @@ -20,8 +20,9 @@ pub struct Shortcut(pub KeyCode); #[derive(Component)] pub enum ButtonAction { SamePosition, - SameSound, SameColor, + SameShape, + SameSound, } /// Returns a game button bundle as a tuple. @@ -86,8 +87,9 @@ fn button_system( *border_color = BorderColor::all(BUTTON_BORDER_COLOR); match action { ButtonAction::SamePosition => answer.position = true, - ButtonAction::SameSound => answer.sound = true, ButtonAction::SameColor => answer.color = true, + ButtonAction::SameShape => answer.shape = true, + ButtonAction::SameSound => answer.sound = true, } } Interaction::Hovered => { @@ -124,8 +126,9 @@ fn button_shortcut_system( if keyboard_input.just_pressed(shortcut.0) { match action { ButtonAction::SamePosition => answer.position = true, - ButtonAction::SameSound => answer.sound = true, ButtonAction::SameColor => answer.color = true, + ButtonAction::SameShape => answer.shape = true, + ButtonAction::SameSound => answer.sound = true, } } diff --git a/src/game/ui/mod.rs b/src/game/ui/mod.rs index 9e1ef39..f1a1558 100644 --- a/src/game/ui/mod.rs +++ b/src/game/ui/mod.rs @@ -86,16 +86,22 @@ pub fn game_ui( ButtonAction::SamePosition ), game_button( - "Sound (S)", + "Color (S)", font.clone(), KeyCode::KeyS, - ButtonAction::SameSound + ButtonAction::SameColor ), game_button( - "Color (D)", + "Shape (D)", font.clone(), KeyCode::KeyD, - ButtonAction::SameColor + ButtonAction::SameShape + ), + game_button( + "Sound (F)", + font.clone(), + KeyCode::KeyF, + ButtonAction::SameSound ), ], ), diff --git a/src/menu/checkbox.rs b/src/menu/checkbox.rs index fbadf7e..ea7f571 100644 --- a/src/menu/checkbox.rs +++ b/src/menu/checkbox.rs @@ -15,8 +15,9 @@ pub struct Checkbox { #[derive(Component)] pub enum CheckboxAction { Position, - Sound, Color, + Shape, + Sound, } type CheckboxQuery<'w> = ( @@ -42,8 +43,9 @@ pub fn checkbox_system( match action { CheckboxAction::Position => settings.position = checkbox.checked, - CheckboxAction::Sound => settings.sound = checkbox.checked, CheckboxAction::Color => settings.color = checkbox.checked, + CheckboxAction::Shape => settings.shape = checkbox.checked, + CheckboxAction::Sound => settings.sound = checkbox.checked, } } } diff --git a/src/menu/ui.rs b/src/menu/ui.rs index 4007993..0550932 100644 --- a/src/menu/ui.rs +++ b/src/menu/ui.rs @@ -83,6 +83,7 @@ pub fn menu_ui( GridTrack::min_content(), GridTrack::min_content(), GridTrack::min_content(), + GridTrack::min_content(), ], row_gap: px(12), column_gap: px(12), @@ -93,10 +94,12 @@ pub fn menu_ui( children![ checkbox(settings.position, CheckboxAction::Position), cue_label("Position", font.clone()), - checkbox(settings.sound, CheckboxAction::Sound), - cue_label("Sound", font.clone()), checkbox(settings.color, CheckboxAction::Color), cue_label("Color", font.clone()), + checkbox(settings.shape, CheckboxAction::Shape), + cue_label("Shape", font.clone()), + checkbox(settings.sound, CheckboxAction::Sound), + cue_label("Sound", font.clone()), ], ), // Play button From e5c1f954eda0e6ffb58f218a8fb84b19244f0279 Mon Sep 17 00:00:00 2001 From: Manuel Mauro Date: Sat, 4 Apr 2026 23:33:45 +0200 Subject: [PATCH 2/4] feat: enable shape cues by default --- src/game/session/engine.rs | 2 +- src/game/settings.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/session/engine.rs b/src/game/session/engine.rs index e51ea11..24d8a87 100644 --- a/src/game/session/engine.rs +++ b/src/game/session/engine.rs @@ -51,6 +51,6 @@ impl CueEngine { impl Default for CueEngine { fn default() -> Self { - CueEngine::new(2, true, true, false, true) + CueEngine::new(2, true, true, true, true) } } diff --git a/src/game/settings.rs b/src/game/settings.rs index d84c4f8..781c31b 100644 --- a/src/game/settings.rs +++ b/src/game/settings.rs @@ -25,7 +25,7 @@ impl Default for GameSettings { round_time: 3.0, position: true, color: true, - shape: false, + shape: true, sound: true, } } From c48b3bd1c6f2f804ca05d820319e2906f957ed25 Mon Sep 17 00:00:00 2001 From: Manuel Mauro Date: Sat, 4 Apr 2026 23:38:49 +0200 Subject: [PATCH 3/4] fix: rotate square mesh so edges are flat, not diamond-oriented MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RegularPolygon places its first vertex at the top, producing a diamond for 4 sides. Rotate the mesh by π/4 to get a flat-edged square. --- src/game/tile/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/game/tile/mod.rs b/src/game/tile/mod.rs index 841a2db..172e190 100644 --- a/src/game/tile/mod.rs +++ b/src/game/tile/mod.rs @@ -63,7 +63,9 @@ impl TileMeshes { TileMeshes { circle: meshes.add(Circle::new(r)), triangle: meshes.add(RegularPolygon::new(r, 3)), - square: meshes.add(RegularPolygon::new(r, 4)), + square: meshes.add(Mesh::from(RegularPolygon::new(r, 4)).rotated_by( + Quat::from_rotation_z(std::f32::consts::FRAC_PI_4), + )), pentagon: meshes.add(RegularPolygon::new(r, 5)), hexagon: meshes.add(RegularPolygon::new(r, 6)), } From d02e364f6088d4b8bccebf3b1dfe27847a76b690 Mon Sep 17 00:00:00 2001 From: Manuel Mauro Date: Sat, 4 Apr 2026 23:41:49 +0200 Subject: [PATCH 4/4] fix: compute per-shape circumradius so all shapes fill the tile cell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RegularPolygon takes a circumradius (center→vertex), but each shape's bounding box relates differently to that radius. Previously all shapes used TILE_SIZE/2, making the square ~70% of its expected size. Now each shape gets its own circumradius computed so the largest bounding-box dimension equals TILE_SIZE: Circle r = size/2 (bbox = 2r × 2r) Triangle r = size/√3 (bbox width = r√3) Square r = size/√2 (bbox = r√2 × r√2 after π/4 rotation) Pentagon r = size/(2·sin72°) (bbox width = 2r·sin72°) Hexagon r = size/2 (bbox height = 2r) --- src/game/tile/mod.rs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/game/tile/mod.rs b/src/game/tile/mod.rs index 172e190..7259034 100644 --- a/src/game/tile/mod.rs +++ b/src/game/tile/mod.rs @@ -58,16 +58,37 @@ pub struct TileMeshes { } impl TileMeshes { + /// Build meshes sized so every shape fills a `TILE_SIZE × TILE_SIZE` box. + /// + /// `RegularPolygon::new(r, n)` takes the *circumradius* (center → vertex). + /// Each shape's bounding box depends on its geometry, so we compute a + /// per-shape circumradius that makes the largest dimension equal to + /// `TILE_SIZE`. pub fn new(meshes: &mut Assets) -> Self { - let r = config::TILE_SIZE / 2.0; + use std::f32::consts::{FRAC_PI_4, TAU}; + + let size = config::TILE_SIZE; + + // Circle: bbox = 2r × 2r → r = size / 2 + // Triangle: bbox = r√3 × 1.5r → r = size / √3 (width-limited) + // Square: bbox = r√2 × r√2 (rot.) → r = size / √2 + // Pentagon: bbox = 2r·sin(72°) × … → r = size / (2·sin(72°)) + // Hexagon: bbox = r√3 × 2r → r = size / 2 (height-limited) + let r_circle = size / 2.0; + let r_triangle = size / 3_f32.sqrt(); + let r_square = size / 2_f32.sqrt(); + let r_pentagon = size / (2.0 * (TAU / 5.0).sin()); + let r_hexagon = size / 2.0; + TileMeshes { - circle: meshes.add(Circle::new(r)), - triangle: meshes.add(RegularPolygon::new(r, 3)), - square: meshes.add(Mesh::from(RegularPolygon::new(r, 4)).rotated_by( - Quat::from_rotation_z(std::f32::consts::FRAC_PI_4), - )), - pentagon: meshes.add(RegularPolygon::new(r, 5)), - hexagon: meshes.add(RegularPolygon::new(r, 6)), + circle: meshes.add(Circle::new(r_circle)), + triangle: meshes.add(RegularPolygon::new(r_triangle, 3)), + square: meshes.add( + Mesh::from(RegularPolygon::new(r_square, 4)) + .rotated_by(Quat::from_rotation_z(FRAC_PI_4)), + ), + pentagon: meshes.add(RegularPolygon::new(r_pentagon, 5)), + hexagon: meshes.add(RegularPolygon::new(r_hexagon, 6)), } }