use std::fmt;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use cgmath::Point3;
use crate::block::Evoxel;
use crate::content::palette;
use crate::math::{GridAab, GridArray};
use crate::mesh::TextureCoordinate;
use crate::util::{ConciseDebug, CustomFormat};
pub type Texel = [u8; 4];
pub trait TextureAllocator {
type Tile: TextureTile<Point = Self::Point>;
type Point;
fn allocate(&self, bounds: GridAab) -> Option<Self::Tile>;
}
pub trait TextureTile: Clone {
type Point: Copy;
fn bounds(&self) -> GridAab;
fn grid_to_texcoord(&self, in_tile_grid: Point3<TextureCoordinate>) -> Self::Point;
fn write(&mut self, data: &[Texel]);
}
impl<T: TextureAllocator> TextureAllocator for &T {
type Tile = T::Tile;
type Point = T::Point;
fn allocate(&self, bounds: GridAab) -> Option<Self::Tile> {
<T as TextureAllocator>::allocate(self, bounds)
}
}
impl<T: TextureAllocator> TextureAllocator for std::sync::Arc<T> {
type Tile = T::Tile;
type Point = T::Point;
fn allocate(&self, bounds: GridAab) -> Option<Self::Tile> {
<T as TextureAllocator>::allocate(self, bounds)
}
}
impl<T: TextureAllocator> TextureAllocator for std::rc::Rc<T> {
type Tile = T::Tile;
type Point = T::Point;
fn allocate(&self, bounds: GridAab) -> Option<Self::Tile> {
<T as TextureAllocator>::allocate(self, bounds)
}
}
pub(super) fn copy_voxels_to_texture<A: TextureAllocator>(
texture_allocator: &A,
voxels: &GridArray<Evoxel>,
) -> Option<A::Tile> {
texture_allocator
.allocate(voxels.bounds())
.map(|mut texture| {
copy_voxels_into_existing_texture(voxels, &mut texture);
texture
})
}
pub(super) fn copy_voxels_into_existing_texture<T: TextureTile>(
voxels: &GridArray<Evoxel>,
texture: &mut T,
) {
let bounds = voxels.bounds();
let mut texels: Vec<Texel> = Vec::with_capacity(bounds.volume());
for z in bounds.z_range() {
for y in bounds.y_range() {
for x in bounds.x_range() {
texels.push(
voxels
.get([x, y, z])
.unwrap_or(&Evoxel::from_color(palette::MISSING_VOXEL_FALLBACK))
.color
.to_srgb8(),
);
}
}
}
texture.write(&texels);
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[allow(clippy::exhaustive_structs)]
pub struct NoTextures;
impl TextureAllocator for NoTextures {
type Tile = NoTexture;
type Point = NoTexture;
fn allocate(&self, _: GridAab) -> Option<Self::Tile> {
None
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[allow(clippy::exhaustive_enums)]
pub enum NoTexture {}
impl TextureTile for NoTexture {
type Point = Self;
fn bounds(&self) -> GridAab {
match *self {}
}
fn grid_to_texcoord(&self, _in_tile: Point3<TextureCoordinate>) -> NoTexture {
match *self {}
}
fn write(&mut self, _data: &[Texel]) {
match *self {}
}
}
impl CustomFormat<ConciseDebug> for NoTexture {
fn fmt(&self, _: &mut fmt::Formatter<'_>, _: ConciseDebug) -> fmt::Result {
match *self {}
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct TestTextureAllocator {
capacity: usize,
count_allocated: AtomicUsize,
}
impl TestTextureAllocator {
pub const fn new() -> Self {
Self {
capacity: usize::MAX,
count_allocated: AtomicUsize::new(0),
}
}
pub fn set_capacity(&mut self, capacity: usize) {
self.capacity = capacity;
}
pub fn count_allocated(&self) -> usize {
self.count_allocated.load(SeqCst)
}
}
impl Default for TestTextureAllocator {
fn default() -> Self {
Self::new()
}
}
impl TextureAllocator for TestTextureAllocator {
type Tile = TestTextureTile;
type Point = TtPoint;
fn allocate(&self, bounds: GridAab) -> Option<Self::Tile> {
self.count_allocated
.fetch_update(SeqCst, SeqCst, |count| {
if count < self.capacity {
Some(count + 1)
} else {
None
}
})
.ok()
.map(|_| ())?;
Some(TestTextureTile { bounds })
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[doc(hidden)]
pub struct TestTextureTile {
bounds: GridAab,
}
impl TextureTile for TestTextureTile {
type Point = TtPoint;
fn bounds(&self) -> GridAab {
self.bounds
}
fn grid_to_texcoord(&self, in_tile: Point3<TextureCoordinate>) -> Self::Point {
in_tile
}
fn write(&mut self, data: &[Texel]) {
assert_eq!(
data.len(),
self.bounds.volume(),
"tile data did not match resolution"
);
}
}
#[doc(hidden)]
pub type TtPoint = Point3<TextureCoordinate>;
#[cfg(test)]
mod tests {
use super::*;
use crate::block::Resolution::*;
#[test]
fn test_texture_allocator() {
let bounds = GridAab::for_block(R8);
let mut allocator = TestTextureAllocator::new();
assert_eq!(allocator.count_allocated(), 0);
assert!(allocator.allocate(bounds).is_some());
assert!(allocator.allocate(bounds).is_some());
assert_eq!(allocator.count_allocated(), 2);
allocator.set_capacity(3);
assert!(allocator.allocate(bounds).is_some());
assert!(allocator.allocate(bounds).is_none());
}
}