全方位讲解:鼠标框选图形功能的技术实现

这篇具有很好参考价值的文章主要介绍了全方位讲解:鼠标框选图形功能的技术实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

如果你正在考虑开发一款图形编辑软件,并希望能够支持常用的框选功能,本文将为你提供一些有用的信息。像下面动画所示那样快速选取单个或多个图形并非易事,尤其是还要考虑到像直线这样的图形有其特别之处。本文提出的解决方案主要是利用包围盒的概念,通过包围盒与包围盒重叠的检测判断图形是否被选中。此外,我们还会详细介绍针对直线的特殊情况该如何进行是否选中的检测,并给出一些关键算法的伪代码,帮助读者理解其实现原理。希望通过本文的讲解,你能够更好地理解框选功能的实现方法,并将其应用于你的应用程序中。

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript

包围盒重叠检测

如果你想让你的图形编辑软件具备框选功能,你可以考虑使用包围盒这一概念。所谓的包围盒是指覆盖所有图形的最小矩形区域。当用户选择某个图形时,程序会生成一个表示该图形包围盒的矩形区域。这种方法适用于大多数图形,但也有例外,例如直线。因为直线本身就没有宽度,所以使用包围盒的方法可能导致框选结果不够理想。在本文中,我们将详细解释如何更准确地框选直线。总的来说,包围盒是一种简单有效的框选方法,但对于直线这类特殊的图形,我们需要采取更为精细的方法来提高框选体验。

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript

对于实时框选功能,只需跟踪鼠标的按下和拖动事件,并计算出对应的包围盒即可,这样判断图形有没有被框选住就可以通过简单的判断两个包围盒有没有重叠就可以了。本文重点介绍框选检测部分的算法实现,不会涉及具体图形的绘制算法的讲解,其实只要掌握基本的图形(点、线、三角形、矩形等)绘制方法就可以了,被框选住的图形效果显示仍然还是基本图形的绘制,比如选中图形包围盒的顶点是由更小的矩形组合而成的,这里假定已经实现了基本图形的绘制。我们更多需要关注的是图形数据结构的表示、包围盒与包围盒重叠的检测、包围盒与直线重叠的检测。

判断两个包围盒A、B是否重叠比较简单,只要在x、y轴坐标上满足两个条件就说明是A和B是重叠的,即A.xmax >= B.xmin && B.xmax >= A.xmin,A.ymax >= B.ymin && B.ymax >= A.ymin。

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript

可以实现一个BoundingBox类,它提供isCollision方法用来检测两个包围盒是否重叠,另外提供一个merge方法来合并两个包围盒,在框选多个图形时需要处理多个包围盒的合并。下面是实现BoundingBox类的伪代码:

class BoundingBox {
    constructor(xmin, ymin, xmax, ymax) {
        this.xmin = xmin;
        this.ymin = ymin;
        this.xmax = xmax;
        this.ymax = ymax;
        this.width = xmax - xmin;
        this.height = ymax - ymin;
    }
	
    isCollision(box) {
        if ((this.xmax >= box.xmin && box.xmax > this.xmin
            && (this.ymax >= box.ymin && box.ymax > this.ymin) {
            return true;
        }
    }

    merge(box) {
        this.xmin = Math.min(this.xmin, box.xmin);
        this.ymin = Math.min(this.ymin, box.ymin);
        this.xmax = Math.max(this.xmax, box.xmax);
        this.ymax = Math.max(this.ymax, box.ymax);
        this.width = Math.abs(this.xmax - this.xmin;
        this.height = Math.abs(this.ymax - this.ymin;
        return this;
    }
}

框选单个图形

首先,给出如下会使用到的对象及其功能:

  • 场景scene表示画布场景对象,scene.children是存放了所有图形对象的列表,scene.render方法负责绘制场景中的图形到canvas画布;

  • selector表示鼠标框选的区域对象,selector.start(x, y)用于鼠标首次点击跟踪鼠标位置,selector.moveTo(x, y)跟踪鼠标拖动的实时位置,selector.isSelected方法检查鼠标框选区域是否选中了图形;

  • 所有的图形对象都包含顶点信息,且都拥有一个getBoundingBox方法,用于生成图形的包围盒,生成包围盒的方法就是遍历其顶点找到(xmin, ymin)、(xmax, ymax);

1. 框选除直线外的其他图形检测方法
除直线外,判断某个图形是否被框选住的条件就是判断鼠标框选区域的包围盒和图形的包围盒是否重叠,这里直接给出伪代码:

class Selector {
    // 模拟鼠标按下开始启动选择
    start(x, y) {
        this._start = new Vector2(x, y);
    }

    // 鼠标最新位置
    moveTo(x, y) {
        this._end = new Vector2(x, y);
        // 更新鼠标选取的包围盒
        this.boundingBox = new BoundingBox(this._start, this._end);
    }

    // 鼠标选取是否选中图形
    isSelected(object) {
	    // 判断鼠标框选区域包围盒是否和图形的包围盒相交
		return this.boundingBox.isCollision(object.getBoundingBox());
	}
}
    
// 遍历场景中的图形
for (let i = 0; i < scene.children.length; i++) {
	let shape = scene.children[i];
	// 检查图形的包围盒是否和鼠标框选区域重叠
	if (selector.isSelected(shape)) {
		// 鼠标框选住图形
		shape.select(); // 设置shape.isSelected为true
	} else {
		shape.unselect(); // 设置shape.isSelected为false
	}
}

被框选中的图形将调用select方法将图形对象的isSelected状态置为true,否则置为false,scene.render方法中会检查每个图形的选中状态并进行选中效果的绘制。

2. 框选直线
接下来分析直线被选中的条件,根据下面的示意图可以得出直线被框选住必须同时满足以下三个条件:

  • 框选矩形的四个顶点不能同时在直线的同一侧,也就是说至少一个点在直线上或直线的另一侧,可以通过把矩形的四个顶点代入直线方程通过正负号进行判断是否分布在两侧;

  • 矩形的xmin必须小于等于直线的xmax,且矩形的xmax必须大于等于直线的xmin;

  • 矩形的ymin必须小于等于直线的ymax,且矩形的ymax必须大于等于直线的ymin;

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript

不难测试出以上条件针对任意被框选住的情况都是适用的,而不仅仅上面示意图中的情形。加入了框选直线之后,selector.isSelected方法的代码需要稍作改动以适配框选图形是直线的情况。

class Selector {
    // 模拟鼠标按下开始启动选择
    start(x, y) {
        this._start = new Vector2(x, y);
    }

    // 鼠标最新位置
    moveTo(x, y) {
        this._end = new Vector2(x, y);
        // 更新鼠标选取的包围盒
        this.boundingBox = new BoundingBox(this._start, this._end);
    }

    // 鼠标选取是否选中图形
    isSelected(object) {
		// 直线不使用包围盒判断
		if (object.type == "line") {
			// 获取直线方程,它是一个方法,输入参数为坐标(x, y),实现代码在下面给出
			let func_line = object.lineEquation();
			// 返回矩形的四个顶点
			let box_points = this.boundingBox.getBoxPoints();
			let result;
			// 遍历矩形的四个顶点,判断是否分布在直线两侧
			for (let i = 0; i < box_points.length; i++) {
				// 只存储直线方程求值的正负号
				let res = func_line(box_points[i].x, box_points[i].y) >= 0;
				if (i == 0) {
					result = res;
				} else if (res != result) {
					// res和result不同表示已经有一个点在直线另一侧
					let xmin = this.boundingBox.xmin;
					let xmax = this.boundingBox.xmax;
					let ymin = this.boundingBox.ymin;
					let ymax = this.boundingBox.ymax;
					let line_xmin = Math.min(object.v0.x, object.v1.x);
					let line_xmax = Math.max(object.v0.x, object.v1.x);
					let line_ymin = Math.min(object.v0.y, object.v1.y);
					let line_ymax = Math.max(object.v0.y, object.v1.y);
					if (((xmin <= line_xmax) && (xmax >= line_xmin)) 
						&& ((ymin <= line_ymax) && (ymax >= line_ymin)) ) {
						// 直线被选中
						return true;
					}
				}
			}
			return false;
		} else {
			// 判断鼠标框选区域包围盒是否和图形的包围盒相交
			return this.boundingBox.isCollision(object.getBoundingBox());
		}
	}
}

// 获取直线方程部分的代码
class Line extends Geometry {
	...	
	// 根据直线的端点得到直线方程
	lineEquation() {
		// 求直线方程a*x + b*y + c = 0
		let v0 = this.vstart;
		let v1 = this.vend;
		let a = v1.y - v0.y;
		let b = v0.x - v1.x;
		let c = v1.x * v0.y - v1.y * v0.x;
		// 返回值为直线方程表示的函数
		// 求ax+by+c的值, 值为0表示(x,y)在直线上,
		// 大于0表示在直线的上方,小于0表示在直线的下方
		return function(x, y) {
			return a * x + b * y + c;
		}
	}
}

框选多个图形

当框选多于一个图形时,一律使用包围盒显示选中的效果,之前的代码已经做到了场景中所有被选中的图形对象isSelected状态为true。在绘制到canvas画布之前,需要检查场景中的图形对象的选中状态,并做下面的处理:

  • 如果只有一个图形被选中且选中的图形为直线,则绘制直线被选中的效果;

  • 如果只有一个图形被选中且选中的图形不是直线,则直接绘制选中的矩形包围盒效果;

  • 如果选中了多个图形,则逐个合并每个图形的包围盒,最终绘制合并后的矩形包围盒的效果。


    伪代码如下:

class Scene {
	... 
	render() {
	    let selected_shapes = [];
		for (let i = 0; i < this.children.length; i++) {
		    if (this.children[i].isSelected) {
			    selected_shapes.push(this.children[i]);
		    }
		    // 绘制图形自身
			this.children[i].draw();
		}
		// 绘制选中效果
		if (selected_shapes.length > 0) {
			if (selected_shapes.length == 1 && selected_shapes[0].type == "line") {
				// 只选中了一个图形且为直线
				// 绘制直线选中效果,假定已经实现
				selected_shapes[0].drawLineSeleted();			
			} else {
				// 选中了多个图形或一个非直线的图形,逐个合并每个图形的包围盒
				let merged_box = new BoundingBox();
				for (let i = 0; i < selected_shapes.length; i++) {
					merged_box.merge(selected_shapes[i].getBoundingBox());
				}
				// 绘制合并后的包围盒效果,假定已经实现
				drawBoundingBox(merged_box);
			}
		}
	}
}

总结

我们已经分析了实现鼠标框选图形的方法,以及如何检测一个图形是否被框选的方法。特别是对于直线对象,我们提供了更具直观的方法来判断是否被框选住,而不需要依赖于包围盒。而对于非直线对象,可以通过对比两个包围盒的大小来判断是否重叠。最后,在框选多个图形时,我们可以合并它们对应的包围盒,以便实现框选多个图形的效果。虽然文章中并未涵盖图形绘制的内容,但它是一个基本技能,很多图形学方面的书籍都会有详细的介绍,并且在网络上也有丰富的资料可供查阅。如果你想了解更多的相关信息,建议你参考《WebGL编程指南》,这是一本非常适合初学者阅读的书籍,里面有许多实用的代码实例可供参考。

添加微信即可免费领取下面的自动化测试资料和一份超全的软件测试面试宝典!!!

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript

在操作软件时拖动鼠标出现一个图形如何实现,算法,javascript文章来源地址https://www.toymoban.com/news/detail-777718.html

到了这里,关于全方位讲解:鼠标框选图形功能的技术实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • TDengine3.0全方位安装体验与数据订阅进阶功能实践

    牛晓青 2021年我曾写了一个专栏,介绍 TDengine2.x 的基础实践以及遇到的问题,2022年初又发布了基于EMQX与TDengine的前后端分离项目实践系列文章,前面这些实践中主要用到了 TDengine 作为时序数据库(Time Series Database)能够高效完成海量时序数据的存储与计算功能,关于 TDengin

    2023年04月19日
    浏览(44)
  • 如何全方位了解购房信息?VR全景技术为您解答

    在存量房贷利率下调政策下,房子逐渐回归到居住属性,在对于有购房刚需的客户来说,无疑是一大利好政策,此类客户有着强烈的看房购房需求,那么该如何全方位的了解购房信息呢? 房企通过VR全景展示、3D样板房、VR数字沙盘等功能,将线下房源实景展示在网上,三维、

    2024年02月09日
    浏览(32)
  • 深度解析知网AIGC检测:从理论到实践,全方位探索前沿技术

    大家好,小发猫降ai今天来聊聊深度解析知网AIGC检测:从理论到实践,全方位探索前沿技术,希望能给大家提供一点参考。降ai辅写 以下是针对论文AI辅写率高的情况,提供一些修改建议和技巧,可以借助此类工具: 还有: 标题: \\\"深度解析知网AIGC检测:从理论到实践,全方

    2024年03月16日
    浏览(47)
  • 全方位支持图文和音视频、100+增强功能,Facebook开源数据增强库AugLy

    Facebook 近日开源了数据增强库 AugLy,包含四个子库,每个子库对应不同的模态,每个库遵循相同的接口。支持四种模态:文本、图像、音频和视频。 最近,Facebook 开源了一个新的 Python 库——AugLy,该库旨在帮助 AI 研究人员使用数据增强来评估和改进机器学习模型的可用性。

    2024年02月15日
    浏览(32)
  • 第六篇【传奇开心果系列】Python的自动化办公库技术点案例示例:大学生数据全方位分析挖掘经典案例

    Pandas在大学生数据的分析和挖掘中发挥着重要作用,帮助研究人员和教育工作者更好地理解大学生群体、优化教学管理和提升教育质量。 Pandas库可以用来分析挖掘大学生数据的各各方面,包括但不限于: 学生成绩数据:可以通过Pandas对大学生的成绩数据进行统计分析、可视

    2024年03月15日
    浏览(77)
  • 全方位解析 pinia

    前言 Vue3已经推出很长时间了,它周边的生态也是越来越完善了。之前我们使用Vue2的时候,Vuex可以说是必备的,它作为一个状态管理工具,给我们带来了极大的方便。Vue3推出后,虽然相对于Vue2很多东西都变了,但是核心的东西还是没有变的,比如说状态管理、路由等等。再

    2024年04月25日
    浏览(36)
  • Kotlin全方位-简单解析

    Kotlin是一种现代化的静态类型编程语言,由JetBrains公司开发。它可以在Java虚拟机(JVM)上运行,并且可以与Java代码无缝地进行互操作。Kotlin旨在提供更简洁、更安全、更具表达力和更高效的编程语言。 Android开发:Kotlin被广泛用于Android应用程序的开发。它可以与Java代码互操

    2024年02月10日
    浏览(38)
  • Java——线程睡眠全方位解析

    在 Java 中,让线程休眠的方法有很多,这些方法大致可以分为两类,一类是设置时间,在一段时间后自动唤醒,而另一个类是提供了一对休眠和唤醒的方法,在线程休眠之后,可以在任意时间对线程进行唤醒。 线程睡眠的方法有以下 5 个: Thread.sleep TimeUnit wait Condition LockSu

    2024年02月04日
    浏览(41)
  • 重写RuoYi-Cloud所有功能 整合 SpringCloudAlibaba Dubbo Mybatis-Plus MQ OSS ES Xxl-Job Docker 全方位升级 定期同步

    转载于:https://blog.csdn.net/weixin_40461281/article/details/122837923 RuoYi-Cloud-Plus  微服务通用权限管理系统  重写 RuoYi-Cloud 全方位升级(不兼容原框架) 系统演示: 传送门 分布式集群版本(功能一致) 功能介绍 使用技术 文档地址 特性注意事项 微服务权限管理系统 RuoYi-Cloud-Plus RuoYi-Clo

    2024年02月08日
    浏览(28)
  • 精彩解读:短链接应用全方位探究

    1. 短链接的定义和原理 短链接是一种将长网址转换为短网址的服务,通过简化网址长度,方便用户分享和传播链接。短链接服务通过将长网址映射到短标识符的方式,实现对原始网址的压缩和简化。用户在访问短链接时,系统会将短链接还原为原始长网址,实现跳转到目标网

    2024年04月08日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包