#![doc = include_str!("../docs/features/prototype_material.md")]
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use bevy::{
asset::weak_handle,
image::{CompressedImageFormats, ImageFormat, ImageSampler, ImageType},
prelude::*,
render::{
render_asset::RenderAssetUsages,
render_resource::{AsBindGroup, ShaderRef},
},
};
use random_color::{options::Luminosity, RandomColor};
use crate::DevAssets;
const SHADER_PATH: &str = "shaders/prototype_material.wgsl";
const SHADER_HANDLE: Handle<Shader> = weak_handle!("0ced3da7-55d3-43be-9e04-5637b0e9ceef");
pub struct PrototypeMaterialPlugin;
impl Plugin for PrototypeMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(MaterialPlugin::<PrototypeMaterialAsset>::default())
.insert_resource(PrototypeMaterialResource::default())
.add_systems(PostUpdate, initialization);
}
}
#[derive(Component, Debug, Clone, Copy)]
pub struct PrototypeMaterial {
color: Color,
}
impl PrototypeMaterial {
pub fn new(feature_name: &str) -> Self {
let mut hasher = DefaultHasher::new();
feature_name.hash(&mut hasher);
let hash = hasher.finish();
let rgb = RandomColor::new()
.luminosity(Luminosity::Bright)
.seed(hash)
.to_rgb_array();
Self {
color: Color::srgb_u8(rgb[0], rgb[1], rgb[2]),
}
}
}
#[derive(Asset, AsBindGroup, TypePath, Debug, Clone)]
pub struct PrototypeMaterialAsset {
#[uniform(0)]
pub color: LinearRgba,
#[texture(1)]
#[sampler(2)]
pub base_texture: Handle<Image>,
}
impl Material for PrototypeMaterialAsset {
fn vertex_shader() -> ShaderRef {
SHADER_HANDLE.into()
}
fn fragment_shader() -> ShaderRef {
SHADER_HANDLE.into()
}
}
#[derive(Resource, Default, Clone, Debug)]
struct PrototypeMaterialResource {
base_texture: Option<Handle<Image>>,
}
fn initialization(
mut commands: Commands,
mut entities: Query<(Entity, &PrototypeMaterial), Changed<PrototypeMaterial>>,
mut resource: ResMut<PrototypeMaterialResource>,
mut images: ResMut<Assets<Image>>,
mut shaders: ResMut<Assets<Shader>>,
mut materials: ResMut<Assets<PrototypeMaterialAsset>>,
) {
if entities.is_empty() {
return;
}
if resource.base_texture.is_none() {
resource.base_texture = Some(
images.add(
Image::from_buffer(
&DevAssets::get("textures/prototype.png")
.expect("Prototype material texture is not embedded")
.data,
ImageType::Format(ImageFormat::Png),
CompressedImageFormats::all(),
false,
ImageSampler::Default,
RenderAssetUsages::all(),
)
.expect("Unable to load prototype material texture"),
),
);
shaders.insert(
&SHADER_HANDLE,
Shader::from_wgsl(
String::from_utf8(
DevAssets::get(SHADER_PATH)
.expect("Prototype material shader is not embedded")
.data
.into(),
)
.expect("Prototype material shader is not valid UTF-8"),
SHADER_PATH,
),
)
}
for (entity, material) in entities.iter_mut() {
commands
.entity(entity)
.insert(MeshMaterial3d(materials.add(PrototypeMaterialAsset {
color: material.color.to_linear(),
base_texture: resource.base_texture.clone().unwrap(),
})));
}
}
#[derive(Default, Clone)]
pub struct PrototypeMaterialMeshBundle {
pub mesh: Handle<Mesh>,
pub material: &'static str,
pub transform: Transform,
pub global_transform: GlobalTransform,
pub visibility: Visibility,
pub inherited_visibility: InheritedVisibility,
pub view_visibility: ViewVisibility,
}