[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;
|
in vec2 TexCoord;
|
||||||
|
|
||||||
uniform sampler2D texture1;
|
uniform sampler2D texture1;
|
||||||
uniform sampler2D texture2;
|
uniform sampler2D overlayTex;
|
||||||
|
uniform sampler2D normalMap;
|
||||||
uniform float mixAmount = 0.2;
|
uniform float mixAmount = 0.2;
|
||||||
|
uniform bool hasOverlay = false;
|
||||||
|
uniform bool hasNormalMap = false;
|
||||||
|
|
||||||
uniform vec3 lightPos;
|
|
||||||
uniform vec3 viewPos;
|
uniform vec3 viewPos;
|
||||||
uniform vec3 lightColor = vec3(1.0);
|
uniform vec3 materialColor = vec3(1.0);
|
||||||
|
|
||||||
uniform float ambientStrength = 0.2;
|
uniform float ambientStrength = 0.2;
|
||||||
uniform float specularStrength = 0.5;
|
uniform float specularStrength = 0.5;
|
||||||
uniform float shininess = 32.0;
|
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()
|
void main()
|
||||||
{
|
{
|
||||||
vec3 norm = normalize(Normal);
|
vec3 norm = normalize(Normal);
|
||||||
vec3 lightDir = normalize(lightPos - FragPos);
|
|
||||||
vec3 viewDir = normalize(viewPos - 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)
|
// Texture mixing (corrected)
|
||||||
vec4 tex1 = texture(texture1, TexCoord);
|
vec4 tex1 = texture(texture1, TexCoord);
|
||||||
vec4 tex2 = texture(texture2, TexCoord);
|
vec3 texColor = tex1.rgb;
|
||||||
vec4 mixedTex = mix(tex1, tex2, mixAmount);
|
if (hasOverlay) {
|
||||||
vec3 texColor = mixedTex.rgb;
|
vec3 overlay = texture(overlayTex, TexCoord).rgb;
|
||||||
|
texColor = mix(texColor, overlay, mixAmount);
|
||||||
vec3 result = (ambient + diffuse + specular) * texColor;
|
}
|
||||||
FragColor = vec4(result, mixedTex.a); // Preserve alpha if needed
|
vec3 baseColor = texColor * materialColor;
|
||||||
|
|
||||||
|
// 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));
|
FragPos = vec3(model * vec4(aPos, 1.0));
|
||||||
Normal = mat3(transpose(inverse(model))) * aNormal;
|
Normal = mat3(transpose(inverse(model))) * aNormal;
|
||||||
TexCoord = aTexCoord;
|
TexCoord = aTexCoord;
|
||||||
|
|
||||||
gl_Position = projection * view * vec4(FragPos, 1.0);
|
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))
|
local duration=$((end_time - start_time))
|
||||||
|
|
||||||
if [ $exit_code -eq 0 ]; then
|
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'"
|
echo -e "[Complete]: Your Modularity Build Completed in ${duration}s!\nThe build should be located under Modularity within another folder called 'Build'"
|
||||||
else
|
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
|
fi
|
||||||
|
|
||||||
exit $exit_code
|
exit $exit_code
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public:
|
|||||||
void setBool(const std::string &name, bool value) const;
|
void setBool(const std::string &name, bool value) const;
|
||||||
void setInt(const std::string &name, int value) const;
|
void setInt(const std::string &name, int value) const;
|
||||||
void setFloat(const std::string &name, float 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 setVec3(const std::string &name, const glm::vec3 &value) const;
|
||||||
void setMat4(const std::string &name, const glm::mat4 &mat) 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;
|
return FileCategory::Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Material files
|
||||||
|
if (ext == ".mat") return FileCategory::Material;
|
||||||
|
|
||||||
// Texture files
|
// Texture files
|
||||||
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" ||
|
if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" ||
|
||||||
ext == ".tga" || ext == ".dds" || ext == ".hdr") {
|
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::Folder: return "folder";
|
||||||
case FileCategory::Scene: return "scene";
|
case FileCategory::Scene: return "scene";
|
||||||
case FileCategory::Model: return "model";
|
case FileCategory::Model: return "model";
|
||||||
|
case FileCategory::Material: return "material";
|
||||||
case FileCategory::Texture: return "image";
|
case FileCategory::Texture: return "image";
|
||||||
case FileCategory::Shader: return "shader";
|
case FileCategory::Shader: return "shader";
|
||||||
case FileCategory::Script: return "code";
|
case FileCategory::Script: return "code";
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ enum class FileCategory {
|
|||||||
Folder,
|
Folder,
|
||||||
Scene,
|
Scene,
|
||||||
Model,
|
Model,
|
||||||
|
Material,
|
||||||
Texture,
|
Texture,
|
||||||
Shader,
|
Shader,
|
||||||
Script,
|
Script,
|
||||||
|
|||||||
142
src/Engine.cpp
142
src/Engine.cpp
@@ -1,5 +1,7 @@
|
|||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
|
#include "ModelLoader.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
void window_size_callback(GLFWwindow* window, int width, int height) {
|
void window_size_callback(GLFWwindow* window, int width, int height) {
|
||||||
glViewport(0, 0, width, height);
|
glViewport(0, 0, width, height);
|
||||||
@@ -109,13 +111,8 @@ void Engine::run() {
|
|||||||
if (aspect <= 0.0f) aspect = 1.0f;
|
if (aspect <= 0.0f) aspect = 1.0f;
|
||||||
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
|
glm::mat4 proj = glm::perspective(glm::radians(FOV), aspect, NEAR_PLANE, FAR_PLANE);
|
||||||
|
|
||||||
renderer.beginRender(view, proj);
|
renderer.beginRender(view, proj, camera.position);
|
||||||
|
renderer.renderScene(camera, sceneObjects);
|
||||||
for (const auto& obj : sceneObjects) {
|
|
||||||
renderer.renderObject(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.renderSkybox(view, proj);
|
|
||||||
renderer.endRender();
|
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() {
|
void Engine::handleKeyboardShortcuts() {
|
||||||
static bool f11Pressed = false;
|
static bool f11Pressed = false;
|
||||||
if (glfwGetKey(editorWindow, GLFW_KEY_F11) == GLFW_PRESS && !f11Pressed) {
|
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.hasUnsavedChanges = false;
|
||||||
projectManager.currentProject.saveProjectFile();
|
projectManager.currentProject.saveProjectFile();
|
||||||
selectedObjectId = -1;
|
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);
|
addConsoleMessage("Loaded scene: " + sceneName, ConsoleMessageType::Success);
|
||||||
} else {
|
} else {
|
||||||
addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error);
|
addConsoleMessage("Error: Failed to load scene: " + sceneName, ConsoleMessageType::Error);
|
||||||
@@ -396,6 +501,7 @@ void Engine::createNewScene(const std::string& sceneName) {
|
|||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
|
||||||
addObject(ObjectType::Cube, "Cube");
|
addObject(ObjectType::Cube, "Cube");
|
||||||
|
addObject(ObjectType::DirectionalLight, "Directional Light");
|
||||||
saveCurrentScene();
|
saveCurrentScene();
|
||||||
|
|
||||||
addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success);
|
addConsoleMessage("Created new scene: " + sceneName, ConsoleMessageType::Success);
|
||||||
@@ -405,6 +511,21 @@ void Engine::addObject(ObjectType type, const std::string& baseName) {
|
|||||||
int id = nextObjectId++;
|
int id = nextObjectId++;
|
||||||
std::string name = baseName + " " + std::to_string(id);
|
std::string name = baseName + " " + std::to_string(id);
|
||||||
sceneObjects.push_back(SceneObject(name, type, 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;
|
selectedObjectId = id;
|
||||||
if (projectManager.currentProject.isLoaded) {
|
if (projectManager.currentProject.isLoaded) {
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
@@ -424,6 +545,13 @@ void Engine::duplicateSelected() {
|
|||||||
newObj.scale = it->scale;
|
newObj.scale = it->scale;
|
||||||
newObj.meshPath = it->meshPath;
|
newObj.meshPath = it->meshPath;
|
||||||
newObj.meshId = it->meshId;
|
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);
|
sceneObjects.push_back(newObj);
|
||||||
selectedObjectId = id;
|
selectedObjectId = id;
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ private:
|
|||||||
char importModelName[128] = ""; // For Assimp models
|
char importModelName[128] = ""; // For Assimp models
|
||||||
|
|
||||||
char fileBrowserSearch[256] = "";
|
char fileBrowserSearch[256] = "";
|
||||||
|
float fileBrowserIconScale = 1.0f; // 0.5 to 2.0 range
|
||||||
|
|
||||||
// Private methods
|
// Private methods
|
||||||
SceneObject* getSelectedObject();
|
SceneObject* getSelectedObject();
|
||||||
@@ -106,6 +107,8 @@ private:
|
|||||||
void duplicateSelected();
|
void duplicateSelected();
|
||||||
void deleteSelected();
|
void deleteSelected();
|
||||||
void setParent(int childId, int parentId);
|
void setParent(int childId, int parentId);
|
||||||
|
void loadMaterialFromFile(SceneObject& obj);
|
||||||
|
void saveMaterialToFile(const SceneObject& obj);
|
||||||
|
|
||||||
// Console/logging
|
// Console/logging
|
||||||
void addConsoleMessage(const std::string& message, ConsoleMessageType type);
|
void addConsoleMessage(const std::string& message, ConsoleMessageType type);
|
||||||
|
|||||||
1312
src/EnginePanels.cpp
1312
src/EnginePanels.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
|||||||
#include "ProjectManager.h"
|
#include "ProjectManager.h"
|
||||||
#include "Rendering.h"
|
#include "Rendering.h"
|
||||||
|
#include "ModelLoader.h"
|
||||||
|
|
||||||
// Project implementation
|
// Project implementation
|
||||||
Project::Project(const std::string& projectName, const fs::path& basePath)
|
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 << "position=" << obj.position.x << "," << obj.position.y << "," << obj.position.z << "\n";
|
||||||
file << "rotation=" << obj.rotation.x << "," << obj.rotation.y << "," << obj.rotation.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 << "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";
|
file << "meshPath=" << obj.meshPath << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +326,10 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
currentObj->name = value;
|
currentObj->name = value;
|
||||||
} else if (key == "type") {
|
} else if (key == "type") {
|
||||||
currentObj->type = static_cast<ObjectType>(std::stoi(value));
|
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") {
|
} else if (key == "parentId") {
|
||||||
currentObj->parentId = std::stoi(value);
|
currentObj->parentId = std::stoi(value);
|
||||||
} else if (key == "position") {
|
} else if (key == "position") {
|
||||||
@@ -325,11 +347,61 @@ bool SceneSerializer::loadScene(const fs::path& filePath,
|
|||||||
¤tObj->scale.x,
|
¤tObj->scale.x,
|
||||||
¤tObj->scale.y,
|
¤tObj->scale.y,
|
||||||
¤tObj->scale.z);
|
¤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") {
|
} else if (key == "meshPath") {
|
||||||
currentObj->meshPath = value;
|
currentObj->meshPath = value;
|
||||||
if (!value.empty() && currentObj->type == ObjectType::OBJMesh) {
|
if (!value.empty() && currentObj->type == ObjectType::OBJMesh) {
|
||||||
std::string err;
|
std::string err;
|
||||||
currentObj->meshId = g_objLoader.loadOBJ(value, 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()) {
|
} else if (key == "children" && !value.empty()) {
|
||||||
std::stringstream ss(value);
|
std::stringstream ss(value);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "Rendering.h"
|
#include "Rendering.h"
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
|
#include "ModelLoader.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#define TINYOBJLOADER_IMPLEMENTATION
|
#define TINYOBJLOADER_IMPLEMENTATION
|
||||||
#include "../include/ThirdParty/tiny_obj_loader.h"
|
#include "../include/ThirdParty/tiny_obj_loader.h"
|
||||||
@@ -409,6 +411,20 @@ Renderer::~Renderer() {
|
|||||||
if (rbo) glDeleteRenderbuffers(1, &rbo);
|
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() {
|
void Renderer::initialize() {
|
||||||
shader = new Shader("Resources/Shaders/vert.glsl", "Resources/Shaders/frag.glsl");
|
shader = new Shader("Resources/Shaders/vert.glsl", "Resources/Shaders/frag.glsl");
|
||||||
if (shader->ID == 0) {
|
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);
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
||||||
glViewport(0, 0, currentWidth, currentHeight);
|
glViewport(0, 0, currentWidth, currentHeight);
|
||||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
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->use();
|
||||||
shader->setMat4("view", view);
|
shader->setMat4("view", view);
|
||||||
shader->setMat4("projection", proj);
|
shader->setMat4("projection", proj);
|
||||||
|
shader->setVec3("viewPos", cameraPos);
|
||||||
texture1->Bind(GL_TEXTURE0);
|
texture1->Bind(GL_TEXTURE0);
|
||||||
texture2->Bind(GL_TEXTURE1);
|
texture2->Bind(GL_TEXTURE1);
|
||||||
shader->setInt("texture1", 0);
|
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) {
|
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);
|
model = glm::scale(model, obj.scale);
|
||||||
|
|
||||||
shader->setMat4("model", model);
|
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) {
|
switch (obj.type) {
|
||||||
case ObjectType::Cube:
|
case ObjectType::Cube:
|
||||||
@@ -530,24 +580,136 @@ void Renderer::renderObject(const SceneObject& obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
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) {
|
void Renderer::renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects) {
|
||||||
|
if (!shader) return;
|
||||||
shader->use();
|
shader->use();
|
||||||
shader->setMat4("view", camera.getViewMatrix());
|
shader->setMat4("view", camera.getViewMatrix());
|
||||||
shader->setMat4("projection", glm::perspective(glm::radians(FOV), (float)currentWidth / (float)currentHeight, NEAR_PLANE, FAR_PLANE));
|
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("viewPos", camera.position);
|
||||||
shader->setVec3("lightColor", glm::vec3(1.0f, 1.0f, 1.0f));
|
|
||||||
shader->setFloat("ambientStrength", 0.25f);
|
shader->setFloat("ambientStrength", 0.25f);
|
||||||
shader->setFloat("specularStrength", 0.8f);
|
shader->setFloat("specularStrength", 0.8f);
|
||||||
shader->setFloat("shininess", 64.0f);
|
shader->setFloat("shininess", 64.0f);
|
||||||
shader->setFloat("mixAmount", 0.3f);
|
shader->setFloat("mixAmount", 0.3f);
|
||||||
|
|
||||||
texture1->Bind(0);
|
// Collect up to 10 lights
|
||||||
texture2->Bind(1);
|
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) {
|
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);
|
glm::mat4 model = glm::mat4(1.0f);
|
||||||
model = glm::translate(model, obj.position);
|
model = glm::translate(model, obj.position);
|
||||||
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
|
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);
|
model = glm::scale(model, obj.scale);
|
||||||
|
|
||||||
shader->setMat4("model", model);
|
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;
|
Mesh* meshToDraw = nullptr;
|
||||||
if (obj.type == ObjectType::Cube) meshToDraw = cubeMesh;
|
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::Capsule) meshToDraw = capsuleMesh;
|
||||||
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
|
else if (obj.type == ObjectType::OBJMesh && obj.meshId != -1) {
|
||||||
meshToDraw = g_objLoader.getMesh(obj.meshId);
|
meshToDraw = g_objLoader.getMesh(obj.meshId);
|
||||||
|
} else if (obj.type == ObjectType::Model && obj.meshId != -1) {
|
||||||
|
meshToDraw = getModelLoader().getMesh(obj.meshId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meshToDraw) {
|
if (meshToDraw) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "../include/Shaders/Shader.h"
|
#include "../include/Shaders/Shader.h"
|
||||||
#include "../include/Textures/Texture.h"
|
#include "../include/Textures/Texture.h"
|
||||||
#include "../include/Skybox/Skybox.h"
|
#include "../include/Skybox/Skybox.h"
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
// Cube vertex data (position + normal + texcoord)
|
// Cube vertex data (position + normal + texcoord)
|
||||||
extern float vertices[];
|
extern float vertices[];
|
||||||
@@ -59,6 +60,7 @@ private:
|
|||||||
Shader* shader = nullptr;
|
Shader* shader = nullptr;
|
||||||
Texture* texture1 = nullptr;
|
Texture* texture1 = nullptr;
|
||||||
Texture* texture2 = nullptr;
|
Texture* texture2 = nullptr;
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<Texture>> textureCache;
|
||||||
Mesh* cubeMesh = nullptr;
|
Mesh* cubeMesh = nullptr;
|
||||||
Mesh* sphereMesh = nullptr;
|
Mesh* sphereMesh = nullptr;
|
||||||
Mesh* capsuleMesh = nullptr;
|
Mesh* capsuleMesh = nullptr;
|
||||||
@@ -71,11 +73,12 @@ public:
|
|||||||
~Renderer();
|
~Renderer();
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
Texture* getTexture(const std::string& path);
|
||||||
void resize(int w, int h);
|
void resize(int w, int h);
|
||||||
int getWidth() const { return currentWidth; }
|
int getWidth() const { return currentWidth; }
|
||||||
int getHeight() const { return currentHeight; }
|
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 renderSkybox(const glm::mat4& view, const glm::mat4& proj);
|
||||||
void renderObject(const SceneObject& obj);
|
void renderObject(const SceneObject& obj);
|
||||||
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects);
|
void renderScene(const Camera& camera, const std::vector<SceneObject>& sceneObjects);
|
||||||
|
|||||||
@@ -7,7 +7,39 @@ enum class ObjectType {
|
|||||||
Sphere,
|
Sphere,
|
||||||
Capsule,
|
Capsule,
|
||||||
OBJMesh,
|
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 {
|
enum class ConsoleMessageType {
|
||||||
@@ -28,8 +60,15 @@ public:
|
|||||||
int parentId = -1;
|
int parentId = -1;
|
||||||
std::vector<int> childIds;
|
std::vector<int> childIds;
|
||||||
bool isExpanded = true;
|
bool isExpanded = true;
|
||||||
std::string meshPath; // Path to OBJ file (for OBJMesh type)
|
std::string meshPath; // Path to imported model file
|
||||||
int meshId = -1; // Index into loaded meshes cache
|
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)
|
SceneObject(const std::string& name, ObjectType type, int id)
|
||||||
: name(name), type(type), position(0.0f), rotation(0.0f), scale(1.0f), id(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);
|
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
|
void Shader::setVec3(const std::string &name, const glm::vec3 &value) const
|
||||||
{
|
{
|
||||||
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
|
glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]);
|
||||||
|
|||||||
Reference in New Issue
Block a user