use std::path::Path; use std::sync::LazyLock; use ecs::Query; use ecs::actions::Actions; use ecs::query::term::Without; use ecs::sole::Single; use crate::asset::{Assets, Label as AssetLabel}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::data_types::dimens::Dimens; use crate::draw_flags::NoDraw; use crate::lighting::{DirectionalLight, GlobalLight, PointLight}; use crate::material::{Flags as MaterialFlags, Material}; use crate::matrix::Matrix; use crate::model::{MaterialSearchResult, Model}; use crate::projection::{ClipVolume as ProjectionClipVolume, Projection}; use crate::renderer::{DEFAULT_TEXTURE_ASSET_LABEL, PendingShaderBindings}; use crate::shader::cursor::{BindingValue as ShaderBindingValue, Cursor as ShaderCursor}; use crate::shader::{ Context as ShaderContext, ModuleSource as ShaderModuleSource, Shader, }; use crate::transform::{Scale, Transform, WorldPosition}; use crate::vector::Vec3; use crate::windowing::window::Window; pub static ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { path: Path::new("").into(), name: Some("default_shader".into()), }); pub fn enqueue_set_shader_bindings( renderable_query: Query, (Without,)>, camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>, window_query: Query<(&Window,)>, point_light_query: Query<(&PointLight, &WorldPosition)>, directional_light_query: Query<(&DirectionalLight,)>, assets: Single, shader_context: Single, global_light: Single, mut actions: Actions, ) { let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { tracing::warn!("No current camera"); return; }; let Some((window,)) = window_query.iter().next() else { // tracing::warn!("No window"); return; }; let default_shader_asset = assets .get_handle_to_loaded::(ASSET_LABEL.clone()) .unwrap(); for ( entity_id, (model, material_flags, world_pos, scale, shader, mut pending_shader_bindings), ) in renderable_query.iter_with_euids() { let shader_asset = match &shader { Some(shader) => &shader.asset_handle, None => &default_shader_asset, }; if shader_asset.id() != default_shader_asset.id() { continue; } let Some(shader_program) = shader_context.get_program(&shader_asset.id()) else { continue; }; let Some(model_spec) = assets.get(&model.spec_asset) else { continue; }; let has_pending_shader_bindings_comp = pending_shader_bindings.is_some(); let shader_bindings = match pending_shader_bindings.as_deref_mut() { Some(pending_shader_bindings) => pending_shader_bindings, None => &mut PendingShaderBindings::default(), }; let shader_cursor = ShaderCursor::new( shader_program .reflection(0) .unwrap() .global_params_var_layout() .unwrap(), ); let model_matrix = create_model_matrix(Transform { position: world_pos.as_deref().cloned().unwrap_or_default().position, scale: scale.as_deref().cloned().unwrap_or_default().scale, }); let inverted_model_matrix = model_matrix.inverse(); let model_material = match model_spec.find_first_material(&assets) { MaterialSearchResult::Found(model_material_asset) => { let Some(model_material) = assets.get(&model_material_asset) else { continue; }; model_material } MaterialSearchResult::NotFound => { continue; } MaterialSearchResult::NoMaterials => &const { Material::builder().build() }, }; if [ &model_material.ambient_map, &model_material.diffuse_map, &model_material.specular_map, ] .into_iter() .flatten() .any(|texture| !assets.is_loaded_and_has_type(&texture.asset_handle)) { continue; } let material_flags = material_flags .as_deref() .unwrap_or(&const { MaterialFlags::builder().build() }); let model_3d_shader_cursor = shader_cursor.field("Uniforms").field("model_3d"); let lighting_shader_cursor = shader_cursor.field("Uniforms").field("lighting"); let material_shader_cursor = lighting_shader_cursor.field("material"); shader_bindings.extend([ (model_3d_shader_cursor.field("model"), model_matrix.into()), ( model_3d_shader_cursor.field("model_inverted"), inverted_model_matrix.into(), ), ( model_3d_shader_cursor.field("view"), create_view_matrix(&camera, &camera_world_pos.position).into(), ), ( model_3d_shader_cursor.field("projection"), create_projection_matrix( &camera, &camera_world_pos.position, window.inner_size(), ) .into(), ), ( lighting_shader_cursor.field("view_pos"), camera_world_pos.position.into(), ), ( material_shader_cursor.field("ambient"), Vec3::from( if material_flags.use_ambient_color { &model_material.ambient } else { &global_light.ambient } .clone(), ) .into(), ), ( material_shader_cursor.field("diffuse"), Vec3::from(model_material.diffuse.clone()).into(), ), ( material_shader_cursor.field("specular"), Vec3::from(model_material.specular.clone()).into(), ), ( material_shader_cursor.field("shininess"), model_material.shininess.into(), ), ( lighting_shader_cursor.field("directional_light_cnt"), u32::try_from(directional_light_query.iter().count()) .expect( "Directional light count does not fit in 32-bit unsigned integer", ) .into(), ), ( lighting_shader_cursor.field("point_light_cnt"), u32::try_from(point_light_query.iter().count()) .expect("Point light count does not fit in 32-bit unsigned integer") .into(), ), ]); shader_bindings.extend(point_light_query.iter().enumerate().flat_map( |(point_light_index, (point_light, point_light_world_pos))| { let point_light_shader_cursor = lighting_shader_cursor .field("point_lights") .element(point_light_index); let phong_shader_cursor = point_light_shader_cursor.field("phong"); let attenuation_props_shader_cursor = point_light_shader_cursor.field("attenuation_props"); [ ( phong_shader_cursor.field("diffuse"), Vec3::from(point_light.diffuse.clone()).into(), ), ( phong_shader_cursor.field("specular"), Vec3::from(point_light.specular.clone()).into(), ), ( point_light_shader_cursor.field("position"), (point_light_world_pos.position + point_light.local_position) .into(), ), ( attenuation_props_shader_cursor.field("constant"), point_light.attenuation_params.constant.into(), ), ( attenuation_props_shader_cursor.field("linear"), point_light.attenuation_params.linear.into(), ), ( attenuation_props_shader_cursor.field("quadratic"), point_light.attenuation_params.quadratic.into(), ), ] }, )); shader_bindings.extend(directional_light_query.iter().enumerate().flat_map( |(directional_light_index, (directional_light,))| { let directional_light_shader_cursor = lighting_shader_cursor .field("directional_lights") .element(directional_light_index); let phong_shader_cursor = directional_light_shader_cursor.field("phong"); [ ( phong_shader_cursor.field("diffuse"), Vec3::from(directional_light.diffuse.clone()).into(), ), ( phong_shader_cursor.field("specular"), Vec3::from(directional_light.specular.clone()).into(), ), ( directional_light_shader_cursor.field("direction"), directional_light.direction.into(), ), ] }, )); shader_bindings.bindings.push(( shader_cursor.field("ambient_map").into_binding_location(), ShaderBindingValue::Texture( model_material .ambient_map .as_ref() .map(|ambient_map| ambient_map.asset_handle.clone()) .unwrap_or_else(|| { assets .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone()) .expect("Not possible") }), ), )); shader_bindings.bindings.push(( shader_cursor.field("diffuse_map").into_binding_location(), ShaderBindingValue::Texture( model_material .diffuse_map .as_ref() .map(|diffuse_map| diffuse_map.asset_handle.clone()) .unwrap_or_else(|| { assets .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone()) .expect("Not possible") }), ), )); shader_bindings.bindings.push(( shader_cursor.field("specular_map").into_binding_location(), ShaderBindingValue::Texture( model_material .specular_map .as_ref() .map(|specular_map| specular_map.asset_handle.clone()) .unwrap_or_else(|| { assets .get_handle_to_loaded(DEFAULT_TEXTURE_ASSET_LABEL.clone()) .expect("Not possible") }), ), )); if !has_pending_shader_bindings_comp { actions.add_components(entity_id, (shader_bindings.clone(),)); } } } fn create_model_matrix(transform: Transform) -> Matrix { let mut matrix = Matrix::new_identity(); matrix.translate(&transform.position); matrix.scale(&transform.scale); matrix } fn create_view_matrix(camera: &Camera, camera_world_pos: &Vec3) -> Matrix { let mut view = Matrix::new(); // tracing::debug!("Camera target: {:?}", camera.target); view.look_at(camera_world_pos, &camera.target, &camera.global_up); view } fn create_projection_matrix( camera: &Camera, camera_world_pos: &Vec3, window_size: &Dimens, ) -> Matrix { match &camera.projection { Projection::Perspective(perspective_proj) => perspective_proj.to_matrix_rh( window_size.width as f32 / window_size.height as f32, ProjectionClipVolume::NegOneToOne, ), Projection::Orthographic(orthographic_proj) => orthographic_proj .to_matrix_rh(camera_world_pos, ProjectionClipVolume::NegOneToOne), } } type RenderableEntity<'a> = ( &'a Model, Option<&'a MaterialFlags>, Option<&'a WorldPosition>, Option<&'a Scale>, Option<&'a Shader>, Option<&'a mut PendingShaderBindings>, );