diff --git a/Cargo.toml b/Cargo.toml index 21ba8ee7..2a9f5f33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" version = "0.1.0" license = "MIT OR Apache-2.0" repository = "https://github.com/modor-engine/modor" -rust-version = "1.79.0" +rust-version = "1.80.1" [workspace.dependencies] ab_glyph = "0.2" @@ -71,10 +71,10 @@ unused_import_braces = "warn" unused_qualifications = "warn" [workspace.lints.clippy] -all = "warn" -pedantic = "warn" -nursery = "warn" -cargo = "warn" +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +cargo = { level = "warn", priority = -1 } dbg_macro = "warn" decimal_literal_representation = "warn" filetype_is_file = "warn" diff --git a/crates/modor/src/lib.rs b/crates/modor/src/lib.rs index e79ede9b..5ef3bc0a 100644 --- a/crates/modor/src/lib.rs +++ b/crates/modor/src/lib.rs @@ -108,7 +108,7 @@ pub use modor_derive::main; /// # Platform-specific /// /// - Web: function is annotated with `#[wasm_bindgen_test::wasm_bindgen_test]` instead of -/// `#[test]`. +/// `#[test]`. /// /// # Examples /// diff --git a/crates/modor_derive/src/updater.rs b/crates/modor_derive/src/updater.rs index af71bc40..f6d0eb0c 100644 --- a/crates/modor_derive/src/updater.rs +++ b/crates/modor_derive/src/updater.rs @@ -7,7 +7,7 @@ use proc_macro2::Span; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote, quote_spanned}; use syn::spanned::Spanned; -use syn::{parse_quote, Attribute, DeriveInput, GenericParam, Generics, Type}; +use syn::{parse_quote, Attribute, DeriveInput, GenericParam, Generics, Type, Visibility}; pub(crate) fn impl_block(input: &DeriveInput) -> Result { let crate_ident = utils::crate_ident(); @@ -22,6 +22,7 @@ pub(crate) fn impl_block(input: &DeriveInput) -> Result Result,)* + #(#field_visibilities #field_idents: ::#crate_ident::Update<'closures, #field_types>,)* phantom: #phantom_type, } @@ -78,6 +79,14 @@ fn field_types(fields: &[ParsedUpdaterField]) -> Vec<&Type> { .collect() } +fn field_visibilities(fields: &[ParsedUpdaterField]) -> Vec<&Visibility> { + fields + .iter() + .filter(|field| field.is_field_method_generated || field.is_for_field_method_generated) + .map(|field| &field.vis) + .collect() +} + fn all_field_fns( input: &DeriveInput, crate_ident: &Ident, @@ -144,6 +153,7 @@ struct UpdaterStruct { struct UpdaterField { ident: Option, ty: Type, + vis: Visibility, attrs: Vec, #[darling(default)] inner_type: bool, @@ -179,6 +189,7 @@ impl TryFrom for ParsedUpdaterStruct { struct ParsedUpdaterField { ident: Ident, type_: Type, + vis: Visibility, doc_attrs: Vec, is_field_method_generated: bool, is_for_field_method_generated: bool, @@ -192,6 +203,7 @@ impl TryFrom for ParsedUpdaterField { Ok(Self { ident, type_: Self::parse_type(field.inner_type, field.ty)?, + vis: field.vis, doc_attrs: Self::parse_doc_attributes(field.attrs), is_field_method_generated: field.field, is_for_field_method_generated: field.for_field, diff --git a/crates/modor_graphics/src/animation.rs b/crates/modor_graphics/src/animation.rs index dccac0f5..43836fd4 100644 --- a/crates/modor_graphics/src/animation.rs +++ b/crates/modor_graphics/src/animation.rs @@ -40,8 +40,10 @@ use std::time::Duration; /// TexturePart::new(2, 1), /// ]; /// Self { -/// sprite: Sprite2D::new(app) -/// .with_material(|m| m.texture = texture.to_ref()), +/// sprite: Sprite2D::from_app(app) +/// .with_material(|m| DefaultMaterial2DUpdater::default() +/// .texture(texture.to_ref()) +/// .apply(app, m)), /// animation: TextureAnimation::new(3, 2) /// .with_fps(5) /// .with_parts(|p| *p = animation_parts), @@ -50,8 +52,10 @@ use std::time::Duration; /// } /// /// fn update(&mut self, app: &mut App) { -/// self.sprite.material.texture_size = self.animation.part_size(); -/// self.sprite.material.texture_position = self.animation.part_position(); +/// DefaultMaterial2DUpdater::default() +/// .texture_size(self.animation.part_size()) +/// .texture_position(self.animation.part_position()) +/// .apply(app, &self.sprite.material); /// self.sprite.update(app); /// self.animation.update(app); /// } diff --git a/crates/modor_graphics/src/camera.rs b/crates/modor_graphics/src/camera.rs index 55ce86e3..438a6998 100644 --- a/crates/modor_graphics/src/camera.rs +++ b/crates/modor_graphics/src/camera.rs @@ -1,6 +1,6 @@ use crate::buffer::{Buffer, BufferBindGroup}; use crate::gpu::{Gpu, GpuManager}; -use crate::{Size, TargetGlob}; +use crate::{Size, Target}; use fxhash::FxHashMap; use modor::{App, Builder, FromApp, Glob, GlobRef, Global}; use modor_physics::modor_math::{Mat4, Quat, Vec2, Vec3}; @@ -29,7 +29,7 @@ use wgpu::{BindGroup, BufferUsages}; /// fn new(app: &mut App) -> Self { /// let camera = app.get_mut::().camera.glob().to_ref(); /// Self { -/// sprite: Sprite2D::new(app) +/// sprite: Sprite2D::from_app(app) /// .with_model(|m| m.size = Vec2::ONE * 0.2) /// .with_model(|m| m.camera = camera) /// } @@ -46,7 +46,7 @@ use wgpu::{BindGroup, BufferUsages}; /// /// impl FromApp for MovingCamera { /// fn from_app(app: &mut App) -> Self { -/// let target = app.get_mut::().target.glob().to_ref(); +/// let target = app.get_mut::().target.to_ref(); /// Self { /// camera: Camera2D::new(app, vec![target]) /// .with_size(Vec2::ONE * 0.5) // zoom x2 @@ -77,13 +77,13 @@ pub struct Camera2D { /// If a camera is linked to a target, then all models linked to the camera are rendered in the /// target. #[builder(form(closure))] - pub targets: Vec>, + pub targets: Vec>, glob: Glob, } impl Camera2D { /// Creates a new camera. - pub fn new(app: &mut App, targets: Vec>) -> Self { + pub fn new(app: &mut App, targets: Vec>) -> Self { Self { position: Vec2::ZERO, size: Vec2::ONE, @@ -94,7 +94,7 @@ impl Camera2D { } /// Updates the camera. - pub fn update(&mut self, app: &mut App) { + pub fn update(&self, app: &mut App) { let target_sizes = self.target_sizes(app); let gpu = app.get_mut::().get_or_init().clone(); let glob = self.glob.get_mut(app); @@ -126,7 +126,7 @@ impl Camera2D { fn target_sizes(&self, app: &App) -> Vec<(usize, Size)> { self.targets .iter() - .map(|target| (target.index(), target.get(app).size)) + .map(|target| (target.index(), target.get(app).size())) .collect() } } @@ -137,7 +137,7 @@ pub struct Camera2DGlob { pub(crate) position: Vec2, pub(crate) size: Vec2, pub(crate) rotation: f32, - pub(crate) targets: Vec>, + pub(crate) targets: Vec>, target_uniforms: FxHashMap, } @@ -168,13 +168,13 @@ impl Camera2DGlob { ) } - pub(crate) fn bind_group(&self, target: &Glob) -> Option<&BindGroup> { + pub(crate) fn bind_group(&self, target_index: usize) -> Option<&BindGroup> { self.target_uniforms - .get(&target.index()) + .get(&target_index) .map(|uniform| &uniform.bind_group.inner) } - fn register_targets(&mut self, targets: &[GlobRef]) { + fn register_targets(&mut self, targets: &[GlobRef]) { let target_indexes: Vec<_> = targets.iter().map(|target| target.index()).collect(); self.target_uniforms .retain(|target_index, _| target_indexes.contains(target_index)); diff --git a/crates/modor_graphics/src/color.rs b/crates/modor_graphics/src/color.rs index 86c2896d..fce01b50 100644 --- a/crates/modor_graphics/src/color.rs +++ b/crates/modor_graphics/src/color.rs @@ -29,6 +29,12 @@ impl From for [f32; 4] { } } +impl From<[f32; 4]> for Color { + fn from(color: [f32; 4]) -> Self { + Self::rgba(color[0], color[1], color[2], color[3]) + } +} + impl Color { /// pub const BLACK: Self = Self::rgb(0., 0., 0.); diff --git a/crates/modor_graphics/src/material/default_2d.rs b/crates/modor_graphics/src/material/default_2d.rs index f46ad27b..44109787 100644 --- a/crates/modor_graphics/src/material/default_2d.rs +++ b/crates/modor_graphics/src/material/default_2d.rs @@ -1,110 +1,146 @@ use crate::resources::Resources; -use crate::{Color, Material, Model2DGlob, ShaderGlobRef, Texture}; -use internal::DefaultMaterial2DData; -use modor::{App, Builder, Glob, GlobRef}; +use crate::{Color, MatGlob, MatUpdater, Material, Model2DGlob, Texture}; +use modor::{App, Glob, GlobRef, Updater}; use modor_input::modor_math::Vec2; use modor_resources::Res; +use std::marker::PhantomData; /// The default material for 2D rendering. /// /// # Examples /// /// See [`Model2D`](crate::Model2D). -#[derive(Debug, Builder)] +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod, Updater)] pub struct DefaultMaterial2D { + shader_color: [f32; 4], + shader_texture_part_position: [f32; 2], + shader_texture_part_size: [f32; 2], /// Color of the rendered instance. /// - /// This color is multiplied to the [`texture`](#structfield.texture) pixel colors. + /// This color is multiplied to the [`texture`](DefaultMaterial2DUpdater::texture) pixel colors. /// /// Default is [`Color::WHITE`]. - #[builder(form(value))] - pub color: Color, + #[updater(inner_type, field, for_field)] + color: PhantomData, /// Texture used to render the models. /// /// If the texture is not loaded, then the instances attached to the material are not rendered. /// /// Default is a white texture. - #[builder(form(value))] - pub texture: GlobRef>, + #[updater(inner_type, field, for_field)] + texture: PhantomData>>, /// Top-left position of the extracted texture section. /// /// [`Vec2::ZERO`] corresponds to top-left corner, and [`Vec2::ONE`] corresponds to bottom-right /// corner of the texture. /// /// Default is [`Vec2::ZERO`]. - #[builder(form(value))] - pub texture_position: Vec2, + #[updater(inner_type, field, for_field)] + texture_position: PhantomData, /// Size of the extracted texture section. /// /// [`Vec2::ONE`] corresponds to the entire texture. /// /// Default is [`Vec2::ONE`]. - #[builder(form(value))] - pub texture_size: Vec2, + #[updater(inner_type, field, for_field)] + texture_size: PhantomData, /// Whether the instance is rendered as an ellipse. /// /// If `false`, then the instance is displayed as a rectangle. /// /// Default is `false`. - #[builder(form(value))] - pub is_ellipse: bool, - default_shader: ShaderGlobRef, - ellipse_shader: ShaderGlobRef, + #[updater(inner_type, field, for_field)] + is_ellipse: PhantomData, } -impl Material for DefaultMaterial2D { - type Data = DefaultMaterial2DData; - type InstanceData = (); - - fn shader(&self) -> ShaderGlobRef { - if self.is_ellipse { - self.ellipse_shader.clone() - } else { - self.default_shader.clone() +impl Default for DefaultMaterial2D { + fn default() -> Self { + Self { + shader_color: Color::WHITE.into(), + shader_texture_part_position: [0., 0.], + shader_texture_part_size: [1., 1.], + color: PhantomData, + texture: PhantomData, + texture_position: PhantomData, + texture_size: PhantomData, + is_ellipse: PhantomData, } } +} - fn textures(&self) -> Vec>> { - vec![self.texture.clone()] - } - - fn is_transparent(&self) -> bool { - self.color.a > 0. && self.color.a < 1. - } +impl Material for DefaultMaterial2D { + type InstanceData = (); - fn data(&self) -> Self::Data { - DefaultMaterial2DData { - color: self.color.into(), - texture_part_position: [self.texture_position.x, self.texture_position.y], - texture_part_size: [self.texture_size.x, self.texture_size.y], - } + fn init(app: &mut App, glob: &MatGlob) { + MatUpdater::default() + .shader(app.get_mut::().default_shader.to_ref()) + .textures(vec![app.get_mut::().white_texture.to_ref()]) + .is_transparent(false) + .apply(app, glob); } fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} } -impl DefaultMaterial2D { - /// Creates a new material. - pub fn new(app: &mut App) -> Self { - let resources = app.get_mut::(); - Self { - color: Color::WHITE, - texture: resources.white_texture.to_ref(), - texture_position: Vec2::ZERO, - texture_size: Vec2::ONE, - is_ellipse: false, - default_shader: resources.default_shader.to_ref(), - ellipse_shader: resources.ellipse_shader.to_ref(), +impl DefaultMaterial2DUpdater<'_> { + /// Runs the update. + pub fn apply(mut self, app: &mut App, glob: &MatGlob) { + let mut updater = MatUpdater::default(); + if let Some(texture) = self + .texture + .take_value(|| Self::retrieve_texture(app, glob)) + { + updater = updater.textures(vec![texture]); + } + if let Some(is_ellipse) = self + .is_ellipse + .take_value(|| Self::retrieve_is_ellipse(app, glob)) + { + updater = updater.shader(if is_ellipse { + app.get_mut::().ellipse_shader.to_ref() + } else { + app.get_mut::().default_shader.to_ref() + }); + } + let mut data = glob.data(app); + let mut is_data_modified = false; + if let Some(color) = self.color.take_value(|| data.shader_color.into()) { + data.shader_color = color.into(); + is_data_modified = true; + } + if let Some(texture_position) = self.texture_position.take_value(|| { + Vec2::new( + data.shader_texture_part_position[0], + data.shader_texture_part_position[1], + ) + }) { + data.shader_texture_part_position = [texture_position.x, texture_position.y]; + is_data_modified = true; } + if let Some(texture_size) = self.texture_size.take_value(|| { + Vec2::new( + data.shader_texture_part_size[0], + data.shader_texture_part_size[1], + ) + }) { + data.shader_texture_part_size = [texture_size.x, texture_size.y]; + is_data_modified = true; + } + if is_data_modified { + updater = updater + .data(data) + .is_transparent(data.shader_color[3] > 0. && data.shader_color[3] < 1.); + } + updater.apply(app, glob); + } + + fn retrieve_texture(app: &mut App, glob: &MatGlob) -> GlobRef> { + let texture = glob.get(app).textures().next().cloned(); + texture.unwrap_or_else(|| app.get_mut::().white_texture.to_ref()) } -} -pub(super) mod internal { - #[repr(C)] - #[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)] - pub struct DefaultMaterial2DData { - pub(crate) color: [f32; 4], - pub(crate) texture_part_position: [f32; 2], - pub(crate) texture_part_size: [f32; 2], + fn retrieve_is_ellipse(app: &mut App, glob: &MatGlob) -> bool { + glob.get(app).shader().index() == app.get_mut::().ellipse_shader.index() } } diff --git a/crates/modor_graphics/src/material/mod.rs b/crates/modor_graphics/src/material/mod.rs index 7258308e..45ee56f2 100644 --- a/crates/modor_graphics/src/material/mod.rs +++ b/crates/modor_graphics/src/material/mod.rs @@ -2,171 +2,193 @@ use crate::buffer::{Buffer, BufferBindGroup}; use crate::gpu::{Gpu, GpuManager}; use crate::model::Model2DGlob; use crate::resources::Resources; -use crate::{DefaultMaterial2D, Shader, ShaderGlobRef, Texture}; +use crate::{Shader, Texture}; use bytemuck::Pod; use derivative::Derivative; use log::error; -use modor::{App, FromApp, Glob, GlobRef, Global, StateHandle}; +use modor::{App, FromApp, Glob, GlobRef, Global, Globals, State, StateHandle, Update}; use modor_resources::Res; use std::any; +use std::any::TypeId; use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; -use wgpu::{ - BindGroupEntry, BindGroupLayout, BindingResource, BufferUsages, Id, Sampler, TextureView, -}; +use std::ops::Deref; +use wgpu::{BindGroupEntry, BindingResource, BufferUsages}; -/// A material that defines the aspect of a rendered model. -/// -/// # Examples -/// -/// See [`Model2D`](crate::Model2D). -#[derive(Debug)] -pub struct Mat { - data: T, - glob: Glob, - phantom_data: PhantomData, -} - -impl Deref for Mat { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.data - } -} - -impl DerefMut for Mat { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.data - } -} - -impl Mat -where - T: Material, -{ - /// Updates the material. - pub fn update(&mut self, app: &mut App) { - self.glob - .take(app, |glob, app| glob.update(app, &self.data)); - } - - /// Returns a reference to global data. - pub fn glob(&self) -> MaterialGlobRef { - MaterialGlobRef { - inner: self.glob.to_ref(), - phantom: PhantomData, - } - } -} - -/// A trait implemented for types that can be converted to a [`Mat`]. -pub trait IntoMat: Sized { - /// Converts to a [`Mat`]. - fn into_mat(self, app: &mut App) -> Mat; -} +pub(crate) mod default_2d; -impl IntoMat for T -where - T: Material, -{ - fn into_mat(self, app: &mut App) -> Mat { - let mut material = Mat { - data: self, - glob: Glob::from_app(app), - phantom_data: PhantomData, - }; - material.update(app); - material - } -} +pub use internal::MatUpdater; -/// The global data of a [`Mat`] with data of type `T`. +/// A [`Mat`] glob. #[derive(Derivative)] #[derivative( Debug(bound = ""), - Clone(bound = ""), Hash(bound = ""), PartialEq(bound = ""), Eq(bound = ""), PartialOrd(bound = ""), Ord(bound = "") )] -pub struct MaterialGlobRef { - inner: GlobRef, +pub struct MatGlob { + inner: Glob, phantom: PhantomData, } -impl Deref for MaterialGlobRef { - type Target = GlobRef; +impl FromApp for MatGlob +where + T: Material, +{ + fn from_app(app: &mut App) -> Self { + let glob = Self { + inner: Glob::::from_app_with(app, |mat, app| { + mat.take(app, |mat, app| { + let data = T::from_app(app); + mat.buffer.update(app, data); + mat.type_name = any::type_name::(); + mat.instance_data_type.type_id = TypeId::of::(); + mat.instance_data_type.size = size_of::(); + mat.instance_data_type.create_fn = + |app, model| bytemuck::cast_slice(&[T::instance_data(app, model)]).to_vec(); + }); + }), + phantom: PhantomData, + }; + T::init(app, &glob); + glob + } +} + +impl Deref for MatGlob +where + T: Material, +{ + type Target = Glob; fn deref(&self) -> &Self::Target { &self.inner } } -/// The global data of a [`Mat`]. +impl MatGlob +where + T: Material, +{ + /// Retrieves material [`data`](MatUpdater::data). + pub fn data(&self, app: &App) -> T { + *bytemuck::from_bytes(&self.get(app).buffer.data) + } +} + +/// A material that defines the aspect of a rendered model. +/// +/// # Examples +/// +/// See [`Model2D`](crate::Model2D). #[derive(Debug, Global)] -pub struct MaterialGlob { +pub struct Mat { pub(crate) is_transparent: bool, + pub(crate) has_transparent_texture: bool, pub(crate) bind_group: BufferBindGroup, - pub(crate) binding_ids: BindingGlobalIds, pub(crate) shader: GlobRef>, + pub(crate) instance_data_type: InstanceDataType, buffer: MaterialBuffer, textures: Vec>>, + type_name: &'static str, + resources: StateHandle, } -impl FromApp for MaterialGlob { +impl FromApp for Mat { fn from_app(app: &mut App) -> Self { let gpu = app.get_mut::().get_or_init().clone(); let shader = app.get_mut::().empty_shader.deref().to_ref(); let textures = vec![]; let resources = app.handle(); let white_texture = Self::white_texture(app, resources); - let texture_refs = Self::textures(app, &textures); + let texture_refs = Self::retrieve_textures(app, &textures); let buffer = MaterialBuffer::new(&gpu); let shader_ref = shader.get(app); Self { is_transparent: false, - bind_group: Self::create_bind_group::( + has_transparent_texture: false, + bind_group: Self::create_bind_group( &gpu, &buffer, &texture_refs, white_texture, shader_ref, + "", ), - binding_ids: BindingGlobalIds::new(shader_ref, &texture_refs), shader, + instance_data_type: InstanceDataType { + type_id: TypeId::of::<()>(), + size: 0, + create_fn: |_, _| panic!("material not created with `MatGlob`"), + }, buffer, textures, + type_name: "", + resources, } } } -impl MaterialGlob { - fn update(&mut self, app: &mut App, material: &T) - where - T: Material, - { +impl MatUpdater<'_, T> +where + T: Material, +{ + /// Runs the update. + pub fn apply(mut self, app: &mut App, glob: &MatGlob) { + let data = self.data.take_value(|| glob.data(app)); + glob.take(app, |mat, app| { + Update::apply(&mut self.is_transparent, &mut mat.is_transparent); + if let Some(data) = data { + mat.buffer.update(app, data); + } + let mut is_bind_group_changed = false; + if let Some(shader) = self.shader.take_value(|| unreachable!()) { + is_bind_group_changed = shader.index() != mat.shader.index(); + mat.shader = shader.deref().to_ref(); + } + if let Some(textures) = self.textures.take_value_checked(|| mat.textures.clone()) { + is_bind_group_changed = true; + mat.textures = textures; + mat.has_transparent_texture = Mat::retrieve_textures(app, &mat.textures) + .iter() + .any(|texture| texture.loaded.is_transparent); + } + if is_bind_group_changed { + mat.refresh_bind_group(app); + } + }); + } +} + +impl Mat { + /// Retrieves material [`shader`](MatUpdater::shader). + pub fn shader(&self) -> &GlobRef> { + &self.shader + } + + /// Retrieves material [`textures`](MatUpdater::textures). + pub fn textures(&self) -> impl Iterator>> { + self.textures.iter() + } + + fn refresh_bind_group(&mut self, app: &mut App) { let gpu = app.get_mut::().get_or_init().clone(); - self.shader = material.shader().deref().to_ref(); - self.textures = material.textures(); - self.buffer.update(&gpu, material); - let resources = app.handle(); - let white_texture = Self::white_texture(app, resources); - let textures = Self::textures(app, &self.textures); - self.is_transparent = material.is_transparent() - || textures.iter().any(|texture| texture.loaded.is_transparent); let shader = self.shader.get(app); - let binding_ids = BindingGlobalIds::new(shader, &textures); - if binding_ids != self.binding_ids { - self.bind_group = - Self::create_bind_group::(&gpu, &self.buffer, &textures, white_texture, shader); - self.binding_ids = binding_ids; - } + let white_texture = self.resources.get(app).white_texture.get(app); + let textures = Self::retrieve_textures(app, &self.textures); + self.bind_group = Self::create_bind_group( + &gpu, + &self.buffer, + &textures, + white_texture, + shader, + self.type_name, + ); } - fn textures<'a>(app: &'a App, textures: &[GlobRef>]) -> Vec<&'a Texture> { + fn retrieve_textures<'a>(app: &'a App, textures: &[GlobRef>]) -> Vec<&'a Texture> { textures.iter().map(|texture| &**texture.get(app)).collect() } @@ -174,21 +196,20 @@ impl MaterialGlob { handle.get(app).white_texture.get(app) } - fn create_bind_group( + fn create_bind_group( gpu: &Gpu, buffer: &MaterialBuffer, textures: &[&Texture], white_texture: &Texture, shader: &Shader, - ) -> BufferBindGroup - where - T: Material, - { - let entries = Self::create_bind_group_entries::( + material_type_name: &str, + ) -> BufferBindGroup { + let entries = Self::create_bind_group_entries( buffer, textures, white_texture, shader.texture_count, + material_type_name, ); BufferBindGroup::new( gpu, @@ -199,15 +220,13 @@ impl MaterialGlob { } #[allow(clippy::cast_possible_truncation)] - fn create_bind_group_entries<'a, T>( + fn create_bind_group_entries<'a>( buffer: &'a MaterialBuffer, textures: &'a [&Texture], white_texture: &'a Texture, shader_texture_count: u32, - ) -> Vec> - where - T: Material, - { + material_type_name: &str, + ) -> Vec> { let mut entries = vec![BindGroupEntry { binding: 0, resource: buffer.inner.resource(), @@ -216,7 +235,7 @@ impl MaterialGlob { let texture = textures.get(i as usize).unwrap_or_else(|| { error!( "Invalid number of textures for material of type `{}`", - any::type_name::() + material_type_name ); &white_texture }); @@ -235,6 +254,13 @@ impl MaterialGlob { } } +#[derive(Debug, Clone, Copy)] +pub(crate) struct InstanceDataType { + pub(crate) type_id: TypeId, + pub(crate) size: usize, + pub(crate) create_fn: fn(&mut App, &Glob) -> Vec, +} + #[derive(Debug)] struct MaterialBuffer { inner: Buffer, @@ -254,91 +280,119 @@ impl MaterialBuffer { } } - fn update(&mut self, gpu: &Gpu, material: &T) + fn update(&mut self, app: &mut App, material: T) where T: Material, { - let data = Self::data(material); + let data = bytemuck::try_cast_slice(&[material]) + .unwrap_or(&[]) + .to_vec(); if self.data != data { - self.inner.update(gpu, &data); + let gpu = app.get_mut::().get_or_init().clone(); + self.inner.update(&gpu, &data); self.data = data; } } - - fn data(material: &impl Material) -> Vec { - bytemuck::try_cast_slice(&[material.data()]) - .unwrap_or(&[]) - .into() - } -} - -#[derive(Debug, PartialEq, Eq)] -pub(crate) struct BindingGlobalIds { - pub(crate) bind_group_layout: Id, - views: Vec>, - samplers: Vec>, -} - -impl BindingGlobalIds { - fn new(shader: &Shader, textures: &[&Texture]) -> Self { - Self { - bind_group_layout: shader.material_bind_group_layout.global_id(), - views: textures - .iter() - .map(|texture| texture.view.global_id()) - .collect(), - samplers: textures - .iter() - .map(|texture| texture.sampler.global_id()) - .collect(), - } - } } -/// A trait for defining [`Mat`] data. +/// A trait for defining [`Mat`] data that are sent to a shader. +/// +/// # Platform-specific +/// +/// - Web: material type size in bytes should be a multiple of 16. /// /// # Examples /// /// See code of `custom_shader` example. -pub trait Material: Sized + 'static { - /// Raw material data type. - type Data: Pod; +pub trait Material: FromApp + Pod + Sized + 'static { /// Raw instance data type. /// /// Each rendered model has its own instance data. /// - /// In case this type has a size of zero with [`mem::size_of`](std::mem::size_of()), + /// In case this type has a size of zero with [`size_of`], /// then no instance data are sent to the shader. type InstanceData: Pod; - /// Returns the shader used to make the rendering. - fn shader(&self) -> ShaderGlobRef; + /// Initializes the material. + fn init(app: &mut App, glob: &MatGlob); - /// Returns the textures sent to the shader. - fn textures(&self) -> Vec>>; + /// Returns the instance data of a given `model`. + fn instance_data(app: &mut App, model: &Glob) -> Self::InstanceData; +} - /// Returns whether the rendered models can be transparent. - /// - /// In case `true` is returned, the models will be rendered in Z-index order. - /// This is less efficient than for opaque models, but this limits the risk of having - /// rendering artifacts caused by transparency. - /// - /// Note that transparency is automatically detected for textures returned by - /// [`Material::textures`]. - /// It means that if [`Material::is_transparent`] - /// returns `false` but one of the textures contains transparent pixels, then the models - /// are considered as transparent. - fn is_transparent(&self) -> bool; +#[derive(Debug, FromApp, State)] +pub(crate) struct MaterialManager { + loaded_shader_indexes: Vec, + loaded_texture_indexes: Vec, +} - /// Returns the raw material data sent to the shader. - /// - /// # Platform-specific - /// - /// - Web: data size in bytes should be a multiple of 16. - fn data(&self) -> Self::Data; +impl MaterialManager { + pub(crate) fn register_loaded_shader(&mut self, index: usize) { + self.loaded_shader_indexes.push(index); + } - /// Returns the instance data of a given `model`. - fn instance_data(app: &mut App, model: &Glob) -> Self::InstanceData; + pub(crate) fn register_loaded_texture(&mut self, index: usize) { + self.loaded_texture_indexes.push(index); + } + + pub(crate) fn update_material_bind_groups(&mut self, app: &mut App) { + app.take::, _>(|materials, app| { + for shader_index in self.loaded_shader_indexes.drain(..) { + for mat in materials.iter_mut() { + if mat.shader.index() == shader_index { + mat.refresh_bind_group(app); + } + } + } + for texture_index in self.loaded_texture_indexes.drain(..) { + for mat in materials.iter_mut() { + if mat + .textures + .iter() + .any(|texture| texture.index() == texture_index) + { + mat.refresh_bind_group(app); + } + } + } + }); + } } -pub(crate) mod default_2d; +mod internal { + use crate::{ShaderGlobRef, Texture}; + use modor::{GlobRef, Updater}; + use modor_resources::Res; + + // this type is only used to generate `MatUpdater` + #[derive(Updater)] + #[allow(dead_code, unreachable_pub)] + pub struct Mat { + /// Material data sent to the shader. + #[updater(field, for_field)] + pub(super) data: T, + /// Whether the rendered models can be transparent. + /// + /// If `true`, the models will be rendered in Z-index order. + /// This is less efficient than for opaque models, but this limits the risk of having + /// rendering artifacts caused by transparency. + /// + /// If [`is_transparent`](MatUpdater::is_transparent) + /// is `false` but one of the [`textures`](MatUpdater::textures) contains transparent + /// pixels, then the models are considered as transparent. + /// + /// Default is `false`. + #[updater(field, for_field)] + pub(super) is_transparent: bool, + /// Shader used to make the rendering. + /// + /// Default is a shader that doesn't render anything. + #[updater(field)] + pub(super) shader: ShaderGlobRef, + /// Textures sent to the shader. + /// + /// Default is no texture. + #[updater(field, for_field)] + pub(super) textures: Vec>>, + } +} diff --git a/crates/modor_graphics/src/mesh.rs b/crates/modor_graphics/src/mesh.rs index 397bb812..0fb5fe38 100644 --- a/crates/modor_graphics/src/mesh.rs +++ b/crates/modor_graphics/src/mesh.rs @@ -1,7 +1,6 @@ use crate::buffer::Buffer; use crate::gpu::GpuManager; use modor::{App, FromApp, Global}; -use std::mem; use wgpu::{ vertex_attr_array, BufferAddress, BufferUsages, VertexAttribute, VertexBufferLayout, VertexStepMode, @@ -46,7 +45,7 @@ pub(crate) trait VertexBuffer: Sized { const ATTRIBUTES: &'static [VertexAttribute]; const STEP_MODE: VertexStepMode; const LAYOUT: VertexBufferLayout<'static> = VertexBufferLayout { - array_stride: mem::size_of::() as BufferAddress, + array_stride: size_of::() as BufferAddress, step_mode: Self::STEP_MODE, attributes: >::ATTRIBUTES, }; diff --git a/crates/modor_graphics/src/model.rs b/crates/modor_graphics/src/model.rs index 9f6cbdb8..5f90fe98 100644 --- a/crates/modor_graphics/src/model.rs +++ b/crates/modor_graphics/src/model.rs @@ -1,17 +1,16 @@ use crate::buffer::Buffer; use crate::gpu::Gpu; +use crate::material::InstanceDataType; use crate::mesh::Mesh; use crate::mesh::VertexBuffer; -use crate::resources::Resources; -use crate::{Camera2DGlob, Material, MaterialGlobRef, Window}; +use crate::resources::{Materials, Resources}; +use crate::{Camera2DGlob, Mat, Window}; use derivative::Derivative; use fxhash::FxHashMap; use modor::{App, Builder, FromApp, Glob, GlobRef, Global, Globals, State, StateHandle}; use modor_input::modor_math::{Mat4, Quat, Vec2}; use modor_physics::Body2D; use std::any::TypeId; -use std::marker::PhantomData; -use std::mem; use wgpu::{vertex_attr_array, BufferUsages, VertexAttribute, VertexStepMode}; /// The instance of a rendered 2D object. @@ -27,31 +26,32 @@ use wgpu::{vertex_attr_array, BufferUsages, VertexAttribute, VertexStepMode}; /// # use modor_physics::modor_math::*; /// # /// struct Circle { -/// material: Mat, -/// model: Model2D, +/// material: MatGlob, +/// model: Model2D, /// } /// /// impl Circle { /// fn new(app: &mut App, position: Vec2, radius: f32, color: Color) -> Self { -/// let material = DefaultMaterial2D::new(app) -/// .with_color(color) -/// .with_is_ellipse(true) -/// .into_mat(app); -/// let model = Model2D::new(app, material.glob()) +/// let material = MatGlob::::from_app(app); +/// DefaultMaterial2DUpdater::default() +/// .color(color) +/// .is_ellipse(true) +/// .apply(app, &material); +/// let model = Model2D::new(app) /// .with_position(position) -/// .with_size(Vec2::ONE * radius * 2.); +/// .with_size(Vec2::ONE * radius * 2.) +/// .with_material(material.to_ref()); /// Self { material, model } /// } /// /// fn update(&mut self, app: &mut App) { -/// self.material.update(app); /// self.model.update(app); /// } /// } /// ``` #[derive(Derivative, Builder)] #[derivative(Debug(bound = ""))] -pub struct Model2D { +pub struct Model2D { /// The position of the model is world units. /// /// Default is [`Vec2::ZERO`]. @@ -88,21 +88,18 @@ pub struct Model2D { pub camera: GlobRef, /// The material used to render the model. #[builder(form(value))] - pub material: MaterialGlobRef, + pub material: GlobRef, mesh: GlobRef, glob: Glob, groups: StateHandle, - phantom: PhantomData, } -impl Model2D -where - T: Material, -{ +impl Model2D { /// Creates a new model. - pub fn new(app: &mut App, material: MaterialGlobRef) -> Self { + pub fn new(app: &mut App) -> Self { let camera = app.get_mut::().camera.glob().to_ref(); let mesh = app.get_mut::().rectangle_mesh.to_ref(); + let material = app.get_mut::().default_2d.to_ref(); let model = Self { position: Vec2::ZERO, size: Vec2::ONE, @@ -114,10 +111,13 @@ where material, mesh, groups: app.handle::(), - phantom: PhantomData, }; - let data = T::instance_data(app, model.glob()); - model.groups.get_mut(app).register_model(&model, data); + let data_type = model.material.get(app).instance_data_type; + let data = (data_type.create_fn)(app, &model.glob); + model + .groups + .get_mut(app) + .register_model(&model, data, data_type); model } @@ -129,8 +129,9 @@ where self.size = glob.size(); self.rotation = glob.rotation(app); } - let data = T::instance_data(app, self.glob()); - self.groups.get_mut(app).update_model(self, data); + let data_type = self.material.get(app).instance_data_type; + let data = (data_type.create_fn)(app, &self.glob); + self.groups.get_mut(app).update_model(self, data, data_type); } /// Returns a reference to global data. @@ -149,15 +150,15 @@ pub struct Model2DGlob; /// An instance group contains all models that are rendered with the same material, camera and mesh. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct InstanceGroup2DProperties { - /// The index of the [`Mat`](crate::Mat) global data. + /// The index of the [`Mat`](Mat). pub material: usize, - /// The index of the [`Camera2D`](crate::Camera2D) global data. + /// The index of the [`Camera2D`](crate::Camera2D). pub camera: usize, pub(crate) mesh: usize, } impl InstanceGroup2DProperties { - fn new(model: &Model2D) -> Self { + fn new(model: &Model2D) -> Self { Self { mesh: model.mesh.index(), camera: model.camera.index(), @@ -197,30 +198,24 @@ impl InstanceGroups2D { } } - fn register_model(&mut self, model: &Model2D, data: T::InstanceData) - where - T: Material, - { + fn register_model(&mut self, model: &Model2D, data: Vec, data_type: InstanceDataType) { let group = InstanceGroup2DProperties::new(model); - self.group_mut(group).register_model(model, data); + self.group_mut(group).register_model(model, data, data_type); let model_index = model.glob.index(); (self.model_groups.len()..=model_index).for_each(|_| self.model_groups.push(None)); self.model_groups[model_index] = Some(group); } - fn update_model(&mut self, model: &Model2D, data: T::InstanceData) - where - T: Material, - { + fn update_model(&mut self, model: &Model2D, data: Vec, data_type: InstanceDataType) { let model_index = model.glob.index(); let old_group = self.model_groups[model_index].expect("internal error: missing model groups"); let group = InstanceGroup2DProperties::new(model); if group == old_group { - self.group_mut(group).update_model(model, data); + self.group_mut(group).update_model(model, data, data_type); } else { self.group_mut(old_group).delete_model(model.glob().index()); - self.group_mut(group).register_model(model, data); + self.group_mut(group).register_model(model, data, data_type); self.model_groups[model_index] = Some(group); } } @@ -249,37 +244,31 @@ impl InstanceGroup2D { .and_then(|type_id| self.buffers[&type_id].buffer.as_ref()) } - fn register_model(&mut self, model: &Model2D, data: T::InstanceData) - where - T: Material, - { + fn register_model(&mut self, model: &Model2D, data: Vec, data_type: InstanceDataType) { let model_index = model.glob().index(); self.model_positions .insert(model_index, self.model_indexes.len()); self.model_indexes.push(model_index); let instance = Instance::new(model); self.z_indexes.push(instance.z()); - self.buffer_mut::() + self.buffer_mut(TypeId::of::(), size_of::()) .push(bytemuck::cast_slice(&[instance])); - if mem::size_of::() > 0 { - self.buffer_mut::() - .push(bytemuck::cast_slice(&[data])); - self.secondary_type = Some(TypeId::of::()); + if data_type.size > 0 { + self.buffer_mut(data_type.type_id, data_type.size) + .push(&data); + self.secondary_type = Some(data_type.type_id); } } - fn update_model(&mut self, model: &Model2D, data: T::InstanceData) - where - T: Material, - { + fn update_model(&mut self, model: &Model2D, data: Vec, data_type: InstanceDataType) { let position = self.model_positions[&model.glob().index()]; let instance = Instance::new(model); self.z_indexes[position] = instance.z(); - self.buffer_mut::() + self.buffer_mut(TypeId::of::(), size_of::()) .replace(position, bytemuck::cast_slice(&[instance])); - if mem::size_of::() > 0 { - self.buffer_mut::() - .replace(position, bytemuck::cast_slice(&[data])); + if data_type.size > 0 { + self.buffer_mut(data_type.type_id, data_type.size) + .replace(position, bytemuck::cast_slice(&data)); } } @@ -304,13 +293,10 @@ impl InstanceGroup2D { } } - fn buffer_mut(&mut self) -> &mut InstanceGroupBuffer - where - T: 'static, - { + fn buffer_mut(&mut self, type_id: TypeId, type_size: usize) -> &mut InstanceGroupBuffer { self.buffers - .entry(TypeId::of::()) - .or_insert_with(|| InstanceGroupBuffer::new::()) + .entry(type_id) + .or_insert_with(|| InstanceGroupBuffer::new(type_size)) } } @@ -323,11 +309,11 @@ pub(crate) struct InstanceGroupBuffer { } impl InstanceGroupBuffer { - fn new() -> Self { + fn new(item_size: usize) -> Self { Self { buffer: None, data: vec![], - item_size: mem::size_of::(), + item_size, is_updated: false, } } @@ -377,7 +363,7 @@ pub(crate) struct Instance { } impl Instance { - pub(crate) fn new(model: &Model2D) -> Self { + pub(crate) fn new(model: &Model2D) -> Self { let z = (f32::from(model.z_index) + 0.5) / (f32::from(u16::MAX) + 1.) + 0.5; Self { transform: (Mat4::from_scale(model.size.with_z(0.)) diff --git a/crates/modor_graphics/src/resources.rs b/crates/modor_graphics/src/resources.rs index ad025868..0db00f38 100644 --- a/crates/modor_graphics/src/resources.rs +++ b/crates/modor_graphics/src/resources.rs @@ -1,7 +1,7 @@ use crate::mesh::Mesh; use crate::{ - DefaultMaterial2D, ShaderGlob, ShaderSource, ShaderUpdater, Size, Texture, TextureSource, - TextureUpdater, + DefaultMaterial2D, MatGlob, ShaderGlob, ShaderSource, ShaderUpdater, Size, Texture, + TextureSource, TextureUpdater, }; use modor::{App, FromApp, Glob, State}; use modor_resources::{Res, ResUpdater}; @@ -38,3 +38,9 @@ impl State for Resources { .apply(app, &self.white_texture); } } + +#[non_exhaustive] +#[derive(Debug, FromApp, State)] +pub(crate) struct Materials { + pub(crate) default_2d: MatGlob, +} diff --git a/crates/modor_graphics/src/runner.rs b/crates/modor_graphics/src/runner.rs index e35076cd..2fa6fa2a 100644 --- a/crates/modor_graphics/src/runner.rs +++ b/crates/modor_graphics/src/runner.rs @@ -160,7 +160,7 @@ where let instance = gpu_manager.instance.clone(); let gpu = gpu_manager.get_or_init().clone(); let surface = app.get_mut::().create_surface(&instance, None); - app.get_mut::().set_surface(&gpu, surface); + app.take::(|window, app| window.set_surface(app, &gpu, surface)); } else { let app = self .app @@ -173,7 +173,7 @@ where let gpu_manager = app.get_mut::(); gpu_manager.configure_window(&surface); let gpu = gpu_manager.get_or_init().clone(); - app.get_mut::().set_surface(&gpu, surface); + app.take::(|window, app| window.set_surface(app, &gpu, surface)); app.take::(State::update); // initialize before shaders app.get_mut::(); self.gamepads = Some(Gamepads::new(app)); diff --git a/crates/modor_graphics/src/shader/mod.rs b/crates/modor_graphics/src/shader/mod.rs index 1b117399..1f79c6ba 100644 --- a/crates/modor_graphics/src/shader/mod.rs +++ b/crates/modor_graphics/src/shader/mod.rs @@ -1,5 +1,6 @@ use crate::anti_aliasing::SupportedAntiAliasingModes; use crate::gpu::{Gpu, GpuManager}; +use crate::material::MaterialManager; use crate::mesh::{Vertex, VertexBuffer}; use crate::model::Instance; use crate::shader::loaded::ShaderLoaded; @@ -11,7 +12,6 @@ use log::error; use modor::{App, FromApp, Glob, GlobRef, Update, Updater}; use modor_resources::{Res, ResSource, ResUpdater, Resource, ResourceError, Source}; use std::marker::PhantomData; -use std::mem; use std::ops::Deref; use std::sync::Arc; use wgpu::{ @@ -48,7 +48,7 @@ where fn from_app(app: &mut App) -> Self { Self { inner: Glob::>::from_app_with(app, |res, app| { - res.get_mut(app).instance_size = mem::size_of::(); + res.get_mut(app).instance_size = size_of::(); }), phantom: PhantomData, } @@ -132,7 +132,7 @@ where /// - binding `0`: camera data /// - group `1` /// - binding `0`: material data (`Material` struct corresponds to -/// [`Material::Data`] on Rust side) +/// the Rust struct implementing [`Material`] trait) /// - binding `(i * 2)`: `texture_2d` value corresponding to texture `i` /// - binding `(i * 2 + 1)`: `sampler` value corresponding to texture `i` /// @@ -197,10 +197,18 @@ impl Resource for Shader { }) } - fn on_load(&mut self, app: &mut App, loaded: Self::Loaded, source: &ResSource) { + fn on_load( + &mut self, + app: &mut App, + index: usize, + loaded: Self::Loaded, + source: &ResSource, + ) { self.loaded = loaded; self.source = source.clone(); self.update(app); + app.get_mut::() + .register_loaded_shader(index); } } diff --git a/crates/modor_graphics/src/sprite.rs b/crates/modor_graphics/src/sprite.rs index d4facd50..05ab941e 100644 --- a/crates/modor_graphics/src/sprite.rs +++ b/crates/modor_graphics/src/sprite.rs @@ -1,5 +1,5 @@ -use crate::{DefaultMaterial2D, IntoMat, Mat, Model2D}; -use modor::{App, Builder}; +use crate::{DefaultMaterial2D, MatGlob, Model2D}; +use modor::{App, Builder, FromApp}; /// A rendered 2D object that can be colored or textured. /// @@ -13,23 +13,25 @@ use modor::{App, Builder}; pub struct Sprite2D { /// Material of the sprite, i.e. the aspect. #[builder(form(closure))] - pub material: Mat, + pub material: MatGlob, /// Model of the sprite, i.e. where the sprite is rendered. #[builder(form(closure))] - pub model: Model2D, + pub model: Model2D, } -impl Sprite2D { - /// Creates a new sprite. - pub fn new(app: &mut App) -> Self { - let material = DefaultMaterial2D::new(app).into_mat(app); - let model = Model2D::new(app, material.glob()); - Self { material, model } +impl FromApp for Sprite2D { + fn from_app(app: &mut App) -> Self { + let material = MatGlob::from_app(app); + Self { + model: Model2D::new(app).with_material(material.to_ref()), + material, + } } +} +impl Sprite2D { /// Updates the sprite. pub fn update(&mut self, app: &mut App) { - self.material.update(app); self.model.update(app); } } diff --git a/crates/modor_graphics/src/target.rs b/crates/modor_graphics/src/target.rs index 28b4caf2..1f9d9604 100644 --- a/crates/modor_graphics/src/target.rs +++ b/crates/modor_graphics/src/target.rs @@ -1,12 +1,13 @@ use crate::gpu::Gpu; +use crate::material::MaterialManager; use crate::mesh::Mesh; use crate::size::NonZeroSize; use crate::{ validation, AntiAliasingMode, Camera2DGlob, Color, InstanceGroup2DProperties, InstanceGroups2D, - MaterialGlob, Shader, Size, Texture, + Mat, Shader, Size, Texture, }; use log::{error, trace}; -use modor::{App, FromApp, Glob, Global, Globals, StateHandle}; +use modor::{App, FromApp, Global, Globals, StateHandle}; use wgpu::{ CommandEncoder, CommandEncoderDescriptor, Extent3d, IndexFormat, LoadOp, Operations, RenderPass, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, @@ -30,7 +31,8 @@ use wgpu::{ /// /// impl State for Root { /// fn init(&mut self, app: &mut App) { -/// app.get_mut::().target.background_color = Color::RED; +/// let target = app.get_mut::().target.to_ref().get_mut(app); +/// target.background_color = Color::RED; /// } /// } /// ``` @@ -47,58 +49,62 @@ pub struct Target { /// Default is [`AntiAliasingMode::None`]. pub anti_aliasing: AntiAliasingMode, pub(crate) supported_anti_aliasing_modes: Vec, + size: Size, texture_format: TextureFormat, loaded: Option, is_error_logged: bool, is_incompatible_anti_aliasing_logged: bool, - glob: Glob, + old_anti_aliasing: AntiAliasingMode, + index: usize, cameras: StateHandle>, - materials: StateHandle>, + materials: StateHandle>, meshes: StateHandle>, } -impl Target { - /// Returns a reference to global data. - pub fn glob(&self) -> &Glob { - &self.glob - } - - /// Returns the sorted list of all supported [`AntiAliasingMode`]. - pub fn supported_anti_aliasing_modes(&self) -> &[AntiAliasingMode] { - &self.supported_anti_aliasing_modes - } - - pub(crate) fn new(app: &mut App) -> Self { +impl FromApp for Target { + fn from_app(app: &mut App) -> Self { Self { background_color: Color::BLACK, anti_aliasing: AntiAliasingMode::None, supported_anti_aliasing_modes: vec![AntiAliasingMode::None], + size: Size::ZERO, texture_format: Texture::DEFAULT_FORMAT, loaded: None, is_error_logged: false, is_incompatible_anti_aliasing_logged: false, - glob: Glob::from_app(app), + old_anti_aliasing: AntiAliasingMode::None, + index: 0, cameras: app.handle(), materials: app.handle(), meshes: app.handle(), } } +} + +impl Global for Target { + fn init(&mut self, _app: &mut App, index: usize) { + self.index = index; + } +} + +impl Target { + /// Returns the size of the target in pixels. + pub fn size(&self) -> Size { + self.size + } + + /// Returns the sorted list of all supported [`AntiAliasingMode`]. + pub fn supported_anti_aliasing_modes(&self) -> &[AntiAliasingMode] { + &self.supported_anti_aliasing_modes + } pub(crate) fn disable(&mut self) { self.loaded = None; } - pub(crate) fn enable( - &mut self, - app: &mut App, - gpu: &Gpu, - size: NonZeroSize, - format: TextureFormat, - ) { - let glob = self.glob.get_mut(app); - glob.size = size.into(); - glob.anti_aliasing = self.anti_aliasing; + pub(crate) fn enable(&mut self, gpu: &Gpu, size: NonZeroSize, format: TextureFormat) { let anti_aliasing = self.fixed_anti_aliasing(); + self.size = size.into(); self.texture_format = format; self.loaded = Some(LoadedTarget { color_buffer_view: Self::create_color_buffer_view( @@ -109,11 +115,13 @@ impl Target { ), depth_buffer_view: Self::create_depth_buffer_view(gpu, size, anti_aliasing), }); + self.old_anti_aliasing = self.anti_aliasing; } pub(crate) fn render(&mut self, app: &mut App, gpu: &Gpu, view: TextureView) { + app.take(MaterialManager::update_material_bind_groups); app.get_mut::().sync(gpu); - self.update_loaded(app, gpu); + self.update_loaded(gpu); let anti_aliasing = self.fixed_anti_aliasing(); let loaded = self .loaded @@ -139,12 +147,10 @@ impl Target { self.log_error(result); } - fn update_loaded(&mut self, app: &mut App, gpu: &Gpu) { - let glob = self.glob.get_mut(app); - if self.anti_aliasing != glob.anti_aliasing { - glob.anti_aliasing = self.anti_aliasing; - let size = glob.size.into(); - self.enable(app, gpu, size, self.texture_format); + fn update_loaded(&mut self, gpu: &Gpu) { + if self.anti_aliasing != self.old_anti_aliasing { + let size = self.size.into(); + self.enable(gpu, size, self.texture_format); } } @@ -292,13 +298,19 @@ impl Target { .get(app) .get(group.camera) .map_or(false, |camera| { - camera.targets.contains(&self.glob().to_ref()) + camera + .targets + .iter() + .any(|target| target.index() == self.index) }) && self .materials .get(app) .get(group.material) - .map_or(false, |material| material.is_transparent == is_transparent) + .map_or(false, |material| { + (material.is_transparent || material.has_transparent_texture) + == is_transparent + }) }) } @@ -314,16 +326,13 @@ impl Target { ) -> Option<()> { let material = self.materials.get(app).get(group.material)?; let shader = material.shader.get(app); - if material.binding_ids.bind_group_layout != shader.material_bind_group_layout.global_id() { - return None; - } let camera = self.cameras.get(app).get(group.camera)?; let mesh = self.meshes.get(app).get(group.mesh)?; let group = &groups.groups[&group]; let primary_buffer = group.primary_buffer()?; let pipeline_params = (self.texture_format, anti_aliasing); pass.set_pipeline(shader.pipelines.get(&pipeline_params)?); - pass.set_bind_group(Shader::CAMERA_GROUP, camera.bind_group(self.glob())?, &[]); + pass.set_bind_group(Shader::CAMERA_GROUP, camera.bind_group(self.index)?, &[]); pass.set_bind_group(Shader::MATERIAL_GROUP, &material.bind_group.inner, &[]); pass.set_index_buffer(mesh.index_buffer.slice(), IndexFormat::Uint16); pass.set_vertex_buffer(0, mesh.vertex_buffer.slice()); @@ -376,21 +385,3 @@ struct LoadedTarget { color_buffer_view: TextureView, depth_buffer_view: TextureView, } - -/// The global data of a [`Target`]. -#[non_exhaustive] -#[derive(Debug, Global)] -pub struct TargetGlob { - /// Size of the target in pixels. - pub size: Size, - anti_aliasing: AntiAliasingMode, -} - -impl Default for TargetGlob { - fn default() -> Self { - Self { - size: Size::ZERO, - anti_aliasing: AntiAliasingMode::None, - } - } -} diff --git a/crates/modor_graphics/src/testing.rs b/crates/modor_graphics/src/testing.rs index 23418f83..affda5d5 100644 --- a/crates/modor_graphics/src/testing.rs +++ b/crates/modor_graphics/src/testing.rs @@ -27,7 +27,7 @@ use std::{env, fs}; /// - the expected and actual textures are different. /// - the [`Texture`] buffer is empty. /// - the actual texture found in the [`Texture`] doesn't match the -/// expected one saved in `$CARGO_MANIFEST_DIR/tests/expected/{key}.png`. +/// expected one saved in `$CARGO_MANIFEST_DIR/tests/expected/{key}.png`. /// - there is an I/O error while reading or writing the expected or the diff texture. /// /// # Examples @@ -86,7 +86,7 @@ pub fn assert_same(app: &App, texture: &Glob>, key: impl AsRef /// - the expected and actual textures are not similar. /// - the [`Texture`] buffer is empty. /// - the actual texture found in the [`Texture`] doesn't match the -/// expected one saved in `$CARGO_MANIFEST_DIR/tests/expected/{key}.png`. +/// expected one saved in `$CARGO_MANIFEST_DIR/tests/expected/{key}.png`. /// - there is an I/O error while reading or writing the expected or the diff texture. /// /// # Examples @@ -153,7 +153,7 @@ pub fn assert_max_component_diff( /// - the expected and actual textures are not similar. /// - the [`Texture`] buffer is empty. /// - the actual texture found in the [`Texture`] doesn't match the -/// expected one saved in `$CARGO_MANIFEST_DIR/tests/expected/{key}.png`. +/// expected one saved in `$CARGO_MANIFEST_DIR/tests/expected/{key}.png`. /// - there is an I/O error while reading or writing the expected or the diff texture. /// /// # Examples diff --git a/crates/modor_graphics/src/texture.rs b/crates/modor_graphics/src/texture.rs index ce8db8b4..5ff7d053 100644 --- a/crates/modor_graphics/src/texture.rs +++ b/crates/modor_graphics/src/texture.rs @@ -1,15 +1,15 @@ use crate::anti_aliasing::SupportedAntiAliasingModes; use crate::gpu::{Gpu, GpuManager}; +use crate::material::MaterialManager; use crate::size::NonZeroSize; use crate::texture::internal::TextureLoaded; -use crate::{AntiAliasingMode, Camera2D, Color, Size, Target, TargetGlob}; +use crate::{AntiAliasingMode, Camera2D, Color, Size, Target}; use getset::{CopyGetters, Getters}; use image::{DynamicImage, RgbaImage}; use modor::{App, FromApp, Glob, GlobRef, Globals, State, StateHandle, Update, Updater}; use modor_input::modor_math::Vec2; use modor_resources::{Res, ResSource, ResUpdater, Resource, ResourceError, Source}; use std::marker::PhantomData; -use std::mem; use std::num::NonZeroU32; use wgpu::{ AddressMode, Buffer, BufferView, CommandEncoderDescriptor, Extent3d, FilterMode, @@ -39,8 +39,10 @@ use wgpu::{ /// resources.texture.to_ref(), /// )); /// Self { -/// sprite: Sprite2D::new(app) -/// .with_material(|m| m.texture = texture) +/// sprite: Sprite2D::from_app(app) +/// .with_material(|m| DefaultMaterial2DUpdater::default() +/// .texture(texture.to_ref()) +/// .apply(app, m)) /// .with_model(|m| m.position = position) /// .with_model(|m| m.size = size) /// .with_model(|m| m.camera = camera), @@ -139,7 +141,7 @@ pub struct Texture { /// /// Doesn't have effect if [`is_target_enabled`](Texture::is_target_enabled) is `false`. #[updater(inner_type, field, for_field)] - camera_targets: PhantomData>>, + camera_targets: PhantomData>>, /// General resource parameters. #[updater(inner_type, field)] res: PhantomData>, @@ -147,7 +149,7 @@ pub struct Texture { /// /// Doesn't have effect if [`is_target_enabled`](Texture::is_target_enabled) is `false`. #[getset(get = "pub")] - target: Target, + target: Glob, /// Default camera of the texture target. /// /// Doesn't have effect if [`is_target_enabled`](Texture::is_target_enabled) is `false`. @@ -166,12 +168,12 @@ impl FromApp for Texture { fn from_app(app: &mut App) -> Self { app.create::(); let gpu = app.get_mut::().get_or_init().clone(); - let mut target = Target::new(app); - target.supported_anti_aliasing_modes = app + let target = Glob::::from_app(app); + target.get_mut(app).supported_anti_aliasing_modes = app .get_mut::() .get(&gpu, Self::DEFAULT_FORMAT) .to_vec(); - let camera = Camera2D::new(app, vec![target.glob().to_ref()]); + let camera = Camera2D::new(app, vec![target.to_ref()]); let loaded = TextureLoaded::default(); let texture = Self::create_texture(&gpu, &loaded); Self::write_texture(&gpu, &loaded, &texture); @@ -219,18 +221,22 @@ impl Resource for Texture { })) } - fn on_load(&mut self, app: &mut App, loaded: Self::Loaded, _source: &ResSource) { - let gpu = app.get_mut::().get_or_init(); + fn on_load( + &mut self, + app: &mut App, + index: usize, + loaded: Self::Loaded, + _source: &ResSource, + ) { + let gpu = app.get_mut::().get_or_init().clone(); self.loaded = loaded; - self.texture = Self::create_texture(gpu, &self.loaded); - Self::write_texture(gpu, &self.loaded, &self.texture); + self.texture = Self::create_texture(&gpu, &self.loaded); + Self::write_texture(&gpu, &self.loaded, &self.texture); self.view = self.texture.create_view(&TextureViewDescriptor::default()); - self.sampler = Self::create_sampler(gpu, self.is_repeated, self.is_smooth); - self.buffer = self - .is_buffer_enabled - .then(|| Self::create_buffer(gpu, self.size())); + self.sampler = Self::create_sampler(&gpu, self.is_repeated, self.is_smooth); self.submission_index = None; - self.update(app); + self.update(app, true, index); + self.copy_texture_in_buffer(&gpu); } } @@ -318,10 +324,10 @@ impl Texture { }) } - fn update(&mut self, app: &mut App) { + fn update(&mut self, app: &mut App, is_reloaded: bool, texture_index: usize) { let gpu = app.get_mut::().get_or_init(); self.sampler = Self::create_sampler(gpu, self.is_repeated, self.is_smooth); - if self.buffer.is_none() && self.is_buffer_enabled { + if (self.buffer.is_none() || is_reloaded) && self.is_buffer_enabled { self.buffer = Some(Self::create_buffer(gpu, self.size())); } else if self.buffer.is_some() && !self.is_buffer_enabled { self.buffer = None; @@ -329,24 +335,22 @@ impl Texture { if self.is_target_enabled { let gpu = app.get_mut::().get_or_init().clone(); let size = self.size().into(); - self.target.enable(app, &gpu, size, Self::DEFAULT_FORMAT); + self.target + .get_mut(app) + .enable(&gpu, size, Self::DEFAULT_FORMAT); } else { - self.target.disable(); + self.target.get_mut(app).disable(); } + app.get_mut::() + .register_loaded_texture(texture_index); } - fn render(&mut self, app: &mut App) { - let gpu = app.get_mut::().get_or_init().clone(); + fn prepare_rendering(&mut self, app: &mut App) -> (GlobRef, TextureView) { self.camera.update(app); - self.render_target(app, &gpu); - self.copy_texture_in_buffer(&gpu); - } - - fn render_target(&mut self, app: &mut App, gpu: &Gpu) { - if self.is_target_enabled { - let view = self.texture.create_view(&TextureViewDescriptor::default()); - self.target.render(app, gpu, view); - } + ( + self.target.to_ref(), + self.texture.create_view(&TextureViewDescriptor::default()), + ) } fn create_texture(gpu: &Gpu, loaded: &TextureLoaded) -> wgpu::Texture { @@ -512,7 +516,7 @@ impl Texture { #[allow(clippy::cast_possible_truncation)] fn calculate_unpadded_row_bytes(width: u32) -> u32 { - let bytes_per_pixel = mem::size_of::() as u32; + let bytes_per_pixel = size_of::() as u32; width * bytes_per_pixel } } @@ -523,11 +527,11 @@ impl TextureUpdater<'_> { glob.take(app, |tex, app| { Update::apply( &mut self.target_anti_aliasing, - &mut tex.target.anti_aliasing, + &mut tex.target.get_mut(app).anti_aliasing, ); Update::apply( &mut self.target_background_color, - &mut tex.target.background_color, + &mut tex.target.get_mut(app).background_color, ); Update::apply(&mut self.camera_position, &mut tex.camera.position); Update::apply(&mut self.camera_size, &mut tex.camera.size); @@ -538,7 +542,7 @@ impl TextureUpdater<'_> { | Update::apply_checked(&mut self.is_buffer_enabled, &mut tex.is_buffer_enabled) | Update::apply_checked(&mut self.is_target_enabled, &mut tex.is_target_enabled) { - tex.update(app); + tex.update(app, false, glob.index()); } }); if let Some(res) = self.res.take_value(|| unreachable!()) { @@ -583,11 +587,35 @@ struct TextureManager; impl State for TextureManager { fn update(&mut self, app: &mut App) { - app.take::>, _>(|textures, app| { - for texture in textures.iter_mut() { - texture.render(app); - } - }); + let texture_indexes = app + .get_mut::>>() + .iter_enumerated() + .filter(|(_, texture)| texture.is_target_enabled) + .map(|(index, _)| index) + .collect::>(); + for texture_index in texture_indexes { + let gpu = app.get_mut::().get_or_init().clone(); + let (target, view) = + Self::run_on_texture(app, texture_index, Texture::prepare_rendering); + target.take(app, |target, app| target.render(app, &gpu, view)); + Self::run_on_texture(app, texture_index, |t, _| t.copy_texture_in_buffer(&gpu)); + } + } +} + +impl TextureManager { + fn run_on_texture( + app: &mut App, + texture_index: usize, + f: impl FnOnce(&mut Texture, &mut App) -> O, + ) -> O { + app.take::>, _>(|glob, app| { + f( + glob.get_mut(texture_index) + .expect("internal error: invalid texture index"), + app, + ) + }) } } diff --git a/crates/modor_graphics/src/window.rs b/crates/modor_graphics/src/window.rs index 4ff314c2..a185b07e 100644 --- a/crates/modor_graphics/src/window.rs +++ b/crates/modor_graphics/src/window.rs @@ -2,7 +2,7 @@ use crate::anti_aliasing::SupportedAntiAliasingModes; use crate::gpu::{Gpu, GpuManager}; use crate::size::NonZeroSize; use crate::{platform, Camera2D, FrameRate, Size, Target}; -use modor::{App, FromApp, State}; +use modor::{App, FromApp, Glob, State}; use std::mem; use std::sync::Arc; use wgpu::{ @@ -27,18 +27,19 @@ use winit::dpi::PhysicalSize; /// /// impl State for Root { /// fn init(&mut self, app: &mut App) { -/// let window = app.get_mut::(); -/// window.title = "My App".into(); -/// window.frame_rate = FrameRate::Unlimited; -/// window.target.background_color = Color::GRAY; -/// // enable maximum supported anti-aliasing -/// window.target.anti_aliasing = window -/// .target -/// .supported_anti_aliasing_modes() -/// .iter() -/// .copied() -/// .max() -/// .unwrap_or_default(); +/// app.take::(|window, app| { +/// window.title = "My App".into(); +/// window.frame_rate = FrameRate::Unlimited; +/// let target = window.target.get_mut(app); +/// target.background_color = Color::GRAY; +/// // enable maximum supported anti-aliasing +/// target.anti_aliasing = target +/// .supported_anti_aliasing_modes() +/// .iter() +/// .copied() +/// .max() +/// .unwrap_or_default(); +/// }); /// } /// } /// ``` @@ -52,7 +53,7 @@ pub struct Window { /// Default is `true`. pub is_cursor_visible: bool, /// Render target of the window. - pub target: Target, + pub target: Glob, /// The rendering frame rate limit. /// /// Default is [`FrameRate::VSync`](FrameRate::VSync). @@ -67,8 +68,8 @@ pub struct Window { impl FromApp for Window { fn from_app(app: &mut App) -> Self { - let target = Target::new(app); - let camera = Camera2D::new(app, vec![target.glob().to_ref()]); + let target = Glob::from_app(app); + let camera = Camera2D::new(app, vec![target.to_ref()]); Self { title: String::new(), is_cursor_visible: true, @@ -131,16 +132,17 @@ impl Window { surface } - pub(crate) fn set_surface(&mut self, gpu: &Gpu, surface: Surface<'static>) { + pub(crate) fn set_surface(&mut self, app: &mut App, gpu: &Gpu, surface: Surface<'static>) { let size = self .surface_size() .expect("internal error: not configured window"); let surface = WindowSurface::new(gpu, surface, size); let format = surface.surface_config.format; self.surface = WindowSurfaceState::Loading(surface); - self.target.supported_anti_aliasing_modes = SupportedAntiAliasingModes::default() - .get(gpu, format) - .to_vec(); + self.target.get_mut(app).supported_anti_aliasing_modes = + SupportedAntiAliasingModes::default() + .get(gpu, format) + .to_vec(); } pub(crate) fn texture_format(&self) -> Option { @@ -170,7 +172,9 @@ impl Window { let size = self.surface_size(); if let Some(surface) = self.surface.take_new() { let texture_format = surface.surface_config.format; - self.target.enable(app, &gpu, surface.size, texture_format); + self.target + .get_mut(app) + .enable(&gpu, surface.size, texture_format); self.surface = WindowSurfaceState::Loaded(surface); } if let WindowSurfaceState::Loaded(surface) = &mut self.surface { @@ -178,11 +182,11 @@ impl Window { surface.update(&gpu, size, self.frame_rate); if size != self.old_state.size { let texture_format = surface.surface_config.format; - self.target.enable(app, &gpu, size, texture_format); + self.target.get_mut(app).enable(&gpu, size, texture_format); self.old_state.size = size; self.camera.update(app); // force camera update to avoid distortion } - surface.render(app, &gpu, &mut self.target); + surface.render(app, &gpu, &self.target); } } @@ -259,7 +263,7 @@ impl WindowSurface { } } - fn render(&self, app: &mut App, gpu: &Gpu, target: &mut Target) { + fn render(&self, app: &mut App, gpu: &Gpu, target: &Glob) { let texture = self .surface .get_current_texture() @@ -267,7 +271,7 @@ impl WindowSurface { let view = texture .texture .create_view(&TextureViewDescriptor::default()); - target.render(app, gpu, view); + target.take(app, |target, app| target.render(app, gpu, view)); texture.present(); } diff --git a/crates/modor_graphics/tests/integration/anti_aliasing.rs b/crates/modor_graphics/tests/integration/anti_aliasing.rs index 5ca43ab1..901de8fc 100644 --- a/crates/modor_graphics/tests/integration/anti_aliasing.rs +++ b/crates/modor_graphics/tests/integration/anti_aliasing.rs @@ -12,6 +12,7 @@ fn retrieve_supported_modes() { let supported_modes = target_glob(&mut app) .get(&app) .target() + .get(&app) .supported_anti_aliasing_modes(); assert_eq!(supported_modes[0], AntiAliasingMode::None); assert!(supported_modes.contains(&AntiAliasingMode::MsaaX4)); @@ -36,7 +37,11 @@ fn enable_supported_anti_aliasing() { fn enable_unsupported_anti_aliasing() { let mut app = App::new::(Level::Info); let target = target_glob(&mut app); - let supported_modes = target.get(&app).target().supported_anti_aliasing_modes(); + let supported_modes = target + .get(&app) + .target() + .get(&app) + .supported_anti_aliasing_modes(); if supported_modes.contains(&AntiAliasingMode::MsaaX16) { return; } @@ -53,20 +58,12 @@ fn target_glob(app: &mut App) -> GlobRef> { app.get_mut::().target.to_ref() } +#[derive(FromApp)] struct Root { sprite: Sprite2D, target: Glob>, } -impl FromApp for Root { - fn from_app(app: &mut App) -> Self { - Self { - sprite: Sprite2D::new(app), - target: Glob::from_app(app), - } - } -} - impl State for Root { fn init(&mut self, app: &mut App) { TextureUpdater::default() diff --git a/crates/modor_graphics/tests/integration/camera.rs b/crates/modor_graphics/tests/integration/camera.rs index df25b70a..67821109 100644 --- a/crates/modor_graphics/tests/integration/camera.rs +++ b/crates/modor_graphics/tests/integration/camera.rs @@ -1,9 +1,7 @@ use log::Level; use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::testing::assert_same; -use modor_graphics::{ - Camera2D, Size, Sprite2D, TargetGlob, Texture, TextureSource, TextureUpdater, -}; +use modor_graphics::{Camera2D, Size, Sprite2D, Target, Texture, TextureSource, TextureUpdater}; use modor_input::modor_math::Vec2; use modor_internal::assert_approx_eq; use modor_resources::testing::wait_resources; @@ -70,30 +68,21 @@ fn camera(app: &mut App) -> &Camera2D { root(app).target.to_ref().get_mut(app).camera() } -fn other_target_glob(app: &App, other_target: &Glob>) -> GlobRef { - other_target.get(app).target().glob().to_ref() +fn other_target_glob(app: &App, other_target: &Glob>) -> GlobRef { + other_target.get(app).target().to_ref() } fn root(app: &mut App) -> &mut Root { app.get_mut::() } +#[derive(FromApp)] struct Root { sprite: Sprite2D, target: Glob>, other_target: Glob>, } -impl FromApp for Root { - fn from_app(app: &mut App) -> Self { - Self { - sprite: Sprite2D::new(app), - target: Glob::from_app(app), - other_target: Glob::from_app(app), - } - } -} - impl State for Root { fn init(&mut self, app: &mut App) { TextureUpdater::default() diff --git a/crates/modor_graphics/tests/integration/material/complex.rs b/crates/modor_graphics/tests/integration/material/complex.rs index 5e5ef3df..e12437ec 100644 --- a/crates/modor_graphics/tests/integration/material/complex.rs +++ b/crates/modor_graphics/tests/integration/material/complex.rs @@ -1,10 +1,10 @@ use bytemuck::{Pod, Zeroable}; use log::Level; -use modor::{App, FromApp, Glob, GlobRef, State}; +use modor::{App, FromApp, Glob, State}; use modor_graphics::testing::assert_same; use modor_graphics::{ - Color, IntoMat, Mat, Material, Model2D, Model2DGlob, ShaderGlob, ShaderGlobRef, ShaderUpdater, - Size, Texture, TextureSource, TextureUpdater, + Color, MatGlob, MatUpdater, Material, Model2D, Model2DGlob, ShaderGlob, ShaderUpdater, Size, + Texture, TextureSource, TextureUpdater, }; use modor_input::modor_math::Vec2; use modor_resources::testing::wait_resources; @@ -28,9 +28,9 @@ fn root(app: &mut App) -> &mut Root { struct Root { texture: Glob>, shader: ShaderGlob, - material: Mat, - model1: Model2D, - model2: Model2D, + material: MatGlob, + model1: Model2D, + model2: Model2D, target: Glob>, } @@ -39,9 +39,9 @@ impl FromApp for Root { let target = Glob::from_app(app); let texture = Glob::from_app(app); let shader = ShaderGlob::from_app(app); - let material = TestMaterial::new(&texture, &shader).into_mat(app); - let model1 = Model2D::new(app, material.glob()); - let model2 = Model2D::new(app, material.glob()); + let material = MatGlob::from_app(app); + let model1 = Model2D::new(app).with_material(material.to_ref()); + let model2 = Model2D::new(app).with_material(material.to_ref()); Self { texture, shader, @@ -62,6 +62,10 @@ impl State for Root { ShaderUpdater::default() .res(ResUpdater::default().path("../tests/assets/complex.wgsl")) .apply(app, &self.shader); + MatUpdater::default() + .textures(vec![self.texture.to_ref()]) + .shader(self.shader.to_ref()) + .apply(app, &self.material); self.model1.position = Vec2::new(-0.25, 0.); self.model1.size = Vec2::new(0.25, 0.5); self.model1.camera = self.target.get(app).camera().glob().to_ref(); @@ -76,70 +80,44 @@ impl State for Root { } fn update(&mut self, app: &mut App) { - self.material.update(app); self.model1.update(app); self.model2.update(app); } } +#[repr(C)] +#[derive(Clone, Copy, Zeroable, Pod)] struct TestMaterial { - color: Color, - texture: GlobRef>, - shader: ShaderGlobRef, + color: [f32; 4], } -impl Material for TestMaterial { - type Data = TestMaterialData; - type InstanceData = TestInstanceData; - - fn shader(&self) -> ShaderGlobRef { - self.shader.clone() - } - - fn textures(&self) -> Vec>> { - vec![self.texture.clone()] +impl Default for TestMaterial { + fn default() -> Self { + Self { + color: Color::DARK_GRAY.into(), + } } +} - fn is_transparent(&self) -> bool { - self.color.a > 0. && self.color.a < 1. - } +impl Material for TestMaterial { + type InstanceData = TestMaterialInstance; - fn data(&self) -> Self::Data { - TestMaterialData { - color: self.color.into(), - } - } + fn init(_app: &mut App, _glob: &MatGlob) {} fn instance_data(_app: &mut App, model: &Glob) -> Self::InstanceData { vec![ - TestInstanceData { + TestMaterialInstance { color: [0., 0., 1., 1.], }, - TestInstanceData { + TestMaterialInstance { color: [0., 1., 0., 1.], }, ][model.index()] } } -impl TestMaterial { - fn new(texture: &Glob>, shader: &ShaderGlob) -> Self { - Self { - color: Color::DARK_GRAY, - texture: texture.to_ref(), - shader: shader.to_ref(), - } - } -} - -#[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] -struct TestMaterialData { - color: [f32; 4], -} - #[repr(C)] #[derive(Clone, Copy, Zeroable, Pod)] -struct TestInstanceData { +struct TestMaterialInstance { color: [f32; 4], } diff --git a/crates/modor_graphics/tests/integration/material/default_2d.rs b/crates/modor_graphics/tests/integration/material/default_2d.rs index f690c472..d835cc36 100644 --- a/crates/modor_graphics/tests/integration/material/default_2d.rs +++ b/crates/modor_graphics/tests/integration/material/default_2d.rs @@ -2,7 +2,8 @@ use log::Level; use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::testing::assert_same; use modor_graphics::{ - Color, DefaultMaterial2D, IntoMat, Mat, Model2D, Size, Texture, TextureSource, TextureUpdater, + Color, DefaultMaterial2D, DefaultMaterial2DUpdater, MatGlob, Model2D, Size, Texture, + TextureSource, TextureUpdater, }; use modor_input::modor_math::Vec2; use modor_resources::testing::wait_resources; @@ -14,21 +15,35 @@ fn create_default() { wait_resources(&mut app); app.update(); assert_same(&app, &target, "material#white"); - assert_eq!(root(&mut app).material.color, Color::WHITE); } #[modor::test(disabled(windows, macos, android, wasm))] fn set_properties() { let (mut app, target) = configure_app(); let texture = root(&mut app).texture.to_ref(); - root(&mut app).material.texture = texture; - root(&mut app).material.is_ellipse = true; - root(&mut app).material.color = Color::DARK_GRAY; - root(&mut app).material.texture_size = Vec2::ONE * 0.75; - root(&mut app).material.texture_position = Vec2::ONE * 0.25; wait_resources(&mut app); + app.take::(|root, app| { + DefaultMaterial2DUpdater::default() + .texture(texture) + .is_ellipse(true) + .color(Color::DARK_GRAY) + .texture_size(Vec2::ONE * 0.75) + .texture_position(Vec2::ONE * 0.25) + .apply(app, &root.material); + }); app.update(); assert_same(&app, &target, "material#custom_default"); + app.take::(|root, app| { + DefaultMaterial2DUpdater::default() + .for_texture(|_| ()) + .for_is_ellipse(|e| *e = false) + .for_color(|c| *c = Color::WHITE) + .for_texture_size(|s| *s = Vec2::ONE * 0.25) + .for_texture_position(|p| *p = Vec2::ZERO) + .apply(app, &root.material); + }); + app.update(); + assert_same(&app, &target, "material#red"); } fn configure_app() -> (App, GlobRef>) { @@ -43,8 +58,8 @@ fn root(app: &mut App) -> &mut Root { struct Root { texture: Glob>, - material: Mat, - model: Model2D, + material: MatGlob, + model: Model2D, target: Glob>, } @@ -52,8 +67,8 @@ impl FromApp for Root { fn from_app(app: &mut App) -> Self { let target = Glob::from_app(app); let texture = Glob::from_app(app); - let material = DefaultMaterial2D::new(app).into_mat(app); - let model = Model2D::new(app, material.glob()); + let material = MatGlob::from_app(app); + let model = Model2D::new(app).with_material(material.to_ref()); Self { texture, material, @@ -79,7 +94,6 @@ impl State for Root { } fn update(&mut self, app: &mut App) { - self.material.update(app); self.model.update(app); } } diff --git a/crates/modor_graphics/tests/integration/material/empty.rs b/crates/modor_graphics/tests/integration/material/empty.rs index 81427238..6d02dad4 100644 --- a/crates/modor_graphics/tests/integration/material/empty.rs +++ b/crates/modor_graphics/tests/integration/material/empty.rs @@ -2,11 +2,11 @@ use bytemuck::{Pod, Zeroable}; use log::Level; -use modor::{App, FromApp, Glob, GlobRef, State}; +use modor::{App, FromApp, Glob, State}; use modor_graphics::testing::assert_same; use modor_graphics::{ - IntoMat, Mat, Material, Model2D, Model2DGlob, ShaderGlob, ShaderGlobRef, ShaderUpdater, Size, - Texture, TextureSource, TextureUpdater, + MatGlob, MatUpdater, Material, Model2D, Model2DGlob, ShaderGlob, ShaderUpdater, Size, Texture, + TextureSource, TextureUpdater, }; use modor_input::modor_math::Vec2; use modor_resources::testing::wait_resources; @@ -27,8 +27,8 @@ fn root(app: &mut App) -> &mut Root { struct Root { shader: ShaderGlob, - material: Mat, - model: Model2D, + material: MatGlob, + model: Model2D, target: Glob>, } @@ -36,8 +36,8 @@ impl FromApp for Root { fn from_app(app: &mut App) -> Self { let target = Glob::from_app(app); let shader = ShaderGlob::from_app(app); - let material = TestMaterial::new(&shader).into_mat(app); - let model = Model2D::new(app, material.glob()); + let material = MatGlob::from_app(app); + let model = Model2D::new(app).with_material(material.to_ref()); Self { shader, material, @@ -52,6 +52,9 @@ impl State for Root { ShaderUpdater::default() .res(ResUpdater::default().path("../tests/assets/red.wgsl")) .apply(app, &self.shader); + MatUpdater::default() + .shader(self.shader.to_ref()) + .apply(app, &self.material); self.model.size = Vec2::ONE * 0.5; self.model.camera = self.target.get(app).camera().glob().to_ref(); TextureUpdater::default() @@ -62,46 +65,18 @@ impl State for Root { } fn update(&mut self, app: &mut App) { - self.material.update(app); self.model.update(app); } } -struct TestMaterial { - shader: ShaderGlobRef, -} +#[repr(C)] +#[derive(Clone, Copy, Zeroable, Pod, FromApp)] +struct TestMaterial; impl Material for TestMaterial { - type Data = TestMaterialData; type InstanceData = (); - fn shader(&self) -> ShaderGlobRef { - self.shader.clone() - } - - fn textures(&self) -> Vec>> { - vec![] - } - - fn is_transparent(&self) -> bool { - false - } - - fn data(&self) -> Self::Data { - TestMaterialData - } + fn init(_app: &mut App, _glob: &MatGlob) {} fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} } - -impl TestMaterial { - fn new(shader: &ShaderGlob) -> Self { - Self { - shader: shader.to_ref(), - } - } -} - -#[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] -struct TestMaterialData; diff --git a/crates/modor_graphics/tests/integration/material/simple.rs b/crates/modor_graphics/tests/integration/material/simple.rs index 929eec72..59a36d8b 100644 --- a/crates/modor_graphics/tests/integration/material/simple.rs +++ b/crates/modor_graphics/tests/integration/material/simple.rs @@ -3,8 +3,8 @@ use log::Level; use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::testing::{assert_max_component_diff, assert_same}; use modor_graphics::{ - Color, IntoMat, Mat, Material, Model2D, Model2DGlob, ShaderGlob, ShaderGlobRef, ShaderUpdater, - Size, Texture, TextureSource, TextureUpdater, + Color, MatGlob, MatUpdater, Material, Model2D, Model2DGlob, ShaderGlob, ShaderUpdater, Size, + Texture, TextureSource, TextureUpdater, }; use modor_input::modor_math::Vec2; use modor_resources::testing::wait_resources; @@ -13,7 +13,11 @@ use modor_resources::{Res, ResUpdater}; #[modor::test(disabled(windows, macos, android, wasm))] fn set_textures_less_than_shader() { let (mut app, target) = configure_app(); - root(&mut app).material.textures = vec![]; + app.take::(|root, app| { + MatUpdater::default() + .textures(vec![]) + .apply(app, &root.material); + }); app.update(); app.update(); assert_same(&app, &target, "material#no_texture"); @@ -23,7 +27,11 @@ fn set_textures_less_than_shader() { fn set_textures_more_than_shader() { let (mut app, target) = configure_app(); let texture = root(&mut app).texture.to_ref(); - root(&mut app).material.textures = vec![texture.clone(), texture]; + app.take::(|root, app| { + MatUpdater::default() + .textures(vec![texture.clone(), texture]) + .apply(app, &root.material); + }); app.update(); assert_same(&app, &target, "material#default"); } @@ -31,7 +39,13 @@ fn set_textures_more_than_shader() { #[modor::test(disabled(windows, macos, android, wasm))] fn set_color_opaque() { let (mut app, target) = configure_app(); - root(&mut app).material.color = Color::WHITE; + app.take::(|root, app| { + MatUpdater::default() + .data(TestMaterial { + color: Color::WHITE.into(), + }) + .apply(app, &root.material); + }); app.update(); app.update(); assert_same(&app, &target, "material#lighter"); @@ -40,7 +54,14 @@ fn set_color_opaque() { #[modor::test(disabled(windows, macos, android, wasm))] fn set_color_transparent() { let (mut app, target) = configure_app(); - root(&mut app).material.color = Color::WHITE.with_alpha(0.5); + app.take::(|root, app| { + MatUpdater::default() + .data(TestMaterial { + color: Color::WHITE.with_alpha(0.5).into(), + }) + .is_transparent(true) + .apply(app, &root.material); + }); app.update(); app.update(); assert_max_component_diff(&app, &target, "material#alpha", 10, 1); @@ -50,7 +71,11 @@ fn set_color_transparent() { fn set_shader() { let (mut app, target) = configure_app(); let shader = root(&mut app).red_shader.to_ref(); - root(&mut app).material.shader = shader; + app.take::(|root, app| { + MatUpdater::default() + .shader(shader) + .apply(app, &root.material); + }); app.update(); app.update(); assert_same(&app, &target, "material#red"); @@ -73,8 +98,8 @@ struct Root { texture: Glob>, shader: ShaderGlob, red_shader: ShaderGlob, - material: Mat, - model: Model2D, + material: MatGlob, + model: Model2D, target: Glob>, } @@ -84,8 +109,8 @@ impl FromApp for Root { let texture = Glob::from_app(app); let shader = ShaderGlob::from_app(app); let red_shader = ShaderGlob::from_app(app); - let material = TestMaterial::new(&texture, &shader).into_mat(app); - let model = Model2D::new(app, material.glob()); + let material = MatGlob::from_app(app); + let model = Model2D::new(app).with_material(material.to_ref()); Self { texture, shader, @@ -109,6 +134,10 @@ impl State for Root { ShaderUpdater::default() .res(ResUpdater::default().path("../tests/assets/red.wgsl")) .apply(app, &self.red_shader); + MatUpdater::default() + .shader(self.shader.to_ref()) + .textures(vec![self.texture.to_ref()]) + .apply(app, &self.material); self.model.size = Vec2::ONE * 0.5; self.model.camera = self.target.get(app).camera().glob().to_ref(); TextureUpdater::default() @@ -119,54 +148,28 @@ impl State for Root { } fn update(&mut self, app: &mut App) { - self.material.update(app); self.model.update(app); } } +#[repr(C)] +#[derive(Clone, Copy, Zeroable, Pod)] struct TestMaterial { - color: Color, - textures: Vec>>, - shader: ShaderGlobRef, -} - -impl Material for TestMaterial { - type Data = TestMaterialData; - type InstanceData = (); - - fn shader(&self) -> ShaderGlobRef { - self.shader.clone() - } - - fn textures(&self) -> Vec>> { - self.textures.clone() - } - - fn is_transparent(&self) -> bool { - self.color.a > 0. && self.color.a < 1. - } - - fn data(&self) -> Self::Data { - TestMaterialData { - color: self.color.into(), - } - } - - fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} + color: [f32; 4], } -impl TestMaterial { - fn new(texture: &Glob>, shader: &ShaderGlob) -> Self { +impl Default for TestMaterial { + fn default() -> Self { Self { - color: Color::DARK_GRAY, - textures: vec![texture.to_ref()], - shader: shader.to_ref(), + color: Color::DARK_GRAY.into(), } } } -#[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] -struct TestMaterialData { - color: [f32; 4], +impl Material for TestMaterial { + type InstanceData = (); + + fn init(_app: &mut App, _glob: &MatGlob) {} + + fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} } diff --git a/crates/modor_graphics/tests/integration/model.rs b/crates/modor_graphics/tests/integration/model.rs index 9f34c2df..a8507545 100644 --- a/crates/modor_graphics/tests/integration/model.rs +++ b/crates/modor_graphics/tests/integration/model.rs @@ -2,8 +2,8 @@ use log::Level; use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::testing::{assert_max_component_diff, assert_same}; use modor_graphics::{ - Camera2DGlob, Color, DefaultMaterial2D, IntoMat, Mat, Model2D, Size, Texture, TextureSource, - TextureUpdater, + Camera2DGlob, Color, DefaultMaterial2D, DefaultMaterial2DUpdater, MatGlob, Model2D, Size, + Texture, TextureSource, TextureUpdater, }; use modor_input::modor_math::Vec2; use modor_physics::{Body2D, Body2DUpdater}; @@ -73,14 +73,20 @@ fn set_body() { fn set_z_index() { let (mut app, target) = configure_app(); let camera = camera1(&mut app); - let material1 = root(&mut app).material1.glob(); - let material2 = root(&mut app).material2.glob(); - let model2 = Model2D::new(&mut app, material2.clone()); - let model3 = Model2D::new(&mut app, material1); - let model4 = Model2D::new(&mut app, material2); + let material1 = root(&mut app).material1.to_ref(); + let material2 = root(&mut app).material2.to_ref(); + let model2 = Model2D::new(&mut app).with_material(material2.clone()); + let model3 = Model2D::new(&mut app).with_material(material1); + let model4 = Model2D::new(&mut app).with_material(material2); root(&mut app).models.extend([model2, model3, model4]); - root(&mut app).material1.color = Color::BLUE.with_alpha(0.5); - root(&mut app).material2.color = Color::GREEN; + app.take::(|root, app| { + DefaultMaterial2DUpdater::default() + .color(Color::BLUE.with_alpha(0.5)) + .apply(app, &root.material1); + DefaultMaterial2DUpdater::default() + .color(Color::GREEN) + .apply(app, &root.material2); + }); root(&mut app).models[0].camera = camera.clone(); root(&mut app).models[0].z_index = -2; root(&mut app).models[0].position = Vec2::ONE * -0.15; @@ -122,7 +128,7 @@ fn set_camera() { #[modor::test(disabled(windows, macos, android, wasm))] fn set_material() { let (mut app, target) = configure_app(); - let material = root(&mut app).material2.glob(); + let material = root(&mut app).material2.to_ref(); root(&mut app).models[0].material = material; app.update(); app.update(); @@ -149,9 +155,9 @@ fn root(app: &mut App) -> &mut Root { } struct Root { - material1: Mat, - material2: Mat, - models: Vec>, + material1: MatGlob, + material2: MatGlob, + models: Vec, target1: Glob>, target2: Glob>, } @@ -160,9 +166,9 @@ impl FromApp for Root { fn from_app(app: &mut App) -> Self { let target1 = Glob::from_app(app); let target2 = Glob::from_app(app); - let material1 = DefaultMaterial2D::new(app).into_mat(app); - let material2 = DefaultMaterial2D::new(app).into_mat(app); - let model = Model2D::new(app, material1.glob()); + let material1 = MatGlob::from_app(app); + let material2 = MatGlob::from_app(app); + let model = Model2D::new(app).with_material(material1.to_ref()); Self { material1, material2, @@ -175,7 +181,9 @@ impl FromApp for Root { impl State for Root { fn init(&mut self, app: &mut App) { - self.material2.color = Color::RED; + DefaultMaterial2DUpdater::default() + .color(Color::RED) + .apply(app, &self.material2); self.models[0].camera = self.target1.get(app).camera().glob().to_ref(); TextureUpdater::default() .res(ResUpdater::default().source(TextureSource::Size(Size::new(30, 20)))) @@ -190,8 +198,6 @@ impl State for Root { } fn update(&mut self, app: &mut App) { - self.material1.update(app); - self.material2.update(app); for model in &mut self.models { model.update(app); } diff --git a/crates/modor_graphics/tests/integration/shader.rs b/crates/modor_graphics/tests/integration/shader.rs index 4cff7172..190ae1b0 100644 --- a/crates/modor_graphics/tests/integration/shader.rs +++ b/crates/modor_graphics/tests/integration/shader.rs @@ -3,8 +3,8 @@ use log::Level; use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::testing::assert_same; use modor_graphics::{ - Color, IntoMat, Mat, Material, Model2D, Model2DGlob, Shader, ShaderGlob, ShaderGlobRef, - ShaderSource, ShaderUpdater, Size, Texture, TextureSource, TextureUpdater, + Color, MatGlob, MatUpdater, Material, Model2D, Model2DGlob, Shader, ShaderGlob, ShaderSource, + ShaderUpdater, Size, Texture, TextureSource, TextureUpdater, }; use modor_input::modor_math::Vec2; use modor_resources::testing::wait_resources; @@ -70,8 +70,6 @@ fn set_alpha_replaced() { .is_alpha_replaced(true) .apply(&mut app, &shader_glob); app.update(); - assert_same(&app, &target, "shader#empty"); // because shader updated after material - app.update(); assert_same(&app, &target, "shader#not_replaced_alpha"); } @@ -90,10 +88,10 @@ fn shader(app: &mut App) -> &Res { } struct Root { - material: Mat, + material: MatGlob, shader: ShaderGlob, - model1: Model2D, - model2: Model2D, + model1: Model2D, + model2: Model2D, target: Glob>, } @@ -101,9 +99,9 @@ impl FromApp for Root { fn from_app(app: &mut App) -> Self { let target = Glob::from_app(app); let shader = ShaderGlob::from_app(app); - let material = TestMaterial::new(&shader).into_mat(app); - let model1 = Model2D::new(app, material.glob()); - let model2 = Model2D::new(app, material.glob()); + let material = MatGlob::from_app(app); + let model1 = Model2D::new(app).with_material(material.to_ref()); + let model2 = Model2D::new(app).with_material(material.to_ref()); Self { material, shader, @@ -119,6 +117,9 @@ impl State for Root { ShaderUpdater::default() .res(ResUpdater::default().path(SIMPLE_SHADER_PATH)) .apply(app, &self.shader); + MatUpdater::default() + .shader(self.shader.to_ref()) + .apply(app, &self.material); self.model1.position = Vec2::ZERO; self.model1.size = Vec2::ONE * 0.5; self.model1.camera = self.target.get(app).camera().glob().to_ref(); @@ -134,53 +135,31 @@ impl State for Root { } fn update(&mut self, app: &mut App) { - self.material.update(app); self.model1.update(app); self.model2.update(app); } } +#[repr(C)] +#[derive(Clone, Copy, Zeroable, Pod)] struct TestMaterial { - color: Color, - shader: ShaderGlobRef, + color: [f32; 4], } -impl Material for TestMaterial { - type Data = TestMaterialData; - type InstanceData = (); - - fn shader(&self) -> ShaderGlobRef { - self.shader.clone() - } - - fn textures(&self) -> Vec>> { - vec![] - } - - fn is_transparent(&self) -> bool { - true - } - - fn data(&self) -> Self::Data { - TestMaterialData { - color: self.color.into(), +impl Default for TestMaterial { + fn default() -> Self { + Self { + color: Color::RED.with_alpha(0.25).into(), } } - - fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} } -impl TestMaterial { - fn new(shader: &ShaderGlob) -> Self { - Self { - color: Color::RED.with_alpha(0.25), - shader: shader.to_ref(), - } +impl Material for TestMaterial { + type InstanceData = (); + + fn init(app: &mut App, glob: &MatGlob) { + MatUpdater::default().is_transparent(true).apply(app, glob); } -} -#[repr(C)] -#[derive(Clone, Copy, Zeroable, Pod)] -struct TestMaterialData { - color: [f32; 4], + fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} } diff --git a/crates/modor_graphics/tests/integration/target.rs b/crates/modor_graphics/tests/integration/target.rs index 556c1954..4fb75668 100644 --- a/crates/modor_graphics/tests/integration/target.rs +++ b/crates/modor_graphics/tests/integration/target.rs @@ -36,33 +36,26 @@ fn set_background() { fn configure_app() -> (App, GlobRef>) { let mut app = App::new::(Level::Info); wait_resources(&mut app); - let target = target_ref(&mut app).glob().to_ref(); - assert_eq!(target.get(&app).size, Size::new(30, 20)); + let target = target_glob(&mut app); + assert_eq!(target.get(&app).size(), Size::new(30, 20)); let target = root(&mut app).target.to_ref(); (app, target) } -fn target_ref(app: &mut App) -> &Target { - root(app).target.to_ref().get_mut(app).target() +fn target_glob(app: &mut App) -> GlobRef { + root(app).target.to_ref().get_mut(app).target().to_ref() } fn root(app: &mut App) -> &mut Root { app.get_mut::() } +#[derive(FromApp)] struct Root { sprite: Sprite2D, target: Glob>, } -impl FromApp for Root { - fn from_app(app: &mut App) -> Self { - let target = Glob::from_app(app); - let sprite = Sprite2D::new(app); - Self { sprite, target } - } -} - impl State for Root { fn init(&mut self, app: &mut App) { self.sprite.model.camera = self.target.get(app).camera().glob().to_ref(); diff --git a/crates/modor_graphics/tests/integration/texture.rs b/crates/modor_graphics/tests/integration/texture.rs index 15ebd0b1..a18600d2 100644 --- a/crates/modor_graphics/tests/integration/texture.rs +++ b/crates/modor_graphics/tests/integration/texture.rs @@ -1,7 +1,9 @@ use log::Level; use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::testing::{assert_max_component_diff, assert_same}; -use modor_graphics::{Color, Size, Sprite2D, Texture, TextureSource, TextureUpdater}; +use modor_graphics::{ + Color, DefaultMaterial2DUpdater, Size, Sprite2D, Texture, TextureSource, TextureUpdater, +}; use modor_input::modor_math::Vec2; use modor_resources::testing::wait_resources; use modor_resources::{Res, ResUpdater, ResourceState}; @@ -214,7 +216,11 @@ fn set_smooth() { #[modor::test(disabled(windows, macos, android, wasm))] fn set_repeated() { let (mut app, glob, target) = configure_app(); - root(&mut app).sprite.material.texture_size = Vec2::ONE * 2.; + app.take::(|root, app| { + DefaultMaterial2DUpdater::default() + .texture_size(Vec2::ONE * 2.) + .apply(app, &root.sprite.material); + }); TextureUpdater::default() .res(ResUpdater::default().source(TextureSource::Bytes(TEXTURE_BYTES))) .is_smooth(false) @@ -242,22 +248,13 @@ fn root(app: &mut App) -> &mut Root { app.get_mut::() } +#[derive(FromApp)] struct Root { texture: Glob>, sprite: Sprite2D, target: Glob>, } -impl FromApp for Root { - fn from_app(app: &mut App) -> Self { - Self { - texture: Glob::from_app(app), - sprite: Sprite2D::new(app), - target: Glob::from_app(app), - } - } -} - impl State for Root { fn init(&mut self, app: &mut App) { TextureUpdater::default() @@ -265,7 +262,9 @@ impl State for Root { .is_buffer_enabled(true) .apply(app, &self.texture); self.sprite.model.camera = self.target.get(app).camera().glob().to_ref(); - self.sprite.material.texture = self.texture.to_ref(); + DefaultMaterial2DUpdater::default() + .texture(self.texture.to_ref()) + .apply(app, &self.sprite.material); TextureUpdater::default() .res(ResUpdater::default().source(TextureSource::Size(Size::new(20, 20)))) .is_target_enabled(true) diff --git a/crates/modor_jobs/src/asset_loading_job.rs b/crates/modor_jobs/src/asset_loading_job.rs index de3be8ee..3de770f2 100644 --- a/crates/modor_jobs/src/asset_loading_job.rs +++ b/crates/modor_jobs/src/asset_loading_job.rs @@ -64,13 +64,13 @@ where /// # Platform-specific /// /// - Web: HTTP GET call is performed to retrieve the file from URL - /// `{current_browser_url}/assets/{path}`. + /// `{current_browser_url}/assets/{path}`. /// - Android: the file is retrieved using the Android - /// [`AssetManager`](https://developer.android.com/reference/android/content/res/AssetManager). + /// [`AssetManager`](https://developer.android.com/reference/android/content/res/AssetManager). /// - Other: if `CARGO_MANIFEST_DIR` environment variable is set (this is the case if the - /// application is run using a `cargo` command), then the file is retrieved from path - /// `{CARGO_MANIFEST_DIR}/assets/{path}`. Else, the file path is - /// `{executable_folder_path}/assets/{path}`. + /// application is run using a `cargo` command), then the file is retrieved from path + /// `{CARGO_MANIFEST_DIR}/assets/{path}`. Else, the file path is + /// `{executable_folder_path}/assets/{path}`. pub fn new(path: impl AsRef, f: impl FnOnce(Vec) -> F + VariableSend + Any) -> Self where F: Future + VariableSend, diff --git a/crates/modor_math/src/quaternion.rs b/crates/modor_math/src/quaternion.rs index b605d88a..96bcabaa 100644 --- a/crates/modor_math/src/quaternion.rs +++ b/crates/modor_math/src/quaternion.rs @@ -115,6 +115,7 @@ impl Quat { self * other } + #[allow(clippy::while_float)] fn normalize_angle(mut angle: f32) -> f32 { while angle > 2. * PI { angle -= 2. * PI; diff --git a/crates/modor_physics/tests/integration/body/collisions.rs b/crates/modor_physics/tests/integration/body/collisions.rs index 96813a13..1b65365a 100644 --- a/crates/modor_physics/tests/integration/body/collisions.rs +++ b/crates/modor_physics/tests/integration/body/collisions.rs @@ -28,7 +28,7 @@ fn colliding_bodies_with_no_interaction() { #[modor::test] fn colliding_bodies_with_sensor() { let mut app = App::new::(Level::Info); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_sensor_interaction(&mut app); app.update(); let body = res.body1.get(&app); @@ -58,7 +58,7 @@ fn colliding_bodies_with_sensor() { #[modor::test] fn colliding_bodies_with_impulse() { let mut app = App::new::(Level::Info); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_impulse_interaction(&mut app, Impulse::default()); app.update(); let body = res.body1.get(&app); @@ -91,7 +91,7 @@ fn colliding_bodies_with_impulse() { ))] fn set_friction(friction: f32, expected_position: Vec2) { let mut app = App::new::(Level::Info); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_impulse_interaction(&mut app, Impulse::new(0., friction)); res.configure_ground(&mut app); res.configure_rolling_ball(&mut app); @@ -107,7 +107,7 @@ fn set_friction(friction: f32, expected_position: Vec2) { fn set_restitution(restitution: f32, expected_position: Vec2) { let mut app = App::new::(Level::Info); app.get_mut::().duration = Duration::from_secs_f32(0.1); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_impulse_interaction(&mut app, Impulse::new(restitution, 0.5)); res.configure_ground(&mut app); res.configure_falling_ball(&mut app); @@ -126,7 +126,7 @@ fn set_restitution(restitution: f32, expected_position: Vec2) { fn set_dominance(dominance: i8, expected_position: Vec2) { let mut app = App::new::(Level::Info); app.get_mut::().duration = Duration::from_secs_f32(0.1); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_impulse_interaction(&mut app, Impulse::new(1., 0.5)); res.configure_ground(&mut app); res.configure_falling_ball(&mut app); @@ -146,7 +146,7 @@ fn set_dominance(dominance: i8, expected_position: Vec2) { ))] fn set_ccd(is_enabled: bool, expected_position: Vec2) { let mut app = App::new::(Level::Info); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_impulse_interaction(&mut app, Impulse::new(1., 0.5)); res.configure_ground(&mut app); res.configure_falling_ball(&mut app); @@ -170,7 +170,7 @@ fn set_ccd(is_enabled: bool, expected_position: Vec2) { ))] fn set_shape(position: Vec2, size: Vec2, shape: Shape2D, collision_count: usize) { let mut app = App::new::(Level::Info); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_sensor_interaction(&mut app); Body2DUpdater::default() .position(position) @@ -185,7 +185,7 @@ fn set_shape(position: Vec2, size: Vec2, shape: Shape2D, collision_count: usize) #[modor::test(cases(rectangle = "Shape2D::Rectangle", circle = "Shape2D::Circle"))] fn update_size(shape: Shape2D) { let mut app = App::new::(Level::Info); - let mut res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); + let res = Resources::from_app_with(&mut app, |res, app| res.init(app, true)); res.add_sensor_interaction(&mut app); Body2DUpdater::default() .shape(shape) @@ -231,7 +231,7 @@ struct Resources { } impl Resources { - fn init(&mut self, app: &mut App, assign_groups: bool) { + fn init(&self, app: &mut App, assign_groups: bool) { Body2DUpdater::default() .collision_group(assign_groups.then(|| self.group1.to_ref())) .apply(app, &self.body1); @@ -243,22 +243,22 @@ impl Resources { .apply(app, &self.body2); } - fn add_sensor_interaction(&mut self, app: &mut App) { + fn add_sensor_interaction(&self, app: &mut App) { CollisionGroupUpdater::new(&self.group1).add_sensor(app, &self.group2); } - fn add_impulse_interaction(&mut self, app: &mut App, impulse: Impulse) { + fn add_impulse_interaction(&self, app: &mut App, impulse: Impulse) { CollisionGroupUpdater::new(&self.group1).add_impulse(app, &self.group2, impulse); } - fn configure_ground(&mut self, app: &mut App) { + fn configure_ground(&self, app: &mut App) { Body2DUpdater::default() .position(Vec2::ZERO) .size(Vec2::new(1., 0.01)) .apply(app, &self.body1); } - fn configure_rolling_ball(&mut self, app: &mut App) { + fn configure_rolling_ball(&self, app: &mut App) { Body2DUpdater::default() .position(Vec2::Y * 0.251) .size(Vec2::ONE * 0.5) @@ -268,7 +268,7 @@ impl Resources { .apply(app, &self.body2); } - fn configure_falling_ball(&mut self, app: &mut App) { + fn configure_falling_ball(&self, app: &mut App) { Body2DUpdater::default() .position(Vec2::Y * 1.) .size(Vec2::ONE * 0.5) diff --git a/crates/modor_resources/src/resource.rs b/crates/modor_resources/src/resource.rs index 61955fa4..248227d4 100644 --- a/crates/modor_resources/src/resource.rs +++ b/crates/modor_resources/src/resource.rs @@ -46,9 +46,15 @@ use std::{any, fmt}; /// }) /// } /// -/// fn on_load(&mut self, app: &mut App, loaded: Self::Loaded, source: &ResSource) { +/// fn on_load( +/// &mut self, +/// app: &mut App, +/// index: usize, +/// loaded: Self::Loaded, +/// source: &ResSource +/// ) { /// self.size = Some(loaded.size_in_bytes); -/// println!("Resource has been successfully loaded from `{source:?}`"); +/// println!("`ContentSize` #{index} has been successfully loaded from `{source:?}`"); /// } /// } /// @@ -107,13 +113,15 @@ pub struct Res { source: Option>, loading: Option>, state: ResourceState, + index: usize, } impl Global for Res where T: Resource, { - fn init(&mut self, app: &mut App, _index: usize) { + fn init(&mut self, app: &mut App, index: usize) { + self.index = index; app.create::>(); app.get_mut::() .are_all_loaded_fns @@ -202,7 +210,7 @@ where .as_ref() .expect("internal error: missing source"); self.state = ResourceState::Loaded; - self.inner.on_load(app, loaded, source); + self.inner.on_load(app, self.index, loaded, source); } fn fail(&mut self, err: ResourceError) { @@ -275,7 +283,15 @@ pub trait Resource: FromApp + Sized { fn load_from_source(source: &Self::Source) -> Result; /// Updates the resource when loading has successfully finished. - fn on_load(&mut self, app: &mut App, loaded: Self::Loaded, source: &ResSource); + /// + /// `index` corresponds to the unique index of the [`Glob>`]. + fn on_load( + &mut self, + app: &mut App, + index: usize, + loaded: Self::Loaded, + source: &ResSource, + ); } /// A trait for defining a source used to load a [`Resource`]. @@ -348,13 +364,13 @@ where /// # Platform-specific /// /// - Web: HTTP GET call is performed to retrieve the file from URL - /// `{current_browser_url}/assets/{path}`. + /// `{current_browser_url}/assets/{path}`. /// - Android: the file is retrieved using the Android - /// [`AssetManager`](https://developer.android.com/reference/android/content/res/AssetManager). + /// [`AssetManager`](https://developer.android.com/reference/android/content/res/AssetManager). /// - Other: if `CARGO_MANIFEST_DIR` environment variable is set (this is the case if the - /// application is run using a `cargo` command), then the file is retrieved from path - /// `{CARGO_MANIFEST_DIR}/assets/{path}`. Else, the file path is - /// `{executable_folder_path}/assets/{path}`. + /// application is run using a `cargo` command), then the file is retrieved from path + /// `{CARGO_MANIFEST_DIR}/assets/{path}`. Else, the file path is + /// `{executable_folder_path}/assets/{path}`. pub fn path(mut self, path: impl Into) -> Self { self.source = Some(ResSource::Path(path.into())); self diff --git a/crates/modor_resources/tests/integration/resource.rs b/crates/modor_resources/tests/integration/resource.rs index 151af8b7..a8563aa7 100644 --- a/crates/modor_resources/tests/integration/resource.rs +++ b/crates/modor_resources/tests/integration/resource.rs @@ -236,7 +236,13 @@ impl Resource for ContentSize { } } - fn on_load(&mut self, _app: &mut App, loaded: Self::Loaded, _source: &ResSource) { + fn on_load( + &mut self, + _app: &mut App, + _index: usize, + loaded: Self::Loaded, + _source: &ResSource, + ) { self.size = Some(loaded.size); } } diff --git a/crates/modor_text/src/font.rs b/crates/modor_text/src/font.rs index 9df6aef3..15fac3a1 100644 --- a/crates/modor_text/src/font.rs +++ b/crates/modor_text/src/font.rs @@ -51,7 +51,13 @@ impl Resource for Font { } } - fn on_load(&mut self, _app: &mut App, loaded: Self::Loaded, _source: &ResSource) { + fn on_load( + &mut self, + _app: &mut App, + _index: usize, + loaded: Self::Loaded, + _source: &ResSource, + ) { self.font = Some(loaded); self.will_change = true; } diff --git a/crates/modor_text/src/material.rs b/crates/modor_text/src/material.rs index 92fb265d..6e251537 100644 --- a/crates/modor_text/src/material.rs +++ b/crates/modor_text/src/material.rs @@ -1,64 +1,68 @@ -use crate::material::internal::TextMaterial2DData; use crate::resources::TextResources; -use modor::{App, Glob, GlobRef}; +use modor::{App, Glob, GlobRef, Updater}; use modor_graphics::modor_resources::Res; -use modor_graphics::{Color, Material, Model2DGlob, ShaderGlobRef, Texture}; +use modor_graphics::{Color, MatGlob, MatUpdater, Material, Model2DGlob, Texture}; +use std::marker::PhantomData; /// A material for 2D text rendering. /// /// # Examples /// /// See [`Text2D`](crate::Text2D). -#[derive(Debug)] +#[repr(C)] +#[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod, Updater)] pub struct TextMaterial2D { - // The color of the rendered text. + shader_color: [f32; 4], + /// The color of the rendered text. /// /// Default is [`Color::WHITE`]. - pub color: Color, - texture: GlobRef>, - shader: ShaderGlobRef, + #[updater(inner_type, field)] + color: PhantomData, + /// The texture containing the text the render. + /// + /// Default is a white texture. + #[updater(inner_type, field)] + texture: PhantomData>>, } -impl Material for TextMaterial2D { - type Data = TextMaterial2DData; - type InstanceData = (); - - fn shader(&self) -> ShaderGlobRef { - self.shader.clone() - } - - fn textures(&self) -> Vec>> { - vec![self.texture.clone()] +impl Default for TextMaterial2D { + fn default() -> Self { + Self { + shader_color: Color::WHITE.into(), + color: PhantomData, + texture: PhantomData, + } } +} - fn is_transparent(&self) -> bool { - self.color.a > 0. && self.color.a < 1. - } +impl Material for TextMaterial2D { + type InstanceData = (); - fn data(&self) -> Self::Data { - TextMaterial2DData { - color: self.color.into(), - } + fn init(app: &mut App, glob: &MatGlob) { + MatUpdater::default() + .shader(app.get_mut::().text_shader.to_ref()) + .apply(app, glob); } fn instance_data(_app: &mut App, _model: &Glob) -> Self::InstanceData {} } -impl TextMaterial2D { - pub(crate) fn new(app: &mut App, texture: GlobRef>) -> Self { - let resources = app.get_mut::(); - Self { - color: Color::WHITE, - texture, - shader: resources.text_shader.to_ref(), +impl TextMaterial2DUpdater<'_> { + /// Runs the update. + pub fn apply(mut self, app: &mut App, glob: &MatGlob) { + let mut updater = MatUpdater::default(); + if let Some(texture) = self.texture.take_value(|| unreachable!()) { + updater = updater.textures(vec![texture]); } - } -} - -pub(super) mod internal { - #[repr(C)] - #[derive(Clone, Copy, Debug, bytemuck::Zeroable, bytemuck::Pod)] - pub struct TextMaterial2DData { - pub(crate) color: [f32; 4], + if let Some(color) = self.color.take_value(|| unreachable!()) { + updater = updater + .data(TextMaterial2D { + shader_color: color.into(), + color: PhantomData, + texture: PhantomData, + }) + .is_transparent(color.a > 0. && color.a < 1.); + } + updater.apply(app, glob); } } diff --git a/crates/modor_text/src/text.rs b/crates/modor_text/src/text.rs index 16dd706c..344f4466 100644 --- a/crates/modor_text/src/text.rs +++ b/crates/modor_text/src/text.rs @@ -1,9 +1,9 @@ use crate::resources::TextResources; -use crate::TextMaterial2D; +use crate::{TextMaterial2D, TextMaterial2DUpdater}; use ab_glyph::{Font, FontVec, Glyph, PxScaleFont, ScaleFont}; use modor::{App, Builder, FromApp, Glob, GlobRef}; use modor_graphics::modor_resources::{Res, ResUpdater}; -use modor_graphics::{IntoMat, Mat, Model2D, Size, Texture, TextureSource, TextureUpdater}; +use modor_graphics::{MatGlob, Model2D, Size, Texture, TextureSource, TextureUpdater}; use std::iter; /// A rendered 2D text. @@ -27,7 +27,9 @@ use std::iter; /// .with_content("Hello world!".into()) /// .with_font(app.get_mut::().font.to_ref()) /// .with_font_height(200.) -/// .with_material(|m| m.color = Color::GREEN), +/// .with_material(|m| TextMaterial2DUpdater::default() +/// .color(Color::GREEN) +/// .apply(app, m)), /// } /// } /// } @@ -85,10 +87,10 @@ pub struct Text2D { pub texture: Glob>, /// Material of the rendered text. #[builder(form(closure))] - pub material: Mat, + pub material: MatGlob, /// Model of the rendered text. #[builder(form(closure))] - pub model: Model2D, + pub model: Model2D, old_state: OldState, } @@ -102,8 +104,11 @@ impl Text2D { TextureUpdater::default() .res(ResUpdater::default().source(TextureSource::Buffer(Size::ONE, vec![0, 0, 0, 0]))) .apply(app, &texture); - let material = TextMaterial2D::new(app, texture.to_ref()).into_mat(app); - let model = Model2D::new(app, material.glob()); + let material = MatGlob::from_app(app); + TextMaterial2DUpdater::default() + .texture(texture.to_ref()) + .apply(app, &material); + let model = Model2D::new(app).with_material(material.to_ref()); Self { content: String::new(), font_height: 100., @@ -141,7 +146,6 @@ impl Text2D { self.update_old_state(); } } - self.material.update(app); self.model.update(app); } diff --git a/crates/modor_text/tests/expected/text#red.png b/crates/modor_text/tests/expected/text#red.png new file mode 100644 index 00000000..3a3b6e9b Binary files /dev/null and b/crates/modor_text/tests/expected/text#red.png differ diff --git a/crates/modor_text/tests/integration/text.rs b/crates/modor_text/tests/integration/text.rs index 46d0540c..38da2bd9 100644 --- a/crates/modor_text/tests/integration/text.rs +++ b/crates/modor_text/tests/integration/text.rs @@ -3,8 +3,8 @@ use modor::{App, FromApp, Glob, GlobRef, State}; use modor_graphics::modor_resources::testing::wait_resources; use modor_graphics::modor_resources::{Res, ResUpdater}; use modor_graphics::testing::assert_max_component_diff; -use modor_graphics::{Size, Texture, TextureSource, TextureUpdater}; -use modor_text::{Alignment, Text2D}; +use modor_graphics::{Color, Size, Texture, TextureSource, TextureUpdater}; +use modor_text::{Alignment, Text2D, TextMaterial2DUpdater}; #[modor::test(disabled(windows, macos, android, wasm))] fn create_default() { @@ -24,6 +24,19 @@ fn set_content() { assert_max_component_diff(&app, &target, "text#other_content", 20, 2); } +#[modor::test(disabled(windows, macos, android, wasm))] +fn set_color() { + let (mut app, target) = configure_app(); + app.take::(|root, app| { + TextMaterial2DUpdater::default() + .color(Color::RED) + .apply(app, &root.text.material); + }); + wait_resources(&mut app); + app.update(); + assert_max_component_diff(&app, &target, "text#red", 20, 2); +} + #[modor::test(disabled(windows, macos, android, wasm))] fn apply_left_alignment() { let (mut app, target) = configure_app(); diff --git a/examples/src/collisions_2d.rs b/examples/src/collisions_2d.rs index 5a852e84..cd2eac3a 100644 --- a/examples/src/collisions_2d.rs +++ b/examples/src/collisions_2d.rs @@ -1,6 +1,6 @@ use modor::log::Level; use modor::{App, FromApp, Glob, State}; -use modor_graphics::{Color, CursorTracker, Sprite2D, Window}; +use modor_graphics::{Color, CursorTracker, DefaultMaterial2DUpdater, Sprite2D, Window}; use modor_physics::modor_math::Vec2; use modor_physics::{ Body2D, Body2DUpdater, Collision2D, CollisionGroup, CollisionGroupUpdater, Shape2D, @@ -57,7 +57,7 @@ impl FromApp for Shape { fn from_app(app: &mut App) -> Self { Self { body: Glob::from_app(app), - sprite: Sprite2D::new(app), + sprite: Sprite2D::from_app(app), collisions: vec![], } } @@ -76,8 +76,10 @@ impl Shape { }) .apply(app, &self.body); self.sprite.model.body = Some(self.body.to_ref()); - self.sprite.material.is_ellipse = is_circle; - self.sprite.material.color = Color::CYAN; + DefaultMaterial2DUpdater::default() + .is_ellipse(is_circle) + .color(Color::CYAN) + .apply(app, &self.sprite.material); } fn update(&mut self, app: &mut App) { @@ -103,7 +105,7 @@ impl FromApp for Cursor { fn from_app(app: &mut App) -> Self { Self { body: Glob::from_app(app), - sprite: Sprite2D::new(app), + sprite: Sprite2D::from_app(app), collisions: vec![], tracker: CursorTracker::new(app), } @@ -119,7 +121,9 @@ impl Cursor { .apply(app, &self.body); self.sprite.model.body = Some(self.body.to_ref()); self.sprite.model.z_index = 1; - self.sprite.material.color = Color::GREEN; + DefaultMaterial2DUpdater::default() + .color(Color::GREEN) + .apply(app, &self.sprite.material); } fn update(&mut self, app: &mut App) { @@ -128,11 +132,13 @@ impl Cursor { .position(self.tracker.position(app)) .apply(app, &self.body); self.body.take(app, |body, app| { - self.sprite.material.color = if body.collisions().is_empty() { - Color::GREEN - } else { - Color::RED - }; + DefaultMaterial2DUpdater::default() + .color(if body.collisions().is_empty() { + Color::GREEN + } else { + Color::RED + }) + .apply(app, &self.sprite.material); self.collisions.clear(); for collision in body.collisions() { self.collisions @@ -162,19 +168,27 @@ impl CollisionNormal { .with_magnitude(0.0025) .unwrap_or_default(); let penetration_position = collision.position - collision.penetration / 2. + lateral_offset; - let mut position = Sprite2D::new(app) + let mut position = Sprite2D::from_app(app) .with_model(|m| m.position = collision.position) .with_model(|m| m.size = Vec2::ONE * 0.02) .with_model(|m| m.z_index = z_index) - .with_material(|m| m.color = color) - .with_material(|m| m.is_ellipse = true); + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .color(color) + .is_ellipse(true) + .apply(app, m); + }); position.update(app); - let mut penetration = Sprite2D::new(app) + let mut penetration = Sprite2D::from_app(app) .with_model(|m| m.position = penetration_position) .with_model(|m| m.size = Vec2::new(0.005, collision.penetration.magnitude())) .with_model(|m| m.rotation = Vec2::Y.rotation(-collision.penetration)) .with_model(|m| m.z_index = z_index) - .with_material(|m| m.color = color); + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .color(color) + .apply(app, m); + }); penetration.update(app); Self { _position: position, diff --git a/examples/src/custom_shader.rs b/examples/src/custom_shader.rs index 50f5e43a..46eee153 100644 --- a/examples/src/custom_shader.rs +++ b/examples/src/custom_shader.rs @@ -1,10 +1,10 @@ use modor::log::Level; -use modor::{App, FromApp, Glob, GlobRef, State}; +use modor::{App, FromApp, Glob, State}; use modor_graphics::modor_input::modor_math::Vec2; use modor_graphics::modor_resources::{Res, ResUpdater}; use modor_graphics::{ - bytemuck, IntoMat, Mat, Material, MaterialGlobRef, Model2D, Model2DGlob, ShaderGlob, - ShaderGlobRef, ShaderUpdater, Texture, TextureUpdater, + bytemuck, MatGlob, MatUpdater, Material, Model2D, Model2DGlob, ShaderGlob, ShaderUpdater, + Texture, TextureUpdater, }; use std::collections::HashMap; @@ -13,51 +13,50 @@ pub fn main() { } struct Root { - texture: Glob>, - shader: ShaderGlob, - material: Mat, sprites: Vec, } impl FromApp for Root { fn from_app(app: &mut App) -> Self { - let texture = Glob::from_app(app); - let shader = ShaderGlob::from_app(app); - let material = BlurMaterial::new(&texture, &shader).into_mat(app); + let material = MatGlob::from_app(app); Self { sprites: vec![ - Sprite::new(app, Vec2::new(-0.25, 0.25), 0, material.glob()), - Sprite::new(app, Vec2::new(0.25, 0.25), 3, material.glob()), - Sprite::new(app, Vec2::new(-0.25, -0.25), 6, material.glob()), - Sprite::new(app, Vec2::new(0.25, -0.25), 9, material.glob()), + Sprite::new(app, Vec2::new(-0.25, 0.25), 0, &material), + Sprite::new(app, Vec2::new(0.25, 0.25), 3, &material), + Sprite::new(app, Vec2::new(-0.25, -0.25), 6, &material), + Sprite::new(app, Vec2::new(0.25, -0.25), 9, &material), ], - texture, - shader, - material, } } } impl State for Root { - fn init(&mut self, app: &mut App) { - TextureUpdater::default() - .res(ResUpdater::default().path("smiley.png")) - .apply(app, &self.texture); - ShaderUpdater::default() - .res(ResUpdater::default().path("blur.wgsl")) - .apply(app, &self.shader); - } - fn update(&mut self, app: &mut App) { - self.material.update(app); for sprite in &mut self.sprites { sprite.update(app); } } } +#[derive(FromApp)] +struct Resources { + shader: ShaderGlob, + texture: Glob>, +} + +impl State for Resources { + fn init(&mut self, app: &mut App) { + ShaderUpdater::default() + .res(ResUpdater::default().path("blur.wgsl")) + .apply(app, &self.shader); + TextureUpdater::default() + .res(ResUpdater::default().path("smiley.png")) + .apply(app, &self.texture); + } +} + struct Sprite { - model: Model2D, + model: Model2D, } impl Sprite { @@ -65,9 +64,10 @@ impl Sprite { app: &mut App, position: Vec2, sample_count: u32, - material: MaterialGlobRef, + material: &MatGlob, ) -> Self { - let model = Model2D::new(app, material) + let model = Model2D::new(app) + .with_material(material.to_ref()) .with_position(position) .with_size(Vec2::ONE * 0.4); app.get_mut::() @@ -86,35 +86,34 @@ struct SpriteProperties { sample_counts: HashMap, } +#[repr(C)] +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] struct BlurMaterial { blur_factor: f32, - texture: GlobRef>, - shader: ShaderGlobRef, + padding1: [f32; 1], + padding2: [f32; 2], } -impl Material for BlurMaterial { - type Data = BlurMaterialData; - type InstanceData = BlurInstanceData; - - fn shader(&self) -> ShaderGlobRef { - self.shader.clone() - } - - fn textures(&self) -> Vec>> { - vec![self.texture.clone()] - } - - fn is_transparent(&self) -> bool { - false - } - - fn data(&self) -> Self::Data { - BlurMaterialData { - blur_factor: self.blur_factor, +impl Default for BlurMaterial { + fn default() -> Self { + Self { + blur_factor: 0.005, padding1: [0.], padding2: [0., 0.], } } +} + +impl Material for BlurMaterial { + type InstanceData = BlurInstanceData; + + fn init(app: &mut App, glob: &MatGlob) { + let resources = app.get_mut::(); + MatUpdater::default() + .shader(resources.shader.to_ref()) + .textures(vec![resources.texture.to_ref()]) + .apply(app, glob); + } fn instance_data(app: &mut App, model: &Glob) -> Self::InstanceData { let sample_counts = &app.get_mut::().sample_counts; @@ -124,24 +123,6 @@ impl Material for BlurMaterial { } } -impl BlurMaterial { - fn new(texture: &Glob>, shader: &ShaderGlob) -> Self { - Self { - blur_factor: 0.005, - texture: texture.to_ref(), - shader: shader.to_ref(), - } - } -} - -#[repr(C)] -#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] -struct BlurMaterialData { - blur_factor: f32, - padding1: [f32; 1], - padding2: [f32; 2], -} - #[repr(C)] #[derive(Clone, Copy, Default, bytemuck::Pod, bytemuck::Zeroable)] struct BlurInstanceData { diff --git a/examples/src/game_of_life.rs b/examples/src/game_of_life.rs index a5534819..10922809 100644 --- a/examples/src/game_of_life.rs +++ b/examples/src/game_of_life.rs @@ -1,7 +1,7 @@ use instant::Instant; use modor::log::Level; use modor::{App, FromApp, State}; -use modor_graphics::{Color, Sprite2D}; +use modor_graphics::{Color, DefaultMaterial2DUpdater, Sprite2D}; use modor_physics::modor_math::Vec2; use std::time::Duration; @@ -31,7 +31,7 @@ impl FromApp for Root { } Self { last_update: Instant::now(), - background: Sprite2D::new(app), + background: Sprite2D::from_app(app), are_cells_alive, cells: vec![], } @@ -110,9 +110,13 @@ impl Root { } fn alive_cell(app: &mut App) -> Sprite2D { - Sprite2D::new(app) + Sprite2D::from_app(app) .with_model(|m| m.size = Vec2::ONE / GRID_SIZE as f32) .with_model(|m| m.z_index = 1) - .with_material(|m| m.color = Color::BLACK) + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .color(Color::BLACK) + .apply(app, m); + }) } } diff --git a/examples/src/mouse.rs b/examples/src/mouse.rs index 44ccb7ac..63b5d3a4 100644 --- a/examples/src/mouse.rs +++ b/examples/src/mouse.rs @@ -1,7 +1,7 @@ use modor::log::Level; use modor::{App, FromApp, State}; use modor_graphics::modor_input::Inputs; -use modor_graphics::{CursorTracker, Sprite2D}; +use modor_graphics::{CursorTracker, DefaultMaterial2DUpdater, Sprite2D}; use modor_physics::modor_math::Vec2; use modor_text::Text2D; @@ -21,9 +21,13 @@ impl FromApp for Root { Self { pressed_buttons_label: text(app, 0.25, "Pressed buttons:"), pressed_buttons: text(app, -0.25, ""), - cursor: Sprite2D::new(app) + cursor: Sprite2D::from_app(app) .with_model(|m| m.size = Vec2::ONE * 0.02) - .with_material(|m| m.is_ellipse = true), + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .is_ellipse(true) + .apply(app, m); + }), tracker: CursorTracker::new(app), } } diff --git a/examples/src/physics_2d.rs b/examples/src/physics_2d.rs index f404baba..11394db5 100644 --- a/examples/src/physics_2d.rs +++ b/examples/src/physics_2d.rs @@ -1,7 +1,7 @@ use modor::log::Level; use modor::{App, FromApp, Glob, State}; use modor_graphics::modor_input::{Inputs, MouseButton}; -use modor_graphics::{Color, CursorTracker, Sprite2D, Window}; +use modor_graphics::{Color, CursorTracker, DefaultMaterial2DUpdater, Sprite2D, Window}; use modor_physics::modor_math::Vec2; use modor_physics::{ Body2D, Body2DUpdater, CollisionGroup, CollisionGroupUpdater, Impulse, Shape2D, @@ -52,14 +52,15 @@ impl State for Root { impl Root { fn init_anti_aliasing(app: &mut App) { - let window = app.get_mut::(); - window.target.anti_aliasing = window - .target - .supported_anti_aliasing_modes() - .iter() - .copied() - .max() - .unwrap_or_default(); + app.take::(|window, app| { + let target = window.target.get_mut(app); + target.anti_aliasing = target + .supported_anti_aliasing_modes() + .iter() + .copied() + .max() + .unwrap_or_default(); + }); } } @@ -78,20 +79,12 @@ impl State for CollisionGroups { } } +#[derive(FromApp)] struct Wall { body: Glob, sprite: Sprite2D, } -impl FromApp for Wall { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - } - } -} - impl Wall { fn init(&mut self, app: &mut App, position: Vec2, size: Vec2) { Body2DUpdater::default() @@ -115,7 +108,7 @@ struct Cannon { impl FromApp for Cannon { fn from_app(app: &mut App) -> Self { Self { - sprite: Sprite2D::new(app), + sprite: Sprite2D::from_app(app), cursor: CursorTracker::new(app), } } @@ -169,20 +162,12 @@ impl State for Objects { } } +#[derive(FromApp)] struct Object { body: Glob, sprite: Sprite2D, } -impl FromApp for Object { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - } - } -} - impl Object { fn init(&mut self, app: &mut App, position: Vec2, velocity: Vec2, is_ball: bool) { let mut rng = rand::thread_rng(); @@ -202,12 +187,14 @@ impl Object { .shape(shape) .apply(app, &self.body); self.sprite.model.body = Some(self.body.to_ref()); - self.sprite.material.is_ellipse = is_ball; - self.sprite.material.color = Color::rgb( - rng.gen_range(0.0..1.0), - rng.gen_range(0.0..1.0), - rng.gen_range(0.0..1.0), - ); + DefaultMaterial2DUpdater::default() + .is_ellipse(is_ball) + .color(Color::rgb( + rng.gen_range(0.0..1.0), + rng.gen_range(0.0..1.0), + rng.gen_range(0.0..1.0), + )) + .apply(app, &self.sprite.material); } fn update(&mut self, app: &mut App) { diff --git a/examples/src/platformer.rs b/examples/src/platformer.rs index c74d3cd0..8227cda0 100644 --- a/examples/src/platformer.rs +++ b/examples/src/platformer.rs @@ -4,7 +4,7 @@ use modor::log::Level; use modor::{App, FromApp, Glob, State, StateHandle}; use modor_graphics::modor_input::modor_math::Vec2; use modor_graphics::modor_input::{Inputs, Key}; -use modor_graphics::{Color, Sprite2D}; +use modor_graphics::{Color, DefaultMaterial2DUpdater, Sprite2D}; use modor_physics::{Body2D, Body2DUpdater, CollisionGroup, CollisionGroupUpdater, Impulse}; use std::time::Duration; @@ -110,20 +110,11 @@ impl State for CollisionGroups { } } +#[derive(FromApp)] struct Platform { body: Glob, sprite: Sprite2D, - next_reverse_instant: Instant, -} - -impl FromApp for Platform { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - next_reverse_instant: Instant::now() + PLATFORM_PERIOD, - } - } + latest_reverse_instant: Instant, } impl Platform { @@ -135,12 +126,14 @@ impl Platform { .collision_group(app.get_mut::().platform.to_ref()) .apply(app, &self.body); self.sprite.model.body = Some(self.body.to_ref()); - self.sprite.material.color = Color::GREEN; + DefaultMaterial2DUpdater::default() + .color(Color::GREEN) + .apply(app, &self.sprite.material); } fn update(&mut self, app: &mut App) { - if Instant::now() >= self.next_reverse_instant { - self.next_reverse_instant = Instant::now() + PLATFORM_PERIOD; + if Instant::now() >= self.latest_reverse_instant + PLATFORM_PERIOD { + self.latest_reverse_instant = Instant::now(); Body2DUpdater::default() .for_velocity(|velocity| *velocity *= -1.) .apply(app, &self.body); @@ -149,22 +142,13 @@ impl Platform { } } +#[derive(FromApp)] struct Character { body: Glob, sprite: Sprite2D, platforms: StateHandle, } -impl FromApp for Character { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - platforms: app.handle(), - } - } -} - impl State for Character { fn init(&mut self, app: &mut App) { Body2DUpdater::default() diff --git a/examples/src/pong/ball.rs b/examples/src/pong/ball.rs index f2caa05e..2185681e 100644 --- a/examples/src/pong/ball.rs +++ b/examples/src/pong/ball.rs @@ -4,12 +4,13 @@ use crate::pong::scores::Scores; use crate::pong::side::Side; use instant::Instant; use modor::{App, FromApp, Glob, Globals, State, StateHandle}; -use modor_graphics::Sprite2D; +use modor_graphics::{DefaultMaterial2DUpdater, Sprite2D}; use modor_physics::modor_math::Vec2; use modor_physics::{Body2D, Body2DUpdater}; use rand::Rng; use std::f32::consts::FRAC_PI_4; +#[derive(FromApp)] pub(crate) struct Ball { body: Glob, sprite: Sprite2D, @@ -18,18 +19,6 @@ pub(crate) struct Ball { bodies: StateHandle>, } -impl FromApp for Ball { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - init_instant: Instant::now(), - collision_groups: app.handle(), - bodies: app.handle(), - } - } -} - impl Ball { const SIZE: Vec2 = Vec2::new(0.03, 0.03); const INITIAL_SPEED: f32 = 0.6; @@ -45,7 +34,9 @@ impl Ball { .collision_group(self.collision_groups.get(app).ball.to_ref()) .apply(app, &self.body); self.sprite.model.body = Some(self.body.to_ref()); - self.sprite.material.is_ellipse = true; + DefaultMaterial2DUpdater::default() + .is_ellipse(true) + .apply(app, &self.sprite.material); self.init_instant = Instant::now(); } @@ -64,7 +55,7 @@ impl Ball { Vec2::new(direction * Self::INITIAL_SPEED, 0.) } - pub(crate) fn handle_collision_with_paddle(&mut self, app: &mut App) { + pub(crate) fn handle_collision_with_paddle(&self, app: &mut App) { let Some(paddle) = self.collided_paddle(app) else { return; }; @@ -80,7 +71,7 @@ impl Ball { .apply(app, &self.body); } - pub(crate) fn handle_collision_with_ball(&mut self, app: &mut App) { + pub(crate) fn handle_collision_with_ball(&self, app: &mut App) { let body = self.body.get(app); let position = body.position(app); let vertical_wall_group = &self.collision_groups.get(app).vertical_wall; @@ -99,7 +90,7 @@ impl Ball { } } - fn apply_acceleration(&mut self, app: &mut App) { + fn apply_acceleration(&self, app: &mut App) { let speed = self .init_instant .elapsed() diff --git a/examples/src/pong/mod.rs b/examples/src/pong/mod.rs index a495b98e..7ed10249 100644 --- a/examples/src/pong/mod.rs +++ b/examples/src/pong/mod.rs @@ -14,6 +14,7 @@ pub fn main() { modor_graphics::run::(Level::Info); } +#[derive(FromApp)] struct Root { left_wall: Wall, right_wall: Wall, @@ -25,25 +26,9 @@ struct Root { right_paddle: Paddle, } -impl FromApp for Root { - fn from_app(app: &mut App) -> Self { - app.create::(); - Self { - left_wall: Wall::from_app(app), - right_wall: Wall::from_app(app), - top_wall: Wall::from_app(app), - bottom_wall: Wall::from_app(app), - separator: Sprite2D::new(app) - .with_model(|m| m.size = Vec2::new(FIELD_BORDER_WIDTH / 4., FIELD_SIZE.y)), - ball: Ball::from_app(app), - left_paddle: Paddle::from_app(app), - right_paddle: Paddle::from_app(app), - } - } -} - impl State for Root { fn init(&mut self, app: &mut App) { + app.create::(); app.take::(|groups, app| { self.left_wall .init(app, WallOrientation::Left, &groups.vertical_wall); @@ -54,6 +39,7 @@ impl State for Root { self.bottom_wall .init(app, WallOrientation::Bottom, &groups.horizontal_wall); }); + self.separator.model.size = Vec2::new(FIELD_BORDER_WIDTH / 4., FIELD_SIZE.y); self.ball.init(app); self.left_paddle.init_player(app, Side::Left); self.right_paddle.init_bot(app, Side::Right); diff --git a/examples/src/pong/paddle.rs b/examples/src/pong/paddle.rs index 9ee8dcd0..1a05715a 100644 --- a/examples/src/pong/paddle.rs +++ b/examples/src/pong/paddle.rs @@ -8,6 +8,7 @@ use modor_graphics::{Sprite2D, Window}; use modor_physics::modor_math::Vec2; use modor_physics::{Body2D, Body2DUpdater}; +#[derive(FromApp)] pub(crate) struct Paddle { body: Glob, sprite: Sprite2D, @@ -16,18 +17,6 @@ pub(crate) struct Paddle { inputs: StateHandle, } -impl FromApp for Paddle { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - controls: None, - window: app.handle(), - inputs: app.handle(), - } - } -} - impl Paddle { pub(crate) const SIZE: Vec2 = Vec2::new(0.04, 0.18); const SPEED: f32 = 0.9; @@ -86,7 +75,7 @@ impl Paddle { } } - fn new_velocity(&mut self, app: &mut App) -> f32 { + fn new_velocity(&self, app: &mut App) -> f32 { if let Some(controls) = self.controls { let inputs = self.inputs.get(app); if inputs.fingers.pressed_iter().count() > 0 { @@ -111,7 +100,7 @@ impl Paddle { } } - fn reset_on_score(&mut self, app: &mut App) { + fn reset_on_score(&self, app: &mut App) { if app.get_mut::().is_reset_required { Body2DUpdater::default() .for_position(|p| p.y = 0.) diff --git a/examples/src/pong/wall.rs b/examples/src/pong/wall.rs index bc7ceb92..958dfb98 100644 --- a/examples/src/pong/wall.rs +++ b/examples/src/pong/wall.rs @@ -6,20 +6,12 @@ use modor_physics::{Body2D, Body2DUpdater, CollisionGroup}; pub(crate) const FIELD_BORDER_WIDTH: f32 = 0.02; pub(crate) const FIELD_SIZE: Vec2 = Vec2::new(1. - FIELD_BORDER_WIDTH, 0.75); +#[derive(FromApp)] pub(crate) struct Wall { body: Glob, sprite: Sprite2D, } -impl FromApp for Wall { - fn from_app(app: &mut App) -> Self { - Self { - body: Glob::from_app(app), - sprite: Sprite2D::new(app), - } - } -} - impl Wall { pub(crate) fn init( &mut self, diff --git a/examples/src/rendering_2d.rs b/examples/src/rendering_2d.rs index 51ab2213..e3b7b174 100644 --- a/examples/src/rendering_2d.rs +++ b/examples/src/rendering_2d.rs @@ -1,7 +1,9 @@ use instant::Instant; use modor::log::{info, Level}; use modor::{App, FromApp, State}; -use modor_graphics::{Color, DefaultMaterial2D, IntoMat, Mat, Model2D, Window}; +use modor_graphics::{ + Color, DefaultMaterial2D, DefaultMaterial2DUpdater, MatGlob, Model2D, Window, +}; use modor_physics::modor_math::Vec2; use modor_physics::Delta; use rand::Rng; @@ -56,36 +58,29 @@ impl State for Root { } } +#[derive(FromApp)] struct Resources { - materials: Vec>, -} - -impl FromApp for Resources { - fn from_app(app: &mut App) -> Self { - Self { - materials: COLORS - .iter() - .map(|&color| { - DefaultMaterial2D::new(app) - .with_color(color) - .with_is_ellipse(true) - .into_mat(app) - }) - .collect(), - } - } + materials: Vec>, } impl State for Resources { - fn update(&mut self, app: &mut App) { - for material in &mut self.materials { - material.update(app); - } + fn init(&mut self, app: &mut App) { + self.materials = COLORS + .iter() + .map(|&color| { + MatGlob::from_app_with(app, |mat, app| { + DefaultMaterial2DUpdater::default() + .color(color) + .is_ellipse(true) + .apply(app, mat); + }) + }) + .collect(); } } struct Object { - model: Model2D, + model: Model2D, next_update: Instant, // A `Body2D` could be used instead of manually handle the velocity, but for performance reasons // this is not recommended with a large amount of objects (> 10K objects). @@ -95,9 +90,10 @@ struct Object { impl Object { fn new(app: &mut App, index: usize) -> Self { let mut rng = rand::thread_rng(); - let material = app.get_mut::().materials[index % COLORS.len()].glob(); + let material = app.get_mut::().materials[index % COLORS.len()].to_ref(); let position = Vec2::new(rng.gen_range(-0.2..0.2), rng.gen_range(-0.2..0.2)); - let model = Model2D::new(app, material) + let model = Model2D::new(app) + .with_material(material.to_ref()) .with_position(position) .with_size(Vec2::ONE * 0.01) .with_z_index(rng.gen_range(i16::MIN..i16::MAX)); diff --git a/examples/src/text_2d.rs b/examples/src/text_2d.rs index 7ac2e742..7d0aa8c0 100644 --- a/examples/src/text_2d.rs +++ b/examples/src/text_2d.rs @@ -2,9 +2,9 @@ use instant::Instant; use modor::log::Level; use modor::{App, FromApp, Glob, State}; use modor_graphics::modor_resources::{Res, ResUpdater}; -use modor_graphics::{Color, Sprite2D}; +use modor_graphics::{Color, DefaultMaterial2DUpdater, Sprite2D}; use modor_physics::modor_math::Vec2; -use modor_text::{Font, FontUpdater, Text2D}; +use modor_text::{Font, FontUpdater, Text2D, TextMaterial2DUpdater}; use std::time::Duration; pub fn main() { @@ -22,14 +22,22 @@ impl FromApp for Root { let size = Vec2::new(1., 0.2); let font = app.get_mut::().font.to_ref(); Self { - background: Sprite2D::new(app) + background: Sprite2D::from_app(app) .with_model(|m| m.size = size) - .with_material(|m| m.color = Color::rgb(0.1, 0.1, 0.1)), + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .color(Color::rgb(0.1, 0.1, 0.1)) + .apply(app, m); + }), text: Text2D::new(app) .with_content("Loading".into()) .with_font(font) .with_font_height(300.) - .with_material(|m| m.color = Color::GREEN) + .with_material(|m| { + TextMaterial2DUpdater::default() + .color(Color::GREEN) + .apply(app, m); + }) .with_model(|m| m.size = size) .with_model(|m| m.z_index = 1), last_update: Instant::now(), diff --git a/examples/src/texture_2d.rs b/examples/src/texture_2d.rs index 00227094..a4d604be 100644 --- a/examples/src/texture_2d.rs +++ b/examples/src/texture_2d.rs @@ -2,7 +2,7 @@ use modor::log::Level; use modor::{App, FromApp, Glob, State}; use modor_graphics::modor_input::modor_math::Vec2; use modor_graphics::modor_resources::{Res, ResUpdater}; -use modor_graphics::{Color, Sprite2D, Texture, TextureUpdater}; +use modor_graphics::{Color, DefaultMaterial2DUpdater, Sprite2D, Texture, TextureUpdater}; use std::f32::consts::{FRAC_PI_2, FRAC_PI_4}; pub fn main() { @@ -16,9 +16,12 @@ struct Root { impl FromApp for Root { fn from_app(app: &mut App) -> Self { - let background_texture = app.get_mut::().background_texture.to_ref(); Self { - background: Sprite2D::new(app).with_material(|m| m.texture = background_texture), + background: Sprite2D::from_app(app).with_material(|m| { + DefaultMaterial2DUpdater::default() + .texture(app.get_mut::().background_texture.to_ref()) + .apply(app, m); + }), smileys: vec![ Smiley::new( app, @@ -82,14 +85,17 @@ impl Smiley { velocity: Vec2, angular_velocity: f32, ) -> Self { - let texture = app.get_mut::().smiley_texture.to_ref(); Self { - sprite: Sprite2D::new(app) + sprite: Sprite2D::from_app(app) .with_model(|m| m.position = position) .with_model(|m| m.size = Vec2::ONE * 0.2) .with_model(|m| m.z_index = z_index) - .with_material(|m| m.color = color) - .with_material(|m| m.texture = texture), + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .color(color) + .texture(app.get_mut::().smiley_texture.to_ref()) + .apply(app, m); + }), velocity, angular_velocity, } diff --git a/examples/src/texture_animation.rs b/examples/src/texture_animation.rs index 044e35aa..e41a522b 100644 --- a/examples/src/texture_animation.rs +++ b/examples/src/texture_animation.rs @@ -3,7 +3,8 @@ use modor::{App, FromApp, Glob, State}; use modor_graphics::modor_input::{Inputs, Key}; use modor_graphics::modor_resources::{Res, ResUpdater}; use modor_graphics::{ - Color, Sprite2D, Texture, TextureAnimation, TexturePart, TextureUpdater, Window, + Color, DefaultMaterial2DUpdater, Sprite2D, Texture, TextureAnimation, TexturePart, + TextureUpdater, Window, }; use modor_physics::modor_math::Vec2; use modor_physics::{Body2D, Body2DUpdater}; @@ -20,7 +21,11 @@ struct Root { impl State for Root { fn init(&mut self, app: &mut App) { self.slime.init(app); - app.get_mut::().target.background_color = Color::DARK_GRAY; + app.get_mut::() + .target + .to_ref() + .get_mut(app) + .background_color = Color::DARK_GRAY; } fn update(&mut self, app: &mut App) { @@ -53,7 +58,7 @@ impl FromApp for Slime { fn from_app(app: &mut App) -> Self { Self { body: Glob::from_app(app), - sprite: Sprite2D::new(app), + sprite: Sprite2D::from_app(app), animation: TextureAnimation::new(5, 9), direction: Direction::Down, } @@ -66,7 +71,9 @@ impl Slime { .size(Vec2::ONE * 0.15) .apply(app, &self.body); self.sprite.model.body = Some(self.body.to_ref()); - self.sprite.material.texture = app.get_mut::().slime_texture.to_ref(); + DefaultMaterial2DUpdater::default() + .texture(app.get_mut::().slime_texture.to_ref()) + .apply(app, &self.sprite.material); self.animation.parts = Direction::Down.stopped_texture_parts(); } @@ -79,8 +86,10 @@ impl Slime { ); self.direction = Direction::from_vec(direction).unwrap_or(self.direction); self.animation.parts = self.direction.texture_parts(direction == Vec2::ZERO); - self.sprite.material.texture_size = self.animation.part_size(); - self.sprite.material.texture_position = self.animation.part_position(); + DefaultMaterial2DUpdater::default() + .texture_size(self.animation.part_size()) + .texture_position(self.animation.part_position()) + .apply(app, &self.sprite.material); Body2DUpdater::default() .for_size(|s| s.x = self.direction.size_x_sign() * s.x.abs()) .velocity(0.2 * direction) diff --git a/examples/src/texture_target.rs b/examples/src/texture_target.rs index 25f3fbe8..b7018114 100644 --- a/examples/src/texture_target.rs +++ b/examples/src/texture_target.rs @@ -2,7 +2,8 @@ use modor::log::Level; use modor::{App, FromApp, Glob, State}; use modor_graphics::modor_resources::{Res, ResUpdater}; use modor_graphics::{ - Camera2D, Color, Size, Sprite2D, Texture, TextureSource, TextureUpdater, Window, + Camera2D, Color, DefaultMaterial2DUpdater, Size, Sprite2D, Texture, TextureSource, + TextureUpdater, Window, }; use modor_physics::modor_math::Vec2; @@ -17,7 +18,11 @@ struct Root { impl FromApp for Root { fn from_app(app: &mut App) -> Self { - app.get_mut::().target.background_color = Color::GRAY; + app.get_mut::() + .target + .to_ref() + .get_mut(app) + .background_color = Color::GRAY; Self { target_rectangle: Self::target_rectangle(app), inner_rectangle: Self::inner_rectangle(app), @@ -35,16 +40,23 @@ impl State for Root { impl Root { fn target_rectangle(app: &mut App) -> Sprite2D { - let target_texture = app.get_mut::().texture.to_ref(); - Sprite2D::new(app).with_material(|m| m.texture = target_texture) + Sprite2D::from_app(app).with_material(|m| { + DefaultMaterial2DUpdater::default() + .texture(app.get_mut::().texture.to_ref()) + .apply(app, m); + }) } fn inner_rectangle(app: &mut App) -> Sprite2D { let target_camera = app.get_mut::().camera.glob().to_ref(); - Sprite2D::new(app) + Sprite2D::from_app(app) .with_model(|m| m.size = Vec2::ONE * 0.2) .with_model(|m| m.camera = target_camera) - .with_material(|m| m.color = Color::RED) + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .color(Color::RED) + .apply(app, m); + }) } } @@ -56,7 +68,7 @@ struct TextureTarget { impl FromApp for TextureTarget { fn from_app(app: &mut App) -> Self { let texture = Glob::>::from_app(app); - let camera = Camera2D::new(app, vec![texture.get(app).target().glob().to_ref()]); + let camera = Camera2D::new(app, vec![texture.get(app).target().to_ref()]); Self { texture, camera } } } @@ -67,6 +79,7 @@ impl State for TextureTarget { .texture .get(app) .target() + .get(app) .supported_anti_aliasing_modes() .iter() .copied() diff --git a/examples/src/touch.rs b/examples/src/touch.rs index a7aad94b..fb2b61f5 100644 --- a/examples/src/touch.rs +++ b/examples/src/touch.rs @@ -2,7 +2,7 @@ use modor::log::Level; use modor::{App, FromApp, State}; use modor_graphics::modor_input::modor_math::Vec2; use modor_graphics::modor_input::Inputs; -use modor_graphics::{Sprite2D, Window}; +use modor_graphics::{DefaultMaterial2DUpdater, Sprite2D, Window}; pub fn main() { modor_graphics::run::(Level::Info); @@ -37,9 +37,13 @@ impl Root { let window_size = window.size(); let camera = window.camera.glob().to_ref(); let position = camera.get(app).world_position(window_size, finger_position); - Sprite2D::new(app) + Sprite2D::from_app(app) .with_model(|m| m.position = position) .with_model(|m| m.size = Vec2::ONE * 0.3) - .with_material(|m| m.is_ellipse = true) + .with_material(|m| { + DefaultMaterial2DUpdater::default() + .is_ellipse(true) + .apply(app, m); + }) } }