Skip to main content

glacier_texture/
box_reflection.rs

1use crate::convert;
2use binrw::{binrw, BinRead, BinWriterExt};
3use directxtex::{
4    HResultError, Image, ScratchImage, CP_FLAGS, CP_FLAGS_NONE, DDS_FLAGS, DDS_FLAGS_NONE,
5    DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_R16G16B16A16_FLOAT, TEX_COMPRESS_DEFAULT,
6    TEX_FILTER_DEFAULT, TEX_THRESHOLD_DEFAULT,
7};
8use std::borrow::Borrow;
9use std::io::{BufWriter, Cursor, Seek, Write};
10use std::ops::{Index, IndexMut};
11use std::path::Path;
12use std::{fs, io, slice};
13
14pub use cubemap_utils::Orientation;
15use glacier_base::math::Vector3;
16#[cfg(feature = "image")]
17use image::{ColorType, DynamicImage, ExtendedColorType};
18
19#[derive(Debug, thiserror::Error)]
20pub enum BoxReflectionError {
21    #[error("Io error")]
22    IoError(#[from] io::Error),
23
24    #[error("Parsing error")]
25    ParsingError(#[from] binrw::Error),
26
27    #[error("Error building boxreflections: {0}")]
28    PackingError(String),
29
30    #[error("DirectxTex error {0}")]
31    DirectXTexError(#[from] HResultError),
32
33    #[error("Error {0}")]
34    Other(String),
35}
36
37#[binrw]
38#[derive(Default, Clone, Debug)]
39pub struct BoxReflectionCache {
40    #[br(temp)]
41    #[bw(calc(entries.len() as u32))]
42    num_entries: u32,
43    #[br(count = num_entries)]
44    entries: Vec<BoxReflection>,
45}
46
47impl BoxReflectionCache {
48    pub fn len(&self) -> usize {
49        self.entries.len()
50    }
51    pub fn is_empty(&self) -> bool {
52        self.entries.is_empty()
53    }
54
55    pub fn as_slice(&self) -> &[BoxReflection] {
56        &self.entries
57    }
58
59    pub fn as_mut_slice(&mut self) -> &mut [BoxReflection] {
60        &mut self.entries
61    }
62
63    pub fn get(&self, index: usize) -> Option<&BoxReflection> {
64        self.entries.get(index)
65    }
66
67    pub fn get_mut(&mut self, index: usize) -> Option<&mut BoxReflection> {
68        self.entries.get_mut(index)
69    }
70
71    fn nearest_by<I, T>(iter: I, position: Vector3) -> Option<I::Item>
72    where
73        I: Iterator<Item = T>,
74        T: Borrow<BoxReflection>,
75    {
76        iter.min_by(|a, b| {
77            let a_ref = a.borrow();
78            let b_ref = b.borrow();
79
80            let dx_a = a_ref.x() - position.x;
81            let dy_a = a_ref.y() - position.y;
82            let dz_a = a_ref.z() - position.z;
83            let da = dx_a * dx_a + dy_a * dy_a + dz_a * dz_a;
84
85            let dx_b = b_ref.x() - position.x;
86            let dy_b = b_ref.y() - position.y;
87            let dz_b = b_ref.z() - position.z;
88            let db = dx_b * dx_b + dy_b * dy_b + dz_b * dz_b;
89
90            da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
91        })
92    }
93
94    pub fn get_at_position(&self, position: Vector3) -> Option<&BoxReflection> {
95        Self::nearest_by(self.entries.iter(), position)
96    }
97
98    pub fn get_at_position_mut(&mut self, position: Vector3) -> Option<&mut BoxReflection> {
99        Self::nearest_by(self.entries.iter_mut(), position)
100    }
101}
102
103#[binrw]
104#[derive(Default, Clone, Debug)]
105pub struct BoxReflection {
106    pos: Vector3,
107    #[br(temp)]
108    #[bw(calc(buffer.len() as u32))]
109    size: u32,
110    #[br(count = size)]
111    buffer: Vec<u8>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum CubemapLayout {
116    HorizontalStrip,
117    VerticalStrip,
118    HorizontalCross,
119    VerticalCross,
120}
121
122impl CubemapLayout {
123    pub fn variants() -> [Self; 4] {
124        [
125            Self::HorizontalStrip,
126            Self::VerticalStrip,
127            Self::HorizontalCross,
128            Self::VerticalCross,
129        ]
130    }
131
132    pub fn from_tile_counts(tiles_x: usize, tiles_y: usize) -> Option<Self> {
133        let dims = (tiles_x, tiles_y);
134        Self::variants()
135            .iter()
136            .copied()
137            .find(|v| v.tile_counts() == dims)
138    }
139    pub fn tile_positions(&self) -> [(usize, usize); 6] {
140        match self {
141            //   +X -X +Y -Y +Z -Z
142            CubemapLayout::HorizontalStrip => [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)],
143            CubemapLayout::VerticalStrip => [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5)],
144            //   .  +Y  .
145            //   -X +Z  +X  -Z
146            //   .  -Y  .
147            CubemapLayout::HorizontalCross => [(2, 1), (0, 1), (1, 0), (1, 2), (1, 1), (3, 1)],
148            //   .  +Y  .
149            //   -X +Z +X
150            //   .  -Y  .
151            //   .  Z-  .
152            CubemapLayout::VerticalCross => [(2, 1), (0, 1), (1, 0), (1, 2), (1, 1), (1, 3)],
153        }
154    }
155
156    pub fn tile_counts(&self) -> (usize, usize) {
157        let face_tile_positions = self.tile_positions();
158        let num_width_tiles = face_tile_positions
159            .iter()
160            .map(|(w, _)| *w)
161            .max()
162            .unwrap_or_default()
163            + 1;
164        let num_height_tiles = face_tile_positions
165            .iter()
166            .map(|(_, h)| *h)
167            .max()
168            .unwrap_or_default()
169            + 1;
170        (num_width_tiles, num_height_tiles)
171    }
172}
173
174impl BoxReflection {
175    #[allow(clippy::misnamed_getters)]
176    pub fn x(&self) -> f32 {
177        self.pos.z
178    } // This is supposed to return z
179    pub fn y(&self) -> f32 {
180        self.pos.y
181    }
182    #[allow(clippy::misnamed_getters)]
183    pub fn z(&self) -> f32 {
184        self.pos.x
185    } // This is supposed to return x
186
187    pub const fn tile_width() -> usize {
188        128
189    }
190    pub const fn tile_height() -> usize {
191        128
192    }
193
194    #[allow(dead_code)]
195    pub(crate) fn buffer_size(&self) -> usize {
196        self.buffer.len()
197    }
198
199    #[cfg(feature = "image")]
200    pub fn from_dynamic_image(
201        image: &DynamicImage,
202        pos: Vector3,
203    ) -> Result<Self, BoxReflectionError> {
204        let extended_color = match &image.color() {
205            ColorType::L8 => ExtendedColorType::L8,
206            ColorType::La8 => ExtendedColorType::La8,
207            ColorType::Rgb8 => ExtendedColorType::Rgb8,
208            ColorType::Rgba8 => ExtendedColorType::Rgba8,
209            ColorType::L16 => ExtendedColorType::L16,
210            ColorType::La16 => ExtendedColorType::La16,
211            ColorType::Rgb16 => ExtendedColorType::Rgb16,
212            ColorType::Rgba16 => ExtendedColorType::Rgba16,
213            ColorType::Rgb32F => ExtendedColorType::Rgb32F,
214            ColorType::Rgba32F => ExtendedColorType::Rgba32F,
215            _ => {
216                return Err(BoxReflectionError::Other(
217                    "Cannot find dynamic image".to_owned(),
218                ))
219            }
220        };
221
222        let scratch_image = crate::image::helpers::dynamic_image_to_scratch_image(
223            image.as_bytes(),
224            image.width(),
225            image.height(),
226            extended_color,
227        )
228        .map_err(|e| BoxReflectionError::Other(e.to_string()))?;
229        Self::from_scratch_image(scratch_image, pos)
230    }
231
232    pub fn from_dds(data: Vec<u8>, pos: Vector3) -> Result<BoxReflection, BoxReflectionError> {
233        let dds = ScratchImage::load_dds(&data, DDS_FLAGS_NONE, None, None)?;
234        Self::from_scratch_image(dds, pos)
235    }
236
237    pub(crate) fn from_scratch_image(
238        scratch_image: ScratchImage,
239        pos: Vector3,
240    ) -> Result<BoxReflection, BoxReflectionError> {
241        let (w, h) = (
242            scratch_image.metadata().width,
243            scratch_image.metadata().height,
244        );
245
246        let cols = w / Self::tile_width();
247        let rows = h / Self::tile_height();
248
249        let layout = CubemapLayout::from_tile_counts(cols, rows);
250
251        if let Some(layout) = layout {
252            let scratch_image = scratch_image.convert(
253                DXGI_FORMAT_R16G16B16A16_FLOAT,
254                TEX_FILTER_DEFAULT,
255                TEX_THRESHOLD_DEFAULT,
256            )?;
257            let scratch =
258                cubemap_utils::decompose_layout(scratch_image.image(0, 0, 0).unwrap(), layout)?;
259            let image = cubemap_utils::compose_layout(&scratch, CubemapLayout::VerticalStrip)?;
260            let compressed = image.compress(
261                DXGI_FORMAT_BC6H_UF16,
262                TEX_COMPRESS_DEFAULT,
263                TEX_THRESHOLD_DEFAULT,
264            )?;
265            let image = compressed.image(0, 0, 0).unwrap();
266            let buffer = convert::image_pixels(image).unwrap_or_default();
267
268            Ok(Self { pos, buffer })
269        } else {
270            Err(BoxReflectionError::Other(
271                "Couldn't parse image format a boxreflection should use 128x128 faces:\n\
272                    Vertical strip:   (1x6) = 128x768\n\
273                    Horizontal strip: (6x1) = 768x128\n\
274                    Horizontal cross: (4x3) = 512x384\n\
275                    Vertical cross:   (3x4) = 384x512\n\
276                refer to https://github.com/Microsoft/DirectXTex/wiki/Texassemble for more info"
277                    .into(),
278            ))
279        }
280    }
281
282    pub fn create_dds(&self, layout: Option<CubemapLayout>) -> Result<Vec<u8>, BoxReflectionError> {
283        self.create_dds_with_rotation(layout, [None, None, None])
284    }
285
286    pub fn create_dds_with_rotation(
287        &self,
288        layout: Option<CubemapLayout>,
289        rotation: [Option<Orientation>; 3],
290    ) -> Result<Vec<u8>, BoxReflectionError> {
291        let cubemap = self.create_cubemap_image(true)?;
292        let scratch = match layout {
293            None => cubemap,
294            Some(layout) => {
295                cubemap_utils::compose_layout_with_rotation(&cubemap, layout, rotation)?
296            }
297        };
298
299        let blob = scratch
300            .save_dds(DDS_FLAGS::DDS_FLAGS_NONE)
301            .map_err(BoxReflectionError::DirectXTexError)?;
302
303        let bytes = blob.buffer();
304        Ok(Vec::from(bytes))
305    }
306
307    #[cfg(feature = "image")]
308    pub fn create_dynamic_image(
309        &self,
310        layout: CubemapLayout,
311    ) -> Result<DynamicImage, BoxReflectionError> {
312        self.create_dynamic_image_with_rotation(layout, [None, None, None])
313    }
314
315    #[cfg(feature = "image")]
316    pub fn create_dynamic_image_with_rotation(
317        &self,
318        layout: CubemapLayout,
319        rotation: [Option<Orientation>; 3],
320    ) -> Result<DynamicImage, BoxReflectionError> {
321        use image::Rgba32FImage;
322
323        let cubemap = self.create_cubemap_image(true)?;
324        let scratch = cubemap_utils::compose_layout_with_rotation(&cubemap, layout, rotation)?;
325
326        let metadata = scratch.metadata();
327        let width = metadata.width;
328        let height = metadata.height;
329
330        let bytes = scratch.pixels();
331
332        if bytes.len() != (width * height * 4 * 2) {
333            return Err(BoxReflectionError::Other(
334                "Failed to parse texture to image format".to_string(),
335            ));
336        }
337
338        let data: Vec<f32> = bytes
339            .chunks_exact(2)
340            .map(|chunk| {
341                let bits = u16::from_le_bytes([chunk[0], chunk[1]]);
342                half::f16::from_bits(bits).to_f32()
343            })
344            .collect();
345
346        let img = Rgba32FImage::from_raw(width as u32, height as u32, data)
347            .ok_or_else(|| BoxReflectionError::Other("Invalid image texture".to_string()))?;
348
349        Ok(DynamicImage::ImageRgba32F(img))
350    }
351
352    fn create_cubemap_image(&self, decompressed: bool) -> Result<ScratchImage, BoxReflectionError> {
353        let pitch = DXGI_FORMAT_BC6H_UF16
354            .compute_pitch(
355                Self::tile_width(),
356                Self::tile_height(),
357                CP_FLAGS::CP_FLAGS_NONE,
358            )
359            .map_err(BoxReflectionError::DirectXTexError)?;
360
361        let face_size = pitch.slice;
362        let base_ptr = self.buffer.as_ptr();
363
364        let images: Vec<(Vec<u8>, Image)> = (0..6)
365            .map(|face| {
366                let ptr = unsafe { base_ptr.add(face * face_size) };
367                let mut out = unsafe { slice::from_raw_parts(ptr, pitch.slice) }.to_vec();
368                let img = Image {
369                    width: Self::tile_width(),
370                    height: Self::tile_height(),
371                    format: DXGI_FORMAT_BC6H_UF16,
372                    row_pitch: pitch.row,
373                    slice_pitch: pitch.slice,
374                    pixels: out.as_mut_ptr(),
375                };
376                (out, img)
377            })
378            .collect();
379
380        let (buffers, faces_array): (Vec<Vec<u8>>, Vec<Image>) = images.into_iter().unzip();
381        let _buffers = buffers; // keeps allocations alive until end of scope  TODO: Remove this hack
382        let mut scratch_image = ScratchImage::default();
383        scratch_image.initialize_cube_from_images(faces_array.as_slice(), CP_FLAGS_NONE)?;
384
385        if decompressed {
386            scratch_image = scratch_image.decompress(DXGI_FORMAT_R16G16B16A16_FLOAT)?;
387        }
388        Ok(scratch_image)
389    }
390}
391
392impl BoxReflectionCache {
393    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, BoxReflectionError> {
394        let data = fs::read(path).map_err(BoxReflectionError::IoError)?;
395        Self::new_inner(&data)
396    }
397
398    pub fn from_memory(data: &[u8]) -> Result<Self, BoxReflectionError> {
399        Self::new_inner(data)
400    }
401
402    fn new_inner(data: &[u8]) -> Result<Self, BoxReflectionError> {
403        let mut stream = Cursor::new(data);
404        BoxReflectionCache::read_le(&mut stream).map_err(BoxReflectionError::ParsingError)
405    }
406
407    pub fn pack_to_vec(&self) -> Result<Vec<u8>, BoxReflectionError> {
408        let mut writer = Cursor::new(Vec::new());
409        self.pack_internal(&mut writer)?;
410        Ok(writer.into_inner())
411    }
412
413    pub fn pack_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), BoxReflectionError> {
414        let file = fs::File::create(path).map_err(BoxReflectionError::IoError)?;
415        let mut writer = BufWriter::new(file);
416        self.pack_internal(&mut writer)?;
417        Ok(())
418    }
419
420    fn pack_internal<W: Write + Seek>(&self, writer: &mut W) -> Result<(), BoxReflectionError> {
421        writer.write_le(self).map_err(|e| {
422            BoxReflectionError::PackingError(format!("Unable to pack boxreflections: {e}"))
423        })?;
424        Ok(())
425    }
426
427    pub fn push(&mut self, br: BoxReflection) {
428        self.entries.push(br)
429    }
430
431    pub fn insert(&mut self, index: usize, br: BoxReflection) {
432        self.entries.insert(index, br)
433    }
434
435    pub fn remove(&mut self, index: usize) -> BoxReflection {
436        self.entries.remove(index)
437    }
438    pub fn try_remove(&mut self, index: usize) -> Option<BoxReflection> {
439        if index < self.entries.len() {
440            Some(self.entries.remove(index))
441        } else {
442            None
443        }
444    }
445
446    pub fn clear(&mut self) {
447        self.entries.clear()
448    }
449
450    pub fn iter(&self) -> impl Iterator<Item = &BoxReflection> {
451        self.entries.iter()
452    }
453
454    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut BoxReflection> {
455        self.entries.iter_mut()
456    }
457}
458
459impl Index<usize> for BoxReflectionCache {
460    type Output = BoxReflection;
461    fn index(&self, index: usize) -> &Self::Output {
462        &self.entries[index]
463    }
464}
465
466impl IndexMut<usize> for BoxReflectionCache {
467    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
468        &mut self.entries[index]
469    }
470}
471
472impl<'a> IntoIterator for &'a BoxReflectionCache {
473    type Item = &'a BoxReflection;
474    type IntoIter = slice::Iter<'a, BoxReflection>;
475    fn into_iter(self) -> Self::IntoIter {
476        self.entries.iter()
477    }
478}
479
480impl<'a> IntoIterator for &'a mut BoxReflectionCache {
481    type Item = &'a mut BoxReflection;
482    type IntoIter = slice::IterMut<'a, BoxReflection>;
483    fn into_iter(self) -> Self::IntoIter {
484        self.entries.iter_mut()
485    }
486}
487
488impl<'a> FromIterator<&'a BoxReflection> for BoxReflectionCache
489where
490    BoxReflection: Clone,
491{
492    fn from_iter<T: IntoIterator<Item = &'a BoxReflection>>(iter: T) -> Self {
493        let entries = iter
494            .into_iter()
495            .map(|b| (*b).clone()) // clone the owned BoxReflection, not the reference
496            .collect::<Vec<BoxReflection>>();
497        Self { entries }
498    }
499}
500
501impl FromIterator<BoxReflection> for BoxReflectionCache {
502    fn from_iter<T: IntoIterator<Item = BoxReflection>>(iter: T) -> Self {
503        Self {
504            entries: iter.into_iter().collect(),
505        }
506    }
507}
508
509mod cubemap_utils {
510    use super::{BoxReflection, BoxReflectionError, CubemapLayout, Image};
511    use crate::box_reflection::cubemap_utils::Orientation::{Rotate180, Rotate270, Rotate90};
512    use bitfield_struct::bitfield;
513    use directxtex::{
514        Rect, ScratchImage, CP_FLAGS_NONE, DXGI_FORMAT_R16G16B16A16_FLOAT, TEX_FILTER_DEFAULT,
515        TEX_FILTER_FLAGS,
516    };
517
518    #[derive(Copy, Clone, Debug)]
519    pub enum Orientation {
520        Rotate90,
521        Rotate180,
522        Rotate270,
523    }
524
525    #[bitfield(u8)]
526    struct Flip {
527        horizontal: bool,
528        vertical: bool,
529        #[bits(6)]
530        _rem: u8,
531    }
532
533    pub fn rotate_image(image: &Image, rotate: Option<Orientation>) {
534        use std::{ptr, slice};
535
536        let pixel_stride = image.format.bits_per_pixel() / 8;
537        let w = image.width;
538        let h = image.height;
539        let src_row_pitch = image.row_pitch;
540
541        let dst_row_pitch = w * pixel_stride;
542        let dst_len = dst_row_pitch * h;
543
544        let src_ptr = image.pixels;
545        let src_slice = unsafe { slice::from_raw_parts(src_ptr as *const u8, src_row_pitch * h) };
546
547        let mut dst = vec![0u8; dst_len];
548        let dst_ptr = dst.as_mut_ptr();
549
550        let rot = rotate.map(|r| r.to_deg()).unwrap_or(0);
551
552        let map = |x: usize, y: usize| -> (usize, usize) {
553            match rot {
554                0 => (x, y),
555                90 => (h - 1 - y, x),
556                180 => (w - 1 - x, h - 1 - y),
557                270 => (y, w - 1 - x),
558                _ => (x, y),
559            }
560        };
561
562        for y in 0..h {
563            let src_row_off = y * src_row_pitch;
564            for x in 0..w {
565                let src_off = src_row_off + x * pixel_stride;
566                let (nx, ny) = map(x, y);
567                let dst_off = ny * dst_row_pitch + nx * pixel_stride;
568                unsafe {
569                    ptr::copy_nonoverlapping(
570                        src_slice.as_ptr().add(src_off),
571                        dst_ptr.add(dst_off),
572                        pixel_stride,
573                    );
574                }
575            }
576        }
577
578        unsafe {
579            ptr::copy_nonoverlapping(dst_ptr, src_ptr, dst_len);
580        }
581    }
582
583    pub(crate) fn compose_layout(
584        images: &ScratchImage,
585        layout: CubemapLayout,
586    ) -> Result<ScratchImage, BoxReflectionError> {
587        compose_layout_with_rotation(images, layout, [None, None, None])
588    }
589
590    pub(crate) fn compose_layout_with_rotation(
591        images: &ScratchImage,
592        layout: CubemapLayout,
593        rotation: [Option<Orientation>; 3],
594    ) -> Result<ScratchImage, BoxReflectionError> {
595        if images.metadata().format != DXGI_FORMAT_R16G16B16A16_FLOAT {
596            return Err(BoxReflectionError::Other(format!(
597                "Invalid format ({:?}), the Image format must be 4-channel half-float",
598                images.metadata().format
599            )));
600        }
601
602        let face_w = BoxReflection::tile_width();
603        let face_h = BoxReflection::tile_height();
604        let bytes_per_pixel: usize = images.metadata().format.bits_per_pixel() / 8;
605
606        let face_tile_positions = layout.tile_positions();
607        let num_width_tiles = face_tile_positions
608            .iter()
609            .map(|(w, _)| *w)
610            .max()
611            .unwrap_or_default()
612            + 1;
613        let num_height_tiles = face_tile_positions
614            .iter()
615            .map(|(_, h)| *h)
616            .max()
617            .unwrap_or_default()
618            + 1;
619        let final_w = num_width_tiles * face_w;
620        let final_h = num_height_tiles * face_h;
621        let final_row_pitch = final_w * bytes_per_pixel;
622        let final_slice_pitch = final_row_pitch * final_h;
623
624        let mut out: Vec<u8> = vec![0u8; final_slice_pitch];
625        let mut image = Image {
626            width: final_w,
627            height: final_h,
628            format: DXGI_FORMAT_R16G16B16A16_FLOAT,
629            row_pitch: final_row_pitch,
630            slice_pitch: final_slice_pitch,
631            pixels: out.as_mut_ptr(),
632        };
633
634        for face_index in 0..6 {
635            let face_image = images
636                .image(0, face_index, 0)
637                .ok_or(BoxReflectionError::Other(
638                    "Failed to find cubemap image".into(),
639                ))?;
640
641            let mut rotation_steps = vec![];
642            rotation_steps.push((Axis::Z, Rotate90)); //Adding this default rotation step to adjust for the standard rotation used by IOI.
643            if let Some(x_rot) = rotation[0] {
644                rotation_steps.push((Axis::X, x_rot));
645            }
646            if let Some(y_rot) = rotation[1] {
647                rotation_steps.push((Axis::Y, y_rot));
648            }
649            if let Some(z_rot) = rotation[2] {
650                rotation_steps.push((Axis::Z, z_rot));
651            }
652            let face_mapping = map_face_and_image_rotations(face_index, rotation_steps);
653
654            if let (Some(new_face_idx), rotation) = face_mapping {
655                rotate_image(face_image, rotation);
656                let (tile_x, tile_y) = face_tile_positions[new_face_idx];
657
658                if matches!(layout, CubemapLayout::VerticalCross) && new_face_idx == 5 {
659                    rotate_image(face_image, Some(Rotate180));
660                }
661
662                let rect = Rect {
663                    x: 0,
664                    y: 0,
665                    w: face_w,
666                    h: face_h,
667                };
668                image.copy_rectangle(
669                    face_image,
670                    &rect,
671                    TEX_FILTER_FLAGS::TEX_FILTER_DEFAULT,
672                    tile_x * face_w,
673                    tile_y * face_h,
674                )?;
675            }
676        }
677        let mut scratch_image = ScratchImage::default();
678        scratch_image.initialize_from_image(&image, false, CP_FLAGS_NONE)?;
679        Ok(scratch_image)
680    }
681
682    pub(crate) fn decompose_layout(
683        image: &Image,
684        layout: CubemapLayout,
685    ) -> Result<ScratchImage, BoxReflectionError> {
686        let face_w = BoxReflection::tile_width();
687        let face_h = BoxReflection::tile_height();
688
689        if image.format != DXGI_FORMAT_R16G16B16A16_FLOAT {
690            return Err(BoxReflectionError::Other(format!(
691                "Invalid format ({:?}), the Image format must be 4-channel half-float",
692                image.format
693            )));
694        }
695
696        let bytes_per_pixel: usize = 8;
697
698        let face_tile_positions = layout.tile_positions();
699
700        let num_w_tiles = face_tile_positions
701            .iter()
702            .map(|(w, _)| *w)
703            .max()
704            .unwrap_or_default()
705            + 1;
706        let num_h_tiles = face_tile_positions
707            .iter()
708            .map(|(_, h)| *h)
709            .max()
710            .unwrap_or_default()
711            + 1;
712        let expected_w = num_w_tiles * face_w;
713        let expected_h = num_h_tiles * face_h;
714
715        if image.width < expected_w || image.height < expected_h {
716            return Err(BoxReflectionError::Other(format!(
717                "Input image too small for requested layout: got {}x{}, need {}x{}",
718                image.width, image.height, expected_w, expected_h
719            )));
720        }
721
722        let mut faces_vec: Vec<(Vec<u8>, Image)> = Vec::with_capacity(6);
723
724        for (face_index, (tile_x, tile_y)) in face_tile_positions.iter().enumerate() {
725            let final_row_pitch = face_w * bytes_per_pixel;
726            let final_slice_pitch = final_row_pitch * face_h;
727
728            let mut out: Vec<u8> = vec![0u8; final_slice_pitch];
729            let mut face_image = Image {
730                width: face_w,
731                height: face_h,
732                format: DXGI_FORMAT_R16G16B16A16_FLOAT,
733                row_pitch: final_row_pitch,
734                slice_pitch: final_slice_pitch,
735                pixels: out.as_mut_ptr(),
736            };
737
738            let rect = Rect {
739                x: tile_x * face_w,
740                y: tile_y * face_h,
741                w: face_w,
742                h: face_h,
743            };
744            face_image.copy_rectangle(image, &rect, TEX_FILTER_DEFAULT, 0, 0)?;
745
746            if matches!(layout, CubemapLayout::VerticalCross) && face_index == 5 {
747                rotate_image(&face_image, Some(Rotate180));
748            }
749            faces_vec.push((out, face_image));
750        }
751        let (buffers, faces_array): (Vec<Vec<u8>>, Vec<Image>) = faces_vec.into_iter().unzip();
752
753        let mut faces_opt: Vec<Option<Image>> = (0..faces_array.len()).map(|_| None).collect();
754        for (face_index, image) in faces_array.into_iter().enumerate() {
755            if let (Some(new_face_idx), rotation) =
756                map_face_and_image_rotation(Axis::Z, Rotate180, face_index)
757            {
758                rotate_image(&image, rotation);
759                faces_opt[new_face_idx] = Some(image);
760            }
761        }
762
763        let faces: Vec<Image> = faces_opt
764            .into_iter()
765            .map(|opt| opt.expect("expected every face to be assigned"))
766            .collect();
767        let _buffers = buffers; // keeps allocations alive until end of scope TODO: Remove this hack
768
769        let mut scratch_image = ScratchImage::default();
770        scratch_image.initialize_cube_from_images(faces.as_slice(), CP_FLAGS_NONE)?;
771        Ok(scratch_image)
772    }
773
774    #[derive(Copy, Clone, Debug)]
775    enum Axis {
776        X,
777        Y,
778        Z,
779    }
780    type Vec3 = (i8, i8, i8);
781
782    impl Orientation {
783        fn to_deg(self) -> u16 {
784            match self {
785                Rotate90 => 90,
786                Rotate180 => 180,
787                Rotate270 => 270,
788            }
789        }
790
791        pub fn add(current: Option<Orientation>, step: Option<Orientation>) -> Option<Orientation> {
792            match (current, step) {
793                (None, None) => None,
794                (Some(r), None) | (None, Some(r)) => Some(r),
795                (Some(a), Some(b)) => {
796                    let sum = (a.to_deg() + b.to_deg()) % 360;
797                    match sum {
798                        0 => None,
799                        90 => Some(Rotate90),
800                        180 => Some(Rotate180),
801                        270 => Some(Rotate270),
802                        _ => unreachable!(),
803                    }
804                }
805            }
806        }
807    }
808
809    fn rotate_vec(axis: Axis, rot: Orientation, (x, y, z): Vec3) -> Vec3 {
810        match axis {
811            Axis::X => match rot {
812                //was y
813                Rotate270 => (z, y, -x),
814                Rotate180 => (-x, y, -z),
815                Rotate90 => (-z, y, x),
816            },
817            Axis::Y => match rot {
818                //was z
819                Rotate270 => (-y, x, z),
820                Rotate180 => (-x, -y, z),
821                Rotate90 => (y, -x, z),
822            },
823            Axis::Z => match rot {
824                //was x
825                Rotate270 => (x, -z, y),
826                Rotate180 => (x, -y, -z),
827                Rotate90 => (x, z, -y),
828            },
829        }
830    }
831
832    fn face_axes(face: usize) -> Option<(Vec3, Vec3, Vec3)> {
833        match face {
834            0 => Some(((1, 0, 0), (0, 0, -1), (0, -1, 0))),  // +X
835            1 => Some(((-1, 0, 0), (0, 0, 1), (0, -1, 0))),  // -X
836            2 => Some(((0, 1, 0), (1, 0, 0), (0, 0, 1))),    // +Y
837            3 => Some(((0, -1, 0), (1, 0, 0), (0, 0, -1))),  // -Y
838            4 => Some(((0, 0, 1), (1, 0, 0), (0, -1, 0))),   // +Z
839            5 => Some(((0, 0, -1), (-1, 0, 0), (0, -1, 0))), // -Z
840            _ => None,
841        }
842    }
843    fn neg(v: Vec3) -> Vec3 {
844        (-v.0, -v.1, -v.2)
845    }
846
847    fn map_face_and_image_rotation(
848        axis: Axis,
849        rot: Orientation,
850        face_index: usize,
851    ) -> (Option<usize>, Option<Orientation>) {
852        let (n_src, r_src, _) = face_axes(face_index).unwrap();
853        let n_rot = rotate_vec(axis, rot, n_src);
854        let r_rot = rotate_vec(axis, rot, r_src);
855
856        let dst = match n_rot {
857            (1, 0, 0) => Some(0),
858            (-1, 0, 0) => Some(1),
859            (0, 1, 0) => Some(2),
860            (0, -1, 0) => Some(3),
861            (0, 0, 1) => Some(4),
862            (0, 0, -1) => Some(5),
863            _ => None,
864        };
865
866        let (_, r_dst, u_dst) = face_axes(dst.unwrap()).unwrap();
867
868        let rot = match r_rot {
869            v if v == r_dst => None,
870            v if v == neg(r_dst) => Some(Rotate180),
871            v if v == u_dst => Some(Rotate90),
872            v if v == neg(u_dst) => Some(Rotate270),
873            _ => unreachable!(),
874        };
875
876        (dst, rot)
877    }
878
879    fn map_face_and_image_rotations(
880        face_index: usize,
881        steps: Vec<(Axis, Orientation)>,
882    ) -> (Option<usize>, Option<Orientation>) {
883        let mut new_index = face_index;
884        let mut new_rot: Option<Orientation> = None;
885        for (axis, rot) in steps {
886            let (new_face, step_face_rot) = map_face_and_image_rotation(axis, rot, new_index);
887            let new_face = match new_face {
888                Some(f) => f,
889                None => return (None, None),
890            };
891            new_index = new_face;
892            new_rot = Orientation::add(new_rot, step_face_rot);
893        }
894        (Some(new_index), new_rot)
895    }
896}