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()
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.
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.