three_d/core/texture/
texture_cube_map.rs

1use crate::core::texture::*;
2
3#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
4///
5/// The 6 sides of a cube map
6///
7pub enum CubeMapSide {
8    /// Positive y
9    Top,
10    /// Negative y
11    Bottom,
12    /// Positive x
13    Right,
14    /// Negative x
15    Left,
16    /// Negative z
17    Front,
18    /// Positive z
19    Back,
20}
21
22///
23/// Iterator over the 6 side of a cube map.
24///
25pub struct CubeMapSideIterator {
26    index: usize,
27}
28
29impl CubeMapSideIterator {
30    ///
31    /// Creates a new iterator over the 6 side of a cube map.
32    ///
33    pub fn new() -> Self {
34        Self { index: 0 }
35    }
36}
37
38impl Default for CubeMapSideIterator {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44impl Iterator for CubeMapSideIterator {
45    type Item = CubeMapSide;
46    fn next(&mut self) -> Option<Self::Item> {
47        self.index += 1;
48        match self.index {
49            1 => Some(CubeMapSide::Right),
50            2 => Some(CubeMapSide::Left),
51            3 => Some(CubeMapSide::Top),
52            4 => Some(CubeMapSide::Bottom),
53            5 => Some(CubeMapSide::Front),
54            6 => Some(CubeMapSide::Back),
55            _ => None,
56        }
57    }
58}
59
60impl CubeMapSide {
61    ///
62    /// Iterator over the 6 side of a cube map.
63    ///
64    pub fn iter() -> CubeMapSideIterator {
65        CubeMapSideIterator::new()
66    }
67
68    pub(in crate::core) fn to_const(self) -> u32 {
69        match self {
70            CubeMapSide::Right => crate::context::TEXTURE_CUBE_MAP_POSITIVE_X,
71            CubeMapSide::Left => crate::context::TEXTURE_CUBE_MAP_NEGATIVE_X,
72            CubeMapSide::Top => crate::context::TEXTURE_CUBE_MAP_POSITIVE_Y,
73            CubeMapSide::Bottom => crate::context::TEXTURE_CUBE_MAP_NEGATIVE_Y,
74            CubeMapSide::Front => crate::context::TEXTURE_CUBE_MAP_POSITIVE_Z,
75            CubeMapSide::Back => crate::context::TEXTURE_CUBE_MAP_NEGATIVE_Z,
76        }
77    }
78
79    /// The up direction that should be used when rendering into this cube map side.
80    pub fn up(&self) -> Vec3 {
81        match self {
82            CubeMapSide::Right => vec3(0.0, -1.0, 0.0),
83            CubeMapSide::Left => vec3(0.0, -1.0, 0.0),
84            CubeMapSide::Top => vec3(0.0, 0.0, 1.0),
85            CubeMapSide::Bottom => vec3(0.0, 0.0, -1.0),
86            CubeMapSide::Front => vec3(0.0, -1.0, 0.0),
87            CubeMapSide::Back => vec3(0.0, -1.0, 0.0),
88        }
89    }
90
91    /// The direction from origo towards the center of this cube map side.
92    pub fn direction(&self) -> Vec3 {
93        match self {
94            CubeMapSide::Right => vec3(1.0, 0.0, 0.0),
95            CubeMapSide::Left => vec3(-1.0, 0.0, 0.0),
96            CubeMapSide::Top => vec3(0.0, 1.0, 0.0),
97            CubeMapSide::Bottom => vec3(0.0, -1.0, 0.0),
98            CubeMapSide::Front => vec3(0.0, 0.0, 1.0),
99            CubeMapSide::Back => vec3(0.0, 0.0, -1.0),
100        }
101    }
102}
103
104///
105/// A texture that covers all 6 sides of a cube.
106///
107pub struct TextureCubeMap {
108    context: Context,
109    id: crate::context::Texture,
110    width: u32,
111    height: u32,
112    number_of_mip_maps: u32,
113    data_byte_size: usize,
114}
115
116impl TextureCubeMap {
117    ///
118    /// Creates a new cube map texture from the given [CpuTexture]s.
119    /// All of the cpu textures must contain data with the same [TextureDataType].
120    ///
121    /// **Note:** Mip maps will not be generated for RGB16F and RGB32F format, even if `mip_map_filter` is specified.
122    ///
123    pub fn new(
124        context: &Context,
125        right: &CpuTexture,
126        left: &CpuTexture,
127        top: &CpuTexture,
128        bottom: &CpuTexture,
129        front: &CpuTexture,
130        back: &CpuTexture,
131    ) -> Self {
132        match &front.data {
133            TextureData::RU8(front_data) => Self::new_with_data(
134                context,
135                front,
136                right.wrap_s,
137                ru8_data(right),
138                ru8_data(left),
139                ru8_data(top),
140                ru8_data(bottom),
141                front_data,
142                ru8_data(back),
143            ),
144            TextureData::RgU8(front_data) => Self::new_with_data(
145                context,
146                front,
147                right.wrap_s,
148                rgu8_data(right),
149                rgu8_data(left),
150                rgu8_data(top),
151                rgu8_data(bottom),
152                front_data,
153                rgu8_data(back),
154            ),
155            TextureData::RgbU8(front_data) => Self::new_with_data(
156                context,
157                front,
158                right.wrap_s,
159                rgbu8_data(right),
160                rgbu8_data(left),
161                rgbu8_data(top),
162                rgbu8_data(bottom),
163                front_data,
164                rgbu8_data(back),
165            ),
166            TextureData::RgbaU8(front_data) => Self::new_with_data(
167                context,
168                front,
169                right.wrap_s,
170                rgbau8_data(right),
171                rgbau8_data(left),
172                rgbau8_data(top),
173                rgbau8_data(bottom),
174                front_data,
175                rgbau8_data(back),
176            ),
177            TextureData::RF16(front_data) => Self::new_with_data(
178                context,
179                front,
180                right.wrap_s,
181                rf16_data(right),
182                rf16_data(left),
183                rf16_data(top),
184                rf16_data(bottom),
185                front_data,
186                rf16_data(back),
187            ),
188            TextureData::RgF16(front_data) => Self::new_with_data(
189                context,
190                front,
191                right.wrap_s,
192                rgf16_data(right),
193                rgf16_data(left),
194                rgf16_data(top),
195                rgf16_data(bottom),
196                front_data,
197                rgf16_data(back),
198            ),
199            TextureData::RgbF16(front_data) => Self::new_with_data(
200                context,
201                front,
202                right.wrap_s,
203                rgbf16_data(right),
204                rgbf16_data(left),
205                rgbf16_data(top),
206                rgbf16_data(bottom),
207                front_data,
208                rgbf16_data(back),
209            ),
210            TextureData::RgbaF16(front_data) => Self::new_with_data(
211                context,
212                front,
213                right.wrap_s,
214                rgbaf16_data(right),
215                rgbaf16_data(left),
216                rgbaf16_data(top),
217                rgbaf16_data(bottom),
218                front_data,
219                rgbaf16_data(back),
220            ),
221            TextureData::RF32(front_data) => Self::new_with_data(
222                context,
223                front,
224                right.wrap_s,
225                rf32_data(right),
226                rf32_data(left),
227                rf32_data(top),
228                rf32_data(bottom),
229                front_data,
230                rf32_data(back),
231            ),
232            TextureData::RgF32(front_data) => Self::new_with_data(
233                context,
234                front,
235                right.wrap_s,
236                rgf32_data(right),
237                rgf32_data(left),
238                rgf32_data(top),
239                rgf32_data(bottom),
240                front_data,
241                rgf32_data(back),
242            ),
243            TextureData::RgbF32(front_data) => Self::new_with_data(
244                context,
245                front,
246                right.wrap_s,
247                rgbf32_data(right),
248                rgbf32_data(left),
249                rgbf32_data(top),
250                rgbf32_data(bottom),
251                front_data,
252                rgbf32_data(back),
253            ),
254            TextureData::RgbaF32(front_data) => Self::new_with_data(
255                context,
256                front,
257                right.wrap_s,
258                rgbaf32_data(right),
259                rgbaf32_data(left),
260                rgbaf32_data(top),
261                rgbaf32_data(bottom),
262                front_data,
263                rgbaf32_data(back),
264            ),
265        }
266    }
267
268    fn new_with_data<T: TextureDataType>(
269        context: &Context,
270        cpu_texture: &CpuTexture,
271        wrap_r: Wrapping,
272        right_data: &[T],
273        left_data: &[T],
274        top_data: &[T],
275        bottom_data: &[T],
276        front_data: &[T],
277        back_data: &[T],
278    ) -> Self {
279        let mut texture = Self::new_empty::<T>(
280            context,
281            cpu_texture.width,
282            cpu_texture.height,
283            cpu_texture.min_filter,
284            cpu_texture.mag_filter,
285            cpu_texture.mipmap,
286            cpu_texture.wrap_s,
287            cpu_texture.wrap_t,
288            wrap_r,
289        );
290        texture.fill(
291            right_data,
292            left_data,
293            top_data,
294            bottom_data,
295            front_data,
296            back_data,
297        );
298        texture
299    }
300
301    ///
302    /// Creates a new texture cube map.
303    ///
304    /// **Note:** Mip maps will not be generated for RGB16F and RGB32F format, even if `mip_map_filter` is specified.
305    ///
306    pub fn new_empty<T: TextureDataType>(
307        context: &Context,
308        width: u32,
309        height: u32,
310        min_filter: Interpolation,
311        mag_filter: Interpolation,
312        mipmap: Option<Mipmap>,
313        wrap_s: Wrapping,
314        wrap_t: Wrapping,
315        wrap_r: Wrapping,
316    ) -> Self {
317        let id = generate(context);
318        let number_of_mip_maps = calculate_number_of_mip_maps::<T>(mipmap, width, height, None);
319        let texture = Self {
320            context: context.clone(),
321            id,
322            width,
323            height,
324            number_of_mip_maps,
325            data_byte_size: std::mem::size_of::<T>(),
326        };
327        texture.bind();
328        set_parameters(
329            context,
330            crate::context::TEXTURE_CUBE_MAP,
331            min_filter,
332            mag_filter,
333            if number_of_mip_maps == 1 {
334                None
335            } else {
336                mipmap
337            },
338            wrap_s,
339            wrap_t,
340            Some(wrap_r),
341        );
342        unsafe {
343            context.tex_storage_2d(
344                crate::context::TEXTURE_CUBE_MAP,
345                number_of_mip_maps as i32,
346                T::internal_format(),
347                width as i32,
348                height as i32,
349            );
350        }
351        texture.generate_mip_maps();
352        texture
353    }
354
355    ///
356    /// Fills the cube map texture with the given pixel data for the 6 images.
357    ///
358    /// # Panic
359    /// Will panic if the length of the data for all 6 images does not correspond to the width, height and format specified at construction.
360    /// It is therefore necessary to create a new texture if the texture size or format has changed.
361    ///
362    pub fn fill<T: TextureDataType>(
363        &mut self,
364        right_data: &[T],
365        left_data: &[T],
366        top_data: &[T],
367        bottom_data: &[T],
368        front_data: &[T],
369        back_data: &[T],
370    ) {
371        check_data_length::<T>(
372            self.width,
373            self.height,
374            1,
375            self.data_byte_size,
376            right_data.len(),
377        );
378        check_data_length::<T>(
379            self.width,
380            self.height,
381            1,
382            self.data_byte_size,
383            left_data.len(),
384        );
385        check_data_length::<T>(
386            self.width,
387            self.height,
388            1,
389            self.data_byte_size,
390            top_data.len(),
391        );
392        check_data_length::<T>(
393            self.width,
394            self.height,
395            1,
396            self.data_byte_size,
397            bottom_data.len(),
398        );
399        check_data_length::<T>(
400            self.width,
401            self.height,
402            1,
403            self.data_byte_size,
404            front_data.len(),
405        );
406        check_data_length::<T>(
407            self.width,
408            self.height,
409            1,
410            self.data_byte_size,
411            back_data.len(),
412        );
413        self.bind();
414        for i in 0..6 {
415            let data = match i {
416                0 => right_data,
417                1 => left_data,
418                2 => top_data,
419                3 => bottom_data,
420                4 => front_data,
421                5 => back_data,
422                _ => unreachable!(),
423            };
424            unsafe {
425                self.context.tex_sub_image_2d(
426                    crate::context::TEXTURE_CUBE_MAP_POSITIVE_X + i as u32,
427                    0,
428                    0,
429                    0,
430                    self.width as i32,
431                    self.height as i32,
432                    format_from_data_type::<T>(),
433                    T::data_type(),
434                    crate::context::PixelUnpackData::Slice(to_byte_slice(data)),
435                );
436            }
437        }
438        self.generate_mip_maps();
439    }
440
441    ///
442    /// Creates a new cube texture generated from the equirectangular texture given as input.
443    ///
444    pub fn new_from_equirectangular<T: PrimitiveDataType + TextureDataType>(
445        context: &Context,
446        cpu_texture: &CpuTexture,
447    ) -> Self {
448        let texture_size = cpu_texture.width / 4;
449        let mut texture = Self::new_empty::<[T; 4]>(
450            context,
451            texture_size,
452            texture_size,
453            Interpolation::Linear,
454            Interpolation::Linear,
455            Some(Mipmap::default()),
456            Wrapping::ClampToEdge,
457            Wrapping::ClampToEdge,
458            Wrapping::ClampToEdge,
459        );
460
461        {
462            let map = Texture2D::new(context, cpu_texture);
463            let fragment_shader_source = "
464            uniform sampler2D equirectangularMap;
465            uniform vec3 direction;
466            uniform vec3 up;
467
468            in vec2 uvs;
469            
470            layout (location = 0) out vec4 outColor;
471            
472            void main()
473            {
474                vec3 right = cross(direction, up);
475                vec3 dir = normalize(up * (uvs.y - 0.5) * 2.0 + right * (uvs.x - 0.5) * 2.0 + direction);
476                vec2 uv = vec2(0.1591 * atan(dir.z, dir.x) + 0.5, 0.3183 * asin(dir.y) + 0.5);
477                outColor = texture(equirectangularMap, uv);
478            }";
479
480            let program = Program::from_source(
481                context,
482                full_screen_vertex_shader_source(),
483                &fragment_shader_source,
484            )
485            .expect("Failed compiling shader");
486
487            for side in CubeMapSide::iter() {
488                let viewport = Viewport::new_at_origo(texture_size, texture_size);
489                texture
490                    .as_color_target(&[side], None)
491                    .clear(ClearState::default())
492                    .write::<CoreError>(|| {
493                        program.use_texture("equirectangularMap", &map);
494                        program.use_uniform("direction", side.direction());
495                        program.use_uniform("up", side.up());
496                        full_screen_draw(context, &program, RenderStates::default(), viewport);
497                        Ok(())
498                    })
499                    .unwrap();
500            }
501        }
502        texture
503    }
504
505    ///
506    /// Returns a [ColorTarget] which can be used to clear, write to and read from the given side and mip level of this texture.
507    /// Combine this together with a [DepthTarget] with [RenderTarget::new] to be able to write to both a depth and color target at the same time.
508    /// If `None` is specified as the mip level, the 0 level mip level is used and mip maps are generated after a write operation if a mip map filter is specified.
509    /// Otherwise, the given mip level is used and no mip maps are generated.
510    ///
511    /// **Note:** [DepthTest] is disabled if not also writing to a depth texture.
512    ///
513    pub fn as_color_target<'a>(
514        &'a mut self,
515        sides: &'a [CubeMapSide],
516        mip_level: Option<u32>,
517    ) -> ColorTarget<'a> {
518        ColorTarget::new_texture_cube_map(&self.context, self, sides, mip_level)
519    }
520
521    /// The width of this texture.
522    pub fn width(&self) -> u32 {
523        self.width
524    }
525
526    /// The height of this texture.
527    pub fn height(&self) -> u32 {
528        self.height
529    }
530
531    /// The number of mip maps of this texture.
532    pub fn number_of_mip_maps(&self) -> u32 {
533        self.number_of_mip_maps
534    }
535
536    pub(in crate::core) fn generate_mip_maps(&self) {
537        if self.number_of_mip_maps > 1 {
538            self.bind();
539            unsafe {
540                self.context
541                    .generate_mipmap(crate::context::TEXTURE_CUBE_MAP);
542            }
543        }
544    }
545
546    pub(in crate::core) fn bind_as_color_target(
547        &self,
548        side: CubeMapSide,
549        channel: u32,
550        mip_level: u32,
551    ) {
552        unsafe {
553            self.context.framebuffer_texture_2d(
554                crate::context::DRAW_FRAMEBUFFER,
555                crate::context::COLOR_ATTACHMENT0 + channel,
556                side.to_const(),
557                Some(self.id),
558                mip_level as i32,
559            );
560        }
561    }
562
563    pub(in crate::core) fn bind(&self) {
564        unsafe {
565            self.context
566                .bind_texture(crate::context::TEXTURE_CUBE_MAP, Some(self.id));
567        }
568    }
569}
570
571impl Drop for TextureCubeMap {
572    fn drop(&mut self) {
573        unsafe {
574            self.context.delete_texture(self.id);
575        }
576    }
577}