use bevy::asset::{Asset, Handle};
use bevy::image::Image;
use bevy::mesh::MeshVertexBufferLayoutRef;
use bevy::pbr::{
ExtendedMaterial, MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline,
StandardMaterial,
};
use bevy::reflect::TypePath;
use bevy::render::render_resource::{
AsBindGroup, RenderPipelineDescriptor, ShaderType, SpecializedMeshPipelineError,
VertexAttribute, VertexFormat,
};
use bevy::shader::ShaderRef;
use crate::vertex_attributes::{ATTRIBUTE_MATERIAL_INDICES, ATTRIBUTE_MATERIAL_WEIGHTS};
pub type PlumeSplatMaterial = ExtendedMaterial<StandardMaterial, PlumeSplatExtension>;
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone, Default)]
pub struct PlumeSplatExtension {
#[texture(100, dimension = "2d_array")]
#[sampler(101, sampler_type = "filtering")]
pub albedo_array: Handle<Image>,
#[texture(103, dimension = "2d_array")]
#[sampler(104, sampler_type = "filtering")]
pub normal_array: Option<Handle<Image>>,
#[texture(105, dimension = "2d_array")]
#[sampler(106, sampler_type = "filtering")]
pub pbr_array: Option<Handle<Image>>,
#[uniform(102)]
pub settings: PlumeSplatSettings,
}
#[derive(Debug, Clone, Copy, ShaderType)]
pub struct PlumeSplatSettings {
pub uv_scale: f32,
pub triplanar_sharpness: f32,
pub height_blend_sharpness: f32,
pub blend_offset: f32,
pub blend_exponent: f32,
}
impl Default for PlumeSplatSettings {
fn default() -> Self {
Self {
uv_scale: 1.0,
triplanar_sharpness: 4.0,
height_blend_sharpness: 0.0,
blend_offset: 0.0,
blend_exponent: 1.0,
}
}
}
impl PlumeSplatExtension {
pub fn new(albedo_array: Handle<Image>) -> Self {
Self {
albedo_array,
..Default::default()
}
}
pub fn with_uv_scale(mut self, scale: f32) -> Self {
self.settings.uv_scale = scale;
self
}
pub fn with_triplanar_sharpness(mut self, sharpness: f32) -> Self {
self.settings.triplanar_sharpness = sharpness;
self
}
pub fn with_height_blending(mut self, sharpness: f32) -> Self {
self.settings.height_blend_sharpness = sharpness;
self
}
pub fn with_normal_array(mut self, normal_array: Handle<Image>) -> Self {
self.normal_array = Some(normal_array);
self
}
pub fn with_pbr_array(mut self, pbr_array: Handle<Image>) -> Self {
self.pbr_array = Some(pbr_array);
self
}
pub fn with_blend_offset(mut self, offset: f32) -> Self {
self.settings.blend_offset = offset.clamp(0.0, 0.5);
self
}
pub fn with_blend_exponent(mut self, exponent: f32) -> Self {
self.settings.blend_exponent = exponent.clamp(1.0, 8.0);
self
}
}
impl MaterialExtension for PlumeSplatExtension {
fn vertex_shader() -> ShaderRef {
"embedded://plumesplat/shaders/plumesplat_ext.wgsl".into()
}
fn fragment_shader() -> ShaderRef {
"embedded://plumesplat/shaders/plumesplat_ext.wgsl".into()
}
fn specialize(
_pipeline: &MaterialExtensionPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayoutRef,
_key: MaterialExtensionKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
if let Some(index) = layout
.0
.attribute_ids()
.iter()
.position(|id| *id == ATTRIBUTE_MATERIAL_INDICES.id)
{
let attr = &layout.0.layout().attributes[index];
descriptor.vertex.buffers[0]
.attributes
.push(VertexAttribute {
format: VertexFormat::Uint32,
offset: attr.offset,
shader_location: 10,
});
}
if let Some(index) = layout
.0
.attribute_ids()
.iter()
.position(|id| *id == ATTRIBUTE_MATERIAL_WEIGHTS.id)
{
let attr = &layout.0.layout().attributes[index];
descriptor.vertex.buffers[0]
.attributes
.push(VertexAttribute {
format: VertexFormat::Uint32,
offset: attr.offset,
shader_location: 11,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_settings() {
let settings = PlumeSplatSettings::default();
assert_eq!(settings.uv_scale, 1.0);
assert_eq!(settings.triplanar_sharpness, 4.0);
assert_eq!(settings.height_blend_sharpness, 0.0);
assert_eq!(settings.blend_offset, 0.0);
assert_eq!(settings.blend_exponent, 1.0);
}
#[test]
fn test_blend_offset_clamping() {
let ext = PlumeSplatExtension::default()
.with_blend_offset(1.0); assert_eq!(ext.settings.blend_offset, 0.5);
let ext = PlumeSplatExtension::default()
.with_blend_offset(-0.5); assert_eq!(ext.settings.blend_offset, 0.0);
}
#[test]
fn test_blend_exponent_clamping() {
let ext = PlumeSplatExtension::default()
.with_blend_exponent(10.0); assert_eq!(ext.settings.blend_exponent, 8.0);
let ext = PlumeSplatExtension::default()
.with_blend_exponent(0.5); assert_eq!(ext.settings.blend_exponent, 1.0);
}
#[test]
fn test_builder_chain() {
let ext = PlumeSplatExtension::default()
.with_uv_scale(2.0)
.with_triplanar_sharpness(8.0)
.with_height_blending(0.5)
.with_blend_offset(0.2)
.with_blend_exponent(3.0);
assert_eq!(ext.settings.uv_scale, 2.0);
assert_eq!(ext.settings.triplanar_sharpness, 8.0);
assert_eq!(ext.settings.height_blend_sharpness, 0.5);
assert_eq!(ext.settings.blend_offset, 0.2);
assert_eq!(ext.settings.blend_exponent, 3.0);
}
}