use std::hash::Hash;
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
calc_func_id,
graph::{BuiltInOperator, EvalContext, ExprError},
Attribute, BoxedModifier, ExprHandle, Modifier, ModifierContext, Module, ShaderWriter,
};
#[derive(Debug, Clone, Copy, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub struct ConformToSphereModifier {
pub origin: ExprHandle,
pub radius: ExprHandle,
pub influence_dist: ExprHandle,
pub attraction_accel: ExprHandle,
pub max_attraction_speed: ExprHandle,
pub shell_half_thickness: Option<ExprHandle>,
pub sticky_factor: Option<ExprHandle>,
}
impl ConformToSphereModifier {
pub fn new(
origin: ExprHandle,
radius: ExprHandle,
influence_dist: ExprHandle,
attraction_accel: ExprHandle,
max_attraction_speed: ExprHandle,
) -> Self {
Self {
origin,
radius,
influence_dist,
attraction_accel,
max_attraction_speed,
shell_half_thickness: None,
sticky_factor: None,
}
}
}
impl Modifier for ConformToSphereModifier {
fn context(&self) -> ModifierContext {
ModifierContext::Update
}
fn attributes(&self) -> &[Attribute] {
&[Attribute::POSITION, Attribute::VELOCITY]
}
fn boxed_clone(&self) -> BoxedModifier {
Box::new(*self)
}
fn apply(&self, module: &mut Module, context: &mut ShaderWriter) -> Result<(), ExprError> {
let func_id = calc_func_id(self);
let func_name = format!("force_field_{0:016X}", func_id);
context.make_fn(
&func_name,
"particle: ptr<function, Particle>",
module,
&mut |m: &mut Module, ctx: &mut dyn EvalContext| -> Result<String, ExprError> {
let origin = ctx.eval(m, self.origin)?;
let radius = ctx.eval(m, self.radius)?;
let influence_dist = ctx.eval(m, self.influence_dist)?;
let shell_half_thickness = if let Some(shell_half_thickness) = self.shell_half_thickness { ctx.eval(m, shell_half_thickness)? } else { "0.1".to_string() };
let max_attraction_speed = ctx.eval(m, self.max_attraction_speed)?;
let attraction_accel = ctx.eval(m, self.attraction_accel)?;
let sticky_factor = if let Some(sticky_factor) = self.sticky_factor { ctx.eval(m, sticky_factor)? } else { "2.0".to_string() };
let attr_pos = format!("(*particle).{}", Attribute::POSITION.name());
let attr_vel = format!("(*particle).{}", Attribute::VELOCITY.name());
Ok(format!(
r##" // Sphere center
let c = {origin};
// Sphere radius
let r = {radius};
// Distance and direction to origin (sphere center)
let rel_pos = c - {attr_pos};
let origin_dist = length(rel_pos);
let origin_dir = normalize(rel_pos);
// Signed distance to sphere surface, negative if inside sphere
let surface_dist = origin_dist - r;
// Influence distance
let influence_dist = {influence_dist};
if (surface_dist > influence_dist) {{
return;
}}
// Current signed radial speed (normal to sphere surface) toward the sphere center, which needs to be
// corrected to conform to the sphere.
let cur_radial_speed = dot({attr_vel}, origin_dir);
// Signed radial speed (toward the sphere center) at which we'd like to move to stick to the surface.
// This is smoothed out to zero as the distance to the surface gets close to zero, to prevent numerical
// oscillations around the surface.
let shell_half_thickness = {shell_half_thickness};
let shell_factor = smoothstep(0., shell_half_thickness, abs(surface_dist));
let max_attraction_speed = {max_attraction_speed};
let max_radial_speed = sign(surface_dist) * shell_factor * max_attraction_speed;
// Delta radial speed to reach the ideal value
let delta_speed = max_radial_speed - cur_radial_speed;
// Conforming delta speed from attraction acceleration
let attraction_accel = {attraction_accel};
let sticky_accel = attraction_accel * {sticky_factor};
let conforming_accel = mix(sticky_accel, attraction_accel, shell_factor);
let conforming_delta_speed = sim_params.delta_time * conforming_accel;
// Final impulse clamped by the maximum acceleration speed
{attr_vel} += sign(delta_speed) * min(abs(delta_speed), conforming_delta_speed) * origin_dir;
"##
))
},
)?;
context.main_code += &format!("{}(&particle);\n", func_name);
Ok(())
}
}
#[derive(Debug, Clone, Copy, Reflect, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LinearDragModifier {
pub drag: ExprHandle,
}
impl LinearDragModifier {
pub fn new(drag: ExprHandle) -> Self {
Self { drag }
}
pub fn constant(module: &mut Module, drag: f32) -> Self {
Self {
drag: module.lit(drag),
}
}
}
impl Modifier for LinearDragModifier {
fn context(&self) -> ModifierContext {
ModifierContext::Update
}
fn attributes(&self) -> &[Attribute] {
&[Attribute::VELOCITY]
}
fn boxed_clone(&self) -> BoxedModifier {
Box::new(*self)
}
fn apply(&self, module: &mut Module, context: &mut ShaderWriter) -> Result<(), ExprError> {
let m = module;
let attr = m.attr(Attribute::VELOCITY);
let dt = m.builtin(BuiltInOperator::DeltaTime);
let drag_dt = m.mul(self.drag, dt);
let one = m.lit(1.);
let one_minus_drag_dt = m.sub(one, drag_dt);
let zero = m.lit(0.);
let expr = m.max(zero, one_minus_drag_dt);
let attr = context.eval(m, attr)?;
let expr = context.eval(m, expr)?;
context.main_code += &format!("{} *= {};", attr, expr);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{ParticleLayout, PropertyLayout};
#[test]
fn mod_drag() {
let mut module = Module::default();
let modifier = LinearDragModifier::constant(&mut module, 3.5);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let mut context =
ShaderWriter::new(ModifierContext::Update, &property_layout, &particle_layout);
assert!(modifier.apply(&mut module, &mut context).is_ok());
assert!(context.main_code.contains("3.5")); }
}