feat: attempt to implement math library
Adds many files, but the next thing is to implement the MeshRegistry properly.
This commit is contained in:
1 parent
9e4f531bd4
commit
e7659ba387
18 files changed
+1232
-136
No files matched your search
@@ -0,0 +1,55 @@
|
||||
use nalgebra::{Matrix4, Vector3, Vector4};
|
||||
use crate::common::scene::SceneInstance;
|
||||
|
||||
/// Contains all information about an instance of a mesh, specifically its translation, rotation,
|
||||
/// and scale. Note that the rotation is a [`Vector4`], and not a `Quaternion`.
|
||||
///
|
||||
/// This may be taken from a scene instance by calling the `Instance::from` method on the
|
||||
/// corresponding [`SceneInstance`]. This is important, as the `Vector4` is not serializable.
|
||||
///
|
||||
/// Before passing this instance into the shaders, construct a transformation matrix by calling
|
||||
/// [`MeshInstance::transform_matrix`].
|
||||
pub struct MeshInstance {
|
||||
/// The ID of the mesh corresponding to this instance.
|
||||
pub mesh_id: String,
|
||||
/// The translation of the instance from the center of the standard space. It is possible that
|
||||
/// local translation will be required in the future.
|
||||
pub translation: Vector4<f32>,
|
||||
/// The rotation of the instance. Note that since this is a [`Vector4`], or Euler angle, it
|
||||
/// suffers from gimbal lock. If this becomes a serious issue, an [`nalgebra::UnitQuaternion`]
|
||||
/// should be used instead.
|
||||
pub rotation: Vector4<f32>,
|
||||
/// The scale of the instance, represented as the scaling in XYZ coordinates.
|
||||
pub scale: Vector3<f32>,
|
||||
}
|
||||
|
||||
impl MeshInstance {
|
||||
/// Constructs a transformation matrix from the current translation, rotation, and scale.
|
||||
pub fn transform_matrix(&self) -> Matrix4<f32> {
|
||||
Matrix4::new_translation(&self.translation.xyz()) *
|
||||
// There's a possibility that these can be altered. Sometimes they're multiplied in the
|
||||
// order ZYX. They are technically applied in the opposite order written here, so in
|
||||
// this case it's actually applying Z, then Y, then X. They *do* produce different results.
|
||||
Matrix4::from_axis_angle(&Vector3::x_axis(), self.rotation.x) *
|
||||
Matrix4::from_axis_angle(&Vector3::y_axis(), self.rotation.y) *
|
||||
Matrix4::from_axis_angle(&Vector3::z_axis(), self.rotation.z) *
|
||||
Matrix4::new_nonuniform_scaling(&self.scale)
|
||||
}
|
||||
|
||||
/// Constructs a `MeshInstance` from the given [`SceneInstance`] and mesh ID.
|
||||
fn from_scene_instance(
|
||||
mesh_id: String,
|
||||
SceneInstance {
|
||||
translation,
|
||||
rotation,
|
||||
scale,
|
||||
}: SceneInstance,
|
||||
) -> Self {
|
||||
Self {
|
||||
mesh_id,
|
||||
translation: Vector3::from(translation).to_homogeneous(),
|
||||
rotation: Vector3::from(rotation).to_homogeneous(),
|
||||
scale: Vector3::from(scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use nalgebra::Vector3;
|
||||
use uuid::Uuid;
|
||||
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
|
||||
use vulkano::memory::allocator::{AllocationCreateInfo, MemoryAllocator, MemoryTypeFilter};
|
||||
use wavefront::Obj;
|
||||
use crate::common::scene::Scene;
|
||||
use crate::gpu::vertex3::Vertex3;
|
||||
|
||||
/// This struct is slightly misleading - it doesn't actually contain the mesh information, but
|
||||
/// the locations of the mesh's information in the overall application's vertex buffer, index buffer,
|
||||
/// and other such structures.
|
||||
///
|
||||
/// Once loaded, the mesh should be accessible via ID through a registry-type struct, which simply
|
||||
/// maintains the list of meshes that have been loaded by the application so far and which assigns
|
||||
/// the correct indices for buffer-to-render logic.
|
||||
///
|
||||
/// That means that this `Mesh` struct is actually just an entry within that registry. They should
|
||||
/// rarely be used directly, and should never be edited.
|
||||
#[derive(Debug)]
|
||||
pub struct Mesh {
|
||||
pub id: String,
|
||||
pub vertex_loc: usize,
|
||||
pub index_loc: usize,
|
||||
}
|
||||
|
||||
impl Mesh {
|
||||
pub fn new(
|
||||
id: impl Into<String>,
|
||||
vertex_loc: usize,
|
||||
index_loc: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
vertex_loc,
|
||||
index_loc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Mesh Registry
|
||||
/// A wrapper around a series of [`Mesh`] structs, keyed by a unique ID, and the buffers that are
|
||||
/// used by the rendering pipeline.
|
||||
#[derive(Debug)]
|
||||
pub struct MeshRegistry {
|
||||
meshes: HashMap<String, Mesh>,
|
||||
allocator: Arc<dyn MemoryAllocator>,
|
||||
pub vertex_buffer: Arc<Subbuffer<[Vertex3]>>,
|
||||
pub index_buffer: Arc<Subbuffer<[u32]>>,
|
||||
}
|
||||
|
||||
impl MeshRegistry {
|
||||
pub fn new(
|
||||
allocator: Arc<dyn MemoryAllocator>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let vertex_buffer = Arc::new(Buffer::new_slice::<Vertex3>(
|
||||
allocator.clone(),
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::VERTEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
memory_type_filter: MemoryTypeFilter::PREFER_HOST |
|
||||
MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
..Default::default()
|
||||
},
|
||||
1_000_000,
|
||||
)?);
|
||||
|
||||
let index_buffer = Arc::new(Buffer::new_slice::<u32>(
|
||||
allocator.clone(),
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::INDEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
memory_type_filter: MemoryTypeFilter::PREFER_HOST |
|
||||
MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
..Default::default()
|
||||
},
|
||||
3_000_000,
|
||||
)?);
|
||||
|
||||
Ok(Self {
|
||||
meshes: HashMap::new(),
|
||||
allocator,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a `MeshRegistry` from the provided [`Scene`].
|
||||
///
|
||||
/// This method will load all the meshes indicated by the scene and allocate the required
|
||||
/// buffers. This significantly simplifies the allocation process, as the buffers only need to
|
||||
/// be allocated once.
|
||||
pub fn from_scene(scene: &Scene, allocator: Arc<dyn MemoryAllocator>) -> Self {
|
||||
// First, load all the meshes into a single map.
|
||||
println!("Loading meshes from scene");
|
||||
let meshes = scene.mesh_paths.iter().map(|(id, path)|
|
||||
(id.clone(), Obj::from_file(path).expect("Couldn't read wavefront file"))
|
||||
).collect::<HashMap<String, Obj>>();
|
||||
|
||||
// Second, build two more maps that contain the vertex and index information.
|
||||
let verts = meshes.iter().map(|(id, obj)|
|
||||
(id.clone(), obj.vertices().map(|v| v.position().into()).collect())
|
||||
).collect::<HashMap<String, Vec<Vector3<f32>>>>();
|
||||
|
||||
let indices = meshes.iter().map(|(id, obj)|
|
||||
(id.clone(), obj.vertices().map(|v| v.position_index()).collect())
|
||||
).collect::<HashMap<String, Vec<usize>>>();
|
||||
|
||||
// Apply static transformations here. The general idea is that we don't want to have to read
|
||||
// any information from the buffer without the correct information. We do want to keep track
|
||||
// of what mesh is at what location in the list though.
|
||||
println!("Applying static mesh transformations");
|
||||
|
||||
// Get Vector3's as a list of Vertex3's (CPU to GPU)
|
||||
|
||||
// Third, allocate and fill buffers with this information.
|
||||
// let vert_buffer = Self::alloc_vert_buffer(allocator, verts.values().collect());
|
||||
|
||||
// Fourth, return the struct.
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn alloc_vert_buffer(
|
||||
alloc: Arc<dyn MemoryAllocator>,
|
||||
verts: Vec<Vertex3>
|
||||
) -> Arc<Subbuffer<[Vertex3]>> {
|
||||
println!("Allocating vertex buffer");
|
||||
Arc::new(Buffer::from_iter(
|
||||
alloc.clone(),
|
||||
BufferCreateInfo {
|
||||
usage: BufferUsage::VERTEX_BUFFER,
|
||||
..Default::default()
|
||||
},
|
||||
AllocationCreateInfo {
|
||||
memory_type_filter: MemoryTypeFilter::PREFER_HOST |
|
||||
MemoryTypeFilter::HOST_SEQUENTIAL_WRITE,
|
||||
..Default::default()
|
||||
},
|
||||
verts
|
||||
).expect("Couldn't allocate vertex buffer"))
|
||||
}
|
||||
|
||||
/// Attempts to find a [`Mesh`] with the given ID, returning `None` if it's not found.
|
||||
pub fn get(&self, uuid: &str) -> Option<&Mesh> {
|
||||
self.meshes.get(uuid)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod mesh;
|
||||
pub mod scene;
|
||||
pub mod instance;
|
||||
@@ -0,0 +1,74 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A scene, which holds all information required to render a world.
|
||||
///
|
||||
/// This includes all mesh information, and all the information that is required to place
|
||||
/// instances of these meshes in the world.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub struct Scene {
|
||||
/// The name of the scene.
|
||||
pub name: String,
|
||||
/// The collection of paths to the meshes, relative to the current scene file.
|
||||
/// This collection is keyed on the ID of the mesh, which can be used later on to place
|
||||
/// the meshes in the scenes with specific instances. This ID will also be used to register
|
||||
/// the meshes with the application's registries, which will allow the program to associate
|
||||
/// specific behavior with the mesh.
|
||||
pub mesh_paths: HashMap<String, String>,
|
||||
/// A collection of instances that are keyed by the mesh's IDs. Each instance can be used to
|
||||
/// place a [`Mesh`] in the rendered scene.
|
||||
pub instances: HashMap<String, SceneInstance>,
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
/// Converts this `Scene` into its byte representation.
|
||||
pub fn bytes(&self) -> Vec<u8> {
|
||||
postcard::to_allocvec(self)
|
||||
.expect("Couldn't serialize scene")
|
||||
}
|
||||
|
||||
/// Reads a `scene` from the given path. Automatically resolves all paths for the meshes that
|
||||
/// are referenced in the struct.
|
||||
///
|
||||
/// Reading a scene file allows for all meshes to be loaded at once, which therefore allows
|
||||
/// the program to allocate pipeline buffers just once during the load of this. It would likely
|
||||
/// be reasonable to create a `MeshRegistry` and `InstanceRegistry` at the same time, with
|
||||
/// respect to a single loaded `Scene`.
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Self {
|
||||
let path = path.as_ref();
|
||||
let mut file = std::fs::File::open(path).unwrap();
|
||||
let mut contents = Vec::new();
|
||||
let _ = file.read_to_end(&mut contents).unwrap();
|
||||
|
||||
let mut scene: Self = postcard::from_bytes(contents.as_slice())
|
||||
.expect("Couldn't deserialize scene");
|
||||
|
||||
let base = path.parent().unwrap_or(&Path::new("."));
|
||||
scene.mesh_paths = scene.mesh_paths
|
||||
.into_iter()
|
||||
.map(|(id, mesh_path)| {
|
||||
let res = base.join(mesh_path);
|
||||
let str = res.to_str().unwrap_or_default();
|
||||
|
||||
(id, str.to_string())
|
||||
}).collect::<HashMap<String, String>>();
|
||||
|
||||
scene
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all information about an instance in the scene, specifically the name of the mesh it
|
||||
/// should use and the translation, rotation, and scale of it.
|
||||
///
|
||||
/// After deserialization, this struct should be converted into an [`Instance`], which contains
|
||||
/// defined vectors that can be used to work with the object before sending it to the shaders.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub struct SceneInstance {
|
||||
pub translation: [f32; 3],
|
||||
pub rotation: [f32; 3],
|
||||
pub scale: [f32; 3],
|
||||
}
|
||||
Reference in new issue
Block a user