Spaces:
Build error
Build error
| namespace hhb { | |
| namespace render { | |
| // 顶点着色器源代码 | |
| const char* vertexShaderSource = "\n" | |
| "#version 330\n" | |
| "layout (location = 0) in vec3 aPos;\n" | |
| "layout (location = 1) in vec3 aNormal;\n" | |
| "\n" | |
| "uniform mat4 model;\n" | |
| "uniform mat4 view;\n" | |
| "uniform mat4 projection;\n" | |
| "\n" | |
| "out vec3 Normal;\n" | |
| "out vec3 FragPos;\n" | |
| "\n" | |
| "void main()\n" | |
| "{\n" | |
| " FragPos = vec3(model * vec4(aPos, 1.0));\n" | |
| " Normal = mat3(transpose(inverse(model))) * aNormal;\n" | |
| " gl_Position = projection * view * model * vec4(aPos, 1.0);\n" | |
| "}\n"; | |
| // 片元着色器源代码 | |
| const char* fragmentShaderSource = "\n" | |
| "#version 330\n" | |
| "out vec4 FragColor;\n" | |
| "\n" | |
| "in vec3 Normal;\n" | |
| "in vec3 FragPos;\n" | |
| "\n" | |
| "uniform vec3 lightPos;\n" | |
| "uniform vec3 viewPos;\n" | |
| "uniform vec3 objectColor;\n" | |
| "uniform vec3 lightColor;\n" | |
| "uniform float metallic;\n" | |
| "uniform float roughness;\n" | |
| "uniform bool isSelected;\n" | |
| "uniform vec3 highlightColor;\n" | |
| "\n" | |
| "// 法线分布函数 (Trowbridge-Reitz GGX)\n" | |
| "float DistributionGGX(vec3 N, vec3 H, float roughness)\n" | |
| "{\n" | |
| " float a = roughness * roughness;\n" | |
| " float a2 = a * a;\n" | |
| " float NdotH = max(dot(N, H), 0.0);\n" | |
| " float NdotH2 = NdotH * NdotH;\n" | |
| " \n" | |
| " float nom = a2;\n" | |
| " float denom = (NdotH2 * (a2 - 1.0) + 1.0);\n" | |
| " denom = 3.1415926535 * denom * denom;\n" | |
| " \n" | |
| " return nom / denom;\n" | |
| "}\n" | |
| "\n" | |
| "// 几何函数 (Schlick-GGX)\n" | |
| "float GeometrySchlickGGX(float NdotV, float roughness)\n" | |
| "{\n" | |
| " float r = (roughness + 1.0);\n" | |
| " float k = (r * r) / 8.0;\n" | |
| " \n" | |
| " float nom = NdotV;\n" | |
| " float denom = NdotV * (1.0 - k) + k;\n" | |
| " \n" | |
| " return nom / denom;\n" | |
| "}\n" | |
| "\n" | |
| "// 几何函数 (Smith)\n" | |
| "float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)\n" | |
| "{\n" | |
| " float NdotV = max(dot(N, V), 0.0);\n" | |
| " float NdotL = max(dot(N, L), 0.0);\n" | |
| " float ggx2 = GeometrySchlickGGX(NdotV, roughness);\n" | |
| " float ggx1 = GeometrySchlickGGX(NdotL, roughness);\n" | |
| " \n" | |
| " return ggx1 * ggx2;\n" | |
| "}\n" | |
| "\n" | |
| "// 菲涅尔方程 (Fresnel-Schlick)\n" | |
| "vec3 FresnelSchlick(float cosTheta, vec3 F0)\n" | |
| "{\n" | |
| " return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);\n" | |
| "}\n" | |
| "\n" | |
| "void main()\n" | |
| "{\n" | |
| " if (isSelected) {\n" | |
| " FragColor = vec4(highlightColor, 1.0);\n" | |
| " return;\n" | |
| " }\n" | |
| " \n" | |
| " vec3 N = normalize(Normal);\n" | |
| " // 对于没有法线的模型或者法线错误的情况,如果面向背对,翻转法线\n" | |
| " if (!gl_FrontFacing) N = -N;\n" | |
| " \n" | |
| " vec3 V = normalize(viewPos - FragPos);\n" | |
| " \n" | |
| " vec3 F0 = vec3(0.04); \n" | |
| " F0 = mix(F0, objectColor, metallic);\n" | |
| " \n" | |
| " // 反射率方程\n" | |
| " vec3 Lo = vec3(0.0);\n" | |
| " \n" | |
| " // 计算每个光源的辐射度 (这里只有一个点光源)\n" | |
| " vec3 L = normalize(lightPos - FragPos);\n" | |
| " vec3 H = normalize(V + L);\n" | |
| " \n" | |
| " // 衰减\n" | |
| " float distance = length(lightPos - FragPos);\n" | |
| " float attenuation = 1.0 / (distance * distance * 0.01 + 1.0); // 调整了衰减系数,防止模型全黑\n" | |
| " vec3 radiance = lightColor * attenuation * 500.0; // 增加了光源强度\n" | |
| " \n" | |
| " // Cook-Torrance BRDF\n" | |
| " float NDF = DistributionGGX(N, H, roughness);\n" | |
| " float G = GeometrySmith(N, V, L, roughness);\n" | |
| " vec3 F = FresnelSchlick(max(dot(H, V), 0.0), F0);\n" | |
| " \n" | |
| " vec3 numerator = NDF * G * F;\n" | |
| " float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.0001;\n" | |
| " vec3 specular = numerator / denominator;\n" | |
| " \n" | |
| " vec3 kS = F;\n" | |
| " vec3 kD = vec3(1.0) - kS;\n" | |
| " kD *= 1.0 - metallic;\n" | |
| " \n" | |
| " float NdotL = max(dot(N, L), 0.0);\n" | |
| " \n" | |
| " Lo += (kD * objectColor / 3.1415926535 + specular) * radiance * NdotL;\n" | |
| " \n" | |
| " // 环境光 (基础的)\n" | |
| " vec3 ambient = vec3(0.2) * objectColor * (1.0 - metallic);\n" | |
| " \n" | |
| " vec3 color = ambient + Lo;\n" | |
| " \n" | |
| " // HDR色调映射\n" | |
| " color = color / (color + vec3(1.0));\n" | |
| " // Gamma校正\n" | |
| " color = pow(color, vec3(1.0/2.2)); \n" | |
| " \n" | |
| " FragColor = vec4(color, 1.0);\n" | |
| "}\n"; | |
| const char* labelVertexShaderSource = "\n" | |
| "#version 330\n" | |
| "layout (location = 0) in vec3 aPos;\n" | |
| "layout (location = 1) in vec3 aLabelColor;\n" | |
| "\n" | |
| "uniform mat4 model;\n" | |
| "uniform mat4 view;\n" | |
| "uniform mat4 projection;\n" | |
| "\n" | |
| "out vec3 LabelColor;\n" | |
| "\n" | |
| "void main()\n" | |
| "{\n" | |
| " LabelColor = aLabelColor;\n" | |
| " gl_Position = projection * view * model * vec4(aPos, 1.0);\n" | |
| "}\n"; | |
| const char* labelFragmentShaderSource = "\n" | |
| "#version 330\n" | |
| "out vec4 FragColor;\n" | |
| "\n" | |
| "in vec3 LabelColor;\n" | |
| "\n" | |
| "void main()\n" | |
| "{\n" | |
| " FragColor = vec4(LabelColor, 1.0);\n" | |
| "}\n"; | |
| RenderManager::RenderManager(int width, int height, const std::string& title) | |
| : width(width), height(height), title(title), window(nullptr), | |
| VAO(0), VBO(0), shaderProgram(0), | |
| labelVAO(0), labelVBO(0), labelShaderProgram(0), | |
| labelModeActive(false), faceCategoriesComputed(false), | |
| triangleCount(0), | |
| trianglePool(nullptr), | |
| zoom(1.0f), frameCount(0), lastFpsUpdate(0.0f), fps(0.0f), | |
| metallic(0.5f), roughness(0.5f), | |
| loadTime(0.0f), memoryUsage(0), | |
| selectedTriangle(nullptr), selectedTriangleIndex(-1), pickTime(0.0f), | |
| showBVH(false), showHighlight(false), | |
| currentHighlightType(HighlightType::None), | |
| highlightCalculated(false), | |
| highlightBlinkTimer_(0.0f), highlightBlinkState_(true) { | |
| commandDispatcher.start(8080); | |
| cameraPosition[0] = 0.0f; | |
| cameraPosition[1] = 0.0f; | |
| cameraPosition[2] = 5.0f; | |
| // 初始化相机旋转 | |
| cameraRotation[0] = 0.0f; | |
| cameraRotation[1] = 0.0f; | |
| // 初始化时间 | |
| lastFrame = std::chrono::steady_clock::now(); | |
| deltaTime = 0.0f; | |
| // 自动旋转初始化 | |
| autoRotate = false; | |
| autoRotateSpeed = 0.5f; | |
| automationMode = false; | |
| // 相机动画初始化 | |
| cameraAnimating = false; | |
| targetCameraPos[0] = targetCameraPos[1] = targetCameraPos[2] = 0.0f; | |
| targetCameraRot[0] = targetCameraRot[1] = 0.0f; | |
| cameraPosStart[0] = cameraPosStart[1] = cameraPosStart[2] = 0.0f; | |
| cameraRotStart[0] = cameraRotStart[1] = 0.0f; | |
| targetZoom = 1.0f; | |
| zoomStart = 1.0f; | |
| cameraAnimDuration = 0.5f; | |
| // 初始化文件路径缓冲区 | |
| memset(filePathBuffer, 0, sizeof(filePathBuffer)); | |
| } | |
| RenderManager::~RenderManager() { | |
| // 等待计算线程完成 | |
| if (calculationThread.joinable()) { | |
| calculationThread.join(); | |
| } | |
| // 清理对象池 | |
| if (trianglePool) { | |
| delete trianglePool; | |
| trianglePool = nullptr; | |
| } | |
| // 清理ImGui | |
| shutdownImGui(); | |
| // 清理OpenGL资源 | |
| glDeleteVertexArrays(1, &VAO); | |
| glDeleteBuffers(1, &VBO); | |
| glDeleteProgram(shaderProgram); | |
| glDeleteVertexArrays(1, &labelVAO); | |
| glDeleteBuffers(1, &labelVBO); | |
| glDeleteProgram(labelShaderProgram); | |
| // 清理GLFW | |
| if (window) { | |
| glfwDestroyWindow(window); | |
| } | |
| glfwTerminate(); | |
| } | |
| void RenderManager::initSkills() { | |
| // 获取技能注册表实例 | |
| auto& registry = skill::SkillRegistry::getInstance(); | |
| // 注册技能 | |
| registry.registerSkill("auto_rotate", std::make_unique<skill::AutoRotateSkill>(*this)); | |
| registry.registerSkill("reset_camera", std::make_unique<skill::ResetCameraSkill>(*this)); | |
| registry.registerSkill("zoom_in", std::make_unique<skill::ZoomInSkill>(*this)); | |
| registry.registerSkill("rotate", std::make_unique<skill::RotateSkill>(*this)); | |
| registry.registerSkill("optimize_view", std::make_unique<skill::OptimizeViewSkill>(*this)); | |
| registry.registerSkill("measure_model", std::make_unique<skill::MeasureModelSkill>(*this)); | |
| registry.registerSkill("analyze_geometry", std::make_unique<skill::AnalyzeGeometrySkill>(*this)); | |
| std::cout << "Skills initialized successfully" << std::endl; | |
| } | |
| bool RenderManager::initialize() { | |
| // 初始化GLFW | |
| if (!glfwInit()) { | |
| std::cerr << "Failed to initialize GLFW" << std::endl; | |
| return false; | |
| } | |
| // 设置GLFW窗口属性 | |
| glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | |
| glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); | |
| glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | |
| // 创建窗口 | |
| window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr); | |
| if (!window) { | |
| std::cerr << "Failed to create GLFW window" << std::endl; | |
| glfwTerminate(); | |
| return false; | |
| } | |
| // 设置当前上下文 | |
| glfwMakeContextCurrent(window); | |
| // 初始化 GLAD | |
| if (!gladLoadGL((GLADloadfunc)glfwGetProcAddress)) { | |
| std::cout << "Failed to initialize GLAD" << std::endl; | |
| return false; | |
| } | |
| // 设置视口 | |
| glViewport(0, 0, width, height); | |
| // 启用深度测试 | |
| glEnable(GL_DEPTH_TEST); | |
| // 初始化着色器 | |
| if (!initShaders()) { | |
| return false; | |
| } | |
| // 初始化标签模式着色器 | |
| if (!initLabelShaders()) { | |
| std::cerr << "Warning: Label shader initialization failed" << std::endl; | |
| } | |
| // 初始化缓冲区 | |
| if (!initBuffers()) { | |
| return false; | |
| } | |
| // 初始化ImGui | |
| initImGui(); | |
| initSkills(); | |
| // 初始化具身智能 Agent:将几何分析能力注册为 LLM 可调用的工具 | |
| // 设置结果回调,当工具执行完成后自动更新 OpenGL 高亮 | |
| embodiedAgent_.setResultCallback([this](const std::vector<int>& indices, HighlightType type, const std::string& desc) { | |
| onToolResult(indices, type, desc); | |
| }); | |
| // 默认配置(用户可通过 UI 修改) | |
| embodiedAgent_.initialize( | |
| "https://api.openai.com", | |
| "", | |
| "gpt-4o-mini", | |
| &geometryAPI | |
| ); | |
| return true; | |
| } | |
| void RenderManager::render() { | |
| if (!automationMode) { | |
| ImGui_ImplOpenGL3_NewFrame(); | |
| ImGui_ImplGlfw_NewFrame(); | |
| ImGui::NewFrame(); | |
| updateImGui(); | |
| } | |
| // 处理来自网络的命令 | |
| while (commandDispatcher.hasCommand()) { | |
| hhb::core::Command cmd = commandDispatcher.getCommand(); | |
| std::cout << "Processing command: " << cmd.action << " with value: " << cmd.value << std::endl; | |
| if (trianglePool) { | |
| if (calculationThread.joinable()) { | |
| calculationThread.join(); | |
| } | |
| calculationThread = std::thread([this, cmd]() { | |
| std::cout << "Starting analysis in background thread..." << std::endl; | |
| std::vector<hhb::core::Triangle*> result_parts; | |
| HighlightType htype = HighlightType::None; | |
| std::string desc; | |
| if (cmd.action == "check_thickness") { | |
| result_parts = geometryAPI.getThinParts(static_cast<float>(cmd.value)); | |
| htype = HighlightType::ThinParts; | |
| desc = "薄弱部位 (厚度<" + std::to_string(static_cast<int>(cmd.value)) + "mm)"; | |
| std::cout << "Found " << result_parts.size() << " thin parts" << std::endl; | |
| } | |
| else if (cmd.action == "find_curved_surfaces") { | |
| result_parts = geometryAPI.getCurvedSurfaces(static_cast<float>(cmd.value)); | |
| htype = HighlightType::CurvedSurfaces; | |
| desc = "曲面/曲线区域 (曲率>" + std::to_string(static_cast<float>(cmd.value)) + ")"; | |
| std::cout << "Found " << result_parts.size() << " curved surface triangles" << std::endl; | |
| } | |
| else if (cmd.action == "find_sharp_edges") { | |
| result_parts = geometryAPI.getSharpEdges(static_cast<float>(cmd.value)); | |
| htype = HighlightType::SharpEdges; | |
| desc = "锐角/棱边区域 (角度>" + std::to_string(static_cast<int>(cmd.value)) + "°)"; | |
| std::cout << "Found " << result_parts.size() << " sharp edge triangles" << std::endl; | |
| } | |
| else if (cmd.action == "find_flat_surfaces") { | |
| result_parts = geometryAPI.getFlatSurfaces(static_cast<float>(cmd.value)); | |
| htype = HighlightType::FlatSurfaces; | |
| desc = "平面区域 (平坦度<" + std::to_string(static_cast<float>(cmd.value)) + ")"; | |
| std::cout << "Found " << result_parts.size() << " flat surface triangles" << std::endl; | |
| } | |
| else { | |
| std::cout << "Unknown command action: " << cmd.action << std::endl; | |
| return; | |
| } | |
| std::unordered_map<hhb::core::Triangle*, int> ptrToIndex; | |
| int idx = 0; | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| ptrToIndex[tri] = idx; | |
| idx++; | |
| }); | |
| std::vector<int> indices; | |
| for (const auto& tri : result_parts) { | |
| auto it = ptrToIndex.find(tri); | |
| if (it != ptrToIndex.end()) { | |
| indices.push_back(it->second); | |
| } | |
| } | |
| std::cout << "Matched " << indices.size() << " / " << result_parts.size() | |
| << " triangles to VBO indices" << std::endl; | |
| { | |
| std::lock_guard<std::mutex> lock(highlightMutex); | |
| newHighlightIndices = std::move(indices); | |
| currentHighlightType = htype; | |
| lastAnalysisDesc = desc; | |
| } | |
| highlightCalculated = true; | |
| std::cout << "Analysis completed: " << desc << std::endl; | |
| }); | |
| } | |
| } | |
| if (highlightCalculated) { | |
| { | |
| std::lock_guard<std::mutex> lock(highlightMutex); | |
| highlightIndices = std::move(newHighlightIndices); | |
| } | |
| showHighlight = true; | |
| highlightCalculated = false; | |
| std::cout << "Updated highlight indices: " << highlightIndices.size() << " parts (" << lastAnalysisDesc << ")" << std::endl; | |
| } | |
| // 检查具身智能 Agent 的异步结果 | |
| if (embodiedAgent_.isProcessing()) { | |
| embodiedAgent_.checkPendingResult(); | |
| } | |
| // 开启深度测试与背景清理 | |
| glEnable(GL_DEPTH_TEST); | |
| glClearColor(0.1f, 0.1f, 0.1f, 1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| // 使用着色器程序 | |
| glUseProgram(shaderProgram); | |
| // 计算 deltaTime | |
| auto currentFrame = std::chrono::steady_clock::now(); | |
| deltaTime = std::chrono::duration<float>(currentFrame - lastFrame).count(); | |
| lastFrame = currentFrame; | |
| // 自动旋转逻辑 | |
| if (autoRotate) { | |
| cameraRotation[1] += autoRotateSpeed * deltaTime; | |
| if (cameraRotation[1] > 360.0f) { | |
| cameraRotation[1] -= 360.0f; | |
| } | |
| } | |
| // 相机平滑动画更新 | |
| if (cameraAnimating) { | |
| auto now = std::chrono::steady_clock::now(); | |
| float elapsed = std::chrono::duration<float>(now - cameraAnimStart).count(); | |
| float t = std::min(elapsed / cameraAnimDuration, 1.0f); | |
| // 使用 smoothstep 进行平滑插值 | |
| t = t * t * (3.0f - 2.0f * t); | |
| // 插值相机位置 | |
| cameraPosition[0] = cameraPosStart[0] + (targetCameraPos[0] - cameraPosStart[0]) * t; | |
| cameraPosition[1] = cameraPosStart[1] + (targetCameraPos[1] - cameraPosStart[1]) * t; | |
| cameraPosition[2] = cameraPosStart[2] + (targetCameraPos[2] - cameraPosStart[2]) * t; | |
| // 插值相机旋转 | |
| cameraRotation[0] = cameraRotStart[0] + (targetCameraRot[0] - cameraRotStart[0]) * t; | |
| cameraRotation[1] = cameraRotStart[1] + (targetCameraRot[1] - cameraRotStart[1]) * t; | |
| // 插值缩放 | |
| zoom = zoomStart + (targetZoom - zoomStart) * t; | |
| // 检查动画是否完成 | |
| if (t >= 1.0f) { | |
| cameraAnimating = false; | |
| cameraPosition[0] = targetCameraPos[0]; | |
| cameraPosition[1] = targetCameraPos[1]; | |
| cameraPosition[2] = targetCameraPos[2]; | |
| cameraRotation[0] = targetCameraRot[0]; | |
| cameraRotation[1] = targetCameraRot[1]; | |
| zoom = targetZoom; | |
| } | |
| } | |
| // 更新 FPS | |
| updateFPS(); | |
| // 设置模型矩阵 - 简单的单位矩阵 | |
| float model[16] = { | |
| 1.0f, 0.0f, 0.0f, 0.0f, | |
| 0.0f, 1.0f, 0.0f, 0.0f, | |
| 0.0f, 0.0f, 1.0f, 0.0f, | |
| 0.0f, 0.0f, 0.0f, 1.0f | |
| }; | |
| GLint modelLoc = glGetUniformLocation(shaderProgram, "model"); | |
| glUniformMatrix4fv(modelLoc, 1, GL_FALSE, model); | |
| // 设置视图矩阵 (CAD Orbit Camera) | |
| float distance = 5.0f / zoom; | |
| // 我们围绕 target (cameraPosition) 进行旋转 | |
| float cx = cosf(cameraRotation[0]); | |
| float sx = sinf(cameraRotation[0]); | |
| float cy = cosf(cameraRotation[1]); | |
| float sy = sinf(cameraRotation[1]); | |
| // 相机在世界坐标系下的实际位置 | |
| // x = target_x + dist * cos(pitch) * sin(yaw) | |
| // y = target_y + dist * sin(pitch) | |
| // z = target_z + dist * cos(pitch) * cos(yaw) | |
| float camPosX = cameraPosition[0] + distance * cx * sy; | |
| float camPosY = cameraPosition[1] + distance * sx; | |
| float camPosZ = cameraPosition[2] + distance * cx * cy; | |
| // View矩阵(列主序) | |
| float view[16] = { | |
| cy, -sx*sy, -cx*sy, 0.0f, | |
| 0.0f, cx, -sx, 0.0f, | |
| sy, sx*cy, cx*cy, 0.0f, | |
| 0.0f, 0.0f, -distance, 1.0f | |
| }; | |
| // 还要把 target 平移加进去,由于我们在原点旋转后再平移,其实 View 矩阵是: | |
| // View = LookAt(camPos, target, up) | |
| // 根据标准推导,旋转矩阵不变,平移部分为 -R * camPos | |
| // R 是前 3x3 转置,上面已经是 R 了 | |
| view[12] = -(view[0]*camPosX + view[4]*camPosY + view[8]*camPosZ); | |
| view[13] = -(view[1]*camPosX + view[5]*camPosY + view[9]*camPosZ); | |
| view[14] = -(view[2]*camPosX + view[6]*camPosY + view[10]*camPosZ); | |
| GLint viewLoc = glGetUniformLocation(shaderProgram, "view"); | |
| glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view); | |
| // 设置投影矩阵 | |
| float aspect = (float)width / (float)height; | |
| float fov = 45.0f; | |
| float nearPlane = 0.1f; | |
| float farPlane = 1000.0f; | |
| float f = 1.0f / tanf(fov * 0.5f * 3.1415926535f / 180.0f); | |
| float projection[16] = { | |
| f / aspect, 0.0f, 0.0f, 0.0f, | |
| 0.0f, f, 0.0f, 0.0f, | |
| 0.0f, 0.0f, (farPlane + nearPlane) / (nearPlane - farPlane), -1.0f, | |
| 0.0f, 0.0f, (2.0f * farPlane * nearPlane) / (nearPlane - farPlane), 0.0f | |
| }; | |
| GLint projectionLoc = glGetUniformLocation(shaderProgram, "projection"); | |
| glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projection); | |
| // 设置光源位置 | |
| float lightPos[3] = { camPosX, camPosY + 10.0f, camPosZ }; | |
| GLint lightPosLoc = glGetUniformLocation(shaderProgram, "lightPos"); | |
| glUniform3fv(lightPosLoc, 1, lightPos); | |
| // 设置观察位置 | |
| float camPosArr[3] = { camPosX, camPosY, camPosZ }; | |
| GLint viewPosLoc = glGetUniformLocation(shaderProgram, "viewPos"); | |
| glUniform3fv(viewPosLoc, 1, camPosArr); | |
| // 设置物体颜色 | |
| float objectColor[3] = { 0.8f, 0.8f, 0.8f }; // 调亮一点的默认颜色 | |
| GLint objectColorLoc = glGetUniformLocation(shaderProgram, "objectColor"); | |
| glUniform3fv(objectColorLoc, 1, objectColor); | |
| // 设置光源颜色 | |
| float lightColor[3] = { 1.0f, 1.0f, 1.0f }; | |
| GLint lightColorLoc = glGetUniformLocation(shaderProgram, "lightColor"); | |
| glUniform3fv(lightColorLoc, 1, lightColor); | |
| // 设置高亮参数 | |
| GLint isSelectedLoc = glGetUniformLocation(shaderProgram, "isSelected"); | |
| GLint highlightColorLoc = glGetUniformLocation(shaderProgram, "highlightColor"); | |
| float highlightColor[3] = {1.0f, 0.5f, 0.0f}; // 橙色高亮 | |
| // 绑定VAO | |
| glBindVertexArray(VAO); | |
| // 绘制普通三角形 | |
| if (isSelectedLoc != -1) { | |
| glUniform1i(isSelectedLoc, GL_FALSE); | |
| } | |
| // 设置PBR参数,保证每一帧实时更新到Shader | |
| GLint metallicLoc = glGetUniformLocation(shaderProgram, "metallic"); | |
| if (metallicLoc != -1) { | |
| glUniform1f(metallicLoc, metallic); | |
| } | |
| GLint roughnessLoc = glGetUniformLocation(shaderProgram, "roughness"); | |
| if (roughnessLoc != -1) { | |
| glUniform1f(roughnessLoc, roughness); | |
| } | |
| if (triangleCount > 0) { | |
| glDrawArrays(GL_TRIANGLES, 0, triangleCount * 3); | |
| } | |
| // 如果有选中的三角形,高亮显示 | |
| if (selectedTriangleIndex >= 0 && isSelectedLoc != -1 && highlightColorLoc != -1) { | |
| glUniform1i(isSelectedLoc, GL_TRUE); | |
| glUniform3fv(highlightColorLoc, 1, highlightColor); | |
| // 只绘制选中的三角形 | |
| glDrawArrays(GL_TRIANGLES, selectedTriangleIndex * 3, 3); | |
| } | |
| highlightParts(); | |
| // 解绑VAO | |
| glBindVertexArray(0); | |
| // 渲染BVH可视化(如果开启) | |
| if (showBVH) { | |
| renderBVH(); | |
| } | |
| if (!automationMode) { | |
| ImGui::Render(); | |
| renderImGui(); | |
| } | |
| // 更新FPS | |
| updateFPS(); | |
| } | |
| void RenderManager::processInput() { | |
| // 检查窗口是否需要关闭 | |
| if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { | |
| glfwSetWindowShouldClose(window, true); | |
| } | |
| ImGuiIO& io = ImGui::GetIO(); | |
| // 处理鼠标输入 | |
| static bool firstMouse = true; | |
| static float lastX = width / 2.0f; | |
| static float lastY = height / 2.0f; | |
| static bool leftButtonPressed = false; | |
| double xpos, ypos; | |
| glfwGetCursorPos(window, &xpos, &ypos); | |
| if (firstMouse) { | |
| lastX = xpos; | |
| lastY = ypos; | |
| firstMouse = false; | |
| } | |
| float xoffset = xpos - lastX; | |
| float yoffset = lastY - ypos; | |
| lastX = xpos; | |
| lastY = ypos; | |
| // 只有当ImGui不需要鼠标时才处理3D场景交互 | |
| if (!io.WantCaptureMouse) { | |
| // 处理鼠标左键旋转(仅在拖动时,不是点击时) | |
| if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) { | |
| // 检测是否是新的点击(用于拾取) | |
| if (!leftButtonPressed) { | |
| // 鼠标左键按下,处理点击事件(拾取) | |
| handleMouseClick(xpos, ypos); | |
| leftButtonPressed = true; | |
| } | |
| // 旋转相机 | |
| float sensitivity = 0.005f; | |
| xoffset *= sensitivity; | |
| yoffset *= sensitivity; | |
| cameraRotation[1] += xoffset; | |
| cameraRotation[0] += yoffset; | |
| // 限制视角范围 | |
| if (cameraRotation[0] > 1.57079632679f) { | |
| cameraRotation[0] = 1.57079632679f; | |
| } | |
| if (cameraRotation[0] < -1.57079632679f) { | |
| cameraRotation[0] = -1.57079632679f; | |
| } | |
| } else { | |
| leftButtonPressed = false; | |
| } | |
| // 处理鼠标中键平移 | |
| if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) { | |
| float panSensitivity = 0.002f * zoom; | |
| // 计算相机右向量和上向量以进行平移 | |
| float cos_rot_x = cosf(cameraRotation[0]); | |
| float sin_rot_x = sinf(cameraRotation[0]); | |
| float cos_rot_y = cosf(cameraRotation[1]); | |
| float sin_rot_y = sinf(cameraRotation[1]); | |
| // 相机坐标系下的右向量 (1,0,0) 转换到世界坐标系 | |
| float rightX = cos_rot_y; | |
| float rightY = 0.0f; | |
| float rightZ = -sin_rot_y; | |
| // 相机坐标系下的上向量 (0,1,0) 转换到世界坐标系 | |
| float upX = sin_rot_y * sin_rot_x; | |
| float upY = cos_rot_x; | |
| float upZ = cos_rot_y * sin_rot_x; | |
| // 移动相机 | |
| cameraPosition[0] -= rightX * xoffset * panSensitivity; | |
| cameraPosition[1] -= rightY * xoffset * panSensitivity; | |
| cameraPosition[2] -= rightZ * xoffset * panSensitivity; | |
| cameraPosition[0] += upX * yoffset * panSensitivity; | |
| cameraPosition[1] += upY * yoffset * panSensitivity; | |
| cameraPosition[2] += upZ * yoffset * panSensitivity; | |
| } | |
| } else { | |
| // ImGui正在使用鼠标,重置按钮状态 | |
| leftButtonPressed = false; | |
| } | |
| // 设置窗口用户指针(用于滚轮回调) | |
| glfwSetWindowUserPointer(window, this); | |
| } | |
| bool RenderManager::shouldClose() { | |
| return glfwWindowShouldClose(window); | |
| } | |
| void RenderManager::swapBuffers() { | |
| glfwSwapBuffers(window); | |
| glfwPollEvents(); | |
| // 计算deltaTime | |
| auto currentFrame = std::chrono::steady_clock::now(); | |
| deltaTime = std::chrono::duration<float>(currentFrame - lastFrame).count(); | |
| lastFrame = currentFrame; | |
| } | |
| void RenderManager::setTriangles(hhb::core::ObjectPool<hhb::core::Triangle>& pool) { | |
| // 保存三角形池的指针 | |
| trianglePool = &pool; | |
| // 更新顶点数据 | |
| updateVertexData(pool); | |
| // 更新缓冲区 | |
| glBindBuffer(GL_ARRAY_BUFFER, VBO); | |
| glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_STATIC_DRAW); | |
| // 设置顶点属性指针 | |
| // glVertexAttribPointer参数定义: | |
| // 第一个参数:属性索引,对应着色器中的layout(location = 0) | |
| // 第二个参数:属性大小,这里是3个顶点数(x, y, z) | |
| // 第三个参数:数据类型,这里是GL_FLOAT | |
| // 第四个参数:是否标准化,这里是GL_FALSE | |
| // 第五个参数:步长,每个顶点的大小(位置3个顶点数 + 法线3个顶点数 = 6个顶点数) | |
| // 第六个参数:偏移量,从缓冲区开始的偏移量 | |
| glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); | |
| glEnableVertexAttribArray(0); | |
| // 设置法线向量属性指针 | |
| glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); | |
| glEnableVertexAttribArray(1); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| // 构建BVH树 | |
| buildBVH(); | |
| } | |
| GLuint RenderManager::loadShader(GLenum type, const char* source) { | |
| GLuint shader = glCreateShader(type); | |
| glShaderSource(shader, 1, (const GLchar**)&source, nullptr); | |
| glCompileShader(shader); | |
| // 检查编译错误 | |
| int success; | |
| char infoLog[512]; | |
| glGetShaderiv(shader, GL_COMPILE_STATUS, &success); | |
| if (!success) { | |
| glGetShaderInfoLog(shader, 512, nullptr, (GLchar*)infoLog); | |
| std::cerr << "Shader compilation error: " << infoLog << std::endl; | |
| return 0; | |
| } | |
| return shader; | |
| } | |
| GLuint RenderManager::createShaderProgram(const char* vertexSource, const char* fragmentSource) { | |
| GLuint vertexShader = loadShader(GL_VERTEX_SHADER, vertexSource); | |
| GLuint fragmentShader = loadShader(GL_FRAGMENT_SHADER, fragmentSource); | |
| if (!vertexShader || !fragmentShader) { | |
| return 0; | |
| } | |
| GLuint program = glCreateProgram(); | |
| glAttachShader(program, vertexShader); | |
| glAttachShader(program, fragmentShader); | |
| glLinkProgram(program); | |
| // 检查链接错误 | |
| int success; | |
| char infoLog[512]; | |
| glGetProgramiv(program, GL_LINK_STATUS, &success); | |
| if (!success) { | |
| glGetProgramInfoLog(program, 512, nullptr, (GLchar*)infoLog); | |
| std::cerr << "Program linking error: " << infoLog << std::endl; | |
| return 0; | |
| } | |
| // 删除着色器,因为它们已经链接到程序中 | |
| glDeleteShader(vertexShader); | |
| glDeleteShader(fragmentShader); | |
| return program; | |
| } | |
| bool RenderManager::initShaders() { | |
| shaderProgram = createShaderProgram(vertexShaderSource, fragmentShaderSource); | |
| if (!shaderProgram) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool RenderManager::initLabelShaders() { | |
| labelShaderProgram = createShaderProgram(labelVertexShaderSource, labelFragmentShaderSource); | |
| if (!labelShaderProgram) { | |
| std::cerr << "Failed to create label shader program" << std::endl; | |
| return false; | |
| } | |
| glGenVertexArrays(1, &labelVAO); | |
| glGenBuffers(1, &labelVBO); | |
| glBindVertexArray(labelVAO); | |
| glBindBuffer(GL_ARRAY_BUFFER, labelVBO); | |
| glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); | |
| glEnableVertexAttribArray(0); | |
| glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); | |
| glEnableVertexAttribArray(1); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glBindVertexArray(0); | |
| return true; | |
| } | |
| void RenderManager::categoryToColor(int categoryId, float* outR, float* outG, float* outB) { | |
| switch (categoryId) { | |
| case 0: *outR = 0.50f; *outG = 0.50f; *outB = 0.50f; break; | |
| case 1: *outR = 0.00f; *outG = 0.00f; *outB = 1.00f; break; | |
| case 2: *outR = 0.00f; *outG = 1.00f; *outB = 0.00f; break; | |
| case 3: *outR = 1.00f; *outG = 0.00f; *outB = 0.00f; break; | |
| case 4: *outR = 1.00f; *outG = 1.00f; *outB = 0.00f; break; | |
| case 5: *outR = 1.00f; *outG = 0.00f; *outB = 1.00f; break; | |
| case 6: *outR = 0.00f; *outG = 1.00f; *outB = 1.00f; break; | |
| case 7: *outR = 1.00f; *outG = 0.50f; *outB = 0.00f; break; | |
| case 8: *outR = 0.50f; *outG = 0.00f; *outB = 1.00f; break; | |
| case 9: *outR = 0.00f; *outG = 0.50f; *outB = 1.00f; break; | |
| case 10: *outR = 0.80f; *outG = 0.80f; *outB = 0.00f; break; | |
| case 11: *outR = 0.00f; *outG = 0.80f; *outB = 0.40f; break; | |
| default: *outR = 1.00f; *outG = 1.00f; *outB = 1.00f; break; | |
| } | |
| } | |
| void RenderManager::computeFaceCategories() { | |
| if (!trianglePool || triangleCount == 0) { | |
| faceCategoryIds.clear(); | |
| faceCategoriesComputed = false; | |
| return; | |
| } | |
| faceCategoryIds.resize(triangleCount, 0); | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| static size_t idx = 0; | |
| if (idx >= triangleCount) { | |
| idx = 0; | |
| } | |
| float nx = tri->normal[0]; | |
| float ny = tri->normal[1]; | |
| float nz = tri->normal[2]; | |
| float nLen = std::sqrt(nx * nx + ny * ny + nz * nz); | |
| if (nLen > 0.0001f) { nx /= nLen; ny /= nLen; nz /= nLen; } | |
| float absNx = std::abs(nx); | |
| float absNy = std::abs(ny); | |
| float absNz = std::abs(nz); | |
| float maxComp = std::max({absNx, absNy, absNz}); | |
| float e1[3] = {tri->vertex2[0] - tri->vertex1[0], | |
| tri->vertex2[1] - tri->vertex1[1], | |
| tri->vertex2[2] - tri->vertex1[2]}; | |
| float e2[3] = {tri->vertex3[0] - tri->vertex1[0], | |
| tri->vertex3[1] - tri->vertex1[1], | |
| tri->vertex3[2] - tri->vertex1[2]}; | |
| float cross[3] = { | |
| e1[1] * e2[2] - e1[2] * e2[1], | |
| e1[2] * e2[0] - e1[0] * e2[2], | |
| e1[0] * e2[1] - e1[1] * e2[0] | |
| }; | |
| float area = 0.5f * std::sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]); | |
| int category = 0; | |
| if (area < 0.00001f) { | |
| category = 7; | |
| } | |
| else if (maxComp > 0.98f) { | |
| if (absNy > 0.98f) category = 1; | |
| else if (absNx > 0.98f) category = 2; | |
| else category = 3; | |
| } | |
| else if (maxComp > 0.85f) { | |
| if (absNy >= absNx && absNy >= absNz) category = 4; | |
| else if (absNx >= absNz) category = 5; | |
| else category = 6; | |
| } | |
| else { | |
| category = 0; | |
| } | |
| faceCategoryIds[idx] = category; | |
| idx++; | |
| }); | |
| faceCategoriesComputed = true; | |
| printf("[LabelMode] Face categories computed: %zu triangles classified\n", faceCategoryIds.size()); | |
| fflush(stdout); | |
| } | |
| void RenderManager::computeCurvature() { | |
| if (!trianglePool || triangleCount == 0) { | |
| faceGaussianCurvature.clear(); | |
| faceMeanCurvature.clear(); | |
| faceCurvatureIds.clear(); | |
| curvatureComputed = false; | |
| return; | |
| } | |
| faceGaussianCurvature.resize(triangleCount, 0.0f); | |
| faceMeanCurvature.resize(triangleCount, 0.0f); | |
| faceCurvatureIds.resize(triangleCount, 0); | |
| struct VertexInfo { | |
| float normal[3] = {0, 0, 0}; | |
| float areaSum = 0.0f; | |
| int valence = 0; | |
| float pos[3] = {0, 0, 0}; | |
| }; | |
| std::unordered_map<int64_t, VertexInfo> vertexMap; | |
| auto vertexKey = [](float x, float y, float z) -> int64_t { | |
| int ix = (int)(x * 10000.0f); | |
| int iy = (int)(y * 10000.0f); | |
| int iz = (int)(z * 10000.0f); | |
| return ((int64_t)(ix & 0xFFFFF) << 40) | ((int64_t)(iy & 0xFFFFF) << 20) | (int64_t)(iz & 0xFFFFF); | |
| }; | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| float e1[3] = {tri->vertex2[0] - tri->vertex1[0], | |
| tri->vertex2[1] - tri->vertex1[1], | |
| tri->vertex2[2] - tri->vertex1[2]}; | |
| float e2[3] = {tri->vertex3[0] - tri->vertex1[0], | |
| tri->vertex3[1] - tri->vertex1[1], | |
| tri->vertex3[2] - tri->vertex1[2]}; | |
| float cross[3] = { | |
| e1[1] * e2[2] - e1[2] * e2[1], | |
| e1[2] * e2[0] - e1[0] * e2[2], | |
| e1[0] * e2[1] - e1[1] * e2[0] | |
| }; | |
| float area = 0.5f * std::sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]); | |
| float nx = tri->normal[0], ny = tri->normal[1], nz = tri->normal[2]; | |
| float nLen = std::sqrt(nx*nx + ny*ny + nz*nz); | |
| if (nLen > 0.0001f) { nx /= nLen; ny /= nLen; nz /= nLen; } | |
| int64_t keys[3] = { | |
| vertexKey(tri->vertex1[0], tri->vertex1[1], tri->vertex1[2]), | |
| vertexKey(tri->vertex2[0], tri->vertex2[1], tri->vertex2[2]), | |
| vertexKey(tri->vertex3[0], tri->vertex3[1], tri->vertex3[2]) | |
| }; | |
| float (*verts)[3] = {&tri->vertex1[0], &tri->vertex2[0], &tri->vertex3[0]}; | |
| for (int v = 0; v < 3; ++v) { | |
| auto& info = vertexMap[keys[v]]; | |
| info.normal[0] += nx * area; | |
| info.normal[1] += ny * area; | |
| info.normal[2] += nz * area; | |
| info.areaSum += area; | |
| info.valence++; | |
| info.pos[0] = verts[v][0]; | |
| info.pos[1] = verts[v][1]; | |
| info.pos[2] = verts[v][2]; | |
| } | |
| }); | |
| for (auto& [key, info] : vertexMap) { | |
| if (info.areaSum > 0.00001f) { | |
| info.normal[0] /= info.areaSum; | |
| info.normal[1] /= info.areaSum; | |
| info.normal[2] /= info.areaSum; | |
| float nLen = std::sqrt(info.normal[0]*info.normal[0] + | |
| info.normal[1]*info.normal[1] + | |
| info.normal[2]*info.normal[2]); | |
| if (nLen > 0.0001f) { | |
| info.normal[0] /= nLen; | |
| info.normal[1] /= nLen; | |
| info.normal[2] /= nLen; | |
| } | |
| } | |
| } | |
| size_t idx = 0; | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| if (idx >= triangleCount) return; | |
| int64_t keys[3] = { | |
| vertexKey(tri->vertex1[0], tri->vertex1[1], tri->vertex1[2]), | |
| vertexKey(tri->vertex2[0], tri->vertex2[1], tri->vertex2[2]), | |
| vertexKey(tri->vertex3[0], tri->vertex3[1], tri->vertex3[2]) | |
| }; | |
| float e1[3] = {tri->vertex2[0] - tri->vertex1[0], | |
| tri->vertex2[1] - tri->vertex1[1], | |
| tri->vertex2[2] - tri->vertex1[2]}; | |
| float e2[3] = {tri->vertex3[0] - tri->vertex1[0], | |
| tri->vertex3[1] - tri->vertex1[1], | |
| tri->vertex3[2] - tri->vertex1[2]}; | |
| float cross[3] = { | |
| e1[1] * e2[2] - e1[2] * e2[1], | |
| e1[2] * e2[0] - e1[0] * e2[2], | |
| e1[0] * e2[1] - e1[1] * e2[0] | |
| }; | |
| float area = 0.5f * std::sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]); | |
| float meanCurv = 0.0f; | |
| if (area > 0.00001f) { | |
| for (int v = 0; v < 3; ++v) { | |
| auto it = vertexMap.find(keys[v]); | |
| if (it != vertexMap.end()) { | |
| float dx = it->second.pos[0] - tri->vertex1[0]; | |
| float dy = it->second.pos[1] - tri->vertex1[1]; | |
| float dz = it->second.pos[2] - tri->vertex1[2]; | |
| float dist = std::sqrt(dx*dx + dy*dy + dz*dz); | |
| float dotProd = it->second.normal[0] * tri->normal[0] + | |
| it->second.normal[1] * tri->normal[1] + | |
| it->second.normal[2] * tri->normal[2]; | |
| float angle = std::acos(std::max(-1.0f, std::min(1.0f, dotProd))); | |
| meanCurv += angle / dist; | |
| } | |
| } | |
| meanCurv /= (3.0f * area); | |
| } | |
| float gaussianCurv = 0.0f; | |
| if (area > 0.00001f) { | |
| float angleSum = 0.0f; | |
| for (int v = 0; v < 3; ++v) { | |
| auto it = vertexMap.find(keys[v]); | |
| if (it != vertexMap.end() && it->second.valence > 0) { | |
| angleSum += 2.0f * 3.14159265f / it->second.valence; | |
| } | |
| } | |
| gaussianCurv = (angleSum - 3.14159265f) / area; | |
| } | |
| faceMeanCurvature[idx] = meanCurv; | |
| faceGaussianCurvature[idx] = gaussianCurv; | |
| float absMean = std::abs(meanCurv); | |
| float absGauss = std::abs(gaussianCurv); | |
| if (absMean < 0.5f && absGauss < 0.5f) { | |
| faceCurvatureIds[idx] = 0; | |
| } else if (absGauss > 2.0f && gaussianCurv > 0) { | |
| faceCurvatureIds[idx] = 1; | |
| } else if (absGauss > 2.0f && gaussianCurv < 0) { | |
| faceCurvatureIds[idx] = 2; | |
| } else if (absMean > 1.0f && meanCurv > 0) { | |
| faceCurvatureIds[idx] = 3; | |
| } else if (absMean > 1.0f && meanCurv < 0) { | |
| faceCurvatureIds[idx] = 4; | |
| } else { | |
| faceCurvatureIds[idx] = 5; | |
| } | |
| idx++; | |
| }); | |
| curvatureComputed = true; | |
| printf("[Curvature] Computed for %zu triangles\n", faceMeanCurvature.size()); | |
| fflush(stdout); | |
| } | |
| void RenderManager::computeGeometricFeatures() { | |
| if (!curvatureComputed) { | |
| computeCurvature(); | |
| } | |
| if (!trianglePool || triangleCount == 0) { | |
| geometricFeaturesComputed = false; | |
| return; | |
| } | |
| faceCategoryIds.resize(triangleCount, 0); | |
| struct EdgeKey { | |
| int64_t v0, v1; | |
| bool operator==(const EdgeKey& o) const { return v0 == o.v0 && v1 == o.v1; } | |
| }; | |
| struct EdgeKeyHash { | |
| size_t operator()(const EdgeKey& k) const { return std::hash<int64_t>()(k.v0) ^ (std::hash<int64_t>()(k.v1) << 1); } | |
| }; | |
| auto vertexKey = [](float x, float y, float z) -> int64_t { | |
| int ix = (int)(x * 10000.0f); | |
| int iy = (int)(y * 10000.0f); | |
| int iz = (int)(z * 10000.0f); | |
| return ((int64_t)(ix & 0xFFFFF) << 40) | ((int64_t)(iy & 0xFFFFF) << 20) | (int64_t)(iz & 0xFFFFF); | |
| }; | |
| auto makeEdge = [](int64_t a, int64_t b) -> EdgeKey { | |
| return a < b ? EdgeKey{a, b} : EdgeKey{b, a}; | |
| }; | |
| std::unordered_map<EdgeKey, std::vector<int>, EdgeKeyHash> edgeToTriangles; | |
| std::vector<int64_t> triVertexKeys(triangleCount * 3); | |
| size_t idx = 0; | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| if (idx >= triangleCount) return; | |
| int64_t k1 = vertexKey(tri->vertex1[0], tri->vertex1[1], tri->vertex1[2]); | |
| int64_t k2 = vertexKey(tri->vertex2[0], tri->vertex2[1], tri->vertex2[2]); | |
| int64_t k3 = vertexKey(tri->vertex3[0], tri->vertex3[1], tri->vertex3[2]); | |
| triVertexKeys[idx * 3 + 0] = k1; | |
| triVertexKeys[idx * 3 + 1] = k2; | |
| triVertexKeys[idx * 3 + 2] = k3; | |
| edgeToTriangles[makeEdge(k1, k2)].push_back((int)idx); | |
| edgeToTriangles[makeEdge(k2, k3)].push_back((int)idx); | |
| edgeToTriangles[makeEdge(k1, k3)].push_back((int)idx); | |
| idx++; | |
| }); | |
| std::vector<bool> visited(triangleCount, false); | |
| std::vector<std::vector<int>> clusters; | |
| for (size_t start = 0; start < triangleCount; ++start) { | |
| if (visited[start]) continue; | |
| int curvId = faceCurvatureIds[start]; | |
| std::vector<int> cluster; | |
| std::vector<int> stack; | |
| stack.push_back((int)start); | |
| while (!stack.empty()) { | |
| int triIdx = stack.back(); | |
| stack.pop_back(); | |
| if (triIdx < 0 || triIdx >= (int)triangleCount || visited[triIdx]) continue; | |
| if (faceCurvatureIds[triIdx] != curvId) continue; | |
| visited[triIdx] = true; | |
| cluster.push_back(triIdx); | |
| int64_t k1 = triVertexKeys[triIdx * 3 + 0]; | |
| int64_t k2 = triVertexKeys[triIdx * 3 + 1]; | |
| int64_t k3 = triVertexKeys[triIdx * 3 + 2]; | |
| auto addNeighbors = [&](int64_t a, int64_t b) { | |
| EdgeKey ek = makeEdge(a, b); | |
| auto it = edgeToTriangles.find(ek); | |
| if (it != edgeToTriangles.end()) { | |
| for (int nIdx : it->second) { | |
| if (!visited[nIdx] && faceCurvatureIds[nIdx] == curvId) { | |
| stack.push_back(nIdx); | |
| } | |
| } | |
| } | |
| }; | |
| addNeighbors(k1, k2); | |
| addNeighbors(k2, k3); | |
| addNeighbors(k1, k3); | |
| } | |
| if (!cluster.empty()) { | |
| clusters.push_back(std::move(cluster)); | |
| } | |
| } | |
| printf("[GeoFeature] Found %zu curvature clusters\n", clusters.size()); | |
| int boundaryEdgeCount = 0; | |
| for (const auto& [ek, tris] : edgeToTriangles) { | |
| if (tris.size() == 1) boundaryEdgeCount++; | |
| } | |
| for (auto& cluster : clusters) { | |
| float totalArea = 0.0f; | |
| float centerX = 0, centerY = 0, centerZ = 0; | |
| float avgMeanCurv = 0; | |
| float avgGaussCurv = 0; | |
| int boundaryEdges = 0; | |
| std::unordered_set<int> clusterSet(cluster.begin(), cluster.end()); | |
| for (int triIdx : cluster) { | |
| float e1[3], e2[3]; | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| static size_t counter = 0; | |
| if (counter == (size_t)triIdx) { | |
| e1[0] = tri->vertex2[0] - tri->vertex1[0]; | |
| e1[1] = tri->vertex2[1] - tri->vertex1[1]; | |
| e1[2] = tri->vertex2[2] - tri->vertex1[2]; | |
| e2[0] = tri->vertex3[0] - tri->vertex1[0]; | |
| e2[1] = tri->vertex3[1] - tri->vertex1[1]; | |
| e2[2] = tri->vertex3[2] - tri->vertex1[2]; | |
| centerX += (tri->vertex1[0] + tri->vertex2[0] + tri->vertex3[0]) / 3.0f; | |
| centerY += (tri->vertex1[1] + tri->vertex2[1] + tri->vertex3[1]) / 3.0f; | |
| centerZ += (tri->vertex1[2] + tri->vertex2[2] + tri->vertex3[2]) / 3.0f; | |
| } | |
| counter++; | |
| if (counter >= triangleCount) counter = 0; | |
| }); | |
| float cross[3] = { | |
| e1[1]*e2[2] - e1[2]*e2[1], | |
| e1[2]*e2[0] - e1[0]*e2[2], | |
| e1[0]*e2[1] - e1[1]*e2[0] | |
| }; | |
| float area = 0.5f * std::sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]); | |
| totalArea += area; | |
| avgMeanCurv += faceMeanCurvature[triIdx]; | |
| avgGaussCurv += faceGaussianCurvature[triIdx]; | |
| int64_t k1 = triVertexKeys[triIdx * 3 + 0]; | |
| int64_t k2 = triVertexKeys[triIdx * 3 + 1]; | |
| int64_t k3 = triVertexKeys[triIdx * 3 + 2]; | |
| auto checkBoundary = [&](int64_t a, int64_t b) { | |
| EdgeKey ek = makeEdge(a, b); | |
| auto it = edgeToTriangles.find(ek); | |
| if (it != edgeToTriangles.end() && it->second.size() == 1) { | |
| boundaryEdges++; | |
| } else if (it != edgeToTriangles.end()) { | |
| bool hasExternal = false; | |
| for (int nIdx : it->second) { | |
| if (clusterSet.find(nIdx) == clusterSet.end()) { | |
| hasExternal = true; | |
| break; | |
| } | |
| } | |
| if (hasExternal) boundaryEdges++; | |
| } | |
| }; | |
| checkBoundary(k1, k2); | |
| checkBoundary(k2, k3); | |
| checkBoundary(k1, k3); | |
| } | |
| if (cluster.empty()) continue; | |
| avgMeanCurv /= cluster.size(); | |
| avgGaussCurv /= cluster.size(); | |
| centerX /= cluster.size(); | |
| centerY /= cluster.size(); | |
| centerZ /= cluster.size(); | |
| int featureCategory = 0; | |
| if (cluster.size() < 5) { | |
| featureCategory = 0; | |
| } | |
| else if (avgGaussCurv > 1.5f && avgMeanCurv > 0.5f && totalArea < 0.5f) { | |
| featureCategory = 8; | |
| } | |
| else if (avgGaussCurv > 1.5f && avgMeanCurv < -0.5f && totalArea < 0.3f) { | |
| featureCategory = 9; | |
| } | |
| else if (avgMeanCurv < -1.0f && boundaryEdges >= 3) { | |
| featureCategory = 10; | |
| } | |
| else if (avgMeanCurv > 1.0f && totalArea > 0.1f && totalArea < 2.0f) { | |
| featureCategory = 11; | |
| } | |
| else if (std::abs(avgMeanCurv) < 0.5f && std::abs(avgGaussCurv) < 0.5f) { | |
| featureCategory = 1; | |
| } | |
| else { | |
| featureCategory = 0; | |
| } | |
| for (int triIdx : cluster) { | |
| faceCategoryIds[triIdx] = featureCategory; | |
| } | |
| } | |
| faceCategoriesComputed = true; | |
| geometricFeaturesComputed = true; | |
| printf("[GeoFeature] Geometric feature classification complete\n"); | |
| fflush(stdout); | |
| } | |
| void RenderManager::updateLabelVertexData() { | |
| if (!faceCategoriesComputed || faceCategoryIds.empty()) { | |
| computeFaceCategories(); | |
| } | |
| std::vector<float> labelData; | |
| labelData.reserve(triangleCount * 3 * 6); | |
| size_t triIdx = 0; | |
| if (trianglePool) { | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| int catId = (triIdx < faceCategoryIds.size()) ? faceCategoryIds[triIdx] : 0; | |
| float r, g, b; | |
| categoryToColor(catId, &r, &g, &b); | |
| labelData.push_back(tri->vertex1[0]); labelData.push_back(tri->vertex1[1]); labelData.push_back(tri->vertex1[2]); | |
| labelData.push_back(r); labelData.push_back(g); labelData.push_back(b); | |
| labelData.push_back(tri->vertex2[0]); labelData.push_back(tri->vertex2[1]); labelData.push_back(tri->vertex2[2]); | |
| labelData.push_back(r); labelData.push_back(g); labelData.push_back(b); | |
| labelData.push_back(tri->vertex3[0]); labelData.push_back(tri->vertex3[1]); labelData.push_back(tri->vertex3[2]); | |
| labelData.push_back(r); labelData.push_back(g); labelData.push_back(b); | |
| triIdx++; | |
| }); | |
| } | |
| glBindBuffer(GL_ARRAY_BUFFER, labelVBO); | |
| glBufferData(GL_ARRAY_BUFFER, labelData.size() * sizeof(float), labelData.data(), GL_STATIC_DRAW); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| } | |
| void RenderManager::renderLabelMode() { | |
| if (!labelShaderProgram || triangleCount == 0) return; | |
| glUseProgram(labelShaderProgram); | |
| float modelMat[16] = { | |
| 1.0f, 0.0f, 0.0f, 0.0f, | |
| 0.0f, 1.0f, 0.0f, 0.0f, | |
| 0.0f, 0.0f, 1.0f, 0.0f, | |
| 0.0f, 0.0f, 0.0f, 1.0f | |
| }; | |
| GLint modelLoc = glGetUniformLocation(labelShaderProgram, "model"); | |
| glUniformMatrix4fv(modelLoc, 1, GL_FALSE, modelMat); | |
| float distance = 5.0f / zoom; | |
| float cx = cosf(cameraRotation[0]); | |
| float sx = sinf(cameraRotation[0]); | |
| float cy = cosf(cameraRotation[1]); | |
| float sy = sinf(cameraRotation[1]); | |
| float camPosX = cameraPosition[0] + distance * cx * sy; | |
| float camPosY = cameraPosition[1] + distance * sx; | |
| float camPosZ = cameraPosition[2] + distance * cx * cy; | |
| float view[16] = { | |
| cy, -sx*sy, -cx*sy, 0.0f, | |
| 0.0f, cx, -sx, 0.0f, | |
| sy, sx*cy, cx*cy, 0.0f, | |
| 0.0f, 0.0f, -distance, 1.0f | |
| }; | |
| view[12] = -(view[0]*camPosX + view[4]*camPosY + view[8]*camPosZ); | |
| view[13] = -(view[1]*camPosX + view[5]*camPosY + view[9]*camPosZ); | |
| view[14] = -(view[2]*camPosX + view[6]*camPosY + view[10]*camPosZ); | |
| GLint viewLoc = glGetUniformLocation(labelShaderProgram, "view"); | |
| glUniformMatrix4fv(viewLoc, 1, GL_FALSE, view); | |
| float aspect = (float)width / (float)height; | |
| float fov = 45.0f; | |
| float nearPlane = 0.1f; | |
| float farPlane = 1000.0f; | |
| float f = 1.0f / tanf(fov * 0.5f * 3.1415926535f / 180.0f); | |
| float projection[16] = { | |
| f / aspect, 0.0f, 0.0f, 0.0f, | |
| 0.0f, f, 0.0f, 0.0f, | |
| 0.0f, 0.0f, (farPlane + nearPlane) / (nearPlane - farPlane), -1.0f, | |
| 0.0f, 0.0f, (2.0f * farPlane * nearPlane) / (nearPlane - farPlane), 0.0f | |
| }; | |
| GLint projectionLoc = glGetUniformLocation(labelShaderProgram, "projection"); | |
| glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, projection); | |
| glBindVertexArray(labelVAO); | |
| glDrawArrays(GL_TRIANGLES, 0, triangleCount * 3); | |
| glBindVertexArray(0); | |
| glUseProgram(0); | |
| } | |
| void RenderManager::setLabelMode(bool active) { | |
| labelModeActive = active; | |
| if (active && !faceCategoriesComputed) { | |
| computeFaceCategories(); | |
| updateLabelVertexData(); | |
| } | |
| } | |
| bool RenderManager::initBuffers() { | |
| // 创建VAO和VBO | |
| glGenVertexArrays(1, &VAO); | |
| glGenBuffers(1, &VBO); | |
| // 绑定VAO | |
| glBindVertexArray(VAO); | |
| // 绑定VBO | |
| glBindBuffer(GL_ARRAY_BUFFER, VBO); | |
| // 解绑VAO和VBO | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glBindVertexArray(0); | |
| return true; | |
| } | |
| void RenderManager::updateVertexData(hhb::core::ObjectPool<hhb::core::Triangle>& pool) { | |
| // 清空顶点数据 | |
| vertexData.clear(); | |
| // 遍历ObjectPool中的所有三角形 | |
| triangleCount = 0; | |
| // 使用ObjectPool的for_each方法遍历所有三角形 | |
| pool.for_each([&](hhb::core::Triangle* tri) { | |
| // 添加顶点1 | |
| vertexData.push_back(tri->vertex1[0]); | |
| vertexData.push_back(tri->vertex1[1]); | |
| vertexData.push_back(tri->vertex1[2]); | |
| // 添加法线 | |
| vertexData.push_back(tri->normal[0]); | |
| vertexData.push_back(tri->normal[1]); | |
| vertexData.push_back(tri->normal[2]); | |
| // 添加顶点2 | |
| vertexData.push_back(tri->vertex2[0]); | |
| vertexData.push_back(tri->vertex2[1]); | |
| vertexData.push_back(tri->vertex2[2]); | |
| // 添加法线 | |
| vertexData.push_back(tri->normal[0]); | |
| vertexData.push_back(tri->normal[1]); | |
| vertexData.push_back(tri->normal[2]); | |
| // 添加顶点3 | |
| vertexData.push_back(tri->vertex3[0]); | |
| vertexData.push_back(tri->vertex3[1]); | |
| vertexData.push_back(tri->vertex3[2]); | |
| // 添加法线 | |
| vertexData.push_back(tri->normal[0]); | |
| vertexData.push_back(tri->normal[1]); | |
| vertexData.push_back(tri->normal[2]); | |
| triangleCount++; | |
| }); | |
| std::cout << "Extracted " << triangleCount << " triangles from ObjectPool" << std::endl; | |
| } | |
| void RenderManager::updateFPS() { | |
| frameCount++; | |
| lastFpsUpdate += deltaTime; | |
| if (lastFpsUpdate >= 1.0f) { | |
| fps = frameCount / lastFpsUpdate; | |
| frameCount = 0; | |
| lastFpsUpdate = 0.0f; | |
| // 更新窗口标题 | |
| std::string newTitle = title + " - FPS: " + std::to_string((int)fps); | |
| glfwSetWindowTitle(window, newTitle.c_str()); | |
| } | |
| } | |
| void RenderManager::buildBVH() { | |
| std::cout << "Building BVH tree..." << std::endl; | |
| // 清空三角形指针数组 | |
| trianglePtrs.clear(); | |
| // 收集所有三角形指针 | |
| trianglePool->for_each([&](hhb::core::Triangle* tri) { | |
| trianglePtrs.push_back(tri); | |
| }); | |
| std::cout << "Collected " << trianglePtrs.size() << " triangles for BVH build" << std::endl; | |
| // 构建BVH树 | |
| auto start_time = std::chrono::high_resolution_clock::now(); | |
| bvh.build(trianglePtrs); | |
| auto end_time = std::chrono::high_resolution_clock::now(); | |
| std::chrono::duration<double, std::micro> build_duration = end_time - start_time; | |
| std::cout << "BVH build completed in " << build_duration.count() << " microseconds" << std::endl; | |
| std::cout << "BVH node count: " << bvh.node_count() << std::endl; | |
| } | |
| void RenderManager::centerModel() { | |
| printf("centerModel() called\n"); | |
| fflush(stdout); | |
| if (triangleCount == 0) { | |
| printf("No triangles loaded, using default camera position\n"); | |
| fflush(stdout); | |
| cameraPosition[0] = 0.0f; | |
| cameraPosition[1] = 0.0f; | |
| cameraPosition[2] = 0.0f; | |
| cameraRotation[0] = 0.0f; | |
| cameraRotation[1] = 0.0f; | |
| zoom = 1.0f; | |
| return; | |
| } | |
| // 获取BVH根节点包围盒 | |
| hhb::core::Bounds rootBounds = bvh.get_root_bounds(); | |
| // 计算包围盒尺寸 | |
| float sizeX = rootBounds.max[0] - rootBounds.min[0]; | |
| float sizeY = rootBounds.max[1] - rootBounds.min[1]; | |
| float sizeZ = rootBounds.max[2] - rootBounds.min[2]; | |
| float maxDim = (std::max)((std::max)(sizeX, sizeY), sizeZ); | |
| // 计算包围盒中心 | |
| float centerX = (rootBounds.max[0] + rootBounds.min[0]) / 2.0f; | |
| float centerY = (rootBounds.max[1] + rootBounds.min[1]) / 2.0f; | |
| float centerZ = (rootBounds.max[2] + rootBounds.min[2]) / 2.0f; | |
| printf("Model bounding box: (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f)\n", | |
| rootBounds.min[0], rootBounds.min[1], rootBounds.min[2], | |
| rootBounds.max[0], rootBounds.max[1], rootBounds.max[2]); | |
| printf("Model center: (%.2f, %.2f, %.2f), max dimension: %.2f\n", | |
| centerX, centerY, centerZ, maxDim); | |
| fflush(stdout); | |
| // 设置相机目标点为模型中心 | |
| cameraPosition[0] = centerX; | |
| cameraPosition[1] = centerY; | |
| cameraPosition[2] = centerZ; | |
| // 重置相机旋转 | |
| cameraRotation[0] = 0.0f; | |
| cameraRotation[1] = 0.0f; | |
| // 计算合适的缩放级别 | |
| // 基础距离是5.0,我们需要让模型在视口中占据合适的比例 | |
| float targetDistance = maxDim > 0 ? maxDim * 1.5f : 5.0f; | |
| zoom = 5.0f / targetDistance; | |
| printf("Camera set to center (%.2f, %.2f, %.2f), zoom: %.4f\n", | |
| cameraPosition[0], cameraPosition[1], cameraPosition[2], zoom); | |
| fflush(stdout); | |
| } | |
| void RenderManager::screenToRay(double xpos, double ypos, float* rayOrigin, float* rayDirection) { | |
| // 将屏幕坐标转换为标准化设备坐标 (NDC) | |
| float ndc_x = (2.0f * static_cast<float>(xpos)) / static_cast<float>(width) - 1.0f; | |
| float ndc_y = 1.0f - (2.0f * static_cast<float>(ypos)) / static_cast<float>(height); | |
| // 计算射线方向(在相机空间中) | |
| float aspect = static_cast<float>(width) / static_cast<float>(height); | |
| float fov = 45.0f; | |
| float tan_half_fov = tan(fov * 0.5f * 3.1415926535f / 180.0f); | |
| rayDirection[0] = ndc_x * aspect * tan_half_fov; | |
| rayDirection[1] = ndc_y * tan_half_fov; | |
| rayDirection[2] = -1.0f; // 相机看向负Z轴 | |
| // 应用相机旋转 | |
| float cos_rot_x = cos(cameraRotation[0]); | |
| float sin_rot_x = sin(cameraRotation[0]); | |
| float cos_rot_y = cos(cameraRotation[1]); | |
| float sin_rot_y = sin(cameraRotation[1]); | |
| // 绕X轴旋转 | |
| float temp_y = rayDirection[1]; | |
| float temp_z = rayDirection[2]; | |
| rayDirection[1] = temp_y * cos_rot_x - temp_z * sin_rot_x; | |
| rayDirection[2] = temp_y * sin_rot_x + temp_z * cos_rot_x; | |
| // 绕Y轴旋转 | |
| float temp_x = rayDirection[0]; | |
| temp_z = rayDirection[2]; | |
| rayDirection[0] = temp_x * cos_rot_y + temp_z * sin_rot_y; | |
| rayDirection[2] = -temp_x * sin_rot_y + temp_z * cos_rot_y; | |
| // 归一化射线方向 | |
| float length = sqrt(rayDirection[0] * rayDirection[0] + rayDirection[1] * rayDirection[1] + rayDirection[2] * rayDirection[2]); | |
| rayDirection[0] /= length; | |
| rayDirection[1] /= length; | |
| rayDirection[2] /= length; | |
| // 射线原点就是相机位置 | |
| rayOrigin[0] = cameraPosition[0]; | |
| rayOrigin[1] = cameraPosition[1]; | |
| rayOrigin[2] = cameraPosition[2]; | |
| } | |
| void RenderManager::handleMouseClick(double xpos, double ypos) { | |
| // 关键:检查 ImGui 是否想捕获鼠标事件 | |
| ImGuiIO& io = ImGui::GetIO(); | |
| if (io.WantCaptureMouse) { | |
| // std::cout << "ImGui captured mouse click, ignoring ray picking." << std::endl; | |
| return; | |
| } | |
| // std::cout << "Mouse clicked at: " << xpos << ", " << ypos << std::endl; | |
| // 计算射线 | |
| float rayOrigin[3]; | |
| float rayDirection[3]; | |
| screenToRay(xpos, ypos, rayOrigin, rayDirection); | |
| std::cout << "Ray origin: (" << rayOrigin[0] << ", " << rayOrigin[1] << ", " << rayOrigin[2] << ")" << std::endl; | |
| std::cout << "Ray direction: (" << rayDirection[0] << ", " << rayDirection[1] << ", " << rayDirection[2] << ")" << std::endl; | |
| // 射线与BVH树相交 | |
| auto start_time = std::chrono::high_resolution_clock::now(); | |
| float t_hit; | |
| hhb::core::Triangle* hit_triangle = nullptr; | |
| bool hit = bvh.intersect(rayOrigin, rayDirection, t_hit, hit_triangle); | |
| auto end_time = std::chrono::high_resolution_clock::now(); | |
| std::chrono::duration<double, std::micro> intersect_duration = end_time - start_time; | |
| pickTime = static_cast<float>(intersect_duration.count()); | |
| std::cout << "Ray intersection completed in " << pickTime << " microseconds" << std::endl; | |
| if (hit) { | |
| selectedTriangle = hit_triangle; | |
| // 查找三角形索引 | |
| selectedTriangleIndex = -1; | |
| if (trianglePool) { | |
| for (size_t i = 0; i < triangleCount; ++i) { | |
| if (&(*trianglePool)[i] == hit_triangle) { | |
| selectedTriangleIndex = static_cast<int>(i); | |
| break; | |
| } | |
| } | |
| } | |
| std::cout << "Hit triangle found! Index: " << selectedTriangleIndex << std::endl; | |
| std::cout << "Intersection distance: " << t_hit << std::endl; | |
| std::cout << "Triangle vertices:" << std::endl; | |
| std::cout << " Vertex 1: (" << hit_triangle->vertex1[0] << ", " << hit_triangle->vertex1[1] << ", " << hit_triangle->vertex1[2] << ")" << std::endl; | |
| std::cout << " Vertex 2: (" << hit_triangle->vertex2[0] << ", " << hit_triangle->vertex2[1] << ", " << hit_triangle->vertex2[2] << ")" << std::endl; | |
| std::cout << " Vertex 3: (" << hit_triangle->vertex3[0] << ", " << hit_triangle->vertex3[1] << ", " << hit_triangle->vertex3[2] << ")" << std::endl; | |
| std::cout << " Normal: (" << hit_triangle->normal[0] << ", " << hit_triangle->normal[1] << ", " << hit_triangle->normal[2] << ")" << std::endl; | |
| } else { | |
| selectedTriangle = nullptr; | |
| selectedTriangleIndex = -1; | |
| std::cout << "No triangle hit." << std::endl; | |
| } | |
| } | |
| void RenderManager::initImGui() { | |
| // 创建ImGui上下文 | |
| ImGui::CreateContext(); | |
| // 设置ImGui样式 | |
| ImGui::StyleColorsDark(); | |
| // 配置ImGui以支持Unicode | |
| ImGuiIO& io = ImGui::GetIO(); | |
| // 禁用键盘导航,避免键位映射问题 | |
| // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // 启用键盘控制 | |
| // 尝试加载系统字体以支持中文 | |
| // 首先添加默认字体 | |
| io.Fonts->AddFontDefault(); | |
| // 直接加载微软雅黑字体并指定中文字符范围 | |
| ImFontConfig config; | |
| config.MergeMode = false; | |
| config.PixelSnapH = true; | |
| if (ImFont* font = io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\msyh.ttc", 18.0f, &config, io.Fonts->GetGlyphRangesChineseFull())) { | |
| std::cout << "Loaded Chinese font: C:\\Windows\\Fonts\\msyh.ttc" << std::endl; | |
| } else { | |
| // 尝试其他字体路径 | |
| if (ImFont* font = io.Fonts->AddFontFromFileTTF("C:\\Windows\\Fonts\\msyh.ttf", 18.0f, &config, io.Fonts->GetGlyphRangesChineseFull())) { | |
| std::cout << "Loaded Chinese font: C:\\Windows\\Fonts\\msyh.ttf" << std::endl; | |
| } else { | |
| std::cout << "Warning: Failed to load Chinese font" << std::endl; | |
| } | |
| } | |
| // 确保ImGui能够处理Unicode字符 | |
| io.Fonts->Build(); | |
| // 初始化ImGui与GLFW的绑定 | |
| // 第二个参数 false 表示不自动安装回调,我们手动处理事件 | |
| ImGui_ImplGlfw_InitForOpenGL(window, false); | |
| // 设置GLFW回调,确保ImGui能接收键盘和鼠标事件 | |
| glfwSetMouseButtonCallback(window, [](GLFWwindow* w, int button, int action, int mods) { | |
| ImGui_ImplGlfw_MouseButtonCallback(w, button, action, mods); | |
| }); | |
| glfwSetScrollCallback(window, [](GLFWwindow* w, double xoffset, double yoffset) { | |
| ImGui_ImplGlfw_ScrollCallback(w, xoffset, yoffset); | |
| }); | |
| glfwSetKeyCallback(window, [](GLFWwindow* w, int key, int scancode, int action, int mods) { | |
| ImGui_ImplGlfw_KeyCallback(w, key, scancode, action, mods); | |
| }); | |
| glfwSetCharCallback(window, [](GLFWwindow* w, unsigned int c) { | |
| ImGui_ImplGlfw_CharCallback(w, c); | |
| }); | |
| // 初始化ImGui与OpenGL的绑定 | |
| ImGui_ImplOpenGL3_Init("#version 330"); | |
| std::cout << "ImGui initialized successfully" << std::endl; | |
| } | |
| void RenderManager::updateImGui() { | |
| // 渲染主菜单栏 | |
| if (ImGui::BeginMainMenuBar()) { | |
| if (ImGui::BeginMenu("File")) { | |
| if (ImGui::MenuItem("Open STL...")) { | |
| printf("UI: Open STL Clicked from Menu!\n"); | |
| fflush(stdout); | |
| openFileDialog(); | |
| } | |
| ImGui::Separator(); | |
| if (ImGui::MenuItem("Exit")) { | |
| glfwSetWindowShouldClose(window, true); | |
| } | |
| ImGui::EndMenu(); | |
| } | |
| if (ImGui::BeginMenu("View")) { | |
| ImGui::MenuItem("Show BVH Bounds", NULL, &showBVH); | |
| if (ImGui::MenuItem("Reset Camera")) { | |
| centerModel(); | |
| } | |
| ImGui::EndMenu(); | |
| } | |
| ImGui::EndMainMenuBar(); | |
| } | |
| // 创建主控制面板 - 左侧常驻面板 | |
| ImGui::SetNextWindowPos(ImVec2(10, 30), ImGuiCond_Always); | |
| ImGui::SetNextWindowSize(ImVec2(280, 0), ImGuiCond_Always); | |
| ImGui::Begin("Huhb CAD Control Panel", nullptr, | |
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | | |
| ImGuiWindowFlags_NoSavedSettings); | |
| // 标题 | |
| ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f), "Huhb CAD Industrial Viewer"); | |
| ImGui::Separator(); | |
| // 文件操作区域 - 主要的 Open STL 按钮 | |
| ImGui::Text("File Operations"); | |
| ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.8f, 1.0f)); | |
| ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.7f, 0.9f, 1.0f)); | |
| if (ImGui::Button("Open STL File", ImVec2(200, 35))) { | |
| printf("UI: Open STL Button Clicked!\n"); | |
| fflush(stdout); | |
| openFileDialog(); | |
| } | |
| ImGui::PopStyleColor(2); | |
| ImGui::Separator(); | |
| // 性能监控区域 | |
| ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.0f, 1.0f), "Model & Performance"); | |
| ImGui::Text("FPS: %.1f", fps); | |
| ImGui::Text("Vertices: %zu", triangleCount * 3); | |
| ImGui::Text("Triangles: %zu", triangleCount); | |
| // 显示包围盒大小 | |
| hhb::core::Bounds rootBounds = bvh.get_root_bounds(); | |
| float sizeX = rootBounds.max[0] - rootBounds.min[0]; | |
| float sizeY = rootBounds.max[1] - rootBounds.min[1]; | |
| float sizeZ = rootBounds.max[2] - rootBounds.min[2]; | |
| if (sizeX < 0) sizeX = sizeY = sizeZ = 0.0f; | |
| ImGui::Text("Bounding Box:"); | |
| ImGui::Text(" Size: %.2f x %.2f x %.2f", sizeX, sizeY, sizeZ); | |
| ImGui::Text("BVH Depth: %d", bvh.depth()); | |
| ImGui::Text("Load Time: %.2f ms", loadTime); | |
| ImGui::Text("Memory Usage: %.2f MB", memoryUsage / (1024.0f * 1024.0f)); | |
| ImGui::Separator(); | |
| // PBR参数调节区域 | |
| ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.5f, 1.0f), "PBR Material"); | |
| ImGui::SliderFloat("Metallic", &metallic, 0.0f, 1.0f); | |
| ImGui::SliderFloat("Roughness", &roughness, 0.0f, 1.0f); | |
| ImGui::Separator(); | |
| // BVH可视化开关 | |
| ImGui::TextColored(ImVec4(1.0f, 0.5f, 0.0f, 1.0f), "Debug Options"); | |
| ImGui::Checkbox("Show BVH Bounds", &showBVH); | |
| if (ImGui::Button("Reset Camera to Model", ImVec2(200, 25))) { | |
| printf("UI: Reset Camera Clicked!\n"); | |
| fflush(stdout); | |
| centerModel(); | |
| } | |
| ImGui::Separator(); | |
| // 拾取信息区域 | |
| ImGui::TextColored(ImVec4(0.8f, 0.5f, 1.0f, 1.0f), "Pick Information"); | |
| ImGui::Text("Pick Time: %.2f us", pickTime); | |
| if (selectedTriangleIndex >= 0) { | |
| ImGui::Text("Selected Triangle Index: %d", selectedTriangleIndex); | |
| ImGui::Text("Normal: (%.3f, %.3f, %.3f)", | |
| selectedTriangle->normal[0], | |
| selectedTriangle->normal[1], | |
| selectedTriangle->normal[2]); | |
| ImGui::Text("Vertex 1: (%.3f, %.3f, %.3f)", | |
| selectedTriangle->vertex1[0], | |
| selectedTriangle->vertex1[1], | |
| selectedTriangle->vertex1[2]); | |
| ImGui::Text("Vertex 2: (%.3f, %.3f, %.3f)", | |
| selectedTriangle->vertex2[0], | |
| selectedTriangle->vertex2[1], | |
| selectedTriangle->vertex2[2]); | |
| ImGui::Text("Vertex 3: (%.3f, %.3f, %.3f)", | |
| selectedTriangle->vertex3[0], | |
| selectedTriangle->vertex3[1], | |
| selectedTriangle->vertex3[2]); | |
| } | |
| ImGui::Separator(); | |
| // 具身智能 AI 助手交互区域 | |
| ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "Embodied AI Assistant"); | |
| ImGui::Separator(); | |
| // 显示 Agent 状态 | |
| auto agentState = embodiedAgent_.getState(); | |
| if (agentState == hhb::core::EmbodiedAIState::Processing || | |
| agentState == hhb::core::EmbodiedAIState::ToolExecuting) { | |
| ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "State: %s", embodiedAgent_.getStateString().c_str()); | |
| } else if (agentState == hhb::core::EmbodiedAIState::Error) { | |
| ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "State: %s", embodiedAgent_.getStateString().c_str()); | |
| } else { | |
| ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "State: Ready"); | |
| } | |
| ImGui::InputText("##AIInput", userInputBuffer, sizeof(userInputBuffer)); | |
| ImGui::SameLine(); | |
| if (ImGui::Button("Send", ImVec2(80, 20))) { | |
| processUserInput(); | |
| } | |
| ImGui::Checkbox("Show Highlight", &showHighlight); | |
| if (showHighlight && !highlightIndices.empty()) { | |
| const char* typeStr = "Unknown"; | |
| switch (currentHighlightType) { | |
| case HighlightType::ThinParts: typeStr = "Weak Structure"; break; | |
| case HighlightType::CurvedSurfaces: typeStr = "Curved Surfaces"; break; | |
| case HighlightType::SharpEdges: typeStr = "Sharp Edges"; break; | |
| case HighlightType::FlatSurfaces: typeStr = "Flat Surfaces"; break; | |
| default: break; | |
| } | |
| ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f), "Highlight: %s (%zu parts)", | |
| typeStr, highlightIndices.size()); | |
| } | |
| // 显示最后的 AI 回复 | |
| std::string lastResp = embodiedAgent_.getLastResponse(); | |
| if (!lastResp.empty()) { | |
| ImGui::TextWrapped("AI: %s", lastResp.c_str()); | |
| } | |
| // 显示错误信息 | |
| std::string lastErr = embodiedAgent_.getLastError(); | |
| if (!lastErr.empty() && lastErr.find("[Error]") != std::string::npos) { | |
| ImGui::TextColored(ImVec4(1.0f, 0.2f, 0.2f, 1.0f), "Error: %s", lastErr.c_str()); | |
| } | |
| if (selectedTriangleIndex < 0) { | |
| ImGui::TextDisabled("No triangle selected"); | |
| } | |
| ImGui::End(); | |
| // 具身智能 AI 聊天面板(右侧悬浮窗口) | |
| ImGui::SetNextWindowPos(ImVec2(width - 340, 30), ImGuiCond_FirstUseEver); | |
| ImGui::SetNextWindowSize(ImVec2(330, height - 40), ImGuiCond_FirstUseEver); | |
| ImGui::Begin("Embodied AI Chat", nullptr, ImGuiWindowFlags_NoSavedSettings); | |
| ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.8f, 1.0f), "Embodied AI CAD Assistant"); | |
| ImGui::SameLine(); | |
| if (embodiedAgent_.isProcessing()) { | |
| ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "[Processing...]"); | |
| } | |
| ImGui::Separator(); | |
| // 显示已注册的工具列表 | |
| auto toolNames = hhb::core::LLMClient::getInstance().getRegisteredToolNames(); | |
| if (!toolNames.empty()) { | |
| if (ImGui::CollapsingHeader("Available Tools")) { | |
| for (const auto& name : toolNames) { | |
| ImGui::BulletText("%s", name.c_str()); | |
| } | |
| } | |
| } | |
| // 聊天历史区域 | |
| auto chatHistory = embodiedAgent_.getChatHistory(); | |
| ImGui::BeginChild("ChatHistory", ImVec2(0, -60), true); | |
| for (const auto& msg : chatHistory) { | |
| if (msg.isUser) { | |
| ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f), "[%s] You: %s", | |
| msg.timestamp.c_str(), msg.content.c_str()); | |
| } else { | |
| ImGui::TextColored(ImVec4(0.5f, 1.0f, 0.5f, 1.0f), "[%s] AI: %s", | |
| msg.timestamp.c_str(), msg.content.c_str()); | |
| } | |
| ImGui::Separator(); | |
| } | |
| // 自动滚动到底部 | |
| if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) | |
| ImGui::SetScrollHereY(1.0f); | |
| ImGui::EndChild(); | |
| // 输入区域 | |
| static char chatInputBuffer[512]; | |
| ImGuiInputTextFlags flags = embodiedAgent_.isProcessing() ? ImGuiInputTextFlags_ReadOnly : 0; | |
| bool inputEnter = ImGui::InputText("##ChatInput", chatInputBuffer, sizeof(chatInputBuffer), | |
| flags | ImGuiInputTextFlags_EnterReturnsTrue); | |
| ImGui::SameLine(); | |
| if (ImGui::Button("Send##ChatSend", ImVec2(80, 20)) || inputEnter) { | |
| std::string chatInput(chatInputBuffer); | |
| if (!chatInput.empty() && !embodiedAgent_.isProcessing()) { | |
| strncpy(userInputBuffer, chatInputBuffer, sizeof(userInputBuffer) - 1); | |
| processUserInput(); | |
| memset(chatInputBuffer, 0, sizeof(chatInputBuffer)); | |
| } | |
| } | |
| ImGui::End(); | |
| } | |
| void RenderManager::renderImGui() { | |
| // 渲染ImGui | |
| ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); | |
| } | |
| void RenderManager::processUserInput() { | |
| std::string userInput(userInputBuffer); | |
| if (userInput.empty()) { | |
| return; | |
| } | |
| SetConsoleOutputCP(CP_UTF8); | |
| std::cout << "[RenderManager] User input: " << userInput << std::endl; | |
| // 优先使用具身智能 Agent 闭环处理 | |
| // 闭环流程:用户自然语言 -> LLM 解析为工具调用 -> C++ 执行几何分析 -> OpenGL 高亮显示 | |
| if (!trianglePool || trianglePool->size() == 0) { | |
| std::cout << "[RenderManager] No model loaded" << std::endl; | |
| return; | |
| } | |
| // 异步调用具身智能 Agent | |
| embodiedAgent_.processInputAsync(userInput); | |
| } | |
| void RenderManager::onToolResult(const std::vector<int>& indices, HighlightType type, const std::string& desc) { | |
| // 具身智能工具执行结果回调:将分析结果映射到 OpenGL 高亮缓冲区 | |
| std::lock_guard<std::mutex> lock(highlightMutex); | |
| newHighlightIndices = indices; | |
| currentHighlightType = type; | |
| lastAnalysisDesc = desc; | |
| highlightCalculated = true; | |
| std::cout << "[RenderManager] Tool result received: " << indices.size() | |
| << " indices, desc=" << desc << std::endl; | |
| } | |
| void RenderManager::highlightParts() { | |
| if (!showHighlight || highlightIndices.empty()) { | |
| return; | |
| } | |
| // 闪烁效果:使用正弦波调制透明度/亮度,周期约 0.5 秒 | |
| highlightBlinkTimer_ += deltaTime; | |
| float blink = 0.5f + 0.5f * sinf(highlightBlinkTimer_ * 6.2831853f); | |
| // 闪烁时在暗色和亮色之间切换,实现"呼吸"效果 | |
| float blinkAlpha = 0.6f + 0.4f * blink; | |
| GLint isSelectedLoc = glGetUniformLocation(shaderProgram, "isSelected"); | |
| GLint highlightColorLoc = glGetUniformLocation(shaderProgram, "highlightColor"); | |
| // 基础颜色:薄弱部位使用高亮红 (R:1.0, G:0.2, B:0.2) | |
| float color[3] = {1.0f, 0.2f, 0.2f}; | |
| switch (currentHighlightType) { | |
| case HighlightType::ThinParts: | |
| color[0] = 1.0f * blinkAlpha; color[1] = 0.2f * blinkAlpha; color[2] = 0.2f * blinkAlpha; break; | |
| case HighlightType::CurvedSurfaces: | |
| color[0] = 0.0f; color[1] = 1.0f * blinkAlpha; color[2] = 0.5f * blinkAlpha; break; | |
| case HighlightType::SharpEdges: | |
| color[0] = 1.0f * blinkAlpha; color[1] = 0.5f * blinkAlpha; color[2] = 0.0f; break; | |
| case HighlightType::FlatSurfaces: | |
| color[0] = 0.0f; color[1] = 0.5f * blinkAlpha; color[2] = 1.0f * blinkAlpha; break; | |
| default: break; | |
| } | |
| if (isSelectedLoc != -1 && highlightColorLoc != -1) { | |
| glUniform1i(isSelectedLoc, GL_TRUE); | |
| glUniform3fv(highlightColorLoc, 1, color); | |
| // 关闭深度写入,使高亮始终可见 | |
| glDepthMask(GL_FALSE); | |
| for (int index : highlightIndices) { | |
| glDrawArrays(GL_TRIANGLES, index * 3, 3); | |
| } | |
| glDepthMask(GL_TRUE); | |
| } | |
| } | |
| void RenderManager::shutdownImGui() { | |
| // 清理ImGui资源 | |
| ImGui_ImplOpenGL3_Shutdown(); | |
| ImGui_ImplGlfw_Shutdown(); | |
| ImGui::DestroyContext(); | |
| } | |
| // 保存截图 | |
| bool RenderManager::saveScreenshot(const std::string& filename) { | |
| printf("saveScreenshot() called with: %s\n", filename.c_str()); | |
| fflush(stdout); | |
| // 创建目录(如果不存在) | |
| size_t lastSlash = filename.find_last_of("\\/"); | |
| if (lastSlash != std::string::npos) { | |
| std::string dir = filename.substr(0, lastSlash); | |
| CreateDirectoryA(dir.c_str(), NULL); | |
| mkdir(dir.c_str(), 0755); | |
| } | |
| // 读取像素数据 | |
| std::vector<unsigned char> pixels(width * height * 3); | |
| glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); | |
| // 翻转图像(因为OpenGL坐标系统与图像坐标系统不同) | |
| for (int i = 0; i < height / 2; ++i) { | |
| for (int j = 0; j < width * 3; ++j) { | |
| std::swap(pixels[i * width * 3 + j], pixels[(height - i - 1) * width * 3 + j]); | |
| } | |
| } | |
| // 保存为PPM格式(简单的图像格式,无需外部库) | |
| std::ofstream file(filename); | |
| if (!file) { | |
| printf("Failed to open file for writing: %s\n", filename.c_str()); | |
| fflush(stdout); | |
| return false; | |
| } | |
| file << "P6\n" << width << " " << height << "\n255\n"; | |
| file.write(reinterpret_cast<const char*>(pixels.data()), pixels.size()); | |
| file.close(); | |
| printf("Screenshot saved to: %s\n", filename.c_str()); | |
| fflush(stdout); | |
| return true; | |
| } | |
| // 捕获模型的三个视角 | |
| void RenderManager::captureModelViews(const std::string& modelName) { | |
| printf("captureModelViews() called for model: %s\n", modelName.c_str()); | |
| fflush(stdout); | |
| // 保存原始相机状态 | |
| float originalPos[3] = {cameraPosition[0], cameraPosition[1], cameraPosition[2]}; | |
| float originalRot[2] = {cameraRotation[0], cameraRotation[1]}; | |
| float originalZoom = zoom; | |
| // 确保模型已加载 | |
| if (triangleCount == 0) { | |
| printf("No model loaded, cannot capture views\n"); | |
| fflush(stdout); | |
| return; | |
| } | |
| // 重置相机到模型中心 | |
| centerModel(); | |
| // 视角1:正面 | |
| cameraRotation[0] = 0.0f; // 俯仰角 | |
| cameraRotation[1] = 0.0f; // 偏航角 | |
| zoom = 1.0f; | |
| // 渲染一帧 | |
| render(); | |
| swapBuffers(); | |
| // 保存截图 | |
| std::string view1 = "screenshots/" + modelName + "_view1.ppm"; | |
| saveScreenshot(view1); | |
| // 视角2:侧面 | |
| cameraRotation[1] = 3.14159f / 2.0f; // 90度偏航 | |
| // 渲染一帧 | |
| render(); | |
| swapBuffers(); | |
| // 保存截图 | |
| std::string view2 = "screenshots/" + modelName + "_view2.ppm"; | |
| saveScreenshot(view2); | |
| // 视角3:顶部 | |
| cameraRotation[0] = -3.14159f / 2.0f; // -90度俯仰 | |
| cameraRotation[1] = 0.0f; | |
| // 渲染一帧 | |
| render(); | |
| swapBuffers(); | |
| // 保存截图 | |
| std::string view3 = "screenshots/" + modelName + "_view3.ppm"; | |
| saveScreenshot(view3); | |
| // 恢复原始相机状态 | |
| cameraPosition[0] = originalPos[0]; | |
| cameraPosition[1] = originalPos[1]; | |
| cameraPosition[2] = originalPos[2]; | |
| cameraRotation[0] = originalRot[0]; | |
| cameraRotation[1] = originalRot[1]; | |
| zoom = originalZoom; | |
| // 调用Python脚本进行模型描述和向量存储 | |
| std::string apiKey = "YOUR_OPENAI_API_KEY"; // 请替换为实际的API密钥 | |
| std::string pythonScript = "model_describer.py"; | |
| std::string command = "python " + pythonScript + " " + apiKey + " " + modelName + " " + view1 + " " + view2 + " " + view3; | |
| printf("Executing Python script: %s\n", command.c_str()); | |
| fflush(stdout); | |
| // 执行Python脚本 | |
| int result = system(command.c_str()); | |
| if (result == 0) { | |
| printf("Python script executed successfully\n"); | |
| } else { | |
| printf("Python script execution failed with code: %d\n", result); | |
| } | |
| fflush(stdout); | |
| printf("Captured 3 views for model: %s\n", modelName.c_str()); | |
| fflush(stdout); | |
| } | |
| void RenderManager::sphericalFibonacciSample(int index, int total, float radius, | |
| float* outX, float* outY, float* outZ) { | |
| const float goldenRatio = 1.6180339887498948482f; | |
| const float phi = 2.0f * 3.14159265358979323846f * index / goldenRatio; | |
| float cosTheta = 1.0f - 2.0f * (index + 0.5f) / total; | |
| float sinTheta = std::sqrt(1.0f - cosTheta * cosTheta); | |
| *outX = radius * sinTheta * std::cos(phi); | |
| *outY = radius * cosTheta; | |
| *outZ = radius * sinTheta * std::sin(phi); | |
| } | |
| bool RenderManager::saveFrameAsPNG(const std::string& filename, int w, int h, | |
| const std::vector<unsigned char>& pixels) { | |
| std::vector<unsigned char> flipped(pixels.size()); | |
| for (int row = 0; row < h; ++row) { | |
| const unsigned char* srcRow = pixels.data() + (h - 1 - row) * w * 3; | |
| unsigned char* dstRow = flipped.data() + row * w * 3; | |
| std::memcpy(dstRow, srcRow, w * 3); | |
| } | |
| int result = stbi_write_png(filename.c_str(), w, h, 3, flipped.data(), w * 3); | |
| if (!result) { | |
| printf("[CaptureSynthetic] Failed to save PNG: %s\n", filename.c_str()); | |
| fflush(stdout); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void RenderManager::computeViewMatrixFromPosition(float camX, float camY, float camZ, | |
| float targetX, float targetY, float targetZ, | |
| float* outView16) { | |
| using namespace glm; | |
| vec3 camPos(camX, camY, camZ); | |
| vec3 target(targetX, targetY, targetZ); | |
| vec3 worldUp(0.0f, 1.0f, 0.0f); | |
| vec3 forward = normalize(target - camPos); | |
| vec3 right = normalize(cross(forward, worldUp)); | |
| if (length(right) < 0.0001f) { | |
| vec3 altUp(0.0f, 0.0f, 1.0f); | |
| right = normalize(cross(forward, altUp)); | |
| } | |
| vec3 up = cross(right, forward); | |
| mat4 viewMat = mat4(1.0f); | |
| viewMat[0][0] = right.x; viewMat[1][0] = right.y; viewMat[2][0] = right.z; viewMat[3][0] = -dot(right, camPos); | |
| viewMat[0][1] = up.x; viewMat[1][1] = up.y; viewMat[2][1] = up.z; viewMat[3][1] = -dot(up, camPos); | |
| viewMat[0][2] = -forward.x; viewMat[1][2] = -forward.y; viewMat[2][2] = -forward.z; viewMat[3][2] = dot(forward, camPos); | |
| viewMat[0][3] = 0.0f; viewMat[1][3] = 0.0f; viewMat[2][3] = 0.0f; viewMat[3][3] = 1.0f; | |
| std::memcpy(outView16, value_ptr(viewMat), 16 * sizeof(float)); | |
| } | |
| RenderManager::CaptureResult RenderManager::captureSyntheticData(const CaptureConfig& config) { | |
| CaptureResult result; | |
| result.totalFrames = config.sampleCount; | |
| result.successFrames = 0; | |
| result.failedFrames = 0; | |
| result.outputDirectory = config.outputDir; | |
| printf("\n========== CaptureSyntheticData START ==========\n"); | |
| printf(" Sample count : %d\n", config.sampleCount); | |
| printf(" Output dir : %s\n", config.outputDir.c_str()); | |
| printf(" Camera radius: %.2f\n", config.cameraRadius); | |
| printf(" Image size : %dx%d\n", config.imageWidth, config.imageHeight); | |
| printf(" Save mask : %s\n", config.saveMask ? "YES" : "NO"); | |
| printf(" Save depth : %s\n", config.saveDepth ? "YES" : "NO"); | |
| fflush(stdout); | |
| if (triangleCount == 0) { | |
| printf("[CaptureSynthetic] ERROR: No model loaded!\n"); | |
| fflush(stdout); | |
| return result; | |
| } | |
| if (config.saveMask) { | |
| computeGeometricFeatures(); | |
| updateLabelVertexData(); | |
| } | |
| namespace fs = std::filesystem; | |
| fs::create_directories(config.outputDir); | |
| fs::create_directories(config.outputDir + "/rgb"); | |
| if (config.saveMask) { | |
| fs::create_directories(config.outputDir + "/mask"); | |
| } | |
| if (config.saveDepth) { | |
| fs::create_directories(config.outputDir + "/depth"); | |
| } | |
| float origPos[3] = {cameraPosition[0], cameraPosition[1], cameraPosition[2]}; | |
| float origRot[2] = {cameraRotation[0], cameraRotation[1]}; | |
| float origZoom = zoom; | |
| SpatialInfo info = getSpatialInfo(); | |
| float targetX = info.center[0]; | |
| float targetY = info.center[1]; | |
| float targetZ = info.center[2]; | |
| float origWidth = (float)width; | |
| float origHeight = (float)height; | |
| glfwSetWindowSize(window, config.imageWidth, config.imageHeight); | |
| glViewport(0, 0, config.imageWidth, config.imageHeight); | |
| width = config.imageWidth; | |
| height = config.imageHeight; | |
| auto timeStart = std::chrono::steady_clock::now(); | |
| std::ostringstream cameraPosesJson; | |
| cameraPosesJson << "{\n"; | |
| float fov = 45.0f; | |
| float nearPlane = 0.1f; | |
| float farPlane = 1000.0f; | |
| float aspect = (float)config.imageWidth / (float)config.imageHeight; | |
| float f = 1.0f / tanf(fov * 0.5f * 3.1415926535f / 180.0f); | |
| float projectionArr[16] = { | |
| f / aspect, 0.0f, 0.0f, 0.0f, | |
| 0.0f, f, 0.0f, 0.0f, | |
| 0.0f, 0.0f, (farPlane + nearPlane) / (nearPlane - farPlane), -1.0f, | |
| 0.0f, 0.0f, (2.0f * farPlane * nearPlane) / (nearPlane - farPlane), 0.0f | |
| }; | |
| for (int i = 0; i < config.sampleCount; ++i) { | |
| float camX, camY, camZ; | |
| sphericalFibonacciSample(i, config.sampleCount, config.cameraRadius, | |
| &camX, &camY, &camZ); | |
| camX += targetX; | |
| camY += targetY; | |
| camZ += targetZ; | |
| cameraPosition[0] = targetX; | |
| cameraPosition[1] = targetY; | |
| cameraPosition[2] = targetZ; | |
| float dx = camX - targetX; | |
| float dy = camY - targetY; | |
| float dz = camZ - targetZ; | |
| float dist = std::sqrt(dx * dx + dy * dy + dz * dz); | |
| cameraRotation[0] = std::asin(dy / dist); | |
| cameraRotation[1] = std::atan2(dx, dz); | |
| zoom = 5.0f / dist; | |
| float viewMatrix[16]; | |
| computeViewMatrixFromPosition(camX, camY, camZ, | |
| targetX, targetY, targetZ, | |
| viewMatrix); | |
| if (i > 0) cameraPosesJson << ",\n"; | |
| cameraPosesJson << " \"" << (i + 1) << "\": {\n"; | |
| cameraPosesJson << " \"position\": [" << camX << ", " << camY << ", " << camZ << "],\n"; | |
| cameraPosesJson << " \"target\": [" << targetX << ", " << targetY << ", " << targetZ << "],\n"; | |
| cameraPosesJson << " \"rotation\": [" << cameraRotation[0] << ", " << cameraRotation[1] << "],\n"; | |
| cameraPosesJson << " \"view_matrix\": ["; | |
| for (int vi = 0; vi < 16; ++vi) { | |
| cameraPosesJson << viewMatrix[vi]; | |
| if (vi < 15) cameraPosesJson << ", "; | |
| } | |
| cameraPosesJson << "],\n"; | |
| cameraPosesJson << " \"projection_matrix\": ["; | |
| for (int pi = 0; pi < 16; ++pi) { | |
| cameraPosesJson << projectionArr[pi]; | |
| if (pi < 15) cameraPosesJson << ", "; | |
| } | |
| cameraPosesJson << "],\n"; | |
| cameraPosesJson << " \"fov_degrees\": " << fov << ",\n"; | |
| cameraPosesJson << " \"near_plane\": " << nearPlane << ",\n"; | |
| cameraPosesJson << " \"far_plane\": " << farPlane << ",\n"; | |
| cameraPosesJson << " \"image_width\": " << config.imageWidth << ",\n"; | |
| cameraPosesJson << " \"image_height\": " << config.imageHeight << "\n"; | |
| cameraPosesJson << " }"; | |
| // === Pass 1: RGB rendering (PBR shader) === | |
| render(); | |
| swapBuffers(); | |
| std::vector<unsigned char> rgbPixels(config.imageWidth * config.imageHeight * 3); | |
| glReadPixels(0, 0, config.imageWidth, config.imageHeight, | |
| GL_RGB, GL_UNSIGNED_BYTE, rgbPixels.data()); | |
| std::ostringstream rgbOss; | |
| rgbOss << config.outputDir << "/rgb/frame_" | |
| << std::setfill('0') << std::setw(4) << (i + 1) << ".png"; | |
| if (saveFrameAsPNG(rgbOss.str(), config.imageWidth, config.imageHeight, rgbPixels)) { | |
| result.successFrames++; | |
| } else { | |
| result.failedFrames++; | |
| } | |
| // === Pass 2: Mask rendering (label shader) === | |
| if (config.saveMask && labelShaderProgram) { | |
| glClearColor(0.0f, 0.0f, 0.0f, 1.0f); | |
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
| glEnable(GL_DEPTH_TEST); | |
| renderLabelMode(); | |
| swapBuffers(); | |
| std::vector<unsigned char> maskPixels(config.imageWidth * config.imageHeight * 3); | |
| glReadPixels(0, 0, config.imageWidth, config.imageHeight, | |
| GL_RGB, GL_UNSIGNED_BYTE, maskPixels.data()); | |
| std::ostringstream maskOss; | |
| maskOss << config.outputDir << "/mask/mask_" | |
| << std::setfill('0') << std::setw(4) << (i + 1) << ".png"; | |
| saveFrameAsPNG(maskOss.str(), config.imageWidth, config.imageHeight, maskPixels); | |
| glClearColor(0.1f, 0.1f, 0.1f, 1.0f); | |
| } | |
| // === Pass 3: Depth map rendering === | |
| if (config.saveDepth) { | |
| std::vector<float> depthPixels(config.imageWidth * config.imageHeight); | |
| glReadPixels(0, 0, config.imageWidth, config.imageHeight, | |
| GL_DEPTH_COMPONENT, GL_FLOAT, depthPixels.data()); | |
| std::vector<unsigned char> depthPng(config.imageWidth * config.imageHeight * 3); | |
| for (int px = 0; px < config.imageWidth * config.imageHeight; ++px) { | |
| float d = depthPixels[px]; | |
| if (d >= 1.0f) d = 1.0f; | |
| if (d <= 0.0f) d = 0.0f; | |
| unsigned char depthByte = (unsigned char)(d * 255.0f); | |
| depthPng[px * 3 + 0] = depthByte; | |
| depthPng[px * 3 + 1] = depthByte; | |
| depthPng[px * 3 + 2] = depthByte; | |
| } | |
| std::ostringstream depthOss; | |
| depthOss << config.outputDir << "/depth/depth_" | |
| << std::setfill('0') << std::setw(4) << (i + 1) << ".png"; | |
| saveFrameAsPNG(depthOss.str(), config.imageWidth, config.imageHeight, depthPng); | |
| std::ostringstream npyOss; | |
| npyOss << config.outputDir << "/depth/depth_" | |
| << std::setfill('0') << std::setw(4) << (i + 1) << ".raw"; | |
| std::ofstream depthFile(npyOss.str(), std::ios::binary); | |
| if (depthFile.is_open()) { | |
| depthFile.write(reinterpret_cast<const char*>(depthPixels.data()), | |
| depthPixels.size() * sizeof(float)); | |
| depthFile.close(); | |
| } | |
| } | |
| glfwPollEvents(); | |
| if ((i + 1) % 50 == 0 || i == 0) { | |
| printf("[CaptureSynthetic] Progress: %d/%d (%.1f%%)\n", | |
| i + 1, config.sampleCount, | |
| 100.0f * (i + 1) / config.sampleCount); | |
| fflush(stdout); | |
| } | |
| } | |
| cameraPosesJson << "\n}\n"; | |
| { | |
| std::string posesPath = config.outputDir + "/camera_poses.json"; | |
| std::ofstream posesFile(posesPath); | |
| if (posesFile.is_open()) { | |
| posesFile << cameraPosesJson.str(); | |
| posesFile.close(); | |
| printf("[CaptureSynthetic] Camera poses saved to: %s\n", posesPath.c_str()); | |
| } | |
| } | |
| auto timeEnd = std::chrono::steady_clock::now(); | |
| result.elapsedSeconds = std::chrono::duration<float>(timeEnd - timeStart).count(); | |
| cameraPosition[0] = origPos[0]; | |
| cameraPosition[1] = origPos[1]; | |
| cameraPosition[2] = origPos[2]; | |
| cameraRotation[0] = origRot[0]; | |
| cameraRotation[1] = origRot[1]; | |
| zoom = origZoom; | |
| glfwSetWindowSize(window, (int)origWidth, (int)origHeight); | |
| glViewport(0, 0, (int)origWidth, (int)origHeight); | |
| width = (int)origWidth; | |
| height = (int)origHeight; | |
| if (config.saveMask) { | |
| std::string legendPath = config.outputDir + "/label_legend.txt"; | |
| std::ofstream legendFile(legendPath); | |
| if (legendFile.is_open()) { | |
| legendFile << "# Semantic Label Color Legend\n"; | |
| legendFile << "# Category -> (R, G, B) in 0-255 range\n\n"; | |
| const char* categoryNames[] = { | |
| "FreeSurface", "HorizontalPlane", "LateralPlane_X", | |
| "LateralPlane_Z", "NearHorizontal", "NearLateral_X", | |
| "NearLateral_Z", "Degenerate", "ConvexFeature_Bolt", | |
| "ConcaveFeature_Hole", "Flange", "Boss" | |
| }; | |
| for (int c = 0; c < 12; ++c) { | |
| float r, g, b; | |
| categoryToColor(c, &r, &g, &b); | |
| legendFile << c << " " << categoryNames[c] << " " | |
| << (int)(r * 255) << " " << (int)(g * 255) << " " << (int)(b * 255) << "\n"; | |
| } | |
| legendFile.close(); | |
| printf("[CaptureSynthetic] Label legend saved to: %s\n", legendPath.c_str()); | |
| } | |
| } | |
| printf("\n========== CaptureSyntheticData COMPLETE ==========\n"); | |
| printf(" Total : %d\n", result.totalFrames); | |
| printf(" Success : %d\n", result.successFrames); | |
| printf(" Failed : %d\n", result.failedFrames); | |
| printf(" Time : %.2f seconds\n", result.elapsedSeconds); | |
| printf(" Output : %s\n", result.outputDirectory.c_str()); | |
| fflush(stdout); | |
| return result; | |
| } | |
| void RenderManager::openFileDialog() { | |
| printf("openFileDialog() called - opening Windows native file dialog...\n"); | |
| fflush(stdout); | |
| OPENFILENAMEA ofn; | |
| ZeroMemory(&ofn, sizeof(ofn)); | |
| ofn.lStructSize = sizeof(ofn); | |
| ofn.hwndOwner = glfwGetWin32Window(window); | |
| char localPathBuffer[512] = {0}; | |
| ofn.lpstrFile = localPathBuffer; | |
| ofn.nMaxFile = sizeof(localPathBuffer); | |
| ofn.lpstrFilter = "STL Files (*.stl)\0*.stl\0All Files (*.*)\0*.*\0"; | |
| ofn.nFilterIndex = 1; | |
| ofn.lpstrFileTitle = nullptr; | |
| ofn.nMaxFileTitle = 0; | |
| ofn.lpstrInitialDir = nullptr; | |
| ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; | |
| printf("Calling GetOpenFileNameA...\n"); | |
| fflush(stdout); | |
| if (GetOpenFileNameA(&ofn)) { | |
| printf("File selected: %s\n", localPathBuffer); | |
| fflush(stdout); | |
| loadFile(localPathBuffer); | |
| } else { | |
| DWORD err = CommDlgExtendedError(); | |
| if (err != 0) { | |
| printf("GetOpenFileNameA failed with error code: %lu\n", err); | |
| fflush(stdout); | |
| } else { | |
| printf("User cancelled file selection\n"); | |
| fflush(stdout); | |
| } | |
| } | |
| printf("File dialog not implemented for this platform\n"); | |
| fflush(stdout); | |
| } | |
| void RenderManager::loadFile(const std::string& filename) { | |
| printf("loadFile() called with: %s\n", filename.c_str()); | |
| fflush(stdout); | |
| // 记录加载开始时间 | |
| auto load_start = std::chrono::high_resolution_clock::now(); | |
| // 创建新的对象池 | |
| if (trianglePool) { | |
| delete trianglePool; | |
| } | |
| trianglePool = new hhb::core::ObjectPool<hhb::core::Triangle>(); | |
| // 解析STL文件 | |
| hhb::core::StlParser parser; | |
| hhb::core::ParserResult result = parser.parse(filename, *trianglePool); | |
| // 记录加载结束时间 | |
| auto load_end = std::chrono::high_resolution_clock::now(); | |
| std::chrono::duration<double, std::milli> load_duration = load_end - load_start; | |
| loadTime = static_cast<float>(load_duration.count()); | |
| if (result.success) { | |
| printf("File loaded successfully in %.2f ms\n", loadTime); | |
| printf("Triangles parsed: %zu\n", result.count); | |
| fflush(stdout); | |
| // 更新顶点数据 | |
| updateVertexData(*trianglePool); | |
| printf("Vertex data updated, vertexData.size() = %zu\n", vertexData.size()); | |
| fflush(stdout); | |
| // 绑定VAO和VBO | |
| glBindVertexArray(VAO); | |
| glBindBuffer(GL_ARRAY_BUFFER, VBO); | |
| glBufferData(GL_ARRAY_BUFFER, vertexData.size() * sizeof(float), vertexData.data(), GL_STATIC_DRAW); | |
| // 设置顶点属性指针 | |
| // 位置属性 (location = 0) | |
| glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); | |
| glEnableVertexAttribArray(0); | |
| // 法线属性 (location = 1) | |
| glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); | |
| glEnableVertexAttribArray(1); | |
| glBindBuffer(GL_ARRAY_BUFFER, 0); | |
| glBindVertexArray(0); | |
| // 构建BVH树 | |
| buildBVH(); | |
| // 加载完模型后,自动适配视角 | |
| centerModel(); | |
| // 更新内存使用统计 | |
| memoryUsage = triangleCount * sizeof(hhb::core::Triangle); | |
| // 重置选中状态 | |
| selectedTriangle = nullptr; | |
| selectedTriangleIndex = -1; | |
| pickTime = 0.0f; | |
| geometryAPI.loadFromPool(*trianglePool); | |
| std::cout << "Model loaded into GeometryAPI from shared pool: " << geometryAPI.getTriangleCount() << " triangles" << std::endl; | |
| // 加载模型到 GeometryExpert | |
| geometryExpert.loadModelFromPool(*trianglePool); | |
| std::cout << "Model loaded into GeometryExpert from shared pool" << std::endl; | |
| // 捕获模型的三个视角 | |
| std::string modelName = filename.substr(filename.find_last_of("\\/") + 1); | |
| modelName = modelName.substr(0, modelName.find_last_of(".")); | |
| captureModelViews(modelName); | |
| printf("Model loading complete. Triangle count: %zu\n", triangleCount); | |
| fflush(stdout); | |
| } else { | |
| printf("Failed to load file: %s\n", result.error.c_str()); | |
| fflush(stdout); | |
| } | |
| } | |
| void RenderManager::renderBVH() { | |
| if (!showBVH || bvh.node_count() == 0) { | |
| return; | |
| } | |
| // 使用线框模式渲染BVH包围盒 | |
| glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); | |
| // 设置线框颜色(绿色) | |
| float bvhColor[3] = {0.0f, 1.0f, 0.0f}; | |
| GLint objectColorLoc = glGetUniformLocation(shaderProgram, "objectColor"); | |
| glUniform3fv(objectColorLoc, 1, bvhColor); | |
| // 关闭光照影响,直接渲染纯色 | |
| GLint metallicLoc = glGetUniformLocation(shaderProgram, "metallic"); | |
| GLint roughnessLoc = glGetUniformLocation(shaderProgram, "roughness"); | |
| if (metallicLoc != -1) glUniform1f(metallicLoc, 0.0f); | |
| if (roughnessLoc != -1) glUniform1f(roughnessLoc, 1.0f); | |
| // 仅渲染根节点包围盒作为示例(完整遍历需要访问BVH内部节点) | |
| hhb::core::Bounds rootBounds = bvh.get_root_bounds(); | |
| renderAABB(rootBounds, bvhColor); | |
| // 恢复填充模式 | |
| glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); | |
| } | |
| void RenderManager::renderAABB(const hhb::core::Bounds& bounds, const float* color) { | |
| // 构建AABB的8个顶点 | |
| float vertices[] = { | |
| bounds.min[0], bounds.min[1], bounds.min[2], | |
| bounds.max[0], bounds.min[1], bounds.min[2], | |
| bounds.max[0], bounds.max[1], bounds.min[2], | |
| bounds.min[0], bounds.max[1], bounds.min[2], | |
| bounds.min[0], bounds.min[1], bounds.max[2], | |
| bounds.max[0], bounds.min[1], bounds.max[2], | |
| bounds.max[0], bounds.max[1], bounds.max[2], | |
| bounds.min[0], bounds.max[1], bounds.max[2] | |
| }; | |
| // 12条线段的索引 (每条线2个顶点) | |
| unsigned int indices[] = { | |
| 0,1, 1,2, 2,3, 3,0, // 底面 | |
| 4,5, 5,6, 6,7, 7,4, // 顶面 | |
| 0,4, 1,5, 2,6, 3,7 // 侧面 | |
| }; | |
| GLuint vao, vbo, ebo; | |
| glGenVertexArrays(1, &vao); | |
| glGenBuffers(1, &vbo); | |
| glGenBuffers(1, &ebo); | |
| glBindVertexArray(vao); | |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); | |
| glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); | |
| glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); | |
| glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); | |
| glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); | |
| glEnableVertexAttribArray(0); | |
| // 禁用法线属性,因为这里只有顶点 | |
| glDisableVertexAttribArray(1); | |
| // 绘制线框 | |
| glDrawElements(GL_LINES, 24, GL_UNSIGNED_INT, 0); | |
| // 清理 | |
| glDeleteVertexArrays(1, &vao); | |
| glDeleteBuffers(1, &vbo); | |
| glDeleteBuffers(1, &ebo); | |
| } | |
| void RenderManager::setTargetCameraPosition(const float pos[3], float duration) { | |
| if (duration <= 0.0f) { | |
| duration = 0.001f; | |
| } | |
| if (cameraAnimating) { | |
| cameraPosStart[0] = cameraPosition[0]; | |
| cameraPosStart[1] = cameraPosition[1]; | |
| cameraPosStart[2] = cameraPosition[2]; | |
| } else { | |
| memcpy(cameraPosStart, cameraPosition, sizeof(cameraPosStart)); | |
| } | |
| targetCameraPos[0] = pos[0]; | |
| targetCameraPos[1] = pos[1]; | |
| targetCameraPos[2] = pos[2]; | |
| cameraAnimStart = std::chrono::steady_clock::now(); | |
| cameraAnimDuration = duration; | |
| cameraAnimating = true; | |
| } | |
| void RenderManager::setTargetCameraRotation(const float rot[2], float duration) { | |
| if (duration <= 0.0f) { | |
| duration = 0.001f; | |
| } | |
| if (cameraAnimating) { | |
| cameraRotStart[0] = cameraRotation[0]; | |
| cameraRotStart[1] = cameraRotation[1]; | |
| } else { | |
| memcpy(cameraRotStart, cameraRotation, sizeof(cameraRotStart)); | |
| } | |
| targetCameraRot[0] = rot[0]; | |
| targetCameraRot[1] = rot[1]; | |
| cameraAnimStart = std::chrono::steady_clock::now(); | |
| cameraAnimDuration = duration; | |
| cameraAnimating = true; | |
| } | |
| void RenderManager::setTargetZoom(float target, float duration) { | |
| if (duration <= 0.0f) { | |
| duration = 0.001f; | |
| } | |
| if (cameraAnimating) { | |
| zoomStart = zoom; | |
| } else { | |
| zoomStart = zoom; | |
| } | |
| targetZoom = target; | |
| cameraAnimStart = std::chrono::steady_clock::now(); | |
| cameraAnimDuration = duration; | |
| cameraAnimating = true; | |
| } | |
| bool RenderManager::isCameraAnimating() const { | |
| return cameraAnimating; | |
| } | |
| void RenderManager::stopCameraAnimation() { | |
| cameraAnimating = false; | |
| } | |
| RenderManager::SpatialInfo RenderManager::getSpatialInfo() { | |
| SpatialInfo info; | |
| // 计算模型的几何中心和包围盒 | |
| if (trianglePtrs.empty()) { | |
| // 模型为空,返回默认值 | |
| memset(info.center, 0, sizeof(info.center)); | |
| memset(info.bounds, 0, sizeof(info.bounds)); | |
| } else { | |
| // 计算包围盒 | |
| float minX = FLT_MAX, minY = FLT_MAX, minZ = FLT_MAX; | |
| float maxX = -FLT_MAX, maxY = -FLT_MAX, maxZ = -FLT_MAX; | |
| for (const auto& triangle : trianglePtrs) { | |
| // 处理第一个顶点 | |
| minX = std::min(minX, triangle->vertex1[0]); | |
| minY = std::min(minY, triangle->vertex1[1]); | |
| minZ = std::min(minZ, triangle->vertex1[2]); | |
| maxX = std::max(maxX, triangle->vertex1[0]); | |
| maxY = std::max(maxY, triangle->vertex1[1]); | |
| maxZ = std::max(maxZ, triangle->vertex1[2]); | |
| // 处理第二个顶点 | |
| minX = std::min(minX, triangle->vertex2[0]); | |
| minY = std::min(minY, triangle->vertex2[1]); | |
| minZ = std::min(minZ, triangle->vertex2[2]); | |
| maxX = std::max(maxX, triangle->vertex2[0]); | |
| maxY = std::max(maxY, triangle->vertex2[1]); | |
| maxZ = std::max(maxZ, triangle->vertex2[2]); | |
| // 处理第三个顶点 | |
| minX = std::min(minX, triangle->vertex3[0]); | |
| minY = std::min(minY, triangle->vertex3[1]); | |
| minZ = std::min(minZ, triangle->vertex3[2]); | |
| maxX = std::max(maxX, triangle->vertex3[0]); | |
| maxY = std::max(maxY, triangle->vertex3[1]); | |
| maxZ = std::max(maxZ, triangle->vertex3[2]); | |
| } | |
| // 计算几何中心 | |
| info.center[0] = (minX + maxX) / 2.0f; | |
| info.center[1] = (minY + maxY) / 2.0f; | |
| info.center[2] = (minZ + maxZ) / 2.0f; | |
| // 保存包围盒信息 | |
| info.bounds[0] = minX; | |
| info.bounds[1] = minY; | |
| info.bounds[2] = minZ; | |
| info.bounds[3] = maxX; | |
| info.bounds[4] = maxY; | |
| info.bounds[5] = maxZ; | |
| } | |
| // 保存摄像头信息 | |
| memcpy(info.cameraPos, cameraPosition, sizeof(info.cameraPos)); | |
| memcpy(info.cameraRot, cameraRotation, sizeof(info.cameraRot)); | |
| info.currentZoom = zoom; | |
| return info; | |
| } | |
| } // namespace render | |
| } // namespace hhb |