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