Initial commit
This commit is contained in:
63
src/camera.cpp
Normal file
63
src/camera.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <algorithm>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "camera.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
glm::mat4 Camera::get_view_matrix() const
|
||||
{
|
||||
return glm::lookAt(position, position + get_front(), glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
}
|
||||
|
||||
glm::mat4 Camera::get_projection_matrix(int width, int height) const
|
||||
{
|
||||
float aspect_ratio = (height > 0)
|
||||
? static_cast<float>(width) / static_cast<float>(height)
|
||||
: 1.0f;
|
||||
return glm::perspective(glm::radians(fov), aspect_ratio, near_plane, far_plane);
|
||||
}
|
||||
|
||||
glm::mat4 Camera::get_view_projection_matrix(int width, int height) const
|
||||
{
|
||||
return get_projection_matrix(width, height) * get_view_matrix();
|
||||
}
|
||||
|
||||
glm::vec3 Camera::get_front() const
|
||||
{
|
||||
glm::vec3 front;
|
||||
front.x = std::cos(glm::radians(yaw)) * std::cos(glm::radians(pitch));
|
||||
front.y = std::sin(glm::radians(pitch));
|
||||
front.z = std::sin(glm::radians(yaw)) * std::cos(glm::radians(pitch));
|
||||
return glm::normalize(front);
|
||||
}
|
||||
|
||||
glm::vec3 Camera::get_right() const
|
||||
{
|
||||
return glm::normalize(glm::cross(get_front(), glm::vec3(0.0f, 1.0f, 0.0f)));
|
||||
}
|
||||
|
||||
glm::vec3 Camera::get_up() const
|
||||
{
|
||||
return glm::normalize(glm::cross(get_right(), get_front()));
|
||||
}
|
||||
|
||||
void Camera::rotate(float delta_yaw, float delta_pitch)
|
||||
{
|
||||
yaw += delta_yaw;
|
||||
pitch += delta_pitch;
|
||||
pitch = std::clamp(pitch, -89.0f, 89.0f);
|
||||
}
|
||||
|
||||
void Camera::look_at(const glm::vec3& target)
|
||||
{
|
||||
glm::vec3 direction = glm::normalize(target - position);
|
||||
|
||||
pitch = glm::degrees(std::asin(direction.y));
|
||||
yaw = glm::degrees(std::atan2(direction.z, direction.x));
|
||||
pitch = std::clamp(pitch, -89.0f, 89.0f);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
44
src/camera.hpp
Normal file
44
src/camera.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef FAN_CAMERA_HPP_
|
||||
#define FAN_CAMERA_HPP_
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include "config.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Camera
|
||||
{
|
||||
public:
|
||||
glm::vec3 position{0.0f, 0.0f, 0.0f};
|
||||
|
||||
// Euler angles
|
||||
float yaw{-90.0f};
|
||||
float pitch{0.0f};
|
||||
|
||||
// Projection settings
|
||||
float fov{CAMERA_FOV};
|
||||
float near_plane{CAMERA_NEAR};
|
||||
float far_plane{CAMERA_FAR};
|
||||
|
||||
Camera() = default;
|
||||
explicit Camera(glm::vec3 position) : position(position) {}
|
||||
|
||||
// Matrix generation
|
||||
glm::mat4 get_view_matrix() const;
|
||||
glm::mat4 get_projection_matrix(int width, int height) const;
|
||||
glm::mat4 get_view_projection_matrix(int width, int height) const;
|
||||
|
||||
// Direction vectors
|
||||
glm::vec3 get_front() const;
|
||||
glm::vec3 get_right() const;
|
||||
glm::vec3 get_up() const;
|
||||
|
||||
// Helper functions
|
||||
void rotate(float delta_yaw, float delta_pitch);
|
||||
void look_at(const glm::vec3& target);
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_CAMERA_HPP_
|
||||
21
src/config.hpp
Normal file
21
src/config.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef FAN_CONFIG_HPP_
|
||||
#define FAN_CONFIG_HPP_
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
inline constexpr const char* VERSION_STRING = "Fan Engine - indev - OpenGL 3.3";
|
||||
|
||||
inline constexpr int WINDOW_WIDTH = 1200;
|
||||
inline constexpr int WINDOW_HEIGHT = 800;
|
||||
|
||||
inline constexpr float CAMERA_FOV = 45.0f;
|
||||
|
||||
inline constexpr float CAMERA_SPEED = 2.5f;
|
||||
inline constexpr float CAMERA_SENSITIVITY = 0.1f;
|
||||
inline constexpr float CAMERA_NEAR = 0.1f;
|
||||
inline constexpr float CAMERA_FAR = 100.0f;
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_CONFIG_HPP_
|
||||
26
src/entity.hpp
Normal file
26
src/entity.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef FAN_ENTITY_HPP_
|
||||
#define FAN_ENTITY_HPP_
|
||||
|
||||
#include "transform.hpp"
|
||||
#include "mesh.hpp"
|
||||
#include "material.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Entity
|
||||
{
|
||||
public:
|
||||
Transform transform{};
|
||||
Rc<Mesh> mesh;
|
||||
Rc<Material> material;
|
||||
|
||||
Entity(Rc<Mesh> mesh, Rc<Material> material)
|
||||
: mesh(mesh)
|
||||
, material(material) {}
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_ENTITY_HPP_
|
||||
21
src/fan.hpp
Normal file
21
src/fan.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef FAN_HPP_
|
||||
#define FAN_HPP_
|
||||
|
||||
#include <camera.hpp>
|
||||
#include <config.hpp>
|
||||
#include <entity.hpp>
|
||||
#include <framebuffer.hpp>
|
||||
#include <light.hpp>
|
||||
#include <loader.hpp>
|
||||
#include <material.hpp>
|
||||
#include <mesh.hpp>
|
||||
#include <renderer.hpp>
|
||||
#include <scene.hpp>
|
||||
#include <shader.hpp>
|
||||
#include <texture.hpp>
|
||||
#include <transform.hpp>
|
||||
#include <utils.hpp>
|
||||
#include <vertex.hpp>
|
||||
#include <window.hpp>
|
||||
|
||||
#endif // FAN_HPP_
|
||||
115
src/framebuffer.cpp
Normal file
115
src/framebuffer.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
|
||||
#include "framebuffer.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Rc<FrameBuffer> FrameBuffer::create(GLsizei width, GLsizei height)
|
||||
{
|
||||
GLuint id, texture, render_buffer;
|
||||
|
||||
glGenFramebuffers(1, &id);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, id);
|
||||
|
||||
// Create the color texture attachment
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
|
||||
GL_UNSIGNED_BYTE, nullptr);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
texture, 0);
|
||||
|
||||
// Create a renderbuffer object for depth and stencil attachment
|
||||
glGenRenderbuffers(1, &render_buffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, render_buffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||
GL_RENDERBUFFER, render_buffer);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
glDeleteRenderbuffers(1, &render_buffer);
|
||||
glDeleteTextures(1, &texture);
|
||||
glDeleteFramebuffers(1, &id);
|
||||
std::cerr << "ERROR: FRAMEBUFFER: Could not create incomplete framebuffer" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return make_rc<FrameBuffer>(id, width, height, texture, render_buffer);
|
||||
}
|
||||
|
||||
FrameBuffer::~FrameBuffer()
|
||||
{
|
||||
glDeleteRenderbuffers(1, &m_render_buffer);
|
||||
glDeleteTextures(1, &m_texture);
|
||||
glDeleteFramebuffers(1, &m_id);
|
||||
}
|
||||
|
||||
FrameBuffer::FrameBuffer(FrameBuffer&& other) noexcept
|
||||
: m_id(std::exchange(other.m_id, 0))
|
||||
, m_width(std::exchange(other.m_width, 0))
|
||||
, m_height(std::exchange(other.m_height, 0))
|
||||
, m_texture(std::exchange(other.m_texture, 0))
|
||||
, m_render_buffer(std::exchange(other.m_render_buffer, 0))
|
||||
{
|
||||
}
|
||||
|
||||
FrameBuffer& FrameBuffer::operator=(FrameBuffer&& other) noexcept
|
||||
{
|
||||
if (this == &other) { return *this; }
|
||||
|
||||
std::swap(m_id, other.m_id);
|
||||
std::swap(m_width, other.m_width);
|
||||
std::swap(m_height, other.m_height);
|
||||
std::swap(m_texture, other.m_texture);
|
||||
std::swap(m_render_buffer, other.m_render_buffer);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLuint FrameBuffer::get_texture_id() const
|
||||
{
|
||||
return m_texture;
|
||||
}
|
||||
|
||||
GLsizei FrameBuffer::get_width() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
GLsizei FrameBuffer::get_height() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
void FrameBuffer::bind() const
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, m_id);
|
||||
}
|
||||
|
||||
void FrameBuffer::unbind() const
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void FrameBuffer::resize(GLsizei width, GLsizei height)
|
||||
{
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
// Resize the color attachment
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, m_width, m_height, 0,
|
||||
GL_RGB, GL_UNSIGNED_BYTE, nullptr);
|
||||
|
||||
// Resize the depth/stencil attachment
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, m_render_buffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8,
|
||||
m_width, m_height);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
48
src/framebuffer.hpp
Normal file
48
src/framebuffer.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef FRAMEBUFFER_HPP_
|
||||
#define FRAMEBUFFER_HPP_
|
||||
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class FrameBuffer
|
||||
{
|
||||
public:
|
||||
static Rc<FrameBuffer> create(GLsizei width, GLsizei height);
|
||||
FrameBuffer(GLuint id, GLsizei width, GLsizei height,
|
||||
GLuint texture, GLuint render_buffer)
|
||||
: m_id{id}
|
||||
, m_width{width}
|
||||
, m_height{height}
|
||||
, m_texture{texture}
|
||||
, m_render_buffer{render_buffer} {}
|
||||
|
||||
~FrameBuffer();
|
||||
FrameBuffer(const FrameBuffer&) = delete;
|
||||
FrameBuffer& operator=(const FrameBuffer&) = delete;
|
||||
FrameBuffer(FrameBuffer&& other) noexcept;
|
||||
FrameBuffer& operator=(FrameBuffer&& other) noexcept;
|
||||
|
||||
GLuint get_texture_id() const;
|
||||
GLsizei get_width() const;
|
||||
GLsizei get_height() const;
|
||||
|
||||
void bind() const;
|
||||
void unbind() const;
|
||||
void resize(GLsizei width, GLsizei height);
|
||||
|
||||
private:
|
||||
GLuint m_id{};
|
||||
GLsizei m_width{};
|
||||
GLsizei m_height{};
|
||||
|
||||
GLuint m_texture{};
|
||||
GLuint m_render_buffer{};
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FRAMEBUFFER_HPP_
|
||||
58
src/light.hpp
Normal file
58
src/light.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#ifndef FAN_LIGHT_HPP_
|
||||
#define FAN_LIGHT_HPP_
|
||||
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
struct DirectionalLight
|
||||
{
|
||||
glm::vec3 direction{-0.2f, -1.0f, -0.3f};
|
||||
|
||||
glm::vec3 ambient{0.05f, 0.05f, 0.05f};
|
||||
glm::vec3 diffuse{0.4f, 0.4f, 0.4f};
|
||||
glm::vec3 specular{0.5f, 0.5f, 0.5f};
|
||||
};
|
||||
|
||||
struct PointLight
|
||||
{
|
||||
glm::vec3 position{0.0f, 0.0f, 0.0f};
|
||||
|
||||
float constant{1.0f};
|
||||
float linear{0.09f};
|
||||
float quadratic{0.032f};
|
||||
|
||||
glm::vec3 ambient{0.05f, 0.05f, 0.05f};
|
||||
glm::vec3 diffuse{0.8f, 0.8f, 0.8f};
|
||||
glm::vec3 specular{1.0f, 1.0f, 1.0f};
|
||||
};
|
||||
|
||||
struct SpotLight
|
||||
{
|
||||
glm::vec3 position{0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 direction{0.0f, 0.0f, -1.0f};
|
||||
|
||||
float cut_off{glm::cos(glm::radians(12.5f))};
|
||||
float outer_cut_off{glm::cos(glm::radians(15.0f))};
|
||||
|
||||
float constant{1.0f};
|
||||
float linear{0.09f};
|
||||
float quadratic{0.032f};
|
||||
|
||||
glm::vec3 ambient{0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 diffuse{1.0f, 1.0f, 1.0f};
|
||||
glm::vec3 specular{1.0f, 1.0f, 1.0f};
|
||||
};
|
||||
|
||||
struct LightEnvironment
|
||||
{
|
||||
DirectionalLight directional_light;
|
||||
std::vector<PointLight> point_lights;
|
||||
std::vector<SpotLight> spot_lights;
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_LIGHT_HPP_
|
||||
120
src/loader.cpp
Normal file
120
src/loader.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <tiny_obj_loader.h>
|
||||
|
||||
#include "loader.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
struct VertexIndexHash {
|
||||
size_t operator()(const tinyobj::index_t& index) const {
|
||||
return ((std::hash<int>()(index.vertex_index) ^
|
||||
(std::hash<int>()(index.normal_index) << 1)) >> 1) ^
|
||||
(std::hash<int>()(index.texcoord_index) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct VertexIndexEqual {
|
||||
bool operator()(const tinyobj::index_t& a, const tinyobj::index_t& b) const {
|
||||
return a.vertex_index == b.vertex_index &&
|
||||
a.normal_index == b.normal_index &&
|
||||
a.texcoord_index == b.texcoord_index;
|
||||
}
|
||||
};
|
||||
|
||||
Opt<Entity> load_obj(const std::string& file_path, Rc<Shader> shader)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
tinyobj::attrib_t attrib;
|
||||
std::vector<tinyobj::shape_t> shapes;
|
||||
std::vector<tinyobj::material_t> materials;
|
||||
std::string err;
|
||||
|
||||
fs::path parent_path = fs::path{file_path}.parent_path();
|
||||
parent_path += fs::path::preferred_separator;
|
||||
std::string base_dir = parent_path.string();
|
||||
|
||||
bool ok = tinyobj::LoadObj(&attrib, &shapes, &materials, &err,
|
||||
file_path.c_str(), base_dir.c_str());
|
||||
if (!ok || !err.empty()) {
|
||||
std::cerr << "ERROR: LOAD_OBJ: Could not load " << file_path << std::endl << err;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Build the unified mesh
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<GLuint> indices;
|
||||
std::unordered_map<tinyobj::index_t, GLuint,
|
||||
VertexIndexHash, VertexIndexEqual> unique_vertices;
|
||||
|
||||
for (const auto& shape : shapes) {
|
||||
for (const auto& index : shape.mesh.indices) {
|
||||
if (unique_vertices.count(index) == 0) {
|
||||
Vertex vertex{};
|
||||
|
||||
vertex.position = {
|
||||
attrib.vertices[3 * index.vertex_index + 0],
|
||||
attrib.vertices[3 * index.vertex_index + 1],
|
||||
attrib.vertices[3 * index.vertex_index + 2]
|
||||
};
|
||||
|
||||
if (index.normal_index >= 0) {
|
||||
vertex.normal = {
|
||||
attrib.normals[3 * index.normal_index + 0],
|
||||
attrib.normals[3 * index.normal_index + 1],
|
||||
attrib.normals[3 * index.normal_index + 2]
|
||||
};
|
||||
}
|
||||
|
||||
if (index.texcoord_index >= 0) {
|
||||
vertex.texture_coords = {
|
||||
attrib.texcoords[2 * index.texcoord_index + 0],
|
||||
1.0f - attrib.texcoords[2 * index.texcoord_index + 1]
|
||||
};
|
||||
}
|
||||
|
||||
unique_vertices[index] = static_cast<GLuint>(vertices.size());
|
||||
vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
indices.push_back(unique_vertices[index]);
|
||||
}
|
||||
}
|
||||
|
||||
auto mesh = Mesh::create(vertices, indices);
|
||||
if (!mesh) {
|
||||
std::cerr << "ERROR: LOAD_OBJ: Could not create mesh" << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto material = make_rc<Material>();
|
||||
material->shader = shader; // Assigned required shader
|
||||
|
||||
// If the .obj file had an .mtl file attached to it
|
||||
if (!materials.empty()) {
|
||||
// Take only the first material found
|
||||
const tinyobj::material_t& mtl = materials[0];
|
||||
|
||||
if (!mtl.diffuse_texname.empty()) {
|
||||
material->diffuse_map = Texture::create(base_dir + mtl.diffuse_texname);
|
||||
if (!material->diffuse_map) {
|
||||
std::cerr << "WARNING: LOAD_OBJ: Could not create diffuse texture" << std::endl;
|
||||
}
|
||||
}
|
||||
if (!mtl.specular_texname.empty()) {
|
||||
material->specular_map = Texture::create(base_dir + mtl.specular_texname);
|
||||
if (!material->specular_map) {
|
||||
std::cerr << "WARNING: LOAD_OBJ: Could not create specular texture" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
material->shininess = mtl.shininess;
|
||||
}
|
||||
|
||||
return Entity{mesh, material};
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
16
src/loader.hpp
Normal file
16
src/loader.hpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef FAN_LOADER_HPP_
|
||||
#define FAN_LOADER_HPP_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "entity.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Opt<Entity> load_obj(const std::string& file_path, Rc<Shader> shader);
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_LOADER_HPP_
|
||||
364
src/main.cpp
Normal file
364
src/main.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#define IMGUI_DEFINE_MATH_OPERATORS
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <fan.hpp>
|
||||
|
||||
using namespace Fan;
|
||||
|
||||
struct UserData
|
||||
{
|
||||
Camera& camera;
|
||||
float delta_time{};
|
||||
bool mouse_grab{};
|
||||
|
||||
float speed{CAMERA_SPEED};
|
||||
float sensitivity{CAMERA_SENSITIVITY};
|
||||
float last_mouse_x{};
|
||||
float last_mouse_y{};
|
||||
bool first_mouse{true};
|
||||
|
||||
bool show_debug{};
|
||||
|
||||
std::vector<Rc<Scene>> scenes{};
|
||||
size_t selected_scene_index{};
|
||||
};
|
||||
|
||||
void process_input(Window& window, Camera& camera, UserData& user_data);
|
||||
void mouse_callback(double pos_x, double pos_y, void* user_data);
|
||||
void setup_dockspace();
|
||||
void draw_panel_window(Camera& camera, UserData& user_data);
|
||||
void draw_viewport_window(FrameBuffer& framebuffer, Window& window, UserData& user_data);
|
||||
std::vector<Vertex> get_cube_vertices();
|
||||
|
||||
template <typename... Args>
|
||||
void overlay_text(float offset_x, float offset_y, ImVec4 color, const char* format, Args... args)
|
||||
{
|
||||
char buffer[256];
|
||||
std::snprintf(buffer, sizeof(buffer), format, args...);
|
||||
|
||||
ImVec2 pos = ImGui::GetWindowPos() + ImVec2{offset_x, offset_y};
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
ImVec2 text_size = ImGui::CalcTextSize(buffer);
|
||||
|
||||
draw_list->AddRectFilled(pos - ImVec2{4, 2}, pos + text_size + ImVec2{4, 2}, IM_COL32(0, 0, 0, 160));
|
||||
draw_list->AddText(pos, ImGui::ColorConvertFloat4ToU32(color), buffer);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
/******************** Initialization ********************/
|
||||
|
||||
auto window = Window::create(WINDOW_WIDTH, WINDOW_HEIGHT, VERSION_STRING);
|
||||
if (!window) return EXIT_FAILURE;
|
||||
window->set_vsync(true);
|
||||
|
||||
Renderer::init();
|
||||
|
||||
Camera camera{{5.0f, 5.0f, 5.0f}};
|
||||
camera.look_at({0.0f, 0.0f, 0.0f});
|
||||
|
||||
UserData user_data{camera};
|
||||
window->set_callback_user_data(&user_data);
|
||||
window->set_mouse_callback(mouse_callback);
|
||||
|
||||
/******************** Resource loading ********************/
|
||||
|
||||
auto shader = Shader::create("./src/shaders/default.vert", "./src/shaders/default.frag");
|
||||
auto debug_shader = Shader::create("./src/shaders/debug.vert", "./src/shaders/debug.frag");
|
||||
|
||||
auto backpack = load_obj("./assets/backpack/backpack.obj", shader);
|
||||
|
||||
auto cube_mesh = Mesh::create(get_cube_vertices());
|
||||
auto cube_texture = Texture::create("./assets/container.jpg");
|
||||
|
||||
auto default_mesh = Mesh::create(get_cube_vertices());
|
||||
auto default_texture = Texture::create("./assets/debugempty.png");
|
||||
|
||||
auto framebuffer = FrameBuffer::create(window->get_width(), window->get_height());
|
||||
|
||||
if (!shader || !debug_shader
|
||||
|| !backpack
|
||||
|| !cube_mesh || !cube_texture
|
||||
|| !default_mesh || !default_texture
|
||||
|| !framebuffer) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/******************** Scene construction ********************/
|
||||
|
||||
// Backpack scene
|
||||
Rc<Scene> backpack_scene = make_rc<Scene>();
|
||||
user_data.scenes.push_back(backpack_scene);
|
||||
|
||||
backpack_scene->add_entity(*backpack);
|
||||
|
||||
auto default_material = make_rc<Material>();
|
||||
default_material->shader = shader;
|
||||
default_material->diffuse_map = default_texture;
|
||||
Entity floor{cube_mesh, default_material};
|
||||
floor.transform.position = {0.0f, -3.0f, 0.0f};
|
||||
floor.transform.scale = {20.0f, 1.0f, 20.0f};
|
||||
backpack_scene->add_entity(floor);
|
||||
|
||||
PointLight light_white;
|
||||
light_white.position = glm::vec3(0.0f, 3.0f, 3.0f);
|
||||
light_white.diffuse = glm::vec3(2.0f, 2.0f, 2.0f); // Bright white
|
||||
light_white.specular = glm::vec3(2.0f, 2.0f, 2.0f); // White highlights
|
||||
|
||||
backpack_scene->light_environment.point_lights.push_back(light_white);
|
||||
|
||||
// Cubes scene
|
||||
Rc<Scene> cubes_scene = make_rc<Scene>();
|
||||
user_data.scenes.push_back(cubes_scene);
|
||||
|
||||
auto cube_material = make_rc<Material>();
|
||||
cube_material->shader = shader;
|
||||
cube_material->diffuse_map = cube_texture;
|
||||
|
||||
std::vector<glm::vec3> positions{
|
||||
{ 0.0f, 0.0f, 0.0f}, { 2.0f, 5.0f, -15.0f}, {-1.5f, -2.2f, -2.5f},
|
||||
{-3.8f, -2.0f, -12.3f}, { 2.4f, -0.4f, -3.5f}, {-1.7f, 3.0f, -7.5f},
|
||||
{ 1.3f, -2.0f, -2.5f}, { 1.5f, 2.0f, -2.5f}, { 1.5f, 0.2f, -1.5f},
|
||||
{-1.3f, 1.0f, -1.5f}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < positions.size(); ++i) {
|
||||
Entity entity{cube_mesh, cube_material};
|
||||
entity.transform.position = positions[i];
|
||||
entity.transform.set_rotation(glm::radians(20.0f * i), {1.0f, 0.3f, 0.5f});
|
||||
cubes_scene->add_entity(entity);
|
||||
}
|
||||
|
||||
PointLight light_warm;
|
||||
light_warm.position = glm::vec3(0.0f, 2.0f, 2.0f);
|
||||
light_warm.diffuse = glm::vec3(1.0f, 0.6f, 0.2f); // Orange
|
||||
light_warm.specular = glm::vec3(1.0f, 0.8f, 0.5f);
|
||||
|
||||
PointLight light_cool;
|
||||
light_cool.position = glm::vec3(-3.0f, 1.0f, -5.0f);
|
||||
light_cool.diffuse = glm::vec3(0.2f, 0.5f, 1.0f); // Blue
|
||||
light_cool.specular = glm::vec3(0.5f, 0.7f, 1.0f);
|
||||
|
||||
cubes_scene->light_environment.point_lights.push_back(light_warm);
|
||||
cubes_scene->light_environment.point_lights.push_back(light_cool);
|
||||
|
||||
/******************** Main loop ********************/
|
||||
|
||||
while (!window->should_close()) {
|
||||
window->begin();
|
||||
window->update();
|
||||
|
||||
user_data.delta_time = window->get_delta_time();
|
||||
process_input(*window, camera, user_data);
|
||||
|
||||
Renderer::set_clear_color({0.0f, 0.0f, 0.0f, 1.0f});
|
||||
Renderer::clear();
|
||||
|
||||
// UI Layer
|
||||
setup_dockspace();
|
||||
draw_panel_window(camera, user_data);
|
||||
draw_viewport_window(*framebuffer, *window, user_data);
|
||||
|
||||
// Scene Rendering Layer
|
||||
const Scene& scene = *user_data.scenes[user_data.selected_scene_index];
|
||||
|
||||
int width = framebuffer->get_width();
|
||||
int height = framebuffer->get_height();
|
||||
framebuffer->bind();
|
||||
Renderer::set_viewport(0, 0, width, height);
|
||||
Renderer::clear();
|
||||
Renderer::draw_scene(scene, camera, width, height);
|
||||
if (user_data.show_debug) {
|
||||
Renderer::draw_debug_wireframes(scene, camera, width, height,
|
||||
debug_shader, default_mesh);
|
||||
}
|
||||
framebuffer->unbind();
|
||||
|
||||
window->end();
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void process_input(Window& window, Camera& camera, UserData& user_data)
|
||||
{
|
||||
if (window.is_key_pressed(GLFW_KEY_ESCAPE)) window.set_should_close(true);
|
||||
|
||||
static bool wireframe = false;
|
||||
if (window.is_key_pressed(GLFW_KEY_H)) {
|
||||
wireframe = !wireframe;
|
||||
Renderer::set_wireframe(wireframe);
|
||||
}
|
||||
|
||||
static bool fullscreen = false;
|
||||
if (window.is_key_pressed(GLFW_KEY_F11)) {
|
||||
fullscreen = !fullscreen;
|
||||
window.set_fullscreen(fullscreen);
|
||||
}
|
||||
|
||||
if (window.is_key_pressed(GLFW_KEY_INSERT)) {
|
||||
user_data.mouse_grab = !user_data.mouse_grab;
|
||||
user_data.first_mouse = true;
|
||||
window.set_mouse_grab(user_data.mouse_grab);
|
||||
}
|
||||
|
||||
if (!user_data.mouse_grab) return;
|
||||
|
||||
float velocity = user_data.speed * user_data.delta_time * (window.is_key_down(GLFW_KEY_LEFT_SHIFT) ? 2.0f : 1.0f);
|
||||
if (window.is_key_down(GLFW_KEY_W)) camera.position += camera.get_front() * velocity;
|
||||
if (window.is_key_down(GLFW_KEY_S)) camera.position -= camera.get_front() * velocity;
|
||||
if (window.is_key_down(GLFW_KEY_A)) camera.position -= camera.get_right() * velocity;
|
||||
if (window.is_key_down(GLFW_KEY_D)) camera.position += camera.get_right() * velocity;
|
||||
}
|
||||
|
||||
void mouse_callback(double pos_x, double pos_y, void* user_data)
|
||||
{
|
||||
auto* data = static_cast<UserData*>(user_data);
|
||||
if (!data->mouse_grab) return;
|
||||
|
||||
if (data->first_mouse) {
|
||||
data->last_mouse_x = pos_x;
|
||||
data->last_mouse_y = pos_y;
|
||||
data->first_mouse = false;
|
||||
}
|
||||
|
||||
float delta_x = (pos_x - data->last_mouse_x) * data->sensitivity;
|
||||
float delta_y = (data->last_mouse_y - pos_y) * data->sensitivity;
|
||||
|
||||
data->last_mouse_x = pos_x;
|
||||
data->last_mouse_y = pos_y;
|
||||
data->camera.rotate(delta_x, delta_y);
|
||||
}
|
||||
|
||||
void setup_dockspace()
|
||||
{
|
||||
ImGuiID dockspace_id = ImGui::GetID("Dockspace");
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
|
||||
static bool first_time = true;
|
||||
if (first_time) {
|
||||
first_time = false;
|
||||
ImGui::DockBuilderRemoveNode(dockspace_id);
|
||||
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->WorkSize);
|
||||
|
||||
ImGuiID main_id = dockspace_id;
|
||||
ImGuiID panel_id = ImGui::DockBuilderSplitNode(main_id, ImGuiDir_Left, 0.25f, nullptr, &main_id);
|
||||
|
||||
ImGui::DockBuilderDockWindow("Panel", panel_id);
|
||||
ImGui::DockBuilderDockWindow("Viewport", main_id);
|
||||
ImGui::DockBuilderFinish(dockspace_id);
|
||||
}
|
||||
|
||||
ImGui::DockSpaceOverViewport(dockspace_id, viewport, ImGuiDockNodeFlags_None);
|
||||
}
|
||||
|
||||
void draw_panel_window(Camera& camera, UserData& user_data)
|
||||
{
|
||||
static auto logo = Texture::create("assets/logo/logo.png");
|
||||
|
||||
ImGui::Begin("Panel");
|
||||
|
||||
if (logo) ImGui::Image(logo->get_id(), {50.0f, 50.0f});
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + (50.0f - ImGui::GetTextLineHeight()) * 0.5f);
|
||||
ImGui::Text("Fan Engine");
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("Press INSERT to toggle camera controls");
|
||||
ImGui::SliderFloat("Camera X", &camera.position.x, -10.0f, 10.0f);
|
||||
ImGui::SliderFloat("Camera Y", &camera.position.y, -10.0f, 10.0f);
|
||||
ImGui::SliderFloat("Camera Z", &camera.position.z, -10.0f, 10.0f);
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Checkbox("Show Debug Wireframes", &user_data.show_debug);
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::Text("Scene Selector");
|
||||
for (size_t i = 0; i < user_data.scenes.size(); ++i) {
|
||||
char label[64];
|
||||
ImFormatString(label, IM_ARRAYSIZE(label), "Scene %zu", i);
|
||||
if (ImGui::RadioButton(label, user_data.selected_scene_index == i)) {
|
||||
user_data.selected_scene_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void draw_viewport_window(FrameBuffer& framebuffer, Window& window, UserData& user_data)
|
||||
{
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f, 0.0f});
|
||||
ImGui::Begin("Viewport");
|
||||
|
||||
ImVec2 size = ImGui::GetContentRegionAvail();
|
||||
if (size.x > 0 && size.y > 0) {
|
||||
if (size.x != framebuffer.get_width() || size.y != framebuffer.get_height()) {
|
||||
framebuffer.resize(size.x, size.y);
|
||||
}
|
||||
ImGui::Image(framebuffer.get_texture_id(), size, ImVec2{0.0f, 1.0f}, ImVec2{1.0f, 0.0f});
|
||||
}
|
||||
|
||||
// Overlays
|
||||
float fps = window.get_delta_time() > 0.0f ? 1.0f / window.get_delta_time() : 0.0f;
|
||||
overlay_text(20.0f, 35.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", VERSION_STRING);
|
||||
overlay_text(20.0f, 52.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "FPS: %.1f", fps);
|
||||
overlay_text(20.0f, 69.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "Frame time: %.2f ms", window.get_delta_time());
|
||||
if (user_data.mouse_grab) { overlay_text(20.0f, 86.0f, {0.0f, 1.0f, 0.0f, 1.0f}, "%s", "INPUT LOCKED"); }
|
||||
|
||||
ImGui::End();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
std::vector<Vertex> get_cube_vertices()
|
||||
{
|
||||
return {
|
||||
// Back Face
|
||||
{{ 0.5f, -0.5f, -0.5f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 0.0f}},
|
||||
{{-0.5f, -0.5f, -0.5f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f}},
|
||||
{{-0.5f, 0.5f, -0.5f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, -0.5f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 1.0f}},
|
||||
{{ 0.5f, 0.5f, -0.5f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 1.0f}},
|
||||
{{ 0.5f, -0.5f, -0.5f}, { 0.0f, 0.0f, -1.0f}, { 0.0f, 0.0f}},
|
||||
// Front Face
|
||||
{{-0.5f, -0.5f, 0.5f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f}},
|
||||
{{ 0.5f, -0.5f, 0.5f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f}},
|
||||
{{ 0.5f, 0.5f, 0.5f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 1.0f}},
|
||||
{{ 0.5f, 0.5f, 0.5f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, 0.5f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 1.0f}},
|
||||
{{-0.5f, -0.5f, 0.5f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f}},
|
||||
// Left Face
|
||||
{{-0.5f, -0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f}},
|
||||
{{-0.5f, -0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, { 1.0f, 0.0f}},
|
||||
{{-0.5f, 0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, 0.5f}, {-1.0f, 0.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f}},
|
||||
{{-0.5f, -0.5f, -0.5f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f}},
|
||||
// Right Face
|
||||
{{ 0.5f, -0.5f, 0.5f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f}},
|
||||
{{ 0.5f, -0.5f, -0.5f}, { 1.0f, 0.0f, 0.0f}, { 1.0f, 0.0f}},
|
||||
{{ 0.5f, 0.5f, -0.5f}, { 1.0f, 0.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{ 0.5f, 0.5f, -0.5f}, { 1.0f, 0.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{ 0.5f, 0.5f, 0.5f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 1.0f}},
|
||||
{{ 0.5f, -0.5f, 0.5f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f}},
|
||||
// Bottom Face
|
||||
{{-0.5f, -0.5f, -0.5f}, { 0.0f, -1.0f, 0.0f}, { 0.0f, 1.0f}},
|
||||
{{ 0.5f, -0.5f, -0.5f}, { 0.0f, -1.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{ 0.5f, -0.5f, 0.5f}, { 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f}},
|
||||
{{ 0.5f, -0.5f, 0.5f}, { 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f}},
|
||||
{{-0.5f, -0.5f, 0.5f}, { 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f}},
|
||||
{{-0.5f, -0.5f, -0.5f}, { 0.0f, -1.0f, 0.0f}, { 0.0f, 1.0f}},
|
||||
// Top Face
|
||||
{{-0.5f, 0.5f, 0.5f}, { 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f}},
|
||||
{{ 0.5f, 0.5f, 0.5f}, { 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f}},
|
||||
{{ 0.5f, 0.5f, -0.5f}, { 0.0f, 1.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{ 0.5f, 0.5f, -0.5f}, { 0.0f, 1.0f, 0.0f}, { 1.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, -0.5f}, { 0.0f, 1.0f, 0.0f}, { 0.0f, 1.0f}},
|
||||
{{-0.5f, 0.5f, 0.5f}, { 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f}}
|
||||
};
|
||||
}
|
||||
30
src/material.cpp
Normal file
30
src/material.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "material.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
void Material::bind() const
|
||||
{
|
||||
shader->use();
|
||||
|
||||
// Diffuse (slot 0)
|
||||
if (diffuse_map) {
|
||||
diffuse_map->bind(0);
|
||||
} else {
|
||||
Texture::unbind(0);
|
||||
}
|
||||
shader->set_int("u_material.diffuse", 0);
|
||||
|
||||
// Specular (slot 1)
|
||||
if (specular_map) {
|
||||
specular_map->bind(1);
|
||||
} else {
|
||||
Texture::unbind(1);
|
||||
}
|
||||
shader->set_int("u_material.specular", 1);
|
||||
|
||||
// Shininess
|
||||
shader->set_float("u_material.shininess", shininess);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
22
src/material.hpp
Normal file
22
src/material.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef FAN_MATERIAL_HPP_
|
||||
#define FAN_MATERIAL_HPP_
|
||||
|
||||
#include "shader.hpp"
|
||||
#include "texture.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
struct Material
|
||||
{
|
||||
Rc<Shader> shader;
|
||||
Rc<Texture> diffuse_map;
|
||||
Rc<Texture> specular_map;
|
||||
float shininess{32.0f};
|
||||
|
||||
void bind() const;
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_MATERIAL_HPP_
|
||||
86
src/mesh.cpp
Normal file
86
src/mesh.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include "mesh.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Rc<Mesh> Mesh::create(const std::vector<Vertex>& vertices,
|
||||
const std::vector<GLuint>& indices/* = {}*/)
|
||||
{
|
||||
GLuint VBO{}, VAO{}, EBO{};
|
||||
bool has_indices = !indices.empty();
|
||||
|
||||
glGenBuffers(1, &VBO);
|
||||
glGenVertexArrays(1, &VAO);
|
||||
glBindVertexArray(VAO);
|
||||
|
||||
// Upload vertex data
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vertices[0]),
|
||||
&vertices[0], GL_STATIC_DRAW);
|
||||
|
||||
// Upload index data
|
||||
if (has_indices) {
|
||||
glGenBuffers(1, &EBO);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(indices[0]),
|
||||
&indices[0], GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
// Define vertex data layout
|
||||
GLsizei stride = sizeof(Vertex);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(Vertex, position));
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(Vertex, normal));
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)offsetof(Vertex, texture_coords));
|
||||
glEnableVertexAttribArray(0);
|
||||
glEnableVertexAttribArray(1);
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
return make_rc<Mesh>(vertices, indices, has_indices, VBO, VAO, EBO);
|
||||
}
|
||||
|
||||
Mesh::~Mesh()
|
||||
{
|
||||
glDeleteBuffers(1, &m_EBO);
|
||||
glDeleteBuffers(1, &m_VBO);
|
||||
glDeleteVertexArrays(1, &m_VAO);
|
||||
}
|
||||
|
||||
Mesh::Mesh(Mesh&& other) noexcept
|
||||
: m_vertices(std::move(other.m_vertices))
|
||||
, m_indices(std::move(other.m_indices))
|
||||
, m_has_indices(std::exchange(other.m_has_indices, false))
|
||||
, m_VBO(std::exchange(other.m_VBO, 0))
|
||||
, m_VAO(std::exchange(other.m_VAO, 0))
|
||||
, m_EBO(std::exchange(other.m_EBO, 0))
|
||||
{
|
||||
}
|
||||
|
||||
Mesh& Mesh::operator=(Mesh&& other) noexcept
|
||||
{
|
||||
if (this == &other) { return *this; }
|
||||
|
||||
std::swap(m_vertices, other.m_vertices);
|
||||
std::swap(m_indices, other.m_indices);
|
||||
std::swap(m_has_indices, other.m_has_indices);
|
||||
std::swap(m_VBO, other.m_VBO);
|
||||
std::swap(m_VAO, other.m_VAO);
|
||||
std::swap(m_EBO, other.m_EBO);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Mesh::draw() const
|
||||
{
|
||||
glBindVertexArray(m_VAO);
|
||||
|
||||
if (m_has_indices) {
|
||||
glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_INT, 0);
|
||||
} else {
|
||||
glDrawArrays(GL_TRIANGLES, 0, m_vertices.size());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
49
src/mesh.hpp
Normal file
49
src/mesh.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef FAN_MESH_HPP_
|
||||
#define FAN_MESH_HPP_
|
||||
|
||||
#include <vector>
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include "vertex.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Mesh
|
||||
{
|
||||
public:
|
||||
static Rc<Mesh> create(const std::vector<Vertex>& vertices,
|
||||
const std::vector<GLuint>& indices = {});
|
||||
Mesh(const std::vector<Vertex>& vertices,
|
||||
const std::vector<GLuint>& indices,
|
||||
bool has_indices,
|
||||
GLuint VBO, GLuint VAO, GLuint EBO)
|
||||
: m_vertices(vertices)
|
||||
, m_indices(indices)
|
||||
, m_has_indices(has_indices)
|
||||
, m_VBO(VBO)
|
||||
, m_VAO(VAO)
|
||||
, m_EBO(EBO) {}
|
||||
|
||||
~Mesh();
|
||||
Mesh(const Mesh&) = delete;
|
||||
Mesh& operator=(const Mesh&) = delete;
|
||||
Mesh(Mesh&& other) noexcept;
|
||||
Mesh& operator=(Mesh&& other) noexcept;
|
||||
|
||||
void draw() const;
|
||||
|
||||
private:
|
||||
std::vector<Vertex> m_vertices{};
|
||||
std::vector<GLuint> m_indices{};
|
||||
bool m_has_indices{};
|
||||
|
||||
GLuint m_VBO{};
|
||||
GLuint m_VAO{};
|
||||
GLuint m_EBO{};
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_MESH_HPP_
|
||||
166
src/renderer.cpp
Normal file
166
src/renderer.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include "renderer.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
void Renderer::init()
|
||||
{
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CCW);
|
||||
}
|
||||
|
||||
void Renderer::set_viewport(int x, int y, int width, int height)
|
||||
{
|
||||
glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
void Renderer::set_clear_color(const glm::vec4& color)
|
||||
{
|
||||
glClearColor(color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
void Renderer::set_wireframe(bool enabled)
|
||||
{
|
||||
glPolygonMode(GL_FRONT_AND_BACK, enabled ? GL_LINE : GL_FILL);
|
||||
}
|
||||
|
||||
void Renderer::clear()
|
||||
{
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void Renderer::draw_scene(const Scene& scene, const Camera& camera,
|
||||
int screen_width, int screen_height)
|
||||
{
|
||||
glm::mat4 view_projection = camera.get_view_projection_matrix(screen_width, screen_height);
|
||||
GLuint last_shader_id = 0;
|
||||
|
||||
// TODO: Order entities by material to minimize uniform uploads
|
||||
for (const Entity& entity : scene.get_entities()) {
|
||||
if (!entity.mesh || !entity.material || !entity.material->shader) { continue; }
|
||||
|
||||
Rc<Shader> shader = entity.material->shader;
|
||||
GLuint current_shader_id = shader->get_id();
|
||||
|
||||
// Only upload scene uniforms if the shader changed
|
||||
if (current_shader_id != last_shader_id) {
|
||||
shader->use();
|
||||
|
||||
// Upload camera uniforms
|
||||
shader->set_mat4("u_view_projection", view_projection);
|
||||
shader->set_vec3("u_view_pos", camera.position);
|
||||
|
||||
// Upload lighting uniforms
|
||||
const LightEnvironment& env = scene.light_environment;
|
||||
|
||||
shader->set_vec3("u_directional_light.direction", env.directional_light.direction);
|
||||
shader->set_vec3("u_directional_light.ambient", env.directional_light.ambient);
|
||||
shader->set_vec3("u_directional_light.diffuse", env.directional_light.diffuse);
|
||||
shader->set_vec3("u_directional_light.specular", env.directional_light.specular);
|
||||
|
||||
// Note: current limit is set to MAX_POINT_LIGHT_COUNT (8)
|
||||
// TODO: Define this limits in a common header file
|
||||
int point_count = std::min(static_cast<int>(env.point_lights.size()), 8);
|
||||
shader->set_int("u_point_light_count", point_count);
|
||||
for (int i = 0; i < point_count; ++i) {
|
||||
std::string prefix = "u_point_lights[" + std::to_string(i) + "].";
|
||||
shader->set_vec3(prefix + "position", env.point_lights[i].position);
|
||||
|
||||
shader->set_float(prefix + "constant", env.point_lights[i].constant);
|
||||
shader->set_float(prefix + "linear", env.point_lights[i].linear);
|
||||
shader->set_float(prefix + "quadratic", env.point_lights[i].quadratic);
|
||||
|
||||
shader->set_vec3(prefix + "ambient", env.point_lights[i].ambient);
|
||||
shader->set_vec3(prefix + "diffuse", env.point_lights[i].diffuse);
|
||||
shader->set_vec3(prefix + "specular", env.point_lights[i].specular);
|
||||
}
|
||||
|
||||
// Note: current limit is set to MAX_SPOT_LIGHT_COUNT (8)
|
||||
// TODO: Define this limits in a common header file
|
||||
int spot_count = std::min(static_cast<int>(env.spot_lights.size()), 8);
|
||||
shader->set_int("u_spot_light_count", spot_count);
|
||||
for (int i = 0; i < spot_count; ++i) {
|
||||
std::string prefix = "u_spot_lights[" + std::to_string(i) + "].";
|
||||
shader->set_vec3(prefix + "position", env.spot_lights[i].position);
|
||||
shader->set_vec3(prefix + "direction", env.spot_lights[i].direction);
|
||||
|
||||
shader->set_float(prefix + "cut_off", env.spot_lights[i].cut_off);
|
||||
shader->set_float(prefix + "outer_cut_off", env.spot_lights[i].outer_cut_off);
|
||||
|
||||
shader->set_float(prefix + "constant", env.spot_lights[i].constant);
|
||||
shader->set_float(prefix + "linear", env.spot_lights[i].linear);
|
||||
shader->set_float(prefix + "quadratic", env.spot_lights[i].quadratic);
|
||||
|
||||
shader->set_vec3(prefix + "ambient", env.spot_lights[i].ambient);
|
||||
shader->set_vec3(prefix + "diffuse", env.spot_lights[i].diffuse);
|
||||
shader->set_vec3(prefix + "specular", env.spot_lights[i].specular);
|
||||
}
|
||||
|
||||
last_shader_id = current_shader_id;
|
||||
}
|
||||
|
||||
// Bind the specific textures and the shininess float
|
||||
entity.material->bind();
|
||||
|
||||
// Upload the model matrix to transform the specific object
|
||||
shader->set_mat4("u_model", entity.transform.get_model_matrix());
|
||||
|
||||
// Dispatch the draw call
|
||||
entity.mesh->draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::draw_debug_wireframes(const Scene& scene, const Camera& camera,
|
||||
int screen_width, int screen_height,
|
||||
const Rc<Shader>& debug_shader,
|
||||
const Rc<Mesh>& default_mesh)
|
||||
{
|
||||
debug_shader->use();
|
||||
debug_shader->set_mat4("u_view_projection", camera.get_view_projection_matrix(screen_width, screen_height));
|
||||
|
||||
// Force wireframe mode and disable depth testing so we can see through objects
|
||||
set_wireframe(true);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
// Draw all standard entities (green)
|
||||
debug_shader->set_vec3("u_color", glm::vec3(0.0f, 0.7f, 0.0f));
|
||||
for (const Entity& entity : scene.get_entities()) {
|
||||
// If an entity has no mesh, use the default debug mesh
|
||||
auto mesh = entity.mesh ? entity.mesh : default_mesh;
|
||||
|
||||
debug_shader->set_mat4("u_model", entity.transform.get_model_matrix());
|
||||
mesh->draw();
|
||||
}
|
||||
|
||||
// Draw point lights (yellow)
|
||||
debug_shader->set_vec3("u_color", glm::vec3(0.7f, 0.7f, 0.0f));
|
||||
for (const PointLight& light : scene.light_environment.point_lights) {
|
||||
glm::mat4 model = glm::translate(glm::mat4(1.0f), light.position);
|
||||
model = glm::scale(model, glm::vec3(0.2f));
|
||||
|
||||
debug_shader->set_mat4("u_model", model);
|
||||
default_mesh->draw();
|
||||
}
|
||||
|
||||
// Draw spot lights (cyan)
|
||||
debug_shader->set_vec3("u_color", glm::vec3(0.0f, 0.7f, 0.7f));
|
||||
for (const SpotLight& light : scene.light_environment.spot_lights) {
|
||||
glm::mat4 model = glm::translate(glm::mat4(1.0f), light.position);
|
||||
model = glm::scale(model, glm::vec3(0.2f));
|
||||
|
||||
debug_shader->set_mat4("u_model", model);
|
||||
default_mesh->draw();
|
||||
}
|
||||
|
||||
// Restore state
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
set_wireframe(false);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
30
src/renderer.hpp
Normal file
30
src/renderer.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef FAN_RENDERER_HPP_
|
||||
#define FAN_RENDERER_HPP_
|
||||
|
||||
#include "scene.hpp"
|
||||
#include "camera.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
static void init();
|
||||
|
||||
static void set_viewport(int x, int y, int width, int height);
|
||||
static void set_clear_color(const glm::vec4& color);
|
||||
static void set_wireframe(bool enabled);
|
||||
|
||||
static void clear();
|
||||
static void draw_scene(const Scene& scene, const Camera& camera,
|
||||
int screen_width, int screen_height);
|
||||
static void draw_debug_wireframes(const Scene& scene, const Camera& camera,
|
||||
int screen_width, int screen_height,
|
||||
const Rc<Shader>& debug_shader,
|
||||
const Rc<Mesh>& default_mesh);
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_RENDERER_HPP_
|
||||
16
src/scene.cpp
Normal file
16
src/scene.cpp
Normal file
@@ -0,0 +1,16 @@
|
||||
#include "scene.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
void Scene::add_entity(const Entity& entity)
|
||||
{
|
||||
m_entities.push_back(entity);
|
||||
}
|
||||
|
||||
const std::vector<Entity>& Scene::get_entities() const
|
||||
{
|
||||
return m_entities;
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
27
src/scene.hpp
Normal file
27
src/scene.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef FAN_SCENE_HPP_
|
||||
#define FAN_SCENE_HPP_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "entity.hpp"
|
||||
#include "light.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Scene
|
||||
{
|
||||
public:
|
||||
LightEnvironment light_environment{};
|
||||
|
||||
void add_entity(const Entity& entity);
|
||||
|
||||
const std::vector<Entity>& get_entities() const;
|
||||
|
||||
private:
|
||||
std::vector<Entity> m_entities{};
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_SCENE_HPP_
|
||||
153
src/shader.cpp
Normal file
153
src/shader.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "shader.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Rc<Shader> Shader::create(const std::string& vertex_shader_path,
|
||||
const std::string& fragment_shader_path)
|
||||
{
|
||||
auto vertex_shader = compile_shader(vertex_shader_path, GL_VERTEX_SHADER);
|
||||
if (!vertex_shader) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto fragment_shader = compile_shader(fragment_shader_path, GL_FRAGMENT_SHADER);
|
||||
if (!fragment_shader) {
|
||||
glDeleteShader(*vertex_shader);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GLuint shader_program = glCreateProgram();
|
||||
glAttachShader(shader_program, *vertex_shader);
|
||||
glAttachShader(shader_program, *fragment_shader);
|
||||
glLinkProgram(shader_program);
|
||||
|
||||
glDeleteShader(*vertex_shader);
|
||||
glDeleteShader(*fragment_shader);
|
||||
|
||||
GLint success;
|
||||
glGetProgramiv(shader_program, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
GLint log_length = 0;
|
||||
glGetProgramiv(shader_program, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
if (log_length > 0) {
|
||||
std::string log(log_length, ' ');
|
||||
glGetProgramInfoLog(shader_program, log_length, nullptr, &log[0]);
|
||||
std::cerr << "ERROR: SHADER: Could not link shader program: " << log << std::endl;
|
||||
}
|
||||
|
||||
glDeleteProgram(shader_program);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return make_rc<Shader>(shader_program);
|
||||
}
|
||||
|
||||
Shader::~Shader()
|
||||
{
|
||||
glDeleteProgram(m_id);
|
||||
}
|
||||
|
||||
Shader::Shader(Shader&& other) noexcept
|
||||
: m_id(std::exchange(other.m_id, 0))
|
||||
{
|
||||
}
|
||||
|
||||
Shader& Shader::operator=(Shader&& other) noexcept
|
||||
{
|
||||
if (this == &other) { return *this; }
|
||||
|
||||
std::swap(m_id, other.m_id);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLuint Shader::get_id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
GLint Shader::get_location(const std::string &name)
|
||||
{
|
||||
GLint location = glGetUniformLocation(m_id, name.c_str());
|
||||
if (location == -1) {
|
||||
std::cerr << "WARNING: SHADER: uniform " << name << " not found" << std::endl;
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
void Shader::set_int(const std::string& name, GLint value)
|
||||
{
|
||||
glUniform1i(get_location(name), value);
|
||||
}
|
||||
|
||||
void Shader::set_float(const std::string& name, float value)
|
||||
{
|
||||
glUniform1f(get_location(name), value);
|
||||
}
|
||||
|
||||
void Shader::set_vec3(const std::string& name, float x, float y, float z)
|
||||
{
|
||||
glUniform3f(get_location(name), x, y, z);
|
||||
}
|
||||
|
||||
void Shader::set_vec3(const std::string& name, const glm::vec3& value)
|
||||
{
|
||||
glUniform3f(get_location(name), value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
void Shader::set_mat4(const std::string& name, const glm::mat4& value)
|
||||
{
|
||||
GLint location = get_location(name);
|
||||
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
|
||||
}
|
||||
|
||||
Opt<GLuint> Shader::compile_shader(const std::string& source_path,
|
||||
GLenum shader_type)
|
||||
{
|
||||
auto shader_source = read_entire_file(source_path);
|
||||
if (!shader_source) {
|
||||
std::cerr << "ERROR: SHADER: Could not read shader " << source_path << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
GLuint shader = glCreateShader(shader_type);
|
||||
const char *shader_source_str = shader_source->c_str();
|
||||
glShaderSource(shader, 1, &shader_source_str, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint success;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
GLint log_length = 0;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
|
||||
|
||||
if (log_length > 0) {
|
||||
std::string log(log_length, ' ');
|
||||
glGetShaderInfoLog(shader, log_length, nullptr, &log[0]);
|
||||
std::cerr << "ERROR: SHADER: Could not compile shader: " << log << std::endl;
|
||||
}
|
||||
|
||||
glDeleteShader(shader);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
void Shader::use() const
|
||||
{
|
||||
if (m_id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
glUseProgram(m_id);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
47
src/shader.hpp
Normal file
47
src/shader.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#ifndef FAN_SHADER_HPP_
|
||||
#define FAN_SHADER_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <glad/gl.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Shader
|
||||
{
|
||||
public:
|
||||
static Rc<Shader> create(const std::string& vertex_path,
|
||||
const std::string& fragment_path);
|
||||
Shader(GLuint id) : m_id{id} {}
|
||||
|
||||
~Shader();
|
||||
Shader(const Shader&) = delete;
|
||||
Shader& operator=(const Shader&) = delete;
|
||||
Shader(Shader&& other) noexcept;
|
||||
Shader& operator=(Shader&& other) noexcept;
|
||||
|
||||
GLuint get_id() const;
|
||||
GLint get_location(const std::string &name);
|
||||
|
||||
// Uniform helpers
|
||||
void set_int(const std::string& name, GLint value);
|
||||
void set_float(const std::string& name, float value);
|
||||
void set_vec3(const std::string& name, float x, float y, float z);
|
||||
void set_vec3(const std::string& name, const glm::vec3& value);
|
||||
void set_mat4(const std::string& name, const glm::mat4& value);
|
||||
|
||||
void use() const;
|
||||
|
||||
private:
|
||||
GLuint m_id{};
|
||||
|
||||
static Opt<GLuint> compile_shader(const std::string& source_path,
|
||||
GLenum shader_type);
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_SHADER_HPP_
|
||||
9
src/shaders/debug.frag
Normal file
9
src/shaders/debug.frag
Normal file
@@ -0,0 +1,9 @@
|
||||
#version 330 core
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform vec3 u_color;
|
||||
|
||||
void main() {
|
||||
frag_color = vec4(u_color, 1.0);
|
||||
}
|
||||
11
src/shaders/debug.vert
Normal file
11
src/shaders/debug.vert
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 a_position;
|
||||
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view_projection;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_view_projection * u_model * vec4(a_position, 1.0);
|
||||
}
|
||||
140
src/shaders/default.frag
Normal file
140
src/shaders/default.frag
Normal file
@@ -0,0 +1,140 @@
|
||||
#version 330 core
|
||||
|
||||
struct Material
|
||||
{
|
||||
sampler2D diffuse;
|
||||
sampler2D specular;
|
||||
float shininess;
|
||||
};
|
||||
|
||||
struct DirectionalLight
|
||||
{
|
||||
vec3 direction;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
};
|
||||
|
||||
struct PointLight {
|
||||
vec3 position;
|
||||
|
||||
float constant;
|
||||
float linear;
|
||||
float quadratic;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
};
|
||||
|
||||
struct SpotLight {
|
||||
vec3 position;
|
||||
vec3 direction;
|
||||
|
||||
float cut_off;
|
||||
float outer_cut_off;
|
||||
|
||||
float constant;
|
||||
float linear;
|
||||
float quadratic;
|
||||
|
||||
vec3 ambient;
|
||||
vec3 diffuse;
|
||||
vec3 specular;
|
||||
};
|
||||
|
||||
in vec3 frag_position;
|
||||
in vec3 normal;
|
||||
in vec2 texture_coords;
|
||||
|
||||
out vec4 frag_color;
|
||||
|
||||
uniform vec3 u_view_pos;
|
||||
uniform Material u_material;
|
||||
|
||||
uniform DirectionalLight u_directional_light;
|
||||
#define MAX_POINT_LIGHT_COUNT 8
|
||||
uniform PointLight u_point_lights[MAX_POINT_LIGHT_COUNT];
|
||||
uniform int u_point_light_count;
|
||||
#define MAX_SPOT_LIGHT_COUNT 8
|
||||
uniform SpotLight u_spot_lights[MAX_SPOT_LIGHT_COUNT];
|
||||
uniform int u_spot_light_count;
|
||||
|
||||
vec3 calculate_directional_light(DirectionalLight light, vec3 normal, vec3 view_dir);
|
||||
vec3 calculate_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_dir);
|
||||
vec3 calculate_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_dir);
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 norm = normalize(normal);
|
||||
vec3 view_dir = normalize(u_view_pos - frag_position);
|
||||
|
||||
vec3 result = calculate_directional_light(u_directional_light, norm, view_dir);
|
||||
|
||||
for (int i = 0; i < u_point_light_count; ++i) {
|
||||
result += calculate_point_light(u_point_lights[i], norm, frag_position, view_dir);
|
||||
}
|
||||
|
||||
for (int i = 0; i < u_spot_light_count; ++i) {
|
||||
result += calculate_spot_light(u_spot_lights[i], norm, frag_position, view_dir);
|
||||
}
|
||||
|
||||
frag_color = vec4(result, 1.0);
|
||||
}
|
||||
|
||||
vec3 calculate_directional_light(DirectionalLight light, vec3 normal, vec3 view_dir)
|
||||
{
|
||||
vec3 light_dir = normalize(-light.direction);
|
||||
float diff = max(dot(normal, light_dir), 0.0);
|
||||
vec3 reflect_dir = reflect(-light_dir, normal);
|
||||
float spec = pow(max(dot(view_dir, reflect_dir), 0.0), u_material.shininess);
|
||||
|
||||
vec3 ambient = light.ambient * vec3(texture(u_material.diffuse, texture_coords));
|
||||
vec3 diffuse = light.diffuse * diff * vec3(texture(u_material.diffuse, texture_coords));
|
||||
vec3 specular = light.specular * spec * vec3(texture(u_material.specular, texture_coords));
|
||||
|
||||
return ambient + diffuse + specular;
|
||||
}
|
||||
|
||||
vec3 calculate_point_light(PointLight light, vec3 normal, vec3 frag_position, vec3 view_dir)
|
||||
{
|
||||
vec3 light_dir = normalize(light.position - frag_position);
|
||||
float diff = max(dot(normal, light_dir), 0.0);
|
||||
vec3 reflect_dir = reflect(-light_dir, normal);
|
||||
float spec = pow(max(dot(view_dir, reflect_dir), 0.0), u_material.shininess);
|
||||
float dist = length(light.position - frag_position);
|
||||
float attenuation = 1.0 / (light.constant + light.linear * dist + light.quadratic * (dist * dist));
|
||||
|
||||
vec3 ambient = light.ambient * vec3(texture(u_material.diffuse, texture_coords));
|
||||
vec3 diffuse = light.diffuse * diff * vec3(texture(u_material.diffuse, texture_coords));
|
||||
vec3 specular = light.specular * spec * vec3(texture(u_material.specular, texture_coords));
|
||||
ambient *= attenuation;
|
||||
diffuse *= attenuation;
|
||||
specular *= attenuation;
|
||||
|
||||
return ambient + diffuse + specular;
|
||||
}
|
||||
|
||||
vec3 calculate_spot_light(SpotLight light, vec3 normal, vec3 frag_position, vec3 view_dir)
|
||||
{
|
||||
vec3 light_dir = normalize(light.position - frag_position);
|
||||
float diff = max(dot(normal, light_dir), 0.0);
|
||||
vec3 reflect_dir = reflect(-light_dir, normal);
|
||||
float spec = pow(max(dot(view_dir, reflect_dir), 0.0), u_material.shininess);
|
||||
float dist = length(light.position - frag_position);
|
||||
float attenuation = 1.0 / (light.constant + light.linear * dist + light.quadratic * (dist* dist));
|
||||
|
||||
float theta = dot(light_dir, normalize(-light.direction));
|
||||
float epsilon = light.cut_off - light.outer_cut_off;
|
||||
float intensity = clamp((theta - light.outer_cut_off) / epsilon, 0.0, 1.0);
|
||||
|
||||
vec3 ambient = light.ambient * vec3(texture(u_material.diffuse, texture_coords));
|
||||
vec3 diffuse = light.diffuse * diff * vec3(texture(u_material.diffuse, texture_coords));
|
||||
vec3 specular = light.specular * spec * vec3(texture(u_material.specular, texture_coords));
|
||||
ambient *= attenuation * intensity;
|
||||
diffuse *= attenuation * intensity;
|
||||
specular *= attenuation * intensity;
|
||||
|
||||
return ambient + diffuse + specular;
|
||||
}
|
||||
24
src/shaders/default.vert
Normal file
24
src/shaders/default.vert
Normal file
@@ -0,0 +1,24 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 a_position;
|
||||
layout (location = 1) in vec3 a_normal;
|
||||
layout (location = 2) in vec2 a_texture_coords;
|
||||
|
||||
out vec3 frag_position;
|
||||
out vec3 normal;
|
||||
out vec2 texture_coords;
|
||||
|
||||
uniform mat4 u_model;
|
||||
uniform mat4 u_view_projection;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = u_view_projection * u_model * vec4(a_position, 1.0);
|
||||
|
||||
frag_position = vec3(u_model * vec4(a_position, 1.0));
|
||||
|
||||
mat3 normal_matrix = mat3(transpose(inverse(u_model))); // TODO: Calculate on CPU for performance
|
||||
normal = normal_matrix * a_normal;
|
||||
|
||||
texture_coords = a_texture_coords;
|
||||
}
|
||||
86
src/texture.cpp
Normal file
86
src/texture.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <iostream>
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "texture.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Rc<Texture> Texture::create(const std::string& image_path)
|
||||
{
|
||||
stbi_set_flip_vertically_on_load(true);
|
||||
|
||||
int width, height, n_channels;
|
||||
stbi_uc *image_data = stbi_load(image_path.c_str(), &width, &height, &n_channels, STBI_rgb_alpha);
|
||||
if (image_data == NULL) {
|
||||
std::cerr << "ERROR: TEXTURE: Could not load image " << image_path << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GLuint texture;
|
||||
glGenTextures(1, &texture);
|
||||
if (glGetError() != GL_NO_ERROR) {
|
||||
std::cerr << "ERROR: TEXTURE: glGenTextures" << std::endl;
|
||||
stbi_image_free(image_data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
// Texture wrapping parameters
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
|
||||
// Texture filtering parameters
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
stbi_image_free(image_data);
|
||||
|
||||
return make_rc<Texture>(texture);
|
||||
}
|
||||
|
||||
Texture::~Texture()
|
||||
{
|
||||
glDeleteTextures(1, &m_id);
|
||||
}
|
||||
|
||||
Texture::Texture(Texture&& other) noexcept
|
||||
{
|
||||
m_id = other.m_id;
|
||||
other.m_id = 0;
|
||||
}
|
||||
|
||||
Texture& Texture::operator=(Texture&& other) noexcept
|
||||
{
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
glDeleteTextures(1, &m_id);
|
||||
m_id = other.m_id;
|
||||
other.m_id = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GLuint Texture::get_id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void Texture::bind(GLuint slot/* = 0*/) const
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D, m_id);
|
||||
}
|
||||
|
||||
void Texture::unbind(GLuint slot/* = 0*/)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
35
src/texture.hpp
Normal file
35
src/texture.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#ifndef FAN_TEXTURE_HPP_
|
||||
#define FAN_TEXTURE_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <glad/gl.h>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Texture
|
||||
{
|
||||
public:
|
||||
static Rc<Texture> create(const std::string& image_path);
|
||||
Texture(GLuint id) : m_id{id} {}
|
||||
|
||||
~Texture();
|
||||
Texture(const Texture&) = delete;
|
||||
Texture& operator=(const Texture&) = delete;
|
||||
Texture(Texture&& other) noexcept;
|
||||
Texture& operator=(Texture&& other) noexcept;
|
||||
|
||||
GLuint get_id() const;
|
||||
|
||||
void bind(GLuint slot = 0) const;
|
||||
static void unbind(GLuint slot = 0);
|
||||
|
||||
private:
|
||||
GLuint m_id{};
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_TEXTURE_HPP_
|
||||
49
src/transform.cpp
Normal file
49
src/transform.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "transform.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
void Transform::rotate(float angle_rad, const glm::vec3& axis)
|
||||
{
|
||||
rotation = glm::angleAxis(angle_rad, glm::normalize(axis)) * rotation;
|
||||
}
|
||||
|
||||
glm::mat4 Transform::get_model_matrix() const
|
||||
{
|
||||
glm::mat4 model = glm::translate(glm::mat4(1.0f), position);
|
||||
model *= glm::mat4_cast(rotation);
|
||||
model = glm::scale(model, scale);
|
||||
return model;
|
||||
}
|
||||
|
||||
glm::vec3 Transform::get_euler_angles() const
|
||||
{
|
||||
return glm::eulerAngles(rotation);
|
||||
}
|
||||
|
||||
glm::vec3 Transform::get_forward() const
|
||||
{
|
||||
return rotation * glm::vec3(0.0f, 0.0f, -1.0f);
|
||||
}
|
||||
|
||||
glm::vec3 Transform::get_right() const
|
||||
{
|
||||
return rotation * glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 Transform::get_up() const
|
||||
{
|
||||
return rotation * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
void Transform::set_rotation(float angle_rad, const glm::vec3& axis)
|
||||
{
|
||||
rotation = glm::angleAxis(angle_rad, glm::normalize(axis));
|
||||
}
|
||||
|
||||
void Transform::set_euler_angles(const glm::vec3& euler_rad)
|
||||
{
|
||||
rotation = glm::quat(euler_rad);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
45
src/transform.hpp
Normal file
45
src/transform.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef FAN_TRANSFORM_HPP_
|
||||
#define FAN_TRANSFORM_HPP_
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtx/quaternion.hpp>
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
struct Transform
|
||||
{
|
||||
glm::vec3 position{0.0f, 0.0f, 0.0f};
|
||||
glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f};
|
||||
glm::vec3 scale{1.0f, 1.0f, 1.0f};
|
||||
|
||||
Transform() = default;
|
||||
explicit Transform(const glm::vec3& position)
|
||||
: position(position) {}
|
||||
|
||||
// Rotate relative to current orientation
|
||||
void rotate(float angle_rad, const glm::vec3& axis);
|
||||
|
||||
// Get the model matrix of the transform
|
||||
glm::mat4 get_model_matrix() const;
|
||||
|
||||
// Get Euler angles in radians
|
||||
glm::vec3 get_euler_angles() const;
|
||||
|
||||
// Get direction vectors
|
||||
glm::vec3 get_forward() const;
|
||||
glm::vec3 get_right() const;
|
||||
glm::vec3 get_up() const;
|
||||
|
||||
// Set rotation using axis-angle (angle in radians)
|
||||
void set_rotation(float angle_rad, const glm::vec3& axis);
|
||||
|
||||
// Set rotation using Euler angles in radians (pitch, yaw, roll / X, Y, Z)
|
||||
void set_euler_angles(const glm::vec3& euler_rad);
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_TRANSFORM_HPP_
|
||||
20
src/utils.cpp
Normal file
20
src/utils.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Opt<std::string> read_entire_file(const std::string& file_path)
|
||||
{
|
||||
std::ifstream file(file_path);
|
||||
if (!file) { return std::nullopt; }
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
27
src/utils.hpp
Normal file
27
src/utils.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef FAN_UTILS_HPP_
|
||||
#define FAN_UTILS_HPP_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
template <typename T>
|
||||
using Rc = std::shared_ptr<T>;
|
||||
|
||||
template <typename T, typename... Args>
|
||||
Rc<T> make_rc(Args&&... args)
|
||||
{
|
||||
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using Opt = std::optional<T>;
|
||||
|
||||
Opt<std::string> read_entire_file(const std::string& file_path);
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_UTILS_HPP_
|
||||
18
src/vertex.hpp
Normal file
18
src/vertex.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef FAN_VERTEX_HPP_
|
||||
#define FAN_VERTEX_HPP_
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
struct Vertex
|
||||
{
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 texture_coords;
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_VERTEX_HPP_
|
||||
270
src/window.cpp
Normal file
270
src/window.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <glad/gl.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
|
||||
#include "window.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
Rc<Window> Window::create(int width, int height, const std::string& title)
|
||||
{
|
||||
glfwSetErrorCallback(error_callback);
|
||||
|
||||
if (!glfwInit()) {
|
||||
std::cerr << "ERROR: WINDOW: Could not initialize GLFW" << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
GLFWwindow *window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
|
||||
if (window == nullptr) {
|
||||
std::cerr << "ERROR: WINDOW: Could not create window" << std::endl;
|
||||
glfwTerminate();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
|
||||
glfwSetCursorPosCallback(window, mouse_callback);
|
||||
glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||
|
||||
if (!gladLoadGL(glfwGetProcAddress)) {
|
||||
std::cerr << "ERROR: WINDOW: Could not initialize OpenGL context" << std::endl;
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int fb_width, fb_height;
|
||||
glfwGetFramebufferSize(window, &fb_width, &fb_height);
|
||||
glViewport(0, 0, fb_width, fb_height);
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.IniFilename = nullptr;
|
||||
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init();
|
||||
|
||||
return make_rc<Window>(window, width, height);
|
||||
}
|
||||
|
||||
Window::Window(GLFWwindow *window, int width, int height)
|
||||
: m_width(width)
|
||||
, m_height(height)
|
||||
, m_window(window)
|
||||
{
|
||||
// Bind this instance to the window for static callbacks
|
||||
glfwSetWindowUserPointer(window, this);
|
||||
};
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
if (!is_valid()) { return; }
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
glfwDestroyWindow(m_window);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
Window::Window(Window&& other) noexcept
|
||||
: m_delta_time(other.m_delta_time)
|
||||
, m_width(other.m_width)
|
||||
, m_height(other.m_height)
|
||||
, m_window(std::exchange(other.m_window, nullptr))
|
||||
, m_last_frame(other.m_last_frame)
|
||||
, m_callback_user_data(other.m_callback_user_data)
|
||||
, m_mouse_user_callback(other.m_mouse_user_callback)
|
||||
, m_current_keys(std::move(other.m_current_keys))
|
||||
, m_previous_keys(std::move(other.m_previous_keys))
|
||||
{
|
||||
if (m_window != nullptr) {
|
||||
glfwSetWindowUserPointer(m_window, this);
|
||||
}
|
||||
}
|
||||
|
||||
Window& Window::operator=(Window&& other) noexcept
|
||||
{
|
||||
if (this == &other) { return *this; }
|
||||
|
||||
std::swap(m_delta_time, other.m_delta_time);
|
||||
std::swap(m_width, other.m_width);
|
||||
std::swap(m_height, other.m_height);
|
||||
std::swap(m_window, other.m_window);
|
||||
std::swap(m_last_frame, other.m_last_frame);
|
||||
std::swap(m_callback_user_data, other.m_callback_user_data);
|
||||
std::swap(m_mouse_user_callback, other.m_mouse_user_callback);
|
||||
std::swap(m_current_keys, other.m_current_keys);
|
||||
std::swap(m_previous_keys, other.m_previous_keys);
|
||||
|
||||
if (m_window != nullptr) {
|
||||
glfwSetWindowUserPointer(m_window, this);
|
||||
}
|
||||
if (other.m_window != nullptr) {
|
||||
glfwSetWindowUserPointer(other.m_window, &other);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Window::should_close() const
|
||||
{
|
||||
return is_valid() ? glfwWindowShouldClose(m_window) : true;
|
||||
}
|
||||
|
||||
bool Window::is_valid() const
|
||||
{
|
||||
return m_window != nullptr;
|
||||
}
|
||||
|
||||
bool Window::is_fullscreen() const
|
||||
{
|
||||
return is_valid() && (glfwGetWindowMonitor(m_window) != nullptr);
|
||||
}
|
||||
|
||||
bool Window::is_key_down(int glfw_key)
|
||||
{
|
||||
return is_valid() && m_current_keys[glfw_key];
|
||||
}
|
||||
|
||||
bool Window::is_key_pressed(int glfw_key)
|
||||
{
|
||||
return is_valid() && m_current_keys[glfw_key] && !m_previous_keys[glfw_key];
|
||||
}
|
||||
|
||||
float Window::get_delta_time()
|
||||
{
|
||||
return m_delta_time;
|
||||
}
|
||||
|
||||
int Window::get_width() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
int Window::get_height() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
|
||||
void Window::set_should_close(bool enabled)
|
||||
{
|
||||
if (is_valid()) { glfwSetWindowShouldClose(m_window, enabled); }
|
||||
}
|
||||
|
||||
void Window::set_mouse_grab(bool enabled)
|
||||
{
|
||||
if (is_valid()) {
|
||||
glfwSetInputMode(m_window, GLFW_CURSOR, enabled ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::set_fullscreen(bool enabled)
|
||||
{
|
||||
if (!is_valid() || enabled == is_fullscreen()) { return; }
|
||||
|
||||
static int windowed_x = 100;
|
||||
static int windowed_y = 100;
|
||||
static int windowed_width = 800;
|
||||
static int windowed_height = 600;
|
||||
|
||||
if (enabled) {
|
||||
glfwGetWindowPos(m_window, &windowed_x, &windowed_y);
|
||||
glfwGetWindowSize(m_window, &windowed_width, &windowed_height);
|
||||
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
if (monitor == nullptr) { return; }
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
if (mode == nullptr) { return; }
|
||||
|
||||
glfwSetWindowMonitor(m_window, monitor, 0, 0,
|
||||
mode->width, mode->height, mode->refreshRate);
|
||||
} else {
|
||||
glfwSetWindowMonitor(m_window, nullptr, windowed_x, windowed_y,
|
||||
windowed_width, windowed_height, GLFW_DONT_CARE);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::set_vsync(bool enabled)
|
||||
{
|
||||
if (is_valid()) glfwSwapInterval(enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
void Window::set_callback_user_data(void *data)
|
||||
{
|
||||
m_callback_user_data = data;
|
||||
}
|
||||
|
||||
void Window::set_mouse_callback(MouseCallback callback)
|
||||
{
|
||||
m_mouse_user_callback = callback;
|
||||
}
|
||||
|
||||
void Window::begin()
|
||||
{
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
}
|
||||
|
||||
void Window::update()
|
||||
{
|
||||
if (!is_valid()) { return; }
|
||||
|
||||
float current_time = static_cast<float>(glfwGetTime());
|
||||
m_delta_time = current_time - m_last_frame;
|
||||
m_last_frame = current_time;
|
||||
|
||||
m_previous_keys = m_current_keys;
|
||||
for (int i = 32; i < 348; ++i) {
|
||||
m_current_keys[i] = glfwGetKey(m_window, i) == GLFW_PRESS;
|
||||
}
|
||||
}
|
||||
|
||||
void Window::end()
|
||||
{
|
||||
if (!is_valid()) { return; }
|
||||
|
||||
glfwGetWindowSize(m_window, &m_width, &m_height);
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(m_window);
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
void Window::error_callback(int error, const char* description)
|
||||
{
|
||||
std::cerr << "ERROR: WINDOW: GLFW: code: " << error << " msg: " << description << std::endl;
|
||||
}
|
||||
|
||||
void Window::framebuffer_size_callback(GLFWwindow* window, int width, int height)
|
||||
{
|
||||
(void) window;
|
||||
glViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
void Window::mouse_callback(GLFWwindow* window, double x_pos, double y_pos)
|
||||
{
|
||||
Window* win = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||
if (win == nullptr || !win->is_valid() || win->m_mouse_user_callback == nullptr) { return; }
|
||||
win->m_mouse_user_callback(x_pos, y_pos, win->m_callback_user_data);
|
||||
}
|
||||
|
||||
} // namespace Fan
|
||||
70
src/window.hpp
Normal file
70
src/window.hpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#ifndef FAN_WINDOW_HPP_
|
||||
#define FAN_WINDOW_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
namespace Fan
|
||||
{
|
||||
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
static Rc<Window> create(int width, int height, const std::string& title);
|
||||
Window(GLFWwindow* window, int width, int height);
|
||||
|
||||
~Window();
|
||||
Window(const Window&) = delete;
|
||||
Window& operator=(const Window&) = delete;
|
||||
Window(Window&& other) noexcept;
|
||||
Window& operator=(Window&& other) noexcept;
|
||||
|
||||
bool should_close() const;
|
||||
bool is_valid() const;
|
||||
bool is_fullscreen() const;
|
||||
bool is_key_down(int glfw_key);
|
||||
bool is_key_pressed(int glfw_key);
|
||||
|
||||
float get_delta_time();
|
||||
int get_width() const;
|
||||
int get_height() const;
|
||||
|
||||
void set_should_close(bool enabled);
|
||||
void set_mouse_grab(bool enabled);
|
||||
void set_fullscreen(bool enabled);
|
||||
void set_vsync(bool enabled);
|
||||
|
||||
// User callbacks
|
||||
using MouseCallback = void (*)(double x_pos, double y_pos, void* user_data);
|
||||
void set_callback_user_data(void *data);
|
||||
void set_mouse_callback(MouseCallback callback);
|
||||
|
||||
// Main loop functions
|
||||
void begin();
|
||||
void update();
|
||||
void end();
|
||||
|
||||
private:
|
||||
float m_delta_time{};
|
||||
int m_width{};
|
||||
int m_height{};
|
||||
|
||||
GLFWwindow* m_window{};
|
||||
float m_last_frame{};
|
||||
void *m_callback_user_data{};
|
||||
MouseCallback m_mouse_user_callback{};
|
||||
|
||||
std::unordered_map<int, bool> m_current_keys{};
|
||||
std::unordered_map<int, bool> m_previous_keys{};
|
||||
|
||||
static void error_callback(int error, const char* description);
|
||||
static void framebuffer_size_callback(GLFWwindow* window, int width, int height);
|
||||
static void mouse_callback(GLFWwindow* window, double x_pos, double y_pos);
|
||||
};
|
||||
|
||||
} // namespace Fan
|
||||
|
||||
#endif // FAN_WINDOW_HPP_
|
||||
Reference in New Issue
Block a user