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 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 CubemapLayout::HorizontalCross => [(2, 1), (0, 1), (1, 0), (1, 2), (1, 1), (3, 1)],
148 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 } 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 } 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; 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()) .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)); 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; 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 Rotate270 => (z, y, -x),
814 Rotate180 => (-x, y, -z),
815 Rotate90 => (-z, y, x),
816 },
817 Axis::Y => match rot {
818 Rotate270 => (-y, x, z),
820 Rotate180 => (-x, -y, z),
821 Rotate90 => (y, -x, z),
822 },
823 Axis::Z => match rot {
824 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))), 1 => Some(((-1, 0, 0), (0, 0, 1), (0, -1, 0))), 2 => Some(((0, 1, 0), (1, 0, 0), (0, 0, 1))), 3 => Some(((0, -1, 0), (1, 0, 0), (0, 0, -1))), 4 => Some(((0, 0, 1), (1, 0, 0), (0, -1, 0))), 5 => Some(((0, 0, -1), (-1, 0, 0), (0, -1, 0))), _ => 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}