use ggez::glam::Vec2;
use ggez::graphics::{self, AsStd140, BlendMode, Canvas, Color, DrawParam, Shader};
use ggez::{event, graphics::ShaderParams};
use ggez::{Context, GameResult};
use std::env;
use std::path;
#[derive(AsStd140)]
struct Light {
light_color: mint::Vector4<f32>,
shadow_color: mint::Vector4<f32>,
pos: mint::Vector2<f32>,
screen_size: mint::Vector2<f32>,
glow: f32,
strength: f32,
}
const OCCLUSIONS_SHADER_SOURCE: &str = include_str!("../resources/occlusions.wgsl");
const SHADOWS_SHADER_SOURCE: &str = include_str!("../resources/shadows.wgsl");
const LIGHTS_SHADER_SOURCE: &str = include_str!("../resources/lights.wgsl");
struct MainState {
background: graphics::Image,
tile: graphics::Image,
torch: Light,
torch_params: ShaderParams<Light>,
static_light: Light,
static_light_params: ShaderParams<Light>,
foreground: graphics::ScreenImage,
occlusions: graphics::Image,
shadows: graphics::ScreenImage,
lights: graphics::ScreenImage,
occlusions_shader: Shader,
shadows_shader: Shader,
lights_shader: Shader,
}
const AMBIENT_COLOR: [f32; 4] = [0.15, 0.12, 0.24, 1.0];
const STATIC_LIGHT_COLOR: [f32; 4] = [0.37, 0.69, 0.75, 1.0];
const TORCH_COLOR: [f32; 4] = [0.80, 0.73, 0.44, 1.0];
const LIGHT_RAY_COUNT: u16 = 1440;
const LIGHT_STRENGTH: f32 = 0.0035;
const LIGHT_GLOW_FACTOR: f32 = 0.00065;
const LIGHT_GLOW_RATE: f32 = 0.9;
impl MainState {
fn new(ctx: &mut Context) -> GameResult<MainState> {
let background = graphics::Image::from_path(ctx, "/bg_top.png")?;
let tile = graphics::Image::from_path(ctx, "/tile.png")?;
let screen_size = {
let size = ctx.gfx.drawable_size();
[size.0 as f32, size.1 as f32]
};
let torch = Light {
pos: [0.0, 0.0].into(),
light_color: TORCH_COLOR.into(),
shadow_color: AMBIENT_COLOR.into(),
screen_size: screen_size.into(),
glow: 0.0,
strength: LIGHT_STRENGTH,
};
let torch_params = ShaderParams::new(ctx, &torch, &[], &[]);
let (w, h) = ctx.gfx.size();
let (x, y) = (100.0 / w as f32, 75.0 / h as f32);
let static_light = Light {
pos: [x, y].into(),
light_color: STATIC_LIGHT_COLOR.into(),
shadow_color: AMBIENT_COLOR.into(),
screen_size: screen_size.into(),
glow: 0.0,
strength: LIGHT_STRENGTH,
};
let static_light_params = ShaderParams::new(ctx, &static_light, &[], &[]);
let color_format = ctx.gfx.surface_format();
let foreground = graphics::ScreenImage::new(ctx, None, 1., 1., 1);
let occlusions =
graphics::Image::new_canvas_image(ctx, color_format, LIGHT_RAY_COUNT.into(), 1, 1);
let shadows = graphics::ScreenImage::new(ctx, None, 1., 1., 1);
let lights = graphics::ScreenImage::new(ctx, None, 1., 1., 1);
let occlusions_shader = Shader::from_wgsl(ctx, OCCLUSIONS_SHADER_SOURCE, "main");
let shadows_shader = Shader::from_wgsl(ctx, SHADOWS_SHADER_SOURCE, "main");
let lights_shader = Shader::from_wgsl(ctx, LIGHTS_SHADER_SOURCE, "main");
Ok(MainState {
background,
tile,
torch,
torch_params,
static_light,
static_light_params,
foreground,
occlusions,
shadows,
lights,
occlusions_shader,
shadows_shader,
lights_shader,
})
}
fn render_light(
&mut self,
ctx: &mut Context,
light: ShaderParams<Light>,
origin: DrawParam,
canvas_origin: DrawParam,
clear: Option<graphics::Color>,
) -> GameResult {
let foreground = self.foreground.image(ctx);
let size = ctx.gfx.drawable_size();
let mut canvas = Canvas::from_image(ctx, self.occlusions.clone(), None);
canvas.set_screen_coordinates(graphics::Rect::new(0., 0., size.0, size.1));
canvas.set_shader(self.occlusions_shader.clone());
canvas.set_shader_params(light.clone());
canvas.draw(&foreground, canvas_origin);
canvas.finish(ctx)?;
let mut canvas = Canvas::from_screen_image(ctx, &mut self.shadows, clear);
canvas.set_screen_coordinates(graphics::Rect::new(0., 0., size.0, size.1));
canvas.set_shader(self.shadows_shader.clone());
canvas.set_shader_params(light.clone());
canvas.draw(
&self.occlusions,
origin.scale([
size.0 / self.occlusions.width() as f32,
size.1 / self.occlusions.height() as f32,
]),
);
canvas.finish(ctx)?;
let mut canvas = Canvas::from_screen_image(ctx, &mut self.lights, clear);
canvas.set_screen_coordinates(graphics::Rect::new(0., 0., size.0, size.1));
canvas.set_blend_mode(BlendMode::ADD);
canvas.set_shader(self.lights_shader.clone());
canvas.set_shader_params(light);
canvas.draw(
&self.occlusions,
origin.scale([
size.0 / self.occlusions.width() as f32,
size.1 / self.occlusions.height() as f32,
]),
);
canvas.finish(ctx)?;
Ok(())
}
}
impl event::EventHandler<ggez::GameError> for MainState {
fn update(&mut self, ctx: &mut Context) -> GameResult {
if ctx.time.ticks() % 100 == 0 {
println!("Average FPS: {}", ctx.time.fps());
}
self.torch.glow =
LIGHT_GLOW_FACTOR * (ctx.time.time_since_start().as_secs_f32() * LIGHT_GLOW_RATE).cos();
self.static_light.glow = LIGHT_GLOW_FACTOR
* (ctx.time.time_since_start().as_secs_f32() * LIGHT_GLOW_RATE * 0.75).sin();
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
self.torch_params.set_uniforms(ctx, &self.torch);
self.static_light_params
.set_uniforms(ctx, &self.static_light);
let origin = DrawParam::new()
.dest(Vec2::new(0.0, 0.0))
.scale(Vec2::new(0.5, 0.5));
let canvas_origin = DrawParam::new();
let foreground = self.foreground.image(ctx);
let mut canvas = Canvas::from_image(ctx, foreground, Color::new(0.0, 0.0, 0.0, 0.0));
canvas.draw(&self.tile, DrawParam::new().dest(Vec2::new(598.0, 124.0)));
canvas.draw(&self.tile, DrawParam::new().dest(Vec2::new(92.0, 350.0)));
canvas.draw(
&self.tile,
DrawParam::new().dest(Vec2::new(442.0, 468.0)).rotation(0.5),
);
canvas.draw(
graphics::Text::new("SHADOWS...").set_scale(48.),
graphics::DrawParam::from([50., 200.]),
);
canvas.finish(ctx)?;
self.render_light(
ctx,
self.torch_params.clone(),
origin,
canvas_origin,
Some(Color::BLACK),
)?;
self.render_light(
ctx,
self.static_light_params.clone(),
origin,
canvas_origin,
None,
)?;
let shadows = self.shadows.image(ctx);
let foreground = self.foreground.image(ctx);
let lights = self.lights.image(ctx);
let mut canvas = Canvas::from_frame(ctx, Color::WHITE);
canvas.draw(&self.background, DrawParam::default());
canvas.set_blend_mode(BlendMode::MULTIPLY);
canvas.draw(&shadows, DrawParam::default());
canvas.set_blend_mode(BlendMode::ALPHA);
canvas.draw(&foreground, DrawParam::default());
canvas.set_blend_mode(BlendMode::ADD);
canvas.draw(&lights, DrawParam::default());
canvas.finish(ctx)?;
Ok(())
}
fn mouse_motion_event(
&mut self,
ctx: &mut Context,
x: f32,
y: f32,
_xrel: f32,
_yrel: f32,
) -> GameResult {
let (w, h) = ctx.gfx.drawable_size();
let (x, y) = (x / w as f32, y / h as f32);
self.torch.pos = [x, y].into();
Ok(())
}
}
pub fn main() -> GameResult {
let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("resources");
path
} else {
path::PathBuf::from("./resources")
};
let cb = ggez::ContextBuilder::new("shadows", "ggez").add_resource_path(resource_dir);
let (mut ctx, event_loop) = cb.build()?;
let state = MainState::new(&mut ctx)?;
event::run(ctx, event_loop, state)
}