Skip to content

Commit

Permalink
Simplify API even further
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanisaacg committed Aug 2, 2020
1 parent 2928a70 commit bcb33de
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 61 deletions.
9 changes: 1 addition & 8 deletions examples/04_render_to_texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,16 @@ async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()>
&gfx,
Image::from_raw(&gfx, None, 512, 512, PixelFormat::RGBA)?,
)?;
// We can use projections to add letterboxing around the content, split-screen,
// picture-in-picture, etc. For now we'll just change the size of our rendering area.
// The projection controls how coordinates map to the drawing target
// Here, we're mapping the coordinates one-to-one from our coordinates to the Surface
gfx.fit_projection(surface.size().unwrap());
// Draw a circle inside a rectangle
gfx.fill_rect(
&Rectangle::new(Vector::new(350.0, 100.0), Vector::new(100.0, 100.0)),
&Rectangle::new(Vector::new(0.0, 0.0), Vector::new(100.0, 100.0)),
Color::RED,
);
gfx.fill_circle(&Circle::new(Vector::new(400.0, 150.0), 50.0), Color::BLACK);
// Flush to the surface, which draws to the image
gfx.flush_surface(&surface)?;

gfx.clear(Color::BLACK);
// Now we're going to set the projection again, this time to the size of the window
gfx.fit_projection(window.size());
// Extract the image from the surface to use
let image = surface.detach().expect("The image failed to detach");
// Draw that image to the screen twice
Expand Down
3 changes: 2 additions & 1 deletion examples/08_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()>
Color::BLUE,
);
// Paint a red square at the mouse position
gfx.fill_circle(&Circle::new(input.mouse().location(), 32.0), Color::RED);
let mouse = gfx.screen_to_world(&window, input.mouse().location());
gfx.fill_circle(&Circle::new(mouse, 32.0), Color::RED);
gfx.present(&window)?;
}
}
24 changes: 5 additions & 19 deletions examples/10_resize.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// Example 10: Resize Handling
// Show the different ways of resizing the window
use quicksilver::{
geom::{Rectangle, Transform, Vector},
geom::{Rectangle, Vector},
graphics::{Color, Element, Graphics, Mesh, ResizeHandler, Vertex},
input::Event,
run, Input, Result, Settings, Window,
};

Expand Down Expand Up @@ -50,33 +49,20 @@ async fn app(window: Window, mut gfx: Graphics, mut input: Input) -> Result<()>
};
// Create a ResizeHandler that will Fit the content to the screen, leaving off area if we need
// to. Here, we provide an aspect ratio of 4:3.
// By default, a ResizeHandler::Fit is applied with the same aspect ratio as the initial size
let resize_handler = ResizeHandler::Fit {
aspect_width: 4.0,
aspect_height: 3.0,
};
let screen = Rectangle::new_sized(SIZE);
// If we want to handle resizes, we'll be setting the 'projection.' This is a transformation
// applied to eveyrthing we draw. By default, the projection is an 'orthographic' view of our
// window size. This means it takes a rectangle equal to the size of our window and transforms
// those coordinates to draw correctly on the screen.
let projection = Transform::orthographic(screen);
gfx.set_resize_handler(resize_handler);
loop {
while let Some(ev) = input.next_event().await {
if let Event::Resized(ev) = ev {
// Using our resize handler from above, create a transform that will correctly fit
// our content to the screen size
let letterbox = resize_handler.projection(ev.size());
// Apply our projection (convert content coordinates to screen coordinates) and
// then the letterbox (fit the content correctly on the screen)
gfx.set_projection(letterbox * projection);
}
}
while let Some(_) = input.next_event().await {}
gfx.clear(Color::BLACK);
// Fill the relevant part of the screen with white
// This helps us determine what part of the screen is the black bars, and what is the
// background. If we wanted white bars and a black background, we could simply clear to
// Color::WHITE and fill a rectangle of Color::BLACK
gfx.fill_rect(&screen, Color::WHITE);
gfx.fill_rect(&Rectangle::new_sized(SIZE), Color::WHITE);
// Draw the RGB triangle, which lets us see the squash and stress caused by resizing
gfx.draw_mesh(&mesh);
gfx.present(&window)?;
Expand Down
105 changes: 78 additions & 27 deletions src/graphics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,20 @@ pub struct Graphics {
vertex_data: Vec<f32>,
index_data: Vec<u32>,
image_changes: Vec<(usize, Image)>,
projection_changes: Vec<(usize, Transform)>,
view_changes: Vec<(usize, Transform)>,
geom_mode_changes: Vec<(usize, GeometryMode)>,
clear_changes: Vec<(usize, Color)>,
blend_mode_changes: Vec<(usize, Option<blend::BlendMode>)>,
transform: Transform,
resize: ResizeHandler,
world_size: Vector,
projection: Transform,
}

const VERTEX_SIZE: usize = 8;

impl Graphics {
pub(crate) fn new(ctx: Context) -> Result<Graphics, QuicksilverError> {
pub(crate) fn new(ctx: Context, world_size: Vector) -> Result<Graphics, QuicksilverError> {
use Dimension::*;
let mut shader = ShaderProgram::new(
&ctx,
Expand All @@ -105,9 +108,10 @@ impl Graphics {
uniforms: &[
Uniform::new("image", UniformType::Sampler2D),
Uniform::new("projection", UniformType::Matrix(D3)),
Uniform::new("view", UniformType::Matrix(D3)),
],
vertex_shader: r#" void main() {
vec3 transformed = projection * vec3(vert_position, 1.0);
vec3 transformed = projection * view * vec3(vert_position, 1.0);
gl_Position = vec4(transformed.xy, 0, 1);
frag_uv = vert_uv;
frag_color = vert_color;
Expand All @@ -134,11 +138,17 @@ impl Graphics {
vertex_data: Vec::new(),
index_data: Vec::new(),
image_changes: Vec::new(),
projection_changes: Vec::new(),
view_changes: Vec::new(),
geom_mode_changes: Vec::new(),
clear_changes: Vec::new(),
blend_mode_changes: Vec::new(),
transform: Transform::IDENTITY,
resize: ResizeHandler::Fit {
aspect_width: world_size.x,
aspect_height: world_size.y
},
world_size,
projection: Transform::IDENTITY,
})
}

Expand All @@ -162,30 +172,58 @@ impl Graphics {
self.clear_changes.push((head, color));
}

/// Set the projection matrix, which is applied to all vertices on the GPU
/// Set the view matrix, which is applied to all vertices on the GPU
///
/// Use this to determine the drawable area with [`Transform::orthographic`], which is
/// important to handling resizes.
pub fn set_projection(&mut self, transform: Transform) {
/// Most of the time, you won't need this at all. However, if you want to apply a change to a
/// great many objects (screen shake, rotations, etc.) setting the view matrix is a good way to
/// do that.
pub fn set_view(&mut self, transform: Transform) {
let head = self.index_data.len();
self.projection_changes.push((head, transform));
}

/// Set the projection to a Rectangle of the given size
///
/// When setting the projection to display a rectangle of a given size, this is a convenient
/// helper method.
pub fn fit_projection(&mut self, size: Vector) {
self.set_projection(Transform::orthographic(Rectangle::new_sized(size)));
self.view_changes.push((head, transform));
}

/// Set the transformation matrix, which is applied to all vertices on the CPU
///
/// Use this to rotate, scale, or translate individual draws or small groups of draws
/// Use this to rotate, scale, or translate individual draws or small groups of draws.
pub fn set_transform(&mut self, transform: Transform) {
self.transform = transform;
}

/// Project a point from the screen to the world
///
/// Use this when checking the mouse position against rendered objects, like a game or UI
pub fn screen_to_world(&self, window: &Window, position: Vector) -> Vector {
let viewport = self.calculate_viewport(window);
let mut projected = position - viewport.top_left();

projected.x *= self.world_size.x / viewport.width();
projected.y *= self.world_size.y / viewport.height();

projected
}

/// Set the size of the virtual window
///
/// Regardless of the size of the actual window, the draw functions all work on a virtual
/// window size. By default, this is the initial size in your Settings. If you start at
/// 400x300, a 400x300 Rectangle will fill the drawable area. If the Window is doubled in size,
/// a 400x300 Rectangle will still fill the drawable area. This function changes the size of
/// the 'virtual window.'
pub fn set_world_size(&mut self, size: Vector) {
self.world_size = size;
}

/// Change how to respond to the window resizing
///
/// The default method of handling resizes is `ResizeHandler::Fit`, which maximizes the area
/// drawn on screen while maintaining aspect ratio. There are a variety of other
/// [`ResizeHandler`] options to choose from.
///
/// [`ResizeHandler`]: crate::graphics::ResizeHandler
pub fn set_resize_handler(&mut self, resize: ResizeHandler) {
self.resize = resize;
}

/// Set the blend mode, which determines how pixels mix when drawn over each other
///
/// Pass `None` to disable blending entirely
Expand Down Expand Up @@ -436,6 +474,9 @@ impl Graphics {
pub fn flush_surface(&mut self, surface: &Surface) -> Result<(), QuicksilverError> {
if let (Some(width), Some(height)) = (surface.0.width(), surface.0.height()) {
self.ctx.set_viewport(0, 0, width, height);
let flip = Transform::scale(Vector::new(1.0, -1.0));
let ortho = Transform::orthographic(Rectangle::new_sized(Vector::new(width as f32, height as f32)));
self.projection = flip * ortho;
} else {
return Err(QuicksilverError::NoSurfaceImageBound);
}
Expand All @@ -444,12 +485,19 @@ impl Graphics {
Ok(())
}

fn calculate_viewport(&self, window: &Window) -> Rectangle {
let size = self.resize.content_size(window.size());
Rectangle::new((window.size() - size) / 2.0, size)
}

pub fn flush_window(&mut self, window: &Window) -> Result<(), QuicksilverError> {
let size = window.size();
let scale = window.scale_factor();
let width = size.x * scale;
let height = size.y * scale;
self.ctx.set_viewport(0, 0, width as u32, height as u32);
self.projection = Transform::orthographic(Rectangle::new_sized(self.world_size));
let viewport = self.calculate_viewport(window);
let offset = viewport.top_left() * window.scale_factor();
let size = viewport.size() * window.scale_factor();
dbg!(offset);
dbg!(size);
self.ctx.set_viewport(offset.x as u32, offset.y as u32, size.x as u32, size.y as u32);
golem::Surface::unbind(&self.ctx);
self.flush_gpu()?;
Ok(())
Expand Down Expand Up @@ -485,14 +533,17 @@ impl Graphics {

self.shader
.set_uniform("image", UniformValue::Int(TEX_BIND_POINT as i32))?;
self.shader
.set_uniform("projection", UniformValue::Matrix3(Self::transform_to_gl(self.projection)))?;

let mut previous = 0;
let mut element_mode = GeometryMode::Triangles;
let change_list = join_change_lists(
join_change_lists(
join_change_lists(
join_change_lists(
self.image_changes.drain(..),
self.projection_changes.drain(..),
self.view_changes.drain(..),
),
self.geom_mode_changes.drain(..),
),
Expand All @@ -518,10 +569,10 @@ impl Graphics {
image.raw().set_active(bind_point);
}
// If we're switching what projection to use, do so now
if let Some(projection) = changes.1 {
let matrix = Self::transform_to_gl(projection);
if let Some(view) = changes.1 {
let matrix = Self::transform_to_gl(view);
self.shader
.set_uniform("projection", UniformValue::Matrix3(matrix))?;
.set_uniform("view", UniformValue::Matrix3(matrix))?;
}
}
// If we're switching the element mode, do so now
Expand Down
9 changes: 3 additions & 6 deletions src/run.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::geom::{Rectangle, Transform, Vector};
use crate::geom::{Transform, Vector};
use crate::graphics::Graphics;
use crate::input::Input;
use std::error::Error;
Expand Down Expand Up @@ -75,9 +75,6 @@ where
#[cfg(feature = "easy-log")]
set_logger(settings.log_level);

let size = settings.size;
let screen_region = Rectangle::new_sized(size);

blinds::run_gl((&settings).into(), move |window, ctx, events| {
#[cfg(not(target_arch = "wasm32"))]
{
Expand All @@ -88,8 +85,8 @@ where
}

let ctx = golem::Context::from_glow(ctx).unwrap();
let mut graphics = Graphics::new(ctx).unwrap();
graphics.set_projection(Transform::orthographic(screen_region));
let mut graphics = Graphics::new(ctx, settings.size).unwrap();
graphics.set_view(Transform::IDENTITY);

async {
match app(crate::Window(window), graphics, Input::new(events)).await {
Expand Down

0 comments on commit bcb33de

Please sign in to comment.