use super::config::TileSetConfiguration;
use super::geometry::TileCoord;
use crate::error::{Error, Result};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct ProcessedTile {
pub coord: TileCoord,
pub packed_id: u32,
pub data: Vec<u8>,
pub mip_data: Option<Vec<u8>>,
}
impl ProcessedTile {
#[must_use]
pub fn full_data(&self) -> Vec<u8> {
let mut result = self.data.clone();
if let Some(ref mip) = self.mip_data {
result.extend_from_slice(mip);
}
result
}
}
pub struct DdsTexture {
pub width: u32,
pub height: u32,
pub block_size: usize,
pub data: Vec<u8>,
pub mip_count: u32,
pub mip_offsets: Vec<usize>,
}
impl DdsTexture {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let data = std::fs::read(path.as_ref())?;
Self::from_bytes(&data)
}
pub fn from_bytes(data: &[u8]) -> Result<Self> {
use ddsfile::Dds;
let dds = Dds::read(std::io::Cursor::new(data))
.map_err(|e| Error::DdsError(format!("Failed to parse DDS: {e}")))?;
let width = dds.get_width();
let height = dds.get_height();
let mip_count = dds.get_num_mipmap_levels();
let block_size = match dds.get_dxgi_format() {
Some(ddsfile::DxgiFormat::BC1_UNorm | ddsfile::DxgiFormat::BC1_UNorm_sRGB) => 8,
Some(ddsfile::DxgiFormat::BC3_UNorm | ddsfile::DxgiFormat::BC3_UNorm_sRGB) => 16,
Some(ddsfile::DxgiFormat::BC5_UNorm | ddsfile::DxgiFormat::BC5_SNorm) => 16,
Some(ddsfile::DxgiFormat::BC7_UNorm | ddsfile::DxgiFormat::BC7_UNorm_sRGB) => 16,
_ => {
match dds.get_d3d_format() {
Some(ddsfile::D3DFormat::DXT1) => 8,
Some(ddsfile::D3DFormat::DXT3 | ddsfile::D3DFormat::DXT5) => 16,
_ => return Err(Error::DdsError("Unsupported DDS format".to_string())),
}
}
};
let mut mip_offsets = Vec::with_capacity(mip_count as usize);
let mut offset = 0usize;
let mut mip_width = width;
let mut mip_height = height;
for _ in 0..mip_count {
mip_offsets.push(offset);
let blocks_wide = mip_width.div_ceil(4);
let blocks_high = mip_height.div_ceil(4);
let mip_size = (blocks_wide * blocks_high) as usize * block_size;
offset += mip_size;
mip_width = (mip_width / 2).max(1);
mip_height = (mip_height / 2).max(1);
}
Ok(Self {
width,
height,
block_size,
data: dds
.get_data(0)
.map_err(|e| Error::DdsError(format!("{e}")))?
.to_vec(),
mip_count,
mip_offsets,
})
}
pub fn get_mip_data(&self, level: u32) -> Option<(&[u8], u32, u32)> {
if level >= self.mip_count {
return None;
}
let start = self.mip_offsets[level as usize];
let mip_width = (self.width >> level).max(1);
let mip_height = (self.height >> level).max(1);
let blocks_wide = mip_width.div_ceil(4);
let blocks_high = mip_height.div_ceil(4);
let mip_size = (blocks_wide * blocks_high) as usize * self.block_size;
let end = start + mip_size;
if end <= self.data.len() {
Some((&self.data[start..end], mip_width, mip_height))
} else {
None
}
}
}
fn extract_tile_with_borders(
src_data: &[u8],
src_width: u32,
src_height: u32,
content_x: u32,
content_y: u32,
content_width: u32,
content_height: u32,
border: u32,
block_size: usize,
) -> Vec<u8> {
let padded_width = content_width + 2 * border;
let padded_height = content_height + 2 * border;
let src_blocks_wide = src_width.div_ceil(4);
let src_blocks_high = src_height.div_ceil(4);
let tile_blocks_wide = padded_width.div_ceil(4);
let tile_blocks_high = padded_height.div_ceil(4);
let border_blocks = border / 4;
let mut tile = Vec::with_capacity((tile_blocks_wide * tile_blocks_high) as usize * block_size);
let content_block_x = content_x / 4;
let content_block_y = content_y / 4;
for tile_by in 0..tile_blocks_high {
for tile_bx in 0..tile_blocks_wide {
let rel_bx = tile_bx as i32 - border_blocks as i32;
let rel_by = tile_by as i32 - border_blocks as i32;
let src_bx = (content_block_x as i32 + rel_bx)
.max(0)
.min(src_blocks_wide as i32 - 1) as u32;
let src_by = (content_block_y as i32 + rel_by)
.max(0)
.min(src_blocks_high as i32 - 1) as u32;
let src_offset = ((src_by * src_blocks_wide + src_bx) as usize) * block_size;
if src_offset + block_size <= src_data.len() {
tile.extend_from_slice(&src_data[src_offset..src_offset + block_size]);
} else {
tile.resize(tile.len() + block_size, 0);
}
}
}
tile
}
pub fn extract_tiles_from_dds(
dds: &DdsTexture,
coords: &[TileCoord],
config: &TileSetConfiguration,
) -> Result<Vec<ProcessedTile>> {
let raw_tile_width = config.raw_tile_width();
let raw_tile_height = config.raw_tile_height();
let border = config.tile_border;
let mut tiles = Vec::with_capacity(coords.len());
for coord in coords {
let level = coord.level as u32;
let (mip_data, mip_width, mip_height) = dds.get_mip_data(level).ok_or_else(|| {
Error::VirtualTexture(format!("Mip level {level} not available in texture"))
})?;
let content_pixel_x = (coord.x as u32) * raw_tile_width;
let content_pixel_y = (coord.y as u32) * raw_tile_height;
let tile_data = extract_tile_with_borders(
mip_data,
mip_width,
mip_height,
content_pixel_x,
content_pixel_y,
raw_tile_width,
raw_tile_height,
border,
dds.block_size,
);
let mip_data = if config.embed_mip && level + 1 < dds.mip_count {
let next_mip = dds.get_mip_data(level + 1);
if let Some((next_data, next_width, next_height)) = next_mip {
let mip_content_x = content_pixel_x / 2;
let mip_content_y = content_pixel_y / 2;
let mip_content_width = raw_tile_width / 2;
let mip_content_height = raw_tile_height / 2;
let mip_border = border / 2;
let mip_tile = extract_tile_with_borders(
next_data,
next_width,
next_height,
mip_content_x,
mip_content_y,
mip_content_width,
mip_content_height,
mip_border,
dds.block_size,
);
Some(mip_tile)
} else {
None
}
} else {
None
};
tiles.push(ProcessedTile {
coord: *coord,
packed_id: coord.to_packed_id(),
data: tile_data,
mip_data,
});
}
Ok(tiles)
}