目录

辅助线的概念

绘制线规则

捕捉辅助线的思路

生成辅助线的思路

总结


在实际绘制几何图形过程时,有几个工具比较实用:

  1. 鼠标绘制时,焦点捕捉已绘制图形的端点、线段上;
  2. 撤销与回退
  3. 辅助线

焦点捕捉的功能的思路相对比较简单,不断地比较当前鼠标所在的屏幕像素点为圆心,R为半径的搜索圆与绘制图形的端点和线段是否相交的问题。但在实时的图形编程的难点在于细节,至于如何优化搜索的速度,有很多方法,涉及比较深的图形搜索方法,比如对所有的图形空间先建立R树空间索引,这里不做详细介绍。

撤销与回退的功能则更加简单,其实就是状态管理,入栈和出栈的问题。

接下来,我们讲述一下在绘制过程中是辅助线的实现思路

辅助线的概念

辅助线分为静态辅助线和动态辅助线。

  • 静态辅助线是指一直摆放在屏幕上不动的辅助线,鼠标能捕捉到上面,比如PS的辅助线。

  • 动态辅助线是指在绘制过程中,根据已绘制的内容,智能的生成辅助线,并能让鼠标捕捉到上面

绘制线规则

  1. 鼠标在移动过程中,在指定的阈值范围内能捕捉到辅助线上的最近的点
  2. 每次绘制端点后,能够根据新生成的端点,生成一条相同方向和正交(相互垂直)的辅助线(根据需要生成指定角度的辅助线)
  3. 在绘制完成后,清除辅助线。

捕捉辅助线的思路

鼠标在屏幕移动过程中,在辅助线附近时,能够捕捉到辅助线是指鼠标的绘制焦点能自动移动到辅助线上,实际移动的是绘制焦点而不是鼠标点。而实现捕捉到辅助线,需要以下两步:

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]}
}

总结

绘制辅助线是一个实时性比较强的功能,原理是简单的几何拓扑知识。实现起来比较简单,几何的实时拓扑难点在于性能调优,选择合适的策略去实现功能。

绘制几何图形,生成辅助线的思路相关推荐

  1. 【OpenCV 4开发详解】图像上绘制几何图形

    本文首发于"小白学视觉"微信公众号,欢迎关注公众号 本文作者为小白,版权归人民邮电出版社发行所有,禁止转载,侵权必究! 经过几个月的努力,小白终于完成了市面上第一本OpenCV 4 ...

  2. Win10系列:VC++绘制几何图形2

    新建了Direct2D中的资源后,接下来初始化用于绘制图形的应用窗口.在解决方案资源管理器窗口中右键点击项目图标,在弹出的菜单栏中选中"添加", 并在"添加"的 ...

  3. 史上最简洁C# 生成条形码图片思路及示例分享

    这篇文章主要介绍了史上最简洁C# 生成条形码图片思路及示例分享,需要的朋友可以参考下 在网上看到一些人写关于条形码的代码都很长,有的甚至拿来卖,所以查了下资料,希望能对大家有帮助. 我的实现原理是: ...

  4. 百度地图js版api绘制几何图形覆盖物并保存数据库

    百度地图js版api绘制几何图形覆盖物并保存数据库 你好!相信你看到这边文章的时候,我的方案,已经满足了你的需求: 1,在html5上调用web版百度地图api,绘制多边形,我项目的是绘制小区. 2, ...

  5. 三线表是什么?R语言使用table1包绘制(生成)三线表、构建不分层的三线表

    三线表是什么?R语言使用table1包绘制(生成)三线表.构建不分层的三线表 目录

  6. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、编写自定义函数在三线表中添加p值

    R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.编写自定义函数在三线表中添加p值 目录

  7. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、设置transpose参数转置三线表、变量作为列,子组(strata)作为行

    R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.设置transpose参数转置三线表.变量作为列,子组(strata)作为行 目录

  8. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、自定义overall的标签名称

    R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.自定义overall的标签名称 目录

  9. R语言使用table1包绘制(生成)三线表、使用单变量分列构建三线表、为指定变量添加单位信息、自定义overall的标签名称

    R语言使用table1包绘制(生成)三线表.使用单变量分列构建三线表.为指定变量添加单位信息.自定义overall的标签名称 目录

最新文章

  1. 开始阅读 深入理解计算机系统
  2. php oauth 服务端,OAuth 2.0 PHP客户端和服务器示例
  3. jsch连接mysql_求用jsch网络工具包通过ssh连接远程oracle数据库并发送sql操作语句(数据库在unix上)java代码例子...
  4. docker常用命令,安装常用实例,一步式安装mysql
  5. Springboot 配置类( @Configuration) 不能使用@Value注解从application.propertyes中加载值以及Environment为null解决方案
  6. Objective-C依然占C位,Swift和SwiftUI在iOS 15中的使用情况
  7. log4j.xml的实用例子
  8. android studio 快捷键修改
  9. 安装rpm包时提示错误:依赖检测失败
  10. 到底要学前端还是后端?
  11. 【RT-Thread】nxp rt10xx 设备驱动框架之--adc搭建和使用
  12. 有关安装vuex报错error found vue@2.6.14及有关vue搭建项目问题
  13. Win10怎么卸载有问题的更新补丁
  14. Spring Security教程
  15. 电脑摄像头未能创建连接服务器,电脑提示未能创建视频预览,请检查设备连接的原因及解决办法...
  16. 基于Slim微型框架实现强大的API—— Slim入门篇
  17. js手机号码校验,邮箱校验
  18. ILLUM 光场相机矫正
  19. 20181218股市早盘
  20. 同在Google工作,薪资差别怎么那么大!

热门文章

  1. Android 8.0 手机亮灭屏
  2. cobbler简单入门
  3. python学习(列表,元祖)
  4. c语言小游戏——弹跳的小球和简单的飞机游戏
  5. 阿里云认证有用吗?阿里云证书含金量及如何获得
  6. wps图片与图片间距怎么调整_wps图片与图片间距怎么调整_微信图文排版,字间距,行间距,怎么调整合适?......
  7. 【肝帝一周总结:全网最全最细】十万字python教程,学不会找我!教到你会为止!!内容超多,建议收藏慢慢看!
  8. 大学生生活中的三大痛点
  9. Codeforces 802 补题
  10. Office365离线安装包