use bevy::{platform::collections::HashSet, prelude::*};
use rand::{seq::SliceRandom, thread_rng};
use crate::{
core::{
parameters::{Point, SimulationSettings, Stick},
spawner::{SpawnBuffer, SpawnRequest, spawner},
},
plugins::{render::plugin::FrameComparison, schedule::plugin::SimulationCycle},
};
pub struct SimulationPlugin;
impl Plugin for SimulationPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(SpawnBuffer::default())
.add_systems(
Update,
(handle_spawn_requests, spawn_buffer)
.chain()
.in_set(SimulationCycle::Preparation1),
)
.add_systems(
Update,
(despawn_overflows, update_points).in_set(SimulationCycle::Compute),
)
.add_systems(
Update,
(converge, filter).chain().in_set(SimulationCycle::Converge),
);
}
}
fn handle_spawn_requests(
mut event_reader: EventReader<SpawnRequest>,
mut buffer: ResMut<SpawnBuffer>,
) {
for event in event_reader.read() {
buffer.buffer.push(event.clone())
}
}
fn spawn_buffer(
mut buffer: ResMut<SpawnBuffer>,
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for mesh_network in buffer.buffer.drain(..) {
spawner(
mesh_network.mesh_network,
&mut commands,
&mut meshes,
&mut materials,
);
}
}
fn despawn_overflows(
stick_query: Query<(Entity, &Stick)>,
point_query: Query<(Entity, &Point)>,
mut commands: Commands,
) {
const ACTUAL_SIMULATION_BOUNDS: f32 = 5_000.;
let mut despawned_points = HashSet::new();
for (stick_entity, stick) in &stick_query {
if let Ok([p1, p2]) = point_query.get_many([stick.point1, stick.point2]) {
let p1_out = p1.1.position.length() >= ACTUAL_SIMULATION_BOUNDS;
let p2_out = p2.1.position.length() >= ACTUAL_SIMULATION_BOUNDS;
if p1_out || p2_out {
if !despawned_points.contains(&p1.0) {
commands.entity(p1.0).despawn();
despawned_points.insert(p1.0);
}
if !despawned_points.contains(&p2.0) {
commands.entity(p2.0).despawn();
despawned_points.insert(p2.0);
}
}
} else {
commands.entity(stick_entity).despawn();
}
}
for (entity, point) in &point_query {
if point.position.length() >= ACTUAL_SIMULATION_BOUNDS
&& !despawned_points.contains(&entity)
{
commands.entity(entity).despawn();
}
}
}
fn update_points(
mut query: Query<&mut Point>,
time: Res<Time>,
mut state: ResMut<FrameComparison>,
sim_settings: Res<SimulationSettings>,
) {
let mut points: Vec<_> = query.iter_mut().collect();
points.shuffle(&mut thread_rng());
let mut max_delta: f32 = 0.0;
for point in points.iter_mut() {
if point.locked {
point.prev_position = point.position;
continue;
}
let previous_position = point.position;
point.update_properties(&time, &sim_settings);
let updated_position = point.position;
let delta = updated_position.distance(previous_position).abs();
if delta > max_delta {
max_delta = delta;
}
}
if max_delta > sim_settings.min_render_delta {
state.frames_since = 0;
state.changed = true;
} else if state.frames_since > sim_settings.max_unchanged_frames {
state.frames_since = 0;
state.changed = true;
} else {
state.frames_since += 1;
state.changed = false;
}
}
fn converge(
mut point_query: Query<&mut Point>,
stick_query: Query<&Stick>,
mut state: ResMut<FrameComparison>,
sim_settings: Res<SimulationSettings>,
) {
for _ in 0..sim_settings.converge_iterations {
constrain_points(&mut point_query, &mut state, &sim_settings);
restore_stick_constraints(&mut point_query, &stick_query, &mut state, &sim_settings);
}
}
fn constrain_points(
query: &mut Query<&mut Point>,
state: &mut ResMut<FrameComparison>,
sim_settings: &Res<SimulationSettings>,
) {
let mut points: Vec<_> = query.iter_mut().collect();
points.shuffle(&mut thread_rng());
let mut max_delta: f32 = 0.0;
for mut pt in points {
if pt.locked {
pt.prev_position = pt.position;
continue;
}
let previous_position = pt.position;
let velocity = pt.calculate_velocity();
let half_width = sim_settings.simulation_bounds.x.1 * 0.5;
let half_depth = sim_settings.simulation_bounds.z.1 * 0.5;
let x_bounds_enabled = sim_settings.simulation_bounds.x.0;
let y_bounds_enabled = sim_settings.simulation_bounds.y.0;
let z_bounds_enabled = sim_settings.simulation_bounds.z.0;
let coef_restitution = sim_settings.coeff_restitution;
if pt.position.y <= 0. && y_bounds_enabled {
pt.position.y = 0.;
pt.prev_position.y = pt.position.y + velocity.y * coef_restitution;
}
if pt.position.x <= -half_width && x_bounds_enabled {
pt.position.x = -half_width;
pt.prev_position.x = pt.position.x + velocity.x * coef_restitution;
}
else if pt.position.x >= half_width && x_bounds_enabled {
pt.position.x = half_width;
pt.prev_position.x = pt.position.x + velocity.x * coef_restitution;
}
if pt.position.z <= -half_depth && z_bounds_enabled {
pt.position.z = -half_depth;
pt.prev_position.z = pt.position.z + velocity.z * coef_restitution;
} else if pt.position.z > half_depth && z_bounds_enabled {
pt.position.z = half_depth;
pt.prev_position.z = pt.position.z + velocity.z * coef_restitution;
}
let updated_position = pt.position;
let delta = updated_position.distance(previous_position).abs();
if delta > max_delta && !state.changed {
max_delta = delta;
}
}
if state.changed {
return;
}
if max_delta > sim_settings.min_render_delta {
state.frames_since = 0;
state.changed = true;
} else {
state.changed = false;
}
}
fn restore_stick_constraints(
point_query: &mut Query<&mut Point>,
stick_query: &Query<&Stick>,
state: &mut ResMut<FrameComparison>,
sim_settings: &Res<SimulationSettings>,
) {
let mut max_delta: f32 = 0.0;
for stick in &mut stick_query.iter() {
if let Ok([mut p1, mut p2]) = point_query.get_many_mut([stick.point1, stick.point2]) {
if p1.locked && p2.locked {
continue;
}
let p1_previous_position = p1.position;
let p2_previous_position = p2.position;
let delta = p2.position - p1.position;
let current_len = delta.length();
let offset = delta * (current_len - stick.length) / current_len / 2.0;
if p1.locked {
p2.position -= 2. * offset;
} else if p2.locked {
p1.position += 2. * offset;
} else {
p1.position += offset;
p2.position -= offset;
}
let p1_updated_position = p1.position;
let p2_updated_position = p2.position;
let delta1 = p1_updated_position.distance(p1_previous_position).abs();
let delta2 = p2_updated_position.distance(p2_previous_position).abs();
if delta1 > max_delta {
max_delta = delta1;
}
if delta2 > max_delta {
max_delta = delta2;
}
}
}
if state.changed {
return;
}
if max_delta > sim_settings.min_render_delta {
state.frames_since = 0;
state.changed = true;
} else {
state.changed = false;
}
}
fn filter(mut q: Query<&mut Point>, sim_settings: Res<SimulationSettings>) {
let jerk_damp: f32 = sim_settings.jerk_damping;
for mut pt in q.iter_mut() {
if pt.locked {
continue;
}
let p_nm1 = pt.previously_rendered_position;
let p_n = pt.prev_position;
let p_np1 = pt.position;
let delta2 = p_np1 - 2.0 * p_n + p_nm1;
let damped = p_np1 - delta2 * jerk_damp;
pt.position = damped;
}
}