#![doc = include_str!("../readme.md")]
pub mod animation;
pub mod color_space_fix;
#[cfg(feature = "bevy_pbr")]
pub mod erased_material;
pub mod generic_material;
pub mod load;
pub mod material_property;
pub mod prelude;
pub mod value;
#[cfg(feature = "bevy_pbr")]
use std::any::TypeId;
use std::sync::Arc;
#[cfg(feature = "bevy_pbr")]
use bevy::{
pbr::{ExtendedMaterial, MaterialExtension},
reflect::{GetTypeRegistration, Typed},
};
use color_space_fix::ColorSpaceFixPlugin;
use generic_material::GenericMaterialShorthands;
use material_property::MaterialPropertyRegistry;
use bevy::prelude::*;
#[cfg(feature = "bevy_pbr")]
use generic_material::GenericMaterialApplied;
use load::{
GenericMaterialLoader, asset::AssetLoadingProcessor, deserializer::MaterialDeserializer, processor::MaterialProcessor,
simple::SimpleGenericMaterialLoader,
};
use prelude::*;
pub struct MaterializePlugin<D: MaterialDeserializer, P: MaterialProcessor> {
pub deserializer: Arc<D>,
pub simple_loader: Option<SimpleGenericMaterialLoader>,
pub animated_materials: bool,
pub do_text_replacements: bool,
pub standard_material_color_space_fix: bool,
pub processor: P,
}
impl<D: MaterialDeserializer, P: MaterialProcessor + Clone> Plugin for MaterializePlugin<D, P> {
fn build(&self, app: &mut App) {
let type_registry = app.world().resource::<AppTypeRegistry>().clone();
if let Some(simple_loader) = self.simple_loader.clone() {
app.register_asset_loader(simple_loader);
}
let shorthands = GenericMaterialShorthands::default();
let property_registry = MaterialPropertyRegistry::default();
#[rustfmt::skip]
app
.add_plugins(MaterializeMarkerPlugin)
.insert_resource(shorthands.clone())
.insert_resource(property_registry.clone())
.register_type::<GenericMaterial3d>()
.init_asset::<GenericMaterial>()
.register_generic_material_sub_asset::<GenericMaterial>()
.register_asset_loader(GenericMaterialLoader {
type_registry,
shorthands,
property_registry,
deserializer: self.deserializer.clone(),
do_text_replacements: self.do_text_replacements,
processor: self.processor.clone(),
})
;
if self.animated_materials {
app.add_plugins(animation::AnimationPlugin);
}
if self.standard_material_color_space_fix {
app.add_plugins(ColorSpaceFixPlugin);
}
#[cfg(feature = "bevy_image")]
app.register_generic_material_sub_asset::<Image>();
#[cfg(feature = "bevy_pbr")]
#[rustfmt::skip]
app
.register_material_property(GenericMaterial::VISIBILITY)
.register_generic_material::<StandardMaterial>()
.add_systems(PreUpdate, (
reload_generic_materials,
visibility_material_property, insert_generic_materials,
).chain())
;
}
}
impl<D: MaterialDeserializer> MaterializePlugin<D, AssetLoadingProcessor<()>> {
pub fn new(deserializer: D) -> Self {
Self::new_with_processor(deserializer, AssetLoadingProcessor(()))
}
}
impl<D: MaterialDeserializer, P: MaterialProcessor> MaterializePlugin<D, P> {
pub fn new_with_processor(deserializer: D, processor: P) -> Self {
Self {
deserializer: Arc::new(deserializer),
simple_loader: None,
animated_materials: true,
do_text_replacements: true,
standard_material_color_space_fix: true,
processor,
}
}
pub fn with_simple_loader(self, loader: SimpleGenericMaterialLoader) -> Self {
Self {
simple_loader: Some(loader),
..self
}
}
pub fn with_text_replacements(self, value: bool) -> Self {
Self {
do_text_replacements: value,
..self
}
}
pub fn with_animated_materials(self, value: bool) -> Self {
Self {
animated_materials: value,
..self
}
}
pub fn with_standard_material_color_space_fix(self, value: bool) -> Self {
Self {
standard_material_color_space_fix: value,
..self
}
}
pub fn with_processor<NewP: MaterialProcessor>(self, f: impl FnOnce(P) -> NewP) -> MaterializePlugin<D, NewP> {
MaterializePlugin {
deserializer: self.deserializer,
simple_loader: self.simple_loader,
animated_materials: self.animated_materials,
do_text_replacements: self.do_text_replacements,
standard_material_color_space_fix: self.standard_material_color_space_fix,
processor: f(self.processor),
}
}
}
impl<D: MaterialDeserializer + Default, P: MaterialProcessor + Default> Default for MaterializePlugin<D, P> {
fn default() -> Self {
Self::new_with_processor(default(), default())
}
}
pub struct MaterializeMarkerPlugin;
impl Plugin for MaterializeMarkerPlugin {
fn build(&self, _app: &mut App) {}
}
#[cfg(feature = "bevy_pbr")]
pub fn insert_generic_materials(
mut commands: Commands,
query: Query<(Entity, &GenericMaterial3d), Without<GenericMaterialApplied>>,
generic_materials: Res<Assets<GenericMaterial>>,
) {
for (entity, holder) in &query {
let Some(generic_material) = generic_materials.get(&holder.0) else { continue };
let material = generic_material.handle.clone();
commands
.entity(entity)
.queue(move |entity: EntityWorldMut<'_>| material.insert(entity))
.insert(GenericMaterialApplied);
}
}
#[cfg(feature = "bevy_pbr")]
pub fn reload_generic_materials(
mut commands: Commands,
mut asset_events: MessageReader<AssetEvent<GenericMaterial>>,
query: Query<(Entity, &GenericMaterial3d), With<GenericMaterialApplied>>,
) {
for event in asset_events.read() {
let AssetEvent::Modified { id } = event else { continue };
for (entity, holder) in &query {
if *id == holder.0.id() {
commands.entity(entity).remove::<GenericMaterialApplied>();
}
}
}
}
impl GenericMaterial {
#[cfg(feature = "bevy_pbr")]
pub const VISIBILITY: MaterialProperty<Visibility> = MaterialProperty::new("visibility");
}
#[cfg(feature = "bevy_pbr")]
pub fn visibility_material_property(
mut query: Query<(&GenericMaterial3d, &mut Visibility), Without<GenericMaterialApplied>>,
generic_materials: Res<Assets<GenericMaterial>>,
) {
for (generic_material_holder, mut visibility) in &mut query {
let Some(generic_material) = generic_materials.get(&generic_material_holder.0) else { continue };
let Ok(new_visibility) = generic_material.get_property(GenericMaterial::VISIBILITY) else { continue };
*visibility = *new_visibility;
}
}
#[cfg(feature = "bevy_pbr")]
pub trait MaterializeAppExt {
fn register_generic_material<M: Material + Reflect + Struct + FromWorld + GetTypeRegistration>(&mut self) -> &mut Self;
fn register_extended_generic_material<
Base: Material + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
Ext: MaterialExtension + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
>(
&mut self,
shorthand: impl Into<String>,
) -> &mut Self;
fn register_generic_material_with_default<M: Material + Reflect + Struct + GetTypeRegistration>(&mut self, default_value: M) -> &mut Self;
fn register_generic_material_shorthand<M: GetTypeRegistration>(&mut self, shorthand: impl Into<String>) -> &mut Self;
}
#[cfg(feature = "bevy_pbr")]
impl MaterializeAppExt for App {
fn register_generic_material<M: Material + Reflect + Struct + FromWorld + GetTypeRegistration>(&mut self) -> &mut Self {
let default_value = M::from_world(self.world_mut());
self.register_generic_material_with_default(default_value)
}
fn register_extended_generic_material<
Base: Material + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
Ext: MaterialExtension + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
>(
&mut self,
shorthand: impl Into<String>,
) -> &mut Self {
let base = Base::from_world(self.world_mut());
let extension = Ext::from_world(self.world_mut());
self.register_generic_material_with_default(ExtendedMaterial { base, extension })
.register_generic_material_shorthand::<ExtendedMaterial<Base, Ext>>(shorthand)
}
fn register_generic_material_with_default<M: Material + Reflect + Struct + GetTypeRegistration>(&mut self, default_value: M) -> &mut Self {
let mut type_registry = self.world().resource::<AppTypeRegistry>().write();
if type_registry.get(TypeId::of::<M>()).is_none() {
type_registry.register::<M>();
}
type_registry.get_mut(TypeId::of::<M>()).unwrap().insert(ReflectGenericMaterial {
default_value: Box::new(default_value),
});
drop(type_registry);
self
}
fn register_generic_material_shorthand<M: GetTypeRegistration>(&mut self, shorthand: impl Into<String>) -> &mut Self {
self.world()
.resource::<GenericMaterialShorthands>()
.values
.write()
.unwrap()
.insert(shorthand.into(), M::get_type_registration());
self
}
}