Created
February 18, 2020 19:45
-
-
Save pcwalton/cb73fecc22d131ad6b106772d24cd686 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// pathfinder/examples/canvas_nanovg/src/main.rs | |
// | |
// Copyright © 2020 The Pathfinder Project Developers. | |
// | |
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
// option. This file may not be copied, modified, or distributed | |
// except according to those terms. | |
use arrayvec::ArrayVec; | |
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, FillStyle, Path2D}; | |
use pathfinder_color::{ColorF, ColorU}; | |
use pathfinder_content::fill::FillRule; | |
use pathfinder_content::gradient::{ColorStop, Gradient}; | |
use pathfinder_geometry::line_segment::LineSegment2F; | |
use pathfinder_geometry::rect::RectF; | |
use pathfinder_geometry::vector::{Vector2F, Vector2I}; | |
use pathfinder_gl::{GLDevice, GLVersion}; | |
use pathfinder_gpu::resources::FilesystemResourceLoader; | |
use pathfinder_renderer::concurrent::rayon::RayonExecutor; | |
use pathfinder_renderer::concurrent::scene_proxy::SceneProxy; | |
use pathfinder_renderer::gpu::options::{DestFramebuffer, RendererOptions}; | |
use pathfinder_renderer::gpu::renderer::Renderer; | |
use pathfinder_renderer::options::BuildOptions; | |
use sdl2::event::Event; | |
use sdl2::keyboard::Keycode; | |
use sdl2::video::GLProfile; | |
use std::f32::consts::PI; | |
use std::time::Instant; | |
fn render_demo(canvas: &mut CanvasRenderingContext2D, | |
mouse_position: Vector2F, | |
window_size: Vector2F, | |
time: f32) { | |
draw_eyes(canvas, | |
RectF::new(Vector2F::new(window_size.x() - 250.0, 50.0), | |
Vector2F::new(150.0, 100.0)), | |
mouse_position, | |
time); | |
draw_graph(canvas, | |
RectF::new(window_size.scale_xy(Vector2F::new(0.0, 0.5)), | |
window_size.scale_xy(Vector2F::new(1.0, 0.5))), | |
time); | |
} | |
fn draw_eyes(canvas: &mut CanvasRenderingContext2D, | |
rect: RectF, | |
mouse_position: Vector2F, | |
time: f32) { | |
let eyes_radii = rect.size().scale_xy(Vector2F::new(0.23, 0.5)); | |
let eyes_left_position = rect.origin() + eyes_radii; | |
let eyes_right_position = rect.origin() + Vector2F::new(rect.width() - eyes_radii.x(), | |
eyes_radii.y()); | |
let eyes_center = f32::min(eyes_radii.x(), eyes_radii.y()) * 0.5; | |
let blink = 1.0 - f32::powf(f32::sin(time * 0.5), 200.0) * 0.8; | |
let mut gradient = | |
Gradient::linear(LineSegment2F::new(Vector2F::new(0.0, rect.height() * 0.5), | |
rect.size().scale_xy(Vector2F::new(0.1, 1.0))) + | |
rect.origin()); | |
gradient.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 32), 0.0)); | |
gradient.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 16), 1.0)); | |
let mut path = Path2D::new(); | |
path.ellipse(eyes_left_position + Vector2F::new(3.0, 16.0), | |
eyes_radii, | |
0.0, | |
0.0, | |
PI * 2.0); | |
path.ellipse(eyes_right_position + Vector2F::new(3.0, 16.0), | |
eyes_radii, | |
0.0, | |
0.0, | |
PI * 2.0); | |
canvas.set_fill_style(FillStyle::Gradient(gradient)); | |
canvas.fill_path(path, FillRule::Winding); | |
let mut gradient = | |
Gradient::linear(LineSegment2F::new(Vector2F::new(0.0, rect.height() * 0.25), | |
rect.size().scale_xy(Vector2F::new(0.1, 1.0))) + | |
rect.origin()); | |
gradient.add_color_stop(ColorStop::new(ColorU::new(220, 220, 220, 255), 0.0)); | |
gradient.add_color_stop(ColorStop::new(ColorU::new(128, 128, 128, 255), 1.0)); | |
let mut path = Path2D::new(); | |
path.ellipse(eyes_left_position, eyes_radii, 0.0, 0.0, PI * 2.0); | |
path.ellipse(eyes_right_position, eyes_radii, 0.0, 0.0, PI * 2.0); | |
canvas.set_fill_style(FillStyle::Gradient(gradient)); | |
canvas.fill_path(path, FillRule::Winding); | |
let mut delta = (mouse_position - eyes_right_position) / eyes_radii.scale(10.0); | |
let distance = delta.length(); | |
if distance > 1.0 { | |
delta = delta.scale(1.0 / distance); | |
} | |
delta = delta.scale_xy(eyes_radii).scale_xy(Vector2F::new(0.4, 0.5)); | |
let mut path = Path2D::new(); | |
path.ellipse(eyes_left_position + | |
delta + | |
Vector2F::new(0.0, eyes_radii.y() * 0.25 * (1.0 - blink)), | |
Vector2F::new(eyes_center, eyes_center * blink), | |
0.0, | |
0.0, | |
PI * 2.0); | |
path.ellipse(eyes_right_position + | |
delta + | |
Vector2F::new(0.0, eyes_radii.y() * 0.25 * (1.0 - blink)), | |
Vector2F::new(eyes_center, eyes_center * blink), | |
0.0, | |
0.0, | |
PI * 2.0); | |
canvas.set_fill_style(FillStyle::Color(ColorU::new(32, 32, 32, 255))); | |
canvas.fill_path(path, FillRule::Winding); | |
let gloss_position = eyes_left_position - eyes_radii.scale_xy(Vector2F::new(0.25, 0.5)); | |
let mut gloss = Gradient::radial(LineSegment2F::new(gloss_position, gloss_position), | |
eyes_radii.x() * 0.1, | |
eyes_radii.x() * 0.75); | |
gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 128), 0.0)); | |
gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 0), 1.0)); | |
canvas.set_fill_style(FillStyle::Gradient(gloss)); | |
let mut path = Path2D::new(); | |
path.ellipse(eyes_left_position, eyes_radii, 0.0, 0.0, PI * 2.0); | |
canvas.fill_path(path, FillRule::Winding); | |
let gloss_position = eyes_right_position - eyes_radii.scale_xy(Vector2F::new(0.25, 0.5)); | |
let mut gloss = Gradient::radial(LineSegment2F::new(gloss_position, gloss_position), | |
eyes_radii.x() * 0.1, | |
eyes_radii.x() * 0.75); | |
gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 128), 0.0)); | |
gloss.add_color_stop(ColorStop::new(ColorU::new(255, 255, 255, 0), 1.0)); | |
canvas.set_fill_style(FillStyle::Gradient(gloss)); | |
let mut path = Path2D::new(); | |
path.ellipse(eyes_right_position, eyes_radii, 0.0, 0.0, PI * 2.0); | |
canvas.fill_path(path, FillRule::Winding); | |
} | |
fn draw_graph(canvas: &mut CanvasRenderingContext2D, rect: RectF, time: f32) { | |
let sample_spread = rect.width() / 5.0; | |
let samples = [ | |
(1.0 + f32::sin(time * 1.2345 + f32::cos(time * 0.33457) * 0.44)) * 0.5, | |
(1.0 + f32::sin(time * 0.68363 + f32::cos(time * 1.30) * 1.55)) * 0.5, | |
(1.0 + f32::sin(time * 1.1642 + f32::cos(time * 0.33457) * 1.24)) * 0.5, | |
(1.0 + f32::sin(time * 0.56345 + f32::cos(time * 1.63) * 0.14)) * 0.5, | |
(1.0 + f32::sin(time * 1.6245 + f32::cos(time * 0.254) * 0.3)) * 0.5, | |
(1.0 + f32::sin(time * 0.345 + f32::cos(time * 0.03) * 0.6)) * 0.5, | |
]; | |
let sample_scale = Vector2F::new(sample_spread, rect.height() * 0.8); | |
let sample_points: ArrayVec<[Vector2F; 6]> = samples.iter() | |
.enumerate() | |
.map(|(index, &sample)| { | |
rect.origin() + Vector2F::new(index as f32, sample).scale_xy(sample_scale) | |
}).collect(); | |
// Draw graph background. | |
let mut background = Gradient::linear(LineSegment2F::new(Vector2F::default(), | |
Vector2F::new(0.0, rect.height())) + | |
rect.origin()); | |
background.add_color_stop(ColorStop::new(ColorU::new(0, 160, 192, 0), 0.0)); | |
background.add_color_stop(ColorStop::new(ColorU::new(0, 160, 192, 64), 1.0)); | |
canvas.set_fill_style(FillStyle::Gradient(background)); | |
let mut path = create_graph_path(&sample_points, sample_spread, Vector2F::default()); | |
path.line_to(rect.lower_right()); | |
path.line_to(rect.lower_left()); | |
canvas.fill_path(path, FillRule::Winding); | |
// Draw graph line shadow. | |
canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 0, 0, 32))); | |
canvas.set_line_width(3.0); | |
let path = create_graph_path(&sample_points, sample_spread, Vector2F::new(0.0, 2.0)); | |
canvas.stroke_path(path); | |
// Draw graph line. | |
canvas.set_stroke_style(FillStyle::Color(ColorU::new(0, 160, 192, 255))); | |
canvas.set_line_width(3.0); | |
let path = create_graph_path(&sample_points, sample_spread, Vector2F::default()); | |
canvas.stroke_path(path); | |
// Draw sample position highlights. | |
for &sample_point in &sample_points { | |
let gradient_center = sample_point + Vector2F::new(0.0, 2.0); | |
let mut background = Gradient::radial(LineSegment2F::new(gradient_center, gradient_center), | |
3.0, | |
8.0); | |
background.add_color_stop(ColorStop::new(ColorU::new(0, 0, 0, 32), 0.0)); | |
background.add_color_stop(ColorStop::new(ColorU::transparent_black(), 1.0)); | |
canvas.set_fill_style(FillStyle::Gradient(background)); | |
canvas.fill_rect(RectF::new(sample_point + Vector2F::new(-10.0, -10.0 + 2.0), | |
Vector2F::splat(20.0))); | |
} | |
// Draw sample positions. | |
let mut path = Path2D::new(); | |
for &sample_point in &sample_points { | |
// TODO(pcwalton) | |
} | |
} | |
fn create_graph_path(sample_points: &[Vector2F], sample_spread: f32, offset: Vector2F) -> Path2D { | |
let mut path = Path2D::new(); | |
path.move_to(sample_points[0] + Vector2F::new(0.0, 2.0)); | |
for pair in sample_points.windows(2) { | |
path.bezier_curve_to(pair[0] + offset + Vector2F::new(sample_spread * 0.5, 0.0), | |
pair[1] + offset - Vector2F::new(sample_spread * 0.5, 0.0), | |
pair[1] + offset); | |
} | |
path | |
} | |
fn main() { | |
// Set up SDL2. | |
let sdl_context = sdl2::init().unwrap(); | |
let video = sdl_context.video().unwrap(); | |
// Make sure we have at least a GL 3.0 context. Pathfinder requires this. | |
let gl_attributes = video.gl_attr(); | |
gl_attributes.set_context_profile(GLProfile::Core); | |
gl_attributes.set_context_version(3, 3); | |
// Open a window. | |
let window_size = Vector2I::new(640, 480); | |
let window = | |
video.window("NanoVG example port", window_size.x() as u32, window_size.y() as u32) | |
.opengl() | |
.build() | |
.unwrap(); | |
// Create the GL context, and make it current. | |
let gl_context = window.gl_create_context().unwrap(); | |
gl::load_with(|name| video.gl_get_proc_address(name) as *const _); | |
window.gl_make_current(&gl_context).unwrap(); | |
// Create a Pathfinder renderer. | |
let mut renderer = Renderer::new(GLDevice::new(GLVersion::GL3, 0), | |
&FilesystemResourceLoader::locate(), | |
DestFramebuffer::full_window(window_size), | |
RendererOptions { | |
background_color: Some(ColorF::new(0.3, 0.3, 0.32, 1.0)), | |
}); | |
// Initialize state. | |
let mut event_pump = sdl_context.event_pump().unwrap(); | |
let mut mouse_position = Vector2F::default(); | |
let start_time = Instant::now(); | |
// Enter the main loop. | |
loop { | |
// Make a canvas. | |
let mut canvas = CanvasRenderingContext2D::new(CanvasFontContext::from_system_source(), | |
window_size.to_f32()); | |
// Render the demo. | |
let time = (Instant::now() - start_time).as_secs_f32(); | |
render_demo(&mut canvas, mouse_position, window_size.to_f32(), time); | |
// Render the canvas to screen. | |
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor); | |
scene.build_and_render(&mut renderer, BuildOptions::default()); | |
window.gl_swap_window(); | |
for event in event_pump.poll_iter() { | |
match event { | |
Event::Quit {..} | Event::KeyDown { keycode: Some(Keycode::Escape), .. } => return, | |
Event::MouseMotion { x, y, .. } => { | |
mouse_position = Vector2I::new(x, y).to_f32(); | |
} | |
_ => {} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment