Slow animation with Nannou - rust

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();
}

Related

How can I (should i) include rand::Rng in my own struct?

I'm writing a graphical simulation to analyze physics interactions at the subatomic level and doing so by creating my own structures (struct Simiulation, struct Particle,...). For initial testing and development purposes I'm using rand::thread_rng().gen_rang(). In order to use this simulation, I have to add use rand::Rng; at the top of the file. As a new Rust user (1 week), I'm unsure if this can be included automatically, without having to type it, in the event I share the simulation with a colleague for collaborative purposes.
I googled nested namespaces which presented C++ / JS options but not Rust. I checked StackOverflow as well, but didn't find anything. I haven't reached the Cargo section in the Book as of yet. My terminology man be incorrect, so let me know if it's called something else in Rust.
Partial Sample Code
use rand::Rng; // <-- Nest this?
// - global -
const NUM_PARTICLES: usize = 5;
const WINDOW_WIDTH: f64 = 500.0; // TODO: Determine Window Width
const WINDOW_HEIGHT: f64 = 500.0; // TODO: Determine Window Height
const PARTICLE_RADIUS: f64 = 2.0; // TODO: Generate max probability distributon
// cross-sectional area to be used (per particle)
fn main() {
let mut starting_image = generate_starting_image();
println!("{:#?}", starting_image);
}
// Each particle has a position with respect to
// the origin and is denoted by (x, y)
// where x is the projection of the position along
// the x-axis and y is the projection of the position along
// the y-axis.
//
// Each particle has a veloctiy denoted by (Vx, Vy)
// where Vx is the velocity component along the x-axis and
// Vy is the velocity component along the y-axis.
#[derive(Debug, Copy, Clone)]
struct Mass {
mass: f64,
}
impl Mass {
// generate random mass for the particle (in kgs)
fn random() -> Self {
Self {
mass: rand::thread_rng().gen_range(1.00..=6.00),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Position {
x: f64,
y: f64,
}
impl Position {
// generate random starting position (x, y) for a particle
fn random() -> Self {
Self {
x: rand::thread_rng().gen_range(1.0..=(WINDOW_WIDTH - 1.0)),
y: rand::thread_rng().gen_range(1.0..=(WINDOW_HEIGHT - 1.0)),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Velocity {
vx: f64,
vy: f64,
}
impl Velocity {
// generate random starting velocity (vx, vy) for a particle
fn random() -> Self {
Self {
vx: rand::thread_rng().gen_range(5.0..20.0),
vy: rand::thread_rng().gen_range(5.0..20.0),
}
}
}
#[derive(Debug, Copy, Clone)]
struct Particle {
position: Position,
velocity: Velocity,
mass: Mass, // mass in kilograms
}
impl Particle {
fn random() -> Self {
Self {
position: Position::random(),
velocity: Velocity::random(),
mass: Mass::random(),
}
}
}
// An image is a snapshot of all the attribute values for each
// particle at some given time interval dt.
#[derive(Debug, Copy, Clone)]
struct Image {
particles: [Particle; NUM_PARTICLES],
}
// Calculate the distance between two points. Necessary to
// determine if a collision is taken place
fn distance (particle_1: &Particle, particle_2: &Particle) -> f64 {
let base_1: f64 = particle_1.position.x - particle_2.position.x;
let base_2: f64 = particle_1.position.y - particle_2.position.y;
let base: f64 = f64::powf(base_1, 2.0) + f64::powf(base_2, 2.0);
f64::powf(base, 0.5)
}
fn generate_starting_image() -> Image {
let mut starting_image = Image {
particles: [Particle::random(); NUM_PARTICLES]
};
let iter = starting_image.particles.iter();
let mut num_created_particles: usize = 0;
// Loop over the starting_image array to populate the
// array with starting particle values
loop {
// stop the loop when the number of particles has
// been generated
if num_created_particles == NUM_PARTICLES { break; }
// loop over the populated values and check to
// make sure the distance between the newly generated
// particle does not occupy the same position as
// a previously generated particle
// Particle to test
let mut current_particle = Particle::random();
// indices of checked particles
let mut checked_particles: usize = 0;
while checked_particles < num_created_particles {
if distance(&current_particle, &starting_image.particles[checked_particles]) <= 4.0 {
current_particle = Particle::random();
checked_particles = 0;
}
// Newly generated particle is further than the minimum
// needed to be placed. increment to next particle to
// be checked.
checked_particles = checked_particles + 1;
}
// Newly generated particle has been checked for proximity
// against all previously generated particles. Insert
// into the particle array and increment to
// generate the next particle
println!("num_created_particles = {num_created_particles}");
starting_image.particles[num_created_particles] = current_particle;
num_created_particles = num_created_particles + 1;
checked_particles = 0;
}
// The starting values for each particle has been generated.
// Return array to begin the simulation
starting_image
}

What is an acceptable approach to dragging sprites with Bevy 0.4?

While trying out Bevy, I had the need for dragging and dropping sprites.
Unfortunately, this does not seem to come ready made or I did not find it in the documentation.
What would be the most idiomatic way to achieve this goal?
What I have tried so far is in my answer, but I'll gladly accept another solution that is better/faster/more idiomatic.
I'm not experienced enough to know what's idiomatic unfortunately, however, here's an overview of how I've implemented sprite dragging in my application, and it feels like a good way to me:
I have a "cursor location" entity with a transform component (and a Cursor component for identification) that I update in a system each frame to the location of the cursor.
Every draggable object has a Hoverable and Draggable component. I iterate over those objects in one system each where I add/remove a Hovered and Dragged component to the entities to indicate if they are hovered or dragged.
I have a system that checks if an object is getting dropped, and if so gives it a Dropped component.
I have a system that runs when an entity gets the 'Dragged' component (using the Added<C> filter), which sets the objects parent to the "cursor location" entity.
And another system for when an entity gets the 'Dropped' component, which clears the parent.
To me, having many systems with small areas of responsibility feels good. I would be interested to hear opposing views as I lack experience.
There are of course many things I've left out in this overview, so here's my code for reference. There are some oddities and unnecessary code for a minimal example since this is adapted from my actual code:
#![allow(clippy::type_complexity)]
use bevy::{prelude::*, render::camera::Camera};
fn main() {
App::build()
.init_resource::<State>()
.add_resource(WindowDescriptor {
title: "Bevy".to_string(),
width: 1024.0,
height: 768.0,
vsync: true,
..Default::default()
})
.add_plugins(DefaultPlugins)
.add_plugin(MyPlugin)
.run();
}
pub struct MyPlugin;
impl Plugin for MyPlugin {
fn build(&self, app: &mut AppBuilder) {
app.add_startup_system(setup.system())
.add_system_to_stage(stage::PRE_UPDATE, cursor_state.system())
.add_system_to_stage(stage::UPDATE, cursor_transform.system())
.add_system_to_stage(stage::UPDATE, draggable.system())
.add_system_to_stage(stage::UPDATE, hoverable.system())
.add_system_to_stage(stage::POST_UPDATE, drag.system())
.add_system_to_stage(stage::POST_UPDATE, drop.system())
.add_system_to_stage(stage::POST_UPDATE, material.system());
}
}
const SPRITE_SIZE: f32 = 55.0;
fn setup(
commands: &mut Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
let bevy_texture = asset_server.load("sprites/bevy-icon.png");
commands
.spawn(Camera2dBundle::default())
.spawn(())
.with(CursorState::default())
.spawn((Transform::default(), GlobalTransform::default(), Cursor));
for _ in 0..4 {
commands
.spawn(SpriteBundle {
material: materials.add(bevy_texture.clone().into()),
sprite: Sprite::new(Vec2::new(SPRITE_SIZE, SPRITE_SIZE)),
..Default::default()
})
.with(Hoverable)
.with(Draggable);
}
}
#[derive(Default)]
struct CursorState {
cursor_world: Vec2,
cursor_moved: bool,
}
struct Cursor;
struct Draggable;
struct Dragged;
struct Dropped;
struct Hoverable;
struct Hovered;
fn cursor_state(
mut state: ResMut<State>,
e_cursor_moved: Res<Events<CursorMoved>>,
windows: Res<Windows>,
mut q_cursor_state: Query<&mut CursorState>,
q_camera: Query<&Transform, With<Camera>>,
) {
let event_cursor_screen = state.er_cursor_moved.latest(&e_cursor_moved);
for mut cursor_state in q_cursor_state.iter_mut() {
if let Some(event_cursor_screen) = event_cursor_screen {
let window = windows.get_primary().unwrap();
let cam_transform = q_camera.iter().last().unwrap();
cursor_state.cursor_world =
cursor_to_world(window, cam_transform, event_cursor_screen.position);
cursor_state.cursor_moved = true;
} else {
cursor_state.cursor_moved = false;
}
}
}
fn cursor_transform(
commands: &mut Commands,
q_cursor_state: Query<&CursorState>,
mut q_cursor: Query<(Entity, &mut Transform), With<Cursor>>,
) {
let cursor_state = q_cursor_state.iter().next().unwrap();
for (cursor_e, mut transform) in q_cursor.iter_mut() {
transform.translation.x = cursor_state.cursor_world.x;
transform.translation.y = cursor_state.cursor_world.y;
commands.remove_one::<Parent>(cursor_e);
}
}
fn hoverable(
commands: &mut Commands,
q_cursor_state: Query<&CursorState>,
q_hoverable: Query<(Entity, &Transform, &Sprite), (With<Hoverable>, Without<Dragged>)>,
) {
let cursor_state = q_cursor_state.iter().next().unwrap();
if cursor_state.cursor_moved {
for (entity, transform, sprite) in q_hoverable.iter() {
let half_width = sprite.size.x / 2.0;
let half_height = sprite.size.y / 2.0;
if transform.translation.x - half_width < cursor_state.cursor_world.x
&& transform.translation.x + half_width > cursor_state.cursor_world.x
&& transform.translation.y - half_height < cursor_state.cursor_world.y
&& transform.translation.y + half_height > cursor_state.cursor_world.y
{
commands.insert_one(entity, Hovered);
} else {
commands.remove_one::<Hovered>(entity);
}
}
}
}
fn material(
mut materials: ResMut<Assets<ColorMaterial>>,
q_hoverable: Query<
(&Handle<ColorMaterial>, Option<&Hovered>, Option<&Dragged>),
With<Hoverable>,
>,
) {
let mut first = true;
for (material, hovered, dragged) in q_hoverable.iter() {
let (red, green, alpha) = if dragged.is_some() {
(0.0, 1.0, 1.0)
} else if first && hovered.is_some() {
first = false;
(1.0, 0.0, 1.0)
} else if hovered.is_some() {
(1.0, 1.0, 0.5)
} else {
(1.0, 1.0, 1.0)
};
materials.get_mut(material).unwrap().color.set_r(red);
materials.get_mut(material).unwrap().color.set_g(green);
materials.get_mut(material).unwrap().color.set_a(alpha);
}
}
fn cursor_to_world(window: &Window, cam_transform: &Transform, cursor_pos: Vec2) -> Vec2 {
// get the size of the window
let size = Vec2::new(window.width() as f32, window.height() as f32);
// the default orthographic projection is in pixels from the center;
// just undo the translation
let screen_pos = cursor_pos - size / 2.0;
// apply the camera transform
let out = cam_transform.compute_matrix() * screen_pos.extend(0.0).extend(1.0);
Vec2::new(out.x, out.y)
}
fn draggable(
commands: &mut Commands,
i_mouse_button: Res<Input<MouseButton>>,
q_pressed: Query<Entity, (With<Hovered>, With<Draggable>)>,
q_released: Query<Entity, With<Dragged>>,
) {
if i_mouse_button.just_pressed(MouseButton::Left) {
if let Some(entity) = q_pressed.iter().next() {
commands.insert_one(entity, Dragged);
}
} else if i_mouse_button.just_released(MouseButton::Left) {
for entity in q_released.iter() {
commands.remove_one::<Dragged>(entity);
commands.insert_one(entity, Dropped);
}
}
}
fn drag(
commands: &mut Commands,
mut q_dragged: Query<(Entity, &mut Transform, &GlobalTransform), Added<Dragged>>,
q_cursor: Query<(Entity, &GlobalTransform), With<Cursor>>,
) {
if let Some((cursor_e, cursor_transform)) = q_cursor.iter().next() {
for (entity, mut transform, global_transform) in q_dragged.iter_mut() {
let global_pos = global_transform.translation - cursor_transform.translation;
commands.insert_one(entity, Parent(cursor_e));
transform.translation.x = global_pos.x;
transform.translation.y = global_pos.y;
}
}
}
fn drop(
commands: &mut Commands,
mut q_dropped: Query<(Entity, &mut Transform, &GlobalTransform), Added<Dropped>>,
) {
for (entity, mut transform, global_transform) in q_dropped.iter_mut() {
let global_pos = global_transform.translation;
transform.translation.x = global_pos.x;
transform.translation.y = global_pos.y;
commands.remove_one::<Parent>(entity);
commands.remove_one::<Dropped>(entity);
}
}
#[derive(Default)]
struct State {
er_cursor_moved: EventReader<CursorMoved>,
}
This code is for bevy 0.4.
This is the solution I came up with. Complete example
main.rs
use bevy::prelude::*;
use bevy::render::pass::ClearColor;
use bevy::window::CursorMoved;
const SPRITE_SIZE: f32 = 55.0;
fn main() {
App::build()
.add_resource(WindowDescriptor {
width: 1000.0,
height: 1000.0,
resizable: false,
title: "Bevy: drag sprite".to_string(),
..Default::default()
})
.add_resource(Msaa { samples: 4 })
.add_resource(ClearColor(Color::rgb(0.9, 0.9, 0.9)))
.add_plugins(DefaultPlugins)
.add_startup_system(setup.system())
.add_system(sprite_system.system())
.add_system(bevy::input::system::exit_on_esc_system.system())
.run();
}
fn setup(
commands: &mut Commands,
asset_server: Res<AssetServer>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
commands.spawn(Camera2dBundle::default());
// show sprite in the middle of the screen
let bevy_texture = asset_server.load("sprites/bevy-icon.png");
commands.spawn(SpriteBundle {
sprite: Sprite::new(Vec2::new(SPRITE_SIZE, SPRITE_SIZE)),
material: materials.add(bevy_texture.clone().into()),
..Default::default()
});
}
#[derive(Default)]
struct State {
cursor_moved_event_reader: EventReader<CursorMoved>,
// store current cursor/mouse position
cursor_pos: Vec2,
// store entity ID and the difference between sprite center and mouse click location
sprite: Option<(Entity, Vec3)>,
}
fn sprite_system(
mut state: Local<State>,
windows: Res<Windows>,
mouse_button_input: Res<Input<MouseButton>>,
cursor_moved_events: Res<Events<CursorMoved>>,
mut sprites: Query<(Entity, &Sprite)>,
mut transforms: Query<&mut Transform>,
) {
let window = windows.get_primary().unwrap();
let half_window = Vec2::new(window.width() / 2.0, window.height() / 2.0);
// if cursor has moved, transform to graphics coordinates and store in state.curser_pos
if let Some(cursor_event) = state.cursor_moved_event_reader.latest(&cursor_moved_events) {
state.cursor_pos = cursor_event.position - half_window;
state.cursor_pos.x = state.cursor_pos.x;
};
// stop dragging if mouse button was released
if mouse_button_input.just_released(MouseButton::Left) {
state.sprite = None;
return;
}
// set new sprite position, if mouse button is pressed and a sprite was clicked on
// take previous click difference into account, to avoid sprite jumps on first move
if mouse_button_input.pressed(MouseButton::Left) && state.sprite.is_some() {
let sprite = state.sprite.unwrap();
let mut sprite_pos = transforms.get_mut(sprite.0).unwrap();
trace!("Sprite position old: {:?}", sprite_pos.translation);
sprite_pos.translation.x = state.cursor_pos.x + sprite.1.x;
sprite_pos.translation.y = state.cursor_pos.y + sprite.1.y;
trace!("Sprite position new: {:?}", sprite_pos.translation);
// position clamping was left out intentionally
}
// store sprite ID and mouse distance from sprite center, if sprite was clicked
if mouse_button_input.just_pressed(MouseButton::Left) {
for (entity, sprite) in sprites.iter_mut() {
let sprite_pos = transforms.get_mut(entity).unwrap().translation;
let diff = cursor_to_sprite_diff(&state.cursor_pos, &sprite_pos);
// sprite is a circle, so check distance from center < sprite radius
if diff.length() < (sprite.size.x / 2.0) {
state.sprite = Some((entity, diff));
}
}
}
}
fn cursor_to_sprite_diff(cursor_pos: &Vec2, sprite_pos: &Vec3) -> Vec3 {
Vec3::new(
sprite_pos.x - cursor_pos.x,
sprite_pos.y - cursor_pos.y,
0.0,
)
}
Cargo.toml
[package]
name = "bevy-drag-sprite"
version = "0.1.0"
authors = ["Me"]
edition = "2018"
[dependencies]
bevy = "0.4"

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 to use math to rotate around the center of a ggez image instead of the top left?

I'm making a game using ggez, using a camera from ggez_goodies.
This camera only has rotation from the top left of an image, but I want it from the center. Is there a good way to position the image so that it rotates around the center instead?
I'd assume you'd just change the position to fix this, what I have currently is just the position
self.image
.draw_camera(
camera,
ctx,
graphics::Point2::new(self.position.0, self.position.1),
self.rotation,
)
.unwrap();
I'm guessing a fix would look sort of like this:
self.image
.draw_camera(
camera,
ctx,
graphics::Point2::new(
self.position.0 + self.rotation_offset.0,
self.position.1 + self.rotation_offset.1,
),
self.rotation,
)
.unwrap();
I think it would be possible with an offset, but I can't figure out how to set the offset based off of the rotation angle.
What offset/change could I make to the position to get the image to rotate based around the center instead of around the top left corner?
In following example:
use ggez;
use ggez::event;
use ggez::graphics;
use ggez::graphics::{DrawMode, MeshBuilder};
use ggez::nalgebra as na;
use ggez::timer::check_update_time;
use ggez::{Context, GameResult};
use std::env;
use std::path;
struct MainState {
rotate: f32,
sprite_batch: graphics::spritebatch::SpriteBatch,
}
impl MainState {
fn new(ctx: &mut Context) -> GameResult<MainState> {
let image = graphics::Image::new(ctx, "/sprite_sheet.png").unwrap();
let batch = graphics::spritebatch::SpriteBatch::new(image);
Ok(MainState {rotate: 0.0, sprite_batch: batch})
}
}
impl event::EventHandler for MainState {
fn update(&mut self, ctx: &mut Context) -> GameResult {
while check_update_time(ctx, 30) {
self.rotate += 0.1;
}
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult {
graphics::clear(ctx, graphics::BLACK);
let mesh = MeshBuilder::new().circle(
DrawMode::fill(),
na::Point2::new(400.0, 300.0),
2.0,
2.0,
graphics::WHITE,
).build(ctx)?;
self.sprite_batch.add(
graphics::DrawParam::new()
.src(graphics::Rect::new(0.0, 0.0, 1.0, 1.0))
.rotation(self.rotate)
.dest(na::Point2::new(400.0, 300.0))
.offset(na::Point2::new(0.5, 0.5)),
);
graphics::draw(
ctx,
&self.sprite_batch,
graphics::DrawParam::new().dest(na::Point2::new(0.0, 0.0)),
)?;
graphics::draw(
ctx,
&mesh,
graphics::DrawParam::new().dest(na::Point2::new(0.0, 0.0)),
)?;
self.sprite_batch.clear();
graphics::present(ctx)?;
Ok(())
}
}
pub fn main() -> GameResult {
let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("resources");
path
} else {
path::PathBuf::from("./resources")
};
let cb = ggez::ContextBuilder::new("oc", "bux")
.add_resource_path(resource_dir)
.window_mode(ggez::conf::WindowMode::default().dimensions(800.0, 600.0));
let (ctx, event_loop) = &mut cb.build()?;
let state = &mut MainState::new(ctx)?;
event::run(ctx, event_loop, state)
}
Where resources/sprite_sheet.png is:
The which do rotation from the center of the image is:
.offset(na::Point2::new(0.5, 0.5))

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