268 lines
9.3 KiB
C++
268 lines
9.3 KiB
C++
|
|
#pragma once
|
|||
|
|
/**
|
|||
|
|
* @file assembly_lod.h
|
|||
|
|
* @brief 大装配 LOD(细节层次)系统
|
|||
|
|
*
|
|||
|
|
* 为装配体节点提供三级 LOD 支持,基于视距自动切换。
|
|||
|
|
* 集成 InstanceCache 实现几何共享,减少大装配的内存占用。
|
|||
|
|
*
|
|||
|
|
* LOD 级别:
|
|||
|
|
* - Full: 完整 B-Rep(精确几何)
|
|||
|
|
* - Simplified: QEM 简化网格(快速渲染)
|
|||
|
|
* - BoundingBox: AABB 包围盒(远距离/不可见)
|
|||
|
|
*
|
|||
|
|
* @ingroup brep
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
#include "vde/brep/assembly.h"
|
|||
|
|
#include "vde/brep/large_assembly.h"
|
|||
|
|
#include "vde/core/aabb.h"
|
|||
|
|
#include <functional>
|
|||
|
|
#include <optional>
|
|||
|
|
|
|||
|
|
namespace vde::brep {
|
|||
|
|
|
|||
|
|
/// 将局部空间 AABB 变换到世界空间
|
|||
|
|
[[nodiscard]] core::AABB3D compute_transformed_bounds(
|
|||
|
|
const core::AABB3D& local_bb, const Transform3D& tf);
|
|||
|
|
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// LODLevel
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/// 细节层次枚举
|
|||
|
|
enum class LODLevel {
|
|||
|
|
Full, ///< 完整 B-Rep 模型(精确几何)
|
|||
|
|
Simplified, ///< QEM 简化后的三角网格
|
|||
|
|
BoundingBox ///< 仅 AABB 包围盒(远距离/剔除测试)
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/// LOD 级别转可读字符串
|
|||
|
|
[[nodiscard]] inline const char* to_string(LODLevel level) {
|
|||
|
|
switch (level) {
|
|||
|
|
case LODLevel::Full: return "Full";
|
|||
|
|
case LODLevel::Simplified: return "Simplified";
|
|||
|
|
case LODLevel::BoundingBox: return "BoundingBox";
|
|||
|
|
}
|
|||
|
|
return "Unknown";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// LODNode — per-node LOD state
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/// 每个装配节点的 LOD 状态
|
|||
|
|
struct LODNode {
|
|||
|
|
AssemblyNode* node = nullptr; ///< 对应的装配节点
|
|||
|
|
core::AABB3D world_bounds; ///< 世界空间包围盒(缓存)
|
|||
|
|
LODLevel current_level = LODLevel::Full; ///< 当前 LOD 级别
|
|||
|
|
bool manual_override = false; ///< 是否手动设置了级别
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// LODConfig
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/// LOD 系统配置参数
|
|||
|
|
struct LODConfig {
|
|||
|
|
double near_plane = 50.0; ///< 近距离阈值:距离 < near_plane → Full
|
|||
|
|
double far_plane = 500.0; ///< 远距离阈值:距离 > far_plane → BoundingBox
|
|||
|
|
double screen_size_threshold = 0.02; ///< 屏幕空间尺寸比阈值
|
|||
|
|
int simplify_num_levels = 3; ///< QEM 简化级别数
|
|||
|
|
bool enable_auto_lod = true; ///< 是否启用自动 LOD 切换
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
// AssemblyLOD
|
|||
|
|
// ═══════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 大装配 LOD 管理器
|
|||
|
|
*
|
|||
|
|
* 管理装配体所有节点的 LOD 级别。支持:
|
|||
|
|
* - 自动视距驱动 LOD 选择
|
|||
|
|
* - 手动设置节点 LOD 级别
|
|||
|
|
* - 按 LOD 级别遍历装配体
|
|||
|
|
* - 集成 InstanceCache 共享几何
|
|||
|
|
*
|
|||
|
|
* @code{.cpp}
|
|||
|
|
* Assembly assy("robot");
|
|||
|
|
* // ... 添加零件 ...
|
|||
|
|
* AssemblyLOD lod(assy);
|
|||
|
|
* lod.set_config(LODConfig{ .near_plane = 100, .far_plane = 1000 });
|
|||
|
|
*
|
|||
|
|
* // 自动选择 LOD
|
|||
|
|
* lod.compute_lod(camera_distance, screen_size);
|
|||
|
|
*
|
|||
|
|
* // 按 LOD 遍历
|
|||
|
|
* lod.traverse_assembly([&](const LODNode& n) {
|
|||
|
|
* switch (n.current_level) {
|
|||
|
|
* case LODLevel::Full: render_brep(n); break;
|
|||
|
|
* case LODLevel::Simplified: render_simplified(n); break;
|
|||
|
|
* case LODLevel::BoundingBox: render_bbox(n); break;
|
|||
|
|
* }
|
|||
|
|
* });
|
|||
|
|
* @endcode
|
|||
|
|
*
|
|||
|
|
* @ingroup brep
|
|||
|
|
*/
|
|||
|
|
class AssemblyLOD {
|
|||
|
|
public:
|
|||
|
|
/**
|
|||
|
|
* @brief 从装配体构建 LOD 管理器
|
|||
|
|
*
|
|||
|
|
* 收集所有零件节点,预计算世界空间 AABB,
|
|||
|
|
* 通过 InstanceCache 注册可共享的几何。
|
|||
|
|
*
|
|||
|
|
* @param assembly 装配体引用
|
|||
|
|
*/
|
|||
|
|
explicit AssemblyLOD(Assembly& assembly);
|
|||
|
|
|
|||
|
|
/// 获取配置
|
|||
|
|
[[nodiscard]] const LODConfig& config() const { return config_; }
|
|||
|
|
|
|||
|
|
/// 设置配置
|
|||
|
|
void set_config(const LODConfig& cfg);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 获取所有 LOD 节点(扁平化列表)
|
|||
|
|
* @return LODNode 列表引用
|
|||
|
|
*/
|
|||
|
|
[[nodiscard]] const std::vector<LODNode>& nodes() const { return nodes_; }
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 根据视距和屏幕尺寸自动选择 LOD 级别
|
|||
|
|
*
|
|||
|
|
* 选择规则:
|
|||
|
|
* - distance > far_plane → BoundingBox
|
|||
|
|
* - distance < near_plane → Full
|
|||
|
|
* - near_plane ≤ distance ≤ far_plane → Simplified
|
|||
|
|
*
|
|||
|
|
* @param camera_distance 相机到装配体中心的距离
|
|||
|
|
* @param screen_size 屏幕空间尺寸比(节点投影 / 屏幕尺寸)
|
|||
|
|
*/
|
|||
|
|
void compute_lod(double camera_distance, double screen_size);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 自动计算单个节点的 LOD 级别
|
|||
|
|
*
|
|||
|
|
* @param node LOD 节点(含预计算的 world_bounds)
|
|||
|
|
* @param camera_distance 相机到装配体中心的距离
|
|||
|
|
* @param screen_size 屏幕空间尺寸比
|
|||
|
|
* @return 推荐的 LOD 级别
|
|||
|
|
*/
|
|||
|
|
[[nodiscard]] LODLevel compute_lod_for_node(
|
|||
|
|
const LODNode& node,
|
|||
|
|
double camera_distance,
|
|||
|
|
double screen_size) const;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 手动设置某节点的 LOD 级别
|
|||
|
|
*
|
|||
|
|
* 手动设置后,该节点将忽略自动 LOD 选择直到调用 clear_manual_override()。
|
|||
|
|
*
|
|||
|
|
* @param node LOD 节点(通过 nodes() 获取)
|
|||
|
|
* @param level 要设置的 LOD 级别
|
|||
|
|
*/
|
|||
|
|
void set_lod_level(LODNode& node, LODLevel level);
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 清除手动 LOD 设置
|
|||
|
|
*
|
|||
|
|
* @param node 要清除手动覆盖的节点
|
|||
|
|
*/
|
|||
|
|
void clear_manual_override(LODNode& node);
|
|||
|
|
|
|||
|
|
/// 清除所有节点的手动 LOD 设置
|
|||
|
|
void clear_all_manual_overrides();
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 按 LOD 级别遍历装配体
|
|||
|
|
*
|
|||
|
|
* 对每个零件节点调用 visitor,传入其 LODNode 状态。
|
|||
|
|
*
|
|||
|
|
* @param visitor 回调函数:void(const LODNode&)
|
|||
|
|
*/
|
|||
|
|
template <typename Visitor>
|
|||
|
|
void traverse_assembly(Visitor&& visitor) const {
|
|||
|
|
for (const auto& node : nodes_) {
|
|||
|
|
visitor(node);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* @brief 按 LOD 遍历(带装配层级上下文)
|
|||
|
|
*
|
|||
|
|
* 遍历装配体树,对每个零件节点调用 visitor。
|
|||
|
|
* 传入 LODNode、父变换矩阵、深度。
|
|||
|
|
*
|
|||
|
|
* @param visitor 回调:bool(LODNode&, const Transform3D& parent_tf, int depth)
|
|||
|
|
* 返回 false 可提前终止遍历
|
|||
|
|
*/
|
|||
|
|
template <typename Visitor>
|
|||
|
|
bool traverse_with_context(Visitor&& visitor) const {
|
|||
|
|
return traverse_impl(&assembly_.root, Transform3D::Identity(), 0,
|
|||
|
|
std::forward<Visitor>(visitor));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 获取 InstanceCache
|
|||
|
|
[[nodiscard]] InstanceCache& cache() { return cache_; }
|
|||
|
|
[[nodiscard]] const InstanceCache& cache() const { return cache_; }
|
|||
|
|
|
|||
|
|
/// 零件总数
|
|||
|
|
[[nodiscard]] size_t part_count() const { return nodes_.size(); }
|
|||
|
|
|
|||
|
|
/// 各级别统计
|
|||
|
|
struct LODStats {
|
|||
|
|
size_t full_count = 0;
|
|||
|
|
size_t simplified_count = 0;
|
|||
|
|
size_t bbox_count = 0;
|
|||
|
|
size_t manual_count = 0;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/// 获取当前 LOD 统计信息
|
|||
|
|
[[nodiscard]] LODStats statistics() const;
|
|||
|
|
|
|||
|
|
/// 获取装配体总包围盒
|
|||
|
|
[[nodiscard]] core::AABB3D total_bounds() const { return total_bounds_; }
|
|||
|
|
|
|||
|
|
/// 更新所有节点的世界空间包围盒(变换改变后调用)
|
|||
|
|
void update_bounds();
|
|||
|
|
|
|||
|
|
private:
|
|||
|
|
Assembly& assembly_;
|
|||
|
|
LODConfig config_;
|
|||
|
|
std::vector<LODNode> nodes_;
|
|||
|
|
InstanceCache cache_;
|
|||
|
|
core::AABB3D total_bounds_;
|
|||
|
|
|
|||
|
|
/// 收集所有零件节点
|
|||
|
|
void collect_nodes(AssemblyNode* node, const Transform3D& parent_tf);
|
|||
|
|
|
|||
|
|
/// 带上下文的遍历实现
|
|||
|
|
template <typename Visitor>
|
|||
|
|
bool traverse_impl(AssemblyNode* node, const Transform3D& parent_tf,
|
|||
|
|
int depth, Visitor&& visitor) const {
|
|||
|
|
Transform3D world = parent_tf * node->local_transform;
|
|||
|
|
if (node->is_part()) {
|
|||
|
|
// 查找对应的 LODNode
|
|||
|
|
for (auto& ln : nodes_) {
|
|||
|
|
if (ln.node == node) {
|
|||
|
|
if (!visitor(ln, world, depth))
|
|||
|
|
return false;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for (const auto& child : node->children) {
|
|||
|
|
if (!traverse_impl(child.get(), world, depth + 1,
|
|||
|
|
std::forward<Visitor>(visitor)))
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
} // namespace vde::brep
|