initial commit

This commit is contained in:
2023-10-14 22:41:16 +02:00
commit 724940f27f
29 changed files with 18776 additions and 0 deletions
+94
View File
@@ -0,0 +1,94 @@
use glad_gl::gl;
use crate::{mesh::Vertex, shader::Shader};
#[derive(Debug)]
pub struct AABB {
pub min: glm::Vec3,
pub max: glm::Vec3,
}
#[derive(Debug)]
pub struct AABBMesh {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub vao: u32,
}
impl AABBMesh {
pub fn new(aabb: &AABB) -> AABBMesh {
let mut vao = 0;
let mut vbo = 0;
let mut ebo = 0;
let vertices = vec![
Vertex { position: glm::vec3(aabb.min.x, aabb.min.y, aabb.min.z), tex_coords: glm::vec2(0.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.max.x, aabb.min.y, aabb.min.z), tex_coords: glm::vec2(1.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.max.x, aabb.max.y, aabb.min.z), tex_coords: glm::vec2(1.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.min.x, aabb.max.y, aabb.min.z), tex_coords: glm::vec2(0.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.min.x, aabb.min.y, aabb.max.z), tex_coords: glm::vec2(0.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.max.x, aabb.min.y, aabb.max.z), tex_coords: glm::vec2(1.0, 0.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.max.x, aabb.max.y, aabb.max.z), tex_coords: glm::vec2(1.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
Vertex { position: glm::vec3(aabb.min.x, aabb.max.y, aabb.max.z), tex_coords: glm::vec2(0.0, 1.0), normal: glm::vec3(0.0, 0.0, 0.0) },
];
let indices = vec![
0, 1, 2, 2, 3, 0, // front
1, 5, 6, 6, 2, 1, // right
5, 4, 7, 7, 6, 5, // back
4, 0, 3, 3, 7, 4, // left
3, 2, 6, 6, 7, 3, // top
4, 5, 1, 1, 0, 4, // bottom
];
unsafe {
gl::GenVertexArrays(1, &mut vao);
gl::GenBuffers(1, &mut vbo);
gl::GenBuffers(1, &mut ebo);
gl::BindVertexArray(vao);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(gl::ARRAY_BUFFER, (std::mem::size_of::<Vertex>() * vertices.len() as usize) as isize, vertices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (std::mem::size_of::<u32>() * indices.len() as usize) as isize, indices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
// vertex positions
gl::EnableVertexAttribArray(0);
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, std::ptr::null());
// vertex normals
gl::EnableVertexAttribArray(1);
gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
// vertex texture coords
gl::EnableVertexAttribArray(2);
gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (6 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
gl::BindVertexArray(0);
}
AABBMesh {
vertices,
indices,
vao,
}
}
pub fn draw(&self, shader: &Shader, model_mat: &glm::Mat4) {
shader.use_shader();
shader.set_mat4fv("model", &model_mat);
unsafe {
// draw Mesh
gl::BindVertexArray(self.vao);
gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);
gl::DrawElements(gl::TRIANGLES, self.indices.len() as i32, gl::UNSIGNED_INT, std::ptr::null());
// reset stuff to default
gl::BindVertexArray(0);
}
}
}
+77
View File
@@ -0,0 +1,77 @@
use glm;
use crate::model;
pub struct Camera {
pub position: glm::Vec3,
pub front: glm::Vec3,
pub up: glm::Vec3,
pub pitch: f32,
pub yaw: f32,
pub speed: f32,
_speed: f32,
pub sensitivity: f32,
pub fov: f32,
}
impl Camera {
pub fn new() -> Camera {
Camera {
position: glm::vec3(0.0, 3.0, 3.0),
front: glm::vec3(0.0, 0.0, -1.0),
up: glm::vec3(0.0, 1.0, 0.0),
pitch: 0.0,
yaw: -90.0,
_speed: 0.0,
speed: 10.0,
sensitivity: 0.05,
fov: 45.0,
}
}
pub fn handle_mouse_scroll(&mut self, yoffset: f32, can_capture_cursor: bool) {
if !can_capture_cursor { return }
self.fov -= yoffset;
if self.fov <= 0.5 {
self.fov = 0.5;
}
if self.fov >= 45.0 {
self.fov = 45.0;
}
// if yoffset > 0.0 {
// self.position = self.position + (self.front * self._speed) + glm::vec3(0.0, 0.0, yoffset);
// } else {
// self.position = self.position - (self.front * self._speed) + glm::vec3(0.0, 0.0, yoffset);
// }
}
pub fn move_camera(&mut self, xoffset: f32, yoffset: f32) {
let new_x = xoffset * self.sensitivity * self._speed;
let new_y = yoffset * self.sensitivity * self._speed;
self.position = self.position + glm::vec3(new_x, new_y, 0.0);
}
pub fn update_speed(&mut self, delta_time: f32) {
self._speed = self.speed * delta_time;
}
pub fn update_position(&mut self, active_model: Option<u32>, objects: &Vec<model::Model>) {
if let Some(id) = active_model {
for obj in objects {
if obj.id == id {
// we scale the center of the object since the model (and therefore the AABB) is scaled
let center_x = ((obj.aabb.max.x / 2.0) + (obj.aabb.min.x / 2.0)) * obj.scaling_factor;
let center_y = ((obj.aabb.max.y / 2.0) + (obj.aabb.min.y / 2.0)) * obj.scaling_factor;
let z = obj.aabb.max.z * obj.scaling_factor + 10.0;
self.position = glm::vec3(center_x, center_y, z);
self.front = glm::vec3(0.0, 0.0, -1.0);
break;
}
}
}
}
}
+250
View File
@@ -0,0 +1,250 @@
//! Provides a glfw-based backend platform for imgui-rs. This crate is modeled
//! after the winit version.
//!
//! ## Usage
//!
//! 1. Initialize a `GlfwPlatform`
//! 2. Attach it to a glfw `Window`
//! 3. Optionally, enable platform clipboard integration
//! 4. Pass events to the platform (every frame)
//! 5. Call frame preparation (every frame)
//! 6. Call render preperation (every frame)
//!
//! ## Examples
//!
//! The [examples](https://github.com/aloucks/imgui-glfw-support/tree/master/examples) can be found on github.
use glfw::{
Action, Cursor, CursorMode, Key as GlfwKey, Modifiers, MouseButton, StandardCursor, Window,
WindowEvent,
};
use imgui::{BackendFlags, ConfigFlags, Context, Io, Key, Ui};
pub struct GlfwPlatform {
hidpi_mode: ActiveHiDpiMode,
hidpi_factor: f64,
}
#[derive(Copy, Clone, Debug, PartialEq)]
enum ActiveHiDpiMode {
Default,
Rounded,
Locked,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum HiDpiMode {
/// The DPI factor from glfw is used directly without adjustment
Default,
/// The DPI factor from glfw is rounded to an integer value.
///
/// This prevents the user interface from becoming blurry with non-integer scaling.
Rounded,
/// The DPI factor from glfw is ignored, and the included value is used instead.
///
/// This is useful if you want to force some DPI factor (e.g. 1.0) and not care about the value
/// coming from glfw.
Locked(f64),
}
struct Clipboard {
window_ptr: *mut glfw::ffi::GLFWwindow,
}
impl imgui::ClipboardBackend for Clipboard {
fn set(&mut self, s: &str) {
unsafe {
glfw::ffi::glfwSetClipboardString(self.window_ptr, s.as_ptr() as *const std::ffi::c_char);
}
}
fn get(&mut self) -> std::option::Option<String> {
unsafe {
let s = glfw::ffi::glfwGetClipboardString(self.window_ptr);
let s = std::ffi::CStr::from_ptr(s);
let bytes = s.to_bytes();
if !bytes.is_empty() {
let v = String::from_utf8_lossy(bytes);
Some(imgui::ImString::new(v).to_string())
} else {
None
}
}
}
}
impl HiDpiMode {
fn apply(&self, hidpi_factor: f64) -> (ActiveHiDpiMode, f64) {
match *self {
HiDpiMode::Default => (ActiveHiDpiMode::Default, hidpi_factor),
HiDpiMode::Rounded => (ActiveHiDpiMode::Rounded, hidpi_factor.round()),
HiDpiMode::Locked(value) => (ActiveHiDpiMode::Locked, value),
}
}
}
impl GlfwPlatform {
/// Initializes a glfw platform instance and configures imgui.
///
/// * backend flgs are updated
/// * keys are configured
/// * platform name is set
pub fn init(imgui: &mut Context) -> GlfwPlatform {
let io = imgui.io_mut();
io.backend_flags.insert(BackendFlags::HAS_MOUSE_CURSORS);
io.backend_flags.insert(BackendFlags::HAS_SET_MOUSE_POS);
io[Key::Tab] = GlfwKey::Tab as _;
io[Key::LeftArrow] = GlfwKey::Left as _;
io[Key::RightArrow] = GlfwKey::Right as _;
io[Key::UpArrow] = GlfwKey::Up as _;
io[Key::DownArrow] = GlfwKey::Down as _;
io[Key::PageUp] = GlfwKey::PageUp as _;
io[Key::PageDown] = GlfwKey::PageDown as _;
io[Key::Home] = GlfwKey::Home as _;
io[Key::End] = GlfwKey::End as _;
io[Key::Insert] = GlfwKey::Insert as _;
io[Key::Delete] = GlfwKey::Delete as _;
io[Key::Backspace] = GlfwKey::Backspace as _;
io[Key::Space] = GlfwKey::Space as _;
io[Key::Enter] = GlfwKey::Enter as _;
io[Key::Escape] = GlfwKey::Escape as _;
io[Key::KeypadEnter] = GlfwKey::KpEnter as _;
io[Key::A] = GlfwKey::A as _;
io[Key::C] = GlfwKey::C as _;
io[Key::V] = GlfwKey::V as _;
io[Key::X] = GlfwKey::X as _;
io[Key::Y] = GlfwKey::Y as _;
io[Key::Z] = GlfwKey::Z as _;
imgui.set_platform_name(Some(format!(
"imgui-glfw-support {}",
env!("CARGO_PKG_VERSION")
)));
GlfwPlatform {
hidpi_mode: ActiveHiDpiMode::Default,
hidpi_factor: 1.0,
}
}
/// Adds platform clipboard integration for the provided window. The caller **must** ensure that
/// the `Window` outlives the imgui `Context` **and** that any imgui functions that may access
/// the clipboard are called from the **main thread** (the thread that's executing the event polling).
pub fn set_clipboard_backend(&self, imgui: &mut Context, window: &Window) {
use glfw::Context;
let window_ptr = window.window_ptr();
imgui.set_clipboard_backend(Clipboard { window_ptr });
}
/// Attaches the platform instance to a glfw window.
///
/// * framebuffer sacle (i.e. DPI factor) is set
/// * display size is set
pub fn attach_window(&mut self, io: &mut Io, window: &Window, hidpi_mode: HiDpiMode) {
let (scale_factor_x, _scale_factor_y) = window.get_content_scale();
let (hidpi_mode, hidpi_factor) = hidpi_mode.apply(scale_factor_x as _);
self.hidpi_mode = hidpi_mode;
self.hidpi_factor = hidpi_factor;
io.display_framebuffer_scale = [hidpi_factor as f32, hidpi_factor as f32];
let (width, height) = window.get_size();
io.display_size = [width as f32, height as f32];
}
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
}
/// Handles a glfw window event
///
/// * keyboard state is updated
/// * mouse state is updated
pub fn handle_event(&self, io: &mut Io, _window: &Window, event: &WindowEvent) {
match *event {
WindowEvent::Key(key, _scancode, action, modifiers) => {
if key as i32 >= 0 {
if action == Action::Release {
io.keys_down[key as usize] = false;
} else {
io.keys_down[key as usize] = true;
}
}
io.key_shift = modifiers.contains(Modifiers::Shift);
io.key_ctrl = modifiers.contains(Modifiers::Control);
io.key_alt = modifiers.contains(Modifiers::Alt);
io.key_super = modifiers.contains(Modifiers::Super);
}
WindowEvent::Size(width, height) => {
io.display_size = [width as _, height as _];
}
WindowEvent::Char(ch) => {
// Exclude the backspace key
if ch != '\u{7f}' {
io.add_input_character(ch);
}
}
WindowEvent::CursorPos(x, y) => {
io.mouse_pos = [x as _, y as _];
}
WindowEvent::Scroll(x, y) => {
io.mouse_wheel_h = x as _;
io.mouse_wheel = y as _;
}
WindowEvent::MouseButton(button, action, _modifiers) => {
let pressed = action == Action::Press;
match button {
MouseButton::Button1 => io.mouse_down[0] = pressed,
MouseButton::Button2 => io.mouse_down[1] = pressed,
MouseButton::Button3 => io.mouse_down[2] = pressed,
_ => (),
}
}
_ => {}
}
}
/// Prepare the window for the next frame.
///
/// Call before calling the imgui-rs `Context::frame` function.
///
/// * mouse cursor is repositioned if requested by imgui
pub fn prepare_frame(&self, io: &mut Io, window: &mut Window) -> Result<(), String> {
if io.want_set_mouse_pos {
let [x, y] = io.mouse_pos;
window.set_cursor_pos(x as _, y as _);
Ok(())
} else {
Ok(())
}
}
/// Prepare the window for rendering.
///
/// Call before calling the imgui backend renderer function (e.g. `imgui_wgpu::Renderer::render`).
///
/// * the mouse cursor is changed or hidden if requested by imgui
pub fn prepare_render(&self, ui: &Ui, window: &mut Window) {
let io = ui.io();
if !io
.config_flags
.contains(ConfigFlags::NO_MOUSE_CURSOR_CHANGE)
{
match ui.mouse_cursor() {
Some(mouse_cursor) if !io.mouse_draw_cursor => {
window.set_cursor_mode(CursorMode::Normal);
window.set_cursor(Some(match mouse_cursor {
// TODO: GLFW has more cursor options on master, but they aren't released yet
imgui::MouseCursor::Arrow => Cursor::standard(StandardCursor::Arrow),
imgui::MouseCursor::ResizeAll => Cursor::standard(StandardCursor::Arrow),
imgui::MouseCursor::ResizeNS => Cursor::standard(StandardCursor::VResize),
imgui::MouseCursor::ResizeEW => Cursor::standard(StandardCursor::HResize),
imgui::MouseCursor::ResizeNESW => Cursor::standard(StandardCursor::Arrow),
imgui::MouseCursor::ResizeNWSE => Cursor::standard(StandardCursor::Arrow),
imgui::MouseCursor::Hand => Cursor::standard(StandardCursor::Hand),
imgui::MouseCursor::NotAllowed => {
Cursor::standard(StandardCursor::Crosshair)
}
imgui::MouseCursor::TextInput => Cursor::standard(StandardCursor::IBeam),
}));
}
_ => window.set_cursor_mode(CursorMode::Hidden),
}
}
}
}
+294
View File
@@ -0,0 +1,294 @@
extern crate imgui;
use imgui::Context;
use std::mem;
use glad_gl::gl;
pub struct Renderer {
program: gl::GLuint,
locs: Locs,
vbo: gl::GLuint,
ebo: gl::GLuint,
font_texture: gl::GLuint,
}
struct Locs {
texture: gl::GLint,
proj_mtx: gl::GLint,
position: gl::GLuint,
uv: gl::GLuint,
color: gl::GLuint,
}
impl Renderer {
pub fn new(
imgui: &mut Context,
) -> Self
{
unsafe {
#[cfg(target_os = "macos")]
let glsl_version = b"#version 150\n\0";
#[cfg(not(target_os = "macos"))]
let glsl_version = b"#version 130\n\0";
let vert_source = b"
uniform mat4 ProjMtx;
in vec2 Position;
in vec2 UV;
in vec4 Color;
out vec2 Frag_UV;
out vec4 Frag_Color;
void main()
{
Frag_UV = UV;
Frag_Color = Color;
gl_Position = ProjMtx * vec4(Position.xy,0,1);
}
\0";
let frag_source = b"
uniform sampler2D Texture;
in vec2 Frag_UV;
in vec4 Frag_Color;
out vec4 Out_Color;
void main()
{
Out_Color = Frag_Color * texture(Texture, Frag_UV.st);
}
\0";
let vert_sources = [glsl_version.as_ptr() as *const gl::GLchar,
vert_source.as_ptr() as *const gl::GLchar];
let vert_sources_len = [glsl_version.len() as gl::GLint - 1,
vert_source.len() as gl::GLint - 1];
let frag_sources = [glsl_version.as_ptr() as *const gl::GLchar,
frag_source.as_ptr() as *const gl::GLchar];
let frag_sources_len = [glsl_version.len() as gl::GLint - 1,
frag_source.len() as gl::GLint - 1];
let program = gl::CreateProgram();
let vert_shader = gl::CreateShader(gl::VERTEX_SHADER);
let frag_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
gl::ShaderSource(vert_shader, 2, vert_sources.as_ptr(), vert_sources_len.as_ptr());
gl::ShaderSource(frag_shader, 2, frag_sources.as_ptr(), frag_sources_len.as_ptr());
gl::CompileShader(vert_shader);
gl::CompileShader(frag_shader);
gl::AttachShader(program, vert_shader);
gl::AttachShader(program, frag_shader);
gl::LinkProgram(program);
gl::DeleteShader(vert_shader);
gl::DeleteShader(frag_shader);
let locs = Locs{
texture: gl::GetUniformLocation(program, b"Texture\0".as_ptr() as _),
proj_mtx: gl::GetUniformLocation(program, b"ProjMtx\0".as_ptr() as _),
position: gl::GetAttribLocation(program, b"Position\0".as_ptr() as _) as _,
uv: gl::GetAttribLocation(program, b"UV\0".as_ptr() as _) as _,
color: gl::GetAttribLocation(program, b"Color\0".as_ptr() as _) as _,
};
let vbo = return_param(|x| gl::GenBuffers(1, x) );
let ebo = return_param(|x| gl::GenBuffers(1, x) );
let mut current_texture = 0;
gl::GetIntegerv(gl::TEXTURE_BINDING_2D, &mut current_texture);
let font_texture = return_param(|x| gl::GenTextures(1, x));
gl::BindTexture(gl::TEXTURE_2D, font_texture);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as _);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _);
gl::PixelStorei(gl::UNPACK_ROW_LENGTH, 0);
{
let atlas = imgui.fonts();
let texture = atlas.build_rgba32_texture();
gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGBA as _, texture.width as _, texture.height as _, 0, gl::RGBA, gl::UNSIGNED_BYTE, texture.data.as_ptr() as _);
atlas.tex_id = (font_texture as usize).into();
}
gl::BindTexture(gl::TEXTURE_2D, current_texture as _);
Self{
program,
locs,
vbo,
ebo,
font_texture,
}
}
}
pub fn render(
&self,
ctx: &mut Context,
) {
use imgui::{DrawVert,DrawIdx,DrawCmd,DrawCmdParams};
unsafe {
let last_active_texture = return_param(|x| gl::GetIntegerv(gl::ACTIVE_TEXTURE, x));
gl::ActiveTexture(gl::TEXTURE0);
let last_program = return_param(|x| gl::GetIntegerv(gl::CURRENT_PROGRAM, x));
let last_texture = return_param(|x| gl::GetIntegerv(gl::TEXTURE_BINDING_2D, x));
let last_sampler = return_param(|x| gl::GetIntegerv(gl::SAMPLER_BINDING, x));
let last_array_buffer = return_param(|x| gl::GetIntegerv(gl::ARRAY_BUFFER_BINDING, x));
let last_element_array_buffer = return_param(|x| gl::GetIntegerv(gl::ELEMENT_ARRAY_BUFFER_BINDING, x));
let last_vertex_array = return_param(|x| gl::GetIntegerv(gl::VERTEX_ARRAY_BINDING, x));
let last_polygon_mode = return_param(|x: &mut [gl::GLint; 2]| gl::GetIntegerv(gl::POLYGON_MODE, x.as_mut_ptr()));
let last_viewport = return_param(|x: &mut [gl::GLint; 4]| gl::GetIntegerv(gl::VIEWPORT, x.as_mut_ptr()));
let last_scissor_box = return_param(|x: &mut [gl::GLint; 4]| gl::GetIntegerv(gl::SCISSOR_BOX, x.as_mut_ptr()));
let last_blend_src_rgb = return_param(|x| gl::GetIntegerv(gl::BLEND_SRC_RGB, x));
let last_blend_dst_rgb = return_param(|x| gl::GetIntegerv(gl::BLEND_DST_RGB, x));
let last_blend_src_alpha = return_param(|x| gl::GetIntegerv(gl::BLEND_SRC_ALPHA, x));
let last_blend_dst_alpha = return_param(|x| gl::GetIntegerv(gl::BLEND_DST_ALPHA, x));
let last_blend_equation_rgb = return_param(|x| gl::GetIntegerv(gl::BLEND_EQUATION_RGB, x));
let last_blend_equation_alpha = return_param(|x| gl::GetIntegerv(gl::BLEND_EQUATION_ALPHA, x));
let last_enable_blend = gl::IsEnabled(gl::BLEND) == gl::TRUE;
let last_enable_cull_face = gl::IsEnabled(gl::CULL_FACE) == gl::TRUE;
let last_enable_depth_test = gl::IsEnabled(gl::DEPTH_TEST) == gl::TRUE;
let last_enable_scissor_test = gl::IsEnabled(gl::SCISSOR_TEST) == gl::TRUE;
gl::Enable(gl::BLEND);
gl::BlendEquation(gl::FUNC_ADD);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::Disable(gl::CULL_FACE);
gl::Disable(gl::DEPTH_TEST);
gl::Enable(gl::SCISSOR_TEST);
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
let [width, height] = ctx.io().display_size;
let [scale_w, scale_h] = ctx.io().display_framebuffer_scale;
let fb_width = width * scale_w;
let fb_height = height * scale_h;
gl::Viewport(0, 0, fb_width as _, fb_height as _);
let matrix = [
[ 2.0 / width as f32, 0.0, 0.0, 0.0],
[ 0.0, 2.0 / -(height as f32), 0.0, 0.0],
[ 0.0, 0.0, -1.0, 0.0],
[-1.0, 1.0, 0.0, 1.0],
];
gl::UseProgram(self.program);
gl::Uniform1i(self.locs.texture, 0);
gl::UniformMatrix4fv(self.locs.proj_mtx, 1, gl::FALSE, matrix.as_ptr() as _);
gl::BindSampler(0, 0);
let vao = return_param(|x| gl::GenVertexArrays(1, x));
gl::BindVertexArray(vao);
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
gl::EnableVertexAttribArray(self.locs.position);
gl::EnableVertexAttribArray(self.locs.uv);
gl::EnableVertexAttribArray(self.locs.color);
gl::VertexAttribPointer(self.locs.position, 2, gl::FLOAT, gl::FALSE, mem::size_of::<DrawVert>() as _, field_offset::<DrawVert, _, _>(|v| &v.pos) as _);
gl::VertexAttribPointer(self.locs.uv, 2, gl::FLOAT, gl::FALSE, mem::size_of::<DrawVert>() as _, field_offset::<DrawVert, _, _>(|v| &v.uv) as _);
gl::VertexAttribPointer(self.locs.color, 4, gl::UNSIGNED_BYTE, gl::TRUE, mem::size_of::<DrawVert>() as _, field_offset::<DrawVert, _, _>(|v| &v.col) as _);
let draw_data = ctx.render();
for draw_list in draw_data.draw_lists() {
let vtx_buffer = draw_list.vtx_buffer();
let idx_buffer = draw_list.idx_buffer();
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
gl::BufferData(gl::ARRAY_BUFFER, (vtx_buffer.len() * mem::size_of::<DrawVert>()) as _, vtx_buffer.as_ptr() as _, gl::STREAM_DRAW);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (idx_buffer.len() * mem::size_of::<DrawIdx>()) as _, idx_buffer.as_ptr() as _, gl::STREAM_DRAW);
for cmd in draw_list.commands() {
match cmd {
DrawCmd::Elements {
count,
cmd_params: DrawCmdParams {
clip_rect: [x, y, z, w],
texture_id,
idx_offset,
..
},
} => {
gl::BindTexture(gl::TEXTURE_2D, texture_id.id() as _);
gl::Scissor((x * scale_w) as gl::GLint,
(fb_height - w * scale_h) as gl::GLint,
((z - x) * scale_w) as gl::GLint,
((w - y) * scale_h) as gl::GLint);
let idx_size = if mem::size_of::<DrawIdx>() == 2 { gl::UNSIGNED_SHORT } else { gl::UNSIGNED_INT };
gl::DrawElements(gl::TRIANGLES, count as _, idx_size, (idx_offset * mem::size_of::<DrawIdx>()) as _);
},
DrawCmd::ResetRenderState => {
unimplemented!("Haven't implemented DrawCmd::ResetRenderState yet");
},
DrawCmd::RawCallback { .. } => {
unimplemented!("Haven't implemented user callbacks yet");
}
}
}
}
gl::DeleteVertexArrays(1, &vao);
gl::UseProgram(last_program as _);
gl::BindTexture(gl::TEXTURE_2D, last_texture as _);
gl::BindSampler(0, last_sampler as _);
gl::ActiveTexture(last_active_texture as _);
gl::BindVertexArray(last_vertex_array as _);
gl::BindBuffer(gl::ARRAY_BUFFER, last_array_buffer as _);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, last_element_array_buffer as _);
gl::BlendEquationSeparate(last_blend_equation_rgb as _, last_blend_equation_alpha as _);
gl::BlendFuncSeparate(last_blend_src_rgb as _, last_blend_dst_rgb as _, last_blend_src_alpha as _, last_blend_dst_alpha as _);
if last_enable_blend { gl::Enable(gl::BLEND) } else { gl::Disable(gl::BLEND) };
if last_enable_cull_face { gl::Enable(gl::CULL_FACE) } else { gl::Disable(gl::CULL_FACE) };
if last_enable_depth_test { gl::Enable(gl::DEPTH_TEST) } else { gl::Disable(gl::DEPTH_TEST) };
if last_enable_scissor_test { gl::Enable(gl::SCISSOR_TEST) } else { gl::Disable(gl::SCISSOR_TEST) };
gl::PolygonMode(gl::FRONT_AND_BACK, last_polygon_mode[0] as _);
gl::Viewport(last_viewport[0] as _, last_viewport[1] as _, last_viewport[2] as _, last_viewport[3] as _);
gl::Scissor(last_scissor_box[0] as _, last_scissor_box[1] as _, last_scissor_box[2] as _, last_scissor_box[3] as _);
}
}
}
impl Drop for Renderer {
fn drop(&mut self) {
unsafe {
gl::DeleteBuffers(1, &self.vbo);
gl::DeleteBuffers(1, &self.ebo);
gl::DeleteProgram(self.program);
gl::DeleteTextures(1, &self.font_texture);
}
}
}
fn field_offset<T, U, F: for<'a> FnOnce(&'a T) -> &'a U>(f: F) -> usize {
unsafe {
let instance = mem::zeroed::<T>();
let offset = {
let field: &U = f(&instance);
field as *const U as usize - &instance as *const T as usize
};
mem::forget(instance);
offset
}
}
fn return_param<T, F>(f: F) -> T where F: FnOnce(&mut T) {
let mut val = unsafe{ mem::zeroed() };
f(&mut val);
val
}
+12
View File
@@ -0,0 +1,12 @@
pub mod shader;
pub mod camera;
#[path="imgui-glfw-support/mod.rs"]
pub mod imgui_glfw_support;
#[path="imgui-opengl-renderer/mod.rs"]
pub mod imgui_opengl_renderer;
pub mod utils;
pub mod mesh;
pub mod model;
pub mod ui;
pub mod log;
pub mod aabb;
+64
View File
@@ -0,0 +1,64 @@
#[derive(Clone)]
pub enum LogLevel {
Debug,
Info,
Warning,
Error,
}
impl From<LogLevel> for mint::Vector4<f32> {
fn from(level: LogLevel) -> Self {
match level {
LogLevel::Debug => [0.5, 0.5, 1.0, 1.0].into(),
LogLevel::Info => [0.5, 0.5, 0.5, 1.0].into(),
LogLevel::Warning => [1.0, 0.64, 0.0, 1.0].into(),
LogLevel::Error => [1.0, 0.0, 0.0, 1.0].into(),
}
}
}
#[derive(Clone)]
pub struct LogMessage {
pub level: LogLevel,
pub message: String,
}
impl LogMessage {
pub fn new(level: LogLevel, message: &str) -> Self {
let severity = match level {
LogLevel::Debug => "DEBUG",
LogLevel::Info => "INFO",
LogLevel::Warning => "WARNING",
LogLevel::Error => "ERROR",
};
Self {
level,
message: format!("[{}]: {}", severity, message),
}
}
}
pub struct Log {
pub history: Vec<LogMessage>,
}
impl Log {
pub fn clear(&mut self) {
self.history.clear();
}
pub fn log(&mut self, message: &str, level: LogLevel) {
// TODO: also write to a log file
self.history.push(LogMessage::new(level, message));
}
}
impl Default for Log {
fn default() -> Self {
Self {
history: Vec::new(),
}
}
}
+368
View File
@@ -0,0 +1,368 @@
use glfw::{Action, Context, Key, Modifiers};
use glad_gl::gl;
use anyhow;
use threedobs::{shader, ui::ui};
fn main() -> anyhow::Result<(), Box<dyn std::error::Error>> {
let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS)?;
glfw::WindowHint::ContextVersion(3, 3);
glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core);
glfw::WindowHint::OpenGlForwardCompat(true);
let (mut window, events) = glfw.create_window(1200, 800, "3dobs", glfw::WindowMode::Windowed).expect("Failed to create GLFW window");
window.set_all_polling(true);
window.set_cursor_mode(glfw::CursorMode::Disabled);
window.make_current();
let mut state = ui::State::default();
glfw.set_swap_interval(glfw::SwapInterval::Sync(1));
let (mut imgui, glfw_platform, renderer) = ui::init_imgui(&mut window);
let mesh_shader = shader::Shader::new("shaders/vertex.glsl", "shaders/frag.glsl")?;
let light_shader = shader::Shader::new("shaders/vertex.glsl", "shaders/light_f.glsl")?;
let grid_shader = shader::Shader::new("shaders/grid_v.glsl", "shaders/grid_f.glsl")?;
let vertices: [f32; 288] = [
// positions // normals // texture coords
-0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0,
0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 0.0,
0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0,
0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 1.0, 1.0,
-0.5, 0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 1.0,
-0.5, -0.5, -0.5, 0.0, 0.0, -1.0, 0.0, 0.0,
-0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0,
0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 0.0,
0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0,
0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 1.0, 1.0,
-0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 1.0,
-0.5, -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0,
-0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0,
-0.5, 0.5, -0.5, -1.0, 0.0, 0.0, 1.0, 1.0,
-0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0,
-0.5, -0.5, -0.5, -1.0, 0.0, 0.0, 0.0, 1.0,
-0.5, -0.5, 0.5, -1.0, 0.0, 0.0, 0.0, 0.0,
-0.5, 0.5, 0.5, -1.0, 0.0, 0.0, 1.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
0.5, 0.5, -0.5, 1.0, 0.0, 0.0, 1.0, 1.0,
0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0,
0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 0.0, 1.0,
0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0,
0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 1.0, 0.0,
-0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0,
0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 1.0, 1.0,
0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0,
0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0,
-0.5, -0.5, 0.5, 0.0, -1.0, 0.0, 0.0, 0.0,
-0.5, -0.5, -0.5, 0.0, -1.0, 0.0, 0.0, 1.0,
-0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 1.0,
0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 1.0, 1.0,
0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0,
0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 1.0, 0.0,
-0.5, 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0,
-0.5, 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 1.0
];
let points_lights: [glm::Vec3; 4] = [
glm::vec3(0.7, 0.2, 2.0),
glm::vec3(2.3, -3.3, -4.0),
glm::vec3(-4.0, 2.0, -12.0),
glm::vec3(0.0, 0.0, -3.0),
];
let ident_mat = glm::mat4(
1., 0., 0., 0.,
0., 1., 0., 0.,
0., 0., 1., 0.,
0., 0., 0., 1.);
let mut delta_time: f32 = 0.0;
let mut last_frame: f32 = 0.0;
let mut last_cursor = None;
let (w, h) = window.get_size();
let mut last_x: f32 = w as f32 / 2.0;
let mut last_y: f32 = h as f32 / 2.0;
let mut first_mouse: bool = true;
unsafe {
// Object 1: Cube/container
//
let mut vbo: u32 = 0;
gl::GenBuffers(1, &mut vbo);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(gl::ARRAY_BUFFER, (vertices.len() * core::mem::size_of::<f32>()) as isize, vertices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
// position attribute, maps to 'aPos' in vertex shader with location 0
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, std::ptr::null());
gl::EnableVertexAttribArray(0);
// normal attribute, maps to 'aNormal' in vertex shader with location 1
gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, (3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
gl::EnableVertexAttribArray(1);
// texture attribute, maps to 'aTexCoords' in vertex shader with location 2
gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, (6 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
gl::EnableVertexAttribArray(2);
// Object 2: Light source
let mut light_vao: u32 = 0;
gl::GenVertexArrays(1, &mut light_vao);
gl::BindVertexArray(light_vao);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
// position attribute, maps to 'aPos' in vertex shader with location 0
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 8 * std::mem::size_of::<f32>() as i32, std::ptr::null());
gl::EnableVertexAttribArray(0);
grid_shader.use_shader();
grid_shader.set_float("near", 0.01);
grid_shader.set_float("far", 200.0);
mesh_shader.use_shader();
// set light uniforms
for i in 0..points_lights.len() {
mesh_shader.set_3fv(&format!("pointLights[{}].position", i), points_lights[i]);
mesh_shader.set_float(&format!("pointLights[{}].constant", i), 1.0);
mesh_shader.set_float(&format!("pointLights[{}].linear", i), 0.09);
mesh_shader.set_float(&format!("pointLights[{}].quadratic", i), 0.032);
mesh_shader.set_3fv(&format!("pointLights[{}].ambient", i), glm::vec3(0.2, 0.2, 0.2));
mesh_shader.set_3fv(&format!("pointLights[{}].diffuse", i), glm::vec3(0.5, 0.5, 0.5));
mesh_shader.set_3fv(&format!("pointLights[{}].specular", i), glm::vec3(1.0, 1.0, 1.0));
}
mesh_shader.set_float("spotLight.cutOff", glm::cos(glm::radians(12.5)));
mesh_shader.set_float("spotLight.outerCutOff", glm::cos(glm::radians(15.0)));
mesh_shader.set_3fv("spotLight.ambient", glm::vec3(0.2, 0.2, 0.2));
mesh_shader.set_3fv("spotLight.diffuse", glm::vec3(0.2, 0.2, 0.2));
mesh_shader.set_3fv("spotLight.specular", glm::vec3(1.0, 1.0, 1.0));
mesh_shader.set_float("spotLight.constant", 1.0);
mesh_shader.set_float("spotLight.linear", 0.09);
mesh_shader.set_float("spotLight.quadratic", 0.032);
mesh_shader.set_3fv("dirLight.direction", glm::vec3(-0.2, -1.0, -0.3));
mesh_shader.set_3fv("dirLight.ambient", glm::vec3(0.2, 0.2, 0.2));
mesh_shader.set_3fv("dirLight.diffuse", glm::vec3(0.5, 0.5, 0.5));
mesh_shader.set_3fv("dirLight.specular", glm::vec3(1.0, 1.0, 1.0));
let scene_fb = create_scene_framebuffer();
// main loop
while !window.should_close() {
let current_frame = glfw.get_time() as f32;
delta_time = current_frame - last_frame;
last_frame = current_frame;
imgui.io_mut().update_delta_time(std::time::Duration::from_secs_f32(delta_time));
state.camera.update_speed(delta_time);
// camera matrices
let view_mat = glm::ext::look_at(state.camera.position, state.camera.position + state.camera.front, state.camera.up);
let projection_mat = glm::ext::perspective(glm::radians(state.camera.fov), state.viewport_size[0] / state.viewport_size[1], 0.01, 200.0);
for (_, event) in glfw::flush_messages(&events) {
// order of handling events is important here
// we need to handle window events first to have an updated
// is_cursor_captured
handle_window_event(&mut window, &event, &mut state);
if !state.is_cursor_captured {
glfw_platform.handle_event(imgui.io_mut(), &window, &event);
}
match event {
glfw::WindowEvent::CursorPos(xpos, ypos) => {
if first_mouse {
last_x = xpos as f32;
last_y = ypos as f32;
first_mouse = false;
}
let xoffset = xpos as f32 - last_x;
let yoffset = last_y - ypos as f32;
last_x = xpos as f32;
last_y = ypos as f32;
if state.can_capture_cursor && window.get_mouse_button(glfw::MouseButtonLeft) == Action::Press {
if window.get_key(glfw::Key::LeftShift) == Action::Press {
state.camera.move_camera(xoffset, yoffset);
} else {
if let Some(active_model) = state.active_model {
let model = state.objects.iter_mut().find(|m| m.id == active_model).unwrap();
model.rotate(xoffset * state.camera.sensitivity, yoffset * state.camera.sensitivity);
}
}
}
}
_ => {}
}
}
//
// draw scene to framebuffer
//
let (scene_texture, rbo) = create_scene_texture_and_renderbuffer(&window, scene_fb);
gl::BindFramebuffer(gl::FRAMEBUFFER, scene_fb);
gl::Enable(gl::DEPTH_TEST);
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
gl::ClearColor(0.2, 0.2, 0.2, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
mesh_shader.use_shader();
mesh_shader.set_mat4fv("view", &view_mat);
mesh_shader.set_mat4fv("projection", &projection_mat);
mesh_shader.set_3fv("spotLight.position", state.camera.position);
mesh_shader.set_3fv("spotLight.direction", state.camera.front);
mesh_shader.set_3fv("viewPos", state.camera.position);
for obj in &state.objects {
if state.wireframe {
gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);
} else {
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
}
if Some(obj.id) == state.active_model {obj.draw(&mesh_shader, state.draw_aabb);}
}
gl::PolygonMode(gl::FRONT_AND_BACK, gl::FILL);
light_shader.use_shader();
light_shader.set_mat4fv("view", &view_mat);
light_shader.set_mat4fv("projection", &projection_mat);
// for i in 0..points_lights.len() {
// let light_model = glm::ext::translate(&ident_mat, points_lights[i]);
// let light_model = glm::ext::scale(&light_model, glm::vec3(0.2, 0.2, 0.2));
// light_shader.set_mat4fv("model", &light_model);
// gl::BindVertexArray(light_vao);
// gl::DrawArrays(gl::TRIANGLES, 0, 36);
// }
// draw grid
if state.draw_grid {draw_grid(&grid_shader, &view_mat, &projection_mat);}
//
// draw ui
//
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
gl::ClearColor(0.1, 0.1, 0.1, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Disable(gl::DEPTH_TEST);
gl::Disable(gl::BLEND);
ui::draw_ui(&mut imgui, &renderer, &glfw_platform, &mut window, &mut state, delta_time, &mut last_cursor, scene_texture);
glfw.poll_events();
window.swap_buffers();
gl::DeleteTextures(1, &scene_texture);
gl::DeleteRenderbuffers(1, &rbo);
}
gl::DeleteVertexArrays(1, &light_vao);
gl::DeleteBuffers(1, &vbo);
gl::DeleteFramebuffers(1, &scene_fb);
}
Ok(())
}
fn draw_grid(shader: &threedobs::shader::Shader, view_mat: &glm::Mat4, projection_mat: &glm::Mat4) {
shader.use_shader();
shader.set_mat4fv("view", &view_mat);
shader.set_mat4fv("projection", &projection_mat);
unsafe {
gl::DrawArrays(gl::TRIANGLES, 0, 6);
}
}
fn handle_window_event(window: &mut glfw::Window, event: &glfw::WindowEvent, state: &mut ui::State) {
match event {
glfw::WindowEvent::Key(Key::O, _, Action::Press, Modifiers::Control) => {
ui::import_model(state);
}
glfw::WindowEvent::Key(Key::Q, _, Action::Press, Modifiers::Control) => {
window.set_should_close(true);
}
glfw::WindowEvent::Key(Key::LeftControl, _, Action::Press, _) => {
state.camera.speed *= 5.0;
}
glfw::WindowEvent::Key(Key::LeftControl, _, Action::Release, _) => {
state.camera.speed /= 5.0;
}
glfw::WindowEvent::MouseButton(glfw::MouseButtonLeft, Action::Press, _) => {
if !state.can_capture_cursor { return; }
state.is_cursor_captured = true;
window.set_cursor_mode(glfw::CursorMode::Disabled);
}
glfw::WindowEvent::MouseButton(glfw::MouseButtonLeft, Action::Release, _) => {
if !state.can_capture_cursor { return; }
state.is_cursor_captured = false;
window.set_cursor_mode(glfw::CursorMode::Normal);
}
glfw::WindowEvent::Scroll(_, yoff) => {
state.camera.handle_mouse_scroll(*yoff as f32, state.can_capture_cursor);
}
glfw::WindowEvent::FramebufferSize(w, h) => {
unsafe {
gl::Viewport(0, 0, *w, *h);
}
}
_ => {}
}
}
fn create_scene_framebuffer() -> u32 {
let mut fb: u32 = 0;
unsafe {
gl::GenFramebuffers(1, &mut fb);
gl::BindFramebuffer(gl::FRAMEBUFFER, fb);
}
return fb;
}
fn create_scene_texture_and_renderbuffer(window: &glfw::Window, fbo: u32) -> (u32, u32) {
let mut fb_texture: u32 = 0;
let mut rbo: u32 = 0;
let (w, h) = window.get_size();
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, fbo);
// texture
gl::GenTextures(1, &mut fb_texture);
gl::BindTexture(gl::TEXTURE_2D, fb_texture);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as i32, w, h, 0, gl::RGB, gl::UNSIGNED_BYTE, std::ptr::null());
gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, fb_texture, 0);
// renderbuffer for depth
gl::GenRenderbuffers(1, &mut rbo);
gl::BindRenderbuffer(gl::RENDERBUFFER, rbo);
gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH24_STENCIL8, w, h);
gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, gl::RENDERBUFFER, rbo);
if gl::CheckFramebufferStatus(gl::FRAMEBUFFER) != gl::FRAMEBUFFER_COMPLETE {
panic!("ERROR::FRAMEBUFFER:: Framebuffer is not complete!");
}
}
return (fb_texture, rbo);
}
+258
View File
@@ -0,0 +1,258 @@
use glad_gl::gl;
use anyhow::Result;
use crate::{shader::Shader, utils};
fn decompose_mat(matrix: &mut glm::Mat4) -> (glm::Vec3, glm::Vec3, glm::Vec3) {
let pos = glm::vec3(matrix.c0.w, matrix.c1.w, matrix.c2.w);
matrix.c3.x = 0.0;
matrix.c3.y = 0.0;
matrix.c3.z = 0.0;
let scale_x = glm::length(glm::vec3(matrix.c0.x, matrix.c1.x, matrix.c2.x));
let scale_y = glm::length(glm::vec3(matrix.c0.y, matrix.c1.y, matrix.c2.y));
let scale_z = glm::length(glm::vec3(matrix.c0.z, matrix.c1.z, matrix.c2.z));
let scale = glm::vec3(scale_x, scale_y, scale_z);
let matrix = glm::mat3(
matrix[0][0] / scale_x, matrix[0][1] / scale_y, matrix[0][2] / scale_z,
matrix[1][0] / scale_x, matrix[1][1] / scale_y, matrix[1][2] / scale_z,
matrix[2][0] / scale_x, matrix[2][1] / scale_y, matrix[2][2] / scale_z,
);
let mut pitch = 0.0;
let mut yaw = 0.0;
let mut roll = 0.0;
// source: https://www.geometrictools.com/Documentation/EulerAngles.pdf
if matrix[0][2] < 1.0 {
if matrix[0][2] > -1.0 {
pitch = (-matrix[1][2]).atan2(matrix[2][2]).to_degrees();
yaw = matrix[0][2].asin().to_degrees();
roll = (-matrix[0][1]).atan2(matrix[0][0]).to_degrees();
} else {
pitch = -(matrix[1][0].atan2(matrix[1][1])).to_degrees();
yaw = -(std::f32::consts::FRAC_PI_2).to_degrees();
roll = 0.0;
}
} else {
pitch = matrix[1][0].atan2(matrix[1][1]).to_degrees();
yaw = std::f32::consts::FRAC_PI_2.to_degrees();
roll = 0.0;
}
let rotation = glm::vec3(pitch, yaw, roll);
(pos, rotation, scale)
}
fn create_rotation_matrix(pitch: f32, yaw: f32, roll: f32, pivot: glm::Vec3) -> glm::Mat4 {
let pitch = pitch.to_radians();
let yaw = yaw.to_radians();
let roll = roll.to_radians();
let mat = utils::mat_ident();
let rot_z = glm::ext::rotate(&mat, roll, glm::vec3(0.0, 0.0, 1.0));
let rot_x = glm::ext::rotate(&mat, pitch, glm::vec3(1.0, 0.0, 0.0));
let rot_y = glm::ext::rotate(&mat, yaw, glm::vec3(0.0, 1.0, 0.0));
let trans_to_origin = glm::ext::translate(&mat, -glm::vec3(pivot.x, pivot.y, pivot.z));
let trans_back_to_center = glm::ext::translate(&mat, glm::vec3(pivot.x, pivot.y, pivot.z));
let combined = trans_back_to_center * rot_z * rot_y * rot_x * trans_to_origin;
combined
}
pub fn apply_rotation(matrix: &glm::Mat4, rot: glm::Vec3, pivot: glm::Vec3) -> glm::Mat4 {
let rot = create_rotation_matrix(rot.x, rot.y, rot.z, pivot);
return rot * *matrix;
}
#[derive(Debug)]
pub struct Mesh {
pub name: String,
pub position: glm::Vec3,
pub rotation: glm::Vec3,
pub scale: glm::Vec3,
pub pivot: glm::Vec3,
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub textures: Vec<Texture>,
pub material: Material,
vao: u32,
}
impl Mesh {
pub fn new(name: &str, vertices: Vec<Vertex>, indices: Vec<u32>, textures: Vec<Texture>, material: Material, transformation: &mut glm::Mat4) -> Mesh {
let mut vao = 0;
let mut vbo = 0;
let mut ebo = 0;
unsafe {
gl::GenVertexArrays(1, &mut vao);
gl::GenBuffers(1, &mut vbo);
gl::GenBuffers(1, &mut ebo);
gl::BindVertexArray(vao);
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
gl::BufferData(gl::ARRAY_BUFFER, (std::mem::size_of::<Vertex>() * vertices.len() as usize) as isize, vertices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (std::mem::size_of::<u32>() * indices.len() as usize) as isize, indices.as_ptr() as *const std::ffi::c_void, gl::STATIC_DRAW);
// vertex positions
gl::EnableVertexAttribArray(0);
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, std::ptr::null());
// vertex normals
gl::EnableVertexAttribArray(1);
gl::VertexAttribPointer(1, 3, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (3 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
// vertex texture coords
gl::EnableVertexAttribArray(2);
gl::VertexAttribPointer(2, 2, gl::FLOAT, gl::FALSE, std::mem::size_of::<Vertex>() as i32, (6 * std::mem::size_of::<f32>()) as *const std::ffi::c_void);
gl::BindVertexArray(0);
}
let (position, rotation, scale) = decompose_mat(transformation);
Mesh {
name: name.to_string(),
vertices,
indices,
textures,
material,
vao,
position,
rotation,
scale,
pivot: position,
}
}
pub fn draw(&self, shader: &Shader, scale: f32, pivot: glm::Vec3) {
let mut diffuse = 1;
let mut specular = 1;
shader.use_shader();
let model_mat = glm::ext::scale(&utils::mat_ident(), glm::vec3(scale, scale, scale));
let model_mat = apply_rotation(&model_mat, self.rotation, pivot);
let model_mat = glm::ext::translate(&model_mat, glm::vec3(self.position.x, self.position.y, self.position.z));
shader.set_mat4fv("model", &model_mat);
shader.set_3fv("material.ambient", self.material.ambient);
shader.set_3fv("material.diffuse", self.material.diffuse);
shader.set_3fv("material.specular", self.material.specular);
shader.set_float("material.shininess", self.material.shininess);
for i in 0..self.textures.len() {
unsafe {
gl::ActiveTexture(gl::TEXTURE0 + i as u32);
match self.textures[i].typ {
russimp::material::TextureType::Diffuse => {
diffuse += 1;
shader.set_int(format!("material.texture_diffuse{}", diffuse).as_str(), i as i32);
},
russimp::material::TextureType::Specular => {
specular += 1;
shader.set_int(format!("material.texture_specular{}", specular).as_str(), i as i32);
},
_ => {}, // don't do anything because unsupported texture types are logged
// when the model is loaded
}
gl::BindTexture(gl::TEXTURE_2D, self.textures[i].id);
}
}
unsafe {
// draw Mesh
gl::BindVertexArray(self.vao);
gl::DrawElements(gl::TRIANGLES, self.indices.len() as i32, gl::UNSIGNED_INT, std::ptr::null());
// reset stuff to default
gl::ActiveTexture(gl::TEXTURE0);
gl::BindVertexArray(0);
}
}
pub fn rotate(&mut self, rotation: glm::Vec3) {
self.rotation = self.rotation + rotation;
}
}
#[derive(Clone, Debug)]
#[repr(packed(2))]
pub struct Vertex {
pub position: glm::Vec3,
pub normal: glm::Vec3,
pub tex_coords: glm::Vec2,
}
impl Vertex {
pub fn new(position: glm::Vec3, normal: glm::Vec3, tex_coords: glm::Vec2) -> Self {
Vertex {
position,
normal,
tex_coords
}
}
}
#[derive(Clone, Debug)]
pub struct Texture {
pub id: u32,
pub typ: russimp::material::TextureType,
pub path: std::path::PathBuf,
}
impl Texture {
pub fn new(path: std::path::PathBuf, typ: russimp::material::TextureType) -> Result<Self, Box<dyn std::error::Error>> {
let path_str = match path.to_str() {
Some(path) => path,
None => return Err("Failed to convert texture path to string".into()),
};
let id = utils::load_texture(path_str)?;
Ok(Texture {
id,
typ,
path,
})
}
}
#[derive(Debug)]
pub struct Material {
pub name: String,
pub ambient: glm::Vec3,
pub diffuse: glm::Vec3,
pub specular: glm::Vec3,
pub shininess: f32,
}
impl Material {
pub fn new(name: String, ambient: glm::Vec3, diffuse: glm::Vec3, specular: glm::Vec3, shininess: f32) -> Self {
Material {
name,
ambient,
diffuse,
specular,
shininess,
}
}
}
impl std::fmt::Display for Material {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Ambient: {:?}\nDiffuse: {:?}\nSpecular: {:?}\nShininess: {}", self.ambient, self.diffuse, self.specular, self.shininess)
}
}
+325
View File
@@ -0,0 +1,325 @@
use crate::{mesh::{Mesh, Vertex, Texture, Material, apply_rotation}, shader::Shader, utils, ui::ui, log, aabb};
use russimp;
use anyhow::{Result, anyhow};
const SUPPORTED_TEXTURE_TYPES: [russimp::material::TextureType; 2] = [
russimp::material::TextureType::Diffuse,
russimp::material::TextureType::Specular,
];
const SCALING_FACTOR: f32 = 10.0;
#[derive(Debug)]
pub struct Model {
pub id: u32,
pub name: String,
pub meshes: Vec<Mesh>,
pub aabb: aabb::AABB,
pub aabb_mesh: aabb::AABBMesh,
pub scaling_factor: f32,
}
fn process_node<'a>(
node: &russimp::node::Node,
scene: &'a russimp::scene::Scene,
meshes: &mut Vec<Mesh>,
dir: &std::path::PathBuf,
loaded_textures: &mut Vec<Texture>,
init_trans: &glm::Mat4,
aabb: &mut aabb::AABB,
scale: &mut f32,
) -> Vec<Box<dyn std::error::Error>> {
let mut errors = vec![];
let node_trans = glm::mat4(
node.transformation.a1, node.transformation.a2, node.transformation.a3, node.transformation.a4,
node.transformation.b1, node.transformation.b2, node.transformation.b3, node.transformation.b4,
node.transformation.c1, node.transformation.c2, node.transformation.c3, node.transformation.c4,
node.transformation.d1, node.transformation.d2, node.transformation.d3, node.transformation.d4,
);
let mut new_trans = *init_trans * node_trans;
for i in 0..node.meshes.len() {
let mesh = &scene.meshes[node.meshes[i] as usize];
let (processed_mesh, mut errs) = process_mesh(mesh, scene, dir, loaded_textures, &mut new_trans, aabb);
errors.append(&mut errs);
meshes.push(processed_mesh);
}
for child in node.children.borrow().clone().into_iter() {
let mut errs = process_node(&child, scene, meshes, dir, loaded_textures, &new_trans, aabb, scale);
errors.append(&mut errs);
}
let scale_factor_x = SCALING_FACTOR / (aabb.max.x - aabb.min.x);
let scale_factor_y = SCALING_FACTOR / (aabb.max.y - aabb.min.y);
let scale_factor_z = SCALING_FACTOR / (aabb.max.z - aabb.min.z);
// Use the minimum scaling factor to maintain proportions
*scale = scale_factor_x.min(scale_factor_y).min(scale_factor_z);
return errors;
}
fn process_mesh(
mesh: &russimp::mesh::Mesh,
scene: &russimp::scene::Scene,
dir: &std::path::PathBuf,
loaded_textures: &mut Vec<Texture>,
transformation: &mut glm::Mat4,
aabb: &mut aabb::AABB
) -> (Mesh, Vec<Box<dyn std::error::Error>>) {
let mut vertices = vec![];
let mut indices = vec![];
let mut textures = vec![];
for i in 0..mesh.vertices.len() {
let pos = glm::vec4(mesh.vertices[i].x, mesh.vertices[i].y, mesh.vertices[i].z, 1.0);
let norm = match mesh.normals.len() {
0 => glm::vec3(0.0, 0.0, 0.0),
_ => glm::vec3(mesh.normals[i].x, mesh.normals[i].y, mesh.normals[i].z),
};
let t = &mesh.texture_coords[0];
let tex_coords = match t {
Some(tex) => {
glm::vec2(tex[i].x, tex[i].y)
},
None => glm::vec2(0.0, 0.0)
};
let vertex = Vertex::new(pos.truncate(3), norm, tex_coords);
if vertex.position.x < aabb.min.x {
aabb.min.x = vertex.position.x;
}
if vertex.position.y < aabb.min.y {
aabb.min.y = vertex.position.y;
}
if vertex.position.z < aabb.min.z {
aabb.min.z = vertex.position.z;
}
if vertex.position.x > aabb.max.x {
aabb.max.x = vertex.position.x;
}
if vertex.position.y > aabb.max.y {
aabb.max.y = vertex.position.y;
}
if vertex.position.z > aabb.max.z {
aabb.max.z = vertex.position.z;
}
vertices.push(vertex);
}
for i in 0..mesh.faces.len() {
for j in 0..mesh.faces[i].0.len() {
indices.push(mesh.faces[i].0[j]);
}
}
let mat = &scene.materials[mesh.material_index as usize];
let material = process_material(mat);
let (mut found_textures, errs) = load_material_textures(mat, dir, loaded_textures);
textures.append(&mut found_textures);
let mesh = Mesh::new(mesh.name.as_str(), vertices, indices, textures, material, transformation);
return (mesh, errs);
}
fn process_material(mat: &russimp::material::Material) -> Material {
let mut mat_name = String::from("Default_Mat");
let mut ambient = glm::vec3(0.2, 0.2, 0.2);
let mut diffuse = glm::vec3(0.7, 0.7, 0.7);
let mut specular = glm::vec3(0.1, 0.1, 0.1);
let mut shininess = 32.0;
// TODO: better way of mapping properties
// for property in mat.properties.iter() {
// match property.key.as_str() {
// "$clr.ambient" => {
// ambient = match &property.data {
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
// glm::vec3(a[0], a[1], a[2])
// },
// _ => panic!("Property should not be this type: {}", property.key)
// };
// },
// "$clr.diffuse" => {
// diffuse = match &property.data {
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
// glm::vec3(a[0], a[1], a[2])
// },
// _ => panic!("Property should not be this type: {}", property.key)
// };
// },
// "$clr.specular" => {
// specular = match &property.data {
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
// glm::vec3(a[0], a[1], a[2])
// },
// _ => panic!("Property should not be this type: {}", property.key)
// }
// }
// "?mat.name" => {
// mat_name = match &property.data {
// russimp::material::PropertyTypeInfo::String(s) => {
// s.to_string()
// },
// _ => panic!("Property should not be this type: {}", property.key)
// };
// }
// "$mat.shininess" => {
// shininess = match &property.data {
// russimp::material::PropertyTypeInfo::FloatArray(a) => {
// a[0]
// },
// _ => panic!("Property should not be this type: {}", property.key)
// };
// }
// _ => {},
// }
// }
Material::new(mat_name, ambient, diffuse, specular, shininess)
}
// TODO: might nuke this cuz we don't need textures I think
fn load_material_textures(
mat: &russimp::material::Material,
dir: &std::path::PathBuf,
loaded_textures: &mut Vec<Texture>
) -> (Vec<Texture>, Vec<Box<dyn std::error::Error>>) {
let mut textures = vec![];
let mut errors = vec![];
for (typ, tex) in mat.textures.iter() {
if SUPPORTED_TEXTURE_TYPES.contains(typ) {
let texture = tex.borrow();
let mut skip = false;
let tex_filename = &texture.filename;
// HACK: fix this
if tex_filename.is_empty() {
continue;
}
// println!("texture filename: {}", tex_filename);
let path = dir.join(tex_filename);
for loaded_tex in &mut *loaded_textures {
if loaded_tex.path == path {
textures.push(loaded_tex.clone());
skip = true;
break;
}
}
if !skip {
match Texture::new(path, *typ) {
Ok(texture) => {
loaded_textures.push(texture.clone());
textures.push(texture);
},
Err(e) => {
let err = anyhow!("Error loading texture: {}", e);
println!("{}", err);
errors.push(err.into());
},
}
}
}
}
return (textures, errors);
}
impl Model {
pub fn new(path: &str, state: &mut ui::State) -> Result<Self, Box<dyn std::error::Error>> {
let scene = russimp::scene::Scene::from_file(path,
vec![
russimp::scene::PostProcess::Triangulate,
russimp::scene::PostProcess::GenerateNormals,
russimp::scene::PostProcess::FlipUVs,
])
.map_err(|e| {
let e = match e {
russimp::RussimpError::TextureNotFound => anyhow!("Texture not found"),
_ => anyhow!("{}", e)
};
return e;
})?;
let root_node = match &scene.root {
Some(root) => root,
None => return Err("Scene has no root node".into()),
};
if scene.flags & russimp::sys::AI_SCENE_FLAGS_INCOMPLETE == 1 {
return Err("Scene is incomplete")?;
}
let directory = match std::path::Path::new(path).parent() {
Some(dir) => dir,
None => return Err("Model path has no parent directory".into()),
}.to_path_buf();
let mut loaded_textures = vec![];
let mut meshes = vec![];
let init_trans_mat = utils::mat_ident();
let mut aabb = aabb::AABB{
min: glm::vec3(std::f32::MAX, std::f32::MAX, std::f32::MAX),
max: glm::vec3(std::f32::MIN, std::f32::MIN, std::f32::MIN),
};
let mut scale: f32 = 1.0;
let errors = process_node(&root_node, &scene, &mut meshes, &directory, &mut loaded_textures, &init_trans_mat, &mut aabb, &mut scale);
for err in errors {
state.logger.history.push(log::LogMessage::new(log::LogLevel::Warning, &err.to_string()));
}
Ok(Model {
id: state.get_next_id(),
name: root_node.name.to_owned(),
aabb_mesh: aabb::AABBMesh::new(&aabb),
aabb,
scaling_factor: scale,
meshes,
})
}
pub fn draw(&self, shader: &Shader, draw_aabb: bool) {
let center_x = ((self.aabb.max.x / 2.0) + (self.aabb.min.x / 2.0)) * self.scaling_factor;
let center_y = ((self.aabb.max.y / 2.0) + (self.aabb.min.y / 2.0)) * self.scaling_factor;
let center_z = ((self.aabb.max.z / 2.0) + (self.aabb.min.z / 2.0)) * self.scaling_factor;
let pivot = glm::vec3(center_x, center_y, center_z);
let model_mat = glm::ext::scale(&utils::mat_ident(), glm::vec3(self.scaling_factor, self.scaling_factor, self.scaling_factor));
let model_mat = apply_rotation(&model_mat, self.meshes[0].rotation, pivot);
let model_mat = glm::ext::translate(&model_mat, glm::vec3(self.meshes[0].position.x, self.meshes[0].position.y, self.meshes[0].position.z));
for mesh in &self.meshes {
mesh.draw(shader, self.scaling_factor, pivot);
}
if draw_aabb {
self.aabb_mesh.draw(shader, &model_mat);
}
}
pub fn rotate(&mut self, xoffset: f32, yoffset: f32) {
let rotation = glm::vec3(-yoffset, xoffset, 0.0);
for mesh in &mut self.meshes {
mesh.rotate(rotation);
}
}
}
+116
View File
@@ -0,0 +1,116 @@
use glad_gl::gl;
use anyhow::{Context, Result};
pub struct Shader {
pub program_id: gl::GLuint,
}
impl Shader {
pub fn new(vertex_path: &str, frag_path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let mut vertex_shader_source = std::fs::read_to_string(vertex_path).with_context(|| format!("Failed to read vertex shader file: {}", vertex_path))?;
vertex_shader_source.push('\0');
let vertex_shader_source = std::ffi::CStr::from_bytes_with_nul(vertex_shader_source.as_bytes())?;
let mut frag_shader_source = std::fs::read_to_string(frag_path).with_context(|| format!("Failed to read frag shader file: {}", frag_path))?;
frag_shader_source.push('\0');
let frag_shader_source = std::ffi::CStr::from_bytes_with_nul(frag_shader_source.as_bytes())?;
unsafe {
let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
gl::CreateShader(vertex_shader);
gl::ShaderSource(vertex_shader, 1, &vertex_shader_source.as_ptr(), std::ptr::null());
gl::CompileShader(vertex_shader);
let mut success1 = 0;
gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success1);
println!("vertex shader {:?} compiled with status: {}",
std::path::Path::new(vertex_path).file_name().unwrap(),
success1
);
if success1 == 0 {
let info_buf = [0u8; 512];
gl::GetShaderInfoLog(vertex_shader as u32, 512, std::ptr::null_mut(), info_buf.as_ptr() as *mut i8);
println!("vertex shader info: {}", std::str::from_utf8(&info_buf).unwrap());
}
let frag_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
gl::CreateShader(frag_shader);
gl::ShaderSource(frag_shader, 1, &frag_shader_source.as_ptr(), std::ptr::null());
gl::CompileShader(frag_shader);
let mut success2 = 0;
gl::GetShaderiv(frag_shader, gl::COMPILE_STATUS, &mut success2);
println!("frag shader {:?} compiled with status: {}",
std::path::Path::new(frag_path).file_name().unwrap(),
success2
);
if success2 == 0 {
let info_buf2 = [0u8; 512];
gl::GetShaderInfoLog(frag_shader as u32, 512, std::ptr::null_mut(), info_buf2.as_ptr() as *mut i8);
println!("frag shader info: {}", std::str::from_utf8(&info_buf2).unwrap());
}
let shader_program = gl::CreateProgram();
gl::AttachShader(shader_program, vertex_shader);
gl::AttachShader(shader_program, frag_shader);
gl::LinkProgram(shader_program);
gl::DeleteShader(vertex_shader);
gl::DeleteShader(frag_shader);
Ok(Self {
program_id: shader_program,
})
}
}
pub fn use_shader(&self) {
unsafe {
gl::UseProgram(self.program_id);
}
}
pub fn set_bool(&self, name: &str, value: bool) {
unsafe {
let c_str = std::ffi::CString::new(name).unwrap();
gl::Uniform1i(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), value as i32);
}
}
pub fn set_int(&self, name: &str, value: i32) {
let c_str = std::ffi::CString::new(name).unwrap();
unsafe {
gl::Uniform1i(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), value);
}
}
pub fn set_float(&self, name: &str, value: f32) {
let c_str = std::ffi::CString::new(name).unwrap();
unsafe {
gl::Uniform1f(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), value);
}
}
pub fn get_float(&self, name: &str) -> f32 {
let c_str = std::ffi::CString::new(name).unwrap();
let mut value = 0.0;
unsafe {
gl::GetUniformfv(self.program_id, gl::GetUniformLocation(self.program_id, c_str.as_ptr()), &mut value);
value
}
}
pub fn set_mat4fv(&self, name: &str, value: &glm::Mat4) {
let c_str = std::ffi::CString::new(name).unwrap();
unsafe {
gl::UniformMatrix4fv(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), 1, gl::FALSE, value.as_array().as_ptr() as *const f32);
}
}
pub fn set_3fv(&self, name: &str, value: glm::Vec3) {
let c_str = std::ffi::CString::new(name).unwrap();
unsafe {
gl::Uniform3fv(gl::GetUniformLocation(self.program_id, c_str.as_ptr()), 1, value.as_array() as *const f32);
}
}
}
+95
View File
@@ -0,0 +1,95 @@
use std::os::raw::c_char;
pub struct DockNode {
id: u32,
}
impl DockNode {
fn new(id: u32) -> Self {
Self { id }
}
pub fn is_split(&self) -> bool {
unsafe {
let node = imgui::sys::igDockBuilderGetNode(self.id);
if node.is_null() {
false
} else {
imgui::sys::ImGuiDockNode_IsSplitNode(node)
}
}
}
/// Dock window into this dockspace
#[doc(alias = "DockBuilder::DockWindow")]
pub fn dock_window(&self, window: &str) {
let window = imgui::ImString::from(window.to_string());
unsafe { imgui::sys::igDockBuilderDockWindow(window.as_ptr(), self.id) }
}
#[doc(alias = "DockBuilder::SplitNode")]
pub fn split<D, O>(&self, split_dir: imgui::Direction, size_ratio: f32, dir: D, opposite_dir: O)
where
D: FnOnce(DockNode),
O: FnOnce(DockNode),
{
if self.is_split() {
// Can't split an already split node (need to split the
// node within)
return;
}
let mut out_id_at_dir: imgui::sys::ImGuiID = 0;
let mut out_id_at_opposite_dir: imgui::sys::ImGuiID = 0;
unsafe {
imgui::sys::igDockBuilderSplitNode(
self.id,
split_dir as i32,
size_ratio,
&mut out_id_at_dir,
&mut out_id_at_opposite_dir,
);
}
dir(DockNode::new(out_id_at_dir));
opposite_dir(DockNode::new(out_id_at_opposite_dir));
}
}
/// # Docking
pub struct UiDocking {}
impl UiDocking {
#[doc(alias = "IsWindowDocked")]
pub fn is_window_docked(&self) -> bool {
unsafe { imgui::sys::igIsWindowDocked() }
}
/// Create dockspace with given label. Returns a handle to the
/// dockspace which can be used to, say, programatically split or
/// dock windows into it
#[doc(alias = "DockSpace")]
pub fn dockspace(&self, label: &str) -> DockNode {
let label = imgui::ImString::from(label.to_string());
unsafe {
let id = imgui::sys::igGetID_Str(label.as_ptr() as *const c_char);
imgui::sys::igDockSpace(
id,
[0.0, 0.0].into(),
0,
std::ptr::null::<imgui::sys::ImGuiWindowClass>(),
);
DockNode { id }
}
}
#[doc(alias = "DockSpaceOverViewport")]
pub fn dockspace_over_viewport(&self) {
unsafe {
imgui::sys::igDockSpaceOverViewport(
imgui::sys::igGetMainViewport(),
0,
std::ptr::null::<imgui::sys::ImGuiWindowClass>(),
);
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod docking;
pub mod ui;
+432
View File
@@ -0,0 +1,432 @@
use glad_gl::gl;
use crate::{camera::Camera, model, imgui_glfw_support, imgui_opengl_renderer, mesh, ui, log};
use std::time::{SystemTime, UNIX_EPOCH};
pub struct State {
pub active_model: Option<u32>,
pub show_console: bool,
pub show_help_menu_about: bool,
pub is_cursor_captured: bool,
pub can_capture_cursor: bool,
pub draw_grid: bool,
pub draw_aabb: bool,
pub wireframe: bool,
pub first_frame_drawn: bool,
pub camera: Camera,
pub objects: Vec<model::Model>,
pub viewport_size: [f32; 2],
pub logger: log::Log,
}
impl Default for State {
fn default() -> Self {
Self {
active_model: None,
show_console: false,
first_frame_drawn: false,
is_cursor_captured: false,
can_capture_cursor: false,
draw_grid: true,
draw_aabb: false,
show_help_menu_about: false,
wireframe: false,
camera: Camera::new(),
objects: vec![],
viewport_size: [0.0, 0.0],
logger: log::Log::default(),
}
}
}
impl State {
pub fn get_next_id(&self) -> u32 {
let mut id = 0;
if let Some(last_model) = self.objects.last() {
id = last_model.id + 1;
}
return id;
}
}
pub fn init_imgui(window: &mut glfw::Window) -> (imgui::Context, imgui_glfw_support::GlfwPlatform, imgui_opengl_renderer::Renderer) {
let mut imgui = imgui::Context::create();
imgui.set_ini_filename(None);
imgui.io_mut().config_flags.insert(imgui::ConfigFlags::DOCKING_ENABLE);
imgui.io_mut().config_flags.set(imgui::ConfigFlags::NAV_ENABLE_KEYBOARD, true);
let mut glfw_platform = imgui_glfw_support::GlfwPlatform::init(&mut imgui);
glfw_platform.attach_window(
imgui.io_mut(),
&window,
imgui_glfw_support::HiDpiMode::Rounded
);
imgui
.fonts()
.add_font(&[imgui::FontSource::DefaultFontData { config: None }]);
imgui.io_mut().font_global_scale = (1.0 / glfw_platform.hidpi_factor()) as f32;
gl::load(|e| window.get_proc_address(e) as *const std::os::raw::c_void);
let renderer = imgui_opengl_renderer::Renderer::new(&mut imgui);
glfw_platform.set_clipboard_backend(&mut imgui, &window);
(imgui, glfw_platform, renderer)
}
pub fn import_model(state: &mut State) {
let models = match rfd::FileDialog::new()
.set_title("Import Model(s)")
.set_directory("./")
.add_filter("All supported files", &["obj", "OBJ", "stl", "STL"])
.add_filter("Wavefront OBJ (.obj)", &["obj", "OBJ"])
.add_filter("STL (.stl)", &["stl", "STL"])
.pick_files() {
Some(m) => m,
None => return,
};
for model_path in &models {
let model = model::Model::new(model_path.to_str().unwrap(), state);
match model {
Ok(m) => {
state.active_model = Some(m.id);
state.objects.push(m);
state.camera.update_position(state.active_model, &state.objects);
},
Err(e) => {
let error = format!("Error loading model \"{}\": {}", model_path.to_str().unwrap(), e);
println!("{}", error);
state.logger.log(&error, log::LogLevel::Error);
},
}
}
}
pub fn draw_main_menu_bar(ui: &imgui::Ui, state: &mut State, window: &mut glfw::Window, delta_time: f32) {
ui.main_menu_bar(|| {
ui.menu("File", || {
if ui.menu_item_config("Import Model(s)").shortcut("Ctrl+O").build() {
import_model(state);
}
if ui.menu_item_config("Quit").shortcut("Ctrl+Q").build() {
window.set_should_close(true);
}
});
ui.menu("View", || {
if ui.menu_item_config("Show Grid").selected(state.draw_grid).build() {
state.draw_grid = !state.draw_grid;
}
});
ui.menu("Help", || {
if ui.menu_item_config("About").selected(state.show_help_menu_about).build() {
state.show_help_menu_about = !state.show_help_menu_about;
}
});
let fps = format!("FPS: {:.1}", 1.0 / delta_time);
let avail_size = [*ui.content_region_avail().get(0).unwrap() - ui.calc_text_size(&fps)[0], 0.0];
ui.dummy(avail_size);
ui.text(&fps);
});
}
fn draw_transformations(ui: &imgui::Ui, mesh: &mut mesh::Mesh) {
imgui::Drag::new("###XPos")
.range(f32::NEG_INFINITY, f32::INFINITY)
.speed(0.1)
.display_format("X: %.3f")
.build(ui, &mut mesh.position.x);
imgui::Drag::new("###YPos")
.range(f32::NEG_INFINITY, f32::INFINITY)
.speed(0.1)
.display_format("Y: %.3f")
.build(ui, &mut mesh.position.y);
imgui::Drag::new("###ZPos")
.range(f32::NEG_INFINITY, f32::INFINITY)
.speed(0.1)
.display_format("Z: %.3f")
.build(ui, &mut mesh.position.z);
imgui::Drag::new("Scale")
.range(f32::NEG_INFINITY, f32::INFINITY)
.speed(0.1)
.display_format("%.7f")
.build_array(ui, mesh.scale.as_array_mut());
imgui::Drag::new("Rotation")
.range(f32::NEG_INFINITY, f32::INFINITY)
.speed(1.0)
.display_format("%.2f")
.build_array(ui, mesh.rotation.as_array_mut());
}
fn draw_mesh_hierarchy(ui: &imgui::Ui, mesh: &mut mesh::Mesh, i: usize) {
ui.tree_node_config(format!("{}###{}", mesh.name.as_str(), i)).build(|| {
ui.text(format!("Vertices: {}", mesh.vertices.len()));
ui.text(format!("Faces: {}", mesh.vertices.len() / 3));
ui.text(format!("Textures: {}", mesh.textures.len()));
ui.tree_node_config(mesh.material.name.as_str()).build(|| {
ui.text(format!("{}", mesh.material));
});
ui.tree_node_config("Transformations").build(|| {
draw_transformations(ui, mesh);
})
});
}
fn draw_object_hierarchy(ui: &imgui::Ui, state: &mut State, idx: usize) -> bool {
let object = &mut state.objects[idx];
if let Some(..) = ui.begin_table_with_sizing("Objects Table", 2, imgui::TableFlags::SIZING_STRETCH_PROP, [0.0, 0.0], 0.0) {
ui.table_next_row();
ui.table_next_column();
ui.tree_node_config(format!("{}###{}", object.name.as_str(), idx))
.build(|| {
for (j, mesh) in &mut object.meshes.iter_mut().enumerate() {
draw_mesh_hierarchy(ui, mesh, j);
}
});
ui.table_next_column();
if ui.small_button(format!("X###{}-{}", object.name.as_str(), idx)) {
let output = format!("Removing object {}", object.name);
println!("{}", output);
state.logger.log(&output, log::LogLevel::Info);
return true;
}
if ui.checkbox("Selected", &mut (Some(state.objects[idx].id) == state.active_model)) {
state.active_model = Some(state.objects[idx].id);
state.camera.update_position(state.active_model, &state.objects);
}
}
return false;
}
fn draw_objects_window(ui: &imgui::Ui, state: &mut State) {
ui.window("Objects")
.size([500.0, 200.0], imgui::Condition::FirstUseEver)
.build(|| {
let mut i = 0;
while i < state.objects.len() {
if draw_object_hierarchy(ui, state, i) {
state.objects.remove(i);
state.active_model = state.objects.last().and_then(|o| Some(o.id));
state.camera.update_position(state.active_model, &state.objects);
continue;
}
i = i + 1;
}
});
}
fn draw_console(ui: &imgui::Ui, state: &mut State) {
ui.window("Console")
.size([500.0, 200.0], imgui::Condition::FirstUseEver)
.build(|| {
ui.child_window("###ConsoleHistory")
.size([0.0, -27.0])
.build(|| {
for line in state.logger.history.iter() {
let style = ui.push_style_color(imgui::StyleColor::Text, line.level.clone());
ui.text_wrapped(line.message.clone());
style.pop();
}
if ui.scroll_y() >= ui.scroll_max_y() {
ui.set_scroll_here_y_with_ratio(1.0);
}
});
ui.separator();
if ui.button("Clear") {
state.logger.clear();
}
});
}
fn create_initial_docking(ui: &imgui::Ui, state: &mut State) {
let flags =
// No borders etc for top-level window
imgui::WindowFlags::NO_DECORATION | imgui::WindowFlags::NO_MOVE
// Show menu bar
| imgui::WindowFlags::MENU_BAR
// Don't raise window on focus (as it'll clobber floating windows)
| imgui::WindowFlags::NO_BRING_TO_FRONT_ON_FOCUS | imgui::WindowFlags::NO_NAV_FOCUS
// Don't want the dock area's parent to be dockable!
| imgui::WindowFlags::NO_DOCKING
;
let padding = ui.push_style_var(imgui::StyleVar::WindowPadding([0.0, 0.0]));
let rounding = ui.push_style_var(imgui::StyleVar::WindowRounding(0.0));
ui.window("Main Window")
.flags(flags)
.position([0.0, 0.0], imgui::Condition::Always)
.size(ui.io().display_size, imgui::Condition::Always)
.build(|| {
// Create top-level docking area, needs to be made early (before docked windows)
let ui_d = ui::docking::UiDocking {};
let space = ui_d.dockspace("MainDockArea");
// Set up splits, docking windows. This can be done conditionally,
// or calling it every time is also mostly fine
if !state.first_frame_drawn {
space.split(
imgui::Direction::Right,
0.3,
|right| {
right.dock_window("Objects");
right.dock_window("Console");
},
|left| {
left.dock_window("Viewer");
}
)
}
});
padding.pop();
rounding.pop();
}
fn draw_viewport(ui: &imgui::Ui, state: &mut State, texture: u32) {
ui.window("Viewer")
.size(ui.content_region_avail(), imgui::Condition::FirstUseEver)
.no_decoration()
.resizable(true)
.build(|| {
let mut tex_size = ui.content_region_avail();
tex_size[1] -= 25.0;
state.viewport_size = tex_size;
if ui.button("Reset Camera") {
state.camera.update_position(state.active_model, &state.objects);
}
ui.same_line();
if ui.button("Capture Scene") {
let now = std::time::Instant::now();
let mut w = 0;
let mut h = 0;
unsafe {
gl::GetTextureLevelParameteriv(texture, 0, gl::TEXTURE_WIDTH, &mut w);
gl::GetTextureLevelParameteriv(texture, 0, gl::TEXTURE_HEIGHT, &mut h);
}
let mut pixels = vec![0u8; (w * h * 4) as usize];
unsafe {
gl::GetTextureImage(texture, 0, gl::RGBA, gl::UNSIGNED_BYTE, (w * h * 4) as i32, pixels.as_mut_ptr() as *mut std::ffi::c_void);
}
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Current time to not be before the UNIX epoch");
let file_name = format!("capture-{}.png", timestamp.as_secs());
let save_path = std::path::Path::new(file_name.as_str());
let capture = image::ImageBuffer::<image::Rgba<u8>, _>::from_raw(w as u32, h as u32, pixels).unwrap();
let capture = image::DynamicImage::ImageRgba8(capture);
let capture = capture.flipv();
let capture = capture.resize_exact(tex_size[0] as u32, tex_size[1] as u32, image::imageops::FilterType::Gaussian);
let _ = capture.save(save_path);
let elapsed = now.elapsed();
state.logger.log(
format!("Scene capture saved to: {} successfully",
save_path
.canonicalize()
.expect("Capture path to be canonicalized")
.to_str()
.expect("Capture path to be valid unicode"))
.as_str(),
log::LogLevel::Info);
state.logger.log(
format!("Scene capture took: {}ms",
elapsed.as_millis())
.as_str(),
log::LogLevel::Debug);
unsafe {
gl::BindFramebuffer(gl::FRAMEBUFFER, 0);
}
}
ui.same_line();
ui.checkbox("Wireframe", &mut state.wireframe);
ui.same_line();
ui.checkbox("Bounding box", &mut state.draw_aabb);
ui.same_line();
ui.set_next_item_width(200.0);
imgui::Drag::new("Camera Speed")
.range(1.0, 10000.0)
.speed(1.0)
.display_format("%.3f")
.build(ui, &mut state.camera.speed);
imgui::Image::new(imgui::TextureId::new(texture.try_into().unwrap()), tex_size)
// flip the image vertically
.uv0([0.0, 1.0])
.uv1([1.0, 0.0])
.build(ui);
// only allow capturing the cursor if the mouse is over the viewport
state.can_capture_cursor = ui.is_item_hovered();
});
}
pub fn draw_ui(
imgui: &mut imgui::Context,
renderer: &imgui_opengl_renderer::Renderer,
glfw_platform: &imgui_glfw_support::GlfwPlatform,
window: &mut glfw::Window,
state: &mut State,
delta_time: f32,
last_cursor: &mut Option<imgui::MouseCursor>,
scene_fb_texture: u32,
) {
glfw_platform.prepare_frame(imgui.io_mut(), window).expect("Failed to prepare imgui frame");
let display_size = imgui.io().display_size;
let ui = imgui.new_frame();
create_initial_docking(ui, state);
draw_main_menu_bar(ui, state, window, delta_time);
if state.show_help_menu_about {
ui.window("About")
.size([300.0, 150.0], imgui::Condition::FirstUseEver)
.opened(&mut state.show_help_menu_about)
.position([display_size[0] / 2.0, display_size[1] / 2.0], imgui::Condition::Always)
.position_pivot([0.5, 0.5])
.build(|| {
ui.text("3dobs - 3D Object Browser");
ui.text(format!("Version: {}-{}", env!("CARGO_PKG_VERSION"), option_env!("GIT_HASH").unwrap_or("unknown")));
ui.text(format!("{}", env!("CARGO_PKG_DESCRIPTION")));
ui.spacing();
ui.spacing();
ui.text(format!("Made by: {}", env!("CARGO_PKG_AUTHORS")));
});
}
draw_viewport(ui, state, scene_fb_texture);
draw_objects_window(ui, state);
draw_console(ui, state);
ui.end_frame_early();
if !state.is_cursor_captured {
let cursor = ui.mouse_cursor();
if *last_cursor != cursor {
*last_cursor = cursor;
glfw_platform.prepare_render(&ui, window);
}
}
imgui.update_platform_windows();
renderer.render(imgui);
state.first_frame_drawn = true;
}
+45
View File
@@ -0,0 +1,45 @@
use glad_gl::gl;
use glm;
use anyhow::{Result, Context};
pub fn load_texture(path: &str) -> Result<u32> {
let tex = image::io::Reader::open(path)
.with_context(|| format!("Failed to open texture file: {}", path))?
.decode()
.with_context(|| format!("Failed to decode texture: {}", path))?;
let mut texture_id: u32 = 0;
let format = match tex.color().channel_count() {
1 => gl::RED,
2 => gl::RG,
3 => gl::RGB,
4 => gl::RGBA,
_ => panic!("Unknown image format")
};
unsafe {
gl::GenTextures(1, &mut texture_id);
gl::BindTexture(gl::TEXTURE_2D, texture_id);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR_MIPMAP_LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
gl::TexImage2D(gl::TEXTURE_2D, 0, format as i32, tex.width() as i32, tex.height() as i32, 0, format, gl::UNSIGNED_BYTE, tex.as_bytes().as_ptr() as *const std::ffi::c_void);
gl::GenerateMipmap(gl::TEXTURE_2D);
}
Ok(texture_id)
}
pub fn mat_ident() -> glm::Mat4 {
glm::mat4(
1., 0., 0., 0.,
0., 1., 0., 0.,
0., 0., 1., 0.,
0., 0., 0., 1.
)
}