use super::fourcc::FourCCTree;
use crate::error::Result;
use crate::virtual_texture::types::{GtsBCParameterBlock, GtsFlatTileInfo, GtsHeader};
use byteorder::{LittleEndian, WriteBytesExt};
use std::io::{Seek, SeekFrom, Write};
#[derive(Debug, Clone)]
pub struct LayerInfo {
pub data_type: u32,
}
#[derive(Debug, Clone)]
pub struct LevelInfo {
pub width: u32,
pub height: u32,
pub width_pixels: u32,
pub height_pixels: u32,
}
#[derive(Debug, Clone)]
pub struct PageFileInfo {
pub filename: String,
pub num_pages: u32,
pub guid: [u8; 16],
}
pub struct GtsWriter {
guid: [u8; 16],
tile_width: i32,
tile_height: i32,
tile_border: i32,
page_size: u32,
layers: Vec<LayerInfo>,
levels: Vec<LevelInfo>,
parameter_blocks: Vec<GtsBCParameterBlock>,
page_files: Vec<PageFileInfo>,
packed_tile_ids: Vec<u32>,
flat_tile_infos: Vec<GtsFlatTileInfo>,
per_level_indices: Vec<Vec<u32>>, fourcc_tree: FourCCTree,
}
impl GtsWriter {
#[must_use]
pub fn new(
guid: [u8; 16],
tile_width: i32,
tile_height: i32,
tile_border: i32,
page_size: u32,
) -> Self {
Self {
guid,
tile_width,
tile_height,
tile_border,
page_size,
layers: Vec::new(),
levels: Vec::new(),
parameter_blocks: Vec::new(),
page_files: Vec::new(),
packed_tile_ids: Vec::new(),
flat_tile_infos: Vec::new(),
per_level_indices: Vec::new(),
fourcc_tree: FourCCTree::new(),
}
}
pub fn add_layer(&mut self, layer: LayerInfo) {
self.layers.push(layer);
}
pub fn add_level(&mut self, level: LevelInfo) {
self.levels.push(level);
self.per_level_indices.push(Vec::new());
}
pub fn add_parameter_block(&mut self, block: GtsBCParameterBlock) {
self.parameter_blocks.push(block);
}
pub fn add_page_file(&mut self, info: PageFileInfo) {
self.page_files.push(info);
}
pub fn add_packed_tile_id(&mut self, packed_id: u32) -> u32 {
let index = self.packed_tile_ids.len() as u32;
self.packed_tile_ids.push(packed_id);
index
}
pub fn add_flat_tile_info(&mut self, info: GtsFlatTileInfo, level: usize) {
let index = self.flat_tile_infos.len() as u32;
self.flat_tile_infos.push(info);
if level < self.per_level_indices.len() {
self.per_level_indices[level].push(index);
}
}
pub fn set_fourcc_tree(&mut self, tree: FourCCTree) {
self.fourcc_tree = tree;
}
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> Result<()> {
let header_pos = writer.stream_position()?;
let placeholder_header = [0u8; 156];
writer.write_all(&placeholder_header)?;
let layers_offset = writer.stream_position()?;
for layer in &self.layers {
writer.write_u32::<LittleEndian>(layer.data_type)?;
writer.write_i32::<LittleEndian>(-1)?; }
let mut level_offsets = Vec::new();
for indices in &self.per_level_indices {
level_offsets.push(writer.stream_position()?);
for &idx in indices {
writer.write_u32::<LittleEndian>(idx)?;
}
}
let levels_offset = writer.stream_position()?;
for (i, level) in self.levels.iter().enumerate() {
writer.write_u32::<LittleEndian>(level.width)?;
writer.write_u32::<LittleEndian>(level.height)?;
writer.write_u64::<LittleEndian>(level_offsets.get(i).copied().unwrap_or(0))?;
writer.write_u32::<LittleEndian>(level.width_pixels)?;
writer.write_u32::<LittleEndian>(level.height_pixels)?;
}
let param_headers_offset = writer.stream_position()?;
let mut param_data_positions = Vec::new();
for (i, _block) in self.parameter_blocks.iter().enumerate() {
writer.write_u32::<LittleEndian>(i as u32)?; writer.write_u32::<LittleEndian>(9)?; writer.write_u32::<LittleEndian>(56)?; param_data_positions.push(writer.stream_position()?);
writer.write_u64::<LittleEndian>(0)?; }
let mut param_block_offsets = Vec::new();
for block in &self.parameter_blocks {
param_block_offsets.push(writer.stream_position()?);
self.write_bc_parameter_block(writer, block)?;
}
let page_files_offset = writer.stream_position()?;
for page_file in &self.page_files {
self.write_page_file_info(writer, page_file)?;
}
let fourcc_offset = writer.stream_position()?;
let fourcc_size = self.fourcc_tree.write(writer)?;
let thumbnails_offset = writer.stream_position()?;
writer.write_u32::<LittleEndian>(0)?;
let packed_tiles_offset = writer.stream_position()?;
for &id in &self.packed_tile_ids {
writer.write_u32::<LittleEndian>(id)?;
}
let flat_tiles_offset = writer.stream_position()?;
for info in &self.flat_tile_infos {
self.write_flat_tile_info(writer, info)?;
}
for (i, &pos) in param_data_positions.iter().enumerate() {
writer.seek(SeekFrom::Start(pos))?;
writer.write_u64::<LittleEndian>(param_block_offsets[i])?;
}
writer.seek(SeekFrom::Start(header_pos))?;
self.write_header(
writer,
layers_offset,
levels_offset,
param_headers_offset,
page_files_offset,
fourcc_offset,
fourcc_size,
thumbnails_offset,
packed_tiles_offset,
flat_tiles_offset,
)?;
Ok(())
}
fn write_header<W: Write>(
&self,
writer: &mut W,
layers_offset: u64,
levels_offset: u64,
param_headers_offset: u64,
page_files_offset: u64,
fourcc_offset: u64,
fourcc_size: u32,
thumbnails_offset: u64,
packed_tiles_offset: u64,
flat_tiles_offset: u64,
) -> Result<()> {
writer.write_u32::<LittleEndian>(GtsHeader::MAGIC)?;
writer.write_u32::<LittleEndian>(5)?; writer.write_u32::<LittleEndian>(0)?; writer.write_all(&self.guid)?;
writer.write_u32::<LittleEndian>(self.layers.len() as u32)?;
writer.write_u64::<LittleEndian>(layers_offset)?;
writer.write_u32::<LittleEndian>(self.levels.len() as u32)?;
writer.write_u64::<LittleEndian>(levels_offset)?;
writer.write_i32::<LittleEndian>(self.tile_width)?;
writer.write_i32::<LittleEndian>(self.tile_height)?;
writer.write_i32::<LittleEndian>(self.tile_border)?;
writer.write_u32::<LittleEndian>(0)?;
writer.write_u32::<LittleEndian>(self.flat_tile_infos.len() as u32)?;
writer.write_u64::<LittleEndian>(flat_tiles_offset)?;
writer.write_u32::<LittleEndian>(0)?; writer.write_u32::<LittleEndian>(0)?;
writer.write_u32::<LittleEndian>(self.packed_tile_ids.len() as u32)?;
writer.write_u64::<LittleEndian>(packed_tiles_offset)?;
for _ in 0..7 {
writer.write_u32::<LittleEndian>(0)?;
}
writer.write_u32::<LittleEndian>(self.page_size)?;
writer.write_u32::<LittleEndian>(self.page_files.len() as u32)?;
writer.write_u64::<LittleEndian>(page_files_offset)?;
writer.write_u32::<LittleEndian>(fourcc_size)?;
writer.write_u64::<LittleEndian>(fourcc_offset)?;
writer.write_u32::<LittleEndian>(self.parameter_blocks.len() as u32)?;
writer.write_u64::<LittleEndian>(param_headers_offset)?;
writer.write_u64::<LittleEndian>(thumbnails_offset)?;
for _ in 0..4 {
writer.write_u32::<LittleEndian>(0)?;
}
Ok(())
}
fn write_bc_parameter_block<W: Write>(
&self,
writer: &mut W,
block: &GtsBCParameterBlock,
) -> Result<()> {
writer.write_u16::<LittleEndian>(block.version)?;
writer.write_all(&block.compression1)?;
writer.write_all(&block.compression2)?;
writer.write_u32::<LittleEndian>(block.b)?;
writer.write_u8(block.c1)?;
writer.write_u8(block.c2)?;
writer.write_u8(block.bc_field3)?;
writer.write_u8(block.data_type)?;
writer.write_u16::<LittleEndian>(block.d)?;
writer.write_u32::<LittleEndian>(block.fourcc)?;
writer.write_u8(block.e1)?;
writer.write_u8(block.save_mip)?;
writer.write_u8(block.e3)?;
writer.write_u8(block.e4)?;
writer.write_u32::<LittleEndian>(block.f)?;
Ok(())
}
fn write_page_file_info<W: Write>(&self, writer: &mut W, info: &PageFileInfo) -> Result<()> {
let utf16: Vec<u16> = info.filename.encode_utf16().collect();
let mut filename_bytes = [0u8; 512];
for (i, &c) in utf16.iter().enumerate().take(255) {
let bytes = c.to_le_bytes();
filename_bytes[i * 2] = bytes[0];
filename_bytes[i * 2 + 1] = bytes[1];
}
writer.write_all(&filename_bytes)?;
writer.write_u32::<LittleEndian>(info.num_pages)?;
writer.write_all(&info.guid)?;
writer.write_u32::<LittleEndian>(2)?;
Ok(())
}
fn write_flat_tile_info<W: Write>(&self, writer: &mut W, info: &GtsFlatTileInfo) -> Result<()> {
writer.write_u16::<LittleEndian>(info.page_file_index)?;
writer.write_u16::<LittleEndian>(info.page_index)?;
writer.write_u16::<LittleEndian>(info.chunk_index)?;
writer.write_u16::<LittleEndian>(info.d)?;
writer.write_u32::<LittleEndian>(info.packed_tile_id_index)?;
Ok(())
}
}
#[must_use]
pub fn create_bc_parameter_block(
compression1: &[u8; 16],
compression2: &[u8; 16],
data_type: u8,
fourcc: u32,
embed_mip: bool,
) -> GtsBCParameterBlock {
GtsBCParameterBlock {
version: 0x238e,
compression1: *compression1,
compression2: *compression2,
b: 0,
c1: 0,
c2: 0,
bc_field3: 0,
data_type,
d: 0,
fourcc,
e1: 0,
save_mip: u8::from(embed_mip),
e3: 0,
e4: 0,
f: 0,
}
}