How do you properly implement a View Matrix? - rust

I'm making a Minecraft-like game using the Rust Vulkano Vulkan API binding to render the game. I have a player rotating either about the Y-axis or X-axis, specifically implemented with a FPS view matrix. The output expected from the view matrix is a 4x4 float, homogeneous matrix ([[f32; 4]; 4]).
I've attempted to use the cgmath library and nalgebra library as my implementation for the FPS view matrix, though I decided to manually implement it only using nalgebra for the matrix operations.
# cargo.toml
vulkano = "0.18.0"
vulkano-shaders = "0.13"
vulkano-win = "0.18.0"
winit = "0.22.0"
nalgebra = "0.21.1"
nalgebra-glm = "0.7.0"
extern crate nalgebra as na;
use na::{
Point3,
Rotation3,
Matrix4,
};
use crate::Dimension;
type Matrix3D = [[f32; 4]; 4];
// generates the mvp matrix for meshes and other pipelines
pub fn gen_mvp(&self, dimensions: Dimension<u32>) -> (Matrix3D, Matrix3D, Matrix3D) {
let proj = Perspective3::new(dimensions.aspect() as f32, 3.14 / 2.0, 0.1, 1000.0);
let eye = &self.position;
let target: Vector3<f32> = Vector3::new(1.0, 0.0, 1.0);
let up = Vector3::new(0.0, -1.0, 0.0);
println!("ROT MAT: {:?}", self.rotation.euler_angles());
println!("POS MAT: {:?}", self.position.coords.data);
println!("ROT POS: {:?}", self.rotation.transform_vector(&target));
let d = self.rotation.transform_vector(&target).data;
println!("TARGET : {:?}", self.rotation.transform_vector(&target));
let roty = Rotation3::from_euler_angles(self.rotation.euler_angles().0, self.rotation.euler_angles().1, 0.0);
// let view = Isometry3::look_at_lh(eye, &(&self.position + &roty * &target), &up);
let view = Self::fpv_view(eye, &self.rotation);
// let rot = UnitQuaternion::new(Vector3::new(0.0, self.rotation.euler_angles().1, self.rotation.euler_angles().2));
// let crd = self.position.coords.data;
// let view_pos = Isometry3::from_parts(Translation3::new(crd[0], crd[1], crd[2]), UnitQuaternion::new(Vector3::new(0.0, 0.0, 0.0)));
// let view_rot = Isometry3::from_parts(Translation3::new(0.0, 0.0, 1.0), rot);
// let view = view_rot * view_pos;
// let model = glm::identity::<f32, >();
let model = Matrix4::identity();
// let eye = Point3::new(0.0, 0.0, 1.0);
// let target = Point3::new(1.0, 0.0, 0.0);
// let view = Isometry3::look_at_rh(&eye, &target, &Vector3::y());
// let model = Isometry3::identity(); // the world
// let proj = perspective (self.fov, dimensions.aspect() as f32, 0.1 , 1000.0);
// let view = Matrix4::from_angle_x(self.rotation.x) * Matrix4::from_angle_y(self.rotation.y) *
// Matrix4::look_at(Point3::new(self.position.x, self.position.y, 1.0+self.position.z), self.position.into(), Vector3::new(0.0, -1.0, 0.0));
// let world = Matrix4::identity();
let proj_matrix = proj.as_matrix();
let view_matrix = view;
let proj_cooked: &[f32] = proj_matrix.as_slice();
let view_cooked: &[f32] = view.as_slice();
let model_cooked: &[f32] = model.as_slice();
let proj_dt;
let view_dt;
let model_dt;
unsafe {
assert_eq!(proj_cooked.len(), 16);
assert_eq!(view_cooked.len(), 16);
assert_eq!(model_cooked.len(), 16);
proj_dt = *(proj_cooked.as_ptr() as *const Matrix3D);
view_dt = *(view_cooked.as_ptr() as *const Matrix3D);
model_dt = *(model_cooked.as_ptr() as *const Matrix3D);
}
(proj_dt, view_dt, model_dt)
}
fn fpv_view(eye: &Point3<f32>, rot: &Rotation3<f32>) -> Matrix4<f32> {
let (roll, pitch, yaw) = rot.euler_angles();
let e = &eye.coords.data;
// -- snip --
let xrot = Matrix4::new(
1.0, 0.0, 0.0, 0.0,
0.0, pitch.cos(),-pitch.sin(), 0.0,
0.0, pitch.sin(), pitch.cos(), 0.0,
0.0, 0.0, 0.0, 1.0,
);
let yrot = Matrix4::new(
yaw.cos(), 0.0, yaw.sin(), 0.0,
0.0, 1.0, 0.0, 0.0,
-yaw.sin(), 0.0, yaw.cos(), 0.0,
0.0, 0.0, 0.0, 1.0,
);
// also tried to transpose the translation matrix too
// let translation = Matrix4::new(
// 1.0, 0.0, 0.0, 0.0,
// 0.0, 1.0, 0.0, 0.0,
// 0.0, 0.0, 1.0, 0.0,
// -e[0], -e[1], -e[2], 1.0,
// );
let translation = Matrix4::new(
1.0, 0.0, 0.0, -e[0],
0.0, 1.0, 0.0, -e[1],
0.0, 0.0, 1.0, -e[2],
0.0, 0.0, 0.0, 1.0,
);
// also tried inverting the view matrix
// (translation * yrot * xrot).try_inverse().unwrap()
translation * yrot * xrot
}
I also attempted to use the traditional look_at(eye, target, up) function.
The result is either weird clipping/warping or not rotating properly. I tried to find some external resources such as MVP matrix tutorials, questions, and forums but the solution always results in a weird rendering.
Update 1
The environment I built currently in the game is just a large 64x64x64 block-sized hollow chunk with textures only on the chunk border with no face culling with alpha-blending enabled.
This is the closest I can get where the rotating about y-axis rotates around a imaginary large circle path, which should just be rotating about the player.
pub fn gen_mvp(&self, dimensions: Dimension<u32>) -> (Matrix3D, Matrix3D, Matrix3D) {
let proj = Perspective3::new(dimensions.aspect() as f32, 3.14 / 2.0, 0.1, 1000.0);
let eye = &self.position;
let target: Vector3<f32> = Vector3::new(0.0, 0.0, 1.0);
let up = Vector3::new(0.0, -1.0, 0.0);
let roty = Rotation3::from_euler_angles(self.rotation.euler_angles().0, self.rotation.euler_angles().1, 0.0);
let view = Isometry3::look_at_lh(eye, &(&self.position + (&roty * &target)), &up);
let model = Matrix4::identity();
let proj_matrix = proj.as_matrix();
let view_matrix = view.to_homogeneous();
let proj_cooked: &[f32] = proj_matrix.as_slice();
let view_cooked: &[f32] = view_matrix.as_slice();
let model_cooked: &[f32] = model.as_slice();
let proj_dt;
let view_dt;
let model_dt;
unsafe {
assert_eq!(proj_cooked.len(), 16);
assert_eq!(view_cooked.len(), 16);
assert_eq!(model_cooked.len(), 16);
proj_dt = *(proj_cooked.as_ptr() as *const Matrix3D);
view_dt = *(view_cooked.as_ptr() as *const Matrix3D);
model_dt = *(model_cooked.as_ptr() as *const Matrix3D);
}
(proj_dt, view_dt, model_dt)
}

The main thing the code did not work was the fact I misinterpreted 2 big parts of the player transformation. First, I thought the model matrices was only for the custom, independent from the world, transformation. But I found model matrices (in conjunction with the world MVP matrices) was also used to rotate the world around the camera (which stays static looking towards the -z direction). Second, most rotation used in pure numerical, mathematical libraries are quaternions, meaning changing only two of the axis will affect the third axis. Mistaken with the quaternions, I decided the only way was to create a manual pure euler angle rotation matrices.
Here's the resulting code as shown below:
// camera.rs
pub struct Camera {
pub position: Point3<f32>,
pub rotation: Rotation<f32>, // manual pure euler angle rotation
rot_speed: f32,
trans_speed: f32,
// fovy: Rotation<f32, Dim>,
}
// -- snip --
// generates the mvp matrix for meshes and other pipelines
pub fn gen_mvp(&self, dimensions: Dimension<u32>) -> (Matrix3D, Matrix3D, Matrix3D) {
let proj = Perspective3::new(dimensions.aspect() as f32, 3.14 / 2.0, 0.1, 1000.0);
let eye = Point3::new(0.0, 0.0, 0.0);
let target = Point3::new(0.0, 0.0, -1.0);
let up = Vector3::new(0.0, -1.0, 0.0);
let view = Isometry3::look_at_lh(&eye, &target, &up);
let crd = &self.position.coords.data;
let model = Translation3::new(crd[0], crd[1], crd[2]);
let model = model.to_homogeneous() * self.rotation.matrix();
let proj_matrix = proj.as_matrix();
let view_matrix = view.to_homogeneous();
let model_matrix = model.try_inverse().unwrap();
let proj_cooked: &[f32] = proj_matrix.as_slice();
let view_cooked: &[f32] = view_matrix.as_slice();
let model_cooked: &[f32] = model_matrix.as_slice();
let proj_dt;
let view_dt;
let model_dt;
unsafe {
assert_eq!(proj_cooked.len(), 16);
assert_eq!(view_cooked.len(), 16);
assert_eq!(model_cooked.len(), 16);
proj_dt = *(proj_cooked.as_ptr() as *const Matrix3D);
view_dt = *(view_cooked.as_ptr() as *const Matrix3D);
model_dt = *(model_cooked.as_ptr() as *const Matrix3D);
}
(proj_dt, view_dt, model_dt)
}
// datatype.rs
// -- snip --
#[derive(Copy, Clone, Debug)]
pub struct Rotation<T: Copy + Debug + PartialEq + Float> {
pub x: T,
pub y: T,
pub z: T,
}
impl Rotation<f32> {
pub fn new(x: f32, y: f32, z: f32) -> Self {
Self {
x,
y,
z,
}
}
pub fn matrix(&self) -> Matrix4<f32> {
let sx = self.x.sin();
let cx = self.x.cos();
let sy = self.y.sin();
let cy = self.y.cos();
let sz = self.z.sin();
let cz = self.z.cos();
Matrix4::new( // z
cz, -sz, 0.0, 0.0,
sz, cz, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
) * Matrix4::new( // y
cy, 0.0, sy, 0.0,
0.0, 1.0, 0.0, 0.0,
-sy, 0.0, cy, 0.0,
0.0, 0.0, 0.0, 1.0,
) * Matrix4::new( // x
1.0, 0.0, 0.0, 0.0,
0.0, cx, -sx, 0.0,
0.0, sx, cx, 0.0,
0.0, 0.0, 0.0, 1.0,
)
}
}
I would like to thank you all for clarifying the model matrices and the nalgebra's Rotation3 (reddit). I hope no one will ever fall in this confusing hole.

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

How to make sure I pass the right byte layout to the wgpu shader as uniform

I have this structure
#[repr(C)]
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct ProjUniform {
view_proj: [[f32; 3]; 3],
}
impl ProjUniform {
fn new() -> Self {
Self {
view_proj: [[0.0, 0.0, 0.0],
[1.0, 1.0, 0.5],
[0.0, 0.0, 1.0],
]
}
}
}
I pass to gpu like this:
let proj_uniform = ProjUniform::new();
let proj_buffer = device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Proj Buffer"),
contents: bytemuck::cast_slice(&[proj_uniform]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST
});
Now I've debugged this, and I don't get the correct mat3 structure in the shader. it is off and twisted. How do I debug this thing?
This is shader code:
[[block]]
struct ProjectUniform {
view_proj: mat3x3<f32>;
};
[[group(1), binding(0)]]
var<uniform> project: ProjectUniform;

How can I manually create meshes in bevy with vertices?

What do I have to do to to create a mesh for bevy with the following vertices:
let mut vertices : Vec<[f32; 3]> = Vec::new();
vertices.push([0.0, 0.0, 0.0]);
vertices.push([1.0, 2.0, 1.0]);
vertices.push([2.0, 0.0, 0.0]);
I then want to spawn a MeshBundle like so
commands
.spawn(MeshBundle {
mesh: mesh,
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
..Default::default()
});
This answer has been updated for the latest bevy = "0.9.1" and uses the default shaders.
The code below demonstrates how to:
Define vertex positions for a bevy::render::pipeline::PrimitiveTopology::TriangleList
Assign vertex normals and uv coordinates to the vertices
Create a triangle using the 3 vertices we defined
It is based on the built in shapes in bevy, which can be found here.
use bevy::prelude::*;
use bevy::render::mesh::{self, PrimitiveTopology};
fn main() {
App::new()
.insert_resource(Msaa { samples: 4 })
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.run();
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
// Positions of the vertices
// See https://bevy-cheatbook.github.io/features/coords.html
mesh.insert_attribute(
Mesh::ATTRIBUTE_POSITION,
vec![[0., 0., 0.], [1., 2., 1.], [2., 0., 0.]],
);
// In this example, normals and UVs don't matter,
// so we just use the same value for all of them
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0., 1., 0.]; 3]);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.]; 3]);
// A triangle using vertices 0, 2, and 1.
// Note: order matters. [0, 1, 2] will be flipped upside down, and you won't see it from behind!
mesh.set_indices(Some(mesh::Indices::U32(vec![0, 2, 1])));
commands.spawn(PbrBundle {
mesh: meshes.add(mesh),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
commands.spawn(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}
You will have to define your own positions, uvs and normals according to your use case. Some shaders won't need all of these mesh attributes.
#frankenapps answer updated for bevy = 0.7
use bevy::prelude::*;
use bevy::render::mesh::{self, PrimitiveTopology};
fn main() {
App::new()
.insert_resource(Msaa { samples: 4 })
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.run();
}
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let vertices = [
([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0]),
([1.0, 2.0, 1.0], [0.0, 1.0, 0.0], [1.0, 1.0]),
([2.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0]),
];
let indices = mesh::Indices::U32(vec![0, 2, 1, 0, 3, 2]);
let mut positions = Vec::new();
let mut normals = Vec::new();
let mut uvs = Vec::new();
for (position, normal, uv) in vertices.iter() {
positions.push(*position);
normals.push(*normal);
uvs.push(*uv);
}
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
// add entities to the world
// plane
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(mesh),
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
..default()
});
// light
commands.spawn_bundle(PointLightBundle {
point_light: PointLight {
intensity: 1500.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_xyz(4.0, 8.0, 4.0),
..default()
});
// camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(-2.0, 2.5, 5.0)
.looking_at(Vec3::ZERO, Vec3::Y),
..default()
});
}

Piston create a a block of multiple square

i want to create something like a square of square like a a square composed of 4 small squares (4x4) for a Tetris I know how to create a simple square and move it,
but I don't know how to create a more complex figure like a 4x4 square or an L and move it like a simple square do you have any idea ?
here the code if you need it =>
extern crate glutin_window;
extern crate graphics;
extern crate opengl_graphics;
extern crate piston;
use glutin_window::GlutinWindow;
use graphics::rectangle;
use opengl_graphics::{GlGraphics, OpenGL};
use piston::event_loop::{EventSettings, Events};
use piston::input::{
Button, Key, PressEvent, ReleaseEvent, RenderArgs, RenderEvent, UpdateArgs, UpdateEvent,
};
use piston::window::WindowSettings;
use piston_window::Transformed;
use std::process;
struct Game {
gl: GlGraphics,
pos_x: f64,
pos_y: f64,
}
impl Game {
fn render(&mut self, arg: &RenderArgs) {
use graphics;
let BACKGROUND: [f32; 4] = [0.349019608, 0.349019608, 0.290196078, 1.0];
let COO: [f32; 4] = [0.364705882, 0.717647059, 0.870588235, 0.8];
let square_c = rectangle::square(100.0, 100.0, 30.0);
let pos_x = self.pos_x as f64;
let pos_y = self.pos_y as f64;
println!("pos x = {}", pos_x);
println!("pos y = {}", pos_y);
self.gl.draw(arg.viewport(), |c, gl| {
graphics::clear(BACKGROUND, gl);
rectangle(COO, square_c, c.transform.trans(pos_x, pos_y), gl); // deplace le carre de -200 vers la gauche
});
}
fn press(&mut self, args: &Button) {
if let &Button::Keyboard(key) = args {
match key {
Key::Up => self.pos_y -= 10.0,
Key::Down => self.pos_y += 10.0,
Key::Left => self.pos_x -= 10.0,
Key::Right => self.pos_x += 10.0,
_ => {
println!("other1");
}
}
}
}
fn release(&mut self, args: &Button) {
if let &Button::Keyboard(key) = args {
match key {
Key::Up => {
println!("Up release");
}
Key::Down => {
println!("Down release");
}
Key::Left => {
println!("Left release");
}
Key::Right => {
println!("Right release");
}
_ => {
println!("other release");
}
}
}
}
}
fn main() {
let opengl = OpenGL::V3_2;
let mut window: GlutinWindow = WindowSettings::new("Tetris Game", [400, 800])
.graphics_api(opengl)
.exit_on_esc(true)
.build()
.unwrap();
let mut game = Game {
gl: GlGraphics::new(opengl),
pos_x: 200.0,
pos_y: 0.0,
};
let mut events = Events::new(EventSettings::new());
while let Some(e) = events.next(&mut window) {
if let Some(r) = e.render_args() {
game.render(&r);
}
if let Some(b) = e.press_args() {
game.press(&b);
}
if let Some(b) = e.release_args() {
game.release(&b);
}
}
}
If you want to create a square of 4 squares, then you just have to repeat what you already have, i.e. call rectangle 4 times and position the rectangles accordingly.
const RED: [f32; 4] = [1.0, 0.0, 0.0, 1.0];
const GREEN: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
const BLUE: [f32; 4] = [0.0, 0.0, 1.0, 1.0];
const YELLOW: [f32; 4] = [1.0, 1.0, 0.0, 1.0];
let (x, y) = (50.0, 50.0);
rectangle(RED, rectangle::square( 0.0, 0.0, 30.0), c.transform.trans(x, y), gl);
rectangle(GREEN, rectangle::square(30.0, 0.0, 30.0), c.transform.trans(x, y), gl);
rectangle(BLUE, rectangle::square( 0.0, 30.0, 30.0), c.transform.trans(x, y), gl);
rectangle(YELLOW, rectangle::square(30.0, 30.0, 30.0), c.transform.trans(x, y), gl);
The above renders a 4 rectangles that look like this:
If you want to create an L shape, then you can just position 2 rectangles to create that shape.
rectangle(RED, [0.0, 0.0, 15.0, 30.0], c.transform.trans(x, y), gl);
rectangle(GREEN, [0.0, 30.0, 30.0, 15.0], c.transform.trans(x, y), gl);
You could also bake the positions directly into the rectangle shape, instead of using .trans(x, y), i.e. for the last one it would be [x, y + 30.0, 30.0, 15.0].
If you instead want to create a single polygon from a series of points, then you can use polygon(). The following creates the same L shape as before, just this time it's completely red.
polygon(
RED,
&[
[ 0.0, 45.0],
[ 0.0, 0.0],
[15.0, 0.0],
[15.0, 30.0],
[30.0, 30.0],
[30.0, 45.0],
],
c.transform.trans(x, y),
gl,
);
In the piston2d-graphics crate's documentation you can see additional draw functions, such as ellipse(), image() and text()

How can the resolution of a PistonWindow be changed after creation?

I'd like to change the resolution of a PistonWindow after it has been created. This would allow a user to change the resolution of the window while playing the game.
WindowSettings has a set_size method, but it doesn't seem to be accessible after the PistonWindow type is created.
A simplified example:
extern crate piston_window;
use piston_window::*;
fn main() {
let mut window: PistonWindow = WindowSettings::new("Game Title", [200, 200])
.exit_on_esc(true)
.build()
.unwrap();
// Do some logic here, and change resolution to 400 x 400.
while let Some(e) = window.next() {
window.draw_2d(&e, |c, g| {
ellipse([1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 100.0, 100.0], c.transform, g)
});
}
}
As far as I know, you can't. At least not of an existing window.
However, you can store the WindowSettings and re-create your window based on it when the user changes the resolution.

Resources