From 314e604067039ccfff9e602bf438624807af2905 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Thu, 11 Jun 2026 23:23:38 -0700 Subject: [PATCH] prototypes/life: Adding colors and sound, broken but saving my place --- prototypes/src/bin/life.rs | 216 +++++++++++++++++++++++++++++-------- 1 file changed, 174 insertions(+), 42 deletions(-) diff --git a/prototypes/src/bin/life.rs b/prototypes/src/bin/life.rs index 30d4ccd..e7bf273 100644 --- a/prototypes/src/bin/life.rs +++ b/prototypes/src/bin/life.rs @@ -1,11 +1,12 @@ // TODO: Rearchitected so it's Cell and Coordiantes not Cell { position { coordiantes } } -use bevy::picking::hover::Hovered; +use std::time::Duration; + +use bevy::{picking::hover::Hovered, platform::collections::HashMap}; use engine::*; -const GREY_TILE: &str = "tileGrey_01.png"; -const BLACK_TILE: &str = "tileBlack_01.png"; -const BACKGROUND: &str = "texture_08.png"; +const TILE: &str = "tileGrey_01.png"; +const FRAME_DURATION: f32 = 0.5; fn main() { let file_path = { @@ -24,10 +25,14 @@ fn main() { ..default() })) .init_resource::() + .init_resource::() .init_state::() .add_message::() .add_message::() .add_systems(Startup, setup) + .add_systems(Startup, load_audio_tones) + .add_systems(Startup, load_materials) + .add_systems(Startup, load_meshes) .add_systems( Update, update_grid_position.run_if(on_message::), @@ -51,7 +56,7 @@ fn main() { Update, simulation_step .run_if(in_state(SimulationState::Run)) - .run_if(cooldown_secs(0.5)), + .run_if(cooldown_secs(FRAME_DURATION)), ) .add_systems( Update, @@ -118,7 +123,7 @@ fn on_add_ui_button( }, BackgroundColor(GRAY.into()), ImageNode { - image: server.load(GREY_TILE), + image: server.load(TILE), color: WHITE.with_alpha(0.5).into(), ..default() }, @@ -202,17 +207,6 @@ fn setup(mut commands: Commands) { _ => todo!(), }); } - - // Spawn Coordiantes - commands - .spawn((Transform::default(), Visibility::Visible)) - .with_children(|parent| { - for x in -10..10 { - for y in -10..10 { - parent.spawn(Coordinates { x, y }); - } - } - }); } #[derive(Debug, Default, Clone, PartialEq, Hash, Eq, Component, Resource)] @@ -264,6 +258,37 @@ impl Coordinates { fn as_translation(&self) -> Vec3 { (Vec2::new(self.x as f32, self.y as f32) * SCALE).extend(0.0) } + + fn as_color(&self) -> Color { + // hue (0..360.0) evenly spaced x in -2..4 + // chroma (0..1) evenly spaced y in -2..5 + let hue = (360.0 / 5.0) * ((self.x + 2) % 5) as f32; + let chroma = (1.0 / 5.0) * ((self.y + 2) % 5) as f32; + let lightness = (1.0 / 5.0) * ((self.y + 2) % 5) as f32; + Color::Oklcha(Oklcha { + hue, + chroma, + lightness, + ..default() + }) + } + + + fn as_cell_pitch(&self) -> CellPitch { + let note = match (self.x % 5) - 2 { + -2 => Note::A, + -1 => Note::B, + 0 => Note::CSharp, + 1 => Note::E, + 2 => Note::FSharp, + _ => panic!("This shouldn't happen!"), + }; + let octave = (self.y % 5) - 2; + + CellPitch { + note, octave + } + } } fn on_add_coordinates( @@ -271,37 +296,30 @@ fn on_add_coordinates( coordinates: Query<&Coordinates>, cells: Query<&Cell>, mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, - server: ResMut, + cell_assets: Res, ) { - // TODO: Reuse the same mesh - let mesh = { - let mesh_h = meshes.add(Rectangle::new(SCALE, SCALE)); - Mesh2d(mesh_h) - }; + let c = coordinates.get(trigger.entity).unwrap(); - // TODO: Resuse the same material - let mat = { - let image: Handle = if cells.contains(trigger.entity) { - server.load(BLACK_TILE) - } else { - server.load(BACKGROUND) - }; - let mat_h = materials.add(image); - MeshMaterial2d(mat_h) + // based on coordinates, get material, mesh, and tone from cell_assets + if cells.contains(trigger.entity) { + let mat_h = cell_assets.materials.get(c).unwrap(); + let mesh_mat_2d = MeshMaterial2d(mat_h.clone()); + commands.entity(trigger.entity).insert(mesh_mat_2d); + + let mesh_2d = Mesh2d(cell_assets.mesh.clone()); + commands.entity(trigger.entity).insert(mesh_2d); }; - let t = { - let c = coordinates.get(trigger.entity).unwrap(); - if cells.contains(trigger.entity) { - Transform::from_translation(c.as_translation() + Vec3::new(0.0, 0.0, 1.0)) - } else { - Transform::from_translation(c.as_translation()) - } + let t = if cells.contains(trigger.entity) { + Transform::from_translation(c.as_translation() + Vec3::new(0.0, 0.0, 1.0)) + } else { + Transform::from_translation(c.as_translation()) }; + commands.entity(trigger.entity).insert(t); - commands.entity(trigger.entity).insert((mesh, mat, t)); + // FIXME!!! + let p = cell_assets.pitches.get(&c.as_cell_pitch()).unwrap(); + commands.entity(trigger.entity).insert((AudioPlayer(p.clone()), PlaybackSettings::LOOP)); } /// When the cursor moves, update the Coordiantes @@ -510,3 +528,117 @@ fn visualize_ui_state( // Scale grid with background? // Pan camera: click and drag + +#[derive(Resource, Default)] +struct CellAssets { + pitches: HashMap>, + materials: HashMap>, + mesh: Handle, +} + +#[derive(Hash, PartialEq, Eq, Debug, Clone)] +enum Note { + A, + B, + CSharp, + E, + FSharp, +} + +impl Note { + fn as_frequency(&self) -> f32 { + match self { + Note::A => 440.0, + Note::B => 493.88, + Note::CSharp => 553.37, + Note::E => 659.25, + Note::FSharp => 698.46, + } + } +} + +#[derive(Hash, PartialEq, Eq, Debug)] +struct CellPitch { + note: Note, + octave: isize, +} + +impl CellPitch { + /// Pitches: + /// A -440 Hz + /// B - 493.88 Hz + /// C# - 554.37 Hz + /// E - 659.25 Hz + /// F# - 698.46 Hz + fn to_frequency(&self) -> f32 { + let base_note = self.note.as_frequency(); + match self.octave { + // Negative octave values -> lower notes + ..=-1 => base_note / (self.octave as f32), + 0 => base_note, + // Positive octave values -> higher notes + 1.. => base_note * (self.octave as f32), + } + } +} + +fn load_audio_tones(mut pitches: ResMut>, mut cell_assets: ResMut) { + let duration = Duration::from_secs_f32(FRAME_DURATION); + let notes = [Note::A, Note::B, Note::CSharp, Note::E, Note::FSharp]; + let octaves = [-2, -1, 0, 1, 2]; + + notes.iter().for_each(|note| { + octaves.iter().for_each(|octave| { + let cell_pitch = CellPitch { + note: note.clone(), + octave: *octave, + }; + let frequency = cell_pitch.to_frequency(); + let handle = pitches.add(Pitch { + frequency, + duration, + }); + cell_assets.pitches.insert(cell_pitch, handle); + }); + }); +} + +fn load_materials( + server: ResMut, + mut cell_assets: ResMut, + mut materials: ResMut>, +) { + let vertical = [-2, -1, 0, 1, 2]; + let horizontal = [-2, -1, 0, 1, 2]; + let texture = server.load(TILE); + + vertical.iter().for_each(|&x| { + horizontal.iter().for_each(|&y| { + let c = Coordinates { x, y }; + let color = c.as_color(); + cell_assets.materials.insert( + c, + materials.add(ColorMaterial { + texture: Some(texture.clone()), + color, + ..default() + }), + ); + }); + }); +} + +fn load_meshes(mut cell_assets: ResMut, mut meshes: ResMut>) { + cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE)); +} + +fn play_audio_tone(cell_assets: Res, mut commands: Commands) { + let handle = cell_assets + .pitches + .get(&CellPitch { + note: Note::A, + octave: 0, + }) + .unwrap(); + commands.spawn((AudioPlayer(handle.clone()), PlaybackSettings::LOOP)); +}