Compare commits
7 Commits
6626ee33e5
...
9287c8050a
| Author | SHA1 | Date | |
|---|---|---|---|
| 9287c8050a | |||
| 7b84fffd2a | |||
|
|
2ee6bcd414 | ||
|
|
72db40115f | ||
|
|
9271a37a81 | ||
|
|
44180b7f85 | ||
|
|
74072f9620 |
23
CMakeLists.txt
Normal file
23
CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(WoodCamUtils LANGUAGES C CXX)
|
||||
|
||||
option(WOODCAM_BUILD_CLOUDVIEW3D "Build the Qt/OpenGL CloudView3D utility" ON)
|
||||
option(WOODCAM_BUILD_CLOUDUTILS "Build the CloudUtils static library" ON)
|
||||
|
||||
# VrCommon is a header-oriented compatibility module. Keep it as an interface
|
||||
# target so consumers get the canonical VZNL_Types.h include path.
|
||||
if(NOT TARGET VrCommon)
|
||||
add_library(VrCommon INTERFACE)
|
||||
target_include_directories(VrCommon INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/VrCommon/Inc
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WOODCAM_BUILD_CLOUDVIEW3D)
|
||||
add_subdirectory(CloudView3D)
|
||||
endif()
|
||||
|
||||
if(WOODCAM_BUILD_CLOUDUTILS)
|
||||
add_subdirectory(CloudUtils)
|
||||
endif()
|
||||
55
CloudUtils/CMakeLists.txt
Normal file
55
CloudUtils/CMakeLists.txt
Normal file
@ -0,0 +1,55 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Gui)
|
||||
|
||||
if(NOT TARGET VrCommon)
|
||||
add_library(VrCommon INTERFACE)
|
||||
target_include_directories(VrCommon INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../VrCommon/Inc
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET VrUtils)
|
||||
add_subdirectory(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../VrUtils"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/VrUtils"
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(CloudUtils STATIC
|
||||
Src/LaserDataLoader.cpp
|
||||
Src/PointCloudImageUtils.cpp
|
||||
Inc/LaserDataLoader.h
|
||||
Inc/PointCloudImageUtils.h
|
||||
)
|
||||
|
||||
set_target_properties(CloudUtils PROPERTIES
|
||||
CXX_STANDARD 11
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
target_include_directories(CloudUtils PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Inc
|
||||
)
|
||||
|
||||
# When CamTool is built with WoodSDK, use its VZ compatibility types first.
|
||||
# This keeps LaserDataLoader's SVzNL* signatures identical to the application
|
||||
# translation units, where WoodSDK maps them to the public SWD* structures.
|
||||
if(TARGET WoodSDK)
|
||||
target_include_directories(CloudUtils BEFORE PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/../../App/woodcam-sdk/WoodSDK/VzToWDInc"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(CloudUtils
|
||||
PUBLIC
|
||||
VrCommon
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
PRIVATE
|
||||
VrUtils
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(CloudUtils PRIVATE /utf-8)
|
||||
endif()
|
||||
@ -1,37 +1,37 @@
|
||||
# CloudUtils搴撻」鐩厤缃枃浠?
|
||||
|
||||
# 鎸囧畾Qt妯″潡
|
||||
QT += core
|
||||
|
||||
# 鎸囧畾椤圭洰妯℃澘涓洪潤鎬佸簱
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
|
||||
win32-msvc {
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
}
|
||||
|
||||
|
||||
# 鎸囧畾C++鏍囧噯
|
||||
CONFIG += c++11
|
||||
|
||||
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/_Inc
|
||||
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/Inc
|
||||
|
||||
INCLUDEPATH += $$PWD/../VrCommon/Inc
|
||||
INCLUDEPATH += $$PWD/../VrUtils/Inc
|
||||
|
||||
# 鎸囧畾鍖呭惈璺緞
|
||||
INCLUDEPATH += ./Inc \
|
||||
./Src
|
||||
|
||||
# 鎸囧畾澶存枃浠?
|
||||
HEADERS += Inc/LaserDataLoader.h \
|
||||
Inc/PointCloudImageUtils.h \
|
||||
Inc/PointCloudFilter.h \
|
||||
Inc/PointCloudXYZRange.h
|
||||
|
||||
# 鎸囧畾婧愭枃浠?
|
||||
SOURCES += Src/LaserDataLoader.cpp \
|
||||
Src/PointCloudImageUtils.cpp \
|
||||
Src/PointCloudFilter.cpp
|
||||
# CloudUtils搴撻」鐩厤缃枃浠?
|
||||
|
||||
# 鎸囧畾Qt妯″潡
|
||||
QT += core
|
||||
|
||||
# 鎸囧畾椤圭洰妯℃澘涓洪潤鎬佸簱
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib
|
||||
|
||||
win32-msvc {
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
}
|
||||
|
||||
|
||||
# 鎸囧畾C++鏍囧噯
|
||||
CONFIG += c++11
|
||||
|
||||
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/_Inc
|
||||
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/Inc
|
||||
|
||||
INCLUDEPATH += $$PWD/../VrCommon/Inc
|
||||
INCLUDEPATH += $$PWD/../VrUtils/Inc
|
||||
|
||||
# 鎸囧畾鍖呭惈璺緞
|
||||
INCLUDEPATH += ./Inc \
|
||||
./Src
|
||||
|
||||
# 鎸囧畾澶存枃浠?
|
||||
HEADERS += Inc/LaserDataLoader.h \
|
||||
Inc/PointCloudImageUtils.h \
|
||||
Inc/PointCloudFilter.h \
|
||||
Inc/PointCloudXYZRange.h
|
||||
|
||||
# 鎸囧畾婧愭枃浠?
|
||||
SOURCES += Src/LaserDataLoader.cpp \
|
||||
Src/PointCloudImageUtils.cpp \
|
||||
Src/PointCloudFilter.cpp
|
||||
|
||||
@ -1,59 +1,59 @@
|
||||
#ifndef POINTCLOUDFILTER_H
|
||||
#define POINTCLOUDFILTER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "VZNL_Types.h"
|
||||
|
||||
// Selects the outlier removal algorithm.
|
||||
enum class PointCloudFilterMethod
|
||||
{
|
||||
None = 0,
|
||||
ScanLineContinuity,
|
||||
RadiusOutlier,
|
||||
StatisticalOutlier
|
||||
};
|
||||
|
||||
struct PointCloudFilterParams
|
||||
{
|
||||
PointCloudFilterMethod method = PointCloudFilterMethod::None;
|
||||
|
||||
// ScanLineContinuity parameters. Units follow the point cloud (normally mm).
|
||||
double continuityThreshold = 20.0;
|
||||
std::size_t minSegmentLength = 5;
|
||||
|
||||
// RadiusOutlier parameters. minNeighbors excludes the point itself.
|
||||
double radius = 5.0;
|
||||
std::size_t minNeighbors = 2;
|
||||
|
||||
// StatisticalOutlier parameters.
|
||||
std::size_t meanK = 8;
|
||||
double standardDeviationMultiplier = 1.0;
|
||||
// Zero estimates a radius from the cloud density; non-zero limits the search.
|
||||
double searchRadius = 0.0;
|
||||
};
|
||||
|
||||
struct PointCloudFilterStats
|
||||
{
|
||||
std::size_t inputPointCount = 0;
|
||||
std::size_t outputPointCount = 0;
|
||||
std::size_t removedPointCount = 0;
|
||||
};
|
||||
|
||||
class PointCloudFilter
|
||||
{
|
||||
public:
|
||||
// The input is not modified. Original nPointIdx values are retained.
|
||||
static bool Filter(const std::vector<std::vector<SVzNL3DPosition>>& input,
|
||||
std::vector<std::vector<SVzNL3DPosition>>& output,
|
||||
const PointCloudFilterParams& params = PointCloudFilterParams(),
|
||||
PointCloudFilterStats* stats = nullptr);
|
||||
|
||||
static bool Filter(const std::vector<SVzNL3DPosition>& input,
|
||||
std::vector<SVzNL3DPosition>& output,
|
||||
const PointCloudFilterParams& params = PointCloudFilterParams(),
|
||||
PointCloudFilterStats* stats = nullptr);
|
||||
};
|
||||
|
||||
#endif // POINTCLOUDFILTER_H
|
||||
#ifndef POINTCLOUDFILTER_H
|
||||
#define POINTCLOUDFILTER_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "VZNL_Types.h"
|
||||
|
||||
// Selects the outlier removal algorithm.
|
||||
enum class PointCloudFilterMethod
|
||||
{
|
||||
None = 0,
|
||||
ScanLineContinuity,
|
||||
RadiusOutlier,
|
||||
StatisticalOutlier
|
||||
};
|
||||
|
||||
struct PointCloudFilterParams
|
||||
{
|
||||
PointCloudFilterMethod method = PointCloudFilterMethod::None;
|
||||
|
||||
// ScanLineContinuity parameters. Units follow the point cloud (normally mm).
|
||||
double continuityThreshold = 20.0;
|
||||
std::size_t minSegmentLength = 5;
|
||||
|
||||
// RadiusOutlier parameters. minNeighbors excludes the point itself.
|
||||
double radius = 5.0;
|
||||
std::size_t minNeighbors = 2;
|
||||
|
||||
// StatisticalOutlier parameters.
|
||||
std::size_t meanK = 8;
|
||||
double standardDeviationMultiplier = 1.0;
|
||||
// Zero estimates a radius from the cloud density; non-zero limits the search.
|
||||
double searchRadius = 0.0;
|
||||
};
|
||||
|
||||
struct PointCloudFilterStats
|
||||
{
|
||||
std::size_t inputPointCount = 0;
|
||||
std::size_t outputPointCount = 0;
|
||||
std::size_t removedPointCount = 0;
|
||||
};
|
||||
|
||||
class PointCloudFilter
|
||||
{
|
||||
public:
|
||||
// The input is not modified. Original nPointIdx values are retained.
|
||||
static bool Filter(const std::vector<std::vector<SVzNL3DPosition>>& input,
|
||||
std::vector<std::vector<SVzNL3DPosition>>& output,
|
||||
const PointCloudFilterParams& params = PointCloudFilterParams(),
|
||||
PointCloudFilterStats* stats = nullptr);
|
||||
|
||||
static bool Filter(const std::vector<SVzNL3DPosition>& input,
|
||||
std::vector<SVzNL3DPosition>& output,
|
||||
const PointCloudFilterParams& params = PointCloudFilterParams(),
|
||||
PointCloudFilterStats* stats = nullptr);
|
||||
};
|
||||
|
||||
#endif // POINTCLOUDFILTER_H
|
||||
|
||||
@ -1,72 +1,72 @@
|
||||
#ifndef POINT_CLOUD_XYZ_RANGE_H
|
||||
#define POINT_CLOUD_XYZ_RANGE_H
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
// XYZ 范围外点的处理方式。
|
||||
//
|
||||
// ReplaceWithZero 保留点在线内的原始槽位,并仅把三维坐标置为零;
|
||||
// Remove 则删除范围外点并压缩扫描线。保留槽位对于按扫描线/网格
|
||||
// 处理的调用方很重要,因此它也是启用范围过滤时的默认模式。
|
||||
enum class PointCloudXYZRangeFilterMode
|
||||
{
|
||||
ReplaceWithZero = 0,
|
||||
Remove = 1
|
||||
};
|
||||
|
||||
// 兼容不需要区分 XYZ 范围限定名称的调用方。
|
||||
using PointCloudRangeFilterMode = PointCloudXYZRangeFilterMode;
|
||||
|
||||
// 点云加载使用的可选轴对齐范围;默认关闭以保持原有读取行为。
|
||||
struct PointCloudXYZRange
|
||||
{
|
||||
using FilterMode = PointCloudXYZRangeFilterMode;
|
||||
|
||||
bool enabled = false;
|
||||
double minX = -std::numeric_limits<double>::infinity();
|
||||
double maxX = std::numeric_limits<double>::infinity();
|
||||
double minY = -std::numeric_limits<double>::infinity();
|
||||
double maxY = std::numeric_limits<double>::infinity();
|
||||
double minZ = -std::numeric_limits<double>::infinity();
|
||||
double maxZ = std::numeric_limits<double>::infinity();
|
||||
// Keep this after the legacy bounds so aggregate initialization remains compatible.
|
||||
PointCloudXYZRangeFilterMode filterMode =
|
||||
PointCloudXYZRangeFilterMode::ReplaceWithZero;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return !std::isnan(minX) && !std::isnan(maxX)
|
||||
&& !std::isnan(minY) && !std::isnan(maxY)
|
||||
&& !std::isnan(minZ) && !std::isnan(maxZ)
|
||||
&& minX <= maxX && minY <= maxY && minZ <= maxZ;
|
||||
}
|
||||
|
||||
bool contains(double x, double y, double z) const
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!isValid() || !std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return x >= minX && x <= maxX
|
||||
&& y >= minY && y <= maxY
|
||||
&& z >= minZ && z <= maxZ;
|
||||
}
|
||||
|
||||
bool isEnabled() const
|
||||
{
|
||||
return enabled && isValid();
|
||||
}
|
||||
};
|
||||
|
||||
using PointCloudRange = PointCloudXYZRange;
|
||||
|
||||
#endif // POINT_CLOUD_XYZ_RANGE_H
|
||||
#ifndef POINT_CLOUD_XYZ_RANGE_H
|
||||
#define POINT_CLOUD_XYZ_RANGE_H
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
// XYZ 范围外点的处理方式。
|
||||
//
|
||||
// ReplaceWithZero 保留点在线内的原始槽位,并仅把三维坐标置为零;
|
||||
// Remove 则删除范围外点并压缩扫描线。保留槽位对于按扫描线/网格
|
||||
// 处理的调用方很重要,因此它也是启用范围过滤时的默认模式。
|
||||
enum class PointCloudXYZRangeFilterMode
|
||||
{
|
||||
ReplaceWithZero = 0,
|
||||
Remove = 1
|
||||
};
|
||||
|
||||
// 兼容不需要区分 XYZ 范围限定名称的调用方。
|
||||
using PointCloudRangeFilterMode = PointCloudXYZRangeFilterMode;
|
||||
|
||||
// 点云加载使用的可选轴对齐范围;默认关闭以保持原有读取行为。
|
||||
struct PointCloudXYZRange
|
||||
{
|
||||
using FilterMode = PointCloudXYZRangeFilterMode;
|
||||
|
||||
bool enabled = false;
|
||||
double minX = -std::numeric_limits<double>::infinity();
|
||||
double maxX = std::numeric_limits<double>::infinity();
|
||||
double minY = -std::numeric_limits<double>::infinity();
|
||||
double maxY = std::numeric_limits<double>::infinity();
|
||||
double minZ = -std::numeric_limits<double>::infinity();
|
||||
double maxZ = std::numeric_limits<double>::infinity();
|
||||
// Keep this after the legacy bounds so aggregate initialization remains compatible.
|
||||
PointCloudXYZRangeFilterMode filterMode =
|
||||
PointCloudXYZRangeFilterMode::ReplaceWithZero;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return !std::isnan(minX) && !std::isnan(maxX)
|
||||
&& !std::isnan(minY) && !std::isnan(maxY)
|
||||
&& !std::isnan(minZ) && !std::isnan(maxZ)
|
||||
&& minX <= maxX && minY <= maxY && minZ <= maxZ;
|
||||
}
|
||||
|
||||
bool contains(double x, double y, double z) const
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!isValid() || !std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return x >= minX && x <= maxX
|
||||
&& y >= minY && y <= maxY
|
||||
&& z >= minZ && z <= maxZ;
|
||||
}
|
||||
|
||||
bool isEnabled() const
|
||||
{
|
||||
return enabled && isValid();
|
||||
}
|
||||
};
|
||||
|
||||
using PointCloudRange = PointCloudXYZRange;
|
||||
|
||||
#endif // POINT_CLOUD_XYZ_RANGE_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,383 +1,383 @@
|
||||
#include "PointCloudFilter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct GridKey
|
||||
{
|
||||
long long x;
|
||||
long long y;
|
||||
long long z;
|
||||
|
||||
bool operator==(const GridKey& other) const
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
};
|
||||
|
||||
struct GridKeyHash
|
||||
{
|
||||
std::size_t operator()(const GridKey& key) const
|
||||
{
|
||||
const std::size_t h1 = std::hash<long long>()(key.x);
|
||||
const std::size_t h2 = std::hash<long long>()(key.y);
|
||||
const std::size_t h3 = std::hash<long long>()(key.z);
|
||||
return h1 ^ (h2 + static_cast<std::size_t>(0x9e3779b9) + (h1 << 6) + (h1 >> 2))
|
||||
^ (h3 + static_cast<std::size_t>(0x9e3779b9) + (h2 << 6) + (h2 >> 2));
|
||||
}
|
||||
};
|
||||
|
||||
struct FlatPoint
|
||||
{
|
||||
SVzNL3DPosition point;
|
||||
};
|
||||
|
||||
bool IsFinite(const SVzNL3DPosition& point)
|
||||
{
|
||||
return std::isfinite(point.pt3D.x) && std::isfinite(point.pt3D.y)
|
||||
&& std::isfinite(point.pt3D.z);
|
||||
}
|
||||
|
||||
double SquaredDistance(const SVzNL3DPosition& first, const SVzNL3DPosition& second)
|
||||
{
|
||||
const double dx = first.pt3D.x - second.pt3D.x;
|
||||
const double dy = first.pt3D.y - second.pt3D.y;
|
||||
const double dz = first.pt3D.z - second.pt3D.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
long long ToCell(double value, double cellSize)
|
||||
{
|
||||
const double coordinate = std::floor(value / cellSize);
|
||||
if (coordinate <= static_cast<double>(std::numeric_limits<long long>::min())) {
|
||||
return std::numeric_limits<long long>::min();
|
||||
}
|
||||
if (coordinate >= static_cast<double>(std::numeric_limits<long long>::max())) {
|
||||
return std::numeric_limits<long long>::max();
|
||||
}
|
||||
return static_cast<long long>(coordinate);
|
||||
}
|
||||
|
||||
GridKey GetKey(const SVzNL3DPosition& point, double cellSize)
|
||||
{
|
||||
GridKey key;
|
||||
key.x = ToCell(point.pt3D.x, cellSize);
|
||||
key.y = ToCell(point.pt3D.y, cellSize);
|
||||
key.z = ToCell(point.pt3D.z, cellSize);
|
||||
return key;
|
||||
}
|
||||
|
||||
void ResetStats(PointCloudFilterStats* stats, std::size_t inputCount)
|
||||
{
|
||||
if (stats != nullptr) {
|
||||
stats->inputPointCount = inputCount;
|
||||
stats->outputPointCount = 0;
|
||||
stats->removedPointCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FinishStats(PointCloudFilterStats* stats, std::size_t outputCount)
|
||||
{
|
||||
if (stats != nullptr) {
|
||||
stats->outputPointCount = outputCount;
|
||||
stats->removedPointCount = stats->inputPointCount >= outputCount
|
||||
? stats->inputPointCount - outputCount : 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool FilterScanLine(const std::vector<SVzNL3DPosition>& input,
|
||||
std::vector<SVzNL3DPosition>& output,
|
||||
const PointCloudFilterParams& params)
|
||||
{
|
||||
output.clear();
|
||||
if (input.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::size_t minLength = std::max<std::size_t>(1, params.minSegmentLength);
|
||||
std::size_t segmentBegin = 0;
|
||||
for (std::size_t index = 1; index <= input.size(); ++index) {
|
||||
bool connected = index < input.size() && IsFinite(input[index - 1])
|
||||
&& IsFinite(input[index])
|
||||
&& std::abs(input[index].pt3D.z - input[index - 1].pt3D.z)
|
||||
<= std::max(0.0, params.continuityThreshold);
|
||||
if (index == input.size() || !connected) {
|
||||
if (index - segmentBegin >= minLength) {
|
||||
output.insert(output.end(), input.begin() + static_cast<std::ptrdiff_t>(segmentBegin),
|
||||
input.begin() + static_cast<std::ptrdiff_t>(index));
|
||||
}
|
||||
segmentBegin = index;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
double EstimateCellSize(const std::vector<FlatPoint>& points)
|
||||
{
|
||||
if (points.size() < 2) {
|
||||
return 1.0;
|
||||
}
|
||||
double minX = points[0].point.pt3D.x;
|
||||
double maxX = minX;
|
||||
double minY = points[0].point.pt3D.y;
|
||||
double maxY = minY;
|
||||
double minZ = points[0].point.pt3D.z;
|
||||
double maxZ = minZ;
|
||||
for (std::size_t index = 1; index < points.size(); ++index) {
|
||||
const SVzNL3DPoint& value = points[index].point.pt3D;
|
||||
minX = std::min(minX, value.x);
|
||||
maxX = std::max(maxX, value.x);
|
||||
minY = std::min(minY, value.y);
|
||||
maxY = std::max(maxY, value.y);
|
||||
minZ = std::min(minZ, value.z);
|
||||
maxZ = std::max(maxZ, value.z);
|
||||
}
|
||||
const double diagonal = std::sqrt((maxX - minX) * (maxX - minX)
|
||||
+ (maxY - minY) * (maxY - minY) + (maxZ - minZ) * (maxZ - minZ));
|
||||
const double scale = diagonal / std::cbrt(static_cast<double>(points.size()));
|
||||
return std::max(scale, 1e-6);
|
||||
}
|
||||
|
||||
template <typename KeepPredicate>
|
||||
void FilterWithGrid(const std::vector<FlatPoint>& points, double cellSize,
|
||||
double radius, KeepPredicate keepPoint,
|
||||
std::vector<bool>& keep)
|
||||
{
|
||||
std::unordered_map<GridKey, std::vector<std::size_t>, GridKeyHash> grid;
|
||||
grid.reserve(points.size() * 2 + 1);
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
grid[GetKey(points[index].point, cellSize)].push_back(index);
|
||||
}
|
||||
|
||||
const long long cellRange = std::max<long long>(1, static_cast<long long>(std::ceil(radius / cellSize)));
|
||||
const double radiusSquared = radius * radius;
|
||||
std::vector<std::size_t> candidates;
|
||||
candidates.reserve(32);
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
const GridKey center = GetKey(points[index].point, cellSize);
|
||||
candidates.clear();
|
||||
for (long long x = -cellRange; x <= cellRange; ++x) {
|
||||
for (long long y = -cellRange; y <= cellRange; ++y) {
|
||||
for (long long z = -cellRange; z <= cellRange; ++z) {
|
||||
GridKey key = {center.x + x, center.y + y, center.z + z};
|
||||
const auto found = grid.find(key);
|
||||
if (found != grid.end()) {
|
||||
candidates.insert(candidates.end(), found->second.begin(), found->second.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keep[index] = keepPoint(index, candidates, radiusSquared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PointCloudFilter::Filter(const std::vector<std::vector<SVzNL3DPosition>>& input,
|
||||
std::vector<std::vector<SVzNL3DPosition>>& output,
|
||||
const PointCloudFilterParams& params,
|
||||
PointCloudFilterStats* stats)
|
||||
{
|
||||
std::size_t inputCount = 0;
|
||||
for (const auto& line : input) {
|
||||
inputCount += line.size();
|
||||
}
|
||||
ResetStats(stats, inputCount);
|
||||
output.clear();
|
||||
output.resize(input.size());
|
||||
|
||||
if (params.method == PointCloudFilterMethod::None) {
|
||||
output = input;
|
||||
FinishStats(stats, inputCount);
|
||||
return true;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::ScanLineContinuity) {
|
||||
std::size_t outputCount = 0;
|
||||
for (std::size_t lineIndex = 0; lineIndex < input.size(); ++lineIndex) {
|
||||
FilterScanLine(input[lineIndex], output[lineIndex], params);
|
||||
outputCount += output[lineIndex].size();
|
||||
}
|
||||
FinishStats(stats, outputCount);
|
||||
return true;
|
||||
}
|
||||
if (params.method != PointCloudFilterMethod::RadiusOutlier
|
||||
&& params.method != PointCloudFilterMethod::StatisticalOutlier) {
|
||||
output.clear();
|
||||
return false;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::RadiusOutlier && params.radius <= 0.0) {
|
||||
output.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<FlatPoint> points;
|
||||
points.reserve(inputCount);
|
||||
for (const auto& line : input) {
|
||||
for (const SVzNL3DPosition& point : line) {
|
||||
if (IsFinite(point)) {
|
||||
points.push_back({point});
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<SVzNL3DPosition> filtered;
|
||||
std::vector<SVzNL3DPosition> flatInput;
|
||||
flatInput.reserve(points.size());
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
flatInput.push_back(points[index].point);
|
||||
// Encode the flattened position only for reconstructing scan lines.
|
||||
flatInput.back().nPointIdx = static_cast<int>(index);
|
||||
}
|
||||
if (!Filter(flatInput, filtered, params, nullptr)) {
|
||||
output.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<bool> retained(points.size(), false);
|
||||
for (const SVzNL3DPosition& point : filtered) {
|
||||
if (point.nPointIdx >= 0
|
||||
&& static_cast<std::size_t>(point.nPointIdx) < retained.size()) {
|
||||
retained[static_cast<std::size_t>(point.nPointIdx)] = true;
|
||||
}
|
||||
}
|
||||
std::size_t pointIndex = 0;
|
||||
for (std::size_t lineIndex = 0; lineIndex < input.size(); ++lineIndex) {
|
||||
for (const SVzNL3DPosition& point : input[lineIndex]) {
|
||||
if (IsFinite(point)) {
|
||||
if (pointIndex < retained.size() && retained[pointIndex]) {
|
||||
output[lineIndex].push_back(point);
|
||||
}
|
||||
++pointIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::size_t outputCount = 0;
|
||||
for (const auto& line : output) {
|
||||
outputCount += line.size();
|
||||
}
|
||||
FinishStats(stats, outputCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PointCloudFilter::Filter(const std::vector<SVzNL3DPosition>& input,
|
||||
std::vector<SVzNL3DPosition>& output,
|
||||
const PointCloudFilterParams& params,
|
||||
PointCloudFilterStats* stats)
|
||||
{
|
||||
ResetStats(stats, input.size());
|
||||
output.clear();
|
||||
if (params.method == PointCloudFilterMethod::None) {
|
||||
output = input;
|
||||
FinishStats(stats, output.size());
|
||||
return true;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::ScanLineContinuity) {
|
||||
FilterScanLine(input, output, params);
|
||||
FinishStats(stats, output.size());
|
||||
return true;
|
||||
}
|
||||
if (params.method != PointCloudFilterMethod::RadiusOutlier
|
||||
&& params.method != PointCloudFilterMethod::StatisticalOutlier) {
|
||||
return false;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::RadiusOutlier && params.radius <= 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<FlatPoint> points;
|
||||
points.reserve(input.size());
|
||||
for (const SVzNL3DPosition& point : input) {
|
||||
if (IsFinite(point)) {
|
||||
points.push_back({point});
|
||||
}
|
||||
}
|
||||
if (points.empty()) {
|
||||
FinishStats(stats, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
const double requestedRadius = params.method == PointCloudFilterMethod::RadiusOutlier
|
||||
? params.radius : params.searchRadius;
|
||||
const double radius = requestedRadius > 0.0 ? requestedRadius : EstimateCellSize(points) * 2.0;
|
||||
const double cellSize = std::max(radius, 1e-6);
|
||||
std::vector<bool> keep(points.size(), false);
|
||||
|
||||
if (params.method == PointCloudFilterMethod::RadiusOutlier) {
|
||||
const std::size_t minNeighbors = params.minNeighbors;
|
||||
FilterWithGrid(points, cellSize, radius,
|
||||
[&points, minNeighbors](std::size_t index, const std::vector<std::size_t>& candidates,
|
||||
double radiusSquared) {
|
||||
std::size_t neighbors = 0;
|
||||
for (std::size_t candidate : candidates) {
|
||||
if (candidate != index && SquaredDistance(points[index].point, points[candidate].point) <= radiusSquared) {
|
||||
++neighbors;
|
||||
}
|
||||
}
|
||||
return neighbors >= minNeighbors;
|
||||
}, keep);
|
||||
}
|
||||
|
||||
if (params.method == PointCloudFilterMethod::StatisticalOutlier) {
|
||||
std::vector<double> meanDistances(points.size(), std::numeric_limits<double>::infinity());
|
||||
FilterWithGrid(points, cellSize, radius,
|
||||
[&points, &meanDistances, ¶ms](std::size_t index,
|
||||
const std::vector<std::size_t>& candidates,
|
||||
double radiusSquared) {
|
||||
std::vector<double> distances;
|
||||
distances.reserve(candidates.size());
|
||||
for (std::size_t candidate : candidates) {
|
||||
if (candidate != index) {
|
||||
const double distance = SquaredDistance(points[index].point, points[candidate].point);
|
||||
if (distance <= radiusSquared) {
|
||||
distances.push_back(std::sqrt(distance));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (distances.empty()) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t count = std::min(params.meanK == 0 ? std::size_t(1) : params.meanK,
|
||||
distances.size());
|
||||
if (count < distances.size()) {
|
||||
std::nth_element(distances.begin(),
|
||||
distances.begin() + static_cast<std::ptrdiff_t>(count),
|
||||
distances.end());
|
||||
}
|
||||
meanDistances[index] = std::accumulate(distances.begin(), distances.begin() + static_cast<std::ptrdiff_t>(count), 0.0)
|
||||
/ static_cast<double>(count);
|
||||
return true;
|
||||
}, keep);
|
||||
std::vector<double> validMeans;
|
||||
for (double value : meanDistances) {
|
||||
if (std::isfinite(value)) {
|
||||
validMeans.push_back(value);
|
||||
}
|
||||
}
|
||||
if (!validMeans.empty()) {
|
||||
const double mean = std::accumulate(validMeans.begin(), validMeans.end(), 0.0)
|
||||
/ static_cast<double>(validMeans.size());
|
||||
double variance = 0.0;
|
||||
for (double value : validMeans) {
|
||||
const double delta = value - mean;
|
||||
variance += delta * delta;
|
||||
}
|
||||
const double deviation = std::sqrt(variance / static_cast<double>(validMeans.size()));
|
||||
const double threshold = mean + std::max(0.0, params.standardDeviationMultiplier) * deviation;
|
||||
for (std::size_t index = 0; index < keep.size(); ++index) {
|
||||
keep[index] = std::isfinite(meanDistances[index]) && meanDistances[index] <= threshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.reserve(points.size());
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
if (keep[index]) {
|
||||
output.push_back(points[index].point);
|
||||
}
|
||||
}
|
||||
FinishStats(stats, output.size());
|
||||
return true;
|
||||
}
|
||||
#include "PointCloudFilter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct GridKey
|
||||
{
|
||||
long long x;
|
||||
long long y;
|
||||
long long z;
|
||||
|
||||
bool operator==(const GridKey& other) const
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
}
|
||||
};
|
||||
|
||||
struct GridKeyHash
|
||||
{
|
||||
std::size_t operator()(const GridKey& key) const
|
||||
{
|
||||
const std::size_t h1 = std::hash<long long>()(key.x);
|
||||
const std::size_t h2 = std::hash<long long>()(key.y);
|
||||
const std::size_t h3 = std::hash<long long>()(key.z);
|
||||
return h1 ^ (h2 + static_cast<std::size_t>(0x9e3779b9) + (h1 << 6) + (h1 >> 2))
|
||||
^ (h3 + static_cast<std::size_t>(0x9e3779b9) + (h2 << 6) + (h2 >> 2));
|
||||
}
|
||||
};
|
||||
|
||||
struct FlatPoint
|
||||
{
|
||||
SVzNL3DPosition point;
|
||||
};
|
||||
|
||||
bool IsFinite(const SVzNL3DPosition& point)
|
||||
{
|
||||
return std::isfinite(point.pt3D.x) && std::isfinite(point.pt3D.y)
|
||||
&& std::isfinite(point.pt3D.z);
|
||||
}
|
||||
|
||||
double SquaredDistance(const SVzNL3DPosition& first, const SVzNL3DPosition& second)
|
||||
{
|
||||
const double dx = first.pt3D.x - second.pt3D.x;
|
||||
const double dy = first.pt3D.y - second.pt3D.y;
|
||||
const double dz = first.pt3D.z - second.pt3D.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
}
|
||||
|
||||
long long ToCell(double value, double cellSize)
|
||||
{
|
||||
const double coordinate = std::floor(value / cellSize);
|
||||
if (coordinate <= static_cast<double>(std::numeric_limits<long long>::min())) {
|
||||
return std::numeric_limits<long long>::min();
|
||||
}
|
||||
if (coordinate >= static_cast<double>(std::numeric_limits<long long>::max())) {
|
||||
return std::numeric_limits<long long>::max();
|
||||
}
|
||||
return static_cast<long long>(coordinate);
|
||||
}
|
||||
|
||||
GridKey GetKey(const SVzNL3DPosition& point, double cellSize)
|
||||
{
|
||||
GridKey key;
|
||||
key.x = ToCell(point.pt3D.x, cellSize);
|
||||
key.y = ToCell(point.pt3D.y, cellSize);
|
||||
key.z = ToCell(point.pt3D.z, cellSize);
|
||||
return key;
|
||||
}
|
||||
|
||||
void ResetStats(PointCloudFilterStats* stats, std::size_t inputCount)
|
||||
{
|
||||
if (stats != nullptr) {
|
||||
stats->inputPointCount = inputCount;
|
||||
stats->outputPointCount = 0;
|
||||
stats->removedPointCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FinishStats(PointCloudFilterStats* stats, std::size_t outputCount)
|
||||
{
|
||||
if (stats != nullptr) {
|
||||
stats->outputPointCount = outputCount;
|
||||
stats->removedPointCount = stats->inputPointCount >= outputCount
|
||||
? stats->inputPointCount - outputCount : 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool FilterScanLine(const std::vector<SVzNL3DPosition>& input,
|
||||
std::vector<SVzNL3DPosition>& output,
|
||||
const PointCloudFilterParams& params)
|
||||
{
|
||||
output.clear();
|
||||
if (input.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::size_t minLength = std::max<std::size_t>(1, params.minSegmentLength);
|
||||
std::size_t segmentBegin = 0;
|
||||
for (std::size_t index = 1; index <= input.size(); ++index) {
|
||||
bool connected = index < input.size() && IsFinite(input[index - 1])
|
||||
&& IsFinite(input[index])
|
||||
&& std::abs(input[index].pt3D.z - input[index - 1].pt3D.z)
|
||||
<= std::max(0.0, params.continuityThreshold);
|
||||
if (index == input.size() || !connected) {
|
||||
if (index - segmentBegin >= minLength) {
|
||||
output.insert(output.end(), input.begin() + static_cast<std::ptrdiff_t>(segmentBegin),
|
||||
input.begin() + static_cast<std::ptrdiff_t>(index));
|
||||
}
|
||||
segmentBegin = index;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
double EstimateCellSize(const std::vector<FlatPoint>& points)
|
||||
{
|
||||
if (points.size() < 2) {
|
||||
return 1.0;
|
||||
}
|
||||
double minX = points[0].point.pt3D.x;
|
||||
double maxX = minX;
|
||||
double minY = points[0].point.pt3D.y;
|
||||
double maxY = minY;
|
||||
double minZ = points[0].point.pt3D.z;
|
||||
double maxZ = minZ;
|
||||
for (std::size_t index = 1; index < points.size(); ++index) {
|
||||
const SVzNL3DPoint& value = points[index].point.pt3D;
|
||||
minX = std::min(minX, value.x);
|
||||
maxX = std::max(maxX, value.x);
|
||||
minY = std::min(minY, value.y);
|
||||
maxY = std::max(maxY, value.y);
|
||||
minZ = std::min(minZ, value.z);
|
||||
maxZ = std::max(maxZ, value.z);
|
||||
}
|
||||
const double diagonal = std::sqrt((maxX - minX) * (maxX - minX)
|
||||
+ (maxY - minY) * (maxY - minY) + (maxZ - minZ) * (maxZ - minZ));
|
||||
const double scale = diagonal / std::cbrt(static_cast<double>(points.size()));
|
||||
return std::max(scale, 1e-6);
|
||||
}
|
||||
|
||||
template <typename KeepPredicate>
|
||||
void FilterWithGrid(const std::vector<FlatPoint>& points, double cellSize,
|
||||
double radius, KeepPredicate keepPoint,
|
||||
std::vector<bool>& keep)
|
||||
{
|
||||
std::unordered_map<GridKey, std::vector<std::size_t>, GridKeyHash> grid;
|
||||
grid.reserve(points.size() * 2 + 1);
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
grid[GetKey(points[index].point, cellSize)].push_back(index);
|
||||
}
|
||||
|
||||
const long long cellRange = std::max<long long>(1, static_cast<long long>(std::ceil(radius / cellSize)));
|
||||
const double radiusSquared = radius * radius;
|
||||
std::vector<std::size_t> candidates;
|
||||
candidates.reserve(32);
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
const GridKey center = GetKey(points[index].point, cellSize);
|
||||
candidates.clear();
|
||||
for (long long x = -cellRange; x <= cellRange; ++x) {
|
||||
for (long long y = -cellRange; y <= cellRange; ++y) {
|
||||
for (long long z = -cellRange; z <= cellRange; ++z) {
|
||||
GridKey key = {center.x + x, center.y + y, center.z + z};
|
||||
const auto found = grid.find(key);
|
||||
if (found != grid.end()) {
|
||||
candidates.insert(candidates.end(), found->second.begin(), found->second.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keep[index] = keepPoint(index, candidates, radiusSquared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PointCloudFilter::Filter(const std::vector<std::vector<SVzNL3DPosition>>& input,
|
||||
std::vector<std::vector<SVzNL3DPosition>>& output,
|
||||
const PointCloudFilterParams& params,
|
||||
PointCloudFilterStats* stats)
|
||||
{
|
||||
std::size_t inputCount = 0;
|
||||
for (const auto& line : input) {
|
||||
inputCount += line.size();
|
||||
}
|
||||
ResetStats(stats, inputCount);
|
||||
output.clear();
|
||||
output.resize(input.size());
|
||||
|
||||
if (params.method == PointCloudFilterMethod::None) {
|
||||
output = input;
|
||||
FinishStats(stats, inputCount);
|
||||
return true;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::ScanLineContinuity) {
|
||||
std::size_t outputCount = 0;
|
||||
for (std::size_t lineIndex = 0; lineIndex < input.size(); ++lineIndex) {
|
||||
FilterScanLine(input[lineIndex], output[lineIndex], params);
|
||||
outputCount += output[lineIndex].size();
|
||||
}
|
||||
FinishStats(stats, outputCount);
|
||||
return true;
|
||||
}
|
||||
if (params.method != PointCloudFilterMethod::RadiusOutlier
|
||||
&& params.method != PointCloudFilterMethod::StatisticalOutlier) {
|
||||
output.clear();
|
||||
return false;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::RadiusOutlier && params.radius <= 0.0) {
|
||||
output.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<FlatPoint> points;
|
||||
points.reserve(inputCount);
|
||||
for (const auto& line : input) {
|
||||
for (const SVzNL3DPosition& point : line) {
|
||||
if (IsFinite(point)) {
|
||||
points.push_back({point});
|
||||
}
|
||||
}
|
||||
}
|
||||
std::vector<SVzNL3DPosition> filtered;
|
||||
std::vector<SVzNL3DPosition> flatInput;
|
||||
flatInput.reserve(points.size());
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
flatInput.push_back(points[index].point);
|
||||
// Encode the flattened position only for reconstructing scan lines.
|
||||
flatInput.back().nPointIdx = static_cast<int>(index);
|
||||
}
|
||||
if (!Filter(flatInput, filtered, params, nullptr)) {
|
||||
output.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<bool> retained(points.size(), false);
|
||||
for (const SVzNL3DPosition& point : filtered) {
|
||||
if (point.nPointIdx >= 0
|
||||
&& static_cast<std::size_t>(point.nPointIdx) < retained.size()) {
|
||||
retained[static_cast<std::size_t>(point.nPointIdx)] = true;
|
||||
}
|
||||
}
|
||||
std::size_t pointIndex = 0;
|
||||
for (std::size_t lineIndex = 0; lineIndex < input.size(); ++lineIndex) {
|
||||
for (const SVzNL3DPosition& point : input[lineIndex]) {
|
||||
if (IsFinite(point)) {
|
||||
if (pointIndex < retained.size() && retained[pointIndex]) {
|
||||
output[lineIndex].push_back(point);
|
||||
}
|
||||
++pointIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::size_t outputCount = 0;
|
||||
for (const auto& line : output) {
|
||||
outputCount += line.size();
|
||||
}
|
||||
FinishStats(stats, outputCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PointCloudFilter::Filter(const std::vector<SVzNL3DPosition>& input,
|
||||
std::vector<SVzNL3DPosition>& output,
|
||||
const PointCloudFilterParams& params,
|
||||
PointCloudFilterStats* stats)
|
||||
{
|
||||
ResetStats(stats, input.size());
|
||||
output.clear();
|
||||
if (params.method == PointCloudFilterMethod::None) {
|
||||
output = input;
|
||||
FinishStats(stats, output.size());
|
||||
return true;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::ScanLineContinuity) {
|
||||
FilterScanLine(input, output, params);
|
||||
FinishStats(stats, output.size());
|
||||
return true;
|
||||
}
|
||||
if (params.method != PointCloudFilterMethod::RadiusOutlier
|
||||
&& params.method != PointCloudFilterMethod::StatisticalOutlier) {
|
||||
return false;
|
||||
}
|
||||
if (params.method == PointCloudFilterMethod::RadiusOutlier && params.radius <= 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<FlatPoint> points;
|
||||
points.reserve(input.size());
|
||||
for (const SVzNL3DPosition& point : input) {
|
||||
if (IsFinite(point)) {
|
||||
points.push_back({point});
|
||||
}
|
||||
}
|
||||
if (points.empty()) {
|
||||
FinishStats(stats, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
const double requestedRadius = params.method == PointCloudFilterMethod::RadiusOutlier
|
||||
? params.radius : params.searchRadius;
|
||||
const double radius = requestedRadius > 0.0 ? requestedRadius : EstimateCellSize(points) * 2.0;
|
||||
const double cellSize = std::max(radius, 1e-6);
|
||||
std::vector<bool> keep(points.size(), false);
|
||||
|
||||
if (params.method == PointCloudFilterMethod::RadiusOutlier) {
|
||||
const std::size_t minNeighbors = params.minNeighbors;
|
||||
FilterWithGrid(points, cellSize, radius,
|
||||
[&points, minNeighbors](std::size_t index, const std::vector<std::size_t>& candidates,
|
||||
double radiusSquared) {
|
||||
std::size_t neighbors = 0;
|
||||
for (std::size_t candidate : candidates) {
|
||||
if (candidate != index && SquaredDistance(points[index].point, points[candidate].point) <= radiusSquared) {
|
||||
++neighbors;
|
||||
}
|
||||
}
|
||||
return neighbors >= minNeighbors;
|
||||
}, keep);
|
||||
}
|
||||
|
||||
if (params.method == PointCloudFilterMethod::StatisticalOutlier) {
|
||||
std::vector<double> meanDistances(points.size(), std::numeric_limits<double>::infinity());
|
||||
FilterWithGrid(points, cellSize, radius,
|
||||
[&points, &meanDistances, ¶ms](std::size_t index,
|
||||
const std::vector<std::size_t>& candidates,
|
||||
double radiusSquared) {
|
||||
std::vector<double> distances;
|
||||
distances.reserve(candidates.size());
|
||||
for (std::size_t candidate : candidates) {
|
||||
if (candidate != index) {
|
||||
const double distance = SquaredDistance(points[index].point, points[candidate].point);
|
||||
if (distance <= radiusSquared) {
|
||||
distances.push_back(std::sqrt(distance));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (distances.empty()) {
|
||||
return false;
|
||||
}
|
||||
const std::size_t count = std::min(params.meanK == 0 ? std::size_t(1) : params.meanK,
|
||||
distances.size());
|
||||
if (count < distances.size()) {
|
||||
std::nth_element(distances.begin(),
|
||||
distances.begin() + static_cast<std::ptrdiff_t>(count),
|
||||
distances.end());
|
||||
}
|
||||
meanDistances[index] = std::accumulate(distances.begin(), distances.begin() + static_cast<std::ptrdiff_t>(count), 0.0)
|
||||
/ static_cast<double>(count);
|
||||
return true;
|
||||
}, keep);
|
||||
std::vector<double> validMeans;
|
||||
for (double value : meanDistances) {
|
||||
if (std::isfinite(value)) {
|
||||
validMeans.push_back(value);
|
||||
}
|
||||
}
|
||||
if (!validMeans.empty()) {
|
||||
const double mean = std::accumulate(validMeans.begin(), validMeans.end(), 0.0)
|
||||
/ static_cast<double>(validMeans.size());
|
||||
double variance = 0.0;
|
||||
for (double value : validMeans) {
|
||||
const double delta = value - mean;
|
||||
variance += delta * delta;
|
||||
}
|
||||
const double deviation = std::sqrt(variance / static_cast<double>(validMeans.size()));
|
||||
const double threshold = mean + std::max(0.0, params.standardDeviationMultiplier) * deviation;
|
||||
for (std::size_t index = 0; index < keep.size(); ++index) {
|
||||
keep[index] = std::isfinite(meanDistances[index]) && meanDistances[index] <= threshold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output.reserve(points.size());
|
||||
for (std::size_t index = 0; index < points.size(); ++index) {
|
||||
if (keep[index]) {
|
||||
output.push_back(points[index].point);
|
||||
}
|
||||
}
|
||||
FinishStats(stats, output.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
43
CloudView3D/CMakeLists.txt
Normal file
43
CloudView3D/CMakeLists.txt
Normal file
@ -0,0 +1,43 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Gui Widgets OpenGL)
|
||||
|
||||
if(NOT TARGET VrCommon)
|
||||
add_library(VrCommon INTERFACE)
|
||||
target_include_directories(VrCommon INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../VrCommon/Inc
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(CloudView3D STATIC
|
||||
Src/PointCloudGLWidget.cpp
|
||||
Inc/CloudView3D.h
|
||||
Inc/CloudView3DTypes.h
|
||||
Inc/PointCloudGLWidget.h
|
||||
)
|
||||
|
||||
set_target_properties(CloudView3D PROPERTIES
|
||||
CXX_STANDARD 17
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
AUTOMOC ON
|
||||
)
|
||||
|
||||
target_include_directories(CloudView3D PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Inc
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../VrCommon/Inc
|
||||
)
|
||||
|
||||
target_link_libraries(CloudView3D PUBLIC
|
||||
VrCommon
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
Qt5::Widgets
|
||||
Qt5::OpenGL
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_compile_options(CloudView3D PRIVATE /utf-8)
|
||||
target_link_libraries(CloudView3D PUBLIC opengl32 glu32)
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
target_link_libraries(CloudView3D PUBLIC GL GLU)
|
||||
endif()
|
||||
@ -1,30 +1,30 @@
|
||||
QT += core gui widgets opengl
|
||||
|
||||
CONFIG += c++17 staticlib
|
||||
TEMPLATE = lib
|
||||
TARGET = CloudView3D
|
||||
|
||||
win32-msvc {
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
QMAKE_CXXFLAGS += /bigobj
|
||||
}
|
||||
|
||||
INCLUDEPATH += $$PWD/Inc
|
||||
INCLUDEPATH += $$PWD/../VrUtils/Inc
|
||||
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/Inc
|
||||
INCLUDEPATH += $$PWD/../VrCommon/Inc
|
||||
|
||||
HEADERS += \
|
||||
Inc/CloudView3D.h \
|
||||
Inc/CloudView3DTypes.h \
|
||||
Inc/PointCloudGLWidget.h
|
||||
|
||||
SOURCES += \
|
||||
Src/PointCloudGLWidget.cpp
|
||||
|
||||
win32 {
|
||||
LIBS += -lopengl32
|
||||
LIBS += -lglu32
|
||||
}
|
||||
|
||||
DEFINES += _USE_MATH_DEFINES
|
||||
QT += core gui widgets opengl
|
||||
|
||||
CONFIG += c++17 staticlib
|
||||
TEMPLATE = lib
|
||||
TARGET = CloudView3D
|
||||
|
||||
win32-msvc {
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
QMAKE_CXXFLAGS += /bigobj
|
||||
}
|
||||
|
||||
INCLUDEPATH += $$PWD/Inc
|
||||
INCLUDEPATH += $$PWD/../VrUtils/Inc
|
||||
INCLUDEPATH += $$PWD/../VrCommon/Inc
|
||||
# VZNL_Types.h is maintained in the shared Utils module.
|
||||
|
||||
HEADERS += \
|
||||
Inc/CloudView3D.h \
|
||||
Inc/CloudView3DTypes.h \
|
||||
Inc/PointCloudGLWidget.h
|
||||
|
||||
SOURCES += \
|
||||
Src/PointCloudGLWidget.cpp
|
||||
|
||||
win32 {
|
||||
LIBS += -lopengl32
|
||||
LIBS += -lglu32
|
||||
}
|
||||
|
||||
DEFINES += _USE_MATH_DEFINES
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#ifndef CLOUD_VIEW_3D_H
|
||||
#define CLOUD_VIEW_3D_H
|
||||
|
||||
#include "CloudView3DTypes.h"
|
||||
#include "PointCloudGLWidget.h"
|
||||
|
||||
#endif // CLOUD_VIEW_3D_H
|
||||
#ifndef CLOUD_VIEW_3D_H
|
||||
#define CLOUD_VIEW_3D_H
|
||||
|
||||
#include "CloudView3DTypes.h"
|
||||
#include "PointCloudGLWidget.h"
|
||||
|
||||
#endif // CLOUD_VIEW_3D_H
|
||||
|
||||
@ -1,77 +1,77 @@
|
||||
#ifndef CLOUD_VIEW_3D_TYPES_H
|
||||
#define CLOUD_VIEW_3D_TYPES_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
struct Point3D
|
||||
{
|
||||
float x, y, z;
|
||||
Point3D() : x(0), y(0), z(0) {}
|
||||
Point3D(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
|
||||
};
|
||||
|
||||
struct Point3DRGB
|
||||
{
|
||||
float x, y, z;
|
||||
uint8_t r, g, b;
|
||||
float pointSize;
|
||||
|
||||
Point3DRGB() : x(0), y(0), z(0), r(255), g(255), b(255), pointSize(0) {}
|
||||
Point3DRGB(float _x,
|
||||
float _y,
|
||||
float _z,
|
||||
uint8_t _r = 255,
|
||||
uint8_t _g = 255,
|
||||
uint8_t _b = 255,
|
||||
float _ps = 0)
|
||||
: x(_x), y(_y), z(_z), r(_r), g(_g), b(_b), pointSize(_ps) {}
|
||||
};
|
||||
|
||||
template<typename PointT>
|
||||
class SimplePointCloud
|
||||
{
|
||||
public:
|
||||
std::vector<PointT> points;
|
||||
std::vector<int> lineIndices;
|
||||
// Each point's original position within its scan line (-1 when unknown).
|
||||
std::vector<int> pointInLineIndices;
|
||||
|
||||
void clear()
|
||||
{
|
||||
points.clear();
|
||||
lineIndices.clear();
|
||||
pointInLineIndices.clear();
|
||||
}
|
||||
size_t size() const { return points.size(); }
|
||||
bool empty() const { return points.empty(); }
|
||||
void reserve(size_t n)
|
||||
{
|
||||
points.reserve(n);
|
||||
lineIndices.reserve(n);
|
||||
pointInLineIndices.reserve(n);
|
||||
}
|
||||
void push_back(const PointT& pt)
|
||||
{
|
||||
points.push_back(pt);
|
||||
pointInLineIndices.push_back(-1);
|
||||
}
|
||||
void push_back(const PointT& pt, int lineIdx)
|
||||
{
|
||||
points.push_back(pt);
|
||||
lineIndices.push_back(lineIdx);
|
||||
pointInLineIndices.push_back(-1);
|
||||
}
|
||||
void push_back(const PointT& pt, int lineIdx, int pointInLineIdx)
|
||||
{
|
||||
points.push_back(pt);
|
||||
lineIndices.push_back(lineIdx);
|
||||
pointInLineIndices.push_back(pointInLineIdx);
|
||||
}
|
||||
};
|
||||
|
||||
using PointCloudXYZ = SimplePointCloud<Point3D>;
|
||||
using PointCloudXYZRGB = SimplePointCloud<Point3DRGB>;
|
||||
|
||||
#endif // CLOUD_VIEW_3D_TYPES_H
|
||||
#ifndef CLOUD_VIEW_3D_TYPES_H
|
||||
#define CLOUD_VIEW_3D_TYPES_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
struct Point3D
|
||||
{
|
||||
float x, y, z; // millimetres
|
||||
Point3D() : x(0), y(0), z(0) {}
|
||||
Point3D(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
|
||||
};
|
||||
|
||||
struct Point3DRGB
|
||||
{
|
||||
float x, y, z; // millimetres
|
||||
uint8_t r, g, b;
|
||||
float pointSize;
|
||||
|
||||
Point3DRGB() : x(0), y(0), z(0), r(255), g(255), b(255), pointSize(0) {}
|
||||
Point3DRGB(float _x,
|
||||
float _y,
|
||||
float _z,
|
||||
uint8_t _r = 255,
|
||||
uint8_t _g = 255,
|
||||
uint8_t _b = 255,
|
||||
float _ps = 0)
|
||||
: x(_x), y(_y), z(_z), r(_r), g(_g), b(_b), pointSize(_ps) {}
|
||||
};
|
||||
|
||||
template<typename PointT>
|
||||
class SimplePointCloud
|
||||
{
|
||||
public:
|
||||
std::vector<PointT> points;
|
||||
std::vector<int> lineIndices;
|
||||
// Each point's original position within its scan line (-1 when unknown).
|
||||
std::vector<int> pointInLineIndices;
|
||||
|
||||
void clear()
|
||||
{
|
||||
points.clear();
|
||||
lineIndices.clear();
|
||||
pointInLineIndices.clear();
|
||||
}
|
||||
size_t size() const { return points.size(); }
|
||||
bool empty() const { return points.empty(); }
|
||||
void reserve(size_t n)
|
||||
{
|
||||
points.reserve(n);
|
||||
lineIndices.reserve(n);
|
||||
pointInLineIndices.reserve(n);
|
||||
}
|
||||
void push_back(const PointT& pt)
|
||||
{
|
||||
points.push_back(pt);
|
||||
pointInLineIndices.push_back(-1);
|
||||
}
|
||||
void push_back(const PointT& pt, int lineIdx)
|
||||
{
|
||||
points.push_back(pt);
|
||||
lineIndices.push_back(lineIdx);
|
||||
pointInLineIndices.push_back(-1);
|
||||
}
|
||||
void push_back(const PointT& pt, int lineIdx, int pointInLineIdx)
|
||||
{
|
||||
points.push_back(pt);
|
||||
lineIndices.push_back(lineIdx);
|
||||
pointInLineIndices.push_back(pointInLineIdx);
|
||||
}
|
||||
};
|
||||
|
||||
using PointCloudXYZ = SimplePointCloud<Point3D>;
|
||||
using PointCloudXYZRGB = SimplePointCloud<Point3DRGB>;
|
||||
|
||||
#endif // CLOUD_VIEW_3D_TYPES_H
|
||||
|
||||
@ -6,9 +6,10 @@
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QMatrix4x4>
|
||||
#include <QVector3D>
|
||||
#include <QQuaternion>
|
||||
#include <QMouseEvent>
|
||||
#include <QVector3D>
|
||||
#include <QQuaternion>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <deque>
|
||||
@ -18,9 +19,10 @@
|
||||
#include "VZNL_Types.h"
|
||||
|
||||
class QToolButton;
|
||||
class QPushButton;
|
||||
class QPinchGesture;
|
||||
class QResizeEvent;
|
||||
class QPushButton;
|
||||
class QPinchGesture;
|
||||
class QResizeEvent;
|
||||
class QTimer;
|
||||
|
||||
/**
|
||||
* @brief 点云显示颜色枚举
|
||||
@ -185,10 +187,30 @@ public:
|
||||
void addPointCloud(const PointCloudXYZ& cloud, const QString& name = "", bool resetView = true);
|
||||
void addPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "", bool resetView = true);
|
||||
/** 删除最早添加的一组点云,用于维护滚动显示窗口。 */
|
||||
bool removeOldestPointCloud();
|
||||
bool removeOldestPointCloud(bool refreshDisplay = true);
|
||||
/** 删除最近添加的一组点云,用于回滚未完成的增量加载。 */
|
||||
bool removeNewestPointCloud();
|
||||
void clearPointClouds();
|
||||
bool removeNewestPointCloud(bool refreshDisplay = true);
|
||||
void clearPointClouds();
|
||||
|
||||
/** 配置流式点云刷新频率;CamTool 使用 30 Hz。 */
|
||||
void setStreamRefreshRate(int refreshRateHz);
|
||||
int streamRefreshRate() const
|
||||
{
|
||||
return m_streamRefreshRate;
|
||||
}
|
||||
/** 配置流式点云最多保留的源扫描线数。 */
|
||||
void setStreamLineLimit(int maxLines);
|
||||
int streamLineLimit() const
|
||||
{
|
||||
return m_streamLineLimit;
|
||||
}
|
||||
void startStreamRefresh();
|
||||
void stopStreamRefresh(bool flushPending = true);
|
||||
/** 缓存一批本地线号从 0 开始的扫描线,下一次刷新时统一提交。 */
|
||||
void queuePointCloudBatch(PointCloudXYZ cloud, int sourceLineCount, const QString& name = QString());
|
||||
void flushPointCloudBatches();
|
||||
void clearPointCloudBatches();
|
||||
|
||||
void setPointCloudColor(PointCloudColor color);
|
||||
void setPointSize(float size);
|
||||
|
||||
@ -391,8 +413,10 @@ signals:
|
||||
float dx,
|
||||
float dy,
|
||||
float dz);
|
||||
void lineSelected(const SelectedLineInfo& line);
|
||||
void viewAnglesChanged(float rotX, float rotY, float rotZ);
|
||||
void lineSelected(const SelectedLineInfo& line);
|
||||
void viewAnglesChanged(float rotX, float rotY, float rotZ);
|
||||
/** 30 Hz 刷新边界;数据源应在该信号处理中批量填充缓存。 */
|
||||
void streamRefreshRequested();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
@ -407,8 +431,15 @@ protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
struct PointCloudData
|
||||
private:
|
||||
struct QueuedPointCloudBatch
|
||||
{
|
||||
PointCloudXYZ cloud;
|
||||
QString name;
|
||||
int sourceLineCount;
|
||||
};
|
||||
|
||||
struct PointCloudData
|
||||
{
|
||||
std::vector<float> vertices;
|
||||
std::vector<float> colors;
|
||||
@ -419,9 +450,11 @@ private:
|
||||
bool pseudoColor;
|
||||
bool hasLineInfo; // 是否有线信息
|
||||
QString name;
|
||||
int colorIndex; // 点云颜色索引
|
||||
int totalLines; // 总线数
|
||||
int pointsPerLine; // 每线点数(网格化点云)
|
||||
int colorIndex; // 点云颜色索引
|
||||
int totalLines; // 总线数
|
||||
int pointsPerLine; // 每线点数(网格化点云)
|
||||
bool streamBatch;
|
||||
int sourceLineCount;
|
||||
|
||||
// 自定义点大小分组(RGBA 中 A > 1 的点按大小分组)
|
||||
struct PointSizeGroup {
|
||||
@ -435,14 +468,19 @@ private:
|
||||
QOpenGLBuffer vertexBuffer; // 顶点 VBO
|
||||
QOpenGLBuffer colorBuffer; // 颜色 VBO
|
||||
bool vboCreated; // VBO 是否已创建
|
||||
QVector3D minBound;
|
||||
QVector3D maxBound;
|
||||
bool hasBounds;
|
||||
|
||||
PointCloudData()
|
||||
: hasColor(false), pseudoColor(false), hasLineInfo(false), colorIndex(0)
|
||||
, totalLines(0), pointsPerLine(0)
|
||||
, hasCustomPointSizes(false)
|
||||
: hasColor(false), pseudoColor(false), hasLineInfo(false), colorIndex(0)
|
||||
, totalLines(0), pointsPerLine(0)
|
||||
, streamBatch(false), sourceLineCount(0)
|
||||
, hasCustomPointSizes(false)
|
||||
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
|
||||
, colorBuffer(QOpenGLBuffer::VertexBuffer)
|
||||
, vboCreated(false)
|
||||
, hasBounds(false)
|
||||
{}
|
||||
|
||||
// 移动构造函数(QOpenGLBuffer 不支持拷贝)
|
||||
@ -456,14 +494,19 @@ private:
|
||||
, pseudoColor(other.pseudoColor)
|
||||
, hasLineInfo(other.hasLineInfo)
|
||||
, name(std::move(other.name))
|
||||
, colorIndex(other.colorIndex)
|
||||
, totalLines(other.totalLines)
|
||||
, pointsPerLine(other.pointsPerLine)
|
||||
, customPointSizeGroups(std::move(other.customPointSizeGroups))
|
||||
, colorIndex(other.colorIndex)
|
||||
, totalLines(other.totalLines)
|
||||
, pointsPerLine(other.pointsPerLine)
|
||||
, streamBatch(other.streamBatch)
|
||||
, sourceLineCount(other.sourceLineCount)
|
||||
, customPointSizeGroups(std::move(other.customPointSizeGroups))
|
||||
, hasCustomPointSizes(other.hasCustomPointSizes)
|
||||
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
|
||||
, colorBuffer(QOpenGLBuffer::VertexBuffer)
|
||||
, vboCreated(false)
|
||||
, minBound(other.minBound)
|
||||
, maxBound(other.maxBound)
|
||||
, hasBounds(other.hasBounds)
|
||||
{
|
||||
// VBO 需要在 GL 上下文中重建,标记为未创建
|
||||
other.vboCreated = false;
|
||||
@ -487,11 +530,16 @@ private:
|
||||
pseudoColor = other.pseudoColor;
|
||||
hasLineInfo = other.hasLineInfo;
|
||||
name = std::move(other.name);
|
||||
colorIndex = other.colorIndex;
|
||||
totalLines = other.totalLines;
|
||||
pointsPerLine = other.pointsPerLine;
|
||||
customPointSizeGroups = std::move(other.customPointSizeGroups);
|
||||
colorIndex = other.colorIndex;
|
||||
totalLines = other.totalLines;
|
||||
pointsPerLine = other.pointsPerLine;
|
||||
streamBatch = other.streamBatch;
|
||||
sourceLineCount = other.sourceLineCount;
|
||||
customPointSizeGroups = std::move(other.customPointSizeGroups);
|
||||
hasCustomPointSizes = other.hasCustomPointSizes;
|
||||
minBound = other.minBound;
|
||||
maxBound = other.maxBound;
|
||||
hasBounds = other.hasBounds;
|
||||
// VBO 需要重建
|
||||
other.vboCreated = false;
|
||||
}
|
||||
@ -507,6 +555,8 @@ private:
|
||||
void updateProjection(const QMatrix4x4& modelView);
|
||||
float fittedCameraDistance(float padding = 1.05f) const;
|
||||
float minimumCameraDistance() const;
|
||||
/** 按当前缩放把点大小从逻辑像素换到实际绘制像素,避免放大后点散成看不见的碎点。 */
|
||||
float effectivePointSize() const;
|
||||
QVector3D colorForIndex(int colorIndex) const;
|
||||
SelectedPointInfo pickPoint(int screenX, int screenY);
|
||||
void drawSelectedPoints();
|
||||
@ -519,15 +569,26 @@ private:
|
||||
void drawBasicShapeSegments(); // 绘制基础图形
|
||||
void drawBasicShapePoints();
|
||||
void drawLineSegmentList(const QVector<LineSegment>& segments);
|
||||
|
||||
void drawPosePoints(); // 绘制姿态点
|
||||
void drawAxisLabels(); // 绘制坐标轴 XYZ 标注
|
||||
void drawSelectionInfoOverlay(); // 右上角显示选点和测量结果
|
||||
QVector3D pseudoColorForZ(float z) const;
|
||||
void rebuildPseudoColors(PointCloudData& data, size_t firstVertex);
|
||||
void updatePseudoColors();
|
||||
void createControlButtons();
|
||||
void updateControlButtonGeometry();
|
||||
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
|
||||
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
|
||||
|
||||
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
|
||||
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
|
||||
void expandCloudBounds(PointCloudData& data, float x, float y, float z);
|
||||
void addPointCloudInternal(const PointCloudXYZ& cloud, const QString& name, bool resetViewAfterAdd, bool refreshDisplay);
|
||||
void trimQueuedPointCloudBatches();
|
||||
void trimDisplayedStreamBatches(bool refreshDisplay);
|
||||
bool removeOldestStreamPointCloud(bool refreshDisplay);
|
||||
void discardLeadingLines(QueuedPointCloudBatch& batch, int lineCount);
|
||||
void discardLeadingStreamLines(PointCloudData& data, int lineCount);
|
||||
void recomputeCloudBounds(PointCloudData& data);
|
||||
|
||||
std::deque<PointCloudData> m_pointClouds;
|
||||
|
||||
@ -623,8 +684,16 @@ private:
|
||||
QVector<QToolButton*> m_viewButtons;
|
||||
QPushButton* m_viewAngleButton; // 左下角旋转角度显示按钮
|
||||
|
||||
QToolButton* m_zoomInButton;
|
||||
QToolButton* m_zoomOutButton;
|
||||
};
|
||||
QToolButton* m_zoomInButton;
|
||||
QToolButton* m_zoomOutButton;
|
||||
|
||||
// 高速输入先进入 CPU 有界缓存,再由 GUI 线程按固定频率批量提交 OpenGL。
|
||||
QTimer* m_streamRefreshTimer;
|
||||
std::deque<QueuedPointCloudBatch> m_queuedPointCloudBatches;
|
||||
int m_queuedPointCloudLineCount;
|
||||
int m_streamDisplayedLineCount;
|
||||
int m_streamRefreshRate;
|
||||
int m_streamLineLimit;
|
||||
};
|
||||
|
||||
#endif // POINT_CLOUD_GL_WIDGET_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -83,9 +83,10 @@ private:
|
||||
QString fileName;
|
||||
};
|
||||
|
||||
void buildStartupSequence(const StartupFiles& files);
|
||||
bool loadStartupItem(const StartupItem& item);
|
||||
bool loadCurrentStartupSequenceItem();
|
||||
void buildStartupSequence(const StartupFiles& files);
|
||||
bool loadStartupItem(const StartupItem& item);
|
||||
bool loadCurrentStartupSequenceItem();
|
||||
void updateZoomButtonGeometry();
|
||||
|
||||
private slots:
|
||||
/**
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
#ifndef CLOUD_VIEW_SETTINGS_H
|
||||
#define CLOUD_VIEW_SETTINGS_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QVector3D>
|
||||
|
||||
#include "PointCloudXYZRange.h"
|
||||
|
||||
struct CloudViewSettingsData
|
||||
{
|
||||
float poseAxisLength = 10.0f;
|
||||
bool pseudoColorEnabled = true;
|
||||
float pseudoColorPeriod = 500.0f;
|
||||
QVector3D pseudoColorStartColor = QVector3D(0.0f, 0.0f, 1.0f);
|
||||
QVector3D pseudoColorEndColor = QVector3D(1.0f, 0.0f, 0.0f);
|
||||
bool pseudoColorDefaultPalette = true;
|
||||
PointCloudXYZRange pointCloudRange;
|
||||
};
|
||||
|
||||
class CloudViewSettings
|
||||
{
|
||||
public:
|
||||
static CloudViewSettingsData load();
|
||||
static bool save(const CloudViewSettingsData& settings);
|
||||
};
|
||||
|
||||
#endif // CLOUD_VIEW_SETTINGS_H
|
||||
#ifndef CLOUD_VIEW_SETTINGS_H
|
||||
#define CLOUD_VIEW_SETTINGS_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QVector3D>
|
||||
|
||||
#include "PointCloudXYZRange.h"
|
||||
|
||||
struct CloudViewSettingsData
|
||||
{
|
||||
float poseAxisLength = 10.0f;
|
||||
bool pseudoColorEnabled = true;
|
||||
float pseudoColorPeriod = 500.0f;
|
||||
QVector3D pseudoColorStartColor = QVector3D(0.0f, 0.0f, 1.0f);
|
||||
QVector3D pseudoColorEndColor = QVector3D(1.0f, 0.0f, 0.0f);
|
||||
bool pseudoColorDefaultPalette = true;
|
||||
PointCloudXYZRange pointCloudRange;
|
||||
};
|
||||
|
||||
class CloudViewSettings
|
||||
{
|
||||
public:
|
||||
static CloudViewSettingsData load();
|
||||
static bool save(const CloudViewSettingsData& settings);
|
||||
};
|
||||
|
||||
#endif // CLOUD_VIEW_SETTINGS_H
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#ifndef CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
|
||||
#define CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
|
||||
|
||||
#include "../../CloudView3D/Inc/PointCloudGLWidget.h"
|
||||
|
||||
#endif // CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
|
||||
#ifndef CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
|
||||
#define CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
|
||||
|
||||
#include "../../CloudView3D/Inc/PointCloudGLWidget.h"
|
||||
|
||||
#endif // CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
|
||||
|
||||
@ -1245,24 +1245,40 @@ void CloudViewMainWindow::setPointCloudRange(const PointCloudXYZRange& range)
|
||||
}
|
||||
}
|
||||
|
||||
bool CloudViewMainWindow::eventFilter(QObject* obj, QEvent* event)
|
||||
{
|
||||
if (obj == m_glWidget && event->type() == QEvent::Resize &&
|
||||
m_btnZoomIn && m_btnZoomOut) {
|
||||
const auto* resizeEvent = static_cast<QResizeEvent*>(event);
|
||||
const int width = resizeEvent->size().width();
|
||||
const int height = resizeEvent->size().height();
|
||||
const int buttonWidth = m_btnZoomIn->width();
|
||||
const int buttonHeight = m_btnZoomIn->height();
|
||||
const int margin = 8;
|
||||
const int gap = 4;
|
||||
const int x = width - buttonWidth - margin;
|
||||
const int y = qMax(0, (height - buttonHeight * 2 - gap) / 2);
|
||||
m_btnZoomIn->move(x, y);
|
||||
m_btnZoomOut->move(x, y + buttonHeight + gap);
|
||||
}
|
||||
return QMainWindow::eventFilter(obj, event);
|
||||
}
|
||||
bool CloudViewMainWindow::eventFilter(QObject* obj, QEvent* event)
|
||||
{
|
||||
if (obj == m_glWidget && event->type() == QEvent::Resize &&
|
||||
m_btnZoomIn && m_btnZoomOut) {
|
||||
updateZoomButtonGeometry();
|
||||
}
|
||||
return QMainWindow::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void CloudViewMainWindow::updateZoomButtonGeometry()
|
||||
{
|
||||
if (!m_glWidget || !m_btnZoomIn || !m_btnZoomOut)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int widgetWidth = m_glWidget->width();
|
||||
const int widgetHeight = m_glWidget->height();
|
||||
if (widgetWidth <= 0 || widgetHeight <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int buttonWidth = m_btnZoomIn->width();
|
||||
const int buttonHeight = m_btnZoomIn->height();
|
||||
const int margin = 8;
|
||||
const int gap = 4;
|
||||
const int x = qMax(0, widgetWidth - buttonWidth - margin);
|
||||
const int y = qMax(0, (widgetHeight - buttonHeight * 2 - gap) / 2);
|
||||
m_btnZoomIn->move(x, y);
|
||||
m_btnZoomOut->move(x, y + buttonHeight + gap);
|
||||
m_btnZoomIn->raise();
|
||||
m_btnZoomOut->raise();
|
||||
}
|
||||
|
||||
void CloudViewMainWindow::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
@ -1417,11 +1433,13 @@ QWidget* CloudViewMainWindow::createViewerArea()
|
||||
m_btnZoomOut->setAutoRepeat(true);
|
||||
m_btnZoomOut->setAutoRepeatDelay(300);
|
||||
m_btnZoomOut->setAutoRepeatInterval(50);
|
||||
connect(m_btnZoomOut, &QToolButton::clicked, m_glWidget, &PointCloudGLWidget::zoomOut);
|
||||
|
||||
m_glWidget->installEventFilter(this);
|
||||
|
||||
// 连接信号
|
||||
connect(m_btnZoomOut, &QToolButton::clicked, m_glWidget, &PointCloudGLWidget::zoomOut);
|
||||
|
||||
m_glWidget->installEventFilter(this);
|
||||
// 初始创建时可能没有收到 resize 事件,立即将按钮放到视口右侧居中。
|
||||
updateZoomButtonGeometry();
|
||||
|
||||
// 连接信号
|
||||
connect(m_glWidget, &PointCloudGLWidget::pointSelected, this, &CloudViewMainWindow::onPointSelected);
|
||||
connect(m_glWidget, &PointCloudGLWidget::twoPointsSelected, this, &CloudViewMainWindow::onTwoPointsSelected);
|
||||
connect(m_glWidget, &PointCloudGLWidget::lineSelected, this, &CloudViewMainWindow::onLineSelected);
|
||||
|
||||
@ -1,171 +1,171 @@
|
||||
#include "CloudViewSettings.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
QString settingsFilePath()
|
||||
{
|
||||
QString settingsDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
if (settingsDir.isEmpty()) {
|
||||
settingsDir = QDir::homePath() + "/CloudView";
|
||||
}
|
||||
QDir().mkpath(settingsDir);
|
||||
return QDir(settingsDir).filePath("CloudView.ini");
|
||||
}
|
||||
}
|
||||
|
||||
CloudViewSettingsData CloudViewSettings::load()
|
||||
{
|
||||
CloudViewSettingsData result;
|
||||
QSettings settings(settingsFilePath(), QSettings::IniFormat);
|
||||
|
||||
// 模式独立于启用状态保存;缺失或非法配置默认采用“替换为全 0”。
|
||||
const QVariant savedModeValue = settings.contains("PointCloudRange/filterMode")
|
||||
? settings.value("PointCloudRange/filterMode")
|
||||
: settings.value("PointCloudRange/mode",
|
||||
static_cast<int>(PointCloudXYZRangeFilterMode::ReplaceWithZero));
|
||||
const int savedFilterMode = savedModeValue.toInt();
|
||||
const PointCloudXYZRangeFilterMode filterMode =
|
||||
savedFilterMode == static_cast<int>(PointCloudXYZRangeFilterMode::Remove)
|
||||
? PointCloudXYZRangeFilterMode::Remove
|
||||
: PointCloudXYZRangeFilterMode::ReplaceWithZero;
|
||||
|
||||
const float poseAxisLength = settings.value("Display/poseAxisLength", result.poseAxisLength).toFloat();
|
||||
if (std::isfinite(poseAxisLength) && poseAxisLength > 0.0f) {
|
||||
result.poseAxisLength = poseAxisLength;
|
||||
}
|
||||
|
||||
result.pseudoColorEnabled = settings.value("PseudoColor/enabled", result.pseudoColorEnabled).toBool();
|
||||
const bool hasPaletteSetting = settings.contains("PseudoColor/defaultPalette");
|
||||
result.pseudoColorDefaultPalette = hasPaletteSetting
|
||||
? settings.value("PseudoColor/defaultPalette").toBool()
|
||||
: result.pseudoColorDefaultPalette;
|
||||
const float period = settings.value("PseudoColor/period", result.pseudoColorPeriod).toFloat();
|
||||
if (std::isfinite(period) && period > 0.0f) {
|
||||
result.pseudoColorPeriod = period;
|
||||
}
|
||||
const QColor startColor(settings.value("PseudoColor/startColor", "#0000ff").toString());
|
||||
const QColor endColor(settings.value("PseudoColor/endColor", "#ff0000").toString());
|
||||
if (startColor.isValid()) {
|
||||
result.pseudoColorStartColor = QVector3D(startColor.redF(), startColor.greenF(), startColor.blueF());
|
||||
}
|
||||
if (endColor.isValid()) {
|
||||
result.pseudoColorEndColor = QVector3D(endColor.redF(), endColor.greenF(), endColor.blueF());
|
||||
}
|
||||
|
||||
if (!hasPaletteSetting && settings.contains("PseudoColor/period")) {
|
||||
const QColor legacyStartColor(settings.value("PseudoColor/startColor", "#0000ff").toString());
|
||||
const QColor legacyEndColor(settings.value("PseudoColor/endColor", "#ff0000").toString());
|
||||
const bool legacyDefaults = qFuzzyCompare(result.pseudoColorPeriod, 100.0f) &&
|
||||
legacyStartColor == QColor("#0000ff") &&
|
||||
legacyEndColor == QColor("#ff0000");
|
||||
if (legacyDefaults) {
|
||||
// 旧版周期色环配置迁移为 Z 范围蓝→红梯度。
|
||||
result.pseudoColorPeriod = 500.0f;
|
||||
result.pseudoColorStartColor = QVector3D(0.0f, 0.0f, 1.0f);
|
||||
result.pseudoColorEndColor = QVector3D(1.0f, 0.0f, 0.0f);
|
||||
result.pseudoColorDefaultPalette = true;
|
||||
} else {
|
||||
// 非默认旧配置继续使用原有的自定义起止色渐变。
|
||||
result.pseudoColorDefaultPalette = false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool rangeEnabled = settings.value("PointCloudRange/enabled", false).toBool();
|
||||
PointCloudXYZRange range;
|
||||
range.enabled = rangeEnabled;
|
||||
range.filterMode = filterMode;
|
||||
if (rangeEnabled) {
|
||||
const auto readBound = [&settings](const char* key, double fallback) {
|
||||
bool ok = false;
|
||||
const double value = settings.value(key).toDouble(&ok);
|
||||
return ok && std::isfinite(value) ? value : fallback;
|
||||
};
|
||||
// Missing bounds retain the range object's implicit unbounded side.
|
||||
range.minX = readBound("PointCloudRange/minX", range.minX);
|
||||
range.maxX = readBound("PointCloudRange/maxX", range.maxX);
|
||||
range.minY = readBound("PointCloudRange/minY", range.minY);
|
||||
range.maxY = readBound("PointCloudRange/maxY", range.maxY);
|
||||
range.minZ = readBound("PointCloudRange/minZ", range.minZ);
|
||||
range.maxZ = readBound("PointCloudRange/maxZ", range.maxZ);
|
||||
if (range.isValid()) {
|
||||
result.pointCloudRange = range;
|
||||
}
|
||||
} else {
|
||||
result.pointCloudRange = range;
|
||||
}
|
||||
// Keep the mode even when a persisted enabled range is invalid and is
|
||||
// therefore reset to the disabled default range.
|
||||
result.pointCloudRange.filterMode = filterMode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CloudViewSettings::save(const CloudViewSettingsData& value)
|
||||
{
|
||||
QSettings settings(settingsFilePath(), QSettings::IniFormat);
|
||||
if (std::isfinite(value.poseAxisLength) && value.poseAxisLength > 0.0f) {
|
||||
settings.setValue("Display/poseAxisLength", value.poseAxisLength);
|
||||
}
|
||||
|
||||
settings.setValue("PseudoColor/enabled", value.pseudoColorEnabled);
|
||||
settings.setValue("PseudoColor/defaultPalette", value.pseudoColorDefaultPalette);
|
||||
settings.setValue("PseudoColor/period", qMax(1.0f, value.pseudoColorPeriod));
|
||||
settings.setValue("PseudoColor/startColor",
|
||||
QColor::fromRgbF(value.pseudoColorStartColor.x(),
|
||||
value.pseudoColorStartColor.y(),
|
||||
value.pseudoColorStartColor.z()).name(QColor::HexRgb));
|
||||
settings.setValue("PseudoColor/endColor",
|
||||
QColor::fromRgbF(value.pseudoColorEndColor.x(),
|
||||
value.pseudoColorEndColor.y(),
|
||||
value.pseudoColorEndColor.z()).name(QColor::HexRgb));
|
||||
|
||||
const PointCloudXYZRange& range = value.pointCloudRange;
|
||||
const bool saveRange = range.enabled && range.isValid();
|
||||
settings.setValue("PointCloudRange/enabled", saveRange);
|
||||
const int filterModeValue = range.filterMode == PointCloudXYZRangeFilterMode::Remove
|
||||
? static_cast<int>(PointCloudXYZRangeFilterMode::Remove)
|
||||
: static_cast<int>(PointCloudXYZRangeFilterMode::ReplaceWithZero);
|
||||
settings.setValue("PointCloudRange/filterMode", filterModeValue);
|
||||
settings.setValue("PointCloudRange/mode", filterModeValue);
|
||||
if (saveRange) {
|
||||
const auto saveBound = [&settings](const char* key, double bound) {
|
||||
if (std::isfinite(bound)) {
|
||||
settings.setValue(key, bound);
|
||||
} else {
|
||||
settings.remove(key);
|
||||
}
|
||||
};
|
||||
saveBound("PointCloudRange/minX", range.minX);
|
||||
saveBound("PointCloudRange/maxX", range.maxX);
|
||||
saveBound("PointCloudRange/minY", range.minY);
|
||||
saveBound("PointCloudRange/maxY", range.maxY);
|
||||
saveBound("PointCloudRange/minZ", range.minZ);
|
||||
saveBound("PointCloudRange/maxZ", range.maxZ);
|
||||
} else {
|
||||
// Disabled ranges use implicit infinite bounds; do not persist infinities.
|
||||
settings.remove("PointCloudRange/minX");
|
||||
settings.remove("PointCloudRange/maxX");
|
||||
settings.remove("PointCloudRange/minY");
|
||||
settings.remove("PointCloudRange/maxY");
|
||||
settings.remove("PointCloudRange/minZ");
|
||||
settings.remove("PointCloudRange/maxZ");
|
||||
}
|
||||
|
||||
// Remove keys from the former min/max/stop based implementation so old
|
||||
// values cannot reappear in the user configuration file.
|
||||
settings.remove("PseudoColor/autoRange");
|
||||
settings.remove("PseudoColor/minZ");
|
||||
settings.remove("PseudoColor/maxZ");
|
||||
settings.remove("PseudoColor/divisionCount");
|
||||
settings.remove("PseudoColor/gradient");
|
||||
settings.remove("PseudoColor/stops");
|
||||
|
||||
settings.sync();
|
||||
return settings.status() == QSettings::NoError;
|
||||
}
|
||||
#include "CloudViewSettings.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QDir>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
QString settingsFilePath()
|
||||
{
|
||||
QString settingsDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
if (settingsDir.isEmpty()) {
|
||||
settingsDir = QDir::homePath() + "/CloudView";
|
||||
}
|
||||
QDir().mkpath(settingsDir);
|
||||
return QDir(settingsDir).filePath("CloudView.ini");
|
||||
}
|
||||
}
|
||||
|
||||
CloudViewSettingsData CloudViewSettings::load()
|
||||
{
|
||||
CloudViewSettingsData result;
|
||||
QSettings settings(settingsFilePath(), QSettings::IniFormat);
|
||||
|
||||
// 模式独立于启用状态保存;缺失或非法配置默认采用“替换为全 0”。
|
||||
const QVariant savedModeValue = settings.contains("PointCloudRange/filterMode")
|
||||
? settings.value("PointCloudRange/filterMode")
|
||||
: settings.value("PointCloudRange/mode",
|
||||
static_cast<int>(PointCloudXYZRangeFilterMode::ReplaceWithZero));
|
||||
const int savedFilterMode = savedModeValue.toInt();
|
||||
const PointCloudXYZRangeFilterMode filterMode =
|
||||
savedFilterMode == static_cast<int>(PointCloudXYZRangeFilterMode::Remove)
|
||||
? PointCloudXYZRangeFilterMode::Remove
|
||||
: PointCloudXYZRangeFilterMode::ReplaceWithZero;
|
||||
|
||||
const float poseAxisLength = settings.value("Display/poseAxisLength", result.poseAxisLength).toFloat();
|
||||
if (std::isfinite(poseAxisLength) && poseAxisLength > 0.0f) {
|
||||
result.poseAxisLength = poseAxisLength;
|
||||
}
|
||||
|
||||
result.pseudoColorEnabled = settings.value("PseudoColor/enabled", result.pseudoColorEnabled).toBool();
|
||||
const bool hasPaletteSetting = settings.contains("PseudoColor/defaultPalette");
|
||||
result.pseudoColorDefaultPalette = hasPaletteSetting
|
||||
? settings.value("PseudoColor/defaultPalette").toBool()
|
||||
: result.pseudoColorDefaultPalette;
|
||||
const float period = settings.value("PseudoColor/period", result.pseudoColorPeriod).toFloat();
|
||||
if (std::isfinite(period) && period > 0.0f) {
|
||||
result.pseudoColorPeriod = period;
|
||||
}
|
||||
const QColor startColor(settings.value("PseudoColor/startColor", "#0000ff").toString());
|
||||
const QColor endColor(settings.value("PseudoColor/endColor", "#ff0000").toString());
|
||||
if (startColor.isValid()) {
|
||||
result.pseudoColorStartColor = QVector3D(startColor.redF(), startColor.greenF(), startColor.blueF());
|
||||
}
|
||||
if (endColor.isValid()) {
|
||||
result.pseudoColorEndColor = QVector3D(endColor.redF(), endColor.greenF(), endColor.blueF());
|
||||
}
|
||||
|
||||
if (!hasPaletteSetting && settings.contains("PseudoColor/period")) {
|
||||
const QColor legacyStartColor(settings.value("PseudoColor/startColor", "#0000ff").toString());
|
||||
const QColor legacyEndColor(settings.value("PseudoColor/endColor", "#ff0000").toString());
|
||||
const bool legacyDefaults = qFuzzyCompare(result.pseudoColorPeriod, 100.0f) &&
|
||||
legacyStartColor == QColor("#0000ff") &&
|
||||
legacyEndColor == QColor("#ff0000");
|
||||
if (legacyDefaults) {
|
||||
// 旧版周期色环配置迁移为 Z 范围蓝→红梯度。
|
||||
result.pseudoColorPeriod = 500.0f;
|
||||
result.pseudoColorStartColor = QVector3D(0.0f, 0.0f, 1.0f);
|
||||
result.pseudoColorEndColor = QVector3D(1.0f, 0.0f, 0.0f);
|
||||
result.pseudoColorDefaultPalette = true;
|
||||
} else {
|
||||
// 非默认旧配置继续使用原有的自定义起止色渐变。
|
||||
result.pseudoColorDefaultPalette = false;
|
||||
}
|
||||
}
|
||||
|
||||
const bool rangeEnabled = settings.value("PointCloudRange/enabled", false).toBool();
|
||||
PointCloudXYZRange range;
|
||||
range.enabled = rangeEnabled;
|
||||
range.filterMode = filterMode;
|
||||
if (rangeEnabled) {
|
||||
const auto readBound = [&settings](const char* key, double fallback) {
|
||||
bool ok = false;
|
||||
const double value = settings.value(key).toDouble(&ok);
|
||||
return ok && std::isfinite(value) ? value : fallback;
|
||||
};
|
||||
// Missing bounds retain the range object's implicit unbounded side.
|
||||
range.minX = readBound("PointCloudRange/minX", range.minX);
|
||||
range.maxX = readBound("PointCloudRange/maxX", range.maxX);
|
||||
range.minY = readBound("PointCloudRange/minY", range.minY);
|
||||
range.maxY = readBound("PointCloudRange/maxY", range.maxY);
|
||||
range.minZ = readBound("PointCloudRange/minZ", range.minZ);
|
||||
range.maxZ = readBound("PointCloudRange/maxZ", range.maxZ);
|
||||
if (range.isValid()) {
|
||||
result.pointCloudRange = range;
|
||||
}
|
||||
} else {
|
||||
result.pointCloudRange = range;
|
||||
}
|
||||
// Keep the mode even when a persisted enabled range is invalid and is
|
||||
// therefore reset to the disabled default range.
|
||||
result.pointCloudRange.filterMode = filterMode;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CloudViewSettings::save(const CloudViewSettingsData& value)
|
||||
{
|
||||
QSettings settings(settingsFilePath(), QSettings::IniFormat);
|
||||
if (std::isfinite(value.poseAxisLength) && value.poseAxisLength > 0.0f) {
|
||||
settings.setValue("Display/poseAxisLength", value.poseAxisLength);
|
||||
}
|
||||
|
||||
settings.setValue("PseudoColor/enabled", value.pseudoColorEnabled);
|
||||
settings.setValue("PseudoColor/defaultPalette", value.pseudoColorDefaultPalette);
|
||||
settings.setValue("PseudoColor/period", qMax(1.0f, value.pseudoColorPeriod));
|
||||
settings.setValue("PseudoColor/startColor",
|
||||
QColor::fromRgbF(value.pseudoColorStartColor.x(),
|
||||
value.pseudoColorStartColor.y(),
|
||||
value.pseudoColorStartColor.z()).name(QColor::HexRgb));
|
||||
settings.setValue("PseudoColor/endColor",
|
||||
QColor::fromRgbF(value.pseudoColorEndColor.x(),
|
||||
value.pseudoColorEndColor.y(),
|
||||
value.pseudoColorEndColor.z()).name(QColor::HexRgb));
|
||||
|
||||
const PointCloudXYZRange& range = value.pointCloudRange;
|
||||
const bool saveRange = range.enabled && range.isValid();
|
||||
settings.setValue("PointCloudRange/enabled", saveRange);
|
||||
const int filterModeValue = range.filterMode == PointCloudXYZRangeFilterMode::Remove
|
||||
? static_cast<int>(PointCloudXYZRangeFilterMode::Remove)
|
||||
: static_cast<int>(PointCloudXYZRangeFilterMode::ReplaceWithZero);
|
||||
settings.setValue("PointCloudRange/filterMode", filterModeValue);
|
||||
settings.setValue("PointCloudRange/mode", filterModeValue);
|
||||
if (saveRange) {
|
||||
const auto saveBound = [&settings](const char* key, double bound) {
|
||||
if (std::isfinite(bound)) {
|
||||
settings.setValue(key, bound);
|
||||
} else {
|
||||
settings.remove(key);
|
||||
}
|
||||
};
|
||||
saveBound("PointCloudRange/minX", range.minX);
|
||||
saveBound("PointCloudRange/maxX", range.maxX);
|
||||
saveBound("PointCloudRange/minY", range.minY);
|
||||
saveBound("PointCloudRange/maxY", range.maxY);
|
||||
saveBound("PointCloudRange/minZ", range.minZ);
|
||||
saveBound("PointCloudRange/maxZ", range.maxZ);
|
||||
} else {
|
||||
// Disabled ranges use implicit infinite bounds; do not persist infinities.
|
||||
settings.remove("PointCloudRange/minX");
|
||||
settings.remove("PointCloudRange/maxX");
|
||||
settings.remove("PointCloudRange/minY");
|
||||
settings.remove("PointCloudRange/maxY");
|
||||
settings.remove("PointCloudRange/minZ");
|
||||
settings.remove("PointCloudRange/maxZ");
|
||||
}
|
||||
|
||||
// Remove keys from the former min/max/stop based implementation so old
|
||||
// values cannot reappear in the user configuration file.
|
||||
settings.remove("PseudoColor/autoRange");
|
||||
settings.remove("PseudoColor/minZ");
|
||||
settings.remove("PseudoColor/maxZ");
|
||||
settings.remove("PseudoColor/divisionCount");
|
||||
settings.remove("PseudoColor/gradient");
|
||||
settings.remove("PseudoColor/stops");
|
||||
|
||||
settings.sync();
|
||||
return settings.status() == QSettings::NoError;
|
||||
}
|
||||
|
||||
@ -1,102 +1,102 @@
|
||||
#include <QApplication>
|
||||
#include <QSurfaceFormat>
|
||||
#include <QIcon>
|
||||
#include <QCommandLineParser>
|
||||
#include <QTimer>
|
||||
#include "CloudViewMainWindow.h"
|
||||
|
||||
#define APP_VERSION "1.2.1"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 设置 OpenGL 格式 - 使用 2.1 版本以确保固定管线兼容
|
||||
QSurfaceFormat format;
|
||||
format.setDepthBufferSize(24);
|
||||
format.setStencilBufferSize(8);
|
||||
format.setVersion(2, 1); // 使用 OpenGL 2.1 以确保固定管线函数可用
|
||||
format.setSamples(4); // 抗锯齿
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
|
||||
// 设置应用信息
|
||||
app.setApplicationName("CloudViewApp");
|
||||
app.setOrganizationName("CloudViewApp");
|
||||
app.setApplicationVersion(APP_VERSION);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("CloudView point cloud and shape viewer");
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
QCommandLineOption cloudOption(
|
||||
QStringList() << "cloud" << "point-cloud",
|
||||
"Load a point cloud or segment file. Positional files are treated the same way.",
|
||||
"file");
|
||||
QCommandLineOption bboxOption(
|
||||
QStringList() << "bbox",
|
||||
"Load a bounding-box JSON file.",
|
||||
"file");
|
||||
QCommandLineOption circleOption(
|
||||
QStringList() << "circle",
|
||||
"Load a circle data file. Row format: {x,y,z}-{r,p,y}-{radius}.",
|
||||
"file");
|
||||
QCommandLineOption rectangleOption(
|
||||
QStringList() << "rectangle",
|
||||
"Load a rectangle data file. Row format: {x,y,z}-{r,p,y}-{width,height}.",
|
||||
"file");
|
||||
QCommandLineOption surfaceOption(
|
||||
QStringList() << "surface",
|
||||
"Load a surface data file. Row format: {x,y,z}-{length,width} or {x,y,z}-{r,p,y}-{length,width}.",
|
||||
"file");
|
||||
QCommandLineOption triangleOption(
|
||||
QStringList() << "triangle",
|
||||
"Load a triangle data file. Row format: {x,y,z}-{x,y,z}-{x,y,z}.",
|
||||
"file");
|
||||
QCommandLineOption sequenceOption(
|
||||
QStringList() << "sequence" << "sequential",
|
||||
"Display startup files one at a time. Closing the window loads the next file.");
|
||||
|
||||
parser.addOption(cloudOption);
|
||||
parser.addOption(bboxOption);
|
||||
parser.addOption(circleOption);
|
||||
parser.addOption(rectangleOption);
|
||||
parser.addOption(surfaceOption);
|
||||
parser.addOption(triangleOption);
|
||||
parser.addOption(sequenceOption);
|
||||
parser.addPositionalArgument("files", "Point cloud or segment files to load.");
|
||||
if (app.arguments().contains(QStringLiteral("--help")) ||
|
||||
app.arguments().contains(QStringLiteral("-h"))) {
|
||||
parser.showHelp(0);
|
||||
}
|
||||
if (app.arguments().contains(QStringLiteral("--version")) ||
|
||||
app.arguments().contains(QStringLiteral("-v"))) {
|
||||
parser.showVersion();
|
||||
}
|
||||
parser.process(app);
|
||||
|
||||
CloudViewMainWindow::StartupFiles startupFiles;
|
||||
startupFiles.displayFiles = parser.values(cloudOption);
|
||||
startupFiles.displayFiles.append(parser.positionalArguments());
|
||||
startupFiles.bboxFiles = parser.values(bboxOption);
|
||||
startupFiles.circleFiles = parser.values(circleOption);
|
||||
startupFiles.rectangleFiles = parser.values(rectangleOption);
|
||||
startupFiles.surfaceFiles = parser.values(surfaceOption);
|
||||
startupFiles.triangleFiles = parser.values(triangleOption);
|
||||
startupFiles.sequential = parser.isSet(sequenceOption);
|
||||
|
||||
// 设置应用程序图标
|
||||
app.setWindowIcon(QIcon(":/logo.png"));
|
||||
|
||||
// 创建并显示主窗口
|
||||
CloudViewMainWindow mainWindow;
|
||||
mainWindow.setWindowTitle(QString("点云查看器 v%1").arg(APP_VERSION));
|
||||
mainWindow.resize(1280, 720);
|
||||
mainWindow.show();
|
||||
|
||||
QTimer::singleShot(0, &mainWindow, [&mainWindow, startupFiles]() {
|
||||
mainWindow.loadStartupFiles(startupFiles);
|
||||
});
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
#include <QApplication>
|
||||
#include <QSurfaceFormat>
|
||||
#include <QIcon>
|
||||
#include <QCommandLineParser>
|
||||
#include <QTimer>
|
||||
#include "CloudViewMainWindow.h"
|
||||
|
||||
#define APP_VERSION "1.2.1"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 设置 OpenGL 格式 - 使用 2.1 版本以确保固定管线兼容
|
||||
QSurfaceFormat format;
|
||||
format.setDepthBufferSize(24);
|
||||
format.setStencilBufferSize(8);
|
||||
format.setVersion(2, 1); // 使用 OpenGL 2.1 以确保固定管线函数可用
|
||||
format.setSamples(4); // 抗锯齿
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
|
||||
// 设置应用信息
|
||||
app.setApplicationName("CloudViewApp");
|
||||
app.setOrganizationName("CloudViewApp");
|
||||
app.setApplicationVersion(APP_VERSION);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("CloudView point cloud and shape viewer");
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
QCommandLineOption cloudOption(
|
||||
QStringList() << "cloud" << "point-cloud",
|
||||
"Load a point cloud or segment file. Positional files are treated the same way.",
|
||||
"file");
|
||||
QCommandLineOption bboxOption(
|
||||
QStringList() << "bbox",
|
||||
"Load a bounding-box JSON file.",
|
||||
"file");
|
||||
QCommandLineOption circleOption(
|
||||
QStringList() << "circle",
|
||||
"Load a circle data file. Row format: {x,y,z}-{r,p,y}-{radius}.",
|
||||
"file");
|
||||
QCommandLineOption rectangleOption(
|
||||
QStringList() << "rectangle",
|
||||
"Load a rectangle data file. Row format: {x,y,z}-{r,p,y}-{width,height}.",
|
||||
"file");
|
||||
QCommandLineOption surfaceOption(
|
||||
QStringList() << "surface",
|
||||
"Load a surface data file. Row format: {x,y,z}-{length,width} or {x,y,z}-{r,p,y}-{length,width}.",
|
||||
"file");
|
||||
QCommandLineOption triangleOption(
|
||||
QStringList() << "triangle",
|
||||
"Load a triangle data file. Row format: {x,y,z}-{x,y,z}-{x,y,z}.",
|
||||
"file");
|
||||
QCommandLineOption sequenceOption(
|
||||
QStringList() << "sequence" << "sequential",
|
||||
"Display startup files one at a time. Closing the window loads the next file.");
|
||||
|
||||
parser.addOption(cloudOption);
|
||||
parser.addOption(bboxOption);
|
||||
parser.addOption(circleOption);
|
||||
parser.addOption(rectangleOption);
|
||||
parser.addOption(surfaceOption);
|
||||
parser.addOption(triangleOption);
|
||||
parser.addOption(sequenceOption);
|
||||
parser.addPositionalArgument("files", "Point cloud or segment files to load.");
|
||||
if (app.arguments().contains(QStringLiteral("--help")) ||
|
||||
app.arguments().contains(QStringLiteral("-h"))) {
|
||||
parser.showHelp(0);
|
||||
}
|
||||
if (app.arguments().contains(QStringLiteral("--version")) ||
|
||||
app.arguments().contains(QStringLiteral("-v"))) {
|
||||
parser.showVersion();
|
||||
}
|
||||
parser.process(app);
|
||||
|
||||
CloudViewMainWindow::StartupFiles startupFiles;
|
||||
startupFiles.displayFiles = parser.values(cloudOption);
|
||||
startupFiles.displayFiles.append(parser.positionalArguments());
|
||||
startupFiles.bboxFiles = parser.values(bboxOption);
|
||||
startupFiles.circleFiles = parser.values(circleOption);
|
||||
startupFiles.rectangleFiles = parser.values(rectangleOption);
|
||||
startupFiles.surfaceFiles = parser.values(surfaceOption);
|
||||
startupFiles.triangleFiles = parser.values(triangleOption);
|
||||
startupFiles.sequential = parser.isSet(sequenceOption);
|
||||
|
||||
// 设置应用程序图标
|
||||
app.setWindowIcon(QIcon(":/logo.png"));
|
||||
|
||||
// 创建并显示主窗口
|
||||
CloudViewMainWindow mainWindow;
|
||||
mainWindow.setWindowTitle(QString("点云查看器 v%1").arg(APP_VERSION));
|
||||
mainWindow.resize(1280, 720);
|
||||
mainWindow.show();
|
||||
|
||||
QTimer::singleShot(0, &mainWindow, [&mainWindow, startupFiles]() {
|
||||
mainWindow.loadStartupFiles(startupFiles);
|
||||
});
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
QT += core gui widgets
|
||||
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib c++11
|
||||
TARGET = ImageView
|
||||
|
||||
win32-msvc {
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
}
|
||||
|
||||
INCLUDEPATH += $$PWD/Inc
|
||||
|
||||
HEADERS += \
|
||||
Inc/ImageView.h
|
||||
|
||||
SOURCES += \
|
||||
Src/ImageView.cpp
|
||||
QT += core gui widgets
|
||||
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib c++11
|
||||
TARGET = ImageView
|
||||
|
||||
win32-msvc {
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
}
|
||||
|
||||
INCLUDEPATH += $$PWD/Inc
|
||||
|
||||
HEADERS += \
|
||||
Inc/ImageView.h
|
||||
|
||||
SOURCES += \
|
||||
Src/ImageView.cpp
|
||||
|
||||
@ -1,83 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <Qt>
|
||||
|
||||
class QLabel;
|
||||
class QEvent;
|
||||
class QGestureEvent;
|
||||
class QGraphicsPixmapItem;
|
||||
class QGraphicsScene;
|
||||
class QMouseEvent;
|
||||
class QPinchGesture;
|
||||
class QWheelEvent;
|
||||
|
||||
// 可缩放、可平移的图片显示控件。
|
||||
class ImageView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ImageView(QWidget* parent = nullptr);
|
||||
~ImageView() override;
|
||||
|
||||
void setImage(const QImage& image);
|
||||
void setImage(const QPixmap& pixmap);
|
||||
bool loadImage(const QString& filePath);
|
||||
void clearImage();
|
||||
|
||||
bool hasImage() const;
|
||||
QImage image() const;
|
||||
|
||||
// 比例以原始图片大小为基准,1.0 表示 100%。
|
||||
double zoomRatio() const;
|
||||
void setZoomRatio(double ratio);
|
||||
void zoomIn(double factor = 1.25);
|
||||
void zoomOut(double factor = 1.25);
|
||||
void resetView();
|
||||
|
||||
void setZoomRange(double minimum, double maximum);
|
||||
double minimumZoomRatio() const;
|
||||
double maximumZoomRatio() const;
|
||||
|
||||
// 图片未铺满视口时的停靠方向,默认居中。
|
||||
void setImageAlignment(Qt::Alignment alignment);
|
||||
Qt::Alignment imageAlignment() const;
|
||||
|
||||
signals:
|
||||
void imageChanged();
|
||||
void zoomRatioChanged(double ratio);
|
||||
|
||||
protected:
|
||||
bool viewportEvent(QEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
bool handleGestureEvent(QGestureEvent* event);
|
||||
void handlePinchGesture(QPinchGesture* gesture);
|
||||
void updateZoomLabel();
|
||||
void updateZoomRatio(double ratio, bool keepCenter);
|
||||
void updateZoomRatioAt(double ratio, const QPoint& viewportAnchor);
|
||||
void repositionZoomLabel();
|
||||
|
||||
QGraphicsScene* m_scene;
|
||||
QGraphicsPixmapItem* m_pixmapItem;
|
||||
QLabel* m_zoomLabel;
|
||||
QImage m_image;
|
||||
double m_zoomRatio;
|
||||
double m_minimumZoomRatio;
|
||||
double m_maximumZoomRatio;
|
||||
Qt::Alignment m_imageAlignment;
|
||||
bool m_panning;
|
||||
bool m_pinching;
|
||||
bool m_needsInitialFit;
|
||||
double m_pinchStartZoomRatio;
|
||||
QPoint m_lastMousePosition;
|
||||
};
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QImage>
|
||||
#include <QPixmap>
|
||||
#include <Qt>
|
||||
|
||||
class QLabel;
|
||||
class QEvent;
|
||||
class QGestureEvent;
|
||||
class QGraphicsPixmapItem;
|
||||
class QGraphicsScene;
|
||||
class QMouseEvent;
|
||||
class QPinchGesture;
|
||||
class QWheelEvent;
|
||||
|
||||
// 可缩放、可平移的图片显示控件。
|
||||
class ImageView : public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ImageView(QWidget* parent = nullptr);
|
||||
~ImageView() override;
|
||||
|
||||
void setImage(const QImage& image);
|
||||
void setImage(const QPixmap& pixmap);
|
||||
bool loadImage(const QString& filePath);
|
||||
void clearImage();
|
||||
|
||||
bool hasImage() const;
|
||||
QImage image() const;
|
||||
|
||||
// 比例以原始图片大小为基准,1.0 表示 100%。
|
||||
double zoomRatio() const;
|
||||
void setZoomRatio(double ratio);
|
||||
void zoomIn(double factor = 1.25);
|
||||
void zoomOut(double factor = 1.25);
|
||||
void resetView();
|
||||
|
||||
void setZoomRange(double minimum, double maximum);
|
||||
double minimumZoomRatio() const;
|
||||
double maximumZoomRatio() const;
|
||||
|
||||
// 图片未铺满视口时的停靠方向,默认居中。
|
||||
void setImageAlignment(Qt::Alignment alignment);
|
||||
Qt::Alignment imageAlignment() const;
|
||||
|
||||
signals:
|
||||
void imageChanged();
|
||||
void zoomRatioChanged(double ratio);
|
||||
|
||||
protected:
|
||||
bool viewportEvent(QEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
bool handleGestureEvent(QGestureEvent* event);
|
||||
void handlePinchGesture(QPinchGesture* gesture);
|
||||
void updateZoomLabel();
|
||||
void updateZoomRatio(double ratio, bool keepCenter);
|
||||
void updateZoomRatioAt(double ratio, const QPoint& viewportAnchor);
|
||||
void repositionZoomLabel();
|
||||
|
||||
QGraphicsScene* m_scene;
|
||||
QGraphicsPixmapItem* m_pixmapItem;
|
||||
QLabel* m_zoomLabel;
|
||||
QImage m_image;
|
||||
double m_zoomRatio;
|
||||
double m_minimumZoomRatio;
|
||||
double m_maximumZoomRatio;
|
||||
Qt::Alignment m_imageAlignment;
|
||||
bool m_panning;
|
||||
bool m_pinching;
|
||||
bool m_needsInitialFit;
|
||||
double m_pinchStartZoomRatio;
|
||||
QPoint m_lastMousePosition;
|
||||
};
|
||||
|
||||
@ -1,34 +1,112 @@
|
||||
ADD_DEFINITIONS(-D__OPENVMS__) #log4cpp 编译选项
|
||||
if(NOT WIN32)
|
||||
ADD_DEFINITIONS(-DLOG4CPP_HAVE_GETTIMEOFDAY) #ARM/Linux 上直接启用 gettimeofday,确保毫秒正确
|
||||
endif()
|
||||
ADD_DEFINITIONS(-DVR_UTILS_EXPORTS) #VrUtils 导出宏定义
|
||||
# ADD_COMPILE_OPTIONS(-Wno-deprecated-declarations) #auto_ptr警告消除
|
||||
|
||||
# 设置CMake策略,允许AUTOMOC和AUTOUIC处理.hh文件
|
||||
# cmake_policy(SET CMP0100 NEW)
|
||||
|
||||
INCLUDE_DIRECTORIES(
|
||||
./_Inc
|
||||
./Inc
|
||||
./MD5
|
||||
./tinyxml2
|
||||
./log4cpp/include
|
||||
)
|
||||
|
||||
AUX_SOURCE_DIRECTORY(./Src SrcS)
|
||||
AUX_SOURCE_DIRECTORY(./MD5 MD5SrcS)
|
||||
AUX_SOURCE_DIRECTORY(./tinyxml2 TinyxmlSrcS)
|
||||
AUX_SOURCE_DIRECTORY(./log4cpp/src LogSrcS)
|
||||
|
||||
# 对特定的.hh文件设置SKIP_AUTOGEN属性
|
||||
set_property(SOURCE log4cpp/src/StringUtil.hh PROPERTY SKIP_AUTOGEN ON)
|
||||
set_property(SOURCE log4cpp/src/PortabilityImpl.hh PROPERTY SKIP_AUTOGEN ON)
|
||||
set_property(SOURCE log4cpp/src/Properties.hh PROPERTY SKIP_AUTOGEN ON)
|
||||
set_property(SOURCE log4cpp/src/Localtime.hh PROPERTY SKIP_AUTOGEN ON)
|
||||
set_property(SOURCE log4cpp/src/PropertyConfiguratorImpl.hh PROPERTY SKIP_AUTOGEN ON)
|
||||
|
||||
ADD_LIBRARY(VrUtils STATIC ${SrcS} ${MD5SrcS}
|
||||
${TinyxmlSrcS}
|
||||
${LogSrcS}
|
||||
)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
# Keep this source list aligned with VrUtils.pro. Do not glob Src/: it contains
|
||||
# legacy standalone sources such as getopt.c that are not part of VrUtils.
|
||||
set(VRUTILS_SOURCES
|
||||
Src/VrDateUtils.cpp
|
||||
Src/VrFileUtils.cpp
|
||||
Src/VrLog.cpp
|
||||
Src/VrNetUtils.cpp
|
||||
Src/VrStringUtils.cpp
|
||||
Src/VrTimeUtils.cpp
|
||||
ini/ConvertUTF.c
|
||||
log4cpp/src/AbortAppender.cpp
|
||||
log4cpp/src/Appender.cpp
|
||||
log4cpp/src/AppenderSkeleton.cpp
|
||||
log4cpp/src/AppendersFactory.cpp
|
||||
log4cpp/src/BasicConfigurator.cpp
|
||||
log4cpp/src/BasicLayout.cpp
|
||||
log4cpp/src/BufferingAppender.cpp
|
||||
log4cpp/src/Category.cpp
|
||||
log4cpp/src/CategoryStream.cpp
|
||||
log4cpp/src/Configurator.cpp
|
||||
log4cpp/src/DailyRollingFileAppender.cpp
|
||||
log4cpp/src/DllMain.cpp
|
||||
log4cpp/src/DummyThreads.cpp
|
||||
log4cpp/src/FactoryParams.cpp
|
||||
log4cpp/src/FileAppender.cpp
|
||||
log4cpp/src/Filter.cpp
|
||||
log4cpp/src/FixedContextCategory.cpp
|
||||
log4cpp/src/HierarchyMaintainer.cpp
|
||||
log4cpp/src/IdsaAppender.cpp
|
||||
log4cpp/src/LayoutAppender.cpp
|
||||
log4cpp/src/LayoutsFactory.cpp
|
||||
log4cpp/src/LevelEvaluator.cpp
|
||||
log4cpp/src/Localtime.cpp
|
||||
log4cpp/src/LoggingEvent.cpp
|
||||
log4cpp/src/MSThreads.cpp
|
||||
log4cpp/src/Manipulator.cpp
|
||||
log4cpp/src/NDC.cpp
|
||||
log4cpp/src/NTEventLogAppender.cpp
|
||||
log4cpp/src/OmniThreads.cpp
|
||||
log4cpp/src/OstreamAppender.cpp
|
||||
log4cpp/src/PThreads.cpp
|
||||
log4cpp/src/PassThroughLayout.cpp
|
||||
log4cpp/src/PatternLayout.cpp
|
||||
log4cpp/src/PortabilityImpl.cpp
|
||||
log4cpp/src/Priority.cpp
|
||||
log4cpp/src/Properties.cpp
|
||||
log4cpp/src/PropertyConfigurator.cpp
|
||||
log4cpp/src/PropertyConfiguratorImpl.cpp
|
||||
log4cpp/src/RemoteSyslogAppender.cpp
|
||||
log4cpp/src/RollingFileAppender.cpp
|
||||
log4cpp/src/SimpleConfigurator.cpp
|
||||
log4cpp/src/SimpleLayout.cpp
|
||||
log4cpp/src/SmtpAppender.cpp
|
||||
log4cpp/src/StringQueueAppender.cpp
|
||||
log4cpp/src/StringUtil.cpp
|
||||
log4cpp/src/SyslogAppender.cpp
|
||||
log4cpp/src/TimeStamp.cpp
|
||||
log4cpp/src/TriggeringEventEvaluatorFactory.cpp
|
||||
log4cpp/src/Win32DebugAppender.cpp
|
||||
log4cpp/src/snprintf.c
|
||||
tinyxml2/tinyxml2.cpp
|
||||
jsoncpp/json_reader.cpp
|
||||
jsoncpp/json_value.cpp
|
||||
jsoncpp/json_writer.cpp
|
||||
)
|
||||
|
||||
add_library(VrUtils STATIC ${VRUTILS_SOURCES})
|
||||
|
||||
set_target_properties(VrUtils PROPERTIES
|
||||
CXX_STANDARD 11
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
)
|
||||
|
||||
target_include_directories(VrUtils
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Inc
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ini
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tinyxml2
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/log4cpp/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/log4cpp/src
|
||||
)
|
||||
|
||||
target_compile_definitions(VrUtils PRIVATE
|
||||
TINYXML2_EXPORT
|
||||
HAVE_SNPRINTF
|
||||
LOG4CPP_HAVE_SSTREAM=1
|
||||
VR_UTILS_EXPORTS
|
||||
)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_compile_definitions(VrUtils PRIVATE LOG4CPP_HAVE_UNISTD_H)
|
||||
set_target_properties(VrUtils PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
target_link_libraries(VrUtils PUBLIC Threads::Threads)
|
||||
|
||||
if(WIN32)
|
||||
# qmake's Windows mkspec supplies these definitions. log4cpp uses the
|
||||
# generic Win32 API names and therefore must see the same character mode.
|
||||
target_compile_definitions(VrUtils PRIVATE UNICODE _UNICODE)
|
||||
target_link_libraries(VrUtils PUBLIC ws2_32 iphlpapi advapi32 shell32)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
target_compile_definitions(VrUtils PRIVATE _HAS_AUTO_PTR_ETC=1)
|
||||
target_compile_options(VrUtils PRIVATE /utf-8)
|
||||
endif()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user