Efficiently draw paths using piston - rust

Piston's graphics library provides a function for drawing a line between two points, but nothing for more than two. How do I efficiently draw a path through many points without having to draw a line for every segment?
Let's say I have the following code:
extern crate piston_window;
use piston_window::*;
fn main() {
let mut window: PistonWindow = WindowSettings::new("Hello Piston!", [640, 480])
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
clear([1.0; 4], g);
let points = [
[100., 100.],
[200., 200.],
[150., 350.],
//...
];
let mut prev = points[0];
for pt in points[1..].iter() {
line([0., 0., 0., 255.], 1., [
prev[0], prev[1], pt[0], pt[1]
], c.transform, g);
prev = *pt;
}
});
}
}
Is there a way to turn it into something like this?
extern crate piston_window;
use piston_window::*;
fn main() {
let mut window: PistonWindow = WindowSettings::new("Hello Piston!", [640, 480])
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
clear([1.0; 4], g);
let points = [
[100., 100.],
[200., 200.],
[150., 350.],
//...
];
path([0., 0., 0., 255.], 1., &points, c.transform, g);
});
}
}
I was referred to the lyon library but I don't know how to use it with piston.

How do I efficiently draw a path through many points without having to draw a line for every segment?
I'm not hugely familiar with Piston, but I would question your assumption that line() has some overhead that makes it inefficient to call repeatedly. In the end, whether you draw lots of lines or a library function draws them they will get drawn and there shouldn't be much difference in performance.
It doesn't look like there is currently a method to draw a sequence of lines between points, but it should just be a matter of looping through them as you were already doing. Using Vec::windows is a bit nicer though, as you don't need temporary mutable variables:
pub fn path<G>(color: Color, radius: Radius, points: &[[Scalar; 2]], transform: Matrix2d, g: &mut G)
where
G: Graphics,
{
for w in points.windows(2) {
line(
color,
radius,
[w[0][0], w[0][1], w[1][0], w[1][1]],
transform,
g,
);
}
}
You might consider making a feature request or a PR to the Piston project.

Related

Slow animation with Nannou

I'm trying to learn Rust and Nannou, and I'm confused why this animation is so slow with only 1000 elements. I was hoping generating animations in Rust would be lighting fast, but not off to a great start.
use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
particles: Vec<f32>,
}
fn model(_app: &App) -> Model {
let n_particles = 1000;
let mut particles: Vec<f32> = vec![];
for idx in 0..n_particles {
particles.push(map_range(idx, 0, n_particles, 0.0, 1.0));
}
Model { particles }
}
fn update(_app: &App, model: &mut Model, _update: Update) {}
fn view(app: &App, model: &Model, frame: Frame) {
// Prepare to draw.
let draw = app.draw();
// Get boundary of the window (to constrain the movements of our circle)
let boundary = app.window_rect();
// Clear the background to purple.
draw.background().color(PLUM);
for idx in 0..model.particles.len() {
let particle = &model.particles[idx];
let sine = app.time.sin() * particle;
let slowersine = (app.time / 2.0).sin() * particle;
// Map the sine wave functions to ranges between the boundaries of the window
let x = map_range(sine, -1.0, 1.0, boundary.left(), boundary.right());
let y = map_range(slowersine, -1.0, 1.0, boundary.bottom(), boundary.top());
// Draw a blue ellipse at the x/y coordinates 0.0, 0.0
draw.ellipse().color(STEELBLUE).x_y(x, y).radius(10.0);
}
// Generate sine wave data based on the time of the app
draw.to_frame(app, &frame).unwrap();
}
I do not think your code is doing what you are expecting. The use of "particle" in the view function seems superfluous when it is ultimately only being used to affect the x and y values passed to the x_y function.
Running your code as-is gave me an unexpected result: It looked like the ellipse was rendered multiple times creating an animated line waving around the screen. Commenting out several lines and removing "* particle" from the "sine" and "slowersine" assignments resulted in an ellipse moving around the window smoothly.
Note: Several warnings are generated due to unused variables now.
Updated code:
use nannou::prelude::*;
fn main() {
nannou::app(model).update(update).simple_window(view).run();
}
struct Model {
particles: Vec<f32>,
}
fn model(_app: &App) -> Model {
let n_particles = 1000;
let mut particles: Vec<f32> = vec![];
for idx in 0..n_particles {
particles.push(map_range(idx, 0, n_particles, 0.0, 1.0));
}
Model { particles }
}
fn update(_app: &App, model: &mut Model, _update: Update) {}
fn view(app: &App, model: &Model, frame: Frame) {
// Prepare to draw.
let draw = app.draw();
// Get boundary of the window (to constrain the movements of our circle)
let boundary = app.window_rect();
// Clear the background to purple.
draw.background().color(PLUM);
// for idx in 0..model.particles.len() {
// let particle = &model.particles[idx];
// let sine = app.time.sin() * particle;
// let slowersine = (app.time / 2.0).sin() * particle;
let sine = app.time.sin();
let slowersine = (app.time / 2.0).sin();
// Map the sine wave functions to ranges between the boundaries of the window
let x = map_range(sine, -1.0, 1.0, boundary.left(), boundary.right());
let y = map_range(slowersine, -1.0, 1.0, boundary.bottom(), boundary.top());
// Draw a blue ellipse at the x/y coordinates 0.0, 0.0
draw.ellipse().color(STEELBLUE).x_y(x, y).radius(10.0);
// }
// Generate sine wave data based on the time of the app
draw.to_frame(app, &frame).unwrap();
}

get_value returning `f64` instead of `[u8; 4]`

I'm using the noise crate and having trouble understanding how to convert their Color type to an RGB value.
noise = "0.7.0"
pub type Color = [u8; 4];
I'm trying to use the get_value() function, seen here in the docs as:
pub fn get_value(&self, x: usize, y: usize) -> Color {
let (width, height) = self.size;
if x < width && y < height {
self.map[x + y * width]
} else {
self.border_color
}
}
get_value() is implemented for PlaneMapBuilder. So I would expect PlaneMapBuilder::get_value(x,y) to return something of the format [r,g,b,a], but this does not happen:
extern crate noise;
use noise::{utils::*, Billow};
fn main() {
let mut my_noise = PlaneMapBuilder::new(&Billow::new()).build();
let my_val = my_noise.get_value(1,1);
println!("{}", my_val.to_string());
///returns something like -0.610765515150546, not a [u8;4] as I would expect
}
In the docs I see this definition of add_gradient_point() which takes a Color as a parameter:
pub fn add_gradient_point(mut self, pos: f64, color: Color) -> Self {
// check to see if the vector already contains the input point.
if !self
.gradient_points
.iter()
.any(|&x| (x.pos - pos).abs() < std::f64::EPSILON)
{
// it doesn't, so find the correct position to insert the new
// control point.
let insertion_point = self.find_insertion_point(pos);
// add the new control point at the correct position.
self.gradient_points
.insert(insertion_point, GradientPoint { pos, color });
}
self
}
Here they use the [u8; 4] structure I would expect for the Color type:
let jade_gradient = ColorGradient::new()
.clear_gradient()
.add_gradient_point(-1.000, [24, 146, 102, 255])
.add_gradient_point(0.000, [78, 154, 115, 255])
What could explain this behavior?
get_value() is implemented for PlaneMapBuilder
You are correct that PlaneMapBuilder "implements" get_value(). However, it is not get_value() from NoiseImage. It is actually NoiseMap, where its get_value() returns a f64 and not Color.
Depending on what kind of "colors" you'd want, then you could instead use ImageRenderer and call its render() method with &my_noise, which returns a NoiseImage.
// noise = "0.7.0"
use noise::{utils::*, Billow};
fn main() {
let my_noise = PlaneMapBuilder::new(&Billow::new()).build();
let image = ImageRenderer::new().render(&my_noise);
let my_val = image.get_value(1, 1);
println!("{:?}", my_val);
// Prints: `[18, 18, 18, 255]`
}
Here they use the [u8; 4] structure I would expect for the Color type
Just to be clear, those are the same thing in this case. In short the type keyword allows you to define new "type aliases" for an existing types. Essentially, you'd be able to give a complex type a shorthand name. However, they are still the same type.

Attempted to leave type `PointerState` uninitialized

I wanted to try to make a game in Rust using Piston. This is the first time I have used this library. I took this code from the official doc to test it. However, when my mouse touches the application window it closes immediately and I don’t understand why.
extern crate glutin_window;
extern crate graphics;
extern crate opengl_graphics;
extern crate piston;
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{GlGraphics, OpenGL};
use piston::event_loop::{EventSettings, Events};
use piston::input::{RenderArgs, RenderEvent, UpdateArgs, UpdateEvent};
use piston::window::WindowSettings;
pub struct App {
gl: GlGraphics, // OpenGL drawing backend.
rotation: f64, // Rotation for the square.
}
impl App {
fn render(&mut self, args: &RenderArgs) {
use graphics::*;
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
let square = rectangle::square(0.0, 0.0, 50.0);
let rotation = self.rotation;
let (x, y) = (args.window_size[0] / 2.0, args.window_size[1] / 2.0);
self.gl.draw(args.viewport(), |c, gl| {
// Clear the screen.
clear(GREEN, gl);
let transform = c
.transform
.trans(x, y)
.rot_rad(rotation)
.trans(-25.0, -25.0);
// Draw a box rotating around the middle of the screen.
rectangle(RED, square, transform, gl);
});
}
fn update(&mut self, args: &UpdateArgs) {
// Rotate 2 radians per second.
self.rotation += 2.0 * args.dt;
}
}
fn main() {
// Change this to OpenGL::V2_1 if not working.
let opengl = OpenGL::V3_2;
// Create an Glutin window.
let mut window: Window = WindowSettings::new("spinning-square", [200, 200])
.graphics_api(opengl)
.exit_on_esc(true)
.build()
.unwrap();
// Create a new game and run it.
let mut app = App {
gl: GlGraphics::new(opengl),
rotation: 0.0,
};
let mut events = Events::new(EventSettings::new());
while let Some(e) = events.next(&mut window) {
if let Some(args) = e.render_args() {
app.render(&args);
}
if let Some(args) = e.update_args() {
app.update(&args);
}
}
}
The error:
thread 'main' panicked at 'attempted to leave type `platform::platform::x11::util::input::PointerState` uninitialized, which is invalid', /home/zenmoa/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/mem/mod.rs:658:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This is apparently a bug in an old version of winit, which is fixed in the latest winit release. However, various crates e.g. amethyst, piston, ggez, etc. still use winit 0.19. In Rust 1.48.0 the issue has apparently manifested itself as a panic.
On the amethyst issue tracker a comment mentions, that for now a possible workaround is to revert back to Rust 1.47.0. If you're using rustup, then you can do that by executing the following command:
rustup default 1.47.0

How to add threading to a for loop in Rust?

I'm trying to write a raytracer in Rust. I'm having difficulty getting the for loops to run in parallel. I'm not sure where the problem is, but I can't seem to get anything on the screen. Is this the correct approach or am I completely heading in the wrong direction?
I've tried running the for loops without multi-threading and it does correctly produce output. I've also added loggers to the consumer loop and I'm getting the correct values as well. It just doesn't seem to update the window.
#[derive(Clone, Copy)]
pub struct Pixel {
pub x: usize,
pub y: usize,
pub color: Vec3,
}
let mut buffer : Vec<u32> = vec![0; WIDTH * HEIGHT];
let (tx, rx) = mpsc::channel()
for x in 0..HEIGHT {
let tx_t = tx.clone();
thread::spawn(move || {
for y in 0..WIDTH {
let mut color = cast_ray(x, y); // returns vec3
let pixel = Pixel { x: x, y: y, color: color };
tx_t.send(pixel).unwrap();
}
});
}
for received in rx {
buffer[received.x * WIDTH + received.y] = received.color.x << 16 | received.color.y << 8 | received.color.z;
}
while window.is_open() && !window.is_key_down(Key::Escape) {
window.update_with_buffer(&buffer).unwrap();
}
I'm expecting a few spheres or color to appear on the screen, but it's just black.

How do I stop Piston from making the screen flash when I don't call `graphics::clear` every time the screen is rendered?

Consider two programs, and the difference between them:
$ diff flashes/src/main.rs doesnt_flash/src/main.rs
22,23c22
<
< let mut i = 0;
---
> let mut cursor_poses: Vec<(f64, f64)> = Vec::new();
28c27
< mx = x; my = y;
---
> cursor_poses.push((x,y));
32,33c31,33
< if i == 0 {
< graphics::clear([1.0; 4], g);
---
> graphics::clear([1.0; 4], g);
> for &(x, y) in cursor_poses.iter() {
> draw_cursor_pos([x, y], &c, g);
35,36d34
< draw_cursor_pos([mx, my], &c, g);
< i+=1;
Video demonstration of the two programs.
The program is an extremely basic paint program, with only one brush width, brush stroke color, canvas size, no saving, etc; oh and to stop drawing, move your mouse out of the window, since every time you go over the window this counts as drawing ;-)
flashes.rs does not draw every pixel every time e.render_args() is reached, except the first time. doesnt_flash.rs does draw every pixel every time e.render_args() is reached. This is the only difference between the two programs.
While it does not take long to generate the content in this program, so it is acceptable to re-generate it hundreds of times as the mouse moves over the window, this seems inefficient. In theory, as more and more points are added to the screen, each iteration of gl.draw takes longer and longer. In practice, the difference between calling graphics::ellipse one time vs. ten thousand times is not significant on modern hardware.
Other programs I'd want to write won't have that luxury as it will take longer to generate the result to put on the screen.
While perusing the API, I came up with no obvious way to just "do nothing". I assume that I would have to write my screen changes to some buffer object, then feed GlGraphics back this buffer object if e.render_args() is called but I don't need to update the screen.
The problem is, I can't seem to find this buffer object. :-(
How can I "do nothing" without getting screen flashing? If my theory is correct, how can I draw to a GlGraphics buffer instead of the screen, then feed my buffer back to the screen when I don't have anything new to draw?
Cargo.toml
[package]
name = "stackoverflow-piston-example"
version = "0.0.0"
authors = ["Fred"]
description = "Note: This program can be used for both of the programs below. Simply use `cargo new` and save either of the below files as `src/main.rs`"
keywords = []
[dependencies]
piston = "0.35.0"
piston2d-opengl_graphics = "0.50.0"
piston2d-graphics = "0.24.0"
piston2d-touch_visualizer = "0.8.0"
pistoncore-sdl2_window = "0.47.0"
doesnt_flash.rs
extern crate piston;
extern crate opengl_graphics;
extern crate graphics;
extern crate touch_visualizer;
extern crate sdl2_window;
use opengl_graphics::{ GlGraphics, OpenGL };
use graphics::{ Context, Graphics };
use piston::input::*;
use piston::event_loop::*;
use sdl2_window::Sdl2Window as AppWindow;
static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
fn main() {
let opengl = OpenGL::V3_2;
let mut window: AppWindow = piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
.exit_on_esc(true).opengl(opengl).build().unwrap();
let ref mut gl = GlGraphics::new(opengl);
let (mut mx, mut my) = (0., 0.);
let mut cursor_poses: Vec<(f64, f64)> = Vec::new();
let mut events = Events::new(EventSettings::new().lazy(true));
while let Some(e) = events.next(&mut window) {
e.mouse_cursor(|x, y| {
cursor_poses.push((x,y));
});
if let Some(args) = e.render_args() {
gl.draw(args.viewport(), |c, g| {
graphics::clear([1.0; 4], g);
for &(x, y) in cursor_poses.iter() {
draw_cursor_pos([x, y], &c, g);
}
}
);
}
}
}
fn draw_cursor_pos<G: Graphics>(
cursor: [f64; 2],
c: &Context,
g: &mut G,
) {
graphics::ellipse(
CURSOR_POS_COLOR,
graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
c.transform,
g
);
}
flashes.rs
extern crate piston;
extern crate opengl_graphics;
extern crate graphics;
extern crate touch_visualizer;
extern crate sdl2_window;
use opengl_graphics::{ GlGraphics, OpenGL };
use graphics::{ Context, Graphics };
use piston::input::*;
use piston::event_loop::*;
use sdl2_window::Sdl2Window as AppWindow;
static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
fn main() {
let opengl = OpenGL::V3_2;
let mut window: AppWindow = piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
.exit_on_esc(true).opengl(opengl).build().unwrap();
let ref mut gl = GlGraphics::new(opengl);
let (mut mx, mut my) = (0., 0.);
let mut i = 0;
let mut events = Events::new(EventSettings::new().lazy(true));
while let Some(e) = events.next(&mut window) {
e.mouse_cursor(|x, y| {
mx = x; my = y;
});
if let Some(args) = e.render_args() {
gl.draw(args.viewport(), |c, g| {
if i == 0 {
graphics::clear([1.0; 4], g);
}
draw_cursor_pos([mx, my], &c, g);
i+=1;
}
);
}
}
}
fn draw_cursor_pos<G: Graphics>(
cursor: [f64; 2],
c: &Context,
g: &mut G,
) {
graphics::ellipse(
CURSOR_POS_COLOR,
graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
c.transform,
g
);
}
I think the flashing is caused by buffer swapping: in flashes.rs only the first buffer to be drawn into is cleared. The second one will be all zeros, or leftover gpu memory if you're unlucky. According to the OpenGL wiki there's no good way around calling graphics::clear:
A modern OpenGL program should always use double buffering. . .
The buffers should always be cleared. On much older hardware, there
was a technique to get away without clearing the scene, but on even
semi-recent hardware, this will actually make things slower. So always
do the clear.
Instead, the usual method is to accumulate your changes to a texture or renderbuffer, and then draw that to the screen, exactly as you described.
I couldn't find any way to do this from within opengl_graphics either (there are no calls to gl::GenFramebuffers anywhere in it) but it's relatively straightforward to set up using raw gl calls. (I've used textures instead of renderbuffers because they have the significant advantage of being supported by high-level methods like Image::draw.)
extern crate piston;
extern crate opengl_graphics;
extern crate graphics;
extern crate sdl2_window;
extern crate gl;
use opengl_graphics::{ GlGraphics, OpenGL, Texture, TextureSettings };
use graphics::{ Context, Graphics, Transformed };
use graphics::image::Image;
use piston::input::*;
use piston::event_loop::*;
use piston::window::Window;
use sdl2_window::Sdl2Window as AppWindow;
use gl::types::GLuint;
static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
fn main() {
let opengl = OpenGL::V3_2;
let mut window: AppWindow = piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
.exit_on_esc(true).opengl(opengl).build().expect("window");
let ref mut gl = GlGraphics::new(opengl);
let (mut mx, mut my) = (0., 0.);
let draw_size = window.draw_size();
// It would also be possible to create a texture by hand using gl::GenTextures and call
// gl::TexImage2D with a null pointer for the data argument, which would require another unsafe
// block but would save this allocation
let texture_buf = vec![0u8; draw_size.width as usize * draw_size.height as usize];
let texture = Texture::from_memory_alpha(&texture_buf, draw_size.width, draw_size.height,
&TextureSettings::new()).expect("texture");
let fbo;
unsafe {
let mut fbos: [GLuint; 1] = [0];
// Create a Framebuffer Object that we can draw to later
gl::GenFramebuffers(1, fbos.as_mut_ptr());
fbo = fbos[0];
// Switch to it as the active framebuffer
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
// Set up the framebuffer object so that draws to it will go to the texture
gl::FramebufferTexture2D(gl::FRAMEBUFFER,
gl::COLOR_ATTACHMENT0, // draw colors, not depth or stencil data
gl::TEXTURE_2D, // the texture's type
texture.get_id(),
0); // mipmap level
}
let mut events = Events::new(EventSettings::new().lazy(true));
while let Some(e) = events.next(&mut window) {
e.mouse_cursor(|x, y| {
mx = x; my = y;
});
e.render(|args| {
// Switch to the texture framebuffer and draw the cursor
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
}
gl.draw(args.viewport(), |c, g| {
draw_cursor_pos([mx, my], &c, g);
});
// Switch to the window framebuffer and draw the texture
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
gl.draw(args.viewport(), |c, g| {
graphics::clear([1f32, 1f32, 1f32, 0f32], g);
// I can't entirely explain this. We already applied the viewport transform when
// we were rendering the cursor, so I think the texture is right-side-up for GL,
// but piston::Image is expecting an image laid out in screen coordinates.
// Since there is an offset in the viewport transform, the flip has to be applied
// first, otherwise it would flip across the origin.
let flipped = c.transform.prepend_transform(graphics::math::scale(1., -1.));
Image::new().draw(&texture, &c.draw_state, flipped, g);
});
});
}
}
fn draw_cursor_pos<G: Graphics>(
cursor: [f64; 2],
c: &Context,
g: &mut G,
) {
graphics::ellipse(
CURSOR_POS_COLOR,
graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
c.transform,
g
);
}
Alternatively, the gfx backend has the promising-sounding Factory::CreateRenderTarget method. My hardware doesn't support it, but I believe using it would look approximately like this:
extern crate piston;
extern crate graphics;
extern crate piston_window;
extern crate gfx_core;
use graphics::{ Context, Graphics, Transformed };
use graphics::image::Image;
use piston::input::*;
use piston::event_loop::*;
use piston::window::Window;
use piston_window::{ PistonWindow, OpenGL, G2dTexture };
use gfx_core::factory::Factory;
use gfx_core::texture::{ SamplerInfo, FilterMethod, WrapMode, Size };
static CURSOR_POS_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];
fn main() {
let opengl = OpenGL::V2_1;
let window_settings =
piston::window::WindowSettings::new("Example for StackOverflow", [600, 600])
.opengl(opengl)
.exit_on_esc(true);
let mut window: PistonWindow = window_settings.build().expect("window");
window.set_lazy(true);
let size = window.draw_size();
let (texture_handle, shader_view, target) = window.factory.create_render_target(size.width as Size, size.height as Size)
.expect("render target");
let sampler = window.factory.create_sampler(SamplerInfo::new(FilterMethod::Scale, WrapMode::Tile));
let texture = G2dTexture {
surface: texture_handle,
sampler: sampler,
view: shader_view,
};
let stencil = window.factory.create_depth_stencil_view_only(size.width as Size, size.height as Size)
.expect("stencil");
let (mut mx, mut my) = (0., 0.);
while let Some(e) = window.next() {
e.mouse_cursor(|x, y| {
mx = x; my = y;
});
if let Some(args) = e.render_args() {
window.g2d.draw(&mut window.encoder, &target, &stencil, args.viewport(), |c, g| {
draw_cursor_pos([mx, my], &c, g);
});
window.draw_2d(&e, |c, g| {
graphics::clear([1f32, 1f32, 1f32, 0f32], g);
let flipped = c.transform.prepend_transform(graphics::math::scale(1., -1.));
Image::new().draw(&texture, &c.draw_state, flipped, g);
});
}
}
}
fn draw_cursor_pos<G: Graphics>(
cursor: [f64; 2],
c: &Context,
g: &mut G,
) {
graphics::ellipse(
CURSOR_POS_COLOR,
graphics::ellipse::circle(cursor[0], cursor[1], 4.0),
c.transform,
g
);
}

Resources