doukutsu-rs/src/graphics/texture_set.rs

597 lines
16 KiB
Rust
Raw Normal View History

2020-08-18 16:46:07 +00:00
use std::collections::HashMap;
2020-08-29 06:59:46 +00:00
use std::io::{BufReader, Read, Seek, SeekFrom};
2020-08-18 16:46:07 +00:00
2020-08-29 06:59:46 +00:00
use image::RgbaImage;
2020-08-19 00:55:21 +00:00
use itertools::Itertools;
2020-08-19 11:21:40 +00:00
use log::info;
2020-08-18 16:46:07 +00:00
use crate::common;
2022-11-19 17:20:03 +00:00
use crate::common::{FILE_TYPES, Rect};
2020-08-19 00:55:21 +00:00
use crate::engine_constants::EngineConstants;
2021-01-28 22:33:43 +00:00
use crate::framework::backend::{BackendTexture, SpriteBatchCommand};
2021-01-27 18:20:47 +00:00
use crate::framework::context::Context;
use crate::framework::error::{GameError, GameResult};
use crate::framework::filesystem;
2021-01-28 22:33:43 +00:00
use crate::framework::graphics::{create_texture, FilterMode};
2020-08-18 16:46:07 +00:00
2021-01-28 22:33:43 +00:00
pub static mut I_MAG: f32 = 1.0;
pub static mut G_MAG: f32 = 1.0;
2021-10-14 04:54:11 +00:00
pub trait SpriteBatch {
fn width(&self) -> usize;
fn height(&self) -> usize;
fn dimensions(&self) -> (usize, usize);
fn real_dimensions(&self) -> (usize, usize);
fn scale(&self) -> (f32, f32);
fn has_glow_layer(&self) -> bool;
fn has_normal_layer(&self) -> bool;
2021-10-31 08:14:13 +00:00
fn glow(&mut self) -> Option<&mut dyn SpriteBatch> {
None
}
fn normal(&mut self) -> Option<&mut dyn SpriteBatch> {
None
}
2021-10-14 04:54:11 +00:00
fn to_rect(&self) -> common::Rect<usize>;
fn clear(&mut self);
fn add(&mut self, x: f32, y: f32);
fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>);
fn add_rect_flip(&mut self, x: f32, y: f32, flip_x: bool, flip_y: bool, rect: &common::Rect<u16>);
fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>);
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
);
2021-10-14 04:54:11 +00:00
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>);
fn add_rect_scaled_tinted(
&mut self,
x: f32,
y: f32,
color: (u8, u8, u8, u8),
scale_x: f32,
scale_y: f32,
rect: &common::Rect<u16>,
);
fn draw(&mut self, ctx: &mut Context) -> GameResult;
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult;
2022-01-06 01:11:17 +00:00
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>>;
2021-10-14 04:54:11 +00:00
}
pub struct DummyBatch;
impl SpriteBatch for DummyBatch {
fn width(&self) -> usize {
1
}
fn height(&self) -> usize {
1
}
fn dimensions(&self) -> (usize, usize) {
(1, 1)
}
fn real_dimensions(&self) -> (usize, usize) {
(1, 1)
}
fn scale(&self) -> (f32, f32) {
(1.0, 1.0)
}
fn has_glow_layer(&self) -> bool {
false
}
fn has_normal_layer(&self) -> bool {
false
}
fn to_rect(&self) -> Rect<usize> {
Rect::new(0, 0, 1, 1)
}
fn clear(&mut self) {}
fn add(&mut self, _x: f32, _y: f32) {}
fn add_rect(&mut self, _x: f32, _y: f32, _rect: &Rect<u16>) {}
fn add_rect_flip(&mut self, _x: f32, _y: f32, _flip_x: bool, _flip_y: bool, _rect: &Rect<u16>) {}
fn add_rect_tinted(&mut self, _x: f32, _y: f32, _color: (u8, u8, u8, u8), _rect: &Rect<u16>) {}
fn add_rect_flip_tinted(
&mut self,
_x: f32,
_y: f32,
_flip_x: bool,
_flip_y: bool,
_color: (u8, u8, u8, u8),
_rect: &Rect<u16>,
2022-11-19 17:20:03 +00:00
) {}
2021-10-14 04:54:11 +00:00
fn add_rect_scaled(&mut self, _x: f32, _y: f32, _scale_x: f32, _scale_y: f32, _rect: &Rect<u16>) {}
fn add_rect_scaled_tinted(
&mut self,
_x: f32,
_y: f32,
_color: (u8, u8, u8, u8),
_scale_x: f32,
_scale_y: f32,
_rect: &Rect<u16>,
2022-11-19 17:20:03 +00:00
) {}
2021-10-14 04:54:11 +00:00
fn draw(&mut self, _ctx: &mut Context) -> GameResult {
Ok(())
}
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult {
Ok(())
}
2022-01-06 01:11:17 +00:00
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>> {
None
}
2021-10-14 04:54:11 +00:00
}
2021-10-31 08:14:13 +00:00
pub struct SubBatch {
2021-01-28 22:33:43 +00:00
batch: Box<dyn BackendTexture>,
2021-10-31 08:14:13 +00:00
width: u16,
height: u16,
real_width: u16,
real_height: u16,
2020-08-18 16:46:07 +00:00
scale_x: f32,
scale_y: f32,
}
2021-10-31 08:14:13 +00:00
pub struct CombinedBatch {
main_batch: SubBatch,
glow_batch: Option<SubBatch>,
}
impl SpriteBatch for SubBatch {
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn width(&self) -> usize {
2021-10-31 08:14:13 +00:00
self.width as _
2020-08-18 16:46:07 +00:00
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn height(&self) -> usize {
2021-10-31 08:14:13 +00:00
self.height as _
2020-08-18 16:46:07 +00:00
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn dimensions(&self) -> (usize, usize) {
2021-10-31 08:14:13 +00:00
(self.width as _, self.height as _)
2020-08-18 16:46:07 +00:00
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn real_dimensions(&self) -> (usize, usize) {
2021-10-31 08:14:13 +00:00
(self.real_width as _, self.real_height as _)
2020-08-18 16:46:07 +00:00
}
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn scale(&self) -> (f32, f32) {
(self.scale_x, self.scale_y)
}
2021-08-16 16:41:42 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn has_glow_layer(&self) -> bool {
2021-10-31 08:14:13 +00:00
false
2021-08-16 16:41:42 +00:00
}
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn has_normal_layer(&self) -> bool {
2021-10-31 08:14:13 +00:00
false
2021-08-16 16:41:42 +00:00
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn to_rect(&self) -> common::Rect<usize> {
2021-10-31 08:14:13 +00:00
common::Rect::<usize>::new(0, 0, self.width as _, self.height as _)
2020-08-18 16:46:07 +00:00
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn clear(&mut self) {
2021-01-28 22:33:43 +00:00
self.batch.clear();
2020-08-18 16:46:07 +00:00
}
2021-10-14 04:54:11 +00:00
fn add(&mut self, x: f32, y: f32) {
2021-01-28 22:33:43 +00:00
let mag = unsafe { I_MAG };
self.batch.add(SpriteBatchCommand::DrawRect(
2021-08-16 16:41:42 +00:00
Rect { left: 0 as f32, top: 0 as f32, right: self.real_width as f32, bottom: self.real_height as f32 },
2021-01-28 22:33:43 +00:00
Rect {
left: x * mag,
top: y * mag,
right: (x + self.width() as f32) * mag,
bottom: (y + self.height() as f32) * mag,
},
));
2020-08-18 16:46:07 +00:00
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>) {
2021-02-05 22:47:13 +00:00
self.add_rect_scaled(x, y, 1.0, 1.0, rect)
2020-08-29 06:59:46 +00:00
}
2021-10-14 04:54:11 +00:00
fn add_rect_flip(&mut self, x: f32, y: f32, flip_x: bool, flip_y: bool, rect: &common::Rect<u16>) {
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
return;
}
let mag = unsafe { I_MAG };
self.batch.add(SpriteBatchCommand::DrawRectFlip(
Rect {
left: rect.left as f32 / self.scale_x,
top: rect.top as f32 / self.scale_y,
right: rect.right as f32 / self.scale_x,
bottom: rect.bottom as f32 / self.scale_y,
},
Rect {
left: x * mag,
top: y * mag,
right: (x + rect.width() as f32) * mag,
bottom: (y + rect.height() as f32) * mag,
},
2021-08-16 16:41:42 +00:00
flip_x,
flip_y,
));
}
2020-12-20 20:57:17 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
2021-02-05 22:47:13 +00:00
self.add_rect_scaled_tinted(x, y, color, 1.0, 1.0, rect)
2020-12-20 20:57:17 +00:00
}
2022-11-20 19:38:36 +00:00
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
2020-08-20 18:31:47 +00:00
return;
}
2021-01-28 22:33:43 +00:00
let mag = unsafe { I_MAG };
2022-11-20 19:38:36 +00:00
self.batch.add(SpriteBatchCommand::DrawRectFlipTinted(
2021-01-28 22:33:43 +00:00
Rect {
2021-02-05 22:47:13 +00:00
left: rect.left as f32 / self.scale_x,
top: rect.top as f32 / self.scale_y,
right: rect.right as f32 / self.scale_x,
bottom: rect.bottom as f32 / self.scale_y,
2021-01-28 22:33:43 +00:00
},
Rect {
2021-02-05 22:47:13 +00:00
left: x * mag,
top: y * mag,
2022-11-20 19:38:36 +00:00
right: (x + rect.width() as f32) * mag,
bottom: (y + rect.height() as f32) * mag,
2021-01-28 22:33:43 +00:00
},
2022-11-20 19:38:36 +00:00
flip_x,
flip_y,
color.into(),
2021-01-28 22:33:43 +00:00
));
2020-08-18 16:46:07 +00:00
}
2022-11-20 19:38:36 +00:00
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &common::Rect<u16>) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
return;
}
2021-01-28 22:33:43 +00:00
let mag = unsafe { I_MAG };
2022-11-20 19:38:36 +00:00
self.batch.add(SpriteBatchCommand::DrawRect(
Rect {
left: rect.left as f32 / self.scale_x,
top: rect.top as f32 / self.scale_y,
right: rect.right as f32 / self.scale_x,
bottom: rect.bottom as f32 / self.scale_y,
},
2021-01-28 22:33:43 +00:00
Rect {
left: x * mag,
top: y * mag,
2021-02-05 09:47:30 +00:00
right: (x + rect.width() as f32 * scale_x) * mag,
bottom: (y + rect.height() as f32 * scale_y) * mag,
2021-01-28 22:33:43 +00:00
},
));
}
2022-11-20 19:38:36 +00:00
fn add_rect_scaled_tinted(
&mut self,
x: f32,
y: f32,
color: (u8, u8, u8, u8),
2022-11-20 19:38:36 +00:00
scale_x: f32,
scale_y: f32,
rect: &common::Rect<u16>,
) {
if (rect.right.saturating_sub(rect.left)) == 0 || (rect.bottom.saturating_sub(rect.top)) == 0 {
return;
}
let mag = unsafe { I_MAG };
2022-11-20 19:38:36 +00:00
self.batch.add(SpriteBatchCommand::DrawRectTinted(
Rect {
left: rect.left as f32 / self.scale_x,
top: rect.top as f32 / self.scale_y,
right: rect.right as f32 / self.scale_x,
bottom: rect.bottom as f32 / self.scale_y,
},
Rect {
left: x * mag,
top: y * mag,
2022-11-20 19:38:36 +00:00
right: (x + rect.width() as f32 * scale_x) * mag,
bottom: (y + rect.height() as f32 * scale_y) * mag,
},
color.into(),
));
}
2020-09-25 19:25:40 +00:00
#[inline(always)]
2021-10-14 04:54:11 +00:00
fn draw(&mut self, ctx: &mut Context) -> GameResult {
2021-01-28 22:33:43 +00:00
self.draw_filtered(FilterMode::Nearest, ctx)
2020-09-25 19:25:40 +00:00
}
2021-10-14 04:54:11 +00:00
fn draw_filtered(&mut self, _filter: FilterMode, _ctx: &mut Context) -> GameResult {
2021-02-05 22:47:13 +00:00
//self.batch.set_filter(filter);
2021-01-28 22:33:43 +00:00
self.batch.draw()?;
self.batch.clear();
2020-08-18 16:46:07 +00:00
Ok(())
}
2022-01-06 01:11:17 +00:00
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>> {
Some(&self.batch)
}
2020-08-18 16:46:07 +00:00
}
2021-10-31 08:14:13 +00:00
impl SpriteBatch for CombinedBatch {
fn width(&self) -> usize {
self.main_batch.width as _
}
fn height(&self) -> usize {
self.main_batch.height as _
}
fn dimensions(&self) -> (usize, usize) {
self.main_batch.dimensions()
}
fn real_dimensions(&self) -> (usize, usize) {
self.main_batch.real_dimensions()
}
fn scale(&self) -> (f32, f32) {
self.main_batch.scale()
}
fn has_glow_layer(&self) -> bool {
self.glow_batch.is_some()
}
fn has_normal_layer(&self) -> bool {
false
}
fn glow(&mut self) -> Option<&mut dyn SpriteBatch> {
2022-01-17 22:29:30 +00:00
self.glow_batch.as_mut().map(|batch| batch as &mut dyn SpriteBatch)
2021-10-31 08:14:13 +00:00
}
fn to_rect(&self) -> Rect<usize> {
self.main_batch.to_rect()
}
fn clear(&mut self) {
self.main_batch.clear()
}
fn add(&mut self, x: f32, y: f32) {
self.main_batch.add(x, y)
}
fn add_rect(&mut self, x: f32, y: f32, rect: &Rect<u16>) {
self.main_batch.add_rect(x, y, rect)
}
fn add_rect_flip(&mut self, x: f32, y: f32, flip_x: bool, flip_y: bool, rect: &Rect<u16>) {
self.main_batch.add_rect_flip(x, y, flip_x, flip_y, rect)
}
fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &Rect<u16>) {
self.main_batch.add_rect_tinted(x, y, color, rect)
}
2022-11-20 19:38:36 +00:00
fn add_rect_flip_tinted(
&mut self,
x: f32,
y: f32,
flip_x: bool,
flip_y: bool,
color: (u8, u8, u8, u8),
rect: &common::Rect<u16>,
) {
self.main_batch.add_rect_flip_tinted(x, y, flip_x, flip_y, color, rect)
}
2021-10-31 08:14:13 +00:00
fn add_rect_scaled(&mut self, x: f32, y: f32, scale_x: f32, scale_y: f32, rect: &Rect<u16>) {
self.main_batch.add_rect_scaled(x, y, scale_x, scale_y, rect)
}
fn add_rect_scaled_tinted(
&mut self,
x: f32,
y: f32,
color: (u8, u8, u8, u8),
scale_x: f32,
scale_y: f32,
rect: &Rect<u16>,
) {
self.main_batch.add_rect_scaled_tinted(x, y, color, scale_x, scale_y, rect)
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.main_batch.draw(ctx)
}
fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult {
self.main_batch.draw_filtered(filter, ctx)
}
2022-01-06 01:11:17 +00:00
fn get_texture(&self) -> Option<&Box<dyn BackendTexture>> {
self.main_batch.get_texture()
}
2021-10-31 08:14:13 +00:00
}
2020-08-18 16:46:07 +00:00
pub struct TextureSet {
2021-10-14 04:54:11 +00:00
pub tex_map: HashMap<String, Box<dyn SpriteBatch>>,
dummy_batch: Box<dyn SpriteBatch>,
2020-08-18 16:46:07 +00:00
}
impl TextureSet {
2022-02-10 07:54:20 +00:00
pub fn new() -> TextureSet {
TextureSet { tex_map: HashMap::new(), dummy_batch: Box::new(DummyBatch) }
2020-11-02 01:38:39 +00:00
}
2022-02-10 07:54:20 +00:00
pub fn unload_all(&mut self) {
self.tex_map.clear();
2020-08-18 16:46:07 +00:00
}
fn make_transparent(rgba: &mut RgbaImage) {
for (r, g, b, a) in rgba.iter_mut().tuples() {
if *r == 0 && *g == 0 && *b == 0 {
*a = 0;
}
}
}
2022-03-06 16:01:23 +00:00
fn load_image(&self, ctx: &mut Context, roots: &Vec<String>, path: &str) -> GameResult<Box<dyn BackendTexture>> {
2020-08-19 00:55:21 +00:00
let img = {
2020-08-28 03:44:33 +00:00
let mut buf = [0u8; 8];
2022-03-06 16:01:23 +00:00
let mut reader = filesystem::open_find(ctx, roots, path)?;
2020-08-28 03:44:33 +00:00
reader.read_exact(&mut buf)?;
reader.seek(SeekFrom::Start(0))?;
let image = image::load(BufReader::new(reader), image::guess_format(&buf)?)?;
2021-02-10 20:14:09 +00:00
let mut rgba = image.to_rgba8();
if image.color().channel_count() != 4 {
TextureSet::make_transparent(&mut rgba);
2020-08-19 00:55:21 +00:00
}
rgba
};
let (width, height) = img.dimensions();
2020-08-19 00:55:21 +00:00
2021-01-28 22:33:43 +00:00
create_texture(ctx, width as u16, height as u16, &img)
}
2022-02-10 07:54:20 +00:00
pub fn find_texture(&self, ctx: &mut Context, roots: &Vec<String>, name: &str) -> Option<String> {
FILE_TYPES.iter().map(|ext| [name, ext].join("")).find(|path| filesystem::exists_find(ctx, roots, path))
2021-10-16 12:59:27 +00:00
}
pub fn load_texture(
&self,
ctx: &mut Context,
constants: &EngineConstants,
name: &str,
) -> GameResult<Box<dyn SpriteBatch>> {
2021-10-31 08:14:13 +00:00
let path = self
2022-02-10 07:54:20 +00:00
.find_texture(ctx, &constants.base_paths, name)
.ok_or_else(|| GameError::ResourceLoadError(format!("Texture \"{}\" is missing.", name)))?;
2021-08-16 16:41:42 +00:00
2022-02-10 07:54:20 +00:00
let glow_path = self.find_texture(ctx, &constants.base_paths, &[name, ".glow"].join(""));
2020-08-18 16:46:07 +00:00
2021-10-16 12:59:27 +00:00
info!("Loading texture: {} -> {}", name, path);
2020-08-18 16:46:07 +00:00
2021-10-31 08:14:13 +00:00
fn make_batch(name: &str, constants: &EngineConstants, batch: Box<dyn BackendTexture>) -> SubBatch {
let size = batch.dimensions();
2022-01-17 22:29:30 +00:00
let orig_dimensions = constants.tex_sizes.get(name).unwrap_or(&size);
2022-03-06 16:01:23 +00:00
let scale =
if f32::abs((orig_dimensions.0 as f32 / size.0 as f32) - (orig_dimensions.1 as f32 / size.1 as f32))
<= f32::EPSILON
{
orig_dimensions.0 as f32 / size.0 as f32
} else if constants.is_cs_plus && constants.base_paths.iter().any(|p| p.contains("/ogph")) {
1.0
} else if constants.is_cs_plus {
0.5
} else {
1.0
};
2021-10-31 08:14:13 +00:00
let width = (size.0 as f32 * scale) as _;
let height = (size.1 as f32 * scale) as _;
SubBatch {
batch,
width,
height,
scale_x: scale,
scale_y: scale,
real_width: size.0 as _,
real_height: size.1 as _,
}
}
2022-02-10 07:54:20 +00:00
let main_batch = make_batch(name, constants, self.load_image(ctx, &constants.base_paths, &path)?);
2021-10-31 08:14:13 +00:00
let glow_batch = if let Some(glow_path) = glow_path {
2022-02-10 07:54:20 +00:00
self.load_image(ctx, &constants.base_paths, &glow_path).ok().map(|b| make_batch(name, constants, b))
2021-10-31 08:14:13 +00:00
} else {
None
};
Ok(Box::new(CombinedBatch { main_batch, glow_batch }))
2020-08-18 16:46:07 +00:00
}
2021-08-16 16:41:42 +00:00
pub fn get_or_load_batch(
&mut self,
ctx: &mut Context,
constants: &EngineConstants,
name: &str,
2021-10-14 04:54:11 +00:00
) -> GameResult<&mut Box<dyn SpriteBatch>> {
if ctx.headless {
return Ok(&mut self.dummy_batch);
}
2020-08-19 13:11:34 +00:00
if !self.tex_map.contains_key(name) {
2020-08-23 02:16:31 +00:00
let batch = self.load_texture(ctx, constants, name)?;
2021-10-15 14:36:05 +00:00
self.tex_map.insert(name.to_owned(), batch);
2020-08-18 16:46:07 +00:00
}
2020-08-19 13:11:34 +00:00
Ok(self.tex_map.get_mut(name).unwrap())
2020-08-18 16:46:07 +00:00
}
}