feat: attempt to implement math library

Adds many files, but the next thing is to implement the MeshRegistry properly.
This commit is contained in:
alphafetus committed 2026-05-08 08:02:38 -04:00
1 parent 9e4f531bd4
commit e7659ba387
18 files changed
+1232 -136

No files matched your search

+19 -44
View File
@@ -1,6 +1,6 @@
use std::error::Error;
use std::sync::Arc;
use vulkano::buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
use vulkano::command_buffer::allocator::{StandardCommandBufferAllocator, StandardCommandBufferAllocatorCreateInfo};
use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, PrimaryAutoCommandBuffer, RenderPassBeginInfo, SubpassBeginInfo, SubpassContents, SubpassEndInfo};
use vulkano::descriptor_set::allocator::{DescriptorSetAllocator, StandardDescriptorSetAllocator};
@@ -28,37 +28,10 @@ use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::window::{Window, WindowAttributes, WindowId};
use crate::math::Vector2;
use crate::common::mesh::MeshRegistry;
use crate::gpu::vertex3::Vertex3;
use crate::triangle;
/// The `Vertex2` is an intermediate structure used to represent the [`Vector2`] in the graphics
/// pipeline. It specifies a positional value and can be passed directly into the shaders.
/// Note that conversion between a `Vector2` and a `Vertex2` may take extra computation time.
///
/// Honestly, I'm still not sure if this should be handled on a per-shader basis, as the position
/// argument changes with each shader.
#[derive(BufferContents, Vertex)]
#[repr(C)]
struct Vertex2 {
#[format(R32G32_SFLOAT)]
position: [f32; 2],
}
impl From<Vector2> for Vertex2 {
fn from(v: Vector2) -> Self {
Self {
position: [v.x, v.y],
}
}
}
#[derive(BufferContents, Vertex)]
#[repr(C)]
struct Vertex3 {
#[format(R32G32B32_SFLOAT)]
position: [f32; 3],
}
struct VulkanContext {
window: Arc<Window>,
device: Arc<Device>,
@@ -73,9 +46,8 @@ struct VulkanContext {
render_pass: Arc<RenderPass>,
framebuffers: Vec<Arc<Framebuffer>>,
pipeline: Arc<GraphicsPipeline>,
vertex_buffer: Arc<Subbuffer<[Vertex2]>>,
index_buffer: Arc<Subbuffer<[u32]>>,
cmd_buffers: Vec<Arc<PrimaryAutoCommandBuffer>>,
mesh_registry: Arc<MeshRegistry>,
}
impl VulkanContext {
@@ -201,10 +173,10 @@ impl VulkanContext {
..Default::default()
},
vec![
Vertex2 { position: [0.5, 0.5] },
Vertex2 { position: [-0.5, 0.5] },
Vertex2 { position: [-0.5, -0.5] },
Vertex2 { position: [0.5, -0.5] },
Vertex3 { position: [0.5, 0.5, 0.0] },
Vertex3 { position: [-0.5, 0.5, 0.0] },
Vertex3 { position: [-0.5, -0.5, 0.0] },
Vertex3 { position: [0.5, -0.5, 0.0] },
]
)?);
@@ -234,6 +206,10 @@ impl VulkanContext {
&index_buffer
)?;
let mesh_registry = Arc::new(MeshRegistry::new(
mem_allocator.clone(),
)?);
Ok(Self {
window,
device,
@@ -248,9 +224,8 @@ impl VulkanContext {
frag_shader,
framebuffers,
pipeline,
vertex_buffer,
index_buffer,
cmd_buffers
cmd_buffers,
mesh_registry,
})
}
@@ -378,7 +353,7 @@ impl VulkanContext {
let fs = frag_shader.entry_point("main")
.ok_or("Failed to find fragment shader entry point")?;
let vertex_input_state = Vertex2::per_vertex()
let vertex_input_state = Vertex3::per_vertex()
.definition(&vs)?;
let stages = vec![
@@ -432,7 +407,7 @@ impl VulkanContext {
queue: &Arc<Queue>,
pipeline: &Arc<GraphicsPipeline>,
framebuffers: &Vec<Arc<Framebuffer>>,
vertex_buffer: &Subbuffer<[Vertex2]>,
vertex_buffer: &Arc<Subbuffer<[Vertex3]>>,
index_buffer: &Subbuffer<[u32]>
) -> Result<Vec<Arc<PrimaryAutoCommandBuffer>>, Box<dyn Error>> {
let buffers = framebuffers.iter()
@@ -456,7 +431,7 @@ impl VulkanContext {
}
).ok()?
.bind_pipeline_graphics(pipeline.clone()).ok()?
.bind_vertex_buffers(0, vertex_buffer.clone()).ok()?
.bind_vertex_buffers(0, vertex_buffer.as_ref().clone()).ok()?
.bind_index_buffer(index_buffer.clone()).ok()?
// .draw(vertex_buffer.len() as u32, 1, 0, 0).ok()?
.draw_indexed(index_buffer.len() as u32, 1, 0, 0, 0).ok()?
@@ -506,8 +481,8 @@ impl VulkanContext {
&self.queue,
&new_pipeline,
&self.framebuffers,
&self.vertex_buffer,
&self.index_buffer
&self.mesh_registry.vertex_buffer,
&self.mesh_registry.index_buffer
)?;
self.pipeline = new_pipeline;
+55
View File
@@ -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),
}
}
}
+155
View File
@@ -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)
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod mesh;
pub mod scene;
pub mod instance;
+74
View File
@@ -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],
}
+29
View File
@@ -0,0 +1,29 @@
use vulkano::buffer::BufferContents;
use vulkano::pipeline::graphics::vertex_input::Vertex;
use crate::common::instance::MeshInstance;
/// A transformation matrix for a mesh that can be passed into the shaders.
#[derive(BufferContents, Vertex)]
#[repr(C)]
pub struct GpuInstance {
#[format(R32G32B32A32_SFLOAT)]
pub transform_col1: [f32; 4],
#[format(R32G32B32A32_SFLOAT)]
pub transform_col2: [f32; 4],
#[format(R32G32B32A32_SFLOAT)]
pub transform_col3: [f32; 4],
#[format(R32G32B32A32_SFLOAT)]
pub transform_col4: [f32; 4],
}
impl Into<GpuInstance> for MeshInstance {
fn into(self) -> GpuInstance {
let m = self.transform_matrix();
GpuInstance {
transform_col1: m.column(0).into(),
transform_col2: m.column(1).into(),
transform_col3: m.column(2).into(),
transform_col4: m.column(3).into(),
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod vertex3;
pub mod instance;
+15
View File
@@ -0,0 +1,15 @@
use vulkano::buffer::BufferContents;
use vulkano::pipeline::graphics::vertex_input::Vertex;
#[derive(Debug, BufferContents, Vertex)]
#[repr(C)]
pub struct Vertex3 {
#[format(R32G32B32_SFLOAT)]
pub(crate) position: [f32; 3],
}
impl From<&[f32; 3]> for Vertex3 {
fn from(v: &[f32; 3]) -> Self {
Self { position: v.clone() }
}
}
+2
View File
@@ -1,5 +1,7 @@
mod app;
pub mod math;
mod common;
pub mod gpu;
use std::error::Error;
use vulkano::swapchain::Surface;
+78 -64
View File
@@ -1,11 +1,11 @@
use std::ops::{Add, Div, Index, IndexMut, Mul, Sub};
use crate::math::traits::Matrix;
use crate::math::Vector2;
use std::ops::{Add, Index, IndexMut, Mul, Sub};
use crate::math::traits::{Matrix, Vector};
use crate::math::{Vector2};
#[derive(Clone, Debug, PartialEq, PartialOrd, Copy, Default)]
pub struct Matrix2 {
row1: [f32; 2],
row2: [f32; 2],
row1: Vector2,
row2: Vector2,
}
impl Matrix2 {
@@ -29,23 +29,23 @@ impl Matrix2 {
}
impl Index<usize> for Matrix2 {
type Output = [f32; 2];
type Output = Vector2;
fn index(&self, index: usize) -> &Self::Output {
if index == 0 {
&self.row1
} else {
&self.row2
fn index(&self, idx: usize) -> &Self::Output {
match idx {
0 => &self.row1,
1 => &self.row2,
_ => panic!("Index out of bounds"),
}
}
}
impl IndexMut<usize> for Matrix2 {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
if index == 0 {
&mut self.row1
} else {
&mut self.row2
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
match idx {
0 => &mut self.row1,
1 => &mut self.row2,
_ => panic!("Index out of bounds"),
}
}
}
@@ -55,8 +55,8 @@ impl Add for Matrix2 {
fn add(self, rhs: Self) -> Self::Output {
Self {
row1: [self[0][0] + rhs[0][0], self[0][1] + rhs[0][1]],
row2: [self[1][0] + rhs[1][0], self[1][1] + rhs[1][1]],
row1: [self[0][0] + rhs[0][0], self[0][1] + rhs[0][1]].into(),
row2: [self[1][0] + rhs[1][0], self[1][1] + rhs[1][1]].into(),
}
}
}
@@ -66,19 +66,29 @@ impl Sub for Matrix2 {
fn sub(self, rhs: Self) -> Self::Output {
Self {
row1: [self[0][0] - rhs[0][0], self[0][1] - rhs[0][1]],
row2: [self[1][0] - rhs[1][0], self[1][1] - rhs[1][1]],
row1: [self[0][0] - rhs[0][0], self[0][1] - rhs[0][1]].into(),
row2: [self[1][0] - rhs[1][0], self[1][1] - rhs[1][1]].into(),
}
}
}
impl Mul<f32> for Matrix2 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
type Output = Matrix2;
fn mul(self, rhs: f32) -> Matrix2 {
Self {
row1: [self[0][0] * rhs, self[0][1] * rhs],
row2: [self[1][0] * rhs, self[1][1] * rhs],
row1: self.row1 * rhs,
row2: self.row2 * rhs,
}
}
}
impl Mul<Vector2> for Matrix2 {
type Output = Vector2;
fn mul(self, rhs: Vector2) -> Self::Output {
Vector2 {
x: self[0].dot(rhs),
y: self[1].dot(rhs),
}
}
}
@@ -95,58 +105,62 @@ impl Mul for Matrix2 {
type Output = Self;
fn mul(self, rhs: Matrix2) -> Self::Output {
let other = rhs.transpose();
Self {
row1: [self[0][0] * rhs[0][0] + self[0][1] * rhs[1][0], self[0][0] * rhs[0][1] + self[0][1] * rhs[1][1]],
row2: [self[1][0] * rhs[0][0] + self[1][1] * rhs[1][0], self[1][0] * rhs[0][1] + self[1][1] * rhs[1][1]],
row1: [self[0].dot(other[0]), self[0].dot(other[1])].into(),
row2: [self[1].dot(other[0]), self[1].dot(other[1])].into(),
}
}
}
impl Div<f32> for Matrix2 {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
todo!()
}
}
impl Div for Matrix2 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
todo!()
}
}
impl Matrix for Matrix2 {
impl Matrix<Vector2> for Matrix2 {
fn identity() -> Self {
Self {
row1: [1.0, 0.0],
row2: [0.0, 1.0],
row1: [1.0, 0.0].into(),
row2: [0.0, 1.0].into(),
}
}
fn transpose(self) -> Self {
fn transpose(&self) -> Self {
Self {
row1: [self[0][0], self[1][0]].into(),
row2: [self[0][1], self[1][1]].into(),
}
}
fn determinant(&self) -> f32 {
self[0][0] * self[1][1] - self[0][1] * self[1][0]
}
fn minor(&self) -> Self {
Self {
row1: [self[1][1], self[1][0]].into(),
row2: [self[0][1], self[0][0]].into(),
}
}
fn cofactor(&self) -> Self {
let mut minor = self.minor();
for row in 0..2 {
for col in 0..2 {
let even = (row + col) % 2 == 0;
if even {
minor[row][col] = -minor[row][col];
}
}
}
minor
}
fn adjugate(&self) -> Self {
todo!()
}
fn determinant(self) -> f32 {
todo!()
}
fn inverse(&self) -> Self {
let adj = self.adjugate();
let det = self.determinant();
fn minor(self) -> Self {
todo!()
}
fn cofactor(self) -> Self {
todo!()
}
fn adjugate(self) -> Self {
todo!()
}
fn inverse(self) -> Self {
todo!()
(1.0 / det) * adj
}
}
+130
View File
@@ -0,0 +1,130 @@
use std::ops::{Add, Div, Mul, Sub};
use crate::math::traits::{Matrix, Vector};
use crate::math::Vector4;
#[derive(Debug, Clone, Default, Copy, PartialEq)]
pub struct Matrix4 {
row1: Vector4,
row2: Vector4,
row3: Vector4,
row4: Vector4,
}
impl Matrix4 {
}
impl Add for Matrix4 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
row1: self.row1 + rhs.row1,
row2: self.row2 + rhs.row2,
row3: self.row3 + rhs.row3,
row4: self.row4 + rhs.row4,
}
}
}
impl Sub for Matrix4 {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
row1: self.row1 - rhs.row1,
row2: self.row2 - rhs.row2,
row3: self.row3 - rhs.row3,
row4: self.row4 - rhs.row4,
}
}
}
impl Div<f32> for Matrix4 {
type Output = Self;
/// Equivalent to multiplying by the reciprocal of `rhs`.
fn div(self, rhs: f32) -> Self::Output {
let rhs = 1.0 / rhs;
self * rhs
}
}
impl Mul<f32> for Matrix4 {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self {
row1: self.row1 * rhs,
row2: self.row2 * rhs,
row3: self.row3 * rhs,
row4: self.row4 * rhs,
}
}
}
impl Mul for Matrix4 {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
let rhs = rhs.transpose();
Self {
row1: [self.row1.dot(rhs.row1), self.row1.dot(rhs.row2), self.row1.dot(rhs.row3), self.row1.dot(rhs.row4)].into(),
row2: [self.row2.dot(rhs.row1), self.row2.dot(rhs.row2), self.row2.dot(rhs.row3), self.row2.dot(rhs.row4)].into(),
row3: [self.row3.dot(rhs.row1), self.row3.dot(rhs.row2), self.row3.dot(rhs.row3), self.row3.dot(rhs.row4)].into(),
row4: [self.row4.dot(rhs.row1), self.row4.dot(rhs.row2), self.row4.dot(rhs.row3), self.row4.dot(rhs.row4)].into(),
}
}
}
impl Mul<Vector4> for Matrix4 {
type Output = Vector4;
fn mul(self, rhs: Vector4) -> Vector4 {
Vector4 {
x: self.row1.dot(rhs),
y: self.row2.dot(rhs),
z: self.row3.dot(rhs),
w: self.row4.dot(rhs),
}
}
}
impl Matrix<Vector4> for Matrix4 {
fn identity() -> Self {
Self {
row1: Vector4::new(1.0, 0.0, 0.0, 0.0),
row2: Vector4::new(0.0, 1.0, 0.0, 0.0),
row3: Vector4::new(0.0, 0.0, 1.0, 0.0),
row4: Vector4::new(0.0, 0.0, 0.0, 1.0),
}
}
fn transpose(&self) -> Self {
Self {
row1: [self.row1.x, self.row2.x, self.row3.x, self.row4.x].into(),
row2: [self.row1.y, self.row2.y, self.row3.y, self.row4.y].into(),
row3: [self.row1.z, self.row2.z, self.row3.z, self.row4.z].into(),
row4: [self.row1.w, self.row2.w, self.row3.w, self.row4.w].into(),
}
}
fn determinant(&self) -> f32 {
todo!()
}
fn minor(&self) -> Self {
todo!()
}
fn cofactor(&self) -> Self {
todo!()
}
fn adjugate(&self) -> Self {
todo!()
}
fn inverse(&self) -> Self {
todo!()
}
}
+22 -10
View File
@@ -1,6 +1,8 @@
use std::ops::{Add, Div, Mul, Sub};
use crate::math::Vector4;
pub trait Vector: Copy + Default + PartialEq + PartialOrd + Add + Sub + Div<f32, Output = Self> + Mul<f32, Output = Self> {
pub trait Vector: Clone + Copy + Default + PartialEq + Add + Sub + PartialOrd
+ Div<f32, Output = Self> + Mul<f32, Output = Self> {
/// Gets the dot product of the two vectors.
fn dot(self, other: Self) -> f32;
@@ -26,32 +28,42 @@ pub trait Vector: Copy + Default + PartialEq + PartialOrd + Add + Sub + Div<f32,
}
}
pub trait Matrix: Copy + Default + PartialEq + PartialOrd + Add + Sub
+ Div<f32, Output = Self> + Div
+ Mul<f32, Output = Self> + Mul {
/// # Matrix Trait
///
/// Requires a few manual implementations, specifically the `identity`, `transpose`, and `determinant`.
/// All other information can be found by the trait itself.
///
/// Note that this trait does not require implementation of the `Div` trait at all - this is because
/// matrices do not have division, only multiplication by a scalar value. This doesn't mean that the
/// scalar cannot be a decimal value, however.
pub trait Matrix<TVec>: Clone + Copy + Default + PartialEq + Add + Sub
+ Mul<f32, Output = Self> + Mul<TVec, Output = TVec>
+ Mul<Output = Self>
where TVec: Vector
{
/// Creates an identity matrix.
fn identity() -> Self;
/// Gets the transpose of this matrix.
fn transpose(self) -> Self;
fn transpose(&self) -> Self;
/// Gets the determinant of this matrix.
fn determinant(self) -> f32;
fn determinant(&self) -> f32;
/// Finds the minor of this matrix. The element at location (`i`, `j`) of the minor is the
/// determinant of the submatrix obtained by removing row `i` and column `j` in the original
/// matrix.
fn minor(self) -> Self;
fn minor(&self) -> Self;
/// Finds the cofactor of this matrix. The element at location (`i`, `j`) of the cofactor is
/// defined as -1^ij * Mij, where Mij is the element at (`i`, `j`) in the minor of the matrix.
/// This is effectively flipping the sign of every other element in the minor of this matrix.
fn cofactor(self) -> Self;
fn cofactor(&self) -> Self;
/// Gets the adjugate of this matrix. This is the transpose of the cofactor of this matrix.
fn adjugate(self) -> Self;
fn adjugate(&self) -> Self;
/// Gets the inverse of this matrix. Note that this is a very expensive operation and should
/// be used as sparingly as possible.
fn inverse(self) -> Self;
fn inverse(&self) -> Self;
}
+22 -1
View File
@@ -1,4 +1,4 @@
use std::ops::{Add, Div, Mul, Sub};
use std::ops::{Add, Div, Mul, Sub, Index, IndexMut};
use crate::math::traits::Vector;
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
@@ -17,6 +17,27 @@ impl Vector2 {
}
}
impl Index<usize> for Vector2 {
type Output = f32;
fn index(&self, idx: usize) -> &f32 {
match idx {
0 => &self.x,
1 => &self.y,
_ => panic!("Index out of bounds"),
}
}
}
impl IndexMut<usize> for Vector2 {
fn index_mut(&mut self, idx: usize) -> &mut f32 {
match idx {
0 => &mut self.x,
1 => &mut self.y,
_ => panic!("Index out of bounds"),
}
}
}
impl Add for Vector2 {
type Output = Self;
+53 -7
View File
@@ -11,8 +11,24 @@ pub struct Vector4 {
}
impl Vector4 {
pub fn new() -> Self {
Self::default()
pub fn new(
x: f32,
y: f32,
z: f32,
w: f32,
) -> Self {
Self {
x, y, z, w
}
}
pub fn one() -> Self {
Self {
x: 1.0,
y: 1.0,
z: 1.0,
w: 1.0,
}
}
pub fn xyzw(&self) -> (f32, f32, f32, f32) {
@@ -83,6 +99,20 @@ impl Mul<Vector4> for f32 {
}
}
impl Mul for Vector4 {
type Output = Self;
/// Computes the Hadamard product of the two vectors.
fn mul(self, rhs: Self) -> Self {
Self {
x: self.x * rhs.x,
y: self.y * rhs.y,
z: self.z * rhs.z,
w: self.w * rhs.w,
}
}
}
impl Div<f32> for Vector4 {
type Output = Self;
@@ -119,12 +149,28 @@ impl Vector for Vector4 {
}
impl From<[f32; 4]> for Vector4 {
fn from(v: [f32; 4]) -> Self {
fn from([x, y, z, w]: [f32; 4]) -> Self {
Self {
x: v[0],
y: v[1],
z: v[2],
w: v[3],
x, y, z, w,
}
}
}
impl From<[f32; 3]> for Vector4 {
/// Creates a homogenous `Vector4` from a 3D positional vector.
fn from([x, y, z]: [f32; 3]) -> Self {
Self {
x, y, z,
w: 1.0,
}
}
}
impl From<Vector3> for Vector4 {
fn from(Vector3 { x, y, z }: Vector3) -> Self {
Self {
x, y, z,
w: 1.0
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
#version 460
layout(location = 0) in vec2 position;
layout(location = 0) in vec3 position;
void main() {
gl_Position = vec4(position, 0.0, 1.0);
gl_Position = vec4(position, 1.0);
}