changed lock to viewport when clicking to Hold right click to move around, added being able to select objects through viewport and improved Material support
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cfloat>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|||||||
151
src/Engine.cpp
151
src/Engine.cpp
@@ -3,6 +3,71 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct MaterialFileData {
|
||||||
|
MaterialProperties props;
|
||||||
|
std::string albedo;
|
||||||
|
std::string overlay;
|
||||||
|
std::string normal;
|
||||||
|
bool useOverlay = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool readMaterialFile(const std::string& path, MaterialFileData& outData) {
|
||||||
|
std::ifstream f(path);
|
||||||
|
if (!f.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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", &outData.props.color.r, &outData.props.color.g, &outData.props.color.b);
|
||||||
|
} else if (key == "ambient") {
|
||||||
|
outData.props.ambientStrength = std::stof(val);
|
||||||
|
} else if (key == "specular") {
|
||||||
|
outData.props.specularStrength = std::stof(val);
|
||||||
|
} else if (key == "shininess") {
|
||||||
|
outData.props.shininess = std::stof(val);
|
||||||
|
} else if (key == "textureMix") {
|
||||||
|
outData.props.textureMix = std::stof(val);
|
||||||
|
} else if (key == "albedo") {
|
||||||
|
outData.albedo = val;
|
||||||
|
} else if (key == "overlay") {
|
||||||
|
outData.overlay = val;
|
||||||
|
} else if (key == "normal") {
|
||||||
|
outData.normal = val;
|
||||||
|
} else if (key == "useOverlay") {
|
||||||
|
outData.useOverlay = std::stoi(val) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeMaterialFile(const MaterialFileData& data, const std::string& path) {
|
||||||
|
std::ofstream f(path);
|
||||||
|
if (!f.is_open()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
f << "# Material\n";
|
||||||
|
f << "color=" << data.props.color.r << "," << data.props.color.g << "," << data.props.color.b << "\n";
|
||||||
|
f << "ambient=" << data.props.ambientStrength << "\n";
|
||||||
|
f << "specular=" << data.props.specularStrength << "\n";
|
||||||
|
f << "shininess=" << data.props.shininess << "\n";
|
||||||
|
f << "textureMix=" << data.props.textureMix << "\n";
|
||||||
|
f << "useOverlay=" << (data.useOverlay ? 1 : 0) << "\n";
|
||||||
|
f << "albedo=" << data.albedo << "\n";
|
||||||
|
f << "overlay=" << data.overlay << "\n";
|
||||||
|
f << "normal=" << data.normal << "\n";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -261,39 +326,16 @@ void Engine::importModelToScene(const std::string& filepath, const std::string&
|
|||||||
void Engine::loadMaterialFromFile(SceneObject& obj) {
|
void Engine::loadMaterialFromFile(SceneObject& obj) {
|
||||||
if (obj.materialPath.empty()) return;
|
if (obj.materialPath.empty()) return;
|
||||||
try {
|
try {
|
||||||
std::ifstream f(obj.materialPath);
|
MaterialFileData data;
|
||||||
if (!f.is_open()) {
|
if (!readMaterialFile(obj.materialPath, data)) {
|
||||||
addConsoleMessage("Failed to open material: " + obj.materialPath, ConsoleMessageType::Error);
|
addConsoleMessage("Failed to open material: " + obj.materialPath, ConsoleMessageType::Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::string line;
|
obj.material = data.props;
|
||||||
while (std::getline(f, line)) {
|
obj.albedoTexturePath = data.albedo;
|
||||||
line.erase(0, line.find_first_not_of(" \t\r\n"));
|
obj.overlayTexturePath = data.overlay;
|
||||||
if (line.empty() || line[0] == '#') continue;
|
obj.normalMapPath = data.normal;
|
||||||
auto pos = line.find('=');
|
obj.useOverlay = data.useOverlay;
|
||||||
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);
|
addConsoleMessage("Applied material: " + obj.materialPath, ConsoleMessageType::Success);
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@@ -301,27 +343,52 @@ void Engine::loadMaterialFromFile(SceneObject& obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Engine::loadMaterialData(const std::string& path, MaterialProperties& props,
|
||||||
|
std::string& albedo, std::string& overlay,
|
||||||
|
std::string& normal, bool& useOverlay)
|
||||||
|
{
|
||||||
|
MaterialFileData data;
|
||||||
|
if (!readMaterialFile(path, data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
props = data.props;
|
||||||
|
albedo = data.albedo;
|
||||||
|
overlay = data.overlay;
|
||||||
|
normal = data.normal;
|
||||||
|
useOverlay = data.useOverlay;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Engine::saveMaterialData(const std::string& path, const MaterialProperties& props,
|
||||||
|
const std::string& albedo, const std::string& overlay,
|
||||||
|
const std::string& normal, bool useOverlay)
|
||||||
|
{
|
||||||
|
MaterialFileData data;
|
||||||
|
data.props = props;
|
||||||
|
data.albedo = albedo;
|
||||||
|
data.overlay = overlay;
|
||||||
|
data.normal = normal;
|
||||||
|
data.useOverlay = useOverlay;
|
||||||
|
return writeMaterialFile(data, path);
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::saveMaterialToFile(const SceneObject& obj) {
|
void Engine::saveMaterialToFile(const SceneObject& obj) {
|
||||||
if (obj.materialPath.empty()) {
|
if (obj.materialPath.empty()) {
|
||||||
addConsoleMessage("Material path is empty", ConsoleMessageType::Warning);
|
addConsoleMessage("Material path is empty", ConsoleMessageType::Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
std::ofstream f(obj.materialPath);
|
MaterialFileData data;
|
||||||
if (!f.is_open()) {
|
data.props = obj.material;
|
||||||
|
data.albedo = obj.albedoTexturePath;
|
||||||
|
data.overlay = obj.overlayTexturePath;
|
||||||
|
data.normal = obj.normalMapPath;
|
||||||
|
data.useOverlay = obj.useOverlay;
|
||||||
|
|
||||||
|
if (!writeMaterialFile(data, obj.materialPath)) {
|
||||||
addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error);
|
addConsoleMessage("Failed to open material for writing: " + obj.materialPath, ConsoleMessageType::Error);
|
||||||
return;
|
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);
|
addConsoleMessage("Saved material: " + obj.materialPath, ConsoleMessageType::Success);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
addConsoleMessage("Failed to save material: " + obj.materialPath, ConsoleMessageType::Error);
|
addConsoleMessage("Failed to save material: " + obj.materialPath, ConsoleMessageType::Error);
|
||||||
|
|||||||
18
src/Engine.h
18
src/Engine.h
@@ -19,9 +19,17 @@ private:
|
|||||||
ViewportController viewportController;
|
ViewportController viewportController;
|
||||||
float deltaTime = 0.0f;
|
float deltaTime = 0.0f;
|
||||||
float lastFrame = 0.0f;
|
float lastFrame = 0.0f;
|
||||||
bool cursorLocked = false;
|
bool cursorLocked = false; // true only while holding right mouse for freelook
|
||||||
int viewportWidth = 800;
|
int viewportWidth = 800;
|
||||||
int viewportHeight = 600;
|
int viewportHeight = 600;
|
||||||
|
// Standalone material inspection cache
|
||||||
|
std::string inspectedMaterialPath;
|
||||||
|
MaterialProperties inspectedMaterial;
|
||||||
|
std::string inspectedAlbedo;
|
||||||
|
std::string inspectedOverlay;
|
||||||
|
std::string inspectedNormal;
|
||||||
|
bool inspectedUseOverlay = false;
|
||||||
|
bool inspectedMaterialValid = false;
|
||||||
|
|
||||||
std::vector<SceneObject> sceneObjects;
|
std::vector<SceneObject> sceneObjects;
|
||||||
int selectedObjectId = -1;
|
int selectedObjectId = -1;
|
||||||
@@ -114,6 +122,14 @@ private:
|
|||||||
void addConsoleMessage(const std::string& message, ConsoleMessageType type);
|
void addConsoleMessage(const std::string& message, ConsoleMessageType type);
|
||||||
void logToConsole(const std::string& message);
|
void logToConsole(const std::string& message);
|
||||||
|
|
||||||
|
// Material helpers
|
||||||
|
bool loadMaterialData(const std::string& path, MaterialProperties& props,
|
||||||
|
std::string& albedo, std::string& overlay,
|
||||||
|
std::string& normal, bool& useOverlay);
|
||||||
|
bool saveMaterialData(const std::string& path, const MaterialProperties& props,
|
||||||
|
const std::string& albedo, const std::string& overlay,
|
||||||
|
const std::string& normal, bool useOverlay);
|
||||||
|
|
||||||
// ImGui setup
|
// ImGui setup
|
||||||
void setupImGui();
|
void setupImGui();
|
||||||
bool initRenderer();
|
bool initRenderer();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Engine.h"
|
#include "Engine.h"
|
||||||
#include "ModelLoader.h"
|
#include "ModelLoader.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cfloat>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
@@ -376,9 +377,11 @@ void Engine::renderFileBrowserPanel() {
|
|||||||
|
|
||||||
for (size_t i = 0; i < pathParts.size(); i++) {
|
for (size_t i = 0; i < pathParts.size(); i++) {
|
||||||
std::string name = (i == 0) ? "Project" : pathParts[i].filename().string();
|
std::string name = (i == 0) ? "Project" : pathParts[i].filename().string();
|
||||||
|
ImGui::PushID(static_cast<int>(i));
|
||||||
if (ImGui::SmallButton(name.c_str())) {
|
if (ImGui::SmallButton(name.c_str())) {
|
||||||
fileBrowser.navigateTo(pathParts[i]);
|
fileBrowser.navigateTo(pathParts[i]);
|
||||||
}
|
}
|
||||||
|
ImGui::PopID();
|
||||||
if (i < pathParts.size() - 1) {
|
if (i < pathParts.size() - 1) {
|
||||||
ImGui::SameLine(0, 2);
|
ImGui::SameLine(0, 2);
|
||||||
ImGui::TextDisabled("/");
|
ImGui::TextDisabled("/");
|
||||||
@@ -1284,8 +1287,168 @@ void Engine::renderInspectorPanel() {
|
|||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs::path selectedMaterialPath;
|
||||||
|
bool browserHasMaterial = false;
|
||||||
|
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile)) {
|
||||||
|
fs::directory_entry entry(fileBrowser.selectedFile);
|
||||||
|
if (fileBrowser.getFileCategory(entry) == FileCategory::Material) {
|
||||||
|
selectedMaterialPath = entry.path();
|
||||||
|
browserHasMaterial = true;
|
||||||
|
if (inspectedMaterialPath != selectedMaterialPath.string()) {
|
||||||
|
inspectedMaterialValid = loadMaterialData(
|
||||||
|
selectedMaterialPath.string(),
|
||||||
|
inspectedMaterial,
|
||||||
|
inspectedAlbedo,
|
||||||
|
inspectedOverlay,
|
||||||
|
inspectedNormal,
|
||||||
|
inspectedUseOverlay
|
||||||
|
);
|
||||||
|
inspectedMaterialPath = selectedMaterialPath.string();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inspectedMaterialPath.clear();
|
||||||
|
inspectedMaterialValid = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inspectedMaterialPath.clear();
|
||||||
|
inspectedMaterialValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto renderMaterialAssetPanel = [&](const char* headerTitle, bool allowApply) {
|
||||||
|
if (!browserHasMaterial) return;
|
||||||
|
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.35f, 0.35f, 0.55f, 1.0f));
|
||||||
|
if (ImGui::CollapsingHeader(headerTitle, ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
|
ImGui::Indent(8.0f);
|
||||||
|
if (!inspectedMaterialValid) {
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "Failed to read material file.");
|
||||||
|
} else {
|
||||||
|
auto textureField = [&](const char* label, const char* idSuffix, std::string& path) {
|
||||||
|
bool changed = false;
|
||||||
|
ImGui::PushID(idSuffix);
|
||||||
|
ImGui::TextUnformatted(label);
|
||||||
|
ImGui::SetNextItemWidth(-140);
|
||||||
|
char buf[512] = {};
|
||||||
|
std::snprintf(buf, sizeof(buf), "%s", path.c_str());
|
||||||
|
if (ImGui::InputText("##Path", buf, sizeof(buf))) {
|
||||||
|
path = buf;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::SmallButton("Clear")) {
|
||||||
|
path.clear();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
bool canUseTex = !fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile) &&
|
||||||
|
fileBrowser.isTextureFile(fs::directory_entry(fileBrowser.selectedFile));
|
||||||
|
ImGui::BeginDisabled(!canUseTex);
|
||||||
|
std::string btnLabel = std::string("Use Selection##") + idSuffix;
|
||||||
|
if (ImGui::SmallButton(btnLabel.c_str())) {
|
||||||
|
path = fileBrowser.selectedFile.string();
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
ImGui::PopID();
|
||||||
|
return changed;
|
||||||
|
};
|
||||||
|
|
||||||
|
ImGui::TextDisabled("%s", selectedMaterialPath.filename().string().c_str());
|
||||||
|
ImGui::TextColored(ImVec4(0.7f, 0.85f, 1.0f, 1.0f), "%s", selectedMaterialPath.string().c_str());
|
||||||
|
ImGui::Spacing();
|
||||||
|
|
||||||
|
bool matChanged = false;
|
||||||
|
if (ImGui::ColorEdit3("Base Color", &inspectedMaterial.color.x)) {
|
||||||
|
matChanged = true;
|
||||||
|
}
|
||||||
|
float metallic = inspectedMaterial.specularStrength;
|
||||||
|
if (ImGui::SliderFloat("Metallic", &metallic, 0.0f, 1.0f)) {
|
||||||
|
inspectedMaterial.specularStrength = metallic;
|
||||||
|
matChanged = true;
|
||||||
|
}
|
||||||
|
float smoothness = inspectedMaterial.shininess / 256.0f;
|
||||||
|
if (ImGui::SliderFloat("Smoothness", &smoothness, 0.0f, 1.0f)) {
|
||||||
|
smoothness = std::clamp(smoothness, 0.0f, 1.0f);
|
||||||
|
inspectedMaterial.shininess = smoothness * 256.0f;
|
||||||
|
matChanged = true;
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Ambient Light", &inspectedMaterial.ambientStrength, 0.0f, 1.0f)) {
|
||||||
|
matChanged = true;
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Detail Mix", &inspectedMaterial.textureMix, 0.0f, 1.0f)) {
|
||||||
|
matChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
matChanged |= textureField("Base Map", "PreviewAlbedo", inspectedAlbedo);
|
||||||
|
if (ImGui::Checkbox("Use Detail Map", &inspectedUseOverlay)) {
|
||||||
|
matChanged = true;
|
||||||
|
}
|
||||||
|
matChanged |= textureField("Detail Map", "PreviewOverlay", inspectedOverlay);
|
||||||
|
matChanged |= textureField("Normal Map", "PreviewNormal", inspectedNormal);
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
if (ImGui::Button("Reload")) {
|
||||||
|
inspectedMaterialValid = loadMaterialData(
|
||||||
|
selectedMaterialPath.string(),
|
||||||
|
inspectedMaterial,
|
||||||
|
inspectedAlbedo,
|
||||||
|
inspectedOverlay,
|
||||||
|
inspectedNormal,
|
||||||
|
inspectedUseOverlay
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("Save")) {
|
||||||
|
if (saveMaterialData(
|
||||||
|
selectedMaterialPath.string(),
|
||||||
|
inspectedMaterial,
|
||||||
|
inspectedAlbedo,
|
||||||
|
inspectedOverlay,
|
||||||
|
inspectedNormal,
|
||||||
|
inspectedUseOverlay))
|
||||||
|
{
|
||||||
|
addConsoleMessage("Saved material: " + selectedMaterialPath.string(), ConsoleMessageType::Success);
|
||||||
|
} else {
|
||||||
|
addConsoleMessage("Failed to save material: " + selectedMaterialPath.string(), ConsoleMessageType::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowApply) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
SceneObject* target = getSelectedObject();
|
||||||
|
bool canApply = target != nullptr;
|
||||||
|
ImGui::BeginDisabled(!canApply);
|
||||||
|
if (ImGui::Button("Apply to Selection")) {
|
||||||
|
if (target) {
|
||||||
|
target->material = inspectedMaterial;
|
||||||
|
target->albedoTexturePath = inspectedAlbedo;
|
||||||
|
target->overlayTexturePath = inspectedOverlay;
|
||||||
|
target->normalMapPath = inspectedNormal;
|
||||||
|
target->useOverlay = inspectedUseOverlay;
|
||||||
|
target->materialPath = selectedMaterialPath.string();
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
|
addConsoleMessage("Applied material to " + target->name, ConsoleMessageType::Success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matChanged) {
|
||||||
|
inspectedMaterialValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::Unindent(8.0f);
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
};
|
||||||
|
|
||||||
if (selectedObjectId == -1) {
|
if (selectedObjectId == -1) {
|
||||||
ImGui::TextDisabled("No object selected");
|
if (browserHasMaterial) {
|
||||||
|
renderMaterialAssetPanel("Material Asset", true);
|
||||||
|
} else {
|
||||||
|
ImGui::TextDisabled("No object selected");
|
||||||
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1393,88 +1556,106 @@ void Engine::renderInspectorPanel() {
|
|||||||
if (ImGui::CollapsingHeader("Material", ImGuiTreeNodeFlags_DefaultOpen)) {
|
if (ImGui::CollapsingHeader("Material", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||||
ImGui::Indent(10.0f);
|
ImGui::Indent(10.0f);
|
||||||
|
|
||||||
if (ImGui::ColorEdit3("Color", &obj.material.color.x)) {
|
auto textureField = [&](const char* label, const char* idSuffix, std::string& path) {
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
bool changed = false;
|
||||||
}
|
ImGui::PushID(idSuffix);
|
||||||
|
ImGui::TextUnformatted(label);
|
||||||
if (ImGui::SliderFloat("Ambient", &obj.material.ambientStrength, 0.0f, 1.0f)) {
|
ImGui::SetNextItemWidth(-160);
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
char buf[512] = {};
|
||||||
}
|
std::snprintf(buf, sizeof(buf), "%s", path.c_str());
|
||||||
if (ImGui::SliderFloat("Specular", &obj.material.specularStrength, 0.0f, 2.0f)) {
|
if (ImGui::InputText("##Path", buf, sizeof(buf))) {
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
path = buf;
|
||||||
}
|
changed = true;
|
||||||
if (ImGui::SliderFloat("Shininess", &obj.material.shininess, 1.0f, 256.0f)) {
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
|
||||||
if (ImGui::SliderFloat("Texture Mix", &obj.material.textureMix, 0.0f, 1.0f)) {
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::Text("Textures");
|
|
||||||
char albedoBuf[512] = {};
|
|
||||||
std::snprintf(albedoBuf, sizeof(albedoBuf), "%s", obj.albedoTexturePath.c_str());
|
|
||||||
if (ImGui::InputText("Albedo##Tex", albedoBuf, sizeof(albedoBuf))) {
|
|
||||||
obj.albedoTexturePath = albedoBuf;
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
|
||||||
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile) && fileBrowser.isTextureFile(fs::directory_entry(fileBrowser.selectedFile))) {
|
|
||||||
if (ImGui::Button("Use Selected##Albedo")) {
|
|
||||||
obj.albedoTexturePath = fileBrowser.selectedFile.string();
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
ImGui::SameLine();
|
||||||
ImGui::Button("Use Selected##Albedo");
|
if (ImGui::SmallButton("Clear")) {
|
||||||
}
|
path.clear();
|
||||||
|
changed = true;
|
||||||
ImGui::Checkbox("Use Overlay", &obj.useOverlay);
|
|
||||||
char overlayBuf[512] = {};
|
|
||||||
std::snprintf(overlayBuf, sizeof(overlayBuf), "%s", obj.overlayTexturePath.c_str());
|
|
||||||
if (ImGui::InputText("Overlay##Tex", overlayBuf, sizeof(overlayBuf))) {
|
|
||||||
obj.overlayTexturePath = overlayBuf;
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
|
||||||
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile) && fileBrowser.isTextureFile(fs::directory_entry(fileBrowser.selectedFile))) {
|
|
||||||
if (ImGui::Button("Use Selected##Overlay")) {
|
|
||||||
obj.overlayTexturePath = fileBrowser.selectedFile.string();
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
ImGui::SameLine();
|
||||||
ImGui::Button("Use Selected##Overlay");
|
bool canUseTex = !fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile) &&
|
||||||
}
|
fileBrowser.isTextureFile(fs::directory_entry(fileBrowser.selectedFile));
|
||||||
|
ImGui::BeginDisabled(!canUseTex);
|
||||||
char normalBuf[512] = {};
|
std::string btnLabel = std::string("Use Selection##") + idSuffix;
|
||||||
std::snprintf(normalBuf, sizeof(normalBuf), "%s", obj.normalMapPath.c_str());
|
if (ImGui::SmallButton(btnLabel.c_str())) {
|
||||||
if (ImGui::InputText("Normal Map##Tex", normalBuf, sizeof(normalBuf))) {
|
path = fileBrowser.selectedFile.string();
|
||||||
obj.normalMapPath = normalBuf;
|
changed = true;
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
|
||||||
if (!fileBrowser.selectedFile.empty() && fs::exists(fileBrowser.selectedFile) && fileBrowser.isTextureFile(fs::directory_entry(fileBrowser.selectedFile))) {
|
|
||||||
if (ImGui::Button("Use Selected##Normal")) {
|
|
||||||
obj.normalMapPath = fileBrowser.selectedFile.string();
|
|
||||||
projectManager.currentProject.hasUnsavedChanges = true;
|
|
||||||
}
|
}
|
||||||
} else {
|
ImGui::EndDisabled();
|
||||||
ImGui::Button("Use Selected##Normal");
|
ImGui::PopID();
|
||||||
|
return changed;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool materialChanged = false;
|
||||||
|
|
||||||
|
ImGui::TextColored(ImVec4(0.8f, 0.7f, 1.0f, 1.0f), "Surface Inputs");
|
||||||
|
if (ImGui::ColorEdit3("Base Color", &obj.material.color.x)) {
|
||||||
|
materialChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float metallic = obj.material.specularStrength;
|
||||||
|
if (ImGui::SliderFloat("Metallic", &metallic, 0.0f, 1.0f)) {
|
||||||
|
obj.material.specularStrength = metallic;
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float smoothness = obj.material.shininess / 256.0f;
|
||||||
|
if (ImGui::SliderFloat("Smoothness", &smoothness, 0.0f, 1.0f)) {
|
||||||
|
smoothness = std::clamp(smoothness, 0.0f, 1.0f);
|
||||||
|
obj.material.shininess = smoothness * 256.0f;
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::SliderFloat("Ambient Light", &obj.material.ambientStrength, 0.0f, 1.0f)) {
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
if (ImGui::SliderFloat("Detail Mix", &obj.material.textureMix, 0.0f, 1.0f)) {
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "Maps");
|
||||||
|
materialChanged |= textureField("Base Map", "ObjAlbedo", obj.albedoTexturePath);
|
||||||
|
if (ImGui::Checkbox("Use Detail Map", &obj.useOverlay)) {
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
materialChanged |= textureField("Detail Map", "ObjOverlay", obj.overlayTexturePath);
|
||||||
|
materialChanged |= textureField("Normal Map", "ObjNormal", obj.normalMapPath);
|
||||||
|
|
||||||
|
ImGui::Spacing();
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Material Asset");
|
ImGui::Text("Material Asset");
|
||||||
static char matPathBuf[512] = {};
|
|
||||||
if (!obj.materialPath.empty()) {
|
char matPathBuf[512] = {};
|
||||||
std::snprintf(matPathBuf, sizeof(matPathBuf), "%s", obj.materialPath.c_str());
|
std::snprintf(matPathBuf, sizeof(matPathBuf), "%s", obj.materialPath.c_str());
|
||||||
}
|
ImGui::SetNextItemWidth(-1);
|
||||||
if (ImGui::InputText("Path##Mat", matPathBuf, sizeof(matPathBuf))) {
|
if (ImGui::InputText("##MaterialPath", matPathBuf, sizeof(matPathBuf))) {
|
||||||
// Defer applying until user hits button
|
|
||||||
}
|
|
||||||
if (ImGui::Button("Apply Material")) {
|
|
||||||
obj.materialPath = matPathBuf;
|
obj.materialPath = matPathBuf;
|
||||||
loadMaterialFromFile(obj);
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasMatPath = obj.materialPath.size() > 0;
|
||||||
|
ImGui::BeginDisabled(!hasMatPath);
|
||||||
|
if (ImGui::Button("Save Material")) {
|
||||||
|
saveMaterialToFile(obj);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Save Material")) {
|
if (ImGui::Button("Reload Material")) {
|
||||||
obj.materialPath = matPathBuf;
|
loadMaterialFromFile(obj);
|
||||||
saveMaterialToFile(obj);
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::BeginDisabled(!browserHasMaterial);
|
||||||
|
if (ImGui::Button("Load Selected")) {
|
||||||
|
obj.materialPath = selectedMaterialPath.string();
|
||||||
|
loadMaterialFromFile(obj);
|
||||||
|
materialChanged = true;
|
||||||
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
|
||||||
|
if (materialChanged) {
|
||||||
|
projectManager.currentProject.hasUnsavedChanges = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Unindent(10.0f);
|
ImGui::Unindent(10.0f);
|
||||||
@@ -1659,6 +1840,11 @@ void Engine::renderInspectorPanel() {
|
|||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (browserHasMaterial) {
|
||||||
|
ImGui::Spacing();
|
||||||
|
renderMaterialAssetPanel("Material Asset (File Browser)", true);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1835,27 +2021,162 @@ void Engine::renderViewport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Left-click picking inside viewport
|
||||||
if (mouseOverViewportImage &&
|
if (mouseOverViewportImage &&
|
||||||
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
ImGui::IsMouseClicked(ImGuiMouseButton_Left) &&
|
||||||
!ImGuizmo::IsUsing() && !ImGuizmo::IsOver())
|
!ImGuizmo::IsUsing() && !ImGuizmo::IsOver())
|
||||||
{
|
{
|
||||||
|
glm::mat4 invViewProj = glm::inverse(proj * view);
|
||||||
|
ImVec2 mousePos = ImGui::GetMousePos();
|
||||||
|
|
||||||
|
auto makeRay = [&](const ImVec2& pos) {
|
||||||
|
float x = (pos.x - imageMin.x) / (imageMax.x - imageMin.x);
|
||||||
|
float y = (pos.y - imageMin.y) / (imageMax.y - imageMin.y);
|
||||||
|
x = x * 2.0f - 1.0f;
|
||||||
|
y = 1.0f - y * 2.0f;
|
||||||
|
|
||||||
|
glm::vec4 nearPt = invViewProj * glm::vec4(x, y, -1.0f, 1.0f);
|
||||||
|
glm::vec4 farPt = invViewProj * glm::vec4(x, y, 1.0f, 1.0f);
|
||||||
|
nearPt /= nearPt.w;
|
||||||
|
farPt /= farPt.w;
|
||||||
|
|
||||||
|
glm::vec3 origin = glm::vec3(nearPt);
|
||||||
|
glm::vec3 dir = glm::normalize(glm::vec3(farPt - nearPt));
|
||||||
|
return std::make_pair(origin, dir);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto rayAabb = [](const glm::vec3& orig, const glm::vec3& dir, const glm::vec3& bmin, const glm::vec3& bmax, float& tHit) {
|
||||||
|
float tmin = -FLT_MAX;
|
||||||
|
float tmax = FLT_MAX;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (std::abs(dir[i]) < 1e-6f) {
|
||||||
|
if (orig[i] < bmin[i] || orig[i] > bmax[i]) return false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float invD = 1.0f / dir[i];
|
||||||
|
float t1 = (bmin[i] - orig[i]) * invD;
|
||||||
|
float t2 = (bmax[i] - orig[i]) * invD;
|
||||||
|
if (t1 > t2) std::swap(t1, t2);
|
||||||
|
tmin = std::max(tmin, t1);
|
||||||
|
tmax = std::min(tmax, t2);
|
||||||
|
if (tmin > tmax) return false;
|
||||||
|
}
|
||||||
|
tHit = (tmin >= 0.0f) ? tmin : tmax;
|
||||||
|
return tmax >= 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto raySphere = [](const glm::vec3& orig, const glm::vec3& dir, float radius, float& tHit) {
|
||||||
|
float b = glm::dot(dir, orig);
|
||||||
|
float c = glm::dot(orig, orig) - radius * radius;
|
||||||
|
float disc = b * b - c;
|
||||||
|
if (disc < 0.0f) return false;
|
||||||
|
float sqrtDisc = sqrtf(disc);
|
||||||
|
float t0 = -b - sqrtDisc;
|
||||||
|
float t1 = -b + sqrtDisc;
|
||||||
|
float t = (t0 >= 0.0f) ? t0 : t1;
|
||||||
|
if (t < 0.0f) return false;
|
||||||
|
tHit = t;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ray = makeRay(mousePos);
|
||||||
|
float closest = FLT_MAX;
|
||||||
|
int hitId = -1;
|
||||||
|
|
||||||
|
for (const auto& obj : sceneObjects) {
|
||||||
|
glm::vec3 aabbMin(-0.5f);
|
||||||
|
glm::vec3 aabbMax(0.5f);
|
||||||
|
|
||||||
|
glm::mat4 model(1.0f);
|
||||||
|
model = glm::translate(model, obj.position);
|
||||||
|
model = glm::rotate(model, glm::radians(obj.rotation.x), glm::vec3(1, 0, 0));
|
||||||
|
model = glm::rotate(model, glm::radians(obj.rotation.y), glm::vec3(0, 1, 0));
|
||||||
|
model = glm::rotate(model, glm::radians(obj.rotation.z), glm::vec3(0, 0, 1));
|
||||||
|
model = glm::scale(model, obj.scale);
|
||||||
|
|
||||||
|
glm::mat4 invModel = glm::inverse(model);
|
||||||
|
glm::vec3 localOrigin = glm::vec3(invModel * glm::vec4(ray.first, 1.0f));
|
||||||
|
glm::vec3 localDir = glm::normalize(glm::vec3(invModel * glm::vec4(ray.second, 0.0f)));
|
||||||
|
|
||||||
|
float hitT = 0.0f;
|
||||||
|
bool hit = false;
|
||||||
|
switch (obj.type) {
|
||||||
|
case ObjectType::Cube:
|
||||||
|
hit = rayAabb(localOrigin, localDir, glm::vec3(-0.5f), glm::vec3(0.5f), hitT);
|
||||||
|
break;
|
||||||
|
case ObjectType::Sphere:
|
||||||
|
hit = raySphere(localOrigin, localDir, 0.5f, hitT);
|
||||||
|
break;
|
||||||
|
case ObjectType::Capsule:
|
||||||
|
hit = rayAabb(localOrigin, localDir, glm::vec3(-0.35f, -0.9f, -0.35f), glm::vec3(0.35f, 0.9f, 0.35f), hitT);
|
||||||
|
break;
|
||||||
|
case ObjectType::OBJMesh: {
|
||||||
|
const auto* info = g_objLoader.getMeshInfo(obj.meshId);
|
||||||
|
if (info && info->boundsMin.x < info->boundsMax.x) {
|
||||||
|
aabbMin = info->boundsMin;
|
||||||
|
aabbMax = info->boundsMax;
|
||||||
|
}
|
||||||
|
hit = rayAabb(localOrigin, localDir, aabbMin, aabbMax, hitT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ObjectType::Model: {
|
||||||
|
const auto* info = getModelLoader().getMeshInfo(obj.meshId);
|
||||||
|
if (info && info->boundsMin.x < info->boundsMax.x) {
|
||||||
|
aabbMin = info->boundsMin;
|
||||||
|
aabbMax = info->boundsMax;
|
||||||
|
}
|
||||||
|
hit = rayAabb(localOrigin, localDir, aabbMin, aabbMax, hitT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ObjectType::DirectionalLight:
|
||||||
|
case ObjectType::PointLight:
|
||||||
|
case ObjectType::SpotLight:
|
||||||
|
case ObjectType::AreaLight:
|
||||||
|
hit = raySphere(localOrigin, localDir, 0.3f, hitT);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hit && hitT < closest && hitT >= 0.0f) {
|
||||||
|
closest = hitT;
|
||||||
|
hitId = obj.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewportController.setFocused(true);
|
||||||
|
if (hitId != -1) {
|
||||||
|
selectedObjectId = hitId;
|
||||||
|
} else {
|
||||||
|
selectedObjectId = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mouseOverViewportImage && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
||||||
viewportController.setFocused(true);
|
viewportController.setFocused(true);
|
||||||
cursorLocked = true;
|
cursorLocked = true;
|
||||||
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
camera.firstMouse = true;
|
camera.firstMouse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cursorLocked && !ImGui::IsMouseDown(ImGuiMouseButton_Right)) {
|
||||||
|
cursorLocked = false;
|
||||||
|
glfwSetInputMode(editorWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||||
|
camera.firstMouse = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlay hint
|
// Overlay hint
|
||||||
ImGui::SetCursorPos(ImVec2(10, 30));
|
ImGui::SetCursorPos(ImVec2(10, 30));
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(1, 1, 1, 0.3f),
|
ImVec4(1, 1, 1, 0.3f),
|
||||||
"WASD: Move | QE: Up/Down | Shift: Sprint | ESC: Release | F11: Fullscreen"
|
"Hold RMB: Look & Move | LMB: Select | WASD+QE: Move | ESC: Release | F11: Fullscreen"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (viewportController.isViewportFocused()) {
|
if (cursorLocked) {
|
||||||
ImGui::SetCursorPos(ImVec2(10, 50));
|
ImGui::SetCursorPos(ImVec2(10, 50));
|
||||||
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Camera Active");
|
ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Freelook Active");
|
||||||
|
} else if (viewportController.isViewportFocused()) {
|
||||||
|
ImGui::SetCursorPos(ImVec2(10, 50));
|
||||||
|
ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "Viewport Focused");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool windowFocused = ImGui::IsWindowFocused();
|
bool windowFocused = ImGui::IsWindowFocused();
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 boundsMin(FLT_MAX);
|
||||||
|
glm::vec3 boundsMax(-FLT_MAX);
|
||||||
|
|
||||||
// Process all meshes in the scene
|
// Process all meshes in the scene
|
||||||
std::vector<float> vertices;
|
std::vector<float> vertices;
|
||||||
result.meshCount = scene->mNumMeshes;
|
result.meshCount = scene->mNumMeshes;
|
||||||
@@ -125,7 +128,7 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
|
|||||||
result.hasTangents = false;
|
result.hasTangents = false;
|
||||||
|
|
||||||
// Process the root node recursively
|
// Process the root node recursively
|
||||||
processNode(scene->mRootNode, scene, vertices);
|
processNode(scene->mRootNode, scene, vertices, boundsMin, boundsMax);
|
||||||
|
|
||||||
// Check mesh properties
|
// Check mesh properties
|
||||||
for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
|
for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
|
||||||
@@ -152,6 +155,9 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
|
|||||||
loaded.hasNormals = result.hasNormals;
|
loaded.hasNormals = result.hasNormals;
|
||||||
loaded.hasTexCoords = result.hasTexCoords;
|
loaded.hasTexCoords = result.hasTexCoords;
|
||||||
|
|
||||||
|
loaded.boundsMin = boundsMin;
|
||||||
|
loaded.boundsMax = boundsMax;
|
||||||
|
|
||||||
loadedMeshes.push_back(std::move(loaded));
|
loadedMeshes.push_back(std::move(loaded));
|
||||||
|
|
||||||
result.success = true;
|
result.success = true;
|
||||||
@@ -165,20 +171,20 @@ ModelLoadResult ModelLoader::loadModel(const std::string& filepath) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelLoader::processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices) {
|
void ModelLoader::processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax) {
|
||||||
// Process all meshes in this node
|
// Process all meshes in this node
|
||||||
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
|
for (unsigned int i = 0; i < node->mNumMeshes; i++) {
|
||||||
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
|
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
|
||||||
processMesh(mesh, scene, vertices);
|
processMesh(mesh, scene, vertices, boundsMin, boundsMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process children nodes
|
// Process children nodes
|
||||||
for (unsigned int i = 0; i < node->mNumChildren; i++) {
|
for (unsigned int i = 0; i < node->mNumChildren; i++) {
|
||||||
processNode(node->mChildren[i], scene, vertices);
|
processNode(node->mChildren[i], scene, vertices, boundsMin, boundsMax);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelLoader::processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices) {
|
void ModelLoader::processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax) {
|
||||||
// Process each face
|
// Process each face
|
||||||
for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
|
for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
|
||||||
aiFace face = mesh->mFaces[i];
|
aiFace face = mesh->mFaces[i];
|
||||||
@@ -192,6 +198,13 @@ void ModelLoader::processMesh(aiMesh* mesh, const aiScene* scene, std::vector<fl
|
|||||||
vertices.push_back(mesh->mVertices[index].y);
|
vertices.push_back(mesh->mVertices[index].y);
|
||||||
vertices.push_back(mesh->mVertices[index].z);
|
vertices.push_back(mesh->mVertices[index].z);
|
||||||
|
|
||||||
|
boundsMin.x = std::min(boundsMin.x, mesh->mVertices[index].x);
|
||||||
|
boundsMin.y = std::min(boundsMin.y, mesh->mVertices[index].y);
|
||||||
|
boundsMin.z = std::min(boundsMin.z, mesh->mVertices[index].z);
|
||||||
|
boundsMax.x = std::max(boundsMax.x, mesh->mVertices[index].x);
|
||||||
|
boundsMax.y = std::max(boundsMax.y, mesh->mVertices[index].y);
|
||||||
|
boundsMax.z = std::max(boundsMax.z, mesh->mVertices[index].z);
|
||||||
|
|
||||||
// Normal
|
// Normal
|
||||||
if (mesh->mNormals) {
|
if (mesh->mNormals) {
|
||||||
vertices.push_back(mesh->mNormals[index].x);
|
vertices.push_back(mesh->mNormals[index].x);
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ private:
|
|||||||
ModelLoader& operator=(const ModelLoader&) = delete;
|
ModelLoader& operator=(const ModelLoader&) = delete;
|
||||||
|
|
||||||
// Process Assimp scene
|
// Process Assimp scene
|
||||||
void processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices);
|
void processNode(aiNode* node, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax);
|
||||||
void processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices);
|
void processMesh(aiMesh* mesh, const aiScene* scene, std::vector<float>& vertices, glm::vec3& boundsMin, glm::vec3& boundsMax);
|
||||||
|
|
||||||
// Storage for loaded meshes (reusing OBJLoader::LoadedMesh structure)
|
// Storage for loaded meshes (reusing OBJLoader::LoadedMesh structure)
|
||||||
std::vector<OBJLoader::LoadedMesh> loadedMeshes;
|
std::vector<OBJLoader::LoadedMesh> loadedMeshes;
|
||||||
|
|||||||
@@ -296,6 +296,9 @@ int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
|
|||||||
faceCount += static_cast<int>(shape.mesh.num_face_vertices.size());
|
faceCount += static_cast<int>(shape.mesh.num_face_vertices.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 boundsMin(FLT_MAX);
|
||||||
|
glm::vec3 boundsMax(-FLT_MAX);
|
||||||
|
|
||||||
for (const auto& shape : shapes) {
|
for (const auto& shape : shapes) {
|
||||||
size_t indexOffset = 0;
|
size_t indexOffset = 0;
|
||||||
for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); f++) {
|
for (size_t f = 0; f < shape.mesh.num_face_vertices.size(); f++) {
|
||||||
@@ -317,6 +320,13 @@ int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
|
|||||||
tv.pos.y = attrib.vertices[3 * size_t(idx.vertex_index) + 1];
|
tv.pos.y = attrib.vertices[3 * size_t(idx.vertex_index) + 1];
|
||||||
tv.pos.z = attrib.vertices[3 * size_t(idx.vertex_index) + 2];
|
tv.pos.z = attrib.vertices[3 * size_t(idx.vertex_index) + 2];
|
||||||
|
|
||||||
|
boundsMin.x = std::min(boundsMin.x, tv.pos.x);
|
||||||
|
boundsMin.y = std::min(boundsMin.y, tv.pos.y);
|
||||||
|
boundsMin.z = std::min(boundsMin.z, tv.pos.z);
|
||||||
|
boundsMax.x = std::max(boundsMax.x, tv.pos.x);
|
||||||
|
boundsMax.y = std::max(boundsMax.y, tv.pos.y);
|
||||||
|
boundsMax.z = std::max(boundsMax.z, tv.pos.z);
|
||||||
|
|
||||||
if (idx.texcoord_index >= 0 && !attrib.texcoords.empty()) {
|
if (idx.texcoord_index >= 0 && !attrib.texcoords.empty()) {
|
||||||
tv.uv.x = attrib.texcoords[2 * size_t(idx.texcoord_index) + 0];
|
tv.uv.x = attrib.texcoords[2 * size_t(idx.texcoord_index) + 0];
|
||||||
tv.uv.y = attrib.texcoords[2 * size_t(idx.texcoord_index) + 1];
|
tv.uv.y = attrib.texcoords[2 * size_t(idx.texcoord_index) + 1];
|
||||||
@@ -378,6 +388,8 @@ int OBJLoader::loadOBJ(const std::string& filepath, std::string& errorMsg) {
|
|||||||
loaded.faceCount = faceCount;
|
loaded.faceCount = faceCount;
|
||||||
loaded.hasNormals = hasNormalsInFile;
|
loaded.hasNormals = hasNormalsInFile;
|
||||||
loaded.hasTexCoords = !attrib.texcoords.empty();
|
loaded.hasTexCoords = !attrib.texcoords.empty();
|
||||||
|
loaded.boundsMin = boundsMin;
|
||||||
|
loaded.boundsMax = boundsMax;
|
||||||
|
|
||||||
loadedMeshes.push_back(std::move(loaded));
|
loadedMeshes.push_back(std::move(loaded));
|
||||||
return static_cast<int>(loadedMeshes.size() - 1);
|
return static_cast<int>(loadedMeshes.size() - 1);
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ public:
|
|||||||
int faceCount = 0;
|
int faceCount = 0;
|
||||||
bool hasNormals = false;
|
bool hasNormals = false;
|
||||||
bool hasTexCoords = false;
|
bool hasTexCoords = false;
|
||||||
|
glm::vec3 boundsMin = glm::vec3(FLT_MAX);
|
||||||
|
glm::vec3 boundsMax = glm::vec3(-FLT_MAX);
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
Reference in New Issue
Block a user