[Improve file explorer & implement Assimp with lighting/material support]

- Reworked file explorer UI for clearer hierarchy and usability
- Actually wired up Assimp properly for model loading
- Added basic lighting support
- Added independent material support per mesh
(oh my gosh this took 4 days to actually get working, let alone not crashing 😭)
This commit is contained in:
Anemunt
2025-12-09 03:09:37 -05:00
parent 9f7007f496
commit 9adb1ff2f5
14 changed files with 1725 additions and 200 deletions

View File

@@ -101,6 +101,9 @@ FileCategory FileBrowser::getFileCategory(const fs::directory_entry& entry) cons
return FileCategory::Model;
}
// Material files
if (ext == ".mat") return FileCategory::Material;
// Texture files
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" ||
ext == ".tga" || ext == ".dds" || ext == ".hdr") {
@@ -139,6 +142,7 @@ const char* FileBrowser::getFileIcon(const fs::directory_entry& entry) const {
case FileCategory::Folder: return "folder";
case FileCategory::Scene: return "scene";
case FileCategory::Model: return "model";
case FileCategory::Material: return "material";
case FileCategory::Texture: return "image";
case FileCategory::Shader: return "shader";
case FileCategory::Script: return "code";

View File

@@ -11,6 +11,7 @@ enum class FileCategory {
Folder,
Scene,
Model,
Material,
Texture,
Shader,
Script,

View File

@@ -1,5 +1,7 @@
#include "Engine.h"
#include "ModelLoader.h"
#include <iostream>
#include <fstream>
void window_size_callback(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
@@ -109,13 +111,8 @@ void Engine::run() {
if (aspect <= 0.0f) aspect = 1.0f;
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
renderer.beginRender(view, proj);
for (const auto& obj : sceneObjects) {
renderer.renderObject(obj);
}
renderer.renderSkybox(view, proj);
renderer.beginRender(view, proj, camera.position);
renderer.renderScene(camera, sceneObjects);
renderer.endRender();
}
@@ -229,6 +226,108 @@ void Engine::importOBJToScene(const std::string& filepath, const std::string& ob
}
}
void Engine::importModelToScene(const std::string& filepath, const std::string& objectName) {
auto& modelLoader = getModelLoader();
ModelLoadResult result = modelLoader.loadModel(filepath);
if (!result.success) {
addConsoleMessage("Failed to load model: " + result.errorMessage, ConsoleMessageType::Error);
return;
}
int id = nextObjectId++;
std::string name = objectName.empty() ? fs::path(filepath).stem().string() : objectName;
SceneObject obj(name, ObjectType::Model, id);
obj.meshPath = filepath;
obj.meshId = result.meshIndex;
sceneObjects.push_back(obj);
selectedObjectId = id;
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
}
addConsoleMessage(
"Imported model: " + name + " (" +
std::to_string(result.vertexCount) + " verts, " +
std::to_string(result.faceCount) + " faces, " +
std::to_string(result.meshCount) + " meshes)",
ConsoleMessageType::Success
);
}
void Engine::loadMaterialFromFile(SceneObject& obj) {
if (obj.materialPath.empty()) return;
try {
std::ifstream f(obj.materialPath);
if (!f.is_open()) {
addConsoleMessage("Failed to open material: " + obj.materialPath, ConsoleMessageType::Error);
return;
}
std::string line;
while (std::getline(f, line)) {
line.erase(0, line.find_first_not_of(" \t\r\n"));
if (line.empty() || line[0] == '#') continue;
auto pos = line.find('=');
if (pos == std::string::npos) continue;
std::string key = line.substr(0, pos);
std::string val = line.substr(pos + 1);
if (key == "color") {
sscanf(val.c_str(), "%f,%f,%f", &obj.material.color.r, &obj.material.color.g, &obj.material.color.b);
} else if (key == "ambient") {
obj.material.ambientStrength = std::stof(val);
} else if (key == "specular") {
obj.material.specularStrength = std::stof(val);
} else if (key == "shininess") {
obj.material.shininess = std::stof(val);
} else if (key == "textureMix") {
obj.material.textureMix = std::stof(val);
} else if (key == "albedo") {
obj.albedoTexturePath = val;
} else if (key == "overlay") {
obj.overlayTexturePath = val;
} else if (key == "normal") {
obj.normalMapPath = val;
} else if (key == "useOverlay") {
obj.useOverlay = std::stoi(val) != 0;
}
}
addConsoleMessage("Applied material: " + obj.materialPath, ConsoleMessageType::Success);
projectManager.currentProject.hasUnsavedChanges = true;
} catch (...) {
addConsoleMessage("Failed to read material: " + obj.materialPath, ConsoleMessageType::Error);
}
}
void Engine::saveMaterialToFile(const SceneObject& obj) {
if (obj.materialPath.empty()) {
addConsoleMessage("Material path is empty", ConsoleMessageType::Warning);
return;
}
try {
std::ofstream f(obj.materialPath);
if (!f.is_open()) {
addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error);
return;
}
f << "# Material\n";
f << "color=" << obj.material.color.r << "," << obj.material.color.g << "," << obj.material.color.b << "\n";
f << "ambient=" << obj.material.ambientStrength << "\n";
f << "specular=" << obj.material.specularStrength << "\n";
f << "shininess=" << obj.material.shininess << "\n";
f << "textureMix=" << obj.material.textureMix << "\n";
f << "useOverlay=" << (obj.useOverlay ? 1 : 0) << "\n";
f << "albedo=" << obj.albedoTexturePath << "\n";
f << "overlay=" << obj.overlayTexturePath << "\n";
f << "normal=" << obj.normalMapPath << "\n";
addConsoleMessage("Saved material: " + obj.materialPath, ConsoleMessageType::Success);
} catch (...) {
addConsoleMessage("Failed to save material: " + obj.materialPath, ConsoleMessageType::Error);
}
}
void Engine::handleKeyboardShortcuts() {
static bool f11Pressed = false;
if (glfwGetKey(editorWindow, GLFW_KEY_F11) == GLFW_PRESS && !f11Pressed) {
@@ -375,6 +474,12 @@ void Engine::loadScene(const std::string& sceneName) {
projectManager.currentProject.hasUnsavedChanges = false;
projectManager.currentProject.saveProjectFile();
selectedObjectId = -1;
bool hasDirLight = std::any_of(sceneObjects.begin(), sceneObjects.end(), [](const SceneObject& o) {
return o.type == ObjectType::DirectionalLight;
});
if (!hasDirLight) {
addObject(ObjectType::DirectionalLight, "Directional Light");
}
addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success);
} else {
addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error);
@@ -396,6 +501,7 @@ void Engine::createNewScene(const std::string& sceneName) {
projectManager.currentProject.hasUnsavedChanges = true;
addObject(ObjectType::Cube, "Cube");
addObject(ObjectType::DirectionalLight, "Directional Light");
saveCurrentScene();
addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success);
@@ -405,6 +511,21 @@ void Engine::addObject(ObjectType type, const std::string& baseName) {
int id = nextObjectId++;
std::string name = baseName + " " + std::to_string(id);
sceneObjects.push_back(SceneObject(name, type, id));
// Light defaults
if (type == ObjectType::PointLight) {
sceneObjects.back().light.type = LightType::Point;
sceneObjects.back().light.range = 12.0f;
sceneObjects.back().light.intensity = 2.0f;
} else if (type == ObjectType::SpotLight) {
sceneObjects.back().light.type = LightType::Spot;
sceneObjects.back().light.range = 15.0f;
sceneObjects.back().light.intensity = 2.5f;
} else if (type == ObjectType::AreaLight) {
sceneObjects.back().light.type = LightType::Area;
sceneObjects.back().light.range = 10.0f;
sceneObjects.back().light.intensity = 3.0f;
sceneObjects.back().light.size = glm::vec2(2.0f, 2.0f);
}
selectedObjectId = id;
if (projectManager.currentProject.isLoaded) {
projectManager.currentProject.hasUnsavedChanges = true;
@@ -424,6 +545,13 @@ void Engine::duplicateSelected() {
newObj.scale = it->scale;
newObj.meshPath = it->meshPath;
newObj.meshId = it->meshId;
newObj.material = it->material;
newObj.materialPath = it->materialPath;
newObj.albedoTexturePath = it->albedoTexturePath;
newObj.overlayTexturePath = it->overlayTexturePath;
newObj.normalMapPath = it->normalMapPath;
newObj.useOverlay = it->useOverlay;
newObj.light = it->light;
sceneObjects.push_back(newObj);
selectedObjectId = id;

View File

@@ -61,6 +61,7 @@ private:
char importModelName[128] = ""; // For Assimp models
char fileBrowserSearch[256] = "";
float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range
// Private methods
SceneObject* getSelectedObject();
@@ -106,6 +107,8 @@ private:
void duplicateSelected();
void deleteSelected();
void setParent(int childId, int parentId);
void loadMaterialFromFile(SceneObject& obj);
void saveMaterialToFile(const SceneObject& obj);
// Console/logging
void addConsoleMessage(const std::string& message, ConsoleMessageType type);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
#include "ProjectManager.h"
#include "Rendering.h"
#include "ModelLoader.h"
// Project implementation
Project::Project(const std::string& projectName, const fs::path& basePath)
@@ -249,8 +250,25 @@ bool SceneSerializer::saveScene(const fs::path& filePath,
file << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n";
file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.z << "\n";
file << "scale=" << obj.scale.x << "," << obj.scale.y << "," << obj.scale.z << "\n";
file << "materialColor=" << obj.material.color.r << "," << obj.material.color.g << "," << obj.material.color.b << "\n";
file << "materialAmbient=" << obj.material.ambientStrength << "\n";
file << "materialSpecular=" << obj.material.specularStrength << "\n";
file << "materialShininess=" << obj.material.shininess << "\n";
file << "materialTextureMix=" << obj.material.textureMix << "\n";
file << "materialPath=" << obj.materialPath << "\n";
file << "albedoTex=" << obj.albedoTexturePath << "\n";
file << "overlayTex=" << obj.overlayTexturePath << "\n";
file << "normalMap=" << obj.normalMapPath << "\n";
file << "useOverlay=" << (obj.useOverlay ? 1 : 0) << "\n";
file << "lightColor=" << obj.light.color.r << "," << obj.light.color.g << "," << obj.light.color.b << "\n";
file << "lightIntensity=" << obj.light.intensity << "\n";
file << "lightRange=" << obj.light.range << "\n";
file << "lightInner=" << obj.light.innerAngle << "\n";
file << "lightOuter=" << obj.light.outerAngle << "\n";
file << "lightSize=" << obj.light.size.x << "," << obj.light.size.y << "\n";
file << "lightEnabled=" << (obj.light.enabled ? 1 : 0) << "\n";
if (obj.type == ObjectType::OBJMesh && !obj.meshPath.empty()) {
if ((obj.type == ObjectType::OBJMesh || obj.type == ObjectType::Model) && !obj.meshPath.empty()) {
file << "meshPath=" << obj.meshPath << "\n";
}
@@ -308,6 +326,10 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
currentObj->name = value;
} else if (key == "type") {
currentObj->type = static_cast<ObjectType>(std::stoi(value));
if (currentObj->type == ObjectType::DirectionalLight) currentObj->light.type = LightType::Directional;
else if (currentObj->type == ObjectType::PointLight) currentObj->light.type = LightType::Point;
else if (currentObj->type == ObjectType::SpotLight) currentObj->light.type = LightType::Spot;
else if (currentObj->type == ObjectType::AreaLight) currentObj->light.type = LightType::Area;
} else if (key == "parentId") {
currentObj->parentId = std::stoi(value);
} else if (key == "position") {
@@ -325,11 +347,61 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
&currentObj->scale.x,
&currentObj->scale.y,
&currentObj->scale.z);
} else if (key == "materialColor") {
sscanf(value.c_str(), "%f,%f,%f",
&currentObj->material.color.r,
&currentObj->material.color.g,
&currentObj->material.color.b);
} else if (key == "materialAmbient") {
currentObj->material.ambientStrength = std::stof(value);
} else if (key == "materialSpecular") {
currentObj->material.specularStrength = std::stof(value);
} else if (key == "materialShininess") {
currentObj->material.shininess = std::stof(value);
} else if (key == "materialTextureMix") {
currentObj->material.textureMix = std::stof(value);
} else if (key == "materialPath") {
currentObj->materialPath = value;
} else if (key == "albedoTex") {
currentObj->albedoTexturePath = value;
} else if (key == "overlayTex") {
currentObj->overlayTexturePath = value;
} else if (key == "normalMap") {
currentObj->normalMapPath = value;
} else if (key == "useOverlay") {
currentObj->useOverlay = (std::stoi(value) != 0);
} else if (key == "lightColor") {
sscanf(value.c_str(), "%f,%f,%f",
&currentObj->light.color.r,
&currentObj->light.color.g,
&currentObj->light.color.b);
} else if (key == "lightIntensity") {
currentObj->light.intensity = std::stof(value);
} else if (key == "lightRange") {
currentObj->light.range = std::stof(value);
} else if (key == "lightInner") {
currentObj->light.innerAngle = std::stof(value);
} else if (key == "lightOuter") {
currentObj->light.outerAngle = std::stof(value);
} else if (key == "lightSize") {
sscanf(value.c_str(), "%f,%f",
&currentObj->light.size.x,
&currentObj->light.size.y);
} else if (key == "lightEnabled") {
currentObj->light.enabled = (std::stoi(value) != 0);
} else if (key == "meshPath") {
currentObj->meshPath = value;
if (!value.empty() && currentObj->type == ObjectType::OBJMesh) {
std::string err;
currentObj->meshId = g_objLoader.loadOBJ(value, err);
} else if (!value.empty() && currentObj->type == ObjectType::Model) {
ModelLoadResult result = getModelLoader().loadModel(value);
if (result.success) {
currentObj->meshId = result.meshIndex;
} else {
std::cerr << "Failed to load model from scene: " << result.errorMessage << std::endl;
currentObj->meshId = -1;
}
}
} else if (key == "children" && !value.empty()) {
std::stringstream ss(value);

View File

@@ -1,5 +1,7 @@
#include "Rendering.h"
#include "Camera.h"
#include "ModelLoader.h"
#include <unordered_map>
#define TINYOBJLOADER_IMPLEMENTATION
#include "../include/ThirdParty/tiny_obj_loader.h"
@@ -409,6 +411,20 @@ Renderer::~Renderer() {
if (rbo) glDeleteRenderbuffers(1, &rbo);
}
Texture* Renderer::getTexture(const std::string& path) {
if (path.empty()) return nullptr;
auto it = textureCache.find(path);
if (it != textureCache.end()) return it->second.get();
auto tex = std::make_unique<Texture>(path);
if (!tex->GetID()) {
return nullptr;
}
Texture* raw = tex.get();
textureCache[path] = std::move(tex);
return raw;
}
void Renderer::initialize() {
shader = new Shader("Resources/Shaders/vert.glsl", "Resources/Shaders/frag.glsl");
if (shader->ID == 0) {
@@ -475,7 +491,7 @@ void Renderer::resize(int w, int h) {
}
}
void Renderer::beginRender(const glm::mat4& view, const glm::mat4& proj) {
void Renderer::beginRender(const glm::mat4& view, const glm::mat4& proj, const glm::vec3& cameraPos) {
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glViewport(0, 0, currentWidth, currentHeight);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
@@ -484,10 +500,12 @@ void Renderer::beginRender(const glm::mat4& view, const glm::mat4& proj) {
shader->use();
shader->setMat4("view", view);
shader->setMat4("projection", proj);
shader->setVec3("viewPos", cameraPos);
texture1->Bind(GL_TEXTURE0);
texture2->Bind(GL_TEXTURE1);
shader->setInt("texture1", 0);
shader->setInt("texture2", 1);
shader->setInt("overlayTex", 1);
shader->setInt("normalMap", 2);
}
void Renderer::renderSkybox(const glm::mat4& view, const glm::mat4& proj) {
@@ -511,6 +529,38 @@ void Renderer::renderObject(const SceneObject& obj) {
model = glm::scale(model, obj.scale);
shader->setMat4("model", model);
shader->setVec3("materialColor", obj.material.color);
shader->setFloat("ambientStrength", obj.material.ambientStrength);
shader->setFloat("specularStrength", obj.material.specularStrength);
shader->setFloat("shininess", obj.material.shininess);
shader->setFloat("mixAmount", obj.material.textureMix);
Texture* baseTex = texture1;
if (!obj.albedoTexturePath.empty()) {
if (auto* t = getTexture(obj.albedoTexturePath)) baseTex = t;
}
if (baseTex) baseTex->Bind(GL_TEXTURE0);
bool overlayUsed = false;
if (obj.useOverlay && !obj.overlayTexturePath.empty()) {
if (auto* t = getTexture(obj.overlayTexturePath)) {
t->Bind(GL_TEXTURE1);
overlayUsed = true;
}
}
if (!overlayUsed && texture2) {
texture2->Bind(GL_TEXTURE1);
}
shader->setBool("hasOverlay", overlayUsed);
bool normalUsed = false;
if (!obj.normalMapPath.empty()) {
if (auto* t = getTexture(obj.normalMapPath)) {
t->Bind(GL_TEXTURE2);
normalUsed = true;
}
}
shader->setBool("hasNormalMap", normalUsed);
switch (obj.type) {
case ObjectType::Cube:
@@ -530,24 +580,136 @@ void Renderer::renderObject(const SceneObject& obj) {
}
}
break;
case ObjectType::Model:
if (obj.meshId >= 0) {
Mesh* modelMesh = getModelLoader().getMesh(obj.meshId);
if (modelMesh) {
modelMesh->draw();
}
}
break;
case ObjectType::PointLight:
case ObjectType::SpotLight:
case ObjectType::AreaLight:
// Lights are not rendered as geometry
break;
case ObjectType::DirectionalLight:
// Not rendered as geometry
break;
}
}
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects) {
if (!shader) return;
shader->use();
shader->setMat4("view", camera.getViewMatrix());
shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE));
shader->setVec3("lightPos", glm::vec3(4.0f, 6.0f, 4.0f));
shader->setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
shader->setVec3("viewPos", camera.position);
shader->setFloat("ambientStrength", 0.25f);
shader->setFloat("specularStrength", 0.8f);
shader->setFloat("shininess", 64.0f);
shader->setFloat("mixAmount", 0.3f);
texture1->Bind(0);
texture2->Bind(1);
// Collect up to 10 lights
struct LightUniform {
int type = 0; // 0 dir,1 point,2 spot
glm::vec3 dir = glm::vec3(0.0f, -1.0f, 0.0f);
glm::vec3 pos = glm::vec3(0.0f);
glm::vec3 color = glm::vec3(1.0f);
float intensity = 1.0f;
float range = 10.0f;
float inner = glm::cos(glm::radians(15.0f));
float outer = glm::cos(glm::radians(25.0f));
};
auto forwardFromRotation = [](const SceneObject& obj) {
glm::vec3 f = glm::normalize(glm::vec3(
glm::sin(glm::radians(obj.rotation.y)) * glm::cos(glm::radians(obj.rotation.x)),
glm::sin(glm::radians(obj.rotation.x)),
glm::cos(glm::radians(obj.rotation.y)) * glm::cos(glm::radians(obj.rotation.x))
));
if (glm::length(f) < 1e-3f ||
!std::isfinite(f.x) || !std::isfinite(f.y) || !std::isfinite(f.z)) {
f = glm::vec3(0.0f, -1.0f, 0.0f);
}
return f;
};
std::vector<LightUniform> lights;
lights.reserve(10);
// Add directionals first, then spots, then points
for (const auto& obj : sceneObjects) {
if (obj.light.enabled && obj.type == ObjectType::DirectionalLight) {
LightUniform l;
l.type = 0;
l.dir = forwardFromRotation(obj);
l.color = obj.light.color;
l.intensity = obj.light.intensity;
lights.push_back(l);
if (lights.size() >= 10) break;
}
}
if (lights.size() < 10) {
for (const auto& obj : sceneObjects) {
if (obj.light.enabled && obj.type == ObjectType::SpotLight) {
LightUniform l;
l.type = 2;
l.pos = obj.position;
l.dir = forwardFromRotation(obj);
l.color = obj.light.color;
l.intensity = obj.light.intensity;
l.range = obj.light.range;
l.inner = glm::cos(glm::radians(obj.light.innerAngle));
l.outer = glm::cos(glm::radians(obj.light.outerAngle));
lights.push_back(l);
if (lights.size() >= 10) break;
}
}
}
if (lights.size() < 10) {
for (const auto& obj : sceneObjects) {
if (obj.light.enabled && obj.type == ObjectType::PointLight) {
LightUniform l;
l.type = 1;
l.pos = obj.position;
l.color = obj.light.color;
l.intensity = obj.light.intensity;
l.range = obj.light.range;
lights.push_back(l);
if (lights.size() >= 10) break;
}
}
}
int count = static_cast<int>(lights.size());
shader->setInt("lightCount", count);
for (int i = 0; i < count; ++i) {
const auto& l = lights[i];
std::string idx = "[" + std::to_string(i) + "]";
shader->setInt("lightTypeArr" + idx, l.type);
shader->setVec3("lightDirArr" + idx, l.dir);
shader->setVec3("lightPosArr" + idx, l.pos);
shader->setVec3("lightColorArr" + idx, l.color);
shader->setFloat("lightIntensityArr" + idx, l.intensity);
shader->setFloat("lightRangeArr" + idx, l.range);
shader->setFloat("lightInnerCosArr" + idx, l.inner);
shader->setFloat("lightOuterCosArr" + idx, l.outer);
}
// Bind base textures once per frame (used by objects)
if (texture1) texture1->Bind(0);
else glBindTexture(GL_TEXTURE_2D, 0);
if (texture2) texture2->Bind(1);
else glBindTexture(GL_TEXTURE_2D, 0);
if (texture1) texture1->Bind(0);
if (texture2) texture2->Bind(1);
for (const auto& obj : sceneObjects) {
// Skip light types in the main render pass (they are handled as gizmos)
if (obj.type == ObjectType::PointLight || obj.type == ObjectType::SpotLight || obj.type == ObjectType::AreaLight) {
continue;
}
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, obj.position);
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
@@ -556,6 +718,38 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
model = glm::scale(model, obj.scale);
shader->setMat4("model", model);
shader->setVec3("materialColor", obj.material.color);
shader->setFloat("ambientStrength", obj.material.ambientStrength);
shader->setFloat("specularStrength", obj.material.specularStrength);
shader->setFloat("shininess", obj.material.shininess);
shader->setFloat("mixAmount", obj.material.textureMix);
Texture* baseTex = texture1;
if (!obj.albedoTexturePath.empty()) {
if (auto* t = getTexture(obj.albedoTexturePath)) baseTex = t;
}
if (baseTex) baseTex->Bind(GL_TEXTURE0);
bool overlayUsed = false;
if (obj.useOverlay && !obj.overlayTexturePath.empty()) {
if (auto* t = getTexture(obj.overlayTexturePath)) {
t->Bind(GL_TEXTURE1);
overlayUsed = true;
}
}
if (!overlayUsed && texture2) {
texture2->Bind(GL_TEXTURE1);
}
shader->setBool("hasOverlay", overlayUsed);
bool normalUsed = false;
if (!obj.normalMapPath.empty()) {
if (auto* t = getTexture(obj.normalMapPath)) {
t->Bind(GL_TEXTURE2);
normalUsed = true;
}
}
shader->setBool("hasNormalMap", normalUsed);
Mesh* meshToDraw = nullptr;
if (obj.type == ObjectType::Cube) meshToDraw = cubeMesh;
@@ -563,6 +757,8 @@ void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>&
else if (obj.type == ObjectType::Capsule) meshToDraw = capsuleMesh;
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
meshToDraw = g_objLoader.getMesh(obj.meshId);
} else if (obj.type == ObjectType::Model && obj.meshId != -1) {
meshToDraw = getModelLoader().getMesh(obj.meshId);
}
if (meshToDraw) {

View File

@@ -5,6 +5,7 @@
#include "../include/Shaders/Shader.h"
#include "../include/Textures/Texture.h"
#include "../include/Skybox/Skybox.h"
#include <unordered_map>
// Cube vertex data (position + normal + texcoord)
extern float vertices[];
@@ -59,6 +60,7 @@ private:
Shader* shader = nullptr;
Texture* texture1 = nullptr;
Texture* texture2 = nullptr;
std::unordered_map<std::string, std::unique_ptr<Texture>> textureCache;
Mesh* cubeMesh = nullptr;
Mesh* sphereMesh = nullptr;
Mesh* capsuleMesh = nullptr;
@@ -71,11 +73,12 @@ public:
~Renderer();
void initialize();
Texture* getTexture(const std::string& path);
void resize(int w, int h);
int getWidth() const { return currentWidth; }
int getHeight() const { return currentHeight; }
void beginRender(const glm::mat4& view, const glm::mat4& proj);
void beginRender(const glm::mat4& view, const glm::mat4& proj, const glm::vec3& cameraPos);
void renderSkybox(const glm::mat4& view, const glm::mat4& proj);
void renderObject(const SceneObject& obj);
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects);

View File

@@ -7,7 +7,39 @@ enum class ObjectType {
Sphere,
Capsule,
OBJMesh,
Model // New type for Assimp-loaded models (FBX, GLTF, etc.)
Model, // New type for Assimp-loaded models (FBX, GLTF, etc.)
DirectionalLight,
PointLight,
SpotLight,
AreaLight
};
struct MaterialProperties {
glm::vec3 color = glm::vec3(1.0f);
float ambientStrength = 0.2f;
float specularStrength = 0.5f;
float shininess = 32.0f;
float textureMix = 0.3f; // Blend factor between albedo and overlay
};
enum class LightType {
Directional = 0,
Point = 1,
Spot = 2,
Area = 3
};
struct LightComponent {
LightType type = LightType::Point;
glm::vec3 color = glm::vec3(1.0f);
float intensity = 1.0f;
float range = 10.0f;
// Spot
float innerAngle = 15.0f;
float outerAngle = 25.0f;
// Area (rect) size in world units
glm::vec2 size = glm::vec2(1.0f, 1.0f);
bool enabled = true;
};
enum class ConsoleMessageType {
@@ -28,8 +60,15 @@ public:
int parentId = -1;
std::vector<int> childIds;
bool isExpanded = true;
std::string meshPath; // Path to OBJ file (for OBJMesh type)
int meshId = -1; // Index into loaded meshes cache
std::string meshPath; // Path to imported model file
int meshId = -1; // Index into loaded mesh caches (OBJLoader / ModelLoader)
MaterialProperties material;
std::string materialPath; // Optional external material asset
std::string albedoTexturePath;
std::string overlayTexturePath;
std::string normalMapPath;
bool useOverlay = false;
LightComponent light; // Only used when type is a light
SceneObject(const std::string& name, ObjectType type, int id)
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(id) {}

View File

@@ -102,6 +102,11 @@ void Shader::setFloat(const std::string &name, float value) const
glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::setVec2(const std::string &name, const glm::vec2 &value) const
{
glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
}
void Shader::setVec3(const std::string &name, const glm::vec3 &value) const
{
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
@@ -110,4 +115,4 @@ void Shader::setVec3(const std::string &name, const glm::vec3 &value) const
void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const
{
glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, glm::value_ptr(mat));
}
}