|
|
|
@ -1,11 +1,12 @@
|
|
|
|
// TODO: Rearchitected so it's Cell and Coordiantes not Cell { position { coordiantes } }
|
|
|
|
// 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::*;
|
|
|
|
use engine::*;
|
|
|
|
|
|
|
|
|
|
|
|
const GREY_TILE: &str = "tileGrey_01.png";
|
|
|
|
const TILE: &str = "tileGrey_01.png";
|
|
|
|
const BLACK_TILE: &str = "tileBlack_01.png";
|
|
|
|
const FRAME_DURATION: f32 = 0.5;
|
|
|
|
const BACKGROUND: &str = "texture_08.png";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn main() {
|
|
|
|
fn main() {
|
|
|
|
let file_path = {
|
|
|
|
let file_path = {
|
|
|
|
@ -24,10 +25,14 @@ fn main() {
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
}))
|
|
|
|
}))
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
|
|
|
|
.init_resource::<CellAssets>()
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_message::<SimulationState>()
|
|
|
|
.add_message::<SimulationState>()
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
|
|
|
|
.add_systems(Startup, load_audio_tones)
|
|
|
|
|
|
|
|
.add_systems(Startup, load_materials)
|
|
|
|
|
|
|
|
.add_systems(Startup, load_meshes)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
update_grid_position.run_if(on_message::<CursorMoved>),
|
|
|
|
@ -51,7 +56,7 @@ fn main() {
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
simulation_step
|
|
|
|
simulation_step
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
.run_if(cooldown_secs(0.5)),
|
|
|
|
.run_if(cooldown_secs(FRAME_DURATION)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
@ -118,7 +123,7 @@ fn on_add_ui_button(
|
|
|
|
},
|
|
|
|
},
|
|
|
|
BackgroundColor(GRAY.into()),
|
|
|
|
BackgroundColor(GRAY.into()),
|
|
|
|
ImageNode {
|
|
|
|
ImageNode {
|
|
|
|
image: server.load(GREY_TILE),
|
|
|
|
image: server.load(TILE),
|
|
|
|
color: WHITE.with_alpha(0.5).into(),
|
|
|
|
color: WHITE.with_alpha(0.5).into(),
|
|
|
|
..default()
|
|
|
|
..default()
|
|
|
|
},
|
|
|
|
},
|
|
|
|
@ -202,17 +207,6 @@ fn setup(mut commands: Commands) {
|
|
|
|
_ => todo!(),
|
|
|
|
_ => 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)]
|
|
|
|
#[derive(Debug, Default, Clone, PartialEq, Hash, Eq, Component, Resource)]
|
|
|
|
@ -264,6 +258,37 @@ impl Coordinates {
|
|
|
|
fn as_translation(&self) -> Vec3 {
|
|
|
|
fn as_translation(&self) -> Vec3 {
|
|
|
|
(Vec2::new(self.x as f32, self.y as f32) * SCALE).extend(0.0)
|
|
|
|
(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(
|
|
|
|
fn on_add_coordinates(
|
|
|
|
@ -271,37 +296,30 @@ fn on_add_coordinates(
|
|
|
|
coordinates: Query<&Coordinates>,
|
|
|
|
coordinates: Query<&Coordinates>,
|
|
|
|
cells: Query<&Cell>,
|
|
|
|
cells: Query<&Cell>,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
cell_assets: Res<CellAssets>,
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
|
|
|
|
server: ResMut<AssetServer>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
// TODO: Reuse the same mesh
|
|
|
|
let c = coordinates.get(trigger.entity).unwrap();
|
|
|
|
let mesh = {
|
|
|
|
|
|
|
|
let mesh_h = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
|
|
|
|
Mesh2d(mesh_h)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: Resuse the same material
|
|
|
|
// based on coordinates, get material, mesh, and tone from cell_assets
|
|
|
|
let mat = {
|
|
|
|
if cells.contains(trigger.entity) {
|
|
|
|
let image: Handle<Image> = if cells.contains(trigger.entity) {
|
|
|
|
let mat_h = cell_assets.materials.get(c).unwrap();
|
|
|
|
server.load(BLACK_TILE)
|
|
|
|
let mesh_mat_2d = MeshMaterial2d(mat_h.clone());
|
|
|
|
} else {
|
|
|
|
commands.entity(trigger.entity).insert(mesh_mat_2d);
|
|
|
|
server.load(BACKGROUND)
|
|
|
|
|
|
|
|
};
|
|
|
|
let mesh_2d = Mesh2d(cell_assets.mesh.clone());
|
|
|
|
let mat_h = materials.add(image);
|
|
|
|
commands.entity(trigger.entity).insert(mesh_2d);
|
|
|
|
MeshMaterial2d(mat_h)
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let t = {
|
|
|
|
let t = if cells.contains(trigger.entity) {
|
|
|
|
let c = coordinates.get(trigger.entity).unwrap();
|
|
|
|
Transform::from_translation(c.as_translation() + Vec3::new(0.0, 0.0, 1.0))
|
|
|
|
if cells.contains(trigger.entity) {
|
|
|
|
} else {
|
|
|
|
Transform::from_translation(c.as_translation() + Vec3::new(0.0, 0.0, 1.0))
|
|
|
|
Transform::from_translation(c.as_translation())
|
|
|
|
} 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
|
|
|
|
/// When the cursor moves, update the Coordiantes
|
|
|
|
@ -510,3 +528,117 @@ fn visualize_ui_state(
|
|
|
|
|
|
|
|
|
|
|
|
// Scale grid with background?
|
|
|
|
// Scale grid with background?
|
|
|
|
// Pan camera: click and drag
|
|
|
|
// Pan camera: click and drag
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
|
|
|
|
struct CellAssets {
|
|
|
|
|
|
|
|
pitches: HashMap<CellPitch, Handle<Pitch>>,
|
|
|
|
|
|
|
|
materials: HashMap<Coordinates, Handle<ColorMaterial>>,
|
|
|
|
|
|
|
|
mesh: Handle<Mesh>,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[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<Assets<Pitch>>, mut cell_assets: ResMut<CellAssets>) {
|
|
|
|
|
|
|
|
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<AssetServer>,
|
|
|
|
|
|
|
|
mut cell_assets: ResMut<CellAssets>,
|
|
|
|
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
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<CellAssets>, mut meshes: ResMut<Assets<Mesh>>) {
|
|
|
|
|
|
|
|
cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn play_audio_tone(cell_assets: Res<CellAssets>, mut commands: Commands) {
|
|
|
|
|
|
|
|
let handle = cell_assets
|
|
|
|
|
|
|
|
.pitches
|
|
|
|
|
|
|
|
.get(&CellPitch {
|
|
|
|
|
|
|
|
note: Note::A,
|
|
|
|
|
|
|
|
octave: 0,
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
commands.spawn((AudioPlayer(handle.clone()), PlaybackSettings::LOOP));
|
|
|
|
|
|
|
|
}
|
|
|
|
|