use ggraphics::*;
use glam::{self, Vec2};
use glow;
use oorandom;
use winit;
use std::time::Duration;
struct Particle {
pos: Vec2,
vel: Vec2,
rot: f32,
rvel: f32,
life: f32,
}
impl Particle {
fn into_quaddata(&self) -> QuadData {
QuadData {
offset: [0.5, 0.5],
color: [1.0, self.life, self.life, self.life],
src_rect: [0.0, 0.0, 1.0, 1.0],
dst_rect: [self.pos.x(), self.pos.y(), 0.1, 0.1],
rotation: self.rot,
}
}
}
struct GameState {
ctx: GlContext,
rng: oorandom::Rand32,
particles: Vec<Particle>,
}
impl GameState {
pub fn new(gl: glow::Context) -> Self {
let mut ctx = GlContext::new(gl);
unsafe {
let particle_texture = {
let image_bytes = include_bytes!("../src/data/wabbit_alpha.png");
let image_rgba = image::load_from_memory(image_bytes).unwrap().to_rgba();
let (w, h) = image_rgba.dimensions();
let image_rgba_bytes = image_rgba.into_raw();
TextureHandle::new(&ctx, &image_rgba_bytes, w as usize, h as usize).into_shared()
};
let mut screen_pass = RenderPass::new_screen(&mut ctx, 800, 600, (0.6, 0.6, 0.6, 1.0));
let shader = GlContext::default_shader(&ctx);
let mut pipeline = QuadPipeline::new(&ctx, shader);
pipeline.new_drawcall(&mut ctx, particle_texture, SamplerSpec::default());
screen_pass.add_pipeline(pipeline);
ctx.passes.push(screen_pass);
}
let rng = oorandom::Rand32::new(12345);
Self {
ctx,
rng,
particles: vec![],
}
}
pub fn add_particles(&mut self, source_pt: Vec2) {
const PARTICLE_COUNT: usize = 100;
for _ in 0..PARTICLE_COUNT {
let particle = Particle {
pos: source_pt,
vel: glam::vec2(
-0.005 + self.rng.rand_float() * 0.01,
0.03 + self.rng.rand_float() * 0.005,
),
rot: 0.0,
rvel: -0.1 + self.rng.rand_float() * 0.2,
life: 1.5,
};
self.particles.push(particle);
}
}
pub fn update(&mut self, frametime: Duration) -> usize {
if frametime.as_secs_f64() < 0.017 {
for particle in &mut self.particles {
particle.life -= frametime.as_secs_f32();
particle.pos += particle.vel;
particle.rot += particle.rvel;
particle.vel -= glam::vec2(0.0, 0.0005);
}
self.particles.retain(|p| p.life > 0.0);
let pass = self.ctx.passes.last_mut().unwrap();
for pipeline in pass.pipelines.iter_mut() {
for drawcall in pipeline.drawcalls_mut() {
drawcall.clear();
for particle in &self.particles {
let q = particle.into_quaddata();
drawcall.add(q);
}
}
}
}
self.particles.len()
}
}
trait Window {
fn request_redraw(&self);
fn swap_buffers(&self);
}
#[cfg(not(target_arch = "wasm32"))]
impl Window for glutin::WindowedContext<glutin::PossiblyCurrent> {
fn request_redraw(&self) {
self.window().request_redraw();
}
fn swap_buffers(&self) {
self.swap_buffers().unwrap();
}
}
#[cfg(target_arch = "wasm32")]
impl Window for winit::window::Window {
fn request_redraw(&self) {
self.request_redraw();
}
fn swap_buffers(&self) {
}
}
fn mainloop(
gl: glow::Context,
event_loop: winit::event_loop::EventLoop<()>,
window: impl Window + 'static,
) {
use instant::Instant;
use log::*;
use winit::event::{Event, WindowEvent};
use winit::event_loop::ControlFlow;
let mut state = GameState::new(gl);
let (vend, rend, vers, shader_vers) = state.ctx.get_info();
info!(
"GL context created.
Vendor: {}
Renderer: {}
Version: {}
Shader version: {}",
vend, rend, vers, shader_vers
);
{
let mut frames = 0;
let target_dt = Duration::from_micros(10_000);
let mut last_frame = Instant::now();
let mut next_frame = last_frame + target_dt;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::WaitUntil(next_frame);
match event {
Event::LoopDestroyed => {
info!("Event::LoopDestroyed!");
return;
}
Event::EventsCleared => {
let now = Instant::now();
let dt = now - last_frame;
if dt >= target_dt {
let num_objects = state.update(dt);
last_frame = now;
next_frame = now + target_dt;
frames += 1;
const FRAMES: u32 = 100;
if frames % FRAMES == 0 {
let fps = 1.0 / dt.as_secs_f64();
info!("{} objects, {:.03} fps", num_objects, fps);
}
window.request_redraw();
}
}
Event::WindowEvent { ref event, .. } => match event {
WindowEvent::Resized(logical_size) => {
info!("WindowEvent::Resized: {:?}", logical_size);
let dpi_factor = 1.0;
let physical_size = logical_size.to_physical(dpi_factor);
state.ctx.set_screen_viewport(
0,
0,
physical_size.width as i32,
physical_size.height as i32,
);
}
WindowEvent::RedrawRequested => {
state.ctx.draw();
window.swap_buffers();
}
WindowEvent::CloseRequested => {
info!("WindowEvent::CloseRequested");
*control_flow = ControlFlow::Exit
}
WindowEvent::MouseInput {
button: winit::event::MouseButton::Left,
state: winit::event::ElementState::Pressed,
..
}
=> {
state.add_particles(glam::vec2(-0.5, -1.0));
}
_ => (),
},
_ => (),
}
});
}
}
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
fn run_wasm() {
console_error_panic_hook::set_once();
use winit::platform::web::WindowExtWebSys;
let event_loop = winit::event_loop::EventLoop::new();
let win = winit::window::WindowBuilder::new()
.with_inner_size(winit::dpi::LogicalSize::new(800.0, 600.0))
.with_title("Heckin' winit")
.build(&event_loop)
.unwrap();
let document = web_sys::window()
.expect("Failed to obtain window")
.document()
.expect("Failed to obtain document");
document
.body()
.expect("Failed to obtain body")
.append_child(&win.canvas())
.unwrap();
let gl = {
use wasm_bindgen::JsCast;
let webgl2_context = win
.canvas()
.get_context("webgl2")
.unwrap()
.unwrap()
.dyn_into::<web_sys::WebGl2RenderingContext>()
.unwrap();
glow::Context::from_webgl2_context(webgl2_context)
};
mainloop(gl, event_loop, win);
}
#[cfg(not(target_arch = "wasm32"))]
fn run_glutin() {
use log::*;
pretty_env_logger::init();
unsafe {
let (gl, event_loop, windowed_context) = {
let el = glutin::event_loop::EventLoop::new();
let wb = glutin::window::WindowBuilder::new()
.with_title("Hello triangle!")
.with_inner_size(glutin::dpi::LogicalSize::new(800.0, 600.0));
let windowed_context = glutin::ContextBuilder::new()
.with_gl(glutin::GlRequest::GlThenGles {
opengl_version: (4, 3),
opengles_version: (3, 0),
})
.with_gl_profile(glutin::GlProfile::Core)
.with_vsync(true)
.build_windowed(wb, &el)
.unwrap();
let windowed_context = windowed_context.make_current().unwrap();
let context = glow::Context::from_loader_function(|s| {
windowed_context.get_proc_address(s) as *const _
});
(context, el, windowed_context)
};
trace!("Window created");
mainloop(gl, event_loop, windowed_context);
}
}
pub fn main() {
#[cfg(target_arch = "wasm32")]
run_wasm();
#[cfg(not(target_arch = "wasm32"))]
run_glutin();
}
#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]
pub fn wasm_main() {
main();
}