use core::convert;
use core::ffi::c_void;
use core::mem;
use core::ptr;
use gl::types::{GLfloat, GLsizei};
use crate::shader::ShaderProgram;
#[expect(
clippy::module_name_repetitions,
reason = "VertexBuffer is more descriptive; and may be used without absolute path."
)]
pub struct VertexBuffer {
vao_id: u32,
vbo_id: u32,
ebo_id: u32,
element_count: i32,
}
impl VertexBuffer {
#[expect(
clippy::similar_names,
reason = "yes clippy, the vao and vbo *should* have a similar name..."
)]
#[inline]
#[must_use]
pub fn new(vertices: &[f32], elements: &[u32]) -> VertexBuffer {
let (mut vbo_id, mut vao_id, mut ebo_id) = (0, 0, 0);
unsafe {
gl::GenVertexArrays(1, &raw mut vao_id);
}
unsafe {
gl::GenBuffers(1, &raw mut vbo_id);
}
unsafe {
gl::GenBuffers(1, &raw mut ebo_id);
}
unsafe {
gl::BindVertexArray(vao_id);
}
unsafe {
gl::BindBuffer(gl::ARRAY_BUFFER, vbo_id);
}
#[expect(
clippy::expect_used,
clippy::indexing_slicing,
reason = "This will only crash if the library user does something really stupid."
)]
unsafe {
gl::BufferData(
gl::ARRAY_BUFFER,
mem::size_of_val(vertices)
.try_into()
.expect("way too much vertex data, overflowed when casting u32 to i32"),
(&raw const vertices[0]).cast::<c_void>(),
gl::STATIC_DRAW,
);
};
unsafe {
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo_id);
}
#[expect(
clippy::expect_used,
clippy::indexing_slicing,
reason = "This will only crash if the library user does something really stupid. It may also crash if there are no elements in the VertexBuffer."
)]
unsafe {
gl::BufferData(
gl::ELEMENT_ARRAY_BUFFER,
mem::size_of_val(elements)
.try_into()
.expect("way too many elements, overflowed when casting u32 to i32"),
(&raw const elements[0]).cast::<c_void>(),
gl::STATIC_DRAW,
);
};
#[expect(
clippy::expect_used,
reason = "This will only crash if the library user does something really stupid."
)]
VertexBuffer {
vao_id,
vbo_id,
ebo_id,
element_count: elements
.len()
.try_into()
.expect("way too many elements, overflowed when casting u32 to i32"),
}
}
#[inline]
pub fn add_attrib(&self, layout: u32, component_count: i32, stride: i32, offset: usize) {
#[expect(
clippy::as_conversions,
clippy::arithmetic_side_effects,
reason = "Necessary for OpenGL C-interopability"
)]
let offset_ptr = (offset * mem::size_of::<GLfloat>()) as *const c_void;
#[expect(
clippy::expect_used,
clippy::arithmetic_side_effects,
reason = "Necessary for OpenGL C-interopability. Seemingly impossible to crash."
)]
unsafe {
gl::VertexAttribPointer(
layout,
component_count,
gl::FLOAT,
gl::FALSE,
stride
* convert::TryInto::<GLsizei>::try_into(mem::size_of::<GLfloat>())
.expect("Realms: Failed to convert size of GLfloat to GLsizei"),
offset_ptr,
);
};
unsafe {
gl::EnableVertexAttribArray(layout);
}
}
#[inline]
pub fn set_layout(&self, component_counts: &[i32]) {
let stride: i32 = component_counts.iter().sum();
let mut offset = 0;
#[expect(
clippy::arithmetic_side_effects,
clippy::unwrap_used,
reason = "This should never crash unless there are more than 2^31 components"
)]
for (layout, component_count) in component_counts.iter().enumerate() {
self.add_attrib(
u32::try_from(layout).unwrap(),
*component_count,
stride,
offset,
);
offset += usize::try_from(*component_count).unwrap();
}
}
#[inline]
pub fn draw(&self, shader_program: &ShaderProgram) {
shader_program.bind();
unsafe {
gl::BindVertexArray(self.vao_id);
}
unsafe {
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_id);
}
unsafe {
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo_id);
}
unsafe {
gl::EnableVertexAttribArray(self.vao_id);
}
unsafe {
gl::DrawElements(
gl::TRIANGLES,
self.element_count,
gl::UNSIGNED_INT,
ptr::null(),
);
}
}
}