VTK|类似CloudCompare的比例尺实现1-源码分析

news2025/7/19 5:54:13

文章目录

  • CloudCompare源码分析
    • void ccGLWindowInterface::drawScale(const ccColor::Rgbub& color)
      • 🧩 总体功能
      • 🧠 函数逐步解析
        • ✅ 1. 断言只在正交模式下使用
        • ✅ 2. 计算显示的实际长度
        • ✅ 3. 字体和图形区域准备
        • ✅ 4. 计算比例尺图形的绘制位置
        • ✅ 5. 绘制比例尺线段和端点刻度
        • ✅ 6. 绘制文字标签(比如 “10”)
      • 🧾 总结
      • ✅ 推荐你关注的点
    • double ccGLWindowInterface::computeActualPixelSize() const
      • ✅ 函数功能概述
      • 🧠 函数逐行解析
      • 🔁 举个例子说明
      • ✅ 总结
    • inline double RoundScale(double equivalentWidth)
    • ✅ 函数功能概述
      • 🧠 逐行详解
        • 1. 计算数量级 `k`
        • 2. 计算“粒度”或跳变步长
        • 3. 将输入值按 granularity 向下舍入
      • ✅ 举例
      • 🔧 总结

CloudCompare 的比例尺(Scale Bar)功能非常直观实用,它的实现具有以下几个关键点。下面将从 设计思想实现方式 两个方面详细说明,并与我们当前的实现思路进行对比,便于你优化自己的系统。


CloudCompare源码分析

源码链接:https://github.com/CloudCompare/CloudCompare
找到对应添加比例尺相关函数代码libs->qCC_glWindow->src->ccGLWindowInterface.cpp

void ccGLWindowInterface::drawScale(const ccColor::Rgbub& color)

在这里插入图片描述

void ccGLWindowInterface::drawScale(const ccColor::Rgbub& color)
{
	// 断言只在正交模式下使用
	assert(!m_viewportParams.perspectiveView); //a scale is only valid in ortho. mode!
	// 比例尺最大占屏幕宽度25%
	float scaleMaxW = glWidth() / 4.0f; //25% of screen width
	 // 1像素对应的真实空间单位长度(如米)
	double pixelSize = computeActualPixelSize();
	// 将比例尺最大宽度(25% 屏幕)转换成实际长度(单位:米),并做了“美化舍入”,如 9.4 -> 10
	//we first compute the width equivalent to 25% of horizontal screen width
	//(this is why it's only valid in orthographic mode !)
	double equivalentWidth = RoundScale(scaleMaxW * pixelSize);
	// 字体和图形区域准备
	QFont font = getTextDisplayFont(); //we take rendering zoom into account!
	QFontMetrics fm(font); // 用于测量字体尺寸
	// 计算比例尺图形的绘制位置
	//we deduce the scale drawing width
	float scaleW_pix = static_cast<float>(equivalentWidth / pixelSize);// 实际长度转换成像素长度
	float trihedronLength = computeTrihedronLength();// 屏幕上小坐标轴的长度
	float dW = 2.0f * trihedronLength + 20.0f * m_captureMode.zoomFactor;
	float dH = std::max(fm.height() * 1.25f, trihedronLength + 5.0f * m_captureMode.zoomFactor);
	float w = glWidth() / 2.0f - dW;
	float h = glHeight() / 2.0f - dH;
	float tick = 3.0f * m_captureMode.zoomFactor;

	ccQOpenGLFunctions* glFunc = functions();
	assert(glFunc);

	//force line width
	glFunc->glPushAttrib(GL_LINE_BIT);
	glFunc->glLineWidth(1.0f);

	// 绘制比例尺线段和端点刻度
	//scale OpenGL drawing
	glColor3ubv_safe<ccQOpenGLFunctions>(glFunc, color);
	glFunc->glBegin(GL_LINES);
	// 主线段
	glFunc->glVertex3f(w - scaleW_pix, -h, 0.0f);
	glFunc->glVertex3f(w, -h, 0.0f);
	// 左刻度
	glFunc->glVertex3f(w - scaleW_pix, -h - tick, 0.0f);
	glFunc->glVertex3f(w - scaleW_pix, -h + tick, 0.0f);
	// 右刻度
	glFunc->glVertex3f(w, -h + tick, 0.0f);
	glFunc->glVertex3f(w, -h - tick, 0.0f);
	glFunc->glEnd();

	glFunc->glPopAttrib(); //GL_LINE_BIT

	// display label 绘制文字标签(比如 “10”)
	double textEquivalentWidth = RoundScale(scaleMaxW * pixelSize);
	QString text = QString::number(textEquivalentWidth);
	glColor3ubv_safe<ccQOpenGLFunctions>(glFunc, color);
	renderText(	glWidth() - static_cast<int>(scaleW_pix / 2 + dW) - fm.width(text) / 2,
				glHeight() - static_cast<int>(dH / 2) + fm.height() / 3,
				text,
				static_cast<uint16_t>(RenderTextReservedIDs::ScaleLabel),
				font);
}

这个函数 ccGLWindowInterface::drawScale() 是 CloudCompare 中 绘制比例尺(Scale Bar) 的主要函数。比例尺通常显示在右下角,仅在**正交视图(Orthographic View)**下启用,用于提示用户当前显示视图的空间尺度。


🧩 总体功能

绘制一个代表实际长度的线段比例尺(如 10m、100m 等),并在旁边绘制文字标签标注其长度。


🧠 函数逐步解析

✅ 1. 断言只在正交模式下使用
assert(!m_viewportParams.perspectiveView); // only valid in ortho. mode!

比例尺仅在正交投影下有效,透视模式下尺寸不再固定,比例尺无意义。


✅ 2. 计算显示的实际长度
float scaleMaxW = glWidth() / 4.0f; // 比例尺最大占屏幕宽度25%
double pixelSize = computeActualPixelSize(); // 1像素对应的真实空间单位长度(如米)
double equivalentWidth = RoundScale(scaleMaxW * pixelSize);
  • pixelSize: 当前视图下 1 像素等于多少“实际单位”(比如米)。
  • equivalentWidth: 将比例尺最大宽度(25% 屏幕)转换成实际长度(单位:米),并做了“美化舍入”,如 9.4 -> 10。

✅ 3. 字体和图形区域准备
QFont font = getTextDisplayFont();
QFontMetrics fm(font); // 用于测量字体尺寸

✅ 4. 计算比例尺图形的绘制位置
float scaleW_pix = equivalentWidth / pixelSize; // 实际长度转换成像素长度
float trihedronLength = computeTrihedronLength(); // 屏幕上小坐标轴的长度
float dW = 2.0f * trihedronLength + 20.0f * m_captureMode.zoomFactor;
float dH = max(fm.height() * 1.25f, trihedronLength + 5.0f * zoomFactor);
float w = glWidth() / 2.0f - dW;
float h = glHeight() / 2.0f - dH;
  • scaleW_pix: 比例尺显示为多长(像素数)。
  • w, h: 比例尺右端点坐标,一般在窗口右下角往左上偏移。
  • dW, dH: 距离边缘和文字的位置偏移量。

✅ 5. 绘制比例尺线段和端点刻度
glFunc->glBegin(GL_LINES);
// 主线段
glVertex3f(w - scaleW_pix, -h, 0.0f);
glVertex3f(w, -h, 0.0f);
// 左刻度
glVertex3f(w - scaleW_pix, -h - tick, 0.0f);
glVertex3f(w - scaleW_pix, -h + tick, 0.0f);
// 右刻度
glVertex3f(w, -h + tick, 0.0f);
glVertex3f(w, -h - tick, 0.0f);
glFunc->glEnd();

绘制了如下图形:

 |     ------------     |
 ^ 左端点     ^ 右端点

✅ 6. 绘制文字标签(比如 “10”)
QString text = QString::number(textEquivalentWidth);
renderText(..., text, ..., font);

将比例尺的实际长度以文字形式显示在比例尺下方居中位置。


🧾 总结

功能实现
只在正交视图下启用assert(!m_viewportParams.perspectiveView)
以屏幕宽度 25% 为上限,计算比例尺真实长度scaleMaxW * pixelSize
比例尺线段与刻度绘制OpenGL glBegin(GL_LINES)...
长度文字标签绘制renderText(...)
缩放自适应pixelSizezoomFactor 控制

✅ 推荐你关注的点

  • RoundScale():控制比例尺长度的跳变规则(如 1/2/5/10/…)
  • computeActualPixelSize():控制比例尺单位与视图缩放的匹配方式
  • renderText():文字绘制细节,是否自动适配 DPI

double ccGLWindowInterface::computeActualPixelSize() const


double ccGLWindowInterface::computeActualPixelSize() const
{
	double pixelSize = m_viewportParams.computePixelSize(glWidth()); // we now use the width as the driving dimension for scaling

	// but we have to compensate for the aspect ratio is h > w
	double ar = static_cast<double>(glHeight()) / glWidth();
	if (ar > 1.0)
	{
		pixelSize *= ar;
	}

	return pixelSize;

}

你贴出的 ccGLWindowInterface::computeActualPixelSize() 是 CloudCompare 中**计算当前视图下每个像素所代表的真实空间长度(单位:米或其他单位)**的函数,是比例尺绘制逻辑的核心支撑之一。


✅ 函数功能概述

这个函数返回一个 pixelSize 值,即 “一个屏幕像素在当前视图中对应的实际长度(单位:米)”。这个值用于:

  • 比例尺长度的换算(实际长度 ↔ 屏幕像素长度)
  • label/文本等图元的缩放保持一致性

🧠 函数逐行解析

double pixelSize = m_viewportParams.computePixelSize(glWidth());
  • 使用 viewport 参数中的方法,传入当前窗口宽度(像素),计算当前视图下的单位像素长度
  • m_viewportParamsccViewportParameters 类型,表示当前视角的投影和缩放状态。
  • 默认使用 窗口宽度(X方向) 作为基础缩放维度。

double ar = static_cast<double>(glHeight()) / glWidth();
if (ar > 1.0)
{
	pixelSize *= ar;
}
  • 这一步是为了补偿高宽比不为1时的缩放失真

    • 当视图是竖屏(高度 > 宽度)时,Y方向单位会拉伸,比例尺就可能被拉短。
    • 通过乘上 aspectRatio = height / width,来补正这种拉伸,使得比例尺长度不变形。

🔁 举个例子说明

假设窗口是 800x600:

  • pixelSize = m_viewportParams.computePixelSize(800):表示800像素宽对应的实际场景宽度(如20米),那 1 像素就是 0.025米。
  • 如果是竖屏(高大于宽,比如 1200x800),Y方向会被压缩,因此乘以 1.5 来修正,让比例尺视觉效果正确。

✅ 总结

作用说明
控制比例尺精度决定 1 像素对应多少实际长度
正交视图缩放适配保证不同窗口尺寸、缩放下比例尺始终反映真实比例
支持非等比例窗口高宽比校正,避免变形显示

inline double RoundScale(double equivalentWidth)


inline double RoundScale(double equivalentWidth)
{
	//we compute the scale granularity (to avoid width values with a lot of decimals)
	int k = static_cast<int>(std::floor(std::log(equivalentWidth) / std::log(10.0f)));
	double granularity = std::pow(10.0, static_cast<double>(k)) / 2.0;
	//we choose the value closest to equivalentWidth with the right granularity
	return std::floor(std::max(equivalentWidth / granularity, 1.0))*granularity;
}

RoundScale() 是 CloudCompare 中用于**“美化”比例尺长度值**的函数,它会将任意的实际长度(如 92.342 米)舍入到一个“看起来舒服”的值(如 100 米)。


✅ 函数功能概述

inline double RoundScale(double equivalentWidth)

这个函数的目的:

  • equivalentWidth(比如 92.3 米)舍入为一个人类友好的、整洁的数值
  • 保证比例尺看起来是 10/20/50/100/500 这种“好看的刻度”

🧠 逐行详解

1. 计算数量级 k
int k = static_cast<int>(std::floor(std::log(equivalentWidth) / std::log(10.0f)));
  • 这是在算 equivalentWidth 的数量级(10 的几次方):

    • equivalentWidth = 92.3,则 log10(92.3) ≈ 1.965floor(...) = 1,说明是两位数 ≈ 10¹。
    • equivalentWidth = 734,则 k = 2(因为 10² = 100)。

2. 计算“粒度”或跳变步长
double granularity = std::pow(10.0, static_cast<double>(k)) / 2.0;
  • 使用 10 的 k 次方除以 2 得到跳变粒度:

    • 对于 92.3,k = 110¹ / 2 = 5.0
    • 对于 734,k = 2100 / 2 = 50.0
  • 这就让比例尺的跳变是:

    • 单位范围 10~100:每 5 增长(10, 15, 20…)
    • 单位范围 100~1000:每 50 增长(100, 150, 200…)

3. 将输入值按 granularity 向下舍入
return std::floor(std::max(equivalentWidth / granularity, 1.0)) * granularity;
  • equivalentWidth / granularity 得到倍数(如 92.3 / 5 = 18.46)

  • std::floor(...) * granularity → 向下舍入为最近的 granularity 倍数:

    • 92.3 → 18 * 5 = 90
    • 734 → 14 * 50 = 700
  • std::max(..., 1.0) 确保不会出现 0 值


✅ 举例

输入值 (equivalentWidth)粒度 (granularity)最终结果 (RoundScale)
92.3590
73450700
0.830.50.5
0.040.050.05

🔧 总结

目的实现
美化比例尺数字按数量级推导跳变步长
保证整齐可读避免像 92.387 这样的杂数
自适应范围自动在 0.05、0.5、5、50、500 之间切换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2376492.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电子电器架构 --- 车载以太网拓扑

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…

phpstorm2024.3 设置中文

要在 PhpStorm 2024.3 中设置中文界面&#xff0c;你可以按照以下步骤进行操作。请注意&#xff0c;PhpStorm 2024.3 版本可能已经包括了中文语言包&#xff0c;但如果你使用的是较早的版本&#xff0c;可能需要下载额外的语言包。 方法一&#xff1a;直接在设置中切换&#x…

vxe-table 同时实现合并单元格与任意列展开行

前一段时间有一个需求&#xff0c;要求既要合并单元格&#xff0c;又要实现树状图的效果&#xff0c;但是展开节点tree-node 可以放在非第一列的任意位置&#xff0c;Vxe-table可以实现如下是效果图&#xff1a; 大家可以一起交流学习&#xff01; ~重点注意事项&#xff1a;…

ArcGIS Desktop使用入门(二)常用工具条——图形

系列文章目录 ArcGIS Desktop使用入门&#xff08;一&#xff09;软件初认识 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——标准工具 ArcGIS Desktop使用入门&#xff08;二&#xff09;常用工具条——编辑器 ArcGIS Desktop使用入门&#xff08;二&#x…

神经网络语言模型(前馈神经网络语言模型)

神经网络语言模型 什么是神经网络&#xff1f;神经网络的基本结构是什么&#xff1f;输入层隐藏层输出层 神经网络为什么能解决问题&#xff1f;通用近似定理为什么需要权重和偏置&#xff1f;为什么需要激活函数&#xff1f;权重是如何确定的&#xff1f;1. 穷举2. 反向传播主…

CUDA编程——性能优化基本技巧

本文主要介绍下面三种技巧&#xff1a; 使用 __restrict__ 让编译器放心地优化指针访存想办法让同一个 Warp 中的线程的访存 Pattern 尽可能连续&#xff0c;以利用 Memory coalescing使用 Shared memory 0. 弄清Kernael函数是Compute-bound 还是 Memory-bound 先摆出一个知…

道通EVO MAX系列无人机-支持二次开发

道通EVO MAX系列无人机-支持二次开发 EVO Max 系列采用Autel Autonomy自主飞行技术&#xff0c;实现复杂环境下的全局路径规划、3D场景重建、自主绕障和返航&#xff1b;高精度视觉导航能力&#xff0c;使其在信号干扰强、信号遮挡、信号弱等复杂环境下&#xff0c;依然获得高精…

计算机网络-MPLS LDP基础实验配置

前面我们学习了LDP的会话建立、标签发布与交换、LDP的工作原理&#xff0c;今天通过一个基础实验来加深记忆。 一、LDP基础实验 实验拓扑&#xff1a; 1、IGP使用OSPF进行通告&#xff0c;使用Lookback接口作为LSR ID&#xff0c;LDP ID自动生成。 2、实验目的&#xff1a;使…

HPE ProLiant DL360 Gen11 服务器,配置 RAID 5 教程!

今天的任务&#xff0c;是帮客户的一台HPE ProLiant DL360 Gen11 服务器&#xff0c;配置RAID 5。依然是按照我的个人传统习惯&#xff0c;顺便做一个教程&#xff0c;分享给有需要的粉丝们。如果你在实际操作中&#xff0c;遇到了什么问题&#xff0c;欢迎在评论区留言&#x…

SARIMA-LSTM融合模型对太阳黑子数量预测分析|附智能体数据代码

全文智能体链接&#xff1a;https://tecdat.cn/?p41969 分析师&#xff1a;Peng Fan 本研究以太阳黑子活动数据为研究对象&#xff0c;旨在帮助客户探索其未来走势并提供预测分析。首先&#xff0c;通过对数据的清洗和处理&#xff0c;包括离群值的识别与处理以及时间序列的建…

C# WinForm DataGridView 非常频繁地更新或重新绘制慢问题及解决

非常频繁地更新 DataGridView问题描述&#xff1a; 在 C# 中无法在合理的时间内刷新我的 DataGridView &#xff0c;我每秒通过网络发送 20 个数据包&#xff0c;获取数据。我想解析这些数据并将其放入 DataGridView 中。我还想调整 DataGridView 的更新间隔&#xff0c;从 0.1…

【数据结构】红黑树(C++)

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树结点定义 四、红黑树的操作 1. 插入操作 1.1 插入过程 1.2 调整过程 1.2.1 叔叔节点存在且为红色 1.2.2 叔叔节点存在且为黑色 1.2.3 叔叔节点不存在 2. 查找操作 2.1 查找逻辑 2.2 算法流程图 2.3 使用示例 …

Android Framework学习五:APP启动过程原理及速度优化

文章目录 APP启动优化概述APP启动流程点击图片启动APP的过程启动触发Zygote 与应用进程创建Zygote进程的创建应用进程初始化 ApplicationActivity 启动与显示 优化启动时黑白屏现象可优化的阶段Application阶段相关优化 Activity阶段数据加载阶段 Framework学习系列文章 APP启动…

Meta的AIGC视频生成模型——Emu Video

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video&#xff0c;作为Meta发布的第二款视频生成模型&#xff0c;在视频生成领域发挥关键作用。 &#x1f33a;优质专栏回顾&am…

Axure难点解决分享:统计分析页面引入Echarts示例动态效果

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:统计分析页面引入Echarts示例动态效果 主要内容:echart示例引入、大小调整、数据导入 应用场景:统计分析页面…

Docker 常见问题及其解决方案

一、安装与启动问题 1.1 安装失败 在不同操作系统上安装 Docker 时&#xff0c;可能会出现安装失败的情况。例如&#xff0c;在 Ubuntu 系统中&#xff0c;执行安装命令后提示依赖缺失。这通常是因为软件源配置不正确或系统缺少必要的依赖包。 解决方案&#xff1a; 确保系统…

IC解析之TPS92682-Q1(汽车LED灯控制IC)

目录 1 IC特性介绍2 主要参数3 接口定义4 工作原理分析TPS92682-Q1架构工作模式典型应用通讯协议 控制帧应答帧协议5 总结 1 IC特性介绍 TPS92682 - Q1 是德州仪器&#xff08;TI&#xff09;推出的一款双通道恒压横流控制器&#xff0c;同时还具有各种电器故障保护&#xff0c…

6.01 Python中打开usb相机并进行显示

本案例介绍如何打开USB相机并每隔100ms进行刷新的代码,效果如下: 一、主要思路: 1. 打开视频流、读取帧 self.cam_cap = cv2.VideoCapture(0) #打开 视频流 cam_ret, cam_frame = self.cam_cap.read() //读取帧。 2.使用定时器,每隔100ms读取帧 3.显示到Qt的QLabel…

2023华为od统一考试B卷【二叉树中序遍历】

前言 博主刷的华为机考题&#xff0c;代码仅供参考&#xff0c;因为没有后台数据&#xff0c;可能有没考虑到的情况 如果感觉对你有帮助&#xff0c;请点点关注点点赞吧&#xff0c;谢谢你&#xff01; 题目描述 思路 0.用Character数组存储树&#xff0c;index下标的左右…

在Spark搭建YARN

&#xff08;一&#xff09;什么是SparkONYarn模式 Spark on YARN&#xff08;Yet Another Resource Negotiator&#xff09;是 Spark 框架在 Hadoop 集群中运行的一种部署模式&#xff0c;它借助 Hadoop YARN 来管理资源和调度任务。 架构组成 ResourceManager&#xff1a;作…