mirror of
https://github.com/IllusionMan1212/3dobs.git
synced 2026-10-24 10:12:35 +00:00
initial commit
This commit is contained in:
+94
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod docking;
|
||||
pub mod ui;
|
||||
+432
@@ -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;
|
||||
}
|
||||
@@ -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.
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user