use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use crate::{
calc_func_id, graph::ExprError, modifier::ShapeDimension, Attribute, BoxedModifier,
EvalContext, ExprHandle, Modifier, ModifierContext, Module, ShaderWriter,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct SetPositionCircleModifier {
pub center: ExprHandle,
pub axis: ExprHandle,
pub radius: ExprHandle,
pub dimension: ShapeDimension,
}
impl SetPositionCircleModifier {
fn eval(
&self,
module: &mut Module,
context: &mut dyn EvalContext,
) -> Result<String, ExprError> {
let func_id = calc_func_id(self);
let func_name = format!("set_position_circle_{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 center = ctx.eval(m, self.center)?;
let axis = ctx.eval(m, self.axis)?;
let radius = match self.dimension {
ShapeDimension::Surface => {
format!("let r = {};", ctx.eval(m, self.radius)?)
}
ShapeDimension::Volume => {
format!("let r = sqrt(frand()) * ({});", ctx.eval(m, self.radius)?)
}
};
Ok(format!(
r##" // Circle center
let c = {};
// Circle basis
let n = {};
let sign = step(0.0, n.z) * 2.0 - 1.0;
let a = -1.0 / (sign + n.z);
let b = n.x * n.y * a;
let tangent = vec3<f32>(1.0 + sign * n.x * n.x * a, sign * b, -sign * n.x);
let bitangent = vec3<f32>(b, sign + n.y * n.y * a, -n.y);
// Circle radius
{}
// Spawn random point on/in circle
let theta = frand() * tau;
let dir = tangent * cos(theta) + bitangent * sin(theta);
(*particle).{} = c + r * dir;
"##,
center,
axis,
radius,
Attribute::POSITION.name(),
))
},
)?;
let code = format!("{}(&particle);\n", func_name);
Ok(code)
}
}
impl Modifier for SetPositionCircleModifier {
fn context(&self) -> ModifierContext {
ModifierContext::Init | ModifierContext::Update
}
fn attributes(&self) -> &[Attribute] {
&[Attribute::POSITION]
}
fn boxed_clone(&self) -> BoxedModifier {
Box::new(*self)
}
fn apply(&self, module: &mut Module, context: &mut ShaderWriter) -> Result<(), ExprError> {
let code = self.eval(module, context)?;
context.main_code += &code;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub struct SetPositionSphereModifier {
pub center: ExprHandle,
pub radius: ExprHandle,
pub dimension: ShapeDimension,
}
impl SetPositionSphereModifier {
fn eval(
&self,
module: &mut Module,
context: &mut dyn EvalContext,
) -> Result<String, ExprError> {
let func_id = calc_func_id(self);
let func_name = format!("set_position_sphere_{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 center = ctx.eval(m, self.center)?;
let radius = match self.dimension {
ShapeDimension::Surface => {
format!("let r = {};", ctx.eval(m, self.radius)?)
}
ShapeDimension::Volume => {
format!(
"let r = pow(frand(), 1./3.) * ({});",
ctx.eval(m, self.radius)?
)
}
};
Ok(format!(
r##" // Sphere center
let c = {};
// Sphere radius
{}
// Spawn randomly along the sphere surface using Archimedes's theorem
let theta = frand() * tau;
let z = frand() * 2. - 1.;
let phi = acos(z);
let sinphi = sin(phi);
let x = sinphi * cos(theta);
let y = sinphi * sin(theta);
let dir = vec3<f32>(x, y, z);
(*particle).{} = c + r * dir;
"##,
center,
radius,
Attribute::POSITION.name(),
))
},
)?;
let code = format!("{}(&particle);\n", func_name);
Ok(code)
}
}
impl Modifier for SetPositionSphereModifier {
fn context(&self) -> ModifierContext {
ModifierContext::Init | ModifierContext::Update
}
fn attributes(&self) -> &[Attribute] {
&[Attribute::POSITION]
}
fn boxed_clone(&self) -> BoxedModifier {
Box::new(*self)
}
fn apply(&self, module: &mut Module, context: &mut ShaderWriter) -> Result<(), ExprError> {
let code = self.eval(module, context)?;
context.main_code += &code;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct SetPositionCone3dModifier {
pub height: ExprHandle,
pub base_radius: ExprHandle,
pub top_radius: ExprHandle,
pub dimension: ShapeDimension,
}
impl SetPositionCone3dModifier {
fn eval(
&self,
module: &mut Module,
context: &mut dyn EvalContext,
) -> Result<String, ExprError> {
let func_id = calc_func_id(self);
let func_name = format!("set_position_cone3d_{0:016X}", func_id);
context.make_fn(
&func_name,
"transform: mat4x4<f32>, particle: ptr<function, Particle>",
module,
&mut |m: &mut Module, ctx: &mut dyn EvalContext| -> Result<String, ExprError> {
let height = ctx.eval(m, self.height)?;
let top_radius = ctx.eval(m, self.top_radius)?;
let base_radius = ctx.eval(m, self.base_radius)?;
Ok(format!(
r##" // Truncated cone height
let h0 = {0};
// Random height ratio
let alpha_h = pow(frand(), 1.0 / 3.0);
// Random delta height from top
let h = h0 * alpha_h;
// Top radius
let rt = {1};
// Bottom radius
let rb = {2};
// Radius at height h
let r0 = rb + (rt - rb) * alpha_h;
// Random delta radius
let alpha_r = sqrt(frand());
// Random radius at height h
let r = r0 * alpha_r;
// Random base angle
let theta = frand() * tau;
let cost = cos(theta);
let sint = sin(theta);
// Random position relative to truncated cone origin (not apex)
let x = r * cost;
let y = h;
let z = r * sint;
let p = vec3<f32>(x, y, z);
let p2 = transform * vec4<f32>(p, 0.0);
(*particle).{3} = p2.xyz;
"##,
height,
top_radius,
base_radius,
Attribute::POSITION.name(),
))
},
)?;
let code = format!("{}(transform, &particle);\n", func_name);
Ok(code)
}
}
impl Modifier for SetPositionCone3dModifier {
fn context(&self) -> ModifierContext {
ModifierContext::Init | ModifierContext::Update
}
fn attributes(&self) -> &[Attribute] {
&[Attribute::POSITION]
}
fn boxed_clone(&self) -> BoxedModifier {
Box::new(*self)
}
fn apply(&self, module: &mut Module, context: &mut ShaderWriter) -> Result<(), ExprError> {
let code = self.eval(module, context)?;
context.main_code += &code;
Ok(())
}
}