Skip to content

Instantly share code, notes, and snippets.

@pcwalton
Created February 18, 2020 19:45
Show Gist options
  • Save pcwalton/cb73fecc22d131ad6b106772d24cd686 to your computer and use it in GitHub Desktop.
Save pcwalton/cb73fecc22d131ad6b106772d24cd686 to your computer and use it in GitHub Desktop.
// 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