commit ffb009b7370afa85b0b9ac5402fbc5662f292cd0 Author: Skye Date: Thu Oct 24 19:46:07 2024 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ce0e4ed --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "library-maths" +version = "0.1.0" +edition = "2021" + +[dependencies] +winit = { version = "0.29", features = ["rwh_05"] } +env_logger = "0.10" +log = "0.4" +wgpu = "22.1.0" +tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] } +bytemuck = "*" +rand = "*" + +[profile.release] +opt-level = 3 + +[profile.dev] +opt-level = 2 \ No newline at end of file diff --git a/src/canvas.rs b/src/canvas.rs new file mode 100644 index 0000000..2202ddd --- /dev/null +++ b/src/canvas.rs @@ -0,0 +1,95 @@ +use std::vec; + +use winit::{event::{self, ElementState, Event, KeyEvent, WindowEvent}, event_loop::{EventLoop, EventLoopWindowTarget}, keyboard::{KeyCode, PhysicalKey}, window::{self, WindowBuilder}}; + +use crate::{state::state::State, structure::{enums::{micellaneous::Shape, Resolution}, structs::Vertex, traits::Drawable}}; + +pub struct Canvas { + pub(crate) added: Vec>, + pub(crate) resolution: Resolution +} + +impl Canvas { + pub fn new(resolution: Resolution) -> Self { + Canvas { + added: vec![], + resolution + } + } + + pub fn add(&mut self, added: Shape) { + match added { + Shape::Regular(polygon) => self.added.push(Box::new(polygon)), + } + } + + pub async fn run(&self) { + let event_loop = EventLoop::new().unwrap(); + let window = WindowBuilder::new().with_title("ciao").build(&event_loop).unwrap(); + + let mut verts = vec![]; + self.uniform(&mut verts); + + let mut state = State::new(&window, Some(bytemuck::cast_slice(&verts)), self.resolution).await; + + event_loop + .run(move |event, control_flow| match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == state.window().id() => { + if !state.input(event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::Escape), + .. + }, + .. + } => control_flow.exit(), + + WindowEvent::Resized(size) => { + state.resize(*size); + } + + WindowEvent::RedrawRequested => { + state.window().request_redraw(); + /* + if !surface_configured { + return; + } ?????? + */ + + state.update(); + + match state.render() { + Ok(_) => (), + Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { + state.resize(state.size); + } + Err(wgpu::SurfaceError::OutOfMemory) => { + log::error!("OutOfMemory"); + control_flow.exit(); + } + Err(wgpu::SurfaceError::Timeout) => { + log::warn!("Surface Timeout") + } + } + } + + _ => (), + } + } + } + _ => {} + }) + .unwrap(); + } + + fn uniform(&self, vector: &mut Vec) { + vector.extend(self.added.iter().flat_map(|d| d.uniform())); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8e12896 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,84 @@ +use state::state::State; +use winit::{event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::EventLoop, keyboard::{KeyCode, PhysicalKey}, window::WindowBuilder}; + +mod state; +mod structure; +mod canvas; + +// * re-exports +pub use canvas::Canvas; +pub use structure::enums::Resolution; +pub use structure::enums::micellaneous::Shape; +pub use structure::structs::baseline::polygon::Polygon; +pub use structure::structs::Vertex; +pub use structure::structs::{Col, Pos}; +// * re-exports + +impl State<'_> { + pub fn run(&mut self) { + env_logger::init(); + let event_loop = EventLoop::new().unwrap(); + let window = WindowBuilder::new() + .with_title("uhhhhh") + .build(&event_loop) + .unwrap(); + + event_loop + .run(move |event, control_flow| match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == self.window().id() => { + if !self.input(event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + event: + KeyEvent { + state: ElementState::Pressed, + physical_key: PhysicalKey::Code(KeyCode::Escape), + .. + }, + .. + } => control_flow.exit(), + + WindowEvent::Resized(size) => { + self.resize(*size); + } + + WindowEvent::RedrawRequested => { + self.window().request_redraw(); + /* + if !surface_configured { + return; + } ?????? + */ + + self.update(); + + match self.render() { + Ok(_) => (), + Err( + wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated, + ) => { + self.resize(self.size); + } + Err(wgpu::SurfaceError::OutOfMemory) => { + log::error!("OutOfMemory"); + control_flow.exit(); + } + Err(wgpu::SurfaceError::Timeout) => { + log::warn!("Surface Timeout") + } + } + } + + _ => (), + } + } + } + _ => {} + }) + .unwrap(); + } +} diff --git a/src/shaders/shader.wgsl b/src/shaders/shader.wgsl new file mode 100644 index 0000000..98a6548 --- /dev/null +++ b/src/shaders/shader.wgsl @@ -0,0 +1,29 @@ +// Vertex shader + +struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec3, +} + + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) color: vec3, +}; + +@vertex +fn vs_main( + model: VertexInput +) -> VertexOutput { + var out: VertexOutput; + out.clip_position = vec4(model.position, 1.0); + out.color = model.color; + return out; +} + +// Fragment shader + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(in.color, 1.0); +} diff --git a/src/state/mod.rs b/src/state/mod.rs new file mode 100644 index 0000000..0d0b840 --- /dev/null +++ b/src/state/mod.rs @@ -0,0 +1 @@ +pub mod state; \ No newline at end of file diff --git a/src/state/state.rs b/src/state/state.rs new file mode 100644 index 0000000..514bf1e --- /dev/null +++ b/src/state/state.rs @@ -0,0 +1,315 @@ +use super::super::structure::{enums::Resolution, structs::Vertex}; +use wgpu::{ + util::{BufferInitDescriptor, DeviceExt}, + BufferUsages, Color, PipelineLayout, PipelineLayoutDescriptor, RenderPassColorAttachment, + RenderPipelineDescriptor, ShaderModuleDescriptor, VertexBufferLayout, VertexState, +}; +use winit::{ + event::WindowEvent, + event_loop::{self, EventLoop}, + window::{Window, WindowBuilder}, +}; + +pub struct State<'a> { + pub clear_color: wgpu::Color, + pub(crate) config: wgpu::SurfaceConfiguration, + pub(crate) device: wgpu::Device, + //pub index_buffer: wgpu::Buffer, + pub num_index: u32, + pub(crate) queue: wgpu::Queue, + pub(crate) render_pipeline: wgpu::RenderPipeline, + pub resolution: Resolution, + pub(crate) size: winit::dpi::PhysicalSize, + pub(crate) surface: wgpu::Surface<'a>, + pub(crate) vertex_buffer: wgpu::Buffer, + // The window must be declared after the surface so + // it gets dropped after it as the surface contains + // unsafe references to the window's resources. + pub(crate) window: &'a Window +} + +impl<'a> State<'a> { + // Creating some of the wgpu types requires async code + pub async fn new( + window: &'a Window, + verts: Option<&[u8]>, + resolution: Resolution, + ) -> State<'a> { + + // * ---------- SIZE ---------- + let size = window.inner_size(); + // * ---------- SIZE ---------- + + // * ---------- INSTANCE ---------- + let instance: wgpu::Instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + #[cfg(not(target_arch = "wasm32"))] + backends: wgpu::Backends::PRIMARY, + #[cfg(target_arch = "wasm32")] + backends: wgpu::Backends::GL, + ..Default::default() + }); + // * ---------- INSTANCE ---------- + + // * ---------- SURFACE ---------- + let surface = instance.create_surface(window).unwrap(); + // * ---------- SURFACE ---------- + + // * ---------- ADAPTER ---------- + let adapter: wgpu::Adapter = instance + .request_adapter(&wgpu::RequestAdapterOptionsBase { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + compatible_surface: Some(&surface), + }) + .await + .unwrap(); + // * ---------- ADAPTER ---------- + + // * ---------- DEVICE & QUEUE ---------- + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + required_features: wgpu::Features::POLYGON_MODE_LINE, + required_limits: { + if cfg!(target_arch = "wasm32") { + wgpu::Limits::downlevel_webgl2_defaults() + } else { + wgpu::Limits::default() + } + }, + label: None, + memory_hints: Default::default(), + }, + None, + ) + .await + .unwrap(); + // * ---------- DEVICE & QUEUE ---------- + + // * ---------- SURFACE CAPABILITIES ---------- + let surface_caps = surface.get_capabilities(&adapter); + // * ---------- SURFACE CAPABILITIES ---------- + + // * ---------- SURFACE FORMAT ---------- + // Shader code in this tutorial assumes an sRGB surface texture. Using a different + // one will result in all the colors coming out darker. If you want to support non + // sRGB surfaces, you'll need to account for that when drawing to the frame. + let surface_format = surface_caps + .formats + .iter() + .find(|f| f.is_srgb()) + .copied() + .unwrap_or(surface_caps.formats[0]); + // * ---------- SURFACE FORMAT ---------- + + // * ---------- SURFACE CONFIG ---------- + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + desired_maximum_frame_latency: 2, + }; + // * ---------- SURFACE CONFIG ---------- + + // * ---------- PIPELINE ---------- + let shader: wgpu::ShaderModule = device.create_shader_module(ShaderModuleDescriptor { + label: Some("shader"), + // source: wgpu::ShaderSource::Wgsl(include_str!("learn_shading.wgsl").into()), + source: wgpu::ShaderSource::Wgsl(include_str!("../shaders/shader.wgsl").into()), + }); + + let render_pipeline_layout: PipelineLayout = + device.create_pipeline_layout(&PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let render_pipeline: wgpu::RenderPipeline = + device.create_render_pipeline(&RenderPipelineDescriptor { + label: Some("Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: VertexState { + module: &shader, + entry_point: "vs_main", + compilation_options: Default::default(), + buffers: &[Vertex::desc()], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + compilation_options: Default::default(), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleStrip, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + unclipped_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + // * ---------- PIPELINE ---------- + + let verts_final = match verts { + Some(v) => v, + None => &[], + }; + + // ! ---------- VERT BUFFER ---------- + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: verts_final, + usage: BufferUsages::VERTEX, + }); + let num_index = (verts_final.len() / std::mem::size_of::()) as u32; + // ! ---------- VERT BUFFER ---------- + + // // ! ---------- INDEX BUFFER ---------- + // let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + // label: Some("Index Buffer"), + // contents: bytemuck::cast_slice(INDICES), + // usage: BufferUsages::INDEX, + // }); + // // ! ---------- INDEX BUFFER ---------- + + let clear_color = Color::WHITE; + + Self { + resolution, + window, + surface, + device, + queue, + config, + size, + clear_color, + render_pipeline, + vertex_buffer, + num_index, + //index_buffer, + } + } + + pub fn window(&self) -> &Window { + &self.window + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.height > 0 && new_size.width > 0 { + self.size = new_size; + + self.config.height = new_size.height; + self.config.width = new_size.width; + + self.surface.configure(&self.device, &self.config); + } + } + + pub fn input(&mut self, event: &WindowEvent) -> bool { + match event { + WindowEvent::CursorMoved { + device_id, + position, + } => { + self.clear_color = Color { + r: position.x / self.size.width as f64, + g: position.y / self.size.height as f64, + b: self.clear_color.b, + a: 1.0, + }; + true + } + + WindowEvent::MouseWheel { + device_id, + delta, + phase, + } => { + match *delta { + winit::event::MouseScrollDelta::LineDelta(x, y) => { + self.clear_color.b += (x as f64 + y as f64) / 50.0 + } + winit::event::MouseScrollDelta::PixelDelta(physical_position) => { + self.clear_color.b += physical_position.x + } + } + true + } + + _ => false, + } + } + + pub fn update(&mut self) {} + + pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("encoder"), + }); + + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(self.clear_color), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + //render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw(0..self.num_index, 0..1); + //render_pass.draw_indexed(0..self.num_index, 0, 0..1); + + // begin_render_pass borrows wncoder mutably, need to drop the borrow since .finish() takes ownership of it. + drop(render_pass); + + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + Ok(()) + } + + pub(crate) fn modify_vert_buffer(&mut self, verts: Vec) { + self.vertex_buffer = self.device.create_buffer_init(&BufferInitDescriptor { + label: Some("Vertex Buffer"), + // contents: bytemuck::cast_slice(&VERTS), + contents: bytemuck::cast_slice(&verts), + usage: BufferUsages::VERTEX, + }); + self.num_index = verts.len() as u32; + } +} diff --git a/src/structure/enums.rs b/src/structure/enums.rs new file mode 100644 index 0000000..5c7e43f --- /dev/null +++ b/src/structure/enums.rs @@ -0,0 +1,39 @@ +pub use micellaneous::Resolution; + + +pub mod micellaneous { + use crate::structure::structs::baseline::polygon::Polygon; + + + #[derive(Debug, Clone, Copy)] + pub enum Resolution { + Res_16_10, + Res_16_9, + Res_4_3, + Res_21_9, + Res_32_9, + Res_5_4, + Custom((u16, u16)), + } + + #[derive(Debug, Clone, Copy)] + pub enum Rotation { + HorizontalAlign, + VerticalAlign, + Custom(f32), + } + + #[derive(Debug, Clone)] + pub enum Shape { + Regular(Polygon), + } +} + +pub mod errors { + + #[derive(Debug, Clone, Copy)] + pub enum PolyError { + NotEnoughSides, + SidesAndTriangleNumberArentConsistant, + } +} diff --git a/src/structure/mod.rs b/src/structure/mod.rs new file mode 100644 index 0000000..5d06b38 --- /dev/null +++ b/src/structure/mod.rs @@ -0,0 +1,3 @@ +pub mod structs; +pub mod enums; +pub mod traits; \ No newline at end of file diff --git a/src/structure/structs.rs b/src/structure/structs.rs new file mode 100644 index 0000000..aef6ba8 --- /dev/null +++ b/src/structure/structs.rs @@ -0,0 +1,270 @@ +pub use baseline::color::Col; +pub use baseline::position::Pos; +pub use baseline::vertex::Vertex; + +pub mod baseline { + pub mod position { + use bytemuck::{bytes_of, Pod, Zeroable}; + + use crate::structure::traits::Drawable; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + pub struct Pos(pub(crate) [f32; 3]); + + impl Pos { + pub fn new(x: f32, y: f32, z: f32) -> Self { + Self([x, y, z]) + } + } + + unsafe impl Zeroable for Pos {} + unsafe impl Pod for Pos {} + } + + pub mod color { + use bytemuck::{Pod, Zeroable}; + use rand::Rng; + + use crate::structure::traits::Drawable; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + pub struct Col(pub(crate) [f32; 3]); + + impl Col { + pub fn new(r: f32, g: f32, b: f32) -> Self { + Self([r, g, b]) + } + pub fn random() -> Self { + Self::new( + rand::thread_rng().gen_range(0.0..=1.0), + rand::thread_rng().gen_range(0.0..=1.0), + rand::thread_rng().gen_range(0.0..=1.0), + ) + } + } + + unsafe impl Zeroable for Col {} + unsafe impl Pod for Col {} + } + + pub mod vertex { + use crate::structure::traits::Drawable; + use bytemuck::{NoUninit, Pod, Zeroable}; + use wgpu::VertexAttribute; + + use super::super::*; + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + pub struct Vertex { + pub(crate) position: Pos, + pub(crate) color: Col, + } + + unsafe impl Zeroable for Vertex {} + unsafe impl Pod for Vertex {} + + impl Vertex { + pub fn new(position: Pos, color: Col) -> Self { + Vertex { + position: position, + color: color, + } + } + + pub fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + VertexAttribute { + format: wgpu::VertexFormat::Float32x3, + offset: 0, + shader_location: 0, + }, + VertexAttribute { + format: wgpu::VertexFormat::Float32x3, + offset: std::mem::size_of::<[f32; 3]>() as u64, + shader_location: 1, + }, + ], + } + } + } + } + + pub mod triangle { + use super::vertex::Vertex; + + #[derive(Debug, Clone, Copy)] + pub struct Triangle([Vertex; 3]); + + impl Triangle { + pub fn new(vertices: [Vertex; 3]) -> Self { + Triangle(vertices) + } + + pub fn verts(&self) -> [Vertex; 3] { + self.0 + } + } + } + + pub mod polygon { + use crate::structure::{enums::{errors::PolyError, micellaneous::Rotation}, traits::Drawable}; + + use super::{color::Col, position::Pos, triangle::Triangle, vertex::Vertex}; + + #[derive(Debug, Clone)] + pub struct Polygon { + triangles: Vec, + center: Vertex, + radius: f32, + n_sides: usize, + rotation: Rotation, + } + + impl Polygon { + /// # USAGE + /// quality of life method, alias [Polygon::new_from_radius_with_rotation()] with `rotation == 0.0` + pub fn new_from_radius(center: Vertex, radius: f32, n_sides: usize) -> Result { + Self::new_from_radius_with_rotation(center, radius, n_sides, Rotation::HorizontalAlign) + } + + /// # USAGE + /// creates a [Polygon] from a center and radius with a rotation. + /// + /// # WARNING + /// - the rotation must be given in radiants. + pub fn new_from_radius_with_rotation( + center: Vertex, + radius: f32, + n_sides: usize, + rotation: Rotation, + ) -> Result { + if n_sides < 3 { + return Err(PolyError::NotEnoughSides); + } + + let rotation_rad = match rotation { + Rotation::HorizontalAlign => { + if n_sides % 6 == 0 { + 0.0 + } else if n_sides % 4 == 0 { + (std::f32::consts::PI * 2.0 / n_sides as f32) / 2.0 + } else if n_sides % 3 == 0 { + match ((n_sides-3)/6)%2 { + 1 => (std::f32::consts::PI * 2.0 / n_sides as f32) / 4.0, + 0 => (std::f32::consts::PI * 2.0)- ((std::f32::consts::PI * 2.0 / n_sides as f32) / 4.0), + _ => panic!(), // should never happen, it's a module. ( % 2) + } + + } else { + 0.0 + } //TODO: add other n_sides + }, + Rotation::VerticalAlign => (std::f32::consts::PI * 2.0 / n_sides as f32) / 2.0, // TODO + Rotation::Custom(rad) => rad, + }; + + let mut vec_tris: Vec = Vec::with_capacity(n_sides); // TODO: add space 4 morph + + let center_x = center.position.0[0]; + let center_y = center.position.0[1]; + + for v in 0..n_sides { + let theta_current = + ((v as f32 / n_sides as f32) * 2.0 * std::f32::consts::PI) + rotation_rad; + let theta_next = + (((v + 1) as f32 / n_sides as f32) * 2.0 * std::f32::consts::PI) + rotation_rad; + let triangle = Triangle::new([ + Vertex::new( + Pos::new( + (theta_current.cos() * radius) + center_x, + (theta_current.sin() * radius) + center_y, + 0.0, + ), + Col::random(), + ), + Vertex::new( + Pos::new( + (theta_next.cos() * radius) + center_x, + (theta_next.sin() * radius) + center_y, + 0.0, + ), + Col::random(), + ), + center, + ]); + vec_tris.push(triangle); + } + Self::new_from_everything_with_rotation(vec_tris, center, radius, n_sides, rotation) + } + + /// # USAGE + /// quality of life method, alias [Polygon::new_from_everything_with_rotation()] with `rotation == Rotation::HorizontalAlign` + fn new_from_everything( + triangles: Vec, + center: Vertex, + radius: f32, + n_sides: usize, + ) -> Result { + Self::new_from_everything_with_rotation( + triangles, + center, + radius, + n_sides, + Rotation::HorizontalAlign, + ) + } + + /// # USAGE + /// creates a [Polygon] from a center and radius with a rotation. + /// + /// # WARNING + /// - the rotation must be given in radiants. + fn new_from_everything_with_rotation( + triangles: Vec, + center: Vertex, + radius: f32, + n_sides: usize, + rotation: Rotation, + ) -> Result { + if n_sides < 3 { + return Err(PolyError::NotEnoughSides); + } + + if triangles.len() != n_sides { + return Err(PolyError::SidesAndTriangleNumberArentConsistant); + } + + Ok(Polygon { + triangles, + center, + radius, + n_sides, + rotation, + }) + } + + fn get_triangles(&self) -> Vec { + self.triangles.clone() + } + + pub fn uniform(&self) -> Vec { + self.get_triangles() + .iter() + .flat_map(|t| t.verts().to_vec()) + .collect::>() + } + } + + impl Drawable for Polygon { + fn uniform(&self) -> Vec { + self.uniform() + } + } + } +} diff --git a/src/structure/traits.rs b/src/structure/traits.rs new file mode 100644 index 0000000..611722a --- /dev/null +++ b/src/structure/traits.rs @@ -0,0 +1,7 @@ +use bytemuck::NoUninit; + +use super::structs::Vertex; + +pub trait Drawable { + fn uniform(&self) -> Vec; +} \ No newline at end of file