|
|
|
|
@ -2,7 +2,10 @@
|
|
|
|
|
|
|
|
|
|
use std::time::Duration;
|
|
|
|
|
|
|
|
|
|
use bevy::{audio::Volume, picking::hover::Hovered, platform::collections::HashMap};
|
|
|
|
|
use bevy::{
|
|
|
|
|
audio::Volume, input::mouse::{AccumulatedMouseMotion, MouseMotion}, picking::hover::Hovered,
|
|
|
|
|
platform::collections::HashMap,
|
|
|
|
|
};
|
|
|
|
|
use engine::*;
|
|
|
|
|
|
|
|
|
|
const TILE: &str = "tileGrey_01.png";
|
|
|
|
|
@ -30,7 +33,7 @@ fn main() {
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
modulate_volume // TODO: .run_if(any_component_added::<AudioSink>)
|
|
|
|
|
modulate_volume, // TODO: .run_if(any_component_added::<AudioSink>)
|
|
|
|
|
)
|
|
|
|
|
.add_systems(Update, grid_gizmo)
|
|
|
|
|
.add_systems(
|
|
|
|
|
@ -53,10 +56,6 @@ fn main() {
|
|
|
|
|
.run_if(in_state(SimulationState::Run))
|
|
|
|
|
.run_if(cooldown_secs(FRAME_DURATION)),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
start_audio_synced.run_if(cooldown_secs(FRAME_DURATION))
|
|
|
|
|
)
|
|
|
|
|
.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
simulation_step.run_if(on_message::<SimulationState>),
|
|
|
|
|
@ -66,6 +65,7 @@ fn main() {
|
|
|
|
|
visualize_ui_state.run_if(on_message::<Pointer<Click>>),
|
|
|
|
|
)
|
|
|
|
|
.add_systems(Update, cell_lifecycle.run_if(on_message::<Lifecycle>))
|
|
|
|
|
.add_systems(Update, mouse_pan.run_if(on_message::<MouseMotion>))
|
|
|
|
|
.add_observer(on_add_ui_button)
|
|
|
|
|
.add_observer(on_add_coordinates)
|
|
|
|
|
.run();
|
|
|
|
|
@ -166,11 +166,14 @@ fn setup(mut commands: Commands) {
|
|
|
|
|
))
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
parent.spawn(UiButton::Power).observe(shutdown);
|
|
|
|
|
// TODO: Play/Pause Toggle
|
|
|
|
|
parent.spawn(UiButton::Pause).observe(pause_sim);
|
|
|
|
|
parent.spawn(UiButton::Step).observe(step_sim);
|
|
|
|
|
parent.spawn(UiButton::Play).observe(play_sim);
|
|
|
|
|
parent.spawn(UiButton::Step).observe(step_sim);
|
|
|
|
|
parent.spawn(UiButton::ZoomOut).observe(zoom_out);
|
|
|
|
|
parent.spawn(UiButton::ZoomIn).observe(zoom_in);
|
|
|
|
|
// TODO: Reset button
|
|
|
|
|
// TODO: Mute/Unmute Toggle
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fn shutdown(_trigger: On<Pointer<Press>>, mut writer: MessageWriter<AppExit>) {
|
|
|
|
|
@ -261,7 +264,7 @@ impl Coordinates {
|
|
|
|
|
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 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 {
|
|
|
|
|
@ -272,7 +275,6 @@ impl Coordinates {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn as_cell_pitch(&self) -> CellPitch {
|
|
|
|
|
debug!("Generating cell pitch for {:?}", self);
|
|
|
|
|
|
|
|
|
|
@ -288,11 +290,15 @@ impl Coordinates {
|
|
|
|
|
let octave = ((self.y + 2).abs() % 5) - 2;
|
|
|
|
|
debug_assert!((-2..=2).contains(&octave));
|
|
|
|
|
|
|
|
|
|
debug!("\t{:?}", CellPitch { note: note.clone(), octave });
|
|
|
|
|
debug!(
|
|
|
|
|
"\t{:?}",
|
|
|
|
|
CellPitch {
|
|
|
|
|
note: note.clone(),
|
|
|
|
|
octave
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CellPitch {
|
|
|
|
|
note, octave
|
|
|
|
|
}
|
|
|
|
|
CellPitch { note, octave }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -324,17 +330,9 @@ fn on_add_coordinates(
|
|
|
|
|
commands.entity(trigger.entity).insert(t);
|
|
|
|
|
|
|
|
|
|
let p = cell_assets.pitches.get(&c.as_cell_pitch()).unwrap();
|
|
|
|
|
commands.entity(trigger.entity).insert((AudioPlayer(p.clone()), PlaybackSettings::ONCE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// All audio starts paused so we can keep them in sync
|
|
|
|
|
/// This starts audios when the system is triggered, which is on a timer
|
|
|
|
|
fn start_audio_synced(query: Query<&AudioSink, With<Cell>>) {
|
|
|
|
|
query.iter().for_each(|sink| {
|
|
|
|
|
if sink.is_paused() {
|
|
|
|
|
sink.play()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
commands
|
|
|
|
|
.entity(trigger.entity)
|
|
|
|
|
.insert((AudioPlayer(p.clone()), PlaybackSettings::ONCE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// When the cursor moves, update the Coordiantes
|
|
|
|
|
@ -603,6 +601,10 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
|
|
|
|
|
};
|
|
|
|
|
let frequency = cell_pitch.to_frequency();
|
|
|
|
|
let duration = Duration::from_secs(100);
|
|
|
|
|
debug!(
|
|
|
|
|
"Adding Pitch frequency: {:?} duration: {:?}",
|
|
|
|
|
frequency, duration
|
|
|
|
|
);
|
|
|
|
|
let handle = pitches.add(Pitch {
|
|
|
|
|
frequency,
|
|
|
|
|
duration,
|
|
|
|
|
@ -613,6 +615,7 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
|
|
|
|
|
debug_assert!(cell_assets.pitches.iter().len() == 25);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Why does audio only play when running simulation?
|
|
|
|
|
fn modulate_volume(mut gv: ResMut<GlobalVolume>, active: Query<Entity, With<AudioSink>>) {
|
|
|
|
|
let ratio = 1.0 / active.count() as f32;
|
|
|
|
|
gv.volume = Volume::Linear(ratio);
|
|
|
|
|
@ -630,11 +633,15 @@ fn load_materials(
|
|
|
|
|
vertical.iter().for_each(|&x| {
|
|
|
|
|
horizontal.iter().for_each(|&y| {
|
|
|
|
|
let c = Coordinates { x, y };
|
|
|
|
|
let pitch = c.as_cell_pitch();
|
|
|
|
|
let pitch = c.as_cell_pitch();
|
|
|
|
|
let handle = {
|
|
|
|
|
let color = c.as_color();
|
|
|
|
|
let texture = Some(tile_handle.clone());
|
|
|
|
|
let color_material = ColorMaterial { color, texture, ..default() };
|
|
|
|
|
let color_material = ColorMaterial {
|
|
|
|
|
color,
|
|
|
|
|
texture,
|
|
|
|
|
..default()
|
|
|
|
|
};
|
|
|
|
|
materials.add(color_material)
|
|
|
|
|
};
|
|
|
|
|
cell_assets.materials.insert(pitch, handle);
|
|
|
|
|
@ -646,3 +653,35 @@ fn load_materials(
|
|
|
|
|
fn load_meshes(mut cell_assets: ResMut<CellAssets>, mut meshes: ResMut<Assets<Mesh>>) {
|
|
|
|
|
cell_assets.mesh = meshes.add(Rectangle::new(SCALE, SCALE));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: Do not place piece if panning
|
|
|
|
|
// TODO: Tie cursor to real world space, not pixels
|
|
|
|
|
fn mouse_pan(
|
|
|
|
|
mouse_motion: Res<AccumulatedMouseMotion>,
|
|
|
|
|
button: Res<ButtonInput<MouseButton>>,
|
|
|
|
|
mut camera: Query<(&Camera, &mut Transform, &GlobalTransform), With<Camera2d>>,
|
|
|
|
|
window: Query<&Window>,
|
|
|
|
|
) {
|
|
|
|
|
if button.pressed(MouseButton::Left) {
|
|
|
|
|
let delta = mouse_motion.delta;
|
|
|
|
|
camera.iter_mut().for_each(|(c, mut t, gt)| {
|
|
|
|
|
window.iter().for_each(|w| {
|
|
|
|
|
let Some(curr_cursor_position) = w.cursor_position() else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let old_cursor_position = curr_cursor_position - delta / w.scale_factor();
|
|
|
|
|
|
|
|
|
|
let Ok(curr_world) = c.viewport_to_world_2d(gt, curr_cursor_position) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let Ok(old_world) = c.viewport_to_world_2d(gt, old_cursor_position) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let delta_world = old_world - curr_world;
|
|
|
|
|
t.translation.x += delta_world.x / 7.0;
|
|
|
|
|
t.translation.y += delta_world.y / 7.0;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|