Initial commit - some engine bugs stopping compiling
This commit is contained in:
186
src/Camera.cpp
Normal file
186
src/Camera.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// src/Camera.cpp
|
||||
|
||||
#include "Camera.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <iostream>
|
||||
|
||||
Camera::Camera(ProjectionType type,
|
||||
const glm::vec3& position,
|
||||
const glm::vec3& target,
|
||||
const glm::vec3& up)
|
||||
: projection_type_(type),
|
||||
position_(position),
|
||||
target_(target),
|
||||
up_(up)
|
||||
{
|
||||
view_dirty_ = projection_dirty_ = true;
|
||||
UpdateFromEulerAngles();
|
||||
}
|
||||
|
||||
void Camera::SetPosition(const glm::vec3& position) {
|
||||
position_ = position;
|
||||
view_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::SetTarget(const glm::vec3& target) {
|
||||
target_ = target;
|
||||
view_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::SetUp(const glm::vec3& up) {
|
||||
up_ = up;
|
||||
view_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::LookAt(const glm::vec3& position,
|
||||
const glm::vec3& target,
|
||||
const glm::vec3& up) {
|
||||
position_ = position;
|
||||
target_ = target;
|
||||
up_ = up;
|
||||
view_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::SetPerspective(float fov,
|
||||
float aspect_ratio,
|
||||
float near_plane,
|
||||
float far_plane) {
|
||||
projection_type_ = ProjectionType::Perspective;
|
||||
fov_ = fov;
|
||||
aspect_ratio_ = aspect_ratio;
|
||||
near_plane_ = near_plane;
|
||||
far_plane_ = far_plane;
|
||||
projection_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::SetOrthographic(float left,
|
||||
float right,
|
||||
float bottom,
|
||||
float top,
|
||||
float near_plane,
|
||||
float far_plane) {
|
||||
projection_type_ = ProjectionType::Orthographic;
|
||||
ortho_left_ = left;
|
||||
ortho_right_ = right;
|
||||
ortho_bottom_ = bottom;
|
||||
ortho_top_ = top;
|
||||
near_plane_ = near_plane;
|
||||
far_plane_ = far_plane;
|
||||
projection_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::SetAspectRatio(float aspect_ratio) {
|
||||
aspect_ratio_ = aspect_ratio;
|
||||
projection_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::Move(const glm::vec3& offset) {
|
||||
position_ += offset;
|
||||
target_ += offset;
|
||||
view_dirty_ = true;
|
||||
}
|
||||
|
||||
void Camera::MoveForward(float distance) {
|
||||
glm::vec3 forward = glm::normalize(target_ - position_);
|
||||
Move(forward * distance);
|
||||
}
|
||||
|
||||
void Camera::MoveBackward(float distance) { MoveForward(-distance); }
|
||||
void Camera::MoveLeft(float distance) { Move(glm::normalize(glm::cross(up_, target_ - position_)) * -distance); }
|
||||
void Camera::MoveRight(float distance) { Move(glm::normalize(glm::cross(up_, target_ - position_)) * distance); }
|
||||
void Camera::MoveUp(float distance) { Move(up_ * distance); }
|
||||
void Camera::MoveDown(float distance) { Move(-up_ * distance); }
|
||||
|
||||
void Camera::Rotate(float yaw, float pitch) {
|
||||
yaw_ += yaw;
|
||||
pitch_ += pitch;
|
||||
if (pitch_ > MAX_PITCH) pitch_ = MAX_PITCH;
|
||||
if (pitch_ < -MAX_PITCH) pitch_ = -MAX_PITCH;
|
||||
UpdateFromEulerAngles();
|
||||
}
|
||||
|
||||
void Camera::SetYaw(float yaw) { yaw_ = yaw; UpdateFromEulerAngles(); }
|
||||
void Camera::SetPitch(float p) { pitch_ = p; UpdateFromEulerAngles(); }
|
||||
|
||||
const glm::mat4& Camera::GetViewMatrix() const {
|
||||
if (view_dirty_) UpdateViewMatrix();
|
||||
return view_matrix_;
|
||||
}
|
||||
|
||||
const glm::mat4& Camera::GetProjectionMatrix() const {
|
||||
if (projection_dirty_) UpdateProjectionMatrix();
|
||||
return projection_matrix_;
|
||||
}
|
||||
|
||||
glm::mat4 Camera::GetViewProjectionMatrix() const {
|
||||
return GetProjectionMatrix() * GetViewMatrix();
|
||||
}
|
||||
|
||||
glm::vec3 Camera::GetForward() const {
|
||||
return glm::normalize(target_ - position_);
|
||||
}
|
||||
|
||||
glm::vec3 Camera::GetRight() const {
|
||||
return glm::normalize(glm::cross(GetForward(), up_));
|
||||
}
|
||||
|
||||
glm::vec3 Camera::ScreenToWorld(const glm::vec2& screen_pos,
|
||||
const glm::vec2& screen_size,
|
||||
float depth) const {
|
||||
glm::vec4 ndc{
|
||||
(screen_pos.x / screen_size.x) * 2.0f - 1.0f,
|
||||
(screen_pos.y / screen_size.y) * 2.0f - 1.0f,
|
||||
depth * 2.0f - 1.0f,
|
||||
1.0f
|
||||
};
|
||||
glm::mat4 invVP = glm::inverse(GetViewProjectionMatrix());
|
||||
glm::vec4 world = invVP * ndc;
|
||||
return glm::vec3(world) / world.w;
|
||||
}
|
||||
|
||||
glm::vec2 Camera::WorldToScreen(const glm::vec3& world_pos,
|
||||
const glm::vec2& screen_size) const {
|
||||
glm::vec4 clip = GetViewProjectionMatrix() * glm::vec4(world_pos, 1.0f);
|
||||
glm::vec3 ndc = glm::vec3(clip) / clip.w;
|
||||
return {
|
||||
((ndc.x + 1.0f) * 0.5f) * screen_size.x,
|
||||
((ndc.y + 1.0f) * 0.5f) * screen_size.y
|
||||
};
|
||||
}
|
||||
|
||||
void Camera::PrintInfo() const {
|
||||
std::cout << "Camera Position: ("
|
||||
<< position_.x << ", " << position_.y << ", " << position_.z << ")\n"
|
||||
<< "Yaw: " << yaw_ << ", Pitch: " << pitch_ << "\n";
|
||||
}
|
||||
|
||||
void Camera::UpdateViewMatrix() const {
|
||||
view_matrix_ = glm::lookAt(position_, target_, up_);
|
||||
view_dirty_ = false;
|
||||
}
|
||||
|
||||
void Camera::UpdateProjectionMatrix() const {
|
||||
if (projection_type_ == ProjectionType::Perspective) {
|
||||
projection_matrix_ = glm::perspective(
|
||||
glm::radians(fov_),
|
||||
aspect_ratio_,
|
||||
near_plane_,
|
||||
far_plane_
|
||||
);
|
||||
} else {
|
||||
projection_matrix_ = glm::ortho(
|
||||
ortho_left_, ortho_right_, ortho_bottom_, ortho_top_,
|
||||
near_plane_, far_plane_
|
||||
);
|
||||
}
|
||||
projection_dirty_ = false;
|
||||
}
|
||||
|
||||
void Camera::UpdateFromEulerAngles() {
|
||||
glm::vec3 dir;
|
||||
dir.x = cos(glm::radians(yaw_)) * cos(glm::radians(pitch_));
|
||||
dir.y = sin(glm::radians(pitch_));
|
||||
dir.z = sin(glm::radians(yaw_)) * cos(glm::radians(pitch_));
|
||||
target_ = position_ + glm::normalize(dir);
|
||||
view_dirty_ = true;
|
||||
}
|
||||
118
src/Camera.h
Normal file
118
src/Camera.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
// Modern camera class using C++23 features
|
||||
class Camera {
|
||||
public:
|
||||
enum class ProjectionType {
|
||||
Perspective,
|
||||
Orthographic
|
||||
};
|
||||
|
||||
// Constructor with designated initializers-like approach
|
||||
explicit Camera(ProjectionType type = ProjectionType::Perspective,
|
||||
const glm::vec3& position = glm::vec3(0.0f, 0.0f, 3.0f),
|
||||
const glm::vec3& target = glm::vec3(0.0f, 0.0f, 0.0f),
|
||||
const glm::vec3& up = glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
~Camera() = default;
|
||||
|
||||
// Delete copy constructor and assignment
|
||||
Camera(const Camera&) = delete;
|
||||
Camera& operator=(const Camera&) = delete;
|
||||
|
||||
// Move semantics
|
||||
Camera(Camera&&) noexcept = default;
|
||||
Camera& operator=(Camera&&) noexcept = default;
|
||||
|
||||
// View matrix management
|
||||
void SetPosition(const glm::vec3& position);
|
||||
void SetTarget(const glm::vec3& target);
|
||||
void SetUp(const glm::vec3& up);
|
||||
void LookAt(const glm::vec3& position, const glm::vec3& target, const glm::vec3& up = glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
// Projection management
|
||||
void SetPerspective(float fov, float aspect_ratio, float near_plane, float far_plane);
|
||||
void SetOrthographic(float left, float right, float bottom, float top, float near_plane, float far_plane);
|
||||
void SetAspectRatio(float aspect_ratio);
|
||||
|
||||
// Movement methods
|
||||
void Move(const glm::vec3& offset);
|
||||
void MoveForward(float distance);
|
||||
void MoveBackward(float distance);
|
||||
void MoveLeft(float distance);
|
||||
void MoveRight(float distance);
|
||||
void MoveUp(float distance);
|
||||
void MoveDown(float distance);
|
||||
|
||||
// Rotation methods (first-person camera style)
|
||||
void Rotate(float yaw, float pitch);
|
||||
void SetYaw(float yaw);
|
||||
void SetPitch(float pitch);
|
||||
|
||||
// Modern getters using [[nodiscard]]
|
||||
[[nodiscard]] const glm::mat4& GetViewMatrix() const;
|
||||
[[nodiscard]] const glm::mat4& GetProjectionMatrix() const;
|
||||
[[nodiscard]] glm::mat4 GetViewProjectionMatrix() const;
|
||||
|
||||
[[nodiscard]] const glm::vec3& GetPosition() const { return position_; }
|
||||
[[nodiscard]] const glm::vec3& GetTarget() const { return target_; }
|
||||
[[nodiscard]] const glm::vec3& GetUp() const { return up_; }
|
||||
[[nodiscard]] glm::vec3 GetForward() const;
|
||||
[[nodiscard]] glm::vec3 GetRight() const;
|
||||
|
||||
[[nodiscard]] float GetFOV() const { return fov_; }
|
||||
[[nodiscard]] float GetAspectRatio() const { return aspect_ratio_; }
|
||||
[[nodiscard]] float GetNearPlane() const { return near_plane_; }
|
||||
[[nodiscard]] float GetFarPlane() const { return far_plane_; }
|
||||
|
||||
[[nodiscard]] float GetYaw() const { return yaw_; }
|
||||
[[nodiscard]] float GetPitch() const { return pitch_; }
|
||||
|
||||
[[nodiscard]] ProjectionType GetProjectionType() const { return projection_type_; }
|
||||
|
||||
// Utility functions
|
||||
[[nodiscard]] glm::vec3 ScreenToWorld(const glm::vec2& screen_pos, const glm::vec2& screen_size, float depth = 1.0f) const;
|
||||
[[nodiscard]] glm::vec2 WorldToScreen(const glm::vec3& world_pos, const glm::vec2& screen_size) const;
|
||||
|
||||
// Debug functions
|
||||
void PrintInfo() const;
|
||||
|
||||
private:
|
||||
ProjectionType projection_type_;
|
||||
|
||||
// View parameters
|
||||
glm::vec3 position_;
|
||||
glm::vec3 target_;
|
||||
glm::vec3 up_;
|
||||
|
||||
// Euler angles for first-person camera
|
||||
float yaw_ = -90.0f; // Initialized to look along negative Z-axis
|
||||
float pitch_ = 0.0f;
|
||||
|
||||
// Projection parameters
|
||||
float fov_ = 45.0f;
|
||||
float aspect_ratio_ = 16.0f / 9.0f;
|
||||
float near_plane_ = 0.1f;
|
||||
float far_plane_ = 100.0f;
|
||||
|
||||
// Orthographic parameters
|
||||
float ortho_left_ = -10.0f;
|
||||
float ortho_right_ = 10.0f;
|
||||
float ortho_bottom_ = -10.0f;
|
||||
float ortho_top_ = 10.0f;
|
||||
|
||||
// Cached matrices (mutable for lazy evaluation)
|
||||
mutable glm::mat4 view_matrix_{1.0f};
|
||||
mutable glm::mat4 projection_matrix_{1.0f};
|
||||
mutable bool view_dirty_ = true;
|
||||
mutable bool projection_dirty_ = true;
|
||||
|
||||
void UpdateViewMatrix() const;
|
||||
void UpdateProjectionMatrix() const;
|
||||
void UpdateFromEulerAngles();
|
||||
|
||||
// Constrain pitch to avoid gimbal lock
|
||||
static constexpr float MAX_PITCH = 89.0f;
|
||||
};
|
||||
1213
src/Engine.cpp
Normal file
1213
src/Engine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
180
src/Engine.h
Normal file
180
src/Engine.h
Normal file
@@ -0,0 +1,180 @@
|
||||
#pragma once
|
||||
#include <glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include "ThreadPool.h"
|
||||
|
||||
|
||||
// Forward declarations
|
||||
class Primitive;
|
||||
class GameObject;
|
||||
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
Engine(int window_width = 1280, int window_height = 720, const char* title = "C++ Engine");
|
||||
virtual ~Engine();
|
||||
|
||||
|
||||
virtual bool OnInitialize();
|
||||
virtual void OnShutdown();
|
||||
virtual void OnUpdate(float delta_time);
|
||||
virtual void OnRender();
|
||||
|
||||
|
||||
void Run();
|
||||
|
||||
|
||||
// Primitive factory methods
|
||||
std::shared_ptr<Primitive> CreateCube(const glm::vec3& color = glm::vec3(1.0f));
|
||||
std::shared_ptr<Primitive> CreateSphere(int segments = 32, const glm::vec3& color = glm::vec3(1.0f));
|
||||
std::shared_ptr<Primitive> CreatePlane(float width = 2.0f, float height = 2.0f, const glm::vec3& color = glm::vec3(1.0f));
|
||||
std::shared_ptr<Primitive> CreateGrid(int size = 20, float spacing = 1.0f, const glm::vec3& color = glm::vec3(0.6f));
|
||||
std::shared_ptr<Primitive> CreateOBJModel(const std::string& objPath, const std::string& texturePath = "");
|
||||
|
||||
|
||||
// GameObject management
|
||||
std::shared_ptr<GameObject> CreateGameObject(std::shared_ptr<Primitive> primitive, const glm::vec3& position = glm::vec3(0.0f));
|
||||
void AddGameObject(std::shared_ptr<GameObject> obj);
|
||||
void RemoveGameObject(std::shared_ptr<GameObject> obj);
|
||||
|
||||
|
||||
// Camera control
|
||||
void SetCameraPosition(const glm::vec3& position);
|
||||
void SetCameraTarget(const glm::vec3& target);
|
||||
void SetCameraOrbit(float distance, float angle, float height = 5.0f);
|
||||
|
||||
GLuint text_vao = 0, text_vbo = 0;
|
||||
|
||||
|
||||
GLFWwindow* GetWindow() const { return window_; }
|
||||
float GetTime() const { return (float)glfwGetTime(); }
|
||||
|
||||
|
||||
|
||||
private:
|
||||
ThreadPool threadPool_;
|
||||
|
||||
// Culling result structure
|
||||
struct CullingBatch {
|
||||
Primitive* primitive;
|
||||
std::vector<glm::mat4> modelMatrices;
|
||||
};
|
||||
|
||||
using CullingResult = std::vector<CullingBatch>;
|
||||
|
||||
// Helper methods
|
||||
CullingResult CullObjectsSubset(
|
||||
const std::vector<GameObject*>& objects,
|
||||
size_t startIdx,
|
||||
size_t endIdx,
|
||||
const glm::mat4& viewProjection
|
||||
);
|
||||
|
||||
void MergeCullingResults(
|
||||
const std::vector<CullingResult>& results,
|
||||
std::unordered_map<Primitive*, std::vector<glm::mat4>>& outBatches
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
void InitializeShader();
|
||||
void RenderScene();
|
||||
void RenderSceneInstanced();
|
||||
void RenderOverlay();
|
||||
void RenderOverlayTestPoint();
|
||||
void InitOverlay();
|
||||
|
||||
static void FramebufferSizeCallback(GLFWwindow* window, int width, int height);
|
||||
|
||||
int width_, height_;
|
||||
GLFWwindow* window_;
|
||||
float last_time_ = 0.0f;
|
||||
|
||||
// Camera
|
||||
glm::vec3 camera_position_ = glm::vec3(0, 5, 10);
|
||||
glm::vec3 camera_target_ = glm::vec3(0, 0, 0);
|
||||
glm::vec3 camera_up_ = glm::vec3(0, 1, 0);
|
||||
|
||||
// Rendering
|
||||
|
||||
|
||||
GLuint shader_program_ = 0;
|
||||
std::vector<std::shared_ptr<GameObject>> game_objects_;
|
||||
|
||||
// Overlay VAO/VBO
|
||||
GLuint overlay_vao = 0;
|
||||
GLuint overlay_vbo = 0;
|
||||
GLuint overlay_program = 0;
|
||||
|
||||
// Performance stats
|
||||
static constexpr int PERF_HISTORY = 300;
|
||||
float perf_times[PERF_HISTORY] = {0};
|
||||
int perf_pos = 0;
|
||||
int perf_count = 0;
|
||||
|
||||
// Projection matrix for overlay
|
||||
glm::mat4 ortho2D;
|
||||
|
||||
// Font buffer for stb_easy_font
|
||||
static const int FONT_BUFFER_SIZE = 9999;
|
||||
char font_buffer[FONT_BUFFER_SIZE];
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Primitive base class
|
||||
class Primitive {
|
||||
public:
|
||||
virtual ~Primitive() = default;
|
||||
virtual void Render() = 0;
|
||||
virtual void Bind() = 0;
|
||||
virtual void Cleanup() = 0;
|
||||
|
||||
virtual GLenum GetDrawMode() const { return GL_TRIANGLES; }
|
||||
virtual GLsizei GetIndexCount() const { return index_count_; }
|
||||
virtual GLsizei GetVertexCount() const { return vertex_count_; }
|
||||
virtual void Bind() const { glBindVertexArray(vao_); }
|
||||
|
||||
|
||||
protected:
|
||||
GLuint vao_ = 0;
|
||||
GLuint vbo_ = 0;
|
||||
GLuint ebo_ = 0;
|
||||
GLsizei index_count_ = 0;
|
||||
GLsizei vertex_count_ = 0;
|
||||
GLenum draw_mode_ = GL_TRIANGLES;
|
||||
glm::vec3 color_;
|
||||
};
|
||||
|
||||
// GameObject class
|
||||
class GameObject {
|
||||
public:
|
||||
GameObject(std::shared_ptr<Primitive> prim, const glm::vec3& pos);
|
||||
// Add:
|
||||
Primitive* GetPrimitive() const { return primitive_.get(); }
|
||||
float GetBoundingRadius() const;
|
||||
|
||||
glm::vec3 GetPosition() const { return position_; }
|
||||
glm::mat4 GetModelMatrix() const;
|
||||
void Render();
|
||||
void Cleanup();
|
||||
|
||||
void SetPosition(const glm::vec3& p) { position_ = p; }
|
||||
void SetRotation(const glm::vec3& r) { rotation_ = r; }
|
||||
void SetScale(const glm::vec3& s) { scale_ = s; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<Primitive> primitive_;
|
||||
glm::vec3 position_;
|
||||
glm::vec3 rotation_;
|
||||
glm::vec3 scale_{1.0f};
|
||||
};
|
||||
87
src/Input.cpp
Normal file
87
src/Input.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
// src/Input.cpp
|
||||
|
||||
#include "Input.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
// Static member definitions
|
||||
GLFWwindow* Input::window_ = nullptr;
|
||||
std::unordered_set<int> Input::pressed_keys_;
|
||||
std::unordered_set<int> Input::held_keys_;
|
||||
std::unordered_set<int> Input::released_keys_;
|
||||
std::array<bool, GLFW_MOUSE_BUTTON_LAST> Input::mouse_buttons_{};
|
||||
glm::vec2 Input::mouse_position_{0.0f, 0.0f};
|
||||
glm::vec2 Input::mouse_delta_{0.0f, 0.0f};
|
||||
float Input::scroll_offset_ = 0.0f;
|
||||
|
||||
void Input::Initialize(GLFWwindow* window) {
|
||||
window_ = window;
|
||||
glfwSetKeyCallback(window, KeyCallback);
|
||||
glfwSetMouseButtonCallback(window, MouseButtonCallback);
|
||||
glfwSetCursorPosCallback(window, CursorPositionCallback);
|
||||
glfwSetScrollCallback(window, ScrollCallback);
|
||||
}
|
||||
|
||||
void Input::Update() {
|
||||
// Clear previous frame states
|
||||
released_keys_.clear();
|
||||
scroll_offset_ = 0.0f;
|
||||
mouse_delta_ = {0.0f, 0.0f};
|
||||
|
||||
// Process held keys: keys that were pressed last frame and still down
|
||||
for (auto key : held_keys_) {
|
||||
if (glfwGetKey(window_, key) == GLFW_PRESS) {
|
||||
// still held
|
||||
} else {
|
||||
released_keys_.insert(key);
|
||||
}
|
||||
}
|
||||
// Update held_keys_: all currently pressed
|
||||
held_keys_.clear();
|
||||
pressed_keys_.clear(); // consumed this frame
|
||||
}
|
||||
|
||||
// GLFW callbacks
|
||||
void Input::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
|
||||
if (action == GLFW_PRESS) {
|
||||
pressed_keys_.insert(key);
|
||||
held_keys_.insert(key);
|
||||
} else if (action == GLFW_RELEASE) {
|
||||
released_keys_.insert(key);
|
||||
held_keys_.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void Input::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
|
||||
if (button < 0 || button >= GLFW_MOUSE_BUTTON_LAST) return;
|
||||
if (action == GLFW_PRESS) {
|
||||
mouse_buttons_[button] = true;
|
||||
} else if (action == GLFW_RELEASE) {
|
||||
mouse_buttons_[button] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Input::CursorPositionCallback(GLFWwindow* window, double xpos, double ypos) {
|
||||
glm::vec2 newPos{static_cast<float>(xpos), static_cast<float>(ypos)};
|
||||
mouse_delta_ = newPos - mouse_position_;
|
||||
mouse_position_ = newPos;
|
||||
}
|
||||
|
||||
void Input::ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
|
||||
scroll_offset_ = static_cast<float>(yoffset);
|
||||
}
|
||||
|
||||
bool Input::IsMouseButtonPressed(MouseButton button) {
|
||||
return mouse_buttons_[static_cast<int>(button)];
|
||||
}
|
||||
|
||||
glm::vec2 Input::GetMousePosition() {
|
||||
return mouse_position_;
|
||||
}
|
||||
|
||||
glm::vec2 Input::GetMouseDelta() {
|
||||
return mouse_delta_;
|
||||
}
|
||||
|
||||
float Input::GetScrollOffset() {
|
||||
return scroll_offset_;
|
||||
}
|
||||
64
src/Input.h
Normal file
64
src/Input.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <unordered_set>
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// Modern input system
|
||||
class Input {
|
||||
public:
|
||||
enum class KeyState : int {
|
||||
Released = GLFW_RELEASE,
|
||||
Pressed = GLFW_PRESS,
|
||||
Held = GLFW_REPEAT
|
||||
};
|
||||
|
||||
enum class MouseButton : int {
|
||||
Left = GLFW_MOUSE_BUTTON_LEFT,
|
||||
Right = GLFW_MOUSE_BUTTON_RIGHT,
|
||||
Middle = GLFW_MOUSE_BUTTON_MIDDLE
|
||||
};
|
||||
|
||||
static void Initialize(GLFWwindow* window);
|
||||
static void Update();
|
||||
|
||||
[[nodiscard]] static bool IsKeyPressed(int key);
|
||||
[[nodiscard]] static bool IsKeyHeld(int key);
|
||||
[[nodiscard]] static bool IsKeyReleased(int key);
|
||||
|
||||
[[nodiscard]] static bool IsMouseButtonPressed(MouseButton button);
|
||||
[[nodiscard]] static glm::vec2 GetMousePosition();
|
||||
[[nodiscard]] static glm::vec2 GetMouseDelta();
|
||||
[[nodiscard]] static float GetScrollOffset();
|
||||
|
||||
[[nodiscard]] static constexpr bool IsValidKey(int key) noexcept {
|
||||
return key >= 0 && key < GLFW_KEY_LAST;
|
||||
}
|
||||
|
||||
private:
|
||||
static GLFWwindow* window_;
|
||||
static std::unordered_set<int> pressed_keys_;
|
||||
static std::unordered_set<int> held_keys_;
|
||||
static std::unordered_set<int> released_keys_;
|
||||
static std::array<bool, GLFW_MOUSE_BUTTON_LAST> mouse_buttons_;
|
||||
static glm::vec2 mouse_position_;
|
||||
static glm::vec2 mouse_delta_;
|
||||
static float scroll_offset_;
|
||||
|
||||
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
|
||||
static void CursorPositionCallback(GLFWwindow* window, double xpos, double ypos);
|
||||
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
|
||||
};
|
||||
|
||||
// Out-of-class definitions
|
||||
inline bool Input::IsKeyPressed(int key) {
|
||||
return IsValidKey(key) && pressed_keys_.contains(key);
|
||||
}
|
||||
inline bool Input::IsKeyHeld(int key) {
|
||||
return IsValidKey(key) && held_keys_.contains(key);
|
||||
}
|
||||
inline bool Input::IsKeyReleased(int key) {
|
||||
return IsValidKey(key) && released_keys_.contains(key);
|
||||
}
|
||||
392
src/LevelEditor.cpp
Normal file
392
src/LevelEditor.cpp
Normal file
@@ -0,0 +1,392 @@
|
||||
// LevelEditor.cpp
|
||||
|
||||
#include "Engine.h"
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
// ---------------------------------------------
|
||||
// LevelEditor: free camera, object placement, light sliders, stats
|
||||
// ---------------------------------------------
|
||||
class LevelEditor : public Engine {
|
||||
private:
|
||||
std::vector<std::shared_ptr<GameObject>> objects_;
|
||||
std::shared_ptr<Primitive> monkeyPrim_;
|
||||
std::shared_ptr<GameObject> selected_;
|
||||
|
||||
|
||||
struct Button {
|
||||
float x, y, w, h;
|
||||
bool pressed;
|
||||
};
|
||||
|
||||
|
||||
// Free cam state
|
||||
bool firstMouse_ = true;
|
||||
double lastX_, lastY_;
|
||||
float yaw_ = -90.0f, pitch_ = 0.0f;
|
||||
float camSpeed_ = 10.0f, mouseSens_ = 0.1f;
|
||||
|
||||
// Light control state
|
||||
float lightR = 1, lightG = 1, lightB = 1, lightI = 1;
|
||||
bool dragR = false, dragG = false, dragB = false, dragI = false;
|
||||
|
||||
|
||||
Button btnOrtho { 560, 150, 80, 20, false };
|
||||
bool useOrtho_ = false;
|
||||
|
||||
// Helpers
|
||||
bool KeyDown(int k) const { return glfwGetKey(window_, k) == GLFW_PRESS; }
|
||||
|
||||
void ProcessMouse(double xpos, double ypos) {
|
||||
if (firstMouse_) {
|
||||
lastX_ = xpos; lastY_ = ypos;
|
||||
firstMouse_ = false;
|
||||
}
|
||||
float xoff = (xpos - lastX_) * mouseSens_;
|
||||
float yoff = (lastY_ - ypos) * mouseSens_;
|
||||
lastX_ = xpos; lastY_ = ypos;
|
||||
yaw_ += xoff;
|
||||
pitch_ = glm::clamp(pitch_ + yoff, -89.0f, 89.0f);
|
||||
glm::vec3 front;
|
||||
front.x = cos(glm::radians(yaw_)) * cos(glm::radians(pitch_));
|
||||
front.y = sin(glm::radians(pitch_));
|
||||
front.z = sin(glm::radians(yaw_)) * cos(glm::radians(pitch_));
|
||||
camera_target_ = camera_position_ + glm::normalize(front);
|
||||
}
|
||||
|
||||
// Place primitive at camera target
|
||||
void Place(std::shared_ptr<Primitive> prim) {
|
||||
glm::vec3 pos = camera_target_;
|
||||
auto go = CreateGameObject(prim, pos);
|
||||
objects_.push_back(go);
|
||||
AddGameObject(go);
|
||||
}
|
||||
|
||||
// Raycast pick uses bounding spheres
|
||||
std::shared_ptr<GameObject> Pick(double mx, double my) {
|
||||
int w,h; glfwGetWindowSize(window_,&w,&h);
|
||||
float x = (2.0f*mx)/w -1.0f, y=1.0f-(2.0f*my)/h;
|
||||
glm::vec4 ray_clip{x,y,-1,1};
|
||||
|
||||
// Create projection and view matrices locally
|
||||
glm::mat4 proj = glm::perspective(glm::radians(60.0f), float(w)/h, 0.1f, 1000.0f);
|
||||
glm::mat4 view = glm::lookAt(camera_position_, camera_target_, camera_up_);
|
||||
|
||||
glm::vec4 ray_eye = glm::inverse(proj)*ray_clip; ray_eye.z=-1; ray_eye.w=0;
|
||||
glm::vec3 dir = glm::normalize(glm::vec3(glm::inverse(view)*ray_eye));
|
||||
|
||||
std::shared_ptr<GameObject> best; float bd=FLT_MAX;
|
||||
for(auto& obj:objects_){
|
||||
glm::vec3 oc = camera_position_ - obj->GetPosition();
|
||||
float b = glm::dot(oc,dir);
|
||||
float c = glm::dot(oc,oc) - obj->GetBoundingRadius()*obj->GetBoundingRadius();
|
||||
float disc = b*b - c;
|
||||
if(disc<0) continue;
|
||||
float t = -b - sqrt(disc);
|
||||
if(t>0 && t<bd){ bd=t; best=obj; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void DrawChar(char c, float x, float y, float s) {
|
||||
static const std::vector<std::vector<std::pair<float,float>>> strokes = {
|
||||
{{0,0},{1,0},{1,1},{0,1},{0,0}},{{0.5f,0},{0.5f,1}},
|
||||
{{0,1},{1,1},{1,0.5f},{0,0.5f},{0,0},{1,0}},
|
||||
{{0,1},{1,1},{1,0},{0,0},{1,0.5f}},
|
||||
{{0,1},{0,0.5f},{1,0.5f},{1,1},{1,0}},
|
||||
{{1,1},{0,1},{0,0.5f},{1,0.5f},{1,0},{0,0}},
|
||||
{{1,1},{0,1},{0,0},{1,0},{1,0.5f},{0,0.5f}},
|
||||
{{0,1},{1,1},{0.5f,0}},
|
||||
{{0,0},{0,1},{1,1},{1,0},{0,0},{0,0.5f},{1,0.5f}},
|
||||
{{1,0},{1,1},{0,1},{0,0.5f},{1,0.5f}}
|
||||
};
|
||||
std::vector<float> v;
|
||||
if(c>='0'&&c<='9'){
|
||||
for(auto&p:strokes[c-'0']){
|
||||
v.push_back(x+p.first*s);
|
||||
v.push_back(y+p.second*s);
|
||||
v.push_back(1);v.push_back(1);v.push_back(1);
|
||||
}
|
||||
glBufferData(GL_ARRAY_BUFFER,v.size()*sizeof(float),v.data(),GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,5*sizeof(float),0);
|
||||
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,5*sizeof(float),(void*)(2*sizeof(float)));
|
||||
glDrawArrays(GL_LINE_STRIP,0,(GLsizei)(v.size()/5));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a letter R,G,B,I
|
||||
void DrawLetter(char c, float x, float y, float s) {
|
||||
struct V{float x,y,r,g,b;};
|
||||
std::vector<V> L;
|
||||
if(c=='R'){
|
||||
L={{x,y,1,1,1},{x,y+s,1,1,1},{x,y+s,1,1,1},{x+s*0.7f,y+s,1,1,1},
|
||||
{x+s*0.7f,y+s,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},
|
||||
{x,y+s*0.5f,1,1,1},{x,y+s*0.5f,1,1,1},{x+s*0.7f,y,1,1,1}};
|
||||
} else if(c=='G'){
|
||||
L={{x+s,y+s*0.3f,1,1,1},{x,y+s*0.3f,1,1,1},{x,y+s*0.3f,1,1,1},
|
||||
{x,y+s*0.7f,1,1,1},{x,y+s*0.7f,1,1,1},{x+s,y+s*0.7f,1,1,1},
|
||||
{x+s,y+s*0.7f,1,1,1},{x+s,y+s*0.5f,1,1,1},{x+s,y+s*0.5f,1,1,1},
|
||||
{x+s*0.5f,y+s*0.5f,1,1,1}};
|
||||
} else if(c=='B'){
|
||||
L={{x,y,1,1,1},{x,y+s,1,1,1},{x,y+s,1,1,1},{x+s*0.7f,y+s,1,1,1},
|
||||
{x+s*0.7f,y+s,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},{x+s*0.7f,y+s*0.5f,1,1,1},
|
||||
{x,y+s*0.5f,1,1,1},{x,y+s*0.5f,1,1,1},{x+s*0.7f,y,1,1,1},
|
||||
{x+s*0.7f,y,1,1,1},{x,y,1,1,1}};
|
||||
} else if(c=='I'){
|
||||
L={{x,y+s,1,1,1},{x+s,y+s,1,1,1},{x+s*0.5f,y+s,1,1,1},
|
||||
{x+s*0.5f,y,1,1,1},{x,y,1,1,1},{x+s,y,1,1,1}};
|
||||
}
|
||||
if(!L.empty()){
|
||||
glBufferData(GL_ARRAY_BUFFER,L.size()*sizeof(V),L.data(),GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
|
||||
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
|
||||
glDrawArrays(GL_LINES,0,(GLsizei)L.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
float MeasureTextWidth(const char* text, float size) {
|
||||
return strlen(text) * size * 0.6f;
|
||||
}
|
||||
|
||||
// Draw a production-ready gradient button
|
||||
bool DrawButton(const Button &btn, const char* label,
|
||||
double mx, double my, bool mb) {
|
||||
struct V { float x,y,r,g,b; };
|
||||
|
||||
// Determine states
|
||||
bool hovered = mx >= btn.x && mx <= btn.x + btn.w
|
||||
&& my >= btn.y && my <= btn.y + btn.h;
|
||||
static bool pressedPrev = false;
|
||||
bool pressed = mb && hovered;
|
||||
bool clicked = pressed && !pressedPrev;
|
||||
pressedPrev = pressed;
|
||||
|
||||
// Colors
|
||||
glm::vec3 topColor = hovered ? glm::vec3(0.3f,0.5f,0.8f) : glm::vec3(0.2f,0.4f,0.7f);
|
||||
glm::vec3 bottomColor = hovered ? glm::vec3(0.1f,0.3f,0.6f) : glm::vec3(0.0f,0.2f,0.5f);
|
||||
if (pressed) {
|
||||
topColor *= 0.8f;
|
||||
bottomColor *= 0.8f;
|
||||
}
|
||||
|
||||
// Vertex data: two triangles forming the quad with gradient
|
||||
V verts[6] = {
|
||||
// first triangle
|
||||
{btn.x, btn.y, bottomColor.r, bottomColor.g, bottomColor.b},
|
||||
{btn.x + btn.w, btn.y, bottomColor.r, bottomColor.g, bottomColor.b},
|
||||
{btn.x + btn.w, btn.y + btn.h, topColor.r, topColor.g, topColor.b },
|
||||
// second triangle
|
||||
{btn.x, btn.y, bottomColor.r, bottomColor.g, bottomColor.b},
|
||||
{btn.x + btn.w, btn.y + btn.h, topColor.r, topColor.g, topColor.b },
|
||||
{btn.x, btn.y + btn.h, topColor.r, topColor.g, topColor.b }
|
||||
};
|
||||
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_DYNAMIC_DRAW);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||
|
||||
// Draw border
|
||||
struct V2 { float x,y,r,g,b; };
|
||||
V2 border[5] = {
|
||||
{btn.x, btn.y, 0,0,0},
|
||||
{btn.x + btn.w, btn.y, 0,0,0},
|
||||
{btn.x + btn.w, btn.y + btn.h, 0,0,0},
|
||||
{btn.x, btn.y + btn.h, 0,0,0},
|
||||
{btn.x, btn.y, 0,0,0}
|
||||
};
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(border), border, GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V2),(void*)0);
|
||||
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V2),(void*)(2*sizeof(float)));
|
||||
glDrawArrays(GL_LINE_STRIP,0,5);
|
||||
|
||||
// Draw label centered
|
||||
float textWidth = MeasureTextWidth(label, 12.0f);
|
||||
float tx = btn.x + (btn.w - textWidth) * 0.5f;
|
||||
float ty = btn.y + (btn.h - 12.0f) * 0.5f;
|
||||
DrawChar(label[0], tx, ty, 12); // loop over each char if full string support needed
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Draw slider at (x,y), width w height h controlling value 0–1
|
||||
void DrawSlider(float x,float y,float w,float h,
|
||||
float &val,bool &drag,
|
||||
double mx,double my,bool mb){
|
||||
// handle drag
|
||||
if(mb && mx>=x && mx<=x+w && my>=y && my<=y+h) drag=true;
|
||||
if(!mb) drag=false;
|
||||
if(drag) val = glm::clamp(float((mx-x)/w),0.0f,1.0f);
|
||||
|
||||
struct V{float x,y,r,g,b;};
|
||||
// track bg
|
||||
std::vector<V> quad = {
|
||||
{x,y,0.2f,0.2f,0.2f},{x+w,y,0.2f,0.2f,0.2f},
|
||||
{x+w,y+h,0.2f,0.2f,0.2f},{x,y+h,0.2f,0.2f,0.2f}
|
||||
};
|
||||
glBufferData(GL_ARRAY_BUFFER,quad.size()*sizeof(V),quad.data(),GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
|
||||
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
|
||||
glDrawArrays(GL_TRIANGLE_FAN,0,4);
|
||||
|
||||
// filled
|
||||
float fw = w*val;
|
||||
quad = {
|
||||
{x,y,0.8f,0.4f,0.4f},{x+fw,y,0.8f,0.4f,0.4f},
|
||||
{x+fw,y+h,0.8f,0.4f,0.4f},{x,y+h,0.8f,0.4f,0.4f}
|
||||
};
|
||||
glBufferData(GL_ARRAY_BUFFER,quad.size()*sizeof(V),quad.data(),GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
|
||||
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
|
||||
glDrawArrays(GL_TRIANGLE_FAN,0,4);
|
||||
|
||||
// knob
|
||||
float kx=x+fw-2, ky=y-2, ks=h+4;
|
||||
quad = {
|
||||
{kx,ky,1.0f,1.0f,1.0f},{kx+ks,ky,1.0f,1.0f,1.0f},
|
||||
{kx+ks,ky+ks,1.0f,1.0f,1.0f},{kx,ky+ks,1.0f,1.0f,1.0f}
|
||||
};
|
||||
glBufferData(GL_ARRAY_BUFFER,quad.size()*sizeof(V),quad.data(),GL_DYNAMIC_DRAW);
|
||||
glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,sizeof(V),(void*)0);
|
||||
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,sizeof(V),(void*)(2*sizeof(float)));
|
||||
glDrawArrays(GL_TRIANGLE_FAN,0,4);
|
||||
}
|
||||
|
||||
public:
|
||||
LevelEditor() : Engine(3840,2160,"Level Editor").enable_vsync = true {}
|
||||
|
||||
bool OnInitialize() override {
|
||||
// load monkey
|
||||
try {
|
||||
monkeyPrim_ = CreateOBJModel("monkey.obj");
|
||||
}
|
||||
catch(...) {
|
||||
monkeyPrim_ = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f));
|
||||
}
|
||||
|
||||
// initial grid - fix glm::vec3 constructor
|
||||
Place(CreateGrid(20, 1.0f, glm::vec3(0.4f, 0.4f, 0.4f)));
|
||||
|
||||
// camera
|
||||
camera_position_ = glm::vec3(0, 20, 20);
|
||||
glm::vec3 dir = glm::normalize(glm::vec3(0,0,0) - camera_position_);
|
||||
camera_target_ = camera_position_ + dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnUpdate(float dt) override {
|
||||
// free flight WASD QE
|
||||
glm::vec3 front = glm::normalize(camera_target_ - camera_position_);
|
||||
glm::vec3 right = glm::normalize(glm::cross(front, camera_up_));
|
||||
if(KeyDown(GLFW_KEY_W)) camera_position_ += front * camSpeed_ * dt;
|
||||
if(KeyDown(GLFW_KEY_S)) camera_position_ -= front * camSpeed_ * dt;
|
||||
if(KeyDown(GLFW_KEY_A)) camera_position_ -= right * camSpeed_ * dt;
|
||||
if(KeyDown(GLFW_KEY_D)) camera_position_ += right * camSpeed_ * dt;
|
||||
if(KeyDown(GLFW_KEY_Q)) camera_position_ -= camera_up_ * camSpeed_ * dt;
|
||||
if(KeyDown(GLFW_KEY_E)) camera_position_ += camera_up_ * camSpeed_ * dt;
|
||||
camera_target_ = camera_position_ + front;
|
||||
|
||||
// mouse look on RMB
|
||||
double mx,my; glfwGetCursorPos(window_,&mx,&my);
|
||||
if(glfwGetMouseButton(window_,GLFW_MOUSE_BUTTON_RIGHT)==GLFW_PRESS)
|
||||
ProcessMouse(mx,my);
|
||||
else firstMouse_=true;
|
||||
|
||||
// placement keys - fix glm::vec3 constructors
|
||||
if(KeyDown(GLFW_KEY_1)) Place(CreateCube(glm::vec3(0.8f, 0.2f, 0.2f)));
|
||||
if(KeyDown(GLFW_KEY_2)) Place(CreateSphere(16, glm::vec3(0.2f, 0.8f, 0.2f)));
|
||||
if(KeyDown(GLFW_KEY_3)) Place(CreatePlane(5, 5, glm::vec3(0.2f, 0.2f, 0.8f)));
|
||||
if(KeyDown(GLFW_KEY_4)) Place(CreateGrid(5, 1.0f, glm::vec3(0.5f, 0.5f, 0.5f)));
|
||||
if(KeyDown(GLFW_KEY_5)) Place(monkeyPrim_);
|
||||
|
||||
// picking
|
||||
if(glfwGetMouseButton(window_,GLFW_MOUSE_BUTTON_LEFT)==GLFW_PRESS)
|
||||
selected_ = Pick(mx,my);
|
||||
|
||||
// move selected with arrows
|
||||
if(selected_){
|
||||
glm::vec3 p = selected_->GetPosition();
|
||||
if(KeyDown(GLFW_KEY_UP)) p.z -= dt*5;
|
||||
if(KeyDown(GLFW_KEY_DOWN)) p.z += dt*5;
|
||||
if(KeyDown(GLFW_KEY_LEFT)) p.x -= dt*5;
|
||||
if(KeyDown(GLFW_KEY_RIGHT)) p.x += dt*5;
|
||||
selected_->SetPosition(p);
|
||||
}
|
||||
|
||||
Engine::OnUpdate(dt);
|
||||
}
|
||||
|
||||
void OnRender() override {
|
||||
|
||||
glUseProgram(shader_program_);
|
||||
int w = width_, h = height_;
|
||||
glm::mat4 proj = useOrtho_
|
||||
? glm::ortho(-30.0f*float(w)/h, 30.0f*float(w)/h, -30.0f, 30.0f, 0.1f, 1000.0f)
|
||||
: glm::perspective(glm::radians(60.0f), float(w)/h, 0.1f, 1000.0f);
|
||||
glUniformMatrix4fv(glGetUniformLocation(shader_program_, "uProjection"), 1, GL_FALSE, &proj[0][0]);
|
||||
|
||||
|
||||
// 1) 3D scene + overlay graphs
|
||||
Engine::OnRender();
|
||||
|
||||
// 2) custom 2D UI: sliders + stats
|
||||
RenderOverlay(); // draws graphs & text
|
||||
|
||||
// setup 2D
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
|
||||
glUseProgram(overlay_program);
|
||||
GLint loc = glGetUniformLocation(overlay_program,"uOrtho");
|
||||
glm::mat4 ortho = glm::ortho(0.0f,float(width_),0.0f,float(height_));
|
||||
glUniformMatrix4fv(loc,1,GL_FALSE,&ortho[0][0]);
|
||||
glBindVertexArray(overlay_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, overlay_vbo);
|
||||
|
||||
// mouse state
|
||||
double mx,my; glfwGetCursorPos(window_,&mx,&my);
|
||||
my = height_ - my; // flip Y coordinate for OpenGL
|
||||
bool mb = glfwGetMouseButton(window_,GLFW_MOUSE_BUTTON_LEFT)==GLFW_PRESS;
|
||||
|
||||
// draw sliders and labels
|
||||
DrawSlider(50,50,200,10, lightR, dragR, mx,my, mb);
|
||||
DrawSlider(50,70,200,10, lightG, dragG, mx,my, mb);
|
||||
DrawSlider(50,90,200,10, lightB, dragB, mx,my, mb);
|
||||
DrawSlider(50,110,200,10,lightI, dragI, mx,my, mb);
|
||||
|
||||
if (DrawButton(btnOrtho, useOrtho_ ? "Ortho On" : "Persp On", mx, my, mb)) {
|
||||
useOrtho_ = !useOrtho_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// draw labels
|
||||
DrawChar('R',30,50,12);
|
||||
DrawChar('G',30,70,12);
|
||||
DrawChar('B',30,90,12);
|
||||
DrawChar('I',30,110,12);
|
||||
|
||||
// update 3D shader light
|
||||
glUseProgram(shader_program_);
|
||||
glUniform3f(glGetUniformLocation(shader_program_,"uLightColor"),lightR,lightG,lightB);
|
||||
glUniform1f(glGetUniformLocation(shader_program_,"uAmbientStrength"),0.1f*lightI);
|
||||
|
||||
// restore
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
glDisable(GL_BLEND);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
void OnShutdown() override {
|
||||
Engine::OnShutdown();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
223
src/Mesh.cpp
Normal file
223
src/Mesh.cpp
Normal file
@@ -0,0 +1,223 @@
|
||||
// src/Mesh.cpp
|
||||
|
||||
#include "Mesh.h"
|
||||
#include <glad.h>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
// Constructor using spans
|
||||
Mesh::Mesh(std::span<const Vertex> vertices, std::span<const unsigned int> indices)
|
||||
: vertex_count_(vertices.size()), index_count_(indices.size()) {
|
||||
SetupMesh(vertices, indices);
|
||||
}
|
||||
|
||||
// Constructor using moved vectors
|
||||
Mesh::Mesh(std::vector<Vertex>&& vertices, std::vector<unsigned int>&& indices)
|
||||
: vertex_count_(vertices.size()), index_count_(indices.size()) {
|
||||
SetupMesh(vertices, indices);
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
Mesh::Mesh(Mesh&& other) noexcept
|
||||
: VAO_(other.VAO_), VBO_(other.VBO_), EBO_(other.EBO_),
|
||||
vertex_count_(other.vertex_count_), index_count_(other.index_count_) {
|
||||
other.VAO_ = other.VBO_ = other.EBO_ = 0;
|
||||
other.vertex_count_ = other.index_count_ = 0;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
Mesh& Mesh::operator=(Mesh&& other) noexcept {
|
||||
if (this != &other) {
|
||||
Cleanup();
|
||||
VAO_ = other.VAO_;
|
||||
VBO_ = other.VBO_;
|
||||
EBO_ = other.EBO_;
|
||||
vertex_count_ = other.vertex_count_;
|
||||
index_count_ = other.index_count_;
|
||||
other.VAO_ = other.VBO_ = other.EBO_ = 0;
|
||||
other.vertex_count_ = other.index_count_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Mesh::~Mesh() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void Mesh::SetupMesh(std::span<const Vertex> vertices, std::span<const unsigned int> indices) {
|
||||
glGenVertexArrays(1, &VAO_);
|
||||
glGenBuffers(1, &VBO_);
|
||||
if (!indices.empty()) {
|
||||
glGenBuffers(1, &EBO_);
|
||||
}
|
||||
|
||||
glBindVertexArray(VAO_);
|
||||
|
||||
// Vertex buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO_);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
|
||||
|
||||
// Index buffer
|
||||
if (!indices.empty()) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
|
||||
}
|
||||
|
||||
// Setup attributes
|
||||
SetupVertexAttributes();
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void Mesh::Cleanup() {
|
||||
if (EBO_) glDeleteBuffers(1, &EBO_);
|
||||
if (VBO_) glDeleteBuffers(1, &VBO_);
|
||||
if (VAO_) glDeleteVertexArrays(1, &VAO_);
|
||||
}
|
||||
|
||||
void Mesh::Draw() const {
|
||||
glBindVertexArray(VAO_);
|
||||
if (index_count_ > 0) {
|
||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(index_count_), GL_UNSIGNED_INT, nullptr);
|
||||
} else {
|
||||
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(vertex_count_));
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void Mesh::DrawInstanced(int instance_count) const {
|
||||
glBindVertexArray(VAO_);
|
||||
if (index_count_ > 0) {
|
||||
glDrawElementsInstanced(GL_TRIANGLES, static_cast<GLsizei>(index_count_), GL_UNSIGNED_INT, nullptr, instance_count);
|
||||
} else {
|
||||
glDrawArraysInstanced(GL_TRIANGLES, 0, static_cast<GLsizei>(vertex_count_), instance_count);
|
||||
}
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
std::unique_ptr<Mesh> Mesh::CreateQuad() {
|
||||
std::vector<Vertex> verts = {
|
||||
{{-1, -1, 0}, {0,0,1}, {0,0}, {}, {}},
|
||||
{{ 1, -1, 0}, {0,0,1}, {1,0}, {}, {}},
|
||||
{{ 1, 1, 0}, {0,0,1}, {1,1}, {}, {}},
|
||||
{{-1, 1, 0}, {0,0,1}, {0,1}, {}, {}},
|
||||
};
|
||||
std::vector<unsigned int> idx = {0,1,2, 2,3,0};
|
||||
return std::make_unique<Mesh>(std::move(verts), std::move(idx));
|
||||
}
|
||||
|
||||
std::unique_ptr<Mesh> Mesh::CreateCube() {
|
||||
std::vector<Vertex> verts = {
|
||||
{{-1, -1, -1}, {0,0,-1}, {0,0}, {}, {}},
|
||||
{{ 1, -1, -1}, {0,0,-1}, {1,0}, {}, {}},
|
||||
{{ 1, 1, -1}, {0,0,-1}, {1,1}, {}, {}},
|
||||
{{-1, 1, -1}, {0,0,-1}, {0,1}, {}, {}},
|
||||
{{-1, -1, 1}, {0,0, 1}, {0,0}, {}, {}},
|
||||
{{ 1, -1, 1}, {0,0, 1}, {1,0}, {}, {}},
|
||||
{{ 1, 1, 1}, {0,0, 1}, {1,1}, {}, {}},
|
||||
{{-1, 1, 1}, {0,0, 1}, {0,1}, {}, {}},
|
||||
};
|
||||
std::vector<unsigned int> idx = {
|
||||
0,1,2, 2,3,0, // back
|
||||
4,5,6, 6,7,4, // front
|
||||
0,4,7, 7,3,0, // left
|
||||
1,5,6, 6,2,1, // right
|
||||
3,2,6, 6,7,3, // top
|
||||
0,1,5, 5,4,0 // bottom
|
||||
};
|
||||
return std::make_unique<Mesh>(std::move(verts), std::move(idx));
|
||||
}
|
||||
|
||||
std::unique_ptr<Mesh> Mesh::CreateSphere(int sectors, int stacks) {
|
||||
std::vector<Vertex> verts;
|
||||
std::vector<unsigned int> idx;
|
||||
const float PI = 3.14159265359f;
|
||||
for (int i = 0; i <= stacks; ++i) {
|
||||
float stackAngle = PI / 2 - i * PI / stacks;
|
||||
float xy = cosf(stackAngle);
|
||||
float z = sinf(stackAngle);
|
||||
for (int j = 0; j <= sectors; ++j) {
|
||||
float sectorAngle = j * 2 * PI / sectors;
|
||||
Vertex v;
|
||||
v.position = { xy * cosf(sectorAngle), xy * sinf(sectorAngle), z };
|
||||
v.normal = glm::normalize(v.position);
|
||||
v.tex_coords = { (float)j / sectors, (float)i / stacks };
|
||||
verts.push_back(v);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < stacks; ++i) {
|
||||
int k1 = i * (sectors + 1);
|
||||
int k2 = k1 + sectors + 1;
|
||||
for (int j = 0; j < sectors; ++j) {
|
||||
if (i != 0) {
|
||||
idx.push_back(k1 + j);
|
||||
idx.push_back(k2 + j);
|
||||
idx.push_back(k1 + j + 1);
|
||||
}
|
||||
if (i != (stacks - 1)) {
|
||||
idx.push_back(k1 + j + 1);
|
||||
idx.push_back(k2 + j);
|
||||
idx.push_back(k2 + j + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::make_unique<Mesh>(std::move(verts), std::move(idx));
|
||||
}
|
||||
|
||||
std::unique_ptr<Mesh> Mesh::CreatePlane(float width, float height, int subdivisions) {
|
||||
std::vector<Vertex> verts;
|
||||
std::vector<unsigned int> idx;
|
||||
int rows = subdivisions + 1;
|
||||
int cols = subdivisions + 1;
|
||||
for (int y = 0; y < rows; ++y) {
|
||||
for (int x = 0; x < cols; ++x) {
|
||||
float fx = (float)x / subdivisions * width - width / 2;
|
||||
float fy = (float)y / subdivisions * height - height / 2;
|
||||
verts.push_back({ {fx, fy, 0}, {0,0,1}, {(float)x/subdivisions, (float)y/subdivisions}, {}, {} });
|
||||
}
|
||||
}
|
||||
for (int y = 0; y < subdivisions; ++y) {
|
||||
for (int x = 0; x < subdivisions; ++x) {
|
||||
int i0 = y * cols + x;
|
||||
int i1 = i0 + 1;
|
||||
int i2 = i0 + cols;
|
||||
int i3 = i2 + 1;
|
||||
idx.push_back(i0); idx.push_back(i2); idx.push_back(i1);
|
||||
idx.push_back(i1); idx.push_back(i2); idx.push_back(i3);
|
||||
}
|
||||
}
|
||||
return std::make_unique<Mesh>(std::move(verts), std::move(idx));
|
||||
}
|
||||
|
||||
void Mesh::UpdateVertices(std::span<const Vertex> new_vertices) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, VBO_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, new_vertices.size() * sizeof(Vertex), new_vertices.data());
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
vertex_count_ = new_vertices.size();
|
||||
}
|
||||
|
||||
void Mesh::UpdateIndices(std::span<const unsigned int> new_indices) {
|
||||
if (!EBO_) {
|
||||
glGenBuffers(1, &EBO_);
|
||||
}
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO_);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, new_indices.size() * sizeof(unsigned int), new_indices.data(), GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
index_count_ = new_indices.size();
|
||||
}
|
||||
|
||||
glm::vec3 Mesh::CalculateCenter() const {
|
||||
// User should implement actual center calculation
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
std::pair<glm::vec3, glm::vec3> Mesh::CalculateBoundingBox() const {
|
||||
// User should implement bounding box calculation
|
||||
return { glm::vec3(0.0f), glm::vec3(0.0f) };
|
||||
}
|
||||
|
||||
void Mesh::PrintInfo() const {
|
||||
std::cout << "Mesh - Vertices: " << vertex_count_
|
||||
<< ", Indices: " << index_count_
|
||||
<< ", VAO: " << VAO_ << "\n";
|
||||
}
|
||||
92
src/Mesh.h
Normal file
92
src/Mesh.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <glm/glm.hpp>
|
||||
#include "glad.h"
|
||||
|
||||
// Vertex structure with alignment for GPU (C++11 alignas)
|
||||
struct alignas(16) Vertex {
|
||||
glm::vec3 position{0.0f};
|
||||
glm::vec3 normal{0.0f, 1.0f, 0.0f};
|
||||
glm::vec2 tex_coords{0.0f};
|
||||
glm::vec3 tangent{0.0f};
|
||||
glm::vec3 bitangent{0.0f};
|
||||
};
|
||||
|
||||
// Modern mesh class using C++23 features
|
||||
class Mesh {
|
||||
public:
|
||||
// Constructor using spans (C++20) for better safety
|
||||
explicit Mesh(std::span<const Vertex> vertices, std::span<const unsigned int> indices = {});
|
||||
|
||||
// Constructor with move semantics for large meshes
|
||||
Mesh(std::vector<Vertex>&& vertices, std::vector<unsigned int>&& indices = {});
|
||||
|
||||
~Mesh();
|
||||
|
||||
// Delete copy constructor and assignment (expensive operation)
|
||||
Mesh(const Mesh&) = delete;
|
||||
Mesh& operator=(const Mesh&) = delete;
|
||||
|
||||
// Move semantics
|
||||
Mesh(Mesh&& other) noexcept;
|
||||
Mesh& operator=(Mesh&& other) noexcept;
|
||||
|
||||
void Draw() const;
|
||||
void DrawInstanced(int instance_count) const;
|
||||
|
||||
// Modern getters using [[nodiscard]]
|
||||
[[nodiscard]] size_t GetVertexCount() const { return vertex_count_; }
|
||||
[[nodiscard]] size_t GetIndexCount() const { return index_count_; }
|
||||
[[nodiscard]] bool HasIndices() const { return index_count_ > 0; }
|
||||
[[nodiscard]] GLuint GetVAO() const { return VAO_; }
|
||||
|
||||
// Static factory methods for common shapes
|
||||
[[nodiscard]] static std::unique_ptr<Mesh> CreateQuad();
|
||||
[[nodiscard]] static std::unique_ptr<Mesh> CreateCube();
|
||||
[[nodiscard]] static std::unique_ptr<Mesh> CreateSphere(int sectors = 36, int stacks = 18);
|
||||
[[nodiscard]] static std::unique_ptr<Mesh> CreatePlane(float width = 1.0f, float height = 1.0f, int subdivisions = 1);
|
||||
|
||||
// Utility functions
|
||||
void UpdateVertices(std::span<const Vertex> new_vertices);
|
||||
void UpdateIndices(std::span<const unsigned int> new_indices);
|
||||
|
||||
// Calculate mesh properties
|
||||
[[nodiscard]] glm::vec3 CalculateCenter() const;
|
||||
[[nodiscard]] std::pair<glm::vec3, glm::vec3> CalculateBoundingBox() const;
|
||||
|
||||
// Debug functions
|
||||
void PrintInfo() const;
|
||||
|
||||
private:
|
||||
GLuint VAO_ = 0, VBO_ = 0, EBO_ = 0;
|
||||
size_t vertex_count_ = 0;
|
||||
size_t index_count_ = 0;
|
||||
|
||||
void SetupMesh(std::span<const Vertex> vertices, std::span<const unsigned int> indices);
|
||||
void Cleanup();
|
||||
|
||||
// Vertex attribute setup using constexpr (C++11)
|
||||
static constexpr void SetupVertexAttributes() {
|
||||
// Position attribute (location = 0)
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// Normal attribute (location = 1)
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// Texture coordinate attribute (location = 2)
|
||||
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords));
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
// Tangent attribute (location = 3)
|
||||
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tangent));
|
||||
glEnableVertexAttribArray(3);
|
||||
|
||||
// Bitangent attribute (location = 4)
|
||||
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, bitangent));
|
||||
glEnableVertexAttribArray(4);
|
||||
}
|
||||
};
|
||||
144
src/OBJLoader.cpp
Normal file
144
src/OBJLoader.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "OBJLoader.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
bool OBJLoader::LoadOBJ(const std::string& filepath) {
|
||||
std::ifstream file(filepath);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open OBJ file: " << filepath << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
// Skip empty lines and comments
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
ProcessLine(line);
|
||||
}
|
||||
|
||||
file.close();
|
||||
BuildVertexArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OBJLoader::ProcessLine(const std::string& line) {
|
||||
std::istringstream iss(line);
|
||||
std::string prefix;
|
||||
iss >> prefix;
|
||||
|
||||
if (prefix == "v") {
|
||||
ProcessVertex(line);
|
||||
} else if (prefix == "vt") {
|
||||
ProcessTexCoord(line);
|
||||
} else if (prefix == "vn") {
|
||||
ProcessNormal(line);
|
||||
} else if (prefix == "f") {
|
||||
ProcessFace(line);
|
||||
} else if (prefix == "mtllib") {
|
||||
iss >> materialFile;
|
||||
}
|
||||
}
|
||||
|
||||
void OBJLoader::ProcessVertex(const std::string& line) {
|
||||
std::istringstream iss(line);
|
||||
std::string prefix;
|
||||
glm::vec3 vertex;
|
||||
|
||||
iss >> prefix >> vertex.x >> vertex.y >> vertex.z;
|
||||
temp_vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
void OBJLoader::ProcessTexCoord(const std::string& line) {
|
||||
std::istringstream iss(line);
|
||||
std::string prefix;
|
||||
glm::vec2 texCoord;
|
||||
|
||||
iss >> prefix >> texCoord.x >> texCoord.y;
|
||||
temp_texCoords.push_back(texCoord);
|
||||
}
|
||||
|
||||
void OBJLoader::ProcessNormal(const std::string& line) {
|
||||
std::istringstream iss(line);
|
||||
std::string prefix;
|
||||
glm::vec3 normal;
|
||||
|
||||
iss >> prefix >> normal.x >> normal.y >> normal.z;
|
||||
temp_normals.push_back(normal);
|
||||
}
|
||||
|
||||
void OBJLoader::ProcessFace(const std::string& line) {
|
||||
// Handle faces like: f 1/1/1 2/2/2 3/3/3 or f 1//1 2//2 3//3 or f 1 2 3
|
||||
std::istringstream iss(line);
|
||||
std::string prefix;
|
||||
iss >> prefix;
|
||||
|
||||
OBJFace face;
|
||||
std::string vertex;
|
||||
int vertexCount = 0;
|
||||
|
||||
while (iss >> vertex && vertexCount < 3) {
|
||||
std::replace(vertex.begin(), vertex.end(), '/', ' ');
|
||||
std::istringstream vertexStream(vertex);
|
||||
|
||||
int vertexIndex = 0, texIndex = 0, normalIndex = 0;
|
||||
|
||||
vertexStream >> vertexIndex;
|
||||
if (vertexStream >> texIndex) {
|
||||
if (vertexStream >> normalIndex) {
|
||||
// All three indices present
|
||||
}
|
||||
}
|
||||
|
||||
// OBJ indices start at 1, convert to 0-based
|
||||
face.vertexIndices[vertexCount] = vertexIndex - 1;
|
||||
face.texCoordIndices[vertexCount] = texIndex > 0 ? texIndex - 1 : -1;
|
||||
face.normalIndices[vertexCount] = normalIndex > 0 ? normalIndex - 1 : -1;
|
||||
|
||||
vertexCount++;
|
||||
}
|
||||
|
||||
if (vertexCount == 3) {
|
||||
faces.push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
void OBJLoader::BuildVertexArray() {
|
||||
vertices.clear();
|
||||
indices.clear();
|
||||
|
||||
for (const auto& face : faces) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
OBJVertex vertex;
|
||||
|
||||
// Position
|
||||
if (face.vertexIndices[i] >= 0 && face.vertexIndices[i] < temp_vertices.size()) {
|
||||
vertex.position = temp_vertices[face.vertexIndices[i]];
|
||||
}
|
||||
|
||||
// Texture coordinates
|
||||
if (face.texCoordIndices[i] >= 0 && face.texCoordIndices[i] < temp_texCoords.size()) {
|
||||
vertex.texCoord = temp_texCoords[face.texCoordIndices[i]];
|
||||
} else {
|
||||
vertex.texCoord = glm::vec2(0.0f);
|
||||
}
|
||||
|
||||
// Normal
|
||||
if (face.normalIndices[i] >= 0 && face.normalIndices[i] < temp_normals.size()) {
|
||||
vertex.normal = temp_normals[face.normalIndices[i]];
|
||||
} else {
|
||||
vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f); // Default up normal
|
||||
}
|
||||
|
||||
// Default white color
|
||||
vertex.color = glm::vec3(1.0f);
|
||||
|
||||
vertices.push_back(vertex);
|
||||
indices.push_back(static_cast<unsigned int>(vertices.size() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/OBJLoader.h
Normal file
40
src/OBJLoader.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glad.h>
|
||||
|
||||
struct OBJVertex {
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec2 texCoord;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
struct OBJFace {
|
||||
int vertexIndices[3];
|
||||
int texCoordIndices[3];
|
||||
int normalIndices[3];
|
||||
};
|
||||
|
||||
class OBJLoader {
|
||||
public:
|
||||
std::vector<OBJVertex> vertices;
|
||||
std::vector<unsigned int> indices;
|
||||
std::string materialFile;
|
||||
|
||||
bool LoadOBJ(const std::string& filepath);
|
||||
|
||||
private:
|
||||
std::vector<glm::vec3> temp_vertices;
|
||||
std::vector<glm::vec2> temp_texCoords;
|
||||
std::vector<glm::vec3> temp_normals;
|
||||
std::vector<OBJFace> faces;
|
||||
|
||||
void ProcessLine(const std::string& line);
|
||||
void ProcessVertex(const std::string& line);
|
||||
void ProcessTexCoord(const std::string& line);
|
||||
void ProcessNormal(const std::string& line);
|
||||
void ProcessFace(const std::string& line);
|
||||
void BuildVertexArray();
|
||||
};
|
||||
89
src/OBJModelPrimitive.cpp
Normal file
89
src/OBJModelPrimitive.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "OBJModelPrimitive.h"
|
||||
#include <iostream>
|
||||
|
||||
OBJModelPrimitive::OBJModelPrimitive(const std::string& objPath, const std::string& texturePath)
|
||||
: textureID_(0), hasTexture_(false) {
|
||||
|
||||
// Load OBJ file
|
||||
OBJLoader loader;
|
||||
if (!loader.LoadOBJ(objPath)) {
|
||||
std::cerr << "Failed to load OBJ file: " << objPath << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Load texture if provided
|
||||
if (!texturePath.empty()) {
|
||||
if (texturePath.ends_with(".bmp")) {
|
||||
textureID_ = SimpleTextureLoader::LoadBMP(texturePath);
|
||||
} else if (texturePath.ends_with(".tga")) {
|
||||
textureID_ = SimpleTextureLoader::LoadTGA(texturePath);
|
||||
}
|
||||
hasTexture_ = (textureID_ != 0);
|
||||
}
|
||||
|
||||
// Create VAO/VBO/EBO
|
||||
glGenVertexArrays(1, &vao_);
|
||||
glGenBuffers(1, &vbo_);
|
||||
glGenBuffers(1, &ebo_);
|
||||
|
||||
glBindVertexArray(vao_);
|
||||
|
||||
// Upload vertex data (position + color + normal + texcoord = 11 floats)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glBufferData(GL_ARRAY_BUFFER, loader.vertices.size() * sizeof(OBJVertex),
|
||||
loader.vertices.data(), GL_STATIC_DRAW);
|
||||
|
||||
// Upload indices
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, loader.indices.size() * sizeof(unsigned int),
|
||||
loader.indices.data(), GL_STATIC_DRAW);
|
||||
|
||||
// Position attribute (location 0)
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(OBJVertex), (void*)offsetof(OBJVertex, position));
|
||||
glEnableVertexAttribArray(0);
|
||||
|
||||
// Color attribute (location 1)
|
||||
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(OBJVertex), (void*)offsetof(OBJVertex, color));
|
||||
glEnableVertexAttribArray(1);
|
||||
|
||||
// Normal attribute (location 2)
|
||||
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(OBJVertex), (void*)offsetof(OBJVertex, normal));
|
||||
glEnableVertexAttribArray(2);
|
||||
|
||||
// Texture coordinate attribute (location 3)
|
||||
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(OBJVertex), (void*)offsetof(OBJVertex, texCoord));
|
||||
glEnableVertexAttribArray(3);
|
||||
|
||||
glBindVertexArray(0);
|
||||
index_count_ = static_cast<GLsizei>(loader.indices.size());
|
||||
}
|
||||
|
||||
OBJModelPrimitive::~OBJModelPrimitive() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
void OBJModelPrimitive::Render() {
|
||||
if (hasTexture_) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID_);
|
||||
}
|
||||
|
||||
glBindVertexArray(vao_);
|
||||
glDrawElements(GL_TRIANGLES, index_count_, GL_UNSIGNED_INT, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
if (hasTexture_) {
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void OBJModelPrimitive::Bind() {
|
||||
glBindVertexArray(vao_);
|
||||
}
|
||||
|
||||
void OBJModelPrimitive::Cleanup() {
|
||||
if (vao_) glDeleteVertexArrays(1, &vao_);
|
||||
if (vbo_) glDeleteBuffers(1, &vbo_);
|
||||
if (ebo_) glDeleteBuffers(1, &ebo_);
|
||||
if (textureID_) glDeleteTextures(1, &textureID_);
|
||||
}
|
||||
18
src/OBJModelPrimitive.h
Normal file
18
src/OBJModelPrimitive.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include "Engine.h"
|
||||
#include "OBJLoader.h"
|
||||
#include "SimpleTextureLoader.h"
|
||||
|
||||
class OBJModelPrimitive : public Primitive {
|
||||
public:
|
||||
OBJModelPrimitive(const std::string& objPath, const std::string& texturePath = "");
|
||||
~OBJModelPrimitive();
|
||||
|
||||
void Render() override;
|
||||
void Bind() override;
|
||||
void Cleanup() override;
|
||||
|
||||
private:
|
||||
GLuint textureID_;
|
||||
bool hasTexture_;
|
||||
};
|
||||
129
src/Renderer.cpp
Normal file
129
src/Renderer.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
// src/Renderer.cpp
|
||||
|
||||
#include "Renderer.h"
|
||||
#include <glad.h>
|
||||
#include <iostream>
|
||||
#include <chrono>
|
||||
|
||||
Renderer::Renderer() = default;
|
||||
|
||||
void Renderer::Initialize() {
|
||||
// Nothing specific for now; ensure GLAD is loaded before this call
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LESS);
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
}
|
||||
|
||||
void Renderer::Shutdown() {
|
||||
// Cleanup if needed
|
||||
}
|
||||
|
||||
void Renderer::BeginFrame() {
|
||||
stats_.draw_calls = 0;
|
||||
stats_.vertices_rendered = 0;
|
||||
stats_.triangles_rendered = 0;
|
||||
frame_start_time_ = GetTime();
|
||||
}
|
||||
|
||||
void Renderer::EndFrame() {
|
||||
float frame_end = GetTime();
|
||||
stats_.frame_time = frame_end - frame_start_time_;
|
||||
}
|
||||
|
||||
void Renderer::Clear(const glm::vec4& color) {
|
||||
glClearColor(color.r, color.g, color.b, color.a);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void Renderer::SetViewport(int x, int y, int width, int height) {
|
||||
viewport_size_ = { width, height };
|
||||
glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
void Renderer::SetCullFace(CullFace c) {
|
||||
if (c == CullFace::None) glDisable(GL_CULL_FACE);
|
||||
else {
|
||||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(static_cast<GLenum>(c));
|
||||
}
|
||||
current_cull_face_ = c;
|
||||
}
|
||||
|
||||
void Renderer::SetBlendMode(BlendMode mode) {
|
||||
if (mode == BlendMode::None) {
|
||||
glDisable(GL_BLEND);
|
||||
} else {
|
||||
glEnable(GL_BLEND);
|
||||
switch (mode) {
|
||||
case BlendMode::Alpha: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); break;
|
||||
case BlendMode::Additive: glBlendFunc(GL_SRC_ALPHA, GL_ONE); break;
|
||||
case BlendMode::Multiply: glBlendFunc(GL_DST_COLOR, GL_ZERO); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
current_blend_mode_ = mode;
|
||||
}
|
||||
|
||||
void Renderer::SetDepthTest(DepthTest dt) {
|
||||
if (dt == DepthTest::None) glDisable(GL_DEPTH_TEST);
|
||||
else {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
GLenum func = GL_LESS;
|
||||
switch (dt) {
|
||||
case DepthTest::Less: func = GL_LESS; break;
|
||||
case DepthTest::LessEqual: func = GL_LEQUAL; break;
|
||||
case DepthTest::Greater: func = GL_GREATER; break;
|
||||
case DepthTest::GreaterEqual:func = GL_GEQUAL; break;
|
||||
case DepthTest::Equal: func = GL_EQUAL; break;
|
||||
case DepthTest::NotEqual: func = GL_NOTEQUAL; break;
|
||||
case DepthTest::Always: func = GL_ALWAYS; break;
|
||||
case DepthTest::Never: func = GL_NEVER; break;
|
||||
default: break;
|
||||
}
|
||||
glDepthFunc(func);
|
||||
}
|
||||
current_depth_test_ = dt;
|
||||
}
|
||||
|
||||
void Renderer::SetWireframe(bool enable) {
|
||||
glPolygonMode(GL_FRONT_AND_BACK, enable ? GL_LINE : GL_FILL);
|
||||
wireframe_enabled_ = enable;
|
||||
}
|
||||
|
||||
void Renderer::PrintRenderInfo() const {
|
||||
std::cout << "OpenGL Version: " << GetOpenGLVersion() << "\n";
|
||||
std::cout << "Viewport: " << viewport_size_.x << "x" << viewport_size_.y << "\n";
|
||||
std::cout << "Draw Calls: " << stats_.draw_calls << "\n";
|
||||
std::cout << "Vertices: " << stats_.vertices_rendered << "\n";
|
||||
std::cout << "Triangles: " << stats_.triangles_rendered << "\n";
|
||||
}
|
||||
|
||||
void Renderer::ResetStats() {
|
||||
stats_ = RenderStats{};
|
||||
}
|
||||
|
||||
std::string Renderer::GetOpenGLVersion() {
|
||||
return reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||
}
|
||||
|
||||
std::string Renderer::GetGPUInfo() {
|
||||
return reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
||||
}
|
||||
|
||||
bool Renderer::CheckOpenGLError(const std::string& op) {
|
||||
GLenum err;
|
||||
bool hasError = false;
|
||||
while ((err = glGetError()) != GL_NO_ERROR) {
|
||||
std::cerr << "OpenGL Error";
|
||||
if (!op.empty()) std::cerr << " in " << op;
|
||||
std::cerr << ": " << err << "\n";
|
||||
hasError = true;
|
||||
}
|
||||
return !hasError;
|
||||
}
|
||||
|
||||
float Renderer::GetTime() const {
|
||||
using clock = std::chrono::high_resolution_clock;
|
||||
return std::chrono::duration<float>(clock::now().time_since_epoch()).count();
|
||||
}
|
||||
96
src/Renderer.h
Normal file
96
src/Renderer.h
Normal file
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include "glad.h"
|
||||
|
||||
// Modern renderer using C++23 features
|
||||
class Renderer {
|
||||
public:
|
||||
// Render states using scoped enum
|
||||
enum class CullFace : GLenum {
|
||||
None = 0,
|
||||
Front = GL_FRONT,
|
||||
Back = GL_BACK,
|
||||
FrontAndBack = GL_FRONT_AND_BACK
|
||||
};
|
||||
|
||||
enum class BlendMode : int {
|
||||
None = 0,
|
||||
Alpha,
|
||||
Additive,
|
||||
Multiply
|
||||
};
|
||||
|
||||
enum class DepthTest : int {
|
||||
None = 0,
|
||||
Less,
|
||||
LessEqual,
|
||||
Greater,
|
||||
GreaterEqual,
|
||||
Equal,
|
||||
NotEqual,
|
||||
Always,
|
||||
Never
|
||||
};
|
||||
|
||||
struct RenderStats {
|
||||
size_t draw_calls = 0;
|
||||
size_t vertices_rendered = 0;
|
||||
size_t triangles_rendered = 0;
|
||||
float frame_time = 0.0f;
|
||||
};
|
||||
|
||||
Renderer();
|
||||
~Renderer() = default;
|
||||
|
||||
// Delete copy constructor and assignment
|
||||
Renderer(const Renderer&) = delete;
|
||||
Renderer& operator=(const Renderer&) = delete;
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
// Frame management
|
||||
void BeginFrame();
|
||||
void EndFrame();
|
||||
|
||||
void Clear(const glm::vec4& color = {0.0f, 0.0f, 0.0f, 1.0f});
|
||||
void SetViewport(int x, int y, int width, int height);
|
||||
|
||||
// Render state management
|
||||
void SetCullFace(CullFace cull_face);
|
||||
void SetBlendMode(BlendMode blend_mode);
|
||||
void SetDepthTest(DepthTest depth_test);
|
||||
void SetWireframe(bool enable);
|
||||
|
||||
// Modern getters using [[nodiscard]]
|
||||
[[nodiscard]] const RenderStats& GetStats() const { return stats_; }
|
||||
[[nodiscard]] glm::ivec2 GetViewportSize() const { return viewport_size_; }
|
||||
[[nodiscard]] bool IsWireframeEnabled() const { return wireframe_enabled_; }
|
||||
|
||||
// Debug functions
|
||||
void PrintRenderInfo() const;
|
||||
void ResetStats();
|
||||
|
||||
// Utility functions
|
||||
[[nodiscard]] static std::string GetOpenGLVersion();
|
||||
[[nodiscard]] static std::string GetGPUInfo();
|
||||
[[nodiscard]] static bool CheckOpenGLError(const std::string& operation = "");
|
||||
|
||||
private:
|
||||
RenderStats stats_;
|
||||
glm::ivec2 viewport_size_{0, 0};
|
||||
|
||||
// Current render states
|
||||
CullFace current_cull_face_ = CullFace::Back;
|
||||
BlendMode current_blend_mode_ = BlendMode::None;
|
||||
DepthTest current_depth_test_ = DepthTest::Less;
|
||||
bool wireframe_enabled_ = false;
|
||||
|
||||
// Performance tracking
|
||||
float frame_start_time_ = 0.0f;
|
||||
|
||||
void SetOpenGLState();
|
||||
void UpdateStats();
|
||||
[[nodiscard]] float GetTime() const;
|
||||
};
|
||||
350
src/ResourceManager.cpp
Normal file
350
src/ResourceManager.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
// src/ResourceManager.cpp
|
||||
|
||||
#include "ResourceManager.h"
|
||||
#include "Shader.h"
|
||||
#include "Mesh.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
|
||||
// Static member definitions
|
||||
std::unordered_map<std::string, std::shared_ptr<Shader>> ResourceManager::shaders_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Texture>> ResourceManager::textures_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Mesh>> ResourceManager::meshes_;
|
||||
bool ResourceManager::hot_reload_enabled_ = false;
|
||||
std::unordered_map<std::string, std::filesystem::file_time_type> ResourceManager::shader_timestamps_;
|
||||
std::unordered_map<std::string, std::filesystem::path> ResourceManager::shader_paths_;
|
||||
std::unordered_map<std::string, std::filesystem::file_time_type> ResourceManager::texture_timestamps_;
|
||||
std::unordered_map<std::string, std::filesystem::path> ResourceManager::texture_paths_;
|
||||
|
||||
std::expected<std::shared_ptr<Shader>, std::string>
|
||||
ResourceManager::LoadShader(const std::string& name,
|
||||
const std::filesystem::path& vert,
|
||||
const std::filesystem::path& frag) {
|
||||
try {
|
||||
auto shader = std::make_shared<Shader>(vert, frag);
|
||||
if (!shader->IsValid()) {
|
||||
return std::unexpected("Shader compilation/linking failed: " + name);
|
||||
}
|
||||
shaders_[name] = shader;
|
||||
if (hot_reload_enabled_) {
|
||||
shader_timestamps_[name] = std::filesystem::last_write_time(vert);
|
||||
shader_paths_[name] = vert;
|
||||
}
|
||||
return shader;
|
||||
} catch (const std::exception& e) {
|
||||
return std::unexpected(std::string("Exception loading shader ") + name + ": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::expected<std::shared_ptr<Shader>, std::string>
|
||||
ResourceManager::LoadShader(const std::string& name,
|
||||
const std::filesystem::path& vert,
|
||||
const std::filesystem::path& geom,
|
||||
const std::filesystem::path& frag) {
|
||||
try {
|
||||
auto shader = std::make_shared<Shader>(vert, geom, frag);
|
||||
if (!shader->IsValid()) {
|
||||
return std::unexpected("Shader compilation/linking failed: " + name);
|
||||
}
|
||||
shaders_[name] = shader;
|
||||
if (hot_reload_enabled_) {
|
||||
shader_timestamps_[name] = std::filesystem::last_write_time(vert);
|
||||
shader_paths_[name] = vert;
|
||||
}
|
||||
return shader;
|
||||
} catch (const std::exception& e) {
|
||||
return std::unexpected(std::string("Exception loading shader ") + name + ": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Shader> ResourceManager::GetShader(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
return (it != shaders_.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
std::expected<std::shared_ptr<Texture>, std::string>
|
||||
ResourceManager::LoadTexture(const std::string& name,
|
||||
const std::filesystem::path& path,
|
||||
bool generate_mipmaps) {
|
||||
try {
|
||||
// TODO: Replace this stub with actual texture loading
|
||||
// auto texture = std::make_shared<Texture>(path, generate_mipmaps);
|
||||
// if (!texture->IsValid()) {
|
||||
// return std::unexpected("Texture loading failed: " + name);
|
||||
// }
|
||||
// textures_[name] = texture;
|
||||
|
||||
if (hot_reload_enabled_) {
|
||||
texture_timestamps_[name] = std::filesystem::last_write_time(path);
|
||||
texture_paths_[name] = path;
|
||||
}
|
||||
|
||||
// For now, return error since texture loading is not implemented
|
||||
return std::unexpected("Texture loading not implemented");
|
||||
} catch (const std::exception& e) {
|
||||
return std::unexpected(std::string("Exception loading texture ") + name + ": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Texture> ResourceManager::GetTexture(const std::string& name) {
|
||||
auto it = textures_.find(name);
|
||||
return (it != textures_.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void ResourceManager::RegisterMesh(const std::string& name, std::shared_ptr<Mesh> mesh) {
|
||||
meshes_[name] = std::move(mesh);
|
||||
}
|
||||
|
||||
std::shared_ptr<Mesh> ResourceManager::GetMesh(const std::string& name) {
|
||||
auto it = meshes_.find(name);
|
||||
return (it != meshes_.end()) ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void ResourceManager::ClearShaders() {
|
||||
shaders_.clear();
|
||||
shader_timestamps_.clear();
|
||||
shader_paths_.clear();
|
||||
}
|
||||
|
||||
void ResourceManager::ClearTextures() {
|
||||
textures_.clear();
|
||||
texture_timestamps_.clear();
|
||||
texture_paths_.clear();
|
||||
}
|
||||
|
||||
void ResourceManager::ClearMeshes() {
|
||||
meshes_.clear();
|
||||
}
|
||||
|
||||
void ResourceManager::ClearAll() {
|
||||
ClearShaders();
|
||||
ClearTextures();
|
||||
ClearMeshes();
|
||||
}
|
||||
|
||||
void ResourceManager::PrintResourceInfo() {
|
||||
std::cout << "Shaders: " << shaders_.size()
|
||||
<< ", Textures: " << textures_.size()
|
||||
<< ", Meshes: " << meshes_.size() << "\n";
|
||||
}
|
||||
|
||||
bool ResourceManager::HasShader(const std::string& name) {
|
||||
return shaders_.count(name) > 0;
|
||||
}
|
||||
|
||||
bool ResourceManager::HasTexture(const std::string& name) {
|
||||
return textures_.count(name) > 0;
|
||||
}
|
||||
|
||||
bool ResourceManager::HasMesh(const std::string& name) {
|
||||
return meshes_.count(name) > 0;
|
||||
}
|
||||
|
||||
void ResourceManager::LoadShadersFromDirectory(const std::filesystem::path& dir) {
|
||||
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||||
std::cerr << "Directory does not exist: " << dir << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::filesystem::path> vert_files;
|
||||
std::unordered_map<std::string, std::filesystem::path> frag_files;
|
||||
std::unordered_map<std::string, std::filesystem::path> geom_files;
|
||||
|
||||
// Collect shader files by base name
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
|
||||
auto path = entry.path();
|
||||
auto base_name = path.stem().string();
|
||||
auto extension = path.extension().string();
|
||||
|
||||
if (extension == ".vert") {
|
||||
vert_files[base_name] = path;
|
||||
} else if (extension == ".frag") {
|
||||
frag_files[base_name] = path;
|
||||
} else if (extension == ".geom") {
|
||||
geom_files[base_name] = path;
|
||||
}
|
||||
}
|
||||
|
||||
// Match and load shader pairs/triplets
|
||||
for (const auto& [base_name, vert_path] : vert_files) {
|
||||
auto frag_it = frag_files.find(base_name);
|
||||
if (frag_it != frag_files.end()) {
|
||||
auto geom_it = geom_files.find(base_name);
|
||||
|
||||
if (geom_it != geom_files.end()) {
|
||||
// Load with geometry shader
|
||||
auto result = LoadShader(base_name, vert_path, geom_it->second, frag_it->second);
|
||||
if (result) {
|
||||
std::cout << "Loaded shader with geometry: " << base_name << "\n";
|
||||
} else {
|
||||
std::cerr << "Failed to load shader " << base_name << ": " << result.error() << "\n";
|
||||
}
|
||||
} else {
|
||||
// Load without geometry shader
|
||||
auto result = LoadShader(base_name, vert_path, frag_it->second);
|
||||
if (result) {
|
||||
std::cout << "Loaded shader: " << base_name << "\n";
|
||||
} else {
|
||||
std::cerr << "Failed to load shader " << base_name << ": " << result.error() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::LoadTexturesFromDirectory(const std::filesystem::path& dir) {
|
||||
if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) {
|
||||
std::cerr << "Directory does not exist: " << dir << "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Common image extensions using unordered_set instead of set
|
||||
std::unordered_set<std::string> image_extensions = {".png", ".jpg", ".jpeg", ".bmp", ".tga", ".dds"};
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
|
||||
auto path = entry.path();
|
||||
auto extension = path.extension().string();
|
||||
|
||||
// Convert to lowercase for comparison
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
|
||||
if (image_extensions.count(extension)) {
|
||||
auto name = path.stem().string();
|
||||
auto result = LoadTexture(name, path);
|
||||
if (result) {
|
||||
std::cout << "Loaded texture: " << name << "\n";
|
||||
} else {
|
||||
std::cerr << "Failed to load texture " << name << ": " << result.error() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::ReloadShader(const std::string& name) {
|
||||
auto shader_it = shaders_.find(name);
|
||||
if (shader_it == shaders_.end()) {
|
||||
std::cerr << "Cannot reload shader: " << name << " not found\n";
|
||||
return;
|
||||
}
|
||||
|
||||
auto path_it = shader_paths_.find(name);
|
||||
if (path_it == shader_paths_.end()) {
|
||||
std::cerr << "Cannot reload shader: " << name << " path not stored\n";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Determine fragment shader path (assume same base name, different extension)
|
||||
auto vert_path = path_it->second;
|
||||
auto frag_path = vert_path;
|
||||
frag_path.replace_extension(".frag");
|
||||
|
||||
// Check if geometry shader exists
|
||||
auto geom_path = vert_path;
|
||||
geom_path.replace_extension(".geom");
|
||||
|
||||
if (std::filesystem::exists(geom_path)) {
|
||||
// Reload with geometry shader
|
||||
auto new_shader = std::make_shared<Shader>(vert_path, geom_path, frag_path);
|
||||
if (new_shader->IsValid()) {
|
||||
shaders_[name] = new_shader;
|
||||
shader_timestamps_[name] = std::filesystem::last_write_time(vert_path);
|
||||
std::cout << "Reloaded shader with geometry: " << name << "\n";
|
||||
} else {
|
||||
std::cerr << "Failed to reload shader with geometry: " << name << "\n";
|
||||
}
|
||||
} else {
|
||||
// Reload without geometry shader
|
||||
auto new_shader = std::make_shared<Shader>(vert_path, frag_path);
|
||||
if (new_shader->IsValid()) {
|
||||
shaders_[name] = new_shader;
|
||||
shader_timestamps_[name] = std::filesystem::last_write_time(vert_path);
|
||||
std::cout << "Reloaded shader: " << name << "\n";
|
||||
} else {
|
||||
std::cerr << "Failed to reload shader: " << name << "\n";
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception reloading shader " << name << ": " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::ReloadTexture(const std::string& name) {
|
||||
auto texture_it = textures_.find(name);
|
||||
if (texture_it == textures_.end()) {
|
||||
std::cerr << "Cannot reload texture: " << name << " not found\n";
|
||||
return;
|
||||
}
|
||||
|
||||
auto path_it = texture_paths_.find(name);
|
||||
if (path_it == texture_paths_.end()) {
|
||||
std::cerr << "Cannot reload texture: " << name << " path not stored\n";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: Replace this with actual texture reloading
|
||||
// auto new_texture = std::make_shared<Texture>(path_it->second);
|
||||
// if (new_texture->IsValid()) {
|
||||
// textures_[name] = new_texture;
|
||||
// texture_timestamps_[name] = std::filesystem::last_write_time(path_it->second);
|
||||
// std::cout << "Reloaded texture: " << name << "\n";
|
||||
// } else {
|
||||
// std::cerr << "Failed to reload texture: " << name << "\n";
|
||||
// }
|
||||
|
||||
// For now, just update timestamp
|
||||
texture_timestamps_[name] = std::filesystem::last_write_time(path_it->second);
|
||||
std::cout << "Texture reload placeholder for: " << name << "\n";
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception reloading texture " << name << ": " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::CheckForChanges() {
|
||||
if (!hot_reload_enabled_) return;
|
||||
|
||||
// Check for shader changes
|
||||
for (const auto& [name, shader] : shaders_) {
|
||||
auto path_it = shader_paths_.find(name);
|
||||
if (path_it == shader_paths_.end()) continue;
|
||||
|
||||
try {
|
||||
auto current = std::filesystem::last_write_time(path_it->second);
|
||||
auto stored_it = shader_timestamps_.find(name);
|
||||
|
||||
if (stored_it != shader_timestamps_.end() && current != stored_it->second) {
|
||||
std::cout << "Detected change in shader: " << name << "\n";
|
||||
ReloadShader(name);
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
std::cerr << "Error checking shader file " << name << ": " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Check for texture changes
|
||||
for (const auto& [name, texture] : textures_) {
|
||||
auto path_it = texture_paths_.find(name);
|
||||
if (path_it == texture_paths_.end()) continue;
|
||||
|
||||
try {
|
||||
auto current = std::filesystem::last_write_time(path_it->second);
|
||||
auto stored_it = texture_timestamps_.find(name);
|
||||
|
||||
if (stored_it != texture_timestamps_.end() && current != stored_it->second) {
|
||||
std::cout << "Detected change in texture: " << name << "\n";
|
||||
ReloadTexture(name);
|
||||
texture_timestamps_[name] = current;
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
std::cerr << "Error checking texture file " << name << ": " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/ResourceManager.h
Normal file
95
src/ResourceManager.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "Shader.h"
|
||||
#include "Mesh.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <expected>
|
||||
|
||||
// Forward declarations
|
||||
class Texture;
|
||||
|
||||
// Modern resource manager using C++23 features
|
||||
class ResourceManager {
|
||||
public:
|
||||
// Delete constructor - this is a static-only class
|
||||
ResourceManager() = delete;
|
||||
~ResourceManager() = delete;
|
||||
ResourceManager(const ResourceManager&) = delete;
|
||||
ResourceManager& operator=(const ResourceManager&) = delete;
|
||||
|
||||
|
||||
|
||||
static bool IsHotReloadEnabled();
|
||||
|
||||
// Shader management using std::expected (C++23)
|
||||
[[nodiscard]] static std::expected<std::shared_ptr<Shader>, std::string>
|
||||
LoadShader(const std::string& name,
|
||||
const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& fragment_path);
|
||||
|
||||
[[nodiscard]] static std::expected<std::shared_ptr<Shader>, std::string>
|
||||
LoadShader(const std::string& name,
|
||||
const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& geometry_path,
|
||||
const std::filesystem::path& fragment_path);
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<Shader> GetShader(const std::string& name);
|
||||
|
||||
// Texture management
|
||||
[[nodiscard]] static std::expected<std::shared_ptr<Texture>, std::string>
|
||||
LoadTexture(const std::string& name, const std::filesystem::path& path, bool generate_mipmaps = true);
|
||||
|
||||
[[nodiscard]] static std::shared_ptr<Texture> GetTexture(const std::string& name);
|
||||
|
||||
// Mesh management (for commonly used meshes)
|
||||
static void RegisterMesh(const std::string& name, std::shared_ptr<Mesh> mesh);
|
||||
[[nodiscard]] static std::shared_ptr<Mesh> GetMesh(const std::string& name);
|
||||
|
||||
// Resource cleanup
|
||||
static void ClearShaders();
|
||||
static void ClearTextures();
|
||||
static void ClearMeshes();
|
||||
static void ClearAll();
|
||||
|
||||
// Resource information
|
||||
[[nodiscard]] static size_t GetShaderCount() { return shaders_.size(); }
|
||||
[[nodiscard]] static size_t GetTextureCount() { return textures_.size(); }
|
||||
[[nodiscard]] static size_t GetMeshCount() { return meshes_.size(); }
|
||||
|
||||
// Debug functions
|
||||
static void PrintResourceInfo();
|
||||
[[nodiscard]] static bool HasShader(const std::string& name);
|
||||
[[nodiscard]] static bool HasTexture(const std::string& name);
|
||||
[[nodiscard]] static bool HasMesh(const std::string& name);
|
||||
|
||||
// Batch loading functions
|
||||
static void LoadShadersFromDirectory(const std::filesystem::path& directory);
|
||||
static void LoadTexturesFromDirectory(const std::filesystem::path& directory);
|
||||
|
||||
// Hot reloading support (for development)
|
||||
static void EnableHotReload(bool enable) { hot_reload_enabled_ = enable; }
|
||||
static void CheckForChanges(); // Call this each frame in debug builds
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string, std::shared_ptr<Shader>> shaders_;
|
||||
static std::unordered_map<std::string, std::shared_ptr<Texture>> textures_;
|
||||
static std::unordered_map<std::string, std::shared_ptr<Mesh>> meshes_;
|
||||
|
||||
// Hot reload support
|
||||
static bool hot_reload_enabled_;
|
||||
static std::unordered_map<std::string, std::filesystem::file_time_type> shader_timestamps_;
|
||||
static std::unordered_map<std::string, std::filesystem::file_time_type> texture_timestamps_;
|
||||
|
||||
static std::unordered_map<std::string, std::filesystem::path> texture_paths_;
|
||||
|
||||
|
||||
// Helper functions
|
||||
[[nodiscard]] static bool IsShaderFile(const std::filesystem::path& path);
|
||||
[[nodiscard]] static bool IsTextureFile(const std::filesystem::path& path);
|
||||
static void ReloadShader(const std::string& name);
|
||||
static void ReloadTexture(const std::string& name);
|
||||
|
||||
static std::unordered_map<std::string, std::filesystem::path> shader_paths_;
|
||||
};
|
||||
215
src/Scene.cpp
Normal file
215
src/Scene.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// src/Scene.cpp
|
||||
|
||||
#include "Scene.h"
|
||||
#include "Shader.h"
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <iostream>
|
||||
|
||||
// Add a node with a full transform matrix
|
||||
size_t Scene::AddNode(std::shared_ptr<Mesh> mesh, const glm::mat4& transform) {
|
||||
SceneNode node;
|
||||
node.mesh = std::move(mesh);
|
||||
node.transform = transform;
|
||||
node.UpdateTransform();
|
||||
nodes_.push_back(node);
|
||||
node_visibility_cache_.push_back(true);
|
||||
return nodes_.size() - 1;
|
||||
}
|
||||
|
||||
// Add a node with position/rotation/scale decomposition
|
||||
size_t Scene::AddNode(std::shared_ptr<Mesh> mesh,
|
||||
const glm::vec3& position,
|
||||
const glm::vec3& rotation,
|
||||
const glm::vec3& scale) {
|
||||
SceneNode node;
|
||||
node.mesh = std::move(mesh);
|
||||
node.position = position;
|
||||
node.rotation = rotation;
|
||||
node.scale = scale;
|
||||
node.UpdateTransform();
|
||||
nodes_.push_back(node);
|
||||
node_visibility_cache_.push_back(true);
|
||||
return nodes_.size() - 1;
|
||||
}
|
||||
|
||||
void Scene::SceneNode::UpdateTransform() {
|
||||
// Create transformation matrix from position, rotation, and scale
|
||||
glm::mat4 T = glm::translate(glm::mat4(1.0f), position);
|
||||
glm::mat4 R = glm::rotate(glm::mat4(1.0f), rotation.x, glm::vec3(1, 0, 0));
|
||||
R = glm::rotate(R, rotation.y, glm::vec3(0, 1, 0));
|
||||
R = glm::rotate(R, rotation.z, glm::vec3(0, 0, 1));
|
||||
glm::mat4 S = glm::scale(glm::mat4(1.0f), scale);
|
||||
|
||||
transform = T * R * S;
|
||||
}
|
||||
|
||||
void Scene::RemoveNode(size_t index) {
|
||||
if (index < nodes_.size()) {
|
||||
nodes_.erase(nodes_.begin() + index);
|
||||
node_visibility_cache_.erase(node_visibility_cache_.begin() + index);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::SetNodeTransform(size_t index, const glm::mat4& transform) {
|
||||
if (index < nodes_.size()) {
|
||||
nodes_[index].transform = transform;
|
||||
nodes_[index].UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::SetNodePosition(size_t index, const glm::vec3& position) {
|
||||
if (index < nodes_.size()) {
|
||||
nodes_[index].position = position;
|
||||
nodes_[index].UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::SetNodeRotation(size_t index, const glm::vec3& rotation) {
|
||||
if (index < nodes_.size()) {
|
||||
nodes_[index].rotation = rotation;
|
||||
nodes_[index].UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::SetNodeScale(size_t index, const glm::vec3& scale) {
|
||||
if (index < nodes_.size()) {
|
||||
nodes_[index].scale = scale;
|
||||
nodes_[index].UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::SetNodeVisibility(size_t index, bool visible) {
|
||||
if (index < nodes_.size()) {
|
||||
nodes_[index].visible = visible;
|
||||
node_visibility_cache_[index] = visible;
|
||||
}
|
||||
}
|
||||
|
||||
size_t Scene::AddDirectionalLight(const glm::vec3& direction,
|
||||
const glm::vec3& color,
|
||||
float intensity) {
|
||||
Light light;
|
||||
light.type = Light::Type::Directional;
|
||||
light.direction = direction;
|
||||
light.color = color;
|
||||
light.intensity = intensity;
|
||||
lights_.push_back(light);
|
||||
return lights_.size() - 1;
|
||||
}
|
||||
|
||||
size_t Scene::AddPointLight(const glm::vec3& position,
|
||||
const glm::vec3& color,
|
||||
float intensity) {
|
||||
Light light;
|
||||
light.type = Light::Type::Point;
|
||||
light.position = position;
|
||||
light.color = color;
|
||||
light.intensity = intensity;
|
||||
lights_.push_back(light);
|
||||
return lights_.size() - 1;
|
||||
}
|
||||
|
||||
size_t Scene::AddSpotLight(const glm::vec3& position,
|
||||
const glm::vec3& direction,
|
||||
const glm::vec3& color,
|
||||
float intensity,
|
||||
float inner_cutoff,
|
||||
float outer_cutoff) {
|
||||
Light light;
|
||||
light.type = Light::Type::Spot;
|
||||
light.position = position;
|
||||
light.direction = direction;
|
||||
light.color = color;
|
||||
light.intensity = intensity;
|
||||
light.inner_cutoff = inner_cutoff;
|
||||
light.outer_cutoff = outer_cutoff;
|
||||
lights_.push_back(light);
|
||||
return lights_.size() - 1;
|
||||
}
|
||||
|
||||
void Scene::RemoveLight(size_t index) {
|
||||
if (index < lights_.size()) {
|
||||
lights_.erase(lights_.begin() + index);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::SetLightEnabled(size_t index, bool enabled) {
|
||||
if (index < lights_.size()) {
|
||||
lights_[index].enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::Render(Shader& shader,
|
||||
const glm::mat4& view_matrix, // Changed from 'view'
|
||||
const glm::mat4& projection_matrix) { // Changed from 'proj'
|
||||
shader.Bind();
|
||||
for (size_t i = 0; i < nodes_.size(); ++i) {
|
||||
const auto& node = nodes_[i];
|
||||
if (!node.visible) continue;
|
||||
// Set model matrix
|
||||
shader.SetUniform("u_Model", node.transform);
|
||||
// Compute normal matrix
|
||||
glm::mat3 normalMat = glm::inverseTranspose(glm::mat3(node.transform));
|
||||
shader.SetUniform("u_NormalMatrix", normalMat);
|
||||
// Set view/projection
|
||||
shader.SetUniform("u_View", view_matrix); // Updated variable name
|
||||
shader.SetUniform("u_Projection", projection_matrix); // Updated variable name
|
||||
// Draw mesh
|
||||
node.mesh->Draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::RenderShadowMap(Shader& shader, // Remove const here
|
||||
const glm::mat4& lightSpace) {
|
||||
shader.Bind();
|
||||
shader.SetUniform("u_LightSpace", lightSpace);
|
||||
for (auto& node : nodes_) {
|
||||
if (!node.visible) continue;
|
||||
shader.SetUniform("u_Model", node.transform);
|
||||
node.mesh->Draw();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::Clear() {
|
||||
nodes_.clear();
|
||||
lights_.clear();
|
||||
node_visibility_cache_.clear();
|
||||
}
|
||||
|
||||
void Scene::PrintSceneInfo() const {
|
||||
std::cout << "Scene has "
|
||||
<< nodes_.size() << " nodes and "
|
||||
<< lights_.size() << " lights.\n";
|
||||
}
|
||||
|
||||
void Scene::SetAllNodesVisibility(bool visible) {
|
||||
for (size_t i = 0; i < nodes_.size(); ++i) {
|
||||
nodes_[i].visible = visible;
|
||||
node_visibility_cache_[i] = visible;
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::UpdateAllNodeTransforms() {
|
||||
for (auto& node : nodes_) {
|
||||
node.UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::FrustumCull(const glm::mat4& vp) {
|
||||
for (size_t i = 0; i < nodes_.size(); ++i) {
|
||||
node_visibility_cache_[i] = IsNodeInFrustum(nodes_[i], vp);
|
||||
nodes_[i].visible = node_visibility_cache_[i];
|
||||
}
|
||||
}
|
||||
|
||||
size_t Scene::GetVisibleNodeCount() const {
|
||||
return std::count(node_visibility_cache_.begin(),
|
||||
node_visibility_cache_.end(), true);
|
||||
}
|
||||
|
||||
bool Scene::IsNodeInFrustum(const SceneNode& node, const glm::mat4& vp) const {
|
||||
// TODO: Implement proper frustum culling
|
||||
// For now, just return true (no culling)
|
||||
return true;
|
||||
}
|
||||
117
src/Scene.h
Normal file
117
src/Scene.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
#include "Mesh.h"
|
||||
#include "Shader.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
// Forward declarations
|
||||
class Camera;
|
||||
|
||||
// Modern scene management using C++23
|
||||
class Scene {
|
||||
public:
|
||||
// Scene node using modern C++ features
|
||||
struct SceneNode {
|
||||
std::shared_ptr<Mesh> mesh;
|
||||
glm::mat4 transform{1.0f};
|
||||
glm::vec3 position{0.0f};
|
||||
glm::vec3 rotation{0.0f}; // Euler angles in degrees
|
||||
glm::vec3 scale{1.0f};
|
||||
bool visible = true;
|
||||
|
||||
// Update transform matrix from position, rotation, scale
|
||||
void UpdateTransform();
|
||||
|
||||
// C++20 spaceship operator
|
||||
auto operator<=>(const SceneNode& other) const = default;
|
||||
};
|
||||
|
||||
// Light structure
|
||||
struct Light {
|
||||
enum class Type { Directional, Point, Spot };
|
||||
|
||||
Type type = Type::Directional;
|
||||
glm::vec3 position{0.0f};
|
||||
glm::vec3 direction{0.0f, -1.0f, 0.0f};
|
||||
glm::vec3 color{1.0f};
|
||||
float intensity = 1.0f;
|
||||
|
||||
// Point/Spot light attenuation
|
||||
float constant = 1.0f;
|
||||
float linear = 0.09f;
|
||||
float quadratic = 0.032f;
|
||||
|
||||
// Spot light specific
|
||||
float inner_cutoff = 12.5f; // degrees
|
||||
float outer_cutoff = 15.0f; // degrees
|
||||
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
Scene() = default;
|
||||
~Scene() = default;
|
||||
|
||||
// Delete copy constructor and assignment
|
||||
Scene(const Scene&) = delete;
|
||||
Scene& operator=(const Scene&) = delete;
|
||||
|
||||
// Move semantics
|
||||
Scene(Scene&&) noexcept = default;
|
||||
Scene& operator=(Scene&&) noexcept = default;
|
||||
|
||||
// Node management
|
||||
size_t AddNode(std::shared_ptr<Mesh> mesh, const glm::mat4& transform = glm::mat4(1.0f));
|
||||
size_t AddNode(std::shared_ptr<Mesh> mesh, const glm::vec3& position,
|
||||
const glm::vec3& rotation = glm::vec3(0.0f),
|
||||
const glm::vec3& scale = glm::vec3(1.0f));
|
||||
|
||||
void RemoveNode(size_t index);
|
||||
void SetNodeTransform(size_t index, const glm::mat4& transform);
|
||||
void SetNodePosition(size_t index, const glm::vec3& position);
|
||||
void SetNodeRotation(size_t index, const glm::vec3& rotation);
|
||||
void SetNodeScale(size_t index, const glm::vec3& scale);
|
||||
void SetNodeVisibility(size_t index, bool visible);
|
||||
|
||||
// Light management
|
||||
size_t AddDirectionalLight(const glm::vec3& direction, const glm::vec3& color = glm::vec3(1.0f), float intensity = 1.0f);
|
||||
size_t AddPointLight(const glm::vec3& position, const glm::vec3& color = glm::vec3(1.0f), float intensity = 1.0f);
|
||||
size_t AddSpotLight(const glm::vec3& position, const glm::vec3& direction,
|
||||
const glm::vec3& color = glm::vec3(1.0f), float intensity = 1.0f,
|
||||
float inner_cutoff = 12.5f, float outer_cutoff = 15.0f);
|
||||
|
||||
void RemoveLight(size_t index);
|
||||
void SetLightEnabled(size_t index, bool enabled);
|
||||
|
||||
// Rendering
|
||||
void Render(Shader& shader, const glm::mat4& view_matrix, const glm::mat4& projection_matrix);
|
||||
void RenderShadowMap(Shader& shader, const glm::mat4& lightSpace);
|
||||
|
||||
|
||||
// Utility functions
|
||||
void Clear();
|
||||
[[nodiscard]] size_t GetNodeCount() const { return nodes_.size(); }
|
||||
[[nodiscard]] size_t GetLightCount() const { return lights_.size(); }
|
||||
[[nodiscard]] const SceneNode& GetNode(size_t index) const { return nodes_[index]; }
|
||||
[[nodiscard]] const Light& GetLight(size_t index) const { return lights_[index]; }
|
||||
|
||||
// Debug functions
|
||||
void PrintSceneInfo() const;
|
||||
|
||||
// Batch operations using ranges (C++20)
|
||||
void SetAllNodesVisibility(bool visible);
|
||||
void UpdateAllNodeTransforms();
|
||||
|
||||
// Culling and optimization
|
||||
void FrustumCull(const glm::mat4& view_projection_matrix);
|
||||
[[nodiscard]] size_t GetVisibleNodeCount() const;
|
||||
|
||||
private:
|
||||
std::vector<SceneNode> nodes_;
|
||||
std::vector<Light> lights_;
|
||||
std::vector<bool> node_visibility_cache_; // For frustum culling
|
||||
|
||||
void SetupLightUniforms(const Shader& shader) const;
|
||||
[[nodiscard]] bool IsNodeInFrustum(const SceneNode& node, const glm::mat4& view_projection_matrix) const;
|
||||
};
|
||||
200
src/Shader.cpp
Normal file
200
src/Shader.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
// src/Shader.cpp
|
||||
|
||||
#include "Shader.h"
|
||||
#include <vector> // Add this include
|
||||
|
||||
// Helper to read a file into a string
|
||||
std::optional<std::string> Shader::ReadFile(const std::filesystem::path& path) {
|
||||
std::ifstream file(path, std::ios::in | std::ios::binary);
|
||||
if (!file) return std::nullopt;
|
||||
std::ostringstream contents;
|
||||
contents << file.rdbuf();
|
||||
return contents.str();
|
||||
}
|
||||
|
||||
// Constructor (vertex + fragment)
|
||||
Shader::Shader(const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& fragment_path) {
|
||||
auto vert_src_opt = ReadFile(vertex_path);
|
||||
auto frag_src_opt = ReadFile(fragment_path);
|
||||
if (!vert_src_opt || !frag_src_opt) {
|
||||
std::cerr << "Failed to read shader files\n";
|
||||
return;
|
||||
}
|
||||
auto vert_res = TryCompileShader(GL_VERTEX_SHADER, *vert_src_opt, vertex_path.string());
|
||||
auto frag_res = TryCompileShader(GL_FRAGMENT_SHADER, *frag_src_opt, fragment_path.string());
|
||||
if (!vert_res.success || !frag_res.success) {
|
||||
std::cerr << vert_res.error_message << "\n" << frag_res.error_message << "\n";
|
||||
return;
|
||||
}
|
||||
std::vector<GLuint> shaders = { vert_res.shader_id, frag_res.shader_id };
|
||||
program_id_ = CreateProgram(shaders);
|
||||
// Cleanup shader objects
|
||||
glDeleteShader(vert_res.shader_id);
|
||||
glDeleteShader(frag_res.shader_id);
|
||||
}
|
||||
|
||||
// Constructor (vertex + geometry + fragment)
|
||||
Shader::Shader(const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& geometry_path,
|
||||
const std::filesystem::path& fragment_path) {
|
||||
auto vert_src_opt = ReadFile(vertex_path);
|
||||
auto geom_src_opt = ReadFile(geometry_path);
|
||||
auto frag_src_opt = ReadFile(fragment_path);
|
||||
if (!vert_src_opt || !geom_src_opt || !frag_src_opt) {
|
||||
std::cerr << "Failed to read shader files\n";
|
||||
return;
|
||||
}
|
||||
auto vert_res = TryCompileShader(GL_VERTEX_SHADER, *vert_src_opt, vertex_path.string());
|
||||
auto geom_res = TryCompileShader(GL_GEOMETRY_SHADER, *geom_src_opt, geometry_path.string());
|
||||
auto frag_res = TryCompileShader(GL_FRAGMENT_SHADER, *frag_src_opt, fragment_path.string());
|
||||
if (!vert_res.success || !geom_res.success || !frag_res.success) {
|
||||
std::cerr << vert_res.error_message << "\n"
|
||||
<< geom_res.error_message << "\n"
|
||||
<< frag_res.error_message << "\n";
|
||||
return;
|
||||
}
|
||||
std::vector<GLuint> shaders = { vert_res.shader_id, geom_res.shader_id, frag_res.shader_id };
|
||||
program_id_ = CreateProgram(shaders);
|
||||
glDeleteShader(vert_res.shader_id);
|
||||
glDeleteShader(geom_res.shader_id);
|
||||
glDeleteShader(frag_res.shader_id);
|
||||
}
|
||||
|
||||
Shader::~Shader() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
Shader::Shader(Shader&& other) noexcept {
|
||||
program_id_ = other.program_id_;
|
||||
other.program_id_ = 0;
|
||||
}
|
||||
|
||||
Shader& Shader::operator=(Shader&& other) noexcept {
|
||||
if (this != &other) {
|
||||
Cleanup();
|
||||
program_id_ = other.program_id_;
|
||||
other.program_id_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Shader::Bind() const {
|
||||
if (program_id_) glUseProgram(program_id_);
|
||||
}
|
||||
|
||||
void Shader::Unbind() const {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void Shader::Cleanup() {
|
||||
if (program_id_) {
|
||||
glDeleteProgram(program_id_);
|
||||
program_id_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint Shader::CompileShader(GLenum type, const std::string& source) {
|
||||
GLuint id = glCreateShader(type);
|
||||
const char* src = source.c_str();
|
||||
glShaderSource(id, 1, &src, nullptr);
|
||||
glCompileShader(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
Shader::CompileResult Shader::TryCompileShader(GLenum type,
|
||||
const std::string& source,
|
||||
const std::string& name) {
|
||||
CompileResult result;
|
||||
GLuint shader = CompileShader(type, source);
|
||||
GLint status;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
GLint length;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
|
||||
std::string log(length, ' ');
|
||||
glGetShaderInfoLog(shader, length, &length, log.data());
|
||||
result.success = false;
|
||||
result.error_message = "Error compiling shader " + name + ": " + log;
|
||||
glDeleteShader(shader);
|
||||
} else {
|
||||
result.success = true;
|
||||
result.shader_id = shader;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
GLuint Shader::CreateProgram(const std::vector<GLuint>& shaders) {
|
||||
GLuint program = glCreateProgram();
|
||||
for (auto s : shaders) {
|
||||
glAttachShader(program, s);
|
||||
}
|
||||
glLinkProgram(program);
|
||||
GLint status;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &status);
|
||||
if (status == GL_FALSE) {
|
||||
GLint length;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
|
||||
std::string log(length, ' ');
|
||||
glGetProgramInfoLog(program, length, &length, log.data());
|
||||
std::cerr << "Error linking program: " << log << "\n";
|
||||
glDeleteProgram(program);
|
||||
return 0;
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
int Shader::GetUniformLocation(const std::string& name) {
|
||||
if (uniform_location_cache_.count(name)) {
|
||||
return uniform_location_cache_[name];
|
||||
}
|
||||
int location = glGetUniformLocation(program_id_, name.c_str());
|
||||
uniform_location_cache_[name] = location;
|
||||
return location;
|
||||
}
|
||||
|
||||
void Shader::PrintActiveUniforms() const {
|
||||
GLint count;
|
||||
glGetProgramiv(program_id_, GL_ACTIVE_UNIFORMS, &count);
|
||||
for (GLint i = 0; i < count; ++i) {
|
||||
char name[256];
|
||||
GLsizei length;
|
||||
GLint size;
|
||||
GLenum type;
|
||||
glGetActiveUniform(program_id_, i, sizeof(name), &length, &size, &type, name);
|
||||
std::cout << "Uniform #" << i << ": " << name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Shader::PrintActiveAttributes() const {
|
||||
GLint count;
|
||||
glGetProgramiv(program_id_, GL_ACTIVE_ATTRIBUTES, &count);
|
||||
for (GLint i = 0; i < count; ++i) {
|
||||
char name[256];
|
||||
GLsizei length;
|
||||
GLint size;
|
||||
GLenum type;
|
||||
glGetActiveAttrib(program_id_, i, sizeof(name), &length, &size, &type, name);
|
||||
std::cout << "Attribute #" << i << ": " << name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Shader::SetUniform(const std::string& name, const glm::vec2& value) {
|
||||
glUniform2fv(GetUniformLocation(name), 1, &value.x);
|
||||
}
|
||||
|
||||
void Shader::SetUniform(const std::string& name, const glm::vec3& value) {
|
||||
glUniform3fv(GetUniformLocation(name), 1, &value.x);
|
||||
}
|
||||
|
||||
void Shader::SetUniform(const std::string& name, const glm::vec4& value) {
|
||||
glUniform4fv(GetUniformLocation(name), 1, &value.x);
|
||||
}
|
||||
|
||||
void Shader::SetUniform(const std::string& name, const glm::mat3& value) {
|
||||
glUniformMatrix3fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
||||
}
|
||||
|
||||
void Shader::SetUniform(const std::string& name, const glm::mat4& value) {
|
||||
glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
||||
}
|
||||
110
src/Shader.h
Normal file
110
src/Shader.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include "glad.h"
|
||||
|
||||
// Modern shader class using C++23 features
|
||||
class Shader {
|
||||
public:
|
||||
// Constructor with path validation using std::filesystem (C++17)
|
||||
explicit Shader(const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& fragment_path);
|
||||
|
||||
// Constructor overload for geometry shaders
|
||||
Shader(const std::filesystem::path& vertex_path,
|
||||
const std::filesystem::path& geometry_path,
|
||||
const std::filesystem::path& fragment_path);
|
||||
|
||||
~Shader();
|
||||
|
||||
// Delete copy constructor and assignment (RAII)
|
||||
Shader(const Shader&) = delete;
|
||||
Shader& operator=(const Shader&) = delete;
|
||||
|
||||
// Move semantics
|
||||
Shader(Shader&& other) noexcept;
|
||||
Shader& operator=(Shader&& other) noexcept;
|
||||
|
||||
void Bind() const;
|
||||
void Unbind() const;
|
||||
|
||||
[[nodiscard]] bool IsValid() const { return program_id_ != 0; }
|
||||
|
||||
// Modern uniform setters using function templates and concepts (C++20)
|
||||
template<typename T>
|
||||
requires std::same_as<T, int>
|
||||
void SetUniform(const std::string& name, T value);
|
||||
|
||||
template<typename T>
|
||||
requires std::same_as<T, float>
|
||||
void SetUniform(const std::string& name, T value);
|
||||
|
||||
template<typename T>
|
||||
requires std::same_as<T, bool>
|
||||
void SetUniform(const std::string& name, T value);
|
||||
|
||||
void SetUniform(const std::string& name, const glm::vec2& value);
|
||||
void SetUniform(const std::string& name, const glm::vec3& value);
|
||||
void SetUniform(const std::string& name, const glm::vec4& value);
|
||||
void SetUniform(const std::string& name, const glm::mat3& value);
|
||||
void SetUniform(const std::string& name, const glm::mat4& value);
|
||||
|
||||
// Array uniforms
|
||||
void SetUniformArray(const std::string& name, const float* values, int count);
|
||||
void SetUniformArray(const std::string& name, const glm::vec3* values, int count);
|
||||
|
||||
// Utility functions using C++20 features
|
||||
[[nodiscard]] int GetUniformLocation(const std::string& name);
|
||||
[[nodiscard]] static std::optional<std::string> ReadFile(const std::filesystem::path& path);
|
||||
|
||||
// Debug information
|
||||
void PrintActiveUniforms() const;
|
||||
void PrintActiveAttributes() const;
|
||||
|
||||
private:
|
||||
GLuint program_id_ = 0;
|
||||
mutable std::unordered_map<std::string, GLint> uniform_location_cache_;
|
||||
|
||||
// Compilation and linking
|
||||
[[nodiscard]] static GLuint CompileShader(GLenum type, const std::string& source);
|
||||
[[nodiscard]] static GLuint CreateProgram(const std::vector<GLuint>& shaders);
|
||||
|
||||
// Error handling using std::expected-like pattern (manual implementation for pre-C++23)
|
||||
struct CompileResult {
|
||||
bool success;
|
||||
std::string error_message;
|
||||
GLuint shader_id = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] static CompileResult TryCompileShader(GLenum type, const std::string& source, const std::string& shader_name);
|
||||
static void CheckCompileErrors(GLuint shader, const std::string& type);
|
||||
static void CheckLinkErrors(GLuint program);
|
||||
|
||||
void Cleanup();
|
||||
};
|
||||
|
||||
// Template implementations
|
||||
template<typename T>
|
||||
requires std::same_as<T, int>
|
||||
void Shader::SetUniform(const std::string& name, T value) {
|
||||
glUniform1i(GetUniformLocation(name), value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires std::same_as<T, float>
|
||||
void Shader::SetUniform(const std::string& name, T value) {
|
||||
glUniform1f(GetUniformLocation(name), value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires std::same_as<T, bool>
|
||||
void Shader::SetUniform(const std::string& name, T value) {
|
||||
glUniform1i(GetUniformLocation(name), static_cast<int>(value));
|
||||
}
|
||||
110
src/SimpleTextureLoader.cpp
Normal file
110
src/SimpleTextureLoader.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include "SimpleTextureLoader.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
GLuint SimpleTextureLoader::LoadBMP(const std::string& filepath) {
|
||||
std::ifstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open BMP file: " << filepath << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
BMPHeader header;
|
||||
file.read(reinterpret_cast<char*>(&header), sizeof(BMPHeader));
|
||||
|
||||
// Check BMP signature
|
||||
if (header.signature[0] != 'B' || header.signature[1] != 'M') {
|
||||
std::cerr << "Not a valid BMP file: " << filepath << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Only support 24-bit BMP for simplicity
|
||||
if (header.bitsPerPixel != 24) {
|
||||
std::cerr << "Only 24-bit BMP files supported: " << filepath << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate image size
|
||||
int imageSize = header.width * header.height * 3;
|
||||
std::vector<unsigned char> imageData(imageSize);
|
||||
|
||||
// Seek to image data
|
||||
file.seekg(header.dataOffset);
|
||||
file.read(reinterpret_cast<char*>(imageData.data()), imageSize);
|
||||
file.close();
|
||||
|
||||
// BMP stores BGR, convert to RGB
|
||||
for (int i = 0; i < imageSize; i += 3) {
|
||||
std::swap(imageData[i], imageData[i + 2]);
|
||||
}
|
||||
|
||||
// Create OpenGL texture
|
||||
GLuint textureID;
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, header.width, header.height,
|
||||
0, GL_RGB, GL_UNSIGNED_BYTE, imageData.data());
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return textureID;
|
||||
}
|
||||
|
||||
GLuint SimpleTextureLoader::LoadTGA(const std::string& filepath) {
|
||||
std::ifstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open TGA file: " << filepath << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
TGAHeader header;
|
||||
file.read(reinterpret_cast<char*>(&header), sizeof(TGAHeader));
|
||||
|
||||
// Skip image ID if present
|
||||
if (header.idLength > 0) {
|
||||
file.seekg(header.idLength, std::ios::cur);
|
||||
}
|
||||
|
||||
// Only support uncompressed RGB/RGBA TGA
|
||||
if (header.imageType != 2) {
|
||||
std::cerr << "Only uncompressed TGA files supported: " << filepath << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int bytesPerPixel = header.bitsPerPixel / 8;
|
||||
int imageSize = header.width * header.height * bytesPerPixel;
|
||||
std::vector<unsigned char> imageData(imageSize);
|
||||
|
||||
file.read(reinterpret_cast<char*>(imageData.data()), imageSize);
|
||||
file.close();
|
||||
|
||||
// TGA stores BGR(A), convert to RGB(A)
|
||||
for (int i = 0; i < imageSize; i += bytesPerPixel) {
|
||||
std::swap(imageData[i], imageData[i + 2]);
|
||||
}
|
||||
|
||||
// Create OpenGL texture
|
||||
GLuint textureID;
|
||||
glGenTextures(1, &textureID);
|
||||
glBindTexture(GL_TEXTURE_2D, textureID);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
GLenum format = (bytesPerPixel == 4) ? GL_RGBA : GL_RGB;
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, format, header.width, header.height,
|
||||
0, format, GL_UNSIGNED_BYTE, imageData.data());
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return textureID;
|
||||
}
|
||||
43
src/SimpleTextureLoader.h
Normal file
43
src/SimpleTextureLoader.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <glad.h>
|
||||
#include <string>
|
||||
|
||||
class SimpleTextureLoader {
|
||||
public:
|
||||
static GLuint LoadBMP(const std::string& filepath);
|
||||
static GLuint LoadTGA(const std::string& filepath);
|
||||
|
||||
private:
|
||||
struct BMPHeader {
|
||||
char signature[2];
|
||||
uint32_t fileSize;
|
||||
uint32_t reserved;
|
||||
uint32_t dataOffset;
|
||||
uint32_t headerSize;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint16_t planes;
|
||||
uint16_t bitsPerPixel;
|
||||
uint32_t compression;
|
||||
uint32_t imageSize;
|
||||
uint32_t xPixelsPerMeter;
|
||||
uint32_t yPixelsPerMeter;
|
||||
uint32_t colorsUsed;
|
||||
uint32_t colorsImportant;
|
||||
};
|
||||
|
||||
struct TGAHeader {
|
||||
uint8_t idLength;
|
||||
uint8_t colorMapType;
|
||||
uint8_t imageType;
|
||||
uint16_t colorMapStart;
|
||||
uint16_t colorMapLength;
|
||||
uint8_t colorMapDepth;
|
||||
uint16_t xOffset;
|
||||
uint16_t yOffset;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint8_t bitsPerPixel;
|
||||
uint8_t imageDescriptor;
|
||||
};
|
||||
};
|
||||
87
src/ThreadPool.h
Normal file
87
src/ThreadPool.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <type_traits> // for std::invoke_result_t
|
||||
|
||||
class ThreadPool {
|
||||
public:
|
||||
// Construct a thread pool with the given number of threads (defaults to hardware concurrency)
|
||||
explicit ThreadPool(size_t numThreads = std::thread::hardware_concurrency())
|
||||
: stopFlag(false)
|
||||
{
|
||||
for (size_t i = 0; i < numThreads; ++i) {
|
||||
workers.emplace_back([this] {
|
||||
for (;;) {
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
condition.wait(lock, [this] {
|
||||
return stopFlag.load() || !tasks.empty();
|
||||
});
|
||||
if (stopFlag.load() && tasks.empty())
|
||||
return;
|
||||
task = std::move(tasks.front());
|
||||
tasks.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Destructor shuts down the pool and joins all threads
|
||||
~ThreadPool() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
// Enqueue a task into the thread pool. Returns a future for the result.
|
||||
template <class F, class... Args>
|
||||
auto enqueue(F&& f, Args&&... args)
|
||||
-> std::future<std::invoke_result_t<F, Args...>>
|
||||
{
|
||||
using return_type = std::invoke_result_t<F, Args...>;
|
||||
|
||||
// Wrap the function and its arguments into a packaged_task
|
||||
auto taskPtr = std::make_shared<std::packaged_task<return_type()>>(
|
||||
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
|
||||
);
|
||||
|
||||
std::future<return_type> result = taskPtr->get_future();
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
if (stopFlag.load())
|
||||
throw std::runtime_error("enqueue on stopped ThreadPool");
|
||||
|
||||
tasks.emplace([taskPtr]() { (*taskPtr)(); });
|
||||
}
|
||||
condition.notify_one();
|
||||
return result;
|
||||
}
|
||||
|
||||
// Signal all threads to shut down and join them
|
||||
void shutdown() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex);
|
||||
stopFlag.store(true);
|
||||
}
|
||||
condition.notify_all();
|
||||
for (std::thread &worker : workers) {
|
||||
if (worker.joinable())
|
||||
worker.join();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::thread> workers; // worker threads
|
||||
std::queue<std::function<void()>> tasks; // task queue
|
||||
std::mutex queueMutex; // protects tasks
|
||||
std::condition_variable condition; // signals availability
|
||||
std::atomic<bool> stopFlag; // indicates shutdown
|
||||
};
|
||||
106
src/Window.cpp
Normal file
106
src/Window.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// src/Window.cpp
|
||||
|
||||
#include "Window.h"
|
||||
#include "glad.h"
|
||||
#include <iostream>
|
||||
|
||||
// Static storage for user callbacks
|
||||
static std::function<void(int, int)> g_resizeCallback;
|
||||
static std::function<void(int, int, int, int)> g_keyCallback;
|
||||
|
||||
Window::Window(int width, int height, const std::string& title)
|
||||
: width_(width), height_(height), title_(title) {
|
||||
Initialize();
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
// Move constructor
|
||||
Window::Window(Window&& other) noexcept
|
||||
: window_(other.window_), width_(other.width_), height_(other.height_), title_(std::move(other.title_)) {
|
||||
other.window_ = nullptr;
|
||||
}
|
||||
|
||||
// Move assignment
|
||||
Window& Window::operator=(Window&& other) noexcept {
|
||||
if (this != &other) {
|
||||
Cleanup();
|
||||
window_ = other.window_;
|
||||
width_ = other.width_;
|
||||
height_ = other.height_;
|
||||
title_ = std::move(other.title_);
|
||||
other.window_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Window::Initialize() {
|
||||
if (!glfwInit()) {
|
||||
std::cerr << "Failed to initialize GLFW\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create window with OpenGL context
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
|
||||
window_ = glfwCreateWindow(width_, height_, title_.c_str(), nullptr, nullptr);
|
||||
if (!window_) {
|
||||
std::cerr << "Failed to create GLFW window\n";
|
||||
glfwTerminate();
|
||||
return;
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent(window_);
|
||||
glfwSetFramebufferSizeCallback(window_, FramebufferSizeCallback);
|
||||
glfwSetKeyCallback(window_, KeyCallback);
|
||||
}
|
||||
|
||||
// Cleans up GLFW window and terminates GLFW if needed
|
||||
void Window::Cleanup() {
|
||||
if (window_) {
|
||||
glfwDestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
void Window::PollEvents() const {
|
||||
glfwPollEvents();
|
||||
}
|
||||
|
||||
bool Window::ShouldClose() const {
|
||||
return glfwWindowShouldClose(window_);
|
||||
}
|
||||
|
||||
void Window::SwapBuffers() const {
|
||||
glfwSwapBuffers(window_);
|
||||
}
|
||||
|
||||
void Window::SetVSync(bool enable) const {
|
||||
glfwSwapInterval(enable ? 1 : 0);
|
||||
}
|
||||
|
||||
void Window::SetResizeCallback(std::function<void(int, int)> callback) {
|
||||
g_resizeCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void Window::SetKeyCallback(std::function<void(int, int, int, int)> callback) {
|
||||
g_keyCallback = std::move(callback);
|
||||
}
|
||||
|
||||
// Static GLFW callbacks
|
||||
void Window::FramebufferSizeCallback(GLFWwindow* window, int width, int height) {
|
||||
if (g_resizeCallback) {
|
||||
g_resizeCallback(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
|
||||
if (g_keyCallback) {
|
||||
g_keyCallback(key, scancode, action, mods);
|
||||
}
|
||||
}
|
||||
48
src/Window.h
Normal file
48
src/Window.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
class Window {
|
||||
public:
|
||||
Window(int width, int height, const std::string& title);
|
||||
~Window();
|
||||
|
||||
// Delete copy constructor and assignment operator (RAII principle)
|
||||
Window(const Window&) = delete;
|
||||
Window& operator=(const Window&) = delete;
|
||||
|
||||
// Move semantics (C++11 and later)
|
||||
Window(Window&& other) noexcept;
|
||||
Window& operator=(Window&& other) noexcept;
|
||||
|
||||
void PollEvents() const;
|
||||
bool ShouldClose() const;
|
||||
void SwapBuffers() const;
|
||||
void SetVSync(bool enable) const;
|
||||
|
||||
GLFWwindow* Handle() const { return window_; }
|
||||
|
||||
// Modern C++ getters using [[nodiscard]] (C++17)
|
||||
[[nodiscard]] int GetWidth() const { return width_; }
|
||||
[[nodiscard]] int GetHeight() const { return height_; }
|
||||
[[nodiscard]] float GetAspectRatio() const { return static_cast<float>(width_) / static_cast<float>(height_); }
|
||||
|
||||
// Callback setters using std::function (more flexible than raw function pointers)
|
||||
void SetResizeCallback(std::function<void(int, int)> callback);
|
||||
void SetKeyCallback(std::function<void(int, int, int, int)> callback);
|
||||
|
||||
private:
|
||||
GLFWwindow* window_;
|
||||
int width_, height_;
|
||||
std::string title_;
|
||||
|
||||
// Static callbacks for GLFW
|
||||
static void FramebufferSizeCallback(GLFWwindow* window, int width, int height);
|
||||
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
|
||||
void Initialize();
|
||||
void Cleanup();
|
||||
};
|
||||
2532
src/glad.c
Normal file
2532
src/glad.c
Normal file
File diff suppressed because it is too large
Load Diff
509
src/main.cpp
Normal file
509
src/main.cpp
Normal file
@@ -0,0 +1,509 @@
|
||||
#include "Engine.h"
|
||||
#include "LevelEditor.cpp"
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <deque>
|
||||
#include <chrono>
|
||||
|
||||
|
||||
// ---------------------------------------------
|
||||
// SceneDemo: existing complex scene demo
|
||||
// ---------------------------------------------
|
||||
class SceneDemo : public Engine {
|
||||
private:
|
||||
float time_ = 0.0f;
|
||||
std::vector<std::shared_ptr<GameObject>> rotating_cubes_;
|
||||
std::vector<std::shared_ptr<GameObject>> floating_spheres_;
|
||||
std::shared_ptr<GameObject> central_sphere_;
|
||||
std::shared_ptr<GameObject> ground_plane_;
|
||||
std::shared_ptr<GameObject> grid_;
|
||||
|
||||
std::mt19937 rng_;
|
||||
std::uniform_real_distribution<float> color_dist_{0.3f, 1.0f};
|
||||
|
||||
public:
|
||||
SceneDemo()
|
||||
: Engine(1920, 1080, "Modern C++ Engine - Complex Scene Demo"),
|
||||
rng_(std::random_device{}()) {}
|
||||
|
||||
bool OnInitialize() override {
|
||||
std::cout << "Creating complex scene with primitives..." << std::endl;
|
||||
|
||||
auto grid_primitive = CreateGrid(25, 1.0f, glm::vec3(0.4f));
|
||||
grid_ = CreateGameObject(grid_primitive, glm::vec3(0,0,0));
|
||||
AddGameObject(grid_);
|
||||
|
||||
auto plane_primitive = CreatePlane(30.0f, 30.0f, glm::vec3(0.2f,0.2f,0.3f));
|
||||
ground_plane_ = CreateGameObject(plane_primitive, glm::vec3(0,-0.01f,0));
|
||||
AddGameObject(ground_plane_);
|
||||
|
||||
auto central_sphere_primitive = CreateSphere(64, glm::vec3(0.9f,0.3f,0.1f));
|
||||
central_sphere_ = CreateGameObject(central_sphere_primitive, glm::vec3(0,3,0));
|
||||
central_sphere_->SetScale(glm::vec3(2.0f));
|
||||
AddGameObject(central_sphere_);
|
||||
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
glm::vec3 color{color_dist_(rng_), color_dist_(rng_), color_dist_(rng_)};
|
||||
auto cube = CreateGameObject(CreateCube(color), glm::vec3(0.0f));
|
||||
rotating_cubes_.push_back(cube);
|
||||
AddGameObject(cube);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 15; ++i) {
|
||||
glm::vec3 color{color_dist_(rng_), color_dist_(rng_), color_dist_(rng_)};
|
||||
float x = (rng_() % 20) - 10.0f;
|
||||
float z = (rng_() % 20) - 10.0f;
|
||||
float y = 1.0f + (rng_() % 5);
|
||||
auto sphere = CreateGameObject(CreateSphere(32, color),
|
||||
glm::vec3(x,y,z));
|
||||
sphere->SetScale(glm::vec3(0.5f + (rng_()%10)*0.1f));
|
||||
floating_spheres_.push_back(sphere);
|
||||
AddGameObject(sphere);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
glm::vec3 color{1.0f - i*0.15f, 0.2f + i*0.15f, 0.5f};
|
||||
auto cube = CreateGameObject(CreateCube(color),
|
||||
glm::vec3(-8, i*2.1f+1, 8));
|
||||
cube->SetScale(glm::vec3(0.8f + i*0.1f));
|
||||
AddGameObject(cube);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
float hue = i/10.0f;
|
||||
glm::vec3 color{hue, 1.0f-hue, 0.5f};
|
||||
auto sphere = CreateGameObject(CreateSphere(24, color),
|
||||
glm::vec3(i*2-9,1,-10));
|
||||
sphere->SetScale(glm::vec3(0.3f));
|
||||
AddGameObject(sphere);
|
||||
}
|
||||
|
||||
std::cout << "Scene created with " << game_objects_.size() << " objects!" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnUpdate(float dt) override {
|
||||
time_ += dt;
|
||||
if (central_sphere_)
|
||||
central_sphere_->SetRotation(glm::vec3(0, time_*30, 0));
|
||||
|
||||
for (size_t i = 0; i < rotating_cubes_.size(); ++i) {
|
||||
float angle = time_*60.0f + i*(360.0f/rotating_cubes_.size());
|
||||
float radius=8.0f, height=2.0f+std::sin(time_*2.0f+i)*1.5f;
|
||||
glm::vec3 pos{std::cos(glm::radians(angle))*radius,
|
||||
height,
|
||||
std::sin(glm::radians(angle))*radius};
|
||||
rotating_cubes_[i]->SetPosition(pos);
|
||||
rotating_cubes_[i]->SetRotation(glm::vec3(angle,angle*1.5f,0));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < floating_spheres_.size(); ++i) {
|
||||
auto sphere = floating_spheres_[i];
|
||||
glm::vec3 p = sphere->GetPosition();
|
||||
float bob = std::sin(time_*3.0f + i*0.5f)*0.3f;
|
||||
sphere->SetPosition(glm::vec3(p.x, p.y+bob, p.z));
|
||||
sphere->SetRotation(glm::vec3(0, time_*20.0f + i*30.0f, 0));
|
||||
}
|
||||
|
||||
float camAng = time_*15.0f;
|
||||
SetCameraOrbit(20.0f, camAng, 12.0f);
|
||||
SetCameraTarget(glm::vec3(0,2,0));
|
||||
|
||||
static float status_timer=0.0f;
|
||||
status_timer += dt;
|
||||
if (status_timer >= 3.0f) {
|
||||
std::cout<<"Scene running: "<<int(time_)<<"s | "
|
||||
<<game_objects_.size()<<" objects | Cam angle: "
|
||||
<<int(camAng)%360<<"°"<<std::endl;
|
||||
status_timer=0.0f;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------
|
||||
// StressTest: incremental spawn until quit
|
||||
// ---------------------------------------------
|
||||
class StressTest : public Engine {
|
||||
public:
|
||||
StressTest() : Engine(1920, 1080, "Zoom Stresser") {}
|
||||
|
||||
|
||||
bool OnInitialize() override {
|
||||
last_report_ = std::chrono::high_resolution_clock::now();
|
||||
|
||||
// Add a ground plane for visual reference
|
||||
auto plane = CreateGameObject(CreatePlane(50.0f, 50.0f, glm::vec3(0.2f, 0.3f, 0.5f)),
|
||||
glm::vec3(0, -1, 0));
|
||||
AddGameObject(plane);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void OnUpdate(float dt) override {
|
||||
time_ += dt;
|
||||
|
||||
// Spawn new objects every second
|
||||
if (game_objects_.size() < 10000) {
|
||||
spawn_timer_ += dt;
|
||||
if (spawn_timer_ >= 1.0f) {
|
||||
spawn_timer_ = 0.0f;
|
||||
AddRow();
|
||||
}
|
||||
}
|
||||
|
||||
// Animate existing objects
|
||||
AnimateObjects();
|
||||
|
||||
// Orbit camera around the scene
|
||||
float camera_angle = time_ * 20.0f; // 20 degrees per second
|
||||
float radius = 30.0f + std::sin(time_ * 0.5f) * 10.0f; // Varying radius
|
||||
float height = 15.0f + std::cos(time_ * 0.3f) * 5.0f; // Varying height
|
||||
SetCameraOrbit(radius, camera_angle, height);
|
||||
SetCameraTarget(glm::vec3(0, 5, 0));
|
||||
|
||||
// Report performance every 2 seconds
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
float elapsed = std::chrono::duration<float>(now - last_report_).count();
|
||||
if (elapsed >= 2.0f) {
|
||||
float fps = 1.0f/dt;
|
||||
std::cout << "Objects: " << game_objects_.size()
|
||||
<< " | FPS: " << fps
|
||||
<< " | Cam angle: " << int(camera_angle) % 360 << "°" << std::endl;
|
||||
last_report_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
void AddRow() {
|
||||
int row = int(stress_objects_.size()) / columns_;
|
||||
for (int i = 0; i < columns_; ++i) {
|
||||
// Colorful gradient based on position
|
||||
float hue = (i + row * columns_) / 100.0f;
|
||||
glm::vec3 color{
|
||||
0.5f + 0.5f * std::sin(hue * 6.28f),
|
||||
0.5f + 0.5f * std::sin(hue * 6.28f + 2.09f),
|
||||
0.5f + 0.5f * std::sin(hue * 6.28f + 4.19f)
|
||||
};
|
||||
|
||||
glm::vec3 pos{
|
||||
i * spacing_ - columns_ * spacing_ / 2.0f,
|
||||
0.5f + row * spacing_,
|
||||
-20.0f + row * 2.0f // Spread them out in depth too
|
||||
};
|
||||
|
||||
// Alternate between cubes and spheres
|
||||
std::shared_ptr<GameObject> obj;
|
||||
if ((i + row) % 2 == 0) {
|
||||
obj = CreateGameObject(CreateCube(color), pos);
|
||||
} else {
|
||||
obj = CreateGameObject(CreateSphere(16, color), pos);
|
||||
}
|
||||
|
||||
obj->SetScale(glm::vec3(0.4f + (i % 5) * 0.1f)); // Varying sizes
|
||||
stress_objects_.push_back(obj);
|
||||
AddGameObject(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimateObjects() {
|
||||
for (size_t i = 0; i < stress_objects_.size(); ++i) {
|
||||
auto& obj = stress_objects_[i];
|
||||
glm::vec3 pos = obj->GetPosition();
|
||||
|
||||
// Floating/bobbing motion
|
||||
float bob_speed = 2.0f + (i % 10) * 0.2f;
|
||||
float bob_height = 0.3f + (i % 7) * 0.1f;
|
||||
float bob = std::sin(time_ * bob_speed + i * 0.1f) * bob_height;
|
||||
obj->SetPosition(glm::vec3(pos.x, pos.y + bob, pos.z));
|
||||
|
||||
// Rotation animation
|
||||
float rot_speed = 30.0f + (i % 13) * 10.0f;
|
||||
glm::vec3 rotation{
|
||||
time_ * rot_speed + i * 10.0f,
|
||||
time_ * (rot_speed * 0.7f) + i * 15.0f,
|
||||
time_ * (rot_speed * 0.4f) + i * 20.0f
|
||||
};
|
||||
obj->SetRotation(rotation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float time_{0.0f};
|
||||
float spawn_timer_{0.0f};
|
||||
std::chrono::high_resolution_clock::time_point last_report_;
|
||||
std::vector<std::shared_ptr<GameObject>> stress_objects_;
|
||||
static constexpr int columns_{500}; // Reduced for better performance
|
||||
static constexpr float spacing_{1.0f};
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------------------
|
||||
// SpinningMonkey: Loads OBJ monkey and spins it
|
||||
// ---------------------------------------------
|
||||
class SpinningMonkey : public Engine {
|
||||
private:
|
||||
float time_ = 0.0f;
|
||||
std::shared_ptr<GameObject> monkey_;
|
||||
std::shared_ptr<GameObject> ground_plane_;
|
||||
std::shared_ptr<GameObject> grid_;
|
||||
|
||||
// Animation parameters
|
||||
float spin_speed_ = 45.0f; // degrees per second
|
||||
float bob_height_ = 2.0f; // vertical bobbing range
|
||||
float bob_speed_ = 1.5f; // bobbing frequency
|
||||
float scale_pulse_ = 0.2f; // scale pulsing amount
|
||||
|
||||
public:
|
||||
SpinningMonkey()
|
||||
: Engine(1920, 1080, "Spinning Monkey Demo - OBJ Model Loader") {}
|
||||
|
||||
bool OnInitialize() override {
|
||||
std::cout << "Loading Suzanne (Blender Monkey) model..." << std::endl;
|
||||
|
||||
// Create ground reference
|
||||
auto grid_primitive = CreateGrid(15, 1.0f, glm::vec3(0.3f, 0.3f, 0.4f));
|
||||
grid_ = CreateGameObject(grid_primitive, glm::vec3(0, 0, 0));
|
||||
AddGameObject(grid_);
|
||||
|
||||
auto plane_primitive = CreatePlane(20.0f, 20.0f, glm::vec3(0.15f, 0.25f, 0.35f));
|
||||
ground_plane_ = CreateGameObject(plane_primitive, glm::vec3(0, -0.01f, 0));
|
||||
AddGameObject(ground_plane_);
|
||||
|
||||
// Try to load monkey model (with optional texture)
|
||||
try {
|
||||
// First try with texture
|
||||
auto monkey_primitive = CreateOBJModel("monkey.obj", "monkey.jpg");
|
||||
if (monkey_primitive) {
|
||||
monkey_ = CreateGameObject(monkey_primitive, glm::vec3(0, 3, 0));
|
||||
monkey_->SetScale(glm::vec3(2.0f));
|
||||
AddGameObject(monkey_);
|
||||
std::cout << "✓ Loaded monkey with texture!" << std::endl;
|
||||
}
|
||||
} catch (...) {
|
||||
std::cout << "! Texture loading failed, trying without texture..." << std::endl;
|
||||
try {
|
||||
// Fallback: load without texture
|
||||
auto monkey_primitive = CreateOBJModel("monkey.obj");
|
||||
if (monkey_primitive) {
|
||||
monkey_ = CreateGameObject(monkey_primitive, glm::vec3(0, 3, 0));
|
||||
monkey_->SetScale(glm::vec3(2.0f));
|
||||
AddGameObject(monkey_);
|
||||
std::cout << "✓ Loaded monkey without texture!" << std::endl;
|
||||
}
|
||||
} catch (...) {
|
||||
std::cout << "✗ Failed to load monkey.obj, using fallback cube..." << std::endl;
|
||||
// Ultimate fallback: colored cube
|
||||
auto fallback_primitive = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f));
|
||||
monkey_ = CreateGameObject(fallback_primitive, glm::vec3(0, 3, 0));
|
||||
monkey_->SetScale(glm::vec3(2.0f));
|
||||
AddGameObject(monkey_);
|
||||
}
|
||||
}
|
||||
|
||||
// Set initial camera position
|
||||
SetCameraPosition(glm::vec3(8, 6, 8));
|
||||
SetCameraTarget(glm::vec3(0, 3, 0));
|
||||
|
||||
std::cout << "Scene initialized with " << game_objects_.size() << " objects!" << std::endl;
|
||||
std::cout << "Controls:" << std::endl;
|
||||
std::cout << " - Camera orbits automatically" << std::endl;
|
||||
std::cout << " - Monkey spins and bobs up and down" << std::endl;
|
||||
std::cout << " - Scale pulses with time" << std::endl;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnUpdate(float dt) override {
|
||||
time_ += dt;
|
||||
|
||||
float spin_y = time_ * spin_speed_;
|
||||
|
||||
if (monkey_) {
|
||||
// Spinning animation
|
||||
|
||||
float spin_x = std::sin(time_ * 0.3f) * 15.0f; // Gentle x-axis wobble
|
||||
float spin_z = std::cos(time_ * 0.7f) * 10.0f; // Gentle z-axis wobble
|
||||
monkey_->SetRotation(glm::vec3(spin_x, spin_y, spin_z));
|
||||
|
||||
// Vertical bobbing
|
||||
float base_height = 3.0f;
|
||||
float bob_offset = std::sin(time_ * bob_speed_) * bob_height_;
|
||||
monkey_->SetPosition(glm::vec3(0, base_height + bob_offset, 0));
|
||||
|
||||
// Scale pulsing
|
||||
float base_scale = 2.0f;
|
||||
float scale_offset = std::sin(time_ * 2.0f) * scale_pulse_;
|
||||
float current_scale = base_scale + scale_offset;
|
||||
monkey_->SetScale(glm::vec3(current_scale));
|
||||
}
|
||||
|
||||
// Orbiting camera
|
||||
float camera_angle = time_ * 25.0f; // 25 degrees per second
|
||||
float camera_radius = 10.0f + std::sin(time_ * 0.8f) * 3.0f; // Varying distance
|
||||
float camera_height = 6.0f + std::cos(time_ * 0.5f) * 2.0f; // Varying height
|
||||
|
||||
SetCameraOrbit(camera_radius, camera_angle, camera_height);
|
||||
SetCameraTarget(glm::vec3(0, 3, 0)); // Always look at monkey
|
||||
|
||||
// Status updates every 5 seconds
|
||||
static float status_timer = 0.0f;
|
||||
status_timer += dt;
|
||||
if (status_timer >= 5.0f) {
|
||||
std::cout << "🐵 Monkey spinning: " << int(time_) << "s | "
|
||||
<< "Rotation: " << int(spin_y) % 360 << "° | "
|
||||
<< "Camera: " << int(camera_angle) % 360 << "°" << std::endl;
|
||||
status_timer = 0.0f;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------
|
||||
// MonkeyGridDemo: 50 jiggling monkeys on grid
|
||||
// ---------------------------------------------
|
||||
class MonkeyGridDemo : public Engine {
|
||||
private:
|
||||
float time_ = 0.0f;
|
||||
std::vector<std::shared_ptr<GameObject>> monkeys_;
|
||||
std::shared_ptr<GameObject> ground_plane_;
|
||||
std::shared_ptr<GameObject> grid_;
|
||||
|
||||
static constexpr int MONKEY_COUNT = 50;
|
||||
static constexpr int GRID_COLS = 7;
|
||||
static constexpr int GRID_ROWS = 8;
|
||||
static constexpr float SPACING = 4.0f;
|
||||
std::mt19937 rng_{ std::random_device{}() };
|
||||
std::uniform_real_distribution<float> jiggle_dist_{ -0.1f, 0.1f };
|
||||
|
||||
public:
|
||||
MonkeyGridDemo()
|
||||
: Engine(1920, 1080, "Monkey Grid Demo") {}
|
||||
|
||||
bool OnInitialize() override {
|
||||
std::cout << "Spawning " << MONKEY_COUNT << " miniature monkeys on grid..." << std::endl;
|
||||
|
||||
// Grid and plane for reference
|
||||
auto grid_primitive = CreateGrid(15, 1.0f, glm::vec3(0.3f));
|
||||
grid_ = CreateGameObject(grid_primitive, glm::vec3(0, 0, 0));
|
||||
AddGameObject(grid_);
|
||||
|
||||
auto plane_primitive = CreatePlane(60.0f, 60.0f, glm::vec3(0.2f, 0.25f, 0.3f));
|
||||
ground_plane_ = CreateGameObject(plane_primitive, glm::vec3(0, -0.01f, 0));
|
||||
AddGameObject(ground_plane_);
|
||||
|
||||
// Load OBJ monkey primitive once
|
||||
std::shared_ptr<Primitive> monkey_primitive;
|
||||
try {
|
||||
monkey_primitive = CreateOBJModel("monkey.obj", "monkey.jpg");
|
||||
if (!monkey_primitive)
|
||||
monkey_primitive = CreateOBJModel("monkey.obj");
|
||||
} catch (...) {
|
||||
monkey_primitive = CreateCube(glm::vec3(1.0f, 0.5f, 0.2f));
|
||||
}
|
||||
|
||||
// Spawn monkeys on GRID_COLS×GRID_ROWS grid
|
||||
int spawned = 0;
|
||||
for (int r = 0; r < GRID_ROWS && spawned < MONKEY_COUNT; ++r) {
|
||||
for (int c = 0; c < GRID_COLS && spawned < MONKEY_COUNT; ++c) {
|
||||
float x = (c - (GRID_COLS - 1) * 0.5f) * SPACING;
|
||||
float z = (r - (GRID_ROWS - 1) * 0.5f) * SPACING;
|
||||
auto monkey = CreateGameObject(monkey_primitive, glm::vec3(x, 1.0f, z));
|
||||
monkey->SetScale(glm::vec3(0.5f)); // shrink to half size
|
||||
monkeys_.push_back(monkey);
|
||||
AddGameObject(monkey);
|
||||
++spawned;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup camera
|
||||
SetCameraOrbit(50.0f, 45.0f, 30.0f);
|
||||
SetCameraTarget(glm::vec3(0, 1.0f, 0));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnUpdate(float dt) override {
|
||||
time_ += dt;
|
||||
|
||||
// Jiggle and slight bob for each monkey
|
||||
for (auto& monkey : monkeys_) {
|
||||
glm::vec3 basePos = monkey->GetPosition();
|
||||
float jigX = jiggle_dist_(rng_);
|
||||
float jigZ = jiggle_dist_(rng_);
|
||||
float bobY = std::sin(time_ * 3.0f + basePos.x + basePos.z) * 0.2f;
|
||||
|
||||
monkey->SetPosition(glm::vec3(
|
||||
basePos.x + jigX,
|
||||
1.0f + bobY,
|
||||
basePos.z + jigZ
|
||||
));
|
||||
|
||||
// Optional subtle rotation
|
||||
float angle = std::sin(time_ * 2.0f + basePos.x - basePos.z) * 15.0f;
|
||||
monkey->SetRotation(glm::vec3(0, angle, 0));
|
||||
}
|
||||
|
||||
// Slow orbiting camera
|
||||
float camAng = time_ * 10.0f;
|
||||
SetCameraOrbit(50.0f, camAng, 30.0f);
|
||||
SetCameraTarget(glm::vec3(0, 1.0f, 0));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ---------------------------------------------
|
||||
// main: choose demo, stress, or monkey by arg
|
||||
// ---------------------------------------------
|
||||
int main(int argc, char** argv) {
|
||||
try {
|
||||
std::string mode = (argc > 1) ? std::string(argv[1]) : "demo";
|
||||
|
||||
if (mode == "stress") {
|
||||
std::cout << "Running Stress Test..." << std::endl;
|
||||
StressTest demo;
|
||||
demo.Run();
|
||||
return 0;
|
||||
|
||||
}
|
||||
else if (mode == "monkeygrid") {
|
||||
std::cout << "Starting Monkey Grid Demo..." << std::endl;
|
||||
MonkeyGridDemo demo;
|
||||
demo.Run();
|
||||
return 0;
|
||||
}
|
||||
else if (mode == "edit") {
|
||||
std::cout << "Starting Zoom Level Editor ..." << std::endl;
|
||||
LevelEditor edit;
|
||||
edit.Run();
|
||||
return 0;
|
||||
}
|
||||
else if (mode == "monkey") {
|
||||
std::cout << "🐵 Starting Spinning Monkey Demo..." << std::endl;
|
||||
std::cout << "====================================" << std::endl;
|
||||
std::cout << "Place your monkey.obj in models/ folder" << std::endl;
|
||||
std::cout << "Optionally add monkey.bmp in textures/ folder" << std::endl;
|
||||
std::cout << "====================================" << std::endl;
|
||||
SpinningMonkey demo;
|
||||
demo.Run();
|
||||
std::cout << "====================================" << std::endl;
|
||||
std::cout << "Monkey demo completed successfully! 🐵" << std::endl;
|
||||
return 0;
|
||||
} else {
|
||||
std::cout << "Starting Modern C++ Engine Scene Demo..." << std::endl;
|
||||
std::cout << "=========================================" << std::endl;
|
||||
SceneDemo demo;
|
||||
demo.Run();
|
||||
std::cout << "=========================================" << std::endl;
|
||||
std::cout << "Demo completed successfully!" << std::endl;
|
||||
return 0;
|
||||
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user