mod canvas;
mod context;
mod drawparam;
mod image;
#[cfg(feature = "mesh")]
mod mesh;
mod shader;
mod text;
mod types;
pub mod spritebatch;
use crate::error::GameResult;
use crate::Context;
pub use self::{
canvas::*, context::GraphicsContext, drawparam::*, image::*, shader::*, text::*, types::*,
};
#[cfg(feature = "mesh")]
pub use self::mesh::*;
use miniquad::PassAction;
static mut DROPPED_BINDINGS: Vec<(miniquad::Bindings, i32, bool)> = Vec::new();
pub(crate) fn add_dropped_bindings(bindings: miniquad::Bindings, delete_texture: bool) {
unsafe { DROPPED_BINDINGS.push((bindings, 1, delete_texture)) };
}
pub(crate) fn release_dropped_bindings() {
unsafe {
for (bindings, counter, delete_texture) in DROPPED_BINDINGS.iter_mut() {
if *counter == 0 {
for v_buffer in bindings.vertex_buffers.iter_mut() {
v_buffer.delete();
}
bindings.index_buffer.delete();
if *delete_texture {
bindings.images[0].delete();
}
}
*counter -= 1;
}
DROPPED_BINDINGS.retain(|(_bindings, counter, _delete_texture)| *counter >= 0);
}
}
pub fn clear(ctx: &mut Context, quad_ctx: &mut miniquad::graphics::GraphicsContext, color: Color) {
let action = PassAction::Clear {
color: Some((color.r, color.g, color.b, color.a)),
depth: None,
stencil: None,
};
let pass = ctx.framebuffer();
quad_ctx.begin_pass(pass, action);
quad_ctx.clear(Some((color.r, color.g, color.b, color.a)), None, None);
}
pub fn draw<D, T>(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
drawable: &D,
params: T,
) -> GameResult
where
D: Drawable,
T: Into<DrawParam>,
{
let params = params.into();
drawable.draw(ctx, quad_ctx, params)
}
pub fn set_projection<M>(context: &mut Context, proj: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let proj = cgmath::Matrix4::from(proj.into());
let gfx = &mut context.gfx_context;
gfx.set_projection(proj);
}
pub fn mul_projection<M>(context: &mut Context, proj: M)
where
M: Into<mint::ColumnMatrix4<f32>>,
{
let proj = cgmath::Matrix4::from(proj.into());
let gfx = &mut context.gfx_context;
let curr = gfx.projection();
gfx.set_projection(proj * curr);
}
pub fn drawable_size(quad_ctx: &miniquad::graphics::GraphicsContext) -> (f32, f32) {
quad_ctx.screen_size()
}
pub fn set_screen_coordinates(context: &mut Context, rect: Rect) -> GameResult {
context.gfx_context.set_screen_coordinates(rect);
Ok(())
}
pub fn screen_coordinates(ctx: &Context) -> Rect {
ctx.gfx_context.screen_rect
}
pub fn set_blend_mode(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
mode: BlendMode,
) -> GameResult {
let (color_blend, alpha_blend) = mode.into();
quad_ctx.set_blend(Some(color_blend), Some(alpha_blend));
ctx.gfx_context.set_blend_mode(mode);
Ok(())
}
pub fn blend_mode(ctx: &Context) -> &BlendMode {
ctx.gfx_context.blend_mode()
}
pub fn set_default_filter(ctx: &mut Context, mode: FilterMode) {
ctx.gfx_context.default_filter = mode;
}
pub fn default_filter(ctx: &Context) -> FilterMode {
ctx.gfx_context.default_filter
}
pub(crate) fn set_current_blend_mode(
quad_ctx: &mut miniquad::graphics::GraphicsContext,
blend_mode: BlendMode,
) {
let (color_blend, alpha_blend) = blend_mode.into();
quad_ctx.set_blend(Some(color_blend), Some(alpha_blend));
}
pub(crate) fn restore_blend_mode(
ctx: &Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
) {
set_current_blend_mode(quad_ctx, ctx.gfx_context.blend_mode)
}
pub fn present(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
) -> GameResult<()> {
crate::graphics::set_canvas(ctx, None);
quad_ctx.commit_frame();
Ok(())
}
pub fn set_fullscreen(quad_ctx: &mut miniquad::graphics::GraphicsContext, fullscreen: bool) {
quad_ctx.set_fullscreen(fullscreen);
}
pub fn set_drawable_size(
quad_ctx: &mut miniquad::graphics::GraphicsContext,
width: u32,
height: u32,
) {
quad_ctx.set_window_size(width, height);
}
pub fn clear_font_cache(ctx: &mut Context, quad_ctx: &mut miniquad::graphics::GraphicsContext) {
use glyph_brush::GlyphBrushBuilder;
use std::cell::RefCell;
use std::rc::Rc;
let font_vec =
glyph_brush::ab_glyph::FontArc::try_from_slice(Font::default_font_bytes()).unwrap();
let glyph_brush = GlyphBrushBuilder::using_font(font_vec).build();
let (glyph_cache_width, glyph_cache_height) = glyph_brush.texture_dimensions();
let initial_contents = vec![255; 4 * glyph_cache_width as usize * glyph_cache_height as usize];
let glyph_cache = Image::from_rgba8(
ctx,
quad_ctx,
glyph_cache_width.try_into().unwrap(),
glyph_cache_height.try_into().unwrap(),
&initial_contents,
)
.unwrap();
let glyph_state = Rc::new(RefCell::new(spritebatch::SpriteBatch::new(
glyph_cache.clone(),
)));
ctx.gfx_context.glyph_brush = Rc::new(RefCell::new(glyph_brush));
ctx.gfx_context.glyph_cache = glyph_cache;
ctx.gfx_context.glyph_state = glyph_state;
}
pub trait Drawable {
fn draw(
&self,
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
param: DrawParam,
) -> GameResult;
fn set_blend_mode(&mut self, mode: Option<BlendMode>);
fn blend_mode(&self) -> Option<BlendMode>;
fn dimensions(&self, _: &mut Context) -> Option<Rect> {
None
}
}
pub fn transform_rect(rect: Rect, param: DrawParam) -> Rect {
match param.trans {
Transform::Values {
scale,
offset,
dest,
rotation,
} => {
let mut r = Rect {
w: rect.w,
h: rect.h,
x: rect.x - offset.x * rect.w,
y: rect.y - offset.y * rect.h,
};
let real_scale = (param.src.w * scale.x, param.src.h * scale.y);
r.w = real_scale.0 * rect.w;
r.h = real_scale.1 * rect.h;
r.x *= real_scale.0;
r.y *= real_scale.1;
r.rotate(rotation);
r.x += dest.x;
r.y += dest.y;
r
}
Transform::Matrix(_m) => todo!("Fix me"),
}
}
#[cfg(test)]
mod tests {
use crate::graphics::{transform_rect, DrawParam, Rect};
use approx::assert_relative_eq;
use std::f32::consts::PI;
#[test]
fn headless_test_transform_rect() {
{
let r = Rect {
x: 0.0,
y: 0.0,
w: 1.0,
h: 1.0,
};
let param = DrawParam::default();
let real = transform_rect(r, param);
let expected = r;
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new().scale([0.5, 0.5]);
let real = transform_rect(r, param);
let expected = Rect {
x: -0.5,
y: -0.5,
w: 1.0,
h: 0.5,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 1.0,
h: 1.0,
};
let param = DrawParam::new().offset([0.5, 0.5]);
let real = transform_rect(r, param);
let expected = Rect {
x: -1.5,
y: -1.5,
w: 1.0,
h: 1.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: 1.0,
y: 0.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new().rotation(PI * 0.5);
let real = transform_rect(r, param);
let expected = Rect {
x: -1.0,
y: 1.0,
w: 1.0,
h: 2.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new()
.scale([0.5, 0.5])
.offset([0.0, 1.0])
.rotation(PI * 0.5);
let real = transform_rect(r, param);
let expected = Rect {
x: 0.5,
y: -0.5,
w: 0.5,
h: 1.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: -1.0,
y: -1.0,
w: 2.0,
h: 1.0,
};
let param = DrawParam::new()
.scale([0.5, 0.5])
.offset([0.0, 1.0])
.rotation(PI * 0.5)
.dest([1.0, 0.0]);
let real = transform_rect(r, param);
let expected = Rect {
x: 1.5,
y: -0.5,
w: 0.5,
h: 1.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: 0.0,
y: 0.0,
w: 1.0,
h: 1.0,
};
let param = DrawParam::new()
.offset([0.5, 0.5])
.rotation(PI * 1.5)
.dest([1.0, 0.5]);
let real = transform_rect(r, param);
let expected = Rect {
x: 0.5,
y: 0.0,
w: 1.0,
h: 1.0,
};
assert_relative_eq!(real, expected);
}
{
let r = Rect {
x: 0.0,
y: 0.0,
w: 1.0,
h: 1.0,
};
let param = DrawParam::new()
.offset([0.5, 0.5])
.rotation(PI * 0.25)
.scale([2.0, 1.0])
.dest([1.0, 2.0]);
let real = transform_rect(r, param);
let sqrt = (2f32).sqrt() / 2.;
let unit = sqrt + sqrt / 2.;
let expected = Rect {
x: -unit + 1.,
y: -unit + 2.,
w: 2. * unit,
h: 2. * unit,
};
assert_relative_eq!(real, expected);
}
}
}