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
|
|
|
|
2020-11-04 23:25:18 +00:00
|
|
|
use crate::common;
|
2021-01-28 22:33:43 +00:00
|
|
|
use crate::common::{FILE_TYPES, Point, 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-11-28 19:25:51 +00:00
|
|
|
use crate::settings::Settings;
|
|
|
|
use crate::shared_game_state::Season;
|
2020-11-04 23:25:18 +00:00
|
|
|
use crate::str;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
pub static mut I_MAG: f32 = 1.0;
|
2020-12-25 22:39:41 +00:00
|
|
|
pub static mut G_MAG: f32 = 1.0;
|
2020-11-25 07:24:38 +00:00
|
|
|
|
2020-08-18 16:46:07 +00:00
|
|
|
pub struct SizedBatch {
|
2021-01-28 22:33:43 +00:00
|
|
|
batch: Box<dyn BackendTexture>,
|
2020-08-18 16:46:07 +00:00
|
|
|
width: usize,
|
|
|
|
height: usize,
|
|
|
|
real_width: usize,
|
|
|
|
real_height: usize,
|
|
|
|
scale_x: f32,
|
|
|
|
scale_y: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SizedBatch {
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn width(&self) -> usize {
|
|
|
|
self.width
|
|
|
|
}
|
|
|
|
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn height(&self) -> usize {
|
|
|
|
self.height
|
|
|
|
}
|
|
|
|
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn dimensions(&self) -> (usize, usize) {
|
|
|
|
(self.width, self.height)
|
|
|
|
}
|
|
|
|
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn real_dimensions(&self) -> (usize, usize) {
|
|
|
|
(self.real_width, self.real_height)
|
|
|
|
}
|
|
|
|
|
2020-09-25 20:02:35 +00:00
|
|
|
#[inline(always)]
|
|
|
|
pub fn scale(&self) -> (f32, f32) {
|
|
|
|
(self.scale_x, self.scale_y)
|
|
|
|
}
|
|
|
|
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn to_rect(&self) -> common::Rect<usize> {
|
|
|
|
common::Rect::<usize>::new(0, 0, self.width, self.height)
|
|
|
|
}
|
|
|
|
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub fn clear(&mut self) {
|
2021-01-28 22:33:43 +00:00
|
|
|
self.batch.clear();
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
pub fn add(&mut self, mut x: f32, mut y: f32) {
|
|
|
|
unsafe {
|
|
|
|
x = (x * G_MAG).floor() / G_MAG;
|
|
|
|
y = (y * G_MAG).floor() / G_MAG;
|
|
|
|
}
|
|
|
|
let mag = unsafe { I_MAG };
|
|
|
|
|
|
|
|
self.batch.add(SpriteBatchCommand::DrawRect(
|
|
|
|
Rect {
|
|
|
|
left: 0 as f32,
|
|
|
|
top: 0 as f32,
|
|
|
|
right: self.real_width as f32,
|
|
|
|
bottom: self.real_height as f32,
|
|
|
|
},
|
|
|
|
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)]
|
2020-11-17 02:42:45 +00:00
|
|
|
pub fn add_rect(&mut self, x: f32, y: f32, rect: &common::Rect<u16>) {
|
2020-08-29 06:59:46 +00:00
|
|
|
self.add_rect_scaled(x, y, self.scale_x, self.scale_y, rect)
|
|
|
|
}
|
|
|
|
|
2020-12-20 20:57:17 +00:00
|
|
|
#[inline(always)]
|
|
|
|
pub fn add_rect_tinted(&mut self, x: f32, y: f32, color: (u8, u8, u8, u8), rect: &common::Rect<u16>) {
|
|
|
|
self.add_rect_scaled_tinted(x, y, color, self.scale_x, self.scale_y, rect)
|
|
|
|
}
|
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
pub fn add_rect_scaled(&mut self, mut x: f32, mut y: f32, mut scale_x: f32, mut scale_y: f32, rect: &common::Rect<u16>) {
|
2020-08-20 18:31:47 +00:00
|
|
|
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-25 07:24:38 +00:00
|
|
|
unsafe {
|
2020-12-25 22:39:41 +00:00
|
|
|
x = (x * G_MAG).floor() / G_MAG;
|
|
|
|
y = (y * G_MAG).floor() / G_MAG;
|
2020-11-25 07:24:38 +00:00
|
|
|
}
|
2021-01-28 22:33:43 +00:00
|
|
|
let mag = unsafe { I_MAG };
|
|
|
|
|
|
|
|
self.batch.add(SpriteBatchCommand::DrawRect(
|
|
|
|
Rect {
|
|
|
|
left: rect.left as f32 / scale_x,
|
|
|
|
top: rect.top as f32 / scale_y,
|
|
|
|
right: rect.right as f32 / scale_x,
|
|
|
|
bottom: rect.bottom as f32 / scale_y,
|
|
|
|
},
|
|
|
|
Rect {
|
|
|
|
left: x * mag,
|
|
|
|
top: y * mag,
|
|
|
|
right: (x + rect.width() as f32) * mag,
|
|
|
|
bottom: (y + rect.height() as f32) * mag,
|
|
|
|
},
|
|
|
|
));
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
pub fn add_rect_scaled_tinted(&mut self, mut x: f32, mut y: f32, color: (u8, u8, u8, u8), mut scale_x: f32, mut scale_y: f32, rect: &common::Rect<u16>) {
|
2020-09-22 19:31:47 +00:00
|
|
|
if (rect.right - rect.left) == 0 || (rect.bottom - rect.top) == 0 {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
unsafe {
|
|
|
|
x = (x * G_MAG).floor() / G_MAG;
|
|
|
|
y = (y * G_MAG).floor() / G_MAG;
|
|
|
|
}
|
|
|
|
let mag = unsafe { I_MAG };
|
|
|
|
|
|
|
|
self.batch.add(SpriteBatchCommand::DrawRectTinted(
|
|
|
|
Rect {
|
|
|
|
left: rect.left as f32 / scale_x,
|
|
|
|
top: rect.top as f32 / scale_y,
|
|
|
|
right: rect.right as f32 / scale_x,
|
|
|
|
bottom: rect.bottom as f32 / scale_y,
|
|
|
|
},
|
|
|
|
Rect {
|
|
|
|
left: x * mag,
|
|
|
|
top: y * mag,
|
|
|
|
right: (x + rect.width() as f32) * mag,
|
|
|
|
bottom: (y + rect.height() as f32) * mag,
|
|
|
|
},
|
|
|
|
color.into(),
|
|
|
|
));
|
2020-09-22 19:31:47 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 19:25:40 +00:00
|
|
|
#[inline(always)]
|
2020-08-18 16:46:07 +00:00
|
|
|
pub 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
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw_filtered(&mut self, filter: FilterMode, ctx: &mut Context) -> GameResult {
|
2021-01-28 22:33:43 +00:00
|
|
|
///self.batch.set_filter(filter);
|
|
|
|
self.batch.draw()?;
|
|
|
|
self.batch.clear();
|
2020-08-18 16:46:07 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct TextureSet {
|
|
|
|
pub tex_map: HashMap<String, SizedBatch>,
|
2020-11-02 01:38:39 +00:00
|
|
|
pub paths: Vec<String>,
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TextureSet {
|
|
|
|
pub fn new(base_path: &str) -> TextureSet {
|
|
|
|
TextureSet {
|
|
|
|
tex_map: HashMap::new(),
|
2020-11-02 01:38:39 +00:00
|
|
|
paths: vec![base_path.to_string(), "".to_string()],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn apply_seasonal_content(&mut self, season: Season, settings: &Settings) {
|
|
|
|
if settings.original_textures {
|
|
|
|
self.paths.insert(0, "/base/ogph/".to_string())
|
|
|
|
} else if settings.seasonal_textures {
|
|
|
|
match season {
|
|
|
|
Season::Halloween => self.paths.insert(0, "/Halloween/season/".to_string()),
|
|
|
|
Season::Christmas => self.paths.insert(0, "/Christmas/season/".to_string()),
|
|
|
|
_ => {}
|
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-28 03:48:41 +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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
fn load_image(&self, ctx: &mut Context, 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];
|
2020-08-19 00:55:21 +00:00
|
|
|
let mut reader = filesystem::open(ctx, 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)?)?;
|
2020-08-19 01:59:16 +00:00
|
|
|
let mut rgba = image.to_rgba();
|
|
|
|
if image.color().channel_count() != 4 {
|
2020-08-28 03:48:41 +00:00
|
|
|
TextureSet::make_transparent(&mut rgba);
|
2020-08-19 00:55:21 +00:00
|
|
|
}
|
2020-08-28 03:48:41 +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)
|
2020-08-28 03:48:41 +00:00
|
|
|
}
|
|
|
|
|
2020-08-19 00:55:21 +00:00
|
|
|
pub fn load_texture(&self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult<SizedBatch> {
|
2020-11-02 01:38:39 +00:00
|
|
|
let path = self.paths.iter().find_map(|s| FILE_TYPES
|
2020-08-18 16:46:07 +00:00
|
|
|
.iter()
|
2020-11-02 01:38:39 +00:00
|
|
|
.map(|ext| [s, name, ext].join(""))
|
|
|
|
.find(|path| {
|
|
|
|
println!("{}", path);
|
|
|
|
filesystem::exists(ctx, path)
|
|
|
|
})
|
|
|
|
).ok_or_else(|| GameError::ResourceLoadError(format!("Texture {} does not exist.", name)))?;
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
info!("Loading texture: {}", path);
|
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
let batch = self.load_image(ctx, &path)?;
|
|
|
|
let size = batch.dimensions();
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
assert_ne!(size.0 as isize, 0, "size.width == 0");
|
|
|
|
assert_ne!(size.1 as isize, 0, "size.height == 0");
|
2020-08-18 16:46:07 +00:00
|
|
|
|
2021-01-28 22:33:43 +00:00
|
|
|
let orig_dimensions = constants.tex_sizes.get(name).unwrap_or_else(|| &size);
|
|
|
|
let scale = orig_dimensions.0 as f32 / size.0 as f32;
|
|
|
|
let width = (size.0 as f32 * scale) as usize;
|
|
|
|
let height = (size.1 as f32 * scale) as usize;
|
|
|
|
println!("{} {} {} {}", size.0, size.1, width, height);
|
2020-08-18 16:46:07 +00:00
|
|
|
|
|
|
|
Ok(SizedBatch {
|
2021-01-28 22:33:43 +00:00
|
|
|
batch,
|
2020-08-18 16:46:07 +00:00
|
|
|
width,
|
|
|
|
height,
|
2021-01-28 22:33:43 +00:00
|
|
|
scale_x: scale,
|
|
|
|
scale_y: scale,
|
|
|
|
real_width: size.0 as usize,
|
|
|
|
real_height: size.1 as usize,
|
2020-08-18 16:46:07 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-19 13:11:34 +00:00
|
|
|
pub fn get_or_load_batch(&mut self, ctx: &mut Context, constants: &EngineConstants, name: &str) -> GameResult<&mut SizedBatch> {
|
|
|
|
if !self.tex_map.contains_key(name) {
|
2020-08-23 02:16:31 +00:00
|
|
|
let batch = self.load_texture(ctx, constants, name)?;
|
2020-08-19 13:11:34 +00:00
|
|
|
self.tex_map.insert(str!(name), 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
|
|
|
}
|
2020-09-10 11:44:59 +00:00
|
|
|
|
|
|
|
pub fn draw_rect(&self, rect: common::Rect, color: [f32; 4], ctx: &mut Context) -> GameResult {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn draw_outline_rect(&self, rect: common::Rect, width: f32, color: [f32; 4], ctx: &mut Context) -> GameResult {
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-18 16:46:07 +00:00
|
|
|
}
|