1#![doc = include_str!("../readme.md")]
2
3pub mod animation;
4pub mod color_space_fix;
5#[cfg(feature = "bevy_pbr")]
6pub mod erased_material;
7pub mod generic_material;
8pub mod load;
9pub mod material_property;
10pub mod prelude;
11pub mod value;
12
13#[cfg(feature = "bevy_pbr")]
14use std::any::TypeId;
15use std::sync::Arc;
16
17#[cfg(feature = "bevy_pbr")]
18use bevy::{
19 pbr::{ExtendedMaterial, MaterialExtension},
20 reflect::{GetTypeRegistration, Typed},
21};
22use color_space_fix::ColorSpaceFixPlugin;
23use generic_material::GenericMaterialShorthands;
24use material_property::MaterialPropertyRegistry;
25
26use bevy::prelude::*;
27#[cfg(feature = "bevy_pbr")]
28use generic_material::GenericMaterialApplied;
29use load::{
30 GenericMaterialLoader, asset::AssetLoadingProcessor, deserializer::MaterialDeserializer, processor::MaterialProcessor,
31 simple::SimpleGenericMaterialLoader,
32};
33use prelude::*;
34
35pub struct MaterializePlugin<D: MaterialDeserializer, P: MaterialProcessor> {
36 pub deserializer: Arc<D>,
37 pub simple_loader: Option<SimpleGenericMaterialLoader>,
39 pub animated_materials: bool,
41 pub do_text_replacements: bool,
43 pub standard_material_color_space_fix: bool,
45 pub processor: P,
46}
47impl<D: MaterialDeserializer, P: MaterialProcessor + Clone> Plugin for MaterializePlugin<D, P> {
48 fn build(&self, app: &mut App) {
49 let type_registry = app.world().resource::<AppTypeRegistry>().clone();
50
51 if let Some(simple_loader) = self.simple_loader.clone() {
52 app.register_asset_loader(simple_loader);
53 }
54
55 let shorthands = GenericMaterialShorthands::default();
56 let property_registry = MaterialPropertyRegistry::default();
57
58 #[rustfmt::skip]
59 app
60 .add_plugins(MaterializeMarkerPlugin)
61 .insert_resource(shorthands.clone())
62 .insert_resource(property_registry.clone())
63 .register_type::<GenericMaterial3d>()
64 .init_asset::<GenericMaterial>()
65 .register_generic_material_sub_asset::<GenericMaterial>()
66 .register_asset_loader(GenericMaterialLoader {
67 type_registry,
68 shorthands,
69 property_registry,
70 deserializer: self.deserializer.clone(),
71 do_text_replacements: self.do_text_replacements,
72 processor: self.processor.clone(),
73 })
74 ;
75
76 if self.animated_materials {
77 app.add_plugins(animation::AnimationPlugin);
78 }
79
80 if self.standard_material_color_space_fix {
81 app.add_plugins(ColorSpaceFixPlugin);
82 }
83
84 #[cfg(feature = "bevy_image")]
85 app.register_generic_material_sub_asset::<Image>();
86
87 #[cfg(feature = "bevy_pbr")]
88 #[rustfmt::skip]
89 app
90 .register_material_property(GenericMaterial::VISIBILITY)
91 .register_generic_material::<StandardMaterial>()
92 .add_systems(PreUpdate, (
93 reload_generic_materials,
94 visibility_material_property, insert_generic_materials,
96 ).chain())
97 ;
98 }
99}
100impl<D: MaterialDeserializer> MaterializePlugin<D, AssetLoadingProcessor<()>> {
101 pub fn new(deserializer: D) -> Self {
103 Self::new_with_processor(deserializer, AssetLoadingProcessor(()))
104 }
105}
106
107impl<D: MaterialDeserializer, P: MaterialProcessor> MaterializePlugin<D, P> {
108 pub fn new_with_processor(deserializer: D, processor: P) -> Self {
110 Self {
111 deserializer: Arc::new(deserializer),
112 simple_loader: Some(default()),
113 animated_materials: true,
114 do_text_replacements: true,
115 standard_material_color_space_fix: true,
116 processor,
117 }
118 }
119
120 pub fn with_simple_loader(self, loader: Option<SimpleGenericMaterialLoader>) -> Self {
122 Self {
123 simple_loader: loader,
124 ..self
125 }
126 }
127
128 pub fn with_text_replacements(self, value: bool) -> Self {
130 Self {
131 do_text_replacements: value,
132 ..self
133 }
134 }
135
136 pub fn with_animated_materials(self, value: bool) -> Self {
138 Self {
139 animated_materials: value,
140 ..self
141 }
142 }
143
144 pub fn with_standard_material_color_space_fix(self, value: bool) -> Self {
146 Self {
147 standard_material_color_space_fix: value,
148 ..self
149 }
150 }
151
152 pub fn with_processor<NewP: MaterialProcessor>(self, f: impl FnOnce(P) -> NewP) -> MaterializePlugin<D, NewP> {
156 MaterializePlugin {
157 deserializer: self.deserializer,
158 simple_loader: self.simple_loader,
159 animated_materials: self.animated_materials,
160 do_text_replacements: self.do_text_replacements,
161 standard_material_color_space_fix: self.standard_material_color_space_fix,
162 processor: f(self.processor),
163 }
164 }
165}
166impl<D: MaterialDeserializer + Default, P: MaterialProcessor + Default> Default for MaterializePlugin<D, P> {
167 fn default() -> Self {
168 Self::new_with_processor(default(), default())
169 }
170}
171
172pub struct MaterializeMarkerPlugin;
174impl Plugin for MaterializeMarkerPlugin {
175 fn build(&self, _app: &mut App) {}
176}
177
178#[cfg(feature = "bevy_pbr")]
184pub fn insert_generic_materials(
185 mut commands: Commands,
186 query: Query<(Entity, &GenericMaterial3d), Without<GenericMaterialApplied>>,
187 generic_materials: Res<Assets<GenericMaterial>>,
188) {
189 for (entity, holder) in &query {
190 let Some(generic_material) = generic_materials.get(&holder.0) else { continue };
191
192 let material = generic_material.handle.clone();
193 commands
194 .entity(entity)
195 .queue(move |entity: EntityWorldMut<'_>| material.insert(entity))
196 .insert(GenericMaterialApplied);
197 }
198}
199
200#[cfg(feature = "bevy_pbr")]
201pub fn reload_generic_materials(
202 mut commands: Commands,
203 mut asset_events: EventReader<AssetEvent<GenericMaterial>>,
204 query: Query<(Entity, &GenericMaterial3d), With<GenericMaterialApplied>>,
205) {
206 for event in asset_events.read() {
207 let AssetEvent::Modified { id } = event else { continue };
208
209 for (entity, holder) in &query {
210 if *id == holder.0.id() {
211 commands.entity(entity).remove::<GenericMaterialApplied>();
212 }
213 }
214 }
215}
216
217impl GenericMaterial {
218 #[cfg(feature = "bevy_pbr")]
220 pub const VISIBILITY: MaterialProperty<Visibility> = MaterialProperty::new("visibility");
221}
222
223#[cfg(feature = "bevy_pbr")]
224pub fn visibility_material_property(
225 mut query: Query<(&GenericMaterial3d, &mut Visibility), Without<GenericMaterialApplied>>,
226 generic_materials: Res<Assets<GenericMaterial>>,
227) {
228 for (generic_material_holder, mut visibility) in &mut query {
229 let Some(generic_material) = generic_materials.get(&generic_material_holder.0) else { continue };
230 let Ok(new_visibility) = generic_material.get_property(GenericMaterial::VISIBILITY) else { continue };
231
232 *visibility = *new_visibility;
233 }
234}
235
236#[cfg(feature = "bevy_pbr")]
237pub trait MaterializeAppExt {
238 fn register_generic_material<M: Material + Reflect + Struct + FromWorld + GetTypeRegistration>(&mut self) -> &mut Self;
246
247 fn register_extended_generic_material<
252 Base: Material + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
253 Ext: MaterialExtension + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
254 >(
255 &mut self,
256 shorthand: impl Into<String>,
257 ) -> &mut Self;
258
259 fn register_generic_material_with_default<M: Material + Reflect + Struct + GetTypeRegistration>(&mut self, default_value: M) -> &mut Self;
263
264 fn register_generic_material_shorthand<M: GetTypeRegistration>(&mut self, shorthand: impl Into<String>) -> &mut Self;
282}
283#[cfg(feature = "bevy_pbr")]
284impl MaterializeAppExt for App {
285 fn register_generic_material<M: Material + Reflect + Struct + FromWorld + GetTypeRegistration>(&mut self) -> &mut Self {
286 let default_value = M::from_world(self.world_mut());
287 self.register_generic_material_with_default(default_value)
288 }
289
290 fn register_extended_generic_material<
291 Base: Material + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
292 Ext: MaterialExtension + FromReflect + Typed + Struct + FromWorld + GetTypeRegistration,
293 >(
294 &mut self,
295 shorthand: impl Into<String>,
296 ) -> &mut Self {
297 let base = Base::from_world(self.world_mut());
298 let extension = Ext::from_world(self.world_mut());
299 self.register_generic_material_with_default(ExtendedMaterial { base, extension })
300 .register_generic_material_shorthand::<ExtendedMaterial<Base, Ext>>(shorthand)
301 }
302
303 fn register_generic_material_with_default<M: Material + Reflect + Struct + GetTypeRegistration>(&mut self, default_value: M) -> &mut Self {
304 let mut type_registry = self.world().resource::<AppTypeRegistry>().write();
305 if type_registry.get(TypeId::of::<M>()).is_none() {
306 type_registry.register::<M>();
307 }
308
309 type_registry.get_mut(TypeId::of::<M>()).unwrap().insert(ReflectGenericMaterial {
310 default_value: Box::new(default_value),
311 });
312
313 drop(type_registry);
314
315 self
316 }
317
318 fn register_generic_material_shorthand<M: GetTypeRegistration>(&mut self, shorthand: impl Into<String>) -> &mut Self {
319 self.world()
320 .resource::<GenericMaterialShorthands>()
321 .values
322 .write()
323 .unwrap()
324 .insert(shorthand.into(), M::get_type_registration());
325 self
326 }
327}