use std::hash::Hash;
use bevy::prelude::*;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use crate::{
impl_mod_render, Attribute, BoxedModifier, CpuValue, EvalContext, ExprError, ExprHandle,
Gradient, Modifier, ModifierContext, Module, RenderContext, RenderModifier, ShaderCode,
ShaderWriter, ToWgslString,
};
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum ImageSampleMapping {
#[default]
Modulate,
ModulateRGB,
ModulateOpacityFromR,
}
impl ImageSampleMapping {
pub fn to_shader_code(&self, texture_slot: &str) -> String {
match *self {
ImageSampleMapping::Modulate => format!("color = color * texColor{texture_slot};"),
ImageSampleMapping::ModulateRGB => {
format!("color = vec4<f32>(color.rgb * texColor{texture_slot}.rgb, color.a);")
}
ImageSampleMapping::ModulateOpacityFromR => {
format!("color.a = color.a * texColor{texture_slot}.r;")
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
pub struct ParticleTextureModifier {
pub texture_slot: ExprHandle,
pub sample_mapping: ImageSampleMapping,
}
impl ParticleTextureModifier {
pub fn new(texture_slot: ExprHandle) -> Self {
Self {
texture_slot,
sample_mapping: default(),
}
}
}
impl_mod_render!(ParticleTextureModifier, &[]);
impl RenderModifier for ParticleTextureModifier {
fn apply_render(
&self,
module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
context.set_needs_uv();
let code = self.eval(module, context)?;
context.fragment_code += &code;
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
impl ParticleTextureModifier {
pub fn eval(
&self,
module: &Module,
context: &mut dyn EvalContext,
) -> Result<String, ExprError> {
let texture_slot = module.try_get(self.texture_slot)?;
let texture_slot = texture_slot.eval(module, context)?;
let sample_mapping = self.sample_mapping.to_shader_code(&texture_slot[..]);
let sample_mapping_name = format!("{:?}", self.sample_mapping);
let mut code = String::with_capacity(1024);
code += &format!(
" // ParticleTextureModifier
var texColor{texture_slot}: vec4<f32>;
switch ({texture_slot}) {{\n"
);
let count = module.texture_layout().layout.len() as u32;
for index in 0..count {
let wgsl_index = index.to_wgsl_string();
code += &format!(" case {wgsl_index}: {{ texColor{texture_slot} = textureSample(material_texture_{index}, material_sampler_{index}, uv); }}\n");
}
code += &format!(" default: {{ texColor{texture_slot} = vec4<f32>(0.0); }}\n");
code += &format!(
" }}
// Sample mapping: {sample_mapping_name}
{sample_mapping}"
);
Ok(code)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum ColorBlendMode {
#[default]
Overwrite,
Add,
Modulate,
}
impl ColorBlendMode {
pub fn to_assign_operator(&self) -> String {
match *self {
ColorBlendMode::Overwrite => "=",
ColorBlendMode::Add => "+=",
ColorBlendMode::Modulate => "*=",
}
.to_string()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct ColorBlendMask(u8);
bitflags! {
impl ColorBlendMask: u8 {
const R = 0b0001;
const G = 0b0010;
const B = 0b0100;
const A = 0b1000;
const RGB = 0b0111;
const RGBA = 0b1111;
}
}
impl Default for ColorBlendMask {
fn default() -> Self {
Self::RGBA
}
}
impl ColorBlendMask {
pub fn to_components(&self) -> String {
let cmp = ['r', 'g', 'b', 'a'];
(0..=3).fold(String::with_capacity(4), |mut acc, i| {
let mask = ColorBlendMask::from_bits_truncate(1u8 << i);
if self.contains(mask) {
acc.push(cmp[i]);
}
acc
})
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct SetColorModifier {
pub color: CpuValue<Vec4>,
pub blend: ColorBlendMode,
pub mask: ColorBlendMask,
}
impl SetColorModifier {
pub fn new<C>(color: C) -> Self
where
C: Into<CpuValue<Vec4>>,
{
Self {
color: color.into(),
blend: default(),
mask: default(),
}
}
}
impl_mod_render!(SetColorModifier, &[]);
impl RenderModifier for SetColorModifier {
fn apply_render(
&self,
_module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
let op = self.blend.to_assign_operator();
let col = self.color.to_wgsl_string();
let s = if self.mask == ColorBlendMask::RGBA {
format!("color {op} {col};\n")
} else {
let mask = self.mask.to_components();
format!("color.{mask} {op} ({col}).{mask};\n")
};
context.vertex_code += &s;
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Default, Clone, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub struct ColorOverLifetimeModifier {
pub gradient: Gradient<Vec4>,
pub blend: ColorBlendMode,
pub mask: ColorBlendMask,
}
impl ColorOverLifetimeModifier {
pub fn new(gradient: Gradient<Vec4>) -> Self {
Self {
gradient,
blend: default(),
mask: default(),
}
}
}
impl_mod_render!(
ColorOverLifetimeModifier,
&[Attribute::AGE, Attribute::LIFETIME]
);
impl RenderModifier for ColorOverLifetimeModifier {
fn apply_render(
&self,
_module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
let func_name = context.add_color_gradient(self.gradient.clone());
context.render_extra += &format!(
r#"fn {0}(key: f32) -> vec4<f32> {{
{1}
}}
"#,
func_name,
self.gradient.to_shader_code("key")
);
let op = self.blend.to_assign_operator();
let col = format!(
"{0}(particle.{1} / particle.{2})",
func_name,
Attribute::AGE.name(),
Attribute::LIFETIME.name()
);
let s = if self.mask == ColorBlendMask::RGBA {
format!("color {op} {col};\n")
} else {
let mask = self.mask.to_components();
let non_mask = match self.blend {
ColorBlendMode::Overwrite => {
format!("color.{}", self.mask.complement().to_components())
}
ColorBlendMode::Add => "0".to_string(),
ColorBlendMode::Modulate => "1".to_string(),
};
format!("color {op} vec4<f32>(({col}).{mask}, {non_mask});\n")
};
context.vertex_code += &s;
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(self.clone())
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct SetSizeModifier {
pub size: CpuValue<Vec3>,
}
impl_mod_render!(SetSizeModifier, &[]);
impl RenderModifier for SetSizeModifier {
fn apply_render(
&self,
_module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
context.vertex_code += &format!("size = {0};\n", self.size.to_wgsl_string());
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Default, Clone, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub struct SizeOverLifetimeModifier {
pub gradient: Gradient<Vec3>,
pub screen_space_size: bool,
}
impl_mod_render!(
SizeOverLifetimeModifier,
&[Attribute::AGE, Attribute::LIFETIME]
);
impl RenderModifier for SizeOverLifetimeModifier {
fn apply_render(
&self,
_module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
let func_name = context.add_size_gradient(self.gradient.clone());
context.render_extra += &format!(
r#"fn {0}(key: f32) -> vec3<f32> {{
{1}
}}
"#,
func_name,
self.gradient.to_shader_code("key")
);
context.vertex_code += &format!(
"size = {0}(particle.{1} / particle.{2});\n",
func_name,
Attribute::AGE.name(),
Attribute::LIFETIME.name()
);
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(self.clone())
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub enum OrientMode {
#[default]
ParallelCameraDepthPlane,
FaceCameraPosition,
AlongVelocity,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Hash, Reflect, Serialize, Deserialize)]
pub struct OrientModifier {
pub mode: OrientMode,
pub rotation: Option<ExprHandle>,
}
impl OrientModifier {
pub fn new(mode: OrientMode) -> Self {
Self {
mode,
..Default::default()
}
}
pub fn with_rotation(mut self, rotation: ExprHandle) -> Self {
self.rotation = Some(rotation);
self
}
}
impl Modifier for OrientModifier {
fn context(&self) -> ModifierContext {
ModifierContext::Render
}
fn as_render(&self) -> Option<&dyn RenderModifier> {
Some(self)
}
fn as_render_mut(&mut self) -> Option<&mut dyn RenderModifier> {
Some(self)
}
fn attributes(&self) -> &[Attribute] {
match self.mode {
OrientMode::ParallelCameraDepthPlane => &[],
OrientMode::FaceCameraPosition => &[Attribute::POSITION],
OrientMode::AlongVelocity => &[Attribute::POSITION, Attribute::VELOCITY],
}
}
fn boxed_clone(&self) -> BoxedModifier {
Box::new(*self)
}
fn apply(&self, _module: &mut Module, _context: &mut ShaderWriter) -> Result<(), ExprError> {
Err(ExprError::TypeError("Wrong modifier context".to_string()))
}
}
impl RenderModifier for OrientModifier {
fn apply_render(
&self,
module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
match self.mode {
OrientMode::ParallelCameraDepthPlane => {
if let Some(rotation) = self.rotation {
let rotation = context.eval(module, rotation)?;
context.vertex_code += &format!(
r#"let cam_rot = get_camera_rotation_effect_space();
let particle_rot_in_cam_space = {};
let particle_rot_in_cam_space_cos = cos(particle_rot_in_cam_space);
let particle_rot_in_cam_space_sin = sin(particle_rot_in_cam_space);
axis_x = cam_rot[0].xyz * particle_rot_in_cam_space_cos + cam_rot[1].xyz * particle_rot_in_cam_space_sin;
axis_y = cam_rot[0].xyz * particle_rot_in_cam_space_sin - cam_rot[1].xyz * particle_rot_in_cam_space_cos;
axis_z = cam_rot[2].xyz;
"#,
rotation
);
} else {
context.vertex_code += r#"let cam_rot = get_camera_rotation_effect_space();
axis_x = cam_rot[0].xyz;
axis_y = cam_rot[1].xyz;
axis_z = cam_rot[2].xyz;
"#;
}
}
OrientMode::FaceCameraPosition => {
if let Some(rotation) = self.rotation {
let rotation = context.eval(module, rotation)?;
context.vertex_code += &format!(
r#"axis_z = normalize(get_camera_position_effect_space() - position);
let particle_rot_in_cam_space = {};
let particle_rot_in_cam_space_cos = cos(particle_rot_in_cam_space);
let particle_rot_in_cam_space_sin = sin(particle_rot_in_cam_space);
let axis_x0 = normalize(cross(view.world_from_view[1].xyz, axis_z));
let axis_y0 = cross(axis_z, axis_x0);
axis_x = axis_x0 * particle_rot_in_cam_space_cos + axis_y0 * particle_rot_in_cam_space_sin;
axis_y = axis_x0 * particle_rot_in_cam_space_sin - axis_y0 * particle_rot_in_cam_space_cos;
"#,
rotation
);
} else {
context.vertex_code += r#"axis_z = normalize(get_camera_position_effect_space() - position);
axis_x = normalize(cross(view.world_from_view[1].xyz, axis_z));
axis_y = cross(axis_z, axis_x);
"#;
}
}
OrientMode::AlongVelocity => {
context.vertex_code += r#"let dir = normalize(position - get_camera_position_effect_space());
axis_x = normalize(particle.velocity);
axis_y = cross(dir, axis_x);
axis_z = cross(axis_x, axis_y);
"#;
}
}
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
pub struct FlipbookModifier {
pub sprite_grid_size: UVec2,
}
impl Default for FlipbookModifier {
fn default() -> Self {
Self {
sprite_grid_size: UVec2::ONE * 2,
}
}
}
impl_mod_render!(FlipbookModifier, &[Attribute::SPRITE_INDEX]);
impl RenderModifier for FlipbookModifier {
fn apply_render(
&self,
_module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
context.sprite_grid_size = Some(self.sprite_grid_size);
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
pub struct ScreenSpaceSizeModifier;
impl_mod_render!(
ScreenSpaceSizeModifier,
&[Attribute::POSITION, Attribute::SIZE]
);
impl RenderModifier for ScreenSpaceSizeModifier {
fn apply_render(
&self,
_module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
context.vertex_code += &format!(
"let w_cs = transform_position_simulation_to_clip(particle.{0}).w;\n
let screen_size_pixels = view.viewport.zw;\n
let projection_scale = vec2<f32>(view.clip_from_view[0][0], view.clip_from_view[1][1]);\n
size = (size * w_cs * 2.0) / min(screen_size_pixels.x * projection_scale.x, screen_size_pixels.y * projection_scale.y);\n",
Attribute::POSITION.name());
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
pub struct RoundModifier {
pub roundness: ExprHandle,
}
impl_mod_render!(RoundModifier, &[]);
impl RenderModifier for RoundModifier {
fn apply_render(
&self,
module: &mut Module,
context: &mut RenderContext,
) -> Result<(), ExprError> {
context.set_needs_uv();
let roundness = context.eval(module, self.roundness)?;
context.fragment_code += &format!(
"let roundness = {};
if (roundness > 0.0f) {{
let n = 2.0f / roundness;
if (pow(abs(1.0f - 2.0f * in.uv.x), n) +
pow(abs(1.0f - 2.0f * in.uv.y), n) > 1.0f) {{
discard;
}}
}}",
roundness
);
Ok(())
}
fn boxed_render_clone(&self) -> Box<dyn RenderModifier> {
Box::new(*self)
}
fn as_modifier(&self) -> &dyn Modifier {
self
}
}
impl RoundModifier {
pub fn constant(module: &mut Module, roundness: f32) -> RoundModifier {
RoundModifier {
roundness: module.lit(roundness),
}
}
#[doc(alias = "circle")]
pub fn ellipse(module: &mut Module) -> RoundModifier {
RoundModifier::constant(module, 1.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn color_blend_mask() {
assert_eq!(ColorBlendMask::RGB.to_components(), "rgb");
assert_eq!(ColorBlendMask::RGBA.to_components(), "rgba");
let m = ColorBlendMask::R | ColorBlendMask::B;
assert_eq!(m.to_components(), "rb");
assert_eq!(m.complement(), ColorBlendMask::G | ColorBlendMask::A);
assert_eq!(m.complement().to_components(), "ga");
}
#[test]
fn mod_particle_texture() {
let mut module = Module::default();
let slot = module.lit(42u32);
let modifier = ParticleTextureModifier::new(slot);
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert_eq!(context.texture_layout.layout.len(), 0); assert_eq!(context.textures.len(), 0); }
#[test]
fn mod_flipbook() {
let modifier = FlipbookModifier {
sprite_grid_size: UVec2::new(3, 4),
};
let mut module = Module::default();
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert!(context.sprite_grid_size.is_some());
assert_eq!(context.sprite_grid_size.unwrap(), UVec2::new(3, 4));
}
#[test]
fn mod_color_over_lifetime() {
let red: Vec4 = Vec4::new(1., 0., 0., 1.);
let blue: Vec4 = Vec4::new(0., 0., 1., 1.);
let mut gradient = Gradient::new();
gradient.add_key(0.5, red);
gradient.add_key(0.8, blue);
let modifier = ColorOverLifetimeModifier {
gradient: gradient.clone(),
blend: ColorBlendMode::Overwrite,
mask: ColorBlendMask::RGBA,
};
let mut module = Module::default();
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert!(context
.render_extra
.contains(&gradient.to_shader_code("key")));
}
#[test]
fn mod_size_over_lifetime() {
let x = Vec3::new(1., 0., 1.);
let y = Vec3::new(0., 1., 1.);
let mut gradient = Gradient::new();
gradient.add_key(0.5, x);
gradient.add_key(0.8, y);
let modifier = SizeOverLifetimeModifier {
gradient: gradient.clone(),
screen_space_size: false,
};
let mut module = Module::default();
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert!(context
.render_extra
.contains(&gradient.to_shader_code("key")));
}
#[test]
fn mod_set_color() {
let mut modifier = SetColorModifier::default();
assert_eq!(modifier.context(), ModifierContext::Render);
assert!(modifier.as_render().is_some());
assert!(modifier.as_render_mut().is_some());
assert_eq!(modifier.boxed_clone().context(), ModifierContext::Render);
let mut module = Module::default();
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert_eq!(modifier.color, CpuValue::from(Vec4::ZERO));
assert_eq!(context.vertex_code, "color = vec4<f32>(0.,0.,0.,0.);\n");
}
#[test]
fn mod_set_size() {
let mut modifier = SetSizeModifier::default();
assert_eq!(modifier.context(), ModifierContext::Render);
assert!(modifier.as_render().is_some());
assert!(modifier.as_render_mut().is_some());
assert_eq!(modifier.boxed_clone().context(), ModifierContext::Render);
let mut module = Module::default();
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert_eq!(modifier.size, CpuValue::from(Vec3::ZERO));
assert_eq!(context.vertex_code, "size = vec3<f32>(0.,0.,0.);\n");
}
#[test]
fn mod_orient() {
let mut modifier = OrientModifier::default();
assert_eq!(modifier.context(), ModifierContext::Render);
assert!(modifier.as_render().is_some());
assert!(modifier.as_render_mut().is_some());
assert_eq!(modifier.boxed_clone().context(), ModifierContext::Render);
}
#[test]
fn mod_orient_default() {
let mut module = Module::default();
let modifier = OrientModifier::default();
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert!(context
.vertex_code
.contains("get_camera_rotation_effect_space"));
assert!(!context
.vertex_code
.contains("cos(particle_rot_in_cam_space)"));
assert!(!context.vertex_code.contains("let axis_x0 ="));
}
#[test]
fn mod_orient_rotation() {
let mut module = Module::default();
let modifier = OrientModifier::default().with_rotation(module.lit(1.));
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert!(context
.vertex_code
.contains("get_camera_rotation_effect_space"));
assert!(context
.vertex_code
.contains("cos(particle_rot_in_cam_space)"));
assert!(!context.vertex_code.contains("let axis_x0 ="));
}
#[test]
fn mod_orient_rotation_face_camera() {
let mut module = Module::default();
let modifier =
OrientModifier::new(OrientMode::FaceCameraPosition).with_rotation(module.lit(1.));
let property_layout = PropertyLayout::default();
let particle_layout = ParticleLayout::default();
let texture_layout = module.texture_layout();
let mut context = RenderContext::new(&property_layout, &particle_layout, &texture_layout);
modifier.apply_render(&mut module, &mut context).unwrap();
assert!(context
.vertex_code
.contains("get_camera_position_effect_space"));
assert!(context
.vertex_code
.contains("cos(particle_rot_in_cam_space)"));
assert!(context.vertex_code.contains("let axis_x0 ="));
}
}