1use bevy_asset::AssetId;
48use bevy_ecs::{
49 query::{QueryData, QueryItem},
50 system::lifetimeless::Read,
51};
52use bevy_image::Image;
53use bevy_light::{EnvironmentMapLight, ParallaxCorrection};
54use bevy_math::{Affine3A, Quat, Vec3};
55use bevy_render::{
56 extract_instances::ExtractInstance,
57 render_asset::RenderAssets,
58 render_resource::{
59 binding_types, BindGroupLayoutEntryBuilder, Sampler, SamplerBindingType, TextureSampleType,
60 TextureView,
61 },
62 renderer::{RenderAdapter, RenderDevice},
63 texture::{FallbackImage, GpuImage},
64};
65
66use core::{num::NonZero, ops::Deref};
67
68use crate::{
69 add_cubemap_texture_view, binding_arrays_are_usable, RenderLightProbeFlags,
70 MAX_VIEW_LIGHT_PROBES,
71};
72
73use super::{LightProbeComponent, RenderViewLightProbes};
74
75#[derive(Clone, Copy, PartialEq, Eq, Hash)]
79pub struct EnvironmentMapIds {
80 pub diffuse: AssetId<Image>,
82 pub specular: AssetId<Image>,
85}
86
87pub(crate) enum RenderViewEnvironmentMapBindGroupEntries<'a> {
90 Single {
93 diffuse_texture_view: &'a TextureView,
95
96 specular_texture_view: &'a TextureView,
98
99 sampler: &'a Sampler,
102 },
103
104 Multiple {
107 diffuse_texture_views: Vec<&'a <TextureView as Deref>::Target>,
114
115 specular_texture_views: Vec<&'a <TextureView as Deref>::Target>,
117
118 sampler: &'a Sampler,
121 },
122}
123
124pub struct EnvironmentMapViewLightProbeInfo {
128 pub(crate) cubemap_index: i32,
130 pub(crate) smallest_specular_mip_level: u32,
132 pub(crate) intensity: f32,
135 pub(crate) affects_lightmapped_mesh_diffuse: bool,
138 pub(crate) rotation: Quat,
140}
141
142impl ExtractInstance for EnvironmentMapIds {
143 type QueryData = Read<EnvironmentMapLight>;
144
145 type QueryFilter = ();
146
147 fn extract(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {
148 Some(EnvironmentMapIds {
149 diffuse: item.diffuse_map.id(),
150 specular: item.specular_map.id(),
151 })
152 }
153}
154
155pub(crate) fn get_bind_group_layout_entries(
158 render_device: &RenderDevice,
159 render_adapter: &RenderAdapter,
160) -> [BindGroupLayoutEntryBuilder; 3] {
161 let mut texture_cube_binding =
162 binding_types::texture_cube(TextureSampleType::Float { filterable: true });
163 if binding_arrays_are_usable(render_device, render_adapter) {
164 texture_cube_binding =
165 texture_cube_binding.count(NonZero::<u32>::new(MAX_VIEW_LIGHT_PROBES as _).unwrap());
166 }
167
168 [
169 texture_cube_binding,
170 texture_cube_binding,
171 binding_types::sampler(SamplerBindingType::Filtering),
172 ]
173}
174
175impl<'a> RenderViewEnvironmentMapBindGroupEntries<'a> {
176 pub(crate) fn get(
179 render_view_environment_maps: Option<&RenderViewLightProbes<EnvironmentMapLight>>,
180 images: &'a RenderAssets<GpuImage>,
181 fallback_image: &'a FallbackImage,
182 render_device: &RenderDevice,
183 render_adapter: &RenderAdapter,
184 ) -> RenderViewEnvironmentMapBindGroupEntries<'a> {
185 if binding_arrays_are_usable(render_device, render_adapter) {
186 let mut diffuse_texture_views = vec![];
188 let mut specular_texture_views = vec![];
189 let mut sampler = None;
190
191 if let Some(environment_maps) = render_view_environment_maps {
192 for &cubemap_id in &environment_maps.binding_index_to_textures {
193 add_cubemap_texture_view(
194 &mut diffuse_texture_views,
195 &mut sampler,
196 cubemap_id.diffuse,
197 images,
198 fallback_image,
199 );
200 add_cubemap_texture_view(
201 &mut specular_texture_views,
202 &mut sampler,
203 cubemap_id.specular,
204 images,
205 fallback_image,
206 );
207 }
208 }
209
210 diffuse_texture_views.resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);
213 specular_texture_views
214 .resize(MAX_VIEW_LIGHT_PROBES, &*fallback_image.cube.texture_view);
215
216 return RenderViewEnvironmentMapBindGroupEntries::Multiple {
217 diffuse_texture_views,
218 specular_texture_views,
219 sampler: sampler.unwrap_or(&fallback_image.cube.sampler),
220 };
221 }
222
223 if let Some(environment_maps) = render_view_environment_maps
224 && let Some(cubemap) = environment_maps.binding_index_to_textures.first()
225 && let (Some(diffuse_image), Some(specular_image)) =
226 (images.get(cubemap.diffuse), images.get(cubemap.specular))
227 {
228 return RenderViewEnvironmentMapBindGroupEntries::Single {
229 diffuse_texture_view: &diffuse_image.texture_view,
230 specular_texture_view: &specular_image.texture_view,
231 sampler: &diffuse_image.sampler,
232 };
233 }
234
235 RenderViewEnvironmentMapBindGroupEntries::Single {
236 diffuse_texture_view: &fallback_image.cube.texture_view,
237 specular_texture_view: &fallback_image.cube.texture_view,
238 sampler: &fallback_image.cube.sampler,
239 }
240 }
241}
242
243impl LightProbeComponent for EnvironmentMapLight {
244 type AssetId = EnvironmentMapIds;
245
246 type ViewLightProbeInfo = EnvironmentMapViewLightProbeInfo;
249
250 type QueryData = Option<Read<ParallaxCorrection>>;
251
252 fn id(&self, image_assets: &RenderAssets<GpuImage>) -> Option<Self::AssetId> {
253 if image_assets.get(&self.diffuse_map).is_none()
254 || image_assets.get(&self.specular_map).is_none()
255 {
256 None
257 } else {
258 Some(EnvironmentMapIds {
259 diffuse: self.diffuse_map.id(),
260 specular: self.specular_map.id(),
261 })
262 }
263 }
264
265 fn intensity(&self) -> f32 {
266 self.intensity
267 }
268
269 fn flags(
270 &self,
271 maybe_parallax_correction: &<Self::QueryData as QueryData>::Item<'_, '_>,
272 ) -> RenderLightProbeFlags {
273 let mut flags = RenderLightProbeFlags::empty();
274 if self.affects_lightmapped_mesh_diffuse {
275 flags.insert(RenderLightProbeFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE);
276 }
277 if maybe_parallax_correction.is_some_and(|parallax_correction| {
278 !matches!(*parallax_correction, ParallaxCorrection::None)
279 }) {
280 flags.insert(RenderLightProbeFlags::ENABLE_PARALLAX_CORRECTION);
281 }
282 flags
283 }
284
285 fn create_render_view_light_probes(
286 view_component: Option<&EnvironmentMapLight>,
287 image_assets: &RenderAssets<GpuImage>,
288 ) -> RenderViewLightProbes<Self> {
289 let mut render_view_light_probes = RenderViewLightProbes::new();
290
291 if let Some(EnvironmentMapLight {
294 diffuse_map: diffuse_map_handle,
295 specular_map: specular_map_handle,
296 intensity,
297 affects_lightmapped_mesh_diffuse,
298 rotation,
299 ..
300 }) = view_component
301 && let (Some(_), Some(specular_map)) = (
302 image_assets.get(diffuse_map_handle),
303 image_assets.get(specular_map_handle),
304 )
305 {
306 render_view_light_probes.view_light_probe_info =
307 Some(EnvironmentMapViewLightProbeInfo {
308 cubemap_index: render_view_light_probes.get_or_insert_cubemap(
309 &EnvironmentMapIds {
310 diffuse: diffuse_map_handle.id(),
311 specular: specular_map_handle.id(),
312 },
313 ) as i32,
314 smallest_specular_mip_level: specular_map.texture_descriptor.mip_level_count
315 - 1,
316 intensity: *intensity,
317 affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse,
318 rotation: *rotation,
319 });
320 };
321
322 render_view_light_probes
323 }
324
325 fn get_world_from_light_matrix(&self, original_transform: &Affine3A) -> Affine3A {
326 *original_transform * Affine3A::from_quat(self.rotation)
328 }
329
330 fn parallax_correction_bounds(
331 &self,
332 maybe_parallax_correction: &<Self::QueryData as QueryData>::Item<'_, '_>,
333 ) -> Vec3 {
334 match *maybe_parallax_correction {
335 Some(&ParallaxCorrection::Custom(bounds)) => bounds,
336 Some(&ParallaxCorrection::Auto) => Vec3::splat(0.5),
337 Some(&ParallaxCorrection::None) | None => Vec3::ZERO,
338 }
339 }
340}
341
342impl Default for EnvironmentMapViewLightProbeInfo {
343 fn default() -> Self {
344 Self {
345 cubemap_index: -1,
346 smallest_specular_mip_level: 0,
347 intensity: 1.0,
348 affects_lightmapped_mesh_diffuse: true,
349 rotation: Quat::IDENTITY,
350 }
351 }
352}