[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:
@@ -6,41 +6,101 @@ in vec3 Normal;
|
||||
in vec2 TexCoord;
|
||||
|
||||
uniform sampler2D texture1;
|
||||
uniform sampler2D texture2;
|
||||
uniform sampler2D overlayTex;
|
||||
uniform sampler2D normalMap;
|
||||
uniform float mixAmount = 0.2;
|
||||
uniform bool hasOverlay = false;
|
||||
uniform bool hasNormalMap = false;
|
||||
|
||||
uniform vec3 lightPos;
|
||||
uniform vec3 viewPos;
|
||||
uniform vec3 lightColor = vec3(1.0);
|
||||
uniform vec3 materialColor = vec3(1.0);
|
||||
|
||||
uniform float ambientStrength = 0.2;
|
||||
uniform float specularStrength = 0.5;
|
||||
uniform float shininess = 32.0;
|
||||
const int MAX_LIGHTS = 10;
|
||||
uniform int lightCount = 0; // up to MAX_LIGHTS
|
||||
// type: 0 dir, 1 point, 2 spot
|
||||
uniform int lightTypeArr[MAX_LIGHTS];
|
||||
uniform vec3 lightDirArr[MAX_LIGHTS];
|
||||
uniform vec3 lightPosArr[MAX_LIGHTS];
|
||||
uniform vec3 lightColorArr[MAX_LIGHTS];
|
||||
uniform float lightIntensityArr[MAX_LIGHTS];
|
||||
uniform float lightRangeArr[MAX_LIGHTS];
|
||||
uniform float lightInnerCosArr[MAX_LIGHTS];
|
||||
uniform float lightOuterCosArr[MAX_LIGHTS];
|
||||
|
||||
// Single directional light controlled by hierarchy (fallback if none set)
|
||||
uniform vec3 lightDir = normalize(vec3(0.3, 1.0, 0.5));
|
||||
uniform vec3 lightColor = vec3(1.0);
|
||||
uniform float lightIntensity = 1.0;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 norm = normalize(Normal);
|
||||
vec3 lightDir = normalize(lightPos - FragPos);
|
||||
vec3 viewDir = normalize(viewPos - FragPos);
|
||||
|
||||
// Ambient
|
||||
vec3 ambient = ambientStrength * lightColor;
|
||||
|
||||
// Diffuse
|
||||
float diff = max(dot(norm, lightDir), 0.0);
|
||||
vec3 diffuse = diff * lightColor;
|
||||
|
||||
// Specular (Blinn-Phong)
|
||||
vec3 halfwayDir = normalize(lightDir + viewDir);
|
||||
float spec = pow(max(dot(norm, halfwayDir), 0.0), shininess);
|
||||
vec3 specular = specularStrength * spec * lightColor;
|
||||
|
||||
// Texture mixing (corrected)
|
||||
vec4 tex1 = texture(texture1, TexCoord);
|
||||
vec4 tex2 = texture(texture2, TexCoord);
|
||||
vec4 mixedTex = mix(tex1, tex2, mixAmount);
|
||||
vec3 texColor = mixedTex.rgb;
|
||||
vec3 texColor = tex1.rgb;
|
||||
if (hasOverlay) {
|
||||
vec3 overlay = texture(overlayTex, TexCoord).rgb;
|
||||
texColor = mix(texColor, overlay, mixAmount);
|
||||
}
|
||||
vec3 baseColor = texColor * materialColor;
|
||||
|
||||
vec3 result = (ambient + diffuse + specular) * texColor;
|
||||
FragColor = vec4(result, mixedTex.a); // Preserve alpha if needed
|
||||
// Normal map (tangent-space)
|
||||
if (hasNormalMap) {
|
||||
vec3 mapN = texture(normalMap, TexCoord).xyz * 2.0 - 1.0;
|
||||
vec3 dp1 = dFdx(FragPos);
|
||||
vec3 dp2 = dFdy(FragPos);
|
||||
vec2 duv1 = dFdx(TexCoord);
|
||||
vec2 duv2 = dFdy(TexCoord);
|
||||
vec3 tangent = normalize(dp1 * duv2.y - dp2 * duv1.y);
|
||||
vec3 bitangent = normalize(-dp1 * duv2.x + dp2 * duv1.x);
|
||||
mat3 TBN = mat3(tangent, bitangent, normalize(Normal));
|
||||
norm = normalize(TBN * mapN);
|
||||
}
|
||||
|
||||
vec3 ambient = ambientStrength * baseColor;
|
||||
vec3 lighting = ambient;
|
||||
|
||||
int count = min(lightCount, MAX_LIGHTS);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int ltype = lightTypeArr[i];
|
||||
vec3 ldir = (ltype == 0) ? -normalize(lightDirArr[i]) : lightPosArr[i] - FragPos;
|
||||
float dist = length(ldir);
|
||||
vec3 lDirN = normalize(ldir);
|
||||
|
||||
float attenuation = 1.0;
|
||||
if (ltype != 0) {
|
||||
float range = lightRangeArr[i];
|
||||
if (range > 0.0 && dist > range) continue;
|
||||
if (range > 0.0) {
|
||||
float falloff = clamp(1.0 - (dist / range), 0.0, 1.0);
|
||||
attenuation = falloff * falloff;
|
||||
}
|
||||
}
|
||||
|
||||
float intensity = lightIntensityArr[i];
|
||||
if (intensity <= 0.0) continue;
|
||||
|
||||
float diff = max(dot(norm, lDirN), 0.0);
|
||||
vec3 diffuse = diff * lightColorArr[i] * intensity;
|
||||
|
||||
vec3 halfwayDir = normalize(lDirN + viewDir);
|
||||
float spec = pow(max(dot(norm, halfwayDir), 0.0), shininess);
|
||||
vec3 specular = specularStrength * spec * lightColorArr[i] * intensity;
|
||||
|
||||
if (ltype == 2) {
|
||||
float cosTheta = dot(-lDirN, normalize(lightDirArr[i]));
|
||||
float spotAtten = smoothstep(lightOuterCosArr[i], lightInnerCosArr[i], cosTheta);
|
||||
attenuation *= spotAtten;
|
||||
}
|
||||
|
||||
lighting += attenuation * (diffuse + specular) * baseColor;
|
||||
}
|
||||
|
||||
float alpha = tex1.a;
|
||||
FragColor = vec4(lighting, alpha); // Preserve alpha if needed
|
||||
}
|
||||
@@ -16,5 +16,6 @@ void main()
|
||||
FragPos = vec3(model * vec4(aPos, 1.0));
|
||||
Normal = mat3(transpose(inverse(model))) * aNormal;
|
||||
TexCoord = aTexCoord;
|
||||
|
||||
gl_Position = projection * view * vec4(FragPos, 1.0);
|
||||
}
|
||||
4
build.sh
4
build.sh
@@ -9,9 +9,11 @@ finish() {
|
||||
local duration=$((end_time - start_time))
|
||||
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
echo -e "================================\n Modularity - Native Linux Build Complete\n================================"
|
||||
echo -e "[Complete]: Your Modularity Build Completed in ${duration}s!\nThe build should be located under Modularity within another folder called 'Build'"
|
||||
else
|
||||
echo "[!]: Your Modularity Build Failed after ${duration}s (exit code ${exit_code})."
|
||||
echo -e "================================\n Modularity - Native Linux Build Failed\n================================"
|
||||
echo "[Failed]: Your Modularity Build Failed after ${duration}s (exit code ${exit_code})."
|
||||
fi
|
||||
|
||||
exit $exit_code
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
void setBool(const std::string &name, bool value) const;
|
||||
void setInt(const std::string &name, int value) const;
|
||||
void setFloat(const std::string &name, float value) const;
|
||||
void setVec2(const std::string &name, const glm::vec2 &value) const;
|
||||
void setVec3(const std::string &name, const glm::vec3 &value) const;
|
||||
void setMat4(const std::string &name, const glm::mat4 &mat) const;
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -11,6 +11,7 @@ enum class FileCategory {
|
||||
Folder,
|
||||
Scene,
|
||||
Model,
|
||||
Material,
|
||||
Texture,
|
||||
Shader,
|
||||
Script,
|
||||
|
||||
142
src/Engine.cpp
142
src/Engine.cpp
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
1322
src/EnginePanels.cpp
1322
src/EnginePanels.cpp
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||
¤tObj->scale.x,
|
||||
¤tObj->scale.y,
|
||||
¤tObj->scale.z);
|
||||
} else if (key == "materialColor") {
|
||||
sscanf(value.c_str(), "%f,%f,%f",
|
||||
¤tObj->material.color.r,
|
||||
¤tObj->material.color.g,
|
||||
¤tObj->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",
|
||||
¤tObj->light.color.r,
|
||||
¤tObj->light.color.g,
|
||||
¤tObj->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",
|
||||
¤tObj->light.size.x,
|
||||
¤tObj->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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user