绘制几何图形,生成辅助线的思路
目录
辅助线的概念
绘制线规则
捕捉辅助线的思路
生成辅助线的思路
总结
在实际绘制几何图形过程时,有几个工具比较实用:
- 鼠标绘制时,焦点捕捉已绘制图形的端点、线段上;
- 撤销与回退
- 辅助线
焦点捕捉的功能的思路相对比较简单,不断地比较当前鼠标所在的屏幕像素点为圆心,R为半径的搜索圆与绘制图形的端点和线段是否相交的问题。但在实时的图形编程的难点在于细节,至于如何优化搜索的速度,有很多方法,涉及比较深的图形搜索方法,比如对所有的图形空间先建立R树空间索引,这里不做详细介绍。
撤销与回退的功能则更加简单,其实就是状态管理,入栈和出栈的问题。
接下来,我们讲述一下在绘制过程中是辅助线的实现思路
辅助线的概念
辅助线分为静态辅助线和动态辅助线。
- 静态辅助线是指一直摆放在屏幕上不动的辅助线,鼠标能捕捉到上面,比如PS的辅助线。
- 动态辅助线是指在绘制过程中,根据已绘制的内容,智能的生成辅助线,并能让鼠标捕捉到上面
绘制线规则
- 鼠标在移动过程中,在指定的阈值范围内能捕捉到辅助线上的最近的点
- 每次绘制端点后,能够根据新生成的端点,生成一条相同方向和正交(相互垂直)的辅助线(根据需要生成指定角度的辅助线)
- 在绘制完成后,清除辅助线。
捕捉辅助线的思路
鼠标在屏幕移动过程中,在辅助线附近时,能够捕捉到辅助线是指鼠标的绘制焦点能自动移动到辅助线上,实际移动的是绘制焦点而不是鼠标点。而实现捕捉到辅助线,需要以下两步:
1. 计算当前绘制焦点与辅助线的最短距离是否少于定义的阈值A(A是一个相对于比较小的值),若少于阈值A,则绘制焦点设置为辅助线上离当前绘制焦点最近的点。
因此,关键的地方,在于获取点到线段的最近的点,以及点与点之间的距离。核心代码如下:
/*** @desc 此方法既获取点到多段连续的线段中最近的点,以及返回最短的距离* @param {Array<number>} coordinates 多段连续的线段的坐标数组.[x1,y1,x2,y2,x3,y3...]* @param {number} offset 起始点的索引偏移量.* @param {number} end 终点的索引.* @param {number} stride 坐标所占的个数,二维图形为2.* @param {number} maxDelta 多段连续的线段中每两个相邻点中最长的那一段的距离的平方.* @param {boolean} isRing 是否是环, 线段是否闭合.* @param {number} x 目标点的x坐标.* @param {number} y 目标点的y坐标* @param {Array<number>} closestPoint 输出的最近的点的对象.* @param {number} [minSquaredDistance=Infinity] 最小的垂直距离,用于较少比较.默认Infinity* @return {number} 点到多段连续的线段中最短的距离.*/
function assignClosestPoint(coordinates, offset, end, stride, maxDelta, isRing, x, y, closestPoint, minSquaredDistance) {if (offset == end) { // 如果起点的索引和终点索引的一致,表示参数错误return minSquaredDistance;}let i, squaredDistance;if (maxDelta === 0) { // 如果最大的相邻点间的距离为0,表示线段各个点为同一个点// 所有点都相同,所以只需测试第一点squaredDistance = squaredDx( // squaredDx方法获取两点之间的垂直距离的平方x, y, coordinates[offset], coordinates[offset + 1]);if (squaredDistance < minSquaredDistance) { // 如果绘制焦点与线段第一个点的距离少于定义的最小距离,则最近的点为线段的第一个点for (i = 0; i < stride; ++i) {closestPoint[i] = coordinates[offset + i];}closestPoint.length = stride;return squaredDistance;} else {return minSquaredDistance;}}const tmpPoint = [NaN, NaN]; // 用于临时存储点到线段的最近的点let index = offset + stride;while (index < end) {assignClosest( // assignClosest方法是获取点到其中一段线段(只有两个坐标构成的线段)coordinates, index - stride, index, stride, x, y, tmpPoint); squaredDistance = squaredDx(x, y, tmpPoint[0], tmpPoint[1]); //得到目标点到任意一个线段的最短距离的平方if (squaredDistance < minSquaredDistance) {minSquaredDistance = squaredDistance;for (i = 0; i < stride; ++i) {closestPoint[i] = tmpPoint[i];}closestPoint.length = stride;index += stride;} else {// 跳过多个点,因为我们知道所有跳过的点都不能比我们找到的最近点更近。我们知道这是因为我们知// 道当前点有多近,到目前为止我们找到的最近点有多近,以及连续点之间的最大距离。例如,如果我// 们当前的最近点距离是10,那么到目前为止我们发现的最好的距离是3,并且连续点之间的最大距离是2,// 那么我们需要跳过至少(10-3)/2==3(向下取整)点,以便有机会找到更近的点。我们使用// math.max(…,1)来确保我们总是前进至少一个点,以避免无限循环。.index += stride * Math.max(((Math.sqrt(squaredDistance) -Math.sqrt(minSquaredDistance)) / maxDelta) | 0, 1);}}if (isRing) {// 如果是环的话,则还需要检查最后的闭合的那一段线段assignClosest(coordinates, end - stride, offset, stride, x, y, tmpPoint);squaredDistance = squaredDx(x, y, tmpPoint[0], tmpPoint[1]);if (squaredDistance < minSquaredDistance) {minSquaredDistance = squaredDistance;for (i = 0; i < stride; ++i) {closestPoint[i] = tmpPoint[i];}closestPoint.length = stride;}}return minSquaredDistance;
}/*** @desc 计算距离* @param {*} p1 起始点* @param {*} p2 终结点*/function dist2D (p1, p2) {var dx = p1[0] - p2[0]var dy = p1[1] - p2[1]return Math.sqrt(dx * dx + dy * dy)}
2. 上述第一个点是针对,只有一条辅助线满足条件的情况。若存在,多条辅助线满足,绘制焦点到辅助线的距离少于阈值A,则取任意两条辅助的焦点作为最新的绘制焦点。(因为阈值A比较小,可以默认其差别不大)
关键在于,求取两个线段的交点,关键代码如下:
/*** @description 根据两条线段的端点,得到线段相交的点* @param {*} d1 线段1的两个端点坐标* @param {*} d2 线段2的两个端点坐标*/function getIntersectionPoint (d1, d2) {var d1x = d1[1][0] - d1[0][0]var d1y = d1[1][1] - d1[0][1]var d2x = d2[1][0] - d2[0][0]var d2y = d2[1][1] - d2[0][1]var det = d1x * d2y - d1y * d2xif (det !== 0) {var k = (d1x * d1[0][1] - d1x * d2[0][1] - d1y * d1[0][0] + d1y * d2[0][0]) / detreturn [d2[0][0] + k * d2x, d2[0][1] + k * d2y]} else return false}
上述的距离,一般指的是屏幕像素坐标的距离。
最后,焦点不断捕捉的过程是实时的,因此,当鼠标移动过程中需要不断地执行上述的两个步骤。因此,调优是需要下点功夫的。
生成辅助线的思路
必须定义当绘制完成图形的某个端点时的回调方法。在回调方法中,其实根据已知的点坐标,计算辅助线的方程,并根据方程进行等距离的点插值。如下步骤:
1. 根据刚绘制的端点与上一个端点,则得到新的直线方程的斜率(辅助线是90度,45度或者延长线)及其经过的一个点(刚绘制的端点),得到辅助线方程
2. 得到直线方程后,根据直线的点向式的代数形式,以固定的距离进行插值,得到屏幕上的辅助线。
/** 新增辅助线
* @param {Array} v 坐标数组,只有两个断电
* @return {*} 辅助线对象
*/
function addGuide (v, ortho) {if (v) {const guideLength = Math.max(this.projExtent_[2] - this.projExtent_[0],this.projExtent_[3] - this.projExtent_[1])// 从定义的最大空间范围中获取辅助线的最大长度const extent = this.projExtent_// 获取最大空间范围const dx = v[0][0] - v[1][0] const dy = v[0][1] - v[1][1]const d = 1 / Math.sqrt(dx * dx + dy * dy)const generateLine = function (/** 方向 **/loopDir) {var p, g = []var loopCond = guideLength * loopDir * 2for (var i = 0; loopDir > 0 ? i < loopCond : i > loopCond; i += (guideLength * loopDir) / 4) { // 插值100个等距离的点if (ortho) p = [v[0][0] + dy * d * i, v[0][1] - dx * d * i]else p = [v[0][0] + dx * d * i, v[0][1] + dy * d * i]// 判断插值的点是否在最大的extent里if (containsCoordinate(extent, p)) g.push(p)else break}return new LineString([g[0], g[g.length - 1]]))}var f0 = generateLine(1)// 正向辅助线var f1 = generateLine(-1)// 反向辅助线return [f0, f1]}
}
总结
绘制辅助线是一个实时性比较强的功能,原理是简单的几何拓扑知识。实现起来比较简单,几何的实时拓扑难点在于性能调优,选择合适的策略去实现功能。
绘制几何图形,生成辅助线的思路相关推荐
- 【OpenCV 4开发详解】图像上绘制几何图形
本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...
- Win10系列:VC++绘制几何图形2
新建了Direct2D中的资源后,接下来初始化用于绘制图形的应用窗口.在解决方案资源管理器窗口中右键点击项目图标,在弹出的菜单栏中选中"添加", 并在"添加"的 ...
- 史上最简洁C# 生成条形码图片思路及示例分享
这篇文章主要介绍了史上最简洁C# 生成条形码图片思路及示例分享,需要的朋友可以参考下 在网上看到一些人写关于条形码的代码都很长,有的甚至拿来卖,所以查了下资料,希望能对大家有帮助. 我的实现原理是: ...
- 百度地图js版api绘制几何图形覆盖物并保存数据库
百度地图js版api绘制几何图形覆盖物并保存数据库 你好!相信你看到这边文章的时候,我的方案,已经满足了你的需求: 1,在html5上调用web版百度地图api,绘制多边形,我项目的是绘制小区. 2, ...
- 三线表是什么?R语言使用table1包绘制(生成)三线表、构建不分层的三线表
三线表是什么?R语言使用table1包绘制(生成)三线表.构建不分层的三线表 目录
- R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、编写自定义函数在三线表中添加p值
R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.编写自定义函数在三线表中添加p值 目录
- R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、设置transpose参数转置三线表、变量作为列,子组(strata)作为行
R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.设置transpose参数转置三线表.变量作为列,子组(strata)作为行 目录
- R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、自定义overall的标签名称
R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.自定义overall的标签名称 目录
- R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、为指定变量添加单位信息、自定义overall的标签名称
R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.为指定变量添加单位信息.自定义overall的标签名称 目录
最新文章
- 开始阅读 深入理解计算机系统
- php oauth 服务端,OAuth 2.0 PHP客户端和服务器示例
- jsch连接mysql_求用jsch网络工具包通过ssh连接远程oracle数据库并发送sql操作语句(数据库在unix上)java代码例子...
- docker常用命令,安装常用实例,一步式安装mysql
- Springboot 配置类( @Configuration) 不能使用@Value注解从application.propertyes中加载值以及Environment为null解决方案
- Objective-C依然占C位,Swift和SwiftUI在iOS 15中的使用情况
- log4j.xml的实用例子
- android studio 快捷键修改
- 安装rpm包时提示错误:依赖检测失败
- 到底要学前端还是后端?
- 【RT-Thread】nxp rt10xx 设备驱动框架之--adc搭建和使用
- 有关安装vuex报错error found vue@2.6.14及有关vue搭建项目问题
- Win10怎么卸载有问题的更新补丁
- Spring Security教程
- 电脑摄像头未能创建连接服务器,电脑提示未能创建视频预览,请检查设备连接的原因及解决办法...
- 基于Slim微型框架实现强大的API—— Slim入门篇
- js手机号码校验,邮箱校验
- ILLUM 光场相机矫正
- 20181218股市早盘
- 同在Google工作,薪资差别怎么那么大!
热门文章
- Android 8.0 手机亮灭屏
- cobbler简单入门
- python学习(列表,元祖)
- c语言小游戏——弹跳的小球和简单的飞机游戏
- 阿里云认证有用吗?阿里云证书含金量及如何获得
- wps图片与图片间距怎么调整_wps图片与图片间距怎么调整_微信图文排版,字间距,行间距,怎么调整合适?......
- 【肝帝一周总结:全网最全最细】十万字python教程,学不会找我!教到你会为止!!内容超多,建议收藏慢慢看!
- 大学生生活中的三大痛点
- Codeforces 802 补题
- Office365离线安装包