提取CloudView3D

This commit is contained in:
YangOne 2026-07-13 18:56:01 +08:00
parent 6a6dd55bec
commit 517889ff90
14 changed files with 6691 additions and 6457 deletions

View File

@ -1,9 +1,9 @@
# CloudUtils库项目配置文件
# CloudUtils搴撻鐩厤缃枃浠?
# 指定Qt模块
# 鎸囧畾Qt妯
QT += core
# 指定项目模板为静态库
# 鎸囧畾椤圭洰妯澘涓洪潤鎬佸簱
TEMPLATE = lib
CONFIG += staticlib
@ -12,24 +12,24 @@ win32-msvc {
}
# 指定C++标准
# 鎸囧畾C++鏍囧噯
CONFIG += c++11
INCLUDEPATH += $$PWD/../../SDK/Device/VzNLSDK/_Inc
INCLUDEPATH += $$PWD/../../SDK/Device/VzNLSDK/Inc
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
# 指定源文件
# 鎸囧畾婧愭枃浠?
SOURCES += Src/LaserDataLoader.cpp \
Src/PointCloudImageUtils.cpp

View File

@ -1,68 +1,70 @@
# Qt 5 兼容QOpenGLWidget widgets 模块中
QT += core gui widgets opengl
CONFIG += c++17
CONFIG -= app_bundle
# Windows平台UTF-8编码支持
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
QMAKE_CXXFLAGS += /bigobj
}
TARGET = CloudView
TEMPLATE = app
# 项目目录
INCLUDEPATH += $$PWD/Inc
INCLUDEPATH += $$PWD/../CloudUtils/Inc
INCLUDEPATH += $$PWD/../../SDK/Device/VzNLSDK/Inc
INCLUDEPATH += $$PWD/../VrUtils/Inc
INCLUDEPATH += $$PWD/../VrCommon/Inc
# 头文件
HEADERS += \
Inc/CloudViewMainWindow.h \
Inc/PointCloudGLWidget.h \
Inc/PointCloudConverter.h
# 源文件
SOURCES += \
main.cpp \
Src/CloudViewMainWindow.cpp \
Src/PointCloudGLWidget.cpp \
Src/PointCloudConverter.cpp
# 资源文件
RESOURCES += resource/resource.qrc
RESOURCES += $$PWD/../../AppUtils/UICommon/common_resources.qrc
# Windows 应用图标
RC_ICONS = resource/logo.ico
# Windows平台库链接
win32:CONFIG(release, debug|release): {
LIBS += -L../CloudUtils/release -lCloudUtils
LIBS += -L../VrUtils/release -lVrUtils
LIBS += -L../VrCommon/release -lVrCommon
}
else:win32:CONFIG(debug, debug|release): {
LIBS += -L../CloudUtils/debug -lCloudUtils
LIBS += -L../VrUtils/debug -lVrUtils
LIBS += -L../VrCommon/debug -lVrCommon
}
# Windows系统库
win32 {
LIBS += -lAdvapi32
LIBS += -lopengl32
LIBS += -lglu32
}
DEFINES += _USE_MATH_DEFINES
# Default rules for deployment
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
# Qt 5 鍏煎锛歈OpenGLWidget ?widgets 潡涓?
QT += core gui widgets opengl
CONFIG += c++17
CONFIG -= app_bundle
# Windows骞冲彴UTF-8缂栫爜鏀寔
win32-msvc {
QMAKE_CXXFLAGS += /utf-8
QMAKE_CXXFLAGS += /bigobj
}
TARGET = CloudView
TEMPLATE = app
# 椤圭洰鐩綍
INCLUDEPATH += $$PWD/Inc
INCLUDEPATH += $$PWD/../CloudView3D/Inc
INCLUDEPATH += $$PWD/../CloudUtils/Inc
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/Inc
INCLUDEPATH += $$PWD/../VrUtils/Inc
INCLUDEPATH += $$PWD/../VrCommon/Inc
# 澶存枃浠?
HEADERS += \
Inc/CloudViewMainWindow.h \
Inc/PointCloudGLWidget.h \
Inc/PointCloudConverter.h
# 婧愭枃浠?
SOURCES += \
main.cpp \
Src/CloudViewMainWindow.cpp \
Src/PointCloudConverter.cpp
# 璧勬簮鏂囦欢
RESOURCES += resource/resource.qrc
RESOURCES += $$PWD/../../AppUtils/UICommon/common_resources.qrc
# Windows 搴旂敤鍥炬爣
RC_ICONS = resource/logo.ico
# Windows骞冲彴搴撻摼鎺?
win32:CONFIG(release, debug|release): {
LIBS += -L../CloudView3D/release -lCloudView3D
LIBS += -L../CloudUtils/release -lCloudUtils
LIBS += -L../VrUtils/release -lVrUtils
LIBS += -L../VrCommon/release -lVrCommon
}
else:win32:CONFIG(debug, debug|release): {
LIBS += -L../CloudView3D/debug -lCloudView3D
LIBS += -L../CloudUtils/debug -lCloudUtils
LIBS += -L../VrUtils/debug -lVrUtils
LIBS += -L../VrCommon/debug -lVrCommon
}
# Windows绯荤粺搴?
win32 {
LIBS += -lAdvapi32
LIBS += -lopengl32
LIBS += -lglu32
}
DEFINES += _USE_MATH_DEFINES
# Default rules for deployment
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

View File

@ -22,8 +22,8 @@
#include <QStringList>
#include <memory>
#include "PointCloudGLWidget.h"
#include "PointCloudConverter.h"
#include "CloudView3D.h"
#include "PointCloudConverter.h"
class QTextEdit;
class QTableWidget;
@ -276,9 +276,14 @@ private:
/**
* @brief
*/
void updateSelectedPointsDisplay();
/**
void updateSelectedPointsDisplay();
void clearMeasureResultDisplay();
void updateMeasureResultDisplay(const SelectedPointInfo& p1,
const SelectedPointInfo& p2,
float distance,
const QString& statusPrefix = QString());
/**
* @brief 线0,0,0
*/
QVector<QVector3D> getOriginalLinePoints(const SelectedLineInfo& lineInfo);
@ -307,7 +312,9 @@ private:
bool loadBoundingBoxFile(const QString& fileName);
void applyTransformToAllClouds(const QMatrix4x4& matrix);
void addGeneratedCloud(const PointCloudXYZ& cloud, const QString& name);
void showBasicShape(const QString& shapeName, const QVector<LineSegment>& segments);
void showBasicShape(const QString& shapeName,
const QVector<LineSegment>& segments,
const QVector<BasicShapePoint>& points = QVector<BasicShapePoint>());
void showBasicSurface(const QString& shapeName, const QVector<SurfaceQuad>& surfaces, const QVector<LineSegment>& outlines);
// 点云显示控件
@ -329,9 +336,10 @@ private:
// 选点测距控件
QCheckBox* m_cbMeasureDistance;
QPushButton* m_btnClearPoints;
QLabel* m_lblPoint1;
QLabel* m_lblPoint2;
QLabel* m_lblDistance;
QLabel* m_lblPoint1;
QLabel* m_lblPoint2;
QLabel* m_lblDistance;
QLabel* m_lblDeltaXYZ;
// 点1坐标编辑控件
QLineEdit* m_editPoint1X;

View File

@ -1,59 +1,14 @@
#ifndef POINT_CLOUD_CONVERTER_H
#define POINT_CLOUD_CONVERTER_H
#include <string>
#include <vector>
#include <memory>
/**
* @brief 3D
*/
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) {}
};
/**
* @brief 3D
*/
struct Point3DRGB
{
float x, y, z;
uint8_t r, g, b;
float pointSize; // 自定义点大小0 表示使用全局默认值(来自 RGBA 中 A > 1 的值)
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) {}
};
/**
* @brief
*/
template<typename PointT>
class SimplePointCloud
{
public:
std::vector<PointT> points;
std::vector<int> lineIndices; // 每个点所属的线索引
void clear() { points.clear(); lineIndices.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); }
void push_back(const PointT& pt) { points.push_back(pt); }
void push_back(const PointT& pt, int lineIdx) {
points.push_back(pt);
lineIndices.push_back(lineIdx);
}
};
using PointCloudXYZ = SimplePointCloud<Point3D>;
using PointCloudXYZRGB = SimplePointCloud<Point3DRGB>;
/**
* @brief
#include <string>
#include <vector>
#include <memory>
#include "CloudView3DTypes.h"
/**
* @brief
* txt/datpcdply
*/
class PointCloudConverter

View File

@ -1,495 +1,6 @@
#ifndef POINT_CLOUD_GL_WIDGET_H
#define POINT_CLOUD_GL_WIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QMatrix4x4>
#include <QVector3D>
#include <QQuaternion>
#include <QMouseEvent>
#include <QWheelEvent>
#include <vector>
#include "PointCloudConverter.h"
#include "VZNL_Types.h"
/**
* @brief
*/
enum class PointCloudColor
{
White,
Red,
Green,
Blue,
Yellow,
Cyan,
Magenta,
Original
};
/**
* @brief
*/
struct SelectedPointInfo
{
bool valid;
size_t index;
float x, y, z;
int cloudIndex;
int lineIndex; // 所属线索引
int pointIndexInLine; // 点在线中的索引
SelectedPointInfo() : valid(false), index(0), x(0), y(0), z(0), cloudIndex(-1), lineIndex(-1), pointIndexInLine(-1) {}
};
/**
* @brief 线
*/
enum class LineSelectMode
{
Vertical, // 纵向选线(同一条扫描线)
Horizontal // 横向选线(所有线的相同索引点)
};
/**
* @brief
*/
enum class EulerRotationOrder
{
ZYX, // Yaw-Pitch-Roll最常用
XYZ, // Roll-Pitch-Yaw
ZXY, // Yaw-Roll-Pitch
YXZ, // Pitch-Roll-Yaw
XZY, // Roll-Yaw-Pitch
YZX // Pitch-Yaw-Roll
};
/**
* @brief 线
*/
struct SelectedLineInfo
{
bool valid;
int cloudIndex; // 点云索引
int lineIndex; // 线索引(纵向选线时使用)
int pointIndex; // 点索引(横向选线时使用)
int pointCount; // 该线上的点数
LineSelectMode mode; // 选线模式
SelectedLineInfo() : valid(false), cloudIndex(-1), lineIndex(-1), pointIndex(-1), pointCount(0), mode(LineSelectMode::Vertical) {}
};
/**
* @brief 线
*/
struct LineSegment
{
float x1, y1, z1; // 起点
float x2, y2, z2; // 终点
float r, g, b; // 颜色 (0-1)
float lineWidth; // 线宽0 表示使用默认值(来自 RGBA 中 A > 1 的值)
LineSegment() : x1(0), y1(0), z1(0), x2(0), y2(0), z2(0), r(1), g(1), b(1), lineWidth(0) {}
LineSegment(float _x1, float _y1, float _z1, float _x2, float _y2, float _z2, float _r = 1.0f, float _g = 1.0f, float _b = 1.0f, float _lw = 0)
: x1(_x1), y1(_y1), z1(_z1), x2(_x2), y2(_y2), z2(_z2), r(_r), g(_g), b(_b), lineWidth(_lw) {}
};
#ifndef CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
#define CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H
/**
* @brief
*/
struct SurfaceQuad
{
QVector3D p1, p2, p3, p4;
float r, g, b, a;
#include "../../CloudView3D/Inc/PointCloudGLWidget.h"
SurfaceQuad() : p1(), p2(), p3(), p4(), r(0.0f), g(0.8f), b(1.0f), a(0.5f) {}
SurfaceQuad(const QVector3D& _p1,
const QVector3D& _p2,
const QVector3D& _p3,
const QVector3D& _p4,
float _r = 0.0f,
float _g = 0.8f,
float _b = 1.0f,
float _a = 0.5f)
: p1(_p1), p2(_p2), p3(_p3), p4(_p4), r(_r), g(_g), b(_b), a(_a) {}
};
/**
* @brief 姿
*
*
* - X轴
* - Y轴绿
* - Z轴
*
* ZYXYaw-Pitch-Roll
* - rz: Z轴旋转Yaw
* - ry: Y轴旋转Pitch
* - rx: X轴旋转Roll
*/
struct PosePoint
{
float x, y, z; // 位置
float rx, ry, rz; // 欧拉角(度)
float scale; // 坐标系大小
PosePoint() : x(0), y(0), z(0), rx(0), ry(0), rz(0), scale(10.0f) {}
PosePoint(float _x, float _y, float _z, float _rx, float _ry, float _rz, float _scale = 10.0f)
: x(_x), y(_y), z(_z), rx(_rx), ry(_ry), rz(_rz), scale(_scale) {}
};
/**
* @brief OpenGL
*/
class PointCloudGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit PointCloudGLWidget(QWidget* parent = nullptr);
~PointCloudGLWidget() override;
void addPointCloud(const PointCloudXYZ& cloud, const QString& name = "");
void addPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "");
void clearPointClouds();
void setPointCloudColor(PointCloudColor color);
void setPointSize(float size);
void resetView();
void fitToContent();
void zoomIn();
void zoomOut();
/**
* @brief
* @param rotX X轴旋转角度
* @param rotY Y轴旋转角度
* @param rotZ Z轴旋转角度
*/
void setViewAngles(float rotX, float rotY, float rotZ = 0.0f);
QVector<SelectedPointInfo> getSelectedPoints() const { return m_selectedPoints; }
SelectedLineInfo getSelectedLine() const { return m_selectedLine; }
void clearSelectedPoints();
void clearSelectedLine();
float calculateDistance(const SelectedPointInfo& p1, const SelectedPointInfo& p2);
/**
* @brief
* @param index 01
* @param x X坐标
* @param y Y坐标
* @param z Z坐标
*/
void updateSelectedPointCoord(int index, float x, float y, float z);
/**
* @brief
* @param index 01
* @param x X坐标
* @param y Y坐标
* @param z Z坐标
*/
void setSelectedPointCoord(int index, float x, float y, float z);
/**
* @brief 线线
*/
bool selectLineByIndex(int lineIndex);
/**
* @brief 线线
*/
bool selectHorizontalLineByIndex(int pointIndex);
/**
* @brief 线
*/
void setLineSelectMode(LineSelectMode mode) { m_lineSelectMode = mode; }
/**
* @brief
*/
void setEulerRotationOrder(EulerRotationOrder order) { m_eulerRotationOrder = order; }
/**
* @brief
*/
EulerRotationOrder getEulerRotationOrder() const { return m_eulerRotationOrder; }
/**
* @brief
*/
void setMeasureDistanceEnabled(bool enabled) { m_measureDistanceEnabled = enabled; }
/**
* @brief
*/
bool isMeasureDistanceEnabled() const { return m_measureDistanceEnabled; }
/**
* @brief 线
* @return (x, y, z)
*/
QVector<QVector3D> getSelectedLinePoints() const;
/**
* @brief
* @param point
*/
void setListHighlightPoint(const QVector3D& point);
/**
* @brief
*/
void clearListHighlightPoint();
/**
* @brief
*/
bool getFirstCloudData(PointCloudXYZ& cloud) const;
/**
* @brief
*/
void replaceFirstCloud(const PointCloudXYZ& cloud, const QString& name);
/**
* @brief
*/
size_t getCloudCount() const { return m_pointClouds.size(); }
/**
* @brief 4x4
* @param matrix 4x4
*/
void transformAllClouds(const QMatrix4x4& matrix);
/**
* @brief Z
* @param rotMatrix
* @param zThreshold Z
* @return
*/
size_t rotateCloudsByZThreshold(const QMatrix4x4& rotMatrix, float zThreshold);
/**
* @brief 线
* @param scanLines 线
* @return
*/
size_t getAllCloudsByLines(std::vector<std::vector<SVzNL3DPosition>>& scanLines) const;
/**
* @brief 线
*/
void addLineSegments(const QVector<LineSegment>& segments);
/**
* @brief 线
*/
void clearLineSegments();
/**
* @brief 线
*/
void setBasicShapeSegments(const QVector<LineSegment>& segments);
/**
* @brief 线
*/
void appendBasicShapeSegments(const QVector<LineSegment>& segments);
/**
* @brief
*/
void appendBasicShapeSurfaces(const QVector<SurfaceQuad>& surfaces);
/**
* @brief 线
*/
void clearBasicShapeSegments();
/**
* @brief 姿
*/
void addPosePoints(const QVector<PosePoint>& poses);
/**
* @brief 姿
*/
void clearPosePoints();
signals:
void pointSelected(const SelectedPointInfo& point);
void twoPointsSelected(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance);
void lineSelected(const SelectedLineInfo& line);
void viewAnglesChanged(float rotX, float rotY, float rotZ);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
private:
struct PointCloudData
{
std::vector<float> vertices;
std::vector<float> colors;
std::vector<int> lineIndices; // 每个点所属的线索引
std::vector<int> originalIndices; // 每个显示点在原始点云中的索引用于计算原始index
std::vector<int> pointInLineIndices; // 每个显示点在所属线中的索引(预计算,支持不等长线)
bool hasColor;
bool hasLineInfo; // 是否有线信息
QString name;
int colorIndex; // 点云颜色索引
int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云)
// 自定义点大小分组RGBA 中 A > 1 的点按大小分组)
struct PointSizeGroup {
float pointSize; // 点大小
std::vector<size_t> indices; // 该大小的点在顶点数组中的索引(顶点索引 = indices[i] * 3
};
std::vector<PointSizeGroup> customPointSizeGroups;
bool hasCustomPointSizes;
// VBO 缓冲区
QOpenGLBuffer vertexBuffer; // 顶点 VBO
QOpenGLBuffer colorBuffer; // 颜色 VBO
bool vboCreated; // VBO 是否已创建
PointCloudData()
: hasColor(false), hasLineInfo(false), colorIndex(0)
, totalLines(0), pointsPerLine(0)
, hasCustomPointSizes(false)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
{}
// 移动构造函数QOpenGLBuffer 不支持拷贝)
PointCloudData(PointCloudData&& other) noexcept
: vertices(std::move(other.vertices))
, colors(std::move(other.colors))
, lineIndices(std::move(other.lineIndices))
, originalIndices(std::move(other.originalIndices))
, pointInLineIndices(std::move(other.pointInLineIndices))
, hasColor(other.hasColor)
, hasLineInfo(other.hasLineInfo)
, name(std::move(other.name))
, colorIndex(other.colorIndex)
, totalLines(other.totalLines)
, pointsPerLine(other.pointsPerLine)
, customPointSizeGroups(std::move(other.customPointSizeGroups))
, hasCustomPointSizes(other.hasCustomPointSizes)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
{
// VBO 需要在 GL 上下文中重建,标记为未创建
other.vboCreated = false;
}
PointCloudData& operator=(PointCloudData&& other) noexcept
{
if (this != &other) {
// 释放自身 VBO
if (vboCreated) {
vertexBuffer.destroy();
colorBuffer.destroy();
vboCreated = false;
}
vertices = std::move(other.vertices);
colors = std::move(other.colors);
lineIndices = std::move(other.lineIndices);
originalIndices = std::move(other.originalIndices);
pointInLineIndices = std::move(other.pointInLineIndices);
hasColor = other.hasColor;
hasLineInfo = other.hasLineInfo;
name = std::move(other.name);
colorIndex = other.colorIndex;
totalLines = other.totalLines;
pointsPerLine = other.pointsPerLine;
customPointSizeGroups = std::move(other.customPointSizeGroups);
hasCustomPointSizes = other.hasCustomPointSizes;
// VBO 需要重建
other.vboCreated = false;
}
return *this;
}
// 禁止拷贝
PointCloudData(const PointCloudData&) = delete;
PointCloudData& operator=(const PointCloudData&) = delete;
};
void computeBoundingBox();
void setCurrentColor(PointCloudColor color);
void setColorByIndex(int colorIndex); // 根据索引设置颜色
SelectedPointInfo pickPoint(int screenX, int screenY);
void drawSelectedPoints();
void drawMeasurementLine();
void drawAxis();
void drawSelectedLine(); // 绘制选中的线
void drawLineSegments(); // 绘制线段
void drawBasicShapeSurfaces(); // 绘制基础图形面片
void drawBasicShapeSegments(); // 绘制基础图形
void drawLineSegmentList(const QVector<LineSegment>& segments);
void drawPosePoints(); // 绘制姿态点
void drawAxisLabels(); // 绘制坐标轴 XYZ 标注
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
std::vector<PointCloudData> m_pointClouds;
QMatrix4x4 m_projection;
QMatrix4x4 m_view;
QMatrix4x4 m_model;
float m_distance;
float m_rotationX;
float m_rotationY;
float m_rotationZ;
QQuaternion m_rotation; // 使用四元数存储旋转状态
QVector3D m_center;
QVector3D m_pan;
QVector3D m_minBound;
QVector3D m_maxBound;
QPoint m_lastMousePos;
bool m_leftButtonPressed;
bool m_rightButtonPressed;
bool m_middleButtonPressed;
PointCloudColor m_currentColor;
float m_pointSize;
LineSelectMode m_lineSelectMode;
EulerRotationOrder m_eulerRotationOrder;
bool m_measureDistanceEnabled;
QVector<SelectedPointInfo> m_selectedPoints;
SelectedLineInfo m_selectedLine;
static const int MAX_SELECTED_POINTS = 2;
// 列表高亮点(与选点功能区分)
bool m_hasListHighlightPoint;
QVector3D m_listHighlightPoint;
int m_colorIndex; // 颜色轮换索引
static const int COLOR_COUNT = 7; // 可用颜色数量
// 线段和姿态点数据
QVector<LineSegment> m_lineSegments;
QVector<LineSegment> m_basicShapeSegments;
QVector<SurfaceQuad> m_basicShapeSurfaces;
QVector<PosePoint> m_posePoints;
};
#endif // POINT_CLOUD_GL_WIDGET_H
#endif // CLOUD_VIEW_POINT_CLOUD_GL_WIDGET_FORWARDER_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +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/../VrCommon/Inc
INCLUDEPATH += $$PWD/../../Device/SDK/VzNLSDK/Inc
HEADERS += \
Inc/CloudView3D.h \
Inc/CloudView3DTypes.h \
Inc/PointCloudGLWidget.h
SOURCES += \
Src/PointCloudGLWidget.cpp
win32 {
LIBS += -lopengl32
LIBS += -lglu32
}
DEFINES += _USE_MATH_DEFINES

View File

@ -0,0 +1,7 @@
#ifndef CLOUD_VIEW_3D_H
#define CLOUD_VIEW_3D_H
#include "CloudView3DTypes.h"
#include "PointCloudGLWidget.h"
#endif // CLOUD_VIEW_3D_H

View File

@ -0,0 +1,54 @@
#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;
void clear() { points.clear(); lineIndices.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); }
void push_back(const PointT& pt) { points.push_back(pt); }
void push_back(const PointT& pt, int lineIdx)
{
points.push_back(pt);
lineIndices.push_back(lineIdx);
}
};
using PointCloudXYZ = SimplePointCloud<Point3D>;
using PointCloudXYZRGB = SimplePointCloud<Point3DRGB>;
#endif // CLOUD_VIEW_3D_TYPES_H

View File

@ -0,0 +1,521 @@
#ifndef POINT_CLOUD_GL_WIDGET_H
#define POINT_CLOUD_GL_WIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
#include <QMatrix4x4>
#include <QVector3D>
#include <QQuaternion>
#include <QMouseEvent>
#include <QWheelEvent>
#include <vector>
#include "CloudView3DTypes.h"
#include "VZNL_Types.h"
/**
* @brief
*/
enum class PointCloudColor
{
White,
Red,
Green,
Blue,
Yellow,
Cyan,
Magenta,
Original
};
/**
* @brief
*/
struct SelectedPointInfo
{
bool valid;
size_t index;
float x, y, z;
int cloudIndex;
int lineIndex; // 所属线索引
int pointIndexInLine; // 点在线中的索引
SelectedPointInfo() : valid(false), index(0), x(0), y(0), z(0), cloudIndex(-1), lineIndex(-1), pointIndexInLine(-1) {}
};
/**
* @brief 线
*/
enum class LineSelectMode
{
Vertical, // 纵向选线(同一条扫描线)
Horizontal // 横向选线(所有线的相同索引点)
};
/**
* @brief
*/
enum class EulerRotationOrder
{
ZYX, // Yaw-Pitch-Roll最常用
XYZ, // Roll-Pitch-Yaw
ZXY, // Yaw-Roll-Pitch
YXZ, // Pitch-Roll-Yaw
XZY, // Roll-Yaw-Pitch
YZX // Pitch-Yaw-Roll
};
/**
* @brief 线
*/
struct SelectedLineInfo
{
bool valid;
int cloudIndex; // 点云索引
int lineIndex; // 线索引(纵向选线时使用)
int pointIndex; // 点索引(横向选线时使用)
int pointCount; // 该线上的点数
LineSelectMode mode; // 选线模式
SelectedLineInfo() : valid(false), cloudIndex(-1), lineIndex(-1), pointIndex(-1), pointCount(0), mode(LineSelectMode::Vertical) {}
};
/**
* @brief 线
*/
struct LineSegment
{
float x1, y1, z1; // 起点
float x2, y2, z2; // 终点
float r, g, b; // 颜色 (0-1)
float lineWidth; // 线宽0 表示使用默认值(来自 RGBA 中 A > 1 的值)
LineSegment() : x1(0), y1(0), z1(0), x2(0), y2(0), z2(0), r(1), g(1), b(1), lineWidth(0) {}
LineSegment(float _x1, float _y1, float _z1, float _x2, float _y2, float _z2, float _r = 1.0f, float _g = 1.0f, float _b = 1.0f, float _lw = 0)
: x1(_x1), y1(_y1), z1(_z1), x2(_x2), y2(_y2), z2(_z2), r(_r), g(_g), b(_b), lineWidth(_lw) {}
};
/**
* @brief
*/
struct SurfaceQuad
{
QVector3D p1, p2, p3, p4;
float r, g, b, a;
SurfaceQuad() : p1(), p2(), p3(), p4(), r(0.0f), g(0.8f), b(1.0f), a(0.5f) {}
SurfaceQuad(const QVector3D& _p1,
const QVector3D& _p2,
const QVector3D& _p3,
const QVector3D& _p4,
float _r = 0.0f,
float _g = 0.8f,
float _b = 1.0f,
float _a = 0.5f)
: p1(_p1), p2(_p2), p3(_p3), p4(_p4), r(_r), g(_g), b(_b), a(_a) {}
};
struct BasicShapePoint
{
float x, y, z;
float r, g, b;
float pointSize;
BasicShapePoint() : x(0), y(0), z(0), r(1), g(1), b(1), pointSize(8.0f) {}
BasicShapePoint(float _x,
float _y,
float _z,
float _r = 1.0f,
float _g = 1.0f,
float _b = 1.0f,
float _size = 8.0f)
: x(_x), y(_y), z(_z), r(_r), g(_g), b(_b), pointSize(_size) {}
};
/**
* @brief 姿
*
*
* - X轴
* - Y轴绿
* - Z轴
*
* ZYXYaw-Pitch-Roll
* - rz: Z轴旋转Yaw
* - ry: Y轴旋转Pitch
* - rx: X轴旋转Roll
*/
struct PosePoint
{
float x, y, z; // 位置
float rx, ry, rz; // 欧拉角(度)
float scale; // 坐标系大小
PosePoint() : x(0), y(0), z(0), rx(0), ry(0), rz(0), scale(10.0f) {}
PosePoint(float _x, float _y, float _z, float _rx, float _ry, float _rz, float _scale = 10.0f)
: x(_x), y(_y), z(_z), rx(_rx), ry(_ry), rz(_rz), scale(_scale) {}
};
/**
* @brief OpenGL
*/
class PointCloudGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit PointCloudGLWidget(QWidget* parent = nullptr);
~PointCloudGLWidget() override;
void addPointCloud(const PointCloudXYZ& cloud, const QString& name = "");
void addPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "");
void clearPointClouds();
void setPointCloudColor(PointCloudColor color);
void setPointSize(float size);
void resetView();
void fitToContent();
void zoomIn();
void zoomOut();
/**
* @brief
* @param rotX X轴旋转角度
* @param rotY Y轴旋转角度
* @param rotZ Z轴旋转角度
*/
void setViewAngles(float rotX, float rotY, float rotZ = 0.0f);
QVector<SelectedPointInfo> getSelectedPoints() const { return m_selectedPoints; }
SelectedLineInfo getSelectedLine() const { return m_selectedLine; }
void clearSelectedPoints();
void clearSelectedLine();
float calculateDistance(const SelectedPointInfo& p1, const SelectedPointInfo& p2);
/**
* @brief
* @param index 01
* @param x X坐标
* @param y Y坐标
* @param z Z坐标
*/
void updateSelectedPointCoord(int index, float x, float y, float z);
/**
* @brief
* @param index 01
* @param x X坐标
* @param y Y坐标
* @param z Z坐标
*/
void setSelectedPointCoord(int index, float x, float y, float z);
/**
* @brief 线线
*/
bool selectLineByIndex(int lineIndex);
/**
* @brief 线线
*/
bool selectHorizontalLineByIndex(int pointIndex);
/**
* @brief 线
*/
void setLineSelectMode(LineSelectMode mode) { m_lineSelectMode = mode; }
/**
* @brief
*/
void setEulerRotationOrder(EulerRotationOrder order) { m_eulerRotationOrder = order; }
/**
* @brief
*/
EulerRotationOrder getEulerRotationOrder() const { return m_eulerRotationOrder; }
/**
* @brief
*/
void setMeasureDistanceEnabled(bool enabled) { m_measureDistanceEnabled = enabled; }
/**
* @brief
*/
bool isMeasureDistanceEnabled() const { return m_measureDistanceEnabled; }
/**
* @brief 线
* @return (x, y, z)
*/
QVector<QVector3D> getSelectedLinePoints() const;
/**
* @brief
* @param point
*/
void setListHighlightPoint(const QVector3D& point);
/**
* @brief
*/
void clearListHighlightPoint();
/**
* @brief
*/
bool getFirstCloudData(PointCloudXYZ& cloud) const;
/**
* @brief
*/
void replaceFirstCloud(const PointCloudXYZ& cloud, const QString& name);
/**
* @brief
*/
size_t getCloudCount() const { return m_pointClouds.size(); }
/**
* @brief 4x4
* @param matrix 4x4
*/
void transformAllClouds(const QMatrix4x4& matrix);
/**
* @brief Z
* @param rotMatrix
* @param zThreshold Z
* @return
*/
size_t rotateCloudsByZThreshold(const QMatrix4x4& rotMatrix, float zThreshold);
/**
* @brief 线
* @param scanLines 线
* @return
*/
size_t getAllCloudsByLines(std::vector<std::vector<SVzNL3DPosition>>& scanLines) const;
/**
* @brief 线
*/
void addLineSegments(const QVector<LineSegment>& segments);
/**
* @brief 线
*/
void clearLineSegments();
/**
* @brief 线
*/
void setBasicShapeSegments(const QVector<LineSegment>& segments);
/**
* @brief 线
*/
void appendBasicShapeSegments(const QVector<LineSegment>& segments);
/**
* @brief
*/
void appendBasicShapeSurfaces(const QVector<SurfaceQuad>& surfaces);
void appendBasicShapePoints(const QVector<BasicShapePoint>& points);
/**
* @brief 线
*/
void clearBasicShapeSegments();
/**
* @brief 姿
*/
void addPosePoints(const QVector<PosePoint>& poses);
/**
* @brief 姿
*/
void clearPosePoints();
signals:
void pointSelected(const SelectedPointInfo& point);
void twoPointsSelected(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance);
void measurementFinished(const SelectedPointInfo& p1,
const SelectedPointInfo& p2,
float distance,
float dx,
float dy,
float dz);
void lineSelected(const SelectedLineInfo& line);
void viewAnglesChanged(float rotX, float rotY, float rotZ);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void wheelEvent(QWheelEvent* event) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
private:
struct PointCloudData
{
std::vector<float> vertices;
std::vector<float> colors;
std::vector<int> lineIndices; // 每个点所属的线索引
std::vector<int> originalIndices; // 每个显示点在原始点云中的索引用于计算原始index
std::vector<int> pointInLineIndices; // 每个显示点在所属线中的索引(预计算,支持不等长线)
bool hasColor;
bool hasLineInfo; // 是否有线信息
QString name;
int colorIndex; // 点云颜色索引
int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云)
// 自定义点大小分组RGBA 中 A > 1 的点按大小分组)
struct PointSizeGroup {
float pointSize; // 点大小
std::vector<size_t> indices; // 该大小的点在顶点数组中的索引(顶点索引 = indices[i] * 3
};
std::vector<PointSizeGroup> customPointSizeGroups;
bool hasCustomPointSizes;
// VBO 缓冲区
QOpenGLBuffer vertexBuffer; // 顶点 VBO
QOpenGLBuffer colorBuffer; // 颜色 VBO
bool vboCreated; // VBO 是否已创建
PointCloudData()
: hasColor(false), hasLineInfo(false), colorIndex(0)
, totalLines(0), pointsPerLine(0)
, hasCustomPointSizes(false)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
{}
// 移动构造函数QOpenGLBuffer 不支持拷贝)
PointCloudData(PointCloudData&& other) noexcept
: vertices(std::move(other.vertices))
, colors(std::move(other.colors))
, lineIndices(std::move(other.lineIndices))
, originalIndices(std::move(other.originalIndices))
, pointInLineIndices(std::move(other.pointInLineIndices))
, hasColor(other.hasColor)
, hasLineInfo(other.hasLineInfo)
, name(std::move(other.name))
, colorIndex(other.colorIndex)
, totalLines(other.totalLines)
, pointsPerLine(other.pointsPerLine)
, customPointSizeGroups(std::move(other.customPointSizeGroups))
, hasCustomPointSizes(other.hasCustomPointSizes)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
{
// VBO 需要在 GL 上下文中重建,标记为未创建
other.vboCreated = false;
}
PointCloudData& operator=(PointCloudData&& other) noexcept
{
if (this != &other) {
// 释放自身 VBO
if (vboCreated) {
vertexBuffer.destroy();
colorBuffer.destroy();
vboCreated = false;
}
vertices = std::move(other.vertices);
colors = std::move(other.colors);
lineIndices = std::move(other.lineIndices);
originalIndices = std::move(other.originalIndices);
pointInLineIndices = std::move(other.pointInLineIndices);
hasColor = other.hasColor;
hasLineInfo = other.hasLineInfo;
name = std::move(other.name);
colorIndex = other.colorIndex;
totalLines = other.totalLines;
pointsPerLine = other.pointsPerLine;
customPointSizeGroups = std::move(other.customPointSizeGroups);
hasCustomPointSizes = other.hasCustomPointSizes;
// VBO 需要重建
other.vboCreated = false;
}
return *this;
}
// 禁止拷贝
PointCloudData(const PointCloudData&) = delete;
PointCloudData& operator=(const PointCloudData&) = delete;
};
void computeBoundingBox();
void setCurrentColor(PointCloudColor color);
void setColorByIndex(int colorIndex); // 根据索引设置颜色
SelectedPointInfo pickPoint(int screenX, int screenY);
void drawSelectedPoints();
void drawMeasurementLine();
void drawAxis();
void drawSelectedLine(); // 绘制选中的线
void drawLineSegments(); // 绘制线段
void drawBasicShapeSurfaces(); // 绘制基础图形面片
void drawBasicShapeSegments(); // 绘制基础图形
void drawBasicShapePoints();
void drawLineSegmentList(const QVector<LineSegment>& segments);
void drawPosePoints(); // 绘制姿态点
void drawAxisLabels(); // 绘制坐标轴 XYZ 标注
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
std::vector<PointCloudData> m_pointClouds;
QMatrix4x4 m_projection;
QMatrix4x4 m_view;
QMatrix4x4 m_model;
float m_distance;
float m_rotationX;
float m_rotationY;
float m_rotationZ;
QQuaternion m_rotation; // 使用四元数存储旋转状态
QVector3D m_center;
QVector3D m_pan;
QVector3D m_minBound;
QVector3D m_maxBound;
QPoint m_lastMousePos;
bool m_leftButtonPressed;
bool m_rightButtonPressed;
bool m_middleButtonPressed;
PointCloudColor m_currentColor;
float m_pointSize;
LineSelectMode m_lineSelectMode;
EulerRotationOrder m_eulerRotationOrder;
bool m_measureDistanceEnabled;
QVector<SelectedPointInfo> m_selectedPoints;
SelectedLineInfo m_selectedLine;
static const int MAX_SELECTED_POINTS = 2;
// 列表高亮点(与选点功能区分)
bool m_hasListHighlightPoint;
QVector3D m_listHighlightPoint;
int m_colorIndex; // 颜色轮换索引
static const int COLOR_COUNT = 7; // 可用颜色数量
// 线段和姿态点数据
QVector<LineSegment> m_lineSegments;
QVector<LineSegment> m_basicShapeSegments;
QVector<SurfaceQuad> m_basicShapeSurfaces;
QVector<BasicShapePoint> m_basicShapePoints;
QVector<PosePoint> m_posePoints;
};
#endif // POINT_CLOUD_GL_WIDGET_H

View File

@ -230,6 +230,7 @@ void PointCloudGLWidget::paintGL()
drawLineSegments();
drawBasicShapeSurfaces();
drawBasicShapeSegments();
drawBasicShapePoints();
drawPosePoints();
// 最后绘制坐标系指示器(覆盖在所有内容之上)
@ -454,6 +455,7 @@ void PointCloudGLWidget::clearPointClouds()
m_lineSegments.clear();
m_basicShapeSegments.clear();
m_basicShapeSurfaces.clear();
m_basicShapePoints.clear();
m_posePoints.clear();
m_minBound = QVector3D(-50, -50, -50);
m_maxBound = QVector3D(50, 50, 50);
@ -897,6 +899,10 @@ void PointCloudGLWidget::computeBoundingBox()
includePoint(surface.p4.x(), surface.p4.y(), surface.p4.z());
}
for (const BasicShapePoint& point : m_basicShapePoints) {
includePoint(point.x, point.y, point.z);
}
for (const PosePoint& pose : m_posePoints) {
const float scale = qMax(0.0f, pose.scale);
includePoint(pose.x - scale, pose.y - scale, pose.z - scale);
@ -1290,10 +1296,14 @@ void PointCloudGLWidget::mousePressEvent(QMouseEvent* event)
emit pointSelected(point);
if (m_selectedPoints.size() == 2) {
float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
}
if (m_selectedPoints.size() == 2) {
const float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
const float dx = m_selectedPoints[1].x - m_selectedPoints[0].x;
const float dy = m_selectedPoints[1].y - m_selectedPoints[0].y;
const float dz = m_selectedPoints[1].z - m_selectedPoints[0].z;
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
emit measurementFinished(m_selectedPoints[0], m_selectedPoints[1], distance, dx, dy, dz);
}
} else {
// 未启用测距:只保留一个点
m_selectedPoints.clear();
@ -1374,10 +1384,14 @@ void PointCloudGLWidget::mouseDoubleClickEvent(QMouseEvent* event)
emit pointSelected(point);
if (m_selectedPoints.size() == 2) {
float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
}
if (m_selectedPoints.size() == 2) {
const float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
const float dx = m_selectedPoints[1].x - m_selectedPoints[0].x;
const float dy = m_selectedPoints[1].y - m_selectedPoints[0].y;
const float dz = m_selectedPoints[1].z - m_selectedPoints[0].z;
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
emit measurementFinished(m_selectedPoints[0], m_selectedPoints[1], distance, dx, dy, dz);
}
} else {
// 未启用测距:只保留一个点
m_selectedPoints.clear();
@ -1607,10 +1621,23 @@ void PointCloudGLWidget::appendBasicShapeSurfaces(const QVector<SurfaceQuad>& su
update();
}
void PointCloudGLWidget::appendBasicShapePoints(const QVector<BasicShapePoint>& points)
{
if (points.isEmpty()) {
return;
}
for (const BasicShapePoint& point : points) {
m_basicShapePoints.append(point);
}
update();
}
void PointCloudGLWidget::clearBasicShapeSegments()
{
m_basicShapeSegments.clear();
m_basicShapeSurfaces.clear();
m_basicShapePoints.clear();
update();
}
@ -1659,11 +1686,29 @@ void PointCloudGLWidget::drawBasicShapeSegments()
{
drawLineSegmentList(m_basicShapeSegments);
}
void PointCloudGLWidget::drawLineSegmentList(const QVector<LineSegment>& segments)
{
if (segments.isEmpty()) {
return;
void PointCloudGLWidget::drawBasicShapePoints()
{
if (m_basicShapePoints.isEmpty()) {
return;
}
glDisable(GL_DEPTH_TEST);
for (const BasicShapePoint& point : m_basicShapePoints) {
glPointSize(point.pointSize);
glBegin(GL_POINTS);
glColor3f(point.r, point.g, point.b);
glVertex3f(point.x, point.y, point.z);
glEnd();
}
glEnable(GL_DEPTH_TEST);
glPointSize(m_pointSize);
}
void PointCloudGLWidget::drawLineSegmentList(const QVector<LineSegment>& segments)
{
if (segments.isEmpty()) {
return;
}
// 按线宽分组绘制

View File

@ -5,7 +5,7 @@ CONFIG -= qt
TARGET = CloudMathClac
INCLUDEPATH += $$PWD/Inc
INCLUDEPATH += $$PWD/../../../SDK/Device/VzNLSDK/Inc
INCLUDEPATH += $$PWD/../../../Device/SDK/VzNLSDK/Inc
INCLUDEPATH += $$PWD/../../../SDK/eigen-3.3.9
HEADERS += \
@ -14,7 +14,7 @@ HEADERS += \
SOURCES += \
Src/CurveFitting.cpp
# 平台特定配置
# 骞冲彴鐗瑰畾閰嶇疆
unix {
QMAKE_CXXFLAGS += -fPIC
}

View File

@ -3,9 +3,10 @@ TEMPLATE = subdirs
# 定义子项目
VrCommon.file = VrCommon/VrCommon.pro
VrUtils.file = VrUtils/VrUtils.pro
CloudUtils.file = CloudUtils/CloudUtils.pro
DataUtils.file = DataUtils/DataUtils.pro
CloudView.file = CloudView/CloudView.pro
CloudUtils.file = CloudUtils/CloudUtils.pro
DataUtils.file = DataUtils/DataUtils.pro
CloudView3D.file = CloudView3D/CloudView3D.pro
CloudView.file = CloudView/CloudView.pro
# 添加子项目
SUBDIRS += \
@ -14,12 +15,15 @@ SUBDIRS += \
CloudUtils \
DataUtils
win32-msvc {
SUBDIRS += CloudView
}
win32-msvc {
SUBDIRS += \
CloudView3D \
CloudView
}
# 设置依赖关系
VrUtils.depends = VrCommon
CloudUtils.depends = VrCommon VrUtils
DataUtils.depends = VrCommon
CloudView.depends = VrCommon VrUtils CloudUtils
DataUtils.depends = VrCommon
CloudView3D.depends = VrCommon VrUtils
CloudView.depends = VrCommon VrUtils CloudUtils CloudView3D

File diff suppressed because it is too large Load Diff